STC: Accelerating Streaming Video Large Language Models via Hierarchical Token Compression
Authors: Yiyu Wang, Xuyang Liu, Xiyan Gui, Xinying Lin, Boxue Yang, Chenfei Liao, Tailai Chen, Linfeng Zhang Affiliations: EPIC Lab (上海交通大学), 四川大学, 华中科技大学, 中山大学, 香港科技大学(广州) GitHub: lern-to-write/STC Venue: CVPR 2026
1. Motivation (研究动机)
1.1 Streaming Video Understanding 的瓶颈
流式视频理解 (SVU) 要求模型在视频帧连续到达时实时处理并响应用户查询。当前 VideoLLM 面临两大计算瓶颈:
Figure 1 解读: 该图展示了 Qwen2-VL 和 LLaVA-OV 在图像理解与视频理解任务中的推理时间分布。关键发现: ViT 编码在视频理解中占比高达 59%-70%, 是图像理解的 2-3倍。例如 LLaVA-OV 处理 32帧视频时, 产生 个 visual token, 而图像理解仅约 1,900 个 token。这说明 ViT 编码是流式视频理解的主要瓶颈, 而非 LLM prefilling。
Figure 2 解读: 对比流式视频 (0.5fps) 与离线视频 (64帧) 在 ViT 编码中相邻帧的 cosine similarity 分布。流式视频在 Layer 20 的平均 cosine similarity 高达 0.85, 而离线视频仅为 0.60。这是因为流式采样率更高, 相邻帧内容几乎相同, 存在极大的 时间冗余。现有方法未能利用这种 ViT 编码阶段的冗余。
1.2 流式场景的两个独特挑战
- ViT 编码中的时间冗余: 流式视频帧采样密集, 相邻帧高度相似, 重复编码浪费计算
- 不完整视频与未知指令: 流式场景无法访问未来帧和用户指令, 要求压缩方法必须 因果化 (causal) 操作
1.3 现有方法的不足
| 方法类别 | 代表工作 | 局限性 |
|---|---|---|
| ViT 内 token merging | ToMe | 破坏编码过程, 导致 SVU 性能严重下降 |
| 离线 token 压缩 | VisionZip, VidCom² | 依赖全局视频可见性或用户指令, 不兼容因果流式约束 |
| 流式 KV cache 压缩 | LiveVLM | 仅压缩 KV cache, 未解决 ViT 编码和长序列问题 |
核心贡献: STC 是 第一个 同时优化 ViT 编码和 LLM prefilling 的即插即用流式 token 压缩框架。
2. Idea (核心思想)
2.1 整体架构
Figure 3 解读: STC 框架总览。底层是原始视频帧流, STC-Cacher 嵌入在 ViT 内部, 通过选择性重计算减少 ViT 编码冗余; STC-Pruner 位于 ViT 之后、LLM 之前, 通过双锚点剪枝压缩 token 序列以降低 prefilling 延迟。两个模块正交互补, 均满足因果约束 (query-agnostic, future-agnostic)。
Streaming 推理流程:
- 连续视频流 被分割为 chunk:
- ViT 编码: , 其中 为 patch 数, 为嵌入维度
- Token 投影到 LLM 嵌入空间, LLM 使用视觉 token 和文本上下文 自回归生成
- KV states 缓存在 memory bank 中供未来使用
2.2 设计要点
- STC-Cacher 面向 ViT 编码阶段, 利用相邻帧的时间冗余, 只对动态 token 进行重新计算
- STC-Pruner 面向 ViT 之后的 token 序列, 用双锚点评估 token novelty, 压缩 prefilling 成本
- 两个模块正交互补, 都遵循 causal 约束, 因此可直接适配流式场景
3. Method (方法)
3.1 STC-Cacher: ViT 中的缓存感知选择性计算
Figure 4 解读: STC-Cacher 的可视化效果。对于参考帧 (reference frame), 计算所有 token 并缓存; 对于后续帧, 仅计算动态 token (如移动物体), 静态 token (如背景) 直接复用缓存特征。图中可见大部分帧区域为静态背景, 只有少量区域存在运动变化。
Figure 5 解读: STC-Cacher 的详细机制。左侧为参考帧的完整 ViT 前向传播 (缓存 K, V, Attention, MLP 输出); 右侧为非参考帧的选择性计算: 通过比较 与 的 cosine similarity 识别动态 token, 仅对这些 token 重新计算 Q 和 V, 然后 scatter-update 到缓存矩阵中完成低秩注意力更新。
核心超参数:
- 缓存间隔 : 每 帧设一个参考帧 (完整前向传播)
- 缓存复用率 : 非参考帧中复用的 token 比例
具体步骤:
(I) 参考帧: 完整计算与缓存
对参考帧 执行完整 ViT 前向传播, 在每层 缓存:
其中 为 Key/Value 投影, 为注意力输出, 为 MLP 输出。
(II) 非参考帧: 识别动态 Token
计算当前帧与参考帧 Key 投影的 cosine similarity:
选择 similarity 最低 (novelty 最高) 的 top-k token 作为动态集合:
其中 , 由 决定。
(III) 选择性注意力计算
仅对动态 token 计算 Query 和 Value:
构建完整 Value 矩阵 (缓存初始化 + scatter 更新):
注意力计算:
(IV) Scatter-Update 输出
MLP 块采用类似的 scatter-update 策略。
伪代码:
# STC-Cacher: Cache-Aware Selective Computation
class STC_Cacher:
def __init__(self, cache_interval=4, reuse_ratio=0.75):
self.N = cache_interval # 每N帧一个参考帧
self.R = reuse_ratio # 75% token 复用缓存
self.cache = {} # 层级缓存 {layer: C_ref}
def process_frame(self, frame, frame_idx, vit_layers):
is_reference = (frame_idx % self.N == 0)
x = frame
for l, layer in enumerate(vit_layers):
if is_reference:
# 完整前向传播 + 缓存
K, V = layer.kv_proj(layer.norm(x))
A = layer.attention(layer.q_proj(layer.norm(x)), K, V)
M = layer.mlp(layer.norm(x + A))
self.cache[l] = {"K": K, "V": V, "A": A, "M": M}
x = x + A + M
else:
# 选择性计算
K_curr = layer.k_proj(layer.norm(x))
sim = cosine_similarity(K_curr, self.cache[l]["K"]) # [T]
# 选择 top-k 动态 token (similarity 最低)
k = int(len(sim) * (1 - self.R))
dynamic_idx = topk(1 - sim, k).indices
# 仅对动态 token 计算 Q, V
Q_sel = layer.q_proj(layer.norm(x))[dynamic_idx]
V_sel = layer.v_proj(layer.norm(x))[dynamic_idx]
# Scatter-update Value 矩阵
V_full = self.cache[l]["V"].clone()
V_full[dynamic_idx] = V_sel
# 选择性注意力
A_sel = layer.attention(Q_sel, K_curr, V_full)
# Scatter-update 输出
A_full = self.cache[l]["A"].clone()
A_full[dynamic_idx] = A_sel
# MLP 同理 scatter-update
M_full = self.cache[l]["M"].clone()
M_sel = layer.mlp(layer.norm(x[dynamic_idx] + A_sel))
M_full[dynamic_idx] = M_sel
x = x + A_full + M_full
return x3.2 STC-Pruner: 双锚点剪枝
Figure 6 解读: STC-Pruner 的机制图。对每帧编码后的 token 序列, 分别计算与 时间上下文锚点 (TCA) 和 空间上下文锚点 (SCA) 的 cosine distance, 生成两个打分矩阵, 加权融合后选择 top-k 高 novelty token 保留, 其余剪枝。TCA 通过历史帧均值建模时间上下文, SCA 通过当前帧均值建模空间上下文。
(I) 锚点建立
- 时间上下文锚点 (TCA): 历史缓冲区的均值
其中 为过去 帧的均值 token 向量。
- 空间上下文锚点 (SCA): 当前帧 token 的均值
其中 为当前帧的 个 visual token。
(II) 动态评分
每个 token 基于与两个锚点的联合 dissimilarity 评分:
其中 。该公式优先保留与历史和当前上下文 都不同 的 token, 即 “双重新颖” 的 token。
(III) Token 剪枝
保留 novelty 最高的 top-k token:
其中 。处理完后将当前帧的 加入历史缓冲区 。
伪代码:
# STC-Pruner: Dual-Anchor Token Pruning
class STC_Pruner:
def __init__(self, prune_ratio=0.75, alpha=0.5, window_size=16):
self.R = prune_ratio # 剪枝 75% token
self.alpha = alpha
self.history = deque(maxlen=window_size) # 滑动窗口历史
def prune(self, Z):
"""Z: [N, D] - 当前帧的 N 个 visual token"""
# 空间锚点: 当前帧均值
a_spatial = Z.mean(dim=0) # [D]
# 时间锚点: 历史缓冲区均值
if len(self.history) > 0:
a_temporal = torch.stack(list(self.history)).mean(dim=0) # [D]
else:
a_temporal = a_spatial
# 计算双锚点 novelty score
d_spatial = 1 - F.cosine_similarity(Z, a_spatial.unsqueeze(0)) # [N]
d_temporal = 1 - F.cosine_similarity(Z, a_temporal.unsqueeze(0)) # [N]
scores = self.alpha * d_temporal + (1 - self.alpha) * d_spatial # [N]
# Top-k 保留
k = int(Z.size(0) * (1 - self.R))
_, indices = scores.topk(k)
Z_pruned = Z[indices]
# 更新历史
self.history.append(a_spatial)
return Z_pruned4. Experimental Setup (实验设置)
| 项目 | 配置 |
|---|---|
| 流式基准 | OVO-Bench (644视频, 2814 QA), StreamingBench (900视频, 4500 QA) |
| 离线基准 | EgoSchema, MLVU-dev, VideoMME |
| End-to-End 模型 | Dispider, LiveCC, StreamForest |
| Offline-to-Online 框架 | ReKV (LLaVA-OV-7B backbone) |
| 采样率 | 0.5 fps |
| STC-Cacher 单独 | , |
| STC-Pruner 单独 | (压缩至 25%) |
| STC 联合 | , , (压缩至 30%) |
5. Experimental Results (实验结果)
5.1 主实验: OVO-Bench 流式视频理解
Table 1: OVO-Bench 综合结果
| 方法 | Real-Time Avg. | Backward Avg. | Forward Avg. | Overall | ViT延迟(s) | LLM延迟(s) |
|---|---|---|---|---|---|---|
| End-to-End + STC-Cacher | ||||||
| Dispider | 51.0 | 36.0 | 34.3 | 40.4 | 26.4 | 115.9 |
| +STC-Cacher | 49.1 | 35.2 | 33.4 | 39.2 | 18.9 (↓28.4%) | 115.9 |
| LiveCC | 57.0 | 69.0 | 53.2 | 59.7 | 181.2 | 818.4 |
| +STC-Cacher | 53.8 | 66.8 | 51.3 | 57.3 | 126.8 (↓30.0%) | 818.4 |
| StreamForest | 61.6 | 52.0 | 53.3 | 54.3 | 103.7 | 366.2 |
| +STC-Cacher | 60.9 | 51.5 | 51.5 | 52.3 | 67.7 (↓34.7%) | 366.2 |
| Offline-to-Online ReKV + Token 压缩 | ||||||
| ReKV (baseline) | 64.4 | 46.7 | 47.8 | 52.6 | 103.7 | 482.4 |
| +ToMe | 53.1 | 42.9 | 43.3 | 46.4 | 70.5 (↓32%) | 257.8 (↓46.6%) |
| +VisionZip | 53.8 | 44.0 | 44.7 | 47.5 | 103.7 | 258.3 (↓46.5%) |
| +VidCom² | 60.4 | 45.6 | 45.8 | 50.4 | 103.7 | 259.1 (↓46.3%) |
| +STC-Pruner | 60.5 | 45.5 | 45.8 | 50.6 | 103.7 | 259.2 (↓46.3%) |
| +STC-Cacher & Pruner | 62.5 | 45.3 | 48.0 | 52.0 | 78.3 (↓24.5%) | 263.7 (↓45.3%) |
关键发现:
- STC-Cacher & Pruner 在 ReKV 上保持 99% 精度 (52.0 vs 52.6), 同时 ViT 延迟降低 24.5%, LLM prefilling 延迟降低 45.3%
- 对比 VidCom², STC 在 OVO-Bench 上提升 1.6 分
- ToMe 虽降低了 ViT 延迟, 但精度严重下降 (46.4 vs 52.6, 损失 6.2 分)
5.2 StreamingBench 结果
Table 2: StreamingBench 综合结果
| 方法 | Overall | ViT延迟(s) | LLM延迟(s) |
|---|---|---|---|
| StreamForest | 77.3 | 103.7 | 366.2 |
| +STC-Cacher | 76.9 | 67.7 (↓34.7%) | 366.2 |
| ReKV | 69.1 | 103.7 | 482.4 |
| +ToMe | 59.4 | 70.5 (↓32%) | 257.8 (↓46.6%) |
| +VisionZip | 60.4 | 103.7 | 258.3 (↓46.5%) |
| +VidCom² | 63.6 | 103.7 | 259.1 |
| +STC-Pruner | 63.7 | 103.7 | 259.1 (↓46.3%) |
| +STC-Cacher & Pruner | 65.2 | 78.3 (↓24.5%) | 263.7 (↓45.3%) |
STC 在 StreamingBench 上也比 VidCom² 提升 1.6 分。
5.3 离线长视频理解
Table 3: 离线长视频基准
| 方法 | EgoSchema | MLVU-dev | VideoMME Overall | Average |
|---|---|---|---|---|
| ReKV | 57.7 | 68.6 | 57.7 | 61.3 |
| +ToMe | 55.2 | 63.1 | 51.7 | 56.7 |
| +VisionZip | 55.8 | 63.2 | 51.6 | 56.9 |
| +VidCom² | 60.6 | 67.1 | 56.8 | 61.5 |
| +STC-Pruner | 60.8 | 67.6 | 57.1 | 61.8 |
| +STC-Cacher & Pruner | 59.0 | 67.0 | 56.5 | 60.8 |
STC-Pruner 单独使用在离线场景也达到 SOTA, 超越 VidCom² 0.3 分。
5.4 消融实验
Figure 7 解读: STC-Cacher 中不同 token 评估策略的消融。上排 (i): 对比不同输入特征 (ToMe/Key/Value/Feature) 用于动态 token 评估, Key 在所有指标上表现最优。下排 (ii): 对比不同动态评估度量 (Cos Sim/DP/L1/L2), Cosine Similarity 一致性地取得最佳效果。黄色线为 ToMe 的性能参考, STC-Cacher 全面超越 ToMe。
Table 4: STC-Cacher 复用特征消融
| 配置 | EPM | STU | REC | EgoSchema |
|---|---|---|---|---|
| 仅 Attention (=85%) | 2.7 | 2.3 | 5.3 | 26.2 |
| 仅 MLP (=85%) | 49.8 | 43.2 | 25.3 | 57.1 |
| Attention + MLP (=75%) | 54.2 | 46.6 | 23.4 | 59.0 |
结论: 必须同时复用 Attention 和 MLP 特征。仅复用 Attention 几乎不可用 (EPM 仅 2.7), 因为需要同时保留注意力路径的位置/上下文信息和 MLP 路径的语义表征。
Table 5: STC-Pruner 动态评分消融
| 配置 | EPM | STU | REC | EgoSchema |
|---|---|---|---|---|
| 仅 SCA () | 50.5 | 47.2 | 25.8 | 59.9 |
| 仅 TCA () | 51.5 | 47.8 | 24.1 | 59.8 |
| SCA + TCA | 51.2 | 48.9 | 25.9 | 59.9 |
结论: 联合使用双锚点效果最稳定。单用 SCA 忽略帧间冗余, 单用 TCA 忽略帧内冗余。
5.5 代码结构与映射
| 论文概念 | 代码文件 | 关键类/函数 |
|---|---|---|
| STC-Cacher (ViT 缓存选择性计算) | model/cache.py | STC_CACHE |
| STC-Pruner (双锚点剪枝) | model/prune.py | STC_Pruner |
| ReKV 集成 | model/llava_onevision_rekv.py | ReKV 模型适配 |
| 评估脚本 | scripts/ | 各 benchmark 评估 |
| 数据加载 | data/ | 数据集配置 |
代码仓库: https://github.com/lern-to-write/STC 目前已支持 ReKV (LLaVA-OV), StreamForest/Dispider/LiveCC 支持即将推出。
5.6 关键结论与启发
5.6.1 核心贡献总结
- 首次揭示流式视频 ViT 编码的时间冗余: 流式场景 (0.5fps) 相邻帧 cosine similarity 高达 0.85, 远高于离线场景的 0.60
- 分层压缩框架 STC: 两个正交模块分别优化 ViT 编码 (STC-Cacher) 和 LLM prefilling (STC-Pruner)
- 即插即用, 无需重训练: 可直接集成到 Dispider, LiveCC, StreamForest, ReKV 等现有模型
- 优秀的效率-精度权衡: ReKV 上保持 99% 精度, ViT 延迟降低 24.5%, LLM prefilling 延迟降低 45.3%
5.6.2 方法设计启发
| 设计选择 | 启发 |
|---|---|
| Key 投影作为动态性指标 | Key 包含最丰富的 token 信息 (位置、历史相关性、注意力贡献), 优于 Value 或完整 Feature |
| Cosine similarity 优于 L1/L2/Dot Product | 归一化后的方向相似度比幅度距离更适合衡量语义变化 |
| 同时复用 Attention + MLP | 位置/上下文信息 (Attention) 和语义表征 (MLP) 缺一不可 |
| 双锚点 (TCA+SCA) 剪枝 | 单一锚点各有盲区: SCA 忽略时间冗余, TCA 忽略空间冗余 |
| 因果约束设计 | 流式场景的核心约束, 所有操作仅依赖历史和当前信息 |
5.6.3 局限性与未来方向
- 代码尚未完全开源 (StreamForest/Dispider/LiveCC 适配 coming soon)
- 缓存间隔 和复用率 为固定超参, 未来可探索自适应调节
- 剪枝权重 的自动学习可能进一步提升效果
- 与 KV cache 压缩方法 (如 Ada-KV) 的联合优化值得探索