The multi-pass KV-block reduction added by 1bit-MONSTER/llama.cpp#13 (branch feat/hrx-decode-split-multipass, commit f59f6d37a on fa1a4563) makes flash_attention.decode_split work above key_value_token_capacity 2048, but a residual out-of-bounds access intermittently faults the decode instead of declining. That violates the HRX lane's constraint that a dispatch must decline and fall back rather than fail the decode.
Symptom
gfx1151 (Strix Halo), Qwen3-Coder-30B-A3B-Instruct-Q4_K_M, -dev HRX0, llama-bench -p 0 -n 8 -d <D>:
Warning: Queue error - HSA_STATUS_ERROR_MEMORY_FAULT
E graph_compute: ... AMDGPU memory access fault at device address 0x00007f508ea94000 (reason mask 0x00000001)
E llama_decode: failed to decode, ret = -3
5-run GPU-fault counts with the partial transients aligned to 4096:
| depth |
capacity |
blocks |
faults |
| 1900 |
1920 |
30 |
0/5 |
| 2000 |
2048 |
32 |
0/5 |
| 2100 |
2112 |
33 |
0/5 |
| 2500 |
2560 |
40 |
0/5 |
| 3000 |
3008 |
47 |
1/5 |
| 4800 |
4864 |
76 |
0/5 |
With the SAME code but the partial transients aligned to 256, d2100 is 5/5. GGML_HRX_DISABLE_DISPATCH=flash_attention_decode_split makes every faulting depth pass (0/5), so the fault is in the multipass path, not the fallback.
Excluded (each measured over 5 runs)
- The reducer - no-op'ing
reduce_completed.multipass (inserting template.return as its first statement) still faults 5/5, so it is not the reduction math.
- The completion counter - replacing its
atomic.reduce<addi> -N reset with a plain release-ordered store of 0 changes nothing; so does zero-initialising it via ConstantInitialization.
- The exact-33 assume bound - relaxing every
range(..., 33, 4096) to range(..., 1, 4096) changes nothing.
- Static sizing - the dispatch's transient bytes are exactly
256 * blocks (partial_max/sum) and 16384 * blocks (partial_output), strictly linear, and the kernel's views match.
- Every readable access is in bounds - the produce's channel stores (
lane_output_base = lane * 4, lane < 32, so the channel is in {0,4,...,124} and a vector<4xf16> store keeps channel + 4 <= value_head_size = 128); the scalar max/sum row stores (guarded by query_head_valid); the reduce's output store (query_head = key_value_head * query_heads_per_key_value_head + query_row < 32, output_channel < value_head_size); the K/V loads (element-guarded by bounded_key_value_token_count); pack_completed_q8's output size (q8_1_x4_byte_count(1, output_hidden_size)).
- Allocation - alignment 256 -> 4096 helps (d2100 5/5 -> 0/5); padding the transient SIZES makes it worse (d3000 5/5, d2100 4/5); 64K alignment is worse still; padding the block count to whole 64-block waves does not help.
Assessment
Every access is provably in bounds, yet the fault moves with the allocation layout and cannot be removed by adding slack. The best remaining explanation is a wrong address emitted by the JIT-specialised code for this capacity, which allocation cannot fix. Prime suspect: the multipass output pass (flash_attention_decode_split_f32_f16_wmma.loom ~911-920), the only O(blocks)-per-output-element code in the reduce.
Impact
1bit-MONSTER/llama.cpp#13 masks part of this by aligning the partial transients to 4096, but the fault is not eliminated and a decode can still fail - so this is not yet safe to land as-is.
The multi-pass KV-block reduction added by 1bit-MONSTER/llama.cpp#13 (branch
feat/hrx-decode-split-multipass, commitf59f6d37aonfa1a4563) makesflash_attention.decode_splitwork abovekey_value_token_capacity2048, but a residual out-of-bounds access intermittently faults the decode instead of declining. That violates the HRX lane's constraint that a dispatch must decline and fall back rather than fail the decode.Symptom
gfx1151 (Strix Halo),
Qwen3-Coder-30B-A3B-Instruct-Q4_K_M,-dev HRX0,llama-bench -p 0 -n 8 -d <D>:5-run GPU-fault counts with the partial transients aligned to 4096:
With the SAME code but the partial transients aligned to 256, d2100 is 5/5.
GGML_HRX_DISABLE_DISPATCH=flash_attention_decode_splitmakes every faulting depth pass (0/5), so the fault is in the multipass path, not the fallback.Excluded (each measured over 5 runs)
reduce_completed.multipass(insertingtemplate.returnas its first statement) still faults 5/5, so it is not the reduction math.atomic.reduce<addi> -Nreset with a plain release-ordered store of 0 changes nothing; so does zero-initialising it viaConstantInitialization.range(..., 33, 4096)torange(..., 1, 4096)changes nothing.256 * blocks(partial_max/sum) and16384 * blocks(partial_output), strictly linear, and the kernel's views match.lane_output_base = lane * 4,lane < 32, so the channel is in {0,4,...,124} and avector<4xf16>store keepschannel + 4 <= value_head_size = 128); the scalar max/sum row stores (guarded byquery_head_valid); the reduce's output store (query_head = key_value_head * query_heads_per_key_value_head + query_row < 32,output_channel < value_head_size); the K/V loads (element-guarded bybounded_key_value_token_count);pack_completed_q8's output size (q8_1_x4_byte_count(1, output_hidden_size)).Assessment
Every access is provably in bounds, yet the fault moves with the allocation layout and cannot be removed by adding slack. The best remaining explanation is a wrong address emitted by the JIT-specialised code for this capacity, which allocation cannot fix. Prime suspect: the multipass output pass (
flash_attention_decode_split_f32_f16_wmma.loom~911-920), the only O(blocks)-per-output-element code in the reduce.Impact
1bit-MONSTER/llama.cpp#13 masks part of this by aligning the partial transients to 4096, but the fault is not eliminated and a decode can still fail - so this is not yet safe to land as-is.