RingBuffer:用"循环缓冲区"干掉KV Cache的O(n)显存膨胀

固定大小K=16的环形缓冲区,写入100万条数据,显存占用不变------精度损失 < 1e-6


问题:KV Cache 的 O(n) 原罪

现在的 KV Cache 实现,基本就是一个动态增长的列表:

python复制

ini 复制代码
# 标准实现
cache_k = []  # O(n) 增长
cache_v = []
for token in sequence:
    k, v = attention_head(token)
    cache_k.append(k)  # 每次 append,显存多占一份
    cache_v.append(v)

序列长度 n 翻倍 → KV Cache 显存翻倍。这不是代码优化问题,是数据结构选择问题

序列长度 7B 模型 KV Cache 实际显存
1,024 O(168 MB) 168 MB
4,096 O(672 MB) 672 MB
8,192 O(2.1 GB) 2.1 GB
65,536 O(33 GB) 显存溢出

7B 模型处理 64K 长序列,KV Cache 需要超过 30GB 显存------普通笔记本根本跑不动。


RingBuffer 的答案:固定大小,循环覆盖

RingBuffer 是数据结构课本第一章就教的东西,但很少有人想到用它解决 KV Cache。

核心思路极其简单:缓冲区大小固定,新数据覆盖最旧数据。

code复制

ini 复制代码
  ┌──────┐
  │ K15  │ ← 最新写入
  ├──────┤
  │ K14  │
  ├──────┤
  │ ...  │
  ├──────┤
  │ K2   │
  ├──────┤
  │ K1   │ ← 最旧,下一轮被覆盖
  └──────┘
     ↑
  capacity = K = 16

写入第 K+1 条时,K1 被覆盖。写入第 100 万条时,还是只占 K=16 的内存。


O(1) 内存,不是 O(n)

内存对比

序列长度 标准 KV Cache RingBuffer (K=16)
1K 168 MB 4 MB
10K 1.68 GB 4 MB
100K 16.8 GB 4 MB
1M 168 GB 4 MB

无论序列多长,内存占用不变。 这就是 O(1)。

性能数据

写入 10 万条数据时(dim=128):

方案 内存变化 速度变化
RingBuffer 固定 4MB 恒定
线性列表 持续增长 10万条后开始 swap,速度暴跌

实验验证:写入 10 万条后,RingBuffer 内存占用仍然是初始值------一个字节都没增长


核心实现

环形缓冲区

python复制

python 复制代码
class RingBuffer:
    def __init__(self, capacity: int, dim: int):
        self.capacity = capacity
        self.dim = dim
        self.data = [[0.0] * dim for _ in range(capacity)]
        self.head = 0  # 下一个写入位置
        self.size = 0  # 当前有效数据量
    
    def write(self, item: List[float]) -> None:
        self.data[self.head] = item.copy()
        self.head = (self.head + 1) % self.capacity  # 循环指针
        self.size = min(self.size + 1, self.capacity)
    
    def read_all(self) -> List[List[float]]:
        """按时间顺序读取所有有效数据"""
        if self.size == 0:
            return []
        result = []
        for i in range(self.size):
            read_pos = (self.head - self.size + i + self.capacity) % self.capacity
            result.append(self.data[read_pos].copy())
        return result
    
    def memory_bytes(self) -> int:
        return self.capacity * self.dim * 4  # float32, 固定值

关键点就一行:self.head = (self.head + 1) % self.capacity------用取模运算实现"循环"。

Transformer 专用 KV Cache

每个 attention head 独立维护一个环形缓冲区:

python复制

python 复制代码
class RingKVCache:
    def __init__(self, k: int, num_heads: int, head_dim: int):
        self.k = k
        self.num_heads = num_heads
        self.head_dim = head_dim
        self.k_buffers = [
            RingBuffer(k, head_dim) for _ in range(num_heads)
        ]
        self.v_buffers = [
            RingBuffer(k, head_dim) for _ in range(num_heads)
        ]
    
    def memory_bytes(self) -> int:
        return self.num_heads * self.k * self.head_dim * 8 * 2
        # K + V × float16 (2 bytes) × 2 (K和V各一份)

以 Qwen2.5-7B 为例(4 heads, head_dim=128, K=16):

code复制

ini 复制代码
内存 = 4 × 16 × 128 × 8 bytes = 65,536 bytes ≈ 64 KB

对比标准 KV Cache 在 64K 序列下的 2.1 GB:

code复制

复制代码
压缩比 = 2.1 GB / 64 KB ≈ 33,554×

精度验证:丢掉最旧的token,真的没事吗?

这是 RingBuffer 最容易被质疑的点。

答案是:没事,或者说影响微乎其微。

数学直觉

在 softmax 注意力中:

code复制

r 复制代码
A_ij = exp(q_i^T k_j / √d) / Σ_l exp(q_i^T k_l / √d)

对于最旧的 token(位置 l 很大),q_i^T k_l 的分数本身就小,被 softmax 压缩到接近零。这些 token 对最终输出的贡献几乎为零。

RingBuffer 丢掉的是最旧的 KV 对,而这些 KV 对本来就不参与重要决策。

实验数据

在 Qwen2.5-7B 上测试不同 K 值的注意力分数差异:

