0%

GPU Memory System

内容参考自:General-Purpose Graphics Processor Architecture

现代 GPU 将内存在逻辑上划分为局部内存空间和全局内存空间

  • 局部内存空间: 每个线程私有,通常用于寄存器溢出,
  • 全局内存空间: 用于多个线程之间共享的数据结构
  • 临时存储器: 由程序员管理的可共享的临时存储器,供在 CTA 中一起执行的线程共享使用
    • 动机: 在许多应用程序中,程序员知道在计算的特定步骤需要访问哪些数据。通过一次性将所有这些数据加载到共享内存中,可以重叠长时间延迟的片外内存访问,并在对这些数据进行计算时避免长时间延迟的内存访问

First-Level Memory Structures

Unified L1Dcache and Shared Memory

一种 GPU l1 cache 组织形式:(统一的 L1Dcache 和 Shared Meomry)

Unified L1Dcache and Shared Memory

  • NVIDIA’s Fermi 架构引入(also present in the Kepler architecture)
  • SRAM (Data Array) 可配置为:
    • 部分用作直接映射的 Shared Memory
      • 在没有 bank 冲突的情况下,lookup 延迟恒定
      • 每个 bank 宽度为 32 位,并拥有自己的 decoder ,从而允许对每个 bank 中的不同行进行独立访问
    • 部分用作组相联 cache
      • 对 global memory 的访问会通过 cache 缓存
  • GPU 流水线通过 replay 处理 Shared Memory Bank 冲突和 L1Dcache miss

Shared Memory / Scratchpad Memory / Local Memory

  • 内存空间相对较小
  • 访问延迟通常与 Register Files 的访问延迟相当
  • 可被同一 CTA 中的所有线程访问

在不同的编程模型中有不同的名称:

  • CUDA 编程模型: shared memory
    • 在 CUDA 编程中,其 local memory 不同于 shared memory, 是线程私有,位于 global memory 中用于寄存器溢出
  • OpenCL: local memory
  • 其他架构: scratchpad memory

Shared Memory 以 SRAM 的形式实现

  • 每个通道对应一个 Bank
  • 每个 Bank 拥有一个读端口和一个写端口
  • 每个线程都可以访问所有的 Bank

使用 Shared Memory 需要考虑的一个关键问题: 潜在的 Bank 冲突
当多个线程在同一周期内访问同一个 Bank ,并且这些线程访问该 Bank 中不同的位置时,就会发生 Bank 冲突

Shared Memory 访问

  1. 仲裁器会判断该 warp 中的请求地址是否会导致 bank 冲突
  2. 如果请求的地址会导致一个或多个 bank 冲突,仲裁器会将请求分为两部分
    • 第一部分包含该 warp 中一组线程的地址,这些地址不会产生 bank 冲突。这部分原始请求会被仲裁器接受,并进一步处理
    • 第二部分包含那些与第一部分中地址产生 bank 冲突的地址。这部分原始请求会被返回到指令流水线,并必须重新执行。

被接受的部分:

  1. 在 Tag Unit 内部绕过 tag lookup (shared memory 直接映射)
  2. 在指令流水线内部安排一个写回事件到 register files (延迟恒定)
  3. Tag Unit 确定每个线程的请求映射到哪个 bank ,以控制 Address CrossBar 将地址分配到 Data Array 内的各个 bank
  4. 数据通过 Data CrossBar 返回到相应线程的通道,写回到 Register Files 中

L1Dcache

cache 部分的访问同 CPU 一致,
区别在于 GPU 中的 L1Dcache 不支持一致性协议

NVIDIA GPUs 解决方案: L1Dcache 中只允许 local memory accesses for register spills 和 stack data or read-only global memory data (starting with Kepler)

Unified Texture And Data Cache

NVIDIA 近期的 GPU 架构将 L1 Data Cache 和 Texture Cache 合并以节省面积。

  • 为方便实现,只有只读的数据会被缓存到 L1 cache 中

AMD’s GCN GPU 架构中的所有向量访存均由 Texture Cache 处理

L1 Texture Cache

Texture Mapping

Texture Mapping:
在三维图形中,为了实现实时渲染所需的高帧率实现这种场景的真实感,图形 API 采用的技术。
一张图像(称为一个 texture)被应用于三维模型中的表面,以使该表面看起来更加真实。

  • 实现:
    1. 渲染流水线首先确定 texture 图像中一个或多个样本的坐标 (这些样本称为 texels)
    2. 这些坐标用于查找包含 texels 的内存位置的地址
  • texture 内存访问具有显著的空间局部性:
    屏幕上的相邻像素映射到相邻的 texels,并且通常需要对附近 texels 的值进行平均

Micro Architecture

L1 Texture Cache

  • Tag Array 和 Data Array 之间通过一个 Fragment FIFO 分隔
    • 作用:隐藏可能需要从 DRAM 访存的 miss 请求的延迟
    • Texture Cache 的设计假设 cache miss 将频繁发生,并且访问地址空间相对较小
    • 为保持 Tag Array 和 Data Array 的规模较小,Tag Array 在时间上大致提前于 Data Array ,Tag Array 内容反映了经过约一个 miss 请求访问 DRAM 所需的时间后 Data Array 的未来状态
  • Texture Filter: 对多个 texels 访问合并,并通过 Register File 返回至指令流水线
    对于双线性插值和三线性插值(mipmap 过滤)等操作,每个 Fragment (pixel) 实际上需要进行四次或八次 texels 查找。 Texture Filter 将这些 texels 组合,生成一个单一的颜色值
  • Miss Request FIFO: 将 miss 请求发送至内存
  • Reorder Buffer: 按顺序将 miss 请求的处理结果从 Memory 返回
    • Memory 会通过使用内存访问调度技术提高 GPU 内存系统中的 DRAM 带宽利用率,这些技术会乱序地处理 miss 请求以减少行切换惩罚
    • 为确保 Data Array 的内容反映 Tag Array 的时间延迟

与常规 CPU cache 相比,Texture Cache 吞吐量得到了提升,但 cache hit/miss 所经历的延迟大致相同

访问过程

  1. LSU 将计算出的 texel 地址发送至 Tag Array 进行 lookup
    • hit: 将指向 Data Array 中数据位置的指针以及完成 texture 操作所需的其他信息放入 Fragment FIFO 尾部的一个 entry 中
    • miss: Tag Array 通过 Miss Request FIFO 发送内存请求
  2. 当有 entry 到达 Fragment FIFO 头部时,Controller 使用 enrty 中的指针从 Data Array 中查找 texel 数据,并返回至 Texture Filter

Memory Partition Unit

为了提供 SIMT Core 所需的大量内存带宽,高性能 GPU 通过 Memory Partition Unit 并行连接多个 DRAM 芯片, 内存流量通过 Address Interleaving 技术在各个 Memory Partition Unit 间进行分配

SIMT core 通过片上互连网络连接到 Memory Partition Unit

  • NVIDIA : Crossbar
  • AMD GPU: ring network

微架构:

  • 每个 Memory Partition Unit 包含
    • 一部分 L2 cache (分布式)
      • 同时包含图形和计算数据
    • 一个或多个 Memory Access Scheduler, 也称为 Frame buffer,FB
      • 对内存读写操作进行重排序,以减少访问 DRAM 的开销
    • 光栅操作(ROP)单元
      • 主要用于图形操作,例如 alpha 混合,并支持图形表面的压缩
      • 还支持原子操作,如 CUDA 编程模型中所使用的操作

L2 Cache

L2 cache 的设计包含多项优化措施,以提高 GPU 单位面积的总体吞吐量。

每个 Memory Partition 内部的 L2 Cache 由两个 slices 组成。每个 slice 包含独立的 Tag Array 和 Data Array,并按顺序处理传入的请求。

为了匹配 GDDR5 中 DRAM 原子性 32 字节的大小,每个 slice 中的 cache line 包含四个 32 字节

  • 合并写入: 当写入 miss 时完全覆盖一个区域中的数据,不会先从内存中读取数据。
  • 未合并的写入: 即未完全覆盖一个区域的写入,有两种解决方案:
    1. 存储 Byte 级别的 Valid 位
    2. 直接绕过 L2 Cache。

为了减少 Memory Access Scheduler 的面积,在等待调度的写入过程中,写入内存的数据会先在 L2 cacheline 中进行缓存

ROP Unit

ROP Unit 包含用于执行原子和归约操作的功能单元。
一系列访问同一内存位置的原子操作可以被流水化,因为 ROP Unit 包含一个 local ROP cache 。

原子操作可用于实现在不同线程块中运行的线程之间的同步。

Memory Access Scheduler

目的:重排序 DRAM 访存请求以减少 DRAM row buffer 和 DRAM cells 之间数据的搬运

GPU 中的每个 Memory Partition 可能包含多个 Memory Access Scheduler ,这些调度器将该 partition 所包含的 L2 Cache 部分连接到外部 DRAM。

  • L2 Cache 的每个 Slice 都拥有自己的 Memory Access Scheduler
  • 每个 Memory Access Scheduler 包含独立的逻辑,用于对来自 L2 Cache 的读请求和写请求进行排序
  • 为了将同一 DRAM Bank 中的读请求分组,采用了两个独立的表:
    1. 读请求排序器(read request sorter),组相联结构,通过内存地址进行访问,并将同一 Bank 中所有针对同一 Row 的读请求映射到一个单一指针
    2. 读请求存储器(read request store): 使用 read request sorter 提供的指针查找独立读请求列表