Repository navigation
Deprecate rope_scaling assignment and normalize legacy RoPE keys - #49515
Conversation
|
@zucchini-nlp @Cyrilvallez Could you take a look at this |
| current = getattr(self, "rope_parameters", None) or {} | ||
| current_layer_keys = self.nested_rope_parameter_keys(current) | ||
| value = copy.deepcopy(value) | ||
| layer_keys = self.nested_rope_parameter_keys(value or {}) | ||
| for parameters in [value[key] for key in layer_keys] or [value]: | ||
| if parameters is not None and "type" in parameters: | ||
| parameters.setdefault("rope_type", parameters.pop("type")) | ||
|
|
||
| base_keys = ("rope_theta", "partial_rotary_factor") | ||
| if current_layer_keys and not layer_keys: | ||
| self.rope_parameters = { | ||
| key: {name: parameters[name] for name in base_keys if name in parameters} | ||
| if (parameters := current[key]) is not None | ||
| else None | ||
| for key in current_layer_keys | ||
| } | ||
| self.convert_rope_params_to_dict(rope_scaling=value) | ||
| else: | ||
| parameters = value or {} | ||
| for key in layer_keys or [None]: | ||
| previous = current.get(key) or {} if key is not None else current | ||
| updated = parameters[key] if key is not None else parameters | ||
| if updated is not None: | ||
| for name in base_keys: | ||
| if name in previous: | ||
| updated.setdefault(name, previous[name]) | ||
| self.rope_parameters = parameters | ||
| self.convert_rope_params_to_dict() |
There was a problem hiding this comment.
wow! Not really sure why users are passing rope_scaling as override when sglang has pinned v5, since the "breaking" as intentional for major release in some way
I don't think it's disallowed on sglangs to override with rope_parameters no?
There was a problem hiding this comment.
Thanks for taking a look! This isn't really about sglang: the reproduction is plain transformers. On LlamaConfig(rope_theta=500000.0), assigning config.rope_scaling = {"type": "yarn", "factor": 4.0, ...} leaves rope_parameters without rope_theta (and with type instead of rope_type), so LlamaRotaryEmbedding(config) raises KeyError: 'rope_type', and a save_pretrained / from_pretrained round trip comes back with the default theta without any error.
v5 still exposes rope_scaling as a property on PreTrainedConfig, and the __init__ kwargs path (convert_rope_params_to_dict) still accepts rope_scaling and keeps the base parameters; only the setter replaces the dict wholesale, so the two entry points disagree. If the alias is meant to be gone, raising or warning on assignment would be clearer than accepting it and corrupting the config.
As for why people still write rope_scaling: it is what the recipes say. sglang's cookbook (Qwen3-Next 1M context, Ling YaRN) launches with --json-model-override-args '{"rope_scaling": ...}', vLLM has the same report (vllm-project/vllm#58675), and both engines are now adding their own merge workaround (sgl-project/sglang#43067). Fixing the alias here removes the need for that.
If you'd rather steer users to rope_parameters, I'm happy to add a FutureWarning on the setter in this PR. The part I'd like to keep is that assigning through the alias no longer drops rope_theta silently.
There was a problem hiding this comment.
If you'd rather steer users to rope_parameters
yes, exactly, that was my point since v5 was deliberately slightly breaking. We can update the cookbook in sglang as well since they have pinned v5 already
There was a problem hiding this comment.
also maybe we just raise the warning/error instead of a long auto-infer code, wdyt?
There was a problem hiding this comment.
Agreed, dropped the inference. Pushed the small version in d0a0a6a: the setter now emits a FutureWarning pointing to rope_parameters, raises a ValueError if rope_theta is missing, and otherwise assigns the value and calls standardize_rope_params() so type is normalised. I went with a warning rather than a hard error for the complete-dict case because SGLang and vLLM both assign rope_scaling today (--json-model-override-args / --hf-overrides), and raising would break them on the next release before the cookbooks are updated; happy to switch to raising if you prefer. Tests are down to two. I'll send the cookbook change to sglang as well.
There was a problem hiding this comment.
Cookbook side is up: sgl-project/sglang#44155 moves the Qwen3-Next and Ling YaRN recipes to rope_parameters (with rope_theta / partial_rotary_factor included).
There was a problem hiding this comment.
because SGLang and vLLM both assign rope_scaling today (--json-model-override-args / --hf-overrides)
you mean in the cookbook right rather than in actual prod code or in tests?
There was a problem hiding this comment.
Production code, in both. vLLM's get_config() applies --hf-overrides with config.update(hf_overrides_kw) (vllm/transformers_utils/config.py), and update() does a setattr per key, so a user's {"rope_scaling": ...} lands in this setter at load time. SGLang's get_config() does the same with --json-model-override-args (python/sglang/srt/utils/hf_transformers/config.py); since sgl-project/sglang#43067 (merged yesterday) it merges rope_scaling / rope_parameters dicts with the loaded values before the setattr, so SGLang now always hands this setter a complete dict, while vLLM still passes whatever the user wrote. The cookbooks are only where people learn to write those overrides, hence the docs PR. Fine with warn or raise, your call.
|
Thank you for your contribution 🤗! CI Security Gate — waiting for maintainer approvalCI is approved automatically only for authors who already have a contribution merged in this repository. A maintainer will review this PR and approve CI to run on it. |
| "instead, with `rope_type` and `rope_theta` included.", | ||
| FutureWarning, | ||
| ) | ||
| parameters = [value[key] for key in self.nested_rope_parameter_keys(value or {})] or [value] |
There was a problem hiding this comment.
i suppose this is for None as value - lets early exit if none
There was a problem hiding this comment.
Done in 1b3766a: None returns right after the warning and leaves rope_parameters untouched.
| if any(p is not None and "rope_theta" not in p for p in parameters): | ||
| raise ValueError("`rope_scaling` no longer fills in `rope_theta`; set `rope_parameters` with it included.") |
There was a problem hiding this comment.
standardize also checks self.rope-theta if available, so i thnk we can call standardize and let it raise an index/key error
There was a problem hiding this comment.
Done, the check is gone; it is assign + standardize_rope_params() now (1b3766a). One thing to flag: a v5 config has no rope_theta attribute any more, so for a dict without rope_theta standardize does setdefault("rope_theta", None) instead of raising, and validate_rope is happy because the key exists; the failure then surfaces at rotary init as TypeError: unsupported operand type(s) for ** or pow(): 'NoneType' and 'Tensor' (checked with LlamaConfig(rope_theta=500000.0) and {"type": "yarn", "factor": 4.0}). Fine by me if that is acceptable; otherwise a one-line check here, or rope_theta joining required_keys in validate_rope, would make it fail at assignment. The requires_rope_theta test is replaced by a None test; 37 pass locally.
| if value is None: | ||
| return | ||
| self.rope_parameters = value |
There was a problem hiding this comment.
we should set None if asked - just not ry to standardize :)
There was a problem hiding this comment.
Done in b84f86d: None is assigned as-is (rope_parameters becomes None) and standardize_rope_params() only runs for a dict; the None test now asserts that. 37 pass locally.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
|
Thanks for the review. I updated the title and description to match the reviewed setter. @ydshieh could you rerun tokenizer shard 4/8? It exits 137 in |
CI recapDashboard: View test results in Grafana |
55928cf
What does this PR do?
Assigning v4-style
rope_scalingon a v5 config replaced the wholerope_parametersdictionary:rope_thetaandpartial_rotary_factorwere dropped, the legacytypekey was not normalised, so rotary-embedding construction failed withKeyError: 'rope_type'and a save/load round trip silently came back with the default theta (#49519).Following the review, the setter now emits a
FutureWarningpointing callers torope_parameters, assigns the supplied value, and callsstandardize_rope_params()only when the value is notNone. It no longer tries to infer missing parameters or adds its ownrope_thetavalidation. A complete dictionary still works and legacytypeis normalised torope_type; assigningNoneleavesrope_parametersasNone.The two regression tests cover the deprecation warning and legacy-key normalisation on a complete dictionary, and assigning
Nonewithout standardisation.Validation
Linux x86_64 and macOS, CPU, Python 3.12, PyTorch 2.14.
tests/utils/test_modeling_rope_utils.py: 37 passed after the change (35 existing + 2 new).ruff format/ruff checkon both files: clean.Code Agent Policy
The Transformers repo is currently being overwhelmed by a large number of PRs and issue comments written by
code agents. These often are low-quality, or fix extremely minor issues that occur rarely or never in practice.
As a result, we're instituting a rule that first-time contributors should not use code agents to submit PRs or issues.
We'd also ask autonomous "OpenClaw"-like agents not to open any PRs or issues.
Issues/PRs from first-time contributors that violate this rule will probably just be closed without review, and we
might block you, especially if you open more than one or appear to be deliberately ignoring this. We especially do not
want new contributors to jump in on random issues to contribute an agent-written fix. This creates lots of noise
for reviewers and other users and will almost certainly get you blocked.
For more information, please read
CONTRIBUTING.md.Before submitting
Pull Request checks?
to it if that's the case.
Who can review?
Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
members/contributors who may be interested in your PR.