KernelBench cuda · RTX PRO 6000

Grid + MinGRU SPS Claude Haiku 5.5

34.0%geomean peak fraction across shapes

manually audited: clean

Haiku 5.5 runs the rollout as four fp32 CUDA kernels per step: layer 0 collapsed to a 4-input map (W_gru[0]·W_enc folded once per call), MinGRU layers 1 and 2 as cp.async double-buffered SIMT GEMMs over 128-env tiles with the gate math in the epilogue, the action head as per-slice partial logits, and a head kernel that does argmax, the move, reward and food respawn with a closed-form LCG jump.

harnessclaudelast resume29mlast resume wall31mcheck12sbenchmark4slast resume tokens6,128cost$23.55gpu-lock wait11mgpu-lock held8mregimethroughput

Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth

No per-shape benchmark data archived for this run.

Kernel source (redacted)
from __future__ import annotations

import os
from pathlib import Path

import torch
import torch.nn as nn
from torch.utils.cpp_extension import load

HERE = Path(__file__).resolve().parent

BOARD = 11
OBS_DIM = 4
HIDDEN = 256
GRU_LAYERS = 3
NUM_ACTIONS = 4
GRU_OUT = 3 * HIDDEN
ENV_BLOCK = 128
CHUNK_ENVS = 16384
RNG_SEED_STRIDE = 10007

_ext = None


def _module():
    global _ext
    if _ext is None:
        os.environ["TORCH_CUDA_ARCH_LIST"] = "12.0"
        build_dir = HERE / "build"
        build_dir.mkdir(exist_ok=True)
        _ext = load(
            name="grid_mingru_ext",
            sources=[str(HERE / "grid_mingru.cu"), str(HERE / "grid_mingru_bind.cpp")],
            extra_cuda_cflags=["-O3"],
            build_directory=str(build_dir),
            verbose=False,
        )
    return _ext


class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.w_enc = nn.Parameter(torch.empty(HIDDEN, OBS_DIM))
        self.b_enc = nn.Parameter(torch.zeros(HIDDEN))
        self.w_gru = nn.Parameter(torch.empty(GRU_LAYERS, GRU_OUT, HIDDEN))
        self.w_a = nn.Parameter(torch.empty(NUM_ACTIONS, HIDDEN))
        self.b_a = nn.Parameter(torch.zeros(NUM_ACTIONS))
        self.w_v = nn.Parameter(torch.empty(1, HIDDEN))
        self.b_v = nn.Parameter(torch.zeros(1))
        self.reset_parameters(0)

    def reset_parameters(self, seed: int = 0) -> None:
        g = torch.Generator(device="cpu")
        g.manual_seed(seed)
        for p in self.parameters():
            tmp = torch.empty(p.shape, dtype=p.dtype, device="cpu")
            tmp.normal_(0.0, 0.02, generator=g)
            p.data.copy_(tmp)

    def forward(self, obs: torch.Tensor, state: torch.Tensor):
        return policy_forward(self, obs, state)


def policy_forward(model: Model, obs: torch.Tensor, state: torch.Tensor):
    logits, new_state, value = _module().policy(
        obs.contiguous(),
        state.contiguous(),
        model.w_enc,
        model.b_enc,
        model.w_gru,
        model.w_a,
        model.b_a,
        model.w_v,
        model.b_v,
    )
    return logits, new_state, value


def env_step(agent, food, actions, rng_state):
    n = agent.shape[0]
    agent_out = torch.empty_like(agent)
    food_out = torch.empty_like(food)
    reward = torch.empty(n, dtype=torch.float32, device=agent.device)
    rng_out = torch.empty_like(rng_state)
    any_hit = torch.zeros(1, dtype=torch.int32, device=agent.device)
    _module().env_step(
        agent.contiguous(),
        food.contiguous(),
        actions.to(torch.int64).contiguous(),
        rng_state.contiguous(),
        agent_out,
        food_out,
        reward,
        rng_out,
        any_hit,
    )
    return agent_out, food_out, reward, rng_out


