Setup
Installation, engines, models, and the configuration reference.
7. SETUP
Verified against Blazar v0.6.1 source. Commands and configuration spot-verified against v0.19.0 (2026-10-03). INFERRED tags mark reasoned statements.
Environment variables (NAME + PURPOSE only)
Product runtime (read by blazar itself)
| Variable | Purpose |
|---|---|
BLAZAR_KEYS |
bootstrap API keys into config at startup |
BLAZAR_REGISTRY_BASE |
override the model registry base URL |
BLAZAR_REGISTRY_TOKEN |
access token for private HF namespaces |
BLAZAR_GH_BASE |
override the GitHub base URL for engine downloads |
BLAZAR_REPO |
override the release repository for downloads |
BLAZAR_INSTALL_BASE_URL |
override the engine artifact download base URL |
BLAZAR_ENGINE_PATH |
point at an engine binary location |
BLAZAR_SYSTEMCTL |
override the systemctl binary used for service management |
BLAZAR_CAPABILITY_REGISTRY |
override (or, when empty, disable) the capability-lane registry URL |
BLAZAR_ENGINE_GATE |
influence the engine update regression gate |
BLAZAR_HOST |
target host for CLI client verbs against a running daemon |
BLAZAR_DEFAULT_CTX |
override the default context size |
BLAZAR_CHILD_TRANSPORT |
override child_transport (tcp / unix) |
RUST_LOG |
log filter; wins over config log_level (escape hatch) |
Installer (read by scripts/install.sh / bootstrap.sh / uninstall.sh)
BLAZAR_REPO, BLAZAR_VERSION, BLAZAR_INSTALL_BASE_URL, BLAZAR_INSTALL_ENGINE, BLAZAR_INSTALL_MODEL, BLAZAR_BOOTSTRAP, BLAZAR_FORCE_BOOTSTRAP, BLAZAR_AUTO_BOOTSTRAP, BLAZAR_AUTO_DRIVER, BLAZAR_CHECKOUT, BLAZAR_SUDO, BLAZAR_SYSTEM_BIN_DIR, BLAZAR_SYSTEMCTL, BLAZAR_UNIT_PATH, BLAZAR_UNIT_MEMORY_HIGH, BLAZAR_SERVICE_USER, BLAZAR_SERVICE_GROUP — all override the corresponding installer default (release source, version pin, download base, bootstrap behavior, driver install, checkout dir, privilege helper, unit-file location/memory limit, service user/group).
Internal test harness (NOT part of the product interface)
A BLAZAR_VALIDATE* / BLAZAR_STUB_* variable family plus BLAZAR_TEST_MTP_GGUF exists for the repository’s own validation harness (scripts/validate.py). Values and semantics are internal to the test suite and intentionally not documented.
Local development setup
- Toolchain: Rust (stable). On a fresh source checkout, a zero-arg
cargo buildworks;scripts/bootstrap.sh --minimalprovisions a C compiler + Rust toolchain first (announced before acting). - Clone and build:
git clone <repo> && cd blazar cargo build --release # binary: target/release/blazar - Paths the daemon uses (created on demand by
dirs.ensure()):~/.config/blazar/config.toml— configuration<XDG data>/blazar/—blazar.db,models/,engines/,run/(per-child pid + child auth secret files,daemon.log— the daemon’s own durable log, rotated todaemon.log.1at 10 MiB — and per-child.sockunderchild_transport = "unix"),speccache/,sessions/- Engine rows in
blazar.dbrecord their server binary relative to the data dir (engines/<tag>/...), so a data dir move/copy stays relocatable; the out-of-treelocallane (BLAZAR_ENGINE_PATH) keeps its absolute path. - XDG variables absent → falls back to CWD with a loud stderr warning (never silent).
- First engine + model:
blazar engine updatethenblazar pull qwen3-0.6b.
Platform & hardware coverage (honesty rows)
What runs where — stated up front so “why doesn’t lane X work here” is a docs lookup, not a bug report:
- Windows is first-class for the app and the llama.cpp lane (winget,
Scoop,
scripts/install.ps1; Vulkan and CPU compute; CUDA when the driver census finds it).blazar doctornames what your machine actually serves each lane with. - The SGLang lane needs Linux or WSL2. The upstream runtime depends on
uvloop, which does not install on Windows — the lane’s install and doctor output say so instead of failing mid-pip. Other lanes are unaffected. - The MLX lane serves MLX quant dirs (mlx-community). Linux install requires an NVIDIA GPU (
mlx[cuda12]backend); Apple Silicon installs plainmlx. Windows: use WSL2 with GPU passthrough, same as the sglang lane’s guidance. - NPU-class accelerators are not a lane yet. Vulkan already covers
Intel Arc / integrated GPUs for compute today; NPU (OpenVINO-class)
support is an upstream-engine question tracked in
docs/research/2026-10-02-competitor-pain-points-audit.md.
Running the test suite
| Item | Command / fact |
|---|---|
| Unit + integration tests | cargo test --workspace (per-crate: cargo test -p blazar-gateway etc.) |
| Integration suites | per-crate tests/ dirs — e.g. blazar-gateway/tests/ (gateway.rs, semcache.rs, sentinel.rs, structured_output.rs, sessions.rs, compat.rs, crash.rs), blazar-runtime/tests/ (supervisor.rs, engine_manager.rs, tuner.rs, mistralrs.rs, replicas.rs, quantize.rs, models_alias.rs, engine_build.rs), blazar-cli/tests/ (upgrade.rs, config_set.rs) |
| Engine stubs | workspace-root tests/fixtures/ — stub_llama_server.rs, stub_llama_bench.rs, stub_llama_quantize.rs (exposed behind the test-util feature; tests spawn stubs instead of real engines) |
| Full harness | scripts/validate.py — the repository’s end-to-end validation harness (internal; drives real gateway + stubs via the BLAZAR_VALIDATE* env family) |
| Matrix scripts | scripts/bench_matrix.py, scripts/engine_coverage.py |
| Interpreting results | standard cargo test output; suite count: 73 workspace test binaries at v0.19.0 |
Build process
cargo build --release— workspace build (4 crates, resolver 2, edition 2024, MSRV 1.95).unsafe_code = deny, clippy pedantic in CI (INFERRED: enforced via repo lint config).- Engine binaries are NOT built by the workspace by default —
blazar engine updatedownloads official artifacts (sha256-verified, regression-gated);blazar engine build cuda|cpubuilds them locally (auto GPU-arch detection vianvidia-smi, host-compiler matching for nvcc, installsbNNNN-cuda-style tags withllama-server/-quantize/-imatrix/-perplexity/-benchbinaries).
Deployment flow
-
End-user install (recommended): curl sh one-liner from the published repo (override via BLAZAR_REPO); Windows via winget (rustup provisioning included) orscripts/install.ps1; also Homebrew and Scoop packages (packaging/). - Service:
scripts/install.shcan install a systemd unit; unit setsRestartPreventExitStatus=3so bind-conflict exits (code 3) never restart-loop. - Run:
blazar serve(aliasstart) — binds127.0.0.1:11435by default (blazar’s own port, never ollama’s 11434; setport = 11434for an ex-ollama drop-in); port 0 = ephemeral. - Upgrade:
blazar upgrade [--version X] [--dry-run]— sha256-verified self-upgrade. - Rollback engines:
blazar engine rollback/blazar engine use <tag>— side-by-side tags, never destructive.
GPU driver preflight (installer)
Before the engine lands, the installer censuses PCI GPU hardware (lspci, provisioned when missing) and — when the driver userspace is absent — installs it from first-party distro repos only (announce-then-act): NVIDIA via apt’s nvidia-driver / pacman’s nvidia nvidia-utils; dnf auto-installs akmod-nvidia only when RPMFusion is already enabled; zypper and all third-party-only lanes print the exact command instead of running it. AMD/Intel without a Vulkan ICD get the mesa Vulkan drivers. Every install ends with the loud chain: REBOOT, then blazar engine update — which auto-picks the newest CUDA build the installed driver supports (runtimes are bundled; no CUDA toolkit is ever installed). Opt out with BLAZAR_AUTO_DRIVER=0. Windows is advise-only: the PowerShell installer detects NVIDIA hardware without nvidia-smi and links the driver download. blazar doctor warns (nvidia driver / vulkan driver rows) whenever PCI GPU hardware is present but its driver is not.
Feature flags and configuration switches
All switches live in ~/.config/blazar/config.toml; manage via blazar config set/get/unset/list/defaults/edit. Defaults below are the live output of blazar config defaults (the binary’s own serialization — authoritative). Keys marked (optional) exist in config.rs but are omitted from the defaults dump while unset.
Grouped sections (optional spelling)
The flat keys above are the canonical form and what every tool reads and writes. For readability, related keys may instead be written inside one of the group tables below — the loader hoists them to the same flat keys before parsing:
[gateway]
host = "127.0.0.1"
port = 11435
[resources]
default_ctx = 16384
max_loaded_models = 2
Groups: gateway (bind/TLS/CORS/timeouts), routing (engine routing, router, failover), resources (context, model cap, idle ladder, RAM/NUMA), speculative (spec* + spec_draft_*), diffusion (sdcpp_*), whisper (whisper_*), vision (image_*, mtmd_*, mmproj_*), yarn (yarn_*), reasoning (reasoning*), observability (otlp_*). Rules: a key set both at the root and in a group is refused (keep one spelling); a key placed in the wrong group is refused with a hint naming its home group; unknown groups stay hard errors. blazar config set/get and the defaults dump use the flat spelling.
Serving & lifecycle
| Key | Default | Purpose |
|---|---|---|
host / port |
127.0.0.1 / 11435 |
bind address (blazar’s own port; port = 11434 = opt-in ex-ollama drop-in; 0 = ephemeral) |
default_ctx |
16384 |
default context size |
max_loaded_models |
0 |
concurrent model cap (0 = auto from hardware probe, bytes-based VRAM admission) |
idle_sleep_secs / idle_timeout_secs |
300 / 1800 |
child-native GPU sleep → SIGTERM eviction ladder |
agent |
false |
agent-oriented defaults |
sessions |
true |
slot KV checkpoints (--slot-save-path) |
session_bank |
true |
auto _auto-<ctx> bank on evict, restore on respawn (>512 MiB banks dropped) |
session_keep_secs |
900 |
session pin lifetime (0 = off) |
router / router_max_models |
false / 0 |
one child serves all models (--models-preset INI; 0 = upstream default 4) |
engine_check_secs |
86400 |
update-currency check cadence (0 = off; check-only, never auto-install) |
models_autoload |
(optional) | autoload model list at startup |
model_load_timeout_secs |
(optional) | per-kind defaults (180 s precompiled / 600 s sglang first-JIT); a pin overrides all lanes |
Memory, KV & context
| Key | Default | Purpose |
|---|---|---|
cache_ram_mb |
8192 |
weights-mmap cap (auto-capped at 30% physical RAM) |
cache_type |
"" |
KV quant: auto ladder f16→q8_0→q4_0 at 90% VRAM (with a K=q8_0/V=q4_0 differential rung between the symmetric grades), or explicit f32/f16/bf16/q8_0/q4_0/q4_1/iq4_nl/q5_0/q5_1 |
cache_type_k |
"" |
Per-phase K cache quant — set side wins over cache_type for K; a lone set side mirrors to the other when no other grade exists |
cache_type_v |
"" |
Per-phase V cache quant — see cache_type_k; q8_0 K + q4_0 V recovers KV capacity at better quality than symmetric q4_0 |
kv_unified |
(optional) | unified KV pool (device-backed shared buffer; auto-emits when slots > 1) |
kv_unified_per_slot |
0 |
per-slot unified pool sizing |
swa_full |
false |
full-attention SWA override |
ctx_checkpoints |
0 |
context checkpoints |
ctx_extend |
0.0 |
YaRN context extension factor |
no_kv_offload |
false |
disable KV offload |
load_mode |
"" |
mmap / mlock / direct-io |
spawn_mem_guard |
true |
refuse load when MemAvailable < model/2 + 512 MiB |
keep_tokens |
0 |
KV keep-token floor |
no_host |
false |
suppress host info in child |
context_shift |
false |
emit --context-shift only when true (upstream default is disabled since b10833) |
Slots, parallelism & determinism
| Key | Default | Purpose |
|---|---|---|
slots |
0 |
parallel slots (0 = auto: capacity-aware clamp by KV budget + trained ctx) |
deterministic |
true→forces slots=1 |
default false; multi-slot perturbs near-tie logits |
slot_prompt_similarity |
0.0 |
slot pick by prefix similarity ((0..=1], 0 = engine default 0.1) |
adaptive_slots |
true |
adopt slots+1 after ~60 s sustained concurrency (cap 4) |
cache_idle_slots |
true |
keep idle slots warm |
repack |
true |
weight repack for batch-1 decode |
batch_size / ubatch_size |
0 / 0 |
physical/max batch (0 = engine default; raising -ub above 512 = measured regression — leave alone) |
cpu_moe_n / cpu_ffn_n |
0 |
MoE experts / FFN layers on CPU |
Multi-GPU sharding & admission
Automatic tensor parallelism: when weights plus KV exceed the best single card but fit the discrete cards per-rank, the SGLang lane plans the spawn across them (--tp-size N) and the fit ladder judges per-rank demand against the bottleneck card. The same planner awareness extends admission: a load that only makes sense sharded is judged against the combined GPU pool instead of the largest single card, and refusals say so (even sharded across N cards (X GiB per rank)). Manual pins (tp_size, dp_size, pp_size, ep_size, tensor split, device lists) always win — auto-sharding only fires when parallelism is unpinned. The llama.cpp lane has its own auto tensor-split for the same case, and the mistral.rs lane spans by layer mapping: it admits a load that exceeds the best single card yet fits the cards combined and lets the engine distribute layers itself (--device-layers omitted), which permits uneven splits a per-rank planner would refuse.
On all-NVIDIA boxes every sharded spawn is pinned: the child receives CUDA_DEVICE_ORDER=PCI_BUS_ID plus CUDA_VISIBLE_DEVICES naming the first N cards in PCI bus order, so ranks and cards stay aligned on mixed-memory pairs. Any doubt — non-NVIDIA or mixed-vendor census, probe short of ranks — leaves the child env untouched and the engine’s own device mapping in charge.
Admission and queueing: when a model is saturated, Blazar parks requests behind bounded admission rather than allowing uncontrolled concurrency to inflate latency and destabilize the workload. Loads that no amount of waiting can fix — the admission floor exceeds every GPU budget even with the box empty — are refused immediately with the numbers and the levers (blazar fit, smaller ctx/slots, a larger card) instead of timing out in a queue.
Speculative decoding
| Key | Default | Purpose |
|---|---|---|
spec |
auto |
family: off / mtp / mtp-adaptive (PR27210 adaptive MTP, n-max 8 capped at the head — pre-wired, teaches until an engine advertises draft-mtp-adaptive) / eagle3 / dflash / dspark / ngram* (target-side verified-lossless) |
spec_auto_ngram |
true |
spec=auto with no usable draft pair (no pair in the catalog, capacity gate, pair not pulled) falls back to n-gram lookup self-speculation instead of running dense — zero extra VRAM, warning names the knobs to disable it; env BLAZAR_SPEC_AUTO_NGRAM (bench: +92% on patterned traffic, −3% on fresh prose, see bench-artifacts/20260927-spec-ngram-default) |
spec_auto_manage |
false |
Per-model spec governor: two consecutive 60 s windows of queue saturation or draft-acceptance collapse (< 0.15) park speculation for that model (respawn dense via the reshape queue); a sustained quiet streak (10 windows) restores it. Per-request and overlay spec pins always win; the router child is exempt; gauge blazar_spec_governor{model,state}; env BLAZAR_SPEC_AUTO_MANAGE |
ubatch_auto |
false |
Adaptive prefill shaping: two consecutive saturated 60 s windows raise that model’s micro-batch ceiling one rung (--ubatch-size 1024 → 2048, capped — larger buffers OOM small cards); a quiet streak (10 windows) steps back down one rung at a time. Only fills where ubatch_size is unset (a config pin always wins); router child exempt; llamacpp lanes; gauge blazar_ubatch_governor{model,tier}; env BLAZAR_UBATCH_AUTO |
Best-of-N fan-out (per request)
best_of (2–4) is a request knob, not a config knob: send it as a body field on /api/chat, /api/generate, or /v1/responses bodies, or as the X-Blazar-Best-Of header on /v1/messages too. The gateway fans the request out to N parallel child candidates, judges them (schema-valid > finish_reason: stop > first), and returns the winner with usage summed across all candidates; the X-Blazar-Best-Of response header itemizes asked/used/usage. Streaming requests cannot be judged and are rejected with a teaching 400, as is the knob on /v1/chat/completions (byte-faithful passthrough lane — the buffered OpenAI lane is /v1/responses). The fan-out degrades to a single send under queue depth, no free decode slots, or bodies over 1 MiB (counters in /metrics: blazar_bestof_fanouts_total, blazar_bestof_degraded_total).
Agentic cascade routing (per request)
cascade is a request knob on /api/chat: send cascade: ["small-model", "big-model"] in the body or X-Blazar-Cascade: small-model, big-model as a header (header wins and pins the order). Candidates are tried in order and each answer is judged against the same ladder best-of-N uses (2xx + requested schema + clean finish); the first pass is served and later candidates are never sent — a request the small model answers costs exactly one small-model call. When the body names the chain and at least two candidates have measured decode rates (A2 telemetry), the chain is tried cheapest-first. Responses carry X-Blazar-Cascade: tried=… served=… reason=first-pass|escalated|none-passed and usage summed across every attempted candidate; nobody-passed serves the last (strongest) candidate’s answer. Streaming requests are rejected with a teaching 400 (a winner cannot be judged before completion), as are chains over 4 models and bodies over 1 MiB. Counters in /metrics: blazar_cascade_runs_total, blazar_cascade_escalations_total.
| spec_cache | true | persistent ngram lookup-cache-dynamic per model |
| spec_draft_cpu_range … spec_draft_* | see dump | draft-model placement: cpu_range, cpu_strict, device, ngl, threads, prio, prio_batch, cpu_strict_batch, threads_batch, type_k/type_v, override_tensor, n_cpu_moe, cpu_moe |
| spec_draft_backend_sampling | true | sampling on the backend |
| spec_draft_p_min / spec_draft_p_split / spec_draft_poll / spec_draft_poll_batch | (optional) | draft acceptance/split/poll tuning (p_split default 0.10) |
| ngram_size_m / ngram_size_n / ngram_min_hits | 0 | ngram/ngram-map-k/k4v families |
| ngram_mod_n_match / ngram_mod_n_max / ngram_mod_n_min | 0 / 0 / 0 | mod-n window (bounds 1..=1024-class) |
| adaptive_decay / adaptive_target | 0 / 0.0 | adaptive spec tuning |
Reasoning & thinking
| Key | Default | Purpose |
|---|---|---|
reasoning |
"" |
on / off / auto |
reasoning_format |
"" |
none / deepseek / deepseek-legacy |
reasoning_effort |
"" |
minimal / low / medium / high / xhigh / max (bridged into chat_template_kwargs, chat lane only) |
reasoning_budget |
-1 |
-1 unrestricted / 0 immediate end / N budget |
reasoning_budget_message |
"" |
message shown when budget hits |
Vision & embeddings
| Key | Default | Purpose |
|---|---|---|
mmproj_policy |
(optional) | lazy (default; respawn w/ projector on first vision request) / attach / skip; bool compat true=attach false=skip; explicit -mm in extra_args wins |
mmproj_offload / mmproj_auto |
true / true |
projector offload / auto-attach |
mmproj_device |
"" |
projector device |
image_max_tokens / image_min_tokens / mtmd_batch_max_tokens |
0 |
vision token budgets |
embd_normalize |
0 |
embedding normalization |
late_chunking_max_tokens |
8192 |
late-chunking embed cap (413 beyond; per-model late_chunking=true opt-in) |
video_ffmpeg_dir / video_fps / video_timestamp_interval |
"" / 0.0 / 0.0 |
video input (ffmpeg-capable vision models) |
YaRN
| Key | Default | Purpose |
|---|---|---|
yarn_orig_ctx / yarn_ext_factor / yarn_attn_factor / yarn_beta_fast / yarn_beta_slow |
0 / -1.0 / 0.0 / 0.0 / 0.0 |
YaRN rope-scaling knobs |
Scheduling & CPU affinity
| Key | Default | Purpose |
|---|---|---|
cpu_range / cpu_strict |
"" / false |
pin child CPU set |
prio / prio_batch / poll / poll_batch |
0 / 0 / 0 / (optional) |
scheduler priority/poll knobs |
threads_http / threads_batch |
0 |
HTTP server / batch threads (0 = engine default) |
numa |
"" |
distribute / isolate |
main_gpu / split_mode / tensor_split |
-1 / "" / "" |
multi-GPU: primary, none/layer/row/tensor, ratios e.g. “3,1” (per-model tensor_split overlay replaces; empty = inherit) |
devices |
[] |
device pinning (names from blazar doctor) |
override_tensor |
[] |
per-tensor backend overrides |
tensor_preset |
"" |
”” or “moe-cpu-offload” (experts→CPU) |
op_offload |
(optional) | op-level offload |
check_tensors |
false |
tensor validation at load |
Child transport & HTTP
| Key | Default | Purpose |
|---|---|---|
child_transport |
tcp |
child transport: tcp (loopback host:port) or unix (per-child run/<name>.sock, Linux/macOS/BSD only) |
child_auth |
(optional) | unset = auto (TCP children authed, UDS not) / true / false; per-child secret at run/<model>.apikey |
sse_ping_interval |
(optional) | SSE ping (-1 disables; upstream 30) |
server_timeout_secs |
(optional) | child HTTP timeout (upstream 3600) |
chat_template_kwargs |
(optional) | JSON object, shape-validated |
cont_batching |
(optional) | true→--cont-batching, false→--no-cont-batching, unset = engine default |
reuse_port |
false |
SO_REUSEPORT |
lora_init_without_apply |
false |
LoRA init behavior |
warmup |
true |
post-spawn warmup pass |
samplers |
"" |
sampler chain (top_k;top_p;typical); --sampler-seq deliberately not a knob |
engine_env |
table | extra env for children + probes |
child_transport = "unix" — spawn children on Unix-domain sockets instead
of loopback TCP: each child listens on run/<name>.sock in the daemon’s run
directory (next to its pidfile), and the gateway dials it over the same
socket, skipping the network stack and per-request port allocation. Socket
paths are deterministic per model, swept for stale files at daemon boot, and
unlinked when the child is evicted. Path budget is the OS sun_path limit
(~104B): a model name deep under a long data_dir fails fast with a
teaching error naming the offending component. Not available on Windows
(config validation rejects it there). tcp remains the default and is
byte-identical to previous releases.
Engines, downloads & updates
| Key | Default | Purpose |
|---|---|---|
engine_asset |
auto |
asset flavor selection |
update_channel |
latest |
latest (incl. prereleases) / stable — a pin, not a floor; llamacpp currency surfaces annotate upstream’s stable marker when the active build is the one it stamps (b11429 (stable v0.6.0)) |
download_connections |
8 |
parallel byte-range download (≥32 MiB; 1 = single-stream resume; Range-rejecting servers forced single) |
download_speed_limit_mb |
0 |
total download speed cap in MB/s across all pull lanes (0 = unlimited); one shared token bucket (1 s burst) covers model pulls, registry pulls, TTS/whisper voices, and engine-binary assets — BLAZAR_DOWNLOAD_SPEED_LIMIT_MB env overrides |
whisper_stream_chunk_ms |
30000 |
whisper streaming (stream=true on /v1/audio/transcriptions or /translations): decode window in milliseconds, 1000..=120000; WAV files are split at PCM frame boundaries and each window is transcribed in order on the same lazy child — BLAZAR_WHISPER_STREAM_CHUNK_MS env overrides |
whisper_vad_model |
unset | Silero VAD for the whisper-server child (--vad --vad-model): skips silence/noise segments before decode — faster long-audio runs, fewer hallucinated spans. Value = a ggml file name (looked up in the whisper engine dir) or a path. Never auto-pulled; a missing file is a teaching error, unset = no VAD |
whisper_beam_size / whisper_best_of / whisper_entropy_thold / whisper_logprob_thold / whisper_word_thold |
unset | First-class decode knobs for the whisper-server child (--beam-size etc.): beam 5 sharpens accuracy on noisy audio at a decode-speed cost; the thresholds prune hallucinated spans. VAD tuning rides whisper_vad_threshold / whisper_vad_min_speech_duration_ms / whisper_vad_min_silence_duration_ms (require whisper_vad_model); whisper_diarize (stereo input) / whisper_tinydiarize (needs a tdrz model) gate speaker separation. All surface-gated: a knob flag missing from the installed engine is a teaching error at spawn, and double-setting one via whisper_extra_args is refused |
whisper_extra_args |
unset | extra boot flags for the whisper-server child, appended after the pinned loopback/port/model/VAD argv — the lever for engine options no first-class knob covers (e.g. --dtw tiny for token-level timestamps by default). Structure-validated at config load; at child compile each flag is gate-checked against the child’s own --help surface (a misspelling fails fast with a teaching error instead of a dead child); --host/--port/--model/--vad/--vad-model are reserved. BLAZAR_WHISPER_EXTRA_ARGS env overrides (comma-separated; spaces inside a segment are preserved, so values with commas/spaces belong in the TOML list) |
rpc_servers |
"" |
llama.cpp RPC worker endpoints for llama-server children (--rpc, comma-separated host:port): model weights and KV cache split across local + remote devices in proportion to each device’s free memory. Start a worker on a GPU box with blazar rpc worker --host 0.0.0.0 (secure loopback default; the worker accepts the connection, so bind it where the gateway can reach it). The RPC protocol has NO authentication — trusted networks only. Dead endpoints are refused at spawn with a teaching error (preflight probe); blazar doctor shows the whole fleet, and blazar rpc status adds per-worker protocol version + per-device free/total memory (queries the worker wire protocol, not the banner). Spawn-time fit verdicts (admission, the too-large refusal math, the spill-to-CPU warn) count every reachable worker’s device VRAM — a worker unreachable at planning is skipped with a warn and still refused at spawn if down; placement ratios are left to the engine’s own fleet distribution. A per-model rpc_servers overlay replaces this list (empty = inherit) |
lazy_mode |
auto |
lazy spawn mode |
fork_retire_days |
7 |
days after full-architecture supersede before a curated fork lane auto-deletes (0 = never; user forks and pinned lanes never auto-delete) |
keep_tags |
2 |
engine builds retained per kind after an install — newest N survive auto-prune; local and the active tag stay protected on top. 2 = fresh build + one rollback anchor for blazar engine rollback; 1 = an update deletes the previous build outright. Also governs whisper/piper voice-dir retention |
capability_registry_url |
upstream default | curated capability-lane catalog; "" disables lane discovery entirely |
Request-path behavior
| Key | Default | Purpose |
|---|---|---|
singleflight |
true |
coalesce identical NON-STREAM chat (5 s bound) |
prompt_preflight |
true |
refuse unfit prompts (exact /tokenize when estimate >90% of ctx) |
predictive_preload |
false |
predictive model load (LC1) |
preload |
[] |
startup preload list — models spawned right after the listener binds (one at a time, warn-not-fail; env BLAZAR_PRELOAD=m1,m2) |
idle_ram_warm |
true |
after an idle-ladder eviction, re-page the evicted weights into the OS cache (RAM tier) so a re-spawn mmaps warm; skipped under load_mode = "direct-io" |
sentinel / sentinel_stall_secs / sentinel_enforce |
true / 30 / false |
quality observation / stall window / hard 422 mode (per-request X-Blazar-Enforce override) |
child_header_timeout_secs |
120 |
ceiling for awaiting child response headers. Buffered (non-stream) responses emit headers only at completion, so a long generation can look like a wedged child — on expiry the gateway probes the child’s /slots: slots still generating ⇒ teaching 504 (stream the request or raise this knob) and the child stays; slots idle ⇒ wedged child evicted + one in-band retry. 0 disables (blanket transport ceiling only) |
sdcpp_child_header_timeout_secs |
900 |
same ceiling scoped to the diffusion lane, where time-to-headers IS generation time (a frame arrives only when fully computed — minutes at native resolutions on offloaded hardware, longer for video); 0 disables |
sdcpp_cache_mode |
unset | step-caching family for sd-server children (--cache-mode): easycache/ucache/dbcache/taylorseer/cache-dit/spectrum — 1.4-2.2x faster renders with near-lossless quality; unset = exact per-step compute |
sdcpp_cache_option |
unset | cache-family tuning (--cache-option, key=value list, e.g. "threshold=0.25,reset=0"): thresholds/warmup/window for the active sdcpp_cache_mode |
sdcpp_control_net / sdcpp_ip_adapter (+ sdcpp_clip_vision) / sdcpp_motion_module / sdcpp_photo_maker / sdcpp_pulid_weights / sdcpp_upscale_model |
unset | component weight paths for sd-server children (--control-net, --ip-adapter requires --clip_vision — config validation enforces the pair, --motion-module = AnimateDiff SD1.5 video, --photo-maker, --pulid-weights Flux, --upscale-model Real-ESRGAN hires fix). Assets are never auto-pulled; paths point at files you provide |
sdcpp_lora_model_dir / sdcpp_hires_upscalers_dir / sdcpp_embd_dir / sdcpp_audio_encoder / sdcpp_high_noise_diffusion_model / sdcpp_uncond_diffusion_model / sdcpp_backend |
unset | scan directories and advanced components (--lora-model-dir, --hires-upscalers-dir, --embd-dir, --audio-encoder wav2vec2 Wan2.2 S2V, Ideogram4 CFG pair, --backend per-module placement like "clip=cpu,vae=cuda0,diffusion=vulkan0") |
sdcpp_flash_attention |
true |
flash-attention kernels (--fa) for sd-server children: faster attention, less VRAM (measured 25% faster renders at identical quality; set false for legacy kernels) |
sdcpp_sage_attn |
false |
8-bit SageAttention kernels (--sage-attn) in the diffusion model: roughly 2x over legacy kernels — but hard-requires a patched GGML CUDA backend (CUDA 12+, SM80+); off CUDA devices (Vulkan/Metal/CPU) blazar skips the flag with a warning instead of letting the child die at load. extra_args --sage-attn overrides for custom postures |
sdcpp_vae_tiling |
false |
tiled VAE decode (--vae-tiling): fits larger-than-VRAM decodes at some speed cost |
sdcpp_tae |
unset | Tiny AutoEncoder path (--tae): fast low-quality VAE decode for previews/iteration |
sdcpp_rpc_servers |
[] |
RPC worker endpoints (--rpc-servers, comma-joined), the diffusion twin of llamacpp rpc_servers; empty = local devices only |
sdcpp_max_vram |
unset | per-device VRAM budget in GiB (--max-vram): "8" or "cuda0=8,cuda1=6" bounding weight placement; unset = full-device budgets |
sdcpp_params_backend |
unset | explicit weight placement (--params-backend): "disk"/"cpu" or per-module ("diffusion=disk,clip=cpu"); pins placement, disabling auto-fit |
sdcpp_split_mode |
unset | multi-device weight distribution (--split-mode): row (matmul rows, CUDA) / layer (blocks per device) or per-module ("diffusion=row,te=layer") |
sdcpp_conditioning_cache_size |
unset | cross-request prompt-embedding cache depth (--conditioning-cache-size, engine default 4): repeated/shared-prefix prompts skip re-encoding; 0 disables |
sdcpp_model_args |
unset | model-builder key=value list (--model-args, e.g. qwen_image_2_1_prefix_cache=true): family-specific engine levers ahead of first-class knobs |
sdcpp_qwen_prefix_cache_type |
unset | Qwen-Image-2.1 prefix KV cache strategy (qwen_image_2_1_prefix_cache_type model-arg, auto or a type name; upstream master-929+): early prefix-cache scheduling — a large VRAM win on repeated compositions. Composes into sdcpp_model_args; an explicit user entry for the same key wins |
sdcpp_extra_args |
unset | extra boot flags for sd-server children, appended ahead of per-model extra_args (engine last-wins — a flag set in both tiers resolves to the model override, with a compile warning naming the duplicate). This is the lever for boot-time model addons the first-class knobs don’t cover: --control-net, --ip-adapter, --photo-maker, --motion-module, --upscale-model, --lora-model-dir, --hires-upscalers-dir, --audio-vae/--audio-encoder, and friends. Pin flags (--model, --vae, --threads, …) are reserved and the rest manifest-gated exactly like the overlay tier; a --backend here owns device posture (the auto weight-split stands down). BLAZAR_SDCPP_EXTRA_ARGS env overrides (comma-separated) |
sdcpp_tensor_type_rules |
unset | per-tensor quant overrides (--tensor-type-rules, regex=type list, e.g. model.=q6_k,vae.=f16): quantizes matching weight groups independently for tighter VRAM |
decode_policy |
free |
ollama_compat lane decode constraint: free lets the model choose prose or a tool call; strict attaches a GBNF grammar confining tool-bearing completions to whitelisted <tool_call> envelopes (names must be request tools, arguments structurally valid JSON) |
raw_lane_max_tokens |
2048 |
server-side cap for ollama_compat raw-completion generations when the caller omits (or exceeds) a cap; 0 disables (overlay wins over global) |
pii_scrub |
true |
redact emails/bearer secrets/IPv4 in why/watch (on by default; set false for verbatim output) |
reasoning interplay |
— | see Reasoning table |
Federation: peers behind one gateway
[[remotes]] names other OpenAI-compatible servers (another blazar box, vLLM, an MLX server — anything speaking /v1/*). Requests for <remote-name>:<model> always route there explicitly. On top of that, with remote_fallback = true (default), a bare model name the local store does not know routes to whichever peer lists it: the gateway keeps a 60-second TTL cache of each peer’s /v1/models listing, prefers the least-busy live peer, and never sends your caller credentials along — the peer’s configured key rides instead, exactly as with explicit prefix routing. Locally-ambiguous names stay local (that is a naming problem), and a model no peer serves gets the ordinary local 404 teaching. Peer listings also merge into GET /v1/models (snapshot of the cache; the first fallback warms it). Set remote_fallback = false to roll the whole feature back to pure-prefix routing.
Fallback covers the OpenAI surfaces (/v1/chat/completions, /v1/completions, /v1/embeddings, /v1/images/generations), and the Ollama-compatible /api/chat (the request is translated to the peer and the reply back into ollama shape). /api/generate stays local-only: it refuses remote prefixes explicitly, so fallback honors the same contract. When a peer starts serving a model inside the TTL window, the first miss for that name triggers one throttled re-probe per peer (at most one every 10 seconds), so new peer models are found without per-request probe traffic.
Remote URL scheme ladder
The bearer key of a [[remotes]] entry must never travel in cleartext off-machine, so remote URLs are classified at boot:
| URL shape | Boot behavior |
|---|---|
https://… (any host) |
accepted |
http:// to 127.0.0.1 / localhost / [::1] |
accepted (never leaves the machine) |
http:// to a private/LAN address (10.*, 192.168.*, 172.16-31.*, 169.254.*, fd00::/8, fe80::/10) |
accepted with a logged warning — the key rides unencrypted on the LAN |
http:// to anything else (public IP or hostname) |
refused with a teaching error |
Hostnames other than localhost count as public (no DNS at validation time) — use an IP literal or https:// for private hosts with names. A lab that genuinely must reach a public cleartext endpoint opts in per remote:
[[remotes]]
name = "legacy-box"
url = "http://203.0.113.7:8000"
allow_insecure_http = true # loud warning instead of boot refusal
Structured output across dialects
One guarantee, three request dialects: the response never silently violates
the schema the caller asked for. Engines enforce schemas natively per request
(llama.cpp / mistral.rs convert the schema to a GBNF grammar); blazar adds the
cross-dialect plumbing and the post-hoc sentinel check. Every constrained
response carries the receipt header x-blazar-structured-output
(json_schema / json_object / gbnf) so clients can prove the constraint
was active; unconstrained responses omit it.
| Dialect | Caller asks via | Translation | Guarantee |
|---|---|---|---|
| OpenAI | response_format (json_object / json_schema) |
pass-through to child | child grammar + sentinel 422 on violation (non-streaming, sentinel_enforce / X-Blazar-Enforce) |
| Ollama | format: "json" or a JSON-schema object |
format → response_format (translate.rs) |
same as OpenAI once translated |
| Anthropic | forced tool: tool_choice {"type":"tool","name":…} + tool input_schema |
→ OpenAI forced-function tool_choice |
child binds the named tool’s schema; nameless forcing is rejected 400 at the boundary |
Failover chains ([[failover]])
An ordered list of local-or-remote targets published under one alias: a
request naming the alias sticks to the first healthy target, and on failure
(5xx, 404, or a blazar admission refusal like an honest fit rejection) the
next target serves inline. A failed target is benched for
min_residence_secs (default 30 — the anti-flap window); when every target
is benched the sticky one keeps serving. All three dialect lanes honor
chains (OpenAI, /api/chat, /v1/messages — the Anthropic lane is
local-targets-only and teaches why). Observation: x-blazar-failover +
x-blazar-served-model response headers, failover_switched events on
/api/events, and /api/failover admin routes (list, detail, pin, unpin —
a pinned target keeps serving even while failing). Keys admission re-charges
per retry attempt (each attempt is real work).
[[failover]]
name = "voicecheck"
alias = "primary"
min_residence_secs = 30 # anti-flap bench window per failed target
[[failover.targets]]
model = "qwen3-8b:q4_k_m" # local target (bare model)
[[failover.targets]]
remote = "bigbox" # remote target (existing [[remotes]] name)
model = "qwen3-8b:q4_k_m" # forwarded as bigbox:qwen3-8b:q4_k_m
MCP tool catalog ([[mcp]])
Declared MCP servers — local stdio processes or remote Streamable HTTP
endpoints — contribute tools to any chat request, on an opt-in
per-request basis: body field "mcp": "all" (or one server name) or
header x-blazar-mcp. The gateway namespaces the tools
(mcp__<server>__<tool>), injects them alongside caller tools, executes
the model’s tool calls itself (MCP 2025-06-18: initialize → tools/list →
tools/call), and feeds results back — every engine gets tool use with
zero child-side MCP support. Bounds: 4 tool rounds per request,
timeout_secs (default 30, max 600) per call, streaming + mcp refuses
with a teaching 400 (the loop is buffered by design). GET /api/mcp
lists the catalog report-only. stdio sessions shut down with the daemon
(stdin close → wait → SIGTERM); HTTP sessions get a best-effort session
DELETE. Security posture: the config owner declares the servers —
per-request opt-in only EXPOSES them to keyed clients, it never lets a
request declare a new command or URL; the gateway logs tool name +
duration, never tool payloads.
Mixed turns (the model calls a caller tool and an mcp__ tool in the
same response): the gateway executes its namespaced calls and answers
the caller-owned ones with an explicit placeholder result naming who
runs them — no tool_call is left dangling, so the next round stays
protocol-complete.
For stock clients that cannot add a field or header (plain OpenAI SDK apps, third-party tools), set a config-wide default:
mcp_default = "all" # "all", one server name, or "none"; validated at boot
Resolution order per request: x-blazar-mcp header > body "mcp" field >
mcp_default > none. "none" is the explicit opt-out — requests that must
not see the catalog send it even when a default is configured.
[[mcp]]
name = "fetch"
command = ["uvx", "mcp-server-fetch"] # stdio transport
timeout_secs = 30 # per tools/call ceiling
[[mcp]]
name = "calc"
command = ["python3", "-m", "mcp_server_calculator"]
[[mcp]]
name = "remote" # Streamable HTTP transport (set
url = "http://mcp.internal:8808/mcp" # exactly one of command / url)
[mcp.env] # optional, stdio only — forwarded
CACHE_DIR = "~/.cache/mcp" # to the process
An MCP server is a program the gateway launches and lets a model call — treat it as a trust boundary. By default a stdio child inherits the daemon’s whole environment; a hardened server starts from an empty one and re-inherits only what you name (stdio-only options, validated at boot):
[[mcp]]
name = "fetch"
command = ["/opt/mcp/fetch-server"] # absolute path: PATH is scrubbed
env_clear = true # start from an EMPTY environment
allowed_env = ["PATH", "HOME"] # the only vars it may re-inherit
cwd = "/opt/mcp" # pin the working directory
max_output_bytes = 1048576 # cap one protocol message at 1 MiB
[mcp.env]
FETCH_TIMEOUT = "30" # explicit env always wins
Scrubbed children can’t find their binary via PATH unless you allow it
back — prefer an absolute command path, or add "PATH" to
allowed_env (Windows also needs "SYSTEMROOT"). The gateway logs a
bounded slice of each child’s stderr (first 32 KiB) at warn so server
diagnostics surface without flooding the log.
Python SDK (sdk/python)
A zero-dependency client (stdlib urllib + raw-socket WebSocket only) for
the native surface: chat() with tools / best_of / mcp, SSE
streaming, ps(), explain(), failover(), mcp_servers(), completion
metadata_update(), Anthropic-dialect batches
(anthropic_batch_create/_get/_results/_cancel/_delete), realtime
voice via blazar.realtime(model) → RealtimeSession.voice_turn(pcm)
(PCM16 mono audio in and out), TTS via blazar.speech(voice, text,
response_format=...) (returns (content_type, audio_bytes); the full
wav/pcm/pcm16/mp3/opus/aac/flac family), voice discovery via
blazar.voices(search=...), and transcription via
blazar.transcribe(path, response_format=..., **knobs) (JSON family
returns a dict — including two-speaker diarized_json on stereo input —
text/srt/vtt return the raw string; list kwargs like
timestamp_granularities are sent per-value). Teaching errors surface
verbatim in BlazarError. Run the demos against a live gateway:
PYTHONPATH=sdk/python python3 sdk/python/examples/demo.py qwen3-0.6b:q4_0
BLAZAR_URL=http://127.0.0.1:11435 PYTHONPATH=sdk/python \
python3 sdk/python/examples/realtime_voice.py qwen3-0.6b:q4_0
Realtime voice (/v1/realtime)
A WebSocket lane that chains the local whisper child, any chat model, and a
piper voice into conversational voice turns — no external service. Connect
with ?model=<chat-model>&voice=<piper-voice>&stt_model=<whisper-size>
(defaults: first voice / whisper-1), authenticate with the same bearer key
as every other route, then drive the protocol: input_audio_buffer.append
(base64 PCM16 mono, or raw binary frames) → commit →
input_audio_transcription.completed → response.audio_transcript.delta /
.done → response.audio.delta chunks / .done. Session control:
session.update (voice / stt_model / instructions — applies from the next
turn), input_audio_buffer.clear, response.create (force a turn on
buffered audio), response.cancel (honored between pipeline stages), and
conversation.item.create (text items feed later turns as context, capped
16 items / 8192 chars). Bounds: 20 MiB audio
buffer, 120 s idle timeout, 600 s hard session TTL. Prepare the lanes once:
blazar whisper --pull tiny and blazar tts --pull en_US-amy-medium.
Optional system ffmpeg (on PATH) unlocks the lossy TTS output formats
(mp3/opus/aac/flac on /v1/audio/speech, both buffered and
"stream": true live streaming); without it the lane
serves wav/pcm and answers lossy requests with a teaching 400 naming
the install hint. Nothing else depends on it.
Security, TLS & network
| Key | Default | Purpose |
|---|---|---|
keys |
[] |
[[keys]]: name/key/models/rpm/tpm/daily_tokens/max_concurrent/weight — non-empty list activates auth |
tls_cert / tls_key |
"" |
PEM pair (both-or-neither) |
cors_origins |
[] |
browser CORS policy: local origins (localhost/127.0.0.1/[::1]/0.0.0.0 any port, plus app:///file:///tauri:///vscode-webview:///vscode-file:// webviews) are always allowed; listed entries are added on top; "*" = any origin |
remotes |
[] |
[[remotes]]: name/url/key; <remote>:<model> routing; duplicate names = pool (3 fails → 30 s markdown, least-inflight LB, prefix stickiness); see Federation: peers behind one gateway above for fallback semantics |
server_tools |
(optional) | EXPERIMENTAL server-side tools (CORS→localhost when set) |
server_tools_runtime |
(optional) | docker:/podman:/ssh: runtime for tools |
mcp_servers_config / mcp_servers_json |
(optional) | MCP server defs (file vs inline JSON, mutually exclusive) |
Observability
| Key | Default | Purpose |
|---|---|---|
log_level |
(optional) | INFO default; RUST_LOG wins |
audit_log |
true |
identity+outcome audit jsonl (<data>/log/audit.jsonl, 16 MiB rotation; one append per generation request — on by default, set false to disable) |
otlp_endpoint / otlp_service |
"" / "blazar" |
OTLP traces/metrics export |
lookup_cache_static / lookup_cache_dynamic |
(optional) | read-only / refreshed lookup caches (must exist) |
Tables
| Table | Purpose |
|---|---|
[warm_peg] |
default = true; per-engine overrides: sglang + mlx (blocking peg, JIT venv class), llamacpp (detached peg), mistral.rs and sdcpp never peg |
[sdcpp] |
(no section — by design) diffusion tuning rides request parameters (steps/cfg/seed/size on POST /v1/images/*), not config knobs; spawn flags (--vae/--llm/--t5xxl…) come from the model’s component set; extra_args passes manifest-probed flags (--backend clip=cpu,vae=cuda0,..., --lora-model-dir, --vae-tiling…) |
[engine_routing] |
mode = "auto"\|"manual" (auto = per-model routing; manual = byte-identical single active engine), policy = "quality"\|"latency"\|"throughput" |
[semantic_cache] |
enabled = false, ttl_secs = 600, threshold = 0.9, max_entries = 256 (opt-in; per-request X-Blazar-Cache overrides) |
[mistralrs] |
mistralrs_pa_memory_fraction (0.05..=0.95), mistralrs_paged_attn (false = classic KV sized from ctx — fits big vision models on 8 GB) + MistralrsTuning knobs; pa_memory_mb = absolute paged-KV MiB for shared GPUs — when set, every --pa-memory-fraction source stays silent (the engine refuses both on one command line); chat_template overrides the lane’s template; isq = in-process quantization at load ("q4k"/"q8_0"/numeric 2..8 — shrinks the resident set, admission stays checkpoint-based so it stays conservative), imatrix/calibration_file sharpen ISQ k-quants, isq_organization = default/moqe |
[mlx] |
MlxTuning knobs for the mlx-lm server lane: draft_model + num_draft_tokens (speculative decoding), kv_bits + kv_group_size + quantized_kv_start (KV quantization — upstream serves one request at a time with kv-bits set), prefill_step_size, prompt_cache_size/prompt_cache_bytes, decode_concurrency/prompt_concurrency, chat_template{,_args}, use_default_chat_template, trust_remote_code, adapter_path; all manifest-gated (warn-and-skip on engines lacking the flag); per-model via model_overrides.<name>.mlx |
[sglang] |
SglangTuning knobs (cache_report defaults ON for blazar-managed children — it powers ps HIT and per-model metrics; set false to opt out; radix_eviction_policy lru/lfu/slru/priority, session_radix_cache, mixed_chunk, …); VRAM ladder flags (--mem-fraction-static, --cpu-offload-gb, --kv-cache-dtype, --context-length) owned by the profile compiler; explicit mem_fraction_static/kv_cache_dtype pins win. Speculation (opt-in, default off): spec_algorithm = "ngram" (draft-free prompt-lookup; DFLASH is not offered — sglang 0.5.21 requires a draft-model path for it) + spec_num_steps / spec_num_draft_tokens; engage adds a verify-KV/graph memory reserve to the ladder. 2026-10-09 receipts (qwen3-1.7b, RTX 4070): ngram lost to dense at every concurrency (−16% C1, −44% C8, +12 s cold) — hence opt-in; the 2026-10-09 greedy-only constraint and attention_backend = "triton" pairing are retired (2026-10-17 live receipts: temperature 0.7-1.0 requests complete on both sampling backends after the CUDA compiler-trio install pin; flashinfer backends work when pinned, triton stays the toolchain-less default). The EAGLE family uses the llamacpp-style draft-pair lane instead (spec = "eagle3" + a pulled safetensors draft). Depth knobs (2026-10-10): speculative_eagle_topk, speculative_accept_threshold_single/_acc (0..=1, spec arms only), mm_attention_backend, grammar_backend, retraction_policy (length/priority — priority requires enable_priority_scheduling = true, enforced at config-validate), enable_two_batch_overlap, enable_tf32_matmul |
[model_overrides.<name>] |
per-model REPLACEMENTS: ctx, slots, deterministic, spec, lazy_mode, loras, extra_args (manifest-validated), cache_type, cache_type_k, cache_type_v, kv_unified, ctx_extend, cpu_moe_n, cpu_ffn_n, override_tensor, devices, warmup, reasoning_budget, reasoning_effort, reasoning, replicas (>1 = prefix-affinity routing), pin (skip in capacity-eviction victim filter), chat_template (XOR chat_template_file), sampler_defaults, spm_infill, late_chunking, rpc_servers, tensor_split, mmproj, sglang, mistralrs, engine (tag or kind; wins over both routing modes) — deny_unknown_fields: typos fail loudly |
Full key grammar and examples: blazar config --help (CONFIG_EXAMPLES, CONFIG_SET_EXAMPLES), and blazar config defaults prints the live default file.