Appearance
Local API server
Use the local API server to expose supported mere.run models through an OpenAI-compatible endpoint. Point an existing client, your editor, or Open WebUI at the loopback endpoint to keep model weights and prompts on the serving machine.
Commands
| Command | What it does |
|---|---|
mere.run api serve | Start an OpenAI-compatible API server for supported local text, image, vision, and audio models. |
mere.run model runtime get | Print typed API runtime settings for a managed model. |
mere.run model runtime set | Update typed API runtime settings for a managed model. |
mere.run status | Show local server, loaded model, and installed model status. |
macOS Serving & Agents console
MereRun Studio exposes this control plane as a top-level Serving & Agents destination (or Shift-Command-S). It is an operational client of the public CLI and HTTP contracts, not a second runtime. The console provides:
- API preflight, app-owned start, stop, restart, and reconnection to a server started outside Studio
- Explicit loopback and local area network (LAN) authentication safety; Studio blocks a non-loopback app-owned launch until an API key is configured
- Live text, image, speech, transcription, and embedding sidecar pools, readiness, activity, queues, pinning, TTLs, aliases, model settings, and text-model load/unload controls
- Unified-memory guard pressure, process CPU, Metal allocation and recommended working set, and macOS thermal/power state where available
- Observed request totals, failures, tokens, latency, throughput, prefix reuse, decode batching, and MTP counters
- Typed Pi readiness, install, configure, and start flows, plus copyable OpenAI software development kit (SDK), curl, BYOA, and Open WebUI connection setup
- A sanitized lifecycle and activity feed that excludes prompts, messages, media, and generated content
Server launches and agent setup sessions use the normal Studio Library lifecycle. API keys still cross the process boundary only through MERERUN_API_KEY.
HTTP routes
POST /v1/chat/completionsPOST /v1/embeddingsPOST /v1/images/generationsPOST /v1/images/editsPOST /v1/vision/geometryPOST /v1/vision/geometry/multiviewPOST /v1/vision/image-to-3dPOST /v1/vision/image-to-3d-multiviewPOST /v1/vision/depth-videoPOST /v1/audio/speechPOST /v1/audio/transcriptions
What it is for
Use the server for repeated requests so the model stays loaded. It supports:
- Editor tooling and local automation
- Retrieval-augmented generation (RAG) and knowledge-base embeddings over private documents
- Image generation, speech, and transcription from an existing OpenAI client
- Runtime evaluation over HTTP before a permanent integration
It is not a hosted service or a relay layer. The server binds to loopback by default. For remote jobs, prefer portable workflows; when you deliberately bind the API server to a non-loopback address, configure its bearer token and rate limit first.
The server keeps its existing in-process request admission, resident model pool, prefix caches, and continuous batching. In addition, the server process holds a weighted machine-wide reservation shared with other mere.run processes. This prevents a terminal, Studio, Raycast, or a second server from blindly loading incompatible working sets while allowing measured concurrency inside the resident pool. Standard servers consume two RAM-scaled permits; DeepSeek V4 Flash servers run with exclusive machine admission.
Runtime entrypoints
CLI
Sources/MereRunCLI/Commands/APIServeCommand.swift
Supporting stack
Sources/MereRunCLI/Support/Hummingbirdpackage dependency declared inPackage.swift
Example
bash
swift run mere.run api serve --engine text-chat-gemma4For Gemma 4 12B vision chat:
bash
swift run mere.run model pull vision-chat-gemma4-12b
swift run mere.run api serve \
--engine text-chat-gemma4 \
--model vision-chat-gemma4-12bFor Muse Glimmer multimodal agent chat (explicit 21.38 GB Q4 target plus the 5.54 GB DFlash2 assistant):
bash
swift run mere.run model pull vision-chat-muse-glimmer-30b --accept-model-license
swift run mere.run api serve \
--engine text-chat-muse-glimmer \
--model vision-chat-muse-glimmer-30bThis engine accepts OpenAI image content parts and function tools. It exposes reasoning effort, but does not claim constrained json_object decoding. The managed pull also installs z-lab's pinned DFlash2 assistant. Requests for at least 32 output tokens use target-verified speculation and return to target-only decode when draft acceptance is poor.
The native DFlash2 companion can be refreshed independently:
bash
swift run mere.run model pull vision-chat-muse-glimmer-30b-dflash2
swift run mere.run api serve \
--model vision-chat-muse-glimmer-30bFor NVIDIA Nemotron 3.5 Lightning with its automatically installed DSpark companion:
bash
swift run mere.run model pull text-chat-nemotron-35-lightning
swift run mere.run api serve \
--engine text-chat-nemotron-h \
--model text-chat-nemotron-35-lightningThe native engine serves text, tools, and stop sequences. Requests with at least 16 output tokens probe DSpark at NVIDIA's recommended three-token width; low draft acceptance automatically routes the rest of that request through the serial target.
For the compact LiquidAI LFM2.5 2.6B MLX 4-bit model:
bash
swift run mere.run model pull text-chat-lfm25-2.6b-4bit --accept-model-license
swift run mere.run api serve --engine text-chat-lfm2 --model text-chat-lfm25-2.6b-4bitFor LFM2.5-VL image content parts, select the pinned vision model explicitly:
bash
swift run mere.run model pull vision-chat-lfm25-3b-8bit --accept-model-license
swift run mere.run api serve --engine text-chat-lfm2 --model vision-chat-lfm25-3b-8bitFor the opt-in Laguna S 2.1 target with its automatically installed DFlash companion:
bash
swift run mere.run model pull text-chat-laguna-s-2-1
swift run mere.run api serve \
--engine text-chat-laguna \
--max-active-requests 2Laguna uses its validated 1/1/20/0.02 temperature, top-p, top-k, and min-p recipe when a request does not override sampling. The server automatically routes output budgets of at least 32 tokens through DFlash and falls back losslessly when measured draft acceptance is poor.
To serve the smaller experimental XS checkpoint instead, select it explicitly; the server will not attach the S DFlash companion:
bash
swift run mere.run model pull text-chat-laguna-xs-2-1
swift run mere.run api serve \
--engine text-chat-laguna \
--model text-chat-laguna-xs-2-1In another terminal, confirm that the server is reachable and which model it reports:
bash
swift run mere.run statusNative embeddings use the same OpenAI-compatible base URL:
bash
swift run mere.run model pull text-embed-qwen3-0.6b
curl http://127.0.0.1:8080/v1/embeddings \
-H "Content-Type: application/json" \
--data '{
"model": "text-embed-qwen3-0.6b",
"input": ["mere.run native embeddings", "local RAG"]
}'Image generation and editing return base64 PNG JSON by default:
bash
swift run mere.run model pull image-zimage-nano
curl http://127.0.0.1:8080/v1/images/generations \
-H "Content-Type: application/json" \
--data '{
"model": "image-zimage-nano",
"prompt": "a compact local AI workstation in morning light",
"size": "1024x1024"
}'
curl http://127.0.0.1:8080/v1/images/edits \
-F model=image-zimage-nano \
-F prompt="make the workstation dusk-lit while preserving the layout" \
-F [email protected]Audio endpoints use the same base URL:
bash
swift run mere.run model pull speech-tts-qwen3-nano
swift run mere.run model pull speech-asr-parakeet
curl http://127.0.0.1:8080/v1/audio/speech \
-H "Content-Type: application/json" \
--output speech.wav \
--data '{
"model": "speech-tts-qwen3-nano",
"input": "mere.run is serving native audio.",
"voice": "nova",
"response_format": "wav"
}'
curl http://127.0.0.1:8080/v1/audio/transcriptions \
-F model=speech-asr-parakeet \
-F response_format=json \
-F [email protected]Optional per-model defaults live with the active model store:
bash
swift run mere.run model runtime set text-chat-gemma4 \
--alias chat-default \
--pinned \
--ttl-seconds 3600 \
--max-context-tokens 8192 \
--max-tokens 1024 \
--temperature 0.6 \
--top-p 0.9 \
--min-p 0.05For a network-exposed server, configure an API key and a rate limit:
bash
export MERERUN_API_KEY=example-api-key
swift run mere.run api serve \
--engine text-chat-gemma4 \
--host 0.0.0.0 \
--port 11434 \
--api-key "$MERERUN_API_KEY" \
--rate-limit-per-minute 120 \
--max-active-requests 1Design notes
- The API server follows the same model-resolution and model-store rules as the rest of the CLI.
ManagedModelCatalogis the API model authority; the server does not scan arbitrary model folders as request-addressable models.--engineand--modelstill define and preload the startup default, while each chat request can select another installed API-capable catalog model with the OpenAImodelfield./v1/embeddingsuses the nativetext-embed-qwen3-0.6bsidecar model; it is listed in/v1/modelswhen installed even though it is not a chat-serving engine./v1/images/generations,/v1/images/edits,/v1/audio/speech, and/v1/audio/transcriptionsare sidecar routes over existing native CLI runtime paths. Installed embedding, image, TTS, and ASR sidecar catalog IDs are listed in/v1/modelseven though they are not chat-serving engines.- The server keeps one most-recently-used embedding lane, one image lane shared by generation and editing, one TTS lane, and one ASR lane resident. Repeated requests for the same model reuse loaded components; mutable generators execute exclusively, and a model or ASR-backend switch unloads the previous runtime before loading the next one so request-selected local paths cannot grow residency without bound.
- Sidecars default to a 300-second idle time to live (TTL). Per-lane autonomous timers expire idle residents without waiting for another request and re-read managed
pinnedandttlSecondssettings while idle, so live settings changes take effect. Managed embedding, image, TTS, and ASR models acceptmodel runtime set <id> --ttl-seconds <seconds>and--pinned; sidecar-specific settings reject text-only sampling, engine, alias, context, and KV controls. The specialqwen-image-editrepository lane is resident but uses the default lifecycle policy because it is not configurable throughmodel runtime. - Sidecar admission samples the same
--memory-guardpolicy before and after an operation. Cold sidecar operations are exclusive across lanes, preventing two model loads from racing each other or an already-running warm sidecar. Image operations remain exclusive because staged image pipelines can reload components even when their resident generator is warm. Before another resident loads, the catalog estimate (or resolved local directory size) is projected against the hard guard with conservative per-family working-set floors; idle unpinned residents are proactively released, and the request fails with a memory-pressure error if adequate headroom cannot be recovered. Under pressure, admission gives idle unpinned text runtimes the first opportunity to unload, one at a time with pressure re-sampled after each eviction; the idle startup default is eligible for this sidecar admission path. If pressure remains, eligible idle sidecars are evicted oldest first. Active, queued, or pinned residents are protected throughout. - Chat, embedding, image, TTS, and ASR inference pass through a fair first-in, first-out (FIFO) request admission actor; the default
--max-active-requests 1serializes local inference across text and media activation peaks and exposes queue depth in status. Raising it is an explicit throughput and unified-memory tradeoff; queued client cancellations are removed from the FIFO instead of being admitted later. Explicit runtime model load/unload maintenance shares the same queue. - Machine admission complements rather than replaces
--max-active-requests: the server's local limit controls request and batching concurrency inside its weighted machine reservation. - Gemma4, Qwen-family, and LFM2 prefills are chunked with cancellation and progress checkpoints before decode; this is cooperative single-request prefill, not continuous batching. Qwen3.8 defaults to 1,024-token chunks and caps them at 512 when live reclaimable memory falls below 16 GiB or a peer is admitted, so a live decoder is not held behind a pressure-heavy prefill.
- Gemma4 uses in-memory prefix KV reuse by default in
api serve; setMERERUN_GEMMA4_PREFIX_KV_CACHE=0for a baseline./runtime/statusreports cache entries, hits, and reused tokens when the Gemma4 model is loaded; the cache stores chunk boundaries plus the stable chat prefix before the final message when it is an exact token prefix, and pruning keeps that stable prefix ahead of ordinary chunk boundaries. - Qwen-family chat uses text-only prefix KV reuse by default in
api serve; setMERERUN_Q35_PREFIX_KV_CACHE=0for a baseline. Vision prompts are excluded because image embeddings alter the effective prefix; text-only requests use the same stable chat-prefix checkpoint and pruning rule as Gemma4. - LFM2 chat uses prefix KV reuse by default in
api serve; setMERERUN_LFM2_PREFIX_KV_CACHE=0for a baseline. It retains exact prompts and the stable conversation prefix before the final message, without cloning every intermediate prefill chunk. Both attention KV and short-convolution state are forked at those retained checkpoints. - Managed Gemma4 12B text and vision installs include the
text-chat-gemma4-12b-mtpassistant companion. The API server uses it only for greedy serial decode-tail speculation after prefill; sampled requests, raw local model paths, prefix-KV seeded requests, and continuous batching stay on baseline decode. - Gemma4, Qwen-family, and LFM2 decode batching engages automatically when
--max-active-requestsis above1. Their engine-specific continuous-batching variables can force the implementation on or force the serial path, and/runtime/statusreports actual batched decode steps instead of assuming the scheduler is active. An eligible Qwen MTP request takes its speculative lane only when no peer is already admitted; contended arrivals use ordinary batching, and a late arrival does not migrate an MTP request already in progress. Gemma4 full-attention rows remain same-position because that engine still uses scalar RoPE/cache offsets. Qwen-family and LFM2 full-attention rows use row-offset-aware ragged KV caches; Qwen-family linear attention and LFM2 short-convolution layers use typed recurrent state, so compatible rows may batch across decode positions. The scheduler services the earliest decode position first, batching compatible rows there or advancing a single lower-offset row until it can join one. - Gemma4 has an experimental packed PolarKV path behind
--kv-quant-scheme polar --kv-bits 2; use it for memory-pressure and long-context synthetic decode testing. It is not the default until checkpoint benchmarks prove the end-to-end model path. - Gemma4, Qwen-family, and LFM2 runtime settings can set
kvCacheModeto explicitaffine8as a memory control relative to full-precision KV. Qwen-family and LFM2 dequantize the generic cache for attention. Gemma Turbo already defaults to a smaller 4-bit TurboQuant cache, so forcing affine 8-bit can increase its KV residency.defaultrestores the engine/model/server default, not necessarily full precision. Gemma additionally acceptspolar2orauto;autokeeps the default KV path below 1024 prompt tokens and switches to decode-deferred packed PolarKV at or above that threshold. /runtime/statusaggregates prefix hits, reused tokens, and batched decode steps across loaded models undercacheStats; it also reports completed chat request counts, generated tokens, and average load/prefill/decode timings underbenchmarkStats. The additivesidecarsobject reports the embedding, image, speech, and transcription resident model/path, active and queued requests, load/access/eviction timestamps, TTL/pinned state, readiness, and lifecycle counters. For text and sidecar entries,loadedremains the compatibility signal that a resident object exists; additiveready: falsemeans text preparation is in progress or a sidecar's first operation is loading or failed. Older payloads can omitready, and older clients can ignore it. SSD KV persistence remains unavailable until the in-memory counters justify it.- Runtime settings are stored at
<active model store>/.mere-run/runtime-model-settings.json. - The runtime pool applies
ttlSecondsopportunistically when handling pool operations; expired idle models unload automatically unless their settings are pinned. Explicit unload remains available for pinned models. - Memory-pressure least-recently-used (LRU) eviction uses the API server's
--memory-guardtier. The guard derives soft/hard ceilings from Darwin physical footprint (RSS elsewhere), host memory headroom, and a tier reserve (safe,balanced,aggressive, orcustom). Elevated pressure pauses extra concurrent admissions and evicts the least-recently-used idle unpinned model; critical pressure evicts every idle unpinned model. Active requests are never evicted. mere.run statusis the preferred quick check before you connect an editor or agent to a local server.- The server is local-first.
- The server must not introduce relay, billing, or hosted-infrastructure concerns.
- Non-loopback binds require an API key, and the OpenAI-compatible routes support basic rate limiting.
- Chat, embedding, image generation, and TTS requests must use
Content-Type: application/json; image editing, STT, and vision geometry/3D/depth requests must usemultipart/form-data; browser-simple form and text posts are rejected before the request body is processed. - Chat requests are validated before generation;
max_tokens,max_completion_tokens,temperature,top_p, and the supportedmin_pextension must stay within bounded ranges. - LoRA adapters are configured at server startup with
--lora; request bodies cannot select local LoRA paths. - Streaming and JSON error paths are sanitized so the local server does not reflect raw internal runtime details back to clients.
Runtime control endpoints
The control endpoints use the same bearer-token behavior as /v1/models, /v1/chat/completions, and /v1/embeddings.
GET /runtime/status: server health, pool entries, active request counts, request admission state, runtime capability flags, memory snapshot, settings path, aggregate cache stats, per-model prefix KV cache stats, per-model decode batching stats when enabled, text/sidecar residency and readiness, and aggregate traffic stats measured from completed native chat requests. Newer servers also include additiveprocesstelemetry: PID, start time, uptime, sampled process CPU, macOS thermal/low-power state, Metal device, live Metal allocation, recommended maximum working set, and unified-memory capability. Older clients ignore it and older servers may omit it.POST /runtime/models/{id}/load: explicitly load an installed API-capable text-pool catalog model.POST /runtime/models/{id}/unload: unload a text-pool model; returns409while active requests are using it.GET /runtime/models/{id}/settings: read typed text-pool runtime defaults.PATCH /runtime/models/{id}/settings: replace typed text-pool runtime defaults.
These four /runtime/models/{id} HTTP operations address the chat/text runtime pool only. Managed embedding, image, TTS, and ASR sidecar TTL/pinning is configured with mere.run model runtime set (or the settings file); there is no sidecar HTTP load/unload/settings endpoint. Sidecars still appear in /v1/models and in the sidecars object returned by /runtime/status.
GET /v1/models returns installed API-servable chat managed IDs, configured aliases, and installed native sidecars. In addition to the standard OpenAI fields, each entry can describe its task, tool_call, reasoning, thinking_levels, input/output modalities, context/output limit, and openai_compat dialect. Agent harnesses can therefore discover what the running server actually supports instead of inferring capabilities from a model name. Missing catalog models fail with an OpenAI-style error that tells the user to pull the model first.
For managed models, these capabilities come from the typed API profile attached to the managed-model catalog entry. The runtime layer adds the request-facing ID or alias and applies configured context/output defaults to the reported limits. A server started from an explicit uncataloged path uses a conservative profile for its selected serving engine.
OpenAI chat compatibility
POST /v1/chat/completions accepts the common Chat Completions request shape:
system,developer,user,assistant, andtoolmessages- string content, text content parts, nullable assistant content, and image content parts when the selected engine supports vision
- assistant
tool_callsand tool response messages tools,tool_choice,parallel_tool_calls;tool_choiceacceptsnone,auto,required, and specific function choices by narrowing the advertised tool list to the named functionresponse_formatstream_options.include_usagestop,seed, penalties, logprobs, reasoning controls, and provider-thinking controls as typed request fieldsmax_completion_tokensalongside legacymax_tokens
The server does not silently drop high-impact fields. Native engines either map supported fields into ChatRequest or return an OpenAI-style invalid_request_error before generation. Metadata-style fields such as metadata, user, and service_tier are accepted as request context but do not change local generation.
For non-streaming chat responses, native runtimes split <think>...</think> blocks out of message.content and expose them as OpenAI-compatible message.reasoning_content when present. Streaming responses still emit token chunks as they are produced.
Native Qwen-family, Nemotron Lightning, and Laguna catalog profiles accept logprobs: true for non-streaming requests, with top_logprobs from 0 through 20. Log-probability capture requires unconstrained text output instead of json_object or json_schema. The response keeps the OpenAI choices[].logprobs.content[] shape and adds explicit raw_logprob, policy_logprob, entropy, margin, region, summary, and capture-overhead fields. Raw measures the target model before sampling transforms; policy measures the exact distribution after temperature/top-k/ top-p/min-p. Reasoning token text is omitted. Capture routes through serial final-target decode, so speculative draft distributions never appear as model confidence.
Engine compatibility:
text-chat-deepseek-v4-flash: raw-proxies the original request body tods4-server, preserving DS4's OpenAI-compatible behavior.text-chat-gemma4: accepts function tools and emits OpenAI tool-call responses when the model generates a tool call.vision-chat-gemma4-12b: uses the Gemma4 serving engine and accepts one OpenAI image content part per message. The native runtime accepts local file paths,file://URLs, or base64 data URLs; it does not fetch remote images.text-chat-q36-nano: uses the Qwen-family serving engine with Qwen3.6 35B-A3B OptiQ chat weights, accepts function tools, and accepts one image content part per message. It also acceptsresponse_format: {"type":"json_object"}and reports structured-output support with strict mode disabled. JSON-object mode forces thinking off and uses token-level constrained serial decoding; it does not implementjson_schema.vision-chat-q38-27bandvision-chat-q38-27b-4bit: use the same native Qwen-family serving engine for the official dense Qwen3.8 27B BF16 checkpoint or the pinned MLX 4-bit conversion. Both accept function tools, one local/base64 image content part per message, and structured JSON output; both default to thinking and the published 1.0/0.95/20 sampling. Pull the 55.59 GB BF16 lane or 19.47 GB 4-bit-plus-MTP lane explicitly before serving it.text-chat-bonsai-27b-1bitandtext-chat-bonsai-27b-2bit: use the same native Qwen-family serving engine for Prism ML's dense packed binary and ternary 27B checkpoints. They accept function tools and one local/base64 image content part per message, default to thinking, and use the published 0.7/0.95/20 sampling when omitted. Start the server with--context-size 262144to expose their full advertised context; the server-wide default remains the conservative 32K limit.text-agent-ornith-9b: uses the same Qwen-family serving engine for the Ornith 1.0 9B OptiQ coding-agent experiment; start it withapi serve --engine text-chat-q36 --model text-agent-ornith-9b.text-agent-ornith-35b-mlx-4bit,-6bit,-8bit, and BF16text-agent-ornith-35b-mlx: use the Qwen-family serving engine for Ornith's official 1.5 35B-A3B MLX family. Install a RAM-appropriate tier explicitly, then pass that id toapi serve --engine text-chat-q36 --model.- Both Ornith lanes serve with thinking-enabled generation by default (the models degenerate without it); the reasoning arrives in the response's
reasoning_contentfield whilecontentcarries only the visible answer. When a request sets no explicittemperature/top_p/min_p, these lanes also apply the model's published top-k of 20. text-chat-lfm25-a1b-8bit: uses the LFM2 serving engine with the LiquidAI LFM2.5 8B-A1B MLX 8-bit weights, accepts function tools, and rejects image content parts.text-chat-lfm25-2.6b-4bit: uses the same native LFM2 serving engine with LiquidAI's dense 2.6B MLX 4-bit weights, accepts function tools, and rejects image content parts.vision-chat-lfm25-3b-8bit: uses the native LFM2 serving engine with LiquidAI's dense language backbone, SigLIP2 vision tower, and multimodal projector. It accepts local-file and base64 image content parts; it does not fetch remote image URLs.text-chat-klein: supportsresponse_format: {"type":"json_object"}with local JSON retry behavior.text-code: accepts plain text chat requests and OpenAIstopsequences; use it for GGUF code models such astext-code-qwen3,text-code-north-mini, andtext-agent-ornith-35b. It rejects tools, images, reasoning controls, logprobs, seed, and structured outputs with explicit errors.
The JSON-object capability applies to the native MLX Qwen-family runtime, including Q35-compatible models that share that generator. The Linux/GGUF Q36 lane still routes through llama.cpp and rejects structured output until its JSON grammar is wired.
Streaming responses only emit assistant content tokens. Local progress labels stay in logs/stderr, and stream_options.include_usage adds the final usage chunk before [DONE].
OpenAI embeddings compatibility
POST /v1/embeddings accepts the common Embeddings request shape:
model:text-embed-qwen3-0.6bor a local Qwen3 embedding model pathinput: a string or an array of stringsencoding_format: omitted orfloatuser: accepted as request context
One request may contain at most 256 texts and 2 MiB of UTF-8 input. The native runtime caps each row at 8,192 tokens, length-packs rows into sequential batches with at most 8,192 padded tokens, and restores response order after evaluation.
The route returns object: "list", one embedding object per input, and prompt_tokens/total_tokens usage counts. Dimension overrides and base64 embedding encoding are rejected with invalid_request_error because the native Qwen3 embedding model has a fixed vector size and returns float vectors.
OpenAI image/audio compatibility
POST /v1/images/generations accepts:
model: a mere.run image model ID, local image model path, or an OpenAI image model name such asdall-e-3mapped toimage-zimage-nanoprompt: required text promptsize:WIDTHxHEIGHT; defaults to1024x1024; each dimension must be a multiple of 16 from 16 through 4,096 pixels, with total area limited to 4,194,304 pixelsn: only1response_format:b64_jsonby default, orurlfor a localfile://URL- local extensions:
seed,negative_prompt,steps(1 through 100), andguidance_scale
POST /v1/images/edits accepts multipart form fields:
image: required input image file part; Open WebUI-styleimage[]repeated parts are accepted for multi-image edit requestsmask: optional mask file partmodel: a mere.run image model ID, local image model path,qwen-image-edit, or an OpenAI image model name such asgpt-image-1mapped toimage-zimage-nanoprompt: required edit instructionsize:WIDTHxHEIGHT; defaults to1024x1024; each dimension must be a multiple of 16 from 16 through 4,096 pixels, with total area limited to 4,194,304 pixelsn: only1response_format:b64_jsonby default, orurlfor a localfile://URL- local extensions:
strength,seed,negative_prompt,steps(1 through 100), andguidance_scale
Masks are accepted for client compatibility. Native edit models use whole-image conditioning rather than strict masked inpainting.
POST /v1/audio/speech accepts:
model:speech-tts-qwen3-nano, a local Qwen3-TTS model path, or OpenAI names such astts-1mapped to the local defaultinput: required textvoice: OpenAI voice names are translated to Qwen3-TTS style descriptions; custom descriptions are passed throughspeed,instructions, andtemperatureresponse_format:wav,mp3,opus,aac, orflac; non-WAV formats requireffmpeg
The normalized input and voice instructions may total at most 32 KiB of UTF-8 text.
POST /v1/audio/transcriptions accepts multipart form fields:
file: required audio file partmodel:speech-asr-parakeet,speech-asr-qwen3, a local ASR model path, or OpenAI names such aswhisper-1mapped tospeech-asr-parakeetlanguagetask:transcribeortranslateresponse_format:json,text,verbose_json,srt, orvttmax_tokens: 1 through 4,096; defaults to 448
Unsupported format choices return OpenAI-style invalid_request_error payloads instead of being ignored.
Vision geometry and 3D compatibility
The five /v1/vision/* routes take multipart/form-data and return JSON whose artifacts are server-local file:// URLs, retained for one hour. Because those URLs only make sense on the serving machine, the routes are loopback-only: authenticated remote clients get an error, and their model IDs are filtered out of /v1/models for non-loopback clients. Inputs must be uploaded file parts; client filesystem paths are rejected. Each route accepts only its managed default model ID (depth video also accepts its metric variant).
POST /v1/vision/geometry accepts:
image: required single input image file partmodel: onlyvision-geometry-moge2-smallresolution_level: integer 0 through 9; defaults to 9token_count: optional integer 1 through 3,600max_points: optional positive point-count cap
POST /v1/vision/geometry/multiview accepts:
image/image[]: one or more image file parts; multipart order is the view ordermodel: onlyvision-geometry-da3-smallprocess_resolution: defaults to 504reference_view:first,middle,saddle-balanced, orsaddle-similarity-range; defaults tosaddle-balancedconfidence_percentile: 0 through 100; defaults to 40max_points: defaults to 1,000,000cameras: optional known-camera JSON, as either one uploaded JSON file or one inline field, not both
POST /v1/vision/image-to-3d accepts:
image: exactly one input image file partmodel: onlyimage-3d-triposrresolution: integer 2 through 512; defaults to 256density_threshold: defaults to 25foreground_ratio: greater than 0 and at most 1; defaults to 0.85already_framed: boolean; defaults to falsevertex_colors: boolean; defaults to true
POST /v1/vision/image-to-3d-multiview accepts:
image/image[]: exactly 4 or 6 non-empty view file partsmodel: onlyimage-3d-instantmesh-baseresolution: integer 2 through 256; defaults to 128vertex_colors: boolean; defaults to truecameras: optionalschemaVersion1 JSON with one 16-value camera per uploaded view
POST /v1/vision/depth-video accepts:
video: exactly one input video file partmodel:vision-depth-vda-small(default) orvision-depth-vda-small-metricinput_size: integer 14 through 1,008; defaults to 518max_frames: integer 1 through 2,400; defaults to 240
Uploads are capped at 100 MiB for the single-image geometry and image-to-3d routes and 512 MiB for the multi-view and depth-video routes.
Open WebUI companion
Open WebUI can use mere.run as an OpenAI-compatible provider without being vendored into this repo. Start mere.run, run the official Open WebUI Docker or pip install, then configure Open WebUI with the mere.run /v1 base URL.
One-command Docker quickstart:
bash
mere.run open-webui quickstart --pull --accept-model-licenseThat starts api serve, runs the official Open WebUI container, configures the OpenAI provider, filters the chat picker to the configured text and vision chat models, points RAG at /v1/embeddings, and keeps image editing disabled for the live-smoke path. Preview the exact commands with:
bash
mere.run open-webui quickstart --dry-runThe macOS Studio passes MERERUN_API_KEY and MERERUN_OPEN_WEBUI_ADMIN_PASSWORD in the child environment so neither secret appears in the process argument list. CLI users may use the same variables; explicit --api-key and --admin-password values still take precedence.
Repeatable smoke harness:
bash
export MERERUN_API_KEY=change-me
mere.run api serve \
--engine text-chat-gemma4 \
--model text-chat-gemma4-12b \
--host 0.0.0.0 \
--port 8080 \
--api-key "$MERERUN_API_KEY"
MERERUN_OPENWEBUI_TEXT_MODEL=text-chat-gemma4-12b \
MERERUN_OPENWEBUI_RESET=1 scripts/smoke-open-webui.sh docker-run
MERERUN_OPENWEBUI_TEXT_MODEL=text-chat-gemma4-12b \
scripts/smoke-open-webui.sh live-smokelive-smoke waits for Open WebUI, signs in to the disposable no-auth smoke admin user, filters the OpenAI chat connection to the configured text and vision chat IDs, imports per-model metadata wrappers, sends text and vision chat through Open WebUI's own proxy, then exercises the direct mere.run API surface and the Open WebUI model list. Use configure, proxy-smoke, api-smoke, and ui-smoke separately when debugging one stage.
Docker bridge path:
bash
export MERERUN_API_KEY=change-me
mere.run api serve \
--engine text-chat-gemma4 \
--model text-chat-gemma4-12b \
--host 0.0.0.0 \
--port 8080 \
--api-key "$MERERUN_API_KEY"
DEFAULT_MODEL_METADATA='{"capabilities":{"file_context":true,"vision":false,"file_upload":true,"web_search":false,"image_generation":true,"code_interpreter":false,"terminal":false,"citations":true,"status_updates":true,"builtin_tools":true}}'
docker run -d \
--name open-webui \
--restart unless-stopped \
-p 3000:8080 \
--add-host=host.docker.internal:host-gateway \
-e OPENAI_API_BASE_URL=http://host.docker.internal:8080/v1 \
-e OPENAI_API_BASE_URLS=http://host.docker.internal:8080/v1 \
-e OPENAI_API_KEY="$MERERUN_API_KEY" \
-e OPENAI_API_KEYS="$MERERUN_API_KEY" \
-e DEFAULT_MODELS=text-chat-gemma4-12b \
-e DEFAULT_MODEL_PARAMS='{"function_calling":"native"}' \
-e DEFAULT_MODEL_METADATA="$DEFAULT_MODEL_METADATA" \
-e RAG_EMBEDDING_ENGINE=openai \
-e RAG_OPENAI_API_BASE_URL=http://host.docker.internal:8080/v1 \
-e RAG_OPENAI_API_KEY="$MERERUN_API_KEY" \
-e RAG_EMBEDDING_MODEL=text-embed-qwen3-0.6b \
-e ENABLE_IMAGE_GENERATION=True \
-e ENABLE_IMAGE_EDIT=False \
-e IMAGE_GENERATION_ENGINE=openai \
-e IMAGE_GENERATION_MODEL=image-zimage-nano \
-e IMAGE_SIZE=1024x1024 \
-e IMAGE_EDIT_ENGINE=openai \
-e IMAGE_EDIT_MODEL=qwen-image-edit \
-e IMAGE_EDIT_SIZE=1024x1024 \
-e IMAGES_OPENAI_API_BASE_URL=http://host.docker.internal:8080/v1 \
-e IMAGES_OPENAI_API_KEY="$MERERUN_API_KEY" \
-e IMAGES_EDIT_OPENAI_API_BASE_URL=http://host.docker.internal:8080/v1 \
-e IMAGES_EDIT_OPENAI_API_KEY="$MERERUN_API_KEY" \
-e AUDIO_TTS_ENGINE=openai \
-e AUDIO_TTS_MODEL=speech-tts-qwen3-nano \
-e AUDIO_TTS_VOICE=nova \
-e AUDIO_TTS_OPENAI_API_BASE_URL=http://host.docker.internal:8080/v1 \
-e AUDIO_TTS_OPENAI_API_KEY="$MERERUN_API_KEY" \
-e AUDIO_TTS_OPENAI_PARAMS='{"response_format":"wav"}' \
-e AUDIO_STT_ENGINE=openai \
-e AUDIO_STT_MODEL=speech-asr-parakeet \
-e AUDIO_STT_OPENAI_API_BASE_URL=http://host.docker.internal:8080/v1 \
-e AUDIO_STT_OPENAI_API_KEY="$MERERUN_API_KEY" \
-e ENABLE_PERSISTENT_CONFIG=False \
-e WEBUI_AUTH=False \
-v open-webui:/app/backend/data \
ghcr.io/open-webui/open-webui:mainThe environment variables in the Docker command preconfigure Open WebUI on a fresh data volume. Run scripts/smoke-open-webui.sh configure after the container is healthy to set Open WebUI's OpenAI connection model_ids filter and import per-model capability wrappers. That keeps image, embedding, TTS, and STT sidecars out of the chat selector while still using them in their own settings. You can also set the same values in the admin UI:
- Base URL:
http://host.docker.internal:8080/v1 - API key: the value of
MERERUN_API_KEY - Chat model: an installed
text-chat-*model from/v1/models - Vision model:
vision-chat-gemma4-12b - RAG embedding model:
text-embed-qwen3-0.6b - Image generation engine/model:
openai/image-zimage-nano - Image editing: disabled for the live smoke path with
ENABLE_IMAGE_EDIT=False - Image edit engine/model when enabled:
openai/qwen-image-edit - TTS engine/model:
openai/speech-tts-qwen3-nano - STT engine/model:
openai/speech-asr-parakeet - Function calling: native mode with
{"function_calling":"native"}
Docker Compose host path
Keep mere.run on the host and run only Open WebUI in Docker. Use this path for Linux or LAN workstations because the mere.run process owns the local model store and runtime/GPU setup while Open WebUI keeps its persistent app data in a Docker volume.
bash
export MERERUN_API_KEY=change-me
mere.run api serve \
--engine text-chat-gemma4 \
--model text-chat-gemma4-12b \
--host 0.0.0.0 \
--port 8080 \
--api-key "$MERERUN_API_KEY"Create open-webui-mere-run/.env:
dotenv
MERERUN_API_KEY=change-me
WEBUI_SECRET_KEY=replace-with-openssl-rand-hex-32
OPEN_WEBUI_IMAGE=ghcr.io/open-webui/open-webui:main
OPEN_WEBUI_BIND=0.0.0.0
OPEN_WEBUI_URL=http://host.example.com:3000
MERERUN_OPENWEBUI_API_URL=http://host.docker.internal:8080/v1
MERERUN_OPENWEBUI_TEXT_MODEL=text-chat-gemma4-12bCreate open-webui-mere-run/compose.yaml:
yaml
services:
open-webui:
image: ${OPEN_WEBUI_IMAGE:-ghcr.io/open-webui/open-webui:main}
container_name: open-webui
restart: unless-stopped
ports:
- "${OPEN_WEBUI_BIND:-0.0.0.0}:3000:8080"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
WEBUI_URL: ${OPEN_WEBUI_URL:-http://localhost:3000}
WEBUI_AUTH: "True"
WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY}
ENABLE_PERSISTENT_CONFIG: "True"
OPENAI_API_BASE_URL: ${MERERUN_OPENWEBUI_API_URL:-http://host.docker.internal:8080/v1}
OPENAI_API_BASE_URLS: ${MERERUN_OPENWEBUI_API_URL:-http://host.docker.internal:8080/v1}
OPENAI_API_KEY: ${MERERUN_API_KEY}
OPENAI_API_KEYS: ${MERERUN_API_KEY}
DEFAULT_MODELS: ${MERERUN_OPENWEBUI_TEXT_MODEL:-text-chat-gemma4-12b}
DEFAULT_MODEL_PARAMS: '{"function_calling":"native"}'
DEFAULT_MODEL_METADATA: '{"capabilities":{"file_context":true,"vision":false,"file_upload":true,"web_search":false,"image_generation":true,"code_interpreter":false,"terminal":false,"citations":true,"status_updates":true,"builtin_tools":true}}'
RAG_EMBEDDING_ENGINE: openai
RAG_OPENAI_API_BASE_URL: ${MERERUN_OPENWEBUI_API_URL:-http://host.docker.internal:8080/v1}
RAG_OPENAI_API_KEY: ${MERERUN_API_KEY}
RAG_EMBEDDING_MODEL: text-embed-qwen3-0.6b
ENABLE_IMAGE_GENERATION: "True"
ENABLE_IMAGE_EDIT: "False"
IMAGE_GENERATION_ENGINE: openai
IMAGE_GENERATION_MODEL: image-zimage-nano
IMAGE_SIZE: 1024x1024
IMAGES_OPENAI_API_BASE_URL: ${MERERUN_OPENWEBUI_API_URL:-http://host.docker.internal:8080/v1}
IMAGES_OPENAI_API_KEY: ${MERERUN_API_KEY}
AUDIO_TTS_ENGINE: openai
AUDIO_TTS_MODEL: speech-tts-qwen3-nano
AUDIO_TTS_VOICE: nova
AUDIO_TTS_OPENAI_API_BASE_URL: ${MERERUN_OPENWEBUI_API_URL:-http://host.docker.internal:8080/v1}
AUDIO_TTS_OPENAI_API_KEY: ${MERERUN_API_KEY}
AUDIO_TTS_OPENAI_PARAMS: '{"response_format":"wav"}'
AUDIO_STT_ENGINE: openai
AUDIO_STT_MODEL: speech-asr-parakeet
AUDIO_STT_OPENAI_API_BASE_URL: ${MERERUN_OPENWEBUI_API_URL:-http://host.docker.internal:8080/v1}
AUDIO_STT_OPENAI_API_KEY: ${MERERUN_API_KEY}
volumes:
- open-webui:/app/backend/data
volumes:
open-webui:Then run:
bash
cd open-webui-mere-run
docker compose up -dFor Open WebUI on a different machine, set MERERUN_OPENWEBUI_API_URL to the mere.run host's LAN URL, for example http://192.0.2.50:8080/v1. Keep WEBUI_AUTH=True, keep a stable WEBUI_SECRET_KEY, and pin OPEN_WEBUI_IMAGE to a tested release tag before treating the instance as shared or production-like.
Pip path:
bash
python3.11 -m pip install --upgrade open-webui
DEFAULT_MODEL_METADATA='{"capabilities":{"file_context":true,"vision":false,"file_upload":true,"web_search":false,"image_generation":true,"code_interpreter":false,"terminal":false,"citations":true,"status_updates":true,"builtin_tools":true}}'
OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
OPENAI_API_BASE_URLS=http://127.0.0.1:8080/v1 \
OPENAI_API_KEY="$MERERUN_API_KEY" \
OPENAI_API_KEYS="$MERERUN_API_KEY" \
DEFAULT_MODELS=text-chat-gemma4-12b \
DEFAULT_MODEL_PARAMS='{"function_calling":"native"}' \
DEFAULT_MODEL_METADATA="$DEFAULT_MODEL_METADATA" \
RAG_EMBEDDING_ENGINE=openai \
RAG_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
RAG_OPENAI_API_KEY="$MERERUN_API_KEY" \
RAG_EMBEDDING_MODEL=text-embed-qwen3-0.6b \
ENABLE_IMAGE_GENERATION=True \
ENABLE_IMAGE_EDIT=False \
IMAGE_GENERATION_ENGINE=openai \
IMAGE_GENERATION_MODEL=image-zimage-nano \
IMAGE_SIZE=1024x1024 \
IMAGE_EDIT_ENGINE=openai \
IMAGE_EDIT_MODEL=qwen-image-edit \
IMAGE_EDIT_SIZE=1024x1024 \
IMAGES_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
IMAGES_OPENAI_API_KEY="$MERERUN_API_KEY" \
IMAGES_EDIT_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
IMAGES_EDIT_OPENAI_API_KEY="$MERERUN_API_KEY" \
AUDIO_TTS_ENGINE=openai \
AUDIO_TTS_MODEL=speech-tts-qwen3-nano \
AUDIO_TTS_VOICE=nova \
AUDIO_TTS_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
AUDIO_TTS_OPENAI_API_KEY="$MERERUN_API_KEY" \
AUDIO_TTS_OPENAI_PARAMS='{"response_format":"wav"}' \
AUDIO_STT_ENGINE=openai \
AUDIO_STT_MODEL=speech-asr-parakeet \
AUDIO_STT_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
AUDIO_STT_OPENAI_API_KEY="$MERERUN_API_KEY" \
ENABLE_PERSISTENT_CONFIG=False \
WEBUI_AUTH=False \
open-webui serve --host 127.0.0.1 --port 3000For pip-based smoke configuration, use the same-host provider URL:
bash
OPEN_WEBUI_MERERUN_API_URL=http://127.0.0.1:8080/v1 \
scripts/smoke-open-webui.sh configureUse Base URL http://127.0.0.1:8080/v1 for any admin UI edits in the pip install. If an existing Open WebUI data directory ignores changed environment variables, update the values in the admin UI, set ENABLE_PERSISTENT_CONFIG=False for smoke, or start with a fresh data directory. Set the vision-chat-gemma4-12b per-model capability vision=true before UI vision upload smoke if you use the conservative global model metadata from this example.
uv path:
bash
curl -LsSf https://astral.sh/uv/install.sh | sh
export DATA_DIR="$HOME/.open-webui-mere-run"
export MERERUN_API_KEY=change-me
DEFAULT_MODEL_METADATA='{"capabilities":{"file_context":true,"vision":false,"file_upload":true,"web_search":false,"image_generation":true,"code_interpreter":false,"terminal":false,"citations":true,"status_updates":true,"builtin_tools":true}}'
DATA_DIR="$HOME/.open-webui-mere-run" \
OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
OPENAI_API_BASE_URLS=http://127.0.0.1:8080/v1 \
OPENAI_API_KEY="$MERERUN_API_KEY" \
OPENAI_API_KEYS="$MERERUN_API_KEY" \
DEFAULT_MODELS=text-chat-gemma4-12b \
DEFAULT_MODEL_PARAMS='{"function_calling":"native"}' \
DEFAULT_MODEL_METADATA="$DEFAULT_MODEL_METADATA" \
RAG_EMBEDDING_ENGINE=openai \
RAG_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
RAG_OPENAI_API_KEY="$MERERUN_API_KEY" \
RAG_EMBEDDING_MODEL=text-embed-qwen3-0.6b \
ENABLE_IMAGE_GENERATION=True \
ENABLE_IMAGE_EDIT=False \
IMAGE_GENERATION_ENGINE=openai \
IMAGE_GENERATION_MODEL=image-zimage-nano \
IMAGES_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
IMAGES_OPENAI_API_KEY="$MERERUN_API_KEY" \
AUDIO_TTS_ENGINE=openai \
AUDIO_TTS_MODEL=speech-tts-qwen3-nano \
AUDIO_TTS_VOICE=nova \
AUDIO_TTS_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
AUDIO_TTS_OPENAI_API_KEY="$MERERUN_API_KEY" \
AUDIO_TTS_OPENAI_PARAMS='{"response_format":"wav"}' \
AUDIO_STT_ENGINE=openai \
AUDIO_STT_MODEL=speech-asr-parakeet \
AUDIO_STT_OPENAI_API_BASE_URL=http://127.0.0.1:8080/v1 \
AUDIO_STT_OPENAI_API_KEY="$MERERUN_API_KEY" \
ENABLE_PERSISTENT_CONFIG=False \
WEBUI_AUTH=False \
uvx --python 3.11 open-webui serve --host 127.0.0.1 --port 3000Run the mere.run API on 127.0.0.1:8080 in another terminal. The explicit Open WebUI --port 3000 avoids colliding with the mere.run API recipe.
The CLI cookbook has the longer copy-paste recipe:
bash
mere.run guide open-webuiFair FIFO request admission is part of the runtime pool, and Gemma4, Qwen-family, and LFM2 chat use engine-specific chunked prefill checkpoints. Matching text prefixes reuse in-memory KV by default for all three families; Qwen-family vision requests remain excluded, and the engine-specific prefix variables can disable reuse for an A/B. Decode batching also engages for all three when --max-active-requests is above 1, with force-on/force-off environment overrides. The implementations merge compatible typed cache rows for actual batched decode calls, then split them so each request retains its own state. Qwen-family and LFM2 full-attention rows can batch across positions with row-offset-aware ragged KV caches; Qwen-family linear state and LFM2 short-convolution state are batched when their typed shapes are compatible. Gemma4 full-attention rows still require matching scalar offsets. SSD KV persistence remains later, measured work; use cacheStats plus benchmarkStats to decide whether that experiment is worth expanding on a real machine.
If you are working on this area, read CLI and runtime internals after the command source.
Troubleshooting
Start with:
bash
swift run mere.run statusserver: downmeans nothing answered the configured/healthURL.loaded models: unavailable (requires API key)means/healthworked but/v1/modelsneeds--api-keyorMERERUN_API_KEY.- A wrong loaded model usually means another server is already bound to that host and port.