3. AI任务性能度量体系

在 Databricks 上优化 AI 任务,第一步不是调参,而是建立一套能“看见问题”的度量体系。AI 负载与普通数据任务最大的区别在于:它同时受计算、显存、数据供给和调度四类资源约束,任何一环成为瓶颈,整体性能都会被拖住。因此,我们需要一组互相配合的指标,而不是只盯住某一个数字。

一、六类核心指标的定义与判读

吞吐量(Throughput)衡量单位时间内完成的实际工作量,常见口径包括 samples/s(训练)、tokens/s(大模型推理)、images/s(视觉任务)。它反映的是“系统整体产出能力”,是成本效率的分子。注意区分峰值吞吐与稳态吞吐:前者出现在短时突发,后者才是长期可依赖的容量依据。

延迟(Latency)描述单个请求或单个 step 的耗时。推理场景要区分 TTFT(首 token 时间)与 TPOT(每 token 时间),前者受预填充和排队影响,后者受解码与显存带宽影响。训练场景则关注 step time 及其波动,P50 与 P99 的差距往往比均值更能暴露调度抖动。

GPU 利用率(GPU Utilization)通常来自 DCGM 的 SM Active 或 GPU Utilization。它高不代表高效——如果大量时间花在等待数据或同步,利用率可能虚高但吞吐很低。因此必须与吞吐量联合判读:利用率高且吞吐高才是健康状态。

显存占用(Memory Usage)包括已分配显存、保留显存和峰值显存。训练中要关注激活值、优化器状态和梯度三部分;推理中要关注 KV Cache 随并发和序列长度的增长。显存逼近上限会触发重算或 OOM,直接改变性能曲线。

I/O 等待(I/O Wait)体现为 GPU 空转等待数据加载。在 Databricks 上,数据可能来自 Delta 表、云对象存储或挂载卷,读取延迟、小文件数量、格式解析开销都会放大等待。典型信号是 GPU 利用率周期性掉坑,而 step time 同步拉长。

成本效率(Cost Efficiency)把性能换算成钱,常用口径是“每百万 token 成本”或“每千样本训练成本”。它等于集群每小时成本除以有效吞吐,是最终决策指标。优化吞吐、降低空转、选择合适实例规格,最终都体现在这个数字上。

AI 任务性能度量体系:指标分层与因果链 资源层 · 硬件与运行时 GPU 利用率 SM Active / 空转占比 显存占用 已分配 / 峰值 / KV Cache I/O 等待 数据加载 / 小文件 / 解析 网络与同步 AllReduce / 通信等待 共同决定 性能层 · 任务表现 吞吐量 samples/s · tokens/s 延迟 TTFT · TPOT · step time 稳定性 P50 / P99 波动 · 抖动 换算为 成本层 · 决策指标 成本效率 = 集群每小时成本 ÷ 有效吞吐 示例口径:$/百万 token、$/千样本 优化目标:提升有效吞吐、压缩空转与重算 最终以单位产出成本下降为验收标准

二、在 Databricks 中采集这些指标

Databricks 提供了三层采集入口,建议组合使用。

第一层:集群与 GPU 硬件指标。启用 GPU 集群时,Databricks 会通过 DCGM 暴露 GPU 指标,可在 Ganglia 或 Metrics 端点查看。关键指标包括 gpu_utilization、gpu_memory_used、gpu_memory_total、gpu_power_draw。这些指标按秒级采样,适合观察周期性掉坑。

第二层:Spark 与任务级指标。通过 Spark UI 的 Stage 页面可以观察 task 时长分布、shuffle 读写量、spill 情况;通过 spark.sql.adaptive 相关日志可以判断是否发生数据倾斜。对于训练任务,建议在代码中主动打点,例如每个 step 记录 loss、step time、当前显存。

# 在训练循环中主动打点(示例)
import time, torch

for step, batch in enumerate(loader):
    t0 = time.time()
    loss = model(batch).loss
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

    step_time = time.time() - t0
    mem_gb = torch.cuda.max_memory_allocated() / 1024**3
    if step % 20 == 0:
        print(f"step={step} step_time={step_time:.3f}s "
              f"peak_mem={mem_gb:.2f}GB loss={loss.item():.4f}")

第三层:MLflow 与推理端点指标。MLflow 可以记录自定义 metric,把 step time、吞吐、显存写入 run,便于跨实验对比。Model Serving 端点则提供 request latency、queue time、token 吞吐等内置指标,可在 Serving UI 中直接查看。

三、把指标串成因果链来定位瓶颈

单看一个指标容易误判,正确做法是沿着“资源层 → 性能层 → 成本层”的因果链排查。例如:

  • GPU 利用率周期性掉到 20%,同时 step time 拉长、吞吐下降 → 优先怀疑 I/O 等待,检查数据加载是否成为瓶颈。
  • GPU 利用率长期 95% 以上但吞吐不升 → 可能已到算力上限,或存在大量重算,需检查显存是否逼近上限。
  • TTFT 高但 TPOT 正常 → 问题在预填充或排队,而非解码,应检查 batch 策略与并发队列。
  • 成本效率突然恶化但吞吐未变 → 检查是否实例规格变更、是否发生 spill 或重试。
建议为每个 AI 任务固定记录一组“最小指标集”:吞吐、P50/P99 延迟、GPU 利用率、峰值显存、I/O 等待占比、单位产出成本。这六个数字足以支撑绝大多数容量与优化决策。

四、一个可复用的监控落地示例

下面给出一个在 Databricks notebook 中周期性采集并写入 Delta 表的简化示例,便于后续用 SQL 做趋势分析。

from pyspark.sql import Row
from datetime import datetime

def collect_metrics(step, step_time, throughput, gpu_util, mem_gb):
    return Row(
        ts=datetime.utcnow(),
        step=step,
        step_time_s=round(step_time, 4),
        throughput=round(throughput, 2),
        gpu_util_pct=round(gpu_util, 1),
        peak_mem_gb=round(mem_gb, 2)
    )

rows = [collect_metrics(0, 0.42, 1180, 87.5, 61.3),
        collect_metrics(20, 0.45, 1105, 82.1, 62.0)]

spark.createDataFrame(rows).write.mode("append").saveAsTable("ai_perf.metrics")

写入后即可用 SQL 观察趋势,例如按 5 分钟窗口计算平均吞吐与 P95 step time,快速识别性能退化区间。指标采集本身也会带来开销,建议采样频率控制在 10~30 秒一次,避免高频写入影响主任务。

注意:GPU 利用率高不等于性能好。若数据供给不足,GPU 会因等待而空转,此时利用率可能仍然显示较高。务必把利用率与吞吐量、I/O 等待联合判读,避免被单一指标误导。

建立度量体系之后,后续的容量规划、参数调优和成本治理才有依据。下一阶段的工作,就是把这些指标接入持续监控,并设定合理的告警阈值。