Read rope_theta from rope_parameters in the Llama injection policy - #8341
Conversation
Kernel injection resolves rope_theta as
if hasattr(self.policy.client_module.self_attn, 'config'):
_config.rope_theta = ...self_attn.config.rope_theta
else:
_config.rope_theta = ...self_attn.rope_theta
transformers 5.0 folded the rotary settings into config.rope_parameters
and dropped the attribute, so against a stock LlamaConfig both branches
raise. The first is taken -- LlamaAttention still has .config -- and
injection dies with
AttributeError: 'LlamaConfig' object has no attribute 'rope_theta'
On transformers 5.8.0 that reproduces with a default LlamaConfig, so it
is not specific to the DeepSeek checkpoint in deepspeedai#8340.
Fall back to rope_parameters['rope_theta'] when the attribute is gone,
keeping the old spellings first so pre-5.0 installs are unaffected. This
is the same drift deepspeedai#7443 adapted to; the num_heads accessor it fixed
alongside still resolves, so only this one moved.
Fixes deepspeedai#8340
Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
|
Opened #8345 for the It turned out to be eight models, and one of them fails quietly rather than loudly: |
| assert _get_rope_theta(self_attn) == 500000.0 | ||
|
|
||
|
|
||
| def test_reads_rope_parameters_when_the_attribute_is_gone(): |
There was a problem hiding this comment.
I ran the helper against a real LlamaConfig on both majors, in clean python:3.12-slim containers, at head ecda5f94. 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:
transformers 4.56.2 hasattr(config,'rope_theta')=True rope_parameters=None
LlamaConfig(rope_theta=5e5) -> 500000.0
transformers 5.8.0 hasattr(config,'rope_theta')=False rope_parameters={'rope_theta': 10000.0, 'rope_type': 'default'}
LlamaConfig(rope_theta=5e5) -> 500000.0
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, that rope_theta is absent from a stock LlamaConfig on 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_parameters on 5.x:
def test_reads_a_real_llama_config():
from transformers.models.llama.configuration_llama import LlamaConfig
assert _get_rope_theta(SimpleNamespace(config=LlamaConfig(rope_theta=500000.0))) == 500000.0I checked it passes on both versions above before suggesting it. I lifted _get_rope_theta into 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.
Right, and the gap is worse than "nothing pins it" — every one of those six passes against the implementation this PR replaces. I checked:
old helper, real LlamaConfig(rope_theta=5e5), transformers 5.16.1
-> AttributeError
old helper, all six SimpleNamespace cases
-> pass
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_theta as 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:
parameters = getattr(config, "rope_parameters", None) or {}
carried = [v for v in (getattr(config, "rope_theta", None), parameters.get("rope_theta")) if v is not None]
assert carried, "neither spelling carries rope_theta on the installed transformers"
assert all(v == 500000.0 for v in carried), f"the spellings disagree: {carried}"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:
hasattr(config, 'rope_theta') False
rope_parameters {'rope_theta': 500000.0, 'rope_type': 'default'}
_get_rope_theta 500000.0
LlamaConfig() stock default 10000.0
8 passing, yapf and flake8 clean. 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.
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_theta parsed out of llama.py at 3dd2716 so this needed no torch install:
transformers 4.56.2 hasattr rope_theta True rope_parameters None
transformers 5.8.0 hasattr rope_theta False rope_parameters {'rope_theta': 500000.0, 'rope_type': 'default'}
transformers 5.16.1 hasattr rope_theta False rope_parameters {'rope_theta': 500000.0, 'rope_type': 'default'}
2 passed on each
There was a problem hiding this comment.
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.
The six existing cases build the config out of SimpleNamespace, so nothing in
the file pinned the claim the change rests on: which spelling a stock
LlamaConfig actually carries. Every one of them passes against the previous
implementation.
Two added. The first resolves a non-default theta through the real class, so it
stays meaningful on either side of the 5.0 boundary -- legacy attribute on 4.x,
rope_parameters on 5.x -- and fails against the previous implementation, which
raises AttributeError there on transformers 5.16.1:
hasattr(LlamaConfig(rope_theta=5e5), 'rope_theta') -> False
rope_parameters -> {'rope_theta': 500000.0, ...}
The second guards the branch rather than the value. If transformers reinstates
rope_theta as a deprecated property the first test still passes while the
injection path silently changes branch, which only matters if the two spellings
can disagree, so it asserts they do not.
Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
There was a problem hiding this comment.
Hi @alanhuangyoo,
Thank you for submitting this PR! This is an important fix.
I found a correctness blocker for the DeepSeek-R1-Distill-Llama-8B configuration reported in #8340. It uses rope_type="llama3" together with factor, low_freq_factor, high_freq_factor, and original_max_position_embeddings.
This patch passes only rope_theta to the legacy DeepSpeed inference configuration, so the additional scaling parameters do not reach the downstream kernel. The kernel currently implements only standard scalar-theta RoPE. Supporting this configuration would require propagating the additional parameters and adding an implementation and dispatch path for Llama-3 scaled RoPE.
@tohtana is right that this patch does not make DeepSeek-R1-Distill-Llama-8B work. It uses rope_type="llama3" with factor, low_freq_factor, high_freq_factor and original_max_position_embeddings, and only rope_theta reaches the kernel. The injected path has no place to put them. Its rotary embedding is built from a scalar base and nothing else: InferenceContext.get_rotary(rotary_dim, rope_theta) and DeepSpeedInferenceConfig carries rope_theta with no scaling fields at all. So after the crash fix that model would have started and run with unscaled positions, producing wrong output with no error. That is worse than the AttributeError this change exists to remove: the crash is at least visible. A config asking for a variant the kernel cannot implement is now refused, naming the type and pointing at running without kernel injection. Both spellings are covered, rope_parameters on 5.x and rope_scaling on 4.x. `default` and an absent type are unscaled and still resolve, so the configuration deepspeedai#8340 reported is fixed as before. Implementing llama3 scaled RoPE would mean propagating the parameters through DeepSpeedInferenceConfig and adding an inv_freq path and dispatch in the kernel. That is a feature rather than a crash fix and belongs in its own change. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
16c9b4c to
8447987
Compare
|
You are right, and the consequence is worse than the parameters not arriving. I checked what the injected path could do with them and the answer is nothing. Its rotary embedding is built from a scalar base: InferenceContext.get_rotary(rotary_dim, rope_theta)and So with only the crash fix, 16c9b4c refuses instead: Both spellings are covered — 16 passing, including a parametrized refusal over On the larger fix: propagating the parameters through |
|
Separate from the review: the red Three of my PRs hit the same 1h30m cancellation today (#8341, #8356, #8438) while six went green, so it looks like the full suite sometimes runs past the job limit rather than anything about these trees. Mentioning it because a red X plus a changes-requested reads worse than the situation is. |
…on-rope-parameters
|
@tohtana — re-review request rather than a new argument. Your changes-requested is still the only thing on this one and I think it is addressed, but I would rather you confirm than assume. You said the patch passes only Agreed, and I went and checked what the injected path could do with them: nothing. Its rotary embedding is built from a scalar So 8447987 refuses instead of silently serving the wrong rotary: If you would rather this PR implement llama3 scaling in the kernel rather than refuse it, say so and I will close this and open that instead; I did not want to bundle them. The red CI here was the 90-minute job timeout, not a failure — no |
tohtana
left a comment
There was a problem hiding this comment.
Thank you for the update, @alanhuangyoo! Looks good to me.
The formatting job reformatted the SimpleNamespace call in test_a_scaled_rope_variant_is_refused. No behaviour change. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
Head branch was pushed to by a user without write access
|
@tohtana thanks — the formatting job was the last red and it is fixed in Ready for the queue whenever suits. I did not touch the branch otherwise — the master merge on it is yours. |
Fixes #8340.
What breaks
DS_LLAMAContainer.create_moduleresolvesrope_thetaas:transformers 5.0 folded the rotary settings into
config.rope_parametersand dropped the attribute.LlamaAttentionstill has.config, so the first branch is taken and it raises.This is not specific to the DeepSeek checkpoint in the issue — on transformers >= 5.0 it reproduces with a default
LlamaConfig. Checked on 5.8.0:and against a real module, both branches are dead:
The fix
Try the old spellings first, then
rope_parameters['rope_theta'], so pre-5.0 installs take exactly the path they take today and nothing changes for them.This is the same drift #7443 adapted to. The
num_headsaccessor it fixed alongside still resolves on 5.8 (config.num_attention_headsis intact), sorope_thetais the only one that moved again.requirements-dev.txtasks fortransformers>=4.51.3with no upper bound, so 5.x is in range.Verified that
rope_thetais the only accessor that moved: against a realLlamaDecoderLayeron 5.8.0,get_hidden_heads(),attention(),mlp()andlayernorm()all resolve once this one is fixed.I exercised the policy and container layer, not a full
init_inference()run against a downloaded checkpoint on GPU.Test
tests/unit/module_inject/test_llama_rope_theta.pycovers the three layouts plus the precedence between them and the not-found case — CPU only, no model download:End to end against a real
LlamaAttentionon 5.8.0:I did not touch
inference/v2. Six of its model implementations readself._config.rope_thetadirectly andexaone4is the only one using agetattrdefault, so they likely have the same exposure — but that is a different engine and a different change, and I have not reproduced it.