
DeepSeek-V4 响应慢先别急着换模型TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_end能帮你的是把请求通道和 Key 这件事一次性理顺真正让体感快起来的还是 TTFT 和 TPOT 这两个指标背后的代码与网关设置。很多人把“慢”笼统归给模型结果调了半天温度参数光标还是转圈。拆开看TTFT 是从发出请求到第一个 token 到达的时间它高基本是流式没开、反向代理把 SSE 攒成一整块或者客户端超时太短导致重试TPOT 是首 token 之后每个 token 的平均间隔它高常见于 System Prompt 每次都在变前缀缓存命中率低让模型反复重算同一段上下文也有可能是简单分类任务被路由到了 Pro 模型。下面按排障顺序把通道切换和 7 个调优方法串起来每一步都能在你的本地服务或网关里对照执行。1. 先把 TTFT 和 TPOT 拆开再决定改哪一层1.1 两个指标各自对应的“嫌疑人”在没有拆分指标之前所有优化都是猜。建议在自己的服务里记两个时间戳请求发出时间 t0首个delta.content到达时间 t1最后一个 chunk 到达时间 t2。TTFT t1 - t0TPOT (t2 - t1) / (output_tokens - 1)。这两个数字一摆出来方向就清楚了。如果 TTFT 占了总时长的大头优先查流式和网关如果 TPOT 高优先查前缀缓存和模型路由。指标计算方式主要影响优先排查TTFT首个 token 到达 - 请求发出等待感、页面是否“卡住”stream、Nginx buffering、超时重试TPOT首 token 后平均每 token 耗时长回答的“拖沓感”System Prompt 一致性、缓存命中、Flash/Pro 路由、并发总时长TTFT TPOT × 输出 token 数端到端体验以上两者的组合还有一种更隐蔽的情况客户端看起来只慢在“最后一下子”其实是应用先把整个流收集成字符串再一次性返回给前端。这种情况下 TTFT 和 TPOT 都正常但前端只看到最后那个完整结果体感依旧很差。所以指标要测在响应出口而不是测在模型调用内部。1.2 在 TaoToken 拿 Key把 Base URL 填进 SDK原文里这一步是去官方控制台申请 Key现在把它换成打开 TaoToken 注册账号进控制台创建 API Key复制出来先放环境变量或配置文件。注意TaoToken 在这里只负责提供调用 DeepSeek-V4 的 Key 和兼容 Base URL它不会替你打开流式也不会替你把 System Prompt 定下来。Base URL 填https://taotoken.net/api末尾不要加/v1更不要把官网的 UTM 参数带进 SDK。import os from openai import OpenAI client OpenAI( api_keyYOUR_API_KEY, # 从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建 base_urlhttps://taotoken.net/api, )先把这条通道跑通用同一把 Key、同一个模型 ID 发一条普通对话不开启任何高级参数。确认能通之后再往下加流式、缓存和路由。模型 ID 不要手写以模型广场当时列表为准Base URL 后面不带/v1这是最容易导致 404 的一处。2. 方法一streamTrue别让首 token 等在缓冲区里2.1 Python SDK 里打开流式并保留 usage不开流式时客户端要等模型把整段话生成完才拿到响应TTFT 自然高得吓人。开启streamTrue之后首个 token 一到就能推给前端。同时建议加上stream_options{include_usage: True}这样最后一个 chunk 会带回 token 用量和缓存命中信息方便后面算 TPOT 和缓存比例。import time t0 time.perf_counter() resp client.chat.completions.create( modelYOUR_MODEL_ID, # 以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场为准 messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_text}, ], streamTrue, stream_options{include_usage: True}, timeout60, ) ttft None last t0 output_tokens 0 for chunk in resp: if chunk.choices and chunk.choices[0].delta.content: delta chunk.choices[0].delta.content if ttft is None: ttft time.perf_counter() - t0 last time.perf_counter() output_tokens 1 print(delta, end, flushTrue) if chunk.usage: print(\nusage:, chunk.usage) print(f\nTTFT{ttft:.3f}s) if output_tokens 1: tpot (last - t0 - ttft) / (output_tokens - 1) print(fTPOT≈{tpot:.4f}s)这段代码只依赖你的本地 Python 环境和那把 Key。TaoToken 负责让请求走到兼容通道但不会替你迭代for chunk in resp也不会替你关掉网关缓冲。真正的速度提升来自你在这一层的写法。2.2 别把流 collect 成列表再处理最常见的反模式是写chunks list(resp)或者text .join(...)以为还在用流式其实已经把整个响应收完了。如果中间还有一层 FastAPI 或 Flask 路由记得用真正的流式响应而不是先把生成器转成字符串再return。下面这个片段让响应头直接声明 SSE 和禁用加速缓冲。from fastapi import FastAPI from fastapi.responses import StreamingResponse app FastAPI() app.get(/chat/stream) async def chat_stream(q: str): def gen(): stream client.chat.completions.create( modelYOUR_MODEL_ID, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: q}, ], streamTrue, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: yield chunk.choices[0].delta.content return StreamingResponse( gen(), media_typetext/event-stream, headers{X-Accel-Buffering: no}, )如果你的框架默认会对响应做压缩或缓冲这个X-Accel-Buffering: no是给 Nginx 看的告诉它不要攒这个流。少了这一行即使应用逐 token 产出前端仍可能等一整段才收到。3. 方法二Nginx 的 proxy_buffering off 和 X-Accel-Buffering: no3.1 反代配置逐行对照如果你的后端前面有 Nginx即使应用开了streamTrueNginx 也可能把 SSE 攒到一定大小才吐给浏览器这时候 TTFT 看起来还是很高。需要两件事应用响应头带X-Accel-Buffering: noNginx 的 location 里关掉proxy_buffering和proxy_cache。下面这份配置针对你自己的流式接口不是让你去改 TaoToken 的地址。location /api/chat/stream { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; add_header X-Accel-Buffering no; proxy_read_timeout 300s; proxy_send_timeout 300s; }proxy_buffering off让 Nginx 收到一点就转发一点proxy_cache off避免中间缓存把流拦下来add_header X-Accel-Buffering no是双保险防止上游忘了设置时 Nginx 仍按默认策略缓冲。如果你在 Nginx 和后端之间还挂了别的网关同样的逻辑要逐层检查。3.2 用 curl -N 确认流是“滴”出来的配置改完不要靠感觉用curl -N直接观察。-N关掉 curl 自己的缓冲Accept: text/event-stream告诉服务端你要流。curl -N -H Accept: text/event-stream \ http://127.0.0.1:8000/api/chat/stream?qtest如果 token 是一段一段出现的说明流已经通了如果仍然等几秒一次性出现回看应用是否真的yield而不是return一个完整字符串。再检查 Nginx 的错误日志和访问日志看请求是否命中了正确的 location。4. 方法三System Prompt 固定下来把 prompt_cache_hit_tokens 拉到 70% 以上4.1 前缀缓存命中的条件前缀缓存不是玄学它要求多次请求的开头部分完全一致相同的 system message、相同的 tools 定义、相同的 few-shot 示例顺序。如果你每次把当前时间、随机 ID、用户昵称拼在 System Prompt 最前面前缀就变了缓存命中率自然掉到很低。把 System Prompt 做成一个常量字符串把动态内容移到 user message 里是提升 TPOT 最便宜的一招。写法前缀是否稳定缓存命中system 里写“现在是 12:01”每次变低system 固定规则时间放在 user 内容里稳定高tools 定义顺序每次从 dict 遍历生成顺序不稳低tools 定义按固定列表输出顺序稳定高多轮对话也是同理把稳定前缀放在最前面把最近几轮动态内容放在后面。只要前缀部分不变后续请求就能复用之前算过的 KV。4.2 在 usage 里观察 prompt_cache_hit_tokensDeepSeek 系列的 usage 里会带prompt_cache_hit_tokens和prompt_cache_miss_tokens。把这两个数打印出来算出命中率目标是把稳定前缀的命中率拉到 70% 以上。usage chunk.usage if usage: hit getattr(usage, prompt_cache_hit_tokens, 0) or 0 miss getattr(usage, prompt_cache_miss_tokens, 0) or 0 total hit miss ratio hit / total if total else 0 print(fcache hit: {ratio:.1%} hit{hit} miss{miss}) if ratio 0.7: print(检查 System Prompt、tools 顺序、示例顺序是否每次都变)如果低于 70%先别继续调 temperature回头把 System Prompt 模板、工具描述顺序、示例顺序固定下来再跑一批同样的请求看比例。缓存命中率上来之后TPOT 通常会明显下降尤其是长 System Prompt 的场景。5. 方法四到七Flash/Pro 路由、并发批处理、连接复用、max_tokens5.1 方法四简单任务走 Flash复杂任务才上 Pro简单意图识别、补全、分类这类任务对模型推理深度要求不高路由到 Flash 型号能明显降低 TPOT只有需要多步推理、代码生成、长链路工具调用的请求才路由到 Pro。模型 ID 以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场当时列表为准不要自己拼日期后缀。下面是一个按任务复杂度选模型的例子。FLASH_MODEL YOUR_FLASH_MODEL_ID # 以模型广场为准 PRO_MODEL YOUR_PRO_MODEL_ID # 以模型广场为准 def pick_model(task: str) - str: hard_keywords (重构, 证明, 多步, 代码生成, 架构) if any(k in task for k in hard_keywords) or len(task) 800: return PRO_MODEL return FLASH_MODEL路由逻辑放在自己的业务代码里TaoToken 只负责把请求送到你选定的模型。不要把所有请求都固定在最重的型号上也不要为了省成本把复杂任务压到 Flash那样反而会因为回答质量差而重试最终更慢。5.2 方法五控制并发别让请求互相排队如果你在自己的服务里批量调用一次性甩出几百个并发TTFT 会被排队拉长。用信号量控制并发或者分批提交。如果是自建推理连续批处理是另一层的事用兼容通道时你能控制的是客户端并发和连接复用。import asyncio sem asyncio.Semaphore(8) async def bounded_call(prompt: str): async with sem: return await call_async(prompt)并发上限没有万能值先观察你的服务在 4、8、16 并发下的 TTFT 曲线找到开始明显抬头的拐点然后把信号量设在那附近。同时确认上游没有把连接数限制得很低否则排队会从客户端转移到网络上。5.3 方法六连接复用别每次请求都新建 client每次请求都OpenAI(...)会重新建 TLS 连接TTFT 白白多出几十到几百毫秒。把 client 做成模块级单例或者用连接池让同一个进程内的请求复用长连接。# 模块级单例进程内复用 client OpenAI( api_keyYOUR_API_KEY, # 从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建 base_urlhttps://taotoken.net/api, timeout60, )注意 timeout 不要设得太短。长回答在 60 秒内没结束很常见超时后重试会把已经生成的部分丢到反而拉高整体耗时。给流式请求单独设一个较长的读超时比如 90 到 120 秒。5.4 方法七max_tokens 和超时别让长输出拖垮体感max_tokens设得过大模型可能生成很长的尾巴超时设得太短长回答会被截断并触发重试。给不同任务设不同的上限分类任务 256 够用摘要 512代码生成可以放到 2048 或 4096。重试策略只针对连接错误不要对超时盲目重试。resp client.chat.completions.create( modelmodel, messagesmessages, streamTrue, max_tokens1024, timeout90, )如果业务允许还可以在客户端做“首 token 后无数据超时”而不是整体超时。这样既能尽早发现卡住又不会误杀正在持续输出的长回答。6. 排障对照改完还慢按这个顺序查6.1 404 / 401 / 路径多了 /v1401Key 没带或带错回 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 控制台确认 Key 状态必要时重新创建一把。404多半是 Base URL 写成了https://taotoken.net/api/v1SDK 又拼了一次路径。记住只填https://taotoken.net/api。模型不存在模型 ID 从模型广场复制不要手写也不要自己加日期后缀。路径重复检查反向代理是否把/v1/chat/completions又转了一次导致上游收到双份路径。现象常见原因处理401 UnauthorizedKey 缺失、过期、带空格重新复制YOUR_API_KEY404 Not FoundBase URL 多了/v1或路径拼错只保留https://taotoken.net/api400 模型不存在模型 ID 手写错误从模型广场复制连接超时网络或代理层超时检查本地出口和 Nginx timeout6.2 流式仍一次性吐出依次查SDK 是否真的streamTruefor chunk in resp是否被list()包住Nginx 是否proxy_buffering off应用是否设置了X-Accel-Buffering: no中间是否还有一层 CDN 或企业网关在缓冲。把每一层都过一遍通常卡在应用层或 Nginx 层。6.3 缓存命中率上不去查 System Prompt 是否每次变、tools 定义顺序是否变、是否在 messages 最前面插了动态内容。如果用了多轮对话把稳定前缀放在 history 最前面把变化的用户输入放在后面。缓存命中率是 TPOT 的先行指标它上不来后面调路由和并发都只是治标。7. 跑通之后去控制台对一下这次调用本地指标正常之后用 TaoToken 模型对话 发一条测试消息确认同一把 Key、同一模型 ID 能返回顺便看这次调用有没有记上账。如果准备长期在 IDE 或 CLI 里调用去 Coding Plan 看套餐是否够用Key 不够就回 控制台 API Keys 再建一把。调优这件事没有一劳永逸先把流式和缓存这两个大头稳住再按业务量慢慢收紧并发和路由DeepSeek-V4 的响应速度自然会回到该有的水平。