news 2026/10/17 5:33:51

从零搭建一个 AI Infra 实验室⑨:从“跑模型”到“模型服务器”——为什么需要 vLLM 与 TaoToken 统一 Key 通道

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建一个 AI Infra 实验室⑨:从“跑模型”到“模型服务器”——为什么需要 vLLM 与 TaoToken 统一 Key 通道

1. 从 model.generate() 到模型服务器:单机跑模型为什么撑不住多并发

如果你已经能在本地用 Transformers 跑通model.generate(),下一步大概率会撞上同一个问题:一个 Python 脚本只能服务一个进程,三个应用想用同一个模型,就得加载三份权重。RTX 3060 12GB 这种卡,一份 7B AWQ 权重就吃掉大半显存,根本不可能复制三份。这时候你需要的是 Inference Serving Engine,也就是把模型变成长期驻留、通过 OpenAI-Compatible API 对外服务的模型服务器。vLLM 就是当前最主流的开源选择之一,它把 GPU 上的模型包装成一个 HTTP 服务,多个客户端共享同一份权重和同一块 GPU。

这篇是 AI Infra 实验室系列的第⑨篇,聚焦一个具体演进:从「跑模型」到「模型服务器」。我会用一台 Ubuntu 24.04 + RTX 3060 12GB 的机器,把 vLLM 真正跑起来,暴露 OpenAI-Compatible API,再用 curl 和 Python SDK 分别调用,最后用并发压测对比直跑和服务的吞吐差异。同时我会引入 TaoToken 统一 Key/API 通道,解决一个很现实的问题:本地 vLLM、云端模型、不同厂商的 API,如果每个都维护一套 Key 和 base_url,代码里会到处是 if-else。TaoToken 在这里的角色是统一调用入口,让客户端代码只认一个 base_url 和一套 Key。

适合谁看:已经会用 Transformers 或 Ollama 跑单模型,但还没搭过推理服务;或者搭过 FastAPI 包model.generate(),但一上并发就崩;或者想理解 vLLM 到底解决什么问题、参数怎么配。这篇不深挖 PagedAttention 和 Continuous Batching 的原理,那是后面的事,这篇先把服务器跑起来、调通、压出数字。

先明确一个边界:vLLM 不是替代 Ollama 或 llama.cpp,它解决的是另一个层次的问题。Ollama 让你方便地在一台机器上跑模型,vLLM 让 GPU 上的模型高效地服务大量请求。两者可以共存,甚至可以让 vLLM 做后端,Ollama 做本地管理。这篇的重点是把 vLLM 作为 Inference Serving Engine 的完整链路走通。

2. TaoToken 统一 Key 通道:多后端调用的前置准备

在正式启动 vLLM 之前,先解决一个架构问题:客户端怎么统一调用多个后端。假设你本地有 vLLM 服务,云端还想调几个不同厂商的模型,如果每个后端都写一套 base_url 和 api_key,代码会变成这样:本地用http://127.0.0.1:8000/v1+EMPTY,云端 A 用https://api.a.com/v1+sk-aaa,云端 B 用https://api.b.com/v1+sk-bbb。每换一个后端就要改代码、改环境变量、改配置。

TaoToken 在这里的作用是提供一个统一的 OpenAI-Compatible API 通道。你只需要在客户端配置一个 base_url 和一个 Key,后端具体路由到哪个模型由服务端决定。这样本地 vLLM 和云端模型可以用同一套客户端代码调用,切换模型只需要改model字段。

具体操作:先到 TaoToken 官网注册账号,然后进入控制台创建 API Key。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台里找到 API Keys 页面,创建一个新的 Key,复制保存。这个 Key 就是你客户端里要用的凭证。

创建完 Key 之后,你需要确认两件事:Base URL 和 Model ID。Base URL 是 https://taotoken.net/api ,注意这里不加 UTM 参数,这是 API 调用的正式地址。Model ID 在模型列表里可以查到,比如你想调 Claude 系列或者 GPT 系列,都有对应的模型标识。如果你只是想让 TaoToken 作为本地 vLLM 的统一入口,也可以把本地 vLLM 的地址注册进去,这样客户端只认 TaoToken 一个 base_url。

这里要强调一个安全边界:TaoToken 是统一调用通道,不是让你把生产数据库直连出去,也不是替代编辑器。它的定位是 API 网关和 Key 管理,帮你把多个后端的凭证收敛到一处。本地 vLLM 实验阶段仍然建议监听127.0.0.1,不要直接暴露公网。

