// GPU-side optimizer kernels (Phase T7): AdamW parameter update and the // global grad-norm reduction + rescale. These eliminate the per-step GPU↔host // roundtrip of every parameter/gradient that the T6 host AdamW + host clip did. // // All F32, row-major, contiguous. The math mirrors xtrain-optim::AdamW::step_host // (the reference); bias correction is passed in as bc1/bc2 = 1 - beta^t. #include extern "C" { // One AdamW step over a single parameter tensor of `n` elements, in place. // m ← b1·m + (1-b1)·g // v ← b2·v + (1-b2)·g² // p ← p − lr·( (m/bc1) / (sqrt(v/bc2) + eps) + wd·p ) // `m`/`v` are this parameter's moment buffers (persisted on device across steps). __global__ void adamw_step_f32( float* p, const float* g, float* m, float* v, float lr, float b1, float b2, float eps, float wd, float bc1, float bc2, int n ) { int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx >= n) return; float gi = g[idx]; float mi = b1 * m[idx] + (1.0f - b1) * gi; float vi = b2 * v[idx] + (1.0f - b2) * gi * gi; m[idx] = mi; v[idx] = vi; float mhat = mi / bc1; float vhat = vi / bc2; p[idx] -= lr * (mhat / (sqrtf(vhat) + eps) + wd * p[idx]); } void launch_adamw_step_f32( float* p, const float* g, float* m, float* v, float lr, float b1, float b2, float eps, float wd, float bc1, float bc2, int n, void* stream ) { int block = 256; int grid = (n + block - 1) / block; adamw_step_f32<<>>( p, g, m, v, lr, b1, b2, eps, wd, bc1, bc2, n); } // Accumulate sum-of-squares of one gradient tensor into *acc (a single f32 on // device, pre-zeroed by the caller). Block-reduces then one atomicAdd per block. __global__ void sumsq_accum_f32(const float* g, float* acc, int n) { __shared__ float shared[32]; int tid = blockIdx.x * blockDim.x + threadIdx.x; float v = (tid < n) ? g[tid] * g[tid] : 0.0f; // block reduce int lane = threadIdx.x & 31; int warp = threadIdx.x >> 5; int nwarps = (blockDim.x + 31) >> 5; #pragma unroll for (int off = 16; off > 0; off >>= 1) v += __shfl_down_sync(0xffffffff, v, off); if (lane == 0) shared[warp] = v; __syncthreads(); v = (threadIdx.x < nwarps) ? shared[threadIdx.x] : 0.0f; if (warp == 0) { #pragma unroll for (int off = 16; off > 0; off >>= 1) v += __shfl_down_sync(0xffffffff, v, off); if (lane == 0) atomicAdd(acc, v); } } void launch_sumsq_accum_f32(const float* g, float* acc, int n, void* stream) { int block = 256; int grid = (n + block - 1) / block; sumsq_accum_f32<<>>(g, acc, n); } // Scale one tensor in place by a scalar (used to apply pre_scale·clip_factor to // each gradient). Same as scale_f32 but in place. __global__ void scale_inplace_f32(float* x, float factor, int n) { int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx < n) x[idx] *= factor; } void launch_scale_inplace_f32(float* x, float factor, int n, void* stream) { int block = 256; int grid = (n + block - 1) / block; scale_inplace_f32<<>>(x, factor, n); } }