config(dqn): strip H100-tuned VRAM overrides from dqn-production.toml

dqn-production.toml hard-coded three H100-tuned values that shadow
GpuProfile auto-detection: batch_size=16384, buffer_size=500K,
gpu_n_episodes=4096. After the L40S-default flip lands (prior commit
8b8bb1af7), workflow `train-ft8ph` deterministically OOMed at fold 0/1/2
with `build_next_states_f32` 4 GiB alloc — because the H100-sized
hyperparams.batch_size + buffer_size + gpu_n_episodes ate ~38 GB of the
L40S's 46 GB usable VRAM before the rollout step.

DqnTrainingProfile.apply_to() runs AFTER train_baseline_rl.rs populates
hyperparams from GpuProfile, so the production TOML always wins. All
three fields are `Option<...>` in the TOML schema — removing the lines
turns apply_to into a no-op for them, and the GpuProfile-detected values
flow through:

  field            | L40S  | H100   | (was forced)
  batch_size       | 4096  | 8192   | 16384
  buffer_size      | 300K  | 500K   | 500K
  gpu_n_episodes   | 2048  | 4096   | 4096

Two pinned assertions in training_profile.rs::tests checked the old
contract `hp.batch_size == 16384`. Rewritten to assert
`hp.batch_size == baseline_batch_size` — locks the new contract that
VRAM-tuned values stay GpuProfile-sourced.

Lib suite 1016/0 green. Audit doc updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-14 15:05:06 +02:00
parent 8b8bb1af70
commit 0acf77e656
3 changed files with 69 additions and 5 deletions

View File

@@ -13,7 +13,11 @@
[training]
epochs = 200
batch_size = 16384
# batch_size: removed 2026-05-14 — sourced from `GpuProfile.training.batch_size`
# (config/gpu/*.toml: H100=8192, L40S=4096, A100=2048, RTX3050=64). Hard-coded
# 16384 here was H100-tuned and caused OOM on L40S after the 2026-05-14
# argo gpu-pool default flip from H100 → L40S (workflow train-ft8ph: next_states
# alloc 4 GiB collision at fold 0 from compounded H100-sized buffers).
learning_rate = 1e-5
gamma = 0.99
weight_decay = 0.0001
@@ -52,7 +56,9 @@ q_gap_threshold = 0.05
noise_sigma = 0.1
[replay_buffer]
buffer_size = 500000
# buffer_size: removed 2026-05-14 — sourced from `GpuProfile.training.buffer_size`
# (config/gpu/*.toml: H100=500K, L40S=300K, A100=200K, RTX3050=5K). Hard-coded
# 500_000 here was H100-tuned. See [training].batch_size comment.
min_replay_size = 1000
per_alpha = 0.3 # was 0.6. Lower = more uniform. Dense micro-rewards have tiny TD-errors, high alpha ignores them.
per_beta_start = 0.4
@@ -64,7 +70,12 @@ patience = 20
min_epochs_before_stopping = 80
[experience]
gpu_n_episodes = 4096
# gpu_n_episodes: removed 2026-05-14 — sourced from `GpuProfile.experience.
# gpu_n_episodes` (config/gpu/*.toml: H100=4096, L40S=2048, A100=Some(?),
# RTX3050=Some(?)). Hard-coded 4096 here was H100-tuned; on L40S the
# `build_next_states_f32` rollout-step alloc = n_episodes × 2 × timesteps ×
# state_dim × 4 = 4 GiB collides with the rest of the collector VRAM by the
# rollout phase (workflow train-ft8ph repro). See [training].batch_size comment.
initial_capital = 35000.0
tx_cost_multiplier = 0.18 # bps: IBKR ES RT = $4.50/contract ($0.85 comm + $1.38 exch + $0.02 reg × 2 sides). At ES=$5100: 0.18 bps × 5100 × 0.0001 = 0.09 pts = $4.50

View File

