当前位置: 首页 > news >正文

【infra之路】阶段二 · 模块二:CUDA 编程入门(上)— 基本功与向量加法

AI Infra 学习路线 · 阶段二 · 模块二(上半部分)
目标:把模块一的理论(SIMT/线程/显存)落到代码 —— 亲手写、编译、运行第一个 CUDA 程序
环境:WSL2 + CUDA Toolkit 12.8 (nvcc) + RTX 5060 Ti,PyTorch cu130(写独立 .cu 不受版本差异影响)


1. 核心心智模型:写"一个线程的活",GPU 复制几千份同时跑

  • 普通编程:一个线程从头到尾循环处理 1000 个数据。
  • CUDA:你写的 kernel 描述单个线程要干的那一小份活,然后告诉 GPU"启动 N 个线程",GPU 把这份活复制 N 份,几千核心同时执行(SIMT,一声令下齐步走)。

关键问题:几千线程跑同一段代码,每个怎么知道自己处理哪块数据?→ 靠唯一编号


2. 线程的组织:Thread → Block → Grid

层级说明对应硬件
Thread线程最小单位,执行 kernel 里那份活工人
Block线程块一组线程打包;整个分配到一个 SM上;块内线程可共享该 SM 的共享显存派给一个车间的工作包
Grid网格所有 block 合起来 = 本次 kernel 启动的全部线程整个工厂这次的订单

启动 kernel 要指定"多少 block、每 block 多少线程",合起来 = 总线程数。


3. 全局索引公式 ★(把"齐步走的线程"和"各处理不同数据"连起来的桥梁)

全局索引 idx = blockIdx.x * blockDim.x + threadIdx.x
  • threadIdx.x:我在自己 block 内是第几个线程
  • blockIdx.x:我属于第几个 block
  • blockDim.x:每个 block 有多少线程

比喻:3 号车间(blockIdx=3)的 5 号工人(threadIdx=5),每车间 10 人(blockDim=10)→ 全厂工号 3×10+5=35 → 处理第 35 个数据。


4. 边界检查 ★★(CUDA 标配,新手必踩的坑)

线程总数(block数 × 每block线程数)几乎不可能正好等于数据量,通常向上取整到稍多一点 → 总有多余线程

关键认知:多余线程默认不会自觉空闲,它们照样执行 kernel(SIMT 齐步走),照样算出自己的 idx(如 1000~1023)去访问数组 →越界访问(数组合法下标只到 999)。

必须用代码显式挡住:

intidx=blockIdx.x*blockDim.x+threadIdx.x;if(idx<n){// 边界检查:只有合法编号才干活c[idx]=a[idx]+b[idx];}

编号 ≥ n 的线程判断为假 → 跳过 → 不越界。


5. 第一个 CUDA 程序:向量加法(c[i]=a[i]+b[i])

CUDA 的 “Hello World”,体现"把循环拆成并行"。

#include<stdio.h>// __global__ : 在 GPU 执行、由 CPU 调用的 kernel__global__voidvectorAdd(float*a,float*b,float*c,intn){intidx=blockIdx.x*blockDim.x+threadIdx.x;if(idx<n){c[idx]=a[idx]+b[idx];// 每个线程只算第 idx 个元素}}intmain(){intn=1000;size_t size=n*sizeof(float);// 1. CPU(host)准备数据,h_ 前缀 = hostfloat*h_a=(float*)malloc(size);float*h_b=(float*)malloc(size);float*h_c=(float*)malloc(size);for(inti=0;i<n;i++){h_a[i]=i;h_b[i]=i*2;}// 2. GPU(device)分配显存(全局显存),d_ 前缀 = devicefloat*d_a,*d_b,*d_c;cudaMalloc(&d_a,size);cudaMalloc(&d_b,size);cudaMalloc(&d_c,size);// 3. 数据 CPU -> GPUcudaMemcpy(d_a,h_a,size,cudaMemcpyHostToDevice);cudaMemcpy(d_b,h_b,size,cudaMemcpyHostToDevice);// 4. 启动 kernelintthreadsPerBlock=256;intblocks=(n+threadsPerBlock-1)/threadsPerBlock;// 向上取整vectorAdd<<<blocks,threadsPerBlock>>>(d_a,d_b,d_c,n);// <<<grid, block>>> CUDA 专有语法// 5. 结果 GPU -> CPUcudaMemcpy(h_c,d_c,size,cudaMemcpyDeviceToHost);// 6. 验证for(inti=0;i<5;i++)printf("c[%d]=%.1f\n",i,h_c[i]);// 7. 释放cudaFree(d_a);cudaFree(d_b);cudaFree(d_c);free(h_a);free(h_b);free(h_c);return0;}

编译运行:

nvcc vector_add.cu-ovector_add ./vector_add

结果:c[i]=3i 全部正确(c[999]=2997)。1000 个加法被一批线程同时算完。

CUDA 程序标准骨架(记牢这条主线)

CPU 准备数据 → cudaMalloc 分配显存 → cudaMemcpy 拷上 GPU(H2D) → kernel<<<grid,block>>> 并行计算 → cudaMemcpy 拷回 CPU(D2H) → cudaFree 释放

"拷上去、算、拷回来"正是显存层级的体现:数据必须先进全局显存,GPU 才能算。


6. 越界实验(亲眼见识"越界不一定报错")

把边界检查删掉、甚至故意启动 25600 个线程(远超 n=1000),结果:
“kernel 执行无报错”,结果还全对。

为什么 GPU 这么宽容:

  • cudaMalloc 实际可能划一大块对齐内存,越界地址很多落在"多给的余量"或其他不严格保护的区域。
  • 硬件不在每次访问做边界校验(那样太慢,违背 GPU 追求吞吐的设计)。

深刻教训:越界访问常常不当场报错,而是埋雷。测试里跑得好好的,换数据规模/换卡后踩到关键内存就崩在最意想不到处,极难复现定位。
边界检查不是可选保险,是必需纪律。不能指望 GPU 兜底,它默认不管。


7. 手动错误检查 ★(比工具更基础的必备习惯)

CUDA kernel 是异步启动的(CPU 发完命令就往下走,不等 GPU),kernel 内部错误不会自动冒泡,必须主动问:

cudaDeviceSynchronize();// 等 GPU 跑完 kernelcudaError_t err=cudaGetLastError();// 取最近一次 CUDA 错误if(err!=cudaSuccess)printf("CUDA 错误: %s\n",cudaGetErrorString(err));

正经 CUDA 代码里到处是这种检查。WSL 用不了 sanitizer(见下),这套手动检查永远可用,更该掌握。


8. WSL 环境限制(记一笔)

compute-sanitizer(CUDA 自带的内存错误检测利器,老版叫 cuda-memcheck)在 WSL 下用不了:
报错Failed to initialize WDDM debugger interface/Device not supported
原因:它需要直接访问 GPU 调试接口,WSL 隔着 Windows 驱动(WDDM)这层拿不到。

结论:WSL 能跑 CUDA,但部分底层调试/分析工具受限。深度使用这类工具需原生 Linux。
→ 反而更要养成"手写边界检查 + cudaGetLastError"的习惯,把防线建在代码里而非工具上。


已掌握(模块二上半)

  • 单线程心智模型(写一份活,GPU 复制 N 份)
  • Thread/Block/Grid 三层组织
  • 全局索引公式 idx = blockIdx.x*blockDim.x + threadIdx.x
  • 边界检查 if(idx<n) 及其必要性(亲手验证越界不报错)
  • CUDA 标准骨架:分配/拷贝H2D/计算/拷回D2H/释放
  • 手动错误检查 cudaGetLastError + cudaGetErrorString
  • 知道 WSL 下 sanitizer 不可用
http://www.jsqmd.com/news/928267/

相关文章:

  • 哈工大神经网络与深度学习第三次总结
  • 2iterable iterator 可迭代对象与迭代器
  • 如何让 AI 读懂你的奇葩需求?针对 Gemini 3.5 优化的 Prompt 进阶指南
  • 鸿蒙原生开发生态全景:从 ArkTS 到纯血鸿蒙
  • mydumper 编译安装与 RPM 部署:从源码到实战的避坑指南
  • 中国建设银行广东茂名分行:警惕AI诈骗的陷阱
  • 跨国链路的物理限制:马蒂斯公式(Mathis‘s Formula)
  • 人形检测数据集, 目标检测/行人检测/安防AI模型训练 密集场景人形检测数据集 / 行人检测数据集训练及应用
  • Protobuf协议解析与微信数据结构设计
  • 开发日志六
  • 对波普尔可证伪主义引发全域系统性灾难的全面批判
  • 百度SEO优化实战指南:2026年百度SEO优化核心技巧全面解析
  • STM32 SAI 通讯原理与 TDM 应用
  • 第四章:暗礁
  • 【个人记账理财助手】手动新增账单功能
  • 2026年最新三亚市金银首饰回收+金条金币+铂金K金 高价回收;实体老店回收黄金 多年口碑 交易放心;TOP5实力权威排行榜推荐+联系方式 - 亦辰小黄鸭
  • 2026最新指南|Codex 接入 MiniMax 模型全攻略:利用 CC Switch 本地路由零基础配置
  • 从一次线上GC故障排查说起:我为什么最终把生产环境从OracleJDK 11换成了Amazon Corretto 11
  • 医疗营销实战:生成式AI在聊天机器人、内容创作与社交媒体中的应用
  • 第1篇 | 政治思维生存逻辑解析
  • 二分查找模板(binary_search)
  • Web应用技术第一次和第二次作业
  • 无人机红外数据集 深度学习框架 无人机高空红外检测系统pyqt5界面 无人机高空红外数据集 无人机高空红外行人车辆检测数据集
  • 【多Agent 协作深度解析】Claude 官方 5 种协调模式的原理、选择与工程实践
  • 微服务架构(MSA)是如何诞生的?
  • 聊天机器人的搭建(一)
  • AI销售助理:1700万美元融资背后的技术架构与落地实践
  • AI内容运营成为大学生就业热门方向,越来越多年轻人开始学习AI营销
  • 单向循环链表超详细精讲 | 带头节点带头指针 + 完整可运行c语言代码
  • 车载AI Agent Harness:行车安全与交互管控