Files
foxhunt/crates/ml-alpha/cuda/reduce_axis0.cu
jgrusewski b23f8f2efa perf(ml-alpha): NVIDIA-grade rewrite of CfC K-loop hot kernels — 2.15× faster
Local L40S profile (perception_overfit smoke) GPU kernel time:
  1589ms → 739ms  (53.5% reduction, 2.15× speedup).
Wall-clock smoke: 9.6s → 4.94s (1.94× faster).

Per-kernel deltas (nsys --cuda-graph-trace=node):

  reduce_axis0:              362ms → 10ms   (36× faster)
    Block layout: per-column (1 block / output) → 32-wide column tile
    (block_dim = 32 × 8). Cross-thread reads were strided by n_tail
    (~40K floats = 160KB stride) — one cache line per thread, 8× HBM
    bandwidth wasted. New tile gives coalesced 128B transactions per
    warp. Block tree-reduce kept (no atomicAdd, per feedback_no_atomicadd).
    +1 shared-mem pad to eliminate 32-way bank conflict on the ty reduce.

  multi_horizon_heads_grn_bwd_batched:  540ms → 113ms  (4.8× faster)
    1. Stage h_row[HIDDEN] and a1[5,HEAD_MID] in shared at block entry.
       Eliminates ~28K redundant DRAM reads/block across Pass 3 + Pass 5.
    2. Pass 5 reorder: k outer / i inner with d_z1[k,m] pinned in
       register; writes to grad_w1_scratch are sequential per-thread.
    3. Block size 64 → 128 threads. Pass 5/6 now partition over i
       (output column): cross-thread writes become COALESCED 128B/warp
       (was stride-128 = 512B). Passes 2/3/4 gate on (tid < HEAD_MID).
    4. Pass 3 thread role: m_out → m_in/n. Same coalescing fix on
       grad_w2 writes AND w2 reads in the d_eta_2 sum.

  cfc_step_backward_batched: 351ms → 271ms  (1.3× faster)
    1. Stage x_b[n_in] and h_old_b[n_hid] in shared (was 128× redundant
       DRAM reads per block; now 1× cooperative load).
    2. Pass 1 thread role: i (output row) → k (output col). For each
       i loop iteration, the warp writes grad_w_in[..., tid] /
       grad_w_rec[..., tid] — COALESCED 128B/warp (was stride-128
       non-coalesced).

  multi_horizon_heads_grn_fwd_batched:  211ms → 204ms
    Stage h_row[HIDDEN] in shared — Pass 1 and Pass 3 both consume.

  cfc_step_batched (fwd):     95ms →  94ms
    Stage x_b and h_old_b in shared.

Shared-mem budgets fit comfortably under the 48KB SM cap (~6KB / ~2KB
respectively). All 9 perception_overfit tests pass — gradient
correctness validated end-to-end (constant-signal overfit, K-loop
capture/replay, stride-4 path, evaluate-only paths).

Discipline:
  - Block tree-reduce only, never atomicAdd
  - No nvrtc; pre-compiled cubins via build.rs
  - Mapped-pinned-only is unaffected (CPU↔GPU contract untouched)
  - Single source of truth: replaced kernels in place, no v2 suffixes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 19:07:29 +02:00

65 lines
2.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.
// reduce_axis0.cu — sum [B, N] → [N] along the leading axis.
//
// Layout: each block reduces TILE_J=32 contiguous columns of `out`,
// using a 32×8 thread grid. The (tx) dimension covers the column tile
// (output index `j`), the (ty) dimension covers a B-stride loop.
//
// DRAM access pattern:
// thread (tx, ty) reads per_batch[(b_base + ty*step + s) * n_tail + (j_base + tx)]
// for s = 0..(n_batch/TILE_B), where (tx varies, ty fixed) walks a warp.
// → 32 consecutive `j` per warp → COALESCED 128-byte transactions.
//
// vs the prior block-per-column layout where adjacent threads strided
// the input by `n_tail` (often >40K floats = 160KB stride), forcing
// one cache line per thread and wasting 8× HBM bandwidth.
//
// Block reduce: per-thread partial sums land in s[ty][tx]; the first
// warp (ty=0) sums the 8 partials per column and writes `out[j]`.
// No atomicAdd, no warp-shuffle across warps; block-tree-reduce only.
//
// Launch contract:
// grid_dim = (ceil(n_tail / 32), 1, 1)
// block_dim = (32, 8, 1)
// shared = 0 (compile-time static)
#define REDAX0_TILE_J 32
#define REDAX0_TILE_B 8
#define REDAX0_BLOCK (REDAX0_TILE_J * REDAX0_TILE_B) // 256 threads/block
extern "C" __global__ __launch_bounds__(REDAX0_BLOCK, 4)
void reduce_axis0(
const float* __restrict__ per_batch, // [B, N]
int n_batch,
int n_tail,
float* __restrict__ out // [N] — OVERWRITE
) {
const int tx = threadIdx.x; // column-tile lane
const int ty = threadIdx.y; // batch-tile lane
const int j = (int)blockIdx.x * REDAX0_TILE_J + tx; // output column
const bool active = (j < n_tail);
float my_sum = 0.0f;
if (active) {
// Stride-loop along B in chunks of TILE_B. Within a warp (fixed ty,
// varying tx) we step `j` by +1 → coalesced 32-float load per warp.
for (int bi = ty; bi < n_batch; bi += REDAX0_TILE_B) {
my_sum += per_batch[(long long)bi * n_tail + j];
}
}
// +1 pad eliminates 32-way shared-mem bank conflict on the ty reduce.
__shared__ float s[REDAX0_TILE_B][REDAX0_TILE_J + 1];
s[ty][tx] = my_sum;
__syncthreads();
// First warp sums the 8 per-batch-tile partials for its column.
if (ty == 0 && active) {
float total = 0.0f;
#pragma unroll
for (int t = 0; t < REDAX0_TILE_B; ++t) {
total += s[t][tx];
}
out[j] = total;
}
}