三星的存内处理(PIM)技术
多年来,存内计算一直是个颇具吸引力的方向,因为内存芯片内部的计算可以利用其更高的内部带宽。此外,存内计算还避免了 DRAM 与传统计算核心之间的长延迟路径。在 Hot Chips 2026 上,三星介绍了他们在 PIM(Processing-in-Memory,存内处理)方向上的持续探索:他们在 LPDDR5X 芯片中实现了 MAC 单元,同时保留了芯片与标准内存控制器对接的能力。

DRAM 芯片内部划分为多个 bank,每个 bank 都有自己的读写逻辑。在正常的 DRAM 访问中,内存控制器选中一个 bank,激活其中的某一行,然后通过列选通(CAS)命令访问数据。带宽受限于芯片对外的 DRAM 接口:即使内存控制器能同时激活所有 bank,也吃不满所有 bank 加起来可用的全部带宽。

三星的 LPDDR5X-PIM 就像一颗普通的 LPDDR5X-9600 芯片,拥有 16 个 bank,但在每个 bank 上都放置了一个 PIM 块。这些 PIM 块访问所挂接的 DRAM bank 时不受芯片外部总线约束,合起来可以利用全部 16 个 bank 的芯片内部带宽,合计 614 GB/s。作为对比,常规 DRAM 访问只能并行命中两个 bank,最高 76.8 GB/s。

PIM 块内部由一个 MAC 树以及环绕它的寄存器堆和控制逻辑构成。一个 1024 位指令寄存器堆最多容纳 64 条 16 位指令;一个 4 kbit 的源寄存器堆用于存放激活向量,为 MAC 阵列提供其中一个源操作数。三星期望由软件把模型权重加载到 DRAM 中,因此所挂接的 DRAM bank 提供第二个操作数。权重在参与 MAC 计算前可以先做缩放,缩放因子来自一个 2 kbit 的 scale 寄存器。

PIM 块的 MAC 阵列支持多种低精度格式。从三星演示材料中的数字来看,每个 PIM 块的 MAC 阵列每个数据时钟可以维持 4 次 INT8 或 FP8 MAC 运算,若不计双倍数据率则为每周期 8 次。输入权重为 4 位时吞吐翻倍,使整颗封装的计算吞吐达到 2.4 TOPS。

这个数字并不算高,但采用多颗 LPDDR5X 芯片的实现可以获得更高的聚合吞吐。例如,八颗 LPDDR5X 芯片合计可达 9.6 INT8 TOPS,大致相当于 Intel Meteor Lake 中的 NPU。不过这也是一套昂贵的配置,因为八颗 16 GB 的 LPDDR5X 芯片意味着 128 GB 系统内存。
用标准 DDR 命令访问计算功能
LPDDR5X-PIM 的一大亮点是:它始终遵循标准 LPDDR5X 协议,同时又暴露出内存标准之外的计算能力。三星的做法是预留一些特殊的行地址,让它们起到类似 MMIO 地址的作用。每个通道有一对预定义的行用于模式控制:激活其中一行会把芯片设为单 bank 模式,另一行则设为多 bank 模式。单 bank 是常规模式,多 bank 模式则把命令作用于全部 16 个 bank,以利用芯片内部带宽。

一些特殊的 per-bank 行会改变读写命令的行为。激活这类特殊行后,读写命令将访问 PIM 寄存器而不是常规 DRAM bank 内容(即 PIM Registers Activated 模式)。三星设想了一个机器学习用例:先在正常的单 bank 模式下由软件把模型权重加载进 DRAM;随后切换到多 bank 模式并进入 PIM Registers Activated 模式,让代码把激活值写入 PIM 源寄存器、在 PIM scale 寄存器中设置缩放因子,并把一条操作指令填入 PIM 指令寄存器。

