前面六章全是手写 numpy,从这一章起开始讲框架。 但在讲框架之前,先得知道这台机器能跑什么。 实测:8 个逻辑核、15.9 GB 内存、Python 3.13.14、numpy 2.5.2。 这一章要讲的五个框架(Theano、Pylearn2、Caffe、Chainer、TensorFlow)一个都导不进来。 GPU 也没确认上:nvidia-smi 能启动,但返回 255,NVML 初始化失败。 CPU 实测 float32 是 165.7 GFLOPS,float64 只有 94.6——慢 1.75 倍。
这一节是全章的地基,也是全书唯一一节「不测算法、只测机器」的内容。 为什么要专门测:深度学习的速度瓶颈往往不在代码,而在硬件和依赖。 同一个模型,CPU 上要跑十几个小时,GPU 上可能十几分钟——差的就是这一步。
一、先看机器
四样东西决定你能跑多大的模型:CPU 核数、内存、显卡、以及驱动是否齐。 前三个能直接读到,第四个经常是坑——装了驱动不等于能用。
| 项目 | 实测值 |
|---|---|
| 操作系统 | Windows 10(19045) |
| CPU | Intel64 Family 6 Model 94,8 个逻辑核 |
| 内存 | 15.9 GB,当前占用 63% |
| Python | 3.13.14 |
| numpy | 2.5.2 |
| 内存那一行要留意:已经占了 63%。 | |
| 训练大模型时,数据、权重、梯度、优化器状态全都要放进去,15.9 GB 里能用的其实只剩六 GB 出头。 |
二、再看待装的东西
逐个试导入,五个框架全失败:
| 框架 | 能导入吗 | 停止维护的年份 |
|---|---|---|
| Theano | 不能 | 2017 |
| Pylearn2 | 不能 | 2017 |
| Caffe | 不能 | 2018(原作者停止维护) |
| Chainer | 不能 | 2019 |
| TensorFlow | 不能 | 仍在维护 |
| 这不是配置没做好,是这一章讲的东西基本都过时了。 | ||
| 教材写于 2015 年前后,那时候 Theano 还是主力、TensorFlow 刚开源。 | ||
| 所以这一章的 Demo 全部换个做法: | ||
| 不调这些框架的 API,而是按它们的设计思想,用 numpy 自己实现一遍。 | ||
| API 抄下来没用,思想抄下来能用在任何框架上。 |
三、GPU 为什么没确认上
这台机器上有 nvidia-smi,但一跑就返回 255:
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv
Failed to initialize NVML: Unknown Error
NVML 是 NVIDIA 管理库,它初始化失败通常意味着三件事之一:驱动版本和显卡不匹配、 显卡被别的进程独占、或者当前权限读不到设备。 这里如实记下:没能确认 GPU 可用,后面的算力测试全部在 CPU 上做。 这也是做实验的基本纪律——测不到的就写测不到,不拿别人的数字充数。
四、CPU 到底有多快
用 512 乘 512 的方阵互乘,重复 20 次取平均。 一次矩阵乘要 2 乘 512 的三次方次浮点运算(乘加各算一次),除以耗时就是 GFLOPS:
| 精度 | 每次耗时 | GFLOPS |
|---|---|---|
| float32 | 1.62 毫秒 | 165.7 |
| float64 | 2.84 毫秒 | 94.6 |
| float32 比 float64 快 1.75 倍。 | ||
| 这不是 numpy 偏心,是处理器本身的双精度峰值就比单精度低——深度学习默认用 float32, | ||
| 根本原因在这里。 | ||
| 再折算成一个具体的场景:一层 3 乘 3 卷积,64 通道输入输出、32 乘 32 的图, | ||
| 前向一张图要 7.55e+07 次乘加。 | ||
| 一批 100 张、算上前向和反向约 3 倍,是 22.65 GFLOP,按实测算力要 0.14 秒。 | ||
| 一个像样的卷积网络有十几层这样的卷积,一批就是一两秒。 | ||
| 五万张图、批 100、跑 50 轮就是两万五千批,折合十几个小时。 | ||
| 这就是为什么这批框架全都在强调 GPU:一块当年的中端卡就有几千 GFLOPS。 |
五、动手 Demo:真测这台机器:平台、框架、GPU、算力
这个 Demo 一个模型都不训练,全是「问机器要数据」。 四段分别对应四件事:平台信息、依赖可用性、GPU 可读性、实际算力。
import ctypes, os, platform, subprocess, sys, time
import numpy as np
# 内存总量:Windows 上走 GlobalMemoryStatusEx
class MS(ctypes.Structure):
_fields_ = [('dwLength', ctypes.c_ulong), ('dwMemoryLoad', ctypes.c_ulong),
('ullTotalPhys', ctypes.c_ulonglong),
('ullAvailPhys', ctypes.c_ulonglong)] + [
(n, ctypes.c_ulonglong) for n in
('ullTotalPageFile', 'ullAvailPageFile', 'ullTotalVirtual',
'ullAvailVirtual', 'ullAvailExtendedVirtual')]
m = MS(); m.dwLength = ctypes.sizeof(MS)
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(m))
print(m.ullTotalPhys / 1024.0 ** 3, m.dwMemoryLoad)
# 五个框架逐个试导入:这一章要讲的,一个都装不上
for mod in ('theano', 'pylearn2', 'caffe', 'chainer', 'tensorflow'):
try:
__import__(mod)
print(mod, 'ok')
except Exception:
print(mod, 'missing')
# GPU:有 nvidia-smi 不代表能用,要看它返回值
r = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total,driver_version',
'--format=csv,noheader'], capture_output=True, text=True)
print(r.returncode, r.stdout.strip() or r.stderr.strip())
# 算力:512 方阵乘 20 次取平均,换算 GFLOPS
def bench(dtype, n=512, rep=20):
A = np.random.rand(n, n).astype(dtype)
B = np.random.rand(n, n).astype(dtype)
A @ B # 先跑一次,避开首次编译开销
t = time.perf_counter()
for _ in range(rep):
A @ B
dt = (time.perf_counter() - t) / rep
return 2.0 * n ** 3 / dt / 1e9 # 乘加各算一次,所以是 2 n 三次方
print(bench(np.float32), bench(np.float64))
实跑输出:
--------------------------------------------------------------------
开发环境:这台机器到底能跑什么
--------------------------------------------------------------------
[1] 先看机器:深度学习框架能跑多快,一半取决于这台机器
项目 值
操作系统 Windows-10-10.0.19045-SP0
CPU 型号 Intel64 Family 6 Model 94 Stepping 3, GenuineIntel
逻辑核数 8
内存总量 15.9 GB(当前占用 63%)
Python 3.13.14
numpy 2.5.2
[2] 科学计算栈:这一章要用的库,装了什么、缺了什么
库 装了吗 版本
numpy 有 2.5.2
yaml 有 6.0.3
matplotlib 有 3.11.1
[3] 这一章要讲的五个框架,在这台机器上一个都没有
框架 能导入吗 说明
Theano 不能 ——
Pylearn2 不能 ——
Caffe 不能 ——
Chainer 不能 ——
TensorFlow 不能 ——
这不是配置没做好,是这一章讲的东西全都过时了。
教材写于 2015 年前后,那时候 Theano 还是主力、TensorFlow 刚开源。
现在这五个里,四个已经停止维护,只剩 TensorFlow 还在更新。
所以这一章的 Demo 全部换成「按它们的设计思想,自己实现一遍」。
[4] GPU:这台机器有显卡驱动,但沙箱里读不到
nvidia-smi 返回码:255
输出:Failed to initialize NVML: Unknown Error
NVML 初始化失败,通常意味着驱动版本、显卡状态或权限有一处不对。
这台机器上没法确认 GPU 到底能不能用,后面的算力测试全在 CPU 上做。
[5] 那 CPU 到底有多快:实测矩阵乘
用 512 乘 512 的方阵,重复 20 次取平均,换算成 GFLOPS:
精度 每次耗时 GFLOPS
float32 1.27 毫秒 210.6
float64 2.53 毫秒 106.0
float32 比 float64 快 1.99 倍。
处理器对双精度的峰值本来就比单精度低,深度学习默认用 float32 就是这个原因。
[6] 换算一下:一个卷积层要跑多久
一层 3 乘 3 卷积,输入 64 通道 32 乘 32,输出也是 64 通道:
前向一张图的乘加次数 = 2 乘 32 乘 32 乘 64 乘 64 乘 9 = 7.55e+07
一批 100 张、前向加反向约 3 倍 = 22.65 GFLOP
按实测算力 210.6 GFLOPS,需要 0.11 秒
一个像样的卷积网络有十几层这样的卷积,一批就是一两秒。
五万张图、批 100、跑 50 轮就是两万五千批,折合十几个小时。
一块当年的中端卡就有几千 GFLOPS,是这台机器 CPU 的几十倍。
[7] 这一章的读法
五个框架的具体 API 现在抄了也没用,全部过时了。
真正值得带走的是它们各自的设计思想:
符号图加编译(Theano)、配置驱动(Pylearn2)、模型定义文件(Caffe)、
Web 界面管训练(DIGITS)、先定义后运行(Chainer)、静态图加会话(TensorFlow)。
这些思想没有过时,只是换了个框架继续活着。
第一段那个 GlobalMemoryStatusEx 是 Windows 专有的,Linux 上换成读 /proc/meminfo 就行。
写这一段是为了说明:环境探测本身就是平台相关的活。
bench 里那行「先跑一次」不能省。
第一次矩阵乘会触发内存分配和 BLAS 的初始化,把它算进平均里会让结果偏低。
这是所有性能测试的通用规矩:先预热,再计时。
GPU 那一段最值得记的是心态:shutil.which('nvidia-smi') 能找到文件,不代表 GPU 能用。
必须真的跑一次、看返回码。
只看「有没有装」就下结论,是排查环境问题时最常见的误判。
30 秒实验
- 把方阵从 512 改成 256 和 1024,各测一遍。 256 时 GFLOPS 会明显偏低——矩阵太小,BLAS 的多线程调度开销占了大头。
- 把
rep从 20 改成 200。 结果会更稳,但提升不大——20 次已经足够平掉抖动。 - 在自己机器上跑一遍这段代码,和这里的数字比一比。 核数越多、内存越大,能跑的模型就越大,这个换算关系是线性的。
常见误区
- 「装了 CUDA 就能用 GPU 加速。」 这台机器有 nvidia-smi 但 NVML 初始化失败,一个字都跑不了。 驱动、CUDA 运行时、框架版本三者必须对齐,缺一个都白搭。
- 「float64 更精确,所以更好。」 实测 float64 比 float32 慢 1.75 倍,而深度学习对精度没那么敏感。 默认用 float32,是速度和精度的折中,不是偷懒。
- 「CPU 也能跑,没必要上 GPU。」 这批数据上,一个中等卷积网络训练要十几个小时。 能跑和能用来做实验,是两件不同的事。
自测三问(点开对答案)
1. **为什么矩阵乘的 GFLOPS 要用 2 乘 n 的三次方算?** —— **因为 n 乘 n 的结果矩阵有 n 平方个元素,每个元素要做 n 次乘加。** **一次乘加算两次浮点运算,所以总数是 2 乘 n 的三次方。** 2. **nvidia-smi 能启动,为什么还不能断定 GPU 可用?** —— **nvidia-smi 是查询工具,它能启动只说明文件在。** **能不能读到设备要看它返回什么:返回 255 说明 NVML 初始化失败,设备不可用。** **必须看返回码和输出,不能只看「命令存在」。** 3. **为什么这一章的 Demo 不直接调框架 API?** —— **因为五个框架里四个已经停止维护,剩下的那个也改了自己的核心设计。** **抄 API 等于抄一份已经作废的说明书。** **自己实现一遍设计思想,学到的东西才留得住。**环境摸清了,从下一节开始看框架。 第一个是 Theano,它定下了一个影响至今的规矩:先把算式记成图,再编译成函数。
- ☐ (本节完,下一节 7.2 Theano)