Repository navigation
qwen27: TF_Q27_PROFILE logs the round's class GPU times, and replies carry gpu_seconds - #1215
Conversation
ashhart
left a comment
There was a problem hiding this comment.
Thanks @grantoverton. Per-class GPU times will be useful. Rebased onto current main, it needs these before an A/B:
gpu_secondsis added to every Qwen3.8-27B reply (qwen27_lanes.zigsets it unconditionally). New reply fields must stay off unless asked for, so please only set it whenTF_Q27_PROFILEis on.- 21e0ad7 (varied tokens in
measure()and a new cost-cache key) changes every drafted server's cost table and depth choice on the dense model. Its rationale is about expert rereads in the MoE stack, which isn't on main. Please move it to its own PR with its own A/B. gpu_secondsadds the whole shared round to each stream (the commit says "the share"), and DFlash drafter time isn't counted. Either is fine if the docs and code agree.- f8c63b6 fixes a regression 6e03930 introduced (span splitting fed stale state to the drafter), so please squash them. That bug also means a profiled run needs to equal an unprofiled run token for token; we'll check that on a Mac.
- Smaller:
TF_Q27_PROFILE=0or an invalid value currently means 64 (better: off, with a warning); "rounds" also counts prefill passes;holdssaturates instead of releasing the trace it held; and there are no tests for the reply field, the env parsing or the hold balance. Please also drop tool trailers and card IDs from commit messages.
The branch also sits on 22 commits from the unlanded Ornith/JointHead stack. Rebasing onto main and keeping only this PR's commits makes it reviewable (we did that locally and it applies with small conflicts).
|
Thanks @grantoverton. The round-class GPU timings are useful on their own, so we'll finish this one on our side as a single commit crediting you: |
…d replies carry gpu_seconds (ashhart#1215) @grantoverton's ashhart#1215, finished on main as one commit. TF_Q27_PROFILE=N attaches the gpu_profile trace to a native Metal Qwen3.8-27B server and logs each kernel class's GPU time every N decode rounds (1 means every 64). The DFlash2 drafter is its own class: a draft frame's buffers are recorded when it finishes. While a frame is open the trace's spans keep the frame's encoder, so a span's own buffer cannot overtake the frame's pending ops and feed the drafter stale state (the fix in f8c63b6, squashed into 6e03930's change). As our review asked: - gpu_seconds is set only with TF_Q27_PROFILE on. Without it every reply is byte-identical to before. - It is the GPU-busy time of the target's forward passes the reply ran in, prompt pass included, with the spans' and the Neural Engine share's own buffers. A shared round counts whole in each reply that shared it, and the drafter's passes are not in it; docs/api.md says the same. - TF_Q27_PROFILE: unset, empty or 0 is off, 1 is every 64 rounds, N is every N, and any other value leaves profiling off with a warning. - Rounds are decode passes: a served prompt pass runs with the trace paused, unsplit and uncounted. - A frame's hold gives back the trace it took (not the one its profiler names at release) and never saturates. - 21e0ad7 (varied tokens in measure() and a new cost-cache key) is left out, for its own PR and A/B. test-q27-profile-host checks the trace (rounds, records, holds, the pause and the value), the lane host's stats with an untimed and a timed fake backend (same tokens and counts, gpu_seconds only when timed), and the OpenAI handler's reply bytes, whole and streamed: the profiled reply less its gpu_seconds member equals the unprofiled reply byte for byte. Co-authored-by: grantoverton <65690763+grantoverton@users.noreply.github.com>
f8c63b6 to
26dff11
Compare
|
Merged, thank you @grantoverton. We finished it on your branch as one commit with you as co-author. On an M5 Ultra with Qwen3.8-27B and DFlash2:
It will be in the next release. |
|
Thanks @grantoverton: |
Part of #1190. Stacked on #1125, #1189, #1191 and #1192; base for review: the #1192 head 7f82411. This branch carries its parents' commits until they merge. Review the last three commits:
TF_Q27_PROFILE=1logs each round's class GPU times, and replies carrygpu_seconds. Opt-in instrumentation, off by default.21e0ad7: varied draft tokens in measure()
Constant-token windows drew token 1 on every row. Each window's expert rereads were therefore priced as L2 hits, and the cost tables understated expert diversity. The draws are now a
DefaultPrngseeded by the window width, deterministic from run to run, and the pending token varies too. The window cost tables shift for every serve, so the cost key gains a varied-drafts part and stale cached costs are retired rather than compared.native/qwen27_lanes.zigonly (+7/−3).6e03930: TF_Q27_PROFILE class timers
The
gpu_profile.Traceper-class timers exist with no production callers.TF_Q27_PROFILE=1wires them up on the 27B lane backend: each round's class GPU times are logged, and replies carrygpu_seconds.core/gpu_profile.zig: the trace gains the classes the 27B engine reports (route, experts, dense/attention path, drafter) and a per-round print.core/lanes/backend.zig/stream.zig,core/engine_api.zig,core/lane_host.zig: the timing points and thegpu_secondsfield carried on replies.families/qwen27/decode_round.zig,dflash/runtime_backend.zig,dflash/runtime_frame.zig,native/qwen27_draft.zig:profile.beginaround the round's sections, the drafter pass included (it was uninstrumented).native/qwen27_lanes.zig/qwen27_served.zig/qwen27_slots.zig,server/chat.zig,server/cli.zig: the flag and the export.README.md/docs/api.md: the env var and the reply field.tests/qwen27/dflash_run.zig: the run harness honors it.With the flag unset, nothing in this commit changes arithmetic or output: it adds stderr logging and a reply field only.
f8c63b6: hold profile spans while a draft frame is open
With
TF_Q27_PROFILEset, DFlash2 draft acceptance collapsed while output stayed exact: 8.1% accepted against 62.8–66.2% unset (paired benches, M3 Ultra).The cause:
Trace.beginmoved each span's op onto a private command buffer that commits and waits at once. Inside a draft frame, that buffer overtook the frame's pending ops on its long-lived encoder. So borrowed trunk kernels (embed, head quant, gather's unstack) read state that had not been written yet: stale hidden state into the logits, stale own-KV into the merged context.A draft
Framenow holds its trace for the active epoch, so spans keep the frame's encoder. The frame's own buffers still time the drafter class.core/gpu_profile.zig,dflash/runtime_frame.zig(+11/−4). With the flag unset, no code path changes.Checks
zig build native -Dcpu=apple_m1 -Dversion=1.0.0passes clean on 21e0ad7 and 6e03930. f8c63b6 builds; its binary is the one the receipt below ran.zig build test test-golden -Dcpu=apple_m1gave test-golden 0 unexpected differences (306/319 + 26/26 equal). Theteststep's device-dependent binaries fail on this machine while its GPU is serving a model. The same binaries fail identically on the base head 30e5dcb (run for comparison), so the failure is inherited, not introduced by this diff.tools/zig/lean_check.py: 444 findings, as on the base (zero added).Receipts
TF_Q27_PROFILE=1, quiet window quiet-002621): the greedy det cell accepted 88/88 drafts on all 5 reps, the same as unprofiled, and the det hash a420a07487e75082 was unchanged and equal across reps. Before the fix, the same cell ran at 8.1% accepted with the flag set.TF_Q27_PROFILE=1. It was taken before the fix, so drafting ran at the reduced acceptance, and the class shares describe that profiled round.Not run
No speed numbers from the fix window are quoted: it gated exactness and acceptance only.