Run any Ollama model or Hugging Face GGUF repo on Runpod Serverless. Works with Runpod model caching, chat and completion requests, streaming, tool calling, and model caching on network volumes.
- Deploy this template from the Runpod Hub
- Pick a model — either
HF_MODELfor a Hugging Face GGUF repo orOLLAMA_MODELfor an Ollama model (see Choosing a model). Leave both empty and you getllama3.2:3b. - Send a request:
curl -X POST "https://api.runpod.ai/v2/<ENDPOINT_ID>/runsync" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"input": {
"messages": [{"role": "user", "content": "Why is the sky blue?"}]
}
}'Important
Hugging Face GGUF Repo (HF_MODEL) is not the same input as Model (OLLAMA_MODEL).
Model(OLLAMA_MODEL) takes an Ollama model name:llama3.2:3b, orhf.co/<repo>:<quant>to use Ollama's own Hugging Face puller.Hugging Face GGUF Repo(HF_MODEL) takes a Hugging Face repo id:unsloth/Qwen3-8B-GGUF. This is the input that works with Runpod's model store.
When both are set, HF_MODEL wins and OLLAMA_MODEL is ignored entirely. The worker log says so at startup.
Full precedence, highest first: input.model (per request) → HF_MODEL + HF_QUANTIZATION → OLLAMA_MODEL → llama3.2:3b when all of them are empty.
| Input | Required | Behaviour |
|---|---|---|
HF_MODEL |
— | Repo id, e.g. unsloth/Qwen3-8B-GGUF. Also accepts hf.co/<org>/<repo>, a full huggingface.co URL, and a trailing :<quant> tag — all normalised to a bare repo id. Must contain .gguf files; a safetensors-only repo fails with an error listing what was found. |
HF_QUANTIZATION |
no | Matched against filenames on -, _, . and / boundaries, case-insensitive. Q4_K_M does not match Q4_K_S, and Q4 does not match Q4_K_M. No match or an ambiguous match → error listing every quantization in the repo. Leave it empty and the smallest GGUF in the repo is used. |
HF_MODEL_FILE |
no | Exact filename, e.g. Qwen3-8B-Q4_K_M.gguf. Overrides HF_QUANTIZATION. |
HF_MODEL = unsloth/Qwen3-8B-GGUF
HF_QUANTIZATION = Q4_K_M
Tip
Use the repo's exact casing. Hugging Face resolves a lowercased repo id with a 307 redirect, so downloads work either way — but Runpod's model store prefills under the canonical casing. The worker falls back to a case-insensitive lookup and logs when it has to, though matching the casing yourself avoids the scan entirely.
If you omit HF_QUANTIZATION and the repo id carries no :<quant> tag, the worker picks Q4_K_M — the same default Ollama's own puller uses, and what model cards assume:
HF_QUANTIZATION not set — defaulting to Q4_K_M in 'unsloth/Qwen3-8B-GGUF': Qwen3-8B-Q4_K_M.gguf (4.7 GiB). Available quantizations: [...]
Only if the repo has no Q4_K_M does it fall back to the smallest file, and it says so. That fallback matters because large repos start very low: unsloth/Qwen3-8B-GGUF's smallest is UD-IQ1_S at 2.3 GB against Q4_K_M's 5.0 GB, and 1-bit output is not usable for most work.
Set HF_QUANTIZATION explicitly whenever you care about the quality/VRAM trade-off.
The model is registered with Ollama as hf/<org>-<repo>:<quant> — for the example above, hf/unsloth-qwen3-8b-gguf:q4_k_m. That name shows up in /api/tags and can be passed as input.model on a request.
Multi-part GGUFs (model-00001-of-00003.gguf) are supported: name any shard in HF_MODEL_FILE, or just set HF_QUANTIZATION, and every shard is loaded.
OLLAMA_MODEL accepts anything ollama pull accepts:
| Source | Example |
|---|---|
| Ollama library | llama3.2:3b, qwen2.5-coder:7b |
| Ollama-style HuggingFace reference | hf.co/prism-ml/Bonsai-27B-gguf:Q1_0 |
For HuggingFace repos referenced this way, specify the quant as a tag (:Q4_K_M, :Q1_0, :F16, ...). Without a tag, Ollama defaults to Q4_K_M and fails if the repo doesn't include one. This path does not use Runpod's model store — use HF_MODEL for that.
Sizing tip: the VRAM needed for weights is roughly the size of the GGUF file plus ~15% overhead for KV cache and activations. Pick a GPU with headroom above that.
Set the endpoint's Model field to the same repo as HF_MODEL. Runpod then pre-downloads it to /runpod-volume/huggingface-cache/hub/models--<org>--<name>/snapshots/<hash>/ before the worker starts, and doesn't bill you for the download. The worker finds it there and registers it with Ollama without fetching anything.
You'll see this in the worker log:
[ModelStore] Using snapshot /runpod-volume/huggingface-cache/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/<hash>
If the model isn't cached, the worker warns and downloads from Hugging Face instead — which is billed cold-start time:
WARN: no cached snapshot for 'unsloth/Qwen3-0.6B-GGUF' under /runpod-volume/huggingface-cache/hub.
Downloading from Hugging Face instead (billed cold-start time).
To use Runpod's model store, set the endpoint's Model field to 'unsloth/Qwen3-0.6B-GGUF'.
Caveats worth knowing before you pick a repo:
- Every quantization in the repo is downloaded. Runpod can't select one, so a repo with a dozen quants caches all of them — for a large model that can be hundreds of GB of prefill. Prefer repos that ship a single quant when the model is large.
- One cached model per endpoint.
- Gated models need two separate tokens. The one for the pre-download goes in the console's Model Caching settings; the container's
HF_TOKENis only used when the worker has to fetch the model itself. - The cache path is
/runpod-volume/huggingface-cache, not/runpod/model-store/.
| Field | Type | Required | Description |
|---|---|---|---|
messages |
array | one of messages/prompt |
Chat messages, OpenAI format |
prompt |
string | one of messages/prompt |
Raw completion prompt |
model |
string | no | Overrides the endpoint's configured model; pulled on demand if missing |
stream |
bool | no | Stream response chunks (default false) |
options |
object | no | Ollama options (temperature, num_ctx, top_p, ...) |
tools |
array | no | Tool definitions for models that support tool calling |
format |
string/object | no | "json" or a JSON schema for structured output |
system |
string | no | System prompt (completion mode) |
template |
string | no | Override the model's chat template for this request |
keep_alive |
string/int | no | How long to keep the model loaded (default: forever) |
{
"input": {
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Write a haiku about GPUs."}
],
"options": {"temperature": 0.7}
}
}{
"input": {
"prompt": "The capital of France is",
"options": {"num_predict": 10}
}
}Set "stream": true and use /run + /stream/<JOB_ID>, or /runsync to receive the aggregated stream. Each chunk is a JSON line in Ollama's streaming format.
Non-streaming responses return Ollama's native response object:
{
"model": "llama3.2:3b",
"message": {"role": "assistant", "content": "..."},
"done": true,
"eval_count": 42,
"eval_duration": 1234567890
}| Variable | Default | Description |
|---|---|---|
HF_MODEL |
— | Hugging Face GGUF repo id. Takes precedence over OLLAMA_MODEL. |
HF_QUANTIZATION |
smallest in repo | Which GGUF quantization to load (Q4_K_M, Q8_0, IQ4_XS, ...) |
HF_MODEL_FILE |
— | Exact .gguf filename; overrides HF_QUANTIZATION |
HF_TOKEN |
— | Hugging Face token for gated/private repos, used only when the worker downloads the model itself |
OLLAMA_MODEL |
— | Ollama model pulled at worker startup; ignored when HF_MODEL is set |
| — | llama3.2:3b |
Used when both HF_MODEL and OLLAMA_MODEL are empty |
OLLAMA_MODELS |
auto | Ollama's model store. Defaults to /runpod-volume/ollama/models when that is writable, else /root/.ollama/models |
RUNPOD_MODEL_CACHE_DIR |
/runpod-volume/huggingface-cache/hub |
Where Runpod's model store mounts its cache |
OLLAMA_TEMPLATE |
— | Chat template override used when registering a Hugging Face GGUF |
OLLAMA_KEEP_ALIVE |
-1 (forever) |
How long models stay loaded in VRAM |
| Configuration | Ollama store | GGUF acquisition | Disk needed |
|---|---|---|---|
| Writable network volume (recommended) | /runpod-volume/ollama/models |
hard-linked from the cache, no copy | image only, plus ~3× model on the volume |
| Model store, no network volume | /root/.ollama/models |
copied (different filesystem) | ~3× model + ~5 GB image |
| Neither | /root/.ollama/models |
downloaded, then hard-linked | ~3× model + ~5 GB image |
Important
Budget ~3× the model size at peak. Ollama re-writes a GGUF when registering it (validating GGUF model in the log), so at peak the blob store holds the hard-linked original, a COPY temp, and the final blob at once. Measured on a real import: 2.49× sampled, up to 3× transient, settling to 2× afterwards.
A 20 GiB model therefore needs ~64 GiB free, which is why the default container disk is 100 GB. The worker now fails fast with the required and available figures instead of letting Ollama die with an opaque 500 after writing 20 GiB.
Where possible the worker hard-links the GGUF into Ollama's blob store rather than uploading it through localhost HTTP, which removes one full copy from the peak.
If you need 1× disk instead, skip the Hugging Face path and use Ollama's native puller — OLLAMA_MODEL=hf.co/<org>/<repo>:<quant> fetches pre-built layers with no re-write. You lose model-store caching in exchange.
A writable network volume is what makes cold starts fast: the registered model persists there, so later workers skip the download, the hashing and the registration entirely.
Everything Ollama pulls lands in OLLAMA_MODELS, not just model-store models — plain Ollama library pulls (llama3.2:3b) and gated hf.co/... pulls are cached on the volume too.
The worker can't tell a network volume from a local volume disk from a model-store mount: all three appear at /runpod-volume. It reports what it can actually observe at startup:
Volume: /runpod-volume present and writable — network volume or local volume disk (indistinguishable from here). Models cached here only survive across workers if it is a network volume.
Model store: /runpod-volume/huggingface-cache/hub present (Runpod pre-downloaded cache)
Ollama store: /runpod-volume/ollama/models
If /runpod-volume exists but isn't writable — a model-store mount with no volume attached — both caches fall back to container disk and the log says so.
- GGUF only. Safetensors-only Hugging Face repos are rejected with an error — use a GGUF conversion of the model.
- Multimodal projectors are skipped. A repo shipping
mmproj-*.ggufalongside the model has that file excluded from automatic selection, since it holds no language-model weights and Ollama rejects every request against it. Vision input is therefore not wired up; the language model is served text-only. - One cached model per endpoint, and the model store downloads every quantization in the repo.
- A GGUF with no embedded chat template produces malformed chat output. Set
OLLAMA_TEMPLATE, or passtemplateper request. The worker logs a warning when it detects this. - VRAM is not enforced, only reported. If the weights don't fit, the worker warns and Ollama offloads the remainder to CPU — the model still answers, much more slowly. Pick a larger GPU, a smaller
HF_QUANTIZATION, or a lowerOLLAMA_CONTEXT_LENGTH.
docker build -t ollama-worker .
# Ollama model
docker run --gpus all -e OLLAMA_MODEL=llama3.2:1b ollama-worker
# Hugging Face GGUF repo
docker run --gpus all -e HF_MODEL=unsloth/Qwen3-0.6B-GGUF -e HF_QUANTIZATION=Q4_K_M ollama-workerWithout a GPU, Ollama falls back to CPU inference — slow, but enough to smoke-test the handler with a small model. You can also test the handler directly against test_input.json:
python3 handler.py --rp_serve_api # requires a local ollama serveThe model-selection and naming logic is pure and covered by unit tests that need no GPU, no network and no Ollama:
python3 -m pytest test_selection.py -v