Honor partial_rotary_factor on the MRoPE path - #4742
Conversation
The source fix this branch carried is superseded by AI-Hypercomputer#4764, which landed the same behaviour on main with an instance-level override and validation that this branch did not have. What remains is the MRoPE-specific coverage, which main does not have: its test file covers PartialRotaryEmbedding and the Gemma4 layout, but not the Qwen3-Omni MRoPE path that AI-Hypercomputer#4616 reported.
78daf37 to
f3f9186
Compare
|
The source fix here is superseded by #4764, which merged earlier today. I have rebased and dropped it, so this is now tests only. Your version is the better one. It adds an instance-level override in partial_rotary_factor=(
self.partial_rotary_factor if self.partial_rotary_factor is not None
else self.config.partial_rotary_factor
),plus validation of the factor and the rotary dim in What is left is the MRoPE regression coverage, which
Worth saying plainly: I could not run these locally. They were written against my implementation and the repo needs Close this if the coverage is not worth carrying. The bug itself is fixed either way, and #4616 is still open if you want to close that too. |
Description
partial_rotary_factorwas silently dropped on the MRoPE path, so the two shipped configs that set bothuse_mrope: trueandpartial_rotary_factor: 0.25applied RoPE to the full head dimension instead of the leading quarter. Reported in #4616.The defect:
Attention.init_rotary_embeddinginsrc/maxtext/layers/attentions.pyroutes everyuse_mropelayer toQwen3OmniMoeThinkerTextRotaryEmbeddingthrough theelif self.use_mrope:branch. That branch passedembedding_dims=rope_embedding_dims, which is the fullhead_dim, and never passed a partial rotary factor because the class did not accept one.PartialRotaryEmbeddingalready implements the intended behavior, but only the non-MRoPE branches can reach it, and theis_qwen3_hybridbranch that does construct it sits below the MRoPE branch in the same chain. There was no error and no shape mismatch, so the misconfiguration was invisible at runtime.Affected shipped configs, both
head_dim: 256:src/maxtext/configs/models/qwen3.5-35b-a3b.ymlsrc/maxtext/configs/models/qwen3.5-397b-a17b.ymlBoth set
partial_rotary_factor: 0.25, so 64 of the 256 head channels should be rotated. Theirmrope_sectionof[11, 11, 10]sums to 32, which is half of that intended 64 channel slice rather than half ofhead_dim, so the frequency layout was wrong too: the interleaved MRoPE sections only reached the first 33 of the 128 angles that the full head dimension produces.The fix teaches
Qwen3OmniMoeThinkerTextRotaryEmbeddingthe constructionPartialRotaryEmbeddingalready uses. It takes apartial_rotary_factor, computesrotary_dim = int(head_dim * partial_rotary_factor), and initializes the base class withembedding_dims=self.rotary_dimso the timescale, and therefore the inverse frequencies, span only the rotated slice. In__call__the leadingrotary_dimchannels are split off, rotated, and the untouched remainder is concatenated back. The shape check now compares againsthead_diminstead of the reducedembedding_dims.init_rotary_embeddingpassesconfig.partial_rotary_factorthrough, matching what the neighboring qwen3 hybrid branch already does.The factor defaults to 1.0. In that case no split happens and the output is unchanged, so
qwen3-vl-*andqwen3-omni-*, which do not set the factor, are unaffected. I verified this directly: dumping the default layer output athead_dim=128withmrope_section=(24, 20, 20), for both 3D multimodal positions and 2D text positions, gives bitwise identical arrays before and after the change.Tests
Five new cases in
tests/unit/partial_rotary_embedding_test.py, in aQwen3OmniMoeThinkerTextRotaryEmbeddingPartialTestclass alongside the existing partial rotary tests:rotary_dimpass through untouched and the leading slice is rotated,PartialRotaryEmbeddingchannel for channel,pyconfig, theirmrope_sectionis checked againstrotary_dim // 2, and the passthrough property is asserted at their realhead_dimof 256.Command:
Fail before, with
src/maxtext/layers/embeddings.pyandsrc/maxtext/layers/attentions.pychecked out from the base ref and only the test file carrying the change:Pass after, with both source files restored:
The pre-existing cases in that file, including the
PartialRotaryEmbeddingandGemma4PartialRotaryEmbeddingsuites, pass in both runs.tests/unit/embeddings_test.pyis also green. Ran on CPU. Formatted with the pinned pyink 24.10.1 at--pyink-indentation=2 --line-length=122, and pylint 3.3.8 rates the changed files 10.00/10.FIXES: #4616