Conversation
…t the spread Two findings from auditing the bench against its own output. The routing still moved with SWEEP. Splitting the payload and routing generators fixed the payload half of that, but wts is (M, TOPK) with M = max(SWEEP) and it was drawn FIRST, so how much randomness it consumed -- and therefore where idx started -- depended on the sweep list. TOKENS=512 and TOKENS=64,...,16384 disagreed on all 512 rows of the 512 tier, worth 1.7% of the slowest rank's load; the tell was recv~1688 against recv~1693 for what should have been the same point. idx consumes exactly one randperm per row, so drawing it first makes its first ct rows the same for any M. Verified: the 512 tier now reports recv~1702 from both sweeps. The mean was the only statistic reported, and it is the least stable one here. Over six repeats of one tier the mean moved 13% while the min moved 1.2%, and the mean sits 10-26% above its own min because the distribution has a long right tail (at 4096 tokens p95 is 1.8x p50). Reporting it alone means a reader cannot tell a clean point from a polluted one -- a sweep taken for the image README read 190 us at a tier that sits at 66, and nothing in the output said so. The per-iteration deltas already existed and were being thrown away, so min/p50/p95 now come with the mean: min on the human line, all three in the JSON row. Every statistic is reduced across ranks the same way, so min is the mean of each rank's own minimum rather than one lucky rank's. The mean stays the headline. It is the right number for what a layer costs, tail included; min is there so the mean can be audited. Neither change alters what is measured, only the routing sample and what is reported alongside it. Verified on 4x MI4xx EP4.
… looks min was on bench_ep.py's own line, but the driver reprints its own table from a regex and only carried the mean across -- so the one-shot output, which is what anyone actually reads, still showed a single unauditable number. The min was only in the log file the run points at. Also adds max. It is the noisiest of the five and must not be compared between runs, but it is the only one that shows an isolated stall: two of six repeats of one tier hit 203 and 241 us against a 59 us median, which moved the mean by 0.7 us and was otherwise invisible.
The driver reprints its own table from this line, so a statistic that is not on it cannot reach the table the reader actually sees.
jhchouuu
force-pushed
the
bench-audit-fixes
branch
from
September 11, 2026 09:53
be45c5e to
d54cdce
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two defects found by auditing the benchmark against its own output, both in how it reports rather than in what it measures.
The routing still moved with the sweep list
_data.pysplit the payload and routing generators precisely so that changingSWEEPcould not resample the routing. That fixed the payload half. Butwtsis(M, TOPK)withM = max(SWEEP), and it was drawn from the routing generator beforeidx— so how much randomness it consumed, and therefore whereidxstarted, still depended on the sweep list.TOKENS=512andTOKENS=64,...,16384disagreed on all 512 rows of the 512 tier. The visible symptom wasrecv~1688from one andrecv~1693from the other for what is nominally the same point; the per-rank load differs by 1.7% on the slowest rank, which is the rank that sets the time.idxconsumes exactly onerandpermper row, so drawn first its firstctrows are identical for anyM. Verified on 4x MI4xx: the 512 tier now reportsrecv~1702from both sweeps.The mean was the only statistic, and it is the least stable one
Measured on 4x MI4xx,
ITERS=200:The mean sits 10–26% above its own min and has a long right tail — at 4096 tokens p95 is 1.8x p50. Reporting it alone means a reader cannot tell a clean point from a polluted one. That is not hypothetical: a bf16 sweep taken for the micro-bench image's README read 190 us at a tier that sits at 66, and nothing in the output distinguished it from a good point. Its
minwould have.The per-iteration deltas already existed and were being discarded. Now
mintrails the human line and all three quantiles go into the JSON row:The mean stays the headline — it is the right number for what a layer costs with the tail included.
minis there so the mean can be audited.Every statistic is reduced across ranks the same way, so the reported
minis the mean of each rank's own minimum, not the global minimum (which would present one lucky rank as the collective).Scope
Neither change alters what is measured — only the routing sample drawn and what is printed alongside the mean. Previously published latencies and ratios are unaffected.
Also checked, and NOT changed
Measured rather than assumed, so they are recorded here:
torch.cuda.Eventlazy initialisation. The docs are clear that the underlying event is created on firstrecord(), and all 800 records happen inside the timed loop — but an A/B against a pre-warmed variant showed no effect (52.4/55.4 vs 54.9/54.9 at ct=64, inside run-to-run noise). The host runs far enough ahead that event creation never starves the GPU.ev[1]/ev[2]gap. Both events are enqueued back-to-back beforerun_c(), so combine's host submission latency lands insideev[2]→ev[3];d + cdoes equalev[0]→ev[3].