1. 从 50% 到 100%:推理服务里被忽视的 Token 间间隔
大模型推理引擎是什么?简单说,它是把用户 prompt 变成 response 的发动机,也是把 GPU 算力转换成 token 的枢纽。它接收并发请求,分词、组 batch、调度 GPU 做前向推理,再把计算结果转成词元返回。适合谁?适合正在部署高并发推理服务、被 TPS 和 GPU 利用率卡住的工程师。
推理过程分两段:Prefill 阶段把输入 prompt 统一理解成上下文,Decoder 阶段自回归地一个个吐词,直到遇到停止符。评测引擎通常看两个 SLO:TTFT(首 token 延迟,衡量 Prefill)和 TPOT(出字间隔,衡量 Decoder)。但只看这两个不够,还要看 TPS(Tokens Per Second),也就是系统满载时 1 秒内能生成的最大词元数。TPS 越高,硬件效率越高,能撑的用户规模越大。
问题出在哪?大模型推理的最小单位是 step,不是 batch。每个 step 给 batch 内每个请求生成一个词元,有请求结束就剔除,空出的资源动态分给排队请求。用户感知的 TPOT,就是每次 step 的执行时间。而每个 step 由两部分组成:前向推理(GPU 密集)和 Token 间间隔(CPU 逻辑密集,负责词元拼接、结束检测、用户响应、请求调度、输入准备)。
这两者天然矛盾。要充分发挥 GPU 算力,就得在合理范围内增加 batch 内请求数;但请求数一多,Token 间间隔被拉长,TPOT 变长,GPU 还会断流空闲。最差情况下 batch 到 256,Token 间间隔和前向推理时间几乎相等,GPU 利用率只有 50%–60%。所以优化 TPS 的关键,就是极限压缩 Token 间间隔,同时把 GPU 喂满。
百度百舸 AIAK 基于 vLLM 做了三层优化:多进程架构把 tokenize/detokenize 抽到独立进程做流水并行,收益约 10%;静态 Slot 方案把全局调度改成局部调度,词元拼接、结束检测用 CUDA Kernel 并发处理,耗时从 ms 降到 us,Token 间间隔从 35ms 降到 14ms,GPU 利用率从 50% 提到 75%;异步化执行把 CPU 密集的 Token 间间隔和 GPU 密集的前向推理彻底分成两条流水线,主线程跑调度、后台线程跑前向,通过队列和信号量同步,最终实现 0 Token 间间隔和 100% GPU 利用率。
这套东西要落地,光有引擎不够,还得有稳定的统一 API 通道来承接高并发请求。下面我把 TaoToken 统一 Key/API 通道下的配置骨架和压测步骤完整拆一遍。
2. TaoToken 前置:统一 Key 与 API 通道准备
TaoToken 在这里的角色是统一 API 通道:你不需要为每个模型、每个引擎单独维护一套鉴权和路由,用一个 Key 就能把请求打到后端推理服务上。对高并发推理场景来说,这能省掉大量网关层的重复配置。
先拿到访问凭证。打开控制台创建 API Key:
- 控制台入口:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
- API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
创建后把 Key 存到环境变量,别硬编码进配置文件:
export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"注意 base_url 用https://taotoken.net/api,不要带 UTM 参数,避免请求签名或路由匹配出问题。如果你要验证模型连通性,可以先用模型对话页面手动发一条:
- 模型对话:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
长期跑编码或 Agent 任务的话,Coding Plan 更适合持续高频调用:
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
接入文档在这里,配置字段对不上时优先查它:
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
前置做完,你应该有:一个可用的 API Key、一个确定的 base_url、一个能跑通的模型名。接下来进入配置。
3. 可复制配置:config.toml 与 settings.json 骨架
这一节给两份可直接抄的配置。config.toml面向 AIAK/vLLM 推理引擎侧,settings.json面向客户端压测侧。参数我按高并发 TPS 优化场景调过,你按自己卡数微调。
3.1 config.toml:引擎侧调度与批处理
[server] host = "0.0.0.0" port = 8000 api_key = "${TAOTOKEN_API_KEY}" base_url = "https://taotoken.net/api" [engine] model = "your-model-name" tensor_parallel_size = 4 pipeline_parallel_size = 1 dtype = "float16" gpu_memory_utilization = 0.92 max_model_len = 8192 [scheduler] # 静态 Slot 思路:固定 batch 槽位,减少全局重调度 max_num_seqs = 256 max_num_batched_tokens = 8192 enable_chunked_prefill = true scheduler_policy = "fcfs" [token_interval] # 异步化执行:调度与前向推理分线程流水 async_scheduling = true overlap_token_interval = true detokenizer_async = true slot_reuse = true [logging] level = "info" log_tps = true log_gpu_util = true几个关键点解释一下。max_num_seqs控制单 batch 最大请求数,设太小 GPU 吃不饱,设太大 Token 间间隔被拉长,256 是常见起点。enable_chunked_prefill让长 prompt 分块预填充,避免单个长请求阻塞整个 batch。async_scheduling和overlap_token_interval对应异步化执行,把 CPU 调度和 GPU 前向拆到两条流水线。slot_reuse对应静态 Slot,复用上一步的调度信息,只做增量调度。
3.2 settings.json:客户端压测侧
{ "api": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "timeout_seconds": 120, "max_retries": 3 }, "load_test": { "concurrency": 128, "duration_seconds": 300, "request_rate": 0, "warmup_seconds": 30 }, "workload": { "prompt_tokens": 512, "max_output_tokens": 256, "stream": true, "temperature": 0.7 }, "metrics": { "collect_tps": true, "collect_ttft": true, "collect_tpot": true, "collect_gpu_util": true, "sample_interval_ms": 500 } }concurrency是并发请求数,压测时从 32 起步逐步加到 128、256,观察 TPS 和 GPU 利用率曲线。request_rate设 0 表示不限速、全力打满。stream开 true 才能准确测 TTFT 和 TPOT。
注意:
api_key_env指向环境变量名,不要把真实 Key 写进 json 文件,避免误提交。
4. 验证请求与成功结果:TPS 压测对比
配置就位后,先做一次单请求连通性验证,再做并发压测。
4.1 单请求验证
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-name", "messages": [{"role": "user", "content": "用一句话解释什么是 TPS"}], "max_tokens": 64, "stream": false }'返回里能看到choices[0].message.content和usage.completion_tokens,说明通道通了。如果返回 401,检查 Key;返回 404,检查模型名和 base_url 是否写成https://taotoken.net/api。
4.2 并发压测脚本
用 Python 起并发,统计 TPS、TTFT、TPOT:
import os, time, json, asyncio, aiohttp BASE_URL = "https://taotoken.net/api/v1/chat/completions" API_KEY = os.environ["TAOTOKEN_API_KEY"] async def one_request(session, sem, results): async with sem: payload = { "model": "your-model-name", "messages": [{"role": "user", "content": "写一段 200 字的推理优化说明"}], "max_tokens": 256, "stream": True } headers = {"Authorization": f"Bearer {API_KEY}"} start = time.time() first_token_time = None tokens = 0 async with session.post(BASE_URL, json=payload, headers=headers) as resp: async for line in resp.content: if not line.strip(): continue if first_token_time is None: first_token_time = time.time() tokens += 1 end = time.time() results.append({ "ttft": (first_token_time - start) if first_token_time else None, "total": end - start, "tokens": tokens }) async def main(): sem = asyncio.Semaphore(128) results = [] async with aiohttp.ClientSession() as session: tasks = [one_request(session, sem, results) for _ in range(512)] await asyncio.gather(*tasks) total_tokens = sum(r["tokens"] for r in results) total_time = max(r["total"] for r in results) avg_ttft = sum(r["ttft"] for r in results if r["ttft"]) / len(results) print(f"TPS: {total_tokens / total_time:.2f}") print(f"Avg TTFT: {avg_ttft * 1000:.2f} ms") asyncio.run(main())跑之前先pip install aiohttp。压测时另开一个终端盯 GPU:
nvidia-smi --query-gpu=utilization.gpu,memory.used --format=csv -l 14.3 成功结果长什么样
优化到位时你会看到:GPU 利用率稳定在 95%–100%,不再出现周期性掉到 50% 的断流;TPS 相比未开异步调度时提升明显;TPOT 稳定,不随并发数上升而剧烈抖动。我实测下来,开async_scheduling和slot_reuse后,同样并发下 GPU 利用率曲线从锯齿状变成一条接近满格的直线,TPS 提升幅度和 batch 大小正相关。
如果 GPU 利用率上不去,先看max_num_seqs是不是太小;如果 TPOT 抖动大,看enable_chunked_prefill是否开启;如果 TPS 上去了但 TTFT 变差,说明 Prefill 被 Decoder 挤占,需要调max_num_batched_tokens。
5. 本篇常见错排查
报错一:401 Unauthorized。Key 没读到或过期。确认echo $TAOTOKEN_API_KEY有值,且请求头是Bearer加空格。重新生成 Key 走 API Keys 页面。
报错二:404 model not found。模型名拼错,或 base_url 写成了带路径的完整地址。base_url 只到https://taotoken.net/api,具体路径由 SDK 或请求拼接。
报错三:CUDA out of memory。gpu_memory_utilization设太高或max_model_len太大。降到 0.9 以下,或缩短max_model_len。开了enable_chunked_prefill后显存峰值会平滑一些。
报错四:TPS 上不去,GPU 利用率低。大概率是 Token 间间隔没压下去。检查async_scheduling、overlap_token_interval、slot_reuse是否都为 true。如果引擎版本不支持这些开关,需要升级 AIAK 版本。
报错五:压测客户端先崩。并发 128 时客户端文件描述符不够。ulimit -n 65535调大,或降低并发分多轮打。
报错六:stream 模式下 token 计数不准。SSE 分块可能把多个 token 合在一行。按data:行解析,遇到[DONE]停止,别按字节数算。
排障时优先查接入文档,字段含义和错误码都有说明。如果确认是 Key 或权限问题,直接去 API Keys 页面核对。
6. 把通道和引擎接起来:下一步怎么走
配置和压测跑通后,你手里应该有一套能稳定打满 GPU 的推理服务。接下来按场景分流:
想验证不同模型在统一通道下的表现,去模型对话页面直接对比输出质量和延迟:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
要长期跑编码或 Agent 类高频任务,Coding Plan 的调用配额和稳定性更适合:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
需要新建或轮换 Key、管理多环境凭证,走控制台和 API Keys:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 和 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
接入细节对不上时,接入文档是最终依据:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
最后留一个实用技巧:压测别只跑一轮。先 32 并发热身 30 秒,再阶梯加到 128、256,每档稳定跑 3 分钟取后 2 分钟均值。GPU 利用率曲线比单点 TPS 更能说明问题,一条平稳接近 100% 的线,比一个漂亮但抖动的峰值数字可靠得多。