GPU Gems 2 · Chapter 32. Taking the Plunge into GPU Computing
从局部性、计算密度、传输摊销、浮点精度和 scatter 绕行策略出发,判断一个 CPU 算法是否值得迁移到 GPU。
学习目标
- 能解释内存访问、计算量、数据传输和数值表示如何共同决定 GPU 迁移收益
- 能修改 StrategyLab 中的 workload、locality 与 precision,并根据派生指标选择 scatter 绕行策略
- 能回答:当地址动态变化、读回成本很高且结果需要高精度时,应如何判断 GPU 是否值得使用
先问:为什么“搬到 GPU”不等于“自然变快”
想象两间工厂:一间擅长让很多工人同时做同一种动作,另一间擅长让少数工人灵活地处理不同订单。把订单送进第一间工厂有固定的装卸成本;如果只做一小步就把半成品搬回来,工人再多也救不了总时间。
本章解决的问题是:面对一个 CPU 算法,怎样在动手改写前判断它是否值得迁移?如果忽略访问方式、装卸成本和数字表示,最常见的结果是并行程序内部很快,但端到端更慢,或者结果看似接近却在大索引处出错。
1. 访问顺序决定内存能否跟上
↡程序连续访问相邻数据的程度;连续、规则的访问通常更容易被硬件批量服务。CPU 和 GPU 都需要 locality,但两者的缓存职责不同。GPU 的 texture cache 主要服务纹理过滤,容量和布局更偏向二维局部区域;它不是一个能像 CPU cache 那样同时缓存任意读写的通用工作区。因此,连续扫描大向量通常容易发挥 GPU 带宽,而 pointer chasing 和随机读会让读取延迟更难隐藏。
原章用 GeForce 6800 Ultra 与 Pentium 4 的历史测量说明了这一差异:同一 GPU 对 cached、sequential、random 三种访问模式的表现可以相差很大。数字只属于当时硬件;可迁移的结论是“先画出地址序列,再判断数据布局”,而不是把旧带宽数字当作现代保证。
2. 让计算覆盖读取等待
↡算术操作数量与内存操作数量的比值;它表示每次数据读取能支撑多少计算。发出 texture fetch 后,GPU 可以先切换去处理下一个 fragment;数据返回后再继续原来的计算。若 kernel 只有一次读取、一次加法和一次写回,等待很容易暴露出来;如果每个读取之后有足够多的独立算术,计算就有机会覆盖这段 latency。
可以用一个粗略比值判断方向:
这个式子在说:每次内存操作支撑的计算越多,任务越可能受 GPU 算力而不是读写等待限制。
原章把这一点和 CPU/GPU 的计算吞吐差异联系起来,但“高 AI 必然更快”仍是不完整的结论。元素必须足够多、访问不能完全随机,且 host 与 GPU 之间不能反复同步;这些条件缺一个,计算优势都可能被边界成本抵消。
3. 把下载和读回放进总时间
↡把初始数据传到 GPU 以及把结果取回 CPU 的两段边界传输;它们不属于 kernel 时间,却属于端到端时间。CPU 版本通常不需要先下载数据,也不需要把结果读回。GPU 版本若只运行一次简单向量加法,必须把两份输入送入显存、运行 kernel,再把结果拿回主机;这些步骤可能超过 kernel 本身节省的时间。模拟类任务更有机会摊薄成本,因为数据可以在 GPU 上迭代许多次之后再读回。
因此比较时至少记录四段:CPU 计算、download、GPU compute、readback。纹理与 frame-buffer 的格式也会影响传输速度;驱动若需要做格式转换,测到的就不只是算法成本。
4. 浮点格式会改变正确性
↡浮点数能保留多少有效位以及能连续表示多大整数的能力;它决定数值误差和地址计算是否可靠。GPU Gems 2 所处的硬件时代,GPU 可能提供 NVIDIA fp16、ATI fp24 或 NVIDIA fp32 等格式。mantissa 位数越少,能精确表示的有效数字越少;这对着色颜色可能足够,对数值模拟、归约和地址计算却可能造成明显误差。fp16 在历史表格中只能连续表示到 2,048 左右的整数,超过这个范围时,某些整数会被跳过。
地址计算尤其危险:如果把一维索引压进浮点值,再用它计算二维纹理坐标,表示误差会表现成偶发的 off-by-one。修法不是“多看几位小数”,而是明确目标格式的 mantissa、最大连续整数、边界行为和允许误差;现代设备支持的格式与旧 GPU 不同,必须在目标 API 上复核。
5. scatter 不能直接照搬
↡从计算出的地址写入内存,例如 a[i] = x;fragment program 通常只能写到预先确定的 fragment 输出位置。在上一章的模型里,gather 是从计算出的地址读取;本章关注相反的 a[i] = x。fragment program 没有任意 texture write 指令,输出地址在 fragment 执行前已经由 rasterization 决定,因此很多 CPU 算法的间接写入不能直接翻译。
固定连接:把 scatter 改写成 gather
spring-mass 系统中,弹簧连接的质量点如果是固定的,就可以先把每条 spring 的 force 写到连续 buffer,再让第二个 pass 遍历每个 mass,主动 gather 会影响它的力。多一个 pass 换来了固定的输出位置和可验证的读取关系。
动态地址:记录地址、排序、再 gather
粒子在每个时间步移动到不同 voxel 时,scatter 地址运行时才知道。可以输出 (value, address) 对,按 address 排序,让相同目标相邻,再对每个目标做搜索与 gather。它的 pass 多、实现重,但不需要把中间结果读回 CPU。
少量写入:用 vertex point rendering
vertex program 天然能改变点的目标位置,因此少量 scatter 可以用点渲染表达。代价是 rasterization 利用率较低,而且多个点落到同一个位置时会发生 collision;可以用 z-buffer 或 blending 设计冲突规则,但不能假设最后写入者稳定。
6. 一个可重复的迁移决策
把决策压缩成四问:访问是否连续?每次读取能否支撑足够计算?download/readback 能否被多次迭代摊薄?若存在 scatter,地址是否固定、散射量多大、碰撞如何处理?这四问比“GPU 有多少 Gflops”更接近真正的工程选择。
measure(addressPattern, arithmeticOps, memoryOps, transfers)
if locality is low: reshape data or keep the CPU path
if arithmeticOps / memoryOps is low and transfers are large: amortize or stop
if scatterAddress is fixed: convert scatter to gather
else if scatterCount is small: render points and define collisions
else: sort (value, address) pairs, then gather代码只是决策骨架,不是某个历史 API 的可运行实现。真正落地时仍需把纹理格式、同步、精度和设备 profile 填进测量表。
先猜一猜:把 histogram 切到 vector,再把 locality 拖低,哪一项会改变推荐策略?打开实验,依次只改一个控件,并观察 texture reads、transfer words、safe integer ceiling 和 recommended mapping。
Strategy Lab · compare the bottleneck shape
What this workload exposes
动态写入地址的 histogram,需要先把 scatter 改成 address sort + gather。
Derived metrics
recommended mapping
address sort + gather
这些是由控件直接推导的工作量与表示范围,不是合成性能分数;最终仍需在目标设备上 profile。
三步验收:先筛选,再改写
第一步:画地址流与计算流
把 CPU 版本每次读写的地址列出来,标出连续段、跳跃段和数据复用;再计算每个元素的 arithmetic operations 与 memory operations。先确认任务的访问形状,再决定是否值得迁移。
本章小结
- locality 决定 GPU 内存系统能否持续供数。
- arithmetic intensity 帮助判断计算能否覆盖读取等待。
- download/readback 属于端到端成本,必须被迭代摊薄。
- floating-point precision 同时影响数值误差和地址安全。
- scatter 要按地址是否固定、规模和碰撞语义改写为 gather、排序或点渲染。
练习
问题 1|迁移判断。 一个向量加法 kernel 每个元素只做 2 次加法,却需要下载两份输入并读回结果;一个反应扩散模拟在同一份数据上运行 500 个迭代。哪一个更可能从 GPU 受益?需要补测哪些数据才能确认?
问题 2|精度与地址。 使用历史 fp16 路径处理一个需要索引到 3,079 的二维纹理。为什么即使小索引测试通过,也不能相信所有地址都正确?你会在 StrategyLab 中改变哪个控件并记录什么证据?
问题 3|修改实验代码。 StrategyLab 中把 histogram 的推荐路径固定为 address sorting + gather。请修改决策逻辑:当散射地址固定时改为 convert scatter to gather,当散射值少于输入的 5% 时改为 render points,并说明要新增哪两个验证指标。
名词解释
本章出现的专业名词,用大白话再讲一遍。
- locality
- arithmetic intensity
- download/readback
- floating-point precision
- scatter