ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从单模型服务到LLM推理平台:模型部署框架选型与实践

从单模型服务到LLM推理平台:模型部署框架选型与实践 做模型部署这行五年多从最早拿 Flask 包一个 ONNX 模型给业务方调用到现在维护着支撑多个业务线的 LLM 推理平台中间踩过的坑比我写过的代码都多。很多朋友问我模型部署到底难在哪为什么一个“跑通”的模型到了线上就各种幺蛾子为什么单模型服务明明很稳定非要搞什么推理平台这篇文章我想把这几年摸出来的东西完整梳理一遍用一条从“单模型服务”到“LLM 推理平台”的演进线把模型部署框架的选型逻辑、实操步骤和避坑经验全部讲清楚。适合刚入门部署的算法工程师也适合正在从单体服务往平台化方向演进的后端同学。1. 部署这件事的真相单模型服务和 LLM 推理平台根本不是一回事很多人一开始接触模型部署都是从“把模型跑起来”开始的。加载权重、起一个 HTTP 接口、调通一个请求就觉得自己会部署了。这个认知在单模型阶段确实够用但一旦场景变成多个模型、高并发、长上下文、多租户之前那一套完全撑不住。这里面的差距不是工具层面的而是架构思维层面的。1.1 从“能跑”到“跑得好”模型部署的三个层次我把模型部署分成三个层次。第一个层次叫“能跑”模型加载成功接口能返回结果仅此而已。第二个层次叫“跑得稳”延迟可控、并发可扛、显存不炸具备基本的监控和告警。第三个层次叫“跑得聪明”有多模型路由、动态扩缩容、推理资源池化、质量评估闭环这时候它才配叫“推理平台”。大部分开源项目和你自己写的脚本处在第一层少数团队能做到第二层真正到第三层的其实不多。我见过不少团队模型效果很好但因为部署架构太弱线上频繁超时业务方最后忍无可忍换掉了整个技术栈。这不是模型的问题是部署的问题。更好的比喻是开餐厅单模型服务就像街边一个小吃摊你一个人能忙得过来推理平台是连锁餐饮要考虑供应链、人力排班、品控标准。从摊子变成连锁店不是你多雇几个人就行而是整个组织方式都要变。1.2 演进路线单体服务如何一步步长成推理平台推理平台不是设计出来的是被逼出来的。我经历的演进路线大概是这样的第一步单模型 FastAPI 服务。模型小、调用量小一个进程、一块 GPU 完全够用。这时候不需要任何复杂架构。第二步多模型同时上线。不同业务方的模型各不相同每个人都要一套环境每个人的服务都占一块 GPU。资源利用率迅速恶化GPU 空闲一大半但新模型就是塞不进来。第三步引入统一推理引擎。ONNX Runtime、TensorRT、vLLM 这类引擎统一接管模型加载和执行。业务方不再自己写加载逻辑提交一个模型文件平台帮你跑。第四步平台化。模型仓库、资源池、弹性伸缩、监控告警、网关路由全部串起来。这时候模型部署从个例问题变成了体系化问题。这个演进过程很像操作系统的诞生早期每个程序自己管内存、管外设后来发现大家重复造轮子且互相打架于是操作系统出来统一管理。推理平台就是模型服务的操作系统。2. 单模型服务的正确打开方式格式、框架与容器化如果你现在还处在单模型阶段别急着上平台先把单模型的部署基本功打扎实。这一节我详细拆解单模型服务的三个核心环节模型格式、服务框架、容器化。每一步都决定了线上稳定性的上限。2.1 模型格式决定部署上限ONNX、TensorRT、GGUF 怎么选模型格式这个事很多人不重视觉得只要能加载就行。但格式直接影响推理性能、硬件兼容性和部署复杂度。ONNX 是当前最通用的中间格式。PyTorch 模型导出 ONNX 之后可以跑在 CPU 上也可以跑在 GPU 上还能被 ONNX Runtime 做图优化。对大部分场景ONNX 是常规选择。但 ONNX 有个问题它保留的是图结构算子的实现是通用的针对特定 GPU 的优化不够激进。TensorRT 就不一样了它是 NVIDIA 专门给自家 GPU 做的优化引擎。同样的模型用 TensorRT 转换之后延迟和吞吐往往比 ONNX Runtime 好一大截。代价是转换时间长、与特定 GPU 型号绑定、对动态形状支持不友好。我在生产环境里踩过 TensorRT 的坑在一台 A100 上转好的引擎换到 V100 上直接加载失败只能重新转换。GGUF 是 llama.cpp 社区推动的格式专门为 CPU 推理和 Apple Silicon 设计。它把模型量化信息直接编码在文件里让 CPU 也能跑出不错的性能。如果你要在 CPU 环境部署 LLM 或者做边缘部署GGUF 是最省心的选择。我的选型建议是三个场景三个方案通用 GPU 部署优先 ONNX因为兼容性和可维护性最好。单一 GPU 型号、性能要求苛刻选 TensorRT但要建立自动化转换流水线。CPU/边缘设备部署 LLM 或中等模型直接用 GGUF别折腾其他格式。2.2 HTTP 服务层用 FastAPI 还是 Triton模型格式定下来之后要起一个服务把模型暴露出去。这里有两类选择一类是通用框架自己包一层另一类是用专门为推理设计的服务框架。通用框架方面我推荐 FastAPI异步支持好、自动生成 OpenAPI 文档、生态丰富。单人做单模型服务时FastAPI 是效率最高的选择。但 FastAPI 有个短板它不管模型的生命周期。你需要自己在启动时加载模型、做进程预热、处理显存回收。这些逻辑写起来不难但坑很多比如模型加载在子进程中导致显存翻倍、多 worker 同时加载导致炸显存。NVIDIA Triton Inference Server 走的是另一条路线它把模型加载、并发调度、动态批处理、模型仓库管理都内置了。你只需要准备好模型仓库目录Triton 自动扫描加载。它支持的并发模型数量和动态批处理能力是手写 FastAPI 很难复刻的。这两者不是替代关系。我的实践中Triton 适合做底层推理引擎FastAPI 适合做业务适配层。Triton 负责高性能推理FastAPI 负责参数校验、业务逻辑、鉴权、结果后处理这是很稳的组合。我见过很多团队试图用 FastAPI 直接硬扛高并发结果事件循环被阻塞、延迟飙升。不是 FastAPI 不行是你没搞明白它擅长的边界。2.3 Docker 化部署的完整套路模型服务一定要容器化这不是可选项而是必选项。Docker 的价值不只是隔离环境更重要的是让部署可复现。我见过最惨的案例同事在自己电脑上跑得好好的模型到服务器上加载就崩查了半天发现是 CUDA 版本不匹配。有了 Docker这类问题基本绝迹。写模型服务的 Dockerfile 有几个关键点。基础镜像最好基于 NVIDIA 官方提供的 CUDA 镜像比如nvidia/cuda:12.1.1-runtime-ubuntu22.04别自己从裸系统装 CUDA折腾且容易出错。安装 Python 依赖时务必锁定版本尤其是torch、onnxruntime-gpu、tensorrt这类与 CUDA 强相关的包。启动命令最后一定要加--preload之类的预热逻辑否则第一个请求会慢得离谱。有一个细节很多人忽略模型文件不应该打进镜像里。标准做法是把模型放到独立的数据卷或对象存储中容器启动时从外部挂载。这样模型更新时不需要重新构建镜像只要更新模型文件即可。我用这套方式后模型迭代发布从半小时缩短到三分钟。提示容器内不要用--shm-size1g这类过小配置。PyTorch 的 DataLoader 和 TensorRT 的某些插件会大量使用共享内存共享内存不足会导致诡异的崩溃。建议至少设置 4G。3. LLM 推理平台的核心设计从 Ollama 到 vLLM 再到生产级架构如果你只是在本地跑一个大模型玩玩Ollama 就够了。但如果你要面向业务提供推理服务情况会复杂得多。LLM 推理平台的核心是解决三个问题怎么高效利用 GPU 显存、怎么扛住并发请求、怎么管理多个模型的生命周期。这一节从轻到重把三种方案逐一展开。3.1 轻量方案Ollama 加 Docker 的快速验证路径Ollama 这几年的火爆是实至名归的它把 LLM 本地部署的门槛降到了极低。一条命令就能拉起 Llama、Qwen 这些主流模型普通人也能在自己电脑上跑起一个对话模型。但我必须说清楚Ollama 适合做验证和轻量使用不适合直接当生产推理平台。用 Docker 部署 Ollama 是标准做法。基本操作如下docker run -d --name ollama \ -v /data/ollama:/root/.ollama \ -p 11434:11434 \ --gpus all \ ollama/ollama:latest启动后拉取模型docker exec -it ollama ollama pull qwen2.5:7b docker exec -it ollama ollama run qwen2.5:7b这套方案的精髓在-v /data/ollama:/root/.ollama这行挂载模型都存放在宿主机上容器删了重建模型不会丢。Ollama 还内置了 OpenAI 兼容接口curl http://localhost:11434/v1/chat/completions就能接入现有应用。Ollama 的局限在于它对并发调度的控制相对粗放多请求并发时缺乏精细的批处理和抢占策略也没有内置的模型热切换和版本管理。它更像是一个好用的本地模型管理器而不是一个面向多业务方的推理平台。但作为快速验证路径它的价值不可替代。我通常在探索新模型时用 Ollama 快速验证效果确认值得上线后再迁移到 vLLM 或 Triton。3.2 高性能引擎vLLM 的关键参数与调优心得vLLM 是目前生产环境部署 LLM 最常用的引擎它的核心优势是 PagedAttention 技术。类比来说传统推理显存分配像未分页的内存管理每个请求预留一块连续显存浪费严重。PagedAttention 把 KV Cache 拆成固定大小的块按需分配显存利用率大幅提升同时配合 Continuous Batching 把多个请求塞进同一个批处理吞吐量比普通方案有明显优势。实际使用 vLLM 时最关键的就是max-model-len、gpu-memory-utilization、max-num-seqs这三个参数。python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --tensor-parallel-size 1 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 32 \ --port 8000max-model-len决定了模型能接受的最大上下文长度。Qwen2.5-7B 理论支持 32K 以上但设得越大KV Cache 占用越高实际能并发的请求就越少。需要权衡业务需求与吞吐量。gpu-memory-utilization是显存使用比例上限设 0.9 一般比较安全别贪心到 0.98CUDA 和其他进程也得占一点显存设太高容易 OOM。max-num-seqs控制单次批处理的最大序列数这个值受显存约束设太大会让单一请求的响应变慢。还有个容易忽略的参数是enforce-eager。默认情况下 vLLM 会使用 CUDA Graph 做计算图优化首次推理会预热一段时间但后续性能好。调试阶段建议加上--enforce-eager跳过优化快速验证逻辑是否正确。注意vLLM 服务启动时加载模型约需要几十秒到几分钟首次请求前务必做一次空请求预热。Kubernetes Deployment 一定要配readinessProbe调用/health接口否则流量进来时模型还没就绪请求会全部超时。3.3 平台层网关、调度、多模型管理的设计要点有了 vLLM 只是有了引擎离平台还差得很远。推理平台的架构我从实际维护经验出发认为至少要包含这几个组件推理网关层。网关的作用和 API 网关类似负责路由、鉴权、限流。业务方不直接知道背后跑的是哪个模型只向网关发请求网关根据路径或模型名路由到具体引擎实例。这一步将底层模型替换对业务方透明化了是我觉得最值钱的设计。模型仓库与版本管理。模型要统一存放不能散落在各个服务器上。一个模型文件用符号链接指向当前生产版本发布新版本时原子切换。这样回滚只需要改一个符号链接非常快。资源池与调度器。多模型共享 GPU 资源池时需要一个调度器决定哪个模型部署在哪块 GPU 上。基础版可以写一个简单的首次适应算法按显存需求分配进阶版接 Kubernetes用自定义调度器管理推理 Pod。GPU 资源很贵调度做得好不好直接决定你的 IT 成本。管理与观测面板。显示每个模型的 QPS、延迟、显存占用、错误率。没有观测面板的推理平台就是盲人开车。我最初从 Prometheus 拉指标、Grafana 出图后来语言模型平台化了直接在面板上做租户维度聚合。平台化建设需要投入大量精力。我建议从两条腿走路开始一条腿是先把所有模型统一收拢到 vLLM/Triton 这类引擎上另一条腿是搭出第一版网关和观测面板。这两个动作做完平台的地基就打了八成了。4. 实操记录从 0 到 1 部署一个可用的 LLM 推理服务这一节我完整记录我最近的实操过程在一台 24G 显存的 RTX 4090 上部署 Qwen2.5-7B-Instruct 模型从裸机开始一路做到一个能扛住业务并发的基础推理服务。4.1 环境准备与模型选择服务器环境是 Ubuntu 22.04、RTX 4090 24G、NVIDIA 驱动 535 系列。注意驱动和 CUDA 不是一回事。驱动是内核层面的CUDA Toolkit 是用户空间的容器内用的是自己的 CUDA 运行时宿主机的 CUDA Toolkit 版本其实不重要驱动版本才重要。模型选择上Qwen2.5-7B-Instruct 是一个综合表现很均衡的选择。它的特点是对中文支持好、指令跟随能力强、量化后资源占用更友好。7B 级别的模型用 FP16 推理大约需要 14G 显存在 24G 显卡上可以留出充足空间给 KV Cache。环境方面我提前装好 Docker 和 NVIDIA Container Toolkit。sudo apt-get update sudo apt-get install -y docker.io sudo systemctl enable docker sudo systemctl start docker # NVIDIA Container Toolkit curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg distribution$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \ sed s#deb https://#deb [signed-by/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit sudo systemctl restart docker装完后用nvidia-smi确认 GPU 可见用docker run --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi确认容器内 GPU 可见。这两步仔细做后续的坑会少很多。4.2 用 Ollama 快速跑通第一版环境就绪后第一版我直接用 Ollama 跑通链路。这步的目的不是定稿架构而是用最低成本验证模型效果和接口格式。docker run -d --name ollama \ -v /data/ollama:/root/.ollama \ -p 11434:11434 \ --gpus all \ ollama/ollama:latest拉取模型并测试调用docker exec -it ollama ollama pull qwen2.5:7b-instruct-q4_K_M curl http://localhost:11434/v1/chat/completions \ -H Content-Type: application/json \ -d {model:qwen2.5:7b-instruct-q4_K_M,messages:[{role:user,content:介绍一下模型部署的流程}],stream:false}这里我特意选了 q4_K_M 量化版本。Qwen2.5-7B 的原始 FP16 权重约 14Gq4_K_M 量化后会降到约 4.4G显存占用大幅减少同时推理质量损失可控。用 Ollama 跑量化版本几乎是零成本验证模型效果的完美路径。验证接口吞吐的时间很短但这步的价值很大。它确认了三件事模型能跑、接口能通、效果可接受。4.3 切到 vLLM 做并发优化Ollama 跑通后我开始迁移到 vLLM。为什么换因为业务方要求稳定批量处理并发请求同一时间可能有十几个请求打进来。Ollama 不是不行但 vLLM 的连续批处理在这些场景在下行吞吐表现更好也更可控。迁移步骤很简单启动 vLLM 服务docker run -d --name vllm \ -v /data/models:/models \ -p 8000:8000 \ --gpus all \ --shm-size4g \ vllm/vllm-openai:latest \ --model /models/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 16这里我遇到了第一个问题模型权重要从 HuggingFace 格式转换还是直接用因为模型文件是 HF 格式vLLM 启动时会自动加载。直接用没问题但 vLLM 每次启动都要重新加载权重大约 30 秒。如果模型文件可以转成更高效的格式会更快但目前 30 秒还在可接受范围内所以我保留了 HF 格式。启动完成后我写了一个并发测试脚本用aiohttp同时发 16 个请求统计 P95 延迟和吞吐。第一次测试结果不理想出现了部分请求超时。排查后发现是max-num-seqs 16配合 8K 上下文导致显存紧张KV Cache 不够部分请求排队过久。把参数调整为--max-num-seqs 8后延迟稳定在可接受范围吞吐也够用。这个调参过程就是典型的“并发与延迟的平衡”参数峰值吞吐P95 延迟显存占用max-num-seqs16高高排队接近上限max-num-seqs8中低余量充足这个实验说明了一个道理参数不是越大越好。max-num-seqs是上限不是最优值要在你的业务流量特征下实测调优。5. 常见问题与排查技巧实录部署这个活日常就是和各种问题打交道。我整理了近几年碰到的最高频的问题和解决思路每个都对应实际的踩坑记录。5.1 显存不足与 OOM 排查显存不足是部署中最常见的问题但表象各不相同。OOM 有时候直接报 CUDA out of memory有时候是服务崩溃有时候是推理结果错乱。我的排查顺序是第一步确认显存被什么吃了。用nvidia-smi查看进程占用用nvidia-smi --query-compute-appspid,used_memory --formatcsv按进程细分。第二步看有没有显存泄漏。一个稳定服务的显存占用应当是平稳的如果发现显存随时间持续上涨说明有泄漏。常见原因是模型在请求处理路径中被反复加载或者在 TensorRT 中动态 shape 导致缓存膨胀。第三步检查 KV Cache 配置。LLM 推理时显存大头在 KV Cache。如果不设置gpu-memory-utilization框架默认按较低比例分配 KV Cache显存就浪费了。设 0.9 是相对合理的起点但如果是多模型共享 GPU建议每个模型的显存预留降低留出调度余量。这里分享一个有效的小技巧给推理容器设置环境变量CUDA_LAUNCH_BLOCKING1虽然会让 GPU 计算变慢但报错信息会精确到行调试阶段靠它定位问题再合适不过。定位完成后生产环境一定要关掉。5.2 推理延迟高和吞吐低的调优思路一个模型推理慢先别急着喷框架。先拆时间分布是首 Token 延迟高还是 Token 间延迟高首 Token 延迟高通常是模型太大、显存带宽受限请求到达时才开始计算前向传播耗时长。优化思路是尽量把模型参数和 KV Cache 都驻留在显存避免碎片化。另外检查是否走了错误的设备比如 NVIDIA GPU 上却加载了 CPU 版本的算子。Token 间延迟高通常是显存带宽瓶颈模型权重越大、每次生成一个 Token 需要读取的权重越多延迟就越高。这也是为什么 4-bit 量化能显著提升速度权重小带宽压力小。有些情况下换更大的 GPU 反而不如用老 GPU 加量化这是很多人容易理解反的地方。吞吐低的问题多数是因为并发不足。vLLM 的 Continuous Batching 设计就是为了刷吞吐但如果你把max-num-seqs设得很小或者max-model-len设得太大导致 KV Cache 不够批处理规模就上不去。检查方法很简单观察vllm:num_requests_running、vllm:num_requests_waiting这两个指标如果 waiting 很高而 running 很低说明资源配置有问题。我曾在部署一个参数较大的模型时发现实际并发吞吐远低于预期一度怀疑是网络问题。后来发现是 vLLM 里模型下载过程走完了代理路径Loading 阶段反复下载失败重试虚耗了大量时间。这类问题不算自研代码问题但也给运维提了个醒推理服务的启动阶段要格外留意网络和依赖源。5.3 Token 计数与上下文长度的坑LLM 部署有一个和传统模型完全不同的坑Token。我第一次把 LLM 服务接进业务时测试发现上下文一长模型就开始胡言乱语。排查后才知道是上下文长度配置不对。Token 是模型处理文本的最小单位一个中文汉字往往对应一个甚至多个 Token。不同模型有不同的 Tokenizer同一个文本在不同 Tokenizer 下的 Token 数量不一样。这导致了一个很实际的问题业务侧用字符数估上下文长度到了模型侧实际 Token 数可能是其好几倍。所以配置max-model-len时一定要用 Token 数而不是字符数。用模型自带的 Tokenizer 做预估再上浮 20% 到 50% 作为安全余量。另外抽屉对话会持续累积上下文如果不做截断或摘要很快就会触达长度上限。生产环境我一般建议开启 Prompt 压缩或滚动窗口把历史会话的早期内容摘要化而不是简单粗暴地截断。另外提醒一点使用 OpenAI 兼容接口时max_tokens参数控制生成长度max_model_len控制整体上下文。两者之间的关系容易算错。如果生成的max_tokens设置过大而上下文历史也长模型还没开始生成就可能因为长度超限而报错。我的建议是把输入历史压缩到上限的一半以内留足生成空间。注意不同国产模型对 Token 长度的表示和历史上下文处理方式差异显著。上线前务必用目标模型自己的 Tokenizer 对真实业务文本做 Token 统计回归不能想当然地用通用规则。5.4 从热词看部署趋势可观测性、自动化测试与框架治理最后聊一点趋势性的观察。很多部署团队的实践正在从“把模型放上去”走向“软件工程化”。自动化测试方面模型服务的测试不同于普通接口测试。除了常规的 Pytest 测试框架做接口断言还要做模型输出质量测试和性能回归测试。我曾经用 Pytest 搭过一套模型服务 CI每次模型更新自动跑一组标准问题集断言格式、生成时长和核心关键词命中率成功后再发布线上。这套流程在传统软件行业稀松平常但在模型部署领域却是分水岭级别的差异。大模型 Agent 框架也正在进入部署视野。现在不少团队在部署的不只是一个 LLM 服务而是一套 Agent 框架加 LLM 网关的组合。网关负责把请求路由到不同的模型商同时做语义缓存和降级。多模型共存时网关层承担的职责越来越重。这背后的本质规律是模型部署的复杂度已经超越“模型”本身走向“服务治理”。模型从 GPU 上跑起来只是长征第一步之后漫长的服务治理才是普通开发者与平台团队的分水岭。想从单模型服务走向推理平台尽早开始在可观测性、自动化测试、网关治理这些“软件工程”方向投入性价比会高于在模型优化上死磕。最后分享一点个人体会模型部署这个领域看起来杂细想其实有一条主线资源生命周期管理。从最早的显存分配到 GPU 资源池调度再到多租户的算力配额本质都是在回答“一块宝贵的 GPU 怎么被最有效地利用”。把握住这条主线你在每一个技术栈选型时都会做出更清晰的判断。如果让我给刚起步的团队一个建议不要一上来就追求大而全的推理平台。先用 Ollama 快速验证模型效果再用 vLLM 把单模型并发搞好然后逐步加网关、观测、模型仓库。平台是被需求推着生长的不是被技术热词拉着跑的。这套路线我自己走过虽然慢但每一步都扎实。也希望这篇从单模型服务到 LLM 推理平台的框架梳理能帮你在自己的部署之路上少踩几个坑。
返回列表