def run(num_envs: int, horizon: int, seed: int, model: Model | None = None) -> dict:
    device = torch.device("cuda:0")
    if model is None:
        model = Model()
    model = model.to(device).eval()

    n = int(num_envs)
    npad = -(-n // ENV_BLOCK) * ENV_BLOCK
    g = torch.Generator(device="cpu")
    g.manual_seed(seed)
    agent = torch.zeros(npad, 2, dtype=torch.int32, device=device)
    food = torch.zeros(npad, 2, dtype=torch.int32, device=device)
    agent[:n] = torch.randint(0, BOARD, (n, 2), generator=g).to(device=device, dtype=torch.int32)
    food[:n] = torch.randint(0, BOARD, (n, 2), generator=g).to(device=device, dtype=torch.int32)
    rewards = torch.zeros(npad, dtype=torch.float32, device=device)
    state = torch.empty(npad, GRU_LAYERS, HIDDEN, dtype=torch.float32, device=device)
    logits = torch.zeros(npad, NUM_ACTIONS, dtype=torch.float32, device=device)
    ctr = torch.zeros(2, dtype=torch.int32, device=device)

    _module().rollout(
        model.w_enc,
        model.b_enc,
        model.w_gru,
        model.w_a,
        model.b_a,
        agent,
        food,
        rewards,
        state,
        logits,
        ctr,
        seed * RNG_SEED_STRIDE,
        n,
        horizon,
        CHUNK_ENVS,
    )
    return {
        "rewards": rewards[:n],
        "positions": agent[:n].to(torch.int64),
        "last_logits": logits[:n],
    }


# ==================================================================
# ===== sidecar: grid_mingru.cu (21547 bytes, loaded by solution.py) =====
# ==================================================================

#include <cuda_runtime.h>
#include <cstddef>
#include <cstdint>

namespace {

constexpr int NACT = 4;
constexpr int HID = 256;
constexpr int GATES = 3 * HID;
constexpr int BOARD = 11;
constexpr unsigned long long LCG_A = 6364136223846793005ULL;
constexpr unsigned long long LCG_MASK = 0x7FFFFFFFFFFFFFFFULL;

constexpr int L0_ENVS = 32;
constexpr int L0_THREADS = 256;

constexpr int GM = 128;
constexpr int GU = 64;
constexpr int GT = 256;
constexpr int GKC = 16;
constexpr int NKC = HID / GKC;
constexpr int HLD = GM;
constexpr int WLD = 196;

__device__ __forceinline__ float sigm(float x) { return 1.0f / (1.0f + expf(-x)); }

__device__ __forceinline__ unsigned long long lcg_step(unsigned long long x) {
    return (x * LCG_A + 1ULL) & LCG_MASK;
}

__device__ __forceinline__ void cp_async16(void* smem, const void* gmem) {
    const unsigned s = static_cast<unsigned>(__cvta_generic_to_shared(smem));
    asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n" ::"r"(s), "l"(gmem));
}
__device__ __forceinline__ void cp_async_commit() { asm volatile("cp.async.commit_group;\n" ::); }
template <int N>
__device__ __forceinline__ void cp_async_wait() { asm volatile("cp.async.wait_group %0;\n" ::"n"(N)); }

// Advances the env rng by 2K global steps in closed form, then draws the respawn food.
__device__ __forceinline__ void respawn_food(unsigned long long rng_off, int env, int K, int& fx, int& fy) {
    unsigned long long ra = 1ULL, rb = 0ULL, pa = LCG_A, pb = 1ULL;
    for (unsigned n = 2u * static_cast<unsigned>(K); n; n >>= 1) {
        if (n & 1u) {
            rb = ra * pb + rb;
            ra = ra * pa;
        }
        pb = pa * pb + pb;
        pa = pa * pa;
    }
    const unsigned long long x0 = static_cast<unsigned long long>(env) + rng_off;
    const unsigned long long x = (ra * x0 + rb) & LCG_MASK;
    const unsigned long long r1 = lcg_step(x);
    const unsigned long long r2 = lcg_step(r1);
    fx = static_cast<int>(r1 % BOARD);
    fy = static_cast<int>(r2 % BOARD);
}

__global__ void precompute_m0(const float* __restrict__ w0, const float* __restrict__ w_enc,
                              const float* __restrict__ b_enc, float* __restrict__ m0,
                              float* __restrict__ c0) {
    __shared__ float red[5][HID];
    const int j = blockIdx.x;
    const int k = threadIdx.x;
    const float w = w0[j * HID + k];
    red[0][k] = w * w_enc[k * 4 + 0];
    red[1][k] = w * w_enc[k * 4 + 1];
    red[2][k] = w * w_enc[k * 4 + 2];
    red[3][k] = w * w_enc[k * 4 + 3];
    red[4][k] = w * b_enc[k];
    __syncthreads();
    for (int s = HID / 2; s > 0; s >>= 1) {
        if (k < s) {
#pragma unroll
            for (int c = 0; c < 5; ++c) red[c][k] += red[c][k + s];
        }
        __syncthreads();
    }
    if (k == 0) {
        for (int c = 0; c < 4; ++c) m0[j * 4 + c] = red[c][0];
        c0[j] = red[4][0];
    }
}

// Layer 0 has a 4-wide input, so its gates are M0 * obs + c0 and its input h0 = W_enc * obs + b_enc.
__global__ void __launch_bounds__(L0_THREADS) layer0_kernel(
    const int* __restrict__ agent, const int* __restrict__ food, const float* __restrict__ m0,
    const float* __restrict__ c0, const float* __restrict__ w_enc, const float* __restrict__ b_enc,
    float* __restrict__ state, float* __restrict__ h0, int* ctr, int npad, int env_base, bool bump, bool first) {
    __shared__ __align__(16) float sM0[GATES * 4];
    __shared__ __align__(16) float sC0[GATES];
    __shared__ float tile[HID][L0_ENVS];
    const int lane = threadIdx.x & 31;
    const int warp = threadIdx.x >> 5;
    const int e0 = env_base + blockIdx.x * L0_ENVS;

    if (bump && blockIdx.x == 0 && threadIdx.x == 0) {
        ctr[0] += ctr[1];
        ctr[1] = 0;
    }

    float stv[HID / 32][4];
#pragma unroll
    for (int p = 0; p < HID / 32; ++p)
#pragma unroll
        for (int q = 0; q < 4; ++q)
            stv[p][q] = first ? 0.f : state[static_cast<size_t>(e0 + warp + 8 * q) * GATES + lane + 32 * p];
    for (int i = threadIdx.x; i < GATES; i += L0_THREADS)
        reinterpret_cast<float4*>(sM0)[i] = reinterpret_cast<const float4*>(m0)[i];
    for (int i = threadIdx.x; i < GATES / 4; i += L0_THREADS)
        reinterpret_cast<float4*>(sC0)[i] = reinterpret_cast<const float4*>(c0)[i];
    __syncthreads();

    float ob[4][4];
#pragma unroll
    for (int q = 0; q < 4; ++q) {
        const int env = e0 + warp + 8 * q;
        const int ax = agent[2 * env];
        const int ay = agent[2 * env + 1];
        const int fx = food[2 * env];
        const int fy = food[2 * env + 1];
        ob[q][0] = (float)(fx - ax) / (float)BOARD;
        ob[q][1] = (float)(fy - ay) / (float)BOARD;
        ob[q][2] = (float)ax / (float)(BOARD - 1);
        ob[q][3] = (float)ay / (float)(BOARD - 1);
    }

#pragma unroll
    for (int p = 0; p < HID / 32; ++p) {
        const int u = lane + 32 * p;
        const float4 mz = *reinterpret_cast<const float4*>(&sM0[u * 4]);
        const float4 mg = *reinterpret_cast<const float4*>(&sM0[(HID + u) * 4]);
        const float4 mp = *reinterpret_cast<const float4*>(&sM0[(2 * HID + u) * 4]);
        const float cz0 = sC0[u];
        const float cz1 = sC0[HID + u];
        const float cz2 = sC0[2 * HID + u];
        const float4 we = *reinterpret_cast<const float4*>(&w_enc[u * 4]);
        const float be = b_enc[u];
#pragma unroll
        for (int q = 0; q < 4; ++q) {
            const int le = warp + 8 * q;
            const int env = e0 + le;
            const float o0 = ob[q][0], o1 = ob[q][1], o2 = ob[q][2], o3 = ob[q][3];
            const float zh = fmaf(mz.w, o3, fmaf(mz.z, o2, fmaf(mz.y, o1, fmaf(mz.x, o0, cz0))));
            const float zg = fmaf(mg.w, o3, fmaf(mg.z, o2, fmaf(mg.y, o1, fmaf(mg.x, o0, cz1))));
            const float zp = fmaf(mp.w, o3, fmaf(mp.z, o2, fmaf(mp.y, o1, fmaf(mp.x, o0, cz2))));
            const float hin = fmaf(we.w, o3, fmaf(we.z, o2, fmaf(we.y, o1, fmaf(we.x, o0, be))));
            float* sp = state + static_cast<size_t>(env) * GATES + u;
            const float st = stv[p][q];
            const float out = st + sigm(zg) * (tanhf(zh) - st);
            const float pz = sigm(zp);
            tile[u][le ^ lane] = fmaf(pz, out, (1.f - pz) * hin);
            *sp = out;
        }
    }
    __syncthreads();
    for (int idx = threadIdx.x; idx < HID * L0_ENVS; idx += L0_THREADS) {
        const int u = idx / L0_ENVS;
        const int c = idx % L0_ENVS;
        h0[static_cast<size_t>(u) * npad + e0 + c] = tile[u][c ^ (u & 31)];
    }
}

// One CTA owns GM envs and GU hidden units (3*GU gate rows). Each thread owns 8 envs x 4 units x 3 gates.
// Layers 1 and 2 use this kernel. Layer 2 writes per-slice partial logits instead of h.
template <bool FINAL>
__global__ void __launch_bounds__(GT) gru_gemm(const float* __restrict__ h_in, float* __restrict__ h_out,
                                              const float* __restrict__ w, float* __restrict__ state, int layer,
                                              bool first, const float* __restrict__ w_a,
                                              float* __restrict__ partial, int npad, int env_base) {
    __shared__ __align__(16) float Hs[2][GKC][HLD];
    __shared__ __align__(16) float Ws[2][GKC][WLD];

    const int tid = threadIdx.x;
    const int ty = tid >> 4;
    const int tx = tid & 15;
    const int env0 = env_base + blockIdx.x * GM;
    const int ub = blockIdx.y * GU;

    int wrow[3], wkq[3], wcol[3];
#pragma unroll
    for (int q = 0; q < 3; ++q) {
        const int p = tid + GT * q;
        const int c = p >> 2;
        wcol[q] = c;
        wkq[q] = p & 3;
        wrow[q] = (c % 3) * HID + ub + c / 3;
    }

    float acc[8][4][3];
#pragma unroll
    for (int j = 0; j < 8; ++j)
#pragma unroll
        for (int i = 0; i < 4; ++i)
#pragma unroll
            for (int g = 0; g < 3; ++g) acc[j][i][g] = 0.f;

    float4 wr[3];

    auto load_w = [&](int kc) {
#pragma unroll
        for (int q = 0; q < 3; ++q)
            wr[q] = *reinterpret_cast<const float4*>(w + static_cast<size_t>(wrow[q]) * HID + kc * GKC + 4 * wkq[q]);
    };
    auto store_w = [&](int buf) {
#pragma unroll
        for (int q = 0; q < 3; ++q) {
            const float* v = reinterpret_cast<const float*>(&wr[q]);
#pragma unroll
            for (int j = 0; j < 4; ++j) Ws[buf][4 * wkq[q] + j][wcol[q]] = v[j];
        }
    };
    auto issue_h = [&](int kc, int buf) {
#pragma unroll
        for (int q = 0; q < 2; ++q) {
            const int p = tid + GT * q;
            const int kr = p >> 5;
            const int c4 = p & 31;
            cp_async16(&Hs[buf][kr][4 * c4], h_in + static_cast<size_t>(kc * GKC + kr) * npad + env0 + 4 * c4);
        }
    };
    auto compute = [&](int buf) {
#pragma unroll
        for (int kk = 0; kk < GKC; ++kk) {
            const float4 h0 = *reinterpret_cast<const float4*>(&Hs[buf][kk][ty * 8]);
            const float4 h1 = *reinterpret_cast<const float4*>(&Hs[buf][kk][ty * 8 + 4]);
            const float4 w0 = *reinterpret_cast<const float4*>(&Ws[buf][kk][tx * 12]);
            const float4 w1 = *reinterpret_cast<const float4*>(&Ws[buf][kk][tx * 12 + 4]);
            const float4 w2 = *reinterpret_cast<const float4*>(&Ws[buf][kk][tx * 12 + 8]);
            const float hv[8] = {h0.x, h0.y, h0.z, h0.w, h1.x, h1.y, h1.z, h1.w};
            const float wv[12] = {w0.x, w0.y, w0.z, w0.w, w1.x, w1.y, w1.z, w1.w, w2.x, w2.y, w2.z, w2.w};
#pragma unroll
            for (int j = 0; j < 8; ++j)
#pragma unroll
                for (int i = 0; i < 4; ++i)
#pragma unroll
                    for (int g = 0; g < 3; ++g) acc[j][i][g] = fmaf(hv[j], wv[3 * i + g], acc[j][i][g]);
        }
    };

    issue_h(0, 0);
    cp_async_commit();
    load_w(0);
    store_w(0);
#pragma unroll 1
    for (int kc = 0; kc < NKC; ++kc) {
        const int buf = kc & 1;
        cp_async_wait<0>();
        __syncthreads();
        if (kc + 1 < NKC) {
            issue_h(kc + 1, buf ^ 1);
            cp_async_commit();
            load_w(kc + 1);
        }
        compute(buf);
        if (kc + 1 < NKC) store_w(buf ^ 1);
    }

    float hin[4][8];
#pragma unroll
    for (int i = 0; i < 4; ++i) {
        const float* hp = h_in + static_cast<size_t>(ub + 4 * tx + i) * npad + env0 + ty * 8;
        const float4 a = *reinterpret_cast<const float4*>(hp);
        const float4 b = *reinterpret_cast<const float4*>(hp + 4);
        hin[i][0] = a.x; hin[i][1] = a.y; hin[i][2] = a.z; hin[i][3] = a.w;
        hin[i][4] = b.x; hin[i][5] = b.y; hin[i][6] = b.z; hin[i][7] = b.w;
    }

    float hout[4][8];
#pragma unroll
    for (int j = 0; j < 8; ++j) {
        const int env = env0 + ty * 8 + j;
        float* sp = state + static_cast<size_t>(env) * GATES + layer * HID + ub + 4 * tx;
        float st[4] = {0.f, 0.f, 0.f, 0.f};
        if (!first) {
            const float4 s4 = *reinterpret_cast<const float4*>(sp);
            st[0] = s4.x; st[1] = s4.y; st[2] = s4.z; st[3] = s4.w;
        }
        float outv[4];
#pragma unroll
        for (int i = 0; i < 4; ++i) {
            const float zh = acc[j][i][0];
            const float zg = acc[j][i][1];
            const float zp = acc[j][i][2];
            outv[i] = st[i] + sigm(zg) * (tanhf(zh) - st[i]);
            const float p = sigm(zp);
            hout[i][j] = fmaf(p, outv[i], (1.f - p) * hin[i][j]);
        }
        *reinterpret_cast<float4*>(sp) = make_float4(outv[0], outv[1], outv[2], outv[3]);
    }

    if (!FINAL) {
#pragma unroll
        for (int i = 0; i < 4; ++i) {
            float* op = h_out + static_cast<size_t>(ub + 4 * tx + i) * npad + env0 + ty * 8;
            *reinterpret_cast<float4*>(op) = make_float4(hout[i][0], hout[i][1], hout[i][2], hout[i][3]);
            *reinterpret_cast<float4*>(op + 4) = make_float4(hout[i][4], hout[i][5], hout[i][6], hout[i][7]);
        }
    } else {
        float pa[8][NACT];
#pragma unroll
        for (int j = 0; j < 8; ++j)
#pragma unroll
            for (int a = 0; a < NACT; ++a) pa[j][a] = 0.f;
#pragma unroll
        for (int i = 0; i < 4; ++i) {
            const int u = ub + 4 * tx + i;
#pragma unroll
            for (int a = 0; a < NACT; ++a) {
                const float wa = w_a[a * HID + u];
#pragma unroll
                for (int j = 0; j < 8; ++j) pa[j][a] = fmaf(wa, hout[i][j], pa[j][a]);
            }
        }
#pragma unroll
        for (int j = 0; j < 8; ++j)
#pragma unroll
            for (int a = 0; a < NACT; ++a) {
                float v = pa[j][a];
                v += __shfl_xor_sync(0xffffffffu, v, 8);
                v += __shfl_xor_sync(0xffffffffu, v, 4);
                v += __shfl_xor_sync(0xffffffffu, v, 2);
                v += __shfl_xor_sync(0xffffffffu, v, 1);
                pa[j][a] = v;
            }
        if (tx == 0) {
#pragma unroll
            for (int j = 0; j < 8; ++j)
#pragma unroll
                for (int a = 0; a < NACT; ++a)
                    partial[(static_cast<size_t>(blockIdx.y) * NACT + a) * npad + env0 + ty * 8 + j] = pa[j][a];
        }
    }
}

__global__ void head_kernel(const float* __restrict__ partial, const float* __restrict__ b_a,
                            int* __restrict__ agent, int* __restrict__ food, float* __restrict__ rewards,
                            float* __restrict__ logits, int* ctr, unsigned long long rng_off, int npad,
                            int n_valid, int env_base) {
    const int e = env_base + blockIdx.x * blockDim.x + threadIdx.x;
    if (e >= npad) return;
    float lg[NACT];
#pragma unroll
    for (int a = 0; a < NACT; ++a) {
        float s = b_a[a];
#pragma unroll
        for (int sl = 0; sl < 4; ++sl) s += partial[(static_cast<size_t>(sl) * NACT + a) * npad + e];
        lg[a] = s;
    }
    int act = 0;
    float best = lg[0];
    if (lg[1] > best) { best = lg[1]; act = 1; }
    if (lg[2] > best) { best = lg[2]; act = 2; }
    if (lg[3] > best) { best = lg[3]; act = 3; }
    if (logits != nullptr) {
#pragma unroll
        for (int a = 0; a < NACT; ++a) logits[e * NACT + a] = lg[a];
    }

    int ax = agent[2 * e];
    int ay = agent[2 * e + 1];
    const int fx0 = food[2 * e];
    const int fy0 = food[2 * e + 1];
    if (act == 0) ay -= 1;
    else if (act == 1) ay += 1;
    else if (act == 2) ax -= 1;
    else ax += 1;
    ax = min(max(ax, 0), BOARD - 1);
    ay = min(max(ay, 0), BOARD - 1);
    agent[2 * e] = ax;
    agent[2 * e + 1] = ay;

    int fx = fx0;
    int fy = fy0;
    if (e < n_valid && ax == fx0 && ay == fy0) {
        atomicOr(&ctr[1], 1);
        respawn_food(rng_off, e, ctr[0], fx, fy);
        rewards[e] += 1.f;
    }
    food[2 * e] = fx;
    food[2 * e + 1] = fy;
}

__global__ void policy_naive(const float* __restrict__ obs, const float* __restrict__ st_in,
                             float* __restrict__ st_out, float* __restrict__ logits, float* __restrict__ value,
                             const float* __restrict__ w_enc, const float* __restrict__ b_enc,
                             const float* __restrict__ w_gru, const float* __restrict__ w_a,
                             const float* __restrict__ b_a, const float* __restrict__ w_v,
                             const float* __restrict__ b_v, int n) {
    const int env = blockIdx.x * blockDim.x + threadIdx.x;
    if (env >= n) return;
    float h[HID];
    float hn[HID];
    const float o0 = obs[4 * env], o1 = obs[4 * env + 1], o2 = obs[4 * env + 2], o3 = obs[4 * env + 3];
    for (int u = 0; u < HID; ++u) {
        const float* we = w_enc + 4 * u;
        h[u] = b_enc[u] + we[0] * o0 + we[1] * o1 + we[2] * o2 + we[3] * o3;
    }
    for (int l = 0; l < 3; ++l) {
        const float* W = w_gru + (size_t)l * GATES * HID;
        for (int u = 0; u < HID; ++u) {
            const float* rh = W + (size_t)u * HID;
            const float* rg = W + (size_t)(HID + u) * HID;
            const float* rp = W + (size_t)(2 * HID + u) * HID;
            float zh = 0.f, zg = 0.f, zp = 0.f;
            for (int k = 0; k < HID; ++k) {
                zh = fmaf(rh[k], h[k], zh);
                zg = fmaf(rg[k], h[k], zg);
                zp = fmaf(rp[k], h[k], zp);
            }
            const size_t si = ((size_t)env * 3 + l) * HID + u;
            const float st = st_in[si];
            const float out = st + sigm(zg) * (tanhf(zh) - st);
            const float p = sigm(zp);
            st_out[si] = out;
            hn[u] = fmaf(p, out, (1.f - p) * h[u]);
        }
        for (int u = 0; u < HID; ++u) h[u] = hn[u];
    }
    for (int ac = 0; ac < NACT; ++ac) {
        float s = b_a[ac];
        for (int k = 0; k < HID; ++k) s = fmaf(w_a[ac * HID + k], h[k], s);
        logits[env * NACT + ac] = s;
    }
    float v = b_v[0];
    for (int k = 0; k < HID; ++k) v = fmaf(w_v[k], h[k], v);
    value[env] = v;
}

__global__ void env_move(const float* __restrict__ agent, const float* __restrict__ food,
                         const int64_t* __restrict__ act, float* __restrict__ agent_out,
                         float* __restrict__ reward, int* __restrict__ any, int n) {
    const int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= n) return;
    float ax = agent[2 * i];
    float ay = agent[2 * i + 1];
    const long long ac = act[i];
    if (ac == 0) ay -= 1.f;
    else if (ac == 1) ay += 1.f;
    else if (ac == 2) ax -= 1.f;
    else if (ac == 3) ax += 1.f;
    ax = fminf(fmaxf(ax, 0.f), (float)(BOARD - 1));
    ay = fminf(fmaxf(ay, 0.f), (float)(BOARD - 1));
    agent_out[2 * i] = ax;
    agent_out[2 * i + 1] = ay;
    const bool hit = (ax == food[2 * i]) && (ay == food[2 * i + 1]);
    reward[i] = hit ? 1.f : 0.f;
    if (hit) atomicOr(any, 1);
}

__global__ void env_rng(const float* __restrict__ agent_new, const float* __restrict__ food,
                        float* __restrict__ food_out, const int64_t* __restrict__ rng,
                        int64_t* __restrict__ rng_out, const int* __restrict__ any, int n) {
    const int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= n) return;
    const unsigned long long r = (unsigned long long)rng[i];
    if (*any) {
        const unsigned long long r1 = lcg_step(r);
        const unsigned long long r2 = lcg_step(r1);
        rng_out[i] = (int64_t)r2;
        const bool hit = (agent_new[2 * i] == food[2 * i]) && (agent_new[2 * i + 1] == food[2 * i + 1]);
        if (hit) {
            food_out[2 * i] = (float)(r1 % BOARD);
            food_out[2 * i + 1] = (float)(r2 % BOARD);
        } else {
            food_out[2 * i] = food[2 * i];
            food_out[2 * i + 1] = food[2 * i + 1];
        }
    } else {
        rng_out[i] = rng[i];
        food_out[2 * i] = food[2 * i];
        food_out[2 * i + 1] = food[2 * i + 1];
    }
}

}  // namespace

