一篇看懂 VKE AI Profiling:AI 应用性能分析优化实战

作者:火山引擎Agent社区日期:2026/6/18

你有没有遇到过这样的情况:

AI 模型在训练时 GPU 利用率忽高忽低,容器资源明明给够了,训练时长却总是超出预期?或者在推理服务上线后,延迟时不时抖动,重启一下又好了,但问题根本找不到?

  • “我的模型在裸机上跑只要 2 小时,放到容器里怎么变成 3 小时了?”
  • “资源都给了,CPU 和内存也没瓶颈,我不知道还要看什么。”

很多团队在模型效果验证通过后,真正进入上线或规模化使用时,往往会遇到一个共同问题:GPU 看起来很忙,但整体效率并不高;系统投入不少,性能瓶颈却不容易快速定位。

这正是 AI Profiling 的价值所在。

在字节跳动内部,所有低 SMA 的训练任务以及推理服务上线前的压测环节,都会进行 AI Profiling 采集,帮助研发同学通过可视化分析快速定位 GPU 效率低下与性能抖动问题。基于这一内部实践,字节跳动 STE 系统技术与工程团队联合火山引擎容器团队,将该能力产品化封装并融入容器服务 VKE,形成了 VKE AI Profiling —— 专为容器环境设计的 AI 性能剖析能力,现已对外提供服务。

本文将结合一个真实的 ResNet18 训练案例,介绍如何使用 VKE AI Profiling 做一次完整的性能排查与优化。

AI Profiling:为什么要做,以及该用什么工具

为什么大模型训练与推理需要 Profiling

过去做传统应用,性能问题更多集中在 CPU、内存、数据库和网络。为什么到了大模型这里,还要专门搞一个 Profiling?

答案是——性能链路变长了,而且长得不是一星半点。

以一个典型的企业大模型系统为例:

  • 最上层是业务应用和调用接口;
  • 中间跑着推理服务、Embedding 服务、批量评测;
  • 最下面还叠着模型训练、微调、GPU 调度和算力利用。

这时候性能问题不再只是“某个接口慢了一秒”这么简单。它可能来自:

  • 数据加载跟不上,GPU 眼巴巴等着输入;
  • Host 到 Device 的数据传输没做 overlap,白白浪费时间;
  • 还在用 FP32,Tensor Core 的加速收益一点没吃到;
  • Python 框架调度、同步操作打断了训练流水线;
  • 多个采样工具分散使用,分析过程重、门槛高、协同成本高。

对于企业来说,Profiling 的意义不是“看一眼时间线截图发群里”,而是能快速回答这几个问题:

  • 当前瓶颈到底在哪——数据、传输、算子、框架、还是同步?
  • 这个问题值不值得我花时间优化?
  • 如果优化了,能省多少算力、提升多少迭代效率?

这也是 VKE AI Profiling 更适合企业工程实践的地方:它把使用门槛尽量压下来,让性能分析从“专家工具”变成“研发团队每个人都能上手用起来的工程能力”。

常见 Profiling 工具怎么选

很多团队第一次做 AI 性能分析时,常常会问一个问题:已经有 PyTorch Profiler、Nsight System 这些工具了,为什么还需要统一的 AI Profiling 能力?

核心不在于“谁替代谁”,而在于不同工具解决的问题层次不同。

如果从企业落地角度总结:

  • 研发同学日常排查,更需要低门槛、低侵入、面向训练和推理全流程的能力;
  • 性能专家深挖极限优化,仍然需要更底层的专项工具;
  • 最理想的方式不是只选一个工具,而是先用统一 Profiling 能力快速定位,再按需进入更深层分析。

从思路到实战:一个真实的 ResNet18 训练案例

为了让过程更具体,我们选了一个相对经典、容易复现的训练任务做演示:

  • 模型: ResNet18
  • 数据集: CIFAR10
  • 框架: PyTorch 2.1.0,TorchVision 0.16.0
  • GPU: 业内主流训练卡
  • Profiling 时长: 5 秒
  • 采集项: python / cuda / pytorch

