gpt-5.5-priority · popcorn · l123_single_gpt
incorrect 23_GibbsSamplingStep.py
model
gpt-5.5-priority
turns / max
1 / 1
tool calls
1
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
popcorn
runtime
—
ref runtime
3.11 μs
speedup
—
started
2026-05-05T02:06:17Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel runtime
—
ref runtime
3.11 μs
speedup
—
kernel mean / median / std
—
ref mean / median / std
3.11 / — / 0.15 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.301
dram util %
0.9%
compute util %
30.1%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
29.21 GB/s
dram util %
0.9%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
53.1%
bottleneck
compute-bound
dominant pipe %
30.1%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
182.5%
ld sectors / req
1.97
st sectors / req
—
regs / thread
100
smem / block
2048 B
block size
512
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
1938.2%
short_scoreboard
67.0%
mio_throttle
51.2%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn


class Model(nn.Module):
    """
    One full sweep of Gibbs sampling for a multivariate Gaussian with
    known precision matrix.  Each coordinate is sampled from its full
    conditional distribution while holding all other coordinates fixed.
    Benchmarks the sequential-scan pattern common in Bayesian models
    with tractable conditionals (e.g., topic models, Boltzmann machines).
    """

    def __init__(self, dim):
        super().__init__()
        self.dim = dim
        # Precision matrix: diagonally dominant for well-conditioning
        L = torch.randn(dim, dim) * 0.1
        precision = L @ L.t() + torch.eye(dim) * 2.0
        self.register_buffer("precision", precision)
        self.register_buffer("mu", torch.zeros(dim))

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        Args:
            x: (B, dim) – current state
        Returns:
            x_new: (B, dim) – state after one full Gibbs sweep
        """
        x = x.clone()
        for i in range(self.dim):
            prec_ii = self.precision[i, i]
            residual = x @ self.precision[i] - x[:, i] * prec_ii
            cond_mean = self.mu[i] - residual / prec_ii
            cond_var = 1.0 / prec_ii
            x[:, i] = cond_mean + torch.sqrt(cond_var) * torch.randn(x.shape[0], device=x.device)
        return x


dim = 32
batch_size = 4096


def get_inputs():
    return [torch.randn(popcorn_pri.jitter_int(batch_size), dim)]


def get_init_inputs():
    return [dim]
turn 0 · FINAL · 1 tool call(s) · 52.9s turn · 52.9s LLM · 12,191 chars (click to expand)
turn 052.9s turn · 52.9s LLM · 0.0s tools · FINAL
reasoning
**Exploring randomness in Python** I’m thinking about needing to craft complete Python code for randomness equivalence. So, I see that using `torch.randn` with shape \( B \) is important. To produce numerically equivalent outputs, I need to think about the RNG, as outputs won't match unless it’s the same. I also need to implement forward computation with random sampling and ensure the generator state in PyTorch is identical. It's key to maintain the order when generating random tensors. I'll aim for something that aligns well with the reference's sequence. **Examining PyTorch RNG behavior** I'm considering how PyTorch's RNG operates, especially with a focus on generating a sequence of random numbers. I need to check if the CUDA random normal generation aligns with the same sequence across multiple calls. The number of calls might result in the same output if the shapes and offsets are consistent. Each batch can also influence this, particularly if dimensions are fixed at 32 and \( B = 4096 \), where I recognize the need to optimize the calculations for efficiency. Updating values across batches seems crucial. **Planning kernel design** I'm thinking about the operations for processing \( 32 \times 4096 \times 32 \), which adds up to around 4 million operations—fairly small. For the custom kernel, it seems logical to assign one row per thread, using \( B = 4096 \) where each thread manages a row while iterating through dimensions sequentially. I’m considering loading the matrix \( P \) to constant or shared memory for efficiency. Plus, I’ll need one thread for each sample, resulting in a grid configuration with \( B \) threads. **Organizing a module load** I need to ensure I'm using `load_inline` for compiling, and I should import `torch` and `nn`. It looks like I need a C++ function name and decide not to use try/except. Also, the build directory should have a unique name. I'll need to add `extra_cuda_cflags` and remember to submit things now. Additionally, I might include `with_cuda=True`. Finally, there will be one tool call to complete this process.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r\"\"\"\n#include <torch/extension.h>\n\ntorch::Tensor gibbs_forward_cuda(torch::Tensor x, torch::Tensor precision, torch::Tensor mu, torch::Tensor noise);\n\"\"\"\n\n_cuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n#ifndef __CUDA_ARCH__\n#define __CUDA_ARCH__ 0\n#endif\n\n__global__ void gibbs32_kernel(const float* __restrict__ x,\n const float* __restrict__ P,\n const float* __restrict__ mu,\n const float* __restrict__ z,\n float* __restrict__ out,\n int B) {\n int b = blockIdx.x * blockDim.x + threadIdx.x;\n if (b >= B) return;\n\n const float* xin = x + ((long long)b) * 32;\n float v0 = xin[0];\n float v1 = xin[1];\n float v2 = xin[2];\n float v3 = xin[3];\n float v4 = xin[4];\n float v5 = xin[5];\n float v6 = xin[6];\n float v7 = xin[7];\n float v8 = xin[8];\n float v9 = xin[9];\n float v10 = xin[10];\n float v11 = xin[11];\n float v12 = xin[12];\n float v13 = xin[13];\n float v14 = xin[14];\n float v15 = xin[15];\n float v16 = xin[16];\n float v17 = xin[17];\n float v18 = xin[18];\n float v19 = xin[19];\n float v20 = xin[20];\n float v21 = xin[21];\n float v22 = xin[22];\n float v23 = xin[23];\n float v24 = xin[24];\n float v25 = xin[25];\n float v26 = xin[26];\n float v27 = xin[27];\n float v28 = xin[28];\n float v29 = xin[29];\n float v30 = xin[30];\n float v31 = xin[31];\n\n #pragma unroll\n for (int i = 0; i < 32; ++i) {\n const float* p = P + i * 32;\n float sum = 0.0f;\n sum = fmaf(v0, p[0], sum);\n sum = fmaf(v1, p[1], sum);\n sum = fmaf(v2, p[2], sum);\n sum = fmaf(v3, p[3], sum);\n sum = fmaf(v4, p[4], sum);\n sum = fmaf(v5, p[5], sum);\n sum = fmaf(v6, p[6], sum);\n sum = fmaf(v7, p[7], sum);\n sum = fmaf(v8, p[8], sum);\n sum = fmaf(v9, p[9], sum);\n sum = fmaf(v10, p[10], sum);\n sum = fmaf(v11, p[11], sum);\n sum = fmaf(v12, p[12], sum);\n sum = fmaf(v13, p[13], sum);\n sum = fmaf(v14, p[14], sum);\n sum = fmaf(v15, p[15], sum);\n sum = fmaf(v16, p[16], sum);\n sum = fmaf(v17, p[17], sum);\n sum = fmaf(v18, p[18], sum);\n sum = fmaf(v19, p[19], sum);\n sum = fmaf(v20, p[20], sum);\n sum = fmaf(v21, p[21], sum);\n sum = fmaf(v22, p[22], sum);\n sum = fmaf(v23, p[23], sum);\n sum = fmaf(v24, p[24], sum);\n sum = fmaf(v25, p[25], sum);\n sum = fmaf(v26, p[26], sum);\n sum = fmaf(v27, p[27], sum);\n sum = fmaf(v28, p[28], sum);\n sum = fmaf(v29, p[29], sum);\n sum = fmaf(v30, p[30], sum);\n sum = fmaf(v31, p[31], sum);\n\n float cur;\n if (i == 0) cur = v0;\n else if (i == 1) cur = v1;\n else if (i == 2) cur = v2;\n else if (i == 3) cur = v3;\n else if (i == 4) cur = v4;\n else if (i == 5) cur = v5;\n else if (i == 6) cur = v6;\n else if (i == 7) cur = v7;\n else if (i == 8) cur = v8;\n else if (i == 9) cur = v9;\n else if (i == 10) cur = v10;\n else if (i == 11) cur = v11;\n else if (i == 12) cur = v12;\n else if (i == 13) cur = v13;\n else if (i == 14) cur = v14;\n else if (i == 15) cur = v15;\n else if (i == 16) cur = v16;\n else if (i == 17) cur = v17;\n else if (i == 18) cur = v18;\n else if (i == 19) cur = v19;\n else if (i == 20) cur = v20;\n else if (i == 21) cur = v21;\n else if (i == 22) cur = v22;\n else if (i == 23) cur = v23;\n else if (i == 24) cur = v24;\n else if (i == 25) cur = v25;\n else if (i == 26) cur = v26;\n else if (i == 27) cur = v27;\n else if (i == 28) cur = v28;\n else if (i == 29) cur = v29;\n else if (i == 30) cur = v30;\n else cur = v31;\n\n float pii = p[i];\n float residual = sum - cur * pii;\n float nv = mu[i] - residual / pii + rsqrtf(pii) * z[(long long)i * B + b];\n\n if (i == 0) v0 = nv;\n else if (i == 1) v1 = nv;\n else if (i == 2) v2 = nv;\n else if (i == 3) v3 = nv;\n else if (i == 4) v4 = nv;\n else if (i == 5) v5 = nv;\n else if (i == 6) v6 = nv;\n else if (i == 7) v7 = nv;\n else if (i == 8) v8 = nv;\n else if (i == 9) v9 = nv;\n else if (i == 10) v10 = nv;\n else if (i == 11) v11 = nv;\n else if (i == 12) v12 = nv;\n else if (i == 13) v13 = nv;\n else if (i == 14) v14 = nv;\n else if (i == 15) v15 = nv;\n else if (i == 16) v16 = nv;\n else if (i == 17) v17 = nv;\n else if (i == 18) v18 = nv;\n else if (i == 19) v19 = nv;\n else if (i == 20) v20 = nv;\n else if (i == 21) v21 = nv;\n else if (i == 22) v22 = nv;\n else if (i == 23) v23 = nv;\n else if (i == 24) v24 = nv;\n else if (i == 25) v25 = nv;\n else if (i == 26) v26 = nv;\n else if (i == 27) v27 = nv;\n else if (i == 28) v28 = nv;\n else if (i == 29) v29 = nv;\n else if (i == 30) v30 = nv;\n else v31 = nv;\n }\n\n float* y = out + ((long long)b) * 32;\n y[0] = v0; y[1] = v1; y[2] = v2; y[3] = v3;\n y[4] = v4; y[5] = v5; y[6] = v6; y[7] = v7;\n y[8] = v8; y[9] = v9; y[10] = v10; y[11] = v11;\n y[12] = v12; y[13] = v13; y[14] = v14; y[15] = v15;\n y[16] = v16; y[17] = v17; y[18] = v18; y[19] = v19;\n y[20] = v20; y[21] = v21; y[22] = v22; y[23] = v23;\n y[24] = v24; y[25] = v25; y[26] = v26; y[27] = v27;\n y[28] = v28; y[29] = v29; y[30] = v30; y[31] = v31;\n}\n\n__global__ void gibbs_generic_kernel(const float* __restrict__ x,\n const float* __restrict__ P,\n const float* __restrict__ mu,\n const float* __restrict__ z,\n float* __restrict__ out,\n int B, int D) {\n int b = blockIdx.x * blockDim.x + threadIdx.x;\n if (b >= B) return;\n float vals[64];\n for (int j = 0; j < D && j < 64; ++j) vals[j] = x[(long long)b * D + j];\n for (int i = 0; i < D && i < 64; ++i) {\n const float* p = P + (long long)i * D;\n float sum = 0.0f;\n for (int j = 0; j < D && j < 64; ++j) sum = fmaf(vals[j], p[j], sum);\n float pii = p[i];\n float residual = sum - vals[i] * pii;\n vals[i] = mu[i] - residual / pii + rsqrtf(pii) * z[(long long)i * B + b];\n }\n for (int j = 0; j < D && j < 64; ++j) out[(long long)b * D + j] = vals[j];\n}\n\ntorch::Tensor gibbs_forward_cuda(torch::Tensor x, torch::Tensor precision, torch::Tensor mu, torch::Tensor noise) {\n const int B = (int)x.size(0);\n const int D = (int)x.size(1);\n auto out = torch::empty_like(x);\n const int threads = 256;\n const int blocks = (B + threads - 1) / threads;\n if (D == 32) {\n gibbs32_kernel<<<blocks, threads>>>(x.data_ptr<float>(), precision.data_ptr<float>(), mu.data_ptr<float>(), noise.data_ptr<float>(), out.data_ptr<float>(), B);\n } else {\n gibbs_generic_kernel<<<blocks, threads>>>(x.data_ptr<float>(), precision.data_ptr<float>(), mu.data_ptr<float>(), noise.data_ptr<float>(), out.data_ptr<float>(), B, D);\n }\n return out;\n}\n\"\"\"\n\n_gibbs_ext = load_inline(\n name=\"gibbs_sweep_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"gibbs_forward_cuda\"],\n with_cuda=True,\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, dim):\n super().__init__()\n self.dim = dim\n L = torch.randn(dim, dim) * 0.1\n precision = L @ L.t() + torch.eye(dim) * 2.0\n self.register_buffer(\"precision\", precision)\n self.register_buffer(\"mu\", torch.zeros(dim))\n\n def forward(self, x: torch.Tensor) -> torch.Tensor:\n # Match the reference RNG stream: reference draws one contiguous (B,) vector\n # for each coordinate, so a contiguous (dim, B) tensor has the same order.\n noise = torch.randn((self.dim, x.shape[0]), device=x.device, dtype=x.dtype)\n return _gibbs_ext.gibbs_forward_cuda(x, self.precision, self.mu, noise)\n" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: correctness check did not pass ((0 / 5) trials). correctness_issue: Output mismatch max_difference: ['4.077594', '4.665485', '4.354724', '4.102122', '4.209945'] avg_difference: ['0.721141', '0.721029', '0.722997', '0.717639', '0.723545']
final submitted kernel (245 lines, 8,512 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

_cpp_src = r"""
#include <torch/extension.h>

