跳至主要内容

量測 Benchmark

這一頁回答的是一個很具體的問題:

當模型本身已經是權重共享的循環式 Transformer 時,為什麼要用 TinyLoop,而不是通用 runtime?

答案不是「TinyLoop 在所有地方都打爆所有框架」。這個儲存庫目前並不支持那種說法。更精確、也更能站得住腳的答案是:

  • TinyLoop 在自己的 loop-aware runtime 路徑上,已經有直接量測到的優勢。
  • TinyLoop 暴露的結構性優勢,通用 runtime 除非自己也變成 loop-aware,否則補不回來。
  • 如果你今天要的是廣泛 checkpoint 支援或成熟的 serving 基礎設施,通用 runtime 仍然是合理選擇。

如何正確閱讀這一頁

下面的比較分成兩類:

  1. 來自這個 repo、已驗證的 TinyLoop benchmark 的直接量測
  2. 與 vLLM、TensorRT-LLM、Hugging Face Transformers、ONNX Runtime 之類通用 runtime 的架構層部署比較

這個區分很重要。我們刻意不會把一個沒有以相同形式公開 checkpoint 的東西,硬包裝成假裝公平的「TinyLoop 對 PyTorch/vLLM latency shootout」。

TinyLoop 的直接實測 Benchmark

1. 目標 H100 產物的核心前向延遲

路徑seq_len=128, loops=8相對早期版本加速權重 VRAM適用情境
早期 TinyLoop runtime208.7 ms1.00×(基線)INT4 216 MB歷史比較基線
預設低位元路徑30.48 ms6.85×INT4 216 MBVRAM 緊、production 預設
FP16-body 快速路徑2.82 ms74.0×FP16-body 619 MBHBM 充足、延遲優先

在等效 407M checkpoint 上,對 generic runtime 的參考比較(同一台 H100 上的 Pythia-410M FP16,作為 CHECKLIST §0 的 north-star 指標):

RuntimeTokens/s權重 VRAMtok/s/MB相對 Pythia
Pythia-410M(FP16)16,519870 MB19.01.0×(基線)
TinyLoop FP1668,303848 MB80.54.2×
TinyLoop INT44,587219 MB20.91.1×,但只用 25% VRAM
TinyLoop INT4 + FP16 body52,265628 MB83.24.4×

這裡最誠實的跨架構指標是 per-byte throughput。TinyLoop 能把相同的矽面積轉成 約 4× 的 tokens per MB of VRAM,因為同一個 loop block 被 8 次 iteration 共用,而不是展成 8 層獨立權重。

這一段代表的含義:

  • TinyLoop 不只是「概念上比較漂亮」,它已經有真正最佳化後的 runtime path。
  • 預設低位元路徑在相同 benchmark shape 上,已經比早期 runtime 快 6.85×
  • FP16-body 是速度路徑,但那是刻意的記憶體交換,不是免費魔法。

2. 共享前綴吞吐提升

PrefixRequestsNO_CACHE wallSHARED wall節省時間加速倍數
500 tokens1012,847 ms5,415 ms7.4 s2.37×
1024 tokens1023,852 ms8,329 ms15.5 s2.86×
1024 tokens50104,605 ms34,531 ms70.1 s3.03×

和 generic prefix cache 相比(例如 vLLM / SGLang):兩邊都會攤提 prefix KV,但在 looped model 上,一份 cached prefix 會同時L 次 loop iteration 被重用,所以有效攤提是非 looped runtime 的 L ×。在 L = 8 時,3.03× 的 headline number 代表每個 cached prefix byte 對應大約 24× 的有效計算節省

這對 looped model 的意義:

  • 在 TinyLoop 裡,共享 prefix 會把 loop block 的成本攤提到 L 次重複套用上。
  • 通用 prefix cache 仍然有價值,但拿不到這種「同一區塊、多次 iteration」的完整槓桿,除非 runtime 明確保留這個結構。
  • 加速幅度會隨 prefix 長度與 request 數量一起放大,50 requests 時的 3.03× 就是複利最明顯的區間。

3. 新快取模式之後的解碼吞吐

