feat(recipes): add qwen3.5-35b-a3b-dpo recipe (#276) - #615
Conversation
The catalog shipped the MoE SFT sibling qwen3.5-35b-a3b-sft in v0.71.24 but no preference-tuning variant for the same base. Add qwen3.5-35b-a3b-dpo, pairing the qwen2.5-7b-dpo DPO shape (format: dpo, lr 5e-6, dpo_beta 0.1, preference_train.jsonl) with the sibling's MoE settings (moe_lora: true, moe_aux_loss_coeff: 0.01, LoRA r16/a32, 4-bit, grad-accum 8), and bump the recipe count 159 -> 160 across the three doc-count sites and the four catalog-size checkpoints the sync guard enforces. The entry carries an explicit `modality: text`, taken from the SFT sibling rather than from the qwen2.5-7b-dpo template, which has no modality line: test_issue427_qwen35_text_modality.py holds every Qwen3.5/3.6/3.8 recipe to the measured decoder-only decision instead of letting it fall back to the schema default, and its family audit is registered here so the new sibling joins that contract rather than silently escaping it. Adds TestIssue276Qwen35A3bDpoRecipe (mirrors the MakazhanAlpamys#280 MoE-DPO recipe-test pattern) pinning the exact model id on both RecipeMeta.model and the parsed config.base, the DPO half and the MoE half of the shape, and a check that the DPO variant shares its base and size with the SFT sibling while differing in task -- so a wrong model id or a half-copied shape is caught. Closes MakazhanAlpamys#276 Claude-Session: https://claude.ai/code/session_01S16V9Zwx9TrSEWXwLtyD4z
5c27a34 to
ea0a61a
Compare
|
Full default suite result, as promised in the description rather than left implied. The one failure was mine, and it was an artefact of my own edit rather than of this branch. I reproduced it deliberately rather than assuming that was the cause: Against the committed tree: So: 19351 passed on the branch, the coverage gate cleared at 82.70% against a floor of 77%, and the single red cell does not reproduce on anything that exists in the PR. CI is the independent check on that claim and is running now — if it disagrees with me, believe CI. |
|
@Srinivasan8888 — reviewed. Every claim in your #276 comment holds when checked independently, and the PR is a merge on the substance. Two small things first, and both are cheap enough to land before CI even finishes its current run. Your claims, verified rather than accepted
The mutations — your five, plus three of mine
Splitting the id mutation across its two surfaces is the one I care about: each is pinned independently, so a future edit to one that forgets the other cannot pass. That is precisely the guard #512 was asked for and did not have, and you had it before being asked. The On ANSI: not at risk, and I reproduced rather than assumed it. Under Two things, both one line1. The changelog fragment credits nobody. 2. Two test method names are now stale. ( Merge state
Push those two and I merge on green. And thank you for holding while the #512 overlap was resolved instead of pushing ahead — that made the whole thing easier to settle fairly. |
…MakazhanAlpamys#276) Review follow-up on MakazhanAlpamys#615. The fragment carried no credit. Applied in the house form the other 0.73.3 fragments use -- credit inline in the opening sentence, `(MakazhanAlpamys#276 by @Srinivasan8888 in MakazhanAlpamys#615)`, rather than appended after `Closes MakazhanAlpamys#276`, which would have repeated the issue number twice in one line. test_v07130.py::TestRecipes and test_v07132.py::TestAsrRecipes both assert `== 160` under a method named `test_catalog_size_is_159`. MakazhanAlpamys#582 renamed all three _158 -> _159 copies; this branch had renamed only the test_recipes.py one. A name that contradicts its own assertion is a trap for whoever bumps the count next. test_v07124.py::test_total_recipe_count_is_158 is left alone: it is stale on main already and predates this branch. Refs MakazhanAlpamys#276 Claude-Session: https://claude.ai/code/session_01S16V9Zwx9TrSEWXwLtyD4z
|
Both pushed in 1. Credit. Added — but not in the position you specified, and I want to say why rather than quietly move it. Appending So I restructured the fragment to that form — bolded lead sentence, credit inline, While rewriting I also folded the 2. Stale names. Thank you for splitting the model-id mutation across both surfaces and re-running rather than reading the test names — the On the merge-state note: rebasing onto the four new commits is a one-liner if you want the branch linear before merge — say the word, otherwise I will leave the history as-is. |
|
Merged as What made this merge on the first reviewYou deviated from the issue text and said so before being asked. #276 says to copy The model id is pinned on two independent surfaces. Mutating That is precisely the guard the competing PR for this issue lacked, and you had it before anyone asked for it. You reported a surviving mutation rather than manufacturing a kill. Removing Everything re-checked independently
Eight mutations, eight killed — your five plus three of mine ( ANSI checked rather than assumed: under Two process notesYou held while the #512 overlap was resolved instead of pushing ahead, and you asked about the #379 overlap on #373 before writing code — citing this exact situation as the reason. That is the entire lesson of yesterday applied within hours of it happening, and it is worth more to this project than any single recipe. The changelog credit line and the two stale |
Fills the gap left by the v0.71.24 model-family expansion, which shipped
kimi-k2.6-sft but no GRPO reasoning variant: grpo_beta 0.1, num_generations 4,
reward_fn accuracy, moe_lora, gradient_checkpointing, 4-bit, max_length 8192,
keeping the Modified MIT licence note in the description. Catalog 160 -> 161.
Six mutations, targeted at the new recipe block specifically, six killed by a
named test:
RecipeMeta.model -> Kimi-K2.7 TestIssue281KimiK26GrpoRecipe::test_recipe_loads_with_exact_model_id
+ test_v07124::test_all_recipes_yaml_base_matches_model
YAML base: -> Kimi-K2.7 ..::test_recipe_loads_with_exact_model_id
+ ..::test_show_and_use_recipe
grpo_beta 0.1 -> 0.2 ..::test_recipe_loads_with_exact_model_id
num_generations 4 -> 8 same
reward_fn accuracy -> format same
task grpo -> sft TestRecipeCatalog::test_recipe_tasks_match_yaml + the above
The two model-id surfaces are pinned INDEPENDENTLY -- RecipeMeta.model and the
YAML base: each fail on their own -- so an edit to one that forgets the other
cannot pass. That is the guard #512 was asked for and did not have, and this PR
carried it before being asked. The control mutation was re-run on the rebased
head rather than the reviewed one, since a rebase is a new tree.
Worth recording: the FIRST mutation run reported three of these as surviving.
That was a maintainer error, not a coverage gap -- a replace-first-occurrence kept
hitting the pre-existing kimi-k2.6-sft entry instead of the new block. Re-run
line-targeted, everything dies. Published in the PR thread rather than only the
corrected run.
The rebase was needed because #615 (qwen3.5-35b-a3b-dpo, #276) merged first and
both PRs bump the same catalog counter and append a test class at the same point.
That is merge ordering, not anything about this work: whichever landed second was
going to hit it. Verified after the author's rebase that nothing was lost --
comparing test methods by AST against main, the only absences are the intended
test_catalog_size_is_160 -> _161 renames, and both test classes are present with
all four of their methods.
merge-tree against main 0 conflicts
CI 14/14
ruff clean
The changelog fragment's credit line is corrected in a follow-up commit rather
than by asking the author to push again; crediting the contributor is the
maintainer's job.
Closes #281
Refs #275. Adds the `qwen3.5-9b-dpo` recipe, completing the sft/grpo/dpo trio for that base. Catalog 161 -> 162. Geometry is the SFT sibling's plus the DPO half of `qwen2.5-7b-dpo`: lr 5e-6, dpo_beta 0.1, LoRA r16/a32, 4bit, max_length 4096. All eleven pre-existing DPO recipes are lr=5e-06 / dpo_beta=0.1, so the new one is not an outlier. The model id `Qwen/Qwen3.5-9B` is byte-identical to the two shipped siblings. Seven mutations, seven killed, no survivors. The two that decide the merge are the same pair that decided #615: RecipeMeta.model changed, YAML base: untouched -> 4 failed YAML base: changed, RecipeMeta.model untouched -> 3 failed so an edit that repairs one surface and forgets the other cannot pass. Also killed: dropping `modality: text` (fails the Qwen3.5 family audit by recipe name), reverting either of two independent count sites, `format: dpo` -> `auto`, deleting the entry (11 tests), and keeping the recipe while UNREGISTERING it from `EXPECTED_QWEN35_TEXT_RECIPES` — the last one is the direction that matters for the family contract, since deleting the recipe only proves the easy half. Count synchronisation is complete: `tests/test_recipe_count_is_synced.py` declares five documentation sites and all five are updated; a repo-wide search for the old count returns nothing. The 7 deleted test lines are four count-assertion bumps and three test renames — no assertion weakened. A changelog fragment was missing and is added in a follow-up maintainer commit rather than sent back for another round.
Adds the ready-made
qwen3.5-35b-a3b-dporecipe. The catalog shipped the MoE SFTsibling
qwen3.5-35b-a3b-sftin v0.71.24 but no preference-tuning variant for thesame base.
Closes #276.Overlap with #512 — read this first
#512 by @kok-o covers this issue and has done since 2026-08-23, eight days ahead
of me. I did not see it before claiming: #276 carried no claim comment and #512's
overlap is visible only in its title. @MakazhanAlpamys has since asked @kok-o to
release #276 and told me to open this anyway, with @kok-o keeping the right to
reclaim it today. If they take it back, close this — they filed first and that
counts.
One correction to my own claim comment. I described the
modality: textcontract as something I found. I found it independently, as a red test rather than
by reading — but it is not novel: #512 registered the same entry in
tests/test_issue427_qwen35_text_modality.pyeight days before I started.@kok-o hit that contract first. I would rather say so here than have a reviewer
find it by diffing the two branches.
The one substantive difference between the two implementations
Every DPO recipe in the catalog uses the same learning rate. Measured, not assumed:
Unanimous, 10 of 10. This PR uses
5e-6, matching that and the issue's explicitinstruction to copy the
qwen2.5-7b-dposhape. #512 uses1e-5, which would bethe only DPO recipe in the catalog off the family value. Stating it as a fact for
whoever reviews; it is not an argument about who should land.
Shape
The
qwen2.5-7b-dpoDPO half —format: dpo,lr: 5e-6,dpo_beta: 0.1,preference_train.jsonl— with the MoE settings from theqwen3.5-35b-a3b-sftsibling:
moe_lora: true,moe_aux_loss_coeff: 0.01, LoRA r16/a32, 4-bit,grad-accum 8,
max_length: 4096.modality: textis taken from the SFT sibling rather than from theqwen2.5-7b-dpotemplate, which has no modality line.test_issue427_qwen35_text_modality.pyholds every Qwen3.5/3.6/3.8 recipe to themeasured decoder-only decision instead of letting it fall back to the schema
default, and its family audit fails on any unregistered new sibling. A literal copy
of the template would have shipped a recipe escaping that contract.
Catalog count 159 → 160 across the three doc-count sites and the four catalog-size
checkpoints the sync guard enforces.
Mutations — five killed, one equivalent
Qwen3.5-35B-A3B→Qwen3.5-35B-A3)modality: textremoved (falls back to schema default)format: dpo→format: autodpo_beta: 0.1droppedThe survivor is not a coverage gap and I am deliberately not killing it.
dpo_betadefaults to
0.1atsrc/soup_cli/config/schema.py:1237:so removing the line produces a byte-identical
SoupConfig— there is no behaviourleft to detect. Killing it would require asserting on the YAML source text rather
than on the loaded config, and a test that pins source strings instead of behaviour
is a shape this project has removed from its own suite before.
glm-5.1-dpohas the identical property. That makes it a repo-wide question aboutwhether explicitly-declared recipe values should be pinned against drift in schema
defaults — not something to answer asymmetrically inside this PR. Per
@MakazhanAlpamys on #276, it stays unaddressed here; happy to open it as its own
issue if it is wanted.
Verification
All five removed test lines are the
159 → 160count-bump sites themselves(
assert len(RECIPES) == 159×4 and thetest_catalog_size_is_159rename). Notest deleted, skipped or weakened.
Qwen/Qwen3.5-35B-A3Bresolves on the Hub (/api/models/...→ 200); it is alsothe id the SFT sibling already ships.
soup recipes showandsoup recipes usewere both run live, not only asserted on.
The full default suite with the
--cov-fail-under=77gate is still running locallyat the time of opening; I will report the result in a comment rather than leave the
claim implied. Everything above is from runs that completed.