/* * XERV CRAYON CUDA ENGINE v3.0 - PRODUCTION GRADE * Architecture: Synchronous CUDA with explicit device initialization * Target Hardware: NVIDIA Tesla T4/V100/A100/H100 * Stability: Maximum compatibility - no async allocators, explicit init */ #include #include #include #include #include // --- DEVICE STATE --- static int32_t *d_base = nullptr; static int32_t *d_check = nullptr; static int32_t *d_values = nullptr; static uint32_t trie_size = 0; static bool engine_loaded = false; static bool cuda_initialized = false; // Forward declarations static void cleanup_cuda_memory(void); // --- SAFE CUDA CALL MACRO --- #define CUDA_SAFE_CALL(call) do { \ cudaError_t err = (call); \ if (err != cudaSuccess) { \ const char* errStr = cudaGetErrorString(err); \ PyErr_Format(PyExc_RuntimeError, "CUDA Error: %s at %s:%d", errStr, __FILE__, __LINE__); \ return NULL; \ } \ } while(0) // --- SIMPLE TOKENIZATION KERNEL --- // Uses per-thread local memory instead of shared memory for maximum stability __global__ void tokenize_kernel( const int32_t* __restrict__ base, const int32_t* __restrict__ check, const int32_t* __restrict__ values, const char* __restrict__ text_pool, const int* __restrict__ offsets, int* out_tokens, int* out_counts, int n_sentences, int max_tokens, uint32_t trie_sz ) { int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx >= n_sentences) return; int start = offsets[idx]; int end = offsets[idx + 1]; int len = end - start; int node = 0; int count = 0; int write_pos = idx * max_tokens; int pos = 0; while (pos < len && count < max_tokens) { int best_token = 1; // UNK token int best_len = 0; int curr = 0; for (int i = pos; i < len && i < pos + 128; ++i) { // Max 128 chars lookahead unsigned char c = (unsigned char)text_pool[start + i]; int next = base[curr] + c; if (next >= 0 && (uint32_t)next < trie_sz && check[next] == curr) { curr = next; int val = values[curr]; if (val != -1) { best_token = val; best_len = (i - pos) + 1; } } else { break; } } out_tokens[write_pos + count] = best_token; count++; pos += (best_len > 0) ? best_len : 1; } out_counts[idx] = count; } // --- INITIALIZE CUDA DEVICE --- static PyObject* init_cuda_device(void) { if (cuda_initialized) { Py_RETURN_TRUE; } int device_count = 0; cudaError_t err = cudaGetDeviceCount(&device_count); if (err != cudaSuccess || device_count == 0) { PyErr_SetString(PyExc_RuntimeError, "No CUDA devices available"); return NULL; } // Set device 0 and force context creation err = cudaSetDevice(0); if (err != cudaSuccess) { PyErr_Format(PyExc_RuntimeError, "Failed to set CUDA device: %s", cudaGetErrorString(err)); return NULL; } // Force context initialization with a dummy allocation void* dummy = nullptr; err = cudaMalloc(&dummy, 1); if (err != cudaSuccess) { PyErr_Format(PyExc_RuntimeError, "Failed to initialize CUDA context: %s", cudaGetErrorString(err)); return NULL; } cudaFree(dummy); cuda_initialized = true; Py_RETURN_TRUE; } // --- GET HARDWARE INFO --- static PyObject* get_hardware_info(PyObject* self, PyObject* args) { int device_count = 0; cudaError_t err = cudaGetDeviceCount(&device_count); if (err != cudaSuccess || device_count == 0) { return PyUnicode_FromString("No CUDA devices found"); } cudaDeviceProp prop; err = cudaGetDeviceProperties(&prop, 0); if (err != cudaSuccess) { return PyUnicode_FromString("Failed to get device properties"); } char info[512]; snprintf(info, sizeof(info), "%s [SM %d.%d, %.1f GB VRAM]", prop.name, prop.major, prop.minor, prop.totalGlobalMem / (1024.0 * 1024.0 * 1024.0)); return PyUnicode_FromString(info); } // --- CLEANUP CUDA MEMORY --- static void cleanup_cuda_memory(void) { if (d_base) { cudaFree(d_base); d_base = nullptr; } if (d_check) { cudaFree(d_check); d_check = nullptr; } if (d_values) { cudaFree(d_values); d_values = nullptr; } engine_loaded = false; trie_size = 0; } // --- LOAD DAT FILE TO GPU --- static PyObject* load_gpu(PyObject* self, PyObject* args) { PyObject* py_bytes; if (!PyArg_ParseTuple(args, "O", &py_bytes)) return NULL; if (!PyBytes_Check(py_bytes)) { PyErr_SetString(PyExc_TypeError, "Expected bytes object"); return NULL; } // Step 1: Initialize CUDA if not done if (!cuda_initialized) { PyObject* init_result = init_cuda_device(); if (init_result == NULL) { return NULL; // Error already set } Py_DECREF(init_result); } // Step 2: Parse DAT file header Py_ssize_t total_len = PyBytes_Size(py_bytes); if (total_len < 12) { PyErr_SetString(PyExc_ValueError, "DAT file too small (< 12 bytes)"); return NULL; } const char* raw = PyBytes_AsString(py_bytes); // Read trie size from offset 8 (standard DAT format) uint32_t sz = 0; memcpy(&sz, raw + 8, sizeof(uint32_t)); // Validate size if (sz == 0) { PyErr_SetString(PyExc_ValueError, "Trie size is 0"); return NULL; } if (sz > (1 << 24)) { // Max 16M entries PyErr_SetString(PyExc_ValueError, "Trie size exceeds maximum (16M entries)"); return NULL; } size_t array_bytes = sz * sizeof(int32_t); size_t required_bytes = 12 + (array_bytes * 3); if ((size_t)total_len < required_bytes) { PyErr_Format(PyExc_ValueError, "DAT file incomplete. Need %zu bytes, got %zd", required_bytes, total_len); return NULL; } // Step 3: Cleanup any previous allocations cleanup_cuda_memory(); // Step 4: Allocate GPU memory (synchronous, most compatible) cudaError_t err; err = cudaMalloc((void**)&d_base, array_bytes); if (err != cudaSuccess) { cleanup_cuda_memory(); PyErr_Format(PyExc_RuntimeError, "cudaMalloc d_base failed: %s", cudaGetErrorString(err)); return NULL; } err = cudaMalloc((void**)&d_check, array_bytes); if (err != cudaSuccess) { cleanup_cuda_memory(); PyErr_Format(PyExc_RuntimeError, "cudaMalloc d_check failed: %s", cudaGetErrorString(err)); return NULL; } err = cudaMalloc((void**)&d_values, array_bytes); if (err != cudaSuccess) { cleanup_cuda_memory(); PyErr_Format(PyExc_RuntimeError, "cudaMalloc d_values failed: %s", cudaGetErrorString(err)); return NULL; } // Step 5: Copy data to GPU (synchronous) const char* data_ptr = raw + 12; err = cudaMemcpy(d_base, data_ptr, array_bytes, cudaMemcpyHostToDevice); if (err != cudaSuccess) { cleanup_cuda_memory(); PyErr_Format(PyExc_RuntimeError, "cudaMemcpy d_base failed: %s", cudaGetErrorString(err)); return NULL; } err = cudaMemcpy(d_check, data_ptr + array_bytes, array_bytes, cudaMemcpyHostToDevice); if (err != cudaSuccess) { cleanup_cuda_memory(); PyErr_Format(PyExc_RuntimeError, "cudaMemcpy d_check failed: %s", cudaGetErrorString(err)); return NULL; } err = cudaMemcpy(d_values, data_ptr + (array_bytes * 2), array_bytes, cudaMemcpyHostToDevice); if (err != cudaSuccess) { cleanup_cuda_memory(); PyErr_Format(PyExc_RuntimeError, "cudaMemcpy d_values failed: %s", cudaGetErrorString(err)); return NULL; } // Step 6: Sync and verify err = cudaDeviceSynchronize(); if (err != cudaSuccess) { cleanup_cuda_memory(); PyErr_Format(PyExc_RuntimeError, "cudaDeviceSynchronize failed: %s", cudaGetErrorString(err)); return NULL; } trie_size = sz; engine_loaded = true; // Return success info (use snprintf because PyUnicode_FromFormat doesn't support %f) char msg[256]; snprintf(msg, sizeof(msg), "Loaded %u entries (%.2f MB) to GPU", sz, (array_bytes * 3) / (1024.0 * 1024.0)); return PyUnicode_FromString(msg); } // --- BATCH TOKENIZATION --- static PyObject* tokenize_batch_gpu(PyObject* self, PyObject* args) { PyObject* list_obj; if (!PyArg_ParseTuple(args, "O", &list_obj)) return NULL; if (!PyList_Check(list_obj)) { PyErr_SetString(PyExc_TypeError, "Expected list of strings"); return NULL; } Py_ssize_t n = PyList_Size(list_obj); if (n == 0) { return PyList_New(0); } // Check engine state if (!engine_loaded || !d_base || !d_check || !d_values) { PyErr_SetString(PyExc_RuntimeError, "CUDA engine not loaded. Call load_gpu() first."); return NULL; } // Build text pool and offsets std::vector text_pool; std::vector offsets; offsets.reserve(n + 1); size_t total_chars = 0; for (Py_ssize_t i = 0; i < n; ++i) { PyObject* item = PyList_GetItem(list_obj, i); if (!PyUnicode_Check(item)) { PyErr_SetString(PyExc_TypeError, "List must contain only strings"); return NULL; } Py_ssize_t len; const char* str = PyUnicode_AsUTF8AndSize(item, &len); if (!str) return NULL; offsets.push_back((int)total_chars); text_pool.insert(text_pool.end(), str, str + len); total_chars += len; } offsets.push_back((int)total_chars); // Calculate max tokens per sentence size_t avg_len = total_chars / n; int max_tok = (int)(avg_len * 2 + 64); if (max_tok > 4096) max_tok = 4096; if (max_tok < 64) max_tok = 64; // Allocate GPU buffers char* d_text = nullptr; int* d_offsets = nullptr; int* d_out = nullptr; int* d_counts = nullptr; cudaError_t err; err = cudaMalloc((void**)&d_text, total_chars); if (err != cudaSuccess) { PyErr_Format(PyExc_RuntimeError, "cudaMalloc d_text failed: %s", cudaGetErrorString(err)); return NULL; } err = cudaMalloc((void**)&d_offsets, offsets.size() * sizeof(int)); if (err != cudaSuccess) { cudaFree(d_text); PyErr_Format(PyExc_RuntimeError, "cudaMalloc d_offsets failed: %s", cudaGetErrorString(err)); return NULL; } err = cudaMalloc((void**)&d_out, n * max_tok * sizeof(int)); if (err != cudaSuccess) { cudaFree(d_text); cudaFree(d_offsets); PyErr_Format(PyExc_RuntimeError, "cudaMalloc d_out failed: %s", cudaGetErrorString(err)); return NULL; } err = cudaMalloc((void**)&d_counts, n * sizeof(int)); if (err != cudaSuccess) { cudaFree(d_text); cudaFree(d_offsets); cudaFree(d_out); PyErr_Format(PyExc_RuntimeError, "cudaMalloc d_counts failed: %s", cudaGetErrorString(err)); return NULL; } // Zero output buffers cudaMemset(d_out, 0, n * max_tok * sizeof(int)); cudaMemset(d_counts, 0, n * sizeof(int)); // Copy input data cudaMemcpy(d_text, text_pool.data(), total_chars, cudaMemcpyHostToDevice); cudaMemcpy(d_offsets, offsets.data(), offsets.size() * sizeof(int), cudaMemcpyHostToDevice); // Launch kernel int threads = 128; // Conservative for stability int blocks = ((int)n + threads - 1) / threads; tokenize_kernel<<>>( d_base, d_check, d_values, d_text, d_offsets, d_out, d_counts, (int)n, max_tok, trie_size ); // Check for kernel errors err = cudaGetLastError(); if (err != cudaSuccess) { cudaFree(d_text); cudaFree(d_offsets); cudaFree(d_out); cudaFree(d_counts); PyErr_Format(PyExc_RuntimeError, "Kernel launch failed: %s", cudaGetErrorString(err)); return NULL; } // Synchronize err = cudaDeviceSynchronize(); if (err != cudaSuccess) { cudaFree(d_text); cudaFree(d_offsets); cudaFree(d_out); cudaFree(d_counts); PyErr_Format(PyExc_RuntimeError, "Kernel execution failed: %s", cudaGetErrorString(err)); return NULL; } // Copy results back std::vector h_out(n * max_tok); std::vector h_counts(n); cudaMemcpy(h_out.data(), d_out, n * max_tok * sizeof(int), cudaMemcpyDeviceToHost); cudaMemcpy(h_counts.data(), d_counts, n * sizeof(int), cudaMemcpyDeviceToHost); // Cleanup GPU buffers cudaFree(d_text); cudaFree(d_offsets); cudaFree(d_out); cudaFree(d_counts); // Build Python result PyObject* result = PyList_New(n); for (Py_ssize_t i = 0; i < n; ++i) { int count = h_counts[i]; PyObject* tokens = PyList_New(count); for (int j = 0; j < count; ++j) { PyList_SetItem(tokens, j, PyLong_FromLong(h_out[i * max_tok + j])); } PyList_SetItem(result, i, tokens); } // Return tuple (results, metadata) PyObject* meta = PyDict_New(); PyDict_SetItemString(meta, "sentences", PyLong_FromSsize_t(n)); PyDict_SetItemString(meta, "max_tokens_per_sentence", PyLong_FromLong(max_tok)); PyObject* full_result = PyTuple_New(2); PyTuple_SetItem(full_result, 0, result); PyTuple_SetItem(full_result, 1, meta); return full_result; } // --- MODULE CLEANUP --- static void module_cleanup(void* module) { cleanup_cuda_memory(); } // --- MODULE DEFINITION --- static PyMethodDef CudaMethods[] = { {"load_gpu", load_gpu, METH_VARARGS, "Load DAT vocabulary to GPU memory"}, {"tokenize_batch_gpu", tokenize_batch_gpu, METH_VARARGS, "Tokenize batch of strings on GPU"}, {"get_hardware_info", get_hardware_info, METH_VARARGS, "Get CUDA device information"}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef cuda_module = { PyModuleDef_HEAD_INIT, "crayon_cuda", "XERV Crayon CUDA Backend v3.0 - Production Grade", -1, CudaMethods, NULL, NULL, NULL, module_cleanup }; PyMODINIT_FUNC PyInit_crayon_cuda(void) { return PyModule_Create(&cuda_module); }