qwenGPU is an experimental model that compiles a SIMT GPU directly into the weights of Qwen3.5-2B. Developer Vixhal (@TheVixhal) released it on GitHub under the MIT license. The model runs real CUDA, compiled to NVIDIA PTX, inside its own forward passes. For developers, it tests one idea: exact computation inside a pretrained LLM, not beside it.
TL;DR
- Size: Qwen3.5-2B (24 layers) along with 45 compiled GPU layers, 69 in total. Context: 262,144 tokens (base model).
- Runs on: Apple silicon, an NVIDIA GPU or a CPU, with about 16 GB of free memory. No quantized build.
- Performance: Every launch in the developer’s runs was exact; text drift is a KL of about 7e-4.
- Best: The reference interpreter matched a real NVIDIA Tesla T4 on 49 of 49 kernels.
- Worst: About 10 to 15 tokens per second on Apple MPS; Qwen-written CUDA passed 4 of 5.
What is qwenGPU?
qwenGPU is Qwen3.5-2B with 45 compiled transformer layers placed in front of Qwen’s own 24. None of those layers are trained. A compiler computes every weight.
How does a GPU fit inside model weights?
The GPU is a 4-lane SIMT machine. It has 15 registers per lane, a 4-stage pipeline, a 32-block ROM and 126 memory words. It uses only Qwen3.5’s own layer types: Gated DeltaNet linear attention and SwiGLU MLPs.
The trick is the Gated DeltaNet recurrent state. In the circuit’s heads, the gate is held open and the write strength is 1. Every key is one-hot. Each update overwrites exactly 1 row, and a one-hot query reads it back. So each row works as a memory cell.
That state holds the program ROM, registers and global memory. The README lists 16 register rows per lane, with 1 that always reads zero. That matches the 15 usable registers in the X post.
The GPU supports integer arithmetic, predicates, divergent branches and loops. It also supports shared memory, atomicAdd, __syncthreads() and warp shuffles.
‘+d[1]+’
‘;d[2].forEach(function(c){h+=’‘+c[0]+’ ‘+c[1]+’‘});document.getElementById(‘qinfo’).innerHTML=h+’
‘;rs();});});
var steps=[
{t:’Host compiles the CUDA kernel to PTX, then lowers it to machine blocks.’,d:’Simplified walkthrough of the demo: dot product of a = [1, 2, 3] and b = [4, 5, 6].’,r:{},m:0},
{t:’PROG · LIVE · MEM · MEM · EXEC(0, 0)’,d:’Host writes setup records: the program blocks and the input arrays a and b go into DeltaNet state as tokens.’,r:{},m:1},
{t:’EXEC(0, 0) → VAL VAL VAL VAL’,d:’The model runs block 0 for warp 0: each lane computes its thread index i.’,r:{r0:[0,1,2,3]},m:1,f:’r0′},
{t:’EXEC(1, 0) → VAL VAL …’,d:’Block 1: predicate i < 3 switches lane 3 off. Lanes load a[i] and b[i] from global memory.’,r:{r0:[0,1,2,3],r1:[1,2,3,’-‘],r2:[4,5,6,’-‘]},m:1,f:’r2′,off:3},
{t:’EXEC(2, 0) → VAL VAL …’,d:’Block 2: each active lane multiplies a[i] by b[i].’,r:{r0:[0,1,2,3],r1:[1,2,3,’-‘],r2:[4,5,6,’-‘],r3:[4,10,18,’-‘]},m:1,f:’r3′,off:3},
{t:’EXEC(3, 0) → MEM out[0] = 32′,d:’Block 3: atomicAdd sums 4 + 10 + 18 into out[0]. The write returns as a MEM record.’,r:{r0:[0,1,2,3],r1:[1,2,3,’-‘],r2:[4,5,6,’-‘],r3:[4,10,18,’-‘]},m:2,off:3},
{t:’EXEC(0, warps) · end of launch · out = [32, 0, 0]’,d:’The host reads out from the model\’s state. In the real demo the model wrote the launch in 47 tokens, exact.’,r:{r0:[0,1,2,3],r1:[1,2,3,’-‘],r2:[4,5,6,’-‘],r3:[4,10,18,’-‘]},m:2,off:3}];
var cur=0,timer=null;
function draw(){var s=steps[cur];document.getElementById(‘qtok’).textContent=s.t;document.getElementById(‘qdesc’).textContent=s.d;document.getElementById(‘qstep’).textContent=”Step “+cur+’ of 6′;
var h=”
lane 0
lane 1
lane 2
lane 3
“;
[‘r0′,’r1′,’r2′,’r3′].forEach(function(r){h+=’
‘+r+’
‘;for(var l=0;l<4;l++){var v=s.r[r]?s.r[r][l]:’·’;var c=(s.f===r&&v!==’-‘)?’flash’:”;if(s.off===l&&cur>2)c+=’ off’;h+=’
‘+v+’
‘;}});
document.getElementById(‘qregs’).innerHTML=h;
var m=”;for(var i=0;i<32;i++){var c=””;if(s.m>=1&&i<6)c=”in”;if(s.m>=2&&i===6)c=”w”;m+=’‘;}document.getElementById(‘qmem’).innerHTML=m;rs();}
document.getElementById(‘qnext’).addEventListener(‘click’,function(){cur=(cur+1)%7;draw();});
document.getElementById(‘qreset’).addEventListener(‘click’,function(){cur=0;clearInterval(timer);timer=null;document.getElementById(‘qplay’).textContent=”Auto-play”;draw();});
document.getElementById(‘qplay’).addEventListener(‘click’,function(){var b=this;if(timer){clearInterval(timer);timer=null;b.textContent=”Auto-play”;return;}b.textContent=”Pause”;timer=setInterval(function(){cur=(cur+1)%7;draw();},1600);});
var ram=document.getElementById(‘qram’),rh=””;for(var i=0;i<32;i++)rh+=’
0
‘;ram.innerHTML=rh;var cells=ram.children,sel=5;
function pick(i){for(var j=0;j<32;j++)cells[j].classList.remove(‘key’,’read’);sel=i;cells[i].classList.add(‘key’);}
for(var k=0;k<32;k++)(function(k){cells[k].addEventListener(‘click’,function(){pick(k)})})(k);pick(5);
document.getElementById(‘qw’).addEventListener(‘click’,function(){cells[sel].textContent=Math.floor(Math.random()*99)+1;pick(sel);});
document.getElementById(‘qr’).addEventListener(‘click’,function(){for(var j=0;j<32;j++)cells[j].classList.remove(‘key’,’read’);cells[sel].classList.add(‘read’);});
function bars(){R.querySelectorAll(‘.fill’).forEach(function(f){f.style.width=”0″;setTimeout(function(){f.style.width=f.dataset.w+’%’},60)});}
draw();window.addEventListener(‘load’,rs);window.addEventListener(‘resize’,rs);setTimeout(rs,300);
})();
