Files
foxhunt/crates/ml-alpha/cuda/rl_curriculum_weights.cu
jgrusewski 148454d37d feat(cuda): curriculum weights E8 + adversarial regime boost kernels
rl_curriculum_weights: per-segment Sharpe → z-score → softmax difficulty
weights. Harder segments sampled more. Block tree-reduce, no atomics.

rl_adversarial_boost: multiply PER priority by boost factor for
negative-reward transitions. Self-regulating — fewer losses → fewer
boosts. ISV-driven threshold + boost magnitude.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:00:21 +02:00

180 lines
6.7 KiB
Plaintext

/* rl_curriculum_weights — GPU-side E8 difficulty-weighted curriculum computation.
*
* Replaces the host-side `enrichment::compute_curriculum_weights` + manual
* ISV write loop with a single device-resident kernel that reads per-segment
* Sharpe from ISV input slots, computes z-score-normalised softmax difficulty
* weights, and writes:
* - per-segment weights to ISV[CURRICULUM_WEIGHT_0_INDEX..+n_segments]
* - scalar concentration (1 - entropy/log(n)) to ISV[CURRICULUM_CONCENTRATION_INDEX]
*
* Run once per epoch (not per step) — launched after the host writes per-segment
* Sharpe into ISV[CURRICULUM_SHARPE_0_INDEX..+n_segments].
*
* Grid = (1, 1, 1)
* Block = (min(n_segments, 256), 1, 1)
* Shared memory = 2 * MAX_SEGMENTS * sizeof(float)
*
* Per `feedback_no_atomicadd.md`: reductions use block tree-reduce only.
* Per `feedback_no_nvrtc.md`: pre-compiled cubin only (build.rs).
*/
/* ISV slot indices — literals mirror canonical Rust constants in
* sp21_isv_slots.rs (CURRICULUM_WEIGHT_0_INDEX=528, CURRICULUM_CONCENTRATION_INDEX=527)
* and the new CURRICULUM_SHARPE_0_INDEX=554 in e8_curriculum_isv_slots.rs.
* Trainer constructor pins names to these indices via compile-time assertions. */
#define CURRICULUM_SHARPE_0_INDEX 554
#define CURRICULUM_WEIGHT_0_INDEX 528
#define CURRICULUM_CONCENTRATION_INDEX 527
#define MAX_SEGMENTS 16
/* Block tree-reduce on shmem[0..bdim], total sum → shmem[0].
* Caller must __syncthreads() BEFORE (writes visible) and AFTER
* (safe to reuse buffer). Per feedback_no_atomicadd.md. */
__device__ __forceinline__ void curriculum_tree_reduce(
float* shmem,
int tid,
int bdim)
{
for (int s = bdim / 2; s > 0; s >>= 1) {
if (tid < s) {
shmem[tid] += shmem[tid + s];
}
__syncthreads();
}
}
/* Block tree-reduce for max: shmem[0] holds the block-wide max.
* Same sync discipline as curriculum_tree_reduce. */
__device__ __forceinline__ void curriculum_tree_reduce_max(
float* shmem,
int tid,
int bdim)
{
for (int s = bdim / 2; s > 0; s >>= 1) {
if (tid < s) {
shmem[tid] = fmaxf(shmem[tid], shmem[tid + s]);
}
__syncthreads();
}
}
extern "C" __global__ void rl_curriculum_weights(
float* __restrict__ isv,
int n_segments /* actual segment count, <= MAX_SEGMENTS */
)
{
int tid = threadIdx.x;
int bdim = blockDim.x;
/* Guard: threads beyond n_segments are inactive for data access
* but participate in tree-reduce sync barriers. */
const int active = (tid < n_segments) ? 1 : 0;
/* Shared memory layout: two buffers of bdim floats each.
* buf_a: working buffer for reductions (mean, std, sum_exp).
* buf_b: secondary buffer for numerically-stable softmax (max finding). */
extern __shared__ float shmem[];
float* buf_a = shmem;
float* buf_b = shmem + bdim;
/* ---------------------------------------------------------------
* Pass 1: Load per-segment Sharpe from ISV and compute mean.
* --------------------------------------------------------------- */
float sharpe_i = 0.0f;
if (active) {
sharpe_i = isv[CURRICULUM_SHARPE_0_INDEX + tid];
}
/* Sum of Sharpe values via tree-reduce → mean. */
buf_a[tid] = sharpe_i;
__syncthreads();
curriculum_tree_reduce(buf_a, tid, bdim);
/* buf_a[0] = sum of all sharpe values (inactive threads contributed 0). */
const float sharpe_mean = buf_a[0] / fmaxf((float)n_segments, 1.0f);
/* ---------------------------------------------------------------
* Pass 2: Variance (for z-score std).
* --------------------------------------------------------------- */
float diff = active ? (sharpe_i - sharpe_mean) : 0.0f;
buf_a[tid] = diff * diff;
__syncthreads();
curriculum_tree_reduce(buf_a, tid, bdim);
const float sharpe_var = buf_a[0] / fmaxf((float)n_segments, 1.0f);
const float sharpe_std = fmaxf(sqrtf(sharpe_var), 1e-6f);
/* ---------------------------------------------------------------
* Pass 3: z-score normalised difficulty → exp weights.
*
* Negate so HARDER segments (lower Sharpe) get larger weights.
* z-clamp to [-3, 3] keeps exp range well-conditioned [0.05, 20].
*
* Numerically-stable softmax: subtract max(z) before exp to
* prevent overflow. With z in [-3, 3] overflow is unlikely, but
* the pattern costs one extra tree-reduce and is strictly correct.
* --------------------------------------------------------------- */
float z_i = 0.0f;
if (active) {
z_i = -(sharpe_i - sharpe_mean) / sharpe_std;
z_i = fmaxf(-3.0f, fminf(3.0f, z_i));
}
/* Find max z for numerically-stable softmax. */
buf_b[tid] = active ? z_i : -1e30f;
__syncthreads();
curriculum_tree_reduce_max(buf_b, tid, bdim);
const float z_max = buf_b[0];
/* exp(z_i - z_max) for numerical stability. */
float exp_i = active ? expf(z_i - z_max) : 0.0f;
/* Sum of exp weights via tree-reduce. */
buf_a[tid] = exp_i;
__syncthreads();
curriculum_tree_reduce(buf_a, tid, bdim);
const float sum_exp = buf_a[0];
/* ---------------------------------------------------------------
* Pass 4: Normalise weights (softmax) and write to ISV.
* --------------------------------------------------------------- */
float weight_i = 0.0f;
if (active && sum_exp > 1e-12f) {
weight_i = exp_i / sum_exp;
} else if (active) {
/* Degenerate case: uniform fallback. */
weight_i = 1.0f / (float)n_segments;
}
if (active) {
isv[CURRICULUM_WEIGHT_0_INDEX + tid] = weight_i;
}
/* ---------------------------------------------------------------
* Pass 5: Concentration scalar = 1 - entropy / log(n_segments).
*
* entropy = -sum(w_i * log(w_i)) with 0*log(0) := 0.
* Normalised entropy ∈ [0, 1]; concentration = 1 - normalised.
* concentration = 0 → perfectly uniform (all segments equal)
* concentration = 1 → single segment dominates
*
* Thread 0 writes the scalar to ISV[CURRICULUM_CONCENTRATION_INDEX].
* --------------------------------------------------------------- */
float h_i = 0.0f;
if (active && weight_i > 1e-12f) {
h_i = -weight_i * logf(weight_i);
}
buf_a[tid] = h_i;
__syncthreads();
curriculum_tree_reduce(buf_a, tid, bdim);
if (tid == 0) {
const float entropy = buf_a[0];
const float log_n = logf(fmaxf((float)n_segments, 2.0f));
const float norm_entropy = fminf(entropy / log_n, 1.0f);
const float concentration = 1.0f - norm_entropy;
isv[CURRICULUM_CONCENTRATION_INDEX] = fmaxf(0.0f, fminf(1.0f, concentration));
}
}