Skip to content

fix(rocm/gemma4): #838 widen indexed MoE gate to T<=63 + retirement-safe dispatch - #1046

Open
bakon11 wants to merge 13 commits into
mudler:mainfrom
bakon11:fix/gemma4-838-indexed-maxt
Open

fix(rocm/gemma4): #838 widen indexed MoE gate to T<=63 + retirement-safe dispatch#1046
bakon11 wants to merge 13 commits into
mudler:mainfrom
bakon11:fix/gemma4-838-indexed-maxt

Conversation

@bakon11

@bakon11 bakon11 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

fix(rocm/gemma4): #838 widen indexed MoE gate to T<=63 + retirement-safe dispatch

Fixes #838.

Problem

T=2..63 MoE tokens are observed on the serial M1 path, and the Gemma-4 FP8
device-indexed gate admitted only T == 1. Everything wider fell through to the
legacy host-gather path, which copies the router weights and indices back to the
host every step. Dispatch retirement was also not scope-safe.

What changed

  • The indexed gate widens from T == 1 to T <= 63, behind
    VT_GEMMA4_DECODE_INDEXED_MAX_T (unset → 63, =1 restores the old T=1-only
    behavior, clamped to [1,63]). The bound is not arbitrary: Gemma4IndexedOkT
    also requires T < kGemma4PrefillBatchMinT, the same constant that admits the
    prefill-batch path at 64, so the two gates cannot both claim a token count.
  • A latent double-scale is fixed along the way. The old code applied the
    per-expert scale to the router weights rw in place before attempting the
    indexed arm, so a fall-through to the host-gather path scaled them a second
    time and squared the factor. The scale now lands on a copy, and
    Gemma4ApplyHostExpertScaleOnce takes an already_scaled argument so the
    fallback cannot repeat it. gemma4 indexed-max-t: fallback scale is once, not s^2 pins that: it asserts the result is orig * 3 and not orig * 9.
  • The T-loop, arm selection, scratch choice and fail-path ordering move into
    gemma4_indexed_gate.h as injectable host seams, so the retire-before-release
    ordering is testable without a GPU. Peer and compute work is retired while the
    scratch buffer is still owned; a buffer whose retirement was not observed is
    quarantined rather than returned to the pool.
  • Gemma4IndexedCall's product instantiations are named explicitly
    (<uint16_t, uint16_t>) so the product lambdas compile against current main.
  • Host indexed tests: 10 cases, 1101 assertions.

Verification

Built and smoked on gfx1201, shipping ROCm 7.2.4 (clang f58b06d), as the
combined #837+#838+#839 stack on d1b0ea3a: gfx1201 build BUILD_RC=0, all 30
layers resident and bound, ready in ~20 s; batched-MoE prefill deadlock-free
(T=2012 done, PEER_ACT active, no hang, no HIP error); Paris/63 quality PASS.

Stated plainly, because it bounds what this PR alone demonstrates: that smoke
covers the three-PR stack, not this branch in isolation. What is isolated
here is the host-side evidence — the 10 cases above, which run on
build-test-cpu and include real RED mutants for the double-scale, the
release-before-retire ordering, the argument packing and the T-loop striding.

No throughput measurement accompanies this change. #838 is filed as perf
and the change flips a product default for T=2..63, so docs/BENCHMARKS.md
still owes a before/after against VT_GEMMA4_DECODE_INDEXED_MAX_T=1 on the same
host. Correctness comes first there: the token-exact result for T=2..63 is owed
before any throughput ratio is accepted. Both need the gfx1201 pair. They are
recorded under ## Open on gfx1201 hardware in
.agents/specs/rocm-gemma4-indexed-max-t.md rather than left implicit.

Maintainer changes on top