虽然这是一个图像训练场景,但分析思路同样适用于其他训练与推理任务。例如:

  • 做模型微调或蒸馏时,可以用同样方法看训练过程瓶颈;
  • 做推理服务时,也可以按类似方式分析数据预处理、Host/Device 拷贝、算子执行和同步等待。

换句话说,案例虽然是训练,但方法论并不局限于训练。 接下来我们就以这个 ResNet18 任务为例,完整走一遍 Profiling 的分析与优化流程。

初始代码:功能正确,但不一定高效

我们准备了一个基础版的 ResNet18 训练脚本,逻辑非常常见:

  • 使用 DataLoader 读取 CIFAR10;
  • 将输入搬到 GPU;
  • 前向、反向、更新参数;
  • 周期性打印 loss。

这类代码通常能顺利跑通,但不代表性能已经合理。很多企业真实项目也是类似情况:先可用,再优化。问题在于,如果缺少性能分析能力,优化往往只能靠经验猜。

为了让后面的优化动作更容易理解,先给出一份可直接运行的简化版训练代码。后文提到的 num_workerspin_memory、AMP 等优化,都是基于这类标准训练循环逐步展开的。这里将 num_epochs 写为 5,更接近日常训练任务;但对于 Profiling 来说,关注重点通常是稳定阶段的若干个 step,因此实际采集时并不依赖完整跑完全部 epoch。

1import torch
2import torch.nn as nn
3import torch.optim as optim
4import torchvision
5import torchvision.transforms as T
6
7device = torch.device("cuda:0")
8
9transform = T.Compose(
10    [
11        T.Resize(224),
12        T.ToTensor(),
13        T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
14    ]
15)
16
17train_set = torchvision.datasets.CIFAR10(
18    root="./data",
19    train=True,
20    download=True,
21    transform=transform,
22)
23
24train_loader = torch.utils.data.DataLoader(
25    train_set,
26    batch_size=32,
27    shuffle=True,
28    num_workers=1,
29)
30
31model = torchvision.models.resnet18(weights=None).to(device)
32criterion = nn.CrossEntropyLoss().to(device)
33optimizer = optim.SGD(model.parameters(), lr=1e-3, momentum=0.9)
34model.train()
35
36
37deftrain_step(data):
38    inputs = data[0].to(device)
39    labels = data[1].to(device)
40    optimizer.zero_grad()
41    outputs = model(inputs)
42    loss = criterion(outputs, labels)
43    loss.backward()
44    optimizer.step()
45    return loss.item()
46
47
48num_epochs = 5
49for epoch inrange(num_epochs):
50    running_loss = 0.0
51    for i, batch_data inenumerate(train_loader):
52        loss = train_step(batch_data)
53        running_loss += loss
54        if i % 100 == 99:
55            print(
56                f"Epoch [{epoch + 1}/{num_epochs}], "
57                f"Step [{i + 1}], Loss: {running_loss / 100:.4f}"
58            )
59            running_loss = 0.0
60

如果只看后面的局部代码片段,确实容易让人误以为是在“单点改参数”。而放在完整训练循环里看,会更容易理解每一步优化究竟改动了哪里、为什么会产生收益。

零代码采集:先看到问题,再决定值不值得改

使用 VKE AI Profiling 时,不需要先为排查问题大幅改造代码。一次典型操作流程如下:

  1. 在 VKE 控制台进入 AI Profiling。
  2. 选择目标训练任务对应的命名空间、容器组和容器。
  3. 勾选采集项:pythoncudapytorch
  4. 将采集时间设置为 5 秒,抓取几个稳定 step 即可。
  5. 下载产物后,在 Perfetto 中查看时间线。

这一步的价值非常直接:先用尽可能低的成本,拿到“问题发生在哪里”的证据。

对于企业团队来说,这比“凭经验改一轮再看结果”更高效,也更容易在研发、算法、平台团队之间达成一致。

从时间线里看问题:三类最常见的训练瓶颈

在这次测试里,我们重点看到了三类很典型的问题:

  • 数据加载阶段过慢,GPU 有等待;
  • Host 到 Device 的数据拷贝仍有明显耗时;
  • 计算阶段没有使用混合精度,训练 step 耗时偏高。

下面按排查顺序展开。

