Skip to content

Fix VRAM usage increase on HIP/ROCm with RDNA3 and possibly more - #71

Closed
stragulus wants to merge 2 commits into
TheTom:feature/turboquant-kv-cachefrom
stragulus:bug/vram-usage-rows-with-rocm
Closed

Fix VRAM usage increase on HIP/ROCm with RDNA3 and possibly more#71
stragulus wants to merge 2 commits into
TheTom:feature/turboquant-kv-cachefrom
stragulus:bug/vram-usage-rows-with-rocm

Conversation

@stragulus

Copy link
Copy Markdown

Overview

Fixes unbounded VRAM usage growth when using flash attention and quantized kv cache types on HIP/ROCm with RDNA3 architecture (and possibly more)

Additional details

This PR batches flash attention kernel invocation with a fixed size batch when quantized kv cache types are used. Without this change, the kernels would increasingly allocate more vram as they require the entire kv cache to be dequantized to f16 types. A compounding problem existed when using the legacy memory pool ggml_cuda_pool_leg (e.g. when using HIP with at least RDNA3). The memory pool allocates fragments of a fixed size, and allocation requests use a best-fit algorithm. Allocation requests that don't fit into any of the existing individual fragments trigger allocation of a new fragment. The pool never deallocates fragments until the pool itself is destructed. Due to the ever-growing sizes of the allocation requests, new allocations would not fit in a previously created fragment, thus another new fragment would be allocated. This rapidly consumes more and more vram, completely cancelling out any benefit to be had from using quantized kv cache types for non-synthetic workloads.

The VEC flash attention kernel solves this by dequantizing the kv cache entries one by one instead of all in advance. Simple, however, this has a very significant impact on prompt processing speed (easily ~50% reduction compared to the other kernels like TILE). While this does completely solve the growing vram usage problem, the performance impact is not acceptable.

Thus, I chose a hybrid solution where we don't fall back to slow VEC, but instead the flash attention kernel calls are batched with a maximum batch size if quantized kv types are used. The code will choose the previous unbatched path for maximum performance if batching is not required. Benchmarks show that prompt processing speeds for a 30k prompt and turbo3 k/v types are ~700 tokens per seconds, versus ~3xx tokens/sec when using the VEC kernel. VRAM usage stays stable, also tested with a 120k bench that shows 0 new allocations after prompt processing starts.

Token generation speed should be unaffected by these changes.

This SHOULD work on the other flash attention kernels as well, but I did not test this.

Passed basic coding challenge using opencode against llama-server with turbo3/turbo3 k/v quants

Tested on an arch linux system with a 7900 XTX/24GB and rocm-7.2.1

Benchmarks

Before changes:

VRAM flat at 18024
| qwen35 27B Q4_K - Medium       |  15.39 GiB |    26.90 B | ROCm       | 100 |  1 |         pp30000 |        697.66 ± 0.00 |
| qwen35 27B Q4_K - Medium       |  15.39 GiB |    26.90 B | ROCm       | 100 |  1 |           tg128 |         28.81 ± 0.00 |

VRAM grows steadily during run to 17856, will keep increasing with longer prompts
| qwen35 27B Q4_K - Medium       |  15.39 GiB |    26.90 B | ROCm       | 100 |   q8_0 |   q8_0 |  1 |         pp30000 |        697.05 ± 0.00 |
| qwen35 27B Q4_K - Medium       |  15.39 GiB |    26.90 B | ROCm       | 100 |   q8_0 |   q8_0 |  1 |           tg128 |         28.35 ± 0.00 |

VRAM grows steadily during run to 17222, will keep increasing with longer prompts
| qwen35 27B Q4_K - Medium       |  15.39 GiB |    26.90 B | ROCm       | 100 | turbo3 | turbo3 |  1 |         pp30000 |        695.24 ± 0.00 |
| qwen35 27B Q4_K - Medium       |  15.39 GiB |    26.90 B | ROCm       | 100 | turbo3 | turbo3 |  1 |           tg128 |         28.18 ± 0.00 |
After changes:

