Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Work: exact-chunks on main `1ce0d662b`; sm_120 measured at `3d2581551`.
| CPU levers (`QUANT-GGUF-CIQ-GEMM`) | Profile DONE: decode **47% threadpool sync**, prefill **~39% paged attn**. **G5 not next** | Parakeet encoder; attn dtype hoist |
| Supported-models list | **LANDED**: FEATURES arch table CI-bound (33 archs) | — |
| `/v1/videos` OpenAI shape | **MERGED** (#71): Sora `model`/`size`/`seconds` + `GET /{id}/content` | `row/SERVE-VIDEOS-REFS` PR open: reference conditioning |
| Vulkan 27B decode | **4.285 vs llama.cpp 4.35 (98.5%)** BINDING, 8/8 clean legs @`81ea01f0`; GPU 240.3->227.7 ms/tok; GEMV lever CLOSED | 3.1 ms short. Open: 20x lm_head bimodality REAL on an IDLE box (reclaim REFUTED, mechanism unknown) |
| Vulkan 27B decode | **4.285 vs llama.cpp 4.35**, 8/8 clean legs; GEMV CLOSED | Gap is ALL host (5.3 ms). Open: 20x lm_head bimodality |
| `BACKEND-ROCM` | **(b) fix in; #140 gfx1201 hipBLAS + Gemma-4 MoE landed (contributor, authorship-preserved); W0 green 4 archs** | compile + M2 ([spec](specs/rocm-unified-memory-b.md)) |
| TP spike #287 (PR #143) | **TP-W1 LANDED**: rank-group table + TP handle (6/6); DSR leak FIXED (unblocks #127/#154/#155) | TP-W2 (linears + loader) |
| Release | SPIKE; 30/30 | #129 |
Expand Down
11 changes: 11 additions & 0 deletions .agents/benchmark-record.md

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions .agents/state.md

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@

- **2026-08** **MXFP4 holds parity with vLLM.** Qwen3-8B MXFP4 runs W4A16 Marlin by default,
matches the vLLM oracle token for token, and decodes **45.45 vs 41.94 tok/s**.
- **2026-08** **Vulkan runs a model end to end.** `opt-125m` greedy is STRICT token-exact on
24 native ops. Decode **8.59 to 91.7 tok/s (10.7x)**; llama.cpp Vulkan stays **2.62x** ahead.
- **2026-08** **Vulkan reaches 98.5% of llama.cpp on a 27B.** `opt-125m` greedy is STRICT
token-exact; Qwen3.6-27B decodes **4.285 vs llama.cpp Vulkan 4.35 tok/s** on GB10, with our
GPU-side work already ahead of its wall clock. Prefill **21.5x**.

vllm.cpp is a from-scratch C++20 inference engine chasing three things at once: be the
**smallest** thing you can deploy, be the **fastest** on the hardware you already own, and still
Expand Down Expand Up @@ -307,7 +308,7 @@ hardware-blocked and why, is in [docs/STATUS.md](docs/STATUS.md).
| **CUDA** | Blackwell, Hopper, Ampere, Ada (sm_80 through sm_121a) | Build-supported, fast GDN path build-verified per-arch. Not runtime-proven here (no such boards) |
| **CPU** | x86-64, arm64 | Correctness / CI reference. At or ahead of llama.cpp on every GGUF axis, Arm i8mm quant-GEMM tier |
| **Metal** | Apple Silicon | Two models end to end, 18 of 75 ops native. Prefill ahead of MLX-LM, warm total 97.6% with the MLX provider |
| **Vulkan** | Portable GPU | 24 native ops; `opt-125m` STRICT token-exact, Qwen3.6-27B runs e2e |
| **Vulkan** | Portable GPU | `opt-125m` STRICT token-exact, Qwen3.6-27B runs e2e at 98.5% of llama.cpp Vulkan decode. Op coverage: [docs/STATUS.md](docs/STATUS.md) |
| **ROCm** | AMD GPUs | W0 skeleton; a contributor ran gfx1201 on 2x R9700 ([#140](https://github.com/mudler/vllm.cpp/pull/140)). No AMD board here: [docs/ROCM.md](docs/ROCM.md) |
| **Intel XPU / ANE** | Intel, Apple NPU | Spiked or roadmap |

Expand Down
31 changes: 31 additions & 0 deletions benchmarks/demo/vulkan_27b_llamacpp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"_source": "docs/BENCHMARKS.md BENCH-VK-LLAMA row; .agents/benchmark-record.md 'BACKEND-VULKAN combined'. Every number here is a MEASURED binding value, not a projection.",
"title": "Vulkan decode, within 1.5% of llama.cpp on a 27B",
"model": "Qwen3.6-27B BF16 (50.89 GiB), greedy, 32-in / 32-out, concurrency 1",
"hardware": "NVIDIA GB10 / DGX Spark",
"workload": "8 legs with the host page cache dropped before each, GPU serialized under one lock; llama.cpp built -DGGML_VULKAN=ON at 237ad9b96 with a from-source shaderc so all five of its Vulkan fast paths report supported (banner: matrix cores NV_coopmat2), llama-bench tg1024, same weights",
"headline": "4.285 vs 4.35 tok/s, 98.5%",
"subline": "our GPU-side work is already ahead of llama.cpp's wall clock; the whole remaining gap is host time",
"ours": {
"label": "vllm.cpp (Vulkan)",
"accent": "teal"
},
"theirs": {
"label": "llama.cpp (Vulkan)",
"accent": "slate"
},
"points": [
{
"c": 1,
"ours": 4.285,
"theirs": 4.35
}
],
"footnotes": [
"Decode tokens/s per stream. Ours is the median of 8 clean legs (4.27 to 4.31, spread 0.8%); llama.cpp is llama-bench tg1024 at 4.35 +- 0.00.",
"Decomposed by GPU timestamps over a two-length diff (output-len 36 minus 4, over 32 decode tokens, so prefill and one-time costs cancel): GPU 227.7 ms/token, host 5.3 ms, wall 233.0. Over the 54.6 GB streamed once per token that is 239.8 GB/s of GPU-side aggregate against llama.cpp's 237.5 GB/s of WALL aggregate.",
"The decode GEMV is at 248.0 GB/s and its lever is closed: the binding constraint is DRAM bandwidth on a working set streamed once per token, not instruction issue or latency. llama.cpp's own rows-per-workgroup trick measured 0.966x here and ships off.",
"Prefill is 21.5x its pre-campaign value on the same model. opt-125m greedy stays STRICT token-exact against the vLLM 0.25.0 oracle with 0 declines.",
"llama.cpp's own CUDA arm on this box is 4.67 tok/s, so its Vulkan backend runs at 93% of its CUDA."
]
}
2 changes: 1 addition & 1 deletion docs/BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ built on it rather than keeping the flattering one.
| Memory footprint vs declared workload (`ROAD-V1-MEM`, #83) | **Never measured, and not measurable today**: there is no auto-sizing to compare against, because the KV pool is a hand-typed `--num-blocks`, so "what the run actually needed" has no number | Once M1's `MemoryBudget` lands: predicted-vs-actual bytes per allocation class, then peak footprint ours-auto vs vLLM at its 0.9 default on the same model and config |
| Startup latency (cold to first `/health`) | **36.51 s vs vLLM 0.25.0's 221.51 s = 6.07x** (medians of 3, 27B-NVFP4, GB10). PROVISIONAL: 3 of 6 legs contended, repeat killed by a host reboot. [Detail](../.agents/benchmark-record.md) | Uncontended 3-rep re-run on a quiet box |
| Speculation depth (`ROAD-V1-D3-SPEC-K`, #81) | **Never measured, MTP is k=1** (our port covers vLLM's k=1 branch only), so no acceptance-vs-depth curve exists | k=2..4 three-way greedy gate, then the c1/c>1 A/B + the per-workload (prose vs code) acceptance-vs-depth curve any dynamic or adaptive depth policy needs |
| Vulkan vs llama.cpp Vulkan (`BENCH-VK-LLAMA`) | 25 NATIVE (+8 GDN); 62 host-tier. **27B prefill 21.5x**, decode **4.28**/4.35 BINDING (8/8 clean legs, merged tree). GPU 240.3->227.7 ms/tok. [Detail](../.agents/specs/vulkan-full-support.md) | `VK-C` coopmat A/B on Thor (`VT_VULKAN_COOPMAT=0` A/Bs it): **11.1x-32.9x** vs our UNTILED scalar kernel, not vs a competent GEMM. `VK-E`: llama.cpp `-DGGML_VULKAN=ON` at `237ad9b96` on dgx, same GGUF, three columns |
| Vulkan vs llama.cpp Vulkan (`BENCH-VK-LLAMA`) | 25 NATIVE (+8 GDN); 62 host-tier. **27B prefill 21.5x**, decode **4.285**/4.35 BINDING, 8/8 clean legs; GPU 227.7 + host 5.3 ms/tok. [source](../benchmarks/demo/vulkan_27b_llamacpp.json) | `VK-C` coopmat A/B on Thor (`VT_VULKAN_COOPMAT=0` A/Bs it): **11.1x-32.9x** vs our UNTILED scalar kernel, not vs a competent GEMM. `VK-E`: llama.cpp `-DGGML_VULKAN=ON` at `237ad9b96` on dgx, same GGUF, three columns |
| ROCm (`BACKEND-GATE-ROCM-VLLM` / `-SGLANG`) | **NOT APPLICABLE: no number measured, claimed or owed.** W0 ctest-green on 4 gfx archs (#41); gfx1201 hipBLAS + Gemma-4 MoE (#140, contributor) ran M0/M1 on 2× R9700, our side CPU-link-verified only. No AMD HW here | The approach-(b) fix (PENDING community) unblocks the first APU model run (M2); the gate becomes a same-box vLLM-ROCm oracle once a model runs ([#41](https://github.com/mudler/vllm.cpp/issues/41)); floor: vLLM |
| SGLang floor arms | Never ran | Both arms of the SGLang comparison |
| Embeddings on the ONE surface (ROW 6, `LlamaModel` + `vllm_embed` + `/v1/embeddings`) | **NO number measured, claimed or owed.** Correctness-gated only, CPU: the 2026-08-08 fold (engine path == direct registry path, f64 LAST+normalize reference on the committed fixture) is plumbing, no speed claim | A REAL embedding checkpoint (e5-mistral class) + a same-box `vllm.LLM(task="embed")` oracle; only then does an embed-throughput bar exist |
Expand Down
2 changes: 2 additions & 0 deletions docs/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ portable/reference path. In normal operation leave them unset.
| `VT_VULKAN_GEMV_PACK` | 2 | Load width for a 16-bit decode GEMV operand: `0` one element per load (2 B), `1` two through the buffer's 32-bit view (4 B), `2` four through a 64-bit view (8 B). Same bytes, same coalescing (32 lanes still cover 128 or 256 CONSECUTIVE bytes), so this cannot reduce DRAM traffic; it reduces LOAD INSTRUCTIONS. **MEASURED, and the two regimes disagree by 7x, which is the point.** In `benchmarks/vulkan_gemv_ab.cpp` over the 27B's own decode shapes (9 arms x 4 rotated passes, each arm paired against the width-0 baseline measured IN THE SAME PASS, because the box drifted 15.5% peak-to-peak between passes) width 2 reads **1.086x**. In REAL 27B decode it reads **1.012x** and **1.020x**, two GPU-timestamp two-length diffs (output-len 36 minus 4 over 32 tokens), moving `vt_matmul_vec` from **90.0% to 91.1%** of the 273 GB/s roof. The sweep re-reads one 356 MB buffer 320 times, so its DRAM rows and TLB stay hot and instruction issue is visible; decode streams 50 GB once per token, where DRAM is the whole story. e2e: **4.126 -> 4.157 tok/s**, 5 of 6 clean pairs, against a 0.88% clean-leg noise floor. Degrades a width at a time when K or an operand byte offset is not aligned to it, and declines entirely for an f32 operand, which is already one element per 32-bit word. Unlike the row count this axis DOES change the answer's low bits, because it repartitions K across lanes; the opt-125m STRICT gate (6/6 token-exact) is what clears it. Vulkan-only |
| `VT_VULKAN_GEMV_ROWS` | 1 | Output elements per decode-GEMV workgroup, `1`, `2` or `4`; llama.cpp's `NUM_ROWS` (`mul_mat_vec_base.glsl:90`, set per vendor by `rm_stdq` in `ggml-vulkan.cpp:4705-4719`). Raising it lets one loaded activation element feed several weight rows. It cannot reduce DRAM traffic either -- the weights are read exactly once per token at any row count -- and the activation re-reads it saves were L2 hits. **MEASURED NEGATIVE on GB10 and shipped OFF:** 0.966x at 2 rows and 0.968x at 4, in all four passes and at every load width, most likely because halving the workgroup count halves the independent sequential read streams the memory controller sees while each surviving workgroup interleaves rows `K*2` bytes apart. Kept as an axis only because llama.cpp raises it exactly on the AMD GCN and Intel parts this backend's portability case is aimed at, and deleting it would mean rediscovering it there. Bit-identical to `1` at any value -- each lane accumulates the same strided subset of K in the same order -- so it is a pure scheduling knob. Vulkan-only |
| `VT_VULKAN_RING` | 128 | Descriptor-set ring depth per pipeline, i.e. how many times one shader may be recorded before the batch must flush. This is what actually caps batch length: at 16 it forced a submit every 40-46 dispatches, because `vt_rms_norm` runs 112 times per forward pass and exhausted its ring seven times. At 128 a whole forward pass (368 dispatches) fits in ONE submit. Each avoided flush is a `vkQueueSubmit` plus a blocking `vkWaitForFences`, and a host profile puts 62% of on-CPU time in the kernel and NVIDIA driver against 14% in our own code -- so submits are the host cost. Lower it to bisect or to cut descriptor-pool size. Vulkan-only |
| `VT_SERVER_MAX_PROMPT_CHARS` | `200000` | Hard cap on templated prompt size for `/v1/chat/completions`. A larger prompt is REJECTED with an error naming this variable, rather than wedging single-batch async prefill for minutes with no client tokens. `0` disables the cap |
| `VT_SERVER_MAX_NEW_TOKENS` | `4096` | Upper bound applied to a chat request's `max_tokens`. Guards the same wedge from the generation side. `0` disables the cap |
| `VT_SERVER_VERBOSE` | off | `=1` turns on the server's chatty progress output, and is the umbrella switch the individual knobs fall back to: `VT_SERVER_PREFILL_PROGRESS` reads it when unset, so `VT_SERVER_VERBOSE=1` alone enables chunked-prefill progress (rate-limited to ~2 Hz per request). `examples/server` sets it from its own verbosity flag, so it is user-facing rather than a kernel tuning switch |
| `VT_VULKAN_BATCH` | on | `=0` forces the per-dispatch submit-and-wait path instead of recording many dispatches into ONE command buffer (with a pipeline barrier between them) and submitting once. MEASURED on GB10: decode **2.62x**, faster in **8 of 8** interleaved pairs (24.8 -> 64.5 tok/s), 40-46 dispatches per submit, opt-125m still 6/6 token-exact. Batching is sound only because all three host-read paths drain the batch first: `Backend::Copy` and `Memset` (host memcpy over the mapped allocation), `Synchronize`, and `Backend::FlushPending`, which op_provider.cpp calls before running a PORTABLE REFERENCE TIER kernel directly over device memory. Without that third one a host kernel would read stale bytes silently. It exists for the same-binary A/B and as the bisect lever. Vulkan-only |
| `VT_VULKAN_GEMV` | on | `=0` forces the decode GEMV onto the portable scalar GEMM instead of `vt_matmul_vec`. The vec tactic gives each output element a WORKGROUP whose lanes stride K, so adjacent lanes read adjacent addresses; the scalar kernel puts one invocation on each output element and loops K there, which for the `MatmulBT` weight layout leaves adjacent lanes `K*2` bytes apart, each pulling its own cache line for 2 bytes of it. Selected only for `MatmulBT` at M=1 with K at least one workgroup wide -- the other orientation is already coalesced and the vec shape would make it worse. MEASURED on GB10: GEMM `ms/call` improves in 7 of 8 interleaved pairs, median 1.8x. Its K reduction is a tree, so unlike the scalar kernel it does NOT share the CPU's accumulation order; the opt-125m STRICT gate (6/6 token-exact) is what clears it. Vulkan-only |
Expand Down
2 changes: 1 addition & 1 deletion docs/STATUS.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions scripts/check-doc-checkpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,20 @@ def public_rule_bindings(root: Path = ROOT) -> dict[str, PublicRuleBinding]:
"benchmarks/demo/qwen36_27b_c1_c32.json",
"examples/cli/main.cpp",
"examples/server/main.cpp",
# The Vulkan-vs-llama.cpp headline, added 2026-08-09. README carried two
# FALSE Vulkan claims ("24 native ops", "llama.cpp Vulkan stays 2.62x
# ahead") that no permitted change could correct, because the backend it
# describes had no headline-benchmark source in this list while the CUDA
# comparison had two.
#
# Adding docs/STATUS.md instead was considered and REJECTED: the rule
# above -- co-edited public projections never justify README churn -- is
# deliberate and directly tested
# (test_readme_is_not_justified_by_coedited_public_projections names
# docs/STATUS.md), and overturning it to unblock a README edit would be
# weakening a checker to make a change pass. The gap was not that the rule
# was wrong; it was that a real headline benchmark had no source file.
"benchmarks/demo/vulkan_27b_llamacpp.json",
}
)

Expand Down
33 changes: 33 additions & 0 deletions scripts/check-pr-size.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"asset",
"evidence",
"vendored_dependency",
"generated",
}
)
PATH_CLASS_BUDGETS = {
Expand All @@ -57,8 +58,29 @@
"asset": 3000,
"evidence": 8000,
"vendored_dependency": 8000,
# A REVIEW budget is a budget on what a human reads. Nobody reads a hex blob,
# and re-deriving one by eye is not review. These files are emitted by a
# tracked generator from reviewed sources, and a dedicated gate reproduces
# them BYTE-FOR-BYTE from those sources on every push, so their correctness is
# established mechanically rather than by reading the diff. The reviewable
# surface is the GENERATOR and its INPUTS, both of which stay `product` or
# `governance_checker` and keep their own tighter budgets.
"generated": 8000,
}

# Machine-generated artifacts, each of which MUST be (a) emitted by a tracked
# generator in this repository, (b) reproduced byte-for-byte by a gate that runs
# in CI, and (c) marked "GENERATED FILE - DO NOT EDIT BY HAND" at its head.
# Adding a path here without all three is how this class would become a hole.
GENERATED_FILES = frozenset(
{
# scripts/gen-vulkan-spirv.py, from src/vt/vulkan/shaders/*.comp.
# Reproduced by `gen-vulkan-spirv.py --check` in the vulkan-spirv-freshness
# CI job; the GLSL it compiles stays `product` and is what review reads.
"src/vt/vulkan/vulkan_spirv.cpp",
}
)

POLICY_FILES = frozenset(
{
".agents/policy.csv",
Expand Down Expand Up @@ -92,6 +114,10 @@
PROCEDURE_FILES = frozenset(
{
"AGENTS.md",
# A tracked SYMLINK to AGENTS.md, for tools that look for CLAUDE.md. It is
# the same procedure text and shares its budget; without this the checker
# failed closed on every change that touched it.
"CLAUDE.md",
".agents/workflow.md",
".agents/verification.md",
".agents/porting.md",
Expand Down Expand Up @@ -126,6 +152,9 @@
{
"README.md",
"CONTRIBUTING.md",
# Landed by a9a8581d and never classified, so the checker failed closed on
# it the same way it did on CLAUDE.md.
"MANIFESTO.md",
"docs/STATUS.md",
"docs/BENCHMARKS.md",
"docs/FEATURES.md",
Expand Down Expand Up @@ -230,6 +259,10 @@ def classify_path(path: str) -> str:

if not _canonical_path(path):
raise ValueError(f"noncanonical repository path {path!r}")
# Ahead of every other rule: a generated artifact under src/ would otherwise
# fall through to `product` and spend a human-review budget on hex.
if path in GENERATED_FILES:
return "generated"
if path in POLICY_FILES:
return "policy"
if path in APPEND_ONLY_FILES:
Expand Down
20 changes: 17 additions & 3 deletions scripts/check-public-doc-tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,10 +401,24 @@ def features_errors(text: str) -> list[str]:
# ON the page deliberately: only OLMo-2 has a committed golden, Granite and
# StableLM skip rather than gate, dflash and deepseek_v2 are build-verified
# only. Nothing was lost, only de-duplicated. Net -2185.
"chars": 276960,
# 244486 since 2026-08-09 (measured 244486): the Laguna-S-2.1 MoE row was a
# SINGLE 33,211-char table row - an 18,215-char accumulated benchmark history
# and a 14,941-char implementation narrative - on a page whose contract is ONE
# binding current-state line per capability, and against a 220-char cell bound
# it was 150x over. It had also become the reason this page could not satisfy
# its own shrink-only ratchet: main sat 253 chars over with no other block
# large enough to pay for anything. Both cells were MOVED VERBATIM, the
# benchmark half to .agents/benchmark-record.md and the implementation half to
# .agents/state.md, leaving the binding result (87% of vLLM, root cause, the
# device-resident fix, default-ON) and the architecture summary ON the page
# with pointers. Nothing was rewritten, condensed or dropped. Net -32728, and
# the ratchet is tightened to the measured value in the same change so the
# headroom cannot be silently re-spent. oversized_cells 47 -> 44 and
# long_paragraphs 89 -> 82 fall out of the same move.
"chars": 244486,
"h2_sections": 11,
"long_paragraphs": 89,
"oversized_cells": 47,
"long_paragraphs": 82,
"oversized_cells": 44,
}
STATUS_REQUIRED = (
("Parity pin", ("parity pin",)),
Expand Down
Loading
Loading