Repository navigation
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
Conversation
…eight has one device copy (engine ggml-org#2137, opt-in) Decode and the 256-aligned prefill kernels read a Q4_K weight in the packed row layout. The generic tiled kernels, which take every other batch size (a prompt's last, unaligned batch among them), read the stored layout. Since #304 each layout is a device copy of its own, so a prompt with a remainder left two copies of each Q4_K weight. With GGML_HRX_Q4K_PACKED_TAIL=1 the tiled paths of the plain matmul, the post-op chains and the fused gate/up pair read the packed layout (weight format 44, through the reader the low-token path already uses). Only for a weight in a weights buffer: the executor refuses a packed read of an ordinary tensor. The stored path stays the default. GGML_HRX_HOST_WEIGHT_TRACE=2 prints one line per device copy made, which is how the copies were counted. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
ready for review at c641c84 (opt-in, default path unchanged; device and host rows in the body) |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at c641c84: READY
The remainder of ggml-org#856, made opt-in — "the default path does not change" — for Q4_K batches that are off the 256 grid and can read past the packed layout: const bool packed_tail = use_wmma && match.gate_format == CommonMulMatWeightFormat::Q4K && …, with the dispatch in its own dispatch-q4k-packed-tail.h.
Two things make this the right shape for a follow-on to a release blocker: it cannot make the shipped path worse (opt-in, guarded, new header), and it is filed against ggml-org#2137, the tracked residual that ggml-org#856's fix deliberately left behind. A fix that names its own remainder is what let ggml-org#856 close on time; this is that remainder being taken seriously rather than forgotten.
…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 (the remainder of ggml-org#856). Opt-in: the default path does not change.
The cause, measured
Since #304 the weights stay in the loader's file mapping and each layout HRX reads is a device copy of its own. Counted with one trace line per copy (added here,
GGML_HRX_HOST_WEIGHT_TRACE=2) on Qwen3-4B Q4_K_M (216 q4_K, 37 q6_K; 2,375.9 MiB), fork tip204493d40, one freshllama-benchprocess per shape:-p 512,-p 1024,-p 100-p 1700(3 x 512 + 164)-p 1700 -n 16Decode and the 256-aligned prefill kernels already share the packed row layout of a q4_K weight. The second copy comes from the batch that is not a multiple of 256 tokens (a prompt's remainder): it runs on the generic tiled kernels, which read the stored layout.
The change
GGML_HRX_Q4K_PACKED_TAIL=1makes the tiled paths read q4_K in the packed row layout:dispatch-q4k-packed-tail.h: the switch and the weight predicate, shared by the two dispatch files. It logs which path it took, once.GGML_HRX_HOST_WEIGHT_TRACE=2: one line per device copy made (id, offset, layout, type, bytes).Why only weights buffers: the executor refuses a packed read of a tensor in an ordinary buffer. My first device run had that: three
MUL_MAT(type_a=q4_K, n=22, ..., magnitude=...)cases failed withvalue 0 is read in the packed weight layout ... but its buffer is not flagged as weights. With the predicate those cases keep the stored layout.Measured at
c641c84d4(gfx1151, each block onegpu-holdticket)Qwen3-4B Q4_K_M, switch off -> on:
-p 100-p 1700-p 1700 -n 16llama-perplexity, wikitext-2, 12 chunks,-c 300 -b 300-c 512 -b 512(control, aligned)llama-bench -p 300 -n 0 -r 4, off on off onllama-bench -p 1700 -n 0 -r 4, warm repetitionstest-backend-ops test -b HRX0 -o MUL_MAT: 627 cases OK, 0 FAIL with the switch off, and the same with it on. That includes ten newweight=residentq4_K cases at 6, 22, 164 and 300 tokens (and the model's two feed-forward shapes at 164), which take the packed path with the switch on (the log line saysread the PACKED layout).hrx-backend-testhost: 69 passed, 0 failed.What is not covered
GGML_HRX_MODEL_BUFFER_COPY=1the stored layout is the loader's buffer and costs nothing extra, so the switch adds packed copies there (measured on a pre-hrx: keep the model weights in the loader's file mapping instead of a device buffer (engine #856) #304 base: a 100-token prompt went from 4 packed copies to 148). That is why it is opt-in here and why a later default has to follow the mapping mode.🤖 Generated with Claude Code