vx-lang / vx-lang/Vx

The road to peak attention: prefill GFLOP/s and decode GB/s, measured against their rooflines (#251 campaign)

Open
#378 11 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

project runtime tracking
Dominant language
Rust
Stars
14
Forks
2
Avg merge
12h 42m
Merged PRs (30d)
61

Description

Tracking issue for the performance campaign on the hiraditya/Vx.1#251 emission path, started 2026-08-20 after grid-striding landed (fe408e57) and the A100 SQ-scaling sweep confirmed ~99% scaling efficiency to 64 SMs with the closed form exact at every grid size.

Two regimes, two rooflines

Attention has two shapes with different physics, and one number cannot represent both:

prefill decode
shape SQ queries x SK keys 1 query/head x SK keys, H heads
bound by arithmetic (matmul-shaped) reading the KV cache
metric GFLOP/s, vs 19.5 TF/s FP32 peak (later 312 TF/s FP16 TC) GB/s of cache read, vs ~2 TB/s A100 HBM
parallelism queries (grid-stride today) heads x split-K chunks

Every claimed improvement reports against its roofline, from device-side events (VX_TIME_KERNEL, 47593f4a -- wall clock on rented pods fit a negative slope from jitter), with the closed-form check passing (o = 0.01*(SK-1)/2 under the zero-query fill) and, for kernel changes, the flash_attention_placed_verified.vx EXPECT row reproduced through the 1x1 launcher.

Where we stand (measured, A100-SXM4-80GB)

Prefill -- scripts/run_flash_bench.sh, fp32, one query per thread:

config device time GFLOP/s % of FP32 peak
64 blocks x 128 thr, K=2048 58.4 ms 73.5 0.38%

Scaling to 64 blocks is ~ideal; the cliff at 109 blocks is throughput quantization, not occupancy (driver reports 16 blocks/SM co-resident; one 4-warp block already saturates a per-SM issue pipe). Diagnosis: scalar loads, one element at a time -- the kernel is load-issue-bound, with K/V L2-resident at ~145 GB/s effective.

Reference points on the same box: cuBLAS SGEMM 16223 GFLOP/s (83% of FP32 peak); FlashAttention-2 reaches 50-73% of the 312 TF/s FP16 tensor-core peak (~225 TF/s) on A100.

Decode -- did not exist until this issue. scripts/templates/flash_decode_bench.vx (in review): flash-decoding's split-K written as two placed Vx regions -- partials owned by (head, chunk), combine owned by head -- both admitted by the hiraditya/Vx.1#251 disjointness rule as plain source, no runtime additions. Baseline being measured now; pre-registered expectation: C=1 at H=8 is 8 threads reading ~0.02% of HBM peak, and scaling with H*C should track the prefill block-scaling curve until per-SM issue saturates again.

The ladder

Ordered by measured diagnosis, not by folklore; each rung gets numbers before the next starts.

  • R1 -- wider loads, more work per thread (both regimes). The measured bottleneck. float4-shaped access needs the emitter to vectorize the inner d loops or the language to express slice loads on the device path.
  • R2 -- SMEM tiles for K/V (prefill). Stage tiles once per block instead of every thread re-reading through L2. Space-3 alloca machinery already exists (materializeGpuKernels rewrites them to .shared globals).
  • R3 -- cooperative queries (prefill): partition a query block across warps, threads cooperate on one row's softmax instead of one thread owning it. This is FA-2's work-partitioning insight and requires cross-thread reduction within a block (barriers exist: 0 used today).
  • R4 -- decode split-K tuning: chunk count vs SK, the combine's cost at large C, and whether region 2 merges into a tree.
  • R5 -- f16/bf16 (#320): halves the bytes decode must read (its roofline doubles in tokens/s) and unlocks tensor cores for prefill.
  • R6 -- MMA / tensor cores (prefill): the 16x that separates CUDA-core fp32 from the FA-2 comparison. Blocked on R5.

Milestone gates, so the campaign has falsifiable exits:

  • prefill >= 1 TF/s fp32 (5% of peak) with R1+R2;
  • decode >= 10% of HBM roofline at SK=16k;
  • then re-aim at the vLLM cost-of-admission comparison from the hiraditya/Vx.1#321 plan.

Discipline

  • Pre-register before measuring (the SQ-scaling prereg caught two wrong mechanisms this week: "bandwidth-bound" was issue-bound, "residency wall" was throughput quantization).
  • Rented pods refuse performance counters (ERR_NVGPUCTRPERM); everything must be measurable with events + occupancy queries + closed forms.
  • Correctness gates every rung: closed form at every config, EXPECT row through the launcher, full suite on commit.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running scripts/run_flash_bench.sh and reviewing scripts/templates/flash_decode_bench.vx, then trace materializeGpuKernels and the grid-striding change fe408e57. The campaign is complete only when each selected rung has device-side measurements, closed-form checks, the flash_attention_placed_verified.vx EXPECT row through the 1x1 launcher, and the stated milestone gates.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, shell
Domain
compilers, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.