由于芯片处于多 bank 模式,每次 PIM 寄存器写入都会广播到全部 16 个 bank。因此 PIM 计算就像一台受限非常强的 SIMD 处理器:操作类型、缩放因子和一个源操作数在所有 bank 之间是相同的。三星也允许在单 bank 模式下写 PIM 寄存器,但该功能定位于调试用途。每个 DRAM 包为 256 位(BL=16),填满一个源寄存器需要 16 条写命令;若在 16 个 bank 上逐个 bank 地填,就需要 256 条写命令,主机到 PIM 寄存器的写入带宽将成为瓶颈。三星实际上也允许在单 bank 模式下访问 PIM 寄存器,但那是作为调试功能而设的。

把 PIM 寄存器准备好之后,软件切回多 bank 模式并发出读命令。这些读命令并不读取 DRAM 内容,而是启动计算,并把结果累加到 PIM 向量寄存器堆中。随后,写命令指示 PIM 块把 VRF 内容写回 DRAM bank。

PIM 还要应对普通内存控制器可能做出的访问重排。代码在通过激活 bank 来配置 PIM 时,PIM 通常会把指令寄存器堆设置成让指令按顺序访问源寄存器的各个元素:比如第一条指令引用第一个源寄存器元素,第二条指令引用第二个,依此类推。然而一旦内存控制器重排了访问顺序,这种约定就会失效。三星用地址对齐模式(Address Align Mode,AAM)来解决:让每条指令从正在访问的列地址中推断自己的源寄存器索引。

当主机用完存内计算、想读取结果时,它把 DRAM 芯片切回单 bank 模式,此后常规 DRAM 读写就会照常访问 DRAM 内容。

软件层面的麻烦?
与使用标准 LPDDR5X 相比,三星在内部利用 LPDDR5X-PIM 时获得了巨大的性能提升。这颗芯片能配合标准内存控制器工作,令人印象深刻,三星解决问题的方式也颇具创造力。

复用标准 DRAM 命令应当能简化硬件,但软件层面的挑战看起来相当陡峭。由于 PIM 模式改变了 DRAM 访问命令的含义,软件无法在使用 PIM 的同时进行常规内存访问。这条限制甚至跨线程成立,因为内存控制器和 DRAM 芯片并不知道一次访问来自哪个线程。如果非 PIM 线程在另一线程使用 PIM 时读取内存,前者的读取可能触发一次非预期的计算,把错误结果送进 PIM VRF;非 PIM 线程的一次写入则可能让 PIM 块把 VRF 数据写回到错误的地址。
三星的应对办法是由主机在内存中划出一个 PIM 专区。我想不出在典型系统里有什么简便做法,能在不牺牲内存带宽和 PIM 性能的前提下做到这一点。硬件通常把地址交错(interleave)分布到各通道,让常见访问模式自然用满各通道带宽;而 PIM 用 per-channel 的行来控制单/多 bank 模式切换,所以放弃交错、把某些内存通道划为 PIM 专用,是创建 PIM 专区唯一合理的办法。这样一来,非 PIM 应用就无法利用被划走的通道带宽,PIM 代码也会损失其余通道的带宽和算力。后者可能是个大问题,因为单芯片的计算吞吐并不算高。
即便划出了 PIM 专区,多任务问题依然存在。如果应用想使用 PIM 并发挥多线程能力,就必须用锁保护 PIM 专区的访问,防止一个线程在做 PIM 计算时另一个线程发起常规内存访问。在多任务的现代操作系统上情况更糟:多个进程可能互不知情地同时想用 PIM。除了让操作系统在运行 PIM 计算代码段时阻塞所有其他线程并关闭中断,我不确定还有什么好办法。无论如何,在 PIM 进行时处理中断或上下文切换对操作系统来说都是噩梦:抢占一个 PIM 线程意味着把内存通道退出 PIM 模式并保存 PIM 状态,操作系统得把每个 bank 上的指令、源、scale 和向量寄存器堆全部读出来存到别处。若只允许单线程运行、不做任务切换,就等于放弃多线程性能,而且一旦代码在 PIM 计算段里耗时过长,还可能出现系统响应性问题。
破坏缓存与乱序执行?
PIM 计算打破了内存子系统对 DRAM 行为的预期,因为 DRAM 可能产生缓存层级从不知情的内存值;缓存也会反过来破坏 PIM 行为——缓存可能把本应触发 PIM 操作的访问吸收掉。因此三星建议把 PIM 内存映射为不可缓存。这就麻烦了:现代 CPU 和 GPU 高度依赖缓存来掩盖 DRAM 延迟,访问不可缓存内存时性能会极差,因为 CPU 或 GPU 核心会把大量时间耗在等待内存上。

