GPU Gems 2 · Chapter 43. GPU Computing for Protein Structure Prediction

把 NMR 距离上下界平滑改写为 GPU 上的 Floyd–Warshall:从三角不等式、ping-pong 缓冲到 RGBA 向量化,理解蛋白质结构预测中的数据并行。

学习目标

  • 能把 NMR 的原子距离约束表示成带上下界的距离矩阵,并用三角不等式收紧它们
  • 能逐步解释 Floyd–Warshall 如何映射到 GPU fragment unit、ping-pong 缓冲和 RGBA 向量化
  • 能修改实验中的边界类型或 k 轮数,判断哪些矩阵单元被更新以及为什么只能写回一个确定位置
  • 能回答:为什么 GPU 实现必须先收紧 upper bound,再据此更新 lower bound?

蛋白质结构预测里,实验设备通常只给出一部分原子之间的距离范围。剩下的范围很宽,像一张只画出少量道路的地图:单条道路的信息不够,但三点之间的绕行关系可以不断排除不可能的距离。本章把这个“收紧范围”的过程变成 GPU 可并行的矩阵更新。

GPU 适合这里不是因为它懂生物学,而是因为每一轮都能让很多矩阵单元同时读取相同的中间节点并写回自己的位置。难点在于更新有时间顺序、上下界有依赖、纹理不能原地读写;处理好这三件事,旧式 fragment unit 也能承担密集的数值计算。

1. 从原子距离到带边界的完全图

是本章的任务。将含有 n 个原子的分子表示为完全图 Kₙ:节点是原子,边 (i, j) 记录距离的上界 uᵢⱼ 和下界 lᵢⱼ。NMR 只提供部分边的约束,未知边可以先用分子直径和接近零的下界占位。

Kₙ:原子是节点,距离上下界是边标签NMR 只测到部分距离;未知边先用宽松上下界占位ijkl边 (i, j)uᵢⱼ:不能超过lᵢⱼ:不能低于平滑后约束更紧每对原子都要满足同一组几何约束
把分子距离约束改写成带上下界标签的完全图。

矩阵存储让 GPU 可以把每条边当作一个独立的输出单元。距离矩阵的对角线是零,通常还保持对称性 D[i, j] = D[j, i]。不过,GPU 版本将上下界分别放在纹理里,即使 (i, j)(j, i) 重复,也能减少 shader 的条件分支和取样次数。

2. 三角不等式如何收紧范围

给每条边带来一个中间节点 k。对上界,绕 i → k → j 的路径提供更紧的候选值:uᵢⱼ ← min(uᵢⱼ, uᵢₖ + uₖⱼ)。对下界,ij 不能比其中一段减去另一段更近:lᵢⱼ ← max(lᵢⱼ, lᵢₖ − uₖⱼ, lₖⱼ − uᵢₖ)

三角不等式:一条中间路径就能收紧边界ikjuᵢₖ / lᵢₖuₖⱼ / lₖⱼuᵢⱼ / lᵢⱼuᵢⱼ ← min(uᵢⱼ, uᵢₖ + uₖⱼ)  lᵢⱼ ← max(lᵢⱼ, lᵢₖ − uₖⱼ, lₖⱼ − uᵢₖ)
中间点 k 提供一条绕行路径,帮助收紧 i 与 j 的距离范围。

上界必须先收紧。lower bound 的两个候选差值使用 upper bound;如果先用宽松的 u 去抬高 l,后面再改变 u,就可能错过本轮本应排除的距离。这个先后关系是算法正确性的一部分,不是 GPU 优化的可选项。

3. Floyd–Warshall:把三重循环拆成 GPU 轮次

原本是全对最短路径算法。把它用于 upper smoothing 时,外层固定 k,内层对所有 (i, j) 执行一次 min 更新;lower smoothing 只需替换更新表达式。串行伪代码的关键结构如下:

for k = 0 .. n - 1:
    for every pair (i, j) in parallel:
        U[i, j] = min(U[i, j], U[i, k] + U[k, j])

这段结构适合 GPU,因为一个输出单元只写 U[i, j],不需要写入其他边。当前 k 的所有 fragment 都可以并行;只有外层的 k 轮必须按顺序推进,下一轮要看到上一轮完整的矩阵。

Floyd–Warshall:固定 k,并行更新全部 (i, j)jikD[i, j] ← min(D[i, j], D[i, k] + D[k, j])一个 fragment (i, j)读:D[i,j]、D[i,k]、D[k,j]算:min(旧值, 绕 k 的路径)写:唯一的 D[i,j]读多个位置、写一个确定位置,正好匹配 GPU 的数据并行模型
外层循环固定 k,内层的每个矩阵单元只写回自己的位置。

在 fragment shader 中,(i, j) 由当前像素位置确定,(i, k)(k, j) 的纹理坐标由 rasterizer 或 shader 提供。每个 fragment 读取三个值,计算一个 min 或 max,然后只输出自己的颜色值。这是“读多个、写一个”的数据并行形状。

4. GPU 实现:纹理、动态更新与 ping-pong

解决外层 k 轮的动态依赖。GPU 不能把同一块纹理同时当作 sampler 输入和 render target;因此第 k 轮从 front 纹理读取,把结果渲染到 back 纹理,结束后交换两个句柄。

动态更新:ping-pong 缓冲隔离读写Front / ping纹理输入:Dᵏ读 D[i, k]、D[k, j]k 轮开始Back / pongrender target:Dᵏ⁺¹写回每个 fragmentk 轮结束渲染交换角色下一轮:上一轮输出成为下一轮输入
GPU 不能同时把同一块存储当作纹理和 render target,因此每轮交换两块缓冲。
for (int k = 0; k < n; ++k) {
    bindTexture(frontBounds);
    bindRenderTarget(backBounds);
    setUniform("uMiddle", k);
    drawFullscreenPrimitive();
    swap(frontBounds, backBounds);
}