void rollout_launch(const float* w_enc, const float* b_enc, const float* w_gru, const float* w_a, const float* b_a,
                    int* agent, int* food, float* rewards, float* state, float* logits, float* m0, float* c0,
                    float* h_a, float* h_b, float* partial, int* ctr, unsigned long long rng_off, int n_valid,
                    int npad, int horizon, int chunk, void* stream_v) {
    cudaStream_t stream = static_cast<cudaStream_t>(stream_v);
    precompute_m0<<<GATES, HID, 0, stream>>>(w_gru, w_enc, b_enc, m0, c0);
    for (int t = 0; t < horizon; ++t) {
        const bool first = (t == 0);
        const bool last = (t == horizon - 1);
        for (int eb = 0; eb < npad; eb += chunk) {
            const int cur = (npad - eb < chunk) ? (npad - eb) : chunk;
            layer0_kernel<<<cur / L0_ENVS, L0_THREADS, 0, stream>>>(agent, food, m0, c0, w_enc, b_enc, state, h_a,
                                                                    ctr, npad, eb, eb == 0, first);
            gru_gemm<false><<<dim3(cur / GM, HID / GU), GT, 0, stream>>>(h_a, h_b, w_gru + GATES * HID, state, 1,
                                                                         first, nullptr, nullptr, npad, eb);
            gru_gemm<true><<<dim3(cur / GM, HID / GU), GT, 0, stream>>>(h_b, nullptr, w_gru + 2 * GATES * HID, state,
                                                                        2, first, w_a, partial, npad, eb);
            head_kernel<<<cur / 32, 32, 0, stream>>>(partial, b_a, agent, food, rewards, last ? logits : nullptr, ctr,
                                                     rng_off, npad, n_valid, eb);
        }
    }
}

