igneum/proto-cuda/dot4-probe.cu

115 lines
6.4 KiB
Text

// dot4-probe (CUDA): dp4a-class throughput on NVIDIA, standalone (no pack, no lottery kernel).
// Counter ASIC 2.0 layer 7 (docs/analysis/int8-matrix-family.md), 5 October 2026. PC job; not run on the Mac.
//
// Three dependent chains, same shape as the OpenCL --memprobe ALU chain (proto-opencl/host.c, probe_alu) and the
// Metal probe (proto-metal/dot4-probe.swift): 1,048,576 lanes x 4,096 steps, best of 3, event time.
// alu x = x * K + rotl(y, 7); y = (y ^ x) + s the card's integer baseline, 5 ops per step counted
// dot4i acc = __dp4a(x, y, acc) (PTX dp4a.s32.s32, sm_61+) one hardware dot4 per step per lane
// dot4e the scalar emulation of the same (4 sign-extended byte products summed, wrapping int32)
// The emulation and the intrinsic must agree bit for bit with the CPU reference (checked on two lanes per run).
//
// Build (Windows, CUDA Toolkit): nvcc -O2 -arch=sm_120 -o dot4-probe-cuda.exe dot4-probe.cu
// Build (Linux): nvcc -O2 -arch=sm_120 -o dot4-probe-cuda dot4-probe.cu
// Run: dot4-probe-cuda [--lanes N] [--steps N] [--reps N] [--device N]
#include <cuda_runtime.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
__host__ __device__ inline uint32_t pm_mix(uint32_t x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return x; }
__host__ __device__ inline uint32_t rotl32(uint32_t v, uint32_t n) { return (v << n) | (v >> (32u - n)); }
__host__ __device__ inline int32_t dot4_emul(uint32_t a, uint32_t b, int32_t acc) {
int32_t r = acc;
for (int i = 0; i < 4; ++i) {
int32_t ba = (int32_t)(int8_t)((a >> (8 * i)) & 0xffu);
int32_t bb = (int32_t)(int8_t)((b >> (8 * i)) & 0xffu);
r = (int32_t)((uint32_t)r + (uint32_t)(ba * bb));
}
return r;
}
__global__ void probe_alu(uint32_t steps, uint32_t seed, uint32_t* out) {
uint32_t g = blockIdx.x * blockDim.x + threadIdx.x;
uint32_t x = pm_mix(g ^ seed), y = x ^ 0x5bd1e995u;
for (uint32_t s = 0; s < steps; ++s) { x = x * 0x9E3779B1u + rotl32(y, 7u); y = (y ^ x) + s; }
out[g] = x ^ y;
}
__global__ void probe_dot4i(uint32_t steps, uint32_t seed, uint32_t* out) {
uint32_t g = blockIdx.x * blockDim.x + threadIdx.x;
uint32_t x = pm_mix(g ^ seed), y = x ^ 0x5bd1e995u;
int32_t acc = (int32_t)pm_mix(x);
for (uint32_t s = 0; s < steps; ++s) {
acc = __dp4a((int)x, (int)y, acc);
x = x * 0x9E3779B1u + (uint32_t)acc;
y = rotl32(y, 7u) ^ ((uint32_t)acc + s);
}
out[g] = (uint32_t)acc ^ x ^ y;
}
__global__ void probe_dot4e(uint32_t steps, uint32_t seed, uint32_t* out) {
uint32_t g = blockIdx.x * blockDim.x + threadIdx.x;
uint32_t x = pm_mix(g ^ seed), y = x ^ 0x5bd1e995u;
int32_t acc = (int32_t)pm_mix(x);
for (uint32_t s = 0; s < steps; ++s) {
acc = dot4_emul(x, y, acc);
x = x * 0x9E3779B1u + (uint32_t)acc;
y = rotl32(y, 7u) ^ ((uint32_t)acc + s);
}
out[g] = (uint32_t)acc ^ x ^ y;
}
static uint32_t lane_ref(const char* name, uint32_t g, uint32_t seed, uint32_t steps) {
uint32_t x = pm_mix(g ^ seed), y = x ^ 0x5bd1e995u;
if (strcmp(name, "alu") == 0) {
for (uint32_t s = 0; s < steps; ++s) { x = x * 0x9E3779B1u + rotl32(y, 7u); y = (y ^ x) + s; }
return x ^ y;
}
int32_t acc = (int32_t)pm_mix(x);
for (uint32_t s = 0; s < steps; ++s) {
acc = dot4_emul(x, y, acc);
x = x * 0x9E3779B1u + (uint32_t)acc;
y = rotl32(y, 7u) ^ ((uint32_t)acc + s);
}
return (uint32_t)acc ^ x ^ y;
}
#define CK(x) do { cudaError_t e = (x); if (e != cudaSuccess) { printf("CUDA error %s at %s:%d\n", cudaGetErrorString(e), __FILE__, __LINE__); return 1; } } while (0)
int main(int argc, char** argv) {
uint32_t lanes = 1u << 20, steps = 4096u; int reps = 3, device = 0;
for (int i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "--lanes") && i + 1 < argc) lanes = (uint32_t)strtoul(argv[++i], 0, 10);
else if (!strcmp(argv[i], "--steps") && i + 1 < argc) steps = (uint32_t)strtoul(argv[++i], 0, 10);
else if (!strcmp(argv[i], "--reps") && i + 1 < argc) reps = atoi(argv[++i]);
else if (!strcmp(argv[i], "--device") && i + 1 < argc) device = atoi(argv[++i]);
else { printf("unknown argument %s\n", argv[i]); return 2; }
}
CK(cudaSetDevice(device));
cudaDeviceProp p; CK(cudaGetDeviceProperties(&p, device));
printf("dot4-probe (CUDA) on %s, sm_%d%d, %d SMs, %d MHz, lanes %u, steps %u, best of %d, event time\n", p.name, p.major, p.minor, p.multiProcessorCount, p.clockRate / 1000, lanes, steps, reps);
uint32_t* d_out; CK(cudaMalloc(&d_out, (size_t)lanes * 4));
uint32_t* h_out = (uint32_t*)malloc((size_t)lanes * 4);
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
printf("| kernel | lanes | steps | best ms | G steps/s (= G dot4/s for dot4 rows) | ns per dependent step | lanes 0 and last ok |\n|---|---|---|---|---|---|---|\n");
const char* names[3] = { "alu", "dot4i", "dot4e" };
for (int k = 0; k < 3; ++k) {
float best = 1e30f; int ok = 1;
for (int r = 0; r < reps; ++r) {
uint32_t seed = 0x2468aceu + (uint32_t)r * 0x9E3779B9u;
CK(cudaEventRecord(e0));
if (k == 0) probe_alu<<<lanes / 256, 256>>>(steps, seed, d_out);
else if (k == 1) probe_dot4i<<<lanes / 256, 256>>>(steps, seed, d_out);
else probe_dot4e<<<lanes / 256, 256>>>(steps, seed, d_out);
CK(cudaEventRecord(e1)); CK(cudaEventSynchronize(e1)); CK(cudaGetLastError());
float ms = 0; CK(cudaEventElapsedTime(&ms, e0, e1)); if (ms < best) best = ms;
CK(cudaMemcpy(h_out, d_out, (size_t)lanes * 4, cudaMemcpyDeviceToHost));
uint32_t gs[2] = { 0u, lanes - 1u };
for (int j = 0; j < 2; ++j) { uint32_t want = lane_ref(names[k], gs[j], seed, steps); if (h_out[gs[j]] != want) { ok = 0; printf("MISMATCH %s lane %u: gpu %08x cpu %08x\n", names[k], gs[j], h_out[gs[j]], want); } }
}
double sps = (double)lanes * (double)steps / (best / 1000.0);
printf("| %s | %u | %u | %.3f | %.2f | %.3f | %s |\n", names[k], lanes, steps, best, sps / 1e9, best * 1e6 / (double)steps, ok ? "yes" : "NO");
printf("RESULT DOT4 vendor=nvidia device=\"%s\" kernel=%s lanes=%u steps=%u best_ms=%.3f gsteps_per_s=%.2f ok=%d\n", p.name, names[k], lanes, steps, best, sps / 1e9, ok);
}
printf("dot4-probe: done\n");
return 0;
}