~/fba-lab/lab/qwen-c/08-kernels

FBALab

Code · architecture · study mode

AboutRoadmapSpeedrun
FBALab

Study mode — no GPU required.

Interactive LLM training & inference lab.

Qwen CAboutContactTermsPrivacyCookiesCommunity

© 2026 FBA Lab

Contact · contact@bubblspace.com · +91 75061 55016

INFERENCE SIMULATION

Qwen3 Pure C — 8 · Every op a kernel

running
◷run_viz.c▸calloc on CPU, cudaMalloc on GPU◎learner⌁08-kernels$no GPU
weights
Load the model
The 4 GB weight file is mapped into memory in milliseconds. No copying — the OS pages in only what inference needs.
run_viz.c · stage 01–02
load
runtime
Run inference
Your prompt flows through 28 layers of attention and computation, one token at a time.
stage 03–04
forward
output
Generate text
The model scores all 151,936 possible next words and picks one. Then repeats.
stage 05–06
1/3
Blocks
Quick summary

These activation buffers are calloc'd in CPU RAM here; the CUDA port allocates the identical buffers in GPU VRAM with cudaMalloc — with one deliberate exception.

Full explanation below the code →

fba-lab — run_viz.c · calloc on CPU, cudaMalloc on GPUexecuting
$ run.c → calloc(p->dim, sizeof(float)) // CPU RAM$ runcu.cu→ cudaMalloc(&s->x, p->dim*4) // VRAMexception: s->logits stays calloc (CPU) for sample()
Explanation

malloc_run_state allocates every activation buffer the forward pass scribbles on: x, xb, q, k, v, the attention scratch att, and the key_cache/value_cache (lines 84–96). In run.c these are calloc — ordinary CPU heap memory — because the compute runs on the CPU.

In the CUDA port (runcu.cu) the exact same buffers are cudaMalloc'd in GPU VRAM, because the compute runs on the GPU. The rule is: *the data must live where the math runs.* If activations sat in CPU RAM while kernels ran on the GPU, every op would copy data across the PCIe bus and performance would collapse. In short: run.c keeps weights (mmap) and activations (calloc) in CPU RAM; runcu.cu keeps both in GPU VRAM.

The one exception is `s->logits` (line 94). It stays on the CPU even in the GPU port, because the sampler (sample()) runs on the CPU. So the GPU computes logits into a device buffer and copies them back to this host buffer once per token.

Why it matters

Deciding "forward() runs on the GPU" forces a decision about *everything it touches*. Once you commit the activations to VRAM, every step of the layer must also run on the GPU — you can't drop back to a CPU loop mid-layer without paying a PCIe round trip each time.

Think about

Why is s->logits allocated on the host even in the CUDA version, when every other buffer moves to the GPU?

// architecture

Live diagram

100%
run.c · CPU RAMcalloc(x, xb, q, k, v…)+ KV cachecompute on CPUruncu.cu · VRAMcudaMalloc(x, xb, q…)+ KV cachecompute on GPUexcept s->logits (host)forward() → kernelsrmsnorm<<<>>>matmul ×7<<<>>>RoPE<<<>>>attention<<<>>>SiLU<<<>>>accum<<<>>>~12 / layer× 28 layers≈ 336 / tokenCPU (host)GPU VRAM — weights resident (~2.4 GB)embedding row → 4 KBlogits ← 608 KB2 PCIe transfers / token
← 7 · GEMV, not GEMM9 · Attention & memory →
Step 8 of 10Skill: Inference Systems
01Blueprint02Load weights03Buffers04Forward pass05Tokenizer06Generate loop07GEMV, not GEMM08Every op a kernel09Attention & memory10Prefill vs decode