Tile-based GPU kernel language & LLM inference engine
Write tiles like Triton. Set up warps and lanes like Gluon. One language.
Tiles and lanes
Whole tiles, as in Triton, and warps and lanes, as in Gluon, in one language and in one kernel. Every picture is sized from the compiler's own traces.
Tiles, like Triton
for<unroll U> kt in range(0, K, TILE_K) { var a = A[pm * TILE_M :+ TILE_M, kt :+ TILE_K] var b = B[kt :+ TILE_K, pn * TILE_N :+ TILE_N] acc += dot(a, b)}TILE_M 32, TILE_N 32, TILE_K 4, 256 threads, schematic
Lanes, like Gluon
for k in range(0, K, ctx.lanes * 4) { let at = k + ctx.lane * 4 acc += sum(W[row, at :+ 4] * x[0, at :+ 4])}let total = sum<over ctx.lane>(acc)32 lanes, 4 elements each per step, schematic
Mixed, like Phobos
var xs = x[0 :+ 1, :]if row < N { for k in range(0, K, ctx.lanes * 4) { let at = k + ctx.lane * 4 acc += sum(W[row, at :+ 4] * xs[0, at :+ 4]) }}let total = sum<over ctx.lane>(acc)4 warps share one tile of x, schematic
Step through it in the visualizerctx in the docsTiles and lanes in one kernel
One compiler, source to PTX
Seven stations between a kernel file and the card, for gemm_fp32.ph, each drawn from what the compiler holds there. The same points travel the whole way.
.ph sourceThe kernel as written: an accumulator, a k loop over two tiles, and the store.
ASTParsed and checked, 40 nodes: the for kept whole, every other statement folded to one.
graph IROps and the values between them, before any memory is placed: 31 nodes, 36 edges, one value carried round the loop.
passesStaging, pipelining, shared-memory placement and barrier elision, shown for the build without @aligned.
MLIRThe gpu, scf, vector, memref and arith dialects, 346 ops in the kernel's regions, lowered through NVVM to LLVM IR.
PTX424 instructions from LLVM's NVPTX backend for sm_75, by family.
GPULoaded and launched through CUDA: 4 CTAs of 256 threads, the grid the host picks for M = N = K = 64.
- .ph source: The kernel as written: an accumulator, a k loop over two tiles, and the store.
- AST: Parsed and checked, 40 nodes: the for kept whole, every other statement folded to one.
- graph IR: Ops and the values between them, before any memory is placed: 31 nodes, 36 edges, one value carried round the loop.
- passes: Staging, pipelining, shared-memory placement and barrier elision, shown for the build without @aligned.
- MLIR: The gpu, scf, vector, memref and arith dialects, 346 ops in the kernel's regions, lowered through NVVM to LLVM IR.
- PTX: 424 instructions from LLVM's NVPTX backend for sm_75, by family.
- GPU: Loaded and launched through CUDA: 4 CTAs of 256 threads, the grid the host picks for M = N = K = 64.
Measured against cuBLAS
Each bundled kernel is autotuned and timed beside cuBLAS on the same card, in the same run. The f32 gemm runs at88% of cublasSgemm_v2.
Kernel throughput against cuBLAS, higher is better
- Phobos
- cuBLAS (cublasSaxpy_v2, cublasSgemm_v2, cublasGemmEx on f16 copies of A and B, cublasGemmEx)
A track is the card's peak for the kernel's precision as phobos-kbench derives it from SM count and clock, so a bar's length is its share of that peak; saxpy's track is the faster of its two rates. The flash attention kernels carry @tensorcore and run their products on tensor cores where the tile shapes qualify; phobos-kbench rates the f32 one against the f32 peak. Measured once per kernel, so there are no error bars.
What the compiler does on its own
Shared memory by liveness
6 tiles, 10 KiB in 8 KiB
A tile lives from its declaration to its last use, and tiles whose lifetimes do not overlap share bytes. Shown for gemm_fp32 without @aligned.
Step through itBarriers where threads meet
20 kept, 9 elided
A barrier follows a tile store only when other threads next touch those bytes. Of flash attention's 29 candidate sites, 9 need none.
Tiles and shared memoryDouble-buffered loops
2 to 4 staging tiles
The next k-slice loads while this one computes, on by default. On sm_80 the copies become cp.async: 32 in the gemm_fp16 trace, 0 on sm_75.
@pipelineTensor cores from one attribute
2.29x the f32 gemm
The same gemm body with @tensorcore: 22.0 TFLOP/s against 9.6 TFLOP/s, operands rounded to f16. mma.sync and ldmatrix with swizzled staging, or WMMA.
@tensorcoreA search, not a guess
0 gemm configurations
@autotune names the tile sizes, the unroll and the CTA size. phobos-kbench compiles and checks every configuration, ranks them on warm clocks, then races the fastest over 30 interleaved rounds.
AutotuningOne instruction seam
6 chips, sm_75 to sm_120
Only the target layer names an instruction. Kernels are compiled and cached for every chip ahead of time, with no GPU in the machine.
The kernel cache
Tile, barrier and copy counts are from the committed compiler traces (commit b60d28c, small problem sizes); the speed-up is from the benchmark above.
The same language runs LLMs
Quantized GGUF models run end to end on kernels written in Phobos, served through an OpenAI-compatible API. Mixture-of-experts models that do not fit in VRAM keep an expert cache on the card and compute misses on the CPU.
One token at a time
The layers turn the last token into logits, the sampler picks the next one, and it goes back in. Decode fuses a chain of layer stages into one persistent kernel.
Each sampled token streams out and goes back into the layers as the next input.
An elastic expert cache
When another program takes 2 GiB of the card, the cache gives the memory back and the CPU threads take more of the misses, so nothing is paged: Phobos decodes at 33.2 tokens/s while llama.cpp drops to 5.6. Once the memory is free, the cache grows back.
The process exits and the cache grows back to 39 experts a block. Phobos decodes at 43.1 tokens/s, llama.cpp at 19.3. The replay took 81.4 s on Phobos and 372.7 s on llama.cpp.
- the card
- CPU, misses
- another process
Tokens per second against llama.cpp
- Phobos
- llama.cpp, or the fork its row names
Decode over 128 tokens and a 128-token prompt. Each pair of bars has its own scale. The ratio is Phobos's rate over llama.cpp's.
Get started
Download a release
Prebuilt
phobos-cli,phobos-compile,phobos-benchandphobos-cachefor Windows and Linux, x64. Latest releasecurl -fsSLO https://github.com/joa/phobos-lang/releases/download/v0.2.0/phobos-v0.2.0-linux-x64.tar.gz && tar -xzf phobos-v0.2.0-linux-x64.tar.gz && rm phobos-v0.2.0-linux-x64.tar.gz$ProgressPreference = 'SilentlyContinue'; iwr https://github.com/joa/phobos-lang/releases/download/v0.2.0/phobos-v0.2.0-windows-x64.zip -OutFile phobos-v0.2.0-windows-x64.zip; Expand-Archive phobos-v0.2.0-windows-x64.zip .; rm phobos-v0.2.0-windows-x64.zipCompile a kernel
Compile a kernel source, such as gemm_fp32.ph from the repository's examples, to PTX for
sm_75by default. No GPU needed.phobos-compile gemm_fp32.phcargo run -r -p phobos-compile -- examples/gemm_fp32.phServe a model
Download MiniCPM5-1B-Q8_0, then start the OpenAI-compatible server with the dashboard on the terminal. Needs an NVIDIA GPU.
phobos-cli --listen 127.0.0.1:8080 --gguf minicpm5-1b-Q8_0.gguf --temp 0.9 --top-p 0.95 -n 32768cargo run --features cuda -r -p phobos-cli -- --listen 127.0.0.1:8080 --gguf minicpm5-1b-Q8_0.gguf --temp 0.9 --top-p 0.95 -n 32768