先看基线版本的时间线,可以明显看到 DataLoader 相关阶段占用了较长时间,CPU 侧供数不足时,GPU 计算无法被持续喂满。

优化一:先解决数据加载瓶颈

在基线版本里,DataLoader 使用 num_workers=1。这在功能上没有问题,但对训练吞吐通常不友好。

当 CPU 侧数据准备跟不上时,GPU 即使算力充足,也会在时间线上表现为间歇性空闲。从成本来说,这是一类非常“亏”的问题,因为它意味着昂贵的 GPU 没有被充分利用。

优化方式很直接:提升 num_workers,让数据加载并行起来。

1train_loader = torch.utils.data.DataLoader(
2    train_set,
3    batch_size=32,
4    shuffle=True,
5    num_workers=8,
6)
7

在实测环境中,数据加载耗时变化如下:

  • num_workers=1:16.5 ms
  • num_workers=8:1.49 ms

这一步优化后,数据加载从 16.5ms 降到了 1.49ms。GPU 不用再等着 CPU 喂数据了。

对于在线推理或离线训练系统来说,这类问题都可以理解为“计算资源充足,但上游数据准备没有跟上”,本质上是同一类资源错配问题。

从优化后的时间线也能看到,DataLoader 阶段被显著压缩,后续计算段更容易连续铺开。

优化二:让数据拷贝尽量和计算重叠

第二个常见问题是数据从 CPU 内存拷贝到 GPU 显存的方式不够高效。

如果只是简单地把数据 .to(device),很多时候会让数据传输成为显式等待点。更常见的优化方式是:

  •  DataLoader 中开启 pin_memory=True
  • 在数据搬运时配合 non_blocking=True

示例代码如下:

1train_loader = torch.utils.data.DataLoader(
2    train_set,
3    batch_size=32,
4    shuffle=True,
5    num_workers=8,
6    pin_memory=True,
7)
8
9inputs = data[0].to(device, non_blocking=True)
10labels = data[1].to(device, non_blocking=True)
11

在实测环境中,数据拷贝耗时从 2.2ms 降低到了:0.76ms。

这个优化看起来不像 AMP 那样“数字很大”,但在真实业务里往往非常值得做。因为它通常改动小、风险低,而且对训练和推理都适用。

在时间线里,开启 pin_memory=True 后,Host 到 Device 的拷贝阶段更短,也更容易与后续计算形成更顺畅的衔接。

优化三:混合精度带来更直接的收益

第三步是开启混合精度计算(Automatic Mixed Precision (AMP)):

pytorch.org/tutorials/r…

对于支持 Tensor Core 的 GPU,很多模型在训练和推理中都能通过混合精度获得明显收益。这里我们使用 autocast 来完成基础版本的 AMP 改造:

1with torch.cuda.amp.autocast(enabled=True, dtype=torch.float16):
2    outputs = model(inputs)
3    loss = criterion(outputs, labels)
4

在这次测试里,我们关注的是 train_step 的平均执行时长。我们的实测结果是:

  • AMP 关闭:29 ms
  • AMP 开启:22.7 ms

这类优化在模型训练和推理体系中都很常见。很多团队在模型效果达标后,会进一步追求更高吞吐或更低成本,而混合精度通常是最值得优先验证的一步。

从时间线视角看,开启 AMP 后,单次 train_step 的执行段进一步收缩,计算密度更高。

结果汇总:不是“玄学调优”,而是可解释的收益

将这三个动作放在一起看,本次训练样例得到了比较清晰的收益:

这组结果的价值,不只是展示训练模型更快了一点,而是说明:

  • 瓶颈是可以被明确识别的,收益是可以被量化验证的;
  • 优化动作和结果之间是有因果对应关系的。

只有当收益被量化之后,性能优化才更容易进入日常研发流程,而不是停留在少数专家的经验里。

小结

为什么这对训练和推理都重要

有人可能会说:你讲的是训练,跟我做推理服务有什么关系?

实际上,两者在工程问题上非常接近。一个大模型系统在生产环境中,常见的性能诉求无非这几类:

  • 降低首 token 时延和整体响应时延;
  • 提高单卡吞吐,降低 GPU 成本;
  • 缩短模型迭代、微调和评测周期;
  • 更快定位性能回退,减少排障时间。

