Don't materialise the PRX attention mask - #14677
Merged
Merged
Conversation
`PRXAttnProcessor2_0` builds the joint [text | image] mask and then expands it
to the full `[B, heads, L_img, L_all]` before handing it to attention. Every
backend broadcasts a mask itself, so the expansion only costs bandwidth:
* `native` / `_native_*`: torch SDPA broadcasts `attn_mask` natively
* `flex`: `_native_flex_attention` does `attn_mask.expand(batch_size,
num_heads, seq_len_q, seq_len_kv)` on a 4-D mask before building the block mask
* `xformers`: same, `attn_mask.expand(...)` for a 4-D mask
* `sage` / `aiter` / `_native_npu`: reject `attn_mask` outright
At PRX-1B's training shape (batch 32, 1024x1024, patch 32 -> 1024 image + 256
text tokens, 28 heads) the expanded mask is 1120 MiB per block, read once per
block per forward, for 16 blocks.
Passing the unexpanded `[B, 1, 1, L_all]` is bitwise identical -- verified with
`torch.equal` on both the block output and the full gradient vector, and against
an fp32 unfused-MATH reference the relative error is unchanged to 6 significant
figures.
Measured on an H200, PRX-1B, batch 32 @ 1024px, bf16 autocast, 5 warmup / 20
timed steps (fake tensors: no dataloader, no text encoder, no loss terms), with
`set_attention_backend("_native_cudnn")`:
8 GPU DDP, compiled 446.1 -> 427.5 ms/step peak 110.2 -> 75.2 GiB
1 GPU, compiled 383.8 -> 373.8 ms/step peak 65.6 -> 63.4 GiB
1 GPU, eager 702.3 -> 649.4 ms/step peak 132.7 -> 97.7 GiB
On the default `native` backend the same change is 809.1 -> 762.1 ms/step eager.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
PRXAttnProcessor2_0builds the joint[text | image]attention mask and then expands it to a dense[B, heads, L_img, L_all]before handing it todispatch_attention_fn:This PR drops the
.expand(...)and passes the broadcastable[B, 1, 1, L_all]instead.Two reasons:
1. It's the documented shape.
.ai/references/models.md("Attention masks") specifies "Padding mask → bool(B, L)or(B, 1, 1, L)". The current dense(B, heads, L_q, L_k)is neither, so this brings PRX in line with the convention rather than introducing a new one.2. The expansion is pure bandwidth. Every backend broadcasts the mask itself:
native/_native_*— torch SDPA broadcastsattn_masknativelyflex—_native_flex_attentiondoesattn_mask.expand(batch_size, num_heads, seq_len_q, seq_len_kv)on a 4-D mask before building the block maskxformers— same,attn_mask.expand(...)for a 4-D maskflash/_flash_3/sage/aiter/_native_npu— rejectattn_maskoutright*_varlenpaths reduce it via_normalize_attn_mask, whose 4-D branch isattn_mask.any(dim=(1, 2))— so[B, 1, 1, L]normalizes fine and keeps the varlen path (a dense additive-float mask would not, permodels.md)At PRX-1B's training shape (batch 32, 1024×1024, patch 32 → 1024 image + 256 text tokens, 28 heads) the expanded mask is 1120 MiB per block, read once per block per forward, for 16 blocks.
Why
[B, 1, 1, L]and not the 2-D[B, L_all]joint_maskis already(B, L_all), so passing it directly looks tempting and_native_attentionwould handle it (it reshapes a 2-D mask to[B, 1, 1, L_k]). But_native_flex_attentiontreats dim 1 of a 2-D mask as the query axis —attn_mask.view(attn_mask.size(0), 1, attn_mask.size(1), 1)— which is wrong here, sinceL_all != seq_len_qfor PRX. The 4-D form is unambiguous on every backend, so that's what this uses.Is it equivalent?
Bitwise, yes. Verified with
torch.equalon both the block output and the concatenated gradient vector, across batch sizes 1 and 3, with and without a mask, using a realistically padded mask (half the text tokens masked). Against an fp32 unfused-MATH reference the relative error is unchanged to 6 significant figures on the forward and to the 8th digit on gradients.Benchmarks
H200, PRX-1B (
hidden_size=1792,num_heads=28,depth=16,patch_size=32,bottleneck_size=256), batch 32 @ 1024×1024, 256 text tokens, fp32 master weights + bf16 autocast, 5 warmup / 20 timed steps,torch.cuda.synchronize()around the timed region. Fake tensors — no dataloader, no text encoder, no loss terms — so this isolates the model step.With
set_attention_backend("_native_cudnn"):On the default
nativebackend the same change is 809.1 → 762.1 ms/step eager.The memory is the larger effect: −35 GiB at eager batch 32, −35 GiB on 8-GPU compiled.
Self-review notes
Ran the
self-reviewskill on the diff against.ai/references/review-rules.md,code_style.mdandmodels.md.Blocking issues: none.
Non-blocking / additional info:
# Copied fromlinkage.PRXAttnProcessor2_0carries no# Copied fromheader and nothing else insrc/ortests/references it, so nomake fix-copiespropagation is needed and there is no ripple to other models.models.mdalso says "Only pass when the batch actually contains padding… Seepipeline_qwenimage.pyencode_promptfor the pattern:if mask.all(): mask = None."PRXPixelPipelinealways passes a mask. Dropping it when it carries no information is worth roughly another 20% of the eager step in my measurements (a no-mask forward also regains the flash path). That is a pipeline behaviour change, so I have deliberately left it out of this PR — happy to open a separate one if you'd like it.docs/update applies. The reasoning lives as a comment at the site.Verdict: READY.
Before submitting
self-reviewskill on the diff?PRXPixelPipelineintegration (Prx #12525, Add PRXPixelPipeline: pixel-space PRX text-to-image pipeline #13928).Who can review?
@sayakpaul @yiyixuxu —
git logontransformer_prx.pyand the models entry in the template. Also related: #12787 (PRX model compilation), since this change matters most undertorch.compile.