Repository navigation
hrx: Q6_K prefill can read the packed rows decode reads, one device copy per weight; fixes a bus error in GGML_HRX_Q6K_PACKED_PREFILL (engine #2137, opt-in) - #310
Conversation
…ight has one device copy (engine ggml-org#2137, opt-in) Q6_K is read in the stored layout by prefill at every batch size and in the packed row layout by decode, so after a prompt and a reply each Q6_K weight had two device copies. With GGML_HRX_Q6K_PACKED_ROWS=1 prefill reads the packed rows too: the tiled kernels as weight format 46 (new 4-wide reader), the 128-aligned f16 prefill kernel as weight_layout 2 (new 16-wide pair reader, in its wide tile and its ordinary body). Only for a weight in a weights buffer. The stored path stays the default. GGML_HRX_Q6K_PACKED_PREFILL, the older switch on the aligned kernel, bound the stored layout while its kernel read 17,536-byte int8 tiles off its wide tile: MUL_MAT(q6_K, m=2048, n=128, k=4096, weight=resident) with it on ended in "HRX buffer download failed" and a bus error. It now selects that kernel only on the wide tile, where the kernel reads the stored rows it is bound to. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
ready for review at ccf14f9 (opt-in; also restricts GGML_HRX_Q6K_PACKED_PREFILL to the tile where its kernel is correct; device and host rows in the body) |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at ccf14f9: READY
The Q6_K counterpart to #308, and it comes with a confession worth having: "It also fixes a fault in an existing opt-in." A follow-on that reports a defect in the flag it is extending is more valuable than one that only adds a case — the first opt-in path was shipped with a bug, and the second part found it because extending it meant reading it.
Opt-in again, with "the default path does not change", and filed against ggml-org#2137 — the residual ggml-org#856 deliberately left. That is now the second part of a residual being taken in order (#308 Q4_K, this Q6_K), which is what makes the "document the duplicate and track it" decision on ggml-org#856 defensible rather than a way of deferring.
The diff replacing the inline packed_tail conjunction with a helper continues the right direction: what was four && clauses on two formats is becoming a named predicate, which is where the shape decision belongs.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES at ccf14f9
The gate is right and the arms miss exactly the half that produced the bus error.
The new gate is token_count % 256 == 0 && match.output_size % 128 == 0 — two conditions, AND. The reported fault was:
MUL_MAT(type_a=q6_K,m=2048,n=128,k=4096,weight=resident): buffer_get: HRX buffer download failed / Bus error, rc 135
m=2048 is the output size → 2048 % 128 == 0 ✔. n=128 is the batch → 128 % 256 != 0 ✘. So the failing case is "outputs aligned, tokens not" — the first condition was satisfied and the second
was the one that had to hold.
The arms you added are n ∈ {22, 100, 164, 300} for q6_K. Measured:
| n | n % 256 | n % 128 | covers which half of the AND |
|---|---|---|---|
| 22 | 22 | 22 | neither condition satisfied |
| 100 | 100 | 100 | neither |
| 164 | 164 | 36 | neither |
| 300 | 44 | 44 | neither |
| 128 | 128 | 0 | outputs aligned, tokens not ← the reported case |
| 384 | 128 | 0 | same half, larger |
Every arm sits in the "neither" cell. A dispatch that tested only token_count % 256 == 0 and dropped the output_size % 128 conjunct would pass all four — and that is the conjunct the
reported case exercised. The AND is only tested in one direction today.
Ask: add n = 128 (the exact reported geometry) and n = 384 for q6_K. Two lines, and they are the cheapest regression pin available for a fault that cost a Bus error and a core
dump. If m also has an off-128 variant worth covering, m = 2048, n = 128 is the reported one and m = 2100 would be its counterpart.
What I verified and like: the fix selects the from_packed kernel only when the tile is genuinely wide ("the from_packed kernel reads the stored rows on its wide tile (tokens a multiple of
256, outputs of 128) and 17,536-byte int8 tiles on every other tile, while this dispatch binds the stored layout"), which is the correct shape — the binding and the kernel's expectation
agree or the kernel is not chosen. Opt-in default unchanged (GGML_HRX_Q6K_PACKED_ROWS=1), and the appended arms for q4_K at n ∈ {6, 22, 164, 300} cover the same off-grid batch question
for the other type.
Measured, not run: the coverage arithmetic above. The device run (28 OK vs 9 OK then rc 135) is yours and needs GGML_HRX=ON.
…out by default, so it has one device copy (engine ggml-org#2137) (#313) llama.cpp #308 and #310 let prefill read the packed rows decode reads, behind GGML_HRX_Q4K_PACKED_TAIL=1 and GGML_HRX_Q6K_PACKED_ROWS=1. The packed layout saves a copy exactly when the stored one costs a copy, which is when the weight is in the loader's file mapping (#304); for an uploaded weight the stored layout is already on the device and the packed one would be the extra copy. So the default now follows the weight's own buffer: packed for a weight in the file mapping, stored otherwise. The switches still force either: =1 packed for every weight, =0 stored for every weight. Test (device-free): hrx-backend-test packed_layout_follows_weight.{q4_k,q6_k}.{file_mapped,uploaded}. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
Refs 1bit-MONSTER/engine#2137. Second part, after #308 (Q4_K). Opt-in: the default path does not change. It also fixes a fault in an existing opt-in.
What was left after #308
Qwen3-4B Q4_K_M (216 q4_K, 37 q6_K; 2,375.9 MiB),
GGML_HRX_Q4K_PACKED_TAIL=1, after a 1,700-token prompt and a 16-token reply: 2,744.0 MiB of device copies. The extra 368 MiB is 35 q6_K weights, each held twice: prefill reads q6_K in the stored layout at every batch size, decode reads it in the packed row layout.The existing
GGML_HRX_Q6K_PACKED_PREFILL=1does not change that. Its dispatch selects a different kernel but binds the stored layout, and on 256-aligned batches that kernel reads the stored rows (measured: registrationcommon.mul_mat.q6_k_prefill_wave32bindsggml-nativewith the switch on).The change
GGML_HRX_Q6K_PACKED_ROWS=1: prefill reads q6_K in the packed rows decode reads, at every batch size.motifs/dequant.loom:ggml_q6k_packed_row64_f32_vector4, four values from the packed layout (layout as written bymaterialize_q6_k_packed_k256_row64_scalerow). The tiled loaders of the generic matmul core and of the fused gate/up kernel take it as weight format 46, next to hrx: Q4_K batches off the 256 grid can read the packed layout, so a weight keeps one device copy (engine #2137, opt-in) #308's format 44.motifs/q6_k_f16.loom:ggml_q6k_packed_row64_f16_pair, the 16-wide pair for the aligned f16 prefill kernel. Same arithmetic asggml_q6k_f16_pair, other addresses.motifs/mul_mat_quantized_f16_prefill.loom(the kernel's wide tile) andops/mul_mat_q6_k_packed_f16_wmma.loom(its ordinary body, newweight_layoutconfig, 2 = packed rows) call it.dispatch-q4k-packed-tail.h.A fault in
GGML_HRX_Q6K_PACKED_PREFILL=1, measured and fixedOff its wide tile (tokens not a multiple of 256, or outputs not a multiple of 128) that switch's kernel reads 17,536-byte int8 tiles, while the dispatch binds the 13,440-byte stored rows. It read past the weight. Before this PR:
The switch now selects that kernel only on the wide tile, where it reads the rows it is bound to. After: the same command, 28 OK, 0 FAIL, rc 0. The 128- and 384-token cases that found it are in this PR.
Measured at
ccf14f9c9(gfx1151, Qwen3-4B Q4_K_M, each block under agpu-holdticket)"on" is
GGML_HRX_Q4K_PACKED_TAIL=1 GGML_HRX_Q6K_PACKED_ROWS=1.-p 100-p 1700-p 1700 -n 16llama-perplexity, wikitext-2, 12 chunks,-c 300 -b 300-c 384 -b 384-c 512 -b 512test-backend-ops -o MUL_MATon HRX0llama-bench -p 300,512,1700 -n 16 -r 4, off on off on (tok/s; this block was run on the same sources one rebase earlier):Prefill at 300 tokens is faster with the switches on, at 512 it is within the spread, at 1,700 it is faster. Decode binds the same packed copy in both arms; the 67.8 is one arm of four ([66 66 69 70]) and the other "on" arm reads 74.6, so I take it as the box, not the change, but it is one pair.
hrx-backend-testhost: 69 passed, 0 failed. The matmul suite includes 14 newweight=residentq6_K cases (22 to 384 tokens, an output that is not a multiple of 128).Not covered
GGML_HRX_MODEL_BUFFER_COPY=1they add packed copies.🤖 Generated with Claude Code