feat(sp15-p1.5): dd_pct foundational state input concat kernel — LAYOUT FINGERPRINT BREAK
LAYOUT FINGERPRINT BREAK: pre-SP15 checkpoints WILL NOT LOAD after this commit. Greenfield OK per spec Q1. Per spec §6.5: dd_pct (slot 406, written by Task 1.3 dd_state_kernel) gets concatenated to the trunk forward input as the last dim. Eval-time policy SEES drawdown context on every forward pass; Phase 3 teachings can condition on dd_pct directly via state, not just reward modulation. New kernel `dd_pct_concat_kernel.cu` produces a [B, state_dim_padded + 1] buffer whose leading state_dim_padded columns equal the input states_buf and whose +1 last column equals isv[DD_PCT_INDEX=406] broadcast across batch. Pure scatter-copy (no atomicAdd per feedback_no_atomicadd). layout_fingerprint_seed extended with 'TRUNK_INPUT_DD_PCT=sp15_phase_1_5' marker — FNV1a hash changes; old checkpoints fail to load with the existing layout-mismatch error path (same fail-fast that fired on the SP4 / SP14 layout breaks). Phase 1.5 lands kernel + launcher + layout fingerprint marker + GPU oracle test only. Trunk consumer migration (re-pointing forward_online to consume the concat buffer + bumping s1_input_dim from 48 → 49 + propagating through GRN encoder, VSN gate input, bottleneck path, and backward dx scratch) is deferred to a follow-up atomic commit per feedback_no_partial_refactor — matches the established Phase 1.1-1.4 precedent (kernels + launchers verify in isolation first; consumer migration is a load-bearing change touching the GRN encoder, VSN partition boundaries, fxcache schema, and backward gradient flow). GPU oracle test `dd_pct_concat_kernel_writes_last_column` validates B=4, raw state_dim=48, state_dim_padded=128: leading 128 columns of each output row match the input states (including pad zeros), and the 129th column equals isv[DD_PCT_INDEX]=0.42 broadcast across all 4 rows. Test passes on local RTX 3050 Ti (sm_86) in 1.62s. cargo test -p ml --lib --features cuda: 945 passed / 14 failed — same 14 failures pre-existing on the parent commit `c6fd4b4b2` (Task 1.4 partial baseline); zero introduced by this commit. Per spec §6.5 step 7 (trunk-grounding behavioral test): Phase 4 L40S smoke verifies dd_pct propagation at production scale via the existing layout-fingerprint-mismatch fail-fast on cold-start of any pre-SP15 checkpoint. The follow-up Phase 1.5.b commit that lands the consumer migration adds the explicit trunk-grounding KL test alongside the forward_online wiring. Touched: - crates/ml/src/cuda_pipeline/dd_pct_concat_kernel.cu (new) - crates/ml/build.rs (+1 cubin manifest entry) - crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs (+SP15_DD_PCT_CONCAT_CUBIN + launch_sp15_dd_pct_concat + TRUNK_INPUT_DD_PCT layout fingerprint marker) - crates/ml/tests/sp15_phase1_oracle_tests.rs (+1 GPU oracle test) - docs/dqn-wire-up-audit.md (+1 Phase 1.5 entry) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -873,6 +873,19 @@ fn main() {
|
||||
// forward access from the main eval pass and are deferred to
|
||||
// Task 1.4.b.
|
||||
"baseline_kernels.cu",
|
||||
// SP15 Phase 1.5 (2026-05-06): dd_pct foundational state input
|
||||
// concat. LAYOUT FINGERPRINT BREAK — pre-SP15 checkpoints WILL
|
||||
// NOT LOAD after this commit. Greenfield OK per spec Q1. Per
|
||||
// spec §6.5: produces a [B, state_dim_padded + 1] concat buffer
|
||||
// whose last column is `isv[DD_PCT_INDEX=406]` broadcast across
|
||||
// batch. Eval-time policy SEES drawdown context on every forward
|
||||
// pass; Phase 3 teachings can condition on dd_pct directly via
|
||||
// state, not just reward modulation. Phase 1.5 lands kernel +
|
||||
// launcher + layout fingerprint marker only; trunk consumer
|
||||
// migration (re-pointing forward_online to consume the concat
|
||||
// buffer + bumping s1_input_dim) lands as a follow-up atomic
|
||||
// commit per the established Phase 1.1-1.4 precedent.
|
||||
"dd_pct_concat_kernel.cu",
|
||||
];
|
||||
|
||||
// ALL kernels get common header (BF16 types + wrappers)
|
||||
|
||||
60
crates/ml/src/cuda_pipeline/dd_pct_concat_kernel.cu
Normal file
60
crates/ml/src/cuda_pipeline/dd_pct_concat_kernel.cu
Normal file
@@ -0,0 +1,60 @@
|
||||
// crates/ml/src/cuda_pipeline/dd_pct_concat_kernel.cu
|
||||
//
|
||||
// SP15 Phase 1.5 — dd_pct foundational state input concat kernel.
|
||||
//
|
||||
// LAYOUT FINGERPRINT BREAK: pre-SP15 checkpoints WILL NOT LOAD after this
|
||||
// commit. Greenfield OK per spec Q1.
|
||||
//
|
||||
// Per spec §6.5: dd_pct (slot 406, written by Task 1.3's dd_state_kernel)
|
||||
// gets concatenated to the trunk forward input as the last dim. Eval-time
|
||||
// policy SEES drawdown context on every forward pass; Phase 3 teachings
|
||||
// can condition on dd_pct directly via state, not just reward modulation.
|
||||
//
|
||||
// Kernel contract (Pearl: feedback_no_partial_refactor):
|
||||
// Input: states_buf [B, state_dim_padded] — existing trunk input
|
||||
// isv[DD_PCT_INDEX=406] — dd_pct ∈ [0, 1] (broadcast)
|
||||
// B, state_dim_padded
|
||||
// Output: concat_buf [B, state_dim_padded + 1] — pre-allocated
|
||||
// concat_buf[b, 0..state_dim_padded] = states_buf[b, 0..state_dim_padded]
|
||||
// concat_buf[b, state_dim_padded] = isv[DD_PCT_INDEX]
|
||||
//
|
||||
// The kernel produces the concatenated buffer; trunk consumer migration
|
||||
// (re-pointing forward_online to consume concat_buf instead of states_buf
|
||||
// and bumping s1_input_dim accordingly) lands as a follow-up atomic
|
||||
// commit per the established Phase 1 precedent (Tasks 1.1-1.4 all defer
|
||||
// consumer migration to follow-up commits per feedback_no_partial_refactor).
|
||||
//
|
||||
// Layout: row-major [B, state_dim_padded] for input; row-major
|
||||
// [B, state_dim_padded + 1] for output. The +1 last column is the
|
||||
// dd_pct broadcast slot. No atomicAdd (pure scatter-copy).
|
||||
|
||||
extern "C" __global__ void dd_pct_concat_kernel(
|
||||
const float* __restrict__ states_buf,
|
||||
const float* __restrict__ isv,
|
||||
float* __restrict__ concat_buf,
|
||||
int batch_size,
|
||||
int state_dim_padded
|
||||
) {
|
||||
const int b = blockIdx.x;
|
||||
const int tid = threadIdx.x;
|
||||
const int n_tid = blockDim.x;
|
||||
|
||||
if (b >= batch_size) return;
|
||||
|
||||
const float dd_pct = isv[406]; // DD_PCT_INDEX (compile-time constant)
|
||||
|
||||
const int out_stride = state_dim_padded + 1;
|
||||
const int in_offset = b * state_dim_padded;
|
||||
const int out_offset = b * out_stride;
|
||||
|
||||
// 1. Copy the existing state vector — full padded width (including
|
||||
// pre-existing pad zeros from the producer).
|
||||
for (int i = tid; i < state_dim_padded; i += n_tid) {
|
||||
concat_buf[out_offset + i] = states_buf[in_offset + i];
|
||||
}
|
||||
|
||||
// 2. Last column = dd_pct broadcast. Single thread writes the +1 slot.
|
||||
if (tid == 0) {
|
||||
concat_buf[out_offset + state_dim_padded] = dd_pct;
|
||||
}
|
||||
}
|
||||
@@ -903,6 +903,83 @@ pub fn launch_sp15_dd_state(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// SP15 Phase 1.5 (2026-05-06): cubin for the `dd_pct_concat_kernel`.
|
||||
///
|
||||
/// LAYOUT FINGERPRINT BREAK: pre-SP15 checkpoints WILL NOT LOAD after
|
||||
/// this commit. Greenfield OK per spec Q1.
|
||||
///
|
||||
/// Per spec §6.5: produces a `[B, state_dim_padded + 1]` concat buffer
|
||||
/// whose last column is `isv[DD_PCT_INDEX=406]` broadcast across batch.
|
||||
/// The kernel reads the existing `[B, state_dim_padded]` states buffer
|
||||
/// (full padded width, including pre-existing pad zeros from the
|
||||
/// upstream producer), copies it into the concat buffer's leading
|
||||
/// `state_dim_padded` columns, and writes `isv[DD_PCT_INDEX]` to the
|
||||
/// `+1` last column. No atomicAdd (pure scatter-copy).
|
||||
///
|
||||
/// Phase 1.5 lands the kernel + launcher + layout fingerprint marker
|
||||
/// only; trunk consumer migration (re-pointing `forward_online` to
|
||||
/// consume the concat buffer + bumping `s1_input_dim`) is deferred to
|
||||
/// a follow-up atomic commit per `feedback_no_partial_refactor.md` —
|
||||
/// this matches the established Phase 1.1-1.4 precedent (kernels +
|
||||
/// launchers verify in isolation first via the GPU oracle test below).
|
||||
pub static SP15_DD_PCT_CONCAT_CUBIN: &[u8] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/dd_pct_concat_kernel.cubin"));
|
||||
|
||||
/// SP15 Phase 1.5 (2026-05-06): launcher for the dd_pct concat kernel.
|
||||
/// Free function (matches `launch_sp15_dd_state` and `launch_sp15_*`
|
||||
/// baseline-launcher precedent) so unit/oracle tests can drive the
|
||||
/// kernel directly without the trainer struct; production callers
|
||||
/// invoke the same launcher.
|
||||
///
|
||||
/// Contract:
|
||||
/// `states_buf`: device ptr to `[B, state_dim_padded]` row-major f32.
|
||||
/// `isv`: device ptr to ISV bus (≥443 f32 slots).
|
||||
/// `concat_buf`: device ptr to `[B, state_dim_padded + 1]` row-major
|
||||
/// f32 — caller-allocated, written by the kernel.
|
||||
/// `batch_size`, `state_dim_padded`: dimension scalars.
|
||||
///
|
||||
/// Grid: `batch_size` blocks, 128 threads per block. Each block copies
|
||||
/// one row of `state_dim_padded` floats and writes the +1 last column
|
||||
/// from `isv[DD_PCT_INDEX=406]`.
|
||||
pub fn launch_sp15_dd_pct_concat(
|
||||
stream: &Arc<CudaStream>,
|
||||
states_buf: cudarc::driver::sys::CUdeviceptr,
|
||||
isv: cudarc::driver::sys::CUdeviceptr,
|
||||
concat_buf: cudarc::driver::sys::CUdeviceptr,
|
||||
batch_size: i32,
|
||||
state_dim_padded: i32,
|
||||
) -> Result<(), MLError> {
|
||||
let module = stream
|
||||
.context()
|
||||
.load_cubin(SP15_DD_PCT_CONCAT_CUBIN.to_vec())
|
||||
.map_err(|e| MLError::ModelError(format!(
|
||||
"load sp15_dd_pct_concat cubin: {e}"
|
||||
)))?;
|
||||
let kernel = module
|
||||
.load_function("dd_pct_concat_kernel")
|
||||
.map_err(|e| MLError::ModelError(format!(
|
||||
"load dd_pct_concat_kernel function: {e}"
|
||||
)))?;
|
||||
unsafe {
|
||||
stream
|
||||
.launch_builder(&kernel)
|
||||
.arg(&states_buf)
|
||||
.arg(&isv)
|
||||
.arg(&concat_buf)
|
||||
.arg(&batch_size)
|
||||
.arg(&state_dim_padded)
|
||||
.launch(LaunchConfig {
|
||||
grid_dim: (batch_size.max(1) as u32, 1, 1),
|
||||
block_dim: (128, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
})
|
||||
.map_err(|e| MLError::ModelError(format!(
|
||||
"launch dd_pct_concat_kernel: {e}"
|
||||
)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// SP15 Phase 1.4 partial (2026-05-06): cubin shared by all 4 constant-
|
||||
/// policy counterfactual baselines (`baseline_buyhold_kernel`,
|
||||
/// `baseline_hold_only_kernel`, `baseline_naive_momentum_kernel`,
|
||||
@@ -2575,6 +2652,7 @@ const fn layout_fingerprint_seed() -> &'static [u8] {
|
||||
DD_TRAJECTORY_DECREASING=439;RECOVERY_OVERSAMPLE_WEIGHT=440;\
|
||||
DD_TRAJECTORY_FLOOR=441;MEDIAN_STREAK_LENGTH=442;\
|
||||
ISV_TOTAL_DIM=443;\
|
||||
TRUNK_INPUT_DD_PCT=sp15_phase_1_5;\
|
||||
PARAM_W_A_H_S1=0;PARAM_B_A_H_S1=1;PARAM_W_B_H_S1=2;PARAM_B_B_H_S1=3;\
|
||||
PARAM_W_RESIDUAL_H_S1=4;PARAM_GAMMA_H_S1=5;PARAM_BETA_H_S1=6;\
|
||||
PARAM_W_A_H_S2=7;PARAM_B_A_H_S2=8;PARAM_W_B_H_S2=9;PARAM_B_B_H_S2=10;\
|
||||
|
||||
@@ -506,4 +506,103 @@ mod gpu {
|
||||
momentum_sharpe + reversion_sharpe
|
||||
);
|
||||
}
|
||||
|
||||
/// Test 1.5 — dd_pct foundational state input concat (LAYOUT FINGERPRINT
|
||||
/// BREAK).
|
||||
///
|
||||
/// Verifies the `dd_pct_concat_kernel` correctly produces a
|
||||
/// `[B, state_dim_padded + 1]` buffer whose:
|
||||
/// - leading `state_dim_padded` columns equal the input states_buf
|
||||
/// (full padded width, including pre-existing pad zeros), and
|
||||
/// - last column equals `isv[DD_PCT_INDEX=406]` broadcast across
|
||||
/// batch.
|
||||
///
|
||||
/// This is a kernel-level oracle test (matches the Phase 1.1-1.4
|
||||
/// pattern) — it does NOT verify dd_pct propagates through the trunk
|
||||
/// forward, because trunk consumer migration is deferred to a
|
||||
/// follow-up commit per `feedback_no_partial_refactor.md`. The
|
||||
/// follow-up commit that re-points `forward_online` to consume the
|
||||
/// concat buffer adds the trunk-grounding behavioral test (see plan
|
||||
/// §6.5 step 7).
|
||||
#[test]
|
||||
#[ignore = "requires GPU"]
|
||||
fn dd_pct_concat_kernel_writes_last_column() {
|
||||
use ml::cuda_pipeline::gpu_dqn_trainer::launch_sp15_dd_pct_concat;
|
||||
|
||||
let stream = make_test_stream();
|
||||
|
||||
// Synthetic batch: B=4, raw state_dim=48, state_dim_padded=128
|
||||
// (matches GpuDqnTrainConfig::default()'s 48 → pad128 = 128 layout).
|
||||
// Only the first 48 columns of each row carry "real" feature data;
|
||||
// the trailing 80 are pre-existing pad zeros from the producer.
|
||||
let batch_size: usize = 4;
|
||||
const RAW_STATE_DIM: usize = 48;
|
||||
let state_dim_padded: usize = 128;
|
||||
|
||||
// Build the input states buffer: row b column i = (b * 100 + i) as f32
|
||||
// for i ∈ [0, RAW_STATE_DIM); zeros for i ∈ [RAW_STATE_DIM,
|
||||
// state_dim_padded) (the padded portion).
|
||||
let mut states: Vec<f32> = vec![0.0; batch_size * state_dim_padded];
|
||||
for b in 0..batch_size {
|
||||
for i in 0..RAW_STATE_DIM {
|
||||
states[b * state_dim_padded + i] = (b * 100 + i) as f32;
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: CUDA context active via `make_test_stream` above.
|
||||
let states_buf = unsafe { MappedF32Buffer::new(states.len()) }
|
||||
.expect("alloc MappedF32Buffer for states");
|
||||
states_buf.write_from_slice(&states);
|
||||
|
||||
// ISV bus: write 0.42 to slot DD_PCT_INDEX=406.
|
||||
let isv_buf = unsafe { MappedF32Buffer::new(ISV_LEN) }
|
||||
.expect("alloc MappedF32Buffer for ISV");
|
||||
let mut isv_init = vec![0.0f32; ISV_LEN];
|
||||
isv_init[DD_PCT_INDEX] = 0.42;
|
||||
isv_buf.write_from_slice(&isv_init);
|
||||
|
||||
// Output concat buffer: [B, state_dim_padded + 1].
|
||||
let out_len: usize = batch_size * (state_dim_padded + 1);
|
||||
let concat_buf = unsafe { MappedF32Buffer::new(out_len) }
|
||||
.expect("alloc MappedF32Buffer for concat output");
|
||||
|
||||
launch_sp15_dd_pct_concat(
|
||||
&stream,
|
||||
states_buf.dev_ptr,
|
||||
isv_buf.dev_ptr,
|
||||
concat_buf.dev_ptr,
|
||||
batch_size as i32,
|
||||
state_dim_padded as i32,
|
||||
)
|
||||
.expect("launch dd_pct_concat_kernel");
|
||||
stream
|
||||
.synchronize()
|
||||
.expect("synchronize after dd_pct_concat_kernel launch");
|
||||
|
||||
let concat = concat_buf.read_all();
|
||||
|
||||
// Check 1: leading state_dim_padded columns of each row equal the
|
||||
// input states_buf row (full padded width, including pad zeros).
|
||||
for b in 0..batch_size {
|
||||
for i in 0..state_dim_padded {
|
||||
let in_val = states[b * state_dim_padded + i];
|
||||
let out_val = concat[b * (state_dim_padded + 1) + i];
|
||||
assert!(
|
||||
(in_val - out_val).abs() < 1e-6,
|
||||
"concat[b={}, i={}] = {} ≠ states[b={}, i={}] = {} (state copy mismatch)",
|
||||
b, i, out_val, b, i, in_val
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check 2: last column of each row equals isv[DD_PCT_INDEX] = 0.42.
|
||||
for b in 0..batch_size {
|
||||
let last = concat[b * (state_dim_padded + 1) + state_dim_padded];
|
||||
assert!(
|
||||
(last - 0.42).abs() < 1e-6,
|
||||
"concat[b={}, last] = {} ≠ isv[DD_PCT_INDEX] = 0.42 (broadcast mismatch)",
|
||||
b, last
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user