Intro-ops/notebooks/03_reduce_sum_kernel.ipynb

274 lines
8.5 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 03 — Reduce Sum 算子\n",
"\n",
"## 学习目标\n",
"\n",
"1. 理解 shared memory 树形归约原理\n",
"2. 理解 `__syncthreads()` 的使用时机和条件分支限制\n",
"3. 理解 `T.Serial` 在归约场景中的作用\n",
"4. 完成 NVIDIA + TileLang 两个 kernel 的 TODO\n",
"\n",
"Reduce sum 是第一个需要**线程间通信**的算子——从 copy/vector_add 的“各自为战“跨越到“协同计算“。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 概念导入:树形归约\n",
"\n",
"目标:把一行 N 个元素求和为 1 个值。每个 block 处理一行:\n",
"\n",
"```\n",
"Step 0: [3, 1, 7, 0, 4, 1, 6, 3] ← 线程各自的部分和存入 shared memory\n",
"Step 1: [7, 2, 13, 3] ← stride=4, 相邻配对相加\n",
"Step 2: [20, 5] ← stride=2\n",
"Step 3: [25] ← stride=1, 最终结果\n",
"```\n",
"\n",
"每步后必须 `__syncthreads()` ——因为下一步要读上一步别人写的数据。\n",
"\n",
"详细图示见 [docs/diagrams/tree-reduction.md](../docs/diagrams/tree-reduction.md)。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## PyTorch 参考实现"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"\n",
"src = torch.randn(32, 128, device=\"cuda\", dtype=torch.float32)\n",
"expected = torch.sum(src, dim=1) # 对每行求和\n",
"\n",
"print(f\"输入 shape: {src.shape}\")\n",
"print(f\"输出 shape: {expected.shape}\")\n",
"print(f\"第一行: src[0, :5] = {src[0, :5]}\")\n",
"print(f\"第一行和: {expected[0]:.6f}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## NVIDIA CUDA Kernel\n",
"\n",
"打开 `ops/reduce_sum/nvidia/kernel.cuh`。这是第一个使用 shared memory 的 kernel。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"KERNEL_SKELETON = \"\"\"\n",
"__global__ void reduce_sum_rowwise_kernel(\n",
" float *out, const float *in, int64_t rows, int64_t cols) {\n",
"\n",
" // TODO: implement a row-wise reduce_sum kernel with shared memory.\n",
" //\n",
" // Suggested steps:\n",
" // 1. Use one block per row.\n",
" // int row = blockIdx.x;\n",
" // 2. Each thread accumulates its columns:\n",
" // float sum = 0;\n",
" // for (int c = threadIdx.x; c < cols; c += blockDim.x)\n",
" // sum += in[row * cols + c];\n",
" // 3. Store partial sum to shared memory, then __syncthreads().\n",
" // 4. Tree reduction:\n",
" // for (int s = blockDim.x/2; s > 0; s >>= 1) {\n",
" // if (threadIdx.x < s) smem[tid] += smem[tid + s];\n",
" // __syncthreads();\n",
" // }\n",
" // 5. Thread 0 writes smem[0] to out[row].\n",
"}\n",
"\"\"\"\n",
"print(KERNEL_SKELETON)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ⚠️ 关键陷阱:`__syncthreads()` 不能在条件分支内\n",
"\n",
"```cuda\n",
"// 错误——死锁!\n",
"if (tid < s) {\n",
" smem[tid] += smem[tid + s];\n",
" __syncthreads(); // 只有 tid < s 的线程执行同步\n",
"}\n",
"\n",
"// 正确——所有线程都到达同步点\n",
"if (tid < s) {\n",
" smem[tid] += smem[tid + s];\n",
"}\n",
"__syncthreads(); // 全部线程到位\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 验证 NVIDIA 版本\n",
"import subprocess, sys\n",
"result = subprocess.run([\n",
" sys.executable, \"-m\", \"pytest\",\n",
" \"tests/op_tests/test_reduce_sum.py\", \"-v\",\n",
" \"--backend\", \"nvidia\"\n",
"], capture_output=True, text=True)\n",
"print(result.stdout)\n",
"if result.returncode == 0:\n",
" print(\"✓ NVIDIA reduce_sum kernel 测试全部通过!\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## TileLang Kernel\n",
"\n",
"打开 `ops/reduce_sum/tilelang/kernel.py`。TileLang 版本用 `T.Serial` + `T.reduce_sum` 替代手动树形归约。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"TILELANG_SKELETON = \"\"\"\n",
"@tilelang.jit\n",
"def reduce_sum_kernel(src, BLOCK_N: int, BLOCK_M: int):\n",
" N, M = T.const(\"N, M\")\n",
" dtype = T.float32\n",
" src: T.Tensor((N, M), dtype)\n",
" out = T.empty((N,), dtype)\n",
"\n",
" # TODO: implement a tiled row-wise reduce_sum kernel.\n",
" #\n",
" # Key insight: outer loop uses T.Serial (accumulate state),\n",
" # inner loop uses T.reduce_sum (parallel reduction within tile).\n",
"\n",
" return out\n",
"\"\"\"\n",
"print(TILELANG_SKELETON)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 为什么外层用 `T.Serial`\n",
"\n",
"分块累加时,每个 chunk 的求和结果需要**累加到同一个累加器**。这是有状态依赖的——后一个 chunk 必须在前一个完成之后才能累加。所以列方向的循环是 `T.Serial`。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 验证 TileLang 版本\n",
"result = subprocess.run([\n",
" sys.executable, \"-m\", \"pytest\",\n",
" \"tests/op_tests/test_reduce_sum.py\", \"-v\",\n",
" \"--backend\", \"tilelang\"\n",
"], capture_output=True, text=True)\n",
"print(result.stdout)\n",
"if result.returncode == 0:\n",
" print(\"✓ TileLang reduce_sum kernel 测试全部通过!\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 对比验证"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from operator_runtime import reduce_sum\n",
"\n",
"src = torch.randn(32, 128, device=\"cuda\", dtype=torch.float32)\n",
"my_out = reduce_sum(src, dim=1, backend=\"nvidia\")\n",
"torch_out = torch.sum(src, dim=1)\n",
"\n",
"print(f\"我的 kernel[:5]: {my_out[:5]}\")\n",
"print(f\"PyTorch[:5]: {torch_out[:5]}\")\n",
"print(f\"最大误差: {(my_out - torch_out).abs().max().item():.2e}\")\n",
"print(f\"一致: {torch.allclose(my_out, torch_out, atol=1e-5, rtol=1e-5)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 进阶Bank Conflict 优化\n",
"\n",
"如果你已经跑通基础版本,可以尝试优化 bank conflict\n",
"\n",
"```cuda\n",
"// 加 padding 错开 bank 访问\n",
"__shared__ float smem[BLOCK_SIZE + PADDING];\n",
"// 或者归约从 stride=2 开始而非 stride=1\n",
"```\n",
"\n",
"对比优化前后的 benchmark 带宽数据。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 检查清单\n",
"\n",
"- [ ] 理解 shared memory 树形归约原理\n",
"- [ ] 理解 `__syncthreads()` 的使用时机和条件分支限制\n",
"- [ ] 理解 `T.Serial` 在归约场景中的作用\n",
"- [ ] NVIDIA `kernel.cuh` TODO 完成\n",
"- [ ] TileLang `kernel.py` TODO 完成\n",
"- [ ] 两种后端测试全部通过\n",
"- [ ] 尝试优化 bank conflict\n",
"- [ ] benchmark 跑通"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}