一. 背景与核心指标

在推理服务中,核心关注点始终是吞吐量与延迟,它们直接影响模型的实际应用效果与用户体验。本文以实践记录的方式,深入讲解如何利用 vLLM-Ascend 构建高效的推理服务,从模型转换、任务调度到资源利用优化,涵盖离线批处理与在线实时服务等多种场景。通过系统性的实践分享,我将带大家了解如何在保证推理精度的前提下,最大化硬件性能,提升服务响应速度,并记录调优过程中遇到的问题与解决方案,为构建稳定、高效的 AI 推理平台提供可参考的经验。

1.1 推理服务的关键指标

吞吐量(Throughput)

  • 定义:单位时间内处理的 Token 数量(tokens/sec)
  • 适用场景:离线批处理、知识库构建
  • 优化目标:最大化 GPU 利用率

延迟(Latency)

  • TTFT:首个 Token 的生成时间,影响用户体验
  • TPOT:后续 Token 的平均生成时间
  • 适用场景:在线对话、实时 API 服务
  • 优化目标:最小化响应时间

1.2 场景选择指南

不同的应用场景对推理服务有不同的需求:

场景

吞吐量

延迟

推荐模式

典型应用

离线批处理

Offline

RAG、数据清洗、日志分析

在线对话

Online

ChatBot、问答系统

API 服务

Online

LLM API、集成服务

大规模推理

Distributed

企业级应用

二. 离线推理(Offline Inference)

离线推理适用于对实时性要求不高,但追求极致吞吐量的场景,如 RAG 知识库构建、数据清洗等。

  • 特点:通过 SamplingParams 充分调度计算资源,尽可能增大 Batch Size,提升资源利用率。
  • 实现:通过 vllm.LLM 类,仅需少量代码即可启动推理引擎进行批处理。

基于 VLLM 框架的 vllm.LLM 类可快速实现离线批处理推理,仅需少量代码即可完成推理引擎初始化、批量请求调度与结果输出,核心代码示例如下:

from vllm import LLM, SamplingParams
import time

# 1. 配置采样参数(适配离线场景,最大化批量处理效率)
sampling_params = SamplingParams(
    max_tokens=100,          # 单条请求生成长度上限
    temperature=0.1,         # 低随机性,适配数据清洗/知识库构建的确定性需求
    top_p=0.9,               # 采样策略,保证生成内容的稳定性
    batch_size=256           # 显式指定批量大小(根据硬件显存调整)
)

# 2. 初始化VLLM推理引擎(适配离线场景的资源配置)
llm = LLM(
    model="your-model-path",  # 本地模型路径/开源模型名(如 "Qwen/Qwen-7B-Chat")
    tensor_parallel_size=4,   # 多卡并行(根据硬件配置调整)
    gpu_memory_utilization=0.95,  # 最大化显存利用率(离线场景无资源预留需求)
    max_num_batched_tokens=8192,  # 单批次最大token数,提升吞吐量
    disable_log_requests=True     # 关闭请求日志,减少资源消耗
)

# 3. 加载离线批量请求(示例:RAG知识库构建的批量文本处理)
with open("offline_texts.txt", "r", encoding="utf-8") as f:
    prompts = [f"基于以下文本提取核心信息:{line.strip()}" for line in f if line.strip()]

# 4. 执行离线批处理推理
start_time = time.time()
outputs = llm.generate(prompts, sampling_params)
end_time = time.time()

# 5. 结果处理与输出(适配离线场景的批量存储)
results = []
for output in outputs:
    results.append({
        "prompt": output.prompt,
        "generated_text": output.outputs[0].text,
        "finish_reason": output.outputs[0].finish_reason
    })
# 将结果写入文件,完成离线任务
with open("offline_inference_results.json", "w", encoding="utf-8") as f:
    json.dump(results, f, ensure_ascii=False, indent=2)

# 吞吐量统计(离线场景核心指标)
total_tokens = sum([len(output.outputs[0].token_ids) for output in outputs])
throughput = total_tokens / (end_time - start_time)
print(f"离线推理完成,总处理请求数:{len(prompts)},总生成Token数:{total_tokens},吞吐量:{throughput:.2f} tokens/s")

运行效果:

三. 在线服务(Online Serving)

对于在线对话、API 服务等场景,vLLM 提供了兼容 OpenAI 协议的 API Server,方便用户快速集成。

