feat(recipes)!: split recipe params into hardware and capability layers - #59
Open
ethan-scitix wants to merge 2 commits into
Open
feat(recipes)!: split recipe params into hardware and capability layers#59ethan-scitix wants to merge 2 commits into
ethan-scitix wants to merge 2 commits into
Conversation
Captures what all 168 (recipe, hardware, precision, framework) triples resolved to before the recipe params are split into hardware/capability layers, so the split can be proven output-preserving for instruct models. Params are stored as ordered key/value pairs rather than a mapping: infer_plans.yaml is dumped with sort_keys=False and compared byte-for-byte under --resume, so parameter order is part of the contract and a fixture that sorted keys would hide a reordering. The generator reads the recipes from a git ref instead of the working tree, because the pre-split schema stops existing in the next commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The flat profiles[hardware][precision][framework] bag mixed two kinds of param with different owners, and was matched on family + size only. A base checkpoint therefore resolved to the same profile as the instruct one and inherited its parsers and tool-choice flags. That is now a live path, not a hypothetical: Qwen2.5-72B-Base is the comparison target for the ARC and HellaSwag ppl/clp tasks and shares the qwen2.5-72b bucket with the Instruct checkpoint, whose every leaf carries enable_auto_tool_choice and tool_call_parser: hermes. Recipes now declare two layers: hardware[gpu][precision][framework] perf, memory, parallelism, context capabilities[instruct|base][fw] parsers, tool choice resolve_profile is replaced by resolve_hardware_profile and resolve_capability_profile; callers merge them with capabilities last. Collapsing the duplicated capability params takes them from 352 lines to 78. The model type comes from the eval config, mapped chat -> instruct and gen -> base. It is *derived*, not read off the explicit `type:` field: no config in the repo sets `type:`, leaving it to inference from the tasks pointing at the model, so reading the explicit field alone would have resolved every base checkpoint to instruct capabilities and made this change a no-op on real configs. The derivation that the eval session already performed is promoted to a shared derive_model_type, since both sites must reach the same answer for one model. BREAKING CHANGE: the `profiles` key is rejected with a migration error rather than read for compatibility; recipe files ship in-repo, so there is no legacy on-disk state to support, and silently accepting it would keep handing base checkpoints the params this split exists to withhold. User customization via `overrides` is unchanged. Instruct resolution is unchanged in content for all 168 profile triples. Key *order* changes for the two gpt-oss recipes alone, whose capability params sat between hardware params pre-split; the golden test pins that set so no further reordering can slip in. Because infer_plans.yaml is compared byte-for-byte, this must ship as a minor bump, where the resume version gate's (major, minor) break axis rejects a 0.7.x resume with a version message instead of a plan diff. Refs: #27 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 task
1 task
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.
Type
Summary
Implements Mechanism A of RFC #27. The RFC's scope was split first (see its comment thread): Mechanism B is superseded by #47, and the two-way case neither can express is now #58. This PR is only the recipe schema half.
profiles[hardware][precision][framework]mixed perf params with behavior params and matched on family + size only, so a base checkpoint resolved to the instruct profile and inherited its parsers.qwen2.5-72b(size_range: [60, 80]) carriesenable_auto_tool_choice: true+tool_call_parser: hermeson every leaf — and Qwen2.5-72B-Base is the named comparison target of the ARC and HellaSwag ppl/clp tasks, which resolve to that same bucket.hardware[gpu][precision][framework](dtype, memory, parallelism, context) andcapabilities[instruct|base][framework](parsers, tool choice).resolve_profile→resolve_hardware_profile+resolve_capability_profile, merged capabilities-last. Collapsing the duplicated capability params takes them from 352 lines to 78, and the three recipe files from 1328 to 1203.type:— it is left to inference from the tasks pointing at the model. Reading the explicit field alone (as the original plan did) would have resolved every base checkpoint to instruct capabilities and made this change a no-op on real configs. The derivation the eval session already performed is promoted to a sharedderive_model_type, since both sites must agree for one model.no_enable_prefix_cachingdeliberately stays on the hardware layer. It is model-intrinsic rather than hardware, but prefix caching is being modelled as an engine-level constraint in [RFC]: Capability constraint layer — declare and enforce the silent cross-group constraints #47; moving it now would migrate it twice. Noted in the YAML.Related Issues
Refs #27. Related: #47 (supersedes Mechanism B), #58 (the two-way case).
Test Plan
Automated
ruff check && ruff format --check) — the one remaining hit issieval/_version.py, build-generated and gitignoredty check) — "All checks passed!"check_layer_imports.py(exit 0), andcheck_preflight.py(all PASS)New coverage:
tests/unit/infer/recipes/test_golden.py— characterization against a committed fixture of all 168 pre-split triples: content preserved, order preserved, base path omits capability paramstests/unit/infer/test_recipes.py— legacy-key rejection,capability_model_typemapping, capability resolution, and a sweep asserting no shipped hardware leaf still carries a parser and every entry declares both model typestests/unit/cli/infer/test_resolve.py— end-to-end throughresolve_infer_config, including the load-bearing case: notype:in config + a gen task ⇒ base capabilitiestests/unit/cli/leaderboard/test_session.py—derive_model_typedirectly, sincesieval runcalls the function rather than the session methodManual
enable_auto_tool_choice: trueactually fails to start on vLLM or merely carries a dead flag. RFC [RFC]: Recipe capability layer — split model-type capabilities out of hardware profiles #27 says "meaningless", never "breaks". If it breaks, this is a bug fix rather than hygiene.Checklist
Required (all PRs)
AI-Generated Code - <model> (<provider>)in module docstringcore/—core/untouched;infer/still imports nothing above itresolve_profilehad exactly two production call sites (cli/infer/recipe.py,infer/topology/resolver.py), both migrated; no references remainIf: Breaking Change
What breaks. The
profileskey is rejected with a migration error naming both replacement layers, rather than read for compatibility. Recipe files ship in-repo so there is no legacy on-disk state to support, and silently acceptingprofileswould keep handing base checkpoints the params this split exists to withhold. User customization viaoverridesis unchanged.resolve_profileis removed from thesieval.infer.recipespublic surface.This must ship as a MINOR bump (0.8.0), not a patch. Instruct resolution is content-identical for all 168 triples, but key order changes for the two gpt-oss recipes, whose capability params sat between hardware params pre-split.
infer_plans.yamlis dumpedsort_keys=Falseand compared byte-for-byte under--resume, so a 0.7.x run resumed on this code would abort. Under 1.0 the resume version gate's break axis is(major, minor), so as 0.8.0 the gate rejects that resume with a clear version message; as 0.7.1 the user would instead get a confusing plan diff. The golden test pins the reordered set to exactly those two recipes so no further reordering can slip in unnoticed. Staged in the pending-CHANGELOG notes with this reasoning.Note for reviewers
Two things I did not do, deliberately:
docs/superpowers/specs/2026-07-01-recipe-capability-layer-design.mdto be committed, and I initially agreed in the [RFC]: Recipe capability layer — split model-type capabilities out of hardware profiles #27 comment. That is wrong for this repo:docs/designs/is in.gitignore(line 185) and onlydocs/guide/is tracked — in the public and internal repo. Design specs are local-only by convention here, so tracking one would need an explicit convention change, which is not this PR's call. The doc now carries a scope-reconciliation header locally. The underlying concern is still valid but narrower than [RFC]: Capability constraint layer — declare and enforce the silent cross-group constraints #47 implies: the sglang truncation evidence is already insglang_gen_model.py:302-328and the--max-logprobsevidence in 9 task docstrings; what is genuinely unrecorded anywhere tracked is the vLLM V1prompt_logprobs× prefix-caching mutual exclusion.🤖 Generated with Claude Code