Commit Graph

74 Commits

Author SHA1 Message Date
Alpamys 9117da1054 docs: soup train has no --task flag
`soup train --help` contains no `--task`; the task is a `soup.yaml` key. Two
places said otherwise, and both read as an instruction rather than as history:

- `docs/commands.md` listed `soup train --task unlearn` in the command
  cheat-sheet, where lines are copied straight into a terminal. Rewritten to
  `soup train  # task: unlearn`, the form the RAFT line directly below it
  already uses.
- `docs/training.md` said "`soup train --task unlearn` is live", two lines
  under a code block that correctly runs `soup train --config unlearn.yaml`.
  Now `task: unlearn`.

Checked every other `--task` in docs/: all 25 remaining are real flags on other
commands (`infer`, `data forge`, `bom emit`, `recipes search`, `eval coverage`,
`ship --task-eval/--task-mode`). Verified against the registered options, not
by reading a diff.

Left alone deliberately: CHANGELOG.md and benchmarks/gate-v0.72.4 also carry
the phrase, but they describe which code path broke rather than telling anyone
what to run, and the gate records are published verbatim on purpose. Four
docstrings in src/ and two in tests/ are narrative, not emitted to users.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:29:24 +05:00
Alpamys 8f9c8435b1 chore: apply pre-commit whitespace fixes
Output of the first `pre-commit run --all-files` after the hooks were finally
activated in d0b7c62: trailing whitespace stripped and end-of-file newlines
normalised across 18 files.

No semantic change — `git diff --ignore-all-space --ignore-blank-lines` over
the whole set is empty. Committed as one batch on purpose: left alone, these
would surface as unrelated noise in the next real diff to each of these files,
since the hooks now fix them on the way past.
2026-08-05 02:48:49 +05:00
Alpamys 115f29df15 docs(readme): show the run, not just the claim
Visitors arrive from the Show HN headline and the first screen is all prose.
Adds a 14.5s GIF directly under the 8B-on-4GB claim, above "Why Soup?".

Segment 44.0-60.0s of the demo, chosen off the .vtt cue list and confirmed
frame by frame rather than from the brief's estimate: the scene cut sits
between 47 and 48s, so the clip opens on 3.5s of the static "Layer streaming
BETA" pre-flight panel (3.60 GB store across 32 layers, 2 x 113 MB VRAM
buffers, Training started!) and then runs the measurement card up to its
settled 3.32 GB / 119.6 tok/s. Both halves read without sound or context.

Encoding: two-pass palettegen/paletteuse so terminal colours survive, 960px
wide, 10 fps, dither=none, diff_mode=rectangle -> 4.63 MB. Readability was the
binding constraint, so the budget was met by cutting duration (16s -> 14.5s)
and fps (12 -> 10) rather than width; a bayer-dithered cut of the same clip
came to 5.08 MB and was dropped. Panel lines verified legible by opening the
generated GIF, not assumed from the source resolution.

Caption numbers are from benchmarks/gate-v0.72.2-nf4.md line 314 (the
re-measurement through shipped code), matching the claim line above it.
"What's New" untouched. README 440 -> 445 lines.
2026-08-04 23:50:07 +05:00
Alpamys 6cb84e7973 fix(deps): both trl bounds were wrong — >=0.14.0,<0.27, settled by construction
v0.72.4 capped `trl<0.25` from a staged-removal table that scored a MODULE
RELOCATION as a field removal. At 0.25 `BCOConfig` moved into
`trl/experimental/bco/`, and at 0.26 kto/orpo/cpo followed — but every one of
them stayed publicly re-exported from `trl` with `max_prompt_length` intact.
Seeing a config file vanish from `trl/trainer/` was read as the field going
away.

Re-derived by parsing each *Config class's own annotated fields across every
wheel 0.24.0 -> 0.29.1 (all five inherit TrainingArguments, so there is no
inherited-field escape hatch):

    version          dpo   kto   orpo   cpo   bco
    0.24.0 - 0.26.2  yes   yes   yes    yes   yes
    0.27.0 - 0.27.2  yes   NO    yes    yes   yes
    0.28.0           yes   NO    NO     NO    NO
    0.29.0 - 0.29.1  NO    NO    NO     NO    NO

Then settled the way reading source cannot — by CONSTRUCTING all six configs
with the exact kwargs the wrappers pass:

    trl 0.26.2   OK dpo · OK ipo · OK kto · OK orpo · OK simpo · OK bco
    trl 0.27.0   FAIL kto: KTOConfig.__init__() got an unexpected keyword
                           argument 'max_prompt_length'
                 OK dpo · OK orpo     <- control: the boundary is KTO's alone