torch::Tensor gibbs_forward_cuda(torch::Tensor x, torch::Tensor precision, torch::Tensor mu, torch::Tensor noise);
"""

_cuda_src = r"""
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>

#ifndef __CUDA_ARCH__
#define __CUDA_ARCH__ 0
#endif

__global__ void gibbs32_kernel(const float* __restrict__ x,
                               const float* __restrict__ P,
                               const float* __restrict__ mu,
                               const float* __restrict__ z,
                               float* __restrict__ out,
                               int B) {
    int b = blockIdx.x * blockDim.x + threadIdx.x;
    if (b >= B) return;

    const float* xin = x + ((long long)b) * 32;
    float v0 = xin[0];
    float v1 = xin[1];
    float v2 = xin[2];
    float v3 = xin[3];
    float v4 = xin[4];
    float v5 = xin[5];
    float v6 = xin[6];
    float v7 = xin[7];
    float v8 = xin[8];
    float v9 = xin[9];
    float v10 = xin[10];
    float v11 = xin[11];
    float v12 = xin[12];
    float v13 = xin[13];
    float v14 = xin[14];
    float v15 = xin[15];
    float v16 = xin[16];
    float v17 = xin[17];
    float v18 = xin[18];
    float v19 = xin[19];
    float v20 = xin[20];
    float v21 = xin[21];
    float v22 = xin[22];
    float v23 = xin[23];
    float v24 = xin[24];
    float v25 = xin[25];
    float v26 = xin[26];
    float v27 = xin[27];
    float v28 = xin[28];
    float v29 = xin[29];
    float v30 = xin[30];
    float v31 = xin[31];

    #pragma unroll
    for (int i = 0; i < 32; ++i) {
        const float* p = P + i * 32;
        float sum = 0.0f;
        sum = fmaf(v0,  p[0],  sum);
        sum = fmaf(v1,  p[1],  sum);
        sum = fmaf(v2,  p[2],  sum);
        sum = fmaf(v3,  p[3],  sum);
        sum = fmaf(v4,  p[4],  sum);
        sum = fmaf(v5,  p[5],  sum);
        sum = fmaf(v6,  p[6],  sum);
        sum = fmaf(v7,  p[7],  sum);
        sum = fmaf(v8,  p[8],  sum);
        sum = fmaf(v9,  p[9],  sum);
        sum = fmaf(v10, p[10], sum);
        sum = fmaf(v11, p[11], sum);
        sum = fmaf(v12, p[12], sum);
        sum = fmaf(v13, p[13], sum);
        sum = fmaf(v14, p[14], sum);
        sum = fmaf(v15, p[15], sum);
        sum = fmaf(v16, p[16], sum);
        sum = fmaf(v17, p[17], sum);
        sum = fmaf(v18, p[18], sum);
        sum = fmaf(v19, p[19], sum);
        sum = fmaf(v20, p[20], sum);
        sum = fmaf(v21, p[21], sum);
        sum = fmaf(v22, p[22], sum);
        sum = fmaf(v23, p[23], sum);
        sum = fmaf(v24, p[24], sum);
        sum = fmaf(v25, p[25], sum);
        sum = fmaf(v26, p[26], sum);
        sum = fmaf(v27, p[27], sum);
        sum = fmaf(v28, p[28], sum);
        sum = fmaf(v29, p[29], sum);
        sum = fmaf(v30, p[30], sum);
        sum = fmaf(v31, p[31], sum);

        float cur;
        if (i == 0) cur = v0;
        else if (i == 1) cur = v1;
        else if (i == 2) cur = v2;
        else if (i == 3) cur = v3;
        else if (i == 4) cur = v4;
        else if (i == 5) cur = v5;
        else if (i == 6) cur = v6;
        else if (i == 7) cur = v7;
        else if (i == 8) cur = v8;
        else if (i == 9) cur = v9;
        else if (i == 10) cur = v10;
        else if (i == 11) cur = v11;
        else if (i == 12) cur = v12;
        else if (i == 13) cur = v13;
        else if (i == 14) cur = v14;
        else if (i == 15) cur = v15;
        else if (i == 16) cur = v16;
        else if (i == 17) cur = v17;
        else if (i == 18) cur = v18;
        else if (i == 19) cur = v19;
        else if (i == 20) cur = v20;
        else if (i == 21) cur = v21;
        else if (i == 22) cur = v22;
        else if (i == 23) cur = v23;
        else if (i == 24) cur = v24;
        else if (i == 25) cur = v25;
        else if (i == 26) cur = v26;
        else if (i == 27) cur = v27;
        else if (i == 28) cur = v28;
        else if (i == 29) cur = v29;
        else if (i == 30) cur = v30;
        else cur = v31;

        float pii = p[i];
        float residual = sum - cur * pii;
        float nv = mu[i] - residual / pii + rsqrtf(pii) * z[(long long)i * B + b];

        if (i == 0) v0 = nv;
        else if (i == 1) v1 = nv;
        else if (i == 2) v2 = nv;
        else if (i == 3) v3 = nv;
        else if (i == 4) v4 = nv;
        else if (i == 5) v5 = nv;
        else if (i == 6) v6 = nv;
        else if (i == 7) v7 = nv;
        else if (i == 8) v8 = nv;
        else if (i == 9) v9 = nv;
        else if (i == 10) v10 = nv;
        else if (i == 11) v11 = nv;
        else if (i == 12) v12 = nv;
        else if (i == 13) v13 = nv;
        else if (i == 14) v14 = nv;
        else if (i == 15) v15 = nv;
        else if (i == 16) v16 = nv;
        else if (i == 17) v17 = nv;
        else if (i == 18) v18 = nv;
        else if (i == 19) v19 = nv;
        else if (i == 20) v20 = nv;
        else if (i == 21) v21 = nv;
        else if (i == 22) v22 = nv;
        else if (i == 23) v23 = nv;
        else if (i == 24) v24 = nv;
        else if (i == 25) v25 = nv;
        else if (i == 26) v26 = nv;
        else if (i == 27) v27 = nv;
        else if (i == 28) v28 = nv;
        else if (i == 29) v29 = nv;
        else if (i == 30) v30 = nv;
        else v31 = nv;
    }

    float* y = out + ((long long)b) * 32;
    y[0] = v0; y[1] = v1; y[2] = v2; y[3] = v3;
    y[4] = v4; y[5] = v5; y[6] = v6; y[7] = v7;
    y[8] = v8; y[9] = v9; y[10] = v10; y[11] = v11;
    y[12] = v12; y[13] = v13; y[14] = v14; y[15] = v15;
    y[16] = v16; y[17] = v17; y[18] = v18; y[19] = v19;
    y[20] = v20; y[21] = v21; y[22] = v22; y[23] = v23;
    y[24] = v24; y[25] = v25; y[26] = v26; y[27] = v27;
    y[28] = v28; y[29] = v29; y[30] = v30; y[31] = v31;
}

