/* backend_loader.c — Windows runtime loader for coli_cuda.dll. * * Why this exists: the engine is built with MinGW-w64 (gcc), but CUDA kernels * must be compiled with MSVC + nvcc. We cannot link a CUDA .o into a gcc binary * reliably across the MSVC/MinGW ABI, and nvcc requires cl.exe as its host * compiler. The clean cross-toolchain split is: build the CUDA backend into a * standalone coli_cuda.dll with nvcc+MSVC, then load it here at runtime via * LoadLibrary/GetProcAddress. The host (glm.exe) never links cudart directly. * * On Linux this file is not compiled (the Makefile links backend_cuda.o * directly). On Windows, when COLI_CUDA is defined, glm.c calls the * coli_cuda_* wrappers below, which forward through function pointers resolved * from the DLL at first use. If the DLL is absent, every call safely returns * the "not initialized" sentinel (0 / no-op) and the engine falls back to CPU. * * ABI note: ColiCudaTensor* is opaque to the host (it stores the pointer, * never dereferences it), so the MSVC-allocated struct is safe to pass across * the boundary as an opaque handle. All scalar types (int, size_t, pointers) * agree between MSVC and MinGW-w64 on x86-64. */ #ifdef _WIN32 #include #include #include #include "backend_cuda.h" /* Function-pointer typedefs matching each exported symbol. */ typedef int (*fn_init)(const int *devices, int count); typedef void (*fn_shutdown)(void); typedef int (*fn_device_count)(void); typedef int (*fn_device_at)(int index); typedef int (*fn_mem_info)(int device, size_t *free_bytes, size_t *total_bytes); typedef void (*fn_stats)(int device, size_t *tensor_count, size_t *tensor_bytes); typedef void (*fn_group_stats)(uint64_t *calls, uint64_t *experts, uint64_t *rows, double *h2d_ms, double *kernel_ms, double *d2h_ms); typedef int (*fn_expert_mlp)(ColiCudaTensor *gate, ColiCudaTensor *up, ColiCudaTensor *down, float *y, const float *x, int S); typedef int (*fn_expert_group)(ColiCudaTensor *const *gates, ColiCudaTensor *const *ups, ColiCudaTensor *const *downs, const int *rows, int count, float *y, const float *x); typedef int (*fn_attention_absorb)(ColiCudaTensor *kv_b, float *ctx, const float *q, const float *latent, const float *rope, int H, int Q, int R, int V, int K, int T, float attention_scale); typedef int (*fn_tensor_upload)(ColiCudaTensor **tensor, const void *weights, const float *scales, int fmt, int I, int O, int device); typedef int (*fn_matmul)(ColiCudaTensor **tensor, float *y, const float *x, const void *weights, const float *scales, int fmt, int S, int I, int O, int device); typedef void (*fn_tensor_free)(ColiCudaTensor *tensor); typedef size_t (*fn_tensor_bytes)(const ColiCudaTensor *tensor); typedef int (*fn_tensor_device)(const ColiCudaTensor *tensor); /* Resolved pointers, plus a flag so we attempt the load at most once. */ static struct { int loaded; /* 1 = load attempted (success or fail), 0 = not yet */ int available; /* 1 = DLL loaded and all symbols resolved */ HMODULE dll; fn_init init; fn_shutdown shutdown; fn_device_count device_count; fn_device_at device_at; fn_mem_info mem_info; fn_stats stats; fn_group_stats group_stats; fn_expert_mlp expert_mlp; fn_expert_group expert_group; fn_attention_absorb attention_absorb; fn_tensor_upload tensor_upload; fn_matmul matmul; fn_tensor_free tensor_free; fn_tensor_bytes tensor_bytes; fn_tensor_device tensor_device; } g_cuda; /* Resolve the DLL and all 11 symbols. Returns 1 on success, 0 otherwise. * Idempotent: the first call (success or fail) sticks; later calls are no-ops * that return the cached result. The engine treats a 0 return as "CUDA * unavailable" and falls back to the CPU path without aborting. */ static int coli_cuda_load(void){ if(g_cuda.loaded) return g_cuda.available; g_cuda.loaded = 1; /* Search the model directory first (so a DLL shipped next to the model * wins), then the engine directory, then the default DLL search path. */ g_cuda.dll = LoadLibraryA("coli_cuda.dll"); if(!g_cuda.dll){ fprintf(stderr, "[CUDA] coli_cuda.dll not found; GPU tier disabled " "(CPU path remains active).\n"); return 0; } #define RESOLVE(name, type) \ /* GetProcAddress returns FARPROC (void(*)(void)); casting it to a \ * specific function-pointer type is the standard LoadLibrary idiom. \ * -Wcast-function-type flags it but it is safe: the DLL exported \ * the symbol with extern "C" and the exact signature we expect. */ \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wcast-function-type\"") \ g_cuda.name = (type)GetProcAddress(g_cuda.dll, "coli_cuda_" #name); \ _Pragma("GCC diagnostic pop") \ if(!g_cuda.name){ \ fprintf(stderr, "[CUDA] coli_cuda.dll missing symbol coli_cuda_" #name "\n"); \ FreeLibrary(g_cuda.dll); g_cuda.dll=NULL; return 0; } RESOLVE(init, fn_init) RESOLVE(shutdown, fn_shutdown) RESOLVE(device_count, fn_device_count) RESOLVE(device_at, fn_device_at) RESOLVE(mem_info, fn_mem_info) RESOLVE(stats, fn_stats) RESOLVE(group_stats, fn_group_stats) RESOLVE(expert_mlp, fn_expert_mlp) RESOLVE(expert_group, fn_expert_group) RESOLVE(attention_absorb, fn_attention_absorb) RESOLVE(tensor_upload, fn_tensor_upload) RESOLVE(matmul, fn_matmul) RESOLVE(tensor_free, fn_tensor_free) RESOLVE(tensor_bytes, fn_tensor_bytes) RESOLVE(tensor_device, fn_tensor_device) #undef RESOLVE g_cuda.available = 1; return 1; } /* ---- Public wrappers: match backend_cuda.h signatures exactly. * Each forwards to the resolved pointer; if the DLL never loaded, return the * "not initialized" result the engine already handles (init returns 0, matmul * returns 0 so the caller marks the tensor cuda_failed and uses CPU). ---- */ int coli_cuda_init(const int *devices, int count){ if(!coli_cuda_load()) return 0; return g_cuda.init(devices, count); } void coli_cuda_shutdown(void){ if(g_cuda.available && g_cuda.shutdown) g_cuda.shutdown(); } int coli_cuda_device_count(void){ if(!g_cuda.available) return 0; return g_cuda.device_count(); } int coli_cuda_device_at(int index){ if(!g_cuda.available) return -1; return g_cuda.device_at(index); } int coli_cuda_mem_info(int device, size_t *free_bytes, size_t *total_bytes){ if(!g_cuda.available){ if(free_bytes)*free_bytes=0; if(total_bytes)*total_bytes=0; return 0; } return g_cuda.mem_info(device, free_bytes, total_bytes); } void coli_cuda_stats(int device, size_t *tensor_count, size_t *tensor_bytes){ if(!g_cuda.available){ if(tensor_count)*tensor_count=0; if(tensor_bytes)*tensor_bytes=0; return; } g_cuda.stats(device, tensor_count, tensor_bytes); } void coli_cuda_group_stats(uint64_t *calls, uint64_t *experts, uint64_t *rows, double *h2d_ms, double *kernel_ms, double *d2h_ms){ if(!g_cuda.available){ if(calls)*calls=0; if(experts)*experts=0; if(rows)*rows=0; if(h2d_ms)*h2d_ms=0; if(kernel_ms)*kernel_ms=0; if(d2h_ms)*d2h_ms=0; return; } g_cuda.group_stats(calls, experts, rows, h2d_ms, kernel_ms, d2h_ms); } int coli_cuda_expert_mlp(ColiCudaTensor *gate, ColiCudaTensor *up, ColiCudaTensor *down, float *y, const float *x, int S){ if(!g_cuda.available) return 0; return g_cuda.expert_mlp(gate, up, down, y, x, S); } int coli_cuda_expert_group(ColiCudaTensor *const *gates, ColiCudaTensor *const *ups, ColiCudaTensor *const *downs, const int *rows, int count, float *y, const float *x){ if(!g_cuda.available) return 0; return g_cuda.expert_group(gates, ups, downs, rows, count, y, x); } int coli_cuda_attention_absorb(ColiCudaTensor *kv_b, float *ctx, const float *q, const float *latent, const float *rope, int H, int Q, int R, int V, int K, int T, float attention_scale){ if(!g_cuda.available) return 0; return g_cuda.attention_absorb(kv_b, ctx, q, latent, rope, H, Q, R, V, K, T, attention_scale); } int coli_cuda_tensor_upload(ColiCudaTensor **tensor, const void *weights, const float *scales, int fmt, int I, int O, int device){ if(!g_cuda.available) return 0; return g_cuda.tensor_upload(tensor, weights, scales, fmt, I, O, device); } int coli_cuda_matmul(ColiCudaTensor **tensor, float *y, const float *x, const void *weights, const float *scales, int fmt, int S, int I, int O, int device){ if(!g_cuda.available) return 0; return g_cuda.matmul(tensor, y, x, weights, scales, fmt, S, I, O, device); } void coli_cuda_tensor_free(ColiCudaTensor *tensor){ if(g_cuda.available && g_cuda.tensor_free) g_cuda.tensor_free(tensor); } size_t coli_cuda_tensor_bytes(const ColiCudaTensor *tensor){ if(!g_cuda.available) return 0; return g_cuda.tensor_bytes(tensor); } int coli_cuda_tensor_device(const ColiCudaTensor *tensor){ if(!g_cuda.available) return -1; return g_cuda.tensor_device(tensor); } #endif /* _WIN32 */