Skip to content
mlmentorship

Sequence packing with block-diagonal masks

Pack short examples without cross-example attention. Separate padding savings, position handling, loss masking, and measured throughput.

Published · 6 min read ·Role-specific ·Advanced

Summary

Sequence packing concatenates multiple training examples back-to-back into a single fixed-length sequence and uses a block-diagonal attention mask so each example only attends within itself, eliminating the FLOPs and memory wasted on padding tokens.

Most NLP corpora have heavily skewed length distributions: many short examples, few long ones. With naive padding to the longest example in the batch, the wasted-token ratio is

This ratio counts padded token positions, not total attention FLOPs or end-to-end throughput. Dense padded implementations can still compute on masked positions; efficient variable-length kernels can avoid that work. How much packing helps depends on lengths, bucketing, kernels, and the rest of the training step.

The mechanism

  1. Pick a fixed packed length (e.g., 8192).
  2. Concatenate examples until adding the next would exceed . Record the boundaries (cumulative sequence lengths, often called cu_seqlens).
  3. Build a block-diagonal attention mask: token in example cannot attend to any token in .
  4. Compute attention with a kernel that respects cu_seqlens (FlashAttention-2 supports this natively via the varlen API).
  5. Apply the loss only on response tokens within each example (mask the boundaries and any prompt tokens for SFT).

The worked construction resets position IDs at each boundary. The block-diagonal mask provides attention isolation; position handling must separately match the model and kernel contract.

Learning objective: map packed-example boundaries to reset position IDs and a causal block-diagonal attention mask that permits no cross-example attention.

Learning objective

Earlier physical position does not grant attention across example boundaries

Earlier position does not permit crossing an example boundaryTokens 0 and 1 belong to A; tokens 2 and 3 belong to B. B cannot attend to A even though A appears earlier in the packed buffer.Causal order stays inside each packedexample111111Rows and columns: A0, A1, B0, B1B reads its own earlier tokens, notearlier example A.
Derivation

1 · PACK TOKENS, KEEP BOUNDARIES

slot012345
examplee1e1e1e2e2e3
position012010

Boundaries: `cu_seqlens = [0, 3, 5, 6]`
The intervals [0, 3), [3, 5), and [5, 6) recover the three examples without padding.

2 · MASK BY EXAMPLE AND TIME

q \ ke1:0e1:1e1:2e2:0e2:1e3:0
e1:0AFFXXX
e1:1AAFXXX
e1:2AAAXXX
e2:0XXXAFX
e2:1XXXAAX
e3:0XXXXXA

A = attend · F = future · X = other example
Only the three lower-triangular diagonal blocks participate in attention.

first use the cumulative boundaries to split the physical row into logical examples and restart positions at each boundary. Then read each mask row as one query: it may attend backward inside its own example (A), never forward in causal training (F), and never across an example boundary (X). Packing removes padding; boundaries preserve the same attention problem each example had before concatenation. Loss masking is a separate per-token decision. Original worked example informed by Krell et al. (2021) and the FlashAttention variable-length interface.
Check: Why may e2:1 attend to e2:0 but not to the earlier physical slot e1:2?
Read it this way: Tokens 0 and 1 belong to A; tokens 2 and 3 belong to B. B cannot attend to A even though A appears earlier in the packed buffer.
Can B1 read A1 merely because A1 appears earlier in the packed buffer?

Numbers

For the illustrated lengths [3,2,1], padding three examples to length three allocates nine token positions for six real tokens. The six-token packed row has no slack, reducing physical positions from nine to six. That 1.5x position ratio is not a measured GPU speedup.

Compare packed and unpacked steps with matched examples, labels, position semantics, and loss weighting. Benchmark tokens per second, peak memory, and task quality on the actual model, precision, hardware, and kernel. Packing can otherwise change per-example weighting even when the attention mask is correct.

Common implementation pitfalls

  • Confusing positions with isolation. A correct block-diagonal mask forbids cross-example attention regardless of numbering. Standard fixed-frequency RoPE preserves within-example attention under a common position shift; absolute embeddings or other scaling rules may not. Follow the specific model/API contract.
  • Wrong target boundaries. Mask any next-token target that would train one example to predict the first token of another. Attention masking and label masking solve different problems.
  • Mixing prompt and response in SFT without masking. For SFT, mask out prompt tokens from the loss within each packed example.
  • Assuming the mask saves computation. A dense implementation can still materialize the full matrix. Use a boundary-aware kernel and measure end-to-end savings rather than inferring them from zero attention weights.

Predict before revealing: keep the causal block-diagonal mask, but change positions from [0,1,2,0,1,0] to [0,1,2,3,4,5]. With standard fixed-frequency RoPE, does this create cross-example attention?

Identify which contract changed

No. The mask still excludes other examples. Each example received only a common position shift, so relative rotary positions and its attention weights are unchanged. Removing the mask would break isolation even with reset positions. Do not extend this invariance claim to arbitrary positional embeddings or scaling variants.

When not to pack

  • Very long single examples that fill or exceed on their own (no concatenation possible; padding is already minimal).
  • When examples have inter-document context that should attend across boundaries (rare).
  • During inference, use the serving engine’s multi-request batching and KV-cache contracts; training-style concatenation is not a drop-in replacement.