mirror of
https://github.com/Comfy-Org/comfy-kitchen.git
synced 2026-11-04 23:37:42 +00:00
[GH-ISSUE #214] int8 attention returns NaN for prepared dense masks containing -inf (gfx1103, ROCm) #62
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-kitchen#62
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @LuXuxue on GitHub (Sep 29, 2026).
Original GitHub issue: https://github.com/Comfy-Org/comfy-kitchen/issues/214
Summary
int8_attentionreturns NaN for the output rows whenever anattn_maskdrops keysvia
-inf, on the prepared dense mask path introduced in #208. A large finitenegative bias produces correct output; only
-inffails. The same mask expressed astorch.boolalso NaNs, because the bit-packed path synthesizes-infas the biasinternally.
This is reproducible on an unmodified checkout of
mainat888b13e(v0.2.36):test_hip_compact_dense_bool_matches_float_biastest_hip_dense_16_accuracy_and_snapshottest_hip_fused_dense_mask_matches_snapshottest_hip_dense_16_preserves_constant_valuestest_int8_attention_long_masked_sequencetest_int8_attention_mask_gqa_broadcast_and_fully_masked_rowtest_hip_fused_dense_mask_graph_reads_updated_biasEnvironment
gfx1103(RDNA3, 6 WGP)888b13e(git describe: v0.2.36), unmodifiedHIP_PLATFORM=amd HIP_ARCHS=gfx1103 python setup.py build_ext --inplaceReproducer
Output on
888b13e:What I have established
1. It is
-infspecifically, not a large-magnitude bias. Sweeping the additivebias value on a fixed mask, every finite value is fine down to
-1e9:-inf-1e9-1e6-72135(=-kMaskedScoreBase2)-50000(=kMaskedScore)-1e4So there is no magnitude threshold to tune around; the finite sentinel already used
elsewhere in the kernel is sufficient.
2. It is the prepared path, not the short-key path.
kv_length <= 64skips maskpacking and takes
MaskMode::kCustom, which is clean. The first failing length is 65:kv_lengthAffects
head_dim64, 128 and 256 alike, and both the fusedint8_attentionandprequantize_int8_attention+int8_attention_from_prequantized.3. Any
-infin a row poisons the entire row, not just the dropped positions.With exactly one key kept, at any position, the whole output is NaN:
Rows that are fully dropped are NaN rather than the zeros the
row_validbookkeeping implies they should be.4. The
-infreaches the score as a bias. Inint8_attn.hipthe packed mask isread and fma'd into the score:
prepare_dense_mask_kernelstores__uint_as_float(0xff800000u)(i.e.-inf) for adropped key, and
prepare_key_mask_kernellikewise. The bit-packed path does not evenread a bias:
bool_mask_biasconstructs one,which explains why the bool mask fails identically.
5.
-ffast-mathis not folding the guard. I expected thekDensevalidity test tobe optimized away and initially reported that as the cause; that was wrong. Compiling
int8_attn.hipforgfx1103with and without-ffast-mathand counting the emittedinstructions:
0x7f800000comparesv_cmp_*_f32v_exp_f32-ffast-mathon-ffast-mathoffThe raw-bits test at
int8_attn.hip:692,survives in both builds, and the guard runs after
tile_mhas been reduced, so itcan only repair a tile whose maximum is already
-inf. It does not sanitize theindividual
-infbias values inside an otherwise-valid tile, andtile_mfor such atile is finite, so the guard does not fire. I do not currently see where the
-infbias turns into a NaN instead of the expected
exp2(-inf - tile_m) == 0, and I wouldrather ask than guess.
One observation that may be the actual bug
The prepared mask is packed in the base-two domain (
prepare_dense_mask_kernelstores
bias * kLog2e), and the kernel accounts for that asymmetrically:kLog2eis applied totile_monly for the 16-bit prepared path, and not for thefloat32 prepared path — yet the float32 bias it is added to was multiplied by
kLog2eat packing time. The sentinel substituted by the validity test,kMaskedScore = -50000, is likewise never rescaled.This is consistent with the observed split: the clamp fixes everything routed through
the 16-bit and bit-packed read sites, and leaves the float32 dense path failing. I have
not confirmed it is the NaN source, and it is a correctness question independent of the
NaN, so I am flagging it rather than claiming it.
Suggested direction
Clamping the bias at the read sites — replacing any non-finite packed value with the
existing finite sentinel, whose sign is preserved so
+infstill means "stronglyattend" — fixes the bit-packed and 16-bit paths completely:
plus changing
bool_mask_biasto return-kMaskedScore * (float)droppedinstead ofthe synthesized
0xff800000u. Note the base-two-domain question in the previoussection applies to whichever sentinel is chosen, since
kMaskedScoreis in thenatural domain.
Measured on
888b13ewith that clamp applied to the three dense read sites andbool_mask_biasreturning a finite sentinel: 541 -> 423 failed, 0 newly broken.Per test:
test_hip_fused_dense_mask_matches_snapshottest_hip_fused_dense_mask_graph_reads_updated_biastest_hip_dense_16_preserves_constant_valuestest_int8_attention_long_masked_sequencetest_int8_attention_mask_gqa_broadcast_and_fully_masked_rowtest_hip_compact_dense_bool_matches_float_biastest_hip_dense_16_accuracy_and_snapshotNote that
test_hip_compact_dense_bool_matches_float_biasdoes not improve eventhough the bool path itself now returns finite values — the test also compares against
expected, which is the additive-infpath, so it keeps failing. The two 192-counttests that remain are the ones exercising the additive path.
The remainder are the additive
-infpath, which the read-site clamp does notreach, consistent with point 4: that
-infis already in the packed buffer, andsomething between the packed read and
exp2turns it into NaN. That is the part Ihave not solved.
Questions
kLog2easymmetry atint8_attn.hip:699intentional for the float32 densepath, given the bias arrives pre-scaled?
exp2does-infbecomeNaN rather than the expected
exp2(-inf - tile_m) == 0? Clamping the read sitesdoes not change the outcome there, so the
-infmust be arriving by another routethat I have not identified.
@tangzzycc commented on GitHub (Sep 29, 2026):
I can reproduce this issue on gfx1151 (AMD Radeon 8060S) as well, so it does not appear to be limited to gfx1103. On
main@888b13e, the same input produces:I tried fixing this in the HIP kernel. My current approach determines mask validity directly from the Boolean bit or the raw FP16/BF16/FP32 exponent bits, tracks valid keys in a lane-local bitset, and prevents masked
-infvalues from entering FMA, max reduction, orexp2. Masked probabilities are explicitly set to zero, while fully masked rows still produce exact zero outputs.After the change, all three mask variants produce zero NaNs. Regression tests also pass for Boolean, FP16, BF16, and FP32 masks across head dimensions 64, 128, and 256, covering both fused and prequantized paths.
The current fix has a measurable performance cost in preliminary gfx1151 A/B tests:
Q=129, KV=257, HD=64: effectively unchanged;Q=129, KV=257, HD=256: approximately 3% slower;Q=256, KV=2048, HD=128: approximately 9–12% slower for the prequantized attention-kernel path, and 8–14% slower for the completeck.int8_attentionoperator call including quantization, mask preparation, and the attention kernel.The regression appears to come from the additional validity tracking and branch/register scheduling pressure.
@lightzl commented on GitHub (Sep 29, 2026):
Real-world confirmation on gfx1151 (AMD Radeon 8060S, ROCm 10.1) — attached is a 2.1 MB minimal reproducer.
This is not only a unit-test issue: with comfy-kitchen 0.2.36 a Qwen-Image 2.1 (bf16) workflow in
ComfyUI 0.37.0 produces garbage images, while 0.2.35 produces correct images for the same
workflow/seed. Instrumenting all comfy_kitchen calls of the real run (4252 calls):
identical in both versions (0.2.35 -> finite, 0.2.36 -> NaN).
The NaN then propagates through the model and the image is corrupted (pixel correlation with the
0.2.35 output: 0.13).
Minimal case in the bundle: q=(1,1,26,128) bf16, k=v=(1,1,4162,128) bf16,
attn_mask=(1,1,26,4162) bool with 325 False entries, scale=None.
0.2.36 -> 3072/3328 NaN; 0.2.35 -> 0 NaN
Trigger follows the MASK, not the data (random q/k/v + captured mask -> NaN; captured q/k/v +
all-True mask -> finite; all-False mask -> NaN; no mask -> finite).
Run: python repro.py (also attached: README.md, cross_test.py)
Workaround for users on ROCm: pin comfy-kitchen==0.2.35 until a fixed release.
comfy-kitchen-036-nan-repro.tar.gz
@comfyanonymous commented on GitHub (Sep 29, 2026):
This should fix it:
github.com/Comfy-Org/comfy-kitchen@19ea55b9eb@tangzzycc commented on GitHub (Sep 30, 2026):
@comfyanonymous Thanks, I tested commit
19ea55bon gfx1151, and it fixes the issue for me. The original reproducer now reports zero non-finite values for additive-inf, additive-60000, and Boolean masks. The newtest_hip_dense_mask_empty_tilescases also all pass (24 passed).I also compared kernel-only performance before and after the fix across head dimensions 64/128/256 and Boolean/FP16/FP32 masks, and did not observe any meaningful performance impact. Representative FP16 results (median of 9 runs):
888b13e)19ea55b)Q=129, KV=257, HD=64Q=256, KV=2048, HD=128Q=129, KV=257, HD=256