feat(recipes): add glm-5.1-grpo recipe (#275) - #656
Conversation
GLM-5.1 shipped SFT (v0.71.24) and DPO (MakazhanAlpamys#280) but no reasoning variant. MakazhanAlpamys#275 collects exactly these task-variants and invites more beyond its six named children, all of which are closed. Measured the gap rather than picking by eye: of the 2026 families, GLM-5.1 is the one at two-of-three whose missing slot is reasoning rather than preference. The two conventions conflict, so this follows MakazhanAlpamys#275's own rule -- the shipped SFT recipe is the copy-paste template, the GRPO recipes supply the task shape. Model geometry from the GLM-5.1 siblings (LoRA r32/a64, batch_size 1, grad_accum 16, 4-bit, moe_lora, moe_aux_loss_coeff, gradient_checkpointing, max_length 8192); task fields from qwen3-30b-a3b-reasoning-grpo (reasoning_train.jsonl, grpo_beta, num_generations, reward_fn). Deliberately NOT the 30B template's r16/a32 -- both GLM-5.1 siblings use r32/a64 for this 754B base. lr 1e-5 is the one value both axes agree on (15 of 22 GRPO recipes, and glm-5.1-sft), so it is not an outlier in either direction -- the MakazhanAlpamys#632 lesson. epochs 3 pairs with 1e-5 across the whole GRPO cohort. No hyperparameter here is a measured recommendation: GLM-5.1 is 754B MoE and was not trained. The model id is pinned on two independent surfaces, RecipeMeta.model and the YAML base:, in separate tests so a mutation to either alone names the surface it broke. RecipeMeta.size was uncovered -- mutating 754B -> 30B survived the whole suite -- so the size is now tied across the three GLM-5.1 variants. Catalog 162 -> 163, swept across the catalog comment, three renamed test_catalog_size_is_163 methods, four assertions, one docstring cross-reference, CONTRIBUTING.md and two docs pages. Snapshot fixture regenerated additively (14 insertions, 0 deletions). Claude-Session: https://claude.ai/code/session_014vWWFgXhj9y46pYCyEjfcy
…s#656) Added after the PR existed rather than guessed, per MakazhanAlpamys#487 -- one fragment per number, and a guessed 583.added.md once collided with a 583.fixed.md already on main and reddened all nine test matrix cells. Claude-Session: https://claude.ai/code/session_014vWWFgXhj9y46pYCyEjfcy
MakazhanAlpamys
left a comment
There was a problem hiding this comment.
Approved — merging once CI finishes. Verified the things a recipe PR can quietly get wrong, rather than that the dict is well-formed.
It validates against the real schema, not just the shape. A task: grpo recipe has cross-validators that a syntactically fine dict can still fail, so I loaded the YAML through load_config_from_string:
base: zai-org/GLM-5.1 | task: grpo
reward_fn: accuracy | num_generations: 4 | grpo_beta: 0.1
lora r/alpha: 32 64 | quant: 4bit
reward_fn and num_generations are both present, and _validate_verifiable_reward is satisfied because accuracy is not verifiable, so no verifiable_domain is required. Passes.
The model id is right, and I checked it because this repo has form here — a glm-5 recipe once shipped with THUDM instead of zai-org and had to be corrected after release. Byte-identical to both siblings:
glm-5.1-sft: zai-org/GLM-5.1
glm-5.1-dpo: zai-org/GLM-5.1
glm-5.1-grpo: zai-org/GLM-5.1
And it is now guarded rather than merely correct: flipping it to THUDM/GLM-5.1 fails test_recipe_delta_matches_its_snapshot[glm-5.1-grpo] by name.
The snapshot fixture is the part that carried real risk, since #640 landed a few hours ago and changed that file from 162 full copies to baseline + deltas. Regeneration is byte-exact — git status --porcelain empty after re-running the generator in your worktree, so the documented Windows CRLF trap did not fire. Your delta also restates nothing from the baseline: the seven default-matching fields (epochs, quantization, grpo_beta, num_generations, reward_fn, moe_aux_loss_coeff, lora.target_modules) are correctly absent from the delta while still spelled out in the YAML for sibling parity. That is exactly the property #640's encoding depends on.
Your deviations from the templates are the right call and you documented them. r32/a64, batch_size: 1 with gradient_accumulation_steps: 16, and max_length: 8192 come from the GLM-5.1 siblings rather than from qwen3-30b-a3b-reasoning-grpo, which is correct precedence — #275 says the shipped SFT recipes are the copy-paste template, and a 754B MoE's geometry is the constraint that dominates. The task fields come from the GRPO template. Nothing looks like an oversight.
test_shares_base_and_size_with_its_glm51_siblings closes a genuine pre-existing hole — RecipeMeta.size had no coverage anywhere in the suite. Zero deleted or weakened tests: the 8 removed lines are all 1:1 count bumps, net +9 test methods.
Count references all updated, and the ones left alone are correctly left alone (historical narrative in test docstrings, and an unrelated #162). The one stale count is .claude/CLAUDE.md at "144 ready-made recipes" — that file is gitignored and maintainer-local, so it is not visible in your checkout and not your problem. I have fixed it on my side.
Your PR body's overlap check is the habit I want to name. Searching gh pr list --search glm --state all, confirming the six named children of #275 were closed, and claiming the item before writing code is exactly what would have prevented the two collisions I mishandled this week. You also self-reported a surviving mutation in your own table and then closed it, which is worth more to me than a table with no survivors in it.
One honest limit, which you stated yourself: 754B MoE is multi-node, nothing here is a measured recommendation, and the HF id is verified as resolving, not as training under this config. That is the correct scope for a recipe PR and I would rather have it said than implied.
validate-recipes is already green; waiting on the rest.
Records the process rather than the recipe: they searched for overlapping work and claimed the item before writing code, which is what would have prevented the two claim collisions this week. Also records that their own mutation table self-reported a survivor and then closed it -- a RecipeMeta.size gap with no coverage anywhere in the suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Merged as Credited for the process, not the recipe. Searching for overlapping work, confirming all six named children of #275 were closed, and claiming the item before writing code is exactly what would have prevented the two claim collisions I mishandled this week — in one of them I held a contributor's PR for five days because I offered the same work to someone else 28 minutes after their branch already existed. Verified before merging: schema validation through the real loader (GRPO cross-validators pass), the base id byte-identical to both siblings and now guarded by a named test, byte-exact fixture regeneration despite #640 having changed that file to delta encoding hours earlier, and the new delta restating nothing from the baseline. Your own mutation table self-reporting a survivor and then closing it — the |
DeepSeek-V4-Flash shipped SFT (v0.71.24) and GRPO (MakazhanAlpamys#279, merged as MakazhanAlpamys#432) but no preference variant. Of the bases still at two-of-three it is the efficiency-tier model, so it is the one a contributor without a multi-node cluster is most likely to reach. The DPO convention in this catalog is unusually tight and was measured rather than assumed: lr 5e-6 in all 12 DPO recipes, dpo_beta 0.1 in all 12, epochs 3 in 11 of 12 (the exception is glm-5.1-dpo at 754B). Both siblings also use epochs 3. LoRA r16/a32 matches both siblings and every non-GLM DPO recipe. Deliberate deviation from the GRPO sibling, stated in the claim before any code was written: batch_size auto / gradient_accumulation_steps 8 come from the SFT sibling rather than GRPO's 1/16, because GRPO's batch shape is driven by rollout generation that DPO does not do, and qwen3.5-35b-a3b-dpo -- the closest MoE-DPO analogue -- uses auto/8. The same reasoning omits gradient_checkpointing, which the SFT sibling and that analogue both leave at its default. No modality line: DeepSeek has no family contract like the Qwen3.5 one, and neither sibling declares it. Carries forward the RecipeMeta.size lesson from MakazhanAlpamys#656 -- size is asserted shared across the three V4-Flash variants, so the hole that survived the whole suite there does not reopen here. Mutating size N/A -> 7B is killed by name. Catalog 163 -> 164, swept across the same 13 sites. Snapshot fixture regenerated additively (12 insertions, 0 deletions). Not trained: DeepSeek-V4-Flash is MoE and no hyperparameter here is a measured recommendation. Claude-Session: https://claude.ai/code/session_014vWWFgXhj9y46pYCyEjfcy
DeepSeek-V4-Flash shipped SFT (v0.71.24) and GRPO (MakazhanAlpamys#279, merged as MakazhanAlpamys#432) but no preference variant. Of the bases still at two-of-three it is the efficiency-tier model, so it is the one a contributor without a multi-node cluster is most likely to reach. The DPO convention in this catalog is unusually tight and was measured rather than assumed: lr 5e-6 in all 12 DPO recipes, dpo_beta 0.1 in all 12, epochs 3 in 11 of 12 (the exception is glm-5.1-dpo at 754B). Both siblings also use epochs 3. LoRA r16/a32 matches both siblings and every non-GLM DPO recipe. Deliberate deviation from the GRPO sibling, stated in the claim before any code was written: batch_size auto / gradient_accumulation_steps 8 come from the SFT sibling rather than GRPO's 1/16, because GRPO's batch shape is driven by rollout generation that DPO does not do, and qwen3.5-35b-a3b-dpo -- the closest MoE-DPO analogue -- uses auto/8. The same reasoning omits gradient_checkpointing, which the SFT sibling and that analogue both leave at its default. No modality line: DeepSeek has no family contract like the Qwen3.5 one, and neither sibling declares it. Carries forward the RecipeMeta.size lesson from MakazhanAlpamys#656 -- size is asserted shared across the three V4-Flash variants, so the hole that survived the whole suite there does not reopen here. Mutating size N/A -> 7B is killed by name. Catalog 163 -> 164, swept across the same 13 sites. Snapshot fixture regenerated additively (12 insertions, 0 deletions). Not trained: DeepSeek-V4-Flash is MoE and no hyperparameter here is a measured recommendation. Claude-Session: https://claude.ai/code/session_014vWWFgXhj9y46pYCyEjfcy
Adds
glm-5.1-grpo, the reasoning task-variant forzai-org/GLM-5.1. Catalog 162 -> 163.Refs #275. Claimed before any code was written.Overlap and attribution
No claim comment existed for this recipe.
gh pr list --search glm --state allreturns #452 (glm-5.1-dpo, merged, by @Osheun) and nothing else touching GLM; no open PR touchesrecipes/,catalog.py, or GRPO. All six named children of #275 are closed — #276 (#615), #277 (#448), #278 (#422), #279, #280 (#452), #281. I checked PR titles rather than only issue threads, since #512 is the reason that rule exists.The shape is inherited, not invented. The geometry is @Osheun's from #452; the MoE-GRPO task fields are from
qwen3-30b-a3b-reasoning-grpo.Shape, and why
GLM-5.1 had SFT (v0.71.24) and DPO (#280) but no reasoning variant. Measured rather than picked by eye — the models at two-of-three:
GLM-5.1 is the one whose missing slot is reasoning rather than preference.
Where each line came from
The two conventions conflict — the MoE-GRPO cohort uses LoRA r16/a32, the GLM-5.1 siblings use r32/a64. I followed #275's own rule ("the shipped SFT recipes are the copy-paste template"), with the GRPO templates supplying the task shape:
base,size: 754B, tagsglm-5.1-sft/glm-5.1-dpobatch_size: 1,gradient_accumulation_steps: 16lora r32/a64moe_lora,moe_aux_loss_coeff,gradient_checkpointing,max_length: 8192task: grpo,reasoning_train.jsonl,grpo_beta,num_generations,reward_fnqwen3-30b-a3b-reasoning-grpolr: 1e-5,epochs: 3glm-5.1-sft; the 1e-5/epochs-3 pairing holds across the cohortlris the one place both axes agree, so it is not an outlier in either direction — the #632 lesson. The closest analogue overall iskimi-k2.6-grpo(1T MoE, 2026 family): samelr,epochs,max_length,moe_lora.Mutation table
Each mutation applied only inside the
glm-5.1-grpoentry (sliced by dict key, sor: 32cannot hit another recipe).__pycache__cleared every round — a stale.pycfakes a restore when the edit is the same length.catalog.pymd5-verified byte-identical after every round.RecipeMeta.modelaloneTestGlm51GrpoRecipe::test_recipe_meta_pins_the_model_idbase:aloneTestGlm51GrpoRecipe::test_yaml_base_pins_the_model_idtest_recipe_meta_pins_the_model_id+ 2 othersRecipeMeta.taskaloneTestRecipeCatalog::test_recipe_tasks_match_yamltask:aloneTestRecipeCatalog::test_recipe_tasks_match_yamltest_glm51_task_variants_are_three_distinct_entries+ 2lora.r32 -> 16test_recipe_loads_with_expected_grpo_moe_shapelora.alpha64 -> 32test_recipe_loads_with_expected_grpo_moe_shapemoe_loratrue -> falsetest_recipe_loads_with_expected_grpo_moe_shapegradient_checkpointingtrue -> falsetest_recipe_loads_with_expected_grpo_moe_shapebatch_size1 -> autotest_recipe_loads_with_expected_grpo_moe_shapegradient_accumulation_steps16 -> 4test_recipe_loads_with_expected_grpo_moe_shapelr1e-5 -> 2e-5test_recipe_loads_with_expected_grpo_moe_shapemax_length8192 -> 2048test_recipe_loads_with_expected_grpo_moe_shapetest_recipe_meta_pins_the_model_idsize754B -> 30BThe survivor that was a real gap
RecipeMeta.sizehad no coverage anywhere in the suite — mutating754B -> 30Bpassed everything. That is not an equivalent mutation, it is a hole, so I closed it rather than reporting it as acceptable:The new test ties
sizeacross all three GLM-5.1 variants rather than pinning a bare literal, so a lone edit fails while a genuine correction applied consistently does not.Reported survivors — equivalent, and proved rather than asserted
Seven lines equal their schema default, so stripping them is behaviourally identical and cannot be killed. I am not manufacturing kills for them:
Independent confirmation from the repo's own tooling: #640's delta encoder omitted exactly these seven fields from the recipe's snapshot delta, because they match the baseline. They are kept in the YAML for parity with the sibling recipes, which also spell them out.
I did not add a test pinning the YAML source text to kill them — that is the shape #621/#640 removed from this suite.
Verification
Those 8 removed test lines, every one accounted for: 3 ×
def test_catalog_size_is_162renamed to_163, 4 ×assert len(RECIPES) == 162bumped to163, and 1 docstring line intest_v07124.pythat namestest_catalog_size_is_162by string (updated so the pointer stays true). Each is a 1:1 replacement. Net +9 test methods; nothing deleted or weakened.Run live, not only asserted on
The written
soup.yamlreloads throughload_config_from_string->zai-org/GLM-5.1 | grpo | lora r32 | lr 1e-05.Count sweep — 13 sites
catalog.pycomment · 3 renamedtest_catalog_size_is_163methods · 4 assertions · 1 docstring cross-reference ·CONTRIBUTING.md·docs/commands.md·docs/serving-and-export.md(×2). Verified withgrep -rn "\b162\b"; the remaining hits are all historical (CHANGELOG.md,CONTRIBUTORS.md,changelog.d/fragments) and correctly left alone.Snapshot fixture regenerated via
scripts/generate_recipe_snapshot.py: 14 insertions, 0 deletions — one recipe block, nothing else moved.Scope limits
https://huggingface.co/api/models/zai-org/GLM-5.1returns HTTP 200. I did not download 754B of weights, so "the checkpoint trains under this config" is unverified.pytorch-smokeand full-suite gates are CI's to run; locally I ran the targeted suites with-o addopts=, which skips the coverage gate.Changelog fragment follows in a second push, once this PR has a number — deliberately not guessed, per #487 and the
583.added.mdcollision.https://claude.ai/code/session_014vWWFgXhj9y46pYCyEjfcy