Files
esh-pfi-infrastructure/scripts/training-probes
vh 7db6c44bcd feat(r49-prep): author-voice LoRA regime prep on gx10 — carriers staged, throughput measured, adapters secured
Prep for the BabyBronte / brokkr-smithy R49 author-voice adapter regime, plus
the operator's "keep the adapter" ruling made durable.

Measured on pfi-gx10 (GB10, sm_121), n=10 per arm after 3 warmup steps, seq
4096, LoRA r=32 on q/k/v/o + MLP, bf16, sdpa, grad-checkpointing on:

  Qwen3-0.6B-Base    dense    0.616 B   1.707 s/step   2,399 tok/s
  Qwen3-1.7B-Base    dense    1.755 B   2.895 s/step   1,415 tok/s
  Qwen3.5-0.8B-Base  hybrid   0.765 B   7.581 s/step     540 tok/s

The dense 1.755 B carrier trains 2.6x faster than the hybrid 0.765 B one on 2.3x
the parameters (~6x per parameter), with more LoRA modules adapted (196 vs 96).
Spreads of 0.6-2.6% put instrument noise an order of magnitude below the effect.
Cause: Qwen3.5 is 18 linear-attention (SSM) layers to 6 attention, and no fused
linear-attention kernel is installed on the box. Grad checkpointing is not the
culprit (19%, and saves 2.6x memory). Batching is not the lever for either
family -- both sit at this box's roofline at batch 1.

Projected per voice on a Brontë-scale corpus: dense 0.6B 2.7 h, dense 1.7B
4.6 h, hybrid 0.8B 12 h. The hybrid would take longer than the 7 h 26B-A4B tune
the regime exists to replace, so the carrier family is now an open decision with
a recommendation for the dense Qwen3 line -- the design doc's original pin.

Two further Qwen3.5 findings, both measured rather than read off the config: the
Base checkpoints ship a vision tower (153/297 model.visual.* Linear tensors that
target_modules="all-linear" would train on text) and an MTP head, both dropped
for free by loading through AutoModelForCausalLM -- which renames modules
relative to the vLLM serving path, so adapter binding needs the
sampled-target-changed check on the serving side; and cross-document packing is
unsafe because SSM state ignores the attention mask, breaking the per-copy
name-consistency invariant the design doc calls sacred. Neither exists on dense.

Adapter disposition, per the operator's ruling: all five gx10-resident ERP
adapters (run-03c/04/05/06/07) mirrored to ana-ml2:/tank/erp-tune/run-<N>/adapter
matching the layout runs 01-03 already used, byte-totals identical both sides and
sha256 matching on every adapter_model.safetensors. /tank/* is deliberately
excluded from ana-ml2's restic sources, so the profile gains one documented
carve-out for /tank/erp-tune/run-*/adapter, verified by resticprofile --dry-run
to expand to exactly those eight paths.

Nothing is training and nothing is queued.
2026-09-09 22:41:47 -07:00
..

Training throughput probes

Instruments for finding where a training step's time actually went. Written 2026-08-24 during the Gemma-4 26B-A4B ERP/RP tune investigation; the lessons they produced live in docs/pfi/training-throughput-playbook.md.

These are diagnostic instruments, not production code. They hard-code paths for that run. Adapt the constants at the top; keep the measurement design.

The probes

script settles GPU runtime
step0_mask.py mask band structure; which layers keep the is_causal fast path no ~30 s
step2_padding.py padding waste, length distribution, CE chunk sizing no ~2 min
step_bucket.py bucketing gain, bucket-size sweep, source diversity no ~3 min
step1_profile.py scaling fit, padding penalty, CE wall clock, kernel table yes ~15 min

Run in that order. Only the last needs the real checkpoint, and it wants an idle card — it loads ~48 GiB and peaks near 77 GiB at 2 × 16,384.

Design rules worth preserving when you adapt these

step1_profile.py reuses the harness's own discover_target_modules and replicates its compute_loss byte-for-byte rather than re-implementing the step. A probe that reimplements the training step measures the probe. If you port this, keep the import from the real harness.

step0_mask.py needs no weights and no GPU — SDPA backend selection and mask construction depend on shapes, dtype and mask presence, not on weight values. That is what makes the correctness assertion cheap enough to run before every job.

The scaling test takes three points, not two. Two points over three plausible terms (quadratic, linear, fixed-per-batch) is underdetermined; see playbook §1.1 for the hour that cost.

step_bucket.py sweeps bucket size deliberately. The first version re-sorted within each bucket, which silently collapsed every bucket size to a full global sort and made the sweep a no-op. If you change the pairing logic, check that the sweep still varies something.

Raw evidence

step1-profile-output-2026-08-24.txt is the unedited output of the run the playbook's numbers come from — scaling points, padding penalty, CE timing, and the full key_averages() kernel table. Kept so the claims can be re-derived rather than taken on faith.

⚠ That table double-counts: key_averages() lists both the ATen op and the CUDA kernel it launched, each carrying the same self device time. Sum device kernel rows only. See playbook §3.4.