Skip to content

Why aiter#5069's measured -25% kernel win moved serving throughput by 0.0% - #8

Merged
jhinpan merged 2 commits into
mainfrom
fix/setup-pr-rebase-proof
Sep 1, 2026
Merged

jhinpan merged 2 commits into
mainfrom
fix/setup-pr-rebase-proof

Conversation

@jhinpan

@jhinpan jhinpan commented Sep 1, 2026 •

Copy link
Copy Markdown
Owner

ROCm/aiter#5069 retuned the GLM-5.2 a8w8 and BF16 GEMM configs for gfx950 and reported, from its own measurement, 49 shapes going 3606.4us → 2702.1us (−25.08%), median +22.76% per shape, zero regressions.

We A/B'd it on one 8× MI355X node — same image, SGLang worktree, recipe and bench protocol, only the four tuning CSVs changed:

conc GLM-5.2 GLM-5.3
1 +0.06% −0.01%
8 −0.05% +0.05%
16 −0.02% +0.08%
32 −0.05% −0.00%
64 −0.09% +0.15%

Ten points, all inside a 0.04–0.35% noise floor. Both arms passed the GSM8K gate.

The null result survives falsification twice

  • The arms really differed — each records the sha256 of what it deployed: a8w8 b453… vs a361…, bf16 c84f… vs 01da….
  • The change really engaged — GLM-5.2's BF16 lookup misses fell 1256 → 616, exactly the N=256, K=6144 half the PR added rows for.

The tuning worked. Serving did not move. New §12 explains why, and none of it is bad luck.

Almost nothing reads the table that changed

aiter.tuned_gemm.tgemm has three call sites in SGLang; one is CUDA-only, dead on ROCm. On a GLM-5.x FP8 checkpoint the live pair is the MoE router (N=n_routed_experts) and the DSA indexer's unquantised weights_proj (N=n_heads) — both tiny. Dense projections take gemm_a8w8_blockscale*; routed experts take fused_moe / tuned_fmoe.csv; lm_head is a plain torch.matmul. Three different tables, and the recipe's headline GEMMs are in none of the ones this PR touched.

The serving logs agree exactly: across both arms the only (N,K) pairs reaching the BF16 table are (256, 6144) and (32, 6144).

Half the PR is unreachable here. The non-block-scale gemm_a8w8_bpreshuffle that a8w8_bpreshuffle_tuned_gemm_glm5.2.csv feeds needs SGLANG_USE_AITER_FP8_PER_TOKEN; GLM-5.2-FP8 is block quantised (weight_block_size: [128,128]) so it takes gemm_a8w8_blockscale*. 99 of the PR's 189 changed gfx950 rows are never read.

Two amplifiers

The tuner optimises M values serving never produces. Shape lists are a geometric ladder (1 2 4 8 16 24 32 48 64 96 128 …); chunked prefill and continuous batching produce dense arbitrary M (6016 6528 6848 7104 7109 7168 7448 …). Lookup probes exact M → round-up by size band → nextPow2 → give up, so M=6016 runs the kernel tuned for M=8192. AITER already ships the fix for the shape list — AITER_TUNE_GEMM=1 records what a real workload executes. A ladder of powers of two is what you get when that step is skipped.

GEMM is 17.3% of real GPU compute. The profile's raw form claims cross_device_reduce_2stage owns 97.4% — until you notice 6 of its 182 calls are 98.9% of that, the longest 2.35s against a 282us median. Those are ranks waiting at the collective. With waits removed: TP all-reduce 29.9%, MoE 18.8%, GEMM 17.3%, mHC 10.1%, KDA 6.1%, DSA attention 1.7%. Even a perfect 25% off the whole GEMM bucket is 4.3% end to end.

The census tool matters more than the finding

glm53_flash/coverage_report.py reads an AITER_LOG_TUNED_CONFIG=1 server log and answers what an op-level speedup claim cannot: does the model read the table that changed, and at which shapes. Run against our own published recipe:

Distinct shapes hitting the BF16 table 104
Distinct shapes missing 1616
Misses falling through to plain torch F.linear 1608
Hits at exactly the tuned M 69.2%
Hits padded up, by up to 2.0× 30.8%

The gfx950 half of the glm53_bf16_tuned_gemm.csv this cookbook pins covers M = 1 and M = 32 only — precisely the two captured decode graph tiers — while prefill drives M to 8192. The interesting work is not retuning the rows we have; the table is nearly empty for this workload.

§12.5 turns this into a checklist: route proof, mechanism proof, end-to-end A/B.

Also: setup_pr.sh was broken

It stopped working on 2026-08-31 when sglang#36507 was rebased. It asserted the measured commit was still an ancestor of the PR head — right instinct, wrong assumption that a branch only moves forward — so reproduction failed at step one. Now it fetches the exact object, which pins the tree just as tightly and survives a rebase, and downgrades the ancestry check to an informational note.

Verified against the actually-rebased upstream: the old assertion fails, the new path succeeds.

Verification

bash verify.sh --render passes: offline checks, GLM-5.3 regression tests, DSV4 launch-script parse, published rows byte-identical to the raw records, jsdom render and deep links.

Summary by Sourcery

Explain the disconnect between AITER GEMM microbenchmark gains and serving throughput by documenting workload coverage, adding a tuning census tool, and repairing rebased-commit reproduction.

New Features:

  • Add a coverage census tool that reports tuned GEMM table hits, misses, and padding drift from AITER server logs.

Bug Fixes:

  • Fix pull-request setup to reproduce measured commits reliably after upstream rebases while retaining exact tree pinning.

Enhancements:

  • Document why substantial kernel-level tuning gains may produce no serving throughput improvement, including execution-route coverage, workload shape mismatch, and end-to-end compute limits.
  • Add guidance for validating tuning claims through route verification, engagement measurements, and controlled serving A/B tests.
  • Expose the GLM-5.3 Flash playbook’s analysis of tuned-table coverage and the observed null serving result.

Documentation:

  • Expand the GLM-5.3 Flash playbook with the measured A/B result, tuning-path analysis, workload coverage census, profiling interpretation, and evaluation checklist.
  • Update the README entry to link to the new tuning-impact analysis and coverage tool.

Tests:

  • Verify the updated reproduction workflow, published tuning rows, launch scripts, rendered documentation, and regression checks through the existing verification script.

ROCm/aiter#5069 retuned the GLM-5.2 a8w8 and BF16 GEMM configs for gfx950 and
reported 49 shapes going 3606.4us -> 2702.1us, zero regressions. A/B'd on one
8x MI355X node with only those four CSVs changed, ten points across both models
all landed inside a 0.04-0.35% noise floor.

The null result survives falsification twice over: each arm records the sha256
of what it deployed, so the tables really did differ, and GLM-5.2's BF16 lookup
misses fell 1256 -> 616, so the change really did engage. The tuning worked.
Serving did not move. Section 12 explains why, and none of it is bad luck.

`tgemm` has three call sites in SGLang and one is CUDA-only. On a GLM-5.x FP8
checkpoint the live pair is the MoE router and the DSA indexer's unquantised
weights_proj -- both tiny. The dense projections take gemm_a8w8_blockscale*,
routed experts take fused_moe and tuned_fmoe.csv, and lm_head is a plain
torch.matmul. The serving logs agree: the only (N,K) pairs that ever reach the
BF16 table are (256, 6144) and (32, 6144). Half the PR cannot be reached at all
here -- the non-block-scale gemm_a8w8_bpreshuffle it feeds needs
SGLANG_USE_AITER_FP8_PER_TOKEN, and this checkpoint is block quantised, so 99 of
its 189 changed gfx950 rows are never read.

