summaryrefslogtreecommitdiffstats
path: root/tools/cuda-ab
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cuda-ab')
-rwxr-xr-xtools/cuda-ab/run/transformerbin0 -> 846232 bytes
-rwxr-xr-xtools/cuda-ab/run/transformer_mtbin0 -> 845968 bytes
-rw-r--r--tools/cuda-ab/transformer.cu472
-rw-r--r--tools/cuda-ab/transformer_mt.cu468
4 files changed, 940 insertions, 0 deletions
diff --git a/tools/cuda-ab/run/transformer b/tools/cuda-ab/run/transformer
new file mode 100755
index 0000000..bcde39e
--- /dev/null
+++ b/tools/cuda-ab/run/transformer
Binary files differ
diff --git a/tools/cuda-ab/run/transformer_mt b/tools/cuda-ab/run/transformer_mt
new file mode 100755
index 0000000..32d159d
--- /dev/null
+++ b/tools/cuda-ab/run/transformer_mt
Binary files differ
diff --git a/tools/cuda-ab/transformer.cu b/tools/cuda-ab/transformer.cu
new file mode 100644
index 0000000..3044522
--- /dev/null
+++ b/tools/cuda-ab/transformer.cu
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * transformer.cu - single-head attention forward + 4-layer FFN mini-transformer.
+ *
+ * Two independent checks in one binary, both verified against an internal
+ * double-precision CPU reference (tolerance 1e-3 relative, 1e-6 absolute floor):
+ *
+ * 1. Attention: X[64x256], Wq/Wk/Wv/Wo[256x256] (head dim 256)
+ * Q = X*Wq, K = X*Wk, V = X*Wv
+ * S = Q*K^T / 16 (scale = 1/sqrt(head_dim))
+ * A = softmax(S) (row-wise)
+ * Y = (A*V)*Wo
+ * 2. Mini-transformer FFN head: h <- ReLU(h*Wf[l]) for 4 layers, then
+ * logits = h*Wh, P = softmax(logits) (row-wise over 256)
+ *
+ * Inputs and weights are multiples of 0.25/0.125, so every GEMM is exact in
+ * fp32; only the softmax exponentials differ from the CPU reference, hence
+ * the relative tolerance.
+ *
+ * Output lines are stable and greppable:
+ *
+ * DEVICE <name> cc=<major>.<minor> sm=<count> mem=<bytes>
+ * TRANSFORMER dh=256 seq=64 bad=<mismatches> ysum=<sum>
+ * FFN4 layers=4 bad=<mismatches> psum=<sum>
+ * STATUS OK|FAIL
+ *
+ * Build with a static CUDA runtime (--cudart=static) so the guest only needs
+ * the driver's libcuda.so, not a CUDA toolkit installation:
+ *
+ * nvcc -O2 --cudart=static -arch=sm_75 -o transformer transformer.cu
+ *
+ * Then, from the guest: /tmp/transformer
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstdint>
+#include <cmath>
+#include <cuda_runtime.h>
+
+#define CHECK(x) \
+ do { \
+ cudaError_t e_ = (x); \
+ if (e_ != cudaSuccess) { \
+ printf("CUDA_ERROR %s at line %d\n", \
+ cudaGetErrorString(e_), __LINE__); \
+ printf("STATUS FAIL\n"); \
+ return 1; \
+ } \
+ } while (0)
+
+#define SEQ 64
+#define DH 256
+#define TILE 16
+#define TOL 1e-3f
+
+__global__ void gemm_nn(const float *A, const float *B, float *C,
+ int M, int N, int K)
+{
+ __shared__ float As[TILE][TILE];
+ __shared__ float Bs[TILE][TILE];
+ int row = blockIdx.y * TILE + threadIdx.y;
+ int col = blockIdx.x * TILE + threadIdx.x;
+ float acc = 0.0f;
+
+ for (int t = 0; t < K / TILE; t++) {
+ As[threadIdx.y][threadIdx.x] = A[row * K + t * TILE + threadIdx.x];
+ Bs[threadIdx.y][threadIdx.x] =
+ B[(t * TILE + threadIdx.y) * N + col];
+ __syncthreads();
+ for (int k = 0; k < TILE; k++)
+ acc += As[threadIdx.y][k] * Bs[k][threadIdx.x];
+ __syncthreads();
+ }
+ C[row * N + col] = acc;
+}
+
+__global__ void gemm_nt(const float *A, const float *B, float *C,
+ int M, int N, int K)
+{
+ /* C[i][j] = sum_k A[i][k] * B[j][k] (B is [N][K], row-major). */
+ __shared__ float As[TILE][TILE];
+ __shared__ float Bs[TILE][TILE];
+ int row = blockIdx.y * TILE + threadIdx.y;
+ int col = blockIdx.x * TILE + threadIdx.x;
+ float acc = 0.0f;
+
+ for (int t = 0; t < K / TILE; t++) {
+ As[threadIdx.y][threadIdx.x] = A[row * K + t * TILE + threadIdx.x];
+ Bs[threadIdx.y][threadIdx.x] =
+ B[(blockIdx.x * TILE + threadIdx.y) * K +
+ t * TILE + threadIdx.x];
+ __syncthreads();
+ for (int k = 0; k < TILE; k++)
+ acc += As[threadIdx.y][k] * Bs[threadIdx.x][k];
+ __syncthreads();
+ }
+ C[row * N + col] = acc;
+}
+
+__global__ void softmax_rows(const float *S, float *P, int rows, int cols,
+ float scale)
+{
+ extern __shared__ float sm[];
+ int row = blockIdx.x;
+ int t = threadIdx.x;
+ float v = S[row * cols + t] * scale;
+ int stride;
+
+ sm[t] = v;
+ __syncthreads();
+ for (stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
+ if (t < stride)
+ sm[t] = fmaxf(sm[t], sm[t + stride]);
+ __syncthreads();
+ }
+ v = expf(v - sm[0]);
+ sm[t] = v;
+ __syncthreads();
+ for (stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
+ if (t < stride)
+ sm[t] += sm[t + stride];
+ __syncthreads();
+ }
+ P[row * cols + t] = v / sm[0];
+}
+
+__global__ void relu_inplace(float *h, int n)
+{
+ int i = blockIdx.x * blockDim.x + threadIdx.x;
+
+ if (i < n && h[i] < 0.0f)
+ h[i] = 0.0f;
+}
+
+/* ------------------------------------------------------------------ host */
+
+static void launch_gemm_nn(const float *A, const float *B, float *C,
+ int M, int N, int K)
+{
+ gemm_nn<<<dim3(N / TILE, M / TILE), dim3(TILE, TILE)>>>(A, B, C, M, N, K);
+}
+
+static void launch_gemm_nt(const float *A, const float *B, float *C,
+ int M, int N, int K)
+{
+ gemm_nt<<<dim3(N / TILE, M / TILE), dim3(TILE, TILE)>>>(A, B, C, M, N, K);
+}
+
+static void launch_softmax(const float *S, float *P, int rows, int cols,
+ float scale)
+{
+ softmax_rows<<<rows, cols, (size_t)cols * sizeof(float)>>>(S, P, rows,
+ cols,
+ scale);
+}
+
+/* Deterministic inputs: multiples of 0.25 (X) and 0.125 (weights). */
+static float xval(int i)
+{
+ return (float)(((i % 7) - 3)) * 0.25f;
+}
+
+static float wval(int i, int seed)
+{
+ return (float)(((i * 13 + seed * 101 + 7) % 5) - 2) * 0.125f;
+}
+
+static void ref_gemm(const double *A, const double *B, double *C,
+ int M, int N, int K)
+{
+ for (int i = 0; i < M; i++)
+ for (int j = 0; j < N; j++) {
+ double acc = 0.0;
+
+ for (int k = 0; k < K; k++)
+ acc += A[i * K + k] * B[k * N + j];
+ C[i * N + j] = acc;
+ }
+}
+
+static void ref_gemm_nt(const double *A, const double *B, double *C,
+ int M, int N, int K)
+{
+ for (int i = 0; i < M; i++)
+ for (int j = 0; j < N; j++) {
+ double acc = 0.0;
+
+ for (int k = 0; k < K; k++)
+ acc += A[i * K + k] * B[j * K + k];
+ C[i * N + j] = acc;
+ }
+}
+
+static void ref_softmax(const double *S, double *P, int rows, int cols,
+ double scale)
+{
+ for (int i = 0; i < rows; i++) {
+ const double *s = S + i * cols;
+ double *p = P + i * cols;
+ double m = s[0] * scale, sum = 0.0;
+
+ for (int j = 1; j < cols; j++)
+ if (s[j] * scale > m)
+ m = s[j] * scale;
+ for (int j = 0; j < cols; j++) {
+ p[j] = exp(s[j] * scale - m);
+ sum += p[j];
+ }
+ for (int j = 0; j < cols; j++)
+ p[j] /= sum;
+ }
+}
+
+static void ref_relu(double *h, int n)
+{
+ for (int i = 0; i < n; i++)
+ if (h[i] < 0.0)
+ h[i] = 0.0;
+}
+
+static int count_bad(const float *gpu, const double *ref, int n)
+{
+ int bad = 0;
+
+ for (int i = 0; i < n; i++) {
+ double g = (double)gpu[i];
+ double r = ref[i];
+
+ if (fabs(g - r) > (double)TOL * (fabs(r) + 1e-6))
+ bad++;
+ }
+ return bad;
+}
+
+static double dsum(const float *h, int n)
+{
+ double s = 0.0;
+
+ for (int i = 0; i < n; i++)
+ s += (double)h[i];
+ return s;
+}
+
+#define NF 4 /* FFN layers */
+
+int main(void)
+{
+ cudaDeviceProp prop;
+ const int seqdh = SEQ * DH, dh2 = DH * DH, seq2 = SEQ * SEQ;
+ const size_t b_seqdh = (size_t)seqdh * sizeof(float);
+ const size_t b_dh2 = (size_t)dh2 * sizeof(float);
+ const size_t b_seq2 = (size_t)seq2 * sizeof(float);
+ float *h_x, *h_y, *h_p;
+ double *r_x, *r_q, *r_k, *r_v, *r_s, *r_a, *r_av, *r_y;
+ double *r_f0, *r_f1, *r_logits, *r_p;
+ float *d_x, *d_q, *d_k, *d_v, *d_s, *d_a, *d_av, *d_y;
+ float *d_wq, *d_wk, *d_wv, *d_wo;
+ float *d_wf[NF], *d_wh, *d_f0, *d_f1, *d_logits, *d_p;
+ float *d_ping, *d_pong, *tmpf;
+ double *r_ping, *r_pong, *tmpd;
+ int bad_y, bad_p, i, l;
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ CHECK(cudaGetDeviceProperties(&prop, 0));
+ printf("DEVICE %s cc=%d.%d sm=%d mem=%llu\n", prop.name,
+ prop.major, prop.minor, prop.multiProcessorCount,
+ (unsigned long long)prop.totalGlobalMem);
+
+ /* host inputs + CPU reference workspace */
+ h_x = (float *)malloc(b_seqdh);
+ h_y = (float *)malloc(b_seqdh);
+ h_p = (float *)malloc(b_seqdh);
+ r_x = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_q = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_k = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_v = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_s = (double *)malloc((size_t)seq2 * sizeof(double));
+ r_a = (double *)malloc((size_t)seq2 * sizeof(double));
+ r_av = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_y = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_f0 = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_f1 = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_logits = (double *)malloc((size_t)seqdh * sizeof(double));
+ r_p = (double *)malloc((size_t)seqdh * sizeof(double));
+ if (!h_x || !h_y || !h_p || !r_x || !r_q || !r_k || !r_v || !r_s ||
+ !r_a || !r_av || !r_y || !r_f0 || !r_f1 || !r_logits || !r_p)
+ return 1;
+
+ for (i = 0; i < seqdh; i++) {
+ h_x[i] = xval(i);
+ r_x[i] = (double)xval(i);
+ }
+
+ /* device buffers */
+ CHECK(cudaMalloc(&d_x, b_seqdh));
+ CHECK(cudaMalloc(&d_q, b_seqdh));
+ CHECK(cudaMalloc(&d_k, b_seqdh));
+ CHECK(cudaMalloc(&d_v, b_seqdh));
+ CHECK(cudaMalloc(&d_s, b_seq2));
+ CHECK(cudaMalloc(&d_a, b_seq2));
+ CHECK(cudaMalloc(&d_av, b_seqdh));
+ CHECK(cudaMalloc(&d_y, b_seqdh));
+ CHECK(cudaMalloc(&d_wq, b_dh2));
+ CHECK(cudaMalloc(&d_wk, b_dh2));
+ CHECK(cudaMalloc(&d_wv, b_dh2));
+ CHECK(cudaMalloc(&d_wo, b_dh2));
+ CHECK(cudaMalloc(&d_wh, b_dh2));
+ CHECK(cudaMalloc(&d_f0, b_seqdh));
+ CHECK(cudaMalloc(&d_f1, b_seqdh));
+ CHECK(cudaMalloc(&d_logits, b_seqdh));
+ CHECK(cudaMalloc(&d_p, b_seqdh));
+ for (l = 0; l < NF; l++)
+ CHECK(cudaMalloc(&d_wf[l], b_dh2));
+
+ /* weights: deterministic, uploaded once */
+ {
+ float *w = (float *)malloc(b_dh2);
+
+ if (!w)
+ return 1;
+ for (i = 0; i < dh2; i++)
+ w[i] = wval(i, 1);
+ CHECK(cudaMemcpy(d_wq, w, b_dh2, cudaMemcpyHostToDevice));
+ for (i = 0; i < dh2; i++)
+ w[i] = wval(i, 2);
+ CHECK(cudaMemcpy(d_wk, w, b_dh2, cudaMemcpyHostToDevice));
+ for (i = 0; i < dh2; i++)
+ w[i] = wval(i, 3);
+ CHECK(cudaMemcpy(d_wv, w, b_dh2, cudaMemcpyHostToDevice));
+ for (i = 0; i < dh2; i++)
+ w[i] = wval(i, 4);
+ CHECK(cudaMemcpy(d_wo, w, b_dh2, cudaMemcpyHostToDevice));
+ for (i = 0; i < dh2; i++)
+ w[i] = wval(i, 20);
+ CHECK(cudaMemcpy(d_wh, w, b_dh2, cudaMemcpyHostToDevice));
+ for (l = 0; l < NF; l++) {
+ for (i = 0; i < dh2; i++)
+ w[i] = wval(i, 10 + l);
+ CHECK(cudaMemcpy(d_wf[l], w, b_dh2,
+ cudaMemcpyHostToDevice));
+ }
+ free(w);
+ }
+ CHECK(cudaMemcpy(d_x, h_x, b_seqdh, cudaMemcpyHostToDevice));
+
+ /* ---- check 1: attention forward ---- */
+ launch_gemm_nn(d_x, d_wq, d_q, SEQ, DH, DH);
+ launch_gemm_nn(d_x, d_wk, d_k, SEQ, DH, DH);
+ launch_gemm_nn(d_x, d_wv, d_v, SEQ, DH, DH);
+ launch_gemm_nt(d_q, d_k, d_s, SEQ, SEQ, DH);
+ launch_softmax(d_s, d_a, SEQ, SEQ, 1.0f / 16.0f);
+ launch_gemm_nn(d_a, d_v, d_av, SEQ, DH, SEQ);
+ launch_gemm_nn(d_av, d_wo, d_y, SEQ, DH, DH);
+ CHECK(cudaGetLastError());
+ CHECK(cudaDeviceSynchronize());
+ CHECK(cudaMemcpy(h_y, d_y, b_seqdh, cudaMemcpyDeviceToHost));
+
+ {
+ double *r_w = (double *)malloc((size_t)dh2 * sizeof(double));
+
+ if (!r_w)
+ return 1;
+ for (i = 0; i < dh2; i++)
+ r_w[i] = (double)wval(i, 1);
+ ref_gemm(r_x, r_w, r_q, SEQ, DH, DH);
+ for (i = 0; i < dh2; i++)
+ r_w[i] = (double)wval(i, 2);
+ ref_gemm(r_x, r_w, r_k, SEQ, DH, DH);
+ for (i = 0; i < dh2; i++)
+ r_w[i] = (double)wval(i, 3);
+ ref_gemm(r_x, r_w, r_v, SEQ, DH, DH);
+ ref_gemm_nt(r_q, r_k, r_s, SEQ, SEQ, DH);
+ ref_softmax(r_s, r_a, SEQ, SEQ, 1.0 / 16.0);
+ ref_gemm(r_a, r_v, r_av, SEQ, DH, SEQ);
+ for (i = 0; i < dh2; i++)
+ r_w[i] = (double)wval(i, 4);
+ ref_gemm(r_av, r_w, r_y, SEQ, DH, DH);
+ free(r_w);
+ }
+
+ bad_y = count_bad(h_y, r_y, seqdh);
+ printf("TRANSFORMER dh=256 seq=64 bad=%d ysum=%.6f\n", bad_y,
+ dsum(h_y, seqdh));
+
+ /* ---- check 2: 4-layer FFN stack + softmax head ---- */
+ CHECK(cudaMemcpy(d_f0, h_x, b_seqdh, cudaMemcpyHostToDevice));
+ d_ping = d_f0;
+ d_pong = d_f1;
+ for (l = 0; l < NF; l++) {
+ launch_gemm_nn(d_ping, d_wf[l], d_pong, SEQ, DH, DH);
+ relu_inplace<<<(seqdh + 255) / 256, 256>>>(d_pong, seqdh);
+ tmpf = d_ping;
+ d_ping = d_pong;
+ d_pong = tmpf;
+ }
+ launch_gemm_nn(d_ping, d_wh, d_logits, SEQ, DH, DH);
+ launch_softmax(d_logits, d_p, SEQ, DH, 1.0f);
+ CHECK(cudaGetLastError());
+ CHECK(cudaDeviceSynchronize());
+ CHECK(cudaMemcpy(h_p, d_p, b_seqdh, cudaMemcpyDeviceToHost));
+
+ {
+ double *r_w = (double *)malloc((size_t)dh2 * sizeof(double));
+
+ if (!r_w)
+ return 1;
+ memcpy(r_f0, r_x, (size_t)seqdh * sizeof(double));
+ r_ping = r_f0;
+ r_pong = r_f1;
+ for (l = 0; l < NF; l++) {
+ for (i = 0; i < dh2; i++)
+ r_w[i] = (double)wval(i, 10 + l);
+ ref_gemm(r_ping, r_w, r_pong, SEQ, DH, DH);
+ ref_relu(r_pong, seqdh);
+ tmpd = r_ping;
+ r_ping = r_pong;
+ r_pong = tmpd;
+ }
+ for (i = 0; i < dh2; i++)
+ r_w[i] = (double)wval(i, 20);
+ ref_gemm(r_ping, r_w, r_logits, SEQ, DH, DH);
+ ref_softmax(r_logits, r_p, SEQ, DH, 1.0);
+ free(r_w);
+ }
+
+ bad_p = count_bad(h_p, r_p, seqdh);
+ printf("FFN4 layers=4 bad=%d psum=%.6f\n", bad_p, dsum(h_p, seqdh));
+
+ if (bad_y != 0 || bad_p != 0) {
+ printf("STATUS FAIL\n");
+ return 1;
+ }
+
+ printf("STATUS OK\n");
+ CHECK(cudaFree(d_x));
+ CHECK(cudaFree(d_q));
+ CHECK(cudaFree(d_k));
+ CHECK(cudaFree(d_v));
+ CHECK(cudaFree(d_s));
+ CHECK(cudaFree(d_a));
+ CHECK(cudaFree(d_av));
+ CHECK(cudaFree(d_y));
+ CHECK(cudaFree(d_wq));
+ CHECK(cudaFree(d_wk));
+ CHECK(cudaFree(d_wv));
+ CHECK(cudaFree(d_wo));
+ CHECK(cudaFree(d_wh));
+ for (l = 0; l < NF; l++)
+ CHECK(cudaFree(d_wf[l]));
+ CHECK(cudaFree(d_f0));
+ CHECK(cudaFree(d_f1));
+ CHECK(cudaFree(d_logits));
+ CHECK(cudaFree(d_p));
+ free(h_x);
+ free(h_y);
+ free(h_p);
+ free(r_x);
+ free(r_q);
+ free(r_k);
+ free(r_v);
+ free(r_s);
+ free(r_a);
+ free(r_av);
+ free(r_y);
+ free(r_f0);
+ free(r_f1);
+ free(r_logits);
+ free(r_p);
+ return 0;
+}
diff --git a/tools/cuda-ab/transformer_mt.cu b/tools/cuda-ab/transformer_mt.cu
new file mode 100644
index 0000000..ffd23aa
--- /dev/null
+++ b/tools/cuda-ab/transformer_mt.cu
@@ -0,0 +1,468 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * transformer_mt.cu - attention forward on 3 independent streams + a
+ * 5-launch sequential loop, in one binary.
+ *
+ * Same single-head attention op as transformer.cu (X[64x256],
+ * Wq/Wk/Wv/Wo[256x256], head dim 256, fp32, softmax(Q*K^T/16), Y=(A*V)*Wo),
+ * exercised two ways:
+ *
+ * 1. Multi-stream class: three independent streams each run the full op on
+ * their own input (X_s[i] = xval(i + s)). Every stream records an event
+ * after its chain; stream 2 waits on streams 0 and 1 via cudaStreamWaitEvent
+ * and the whole span is timed with cudaEventElapsedTime.
+ * 2. Repeated-launch class: the op is launched 5 times in a loop on one
+ * stream with per-iteration inputs and per-iteration outputs, one
+ * synchronization at the end.
+ *
+ * Both classes are verified against an internal double-precision CPU
+ * reference (tolerance 1e-3 relative, 1e-6 absolute floor).
+ *
+ * Output lines are stable and greppable:
+ *
+ * DEVICE <name> cc=<major>.<minor> sm=<count> mem=<bytes>
+ * TRANSFORMER_MT streams=3 bad=<mismatches> ysum=<sum>
+ * TRANSFORMER_LOOP launches=5 bad=<mismatches> ysum=<sum>
+ * TIMING ms=<elapsed>
+ * STATUS OK|FAIL
+ *
+ * Build with a static CUDA runtime (--cudart=static) so the guest only needs
+ * the driver's libcuda.so, not a CUDA toolkit installation:
+ *
+ * nvcc -O2 --cudart=static -arch=sm_75 -o transformer_mt transformer_mt.cu
+ *
+ * Then, from the guest: /tmp/transformer_mt
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstdint>
+#include <cmath>
+#include <cuda_runtime.h>
+
+#define CHECK(x) \
+ do { \
+ cudaError_t e_ = (x); \
+ if (e_ != cudaSuccess) { \
+ printf("CUDA_ERROR %s at line %d\n", \
+ cudaGetErrorString(e_), __LINE__); \
+ printf("STATUS FAIL\n"); \
+ return 1; \
+ } \
+ } while (0)
+
+#define SEQ 64
+#define DH 256
+#define TILE 16
+#define TOL 1e-3f
+#define NST 3
+#define NLOOP 5
+
+__global__ void gemm_nn(const float *A, const float *B, float *C,
+ int M, int N, int K)
+{
+ __shared__ float As[TILE][TILE];
+ __shared__ float Bs[TILE][TILE];
+ int row = blockIdx.y * TILE + threadIdx.y;
+ int col = blockIdx.x * TILE + threadIdx.x;
+ float acc = 0.0f;
+
+ for (int t = 0; t < K / TILE; t++) {
+ As[threadIdx.y][threadIdx.x] = A[row * K + t * TILE + threadIdx.x];
+ Bs[threadIdx.y][threadIdx.x] =
+ B[(t * TILE + threadIdx.y) * N + col];
+ __syncthreads();
+ for (int k = 0; k < TILE; k++)
+ acc += As[threadIdx.y][k] * Bs[k][threadIdx.x];
+ __syncthreads();
+ }
+ C[row * N + col] = acc;
+}
+
+__global__ void gemm_nt(const float *A, const float *B, float *C,
+ int M, int N, int K)
+{
+ /* C[i][j] = sum_k A[i][k] * B[j][k] (B is [N][K], row-major). */
+ __shared__ float As[TILE][TILE];
+ __shared__ float Bs[TILE][TILE];
+ int row = blockIdx.y * TILE + threadIdx.y;
+ int tcol = blockIdx.x * TILE + threadIdx.x;
+ float acc = 0.0f;
+
+ for (int t = 0; t < K / TILE; t++) {
+ As[threadIdx.y][threadIdx.x] = A[row * K + t * TILE + threadIdx.x];
+ Bs[threadIdx.y][threadIdx.x] =
+ B[(blockIdx.x * TILE + threadIdx.y) * K +
+ t * TILE + threadIdx.x];
+ __syncthreads();
+ for (int k = 0; k < TILE; k++)
+ acc += As[threadIdx.y][k] * Bs[threadIdx.x][k];
+ __syncthreads();
+ }
+ C[row * N + tcol] = acc;
+}
+
+__global__ void softmax_rows(const float *S, float *P, int rows, int cols,
+ float scale)
+{
+ extern __shared__ float sm[];
+ int row = blockIdx.x;
+ int t = threadIdx.x;
+ float v = S[row * cols + t] * scale;
+ int stride;
+
+ sm[t] = v;
+ __syncthreads();
+ for (stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
+ if (t < stride)
+ sm[t] = fmaxf(sm[t], sm[t + stride]);
+ __syncthreads();
+ }
+ v = expf(v - sm[0]);
+ sm[t] = v;
+ __syncthreads();
+ for (stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
+ if (t < stride)
+ sm[t] += sm[t + stride];
+ __syncthreads();
+ }
+ P[row * cols + t] = v / sm[0];
+}
+
+/* ------------------------------------------------------------------ host */
+
+struct attn_bufs {
+ float *q, *k, *v, *s, *a, *av, *y;
+};
+
+static void launch_chain(cudaStream_t st, const float *x, struct attn_bufs b,
+ const float *wq, const float *wk, const float *wv,
+ const float *wo)
+{
+ dim3 g1(DH / TILE, SEQ / TILE);
+ dim3 g2(SEQ / TILE, SEQ / TILE);
+ dim3 tb(TILE, TILE);
+
+ gemm_nn<<<g1, tb, 0, st>>>(x, wq, b.q, SEQ, DH, DH);
+ gemm_nn<<<g1, tb, 0, st>>>(x, wk, b.k, SEQ, DH, DH);
+ gemm_nn<<<g1, tb, 0, st>>>(x, wv, b.v, SEQ, DH, DH);
+ gemm_nt<<<g2, tb, 0, st>>>(b.q, b.k, b.s, SEQ, SEQ, DH);
+ softmax_rows<<<SEQ, SEQ, (size_t)SEQ * sizeof(float), st>>>(b.s, b.a,
+ SEQ, SEQ,
+ 1.0f / 16.0f);
+ gemm_nn<<<g1, tb, 0, st>>>(b.a, b.v, b.av, SEQ, DH, SEQ);
+ gemm_nn<<<g1, tb, 0, st>>>(b.av, wo, b.y, SEQ, DH, DH);
+}
+
+static float xval(int i)
+{
+ return (float)(((i % 7) - 3)) * 0.25f;
+}
+
+static float wval(int i, int seed)
+{
+ return (float)(((i * 13 + seed * 101 + 7) % 5) - 2) * 0.125f;
+}
+
+static void ref_gemm(const double *A, const double *B, double *C,
+ int M, int N, int K)
+{
+ for (int i = 0; i < M; i++)
+ for (int j = 0; j < N; j++) {
+ double acc = 0.0;
+
+ for (int k = 0; k < K; k++)
+ acc += A[i * K + k] * B[k * N + j];
+ C[i * N + j] = acc;
+ }
+}
+
+static void ref_gemm_nt(const double *A, const double *B, double *C,
+ int M, int N, int K)
+{
+ for (int i = 0; i < M; i++)
+ for (int j = 0; j < N; j++) {
+ double acc = 0.0;
+
+ for (int k = 0; k < K; k++)
+ acc += A[i * K + k] * B[j * K + k];
+ C[i * N + j] = acc;
+ }
+}
+
+static void ref_softmax(const double *S, double *P, int rows, int cols,
+ double scale)
+{
+ for (int i = 0; i < rows; i++) {
+ const double *s = S + i * cols;
+ double *p = P + i * cols;
+ double m = s[0] * scale, sum = 0.0;
+
+ for (int j = 1; j < cols; j++)
+ if (s[j] * scale > m)
+ m = s[j] * scale;
+ for (int j = 0; j < cols; j++) {
+ p[j] = exp(s[j] * scale - m);
+ sum += p[j];
+ }
+ for (int j = 0; j < cols; j++)
+ p[j] /= sum;
+ }
+}
+
+static int count_bad(const float *gpu, const double *ref, int n)
+{
+ int bad = 0;
+
+ for (int i = 0; i < n; i++) {
+ double g = (double)gpu[i];
+ double r = ref[i];
+
+ if (fabs(g - r) > (double)TOL * (fabs(r) + 1e-6))
+ bad++;
+ }
+ return bad;
+}
+
+static double dsum(const float *h, int n)
+{
+ double s = 0.0;
+
+ for (int i = 0; i < n; i++)
+ s += (double)h[i];
+ return s;
+}
+
+/* Verify one attention result against the CPU reference for input offset o. */
+static int verify_one(const float *h_y, int o, double *ysum_out)
+{
+ const int seqdh = SEQ * DH, seq2 = SEQ * SEQ;
+ double *x, *q, *k, *v, *s, *a, *av, *y, *w;
+ int i, bad;
+
+ x = (double *)malloc((size_t)seqdh * sizeof(double));
+ q = (double *)malloc((size_t)seqdh * sizeof(double));
+ k = (double *)malloc((size_t)seqdh * sizeof(double));
+ v = (double *)malloc((size_t)seqdh * sizeof(double));
+ s = (double *)malloc((size_t)seq2 * sizeof(double));
+ a = (double *)malloc((size_t)seq2 * sizeof(double));
+ av = (double *)malloc((size_t)seqdh * sizeof(double));
+ y = (double *)malloc((size_t)seqdh * sizeof(double));
+ w = (double *)malloc((size_t)DH * DH * sizeof(double));
+ if (!x || !q || !k || !v || !s || !a || !av || !y || !w)
+ exit(1);
+
+ for (i = 0; i < seqdh; i++)
+ x[i] = (double)xval(i + o);
+ for (i = 0; i < DH * DH; i++)
+ w[i] = (double)wval(i, 1);
+ ref_gemm(x, w, q, SEQ, DH, DH);
+ for (i = 0; i < DH * DH; i++)
+ w[i] = (double)wval(i, 2);
+ ref_gemm(x, w, k, SEQ, DH, DH);
+ for (i = 0; i < DH * DH; i++)
+ w[i] = (double)wval(i, 3);
+ ref_gemm(x, w, v, SEQ, DH, DH);
+ ref_gemm_nt(q, k, s, SEQ, SEQ, DH);
+ ref_softmax(s, a, SEQ, SEQ, 1.0 / 16.0);
+ ref_gemm(a, v, av, SEQ, DH, SEQ);
+ for (i = 0; i < DH * DH; i++)
+ w[i] = (double)wval(i, 4);
+ ref_gemm(av, w, y, SEQ, DH, DH);
+
+ bad = count_bad(h_y, y, seqdh);
+ *ysum_out = dsum(h_y, seqdh);
+ free(x);
+ free(q);
+ free(k);
+ free(v);
+ free(s);
+ free(a);
+ free(av);
+ free(y);
+ free(w);
+ return bad;
+}
+
+int main(void)
+{
+ cudaDeviceProp prop;
+ const int seqdh = SEQ * DH, dh2 = DH * DH;
+ const size_t b_seqdh = (size_t)seqdh * sizeof(float);
+ const size_t b_dh2 = (size_t)dh2 * sizeof(float);
+ const size_t b_seq2 = (size_t)SEQ * SEQ * sizeof(float);
+ cudaStream_t st[NST], st_loop;
+ cudaEvent_t ev[NST], t0, t1;
+ struct attn_bufs ab[NST], lb;
+ float *d_x[NST], *h_y[NST];
+ float *d_xl[NLOOP], *d_yl[NLOOP], *h_yl[NLOOP];
+ float *d_wq, *d_wk, *d_wv, *d_wo, *wl;
+ float ms = 0.0f;
+ int bad_mt = 0, bad_loop = 0, s, i;
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ CHECK(cudaGetDeviceProperties(&prop, 0));
+ printf("DEVICE %s cc=%d.%d sm=%d mem=%llu\n", prop.name,
+ prop.major, prop.minor, prop.multiProcessorCount,
+ (unsigned long long)prop.totalGlobalMem);
+
+ CHECK(cudaMalloc(&d_wq, b_dh2));
+ CHECK(cudaMalloc(&d_wk, b_dh2));
+ CHECK(cudaMalloc(&d_wv, b_dh2));
+ CHECK(cudaMalloc(&d_wo, b_dh2));
+ wl = (float *)malloc(b_dh2);
+ if (!wl)
+ return 1;
+ for (i = 0; i < dh2; i++)
+ wl[i] = wval(i, 1);
+ CHECK(cudaMemcpy(d_wq, wl, b_dh2, cudaMemcpyHostToDevice));
+ for (i = 0; i < dh2; i++)
+ wl[i] = wval(i, 2);
+ CHECK(cudaMemcpy(d_wk, wl, b_dh2, cudaMemcpyHostToDevice));
+ for (i = 0; i < dh2; i++)
+ wl[i] = wval(i, 3);
+ CHECK(cudaMemcpy(d_wv, wl, b_dh2, cudaMemcpyHostToDevice));
+ for (i = 0; i < dh2; i++)
+ wl[i] = wval(i, 4);
+ CHECK(cudaMemcpy(d_wo, wl, b_dh2, cudaMemcpyHostToDevice));
+
+ /* ------------- class 1: 3 independent streams ------------- */
+ for (s = 0; s < NST; s++) {
+ float *hx = (float *)malloc(b_seqdh);
+
+ if (!hx)
+ return 1;
+ for (i = 0; i < seqdh; i++)
+ hx[i] = xval(i + s);
+ h_y[s] = (float *)malloc(b_seqdh);
+ if (!h_y[s])
+ return 1;
+
+ CHECK(cudaMalloc(&d_x[s], b_seqdh));
+ CHECK(cudaMalloc(&ab[s].q, b_seqdh));
+ CHECK(cudaMalloc(&ab[s].k, b_seqdh));
+ CHECK(cudaMalloc(&ab[s].v, b_seqdh));
+ CHECK(cudaMalloc(&ab[s].s, b_seq2));
+ CHECK(cudaMalloc(&ab[s].a, b_seq2));
+ CHECK(cudaMalloc(&ab[s].av, b_seqdh));
+ CHECK(cudaMalloc(&ab[s].y, b_seqdh));
+ CHECK(cudaStreamCreate(&st[s]));
+ CHECK(cudaEventCreate(&ev[s]));
+ CHECK(cudaMemcpyAsync(d_x[s], hx, b_seqdh,
+ cudaMemcpyHostToDevice, st[s]));
+ free(hx);
+ }
+ CHECK(cudaEventCreate(&t0));
+ CHECK(cudaEventCreate(&t1));
+ CHECK(cudaEventRecord(t0));
+
+ for (s = 0; s < NST; s++) {
+ launch_chain(st[s], d_x[s], ab[s], d_wq, d_wk, d_wv, d_wo);
+ CHECK(cudaGetLastError());
+ CHECK(cudaEventRecord(ev[s], st[s]));
+ CHECK(cudaMemcpyAsync(h_y[s], ab[s].y, b_seqdh,
+ cudaMemcpyDeviceToHost, st[s]));
+ }
+ /* event-ordered join: stream 2 waits on streams 0 and 1 */
+ CHECK(cudaStreamWaitEvent(st[2], ev[0], 0));
+ CHECK(cudaStreamWaitEvent(st[2], ev[1], 0));
+ CHECK(cudaEventRecord(t1, st[2]));
+ for (s = 0; s < NST; s++)
+ CHECK(cudaStreamSynchronize(st[s]));
+ CHECK(cudaEventElapsedTime(&ms, t0, t1));
+
+ for (s = 0; s < NST; s++) {
+ double ys;
+
+ bad_mt += verify_one(h_y[s], s, &ys);
+ }
+ printf("TRANSFORMER_MT streams=%d bad=%d ysum=%.6f\n", NST, bad_mt,
+ dsum(h_y[0], seqdh));
+
+ /* ----------------- class 2: 5-launch loop ----------------- */
+ CHECK(cudaStreamCreate(&st_loop));
+ CHECK(cudaMalloc(&lb.q, b_seqdh));
+ CHECK(cudaMalloc(&lb.k, b_seqdh));
+ CHECK(cudaMalloc(&lb.v, b_seqdh));
+ CHECK(cudaMalloc(&lb.s, b_seq2));
+ CHECK(cudaMalloc(&lb.a, b_seq2));
+ CHECK(cudaMalloc(&lb.av, b_seqdh));
+ CHECK(cudaMalloc(&lb.y, b_seqdh)); /* unused; op writes per-iter y */
+ for (i = 0; i < NLOOP; i++) {
+ float *hx = (float *)malloc(b_seqdh);
+
+ if (!hx)
+ return 1;
+ for (s = 0; s < seqdh; s++)
+ hx[s] = xval(s + 11 * i);
+ CHECK(cudaMalloc(&d_xl[i], b_seqdh));
+ CHECK(cudaMalloc(&d_yl[i], b_seqdh));
+ CHECK(cudaMemcpy(d_xl[i], hx, b_seqdh,
+ cudaMemcpyHostToDevice));
+ h_yl[i] = (float *)malloc(b_seqdh);
+ if (!h_yl[i])
+ return 1;
+ free(hx);
+ }
+
+ for (i = 0; i < NLOOP; i++) {
+ struct attn_bufs b = lb;
+
+ b.y = d_yl[i];
+ launch_chain(st_loop, d_xl[i], b, d_wq, d_wk, d_wv, d_wo);
+ CHECK(cudaGetLastError());
+ }
+ CHECK(cudaStreamSynchronize(st_loop));
+ for (i = 0; i < NLOOP; i++) {
+ double ys;
+
+ CHECK(cudaMemcpy(h_yl[i], d_yl[i], b_seqdh,
+ cudaMemcpyDeviceToHost));
+ bad_loop += verify_one(h_yl[i], 11 * i, &ys);
+ }
+ printf("TRANSFORMER_LOOP launches=%d bad=%d ysum=%.6f\n", NLOOP,
+ bad_loop, dsum(h_yl[NLOOP - 1], seqdh));
+ printf("TIMING ms=%.3f\n", ms);
+
+ if (bad_mt != 0 || bad_loop != 0) {
+ printf("STATUS FAIL\n");
+ return 1;
+ }
+
+ printf("STATUS OK\n");
+ for (s = 0; s < NST; s++) {
+ CHECK(cudaEventDestroy(ev[s]));
+ CHECK(cudaStreamDestroy(st[s]));
+ CHECK(cudaFree(d_x[s]));
+ CHECK(cudaFree(ab[s].q));
+ CHECK(cudaFree(ab[s].k));
+ CHECK(cudaFree(ab[s].v));
+ CHECK(cudaFree(ab[s].s));
+ CHECK(cudaFree(ab[s].a));
+ CHECK(cudaFree(ab[s].av));
+ CHECK(cudaFree(ab[s].y));
+ free(h_y[s]);
+ }
+ for (i = 0; i < NLOOP; i++) {
+ CHECK(cudaFree(d_xl[i]));
+ CHECK(cudaFree(d_yl[i]));
+ free(h_yl[i]);
+ }
+ CHECK(cudaEventDestroy(t0));
+ CHECK(cudaEventDestroy(t1));
+ CHECK(cudaStreamDestroy(st_loop));
+ CHECK(cudaFree(lb.q));
+ CHECK(cudaFree(lb.k));
+ CHECK(cudaFree(lb.v));
+ CHECK(cudaFree(lb.s));
+ CHECK(cudaFree(lb.a));
+ CHECK(cudaFree(lb.av));
+ CHECK(cudaFree(lb.y));
+ CHECK(cudaFree(d_wq));
+ CHECK(cudaFree(d_wk));
+ CHECK(cudaFree(d_wv));
+ CHECK(cudaFree(d_wo));
+ free(wl);
+ return 0;
+}