Skip to content
OpenSmartRoute
Documentation
Hosted platform

Model server

Host open-weight models on your own hardware behind the Ollama and OpenAI APIs: pulls, residency with a fit check, admission queue, metrics; CPU and CUDA.

Hosted platform 6 min read

The OpenSmartRoute model server (osr-models) hosts open-weight models on your own hardware - a laptop, a VM, a container, a GPU node - behind the APIs clients already speak. It is what the platform's self-hosted targets run on, and it works on its own for anyone who wants a model server with a fit check, an admission queue and metrics instead of a cold-load lottery.

APIsOllama (/api/generate, /api/chat, /api/embed, /api/tags, /api/show, /api/ps, /api/pull, /api/create, /api/copy, /api/delete, /api/blobs, /api/version), OpenAI (/v1/models, /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/responses, /v1/rerank), Anthropic Messages (/v1/messages), control (/models, /models/load, /models/unload), operations (/healthz, /readyz, /metrics)
Enginellama.cpp llama-server, one process per loaded model, on CPU or NVIDIA CUDA; GGUF weights
Sourcesollama.com (qwen3.5:4b), Hugging Face GGUF repositories (hf.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF:Q4_K_M, or a file inside a repository), local .gguf files
Storecontent-addressed blobs and manifests in Ollama's on-disk layout: a store Ollama filled is served as-is, so switching costs no download
Residencya model is loaded only when its footprint (weights + KV cache x context x slots + overhead) fits the memory budget after evicting idle models; a busy model is never evicted; keep_alive pins, expires or unloads
Admissionparallel slots per model, a bounded queue with a deadline, 503 + Retry-After past it (the platform re-routes to a cloud tier on that answer instead of waiting)
MetricsPrometheus: requests, latency, tokens (prompt / cached / completion), slots, queue, resident bytes, loads, evictions, refusals - plus the engine's own series per model

Run it

Docker (CPU)

docker run -d --name models -p 11434:11434 -v osr-models:/root/.ollama \
  -e OSR_OLLAMA_MODELS="qwen3.5:4b qwen3-embedding:4b" -e OSR_OLLAMA_PRELOAD=1 \
  opensmartroute/models
curl http://localhost:11434/api/chat -d '{"model":"qwen3.5:4b","messages":[{"role":"user","content":"Hello"}],"stream":false}'

Docker (NVIDIA GPU) - the cuda image puts every layer on the GPU (OSR_MODELS_GPU_LAYERS to split):

docker run -d --gpus all -p 11434:11434 -v osr-models:/root/.ollama opensmartroute/models:cuda

The command line - the same binary inside the container manages the store and talks to the server:

docker exec models osr-models pull hf.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF:Q4_K_M
docker exec models osr-models run hf.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF:Q4_K_M "Why is the sky blue?"
docker exec models osr-models ps

Outside a container the package runs next to any llama-server binary from a llama.cpp release (point OSR_MODELS_LLAMA_SERVER at it or put it on the PATH) and osr-models serve starts the server.

The server listens on Ollama's port (11434) and reads Ollama's variables, so an Ollama client, probe or start-up script works unchanged; the container even answers to ollama list, ollama pull, ollama run.

Models

CommandWhat it does
osr-models pull qwen3.5:4bfrom the ollama.com library (any registry speaking its protocol: host/namespace/name:tag)
osr-models pull hf.co/<org>/<repo>:<quant>the GGUF of that quantisation from a Hugging Face repository (Q4_K_M when no quantisation is named); a multimodal projector (mmproj-*.gguf) in the same folder comes along; HF_TOKEN for gated repositories
osr-models pull hf.co/<org>/<repo>/<path>.ggufone file of a repository
osr-models pull ./model.gguf --name my-modelimport a local file
osr-models pull qwen3.5:4b=hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_Mpull from one source, store under another name: every API, show and the platform's mapping see qwen3.5:4b while the weights come from the Hugging Face repository. Use it where the ollama.com blob is Ollama's own conversion that a standard llama.cpp cannot load (gpt-oss:20b, glm-4.7-flash, glm-ocr, translategemma:4b, qwen3.5:4b). POST /api/pull takes the same form in model, or the source in a source key
osr-models list / ps / show MODEL / rm MODEL / stop MODELthe store, what is in memory, a model's details and metadata, delete, unload
POST /api/createa new model from an existing one plus a system prompt, parameters (temperature, stop, num_ctx, ...), a template or adapters - the same body Ollama takes

Pulls resume after an interruption, verify the digest, and are shared between replicas that mount the same store through a lock in it. The deployment decides which sources are allowed (OSR_MODELS_SOURCES, default ollama.com and Hugging Face).

A model's card is what POST /api/show returns: details (family, parameter count, quantisation), model_info (the GGUF metadata - layer count, heads, trained context), capabilities (completion, tools, thinking, vision, embedding, read off the weights), system, parameters, template, license. The system prompt and parameters of a card apply to every request that does not set its own.

Requests