__global__ void gibbs_generic_kernel(const float* __restrict__ x,
                                     const float* __restrict__ P,
                                     const float* __restrict__ mu,
                                     const float* __restrict__ z,
                                     float* __restrict__ out,
                                     int B, int D) {
    int b = blockIdx.x * blockDim.x + threadIdx.x;
    if (b >= B) return;
    float vals[64];
    for (int j = 0; j < D && j < 64; ++j) vals[j] = x[(long long)b * D + j];
    for (int i = 0; i < D && i < 64; ++i) {
        const float* p = P + (long long)i * D;
        float sum = 0.0f;
        for (int j = 0; j < D && j < 64; ++j) sum = fmaf(vals[j], p[j], sum);
        float pii = p[i];
        float residual = sum - vals[i] * pii;
        vals[i] = mu[i] - residual / pii + rsqrtf(pii) * z[(long long)i * B + b];
    }
    for (int j = 0; j < D && j < 64; ++j) out[(long long)b * D + j] = vals[j];
}

torch::Tensor gibbs_forward_cuda(torch::Tensor x, torch::Tensor precision, torch::Tensor mu, torch::Tensor noise) {
    const int B = (int)x.size(0);
    const int D = (int)x.size(1);
    auto out = torch::empty_like(x);
    const int threads = 256;
    const int blocks = (B + threads - 1) / threads;
    if (D == 32) {
        gibbs32_kernel<<<blocks, threads>>>(x.data_ptr<float>(), precision.data_ptr<float>(), mu.data_ptr<float>(), noise.data_ptr<float>(), out.data_ptr<float>(), B);
    } else {
        gibbs_generic_kernel<<<blocks, threads>>>(x.data_ptr<float>(), precision.data_ptr<float>(), mu.data_ptr<float>(), noise.data_ptr<float>(), out.data_ptr<float>(), B, D);
    }
    return out;
}
"""

_gibbs_ext = load_inline(
    name="gibbs_sweep_ext_v1",
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=["gibbs_forward_cuda"],
    with_cuda=True,
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3", "--use_fast_math"],
    verbose=False,
)


class ModelNew(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.dim = dim
        L = torch.randn(dim, dim) * 0.1
        precision = L @ L.t() + torch.eye(dim) * 2.0
        self.register_buffer("precision", precision)
        self.register_buffer("mu", torch.zeros(dim))

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # Match the reference RNG stream: reference draws one contiguous (B,) vector
        # for each coordinate, so a contiguous (dim, B) tensor has the same order.
        noise = torch.randn((self.dim, x.shape[0]), device=x.device, dtype=x.dtype)
        return _gibbs_ext.gibbs_forward_cuda(x, self.precision, self.mu, noise)