最近一年,几乎每周都会有人问我同一个问题:模型部署到底该怎么做?尤其当“模型部署”从“把单个模型包装成一个API”变成“搭建一个LLM推理平台”之后,很多团队以前习惯的那套方案一下子就不够用了。以前你可能只需要把训练好的模型用FastAPI包起来,暴露一个/predict接口,能扛住几千QPS就算成功;现在你面对的是多并发、流式输出、KV Cache、量化、多模型路由、网关、可观测性,甚至还要对接知识库和可视化界面。这篇文章我会把这几年从单模型服务到LLM推理平台的部署框架做一次全景梳理,把我实际踩过的坑、验证过的选型和可直接抄的配置一起聊透,适合正在规划模型落地的工程师、技术负责人,也包括刚准备在本地跑通第一套大模型服务的同学。
1. 模型部署的技术演进与核心思路
1.1 从单模型服务到推理平台,究竟发生了什么
先聊“单模型服务”。这个阶段的技术栈非常轻:一个模型文件、一个推理脚本、一个轻量Web框架,再加一个容器。典型的例子是图像分类或目标检测,输入是一张图片,模型加载后执行一次前向计算,返回结构化结果。这里的关键指标是单请求延迟和最大并发数,因为每个请求的输入输出长度固定,显存占用也基本不变,所以容量规划很好做。你甚至可以提前算好一张卡能同时跑多少个实例。
但LLM来了之后,这套逻辑直接被打破。第一,输入输出长度是动态的,同一个模型处理一句“你好”和生成一篇长文,占用的显存和耗时天差地别;第二,生成式推理不是一次前向,而是一个逐步解码的过程,单个请求可能持续几秒甚至几十秒;第三,业务上通常需要流式返回,用户想看到的不是30秒后的一整段答案,而是像ChatGPT那样一个字一个字往外蹦。这三个变化让“拉起来一个服务”彻底不够用,你需要考虑一个完整的推理平台:模型仓库、推理引擎、网关路由、弹性伸缩、配额管理、监控告警,以及面向业务系统的OpenAI兼容接口。
所以我的理解是:单模型服务解决的是“模型能不能被调用”,LLM推理平台解决的是“模型能不能被稳定、高效、安全地当作基础设施来用”。如果没有平台化思路,直接把模型接口丢到生产环境,很快会被并发、显存和故障隔离打得措手不及。
1.2 部署框架全景图:自研、开源、托管方案怎么选
模型部署框架 Today 非常多,不是越火越好,而是得分层看。我习惯把它分成三层:推理引擎层、服务封装层、平台调度层。
推理引擎层最贴近硬件,负责把模型真正跑起来。这个赛道里,vLLM是目前社区热度最高的,核心是PagedAttention和Continuous Batching,能大幅提升吞吐;Hugging Face推出了TGI(Text Generation Inference),主打和Transformers生态无缝衔接;NVIDIA的TensorRT-LLM则是专门针对自家GPU做了极致优化,上线生产前一般要编译engine,调优成本高但上限也高;还有llama.cpp和基于它的Ollama,走的是GGUF格式路线,在小显存和CPU环境下特别友好。
服务封装层解决的是“怎么把模型暴露成接口”。TorchServe和Triton Inference Server是老牌力量,它们支持多框架、动态批处理、模型版本管理,但针对LLM的优化比较有限,更多是被当成一个稳定的后端来用;新一点的BentoML可以把模型、预处理、依赖一起打包成一个服务,体验很好,适合中小团队。
平台调度层负责把多个模型服务编排成平台。如果你用Kubernetes,可以选KServe、Seldon Core,它们提供Serverless模型服务、自动伸缩、灰度发布;如果你不想背上K8s,用Ray Serve或容器编排也能搭出轻量方案。托管给云厂商也可以,但很多场景下数据合规和成本控制让团队更倾向于私有化开源方案。
选型决策我放在一张表里,方便对照:
| 维度 | 轻量实验/单卡 | 多个模型/中等规模 | 大规模生产集群 |
|---|---|---|---|
| 推理引擎 | Ollama、llama.cpp | vLLM、TGI | TensorRT-LLM、vLLM |
| 服务封装 | 内置API、FastAPI自研 | BentoML、Triton | Triton、TGI |
| 平台调度 | Docker Compose | Docker + Nginx/ALB | K8s + KServe/Seldon |
| 典型难度 | 低,当天能跑通 | 中,需要一些运维 | 高,需要专门团队 |
别一上来就照着最重的方案去搭,先想清楚当前阶段要解决什么问题。我也是从单机Ollama开始,后来才一步步走到vLLM和网关侧的。
2. 核心组件拆解与选型要点
2.1 模型后端:从TorchServe/Triton到vLLM/Ollama
模型后端是整个平台的心脏,选错后面全难受。传统CV或推荐模型时代,我常用TorchServe和Triton。TorchServe偏PyTorch原生,部署的时候把.pt或.onnx放进去,再写个handler定义输入输出;Triton则更通用,支持TensorFlow、PyTorch、ONNX、TensorRT等一堆runtime,而且有动态批处理(Dynamic Batching),在传统模型上的吞吐优化非常显著。
但到LLM上,传统框架的问题就暴露了:它们把一次请求当作一个固定batch里的一个样本,缺乏对“生成过程”的感知。LLM推理的特点是计算密集但访存又很敏感,每一次解码都要读取全部权重,如果不做缓存和批处理,GPU利用率低得惨不忍睹。vLLM之所以火,是因为它用PagedAttention把KV Cache按页管理,显存碎片和浪费大幅减少;配合Continuous Batching,新的请求可以在前一个请求解码间隙插入进来,大大提高了吞吐。TGI也有类似思路,从实用角度两个都值得尝试。
Ollama这个后起之秀,本质是把llama.cpp的能力封装成了对用户极其友好的本地服务。它支持直接拉取qwen2.5、llama3等模型,底层自动处理GGUF量化格式,一条命令起服务,适合开发机和个人电脑。GGUF这种格式的价值在于,它是为CPU/小显存推理设计的,它把量化后的权重打包成一个文件,连分片都省了。如果业务需要私有化部署到一台只有16GB显存的机器上,Ollama加q4_k_m量化版本通常是最快的出路。
我总结的选型原则是:优先看“是否能原生支持动态批处理、前缀缓存、量化推理”,再看“是否兼容OpenAI接口”。vLLM和Ollama都做到了,而TorchServe和Triton要做LLM,需要做大量胶水工作,除非你已经有成熟的传统模型集群,否则对新项目我不推荐原地硬套。
2.2 网关与路由:为什么需要LLM网关
很多团队在部署vLLM之后,直接把http://localhost:8000丢给前端调用,短期能跑,但到了多个模型、多个副本、多个租户共存的阶段,就会失控。这时候你需要一个LLM网关,它能解决四类问题:路由、安全、可观测、成本控制。
路由方面,网关可以根据业务来源把请求分到不同的模型,比如简单问答走小模型,复杂推理走大模型;也可以做模型副本间的负载均衡,比如后端起了两个vLLM实例,网关轮询分发。安全方面,网关是统一的认证入口,避免每个模型服务各自做鉴权,还能做API Key管理、请求体校验、敏感信息过滤。可观测方面,网关是天然的埋点位置,记录每个请求的模型名、输入输出token数、延迟、失败原因。成本控制方面,网关可以做限流、配额,防止某个业务方把推理资源全打满。
开源方案里,LiteLLM非常轻量,直接封装了一堆模型的OpenAI兼容接口,做“模型路由+Key管理”够用;如果需要更细粒度的流量治理,可以选择Higress或APISIX这类云原生网关,它们都推出了AI原生的插件能力;如果团队以Kubernetes为核心,Envoy AI Gateway或Kong AI Gateway也值得尝试。我个人经验是:不要为了“AI原生网关”这个词而引入一个全新的重组件,前期可以用Nginx加Lua脚本实现限流和路由,等模型数量和调用方真正多了,再平滑迁移到专用网关。
2.3 前端可视化与知识库:让模型被真正用起来
一个推理引擎如果没有可聊天的前端,它只是裸接口,业务方用起来门槛很高。所以在正式环境的部署框架里,我通常会把“可视化界面”和“知识库增强”也纳入进来,这不是锦上添花,而是决定平台能不能被业务用起来的关键。
最经典的组合是Ollama加Open WebUI。Open WebUI是一个开源聊天界面,支持管理多个模型、多用户、会话历史,还能绑文档做简单RAG。Docker一条命令就能跑起来,模型服务指向Ollama的地址即可。如果你需要更强大的知识库能力,可以接Dify、RAGFlow或MaxKB,它们都支持对接本地推理服务,内置文档解析、向量化、召回和重排流程。这里有一个容易被忽略的点:大模型本身的聊天能力不等于企业知识问答能力,你必须把文档切片、Embedding、向量存储这些“外挂”搭好,模型的回答才会贴合自己业务。
向量数据库选型也值得多说一句。小规模场景直接用Chroma或Qdrant单机版本,数据量上去后可以换Milvus或Weaviate。另外,如果你在网上看到“RAG、GraphRAG、LLM Wiki”这类概念,不用被名字吓住,核心都是一样的:把知识变成可检索的内容,在调用大模型前先检索相关内容,再拼进提示词里。这部分其实就是把纯模型平台升级成“业务知识平台”的关键一步。
3. 实操部署:从零搭建一套可用的LLM推理服务
3.1 环境准备与加速器配置
正式环境部署前,先把GPU、驱动、容器环境理清楚,否则后面都是玄学排障。第一步是检查硬件是否可见,执行nvidia-smi,确认驱动已装好,能看到卡和显存。然后确认CUDA和容器运行时。我强烈建议用Docker跑模型服务,因为宿主机装一堆Python依赖很容易把环境搞乱,而容器可以隔离一切。安装好NVIDIA Container Toolkit后,跑docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi验证容器内能看见GPU。
显存估算也要提前做。一个7B参数模型,FP16精度的权重大约7 * 2 = 14GB,但“权重放进去”不等于“能推理”,KV Cache还要占一份额外的显存。粗略算法是:显存 ≈ 权重大小 + 输入输出token数 × 层数 × hidden_size × 2(注意精度),实际项目里更快的办法是直接用transformers的model.get_memory_footprint()看,或者用Ollama/vLLM启动时的报错来判断。经验值:7B Q4量化版在16GB显存的消费卡上可以跑,7B FP16要24GB才稳,13B量化版也建议24GB起步。
配置环境变量的时候注意几个坑。Ollama默认占满所有显存,可以在启动脚本里设置OLLAMA_MAX_LOADED_MODELS=1和OLLAMA_GPU_OVERHEAD;vLLM则要显式指定--gpu-memory-utilization,我通常设为0.9,留一点余量给其他进程。另外,不要在容器里再装一套CUDA Toolkit,尽量依赖基础镜像已有的runtime,除非你有非常特殊的算子编译需求。
3.2 用Ollama+Docker快速部署本地模型
如果你的目标是快速把一个大模型在本地或单机上跑起来,Ollama是最省力的选择。安装Ollama官方镜像后,直接启动:
docker run -d --gpus all \ -v ollama:/root/.ollama \ -p 11434:11434 \ --name ollama \ ollama/ollama拉取模型用命令行:
docker exec -it ollama ollama pull qwen2.5:7b-instruct拉完之后,可以先用CURL测试:
curl http://localhost:11434/api/generate \ -d '{"model":"qwen2.5:7b-instruct","prompt":"你好","stream":false}'Ollama默认的/api/generate和/api/chat接口可以满足简单的调用需求,但如果你想用OpenAI SDK去连接,记得Ollama也提供/v1/chat/completions这个兼容接口,很多框架直接写base_url="http://localhost:11434/v1"就行。
还有一个容易被低估的功能是自定义Modelfile。你可以把本地下载的GGUF文件拿进来,用Modelfile指定FROM /path/to/model.gguf和PARAMETER temperature等,然后执行ollama create mymodel -f Modelfile,这比硬改代码灵活得多。比如你有自研模型的GGUF量化版,想统一从Ollama这里管理,就特别方便。
实操中常遇到的问题有两个:第一,拉模型很慢,建议先在国内镜像源或离线包方式下载好.gguf文件,再导入Ollama,而不是直接ollama pull;第二,磁盘空间。Ollama会把模型放在/root/.ollama/models,一个7B量化版通常是4~5GB,7B原版要14GB以上,部署前先df -h看清楚。
3.3 用vLLM部署生产级推理服务
当你的业务需要高并发、长上下文、稳定吞吐时,Ollama就不够顶了,这时候我通常直接上vLLM。安装很简单,如果服务器能联网:
pip install vllm但更推荐用官方Docker镜像,避免编译依赖把自己折腾崩溃。启动一个7B模型的例子:
docker run -d --gpus all \ -v /data/models:/models \ -p 8000:8000 \ vllm/vllm-openai \ --model /models/Qwen2.5-7B-Instruct \ --served-model-name my-llm \ --dtype bfloat16 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --enable-prefix-caching启动之后,它对外暴露的就是OpenAI兼容接口,使用/v1/chat/completions,业务方几乎不用改代码就可以从真实OpenAI切过来。
生产环境下有几点建议:第一,模型路径建议挂在独立的磁盘上,不要放在系统盘,因为模型文件超大;第二,--max-model-len直接影响显存占用,别拍脑袋设一个很大的数,根据业务场景最长输入输出token来定,没必要为极端情况浪费显存;第三,量化选项。如果显存是瓶颈,可以用AWQ或GPTQ量化后的模型,启动参数加--quantization awq --model /path/to/awq-model,通常能省一半显存,代价是精度和速度可能稍微下降;第四,如果有多张GPU,可以用--tensor-parallel-size 2开启张量并行,相当于把模型分到两张卡上共同推理。
我在第一次用vLLM部署时踩过一个很蠢的坑:默认的--host是0.0.0.0没问题,但我忘了设置--trust-remote-code,导致某些自定义模型加载失败。如果你用的模型不是当前HuggingFace上热门的架构,可能也需要这个参数,具体看模型的repo说明。
3.4 网关、监控与日志接入
模型服务起来了,不等于平台稳了。我会在模型服务前面加一个轻量网关层,最简单的做法是Nginx反代加一层健康检查和限流。可以用如下配置示例:
upstream llm_backend { server 127.0.0.1:8000; keepalive 32; } server { listen 80; location /v1/ { proxy_pass http://llm_backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_read_timeout 300s; proxy_send_timeout 300s; limit_req zone=llm burst=20 nodelay; } }注意这里proxy_read_timeout、proxy_send_timeout必须调大,LLM生成动辄几十秒甚至几分钟,如果保持默认的60秒,前端就会看到超时。
监控这一层不要拖到最后再补。vLLM本身会暴露/metrics接口,格式是Prometheus标准,我直接用node-exporter模式的抓取方式接入。关键指标有:gpu_cache_usage_perc、num_requests_running、num_requests_waiting、time_to_first_token_seconds、generation_tokens_total。每一项都对应一个具体的体验问题:首字延迟高说明排队或模型加载慢,cache使用率高说明需要扩容,生成token总量可以用来做成本核算。
日志也要在网关层统一做结构化JSON,至少包含request_id、model、prompt_tokens、completion_tokens、ttft、total_time和status_code。有这些数据后,你可以轻松回答老板的经典三连问:“平台性能怎么样?”“哪个模型最费钱?”“今天为什么有报错?”
4. 性能调优、容量规划与问题排查实录
4.1 吞吐量与延迟:关键指标和优化手法
模型服务上线后,最常被追问的其实是性能数字。LLM推理的延迟指标和传统接口完全不一样。传统接口只看P95延迟,LLM还得拆成TTFT(Time To First Token,首Token时间)和ITL(Inter-Token Latency,相邻Token间隔时间)。TTFT影响“感觉快不快”,ITL决定“生成长度固定的情况下整体要多长时间”。而吞吐量用Tokens/s描述,可以按并发请求的累计生成token数除以耗时计算。
优化手法里,收益最大的是“连续批处理”和“前缀缓存”。连续批处理是vLLM的看家本领,不需要你额外改代码;前缀缓存是--enable-prefix-caching,如果大批请求共享系统提示词或知识库前缀,缓存能直接跳过重复的前缀计算,TTFT有肉眼可见的下降。另一个常用手段是量化。我实测下来,用AWQ 4bit量化一个7B模型,显存占用从14GB降到5GB左右,同一张卡上可以支撑更大的并发,生成速度基本持平甚至因为batch更大了反而更快。
这里给一个不好看的真相:不要只看单请求的benchmark,要看“在目标并发和延迟要求下,系统整体能跑多少TTFT”。我用过很多框架的官方基准,效果都很好看,但放到自己业务流里,受上下文长度、停用词、业务后处理影响,真实吞吐可能要打个五折。所以性能调优的第一步是先做压测,用locust或ghz打一个符合业务比例的数据集,再结合监控指标调整。
4.2 并发与排队:估算需要几张卡
很多团队低估了容量规划的复杂度。LLM推理的容量不是“一张卡能跑多少并发”,而是“一张卡在目标延迟下能每秒生成多少token”。我一般用一个简化模型估算:
假设单卡(比如A100 40GB)部署一个7B量化模型,实测生成吞吐大概在800~1500 Tokens/s(取决于量化、批大小和上下文)。一个用户的请求,平均输入500 token,输出1500 token,那一个请求总生成时长约1500 / 1000 = 1.5秒。如果希望同时支撑50个用户请求,单卡每秒需要生成50 × 1500 / 1.5 = 50000 Tokens/s,这显然一张卡不够。哪怕目标是同时排队20个请求,也要大概20 × 1500 / 1.5 / 1000 = 20张卡。当然这个算法很粗糙,实际中还要考虑排队等待、上下文峰值和前缀命中率。
多卡部署时,我想强调“张量并行”不等于“数据并行”。张量并行是把一个模型切到多张卡上,解决显存单卡放不下的问题;数据并行才是启动多个模型副本分散请求,解决吞吐不够的问题。很多新手一上来就想用--tensor-parallel-size 4跑一个7B模型,结果每张卡都在协作处理同一个请求,显存资源被白白占用,吞吐反而不高。正确的思路是:显存够放单模型副本,就优先数据并行跑多个副本,再在前面用网关负载均衡;只有模型单卡放不下才用张量并行。
排队策略也要提前定。vLLM默认会在请求过多时排队等待,但如果不设置超时,业务方可能挂起很久。我在网关层会把等待上限设为30秒,超过直接返回503,同时用num_requests_waiting指标监控排队深度,排队一旦超过10就触发扩容告警。
4.3 常见问题速查表与避坑经验汇总
把我在正式环境里遇到过的典型问题和排查方法整理成一张速查表,希望能帮你们少走弯路:
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 启动时CUDA OOM | 模型权重 + KV Cache超出显存 | 看gpu-memory-utilization,换量化模型,减小max-model-len |
| 容器内看不到GPU | 缺少NVIDIA Container Toolkit | 在宿主机跑docker run --rm --gpus all ... nvidia-smi验证 |
| Ollama拉模型老是断 | 网络问题或镜像未配置 | 改用离线GGUF导入,或配置国内镜像源 |
| vLLM报“ValueError: Tensor parallelism” | 模型参数量无法被TP整除 | 换成--tensor-parallel-size 1,或换卡数 |
| 请求长时间卡住无返回 | 网关超时太短或模型排队过深 | 调大proxy_read_timeout,检查vLLM的waiting队列 |
| 流式输出断断续续 | 启用了代理缓冲或响应压缩 | Nginx关掉gzip,关闭proxy buffering,确保SSE正常 |
| 模型加载后立刻退出 | 代码需要trust_remote_code | 增加--trust-remote-code并按模型repo说明操作 |
| 返回结果全是空或乱码 | 模版使用错误或分词器不匹配 | 确认chat template,测试前先跑一遍vllm.entrypoints.openai.api_server里的docs页 |
除了这些,还有几个经验我每次都会强调。第一,上线前先在测试环境完整跑一遍压测 + 长稳测试,LLM服务最怕的不是慢,而是内存泄漏和单卡退化,长时间运行一两天后性能下降,多半跟前缀缓存或动态批处理的碎片有关,重启往往只能治标,要盯着指标才能定位根因。第二,模型的版本和权重文件要做不可变管理,不要在服务运行的时候原地覆盖模型文件,否则会引发莫名其妙的推理结果漂移,我习惯用类似model-repo/my-llm/20250101/这种带时间戳的目录来组织。第三,启动参数要固化成配置文件并入库,我见过不止一次,某天服务崩了,接手的人一头雾水,只记得当时“随便敲了几个参数”,结果很难复现。
最后分享一个我个人坚持的小习惯:无论项目多小,我都会在部署文档里写清楚三件事——模型服务怎么拉起的、依赖哪些显存和磁盘资源、监控面板看哪些指标。这套文档价值极大,因为LLM推理平台不是一次建完就结束的,模型会换、流量会涨、框架会升级,只有让“架构成文字”,后人才敢动它。从单模型服务一路走到今天,我最大的体会是:稳定永远比炫技重要,一个能长期跑在正式环境里的框架,远比一个几天就要重搭一遍的“高级方案”有价值。