恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
老显卡也能学CUDA:940MX编程实践与踩坑全复盘
首页
资讯中心
/
老显卡也能学CUDA:940MX编程实践与踩坑全复盘
老显卡也能学CUDA:940MX编程实践与踩坑全复盘
发布时间:2026/9/18 10:46:35
家里的旧笔记本翻出来屏幕都花了一块但我没舍得扔原因就一个上面那块 NVIDIA GeForce 940MX 还能跑 CUDA。可能有人觉得这显卡已经淘汰八百年了显存小、带宽低、算力弱能学什么但我用这块卡从零把 CUDA 编程摸了一遍顺带把 PyTorch 也跑通了。这篇文章就围绕 GeForce 940MX 的 CUDA 编程实践把我装驱动、配环境、写核函数、踩坑排错的过程全部复盘一遍。如果你手里也是一块老卡或者你正被 CUDA 环境折腾得想砸电脑这篇文章应该能帮你省几天时间。先说明一下940MX 不是一张能跑大模型的卡它的定位是入门级移动端独显。但它有几个很珍贵的特点支持 CUDA、功耗低、结构简单、兼容性好特别适合拿来理解 GPU 编程的核心逻辑。后面所有内容都是我在真实环境里验证过的包括多版本 CUDA 安装、驱动兼容性、PyTorch 跑不起来的坑、以及怎么在这块卡上写出第一个能跑的 CUDA 程序。1. 940MX 这块“过气卡”架构、算力和到底能干嘛1.1 Maxwell 架构和 CUDA 计算能力 5.0要搞懂 940MX 能干什么先看硬件底子。940MX 核心是 GM108属于 NVIDIA Maxwell 架构CUDA 计算能力是 5.0也就是常说的 sm_50。它有 384 个 CUDA 核心基础频率 1122 MHzBoost 频率差不多 1242 MHz显存有 2GB 和 4GB 两种版本位宽只有 64 位带宽在 40GB/s 左右。这些数字意味着什么举个例子桌面级的 RTX 3060 有 3584 个 CUDA 核心940MX 大概只有它的十分之一多一点。但学习 CUDA 编程不需要和性能较劲你需要的是能运行核函数、能理解线程调度、能调试并行逻辑的一块硬件940MX 完全满足。CUDA 计算能力是一个特别重要的概念它决定了你在编译程序时该指定什么架构参数。计算能力 5.0 对应的编译目标就是 sm_50写代码时用-archsm_50告诉编译器你的目标是 Maxwell 架构。如果你拿支持新特性的编译参数去编译老卡要么编译报错要么运行时直接崩掉。有个很容易踩的坑很多教程默认用-archsm_86或-archsm_90编译那是给 Ampere、Hopper 架构用的。放在 940MX 上跑起来就会出现no kernel image is available for execution on the device这种报错后面我会专门讲这个问题。1.2 它能做什么、做不了什么用 940MX 学 CUDA我认为它最大的价值是逼你把底层原理搞清楚。没有那些自动化优化工具你必须手动设计线程块、手动管理显存、手动做数据拷贝这些恰恰是 CUDA 编程的核心内容。它能做的事情不少CUDA C/C 入门和并行算法实验像数组求和、矩阵乘法、图像卷积这些经典案例完全没问题。小规模的深度学习推理和训练比如 LeNet 跑 MNIST 手写数字识别batch size 调小一点完全可以跑。学习显存管理、流、事件、共享内存这些进阶概念硬件不支持不代表你不能从代码里理解逻辑。在 WSL 或者 Windows 原生环境里验证 CUDA 跑通的基础流程。它做不了的事情也很明确大 Batch 训练、大模型推理、使用张量核心做混合精度加速、跑现在动辄几十 GB 的深度学习模型这些都得靠边站。所以如果你手里已经有更好的显卡940MX 的价值就更多是“学习用”而不是“生产力工具”。现实一点说在 940MX 上调一个简单的卷积核观察它的运行时间和带宽占用这种经验放到任何一张新卡上都一样适用。硬件会过时并行计算和内存模型这些底层思维不会过时。2. 环境搭建驱动、Toolkit 和多版本共存的正确姿势2.1 第一步永远是驱动不管你要装什么 CUDA 工具包第一步永远是先确认驱动能用。在命令行里直接敲nvidia-smi能看到显卡型号、驱动版本、显存占用这些信息就说明驱动已经装好了。注意一个容易混淆的点nvidia-smi里显示的 “CUDA Version” 表示当前驱动支持的最高 CUDA 版本不是你已经安装的 CUDA 工具包版本。驱动是“底座”CUDA Toolkit 是“上层建筑”两者是分开的。驱动版本比较新你可以往后兼容装旧版 Toolkit如果驱动版本太旧新版本 Toolkit 可能直接拒绝对接。940MX 的实际驱动支持情况还算好因为 N 卡驱动一直没完全放弃 Maxwell 架构。我个人的建议是装最新稳定版驱动不要追测试版。笔记本用户尤其注意如果电脑是双显卡Intel 核显 N 卡独显需要去 NVIDIA 控制面板的“管理 3D 设置”里把首选图形处理器改成“高性能 NVIDIA 处理器”。这一步不做有些程序会傻乎乎地跑到核显上导致 CUDA 初始化失败。2.2 CUDA Toolkit 安装与版本选择CUDA Toolkit 是写 CUDA C 程序时必须要装的它包含nvcc编译器、CUDA 运行时库、调试工具和开发库。在 940MX 这块 Maxwell 老卡上我对 Toolkit 版本的建议是如果你只是学 CUDA 编程装 11.8 或者 12.1 都行这两个版本目前非常稳定社区资料也多。如果是为了跑 PyTorch那复杂一点得看 PyTorch 这个框架支持什么架构后面专门说。Windows 安装没什么特别的地方直接去官网下载对应系统的安装包装的时候选自定义路径。比如我装在C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8装完以后必须配置环境变量。在系统变量的Path里加上C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin如果忘记配命令行里敲nvcc -V会提示找不到命令这是最常见的初学问题之一。WSL2 里的情况更清爽一些因为 WSL2 不需要单独装显卡驱动它直接复用 Windows 主机的驱动。你只需要在 WSL2 的 Linux 环境里装 CUDA Toolkitwget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt update sudo apt install cuda-toolkit-12-1装上之后同样要加环境变量。Linux 下习惯把 CUDA 目录加到/etc/profile.d/cuda.sh内容大概是export PATH/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda/lib64:$LD_LIBRARY_PATH我个人的实操感受是WSL2 下写 CUDA 比 Windows 原生命令行舒服很多但如果你是刚入门Windows 原生也完全够。关键不是用哪个系统而是把“驱动——工具包——编译器——运行时”这条链路跑通。2.3 多版本 CUDA 共存的两个思路我在折腾的过程中发现实际项目里经常需要好几个 CUDA 版本共存。有的老代码必须用 CUDA 10.2有的框架要求 CUDA 11.8还有的默认环境是 CUDA 12.1。全装在一起也没问题关键是别让它们在环境变量里打架。Windows 下的思路是“不覆盖安装”。每装一个新版本安装路径都改成独立的目录比如CUDA\v11.8、CUDA\v12.1。改动环境变量前先把之前的版本信息记下来需要切换哪个版本就把对应版本的bin目录放到Path最前面。为了省事我写了一个简单的批处理脚本切换版本时一键修改环境变量echo off set CUDA_HOMEC:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 set PATH%CUDA_HOME%\bin;%PATH% nvcc -VLinux 下的思路是“符号链接”。CUDA 安装程序默认会把目录放到/usr/local/cuda-11.8这种带版本号的路径下然后通过一个软链接/usr/local/cuda指向当前要用的版本sudo rm /usr/local/cuda sudo ln -s /usr/local/cuda-11.8 /usr/local/cuda这样做的好处是所有编译脚本里只需要写固定路径/usr/local/cuda切换版本就等于切换软链接不用到处修改脚本。更规范的做法是使用update-alternatives但单纯学 CUDA 的话软链接已经够用。经验之谈装完新版本一定要在命令行里重新开一个终端再敲nvcc -V因为环境变量在不同终端会话里不一定立即生效。很多人装完以后“明明配了还是找不到”其实十有八九是没开新终端。3. 第一个 CUDA 程序从核函数到编译运行3.1 最小核函数让 GPU 出声环境搭好以后第一件事不是去跑深度学习而是写一个最简单的 CUDA 程序验证整条链路是通的。我在 940MX 上写的第一个程序就是这样#include cstdio __global__ void hello_from_gpu() { printf(Hello from thread %d, block %d\n, threadIdx.x, blockIdx.x); } int main() { hello_from_gpu2, 5(); cudaDeviceSynchronize(); return 0; }编译命令nvcc -archsm_50 -o hello hello.cu ./hello运行后会看到 10 条输出因为2, 5的意思是启动 2 个线程块每个线程块里有 5 个线程一共 10 个线程。这个写法看起来很抽象但它透露了 CUDA 编程的核心思想你先定义一个核函数这个函数会在 GPU 上执行然后通过尖括号语法告诉运行时你要启动多少个线程。CPU 端只负责调度的逻辑真正的并行执行发生在 GPU 上。cudaDeviceSynchronize()也很重要它让 CPU 等 GPU 把活干完再继续。如果不加这一句程序可能直接退出了GPU 上的 printf 结果都来不及打印出来——那又是一种新式“看不懂”的报错。3.2 向量加法从内存拷贝到线程索引学会 Hello World 之后下一个必写程序是向量加法。这个程序能把 CUDA 的“显存管理 线程索引”全部串起来#include cstdio #include cstdlib __global__ void vecAdd(const float* a, const float* b, float* c, int n) { int i blockIdx.x * blockDim.x threadIdx.x; if (i n) { c[i] a[i] b[i]; } } int main() { int n 1 20; size_t bytes n * sizeof(float); float *h_a (float*)malloc(bytes); float *h_b (float*)malloc(bytes); float *h_c (float*)malloc(bytes); for (int i 0; i n; i) { h_a[i] 1.0f; h_b[i] 2.0f; } float *d_a, *d_b, *d_c; cudaMalloc(d_a, bytes); cudaMalloc(d_b, bytes); cudaMalloc(d_c, bytes); cudaMemcpy(d_a, h_a, bytes, cudaMemcpyHostToDevice); cudaMemcpy(d_b, h_b, bytes, cudaMemcpyHostToDevice); int threads 256; int blocks (n threads - 1) / threads; vecAddblocks, threads(d_a, d_b, d_c, n); cudaMemcpy(h_c, d_c, bytes, cudaMemcpyDeviceToHost); for (int i 0; i 10; i) { printf(c[%d] %f\n, i, h_c[i]); } cudaFree(d_a); cudaFree(d_b); cudaFree(d_c); free(h_a); free(h_b); free(h_c); return 0; }这里的核心踩坑点在于GPU 不能直接用 CPU 上的普通数组必须先用cudaMalloc在显存里分配空间再把数据用cudaMemcpy拷过去。运算结束后结果还得从显存拷回内存。数据拷贝的带宽就那么大40GB/s 的 940MX 很容易成为瓶颈这也是很多并行程序眼看着“计算很快”实际总时间没下来多少的原因。线程索引那行是最值得多花时间吃透的地方int i blockIdx.x * blockDim.x threadIdx.x;意思就是当前线程在整个线程网格里的全局编号等于“所在块编号乘以块大小再加上块内线程编号”。比如第 3 个块、第 5 个线程块大小 256那全局编号就是 3 * 256 5。这个编号就是你要处理的数据下标理解了这条公式基本就理解了一维并行。3.3 用事件计时看看 940MX 的真实表现写 CUDA 程序不测性能等于白写。我用 CUDA 事件接口给向量加法加了个计时cudaEvent_t start, stop; cudaEventCreate(start); cudaEventCreate(stop); cudaEventRecord(start); vecAddblocks, threads(d_a, d_b, d_c, n); cudaEventRecord(stop); cudaEventSynchronize(stop); float ms 0; cudaEventElapsedTime(ms, start, stop); printf(kernel time: %f ms\n, ms);在 940MX 上跑一百万 float 的向量加法核函数本身大概在 0.2ms 到 0.5ms 之间主要看显存频率和是否触发 Boost 频率。但把两次cudaMemcpy的时间算进去总耗时能到 5ms 以上。这就是为什么我反复强调“显存拷贝是老大难”的原因。学 CUDA 的人如果只盯着核函数时间看很容易对实际性能产生误判。这里我也顺便摸了一下这块卡的理论上限。384 个 CUDA 核心在 1242 MHz 下FP32 理论算力大概是每秒 950 亿次浮点运算也就是不到 1 TFLOPS。和现在动辄几十 TFLOPS 的新卡比确实不够看但写一个小规模矩阵乘法或者卷积完全能感受到并行计算带来的加速比。3.4 编译参数和常见编不过的问题在 940MX 上编译 CUDA 程序我建议固定使用这条命令nvcc -archsm_50 -o program program.cu-archsm_50是给旧卡量身定制的。如果不加这个参数编译器会用自己的默认架构新版本 CUDA 可能默认生成新架构的机器码在 940MX 上运行就会报no kernel image is available。加了sm_50之后生成的 cubin 就是 Maxwell 能识别的格式。还有一个常见问题是编译时报错说找不到某些头文件。这种大概率是环境变量没配好nvcc找不到 CUDA 的头文件目录。Windows 下检查INCLUDE环境变量里有没有...\CUDA\v11.8\includeLinux 下检查/usr/local/cuda/include是否被默认路径覆盖。另外新版 CUDA Toolkit 里已经默认不装 CUDA Samples 了。以前装完 Toolkit 就能在开始菜单里看到一堆示例工程现在CUDA Samples 找不到非常正常你需要自己从 GitHub 拉git clone https://github.com/NVIDIA/cuda-samples.git cd cuda-samples make编译的时候如果报错说cuda_runtime.h not found多半是 Makefile 没找到 CUDA 路径把HOST_COMPILER和CUDA_PATH环境变量指对就行。4. 深度学习实践让 PyTorch 在这张卡上跑起来4.1 那个著名的 no kernel image 报错现在很多人学 CUDA 其实是绕道走的本质是想跑 PyTorch。但老卡跑 PyTorch 有个标志性的大坑就是下面这串报错torch.cuda.OutOfMemoryError: CUDA out of memory.还有个更折磨人的RuntimeError: CUDA error: no kernel image is available for execution on the device第二行报错翻译成人话就是PyTorch 里自带的 GPU 内核没有针对你的显卡架构编译或者编译时用的架构和你的显卡对不上。我一度以为是驱动坏了把驱动卸了重装折腾一天也没用最后才明白是 PyTorch 的 wheel 包和显卡架构不匹配。诊断方法很简单在 Python 里打印两行import torch print(torch.cuda.is_available()) print(torch.cuda.get_arch_list())torch.cuda.get_arch_list()会输出当前 PyTorch 支持的架构列表。如果里面没有sm_50那就说明这版 PyTorch 压根没给 Maxwell 老卡留活路。这时候要么降级到包含sm_50的旧版 PyTorch要么自己从源码重新编译编译时加入sm_50的目标架构。4.2 怎么挑一个能跑的老版本以我实测的结果来看940MX 上最稳妥的 PyTorch 组合是 PyTorch 1.13.1 搭配 CUDA 11.7。这个版本支持 Python 3.10而且官方 wheel 里带了sm_50的编译结果直接装上就能用不需要自己编译。安装命令可以参考conda create -n cuda_env python3.10 conda activate cuda_env pip install torch1.13.1cu117 torchvision0.14.1cu117 --extra-index-url https://download.pytorch.org/whl/cu117装好以后先跑一个最简单的验证import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0)) x torch.randn(1024, 1024, devicecuda) y torch.mm(x, x) print(y.sum().item())如果能正常输出说明 PyTorch 已经成功调用 940MX 了。也有一个现实问题是现在的很多新库都在快速升级如果你非要装最新版 PyTorch很可能只能面对no kernel image。我的建议是在 940MX 上就把环境锁死别随便升级。这块卡再好学也不是让你做生产环境的稳定第一性能并不重要。4.3 跑一个小模型MNIST 手写数字识别为了验证整条链路确实能用于深度学习我在 940MX 上跑了一个非常小的 LeNet 模型数据集就用 MNIST。模型结构简单两个卷积层加两个全连接层总参数量不到 200K。训练参数我是这样设置的batch size 64epoch 3优化器用 Adam学习率 0.001。940MX 没有太多显存所以输入的图片归一化之后直接是 1x28x28 的张量不做额外的数据增强。整个训练过程大约十分钟跑完准确率能到 98% 左右。这个结果放在今天的大模型时代不值一提但对于验证“CUDA 环境 PyTorch 黑乎乎的老显卡”这条链路来说意义不一样。这里有个非常重要的认知模型训练时的大部分时间都花在数据搬运和矩阵运算上940MX 性能弱不代表代码逻辑有错反而是观察 GPU 利用率和显存峰值的好机会。用nvidia-smi实时查看显存占用你能看到显存在跳、利用率在动那种“代码真的在 GPU 上跑”的感觉比任何宣传都直观。我的建议是初学者在 940MX 上跑一个 MNIST比在云服务器上跑一个大模型收获大得多因为你能感受到限制也会主动去思考如何减小 batch、如何减少中间张量、如何降低数据传输开销。4.4 给 940MX 用户的训练调参建议在 940MX 上跑深度学习下面几个参数会直接影响成败batch size 一定要小32 或者 64 就够再大可能直接 OOM。图片数据不要一次性加载到 GPU用 DataLoader 做小批量读取。别开混合精度训练Maxwell 架构的老卡对 FP16 支持很差开了反而可能出问题。尽量用 CPU 端先把数据预处理完再转成 GPU 张量避免在 GPU 上做大量转换。如果有条件把 PyTorch 的num_workers调成 2 以上让数据加载在后台并行。这些建议放到新卡上依然是有效的思路只是新卡容错空间大老卡容错空间小所以你就更容易暴露问题。5. 常见问题与排查技巧实录5.1 错误速查表我把这次实践中遇到的高频问题整理成了表格方便你直接对照现象常见原因解决方法nvcc -V提示找不到命令环境变量没配置或终端没重启检查 CUDA bin 路径是否加入 Path开新终端再试nvidia-smi报驱动通信失败驱动与系统不兼容或显卡掉驱动重启然后用 DDU 卸载后重装最新驱动torch.cuda.is_available()为 FalsePyTorch 装成 CPU 版或显卡没被识别重新安装带 GPU 支持的 PyTorch检查 NVIDIA 控制面板no kernel image is availablePyTorch 二进制不含 sm_50 架构安装 1.13.1 cu117或源码编译cublas_status_execution_failed显存不足或 cuDNN/cuBLAS 版本冲突减小 batch检查 torch.backends.cudnn 是否关闭检测到 CUDA 版本不匹配多个 Toolkit 的环境变量互相污染清理 Path保留一个 CUDA 版本CUDA Samples 找不到新版 Toolkit 已不含 Samples从 GitHub 手动下载 cuda-samples 编译编译时缺少cuda_runtime.h编译器找不到头文件路径设置CUDA_PATH环境变量检查 include 目录5.2 我在 940MX 上踩过的几个隐蔽坑第一个坑是笔记本功耗管理。940MX 在部分笔记本上默认会进入节能模式GPU 频率被锁得很低跑出的性能数据特别难看。你想把问题定位到“显卡太老”之前先看看电源管理模式是不是“最佳性能”。有些笔记本还要在 BIOS 里设置独显直连或者可切换显卡模式Windows 才会稳定调用独显。第二个坑是 CUDA Toolkit 版本和驱动的微妙关系。新驱动支持新 Toolkit但如果你在旧驱动上强装 CUDA 12.1运行时会出现一堆莫名其妙的错误。降低 Toolkit 版本或者升级驱动到 525 以上问题就消失了。我的做法是先在官网查清楚驱动对应的最低 Toolkit 版本再决定装什么。第三个坑是 PyTorch 的缓存和旧版残留。有时候你明明装了 1.13.1torch.version.cuda却显示 12.1 或者更奇怪的值。原因是环境里同时存在多个 PyTorchPython 的 import 顺序把旧的装到了前面。用pip list | findstr torchWindows或者pip list | grep torchLinux检查干净环境建一个 conda 环境再装能减少很多这种问题。第四个坑是 WSL2 里提示的 CUDA 版本和 Windows 的驱动版本对不上。WSL2 里你不需要装驱动但 Linux 侧的 CUDA Toolkit 版本必须能兼容 Windows 侧驱动的最高支持版本。如果 Windows 侧驱动是 535那装 CUDA 11.8 或 12.1 都没问题但千万别盲目装最新 Toolkit先看nvidia-smi显示的 “CUDA Version”。5.3 老卡也有自己的使用习惯用 940MX 折腾了这么久说点掏心窝的经验。老卡虽然性能弱但它的调试路径更清晰很多时候新卡一套就过的程序在老卡上必须一步步检查架构、版本、显存大小、驱动兼容性。这虽然烦人但恰恰是学 CUDA 编程最好的训练。你会被迫理解每个依赖项是什么为什么需要什么时候会发生冲突。另外我强烈建议养成“最小复现”的习惯。比如我遇到no kernel image时没有直接去重装全套环境而是先写一个最简单的 CUDA 程序跑一遍确定底层硬件和驱动没问题再一层层分析 PyTorch 的问题。隔离问题往往比解决问题更重要。最后还是想劝一句940MX 真的别去追求跑新模型。它最好的归宿是作为一块“学习卡”让你把 CPU 和 GPU 的分工、显存和内存的区别、线程和块的关系彻底搞明白。等你有机会换到新卡再回头你会感谢这段用老卡抠性能、抠内存、抠兼容性的日子。那些经验比卡本身的规格值钱得多。