void policy_launch(const float* obs, const float* st_in, float* st_out, float* logits, float* value,
                   const float* w_enc, const float* b_enc, const float* w_gru, const float* w_a, const float* b_a,
                   const float* w_v, const float* b_v, int n, void* stream_v) {
    cudaStream_t stream = static_cast<cudaStream_t>(stream_v);
    const int block = 128;
    policy_naive<<<(n + block - 1) / block, block, 0, stream>>>(obs, st_in, st_out, logits, value, w_enc, b_enc,
                                                                w_gru, w_a, b_a, w_v, b_v, n);
}

void env_step_launch(const float* agent, const float* food, const int64_t* actions, float* agent_out,
                     float* food_out, float* reward, const int64_t* rng, int64_t* rng_out, int* any, int n,
                     void* stream_v) {
    cudaStream_t stream = static_cast<cudaStream_t>(stream_v);
    const int block = 128;
    const int grid = (n + block - 1) / block;
    env_move<<<grid, block, 0, stream>>>(agent, food, actions, agent_out, reward, any, n);
    env_rng<<<grid, block, 0, stream>>>(agent_out, food, food_out, rng, rng_out, any, n);
}


# ==================================================================
# ===== sidecar: grid_mingru_bind.cpp (4452 bytes, loaded by solution.py) =====
# ==================================================================

