A step-by-step guide to integrating ONNX models into a Quarto blog post.
ai
quarto
meta
Learn how to use ONNX models in your Quarto blog with this comprehensive guide.
Author
Shon Czinner
Published
May 8, 2026
To get ONNX models working in your quarto blog, first you’ll need to have a model. In my case I’m using one from pytorch. This requires installing pytorch for the model, onnxscript and onnx for exporting the model, and onnxruntime for loading the model back into python to test it out.
To get the model into your quarto blog, you’ll need the model, and the files to load it and display/interact with it.
Make sure you list everything as a resource in the YAML block at the top of the post.
import torchimport torch.nn as nnimport onnxruntime as ortimport numpy as npfrom IPython.display import Markdown,HTMLclass MyModel(nn.Module):def__init__(self):super(MyModel, self).__init__()self.fc1 = nn.Linear(10, 20)self.relu = nn.ReLU()self.fc2 = nn.Linear(20, 1)def forward(self, x): x =self.fc1(x) x =self.relu(x) x =self.fc2(x)return x
Export the model
Code
model = MyModel() model.eval() # Set the model to evaluation modeINPUT_NAME ="input_features"OUTPUT_NAME ="prediction"INPUT_SHAPE = (1, 10)torch.onnx.export(model, torch.randn(*INPUT_SHAPE),"model.onnx", input_names=[INPUT_NAME], output_names=[OUTPUT_NAME], opset_version=20);
[torch.onnx] Obtain model graph for `MyModel([...]` with `torch.export.export(..., strict=False)`...
[torch.onnx] Obtain model graph for `MyModel([...]` with `torch.export.export(..., strict=False)`... ✅
[torch.onnx] Run decompositions...
[torch.onnx] Run decompositions... ✅
[torch.onnx] Translate the graph into ONNX...
[torch.onnx] Translate the graph into ONNX... ✅
[torch.onnx] Optimize the ONNX graph...
[torch.onnx] Optimize the ONNX graph... ✅
Test Model in Python
Code
def to_numpy(tensor):return tensor.detach().cpu().numpy()ort_session = ort.InferenceSession("model.onnx")# Create a test input using the same shape we defined earlierx = torch.randn(*INPUT_SHAPE)# Initialize the sessionort_session = ort.InferenceSession("model.onnx")# Use the explicit name we assigned during export# This replaces: ort_session.get_inputs()[0].nameort_inputs = {INPUT_NAME: to_numpy(x)}# Run the modelort_outs = ort_session.run(None, ort_inputs)print(f"ONNX Runtime output ({OUTPUT_NAME}):", ort_outs)
withopen("incrementbutton.html", "r") as f: raw_code = f.read()# Using Markdown to wrap the code in a syntax-highlighted blockdisplay(Markdown(f"```html\n{raw_code}\n```"))
withopen("loadmodel.html", "r") as f: model_code = f.read()# Using Markdown to wrap the code in a syntax-highlighted blockdisplay(Markdown(f"```html\n{model_code}\n```"))
<div id="ai-widget" style="padding: 20px; border: 1px solid #ddd; border-radius: 10px; background: #fff; max-width: 400px; margin: 10px auto; font-family: system-ui, -apple-system, sans-serif;"><h4 style="margin-top: 0;">MyModel Inference</h4><div id="status-light" style="font-size: 0.85em; margin-bottom: 15px;"><span style="height: 10px; width: 10px; background-color: #bbb; border-radius: 50%; display: inline-block; margin-right: 5px;"></span> Status: <span id="status-text">Initializing...</span></div><div style="display: flex; gap: 10px; flex-direction: column;"><button id="run-inference" style="padding: 10px; cursor: pointer; background: #007bff; color: white; border: none; border-radius: 5px;" disabled> Predict from Random Input</button><div id="prediction-output" style="padding: 10px; background: #f8f9fa; border-radius: 5px; font-weight: bold; text-align: center; min-height: 20px;"> --</div></div></div><script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.0/dist/ort.min.js"></script><script type="module">// 1. Version and WASM Configuration ort.env.wasm.wasmPaths="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.0/dist/";// 2. Model Configuration (Matches your Python Export variables)const CONFIG = {modelPath:'./model.onnx',externalDataPath:'model.onnx.data',inputName:'input_features',// Matches INPUT_NAME in PythonoutputName:'prediction',// Matches OUTPUT_NAME in PythoninputShape: [1,10],// Matches INPUT_SHAPE in PythoninputSize:10// Total elements (1 * 10) };let session;const statusText =document.getElementById('status-text');const statusLight =document.getElementById('status-light').children[0];const predictBtn =document.getElementById('run-inference');const outputDiv =document.getElementById('prediction-output');asyncfunctioninit() {try {// Using the External Data logic with variables session =await ort.InferenceSession.create(CONFIG.modelPath, {executionProviders: ['wasm'],externalData: [ {path: CONFIG.externalDataPath,data:`./${CONFIG.externalDataPath}` } ] }); statusText.innerText="Ready"; statusLight.style.backgroundColor="#28a745"; predictBtn.disabled=false; } catch (e) { statusText.innerText="Error loading model"; statusLight.style.backgroundColor="#dc3545";console.error(e); } }asyncfunctionrun() {try {// Use CONFIG.inputSize instead of a magic number 10const inputData =Float32Array.from( { length: CONFIG.inputSize }, () =>Math.random() *2-1 );// Use CONFIG.inputShapeconst inputTensor =new ort.Tensor('float32', inputData, CONFIG.inputShape);// Use CONFIG.inputName and CONFIG.outputNameconst feeds = { [CONFIG.inputName]: inputTensor };const results =await session.run(feeds);const resultValue = results[CONFIG.outputName].data[0]; outputDiv.innerText=`Result: ${resultValue.toFixed(6)}`; } catch (e) { outputDiv.innerText="Inference failed";console.error(e); } } predictBtn.onclick= run;init();</script>