背景:GPU 利用率高,不等于能效合理
大模型推理上线后,团队通常先观察 GPU 利用率、显存占用、Token 吞吐和延迟。当机房功率或成本成为约束时,最直觉的动作是降低 GPU 的 Power Cap。这个方法对部分高计算强度任务有效,但不能被当成所有推理阶段的通用答案。
原因在于一次请求至少包含两个性质不同的阶段:
| 阶段 | 特征 | 瓶颈倾向 |
|---|---|---|
| Prefill | 并行处理输入 Token,包含较大矩阵运算 | 计算受限,易靠近功率上限 |
| Decode | 每步只生成少量 Token,频繁读取权重与 KV 数据 | 显存带宽受限,功耗常低于上限 |
2026 年一项针对 NVIDIA H200、约 4B 参数模型和多种注意力架构的研究发现,在其测试范围内,Decode 实际功耗常低于所设置的最低 Power Cap,因此限制并未真正触发;直接控制 SM Clock 反而形成了更清晰的能耗—吞吐前沿。这个结论不能无条件外推到所有 GPU 和模型,但它揭示了一个重要工程原则:先判断瓶颈,再选择能耗控制手段。
核心原理:Power Cap 是上限,Clock Lock 才是明确运行点
Power Cap 只在实际功耗触顶时生效
nvidia-smi --power-limit 设置的是板卡允许使用的最大功率。若工作负载实际只消耗 220W,即使上限从 700W 调到 400W,也不会自动把功耗压到 400W 以下。只有当实际需求试图超过限制时,功率管理算法才会调整性能状态。
因此,评估 Power Cap 是否有效时,不能只记录”配置值”,还必须记录:
- 实际平均与瞬时功耗;
- 请求与实际 GPU Clock;
- Clock Event Reasons;
- Prefill 与 Decode 各自的吞吐和能耗。
NVIDIA 文档将 SW Power Cap、HW Thermal Slowdown、SW Thermal Slowdown 等列为导致频率下降的事件原因。若只看到 Clock 下降,却不保存事件原因,就可能把散热问题误判为节能策略生效。
Clock Lock 提供可重复的频率控制
在支持的 GPU 上,可以使用 --lock-gpu-clocks 设置 GPU 时钟范围。它比 Power Cap 更接近”指定运行点”,但仍要验证请求频率是否等于实际频率,因为硬件、固件和驱动可能对请求值进行限制或取最近支持值。
# 查询当前功率、时钟、温度与事件原因
nvidia-smi --query-gpu=uuid,power.draw.average,power.limit,clocks.current.sm,temperature.gpu,clocks_event_reasons.active \
--format=csv
# 示例:锁定一个经过压测验证的 SM Clock
sudo nvidia-smi -i 0 --lock-gpu-clocks=<validated_clock_mhz>
# 回滚到默认时钟
sudo nvidia-smi -i 0 --reset-gpu-clocks
这里的 <validated_clock_mhz> 不能从其他 GPU 型号照抄,也不应直接使用论文中的数值。它必须来自当前硬件、模型、驱动和推理引擎组合的实际压测。
用 Joules/Token 衡量,而不是只看 Watts
瞬时功率低不一定更节能。若降频导致请求运行时间显著变长,总能耗反而可能升高。更合适的基本指标是:
request_energy_joules = ∫ power_watts(t) dt
joules_per_output_token = request_energy_joules / output_tokens
tokens_per_joule = output_tokens / request_energy_joules
生产环境还应同时约束:
- TTFT:首 Token 延迟;
- TPOT / ITL:相邻输出 Token 延迟;
- P95/P99 请求延迟;
- Token 吞吐和请求 Goodput;
- 错误率、超时率与质量门禁。
能效策略的目标不是得到最低瓦数,而是在满足质量和 SLO 的条件下,找到更优的 Tokens/Joule。
工程落地:建立分阶段、分桶的能效控制面
第一步:冻结工作负载指纹
每次能效压测都应绑定不可变指纹,至少包含:
| 指纹维度 | 示例内容 |
|---|---|
| 模型 | 名称、权重修订版本、量化格式 |
| GPU | SKU、驱动、CUDA、固件、BIOS 版本 |
| 推理引擎 | 版本、Attention Backend、Kernel 配置 |
| 并行策略 | Tensor/Pipeline Parallel 配置 |
| 负载 | 输入长度、输出长度、Batch Size、并发 |
| 控制参数 | Power Limit、请求 Clock、实际 Clock |
| 环境 | 温度、风扇或冷却状态、Clock Event Reasons |
没有这个指纹,同一个”Clock=某值”的结果无法复现,也不能安全迁移到其他节点池。
第二步:把 Prefill 与 Decode 分开测量
不要只跑一个混合流量压测并输出平均功耗。至少应建立以下矩阵:
| 维度 | 建议分桶 |
|---|---|
| 阶段 | Prefill、Decode、端到端 |
| 输入长度 | 短、中、长上下文 |
| 输出长度 | 短回答、长生成 |
| Batch/并发 | 低、中、高 |
| 控制变量 | 默认、不同 Power Cap、不同 SM Clock |
| 结果 | TTFT、TPOT、tok/s、J/token、事件原因 |
Prefill 可能因降低时钟而明显损失 TTFT,Decode 则可能在某一频率以上几乎不再获得吞吐收益。最终策略应允许两个阶段使用不同配置;若系统没有物理分离 Prefill/Decode,也可按请求类型或负载桶选择保守档位。
第三步:通过 DCGM 建立统一遥测
DCGM Exporter 可以向 Prometheus 暴露 GPU 温度、功耗、总能耗、利用率和错误等指标。关键指标包括:
DCGM_FI_DEV_GPU_TEMPDCGM_FI_DEV_POWER_USAGEDCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION- GPU 利用率、显存占用与 XID 错误
仅有节点级功耗还不够。推理网关或 Serving Worker 还需记录请求的输入/输出 Token、阶段耗时和活动 GPU UUID,才能把设备遥测与请求统计对齐。
一个可行的聚合方式是按固定时间窗计算:
window_energy = energy_counter_end - energy_counter_start
window_output_tokens = Σ completed_output_tokens
window_joules_per_token = window_energy / window_output_tokens
多租户或 Continuous Batching 环境中的请求级能耗归因通常是近似值,应明确标记归因方法,避免把节点总能耗伪装成精确账单。
第四步:生成 SLO 约束下的 Pareto 前沿
对每个工作负载桶,绘制能效前沿图:
- 横轴:Token 吞吐或 P95 TPOT;
- 纵轴:Tokens/Joule 或 Joules/Token;
- 数据点:不同 Power Cap 与 Clock 组合。
只有同时满足质量和延迟门禁的点,才进入候选集合。然后选择位于 Pareto 前沿、且离热降频区域有足够余量的运行点。
建议按以下结构保存策略:
policy_id: h200-model-a-decode-medium-batch-v3
fingerprint:
gpu_sku: H200
model_revision: <immutable_revision>
serving_engine: <version>
workload_bucket: decode-medium-batch
controls:
power_limit_watts: <validated_limit>
sm_clock_mhz: <validated_clock>
guards:
max_p95_tpot_ms: <slo>
max_thermal_event_rate: <threshold>
min_tokens_per_joule: <baseline>
rollback:
reset_gpu_clocks: true
restore_default_power_limit: true
数值应由本环境压测生成,而不是写死在通用模板里。
第五步:把热降频作为发布门禁,而不是普通告警
温度高并不等同于已经降频,真正需要关注的是温度、Clock、功耗与事件原因的组合。至少建立以下门禁:
- 压测期间出现 HW Thermal Slowdown 或持续 SW Thermal Slowdown,候选策略不得发布。
- 实际 Clock 与目标 Clock 长时间偏离,必须标记硬件或固件不一致。
- 相同配置下节点间 Tokens/Joule 明显分叉,先排查散热、供电和驱动,不直接放宽阈值。
- Clock Event Reasons 无法采集时,能效实验只能作为探索数据,不能作为自动发布依据。
第六步:灰度和自动回滚
时钟和功率配置属于基础设施变更,应通过专用节点池或维护控制面执行,而不是让业务 Pod 任意调用特权命令。
推荐发布流程:
- 单卡离线基准;
- 节点级 1% 灰度;
- 按模型和长度桶扩大流量;
- 比较 TTFT、TPOT、Tokens/Joule、超时率和热事件;
- 任一 SLO 或硬件门禁失败,重置 Clock 和 Power Limit;
- 回滚后重新验证实际值已恢复,而不是只检查命令返回码。
适用场景
该方法更适合:
- GPU 电力配额或机架功率密度受限的推理集群;
- 长时间稳定运行、流量形态较可预测的在线 Serving;
- Prefill 与 Decode 可独立调度或具有明确流量桶的系统;
- 需要比较不同 GPU、模型或 Kernel 的能效表现;
- 希望把能耗纳入容量和成本治理,但不接受明显延迟退化的团队。
不适合直接套用的场景包括:流量极端稀疏、GPU 经常空闲、硬件遥测不完整、节点散热状态不稳定,或者模型和内核版本频繁变化但没有配置指纹。
常见误区
误区一:Power Cap 越低,能耗一定越低。 Power Cap 只有在实际功耗触顶时才起作用;即使功率下降,也可能因为运行时间增加而提高总 Joules。
误区二:看到 GPU Utilization 接近 100% 就认为计算受限。 利用率指标表示引擎在采样周期内是否活跃,不直接说明瓶颈位于计算、HBM、互联还是 Kernel Launch。能效调优仍需结合吞吐对 Clock 的敏感性和性能计数器。
误区三:只测端到端平均值。 平均值会掩盖 Prefill 与 Decode 的差异,也会把短请求和长请求混在一起。最终策略必须按阶段和工作负载桶解释。
误区四:只记录请求 Clock,不记录实际 Clock。 驱动可能选择最近支持频率,固件也可能限制持续频率。没有实际 Clock 和事件原因,实验结果无法审计。
误区五:把节点能耗精确分摊到每个请求。 Continuous Batching、共享 GPU 和后台系统功耗使请求级归因天然存在误差。应公开分摊方法和误差边界,计费前还需额外校准。
上线检查清单
- 最近 30 天文章主题已完成 exact、near 与 family duplicate 检查。
- 模型、GPU、驱动、推理引擎和 Kernel 配置已形成不可变指纹。
- Prefill、Decode 与端到端分别完成长度和 Batch 分桶压测。
- 同时记录 Power Limit、实际功耗、请求/实际 Clock、温度和 Clock Event Reasons。
- 使用 Joules/Token 或 Tokens/Joule,而不是只比较 Watts。
- 候选运行点满足 TTFT、TPOT、P95/P99、错误率和质量门禁。
- 已确认不存在持续的功率或热降频事件。
- 时钟和功率修改由受控基础设施组件执行,业务容器无直接特权。
- 灰度策略按 GPU SKU、模型版本和工作负载桶隔离。
- 回滚会恢复默认 Clock/Power Limit,并验证实际状态。
延伸 FAQ
如何判断 Decode 是否适合降低 SM Clock? 观察不同 Clock 下的 TPOT、Token 吞吐和实际功耗。如果降低 Clock 后吞吐基本不变、功耗下降且没有错误或热事件,说明当前桶可能不是计算受限。结论只对该模型、Batch、上下文和 GPU 指纹有效。
DCGM 的瞬时功率足够计算请求能耗吗? 对持续时间足够长的窗口,可以通过功率积分或硬件能量计数器估算。对很短的请求,采样频率、计数器粒度和共享负载会带来较大误差,更适合按时间窗或批次聚合,而不是声称精确到单请求。
参考资料
- NVIDIA System Management Interface Documentation
- NVIDIA NVML API Reference Guide
- NVIDIA DCGM Exporter Documentation
- NVIDIA DCGM Feature Overview
- The Illusion of Power Capping in LLM Decode: A Phase-Aware Energy Characterisation Across Attention Architectures
- Position: LLM Inference Should Be Evaluated as Energy-to-Token Production