硬件编程与高性能计算:一张可走通的学习地图 / A Practical Learning Map for Hardware Programming and HPC
📅 创建时间:2026-06-02 🏷️ 标签:#Hardware #HPC #CUDA #GPU #并行计算 📚 前置知识:C++ 基础(会写类、指针、STL 即可) 📚 相关知识:[[/04-ai/01-llm-engineering/09-transformer-training-computation]] [[/04-ai/01-llm-engineering/10-transformer-inference-computation]] [[/03-web/05-backend-engineering/08-concurrency]]
文档目标
如果 CUDA、GPU、Cache、SIMD、MPI 像一堆互不相干的缩写,先记住:它们都在解决同一件事——让数据更快地完成计算。
程序的耗时,大致可以拆成三部分:
总时间 = 计算时间 + 搬运数据的时间 + 相互等待的时间容量则是另一条硬约束:数据装不下,再快的计算单元也无法直接工作。整套笔记就是沿着计算、存储、通信、容量四条线,理解从单核 CPU 到 GPU 集群的性能问题。
详细的分层阅读方法和生活化类比见 目录首页。第一次阅读不要求吃透所有公式、参数和代码。
学完后你应该能回答
- 为什么同一段循环,顺序访问通常比随机访问快?
- 为什么 GPU 核心很多,却不适合所有任务?
- 为什么用了 8 张 GPU,训练通常不会正好快 8 倍?
- 为什么优化前要先判断瓶颈在计算、访存还是等待?
- 为什么模型参数看似能装进显存,训练时仍可能 OOM?
两个例子贯穿全部章节
先用向量相加理解机制,再用大模型训练理解规模:
向量相加(建立直觉) 大模型训练(落到真实系统)
│ │
├─ 如何拆成独立任务 ├─ 参数和激活能否装下
├─ 线程如何分工 ├─ 多卡如何分工与同步
├─ 数据放在哪里 ├─ CPU 能否及时供给数据
└─ 时间耗在哪里 └─ 如何测量真实瓶颈文中的“单卡训练 175B 模型 30 天”是用于串联概念的假设场景,不是严谨的可运行基线。单卡首先会遇到容量约束;不同精度、优化器和并行策略也会显著改变时间。学习时要把它当作问题引子,而不是性能结论。
章节之间的因果关系
程序为什么慢?
├─ 单个处理器如何工作? → 01 CPU、Cache、内存层次
├─ 工作能拆开同时做吗? → 02 并行上限与效率
├─ GPU 如何执行这些工作? → 03 GPU 架构 → 04 CUDA 线程模型
├─ 数据如何及时送到计算单元? → 05 内存 → 06 优化 → 11 测量工具
├─ 一台机器还不够怎么办? → 07 CPU 并行 → 08 多机多卡 → 09 协同
└─ 怎样用于 AI 训练? → 10 训练优化 → 12 NPU → 13 趋势建议实际按 01 → 02 → 03 → 04 → 05 → 11 → 06 → 07 → 09 → 08 → 10 阅读。性能分析放在优化之前,是为了先找到问题再选手段。
技术栈总览
┌─────────────────────────────────────────────────────────────────┐
│ 硬件编程技术栈 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ PyTorch │ │ TensorRT │ │ DeepSpeed │ │
│ │ (框架层) │ │ (推理优化) │ │ (分布式) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 编程层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ CUDA C++ │ │ OpenMP │ │ MPI │ │
│ │ (.cu) │ │ (#pragma) │ │ (集群通信)│ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 硬件层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ GPU │ │ CPU 多核 │ │ HPC 集群 │ │
│ │ (NVIDIA) │ │ (x86/ARM) │ │ (多节点) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘给 C++ 攻城狮的 CUDA 工具链入门
本节专门为只学过 C++ 的你准备。如果你只会 g++ main.cpp -o app,下面的内容让你从零搞懂 CUDA 项目的编译流程。
什么是 nvcc?
nvcc 是 NVIDIA CUDA 编译器(NVIDIA CUDA Compiler)。它的作用类似 g++,但专门用于编译 CUDA 代码。
g++ main.cpp -o app # C++ 代码 → CPU 可执行文件
nvcc main.cu -o app # CUDA 代码 → GPU 可执行文件nvcc 实际上是一套工具链的入口,它会:
- 分离代码中的 CPU 部分和 GPU 部分
- 用
g++编译 CPU 部分 - 用
ptxas(NVIDIA 的汇编器)编译 GPU 部分 - 用
gold(链接器)合并两者
nvcc 的安装
# Windows:安装 CUDA Toolkit
# https://developer.nvidia.com/cuda-downloads
# 下载后一路 Next,默认安装到:
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.x
# 安装完成后,命令行验证:
nvcc --version
# 输出类似:
# nvcc: NVIDIA (R) Cuda compiler driver
# Cuda compilation tools, release 12.4, V12.4.131第一个 CUDA 项目的文件结构
my_cuda_project/
├── main.cpp ← C++ 代码:CPU 端(Host),负责调度和数据传输
├── vector_add.cu ← CUDA 代码:GPU 端(Device),具体计算逻辑
├── vector_add.cuh ← CUDA 头文件:kernel 函数声明
└── CMakeLists.txt ← 编译配置(用 CMake 代替直接调用 nvcc)为什么分成 .cpp 和 .cu? CUDA 代码混合了 CPU 代码和 GPU 代码,但:
.cpp文件:纯 CPU 代码,用g++编译.cu文件:GPU 代码,用nvcc编译nvcc能同时处理两者,但你也可以分开编译后链接
编译方式一:直接用 nvcc
# 最简单的编译方式(一行命令搞定)
nvcc vector_add.cu main.cpp -o vector_add
# 指定 GPU 架构(可选,加了编译更精确)
nvcc vector_add.cu main.cpp -o vector_add \
--generate-code arch=compute_80,code=sm_80
# 参数解释:
# --generate-code arch=compute_80,code=sm_80
# compute_80 = 虚拟架构(PTX 汇编级别)
# sm_80 = 真实架构(A100 的 compute capability 是 8.0)
# 简单理解为:告诉 nvcc 你的 GPU 是什么型号编译方式二:用 CMake(推荐)
实际项目中都用 CMake 管理大型项目:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(my_cuda_project LANGUAGES CXX CUDA)
# 查找 CUDA
find_package(CUDAToolkit REQUIRED)
# 你的源文件
add_executable(vector_add main.cpp vector_add.cu)
# 链接 CUDA 库
target_link_libraries(vector_add PRIVATE CUDA::cudart)mkdir build
cd build
cmake ..
cmake --build . --config Release
./vector_add常用 nvcc 编译选项速查
nvcc main.cu -o app [选项]
常用选项:
-o <file> 输出文件名
-arch=sm_XX 指定 GPU 架构(如 sm_80 = A100)
-O0 / -O1 / -O2 / -O3 优化级别(默认 -O3)
-g 生成调试信息(gdb 可调)
-lineinfo 生成行号信息(cuda-gdb 可调)
--ptx 只输出 PTX 汇编,不生成最终二进制
--run 编译后直接运行(仅限简单程序)
-Xcompiler -Wall 把 g++ 的警告也显示出来
常见架构对照:
sm_70 = V100
sm_80 = A100
sm_86 = RTX 3090 / A5000
sm_89 = Orin
sm_90 = H100 / H200常见错误与排查
# 错误1:nvcc: command not found
# 原因:CUDA 没有加入 PATH
# 解决:
# Windows: 系统环境变量 → PATH → 添加
# C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.x\bin
# 错误2:unsupported GPU architecture
# 原因:nvcc 版本太老,不支持你的 GPU
# 解决:升级 CUDA Toolkit,或用 -arch=sm_XX 指定更低架构
# 错误3:undefined reference to 'cudaMalloc'
# 原因:没有链接 CUDA 运行时库
# 解决:target_link_libraries(... CUDA::cudart)
# 错误4:kernel launch failed: no kernel image is available
# 原因:编译时指定的 GPU 架构和运行时的 GPU 不匹配
# 解决:重新用正确的 -arch=sm_XX 编译开发工具推荐
┌──────────────────────────────────────────────────────────────┐
│ CUDA 开发工具推荐 │
├──────────────────────────────────────────────────────────────┤
│ │
│ IDE │
│ ✅ VS Code + NVIDIA Nsight VS Code Edition(免费) │
│ ✅ Visual Studio + Nsight Visual Studio Edition(功能最强) │
│ ✅ CLion(支持 CUDA 语法高亮) │
│ │
│ 命令行调试 │
│ ✅ cuda-gdb ← GPU 专用调试器 │
│ ✅ compute-sanitizer ← 内存错误检测(越界/未初始化/竞争) │
│ │
│ 性能分析 │
│ ✅ NVIDIA Nsight Systems ← 系统级 timeline 分析 │
│ ✅ NVIDIA Nsight Compute ← GPU kernel 级别性能分析 │
│ │
│ 在线实验(无需本地 GPU) │
│ ✅ Google Colab + 切换到 T4 GPU(免费,有时间限制) │
│ ✅ Lambda Lab / Vast.ai(租用 GPU 云服务器) │
│ │
└──────────────────────────────────────────────────────────────┘C++ 到 CUDA C++:最小的认知跨越
作为 C++ 程序员,你需要理解的核心变化只有一个:
┌────────────────────────────────────────────────────────────┐
│ │
│ C++:代码在 CPU 上运行 │
│ ↓ │
│ main() → 调用函数 → 函数在 CPU 上执行 │
│ │
│ CUDA C++:代码分为两部分 │
│ │
│ Host(C++):负责调度、数据传输、控制流 │
│ Device(CUDA):负责大规模并行计算 │
│ │
│ 你写的 .cu 文件 = Host 代码 + Device 代码(混合在一起) │
│ │
│ 最简单的理解: │
│ CUDA = C++ + 一套新的关键字(__global__, __device__ 等) │
│ + 一套新的运行时 API(cudaMalloc, cudaMemcpy 等) │
│ │
└────────────────────────────────────────────────────────────┘// main.cpp(C++,Host 端)
int main() {
// 申请 GPU 显存
float* d_a;
cudaMalloc(&d_a, N * sizeof(float));
// CPU 数据拷到 GPU
cudaMemcpy(d_a, h_a, N * sizeof(float), cudaMemcpyHostToDevice);
// 调用 GPU kernel(launch 一个 grid)
add<<<blocks, threads>>>(d_a, d_b, d_c);
// GPU 结果拷回 CPU
cudaMemcpy(h_c, d_c, N * sizeof(float), cudaMemcpyDeviceToHost);
// 释放显存
cudaFree(d_a);
}// vector_add.cu(CUDA C++,Device 端)
// __global__ = 在 GPU 上运行,可从 CPU 调用
__global__
void add(float* a, float* b, float* c, int N) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < N) {
c[i] = a[i] + b[i];
}
}你不需要立刻理解上面每个细节——第 4 章会从头讲清楚。现在只需要记住:CUDA C++ 就是在 C++ 里加了 GPU 并行计算的能力。
硬件技术分层图谱
┌─────────────────────────────────────────────────────────────┐
│ 硬件编程技术分层 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 分布式训练层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 数据并行 │ │ 模型并行 │ │ 流水线并行 │ │
│ │ (DDP/DeepS)│ │ (Megatron)│ │ (PipeDream)│ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 通信层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ NCCL │ │ NVLink │ │ InfiniBand │ │
│ │ (GPU间通信)│ │ (卡间直连) │ │ (节点间) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 编程框架层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ CUDA C++ │ │ OpenMP │ │ MPI │ │
│ │ (.cu) │ │ (#pragma) │ │ (集群) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ GPU 硬件层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 流式多处理器│ │ 全局/共享内存│ │ TensorCore │ │
│ │ (SM) │ │ (Global/ │ │ (矩阵加速) │ │
│ │ │ │ Shared) │ │ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ CPU 硬件层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 多级缓存 │ │ SIMD 单元 │ │ NUMA 拓扑 │ │
│ │ L1/L2/L3 │ │ AVX-512 │ │ (多路) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘AI 辅助 vs 必须深入原理
┌─────────────────────────────────────────────────────────────┐
│ AI 可查 vs 必须理解清单 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 可以高度依赖 AI(偏记忆/查表型): │
│ ✅ CUDA API 具体语法——AI 随时告诉你用什么函数 │
│ ✅ 特定 GPU 型号的参数——A100/H100 的 SM 数量/显存大小 │
│ ✅ CUDA .cu 文件的 kernel 模板——矩阵乘法代码框架 │
│ ✅ nvcc 编译选项——需要时查文档即可 │
│ ✅ NPU 厂商的 SDK 命令速查——昇腾 CANN API 列表 │
│ │
│ 必须深入理解原理的(决定你能否解决实际问题的): │
│ 🔴 阿姆达尔定律——知道并行优化的理论上限在哪里 │
│ 🔴 GPU 内存层次——为什么合并访问能提升 10 倍性能 │
│ 🔴 SIMT 执行模型——Warp 分支分化如何影响性能 │
│ 🔴 CUDA Stream 异步执行——如何让计算和通信重叠 │
│ 🔴 显存 OOM 的根因——百亿参数为什么放不下单卡 │
│ 🔴 NPU vs GPU 的生态差异——昇腾 CANN 和 CUDA 的本质区别 │
│ │
│ 原则:能查到的不用背,理解不了的面试必挂。 │
│ │
└─────────────────────────────────────────────────────────────┘学习路径与面试频率
┌─────────────────────────────────────────────────────────────┐
│ 面试频率 × 理解深度 矩阵 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 高面试频率 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CUDA 线程层次(Thread/Block/Grid) ⭐⭐⭐ 高理解 │ │
│ │ GPU vs CPU 架构差异 ⭐⭐⭐ 高理解 │ │
│ │ CUDA 内存模型(Global/Shared/Register)⭐⭐⭐ 高理解 │ │
│ │ 阿姆达尔定律 + 加速比 ⭐⭐⭐ 高理解 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 中面试频率 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SIMD 向量化(AVX-512/SVE) ⭐⭐ 中理解 │ │
│ │ OpenMP 并行编程 ⭐⭐ 中理解 │ │
│ │ MPI/NCCL 通信原语 ⭐⭐ 中理解 │ │
│ │ 混合精度训练(FP16/BF16) ⭐⭐ 中理解 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 低面试频率(但要会用) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ NPU 生态(昇腾/寒武纪/TPU) ⭐ 理解概念 │ │
│ │ 性能分析工具(nsys/ncu) ⭐ 会用即可 │ │
│ │ DPU/存算一体 ⭐ 理解概念 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘各章节详情
| 章节 | 主题 | 核心场景 |
|---|---|---|
| [[01-computer-architecture-basics]] | 计算机架构基础 | 为什么 GPU 比 CPU 更适合训练任务 |
| [[02-parallel-computing-theory]] | 并行计算理论 | 阿姆达尔定律告诉你 30 天的优化极限 |
| [[03-gpu-architecture]] | GPU 架构深入 | GPU 上万个核心如何分工协作 |
| [[04-cuda-programming-model]] | CUDA 编程模型 | 第一个 .cu 文件的完整编写流程 |
| [[05-cuda-kernel-and-memory]] | CUDA 内存管理 | 百亿参数如何装进显存 |
| [[06-cuda-optimization]] | CUDA 性能优化 | 优化后从 30 天缩短到 10 天 |
| [[07-openmp-simd]] | CPU 并行编程 | OpenMP + SIMD 向量化 |
| [[08-mpi-cluster-hpc]] | HPC 集群通信 | 多卡多节点分布式训练 |
| [[09-heterogeneous-computing]] | 异构计算 | CPU + GPU 如何协同 |
| [[10-dl-training-optimization]] | 训练优化实战 | 混合精度 + DeepSpeed ZeRO |
| [[11-performance-analysis-tools]] | 性能分析工具 | 找到真正的瓶颈 |
| [[12-npu-landscape]] | NPU 全景 | 昇腾/寒武纪/TPU/苹果生态 |
| [[13-future-trends]] | 未来趋势 | DPU/光计算/量子 |
开始之前:为什么硬件编程这么难?
因为硬件编程不是学"一个语言",而是学一套系统——从 C++ 代码到 GPU 电路,中间经过了好几层抽象:
你写的 C++ 代码
↓(g++ 编译)
CPU 汇编 / 机器码 → CPU 执行
你写的 CUDA C++ 代码
↓(nvcc 编译)
分离 → Host 端用 g++ 编译 → CPU 控制
→ Device 端用 ptxas 编译 → GPU 并行执行每一层的抽象都在"欺骗"你:
- C++ 让你以为内存是无限的
- CUDA 让你以为 GPU 线程是免费的
- 分布式训练让你以为计算是线性加速的
学完这 13 章,你就能看穿这些"谎言",写出真正高效的代码。
学习状态:🟡 开始学习