这些问题的背后,都离不开 Profiling。举个例子:

  • 推理慢,可能不是模型本身,而是预处理、拷贝或同步在阻塞;
  • 训练慢,可能不是 GPU 算力不够,而是输入流水线没喂满;
  • 成本高,可能不是必须加卡,而是现有资源利用率还没打够。

从这个角度看,AI Profiling 不是一个“锦上添花”的工具,而是 AI 基础设施走向工程化、规模化的必需能力。

还有哪些优化值得继续做

本文只展开三类最容易落地、也最容易看到收益的优化方向。如果团队有更高的性能要求,还可以进一步验证:

  • 模型编译,例如 torch.compile 带来的框架调度开销下降;
  • 减少不必要的同步,例如延后 loss.item() 的调用;
  • 多机多卡训练中的通信与计算重叠;
  • 推理场景中的批处理、算子融合与显存优化。

这些方向本文不展开,留给对深度优化更感兴趣的读者继续实践。

写在最后

今天企业做 AI,已经从“有没有模型能力”逐渐走向“有没有稳定的 AI 工程能力”。

在这个过程中,VKE AI Profiling 的意义并不只是提供一次性能采集,而是帮助团队建立一种更高效的工程方式:

  • 用统一入口快速发现问题;
  • 用真实时间线判断瓶颈位置;
  • 用可量化结果验证优化价值。

对于训练场景如此,对于推理场景同样如此。随着大模型系统越来越多地进入真实业务,性能、成本和稳定性会越来越成为核心竞争力的一部分。

如果你的团队已经在推进模型训练优化,或者在做推理性能与成本治理,不妨从一次 5 秒钟的 Profiling 开始。很多时候,真正影响效率的瓶颈,并不在想象中,而在时间线里。


一篇看懂 VKE AI Profiling:AI 应用性能分析优化实战》 是转载文章,点击查看原文


相关推荐


架构视图与文档:C4 模型从入门到实战
ltl2026/6/10

