1. 从一次线上超时说起:TGI、vLLM、Ollama 到底怎么选
模型部署这件事,训练完只是开始。我见过太多团队把模型跑通就以为万事大吉,结果一上量就崩。TGI、vLLM、Ollama 这三个名字你一定不陌生,但它们在生产环境里的差异,远比"哪个快"复杂得多。TGI 是 HuggingFace 官方推理服务,vLLM 靠 PagedAttention 和连续批处理打天下,Ollama 则是单机开发的一把好手。三者定位不同,选错了不是性能差一点,而是架构层面就要返工。
这篇文章面向的是要把模型真正上线、并且需要统一管理多个模型调用的工程师。我会把三套框架的启动参数、并发能力、资源占用摊开对比,给出可直接复制的部署配置和压测脚本,最后演示怎么用 TaoToken 的统一 Key 和 API 通道,把多模型调用收敛到一套服务下。你跟着做,能完成从模型上线到调用验证的完整闭环。
先说结论性的判断:生产高并发 GPU 服务优先 vLLM,HF 生态项目或需要消息路由选 TGI,个人电脑和内部工具用 Ollama。但真正的难点不在选型,而在选完之后——多模型、多框架、多 Key 怎么统一管。这正是后面要重点解决的。
2. 三类推理框架的生产部署差异与统一接入前置
2.1 vLLM:PagedAttention 与连续批处理
vLLM 的核心机制有两个。PagedAttention 把 KV cache 切成固定大小的页,按需分配,不像传统方案一次性预分配整个上下文长度,显存利用率大幅提升。连续批处理让新请求随时插入正在跑的 batch,GPU 算力始终有活干。两个机制叠加,7B 模型单卡吞吐能到 transformers 原生推理的十倍以上。
工程成熟度上 vLLM 是三家里最高的,OpenAI 兼容 API、Prometheus 监控、多卡张量并行、量化模型支持都是开箱即用。缺点是配置项多,gpu-memory-utilization、max-model-len、max-num-seqs这几个参数调不好容易 OOM 或者排队,上手有门槛。
2.2 TGI:HuggingFace 官方全家桶
TGI 用 Rust 和 Python 写成,优势在功能集成度。加载时自动做权重量化、利用 TensorRT 加速、支持消息路由到不同模型、文本流式输出按块返回。HF hub 上的模型直接填名字就能拉取,不需要额外配置。部署形态以 Docker 为主,官方镜像带健康检查和优雅关闭。
弱点是对自定义模型和特殊需求的支持没有 vLLM 灵活,社区迭代速度略慢。如果你的模型就在 HF 生态里,TGI 的省心程度很高。
2.3 Ollama:单机开发利器
Ollama 一条命令把模型跑成 REST API,ollama pull加ollama run两行搞定。它吃 GGUF 格式,底层是 llama.cpp,CPU 和 GPU 都能跑。开发机上用它调试业务代码非常顺,模型管理、自动下载、开箱即用都是加分项。
生产环境的短板明显:单机单进程,没有内置负载均衡和多副本管理,监控指标少,吞吐上限低。小团队内部工具可以拿它顶,面向用户的线上服务还是建议上 vLLM 或 TGI。
2.4 三方对比与切换成本
| 维度 | vLLM | TGI | Ollama |
|---|---|---|---|
| 吞吐能力 | 最高,连续批处理 | 高,含 TensorRT 选项 | 低,单进程 |
| 延迟表现 | 好,PagedAttention 省显存 | 好,流式优化 | 一般,CPU 偏慢 |
| 功能覆盖 | OpenAI API、量化、监控、张量并行 | 量化、路由、HF 生态 | 模型管理、多格式 |
| 部署复杂度 | 中等,参数多 | 中等,Docker 成熟 | 最低 |
| 适用场景 | 生产高并发 GPU 服务 | HF 生态中型服务 | 开发调试、内部工具 |
三套方案之间切换的成本,多数情况下就是启动命令和base_url的改动。业务代码走 OpenAI 兼容 API,调用端完全一致。但当你同时维护多个模型、多个框架实例时,Key 管理、通道切换、调用统计就会变成新的麻烦。这就是 TaoToken 要解决的问题——把多模型调用收敛到统一的 Key 和 API 通道下。
TaoToken 的定位是统一接入层,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。它不替代你的推理框架,而是在框架之上提供统一的调用入口,让你用一套 Key 管理多个模型服务。
3. 可复制配置:vLLM 启动、TGI 编排与 TaoToken 接入
3.1 vLLM 一条命令启动服务
vLLM 安装后自带 serve 命令,指定模型名和几个关键参数就能起服务。模型首次运行会自动从 HuggingFace 下载,也可以先下到本地再指向目录。
# 启动 vLLM 服务,提供 OpenAI 兼容 API # 安装: pip install vllm>=0.5 # 首次运行自动下载模型,约 15GB,需保证磁盘空间 vllm serve Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen7b \ --gpu-memory-utilization 0.9 \ --max-model-len 4096 \ --max-num-seqs 128 \ --port 8000参数含义逐个说。served-model-name是客户端调用时的模型名,起个别名方便业务代码稳定引用,模型升级时不用改代码。gpu-memory-utilization控制显存占用比例,0.9 意味着 90% 的显存用来跑权重和 KV cache,剩下 10% 留给计算缓冲,开太满会 OOM。max-model-len限制上下文总长度,4096 对应单请求 prompt 加回答的上限,设太大每个请求预占的 KV 空间就大,并发数被压下来。max-num-seqs是最大并发请求数,128 对 7B 模型在 24GB 卡上合适。
启动日志出现Application startup complete就说明服务就绪,此时http://localhost:8000/v1就是 OpenAI 兼容端点,/health是健康检查端点,/metrics是 Prometheus 指标。
3.2 TGI 的 docker-compose 编排
TGI 官方推荐 Docker 部署,镜像自带运行时和依赖。docker-compose 把服务、健康检查、重启策略一次定义好。
# docker-compose.yml # 用 docker compose 部署 TGI 推理服务 # 前置: 宿主机安装 nvidia-container-toolkit # 启动: docker compose up -d version: "3.8" services: tgi: image: ghcr.io/huggingface/text-generation-inference:2.3.1 ports: - "8080:80" volumes: - ./models:/data environment: MODEL_ID: Qwen/Qwen2.5-7B-Instruct MAX_INPUT_TOKENS: "2048" MAX_TOTAL_TOKENS: "4096" NUM_SHARD: "1" deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80/health"] interval: 30s timeout: 5s retries: 3 start_period: 120s restart: unless-stoppedhealthcheck是生产部署最容易忽略的环节。容器进程活着和模型真正就绪是两回事,TGI 启动过程要下载并加载模型,这个阶段进程活着但接口返回错误。start_period设置 120 秒给足加载时间,之后每 30 秒探测一次/health,连续 3 次失败就判定容器不健康。
3.3 TaoToken 统一接入配置
当你有多个模型服务——vLLM 跑一个、TGI 跑一个、可能还有云厂商 API——每个都要单独管 Key、单独记地址,调用代码里到处是硬编码。TaoToken 的做法是提供一个统一的 API 通道,你用一套 Key 就能调用多个模型。
先到 TaoToken 控制台创建 API Key,地址是 https://taotoken.net/console 。拿到 Key 后,配置方式和你用 OpenAI SDK 完全一致,只是把base_url指向 TaoToken 的 API 端点。
# taotoken_client.py # 用 TaoToken 统一通道调用多个模型 # 安装: pip install openai from openai import OpenAI # base_url 指向 TaoToken 的 API 端点 # api_key 用你在控制台创建的 Key client = OpenAI( base_url="https://taotoken.net/api", api_key="sk-your-taotoken-key", ) # 调用 vLLM 部署的模型 resp = client.chat.completions.create( model="qwen7b", messages=[ {"role": "system", "content": "你是课程答疑助手,回答简洁"}, {"role": "user", "content": "请解释什么是梯度下降"}, ], max_tokens=256, temperature=0.7, ) print(resp.choices[0].message.content)如果你用的是 Claude Code 这类编码工具,TaoToken 也提供了对应的接入方式。Claude Code 的配置在~/.claude/settings.json,把 API 地址指向 TaoToken 的通道即可。具体路径和参数可以参考接入文档 https://taotoken.net/doc 。
对于需要长期编码和 Agent 场景的团队,Coding Plan 提供了更稳定的通道和额度管理,地址是 https://taotoken.net/coding-plan 。模型对话的在线验证入口在 https://taotoken.net/models ,API Key 管理在 https://taotoken.net/api-keys 。
4. 验证请求与压测:确认服务真正可用
4.1 基础连通性验证
服务起来后,先用 curl 确认接口通。
# 验证 vLLM 服务是否就绪 curl -s http://localhost:8000/health # 发一个最小请求验证推理正常 curl -s http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen7b", "messages": [{"role": "user", "content": "你好"}], "max_tokens": 32 }'返回里能看到choices[0].message.content就说明推理链路通了。如果走 TaoToken 通道,把地址换成https://taotoken.net/api/v1/chat/completions,Header 里带上Authorization: Bearer sk-your-key。
4.2 并发压测脚本
上线前必须压测。脚本用 httpx 的异步客户端实现固定并发,统计成功率、平均延迟、P95 延迟和吞吐。
# bench_concurrent.py # 并发压测 OpenAI 兼容推理服务 # 安装: pip install httpx import asyncio import statistics import time import httpx BASE_URL = "http://localhost:8000/v1" MODEL = "qwen7b" PROMPTS = [ "帮我写一封催客户付款的邮件", "解释一下什么是注意力机制", "写一个 python 函数计算斐波那契数列", "总结这段文字的主要观点:机器学习是人工智能的一个分支", ] async def one_request(client, prompt): payload = { "model": MODEL, "messages": [{"role": "user", "content": prompt}], "max_tokens": 128, } t0 = time.perf_counter() try: r = await client.post( f"{BASE_URL}/chat/completions", json=payload, timeout=120, ) elapsed = time.perf_counter() - t0 data = r.json() tokens = data["usage"]["completion_tokens"] return {"ok": True, "latency": elapsed, "tokens": tokens} except Exception: return {"ok": False, "latency": time.perf_counter() - t0, "tokens": 0} async def bench(concurrency, rounds): results = [] async with httpx.AsyncClient() as client: for _ in range(rounds): tasks = [ one_request(client, PROMPTS[i % len(PROMPTS)]) for i in range(concurrency) ] results.extend(await asyncio.gather(*tasks)) return results if __name__ == "__main__": results = asyncio.run(bench(concurrency=50, rounds=10)) ok = [r for r in results if r["ok"]] total_tokens = sum(r["tokens"] for r in ok) lats = sorted(r["latency"] for r in ok) fail = len(results) - len(ok) avg_ms = statistics.mean(lats) * 1000 if lats else 0 p95_ms = lats[int(len(lats) * 0.95) - 1] * 1000 if lats else 0 throughput = total_tokens / sum(lats) if lats else 0 print(f"总请求: {len(results)}, 成功: {len(ok)}, 失败: {fail}") print(f"平均延迟: {avg_ms:.0f} ms") print(f"P95延迟: {p95_ms:.0f} ms") print(f"总生成token: {total_tokens}") print(f"吞吐: {throughput:.1f} tokens/s")4.3 压测结果解读
50 并发、128 token 生成,平均延迟 1.8 秒,P95 延迟 3.2 秒,吞吐 420 tokens/s,这是 7B 模型在单张 A10 上的典型表现。50 并发下的平均延迟比单请求的 0.9 秒翻倍,这是连续批处理的正常代价。P95 和平均值的差距反映排队效应,差距越大说明并发余量越小。失败率超过 1% 就该警惕,先降并发看延迟是否回落。
4.4 长文本请求饿死短文本的坑
压测阶段容易忽略混合负载。脚本里全是短 prompt 短回答,上线后真实流量里有 10% 的长文本请求,prompt 动辄 2000 token。结果那部分长请求占住 batch 后,短请求排队时间直线上升,短文本平均延迟从 1.8 秒涨到 4.2 秒。
根因是max-model-len设得太大,每个请求按 4096 预占 KV cache,长请求吃掉的 KV 空间是短请求的数倍,剩余空间容纳的并发请求数骤降。解法是把长文本和短文本分流到不同实例,短文本实例max-model-len设 2048,长文本实例设 8192,各自独立扩容。压测脚本里混入 20% 的长文本样本,压出来的容量才和线上真实情况对得上。
5. 本篇常见报错排查
5.1 401 Unauthorized
走 TaoToken 通道时最常见的报错。先确认 Header 里的Authorization: Bearer sk-xxx格式正确,Key 没有多余空格。然后到 https://taotoken.net/api-keys 确认 Key 状态正常、额度充足。如果本地 vLLM 报 401,检查是不是误传了非空 api_key——vLLM 本地模式不校验,但有些客户端会强制要求。
5.2 local proxy failed
这个报错通常出现在网络层。检查你的服务地址是否可达,curl一下/health端点。如果是容器内调用宿主机服务,注意localhost在容器里指向容器自身,要用宿主机的实际 IP 或host.docker.internal。TaoToken 通道的地址是https://taotoken.net/api,确认没有拼错路径。
5.3 reading choices 报错
KeyError: 'choices'或reading 'choices'这类报错,说明返回体结构和你预期的不一致。先打印完整响应看error字段。常见原因是模型名写错——model参数必须和启动时的served-model-name一致。另一个原因是请求被限流,返回了错误结构。走 TaoToken 时确认模型 ID 在 https://taotoken.net/models 里存在。
5.4 OAuth 相关报错
Claude Code 接入时如果报 OAuth 错误,检查~/.claude/settings.json里的配置。Base URL、Key、Model ID 三件套要写全。Base URL 指向 TaoToken 的通道地址,Key 用控制台创建的,Model ID 填你要用的模型。三件套缺一个都会导致认证失败。配置细节参考 https://taotoken.net/doc 。
5.5 显存不足与 OOM
torch.cuda.OutOfMemoryError是部署阶段的高频问题。先把gpu-memory-utilization降到 0.85,再缩max-model-len。如果还不够,考虑把模型量化到 INT4 再部署,vLLM 和 TGI 都支持 AWQ、GPTQ,启动时指定quantization参数即可。多卡场景检查tensor-parallel-size是否和实际卡数匹配。
5.6 健康检查路径配错导致流量打到未就绪实例
这是上线初期最疼的事故。编排系统配的健康检查路径是默认的/,没改成/health。vLLM 在模型加载期间对/也返回 200,编排系统判定实例健康,把流量均匀打过去,结果一半请求打到还没加载完的实例上。改成GET /health后,加载期间的实例返回 503,编排系统自动摘除,错误率归零。任何推理服务上线,健康检查路径必须单独 curl 验证一遍。
6. 统一接入后的调用验证与长期管理
多模型服务跑起来后,真正的运维挑战才刚开始。你可能有 vLLM 跑的生产模型、TGI 跑的 HF 生态模型、Ollama 跑的本地调试模型,每个都有自己的地址和 Key。TaoToken 的价值在这里体现——把所有这些收敛到一套 Key 和 API 通道下,调用方只需要知道 TaoToken 的地址和模型 ID。
验证统一接入是否成功,最简单的办法是写一个多模型调用脚本,用同一个 client 依次调用不同模型。
# multi_model_check.py # 用同一套 TaoToken Key 调用多个模型 from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key="sk-your-taotoken-key", ) models = ["qwen7b", "qwen-plus", "claude-3-5-sonnet"] for m in models: try: resp = client.chat.completions.create( model=m, messages=[{"role": "user", "content": "用一句话介绍你自己"}], max_tokens=64, ) print(f"[{m}] {resp.choices[0].message.content}") except Exception as e: print(f"[{m}] 调用失败: {e}")这段脚本能跑通,说明统一通道工作正常。后续新增模型时,只需要在 TaoToken 侧配置好通道,业务代码不用改。
长期管理上,监控至少盯五个指标:显存利用率超过 95% 持续 5 分钟告警,GPU 利用率低于 50% 说明批处理没吃满,排队请求数超过阈值直接扩容,TTFT 首 token 延迟超过 5 秒体验就崩,错误率超过 1% 立即处理。vLLM 的/metrics端点暴露了vllm:num_requests_waiting等字段,Prometheus 抓取后配 Grafana 面板。
容量规划按业务增长的预估请求量反推卡数,提前两周申请资源。成本监控每月一次,看单位请求成本的变化趋势。日志按天轮转保留 30 天,重点记录 request_id、输入长度、输出长度、耗时、错误码五列,用户报障时凭 request_id 秒查整条请求的耗时分布。
最后说一个实际经验:压测脚本本身要纳入版本管理。我们吃过一次亏,两次压测数据对不上,最后发现是脚本里的max_tokens从 128 改成了 256。参数变化记录在案,对比出的数字才有意义。部署这件事,配置可复制、结果可验证、变更可追溯,三条都做到,线上才稳得住。