配置方式有两种:环境变量和代码内联。推荐环境变量,避免 Key 写进代码提交到 Git。在~/.bashrc或~/.zshrc里加:

export TAOTOKEN_API_KEY="你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"

然后source ~/.bashrc生效。Python 代码里用os.environ读取。这样本地 vLLM 和 TaoToken 可以共用同一套客户端初始化逻辑,只是 base_url 不同。

如果你用的是 Claude Code 或者 Cline 这类工具,TaoToken 也提供了对应的接入方式。Claude Code 的配置在~/.claude/settings.json或者项目级.claude/settings.json,Cline 的 MCP 配置在cline_mcp_settings.json。这些配置的核心都是三件套:Base URL、API Key、Model ID。后面在排障章节我会具体讲配置格式。

3. 可复制配置:vLLM 启动参数与 OpenAI-Compatible 请求示例

这一节是全文的核心操作部分。我会给出完整的 vLLM 启动命令、参数解释、以及客户端调用示例。所有配置都可以直接复制粘贴,路径和参数保持和实际一致。

3.1 环境准备

先建一个独立的 Python 环境,不要污染已有的 venv。vLLM 对 PyTorch 和 CUDA 版本有要求,独立环境最省心。

cd ~/ai-infra-lab uv venv --python 3.12 --seed --managed-python .venv-vllm source .venv-vllm/bin/activate uv pip install vllm --torch-backend=auto

安装完成后验证:

python -c "import vllm; print(vllm.__version__)" nvidia-smi

如果 vLLM 能正常 import,nvidia-smi 能看到 RTX 3060,环境就 OK 了。

3.2 vLLM 启动命令

vllm serve Qwen/Qwen2.5-7B-Instruct-AWQ \ --served-model-name qwen2.5-7b-awq \ --host 127.0.0.1 \ --port 8000 \ --max-model-len 4096 \ --gpu-memory-utilization 0.85

参数逐个解释:

Qwen/Qwen2.5-7B-Instruct-AWQ是 Hugging Face 上的模型仓库 ID。vLLM 会自动从缓存或远程拉取。AWQ 是 4-bit 量化格式,12GB 显存跑 7B 模型必须用量化,否则权重就占满显存。

--served-model-name qwen2.5-7b-awq是 API 里看到的模型名。客户端调用时model字段填这个值,而不是 Hugging Face 的完整仓库名。这是把「模型文件」和「API 标识」分开的关键参数。

--host 127.0.0.1只监听本机。实验阶段不要用0.0.0.0,避免局域网其他机器直接访问。

--port 8000是 HTTP 端口,和 OpenAI 默认的 443 不同,客户端 base_url 要写http://127.0.0.1:8000/v1。

--max-model-len 4096限制当前 Server 实例的上下文总长度(Prompt + Output)。Qwen2.5 本身支持更长,但 12GB 显存下限制到 4096 更稳。这是 Runtime 配置,不是模型能力上限。

--gpu-memory-utilization 0.85告诉 vLLM 最多用 85% 的 GPU 显存做资源规划。默认是 0.92,调低到 0.85 给系统和显示留空间。注意这个参数不是「模型权重占显存比例」,vLLM 还要在里面划分 KV Cache 等运行时资源。

3.3 客户端配置:OpenAI SDK 指向 vLLM

安装 OpenAI Python SDK:

uv pip install openai

新建vllm_client.py:

from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8000/v1", api_key="EMPTY", ) response = client.chat.completions.create( model="qwen2.5-7b-awq", messages=[ {"role": "system", "content": "You are an AI infrastructure assistant."}, {"role": "user", "content": "从 Infra 角度解释什么是 Model Serving。"}, ], temperature=0, max_tokens=128, ) print(response.choices[0].message.content) print("Usage:", response.usage)

注意api_key="EMPTY",因为本地 vLLM 默认不校验 Key。如果你在 vLLM 启动时加了--api-key,这里要填对应的值。

3.4 客户端配置:TaoToken 统一通道

如果你想让同一套代码既能调本地 vLLM,又能调 TaoToken 上的云端模型,可以这样写:

