test(attention): anchor the context-parallel suite to an independent reference - #3536
nvegesna-netizen wants to merge 2 commits into
Conversation
run_attention_with_cp.py grades a context-parallel run against a non-CP run *of the same backend*. That validates the CP plumbing and nothing about the kernel beneath it: any error affecting both sides equally cancels, and the comparison passes. This is not theoretical. A backend was found returning an all-zero output while two of three cp_comm_type values reported PASS -- zeros on one side, zeros on the other, agreement. Only p2p failed, and only because its ring merge turned the accompanying -inf LSE into a NaN that the zeros did not match. The same structure then reported a partial fix as a regression, because correcting one side made the two sides disagree while the fully broken state had looked fine. So the suite could report green on a backend computing nothing at all. This checks one (batch, head) slice of the non-CP output against a float64 reference before the comparison, so a systematically wrong kernel cannot cancel itself out. It is a sanity bound rather than a precision test -- the existing CP/no-CP comparison covers precision -- aimed at catastrophic wrongness: zeros, NaNs, a mask applied in the wrong place. An all-zero output is asserted separately so it is named rather than reported as a large error. float64 and not float32: torch computes fp32 matmuls in TF32 on Ampere and newer, whose significand is 11 bits, the same as fp16, so an fp32 reference cannot judge a bf16 kernel. Configurations the reference does not model -- fp8, bias, non-vanilla softmax, thd, padding masks -- are skipped rather than approximated, so it never fails for a reason it cannot explain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…chor Softcap rescales the scores through tanh before the softmax, and the float64 reference does not model it, so test_cp_with_flash_attention_softcap failed on all three cp_comm_type values against a correct backend. Modelling it would mean duplicating the softcap formula in the reference, which is how a reference starts reimplementing the thing it is meant to check independently. Skipped instead, alongside fp8, bias, non-vanilla softmax, thd and padding masks. Caught by the false-positive arm of the verification run: with the anchor in place and a correct backend, the whole FlashAttention CP suite must pass. Three softcap cases did not. That arm exists because a reference that rejects correct kernels would be worse than the gap it closes. Measured on B200 in the same run: against the backend that returns all zeros, the anchor now fails all_gather and a2a naming the zero output -- both of which reported PASS before it existed -- while every non-softcap configuration on a correct backend still passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
One note on merge order, since these two are related but independent. Please land #3532 before this one. #3532 fixes the FA4 defect that this change uses as its Kept separate from #3532 deliberately rather than folded in:
No dependency between them otherwise — different files, cut from |
|
Problem
tests/pytorch/attention/run_attention_with_cp.pygrades a context-parallel run against a non-CPrun of the same backend (
tensors_no_cpvstensors_cp). That validates the context-parallelplumbing and nothing about the kernel beneath it: any error affecting both sides equally cancels,
and the comparison passes.
This is not hypothetical. Measured on B200 against the FA4 defect in #3528, where the backend
returns an all-zero output for causal attention:
cp_comm_typeall_gathernon-CP output is all zerosa2anon-CP output is all zerosp2pTwo of three communication types reported PASS on a backend returning nothing at all. Zeros on one
side, zeros on the other, agreement. Only
p2pfailed, and only incidentally: its ring merge turnedthe accompanying
-infLSE into a NaN that the zeros on the other side did not match.The same structure also inverts the sign of a partial fix. An earlier attempt at #3528 corrected the
CP side only, which made the two sides disagree — so the fix was reported as a regression in
all_gatheranda2a, while the fully broken state had been reported as a pass.Change
Check one
(batch, head)slice of the non-CP output against an independently computed float64reference, before the CP/no-CP comparison, so a systematically wrong kernel cannot cancel itself out.
It is a sanity bound, not a precision test — the existing CP/no-CP comparison already covers
precision. It targets catastrophic wrongness: zeros, NaNs, a mask applied in the wrong place. An
all-zero output is asserted separately from the tolerance so it is named rather than surfacing as a
large numeric error.
float64 rather than float32: torch computes fp32 matmuls in TF32 on Ampere and newer, whose
significand is 11 bits — the same as fp16 — so an fp32 reference cannot judge a bf16 kernel.
Configurations the reference does not model are skipped rather than approximated: fp8, attention
bias, non-vanilla softmax,
thd, padding masks, and softcap. Modelling softcap would meanduplicating its
tanhrescaling in the reference, which is how a reference starts reimplementingthe thing it is meant to check independently.
Verification (B200)
The negative control is the real defect rather than a synthetic one: this branch is cut from
main,which still carries the FA4 zero-output bug, so the check can be tested against a backend that is
genuinely returning nothing.
out.abs().max() = 0.0confirmedall_gather,a2a,p2pall fail, naming the all-zero outputall_gather,a2a,p2pall passThe third row matters as much as the second. A reference that rejects correct kernels would be worse
than the gap it closes, and it is the more likely way to get this wrong — the mask convention has to
match at bottom-right alignment, sliding windows and GQA. An earlier revision failed three softcap
cases for exactly that reason; those are now skipped.
Note
#3532 fixes the FA4 defect used as the negative control here. Once it lands, that particular
backend will no longer return zeros — but the gap this closes is structural and independent of it:
the suite cannot detect any error affecting CP and non-CP equally.