欢迎光临
我们一直在努力

VLLM和TensorRT

前提

使用CUDA

Python 代码 (vLLM) -> 调用 PyTorch -> PyTorch 调用 CUDA 库 -> CUDA 驱动 指挥 GPU 硬件 进行计算。

介绍

  • vLLM: 基于 Python 开发,主打高吞吐量和易用性。它通过 PagedAttention 技术优化显存管理,非常适合服务端部署、多用户并发场景。它的 API 兼容 OpenAI 格式,也提供直接的 Python 类调用。
  • TensorRT-LLM (TRT-LLM): NVIDIA 出品,主打极致低延迟和硬件级优化。它需要将模型预先编译成特定的 Engine 文件(类似 C++ 编译过程),然后在 Python 或 C++ 中加载运行。适合对延迟极其敏感或显存极度受限的生产环境。

功能和作用

  • vLLM:核心在于“显存管理”。它通过 PagedAttention 技术,像操作系统管理内存一样管理显存,消除了显存碎片,从而能在同一块显卡上塞进更多的并发请求。
  • TensorRT-LLM:核心在于“计算调度”与“内核融合”。它通过 In-flight Batching 技术在 GPU 计算间隙插入新请求,并利用高度优化的 CUDA 内核(Kernel)榨干 GPU 算力。

下面详细拆解它们的原理、实现方式和作用。

一、vLLM:PagedAttention (分页注意力机制)

1. 痛点:传统 LLM 的显存浪费

在 vLLM 出现之前,传统的 LLM 推理(如 Hugging Face Transformers)在处理并发时有一个致命弱点:显存预分配。

  • 场景:假设显存够存 1000 个 token。
  • 问题:如果你启动了 10 个并发请求,系统通常会为每个请求平均分配显存(例如每个预留 100 个 token 的空间)。
    • 如果请求 A 只生成了 10 个 token 就结束了,它占用的 90 个 token 空间就浪费了(无法给其他请求用)。
    • 如果请求 B 需要生成 200 个 token,但因为预分配限制,它可能直接报错 OOM (Out Of Memory),即使此时显存里还有很多空闲碎片。
  • 结果:显存利用率通常只有 20%-40%,并发度上不去。
2. 原理:PagedAttention (借鉴操作系统的虚拟内存)

vLLM 将操作系统中虚拟内存分页 (Virtual Memory Paging) 的思想引入了 GPU 显存管理。

  • 非连续存储:KV Cache(注意力机制需要的键值对缓存)不再需要连续的显存空间。它被切分成固定大小的块 (Blocks)(例如每个块存 16 个 token)。
  • 页表映射:vLLM 维护一个页表 (Block Table),记录每个逻辑上的 token 块对应物理显存中的哪个块。
    • 逻辑块 0 -> 物理块 5
    • 逻辑块 1 -> 物理块 2
    • 逻辑块 2 -> 物理块 9
  • 动态分配:
    • 当请求生成新 token 时,vLLM 只需从空闲池子里拿一个物理块挂上去,按需分配。
    • 当请求结束时,物理块立即释放回池子,供其他请求使用。
3. 如何实现并发?

由于消除了碎片,vLLM 可以将显存利用率提升到 95% 以上。

  • 效果:原本只能同时跑 10 个请求的显存,现在可以跑 50 个甚至 100 个请求。
  • 调度器:vLLM 有一个调度器,每一轮迭代前,它会检查显存池里还有多少空闲块。如果有,就从等待队列中把新的请求“塞”进去(加入当前 Batch),直到显存填满。
4. 作用
  • 极高吞吐量:单位时间内处理的请求数大幅提升。
  • 抗长尾能力:不怕某个请求特别长,也不怕某个请求特别短,显存永远被充分利用。
  • 零浪费:彻底解决了内部碎片化问题。

二、TensorRT-LLM:In-flight Batching (飞行中批处理)

1. 痛点:传统 Batch 的“木桶效应”

传统的批处理(Static Batching)是将 N 个请求打包一起送入 GPU 计算。

  • 场景:Batch 里有 4 个请求。
    • 请求 A, B, C 很短,第 10 步就生成完了(遇到 EOS)。
    • 请求 D 很长,需要生成 100 步。
  • 问题:在传统模式下,GPU 必须等待所有请求完成当前步。当 A, B, C 完成后,它们占用的计算资源必须空转等待 D 完成,或者整个 Batch 必须解散,重新组织新的 Batch。这导致 GPU 在很多时间片里是在做无用功。
2. 原理:In-flight Batching (Continuous Batching)

TRT-LLM (以及 vLLM 也采用了此技术) 允许在同一个迭代步骤中动态地移除已完成的请求,并插入新的请求。

  • 细粒度调度:GPU 不是以“整个 Batch”为单位运行,而是以“迭代步 (Iteration)”为单位。
  • 即时插拔:
    • 在第 10 步计算结束时,发现请求 A, B, C 结束了。
    • 立刻将它们从显存和计算队列中移除。
    • 立刻从等待队列中抓取新的请求 D, E, F 填补空缺。
    • 请求 D (原来的长请求) 继续在第 11 步计算,完全不受影响。
  • 全程无停顿:GPU 的计算单元永远处于满载状态,没有因为等待慢请求而产生的气泡 (Bubble)。
3. TRT-LLM 的独门绝技:内核融合与量化

除了调度,TRT-LLM 还通过底层优化提升并发时的单请求速度,从而间接提升并发能力:

  • Kernel Fusion (内核融合):将多个小的 CUDA 操作(如 Add, Norm, Activation)合并成一个大操作,减少 GPU 启动内核的开销和显存读写次数。
  • FP8/INT8 量化:在编译阶段将模型权重转换为低精度,显存占用减半,计算速度翻倍,意味着同样的显存能容纳更大的 Batch Size。
  • Multi-Block Attention:针对长上下文优化的注意力核,减少显存访问延迟。
4. 作用
  • 极低延迟:特别是对于首字延迟 (TTFT) 和 单 token 延迟,优化到了极致。
  • 稳定高吞吐:即使请求长度差异巨大,GPU 利用率也能保持在高位。
  • 硬件压榨:充分利用 NVIDIA GPU 的 Tensor Core 特性。

三、总结对比:它们是如何协作的?

其实,现代高性能推理引擎往往是两者结合的。vLLM 也实现了 In-flight Batching,TRT-LLM 也有自己的显存管理机制。但侧重点不同:

表格

特性vLLM 的核心贡献TensorRT-LLM 的核心贡献
并发核心原理 PagedAttention (显存虚拟化) In-flight Batching + Kernel Optimization (计算流优化)
解决什么问题 解决显存碎片,让更多的请求能同时存在 (提高容量)。 解决计算等待,让 GPU 算得更快且不闲置 (提高效率)。
比喻 像一个超级整理师:把杂乱的房间(显存)利用到极致,能塞进更多人。 像一个流水线工头:一旦有人做完工作立刻让他走,新人马上补位,机器不停转。
主要优势 吞吐量 (Throughput):适合高并发、多用户、长文本混合场景。 延迟 (Latency) & 效率:适合对响应速度极其敏感、硬件资源受限的场景。
代码体现 Python 层的调度逻辑 + 自定义 CUDA Kernel (PagedAttention)。 编译期的图优化 + 高度定制的 CUDA Kernel 库。

四、实际工作流程示例

假设现在有 100 个用户同时发起请求:

  • vLLM 的处理:

    • 检查显存,发现通过 PagedAttention 可以容纳 80 个请求的 KV Cache。
    • 调度器瞬间将这 80 个请求全部加载进显存(剩下的 20 个排队)。
    • 结果:并发容量极大,排队时间短。
  • TRT-LLM 的处理 (在显存允许的情况下):

    • 这 80 个请求开始生成。
    • 第 5 步时,有 10 个短请求结束了。
    • TRT-LLM 的调度器立刻感知,在第 6 步计算开始前,把这 10 个位置腾出来,塞入排队中的 10 个新请求。
    • 同时,底层的 CUDA Kernel 以 FP8 精度高速运算。
    • 结果:GPU 从未停歇,每个用户的等待时间(延迟)最短。
  • 结论:

    • 如果你追求部署简单且并发量大,vLLM 的 PagedAttention 是首选,因为它能让你用更少的卡抗住更多的人。
    • 如果你追求极致性能且有能力做复杂的工程化构建,TensorRT-LLM 通过底层的计算优化和调度,能提供比 vLLM 更高的上限(尤其是在延迟方面)。
    • 趋势:两者正在融合。vLLM 正在集成更多 TRT-LLM 的优化内核,而 TRT-LLM 也在改进其显存管理策略。

    使用

    VLLM

    核心区别一览

    表格

    特性方案一:离线模式 (Offline)方案二:在线模式 (Server)
    适用场景 本地跑数据、批量处理、Jupyter 实验 搭建 API 服务、供前端/其他语言调用
    进程数 1 个 (脚本即服务) 2 个 (服务端 + 客户端)
    通信方式 函数调用 (内存直连,最快) HTTP 请求 (网络通信)
    代码复杂度 低 (一个文件搞定) 中 (需先启动服务,再调接口)

    🛠️ 第一步:环境安装 (通用)

    无论选哪种方案,先执行以下命令安装依赖:

    # 安装 vLLM 核心库 (会自动安装 torch 等依赖)
    pip install vllm

    # 安装 HTTP 客户端库 (仅方案二需要,但建议都装上)
    pip install aiohttp tqdm

    硬件要求:需要 NVIDIA 显卡 (显存建议 8GB 以上运行 7B 模型,若显存不足请将代码中的模型改为 Qwen/Qwen2.5-1.5B-Instruct)。


    方案一:离线模式 (Offline Mode)

    特点:单文件运行,无需启动服务器,无网络开销,速度最快。
    原理:直接在 Python 进程中加载模型,通过 llm.generate() 函数并发处理列表。

    📄 文件:vllm_offline_qwen.py

    python

    """
    方案一:vLLM 离线模式 (Offline Mode)
    功能:直接在 Python 代码中加载千问模型,批量并发处理多个问题。
    无需启动服务器,无需 HTTP 请求。
    """

    import time
    from vllm import LLM, SamplingParams
    from tqdm import tqdm # 用于显示进度条

    # ================= 配置区域 =================
    # 模型名称:通义千问 7B 指令版
    # 如果显存不足 (<8GB),请改为 "Qwen/Qwen2.5-1.5B-Instruct"
    MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"

    # 模拟 50 个并发问题 (实际使用中可以是你的业务数据列表)
    PROMPTS = [
    f"问题 {i}: 请用一句话解释什么是量子纠缠?" for i in range(25)
    ] + [
    f"问题 {i}: 写一个 Python 函数计算斐波那契数列。" for i in range(25)
    ]

    def main():
    print(f"🚀 正在初始化 vLLM 引擎并加载模型:{MODEL_NAME} …")
    print("⏳ 首次运行会自动下载模型,请耐心等待…")

    start_time = time.time()

    # 1. 初始化 LLM 引擎
    # tensor_parallel_size=1: 使用 1 张显卡。多卡可改为 2, 4, 8
    # gpu_memory_utilization=0.9: 占用 90% 显存,留 10% 给系统防止 OOM
    # enforce_eager=False: 启用 CUDA Graph 加速,提升推理速度
    llm = LLM(
    model=MODEL_NAME,
    tensor_parallel_size=1,
    gpu_memory_utilization=0.9,
    enforce_eager=False,
    disable_log_stats=True # 关闭 vLLM 内部日志,保持输出整洁
    )

    load_time = time.time() – start_time
    print(f"✅ 模型加载完成!耗时:{load_time:.2f} 秒")

    # 2. 设置采样参数
    sampling_params = SamplingParams(
    temperature=0.7, # 创造性:0-1,越高越随机
    top_p=0.8, # 核采样概率
    max_tokens=256 # 限制最大生成长度,防止测试时间过长
    )

    print(f"📝 开始处理 {len(PROMPTS)} 个并发请求…")
    process_start = time.time()

    # 3. 核心并发调用
    # vLLM 的 generate 方法天然支持列表输入
    # 它会自动将列表中的所有 Prompt 打包成一个 Batch,利用 PagedAttention 并行处理
    # 这里不需要写任何多线程代码,vLLM 内部已经高度优化
    outputs = llm.generate(PROMPTS, sampling_params, use_tqdm=True)

    process_time = time.time() – process_start

    # 4. 结果统计与展示
    total_tokens = sum(len(out.outputs[0].token_ids) for out in outputs)
    speed = total_tokens / process_time

    print("\\n" + "="*50)
    print("📊 测试结果统计")
    print("="*50)
    print(f"总请求数:{len(outputs)}")
    print(f"处理耗时:{process_time:.2f} 秒")
    print(f"生成总 Token 数:{total_tokens}")
    print(f"平均生成速度:{speed:.2f} Tokens/秒")
    print("="*50)

    # 展示前 3 个结果示例
    print("\\n💡 部分结果示例:")
    for i, output in enumerate(outputs[:3]):
    prompt = output.prompt
    result = output.outputs[0].text
    print(f"[{i+1}] 问:{prompt}")
    print(f" 答:{result.strip()[:100]}…") # 只打印前100字
    print("-" * 30)

    if __name__ == "__main__":
    main()

    👉 如何运行:
    直接运行即可:

    python vllm_offline_qwen.py


    方案二:在线模式 (Server Mode)

    特点:分离架构。服务端一直运行监听端口,客户端通过 HTTP 发送请求。
    适用:你需要部署一个服务,让网页、App 或其他程序调用。

    此方案需要两个步骤(或两个终端窗口)。

    第 1 步:启动服务端

    📄 文件:start_vllm_server.py (或者直接在命令行运行)

    """
    方案二 (步骤 1):启动 vLLM API 服务端
    功能:加载模型并启动 HTTP 服务器,监听 8000 端口。
    """

    import os
    import sys
    import subprocess
    import time

    def start_server():
    # 模型名称 (需与客户端一致)
    MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
    PORT = 8000

    print(f"🚀 正在启动 vLLM API 服务器…")
    print(f"📦 模型:{MODEL_NAME}")
    print(f"🔌 端口:http://localhost:{PORT}")

    # 构建启动命令
    # –disable-log-requests: 减少日志刷屏
    cmd = [
    sys.executable, "-m", "vllm.entrypoints.openai.api_server",
    "–model", MODEL_NAME,
    "–port", str(PORT),
    "–host", "0.0.0.0",
    "–tensor-parallel-size", "1",
    "–gpu-memory-utilization", "0.9",
    "–disable-log-requests"
    ]

    # 启动子进程
    # 注意:在实际生产中,通常直接用命令行运行,而不是用 python 脚本启动
    # 这里为了演示完整性,用 subprocess 启动
    process = subprocess.Popen(cmd)

    print("⏳ 等待服务就绪 (约 10-20 秒)…")
    time.sleep(15) # 简单等待,实际应检查健康接口

    print("✅ 服务已启动!请保持此窗口运行。")
    print("👉 现在请打开另一个终端,运行 'start_vllm_client.py' 进行测试。")

    # 阻塞主进程,保持服务运行
    try:
    process.wait()
    except KeyboardInterrupt:
    print("\\n🛑 正在停止服务…")
    process.terminate()

    if __name__ == "__main__":
    start_server()

    👉 如何运行服务端:
    在 终端 1 运行:

    python start_vllm_server.py

    (看到 Uvicorn running on http://0.0.0.0:8000 表示成功)


    第 2 步:运行客户端

    📄 文件:start_vllm_client.py

    python

    """
    方案二 (步骤 2):vLLM 客户端并发测试
    功能:通过 HTTP 请求向 vLLM 服务端发送大量并发问题。
    """

    import asyncio
    import aiohttp
    import time
    from tqdm import tqdm

    # ================= 配置区域 =================
    # 服务端地址 (必须与启动服务时的端口一致)
    API_URL = "http://localhost:8000/v1/chat/completions"
    MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"

    # 构造 50 个测试问题
    PROMPTS = [f"问题 {i}: 今天天气不错,适合做什么?" for i in range(50)]

    async def send_request(session, prompt, req_id):
    """发送单个 HTTP 请求"""
    payload = {
    "model": MODEL_NAME,
    "messages": [{"role": "user", "content": prompt}],
    "temperature": 0.7,
    "max_tokens": 100
    }

    start = time.time()
    try:
    async with session.post(API_URL, json=payload, timeout=aiohttp.ClientTimeout(total=60)) as resp:
    if resp.status == 200:
    data = await resp.json()
    content = data["choices"][0]["message"]["content"]
    latency = time.time() – start
    return {"id": req_id, "success": True, "latency": latency, "content": content[:50]}
    else:
    return {"id": req_id, "success": False, "error": f"Status {resp.status}"}
    except Exception as e:
    return {"id": req_id, "success": False, "error": str(e)}

    async def main():
    print(f"🚀 开始向 {API_URL} 发送 {len(PROMPTS)} 个并发请求…")

    # 创建 TCP 连接器,限制最大连接数以防本地端口耗尽
    connector = aiohttp.TCPConnector(limit=len(PROMPTS))

    start_time = time.time()

    async with aiohttp.ClientSession(connector=connector) as session:
    # 创建所有任务
    tasks = [send_request(session, p, i) for i, p in enumerate(PROMPTS)]

    # 并发执行所有任务,并显示进度条
    results = await tqdm.gather(*tasks, desc="Processing")

    total_time = time.time() – start_time

    # 统计结果
    success_count = sum(1 for r in results if r["success"])
    avg_latency = sum(r["latency"] for r in results if r["success"]) / success_count if success_count > 0 else 0

    print("\\n" + "="*50)
    print("📊 客户端测试结果")
    print("="*50)
    print(f"总请求数:{len(results)}")
    print(f"成功数:{success_count}")
    print(f"总耗时:{total_time:.2f} 秒")
    print(f"平均延迟:{avg_latency:.3f} 秒/请求")
    print(f"吞吐量:{success_count/total_time:.2f} 请求/秒")
    print("="*50)

    # 展示一个成功结果
    for r in results:
    if r["success"]:
    print(f"\\n💡 示例回答 (ID:{r['id']}): {r['content']}…")
    break

    if __name__ == "__main__":
    # 运行异步主函数
    asyncio.run(main())

    👉 如何运行客户端:
    在 终端 2 (保持终端 1 的服务端运行) 运行:

    python start_vllm_client.py


    💡 总结与建议

  • 想最简单、最快跑通?
    👉 选 方案一 (离线模式)。只需一个文件,没有网络开销,代码逻辑最直观。适合你自己跑数据。

  • 想做个服务给别人用?
    👉 选 方案二 (在线模式)。虽然麻烦点(要开两个窗口),但这是标准的生产环境部署方式。你的前端、Java 后端、手机 App 都可以通过 HTTP 访问它。

  • 关于并发原理:

    • 方案一:vLLM 在 llm.generate(list) 内部自动把你的 50 个问题打包成一个巨大的 Batch,一次性塞进 GPU 计算。
    • 方案二:客户端瞬间发出 50 个 HTTP 请求,vLLM 服务端接收到后,同样会将它们合并成一个 Batch 处理(Continuous Batching),所以即使是通过网络,速度也非常快。
  • TensorRT

  • 自动编译阶段:一键下载千问模型并编译成 TensorRT 引擎。
  • 并发推理阶段:加载引擎并进行高并发测试。
  • ⚠️ 重要前提:

  • 硬件:必须使用 NVIDIA GPU (建议显存 16GB+ 运行 7B 模型,或 8GB+ 运行 1.5B 模型)。
  • 环境:TensorRT-LLM 对环境要求极高。强烈建议在 NVIDIA 官方 Docker 容器中运行,否则在本地物理机安装依赖会非常痛苦且容易失败。
    • 推荐启动命令 (如果你用 Docker):
      bash docker run –gpus all -it –rm -v $(pwd):/workspace nvcr.io/nvidia/tensorrt_llm:0.14.0-py3
    • 进入容器后,执行下方的 install_deps.sh 安装额外依赖。

    🚀 全自动一键运行脚本 (run_trt_qwen.py)

    将以下代码保存为 run_trt_qwen.py。
    逻辑:脚本会自动检测是否有编译好的引擎。如果没有,它会自动执行编译过程;如果有,直接进行并发测试。

    python

    import os
    import sys
    import subprocess
    import time
    import argparse
    from pathlib import Path

    # ================= 配置区域 =================
    # 模型选择:根据显存大小选择
    # 显存 < 8GB: 使用 "Qwen/Qwen2.5-1.5B-Instruct"
    # 显存 >= 12GB: 使用 "Qwen/Qwen2.5-7B-Instruct"
    MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct"

    # 输出目录 (编译后的引擎和中间文件存放处)
    OUTPUT_DIR = "./trt_engine_output"
    # 工作目录 (用于构建脚本)
    WORKSPACE_DIR = "./trt_build_workspace"

    # 并发测试配置
    NUM_REQUESTS = 50 # 并发请求数量
    MAX_OUTPUT_LEN = 100 # 每个回答最大长度

    def print_header(text):
    print("\\n" + "="*60)
    print(f" {text}")
    print("="*60 + "\\n")

    def run_command(cmd, cwd=None):
    """运行 shell 命令并实时打印输出"""
    print(f"🔧 执行命令: {' '.join(cmd)}")
    process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    for line in process.stdout:
    print(line, end='')
    process.wait()
    if process.returncode != 0:
    raise Exception(f"命令执行失败: {' '.join(cmd)}")
    return True

    def build_engine():
    """步骤 1: 下载模型并编译 TensorRT 引擎"""
    print_header("步骤 1: 构建 TensorRT 引擎 (首次运行需耗时 5-10 分钟)")

    if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)
    if not os.path.exists(WORKSPACE_DIR):
    os.makedirs(WORKSPACE_DIR)

    # 1. 准备构建脚本 (使用 TRT-LLM 自带的 examples 脚本逻辑)
    # 注意:这里调用的是 trtllm-build 命令,这是 TRT-LLM 的核心编译工具
    # 为了简化,我们直接使用 Python API 进行构建,这样更灵活且无需写复杂的 shell 脚本

    build_script = f"""
    import tensorrt_llm
    from tensorrt_llm.models import QWenForCausalLM
    from tensorrt_llm.builders import build_engine
    from transformers import AutoTokenizer
    import torch

    model_name = "{MODEL_NAME}"
    output_dir = "{OUTPUT_DIR}"

    print(f"正在加载模型:{{model_name}} …")
    # 加载 HF 模型
    model = QWenForCausalLM.from_hugging_face(model_name, dtype="float16")

    print("正在编译引擎 (这可能比较慢,请耐心等待)…")
    # 构建配置
    config = tensorrt_llm.BuildConfig(
    max_batch_size=64, # 支持的最大并发数
    max_input_len=1024,
    max_output_len={MAX_OUTPUT_LEN},
    max_beam_width=1,
    use_gpt_attention_plugin="float16",
    use_gemm_plugin="float16",
    enable_context_fmha=True # 启用 Flash Attention 加速
    )

    # 编译并保存
    build_engine(model, config).save_to_directory(output_dir)
    print("✅ 引擎构建完成!")
    """

    # 将构建逻辑写入临时文件并运行
    temp_build_file = "temp_build_engine.py"
    with open(temp_build_file, "w") as f:
    f.write(build_script)

    try:
    run_command([sys.executable, temp_build_file])
    print("✅ 引擎构建成功!文件位于:", OUTPUT_DIR)
    except Exception as e:
    print(f"❌ 引擎构建失败:{e}")
    print("💡 提示:请确保已正确安装 tensorrt_llm 且在 NVIDIA Docker 环境中运行。")
    sys.exit(1)
    finally:
    if os.path.exists(temp_build_file):
    os.remove(temp_build_file)

    def run_concurrent_inference():
    """步骤 2: 加载引擎并进行高并发推理"""
    print_header("步骤 2: 执行高并发推理测试")

    try:
    from tensorrt_llm.runtime import ModelRunner
    from transformers import AutoTokenizer
    import torch
    except ImportError:
    print("❌ 无法导入 tensorrt_llm。请确认安装是否正确。")
    sys.exit(1)

    # 1. 加载 Tokenizer
    print(f"正在加载 Tokenizer: {MODEL_NAME} …")
    tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, pad_token='<|extra_0|>')

    # 2. 初始化 Runner (加载编译好的引擎)
    print(f"正在加载 TensorRT 引擎从:{OUTPUT_DIR} …")
    runner = ModelRunner.from_dir(engine_dir=OUTPUT_DIR, rank=0)

    # 3. 准备并发数据
    prompts = [f"问题 {i}: 请用简短的语言解释人工智能。" for i in range(NUM_REQUESTS)]

    # 预处理输入 (Tokenize)
    input_ids_list = []
    for prompt in prompts:
    # 应用 Chat Template (千问模型需要)
    messages = [{"role": "user", "content": prompt}]
    text_input = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    input_ids = tokenizer.encode(text_input, return_tensors="pt").squeeze(0).cuda()
    input_ids_list.append(input_ids)

    print(f"🚀 开始处理 {NUM_REQUESTS} 个并发请求…")
    start_time = time.time()

    # 4. 批量推理 (Batch Inference)
    # TRT-LLM 的 generate 方法天然支持 Batch 输入
    # input_ids 需要填充到相同长度 (pad)
    max_len = max(len(ids) for ids in input_ids_list)
    padded_input_ids = torch.full((len(input_ids_list), max_len), tokenizer.pad_token_id, dtype=torch.int32, device='cuda')
    input_lengths = torch.tensor([len(ids) for ids in input_ids_list], dtype=torch.int32, device='cuda')

    for i, ids in enumerate(input_ids_list):
    padded_input_ids[i, -len(ids):] = ids

    with torch.no_grad():
    # 执行生成
    outputs = runner.generate(
    inputs=padded_input_ids,
    input_lengths=input_lengths,
    max_new_tokens=MAX_OUTPUT_LEN,
    end_id=tokenizer.eos_token_id,
    pad_id=tokenizer.pad_token_id,
    temperature=0.7,
    top_k=1,
    streaming=False
    )

    end_time = time.time()
    total_time = end_time – start_time

    # 5. 解析结果并统计
    # outputs shape: [batch, beam, seq_len]
    generated_ids = outputs[:, 0, :]

    success_count = 0
    total_output_tokens = 0

    print("\\n💡 部分结果示例:")
    for i in range(min(3, NUM_REQUESTS)):
    output_ids = generated_ids[i]
    # 解码 (跳过输入部分)
    input_len = input_lengths[i].item()
    text = tokenizer.decode(output_ids[input_len:], skip_special_tokens=True)
    print(f"[{i}] {text[:80]}…")
    total_output_tokens += len(output_ids) – input_len
    success_count += 1

    # 性能统计
    tokens_per_sec = total_output_tokens / total_time
    req_per_sec = success_count / total_time

    print("\\n" + "="*60)
    print("📊 TensorRT-LLM 并发测试结果")
    print("="*60)
    print(f"模型:{MODEL_NAME}")
    print(f"并发请求数:{NUM_REQUESTS}")
    print(f"总耗时:{total_time:.2f} 秒")
    print(f"总生成 Token 数:{total_output_tokens}")
    print(f"吞吐量 (Tokens/s): {tokens_per_sec:.2f}")
    print(f"请求处理速度 (Req/s): {req_per_sec:.2f}")
    print("="*60)

    runner.close()

    def main():
    parser = argparse.ArgumentParser(description="TensorRT-LLM Qwen 并发测试")
    parser.add_argument("–rebuild", action="store_true", help="强制重新编译引擎")
    args = parser.parse_args()

    engine_exists = os.path.exists(os.path.join(OUTPUT_DIR, "rank0.engine")) or os.path.exists(OUTPUT_DIR)

    if args.rebuild or not engine_exists:
    build_engine()

    run_concurrent_inference()

    if __name__ == "__main__":
    main()


    🛠️ 如何真正“直接拿到就能用”?

    由于 TensorRT-LLM 的依赖极其复杂(需要特定版本的 CUDA, TensorRT, PyTorch, Transformers),在普通 Linux/Windows 上直接 pip install 成功率极低。

    最稳妥的“一键运行”方案是使用 Docker。

    1. 准备环境 (只需做一次)

    确保你安装了 NVIDIA Docker Toolkit。然后运行以下命令拉取官方镜像并启动容器:

    # 拉取最新的 TRT-LLM 镜像 (约 10GB)
    docker pull nvcr.io/nvidia/tensorrt_llm:0.14.0-py3

    # 启动容器,挂载当前目录以便代码互通
    docker run –gpus all -it –rm \\
    -v $(pwd):/workspace \\
    -w /workspace \\
    nvcr.io/nvidia/tensorrt_llm:0.14.0-py3 \\
    bash

    2. 在容器内安装额外依赖

    进入容器后(你会看到命令行提示符变了),执行:

    # 安装 huggingface hub 和 tqdm (镜像里可能缺这些)
    pip install huggingface_hub tqdm aiohttp

    # 登录 HuggingFace (如果需要下载千问模型,有时需要 token,公开模型可跳过)
    # huggingface-cli login

    3. 运行脚本

    将上面的 run_trt_qwen.py 保存到当前目录,然后在容器内运行:

    # 第一次运行会自动下载模型并编译 (耗时 5-10 分钟)
    python run_trt_qwen.py

    # 第二次运行 (引擎已存在,直接秒开并发测试)
    python run_trt_qwen.py


    💡 代码核心原理解析

  • 两阶段执行:

    • Build 阶段:QWenForCausalLM.from_hugging_face 加载权重 -> build_engine 进行图层融合、显存规划、Kernel 优化 -> 保存为 .engine 文件。这是 TRT-LLM 快的原因,因为它把很多运行时的计算提前算好了。
    • Runtime 阶段:ModelRunner.from_dir 加载二进制引擎 -> runner.generate 执行。这一步极快,没有 Python 解释器的开销。
  • 并发实现:

    • 代码中构建了 padded_input_ids (形状 [Batch_Size, Seq_Len])。
    • TRT-LLM 的 C++ 后端会一次性接收整个 Batch,利用 In-flight Batching 技术,在 GPU 内部并行处理这 50 个请求。
    • 你不需要写任何多线程代码,Batch Size 就是并发数。
  • 为什么比 vLLM 难用?

    • vLLM 是“解释执行”,灵活性高,改个参数马上生效。
    • TRT-LLM 是“编译执行”,改个参数(如最大长度)可能需要重新编译。但换来的是极致的延迟降低和显存利用率。
  • ⚠️ 常见问题排查

    • OOM (显存不足):
      • 修改代码中的 MODEL_NAME 为 "Qwen/Qwen2.5-1.5B-Instruct"。
      • 或者在 BuildConfig 中减小 max_batch_size。
    • 下载模型慢:
      • 设置镜像源:export HF_ENDPOINT=https://hf-mirror.com (在中国大陆地区)。
    • 找不到 tensorrt_llm 模块:
      • 务必使用官方 Docker 镜像。本地手动安装极易出错。

    混合使用

    ⚠️ 重要前置条件

    由于要在一个脚本里同时加载两个重型引擎,对显存要求极高:

    • 你需要一张大显存显卡(如 A100 80G, H100, 或 双卡 4090)。
    • 或者,你需要加载不同大小的模型(例如:TRT 跑 1.5B 小模型处理简单问题,vLLM 跑 7B 大模型处理复杂问题),以节省显存。

    下面的代码为了让你能直接运行测试逻辑,做了以下处理:

  • 真实路由逻辑:完全真实的基于内容的分类算法。
  • 引擎加载模拟:由于 TRT 需要预先编译 .engine 文件(无法在单脚本中动态完成),我在代码中保留了真实的 vLLM 加载代码,但对 TRT 部分做了接口级模拟(结构完全一致,只是内部计算换成了模拟延迟)。
    • 注:如果你已经有编译好的 TRT 引擎,只需取消注释 load_trt_engine 中的几行代码即可替换为真实调用。

  • 🚀 真实混合调用代码 (hybrid_direct_call.py)

    python

    import time
    import random
    import asyncio
    from typing import List, Dict, Any, Optional
    from dataclasses import dataclass
    from enum import Enum

    # ================= 配置区域 =================

    # 模型配置
    # 建议:TRT 跑小模型 (快/省显存),vLLM 跑大模型 (强/长上下文)
    VLLM_MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
    TRT_MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct" # 假设 TRT 编译的是这个小模型

    # 路由策略阈值
    MAX_LENGTH_FOR_TRT = 50 # 少于 50 字走 TRT
    KEYWORDS_FOR_VLLM = ["代码", "写一个", "分析", "总结", "推理", "复杂", "长文"]

    # ================= 核心引擎封装类 =================

    class VLLMEngine:
    """
    真实 vLLM 引擎封装
    """
    def __init__(self, model_name: str):
    print(f"🚀 [vLLM] 正在加载模型:{model_name} …")
    try:
    from vllm import LLM, SamplingParams
    self.llm = LLM(
    model=model_name,
    tensor_parallel_size=1,
    gpu_memory_utilization=0.4, # 只占 40% 显存,留给 TRT
    enforce_eager=False,
    disable_log_stats=True
    )
    self.sampling_params = SamplingParams(temperature=0.7, max_tokens=200)
    print("✅ [vLLM] 模型加载完成")
    self.available = True
    except Exception as e:
    print(f"❌ [vLLM] 加载失败 (可能是显存不足): {e}")
    self.available = False

    def generate(self, prompt: str) -> str:
    if not self.available:
    raise RuntimeError("vLLM engine not available")

    # 真实调用 vLLM
    outputs = self.llm.generate([prompt], self.sampling_params)
    return outputs[0].outputs[0].text

    class TRTEngine:
    """
    TensorRT-LLM 引擎封装
    注意:真实使用需要预先编译好 .engine 文件。
    此处为了代码可直接运行演示路由逻辑,实现了真实接口结构,但内部计算做了模拟。
    """
    def __init__(self, model_name: str, engine_dir: Optional[str] = None):
    print(f"🚀 [TRT] 正在初始化引擎:{model_name} …")
    self.model_name = model_name
    self.engine_dir = engine_dir
    self.runner = None
    self.tokenizer = None
    self.available = False

    # 【真实环境开启这里】
    # if engine_dir and os.path.exists(engine_dir):
    # try:
    # from tensorrt_llm.runtime import ModelRunner
    # from transformers import AutoTokenizer
    # import torch
    # self.tokenizer = AutoTokenizer.from_pretrained(model_name)
    # self.runner = ModelRunner.from_dir(engine_dir=engine_dir, rank=0)
    # self.available = True
    # print("✅ [TRT] 引擎加载完成 (真实模式)")
    # except Exception as e:
    # print(f"❌ [TRT] 加载失败:{e}")

    # 【演示模式:如果没有 engine 文件,自动进入模拟模式以便你测试路由逻辑】
    if not self.available:
    print("⚠️ [TRT] 未检测到编译好的引擎文件,已切换到【模拟模式】用于演示路由逻辑。")
    print(" (真实生产中,请在此处加载真实的 ModelRunner)")
    self.available = True # 标记为可用,但内部是模拟的
    self.is_mock = True
    else:
    self.is_mock = False

    def generate(self, prompt: str) -> str:
    if not self.available:
    raise RuntimeError("TRT engine not available")

    if self.is_mock:
    # 模拟 TRT 的极速特性 (0.1s – 0.3s)
    time.sleep(random.uniform(0.1, 0.3))
    return f"[TRT 模拟响应] 针对 '{prompt[:20]}…' 的快速回答。"

    # 【真实环境开启这里】
    # import torch
    # messages = [{"role": "user", "content": prompt}]
    # text_input = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    # input_ids = self.tokenizer.encode(text_input, return_tensors="pt").squeeze(0).cuda()
    # with torch.no_grad():
    # outputs = self.runner.generate(…)
    # return self.tokenizer.decode(outputs…)

    # ================= 智能路由器 (核心逻辑) =================

    class HybridRouter:
    def __init__(self, vllm_engine: VLLMEngine, trt_engine: TRTEngine):
    self.vllm = vllm_engine
    self.trt = trt_engine

    def decide_engine(self, prompt: str) -> str:
    """
    纯内存决策,无网络开销
    返回:'vllm' 或 'trt'
    """
    length = len(prompt)

    # 1. 关键词优先原则 (复杂任务强制 vLLM)
    for kw in KEYWORDS_FOR_VLLM:
    if kw in prompt:
    return 'vllm'

    # 2. 长度原则 (短任务优先 TRT)
    if length <= MAX_LENGTH_FOR_TRT:
    if self.trt.available:
    return 'trt'
    else:
    return 'vllm' # TRT 不可用时降级

    # 3. 默认长任务走 vLLM
    return 'vllm'

    def process(self, prompt: str) -> Dict[str, Any]:
    start_time = time.time()

    # 1. 决策
    target = self.decide_engine(prompt)
    reason = f"Len={len(prompt)}"

    # 2. 执行 (直接函数调用,非 HTTP)
    try:
    if target == 'trt':
    response = self.trt.generate(prompt)
    engine_used = "TensorRT"
    else:
    response = self.vllm.generate(prompt)
    engine_used = "vLLM"

    latency = time.time() – start_time
    return {
    "prompt": prompt[:30] + "…",
    "engine": engine_used,
    "reason": reason,
    "latency": latency,
    "response": response[:50] + "…",
    "success": True
    }
    except Exception as e:
    # 故障降级逻辑 (如果首选挂了,尝试另一个)
    print(f"⚠️ {target} 执行失败:{e}, 尝试降级…")
    fallback = 'vllm' if target == 'trt' else 'trt'
    try:
    if fallback == 'trt' and self.trt.available:
    response = self.trt.generate(prompt)
    engine_used = "TensorRT (Fallback)"
    elif fallback == 'vllm' and self.vllm.available:
    response = self.vllm.generate(prompt)
    engine_used = "vLLM (Fallback)"
    else:
    raise RuntimeError("No fallback available")

    latency = time.time() – start_time
    return {
    "prompt": prompt[:30] + "…",
    "engine": engine_used,
    "reason": f"Fallback from {target}",
    "latency": latency,
    "response": response[:50] + "…",
    "success": True
    }
    except Exception as e2:
    return {
    "prompt": prompt[:30] + "…",
    "engine": "None",
    "reason": "All Failed",
    "latency": time.time() – start_time,
    "response": "",
    "success": False,
    "error": str(e2)
    }

    # ================= 主程序 =================

    def main():
    print("="*60)
    print("🧠 启动混合推理系统 (进程内直连模式)")
    print("="*60)

    # 1. 初始化引擎
    # 注意:如果你的显存不够同时加载 7B+1.5B,vLLM 可能会失败,代码会自动处理
    vllm_eng = VLLMEngine(VLLM_MODEL_NAME)

    # TRT 需要编译好的 engine 目录,这里传 None 会进入模拟模式
    trt_eng = TRTEngine(TRT_MODEL_NAME, engine_dir=None)

    if not vllm_eng.available and not trt_eng.available:
    print("❌ 错误:没有可用的引擎!")
    return

    router = HybridRouter(vllm_eng, trt_eng)

    # 2. 构造测试数据
    test_cases = [
    "你好", # 短 -> TRT
    "1+1 等于几?", # 短 -> TRT
    "写一个 Python 快速排序", # 关键词 -> vLLM
    "请分析量子力学的哲学意义", # 关键词 -> vLLM
    "今天天气怎么样", # 短 -> TRT
    "总结这篇文章的中心思想…", # 关键词 -> vLLM
    "Hello", # 短 -> TRT
    "帮我推理一下这个逻辑题…", # 关键词 -> vLLM
    ] * 3 # 重复几次

    print(f"\\n⚡ 开始处理 {len(test_cases)} 个请求 (无 HTTP 开销)…\\n")

    results = []
    for prompt in test_cases:
    res = router.process(prompt)
    results.append(res)

    # 实时打印
    status = "✅" if res['success'] else "❌"
    print(f"{status} 路由至:{res['engine']:<15} | 耗时:{res['latency']:.3f}s | 原因:{res['reason']}")
    # print(f" 问:{res['prompt']}")
    # print(f" 答:{res['response']}")

    # 3. 统计
    trt_count = sum(1 for r in results if "TensorRT" in r['engine'])
    vllm_count = sum(1 for r in results if "vLLM" in r['engine'])
    avg_latency = sum(r['latency'] for r in results if r['success']) / len(results)

    print("\\n" + "="*60)
    print("📊 最终统计")
    print("="*60)
    print(f"TensorRT 处理数:{trt_count}")
    print(f"vLLM 处理数: {vllm_count}")
    print(f"平均延迟: {avg_latency:.3f} 秒")
    print(f"架构模式: 进程内直接调用 (In-Process Direct Call)")
    print("="*60)

    if __name__ == "__main__":
    main()

    💡 代码核心改动点

  • 使用本地:

    • 没有 aiohttp,没有 requests,没有 port,没有 url。
    • 所有调用都是 engine.generate(prompt) 这样的本地函数调用。
  • 双引擎共存 (HybridRouter):

    • VLLMEngine 类:真实实例化 vllm.LLM。
    • TRTEngine 类:
      • 真实模式:如果你有编译好的 .engine 文件,取消注释中的 ModelRunner.from_dir 代码,它就是真实的 TRT 调用。
      • 演示模式:如果没有 engine 文件,它会自动进入“模拟模式”,模拟 TRT 的低延迟特性,确保你复制代码后立刻就能跑通路由逻辑,而不会因为缺少编译文件报错。
  • 显存管理:

    • 在初始化 vLLM 时,设置了 gpu_memory_utilization=0.4,只占用 40% 显存,预留空间给 TRT 引擎(如果需要同时加载真实模型,这点至关重要)。
  • 🛠️ 如何真正使用真实 TRT?

    如果你想把模拟换成真实的:

  • 先编译模型(这需要单独运行一次,参考之前的 TRT 编译步骤):
    生成 ./trt_engine_output 目录。
  • 修改代码:python

    # 在 main 函数中
    trt_eng = TRTEngine(TRT_MODEL_NAME, engine_dir="./trt_engine_output")

  • 取消注释:
    在 TRTEngine 类中,把 if engine_dir … 块里的代码取消注释,把 self.is_mock = True 的逻辑注释掉。
  • VLLM+Ray(CPU调度)

    核心思路:Ray + vLLM 分布式推理

    架构变化:

    • 之前 (单进程):主进程加载模型 -> 循环处理列表。受限于单卡显存,无法利用多卡加速批量处理。
    • 现在 (Ray 分布式):
    • Head Node:负责任务分发和调度。
    • Worker Nodes (Actors):每个 Worker 独占一张 GPU,加载一个 vLLM 实例。
    • 并行策略:将大问题列表切分 (Chunking),分发给多个 Worker 同时处理,最后汇总结果。

    优势:

    • 线性加速:如果有 4 张卡,理论速度提升接近 4 倍。
    • 显存隔离:每个 Worker 独立占用显存,避免单进程管理多卡的复杂性。
    • 弹性扩展:代码无需大改,即可从单机扩展到多机集群。

    🛠️ 第一步:环境安装

    你需要额外安装 ray 和 ray[default]:

    # 基础依赖
    pip install vllm tqdm

    # 安装 Ray (支持分布式调度)
    pip install "ray[default]"


    📄 完整代码:vllm_ray_qwen.py

    这段代码会自动检测你机器上的 GPU 数量,启动对应数量的 Worker,并并行处理任务。

    python

    import ray
    import time
    from typing import List, Dict, Any
    from tqdm import tqdm
    import os

    # ================= 配置区域 =================
    MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
    # 如果显存较小,请改为 "Qwen/Qwen2.5-1.5B-Instruct"

    # 每个 vLLM 实例占用的显存比例 (0.9 表示 90%)
    # 注意:如果是多卡,每张卡都会独立加载一个模型副本
    GPU_MEMORY_UTILIZATION = 0.9

    # 采样参数
    MAX_TOKENS = 256
    TEMPERATURE = 0.7

    # ================= Ray Actor 定义 =================

    @ray.remote(num_gpus=1)
    class VLLMWorker:
    """
    Ray Actor: 每个实例独占一张 GPU,运行一个独立的 vLLM 引擎
    """
    def __init__(self, model_name: str):
    # 关键:在 Actor 内部导入 vllm,确保它在 GPU 上下文中初始化
    from vllm import LLM, SamplingParams

    # 获取当前 Actor 绑定的 GPU ID (用于日志调试)
    gpu_id = os.environ.get("CUDA_VISIBLE_DEVICES", "Unknown")
    print(f"🚀 [Worker] 正在 GPU {gpu_id} 上加载模型:{model_name} …")

    self.llm = LLM(
    model=model_name,
    tensor_parallel_size=1, # 每个 Worker 只用 1 张卡
    gpu_memory_utilization=GPU_MEMORY_UTILIZATION,
    enforce_eager=False,
    disable_log_stats=True
    )

    self.sampling_params = SamplingParams(
    temperature=TEMPERATURE,
    max_tokens=MAX_TOKENS,
    top_p=0.8
    )
    print(f"✅ [Worker] GPU {gpu_id} 准备就绪")

    def generate_batch(self, prompts: List[str]) -> List[str]:
    """
    处理一批提示词,返回生成的文本列表
    """
    if not prompts:
    return []

    outputs = self.llm.generate(prompts, self.sampling_params)
    return [out.outputs[0].text for out in outputs]

    # ================= 主调度逻辑 =================

    def chunkify(data: List[Any], n_chunks: int) -> List[List[Any]]:
    """将列表均匀切分成 n 份"""
    avg = len(data) / float(n_chunks)
    out = []
    last = 0.0
    while last < len(data):
    out.append(data[int(last):int(last + avg)])
    last += avg
    return out

    def main():
    print("="*60)
    print("🧠 启动 Ray + vLLM 分布式推理系统")
    print("="*60)

    # 1. 初始化 Ray
    # ignore_reinit_error=True 防止重复初始化报错
    # 如果是在多机集群,这里需要指定 address="ip:port"
    if not ray.is_initialized():
    ray.init(ignore_reinit_error=True, log_to_driver=True)

    print(f"✅ Ray 已初始化。集群资源:{ray.cluster_resources()}")

    # 自动检测可用 GPU 数量
    num_gpus = int(ray.cluster_resources().get("GPU", 0))
    if num_gpus == 0:
    print("❌ 未检测到 GPU 资源,请检查 NVIDIA 驱动和 Ray 配置。")
    return

    print(f"📡 检测到 {num_gpus} 张可用 GPU,将启动 {num_gpus} 个 Worker。")

    # 2. 创建 Worker 池
    # 每个 Worker 会独占一张 GPU (因为 @ray.remote(num_gpus=1))
    workers = [VLLMWorker.remote(MODEL_NAME) for _ in range(num_gpus)]

    # 等待所有 Worker 加载完成 (阻塞直到所有模型加载完毕)
    print("⏳ 等待所有 Worker 加载模型完成…")
    ray.get([w.__ready__() for w in workers]) if hasattr(workers[0], '__ready__') else time.sleep(2)
    # 注:上面这行是伪代码逻辑,实际我们通过在 __init__ 打印日志来确认,
    # 或者简单 sleep 等待,因为 ray.get(worker.init.remote()) 模式需要显式定义 init 方法。
    # 为了简化,我们直接在第一次调用时隐式等待,或者加个简单的 barrier。
    # 这里为了稳健,我们调用一个空方法或直接进行任务分发,Ray 会自动排队等待初始化完成。

    # 显式Barrier: 确保所有 worker 初始化完成
    ray.get([w.generate_batch.remote(["test"]) for w in workers]) # 预热/同步
    print("✅ 所有 Worker 已就绪。")

    # 3. 准备数据 (模拟 200 个问题)
    all_prompts = [f"问题 {i}: 请简要解释量子纠缠。" for i in range(200)]
    print(f"📝 待处理任务总数:{len(all_prompts)}")

    # 4. 任务切分与分发
    # 将 200 个问题切成 num_gpus 份,每份给一个 Worker
    chunks = chunkify(all_prompts, num_gpus)

    print(f"⚡ 开始并发处理 (分为 {len(chunks)} 个批次)…")
    start_time = time.time()

    # 异步提交所有任务
    futures = []
    for i, chunk in enumerate(chunks):
    if chunk:
    # remote() 返回的是 ObjectRef (未来对象),不会阻塞
    future = workers[i].generate_batch.remote(chunk)
    futures.append(future)

    # 5. 收集结果 (阻塞直到所有任务完成)
    # ray.get 会并行等待所有结果返回
    all_results = ray.get(futures)

    end_time = time.time()
    total_time = end_time – start_time

    # 6. 结果汇总与统计
    # all_results 是一个列表的列表,需要展平
    flat_results = [text for chunk_res in all_results for text in chunk_res]

    total_tokens_estimated = sum(len(text.split()) * 1.3 for text in flat_results) # 粗略估算 token 数

    print("\\n" + "="*60)
    print("📊 分布式测试报告")
    print("="*60)
    print(f"GPU 数量:{num_gpus}")
    print(f"总任务数:{len(flat_results)}")
    print(f"总耗时:{total_time:.2f} 秒")
    print(f"平均每秒处理请求数 (QPS): {len(flat_results)/total_time:.2f}")
    print(f"预估吞吐量 (Tokens/s): {total_tokens_estimated/total_time:.2f}")
    print("="*60)

    # 展示部分结果
    print("\\n💡 部分结果示例:")
    for i, text in enumerate(flat_results[:3]):
    print(f"[{i}] {text[:100]}…")

    # 7. 释放资源
    ray.shutdown()
    print("\\n👋 Ray 会话已关闭。")

    if __name__ == "__main__":
    main()


    💡 代码核心解析

    1. @ray.remote(num_gpus=1)

    这是最关键的一行。

    • 它告诉 Ray:“这个类 (VLLMWorker) 运行时必须独占 1 张 GPU”。
    • 当你创建 workers = [VLLMWorker.remote() for _ in range(4)] 时,Ray 会自动检测你有几张卡。
      • 如果你有 4 张卡,它会启动 4 个进程,每个进程绑定一张卡。
      • 如果你只有 1 张卡,它会排队执行(或者报错,取决于配置),通常建议 Worker 数量 <= GPU 数量。
    2. 进程隔离与显存管理
    • 每个 VLLMWorker 都是一个独立的 Python 进程。
    • 每个进程内部都会执行 from vllm import LLM,这意味着每张显卡上都加载了一个完整的模型副本。
    • 优点:互不干扰,一个 Worker 崩了不影响其他。
    • 缺点:显存消耗是 N * 模型大小。如果你的显存不够加载 N 个副本,需要减少 Worker 数量或改用“张量并行”(Tensor Parallelism,即多个卡合力跑一个大模型,而不是每个卡跑一个小模型)。
      • 本代码采用的是 数据并行 (Data Parallelism) 策略:多卡各跑各的,处理不同数据。适合高吞吐场景。
    3. 任务切分 (chunkify)
    • 我们将 200 个问题平均分给 4 个 Worker,每个处理 50 个。
    • worker.generate_batch.remote(chunk) 是异步的。主程序发出指令后立刻继续,不会等待该 Worker 算完。
    • ray.get(futures) 在最后统一等待所有结果回来。
    4. 性能提升原理
    • 单卡模式:处理 200 个问题,串行排队。
    • Ray 多卡模式:4 张卡同时处理,理论上速度提升 4 倍。
    • vLLM 内部的 PagedAttention 保证了单个 Worker 在处理那 50 个问题时的内部并发效率,而 Ray 保证了多卡之间的宏观并发效率。

    🚀 如何运行?

    场景 A:单机多卡 (最常见)

    直接运行脚本即可。Ray 会自动发现本地所有 GPU。

    python vllm_ray_qwen.py

    观察输出:你会看到 [Worker] 正在 GPU 0 上加载…, [Worker] 正在 GPU 1 上加载… 等日志。

    场景 B:多机集群 (进阶)

    如果你想用 2 台机器(每台 4 卡,共 8 卡):

  • 机器 1 (Head):bash

    ray start –head –port=6379

  • 机器 2 (Worker):bash

    ray start –address='机器1IP:6379'

  • 运行脚本 (在机器 1 上运行):bash

    python vllm_ray_qwen.py
    Ray 会自动把任务调度到两台机器的 8 张卡上运行。

  • ⚠️ 注意事项

  • 显存爆炸风险:

    • 如果你有 4 张 24GB 的卡,模型是 7B (约 14GB 显存)。
    • 4 个 Worker = 4 * 14GB = 56GB 显存需求。你的总显存是 96GB,可以运行。
    • 如果模型是 70B,单卡放不下,这种“数据并行”模式就跑不通了。这时需要用 vLLM 的 tensor_parallel_size=4 (4 张卡合力跑一个模型),但这需要修改代码逻辑(只启动 1 个 Worker,但让它用 4 张卡)。
    • 本代码适用于:单卡能放下模型,且有多余显卡想提升吞吐量的场景。
  • 首次运行慢:

    • 每个 Worker 都要下载/加载一次模型。第一次运行会花较多时间初始化,后续推理会非常快。
  • 赞(0)
    未经允许不得转载:171主机测评 » VLLM和TensorRT
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址