Add flag-controlled sliced MLA projections - #4630
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
5a039e3 to
2a881d3
Compare
|
🤖 Hi @NuojCheng, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @NuojCheng, but I was unable to process your request. Please see the logs for more details. |
Port performance optimization from CL 947805696 into MaxText under a new configuration flag use_sliced_mla_projections (defaulting to False). - Add use_sliced_mla_projections flag to base.yml and types.py. - Support slice_bounds in DenseGeneral.__call__ to slice projection weights/bias prior to contraction for unquantized paths. - Update mla_query_projection and mla_get_key_value in attention_mla.py to use sliced projections when enabled. - Add unit test coverage in linears_test.py. TAG=agy CHANGE_STEWARD=true
Verify correctness of sliced MLA projections by comparing outputs against the default unsliced implementation in train, prefill, and autoregressive modes. CHANGE_STEWARD=true TAG=agy CONV=86839aa9-de96-414b-b896-1ac9aeb38589
Add documentation for the new use_sliced_mla_projections performance optimization flag in the DeepSeek runner guide. TAG=agy CONV=a262e292-a0d4-49f9-ad26-f42e697a5979 CHANGE_STEWARD=true
Remove tpu_only decorator from test_sliced_mla_projections to run it on CPU. Add test_slice_bounds_with_quantization to cover assertion in DenseGeneral. Format with pyink and fix pylint warnings. TAG=agy CONV=e336d40c-d648-45cb-9abd-7b79a924be5e CHANGE_STEWARD=true
2a881d3 to
fc728c5
Compare
|
🤖 Hi @NuojCheng, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @NuojCheng, but I was unable to process your request. Please see the logs for more details. |
| kernel = jnp.asarray(kernel, self.dtype) | ||
|
|
||
| if slice_bounds is not None: | ||
| assert self.quant is None, "sliced contraction is only supported when quant is None" |
There was a problem hiding this comment.
we discourage using assertion error in general, since they can be skipped sometimes. We could use if ... raise ValueError() instead
| model_mode=MODEL_MODE_TRAIN, | ||
| ) | ||
|
|
||
| self.assertTrue(jnp.allclose(out_normal_train, out_sliced_train, rtol=1e-05, atol=1e-05, equal_nan=False)) |
There was a problem hiding this comment.
for training we want to compare gradients to protect backward pass correctness
|
🤖 Hi @RissyRan, 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.
The Pull Request introduces an elegant performance optimization for Multi-Head Latent Attention (MLA) projection operations. By slicing weight kernels and biases along the output feature axis prior to contraction, it successfully avoids unnecessary jnp.split operations on sharded tensors, which reduces memory copies and layout transitions. The implementation is highly robust, and the author has written comprehensive unit tests that cover all model execution modes (train, prefill, autoregressive decode) along with rigorous gradient checks.
🔍 General Feedback
- Performance-Oriented Design: Slicing the parameters before contraction is a great optimization that reduces network transfer and gather overhead on TPUs/GPUs.
- Exemplary Test Coverage: The detailed test assertions for forward outputs, input gradients, parameter gradients, and KV cache states are excellent and ensure zero regressions.
- Clear Documentation: The added constraint documentation in
Run_DeepSeek.mdis precise, well-written, and easy to understand.
| if self.q_lora_rank == 0: | ||
| q = self.query(inputs_q, out_sharding=query_sharding) | ||
| q_nope, q_pe = jnp.split(q, [self.qk_nope_head_dim], axis=-1) |
There was a problem hiding this comment.
| if self.q_lora_rank == 0: | |
| q = self.query(inputs_q, out_sharding=query_sharding) | |
| q_nope, q_pe = jnp.split(q, [self.qk_nope_head_dim], axis=-1) | |
| if self.q_lora_rank == 0: | |
| if self.config.use_sliced_mla_projections and self.query.quant is None: | |
| q_nope = self.query( | |
| inputs_q, | |
| out_sharding=query_sharding, | |
| slice_bounds=(0, self.qk_nope_head_dim), | |
| ) # [B, L, n_heads, qk_nope_head_dim] | |
| q_pe = self.query( | |
| inputs_q, | |
| out_sharding=query_sharding, | |
| slice_bounds=(self.qk_nope_head_dim, self.qk_head_dim), | |
| ) # [B, L, n_heads, qk_rope_head_dim] | |
| else: | |
| q = self.query(inputs_q, out_sharding=query_sharding) | |
| q_nope, q_pe = jnp.split(q, [self.qk_nope_head_dim], axis=-1) |
| wkva_out_sharding = create_sharding(self.mesh, key_logical_name) | ||
| kv_out = self.wkv_b(low_rank_main, out_sharding=wkva_out_sharding) | ||
|
|
||
| # Split kv_out into key_nope and value parts. | ||
| key_nope, value = jnp.split(kv_out, [self.qk_nope_head_dim], axis=-1) | ||
| if self.config.use_sliced_mla_projections and self.wkv_b.quant is None: | ||
| key_nope = self.wkv_b( | ||
| low_rank_main, | ||
| out_sharding=wkva_out_sharding, | ||
| slice_bounds=(0, self.qk_nope_head_dim), | ||
| ) # [B, L, n_heads, qk_nope_head_dim] | ||
| value = self.wkv_b( | ||
| low_rank_main, | ||
| out_sharding=wkva_out_sharding, | ||
| slice_bounds=( | ||
| self.qk_nope_head_dim, | ||
| self.qk_nope_head_dim + self.v_head_dim, | ||
| ), | ||
| ) # [B, L, n_heads, v_head_dim] |
There was a problem hiding this comment.
| wkva_out_sharding = create_sharding(self.mesh, key_logical_name) | |
| kv_out = self.wkv_b(low_rank_main, out_sharding=wkva_out_sharding) | |
| # Split kv_out into key_nope and value parts. | |
| key_nope, value = jnp.split(kv_out, [self.qk_nope_head_dim], axis=-1) | |
| if self.config.use_sliced_mla_projections and self.wkv_b.quant is None: | |
| key_nope = self.wkv_b( | |
| low_rank_main, | |
| out_sharding=wkva_out_sharding, | |
| slice_bounds=(0, self.qk_nope_head_dim), | |
| ) # [B, L, n_heads, qk_nope_head_dim] | |
| value = self.wkv_b( | |
| low_rank_main, | |
| out_sharding=wkva_out_sharding, | |
| slice_bounds=( | |
| self.qk_nope_head_dim, | |
| self.qk_nope_head_dim + self.v_head_dim, | |
| ), | |
| ) # [B, L, n_heads, v_head_dim] | |
| wkva_out_sharding = create_sharding(self.mesh, key_logical_name) | |
| wkvb_out_sharding = create_sharding(self.mesh, value_logical_name) | |
| if self.config.use_sliced_mla_projections and self.wkv_b.quant is None: | |
| key_nope = self.wkv_b( | |
| low_rank_main, | |
| out_sharding=wkva_out_sharding, | |
| slice_bounds=(0, self.qk_nope_head_dim), | |
| ) # [B, L, n_heads, qk_nope_head_dim] | |
| value = self.wkv_b( | |
| low_rank_main, | |
| out_sharding=wkvb_out_sharding, | |
| slice_bounds=( | |
| self.qk_nope_head_dim, | |
| self.qk_nope_head_dim + self.v_head_dim, | |
| ), | |
| ) # [B, L, n_heads, v_head_dim] |
| if slice_bounds is not None: | ||
| if self.quant is not None: | ||
| raise ValueError("sliced contraction is only supported when quant is None") | ||
| begin, end = slice_bounds | ||
| kernel = kernel[..., begin:end] |
There was a problem hiding this comment.
| if slice_bounds is not None: | |
| if self.quant is not None: | |
| raise ValueError("sliced contraction is only supported when quant is None") | |
| begin, end = slice_bounds | |
| kernel = kernel[..., begin:end] | |
| if slice_bounds is not None: | |
| if self.quant is not None: | |
| raise ValueError("sliced contraction is only supported when quant is None") | |
| begin, end = slice_bounds | |
| if not (0 <= begin < end <= kernel.shape[-1]): | |
| raise ValueError( | |
| f"slice_bounds {slice_bounds} must be valid and within [0, {kernel.shape[-1]}]" | |
| ) | |
| kernel = kernel[..., begin:end] |
RissyRan
left a comment
There was a problem hiding this comment.
Thanks! LGTM, please address comments before merge.
| low_rank_q = self.q_norm(low_rank_q) # RMSNorm on low rank | ||
| low_rank_q = checkpoint_name(low_rank_q, "mla_q") | ||
| q = self.wq_b(low_rank_q, out_sharding=query_sharding) # [B, L, n_heads, qk_head_dim] | ||
| if self.config.use_sliced_mla_projections and self.wq_b.quant is None: |
There was a problem hiding this comment.
Shall we add assertion in types.py file to indicate that use_sliced_mla_projections doesn't work with quantization config for early check?
|
|
||
| To optimize Multi-Head Latent Attention (MLA) performance, you can enable sliced projections. | ||
|
|
||
| * **Flag**: `use_sliced_mla_projections` (default: `False`) |
There was a problem hiding this comment.
I see we put proj as short name in base.yml. Maybe we could make it as use_sliced_mla_proj?
Performance optimizations that avoids an unnecessary split operation.
Tests
Checklist
Before submitting this PR, please make sure (put X in square brackets):
gemini-reviewlabel.