From Notebook to App: Wrap a Model in a UI Anyone Can Use
Build an AI app in PyTorch by wrapping a pretrained model in a Gradio app — a beginner-friendly capstone that turns a model into a real, shareable little tool.
Series: Practical PyTorch: Running Models — Vision — Part 5 of 5
A model running in a notebook is a private victory. You typed a cell, got a result, felt clever — and nobody else can see it. This post closes the gap between you ran a model and you built a thing people can use. We’re going to take a pretrained model, wrap it in a plain Python function, and put a little web UI on it with Gradio — running right inside the notebook, with a public link you can text to a friend.
This is the vision capstone — the payoff for the whole arc from convolution to ResNet. By the end you’ll have a real, shareable image-classification app, and you’ll see how the vision pieces snap together.
Open the companion notebook in ColabA model is already a function
Here’s the reframe that makes the whole thing easy: a model is just a function. Input goes in, output comes out. That’s been the shape of everything in this series: load a pretrained thing, hand it an input, read the result. An app is nothing more than that same function with a UI bolted on. The model does the hard part. Our job is just to give it a way in and a way out.
So we start by writing the function. Let’s use an image classifier — point it at a photo, get back the most likely labels. We’ll lean on pipeline() from the Hugging Face pipelines post because it does the preprocessing for us, and reach for ResNet-50 — the same CNN family you read up close in Part 2:
from transformers import pipeline
classifier = pipeline("image-classification", model="microsoft/resnet-50")
def classify(img):
results = classifier(img)
# Turn the list of {label, score} into the {label: score} a UI wants.
return {r["label"]: r["score"] for r in results}
That’s it. classify takes an image and returns a dictionary of labels and confidence scores. You could call it in a cell and print the result, but a dictionary isn’t something you hand a non-engineer. It needs a face.
Putting a UI on it with Gradio
Gradio is a library that builds a web interface around a Python function. You tell it what kind of input your function takes and what kind of output it returns, and it generates the page — file upload, buttons, result display, all of it. No HTML, no JavaScript, no front-end anything.
import gradio as gr
demo = gr.Interface(
fn=classify,
inputs=gr.Image(type="pil"),
outputs=gr.Label(num_top_classes=3),
title="What's in this picture?",
description="Upload an image and the model guesses what it sees.",
)
demo.launch(share=True)
Three arguments carry the weight. fn is your function — the model. inputs=gr.Image(type="pil") tells Gradio to render an image uploader and hand your function a PIL image (which is exactly what the pipeline wants — the types line up on purpose). outputs=gr.Label(num_top_classes=3) says “the function returns label scores; draw them as a tidy top-3 bar chart.”
Run that cell and a working app appears inside the notebook: drag in a photo, watch the bars fill in. Everything you learned about inputs and outputs back in Part 4 is quietly doing its job here — the uploader produces a PIL image, the pipeline preprocesses it, the model scores it, and Gradio renders the result. You just don’t have to think about any of it.
The public link

The share link tunnels from a friend’s browser back to the Gradio app in your notebook.
Notice share=True. Without it, the app only exists in your notebook. With it, Gradio spins up a temporary public URL — something like https://a1b2c3.gradio.live — that tunnels back to your running notebook. Send it to someone and they can use your app in their browser, no install, no Colab account.
That link is the punchline of the whole series. You started not knowing what a tensor was; you’re ending with a URL you can paste into a chat and say “here, try this.” The link lives as long as your notebook is running (roughly up to 72 hours), then it’s gone, which makes it perfect for a quick demo and useless for production. But for “let me show you what I built,” it’s exactly the right tool, and there’s nothing else to install or configure to get it.
A taste of another modality: making images
You’ve been running models that read and classify. To hint at how much else is one from_pretrained away, let’s flip to a model that creates — text-to-image generation, via Hugging Face’s diffusers library. Same muscle memory, brand-new superpower:
from diffusers import AutoPipelineForText2Image
import torch
pipe = AutoPipelineForText2Image.from_pretrained(
"stabilityai/sdxl-turbo",
dtype=torch.float16,
).to("cuda")
image = pipe("a watercolor fox in a misty forest",
num_inference_steps=2,
guidance_scale=0.0).images[0]
image # in a notebook, this just displays the picture
Read it as the same pattern you already know: from_pretrained loads the model, you hand it an input (a text prompt this time), you read the output (a PIL image). sdxl-turbo is a “turbo” model tuned to produce a picture in just a couple of steps, which is why we set num_inference_steps=2 and guidance_scale=0.0 — those are simply its recommended fast-mode settings. A regular image model would want more steps and a higher guidance value, and would take correspondingly longer. The point isn’t this one model; it’s that text, audio, and image models all answer to the same few lines.
And here’s the lovely part: wrap that in a gr.Interface with inputs="text" and outputs="image", and you’ve built an image generator with a UI. The recipe never changed — only the model did.
Gotchas
A few things that catch people on their first app:
- The share link is temporary, not a deployment.
share=Truegives you a demo URL that dies when your notebook stops (and within ~72 hours regardless). For something permanent, look at Hugging Face Spaces — it hosts Gradio apps for free. The share link is for showing, not shipping. diffusersreally wants a GPU. Image generation is heavy. On a CPU it can take minutes per image, if it finishes at all. Flip on the GPU (Runtime → Change runtime type → GPU) before you run thediffuserscell, and keep the.to("cuda")— on a CPU-only runtime that line will error, which is your sign the GPU isn’t on.- Match the input type to what the model expects.
gr.Image(type="pil")hands your function a PIL image;type="numpy"hands it a NumPy array instead. Theimage-classificationpipeline is happy with PIL, so we used PIL. If your function chokes on its input, this is the first thing to check. - The first run is slow, then it’s fast. The first call downloads model weights (sometimes gigabytes). After that they’re cached for the session, and the app responds quickly. Don’t mistake the one-time download for the app being slow.
float16is a GPU thing.dtype=torch.float16halves memory and speeds things up, but half-precision is a GPU trick (see the Hub and bigger models post). On CPU, drop it and let the model run in the default precision.- Launching twice. Re-running
launch()in the same session can complain that a port is in use. If it grumbles, restart the runtime (Runtime → Restart) and run from the top — the cleanest reset there is.
What’s next
You’ve taken the vision arc all the way: build the layers, read a real network, run it on a photo, and now wrap it in an app with a link you can share. That’s a complete skill — load a classifier, feed it the right input, read its output, and put a face on it someone else can use.
But every model in that arc has one thing in common: from LeNet to ResNet, they’re all built to see. The models that read text, translate, answer questions, and power the chatbots you’ve actually talked to belong to a different family — the transformer. Before we get there, though, there’s a quieter question to settle: a model only ever does arithmetic, so how does a word become something it can compute with at all?
Next up, the Language series begins: From Words to Vectors: How a Model Turns Text into Numbers, the step that has to happen before any model can read a word.
Target keyword(s): build an AI app pytorch, gradio app, run pretrained models pytorch.
Comments