Three repairs were applied while landing, none touching product behavior:

  • The donor evidence moved from .agents/evidence/ — a path
    scripts/check-pr-size.py cannot classify, so the gate refused the change
    outright — to .agents/specs/rocm-gemma4-indexed-max-t-donor.{md,log}, which
    match SPEC and SPEC_EVIDENCE at check-pr-size.py:188-189. The bytes are
    unchanged; both slice sha256s still match the manifest table.
  • docs/FEATURES.md is left as main has it. The branch had rewritten the
    Gemma4 row, dropping the spec link, the VT_GEMMA4_*/VT_ATTN_* env pointer
    and the test_gemma4_rocm_fp8_seams seam name. Restoring those and adding the
    ROCm Gemma-4 hyp A: T=2..63 MoE is observed on serial M1; widen indexed gate #838 sentence does not fit — check-public-doc-tables caps a cell at 220
    characters and main's is already 219 — and check-doc-checkpoint does not ask
    for a FEATURES edit here, because feature_surface now keys off a change to
    the set of REGISTER_VLLM_MODEL registrations (doc-checkpoint makes docs/FEATURES.md a LOCK: 2 of 5 concurrent LTX-2.5 PRs conflict there and nowhere else #595, fix(#595): doc-checkpoint asks whether the registry moved, not whether a file did #1086) and this branch
    changes none. The ROCm Gemma-4 hyp A: T=2..63 MoE is observed on serial M1; widen indexed gate #838 detail stays where the branch already put it in full:
    docs/USAGE.md, docs/ENVIRONMENT.md and the spec.
  • The source-invariant case sliced rocm_gemma4_experts.hip between
    RetireGemma4Fp8TopKIndexedPeer and the next symbol, then asserted
    return true; was absent. That is a slice of the gap between two
    functions, not of the function, so an unrelated definition added after the
    closing brace lands inside it. A git merge-tree of this branch with open PR
    fix(rocm/gemma4): #839 retirement-safe prefill peer GeGLU Launch/Finish #1047 (3df512a11) grows that gap from 16 lines to 176 and adds three
    return true; inside it, which would have turned main red on
    build-test-cpu once both landed — this case is registered unconditionally
    and reads the source from disk. It now brace-matches the function's own body
    and gains two assertions, that the slice is bounded and that the function does
    synchronize the compute stream, so the guarantee is stated more completely
    than before rather than relaxed. Proved against the real test binary, built
    standalone and pointed at a scratch source root: the repaired case reds on a
    mutant that discards the sync status and returns unconditional success (3
    assertions fail), and stays green on the actual merged tree, where the
    original case reds. The scratch copy was restored and its sha256 compared
    against the tree.

The branch was rebuilt by rebase rather than merge, so it carries no untrailered
merge commit; all six original commits keep their authorship. The docs/USAGE.md
conflict against #837's landed text was resolved as a union — both the GetBlas
dual-slot paragraph and this row's env description survive.

Known-unrelated CI

windows-msvc-cpu and windows-msvc-vulkan are red on every open PR from a
break predating this branch (#503, #584).

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]

@localai-bot
localai-bot force-pushed the fix/gemma4-838-indexed-maxt branch from 8343f06 to 9d4a16c Compare August 17, 2026 14:22
localai-bot pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 17, 2026
…ce invariant that survives mudler#1047

Maintainer repairs applied while landing mudler#1046. No product behavior changes.

Donor evidence moves from `.agents/evidence/` -- a path
`scripts/check-pr-size.py` cannot classify, so the gate refused the change
outright -- to `.agents/specs/rocm-gemma4-indexed-max-t-donor.{md,log}`, which
match `SPEC` and `SPEC_EVIDENCE` at `check-pr-size.py:188-189`. The bytes are
unchanged: both slice sha256s still match the manifest table, which is what
that table exists to check.

`docs/FEATURES.md` is left as `main` has it. The branch had rewritten the
Gemma4 row, dropping the spec link, the `VT_GEMMA4_*`/`VT_ATTN_*` env pointer
and the `test_gemma4_rocm_fp8_seams` seam name. Restoring those and adding the
mudler#838 sentence does not fit -- `check-public-doc-tables` caps a cell at 220
characters and main's is already 219 -- and `check-doc-checkpoint` does not ask
for a FEATURES edit here, because `feature_surface` now keys off a change to
the set of `REGISTER_VLLM_MODEL` registrations (mudler#595, mudler#1086) and this branch
changes none. The mudler#838 detail stays where the branch already put it in full:
`docs/USAGE.md`, `docs/ENVIRONMENT.md` and the spec.

The source-invariant case in `test_gemma4_indexed_max_t.cpp` sliced
`rocm_gemma4_experts.hip` between `RetireGemma4Fp8TopKIndexedPeer` and the next
symbol, then asserted `return true;` was absent from the result. That is a
slice of the GAP between two functions, not of the function, so any unrelated
definition added after the closing brace lands inside it. Open PR mudler#1047 inserts
about 160 lines exactly there and carries its own `return true;`, which would
have turned `main` red on `build-test-cpu` after both landed -- this case is
registered unconditionally and reads the source from disk. It now brace-matches
the function's own body, and gains two assertions that the slice is bounded and
that the function does sync the compute stream, so the guarantee is stated more
completely than before rather than relaxed.

Proved by mutation against the real test binary, built standalone and pointed
at a scratch source root: the repaired case reds on a mutant that discards the
sync status and returns unconditional success (3 assertions fail), and stays
green under the mudler#1047-shaped insertion, where the original case reds. The
worktree sources were never mutated; the scratch copy was restored and its
sha256 compared against the tree.

The remaining review findings need a gfx1201 pair and are recorded under
`## Open on gfx1201 hardware` in the row spec instead of being papered over.
The `rw_idx` pooled buffer on the T=1 path contradicts Scope item 3's
hipGraph-stable invariant, and this branch still records no measurement for a
change that flips a product default across T=2..63.

The branch was rebuilt by rebase rather than merge, so it carries no
untrailered merge commit; all six original commits keep their authorship.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
localai-bot pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 17, 2026
…ce invariant that survives mudler#1047

Maintainer repairs applied while landing mudler#1046. No product behavior changes.

Donor evidence moves from `.agents/evidence/` -- a path
`scripts/check-pr-size.py` cannot classify, so the gate refused the change
outright -- to `.agents/specs/rocm-gemma4-indexed-max-t-donor.{md,log}`, which
match `SPEC` and `SPEC_EVIDENCE` at `check-pr-size.py:188-189`. The bytes are
unchanged: both slice sha256s still match the manifest table, which is what
that table exists to check.

`docs/FEATURES.md` is left as `main` has it. The branch had rewritten the
Gemma4 row, dropping the spec link, the `VT_GEMMA4_*`/`VT_ATTN_*` env pointer
and the `test_gemma4_rocm_fp8_seams` seam name. Restoring those and adding the
mudler#838 sentence does not fit -- `check-public-doc-tables` caps a cell at 220
characters and main's is already 219 -- and `check-doc-checkpoint` does not ask
for a FEATURES edit here, because `feature_surface` now keys off a change to
the set of `REGISTER_VLLM_MODEL` registrations (mudler#595, mudler#1086) and this branch
changes none. The mudler#838 detail stays where the branch already put it in full:
`docs/USAGE.md`, `docs/ENVIRONMENT.md` and the spec.

The source-invariant case in `test_gemma4_indexed_max_t.cpp` sliced
`rocm_gemma4_experts.hip` between `RetireGemma4Fp8TopKIndexedPeer` and the next
symbol, then asserted `return true;` was absent from the result. That is a
slice of the GAP between two functions, not of the function, so any unrelated
definition added after the closing brace lands inside it. A `git merge-tree` of
this branch with open PR mudler#1047 (`3df512a11`) grows that gap from 16 lines to
176 and puts three `return true;` inside it, which turns `main` red on
`build-test-cpu` once both land -- this case is registered unconditionally and
reads the source from disk. It now brace-matches the function's own body, and
gains two assertions that the slice is bounded and that the function does sync
the compute stream, so the guarantee is stated more completely than before
rather than relaxed.

Proved against the real test binary, built standalone and pointed at a scratch
source root. The repaired case reds on a mutant that discards the sync status
and returns unconditional success (3 assertions fail), and stays green on the
actual merged tree, where the original case reds with 1 failed assertion. The
worktree sources were never mutated; the scratch copy was restored and its
sha256 compared against the tree.

The remaining review findings need a gfx1201 pair and are recorded under
`## Open on gfx1201 hardware` in the row spec instead of being papered over.
The `rw_idx` pooled buffer on the T=1 path contradicts Scope item 3's
hipGraph-stable invariant, and this branch still records no measurement for a
change that flips a product default across T=2..63.

The branch was rebuilt by rebase rather than merge, so it carries no
untrailered merge commit; all six original commits keep their authorship.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
@localai-bot
localai-bot force-pushed the fix/gemma4-838-indexed-maxt branch from 9d4a16c to cff626f Compare August 17, 2026 14:25
@localai-bot

Copy link
Copy Markdown
Collaborator

Thanks — the core of this is right, and the double-scale fix inside it is a real bug caught in passing. I rebased the branch onto current main and pushed the maintainer repairs so the gates go green, same as I did on #1045. I have not merged it, because two of the findings below need the gfx1201 pair and I would rather leave them open than paper over them.

What I changed on your branch

Your six commits are preserved with their authorship, rebased (not merged, so no untrailered merge commit). One repair commit sits on top.

  1. .agents/evidence/ is a path check-pr-size.py cannot classify, so the gate refused the change outright:

    ERROR: PR size check could not classify the change: unclassified repository path
    '.agents/evidence/rocm-gemma4-indexed-max-t/MANIFEST.md'
    

    The classifiable homes are .agents/specs/*.md (SPEC) and .agents/specs/*.{patch,json,log} (SPEC_EVIDENCE), at check-pr-size.py:188-189. The three files are now rocm-gemma4-indexed-max-t-donor.md, -donor-gate-911-933.log and -donor-ok-1088-1110.log, with the manifest table and the spec's two references updated. The bytes did not change: both slice sha256s still match the table, which is exactly what that table exists to check. This is the same move fix(rocm/gemma4): #837 GetBlas dual-slot TLS + host lifetime seam #1045 needed — worth knowing for fix(rocm/gemma4): #839 retirement-safe prefill peer GeGLU Launch/Finish #1047 and ROCm Gemma-4 hyp C: prefill peer GeGLU helper is monolithic; retirement-safe Launch/Finish #839.

  2. docs/FEATURES.md is back to what main has. Your rewrite of the Gemma4 row dropped the spec link, the VT_GEMMA4_*/VT_ATTN_* env pointer and the test_gemma4_rocm_fp8_seams seam name. I could not restore those and add the ROCm Gemma-4 hyp A: T=2..63 MoE is observed on serial M1; widen indexed gate #838 sentence: check-public-doc-tables caps a table cell at 220 characters and main's cell is already 219. And it turns out nothing asks for the edit — as of fix(#595): doc-checkpoint asks whether the registry moved, not whether a file did #1086/doc-checkpoint makes docs/FEATURES.md a LOCK: 2 of 5 concurrent LTX-2.5 PRs conflict there and nowhere else #595, check-doc-checkpoint's feature_surface class keys off a change to the set of REGISTER_VLLM_MODEL(...) registrations rather than off the path src/vllm/model_executor/models/, and this branch changes none. Your ROCm Gemma-4 hyp A: T=2..63 MoE is observed on serial M1; widen indexed gate #838 detail already lives in full in docs/USAGE.md, docs/ENVIRONMENT.md and the spec, so nothing was lost.

  3. documentation-checkpoint goes green on the rebase, and the reason is worth knowing. It was not your change — the fix in (2), commit 8fa405bb7, is not an ancestor of your old base d1b0ea3a but is an ancestor of current main. Rebasing picked up the repaired checker. If any of your other branches show that red, rebasing is the whole fix.

  4. docs/USAGE.md conflicted against ROCm Gemma-4 hyp B: GetBlas single TLS destroys hipBLAS handle on peer-MoE device hop #837's landed text in all four of your commits that touch it. I resolved it as a union each time, so both the GetBlas dual-slot paragraph and this row's VT_GEMMA4_DECODE_INDEXED_MAX_T description survive. I also dropped main's now-false "No new env." sentence, since this row adds one.

  5. The source-invariant case would have turned main red once fix(rocm/gemma4): #839 retirement-safe prefill peer GeGLU Launch/Finish #1047 landed. This is the one I would most like you to look at, because it is a shape rather than a typo:

    const auto ret = hip.find("bool RetireGemma4Fp8TopKIndexedPeer");
    const auto ret_end = hip.find("RunGemma4Fp8ExpertGeGLUPrefillOnExpertDevice", ret);
    const std::string retire = hip.substr(ret, ret_end - ret);
    CHECK(retire.find("return true;") == std::string::npos);

    That slices the gap between two symbols, not the function. Anything added after RetireGemma4Fp8TopKIndexedPeer's closing brace lands inside the slice, and this case is registered unconditionally and reads the .hip from disk — so it runs on build-test-cpu.

    I checked this against the real merged tree rather than reasoning about it. git merge-tree of this branch with fix(rocm/gemma4): #839 retirement-safe prefill peer GeGLU Launch/Finish #1047's head 3df512a11 grows that gap from 16 lines to 176, and the added region carries three return true; of its own. Running your test binary against that exact merged file:

    [doctest] test cases:   10 |    9 passed | 1 failed | 0 skipped
    [doctest] assertions: 1099 | 1098 passed | 1 failed |
    [doctest] Status: FAILURE!
    

    So main goes red once both land, with a failure pointing at a function neither PR touched.

    It now brace-matches the function's own body via a FunctionBody() helper (which skips a forward declaration by requiring the first { before the first ;), and gains two assertions: that the slice is bounded, and that the function does synchronize the compute stream. The intent is stated more completely than before, not relaxed.

    I built the real test TU standalone against a scratch source root and ran both versions three ways:

    source under test your case repaired case
    as committed SUCCESS, 1099 assertions SUCCESS, 1101 assertions
    mutant: sync status discarded, return true; FAILURE, 3 failed FAILURE, 3 failed
    real merge-tree of this branch + fix(rocm/gemma4): #839 retirement-safe prefill peer GeGLU Launch/Finish #1047 FAILURE, 1 failed SUCCESS

    The middle row is the one that matters most: the repaired case still catches the defect it exists for, so the intent is intact rather than relaxed. The bottom row is the landmine. The scratch copy was restored and its sha256 compared against the tree afterwards; nothing in the worktree was mutated.

What I could not verify

I have no RDNA4 hardware and did not build the project (this host is at 95% disk, and a false ENOSPC failure reads as a code verdict). The test above was compiled and run as a standalone TU — real doctest, real binary, real assertions — and the full test file passes -fsyntax-only against the project headers. It has not been through the project's CMake build or a HIP compile.

What needs you

These are recorded under ## Open on gfx1201 hardware in .agents/specs/rocm-gemma4-indexed-max-t.md so they do not get lost. The first two are why I have not merged.

A. rw_idx breaks the invariant Scope item 3 exists to protect. At gemma4_moe.cpp:760 the T=1 arm now reads its router weights from a per-call pooled DBuf where it previously used the caller-owned rw. A pooled buffer's address moves between calls, so it is not capture-stable — while the comment three lines below still reads "Stable T=1 acc for hipGraph (do not pool-Release)", and your spec's Scope item 3 says "T=1: keep hipGraph-stable TLS acc". Decode hipGraph is lab-only today, which is exactly why nothing observes this; it is latent, not absent. I did not touch it because you own the graph-capture context and the hardware to check it. Either hold rw_idx in the same TLS that holds acc_fast, or say in the spec that T=1 has stopped being capture-stable and why that is acceptable.

B. No measurement. #838 is filed as perf, and this flips a product default across T=2..63. Nothing in docs/BENCHMARKS.md or the spec records a before/after. Correctness first: the token-exact result for T=2..63 against VT_GEMMA4_DECODE_INDEXED_MAX_T=1 on the same host, then the throughput ratio. Both need your box.

C. The per-expert scale costs one kernel launch per token — up to 63 of them. gemma4_moe.cpp:766-773 calls vt::ApplyExpertScaleRw inside the T-loop. ApplyExpertScaleRwKernel (rocm_fp8_channel_gemv.hip:635-641) is <<<1, G>>> with g = threadIdx.x, and it indexes rw[g], ri[g] and escale[ri[g]] purely linearly — no reduction, no shared memory, no cross-thread interaction — and ri holds global expert ids. So one call with G = T*top_k is element-wise identical to the loop, and Gemma4IndexedOkT bounds top_k <= 8 and T <= 63, giving G <= 504, comfortably inside a 1024-thread block.

I deliberately did not make this change, for a reason I think is worth stating: collapsing the loop removes the structural guarantee that G stays small. Today G = top_k <= 8 by construction; after the collapse G scales with T, and ApplyExpertScaleRwRocm checks neither G against the block limit nor the launch result. Widen the T bound later and the launch fails silently, the scale never applies, and the tokens are quietly wrong. So the one-call version is worth doing and should ship with that guard — on hardware that can run it, with (B) already answered so the win is a number rather than a claim.

D. Six test-only helpers ship in a production header. Gemma4IndexedHostApplyToken, Gemma4IndexedHostSerialRef, Gemma4IndexedOracleClose, Gemma4IndexedArgsEq, Gemma4IndexedMayReleaseToPool and Gemma4IndexedFailPathRetireThenMaybeRelease (gemma4_indexed_gate.h:113-160) have no production caller. Moving them into tests/ is mechanical, and I left it to you only because it churns includes across your other open branches — say the word and I will do it.

While you are there: the "tensor oracle" computes s = (e+1) * rw[g], which models none of the FP8 GeGLU arithmetic. What it actually gates is the T-loop's striding and the argument packing — genuinely worth gating, and the tests around it do catch real mutants. But the name promises a numerical oracle it is not, and someone will eventually read a green run as evidence the kernel math is right.

E. Smaller, all yours:

  • Gemma4IndexedHelperHits() does a global std::atomic fetch_add per token on the decode hot path purely so a host test can observe it.
  • Gemma4IndexedScratchValidForT cannot return false in the T>1 branch that calls it — Gemma4IndexedScratchKindFor(T) supplies the kind, y_base is non-null and elems == T*H, so all three conditions hold by construction. Dead guard, or the call site is missing a case.
  • The new RestoreComputeDev (rocm_gemma4_experts.hip:572) adds a second hipSetDevice on a success path that already ends with one at :659 — and whose own comment at :639 says "no hipSetDevice between stream ops (graph-prep + less driver tax)".
  • RetireGemma4Fp8TopKIndexedPeer returns ok = true while skipping the expert-stream drain entirely when tls.edev != expert_dev. That is a real retirement that did not happen, reported as one that did.
  • One comment string is load-bearing: the source-invariant case asserts retire-before-acc_idx-dtor appears in gemma4_moe.cpp. Reordering the statements it names while keeping the comment passes; deleting the comment while keeping the order fails. I left it because fixing it properly means asserting on the statement order, which is a bigger change than this landing pass should make.

One heads-up for #1047

The same merge-tree shows docs/USAGE.md conflicting between the two branches — you have four commits here rewriting the "Gemma4 FP8 on ROCm (RDNA4)" paragraph, and #837 landed its own text into it. I resolved it as a union on this branch; #1047 will want the same treatment rather than taking one side, or the GetBlas dual-slot sentences disappear.


Happy to take A and C myself if you send me a trace, or just confirm the graph-capture behavior — I mainly need someone with the hardware to say which way A should go. Once A and B are answered this is ready; everything else here is either landed or filed.

@localai-bot

Copy link
Copy Markdown
Collaborator

@bakon11 — I re-verified this against current main and did not push anything. It is still clean and I did not want to reset its CI approval for no gain.

State as of cff626f93:

Your FunctionBody() brace-matcher is confirmed against #1047 on a real merged tree, not by argument. I merged the two rebased branches and measured:

slice #838 alone #838 + #839 merged
FunctionBody("bool RetireGemma4Fp8TopKIndexedPeer") 13 lines, no return true; 13 lines, no return true;
the old symbol-to-symbol slice 17 lines, no return true; 177 lines, contains return true;

rocm_gemma4_experts.hip auto-merges clean, and the whole suite is 10/10 / 1101 assertions against the merged sources. So the old assertion really would have red-ed and the repair is load-bearing.


Two things still hold this open, and both are yours.

1. src/vllm/model_executor/models/gemma4_moe.cpp:760 puts a pooled DBuf on the hipGraph-capture path. When escale_ptr is set you now copy the router weights into a per-call rw_idx and pass helper_rw into Gemma4IndexedDispatchTokens on the TlsT1 arm — three lines above the comment "Stable T=1 acc for hipGraph (do not pool-Release)", and Scope item 3 in the spec says the T=1 path stays capture-stable. aft.acc is still TLS-stable, but a pooled allocation's address moves between calls and kernel arguments are baked at capture, so the rw pointer in a captured graph can go stale. main scaled rw in place and passed rw.ptr(), which was caller-owned and therefore stable.

Your reason for not mutating in place is good and I am not asking you to undo it — "fallback must still see unscaled weights" is a real correctness fix for T>1. The shape that keeps both is a TLS-stable rw_idx on the T=1 arm, keyed the way AccFastTls is keyed (device, plus T*top_k), so the scaled copy has a fixed address for the life of the capture. That is a small change, but it is on the decode path on your hardware and I am not going to write it blind.

It is latent today — decode hipGraph is lab-only — but it is silent, which is the part I do not want to leave undocumented.

2. No measurement on a perf default flip across T=2..63. This widens the device-indexed gate from T == 1 to T <= 63 by default, which is the right direction, and the host-gather path it replaces copies router weights and indices back to the host every step. But nothing in the PR, the spec or docs/BENCHMARKS.md says what it bought, and the interesting part is the shape — whether the win holds at T=2 and T=8, not just at T=63. Only you have the box.

Everything else here is ready.

@bakon11

bakon11 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Both items accepted — the TLS-stable rw_idx fix (your suggested AccFastTls-keyed shape) and the T={2,8,63} gate-flip measurements are queued on the 2×R9700 now; fix lands on this branch (on top of 9d4a16c5), numbers go to docs/BENCHMARKS.md. Thanks for confirming the brace-matcher on a real merged tree.

@bakon11

bakon11 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Both owed items delivered (pushed cff626f9ed1c0fe6, stacked on your rebase, no force):

1. hipGraph capture-stabilitye2ebf084: TLS-stable T=1 rw_idx keyed like AccFastTls (device + T·top_k), exactly the shape you suggested — the scaled router-weight copy now has a fixed address for the life of a capture, and the T>1 fallback still sees unscaled weights. Plus 4616f440/ed1c0fe6: explicit current-device in the indexed peer helper/retire (found while validating on the 2×R9700). Keyed identity GREEN, natural-EOS identity GREEN.

2. The T=2..63 default-flip measurement (2×R9700, isolated serve, PREFIX_CACHE=0, profile off, 4-process A(idx=63)→B(1)→B(1)→A(63), 5 bursts/leg, 32 tok/request API-usage denominator, independently reviewed raw):

T indexed T≤63 vs host-gather verdict
2 +14.0% (1.1403× median, both order pairings favor A) GREEN
8 +2–4% directional held (within dispersion — reported honestly, not promoted)
63 +24.6% (1.2455× median, both pairings favor A) GREEN

So the flip pays at both ends of the range and the win grows with T; the shape question is answered — it's not a T=63-only artifact. rocprof arm-verification confirmed each arm dispatches the distinct kernel path before timing was accepted. Numbers + protocol are in docs/BENCHMARKS.md on the branch.

bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 18, 2026
T=1 scaled router weights live in RwIdxTls (compute_dev + T*top_k).
T>1 keeps pooled rw_idx_owned. Fresh Copy+scale every call; never
mutate caller rw. Host source invariants added. No GPU.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 18, 2026
VT_GEMMA4_PROFILE=1 emits one parseable dispatch line per
RunGemma4Moe at successful indexed return (T=1 TLS and T>1
owned) and at legacy host-gather before router D2H. No
control-flow/sync/copy/alloc change. Default OFF silent.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 18, 2026
…ler#1046)

Set compute device before compute-stream event record; expert
device before expert wait/H2D/launch/event; compute device
before compute wait and result D2D. Fail retirement syncs each
stream under its owning device and restores compute. Replaces
the false "no hipSetDevice" claim. No algorithm/alloc/event
order/lifetime/overlap/gate/fallback change.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 18, 2026
Set compute before cst sync; set tls.edev before est sync when
TLS ownership matches; restore compute on exit. Best-effort
ok=false on any set/sync fail. Completes a060/51ed current-
device contract. No reinit or event-destroy.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator

This branch is currently conflicting with main, and its latest commit merges main into the feature branch. Please rebase the feature commits onto current main instead, then rerun the ROCm-specific evidence; the current conflict prevents a meaningful merge review.

@bakon11

bakon11 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Conflict resolved — merged current main into the branch (dd19c8e3), matching the merge style already on this branch rather than rewriting your rebase. Sole conflict was docs/USAGE.md: resolved as a union — this PR's VT_GEMMA4_DECODE_INDEXED_MAX_T paragraph kept, and the Prefill peer (#839) sentence that landed via #1047 preserved verbatim. Dropped only main's stale "No new env." clause, which stops being true once this PR documents a new env knob. Payload unchanged (gemma4_indexed_gate.h, gemma4_moe.cpp); 0 behind main.

@bakon11
bakon11 force-pushed the fix/gemma4-838-indexed-maxt branch from dd19c8e to 9f0ace5 Compare August 19, 2026 03:16
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…ce invariant that survives mudler#1047

Maintainer repairs applied while landing mudler#1046. No product behavior changes.

Donor evidence moves from `.agents/evidence/` -- a path
`scripts/check-pr-size.py` cannot classify, so the gate refused the change
outright -- to `.agents/specs/rocm-gemma4-indexed-max-t-donor.{md,log}`, which
match `SPEC` and `SPEC_EVIDENCE` at `check-pr-size.py:188-189`. The bytes are
unchanged: both slice sha256s still match the manifest table, which is what
that table exists to check.

`docs/FEATURES.md` is left as `main` has it. The branch had rewritten the
Gemma4 row, dropping the spec link, the `VT_GEMMA4_*`/`VT_ATTN_*` env pointer
and the `test_gemma4_rocm_fp8_seams` seam name. Restoring those and adding the
mudler#838 sentence does not fit -- `check-public-doc-tables` caps a cell at 220
characters and main's is already 219 -- and `check-doc-checkpoint` does not ask
for a FEATURES edit here, because `feature_surface` now keys off a change to
the set of `REGISTER_VLLM_MODEL` registrations (mudler#595, mudler#1086) and this branch
changes none. The mudler#838 detail stays where the branch already put it in full:
`docs/USAGE.md`, `docs/ENVIRONMENT.md` and the spec.

The source-invariant case in `test_gemma4_indexed_max_t.cpp` sliced
`rocm_gemma4_experts.hip` between `RetireGemma4Fp8TopKIndexedPeer` and the next
symbol, then asserted `return true;` was absent from the result. That is a
slice of the GAP between two functions, not of the function, so any unrelated
definition added after the closing brace lands inside it. A `git merge-tree` of
this branch with open PR mudler#1047 (`3df512a11`) grows that gap from 16 lines to
176 and puts three `return true;` inside it, which turns `main` red on
`build-test-cpu` once both land -- this case is registered unconditionally and
reads the source from disk. It now brace-matches the function's own body, and
gains two assertions that the slice is bounded and that the function does sync
the compute stream, so the guarantee is stated more completely than before
rather than relaxed.

Proved against the real test binary, built standalone and pointed at a scratch
source root. The repaired case reds on a mutant that discards the sync status
and returns unconditional success (3 assertions fail), and stays green on the
actual merged tree, where the original case reds with 1 failed assertion. The
worktree sources were never mutated; the scratch copy was restored and its
sha256 compared against the tree.

The remaining review findings need a gfx1201 pair and are recorded under
`## Open on gfx1201 hardware` in the row spec instead of being papered over.
The `rw_idx` pooled buffer on the T=1 path contradicts Scope item 3's
hipGraph-stable invariant, and this branch still records no measurement for a
change that flips a product default across T=2..63.

The branch was rebuilt by rebase rather than merge, so it carries no
untrailered merge commit; all six original commits keep their authorship.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
T=1 scaled router weights live in RwIdxTls (compute_dev + T*top_k).
T>1 keeps pooled rw_idx_owned. Fresh Copy+scale every call; never
mutate caller rw. Host source invariants added. No GPU.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
VT_GEMMA4_PROFILE=1 emits one parseable dispatch line per
RunGemma4Moe at successful indexed return (T=1 TLS and T>1
owned) and at legacy host-gather before router D2H. No
control-flow/sync/copy/alloc change. Default OFF silent.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…ler#1046)

Set compute device before compute-stream event record; expert
device before expert wait/H2D/launch/event; compute device
before compute wait and result D2D. Fail retirement syncs each
stream under its owning device and restores compute. Replaces
the false "no hipSetDevice" claim. No algorithm/alloc/event
order/lifetime/overlap/gate/fallback change.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
Set compute before cst sync; set tls.edev before est sync when
TLS ownership matches; restore compute on exit. Best-effort
ok=false on any set/sync fail. Completes a060/51ed current-
device contract. No reinit or event-destroy.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
@bakon11

bakon11 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Rebased as asked — 9f0ace5a, linear, zero merge commits, 0 behind main. My earlier merge-into-branch is gone; sorry for the noise, your convention is clearly the right one here.

What I did: replayed the 11 feature commits onto current main (your own land #838 commit keeps its authorship). Two conflicts, both additive unions:

  • tests/CMakeLists.txt — kept both registrations: test_gemma4_moe_device_arm_guard (from main) and test_gemma4_indexed_max_t (this PR).
  • docs/USAGE.md — kept this PR's VT_GEMMA4_DECODE_INDEXED_MAX_T paragraph and preserved the Prefill peer (#839) sentence that landed via fix(rocm/gemma4): #839 retirement-safe prefill peer GeGLU Launch/Finish #1047 verbatim; dropped only the stale "No new env." clause, which stops being true once this PR documents a knob.

Verified on the new head: gemma4_indexed_gate.h + test_gemma4_indexed_max_t.cpp present, the TLS-stable T=1 rw_idx for hipGraph fix you asked for is intact, docs carry both sentences.

ROCm evidence re-run is on our 2×R9700 queue and I'll post it here; the previously reported numbers (indexed T≤63 vs host-gather: +14.0% @t=2, +24.6% @t=63, T=8 within dispersion) were measured on the same payload.

@bakon11
bakon11 force-pushed the fix/gemma4-838-indexed-maxt branch from 9f0ace5 to 1066a1f Compare August 19, 2026 16:54
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…ce invariant that survives mudler#1047

Maintainer repairs applied while landing mudler#1046. No product behavior changes.

Donor evidence moves from `.agents/evidence/` -- a path
`scripts/check-pr-size.py` cannot classify, so the gate refused the change
outright -- to `.agents/specs/rocm-gemma4-indexed-max-t-donor.{md,log}`, which
match `SPEC` and `SPEC_EVIDENCE` at `check-pr-size.py:188-189`. The bytes are
unchanged: both slice sha256s still match the manifest table, which is what
that table exists to check.

`docs/FEATURES.md` is left as `main` has it. The branch had rewritten the
Gemma4 row, dropping the spec link, the `VT_GEMMA4_*`/`VT_ATTN_*` env pointer
and the `test_gemma4_rocm_fp8_seams` seam name. Restoring those and adding the
mudler#838 sentence does not fit -- `check-public-doc-tables` caps a cell at 220
characters and main's is already 219 -- and `check-doc-checkpoint` does not ask
for a FEATURES edit here, because `feature_surface` now keys off a change to
the set of `REGISTER_VLLM_MODEL` registrations (mudler#595, mudler#1086) and this branch
changes none. The mudler#838 detail stays where the branch already put it in full:
`docs/USAGE.md`, `docs/ENVIRONMENT.md` and the spec.

The source-invariant case in `test_gemma4_indexed_max_t.cpp` sliced
`rocm_gemma4_experts.hip` between `RetireGemma4Fp8TopKIndexedPeer` and the next
symbol, then asserted `return true;` was absent from the result. That is a
slice of the GAP between two functions, not of the function, so any unrelated
definition added after the closing brace lands inside it. A `git merge-tree` of
this branch with open PR mudler#1047 (`3df512a11`) grows that gap from 16 lines to
176 and puts three `return true;` inside it, which turns `main` red on
`build-test-cpu` once both land -- this case is registered unconditionally and
reads the source from disk. It now brace-matches the function's own body, and
gains two assertions that the slice is bounded and that the function does sync
the compute stream, so the guarantee is stated more completely than before
rather than relaxed.

Proved against the real test binary, built standalone and pointed at a scratch
source root. The repaired case reds on a mutant that discards the sync status
and returns unconditional success (3 assertions fail), and stays green on the
actual merged tree, where the original case reds with 1 failed assertion. The
worktree sources were never mutated; the scratch copy was restored and its
sha256 compared against the tree.

The remaining review findings need a gfx1201 pair and are recorded under
`## Open on gfx1201 hardware` in the row spec instead of being papered over.
The `rw_idx` pooled buffer on the T=1 path contradicts Scope item 3's
hipGraph-stable invariant, and this branch still records no measurement for a
change that flips a product default across T=2..63.

The branch was rebuilt by rebase rather than merge, so it carries no
untrailered merge commit; all six original commits keep their authorship.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
T=1 scaled router weights live in RwIdxTls (compute_dev + T*top_k).
T>1 keeps pooled rw_idx_owned. Fresh Copy+scale every call; never
mutate caller rw. Host source invariants added. No GPU.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
VT_GEMMA4_PROFILE=1 emits one parseable dispatch line per
RunGemma4Moe at successful indexed return (T=1 TLS and T>1
owned) and at legacy host-gather before router D2H. No
control-flow/sync/copy/alloc change. Default OFF silent.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…ler#1046)

Set compute device before compute-stream event record; expert
device before expert wait/H2D/launch/event; compute device
before compute wait and result D2D. Fail retirement syncs each
stream under its owning device and restores compute. Replaces
the false "no hipSetDevice" claim. No algorithm/alloc/event
order/lifetime/overlap/gate/fallback change.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
Set compute before cst sync; set tls.edev before est sync when
TLS ownership matches; restore compute on exit. Best-effort
ok=false on any set/sync fail. Completes a060/51ed current-
device contract. No reinit or event-destroy.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 added a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…opped by rebase (mudler#1046)

The 11-commit replay onto current main rewrote the Gemma4-FP8-on-ROCm section
wholesale, which silently deleted two paragraphs that landed on main after this
branch was authored: the contributor KEEP recipe (2014/1099 t/s prefill, 55 t/s
decode) and the no-denominator caveat referencing mudler#845. Both restored verbatim;
the indexed-max-T text this PR adds is unchanged. Net USAGE.md diff vs main is
now additions plus the intentional rewrite of the env sentence only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bakon11
bakon11 force-pushed the fix/gemma4-838-indexed-maxt branch from 1066a1f to 38fe058 Compare August 19, 2026 16:55
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…ce invariant that survives mudler#1047

Maintainer repairs applied while landing mudler#1046. No product behavior changes.

Donor evidence moves from `.agents/evidence/` -- a path
`scripts/check-pr-size.py` cannot classify, so the gate refused the change
outright -- to `.agents/specs/rocm-gemma4-indexed-max-t-donor.{md,log}`, which
match `SPEC` and `SPEC_EVIDENCE` at `check-pr-size.py:188-189`. The bytes are
unchanged: both slice sha256s still match the manifest table, which is what
that table exists to check.

`docs/FEATURES.md` is left as `main` has it. The branch had rewritten the
Gemma4 row, dropping the spec link, the `VT_GEMMA4_*`/`VT_ATTN_*` env pointer
and the `test_gemma4_rocm_fp8_seams` seam name. Restoring those and adding the
mudler#838 sentence does not fit -- `check-public-doc-tables` caps a cell at 220
characters and main's is already 219 -- and `check-doc-checkpoint` does not ask
for a FEATURES edit here, because `feature_surface` now keys off a change to
the set of `REGISTER_VLLM_MODEL` registrations (mudler#595, mudler#1086) and this branch
changes none. The mudler#838 detail stays where the branch already put it in full:
`docs/USAGE.md`, `docs/ENVIRONMENT.md` and the spec.

The source-invariant case in `test_gemma4_indexed_max_t.cpp` sliced
`rocm_gemma4_experts.hip` between `RetireGemma4Fp8TopKIndexedPeer` and the next
symbol, then asserted `return true;` was absent from the result. That is a
slice of the GAP between two functions, not of the function, so any unrelated
definition added after the closing brace lands inside it. A `git merge-tree` of
this branch with open PR mudler#1047 (`3df512a11`) grows that gap from 16 lines to
176 and puts three `return true;` inside it, which turns `main` red on
`build-test-cpu` once both land -- this case is registered unconditionally and
reads the source from disk. It now brace-matches the function's own body, and
gains two assertions that the slice is bounded and that the function does sync
the compute stream, so the guarantee is stated more completely than before
rather than relaxed.

Proved against the real test binary, built standalone and pointed at a scratch
source root. The repaired case reds on a mutant that discards the sync status
and returns unconditional success (3 assertions fail), and stays green on the
actual merged tree, where the original case reds with 1 failed assertion. The
worktree sources were never mutated; the scratch copy was restored and its
sha256 compared against the tree.

The remaining review findings need a gfx1201 pair and are recorded under
`## Open on gfx1201 hardware` in the row spec instead of being papered over.
The `rw_idx` pooled buffer on the T=1 path contradicts Scope item 3's
hipGraph-stable invariant, and this branch still records no measurement for a
change that flips a product default across T=2..63.

The branch was rebuilt by rebase rather than merge, so it carries no
untrailered merge commit; all six original commits keep their authorship.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
T=1 scaled router weights live in RwIdxTls (compute_dev + T*top_k).
T>1 keeps pooled rw_idx_owned. Fresh Copy+scale every call; never
mutate caller rw. Host source invariants added. No GPU.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
VT_GEMMA4_PROFILE=1 emits one parseable dispatch line per
RunGemma4Moe at successful indexed return (T=1 TLS and T>1
owned) and at legacy host-gather before router D2H. No
control-flow/sync/copy/alloc change. Default OFF silent.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…ler#1046)

Set compute device before compute-stream event record; expert
device before expert wait/H2D/launch/event; compute device
before compute wait and result D2D. Fail retirement syncs each
stream under its owning device and restores compute. Replaces
the false "no hipSetDevice" claim. No algorithm/alloc/event
order/lifetime/overlap/gate/fallback change.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 pushed a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
Set compute before cst sync; set tls.edev before est sync when
TLS ownership matches; restore compute on exit. Best-effort
ok=false on any set/sync fail. Completes a060/51ed current-
device contract. No reinit or event-destroy.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
bakon11 added a commit to bakon11/vllm.cpp that referenced this pull request Aug 19, 2026
…opped by rebase (mudler#1046)

The 11-commit replay onto current main rewrote the Gemma4-FP8-on-ROCm section
wholesale, which silently deleted two paragraphs that landed on main after this
branch was authored: the contributor KEEP recipe (2014/1099 t/s prefill, 55 t/s
decode) and the no-denominator caveat referencing mudler#845. Both restored verbatim;
the indexed-max-T text this PR adds is unchanged. Net USAGE.md diff vs main is
now additions plus the intentional rewrite of the env sentence only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Don Mirror and others added 13 commits August 19, 2026 12:32
Independent row/ROCM-GEMMA4-INDEXED-MAX-T from origin/main 3ce5a1d.
Pins donor gate slices (SHA256 d0d28f3d… / 5509f3f7…).
Hypothesis A only; T=19 is observed on serial M1 — cause unconfirmed.
Tensor oracle T=2,19,63 same-dev+peer vs serial reference (64cb mudler#4).

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
Default VT_GEMMA4_DECODE_INDEXED_MAX_T=63. T=1 keeps hipGraph TLS
acc; T=2..63 uses an owned [T,H] buffer and the existing per-token
indexed helpers. T>=64 still misses the gate (prefill-batch). Packed
batched / INDEXED_NOSYNC stay out.

Host predicate table + host tensor oracle (abs_tol = 2^-7 *
max_abs(ref); exact-zero support) vs serial mix. GPU/p42k HOLD.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
b6b7d99e is not a review target (0ee9/5ebb/d2da).

- Gemma4IndexedDispatchTokens is the production T-loop; tests inject
  distinct same-dev/peer helpers and assert witness, offsets, owner canary
- Indexed path scales a rw scratch copy; fallback applies host scale once
- RunGemma4Fp8TopKIndexedOnExpertDevice restores compute_dev on every return

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
aab2b4a8 is not a review target (7ec2).

- Product-loop helper writes y; independent serial ref; oracle
  T={2,19,63} x {same-dev,peer}; stride/ownership REDs corrupt output
- Post-enqueue fail syncs expert+compute streams before rw_idx/acc_idx
  return to DevicePool (RetireGemma4Fp8TopKIndexedPeer)

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
d973/0f32 stop-ships on a fresh head (7c416eb6 is not a review target):
- retire indexed peer/compute work inside acc_idx lexical scope; quarantine
  via DBuf::Release when retirement is not observed
- RetireGemma4Fp8TopKIndexedPeer fail-closed (no discarded hip errors)
- serial ref no longer calls candidate ApplyToken
- production selector Gemma4IndexedSelectArm/RunSelectedArm + packed args
- T>1 owner is OwnedTH; T=1 TLS rejected for T>1

Host: 10/10 cases, 1099 assertions.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
HIP compile of gemma4_moe.cpp failed: class-template used as a
lambda parameter without arguments. Dispatch YT/XT are uint16_t.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
…ce invariant that survives mudler#1047

Maintainer repairs applied while landing mudler#1046. No product behavior changes.

Donor evidence moves from `.agents/evidence/` -- a path
`scripts/check-pr-size.py` cannot classify, so the gate refused the change
outright -- to `.agents/specs/rocm-gemma4-indexed-max-t-donor.{md,log}`, which
match `SPEC` and `SPEC_EVIDENCE` at `check-pr-size.py:188-189`. The bytes are
unchanged: both slice sha256s still match the manifest table, which is what
that table exists to check.

`docs/FEATURES.md` is left as `main` has it. The branch had rewritten the
Gemma4 row, dropping the spec link, the `VT_GEMMA4_*`/`VT_ATTN_*` env pointer
and the `test_gemma4_rocm_fp8_seams` seam name. Restoring those and adding the
mudler#838 sentence does not fit -- `check-public-doc-tables` caps a cell at 220
characters and main's is already 219 -- and `check-doc-checkpoint` does not ask
for a FEATURES edit here, because `feature_surface` now keys off a change to
the set of `REGISTER_VLLM_MODEL` registrations (mudler#595, mudler#1086) and this branch
changes none. The mudler#838 detail stays where the branch already put it in full:
`docs/USAGE.md`, `docs/ENVIRONMENT.md` and the spec.

The source-invariant case in `test_gemma4_indexed_max_t.cpp` sliced
`rocm_gemma4_experts.hip` between `RetireGemma4Fp8TopKIndexedPeer` and the next
symbol, then asserted `return true;` was absent from the result. That is a
slice of the GAP between two functions, not of the function, so any unrelated
definition added after the closing brace lands inside it. A `git merge-tree` of
this branch with open PR mudler#1047 (`3df512a11`) grows that gap from 16 lines to
176 and puts three `return true;` inside it, which turns `main` red on
`build-test-cpu` once both land -- this case is registered unconditionally and
reads the source from disk. It now brace-matches the function's own body, and
gains two assertions that the slice is bounded and that the function does sync
the compute stream, so the guarantee is stated more completely than before
rather than relaxed.

Proved against the real test binary, built standalone and pointed at a scratch
source root. The repaired case reds on a mutant that discards the sync status
and returns unconditional success (3 assertions fail), and stays green on the
actual merged tree, where the original case reds with 1 failed assertion. The
worktree sources were never mutated; the scratch copy was restored and its
sha256 compared against the tree.

The remaining review findings need a gfx1201 pair and are recorded under
`## Open on gfx1201 hardware` in the row spec instead of being papered over.
The `rw_idx` pooled buffer on the T=1 path contradicts Scope item 3's
hipGraph-stable invariant, and this branch still records no measurement for a
change that flips a product default across T=2..63.

The branch was rebuilt by rebase rather than merge, so it carries no
untrailered merge commit; all six original commits keep their authorship.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
T=1 scaled router weights live in RwIdxTls (compute_dev + T*top_k).
T>1 keeps pooled rw_idx_owned. Fresh Copy+scale every call; never
mutate caller rw. Host source invariants added. No GPU.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
VT_GEMMA4_PROFILE=1 emits one parseable dispatch line per
RunGemma4Moe at successful indexed return (T=1 TLS and T>1
owned) and at legacy host-gather before router D2H. No
control-flow/sync/copy/alloc change. Default OFF silent.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
…ler#1046)

Set compute device before compute-stream event record; expert
device before expert wait/H2D/launch/event; compute device
before compute wait and result D2D. Fail retirement syncs each
stream under its owning device and restores compute. Replaces
the false "no hipSetDevice" claim. No algorithm/alloc/event
order/lifetime/overlap/gate/fallback change.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
Set compute before cst sync; set tls.edev before est sync when
TLS ownership matches; restore compute on exit. Best-effort
ok=false on any set/sync fail. Completes a060/51ed current-
device contract. No reinit or event-destroy.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Hermes:grok-4.6 [Hermes]
…opped by rebase (mudler#1046)

The 11-commit replay onto current main rewrote the Gemma4-FP8-on-ROCm section
wholesale, which silently deleted two paragraphs that landed on main after this
branch was authored: the contributor KEEP recipe (2014/1099 t/s prefill, 55 t/s
decode) and the no-denominator caveat referencing mudler#845. Both restored verbatim;
the indexed-max-T text this PR adds is unchanged. Net USAGE.md diff vs main is
now additions plus the intentional rewrite of the env sentence only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…on rewrite (mudler#1046)

docs/USAGE.md is a high-traffic file; main moved three times in the last hour.
Rewriting the whole Gemma4-FP8-on-ROCm paragraph made every replay a silent
preference for our older block (it already dropped two of main's paragraphs
once). Restore main's env/GetBlas/hipGraph text verbatim and add the
VT_GEMMA4_DECODE_INDEXED_MAX_T documentation as its own adjacent paragraph, so
future rebases either apply cleanly or raise a real conflict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bakon11
bakon11 force-pushed the fix/gemma4-838-indexed-maxt branch from 38fe058 to 9bf4370 Compare August 19, 2026 17:33
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.

ROCm Gemma-4 hyp A: T=2..63 MoE is observed on serial M1; widen indexed gate

4 participants