资讯动态

调度器视角下的NVIDIA GPU硬件结构:从SM到NVLink的关键解析

发布时间:2026/9/6 9:29:16 来源:尧图企业网站定制
1. 从调度视角重新认识 NVIDIA 硬件做 GPU 调度绕不开一个基础问题你到底在调度什么很多刚接触集群调度或者推理加速的朋友上来就去看 Kubernetes 的 device plugin、去看 vLLM 的 PagedAttention结果发现文档越看越晕。原因很简单——你还没建立起“硬件结构决定调度策略”这条主线。我写这个系列第一篇文章就打算把 NVIDIA 的硬件结构彻底摊开从调度器的视角重新审视每一层硬件组件。所谓调度本质上是“在正确的时间把正确的任务放到正确的计算单元上”。放在 NVIDIA 的场景里这个“计算单元”不是一个模糊的“GPU”而是从 CPU 侧到 GPU 内部 SMStreaming Multiprocessor的一整条层级链路。任务从主机端发起经过 PCIe 或 NVLink 传输到达 GPU 显存再由 GPU 内部的线程调度器分发到具体的计算核心。这一整条路径上每一个环节都有资源约束也都对应着不同的调度决策点。这篇文章适合谁看如果你是做 GPU 集群管理、推理服务性能优化、或者正在研究 vLLM、TensorRT-LLM 这类框架的 scheduling 实现建议花十分钟把硬件结构搞清楚。否则后面讨论“为什么这个调度策略能提升吞吐”“为什么显存分配要这样设计”的时候你会缺少一个关键的认知底座。2. 完整计算链路的宏观拆解从 CPU 到 GPU 的每一步2.1 CPU 与 GPU 的分工边界在哪先看整条链路的起点。任何 GPU 任务都始于 CPU 侧的一段代码哪怕是最简单的cudaMemcpy也需要 CPU 先发起调用。CPU 的角色是“控制面”负责逻辑判断、任务编排、内存分配GPU 的角色是“数据面”负责大规模并行计算。这个分工决定了调度器必须同时管理两个侧面的资源状态。CPU 侧的资源状态包括当前有多少个进程在排队、每个进程需要多大的显存、数据传输的源地址和目的地址是否就绪。GPU 侧的资源状态包括SM 占用率、显存剩余量、计算队列深度、NVLink 带宽占用等。一个成熟的调度器比如 Kubernetes 的 gpu-device-plugin 配合 scheduler-extender要做的就是把这两侧的状态统一抽象成可量化的资源视图然后做出分配决策。这里面有个常见的认知误区有人觉得 GPU 调度就是“分配显存大小”。实际上显存只是 GPU 资源的一种而且往往是最好管理的一种。真正的调度难点在 SM 计算资源的分配和上下文切换的代价控制。举个例子一个 A100 80GB 的卡显存划分成两个 40GB 的实例很容易但如果两个任务都需要大量计算资源它们会在 SM 层面互相争抢导致各自的吞吐都上不去。这就是为什么新架构里出现了 MIGMulti-Instance GPU技术——后面我详细讲。2.2 系统拓扑PCIe 和 NVLink 决定数据传输的上限现代 GPU 服务器里CPU 和 GPU 之间、GPU 和 GPU 之间通过两种主流总线互联PCIe 和 NVLink。这两者的带宽差异极大直接决定了数据移动的策略。PCIe Gen4 x16 的单向带宽大约是 32GB/s双向 64GB/s。看起来不小但和 GPU 内部的计算速度比起来就差远了——A100 的 FP16 算力是 312 TFLOPS如果计算一个 FP16 矩阵每秒钟需要消费的数据量远超 PCIe 能提供的带宽。换句话说PCIe 是瓶颈而且是严重瓶颈。NVLink 的出现就是为了解决这个问题。以 A100 为例第三代 NVLink 单链路带宽是 50GB/s 双向一张卡通常有 12 条 NVLink 链路总双向带宽达到 600GB/s。到了 H100 的第四代 NVLink单卡总带宽进一步提升到 900GB/s。这意味着什么意味着如果你有多卡互联的需求数据通过 NVLink 传输比通过 PCIe 快一个数量级调度器在做数据放置决策时必须优先考虑数据在 NVLink 域内的局部性。实际调度时的选择逻辑是这样的如果一个任务需要频繁和其他 GPU 上的任务交换中间结果比如张量并行的 Transformer 模型调度器应该把这几张卡放在同一个 NVLink 域内。在 DGX 系列服务器里8 张 GPU 通过 NVSwitch 全互联任何两张卡之间都能享受 NVLink 带宽。而在普通双卡服务器里GPU 之间的通信可能要走 PCIe Switch甚至经过 CPU 的 PCIe Root Complex延迟和带宽都会有明显劣化。2.3 数据搬运中的隐藏成本拷贝与映射的取舍在 CPU 与 GPU 之间搬运数据有两种基本方式显式拷贝cudaMemcpy和统一寻址Unified Memory。这两种方式在调度器设计中的含义完全不同。显式拷贝的优点是行为可预测——调度器清楚知道数据传输什么时候开始、什么时候结束、占用了多少 PCIe 带宽。缺点是编程复杂需要手动管理两块内存的生命周期。统一寻址的优点是透明GPU 缺页时自动从 CPU 内存迁移数据缺点是性能不可控——你无法预知 GPU 什么时候会触发缺页中断数据迁移的延迟实时变化这对调度器来说是极大的不友好。从我实际做调度的经验来看凡是追求确定性延迟的场景比如在线推理必须用显式拷贝并且最好用 CUDA Stream 将传输和计算重叠起来。凡是追求开发效率的原型验证场景可以用统一寻址但要做好性能波动的心理准备。调度器的设计必须感知这两种模式否则你无法准确估算一个任务的实际执行时间。3. GPU 内部的核心微观结构从 SM 到线程束的工作原理3.1 SM 的内部构造与各组件职责前面讲的是数据如何到达 GPU现在讲数据到了 GPU 之后怎么被计算。GPU 的计算核心是 SMStreaming Multiprocessor一个 GPU 上有几十到上百个 SM每个 SM 内部又分成几个处理块。以 Ampere 架构的 GA100 为例每个 SM 包含 4 个处理块每块有 32 个 FP32 CUDA Core 和 32 个 INT32 单元另外还有 4 个 Tensor Core第三代。寄存器文件Register File大小是 256KBShared Memory 最大 164KB。这个配置决定了每个 SM 能同时容纳多少线程、多少 warp。调度器的核心任务之一就是决定把多少个 block 放进同一个 SM这个决策直接受限于寄存器和共享内存的容量。这里有个关键概念叫 occupancy占用率指的是实际活跃的 warp 数占理论上限的比例。Occupancy 越高越能隐藏访存延迟——一个 warp 在等数据回来的时候调度器可以切到另一个 warp 执行计算。但这不代表 occupancy 越高越好。过高的 occupancy 意味着每个线程分到的寄存器变少可能导致寄存器溢出spill反而拖慢性能。实战里我踩过这个坑在调一个 NLP 模型的 kernel 时把 block 大小从 256 改成 512occupancy 确实提升了但性能反而下降了 15%。原因是寄存器不够用部分变量被压到 local memory产生了额外的访存开销。调度器在设计时不能只看 SM 占用率这个单一指标要综合考察寄存器压力、shared memory 用量、访存模式等多个维度。3.2 Warp 调度机制对任务切分的影响Warp线程束是 NVIDIA GPU 调度的基本单位一个 warp 包含 32 个线程。GPU 的 warp scheduler 每周期从可执行的 warp 中选一个发出一条指令。这个机制对上层调度器有深刻影响当你想把一个任务拆成细粒度的小任务去填充 GPU 时你真正填充的是 warp slot而不是线程 slot。在上面提到的 Ampere SM 中每个 SM 有 4 个 warp scheduler每个 scheduler 最多管理 16 个 warp所以单个 SM 最多可以容纳 64 个 warp也就是 2048 个线程。这个数字是所有任务拆分时的硬约束。如果你设计了一个 kernel每个 block 有 1024 个线程那么一个 SM 最多同时容纳 2 个这样的 block如果每个 block 只有 128 个线程SM 可以容纳 16 个 block理论上限实际还要看寄存器和 shared memory。从调度器的视角看block 的粒度设计不只是 GPU 内部的事它直接影响你能不能把一个 GPU 有效地分给多个任务。比如 A100 有 108 个 SM如果你用的推理框架比如 TensorRT-LLM默认把整个 GPU 当成一个执行单位那么并发多个请求时block 的分配由框架内部决定你无法细粒度控制。这也是很多人觉得GPU 利用率上不去的根源——不是 GPU 不行而是你给的并行度不够或者任务拆得太粗。3.3 异步执行与 CUDA Stream 的资源隔离CUDA Stream 是 GPU 任务管线的核心抽象。同一个 Stream 里的操作按顺序执行不同 Stream 之间的操作可以并发。这个并发能力是调度器实现计算和传输重叠的基础——一个 Stream 在做 kernel 计算另一个 Stream 可以同时做数据拷贝。但 Stream 不是免费的。每个 Stream 在硬件上占用一定的资源创建太多 Stream 反而增加调度开销。GPU 硬件层面有多个 DMA 引擎Copy Engine和多个计算队列SM 层面的 warp scheduler 会轮转服务来自不同 Stream 的任务。设计调度器时Stream 的数量需要根据任务特征动态调整固定的 Stream 池通常是一种次优解。在实际落地时我常用的模式是动态 Stream 池预先创建一组可复用的 Stream任务到达时从池中取一个执行完归还。这样避免了频繁创建/销毁 Stream 的系统调用开销同时保持了并发度可控。4. 内存层级与访存模式调度器必须吃透的隐性约束4.1 显存带宽与存储层级不是所有数据都一样快GPU 的片上存储层级从快到慢依次是寄存器 - Shared Memory - L1 Cache - L2 Cache - 显存HBM- CPU 内存。每一级的带宽和延迟差异是两个数量级起步的量级。寄存器访问没有延迟Shared Memory 的延迟大约 20~30 个周期L2 Cache 命中在 200 个周期左右而 HBM 显存的延迟高达 400~600 个周期。调度器在做任务放置时需要估算任务的访存模式如果一个任务的数据是流式的读取一次不再复用把它安排在带宽优先的配置上比如让多个 SM 共享 L2 的尾部tail部分如果一个任务的数据局部性强反复访问同一块数据应该优先利用 Shared Memory。这个决策在集群调度层面通常被忽略但在单卡多租户场景比如 MIG中非常重要。有一个值得注意的细节是 L2 Cache 的划分。Ampere 架构支持 L2 Cache 的 Set-aside预留功能可以为特定流设置固定的 L2 容量。这在多任务共享 GPU 时会显著影响性能。比如一个任务对延迟敏感你可以把它设置为 20MB L2 预留另一个任务带宽型不需要太多 cache走默认策略即可。调度器如果能感知并控制这些软资源对混合负载的性能隔离会有质的提升。4.2 显存分配策略对调度器的影响显存的分配和回收是调度器最容易感知的资源维度。NVIDIA 驱动层的显存分配默认是惰性的lazy allocation——cudaMalloc只保留虚拟地址空间物理显存页面在实际访问时才映射。这带来一个坑任务启动时占用的显存可能远小于它峰值需要的显存两个任务的显存使用量可能在运行中动态变化。对调度器来说这意味着单纯记录已分配显存是不准确的必须同时跟踪保留显存和实际使用显存两个指标。在 Kubernetes 环境下通过 nvidia-smi 查询到的显存使用量是实际使用量而 dmon 里记录的 FB_USED 是设备端实际分配的物理显存。两者的口径差异会导致调度误判——以为显存还够用结果新任务一启动就 OOM。解决思路有两个层面一是调度器层面对显存预留做保守估计按任务声明峰值分配二是利用 CUDA 的显存池机制比如通过 CUDA 的 VMMVirtual Memory Management接口做细粒度管理。后者更灵活但实现复杂度高适合对显存管理有极端需求的场景比如 vLLM 的 continuous batching 就是自己管理 KV Cache 显存。4.3 显存带宽的计算示例这个任务到底快不快来看一个实际的计算示例。假设我们要在 A100 上搬运一个 2GB 的矩阵做矩阵乘法。A100 的 HBM2e 带宽是 2TB/s 左右注意后面和 PCIe 带宽的对比。理论上的最小搬运时间是 2GB / 2TB/s 1ms。这个数据说明的是一旦数据已经在显存里GPU 内部的读取速度是很快的。但如果你是从 CPU 内存通过 PCIe 拷贝进 GPUPCIe Gen4 x16 的带宽约 32GB/s传送 2GB 需要 62.5ms。这个时间差异达到两个数量级。换句话说数据放错了位置性能损失是灾难性的。调度器的核心职责之一就是把数据提前安排在正确的位置——所谓 data locality数据局部性。这个计算同样提醒我们在评估一个调度策略时要分清这个延迟花在哪一段链路上。有不少人做推理性能分析发现端到端延迟高就一头扎进 kernel 优化里。实际上如果数据从网络接收到 GPU 显存的路径上没有优化比如 socket 收包后先拷到 CPU 内存再拷到 GPU那这一段的传输延迟可能远大于 GPU 计算本身。调度器应该优先优化数据传输路径。5. 多 GPU 扩展与卡间互联大模型时代逃不开的调度问题5.1 从 PCIe 到 NVSwitch卡间通信的演进大模型训练和推理绕不开多卡并行。一张 A100 显存 80GB放不下 70B 参数模型的权重至少需要 140GB 的 FP16 权重和对应的优化器状态必须多卡拆分。拆分方式有数据并行、张量并行、流水线并行等不同并行方式对卡间通信的需求完全不同。数据并行Data Parallelism的特点是每个 GPU 持有完整模型副本通信集中在梯度同步阶段。梯度同步的量级和模型大小相关但频率低通常通信带宽要求不高。张量并行Tensor Parallelism把每个 Transformer 层的矩阵切成多块放在不同的 GPU 上计算过程中需要进行多次 AllReduce 通信对带宽和延迟都极其敏感。流水线并行Pipeline Parallelism又不一样它是按层切分通信集中在层间交接处频率较低但单次数据量大。调度器在多卡分配时要根据并行策略来决定 GPU 的物理位置。张量并行的 GPU 之间必须有高速互联最好是 NVLink 直接相连数据并行的 GPU 可以分散在不同节点但最好同构。在我实际管理的一台 8 卡 DGX 上GPU 之间走 NVSwitch 全互联任何两卡之间的通信带宽都一样调度就很简单。但在普通 2 卡或 4 卡的服务器上GPU 可能挂在不同的 PCIe Switch 下跨 Switch 的通信带宽会折损这就是调度器需要注意的 NUMA 亲和性 问题在 GPU 场景的映射。5.2 通信计算重叠一张卡同时做计算和通信高性能的调度策略必须支持通信和计算的重叠。这个能力依赖于硬件的异步执行支持——GPU 的 DMA 引擎可以独立于 SM 工作NVLink 传输可以在 kernel 执行的同时进行。重点在于调度器要主动拆出通信阶段不与其他计算阶段串行。以流水线并行中的 micro-batch 切分为例把一个大 batch 切成多个 micro-batch让当前 micro-batch 计算的同时上一个 micro-batch 的梯度在后台做 AllReduce。这要求调度器能识别并分离计算任务和通信任务把它们放到不同的 CUDA Stream 中。框架层面比如 Megatron-LM已经实现了这种机制但如果你自己写调度器需要理解底层原理否则很容易做串行化处理白白浪费硬件能力。一个值得实践的优化技巧是带宽控制bandwidth throttling。NVLink 是全双工但如果同时发起大量通信任务NVSwitch 内部的仲裁可能造成延迟抖动。调度器可以为不同优先级的通信设置不同的带宽上限通过 NCCL 的NCCL_MAX_NCHANNELS或环境变量控制低优先级通信避开高优先级通信的敏感期。这在多租户共享 GPU 集群时非常有用。5.3 MIG 与多租户隔离的硬件基础MIGMulti-Instance GPU是 NVIDIA 在 Ampere 架构引入的硬件分区技术能把一块物理 GPU 切成多个相互隔离的 GPU 实例。A100 40GB 可以切成最多 7 个实例H100 有更灵活的切分模式。每个 MIG 实例拥有独立的 SM、显存带宽、L2 Cache 切片实例之间天然隔离。从调度器的立场看MIG 是真正的硬隔离手段——一个 MIG 实例上的任务不会因为其他实例的任务导致性能波动。这与时间片共享MPSMulti-Process Service有本质区别。MPS 只是把多个进程的 kernel 合并调度到同一批 SM 上没有硬件隔离一个进程的 kernel 可能阻塞其他进程的 kernel。但在使用 MIG 时要注意几个细节。第一MIG 实例的切割粒度是固定的你必须按硬件支持的模板来切不能任意自定义大小。第二不是所有 CUDA 功能都支持 MIG——例如 UVMUnified Virtual Memory在 MIG 上受限某些 GPU 间的 P2P 通信也不支持跨 MIG 实例。第三MIG 模式下 GPU 显存被切片小实例的显存可能不足以跑大的 batch这会影响你在推理服务中的 batch 大小设置。实际部署推理服务时我用过4 个 A100 切成 2 个 40GB 实例 4 个 10GB 实例的组合小请求走小实例大请求走大实例。这个组合在资源利用率和性能隔离之间取得了不错的平衡。关键前提是调度器必须支持 MIG 实例的声明和资源上报——在 Kubernetes 场景下你可以用 NVIDIA 的 device plugin 的 MIG 模式它能自动把 MIG 实例注册为可调度的设备。6. 影响调度决策的关键数据速查Ampere 架构版为了让上面的讨论更容易落地我把几个核心指标整理成一张对照表。这里的数值以 A100GA100为例不同型号会有差异但量级关系是通用的。硬件维度典型数值对调度器的意义SM 数量108A100 80GB总计算槽位的上限决定最大并行 block 数单 SM 最大线程数2048任务 block 内线程数的设计参考寄存器文件256KB/SM限制每个 SM 同时容纳的 block 数Shared Memory164KB/SM影响 kernel 数据复用策略的选择L2 Cache40MBA100可预留Set-aside支持多任务缓存隔离HBM 带宽~2TB/s判断任务是否访存密集的基准PCIe Gen4 x16 带宽~32GB/s 单向CPU-GPU 数据传输的瓶颈点NVLink第三代50GB/s 每链路600GB/s 全卡多卡任务放置的亲和性依据MIG 切分粒度1g.5gb 到 7g.40bg 不等多租户隔离的最小分配单位这张表不是给你背参数的而是当你设计调度策略时拿这些数去估算你的决策边界。比如你猜一个任务在 A100 上跑 100ms如果它要从 CPU 传 1GB 数据进来PCIe 传输就要 32ms——直接占掉了近三分之一的时间预算。这种数量级的估算能力是调度系统设计的基本功。7. 一些容易被忽视的硬件细节与我的实践心得组原层面讲了这么多最后分享几个实操中容易踩坑的硬件细节。不是原理性的全是血泪教训。第一GPU 的功耗和散热会限制实际频率。A100 的 TDP 是 400W如果你把多张卡的 SM 都跑满整机功耗超过供电上限后GPU 会自动降频power capping。调度器如果不监控当前功耗状态单纯按SM 空闲来判断能不能塞新任务很可能导致所有卡一起降频整体吞吐反而下降。我在生产环境里遇到过两次这种情况后来加了功耗监控在调度决策中引入了一个降频惩罚因子效果立竿见影。第二nvidia-smi显示的显存使用量存在滞后性。CUDA 缓存机制导致显存不会立即释放——一个进程结束后部分显存页面会被驱动缓存供下一个进程复用。如果你用nvidia-smi -q -d MEMORY去量化剩余显存看到的值往往偏保守。精确的方式是用 NVMLNVIDIA Management Library的nvmlDeviceGetMemoryInfo配合nvidia-smi pmon看实时状态。第三PCIe 链路的降速问题。服务器经过长时间运行后PCIe 链路可能因为信号完整性退化从 Gen4 降到 Gen3 甚至更低。用lspci -vv能查当前链路速率。如果你发现某几张卡的数据传输性能突然变差先查这个不要一头扎进代码里优化。第四时钟频率和功耗的开关状态会影响 GPU 的响应延迟。默认情况下GPU 为了省电会进入低功耗状态任务到来时需要几百毫秒来提升频率到工作状态。对延迟敏感的场景比如实时推理一定要把 GPU 的性能模式设置为持续模式nvidia-smi -pm 1并锁定最大时钟频率nvidia-smi -lgc。这个操作在初始化阶段就做不要等流量来了再配。第五如果要大规模部署 GPU 调度强烈建议把 NVML 嵌入到你的监控体系里而不是依赖外部工具。NVML 提供的指标非常丰富温度、功耗、SM 占用率、显存带宽利用率、PCIe 吞吐、NVLink 错误计数等。调度器不仅要看资源够不够还要看资源用得好不好这些指标是持续调优的依据。这个系列的第一篇就到这里。硬件的结构决定调度的边界和策略把这一层吃透了后面讨论具体的调度算法、集群调度器设计、推理框架的 batch 策略时你会有完全不同的理解深度。下一篇我计划深入剖析 GPU 的计算流与调度器如何利用流并行来提高设备利用率尤其会讲清楚 OpenCL 和 CUDA 在调度模型上的关键差异——如果你在用跨平台异构计算框架这部分会很有参考价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价