配置Decode msTok/s相對基線變化KV 節省
FP16 KV 基線67493
FP16 KV + fp16_body527120+29 %0
FP16 h + fp16_body415152+63 %~46 %
INT8 h + fp16_body423149+60 %~66 %
INT4 h + fp16_body425148+59 %~78 %

和 generic INT8 KV quantizer 相比(常見於 vLLM / TensorRT-LLM 的 production baseline):

面向Generic INT8 KVTinyLoop INT4-h + fp16_body優勢
KV 節省~50 %~78 %多省 28 個百分點
Decode throughput持平或微退步相對 FP16 基線 +59 %tok/s 上升,不是下降
品質通常 ± 0 PPLΔPPL ≤ +0.08(到 seq=4096 仍 ranking-safe)可比

Generic INT8-KV 通常只是純記憶體節省,有時還帶一點 throughput 代價。TinyLoop 的 store-h + fp16_body 把它翻成同時拿到記憶體與吞吐優勢,因為壓縮後的 K/V 會透過同一條 tensor-core GEMM pipeline 重建,而這只有在 W_k / W_v 跨 loop iteration 共享時才成立。

這也是目前 TinyLoop 最強的 runtime 論點之一:

  • loop-aware KV 壓縮可以同時變成記憶體與延遲優勢。
  • 這招依賴共享 loop block,不是一般深層 Transformer 自帶的特性。
  • INT4-h + fp16_body 幾乎在釋放 4/5 KV bytes 的同時,還能做到接近 於 FP16 KV 基線的速度級別。

4. RTX 4090(Ada, SM 8.9)上的 INT4 tensor-core GEMM

上游 Marlin(Frantar 2024,Apache 2.0,vendor 於 cuda/marlin/)是一個為 Ampere 與 Ada 設計的 INT4 × FP16 tensor-core GEMM。對 4090 來說,它能在權重仍放在 INT4 GMEM 的前提下逼近 FP16 tensor-core 峰值。TinyLoop 的 wrapper 加上 tests/bench_marlin_gemm 的量測結果(2026-04-19)如下:

ShapeN × KNaive TFLOPSMarlin TFLOPS加速佔 tensor-core 峰值
attn_qkv_like2048 × 20486.1084.2013.8×51 %
qkv_stacked6144 × 20486.77128.0218.9×78 %
mlp_down2048 × 81926.29131.5920.9×80 %
mlp_gate_up8192 × 20486.77142.3021.0×86 %

這說明:

  • batch × time = 256 時,Marlin 能用 INT4-packed weights 打到 4090 理論 FP16 tensor-core 峰值的 86%
  • Naive kernel 不論 batch 多大,大約都卡在 6.8 TFLOPS 附近,因為每個輸出元素只有單一 thread 計算,讀權重頻寬成為上限。
  • 單 token decode(BT = 1)時,Marlin 仍有約 0.010 ms 的 tile floor,但 wall-clock 依然比 naive 快約 2.4×。真正的大勝區是在 prefill(BT ≥ 64)。
  • Hopper(SM 9.0)不在 upstream Marlin 的目標範圍內,因此 TinyLoop 在那裡仍會回退到 FP16 body cache path。

完整 36 列(shape × BT)資料表放在 paper_draft bundle 下的 figures/marlin_vs_naive.csvfigures/marlin_vs_naive.{png,pdf}

5. 非對稱校正的額外成本(Ada, SM 8.9)

TinyLoop 的 GPTQ 權重是非對稱格式(W = q·scale + zero),而 upstream Marlin 假設對稱格式(W = (q−8)·scale)。因此多出來的殘差會收斂成每個 group、每個 column 的 δ = zero + 8·scale,再以小型 reduction + 小型 GEMM 累加回輸出 C。這個流程已經作為 cuda/marlin_asymm_correction.cu ship 出去,並在 5 種 shape 上驗證到 FP16 noise floor。

4090 上的額外成本如下:

工作負載Marlin+correctioncorrection alone相對開銷
decode M=1 attn_qkv 2048×204811.1 μs19.0 μs7.8 μs+71 %
prefill M=32 attn_qkv 2048×204823.1 μs27.7 μs7.7 μs+33 %
prefill M=128 attn_qkv 2048×204824.2 μs33.1 μs7.7 μs+32 %
prefill M=256 mlp_gate 8192×204860.5 μs83.0 μs23.0 μs+38 %
prefill M=256 mlp_down 2048×819265.6 μs96.0 μs30.7 μs+47 %