VRAM flat at 18024
| qwen35 27B Q4_K - Medium | 15.39 GiB | 26.90 B | ROCm | 100 | 1 | pp30000 | 695.04 ± 0.00 |
| qwen35 27B Q4_K - Medium | 15.39 GiB | 26.90 B | ROCm | 100 | 1 | tg128 | 27.92 ± 0.00 |

VRAM usage stable at 17124MB
| qwen35 27B Q4_K - Medium | 15.39 GiB | 26.90 B | ROCm | 100 | q8_0 | q8_0 | 1 | pp30000 | 698.56 ± 0.00 |
| qwen35 27B Q4_K - Medium | 15.39 GiB | 26.90 B | ROCm | 100 | q8_0 | q8_0 | 1 | tg128 | 28.82 ± 0.00 |

VRAM stable at 16508MB
| qwen35 27B Q4_K - Medium | 15.39 GiB | 26.90 B | ROCm | 100 | turbo3 | turbo3 | 1 | pp30000 | 675.35 ± 0.00 |
| qwen35 27B Q4_K - Medium | 15.39 GiB | 26.90 B | ROCm | 100 | turbo3 | turbo3 | 1 | tg128 | 27.47 ± 0.00 |




- I have read and agree with the [contributing guidelines](https://github.com/ggml-org/llama.cpp/blob/master/CONTRIBUTING.md)
- AI usage disclosure: YES, used claude to assist finding the problem, and prototype solutions

having to dequantize the entire kv cache, thus preserving vram memory
… this commit fixes.

1. fattn-common.cuh sinks fix: Only pass in sinks on the first batch call

2. fattn-vec.cuh mask stride fix: align with how fattn-tile and fattn-wmma already compute the mask row stride.
@stragulus stragulus closed this by deleting the head repository Apr 12, 2026
@stragulus

Copy link
Copy Markdown
Author

I had to wipe out my fork as it somehow blocked forking llama.cpp itself - since these changes are not related to the turbo quants themselves and are a pre-existing issue in llama.cpp, I will instead add the PR there. ggml-org#19979

fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Jul 20, 2026
Die 'Priorisierung nach Cost-Benefit' Sektion listete noch M1-M4 Items
als 'Sofort umsetzbar' obwohl alle ✅/❌ sind. Aktualisiert auf M6
(Forschung):

- Tier 2: AtomicBot-ai#36 Auto Parameter Fitting TP (einziger offener Tier-2-Item)
- Tier 3: 11 Items nach Cost-Benefit sortiert (AtomicBot-ai#73, AtomicBot-ai#72, AtomicBot-ai#71, TheTom#76, ...)
- Tier 4: 15 Forschungs-Items (3+ Monate Solo-Agent)
- Verschobene Items (⏭️) mit Re-Eval-Hinweisen

Header-Datum auf 2026-07-19 aktualisiert.
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Jul 20, 2026
…cBot-ai#71 CPU-GPU MoE ⏭️ Tiefen-Evals

3 parallele Tiefen-Evals für Tier-3 Items:

AtomicBot-ai#73 CascadeInfer (arXiv:2512.19179) ❌ VERWORFEN:
- Multi-Instance-Cluster-Scheduler für vLLM (3000 LoC Python + 700 C++)
- Fork ist Single-Instance, Architektur-Mismatch fundamental
- 67% Latenz-Reduktion nur unter Cluster-Load (16+ GPUs)
- Kein Host im LAN hat Multi-Instance-Setup
- Kein Referenzcode, Aufwand 2-3 MONATE nicht 2-3 Wochen

AtomicBot-ai#72 N4_0 Native 4-bit Float (PR ggml-org#23572) ❌ VERWORFEN:
- PR stale (7 Wochen, kein Maintainer-Approval, NVFP4-Experte lehnt ab)
- N4_0 ist nur CLI-Alias für NVFP4 (6 Zeilen), kein neues Feature
- Backend-Infrastruktur existiert bereits im Fork
- Zero Hardware-Benefit: Kein Blackwell im Fleet (FP4 MMA nur RTX 50xx)
- Auf Ada/Ampere: DP4A-Fallback, Accuracy-Regression vs Q4_0
- Reaktivierung: 30 Min Cherry-Pick wenn Blackwell-Host kommt

AtomicBot-ai#71 Efficient CPU-GPU Collaborative MoE (arXiv:2512.16473) ⏭️ SPÄTER:
- Fork hat bereits 1883-Zeilen MoE-Cache (moe-cache.cu)
- AtomicBot-ai#71 ist Upgrade auf set-associative (N×M), kein Neubau
- Aufwand revidiert: 3-4 Wochen → 2 Wochen (CUDA-only)
- Styx + Hydra profitieren (8GB VRAM, PCIe-Bottleneck)
- Bedingung: erst nach AtomicBot-ai#69 Heuristic-Benchmark (Konfundierung vermeiden)
- Referenzcode: github.com/elsa-lab/MoE-CPU-GPU-Collaborative-Inference (MIT, PyTorch)

Priorisierung aktualisiert: 9 offene Tier-3 Items statt 11.
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Jul 20, 2026
…efen-Evals — Tier-3 komplett

Letzte 2 Tiefen-Evals für Tier-3 Items:

AtomicBot-ai#43 SliderQuant (Sliding-layer PTQ) ⏭️ SPÄTER (conditional):
- PTQ, Fork nutzt QAT Q4_K_XL (methodisch überlegen, wie AtomicBot-ai#22 GWQ)
- Referenzcode vorhanden (github.com/deep-optimization/SliderQuant, PyTorch)
  aber hilft nicht beim C++/Vulkan-Port
- Vulkan-Kernel-Neuentwicklung hochriskant für Mars/Venus
- Hardware-Relevanz niedrig: QAT löst Low-Bit-Druck bereits
- Aufwand revidiert: 4-6 → 6-10 Wochen
- Reaktivierung nur bei QAT-Lücke für spezifisches Zielmodell

AtomicBot-ai#44 Alloc-MoE (Budget-aware Expert Activation) ☐ MACHBAR GESTAFFELT:
- Alloc-L (Layer-Level, DP + Sensitivity-Profiling) ist MVP mit 3-4 Wochen
- Alloc-T (Token-Level) bringt bei Decode T=1 nichts — 1.34× decode-Speedup
  stammt aus Alloc-L (reduziertes K), nicht Alloc-T
- Synergie mit AtomicBot-ai#40 (Freq-Tracking als Input) und AtomicBot-ai#62 (Profiler-Infra)
- Kein Konflikt mit moe-cache.cu (synergistisch)
- Styx profitiert klar (CPU-bound → halbe Aktivierungen = halbe CPU-Last)
- RISIKO: 17% Quality-Drop bei K=2 (Gemma 4 A4B) — Quality-Benchmark vor Go
- Aufwand revidiert: 6-8 → 3-4 Wo (Phase 1) / 7-9 Wo (voll+Vulkan)

Tier-3 Tiefen-Evals KOMPLETT (11/11 Items):
- ☐ Machbar: AtomicBot-ai#71, AtomicBot-ai#44, AtomicBot-ai#18 (gestaffelt, mit Solo-Plan)
- ⏭️ Später: TheTom#76, AtomicBot-ai#75, AtomicBot-ai#23, AtomicBot-ai#43 (Abhängigkeiten/Risiko)
- ❌ Verworfen: AtomicBot-ai#73, AtomicBot-ai#72, AtomicBot-ai#74, AtomicBot-ai#22 (Architektur/Redundanz/QAT-Konflikt)

Priorisierung aktualisiert: 3 machbare + 4 spätere + 4 verworfene.
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Jul 20, 2026
Styx Benchmark 2026-07-21 (GTX 1070, 26B-A4B QAT, -ncmoe 20, turbo3/4,
FA, Budget=512MB, Reserve=256MB):

| Policy    | tg128 t/s       | Hit-Rate | Slots |
|-----------|-----------------|----------|-------|
| LRU       | 28.16 ± 0.92    | 39.7%    | 320   |
| Heuristic | 27.28 ± 0.75    | 39.9%    | 320   |

Heuristic -3.1% langsamer als LRU, 5%-Schwelle deutlich verfehlt.
Hit-Rate identisch → Heuristic trifft dieselben Eviction-Entscheidungen
wie LRU auf diesem Workload.

Root Cause: α=0.7/β=0.3 Frequency korreliert stark mit Recency auf
tg128-Runs. Paper's 2.6× bezieht sich auf SSD-Offloading (nicht
CPU-Offload wie Styx) — Hebel kleiner.

Phase 2 (FlashMoE FFN) ❌ verworfen — wenn schon einfache Heuristic
kein Win, ist FFN-Version skeptisch. AtomicBot-ai#69 → ❌.

Voraussetzung für AtomicBot-ai#71 (CPU-GPU Collaborative MoE) und AtomicBot-ai#18 (DALI
Cache-Policy) erfüllt — beide können jetzt starten.

Throttle=1 → Bail-out für beide Policies bestätigt (Cache-Overhead
> CPU-Path bei 1.3-1.6ms vs 540-615us pure-CPU).
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Jul 20, 2026
N-index M-way set-associative cache für moe-cache.cu.
- Voraussetzung (AtomicBot-ai#69 Benchmark) erfüllt (2026-07-21: ❌ NO-GO)
- 2 Wochen Aufwand, CUDA-only (Styx/Hydra)
- MIT-Referenzcode (github.com/elsa-lab/MoE-CPU-GPU-Collaborative-Inference)
- 4 Phasen: Design (2-3d), Impl (4-5d), Bench (3-4d), Doku (1-2d)
- Go/No-Go-Gate: >5% Speedup vs LRU
- Trilium: Note 0PCCcdM2WgpJ (M6 Tier-3 Evals), TTT 2026-07-21
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Aug 2, 2026
… Cache — ❌ NO-GO

Implementiert set-associative Cache (POLICY_SET_ASSOC_LRU) als neue
Eviction-Policy in moe-cache.cu. N×M Slots mit LRU pro Set, Hash-basiertes
Set-Mapping (key % n_sets). Env-Vars: GGML_CUDA_MOE_CACHE_POLICY=set-assoc-lru
+ GGML_CUDA_MOE_CACHE_SET_WAYS=M (Default 4).

Benchmark auf hydra (RTX 3070, 8GB, 26B-A4B QAT, -ngl 0):
- 512MB (160 slots): LRU tg64=1.90, 40×4=1.42 (-25%), 80×2=0.94 (-50%), 20×8=1.13 (-41%)
- 1024MB (320 slots): LRU tg128=1.36, 80×4=1.34 (-1.5% Rauschen), 40×8=1.35 (-0.7% Rauschen)

Go/No-Go >5% → ❌ NO-GO. Root Cause: 128 Experten × 30 Layer = 3840 Entries
→ 160-320 Slots = starke Oversubscription. Fully-associative LRU hat keine
Conflict-Misses, Set-assoc restrictiert Placement. HashMap O(1) nicht
Bottleneck (PCIe dominiert). Paper's 4.4× war RTX 4090 + SSD-Offloading.

Code bleibt als POLICY_SET_ASSOC_LRU (Default OFF) für Modelle mit wenigen
Experten (≤16). ROADMAP AtomicBot-ai#71 → ❌.
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Aug 2, 2026
… ❌ NO-GO

Implementiert sliding-window workload accumulation (DALI Phase 1) als neue
Eviction-Policy. Windowed frequency pro Slot, periodic reset nach wsize
plan()-Calls, evict slot mit niedrigstem workload_score. Env-Vars:
GGML_CUDA_MOE_CACHE_POLICY=workload + GGML_CUDA_MOE_CACHE_WSIZE=N (Default 32).

Benchmark auf hydra (RTX 3070, 8GB, 26B-A4B QAT, -ngl 0):
- n=128 r=3: LRU tg128=2.19, Workload=3.97 (+81%) — Artefakt (Discovery-Phase)
- n=256 r=5: LRU tg256=4.42, Workload=4.53 (+2.5%), Heuristic=4.50 (+1.8%)
- n=512 r=5: 512MB: Workload=4.40 (-1.1%); 1024MB: Workload=4.54 (+0.4% Rauschen)

Go/No-Go >5% → ❌ NO-GO. Root Cause: 128 Experten × 30 Layer = 3840 Entries
→ 160-320 Slots = 24:1 Oversubscription. Eviction-Policy nicht Bottleneck
(PCIe dominiert). LRU ist 'good enough'.

MoE-Cache-Thema für 128-Expert-Modelle erschöpft:
AtomicBot-ai#69 Heuristic ❌, AtomicBot-ai#71 Set-Assoc ❌, AtomicBot-ai#18 Workload-Aware ❌

Code bleibt als POLICY_WORKLOAD (Default OFF). ROADMAP AtomicBot-ai#18 → ❌.
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Aug 2, 2026
…ommen

4 Eviction-Policies dokumentiert: LRU (Default), Heuristic, Set-Associative,
Workload-Aware. Env-Vars und ROADMAP-Referenzen (AtomicBot-ai#18/AtomicBot-ai#69/AtomicBot-ai#71) eingetragen.
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Aug 2, 2026
… ROADMAP

P1 AtomicBot-ai#3: Pool-Alloc in moe_cache_begin unter g.mu Lock — verhindert Data Race
mit Worker/Backfill-Threads die d.pools[pi] lesen. cudaMalloc unter Lock
akzeptabel (nur bei Pool-Erstellung, nicht im Hot Path).

P2: Workload-Eviction Tie-Breaker — bei gleichem workload_score den LRU-
ältesten Slot evicten (cur_tick - last_access). Verhindert deterministische
Slot-0-Eviction nach Window-Reset.

P3: ROADMAP 'MoE-Cache erschöpft' Sektion — fasst AtomicBot-ai#69/AtomicBot-ai#71/AtomicBot-ai#18 Ergebnisse
zusammen. LRU ist optimal bei 128-Expert 24:1 Oversubscription.
fukuro-kun referenced this pull request in fukuro-kun/fukuro-llama-cpp-turboquant Aug 2, 2026
…i#18/AtomicBot-ai#44 ❌ NO-GO Cleanup

-358 Zeilen experimenteller Code aus 3 ❌ NO-GO Experimenten entfernt:

AtomicBot-ai#71 POLICY_SET_ASSOC_LRU: N-index M-way set-associative Cache (-25% bis -50%)
AtomicBot-ai#18 POLICY_WORKLOAD: DALI workload-aware windowed-frequency Cache (±0%)
AtomicBot-ai#44 LLAMA_MOE_K_OVERRIDE: K-Reduktion (+13.5% PPL bei K=6)

Entfernt:
- enum eviction_policy: POLICY_SET_ASSOC_LRU, POLICY_WORKLOAD
- struct moe_cache_slot: workload_score Feld
- struct moe_cache_pool: n_sets, n_ways, set_lru_head/tail, set_n_used, window_count
- struct moe_cache_global: set_assoc_ways, workload_wsize
- Set-associative LRU-Helfer (moe_cache_set_idx, set_lru_remove, set_lru_push_back)
- Workload Reset-Logik und Workload-Eviction-Zweig
- Env vars: GGML_CUDA_MOE_CACHE_SET_WAYS, GGML_CUDA_MOE_CACHE_WSIZE
- GGML_CUDA_MOE_CACHE_POLICY: set-assoc-lru und workload cases
- src/llama-model.cpp: LLAMA_MOE_K_OVERRIDE Block

Behalten:
- POLICY_LRU (default, funktioniert)
- POLICY_HEURISTIC (bestehend, funktioniert)
- Grundlegende Cache-Infrastruktur (slots, map, lru_head/tail, freq, last_access)
- Prefetch/backfill/worker Logik

Build grün auf Hydra. MoE-Cache initialisiert korrekt mit LRU-Policy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant