1. 从“感觉慢”到“精准定位”:为什么我们需要Ascend Profiler
大家好,我是老张,在AI大模型和智能硬件这个圈子里摸爬滚打了十几年。今天想和大家聊聊一个非常实际的问题:当你把一个大模型,比如Qwen或者Llama,部署到昇腾NPU上,用上了vLLM-Ascend这样的高性能推理框架,服务也跑起来了,但总觉得响应速度没达到预期,吞吐量上不去,甚至偶尔还会卡顿。这时候你该怎么办?
凭感觉猜吗?是模型太大?是网络带宽不够?还是NPU没跑满?以前我们可能得靠经验,或者加一堆打印日志,像“盲人摸象”一样去排查,效率低不说,还经常找错方向。但现在,有了Ascend PyTorch Profiler,情况就完全不同了。它就像给整个推理系统装上了一套“X光机”和“心电图仪”,能从请求接收的第一个字节开始,一直追踪到结果返回的最后一个比特,把框架、算子、内存、通信、硬件执行的每一个细节都清晰地记录下来。
我遇到过不少团队,一提到性能优化,第一反应就是“堆硬件”——加卡、换更强的芯片。这当然能解决问题,但成本太高。更多的时候,瓶颈可能藏在一些意想不到的地方:比如某个不起眼的算子因为数据排布不合适,在NPU上执行效率极低;或者内存频繁分配释放,造成了大量“空泡”时间;又或者是计算和通信没有很好地重叠,导致NPU在等数据,闲着没事干。
Ascend Profiler 的价值就在于,它能帮你把“感觉慢”这种模糊的抱怨,转化为一系列具体、可量化的数据指标。你可以看到一条完整的时间线(Timeline),哪个模块花了多少时间一目了然;可以拿到每个算子的耗时统计,知道谁是“拖后腿”的元凶;还能分析内存的分配与释放,以及计算和通信的重叠度。有了这些数据,你的优化就不再是“拍脑袋”,而是“精准手术”。
接下来的内容,我会以一个真实的在线推理服务调优案例为线索,手把手带你走一遍从数据采集、分析到定位瓶颈、实施优化的全过程。你会发现,用好Profiler,性能调优其实是一门有章可循的“数据科学”。
2. 实战第一步:如何采集vLLM-Ascend的全栈性能数据
工欲善其事,必先利其器。想要分析,首先得把数据采全、采准。vLLM-Ascend框架已经很好地集成了Ascend PyTorch Profiler,我们只需要用对方法就行。这里我分享几种最常用的采集方式,你可以根据自己的场景来选择。
2.1 环境变量:一键开启性能采集的大门
最简单粗暴的方式,就是通过环境变量。你只需要在启动你的vLLM服务或推理脚本前,设置一个环境变量:
export VLLM_TORCH_PROFILER_DIR="./my_profile_data"
这个 VLLM_TORCH_PROFILER_DIR 环境变量就是告诉vLLM:“嘿,把性能数据都记录到我指定的这个目录下”。之后你正常启动服务、发送请求,所有的性能数据就会自动记录在 ./my_profile_data 目录里。这种方式特别适合在容器化环境或者自动化脚本中集成,无需修改代码。
我建议这个目录路径最好用绝对路径,并且确保有足够的写入权限。数据量取决于你采集的时长和模型复杂度,一次完整的端到端推理分析,几个GB的空间预留是必要的。
2.2 代码集成:灵活控制采集的起止点
如果你需要更精细的控制,比如只想分析某一段特定请求的处理过程,而不是整个服务的生命周期,那么直接在代码里调用Profiler的接口会更合适。vLLM-Ascend的LLM对象提供了 start_profile() 和 stop_profile() 这对“开关”。
来看一个我常用的离线推理分析脚本模板:
import os
import time
from vllm import LLM, SamplingParams
# 设置性能数据输出目录(代码设置优先级高于环境变量)
os.environ["VLLM_TORCH_PROFILER_DIR"] = "./detailed_profile"
# 初始化模型
llm = LLM(model="Qwen2.5-7B-Instruct", tensor_parallel_size=2) # 假设我们用了2张NPU卡
# 准备采样参数和输入
sampling_params = SamplingParams(temperature=0.7, top_p=0.9)
prompts = [
"请用中文解释一下机器学习中的过拟合现象。",
"写一首关于春天的五言绝句。",
"将以下英文翻译成中文:'The rapid advancement of AI technology brings both opportunities and challenges.'"
]
# **关键步骤:在生成文本前开启采集**
llm.start_profile()
print("开始推理并采集性能数据...")
start_time = time.time()
outputs = llm.generate(prompts, sampling_params)
end_time = time.time()
# **推理结束后立即停止采集**
llm.stop_profile()
print(f"推理完成,总耗时:{end_time - start_time:.2f}秒")
for output in outputs:
print(f"输入:{output.prompt[:30]}... -> 输出:{output.outputs[0].text[:50]}...")
这段代码的精髓在于,start_profile() 和 stop_profile() 紧紧包裹住了我们真正关心的推理调用 llm.generate()。这样采集到的数据非常“干净”,只包含模型推理本身以及框架调度的开销,避免了服务初始化、空闲等待等无关信息的干扰。对于定位生成阶段的瓶颈,这种方法是最有效的。
2.3 在线服务与压测结合:模拟真实负载场景
很多时候,我们更关心服务在并发请求下的表现。单个请求可能跑得很快,但一旦多个用户同时访问,性能就可能急剧下降。这时候,就需要结合vLLM自带的benchmark工具进行压测式性能采集。
具体操作分三步走,我习惯称之为“三板斧”:
- 启动服务:用你熟悉的参数启动vLLM的OpenAI兼容API服务。这里注意,为了后续分析方便,我通常会固定一些参数,比如序列长度。
VLLM_PROMPT_SEQ_BUCKET_MAX=256 VLLM_PROMPT_SEQ_BUCKET_MIN=256 \ python3 -m vllm.entrypoints.openai.api_server \ --port 8080 \ --model "Qwen2.5-1.5B-Instruct" \


447

被折叠的 条评论
为什么被折叠?



