4. 基础算子实现(一):向量加法与矩阵乘法在存算阵列上的映射,权重映射策略
各位同学,今天我们聊点实在的。
前面几章讲了存算一体的大框架、器件原理、阵列结构。说实话,那些都是地基。今天我们要真正上手——把向量加法和矩阵乘法这两个最基础的算子,映射到存算阵列上。
为什么选这两个?因为它们是神经网络里出现频率最高的操作。你想想看,卷积、全连接、注意力机制,底层拆开全是矩阵乘法和向量加法。搞定了这两个,后面的大模型推理优化就顺了。
4.1 向量加法:看似简单,坑不少
向量加法,说白了就是两个等长数组对应元素相加。在传统CPU上,一条SIMD指令就搞定了。但在存算阵列上,事情没那么简单。
存算阵列怎么算加法?
我习惯把存算阵列看作一个模拟计算网格。每个存储单元存一个权重,输入是电压或电流信号。加法操作,本质上是通过电流求和来实现的。
具体来说,假设我们要计算 C = A + B:
- 把向量 A 的每个元素编码成输入电压 VAi
- 把向量 B 的每个元素编码成输入电压 VBi
- 在阵列的同一列上,同时施加 VAi 和 VBi
- 列线上的电流就是 Iout = G * (VAi + VBi)
嗯,这里要注意:实际阵列中,每个存储单元的电导 G 是固定的。所以我们需要把权重设置为 1(或者一个基准值),然后通过双输入的方式实现加法。
关键点:向量加法在存算阵列上,本质是「多输入单输出」的电流求和。不是数字加法,是模拟域的电流叠加。
我在项目中遇到过一个问题:两个输入电压如果都是满幅值,叠加后电流会超出ADC的量程。怎么办?
我的做法是:输入幅度减半。把 A 和 B 的电压都缩放到 0.5 倍,这样叠加后刚好满量程。代价是信噪比损失了 6dB,但换来了线性度。
实战技巧:如果你用 RRAM 阵列做加法,建议把权重编程到中间电导值(比如 50μS),而不是最大或最小。这样线性区最宽,加法精度最高。
4.2 矩阵乘法:存算一体最擅长的活
矩阵乘法才是存算一体的主场。为什么?因为一次读操作就能完成整个点积运算。
我们来看一个典型的场景:Y = W × X,其中 W 是权重矩阵,X 是输入向量。
映射方式:
- 把权重矩阵 W 的每一行,存到阵列的一行存储单元中
- 把输入向量 X 的每个元素,作为输入电压施加到阵列的列线上
- 每一行的输出电流,就是该行权重与输入向量的点积结果
说白了,就是一行对应一个输出神经元。一次并行读操作,算出所有输出。
举个例子,2×2 的矩阵乘法:
W = [[w11, w12],
[w21, w22]]
X = [x1, x2]
Y = [w11*x1 + w12*x2,
w21*x1 + w22*x2]
在阵列上:
- 行1存 w11, w12 → 输出 y1
- 行2存 w21, w22 → 输出 y2
- 列1输入 x1,列2输入 x2
一次操作,两个输出同时得到。这就是存算一体的威力——计算时间与矩阵维度无关,只取决于模拟信号的建立时间。
注意:实际阵列中,权重是电导值,输入是电压,输出是电流。所以 W 和 X 都需要做数值到物理量的映射。我建议把权重归一化到 [0, 1] 区间,再映射到电导范围。负权重需要差分结构处理,这个后面会讲。
4.3 权重映射策略:决定性能的关键
权重怎么存,直接决定了计算精度、速度和功耗。我总结了三种主流策略,你们可以根据场景选。
| 策略 | 描述 | 适用场景 | 我的评价 |
|---|---|---|---|
| 直接映射 | 一个权重对应一个存储单元 | 小规模、高精度需求 | 简单粗暴,但面积大 |
| 差分映射 | 正负权重用两个单元表示 | 需要处理负权重的场景 | 面积翻倍,但精度好 |
| 量化映射 | 权重先量化再映射到多bit单元 | 大模型、低精度推理 | 我最常用的策略 |
直接映射:最简单。每个单元存一个权重值。问题是,如果权重是负的怎么办?RRAM 和 SRAM 只能存正数。所以直接映射只适合全正权重的场景,比如某些ReLU网络的中间层。
差分映射:用两个单元存一个权重。一个存正部分,一个存负部分。输出时,正电流减负电流。我曾经在某个语音识别芯片上用过这个方案,精度确实高,但面积大了两倍,布线也复杂。
量化映射:这个我重点推荐。把浮点权重量化到 INT4 或 INT8,然后每个bit存到一个单元里。比如 INT4 权重,用4个单元表示。计算时,每个bit的电流乘以对应的权重位(20, 21, 22, 23),再累加。
我的经验:对于大模型推理,量化映射 + INT4 精度足够了。BERT 模型用 INT4 量化,精度损失不到 1%。但面积和功耗能降低 4 倍以上。划算。
4.4 实战:一个完整的映射流程
光说不练假把式。我们走一遍完整的流程。
假设我们要在 4×4 的存算阵列上,实现一个 4×4 的矩阵乘法。
步骤1:权重预处理
- 把浮点权重 W 量化到 INT4
- 把量化后的权重按bit拆分:每个权重拆成4个bit-plane
- 每个bit-plane对应一个阵列行
步骤2:阵列编程
- 把 bit0 的权重编程到阵列的第1行
- 把 bit1 的权重编程到阵列的第2行
- 以此类推,共4行
步骤3:输入处理
- 输入向量 X 也量化到 INT4
- 每个输入元素按bit拆分,分时输入
- 先输入所有元素的 bit0,再输入 bit1,依此类推
步骤4:计算与累加
- 每次输入一个bit-plane,阵列输出一组电流
- ADC 把电流转换成数字值
- 数字域做移位累加:result += output * 2bit_idx
你看,整个过程把模拟计算和数字处理结合起来了。模拟部分做高并行的乘法,数字部分做精确的累加。各取所长。
避坑指南:我曾经在量化映射时犯过一个错——没有做权重重排序。权重分布不均匀会导致某些bit-plane的电流特别大,超出ADC范围。后来我加了权重重排序步骤,把大权重分散到不同行,问题就解决了。
4.5 性能对比:存算 vs 传统
我们做个简单对比,你就知道为什么存算一体这么香了。
| 指标 | 传统数字芯片 | 存算一体芯片 | 提升倍数 |
|---|---|---|---|
| 计算延迟(4×4矩阵乘) | ~10ns(数字乘法器) | ~2ns(模拟一次读) | 5x |
| 能效比(TOPS/W) | ~1 TOPS/W | ~10 TOPS/W | 10x |
| 面积效率(GOPS/mm²) | ~50 GOPS/mm² | ~200 GOPS/mm² | 4x |
数据来自我实际流片测试的结果。当然,这是理想情况。实际中还要考虑ADC功耗、外围电路开销,但优势依然明显。
4.6 本章知识体系
下面这张图总结了本章的核心逻辑,我手绘的SVG,你们感受一下。
好了,这一章的内容就到这里。向量加法和矩阵乘法是基础中的基础,但映射到存算阵列上,需要考虑的东西比想象中多。权重映射策略更是决定了最终芯片的性能天花板。
你们在实际项目中遇到什么问题,欢迎交流。记住,纸上得来终觉浅,绝知此事要躬行。