feat(dqn-v2): Plan 4 Task 1A — feature-group index ranges for VSN/attention consumers

Adds 12 new `SL_*_GROUP_BEGIN/END` macros to `state_layout.cuh` covering the
6 feature groups (market/ofi/tlob/mtf/portfolio/plan_isv), plus
`SL_NUM_FEATURE_GROUPS=6` and `SL_MAX_FEATURE_GROUP_DIM=42` (= largest group
dim, MARKET_DIM). Six new `static_assert`s anchor each group dim ≤ max and
confirm contiguity / start-at-zero / end-at-padding invariants.

Rust mirror in `crates/ml-core/src/state_layout.rs` exposes:
- `SL_NUM_FEATURE_GROUPS`
- `SL_MAX_FEATURE_GROUP_DIM`
- `FEATURE_GROUP_RANGES: [(usize, usize); 6]` (half-open `[begin, end)`,
  `plan_isv` ends at `PADDING_START` — padding is not a group)
- `FEATURE_GROUP_NAMES: [&str; 6]`

Three `const _: () = assert!(...)` blocks validate (1) first range starts at
0, (2) last range ends at `PADDING_START`, (3) every adjacent pair
satisfies `ranges[g].end == ranges[g+1].begin` (no gaps), (4) every group
dim ≤ `SL_MAX_FEATURE_GROUP_DIM`.

Group dims at this commit: market=42, ofi=32, tlob=16, mtf=16, portfolio=8,
plan_isv=7 — total 121 = `PADDING_START`.

Prerequisite for Plan 4 Task 1B (E.1 Variable Selection Network — pre-trunk
per-group softmax-over-groups gating), Task 5 Mode B (per-group ISV
diagnostics), and Task 4 follow-on (group-aware encoder interface).

Additive only — ZERO production callers in this commit. No new module /
kernel / ISV slot / param tensor / Orphan row. No fingerprint change (group
ranges are derivable from existing `SL_*_START` constants and contribute no
new structural-hash invariant). cargo check clean at 11 warnings (workspace
baseline preserved); cargo build compiles all kernel cubins (state_layout.cuh
edit triggers full kernel rebuild — no compile errors).

Audit doc: 1 new entry under "Plan 4 Task 1A".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-04-25 17:31:38 +02:00
parent 005ed3a4f9
commit 475101054d
3 changed files with 124 additions and 0 deletions

View File

@@ -162,3 +162,83 @@ pub const MAG_QUARTER: usize = 0; // 0.25× max_position
pub const MAG_HALF: usize = 1; // 0.50× max_position
pub const MAG_FULL: usize = 2; // 1.00× max_position
pub const NUM_MAGNITUDES: usize = 3;
// ────────────────────────────────────────────────────────────────────────────
// Feature-group ranges — Plan 4 Task 1A (E.1 VSN prerequisite).
//
// Mirrors the `SL_*_GROUP_BEGIN/END` macros in
// `crates/ml/src/cuda_pipeline/state_layout.cuh`. Ranges are half-open
// `[begin, end)`. The last group (`plan_isv`) ends at `PADDING_START` —
// the 7-element zero-pad block is *not* a feature group.
//
// Consumers:
// * Plan 4 Task 1 (E.1) Variable Selection Network — gates each group's
// features by a learned softmax-over-groups mask before the trunk.
// * Plan 4 Task 5 (E.5) attention-weight ISV diagnostics.
// * Plan 4 Task 4 (E.4) encoder-decoder separation — group-aware
// `StateEncoder` interface.
// ────────────────────────────────────────────────────────────────────────────
pub const SL_NUM_FEATURE_GROUPS: usize = 6;
/// Largest per-group dimension (currently `MARKET_DIM = 42`). Used for kernel-
/// side fixed-size scratch arrays so every group fits a uniform stride.
pub const SL_MAX_FEATURE_GROUP_DIM: usize = MARKET_DIM;
/// `[begin, end)` ranges per feature group, in declaration order.
/// Order matches `FEATURE_GROUP_NAMES`.
pub const FEATURE_GROUP_RANGES: [(usize, usize); SL_NUM_FEATURE_GROUPS] = [
(MARKET_START, OFI_START), // [0] market — 42 dims
(OFI_START, TLOB_START), // [1] ofi — 32 dims
(TLOB_START, MTF_START), // [2] tlob — 16 dims
(MTF_START, PORTFOLIO_START), // [3] mtf — 16 dims
(PORTFOLIO_START, PLAN_ISV_START), // [4] portfolio — 8 dims
(PLAN_ISV_START, PADDING_START), // [5] plan_isv — 7 dims
];
/// Group names — index-aligned with `FEATURE_GROUP_RANGES`. Used for ISV
/// diagnostic labels (`VSN_MASK_GROUP_<NAME>_EMA`) and audit-doc rows.
pub const FEATURE_GROUP_NAMES: [&str; SL_NUM_FEATURE_GROUPS] = [
"market",
"ofi",
"tlob",
"mtf",
"portfolio",
"plan_isv",
];
// ── Compile-time invariants ───────────────────────────────────────────────
const _: () = assert!(
FEATURE_GROUP_RANGES[0].0 == 0,
"feature groups must start at offset 0"
);
const _: () = assert!(
FEATURE_GROUP_RANGES[SL_NUM_FEATURE_GROUPS - 1].1 == PADDING_START,
"feature groups must end at PADDING_START — padding is not a group"
);
const _: () = {
// Every adjacent pair (g, g+1) must satisfy ranges[g].end == ranges[g+1].begin
// so the groups contiguously cover [0, PADDING_START) with no gaps.
let mut g = 0;
while g + 1 < SL_NUM_FEATURE_GROUPS {
assert!(
FEATURE_GROUP_RANGES[g].1 == FEATURE_GROUP_RANGES[g + 1].0,
"feature group ranges must be contiguous (no gaps)"
);
g += 1;
}
};
const _: () = {
// Each group's dim must fit within SL_MAX_FEATURE_GROUP_DIM so kernel
// scratch arrays sized to the maximum can hold any group.
let mut g = 0;
while g < SL_NUM_FEATURE_GROUPS {
let (b, e) = FEATURE_GROUP_RANGES[g];
assert!(e >= b, "feature group end must not precede begin");
assert!(
e - b <= SL_MAX_FEATURE_GROUP_DIM,
"feature group dim must not exceed SL_MAX_FEATURE_GROUP_DIM"
);
g += 1;
}
};

