Repository navigation
feat(nn): add freqs and partial_rotary_factor support to RoPE - #4732
Open
shadyuwugurl wants to merge 1 commit into
Open
shadyuwugurl wants to merge 1 commit into
shadyuwugurl wants to merge 1 commit into
Conversation
mx.fast.rope already accepts a custom freqs array, but nn.RoPE did not expose it, so every rotary encoding that is not a plain power law had to be reimplemented in Python on top of mlx. Expose freqs directly and add partial_rotary_factor, which builds the wavelengths for a proportional (partial) rotary encoding using the same convention as transformers' _compute_proportional_rope_parameters. This removes the need for a Python reimplementation of scaled, partial and proportional rotary encodings (gemma-4, qwen3.5 and others).
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mx.fast.ropealready accepts a caller-suppliedfreqsarray, butmlx.nn.RoPEdid not expose it — it only forwardeddims,traditional,baseandscale. As a result, every rotary encoding that is not a plain power law had to be reimplemented in Python on top of MLX.This PR exposes
freqsonnn.RoPEand adds apartial_rotary_factorconvenience that builds the wavelengths for a proportional (partial) rotary encoding.Why this matters
mlx-lmcurrently carries six Python reimplementations of rotary encodings inmlx_lm/models/rope_utils.py—SuScaledRoPE,Llama3RoPE,YarnRoPE,ProportionalRoPE,DynamicNTKScalingRoPE— purely becausenn.RoPEcould not express them. All of them are thin wrappers that build afreqsarray and callmx.fast.rope.Current models that need this:
google/gemma-4-12B(gemma4_text)partial_rotary_factor: 0.25,rope_type: "proportional",global_head_dim: 512vshead_dim: 256Qwen3.5(qwen3_5)partial_rotary_factor: 0.25,mrope_section,linear_key_head_dim: 192Semantics
partial_rotary_factorfollowstransformers'_compute_proportional_rope_parametersexactly:The non-rotated features get an infinite wavelength, so their inverse frequency is zero and
mx.fast.ropeleaves them unchanged. This matches the HF reference, whereinv_freqis zero-padded and the cosine/sine pair is duplicated.The frequency-scaling factor that HF applies via
rope_scaling["factor"]is expressed by thefreqspath (freqs *= factor), keeping the two arguments independent.Behaviour
nn.RoPE(dims)produces exactly the same result as before.freqstakes precedence overbase(mirroringmx.fast.rope, which requires exactly one of the two).freqsis stored withobject.__setattr__so it is not registered as a module parameter;nn.RoPE(8, freqs=...).parameters()is empty, as it should be for a constant.Validation
New tests in
python/tests/test_nn.py(TestRoPEVariants) cover:mx.fast.ropefreqsmatchmx.fast.ropepartial_rotary_factorand explicitfreqsagreefreqsare not exposed as parametersValueErrorLocal run on an M5 (Apple Silicon),
mlx0.32.4:Note on
MLX_ENABLE_TF32While validating numerical parity of the rotary implementation I found that float32 matmuls on the Metal GPU path use TF32 by default on M5 (
mx.utils.h,enable_tf32(), default1), giving ~7e-4 relative error versus ~1e-6 for the same op on MLX's CPU stream. WithMLX_ENABLE_TF32=0the error drops to ~5e-7. This is orthogonal to this PR and is already discussed in #4595 — I mention it only because it changes the expected tolerance of float32 comparisons on Apple Silicon.🤖 Generated with Claude Code