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:
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user