From ec891360291102eae60a41c072d1e8dcf295f10a Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Wed, 15 Jul 2026 00:18:05 +0800 Subject: [PATCH] GPU resident pipeline: batch CUDA attention, head-sharded kv_b, prefill expert groups, W4A16 mixed dispatch (#111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fuse CUDA expert MLP execution * Group CUDA expert transfers by device * Instrument grouped CUDA expert execution * Bound grouped CUDA decode scratch * Execute expert groups across GPUs in parallel * Release host backing for multi-GPU experts * Define quality-preserving memory policies * Overlap cold expert loading with resident compute * Adapt expert placement with session LFRU * Fuse q4 expert gate and up dispatch * Plan CPU work on physical cores * Batch grouped expert CUDA kernels * Separate VRAM and RAM expert placement * Add ragged multi-sequence decode forward * feat(runtime): add continuous decode scheduler * Route concurrent API requests through batch scheduler * Harden multiplex request lifecycle and framing * Cancel disconnected multiplex requests * Bind API port before starting the engine * fix automatic KV slot allocation * add native int4 Tensor Core grouped GEMM * add Tensor Core throughput benchmark * optimize packed int4 low-row kernels * add asynchronous CUDA staging streams * document validated six-GPU dense acceleration * tune six-GPU expert hot set * raise validated expert hot-set target * add CUDA MLA absorption core * fuse grouped expert gate and up projections * Warn for explicit lossy routing flags * Add full-resident expert placement mode * Adapt VRAM expert slots to live routes * Accelerate int4 matvec on AVX-512 * Reduce AVX-512 and RoPE decode overhead * Seed every GPU expert layer after prefill * Limit live GPU swaps during decode * CUDA batch MLA attention, kv_b head-sharding, fused o_proj, expert-group dispatch, W4A16 kernels Lab-qualified on the 6x RTX 5090 machine (914-token request benchmark): - batch MLA absorption kernel (COLI_CUDA_ATTN=1): whole-batch attention on device, 154.8s -> 102.4s - attention -> o_proj fusion on the layer device: -> 97.4s - kv_b head-sharding across cards (COLI_CUDA_ATTN_SHARD=1), no weight duplication: -> 94.05s - per-device expert-group dispatch with pinned-buffer async transfers, W4A16 tensor-core kernels for the shared expert, OMP hot-thread tuning Negative results (reverted, kept out): GPU-side weighted scatter-add (atomics + per-layer D2H lose 43.8%), shared-expert fused small-batch kernel (-38.8%), W4A4 grouped tensor cores (int4 activations corrupt output). Details in the lab research log. * GPU resident pipeline: device-resident prefill attention chain, GPU expert groups in prefill, batched router, W4A16 mixed dispatch COLI_CUDA_PIPE=1 keeps the prefill data plane on the layer home device; control flow (routing, cache/pin management) stays on CPU. Any CUDA failure falls back to the unchanged CPU path. - Device primitives + unit tests (tests/test_pipe_cuda.cu): rmsnorm (strided), interleaved RoPE, silu-mul, residual add, fixed-order row merge (no atomics), device-input GEMM, persistent per-device scratch. All verified against the engine's CPU math on SM120 (worst 1.2e-5). - attn_pipe_prefill: q_a -> norm -> q_b -> rope -> kv_a -> norm -> rope -> batch attention -> o_proj in one device chain (q_a/q_b/kv_a colocated with kv_b); only the final [S,D] and the new KV rows return to host. Attention 41.2s -> 30.8s on the 1571-token benchmark. - Prefill batch-union now uses the GPU expert groups (previously gated to S<=64, leaving all VRAM-resident experts idle during prefill - measured 21ms of GPU expert time in a 148s prefill). Expert phase 78.9s -> 69.0s. - Router computed as one batched matmul instead of S sequential rows (bit-identical math). - W4A16 tensor-core path for expert groups (COLI_CUDA_TC_W4A16=1) with row-count mixed dispatch: >=16 rows per expert use tensor cores, smaller batches keep the naive kernel (tensor cores measured negative below ~16 rows). Expert phase 69.0s -> 64.3s, decode unaffected. Net on the 1571-token prefill benchmark: 148.8s -> 114.3-126.8s (component timings stable across runs; wall drifts +-3-5s because .coli_usage placement learning shifts the expert tiers between runs). PROFILO now also prints the prefill-phase breakdown. * Skip OMP hot-thread tuning when CUDA is enabled The active-spin worker team measured 66.9s->20.9s on the CPU-only Zen5 build, but on the six-GPU full-residency workload the spinning workers contend with the CUDA dispatch threads: ~4x slower prefill with the process stuck near 1.8 cores. Gate the tuning on COLI_CUDA so each configuration keeps the behavior it was measured to prefer. * Inc.2a: sparse layers fully resident on the layer device, residual hops cards at layer boundaries COLI_CUDA_PIPE=2 keeps the residual stream on the layer home device for consecutive sparse layers (cudaMemcpyPeer at boundaries): in/post norms, attention chain, both residual adds and the shared-expert MLP run on device. Per layer only the post-norm activations (router + CPU-tier experts + group gather), the new KV rows and, on DSA indexer layers, the pre-attention norm leave the card. Per-layer transfers drop from ~130MB to ~70MB. A device-side snapshot at layer entry makes any mid-layer CUDA failure fall back to the unchanged CPU path idempotently. 1571-token prefill: 127.1s (PIPE=1 control) -> 117.6/118.9s, components attention 30.8->26.1, other 31.8->22.5-24.5; output verified coherent against the control. * Head-sharded attention inside the pipe: negative on PCIe star topology, gated opt-in Slicing q per card from the home device and collecting ctx back serializes ~95MB/layer through the home card's PCIe link: attention 26.1s -> 41.4/44.4s on the 1571-token benchmark (two repeats), wall 117.6 -> 135-138s. The standalone host-path sharding won because six cards uploaded from host RAM in parallel; a home-device star has no such parallelism without NVLink. Kept behind COLI_CUDA_PIPE_SHARD=1 for interconnects where peer bandwidth does not share one root port. * Inc.3: device-resident KV shadow for decode attention Decode re-uploaded the whole latent+rope window per layer per token (~300MB/token at 1571 context). Each layer now keeps a device shadow of the compressed KV on its kv_b card, bulk-synced when behind and appended incrementally; the host cache stays canonical. Invalidation on kv_bind (slot switch), kv_alloc (resize) and on any overwrite of mirrored rows, with the legacy full-upload path as fallback. Measured (COLI_CUDA_PIPE gate): short-context decode 5.48 -> 5.59/5.87 tok/s, 1571-context decode 4.14 -> 4.22 tok/s. Decode remains CPU-expert bound; the shadow removes the transfer tax, not the compute. * tools: unified user-experience benchmark (bench_ux.sh) Two fixed scenarios (short chat, long-document QA), TTFT + decode tok/s + first-line drift check, TEMP=0 DRAFT=0 enforced, medians over REPS runs. Encodes the measurement discipline from the lab record: same binary per comparison, judge medians because .coli_usage placement learning drifts wall times between runs. * tools: bench_ux.sh executable bit * gitignore compiled test binaries * tools: expert_atlas.py — measure per-expert topic affinity (#175) Diffs .coli_usage across 10 themed probe batches (code/math/chinese/ prose/science/law/poetry/structured/translation/casual, 3 prompts each) driven through a running API server — one engine load total. Every touched expert gets a topic-affinity vector, entropy, and a specialist/ generalist label; output experts.json feeds the Brain page hover. * serve: persist .coli_usage after every turn in mux mode, not only at exit run_serve_mux saved the learning cache once at shutdown; a crash lost the whole session's routing history, and live consumers of the file (expert_atlas.py diffs it between probe batches) saw a frozen snapshot. Now saved per turn like the interactive path (165KB write, negligible). * web: Brain hover shows measured expert atlas when published If /experts.json (from tools/expert_atlas.py, #175) is served next to the app, the tooltip upgrades from the depth heuristic to measured data: specialist/generalist label, entropy, and the top-3 topic affinities. Row index maps to real layer (row+3, last row = MTP 78). Falls back to the heuristic when no atlas is published. --------- Co-authored-by: JustVugg --- .gitignore | 3 + c/backend_cuda.cu | 432 +++++++++++++++++ c/backend_cuda.h | 62 +++ c/glm.c | 440 ++++++++++++++++-- .../{test_i4_acc512 => test_compat_direct} | Bin 16200 -> 16208 bytes c/tests/test_decode_batch | Bin 16448 -> 0 bytes c/tests/test_idot | Bin 239888 -> 0 bytes c/tests/test_pipe_cuda.cu | 137 ++++++ c/tests/test_schema_gbnf | Bin 0 -> 50648 bytes c/tools/bench_ux.sh | 51 ++ c/tools/expert_atlas.py | 141 ++++++ web/src/Brain.tsx | 29 +- web/src/index.css | 5 + 13 files changed, 1267 insertions(+), 33 deletions(-) rename c/tests/{test_i4_acc512 => test_compat_direct} (72%) delete mode 100755 c/tests/test_decode_batch delete mode 100755 c/tests/test_idot create mode 100644 c/tests/test_pipe_cuda.cu create mode 100755 c/tests/test_schema_gbnf create mode 100755 c/tools/bench_ux.sh create mode 100644 c/tools/expert_atlas.py diff --git a/.gitignore b/.gitignore index 203a322..ce6a80c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ c/backend_metal_test c/tests/test_tier c/mio_env/ c/bench/ +c/tests/test_decode_batch +c/tests/test_i4_acc512 +c/tests/test_idot diff --git a/c/backend_cuda.cu b/c/backend_cuda.cu index 3bb853b..9ce142f 100644 --- a/c/backend_cuda.cu +++ b/c/backend_cuda.cu @@ -18,12 +18,14 @@ struct ColiCudaTensor { 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; size_t host_x_cap,host_y_cap; float *aq,*al,*ar,*ac; size_t aq_cap,al_cap,ar_cap,ac_cap; + float *pipe_buf[24]; size_t pipe_cap[24]; /* scratch persistenti del resident pipeline */ cudaStream_t stream; void *group_desc; size_t group_desc_cap; size_t tensor_count, tensor_bytes; @@ -120,6 +122,68 @@ __global__ static void silu_mul(float *gate, const float *up, size_t n) { } } +/* 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=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= bytes) return 1; if (*ptr) cudaFree(*ptr); @@ -286,6 +381,7 @@ extern "C" int coli_cuda_init(const int *devices, int count) { 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; } @@ -307,6 +403,7 @@ extern "C" void coli_cuda_shutdown(void) { 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<24;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->stream) cudaStreamDestroy(ctx->stream); @@ -453,6 +550,34 @@ extern "C" int coli_cuda_expert_mlp(ColiCudaTensor *gate, ColiCudaTensor *up, return 1; } +extern "C" int coli_cuda_shared_mlp_w4a16(ColiCudaTensor *gate,ColiCudaTensor *up, + ColiCudaTensor *down,float *y,const float *x,int S){ + 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, @@ -510,6 +635,38 @@ extern "C" int coli_cuda_expert_group(ColiCudaTensor *const *gates, 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"))){ + /* 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")); @@ -547,6 +704,7 @@ extern "C" int coli_cuda_expert_group(ColiCudaTensor *const *gates, return 1; } + 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){ @@ -569,6 +727,49 @@ extern "C" int coli_cuda_attention_absorb(ColiCudaTensor *w,float *ctx,const flo 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){ + 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){ + return attention_absorb_batch_run(w,proj,out,q,latent,rope,S,H,Q,R,V,K,T,scale); +} + extern "C" void coli_cuda_tensor_free(ColiCudaTensor *tensor) { if (!tensor) return; DeviceContext *ctx = find_ctx(tensor->device); @@ -590,3 +791,234 @@ extern "C" size_t coli_cuda_tensor_bytes(const ColiCudaTensor *tensor) { 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=24||!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){ + 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){ + 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){ + 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){ + 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"); +} +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(!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){ + 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){ + 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){ + 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(!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(!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(!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(!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"); +} diff --git a/c/backend_cuda.h b/c/backend_cuda.h index 533bbea..c84a265 100644 --- a/c/backend_cuda.h +++ b/c/backend_cuda.h @@ -56,6 +56,14 @@ COLI_CUDA_DLLEXPORT int coli_cuda_matmul(ColiCudaTensor **tensor, COLI_CUDA_DLLEXPORT int coli_cuda_expert_mlp(ColiCudaTensor *gate, ColiCudaTensor *up, ColiCudaTensor *down, float *y, const float *x, int S); +/* Prefill-oriented shared expert path. INT4 weights stay packed in global + * memory, activations are converted to FP16 per tile, and Tensor Cores + * accumulate into FP32. Unlike COLI_CUDA_TC_INT4 this does not quantize the + * activation to INT4. */ +int coli_cuda_shared_mlp_w4a16(ColiCudaTensor *gate, ColiCudaTensor *up, + ColiCudaTensor *down, float *y, + const float *x, int S); + /* Packed group of same-shaped experts. Inputs and outputs contain sum(rows) * consecutive [D] rows in call order. */ COLI_CUDA_DLLEXPORT int coli_cuda_expert_group(ColiCudaTensor *const *gates, @@ -69,6 +77,21 @@ COLI_CUDA_DLLEXPORT int coli_cuda_attention_absorb(ColiCudaTensor *kv_b,float *c const float *latent,const float *rope,int H,int Q, int R,int V,int K,int T,float attention_scale); +/* Causal MLA absorption for S contiguous rows from one sequence. The KV + * arrays contain T rows ending at the final query; query s attends T-S+s+1 + * rows. One transfer and one launch replace S host round-trips. */ +COLI_CUDA_DLLEXPORT int coli_cuda_attention_absorb_batch(ColiCudaTensor *kv_b,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 attention_scale); + +/* Same attention batch followed immediately by resident o_proj on the same + * device. Only the final [S,D] tensor crosses back to the host. */ +COLI_CUDA_DLLEXPORT int coli_cuda_attention_project_batch(ColiCudaTensor *kv_b,ColiCudaTensor *o_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 attention_scale); + COLI_CUDA_DLLEXPORT void coli_cuda_tensor_free(ColiCudaTensor *tensor); COLI_CUDA_DLLEXPORT size_t coli_cuda_tensor_bytes(const ColiCudaTensor *tensor); COLI_CUDA_DLLEXPORT int coli_cuda_tensor_device(const ColiCudaTensor *tensor); @@ -77,8 +100,47 @@ COLI_CUDA_DLLEXPORT int coli_cuda_tensor_device(const ColiCudaTensor *tensor); int coli_cuda_tensor_update(ColiCudaTensor *tensor, const void *weights, const float *scales); +/* ---- resident-pipeline primitives (Inc.0): device-pointer entry points ---- */ +float *coli_cuda_pipe_scratch(int device,int slot,size_t bytes); +void *coli_cuda_pipe_alloc(int device,size_t bytes); +void coli_cuda_pipe_free(int device,void *p); +int coli_cuda_pipe_upload(int device,void *dst,const void *src,size_t bytes); +int coli_cuda_pipe_download(int device,const void *src,void *dst,size_t bytes); +int coli_cuda_pipe_rmsnorm(int device,float *y_dev,const float *x_dev, + const float *w_dev,int S,int D,float eps); +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); +int coli_cuda_pipe_silu_mul(int device,float *gate_dev,const float *up_dev,size_t n); +int coli_cuda_pipe_add(int device,float *x_dev,const float *t_dev,size_t n); +int coli_cuda_pipe_rows_add(int device,float *x_dev,const float *partial_dev, + const int *rows_dev,int nrows,int D); +int coli_cuda_pipe_gemm(ColiCudaTensor *t,float *y_dev,const float *x_dev,int S); +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); +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); +int coli_cuda_pipe_copy2d(int device,float *dst,int dpitch,const float *src, + int spitch,int width,int height); +int coli_cuda_attention_project_batch_dev(ColiCudaTensor *kv_b,ColiCudaTensor *o_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); +int coli_cuda_attention_absorb_batch_dev(ColiCudaTensor *kv_b_shard,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); +int coli_cuda_attention_absorb_kvdev(ColiCudaTensor *kv_b,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); +int coli_cuda_pipe_peer_copy(int dst_dev,float *dst,int src_dev, + const float *src,size_t bytes); +int coli_cuda_attention_project_batch_dev_out(ColiCudaTensor *kv_b,ColiCudaTensor *o_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); +int coli_cuda_pipe_sync(int device); + #ifdef __cplusplus } #endif #endif + diff --git a/c/glm.c b/c/glm.c index 561d410..2780b79 100644 --- a/c/glm.c +++ b/c/glm.c @@ -113,6 +113,11 @@ typedef struct { float *in_ln, *post_ln; /* MLA (densa, quantizzata) */ QT q_a, q_b, kv_a, kv_b, o; float *q_a_ln, *kv_a_ln; +#ifdef COLI_CUDA + ColiCudaTensor *kv_b_shard[COLI_CUDA_MAX_DEVICES]; + int shard_h0[COLI_CUDA_MAX_DEVICES],shard_hn[COLI_CUDA_MAX_DEVICES],n_kv_b_shard; + int shared_w4a16_failed; +#endif int sparse; /* dense mlp (sparse==0) */ QT gate_proj, up_proj, down_proj; @@ -152,6 +157,7 @@ typedef struct { int *kv_start; /* prima pos valida nella KV del layer (MTP: parziale) */ KVState *kv; ESlot **ecache; int *ecn; int ecap; /* LRU expert per-layer */ + float **kv_dev_L, **kv_dev_R; int *kv_dev_valid; /* ombra KV su device (decode) */ ESlot ws[64]; /* working set del layer corrente (load paralleli) */ ESlot **pin; int *npin; /* HOT-STORE: expert pinnati in RAM (mai evicted) */ uint32_t **eusage; /* contatori persistenti (per STATS/PIN) */ @@ -1065,6 +1071,33 @@ static float *ld(Model *m, const char *name){ /* tensore 1D f32 residente (nor float *p=(float*)qalloc((size_t)n*sizeof(float)); /* registrato per la GPU sotto METAL */ st_read_f32(&m->S,name,p,0); return p; } +#ifdef COLI_CUDA +static void qt_cuda_colocate(QT *dst,const QT *src){ + if(!g_cuda_enabled||!g_cuda_dense||!dst->cuda_eligible||!src->cuda_eligible|| + dst->cuda_device==src->cuda_device)return; + int old=-1,now=-1;for(int i=0;icuda_device)old=i;if(g_cuda_devices[i]==src->cuda_device)now=i; + } + if(old>=0)g_cuda_dense_projected[old]-=qt_bytes(dst); + if(now>=0)g_cuda_dense_projected[now]+=qt_bytes(dst); + dst->cuda_device=src->cuda_device; +} +static void layer_cuda_shard_kvb(Layer *l,int H,int Q,int V){ + if(!g_cuda_enabled||!g_cuda_dense||g_cuda_ndev<2||l->kv_b.fmt==0)return; + int rb=l->kv_b.fmt==1?l->kv_b.I:(l->kv_b.fmt==2?(l->kv_b.I+1)/2:(l->kv_b.I+3)/4); + const uint8_t *weights=l->kv_b.fmt==1?(const uint8_t*)l->kv_b.q8:l->kv_b.q4; + for(int d=0,h0=0;dkv_b.s+(int64_t)h0*(Q+V); + if(!coli_cuda_tensor_upload(&l->kv_b_shard[d],part,scale,l->kv_b.fmt,l->kv_b.I,rows,g_cuda_devices[d]))return; + l->shard_h0[d]=h0;l->shard_hn[d]=hn;l->n_kv_b_shard++;h0+=hn; + } + int old=-1;for(int i=0;ikv_b.cuda_device)old=i; + if(old>=0)g_cuda_dense_projected[old]-=qt_bytes(&l->kv_b); + l->kv_b.cuda_eligible=0; +} +#endif static void model_init(Model *m, const char *snap, int cap, int ebits, int dbits){ memset(m,0,sizeof(*m)); m->ebits=ebits; m->dbits=dbits; @@ -1079,6 +1112,8 @@ static void model_init(Model *m, const char *snap, int cap, int ebits, int dbits m->L=calloc(c->n_layers,sizeof(Layer)); int NR=c->n_layers+1; /* +1: riga del layer MTP */ m->ecap=cap; m->ecache=calloc(NR,sizeof(ESlot*)); m->ecn=calloc(NR,sizeof(int)); + m->kv_dev_L=calloc(NR,sizeof(float*)); m->kv_dev_R=calloc(NR,sizeof(float*)); + m->kv_dev_valid=calloc(NR,sizeof(int)); m->eroute=calloc(NR,sizeof(int*)); m->enr=calloc(NR,sizeof(int)); m->pin=calloc(NR,sizeof(ESlot*)); m->npin=calloc(NR,sizeof(int)); m->eusage=calloc(NR,sizeof(uint32_t*)); m->eheat=calloc(NR,sizeof(uint32_t*)); @@ -1097,6 +1132,14 @@ static void model_init(Model *m, const char *snap, int cap, int ebits, int dbits l->kv_a_ln= ld(m,P("self_attn.kv_a_layernorm.weight")); l->kv_b = qt_load(m,P("self_attn.kv_b_proj.weight"), H*(c->qk_nope+c->v_head), c->kv_lora, dbits); l->o = qt_load(m,P("self_attn.o_proj.weight"), D, H*c->v_head, dbits); +#ifdef COLI_CUDA + qt_cuda_colocate(&l->o,&l->kv_b); + qt_cuda_colocate(&l->q_a,&l->kv_b); /* PIPE: intera catena attention sulla */ + qt_cuda_colocate(&l->q_b,&l->kv_b); /* stessa scheda / whole attention chain */ + qt_cuda_colocate(&l->kv_a,&l->kv_b); /* on the layer home device */ + if(getenv("COLI_CUDA_ATTN_SHARD")&&atoi(getenv("COLI_CUDA_ATTN_SHARD"))) + layer_cuda_shard_kvb(l,H,c->qk_nope,c->v_head); +#endif l->sparse = (i >= c->first_dense); if(!l->sparse){ l->gate_proj = qt_load(m,P("mlp.gate_proj.weight"), c->dense_inter, D, dbits); @@ -1109,6 +1152,11 @@ static void model_init(Model *m, const char *snap, int cap, int ebits, int dbits l->sh_gate = qt_load(m,P("mlp.shared_experts.gate_proj.weight"), sI, D, dbits); l->sh_up = qt_load(m,P("mlp.shared_experts.up_proj.weight"), sI, D, dbits); l->sh_down = qt_load(m,P("mlp.shared_experts.down_proj.weight"), D, sI, dbits); +#ifdef COLI_CUDA + qt_cuda_colocate(&l->sh_gate,&l->kv_b); /* PIPE2: shared chain on the layer home device */ + qt_cuda_colocate(&l->sh_up,&l->sh_gate); + qt_cuda_colocate(&l->sh_down,&l->sh_gate); +#endif m->ecache[i]=calloc(cap,sizeof(ESlot)); m->eroute[i]=calloc(c->topk,sizeof(int)); /* metodo C: ultimo routing del layer */ m->eusage[i]=calloc(c->n_experts,sizeof(uint32_t)); @@ -1573,7 +1621,10 @@ static void qt_matvec_rows(const QT *t, int r0, int n, const float *x, float *y) y[j]=(float)a; } } -static int g_absorb=-1; /* ABSORB: -1 auto (decode S<=4), 0 mai, 1 sempre (test) */ +static int g_absorb=-1; +#ifdef COLI_CUDA +static int g_cuda_pipe=0; /* COLI_CUDA_PIPE=1: prefill attention chain resident on the layer home device */ +#endif /* ABSORB: -1 auto (decode S<=4), 0 mai, 1 sempre (test) */ static int g_dsa_force=0; /* DSA_FORCE=1: selezione sempre attiva (test: top-min(k,T)=denso) */ static int cmp_fdesc(const void *a,const void *b){ float x=*(const float*)a, y=*(const float*)b; return xy?-1:0; } @@ -1581,6 +1632,141 @@ static int cmp_fdesc(const void *a,const void *b){ /* attenzione MLA con KV-cache compressa, su token nuovi x[S,hidden], pos_base = pos del primo */ /* kvs/pos describe a ragged decode batch: each row may belong to a different * sequence. NULL keeps the original contiguous, currently-bound KV path. */ +#ifdef COLI_CUDA +/* Ombra KV su device per il DECODE: righe [0,upto) valide sulla scheda di kv_b. + * L'host resta canonico; l'ombra si riallinea in blocco quando resta indietro e + * viene invalidata da kv_bind / dalla riscrittura di righe gia' specchiate. */ +static int kv_dev_sync(Model *m, Layer *l, int layer, int upto){ + Cfg *c=&m->c; int kvl=c->kv_lora, R=c->qk_rope, dev=l->kv_b.cuda_device; + if(upto>m->max_t) return 0; + if(!m->kv_dev_L[layer]){ + m->kv_dev_L[layer]=(float*)coli_cuda_pipe_alloc(dev,(size_t)m->max_t*kvl*4); + m->kv_dev_R[layer]=(float*)coli_cuda_pipe_alloc(dev,(size_t)m->max_t*R*4); + m->kv_dev_valid[layer]=0; + if(!m->kv_dev_L[layer]||!m->kv_dev_R[layer]) return 0; + } + int v=m->kv_dev_valid[layer]; + if(vkv_dev_L[layer]+(size_t)v*kvl, + coli_kv_row(m->kv->Lc[layer],v,kvl),(size_t)(upto-v)*kvl*4)|| + !coli_cuda_pipe_upload(dev,m->kv_dev_R[layer]+(size_t)v*R, + coli_kv_row(m->kv->Rc[layer],v,R),(size_t)(upto-v)*R*4)) return 0; + m->kv_dev_valid[layer]=upto; + } + return 1; +} + +/* Inc.1a — catena attention residente sul device del layer / attention chain + * resident on the layer home device. Proiezioni q/kv, norme, RoPE, batch + * attention e o_proj girano sulla scheda di kv_b; scaricano solo out [S,D], + * i nuovi record KV [S,kvl+R] e nulla altro. Ritorna 0 su qualsiasi errore: + * il chiamante riesegue il percorso CPU (idempotente). */ +static int attn_pipe_prefill(Model *m, Layer *l, int layer, const float *x, int x_is_dev, + int S, int pos_base, float *out, float *out_dev){ + Cfg *c=&m->c; int H=c->n_heads, D=c->hidden, qh=c->qk_head; + int kvl=c->kv_lora, R=c->qk_rope, ql=c->q_lora; + int dev=l->kv_b.cuda_device; + if(l->q_a.cuda_device!=dev||l->q_b.cuda_device!=dev|| + l->kv_a.cuda_device!=dev||l->o.cuda_device!=dev) return 0; + int st0=m->kv_start[layer], T=pos_base+S-st0, old=pos_base-st0; + if(T8192) return 0; + double t0=now_s(); + size_t xb=(size_t)S*D*4, qrb=(size_t)S*ql*4, qb=(size_t)S*H*qh*4; + size_t cb=(size_t)S*(kvl+R)*4, lb=(size_t)T*kvl*4, rb=(size_t)T*R*4; + float *chost=NULL; int ok=0; + /* scratch persistenti (slot fissi per device): zero churn di cudaMalloc */ + float *xd =x_is_dev?(float*)x:coli_cuda_pipe_scratch(dev,0,xb); + float *qrd=coli_cuda_pipe_scratch(dev,1,qrb); + float *qd =coli_cuda_pipe_scratch(dev,2,qb), *cd =coli_cuda_pipe_scratch(dev,3,cb); + float *ld_=coli_cuda_pipe_scratch(dev,4,lb), *rd =coli_cuda_pipe_scratch(dev,5,rb); + float *w1 =coli_cuda_pipe_scratch(dev,6,(size_t)ql*4); + float *w2 =coli_cuda_pipe_scratch(dev,7,(size_t)kvl*4); + chost=(float*)malloc(cb); + if(!xd||!qrd||!qd||!cd||!ld_||!rd||!w1||!w2||!chost) goto done; + if((!x_is_dev&&!coli_cuda_pipe_upload(dev,xd,x,xb))|| + !coli_cuda_pipe_upload(dev,w1,l->q_a_ln,(size_t)ql*4)|| + !coli_cuda_pipe_upload(dev,w2,l->kv_a_ln,(size_t)kvl*4)) goto done; + /* proiezioni + norme + rope, tutto sul device */ + if(!coli_cuda_pipe_gemm(l->q_a.cuda,qrd,xd,S)) goto done; + if(!coli_cuda_pipe_rmsnorm(dev,qrd,qrd,w1,S,ql,c->eps)) goto done; + if(!coli_cuda_pipe_gemm(l->q_b.cuda,qd,qrd,S)) goto done; + if(!coli_cuda_pipe_rope_base(dev,qd,pos_base,S*H,qh,c->qk_nope,R,H,c->theta)) goto done; + if(!coli_cuda_pipe_gemm(l->kv_a.cuda,cd,xd,S)) goto done; + if(!coli_cuda_pipe_rmsnorm_s(dev,cd,cd,w2,S,kvl,c->eps,kvl+R,kvl+R)) goto done; + if(!coli_cuda_pipe_rope_base(dev,cd,pos_base,S,kvl+R,kvl,R,1,c->theta)) goto done; + /* cache latente [T,kvl] + rot [T,R] contigue: righe vecchie da host, nuove da cd */ + if(old>0){ + if(!coli_cuda_pipe_upload(dev,ld_,coli_kv_row(m->Lc[layer],st0,kvl),(size_t)old*kvl*4)|| + !coli_cuda_pipe_upload(dev,rd,coli_kv_row(m->Rc[layer],st0,R),(size_t)old*R*4)) goto done; + } + if(!coli_cuda_pipe_copy2d(dev,ld_+(size_t)old*kvl,kvl,cd,kvl+R,kvl,S)) goto done; + if(!coli_cuda_pipe_copy2d(dev,rd+(size_t)old*R,R,cd+kvl,kvl+R,R,S)) goto done; + /* KV host resta canonica: scarica i record nuovi (gia' normati+ropati) */ + if(!coli_cuda_pipe_download(dev,cd,chost,cb)) goto done; + for(int s=0;sLc[layer],pos_base+s,kvl),chost+(size_t)s*(kvl+R),kvl*4); + memcpy(coli_kv_row(m->Rc[layer],pos_base+s,R),chost+(size_t)s*(kvl+R)+kvl,R*4); + } + if(m->kv_dev_valid[layer]>pos_base) m->kv_dev_valid[layer]=pos_base; + m->t_aproj+=now_s()-t0; t0=now_s(); +#ifdef COLI_CUDA + /* Negativo (2026-07-13): P2P a stella dal device di casa serializza ~95MB/layer + * sul suo link PCIe — attention 26->41-44s. Resta opt-in per topologie NVLink. */ + if(out_dev && l->n_kv_b_shard>1 && + getenv("COLI_CUDA_PIPE_SHARD") && atoi(getenv("COLI_CUDA_PIPE_SHARD"))){ + /* head-shard nel pipeline: q gia' sul device di casa. Per ogni scheda: + * slice di q (repack strided->contiguo), broadcast latent+rope via P2P, + * score parallelo sui rispettivi head, ctx slice riportata a casa e + * ricomposta, poi o_proj residente. */ + int n=l->n_kv_b_shard, vh=c->v_head; + size_t ctxb=(size_t)S*H*vh*4; + size_t stage_one=(size_t)S*H*(size_t)(c->qk_head>vh?c->qk_head:vh)*4; + float *ctx_full=coli_cuda_pipe_scratch(dev,16,ctxb); + float *stage=coli_cuda_pipe_scratch(dev,17,stage_one*n); + int ok_sh=(ctx_full&&stage)?1:0; + if(ok_sh){ + #pragma omp parallel for schedule(static) reduction(&:ok_sh) + for(int d2=0;d2shard_hn[d2], h0=l->shard_h0[d2]; + int sdev=coli_cuda_tensor_device(l->kv_b_shard[d2]); + float *st=stage+(size_t)d2*(stage_one/4); + size_t qsb=(size_t)S*hn*qh*4, csb=(size_t)S*hn*vh*4; + float *qs_r=coli_cuda_pipe_scratch(sdev,18,qsb); + float *ld_r=coli_cuda_pipe_scratch(sdev,19,(size_t)T*kvl*4); + float *rr_r=coli_cuda_pipe_scratch(sdev,20,(size_t)T*R*4); + float *cx_r=coli_cuda_pipe_scratch(sdev,21,csb); + int okd=qs_r&&ld_r&&rr_r&&cx_r; + /* slice di q: [S,H,qh] -> [S,hn,qh] contigua sul device di casa */ + okd=okd&&coli_cuda_pipe_copy2d(dev,st,hn*qh,qd+(size_t)h0*qh,H*qh,hn*qh,S); + okd=okd&&coli_cuda_pipe_peer_copy(sdev,qs_r,dev,st,qsb); + okd=okd&&coli_cuda_pipe_peer_copy(sdev,ld_r,dev,ld_,(size_t)T*kvl*4); + okd=okd&&coli_cuda_pipe_peer_copy(sdev,rr_r,dev,rd,(size_t)T*R*4); + okd=okd&&coli_cuda_attention_absorb_batch_dev(l->kv_b_shard[d2],cx_r,qs_r,ld_r,rr_r, + S,hn,c->qk_nope,R,vh,kvl,T,c->attn_scale); + okd=okd&&coli_cuda_pipe_peer_copy(dev,st,sdev,cx_r,csb); + okd=okd&&coli_cuda_pipe_copy2d(dev,ctx_full+(size_t)h0*vh,H*vh,st,hn*vh,hn*vh,S); + ok_sh&=okd; + } + } + if(ok_sh){ + ok=coli_cuda_pipe_gemm(l->o.cuda,out_dev,ctx_full,S)&&coli_cuda_pipe_sync(dev); + } else ok=0; + if(!ok) + ok=coli_cuda_attention_project_batch_dev_out(l->kv_b.cuda,l->o.cuda,out_dev,qd,ld_,rd, + S,H,c->qk_nope,R,c->v_head,kvl,T,c->attn_scale); + } else +#endif + ok=out_dev?coli_cuda_attention_project_batch_dev_out(l->kv_b.cuda,l->o.cuda,out_dev,qd,ld_,rd, + S,H,c->qk_nope,R,c->v_head,kvl,T,c->attn_scale) + :coli_cuda_attention_project_batch_dev(l->kv_b.cuda,l->o.cuda,out,qd,ld_,rd, + S,H,c->qk_nope,R,c->v_head,kvl,T,c->attn_scale); + m->t_acore+=now_s()-t0; +done: + free(chost); /* gli scratch device restano al contesto */ + return ok; +} +#endif + static void attention_rows(Model *m, Layer *l, int layer, float *x, int S, int pos_base, KVState *const *kvs, const int *positions, float *out){ Cfg *c=&m->c; int H=c->n_heads, D=c->hidden, qh=c->qk_head, vh=c->v_head; @@ -1631,12 +1817,24 @@ static void attention_rows(Model *m, Layer *l, int layer, float *x, int S, int p * batch-union. matmul_qt_ex(...,0) keeps them on the EXACT int4 kernel: letting S>1 pull * them into IDOT is much faster but costs ~12% perplexity (measured). Batching alone is * bit-identical to upstream; the kernel switch is not. */ - matmul_qt_ex(QR, x, &l->q_a, S, 0); - for(int s=0;sq_lora; - rmsnorm(qr, qr, l->q_a_ln, c->q_lora, c->eps); } /* q_b legge il residuo NORMATO */ - matmul_qt_ex(Q, QR, &l->q_b, S, 0); - matmul_qt_ex(comp, x, &l->kv_a, S, 0); - for(int s=0;s=8&&layern_layers&&g_cuda_enabled&&c->kv_lora<=512&& + !(m->has_dsa&&pos_base+S>c->index_topk)&& + l->q_a.cuda_eligible&&l->q_b.cuda_eligible&&l->kv_a.cuda_eligible&& + l->kv_b.cuda_eligible&&l->o.cuda_eligible&& + qt_cuda_upload(&l->q_a)&&qt_cuda_upload(&l->q_b)&&qt_cuda_upload(&l->kv_a)&& + qt_cuda_upload(&l->kv_b)&&qt_cuda_upload(&l->o)) + pipe_done=attn_pipe_prefill(m,l,layer,x,0,S,pos_base,out,NULL); +#endif + if(!pipe_done){ + matmul_qt_ex(QR, x, &l->q_a, S, 0); + for(int s=0;sq_lora; + rmsnorm(qr, qr, l->q_a_ln, c->q_lora, c->eps); } /* q_b legge il residuo NORMATO */ + matmul_qt_ex(Q, QR, &l->q_b, S, 0); + matmul_qt_ex(comp, x, &l->kv_a, S, 0); + } + if(!pipe_done) for(int s=0;skv; int pos=positions?positions[s]:pos_base+s; float *qfull=Q+(int64_t)s*H*qh; @@ -1644,6 +1842,10 @@ static void attention_rows(Model *m, Layer *l, int layer, float *x, int S, int p const float *cs=comp+(int64_t)s*cw; float *Ldst=coli_kv_row(ks->Lc[layer],pos,c->kv_lora); float *Rdst=coli_kv_row(ks->Rc[layer],pos,c->qk_rope); +#ifdef COLI_CUDA + if(ks==m->kv&&m->kv_dev_valid&&layer<=c->n_layers&&m->kv_dev_valid[layer]>pos) + m->kv_dev_valid[layer]=pos; /* riga riscritta: l'ombra si accorcia */ +#endif memcpy(Ldst, cs, c->kv_lora*sizeof(float)); rmsnorm(Ldst, Ldst, l->kv_a_ln, c->kv_lora, c->eps); /* latente normato */ memcpy(Rdst, cs+c->kv_lora, c->qk_rope*sizeof(float)); @@ -1720,7 +1922,17 @@ static void attention_rows(Model *m, Layer *l, int layer, float *x, int S, int p * k/v per ogni token del contesto. Per linearita': * q·k_nope_t = (W_K^hT q_nope)·L_t ctx^h = W_V^h (Σ_t a_t L_t) * costo per step ~O(T·kv_lora) invece di O(T·H·(nope+vh)) del matmul kvb_all. */ - int absorb = kvs || g_absorb==1 || (g_absorb<0 && S<=4); + if(pipe_done){ + free(ctx); free(Q); free(QR); free(comp); + m->t_attn += now_s()-ta0; + return; + } + int cuda_absorb=0; +#ifdef COLI_CUDA + cuda_absorb=layern_layers&&!kvs&&g_cuda_enabled&&getenv("COLI_CUDA_ATTN")&& + atoi(getenv("COLI_CUDA_ATTN"))&&c->kv_lora<=512; +#endif + int absorb = kvs || g_absorb==1 || (g_absorb<0 && S<=4) || cuda_absorb; if(absorb && c->kv_lora<=512){ m->t_aproj+=now_s()-ta0; double tac=now_s(); int kvl=c->kv_lora, r0v=c->qk_nope; /* offset righe V dentro il blocco di testa */ @@ -1739,19 +1951,48 @@ static void attention_rows(Model *m, Layer *l, int layer, float *x, int S, int p if(nt>sc_cap) sc_cap=nt; } float *sc_all = falloc((int64_t)omp_get_max_threads()*sc_cap); - int cuda_core=0; + int cuda_core=0,cuda_projected=0; #ifdef COLI_CUDA - if(S<=4&&g_cuda_enabled&&getenv("COLI_CUDA_ATTN")&&atoi(getenv("COLI_CUDA_ATTN"))&& + if(cuda_absorb&&l->n_kv_b_shard>1){ + int n=l->n_kv_b_shard,st0=m->kv_start[layer],nt=pos_base+S-st0,ok=1; + float *qs=falloc((int64_t)S*H*qh),*cs=falloc((int64_t)S*H*vh); + for(int d=0;dshard_h0[d]*S*qh+(int64_t)s*l->shard_hn[d]*qh, + Q+((int64_t)s*H+l->shard_h0[d])*qh,(size_t)l->shard_hn[d]*qh*sizeof(float)); + #pragma omp parallel for schedule(static) reduction(&:ok) + for(int d=0;dkv_b_shard[d], + cs+(int64_t)l->shard_h0[d]*S*vh,qs+(int64_t)l->shard_h0[d]*S*qh, + coli_kv_row(m->Lc[layer],st0,kvl),coli_kv_row(m->Rc[layer],st0,c->qk_rope), + S,l->shard_hn[d],c->qk_nope,c->qk_rope,vh,kvl,nt,c->attn_scale); + if(ok)for(int d=0;dshard_h0[d])*vh, + cs+(int64_t)l->shard_h0[d]*S*vh+(int64_t)s*l->shard_hn[d]*vh, + (size_t)l->shard_hn[d]*vh*sizeof(float)); + free(qs);free(cs);cuda_core=ok; + } else if(cuda_absorb&&l->kv_b.cuda_eligible&&l->o.cuda_eligible&& + qt_cuda_upload(&l->kv_b)&&qt_cuda_upload(&l->o)){ + int st0=m->kv_start[layer],nt=pos_base+S-st0; + cuda_core=cuda_projected=coli_cuda_attention_project_batch(l->kv_b.cuda,l->o.cuda,out,Q, + coli_kv_row(m->Lc[layer],st0,kvl),coli_kv_row(m->Rc[layer],st0,c->qk_rope), + S,H,c->qk_nope,c->qk_rope,vh,kvl,nt,c->attn_scale); + } else if(S<=4&&g_cuda_enabled&&getenv("COLI_CUDA_ATTN")&&atoi(getenv("COLI_CUDA_ATTN"))&& l->kv_b.cuda_eligible&&qt_cuda_upload(&l->kv_b)){ cuda_core=1; for(int s=0;skv;int pos=positions?positions[s]:pos_base+s; int st0=ks->kv_start[layer],nt=pos+1-st0; if(dnsel&&dnsel[s]>0){cuda_core=0;break;} - cuda_core=coli_cuda_attention_absorb(l->kv_b.cuda,ctx+(int64_t)s*H*vh, - Q+(int64_t)s*H*qh,coli_kv_row(ks->Lc[layer],st0,kvl), - coli_kv_row(ks->Rc[layer],st0,c->qk_rope),H,c->qk_nope,c->qk_rope, - vh,kvl,nt,c->attn_scale); + cuda_core=0; + if(g_cuda_pipe&&ks==m->kv&&layern_layers&&kv_dev_sync(m,l,layer,pos+1)) + cuda_core=coli_cuda_attention_absorb_kvdev(l->kv_b.cuda,ctx+(int64_t)s*H*vh, + Q+(int64_t)s*H*qh,m->kv_dev_L[layer]+(size_t)st0*kvl, + m->kv_dev_R[layer]+(size_t)st0*c->qk_rope,H,c->qk_nope,c->qk_rope, + vh,kvl,nt,c->attn_scale); + if(!cuda_core) + cuda_core=coli_cuda_attention_absorb(l->kv_b.cuda,ctx+(int64_t)s*H*vh, + Q+(int64_t)s*H*qh,coli_kv_row(ks->Lc[layer],st0,kvl), + coli_kv_row(ks->Rc[layer],st0,c->qk_rope),H,c->qk_nope,c->qk_rope, + vh,kvl,nt,c->attn_scale); } } #endif @@ -1786,7 +2027,7 @@ static void attention_rows(Model *m, Layer *l, int layer, float *x, int S, int p } } m->t_acore+=now_s()-tac; double tao=now_s(); - matmul_qt(out, ctx, &l->o, S); m->t_aout+=now_s()-tao; + if(!cuda_projected){matmul_qt(out, ctx, &l->o, S);} m->t_aout+=now_s()-tao; free(ctx); free(Q); free(QR); free(comp); free(sc_all); m->t_attn += now_s()-ta0; return; @@ -1840,7 +2081,7 @@ static void attention(Model *m, Layer *l, int layer, float *x, int S, int pos_ba * una volta sola e moltiplicato per tutte le posizioni che lo usano (pesi letti 1 volta); * lo shared expert e' un unico matmul a S righe. Per posizione l'accumulo resta * nell'ordine (routed nel loro ordine di union, poi shared). */ -static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out){ +static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out, int with_shared){ if(g_pilot_real){ /* barriera cross-layer: prendi possesso di QUESTO layer e aspetta * l'eventuale load-pilota in volo sullo stesso layer (dopodiche' il * worker droppa ogni nuovo load <= layer -> ecache[layer] e' stabile @@ -1855,11 +2096,14 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out){ } } Cfg *c=&m->c; int D=c->hidden, E=c->n_experts, K=c->topk, I=c->moe_inter; - float *logit=falloc(E), *choice=falloc(E); + float *choice=falloc(E); int sI=c->moe_inter*c->n_shared; /* ---- FASE A: routing di tutte le S posizioni ---- */ int *idxs=malloc((size_t)S*K*sizeof(int)); float *ws=malloc((size_t)S*K*sizeof(float)); int *keff=malloc(S*sizeof(int)); + /* router in UN matmul batch: stessa matematica, via le S chiamate S=1 */ + float *logits_all=falloc((int64_t)S*E); + int pre_routed=0; (void)pre_routed; #ifdef COLI_METAL if(g_pre_idx){ /* routing gia' calcolata dal layer CB (GPU) */ memcpy(idxs,g_pre_idx,(size_t)S*K*sizeof(int)); @@ -1874,11 +2118,13 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out){ } for(int d=0;drouter, S, D, E); + if(!pre_routed) for(int s=0;srouter, 1, D, E); + float *logit=logits_all+(int64_t)s*E; for(int e=0;erouter_bias[e]; } int *idx=idxs+(int64_t)s*K; float *w=ws+(int64_t)s*K; int Ksel = g_topk>0 ? (g_topkws[q]; m->ws[q]=tmp; dst->used=(uint64_t)__atomic_add_fetch(&m->eclock,1,__ATOMIC_RELAXED); } } } - /* ---- FASE E: shared expert, un matmul a S righe (skipped se fuso nel blocco GPU) ---- */ - float *sg=falloc((int64_t)S*sI), *su=falloc((int64_t)S*sI); + /* ---- FASE E: shared expert (PIPE2: gia' sul device; Metal CB: gia' sommata) ---- */ + if(!with_shared) goto shared_done; + { + float *sg=NULL,*su=NULL;int shared_cuda=0; #ifdef COLI_METAL if(g_pre_sh){ for(int64_t z=0;z<(int64_t)S*D;z++) out[z]+=g_pre_sh[z]; shared_on_gpu=1; } + if(shared_on_gpu) shared_cuda=2; /* gia' sommato in out: salta calcolo e add */ #endif - if(!shared_on_gpu){ +#ifdef COLI_CUDA + int shared_min=getenv("COLI_CUDA_SHARED_W4A16_MIN_ROWS")? + atoi(getenv("COLI_CUDA_SHARED_W4A16_MIN_ROWS")):32; + if(shared_min<16)shared_min=16; + if(shared_cuda==0&&S>=shared_min&&!l->shared_w4a16_failed&&!omp_in_parallel()&&g_cuda_enabled&& + l->sh_gate.fmt==2&&l->sh_up.fmt==2&&l->sh_down.fmt==2&& + getenv("COLI_CUDA_SHARED_W4A16")&&atoi(getenv("COLI_CUDA_SHARED_W4A16"))&& + qt_cuda_upload(&l->sh_gate)&&qt_cuda_upload(&l->sh_up)&&qt_cuda_upload(&l->sh_down)){ + shared_cuda=coli_cuda_shared_mlp_w4a16(l->sh_gate.cuda,l->sh_up.cuda, + l->sh_down.cuda,hh,x,S); + if(!shared_cuda)l->shared_w4a16_failed=1; + } +#endif + if(!shared_cuda){ + sg=falloc((int64_t)S*sI);su=falloc((int64_t)S*sI); matmul_qt(sg, x, &l->sh_gate, S); matmul_qt(su, x, &l->sh_up, S); for(int64_t z=0;z<(int64_t)S*sI;z++) sg[z]=siluf(sg[z])*su[z]; matmul_qt(hh, sg, &l->sh_down, S); - for(int64_t z=0;z<(int64_t)S*D;z++) out[z]+=hh[z]; } - free(logit); free(choice); free(idxs); free(ws); free(keff); free(uniq); - free(xg); free(gg); free(uu); free(hh); free(rows); free(rw); free(sg); free(su); + if(shared_cuda!=2) for(int64_t z=0;z<(int64_t)S*D;z++) out[z]+=hh[z]; + free(sg); free(su); + } +shared_done: + free(logits_all); free(choice); free(idxs); free(ws); free(keff); free(uniq); + free(xg); free(gg); free(uu); free(hh); free(rows); free(rw); #ifdef COLI_CUDA - free(group_x); free(group_y); free(group_row); free(group_weight); + free(group_x);free(group_y); + free(group_row); free(group_weight); #endif } @@ -2393,6 +2663,65 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S){ } /* forward di UN layer (usato dai 78 principali e dal layer MTP) */ +#ifdef COLI_CUDA +/* Inc.2a — intero layer SPARSO residente sul device del layer. x_dev entra e resta; + * lasciano il device solo: nrm post-attention (router + expert CPU + gather dei + * gruppi), i nuovi record KV, e la nrm pre-attention sui layer con indexer DSA. + * Ritorna 0 su errore: il chiamante ripristina lo snapshot e rifa' il layer su CPU. */ +static int pipe_layer_sparse(Model *m, Layer *l, int li, float *x_dev, int S, int pos_base, + float *nrm_host, float *out_host){ + Cfg *c=&m->c; int D=c->hidden, dev=l->kv_b.cuda_device; + int sI=c->moe_inter*c->n_shared; + size_t xb=(size_t)S*D*4; + if(!l->sh_gate.cuda_eligible||!l->sh_up.cuda_eligible||!l->sh_down.cuda_eligible|| + !qt_cuda_upload(&l->sh_gate)||!qt_cuda_upload(&l->sh_up)||!qt_cuda_upload(&l->sh_down)|| + l->sh_gate.cuda_device!=dev||l->sh_up.cuda_device!=dev||l->sh_down.cuda_device!=dev) return 0; + float *w_in =coli_cuda_pipe_scratch(dev,8,(size_t)D*4); + float *w_post=coli_cuda_pipe_scratch(dev,9,(size_t)D*4); + float *nrm_d=coli_cuda_pipe_scratch(dev,10,xb); + float *y_d =coli_cuda_pipe_scratch(dev,11,xb); + float *sg_d =coli_cuda_pipe_scratch(dev,12,(size_t)S*sI*4); + float *su_d =coli_cuda_pipe_scratch(dev,13,(size_t)S*sI*4); + float *snap =coli_cuda_pipe_scratch(dev,14,xb); + if(!w_in||!w_post||!nrm_d||!y_d||!sg_d||!su_d||!snap) return 0; + if(!coli_cuda_pipe_peer_copy(dev,snap,dev,x_dev,xb)) return 0; /* snapshot per il fallback */ + if(!coli_cuda_pipe_upload(dev,w_in,l->in_ln,(size_t)D*4)|| + !coli_cuda_pipe_upload(dev,w_post,l->post_ln,(size_t)D*4)) return 0; + double ta=now_s(); + if(!coli_cuda_pipe_rmsnorm(dev,nrm_d,x_dev,w_in,S,D,c->eps)) return 0; + /* DSA: i layer con indexer FULL cachano k_idx dalla nrm pre-attention (CPU, piccolo) */ + if(m->has_dsa && lin_layers && m->kv_start[li]==0 && c->idx_type[li]){ + if(!coli_cuda_pipe_download(dev,nrm_d,nrm_host,xb)) return 0; + int nh=c->index_nh, hd=c->index_hd; (void)nh; + for(int s=0;skv->Ic[li],pos,hd); + matmul_qt(kd, nrm_host+(int64_t)s*D, &m->ix_wk[li], 1); + layernorm(kd, m->ix_knw[li], m->ix_knb[li], hd, 1e-6f); + rope_interleave(kd, pos, c); + } + } + if(!attn_pipe_prefill(m,l,li,nrm_d,1,S,pos_base,NULL,y_d)) return 0; + if(!coli_cuda_pipe_add(dev,x_dev,y_d,(size_t)S*D)) return 0; /* prima mutazione */ + if(!coli_cuda_pipe_rmsnorm(dev,nrm_d,x_dev,w_post,S,D,c->eps)) return 0; + if(!coli_cuda_pipe_download(dev,nrm_d,nrm_host,xb)) return 0; + m->t_attn+=now_s()-ta; + /* expert routed su CPU/gruppi GPU come oggi (shared saltata: la fa il device) */ + moe(m,l,li,nrm_host,S,out_host,0); + double te=now_s(); + if(!coli_cuda_pipe_upload(dev,y_d,out_host,xb)) return 0; + if(!coli_cuda_pipe_add(dev,x_dev,y_d,(size_t)S*D)) return 0; + if(!coli_cuda_pipe_gemm(l->sh_gate.cuda,sg_d,nrm_d,S)) return 0; + if(!coli_cuda_pipe_gemm(l->sh_up.cuda,su_d,nrm_d,S)) return 0; + if(!coli_cuda_pipe_silu_mul(dev,sg_d,su_d,(size_t)S*sI)) return 0; + if(!coli_cuda_pipe_gemm(l->sh_down.cuda,y_d,sg_d,S)) return 0; + if(!coli_cuda_pipe_add(dev,x_dev,y_d,(size_t)S*D)) return 0; + if(!coli_cuda_pipe_sync(dev)) return 0; + m->t_emm+=now_s()-te; + return 1; +} +#endif + static void layer_forward_rows(Model *m, Layer *l, int li, float *x, int S, int pos_base, KVState *const *kvs, const int *positions, float *nrm, float *tmp){ Cfg *c=&m->c; int D=c->hidden; @@ -2459,7 +2788,7 @@ static void layer_forward_rows(Model *m, Layer *l, int li, float *x, int S, int if(g_pilot && S<=8 && li+1n_layers && m->L[li+1].sparse) pilot_prefetch(m,li+1,x,S); if(g_looka && S==1 && li+1n_layers && m->L[li+1].sparse) la_predict(m,li+1,x,1); for(int s=0;spost_ln, D, c->eps); - if(l->sparse) moe(m,l,li,nrm,S,tmp); else dense_mlp(l,nrm,S,D,c->dense_inter,tmp); + if(l->sparse) moe(m,l,li,nrm,S,tmp,1); else dense_mlp(l,nrm,S,D,c->dense_inter,tmp); for(int64_t j=0;j<(int64_t)S*D;j++) x[j]+=tmp[j]; } static void layer_forward(Model *m, Layer *l, int li, float *x, int S, int pos_base, float *nrm, float *tmp){ @@ -2474,13 +2803,53 @@ static void layers_forward_rows(Model *m, float *x, int S, int pos_base, pthread_mutex_unlock(&g_pilot_mx); } float *nrm=falloc((int64_t)S*D), *tmp=falloc((int64_t)S*D); +#ifdef COLI_CUDA + /* PIPE2 (Inc.2a): il residuo resta sul device del layer, saltando tra le schede + * ai confini di layer. x host diventa STALE finche' la residenza e' attiva. */ + float *x_dev=NULL; int x_dev_on=-1; + size_t xb=(size_t)S*(size_t)D*4; + int pipe2 = g_cuda_pipe>=2 && !kvs && S>=8 && g_cuda_enabled && c->kv_lora<=512 && + !(m->has_dsa && pos_base+S>c->index_topk); +#endif for(int i=0;in_layers;i++){ /* progresso su stderr per i batch grossi (prefill): il primo byte di risposta * puo' arrivare dopo MINUTI di streaming — al buio sembra un blocco. */ if(S>=8 && (i%4==0 || i==c->n_layers-1)) fprintf(stderr,"[prefill] layer %d/%d · %d token\n", i+1, c->n_layers, S); +#ifdef COLI_CUDA + Layer *l=&m->L[i]; + if(pipe2 && l->sparse && in_layers && + l->q_a.cuda_eligible&&l->q_b.cuda_eligible&&l->kv_a.cuda_eligible&& + l->kv_b.cuda_eligible&&l->o.cuda_eligible&& + qt_cuda_upload(&l->q_a)&&qt_cuda_upload(&l->q_b)&&qt_cuda_upload(&l->kv_a)&& + qt_cuda_upload(&l->kv_b)&&qt_cuda_upload(&l->o)&& + l->q_a.cuda_device==l->kv_b.cuda_device&&l->q_b.cuda_device==l->kv_b.cuda_device&& + l->kv_a.cuda_device==l->kv_b.cuda_device&&l->o.cuda_device==l->kv_b.cuda_device){ + int dev=l->kv_b.cuda_device, ok=1; + float *dst=coli_cuda_pipe_scratch(dev,15,xb); + if(dst){ + if(x_dev_on<0) ok=coli_cuda_pipe_upload(dev,dst,x,xb); + else if(x_dev_on!=dev){ ok=coli_cuda_pipe_peer_copy(dev,dst,x_dev_on,x_dev,xb); } + else dst=x_dev; + if(ok){ + x_dev=dst; x_dev_on=dev; + if(pipe_layer_sparse(m,l,i,x_dev,S,pos_base,nrm,tmp)) continue; + /* fallback: snapshot -> host, layer rifatto sul percorso CPU */ + coli_cuda_pipe_peer_copy(dev,x_dev,dev,coli_cuda_pipe_scratch(dev,14,xb),xb); + coli_cuda_pipe_download(dev,x_dev,x,xb); + x_dev_on=-1; + }else x_dev_on=-1; + } + } else if(x_dev_on>=0){ /* layer fuori pipe: il residuo torna a casa */ + coli_cuda_pipe_download(x_dev_on,x_dev,x,xb); + x_dev_on=-1; + } +#endif layer_forward_rows(m,&m->L[i],i,x,S,pos_base,kvs,positions,nrm,tmp); } +#ifdef COLI_CUDA + if(x_dev_on>=0) coli_cuda_pipe_download(x_dev_on,x_dev,x,xb); +#endif free(nrm); free(tmp); } static void layers_forward(Model *m, float *x, int S, int pos_base){ @@ -2490,6 +2859,14 @@ static void layers_forward(Model *m, float *x, int S, int pos_base){ static void kv_alloc(Model *m, int max_t){ Cfg *c=&m->c; KVState *k=m->kv; +#ifdef COLI_CUDA + if(m->kv_dev_L) for(int i=0;in_layers+1;i++){ /* dimensioni cambiate: ombra da rifare */ + if(m->kv_dev_L[i]){ coli_cuda_pipe_free(m->L[in_layers?i:0].kv_b.cuda_device,m->kv_dev_L[i]); m->kv_dev_L[i]=NULL; } + if(m->kv_dev_R[i]){ coli_cuda_pipe_free(m->L[in_layers?i:0].kv_b.cuda_device,m->kv_dev_R[i]); m->kv_dev_R[i]=NULL; } + m->kv_dev_valid[i]=0; + } +#endif + if(k->Lc){ for(int i=0;in_layers+1;i++){ free(k->Lc[i]); free(k->Rc[i]); } free(k->Lc); free(k->Rc); } if(k->Lc){ for(int i=0;in_layers+1;i++){ #ifdef COLI_METAL if(g_metal_enabled){ coli_metal_unregister(k->Lc[i]); coli_metal_unregister(k->Rc[i]); } @@ -2522,6 +2899,8 @@ static void kv_alloc(Model *m, int max_t){ } static void kv_bind(Model *m, KVState *k){ + if(m->kv!=k && m->kv_dev_valid) /* ombra legata al KVState corrente */ + for(int i=0;ic.n_layers+1;i++) m->kv_dev_valid[i]=0; m->kv=k; m->Lc=k->Lc; m->Rc=k->Rc; m->Ic=k->Ic; m->max_t=k->max_t; m->kv_start=k->kv_start; } @@ -3034,6 +3413,7 @@ static void run_text(Model *m, const char *snap, const char *prompt, int ngen){ repin_pass_limit(m,limit); /* prompt routing seeds every GPU layer */ } prefill_t=now_s()-prefill_t; + printf("PROFILO PREFILL (%.2fs):\n",prefill_t); profile_print(m,prefill_t); m->hits=m->miss=m->ereq=m->gpu_expert_calls=0; m->n_emit=m->n_fw=0; g_last_repin=0; @@ -3329,6 +3709,7 @@ static void mux_done(Model *m, ServeCtx *sc, ServeReq *r){ double dt=now_s()-r->started; if(dt<1e-6) dt=1e-6; double dh=(double)(m->hits-r->hits0), dm=(double)(m->miss-r->miss0); hwinfo_emit(m); + usage_save(m); /* la cache che impara non deve aspettare l'uscita */ tiers_emit(m); emap_emit(m); hits_emit(m); @@ -4204,6 +4585,7 @@ int main(int argc, char **argv){ if(!g_cuda_enabled){ fprintf(stderr,"[CUDA] requested backend is unavailable\n"); return 2; } } g_cuda_dense=getenv("CUDA_DENSE")?atoi(getenv("CUDA_DENSE")):0; + g_cuda_pipe=getenv("COLI_CUDA_PIPE")?atoi(getenv("COLI_CUDA_PIPE")):0; const char *cuda_expert=getenv("CUDA_EXPERT_GB"); g_cuda_expert_auto=cuda_expert&&!strcmp(cuda_expert,"auto"); g_cuda_expert_gb=cuda_expert&&!g_cuda_expert_auto?atof(cuda_expert):0; diff --git a/c/tests/test_i4_acc512 b/c/tests/test_compat_direct similarity index 72% rename from c/tests/test_i4_acc512 rename to c/tests/test_compat_direct index 76f68a3e70a9375bd3b0c6f710f7af09dd687e68..074a7baf262a18295a974bd917c31d4e8f22ee46 100755 GIT binary patch delta 870 zcmX?6ccE^A2IGW{n&OO{GK>shATwEzQFwC<7%_q^7p=4oiSxrez%ran18w;-`ZAtkdYHMvBgB(=Dt*h--|JF}o5HAO)qz(3eC zLLnouSRpU5B(p44!9U)`Gsx9BL=$8SqcpR%0gz?|;ydsE|4#wZe}HrkkmmXD|Njyo zEd!+Y0BIW_?E|DmfEWZAtAZF9D+DIjD+n^am^@KIoUvf?N(FVsjL8>)>;xbw%nC9m zVl$)SdY;WYbYCzrYD`{eATDVP6lG!%VAvo4G6w-nCtn20dH@ALvI>F-84xROGNYlm zWEPOc$RNgWz!p_@;$%ah>|C&{D8mjk**ze8p|+m}s}W(4P((EWtY-27LkZ8DU=`vF zC(ulJ1lAzT0E-!z{;y#55)3=+VImNkd$OXDII9LQiWMerWD;g9pB!i;&gcRXWelC% zXe6E*1C$eD5@g6h^J@}NT#!ka!9xMn#8RNR0D~k0g9EB~6Aty$fZ`ClmrfQm7MI)v zQpX?!6olw!ff_S$vLjHo8Yn9SG`|5FFKSTPgOdw^vS*>P2~b&SsO+W53ysB1?}1Fl z;^I#@#Mzl(E>oP$Xd=#NGg;C^pBIvr;~{BzbD&8%^W+0eESpc5C$KO&PS&*1=Y)m5 h%j85GbIu9{h$AXCFSK!IVsx7P($1W*XtJWcIsij2o1p*z delta 854 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 diff --git a/c/tests/test_idot b/c/tests/test_idot deleted file mode 100755 index bab981f1a6ada6fb0c07bc449ef2f2a0a4c9ef93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 239888 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 z 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�++g`cKRSuLszuJ3qmYyu;^FBzla&6>P%h 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 diff --git a/c/tests/test_pipe_cuda.cu b/c/tests/test_pipe_cuda.cu new file mode 100644 index 0000000..f20a9e5 --- /dev/null +++ b/c/tests/test_pipe_cuda.cu @@ -0,0 +1,137 @@ +/* Unit tests for the resident-pipeline primitives (Inc.0). + * Each primitive is checked against the exact CPU math from glm.c. + * Build: nvcc backend_cuda.cu tests/test_pipe_cuda.cu -o pipe_test && ./pipe_test */ +#include +#include +#include +#include +#include +#include "../backend_cuda.h" + +static uint64_t rng=0x9E3779B97F4A7C15ULL; +static float rndf(void){ rng^=rng<<13; rng^=rng>>7; rng^=rng<<17; + return (float)((double)(rng>>11)/9007199254740992.0*2.0-1.0); } + +static void ref_rmsnorm(float *out,const float *x,const float *w,int D,float eps){ + double ms=0; for(int i=0;i1.f?fabsf(b[i]):1.f; + if(d/m>worst) worst=d/m; + } + printf(" %-14s max rel %.3e %s\n",what,worst,worst<=tol?"ok":"FAIL"); + return worst<=tol; +} + +int main(void){ + int dev0=0; + if(!coli_cuda_init(&dev0,1)){ puts("pipe tests: skipped (no CUDA device)"); return 0; } + int ok=1; + enum { S=7, D=6144, I=2048, R=64, H=4, QH=192 }; + + /* rmsnorm */ + { + float *x=(float*)malloc((size_t)S*D*4), *w=(float*)malloc(D*4); + float *got=(float*)malloc((size_t)S*D*4), *ref=(float*)malloc((size_t)S*D*4); + for(size_t i=0;i<(size_t)S*D;i++) x[i]=rndf()*3; + for(int i=0;iljS8i=U>)lWXer-vtmAoNg| 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 literal 0 HcmV?d00001 diff --git a/c/tools/bench_ux.sh b/c/tools/bench_ux.sh new file mode 100755 index 0000000..73fbc3f --- /dev/null +++ b/c/tools/bench_ux.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Unified user-experience benchmark: fixed scenarios, fixed decoding, medians. +# +# Reports, per scenario: TTFT (prefill wall), decode tok/s, and the first line +# of the generated text (drift check). Runs each scenario REPS times and prints +# every sample; judge by the median, not the best. +# +# Usage: SNAP=/path/to/model [REPS=3] [PIPE=2] tools/bench_ux.sh [engine-binary] +# The engine env (COLI_CUDA, PIN, ...) is inherited from the caller so the same +# script exercises CPU-only, CUDA and pipeline configurations. +# +# Discipline (docs/experiments/glm52-6x5090-2026-07-12.md): +# - TEMP=0 DRAFT=0 always: greedy, no speculation, one variable at a time. +# - Same binary for every configuration under comparison. +# - .coli_usage drifts placement between runs: compare medians of >=3 reps, +# or snapshot/restore the usage file around the battery. +set -u +GLM="${1:-./glm}" +REPS="${REPS:-3}" +export TEMP=0 DRAFT=0 +[ -n "${PIPE:-}" ] && export COLI_CUDA_PIPE="$PIPE" + +SHORT_PROMPT="Explain why the sky is blue in simple terms." +LONGDOC_PROMPT=$(python3 - <<'PY' +base=("蜂鸟是世界上最小的鸟类之一,主要分布在美洲大陆。它们的翅膀每秒可以扇动五十到八十次," +"使它们能够在空中悬停、倒飞,甚至垂直起降。蜂鸟的新陈代谢极快,心跳每分钟可达一千两百次," +"因此它们必须不断进食来维持能量。它们的主要食物是花蜜,同时也会捕食小型昆虫和蜘蛛来补充蛋白质。" +"蜂鸟的喙细长,适合深入花朵内部吸食花蜜,舌头呈管状,可以快速伸缩。在授粉过程中," +"蜂鸟扮演着重要的角色,许多美洲植物依赖蜂鸟传粉。") +print((base*12)+"请根据上文回答:蜂鸟的主要食物是什么?") +PY +) + +scenario(){ # name prompt ngen + local name=$1 prompt=$2 ngen=$3 + for r in $(seq 1 "$REPS"); do + local log; log=$(mktemp) + NGEN=$ngen PROMPT="$prompt" "$GLM" 64 4 4 >"$log" 2>&1 + local line; line=$(grep -aE "prefill .* decode .*tok/s" "$log" | tail -1) + local head; head=$(grep -av "^\[" "$log" | grep -avE "PROFILE|PROFILO|ATTENTION|expert|CUDA|prefill|specul|^---|TOPP|stop|Motore|caricato|prompt:|token" \ + | tail -1 | tr -d '\n' | cut -c1-48) + printf "%-10s r%d %s\n" "$name" "$r" "$line" + printf "%-10s r%d text: %s\n" "$name" "$r" "$head" + rm -f "$log" + done +} + +echo "== bench_ux: reps=$REPS pipe=${COLI_CUDA_PIPE:-unset} $(date +%F' '%T) ==" +scenario chat "$SHORT_PROMPT" 96 +scenario longdoc "$LONGDOC_PROMPT" 96 +echo "== bench_ux done $(date +%T) ==" diff --git a/c/tools/expert_atlas.py b/c/tools/expert_atlas.py new file mode 100644 index 0000000..dd0a2d6 --- /dev/null +++ b/c/tools/expert_atlas.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Expert Atlas (#175): measure per-expert topic affinity by diffing .coli_usage +across themed probe batches, served through a running colibri API server. + +Protocol per category: snapshot .coli_usage -> send probes -> snapshot again; +the delta is that category's expert-activation spectrum. One engine load total. + +Output: experts.json — for every (layer, expert): counts per category, +normalized affinity, entropy, and a "specialist" label when one topic dominates. + +Usage (server already running with the model): + python3 tools/expert_atlas.py --api http://127.0.0.1:8000 \ + --usage /path/to/model/.coli_usage --out experts.json --ngen 64 +""" +import argparse, json, math, time, urllib.request + +PROBES = { + "code": [ + "Write a Python function that parses a CSV file and returns a dict keyed by the first column.", + "Explain the difference between a mutex and a semaphore, with a C example.", + "Refactor this into idiomatic Rust: for i in range(len(xs)): total += xs[i] * 2", + ], + "math": [ + "Prove that the square root of 2 is irrational.", + "Compute the derivative of x^3 * ln(x) and explain each step.", + "A fair die is rolled 4 times. What is the probability of at least one six?", + ], + "chinese": [ + "请用中文解释一下什么是光合作用,以及它对地球生态系统的重要性。", + "把这句话翻译成中文并解释语法:The early bird catches the worm.", + "写一段关于秋天的短文,一百字左右。", + ], + "english_prose": [ + "Write a vivid paragraph describing an old lighthouse keeper watching a storm arrive.", + "Summarize the plot of Romeo and Juliet in three sentences.", + "Continue this story: The last train left the station, and Maria realized her mistake.", + ], + "science": [ + "Explain how mRNA vaccines work at the cellular level.", + "Why is the sky blue during the day but red at sunset?", + "Describe the life cycle of a massive star, from formation to supernova.", + ], + "law": [ + "Explain the difference between a patent, a trademark, and a copyright.", + "What are the key elements required to form a legally binding contract?", + "Summarize what 'due process' means in constitutional law.", + ], + "poetry": [ + "Write a short poem about a hummingbird in the style of Emily Dickinson.", + "Compose a haiku about winter rain, then explain its imagery.", + "Write four rhyming lines about the sea at night.", + ], + "structured": [ + 'Convert to JSON: name Alice, age 30, hobbies reading and chess, address 5 Oak St.', + "Write a SQL query returning the top 5 customers by total order value, with the schema you assume.", + "Write a regex that matches ISO-8601 dates and explain each part.", + ], + "translation": [ + "Translate into French, German and Spanish: 'Knowledge is the only treasure that grows when shared.'", + "Translate this Italian sentence to English and comment on nuance: 'In bocca al lupo per domani.'", + "Translate into Japanese: 'The meeting has been moved to next Tuesday afternoon.'", + ], + "casual": [ + "Hey! Any tips for staying awake during boring afternoon meetings?", + "What should I cook tonight? I have eggs, rice, tomatoes and some cheese.", + "My friend is always late. How do I tell them it bothers me without being rude?", + ], +} + + +def read_usage(path): + counts = {} + try: + with open(path) as f: + for line in f: + p = line.split() + if len(p) == 3: + counts[(int(p[0]), int(p[1]))] = int(p[2]) + except FileNotFoundError: + pass + return counts + + +def diff(after, before): + return {k: v - before.get(k, 0) for k, v in after.items() if v - before.get(k, 0) > 0} + + +def chat(api, prompt, ngen): + body = json.dumps({"model": "glm-5.2-colibri", "stream": False, "max_tokens": ngen, + "messages": [{"role": "user", "content": prompt}]}).encode() + req = urllib.request.Request(f"{api}/v1/chat/completions", data=body, + headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=600) as r: + json.load(r) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--api", default="http://127.0.0.1:8000") + ap.add_argument("--usage", required=True, help="path to the model's .coli_usage") + ap.add_argument("--out", default="experts.json") + ap.add_argument("--ngen", type=int, default=64) + a = ap.parse_args() + + spectra = {} + for cat, prompts in PROBES.items(): + before = read_usage(a.usage) + t0 = time.time() + for p in prompts: + chat(a.api, p, a.ngen) + time.sleep(2) # let the engine flush .coli_usage + spectra[cat] = diff(read_usage(a.usage), before) + total = sum(spectra[cat].values()) + print(f"[{cat}] {len(spectra[cat])} experts touched, {total} selections, {time.time()-t0:.0f}s", flush=True) + + cats = list(PROBES.keys()) + experts = {} + for cat, spec in spectra.items(): + for k, v in spec.items(): + experts.setdefault(k, {c: 0 for c in cats})[cat] = v + + atlas = {} + for (layer, eid), counts in experts.items(): + total = sum(counts.values()) + if total < 8: + continue # too few observations to characterise + aff = {c: v / total for c, v in counts.items() if v} + ent = -sum(p * math.log2(p) for p in aff.values()) + top = max(aff, key=aff.get) + label = f"specialist: {top}" if aff[top] >= 0.45 and ent < 2.2 else "generalist" + atlas[f"{layer}:{eid}"] = {"counts": counts, "affinity": {c: round(p, 3) for c, p in aff.items()}, + "entropy": round(ent, 2), "top": top, "label": label} + + spec_n = sum(1 for v in atlas.values() if v["label"].startswith("specialist")) + with open(a.out, "w") as f: + json.dump({"categories": cats, "ngen": a.ngen, "experts": atlas}, f) + print(f"\natlas: {len(atlas)} experts characterised, {spec_n} specialists -> {a.out}") + + +if __name__ == "__main__": + main() diff --git a/web/src/Brain.tsx b/web/src/Brain.tsx index ac20695..6811238 100644 --- a/web/src/Brain.tsx +++ b/web/src/Brain.tsx @@ -4,6 +4,7 @@ import { BrainCircuit, Flame, Layers } from "lucide-react" import { endpoint } from "@/lib/api" interface ExpertMap { rows: number; cols: number; map: string; hits: string; seq: number } +interface AtlasEntry { affinity: Record; entropy: number; top: string; label: string } const TIER_NAME = ["Disk", "RAM", "VRAM"] const TIER_RGB: [number, number, number][] = [[58, 71, 80], [90, 155, 216], [78, 214, 165]] @@ -26,11 +27,19 @@ export function Brain({ baseUrl, apiKey, connected }: { baseUrl: string; apiKey: const wrapRef = useRef(null) const [wrapSize, setWrapSize] = useState({ w: 1200, h: 700 }) const [data, setData] = useState(null) + const [atlas, setAtlas] = useState | null>(null) const [tip, setTip] = useState<{ x: number; y: number; row: number; col: number; tier: number; heat: number } | null>(null) const pulseRef = useRef(null) // per-expert pulse intensity 0..1 const lastSeq = useRef(0) const rafRef = useRef(0) + // load the expert atlas if published (measured topic affinity, #175) + useEffect(() => { + fetch("/experts.json").then(r => r.ok ? r.json() : null).then(d => { + if (d?.experts) setAtlas(d.experts) + }).catch(() => {}) + }, []) + // track container size for responsive cell sizing useEffect(() => { const el = wrapRef.current @@ -146,14 +155,26 @@ export function Brain({ baseUrl, apiKey, connected }: { baseUrl: string; apiKey: setTip(null)} /> {!connected &&

Connect to the engine to see the cortex.

} - {tip && data && ( + {tip && data && (() => { + const isMtp = tip.row === data.rows - 1 + const realLayer = isMtp ? 78 : tip.row + 3 + const entry = atlas?.[`${realLayer}:${tip.col}`] + return (
-
Layer row {tip.row}{tip.row === data.rows - 1 ? " (MTP)" : ""} · Expert {tip.col}
+
Layer {realLayer}{isMtp ? " (MTP)" : ""} · Expert {tip.col}
Tier: {TIER_NAME[tip.tier]}
Heat: {tip.heat === 0 ? "never routed" : `~2^${tip.heat} selections`}
-
{depthRole(tip.row, data.rows, tip.row === data.rows - 1)}
+ {entry ? <> +
+ {entry.label.startsWith("specialist") ? `⭐ Specialist: ${entry.top}` : "Generalist"} + (entropy {entry.entropy}) +
+
{Object.entries(entry.affinity).sort((a, b) => b[1] - a[1]).slice(0, 3) + .map(([c, p]) => `${c} ${Math.round(p * 100)}%`).join(" · ")}
+ :
{depthRole(tip.row, data.rows, isMtp)}
}
- )} + ) + })()} ) } diff --git a/web/src/index.css b/web/src/index.css index 59d5bc6..b428805 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -146,3 +146,8 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus- .brain-legend { gap: 8px; font-size: 10px; } .brain-tip { max-width: 220px; font-size: 10px; } } + +/* atlas hover extras */ +.brain-tip-spec { color: var(--primary); font-weight: 700; } +.brain-tip-spec small, .brain-tip-aff { color: #8b9aa3; font-weight: 400; } +.brain-tip-aff { font-size: 10px; }