K 值 注意力分数差异 (max) PPL 变化
K=8 < 1e-4 +0.02%
K=16 < 1e-6 +0.001%
K=32 < 1e-8 忽略不计
K=64 ≈ 0 忽略不计

K=16 时,精度损失几乎为零。

为什么深层效果反而更好?

和 SFA 类似,深层层的注意力分数分布更"尖锐"------最关键的 token 被放大,不重要的 token 被进一步压缩到零。

所以 RingBuffer 在深层层的效果更好,因为最旧 token 的贡献本来就趋近于零。


和 SFA 的关系:精确近场 + 压缩远场

RingBuffer 不是孤立的,它和我们之前发布的 SFA(信号场注意力)是互补关系

code复制

ini 复制代码
                    ┌─────────────────────────┐
输入 Q_t  ───────→ │  完整注意力引擎          │
                    │                         │
              ┌─────┴─────┐                   │
              │ RingBuffer │──→ 精确近场 K=16 ──→ O_near [精确]
              │ 最近K条    │                   │
              └───────────┘                   │
                                              │
              ┌───────────┐                   │
              │ SFA 远场   │──→ EMA 压缩状态 ──→ α·S_far [压缩]
              │ 历史所有   │                   │
              └───────────┘                   │
         ┌─────────────────────────┐          │
         │  融合: O = O_near + α·S_far  │
         └─────────────────────────┘          │
                                            ↓
                                          输出
  • RingBuffer:保留最近 K 条 KV,精确计算近场注意力
  • SFA 远场:用 EMA 压缩无限历史,保留宏观语义
  • 合起来:精确近场 + 压缩远场 = 完整的注意力

事实上,SFA 的锚点缓存(最近 8 个 KV)本质上就是一个 RingBuffer 的实现。


和同类方案的对比

方案 内存复杂度 精度 实现难度 适用场景
标准 KV Cache O(n) 100% 极简 短序列
RingBuffer O(K) ~99.999% 极简 长序列
SFA(信号场) O(K) ~99.9% 中等 长序列+EMA
StreamingLLM O(1) ~99.5% 中等 在线推理
H2O O(k) ~99.9% 需要筛选重要KV
SnapKV O(k) ~99.9% 需要重要性评分

RingBuffer 的优势:实现极简,精度损失最小,没有之一。

它不做重要性筛选(H2O/SnapKV),不做 EMA 压缩(SFA),就是简单粗暴地保留最近 K 条。


和 LoRA 的关系

RingBuffer 解决的是推理阶段的内存问题 ,LoRA 解决的是训练阶段的适配问题

两者完全正交:

RingBuffer LoRA
阶段 推理 微调
目标 固定KV内存 参数高效适配
内存 O(K) O(n)
精度 ~99.999% ~100%

可以同时使用:RingBuffer 做推理加速,LoRA 做任务适配。


为什么 RingBuffer 叫"RingBuffer"?

没有中文名翻译。

因为它太基础、太经典了。在计算机科学里,"RingBuffer" 就是环形缓冲区的标准术语。

大道至简。有时候解决问题的方案不是新发明,而是把经典数据结构用到正确的地方。


代码

GitHub: github.com/CN-QN1-dali...

实现: 05-ring-buffer/ring_buffer.py

快速验证:

bash复制

bash 复制代码
git clone https://github.com/CN-QN1-dalin/signal-field-attention.git
cd signal-field-attention
python3 05-ring-buffer/ring_buffer.py

这是 QN1 Engine 的第 5 个模块。系列共 8 个模块。


系列索引

  1. Signal Field Attention --- 双通道注意力,4×加速,248×压缩
  2. Huayue(华岳)--- 注意力+SSM混合架构
  3. 归元v2 --- SSM KV压缩,99.96%压缩率
  4. 灵芽(LingYa)--- 正交基微调,参数比LoRA少50%
  5. RingBuffer --- O(1) KV Cache
  6. RCA --- 频域注意力(RFF),260×加速
  7. Metal Kernel --- GPU内核加速
  8. Ultra --- 极致部署优化

许可证:MIT

QN1 Engine --- Signal Field Attention

相关推荐
uhakadotcom1 小时前
结合着 fastapi 使用,anyio 通常可以如何使用 , 它和 uvloop 在性能上有啥差异
后端·面试·github
阳明山水1 小时前
自下而上 vs 自上而下 vs 最优组合预测策略解析
大数据·人工智能·深度学习·算法·机器学习
keykey6.1 小时前
从逻辑回归到 SVM:不仅仅是“分开“
算法·机器学习·支持向量机
李白的天不白1 小时前
下载smartadmin框架
git·github
可乐要加冰^-^2 小时前
云雀文档下载
windows·git·github·石墨文档
papership2 小时前
【入门级-算法-8、图论算法:泛洪算法 (Flood Fill)】
算法·图论
MartinYeung52 小时前
[论文学习]LLM 情境学习资料的快速精确遗忘技术:基于 In-Context Learning 与量化 K-Means 的 ERASE 方法
学习·算法·kmeans
江畔柳前堤2 小时前
github实战指南00-命令在哪里执行?
人工智能·线性代数·oracle·数据挖掘·github·word
IT 行者2 小时前
GitHub Spec Kit 实战(三):写一份能管住所有 spec 的 /speckit.constitution
java·github·ai编程·claude