@@ -1198,12 +1198,18 @@ mod tests {
#[test]
fn test_apply_to_dqn_production() {
let mut hp = crate::trainers::dqn::DQNHyperparameters::conservative();
let baseline_batch_size = hp.batch_size;
let profile = DqnTrainingProfile::load("dqn-production");
profile.apply_to(&mut hp);
// [training] values must have been applied
assert_eq!(hp.epochs, 200);
assert_eq!(hp.batch_size, 16384);
// batch_size: NOT overridden by dqn-production.toml (removed 2026-05-14
// — H100-tuned 16384 caused OOM on L40S default; the value now flows
// from `GpuProfile.training.batch_size`). Assertion: apply_to leaves
// `batch_size` at the conservative() baseline so the GpuProfile-supplied
// value (set earlier in train_baseline_rl.rs:443) flows through cleanly.
assert_eq!(hp.batch_size, baseline_batch_size);
assert!((hp.learning_rate - 1e-5).abs() < 1e-10);
// OFI MBP-10 data dir should be set in production profile
@@ -1468,11 +1474,15 @@ mod tests {
fn test_production_profile_applies_all_sections() {
let profile = DqnTrainingProfile::load("dqn-production");
let mut hp = crate::trainers::dqn::DQNHyperparameters::conservative();
let baseline_batch_size = hp.batch_size;
profile.apply_to(&mut hp);
// [training] section
assert_eq!(hp.epochs, 200);
assert_eq!(hp.batch_size, 16384);
// batch_size: see test_apply_to_dqn_production — VRAM-tuned values flow
// from GpuProfile, not the training TOML. apply_to leaves the conservative
// baseline in place so GpuProfile values aren't shadowed.
assert_eq!(hp.batch_size, baseline_batch_size);
assert!((hp.learning_rate - 1e-5).abs() < 1e-10);
assert!((hp.gamma - 0.99).abs() < 0.001);
assert!((hp.reward_scale - 1.0).abs() < 0.01);

View File

@@ -2,6 +2,49 @@
**Status:** Populated during Plan 1 Task 6 (A.5 orphan audit). Updated on every commit per Invariant 7.
## 2026-05-14 — config(dqn): strip H100-tuned VRAM overrides from dqn-production.toml
**Branch:** `sp20-aux-h-fixed`. **Trigger:** workflow `train-ft8ph` OOM at
`build_next_states_f32` 4 GiB alloc on fold 0/1/2 after the L40S-default
flip on the prior commit.
**Root cause:** `config/training/dqn-production.toml` hard-coded three
H100-tuned values that shadow `GpuProfile` auto-detection:
| field | dqn-production | GpuProfile L40S | GpuProfile H100 |
|-------|---------------:|----------------:|----------------:|
| `batch_size` | 16384 | 4096 | 8192 |
| `buffer_size` | 500K | 300K | 500K |
| `gpu_n_episodes` | 4096 | 2048 | 4096 |
The training profile loader's `apply_to(&mut hyperparams)` runs AFTER
`train_baseline_rl.rs:443-490` populates hyperparams from `GpuProfile`
— so the production TOML always wins. On L40S (46 GB usable, but only
~7.86 GB free by the time the rollout-step `next_states` alloc fires
because the H100-sized batch/buffer/episode buffers were already
committed), the 4 GiB `n_episodes × 2 × timesteps × state_dim × 4 =
4096 × 2 × 1000 × 128 × 4` allocation OOMs.
**Fix:** delete the three lines from `dqn-production.toml`. All three
fields are `Option<...>` in the TOML schema, so removal turns
`apply_to` into a no-op for them; values then flow cleanly from
`GpuProfile` (auto-detected per `profile_name_for_device("NVIDIA L40S")
→ "l40s"`). H100 detection still gets H100 values; L40S gets L40S
values; A100 / RTX3050 likewise.
**Test updates:** two pinned assertions in `training_profile.rs::tests`
(`test_apply_to_dqn_production` line 1206, `test_production_profile_
applies_all_sections` line 1475) checked `hp.batch_size == 16384` after
apply. Rewritten to check `hp.batch_size == baseline_batch_size` (i.e.,
apply_to is a no-op on this field) — locks the new contract that VRAM-
tuned values stay GpuProfile-sourced.
**Tests:** all 19 `training_profile` tests pass. Full lib suite
**1016/0 green** maintained.
**Touched:** `config/training/dqn-production.toml`,
`crates/ml/src/training_profile.rs` (test updates only).
## 2026-05-14 — SP22 H6 vNext: FoldReset registry entries for B5b/C collector buffers
**Branch:** `sp20-aux-h-fixed`. **Builds on:** `4b40710b7` (B5b-2 collector