kempnerforge.model.masking¶
Attention mask builders.
One predicate covers every visibility rule self-attention implements:
visible(q, k) = same_document(q, k)
AND (k <= q OR same_nonnegative_group(q, k)) AND valid_key(k)
build_attention_mask materializes it once per forward, in whichever form
the configured backend consumes: a FlexAttention BlockMask under
attention_backend="flex", a dense (batch, 1, seq_len, seq_len) bool
tensor for F.scaled_dot_product_attention otherwise, or None when no
term applies and plain causal attention is already exact. Attention and
MoTAttention take that one object and derive no mask of their own, so a
further visibility rule is a new argument here rather than a new parameter on
every attention signature.
FlexAttention replaces the dense mask rather than computing it: the predicate
is compiled into the attention kernel and fully-masked blocks are skipped
rather than computed. For document packing the mask is block-diagonal and
mostly zeros, so that is the difference between paying for the full S x S
attention and paying only for the blocks inside a document. Key validity is
deliberately not folded into the BlockMask; those batches take the dense
form instead.
Functions
|
Build the one attention mask for a forward pass. |
|
Dense boolean mask for |
|
Block-diagonal causal mask: q attends to k iff same document and k <= q. |
|
Return the |
|
Dense |
|
Select the flex helpers compiled for a varying batch size, or the pinned-shape pair. |
- kempnerforge.model.masking.use_dynamic_flex_shapes(enabled)[source]¶
Select the flex helpers compiled for a varying batch size, or the pinned-shape pair.
- Parameters:
enabled (bool)
- Return type:
None
- kempnerforge.model.masking.flex_attention_fn(compiled)[source]¶
Return the
flex_attentioncallable to use.- Parameters:
compiled (bool) – Whether to return the
torch.compile-wrapped kernel. True on CUDA; False on CPU, where the eager decomposition keeps unit tests off Inductor’s C++ codegen path. Note torch 2.11 has no CPU backward for FlexAttention, so the CPU path is forward-only.- Return type:
- kempnerforge.model.masking.build_attention_mask(*, backend, batch, seq_len, device, doc_ids=None, group_ids=None, key_padding_mask=None)[source]¶
Build the one attention mask for a forward pass.
The single entry point every caller uses, and the one place the ids are validated: it owns which backend form the semantics are expressed in, so callers pass ids and attention consumes one mask.
backend="flex"returns aBlockMaskwhenever the predicate is expressible in it, which is every case except key validity; everything else returns the dense form.The returned mask is square over the residual stream, so it is valid only for a forward whose keys are exactly its queries. Incremental decoding with a
KVCachegrows the key axis every step, which is whyTransformer.forwardrefuses every mask input alongsidekv_caches.- Parameters:
backend (str) –
ModelConfig.attention_backend,"sdpa"or"flex".batch (int) – Batch size of the residual stream.
seq_len (int) – Query and key length, the residual stream’s length.
device (torch.device) – Device to materialize the mask on. Any ids passed must already be on it.
doc_ids (torch.Tensor | None) – Optional per-token document ids, shape
(batch, seq_len). Tokens attend only within their own document.group_ids (torch.Tensor | None) – Optional per-token bidirectional-visibility group ids, shape
(batch, seq_len); negative means no group. Tokens sharing a nonnegative id see each other regardless of order. All-negative ids give plain causal visibility but still build a mask;Nonekeeps the unmasked path.key_padding_mask (torch.Tensor | None) – Optional per-key validity mask, shape
(batch, seq_len);True= attend,False= drop (e.g. the visual tokens of padded video frames).
- Returns:
A
BlockMask, a dense(batch, 1, seq_len, seq_len)bool tensor, orNonewhen no term applies and attention is plain causal.- Raises:
ValueError – If any input’s shape is not exactly
(batch, seq_len).- Return type:
torch.Tensor | BlockMask | None
- kempnerforge.model.masking.same_group_mask(group_ids)[source]¶
Dense
same_nonnegative_groupterm, for the dense mask path.The counterpart of the group term inside
build_doc_causal_block_mask.- Parameters:
group_ids (torch.Tensor) – Per-token group ids, shape
(batch, seq_len); negative means no group.- Returns:
Bool tensor of shape
(batch, 1, seq_len, seq_len),Truewhere query and key share a group id and that id is nonnegative.- Return type:
- kempnerforge.model.masking.build_dense_attention_mask(*, batch, seq_len, device, doc_ids=None, group_ids=None, key_padding_mask=None)[source]¶
Dense boolean mask for
F.scaled_dot_product_attention.Truemeans the query may attend to the key. Starts causal, widens by the group term, then narrows by document isolation and key validity, so a group never reaches across a document and never revives an invalid key.A query row left with no reachable key – the leading positions of an all-padded clip, say – would softmax over all
-infand yield NaN, so such rows are unmasked instead. Their outputs are discarded downstream, byoutput_sliceor by the clip’s labels being all-100.An explicit mask is not a FlashAttention-2 shape, so SDPA falls back to the mem-efficient or math kernel whenever one is returned.
Nonekeeps theis_causalfast path, which is why it is returned rather than an all-causal tensor.Shapes are not checked here:
build_attention_maskvalidates the ids before it hands them on.- Parameters:
batch (int) – Batch size of the residual stream.
seq_len (int) – Query and key length.
device (torch.device) – Device to materialize the causal term on. Any ids passed must already be on it.
doc_ids (torch.Tensor | None) – Optional per-token document ids, shape
(batch, seq_len).group_ids (torch.Tensor | None) – Optional per-token group ids, shape
(batch, seq_len); negative means no group.key_padding_mask (torch.Tensor | None) – Optional per-key validity mask, shape
(batch, seq_len);True= attend.
- Returns:
Bool tensor of shape
(batch, 1, seq_len, seq_len), orNonewhen every optional term isNoneand the mask would be plain causal.- Return type:
torch.Tensor | None
- kempnerforge.model.masking.build_doc_causal_block_mask(doc_ids, device, group_ids=None)[source]¶
Block-diagonal causal mask: q attends to k iff same document and k <= q.
group_idswidens causality within caller-defined spans:visible(q, k) = same_document AND (k <= q OR same_nonnegative_group). Tokens with a negative group id stay purely causal, and document isolation always dominates, so a group id reused across documents never joins them. Withoutdoc_idsthe document term is dropped and each row is one document.Built once per forward and shared by every layer.
H=Nonebroadcasts the mask over heads, which is what lets grouped-query attention passenable_gqa=Trueinstead of materializing repeated K/V heads, and what keeps the mask correct under tensor parallelism, where each rank holds only a shard of the heads.seq_lenmust be at leastFLEX_BLOCK_SIZE: below it an Inductor-compiled model silently leaks attention across document boundaries, soJobConfig.validaterejects that configuration outright. This kernel is not the culprit – it is exact at those lengths, as is the same model underbackend="eager"or"aot_eager"; only Inductor codegen diverges.- Parameters:
doc_ids (torch.Tensor | None) – Optional per-token document ids, shape
(batch, seq_len);Nonetreats each row as one document.device (torch.device) – Device on which to materialize the
BlockMask.group_ids (torch.Tensor | None) – Optional per-token bidirectional-visibility group ids, shape
(batch, seq_len); negative means no group.Nonekeeps the mask exactly causal-within-document. Ids are compared at full int64 width, unlikedoc_ids, so any caller-defined id is usable.
- Returns:
A
BlockMaskover(batch, seq_len, seq_len), head-broadcast.- Raises:
ValueError – If neither
doc_idsnorgroup_idsis given.- Return type:
BlockMask