Repository navigation
hrx: a tied weight's 2-to-8-token batch stays on the kquant kernel (engine #2137) - #325
Conversation
…ngine ggml-org#2137) #324 hands a 2-to-8-token batch to the packed prefill kernels when prefill reads the weight packed. A weight a GET_ROWS also reads (a tied token embedding, engine ggml-org#1316) is never packed: prefill reads it stored through weight_has_native_layout_reader, so declining the kquant kernel only moved the batch to the stored f32 WMMA kernel. Same clause as the conv4 matcher (#322). Asked in review on #324. Two host rows (Q4_K, 6 and 8 tokens) fail without the clause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
VERDICT: READY at The chain, in the PR's own wordsThat is the exact condition I wrote into the #324 review — "when a The arms are stated in the right direction"Two host rows (Q4_K, 6 and 8 tokens) fail without the clause." Per arm, per token count, affirmative — the third time tonight this sentence has appeared and the reason it matters: without it a test-only or small-diff PR cannot be checked by a reader. Two asks
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 3afe171
The new clause reads correctly and the evidence is the right kind — a row that fails on the fork without it and passes with it.
bool few_token_batch_reads_packed(const Graph & graph, const CommonMulMatMatch & match) {
if (match.token_count <= 1 || match.weight == nullptr) return false;
for (const GraphNode * consumer : graph.index().consumers(match.weight->id))
if (consumer != nullptr && consumer->op == GGML_OP_GET_ROWS) return false;
…
}The A/B is stated per-library rather than per-number, which is what makes it checkable: "this PR: pass — ggml_kquant_mul_mat_decode_tokens_f32" against "fork main (#324 without the clause): fail —
ggml_mul_mat_f32_f32_wmma takes the batch", with host suite 85/85. Naming the kernel that wins in each case is the assertion a matcher change needs: "it fails" could mean many things, but
"the wrong kernel takes the batch" names the defect.
The reasoning is the part worth keeping, and it is a closure argument rather than a preference: a tied token embedding is read by GET_ROWS stored, so prefill cannot pack that weight at all
(weight_has_native_layout_reader in dispatch-mul-mat.cpp) — therefore the kquant few-token kernels and prefill read the same stored copy, and declining would only hand the batch to a slower kernel.
That is why the clause is "not for a weight a GET_ROWS also reads stored" and not "not for small batches".
And the precedent is cited instead of re-derived: "Same clause as the conv4 matcher in dispatch-ssm-conv.cpp." A matcher rule that exists in two places for the same reason is a rule; one that
exists in one place for a reason nobody wrote down is a coincidence. Worth keeping that sentence.
The honest boundary is stated twice: "The device suite was not run for this change. The change affects only which kernel a matcher picks" — defensible for a scheduling predicate, and said
rather than omitted. Plus the "Not in this PR" note that a tied Q6_K weight at 6–16 tokens reaches ggml_mul_mat_q6_k_i8_prepacked_f16_wmma before the kquant matcher sees it, with #320 named as where
that is dealt with.
One suggestion for the arm: the two rows are tokens6 and tokens8, i.e. the middle of the 2-to-8 window. A tokens2 row would pin the lower edge where token_count <= 1 stops applying — the
boundary the predicate's first line creates — and that is the value a later edit to <= 1 would move without any row failing.
Not run: the host suite (needs GGML_HRX=ON and the private runtime); my check is the predicate's placement, the closure argument against weight_has_native_layout_reader, and the cited precedent.
|
VERDICT: READY at The fix answers a review question by adding a consumer clause to a weight predicate, which is the correct direction: The evidence table is the discriminating form I keep asking for: Two rows that differ in which kernel the matcher picks, with the fork-main run failing on the specific wrong kernel — that is what proves the new clause is load-bearing rather than incidental. A single "both pass" row would have proved nothing, since the wrong kernel still computes plausible values. Two asks:
Scope of my check: the diff summary, the body's before/after table, and the "Not in this PR" note (the Q6_K 6-to-16 case reaching the prepacked kernel first). No build, no device: the arms are host-side scheduling checks by design, and the device suite was not run — which the body states. |
Refs engine ggml-org#2137. Follow-up to #324, answering the GET_ROWS question in its review.
What changes
#324 makes the kquant few-token kernels skip a 2-to-8-token batch when prefill reads the weight packed, so the weight isn't copied a second time in the stored layout. A weight that a GET_ROWS also reads, such as a tied token embedding (engine ggml-org#1316), is never packed. Prefill reads it stored too, because
weight_has_native_layout_readerin dispatch-mul-mat.cpp keeps it stored. Skipping the kquant kernel for that weight saves no memory. It only moved the batch toggml_mul_mat_f32_f32_wmma, which reads the same stored copy and is slower.few_token_batch_reads_packednow takes the graph and returns false for a weight with a GET_ROWS consumer. This is the same clause as the conv4 matcher in dispatch-ssm-conv.cpp (#322), so the two PRs now agree on this case.Evidence
hrx-backend-test, new rowstied_weight_few_tokens_keeps_kquant.q4_k.tokens6and.q4_k.tokens8: a file-mapped Q4_K weight read by both a GET_ROWS and a MUL_MAT.ggml_kquant_mul_mat_decode_tokens_f32ggml_mul_mat_f32_f32_wmmatakes the batchThe device suite was not run for this change. The change affects only which kernel a matcher picks, and the rows are host-side scheduling checks.
Not in this PR
A tied Q6_K weight at 6 to 16 tokens goes to
ggml_mul_mat_q6_k_i8_prepacked_f16_wmmabefore the kquant matcher sees it. #320 deliberately keeps that kernel for a weight a GET_ROWS reads (dispatch-mul-mat.cpp,match_q6_k_i8_prepacked_dispatch). Its i8 layout is still a second layout next to the stored one the gather reads, so I left that case out here and noted it on ggml-org#2137. A tied embedding is only in an HRX graph withLLAMA_INPUT_LAYER_ON_DEV=1.🤖 Generated with Claude Code