Files
foxhunt/crates/ml-alpha/cuda/rl_multires_features_update.cu
jgrusewski 4bed8f2dbf refactor(rl): pre-allocate 56 replay-step gradient buffers for Graph C
Move all step_synthetic/dqn_replay_step alloc_zeros to persistent
trainer fields (ss_* prefix). Enables CUDA Graph capture of the replay
training step — all device pointers are now stable across steps.

Introduces reduce_axis0_free() to resolve borrow-checker E0502 when
both source (per-batch scratch) and destination (reduced grad) are
self fields passed to the same function.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 23:00:40 +02:00

110 lines
4.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// rl_multires_features_update.cu — per-batch multi-resolution features
// derived from real-time timestamp deltas across 3 horizons.
//
// Maintains streaming EMA state per (batch, horizon, feature) and
// outputs 12 normalized features [B × 3 × 4]:
// per horizon h ∈ {1s, 10s, 600s}:
// [0] price_change = EMA of (mid - prev_mid) / σ_price
// [1] vol_realized = sqrt(EMA of (mid - prev_mid)²)
// [2] oflow_imb = EMA of (bid_sz[0] - ask_sz[0]) / (bid_sz[0] + ask_sz[0])
// [3] trade_burst = EMA of event_rate (1/dt_seconds, capped at 1000)
//
// The EMA alpha per horizon = dt / horizon_seconds (time-weighted
// exponential: a 1-second event on a 10s horizon contributes α=0.1).
// This gives identical time-constant semantics to a circular buffer
// with O(1) state per feature.
//
// State buffers (persistent across steps):
// multires_state_d [B × 3 × 4]: running EMA values
// prev_mid_d [B]: previous step's mid price
// prev_ts_ns_d [B]: previous step's timestamp (for dt computation)
//
// One thread per batch. No shared memory.
// Per `feedback_no_atomicadd`, `feedback_cpu_is_read_only`.
#include <stdint.h>
#define N_HORIZONS 3
#define N_FEATURES 4
#define MULTIRES_DIM (N_HORIZONS * N_FEATURES)
#define RL_MULTIRES_HORIZON_1_INDEX 521
extern "C" __global__ void rl_multires_features_update(
float* __restrict__ multires_state, // [B × MULTIRES_DIM] IN/OUT
float* __restrict__ multires_output, // [B × MULTIRES_DIM] OUT (normalized)
float* __restrict__ prev_mid, // [B] IN/OUT
unsigned long long* __restrict__ prev_ts_ns, // [B] IN/OUT
const float* __restrict__ bid_px, // [BOOK_LEVELS]
const float* __restrict__ ask_px, // [BOOK_LEVELS]
const float* __restrict__ bid_sz, // [BOOK_LEVELS]
const float* __restrict__ ask_sz, // [BOOK_LEVELS]
const float* __restrict__ isv,
const unsigned long long* __restrict__ current_ts_ns_ptr,
int b_size
) {
const int b = blockIdx.x * blockDim.x + threadIdx.x;
if (b >= b_size) return;
const float mid = 0.5f * (bid_px[0] + ask_px[0]);
const float old_mid = prev_mid[b];
const unsigned long long current_ts_ns = current_ts_ns_ptr[0];
const unsigned long long old_ts = prev_ts_ns[b];
// dt in seconds (protect against zero/backward timestamps).
const float dt = (current_ts_ns > old_ts)
? (float)(current_ts_ns - old_ts) * 1e-9f
: 1e-6f;
const float price_delta = mid - old_mid;
const float best_bid_sz = bid_sz[0];
const float best_ask_sz = ask_sz[0];
const float total_sz = best_bid_sz + best_ask_sz + 1e-8f;
const float imbalance = (best_bid_sz - best_ask_sz) / total_sz;
const float event_rate = fminf(1.0f / (dt + 1e-8f), 1000.0f);
const int base = b * MULTIRES_DIM;
for (int h = 0; h < N_HORIZONS; ++h) {
const float horizon_s = isv[RL_MULTIRES_HORIZON_1_INDEX + h];
const float alpha = fminf(dt / (horizon_s + 1e-8f), 1.0f);
const float one_minus_alpha = 1.0f - alpha;
const int off = base + h * N_FEATURES;
// EMA updates.
float ema_price = multires_state[off + 0];
float ema_vol_sq = multires_state[off + 1];
float ema_imb = multires_state[off + 2];
float ema_burst = multires_state[off + 3];
// First-observation bootstrap (sentinel = 0 for all EMAs).
if (old_ts == 0ULL) {
ema_price = price_delta;
ema_vol_sq = price_delta * price_delta;
ema_imb = imbalance;
ema_burst = event_rate;
} else {
ema_price = one_minus_alpha * ema_price + alpha * price_delta;
ema_vol_sq = one_minus_alpha * ema_vol_sq + alpha * (price_delta * price_delta);
ema_imb = one_minus_alpha * ema_imb + alpha * imbalance;
ema_burst = one_minus_alpha * ema_burst + alpha * event_rate;
}
multires_state[off + 0] = ema_price;
multires_state[off + 1] = ema_vol_sq;
multires_state[off + 2] = ema_imb;
multires_state[off + 3] = ema_burst;
// Normalized output.
const float vol = sqrtf(ema_vol_sq + 1e-8f);
multires_output[off + 0] = ema_price / (vol + 1e-8f);
multires_output[off + 1] = vol;
multires_output[off + 2] = ema_imb;
multires_output[off + 3] = ema_burst / 100.0f;
}
prev_mid[b] = mid;
prev_ts_ns[b] = current_ts_ns;
}