So the cap is <0.27, and 0.25.0/0.25.1/0.26.0/0.26.1/0.26.2 were excluded for
no reason. That is the third wrong answer this bound has had (after "trl 1.x
removed them" and "the break is 0.29.0"), and the first two were also produced
by reading rather than running. The rule this earns: a version bound derived
from source is a hypothesis; the experiment that settles it is constructing the
object.

Two more corrections from the same pass:

- The floor >=0.7.0 was impossible, and nobody checked it while carefully
  fixing the ceiling. `setup()` imports GRPOTrainer unconditionally and trl
  first exports it at 0.14.0 (OnlineDPO / KTO / BCO / BasePairwiseJudge at
  0.11.0; 0.7.0 has none of them). Resolvers pick the newest allowed version,
  so it only bit under a constraints file or anyone reading the metadata as a
  support claim.

- ORPOConfig/CPOConfig are not "deleted" at 0.29 — the modules survive under
  trl/experimental/. They, BCOConfig and their trainers are dropped from the
  public `trl` namespace, which is what Soup imports, so the 0.29 break is an
  ImportError rather than a rejected kwarg. Harder than described, not milder,
  and it means going past 0.29 needs an import strategy (#326).

Closes the blind spot that let this ship, for all six trainers
(tests/test_trl_preference_config_contract.py)

The bug was invisible because the trl imports and the config construction live
inside `setup()`, and constructing a wrapper touches trl not at all. v0.72.4's
streaming suite closed that for four of the six; tests/test_bco.py:266 and
tests/test_ipo.py:191 still MOCK `.setup` out, so bco and ipo had no test that
executed it. The new file drives the real `setup()` for all six on the ordinary
non-streaming path, asserts the BUILT config carries max_prompt_length/
max_length (so a trl release that accepts the kwarg and stops storing it fails
too), and derives the covered set from the trainer sources so a seventh trainer
adopting the argument joins automatically.

Revert the KTO skip: it blamed the device, and the variable is the torch version

`skipif(not cuda)` on test_kto_streams_at_batch_two made the test dead in CI —
there are no GPU runners (ubuntu/windows/macos) — leaving it alive only on this
box, under the one torch where it passes, while torch carries no upper bound.
Three things say the rationale ("a streamed model on CPU is a test convenience")
does not hold:

- the same CI run had test_v07200.py::test_one_training_step_actually_runs, the
  identical streamed train() for SFT, pass on that CPU runner;
- running this body here with CUDA masked passes (torch 2.5.1);
- the error comes from check_same_device, i.e. an op received a meta
  placeholder next to a real tensor — a streaming property. Newer torch
  decomposes more ops, which is why only the newer stack surfaces it.

Now it runs everywhere and xfails on exactly that signature on CPU; anything
else, and the same signature on CUDA, is a hard failure. Real defect filed as
#328. With CUDA hidden: 21 passed, 2 skipped (both genuinely CUDA-only).

Also fixed
- pyproject.toml: 14 double-encoded em-dashes, one of them the `unit` marker
  description that `pytest --markers` prints to users. The existing mojibake
  guard covered only the package docstring; widened to the file, which is not
  importable Python and so was invisible to every source-level check.
- docs/commands.md called itself "the full soup command list" while missing
  eight, three of them glued onto the end of a previous line by absent newlines
  and therefore invisible on the rendered page. Now 77/77, asserted against the
  live Typer app so the claim stays true by construction.

Verified: ruff clean; full suite 16935 passed / 129 skipped / 0 failed (13:15);
wheel builds with Requires-Dist: trl<0.27,>=0.14.0; `pytest --markers` renders
the em-dash. No version bump and no tag — the packaging metadata change wants
CI on the resolver stack before it rides a release.
2026-08-03 22:36:39 +05:00
Alpamys 4fb25404f9 feat(streaming): preference losses over layer streaming (v0.72.4)
DPO / ORPO / SimPO / KTO join task=sft on the layer-streaming engine.

The reference model is the SAME streamed base with adapters disabled — one set
of weights, one stream. Measured: streamed DPO peaks at 0.914x streamed SFT with
a byte-identical store and pool, where forcing a real second instance costs
+730.44 MB against 730.44 MB of weights. All four are bit-exact (0.0) against a
resident run of the same loss.

KTO is NOT reference-free: kto_trainer.py:466-476 is byte-for-byte DPO's
three-branch reference selection, so it was gated separately. It also requires
batch_size >= 2, refused at parse time rather than minutes into sharding.

grpo/ppo stay excluded permanently — rollouts re-read every layer per generated
token. The refusal deliberately names no release.

The ~390-line streaming setup moved verbatim into trainer/stream_setup.py so
five wrappers cannot drift. _STREAM_ROWS_PER_EXAMPLE is 2 for the concatenating
losses and 1 for KTO, measured not assumed: the VRAM pre-flight would otherwise
under-predict by half, and on Windows that is a silent spill, not an error.

Honest cost: the reference is free in memory, not in time — DPO reads the layer
stack 1.52x as often per step as SFT.

Also closes five holes in the release checklist itself:
- benchmarks/ was never in it, so gate records (which live under a gitignored
  .claude/) were never published. The public record behind the preprint DOI was
  about to fall a release behind; benchmarks/gate-v0.72.4-preference-losses.md
  and its index row are here, and the checklist now names the step.
- tests/test_version_sync.py asserts pyproject.toml == __init__.py. Every other
  version test in the suite is a >= floor check, so bumping one and forgetting
  the other kept CI green. Verified red-green.
- .claude/paper/ (the DOI preprint) had no "did this release change what it
  claims?" step. For v0.72.4 the answer is no: no measured number moves and its
  task: sft configs stay valid.
- The Docs section header said steps 7-12 while containing 7-13, so plan.md sat
  outside its own section.
- The README size anchor said ~238 lines against a real 426.

Notes for whoever hits these next:
- `pre-commit run --all-files` rewrites ~740 files here (ruff-format on
  pre-existing code). No pre-commit git hook is installed and CI runs only
  `ruff check`, so run it with --files on your own paths or the diff explodes.
- Measuring streamed peak VRAM across setup() charges the pre-flight's own GEMM
  probe (three 4096^3 matrices, ~100 MB) to the step. Reset the peak counter
  after setup.
- The buffer pool is freed by cycle collection, not by close(): back-to-back
  streamed runs in one process retain the previous pool (+47.65 MB measured)
  until a gc pass. Call gc.collect() between arms when measuring.
- A resident model built from a float32 fixture vs a bf16 streamed one measures
  the dtype gap, not streaming — that cost an hour chasing a 9.96e-04 "failure".
- Two concurrent pytest runs on a 4 GB card produce false CUDA failures; run the
  suite alone.

Tests: 16977 -> 17051.
2026-08-03 18:38:13 +05:00
Alpamys bcbf72e586 feat(train): layer streaming breadth — 6 more archs, bigger batches, resume, disk tier (v0.72.3)
Lifts the v0.72.0-.2 scope freeze. Every capability was gated against a
streamed-vs-resident bit-exactness reference before it was written.

- Six more families (mistral/gemma/gemma2/gemma3_text/phi/phi3), each
  bit-exact vs the same checkpoint loaded resident, under bf16 AND NF4.
  Multimodal gemma3 stays refused — only gemma3_text.
- batch_size > 1 and gradient_accumulation_steps > 1 now work.
- A batch- and vocab-aware VRAM pre-flight that refuses a run predicted
  not to fit. Fitted to 10 real runs: worst error 0.85%, never
  under-predicts. On Windows an over-budget step does not OOM; WDDM
  spills silently, so the estimator is the only guard.
- A throughput bracket from a GEMM ceiling measured on the user's own
  card in the same session, printed with the SM clock.
- --resume / --hf-resume: load_state_dict narrows keys by child name, so
  a canonical checkpoint matched 0 of N tensors and PEFT warned only.
  Keys are now redirected at load time, mirroring the v0.72.1 save fix.
- An NVMe disk overflow tier (stream_source: auto|ram|disk), bit-exact
  against the RAM tier. Its speed relative to RAM is UNMEASURED here and
  no figure is claimed.
- soup doctor --disk reports the detected media type.

Fixes: estimate_logits_bytes charged 6 bytes/element where the measured
peak is 14; the NVMe tier guard was wired to a hardcoded constant;
streaming sources leaked handles when training raised; subprocess
helpers resolved tools by bare name (CWE-427 on Windows).

112 tests in tests/test_v07203.py; 16867 -> 16977.
2026-07-28 23:15:20 +05:00
Alpamys 1c7540c314 docs: fix two broken anchor links in the layer-streaming pages
The v0.72.2 heading change ("Layer Streaming (BETA, v0.72.0)" ->
"... v0.72.0; NF4 v0.72.2") moved its anchor, leaving the inbound pointer from
docs/training.md dead. Repointed, and took the opportunity to mention that
`quantization: 4bit` is what makes 8B fit a 4 GB card.

Also fixes a PRE-EXISTING dead self-link: the TOC entry for "FP8 Attention +
NVFP4 + Native unsloth_bnb_4bit" carried a "(v0.53.0)" suffix its heading never
had, so that anchor has never resolved.

Verified by walking every `performance-and-quantization.md#...` reference in
docs/ against the file's real headings, using GitHub's slug rule (each space
becomes one hyphen, so punctuation leaves a double hyphen — a naive
whitespace-collapsing slugger reports seven false positives here). 0 broken.

Docs-only: no version bump, no tag, does not ship to PyPI.
2026-07-28 16:29:09 +05:00
Alpamys 08343f8f35 feat(train): NF4 layer streaming — fine-tune Llama-3.1-8B on a 4 GB card (v0.72.2)
Layer streaming (v0.72.0) was bf16-only, capping it near 3B on a small card.
Quantising the streamed base to NF4 makes the RAM store ~4x smaller, which is
what brings 8B within reach.

Measured on a 4 GB RTX 3050 Laptop through the shipped code (50 steps after 10
warm-up, batch 1, S=512, PagedAdamW8bit, GEMM ceiling taken in the same session):
  Llama-3.1-8B-Instruct  119.6 tok/s  3.32 GB peak  3.60 GB pinned  100%  952 MHz
  Qwen2.5-3B             264.2 tok/s  1.76 GB peak  1.43 GB pinned  100%  960 MHz

3B is 1.85x the bf16 path, but that is PINNING, not arithmetic: 1.43 GB
page-locks where 5.55 GB did not, restoring async copy_ (util 79.3% -> 100%).
14B was not run — its store exceeds this box's measured 7.12 GB pinned ceiling.

A streamed NF4 run is bit-exact against a RESIDENT NF4 run, now as CPU-runnable
CI tests rather than only a gate result.

Notable, because each fails silently:
- PEFT dispatches lora.bnb.Linear4bit only when is_loaded_in_4bit is stamped;
  without it the generic lora.layer.Linear runs against a Linear4bit base and
  casts differently (9.375e-01 logit divergence, no warning). Pinned by a test
  with a control that deletes the marker.
- hf_quantizer must be stamped too, or Trainer.__init__ dies formatting its own
  "cannot fine-tune" error. Found by the end-to-end test.
- The shard cache is keyed on quant/double_quant/quant_device as well as dtype
  and source fingerprint; a bf16 cache reused for an NF4 request would feed
  full-precision bytes to matmul_4bit.
- index.json is a trust boundary: its shape/blocksize reach bnb kernels that do
  not bounds-check, so from_json validates and the runtime cross-checks the
  claim against the bytes on disk.
- A streamed NF4 model over-reported parameters ~6.5x (878,154,048 vs
  134,515,008 for SmolLM2-135M). Display-only; ~52 B at 8B.

Scope unchanged and still BETA: RAM tier, sft, Llama/Qwen, batch 1, no
accumulation, no resume. quantization values other than none/4bit are refused.

Tests: 16752 -> 16840 (+88 in tests/test_v07202.py).
Full suite: 16734 passed, 129 skipped.
2026-07-28 15:15:20 +05:00
Alpamys af8683b61d fix(train): streamed adapters were saved unloadable (v0.72.1)
v0.72.0's layer-streaming wrapper holds the real decoder layer as a child
named `inner`, so every saved LoRA adapter key carried an `.inner.` segment.
Such a file reloads as ZERO tensors into any normal model: soup merge,
soup serve, soup chat and PeftModel.from_pretrained all returned the untuned
base while PEFT emitted only a UserWarning. Training was correct; only the
artifact was inert.

StreamedDecoderLayer.state_dict() now delegates to the wrapped layer at the
wrapper's own prefix, so every artifact path -- the final trainer.save_model(),
each save_steps checkpoint, and therefore soup adapters, the Registry, merge
and serve -- becomes canonical at once. Serialisation-only by design: the
forward path is untouched, so v0.72.0's bit-exactness gates remain valid
without being re-earned.

Also fixes --hf-resume bypassing the streaming resume refusal. The guard
tested only --resume, while --hf-resume reaches resume_from through another
branch. Pre-fix that combination matched keys by accident; post-fix it would
have matched nothing and silently continued training with a freshly
initialised adapter -- i.e. the adapter-key fix alone would have made that
one path worse.

Roadmap renumbered (this release was inserted ahead of NF4): every
"lands in vX.Y.Z" refusal corrected -- NF4 v0.72.2, disk tier / more
architectures / larger batches / gradient accumulation / checkpoint-resume
v0.72.3, preference losses v0.72.4.

Found by the v0.72.2 NF4 gate, not by the 159 v0.72.0 tests -- none of them
saved an adapter and loaded it back. The new regression test does exactly
that, by count, by name and by value, with a negative control that re-mangles
the keys and asserts the reload yields zeros (0-of-N loading raises nothing,
so a green round-trip without the control proves nothing).

Tests: +17 in tests/test_v07201.py (16735 -> 16752).
Full suite: 16623 passed, 129 skipped, 4 deselected.

Note for maintainers: do not edit soup_cli/__init__.py while a suite is in
flight -- a mid-run version bump made test_cli_subprocess::test_version fail
spuriously (the subprocess and the imported constant disagreed).
2026-07-27 23:18:11 +05:00
Alpamys 4dae34a6a4 docs(v0.72.0): list layer streaming in the README docs index and commands reference
Two spots the release missed: the README's own docs-index row for
Performance & quantization (docs/README.md's equivalent row was already
updated), and docs/commands.md, which lists config-driven training
features in the same style as LISA and Spectrum.

Docs-only — no version bump.
2026-07-27 01:06:53 +05:00
Alpamys 00833ac789 feat(train): layer streaming — fine-tune models larger than VRAM (v0.72.0 BETA)
The frozen base lives in CPU RAM and is streamed into a small pool of
pre-allocated VRAM buffers one decoder layer at a time, so peak VRAM is
bounded by ONE layer instead of the whole model. Only the LoRA adapters,
their gradients and optimizer state stay resident.

Measured on an RTX 3050 Laptop 4 GB (Windows, 16.9 GB RAM), batch 1,
gradient checkpointing on, 50 steps after 10 warm-up:

  Qwen2.5-0.5B  S=512   978.6 tok/s  91.4% util  1.47 GB peak
  Qwen2.5-1.5B  S=512   525.0 tok/s  96.8% util  1.82 GB peak
  Qwen2.5-1.5B  S=1024  487.6 tok/s  96.7% util  2.96 GB peak
  Qwen2.5-3B    S=512   143.1 tok/s  79.3% util  2.15 GB peak

Qwen2.5-3B trains in 2.15 GB on a 4 GB card where a resident run OOMs.
Honest cost: 1.43x slower than resident, measured at 0.5B — the only
apples-to-apples comparison available on this box, because 1.5B and above
cannot run resident here at all.

Correctness was gated before any src/ code was written: streamed vs
resident logits are bit-exact (max abs diff 0.0), the layer-0 LoRA
gradient is non-zero on all layers, a 100-step loss curve matches
resident exactly, and same-seed runs are identical.

New:
- utils/layer_stream.py          pure planner (no top-level torch)
- utils/layer_shard.py           per-layer safetensors sharder
- utils/layer_stream_runtime.py  buffer pool, RAM source, prefetch, wrapper
- training.stream_layers / stream_source / stream_buffers

Notes for future maintainers:
- transformers' Trainer.__init__ and accelerate's prepare_model BOTH call
  model.to(), which raises NotImplementedError on meta parameters. The
  streamed layer overrides _apply to pass meta tensors through, and the
  model declares hf_device_map. Without either, every run dies at trainer
  construction — no unit test that stops at model(input_ids=...) sees it.
- The shard cache is keyed to a fingerprint of the source checkpoint, not
  just the model slug: a base retrained in place must re-shard rather than
  silently stream stale weights.
- The pre-flight hardware-fit gate models a RESIDENT run, so it is skipped
  for streaming — otherwise it refuses exactly the runs this enables.
- expandable_segments:True is silently ignored on Windows; probed, not
  claimed.

Scope (every refusal names the release that lifts it): RAM tier, bf16,
task=sft, Llama/Qwen, batch 1, no gradient accumulation, no --resume.
NF4 is v0.72.1; disk tier / bigger batches / accumulation / resume are
v0.72.2. Proof-of-mechanism at 3B — nothing above 3B was measured.

Tests: 16576 -> 16735 (+159 in tests/test_v07200.py)
2026-07-26 23:58:06 +05:00
Alpamys 03282e9ef6 feat(reward): soup reward stress — adversarial verifier gameability probe (v0.71.41)
Turn the reward-hacking detector on the verifier itself: feed empty /
length-padded / repetition / sentinel-spam completions and flag any the
verifier accepts. Loads via the existing load_reward_fn (probes a synth .py
or a builtin); a gold-requiring target with no --references is a hard error,
never a false "robust". Exit 0=robust / 2=gameable / 1=error. Pure, offline,
no schema change, no new deps.

Also corrects the ops-docs Telemetry section (the sender exists but is wired
to nothing — no data is sent). Telemetry flywheel deferred pending a public
privacy policy.

Tests: 16490 -> 16529 (+39). 5 sequential ECC reviews, every finding fixed.
2026-07-19 20:53:37 +05:00
Alpamys ea3325ea09 docs: sync CONTRIBUTING structure + docs index for v0.71.40
Add utils/reward_synth to the CONTRIBUTING module list, fix the stale "Test
Files (313 -> 318)" count, and add reward-verifier synthesis to the training
docs-index row. Docs-only; no version bump.
2026-07-19 19:17:16 +05:00
Alpamys 43a7fb21a5 feat(reward): soup reward synth — synthesize a deterministic reward verifier (v0.71.40)
Point `soup reward synth <refs.jsonl> -o reward.py` at reference (gold) outputs and it
infers a deterministic verifier (numeric / json_schema / regex / tool_call), emits a
readable, committable .py reward_fn that rides load_reward_fn's existing .py path (no new
exec surface), and — the moat — REFUSES to emit one that can't discriminate its references
from auto-perturbed negatives via a mandatory calibration report (accept refs >=90% AND
reject negatives; hard floor at discrimination<=0). Nothing in TRL/Unsloth/Axolotl
synthesizes a reward.

Fixes #311: a comma-separated reward_fn ("accuracy,format") now loads as a reward ensemble
(GRPOTrainer reward_funcs=[...], unlocks the rm_ensemble detector), GRPO-only and validated
at config-parse; the deepseek-v3-reasoning recipe that shipped this previously crashed with
"Unknown reward function".

Riders: reward_fn field-validator (null-byte/blank/oversize/empty-comma-segment); comma-aware
verifiable-domain check; envs/calculator + guess_number docstrings corrected.

5 sequential ECC reviews, every finding fixed (python HIGH PPO gate; code 2xHIGH per-tool
arg binding + json_schema mixed-shape refuse; security HIGH rel_hint codegen injection; tdd
8xHIGH). Live smoke on RTX 3050: synth from envs/calculator agrees with math_verify;
degenerate refused (exit 2); real GRPO on SmolLM2-135M with reward_fn=accuracy,format
completed optimizer steps. +103 tests (tests/test_v07140.py); 16387 -> 16490.
2026-07-19 18:50:47 +05:00
Alpamys cba984a574 feat(ship): close the evidence loop — emit-evidence + config + provenance + PR comment (v0.71.39)
soup ship's verdict is now emittable, committable, reviewable, and provenance-bound
so a fine-tuning gate runs on every PR:

- --emit-evidence: verdict_to_evidence re-serialises scores into the --evidence
  INPUT schema (output replayable as input, #312)
- ShipConfig under eval.ship + --config: committable gate policy read with
  CLI > config > default precedence (Click ParameterSource)
- --push owner/repo#N: verdict as a GitHub PR comment (best-effort, reuses
  adapter_pr.post_pr_comment; never flips the SHIP/DON'T-SHIP exit code)
- provenance/staleness: --config+--emit-evidence STAMPS config_sha (eval.ship
  excluded from the recipe hash) + base_model + data_sha; --config+--evidence
  GATES, refusing drifted/absent config_sha (exit 3)
- soup ci init --config binds the generated gate to the committed config

Security: config_sha shape-gated before echo (ESC hygiene); data_sha via
O_NOFOLLOW + symlink guard + 8 GiB cap; ci path rejects '#' + NEL/LS/PS
(YAML plain-scalar run: truncation).

16330 -> 16387 tests (+57 tests/test_v07139.py).
2026-07-19 12:30:35 +05:00
Alpamys f6bc8e7bdd feat(ship): make soup ship's leg-2 regression gate real (v0.71.38)
soup ship's leg 2 — the catastrophic-forgetting / regression gate that carries
the whole SHIP / DON'T-SHIP claim — was 15 trivia prompts scored by raw
substring containment (it credited "B" for "Berlin", "3" for "13") with zero
coverage for tool-calling, safety, or JSON. This makes the gate real.

- forgetting.py: score_answer/extract_mcq_letter replace the substring scorer
  with answer-extraction (cue -> paren -> clause-terminating bare letter) +
  boundary-aware token match. MINI_BENCHMARKS expanded (mmlu 26 / common_sense
  24 / instruction 24) + new mini_arithmetic (36) so a 1-item flip trips 0.05.
  BREAKING: an existing run's verdict can change (the old gate under-reported).
- eval/gate_suites.py (new): bundled offline general-suite registry, no torch.
  DEFAULT_GENERAL_SUITE = the 4 MCQ suites + 3 behavioural JSONL suites
  (mini_tool_call / mini_format_json / mini_safety) scored per-model-absolute
  by the pure custom/diagnose scorers. _fraction_passing isolates a per-item
  scorer exception (deep-JSON RecursionError scores as a failed item).
- ship.py: leg-2 scores bundled suites offline (base+tuned) before routing any
  non-bundled name to lm-eval; default general suite = the full bundled set.
  Exit-code taxonomy: usage errors move 2 -> 3 so exit 2 means only DON'T-SHIP
  (a typo'd flag was previously indistinguishable from a caught regression).
- diagnose/__init__: "Six" -> "Seven" probes + re-export all 7 score_* fns;
  removed the dead SUPPORTED_TASK_MODES "pairwise reserved" gate.
- Bundled gate fixtures ship in the wheel via the pyproject artifacts glob.

Every bundled item is original, hand-authored (no MMLU/GSM8K rows copied).
Test count 16288 -> 16330 (+42 in tests/test_v07138.py).
2026-07-17 22:42:47 +05:00
Alpamys a95fedeb0e fix(cli): quote install hints so `pip install soup-cli[extra]` works on cmd.exe (v0.71.37)
Every printed and documented `pip install 'soup-cli[extra]'` was bash / zsh /
PowerShell syntax and failed on Windows cmd.exe:

    ERROR: Invalid requirement: "'soup-cli[train]'": Expected package name at
    the start of dependency specifier

cmd.exe has no single-quote quoting, so it passes the quotes to pip verbatim
and pip rejects the requirement. Nothing in Soup can fix that once the command
is typed -- pip and the shell own it, and Soup is not installed yet when the
README line runs -- so the fix is the spelling we print.

Migrated 147 sites across 67 files to `pip install "soup-cli[extra]"`:
  - 64 in src/  (Rich console hints + plain ImportError text)
  - 57 in README.md + docs/
  - 22 in src/soup_cli/templates/*.yaml + examples/configs/*.yaml
  -  3 in examples/README.md

Double quotes are the only spelling valid in every shell (cmd, PowerShell,
bash, zsh), which is why the repo already used `pip install -e ".[dev]"`.
Measured on Windows: single quotes fail ONLY on cmd; double quotes pass
everywhere; bare passes on Windows but zsh globs `[extra]` and fails.

Method note (the PR #247 class): the hints sit INSIDE double-quoted Python
string literals, so a blind ' -> " sed produces SyntaxError. A tokenize-based
rewriter escaped `\"` in DQUOTE tokens and left bare `"` in TRIPLE / COMMENT
tokens; every touched .py was compile-checked. The full suite (not ruff, not
compile-check) caught two rewriter blind spots: the real YAML templates under
src/soup_cli/templates/ (byte-identical drift test) and examples/README.md.

A regression test (tests/test_v07137.py) scans the package and every docs code
block for the single-quoted form; prose may still name it so a reader from an
older tutorial recognises the error.

Also bundles #315 (@Sanjays2402): eval-gate benchmark tasks now run via
ForgettingDetector instead of a helper that never existed. Closes #310.

Test count: 16283 -> 16288 (+4 in tests/test_v07137.py).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 20:40:54 +05:00
Alpamys 55dd6301b5 docs: v0.71.36 Data Moat II (version bump + CHANGELOG + docs)
Version 0.71.35 -> 0.71.36 in pyproject.toml + __init__.py.

Docs are written around what the live smoke MEASURED, not what the plan
assumed:

- The semantic-dedup headline is WITHDRAWN. docs/data.md leads with the
  measured overlap: paraphrase cosines (0.49-0.76) overlap genuinely-distinct
  rows (0.54-0.76), and "Add two numbers"/"Multiply two numbers" (0.759)
  scores HIGHER than the true paraphrase "reverse a string"/"invert the order
  of characters" (0.491). No threshold separates them, so the page says
  plainly that lowering --threshold trades duplicates for silent data loss,
  and explains why the 0.8 default is deliberately conservative. The claim it
  does make -- catches REWORDINGS MinHash's shingling misses (0.88-0.91) --
  is the one the numbers support.
- docs/data.md canary section states the real verdict rule (binomial tail
  over the count, not any-single-canary) and why: at K=16 an any-canary rule
  fires on a CLEAN model ~15% of the time.
- docs/training.md --replay reports the honest result: 7% better retention
  than control, but forgetting without it was only +4% (mild), so it is
  proof-of-mechanism at 135M + LoRA, not a production claim.
- CHANGELOG known-limitations carry the same numbers rather than hedging.
- README What's New leads with the two blocking bugs the smoke found (the
  hardware-fit gate refusing locally-merged models; the [extra] hints
  printing without the extra).

Counts 16001 -> 16254 tests, 313 -> 314 files (CONTRIBUTING).
2026-07-17 12:03:15 +05:00
Alpamys aa347222a6 docs: close the v0.71.35 checklist gaps (index row, toolchain, stale counts)
Docs-only — no src/ changes, so no version bump (per the CI-only/docs-only rule).

Audit of the v0.71.35 release against the checklist found four misses:

* README's docs/ index table was missing the docs/compliance.md row — a new
  topic page unreachable from the front door (step 9: "if the release adds a
  whole new topic area, add/adjust the row in the README's docs/ index table").
  It was only added to docs/README.md.
* The GGUF toolchain was never documented, despite the slot's own plan calling
  it out ("Risk: MSVC build variance — document the exact toolchain; keep the
  llama.cpp tag pinned"). Added the exact, verified build commands + the
  VS2022 component actually required, the single- vs multi-config binary
  layouts, and an explicit warning never to install llama.cpp's
  requirements.txt (it pins torch~=2.2.1 CPU and downgrades a CUDA torch —
  the bug fixed in v0.71.35).
* docs/peft-and-efficiency.md still said "16 built-in templates" — stale as a
  direct result of the 4 compliance templates (now 21).
* Stale counts CONTRIBUTING "Test Files (307 files)" -> 313 and "existing 15
  templates" -> 21 (that step also still pointed at schema.py rather than the
  templates/ YAML + manifest.json registry).

The documented build command and binary path are the ones actually executed
during the release validation, not idealised.
2026-07-15 21:04:01 +05:00
Alpamys dc73e947e9 feat(compliance): init templates + soup card + soup ci init + GGUF-on-Windows (v0.71.35)
Ship a regulated fine-tune with the paperwork it needs, plus GGUF export that
actually works on Windows.

Added:
* `soup init --template hipaa|soc2|eu-ai-act|sr-11-7` (templates 17 -> 21).
  Design constraint verified in-repo, not assumed: Soup's compliance controls
  are CLI flags/commands, NOT schema keys (audit_log/bom/attest/repro_receipt/
  annex_xi/track_energy/pii/decontaminate have zero matches in config/schema.py),
  so a template cannot "pre-wire audit-log on" as YAML. Each is a valid
  SoupConfig + header comments naming that regime's exact commands.
* `soup card <registry-id> -o MODELCARD.md` — registry entry -> publishable HF
  card (training config, eval scorecard, config/data hashes, lineage, artifacts).
  Pure build_model_card() over dicts, reused by `soup push --card`.
* `soup ci init` — writes .github/workflows/soup-gate.yml chaining
  data validate -> expect -> ship --evidence (exit 2 blocks the merge).
* docs/compliance.md quickstart.

Fixed (GGUF-on-Windows, validated end-to-end for the first time; closes the
CPU-validatable half of #70/#144). Four independently-fatal bugs:
* export cloned llama.cpp into the CURRENT directory: SOUP_DIR is the bare name
  ".soup" but was used relatively instead of anchored to home like tracker.py /
  registry/store.py, so ~/.soup/llama.cpp was never found.
* the first GGUF export DOWNGRADED the user's torch and broke CUDA: the
  auto-clone pip-installed llama.cpp's requirements.txt (pins torch~=2.2.1 from
  the CPU index) into the user's interpreter. Observed live: torch 2.5.1+cu ->
  2.2.2+cpu, transformers 4.57 -> 4.46. Now installs only gguf/sentencepiece/
  protobuf, unpinned, non-fatally.
* a correctly-built llama.cpp was not found on Windows: MSVC (like Xcode) is a
  multi-config generator emitting build/bin/Release/llama-quantize.exe.
* `soup deploy ollama` failed on a relative GGUF path ("pull model manifest:
  file does not exist") — ollama resolves FROM against the Modelfile's dir and
  Soup writes it to a temp dir. Modelfile now emits an absolute path.

Also fixed a pre-existing model-card injection hole (affects `soup push`'s own
auto-card): _render_training_section interpolated base/task/scheduler/recipe
unescaped, and SoupConfig.base/scheduler have no charset validator, so a
crafted-but-valid config could smuggle raw HTML or a code-span-breaking backtick
into a card published to the Hub.

Step-6 live smoke (real train -> registry push -> card) caught a bug 90 green
tests missed: is_adapter came only from registry artifacts, so a real LoRA run
with no artifacts rendered "Full model" + library_name: transformers — a false
claim in a provenance document.

5 sequential reviews, every finding fixed (code HIGH: the generated workflow ran
`pip install -e ".[dev]"`, which only works in the Soup source tree, breaking the
gate's first step for every downstream user; security HIGH/MEDIUM/3 LOW; tdd HIGH:
push --card was only --help-tested). All 5 new fixes mutation-verified as real
pins. Tests 15906 -> 16001 (+95); full suite 15872 passed / 126 skipped.
2026-07-15 20:09:10 +05:00
Alpamys ff4c9963cb docs: v0.71.34 adapter algebra + LISA (version bump + CHANGELOG + docs) 2026-07-15 13:39:46 +05:00
Alpamys 4e88f70691 docs: list soup draft in the serving docs-index rows
Docs-only; no version bump (per the CI-only/docs-only hotfix rule in the
release checklist).
2026-07-13 13:34:22 +05:00
Alpamys 2f79aebc5a docs(draft): v0.71.33 — soup draft, shipped as the honest measurement gate
Version 0.71.32 -> 0.71.33. CHANGELOG (with a Known-limitations block), README
What's New, docs/commands.md, docs/serving-and-export.md, CLAUDE.md (arch + CLI
+ counts + history roll), CONTRIBUTING counts.

The live smoke changed the pitch. The plan promised '~1.5-2x faster to serve by
distilling its own draft'. Measured on SmolLM2-360M-Instruct <- 135M-Instruct:
the STOCK draft already scored 69.3% acceptance; distilling it gave 69.7% at 2
epochs and 69.3% at 10 -- no gain beyond noise. A small same-family draft is
already at its capacity ceiling vs the target, and logit KD cannot buy capacity
it does not have. Assisted decoding also measured 0.55-0.64x -- a net SLOWDOWN.

So the speedup claim is withdrawn, not shipped. The feature ships as the honest
gate: soup draft measure tells you whether speculative decoding is worth
enabling BEFORE you ship it, and on this pair it correctly says no. That
negative result is stated in the CHANGELOG, the README, the serving docs and
CLAUDE.md, and the pre-existing unverified '2-3x faster' line in the
speculative-decoding docs was tempered to match.

Full suite: 15680 passed / 124 skipped (15806 collected). ruff clean.
2026-07-13 12:46:19 +05:00
Alpamys a9e17b6add docs: bump stale recipe count 138 -> 142 (commands + serving-and-export) 2026-07-07 19:25:13 +05:00
Alpamys 5369e81d9d docs(v0.71.32): version bump + CHANGELOG/README/docs for ASR (Whisper) fine-tuning 2026-07-07 17:24:40 +05:00
Alpamys 8f59b9b9d7 docs(v0.71.31): judge-in-the-loop suite — version bump, CHANGELOG, docs, recipe, counts
Bump 0.71.30 -> 0.71.31; +1 recipe online-dpo-smollm2-135m (137->138); CHANGELOG
[0.71.31]; README What's New; docs (training/data/evaluation/commands/serving);
recipe-count test asserts 137->138.
2026-07-06 13:51:23 +05:00
Alpamys f8dd8fd584 chore: stop tracking internal planning docs
Move docs/superpowers/ (internal brainstorming plans + design specs) out of
the repo and into .gitignore; drop the internal doc reference in CONTRIBUTING.md.
Files remain locally, just no longer version-controlled.
2026-07-05 20:52:50 +05:00
Alpamys 6962573ba1 docs(spec): v0.71.31 judge-in-the-loop suite design (online-DPO + best-of-N + evolve + ship pairwise #284) 2026-07-05 20:50:20 +05:00
Alpamys 4c9490164a docs: fix stale recipe/test counts missed in v0.71.30 (137 recipes, 307 test files) 2026-07-05 20:22:46 +05:00
Alpamys 93449442b8 docs(prm): v0.71.30 release — version bump, CHANGELOG, README What's New, docs/training (PRM-guided GRPO), recipe/test counts 2026-07-05 19:32:38 +05:00
Alpamys 3c21c5e7c8 docs: v0.71.30 implementation plan (PRM-guided GRPO + envs) 2026-07-05 16:47:12 +05:00
Alpamys b5374236dc docs: v0.71.30 design spec (PRM-guided GRPO + bundled rollout envs) 2026-07-05 16:45:31 +05:00
Alpamys 05b35148f2 docs: add soup shrink to CONTRIBUTING utils list + docs index (v0.71.29 follow-up) 2026-07-05 13:38:16 +05:00
Alpamys 8a21e13b39 docs(shrink): v0.71.29 release docs (version bump, CHANGELOG, README, docs, CONTRIBUTING) 2026-07-05 11:50:43 +05:00
Alpamys 0141d6267e docs(shrink): design spec + implementation plan (v0.71.29) 2026-07-05 10:25:02 +05:00
Alpamys 2c782b3eb5 docs: soup mcp serve MCP server (v0.71.28) 2026-07-04 20:50:55 +05:00
Alpamys 937abb9e0d feat(data): soup data doctor + soup data lint — Fine-tune Doctor (v0.71.27)
Add `soup data doctor` and `soup data lint`, killing the top *silent*
fine-tune failures before a single training step: EOS-missing-from-labels
(the #1 "model never stops generating" bug), BOS duplication, no-system-role
templates, and preference-data length bias (the #1 silent DPO degradation) —
none of which any competitor (Unsloth/Axolotl/LlamaFactory) checks for.

- utils/data_doctor.py: 8-check chat-template compat report over a
  tokenizer + sampled rows, OK/MINOR/MAJOR taxonomy mirroring diagnose;
  --show-mask N renders per-token trained/masked colouring through the
  SAME masking dispatch (_build_row_labels) the report itself uses, so
  the two can never disagree about what's actually trained.
- utils/data_lint.py: preference-data linter (dpo/orpo/simpo/ipo/bco/kto)
  — length bias (Cohen's d), label imbalance, near-duplicates (MinHash),
  identical chosen==rejected pairs, prompt leakage.
- commands/data_doctor.py: Typer layer for both commands; strips C0
  control bytes before untrusted dataset content reaches the terminal.
- commands/diagnose.py: hardens the --evidence loader against a TOCTOU
  symlink swap (O_NOFOLLOW + fstat-on-open-fd), backporting the pattern
  soup ship shipped in v0.71.25 (closes v0.71.25 known-limitation (4)).

Live smoke against the real HuggingFaceTB/SmolLM2-135M-Instruct tokenizer
(Windows + RTX 3050) found and fixed two genuine bugs beyond the synthetic
fixtures: the EOS check needed to span-search the whole trained region
(not just the last token), and two apply_chat_template call sites needed
a broad except Exception for jinja2.exceptions.TemplateError.

+173 tests (14788 -> 15042). 5 sequential ECC reviews, every finding fixed.
2026-07-04 14:03:30 +05:00
Alpamys dca58c4107 docs(train): v0.71.26 release — closed-loop reward-hacking mitigation
Version 0.71.25 -> 0.71.26 (pyproject + __init__). CHANGELOG [0.71.26] entry
(feature + security). README What's New slot. docs/training.md mitigation
section + docs/commands.md flag. CONTRIBUTING + examples/README. Also folds in
the already-merged qwen2.5-coder-7b-sft recipe (#285) that rides this release.
2026-07-01 16:55:59 +05:00
Salil M 14de9ec9d5
feat(recipes): add ready-made SFT recipe for Qwen2.5-Coder-7B-Instruct (#285)
* feat(recipes): add qwen2.5-coder-7b-sft recipe to catalog

* test(recipes): update catalog size assertion to 134 in recipe tests

* test(recipes): update version catalog count to 134 in v0.71.24 tests

* docs(contrib): bump recipe count in project structure overview

* docs(commands): update recipe list count reference to 134

* docs(serve): update Web UI recipe count references in serving docs
2026-06-28 19:22:29 +05:00
Alpamys 6cb2e0201e docs: index soup ship in docs/README + CONTRIBUTING utils list 2026-06-28 00:02:23 +05:00
Alpamys 6cb1abab8f feat(eval): soup ship — SHIP / DON'T-SHIP verdict (v0.71.25)
Add `soup ship`, a binary SHIP / DON'T-SHIP verdict after fine-tuning: it
SHIPs only when (leg 1) the task metric strictly improved AND (leg 2) no
general benchmark regressed past a forgetting threshold (default 0.05
absolute points) — otherwise DON'T SHIP, even if the task metric looks
great. The moat is leg 2 (catastrophic-forgetting gate) fused with the
task win into one decision. Exit: 0=SHIP, 2=DON'T SHIP, 1=runtime error.

- utils/ship_verdict.py: pure engine (no top-level torch) — frozen
  TaskWin/BenchmarkDelta/ShipVerdict + decide_ship (single source of
  truth for the threshold) + compute_benchmark_deltas + render/serialize.
- commands/ship.py: Typer command; --evidence offline path + live
  metric/judge leg-1 + mini(default)/lm-eval leg-2; --baseline/--output.
- Reuses run_eval / JudgeEvaluator / ForgettingDetector / resolve_baseline
  / _run_lm_eval / live_eval.make_generator.
- Hardening: --evidence O_NOFOLLOW + size cap; --task-eval cwd-contained;
  --judge-model urlparse SSRF guard; lm-eval model_args injection guard;
  --general-suite bounded.

Schema (ShipConfig) deferred — v1 is CLI-only. Pairwise judge win-rate is
a planned fast-follow. +79 tests (14514 -> 14593).
2026-06-27 23:37:35 +05:00
Alpamys 418f86390a feat(recipes): 2026 model-family expansion — 17 SFT recipes, catalog 116→133 (v0.71.24)
Add ready-made SFT recipes for the open-weight models released Feb–Jun 2026,
each base repo-ID verified to resolve on Hugging Face:
- Qwen3.5 0.8B/2B/4B/9B/27B + MoE 35B-A3B/122B-A10B/397B-A17B (Apache-2.0)
- Qwen3.6 27B + 35B-A3B (Apache-2.0)
- DeepSeek-V4 Flash/Pro (MIT), GLM-5.1 (MIT)
- Kimi-K2.5/K2.6 (Modified MIT), MiniMax-M3 (MiniMax Community License)
- Mistral-Large-3 (Apache-2.0, 675B/41B-active multimodal MoE)

Fix stale glm-5-sft repo-ID THUDM/glm-5 -> zai-org/GLM-5 (org migration).
+220 tests (tests/test_v07124.py). Catalog count 116 -> 133.
2026-06-21 13:00:47 +05:00
Alpamys e91da922c3 docs: correct recipe count to 116 and refresh stale TTS/BitNet blurbs
Recipe-count drift: the CLI help (commands.md) and Web UI pages
(serving-and-export.md) claimed 43 ready-made recipes and the catalog
docstring said ~30, while RECIPES actually holds 116 (test_recipes already
asserts len == 116). Aligned every user-facing count to 116.

Also refreshed 6 recipe descriptions still tagged "schema-only stub
(live in v0.52.1)": the TTS task (#131) and BitNet 1.58 SFT (#134) went
live in v0.71.20, so orpheus/sesame/llasa/spark/oute TTS and the Falcon-E
BitNet recipe now read "live (v0.71.20)".

Doc-drift only — description strings + one comment; no functional change,
no version bump.
2026-06-20 12:30:00 +05:00
Alpamys fcf4b33394 feat(train): native Spectrum targeted training — soup spectrum scan + training.unfrozen_parameters (v0.71.23)
Closes #266. `soup spectrum scan` streams safetensors per-tensor (no model
load, CPU-friendly) and computes a singular-value SNR per weight matrix
(Marchenko-Pastur, arXiv:2406.06623), emitting a ready-to-paste
training.unfrozen_parameters patch. The SFT trainer freezes all params then
unfreezes the matched set (full FT, LoRA off).

- utils/spectrum_scan.py: pure-numpy transpose-invariant SNR kernel +
  per-tensor safetensors streaming (2^31 SVD cap, symlink skip) + cache
  (~/.soup/spectrum, SOUP_SPECTRUM_CACHE_DIR containment) + hardened
  hubs.snapshot_download.
- commands/spectrum.py: soup spectrum scan (SNR table + YAML patch).
- schema: training.unfrozen_parameters (caps/NUL/invalid-regex/ReDoS reject)
  + gates (sft/transformers/text/quantization=none; mutually exclusive with
  LoRA features / freeze_layers / freeze_ratio / train_router_only /
  expand_layers).
- trainer/sft.py: full-FT branch via apply_unfrozen_parameters +
  enable_input_require_grads (fixes grad-checkpointing through frozen
  embeddings).

Existing spectrum trainer-plugin untouched (back-compat); LISA -> #267.
Live-validated on Windows + RTX 3050: CPU scan of SmolLM2-135M + top-25%
unfrozen full-FT train (loss 3.455 -> 0.719).

Note: after the version bump the editable install metadata was stale
(0.71.17); pip install -e . --force-reinstall --no-deps re-synced it so
test_cli_subprocess::test_version passes.

+94 tests in tests/test_v07123.py (14184 -> 14278).
2026-06-12 17:40:46 +05:00
Alpamys 8b7d63f944 docs: clarify Orpheus live-codec TTS is live in training.md (v0.71.22)
The live-codec block claimed the entire data.format=audio path was "not
validated on the maintainer's box" and only surfaced a RuntimeError. v0.71.22
made the Orpheus SNAC encode live + validated; note that while the other four
families stay dependency-gated. Docs-only, no version bump.
2026-06-10 22:02:50 +05:00
Alpamys ed5fc3a8b3 feat(precision,rollout): live fp8/nvfp4 + vLLM sleep + openenv rollout + apple-adapter + delinearize-llama4 (v0.71.21)
Closes #141, #124, #125, #228, #97.

- #141: apply_fp8_attention (torchao float8 on attention projections, Hopper
  gate) + apply_nvfp4 (NVFP4Config, Blackwell gate); partial-conversion honesty;
  wired into the v0.28 speed/memory pipeline with yellow-advisory degrade.
- #124: vllm_sleep_mode live - create_vllm_engine(sleep_mode=True) +
  vllm_sleep_cycle ctx (wake in finally) + TRL GRPOConfig hook probe.
- #125: openenv rollout fully live via training.rollout_func module:fn
  resolver; rows replace the prompt dataset; art/ruler/nemo_gym honest dep
  gates + _EXTERNAL_ROLLOUT_RUNNERS seam. Real GRPO train on SmolLM2-135M.
- #228: convert_apple_adapter live - PEFT LoRA <-> mlx-lm (both matrices
  transpose, bf16 upcast, adapters.safetensors + num_layers, npz legacy read,
  np.ascontiguousarray fix for safetensors non-contiguous mangling);
  *-to-apple upstream-gated exit 3.
- #97: delinearize-llama4 live - [E*din,dout] -> [E,din,dout] per shard,
  config.json expert-count probe + --num-experts, sidecar copy, atomic writes.

Review waves: 3 HIGH + ~8 MEDIUM + ~12 LOW fixed.
Tests: 13874 -> 14084 (+210 in tests/test_v07121.py).
Full suite: 13967 passed, 117 skipped. ruff clean.
2026-06-10 16:29:52 +05:00
Alpamys a4dfbb308c feat(trainer): live TTS / BitNet / MoE-expert-quant trainers (v0.71.20)
Lift three v0.52.0 schema-only NotImplementedError stubs to real code.

- #131 TTS: TTSTrainerWrapper(SFTTrainerWrapper) — TTS fine-tune = next-token
  CE over [text][audio-codec-token] chat; per-family emotion templating
  (Orpheus/Oute) + codec special-token registration. Pre-encoded chat path
  live-validated on SmolLM2-135M-Instruct; live-codec (data.format=audio)
  hardware-gated per family.
- #134 BitNet: BitNetTrainerWrapper gated on onebitllms; export --format
  bitnet|tq1_0 runs real llama.cpp TQ1_0 ternary GGUF export.
- #136 MoE: apply_moe_expert_quant swaps fused-MoE experts to bnb Linear4bit/
  Linear8bitLt (pre-LoRA); train_router_only freezes experts (post-LoRA).
  Live-validated on RTX 3050 (dequant err 0.0155).

Review fixes: H1 explicit Params4bit/Int8Params weight-carry; H2 quant
pre-LoRA / freeze post-LoRA + skip PEFT-wrapped modules; M4 device-aware
placement.

Tests 13807 -> 13874 (+69 in test_v07120.py, -2 lifted stubs in test_v0520.py).
2026-06-10 12:37:14 +05:00
Alpamys 853b348898 docs: refresh quant-menu modality + multipack sharding notes (v0.71.19)
- performance-and-quantization.md: the Quant Menu multi-trainer note said
  "vision / audio modality is still SFT-only inline-BNB (wiring tracked as a
  follow-up)" — stale after v0.71.19 #81 dropped the modality gate. Now states
  vision/audio thread the unified loader (full gptq/awq/hqq/aqlm/eetq/mxfp4/fp8
  menu), with the upstream class+kernel caveat.
- peft-and-efficiency.md: added the v0.71.19 #80 multi-GPU sharding paragraph to
  the Multipack section (accelerator.prepare + BatchSamplerShard under
  num_processes>1; identical bin seed across ranks; single-GPU unchanged).

Docs-only — no version bump / tag (the v0.71.19 code already shipped at f51331d).
2026-06-09 13:02:30 +05:00
Alpamys 70fd5ee9f3 feat(distill,agent,cloud): on-policy MiniLLM + aligned ULD + agent sandbox eval + Modal cloud (v0.71.18)
Closes #257, #258, #110, #16.

- #257 MiniLLM true on-policy rollout: minillm_on_policy_rollout (Gu et al. §3.1
  autoregressive teacher-mixed rollout, reverse-KL on full distributions,
  grad-to-student-only) + on_policy_term + training.minillm_on_policy /
  minillm_rollout_length, wired into DistillTrainer.compute_loss.
- #258 cross-tokenizer ULD wasserstein_aligned: align_token_sequences (difflib
  char-span) + aggregate_aligned_logits + uld_aligned_loss for fully-disjoint
  tokenizers, wired into DistillTrainer.
- #110 soup agent eval --sandbox: build_eval_stub (base64-embed-as-data) +
  run_eval_in_sandbox (v0.25 RLVR isolation + SANDBOX_NETWORK_GUARD) +
  classify_sandbox_outcome (ok/tool_error/timeout/arg_error).
- #16 soup train --cloud modal: render a Modal app from soup.yaml (config
  base64-embedded), plan-only default, --cloud-submit token-gated; [modal] extra.

+114 tests (tests/test_v07118.py); 13656 -> 13770. Step-6 smoke on real input
(Windows + RTX 3050): Modal stub render, real subprocess sandbox scorecard,
on-policy distill (tiny-gpt2), cross-tokenizer aligned ULD (GPT-2 + Llama).
2026-06-08 23:49:14 +05:00
Alpamys 0d55ba9e46 feat(serve): serve-time MoLE + per-request vector banks + epoch RAFT shuffle (v0.71.17)
Closes #259 (soup serve --mole: load base + N frozen task LoRAs + mole_gate.pt,
blend per-token at decode; train writes mole_manifest.json).
Closes #260 (soup serve --bank active user per-request via contextvars.ContextVar,
no cross-request leak; streaming path re-selects in-context).
Closes #253 (data.raft_epoch_shuffle: re-permute golden/distractor docs each epoch;
epoch=0 == legacy order).
Closes #254 (soup diagnose --citation-style / --shuffle-seed into the live probe).

Fix: MoLE train() returns initial_loss/final_loss/total_steps/duration_secs so
task=moe_lora_routing completes cleanly (surfaced by the #259 smoke).

Validated live on SmolLM2-135M (RTX 3050). 13595 -> 13656 tests.
2026-06-08 20:06:43 +05:00