ARTICLE DETAIL

资讯详情

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

大模型推理引擎到底在调度什么?从 vLLM 到 SGLang 看服务端黑箱与 TaoToken 统一 Key 通道

大模型推理引擎到底在调度什么?从 vLLM 到 SGLang 看服务端黑箱与 TaoToken 统一 Key 通道 1. 请求进来之后推理引擎到底在忙什么如果你只是在 API 层面用大模型很容易把这件事想得特别简单发请求、等结果、结束。但你只要开始真做线上服务就会发现这套东西根本不是“发出去、回回来”这么朴素。你看到的只是一个接口后面其实是一个不停调度请求、抢显存、拆批次、复用缓存、分配 GPU、平衡 TTFT 和 TPOT 的服务系统。它更像一个小型操作系统只是这个操作系统管理的不是文件和进程而是 token、KV Cache、batch、prefill、decode以及那些正在排队的人。所以这个问题比“模型怎么跑”更值得问大模型推理引擎到底在调度什么答案其实很直接——它调度的是有限 GPU 资源和无限变化的请求形态之间的冲突。一个请求进来以后不是立刻就算。你发一个长 Prompt它跟一个短问答不是同一种负载你让模型输出 20 个字和输出 2000 个字也不是同一种负载你前面塞了 10 万 token 的文档和只塞了一句问题更不是同一种负载。但推理引擎在调度时必须把这些完全不同的请求塞进同一套 GPU 资源池里。这就麻烦了因为 LLM 推理有两个阶段Prefill 和 Decode性格还完全相反。Prefill 需要并行计算把一大段输入吃进去Decode 需要一点一点生成每次只吐一个 token。如果引擎把这两类请求粗暴混在一起就会出现两边都不爽的局面Prefill 会把 Decode 挤死Decode 又会让 Prefill 的大 batch 打不满。这也是为什么推理引擎的第一件大事不是“算模型”而是“分配工作”。谁先上、谁后上、谁跟谁一起上、谁该等一下、谁该换 GPU这些才是它真正的日常。本文会从 vLLM 到 SGLang 拆开这个服务端黑箱给出可复制的调度参数配置与压测验证动作并说明如何通过 TaoToken 统一 Key/API 通道接入多模型服务方便你对比不同引擎的调度表现。适合正在自建推理服务、或者准备把模型服务接进业务链路的开发者。2. vLLM 与 SGLang 的调度黑箱KV Cache 分页与连续批处理推理引擎这条线里vLLM 的重要性特别高。它不是第一个做 LLM serving 的系统但它把一个之前没人真正解决好的问题做明白了——KV Cache 的显存管理。PagedAttention 的核心思想很像操作系统分页不是给每个请求预留一整块连续大内存而是把 KV Cache 切成小 block按需分配block table 负责映射。这件事看起来像内存管理的小改良其实对 serving 很关键因为 LLM 请求的长度差异太大了。如果你按最大长度去预留浪费会非常夸张。PagedAttention 让这些 cache block 像页一样可共享、可回收、可复制写把“显存不够”从一个粗暴的硬件问题变成了可以调度的资源问题。一旦你能按 block 管显存后面很多事都能谈连续批处理、前缀复用、分支共享、并发控制甚至分离式推理。vLLM 不是把模型跑快了一点它是把“显存怎么分”这件事工程化了。连续批处理解决的是“谁在等谁”。如果只做静态 batching推理系统很容易被一个长请求拖死你凑一批一起算等齐以后一起上这对图像分类也许还行但对 LLM serving 非常不友好因为每个请求的长度和生成速度差异极大静态 batch 往往会让短请求白白等长请求。连续批处理的核心意思很朴素不是等整批结束再换人而是每一轮 decode step 结束后把做完的请求踢出去把新来的请求补进来。这样 GPU 能持续被喂着跑请求也不用傻等。SARATHI 这类工作把这个思路推得更明显它通过 chunked-prefills 和 decode-maximal batching让 prefill 的 chunk 去“顺带”填充 decode 的空隙提升 decode throughput。Prompt Cache 和 RadixAttention 解决的是“有没有重复干活”。很多 LLM 应用其实并不是每次都在从零开始System Prompt 一样、工具定义一样、模板一样、RAG 的某些检索块一样、多轮对话里前面那一大坨背景也常常一样。如果每次都重新 prefill 一遍那就是赤裸裸的重复劳动。vLLM 有 prefix cachingSGLang 有 RadixAttention两者的思路都在于把重复前缀的 KV cache 留住下次命中就直接复用。SGLang 的论文把这个讲得很明确系统专门针对 structured generation、multi-turn chat、RAG、JSON decoding 这些复杂程序做优化RadixAttention 就是让 KV reuse 变得系统化的关键组件之一。这里真正值得注意的不只是“省了一次 prefill”而是推理引擎开始把“重复内容”看成一等公民。以前你可能觉得缓存只是个性能小优化现在不一样了缓存已经是 serving 架构的一部分。谁复用得好谁就更省钱谁前缀稳定谁就更快谁的工作流更结构化谁就更吃得到系统红利。这也是为什么 Agent、RAG、代码助手一类场景会特别吃推理引擎的红利因为它们天生会把相同前缀反复打进来。调度器最在乎的其实不是单次平均耗时而是两种延迟TTFT 和 TPOT。前者决定用户多久能看到第一个字后者决定后面每个 token 的速度。这两个东西在 serving 里经常是互相打架的你为了更低 TTFT可能得减少排队让请求更快进去你为了更高吞吐可能得把 batch 塞得更满但 batch 太满TTFT 又容易上去。DistServe 这篇论文把这个矛盾说得很直接它指出 prefill 和 decode 的资源画像不一样如果混在一起就会互相干扰于是提出把 prefill 和 decode 分离部署到不同 GPU 上并根据 TTFT 和 TPOT 约束去共同优化资源分配和并行策略。这其实就是推理引擎调度的本质不是让 GPU 一直忙而是让 GPU 忙得有意义忙在正确的阶段、正确的请求、正确的资源池里。如果把问题再往前推一步就会发现“一个 GPU 跑一个请求”这套思路迟早会撞到天花板因为 Prefill 更像算力密集型Decode 更像带宽密集型把它们硬塞在一个池子里最后往往是谁都没吃满。所以分离式推理开始变得合理Prefill GPU 专门负责把输入吃进去写好 KV CacheDecode GPU 专门负责后续 token 的生成中间用高速互连把 KV Cache 传过去。推理引擎越往后走越像一个真正的集群调度系统它不是“模型盒子”而是“模型工作流的操作系统”。SGLang 的重点则更偏 structured generation。它的出发点不是“怎么把一个请求跑得更快”而是“怎么让复杂的语言模型程序可表达、可复用、可优化”。SGLang 的前端负责控制流、选择、并行后端负责用 RadixAttention、compressed FSM、speculative execution 这些东西提速在 agent control、logical reasoning、few-shot、JSON decoding、RAG pipelines、多轮 chat 上都给出了很强的吞吐表现。很多人把 LLM 应用写成了一堆 prompt 拼接但真正成熟的推理引擎已经开始把这些结构看成程序了。程序就应该有缓存、有状态、有控制流、有分支复用这就是 SGLang 这类系统的方向感不是单次生成而是生成程序化不是临时拼 prompt而是把 prompt 结构本身系统化。3. 可复制配置vLLM 与 SGLang 调度参数怎么落地这一节直接给你能跑的配置。先说清楚一个前提下面所有配置都假设你已经把模型权重放在本地路径并且用 OpenAI 兼容接口对外暴露服务。这样做的最大好处是你后面无论用哪家统一 Key 通道去接客户端代码都不用改。先看 vLLM 的启动命令。这里我用的参数都是和调度强相关的不是随便堆的python -m vllm.entrypoints.openai.api_server \ --model /models/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 64 \ --max-num-batched-tokens 8192 \ --enable-prefix-caching \ --enable-chunked-prefill \ --swap-space 8 \ --disable-log-requests逐个解释关键参数。--gpu-memory-utilization 0.90决定 vLLM 愿意占用多少显存留 10% 给系统和其他进程设太高容易 OOM设太低 KV Cache block 不够并发一上来就排队。--max-num-seqs 64是同时能处理的序列数上限这个值直接决定连续批处理的宽度太小 GPU 喂不饱太大 TTFT 会抖。--max-num-batched-tokens 8192控制单次前向的 token 总量配合--enable-chunked-prefill使用长 prompt 会被切成 chunk 塞进 decode 的空隙里。--enable-prefix-caching打开前缀复用如果你的应用有固定 System Prompt这个开关的收益非常明显。--swap-space 8是 CPU 侧交换空间单位 GB显存吃紧时能兜底。再看 SGLang 的启动命令它的参数命名不太一样但调度的核心逻辑是相通的python -m sglang.launch_server \ --model-path /models/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --host 0.0.0.0 \ --port 30000 \ --context-length 8192 \ --mem-fraction-static 0.85 \ --max-running-requests 64 \ --chunked-prefill-size 4096 \ --schedule-policy lpm \ --enable-radix-cache \ --log-level warning--mem-fraction-static 0.85对应 vLLM 的显存占用比例SGLang 把静态显存和动态 KV 池分开算所以这个值通常比 vLLM 略低一点更稳。--max-running-requests 64就是并发上限。--chunked-prefill-size 4096是 chunked prefill 的切块大小。--schedule-policy lpm是调度策略lpm 指 longest prefix match会优先调度前缀匹配度高的请求配合 RadixAttention 用效果最好。--enable-radix-cache打开基数树缓存这是 SGLang 的招牌能力。如果你要把这两个服务接进统一通道可以在客户端侧用一份 JSON 配置管理多个后端。下面这份配置可以直接复制路径和字段名按你自己的环境改{ providers: { vllm-local: { base_url: http://127.0.0.1:8000/v1, api_key: sk-local-vllm, model_id: qwen2.5-7b }, sglang-local: { base_url: http://127.0.0.1:30000/v1, api_key: sk-local-sglang, model_id: qwen2.5-7b }, taotoken: { base_url: https://taotoken.net/api, api_key: sk-your-taotoken-key, model_id: qwen2.5-7b } }, default_provider: taotoken }这份配置里base_url、api_key、model_id三件套是必须写全的缺一个都会在请求时直接报错。TaoToken 的 API 地址是https://taotoken.net/api注意这里不带任何查询参数Key 在控制台生成后填到api_key字段即可。这样你本地跑 vLLM 和 SGLang 做压测线上走统一通道做对比客户端代码只需要切换default_provider一个字段。4. 验证请求与压测怎么确认调度真的生效了配置写完不算完你得验证调度参数真的起作用了。最直接的办法是发一个请求看返回再用压测工具看吞吐和延迟曲线。先做单请求验证确认服务能通curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-local-vllm \ -d { model: qwen2.5-7b, messages: [{role: user, content: 用一句话解释什么是 KV Cache}], max_tokens: 128, temperature: 0.7 }如果返回里能看到choices数组和正常的content说明服务通了。如果走 TaoToken 统一通道把base_url换成https://taotoken.net/apiapi_key换成你在控制台生成的 Key其余请求体完全一样。这就是统一 Key 通道的价值你压测不同引擎时客户端只改一个地址不用重写业务代码。单请求通了之后上压测。我用的是vllm bench serve它对 OpenAI 兼容接口的压测支持比较完整vllm bench serve \ --backend openai-chat \ --base-url http://127.0.0.1:8000 \ --model qwen2.5-7b \ --endpoint /v1/chat/completions \ --dataset-name sharegpt \ --dataset-path /data/ShareGPT_V3_unfiltered_cleaned_split.json \ --num-prompts 500 \ --request-rate 20 \ --max-concurrency 64 \ --save-result \ --result-dir ./bench-vllm跑完之后重点看几个指标。TTFT是首 token 延迟TPOT是每 token 输出时间Throughput是总吞吐Request throughput是每秒完成请求数。如果你开了--enable-prefix-caching可以再跑一遍同样的数据集对比 TTFT 和吞吐的变化。实测下来固定 System Prompt 的场景里前缀缓存命中后 TTFT 能降一大截吞吐也会跟着上去。SGLang 侧可以用它自带的bench_servingpython -m sglang.bench_serving \ --backend sglang \ --host 127.0.0.1 \ --port 30000 \ --model qwen2.5-7b \ --dataset-name sharegpt \ --dataset-path /data/ShareGPT_V3_unfiltered_cleaned_split.json \ --num-prompts 500 \ --request-rate 20 \ --max-concurrency 64对比两个引擎的结果时注意控制变量同样的数据集、同样的并发、同样的max_tokens。你会发现 SGLang 在多轮对话和结构化输出场景下RadixAttention 的复用优势会体现得比较明显而 vLLM 在纯高并发短请求场景下PagedAttention 的显存管理更稳。这不是谁绝对更好而是调度策略适配的工作负载不同。如果你要对比线上统一通道和本地引擎的表现可以把base_url指向https://taotoken.net/api用同一份压测脚本再跑一遍。这样你手里就有三组数据vLLM 本地、SGLang 本地、统一通道。哪组在你的业务负载下 TTFT 和 TPOT 更可控一目了然。5. 常见报错排查401、local proxy failed 与 reading choices调度参数配错或者通道接错报错信息往往很直接但新手容易卡住。这里列几个我踩过的坑对照着查。第一个是401 Unauthorized。这个几乎都是 Key 的问题。检查三件事api_key字段有没有填、Key 有没有多余空格、Key 是不是已经过期。如果你用的是本地 vLLM 或 SGLangapi_key可以随便填一个非空字符串但如果是走 TaoToken 统一通道必须用控制台生成的真实 Key。另外注意base_url和api_key要配套本地地址配本地 Key线上地址配线上 Key混用必报 401。第二个是local proxy failed或连接被拒绝。这个通常不是 Key 的问题而是地址或端口不对。先确认服务真的在监听curl http://127.0.0.1:8000/v1/models能不能返回模型列表。如果本地服务没起来检查启动命令里的--host 0.0.0.0和--port有没有被占用。如果走统一通道报这个错检查base_url是不是写成了https://taotoken.net/api注意结尾不要多加/v1路径拼接由客户端库处理。第三个是reading choices相关的报错比如KeyError: choices或者list index out of range。这个说明请求发出去了但返回体不是标准的 OpenAI 格式。常见原因有两个一是model_id写错了服务端找不到对应模型返回了错误结构二是max_tokens设得太大超过了--max-model-len服务端直接拒绝。检查model_id是否和启动命令里的--served-model-name一致检查max_tokens加上输入长度有没有超过上下文窗口。第四个是 OOM 或者CUDA out of memory。这个和调度参数直接相关。--gpu-memory-utilization设太高、--max-num-seqs设太大、--max-num-batched-tokens设太大都会把显存吃爆。排查顺序是先把--max-num-seqs降到 16再把--max-num-batched-tokens降到 4096跑通了再往上加。SGLang 侧对应调--mem-fraction-static和--max-running-requests。第五个是 OAuth 或鉴权头格式问题。有些客户端库默认用Authorization: Bearer key有些用api-key头。如果你手动拼请求确认头字段名和值格式对。走统一通道时标准做法就是Authorization: Bearer sk-xxx不要自己加额外前缀。排查的时候有个通用思路先用curl发一个最小请求确认服务通再逐步加参数看哪一步开始报错。不要一上来就跑压测那样报错信息会被淹没在并发日志里。6. 统一 Key 通道多引擎对比与长期接入把 vLLM 和 SGLang 都跑起来之后你会发现一个现实问题每个引擎的地址、Key、模型名都不一样业务代码里到处是硬编码的base_url换一个后端就要改一次代码。这时候统一 Key 通道的价值就出来了。TaoToken 的接入方式很简单Base URL 用https://taotoken.net/apiKey 在控制台生成模型 ID 按你要调用的模型填。三件套写全之后客户端代码和调本地服务完全一样。你可以把本地 vLLM、本地 SGLang、统一通道三个后端都写进同一份配置用default_provider切换。压测的时候切到本地线上跑的时候切到统一通道业务代码一行不用改。如果你要长期做编码类或 Agent 类任务可以关注 Coding Plan它更适合持续性的开发场景。如果只是临时验证某个模型的表现用模型对话页面直接试就行。接入文档里有完整的参数说明和示例API Keys 页面负责生成和管理 Key。回到调度这个话题。推理引擎的调度能力最终要落到你的业务负载上才有意义。固定前缀多不多、输入输出长度分布怎么样、工作负载是不是结构化、有没有多轮共用上下文、TTFT 和 TPOT 哪个更卡——这些判断比你盲目换更大模型更重要。vLLM 的 PagedAttention 解决内存管理连续批处理解决吞吐和队列prefix caching 和 RadixAttention 解决复用DistServe 解决阶段冲突SARATHI 解决 chunked prefill 和 decode 的互相占位SGLang 解决结构化生成程序的执行。这些东西合在一起才是现在推理引擎的真实面貌。它不是一个模型 runtime而是一整套为 LLM 工作负载量身定制的调度系统。你看到的是一次回答它看到的是一个不断变化的工作负载图。当你的应用开始变大最先告诉你“你得懂一点底层”的从来不是模型而是成本和延迟。大模型不是只要更聪明还要更会被调度。能把它调度明白的人才真正理解了它怎么活在生产环境里。
返回列表