Skip to content

feat(recipes): add glm-5.1-grpo recipe (#275) - #656

Merged
MakazhanAlpamys merged 2 commits into
MakazhanAlpamys:mainfrom
Srinivasan8888:feature/recipe-glm51-grpo
Sep 4, 2026
Merged

feat(recipes): add glm-5.1-grpo recipe (#275)#656
MakazhanAlpamys merged 2 commits into
MakazhanAlpamys:mainfrom
Srinivasan8888:feature/recipe-glm51-grpo

Conversation

@Srinivasan8888

Copy link
Copy Markdown
Contributor

Adds glm-5.1-grpo, the reasoning task-variant for zai-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 all returns #452 (glm-5.1-dpo, merged, by @Osheun) and nothing else touching GLM; no open PR touches recipes/, 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:

zai-org/GLM-5.1                  ['dpo', 'sft']         missing=['grpo']
Qwen/Qwen3.5-35B-A3B             ['dpo', 'sft']         missing=['grpo']
deepseek-ai/DeepSeek-V4-Flash    ['grpo', 'sft']        missing=['dpo']
moonshotai/Kimi-K2.6             ['grpo', 'sft']        missing=['dpo']
Qwen/Qwen3.5-9B                  ['dpo','grpo','sft']   complete (#632)

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:

line source note
base, size: 754B, tags glm-5.1-sft / glm-5.1-dpo same base model
batch_size: 1, gradient_accumulation_steps: 16 GLM-5.1 siblings 754B geometry
lora r32/a64 GLM-5.1 siblings not the 30B template's r16/a32 — both siblings use r32/a64 here
moe_lora, moe_aux_loss_coeff, gradient_checkpointing, max_length: 8192 GLM-5.1 siblings
task: grpo, reasoning_train.jsonl, grpo_beta, num_generations, reward_fn qwen3-30b-a3b-reasoning-grpo the MoE-GRPO task shape
lr: 1e-5, epochs: 3 both agree 15 of 22 GRPO recipes use 1e-5, as does glm-5.1-sft; the 1e-5/epochs-3 pairing holds across the cohort

lr is the one place both axes agree, so it is not an outlier in either direction — the #632 lesson. The closest analogue overall is kimi-k2.6-grpo (1T MoE, 2026 family): same lr, epochs, max_length, moe_lora.

Mutation table

Each mutation applied only inside the glm-5.1-grpo entry (sliced by dict key, so r: 32 cannot hit another recipe). __pycache__ cleared every round — a stale .pyc fakes a restore when the edit is the same length. catalog.py md5-verified byte-identical after every round.

# mutation verdict named test that fails
1 RecipeMeta.model alone KILLED TestGlm51GrpoRecipe::test_recipe_meta_pins_the_model_id
2 YAML base: alone KILLED TestGlm51GrpoRecipe::test_yaml_base_pins_the_model_id
3 both model surfaces together KILLED test_recipe_meta_pins_the_model_id + 2 others
4 RecipeMeta.task alone KILLED TestRecipeCatalog::test_recipe_tasks_match_yaml
5 YAML task: alone KILLED TestRecipeCatalog::test_recipe_tasks_match_yaml
6 both task surfaces together KILLED test_glm51_task_variants_are_three_distinct_entries + 2
7 lora.r 32 -> 16 KILLED test_recipe_loads_with_expected_grpo_moe_shape
8 lora.alpha 64 -> 32 KILLED test_recipe_loads_with_expected_grpo_moe_shape
9 moe_lora true -> false KILLED test_recipe_loads_with_expected_grpo_moe_shape
10 gradient_checkpointing true -> false KILLED test_recipe_loads_with_expected_grpo_moe_shape
11 batch_size 1 -> auto KILLED test_recipe_loads_with_expected_grpo_moe_shape
12 gradient_accumulation_steps 16 -> 4 KILLED test_recipe_loads_with_expected_grpo_moe_shape
13 lr 1e-5 -> 2e-5 KILLED test_recipe_loads_with_expected_grpo_moe_shape
14 max_length 8192 -> 2048 KILLED test_recipe_loads_with_expected_grpo_moe_shape
15 delete the whole entry KILLED test_recipe_meta_pins_the_model_id
16 size 754B -> 30B SURVIVED, then KILLED see below

The survivor that was a real gap

RecipeMeta.size had no coverage anywhere in the suite — mutating 754B -> 30B passed everything. That is not an equivalent mutation, it is a hole, so I closed it rather than reporting it as acceptable:

[KILLED  ] size 754B -> 30B (was a SURVIVOR)
           -> TestGlm51GrpoRecipe::test_shares_base_and_size_with_its_glm51_siblings

The new test ties size across 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:

stripped line                    identical loaded config?   schema default
  epochs: 3                                          True   epochs = 3
  quantization: 4bit                                 True   quantization = '4bit'
  grpo_beta: 0.1                                     True   grpo_beta = 0.1
  num_generations: 4                                 True   num_generations = 4
  reward_fn: accuracy                                True   reward_fn = 'accuracy'
  moe_aux_loss_coeff: 0.01                           True   moe_aux_loss_coeff = 0.01
  target_modules: auto                               True   target_modules = 'auto'

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

$ .venv/bin/python -m pytest tests/test_recipes.py tests/test_v07124.py tests/test_v07130.py \
    tests/test_v07132.py tests/test_recipe_count_is_synced.py tests/test_issue621_recipe_snapshot.py \
    tests/test_issue427_qwen35_text_modality.py tests/test_issue477_qwen38_catalog.py \
    tests/test_cli_startup_is_light.py -q -o addopts=
690 passed, 1 skipped, 2 warnings in 23.53s

$ .venv/bin/python -m ruff check src/soup_cli/ scripts/ tests/
All checks passed!

$ git diff --check          # clean
$ git diff -- tests/ | grep -c "^-[^-]"
8

Those 8 removed test lines, every one accounted for: 3 × def test_catalog_size_is_162 renamed to _163, 4 × assert len(RECIPES) == 162 bumped to 163, and 1 docstring line in test_v07124.py that names test_catalog_size_is_162 by 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

$ soup recipes use glm-5.1-grpo --yes
✓ Recipe glm-5.1-grpo written to soup.yaml

$ soup recipes list | grep glm-5.1
│ glm-5.1-sft   │ zai-org/GLM-5.1 │ sft  │ 754B │ GLM 5.1 MoE SFT  │
│ glm-5.1-dpo   │ zai-org/GLM-5.1 │ dpo  │ 754B │ GLM 5.1 MoE DPO  │
│ glm-5.1-grpo  │ zai-org/GLM-5.1 │ grpo │ 754B │ GLM 5.1 MoE GRPO │

The written soup.yaml reloads through load_config_from_string -> zai-org/GLM-5.1 | grpo | lora r32 | lr 1e-05.

Count sweep — 13 sites

catalog.py comment · 3 renamed test_catalog_size_is_163 methods · 4 assertions · 1 docstring cross-reference · CONTRIBUTING.md · docs/commands.md · docs/serving-and-export.md (×2). Verified with grep -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

  • Not trained. GLM-5.1 is 754B MoE, multi-node. I cannot run it and am not implying otherwise. No hyperparameter here is a measured recommendation — every value is inherited from a shipped sibling or the GRPO template, itemised above.
  • Base repo id verified as resolving onlyhttps://huggingface.co/api/models/zai-org/GLM-5.1 returns HTTP 200. I did not download 754B of weights, so "the checkpoint trains under this config" is unverified.
  • The pytorch-smoke and 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.md collision.

https://claude.ai/code/session_014vWWFgXhj9y46pYCyEjfcy

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 MakazhanAlpamys left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@MakazhanAlpamys
MakazhanAlpamys merged commit 5bce698 into MakazhanAlpamys:main Sep 4, 2026
15 checks passed
MakazhanAlpamys added a commit that referenced this pull request Sep 4, 2026
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>
@MakazhanAlpamys

Copy link
Copy Markdown
Owner

Merged as 5bce698, credited in CONTRIBUTORS at 0adac2a.

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 RecipeMeta.size gap that had no coverage anywhere in the suite — is worth more to me than a table with no survivors in it.

#657 and #658 are next in my queue.

Srinivasan8888 added a commit to Srinivasan8888/Soup that referenced this pull request Sep 4, 2026
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
Srinivasan8888 added a commit to Srinivasan8888/Soup that referenced this pull request Sep 4, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants