#include "backend_cuda.h" #include #include #include #include #include #include struct RaggedKVEntry { const void *key; const float *host_l,*host_r; float *latent,*rope; int length,capacity,K,R; }; struct ColiCudaTensor { void *weights; float *scales; size_t weight_bytes; int fmt, I, O, device; int gs; /* quant group size; 0 = per-row scales (#334) */ size_t scale_count; /* floats in `scales`: O per-row, O*ng grouped */ int tracked; RaggedKVEntry ragged[512]; int ragged_count; }; typedef struct { int device; int compute_major,compute_minor; float *x, *y, *gate, *up; size_t x_cap, y_cap, gate_cap, up_cap; uint8_t *qx; float *qscale; size_t qx_cap, qscale_cap; float *host_x,*host_y,*host_kv; size_t host_x_cap,host_y_cap,host_kv_cap; float *aq,*al,*ar,*ac; size_t aq_cap,al_cap,ar_cap,ac_cap; float *pipe_buf[27]; size_t pipe_cap[27]; /* scratch persistenti del resident pipeline */ cudaStream_t stream; void *group_desc; size_t group_desc_cap; size_t tensor_count, tensor_bytes; int group_pending; size_t group_pending_bytes; /* async expert-group in flight (Inc.4) */ } DeviceContext; typedef struct { const void *g,*u,*d; const float *gs,*us,*ds; int gf,uf,df,rows,offset; int ggs,ugs,dgs; /* per-tensor quant group size; 0 = per-row scales (#334 fmt=4) */ } GroupDesc; static DeviceContext g_ctx[COLI_CUDA_MAX_DEVICES]; static int g_nctx; static uint64_t g_group_calls,g_group_experts,g_group_rows; static double g_group_h2d_ms,g_group_kernel_ms,g_group_d2h_ms; static std::mutex g_group_stats_mu; static int cuda_ok(cudaError_t err, const char *what) { if (err == cudaSuccess) return 1; std::fprintf(stderr, "[CUDA] %s: %s\n", what, cudaGetErrorString(err)); (void)cudaGetLastError(); /* consume the sticky error: a failed call must not poison the next launch's error check */ return 0; } static DeviceContext *find_ctx(int device) { for (int i = 0; i < g_nctx; i++) if (g_ctx[i].device == device) return &g_ctx[i]; return nullptr; } /* cudaSetDevice on every call doubles expert-matmul time on 2 GPUs when the * serial expert loop alternates devices (measured on RTX 5090 + 4090: 14.3s * -> 25.4s per 32 tokens). The current device is per-thread in the CUDA * runtime, so a thread-local cache skips the redundant switches. */ static thread_local int g_current_device = -1; static int select_ctx(DeviceContext *ctx) { if (!ctx) return 0; if (g_current_device == ctx->device) return 1; if (!cuda_ok(cudaSetDevice(ctx->device), "select device")) return 0; g_current_device = ctx->device; return 1; } __host__ __device__ static size_t row_bytes(int fmt, int I) { if (fmt == 0) return (size_t)I * sizeof(float); if (fmt == 1) return (size_t)I; if (fmt == 2) return (size_t)(I + 1) / 2; if (fmt == 3) return (size_t)(I + 3) / 4; if (fmt == 4) return (size_t)(I + 1) / 2; /* grouped int4: nibbles like fmt 2 */ return 0; } __device__ static float weight_at(const void *weights, int fmt, size_t row, int i) { const uint8_t *base = static_cast(weights) + row; if (fmt == 0) return reinterpret_cast(base)[i]; if (fmt == 1) return static_cast(reinterpret_cast(base)[i]); const uint8_t *q = base; if (fmt == 2) { uint8_t v = q[i >> 1]; int n=(i&1)?(v>>4):(v&15); return static_cast(n&8?n-16:n); } uint8_t v = q[i >> 2]; return static_cast(((v >> ((i & 3) * 2)) & 3) - 2); } __global__ static void offset_to_signed_s4(uint8_t *q,size_t n){ size_t i=(size_t)blockIdx.x*blockDim.x+threadIdx.x;if(i> 1; n; n >>= 1) { if (threadIdx.x < n) partial[threadIdx.x] += partial[threadIdx.x + n]; __syncthreads(); } if (!threadIdx.x) y[(size_t)s * O + o] = partial[0] * (fmt ? scales[o] : 1.0f); } __global__ static void silu_mul(float *gate, const float *up, size_t n) { size_t i = (size_t)blockIdx.x * blockDim.x + threadIdx.x; if (i < n) { float v = gate[i]; gate[i] = (v / (1.0f + expf(-v))) * up[i]; } } /* Four warps share one A tile and compute 16x64 outputs. This matters for * prefill: the first prototype reloaded/converter A once per 16 output cols. */ __global__ static void w4a16_matmul(float *y,const float *x,const uint8_t *w, const float *scale,int M,int K,int N){ #if __CUDA_ARCH__ >= 700 using namespace nvcuda;int warp=threadIdx.x>>5,lane=threadIdx.x&31; int m0=blockIdx.y*16,n0=blockIdx.x*64+warp*16; __shared__ __half ah[256],bh[4][256]; wmma::fragment acc;wmma::fill_fragment(acc,0.f); size_t rb=(size_t)(K+1)/2; for(int k0=0;k0>1)];int a=(gk&1)?q>>4:q&15; v=(float)(a&8?a-16:a)*scale[gn];} bh[warp][z]=__float2half(v); /* [Ntile,Ktile] == B col-major */ } __syncthreads(); wmma::fragment af; wmma::fragment bf; wmma::load_matrix_sync(af,ah,16);wmma::load_matrix_sync(bf,bh[warp],16); wmma::mma_sync(acc,af,bf,acc);__syncthreads(); } __shared__ float out[4][256];wmma::store_matrix_sync(out[warp],acc,16,wmma::mem_row_major);__syncwarp(); for(int z=lane;z<256;z+=32){int m=z/16,n=z%16; if(m0+mFP16 conversion of A. */ __global__ static void w4a16_gate_up(float *gate,float *up,const float *x, const uint8_t *gw,const uint8_t *uw,const float *gs,const float *us, int M,int K,int N){ #if __CUDA_ARCH__ >= 700 using namespace nvcuda;int warp=threadIdx.x>>5,lane=threadIdx.x&31,which=warp&1,tile=warp>>1; int m0=blockIdx.y*16,n0=blockIdx.x*64+tile*16;const uint8_t *w=which?uw:gw; const float *scale=which?us:gs;float *y=which?up:gate;size_t rb=(size_t)(K+1)/2; __shared__ __half ah[256],bh[8][256]; wmma::fragment acc;wmma::fill_fragment(acc,0.f); for(int k0=0;k0>1)];int a=(gk&1)?q>>4:q&15; v=(float)(a&8?a-16:a)*scale[gn];}bh[warp][z]=__float2half(v);} __syncthreads(); wmma::fragment af; wmma::fragment bf; wmma::load_matrix_sync(af,ah,16);wmma::load_matrix_sync(bf,bh[warp],16); wmma::mma_sync(acc,af,bf,acc);__syncthreads(); } __shared__ float out[8][256];wmma::store_matrix_sync(out[warp],acc,16,wmma::mem_row_major);__syncwarp(); for(int z=lane;z<256;z+=32){int m=z/16,n=z%16; if(m0+m=S)return; const float *xs=x+(size_t)s*K; float v=0; for(int i=threadIdx.x;i>=1){if(threadIdx.x0?m[0]/7.f:1.f; if(!threadIdx.x)scale[s]=sc; uint8_t *dst=q+(size_t)s*((K+1)/2); for(int b=threadIdx.x;b<(K+1)/2;b+=blockDim.x){ int i=b*2,a=__float2int_rn(xs[i]/sc),c=i+1= 750 using namespace nvcuda; int warp=threadIdx.x/32,lane=threadIdx.x%32,tile=blockIdx.x*8+warp,c=blockIdx.y; if(tile*8>=O)return; GroupDesc d=desc[c]; const void *w=which==0?d.g:(which==1?d.u:d.d); const float *ws=which==0?d.gs:(which==1?d.us:d.ds); int fmt=which==0?d.gf:(which==1?d.uf:d.df); if(fmt!=2)return; wmma::fragment acc; wmma::fill_fragment(acc,0); const uint8_t *a=x+(size_t)d.offset*((K+1)/2); const uint8_t *b=(const uint8_t*)w+(size_t)(tile*8)*((K+1)/2); for(int k=0;k af; wmma::fragment bf; wmma::load_matrix_sync(af,a+k/2,K); wmma::load_matrix_sync(bf,b+k/2,K); wmma::mma_sync(acc,af,bf,acc); } __shared__ int out[8][64]; wmma::store_matrix_sync(out[warp],acc,8,wmma::mem_row_major); for(int i=lane;i<64;i+=32){int s=i/8,o=tile*8+i%8; if(s=d.rows) return; const void *w=which?d.u:d.g; const float *sc=which?d.us:d.gs; int fmt=which?d.uf:d.gf; size_t rb=row_bytes(fmt,D),row=(size_t)o*rb; const float *xs=x+(size_t)(d.offset+s)*D; float sum=0; for(int i=threadIdx.x;i>=1){ if(threadIdx.x=d.rows) return; size_t rb=row_bytes(d.df,I),row=(size_t)o*rb; const float *xs=x+(size_t)(d.offset+s)*I; float sum=0; for(int i=threadIdx.x;i>=1){ if(threadIdx.x>4; *lo=(float)(a&8?a-16:a); *hi=(float)(b&8?b-16:b); } /* Exact low-row W4A32 path. It consumes each packed weight byte once instead * of routing both nibbles through weight_at(), preserving FP32 activations. */ __global__ static void grouped_hidden_w4(float *y,const float *x,const GroupDesc *desc, int I,int D,int which){ int o=blockIdx.x,s=blockIdx.y,c=blockIdx.z;GroupDesc d=desc[c];if(s>=d.rows)return; const uint8_t *w=(const uint8_t*)(which?d.u:d.g);const float *sc=which?d.us:d.gs; const uint8_t *row=w+(size_t)o*((D+1)/2);const float *xs=x+(size_t)(d.offset+s)*D; float sum=0;for(int b=threadIdx.x;b<(D+1)/2;b+=blockDim.x){float a,z;unpack_s4(row[b],&a,&z); int i=b*2;sum+=xs[i]*a;if(i+1>=1){if(threadIdx.x=d.rows)return; const uint8_t *gr=(const uint8_t*)d.g+(size_t)o*((D+1)/2); const uint8_t *ur=(const uint8_t*)d.u+(size_t)o*((D+1)/2); const float *xs=x+(size_t)(d.offset+s)*D;float ga=0,ua=0; for(int b=threadIdx.x;b<(D+1)/2;b+=blockDim.x){float g0,g1,u0,u1;unpack_s4(gr[b],&g0,&g1);unpack_s4(ur[b],&u0,&u1); int i=b*2;ga+=xs[i]*g0;ua+=xs[i]*u0;if(i+1>=1){if(threadIdx.x=d.rows)return; const uint8_t *row=(const uint8_t*)d.d+(size_t)o*((I+1)/2); const float *xs=x+(size_t)(d.offset+s)*I;float sum=0; for(int b=threadIdx.x;b<(I+1)/2;b+=blockDim.x){float a,z;unpack_s4(row[b],&a,&z); int i=b*2;sum+=xs[i]*a;if(i+1>=1){if(threadIdx.x=d.rows)return; const uint8_t *gr=(const uint8_t*)d.g+(size_t)o*((D+1)/2); const uint8_t *ur=(const uint8_t*)d.u+(size_t)o*((D+1)/2); int ggs=d.ggs>0?d.ggs:D, ugs=d.ugs>0?d.ugs:D; const float *gsc=d.gs+(size_t)o*(size_t)((D+ggs-1)/ggs); const float *usc=d.us+(size_t)o*(size_t)((D+ugs-1)/ugs); const float *xs=x+(size_t)(d.offset+s)*D;float ga=0,ua=0; for(int b=threadIdx.x;b<(D+1)/2;b+=blockDim.x){float g0,g1,u0,u1;unpack_s4(gr[b],&g0,&g1);unpack_s4(ur[b],&u0,&u1); int i=b*2;float gv=gsc[i/ggs],uv=usc[i/ugs]; ga+=xs[i]*g0*gv;ua+=xs[i]*u0*uv; if(i+1>=1){if(threadIdx.x=d.rows)return; const uint8_t *row=(const uint8_t*)d.d+(size_t)o*((I+1)/2); int dgs=d.dgs>0?d.dgs:I; const float *dsc=d.ds+(size_t)o*(size_t)((I+dgs-1)/dgs); const float *xs=x+(size_t)(d.offset+s)*I;float sum=0; for(int b=threadIdx.x;b<(I+1)/2;b+=blockDim.x){float a,z;unpack_s4(row[b],&a,&z); int i=b*2;float sv=dsc[i/dgs]; sum+=xs[i]*a*sv;if(i+1>=1){if(threadIdx.x=S||nt<1)return; extern __shared__ float sm[];float *qa=sm,*cl=qa+K,*scores=cl+K,*red=scores+T; const float *qs=q+((size_t)s*H+h)*(Q+R); for(int k=tid;k>1;n;n>>=1){if(tid>1;n;n>>=1){if(tid=S||nt<1||nt>T)return; extern __shared__ float sm[];float *qa=sm,*cl=qa+K,*scores=cl+K,*red=scores+T; const float *qs=q+((size_t)s*H+h)*(Q+R); const float *ls=latent[s],*rs=rope[s]; for(int k=tid;k>1;n;n>>=1){if(tid>1;n;n>>=1){if(tid= bytes) return 1; if (*ptr) cudaFree(*ptr); *ptr = nullptr; *cap = 0; if (!cuda_ok(cudaMalloc(ptr, bytes), "scratch allocation")) return 0; *cap = bytes; return 1; } static int reserve_bytes(void **ptr,size_t *cap,size_t bytes){ if(*cap>=bytes) return 1; if(*ptr) cudaFree(*ptr); *ptr=nullptr; *cap=0; if(!cuda_ok(cudaMalloc(ptr,bytes),"descriptor allocation")) return 0; *cap=bytes; return 1; } static int reserve_pinned(float **ptr,size_t *cap,size_t bytes){ if(*cap>=bytes)return 1;if(*ptr)cudaFreeHost(*ptr);*ptr=nullptr;*cap=0; if(!cuda_ok(cudaMallocHost(ptr,bytes),"pinned staging allocation"))return 0;*cap=bytes;return 1; } extern "C" int coli_cuda_init(const int *devices, int count) { int available = 0; if (!devices || count < 1 || count > COLI_CUDA_MAX_DEVICES) return 0; if (!cuda_ok(cudaGetDeviceCount(&available), "device discovery")) return 0; g_nctx = 0; for (int i = 0; i < count; i++) { int device = devices[i]; if (device < 0 || device >= available) { std::fprintf(stderr, "[CUDA] invalid device %d (available: 0..%d)\n", device, available - 1); g_nctx = 0; return 0; } if (find_ctx(device)) { std::fprintf(stderr, "[CUDA] duplicate device %d\n", device); g_nctx = 0; return 0; } DeviceContext *ctx = &g_ctx[g_nctx]; *ctx = {}; ctx->device = device; if (!select_ctx(ctx)) { g_nctx = 0; return 0; } cudaDeviceProp prop{}; if (!cuda_ok(cudaGetDeviceProperties(&prop, device), "device properties")) { g_nctx = 0; return 0; } ctx->compute_major=prop.major;ctx->compute_minor=prop.minor; if(!cuda_ok(cudaStreamCreateWithFlags(&ctx->stream,cudaStreamNonBlocking),"stream creation")){ g_nctx=0;return 0; } g_nctx++; std::fprintf(stderr, "[CUDA] device %d: %s, %.1f GB VRAM, sm_%d%d\n", device, prop.name, prop.totalGlobalMem / 1e9, prop.major, prop.minor); } return 1; } extern "C" void coli_cuda_shutdown(void) { for (int i = 0; i < g_nctx; i++) { DeviceContext *ctx = &g_ctx[i]; if (!select_ctx(ctx)) continue; if (ctx->x) cudaFree(ctx->x); if (ctx->y) cudaFree(ctx->y); if (ctx->gate) cudaFree(ctx->gate); if (ctx->up) cudaFree(ctx->up); if (ctx->qx) cudaFree(ctx->qx); if (ctx->qscale) cudaFree(ctx->qscale); if(ctx->aq)cudaFree(ctx->aq);if(ctx->al)cudaFree(ctx->al);if(ctx->ar)cudaFree(ctx->ar);if(ctx->ac)cudaFree(ctx->ac); for(int b=0;b<27;b++) if(ctx->pipe_buf[b]) cudaFree(ctx->pipe_buf[b]); if (ctx->host_x) cudaFreeHost(ctx->host_x); if (ctx->host_y) cudaFreeHost(ctx->host_y); if (ctx->host_kv) cudaFreeHost(ctx->host_kv); if (ctx->stream) cudaStreamDestroy(ctx->stream); if (ctx->group_desc) cudaFree(ctx->group_desc); ctx->x = ctx->y = ctx->gate = ctx->up = nullptr; ctx->qx=nullptr; ctx->qscale=nullptr; ctx->aq=ctx->al=ctx->ar=ctx->ac=nullptr; ctx->host_x=ctx->host_y=ctx->host_kv=nullptr;ctx->stream=nullptr; ctx->x_cap = ctx->y_cap = ctx->gate_cap = ctx->up_cap = 0; ctx->qx_cap=ctx->qscale_cap=0; ctx->aq_cap=ctx->al_cap=ctx->ar_cap=ctx->ac_cap=0; ctx->host_x_cap=ctx->host_y_cap=ctx->host_kv_cap=0; ctx->group_desc=nullptr; ctx->group_desc_cap=0; } g_nctx = 0; } extern "C" int coli_cuda_device_count(void) { return g_nctx; } extern "C" int coli_cuda_device_at(int index) { return index >= 0 && index < g_nctx ? g_ctx[index].device : -1; } extern "C" int coli_cuda_mem_info(int device, size_t *free_bytes, size_t *total_bytes) { DeviceContext *ctx = find_ctx(device); if (!free_bytes || !total_bytes || !select_ctx(ctx)) return 0; return cuda_ok(cudaMemGetInfo(free_bytes, total_bytes), "memory info"); } extern "C" void coli_cuda_stats(int device, size_t *tensor_count, size_t *tensor_bytes) { size_t count = 0, bytes = 0; for (int i = 0; i < g_nctx; i++) if (device < 0 || g_ctx[i].device == device) { count += g_ctx[i].tensor_count; bytes += g_ctx[i].tensor_bytes; } if (tensor_count) *tensor_count = count; if (tensor_bytes) *tensor_bytes = bytes; } extern "C" void coli_cuda_group_stats(uint64_t *calls, uint64_t *experts, uint64_t *rows, double *h2d_ms, double *kernel_ms, double *d2h_ms) { if(calls) *calls=g_group_calls; if(experts) *experts=g_group_experts; if(rows) *rows=g_group_rows; if(h2d_ms) *h2d_ms=g_group_h2d_ms; if(kernel_ms) *kernel_ms=g_group_kernel_ms; if(d2h_ms) *d2h_ms=g_group_d2h_ms; } /* group size for the NEXT upload on this thread (fmt=4): routed through a * thread_local so the widely-wired upload signature (and the Windows DLL ABI) * stays untouched. pin_load uploads in parallel, hence thread_local. */ static thread_local int g_upload_gs = 0; extern "C" int coli_cuda_tensor_upload_g(ColiCudaTensor **tensor, const void *weights, const float *scales, int fmt, int I, int O, int device, int gs); extern "C" int coli_cuda_tensor_upload(ColiCudaTensor **tensor, const void *weights, const float *scales, int fmt, int I, int O, int device) { if (!tensor) return 0; if (*tensor) { /* Cached device copy: usable even when the caller's host pointers are * gone. CUDA_RELEASE_HOST slots null their host pointers after upload, * and with the old order (!weights checked first) every later matmul * on such a slot failed here — the GPU tier silently never computed * for host-released slab experts. */ ColiCudaTensor *t = *tensor; return t->fmt == fmt && t->I == I && t->O == O && t->device == device; } DeviceContext *ctx = find_ctx(device); if (!weights || I < 1 || O < 1 || !select_ctx(ctx)) return 0; size_t rb = row_bytes(fmt, I); if (!rb || (fmt && !scales)) return 0; ColiCudaTensor *t = static_cast(std::calloc(1, sizeof(*t))); if (!t) return 0; t->fmt = fmt; t->I = I; t->O = O; t->device = device; t->weight_bytes = rb * (size_t)O; t->gs = (fmt==4 && g_upload_gs>0) ? g_upload_gs : 0; t->scale_count = t->gs ? (size_t)O * (size_t)((I + t->gs - 1) / t->gs) : (size_t)O; if (!cuda_ok(cudaMalloc(&t->weights, t->weight_bytes), "tensor allocation") || !cuda_ok(cudaMemcpy(t->weights, weights, t->weight_bytes, cudaMemcpyHostToDevice), "tensor upload")) { coli_cuda_tensor_free(t); return 0; } if(fmt==2||fmt==4){ /* same nibble layout: offset-binary -> signed in place */ offset_to_signed_s4<<<(unsigned)((t->weight_bytes+255)/256),256>>>((uint8_t*)t->weights,t->weight_bytes); if(!cuda_ok(cudaGetLastError(),"int4 weight conversion")){coli_cuda_tensor_free(t);return 0;}} if (fmt) { if (!cuda_ok(cudaMalloc(&t->scales, t->scale_count * sizeof(float)), "scale allocation") || !cuda_ok(cudaMemcpy(t->scales, scales, t->scale_count * sizeof(float), cudaMemcpyHostToDevice), "scale upload")) { coli_cuda_tensor_free(t); return 0; } } t->tracked = 1; ctx->tensor_count++; ctx->tensor_bytes += t->weight_bytes + (fmt ? t->scale_count * sizeof(float) : 0); *tensor = t; return 1; } extern "C" int coli_cuda_tensor_upload_g(ColiCudaTensor **tensor, const void *weights, const float *scales, int fmt, int I, int O, int device, int gs){ g_upload_gs = gs>0 ? gs : 0; int r = coli_cuda_tensor_upload(tensor, weights, scales, fmt, I, O, device); g_upload_gs = 0; return r; } extern "C" int coli_cuda_tensor_update(ColiCudaTensor *tensor, const void *weights, const float *scales) { if (!tensor || !weights || (tensor->fmt && !scales)) return 0; DeviceContext *ctx=find_ctx(tensor->device); if (!select_ctx(ctx)) return 0; if (!cuda_ok(cudaMemcpy(tensor->weights,weights,tensor->weight_bytes, cudaMemcpyHostToDevice),"tensor refresh")) return 0; if(tensor->fmt==2||tensor->fmt==4){ offset_to_signed_s4<<<(unsigned)((tensor->weight_bytes+255)/256),256>>>( (uint8_t*)tensor->weights,tensor->weight_bytes); if(!cuda_ok(cudaGetLastError(),"int4 weight refresh")) return 0; } return !tensor->fmt || cuda_ok(cudaMemcpy(tensor->scales,scales, (tensor->scale_count?tensor->scale_count:(size_t)tensor->O)*sizeof(float), cudaMemcpyHostToDevice),"scale refresh"); } /* Test hook: COLI_GPU_FAIL_AFTER=N makes every GPU COMPUTE entry point report * failure after N successful calls (N=0: every call fails), exercising the * engine's CPU fallbacks and host-rematerialization end-to-end without real * hardware faults. Uploads/queries are not gated. Unset: no effect. */ static long g_gpu_calls; static int fault_injected(void) { const char *fa = std::getenv("COLI_GPU_FAIL_AFTER"); return fa && g_gpu_calls++ >= std::atol(fa); } extern "C" 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 (fault_injected()) return 0; if (S < 1 || !coli_cuda_tensor_upload(tensor, weights, scales, fmt, I, O, device)) return 0; ColiCudaTensor *t = *tensor; DeviceContext *ctx = find_ctx(t->device); if (!select_ctx(ctx)) return 0; size_t rb = row_bytes(fmt, I); size_t xb = (size_t)S * I * sizeof(float), yb = (size_t)S * O * sizeof(float); if (!reserve(&ctx->x, &ctx->x_cap, xb) || !reserve(&ctx->y, &ctx->y_cap, yb)) return 0; if (!cuda_ok(cudaMemcpy(ctx->x, x, xb, cudaMemcpyHostToDevice), "input upload")) return 0; dim3 grid((unsigned)O, (unsigned)S); quant_matmul<<>>(ctx->y, ctx->x, t->weights, t->scales, fmt, S, I, O, rb); if (!cuda_ok(cudaGetLastError(), "matmul launch") || !cuda_ok(cudaMemcpy(y, ctx->y, yb, cudaMemcpyDeviceToHost), "output download")) return 0; return 1; } extern "C" int coli_cuda_expert_mlp(ColiCudaTensor *gate, ColiCudaTensor *up, ColiCudaTensor *down, float *y, const float *x, int S) { if (fault_injected()) return 0; if (!gate || !up || !down || !x || !y || S < 1 || gate->device != up->device || gate->device != down->device || gate->I != up->I || gate->O != up->O || down->I != gate->O || down->O != gate->I) return 0; DeviceContext *ctx = find_ctx(gate->device); if (!select_ctx(ctx)) return 0; int D = gate->I, I = gate->O; size_t xb=(size_t)S*D*sizeof(float), ib=(size_t)S*I*sizeof(float); size_t yb=(size_t)S*D*sizeof(float); if (!reserve(&ctx->x,&ctx->x_cap,xb) || !reserve(&ctx->y,&ctx->y_cap,yb) || !reserve(&ctx->gate,&ctx->gate_cap,ib) || !reserve(&ctx->up,&ctx->up_cap,ib)) return 0; if (!cuda_ok(cudaMemcpy(ctx->x,x,xb,cudaMemcpyHostToDevice),"expert input upload")) return 0; dim3 hidden_grid((unsigned)I,(unsigned)S), output_grid((unsigned)D,(unsigned)S); quant_matmul<<>>(ctx->gate,ctx->x,gate->weights,gate->scales, gate->fmt,S,D,I,row_bytes(gate->fmt,D)); quant_matmul<<>>(ctx->up,ctx->x,up->weights,up->scales, up->fmt,S,D,I,row_bytes(up->fmt,D)); size_t n=(size_t)S*I; silu_mul<<<(unsigned)((n+255)/256),256>>>(ctx->gate,ctx->up,n); quant_matmul<<>>(ctx->y,ctx->gate,down->weights,down->scales, down->fmt,S,I,D,row_bytes(down->fmt,I)); if (!cuda_ok(cudaGetLastError(),"expert MLP launch") || !cuda_ok(cudaMemcpy(y,ctx->y,yb,cudaMemcpyDeviceToHost),"expert output download")) return 0; return 1; } extern "C" int coli_cuda_shared_mlp_w4a16(ColiCudaTensor *gate,ColiCudaTensor *up, ColiCudaTensor *down,float *y,const float *x,int S){ if (fault_injected()) return 0; if(!gate||!up||!down||!x||!y||S<1||gate->fmt!=2||up->fmt!=2||down->fmt!=2|| gate->device!=up->device||gate->device!=down->device||gate->I!=up->I|| gate->O!=up->O||down->I!=gate->O||down->O!=gate->I)return 0; DeviceContext *ctx=find_ctx(gate->device);if(!select_ctx(ctx)||ctx->compute_major<7)return 0; int D=gate->I,I=gate->O;size_t xb=(size_t)S*D*sizeof(float),ib=(size_t)S*I*sizeof(float); if(!reserve(&ctx->x,&ctx->x_cap,xb)||!reserve(&ctx->gate,&ctx->gate_cap,ib)|| !reserve(&ctx->up,&ctx->up_cap,ib)||!reserve(&ctx->y,&ctx->y_cap,xb)|| !reserve_pinned(&ctx->host_x,&ctx->host_x_cap,xb)|| !reserve_pinned(&ctx->host_y,&ctx->host_y_cap,xb))return 0; std::memcpy(ctx->host_x,x,xb); if(!cuda_ok(cudaMemcpyAsync(ctx->x,ctx->host_x,xb,cudaMemcpyHostToDevice,ctx->stream), "shared w4a16 input upload"))return 0; dim3 hidden((unsigned)((I+63)/64),(unsigned)((S+15)/16)); dim3 output((unsigned)((D+63)/64),(unsigned)((S+15)/16)); w4a16_gate_up<<stream>>>(ctx->gate,ctx->up,ctx->x, (const uint8_t*)gate->weights,(const uint8_t*)up->weights,gate->scales,up->scales,S,D,I); silu_mul<<<(unsigned)(((size_t)S*I+255)/256),256,0,ctx->stream>>>(ctx->gate,ctx->up,(size_t)S*I); w4a16_matmul<<stream>>>(ctx->y,ctx->gate,(const uint8_t*)down->weights,down->scales,S,I,D); if(!cuda_ok(cudaGetLastError(),"shared w4a16 launch")|| !cuda_ok(cudaMemcpyAsync(ctx->host_y,ctx->y,xb,cudaMemcpyDeviceToHost,ctx->stream), "shared w4a16 output download")|| !cuda_ok(cudaStreamSynchronize(ctx->stream),"shared w4a16 synchronize"))return 0; std::memcpy(y,ctx->host_y,xb); return 1; } extern "C" 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 (fault_injected()) return 0; if (!gates || !ups || !downs || !rows || !x || !y || count < 1) return 0; ColiCudaTensor *first=gates[0]; if (!first) return 0; int device=first->device,D=first->I,I=first->O,total=0,max_rows=0; GroupDesc host[64]; if(count>64) return 0; int all_s4=1,all_q4=1,any_g4=0; for(int c=0;cdevice!=device||u->device!=device||d->device!=device|| g->I!=D||u->I!=D||g->O!=I||u->O!=I||d->I!=I||d->O!=D) return 0; host[c]={g->weights,u->weights,d->weights,g->scales,u->scales,d->scales, g->fmt,u->fmt,d->fmt,rows[c],total, g->gs,u->gs,d->gs}; all_s4&=g->fmt==2&&u->fmt==2&&d->fmt==2; all_q4&=(g->fmt==2||g->fmt==4)&&(u->fmt==2||u->fmt==4)&&(d->fmt==2||d->fmt==4)&& !(g->gs&1)&&!(u->gs&1)&&!(d->gs&1); /* even gs: a packed byte never straddles groups */ any_g4|=g->fmt==4||u->fmt==4||d->fmt==4; total+=rows[c]; if(rows[c]>max_rows) max_rows=rows[c]; } DeviceContext *ctx=find_ctx(device); if(!select_ctx(ctx)) return 0; size_t xb=(size_t)total*D*sizeof(float), ib=(size_t)total*I*sizeof(float); if(!reserve(&ctx->x,&ctx->x_cap,xb)||!reserve(&ctx->y,&ctx->y_cap,xb)|| !reserve(&ctx->gate,&ctx->gate_cap,ib)||!reserve(&ctx->up,&ctx->up_cap,ib)|| !reserve_bytes(&ctx->group_desc,&ctx->group_desc_cap,(size_t)count*sizeof(GroupDesc))) return 0; int async=!getenv("COLI_CUDA_ASYNC")||atoi(getenv("COLI_CUDA_ASYNC")); if(async&&(!reserve_pinned(&ctx->host_x,&ctx->host_x_cap,xb)|| !reserve_pinned(&ctx->host_y,&ctx->host_y_cap,xb)))return 0; cudaError_t copy_desc=async?cudaMemcpyAsync(ctx->group_desc,host,(size_t)count*sizeof(GroupDesc), cudaMemcpyHostToDevice,ctx->stream) :cudaMemcpy(ctx->group_desc,host,(size_t)count*sizeof(GroupDesc),cudaMemcpyHostToDevice); if(!cuda_ok(copy_desc,"expert group descriptors"))return 0; int profile=getenv("COLI_CUDA_PROFILE")&&atoi(getenv("COLI_CUDA_PROFILE")); cudaEvent_t ev[4]={}; if(profile) for(int i=0;i<4;i++) if(!cuda_ok(cudaEventCreate(&ev[i]),"profile event")) profile=0; if(profile) cudaEventRecord(ev[0],ctx->stream); if(async)std::memcpy(ctx->host_x,x,xb); cudaError_t copy_x=async?cudaMemcpyAsync(ctx->x,ctx->host_x,xb,cudaMemcpyHostToDevice,ctx->stream) :cudaMemcpy(ctx->x,x,xb,cudaMemcpyHostToDevice); if(!cuda_ok(copy_x,"expert group input upload")) return 0; if(profile) cudaEventRecord(ev[1],ctx->stream); GroupDesc *dev=(GroupDesc*)ctx->group_desc; int tc=getenv("COLI_CUDA_TC_INT4")&&atoi(getenv("COLI_CUDA_TC_INT4")); tc=tc&&all_s4&&D%32==0&&I%32==0&&D%8==0&&I%8==0; int tc_min=getenv("COLI_CUDA_TC_MIN_ROWS")?atoi(getenv("COLI_CUDA_TC_MIN_ROWS")):8; for(int c=0;c=tc_min; if(tc){ size_t qb=(size_t)(total+7)*(size_t)(D>I?D:I)/2; if(!reserve_bytes((void**)&ctx->qx,&ctx->qx_cap,qb)|| !reserve(&ctx->qscale,&ctx->qscale_cap,(size_t)(total+7)*sizeof(float)))return 0; cudaMemsetAsync(ctx->qx,0,qb,ctx->stream); quantize_s4_rows<<stream>>>(ctx->qx,ctx->qscale,ctx->x,total,D); grouped_s4_wmma<<stream>>>(ctx->gate,ctx->qx,ctx->qscale,dev,D,I,0); grouped_s4_wmma<<stream>>>(ctx->up,ctx->qx,ctx->qscale,dev,D,I,1); silu_mul<<<(unsigned)(((size_t)total*I+255)/256),256,0,ctx->stream>>>(ctx->gate,ctx->up,(size_t)total*I); quantize_s4_rows<<stream>>>(ctx->qx,ctx->qscale,ctx->gate,total,I); grouped_s4_wmma<<stream>>>(ctx->y,ctx->qx,ctx->qscale,dev,I,D,2); }else if(all_s4&&ctx->compute_major>=7&&getenv("COLI_CUDA_TC_W4A16")&& atoi(getenv("COLI_CUDA_TC_W4A16"))&& [&]{ int tc16_min=getenv("COLI_CUDA_TC_W4A16_MIN")?atoi(getenv("COLI_CUDA_TC_W4A16_MIN")):16; for(int c=0;c=tc16_min) return 1; return 0; }()){ /* At least one expert has enough rows for a Tensor Core tile. Groups * where EVERY expert is below the threshold (decode: r=1) fall through * to the grouped-W4 path below — 3 launches for the whole group instead * of 4 per expert (#431: the launch flood measured at ~981 micro-kernels * per token came from decode riding this branch's per-expert fallback). */ /* W4A16 Tensor Core per gruppo: attivazioni fp16 per tile (lossless al * contrario del path W4A4), un lancio per expert dentro lo stream — * l'overhead di lancio e' trascurabile rispetto ai GEMM. */ int tc16_min=getenv("COLI_CUDA_TC_W4A16_MIN")?atoi(getenv("COLI_CUDA_TC_W4A16_MIN")):16; int off16=0; for(int c=0;cgate+(size_t)off16*I,*u16=ctx->up+(size_t)off16*I; float *x16=ctx->x+(size_t)off16*D,*y16=ctx->y+(size_t)off16*D; if(r>=tc16_min){ dim3 hg16((unsigned)((I+63)/64),(unsigned)((r+15)/16)); dim3 og16((unsigned)((D+63)/64),(unsigned)((r+15)/16)); w4a16_gate_up<<stream>>>(g16,u16,x16, (const uint8_t*)host[c].g,(const uint8_t*)host[c].u,host[c].gs,host[c].us,r,D,I); silu_mul<<<(unsigned)(((size_t)r*I+255)/256),256,0,ctx->stream>>>(g16,u16,(size_t)r*I); w4a16_matmul<<stream>>>(y16,g16, (const uint8_t*)host[c].d,host[c].ds,r,I,D); }else{ /* piccoli batch: tile TC quasi vuoti + overhead di lancio — il * kernel naive per-elemento resta piu' veloce (misurato in decode) */ quant_matmul<<stream>>>(g16,x16, host[c].g,host[c].gs,host[c].gf,r,D,I,row_bytes(host[c].gf,D)); quant_matmul<<stream>>>(u16,x16, host[c].u,host[c].us,host[c].uf,r,D,I,row_bytes(host[c].uf,D)); silu_mul<<<(unsigned)(((size_t)r*I+255)/256),256,0,ctx->stream>>>(g16,u16,(size_t)r*I); quant_matmul<<stream>>>(y16,g16, host[c].d,host[c].ds,host[c].df,r,I,D,row_bytes(host[c].df,I)); } off16+=r; } }else if(all_s4&&(!getenv("COLI_CUDA_W4_PACKED")||atoi(getenv("COLI_CUDA_W4_PACKED")))){ dim3 hg((unsigned)I,(unsigned)max_rows,(unsigned)count),og((unsigned)D,(unsigned)max_rows,(unsigned)count); int dual=!getenv("COLI_CUDA_DUAL_PROJ")||atoi(getenv("COLI_CUDA_DUAL_PROJ")); if(dual)grouped_hidden_w4_dual<<stream>>>(ctx->gate,ctx->up,ctx->x,dev,I,D); else{ grouped_hidden_w4<<stream>>>(ctx->gate,ctx->x,dev,I,D,0); grouped_hidden_w4<<stream>>>(ctx->up,ctx->x,dev,I,D,1); } silu_mul<<<(unsigned)(((size_t)total*I+255)/256),256,0,ctx->stream>>>(ctx->gate,ctx->up,(size_t)total*I); grouped_down_w4<<stream>>>(ctx->y,ctx->gate,dev,D,I); }else if(all_q4&&any_g4){ /* grouped-int4 (fmt=4) present: per-group scales (#334). fmt=2 members * ride along as the ng=1 special case. */ dim3 hg((unsigned)I,(unsigned)max_rows,(unsigned)count),og((unsigned)D,(unsigned)max_rows,(unsigned)count); grouped_hidden_g4_dual<<stream>>>(ctx->gate,ctx->up,ctx->x,dev,I,D); silu_mul<<<(unsigned)(((size_t)total*I+255)/256),256,0,ctx->stream>>>(ctx->gate,ctx->up,(size_t)total*I); grouped_down_g4<<stream>>>(ctx->y,ctx->gate,dev,D,I); }else{ /* generic path decodes fmt 0/1/2/3 only — a fmt=4 group that slipped the * gates above (odd gs) must NOT be silently decoded as int2 (#334). */ for(int c=0;cstream>>>(ctx->gate,ctx->x,dev,I,D,0); grouped_hidden<<stream>>>(ctx->up,ctx->x,dev,I,D,1); silu_mul<<<(unsigned)(((size_t)total*I+255)/256),256,0,ctx->stream>>>(ctx->gate,ctx->up,(size_t)total*I); grouped_down<<stream>>>(ctx->y,ctx->gate,dev,D,I); } if(profile) cudaEventRecord(ev[2],ctx->stream); if(!async&&!cuda_ok(cudaStreamSynchronize(ctx->stream),"expert group synchronize"))return 0; cudaError_t copy_y=async?cudaMemcpyAsync(ctx->host_y,ctx->y,xb,cudaMemcpyDeviceToHost,ctx->stream) :cudaMemcpy(y,ctx->y,xb,cudaMemcpyDeviceToHost); if(!cuda_ok(cudaGetLastError(),"expert group launch")||!cuda_ok(copy_y,"expert group output download"))return 0; if(async){if(!cuda_ok(cudaStreamSynchronize(ctx->stream),"expert group synchronize"))return 0; std::memcpy(y,ctx->host_y,xb);} if(profile){ cudaEventRecord(ev[3],ctx->stream); cudaEventSynchronize(ev[3]); float a=0,b=0,c=0; cudaEventElapsedTime(&a,ev[0],ev[1]); cudaEventElapsedTime(&b,ev[1],ev[2]); cudaEventElapsedTime(&c,ev[2],ev[3]); { std::lock_guard lock(g_group_stats_mu); g_group_h2d_ms+=a; g_group_kernel_ms+=b; g_group_d2h_ms+=c; } for(int i=0;i<4;i++) cudaEventDestroy(ev[i]); } { std::lock_guard lock(g_group_stats_mu); g_group_calls++; g_group_experts+=(uint64_t)count; g_group_rows+=(uint64_t)total; } return 1; } /* ---- Async expert group (Inc.4): issue/take split of coli_cuda_expert_group ---- * The measured cost of the sync call at decode is ~0.45 ms/call of HOST-side wait * (stream sync + staging), vs ~0.18 ms of actual GPU work — 70% tax, paid ~5x per * layer because a token's 8 experts scatter across devices. issue() stages and * launches on the device stream and returns immediately; take() syncs and hands * back the pinned result rows. One issue may be outstanding per device; moe() * takes at each layer end, which also orders the next layer's reuse of the ctx * scratch buffers. Small batches only (decode/spec): bigger totals keep the sync * path with its TC variants. Numerics are the sync path's small-batch kernels, * so greedy output is byte-identical by construction. */ extern "C" int coli_cuda_expert_group_issue(ColiCudaTensor *const *gates, ColiCudaTensor *const *ups, ColiCudaTensor *const *downs, const int *rows, int count, const float *x) { if (!gates || !ups || !downs || !rows || !x || count < 1 || count > 64) return 0; ColiCudaTensor *first=gates[0]; if (!first) return 0; int device=first->device,D=first->I,I=first->O,total=0; GroupDesc host[64]; for(int c=0;cdevice!=device||u->device!=device||d->device!=device|| g->I!=D||u->I!=D||g->O!=I||u->O!=I||d->I!=I||d->O!=D) return 0; host[c]={g->weights,u->weights,d->weights,g->scales,u->scales,d->scales, g->fmt,u->fmt,d->fmt,rows[c],total}; total+=rows[c]; } if(total>8) return 0; /* decode-scale only */ DeviceContext *ctx=find_ctx(device); if(!ctx||ctx->group_pending||!select_ctx(ctx)) return 0; size_t xb=(size_t)total*D*sizeof(float), ib=(size_t)total*I*sizeof(float); if(!reserve(&ctx->x,&ctx->x_cap,xb)||!reserve(&ctx->y,&ctx->y_cap,xb)|| !reserve(&ctx->gate,&ctx->gate_cap,ib)||!reserve(&ctx->up,&ctx->up_cap,ib)|| !reserve_pinned(&ctx->host_x,&ctx->host_x_cap,xb)|| !reserve_pinned(&ctx->host_y,&ctx->host_y_cap,xb)) return 0; std::memcpy(ctx->host_x,x,xb); if(!cuda_ok(cudaMemcpyAsync(ctx->x,ctx->host_x,xb,cudaMemcpyHostToDevice,ctx->stream), "expert group issue upload")) return 0; for(int c=0;cgate+(size_t)host[c].offset*I,*u16=ctx->up+(size_t)host[c].offset*I; float *x16=ctx->x+(size_t)host[c].offset*D,*y16=ctx->y+(size_t)host[c].offset*D; quant_matmul<<stream>>>(g16,x16, host[c].g,host[c].gs,host[c].gf,r,D,I,row_bytes(host[c].gf,D)); quant_matmul<<stream>>>(u16,x16, host[c].u,host[c].us,host[c].uf,r,D,I,row_bytes(host[c].uf,D)); silu_mul<<<(unsigned)(((size_t)r*I+255)/256),256,0,ctx->stream>>>(g16,u16,(size_t)r*I); quant_matmul<<stream>>>(y16,g16, host[c].d,host[c].ds,host[c].df,r,I,D,row_bytes(host[c].df,I)); } if(!cuda_ok(cudaGetLastError(),"expert group issue launch")|| !cuda_ok(cudaMemcpyAsync(ctx->host_y,ctx->y,xb,cudaMemcpyDeviceToHost,ctx->stream), "expert group issue download")) return 0; ctx->group_pending=1; ctx->group_pending_bytes=xb; { std::lock_guard lock(g_group_stats_mu); g_group_calls++; g_group_experts+=(uint64_t)count; g_group_rows+=(uint64_t)total; } return 1; } extern "C" const float *coli_cuda_expert_group_take(int device) { DeviceContext *ctx=find_ctx(device); if(!ctx||!ctx->group_pending) return nullptr; ctx->group_pending=0; if(!select_ctx(ctx)) return nullptr; if(!cuda_ok(cudaStreamSynchronize(ctx->stream),"expert group take")) return nullptr; return ctx->host_y; } extern "C" int coli_cuda_attention_absorb(ColiCudaTensor *w,float *ctx,const float *q, const float *latent,const float *rope,int H,int Q, int R,int V,int K,int T,float scale){ if (fault_injected()) return 0; if(!w||!ctx||!q||!latent||!rope||H<1||Q<1||R<1||V<1||K<1||K>512||T<1||T>4096|| w->I!=K||w->O!=H*(Q+V))return 0; DeviceContext *dc=find_ctx(w->device);if(!select_ctx(dc))return 0; size_t qb=(size_t)H*(Q+R)*sizeof(float),lb=(size_t)T*K*sizeof(float); size_t rb=(size_t)T*R*sizeof(float),cb=(size_t)H*V*sizeof(float); if(!reserve(&dc->aq,&dc->aq_cap,qb)||!reserve(&dc->al,&dc->al_cap,lb)|| !reserve(&dc->ar,&dc->ar_cap,rb)||!reserve(&dc->ac,&dc->ac_cap,cb))return 0; if(!cuda_ok(cudaMemcpyAsync(dc->aq,q,qb,cudaMemcpyHostToDevice,dc->stream),"attention q upload")|| !cuda_ok(cudaMemcpyAsync(dc->al,latent,lb,cudaMemcpyHostToDevice,dc->stream),"attention latent upload")|| !cuda_ok(cudaMemcpyAsync(dc->ar,rope,rb,cudaMemcpyHostToDevice,dc->stream),"attention rope upload"))return 0; size_t shared=(size_t)(2*K+T)*sizeof(float); attention_absorb_kernel<<stream>>>(dc->ac,dc->aq,dc->al,dc->ar,w->weights,w->scales, w->fmt,H,Q,R,V,K,T,scale); if(!cuda_ok(cudaGetLastError(),"attention absorb launch")|| !cuda_ok(cudaMemcpyAsync(ctx,dc->ac,cb,cudaMemcpyDeviceToHost,dc->stream),"attention context download")|| !cuda_ok(cudaStreamSynchronize(dc->stream),"attention synchronize"))return 0; return 1; } static int attention_absorb_batch_run(ColiCudaTensor *w,ColiCudaTensor *proj,float *out, const float *q,const float *latent,const float *rope,int S,int H,int Q,int R,int V, int K,int T,float scale){ if(!w||!out||!q||!latent||!rope||S<1||H<1||Q<1||R<1||V<1||K<1||K>512|| T8192||w->I!=K||w->O!=H*(Q+V))return 0; if(proj&&(proj->device!=w->device||proj->I!=H*V))return 0; DeviceContext *dc=find_ctx(w->device);if(!select_ctx(dc))return 0; size_t qb=(size_t)S*H*(Q+R)*sizeof(float),lb=(size_t)T*K*sizeof(float); size_t rb=(size_t)T*R*sizeof(float),cb=(size_t)S*H*V*sizeof(float); if(!reserve(&dc->aq,&dc->aq_cap,qb)||!reserve(&dc->al,&dc->al_cap,lb)|| !reserve(&dc->ar,&dc->ar_cap,rb)||!reserve(&dc->ac,&dc->ac_cap,cb))return 0; if(!cuda_ok(cudaMemcpyAsync(dc->aq,q,qb,cudaMemcpyHostToDevice,dc->stream),"attention batch q upload")|| !cuda_ok(cudaMemcpyAsync(dc->al,latent,lb,cudaMemcpyHostToDevice,dc->stream),"attention batch latent upload")|| !cuda_ok(cudaMemcpyAsync(dc->ar,rope,rb,cudaMemcpyHostToDevice,dc->stream),"attention batch rope upload"))return 0; size_t shared=(size_t)(2*K+T+256)*sizeof(float); attention_absorb_batch_kernel<<stream>>>(dc->ac,dc->aq,dc->al, dc->ar,w->weights,w->scales,w->fmt,S,H,Q,R,V,K,T,scale); if(!cuda_ok(cudaGetLastError(),"attention batch launch"))return 0; const float *src=dc->ac;size_t ob=cb; if(proj){ ob=(size_t)S*proj->O*sizeof(float);if(!reserve(&dc->y,&dc->y_cap,ob))return 0; quant_matmul<<O,S),256,0,dc->stream>>>(dc->y,dc->ac,proj->weights, proj->scales,proj->fmt,S,proj->I,proj->O,row_bytes(proj->fmt,proj->I)); if(!cuda_ok(cudaGetLastError(),"attention o_proj launch"))return 0;src=dc->y; } if(!cuda_ok(cudaMemcpyAsync(out,src,ob,cudaMemcpyDeviceToHost,dc->stream), proj?"attention projected output download":"attention batch context download")|| !cuda_ok(cudaStreamSynchronize(dc->stream),"attention batch synchronize"))return 0; return 1; } extern "C" int coli_cuda_attention_absorb_batch(ColiCudaTensor *w,float *ctx,const float *q, const float *latent,const float *rope,int S,int H,int Q,int R,int V,int K,int T, float scale){ if (fault_injected()) return 0; return attention_absorb_batch_run(w,nullptr,ctx,q,latent,rope,S,H,Q,R,V,K,T,scale); } extern "C" int coli_cuda_attention_project_batch(ColiCudaTensor *w,ColiCudaTensor *proj, float *out,const float *q,const float *latent,const float *rope,int S,int H,int Q, int R,int V,int K,int T,float scale){ if (fault_injected()) return 0; return attention_absorb_batch_run(w,proj,out,q,latent,rope,S,H,Q,R,V,K,T,scale); } extern "C" int coli_cuda_attention_project_ragged(ColiCudaTensor *w,ColiCudaTensor *proj, float *out,const float *q,const void *const *keys, const float *const *latent,const float *const *rope, const int *lengths,int S,int H,int Q,int R,int V,int K,int T,float scale){ if(!w||!proj||!out||!q||!keys||!latent||!rope||!lengths||S<1||S>512||T<1||T>8192|| H<1||Q<1||R<1||V<1||K<1||K>512||w->I!=K||w->O!=H*(Q+V)|| proj->device!=w->device||proj->I!=H*V)return 0; DeviceContext *dc=find_ctx(w->device); if(!select_ctx(dc))return 0; float **dl=(float**)std::malloc((size_t)S*sizeof(*dl)); float **dr=(float**)std::malloc((size_t)S*sizeof(*dr)); int *old=(int*)std::malloc((size_t)S*sizeof(*old)); int *add=(int*)std::malloc((size_t)S*sizeof(*add)); int *off=(int*)std::malloc((size_t)S*sizeof(*off));int packed_n=0; if(!dl||!dr||!old||!add||!off){std::free(dl);std::free(dr);std::free(old);std::free(add);std::free(off);return 0;} for(int s=0;sT){std::free(dl);std::free(dr);std::free(old);std::free(add);std::free(off);return 0;} RaggedKVEntry *e=nullptr; for(int i=0;iragged_count;i++)if(w->ragged[i].key==keys[s]){e=&w->ragged[i];break;} if(!e){ if(w->ragged_count>=512){std::free(dl);std::free(dr);std::free(old);std::free(add);std::free(off);return 0;} e=&w->ragged[w->ragged_count++];std::memset(e,0,sizeof(*e));e->key=keys[s]; } if(e->K!=K||e->R!=R||e->host_l!=latent[s]||e->host_r!=rope[s]||lengths[s]length){ if(e->latent)cudaFree(e->latent);if(e->rope)cudaFree(e->rope); e->latent=e->rope=nullptr;e->length=e->capacity=0; e->K=K;e->R=R;e->host_l=latent[s];e->host_r=rope[s]; } if(lengths[s]>e->capacity){ int cap=(lengths[s]+63)&~63;float *nl=nullptr,*nr=nullptr; if(!cuda_ok(cudaMalloc(&nl,(size_t)cap*K*sizeof(float)),"ragged KV latent page")|| !cuda_ok(cudaMalloc(&nr,(size_t)cap*R*sizeof(float)),"ragged KV rope page")){ if(nl)cudaFree(nl);if(nr)cudaFree(nr);std::free(dl);std::free(dr);std::free(old);std::free(add);std::free(off);return 0; } if(e->length){ cudaMemcpyAsync(nl,e->latent,(size_t)e->length*K*sizeof(float),cudaMemcpyDeviceToDevice,dc->stream); cudaMemcpyAsync(nr,e->rope,(size_t)e->length*R*sizeof(float),cudaMemcpyDeviceToDevice,dc->stream); } if(e->latent)cudaFree(e->latent);if(e->rope)cudaFree(e->rope); e->latent=nl;e->rope=nr;e->capacity=cap; } dl[s]=e->latent;dr[s]=e->rope;old[s]=e->length;add[s]=lengths[s]-e->length; off[s]=packed_n;packed_n+=add[s]*(K+R); } size_t qb=(size_t)S*H*(Q+R)*sizeof(float); size_t cb=(size_t)S*H*V*sizeof(float),ob=(size_t)S*proj->O*sizeof(float); size_t pb=(size_t)packed_n*sizeof(float); size_t desc=(size_t)S*(2*sizeof(float*)+4*sizeof(int)); int ok=reserve(&dc->aq,&dc->aq_cap,qb)&&reserve(&dc->ac,&dc->ac_cap,cb)&& reserve(&dc->y,&dc->y_cap,ob)&&reserve_bytes(&dc->group_desc,&dc->group_desc_cap,desc)&& (!pb||(reserve(&dc->al,&dc->al_cap,pb)&&reserve_pinned(&dc->host_kv,&dc->host_kv_cap,pb))); char *db=(char*)dc->group_desc;float **ddl=(float**)db,**ddr=ddl+S; int *dn=(int*)(ddr+S),*dold=dn+S,*dadd=dold+S,*doff=dadd+S; if(ok&&pb){ for(int s=0;shost_kv+off[s]; std::memcpy(p,latent[s]+(size_t)old[s]*K,(size_t)add[s]*K*sizeof(float)); std::memcpy(p+(size_t)add[s]*K,rope[s]+(size_t)old[s]*R,(size_t)add[s]*R*sizeof(float)); } ok=cuda_ok(cudaMemcpyAsync(dc->al,dc->host_kv,pb,cudaMemcpyHostToDevice,dc->stream),"ragged KV append upload"); } if(ok)ok=cuda_ok(cudaMemcpyAsync(dc->aq,q,qb,cudaMemcpyHostToDevice,dc->stream),"ragged q upload")&& cuda_ok(cudaMemcpyAsync(ddl,dl,(size_t)S*sizeof(float*),cudaMemcpyHostToDevice,dc->stream),"ragged latent pointers")&& cuda_ok(cudaMemcpyAsync(ddr,dr,(size_t)S*sizeof(float*),cudaMemcpyHostToDevice,dc->stream),"ragged rope pointers")&& cuda_ok(cudaMemcpyAsync(dn,lengths,(size_t)S*sizeof(int),cudaMemcpyHostToDevice,dc->stream),"ragged lengths upload")&& cuda_ok(cudaMemcpyAsync(dold,old,(size_t)S*sizeof(int),cudaMemcpyHostToDevice,dc->stream),"ragged old lengths")&& cuda_ok(cudaMemcpyAsync(dadd,add,(size_t)S*sizeof(int),cudaMemcpyHostToDevice,dc->stream),"ragged append lengths")&& cuda_ok(cudaMemcpyAsync(doff,off,(size_t)S*sizeof(int),cudaMemcpyHostToDevice,dc->stream),"ragged append offsets"); if(ok&&pb)ragged_kv_append<<stream>>>(ddl,ddr,dc->al,dold,dadd,doff,K,R); if(ok)for(int s=0;sragged_count;i++)if(w->ragged[i].key==keys[s]){w->ragged[i].length=lengths[s];break;} } std::free(dl);std::free(dr);std::free(old);std::free(add);std::free(off);if(!ok)return 0; size_t shared=(size_t)(2*K+T+256)*sizeof(float); attention_absorb_ragged_kernel<<stream>>>(dc->ac,dc->aq,ddl,ddr, dn,w->weights,w->scales,w->fmt,S,H,Q,R,V,K,T,scale); quant_matmul<<O,S),256,0,dc->stream>>>(dc->y,dc->ac,proj->weights, proj->scales,proj->fmt,S,proj->I,proj->O,row_bytes(proj->fmt,proj->I)); return cuda_ok(cudaGetLastError(),"ragged attention launch")&& cuda_ok(cudaMemcpyAsync(out,dc->y,ob,cudaMemcpyDeviceToHost,dc->stream),"ragged output download")&& cuda_ok(cudaStreamSynchronize(dc->stream),"ragged attention synchronize"); } extern "C" void coli_cuda_tensor_free(ColiCudaTensor *tensor) { if (!tensor) return; DeviceContext *ctx = find_ctx(tensor->device); if (ctx) select_ctx(ctx); if (tensor->tracked && ctx) { size_t bytes = tensor->weight_bytes + (tensor->fmt ? (size_t)tensor->O * sizeof(float) : 0); if (ctx->tensor_count) ctx->tensor_count--; if (ctx->tensor_bytes >= bytes) ctx->tensor_bytes -= bytes; } if (tensor->weights) cudaFree(tensor->weights); if (tensor->scales) cudaFree(tensor->scales); for(int i=0;iragged_count;i++){ if(tensor->ragged[i].latent)cudaFree(tensor->ragged[i].latent); if(tensor->ragged[i].rope)cudaFree(tensor->ragged[i].rope); } std::free(tensor); } extern "C" size_t coli_cuda_tensor_bytes(const ColiCudaTensor *tensor) { return tensor ? tensor->weight_bytes + (tensor->fmt ? (size_t)tensor->O * sizeof(float) : 0) : 0; } extern "C" int coli_cuda_tensor_device(const ColiCudaTensor *tensor) { return tensor ? tensor->device : -1; } /* ==== resident-pipeline primitives (Inc.0, 2026-07-13) ==== * Device-side building blocks so the residual stream can stay on the layer's * home device across a whole layer. Control flow stays on CPU; only the data * plane lives here. All entry points take DEVICE pointers (no transfers) — * the caller owns staging via the pipe buffer API below. */ __global__ static void pipe_rmsnorm_rows(float *y,const float *x,const float *w, int D,float eps,int xstride,int ystride){ const float *xr=x+(size_t)blockIdx.x*xstride; float *yr=y+(size_t)blockIdx.x*ystride; __shared__ double sh[256]; double a=0; for(int i=threadIdx.x;i0;s>>=1){ if(threadIdx.x=27||!select_ctx(ctx)) return NULL; if(!reserve(&ctx->pipe_buf[slot],&ctx->pipe_cap[slot],bytes)) return NULL; return ctx->pipe_buf[slot]; } extern "C" void *coli_cuda_pipe_alloc(int device,size_t bytes){ DeviceContext *ctx=find_ctx(device); if(!select_ctx(ctx)) return NULL; void *p=NULL; if(!cuda_ok(cudaMalloc(&p,bytes),"pipe alloc")) return NULL; return p; } extern "C" void coli_cuda_pipe_free(int device,void *p){ DeviceContext *ctx=find_ctx(device); if(!p||!select_ctx(ctx)) return; cudaFree(p); } extern "C" int coli_cuda_pipe_upload(int device,void *dst,const void *src,size_t bytes){ DeviceContext *ctx=find_ctx(device); if(!select_ctx(ctx)) return 0; return cuda_ok(cudaMemcpy(dst,src,bytes,cudaMemcpyHostToDevice),"pipe upload"); } extern "C" int coli_cuda_pipe_download(int device,const void *src,void *dst,size_t bytes){ DeviceContext *ctx=find_ctx(device); if(!select_ctx(ctx)) return 0; return cuda_ok(cudaMemcpy(dst,src,bytes,cudaMemcpyDeviceToHost),"pipe download"); } extern "C" int coli_cuda_pipe_rmsnorm(int device,float *y_dev,const float *x_dev, const float *w_dev,int S,int D,float eps){ if (fault_injected()) return 0; DeviceContext *ctx=find_ctx(device); if(S<1||D<1||!select_ctx(ctx)) return 0; pipe_rmsnorm_rows<<>>(y_dev,x_dev,w_dev,D,eps,D,D); return cuda_ok(cudaGetLastError(),"pipe rmsnorm"); } extern "C" int coli_cuda_pipe_rmsnorm_s(int device,float *y_dev,const float *x_dev, const float *w_dev,int S,int D,float eps, int xstride,int ystride){ if (fault_injected()) return 0; DeviceContext *ctx=find_ctx(device); if(S<1||D<1||xstride>>(y_dev,x_dev,w_dev,D,eps,xstride,ystride); return cuda_ok(cudaGetLastError(),"pipe rmsnorm strided"); } extern "C" int coli_cuda_pipe_rope(int device,float *v_dev,const int *pos_dev, int rows,int stride,int offset,int R,int heads, float theta){ if (fault_injected()) return 0; DeviceContext *ctx=find_ctx(device); if(rows<1||R<2||R>256||heads<1||!select_ctx(ctx)) return 0; pipe_rope_rows<<>>(v_dev,pos_dev,0,stride,offset,R,heads,theta); return cuda_ok(cudaGetLastError(),"pipe rope"); } extern "C" int coli_cuda_pipe_rope_base(int device,float *v_dev,int pos_base,int rows, int stride,int offset,int R,int heads,float theta){ if (fault_injected()) return 0; DeviceContext *ctx=find_ctx(device); if(rows<1||R<2||R>256||heads<1||!select_ctx(ctx)) return 0; pipe_rope_rows<<>>(v_dev,NULL,pos_base,stride,offset,R,heads,theta); return cuda_ok(cudaGetLastError(),"pipe rope base"); } /* ---- device router (#431 PR-A) ------------------------------------------- * Router for one decode row, entirely on the layer's home device: logits GEMV * (E x D, tiny) + sigmoid, bias-augmented top-K selection, route-level TOPP * truncation, norm_topk and routed_scale — a float-faithful clone of moe()'s * plain routing path (colibri.c FASE A). Selection runs single-thread so the * argmax order, tie-breaking (strict >, lowest index wins) and weight math * match the CPU reference exactly; only the dot/expf rounding can differ, * which is the documented kernel-family divergence class (#100/#163). * Results are packed [idx[K] | w[K] | keff] in one scratch buffer and read * back with a single tiny D2H. */ __global__ void pipe_router_logits(const float *__restrict__ x, const float *__restrict__ W, const float *__restrict__ bias, int D, float *logit, float *choice){ int e = blockIdx.x; const float *w = W + (size_t)e*D; float acc = 0.f; for(int i=threadIdx.x; i>1; s>0; s>>=1){ if(threadIdx.xbv){bv=choice[e];best=e;} } idx[kk]=best; w[kk]=logit[best]; } int Ke=Ksel; if(topp>0.f && topp<1.f){ for(int a=1;a=0 && w[b]=topp*tot){ Ke=kk+1; break; } } } if(norm_topk){ float sm=0.f; for(int kk=0;kk4096||Ksel<1||Ksel>64||!select_ctx(ctx)) return 0; size_t pack=(size_t)Ksel*(sizeof(int)+sizeof(float))+sizeof(int); float *logit=coli_cuda_pipe_scratch(device,22,(size_t)E*sizeof(float)); float *chc =coli_cuda_pipe_scratch(device,23,(size_t)E*sizeof(float)); char *out =(char*)coli_cuda_pipe_scratch(device,24,pack); if(!logit||!chc||!out) return 0; pipe_router_logits<<>>(x_dev,(const float*)rw_dev,(const float*)rb_dev,D,logit,chc); pipe_router_select<<<1,1>>>(logit,chc,E,Ksel,topp,norm_topk,routed_scale,out); if(!cuda_ok(cudaGetLastError(),"pipe router launch")) return 0; char buf[64*(sizeof(int)+sizeof(float))+sizeof(int)]; if(!cuda_ok(cudaMemcpy(buf,out,pack,cudaMemcpyDeviceToHost),"pipe router readback")) return 0; memcpy(idx_host,buf,(size_t)Ksel*sizeof(int)); memcpy(w_host,buf+Ksel*sizeof(int),(size_t)Ksel*sizeof(float)); memcpy(keff_host,buf+Ksel*(sizeof(int)+sizeof(float)),sizeof(int)); return 1; } extern "C" int coli_cuda_pipe_copy2d(int device,float *dst,int dpitch,const float *src, int spitch,int width,int height){ DeviceContext *ctx=find_ctx(device); if(!select_ctx(ctx)) return 0; return cuda_ok(cudaMemcpy2D(dst,(size_t)dpitch*4,src,(size_t)spitch*4, (size_t)width*4,height,cudaMemcpyDeviceToDevice),"pipe copy2d"); } /* attention batch + fused o_proj with DEVICE-resident q/latent/rope: the whole * upstream projection chain stayed on this device, so nothing is uploaded here. * Only the final [S,O] projection is downloaded to host. */ extern "C" int coli_cuda_attention_project_batch_dev(ColiCudaTensor *w,ColiCudaTensor *proj, float *out,const float *q_dev,const float *latent_dev,const float *rope_dev, int S,int H,int Q,int R,int V,int K,int T,float scale){ if (fault_injected()) return 0; if(!w||!proj||!out||!q_dev||!latent_dev||!rope_dev||S<1||H<1||Q<1||R<1||V<1|| K<1||K>512||T8192||w->I!=K||w->O!=H*(Q+V)|| proj->device!=w->device||proj->I!=H*V)return 0; DeviceContext *dc=find_ctx(w->device);if(!select_ctx(dc))return 0; size_t cb=(size_t)S*H*V*sizeof(float); if(!reserve(&dc->ac,&dc->ac_cap,cb))return 0; size_t shared=(size_t)(2*K+T+256)*sizeof(float); attention_absorb_batch_kernel<<stream>>>(dc->ac,q_dev,latent_dev, rope_dev,w->weights,w->scales,w->fmt,S,H,Q,R,V,K,T,scale); if(!cuda_ok(cudaGetLastError(),"pipe attention launch"))return 0; size_t ob=(size_t)S*proj->O*sizeof(float); if(!reserve(&dc->y,&dc->y_cap,ob))return 0; quant_matmul<<O,S),256,0,dc->stream>>>(dc->y,dc->ac,proj->weights, proj->scales,proj->fmt,S,proj->I,proj->O,row_bytes(proj->fmt,proj->I)); if(!cuda_ok(cudaGetLastError(),"pipe o_proj launch"))return 0; if(!cuda_ok(cudaMemcpyAsync(out,dc->y,ob,cudaMemcpyDeviceToHost,dc->stream),"pipe attention download")|| !cuda_ok(cudaStreamSynchronize(dc->stream),"pipe attention sync"))return 0; return 1; } extern "C" int coli_cuda_pipe_silu_mul(int device,float *gate_dev,const float *up_dev, size_t n){ if (fault_injected()) return 0; DeviceContext *ctx=find_ctx(device); if(!n||!select_ctx(ctx)) return 0; silu_mul<<<(unsigned)((n+255)/256),256>>>(gate_dev,up_dev,n); return cuda_ok(cudaGetLastError(),"pipe silu mul"); } extern "C" int coli_cuda_pipe_add(int device,float *x_dev,const float *t_dev,size_t n){ if (fault_injected()) return 0; DeviceContext *ctx=find_ctx(device); if(!n||!select_ctx(ctx)) return 0; pipe_add_n<<<(unsigned)((n+255)/256),256>>>(x_dev,t_dev,n); return cuda_ok(cudaGetLastError(),"pipe add"); } extern "C" int coli_cuda_pipe_rows_add(int device,float *x_dev,const float *partial_dev, const int *rows_dev,int nrows,int D){ if (fault_injected()) return 0; DeviceContext *ctx=find_ctx(device); if(nrows<1||D<1||!select_ctx(ctx)) return 0; pipe_rows_add<<>>(x_dev,partial_dev,rows_dev,D); return cuda_ok(cudaGetLastError(),"pipe rows add"); } /* GEMM with device-resident activations: same quant_matmul kernel as * coli_cuda_matmul, zero host transfers. */ extern "C" int coli_cuda_pipe_gemm(ColiCudaTensor *t,float *y_dev,const float *x_dev, int S){ if (fault_injected()) return 0; if(!t||S<1) return 0; DeviceContext *ctx=find_ctx(t->device); if(!select_ctx(ctx)) return 0; dim3 grid((unsigned)t->O,(unsigned)S); quant_matmul<<>>(y_dev,x_dev,t->weights,t->scales,t->fmt,S,t->I,t->O, row_bytes(t->fmt,t->I)); return cuda_ok(cudaGetLastError(),"pipe gemm"); } /* copia diretta scheda->scheda (P2P se disponibile, altrimenti staging driver) */ extern "C" int coli_cuda_pipe_peer_copy(int dst_dev,float *dst,int src_dev, const float *src,size_t bytes){ if(!dst||!src) return 0; if(dst_dev==src_dev){ DeviceContext *c=find_ctx(dst_dev); if(!select_ctx(c)) return 0; return cuda_ok(cudaMemcpy(dst,src,bytes,cudaMemcpyDeviceToDevice),"pipe intra copy"); } return cuda_ok(cudaMemcpyPeer(dst,dst_dev,src,src_dev,bytes),"pipe peer copy"); } /* come attention_project_batch_dev ma l'uscita di o_proj RESTA sul device (out_dev). */ extern "C" int coli_cuda_attention_project_batch_dev_out(ColiCudaTensor *w,ColiCudaTensor *proj, float *out_dev,const float *q_dev,const float *latent_dev,const float *rope_dev, int S,int H,int Q,int R,int V,int K,int T,float scale){ if (fault_injected()) return 0; if(!w||!proj||!out_dev||!q_dev||!latent_dev||!rope_dev||S<1||H<1||Q<1||R<1||V<1|| K<1||K>512||T8192||w->I!=K||w->O!=H*(Q+V)|| proj->device!=w->device||proj->I!=H*V)return 0; DeviceContext *dc=find_ctx(w->device);if(!select_ctx(dc))return 0; size_t cb=(size_t)S*H*V*sizeof(float); if(!reserve(&dc->ac,&dc->ac_cap,cb))return 0; size_t shared=(size_t)(2*K+T+256)*sizeof(float); attention_absorb_batch_kernel<<stream>>>(dc->ac,q_dev,latent_dev, rope_dev,w->weights,w->scales,w->fmt,S,H,Q,R,V,K,T,scale); if(!cuda_ok(cudaGetLastError(),"pipe attention launch (dev out)"))return 0; quant_matmul<<O,S),256,0,dc->stream>>>(out_dev,dc->ac,proj->weights, proj->scales,proj->fmt,S,proj->I,proj->O,row_bytes(proj->fmt,proj->I)); if(!cuda_ok(cudaGetLastError(),"pipe o_proj launch (dev out)"))return 0; return cuda_ok(cudaStreamSynchronize(dc->stream),"pipe attention sync (dev out)"); } /* absorb batch con TUTTO su device (q/latent/rope gia' residenti sulla scheda * dello shard, ctx resta sul device): il cuore della attention head-shardata * dentro il pipeline. Nessun trasferimento host. */ extern "C" int coli_cuda_attention_absorb_batch_dev(ColiCudaTensor *w,float *ctx_dev, const float *q_dev,const float *latent_dev,const float *rope_dev, int S,int H,int Q,int R,int V,int K,int T,float scale){ if (fault_injected()) return 0; if(!w||!ctx_dev||!q_dev||!latent_dev||!rope_dev||S<1||H<1||Q<1||R<1||V<1|| K<1||K>512||T8192||w->I!=K||w->O!=H*(Q+V))return 0; DeviceContext *dc=find_ctx(w->device);if(!select_ctx(dc))return 0; size_t shared=(size_t)(2*K+T+256)*sizeof(float); attention_absorb_batch_kernel<<stream>>>(ctx_dev,q_dev,latent_dev, rope_dev,w->weights,w->scales,w->fmt,S,H,Q,R,V,K,T,scale); if(!cuda_ok(cudaGetLastError(),"pipe shard attention launch"))return 0; return cuda_ok(cudaStreamSynchronize(dc->stream),"pipe shard attention sync"); } /* absorb per il DECODE con KV gia' residente: carica solo q (poche KB), * latent/rope arrivano dall'ombra device. ctx torna a host (S piccolo). */ extern "C" int coli_cuda_attention_absorb_kvdev(ColiCudaTensor *w,float *ctx,const float *q, const float *latent_dev,const float *rope_dev,int H,int Q,int R,int V,int K,int T, float scale){ if (fault_injected()) return 0; if(!w||!ctx||!q||!latent_dev||!rope_dev||H<1||Q<1||R<1||V<1||K<1||K>512||T<1||T>8192|| w->I!=K||w->O!=H*(Q+V))return 0; DeviceContext *dc=find_ctx(w->device);if(!select_ctx(dc))return 0; size_t qb=(size_t)H*(Q+R)*sizeof(float),cb=(size_t)H*V*sizeof(float); if(!reserve(&dc->aq,&dc->aq_cap,qb)||!reserve(&dc->ac,&dc->ac_cap,cb))return 0; if(!cuda_ok(cudaMemcpyAsync(dc->aq,q,qb,cudaMemcpyHostToDevice,dc->stream),"kvdev q upload"))return 0; size_t shared=(size_t)(2*K+T+256)*sizeof(float); attention_absorb_batch_kernel<<stream>>>(dc->ac,dc->aq,latent_dev, rope_dev,w->weights,w->scales,w->fmt,1,H,Q,R,V,K,T,scale); if(!cuda_ok(cudaGetLastError(),"kvdev absorb launch")|| !cuda_ok(cudaMemcpyAsync(ctx,dc->ac,cb,cudaMemcpyDeviceToHost,dc->stream),"kvdev ctx download")|| !cuda_ok(cudaStreamSynchronize(dc->stream),"kvdev absorb sync"))return 0; return 1; } extern "C" int coli_cuda_pipe_sync(int device){ DeviceContext *ctx=find_ctx(device); if(!select_ctx(ctx)) return 0; return cuda_ok(cudaDeviceSynchronize(),"pipe sync"); }