What's New
Closes the optimizer-breadth gap with LlamaFactory + Axolotl in a single release.
- 14 new optimizers —
BAdam,APOLLO(apollo_adamw),Adam-mini,lomo/adalomo,grokadamw,schedule_free_adamw/schedule_free_sgd,muon,dion,came_pytorch, plus TorchAOao_adamw_{fp8,4bit,8bit}. Closed allowlist rejects typos at config-load time with an actionable error. - Per-module LR groups —
training.lr_groups: {q_proj: 1e-4, mlp: 5e-5}(or list-of-pairs / list-of-dicts). First-match-wins routing; remaining params fall through to the base lr. Capped at 32 entries with regex / null-byte / NaN-Inf hardening + best-effort ReDoS probe. PyYAML scientific notation (1e-4without dot) is auto-coerced. - LoftQ quantization-aware LoRA init —
training.lora.init_strategy: loftq(withloftq_iterandloftq_bits ∈ {2, 4, 8}) jointly initialises A/B with low-bit base weights. Composes with QLoRA for stronger adapter quality on aggressive quantization. - LLaMA Pro block expansion + Mixture-of-Depths schemas —
training.expand_layers(1-64) +training.freeze_trainable_layers(signed, |x| ≤ 1000) +training.use_mod. Schema fields ship in v0.41.0 to lock the YAML surface; full live wiring lands in v0.41.1 (mirrors v0.27.0 / v0.37.0 stub-then-live releases). - Friendly aliases —
training.load_in_8bit: true/load_in_16bit: trueremapquantizationfor users coming from LF / Axolotl conventions. Mutually exclusive; rejected when combined with explicit Quant Menu formats. - +120 net new tests — 5122 → 5242. Covers optimizer allowlist, lr_groups parsing/runtime/schema-roundtrip, LoftQ + LLaMA Pro validators, alias remap rules, frozen
LrGroupdataclass, ReDoS / bool-as-int / null-byte / YAML-string-coercion hardening.
Install / Upgrade
pip install --upgrade soup-cli
# or pin
pip install soup-cli==0.41.0Security
optimizerfield uses a closedfrozensetallowlist (HF-native + bnb-backed + 14 v0.41.0 additions). Non-string / empty / null-byte / >64-char rejected at schema-load._OPTIMIZER_PACKAGESwrapped intypes.MappingProxyTypeso the registry cannot be mutated at runtime (matches v0.36.0_REGISTRYpolicy).lr_groupspatterns: ≤256 chars, no null bytes, mustre.compile, plus a best-effort ReDoS probe (compiled.search("a"*128)) catches catastrophic-backtracking patterns at schema-load. LR values:(0.0, 1.0]bounds +math.isfinite(NaN AND ±inf rejected) + bool rejection. Duplicates rejected.LrGroupis@dataclass(frozen=True).expand_layers/freeze_trainable_layersusefield_validator(mode="before")soboolis rejected BEFORE Pydantic'sge/lesilently coercesTrueto1(matches the project bool-as-int policy applied since v0.30.0).load_in_8bit/load_in_16bitaliases use theis Truepolicy: explicitFalseis treated as "no preference" (never silently rewrites the field). Mutually-exclusive both-True rejected. Combining alias=True with an explicit Quant Menu format raises rather than silently overriding the user's pick. Alias-driven rewrite uses directself.quantization = ...(Pydantic v2 BaseModel non-frozen path), NOTobject.__setattr__— code review caught that the latter would silently bypass any future field validator onquantization._count_layersuseshasattr(layers, "__len__")instead oftry/except TypeErrorso legitimate__len__bugs surface loudly.
Known Limitations
- LLaMA Pro live wiring deferred to v0.41.1 —
expand_model_blocksraisesNotImplementedErroron non-zero block counts; the schema fields lock the YAML surface so v0.41.1 can land the trainer-side wiring (zero-init weights, freeze original layers, route through HF Trainer) without breaking config compat. Same stub-then-live pattern as v0.27.0 MII / v0.37.0 multipack. - Mixture-of-Depths live wiring deferred to v0.41.1 —
use_mod=Trueparses but the actual selective-token routing patch lands in v0.41.1. - Optimizer dependency check is advisory —
validate_optimizer_nameaccepts every entry in the allowlist regardless of whether the underlying package is installed; a friendly "pip install <required_package(name)>" advisory fires at trainer-construction time. Schema-load happens before deps can be probed and CI environments often lack optional optimizer packages. load_in_8bit/load_in_16bitrewritequantizationonly when set toTrue— explicitFalseis intentionally a no-op (treated as "no preference") so a YAML withload_in_8bit: falseandquantization: 4bitstill trains in 4-bit.
🤖 Generated with Claude Code