上个月有个师弟在群里问我:“实验室分到的那台机器只有CPU,论文方向又被定成了深度学习,怎么办?”这问题我太熟了。我读研那会儿,整个实验室就一块老旧的GPU,还是隔壁组导师借来用的,排队排得比食堂打饭还难受。后来没办法,我硬是靠着纯CPU环境把毕业论文的实验做完了,虽然速度确实感人,但该学的东西一样没落。写了这篇内容,就是想给同样被硬件卡的同学们一点实际参考:无GPU环境下深度学习到底能不能做、怎么做、哪些坑千万别踩。文章会先帮你判断哪些任务在CPU上能跑、哪些必须想别的办法,再给出一套完整的CPU版PyTorch配置流程,分享几个提升训练效率的实测技巧,最后整理一些我踩过的坑和排查思路。不管你是准备课程作业、应付毕业设计,还是真的想入门深度学习,这篇都能用得上。
1. 没有GPU,到底还能不能做深度学习
1.1 先搞清楚真实需求:你是要“学会”还是要“训出”
每次看到“实验室没有GPU怎么办”这个问题,我的第一反应不是直接回答能或不能,而是反过来问一句:你到底想干什么?
想清楚这个问题特别重要,因为“深度学习”这四个字在不同人嘴里指的是完全不同的工作量。如果你是跟着李沐的《动手学深度学习》跑代码,或者在看那本被很多人叫“鱼书”的《深度学习入门:基于Python的理论与实现》,又或者在做吴恩达课程的课后习题,那么我可以负责任地说:纯CPU完全够用。这些课程里的MNIST手写数字识别、CIFAR-10图像分类、简单的情感分类,模型规模都很小,在普通CPU上训练一个epoch也就是几十秒到几分钟的事。我当年就是拿一台连独立显卡都没有的笔记本电脑把这些课的代码都跑通的,一点问题没有。
如果你是在跑自己的毕业设计,或者导师给的方向涉及ViT、GAN、大语言模型微调这些东西,那结论就不一样了。这些任务的计算量是课程代码的几百倍上千倍,CPU硬扛不是不行,但训练一次要几小时甚至几天,实验迭代一轮下来整个人都麻了。所以,判断的标准不是“有没有GPU”,而是“任务的计算量在不在CPU能承受的范围内”。
这里我建议你先做一个简单估算:看一眼你的模型参数量,再跑一次单batch的forward和backward,记下耗时。如果单次迭代超过5秒,而你的训练集有几万张图片,那这个任务在CPU上做到毕业基本不现实。趁早做两手准备,要么换小模型,要么去云上租GPU。
1.2 一个残酷但清醒的结论:有些任务确实不该在CPU上跑
我也不想给你灌鸡汤。深度学习里有一类任务天生不适合CPU,比如从头预训练GPT这样的大语言模型、训练高分辨率GAN、做视频理解、跑NeRF三维重建。它们的共同特点是:要么显存占用量大得离谱,要么计算密度极高。CPU的内存带宽和浮点算力就摆在那里,不是靠优化代码就能翻盘的。这就好比让一辆家用轿车去拉几十吨的重卡货,你再怎么换轮胎、调发动机,物理上限摆着。
但“不能跑重任务”不代表“没得学”。深度学习的核心原理——反向传播、过拟合、正则化、卷积的计算过程——用小模型、小数据同样能看得清清楚楚。我在CPU上跑过带L2正则化的PyTorch代码,明明就是很简单的两层卷积网络加一个weight decay参数,但把不同正则化强度的训练曲线对比画出来,那种“过拟合被压住”的感觉,比看十遍理论都直观。
而且实话实说,无GPU环境反而能逼你养成一些好习惯。因为慢,你会珍惜每一次训练机会,动手之前先想清楚模型结构、先在小规模数据上做冒烟测试,也会更早接触迁移学习、特征复用这些工程技巧。这些习惯等你真正有了GPU之后,一样是受益终身的。
2. 方案选型:四类可行路子,按需取用
2.1 场景一:CPU硬扛——适合教学、入门和小规模实验
无GPU条件下,最朴素也最可控的方案就是直接在本地CPU上跑。它不需要额外花钱,不依赖外部网络环境,代码改起来也快。适合的场景我帮你列一下:
- 经典机器学习算法:MLP、逻辑回归、决策树、SVM,这些完全用不到GPU,CPU跑得飞快;
- 小规模CNN:LeNet、简单的两层卷积网络在MNIST上,普通CPU一个epoch几十秒到两三分钟;
- 迁移学习只训练分类层:用现成的ResNet18提取特征保存下来,只训练最后那个全连接层,计算量非常小;
- 代码逻辑验证、教学演示、算法课程作业。
CPU硬扛的心理预期要放低。一个常见的参考数据是:batch size为32、输入分辨率224x224的ResNet18,在普通i5或i7处理器上,一次forward加backward大概需要几秒到十几秒。这跟A100比确实没法看,但如果是小数据集,咬咬牙也能出结果。我当年做毕业论文最核心的那几张对比表格,就是用CPU反复跑出来的,虽然时间跨度拉了一个月,但最后提交的时候数据是完整的、可复现的。
2.2 场景二:免费云GPU平台——学生党的救命稻草
如果你的任务中等规模,又实在没钱买卡,免费的云GPU平台是值得认真考虑的选项。Kaggle的Notebook每周赠送30小时的GPU额度,Google Colab也有免费额度,虽然有时候排队排到天荒地老,但胜在不要钱。国内一些面向高校的AI实训平台、竞赛平台也会提供免费算力,具体资源随时间会变,但思路是一样的:多注册几个平台,把它们的免费额度用起来。
我个人的经验是,在这些免费平台上跑中等规模的任务,比如CIFAR-10、SVHN这类图像分类,或者中小型NLP模型的微调,是完全可以接受的。它们的GPU型号不算新,但比CPU强太多了,一个epoch几十秒就能跑完,实验节奏一下子从“半天跑一次”变成“十分钟跑一次”。
这类平台有一个通病:Session会被回收。你要是把训练任务挂那不管,隔几个小时回来看,可能连接都没了。解决办法是老生常谈但必须做——定时保存checkpoint,每跑完一个指定轮数就把模型权重和优化器状态存下来。断线了重新连上去接着跑就行,不用从头再来。
2.3 场景三:按量租用GPU服务器——重任务的性价比之选
如果任务真的很重,比如连续训练十几个小时以上,那就别在免费平台耗了,直接按量租GPU。现在主流的云服务商和垂直算力平台都支持按小时计费,用多久付多久,没有硬件维护成本。我身边学生用得比较多的是AutoDL、恒源云这类面向高校的算力平台,也有直接用阿里云、腾讯云的GPU实例的,大家按自己预算和习惯选就行。
租用有几个注意点:第一,选配置的时候看清楚GPU型号和显存大小,显存决定你能跑多大的模型;第二,关注计费方式,按时计费而非包月,用完了记得关机,不然分分钟扣钱;第三,选有数据盘或网盘挂载功能的平台,上传下载数据集会方便很多。从我实际体验看,一张消费级显卡租一天的价格,比很多人想象得便宜,而且省去了一堆环境配置的功夫,对赶时间的人来说很值。
2.4 场景四:换个思路——用更轻的模型和技巧绕过算力瓶颈
无GPU环境下,选模型比调代码重要得多。同样一个图像分类任务,你用ResNet152和用MobileNetV3,体验完全是两个世界。轻量级模型不是不能打,很多场景下精度差距很小,但速度能快十几倍。
给你几个我实测过的组合:
- 图像分类:MobileNet、EfficientNet-Lite、ShuffleNet这类轻量CNN,在CIFAR-10上CPU训练完全可行;
- 自然语言处理:直接用DistilBERT等蒸馏过的预训练小模型,比BERT-base轻一半以上,很多任务上精度损失很小;
- 迁移学习:加载别人预训练好的模型权重,冻结大部分层,只训练最后几层,这是无GPU环境最划算的方案。
还有一个思路是知识蒸馏:先在大模型上算出teacher的预测分布,再用这个分布去训练一个小模型。这个操作在无GPU环境下做有点难度,因为你自己得先有个大模型来蒸馏,但如果你能从公开渠道拿到蒸馏数据,那效果是立竿见影的。
3. 环境配置实操:手把手跑通CPU版PyTorch
3.1 理清版本差异:别再把GPU版装进无GPU的机器
很多第一次配环境的人,在无GPU机器上直接执行pip install torch,结果装出来的包有几百MB甚至几个GB,运行的时候还老提示CUDA不可用。根子就在于:PyTorch默认的pip包是带CUDA依赖的GPU版本,你虽然没显卡,但它把CUDA相关的库一并装上了。在无GPU机器上这倒不会报大错,但白白占了一堆硬盘空间,而且某些场景下启动会变慢。
正确的做法是安装时要明确指定CPU版本的安装源。PyTorch官方提供了CPU版本的whl包,安装命令是:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu如果你用的是Anaconda,还可以用conda直接安装CPU版:
conda create -n dl python=3.10 conda activate dl conda install pytorch torchvision torchaudio cpuonly -c pytorch这两条命令我都实测过,装出来的PyTorch是纯CPU版本,体积小,运行稳定。conda方式适合你已经依赖conda管理环境的情况,pip方式更轻量,随便一个Python虚拟环境都能用。
3.2 完整安装流程:以Anaconda为例装CPU版PyTorch
下面这套流程我在好几台机器上跑过,照着做基本不会出问题。
第一步,装Anaconda或Miniconda。Anaconda自带Python和一堆常用库,Miniconda更精简,看个人喜好。装完之后打开终端或者Anaconda Prompt,输入conda --version检查是否装好了。
第二步,创建独立的深度学习环境。这一步强烈推荐,不要直接在base环境里干活,因为不同项目依赖的Python版本和包版本可能冲突,独立环境可以隔离脏乱差。
conda create -n lab python=3.10 -y conda activate lab第三步,安装CPU版PyTorch。这一步我建议优先用conda命令,因为conda会自动处理依赖关系:
conda install pytorch torchvision torchaudio cpuonly -c pytorch如果你在下载时遇到速度很慢的问题,可以先配置国内镜像源,比如清华源或者阿里源,然后再执行上面的安装命令,速度会快很多。conda配置镜像的方法网上有很多,我在这里就不赘述了,总之是改一个.condarc配置文件的事。
第四步,验证环境。安装完成后在Python里执行下面这行命令:
import torch print(torch.__version__) print(torch.backends.mkldnn.enabled)如果输出版本号类似2.3.0,而mkldnn那一行输出True,说明CPU版PyTorch装好了,而且底层已经开启了MKL-DNN(oneDNN)加速,这个对CPU训练提速非常重要。
3.3 代码兼容性:CPU版也能无缝切换
配好环境之后,还有一个很重要的习惯要养成,就是写代码的时候做好设备抽象。我在帮师弟改代码时经常看到他们把设备写死成cuda:
device = torch.device("cuda")这么写在无GPU机器上肯定报错。更稳妥的写法是:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")这样你的代码在无GPU机器上自动使用CPU,在云上租了GPU之后又能自动切到CUDA。模型、数据、损失函数,统一用.to(device)移到对应设备就行。我自己的经验是,所有训练脚本都按这个模板写,后面换环境几乎零成本。
另外还有一个小细节:如果要从GPU机器上把训练好的权重拿回CPU机器做推理或继续训练,加载模型时要指定map_location:
checkpoint = torch.load("model.pth", map_location="cpu")不然大概率会遇到运行设备不匹配的报错。
4. 无GPU环境下的训练提速技巧
4.1 从源头上减负:小batch、小模型、小数据集
CPU训练最怕的就是贪大。GPU上轻松跑64或128的batch size,在CPU上最好降到16或32。小batch除了减少单次运算量,还有一个额外的好处:梯度更“吵”,反而带来一定的正则化效果,有些任务上最终精度并不差。我实测过,MNIST上一个简单的CNN,batch 32和batch 128相比,训练轮数要更多一些,但每轮耗时少一大截,综合起来时间差不多,而显存(或者说内存)占用却低很多。
模型层面的减负同样重要。刚开始做实验的时候,别一上来就上ResNet,先用一个两三层的小卷积网络把代码逻辑跑通。等确认forward、backward、精度计算这些环节都没问题了,再换大模型。我在无GPU环境下的习惯是:先在1000张图片的子集上做冒烟测试,确保能正常收敛,再决定是扩大数据量还是在本地硬跑。
数据集方面,建议把数据提前预处理成内存中的numpy数组,而不是每次训练都从磁盘读图、解码、做随机裁剪。后者在CPU环境下的开销非常大,很可能你训练代码本身只要1秒钟,但数据加载要5秒钟,最终一个epoch下来大部分时间花在等数据上。
4.2 用线程和内存优化把CPU性能榨干
CPU训练中一个常被忽略的瓶颈是数据加载。DataLoader默认的num_workers等于0,意思是数据加载在主进程里串行执行,模型算完一个batch还得等着加载下一个,浪费大量时间。把num_workers调成4或8,让数据读取和模型计算在多个进程里并行,提速效果立竿见影。
dataloader = DataLoader(dataset, batch_size=32, num_workers=4, shuffle=True)不过Windows系统下num_workers大于0可能会有一些小问题,比如报DataLoader worker的错,如果遇到了可以先用0跑通,再想办法优化。Linux和macOS基本没这个问题。
线程数的设置也值得调一调。PyTorch默认会用满所有CPU核心,但超线程带来的收益其实是递减的,盲目把线程设到很大反而可能因为上下文切换浪费性能。我一般用物理核心数而不是逻辑核心数:
import torch torch.set_num_threads(8)如果你的CPU是8核16线程,设8通常比设16更快。这个可以自己试,跑一个epoch对比一下时间就知道了。
再说一个内存相关的小技巧:pin_memory这个参数是给GPU用的,在CPU环境下设不设都无所谓。但如果你同时开了很多个DataLoader进程,内存占用会迅速爬升,小内存机器要小心。内存不够的时候系统会疯狂swap,那速度才叫一个惨烈。
4.3 梯度累积与检查点策略:省时间的关键操作
如果你用小batch还是嫌慢,可以考虑梯度累积。这个技巧的原理是:不每个batch都更新参数,而是攒几个batch的梯度再一起更新,等效于用一个更大的batch size,但每次计算量不变。
accumulation_steps = 4 optimizer.zero_grad() for i, (inputs, labels) in enumerate(dataloader): outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()这样做的好处是减少了优化器的更新频率,也就减少了一些同步和调度开销,在CPU环境下实测能稳住训练速度。需要注意的是,梯度累积会影响BatchNorm的行为,用的时候要留意,一般建议在不用BatchNorm的模型上使用,或者配合梯度缩放一起处理。
检查点策略的重要性怎么强调都不为过。CPU训练很慢,一次训练可能跑好几个小时,如果中途断电或者手动误操作,不保存就全废了。我一般每个epoch结束就保存一次checkpoint,里面包含模型权重、优化器状态、当前epoch编号,甚至还包括随机数生成器的状态,这样就能在出错时精确恢复现场:
torch.save({ 'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), }, f'checkpoint_epoch_{epoch}.pth')恢复训练时加载这批状态之后继续循环,省去重头训练的时间。这招在免费云GPU平台上特别管用,session断了也不慌。
还有一个我踩过坑才学乖的经验:CPU环境下验证不要每个epoch都做。验证集的完整跑一遍,在训练里也占不少时间。我在无GPU环境下经常每隔5到10个epoch才验证一次,或者只抽验证集的一个子集来做,省下来的时间能多跑好几轮训练。
5. 常见问题与排查技巧实录
5.1 装了CPU版却报错CUDA不可用
这是无GPU环境下最常遇到的报错之一。你明明看到自己装的是CPU版PyTorch,但代码一运行就提示CUDA不可用。这种情况百分之九十是代码里硬编码了cuda设备,比如:
model = torch.nn.DataParallel(model.cuda())或者更直接的:
tensor = tensor.cuda()解决办法就是把设备相关逻辑改成上一节说的自适应写法。还有一个排查思路:用pip list或pip show torch看一下已安装PyTorch的版本号,如果版本号后面带+cu后缀,比如2.3.0+cu118,说明装的确实是GPU版,需要卸载重装CPU版。
如果有人是从GPU机器拷贝过来的代码,加载权重时也可以用map_location解决。需要注意的是,PyTorch在CPU上加载GPU权重有时会提示缺少什么键,那多半是模型结构定义和保存时的结构不一致,跟设备无关,要查模型定义。
5.2 CPU训练慢到崩溃:先看这几处配置
遇到训练慢,第一件事不是猜,而是测。把一次完整迭代拆开看,数据加载耗时多少、模型计算耗时多少。一个简单的办法是,在训练循环里手动记录时间戳:
import time t0 = time.time() data_start = time.time() inputs, labels = next(iter(dataloader)) print(f"数据加载耗时: {time.time() - data_start:.2f}s") t_start = time.time() outputs = model(inputs) print(f"模型计算耗时: {time.time() - t_start:.2f}s")如果数据加载耗时占了很大比例,就按前面说的预处理和num_workers方案优化。如果模型计算是瓶颈,那就得从源头减负了:减小batch size、换更小的模型、降低输入分辨率。
还有,检查一下是不是所有核心都在满负荷工作。打开任务管理器或htop,看看CPU占用率。如果某个进程只用一个核心,但你机器是八核的,说明torch.set_num_threads或者环境变量设置出了问题。有些情况下需要设置环境变量OMP_NUM_THREADS,因为PyTorch后端也依赖OpenMP的线程池:
export OMP_NUM_THREADS=85.3 内存OOM与硬盘空间告急
CPU训练也会OOM,只不过爆的是内存而不是显存。最常见的报错是RuntimeError: DataLoader worker (pid(s) ...) is killed,这就是内存爆了,系统直接把进程给杀了。处理思路从易到难:先减小DataLoader的num_workers,哪怕设为0也行,让数据加载只占一个进程;再减小batch size;如果数据集本身太大,比如几十GB,就要考虑增量加载,每次只把当前要用的一批数据读进内存,用完就释放。
硬盘空间的坑也不小,特别是数据集和频繁保存的checkpoint。我遇到过训练到一半硬盘满了的悲剧,训练中断不说,之前的日志和部分权重也没了。建议在训练脚本里加一个简单的硬盘可用空间检查,低于阈值就主动暂停并释放一些不需要的文件。控制checkpoint数量也是好习惯,保留最新的两个滚动覆盖就行,别每个epoch都留着,一个模型权重动辄几百MB,几个epoch下来硬盘就吃紧了。
写在后面
我个人的体会是,无GPU环境不是灾,反而是一层滤镜,把深度学习里那些“靠堆硬件就能解决”的幻觉滤掉了,逼着你去理解模型、数据和计算流程的细节。因为等不起,我学会了先跑小实验再放大规模;因为资源紧张,我学会了保存所有中间状态而不是只留最后的权重;因为换卡困难,我学会了写设备无关的代码,后来真拿到GPU卡的时候,迁移过程只改了一行。
最后再分享一个很实用的小细节:把你验证通过的安装命令、依赖版本、训练脚本里那些微调过的参数,都写进项目根目录的README或者requirements文件里。团队里其他人换电脑、换云平台的时候照着走,能少踩一大半我踩过的坑。这种“穷环境逼出来的配置文档”,往往是整个项目最值钱的部分之一。