[rl] RendererConfig silently drops the removed preserve_* renderer knobs - #4365
[rl] RendererConfig silently drops the removed preserve_* renderer knobs#4365andylizf wants to merge 3 commits into
Conversation
…tention through auto renderers replaced preserve_all_thinking and preserve_thinking_between_tool_calls with a single thinking_retention enum (PrimeIntellect-ai/renderers#88). Neither name is a field on a renderer config any more, so RendererConfig.build, which forwards by matching field names, drops both without a word -- and drops them before the library's own validator, which raises and names the replacement, can see them. Forward thinking_retention as its own field, and raise when a removed knob is set to True and the resolved config cannot take it. On the auto path (name=None or "auto") the library's AutoRendererConfig carries thinking_retention into the resolved config and nothing else, so forward that one knob there and raise for the rest instead of dropping them. An explicit False is left alone: it meant "defer to the chat template", which is what dropping it does.
|
Hi @andylizf! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
The following ciflow label(s) have been added but CI has not been triggered yet because the workflows are awaiting approval:
Once a maintainer approves the workflows (scroll to the bottom of the PR page), the corresponding CI jobs will be triggered automatically. Please ping one of the reviewers if you do not have access to approve and run workflows. |
|
hey @andylizf , thanks for the PR! I am on PTO until Monday. I will try to review over the weekend. If not, i will do it on Tuesday |
| enable_thinking: Let the model emit reasoning, when supported. | ||
| preserve_all_thinking: Keep historical reasoning in future prompts. | ||
| preserve_thinking_between_tool_calls: Keep reasoning during tool loops. | ||
| preserve_all_thinking: Removed upstream; see `thinking_retention`. |
There was a problem hiding this comment.
we dont really do backward compatibility. If this is a dead knob, lets remove it
| renderer_name = _RENDERER_BY_MODEL.get(self.name, self.name) | ||
| renderer_config = config_from_name(renderer_name) if renderer_name else None | ||
|
|
||
| # `renderers` replaced these two bools with the single `thinking_retention` |
There was a problem hiding this comment.
same here, no need to keep comments about historical motivation
|
@claude please review. What is a robust and elegant/simple way of forwarding configs without silently allowing errors like this? please think of 3 ideas with code snippets. Dont be verbose. |
|
Claude finished @felipemello1's task in 46s —— View job Forwarding configs without silent dropsThe root cause is the Idea 1 — Delegate to the library's own guard (simplest, my pick). args = {
f.name: getattr(self, f.name)
for f in fields(self)
if f.name != "name" and getattr(self, f.name) is not None
}
# config_type has extra="forbid" + a validator naming thinking_retention,
# so a removed/typo'd knob raises here instead of vanishing.
return create_renderer(tokenizer, config_type(**args))Idea 2 — Don't mirror the schema at all. name: str | None = None
options: dict[str, Any] = field(default_factory=dict) # forwarded verbatim
...
config_type(**self.options) # unknown keys -> pydantic raisesTrade-off: loses per-field typing/discoverability in TorchTitan's config. Good when the wrapped schema churns often. Idea 3 — Explicit diff, if you want to keep the typed mirror. provided = {f.name for f in fields(self)
if f.name != "name" and getattr(self, f.name) is not None}
unknown = provided - config_type.model_fields.keys()
if unknown:
raise ValueError(f"{sorted(unknown)} not accepted by {config_type.__name__}")Recommendation: Idea 1. It deletes code, removes the mirror-vs-library drift entirely, and reuses the guard Want me to push this simplification to the branch? |
RendererConfig.buildforwards knobs to therenderersconfig by matching fieldnames, and drops anything that does not match:
renderersreplacedpreserve_all_thinkingandpreserve_thinking_between_tool_callswith a single
thinking_retentionenum in PrimeIntellect-ai/renderers#88 (2026-06-26).Neither name is a field on a renderer config any more, so both are dropped. Both are
still declared on
RendererConfigand documented in its docstring, andcomponents/training_sample_builder.pyrecommends settingpreserve_all_thinking=Trueto keep thinking from being stripped out of history.
experiments/rl/requirements.txttracks
renderersatmain, so the knobs have been inert since that commit.The drop is silent, and it defeats a guard the library added for this case:
rendererskeeps a validator that raises
"[...] were replaced by thinking_retention"when an oldname reaches it, and the typed configs use
extra="forbid". The name filter removes thefield before either can see it. Startup logs the result without flagging it:
Where we hit it: a recipe setting
preserve_all_thinking=Trueran under the renderer'simplied
tool_cyclepolicy instead. For an agent loop that returns tool output as plainuser messages, every turn reads as a new user query, so
bridge_to_next_turnrefuses tocontinue and the trajectory is re-rendered from scratch — 40694 of 40697 turns in one
run, against a code comment that expects zero. On a hybrid model without prefix caching
that is a full recompute per turn.
This PR:
thinking_retentiontoRendererConfigso callers have a working path, typedas the library's
Literal["tool_cycle", "all"].Trueand the resolved config cannot take it,naming the replacement. An explicit
Falseis left alone: it meant "defer to thechat template", which is what dropping it already does.
thinking_retentionon the auto path by constructingAutoRendererConfig,which the library provides precisely to carry it through tokenizer-based resolution.
Other knobs set there still reach nothing, so they raise rather than being ignored,
and
create_renderer(tokenizer, None)is kept unchanged when nothing is set.Verified against the installed
renderers:thinking_retentionthrough auto resolves tothe same config object as naming the renderer explicitly, across all 53 models in
MODEL_RENDERER_MAP;create_renderer(tokenizer, None)andcreate_renderer(tokenizer, AutoRendererConfig())are equivalent; a matrix over names(
None,"auto","qwen3", unknown) x both legacy fields (None/False/True) xretention set/unset behaves as described; and existing callers that set only
nameandenable_thinking, or passtool_parseralongside a model-specific renderer, areunaffected.
One gap left alone:
config_from_name()still runs before the removed-knob check, so anunknown renderer name reports that first and masks the legacy message. I can reorder it
if you would rather the legacy message win.