解讀重點:

  • production shape grid 上,相對開銷大約落在 31–71%
  • 最差是單 token decode(M = 1),因為固定 kernel launch latency 佔主導。
  • correction kernel 的純計算量其實不到 1 μs,觀察到的 8–31 μs 大多是兩次 kernel launch 的成本。
  • 如果之後改成 CUDA graphs replay 或真正的單 kernel fuse,還有再砍半空間。
  • 正確性目前很緊:cuda-vs-python max_abs ≤ 0.004cuda-vs-FP16-reference max_abs ≤ 0.012,涵蓋到 M = 32, N = K = 2048, 16 groups

6. 消費級 Ada 上的 L2 resident 優勢

4090 有 72 MB L2,比 H100 的 50 MB 還大。407M INT4 loop block 只有 6 MB,W / L2 = 0.08,非常容易塞進 L2。tests/bench_l2_residency(2026-04-19)的量測:

場景單次 iteration 時間
Scenario A iter 0(cold)0.0503 ms/iter
Scenario A iter 1+(L2 warm)0.0410 ms/iter
Scenario C(每次都 flush L2)0.0480 ms/iter

這代表:

  • 從 cold 進入 warm 之後,大約能多拿到 18% 的速度。
  • 如果每次都強制回 HBM,會比 warm 狀態慢約 14%
  • 這個結果在第二個硬體平台上再次支撐了「HBM-free decode」這個架構論點。

7. 1B 規模下的量化底線(誠實的負結果)

在 1B-effective、step-107K checkpoint 上,使用對稱 per-group min-max quantization,搭配 WikiText-103 validation(30K tokens、L = 8):

配置PPL與 FP16 差距
FP16 基線56.6
INT4 naive(min-max、無 calibration)59.6+3.0
INT2 naive(min-max、無 calibration)39,524+39,467
INT2 + GPTQ(有 calibration)651+594

這裡釘死了三件事:

  • INT4 naive 在這個架構上是可以用的。 權重壓到 8× 時,PPL 只增加約 3。
  • INT2 naive 幾乎致命。 任何 per-token 類型的評估都會崩。
  • GPTQ 雖然把 gap 拉回很多,但還是不夠。 在 1B 規模下,低於 4-bit 的量化還需要更多額外機制。

這對部署故事的意義很直接:INT4 才是目前 production sweet spot,而上面的 Marlin + 非對稱校正路徑正是我們希望 dispatch 走到的那條路。

8. 端對端 runtime dispatch:真實的 Model.score() on 1 B-class INT4

Marlin kernel stack 已經接進 src/inference.cpp::quant_gemm,所以真實的 PyModel.generate() / .score() 呼叫會在 Ampere / Ada 上直接走 tensor cores,而不需要呼叫端做任何特殊處理。完整 pipeline 可參考:Marlin INT4 on Ada — how it works

路徑PPL備註
Native INT4(int4_gemm8.9755預設 TinyLoop scalar INT4 GEMM,理論上限約 6.8 TFLOPS
Marlin + 非對稱校正(已 dispatch)8.9157Δ −0.0597 PPL,約 0.67% drift;15 個權重已封包、約 101 MB Marlin VRAM 開銷、沒有 fallback

同一個模型上的 wall-clock A/B(2 次 warmup 後量 5 次 Model.score(loops=8)):

seq_lenNative INT4 medianMarlin dispatch median加速
128149.6 ms117.4 ms1.27×
256350.9 ms257.9 ms1.36×

這一段補齊的事情:

  • 一行 opt-in 就能開。 tl.pack_model_for_marlin(model) + tl.set_marlin_dispatch(True),或直接設 TINYLOOP_USE_MARLIN=1
  • 不需要 checkpoint migration。 現有 .tinyloop 非對稱 GPTQ 檔案,大約 2 秒內就能 pack 好後直接跑 Marlin。
  • 可以優雅回退。 Marlin CALL_IF table 不支援的 shape,會自動回 native INT4。
  • 不碰 Hopper。 kernels::marlin_available() 在 SM 9.0 會回 false,H100 仍建議走 FP16 body cache path。

四個 parity tests 加上一個端對端 test_marlin_e2e 目前都能在 4090 上通過;前置條件不滿足時也會 cleanly skip,因此 CI matrix 不會被非 GPU runner 拖垮。

9. iteration-axis delta-KV:完整前向路徑的 PPL A/B(2026-04-19)

delta-KV 方案沿著iteration 軸壓縮 KV cache,而這正是權重共享 loop 才有的冗餘:每個 token 保留一份 FP16 anchor h⁰,再存每個 iteration 的 INT8 delta Δh^l = h^l − h^(l-1),搭配 per-head FP16 scale grid。架構背景可參考:Architecture-Exclusive Features → Iteration-axis delta-KV

完整前向路徑的 PPL A/BTINYLOOP_KV_DELTA=1、407M 2 B-class INT4、20 個 prompts × 16 個 decode tokens,共 320 個 scored positions):

