计算机体系结构
Memory Consistency and Cache Coherence
Memory Consistency and Cache Coherence 定义
1. Memory Consistency(内存一致性)
-
定义:内存一致性是指多核或多处理器系统中,多个处理器对共享内存的访问顺序是否一致,以及这些访问操作是否满足特定的规则或模型。
-
关注点:内存操作的全局可见顺序,确保所有处理器对内存的读写操作按照一致的顺序进行。
-
问题背景:在多核系统中,不同处理器可能同时访问共享内存,如果没有明确的内存一致性模型,可能会导致程序行为不可预测。
-
内存一致性模型:
-
顺序一致性(Sequential Consistency):所有处理器的内存操作按照一个全局顺序执行,且每个处理器的操作顺序保持不变。
-
弱一致性模型(Weak Consistency):允许某些操作乱序执行,但需要通过同步操作(如内存屏障)来保证一致性。
-
-
示例:
-
如果两个处理器分别执行写操作和读操作,内存一致性模型定义了读操作是否能立即看到写操作的结果。
-
2. Cache Coherence(缓存一致性)
-
定义:缓存一致性是指多核或多处理器系统中,每个处理器的私有缓存与共享内存之间数据的一致性,确保所有处理器看到的数据是最新的。
-
关注点:多个缓存副本之间的数据一致性,避免脏数据或过时数据。
-
问题背景:每个处理器都有自己的缓存,如果某个处理器修改了缓存中的数据,其他处理器的缓存可能仍然保存旧值,导致数据不一致。
-
缓存一致性协议:
-
MESI协议:通过标记缓存行的状态(Modified、Exclusive、Shared、Invalid)来维护一致性。
-
写失效(Write Invalidate):当一个处理器修改数据时,使其他处理器的缓存副本失效。
-
写更新(Write Update):当一个处理器修改数据时,将新值广播给其他处理器的缓存。
-
-
示例:
-
如果处理器A修改了某个内存位置的值,处理器B的缓存中该位置的旧值必须被更新或失效。
-
3. 主要区别
特性 | Memory Consistency(内存一致性) | Cache Coherence(缓存一致性) |
---|---|---|
关注点 | 内存操作的全局顺序和可见性 | 多个缓存副本之间的数据一致性 |
范围 | 涉及所有内存操作(读/写)的顺序 | 仅涉及缓存和内存之间的数据一致性 |
问题背景 | 多个处理器对共享内存的访问顺序是否一致 | 多个处理器的缓存中数据是否一致 |
解决方案 | 内存一致性模型(如顺序一致性、弱一致性) | 缓存一致性协议(如MESI、写失效、写更新) |
4. 总结
-
Memory Consistency 关注的是多处理器系统中内存操作的全局顺序和可见性。
-
Cache Coherence 关注的是多处理器系统中缓存副本之间的数据一致性。
TSO(Total Store Ordering)内存模型
- TSO(Total Store Ordering)是一个被广泛使用的内存模型
- 并在x86架构中使用,RISC-V也提供了TSO扩展,即RVTSO,人们普遍认为x86内存模型等同于TSO,然而Intel和AMD从来没有保证这一点
- x86选择放弃SC(顺序一致性sequential consistency),以更好地支持基于FIFO的write buffer,用于加速性能
- TSO和SC最关键的区别就是store可能被放入write buffer中且允许load的bypass
- 对于单核视角来说,和SC没有区别,执行顺序和程序顺序一致
- 对于多核来说,需要额外的手段来保证memory consistency
- 由于TSO只允许store-load的重排,因此需要使用FENCE的场合不多,FENCE的实现方式也不是很重要。一种简单的实现是,当FENCE被执行时,清空write buffer,并在FENCE提交之前不再执行后面的load指令。
x86的多核宽松内存一致性模型
- 被修饰的汇编指令成为“原子的”
- 本身是原子指令,比如“XCHG”和“XADD”汇编指令
- 本身不是原子指令,但是被
LOCK指令前缀
修饰后成为原子指令,比如LOCK CMPXCHG - 被修饰的汇编指令A在执行期间,会在内存总线上声言一个
#LOCK
信号,该信号导致内存被锁住,此时内存不能再被其他汇编指令存取,直到A执行完成。经过分析可知,A的执行效果与“暂停执行其他所有汇编指令直到A执行完成等价,因此此时A是原子的
- fence
- sfence: 在sfence指令前的写操作当必须在sfence指令后的写操作前完成
- lfence:在lfence指令前的读操作当必须在lfence指令后的读操作前完成
- mfence:在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成
-
C++11中支持的内存模型
- Acquire-Release能保证不同线程之间的Synchronizes-With关系,同时也约束到同一个线程中前后语句的执行顺序。
- Release-Consume只约束有明确的carry-a-dependency关系的语句的执行顺序,同一个线程中的其他语句的执行先后顺序并不受这个内存模型的影响
enum memory_order {
memory_order_relaxed, // Relaxed
memory_order_consume, // Release-Consume
memory_order_acquire, // Acquire-Release 用来修饰一个读操作,表示在本线程中,所有后续的关于此变量的内存操作都必须在本条原子操作完成后执行
memory_order_release, // Acquire-Release 用来修饰一个写操作,表示在本线程中,所有之前的针对该变量的内存操作完成后才能执行本条原子操作
memory_order_acq_rel, // Acquire-Release 同时包含memory_order_acquire和memory_order_release标志
memory_order_seq_cst // 顺序一致性模型
};
einsum
两个基本概念
自由索引(Free indices)和求和索引(Summation indices):
- 自由索引,出现在箭头右边的索引,比如上面的例子就是 i 和 j;
- 求和索引,只出现在箭头左边的索引,表示中间计算结果需要这个维度上求和之后才能得到输出,比如上面的例子就是 k;
三条基本规则
- 规则一,equation 箭头左边,在不同输入之间重复出现的索引表示,把输入张量沿着该维度做乘法操作,比如还是以上面矩阵乘法为例, "ik,kj->ij",k 在输入中重复出现,所以就是把 a 和 b 沿着 k 这个维度作相乘操作;
- 规则二,只出现在 equation 箭头左边的索引,表示中间计算结果需要在这个维度上求和,也就是上面提到的求和索引;
- 规则三,equation 箭头右边的索引顺序可以是任意的,比如上面的 "ik,kj->ij" 如果写成 "ik,kj->ji",那么就是返回输出结果的转置,用户只需要定义好索引的顺序,转置操作会在 einsum 内部完成。
特殊规则
特殊规则有两条:
- equation 可以不写包括箭头在内的右边部分,那么在这种情况下,输出张量的维度会根据默认规则推导。就是把输入中只出现一次的索引取出来,然后按字母表顺序排列,比如上面的矩阵乘法 "ik,kj->ij" 也可以简化为 "ik,kj",根据默认规则,输出就是 "ij" 与原来一样;
- equation 中支持 "..." 省略号,用于表示用户并不关心的索引,比如只对一个高维张量的最后两维做转置可以这么写:
a = torch.randn(2,3,5,7,9)
# i = 7, j = 9
b = torch.einsum('...ij->...ji', [a])
二次变换(bilinear transformation)
np_a = a.numpy()
np_b = b.numpy()
np_c = c.numpy()
np_out = np.empty((2, 5), dtype=np.float32)
np_out = torch.einsum('ik,jkl,il->ij', [a, b, c]).numpy()
# ik broadcast成ikl
# il broadcast成 ikl
# 'ik,jkl,il->ij'可以理解成'ikl,jkl,ikl->ij'
for i in range(0, 2):
for j in range(0, 5):
# 求和索引内循环 这里是 k 和 l
sum_result = 0
for k in range(0, 3):
for l in range(0, 7):
sum_result += np_a[i, k] * np_b[j, k, l] * np_c[i, l]
np_out[i, j] = sum_result
总结
a = torch.rand(2,3)
b = torch.rand(3,4)
c = torch.einsum("ik,kj->ij", [a, b])
# 等价操作 torch.mm(a, b)
equation 中的字符也可以理解为索引,就是输出张量的某个位置的值,是怎么从输入张量中得到的,比如上面矩阵乘法的输出 c 的某个点 c[i, j] 的值是通过 a[i, k] 和 b[k, j] 沿着 k 这个维度做内积得到的。
RAM
DRAM
- 电容
- 带宽不是很高
- 需要刷新,会有颠簸
SRAM
- 面积和功耗不能和工艺平行
- 类型
- Cpu register Flip Flops 每个bit都有一读一写
- L1/L2 SRAM 6个晶体管,一般最多每个bank一读一写
- L3/L4 eDRAM/GCRAM 4晶体管/电容 读写速度明显降低,也会有使用上的问题
NoC
OpenSMART
https://github.com/hyoukjun/OpenSMART/tree/master
connect
https://users.ece.cmu.edu/~mpapamic/connect/
https://github.com/crossroadsfpga/connect/tree/main
Flexnoc
is a commercial NoC generator by Arteris which generates a customized
topology for each SoC,
Cache写机制 Write-through与Write-back
Cache写机制分为write through和write back两种。
Write-through: Write is done synchronously both to the cache and to the backing store.
Write-back (or Write-behind) : Writing is done only to the cache. A modified cache block is written back to the store, just before it is replaced.
Write-through(直写模式)在数据更新时,同时写入缓存Cache和后端存储。此模式的优点是操作简单;缺点是因为数据修改需要同时写入存储,数据写入速度较慢。
Write-back(回写模式)在数据更新时只写入缓存Cache。只在数据被替换出缓存时,被修改的缓存数据才会被写到后端存储。此模式的优点是数据写入速度快,因为不需要写存储;缺点是一旦更新后的数据未被写入存储时出现系统掉电的情况,数据将无法找回。
Write-misses写缺失的处理方式
对于写操作,存在写入缓存缺失数据的情况,这时有两种处理方式:
Write allocate (aka Fetch on write) – Datum at the missed-write location is loaded to cache, followed by a write-hit operation. In this approach, write misses are similar to read-misses.
No-write allocate (aka Write-no-allocate, Write around) – Datum at the missed-write location is not loaded to cache, and is written directly to the backing store. In this approach, actually only system reads are being cached.
Write allocate方式将写入位置读入缓存,然后采用write-hit(缓存命中写入)操作。写缺失操作与读缺失操作类似。
No-write allocate方式并不将写入位置读入缓存,而是直接将数据写入存储。这种方式下,只有读操作会被缓存。
无论是Write-through还是Write-back都可以使用写缺失的两种方式之一。只是通常Write-back采用Write allocate方式,而Write-through采用No-write allocate方式;因为多次写入同一缓存时,Write allocate配合Write-back可以提升性能;而对于Write-through则没有帮助。
处理流程图
Write-through模式处理流程:A Write-Through cache with No-Write Allocation
Write-back模式处理流程:A Write-Back cache with Write Allocation
人工智能的产业
- 模型算法
- 科研
- 企业商用
- 数据收集、标注
- 软件框架
- 科研
- 商业部署
- 加速芯片
- 云训练芯片
- 云推理
- 边沿推理
- 云服务
- 基础软件框架
- 硬件
- 服务
- 应用
- 各种AI云服务
- 边缘AI
- 特定领域
- 基础设施:安防
- 自动驾驶、机器人
- 工业、医疗、教育、金融