NPU跑LLM实战指南:KV Cache动态性如何突破硬件限制
NPU跑LLM实战指南:KV Cache动态性如何突破硬件限制
副标题: 从预分配+Attention Mask到三层软件栈,完整解析NPU推理架构
痛点:为什么NPU跑LLM这么难?
LLM的生成机制和NPU的硬件特性存在根本冲突:
| LLM特性 | NPU特性 | 冲突点 |
|---|---|---|
| 逐token生成 | 固定shape执行 | KV Cache动态增长 |
| 动态长度 | 编译时确定 | 无法预知prompt长度 |
| Attention全量计算 | 固定计算单元 | 无法动态扩展 |
核心问题:
LLM生成文本是逐token的,每步都需要attention前面所有token。KV Cache存储K和V向量,避免重复计算。但每生成一个token,cache就增长一行(动态)。NPU要求固定shape的tensor,需要编译时确定。
一、KV Cache的动态性 vs NPU的静态模型
1.1 问题本质
# LLM的KV Cache增长过程defllm_generation():""" LLM逐token生成过程 """kv_cache=[]# 动态增长fortokeninprompt:k,v=compute_kv(token)kv_cache.append((k,v))# 每步增长output=attention(kv_cache)# 全量计算# 生成阶段whilenoteos:new_token=generate(kv_cache)k,v=compute_kv(new_token)kv_cache.append((k,v))# 继续增长NPU的约束:
NPU编译时需要知道: - Tensor的shape(固定) - 计算图的拓扑(固定) - 内存分配(固定) 但KV Cache是动态增长的!1.2 冲突分析
| 维度 | LLM需求 | NPU约束 |
|---|---|---|
| 内存 | 动态分配 | 预分配固定大小 |
| 计算 | 可变长度 | 固定shape |
| 编译 | 运行时决定 | 编译时确定 |
二、核心解决方案:预分配 + Attention Mask
2.1 预分配策略
核心思路:既然NPU需要固定大小,就预分配一个足够大的缓冲区。
# 预分配参数MAX_PROMPT_LEN=1024# prefill最大处理token数MIN_RESPONSE_LEN=128# 预留给生成的token数KV_CACHE_CAPACITY=MAX_PROMPT_LEN+MIN_RESPONSE_LEN# 1152# 物理大小不变(始终1152),变化的只是mask中1的个数2.2 Attention Mask机制
importnumpyasnpclassKVCache:""" 预分配KV Cache + Attention Mask """def__init__(self,capacity=1152):self.capacity=capacity# 预分配固定大小的KV Cacheself.K=np.zeros((capacity,hidden_dim))self.V=np.zeros((capacity,hidden_dim))# Attention Mask: 1=有效, 0=空位self.mask=