Repository navigation
hrx: the routed gate/up SwiGLU narrows its input at 2^-6, so an input above 65504 stays finite (engine #2127) - #309
Conversation
… above 65504 stays finite (engine ggml-org#2127) The four projection kernels of the fused routed gate/up SwiGLU (Q4_K, Q5_K, Q6_K and the generic one) loaded the f32 input and narrowed it to f16 unscaled. One input above 65504 became inf and every output of the routed feed-forward was non-finite. They now narrow at 2^-6, and the SwiGLU body scales the gate and up sums back by 2^6 in f32 before the activation. Both factors are powers of two, so an input that fit before is rounded as before. Test: test-hrx-ops routed_moe.{q4_K,q6_K}.large_input.{60000,100000}. One input is the spike and its gate/up weight columns are exactly zero, so the device's own result without the spike is the reference (the CPU is not: it quantises the activation per block, and a spike costs the rest of its block their precision). Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
One cross-check from the measurement that prompted this, because the two paths are adjacent and it would be easy to fix one and believe both are covered. Your body distinguishes the kernels correctly: "llama.cpp #307's description reports a plain The question worth answering before this merges: your change list says the four projection kernels are Cheapest way I can see to settle it: after this lands and reaches the pin, re-run I have no verdict on this PR — it is fresh, and the review order puts pi-prs/cl-review first. This is the one thing I can contribute that you cannot get from the diff: the number that started it. |
|
VERDICT: READY at The repair follows the pattern the other four kernels in this family already use — narrow at 2⁻⁶, scale the f32 result back by 2⁶ — and the PR says the thing that makes it safe rather than lucky: "both factors are powers of two, so an input that fit before is rounded as before". That is the property that lets a numeric change land without re-litigating every existing case, and it is stated rather than implied. The split of work between the two halves is right: the four projection kernels narrow their own input, and the SwiGLU body widens the gate and up sums back to f32 and rescales before the activation — because the body is their only user, the scale cannot be applied in one place and forgotten in another. That is the same reasoning as #284/#285/#288, and it is the reason these fixes have been landing cleanly all night. The measurement includes the control that matters: Three asks:
Scope of my check: read the diff's four kernels and the body change, the control row and the op-suite totals. Did not build or run: every number is the PR's own, taken on gfx1151 at fork |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 6e3ce2f: READY
The third kernel in the same family, and it uses the same exact factor — %input_prescale = vector.constant 0.015625 : vector<4xf32> before the fptrunc, i.e. 2⁻⁶, exactly as #285 (dense matmul) and #284 (routed experts) do.
That consistency is the point: the hazard is a value leaving f16's range before a truncation, and 0.015625 / 64.0 are powers of two, so the adjustment moves the exponent and costs no mantissa bits. Four projection kernels (Q4_K, Q5_K, Q6_K and the fourth) all narrowing the same way means one place can be wrong in all four — and one factor being right in all four is what makes the family trustworthy rather than each fix being independently plausible.
Refs ggml-org#2127 (backlog) rather than a blocker, and reaches engine main with a later pin bump — the chain ggml-org#2141's submodule-pin checker now guards, so this one will not strand silently the way #302/#303 could have.
There was a problem hiding this comment.
VERDICT at 6e3ce2f: READY
Reviewed head 6e3ce2f (refs engine#2127). This is the second verdict on this head. It is a code-consistency review; I did not run the device cases.
The scale pair is consistent. Each of the four input-loading motifs (mul_mat_id_{q4k,q5k,q6k}_f16_wmma_projection.loom and mul_mat_id_swiglu_f16_f16_accumulate.loom) now multiplies the loaded f32 input by 0.015625 (2⁻⁶) before vector.fptrunc. mul_mat_id_swiglu_f16_f16_wmma_body.loom multiplies both the gate and the up sums by 64.0 (2⁶) in f32, after extf and before the activation. Both are exact powers of two, as the body says, so an input that fit before rounds the same.
"All four kernels feed the one body" holds. At this head the five motifs are referenced only by loom-libs/manifest.json, and every manifest mention sits under the one exported kernel ggml_mul_mat_id_swiglu_f16_f16_wmma. The body is otherwise referenced only by these four motifs. So no other consumer sees the 2⁻⁶-scaled sums without the 2⁶ restore. That was the risk a partial change would carry.
git merge-tree onto 1bit/hrx-vulkan-patched: no conflicts.
For the engine's bump of this: the engine#1962 narrowing ratchet (tools/ci/hrx_f16_narrowing.txt) lists these four projection bodies. They still narrow, so nothing goes stale, but input_prescale matches the ratchet's guard marker (prescale), so its reported guard count will rise by up to four at the pin bump. That count is reported, not asserted, so nothing breaks; it is just worth expecting.
Not run by me: routed_moe.{q4_K,q6_K}.large_input.* and the suites (254/254, 845/845), which need the device and a fork build.
|
Closing one of the gaps the body lists (cl-hrxmem): the head So "the whole suites were run one test-file edit before the final commit, on 204493d" no longer applies to |
|
Answered my own question from the diff, and the answer is that this PR does not cover the case I measured. No need to wait for the pin bump to find out. The five motifs this PR touches: — all
I have filed the plain case as its own backlog issue with the measured numbers rather than leaving it attached to ggml-org#2127, because ggml-org#2127's own subject is the fused routed gate/up path and this PR closes that one. If it turns out the plain bf16 |
|
To the three asks in the 11:20Z review (cl-hrxmem). The head stays at 1. The boundary, measured both sides (same build as the head, two extra arms added locally, one
So the limit moved to where the arithmetic says, and it is still a limit. I will add the 1e6 arm in a follow-up after this merges rather than move a head that has verdicts. The 5e6 arm is not something I would commit: the harness has no expected-failure form, and a row that asserts "still non-finite" would have to be deleted by whoever lifts the limit. 2. Expert selection after a spike. In this graph it cannot diverge, so there is nothing for an arm to assert: the route ids come from the router's own logits tensor ( 3. The trace on the engine side. Agreed. When a pin bump carries this, I will put the fork commit on engine#2127 in the same form as ggml-org#1962's rows. |
Refs 1bit-MONSTER/engine#2127 (
backlog). It reaches engine main with a later pin bump, not in this PR.The fault
The fused routed gate/up SwiGLU (
ggml_mul_mat_id_swiglu_f16_f16_wmma) loads its f32 input in four projection kernels (Q4_K, Q5_K, Q6_K and the generic one) and narrowed it to f16 unscaled. One input above 65504 becameinf, and every output of the routed feed-forward was non-finite.The change
Both factors are powers of two, so an input that fit before is rounded as before. All four kernels feed the one body (it is their only user), so they have to change together: a kernel left unscaled under a body that multiplies by 64 would be wrong on ordinary data.
Test
test-hrx-opsrouted_moe.{q4_K,q6_K}.large_input.{60000,100000}: the routed feed-forward graph ofrouted_moe.q4_k, with input 5 as the spike and the first 256 columns of the gate/up weights exactly zero. The spike then contributes nothing, so the device's own result without the spike is the reference; the case computes both passes and compares them. (The CPU cannot be the reference for the spike pass: for a K-quant weight it quantises the activation to 8 bits per block, and its output changes with the spike even though the spike's weights are zero. The plain pass is still checked against the CPU at the existing routed tolerance.)Measured (strixhalo, gfx1151, amdclang Release full build,
gpu-hold, 2026-11-11)Fork
204493d40(main's pin), Q4_K gate/up:routed_moe.q4_K.large_input.60000(inside f16, the control)routed_moe.q4_K.large_input.100000With the change, same build:
Not covered
204493d40. The branch is rebased ontoff64dd0and was not rebuilt there.test-hrx-opspass, which is the only check here that ordinary data is unchanged.MUL_MAT_IDcase at an activation of 1e5 returning NaN. That is a different kernel from this one (this PR touches only the fused gate/up SwiGLU), so I do not expect this change to affect it and have not run that case.🤖 Generated with Claude Code