Repository navigation
feat(server): report per-request prefill and decode timings in api responses - #814
Conversation
…refill and decode timings in api responses
|
@jason-fxz Once MTP/draft token generation is done, I can wrap up this PR. Does that timeline work for you? |
|
MTP is in (#836, #844), so this can go ahead now. Before adding the draft fields, the spans need to come from the scheduler instead of the API handler. Today Scheduler The scheduler should keep a small per-request Semantics All times are wall-clock times measured in the scheduler, not in the API process.
These spans line up with vLLM's per-request metrics: Output (llama.cpp b10408+
|
agammann
left a comment
There was a problem hiding this comment.
The new /v1/completions timing accumulator spans the entire for prompt in prompts loop, but this endpoint already accepts an array of prompts and processes them sequentially. With two prompts, first_output_at is set on the first prompt and last_output_at is updated on the second, so predicted_ms includes the second prompt’s prefill/queue time, while prompt_ms includes only the first prompt’s prefill even though prompt_n sums both. The reported prefill/decode rates are therefore wrong for batch completions. Could you measure each prompt separately and sum its prefill/decode spans (or document and report a distinct aggregate wall-clock metric), with a two-prompt timing test?
…nd --enable-timings-report
a54a215 to
8b78f8a
Compare
|
@jason-fxz @agammann all addressed in 8b78f8a, rebased onto current main.
Tests:
|
|
@void-mckenzie
|
…qstats with perf_counter
|
accepted drafts now count LEt me know if this looks good! Thanks! |
Upstream dacac5f..628b88f. Conflict resolutions: - FlashML-org#814 timings: upstream's final version replaces the PR head next adopted (a4a62a5). `timings` now comes from upstream's scheduler-side ReqStats (queue_ms, unrounded spans, draft_n/draft_n_accepted) on the new DetokenizeMsg/UserReply `timings` field; next's old API-side timings_block / gen_timings_block are gone, so there is one source. Option A per user: --enable-timings-report defaults ON on next (BooleanOptionalAction, --no-enable-timings-report turns it off; upstream defaults off). FlashML-org#504 metrics are unchanged: GenTimer + prefill_ms wire field, now on GenDone/GenResult.gen_timings (GenDone/GenResult.timings is upstream's dict); GenTimings drops its unused prompt_ms/predicted_ms. The ReqStats stamps were added to next's own _drain_single_token and _drain_multi_token paths (_drain_verify/MTP came from upstream). DFlash draft counters come from a new ForwardOutput.draft_len (verify_len - 1). Streams carry timings on the last chunk only (upstream). /v1/completions keeps next's n>1 fan-out, disconnect aborts and error codes, and reports prompt 0 / choice 0 timings. Responses passes both metrics= and state=; in streams timings ride the event data, not the response snapshot. - FlashML-org#790, FlashML-org#651: identical to next's earlier adoptions (e920eb4, 56ed267); no code change. For the FlashML-org#651 test, next's pool-pressure variant (371d985) is kept. - FlashML-org#854 attention: next's fp8/nvfp4 KV branches are kept, and every slot load takes upstream's KV_OFFSET_DTYPE cast. - FlashML-org#764 engine: upstream's _disable_unsafe_cpu_moe_graphs sits next to next's DFlash helpers. - FlashML-org#851/FlashML-org#845/FlashML-org#856/FlashML-org#858/FlashML-org#859: no conflicts beyond docs; cli.md merges next's DFlash rows with FlashML-org#856's GGUF/mtp sidecar wording. - core.py: Req gains `stats`. Upstream's output_token_counts is dropped, since next does penalties through token_pool. - tokenizer/server.py, message/*: both next's prefill_ms/logprobs and upstream's timings are kept. Assisted-by: Claude Opus 5.5
Upstream merged FlashML-org#651, FlashML-org#790, FlashML-org#814 (adopted here before), and FlashML-org#764, FlashML-org#845, FlashML-org#851 (deferred here); they now come through the sync. FlashML-org#851 was marked closed by the render before upstream/main had its squash commit. Assisted-by: Claude Opus 5.5

Closes #813. Production deployments front FreeToken with proxies and dashboards that track per-request stats, and those read prefill/decode rates and cache hits from the response itself; without this block the numbers silently go missing. Every chat completion, completion and Responses response carries a llama.cpp-shaped
timingsobject next tousage(fields and arithmetic fromserver_slot_stats::to_json), so frontends like llama-swap and Open Web UI show them. On streams it rides the final finish chunk as well as theinclude_usagechunk, like llama.cpp.prompt_ncounts only the tokens evaluated after the cached prefix, and decode rates divide byn_gen - 1(the first token rides the last prompt batch), matching llama.cpp exactly, socache_n + prompt_nreconstructs the prompt total.cache_nis unconditional (llama.cpp and stats semantics);--enable-cache-reportstill gates onlyusage.prompt_tokens_details.cached_tokens./v1/messagesis left out on purpose: llama.cpp emits notimingsthere either (to_json_anthropickeeps the strict Anthropic schema, with the cache hit inusage.cache_read_input_tokens), and FreeToken already reports that field under--enable-cache-report. Adding an off-spec key would be looser than llama.cpp itself.Verified
pytest tests/server/: 627 passed, 3 new tests (llama.cpp arithmetic, edge shapes, span capture) in the existing accounting file.ft serve ... --moe-cache-size 800 --max-prefill-length 12288): a 19,008-token cold prefill reports 1727 t/s over 11.0 s (2 chunks); a 1,800-token coding decode reports 16.31 t/s / 61.29 ms per token; a repeated 1,300-token prompt reportscache_n: 1280andprompt_n: 20, summing to the prompt total. All four surfaces (chat stream + full, completions, Responses) emit the block.