Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flashlet πŸ”₯

A pocket-sized LLM inference engine: continuous batching + paged KV-cache, FlashInfer as the reference attention backend, hand-written CuTe DSL kernels where we can beat it. Runs Qwen2.5-0.5B (bf16) end-to-end on an RTX 4050 Laptop (sm_89, 6 GB).

Architecture

flashlet/
β”œβ”€β”€ flashlet/
β”‚   β”œβ”€β”€ scheduler.py        continuous batching + chunked prefill + radix prefix reuse
β”‚   β”œβ”€β”€ kv_cache.py         paged KV pool, free-list allocator, radix cache w/ refcounts
β”‚   β”œβ”€β”€ model.py            Qwen2.5 forward pass (HF weights, our own loop)
β”‚   β”œβ”€β”€ engine.py           step(): schedule -> plan -> attention -> MLP -> sample
β”‚   β”œβ”€β”€ backends/
β”‚   β”‚   β”œβ”€β”€ base.py         AttentionBackend ABC: plan() / run()
β”‚   β”‚   β”œβ”€β”€ torch_backend.py    dense SDPA reference (correctness oracle)
β”‚   β”‚   └── flashinfer_backend.py  BatchPrefillWithPagedKVCacheWrapper, persistent bufs
β”‚   └── bench/              harness + workloads + `python -m flashlet.bench`
β”œβ”€β”€ kernels/                standalone CuTe DSL kernels (M4)
β”œβ”€β”€ tests/                  pytest suite
└── scripts/m0_check.py     end-to-end sanity check vs HF generate

Quickstart

pip install torch flashinfer-python transformers fastapi uvicorn
python3 scripts/m0_check.py                     # greedy gen + HF parity + TPS
python3 -m pytest tests/ -q                     # unit tests
python3 -m flashlet.bench --backend flashinfer --concurrency 1 8 32 128 \
    --max-new-tokens 32 --max-num-pages 8192 --out results.json

python3 -m flashlet.server --backend flashinfer --port 8000     # HTTP server
curl -s localhost:8000/health
curl -s localhost:8000/generate -H 'Content-Type: application/json' \
    -d '{"prompt": "The capital of France is", "max_new_tokens": 32}'
curl -s -N localhost:8000/generate_stream -H 'Content-Type: application/json' \
    -d '{"prompt": "Tell me a story", "max_new_tokens": 64}'    # SSE, token by token

Results

Qwen2.5-0.5B-Instruct, bf16, RTX 4050 Laptop (sm_89), greedy, max_new_tokens=32, mixed prompt lengths (32-768 tok), closed-loop, --max-num-pages 8192.

backend c decode tok/s TTFT p50/p99 (ms) ITL p50 (ms) peak VRAM
flashinfer 1 112.5 459 / 906 8.61 2.78 GB
flashinfer 8 868 581 / 1126 8.26 5.54 GB
flashinfer 32 3,204 887 / 1,682 7.12 5.55 GB
flashinfer 128 5,961 2,147 / 4,254 2.13 5.57 GB
torch ref 1 76.6 662 / 1,284 12.64 2.64 GB
torch ref 8 215 1,950 / 3,764 33.31 5.26 GB
torch ref 32 290 5,671 / 11,152 78.72 5.26 GB
torch ref 128 315 19,929 / 46,438 40.39 5.28 GB

Saturated-batch microbenchmark (128 seqs Γ— 300-tok prompts, pure decode): 11,238 tok/s at 11.4 ms/step with the FlashInfer backend.

Single-stream peak: ~124 tok/s (flashinfer) vs ~101 tok/s (torch reference).

TTFT includes queue wait in closed-loop batches (later requests wait for earlier ones at low concurrency). Logits agree with HF transformers on 99.3% of argmax positions; greedy outputs diverge on near-ties due to bf16 kernel reduction order, as expected.

Design notes

  • One ragged BatchPrefillWithPagedKVCacheWrapper.plan()/run() call per step handles chunked prefills AND decodes together (qo_indptr = [512,1,1,...], causal mask covers both).
  • Metadata lives in persistent device buffers updated in-place each step; CUDA-graph-friendly by construction.
  • KV pool layout [layers, pages, page_size, kv_heads, head_dim] matches FlashInfer NHD exactly: set_kv is a single index_copy_, zero copies.
  • Radix prefix cache with explicit page refcounts; adoption never covers the final prompt token (guarantees a query token exists); LRU-free eviction on pool pressure.
  • Chunked prefill shares one token budget across all active prefills; decoders always step every iteration.

Megakernel roadmap

A standalone CuTe DSL megakernel for Qwen2.5 (whole-model fused kernel: GEMV, GQA attention, RMSNorm, RoPE and residual ops in one launch graph, see qwen_2.5_megakernal) already measures 140 tok/s single-stream decode on the same RTX 4050. That is close to the practical roofline for this GPU: bf16 weights are about 1 GB, so 140-160 tok/s means streaming roughly 140-160 GB/s of weight reads per token, near the sustained bandwidth ceiling of the laptop's memory bus.

Integration into flashlet as a third attention/model backend is planned: the megakernel would replace the per-layer op sequence for pure-decode steps while flashlet keeps the scheduler, paged KV pool and continuous batching around it. Expect single-stream numbers to land in the 140-160 tok/s band once wired in.

Milestones

  • M0 skeleton: greedy decode, coherent text, HF parity
  • M1 paged KV cache: alloc/free cycles, pinnedβ†’device metadata, radix reuse
  • M2 scheduler: continuous batching + chunked prefill, mixed-batch tests
  • M3 FlashInfer backend: plan/run integration, parity vs reference, bench table
  • M4 CuTe DSL paged GQA decode kernel (sm_89)
  • M5 RoPE+residual fusion kernel
  • M6 Blackwell port (stretch)

About

Pocket-sized LLM inference engine.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages