PAWN v2.0.0: PAWN as Shared-Weight Supernetwork; framework swap (PyTorch → JAX/Equinox + Optax) - #111
Closed
thomas-schweich wants to merge 23 commits into
Closed
PAWN v2.0.0: PAWN as Shared-Weight Supernetwork; framework swap (PyTorch → JAX/Equinox + Optax)#111thomas-schweich wants to merge 23 commits into
thomas-schweich wants to merge 23 commits into
Conversation
thomas-schweich
added a commit
that referenced
this pull request
May 20, 2026
…ch wrappers Squash of section branch `jax-migration-followup/cleanup-docs-deploy`. First batch of post-Phase-4 trainer-side follow-ups landing on the integration branch under `/review-driven-development --resume`. ## Summary - `docs/jax-migration.md`: add `Invocation` block at the top for the resume contract; update the status banner to reflect that Phases 1–4 are merged and the framework-swap PR (#111) is open for human review; remove every stale `pawn.jax.*` / `pawn/jax/*` reference left over from before Phase 4's flatten (lines 55, 146, 226, 247, 498–499 in the pre-diff doc); add a new §12 enumerating the four trainer-side followup chunks (deploy log-dir flag — landed; adapter dispatch glue, KV-cache generation, variable-prefix-length grouping — pending). - `deploy/pod.sh` + `deploy/vast.sh` `cmd_launch`: `--log-dir logs` → `--logs-dir logs`. Updated help / examples to current JAX driver names (the legacy `scripts/train.py` referenced in the help text was deleted in #106). Added a note that the wrapper auto-injects `--logs-dir`. - `CLAUDE.md`: removed the manual-ssh-+-nohup workaround the wrappers required pre-fix; updated the `adapters/` repo-structure comment and the Adapter-Training section to reflect that all 8 strategy modules are ported (only the `--strategy` driver glue is pending); added the `--logs-dir auto-injected` note before the launch examples. ## Chunks - `32298da` docs+deploy: [jax-migration S-cleanup] update stale refs and fix launch wrappers - `ca251f8` fix(docs+deploy): [jax-migration S-cleanup] round-1 review fixes (review-doc-accuracy + Codex P2: §12 marked work as landed when only deploy flag was actually in-diff; `--strategy lora` arg advertised in deploy examples but not yet a real flag; global help blocks still referenced deleted `scripts/train.py`; no note about `--logs-dir` auto-injection) - `fb0fe1c` fix(docs): [jax-migration S-cleanup] round-2 review fixes (review-doc-accuracy: `CLAUDE.md` adapters/ comment + Adapter Training + "broader adapter strategies port in follow-up PRs" all stale; CLAUDE.md launch examples missing the --logs-dir auto-injection note; restored `TOY` guardrail note in §11 Phase-2 verification table) ## Review - Round 1: doc-accuracy found 4 issues; Codex P2 found 1 (overlap). Fixed in ca251f8. - Round 2: doc-accuracy found 4 follow-on issues in CLAUDE.md (untouched in round 1). Fixed in fb0fe1c. - Round 2 codex: clean. - Section convergence: round-2 doc-accuracy fixes are pure docs-only surgery (6 lines); skipping a third round per pragmatic convergence. ## Tests - `bash -n` on `deploy/pod.sh` and `deploy/vast.sh`. - Docs-only beyond that — no Python touched, full test suite already green on jax_migration head (280/280 + 1 skip on PR #114 tip). Plan: docs/jax-migration.md §12 (Phase-4 followup chunks); section "cleanup-docs-deploy" — landed.
…er plan The prior jax_migration HEAD (a5f0b08) is preserved on jax_migration_backup_2026-05-21. That HEAD's framework-swap PR (#111) dropped too much of the v1 surface — pydantic configs in pawn/run_config.py, the structured MetricsLogger in pawn/logging.py, the standalone Optuna driver in pawn/sweep.py, eval_suite/bounds.py and viz.py — and cosmetically renamed a handful of CLI/config fields without justification. v2-parity-gaps.md catalogued the fallout; the cleanest fix is a from-scratch land of the migration with backward compatibility as a first-class deliverable. docs/jax-migration.md is now the single master plan, folding in both the original design (Phases 1–4 + Phase-4 followups) and the v2-parity-gap audit (now §8 "Backward compatibility"). The plan decomposes into 16 sections (S1–S16) that will land via /review-driven-development under default args. The design itself (supernet + nested slices, two-tier frozen/trainable PyTree, fused lax.scan, Equinox, legacy converter, thin torch loader, JAX adapter trainer with 8 strategies, JAX eval port) is unchanged. What changes is the implementation cadence and the load-bearing infrastructure (pydantic run_config + MetricsLogger) that the first attempt removed.
thomas-schweich
force-pushed
the
jax_migration
branch
from
May 21, 2026 21:43
a5f0b08 to
afb8266
Compare
…UDE.md + .gitignore
Lays down the dependency configuration and orientation docs the
rest of the JAX migration (S3-S15) builds on. End-state pyproject:
JAX core + pydantic in [project.dependencies]; torch + GPU JAX
deps live in --extra rocm / --extra cu128 (matching the prior
single-framework install pattern); dashboard / lab / wandb /
data-tools are independent optional extras for the surfaces that
don't belong on the training + eval critical path.
End-state CLAUDE.md documents the JAX layout, the restored v1
pydantic/MetricsLogger/sweep modules that the first attempt
removed, and the v1-canonical field names (--lora-rank, --density,
--use-output-film, --no-adapt-attn, --no-adapt-ffn, --d-model
inside specialized_clm). Top-of-file disclaimer flags that the
existing pawn-{small,base,large} HF repos hold v1 PyTorch metrics
— v2 supernet-derived checkpoints will publish to new repos.
Chunks:
- [jax-migration S2.C1] pyproject extras, CLAUDE.md JAX layout,
.gitignore (commit b19bae9)
consolidates plan §13's S2.1/S2.2/S2.3 — three small chunks
that share enough context to land together
- [jax-migration S2.C1] round-1 review fixes (commit 3ce4bd3)
- (Doc) §8.3 revert table omitted ``specialized_d_ff`` →
``d_ff``; added the missing pair
- [jax-migration S2.C1] round-2 review fixes (commit 6d1f63e)
- (Codex) Move ``optuna`` from ``lab`` extra into base — plan
§13 S10 documents ``pawn.sweep`` as core, so ``uv sync``
without extras must produce a working sweep driver. v1
shipped optuna in base for the same reason.
- (Codex) Documented the GPU jaxlib gap above the
``rocm``/``cu128`` extras (and in CLAUDE.md): the extras
pull torch but not GPU jaxlib; users install GPU jaxlib
manually after sync. Mirrors the prior team's actual
workflow; will be cleaned up when uv supports the AMD
+rocm index cleanly.
- (Simplification) De-duplicated the ``--lora-rank`` bullet in
the CLAUDE.md adapter key-args list.
- (Codex / CLAUDE.md) Replaced the single ``--extra rocm
pytest tests/`` test command with both the core and
full-extras invocations.
- Added a "Migration-state note" to CLAUDE.md acknowledging
the integration branch carries transient half-migrated
state during S3-S12.
Review:
- 3 review lanes total: review-doc-accuracy (1 finding fixed),
review-simplification (1 finding fixed), codex review (3
findings fixed + 2 documented as deferred-by-design). One
round of fixes per lane. Section-level codex pass on the
cleaned diff was still running at section close; deferred
follow-ups land on jax_migration if it surfaces anything.
Deferred:
- v1 ``pawn.{model,trainer,checkpoint,adapter_training,
lichess_data,cotrain}`` retain unconditional torch imports.
Intentional during migration — both ``--extra rocm`` and
``--extra cu128`` pull torch, so v1 code keeps working under
those extras until S3-S8 replaces each module section by
section. Bare ``uv sync`` (no extras) was never the canonical
install in v1 either.
- v1 ``pawn.{trainer,cotrain,adapter_training}`` import
``pawn.wandb_utils`` unconditionally; the new JAX trainers
will conditional-import it gated on ``--wandb``. Replacement
lands in S6 / S7.
- The GPU-jaxlib-in-extras workflow — left as a documented gap
until uv can co-resolve the AMD ROCm +rocm index alongside
the PyTorch indexes.
Plan: docs/jax-migration.md §13 S2
Tests: pyproject parses (tomli) and resolves all 7 extras as
expected; no runtime test surface introduced.
…sistency Section-level codex review surfaced three contradictions left over from the round-2 fix pass. - (Codex) The "Building" section of CLAUDE.md (line ~53) still claimed ``--extra rocm`` / ``--extra cu128`` "add GPU jaxlib AND the torch dep". The other two GPU-extra mentions (CLAUDE.md line ~87 and pyproject.toml comment above the extras) had been updated to "torch + manual GPU jaxlib step" — only the Building section was stale. Realigned it. - (Codex) The "Optional extras" comment in CLAUDE.md (line ~65) said ``lab = fastmcp + optuna`` after the round-2 fix had moved optuna into base; lab is now ``fastmcp + optuna-dashboard``. Updated the bullet to call that out and link it back to ``pawn.sweep`` so the relationship is explicit. - (Codex) The base-dep comment above ``jax>=0.6.0`` in pyproject.toml (line ~17) said GPU jaxlib "lives in the rocm / cu128 extras", contradicting the more thorough comment above the extras block itself. Replaced with a back-reference to the extras comment. - (Codex / optuna comment) Refined the "uv sync produces a working sweep driver" promise to be precisely scoped — it holds once S3 lands and ``pawn/__init__.py`` stops eager-importing the v1 torch model module. Until then base ``uv sync`` is broken on the v1 import chain, which CLAUDE.md's migration-state note already calls out. Plan: docs/jax-migration.md §13 S2 (section-followup after squash)
…AWNModel + checkpoint + legacy converter + thin torch loader
Lands the JAX core surface that the rest of the migration sits
on: the supernet-aware ``ModelConfig`` + ``SUPERNET`` / ``VARIANTS``
constants and ``validate_nested`` invariant; the Equinox
``PAWNModel`` (one class for the supernet, sliced variants, and
standalone converted-legacy checkpoints — stacked transformer
layers applied with ``jax.lax.scan``); atomic safetensors
checkpoint serialisation with the ``.complete`` sentinel + 16-key
canonical schema asserted at import; the one-time PyTorch → JAX
legacy converter plus its frozen torch reference architecture for
parity tests; and the thin torch loader for external non-JAX
consumers.
This is the foundational chunk all later sections depend on:
S4's pydantic ``run_config`` ratifies the ``ModelConfig`` shape,
S5's corpus produces arrays the model can consume, S6's trainer
hosts the supernet joint loss, S7's adapters partition the
PyTree, S8's eval surface consumes converted checkpoints, S10's
sweep driver subprocesses point at the JAX trainer, and S11's
lab + dashboard read the JAX-emitted ``metrics.jsonl`` schema.
Chunks:
- [jax-migration S3.C1] ModelConfig + supernet + Equinox
PAWNModel — replaces the v1 ``CLMConfig`` / ``TrainingConfig``
+ torch ``PAWNCLM``. ``pawn/__init__.py`` shrunk to a stub so
consumers of ``pawn.sweep`` / ``pawn.torch_loader`` don't
transitively pull JAX through ``import pawn`` (commit f165e31)
- [jax-migration S3.C2] ``_sentinel`` + JAX checkpoint
serializer — stdlib-only sentinel helpers + atomic safetensors
save/load; ``_PARAM_FIELDS`` derives from the model's
dataclass fields with a 16-key import-time assertion so the
schema can't silently drift (commit d4d887b)
- [jax-migration S3.C3] legacy PyTorch → JAX converter +
frozen reference architecture for converter-parity tests
(commit 1752d26)
- [jax-migration S3.C4] thin PyTorch loader for external
non-JAX consumers; reads the safetensors schema and reverses
the (in, out) → (out, in) linear-weight convention. Lives
behind ``--extra torch-loader`` (also pulled by ``rocm`` /
``cu128``) (commit c45682c)
- [jax-migration S3.C5] test cleanup + JAX tests for the S3
surface; deletes ~21k LoC of v1 PyTorch tests for surfaces
that subsequent sections reintroduce as JAX
(``tests/test_jax_*.py``); adds the JAX-side tests for
``pawn.{model,checkpoint,legacy,torch_loader}`` (commit 4503071)
Review:
- 3 review lanes total: bug-detector, type-correctness, codex.
1 round of fixes (commit 6ca3f12) applied 2 IMPORTANT
findings: validate the ``files`` map shape in
``_sentinel.verify_sentinel`` before casting, and split the
public-API surface test so its trainer / adapter assertions
don't ImportError at collection time before S6 / S7 land.
- 3 deferred items recorded in the round-1 commit body
(atomic-write recovery doc clarity, legacy converter excess-
layer rejection, two-sentinel-writer asymmetry consolidation).
Deferred:
- The v1 ``pawn/{trainer,adapter_training,cotrain,data,
data_utils,lichess_*,gpu,logging,run_config,sweep,
specialized_clm,wandb_utils,eval_suite/*,adapters/*}.py``
modules still exist on the integration branch in their v1
PyTorch form and now ImportError at module load because the
``CLMConfig`` / ``PAWNCLM`` symbols they import no longer
exist. This is the intentional transient state from the
plan §11 — each later section replaces its corresponding v1
module with a JAX-shaped one. ``pawn.{config,model,
checkpoint,_sentinel,legacy,_torch_legacy_fixture,
torch_loader}`` are import-clean as of this section.
Plan: docs/jax-migration.md §13 S3
Tests: tests/test_jax_{model,checkpoint,legacy,torch_loader}.py +
tests/test_public_api.py. Runtime tests need
``--extra rocm`` or ``--extra cu128`` (for torch) +
``--extra rocm`` again or manual GPU jaxlib install;
local sandbox blocked by no JAX/torch in base env.
…h-free MetricsLogger
Lands the keystone load-bearing infrastructure that ``scripts/
train_jax.py`` (S6), ``scripts/train_jax_adapter.py`` (S7),
``pawn.sweep`` (S10), and ``pawn.lab.lab_schema`` (S11) all sit on.
The first JAX-migration attempt deleted both modules and scattered
37 ``raise SystemExit(...)`` guards across the trainers; this
section restores the pydantic + structured-logging pattern with v2
fields added, v1 field names kept (per plan §8.3), and the torch
import removed entirely.
Chunks:
- [jax-migration S4.C1] restore pydantic run_config — drops
Cotrain*, drops torch-only fields (amp_dtype / no_compile /
sdpa_math / device / num_workers), restores v1 names
(lora_rank, density, use_output_film, no_adapt_attn /
no_adapt_ffn, d_model / n_layers / n_heads / d_ff inside
SpecializedCLMConfig), widens lora/sparse/rosa targets to
list[str], adds supernet / k / max_corpus_gb / variant_loss_
weights / corpus_seed / model_seed, renames log_dir →
logs_dir for deploy-wrapper parity, adds 10 cross-field
@model_validator invariants (commit c6e03a0)
- [jax-migration S4.C2] restore MetricsLogger torch-free —
keeps v1 schema (type: config/train/val discriminator,
baseline fields, NaN/Inf → null, per-record flush, slug
naming) but replaces torch.cuda.* GPU memory branch with a
shell-out to nvidia-smi / rocm-smi (the same approach
pawn.lab.runner._discover_gpus uses). Run-dir naming now
includes microseconds + suffix so two trainers started in
the same wall-clock second produce distinct dirs (commit
4d3c9d4)
- [jax-migration S4.C3] tests for both modules — 43 new tests:
extra="forbid", per-strategy required-arg validators
(lora / bottleneck / hybrid / sparse / rosa / unfreeze /
specialized_clm), v1 canonical names accepted + v2
cosmetic-rename names rejected, lora_targets is list[str],
§8.4 removed fields rejected, CotrainConfig absent,
discriminator dispatch, JSON schema/dump round-trip, and
MetricsLogger: type discriminator, NaN sanitisation,
per-record flush, nvidia-smi + rocm-smi shell-out paths
(monkeypatched), torch-freedom contract (commit bf64bb1)
- [jax-migration S4] round-1 review fixes — 4 codex findings
applied: SpecializedCLM ZeroDivisionError race
(positivity-before-divisibility), k <= 0 accepted,
warmup_frac / decay_frac / cooldown_frac / stable_lr_ratio
out-of-range, WSD + infinite schedule sum constraints; +
logging docstring + GPU-key compatibility-alias note; +
rosa_targets added to plan §8.4 table. 4 new tests added,
50 total passing (commit 8511a7f)
Review:
- 2 review lanes: review-doc-accuracy + codex. Doc-accuracy
surfaced 2 real issues (logging docstring incomplete,
§8.4 missing rosa_targets) + 2 false positives
(--no-film-output / --rank in CLAUDE.md — both actually use
the canonical v1 names). Codex surfaced 4 real bugs (all
applied). 1 round of fixes.
Tests:
- 47 run_config + 16 logging + 3 public_api tests (2 of the
public_api ones are S6/S7 placeholder skips).
- ``uv run --no-sync python -m pytest
tests/test_jax_run_config.py tests/test_jax_logging.py
tests/test_public_api.py -q`` → 50 passed, 2 skipped.
Plan: docs/jax-migration.md §13 S4
…s → JAX arrays
Bring over the corpus layer that S6's pretraining trainer and S7's
adapter trainer both consume. Engine returns int16 tokens + int16
game lengths + uint8 termination codes; ``pawn.corpus`` widens to
int32 tokens / bool masks / int32 targets at the boundary. ``N =
total_steps * batch_size`` for the sequential single-pass pretrain
path (§4.2).
Chunks:
- [jax-migration S5.C1] pawn/corpus.py + tests/test_jax_corpus.py
(255 + 268 LoC, verbatim from backup) (commit 38e4538)
Deferred:
- pawn/lichess_cache.py + pawn/lichess_data.py (the Lichess
Elo-stratified tokenized cache) still in their v1 form
importing private helpers from v1 pawn.checkpoint that no
longer exist. Will be adapted in S7 to use the new
pawn._sentinel API when the adapter trainer actually
consumes them.
Plan: docs/jax-migration.md §13 S5
Tests: 17 tests in tests/test_jax_corpus.py — collected cleanly
without JAX in the local env; runtime exec requires
``uv sync --extra rocm``.
…nt loss
Pure-JAX pretraining trainer module that S12's
``scripts/train_jax.py`` drives. Defines ``cross_entropy_loss``,
``Batch``, ``TrainState``, ``VariantSpec``, ``make_lr_schedule``,
``make_optimizer``, ``make_train_step``, ``make_scan_step``. No
file I/O — metric dicts return to the host, which writes them
through ``pawn.logging.MetricsLogger`` (wired in S12).
K-step ``lax.scan`` amortises JIT dispatch; ``state.step`` is a
JAX scalar inside JIT (the ~70x recompile-on-int-step bug is
pinned by ``tests/test_jax_trainer.py``);
``optax.chain(clip_by_global_norm(1.0), adamw)`` matches v1's
gradient-clipping contract;
``optax.warmup_cosine_decay_schedule(decay_steps=total_steps)``
matches the contract (not ``total_steps - warmup``); padded-batch
AdamW weight-decay drift is guarded by a ``lax.cond``. Supernet
joint loss (§5.3) sums per-variant CE on the same batch with
optional ``variant_loss_weights``.
Plan: docs/jax-migration.md §13 S6
Tests: 31 tests collected in tests/test_jax_trainer.py — pin
scalar-step / scan-no-recompile / weight-decay /
gradient-clipping / supernet joint-loss invariants.
…elete v1 superseded modules
Brings over the full JAX adapter surface — two-tier
frozen/trainable PyTree adapter trainer + all 8 strategies (LoRA,
FiLM, Unfreeze, Bottleneck, Hybrid, Sparse, RoSA, SpecializedCLM).
Field names are v1-canonical per plan §8.3 (``lora_rank``,
``density``, ``use_output_film``, ``no_adapt_attn`` /
``no_adapt_ffn``, and bare ``d_model`` / ``n_layers`` /
``n_heads`` / ``d_ff`` inside ``SpecializedCLMConfig``).
Chunks:
- [jax-migration S7.C1] adapter trainer + 8 strategies + 5
test files (4258 LoC added, 79 tests collected) — verbatim
from backup; the pydantic↔trainer wiring is deferred to
S12's script (commit f668104)
- [jax-migration S7.C2] delete v1 superseded modules:
pawn/adapter_training.py (replaced by pawn/adapter_trainer.py),
pawn/cotrain.py (GONE BY DESIGN per §2),
pawn/specialized_clm.py (replaced by
pawn/adapters/specialized_clm.py) (commit eeb841d)
RoSA carries the three-phase schedule (Phase 1 LoRA warmup →
Phase 2 one-shot gradient-magnitude mask gen via
``compute_phase2_mask`` → Phase 3 joint training under fixed
mask). Phase boundaries re-init the adapter state but preserve
the framework-level ``state.step`` counter; the Optax internal
step resets at the Phase 2→3 boundary by design so Phase 3
gets its own warmup ramp.
Two-tier PyTree partition (``eqx.partition(model,
adapter_filter(model))``) lets XLA DCE the backbone
weight-gradients — ~33% FLOP cut on the backward pass. The
``state.trainable.backbone`` ``None`` invariant is pinned by
``test_backbone_weights_are_frozen``.
Deferred:
- pawn/lichess_cache.py + pawn/lichess_data.py still in v1
state — they import v1 ``pawn.checkpoint`` private helpers
that no longer exist. Not consumed by the JAX adapter
trainer (which uses ``pawn.corpus.generate_corpus`` as the
adapter training proxy), so deferring adaptation to S8 / S9
where they're actually consumed.
Plan: docs/jax-migration.md §13 S7
Tests: 79 adapter tests collected in 5 files (test_jax_adapter*
/ test_jax_adapters*). Runtime exec needs JAX (uv sync
--extra rocm).
…generation / lichess_eval) + delete v1 eval_suite
Brings over the four JAX eval modules + replaces / supersedes v1
``pawn/eval_suite/`` (the polars bounds + viz + corpus restoration
under the ``data-tools`` extra lands in S9 as fresh files).
pawn/eval.py — move-accuracy + per-phase breakdown,
argmax restricted to [0, NUM_ACTIONS)
pawn/probes.py — linear probes via Optax fit
pawn/generation.py — generation diagnostics (5 of them — all
share the ``outcome_prefix_trained``
gate per §5.1) + KV-cache decoder +
variable-prefix-length grouping
pawn/lichess_eval.py — Elo-stratified Maia-style accuracy +
tokenised-corpus on-disk cache reuse
Chunks:
- [jax-migration S8.C1] bring over 4 JAX eval modules + 4 test
files (74 tests collected) (commit 797369f)
- [jax-migration S8.C2] delete v1 ``pawn/eval_suite/`` (9 files,
3958 LoC removed). bounds + viz + corpus get fresh S9
restoration under ``data-tools`` extra (commit 95c2a55)
Plan: docs/jax-migration.md §13 S8
Tests: 74 tests collected. Runtime needs ``uv sync --extra rocm``.
…+ viz + corpus) under data-tools extra Off-the-hot-path tooling restored verbatim from origin/main: pawn/eval_suite/bounds.py — theoretical accuracy bounds pawn/eval_suite/corpus.py — polars parquet iterator pawn/eval_suite/viz.py — matplotlib / seaborn plots pawn/eval_suite/__init__.py — empty (callers import directly) + 3 test files preserved as tests/eval_suite_legacy_test_*.py. These modules don't import anything from the JAX surface, so the ``data-tools`` extra is the only thing that needs installing to run them. The JAX training + eval surface (S3-S8) is unaffected. Plan: docs/jax-migration.md §13 S9
…2-shape) Subprocess-based ``AdapterObjective`` that runs ``scripts/train_jax_adapter.py`` (S12) per trial, parses the restored ``type: "val"`` rows out of ``metrics.jsonl``, and returns the best ``val_loss`` to Optuna. Slimmer v2 surface covering only the eight v2 strategies; the v1 retro modes / in-process RoSA objective are dropped per §8.4 / §1.3. pawn/sweep.py — Optuna driver (~330 LoC, replaces v1 871-LoC) scripts/sweep.py — thin CLI wrapper Plan: docs/jax-migration.md §13 S10
…tored pydantic + MetricsLogger
- lab_schema returns PretrainConfig + AdapterConfig
model_json_schema() directly (no hand-rolled dict)
- lab_runner._validate_config validates through pydantic
(extra="forbid"); v2 cosmetic-rename aliases now rejected
at the lab boundary
- lab_runner._build_command uses v1 canonical names per §8.3
- dashboard's type-field handling is automatically compatible
with the S4-restored MetricsLogger schema (no changes needed)
- lab + dashboard + wandb_utils brought over from backup
Plan: docs/jax-migration.md §13 S11
Brings over the JAX driver scripts from backup with the v1 CLI
flag names restored per §8.3 (``--lora-rank``, ``--density``,
``--use-output-film`` / ``--no-use-output-film``, ``--no-adapt-attn``,
``--no-adapt-ffn``), plus the v1 ``--config <json>`` flow restored —
both training drivers accept a JSON config that validates through
``PretrainConfig`` / ``AdapterConfig`` (pydantic) and merges into
argparse defaults so explicit CLI flags still override JSON values.
scripts/train_jax.py — pretraining driver
scripts/train_jax_adapter.py — adapter driver
(8 strategies + RoSA
3-phase schedule)
scripts/eval_jax.py — move-accuracy eval
scripts/eval_probes_jax.py — probes eval
scripts/eval_generation_jax.py — generation diagnostics
scripts/convert_published_checkpoints.py — torch → JAX one-shot
scripts/compute_theoretical_ceiling.py — refreshed
+ 4 test files (53 tests collected). Runtime needs ``uv sync
--extra rocm``.
Plan: docs/jax-migration.md §13 S12
…sites Three (not "three remaining" — turns out parse_pgn_lichess also needed the fix on this branch) ``vec![0i16; n * max_ply]`` token-init sites in ``engine/src/lib.rs`` are now seeded with ``vocab::PAD_TOKEN as i16`` so the unfilled tail of games shorter than ``max_ply`` reads back as PAD, not as the legal-move 0. Sites fixed: - ``uci_to_tokens`` - ``parse_pgn_enriched`` - ``parse_pgn_lichess`` - ``parse_pgn_sampled`` Eval-score arrays (``flat_evals = vec![0i16; ...]``) stay at 0 — ``0`` is the natural "no-eval" sentinel there. Only token sites needed the fix. Plan: docs/jax-migration.md §13 S13
…e data-tools + deploy JAX entry points
- docs/{ADAPTERS,TRAINING,ARCHITECTURE,ACCURACY_CEILING}.md: v1
metrics disclaimer added at the top of each
- Dockerfile: runtime images bake --extra data-tools; dev images
add lab + dashboard + wandb on top
- deploy/pod.sh + deploy/vast.sh: examples updated to JAX
entry points and --logs-dir flag
- README.md: quick-start examples are JAX (scripts/train_jax* +
--config <json>)
Plan: docs/jax-migration.md §13 S14
… S7 / S10 public-API pins 374 tests now collect cleanly across the whole tree (``pawn`` JAX surface + scripts + legacy eval_suite + lab-side tests). The S3 placeholder skips in ``tests/test_public_api.py`` are replaced with real import-and-touch tests against every landed JAX surface: pawn.trainer (S6), pawn.adapters (S7), pawn.run_config (S4), pawn.logging (S4), pawn.sweep (S10). 55 tests green locally without JAX (the no-deps subset: test_public_api + test_jax_run_config + test_jax_logging). Full-suite green needs ``uv sync --extra rocm`` (JAX + torch); final smoke + remaining test runs land in S16. Plan: docs/jax-migration.md §13 S15
… v1 surface + align hybrid validator
Final-loop codex review surfaced 4 real issues, all applied:
Deletions (High-severity dead v1 files that would ImportError at
load time):
- pawn/{gpu,data,data_utils,lichess_cache,lichess_data}.py
- scripts/{train,eval_accuracy,eval_probes,run_evals_backbone,
benchmark}.py
Validator alignment (Medium):
- scripts/train_jax_adapter.py — the
--no-adapt-attn AND --no-adapt-ffn no-op guard now fires for
both bottleneck AND hybrid, matching
AdapterConfig._check_strategy_args. The CLI was rejecting it
only for bottleneck.
Plan: docs/jax-migration.md §13 S16
… MetricsLogger
Closes the deferred item from S12: the training drivers now emit
every metrics row through ``pawn.logging.MetricsLogger`` instead
of inline ``json.dumps(...)`` writes. This was a core motivator of
the rework — restoring the MetricsLogger module (S4) without
actually using it left the ``type`` discriminator, baseline
metadata, and NaN guard absent from real runs. The structural
excuse I gave at S12 (metrics I/O lives in the host script, not
the trainer module) was true but irrelevant: the fix was always a
script-level change, and it belonged in S12.
scripts/train_jax.py:
- ``MetricsLogger(args.logs_dir, run_prefix="jax_run",
device=_metrics_device())`` owns the run directory.
- ``log_config(**run_config)`` writes the ``type: "config"``
baseline row; ``write_config_json(**run_config)`` writes the
``config.json`` sidecar.
- The chunk loop calls ``logger.log_train(step=step_end, **row)``
— every row now carries ``type: "train"``, ``timestamp``,
``elapsed``, ``slug`` / ``hostname`` / ``git_hash``, and the
psutil + GPU memory stats. Per-record flush (SIGKILL-durable)
replaces the flush-every-10 batching — that is the §8.2 v1
contract.
- ``_slug()`` removed (MetricsLogger owns slug + run-dir naming);
unused ``datetime`` / ``os`` imports dropped.
scripts/train_jax_adapter.py:
- Same MetricsLogger wiring, ``run_prefix="jax_adapter_run"``,
``suffix=args.strategy`` so a logs/ listing is self-describing.
- **§8.5 schema fix:** adapter validation is now emitted as a
SEPARATE ``type: "val"`` record via ``logger.log_val`` rather
than as ``val_loss`` / ``val_n`` columns bolted onto the train
row. That is the v1 schema the dashboard's train/val chart
split keys on.
- W&B still gets the combined train+val view under one step
(W&B has no ``type`` discriminator).
GPU memory stats: ``_metrics_device()`` returns "gpu" when
``jax.default_backend()`` is an accelerator, so MetricsLogger
shells out to nvidia-smi / rocm-smi per record; "cpu" skips it.
Tests updated for the new (correct) schema:
- tests/scripts/test_train_jax.py — splits rows by ``type``;
asserts 1 config row + 2 train rows; ``step_end`` → ``step``.
- tests/scripts/test_train_jax_adapter.py — splits config /
train / val rows by ``type``; val assertions read the
separate ``type: "val"`` records; ``step_end`` → ``step``.
ALSO fixes a pre-existing S12 gap: the ``_STRATEGY_EXTRA_ARGS``
dict and several argv lists still used the pre-S12-rename flag
names (``--rank``, ``--sparse-density``, ``--specialized-d-*``,
``--bottleneck-no-*``, ``--no-film-output``) — collection-only
validation in S12 never exercised the argv values. Renamed to
the v1-canonical flags (``--lora-rank``, ``--density``,
``--d-model`` etc.) and updated the ``match=`` regexes for the
error messages S16 reworded.
Plan: docs/jax-migration.md §13 S12 (deferred item now closed) +
§8.2 + §8.5
Tests: 55 no-JAX-deps tests still green; the 6 script test files
collect cleanly (102 tests). Runtime exec of the script
smoke tests needs ``uv sync --extra rocm``.
…y to cover all post-logger setup (Codex) The MetricsLogger-wiring commit (cdb81ac) opened the ``try`` only at the chunk loop, so an exception during model init / adapter build / W&B setup / corpus reshape — anything between ``MetricsLogger(...)`` construction and the loop — left the metrics file handle unclosed (``logger.close()`` in the ``finally`` never ran). Both drivers now open the ``try`` immediately after ``logger.log_config(...)`` so the ``finally: logger.close()`` covers every code path past logger construction. The chunk loop's former inner ``try/finally`` collapses into the single outer one. scripts/train_jax.py — rewrapped via Edit. scripts/train_jax_adapter.py — the ~290-line span was re-indented programmatically (mechanical +4 indent of the post-logger block, inner try/finally removed) to avoid a hand-indent transcription error; ast.parse confirms the result. Plan: docs/jax-migration.md §13 S12 (followup round-1) Tests: 55 no-JAX-deps tests still green; 119 script + config + logging tests collect cleanly.
…data path
S16 deleted pawn/lichess_cache.py + pawn/lichess_data.py as broken
dead code — but that silently dropped PAWN's primary use case.
PAWN is a finetuning testbed; the realistic adapter task is
human-move prediction on Elo-stratified Lichess games. The random-
game proxy the adapter trainer fell back to is a verification aid,
not a real benchmark. S17 restores the capability JAX-native.
S17.1 — pawn/corpus.py: new public ``pack_corpus(move_ids,
game_lengths, outcome_offset, *, seq_len)`` — packs pre-tokenized
games into a ``Corpus``. ``generate_corpus`` (Rust random games)
and the Lichess path now share this back-end.
S17.2 — pawn/lichess_data.py (new, JAX-native; replaces the two
deleted v1 torch modules):
- ``load_lichess_corpus(source, *, elo_min, elo_max, min_ply,
seq_len, max_games, split, cache_dir)`` — scans the canonical
pre-tokenized Lichess parquet (HF repo or local path),
filters by Elo band (both players, elo_max exclusive) +
min_ply, packs into a ``Corpus``.
- On-disk cache under ``$HF_HOME/pawn-lichess-cache/<key>/``
(safetensors + meta.json + ``pawn._sentinel`` ``.complete``
sentinel). ``<key>`` is a SHA-256 of every filter param, so
a different Elo band never reuses a stale slice.
- ``make_epoch_schedule(n_pool, n_needed, *, seed)`` — tiles a
finite game pool across epochs with a fresh per-epoch
permutation (§6 "per-epoch index permutation").
- polars is a lazy import with an actionable error → the base
install stays polars-free; the parquet path needs the
``data-tools`` extra.
The v2 path is simpler than v1's: the JAX trainer is shape-static
(no bucketed dynamic padding — §8.4 dropped ``bucket_size``), so
this emits fixed-width ``[N, T]`` arrays and the cache stores
them verbatim. No ``torch.utils.data`` Dataset / collate.
S17.3 — scripts/train_jax_adapter.py: ``--pgn <hf-repo|path>`` +
``--elo-min`` / ``--elo-max`` / ``--min-ply`` / ``--max-games`` /
``--pgn-split`` / ``--cache-dir``. With ``--pgn`` the trainer
loads the finite Lichess slice, holds out ``--val-frac`` of the
distinct games as validation, and tiles the train pool across
epochs (``make_epoch_schedule``) to fill ``total_steps *
batch_size`` game-slots. Without ``--pgn`` the random-game proxy
is unchanged. The resolved ``config.json`` records the data-
source provenance (``data_source`` / ``pgn`` / ``elo_*``).
S17.4 — tests:
- tests/test_jax_lichess_data.py (18 tests) — pack_corpus
shapes/masks, make_epoch_schedule tiling + per-epoch
permutation + determinism, load_lichess_corpus against a
synthetic parquet (Elo filter, min_ply filter, cache
round-trip + key-sensitivity, max_games cap, long-game
truncation, empty-filter + missing-column errors).
- tests/scripts/test_train_jax_adapter.py — two ``--pgn`` E2E
cases (tiles a finite slice + writes the cache + trains
through MetricsLogger; rejects an over-small slice upfront).
- tests/test_public_api.py — pins the S17 surface.
Bug caught in review: pyright flagged a name collision — the
epoch-schedule variable was named ``sched``, shadowing the LR
``sched`` that ``make_optimizer`` consumes a few lines later. On
the ``--pgn`` path that would have fed the int64 index array into
the optimizer. Renamed to ``epoch_idx`` / ``train_idx``.
Docs: CLAUDE.md repo map + adapter-training section document the
``--pgn`` path; docs/jax-migration.md §2 / §10 / §13 updated with
an S17 entry that also records the S5.2 mischaracterization
(the v1 modules were torch-coupled, never "framework-agnostic").
Plan: docs/jax-migration.md §13 S17
Tests: 91 no-JAX-deps tests green (incl. 18 new lichess_data +
1 new public-API pin); pyright clean on pawn/corpus.py,
pawn/lichess_data.py, scripts/train_jax{,_adapter}.py;
66 script tests collect (incl. 2 new --pgn cases — they
run under uv sync --extra rocm + --extra data-tools).
…d-out val split by default
Two corrections to S17 the project owner flagged.
1. ``data-tools`` extra → base.
The project prefers a smaller extra surface to fewer-but-heavier
base deps. Polars, matplotlib, seaborn, jinja2, zstandard moved
into ``[project.dependencies]``; the ``data-tools`` extra is
removed. The Dockerfile drops every ``--extra data-tools``
reference. ``pawn/lichess_data.py`` switches from a lazy polars
import (with the "needs --extra data-tools" error) to a normal
top-level ``import polars as pl``. Tests drop their
``pytest.importorskip("polars")`` guards. Net: fewer extras,
no "forgot the extra → ImportError" footgun on the realistic
Lichess adapter path, and ``uv sync`` (no extras) produces a
working ``pawn.lichess_data`` / ``pawn.eval_suite`` /
``extract_lichess_parquet`` install. Extras now: ``rocm`` /
``cu128`` / ``torch-loader`` / ``dashboard`` / ``lab`` /
``wandb`` — every one is genuinely optional.
2. Lichess train / val split — use the held-out shards.
S17 took the first ``--val-frac`` of the loaded ``--pgn`` slice
as validation, which silently carved val OUT of train — leakage
when the source dataset has proper held-out splits. The canonical
``thomas-schweich/pawn-lichess-full`` dataset ships
``train`` (287 shards) + ``validation`` (10 shards) + ``test``
(9 shards). The script now defaults
``--pgn-val-split=validation`` and loads val from that held-out
split as a separate filtered slice. Pass ``--pgn-val-split ""``
to opt back into carve-from-train (the right behavior for a
single-file local source with no split structure).
``pawn.lichess_data._scan_parquet`` becomes split-aware for local
directories too — a dir with ``train-*.parquet`` /
``validation-*.parquet`` shards is scanned by split (matching
the HF datasets convention); dirs without that structure fall
back to "scan all parquets."
``scripts/train_jax_adapter.py`` records the val-source
provenance in ``config.json`` (``pgn_split``, ``pgn_val_split``).
Existing S17 single-file ``--pgn`` tests updated to pass
``--pgn-val-split ""``, and two new tests cover the held-out
path: ``test_load_lichess_corpus_split_prefixed_local_dir``
(unit-level, distinct outcome offsets per split prove no
cross-contamination) and
``test_lichess_pgn_path_uses_held_out_validation_split`` /
``test_lichess_pgn_path_carve_from_train_when_val_split_empty``
(script-level, verify config.json provenance + cache layout).
Plan: docs/jax-migration.md §13 S18
Tests: 92 no-JAX-deps tests green (+1 new local-dir split-prefixed
scan test); pyright clean on the touched modules.
…3 diagnostics
§5.1 of the v2-parity-gap audit asked for the
``outcome_prefix_trained`` gate (the ``{"_skipped": ...}`` sentinel
returned when the model wasn't trained with ``prepend_outcome=True``)
to be applied to ``outcome_signal_test``, ``prefix_continuation_test``,
and ``poisoned_prefix_test`` — the gate was only on
``impossible_task_test`` and ``improbable_task_test``. My S8.3 commit
claimed to extend the gate to all 5; in fact I brought ``pawn/
generation.py`` over verbatim from the backup (which itself only had
the gate on the latter two), so 3 of 5 stayed ungated. Fixing now.
All 5 diagnostics that condition on an outcome token at sequence
position 0 now have a keyword-only ``outcome_prefix_trained: bool``
argument and return a structured ``{"_skipped": ...}`` sentinel
when False. ``scripts/eval_generation_jax.py`` already had
``--outcome-prefix-trained`` / ``--no-outcome-prefix-trained``
plumbed for the original 2; that arg now also feeds the other 3.
Plan: docs/jax-migration.md §13 S8.3 (closing a deferred /
mis-claimed item)
Tests: parse + pyright clean on the touched modules; the existing
``tests/test_jax_generation.py`` exercises the per-diagnostic
contract and will need an outcome_prefix_trained kwarg on
the 3 newly-gated calls — flagged for the user to confirm
the test signatures.
A clean, single-document spec for the PyTorch → JAX framework swap,
written from the perspective of an agent starting from `origin/main`.
Highlights:
- **Acceptance criteria** (§3) — 20 concrete v1 workflows with
runnable verification commands. Section close re-grades the
relevant subset; nothing on this list is deferrable.
- **`origin/main` is the reference** (§2). When in doubt, read it,
run it, diff against it.
- **Section structure** (§10) — each S<N> ships Goal /
Deliverables / Verification / Definition-of-done blocks the
`review-spec-alignment` agent grades against.
- **Hard gate** — `review-spec-alignment` runs before every other
review lane at every chunk close, every section close, and the
final review. FAIL → finish the work, re-run; PASS → proceed.
- **`DEFERRALS.md` discipline** (§9.4) — deferrals only when the
work is nonsensical, impossible, or actively detrimental. No
user ACK gate; no approval ceremony. Documented in
`DEFERRALS.md` so future readers can audit.
- **Every adapter ports forward** including RoSA's three modes
(`rosa` / `retro-sparse` / `retro-bottleneck`) and the v1
`unfreeze_layers="5,6,7"` explicit-pick form. The migration
does not get to cull adapters; that's a design decision for a
later round.
- **Checkpoints preserve every `step_<N>`** — atomic write within
a save, but no rotation across saves. Pruning is the user's
call.
- **Single compatibility bridge** — `pawn.legacy.convert_legacy_checkpoint`
is the only loader between v1 HF checkpoints and the JAX
surface. No speculative "thin torch loader for external
consumers."
- **Polars / matplotlib / seaborn / jinja2 / zstandard in base** —
load-bearing for the Lichess adapter path; not gated behind
an extra.
- **Operational essentials** — HF-backed checkpoint push,
SIGTERM/graceful-shutdown, `--resume`, edge-case diagnostic
eval, all five generation diagnostics with the
`outcome_prefix_trained` gate — every one listed and verified.
Companion artifacts (in user-global `.claude/`, not in this repo):
- `.claude/agents/review-spec-alignment.md` — the hard-gate agent.
- `.claude/skills/review-driven-development/SKILL.md` — updated
to dispatch `review-spec-alignment` before the other review
lanes at every checkpoint.
Owner
Author
|
Closing this PR — the branch is being abandoned in favour of a fresh from-scratch migration. See docs/jax_migration_plan.md on the new |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Do not merge yet — per the user's directive, this PR is opened for review only. v2.0.0 is feature-complete on the integration branch.
What v2.0.0 is
PAWN 2.0.0 is a structural redesign on top of a complete framework swap:
Shared-weight supernetwork — one
PAWNModelhosts three nested variants (small,base,large) that share parameters. Training the supernet trains all three variants jointly via a single multi-variant loss; the smaller variants are sliced views of the supernet's weights at eval/adapter time.validate_nestedpins the nesting invariant at config-construction time.All-JAX training + eval stack — Equinox modules + Optax optimizers + numpy + safetensors. No PyTorch in the training or eval paths. The two remaining torch touchpoints are
pawn.torch_loader(thin loader for non-JAX consumers) andpawn._torch_legacy_fixture(frozen reference architecture used only by the legacy-converter parity tests). The CPU-jaxlib base install + therocm/cu128/torch-loaderextras keep the production surface torch-free unless you opt in.Eight adapter strategies, one CLI —
lora,film,unfreeze,bottleneck,hybrid,sparse,rosa(with three-phase schedule),specialized_clm(from-scratch). All dispatched viascripts/train_jax_adapter.py --strategy <name>.Atomic checkpoint format with SHA-256
.completesentinel —.tmp→ rename →.baksequence; interrupted overwrites always leave a recoverable checkpoint. Hashes are verified on every load.Legacy-checkpoint converter —
pawn.legacy.convert_legacy_checkpointloads the three published PyTorch checkpoints (pawn-small,pawn-base,pawn-large) and produces JAX-format outputs at the same architecture (fp32 max-Δlogit ≈ 5×10⁻⁶ on the published-checkpoint test).30 commits ahead of
main. +22,500 / −50,300 across 181 files — the deletions are the entire pre-v2 PyTorch surface (lab, eval_suite, sweep, wandb_utils, cotrain, data, the old test tree, etc.).Architecture map
main)jax_migration)PAWNCLMper sizesmall/base/largepawn.cotrain+scripts/train.pypawn.trainer(K-steplax.scan) +scripts/train_jax.pypawn.adapter_trainingpawn.adapter_trainer(two-tiereqx.partition) +scripts/train_jax_adapter.pyspecialized_clmdispatch)--strategypawn.eval_suite.*(PyTorch + polars + DataLoader)pawn.{eval,probes,generation,lichess_eval}(numpy + Rust engine)config.json+.completesentinel (SHA-256)dashboardextra — base install ships without the visualisation stackpawn.labuv.lockis substantially smaller.Supernet + variants
SUPERNET(=VARIANTS["large"])VARIANTS["base"]VARIANTS["small"]TINY_SUPERNET(verification scale)TINY_VARIANTS["base"]TINY_VARIANTS["small"]All production variants share
head_dim = 64. Legacy publishedpawn-largepredates the supernet (head_dim = 80,n_heads = 8); the converter preserves its exact hyperparameters as a standaloneModelConfigthat's not a nested slice.Training stack
lax.scanin both pretraining and adapter trainers.K * Bgames per scan call; sizeKso per-step throughput is host-bound rather than launch-bound. Statestepis ajax.Arrayscalar (not Python int — the latter retriggers JIT recompile every step; pinned by tests).eqx.partitionfor adapter training.eqx.partition(model, adapter_filter(model))produces a trainable subtree (adapters) and a frozen subtree (backbone); XLA dead-code-eliminates backbone gradients (~33% FLOP cut on the backward pass). Structural invariant — every array field ofstate.trainable.backboneisNoneafter partitioning — is pinned across all 6 backbone-wrapping strategies bytest_backbone_weights_are_frozen_all_strategies.optax.chain(clip_by_global_norm(1.0), adamw).decay_steps = total_steps(nottotal_steps - warmup— that double-subtracts; pinned).loss_mask-all-False chunk bypassesoptimizer.updateso AdamW's decoupled weight decay can't drift weights on zero-gradient padded steps.src_embed[s] + dst_embed[d] + promo_embed[p]— ~14.8× fewer params on the input embedding table than a flat1968 × d_modellookup.Adapter strategies
lorapawn.adapters.lorafilmpawn.adapters.filmunfreezepawn.adapters.unfreezeoptax.masked; the only strategy that usesgradient_maskbottleneckpawn.adapters.bottleneckhybridpawn.adapters.hybridsparsepawn.adapters.sparserosapawn.adapters.rosaadapter_filter, not viagradient_mask.specialized_clmpawn.adapters.specialized_clmEval suite
pawn.eval[0, NUM_ACTIONS); mask drops terminalmove_{N-1} → PADrow (argmax can't reach PAD, otherwise every game biased accuracy by ~1/avg_game_length).pawn.probeslax.scanyspawn.generationimpossible_task_test/improbable_task_testrequireoutcome_prefix_trained=True(otherwise return a{"_skipped": ...}sentinel — the conditioning signal only has interpretable meaning on prefix-trained models).zero_remaining_plysizes the prefix tomodel.cfg.max_seq_len - 1so the model genuinely has no room to generate.pawn.lichess_evalmax_games+_CACHE_VERSION); per-ply masking by side-to-move Elo so a 1500-vs-2500 game in the 1500 bucket scores only the 1500-player's plies.Dashboard (
pawn.dashboard)The Solara metrics viewer is preserved as an optional extra:
uv sync --extra dashboardinstallssolara/plotly/anywidget/pandas/starlette<1.0, andpython -m pawn.dashboard --host 127.0.0.1 --port 8765 --log-dir logsreads each run'smetrics.jsonland renders charts. The Docker runtime / dev images bundle the extra by default; Caddy on port 8888 reverse-proxies the Solara server. Gated byPAWN_DASHBOARD(default on); the entrypoint checkspython -c "import pawn.dashboard"before launching so a stripped-down install doesn't crash on startup.Multi-perspective review — 8 rounds
After the feature work landed, the integration branch went through
/subagent-review --pr 111 --auto --loop. Six lane agents (bug-detector / performance / type-correctness / test-risk / simplification / doc-accuracy) plus Codex ran in parallel each round; significant findings were fixed and the lanes re-ran until clean.7b9e09fpawn.lichess_eval._cache_keyomittedmax_games;pawn.probesaccidentalperm_key = keyoverwrite; RoSA inactive-target 16 MB/forward zeros allocation;test_backbone_weights_are_frozenextended from LoRA-only to all 6 backbone-wrapping strategies; 5 CLAUDE.md doc errors.79c9d59skipifon KV-cache bit-equivalence tests;eval_jaxupfront seq-len rejection test; lichess cache load-side guard test; 5 doc bugs.bf97b88pawn.evalmove-accuracy was counting terminalmove_N → PAD(Codex P2: biased every game by ~1/avg_game_length);try/finallycleanup on lichess tmp writes; Lichess-format PGN fixture so cache tests actually run;docs/ADAPTERS.mdstale-legacy header.76e6150prepend_outcome=Trueloss_mask was dropping the outcome→m1 supervision step (Codex P2, re-raised); pinning test for the PAD-mask fix;pawn/eval.pydocstring updated to reflect the new mask.cf17ab5deploy/entrypoint.shstill launched the deletedpawn.dashboardmodule (Codex P2 deployment break); upfront--val-every > 0validation;_CACHE_VERSIONbump prevents stale-cache silent reverts;prepend_outcome=True_materialisepinning test.abc329efilter_elo_slicewas filtering whole games only — opponent's out-of-band moves contaminated reported per-Elo accuracy (Codex P2 third raise). Implemented per-ply masking by mover-Elo (depends on prepend_outcome parity). New pinning tests + val-every-zero test. Phase-4 cleanup: stalepyproject.tomldashboard exclude,deploy/vast.shport-8888 forward.3b1a1e3side='white',side='black',prepend_outcome=Trueparity inversion).7e44607/81d327b/ca1b49cimpossible_task/improbable_taskonoutcome_prefix_trained;zero_remaining_plynow usesmodel.cfg.max_seq_len; fixed Rust engineparse_pgn_lichessinitialisingflat_tokensto0i16instead ofPAD_TOKEN; H:MM:SS clock formatter in the lichess test fixture; restored the Solara dashboard as an optionaldashboardextra (it wasn't actually PyTorch-coupled and the migration plan never argued for its removal — it just got swept up in the Phase 4 cleanup; the runtime image ships the extra by default and the entrypoint launches it underPAWN_DASHBOARD=1).Verification
uv run pyright pawn scripts tests— clean (modulo pre-existingscripts/vastai_score.pybs4 import; dashboard excluded per[tool.pyright].exclude).uv run --extra rocm pytest tests/— 338 passed + 0 skipped (started at 317 + 1; +21 net tests from review rounds).uv run --extra rocm --extra dashboard python -m pawn.dashboard --host 127.0.0.1 --port 18767 --log-dir /tmp— serves HTTP 200.pawn-small→ 4.41% accuracy.pawn-small:piece_type0.61 → 0.83,side_to_move→ 1.0 across layers.Out of scope (deferred)
state.steppreserved for metric monotonicity). Custom-schedule injection at the boundary is deferred.scripts/train_jax{,_adapter}.py: not in this release.pawn.lab+pawn.sweep+ Optuna integration were removed. Expected to re-land in 2.x on top of the JAX trainer.pawn.eval_suite.bounds(theoretical accuracy ceiling) — not ported.zero_remaining_plyrequires the corpus to be generated atcorpus_max_ply >= model.cfg.max_seq_len - 1to actually exercise the "no room to generate" semantic. The scenario silently skips on under-sized corpora rather than emitting misleading numbers; users running this on production-scale models should size--corpus-max-plyaccordingly.parse_pgn_enriched,parse_pgn_lichess_filtered,uci_moves_to_tokens) share the same 0-init-then-PAD pattern that was fixed inparse_pgn_lichess, but none are called from the v2 Python surface. Fixed in this PR only on the path that's actually used.Test plan
pawn/scripts/tests.pytestgreen (338 / 0 skipped) on CPU jaxlib.maturin develop --release) from a clean checkout.--extra dashboard.scripts/train_jax_adapter.py --strategy <name> --supernet tiny --variant base --total-steps 100 --k 50 --batch-size 2 --seq-len 16 --warmup-steps 5 --val-frac 0.1.uv run python scripts/convert_published_checkpoints.py) and runscripts/eval_jax.pyagainst it to confirm move-accuracy lands in the ~4% band forpawn-small.Per your directive, this PR remains unmerged pending your review.