3.1 OpenAI 兼容性

  • 无缝迁移:vLLM 兼容 OpenAI 的 API 接口。这意味着现有的基于 OpenAI SDK 构建的应用,只需修改 base_url 即可直接调用昇腾算力,无需修改业务代码。
  • 启动方式:使用 vllm.entrypoints.openai.api_server 模块即可启动服务。

测试代码:

# 文件名:start_vllm_server.py
from vllm.entrypoints.openai import api_server

if __name__ == "__main__":
    # 启动本地 OpenAI 兼容 API 服务
    # 默认在 http://0.0.0.0:8000
    api_server.main()

启动命令:

python start_vllm_server.py

启动后,你可以像调用 OpenAI API 一样调用本地服务,例如用 Python requests

import requests

url = "http://localhost:8000/v1/chat/completions"
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer fake-token"  # vLLM 默认可使用任意 token
}
data = {
    "model": "chat-bloom-7b1",
    "messages": [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello, vLLM!"}
    ]
}

response = requests.post(url, headers=headers, json=data)
print(response.json())

3.2 调用示例

启动 VLLM OpenAI 兼容服务

vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000 --host 0.0.0.0

HTTP 客户端调用

在终端执行curl命令,向服务发送请求:

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [
      {
        "role": "user",
        "content": "Which team won the World Series in 2020?"
      }
    ],
    "max_tokens": 50
  }'

请求结果:

Python SDK 调用

pip install openai

编写test_openai_sdk.py代码:

from openai import OpenAI

# 配置客户端,指向本地VLLM服务
client = OpenAI(
    base_url="http://localhost:8000/v1",  # 本地VLLM服务地址
    api_key="dummy_key"  # VLLM兼容服务无需真实API Key,填任意值即可
)

# 发送聊天请求
response = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[
        {"role": "user", "content": "写一首关于昇腾AI的七言诗"}
    ],
    max_tokens=100
)


print(response)


print("\n--- Content ---")
print(response.choices[0].message.content)

运行代码:

python test_openai_sdk.py

四. 分布式推理(Distributed Inference)

随着模型参数量的增加(如 72B、100B),单卡显存往往无法容纳整个模型。此时需要使用张量并行技术。

  • 原理:将矩阵运算切分到多张 NPU 上并行执行,利用 HCCL进行卡间通信。
  • 优势
    • 显存扩展:解决了单卡显存不足的问题。
    • 性能提升:通过并行计算,显著提升了大模型的推理速度。
  • 注意事项:多卡推理依赖于服务器内部的高速互联,需确保通信链路畅通。

五. 常见问题与注意事项

在构建服务时,需要注意以下几点以避免常见问题:

  1. 端口冲突:若使用 --net=host 模式,需提前规划好端口,避免冲突。
  2. 冷启动耗时:首次推理会进行图编译,可能导致超时。建议在服务启动后进行预热。
  3. 模型名称匹配:客户端请求的 model 参数必须与服务端加载的模型名称一致,否则会报错 400。

六. 性能优化策略

6.1 批处理大小优化

批处理大小是影响吞吐量的关键因素。

批处理大小的影响

  • 太小:无法充分利用 NPU 的计算能力,吞吐量低
  • 太大:显存溢出,导致 OOM 错误
  • 最优:在不溢出显存的前提下,尽可能大

找到最优批处理大小的方法

# 二分搜索找到最大批处理大小
def find_max_batch_size(model_name, max_tokens=2048):
    low, high = 1, 256
    max_batch = 1
    
    while low <= high:
        mid = (low + high) // 2
        try:
            llm = LLM(model=model_name, tensor_parallel_size=1)
            # 尝试处理 mid 个请求
            prompts = ["Hello"] * mid
            llm.generate(prompts, SamplingParams(max_tokens=max_tokens))
            max_batch = mid
            low = mid + 1
        except RuntimeError as e:
            if "out of memory" in str(e):
                high = mid - 1
            else:
                raise
    
    return max_batch

6.2 内存管理优化

显存使用分析

  • 模型权重:占用显存的主要部分
  • KV 缓存:随着序列长度增加而增加
  • 激活值:前向传播过程中产生的中间结果

优化技巧

  1. 使用量化模型:INT8 或 INT4 量化可以减少 75% 的显存占用
  2. 启用 PagedAttention:vLLM 默认启用,可以减少 KV 缓存的碎片化
  3. NPU显存比例:通过 npu_memory_utilization参数控制

6.3 推理优化

采样参数优化

