-
Notifications
You must be signed in to change notification settings - Fork 5k
Read rope_theta from rope_parameters in the Llama injection policy #8341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
tohtana
merged 6 commits into
deepspeedai:master
from
alanhuangyoo:fix/llama-injection-rope-parameters
Sep 8, 2026
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
ecda5f9
Read rope_theta from rope_parameters in the Llama injection policy
alanhuangyoo 3dd2716
Pin the Llama rope_theta helper against a real LlamaConfig
alanhuangyoo 8447987
Refuse rotary variants kernel injection cannot serve
alanhuangyoo ee60682
Merge remote-tracking branch 'upstream/master' into fix/llama-injecti…
alanhuangyoo e8ba241
Merge branch 'master' into fix/llama-injection-rope-parameters
tohtana 4c687f1
Apply yapf formatting to the rope_theta test
alanhuangyoo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # DeepSpeed Team | ||
| """Llama kernel injection must find rope_theta wherever the installed transformers keeps it. | ||
|
|
||
| The value has moved twice. Older releases exposed ``config.rope_theta``; transformers 5.0 | ||
| folded the rotary settings into ``config.rope_parameters`` and dropped the attribute, so an | ||
| injection policy that only knows the old spelling raises AttributeError against a stock | ||
| LlamaConfig. Older still, it lived on the attention module. | ||
| """ | ||
|
|
||
| from types import SimpleNamespace | ||
|
|
||
| import pytest | ||
|
|
||
| from deepspeed.module_inject.containers.llama import _get_rope_theta | ||
|
|
||
|
|
||
| def test_reads_the_legacy_config_attribute(): | ||
| self_attn = SimpleNamespace(config=SimpleNamespace(rope_theta=500000.0)) | ||
|
|
||
| assert _get_rope_theta(self_attn) == 500000.0 | ||
|
|
||
|
|
||
| def test_reads_rope_parameters_when_the_attribute_is_gone(): | ||
| # transformers >= 5.0: the attribute is absent and the value sits in the dict. | ||
| config = SimpleNamespace(rope_parameters={"rope_theta": 10000.0, "rope_type": "default"}) | ||
| self_attn = SimpleNamespace(config=config) | ||
|
|
||
| assert _get_rope_theta(self_attn) == 10000.0 | ||
|
|
||
|
|
||
| def test_prefers_the_attribute_when_both_are_present(): | ||
| config = SimpleNamespace(rope_theta=500000.0, rope_parameters={"rope_theta": 10000.0}) | ||
| self_attn = SimpleNamespace(config=config) | ||
|
|
||
| assert _get_rope_theta(self_attn) == 500000.0 | ||
|
|
||
|
|
||
| def test_falls_back_to_the_module_attribute(): | ||
| # No config at all, the layout the policy handled before configs were attached. | ||
| self_attn = SimpleNamespace(rope_theta=1000000.0) | ||
|
|
||
| assert _get_rope_theta(self_attn) == 1000000.0 | ||
|
|
||
|
|
||
| def test_falls_back_when_rope_parameters_carries_no_theta(): | ||
| config = SimpleNamespace(rope_parameters={"rope_type": "default"}) | ||
| self_attn = SimpleNamespace(config=config, rope_theta=250000.0) | ||
|
|
||
| assert _get_rope_theta(self_attn) == 250000.0 | ||
|
|
||
|
|
||
| def test_resolves_against_a_real_llama_config(): | ||
| """The six cases above build the config by hand, so none of them pins the claim this | ||
| change rests on: which spelling a stock `LlamaConfig` actually carries. | ||
|
|
||
| This one stays meaningful on either side of the 5.0 boundary — it takes the legacy | ||
| attribute on 4.x and `rope_parameters` on 5.x — and it uses a non-default theta, so a | ||
| helper that returned the class default would fail it. | ||
| """ | ||
| LlamaConfig = pytest.importorskip("transformers.models.llama.configuration_llama").LlamaConfig | ||
|
|
||
| self_attn = SimpleNamespace(config=LlamaConfig(rope_theta=500000.0)) | ||
|
|
||
| assert _get_rope_theta(self_attn) == 500000.0 | ||
|
|
||
|
|
||
| def test_the_two_spellings_do_not_disagree_on_a_real_config(): | ||
| """Guards the branch rather than the value. | ||
|
|
||
| If transformers reinstates `rope_theta` as a deprecated property, the test above still | ||
| passes while the injection path silently changes which branch it takes. That is only a | ||
| problem if the two spellings can disagree, so this asserts they cannot. | ||
| """ | ||
| LlamaConfig = pytest.importorskip("transformers.models.llama.configuration_llama").LlamaConfig | ||
| config = LlamaConfig(rope_theta=500000.0) | ||
|
|
||
| parameters = getattr(config, "rope_parameters", None) or {} | ||
| carried = [ | ||
| value for value in (getattr(config, "rope_theta", None), parameters.get("rope_theta")) if value is not None | ||
| ] | ||
|
|
||
| assert carried, "neither spelling carries rope_theta on the installed transformers" | ||
| assert all(value == 500000.0 for value in carried), f"the spellings disagree: {carried}" | ||
|
|
||
|
|
||
| def test_raises_when_nothing_carries_it(): | ||
| with pytest.raises(AttributeError): | ||
| _get_rope_theta(SimpleNamespace(config=SimpleNamespace())) | ||
|
|
||
|
|
||
| # --- scaled rotary variants ---------------------------------------------------- | ||
| # | ||
| # The injected kernel builds its rotary embedding from a scalar base | ||
| # (`InferenceContext.get_rotary(rotary_dim, rope_theta)`) and carries no scaling | ||
| # parameters at all, so a config asking for one cannot be served here. | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("rope_type", ["llama3", "linear", "dynamic", "yarn", "longrope"]) | ||
| def test_a_scaled_rope_variant_is_refused(rope_type): | ||
| """Reading only rope_theta out of a scaled config is silently wrong. | ||
|
|
||
| DeepSeek-R1-Distill-Llama-8B (#8340) is the live case: `rope_type="llama3"` with | ||
| `factor`, `low_freq_factor`, `high_freq_factor` and `original_max_position_embeddings`. | ||
| Dropping those and keeping the base runs the model with unscaled positions and no error, | ||
| which is worse than the AttributeError this helper exists to remove. | ||
| """ | ||
| config = SimpleNamespace( | ||
| rope_parameters={ | ||
| "rope_type": rope_type, | ||
| "rope_theta": 500000.0, | ||
| "factor": 8.0, | ||
| "low_freq_factor": 1.0, | ||
| "high_freq_factor": 4.0, | ||
| "original_max_position_embeddings": 8192, | ||
| }) | ||
|
|
||
| with pytest.raises(ValueError, match="cannot serve rope_type"): | ||
| _get_rope_theta(SimpleNamespace(config=config)) | ||
|
|
||
|
|
||
| def test_a_scaled_variant_in_the_legacy_rope_scaling_spelling_is_refused(): | ||
| """transformers < 5.0 carries the same request under `rope_scaling`.""" | ||
| config = SimpleNamespace(rope_theta=500000.0, rope_scaling={"rope_type": "llama3", "factor": 8.0}) | ||
|
|
||
| with pytest.raises(ValueError, match="cannot serve rope_type"): | ||
| _get_rope_theta(SimpleNamespace(config=config)) | ||
|
|
||
|
|
||
| def test_the_default_rope_type_is_not_refused(): | ||
| """`rope_type: "default"` is what standardize_rope_params writes for plain RoPE.""" | ||
| config = SimpleNamespace(rope_parameters={"rope_theta": 500000.0, "rope_type": "default"}) | ||
|
|
||
| assert _get_rope_theta(SimpleNamespace(config=config)) == 500000.0 | ||
|
|
||
|
|
||
| def test_a_real_llama_config_is_not_refused(): | ||
| """The stock config the crash fix targets carries no scaling and must still resolve.""" | ||
| LlamaConfig = pytest.importorskip("transformers.models.llama.configuration_llama").LlamaConfig | ||
|
|
||
| assert _get_rope_theta(SimpleNamespace(config=LlamaConfig(rope_theta=500000.0))) == 500000.0 | ||
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran the helper against a real
LlamaConfigon both majors, in cleanpython:3.12-slimcontainers, at headecda5f94. It resolves correctly on each, and on 5.8.0 a non-default theta survives the move, which is the part that matters for an actual checkpoint rather than the stock default:The suggestion is about the tests rather than the fix. All six build the config out of
SimpleNamespace, so the claim the PR rests on, thatrope_thetais absent from a stockLlamaConfigon 5.x and present on 4.x, is the one thing nothing pins. If transformers reinstates the attribute as a deprecated property, every test here still passes and the injection path silently changes branch.One test with the real class covers it, and it stays meaningful on either side of the boundary, taking the legacy branch on 4.x and
rope_parameterson 5.x:I checked it passes on both versions above before suggesting it. I lifted
_get_rope_thetainto a standalone module to run this without a torch install, so I exercised the helper and the config, not the container.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, and the gap is worse than "nothing pins it" — every one of those six passes against the implementation this PR replaces. I checked:
So the file had no test that could fail before the fix. Added in 3dd2716, along with a second one for the part your suggestion does not cover.
Your test pins the value. It does not catch the branch flip you describe: if transformers reinstates
rope_thetaas a deprecated property, it still passes and the injection path silently takes the legacy branch. That only matters if the two spellings can disagree, so the second test asserts they cannot:It holds on both sides — one entry on 4.x, one on 5.x, two agreeing entries in the reinstated case, and a failure naming the values if they ever diverge.
Measured here on 5.16.1, matching your 5.8.0 run:
8 passing,
yapfandflake8clean. This is the same thing you found on #8345 and it was the right thing to find twice — I had fixed the synthetic-config problem there and left it standing here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed on the second test. Mine pins the value and would sit green through exactly the reinstatement I described, so it does not cover the case I raised it for.
Ran both real-config tests standalone, with
_get_rope_thetaparsed out ofllama.pyat3dd2716so this needed no torch install:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for running it across three majors — 4.56.2 is the half I could not measure here, so that closes the gap the tests are meant to cover. Nothing further from me on this one.