想象你是一家超大餐厅的主厨,后厨里有几口顶级大铁锅——这就是GPU显卡。每口锅旁有巨大的食材储藏柜(显存)和几排灶眼(计算核心)。过去,一位客人点一份“佛跳墙”就能独占一整口锅、整个储藏柜,哪怕他只用到十分之一的灶眼,其他灶眼也只能干看着闲置。可餐厅每天要接成百上千份订单:有需要炖一整天的佛跳墙(大模型训练)、有只需三分钟的快手菜(小推理任务)、还有做完一道菜就要换菜单的私房定制(多租户服务)。如果每位客人都独占一口锅,再大的后厨也忙不过来,灶眼却大量空转。 聪明的餐厅老板想出了妙招:要么让几份快手菜按时间轮流用同一口锅(时间片共享),要么在物理上把大铁锅隔成几个独立小火灶(硬件切分),要么用一套精细的排班系统动态分配储藏柜和灶眼(调度器)。Slurm是那种“提前预约、排班管理”的排班主管,K8s则是“按需点餐、动态扩缩”的智能出餐系统。两者都在琢磨同一件事:如何把一口GPU大锅的资源——显存和算力——安全、高效地分给众多任务。这场“分餐大战”的实战智慧,正是本篇文章要带你一步步拆解的。
掌握GPU共享与切分,是从“会用GPU”迈向“会管GPU”的关键一步——无论做模型训练还是推理服务,你都能用最少的卡支撑最多的业务。
目录
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
GPU资源的“两面性”:显存与算力
回到我们那间忙碌的后厨。一口大铁锅(GPU)其实包含两类宝贵的资源:灶眼的数量和火力大小(算力),以及储藏柜的容积(显存)。灶眼决定了同时能翻炒多少食材——对应GPU的计算核心(CUDA Core)数量和频率;储藏柜决定了能存放多少食材——对应显卡上的显存容量。有趣的是,一口锅的灶眼和储藏柜往往不成正比:有的锅灶眼多但储藏柜小(算力型显卡),有的锅灶眼少但储藏柜很大(显存型显卡)。
当我们说“共享GPU”时,本质上是在回答两个问题:储藏柜的格子怎么分?灶眼的火力怎么分?一个训练大语言模型的任务,像是要同时炖几百斤高汤——它对储藏柜(显存)的需求极大,动辄几十GB,但真正炒菜时也许只用一半灶眼;而一个实时图像识别任务,则像是做一道讲究速度的炝炒时蔬——要大火快炒(高算力),但食材很少(显存需求低)。因此,“一刀切”的独占模式往往造成资源浪费:大显存任务占着锅,灶眼却闲置大半。
共享的第一步,就是识别任务的“食材需求”和“火候需求”。假设一口锅的总储藏容积为 \(M_{\text{total}}\),一道菜最少需要 \(M_{\text{job}}\) 的食材存储空间,那么这口锅最多能同时服务多少道这样的菜呢?
回到比喻里:一口储藏柜有80格,一道菜至少需要30格,那最多能同时做2道菜(80÷30=2.66,向下取整为2),剩下的20格只能闲置,这就是显存维度对共享数量的硬约束。而在算力维度上,只要任务的实际灶眼用量有富余,共享就能继续叠加——当然,这会把我们带到“轮流用锅”和“砌墙隔灶”的方案上去。
- 分子 \(M_{\text{total}}\)指GPU的物理显存总容量,相当于整个储藏柜的容积,单位是GB。
- 分母 \(M_{\text{job}}\)指单个任务运行所需的最小显存,相当于一道菜至少需要占用的储藏格。
- 向下取整 \(\lfloor \cdot \rfloor\)因为显存分配必须保证每个任务都放得下,多出来的零头无法再凑合一个新任务。
时间片调度:GPU的“轮流用锅”
后厨最朴素的分餐方案就是“排队等锅”:既然一口锅不能同时做所有菜,那就规定每道菜轮流用锅炒几分钟,由排班主管掐表切换。这就是时间片调度(Time-slicing)的核心思想——多个任务在极短的时间间隔内交替使用整个GPU。
在NVIDIA的驱动实现中,时间片调度器会让多个计算上下文(CUDA Context)共享一对GPU通道:任务A跑50微秒,切换到任务B跑50微秒,再切回任务A……由于切换频率非常高,从用户视角看,多个任务似乎“同时”在运行。这种共享方式的优点是实现简单、无需任何硬件改造,也不需要给每个任务划分固定资源边界;缺点是切换有代价,切换瞬间GPU的缓存、寄存器状态需要保存和恢复,就像厨师轮换时要洗锅、重新摆台一样,总会有几秒钟的空档。
时间片模型的算力分配非常直观:假设GPU的总算力为 \(P_{\text{total}}\),有 \(N\) 个任务在轮流使用,那么每个任务在理想情况下的等效算力为:
时间片调度的妙处在于“看不见的排班成本”:如果每个任务的GPU利用率不足10%,时间片共享几乎是无损的——因为切换空闲时段的代价极低;可如果多个任务都很“饥饿”(各自都想要100%算力),频繁切换就会导致缓存不断被冲刷、命中率骤降,这就是时间片共享的性能天花板。它适合那些本身算力需求不高、但对显存容量有硬性要求的任务——分不到灶眼的完整火力,但至少能把食材放进储藏柜。
- 总算力 \(P_{\text{total}}\)GPU所有计算核心的峰值算力之和,好比一整口锅满火力开足时的总功率。
- 共享任务数 \(N\)参与时间片轮转的任务个数,即排班表上等待用锅的菜品数量。
- 均分关系 \(\frac{P_{\text{total}}}{N}\)每个任务在宏观时间窗口内获得的等效算力,因为时间被切成了N份。
MIG硬件切分:后厨砌墙
时间片调度虽然灵活,但存在软肋:任务之间没有硬性隔离,一个任务崩溃可能拖累同锅的其他任务,就像一位厨师把锅烧糊了,整个后厨都得跟着呛。NVIDIA为高端GPU(如A100、H100)设计了MIG(Multi-Instance GPU)技术,相当于在物理层面给大铁锅砌上实墙,将一个GPU分割成最多7个独立的GPU实例。
MIG切分的核心特点是“硬件级隔离”。每个计算实例(Compute Instance)拥有专属的计算核心、显存带宽和L2缓存分区,以及独立的显存块——它们不再共享任何关键资源。换句话说,切分后的小实例在软硬件视角上就是一块独立的“小GPU”,有自己的显存空间和算力配额,任务完全感知不到其他实例的存在,隔离性比时间片共享强得多。
MIG实例的算力配额取决于显存切分的比例。设GPU总计算能力为 \(P_{\text{total}}\),某实例被分配的显存占比为 \(f_{\text{slice}}\)(例如将40GB显存从80GB中分出,\(f_{\text{slice}}=0.5\)),则该实例理论上获得的算力为:
MIG的最大价值是“确定性性能”:多租户场景下,互不信任的任务(比如两家公司的模型训练)可以安全地跑在同一块物理GPU上,彼此性能不受到干扰。但它也有代价——MIG的切分粒度是离散的(如1g.5gb、2g.10gb、7g.40gb等固定模板),不能随意微调;且一旦切分完毕,每个实例的资源就是钉死的,无法像时间片那样灵活弹性调度。这就像砌好的隔断墙不能随着客人多少随时拆改。
- 总算力 \(P_{\text{total}}\)物理GPU的最大计算能力,可以理解为整个后厨满负荷出菜的理论上限。
- 切分比例 \(f_{\text{slice}}\)该实例获得的显存占物理GPU显存的比例,也就是“这面墙隔出多大的厨房”。
- 实例算力 \(P_{\text{inst}}\)隔离后小厨房的理论出菜能力,与占用的显存比例严格挂钩。
Slurm共享实现:学校的场馆预约系统
Slurm是超算和数据中心最常用的作业调度系统。它就像大学图书馆的场馆预约系统:用户先提交作业申请(申请单),说明要用几口锅(GPU数量)、多大储藏柜(显存)、什么时段(时间限制),然后由Slurm的调度器根据各“场馆”的空闲情况统一排班。
要让Slurm支持GPU共享,最关键的是配置GRES(Generic Resource)插件。管理员需要在slurm.conf中声明节点的GPU资源类型,并在提交作业时用--gres=gpu:2声明需要几块GPU。然而,传统的Slurm只以“整卡”为粒度分配GPU,一台GPU被分配给一个作业后就完全独占。要实现“一卡多作业”,需要借助额外的机制:一种是使用cgroup将GPU显存和计算资源做限额隔离,另一种是让多个作业通过设置CUDA_VISIBLE_DEVICES共享同一块物理卡(配合MIG使用)。
实际共享时,系统需要量化资源利用率。设某GPU在时间窗口 \(T_{\text{total}}\) 内实际有计算任务的时间为 \(T_{\text{busy}}\),则该GPU的利用率:
Slurm的优势在于“成熟的排队与优先级策略”:它支持多队列(对应用户或项目)、优先级抢占、关联资源限制等高级特性,特别适合离线批处理场景——提交训练任务后排队等待分配。但Slurm对GPU共享的细粒度控制相对粗糙:如果不用MIG,它很难精确限制某个作业的显存上限;如果不用类似MPS(多进程服务)的插件,计算资源共享也基本靠“自觉”。
- 忙碌时间 \(T_{\text{busy}}\)GPU实际执行算子(Kernel)的累计时长,可以理解为灶台真正开火的时间。
- 总时间 \(T_{\text{total}}\)观察窗口的总时长,比如一小时、一天。
- 利用率 \(U_{\text{gpu}}\)两者之比,衡量GPU的“空闲程度”,是调度器决定能否接纳更多共享作业的重要依据。
K8s共享实现:按需点餐的容器调度
如果说Slurm是“提前预约的场馆”,Kubernetes(K8s)就是“按需点餐的智能餐厅”:用户提交一个Pod(相当于点一份菜),K8s的调度器(kube-scheduler)在毫秒级内找到合适的“餐桌”(节点)把它安排下去。它天生面向容器化的微服务,特别适合在线推理和弹性训练场景。
K8s共享GPU的起点是DevicePlugin机制:NVIDIA提供的device-plugin守护进程定期向kubelet报告节点上的GPU资源数量,使之成为可调度的扩展资源nvidia.com/gpu。这样一来,Pod在YAML中声明resources.limits: nvidia.com/gpu: 1,调度器就会把Pod调度到有空闲GPU的节点上。但这个机制只解决“整卡分配”,不支持一块卡分给多个Pod。
要支持共享,社区衍生出两条路:一是时间片共享,通过修改device-plugin将一块卡虚拟成多个资源单位(例如1卡虚拟为10个nvidia.com/gpu单位),多个Pod轮流使用;二是显存隔离式共享(vGPU方案),类似给每个Pod分配独立的显存上限和算力配额。调度器在决定Pod放到哪块GPU上时,需要综合评分。设候选GPU的显存剩余率为 \(U_{\text{mem}}\)、与已调度Pod的亲和度评分为 \(A_{\text{share}}\),则综合调度得分为:
K8s的GPU共享方案生态丰富:从开源的时间片虚拟化(如NVIDIA的time-slicing device plugin)到商业化的vGPU方案(如阿里云的cGPU、腾讯的vCUDA),都试图在“共享”和“隔离”之间找到平衡点。与Slurm相比,K8s更强于弹性伸缩:推理服务高峰时自动扩展Pod数量,低谷时自动缩容,GPU资源随业务潮汐动态调配。但牺牲的是排队与优先级管理的能力——K8s的调度器对“优先级队列”的支持不如Slurm成熟。
- 显存剩余率 \(U_{\text{mem}}\)候选GPU剩余显存占总显存的比例,表示这个“储藏柜”还有多少格子可放新食材。
- 亲和度 \(A_{\text{share}}\)衡量与该GPU上已有任务共存时的干扰风险(如缓存争抢程度),是经验性的评分项。
- 权重 \(w_1, w_2\)管理员根据集群策略设定的调节系数,可以偏向“多塞任务”还是“减少干扰”。
性能开销与隔离性:共享的代价
天下没有免费的午餐。GPU共享带来了高利用率,同时也不可避免地引入性能开销。我们可以把这些开销分成两类:计算干扰和显存争抢。
计算干扰主要来自缓存竞争。现代GPU的L2缓存是多个计算核心共享的,当一个任务频繁读写的数据恰好被另一个任务挤占出缓存时,性能就会显著下降。这就像两位厨师共用一个备料台,一个人堆满食材,另一个人找东西就要翻半天。显存争抢则是带宽层面的冲突:多个任务同时从显存搬运数据,总线带宽被瓜分殆尽。一个显存密集型任务遇到另一个同样密集的任务,两个任务的有效吞吐率可能都下降30%以上。
不同方案的隔离强度差异很大。时间片共享的隔离强度最弱——任务之间只隔着一道“屏风”;MIG的隔离强度最高——任务之间隔着实墙;而vGPU/显存限额方案居中。衡量共享效率时,可以这样建模:假设有 \(N\) 个任务并行共享,每个任务获得的等效算力为 \(P_{\text{eff}}\),而GPU总算力为 \(P_{\text{total}}\),共享带来的额外开销因子为 \(\alpha\)(由缓存污染、带宽争抢等引起),则系统总效率为:
理解这个公式,就理解了共享调度的本质:它是一个“多快好省”的博弈。共享可以让 \(N\) 个任务同时获得资源,提高硬件利用率,但 \(\alpha\) 的膨胀又会吞噬部分收益。调度的艺术,就是在 \(N\) 和 \(\alpha\) 之间找到平衡点——让足够多的任务共享,又不至于让干扰失控。实践中,通过监控GPU利用率、缓存命中率、内存带宽压力,可以量化 \(\alpha\),并据此动态调整共享策略。
- 并行任务数 \(N\)同一物理GPU上同时运行的任务数量。
- 单任务等效算力 \(P_{\text{eff}}\)每个任务实际拿到的计算能力,通常小于独占时的峰值。
- 干扰开销因子 \(\alpha\)缓存污染和带宽争抢导致的无谓开销比例,越大说明共享环境越“拥挤”。
- 总效率 \(E_{\text{total}}\)共享后系统整体的有效产出除以理论最大产出,理想值为1,实际总小于1。
方案对比与选型:挑哪个“分餐方案”
后厨的分餐方式已经积累了三种主流方案:独占(整卡)、时间片共享、MIG切分,外加K8s生态下的vGPU显存方案。选型时,最先要回答的问题是:你更看重隔离性,还是更看重资源利用率?
独占方案的隔离性满分——任务完全不共享资源,性能可预测性最好,适合对性能敏感的SLA服务(如核心在线推理、模型压测),但利用率最低。时间片共享的利用率最高——不需要任何硬性分区,任务空闲的资源自动释放给其他任务使用,适合任务并发多但单个任务资源需求不高的场景(如大量小模型批量推理、数据处理管道),但性能波动大。MIG切分是折中——硬件隔离保障了性能确定性,切分粒度固定但灵活度不如时间片,适合多租户场景(如将一台A100分割给三个团队各自使用)。
成本与收益的权衡可以这样简化:一张A100的价格是固定的,独占意味着一个业务独享全部性能,但若它的实际利用率只有30%,那70%的投资就打了水漂。时间片共享可以让这张卡服务5个业务,但这5个业务各自能用的算力约为原来的20%——总产出是100%但被干扰打了折扣。MIG让3个业务各自拥有隔离的30%性能,总产出90%但稳定可控。
选型建议的黄金法则是“按任务画像匹配”:大模型训练任务(长时间、高资源消耗、对性能波动敏感)适合独占或大MIG实例;在线推理任务(短小、高频、并发多)适合时间片或小MIG实例批量部署;多团队共享一张卡时优先MIG保证公平;而K8s环境中的突发业务(高峰扩缩容)适合时间片共享的弹性机制。
工程实践与调优:管理“分餐”的实战手册
最后,让我们站在运维工程师的视角,看看GPU共享落地时需要的实战技能。
选型依据:首先要盘点业务的任务类型、显存/算力需求、QoS(服务质量)要求,然后决定共享方案。一个实用的决策树是:有强隔离需求(如不同客户的任务)→ MIG;无隔离需求但任务很多 → 时间片/vGPU;任务虽少但单个需求极大 → 独占。Slurm环境优先考虑GRES+MIG或cgroup方案,K8s环境优先考虑device-plugin扩展资源配合时间片/vGPU插件。
监控与诊断:共享后必须建立监控体系,核心指标包括:GPU计算利用率(nvidia-smi的utilization.gpu)、显存使用率、显存带宽利用率、GPU缓存命中率(可通过ncu或nvprof分析)、SM活跃度等。如果发现某个任务的性能与其他任务共享后显著下降,就要检查是否发生了L2缓存或带宽争抢,及时调整任务组合或改用隔离性更强的方案。
典型故障:共享场景最常见的故障是显存OOM——多个任务挤在一起显存超卖,需要配置好显存上限(如cgroup memory限额或MIG的显存边界)。其次是“算力饿死”:某些任务被优先级更高的任务持续抢占,导致前者的算力接近零——需要合理设置优先级和配额。还有一种隐蔽故障是版本兼容性:MIG模式要求NVIDIA驱动与容器运行时(如CUDA 11+)的特定版本支持,版本不匹配会导致实例创建失败。
最佳实践建议:
1. 混合调度:独占与共享并存,关键业务独占、弹性业务共享,避免全集群单一策略。
2. 精细化配额:每个共享任务都设置显存上限和算力上限,防止“一颗老鼠屎坏一锅汤”。
3. 动态调整:定期观察利用率报表,利用率长期低于50%的卡降低共享粒度,长期高于90%且延迟恶化则增加隔离。
4. 自动化运维:在K8s中结合HPA(水平自动扩缩)和GPU共享插件,让资源随流量潮汐自动伸缩。
AI 应用场景
云端推理服务的GPU池化
阶段:模型部署与推理服务
解决的问题:传统的GPU部署方式下,每个模型实例独占一块GPU,而实际推理任务往往只占用GPU不到30%的算力和少量显存。大量资源被闲置,导致推理服务成本居高不下。通过K8s的GPU时间片共享或vGPU方案,多个推理Pod可以共享同一块物理GPU,将单卡支撑的并发请求数提升3-5倍,并且在推理高峰期自动扩容Pod、低谷期自动缩容,极大降低GPU采购成本。
具体案例:某AI公司使用K8s部署了50个基于GPT风格的文本生成模型服务,原先需要50块GPU。采用时间片共享后,将模型按负载组合到20块GPU上,通过nvidia.com/gpu扩展资源声明共享配额,并配置HPA根据请求数自动伸缩。改造后GPU利用率从25%提升到75%,推理P95延迟仅增加了8%,但GPU采购成本下降了60%。
高校GPU集群的多租户MIG切分
阶段:资源调度与管理
解决的问题:高校GPU集群需要同时服务多个课题组,但不同课题组的训练任务相互不信任,且单个任务显存需求各异。Slurm加上MIG切分可以实现硬件级隔离:将一块A100切分为7个实例,按课题组分配,每个组拥有独立的显存和算力边界,互不干扰。相比给每个组单独买一块GPU,成本大幅降低;相比时间片共享,性能稳定性大幅提升。
具体案例:某高校计算中心的A100节点部署了Slurm,配置MIG模式后将每张卡切分为3个实例(每个约26GB显存)。不同课题组通过--gres=gpu:mig:1申请实例,管理员通过QOS(服务质量)限制每个组最多同时使用4个实例。一个学期下来,同一批GPU服务的课题组数量从8个增加到21个,训练任务的平均排队时间反而缩短了40%。
大模型训练与数据预处理混合调度
阶段:训练与数据处理并行
解决的问题:大模型训练任务(如预训练)通常需要独占整卡以保证稳定的训练吞吐,而数据预处理任务(如tokenize、图像增强)只用到少量显存和算力。通过Slurm的cgroup与GPU共享,可以让预处理任务“挤”进训练任务的空闲显存空间,同时限制其算力占比以保证训练性能不受影响。
具体案例:某公司的训练集群中,8卡A100正在跑一个70B模型的全参数微调,显存占用了每卡约60GB,但算力利用率只有70%左右。运维团队在Slurm中配置了cgroup显存限额和算力权重,将数据预处理任务调度到这些卡的剩余显存中。训练吞吐量只下降了3%,但此前需要单独两台CPU机器跑两天的数据预处理任务,现在一天内就完成了。
Python 演示
class GpuTask: def __init__(self, name, memory, compute): self.name = name self.memory = memory # 显存需求 GB self.compute = compute # 算力需求 TFLOPS class GpuScheduler: def __init__(self, total_memory=80, total_compute=100): self.total_memory = total_memory self.total_compute = total_compute self.tasks = [] self.mode = "mig" # mig / timeslice / exclusive def commit(self, task): """提交任务,检查显存与算力是否可容纳""" if task.memory > self.total_memory or task.compute > self.total_compute: print("[拒绝] {} 超出GPU能力".format(task.name)) return False free_mem = self.total_memory - sum(t.memory for t in self.tasks) if free_mem >= task.memory: self.tasks.append(task) print("[分配] {} 占用 {:.0f}GB 显存".format(task.name, task.memory)) return True print("[排队] {} 因显存不足等待".format(task.name)) return False def utilization(self): """返回 (显存利用率, 算力利用率)""" mem_used = sum(t.memory for t in self.tasks) comp_used = sum(t.compute for t in self.tasks) return mem_used / self.total_memory, comp_used / self.total_compute def timeslice_schedule(self, slice_time=0.5): """模拟时间片轮转调度""" print("--- 时间片调度 ---") for i, task in enumerate(self.tasks): print("[时间片] {} 获得 {:.1f} 单位时间".format(task.name, slice_time)) def mig_slice(self): """按显存比例模拟MIG切分""" print("--- MIG切分 ---") total_req = sum(t.memory for t in self.tasks) or 1 for task in self.tasks: ratio = task.memory / total_req alloc = self.total_compute * ratio print("[MIG] {} 获得 {:.1%} 算力 ({:.1f} TFLOPS)".format( task.name, ratio, alloc)) if task.compute > alloc: print(" [警告] {} 算力需求超出MIG配额".format(task.name)) if __name__ == "__main__": scheduler = GpuScheduler(total_memory=80, total_compute=100) tasks = [ GpuTask("大模型推理", memory=40, compute=50), GpuTask("图像分类", memory=20, compute=25), GpuTask("语音识别", memory=15, compute=20), ] for t in tasks: scheduler.commit(t) mem_u, comp_u = scheduler.utilization() print("显存利用率: {:.0%}, 算力利用率: {:.0%}".format(mem_u, comp_u)) scheduler.timeslice_schedule() scheduler.mig_slice()
class GpuTask定义任务的数据结构,包含任务名、显存需求和算力需求,类比后厨中一道菜需要的储藏格与灶眼火力。class GpuScheduler调度器主类,管理总资源与已提交任务列表,类似排班主管记录每口锅的占用情况。scheduler.commit(task)检查任务是否超出GPU容量,并统计显存是否足够,不够则排队等待。free_mem = self.total_memory - sum(t.memory for t in self.tasks)计算剩余显存,即储藏柜还有多少空位可以放新食材。return mem_used / self.total_memory, comp_used / self.total_compute分别返回显存和算力的利用率,对应监控面板上的两个核心指标。def timeslice_schedule(self, slice_time=0.5)模拟时间片轮转,每个任务获得等长的GPU使用时段。ratio = task.memory / total_req计算MIG切分时该任务应得的显存占比,进而推导其算力配额。if task.compute > alloc检查任务的算力需求是否超出了MIG配额,若超出则给出警告,提示该任务不适合MIG的固定比例分配。
常见误区
误区只要GPU显存能装下多个任务的数据,就可以放心地把GPU切分成任意多个部分供大家一起使用,反正显存管够。
正解显存只是资源的一个维度,计算核心、显存带宽、L2缓存同样会被共享。即使显存很充裕,如果多个任务都是计算密集型,它们会互相争抢计算核心和缓存资源,导致各自性能大幅下降。就像储藏柜再大,灶眼不够用,菜也炒不出来。切分前必须同时预估显存和算力两个维度,并给每个任务设置算力上限。
为何常见:初学GPU时,人们最先看到的指标是显存(nvidia-smi的Memory-Usage),因为它在界面上一目了然,而算力和带宽的消耗是隐性的、需要专业工具才能观察,于是“显存=全部资源”的错觉就形成了。
误区多个任务轮流用GPU,反正切换那么快,每个任务都能获得完整的GPU性能,只是稍微慢一点点而已。
正解时间片共享有明显的上下文切换开销,且任务之间的缓存污染是持续的——任务A刚把数据载入L2缓存,切到任务B后很快又被冲掉了。当多个任务都处于高负载时,每个任务的实际性能可能只有独占时的50%-70%,而不是简单的1/N。这就像几位厨师共用一个灶台,每次轮换都要洗锅、重新备料,真正炒菜的时间被切掉了一大截。
为何常见:在任务负载都比较低的时候,时间片共享确实几乎无感,人们容易把“低负载下的顺滑体验”外推到高负载场景,忽视任务增多后干扰加剧的非线性效应。
误区MIG(多实例GPU)跟K8s里的vGPU一样,都是在软件上把一个GPU模拟成多个小GPU,本质上都是同一个GPU在分时工作。
正解MIG是硬件级的切分——切分后每个实例拥有独立的计算核心、显存通道、L2缓存和显存颗粒隔离,它们在物理上就是互相独立的资源单元,任务之间完全无法感知彼此的存在。而vGPU/时间片是软件级的虚拟化,底层仍然是所有任务共享同一组计算核心和缓存,只是在配额和调度策略上做了隔离。可以理解为:MIG砌的是承重墙,vGPU挂的只是活动隔断。
为何常见:从用户视角看,MIG和vGPU都让一块GPU“看起来像”多块GPU,功能相似,容易忽略底层物理隔离的差异。只有遇到性能干扰或硬件故障隔离时,两者的本质区别才真正显现。
误区Slurm只能按整卡分配GPU,无法实现一卡多任务共享,GPU共享是K8s的专属能力。
正解Slurm同样支持GPU共享。一方面可以通过MIG创建实例,让Slurm把每个MIG实例当成一个可调度的“GPU“来分配;另一方面也可以通过cgroup和MPS(CUDA多进程服务)来限制显存与算力,使多个作业共享同一物理卡。区别只在于Slurm的共享配置更繁琐、对在线弹性扩缩容的支持不如K8s,但”不能共享”的说法是不准确的。
为何常见:很多教程展示Slurm时,用的都是--gres=gpu:1这样的整卡申请示例,很少深入讲解GRES与MIG的组合,容易让人以为Slurm的能力边界就在整卡。
误区相同物理GPU上同时跑的任务越多,GPU就越不容易空闲,所以利用率一定会单调上升。
正解共享任务过多会带来严重的资源争抢,导致每个任务执行得极慢、频繁阻塞,甚至出现显存抖动和任务超时。更严重的是,过度共享增加了调度器的负担和任务间的干扰,实际有效吞吐可能不升反降。利用率的正确优化方向是:先让每个任务吃饱(算力与显存匹配),再通过监控找到“收益递减点”,控制在最优并发数,而不是一味堆任务。
为何常见:人们在日常经验中习惯了“人多力量大”的直觉,忽略了GPU硬件资源的强竞争特性。只有当任务间资源互补(一个吃显存、一个吃算力)时,共享的收益才会明显,而盲目塞任务往往适得其反。
面试题
Q1. GPU资源中“显存”和“算力”之间的关系类似于以下哪项?
- A.锅的容积与灶眼的火力← 正确答案
- B.锅的价格与锅的品牌
- C.厨房的电费与水费
- D.菜单的数量与厨师的人数
答案:A。显存对应储藏柜/锅的容积,决定能装多少数据;算力对应灶眼火力,决定计算速度。两者独立又可互相制约,共享时必须同时考虑。选项B、C、D都不构成GPU计算的两类核心资源维度。
Q2. 时间片共享GPU时,N个任务轮流使用,理想情况下每个任务获得的等效算力为?
- A.P/N← 正确答案
- B.P×N
- C.P+1
- D.N/P
答案:A。时间片轮流使用意味着每个任务在总时间中只占用1/N的份额,所以等效算力是总算力P除以N。P×N是错误的放大,P+1和N/P是误导选项。实际中还会因上下文切换产生额外开销。
Q3. 以下哪种方式属于GPU的硬件级切分?
- A.MIG多实例GPU← 正确答案
- B.CUDA时间片调度
- C.K8s虚拟GPU插件
- D.Docker容器限额
答案:A。MIG通过硬件将GPU切割为多个拥有独立显存、计算核心和缓存的实例,是物理隔离。CUDA时间片、K8s vGPU和Docker限额都属于软件层的资源限制,核心硬件仍是共享的。
Q4. 下列哪些是时间片共享GPU可能带来的性能开销?
- A.上下文切换消耗← 正确答案
- B.L2缓存被冲刷污染← 正确答案
- C.显存容量物理减半
- D.计算Kernel排队等待← 正确答案
答案:ABD。时间片共享中,任务切换需要保存/恢复GPU状态(上下文切换开销),频繁切换会把对方刚写入L2缓存的数据冲刷掉(缓存污染),多个任务在同一个SM上排队也会增加等待。显存容量没有物理减少,选项C错误。
Q5. Slurm中通过GRES机制可以完成什么操作?
- A.声明并管理GPU等通用资源← 正确答案
- B.加速GPU的矩阵运算
- C.代替CUDA进行编译
- D.直接修改显存频率
答案:A。GRES(Generic Resource)是Slurm对GPU等通用资源的管理框架,管理员在节点上声明GPU资源类型,用户在作业提交时用--gres指定GPU数量,调度器据此完成分配。其他选项与GRES的职责无关。
Q6. 在K8s中实现GPU共享可以使用哪些方式?
- A.DevicePlugin上报扩展资源← 正确答案
- B.时间片设备插件虚拟化← 正确答案
- C.vGPU显存隔离方案← 正确答案
- D.修改NodePort端口
答案:ABC。K8s通过DevicePlugin让GPU成为可调度资源,时间片插件将1块卡虚拟为多个资源单位,vGPU方案实现显存与算力隔离。NodePort是K8s的服务暴露方式,与GPU共享无关。
Q7. MIG切分时,实例算力分配依据是什么?
- A.显存占比← 正确答案
- B.网络带宽
- C.CPU核数
- D.任务优先级
答案:A。MIG根据显存切分比例分配算力,公式为 P_inst = P_total × f_slice。显存分得多,算力配额也多。网络带宽、CPU核数不是MIG算力分配的依据,任务优先级是调度策略层面的考量。
Q8. GPU共享时“干扰开销因子α”增大的主要原因是什么?
- A.L2缓存争抢与显存带宽竞争← 正确答案
- B.GPU风扇转速下降
- C.任务代码中注释过多
- D.磁盘可用空间不足
答案:A。共享环境下任务间的缓存争抢会导致命中率下降,显存带宽被多个任务瓜分导致数据传输变慢,这两者共同构成干扰开销。风扇、注释和磁盘空间都与GPU共享的运行时干扰无关。
Q9. 下列哪些属于Slurm与K8s在GPU调度上的差异?
- A.Slurm偏离线批处理,K8s偏在线弹性服务← 正确答案
- B.Slurm基于队列与优先级,K8s基于Pod与自动伸缩← 正确答案
- C.Slurm支持MIG实例调度,K8s也支持← 正确答案
- D.Slurm完全不支持GPU共享
答案:ABC。Slurm适合大批量离线作业和成熟的优先级排队,K8s适合容器化的在线推理和弹性伸缩;两者生态都支持MIG。D选项错误,Slurm通过MIG和cgroup也能实现GPU共享。
Q10. 大规模并发共享GPU时,性能急剧下降的根源通常是什么?
- A.资源竞争与级联干扰← 正确答案
- B.显卡电源线松动
- C.CUDA驱动版本过低
- D.显存颗粒老化
答案:A。当大量任务共享GPU时,L2缓存命中率急剧下降、显存带宽饱和、调度排队加剧,形成“用户等待→任务超时→重试→更多竞争”的级联效应,这是性能雪崩的根本原因。驱动、电源、显存老化属于硬件或环境问题,并非大规模并发共享特有的现象。