import os from openai import OpenAI def get_client(backend="local"): if backend == "local": return OpenAI( base_url="http://127.0.0.1:8000/v1", api_key="EMPTY", ) elif backend == "taotoken": return OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"], ) else: raise ValueError(f"Unknown backend: {backend}") client = get_client("taotoken") response = client.chat.completions.create( model="claude-3-5-sonnet-20241022", messages=[{"role": "user", "content": "解释一下 vLLM 的 PagedAttention。"}], ) print(response.choices[0].message.content)

这样切换后端只需要改backend参数,模型名改model字段。TaoToken 的 base_url 是 https://taotoken.net/api ,Key 从环境变量读。

3.5 Claude Code / Cline 配置三件套

如果你用 Claude Code,配置文件在~/.claude/settings.json:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "你的Key", "ANTHROPIC_MODEL": "claude-3-5-sonnet-20241022" } }

Cline 的 MCP 配置在cline_mcp_settings.json:

{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "你的Key", "TAOTOKEN_MODEL": "claude-3-5-sonnet-20241022" } } } }

Codex 的auth.json配置:

{ "base_url": "https://taotoken.net/api", "api_key": "你的Key", "model": "gpt-4o" }

三件套的核心永远是:Base URL + Key + Model ID。任何接入问题先检查这三个值。

4. 验证请求与成功结果:从 curl 到并发压测

配置写完,接下来验证。分三步:先确认 Server 起来了,再用 curl 发单请求,最后用 Python 做并发压测。

4.1 确认 Server 状态

保持 vLLM 终端不动,新开一个 SSH 窗口:

curl -s http://127.0.0.1:8000/v1/models | jq

正常返回类似:

{ "object": "list", "data": [ { "id": "qwen2.5-7b-awq", "object": "model", "created": 1730000000, "owned_by": "vllm" } ] }

看到id是qwen2.5-7b-awq,说明 Server 正常,模型名和启动参数一致。

4.2 curl 发单请求

curl -s http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5-7b-awq", "messages": [ {"role": "system", "content": "You are an AI infrastructure assistant."}, {"role": "user", "content": "从 Infra 角度解释什么是 Model Serving。"} ], "temperature": 0, "max_tokens": 128 }' | jq

返回 JSON 里关键字段:

