==> 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}