绕过缓存并不是唯一的问题。PIM 读的行为类似 MMIO 访问:它们会引发计算、改变 PIM VRF 的值,而不只是取回数据。CPU 还会通过在确认真正需要之前就发起加载来掩盖内存延迟:分支预测让 CPU 在核心尚未确定指令会被执行时就发射指令;预取器观察内存访问模式,抢在指令请求数据之前把数据装入缓存。如果 CPU 加载的数据后来用不上,通常无妨,因为加载一般不会导致错误的程序行为。遗憾的是 PIM 不一样——读会触发计算并修改 PIM VRF 内容。

使用 PIM 专区,很可能意味着内存访问既要不可缓存、也要不可推测。让 CPU 在无缓存、无预取、无乱序执行的状态下运行,会严重削弱性能。
存内计算的通用挑战
撇开 PIM 模式的种种困难,存内计算还给软件带来了更高层面的挑战。每个 PIM 块只能快速访问本地挂接的 DRAM bank,其余输入数据都得经由 DRAM 芯片相对受限的外部接口进来。PIM 块之间无法直接交换数据,因此当某个 PIM 块需要用到另一个 PIM 块产生的结果时,主机必须用常规 DRAM 读写来搬运数据。
结语
得益于与标准内存控制器协同工作的能力,三星的 LPDDR5X-PIM 理论上可以装进任何服务器、台式机、笔记本甚至移动设备。但这并不意味着在典型的软硬件范式下它会用起来轻松。PIM 模式切换给多任务操作系统添了乱;在暗处修改 DRAM 内容、给读命令附加副作用,则破坏了 CPU 的缓存、预取和乱序执行。

我认为,如果不对整个内存子系统做改动,就没有轻松使用存内计算的捷径。例如,下面这样的设计应该能让软件更容易接受:
扩展 DRAM 接口,新增一组计算专用命令,避免模式切换的复杂性
从缓存一致性的角度看,让内存控制器表现得像一个对等的 CPU 核心:在使用存内计算命令之前,内存控制器先对所有受影响的缓存行发起 read-for-ownership(RFO)请求,从而取得任何已修改的数据并在开始存内计算前写回 DRAM,确保存内计算的结果反映 CPU 侧最新的写入;随后,内存控制器持有这些缓存行的所有权直至存内计算完成,让 CPU 核心无需失效或绕过缓存就能观察到存内计算的结果
新增一组 CPU 指令(类似 “rep macb”),对一块内存执行乘累加运算,乘数/缩放因子固定、数值特性不作定义。CPU 可以自行决定是使用存内计算(若 DRAM 支持),还是生成一串内部运算(若处理的是已在缓存里的一小批数据)
有了这些硬件改动,软件就能在多任务操作系统里使用存内计算,既不必预留内存,也不必为了 PIM 相关的锁和同步牺牲线程级并行。一条透明的 CPU 指令避免了分发硬件特定二进制的问题,让代码具备向前兼容性,能自动利用新的硬件能力,包括不同的存内计算实现。它还让硬件可以结合实现相关的知识和实时数据(比如 no-fill-on-miss 的缓存查找)做出在哪执行计算的最优决策。我不喜欢那条软件路线——预留内存区域、标记为不可缓存、阻塞线程——在性能、内存容量和响应性之间要做的取舍实在太多了。
1111SharePrevious