refactor(sp5): Task A0 quality fix-up — rustdoc + HashSet overlap test
Address two code-quality review items on commit 6dcaf1a1c:
1. Convert file-level + Pearl-section comments from `//` to rustdoc
(`//!` module-level + `///` on first constant of each section).
Matches sp4_isv_slots.rs style; SP5 module now `cargo doc`-discoverable
as peer of SP4.
2. Replace spot-check assertions in slot_layout_no_overlaps_and_total_correct
with a HashSet enumerating every slot reachable through every accessor.
Asserts exactly 110 unique slots, min=174, max=285, the 2-slot carve-out
gap (278, 279) absent, and the set equals {174..278} ∪ {280..286}.
Test now actually verifies the no-overlaps invariant its name promised.
Also adds SP5 section to docs/isv-slots.md (Invariant 7 audit-doc update
required by pre-commit hook for cuda_pipeline component changes).
No constant values, accessor signatures, or fingerprint string changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,28 +1,31 @@
|
||||
// crates/ml/src/cuda_pipeline/sp5_isv_slots.rs
|
||||
//
|
||||
// SP5 per-branch + per-group adaptation ISV slot allocation.
|
||||
//
|
||||
// All adaptive values live on ISV via Pearls A+D smoothing per
|
||||
// `feedback_isv_for_adaptive_bounds`. Slot ranges:
|
||||
//
|
||||
// 174..226 Per-branch (4 branches × 13 quantities for Pearls 1/2/3 + shared signal)
|
||||
// 226..250 Per-group Adam β1/β2/ε (8 groups × 3 hparams)
|
||||
// 250..270 Per-branch IQN τ schedule (4 branches × 5 quantiles)
|
||||
// 270..274 Per-direction trail distance (4 directions)
|
||||
// 274..278 Per-branch num_atoms (4 branches)
|
||||
// 280..286 Cross-fold-persistent Kelly slots (NOT in fold-reset registry)
|
||||
//
|
||||
// Total: 110 new SP5 ISV slots (52 + 24 + 20 + 4 + 4 + 6).
|
||||
//! SP5 per-branch + per-group adaptation ISV slot allocation.
|
||||
//!
|
||||
//! All adaptive values live on ISV via Pearls A+D smoothing per
|
||||
//! `feedback_isv_for_adaptive_bounds`. Slot ranges:
|
||||
//!
|
||||
//! 174..226 Per-branch (4 branches × 13 quantities for Pearls 1/2/3 + shared signal)
|
||||
//! 226..250 Per-group Adam β1/β2/ε (8 groups × 3 hparams)
|
||||
//! 250..270 Per-branch IQN τ schedule (4 branches × 5 quantiles)
|
||||
//! 270..274 Per-direction trail distance (4 directions)
|
||||
//! 274..278 Per-branch num_atoms (4 branches)
|
||||
//! 278..280 Intentional 2-slot carve-out gap (boundary marker, not allocated)
|
||||
//! 280..286 Cross-fold-persistent Kelly slots (NOT in fold-reset registry)
|
||||
//!
|
||||
//! Total: 110 new SP5 ISV slots (52 + 24 + 20 + 4 + 4 + 6).
|
||||
|
||||
pub const SP5_SLOT_BASE: usize = 174;
|
||||
|
||||
// ── Pearl 1: per-branch atom span (16 slots) ──────────────────────────
|
||||
/// Adapts the atom distribution center per branch; tracks reward signal magnitude
|
||||
/// so C51 atoms span the live return distribution rather than a fixed range.
|
||||
pub const ATOM_V_CENTER_BASE: usize = 174; // [4]
|
||||
pub const ATOM_V_HALF_BASE: usize = 178; // [4]
|
||||
pub const ATOM_HEADROOM_BASE: usize = 182; // [4]
|
||||
pub const ATOM_CLIP_RATE_BASE: usize = 186; // [4]
|
||||
|
||||
// ── Pearl 2: per-branch loss budget (20 slots) ────────────────────────
|
||||
/// Adapts loss component budget weights per branch; balances C51/IQN/CQL/Ens
|
||||
/// contributions based on measured gradient magnitudes.
|
||||
pub const BUDGET_C51_BASE: usize = 190; // [4]
|
||||
pub const BUDGET_IQN_BASE: usize = 194; // [4]
|
||||
pub const BUDGET_CQL_BASE: usize = 198; // [4]
|
||||
@@ -30,31 +33,44 @@ pub const BUDGET_ENS_BASE: usize = 202; // [4]
|
||||
pub const FLATNESS_BASE: usize = 206; // [4]
|
||||
|
||||
// ── Pearl 3: per-branch NoisyNet σ (12 slots) ─────────────────────────
|
||||
/// Adapts NoisyNet exploration noise per branch; scales σ to maintain
|
||||
/// target branch entropy without manual schedule tuning.
|
||||
pub const NOISY_SIGMA_BASE: usize = 210; // [4]
|
||||
pub const SIGMA_FRACTION_BASE: usize = 214; // [4]
|
||||
pub const BRANCH_ENTROPY_BASE: usize = 218; // [4]
|
||||
|
||||
// ── shared signal: per-branch Q variance (4 slots) ────────────────────
|
||||
/// Q-value variance per branch; shared diagnostic consumed by Pearls 1 and 2.
|
||||
pub const Q_VAR_PER_BRANCH_BASE: usize = 222; // [4]
|
||||
|
||||
// ── Pearl 4: per-group Adam hyperparams (24 slots) ────────────────────
|
||||
/// Adapts Adam β1/β2/ε per param group using Wiener-optimal α (Pearl D);
|
||||
/// replaces fixed Adam hparams with signal-driven estimates.
|
||||
pub const ADAM_BETA1_BASE: usize = 226; // [8 param groups]
|
||||
pub const ADAM_BETA2_BASE: usize = 234; // [8]
|
||||
pub const ADAM_EPS_BASE: usize = 242; // [8]
|
||||
|
||||
// ── Pearl 5: per-branch IQN τ schedule (20 slots) ─────────────────────
|
||||
/// Adapts IQN quantile schedule per branch; concentrates τ samples where
|
||||
/// the distributional loss is highest.
|
||||
pub const IQN_TAU_BASE: usize = 250; // [4 branches × 5 quantiles]
|
||||
|
||||
// ── Pearl 8: per-direction trail distance (4 slots) ───────────────────
|
||||
/// Adapts trailing stop distance per direction (Short/Hold/Long/Flat);
|
||||
/// derived from per-direction return volatility on ISV.
|
||||
pub const TRAIL_DIST_PER_DIR_BASE: usize = 270; // [4 dirs: Short, Hold, Long, Flat]
|
||||
|
||||
// ── Pearl 1-ext: per-branch num_atoms (4 slots) ───────────────────────
|
||||
/// Adapts C51 atom count per branch; extends Pearl 1 atom-span adaptation
|
||||
/// with a dynamic resolution axis.
|
||||
pub const ATOM_NUM_ATOMS_BASE: usize = 274; // [4]
|
||||
|
||||
// ── Pearl 6: cross-fold-persistent Kelly slots (6 slots) ──────────────
|
||||
// NOT in fold-reset registry. Persist trade history across fold boundaries.
|
||||
// Allocated in separate range 280..286 with intentional 2-slot gap from
|
||||
// the rest of SP5 to make the carve-out structurally visible.
|
||||
/// Cross-fold-persistent Kelly fraction and trade history signals.
|
||||
/// NOT in fold-reset registry — these slots persist trade statistics
|
||||
/// across fold boundaries. Allocated in a separate range 280..286 with
|
||||
/// an intentional 2-slot gap (278, 279) from the per-fold block to make
|
||||
/// the carve-out structurally visible in the layout.
|
||||
pub const KELLY_F_SMOOTH_INDEX: usize = 280;
|
||||
pub const CONVICTION_SMOOTH_INDEX: usize = 281;
|
||||
pub const TRADE_VAR_SMOOTH_INDEX: usize = 282;
|
||||
@@ -103,41 +119,81 @@ pub const SP5_LAYOUT_FINGERPRINT_FRAGMENT: &str =
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn slot_layout_no_overlaps_and_total_correct() {
|
||||
// Per-branch block 174..226 (52 slots, contiguous)
|
||||
let mut slots = HashSet::new();
|
||||
|
||||
// Pearl 1: atom span (b in 0..4)
|
||||
for b in 0..4 {
|
||||
assert!(atom_v_center(b) >= 174 && atom_v_center(b) < 178);
|
||||
assert!(atom_v_half(b) >= 178 && atom_v_half(b) < 182);
|
||||
assert!(atom_headroom(b) >= 182 && atom_headroom(b) < 186);
|
||||
assert!(atom_clip_rate(b) >= 186 && atom_clip_rate(b) < 190);
|
||||
assert!(budget_c51(b) >= 190 && budget_c51(b) < 194);
|
||||
assert!(noisy_sigma(b) >= 210 && noisy_sigma(b) < 214);
|
||||
assert!(q_var_per_branch(b) >= 222 && q_var_per_branch(b) < 226);
|
||||
slots.insert(atom_v_center(b));
|
||||
slots.insert(atom_v_half(b));
|
||||
slots.insert(atom_headroom(b));
|
||||
slots.insert(atom_clip_rate(b));
|
||||
}
|
||||
// Per-group Adam block 226..250 (24 slots)
|
||||
// Pearl 2: loss budget (b in 0..4)
|
||||
for b in 0..4 {
|
||||
slots.insert(budget_c51(b));
|
||||
slots.insert(budget_iqn(b));
|
||||
slots.insert(budget_cql(b));
|
||||
slots.insert(budget_ens(b));
|
||||
slots.insert(flatness(b));
|
||||
}
|
||||
// Pearl 3: NoisyNet σ (b in 0..4)
|
||||
for b in 0..4 {
|
||||
slots.insert(noisy_sigma(b));
|
||||
slots.insert(sigma_fraction(b));
|
||||
slots.insert(branch_entropy(b));
|
||||
}
|
||||
// Shared signal: Q variance (b in 0..4)
|
||||
for b in 0..4 {
|
||||
slots.insert(q_var_per_branch(b));
|
||||
}
|
||||
// Pearl 4: per-group Adam hparams (g in 0..8)
|
||||
for g in 0..8 {
|
||||
assert!(adam_beta1(g) >= 226 && adam_beta1(g) < 234);
|
||||
assert!(adam_beta2(g) >= 234 && adam_beta2(g) < 242);
|
||||
assert!(adam_eps(g) >= 242 && adam_eps(g) < 250);
|
||||
slots.insert(adam_beta1(g));
|
||||
slots.insert(adam_beta2(g));
|
||||
slots.insert(adam_eps(g));
|
||||
}
|
||||
// IQN τ block 250..270 (20 slots)
|
||||
// Pearl 5: IQN τ schedule (b in 0..4, q in 0..5)
|
||||
for b in 0..4 {
|
||||
for q in 0..5 {
|
||||
let s = iqn_tau(b, q);
|
||||
assert!(s >= 250 && s < 270);
|
||||
slots.insert(iqn_tau(b, q));
|
||||
}
|
||||
}
|
||||
// Trail + num_atoms 270..278 (8 slots)
|
||||
// Pearl 8: trail distance (d in 0..4)
|
||||
for d in 0..4 {
|
||||
assert!(trail_dist_per_dir(d) >= 270 && trail_dist_per_dir(d) < 274);
|
||||
assert!(atom_num_atoms(d) >= 274 && atom_num_atoms(d) < 278);
|
||||
slots.insert(trail_dist_per_dir(d));
|
||||
}
|
||||
assert_eq!(SP5_SLOT_END, 286);
|
||||
// SP5_PRODUCER_COUNT counts only allocated slots: 110
|
||||
// 52 per-branch + 24 Adam + 20 IQN τ + 4 trail + 4 num_atoms + 6 Kelly = 110
|
||||
assert_eq!(SP5_PRODUCER_COUNT, 110);
|
||||
// Pearl 1-ext: num_atoms (b in 0..4)
|
||||
for b in 0..4 {
|
||||
slots.insert(atom_num_atoms(b));
|
||||
}
|
||||
// Pearl 6: cross-fold-persistent Kelly (6 scalar constants)
|
||||
slots.insert(KELLY_F_SMOOTH_INDEX);
|
||||
slots.insert(CONVICTION_SMOOTH_INDEX);
|
||||
slots.insert(TRADE_VAR_SMOOTH_INDEX);
|
||||
slots.insert(KELLY_SAMPLE_COUNT_INDEX);
|
||||
slots.insert(WIN_RATE_SMOOTH_INDEX);
|
||||
slots.insert(LOSS_RATE_SMOOTH_INDEX);
|
||||
|
||||
// 1. Exactly 110 unique slots.
|
||||
assert_eq!(slots.len(), 110, "expected 110 unique slots, got {}", slots.len());
|
||||
|
||||
// 2. Min slot is SP5_SLOT_BASE = 174.
|
||||
assert_eq!(*slots.iter().min().unwrap(), 174);
|
||||
|
||||
// 3. Max slot is SP5_SLOT_END - 1 = 285.
|
||||
assert_eq!(*slots.iter().max().unwrap(), 285);
|
||||
|
||||
// 4. Intentional carve-out gap (278, 279) is absent.
|
||||
assert!(!slots.contains(&278), "slot 278 must be absent (carve-out gap)");
|
||||
assert!(!slots.contains(&279), "slot 279 must be absent (carve-out gap)");
|
||||
|
||||
// 5. Set equals {174..278} ∪ {280..286} — no holes, no overlaps.
|
||||
let expected: HashSet<usize> = (174..278).chain(280..286).collect();
|
||||
assert_eq!(slots, expected, "slot set does not match expected {{174..278}} ∪ {{280..286}}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -119,3 +119,43 @@ return the absolute slot index for a given group/branch.
|
||||
|
||||
Producers and consumers will be wired in subsequent SP4 layer-B and layer-C
|
||||
tasks; until then the 40 slots are zero-initialized and reserved.
|
||||
|
||||
## SP5: Per-branch + per-group adaptation layer (Task A0 reservation)
|
||||
|
||||
SP5 Task A0 extends `ISV_TOTAL_DIM` from 173 → 286 by reserving 110 slots at
|
||||
`[174..278) ∪ [280..286)`. An intentional 2-slot carve-out gap at `[278..280)`
|
||||
separates the per-fold block from the cross-fold-persistent Kelly block.
|
||||
Constants live in `crates/ml/src/cuda_pipeline/sp5_isv_slots.rs`.
|
||||
|
||||
| Range | Base constant | Family | Pearl | Purpose |
|
||||
|---|---|---|---|---|
|
||||
| [174..178) | `ATOM_V_CENTER_BASE` | per-branch [4] | Pearl 1 | C51 atom distribution center |
|
||||
| [178..182) | `ATOM_V_HALF_BASE` | per-branch [4] | Pearl 1 | C51 atom half-width |
|
||||
| [182..186) | `ATOM_HEADROOM_BASE` | per-branch [4] | Pearl 1 | C51 atom headroom |
|
||||
| [186..190) | `ATOM_CLIP_RATE_BASE` | per-branch [4] | Pearl 1 | C51 atom clip rate |
|
||||
| [190..194) | `BUDGET_C51_BASE` | per-branch [4] | Pearl 2 | C51 loss budget weight |
|
||||
| [194..198) | `BUDGET_IQN_BASE` | per-branch [4] | Pearl 2 | IQN loss budget weight |
|
||||
| [198..202) | `BUDGET_CQL_BASE` | per-branch [4] | Pearl 2 | CQL loss budget weight |
|
||||
| [202..206) | `BUDGET_ENS_BASE` | per-branch [4] | Pearl 2 | Ensemble loss budget weight |
|
||||
| [206..210) | `FLATNESS_BASE` | per-branch [4] | Pearl 2 | Loss flatness diagnostic |
|
||||
| [210..214) | `NOISY_SIGMA_BASE` | per-branch [4] | Pearl 3 | NoisyNet σ level |
|
||||
| [214..218) | `SIGMA_FRACTION_BASE` | per-branch [4] | Pearl 3 | NoisyNet σ fraction |
|
||||
| [218..222) | `BRANCH_ENTROPY_BASE` | per-branch [4] | Pearl 3 | Branch action entropy |
|
||||
| [222..226) | `Q_VAR_PER_BRANCH_BASE` | per-branch [4] | shared | Q-value variance |
|
||||
| [226..234) | `ADAM_BETA1_BASE` | per-group [8] | Pearl 4 | Adam β1 per param group |
|
||||
| [234..242) | `ADAM_BETA2_BASE` | per-group [8] | Pearl 4 | Adam β2 per param group |
|
||||
| [242..250) | `ADAM_EPS_BASE` | per-group [8] | Pearl 4 | Adam ε per param group |
|
||||
| [250..270) | `IQN_TAU_BASE` | per-branch×quantile [4×5] | Pearl 5 | IQN τ schedule |
|
||||
| [270..274) | `TRAIL_DIST_PER_DIR_BASE` | per-direction [4] | Pearl 8 | Trail stop distance |
|
||||
| [274..278) | `ATOM_NUM_ATOMS_BASE` | per-branch [4] | Pearl 1-ext | C51 atom count |
|
||||
| [278..280) | — | gap | — | Intentional carve-out (not allocated) |
|
||||
| [280] | `KELLY_F_SMOOTH_INDEX` | scalar | Pearl 6 | Kelly fraction EMA (cross-fold) |
|
||||
| [281] | `CONVICTION_SMOOTH_INDEX` | scalar | Pearl 6 | Conviction EMA (cross-fold) |
|
||||
| [282] | `TRADE_VAR_SMOOTH_INDEX` | scalar | Pearl 6 | Trade variance EMA (cross-fold) |
|
||||
| [283] | `KELLY_SAMPLE_COUNT_INDEX` | scalar | Pearl 6 | Kelly sample count (cross-fold) |
|
||||
| [284] | `WIN_RATE_SMOOTH_INDEX` | scalar | Pearl 6 | Win rate EMA (cross-fold) |
|
||||
| [285] | `LOSS_RATE_SMOOTH_INDEX` | scalar | Pearl 6 | Loss rate EMA (cross-fold) |
|
||||
|
||||
Kelly slots `[280..286)` are NOT in the fold-reset registry. All other SP5 slots
|
||||
are per-fold and reset at fold boundaries. Task A0 is reservation-only; producers
|
||||
and consumers land in subsequent SP5 tasks.
|
||||
|
||||
Reference in New Issue
Block a user