你上次打开团队的架构图是什么时候? 大多数团队都有架构图。Confluence 上躺着一张两年前画的系统拓扑图,Visio 文件在某个共享盘里,PowerPoint 里有几页"技术方案评审"的框线图。问题是:没人信它们。新人入职时看一眼,发现和实际系统对不上,从此再也不看。老人心里有一张"真正的架构图",但那张图只存在于他的脑子里。 这不是个别现象。Simon Brown 在 2018 年的调查中发现,超过半数的开发者认为自己团队的架构文档"基本没用"或"严重过时"(Simon Brown, "


Java MyBatis-Flex 实战指南:从 BaseMapper 到 QueryWrapper 的轻量 ORM 用法
唐青枫2026/6/3

简介 MyBatis-Flex 是一个基于 MyBatis 的增强框架。 它的定位很直接: 保留 MyBatis 写 SQL 的灵活性,同时补上通用 CRUD、链式查询、分页、逻辑删除、乐观锁等常用能力。 普通 MyBatis 项目里,常见代码结构是: Entity Mapper 接口 Mapper XML Service Controller 如果只是做一张表的增删改查,也要写不少重复 SQL。 MyBatis-Flex 的思路是: 简单 CRUD 交给 BaseMapper 复杂条件交给


我用AI一小时撸了个单词学习站,每天自动生成5个单词
修己xj2026/5/26

AI编程热度居高不下,我也一直在用CodeBuddy辅助开发。最近发现了一个有意思的功能——Skill(技能包),灵机一动:不如写个技能,每天自动生成英文单词并部署成网页,这样打开浏览器就能学。 说干就干,全程AI辅助,一个多小时就搞定了。过程分享给大家。 以下是项目地址及在线地址 在线访问:xiuji008.github.io/everyday-en… 项目完全开源:github.com/xiuji008/ev… 灵感来源 CodeBuddy的Skill功能可以定义一套自动化流程。比如我说“


claudeCode+DeepSeekV4pro[1m]
冰镇生鲜2026/5/5

Claude Code 完美调和配置 这是经过多轮磨合后的全局配置,可以直接复制到新电脑的 ~/.claude/settings.json 使用。 也可以直接让 AI 处理一切。 完整配置文件 ~/.claude/settings.json { "env": { "ANTHROPIC_AUTH_TOKEN": "sk-你的token", "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic", "AN


【AI Agent | 第七篇】Skill的使用:将经验沉淀成可复用工作流
程序员夏末2026/4/25

前言   此文是我近期学习Agent中关于Skill使用的一点心得,结合AI整理总结而来,分享给大家。一是为了记录,二是用于日后的回顾,三也是希望能给其他初学者带来一点点帮助。 目录 1. 为什么我会开始关注 Skill?2. Skill 到底解决了什么问题?3. 从写博客这个例子理解 Skill 的价值4. 一个 Skill 通常包含哪些内容?5. Skill 使用时会加载哪些上下文?6. 写 Skill 时我踩到的几个细节7. 什么样的工作流适合沉淀成 Skill?8. 总结


【AI工具篇】Windows 安装 WSL 全攻略:wsl --install 一键部署 + VSCode 搭配使用好处详解
*星星之火*2026/4/16

Windows 安装 WSL 全攻略:wsl --install 一键部署 + VSCode 搭配使用好处详解 前言 在 Windows 上做开发,尤其是后端、C/C++、Python、Docker、机器学习等开发时,经常会遇到环境不一致、命令不兼容、依赖难装等问题。 传统虚拟机笨重卡顿,双系统切换麻烦,而 WSL(Windows Subsystem for Linux) 完美解决了这些痛点。 本文详细介绍: Windows 安装 WSL 的好处一条命令 wsl --install 完成安装V


【docker】Ubuntu22使用skopeo离线推送镜像
s9123601012026/4/8

1,准备安装skopeo # 安装skopeo apt install skopeo --fix-missing # 创建目录 mkdir -p skopeo_bundle # 拷贝主要的 cp /usr/bin/skopeo skopeo_bundle/ # 下载依赖库 ldd /usr/bin/skopeo | awk '{print $3}' | grep '/' | xargs -I '{}' cp -v '{}' skopeo_bundle/ # 压缩 tar czf skopeo


极速上手:Puppeteer + 原生代理IP 突破无头检测(金融与突发新闻抓取 Cheat Sheet)
亿牛云爬虫专家2026/3/31

在金融量化分析、宏观经济数据追踪或突发新闻监控等场景中,数据价值随时间呈指数级衰减。高频并发抓取极易触发目标网站的反爬策略(如 Cloudflare 盾、无头浏览器指纹识别)以及严苛的 IP 封禁。 终极解法: 使用 puppeteer-extra-plugin-stealth 抹平自动化指纹,配合 爬虫原生代理IP 进行高匿 IP 轮换。本文提供可直接用于生产环境的配置清单与核心业务代码。 核心优势:为什么金融与突发新闻需要“即时采集”? 在讲技术实现之前,我们需要明确高频即时采集的不可


【Web】使用Vue3+PlayCanvas开发3D游戏(五)3D模型鼠标交互控制
沙振宇2026/3/23

文章目录 一、效果二、简介三、知识点3.1、核心交互需求分析3.2、基础准备:变量定义3.3、工具函数封装3.3.1、角度转弧度(原生实现)3.3.2、相机位置更新函数3.3.3、视角重置函数(双击触发) 3.4、鼠标交互核心逻辑实现3.4.1、初始化鼠标事件监听3.4.2、关键逻辑说明 3.5、集成到初始化流程3.6、生命周期清理 四、完整源码 一、效果 二、简介 在《【Web】使用 Vue3+PlayCanvas 开发 3D 游戏(四)3D 障碍物躲避游戏 2


pycharm创建桌面时间控件小程序
chushiyunen不懂代码的小白2026/3/15

文章目录 步骤 主要是为了走一遍python创建exe的流程。 步骤 1、新建一个项目,名称为:desktop_widget 2、创建一个python文件,名称为:desktop_widget.py,内容如下: import tkinter as tk from datetime import datetime class DesktopWidget: def __init__(self): self.root = tk.Tk()

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读