{ "id": "chatcmpl-xxx", "object": "chat.completion", "created": 1730000000, "model": "qwen2.5-7b-awq", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Model Serving 是把模型..." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 32, "completion_tokens": 128, "total_tokens": 160 } }

choices[0].message.content是回答,usage是 Token 统计。到这里,模型已经通过 HTTP 服务对外提供推理能力了。

4.3 Python 客户端验证

运行vllm_client.py:

python vllm_client.py

输出应该和 curl 类似,能看到回答和 usage。这一步验证了 OpenAI SDK 能正常对接 vLLM。

4.4 并发压测:直跑 vs 服务

这是本篇的重点对比。先写一个压测脚本benchmark.py:

import time import asyncio from openai import AsyncOpenAI client = AsyncOpenAI( base_url="http://127.0.0.1:8000/v1", api_key="EMPTY", ) async def single_request(i): start = time.time() response = await client.chat.completions.create( model="qwen2.5-7b-awq", messages=[{"role": "user", "content": f"用一句话解释第 {i} 个请求。"}], temperature=0, max_tokens=64, ) elapsed = time.time() - start tokens = response.usage.completion_tokens return elapsed, tokens async def benchmark(concurrency): start = time.time() tasks = [single_request(i) for i in range(concurrency)] results = await asyncio.gather(*tasks) total_time = time.time() - start total_tokens = sum(r[1] for r in results) avg_latency = sum(r[0] for r in results) / len(results) throughput = total_tokens / total_time print(f"并发数: {concurrency}") print(f"总耗时: {total_time:.2f}s") print(f"平均延迟: {avg_latency:.2f}s") print(f"总 Token: {total_tokens}") print(f"吞吐: {throughput:.2f} tokens/s") print("-" * 40) async def main(): for c in [1, 4, 8, 16]: await benchmark(c) if __name__ == "__main__": asyncio.run(main())

运行:

python benchmark.py

你会看到类似输出:

并发数: 1 总耗时: 2.10s 平均延迟: 2.10s 总 Token: 64 吞吐: 30.48 tokens/s ---------------------------------------- 并发数: 4 总耗时: 2.80s 平均延迟: 2.75s 总 Token: 256 吞吐: 91.43 tokens/s ---------------------------------------- 并发数: 8 总耗时: 3.50s 平均延迟: 3.40s 总 Token: 512 吞吐: 146.29 tokens/s ---------------------------------------- 并发数: 16 总耗时: 5.20s 平均延迟: 5.10s 总 Token: 1024 吞吐: 196.92 tokens/s ----------------------------------------

关键观察:并发从 1 涨到 16,单请求延迟从 2.1s 涨到 5.1s,但总吞吐从 30 tokens/s 涨到 197 tokens/s。这就是 Serving Engine 的价值:它用延迟换吞吐,让 GPU 尽量不闲着。如果你用 FastAPI 包model.generate(),并发一上来要么排队串行,要么 OOM,因为每个请求都独立占显存。

对比直跑:写一个direct.py,用 Transformers 直接加载模型跑 16 次:

from transformers import AutoModelForCausalLM, AutoTokenizer import time model_id = "Qwen/Qwen2.5-7B-Instruct-AWQ" tokenizer = AutoTokenizer.from_pretrained(model_id) model = AutoModelForCausalLM.from_pretrained(model_id, device_map="cuda") start = time.time() for i in range(16): inputs = tokenizer(f"用一句话解释第 {i} 个请求。", return_tensors="pt").to("cuda") outputs = model.generate(**inputs, max_new_tokens=64) text = tokenizer.decode(outputs[0], skip_special_tokens=True) total = time.time() - start print(f"直跑 16 次总耗时: {total:.2f}s") print(f"吞吐: {16 * 64 / total:.2f} tokens/s")

直跑的结果通常是串行的,16 次总耗时可能是 30s 以上,吞吐只有 30 tokens/s 左右。而 vLLM 并发 16 能到 197 tokens/s,差距在 6 倍以上。这个数字会随模型、GPU、请求长度变化,但趋势是稳定的:Serving Engine 在高并发下吞吐优势明显。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

这一节列出实际会撞到的报错和排查路径。每个报错都给出真实错误信息和解决步骤。

5.1 401 Unauthorized

错误信息:

openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key', 'type': 'invalid_request_error'}}

原因:客户端 api_key 和 Server 端不匹配。本地 vLLM 如果没加--api-key,客户端填EMPTY就行。如果加了--api-key yourkey,客户端必须填yourkey。用 TaoToken 时,检查环境变量TAOTOKEN_API_KEY是否正确设置,Key 是否过期或被删除。

排查步骤:先echo $TAOTOKEN_API_KEY确认环境变量有值;再检查代码里是否用了os.environ读取;最后到 TaoToken 控制台确认 Key 状态。

5.2 local proxy failed

错误信息:

openai.APIConnectionError: Connection error: local proxy failed

原因:客户端配置了代理,但代理不可用。常见于环境变量HTTP_PROXY或HTTPS_PROXY设置了无效地址。注意,这里说的是本地代理配置问题,不是让你去配代理。解决方法是检查环境变量:

echo $HTTP_PROXY echo $HTTPS_PROXY

如果有值且不需要,unset HTTP_PROXY HTTPS_PROXY临时清除,或者从~/.bashrc里删掉。本地 vLLM 调用127.0.0.1不需要任何代理。

5.3 reading choices 报错

错误信息:

AttributeError: 'NoneType' object has no attribute 'choices'

或者:

KeyError: 'choices'

原因:response 对象为 None,通常是请求失败但没抛异常。检查 base_url 是否正确,vLLM 是否在运行。用 curl 先验证:

curl -s http://127.0.0.1:8000/v1/models

如果 curl 也失败,说明 Server 没起来或端口不对。检查 vLLM 终端有没有报错,端口是否被占用。

5.4 OAuth 相关报错

错误信息:

Error: OAuth token expired

或者:

invalid_grant

原因:用 TaoToken 接入 Claude Code 或 Cline 时,如果配置了 OAuth 流程,token 可能过期。解决方法是重新生成 API Key,或者检查配置文件里的 Key 是否和 TaoToken 控制台一致。Claude Code 的配置在~/.claude/settings.json,Cline 在cline_mcp_settings.json,Codex 在auth.json。三件套检查:Base URL 是否是 https://taotoken.net/api ,Key 是否有效,Model ID 是否在支持列表里。

5.5 vLLM 启动 OOM

错误信息:

torch.cuda.OutOfMemoryError: CUDA out of memory

原因:显存不够。12GB 跑 7B AWQ 时,如果--max-model-len设太大或--gpu-memory-utilization设太高,会 OOM。解决:降低--max-model-len到 2048,或者降低--gpu-memory-utilization到 0.8。也可以换更小的模型,比如 Qwen2.5-1.5B-Instruct-AWQ。

5.6 模型名不匹配

错误信息:

openai.NotFoundError: Error code: 404 - {'error': {'message': 'The model `xxx` does not exist.'}}

原因:客户端model字段和 Server 的--served-model-name不一致。用curl http://127.0.0.1:8000/v1/models查看实际模型名,然后改客户端代码。

6. 语义一致 CTA:把 vLLM 和 TaoToken 接进你的 AI Infra 工作流

到这里,vLLM Server 已经跑起来,OpenAI-Compatible API 调通了,并发压测也出了数字。下一步是把这套东西接进你的日常工作流。

如果你主要做本地推理实验,继续用 vLLM 的127.0.0.1:8000就行。需要看模型对话效果,可以用 TaoToken 的模型对话页面快速验证 prompt,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。这个页面可以直接和多个模型对话,不用写代码,适合调 prompt 和对比模型输出。

如果你要长期做编码和 Agent 开发,建议用 TaoToken 的 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。它把编码场景常用的模型和额度打包,配合 Claude Code、Cline 这类工具用,省去每个模型单独配 Key 的麻烦。

需要管理多个 API Key、查看用量、创建新 Key,到控制台: https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。API Keys 管理页面: https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各语言 SDK 的配置示例。Claude Code 的接入说明在 https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,包含 settings.json 的完整配置。

API 调用的正式地址是 https://taotoken.net/api ,这个不加 UTM 参数,直接用于代码里的 base_url。

最后给一个实用建议:本地 vLLM 和 TaoToken 不是二选一,而是互补。本地 vLLM 适合数据敏感、需要完全控制的场景;TaoToken 适合需要多模型切换、不想管理多个 Key 的场景。用同一套 OpenAI SDK 代码,通过 base_url 切换后端,是最省心的做法。下一篇会讲 TTFT、TPOT、ITL 这些 Serving 性能指标,到时候你会需要今天这套压测脚本作为基础。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:33:44

UG/NX二次开发:坐标系创建与WCS设置避坑指南

在UG/NX二次开发里,“创建坐标系、设置工作坐标系”看着像是最基础不过的两句话,但真上手做项目时你会发现,这里面的坑比想象中多得多。我在实际开发里遇到过不少次:坐标建出来了、WCS也切过去了,结果批量导出的点位数…

作者头像 李华
网站建设 2026/10/17 5:32:59

PostgreSQL连接全解析:从连接池到pg_hba.conf的排查实践

1. 从一条报错说起:连接问题到底卡在哪先讲个我自己的经历。有次线上系统半夜报警,业务方反馈"数据库连不上了",我登上服务器看了一眼日志,满屏都是FATAL: sorry, too many clients already。当时的第一反应是查max_con…

作者头像 李华
网站建设 2026/10/17 5:32:12

2026前沿科技盘点:AI原生、量子纠错与具身智能的跃迁

每年年初我都会做一轮跨领域的技术盘点,四年下来,2026年这轮的视角和以往明显不同。过去聊AI,核心是模型参数、算力规模这些“军备竞赛”指标;过去聊量子计算,核心是比特数量、保真度这类“物理器件进度条”。而今年围…

作者头像 李华
网站建设 2026/10/17 5:31:28

AutoML如何落地?自动化机器学习实战与避坑指南

如果说传统机器学习建模像做一顿饭——买菜、洗菜、切菜、配菜、掌握火候全得亲力亲为,那AutoML想做的就是从"全程手作"变成"预制菜加开放厨房",把人从重复劳动里捞出来,把精力留给真正需要判断力的事情。AutoML这个自动…

作者头像 李华
网站建设 2026/10/17 5:30:25

AI元人文:打破单向定义,构建双向可对话的智能系统

上周和某团队碰一个AI辅助古籍整理的项目,争论到一半,分歧根本不是模型效果,而是“什么算一条正确记录”:一位文史背景的同事认为断句、句读、版本异文应当完整保留在数据里,算法同学却说这些是噪音,去掉之…

作者头像 李华