L = 8

模式PPLKL vs FP16top-1top-5wall-clock
fp163.7890100.00 %100.00 %96.3 ms
int8_kv3.7801.98 × 10⁻⁴99.69 %99.50 %105.2 ms
h_int8(前一代最佳)3.7004.03 × 10⁻³96.88 %97.19 %147.2 ms
delta_int83.7958.08 × 10⁻⁵99.06 %99.50 %135.4 ms

L = 32

模式PPLKL vs FP16top-1top-5wall-clock
fp169.3060100.00 %100.00 %286.4 ms
int8_kv9.2751.53 × 10⁻⁴98.75 %99.38 %317.7 ms
h_int8(前一代最佳)8.9624.71 × 10⁻³97.81 %96.25 %459.1 ms
delta_int89.2929.08 × 10⁻⁴99.38 %97.75 %442.7 ms

delta-INT8 per-head 在所有測試過的 L 上,都嚴格優於舊的 store-h-INT8

LKL 比值(delta / h_int8top-1 改善top-5 改善wall-clock
81 / 50+2.18 pp+2.31 pp快 7.9 %
321 / 5.2+1.57 pp+1.50 pp快 3.6 %

Phase A reconstruction-error backing study(20 個真實 prompts、per-row scale、pre-runtime):

Ldirect_int8 final errdelta_int8 final errdelta 優勢
80.02470.01471.68 ×
160.02190.00992.21 ×
320.01690.00592.85 ×

優勢會隨 L 增長而擴大,因為 trajectory 本身在收斂(ρ: 0.68 → 0.04,跨 32 次 iteration)。直接 per-iter 量化每一輪都付一次完整精度成本;delta 方案則只需要支付逐步縮小的 ‖Δh^l‖

Kernel-level validation(RTX 4090,SM_89)

Kernel測試 shape結果
delta_kv_reconstruct_int8(per-row)(T, D, L) up to (256, 2048, 32)對 Python reference 的 per-element drift ≤ 0.20 %
delta_kv_write_int8(per-row)同上四個 shape98.90–98.95 % INT8-exact,剩下約 1% 是 round tie-break
delta_kv_reconstruct_int8_layered_per_headruntime path(prefill + decode)✓ 透過 test_kv_delta_e2e_smoke 端對端驗證
delta_kv_write_int8_per_headruntime path✓ 端對端驗證
test_kv_delta_alloc(儲存空間)2 pre + 8 loop,D=2048total_bytes = 16,310,384 B 與解析解完全一致

這一段補齊的是:

  • 可直接替換 TINYLOOP_KV_H_MODE=1 + TINYLOOP_KV_INT8=1TINYLOOP_KV_DELTA=1 即可,在相近儲存量下更接近 FP16 品質。
  • 相對上一代 store-h-INT8,每個指標都更好。 KL 小 5–50×、top-1 高 1.5–2 個百分點、top-5 高 1.5–2 個百分點、wall-clock 快 3.6–7.9%。
  • 這是架構專屬特性。 非權重共享 Transformer 沒有天然的 Δh^l 收斂結構,因此沒有對等方案。
  • runtime 已經完整 ship。 Prefill capture、attention reconstruct-layered read path、decode ring-buffer support 都已接上。