Support Ring Attention with DeepSeek DSA Sparse Indexer - #4767
Conversation
There was a problem hiding this comment.
Code Review
This pull request enables support for sparse indexer masks within the TPU Tokamax ring attention kernel, integrating indexer loss logging and auxiliary loss calculations into the training loop. The review feedback highlights a few critical runtime issues: a mismatch in the evaluation metric dictionary key for logging indexer loss, an incorrect keyword argument (_shape instead of shape) when instantiating FullMask, and a potential ValueError when concatenating zero-dimensional arrays in the loss calculation when scan_layers=False.
b4b7cde to
5624dfa
Compare
5624dfa to
d7881bd
Compare
b2f31fe to
e642709
Compare
It seems Linen is currently broken for DSA, Line, and the same configs succeed in nnx (Should I add a check that rejects linen + Indexer?). What happened was that I suddenly stopped seeing Indexer loss calculation in my traces, and I found that it seems that due to some recent changes of NNX, indexer_losses silently defaulted to 0 (breaking loss function and backward for Indexer), just like in #4525 for maxtext/src/maxtext/layers/nnx_decoders.py Line 1092 in 5e89a48 nnx.Intermediate after a layer pass. I followed I similar approach by creating a custom subclass for indexer_losses, making sure it does not get dropped by the nnx.Intermediate filter and manually pop it in train.py. I believe the only part this would affect is how indexer_losses is handled, and I have checked that it correctly appears again in the logs as shown in the PR description, all other components remain the same, I believe comparison with Linen is not required.
|
|
🤖 Hi @zcjhao, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
There was a problem hiding this comment.
This pull request introduces comprehensive, end-to-end support for DeepSeek Sparse Attention (DSA) Indexer with Tokamax Ring Context Parallelism during training. The implementation includes dynamic per-ring-step mask extraction for both the forward and backward passes, correct handling of the auxiliary Flax NNX loss variables under scanned layers, and load balancing mask reordering fixes. The codebase is well-structured, follows established project conventions, and includes solid test coverage for all new functionality.
🔍 General Feedback
- Excellent Architecture & Design: The slicing and tiling logic of the global indexer mask into per-step KV-shard blocks, and aligning transposition schedules for the backward pass with the hardware scheduler, is extremely well thought out and executed.
- Robust Integration with NNX: Defining
class indexer_lossesdirectly as annnx.Variableinstead of inheriting fromnnx.Intermediateperfectly solves the state-pruning issue during scanned layer scans. - Thorough Test Coverage: The addition of parameterized unit tests verifying standard dot-product, Flash Attention, and Ring Context Parallelism equivalence is excellent.
- Areas for Improvement: Suggested minor fixes to prevent test hardcoding (so parameterized test coverage of higher context parallelism isn't overridden) and a potential performance optimization to move mask boolean conversion out of scanned hot loops.
huytransformer
left a comment
There was a problem hiding this comment.
Can u please address these 2 then this LGTM
| current_kv_shard_idx = (ring_axis_idx - i) % ring_axis_size | ||
| local_dkv_mask_info = _dynamic_slice_mask_info(dkv_mask_info, current_kv_shard_idx, ring_axis_size) | ||
| local_dkv_mask_info = _offset_q_sequence_for_kv_shard(local_dkv_mask_info, current_kv_shard_idx, k_current.shape[-2]) | ||
| local_dkv_mask_info = _inject_local_indexer_mask( |
There was a problem hiding this comment.
Can u check if dq_reduction_step=3 give correct dQ when ring attention use indexer mask? If not perhaps reject this combination for now?
| scaling_factor=self.config.indexer_loss_scaling_factor, | ||
| ) | ||
| self.indexer_loss = nnx.Intermediate(indexer_loss) | ||
| self.indexer_loss = indexer_losses(indexer_loss) |
There was a problem hiding this comment.
can u add indexer_losses to Decoder.scan_decoder_layers() and test with pure_nnx=False and scan_layers=True
Description
This PR adds end-to-end support for DeepSeek Sparse Attention (DSA) Indexer with Tokamax Ring Context Parallelism during training. Previously,
use_indexer=Truewas prohibited with Ring Attention due to a lack of dynamic per-ring-step mask extraction. This PR also solve 2 bugs concerning load balancing with Indexer and indexer losses.Problems Solved & Key Design Choices:
indexer_maskinto per-step KV-shard blocks(ring_axis_idx - step) % ring_axis_sizeand tiles them into hardware blocks(block_q, block_kv).(q_blocks, kv_blocks), whereas the backward(kv_blocks, q_blocks). We apply.swapaxes(0, 1)and. swapaxes(-1, -2)whenis_dkv=True, aligning the mask blocks with the hardware execution schedule.Forward Flow: From Global Mask to TPU Register Tile
Backward Flow: From Saved Mask to Transposed Gradient Tiles
Isolated Flax NNX Auxiliary Loss:
_apply_layers_sequentiallyfilters scanned layer states viannx.filter_state(..., nnx.Not((nnx.RngState, nnx.Intermediate))). Subclassingnnx.Intermediatecausedjax.lax.scanto prune the loss variable during execution, resulting inindexer_loss: 0.000and broken backward gradients.class indexer_losses(nnx.Variable):which inherits directly fromnnx.Variablerather thannnx Intermediate. This allows the variable to pass throughjax.lax.scanunharmed without modifying core decoder scanning logic.Loss Harvesting & Objective Injection:
train.py'sloss_fn,indexer_lossesis popped before generic intermediates (mirroring the upstream MTP loss pattern).loss += indexer_loss), restoring full automatic differentiation VJP gradient flow to indexer Query/Key projection weights (wq_b,wkv_b).Fix Indexer Load Balancing Mask Bug in Splash Attention
indexer_mask wasnot being restored to chronological order before passing it to Splash Attention (unlike the Key and Value tensors, which were reordered).The newly added unit test
test_tpu_flash_attention_context_parallel_with_indexercomparing multi-chip Flash Attention load-balancing against a single-chip baseline fails without it.screenshot
Tests
Image
HLO
configs_value_test.pytest_tpu_tokamax_ring_config_validation_accepts_indexerto verify thatpyconfig.initializesuccessfully accepts the combination of MLA, the DSA Sparse Indexer, and Tokamax Ring Attention.test_tpu_tokamax_ring_config_validation_rejects_unsupported_configs.tokamax_ring_attention_test.pytest_call_ring_attention_threads_indexer_mask_without_segment_idsto test batch vectorization and mapping threads on the dynamicindexer_maskwithout segmentation IDs.test_call_ring_attention_threads_indexer_mask_with_segment_idsto test batch vectorization and mapping threads on the dynamicindexer_maskwhen utilizing segmentation IDs.attention_test.pytest_tpu_dot_product_context_parallel_with_indexer— Parameterized forward equivalence test verifying that standard dot-product MLA + Indexer calculates logits successfully and accurately across standard context parallelism permutations (testing combinations of matrix sizes andload_balance=True/False).test_tpu_flash_attention_context_parallel_with_indexer— Parameterized forward equivalence test verifying that standard Flash Attention MLA + Indexer matches reference outputs properly across standard context parallelism combinations.test_tpu_flash_attention_ring_context_parallel_with_indexer— Parameterized forward equivalence test verifying that MLA + Indexer under Tokamax Ring Attention identically matches the logits of single-device generic dot-product MLA + Indexer.test_tpu_flash_attention_ring_context_parallel_grad_with_indexer— Parameterized backward gradient equivalence test verifying that the backward input gradients and auxiliaryindexer_lossesmatch bit-for-bit with the reference gradients when utilizingis_dkv=Truetransposition.train_nnx_test.pyAdded:
test_indexer_losses_harvested_and_injected_into_lossto simulate multi-layer Transformer intermediate loss states and accurately validate the extraction and calculation of theexpected_indexer_lossscalar objective.Ran
deepseek32_vs_reference_test.py(we werify MaxText Dot Product Baseline matches Official PyTorch version, and our previous tests show that Ring Attention version matches Dot product baseline. Because Ring Attention = Dot Product Baseline and Dot Product Baseline = PyTorch Reference, this shows that distributed Ring Attention with Indexer matches the reference implementation):Checklist
Before submitting this PR, please make sure (put X in square brackets):
gemini-reviewlabel.