diff options
Diffstat (limited to 'tools/cuda-ab/perf/perf_workload.cu')
| -rw-r--r-- | tools/cuda-ab/perf/perf_workload.cu | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/tools/cuda-ab/perf/perf_workload.cu b/tools/cuda-ab/perf/perf_workload.cu new file mode 100644 index 0000000..e06454e --- /dev/null +++ b/tools/cuda-ab/perf/perf_workload.cu @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * perf_workload.cu - the Open-IOV near-native performance workload. + * + * One binary, two roles: the bare-metal oracle (run on the host with the + * distribution driver loaded) and the measured side (run in the guest on the + * Open-IOV emulated vGPU). Every metric is a stable, machine-readable PERF + * line; every result is verified exactly on both sides, so a perf run can + * never silently measure corrupted work. + * + * Stable output (greppable): + * PERF device=<name> cc=<maj>.<min> sm=<count> + * PERF run_cfg n_warm=.. n_malloc=.. n_thru=.. sizes=.. + * PERF init_ms=<f> + * PERF malloc_first_ms=<f> + * PERF malloc_ms n=.. mean=.. p50=.. p95=.. + * PERF copy dir=htod bytes=.. ms=.. gbps=.. + * PERF copy dir=dtoh bytes=.. ms=.. gbps=.. + * PERF kernel_fma iters=.. ms=.. gflops=.. bad=.. + * PERF launch_first_ms=<f> + * PERF launch_warm n=.. mean=.. p50=.. p95=.. + * PERF throughput n=.. wall_ms=.. kps=.. + * PERF clock64 available=0|1 min=.. median=.. + * PERF checksum fma=.. pattern=.. copy_bad=.. bad=.. + * PERF STATUS OK|FAIL + * + * Build with a static CUDA runtime so the guest needs only the driver's + * libcuda.so, exactly like ab.sh/victory_mail: + * + * nvcc -O2 -arch=sm_75 --cudart=static -o perf_workload perf_workload.cu + * + * Run (either side): + * ./perf_workload [--n-warm=N] [--n-thru=N] [--no-clock64] + */ + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <cmath> +#include <ctime> +#include <cuda_runtime.h> + +#define FMA_THREADS 8192 +#define FMA_ITERS 250000 +#define FMA_A 0.9999f +#define FMA_B 1.0e-6f + +#define PAT_THREADS 32768 +#define PAT_SEED 0x9e3779b9u + +#define MALLOC_BYTES (256u * 1024u) +#define N_MALLOC 50 +#define N_COPY 3 +#define CLK_BLOCKS 48 +#define CLK_ITERS 10000 + +static double now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec * 1e3 + (double)ts.tv_nsec / 1e6; +} + +static int cmp_double(const void *a, const void *b) +{ + double x = *(const double *)a; + double y = *(const double *)b; + + return (x > y) - (x < y); +} + +static double pct(double *v, int n, double p) +{ + qsort(v, n, sizeof(double), cmp_double); + return v[(int)(p / 100.0 * (n - 1) + 0.5)]; +} + +#define CK(x) do { \ + cudaError_t e_ = (x); \ + if (e_ != cudaSuccess) { \ + printf("PERF ERROR %s at line %d\n", \ + cudaGetErrorString(e_), __LINE__); \ + printf("PERF STATUS FAIL\n"); \ + return 1; \ + } \ +} while (0) + +__global__ void fma_chain(float *out, int iters, int n) +{ + int i = blockIdx.x * blockDim.x + threadIdx.x; + float x = 0.0f; + + if (i >= n) + return; + for (int t = 0; t < iters; t++) + x = __fmaf_rn(x, FMA_A, FMA_B); + out[i] = x; +} + +static float host_chain(int iters) +{ + float x = 0.0f; + + for (int t = 0; t < iters; t++) + x = fmaf(x, FMA_A, FMA_B); + return x; +} + +__global__ void pattern_kernel(unsigned *out, int n) +{ + int i = blockIdx.x * blockDim.x + threadIdx.x; + + if (i >= n) + return; + out[i] = (unsigned)i * 2654435761u ^ PAT_SEED; +} + +static unsigned host_pattern(int i) +{ + return (unsigned)i * 2654435761u ^ PAT_SEED; +} + +__global__ void clock_probe(unsigned long long *out) +{ + if (threadIdx.x == 0) { + unsigned long long s = clock64(); + float x = 0.0f; + + for (int t = 0; t < CLK_ITERS; t++) + x = __fmaf_rn(x, FMA_A, FMA_B); + out[blockIdx.x * 2 + 0] = s; + out[blockIdx.x * 2 + 1] = clock64(); + } +} + +struct opts { + int n_warm; + int n_thru; + int clock64; +}; + +static int parse_args(int argc, char **argv, struct opts *o) +{ + o->n_warm = 50; + o->n_thru = 100; + o->clock64 = 1; + for (int i = 1; i < argc; i++) { + if (!strncmp(argv[i], "--n-warm=", 9)) + o->n_warm = atoi(argv[i] + 9); + else if (!strncmp(argv[i], "--n-thru=", 9)) + o->n_thru = atoi(argv[i] + 9); + else if (!strcmp(argv[i], "--no-clock64")) + o->clock64 = 0; + else { + printf("usage: %s [--n-warm=N] [--n-thru=N] [--no-clock64]\n", + argv[0]); + return -1; + } + } + if (o->n_warm < 1) + o->n_warm = 1; + if (o->n_thru < 1) + o->n_thru = 1; + return 0; +} + +int main(int argc, char **argv) +{ + struct opts o; + double t_start = now_ms(); + double t0; + float ms; + double samples[256]; + int bad = 0, pat_bad = 0, copy_bad = 0; + float fma_ref = host_chain(FMA_ITERS); + + if (parse_args(argc, argv, &o) != 0) + return 2; + + /* P1: process start -> device ready (the init path). */ + int dev = 0; + struct cudaDeviceProp prop; + + CK(cudaGetDeviceCount(&dev)); + if (dev < 1) { + printf("PERF ERROR no CUDA devices\n"); + printf("PERF STATUS FAIL\n"); + return 1; + } + CK(cudaSetDevice(0)); + CK(cudaGetDeviceProperties(&prop, 0)); + printf("PERF device=%s cc=%d.%d sm=%d\n", prop.name, + prop.major, prop.minor, prop.multiProcessorCount); + + /* Touch the device so init_ms covers the full lazy-init path. */ + CK(cudaFree(0)); + printf("PERF init_ms=%.3f\n", now_ms() - t_start); + printf("PERF run_cfg n_warm=%d n_malloc=%d n_thru=%d sizes=4096,262144,4194304,16777216\n", + o.n_warm, N_MALLOC, o.n_thru); + + /* P2: cudaMalloc first-call and steady-state latency. */ + void *d_m = NULL; + + t0 = now_ms(); + CK(cudaMalloc(&d_m, MALLOC_BYTES)); + printf("PERF malloc_first_ms=%.3f\n", now_ms() - t0); + CK(cudaFree(d_m)); + + for (int i = 0; i < 8; i++) { + CK(cudaMalloc(&d_m, MALLOC_BYTES)); + CK(cudaFree(d_m)); + } + for (int i = 0; i < N_MALLOC; i++) { + t0 = now_ms(); + CK(cudaMalloc(&d_m, MALLOC_BYTES)); + CK(cudaFree(d_m)); + samples[i] = now_ms() - t0; + } + { + double s2[256]; + double a = 0; + + memcpy(s2, samples, sizeof(double) * N_MALLOC); + for (int i = 0; i < N_MALLOC; i++) + a += samples[i]; + printf("PERF malloc_ms n=%d mean=%.3f p50=%.3f p95=%.3f\n", + N_MALLOC, a / N_MALLOC, + pct(s2, N_MALLOC, 50), pct(s2, N_MALLOC, 95)); + } + + /* Verification pattern kernel + first launch. */ + unsigned *d_pat = NULL, *h_pat; + + h_pat = (unsigned *)malloc(sizeof(unsigned) * PAT_THREADS); + CK(cudaMalloc(&d_pat, sizeof(unsigned) * PAT_THREADS)); + + cudaEvent_t e0, e1; + + CK(cudaEventCreate(&e0)); + CK(cudaEventCreate(&e1)); + CK(cudaEventRecord(e0)); + pattern_kernel<<<(PAT_THREADS + 255) / 256, 256>>>(d_pat, PAT_THREADS); + CK(cudaGetLastError()); + CK(cudaEventRecord(e1)); + CK(cudaDeviceSynchronize()); + CK(cudaEventElapsedTime(&ms, e0, e1)); + printf("PERF launch_first_ms=%.3f\n", (double)ms); + CK(cudaMemcpy(h_pat, d_pat, sizeof(unsigned) * PAT_THREADS, + cudaMemcpyDeviceToHost)); + for (int i = 0; i < PAT_THREADS; i++) + if (h_pat[i] != host_pattern(i)) + pat_bad++; + + /* P4: warm launch latency (enqueue -> sync completion per launch). */ + if (o.n_warm > 256) + o.n_warm = 256; + for (int i = 0; i < o.n_warm; i++) { + CK(cudaEventRecord(e0)); + pattern_kernel<<<(PAT_THREADS + 255) / 256, 256>>>(d_pat, + PAT_THREADS); + CK(cudaGetLastError()); + CK(cudaEventRecord(e1)); + CK(cudaDeviceSynchronize()); + CK(cudaEventElapsedTime(&ms, e0, e1)); + samples[i] = (double)ms; + } + { + double s2[256]; + double a = 0; + + memcpy(s2, samples, sizeof(double) * o.n_warm); + for (int i = 0; i < o.n_warm; i++) + a += samples[i]; + printf("PERF launch_warm n=%d mean=%.3f p50=%.3f p95=%.3f\n", + o.n_warm, a / o.n_warm, + pct(s2, o.n_warm, 50), pct(s2, o.n_warm, 95)); + } + + /* P5/P9: compute kernel vs the bare-metal oracle (bit-exact). */ + float *d_fma = NULL, *h_fma; + + h_fma = (float *)malloc(sizeof(float) * FMA_THREADS); + CK(cudaMalloc(&d_fma, sizeof(float) * FMA_THREADS)); + CK(cudaEventRecord(e0)); + fma_chain<<<FMA_THREADS / 256, 256>>>(d_fma, FMA_ITERS, FMA_THREADS); + CK(cudaGetLastError()); + CK(cudaEventRecord(e1)); + CK(cudaDeviceSynchronize()); + CK(cudaEventElapsedTime(&ms, e0, e1)); + printf("PERF kernel_fma iters=%d ms=%.3f gflops=%.2f\n", + FMA_ITERS, (double)ms, + 2.0 * (double)FMA_THREADS * (double)FMA_ITERS / + ((double)ms * 1.0e6)); + CK(cudaMemcpy(h_fma, d_fma, sizeof(float) * FMA_THREADS, + cudaMemcpyDeviceToHost)); + for (int i = 0; i < FMA_THREADS; i++) + if (h_fma[i] != fma_ref) + bad++; + + /* P10: steady-state throughput (enqueue burst, one sync). */ + CK(cudaEventRecord(e0)); + for (int i = 0; i < o.n_thru; i++) { + pattern_kernel<<<(PAT_THREADS + 255) / 256, 256>>>(d_pat, + PAT_THREADS); + CK(cudaGetLastError()); + } + CK(cudaEventRecord(e1)); + CK(cudaDeviceSynchronize()); + CK(cudaEventElapsedTime(&ms, e0, e1)); + printf("PERF throughput n=%d wall_ms=%.3f kps=%.2f\n", + o.n_thru, (double)ms, + (double)o.n_thru / ((double)ms / 1.0e3)); + + /* P7/P8: HtoD and DtoH copy bandwidth, and a small round-trip check. */ + { + const size_t sizes[] = { 4096u, 262144u, 4194304u, 16777216u }; + unsigned char *h_a, *h_b, *d_a; + + h_a = (unsigned char *)malloc(sizes[3]); + h_b = (unsigned char *)malloc(sizes[3]); + if (!h_a || !h_b) { + printf("PERF ERROR host alloc failed\n"); + printf("PERF STATUS FAIL\n"); + return 1; + } + CK(cudaMalloc((void **)&d_a, sizes[3])); + for (int s = 0; s < 4; s++) { + size_t n = sizes[s]; + double best_h = 1e30, best_d = 1e30; + + for (int i = 0; i < (int)n; i++) + h_a[i] = (unsigned char)(i * 131 + s); + for (int rep = 0; rep <= N_COPY; rep++) { + CK(cudaEventRecord(e0)); + CK(cudaMemcpy(d_a, h_a, n, cudaMemcpyHostToDevice)); + CK(cudaEventRecord(e1)); + CK(cudaDeviceSynchronize()); + CK(cudaEventElapsedTime(&ms, e0, e1)); + if (rep > 0 && (double)ms < best_h) + best_h = (double)ms; + } + for (int rep = 0; rep <= N_COPY; rep++) { + CK(cudaEventRecord(e0)); + CK(cudaMemcpy(h_b, d_a, n, cudaMemcpyDeviceToHost)); + CK(cudaEventRecord(e1)); + CK(cudaDeviceSynchronize()); + CK(cudaEventElapsedTime(&ms, e0, e1)); + if (rep > 0 && (double)ms < best_d) + best_d = (double)ms; + } + printf("PERF copy dir=htod bytes=%zu ms=%.4f gbps=%.3f\n", + n, best_h, (double)n / (best_h * 1.0e6)); + printf("PERF copy dir=dtoh bytes=%zu ms=%.4f gbps=%.3f\n", + n, best_d, (double)n / (best_d * 1.0e6)); + if (s == 0 && memcmp(h_a, h_b, n) != 0) + copy_bad++; + } + free(h_a); + free(h_b); + cudaFree(d_a); + } + + /* Optional clock64 cross-check (SM cycle counter, not wall time). */ + if (o.clock64) { + unsigned long long *d_clk, *h_clk; + + h_clk = (unsigned long long *)malloc( + sizeof(unsigned long long) * CLK_BLOCKS * 2); + CK(cudaMalloc((void **)&d_clk, + sizeof(unsigned long long) * CLK_BLOCKS * 2)); + CK(cudaMemset(d_clk, 0, + sizeof(unsigned long long) * CLK_BLOCKS * 2)); + clock_probe<<<CLK_BLOCKS, 32>>>(d_clk); + CK(cudaGetLastError()); + CK(cudaDeviceSynchronize()); + CK(cudaMemcpy(h_clk, d_clk, + sizeof(unsigned long long) * CLK_BLOCKS * 2, + cudaMemcpyDeviceToHost)); + { + double cyc[CLK_BLOCKS]; + int n = 0; + + for (int i = 0; i < CLK_BLOCKS; i++) { + if (h_clk[i * 2 + 1] > h_clk[i * 2] && + h_clk[i * 2] != 0) + cyc[n++] = (double)(h_clk[i * 2 + 1] - + h_clk[i * 2]); + } + if (n > 0) + printf("PERF clock64 available=1 min=%.0f median=%.0f\n", + pct(cyc, n, 0), pct(cyc, n, 50)); + else + printf("PERF clock64 available=0 min=0 median=0\n"); + } + free(h_clk); + cudaFree(d_clk); + } else { + printf("PERF clock64 available=0 min=0 median=0\n"); + } + + printf("PERF checksum fma=%.9g pattern=%u copy_bad=%d bad=%d\n", + (double)fma_ref, host_pattern(PAT_THREADS - 1), copy_bad, + bad + pat_bad); + printf("PERF STATUS %s\n", + (bad + pat_bad + copy_bad) == 0 ? "OK" : "FAIL"); + + cudaEventDestroy(e0); + cudaEventDestroy(e1); + cudaFree(d_pat); + cudaFree(d_fma); + free(h_pat); + free(h_fma); + return (bad + pat_bad + copy_bad) == 0 ? 0 : 1; +} |