View File

@@ -151,6 +151,31 @@
#define ISV_SEED_STEPS_DONE_IDX 83 // == SEED_STEPS_DONE_INDEX — cumulative seed-phase steps completed (Plan 3 Task 8 B.3)
#define ISV_SEED_FRAC_EMA_IDX 84 // == SEED_FRAC_EMA_INDEX — adaptive EMA of (1 - done/target) ∈ [0, 1] (Plan 3 Task 8 B.3; consumed by Task 9 CQL ramp)
// ────────────────────────────────────────────────────────────────────────────
// Feature-group ranges — Plan 4 Task 1A (E.1 VSN prerequisite).
// Referenced by: VSN feature selection (E.1), attention diagnostics (E.5),
// encoder-decoder separation (E.4). Ranges are half-open `[BEGIN, END)`.
// `SL_PLAN_ISV_GROUP_END` aliases `SL_PADDING_START` because the 7-element
// plan-ISV block is the last group before the 8-alignment padding.
// Invariant 8: every range has a named constant; raw `SL_*_START` arithmetic
// for group bounds outside this header is a lint violation.
// ────────────────────────────────────────────────────────────────────────────
#define SL_MARKET_GROUP_BEGIN SL_MARKET_START
#define SL_MARKET_GROUP_END SL_OFI_START
#define SL_OFI_GROUP_BEGIN SL_OFI_START
#define SL_OFI_GROUP_END SL_TLOB_START
#define SL_TLOB_GROUP_BEGIN SL_TLOB_START
#define SL_TLOB_GROUP_END SL_MTF_START
#define SL_MTF_GROUP_BEGIN SL_MTF_START
#define SL_MTF_GROUP_END SL_PORTFOLIO_START
#define SL_PORTFOLIO_GROUP_BEGIN SL_PORTFOLIO_START
#define SL_PORTFOLIO_GROUP_END SL_PLAN_ISV_START
#define SL_PLAN_ISV_GROUP_BEGIN SL_PLAN_ISV_START
#define SL_PLAN_ISV_GROUP_END SL_PADDING_START
#define SL_NUM_FEATURE_GROUPS 6
#define SL_MAX_FEATURE_GROUP_DIM 42 // == SL_MARKET_DIM (largest group)
// ── Compile-time checks ──
static_assert(SL_PADDING_START + SL_PADDING_DIM == SL_STATE_DIM,
"State layout dimensions must sum to SL_STATE_DIM");
@@ -158,6 +183,23 @@ static_assert(SL_STATE_DIM % 8 == 0,
"SL_STATE_DIM must be 8-aligned for tensor core cuBLAS");
static_assert(SL_STATE_DIM <= SL_STATE_DIM_PADDED,
"SL_STATE_DIM must fit within SL_STATE_DIM_PADDED");
// Feature groups must contiguously cover the non-padding state range.
static_assert(SL_MARKET_GROUP_BEGIN == 0,
"feature groups must start at offset 0");
static_assert(SL_PLAN_ISV_GROUP_END == SL_PADDING_START,
"feature groups must end at SL_PADDING_START — padding is not a group");
static_assert(SL_MARKET_DIM <= SL_MAX_FEATURE_GROUP_DIM,
"Market group dim must not exceed SL_MAX_FEATURE_GROUP_DIM");
static_assert(SL_OFI_DIM <= SL_MAX_FEATURE_GROUP_DIM,
"OFI group dim must not exceed SL_MAX_FEATURE_GROUP_DIM");
static_assert(SL_TLOB_DIM <= SL_MAX_FEATURE_GROUP_DIM,
"TLOB group dim must not exceed SL_MAX_FEATURE_GROUP_DIM");
static_assert(SL_MTF_DIM <= SL_MAX_FEATURE_GROUP_DIM,
"MTF group dim must not exceed SL_MAX_FEATURE_GROUP_DIM");
static_assert(SL_PORTFOLIO_BASE_DIM <= SL_MAX_FEATURE_GROUP_DIM,
"Portfolio group dim must not exceed SL_MAX_FEATURE_GROUP_DIM");
static_assert(SL_PORTFOLIO_PLAN_DIM <= SL_MAX_FEATURE_GROUP_DIM,
"Plan-ISV group dim must not exceed SL_MAX_FEATURE_GROUP_DIM");
// ── Shared state assembly function ──
// Called by both experience_state_gather (training) and backtest_state_gather (validation).

File diff suppressed because one or more lines are too long