skip to content
==> src/fake.c <==
1/* The simulated device. */
2#include "mesh/cuda_fake.h"
3#include "mesh/text.h"
4
5#include <pthread.h>
6#include <stdatomic.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <time.h>
11
12static mesh_fake_cuda_cfg cfg;
13static mesh_fake_cuda_stats st;
14static pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
15static char fail_call[64];
16static CUresult fail_code;
17static size_t stack_limit = 1024;
18
19typedef struct CUmod_st {
20    char entry[64];
21    int regs, local, maxthreads, shared;
22    uint32_t rt_abi;
23    struct CUfunc_st *func;
24} fmod;
25
26struct CUfunc_st {
27    fmod *mod;
28    int max_dyn_smem;
29};
30
31struct CUstream_st {
32    atomic_int busy;
33};
34
35static int dummy_ctx, dummy_green, dummy_desc;
36
37static CUresult failing(const char *call) {
38    pthread_mutex_lock(&mu);
39    CUresult r = 0;
40    if (fail_call[0] && strcmp(fail_call, call) == 0) {
41        r = fail_code;
42        fail_call[0] = 0;
43    }
44    pthread_mutex_unlock(&mu);
45    return r;
46}
47#define FAILS(name)                       \
48    do {                                  \
49        CUresult r_ = failing(name);      \
50        if (r_) return r_;                \
51    } while (0)
52
53static CUresult f_init(unsigned f) {
54    (void)f;
55    FAILS("cuInit");
56    st.inits++;
57    return 0;
58}
59static CUresult f_err_name(CUresult r, const char **name) {
60    *name = mesh_cu_result_name(r);
61    return *name ? 0 : 1;
62}
63static CUresult f_loading(int *mode) {
64    *mode = cfg.loading_mode;
65    return 0;
66}
67static CUresult f_dev_get(CUdevice *d, int o) {
68    FAILS("cuDeviceGet");
69    *d = o;
70    return 0;
71}
72static CUresult f_dev_name(char *n, int len, CUdevice d) {
73    (void)d;
74    snprintf(n, (size_t)len, "%s", cfg.name);
75    return 0;
76}
77static CUresult f_dev_attr(int *v, int a, CUdevice d) {
78    (void)d;
79    switch (a) {
80    case CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK: *v = cfg.max_threads; break;
81    case CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK: *v = cfg.regs_per_block; break;
82    case CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT: *v = cfg.sms; break;
83    case CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR: *v = cfg.cc_major; break;
84    case CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR: *v = cfg.cc_minor; break;
85    case CU_DEVICE_ATTRIBUTE_HOST_NATIVE_ATOMIC_SUPPORTED: *v = cfg.host_atomics; break;
86    case CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK_OPTIN: *v = cfg.max_shared_optin; break;
87    case CU_DEVICE_ATTRIBUTE_MAX_BLOCKS_PER_MULTIPROCESSOR: *v = cfg.blocks_per_sm; break;
88    default: return 1; /* CUDA_ERROR_INVALID_VALUE */
89    }
90    return 0;
91}
92static CUresult f_primary(CUcontext *c, CUdevice d) {
93    (void)d;
94    st.primary_ctx++;
95    *c = (CUcontext)(void *)&dummy_ctx;
96    return 0;
97}
98static CUresult f_set_current(CUcontext c) {
99    (void)c;
100    return 0;
101}
102static CUresult f_get_limit(size_t *v, int l) {
103    if (l != CU_LIMIT_STACK_SIZE) return 1;
104    *v = stack_limit;
105    return 0;
106}
107static CUresult f_set_limit(int l, size_t v) {
108    if (l != CU_LIMIT_STACK_SIZE) return 1;
109    stack_limit = v;
110    st.stack_limit = v;
111    return 0;
112}
113static CUresult f_dev_resource(CUdevice d, CUdevResource *r, int type) {
114    (void)d;
115    if (type != CU_DEV_RESOURCE_TYPE_SM) return 1;
116    r->type = type;
117    r->u.sm.smCount = (unsigned)cfg.sms;
118    return 0;
119}
120static CUresult f_split(CUdevResource *res, unsigned *n, const CUdevResource *in, CUdevResource *rem, unsigned flags, unsigned min) {
121    (void)flags;
122    if (min > in->u.sm.smCount || min % 2) {
123        *n = 0;
124        return 0;
125    }
126    res->type = CU_DEV_RESOURCE_TYPE_SM;
127    res->u.sm.smCount = min;
128    rem->type = CU_DEV_RESOURCE_TYPE_SM;
129    rem->u.sm.smCount = in->u.sm.smCount - min;
130    *n = 1;
131    st.green_sms = (int)min;
132    return 0;
133}
134static CUresult f_gen_desc(CUdevResourceDesc *d, CUdevResource *r, unsigned n) {
135    (void)r, (void)n;
136    *d = (CUdevResourceDesc)(void *)&dummy_desc;
137    return 0;
138}
139static CUresult f_green_create(CUgreenCtx *g, CUdevResourceDesc d, CUdevice dev, unsigned f) {
140    (void)d, (void)dev, (void)f;
141    st.green_ctx++;
142    *g = (CUgreenCtx)(void *)&dummy_green;
143    return 0;
144}
145static CUresult f_from_green(CUcontext *c, CUgreenCtx g) {
146    (void)g;
147    *c = (CUcontext)(void *)&dummy_ctx;
148    return 0;
149}
150static CUresult new_stream(CUstream *s) {
151    FAILS("cuStreamCreate");
152    struct CUstream_st *x = mesh_alloc(sizeof *x);
153    atomic_init(&x->busy, 0);
154    *s = x;
155    pthread_mutex_lock(&mu);
156    st.streams_created++;
157    pthread_mutex_unlock(&mu);
158    return 0;
159}
160static CUresult f_green_stream(CUstream *s, CUgreenCtx g, unsigned f, int p) {
161    (void)g, (void)f, (void)p;
162    return new_stream(s);
163}
164static CUresult f_stream_create(CUstream *s, unsigned f) {
165    (void)f;
166    return new_stream(s);
167}
168static CUresult f_stream_destroy(CUstream s) {
169    pthread_mutex_lock(&mu);
170    st.streams_destroyed++;
171    pthread_mutex_unlock(&mu);
172    /* A stream with a running kernel is leaked rather than freed under it. */
173    if (atomic_load(&s->busy) == 0) free(s);
174    return 0;
175}
176static CUresult f_stream_query(CUstream s) {
177    FAILS("cuStreamQuery");
178    return atomic_load(&s->busy) ? CUDA_ERROR_NOT_READY : 0;
179}
180static CUresult f_stream_sync(CUstream s) {
181    while (atomic_load(&s->busy)) {
182        struct timespec t = {0, 100000};
183        nanosleep(&t, NULL);
184    }
185    return 0;
186}
187static CUresult f_host_register(void *p, size_t n, unsigned flags) {
188    (void)p, (void)n;
189    FAILS("cuMemHostRegister");
190    if (!(flags & CU_MEMHOSTREGISTER_DEVICEMAP)) return 1;
191    st.host_registered++;
192    return 0;
193}
194static CUresult f_host_dev_ptr(CUdeviceptr *d, void *p, unsigned f) {
195    (void)f;
196    *d = (CUdeviceptr)(uintptr_t)p;
197    return 0;
198}
199static int kv(const char *s, const char *key, int dflt) {
200    const char *p = strstr(s, key);
201    return p ? atoi(p + strlen(key)) : dflt;
202}
203static CUresult f_load_data(CUmodule *m, const void *image) {
204    FAILS("cuModuleLoadData");
205    const char *s = image;
206    if (strncmp(s, "FAKECUBIN", 9) != 0) return 200; /* CUDA_ERROR_INVALID_IMAGE */
207    fmod *x = mesh_alloc(sizeof *x);
208    const char *e = strstr(s, "entry=");
209    if (e) sscanf(e + 6, "%63s", x->entry);
210    else strcpy(x->entry, "mesh_main");
211    x->regs = kv(s, "regs=", 32);
212    x->local = kv(s, "local=", 0);
213    x->maxthreads = kv(s, "maxthreads=", 1024);
214    x->shared = kv(s, "shared=", 0);
215    x->rt_abi = (uint32_t)kv(s, "rt_abi=", 6);
216    *m = x;
217    pthread_mutex_lock(&mu);
218    st.modules_loaded++;
219    st.modules_live++;
220    pthread_mutex_unlock(&mu);
221    return 0;
222}
223static CUresult f_get_function(CUfunction *f, CUmodule m, const char *name) {
224    if (strcmp(m->entry, name) != 0) return 500; /* CUDA_ERROR_NOT_FOUND */
225    if (!m->func) {
226        m->func = mesh_alloc(sizeof *m->func);
227        m->func->mod = m;
228    }
229    *f = m->func;
230    return 0;
231}
232static CUresult f_unload(CUmodule m) {
233    pthread_mutex_lock(&mu);
234    st.modules_unloaded++;
235    st.modules_live--;
236    pthread_mutex_unlock(&mu);
237    free(m->func);
238    free(m);
239    return 0;
240}
241static CUresult f_get_global(CUdeviceptr *d, size_t *bytes, CUmodule m, const char *name) {
242    if (strcmp(name, "MESH_RT_ABI") != 0) return 500;
243    *d = (CUdeviceptr)(uintptr_t)&m->rt_abi;
244    *bytes = 4;
245    return 0;
246}
247static CUresult f_func_attr(int *v, int a, CUfunction f) {
248    switch (a) {
249    case CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK: *v = f->mod->maxthreads; break;
250    case CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES: *v = f->mod->shared; break;
251    case CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES: *v = f->mod->local; break;
252    case CU_FUNC_ATTRIBUTE_NUM_REGS: *v = f->mod->regs; break;
253    default: return 1;
254    }
255    return 0;
256}
257static CUresult f_func_set_attr(CUfunction f, int a, int v) {
258    if (a != CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES) return 1;
259    if (v > cfg.max_shared_optin) return 1;
260    f->max_dyn_smem = v;
261    st.opted_in_smem = v;
262    return 0;
263}
264static CUresult f_occupancy(int *blocks, CUfunction f, int threads, size_t smem) {
265    (void)f, (void)threads, (void)smem;
266    *blocks = cfg.blocks_per_sm_occupancy ? cfg.blocks_per_sm_occupancy : 16;
267    return 0;
268}
269static CUresult f_alloc(CUdeviceptr *d, size_t n) {
270    FAILS("cuMemAlloc");
271    void *p = malloc(n);
272    if (!p) return 2;
273    memset(p, 0xa5, n); /* garbage: the memset must clear it */
274    *d = (CUdeviceptr)(uintptr_t)p;
275    st.allocs++;
276    return 0;
277}
278static CUresult f_free(CUdeviceptr d) {
279    free((void *)(uintptr_t)d);
280    st.frees++;
281    return 0;
282}
283static CUresult f_memset(CUdeviceptr d, unsigned char v, size_t n, CUstream s) {
284    (void)s;
285    memset((void *)(uintptr_t)d, v, n);
286    st.memsets++;
287    return 0;
288}
289static CUresult f_memcpy_dtoh(void *dst, CUdeviceptr src, size_t n) {
290    memcpy(dst, (const void *)(uintptr_t)src, n);
291    return 0;
292}
293
294typedef struct {
295    CUstream s;
296    char entry[64];
297    uint64_t mailbox;
298    uint32_t slot, nodes, threads;
299} launch;
300
301static void *run_launch(void *arg) {
302    launch *l = arg;
303    if (cfg.run) cfg.run(l->entry, l->mailbox, l->slot, l->nodes, l->threads, cfg.user);
304    pthread_mutex_lock(&mu);
305    st.running--;
306    pthread_mutex_unlock(&mu);
307    atomic_fetch_sub(&l->s->busy, 1);
308    free(l);
309    return NULL;
310}
311
312static CUresult f_launch(CUfunction f, unsigned gx, unsigned gy, unsigned gz, unsigned bx, unsigned by, unsigned bz, unsigned smem,
313                         CUstream s, void **params, void **extra) {
314    (void)gy, (void)gz, (void)by, (void)bz, (void)extra;
315    FAILS("cuLaunchKernel");
316    if ((int)bx > f->mod->maxthreads || smem + (unsigned)f->mod->shared > (unsigned)(f->max_dyn_smem > 48 * 1024 ? f->max_dyn_smem : 48 * 1024))
317        return 1; /* CUDA_ERROR_INVALID_VALUE: the shape does not fit */
318    launch *l = mesh_alloc(sizeof *l);
319    l->s = s;
320    strcpy(l->entry, f->mod->entry);
321    memcpy(&l->mailbox, params[0], 8);
322    memcpy(&l->slot, params[1], 4);
323    l->nodes = gx;
324    l->threads = bx;
325    pthread_mutex_lock(&mu);
326    st.launches++;
327    st.running++;
328    st.last_grid = gx;
329    st.last_block = bx;
330    st.last_smem = smem;
331    st.last_mailbox = l->mailbox;
332    st.last_slot = l->slot;
333    pthread_mutex_unlock(&mu);
334    atomic_fetch_add(&s->busy, 1);
335    pthread_t t;
336    pthread_attr_t at;
337    pthread_attr_init(&at);
338    pthread_attr_setdetachstate(&at, PTHREAD_CREATE_DETACHED);
339    pthread_create(&t, &at, run_launch, l);
340    pthread_attr_destroy(&at);
341    return 0;
342}
343
344static const mesh_cuda_fns fns = {
345    f_init, f_err_name, f_loading, f_dev_get, f_dev_name, f_dev_attr, f_primary, f_set_current, f_get_limit, f_set_limit,
346    f_dev_resource, f_split, f_gen_desc, f_green_create, f_from_green, f_green_stream, f_stream_create, f_stream_destroy,
347    f_stream_query, f_stream_sync, f_host_register, f_host_dev_ptr, f_load_data, f_get_function, f_unload, f_get_global,
348    f_func_attr, f_func_set_attr, f_occupancy, f_alloc, f_free, f_memset, f_memcpy_dtoh, f_launch,
349};
350
351void mesh_fake_cuda_install(const mesh_fake_cuda_cfg *c) {
352    mesh_fake_cuda_cfg d = {"Fake GB10", 12, 1, 48, 24, 1, 1024, 65536, 101376, CU_MODULE_EAGER_LOADING, 0, NULL, NULL};
353    if (c) {
354        if (c->name) d.name = c->name;
355        if (c->cc_major) d.cc_major = c->cc_major, d.cc_minor = c->cc_minor;
356        if (c->sms) d.sms = c->sms;
357        if (c->blocks_per_sm) d.blocks_per_sm = c->blocks_per_sm;
358        if (c->max_threads) d.max_threads = c->max_threads;
359        if (c->regs_per_block) d.regs_per_block = c->regs_per_block;
360        if (c->max_shared_optin) d.max_shared_optin = c->max_shared_optin;
361        if (c->loading_mode) d.loading_mode = c->loading_mode;
362        d.host_atomics = c->host_atomics;
363        d.blocks_per_sm_occupancy = c->blocks_per_sm_occupancy;
364        d.run = c->run;
365        d.user = c->user;
366    }
367    cfg = d;
368    memset(&st, 0, sizeof st);
369    mesh_cuda_set(&fns);
370}
371
372mesh_fake_cuda_stats mesh_fake_cuda_stats_now(void) {
373    pthread_mutex_lock(&mu);
374    mesh_fake_cuda_stats s = st;
375    pthread_mutex_unlock(&mu);
376    return s;
377}
378
379void mesh_fake_cuda_fail_next(const char *call, CUresult code) {
380    pthread_mutex_lock(&mu);
381    snprintf(fail_call, sizeof fail_call, "%s", call);
382    fail_code = code;
383    pthread_mutex_unlock(&mu);
384}
385
386bool mesh_fake_cuda_idle(unsigned ms) {
387    for (unsigned i = 0; i < ms * 10; i++) {
388        if (mesh_fake_cuda_stats_now().running == 0) return true;
389        struct timespec t = {0, 100000};
390        nanosleep(&t, NULL);
391    }
392    return mesh_fake_cuda_stats_now().running == 0;
393}