GPU Gems 2 · Chapter 29. Streaming Architectures and Technology Trends
从算力与通信增速差异出发,解释 GPU 为什么用长数据流、独立 kernel、三级并行、片上局部性与深流水换取高吞吐。
学习目标
- 能解释 stream architecture 在 GPU 执行与存储层次中的位置,以及算力、通信、延迟和功耗趋势为何推动这种组织
- 能把一段批处理改写为 stream 与独立 kernel,标出 task/data/instruction parallelism、片上局部性和外部流量
- 能回答:给定元素数、每元素运算量、全局读写量与局部性时,工作负载更可能受计算还是通信限制,应先改算法、数据布局还是并行规模
更多机器,不等于工厂自动更快
工厂可以不断增加工作台,但原料如果仍从远处逐件运入,新工作台只会等待。真正的改进是把相似零件成批送入,让多条工序同时运转,并让上一道工序的产物直接交给下一道。
处理芯片也面临同样的不平衡:计算部件增加得快,搬运数据和等待数据的改善却慢。没有暴露足够多的独立工作,或让中间结果反复离开芯片,再多计算资源也会闲置。先预测:实验里保持元素数不变,把每元素全局读取翻倍并把局部性降到零,单纯增加算术操作能否恢复吞吐?
Streaming Architectures 实验
先预测:保持元素数不变,把 global words 加倍、locality 降低,增加算术操作还能否让并行单元持续忙碌?
长 stream 暴露数据并行;局部中间结果和多个 resident waves 共同隐藏外部延迟。
本章主题 Streaming Architectures and Technology Trends 的核心是一条因果链:技术趋势扩大计算与通信的增速差,架构因此偏向高密度数据通路、显式并行、局部通信和吞吐优先的执行模型。
1. 技术趋势:关键不是都变快,而是斜率不同
原章引用 2003 年 ITRS 路线图,以约 50%/年的芯片能力增长、25%/年的 DRAM bandwidth 增长和仅 5%/年的 DRAM latency 改善解释不平衡。它还列出 GeForce FX 5800、FX 5950 与 GeForce 6800 的可编程浮点能力相对外部带宽约为 2、2.66、接近 6 operations per word。这些数值只描述当时的代际,不是现代 GPU 基准。
三项趋势导出三种压力。第一,跨芯片甚至跨芯片内部区域的通信相对算术更贵,可能值得用 recompute 或专用逻辑替代搬运。第二,带宽改善快于单次访问延迟,硬件必须在等待时继续执行其他工作。第三,晶体管总数增长快于单管功耗下降,原章预见 operations per watt 会比绝对 operations per second 更重要。
2. 芯片面积:control、data path 与 storage 的取舍
↡执行算术、逻辑和数据变换的硬件路径;相对于控制与存储,它直接完成程序要求的计算。同一块 die 上的晶体管大致分给 control、data path 与 storage。复杂分支预测、乱序执行和大缓存提高单线程通用性与低延迟,却占用本可复制更多算术单元的面积;图形工作负载拥有大量同构元素和较规则控制,GPU 可以把更多预算交给并行 data path,并让 rasterization 等稳定任务使用专用单元。
专用化不是“固定功能永远优于可编程”。它用适用范围换单位面积与单位功耗效率;如果需求变化过快,硬连线功能可能失去价值。原章描述的演进正是图形管线从固定功能逐步加入 vertex 与 fragment programmability,同时保留适合专用实现的阶段。
3. 用三级并行填满 data path
原章区分三种可叠加的并行。task parallelism 让顺序阶段同时处理不同批次,例如 vertex stage 处理批次 B 时 raster stage 处理批次 A;data parallelism 让同一阶段同时处理多个独立元素;instruction parallelism 则在单个元素内部重叠互不依赖的简单操作。
↡让同一个 kernel 同时应用于多个互不依赖的数据元素,是 GPU 扩展大量算术通路的主要并行来源。三级并行不能靠硬件凭空创造。输入太短会使 lanes 不满;跨元素依赖会阻止 data-parallel 映射;长串行依赖会减少 instruction overlap;某个 stage 远慢于其他 stage 又会让整条 task pipeline 堵塞。程序需要把独立性和通信边界写进结构,硬件才能安全调度。
4. stream 与 kernel:把独立性写进程序模型
↡由同一种数据类型构成的有序元素集合;长 stream 能摊薄启动成本并提供足够多的并行工作。 ↡对一个或多个完整 streams 执行计算并产生输出 streams 的函数;同一 kernel 内不同元素之间不得互相依赖。stream 可以是 numbers、points、triangles 或 matrices。典型 kernel 对每个输入元素执行同一函数,也就是 map;模型还允许 expansion、reduction 与 filter。限制 kernel 输出只依赖输入、且单个 kernel 内元素相互独立,使编译器提前知道局部数据需求,也使表面串行的 element loop 可直接映射到并行硬件。
图形管线天然符合这种模型:vertex、assembly、clipping、rasterization 和 fragment processing 是连接的 kernels,阶段间通过 streams 传递,局部输出很快被下一阶段消费。模型不是让通信消失,而是使“何处传递、传什么、能否保留在片上”变得显式。
const shaded = vertices.map((vertex) => shadeVertex(vertex, uniforms));
const triangles = assembleTriangles(shaded);
const fragments = rasterize(triangles);
const visible = fragments.filter((fragment) => depthPasses(fragment));
const colors = visible.map((fragment) => shadeFragment(fragment, textures));这段代码是结构镜像,不代表 JavaScript 数组会自动获得 GPU 性能。真实实现必须把每个 stage 映射到目标 API 的 dispatch/draw、buffer、同步和 layout,并确认中间 streams 没有因不必要的 host round-trip 离开设备。
5. arithmetic intensity:每个外部 word 做多少工作
↡有效算术操作数与外部传输数据量之比;它描述每搬运一个 word 或 byte 能完成多少计算。本节符号如下:
- :stream 元素数
- :每元素有效 operations
- :每元素访问的 global words
- :被片上复用、缓存或消除的比例
- :相对 off-chip traffic 的 arithmetic intensity
总有效运算和外部 word 数分别是
这个式子在说:元素数同时放大计算与原始流量,而片上局部性 只减少真正离开芯片的数据量。
因此
这个式子在说:减少一次外部读取、提高复用或在已读数据上做更多有用工作,都能提升每个外部 word 的计算量;单纯扩大 只增加并行规模,不会改变这个比值。
缓存用 storage transistors 换 bandwidth,compression 用压缩/解压计算和逻辑换 bandwidth,recompute 则直接用廉价计算替代查询表或远端传输。选择前要检查数据是否会复用、压缩率是否稳定、重算是否精确,以及新增计算会不会把通信瓶颈反转成 data-path 瓶颈。
6. latency tolerance:等待时切换到 ready work
↡在一批工作等待高延迟操作时执行其他独立工作,从而减少处理单元暴露空闲时间的能力。高吞吐架构不要求每个元素尽快完成,而是要求单位时间完成尽可能多的元素。某个 wave 发出 memory request 后,若还有其他 ready waves,处理单元可以继续发射它们;数据返回后再恢复原 wave。访问 latency 依然存在,只是被独立工作覆盖。
长 stream 提供更多可切换元素,深 pipeline 让多个阶段重叠,局部状态则避免每次切换都访问外部 memory。若 stream 太短、寄存器占用让 resident waves 太少,或所有 waves 同时依赖同一个远端结果,延迟就会重新暴露为 stall。
7. 从历史架构动机到现代工作负载判断
原章认为 programmable vertex/fragment stages 已让 GPU 成为可处理图形之外任务的 stream processor,并展望更广的 programmability、阶段资源共享、功耗管理,以及 CPU/GPU 功能继续融合。这些预测应作为历史脉络阅读;现代接口和微架构已演进,但判断工作负载的方法仍可复用。
先检查是否有数百个以上同类型、相互独立的元素;再标记 global inputs/outputs 与 stage-local intermediates;然后计算有效 operations 和 bytes/words,观察 communication、compute、control divergence 或 occupancy 哪条限制吞吐;最后才决定 fusion、tiling、compression、recompute 或扩大并行规模。
一个 serial、分支复杂、每步依赖上一步且必须低延迟返回的任务,未必适合吞吐导向 GPU。反过来,长 stream、规则 kernel、高局部性和允许批量完成的任务,正好匹配本章描述的架构契约。选择不是“CPU 或 GPU 谁更强”,而是程序暴露的结构与目标机器资源是否一致。
三步验收:独立性、流量、等待
三个阶段对同一 workload 保留 CPU reference、元素计数、operations、global words 与输出 hash。先证明并行拆分正确,再测量流量,最后观察延迟是否被 ready work 覆盖。
第一步:画出 stream、kernel 与依赖边界
为每个 kernel 标出 input/output types、element count 和跨元素读写。任何同一 pass 内的邻居依赖、共享写冲突或全局顺序,都必须先改写为独立 passes 或显式 reduction。
本章小结
- 算力、带宽、延迟与功耗的不同斜率推动吞吐架构。
- GPU 用更多 data path 和三级并行处理规则批量工作。
- stream/kernel 显式表达元素独立性与阶段通信。
- arithmetic intensity 衡量每个外部 word 的有效工作。
- latency tolerance 依赖足够多 ready work,而非消除延迟。
练习
问题 1|手算强度。 一个 kernel 处理 4096 个元素,每元素 32 次有效 operations、读写 8 个 global words;tiling 让 75% 数据留在片上。优化前后 arithmetic intensity 各是多少?元素数为何不影响比值?
问题 2|拆分 kernel。 某循环让元素 先读取刚更新的元素 ,随后所有元素把结果累加到一个全局值。如何改造成可验证的 stream pipeline?
问题 3|修改实验并提出优化。 在 StreamingLab 中选择 serial,把 stream elements 降到 32、global words 调到 12、locality 调到 0。依次改变哪两个参数最能区分“并行不足”和“通信过重”?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- data path
- data parallelism
- stream
- kernel
- arithmetic intensity
- latency tolerance