# 对话场景:优先考虑延迟
params_chat = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=512,
    use_beam_search=False  # 关闭束搜索,降低延迟
)

# 文本生成场景:可以考虑更复杂的采样
params_generation = SamplingParams(
    temperature=0.8,
    top_p=0.95,
    max_tokens=2048,
    use_beam_search=True  # 启用束搜索,提高质量
)

预填充优化

  • 对于长提示词,vLLM 会在预填充阶段一次性处理所有 token
  • 这个阶段可以充分利用 NPU 的并行能力
  • 合理设计提示词可以提升性能

七. 监控和指标收集

7.1 性能监控

关键性能指标(KPI)

import time
from typing import List

class PerformanceMonitor:
    def __init__(self):
        self.requests = []
    
    def record_request(self, prompt: str, response: str, 
                      ttft: float, tpot: float):
        """记录单个请求的性能指标"""
        self.requests.append({
            'prompt_len': len(prompt.split()),
            'response_len': len(response.split()),
            'ttft': ttft,  # Time To First Token
            'tpot': tpot,  # Time Per Output Token
            'timestamp': time.time()
        })
    
    def get_stats(self):
        """计算统计指标"""
        if not self.requests:
            return {}
        
        ttfts = [r['ttft'] for r in self.requests]
        tpots = [r['tpot'] for r in self.requests]
        
        return {
            'avg_ttft': sum(ttfts) / len(ttfts),
            'p99_ttft': sorted(ttfts)[int(len(ttfts) * 0.99)],
            'avg_tpot': sum(tpots) / len(tpots),
            'p99_tpot': sorted(tpots)[int(len(tpots) * 0.99)],
            'total_requests': len(self.requests),
            'throughput': len(self.requests) / (self.requests[-1]['timestamp'] - self.requests[0]['timestamp'])
        }

7.2 资源监控

NPU 资源使用情况

# 实时监控 NPU 利用率
watch -n 1 'npu-smi info'

# 监控内存使用
npu-smi info | grep "Memory Usage"

# 监控功耗
npu-smi monitor -i 0

应用级监控

import psutil
import os

def monitor_system_resources():
    """监控系统资源使用"""
    process = psutil.Process(os.getpid())
    
    return {
        'cpu_percent': process.cpu_percent(interval=1),
        'memory_mb': process.memory_info().rss / 1024 / 1024,
        'num_threads': process.num_threads(),
    }

八. 故障排查与调试

8.1 常见问题

问题一:推理速度慢

诊断步骤:

  1. 检查 NPU 利用率:npu-smi info
  2. 检查批处理大小是否过小
  3. 检查是否有其他进程占用 NPU
  4. 尝试增加 gpu_memory_utilization

问题二:显存溢出

症状:

RuntimeError: out of memory

解决方案:

  1. 减小批处理大小
  2. 减小最大生成长度
  3. 使用量化模型
  4. 启用 PagedAttention(默认启用)

问题三:延迟不稳定

原因:

  • 系统负载波动
  • 垃圾回收(GC)暂停
  • 其他进程竞争资源

解决:

  • 在专用服务器上部署
  • 关闭不必要的后台进程
  • 使用 CPU 亲和性绑定进程

8.2 调试工具

启用详细日志

export VLLM_LOG_LEVEL=DEBUG
export ASCEND_LOG_LEVEL=3
python inference_service.py

性能分析

import cProfile
import pstats

def profile_inference():
    """对推理过程进行性能分析"""
    profiler = cProfile.Profile()
    profiler.enable()
    
    # 执行推理
    llm = LLM(model="meta-llama/Llama-2-7b-hf")
    llm.generate(["Hello"] * 10)
    
    profiler.disable()
    stats = pstats.Stats(profiler)
    stats.sort_stats('cumulative')
    stats.print_stats(20)  # 打印前 20 个耗时最长的函数

九. 总结

vLLM-Ascend 提供了灵活的推理服务构建方式:

  • 离线场景:关注高吞吐量,通过增大批处理大小和充分利用 NPU 资源
  • 在线场景:关注低延迟,通过优化采样参数和内存管理
  • 超大模型:通过 TP 技术实现多卡并行推理

关键优化策略:

  1. 找到最优批处理大小:在不溢出显存的前提下最大化
  2. 监控关键指标:TTFT、TPOT、吞吐量
  3. 定期性能分析:使用 profiling 工具找到瓶颈
  4. 生产环境部署:使用多副本、负载均衡、监控告警

昇腾PAE案例库对本文写作亦有帮助。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