Studio: expose prefill progress over the API so clients can show the wait before the first token
Problem or Use Case
On a large GGUF, the gap between an OpenAI-compatible request and its first token is
dominated by prompt processing, and it can run into minutes. Measured here on
Qwen3.8-27B UD-Q4_K_S, 64k context, one slot, speculative decoding off, Apple M4 Pro,
time from request to first output byte on /v1/chat/completions with stream: true:
| prompt | kv_cache_dtype: iq4_nl |
kv_cache_dtype: q8_0 |
|---|---|---|
| 2,000 tokens | 37.8 s | 28.5 s |
| 8,000 tokens | 327.0 s | 84.6 s |
PR #10911 fixed the transport half of this: the passthrough now paces : keep-alive
comments through the silent window, so clients and proxies stop tearing the stream down.
What it cannot do is tell the user anything, because an SSE comment carries no payload and
every OpenAI SDK discards it. So a five minute prefill still presents to the user as a
stalled application with no way to tell it apart from a hang.
Studio's own UI does not have this problem, because it can read the engine directly. Every external client is left guessing.
Proposed Solution
Publish the prefill progress Studio already computes. The numbers exist on both sides of the boundary today:
- llama-server logs them per request:
slot print_timing: id 0 | task 24 | prompt processing, n_tokens = 4396, progress = 0.63, t = 73.19 s / 60.06 tokens per second - Studio's backend logs its own rollup:
{"event": "engine_stats", "gen_tok_s": ..., "prompt_tok_s": ..., "running": 1, "waiting": 0}
Neither reaches an API client. Concretely:
GET /slotsandGET /v1/slotsboth return 404, andGET /propsreports"endpoint_slots": false, so llama.cpp's own per-slot progress is not reachable through Studio.GET /api/inference/monitorreports only coarse state. Polled at t+22s, t+32s and t+42s during an 8,000 token prefill it was byte-identical every time:
{"status":"generating","active_model":"unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_S",
"context_length":64000,"active_requests":1,
"queue":{"capacity":1,"active":1,"queued":0,"free":0}, ...}
GET /api/inference/active-generationscarries a handle, a model and astarted_at, and nothing that advances.
Either of these would be enough:
- Add the counters to
/api/inference/monitor, per active generation: prompt tokens processed, prompt tokens total, and a phase marker (prefillvsgenerating). A client can then poll and render a real percentage. - Enable the llama.cpp
/slotspassthrough (or a filtered subset of it), which gives the same information through an interface clients already understand.
Option 1 fits the existing API surface better, since /api/inference/monitor is already
the place a client looks to see what the server is doing.
Why it matters beyond one client
Any agent or app driving Studio over /v1 hits this: the ones in #6380, #9171 and #10306
are all external clients. Progress telemetry is what lets them distinguish "the model is
working on a long prompt" from "this request is dead", which is the same distinction
#10911 gave the transport layer and which the application layer still cannot make.
Feature Type
API / integration; observability
Scope
Small to medium. The values are already computed and logged; this is plumbing them onto an existing response.
Source: unslothai/unsloth