#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>
#include <vector>

void rollout_launch(const float* w_enc, const float* b_enc, const float* w_gru, const float* w_a, const float* b_a,
                    int* agent, int* food, float* rewards, float* state, float* logits, float* m0, float* c0,
                    float* h_a, float* h_b, float* partial, int* ctr, unsigned long long rng_off, int n_valid,
                    int npad, int horizon, int chunk, void* stream);
void policy_launch(const float* obs, const float* st_in, float* st_out, float* logits, float* value,
                   const float* w_enc, const float* b_enc, const float* w_gru, const float* w_a, const float* b_a,
                   const float* w_v, const float* b_v, int n, void* stream);
void env_step_launch(const float* agent, const float* food, const int64_t* actions, float* agent_out,
                     float* food_out, float* reward, const int64_t* rng, int64_t* rng_out, int* any, int n,
                     void* stream);

namespace {
constexpr int64_t HID = 256;
constexpr int64_t GATES = 768;
constexpr int64_t NACT = 4;
}  // namespace

void rollout(torch::Tensor w_enc, torch::Tensor b_enc, torch::Tensor w_gru, torch::Tensor w_a, torch::Tensor b_a,
             torch::Tensor agent, torch::Tensor food, torch::Tensor rewards, torch::Tensor state,
             torch::Tensor logits, torch::Tensor ctr, int64_t rng_off, int64_t n_valid, int64_t horizon,
             int64_t chunk) {
    const int64_t npad = agent.size(0);
    const auto opts = w_enc.options();
    torch::Tensor m0 = torch::empty({GATES, 4}, opts);
    torch::Tensor c0 = torch::empty({GATES}, opts);
    torch::Tensor h_a = torch::empty({HID, npad}, opts);
    torch::Tensor h_b = torch::empty({HID, npad}, opts);
    torch::Tensor partial = torch::empty({4, NACT, npad}, opts);
    rollout_launch(w_enc.data_ptr<float>(), b_enc.data_ptr<float>(), w_gru.data_ptr<float>(), w_a.data_ptr<float>(),
                   b_a.data_ptr<float>(), agent.data_ptr<int>(), food.data_ptr<int>(), rewards.data_ptr<float>(),
                   state.data_ptr<float>(), logits.data_ptr<float>(), m0.data_ptr<float>(), c0.data_ptr<float>(),
                   h_a.data_ptr<float>(), h_b.data_ptr<float>(), partial.data_ptr<float>(), ctr.data_ptr<int>(),
                   static_cast<unsigned long long>(rng_off), static_cast<int>(n_valid), static_cast<int>(npad),
                   static_cast<int>(horizon), static_cast<int>(chunk), at::cuda::getCurrentCUDAStream().stream());
    C10_CUDA_KERNEL_LAUNCH_CHECK();
}

