首页 / 资讯中心 / 文章详情

TensorRT-LLM 部署 Qwen1.5 实战:从引擎编译到生产级推理服务

TensorRT-LLM 部署 Qwen1.5 实战:从引擎编译到生产级推理服务 ★ FEATURED ARTICLE
简介这份资源面向希望掌握大语言模型高效推理与落地部署的开发者聚焦于使用TensorRT-LLM对Qwen1.5进行推理优化与工程化部署适合具备一定深度学习基础、想突破模型推理速度与显存瓶颈的中高级技术人员。压缩包共5个文件以4个Python脚本和1份Markdown说明文档为主整体约25KB脚本分别承担模型结构定义、层工具封装、权重转换与推理流程等职责文档则梳理整体操作脉络。目前已有593人学习下载可作为入门大模型部署的实战参考。读者可借助源码与流程教程理解从权重转换、引擎构建到推理运行的完整链路掌握层融合、精度校准等优化思路并对照脚本排查环境配置与部署中的常见问题降低大模型部署的实践门槛。1. TensorRT-LLM 部署 Qwen1.5从权重到推理服务的完整链路Qwen1.5 系列模型在中文场景下的表现让不少团队动了私有化部署的念头但真正把权重跑成线上服务时很多人会卡在同一个地方用 PyTorch 直接加载推理显存吃紧、吞吐上不去并发一上来延迟就崩。TensorRT-LLM 就是冲着这个问题来的——它把模型编译成 TensorRT 引擎用 CUDA Graph、Paged KV Cache、连续批处理这些手段把 GPU 利用率压榨出来。这套方案适合手里有 A100/H100/L40S 这类卡、想把 Qwen1.5 做成稳定 API 服务的团队也适合想搞清楚大模型推理底层到底在做什么的工程师。下面按「编译引擎 → 跑通推理 → 接服务 → 排坑」的顺序把整条链路拆开讲。2. 环境准备与 TensorRT-LLM 编译链路为什么不能直接 pip install2.1 版本矩阵是第一个拦路虎TensorRT-LLM 不是一个纯 Python 包它依赖 TensorRT、CUDA、cuBLAS、NCCL、MPI 这一整套底层库版本之间咬得很死。我见过最常见的翻车场景是照着某篇教程pip install tensorrt_llm结果 import 就报undefined symbol折腾半天发现是 TensorRT 版本和 CUDA 对不上。比较稳的做法是用 NVIDIA 官方提供的 Docker 镜像作为起点而不是在裸机上硬装。截至我写这篇时的经验TensorRT-LLM 0.9.x 到 0.10.x 对应 CUDA 12.1/12.4、TensorRT 10.xQwen1.5 的模型结构在 0.9 之后支持得比较完整。如果你用的是 0.8 以前的版本Qwen1.5 的 RoPE 实现和 attention 层可能对不上编译出来的引擎输出会乱码。# 拉取官方镜像注意 tag 要和你的驱动匹配 docker pull nvcr.io/nvidia/tensorrt-llm:0.10.0 # 启动容器挂载模型目录和工作目录 docker run --gpus all -it --rm \ --shm-size16g \ -v /data/models:/models \ -v /data/workspace:/workspace \ nvcr.io/nvidia/tensorrt-llm:0.10.0 bash--shm-size这个参数别省TensorRT-LLM 在编译和推理时会用共享内存做进程间通信默认 64MB 在编译大模型时直接 OOM。--gpus all确保容器能看到所有卡如果你只想用特定几张卡用--gpus device0,1这种写法。2.2 从 HuggingFace 权重到 TensorRT 引擎TensorRT-LLM 的编译流程分两步先把 HuggingFace 格式的权重转成 TensorRT-LLM 自己的 checkpoint 格式再根据 checkpoint 构建 TensorRT 引擎。这两步分开是有原因的——checkpoint 转换只做一次引擎构建可以针对不同 batch size、不同精度反复做。# 第一步转换权重格式 python3 convert_checkpoint.py \ --model_dir /models/Qwen1.5-7B-Chat \ --output_dir /workspace/qwen1.5-7b-ckpt \ --dtype float16 \ --tp_size 1 # 第二步构建引擎 trtllm-build \ --checkpoint_dir /workspace/qwen1.5-7b-ckpt \ --output_dir /workspace/qwen1.5-7b-engine \ --gemm_plugin float16 \ --gpt_attention_plugin float16 \ --max_batch_size 8 \ --max_input_len 2048 \ --max_output_len 1024 \ --max_num_tokens 4096--tp_size是张量并行度单卡就写 1如果你有 4 张卡想跑 72B 模型这里写 4同时trtllm-build也要加--tp_size 4。--max_batch_size决定了引擎能接受的最大并发请求数这个值直接影响显存占用——设大了显存爆设小了吞吐上不去。--max_input_len和--max_output_len是输入输出的 token 上限Qwen1.5 的上下文窗口是 32K但实际部署时很少有人真开到 32K因为 KV Cache 会吃掉大量显存。--gpt_attention_plugin和--gemm_plugin这两个插件是性能关键它们把 attention 计算和矩阵乘法的 kernel 做了融合优化。如果你编译时忘了加推理速度可能只有加了插件的三分之一。2.3 编译产物里到底有什么编译完成后/workspace/qwen1.5-7b-engine目录下会生成.engine文件和config.json。.engine是序列化后的 TensorRT 引擎跟硬件绑定——你在 A100 上编译的引擎拿到 H100 上跑不了甚至同型号卡但驱动版本不同也可能出问题。所以生产环境里引擎编译要放在目标机器上做或者至少保证编译和运行的 CUDA/TensorRT 版本完全一致。config.json里记录了模型的 vocab size、hidden dim、num layers 这些元信息推理时 runtime 会读这个文件来初始化。如果你手动改过模型结构这个文件也要同步改否则会出现维度不匹配的报错。3. 跑通第一个推理请求Python runtime 与参数调优3.1 最小可运行示例引擎编译好之后用 TensorRT-LLM 的 Python runtime 加载并推理。下面这段代码是我在调试时最常用的最小示例去掉了一切不必要的封装import tensorrt_llm from tensorrt_llm.runtime import ModelRunner from transformers import AutoTokenizer # 加载 tokenizer注意 Qwen1.5 用的是自己的 tokenizer tokenizer AutoTokenizer.from_pretrained( /models/Qwen1.5-7B-Chat, trust_remote_codeTrue ) # 初始化 runner指定引擎目录 runner ModelRunner.from_dir( engine_dir/workspace/qwen1.5-7b-engine, rank0 ) # 构造输入 prompt 用一句话解释什么是张量并行 input_ids tokenizer.encode(prompt, return_tensorspt).cuda() # 生成 output_ids runner.generate( input_ids, max_new_tokens256, end_idtokenizer.eos_token_id, pad_idtokenizer.pad_token_id, temperature0.7, top_p0.9 ) # 解码输出 output_text tokenizer.decode(output_ids[0], skip_special_tokensTrue) print(output_text)ModelRunner.from_dir会自动读取引擎目录下的config.json和.engine文件rank参数在多卡张量并行时用来指定当前进程的卡号。generate方法里的max_new_tokens控制生成长度temperature和top_p是采样参数这些和 HuggingFace 的generate语义一致。3.2 三个必调参数batch size、KV Cache 和精度max_batch_size在编译时就已经固定了运行时不能超过这个值。但实际能跑多少并发还取决于 KV Cache 的显存占用。Qwen1.5-7B 在 float16 下每个 token 的 KV Cache 大约是 2 * num_layers * hidden_dim * 2 bytes7B 模型 32 层、hidden 4096算下来每个 token 约 0.5MB。如果你设max_input_len2048、max_output_len1024单个请求最多占 1.5GB 左右的 KV Cache8 个并发就是 12GB加上模型权重本身的 14GB一张 40GB 的 A100 刚好够用。精度选择上float16 是最常用的精度损失可接受显存和速度平衡得最好。如果你对精度要求极高可以用 float32但显存翻倍、速度减半。int8 量化能进一步省显存但 Qwen1.5 的 int8 量化需要额外做校准而且中文场景下量化后的输出质量下降比英文明显我一般不建议在中文对话场景用 int8。Paged KV Cache是 TensorRT-LLM 的一个重要特性它把 KV Cache 分成固定大小的 block 来管理类似操作系统的虚拟内存分页。开启后显存碎片大幅减少长序列场景下能多跑不少并发。在trtllm-build时加--paged_kv_cache enable就能打开但注意这个特性对max_num_tokens有要求一般设成max_batch_size * max_input_len的 1.5 倍左右比较安全。3.3 流式输出怎么接线上服务不可能等模型全部生成完再返回流式输出是刚需。TensorRT-LLM 的 runtime 支持逐 token 返回但需要你自己在 Python 层做封装from tensorrt_llm.runtime import ModelRunner runner ModelRunner.from_dir(engine_dir/workspace/qwen1.5-7b-engine) # 使用 streaming 模式 for output_ids in runner.generate( input_ids, max_new_tokens512, streamingTrue ): # 每次拿到一个新 token 就解码并推送 token_text tokenizer.decode(output_ids[0][-1:], skip_special_tokensTrue) yield token_text这里有个坑streamingTrue时generate返回的是一个生成器每次 yield 的是当前步的完整 output_ids你需要自己取最后一个 token 来解码。如果直接 decode 整个序列会出现重复输出的问题。4. 接入生产服务从单机推理到 API 网关4.1 用 FastAPI 包一层推理服务单机跑通之后下一步是把它变成 HTTP 服务。FastAPI 是最常见的选择轻量、异步支持好。下面是一个简化但可用的服务端代码from fastapi import FastAPI from fastapi.responses import StreamingResponse from pydantic import BaseModel import asyncio app FastAPI() class GenerateRequest(BaseModel): prompt: str max_new_tokens: int 256 temperature: float 0.7 app.post(/generate) async def generate(req: GenerateRequest): input_ids tokenizer.encode(req.prompt, return_tensorspt).cuda() async def token_stream(): for output_ids in runner.generate( input_ids, max_new_tokensreq.max_new_tokens, temperaturereq.temperature, streamingTrue ): token_text tokenizer.decode( output_ids[0][-1:], skip_special_tokensTrue ) yield fdata: {token_text}\n\n return StreamingResponse(token_stream(), media_typetext/event-stream)这里用 SSEServer-Sent Events做流式推送前端用EventSource接收。注意runner.generate是阻塞的在 async 函数里直接调用会卡住事件循环生产环境要用run_in_executor包一层或者用 TensorRT-LLM 自带的Executor做异步推理。4.2 多卡张量并行的启动方式如果你用 4 张卡跑 72B 模型启动方式会不一样。TensorRT-LLM 用 MPI 做多进程通信每个进程负责一张卡mpirun -n 4 --allow-run-as-root \ python3 -m tensorrt_llm.runtime \ --engine_dir /workspace/qwen1.5-72b-engine \ --tokenizer_dir /models/Qwen1.5-72B-Chat \ --tp_size 4-n 4要和--tp_size 4一致否则会出现 rank 不匹配的报错。--allow-run-as-root在容器里跑 mpirun 时经常需要加但生产环境建议用非 root 用户。4.3 和 vLLM、Ollama 的选型对比现在部署大模型的工具不少vLLM 和 Ollama 也常被拿来比较。简单说Ollama 适合本地开发和个人使用开箱即用但性能上限低vLLM 的 PagedAttention 做得很好吞吐在多数场景下和 TensorRT-LLM 接近但部署更简单TensorRT-LLM 的优势在于极致的延迟优化和 NVIDIA 生态的深度集成适合对首 token 延迟和吞吐有硬性要求的场景。维度TensorRT-LLMvLLMOllama首 token 延迟最低中等较高吞吐最高高低部署复杂度高中低硬件绑定NVIDIA 专用多平台多平台量化支持FP16/INT8/INT4FP16/INT8GGUF 多种如果你团队里没有专门做推理优化的工程师vLLM 可能是更务实的选择。但如果你已经在用 TensorRT 做其他推理服务或者对延迟有极致要求TensorRT-LLM 值得投入。5. 避坑与排查那些让我熬夜的报错5.1 编译时 OOM显存明明够却报 out of memory现象trtllm-build跑到一半报 CUDA out of memory但nvidia-smi看显存还有富余。原因TensorRT 在构建引擎时会做 tactic 搜索这个过程会临时占用大量显存峰值可能是模型权重的 2-3 倍。另外--max_num_tokens设得过大也会导致编译期显存爆炸。解决先把--max_batch_size和--max_num_tokens调小编译成功后再用trtllm-build的--profiling_verbosity看实际显存占用逐步调大。如果卡本身显存不够可以在另一台显存更大的机器上编译但要注意引擎和运行环境的 CUDA 版本必须一致。5.2 推理输出乱码或重复现象模型生成的文本出现大量重复、乱码或者输出到一半突然变成无意义字符。原因最常见的是 tokenizer 不匹配。Qwen1.5 的 tokenizer 和 Qwen1.0 有差异如果你用错了 tokenizer 版本encode 出来的 input_ids 和模型训练时的对不上。另一个可能是end_id设错了导致模型不知道什么时候该停。解决确认tokenizer_dir指向的是 Qwen1.5 自己的 tokenizer 目录end_id用tokenizer.eos_token_idpad_id用tokenizer.pad_token_id。如果还是乱码检查编译引擎时的--dtype和推理时的是否一致。5.3 多卡启动时报 NCCL 错误现象mpirun启动多卡推理时报 NCCL 通信超时或unhandled system error。原因NCCL 需要正确的网络接口配置容器环境下如果没开--network host或者没设NCCL_SOCKET_IFNAMENCCL 可能选错网卡。解决在启动脚本里加export NCCL_SOCKET_IFNAMEeth0根据实际网卡名调整容器启动时加--network host。如果用的是 InfiniBand还要确认NCCL_IB_DISABLE0。5.4 长序列推理时显存缓慢增长现象服务跑一段时间后显存占用越来越高最终 OOM。原因KV Cache 没有正确释放。TensorRT-LLM 的 Paged KV Cache 需要显式调用释放接口如果请求异常中断比如客户端断连对应的 block 可能没被回收。解决在服务层加超时和异常处理确保每个请求结束后都调用runner.reset()或对应的释放方法。另外可以开启--kv_cache_free_gpu_mem_fraction参数限制 KV Cache 最多占用的显存比例留出余量。5.5 引擎加载慢每次启动都要等几分钟现象服务重启时加载引擎要花好几分钟影响发布效率。原因TensorRT 引擎反序列化本身就需要时间模型越大越慢。另外如果引擎文件放在网络存储上IO 也会成为瓶颈。解决把引擎文件放在本地 NVMe 盘上不要放 NFS。如果启动时间实在无法接受可以考虑用 TensorRT-LLM 的--load_format参数做并行加载或者把引擎预加载到内存里做热备。6. 进阶技巧用 Triton Inference Server 做生产级部署单机 FastAPI 服务在并发量上来之后会遇到瓶颈——Python GIL、请求排队、没有健康检查。生产环境更推荐用 NVIDIA Triton Inference Server 来托管 TensorRT-LLM 引擎它原生支持动态批处理、模型版本管理、Prometheus 监控这些企业级功能。Triton 部署 TensorRT-LLM 的核心是配置config.pbtxt指定tensorrt_llmbackend 和引擎路径name: qwen1.5-7b backend: tensorrtllm max_batch_size: 8 input [ { name: input_ids data_type: TYPE_INT32 dims: [-1] } ] output [ { name: output_ids data_type: TYPE_INT32 dims: [-1, -1] } ] parameters: { key: engine_dir value: { string_value: /workspace/qwen1.5-7b-engine } } parameters: { key: tokenizer_dir value: { string_value: /models/Qwen1.5-7B-Chat } }启动 Triton 时指定模型仓库路径它会自动加载配置并暴露 HTTP/gRPC 接口。动态批处理是 Triton 的杀手锏——多个请求会在服务端自动合并成一个 batch 送给引擎吞吐能比单请求模式高好几倍。但要注意max_batch_size要和引擎编译时的一致否则 Triton 会拒绝加载。验证服务是否正常可以用curl发一个简单请求curl -X POST http://localhost:8000/v2/models/qwen1.5-7b/generate \ -H Content-Type: application/json \ -d { text_input: 你好请介绍一下你自己, max_tokens: 128, temperature: 0.7 }如果返回{text_output: ...}就说明链路通了。Triton 还支持stream参数做流式输出但需要客户端用 gRPC 或者 WebSocket 来接收。我自己的习惯是开发调试阶段用 Python runtime 快速验证确认模型和参数没问题后再切到 Triton 做压测和上线。压测时重点关注首 token 延迟TTFT和每 token 延迟TPOT这两个指标TTFT 决定用户感知的响应速度TPOT 决定生成流畅度。如果 TTFT 超过 500ms用户就会觉得卡TPOT 超过 50ms输出就会一顿一顿的。最后说一个血泪教训引擎编译时的参数一旦确定后期想改max_batch_size或max_input_len就必须重新编译而重新编译意味着重新做 tactic 搜索又要等几十分钟。所以第一次编译时宁可把参数设大一点留余量也别为了省显存卡得太死。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站