#11141·unsloth

Studio: expose prefill progress over the API so clients can show the wait before the first token

Author: kpulikCreated Sep 16, 2026Updated Sep 16, 2026

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 /slots and GET /v1/slots both return 404, and GET /props reports "endpoint_slots": false, so llama.cpp's own per-slot progress is not reachable through Studio.
  • GET /api/inference/monitor reports 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-generations carries a handle, a model and a started_at, and nothing that advances.

Either of these would be enough:

  1. Add the counters to /api/inference/monitor, per active generation: prompt tokens processed, prompt tokens total, and a phase marker (prefill vs generating). A client can then poll and render a real percentage.
  2. Enable the llama.cpp /slots passthrough (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.