Кэш KV — гэта невідзімая база дадзёў, якая спрацоўвае з памяцю GPU пад час дэкодавання LLM.
Кэш KV таяму спожывае памяць GPU пад час апрацоўкі LLM: попераднее заповнення протыва дэкадавання, матэматыка KV, GQA і MQA, PagedAttention, політыкі квантызаціі і выгнання.
Крыніцы: The KV Cache: AI’s Unseen Database Dominating GPU Memory
Асалідны бутылчасты канат у вырахоўванні LLM
Пад час вырахоўвання працэсоры GPU часта застаюцца без працы, пакалі пропускная здатнась цёрпілля задае ўсе большы кэш ключ-значэння. Кожны новы токен дадае тэнзоры K/V, якія неабходна перачытаць у наступных кроках. Сістэмы, якія ігнаруюць зростанне кэшу ключ-значэння, сталквуюцца з проблемамі кантэксту і затрымкаў задаўна, яшчэ перш чым заканчуецца колькасць FLOPs.
Як функцыонуе кэш ключ-значэння
Для апранткі неабходны ключы і значэння кожнага паканальнага токена. Іх кэшаванне дазволяе утрымацца ад перысчытання всіго прадку на кожны крок. Адплата за гэта — памяць, якая прыбліжна працоўвае лінейна з дужынай секвэнцы × колькасцю шароў × колькасцю галав × размер.
Prefill проты дэкодавання
Prefill обрабоўвае запіт і запаўняе кэш. Дэкодаванне дадае по аднаму токену, перачытваючы весь кэш на кожны крок. Prefill выкалічвае больш рэсурсаў, тады як дэкодаванне выкалічвае больш пропускной здатнась цёрпілля.
import torch
import torch.nn as nn
# Configuration representing a simplified LLM layer
batch_size = 1
num_heads = 8
head_dim = 64
hidden_dim = 512
# Projection layers for Keys and Values
W_k = nn.Linear(hidden_dim, num_heads * head_dim)
W_v = nn.Linear(hidden_dim, num_heads * head_dim)
# --- 1. PREFILL PHASE ---
# The prompt has 5 tokens. We process them simultaneously.
prompt_len = 5
prompt_embeddings = torch.randn(batch_size, prompt_len, hidden_dim)
# Project to K and V, then reshape to: [Batch, Heads, SeqLen, HeadDim]
kv_cache_k = W_k(prompt_embeddings).view(batch_size, prompt_len, num_heads, head_dim).transpose(1, 2)
kv_cache_v = W_v(prompt_embeddings).view(batch_size, prompt_len, num_heads, head_dim).transpose(1, 2)
print(f"Prefill complete. Cache K shape: {kv_cache_k.shape}")
# Output: [1, 8, 5, 64] (5 tokens cached)
# --- 2. DECODING PHASE ---
# We generate 3 subsequent tokens, one by one.
for step in range(3):
# Input is only the single, newly generated token (SeqLen = 1)
new_token_embedding = torch.randn(batch_size, 1, hidden_dim)
# Project only the new token's K and V
new_k = W_k(new_token_embedding).view(batch_size, 1, num_heads, head_dim).transpose(1, 2)
new_v = W_v(new_token_embedding).view(batch_size, 1, num_heads, head_dim).transpose(1, 2)
# Append the new token's representations to our historical cache
# This prevents recalculating the past prompt_len + step tokens
kv_cache_k = torch.cat([kv_cache_k, new_k], dim=2)
kv_cache_v = torch.cat([kv_cache_v, new_v], dim=2)
print(f"Decoding Step {step + 1} | Cache K shape: {kv_cache_k.shape}")
# Step 1 Output: [1, 8, 6, 64]
# Step 2 Output: [1, 8, 7, 64]
# Step 3 Output: [1, 8, 8, 64]
def calculate_kv_cache_gb(batch_size: int, seq_len: int, layers: int, hidden_dim: int, precision_bytes: int = 2) -> float:
# 2 represents the key and value states
total_bytes = batch_size * seq_len * layers * hidden_dim * 2 * precision_bytes
return total_bytes / (1024 ** 3) # Convert to Gigabytes
# Llama 2 70B architecture parameters (assuming standard MHA)
llama_70b_gb = calculate_kv_cache_gb(
batch_size=32,
seq_len=8192,
layers=80,
hidden_dim=8192
)
print(f"KV Cache Size: {llama_70b_gb:.2f} GB")
# Output: KV Cache Size: 320.00 GB
+---------------------------------------------------------------+
| GPU VRAM (80 GB) |
+------------------------------+--------------------------------+
| Static Model Weights (Fixed) | KV Cache (Dynamic / Variable) |
| [==========================] | [=======================> ] |
| FP16 Weights (e.g., ~35GB/GPU| Grows linearly with: |
| across split tensor setups) | Batch Size * Sequence Length |
+------------------------------+--------------------------------+
MHA (1:1 Ratio) GQA (Grouped Ratio) MQA (Many:1 Ratio)
[Q1][Q2][Q3][Q4] [Q1][Q2] [Q3][Q4] [Q1][Q2][Q3][Q4]
| | | | \ / \ / \ | / /
[K1][K2][K3][K4] [ K1 ] [ K2 ] [ K1 ]
[V1][V2][V3][V4] [ V1 ] [ V2 ] [ V1 ]
# A simple simulation of PagedAttention block mapping
class VirtualPageTable:
def __init__(self, block_size: int):
self.block_size = block_size
self.logical_to_physical = {} # Maps (request_id, block_index) -> physical_block_id
self.free_blocks = list(range(1000)) # Pool of physical memory blocks
def allocate_block_for_token(self, request_id: int, token_index: int) -> int:
# Determine which logical block the token falls into
logical_block_idx = token_index // self.block_size
key = (request_id, logical_block_idx)
# Allocate a new physical block only when the previous one is full
if key not in self.logical_to_physical:
physical_block = self.free_blocks.pop(0)
self.logical_to_physical[key] = physical_block
return self.logical_to_physical[key]
# Instantiating a table with a block size of 4 tokens
table = VirtualPageTable(block_size=4)
# Token 5 belongs to logical block 1 (tokens 4-7). We allocate physical space on demand.
physical_id = table.allocate_block_for_token(request_id=42, token_index=5)
print(f"Logical token mapped to physical block: {physical_id}")
FP16/BF16 Value: [0.12345678] (16 bits of precision)
↓ Quantization via Scaling Factor (e.g., scale = 127.0)
INT8 Value: [15] (8 bits of precision, 50% memory saved)
Sequence: [T1] [T2] [T3] [T4] [T5] [T6] [T7]
└───── Window Size (4) ─────┘
(T1 and T2 are evicted from active memory)
# Example: Initializing a vLLM engine with memory optimizations
from vllm import LLMEngine, EngineArgs
# Define engine arguments optimized for memory management
engine_args = EngineArgs(
model="meta-llama/Llama-3-8B-Instruct",
# 1. Quantization: Reduces KV cache footprint
quantization="fp8",
# 2. Paged KV Cache: Eliminates physical fragmentation
gpu_memory_utilization=0.90,
# 3. Dynamic Batching: Prevents static padding overhead
max_num_seqs=256
)
# Initialize the engine with built-in memory guardrails
engine = LLMEngine.from_engine_args(engine_args)
Механізмы дадзення новых даных
У рэалізацыях храняюцца буферы K/V для кожнага шара, і ў кожны крок запісваецца новы радок. Блэгі ў гэтым аспекце таямніча паслабляюць працэс атэнзіі — лепш выбіраць перавераныя кэрнелі.
Матэматыка памяці
Байты ≈ 2 (K і V) × шары × kv_heads × head_dim × seq_len × bytes_per_element. Дзялёны кантэкст у большых мадэлях можа займаць больш памяці, чым самі вагі.
Тэхнікі оптымізацыі
Перадзялёвка архітэктуры: MHA, GQA, MQA
Атэнзія з групаванымі запітамі і калькuluванням для адночасных запітоў зменшае колькасць KV-галав, таму для аднаковага дзялёвання памяці трэбуецца менша колькасць кэша.
PagedAttention
Ставіцеся да KV як да вярчынай памяці: выдзеляйце блокі за патрэбай, зменшайце фрагментацыю, ўможлівяйце большую групавання дадзеных.
Квантывацыя KV
Калі бюджэт на якасць дазваляе, зберагайце элементы кэшу з меньшай шчыліцей.
Слайдуючыя ваканы / вывядзенне
Адхромяйце або стысніце далёкія токены, калі задача падпрыемліе обмежаны час адказу.
Матрыца рашэнняў для працы
Выбірайце мадэлі GQA/MQA, калі ёсць обмежэння памяці; актыўвайце пагаваную увагу для высокай канкурантнасці; квантызуйце KV толькі пасля адгукнення; выкарыстоўвайце ваканы для чату, калі патрэбны локальныя кантэксты.
Заключэнне
KV-кэш — гэта невідразумелая база дадзеных для аутарэгрэсівнага дэкадавання. Аптымізуйце яго часткава — архітэктура, пагаванне, шчыліца, вывядзенне — або платыце за гэта GPU-магчымасцямі і затрымкамі.
Меравайце токены/секунду і байты KV на токан. «Шырэй» кернель, які удвойвае розмер кэшу, можа прасціпіцца пад навантажэнням з многамясцовым відвядзенням.
Документавайце максымальны кантэкст на SKU, каб продукт не могаў таямна запрашваць 128к на карцы, выдазванай для 8k.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвое збяльшвае розмер кэша, можа прасіць пад навантажэнням калькуляцый з адночасным вікорыстаннем.
Запісуйце максімальны обсяг кантэксту на кожны SKU, ўбліжчы, каб продукт не могаў таямніча запрашваць 128 кабацоў на картку, прызначаную для 8 кабацоў.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвое збяльшвае розмер кэша, можа прасіць пад навантажэнням калькуляцый з адночасным вікорыстаннем.
Запісуйце максімальны обсяг кантэксту на кожны SKU, ўбліжчы, каб продукт не могаў таямніча запрашваць 128 кабацоў на картку, прызначаную для 8 кабацоў.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвое збяльшвае паветрак кэша, можа прасіць про большыя рэсурсы пад навантажэнням калькуляцый для адночасных корыстувачаў.
Запісуйце максымальны обсяг кантэкста на адну SKU, каб продукт не могў таямна запрашваць 128 каляў, калі карта прызначаная для 8 каляў.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвое збяльшвае паветрак кэша, можа прасіць про большыя рэсурсы пад навантажэнням калькуляцый для адночасных корыстувачаў.
Запісуйце максымальны обсяг кантэкста на адну SKU, каб продукт не могў таямна запрашваць 128 каляў, калі карта прызначаная для 8 каляў.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыныма вікорыстаннем.
Запісуйце максімальны обсяг кантэкста на адну SKU, каб продукт не могаў таямна запрашваць 128 кабают на картку, прызначаную для 8 кабают.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыным вікорыстаннем.
Запісуйце максімальны обсяг кантэкста на адну SKU, каб продукт не могаў таямна запрашваць 128 кабают на картку, прызначаную для 8 кабают.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыныма вікорыстаннем.
Запісуйце максімальны обсяг кантэкста на адну SKU, каб продукт не могаў таямна запрашваць 128 кабают на картку, прызначаную для 8 кабают.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыным вікорыстаннем.
Запісуйце максімальны обсяг кантэкста на адну SKU, каб продукт не могаў таямна запрашваць 128 кабают на картку, прызначаную для 8 кабают.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыныма вядомасцямі.
Запісуйце максымальны обсяг кантэкста на адну SKU, каб продукт не могў таямна запрашваць 128 каляў на картцы, прызначанай для 8 каляў.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыным вядомасцям.
Запісуйце максымальны обсяг кантэкста на адну SKU, каб продукт не могў таямна запрашваць 128 каляў на картцы, прызначанай для 8 каляў.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыныма вікорыстаннем.
Запісуйце максімальны обсяг кантэкста на адну SKU, каб продукт не могаў таямна запрашваць 128 кабают на картку, прызначаную для 8 кабают.
Пры групаванні дадзеных памятайце, што крокі дэкадавання должны сінхронізавацца з найдужэйшым ланцугам; політыка даполнення і непаўзлівое групаванне маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькі токена за секунду і колькі байт KV на токен. “Шырэйшы” кернель, які удвоюе розмер кэша, можа прасціпіцца пад навантажэнням калькуляў з аднойчыным вікорыстаннем.
Запісуйце максімальны обсяг кантэкста на адну SKU, каб продукт не могаў таямна запрашваць 128 кабают на картку, прызначаную для 8 кабают.
Пры групаванні дадзеных памятаце, што крокі дэкадавання должны сінхронізавацца з найдольшым ланцугам дадзеных; політыка заполнення і безперывна групавання маюць такое ж значэнне, як і чыстая пропускная здатнасць.
Замерайце колькасць токенаў за секунду і колькісць байтав KV на токан. “Шырэй” кернель, які подвойвае розмер кэша, можа прасціягнуцца пад навантажэнням калькуляцый з удзелам колькох корыстувачаў.
Асіметрыя ў прадзеўленні/дэкадаванні таксама вплывае на автаскаляванне: расты ў дужжыне запітоў збільшваюць навантажэння на прадзеўленне, а дужгі час генеравання — на пропускную здатнасць. Неабходныя розміры пулоў для обох сцэнарыяў.
Пораўняйце заявы віробнікаў пра “дужгі кантэкст”, запытавшыся пра колькісць байтав KV пад максімальной дужжынай з урахоўванням вашага тыпу дадзеных і розмеру пакета — а не пра колькісць токенаў, наводзючыхся ў маркетынгу.
Політыкі вывядзення дадзеных должны быць адповідныя продукту: падсумаванне выключэных фрагментаў дадзеных лепшая, чым ўсуненне іх без следу, для агентаў падтрымкі, якія цытуюць пакульнія крокі обработкі.
Скедуляры безперывнага групавання дадзеных, якія ігнаруюць фрагментацыю дадзеных KV, таксама можу страждзець ад нехваткі памяці; структуры з пагінаванням і механізмы дэфрагментацыі павінны быць частынай той жа версіі продукту.
Асіметрыя падчыплення/дэкодавання таксама вплывае на аўтаскалярызацію: растышчэнне дужоў запіту збільшвае навантажэння на працэс падчыплення, а дужа дзейсныя генерэрацыі — навантажэння на пропускную здатнась. Для обох форм трэба выступкі памяці.
Порашчырайце тверджэння віробнікаў пра „дзялгі кантэкст“ за дапамогою запіту пра колькасць байтов KV пры максімальной дужоўзе з урахавленнем вашага тыпу дадзенняў і розмеру пакета — а не маркетынгавых тэрмінаў.
Палітыкі вываджэння з працы должны быць адповідныя продукту: падсумаванне выключэных частак кантэксту лепшая, чым мовчазнае забыванне, для агентаў падтрымкі, якія цітуюць пакульнія крокі.
Скеджулеры стацыонарнага пакетавання, якія ігнаруюць фрагментацыю KV, яшчэ і так спрачынаюць нехватку памяці; структуры з раздзеленнем на стораны і механізмы дэфрагментацыі павінны быць частынай той жа версіі продукту.
Асіметрыя падчыплення/дэкодавання таксама вплывае на аўтаскалярызацію: растышчэнне дужоў запіту збільшвае навантажэння на працэс падчыплення, а дужа дзейсныя генерэрацыі — навантажэння на пропускную здатнась. Для обох форм трэба выступкі памяці.
Пораўняйце тверджэнні прымоўчальнікаў аб «дзягучым кантэксце», запрашаючы колькасць байтов KV пры максімальной дужыні, уз вашы тып дадзеных і размер пакета — а не маркетынгавыя токены.
Палітыкі адключэння павінны быць усведамленыя пра продукт: стварэнне апісу выключаных фрагментаў лепшая, чым мовчазнае забыванне, для агентаў падтрымкі, якіе цітуюць паканянне ранейшых запытаў.
Раскладчыкі непаўзлівага формавання пакетаў, якія ігнаруюць фрагментацыю KV, яшчэ і так спрачоўваюць недастатак памяці; структуры з раздзеленнем на стораны і механізмы дэфрагментацыі павінны быць уключаныя ў адно выданне.
Асіметрыя падчысквання/дэкадавання таксама вплывае на автаскаляроўканне: разбіжнасці ў дужыне запытаў перавантажуюць працэс падчысквання, а дзягучыя генераціі — пропускную здатнась. Неабходныя буферы памяці для обох фарматаў.
Пораўняйце тверджэнні прымоўчальнікаў аб «дзягучым кантэксце», запрашаючы колькасць байтов KV пры максімальной дужыні, уз вашы тып дадзеных і размер пакета — а не маркетынгавыя токены.
Палітыкі адключэння павінны быць усведамленыя пра продукт: стварэнне апісу выключаных фрагментаў лепшая, чым мовчазнае забыванне, для агентаў падтрымкі, якіе цітуюць паканянне ранейшых запытаў.