Two amplifiers sit behind that. The tuner's shape list is a geometric M ladder
while serving produces dense arbitrary M, and lookup pads up to nextPow2, so
M=6016 runs a kernel tuned for 8192. And GEMM across all backends is 17.3% of
real GPU compute -- a profile whose raw form claims the TP all-reduce owns 97.4%
until you notice 6 of its 182 calls are ranks waiting, not reducing.

The census tool that establishes this is worth more than the finding. Running it
on our own published recipe says the gfx950 half of the glm53 table we pin
covers M=1 and M=32 only, while chunked prefill drives M to 8192: 1616 distinct
shapes miss and 1608 of them fall through to plain torch F.linear. The
interesting work is not retuning the rows we have.

Also fixes setup_pr.sh, which stopped working on 2026-08-31 when sglang#36507
was rebased. It asserted that the measured commit was still an ancestor of the
PR head -- right instinct, but it assumed the branch only moves forward, and
reproduction failed at step one. Fetching the exact object pins the tree just as
tightly and survives a rebase. Verified against the rebased upstream: the old
assertion fails, the new path succeeds.
@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This PR turns a surprising null serving result into a reproducible analysis: it proves the tuning was deployed and engaged, shows that most retuned rows are not on the live GLM-5.x ROCm paths or do not match serving shapes, quantifies GEMM’s bounded end-to-end impact, adds a coverage census tool and review checklist, and repairs setup-script reproduction after upstream rebases.

Sequence diagram for tuning coverage census

sequenceDiagram
    participant Workload as Serving workload
    participant SGLang
    participant AITER as AITER GEMM lookup
    participant Table as BF16 tuned table
    participant Report as coverage_report.py
    Workload->>SGLang: Execute representative load
    SGLang->>AITER: Lookup M, N, K
    AITER->>Table: Probe exact M
    alt tuned row found
        Table-->>AITER: Return padded_M and kernel config
    else no tuned row
        Table-->>AITER: Miss, use torch F.linear
    end
    AITER-->>SGLang: Run selected solution
    SGLang-->>Report: AITER_LOG_TUNED_CONFIG log
    Report->>Report: Count hits, misses, tables, and padded_M drift
Loading

Flow diagram for validating a tuning claim

flowchart TD
    Start[Candidate kernel tuning claim] --> Route[Prove the live model route]
    Route --> Mechanism[Measure hit and miss changes]
    Mechanism --> AB[Run controlled end-to-end A/B]
    AB --> Gate{Correctness gate passes?}
    Gate -- No --> Reject[Reject performance conclusion]
    Gate -- Yes --> Noise[Compare delta with arm noise floor]
    Noise --> Conclusion[Report serving win or null result]
Loading

File-Level Changes

Change Details Files
Documents why AITER’s measured GEMM improvement produced no serving-throughput gain and adds a reproducible methodology for validating tuning claims.
  • Adds an end-to-end A/B analysis showing the four CSV changes were deployed, engaged, and statistically indistinguishable in serving benchmarks.
  • Maps GLM-5.x GEMM call paths to their actual tuning tables, identifying unreachable rows and the small BF16 GEMMs affected.
  • Explains shape mismatch between geometric tuning ladders and dense serving workloads, including padding and fallback behavior.
  • Attributes the limited end-to-end opportunity using a wait-corrected GPU-compute profile.
  • Adds a checklist requiring route proof, mechanism proof, and end-to-end A/B evidence.
glm53_flash_playbook.md
README.md
Adds a log-census utility to measure tuned BF16 GEMM table coverage and M-padding behavior under representative serving loads.
  • Parses AITER hit and miss logs and reports distinct-shape coverage by table.
  • Summarizes padded-M inflation and worst cases.
  • Breaks down misses by (N,K) and documents instrumentation limitations.
glm53_flash/coverage_report.py
Makes PR reproduction robust to rebased upstream branches while retaining exact measured-tree pinning.
  • Fetches the measured commit object directly, verifies it exists, and checks it out detached.
  • Changes the PR-head ancestry assertion into an informational rebased-upstream note.
glm53_flash/setup_pr.sh

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="glm53_flash/coverage_report.py" line_range="45-47" />
<code_context>
+
+def base(f): return f.rsplit("/", 1)[-1]
+
+print(f"{len(hits)} distinct shapes hit, {len(misses)} distinct shapes missed\n")
+print("=== distinct shapes per table ===")
+tbl = collections.Counter(base(h["file"]) for h in hits)
</code_context>
<issue_to_address>
**issue (bug_risk):** The report counts every matching log line as a distinct shape, but AITER's cache is per worker process, so a multi-rank server emits the same shape once per rank. The displayed hit/miss totals and percentages are therefore inflated by worker duplication rather than representing distinct shapes.

**Triggers:** When the server log combines lookup output from multiple tensor-parallel worker processes.

**Suggested fix:** Deduplicate records by the lookup key, such as `(M, N, K, table, padded_M, library)`, before computing counts and percentages.

```suggestion
def base(f): return f.rsplit("/", 1)[-1]

def key(h): return (h["M"], h["N"], h["K"], h["file"], h.get("pM"), h["lib"])

hits = list({key(h): h for h in hits}.values())
misses = list({key(h): h for h in misses}.values())

print(f"{len(hits)} distinct shapes hit, {len(misses)} distinct shapes missed\n")
```
</issue_to_address>

### Comment 2
<location path="glm53_flash/setup_pr.sh" line_range="34-39" />
<code_context>
+  || git fetch --no-tags origin 'pull/${SGLANG_PR}/head'
+git cat-file -e '${SGLANG_HEAD}^{commit}' || exit 1
 git checkout -q --detach '${SGLANG_HEAD}'
+git merge-base --is-ancestor '${SGLANG_HEAD}' FETCH_HEAD 2>/dev/null \
+  || echo 'note: measured commit is no longer an ancestor of the PR head (rebased upstream); the tree checked out above is still exactly the measured one'
 echo '--- after ---'
 git log -1 --format='%H %ci %s'
</code_context>
<issue_to_address>
**nitpick (bug_risk):** When fetching `SGLANG_HEAD` succeeds, `FETCH_HEAD` points to that same commit, so `git merge-base --is-ancestor '${SGLANG_HEAD}' FETCH_HEAD` compares a commit with itself and always succeeds. A rebased PR is therefore never reported by the supposedly informational ancestry check on the normal successful path.

**Triggers:** When the exact measured commit can be fetched directly from the remote, including the rebased-PR case described in the change.

**Suggested fix:** Fetch or save the PR head separately and compare `SGLANG_HEAD` against that commit instead of comparing it with the exact-object fetch's `FETCH_HEAD`.

```suggestion
git fetch --no-tags origin 'pull/${SGLANG_PR}/head' 2>/dev/null
PR_HEAD="\$(git rev-parse FETCH_HEAD)"
git fetch --no-tags origin '${SGLANG_HEAD}' 2>/dev/null \
  || git fetch --no-tags origin 'pull/${SGLANG_PR}/head'
git cat-file -e '${SGLANG_HEAD}^{commit}' || exit 1
git checkout -q --detach '${SGLANG_HEAD}'
git merge-base --is-ancestor '${SGLANG_HEAD}' "\$PR_HEAD" 2>/dev/null \
  || echo 'note: measured commit is no longer an ancestor of the PR head (rebased upstream); the tree checked out above is still exactly the measured one'
```
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: glm53_flash/coverage_report.py:47


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +45 to +47
def base(f): return f.rsplit("/", 1)[-1]

print(f"{len(hits)} distinct shapes hit, {len(misses)} distinct shapes missed\n")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The report counts every matching log line as a distinct shape, but AITER's cache is per worker process, so a multi-rank server emits the same shape once per rank. The displayed hit/miss totals and percentages are therefore inflated by worker duplication rather than representing distinct shapes.

Triggers: When the server log combines lookup output from multiple tensor-parallel worker processes.

Suggested fix: Deduplicate records by the lookup key, such as (M, N, K, table, padded_M, library), before computing counts and percentages.

Suggested change
def base(f): return f.rsplit("/", 1)[-1]
print(f"{len(hits)} distinct shapes hit, {len(misses)} distinct shapes missed\n")
def base(f): return f.rsplit("/", 1)[-1]
def key(h): return (h["M"], h["N"], h["K"], h["file"], h.get("pM"), h["lib"])
hits = list({key(h): h for h in hits}.values())
misses = list({key(h): h for h in misses}.values())
print(f"{len(hits)} distinct shapes hit, {len(misses)} distinct shapes missed\n")

Comment thread glm53_flash/setup_pr.sh
Comment on lines +34 to +39
git fetch --no-tags origin '${SGLANG_HEAD}' 2>/dev/null \
|| git fetch --no-tags origin 'pull/${SGLANG_PR}/head'
git cat-file -e '${SGLANG_HEAD}^{commit}' || exit 1
git checkout -q --detach '${SGLANG_HEAD}'
git merge-base --is-ancestor '${SGLANG_HEAD}' FETCH_HEAD 2>/dev/null \
|| echo 'note: measured commit is no longer an ancestor of the PR head (rebased upstream); the tree checked out above is still exactly the measured one'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (bug_risk): When fetching SGLANG_HEAD succeeds, FETCH_HEAD points to that same commit, so git merge-base --is-ancestor '${SGLANG_HEAD}' FETCH_HEAD compares a commit with itself and always succeeds. A rebased PR is therefore never reported by the supposedly informational ancestry check on the normal successful path.

Triggers: When the exact measured commit can be fetched directly from the remote, including the rebased-PR case described in the change.

Suggested fix: Fetch or save the PR head separately and compare SGLANG_HEAD against that commit instead of comparing it with the exact-object fetch's FETCH_HEAD.

Suggested change
git fetch --no-tags origin '${SGLANG_HEAD}' 2>/dev/null \
|| git fetch --no-tags origin 'pull/${SGLANG_PR}/head'
git cat-file -e '${SGLANG_HEAD}^{commit}' || exit 1
git checkout -q --detach '${SGLANG_HEAD}'
git merge-base --is-ancestor '${SGLANG_HEAD}' FETCH_HEAD 2>/dev/null \
|| echo 'note: measured commit is no longer an ancestor of the PR head (rebased upstream); the tree checked out above is still exactly the measured one'
git fetch --no-tags origin 'pull/${SGLANG_PR}/head' 2>/dev/null
PR_HEAD="\$(git rev-parse FETCH_HEAD)"
git fetch --no-tags origin '${SGLANG_HEAD}' 2>/dev/null \
|| git fetch --no-tags origin 'pull/${SGLANG_PR}/head'
git cat-file -e '${SGLANG_HEAD}^{commit}' || exit 1
git checkout -q --detach '${SGLANG_HEAD}'
git merge-base --is-ancestor '${SGLANG_HEAD}' "\$PR_HEAD" 2>/dev/null \
|| echo 'note: measured commit is no longer an ancestor of the PR head (rebased upstream); the tree checked out above is still exactly the measured one'

A bare 'exit 1' after the reachability check leaves whoever is reproducing with
no idea what happened or what to do. Name the commit, say the branch was rebased
and the object collected, and say the right response is to re-measure rather
than substitute a different head -- the published numbers are tied to this tree.

Also re-verified the fetch itself properly. The earlier check ran in a worktree
that already had the object, so cat-file would have passed either way. Repeated
it in an empty repo against github.com: fetching the exact SHA succeeds even
though the branch no longer descends from it.
@jhinpan
jhinpan merged commit 94a2d69 into main Sep 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant