Intro-ops/course/scripts/02_vector_add_script.md

3.9 KiB
Raw Blame History

Vector Add 算子 — 教学视频脚本

时长15-20 分钟 | 难度:入门


[5min] 概念讲解

开场30s

"上次我们做了 copy——纯内存搬运。今天加一点计算vector_add逐元素加法。结构上和 copy 几乎一样,但引入了两个输入和一个辅助函数。"

算子在深度学习中的用途1min

"逐元素操作element-wise ops在深度学习中极常见ReLU、dropout、residual connection 的加法、batch norm 的 scale+shift。vector_add 是所有这些操作的原型。"

算法推导3.5min

关键画面: 展示线程网格布局图(docs/diagrams/thread-grid-layout.md

讲解要点:

  1. 和 copy 一样的 grid-stride loop 模式
  2. 区别:每次循环处理 a[i] + b[i] 而非 src[i]
  3. 因为是逐元素,线程间仍然零通信
  4. half 精度需要特殊处理:__hadd() 而非 +

过渡语: "vector_add 的 CUDA kernel 骨架比 copy 多了一个辅助函数——我们来写。"


[10min] 代码实操

CUDA Kernel5min

打开文件: ops/vector_add/nvidia/kernel.cuh

Step 1: add_values<T> 泛型版1min

template <typename T>
__device__ T add_values(T a, T b) {
    return a + b;  // 泛型版本直接用 +
}

"__device__ 表示这个函数运行在 GPU 上,只能被 kernel 或其他 device 函数调用。为什么抽出一个函数?因为 half 精度需要特化。"

Step 2: add_values<half> 特化版1min

template <>
__device__ inline half add_values<half>(half a, half b) {
    return __hadd(a, b);  // half 专用加法指令
}

关键决策点: "为什么 half 用 __hadd 而不是 +CUDA 中 half 是存储类型——算术操作需要先转 float 或调用 intrinsics。__hadd 是硬件原生指令,更快。"

Step 3: kernel 函数3min

和 copy kernel 几乎一样,只有行不同:out[i] = add_values(a[i], b[i]);

"注意 __restrict__ 关键字——告诉编译器 outab 指向不重叠的内存区域,可以做更激进的优化。"

TileLang Kernel5min

打开文件: ops/vector_add/tilelang/kernel.py

边写边讲:

  • 外层 T.Parallel 分发 tile内层 T.Parallel 分发元素
  • "两层都是 T.Parallel——因为所有 (a[i], b[i]) 独立计算"

对比: "CUDA 里你手动写了三层抽象thread → block → gridTileLang 两层 Parallel 就完成了。编译器把外层的 tile Parallel 映射到 block内层的 element Parallel 映射到 thread。"


[3min] 测试验证

跑测试

PYTHONPATH=python:. CAMP_BUILD_DIR=build-nvidia \
  pytest tests/op_tests/test_vector_add.py -v --backend nvidia

重点展示: "注意测试里包含了 half 精度——如果你 add_values<half> 写错了,这里会炸。"

Benchmark 解读

"vector_add 的理论带宽利用率和 copy 接近——因为它也是 memory-bound 的。如果你的带宽比 copy 低很多,检查是否有非合并访问。"


[2min] 常见错误演示

错误 1half 用 a + b30s

"CUDA 里 half + half 会自动提升为 float 再截断——能编译通过但结果有精度损失。必须用 __hadd。"

错误 2不特化 half 就直接在 kernel 里用 +30s

"add_values<T> 里泛型版用 a + bhalf 特化版用 __hadd。如果没写特化版half 走泛型 a + b 也能跑——但性能差。测试能通过benchmark 会暴露。"

错误 3TileLang 忘记内层 Parallel1min

"如果内层用 T.Serial——tile 内的 256 个元素就变顺序执行了,线程完全没利用上。结果速度和单线程差不多。"


课后挑战

"尝试给 vector_add 添加第三个输入:c = a + b + cin-place add。提示operator_runtime/ops/vector_add.pyvector_add_ 函数签名。"