skip to content
==> tests/cuda.c <==
1/* cuda.rs against a simulated device: eager loading, primary and green contexts,
2 * limits and register caps, a module's whole life (shape, scratch, globals,
3 * occupancy, launch, query, unload), and the refusals with their messages. */
4#include "mesh/check.h"
5#include "mesh/cuda.h"
6#include "mesh/cuda_fake.h"
7
8#include <stdatomic.h>
9#include <stdlib.h>
10#include <string.h>
11#include <time.h>
12
13static atomic_int ran;
14
15static void kernel(const char *entry, uint64_t mailbox, uint32_t slot, uint32_t nodes, uint32_t threads, void *user) {
16    (void)user;
17    CHECK_S(entry, "mesh_main");
18    _Atomic uint32_t *w = (_Atomic uint32_t *)(uintptr_t)(mailbox + 0x100);
19    atomic_store(w, slot * 1000 + nodes * 10 + threads / 32);
20    struct timespec t = {0, 20000000};
21    nanosleep(&t, NULL);
22    atomic_fetch_add(&ran, 1);
23}
24
25int main(void) {
26    char *err = NULL;
27    /* force_eager refuses another setting. */
28    setenv("CUDA_MODULE_LOADING", "LAZY", 1);
29    CHECK(!mesh_cuda_force_eager(&err));
30    CHECK_S(err, "CUDA_MODULE_LOADING=\"LAZY\": refusing to run; module loading must be EAGER (a lazy load blocks behind resident kernels)");
31    unsetenv("CUDA_MODULE_LOADING");
32    mesh_cuda_gpu g;
33    CHECK(!mesh_cuda_gpu_open(0, &g, &err));
34    CHECK_S(err, "CUDA_MODULE_LOADING is not EAGER: call cuda::force_eager() first");
35    CHECK(mesh_cuda_force_eager(&err));
36    CHECK_S(getenv("CUDA_MODULE_LOADING"), "EAGER");
37
38    /* A driver that loads lazily is refused. */
39    mesh_fake_cuda_install(&(mesh_fake_cuda_cfg){.loading_mode = CU_MODULE_LAZY_LOADING});
40    CHECK(!mesh_cuda_gpu_open(0, &g, &err));
41    CHECK_S(err, "the driver loads modules CU_MODULE_LAZY_LOADING; EAGER is required (CUDA_MODULE_LOADING must be EAGER before cuInit)");
42
43    mesh_fake_cuda_install(&(mesh_fake_cuda_cfg){.host_atomics = 1, .run = kernel});
44    CHECK(mesh_cuda_gpu_open(0, &g, &err));
45    CHECK_S(g.name, "Fake GB10");
46    CHECK(g.cc_major == 12 && g.cc_minor == 1 && g.sms_total == 48 && g.sms == 48 && !g.green && g.blocks_per_sm == 24);
47    CHECK(g.host_atomics);
48    CHECK(g.limits.max_threads == 1024 && g.limits.regs_per_block == 65536 && g.limits.max_shared == 101376);
49    CHECK_I(mesh_fake_cuda_stats_now().primary_ctx, 1);
50    mesh_cuda_gpu_free(&g);
51
52    /* A green context of 8 SMs; an odd split is refused. */
53    CHECK(mesh_cuda_gpu_open(8, &g, &err));
54    CHECK(g.green && g.sms == 8 && g.sms_total == 48);
55    mesh_cuda_stream s;
56    CHECK(mesh_cuda_new_stream(&g, &s, &err));
57    mesh_cuda_stream_destroy(&s);
58    mesh_cuda_gpu_free(&g);
59    CHECK(!mesh_cuda_gpu_open(7, &g, &err));
60    CHECK_S(err, "green context: cannot split 7 SMs off 48");
61
62    /* Register caps and the block limits. */
63    mesh_cuda_limits l = {1024, 65536, 101376};
64    CHECK_U(mesh_cuda_reg_cap(&l, 1), 0);
65    CHECK_U(mesh_cuda_reg_cap(&l, 8), 0);
66    CHECK_U(mesh_cuda_reg_cap(&l, 16), 128);
67    CHECK_U(mesh_cuda_reg_cap(&l, 32), 64);
68    mesh_cuda_limits small = {1024, 16384, 49152};
69    CHECK_U(mesh_cuda_reg_cap(&small, 32), 16);
70    CHECK(mesh_cuda_limits_check(&l, "p", 32, 0x200, &err));
71    CHECK(!mesh_cuda_limits_check(&small, "p", 33, 0x200, &err));
72    CHECK_S(err, "occupancy: p: warps 33 = 1056 threads per node; the device allows 1024 per block");
73    CHECK(!mesh_cuda_limits_check(&small, "p", 1, 50000, &err));
74    CHECK_S(err, "occupancy: p: 50000 bytes of shared memory per node (the runtime's 512 + shared_bytes); the device allows 49152 per block");
75
76    /* A module's life. */
77    CHECK(mesh_cuda_gpu_open(0, &g, &err));
78    uint8_t *mailbox = calloc(1, 0x1000);
79    uint64_t dev = 0;
80    CHECK(mesh_cuda_host_register(&g, mailbox, 0x1000, &dev, &err));
81    CHECK_U(dev, (uintptr_t)mailbox);
82    mesh_cuda_kernel k;
83    const uint8_t cubin[] = "FAKECUBIN entry=mesh_main regs=40 local=128 maxthreads=512 rt_abi=6";
84    CHECK(!mesh_cuda_load_cubin(&g, cubin, "other", &k, &err));
85    CHECK_S(err, "cuModuleGetFunction(other): CUDA_ERROR_NOT_FOUND");
86    CHECK_I(mesh_fake_cuda_stats_now().modules_live, 0);
87    CHECK(mesh_cuda_load_cubin(&g, cubin, "mesh_main", &k, &err));
88    CHECK(k.threads == 32 && k.smem == 0x200 && k.scratch == 0);
89    uint32_t v;
90    CHECK(mesh_cuda_kernel_regs(&k, &v, &err) && v == 40);
91    CHECK(mesh_cuda_kernel_local_bytes(&k, &v, &err) && v == 128);
92    CHECK(mesh_cuda_kernel_global_u32(&k, "MESH_RT_ABI", &v, &err) && v == 6);
93    CHECK(!mesh_cuda_kernel_set_shape(&k, 1024, 0x200, &err));
94    CHECK_S(err, "regs: 40 registers per thread allow 512 threads per block; the node has 1024");
95    CHECK(mesh_cuda_kernel_set_shape(&k, 256, 0x200 + 0x10000, &err));
96    CHECK_I(mesh_fake_cuda_stats_now().opted_in_smem, 0x10200);
97    CHECK(mesh_cuda_kernel_alloc_scratch(&k, 4096, &err));
98    CHECK(k.scratch != 0);
99    for (int i = 0; i < 4096; i++) CHECK_U(((uint8_t *)(uintptr_t)k.scratch)[i] | (i == 4095 ? 0 : 0), 0);
100    CHECK(mesh_cuda_kernel_max_blocks_per_sm(&k, &v, &err) && v == 16);
101    size_t stack;
102    CHECK(mesh_cuda_set_stack_limit(&g, 4096, &err) && mesh_cuda_stack_limit(&g, &stack, &err) && stack == 4096);
103    CHECK(mesh_cuda_new_stream(&g, &s, &err));
104    uint64_t dmb = dev;
105    uint32_t slot = 3;
106    void *params[] = {&dmb, &slot};
107    CHECK(mesh_cuda_launch_on(&g, &s, &k, 2, params, &err));
108    bool done = true;
109    CHECK(mesh_cuda_query(&g, &s, &done, &err) && !done);
110    CHECK(mesh_fake_cuda_idle(2000));
111    CHECK(mesh_cuda_query(&g, &s, &done, &err) && done);
112    CHECK_U(*(uint32_t *)(mailbox + 0x100), 3 * 1000 + 2 * 10 + 8);
113    mesh_fake_cuda_stats fs = mesh_fake_cuda_stats_now();
114    CHECK(fs.last_grid == 2 && fs.last_block == 256 && fs.last_smem == 0x10200 && fs.last_slot == 3);
115    CHECK(mesh_cuda_unload(&g, &k, &err));
116    fs = mesh_fake_cuda_stats_now();
117    CHECK(fs.modules_live == 0 && fs.frees == 1);
118    mesh_cuda_stream_destroy(&s);
119    /* A failed launch carries the driver's name for the error. */
120    mesh_fake_cuda_fail_next("cuModuleLoadData", 200);
121    CHECK(!mesh_cuda_load_cubin(&g, cubin, "mesh_main", &k, &err));
122    CHECK_S(err, "cuModuleLoadData(cubin): CUDA_ERROR_INVALID_IMAGE");
123    free(err);
124    free(mailbox);
125    mesh_cuda_gpu_free(&g);
126    return mesh_check_done();
127}