std::vector<torch::Tensor> policy(torch::Tensor obs, torch::Tensor st_in, torch::Tensor w_enc, torch::Tensor b_enc,
                                  torch::Tensor w_gru, torch::Tensor w_a, torch::Tensor b_a, torch::Tensor w_v,
                                  torch::Tensor b_v) {
    const int64_t n = obs.size(0);
    const auto opts = obs.options();
    torch::Tensor logits = torch::empty({n, NACT}, opts);
    torch::Tensor st_out = torch::empty_like(st_in);
    torch::Tensor value = torch::empty({n}, opts);
    policy_launch(obs.data_ptr<float>(), st_in.data_ptr<float>(), st_out.data_ptr<float>(), logits.data_ptr<float>(),
                  value.data_ptr<float>(), w_enc.data_ptr<float>(), b_enc.data_ptr<float>(), w_gru.data_ptr<float>(),
                  w_a.data_ptr<float>(), b_a.data_ptr<float>(), w_v.data_ptr<float>(), b_v.data_ptr<float>(),
                  static_cast<int>(n), at::cuda::getCurrentCUDAStream().stream());
    C10_CUDA_KERNEL_LAUNCH_CHECK();
    return {logits, st_out, value};
}

void env_step(torch::Tensor agent, torch::Tensor food, torch::Tensor actions, torch::Tensor rng,
              torch::Tensor agent_out, torch::Tensor food_out, torch::Tensor reward, torch::Tensor rng_out,
              torch::Tensor any) {
    env_step_launch(agent.data_ptr<float>(), food.data_ptr<float>(), actions.data_ptr<int64_t>(),
                    agent_out.data_ptr<float>(), food_out.data_ptr<float>(), reward.data_ptr<float>(),
                    rng.data_ptr<int64_t>(), rng_out.data_ptr<int64_t>(), any.data_ptr<int>(),
                    static_cast<int>(agent.size(0)), at::cuda::getCurrentCUDAStream().stream());
    C10_CUDA_KERNEL_LAUNCH_CHECK();
}

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
    m.def("rollout", &rollout);
    m.def("policy", &policy);
    m.def("env_step", &env_step);
}

20261007_170313_claude_claude-haiku-5-5_04_grid_mingru_sps