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 流式场景的两个独特挑战

  1. ViT 编码中的时间冗余: 流式视频帧采样密集, 相邻帧高度相似, 重复编码浪费计算
  2. 不完整视频与未知指令: 流式场景无法访问未来帧和用户指令, 要求压缩方法必须 因果化 (causal) 操作

1.3 现有方法的不足

方法类别代表工作局限性
ViT 内 token mergingToMe破坏编码过程, 导致 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 x

3.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_pruned

4. 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.OverallViT延迟(s)LLM延迟(s)
End-to-End + STC-Cacher
Dispider51.036.034.340.426.4115.9
+STC-Cacher49.135.233.439.218.9 (↓28.4%)115.9
LiveCC57.069.053.259.7181.2818.4
+STC-Cacher53.866.851.357.3126.8 (↓30.0%)818.4
StreamForest61.652.053.354.3103.7366.2
+STC-Cacher60.951.551.552.367.7 (↓34.7%)366.2
Offline-to-Online ReKV + Token 压缩
ReKV (baseline)64.446.747.852.6103.7482.4
+ToMe53.142.943.346.470.5 (↓32%)257.8 (↓46.6%)
+VisionZip53.844.044.747.5103.7258.3 (↓46.5%)
+VidCom²60.445.645.850.4103.7259.1 (↓46.3%)
+STC-Pruner60.545.545.850.6103.7259.2 (↓46.3%)
+STC-Cacher & Pruner62.545.348.052.078.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 综合结果

方法OverallViT延迟(s)LLM延迟(s)
StreamForest77.3103.7366.2
+STC-Cacher76.967.7 (↓34.7%)366.2
ReKV69.1103.7482.4
+ToMe59.470.5 (↓32%)257.8 (↓46.6%)
+VisionZip60.4103.7258.3 (↓46.5%)
+VidCom²63.6103.7259.1
+STC-Pruner63.7103.7259.1 (↓46.3%)
+STC-Cacher & Pruner65.278.3 (↓24.5%)263.7 (↓45.3%)

STC 在 StreamingBench 上也比 VidCom² 提升 1.6 分。

5.3 离线长视频理解

Table 3: 离线长视频基准

方法EgoSchemaMLVU-devVideoMME OverallAverage
ReKV57.768.657.761.3
+ToMe55.263.151.756.7
+VisionZip55.863.251.656.9
+VidCom²60.667.156.861.5
+STC-Pruner60.867.657.161.8
+STC-Cacher & Pruner59.067.056.560.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 复用特征消融

配置EPMSTURECEgoSchema
仅 Attention (=85%)2.72.35.326.2
仅 MLP (=85%)49.843.225.357.1
Attention + MLP (=75%)54.246.623.459.0

结论: 必须同时复用 Attention 和 MLP 特征。仅复用 Attention 几乎不可用 (EPM 仅 2.7), 因为需要同时保留注意力路径的位置/上下文信息和 MLP 路径的语义表征。

Table 5: STC-Pruner 动态评分消融

配置EPMSTURECEgoSchema
仅 SCA ()50.547.225.859.9
仅 TCA ()51.547.824.159.8
SCA + TCA51.248.925.959.9

结论: 联合使用双锚点效果最稳定。单用 SCA 忽略帧间冗余, 单用 TCA 忽略帧内冗余。

5.5 代码结构与映射

论文概念代码文件关键类/函数
STC-Cacher (ViT 缓存选择性计算)model/cache.pySTC_CACHE
STC-Pruner (双锚点剪枝)model/prune.pySTC_Pruner
ReKV 集成model/llava_onevision_rekv.pyReKV 模型适配
评估脚本scripts/各 benchmark 评估
数据加载data/数据集配置

代码仓库: https://github.com/lern-to-write/STC 目前已支持 ReKV (LLaVA-OV), StreamForest/Dispider/LiveCC 支持即将推出。

5.6 关键结论与启发

5.6.1 核心贡献总结

  1. 首次揭示流式视频 ViT 编码的时间冗余: 流式场景 (0.5fps) 相邻帧 cosine similarity 高达 0.85, 远高于离线场景的 0.60
  2. 分层压缩框架 STC: 两个正交模块分别优化 ViT 编码 (STC-Cacher) 和 LLM prefilling (STC-Pruner)
  3. 即插即用, 无需重训练: 可直接集成到 Dispider, LiveCC, StreamForest, ReKV 等现有模型
  4. 优秀的效率-精度权衡: 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) 的联合优化值得探索