1. 激活压缩概述:为什么需要激活压缩?

大家好,我是你们这堂课的主讲。咱们开门见山——激活压缩,说白了就是给神经网络里的中间数据“瘦身”。

你可能会问:模型权重压缩我听过,激活值凑什么热闹?

嗯,这个问题我十年前刚入行时也问过。当时我带一个语音识别芯片项目,模型跑在FPGA上,功耗怎么都压不下去。后来一测才发现,激活值搬运消耗的能量,比计算本身高出两个数量级。从那以后,我再也不敢小看激活压缩了。

1.1 激活值的特点与冗余性分析

先聊聊激活值长什么样。我个人习惯把激活值分成三个维度来看:

  • 数值分布:大部分激活值集中在零附近,呈长尾分布
  • 空间冗余:相邻神经元输出高度相关,尤其在CNN的feature map里
  • 时间冗余:连续帧之间,激活值变化很小(RNN/LSTM场景)

举个例子。我在做图像分类芯片时,统计过ResNet-50中间层的激活值分布:

层类型激活值稀疏度(接近零的比例)有效位宽(bit)
Conv1 (ReLU后)48%4.2
ResBlock 中间层62%3.8
FC层 (Softmax前)15%6.1

看到没?大部分层超过一半的激活值都是零。你想想看,用32位浮点去存一个零值,这不是浪费是什么?

核心洞察:激活值的有效信息量远低于其存储位宽。压缩空间巨大。

1.2 为什么非压缩不可?能量效率的账本

咱们算一笔能量账。我习惯用45nm CMOS工艺下的典型数据:

  • 一次32位浮点加法:0.9 pJ
  • 一次32位浮点乘法:3.7 pJ
  • 从SRAM读32位数据:5 pJ
  • 从DRAM读32位数据:640 pJ

看出来了吗?数据搬运是能耗大头。激活压缩直接减少搬运的数据量,效果立竿见影。

我曾经优化过一个边缘AI芯片,原始设计里激活值占芯片总功耗的67%。通过4位量化+稀疏跳过,硬生生把激活相关功耗砍到22%。那次流片回来,测试结果跟仿真完全吻合——说实话,那种成就感比发爽多了。

注意:压缩不是免费的午餐。解压缩逻辑会引入额外面积和功耗。如果压缩率不够高,反而得不偿失。我见过有人硬上8倍压缩,结果解压电路比省下来的存储还大——这就尴尬了。

1.3 激活压缩的三大技术路线

目前业界主流做法,我归纳成三条路:

  1. 量化:降低数值精度。从FP32到INT8,甚至到二值/三值
  2. 稀疏化:跳过零值或接近零的激活,只传输有效数据
  3. 剪枝+编码:结构化剪枝后,用游程编码或霍夫曼编码进一步压缩

这三条路不是互斥的。我实际项目中,通常先做量化,再做稀疏跳过,最后加一层轻量编码。效果1+1+1 > 3。

1.4 激活压缩的知识体系总览

下面这张图,是我梳理的本章知识框架。你看一眼,心里就有谱了。

激活压缩知识体系 激活压缩 为什么需要压缩? 激活值特点 能量效率瓶颈 冗余性分析 三大技术路线 权衡与挑战 数值分布 空间冗余 时间冗余 量化 稀疏化 长尾分布 相邻相关 帧间相似 INT8/4/2 零值跳过 核心目标:在精度损失可控的前提下, 最大化激活值的能量效率

我的建议:初学者别一上来就搞花哨的编码。先把量化吃透,再考虑稀疏跳过。这两招用好了,80%的压缩收益就到手了。

1.5 一个简单的量化示例

光说不练假把式。咱们看一段伪代码,演示最基础的线性量化:

// 激活值量化:FP32 -> INT8
// 输入:float激活值数组,长度N
// 输出:int8量化值 + 缩放因子

float find_scale(float* act, int N) {
    float max_val = 0.0f;
    for (int i = 0; i < N; i++) {
        if (fabs(act[i]) > max_val)
            max_val = fabs(act[i]);
    }
    return max_val / 127.0f;  // INT8范围[-128, 127]
}

void quantize(float* act, int8_t* q_act, float* scale, int N) {
    *scale = find_scale(act, N);
    for (int i = 0; i < N; i++) {
        q_act[i] = (int8_t)round(act[i] / (*scale));
        // 钳位到[-128, 127]
        if (q_act[i] < -128) q_act[i] = -128;
        if (q_act[i] > 127)  q_act[i] = 127;
    }
}

这段代码看着简单,但坑不少。我曾经在项目里直接用全局max做量化,结果某个离群点把整个动态范围撑大了,精度掉得一塌糊涂。后来改用百分位截断(比如取99.9%分位点),效果才稳住。

嗯,这就是为什么我总说:理论要落地,细节是魔鬼。