Distill model ([llm.distill] / [llm.distill.remote])

Part of the config reference. For the feature itself, see Issue distillation.

[llm.distill] configures the model half of issue distillation — the LLM pass that fills a distilled record’s root_cause / decision / outcome fields. Like [llm.dream] it is out-of-process, opt-in, and gated: rag-rat distill extract builds the deterministic substrate with no model, and rag-rat distill drain runs the model pass only when enabled = true. The serving config lives under [llm.distill.remote], mirroring [llm.dream.remote] — a connect endpoint (an already-running chat server) or an ephemeral cookbook-provisioned GPU box.

[llm.distill]
enabled = false             # off by default — `distill drain` refuses if work is pending and this is false

With no [llm.distill.remote] block, distill defaults to the validated 30B ephemeral box — an on-demand vLLM GPU provisioned through the cookbook (the same mechanism embeddings and dream use). Distillation prompts are dense (full commit bodies + the fix diff + coalesced partners + cross-refs), so a small local model produces poor records and CPU inference is impractical at corpus scale — the big model earns its place. This is the exact config the full-corpus verification ran on; the equivalent explicit block:

# DEFAULT when [llm.distill.remote] is omitted, shown explicitly:
[llm.distill.remote]
backend  = "vllm"                                   # ollama | vllm  (infinity is embed-only — rejected here)
cookbook = "@rag-rat/cookbook modal"                # provision an ephemeral box (mutually exclusive with endpoint)
gpu      = "L40S"                                    # provider-specific GPU class (validated at provision time)
model    = "Qwen/Qwen3-30B-A3B-Instruct-2507-FP8"   # HF id for vllm (an ollama name for the ollama backend)
provision_timeout_s = 1500                           # cold-pull budget; stays under the box's 30-min hard lifetime
request_timeout_s = 240

On the model-comparison run this beat a 4B on every content metric (rejected-alternatives coverage, root-cause coverage, qualified anchors) while ~3× faster and ~20% cheaper per thread (MoE, ~3B active); a 4B on an L4 is the constrained fallback. Nothing is provisioned until you set enabled = true and the drain has pending work — a zero-work run never cold-starts a paid box.

Connect to a running server instead. To point distill at an already-running OpenAI-compatible chat server (a local Ollama, a shared vLLM, …) rather than provisioning a box, set endpoint in place of cookbook:

[llm.distill.remote]
backend  = "ollama"
endpoint = "http://localhost:11434"     # OpenAI-compatible chat server (mutually exclusive with cookbook)
model    = "qwen3:4b-instruct"          # server-side model name
# auth_env = "DISTILL_TOKEN"            # optional: names the env var holding the bearer token for a protected endpoint
request_timeout_s = 240

A local 4B connect is cheap but low-quality on distill’s dense prompts — fine for a smoke test, not a corpus backfill.

provision_timeout_s. A 30B re-downloads ~30 GB of weights from Hugging Face on a cold box and can exceed the vLLM boot default (15 min). The default (1500 s / 25 min) gives that cold pull headroom while staying under the cookbook box’s hard 30-min lifetime — a box that spends its whole budget provisioning still has room to serve at least one full request_timeout_s request before it self-destructs. Don’t raise it past ~28 min: the box vanishes at 30, so a larger budget only fails the slow cold start it was meant to cover. The first cold box is slow; a clean teardown publishes the HF + compile cache markers, so every later box boots in ~2 min.

request_timeout_s. Keep this low (≈240 s, not the 900 s dream uses). A bounded record generates in well under a minute, but a rare guided-decoding whitespace loop otherwise blocks the sequential drain for the full timeout; a low cap fails it fast and re-queues the thread. See #874.

Running the pass

rag-rat distill extract              # deterministic substrate only — no model, no cost
rag-rat distill drain --limit 100    # extract, then drain up to 100 queued threads through the model

The box is provisioned only when there is pending work (a zero-work run never cold-starts a paid GPU), and torn down when the run ends. The client speaks the standard /v1/chat/completions route (temperature 0, no streaming, guided JSON where the backend supports it).

Batch long runs. The cookbook caps a box’s lifetime (~30 min), so a single drain cannot finish a large corpus on one box — size --limit to one box’s serving window (~70–150 threads by thread size) and loop distill drain until the queue empties. The queue is resumable and every completed record is durable, so a killed box loses nothing. See #875.

Source: docs/config/distill.md · synced from cq27-dev/rag-rat