Everything the Ollama and OpenAI dialects define for chat works on both paths: streaming, tools and tool calls, images (multimodal models), format: "json" or a JSON schema (response_format on /v1), think levels (reasoning_effort on /v1 - none switches a thinking model's reasoning off so it answers inside a small max_tokens), keep_alive, the Ollama options (temperature, top_p, top_k, min_p, num_predict, seed, stop, penalties). Answers carry Ollama's statistics (prompt_eval_count, prompt_eval_cached_count, eval_count, durations in nanoseconds, load_duration on the request that loaded the model) and the OpenAI usage with cached_tokens. The prompt cache is on: a repeated system prompt or conversation prefix is not evaluated again.

An empty request loads a model ({"model": "...", "messages": []}), keep_alive: 0 with it unloads; GET /api/ps lists what is resident with its slots, queue and expires_at (0001-01-01 = pinned).

Settings

Every setting is an environment variable; the OSR_MODELS_* name wins over the Ollama name it mirrors.

SettingOllama nameDefaultMeaning
OSR_MODELS_HOSTOLLAMA_HOST127.0.0.1:11434listen address
OSR_MODELS_STOREOLLAMA_MODELS~/.ollama/models if present, else ~/.osr/modelsthe store directory (/root/.ollama/models in the image)
OSR_MODELS_PRELOADOSR_OLLAMA_MODELS-models pulled at start (name or name=source; a name=source entry whose stored weights came from anywhere else - an earlier ollama.com pull of the same tag - is pulled again from the source); with OSR_OLLAMA_PRELOAD=1 loaded and pinned too
OSR_MODELS_CONTEXTOLLAMA_CONTEXT_LENGTH8192context per slot (clamped to the model's trained window and its num_ctx)
OSR_MODELS_PARALLELOLLAMA_NUM_PARALLEL1generations one model decodes at once (embedding models always 1)
OSR_MODELS_KV_CACHEOLLAMA_KV_CACHE_TYPEf16KV cache precision (q8_0 halves it; q4_0 quarters it)
OSR_MODELS_MAX_LOADEDOLLAMA_MAX_LOADED_MODELS0 = as many as fitcap on resident models
OSR_MODELS_KEEP_ALIVEOLLAMA_KEEP_ALIVE5midle time before a model is unloaded; -1 = never
OSR_MODELS_QUEUE_MAXOLLAMA_MAX_QUEUE4 x parallelrequests that may wait for a slot before 503
OSR_MODELS_QUEUE_TIMEOUT-20slongest wait for a slot
OSR_MODELS_LOAD_TIMEOUTOLLAMA_LOAD_TIMEOUT20mlongest load (a network share is slow)
OSR_MODELS_REQUEST_TIMEOUT-15mlongest silence from an engine during one request (a thinking model on a CPU can be quiet for minutes)
OSR_MODELS_DEVICE-autocpu, cuda, or detect
OSR_MODELS_GPU_LAYERS-alllayers offloaded to the GPU
OSR_MODELS_THREADS-engine defaultCPU threads per engine
OSR_MODELS_FLASH_ATTENTIONOLLAMA_FLASH_ATTENTIONonflash attention (forced on for a quantised KV cache)
OSR_MODELS_MEMORY-detected (cgroup limit or physical memory)the memory budget before headroom, e.g. 28Gi
OSR_MODELS_HEADROOM-0.12share of memory kept for the OS and page cache
OSR_MODELS_SOURCES-registry.ollama.ai huggingface.co hf.coregistries pulls may use
OSR_MODELS_HF_TOKENHF_TOKEN-Hugging Face token for gated repositories
OSR_MODELS_TOKEN--bearer token required off the loopback (/healthz, /readyz, /api/version stay open)
OSR_MODELS_SLEEP_IDLE-offseconds of idleness after which an engine releases its memory but keeps its process
OSR_MODELS_LLAMA_SERVER-PATHthe engine binary

Sizing is arithmetic the SDK does for you: osr capacity show <model> --memory 28Gi --cores 4 prints a model's footprint, whether it fits and the expected tokens/s; osr capacity plan <models...> --memory 28Gi packs a set onto hosts; osr capacity ps --url http://<server>:11434 lists what the server holds right now.

With the platform

The platform treats a model server as an ollama provider: its base URL (http://<server>:11434/v1) in OSR_PLATFORM_PROVIDERS or the operator console, targets mapped to model names, concurrency equal to the slots the server offers. The provider check reads /api/ps for residency, the keep-warm pass loads mapped models that are not resident, and the calibrator turns the server's measured latency into what the router ranks on. The Docker Compose stacks run the model server as the llm profile; the Azure template's self-hosted model apps run it when OSR_OLLAMA_IMAGE names the model server image (the Ollama image stays the default) - same volume, same start-up, same probes.

Operations

  • GET /healthz - engine version, device, instances, memory budget and use, store usage, preload progress and errors; GET /readyz - 200 once the preload list is pulled (and loaded when asked), 503 before.
  • GET /metrics - osr_models_requests_total, osr_models_request_seconds, osr_models_tokens_total, osr_models_slots_busy, osr_models_queue, osr_models_resident_bytes, osr_models_loads_total, osr_models_evictions_total, osr_models_refused_total, osr_models_load_seconds, and the engine's llamacpp:* series labelled by model.
  • GET /models - every model in the store with its status (unloaded, loading, loaded), footprint and the engine arguments; GET /models/<id>/logs - the engine's last log lines when a load fails.
  • A load that does not fit answers 507 with the budget and what to change (shorter context, q8_0 KV cache, a smaller quantisation). A saturated model answers 503 with Retry-After.
  • Prompts and answers are never written to disk; the engine processes bind to the loopback only.

Something wrong or out of date on this page? Tell us - the page is named for you, so a one-line note is enough.