这里的 drawFullscreenPrimitive 不是在画蛋白质几何,而是在启动一张覆盖距离矩阵的计算网格。每个 fragment 的颜色就是一个新的距离边界;将纹理坐标生成交给 rasterizer,还能减少 shader 指令并让访问模式更规则。

把 GPU 四分量运算单元利用起来。连续四条边可以装进一个 RGBA texel:T[i, j] = (D[i, 4j], D[i, 4j+1], D[i, 4j+2], D[i, 4j+3])。这样渲染矩形的宽度约变为原来的四分之一,但 shader 必须把 D[i, k] 广播到正确通道,并处理 n 不是 4 的倍数时的尾部。

向量化:把四条边打包进一个 RGBA texel原始距离矩阵的一行D[i,4j]D[i,4j+1]D[i,4j+2]D[i,4j+3]D[i,4j+4]D[i,4j+5]pack一个纹理 texel T[i, j]RD[i,4j]GD[i,4j+1]BD[i,4j+2]AD[i,4j+3]存储和渲染宽度约缩小为四分之一,但 shader 要按通道重排索引
把四条连续边装进 RGBA,一个 fragment 同时处理四个标量更新。

5. 动手实验:观察 k 轮怎样收紧矩阵

先预测:把“完成的 k 轮”从 0 拖到 3,哪一类矩阵单元会先变小?打开 RGBA 向量化后,数值会改变,还是只会改变存储和 fetch 估算?

GPU Gems 2 · Chapter 43

距离上下界平滑:每一轮 k 都在矩阵上留下证据

改变中间节点、边界类型和打包方式,观察哪些矩阵单元被收紧,以及 GPU 需要读写多少数据。

▷ 可交互

上界矩阵 U · 初始边界

高亮的行/列展示当前中间节点 k 的读路径。

i\j012345
00.07.59.211.810.613.2
17.50.06.410.19.812.3
29.26.40.07.18.610.8
311.810.17.10.06.28.4
410.69.88.66.20.05.7
513.212.310.88.45.70.0
当前操作:尚未引入中间节点;GPU 仍以一个 fragment 写回一个确定的矩阵位置。

实验中的上界矩阵使用与官方算法相同的 min 更新,下界矩阵使用依赖 upper 的 max 更新;每次推进都按矩阵副本计算一整轮,避免把同一轮的新值提前泄漏给其他单元。绿色单元表示相对初始边界已经收紧,蓝色行列表示当前中间节点的读路径。

6. 三角存储、精度与规模边界

上下界矩阵是对称的,因此只保存上三角和下三角可以节省空间;原章选择把两个三角分别存进纹理,并在几何上只绘制对应区域。这样做会浪费 (i, j)(j, i) 的重复值,却能减少分支,让 texture fetch 和 fragment 区域更规则。对于 GPU,少一次条件判断有时比少一半 texel 更值得,必须在目标设备上测量。

距离平滑对精度敏感。原章的实验使用 32 位 IEEE 浮点纹理,并在不同矩阵规模上比较 CPU 与 GPU 时间;GPU 的优势来自并行性和带宽,而不是改变了 O(n³) 的算法复杂度。规模继续扩大时,纹理边长、GPU 显存和单次 render target 限制会先成为瓶颈,不能把“更大的 n”当作免费收益。

实现检查可以按下面的顺序收敛:

  1. 先用 CPU 标量版本验证 upper 和 lower 的更新式与顺序。
  2. 再用双缓冲 fragment 版本逐 k 对比每个矩阵单元。
  3. 打开三角区域裁剪,确认只读取合法的上三角或下三角。
  4. 最后加入 RGBA packing,并测试 n mod 4 的尾部通道和对称性。

小结

  • 距离上下界平滑把稀疏 NMR 约束变成可迭代的矩阵更新。
  • 三角不等式用中间节点收紧 upper,再用 upper 抬高 lower。
  • Floyd–Warshall 的内层 (i, j) 更新具有 GPU 需要的独立写回形状。
  • ping-pong 隔离动态轮次的读写,RGBA packing 减少存储宽度与向量 fetch。
  • 精度、纹理尺寸、尾部通道和显存限制决定了 GPU 实现的实际边界。

练习

问题 1|边界规则 给定 uᵢⱼ = 12uᵢₖ = 5uₖⱼ = 4,以及 lᵢⱼ = 2lᵢₖ = 6lₖⱼ = 5。经过 k 的 upper 和 lower 候选值分别是多少?

问题 2|改写 Demo 把实验改成只显示 lower smoothing,并在每次推进后标出相对初始值变大的单元。为什么 lower 更新仍然需要一份已经收紧的 upper 矩阵?

问题 3|GPU 取舍 n 不是 4 的倍数时,RGBA packing 要处理什么?如果结果与 CPU 版本不同,按什么顺序排查?

名词解释

本章出现的专业名词,用大白话再讲一遍。

distance-bound smoothing
不断排除不可能的距离,让每对原子的上下界变得更紧。
triangle inequality
三点之间一条边不能超过另外两条边之和,也不能短于两段距离的差。
Floyd-Warshall algorithm
轮流指定中间节点,尝试用绕行路径改进所有点对距离的算法。
ping-pong buffering
用两块存储轮流读写,避免同一纹理同时被读和写。
vectorization
把多个独立标量塞进一个向量,一次执行相同的运算。

资料与写作方式声明

本章以GPU Gems 系列权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…