第一篇分享我留了一个钩子:说后端工程师学 GPU,第一步不是去背 CUDA 编程手册,而是先在脑子里装一张和 CPU 完全不同的硬件地图。这篇就干这件事。我不打算讲太深的微架构细节,目标是让你读完以后,再看到nvidia-smi的一堆输出、再听到"显存带宽""SM 占用率""CUDA 上下文"这些词,不会再一头雾水。你会发现,GPU 这套东西,骨子里还是一套计算机系统,只是它为了一个极端目标,把另一堆东西全部牺牲掉了。而我们要做的,就是把 CPU 时代那套心智模型拆掉三分之一,再重新拼出另一套。
1. 从 nvidia-smi 的第一行说起:GPU 不是 CPU 的加强版,而是另一套计算哲学
先讲个我见过无数次的场景:一个纯后端工程师拿到一台带 GPU 的服务器,第一个动作通常就是敲nvidia-smi,然后盯着输出看半天。能看到温度、风扇转速、显存占用,唯独看不到他最熟悉的"CPU 使用率"和"平均负载"。紧接着脑子里冒出一堆问题:"为什么这块卡有 80G 存储空间?""利用率 30% 算高算低?""这个功耗正常吗?"
这些问题我能理解,因为在 CPU 的世界里,一个进程占用多少 CPU、多少内存,基本能对应到业务负载。但在 GPU 的世界里,观察指标不同,底层逻辑完全不同。如果还用 CPU 的直觉去理解 GPU,第一步就会走偏。
1.1 GPU 用"控制逻辑"换来了"计算密度"
要建立心智模型,先看一个最关键的事实:一个现代 CPU 芯片的大部分面积,都给了缓存、乱序执行、分支预测器和各种控制逻辑。真正干算术运算的 ALU 只占一小块。为什么?因为 CPU 要面对的是"什么任务都可能有"的场景:有 if/else、有随机内存访问、有依赖关系复杂的运算,它必须用大量硬件去猜测下一步会发生什么,猜错了还能回滚重来,最大程度保证每一条指令都在流水线里高效运行。
GPU 干了件非常极端的事:把 CPU 引以为傲的控制逻辑、大缓存、分支预测全部砍到最低,然后把省下来的面积全部堆成简单的小计算单元。NVIDIA 把这些小单元组织成一个个 Streaming Multiprocessor(SM),每个 SM 里面有大量小核心。这些小核心没有复杂的乱序执行,没有大容量私有缓存,它们靠的就是"数量碾压"和"一个指令喂给一堆数据同时算"。
我用到的一个类比,对后端工程师特别有用:CPU 像一个米其林餐厅的顶级大厨,能力全面,什么菜都能做,但一次只能做几道;GPU 像一条自动化快餐流水线,动作单一,但同样时间里能出几百份薯条。你让流水线去做米其林主厨的工作,它做不了;你让主厨去炸薯条,他一个人也扛不住连锁店的订单量。GPU 之所以在深度学习和科学计算里这么好用,是因为这些场景的工作负载恰好是"大量简单重复的算术运算",正对流水线胃口。
理解了这一点,你再回看nvidia-smi里那行 GPU 利用率,就不会拿它和 CPU 使用率等同了。它反映的是 SM(也就是那些流水线)在一段时间内有多少比例在干活。但注意,SM 在干活不代表它在高效地算,它可能是在排队等显存数据——这个误区后面单独讲。
1.2 nvidia-smi 输出里的每一列,到底在告诉你什么
我第一次带后端同事看nvidia-smi的时候,用了整整一下午把那几列数据一个个掰开揉碎讲清楚。这里我简单写一下最有用的几列:
| 指标 | 后端直觉理解 | GPU 层面的真正含义 |
|---|---|---|
| GPU-Util | 类似 CPU 使用率 | SM 有 warp 在执行的时间占比,不代表计算单元在做有效运算 |
| Memory-Usage | 类似内存占用 | Device Memory(显存)已分配量,包括上下文、模型、中间张量 |
| Power | 类似 PSU 功率 | 当前功耗,能间接反映负载状态,但受时钟频率和冷却策略影响 |
| Temperature | 类似机房温度 | 核心温度,超过阈值会降频,导致性能断崖 |
| Volatile GPU-Util | 类似瞬时负载 | 一段时间窗口内的 SM 占用统计,短任务会让它反复跳动 |
我刚接触 GPU 服务器时犯过一个错:看到批处理任务里 GPU 利用率只有 50%,就以为任务写得低效。后来用 profiler 一看,其实瓶颈在显存读写,SM 闲着等数据,利用率自然上不去。这个现象对后端工程师特别有迷惑性,因为你把 CPU 上的经验搬过来——CPU 利用率 50% 通常意味着还有一半算力可以压榨——但在 GPU 上,利用率低可能是数据喂得太慢,你再怎么优化计算逻辑也没用。
1.3 异构计算:你的程序同时在两个"机器"上跑
CPU 和 GPU 协同工作才是常态。这也是"异构计算"这个词的来源:同一份程序里,一部分逻辑跑在 CPU(Host)上,一部分跑在 GPU(Device)上。CPU 负责控制流、系统调用、网络 I/O、任务调度这些复杂逻辑;GPU 负责计算密集的张量运算、矩阵乘法、大规模并行数据处理。
这个模型对后端工程师来说并不难理解:它很像一个 Node.js 服务把 CPU 密集任务丢给一个独立的 Worker 集群。主机端发指令、搬运数据,设备端做重活,干完再传回来。但代价就是:数据搬运本身要花钱(时间),而且这条通道比你想的窄得多。这是下一章的核心话题。
2. 内存模型与服务端缓存思维:显存和 CPU 内存不是一回事
后端工程师对内存模型的理解往往是"内存就是一大块数组,进程虚拟地址空间一套,物理内存一套,通过页表映射"。这套模型放在 CPU 上基本成立,但只要一涉及 GPU,你会发现显存虽然听着像"显卡上的内存",它的物理特性和逻辑层级完全不是同一个用法。不搞清楚这层差异,后面看性能分析报告的时候会非常痛苦。
2.1 显存的带宽有多夸张:一条极宽的公路
先说最重要也最容易被忽略的指标——显存带宽。拿 DDR5 内存举例,主流服务器单条带宽大致几十 GB/s 这个量级;而一块 H100 的 HBM3 显存带宽可以到 3TB/s 以上,A100 也有 2TB/s 左右。差了将近两个数量级。
为什么需要这么夸张的带宽?因为 GPU 有上万个计算单元同时在跑,每一个单元都需要源源不断地喂数据。如果显存速度跟不上,所有计算单元都只能空转等待。你可以把 GPU 核心想象成一家店里的几百个收银员,他们处理一笔交易很快,但要是货架到收银台的传送带一次只能送一件商品,那收银员再快也白搭。带宽,就是那条传送带的宽度。
反过来,高带宽不等于低延迟。显存的延迟其实比 CPU 访问主存还要高,只是 GPU 通过超大规模的并行掩盖了延迟——当一个线程组在等数据时,调度器立刻切到另一组线程去算。这个策略对你而言可能有点别扭,因为你习惯的优化思路是"减少等待、预言缓存";GPU 的做法更接近"让等待的人足够多,多到浪费等待时间也无所谓"。
2.2 GPU 的三级内存结构:从寄存器到共享内存再到全局显存
和 CPU 有 L1/L2/L3 缓存一样,GPU 的内存也有明确的层级,但更关键的是共享内存(Shared Memory)这一层,它和 CPU 的缓存有本质区别:
- 寄存器(Register):每个线程私有,访问速度最快,容量极小。
- 共享内存(Shared Memory):一个 Block 内的线程可以共同访问的手动管理缓存,延迟远低于全局显存,但数据需要显式加载和显式回收。
- 全局显存(Global Memory):就是
nvidia-smi里看到的那个容量,也是所有复制进来的数据、模型参数、中间张量存放的地方。访问它要通过 L2 缓存,延迟最高。
如果你把这套结构映射到后端系统里,大概可以这样对应:寄存器 ≈ 栈上的局部变量;共享内存 ≈ 你可以手工控制的 Redis 缓存,但要自己管理过期时间;全局显存 ≈ 分布式数据库存储。区别在于:CPU 的缓存对程序员是透明的,你写普通代码根本不用管 L1/L2;但 GPU 的共享内存是显式管理的,想让程序跑得快,程序员得自己决定哪些数据放在共享内存里复用,哪些数据直接从显存读。
这也是为什么很多写惯了 Java/Go 的后端工程师,初学 CUDA 都会有点不适:你在 CPU 上很少需要手动优化缓存命中,但在 GPU 上这是家常便饭。哪怕是调用 PyTorch,显存带宽和访存模式也会直接影响训练和推理的速度,只不过框架帮你把底层优化屏蔽掉了一部分。
2.3 PCIe 和 NVLink:GPU 之间以及 GPU 与 CPU 之间的"命脉"
绝大多数后端工程师第一次搞 GPU 集群时,都会忽略一个事实——GPU 和 GPU 之间、GPU 和 CPU 之间的通信链路,往往是整个系统真正的瓶颈。数据不会凭空出现在显存里。你要把训练数据或者推理输入从内存拷贝到显存,这个过程要走 PCIe 总线;如果模型太大需要多卡并行,卡与卡之间的梯度同步要走 NVLink 或 InfiniBand。
典型的 PCIe 5.0 x16 的实际带宽大约在 50~60 GB/s 这个范围,看起来不小,但和 GPU 内部 2TB/s 的显存带宽一比,就是一条乡间小路和高速公路的区别。于是你会看到一个反直觉的现象:计算极快的 Kernel,可能被一次小小的内存拷贝毁掉。尤其是推理场景里,如果每次请求都要把数据从 CPU 内存拷贝到显存再触发计算,哪怕 Kernel 只要 0.5ms,一次 H2D(Host to Device)拷贝加 D2H 拷贝可能就要 0.2ms。占比高得惊人。
我在给一个推荐系统服务做 GPU 加速时,就遇到过这个情况:用 GPU 的确把矩阵运算缩短了 80%,但因为输入向量小、请求频繁,数据拷贝开销几乎抵消了所有收益。最后方案是改用批量请求、常驻显存的池化方式,才把端到端延迟真正降下来。所以后面无论你怎么深入学习,脑子里永远要留一根弦:算得快不算赢,数据搬得少才算赢。
3. 计算强度才是 GPU 性能的分水岭:为什么大模型训练和推理要的不一样
一个后端工程师第一次接触 GPU 选型时,最常见的操作是拿一张 TFLOPS 指标表,哪块卡的算力高就觉得哪块好。这个思路错得不算离谱,但粗了。真实项目里,GPU 能不能跑满,不是一个算力指标能决定的,还得看你的计算模式是"计算密集"还是"访存密集"——用行话说,是 Compute-Bound 还是 Memory-Bound。
3.1 一次矩阵乘法就能看明白的算术强度
矩阵乘法可能是 GPU 上最常见的计算任务了。一个简单的例子:两个 N×N 的矩阵相乘,计算量大约是 2N³ 次浮点运算。如果 N = 4096,那差不多是 1370 亿次浮点运算。要完成这次乘法,需要把两个矩阵读进来,也就是 2×4096² = 3300 万个浮点数的数据量,约 134MB(按 FP32 算)。用这 134MB 访存换取 1370 亿次的计算,算术强度大约在 100 以上。含义是:每读一个字节,可以做 100 次浮点运算。
算术强度高的任务,是 GPU 最喜欢的,因为它可以让计算单元满负荷运行,不必频繁等数据。但如果你的业务是"讲一句话,逐个 token 生成答案",情况就完全不同了:自回归式的生成任务,每一步只计算一个 token 的推理结果,矩阵的规模通常很小,算术强度急剧下降,大量时间花在把模型参数从显存搬到计算单元上。于是你看到的就是 GPU 利用率 30%、显存带宽拉满但算力没吃满的"假低效"状态。
3.2 训练吃算力,推理吃带宽
基于上面的算术强度概念,你可以得出一个后端工程师特别宝贵的判断思路:
| 场景 | 主要瓶颈 | 硬件偏好 |
|---|---|---|
| 大模型训练(大 batch、长序列) | 计算吞吐量(Compute-Bound) | 高 TFLOPS、多卡并行、NVLink 高速互联 |
| 在线推理(单请求、自回归) | 显存带宽和请求串行延迟 | 高显存带宽、低延迟、合适的 batch 合并 |
| 推荐系统/特征工程 | 数据搬运和频繁小矩阵运算 | 带宽与算力均衡,注意 PCIe 传输 |
| 科学计算/分子模拟 | 依赖具体算法,常为混合型 | 双精度性能、显存容量、带宽 |
这个表格不会告诉你哪块卡"最好",但能告诉你什么场景不该只看算力。我自己做 LLM 在线推理服务的时候,就曾经困惑为什么一张 4090 在跑一个小模型时生成速度反而不如 A100——后来理解了,因为个人级显卡算力虽强,但显存带宽和互联能力被定位策略限制,喂不饱自回归生成的频繁参数读取。很多后端同事在 GPU 选型上栽跟头,都是因为把"训练基准"直接套到了"推理场景"上。
3.3 一张看懂 GPU 选型参数的对照表
为了方便你做最初的判断,我整理了一份常见 GPU 的粗对比,参数都是公开资料里常见的大致数值,具体以官方为准:
| GPU 型号 | FP16 算力(Tensor Core) | 显存带宽 | 显存容量 | 典型定位 |
|---|---|---|---|---|
| A100 80G | 约 312 TFLOPS | 约 2TB/s | 80GB HBM2e | 训练/多卡集群,均衡 |
| H100 | 约 990 TFLOPS | 约 3.35TB/s | 80GB HBM3 | 大模型训练,算力极高 |
| L40S | 约 362 TFLOPS | 约 864GB/s | 48GB GDDR6 | 推理/图形/中等训练 |
| RTX 4090 | 约 330 TFLOPS(非官方锁定驱动下参考) | 约 1TB/s | 24GB GDDR6X | 个人开发/小模型推理 |
看这张表时,我希望你重点观察两列:算力和带宽。你会看到 H100 算力是 A100 的三倍多,带宽只提升了不到 70%。如果做大规模训练,算力提升能直接转成更短的训练时间;但做在线推理,H100 相对 A100 的收益可能远没有纸面那么夸张,因为推理瓶颈在带宽和延迟。这类结论,如果你不建立计算强度的心智模型,是很难自己想明白的。
4. 像排查 CPU 服务一样排查 GPU 问题:后端工程师的四个高频事故现场
后端工程师最擅长的能力之一就是排障。服务 CPU 飙高、内存泄漏、连接池占满,这些套路你都熟。但 GPU 服务的排障思路有它自己的特点,很多故障现象长得和 CPU 场景很像,实际根因却完全不相干。我把这几年带后端团队踩过的四个典型事故写下来,每条都对应一个真实场景,能帮你少走不少弯路。
4.1 显存泄漏:进程死了,卡还占着
如果你把 GPU 服务当成普通后端服务来发版,大概率会遇到一个诡异场景:服务进程已经 kill 掉了,nvidia-smi里却还显示原进程占用了几个 GB 显存。更进一步,如果同一块卡上跑着多个进程,某个进程退出后显存不释放,新任务就报 CUDA Out Of Memory,但你就是查不到是谁占的。
这个问题的本质和普通内存泄漏有点像,但多了个"运行时上下文"的概念。每个进程在第一次调用 CUDA API 时,CUDA Runtime 会为它创建一个 Context,Context 里包含显存分配信息、模块加载信息等。正常退出时 Context 会销毁,显存释放。但如果进程被kill -9、或者某个线程被异常终止而 CUDA 没有机会做清理,上下文就会残留。
排查手段也不复杂:nvidia-smi --query-compute-apps=pid,used_memory,process_name --format=csv能看到当前哪些进程占用了显存。如果看到 PID 已经不存在但显存还挂着,那就nvidia-smi --id=<卡号> --gpu-reset(注意先把卡上其他稳定任务停了,这招会让卡上所有 CUDA 进程失效)。另外在代码层面,养成用 PyTorch 的torch.cuda.empty_cache()释放显存缓存,配合pynvml在服务内周期性监控显存增长,可以尽早发现泄漏。
我在实际项目中见过最典型的一次:一个日志打点线程,每处理一条日志就创建一个 CUDA 流,从不释放,跑了一个月后显存占满了,服务 OOM。这个错误放到 CPU 场景里就是"线程池只创建不回收",所以排查思路完全可以复用,只是监控工具从top变成了nvidia-smi和pynvml。
4.2 GPU 利用率 100% 但吞吐就是上不去:SM 在忙不等于数据在流动
这是继显存泄漏之后第二个高频误区。现象非常明确:nvidia-smi显示 GPU 利用率接近 100%,按理说算力应该已经吃满了,但服务的 QPS 和延迟数据远低于预期。很多后端同事会怀疑业务侧逻辑太慢,查来查去,最后发现 GPU 其实在"无效忙碌"。
前面提过,GPU 利用率统计的是 SM 上有没有 warp 在执行指令。如果程序正在反复从全局显存读取数据,每条指令需要等待数千个时钟周期的访存返回,SM 看起来依然有 warp 占据,但实际上计算单元大部分时间都在原地等数据。这种场景下,nvidia-smi的利用率数字会很高,但性能极其拉胯。要想定位,单靠nvidia-smi不够,必须上性能分析工具。
NVIDIA 的 Nsight Compute(命令行是ncu)可以提供准确的 SM 忙碌占比、Memory Throughput、以及各种 stall 原因。我排查过一次自研推理引擎的问题,用ncu一看发现Memory Throughput超过 90%,明显访存受限。这时候我压根不需要去逐行优化 Kernel,而是先去修改数据布局、减少中间张量拷贝、加大 batch 批量复用模型参数,三个改动下来,吞吐翻了一倍多。后端排障里常用的"是 CPU 瓶颈还是 IO 瓶颈"的思路,在 GPU 上完全适用,只是把 CPU 换成 SM,把 IO 换成显存带宽。
4.3 MIG 与多租户切分:GPU 也有"虚拟机"的概念
后端工程师对隔离都很敏感。服务之间不能互相抢占,内存和 CPU 配额要有上限。但在 GPU 上,通常一张卡会被多个任务共享,如果纯靠时间片调度,A 任务的峰值负载就可能拉垮 B 任务的延迟。这时候 NVIDIA 的 MIG(Multi-Instance GPU)技术就派上用场了。
MIG 可以把一块物理 GPU 切分成多个逻辑独立的 GPU 实例,每个实例拥有自己专属的显存切片、SM 子集、内存带宽配额,互不干扰。它的隔离性比软件层的时间片调度强得多,非常像 CPU 场景里的虚拟机 vs 多进程——MIG 就是 GPU 时代的 KVM,时间片调度就像单纯跑一堆容器但不加 cgroup 限制,隔离性差。
用nvidia-smi配置 MIG 其实不复杂,但要先通过nvidia-smi -mig-mode 1开启 MIG 模式,然后用nvidia-smi mig -cgi创建实例,再把实例分配给容器或进程。我第一次给团队搭 MIG 环境时踩过一个坑:MIG 创建后,容器里必须配置对应的NVIDIA_VISIBLE_DEVICES环境变量,否则容器里看不到 MIG 实例,GPU 资源进不来。这点和给容器透传显卡、需要在docker run里加--gpus是同一个套路,但 MIG 的粒度要更细一层。
多租户场景里,我建议给每个服务实例固定 MIG 实例而不是靠默认调度器随机分卡。否则服务间会出现"一损俱损"的连锁问题:一个任务在跑训练把显存带宽吃满,旁边推理服务的 token 生成速度瞬间掉一半。用 MIG 做资源隔离后,高峰期延时的稳定性提升了一个量级,运维那边的告警也清净多了。
4.4 CUDA 版本地狱:比 JDK8 和 JDK17 的冲突更隐蔽
后端工程师对 JDK 版本、Go 版本、Node 版本的兼容性问题应该都深有体会。CUDA 生态的版本地狱有过之而无不及,因为它有三层概念,很多人一开始只看到两个界面:nvcc --version和nvidia-smi里的 "CUDA Version",这两者经常不一致,导致不少人误判环境。
简单拆解一下:CUDA Driver(驱动)是驻留在操作系统内核态的底层组件,它决定你的 GPU 最高能支持到哪个 CUDA 运行时版本;CUDA Toolkit 是用户态的开发库和编译器,nvcc是它的编译器前端;CUDA Runtime(cudart)则是程序运行时链接的库文件。nvidia-smi里显示的 CUDA Version 通常是 Driver 支持的最高版本,而nvcc --version显示的是你当前安装的 Toolkit 版本。只要 Toolkit 版本 <= Driver 支持的版本,通常就能正常工作;但如果驱动太老而 Toolkit 太新,程序一跑就报CUDA driver version is insufficient。
这种问题在 Docker 容器里尤其常见:宿主机驱动是新的,但容器镜像里带的是旧 CUDA Toolkit,或者反过来宿主机驱动太老、容器里新框架需要高版本 CUDA。排查思路和排查依赖冲突一样:先确定宿主机驱动版本和支持的上限,再确认容器内python -c "import torch; print(torch.version.cuda)"对应的 CUDA Runtime 版本,最后确认路径上有没有多个 CUDA 版本互相干扰。用环境变量CUDA_HOME、LD_LIBRARY_PATH锁定版本,跟你在 Java 里管理 JAVA_HOME 其实是同一个方法论。
5. 不写一行 CUDA,也能让 GPU 跑得更好:后端工程师的三条实用路径
听到这里,你可能会觉得:理解是理解了,但总不会让我去写 CUDA Kernel 吧?当然不需要。后端工程师在 GPU 生态里最大价值不是写出最快的 Kernel,而是把 GPU 当作一个分布式计算资源管好、用好、优化好。我用过且验证有效的路径有这么几条,按上手成本从低到高排。
5.1 路径一:站在推理框架的肩膀上,用 TensorRT 和 OnnxRuntime
大多数 GPU 加速场景,你并不需要直接操作 GPU。现成的推理框架已经把底层 Kernel 优化得很好了。比如 NVIDIA 的 TensorRT,可以对 ONNX 模型做层融合、精度校准、内存复用,在很多场景下能比原始的 PyTorch 推理快几倍。OnnxRuntime 的 CUDA Execution Provider 也是类似思路,部署起来更简单,适合快速验证。
我印象最深的一次:给一个自然语言处理服务加速,原始模型在 PyTorch 上用 GPU 推理,P99 延迟 40ms。导出 ONNX 再走 OnnxRuntime,没做任何算子级优化,延迟直接降到 18ms。后来又改用 TensorRT,进一步优化 batch 和显存复用,压到了 10ms 以下。很多后端同事会觉得框架调优是算法工程师的事,但实际经验告诉我:作为一个后端工程师,你只要能熟练完成 "PyTorch 导出 ONNX → 用 TensorRT 重新部署 → 对比性能" 这条链路,就已经能解决大量与 GPU 相关的性能问题。
这条路径的重点不在 CUDA 编程,而在工程化:你要理解模型转换带来的算子变化、动态 shape 的处理、显存碎片的控制。这些东西本质上都是你熟悉的部署和性能测试工作,只不过对象从服务代码变成了模型。
5.2 路径二:用 PyTorch 层面优化,而不是直接操作 CUDA
如果你想更进一步,也没必要马上跳到 CUDA。PyTorch 提供了大量高层 API,能让你在 Python 环境里就实现可观的 GPU 性能优化:
- 把多个小张量操作合并成批次操作,减少 Kernel 启动次数。Kernel 启动是有开销的,后端工程师可以把它理解为"一次 RPC 调用开销",调用多了,性能自然下降。
- 设置
torch.backends.cudnn.benchmark = True,让 cuDNN 自动挑选最优卷积/矩阵算法,这对固定 shape 的模型非常有效。 - 用
torch.compile或torch.cuda.graphs捕获 CUDA Graph,把一连串 Kernel 启动变成一次图执行,大幅降低 CPU 侧调度开销。
这套思路最吸引人的地方是:它不需要你懂 GPU 微架构,只需要你有"减少开销"的后端工程直觉,然后找到对应的 API。我之前在优化一个多路召回模型时,只是把原先逐条处理用户的循环改成了用 padding 打包成 batch,GPU 利用率直接从 35% 到了 70% 多。这个优化放在你的后端服务里,等价于"把 N+1 次查询合并成一次批量查询"——你早就会了,只是不知道 GPU 上也要这么做。
5.3 给 GPU 服务做容量规划:一套后端工程师的实用方法论
最后说说 GPU 资源容量规划。这块后端工程师特别容易忽略,因为习惯了 CPU 服务可以靠水平扩容堆机器,但 GPU 服务器成本高,而且单卡资源有限,规划不好会浪费大量预算。
我的建议是算清楚三笔账。第一笔是模型的显存底价:一个 7B 参数模型,FP16 权重大约需要 14GB 显存,再加上 KV Cache、激活值和 CUDA Context,实际上你需要按 20-24GB 去预留。第二笔是并发和吞吐:推理服务的 token 生成吞吐往往受显存带宽限制,单卡能同时服务的并发数有限,该并发量和模型大小成反比。第三笔是扩展策略:是小 Batch 低延迟,还是大 Batch 高吞吐,方向不同对卡的选择完全不同。
你不需要一开始就做得很精细,但至少要有"容量规划"这个概念。我见过一个团队,为了图省事,把几十个小模型全塞在同一张卡上,结果互相争抢显存带宽,整体吞吐比分开部署还低。后来按模型调用频率和显存占用重新分了组,同样的卡数,P99 延迟降了 40%。这在 CPU 后端里就是"服务混部"问题,你完全可以用已有的架构思维来解决 GPU 资源调度。
我个人的看法是:后端工程师学 GPU,不用从 CUDA 汇编入门,更不需要背死板的理论。真正有价值的,是先搞清楚三个问题——GPU 硬件把资源花在了哪里、计算强度和访存强度哪个才是你业务的瓶颈、以及 GPU 服务应该怎么观察和隔离。把这套硬件心智模型装进脑子里,后面不管你是用 PyTorch 调模型、还是用 TensorRT 加速推理,方向都不会跑偏。如果你正带着团队做 GPU 相关项目,我建议你干的第一件事不是囤卡,而是打开你的 Nsight 或至少认真读一次nvidia-smi的关键指标,把 SM 占用率、显存带宽、PCIe 拷贝这三组数字解读明白。能做到这一步,你就已经比大多数从零开始的入门者领先了。