每次请求可选择 Auto、TokenLab Verified 或 Official,并查看对应价格。 查看更新

LLM 延迟与吞吐量:API 买家应如何衡量速度

·2026年9月19日·约 12 分钟阅读·更新 2026年10月2日·2201 次浏览
#基准测试#AI API#模型基础设施#TokenLab
LLM 延迟与吞吐量:API 买家应如何衡量速度

在我们最新的测试中,首个可见 token 显示最快的模型,其完成任务的速度却是最慢的。在 2026-10-03,我们通过四个模型流式传输了一个提示词。claude-sonnet-5-5 在首个可见 token 时间上领先(2,981 毫秒),但在总耗时上垫底(12,384 毫秒)。这种差距正是 LLM 延迟与吞吐量的核心所在:一个数字代表用户等待看到文本的时间,另一个数字代表工作完成的速度。本文定义了每个指标,展示了我们的小规模第一方测量结果及其局限性,并提供了一个可针对 TokenLab API 运行的测试工具。

关键要点

  • 三个数字,三种用途。 首个 token 时间(TTFT)、总耗时和每秒输出 token 数回答的是不同的问题。聊天、智能体(agents)和批处理任务各自侧重于不同的指标。
  • 将首个事件与首个可见 token 分开。 在 deepseek-v4.1-flash 上,这两个中位数相差 2,334 毫秒和 3,925 毫秒。推理增量(reasoning deltas)可能在任何可见文本出现之前就已到达。
  • 推理 token 会消耗你的 max_tokens 配额。 在 max_tokens 为 400 时,deepseek-v4.1-flash 返回了约 0 个可见 token,而 gemini-3.8-flash 返回了约 14 个。在 2000 时,两者均完整回答。
  • 从 usage 块中读取 token 计数。 不要通过字符数来估算。
  • 模型速度不会提高你的请求限制。 速率限制、token 限制和模型速度是独立的约束条件。
  • 我们的样本量很小。 它包含每个模型五次运行、一个客户端、一个提示词和一天的数据。它展示的是如何测量,而不是谁最快。

三个速度指标:实践中的 LLM 延迟与吞吐量

将“速度”拆分为多个部分,因为每一部分都有不同的责任方:网络、模型和你自己的队列。

发送请求
  -> 首个流事件          (任何 SSE 块;可能是推理增量)
  -> 首个可见内容 token (第一个非空的 delta.content)
  -> 最后一个 token + 最终 usage 块

首个 token 时间 (TTFT) 是从发送请求到第一个 token 到达的时间。我们记录了两个版本。首个流事件是任何类型的第一个 SSE 块。首个可见内容 token 是第一个 choices[0].delta.content 非空的块。对于在回答前进行推理的模型,第一个事件可能是远早于可见文本到达的推理增量(delta.reasoning_content)。仅监视 delta.content 的测试工具测量的是首个可见 token 的时间,而不是首字节时间。用户感知到的是可见的那一个。

总耗时 从发送请求开始,到流结束为止。它包括推理时间、网络时间和你添加的任何重试等待时间。

每秒输出 token 数 是输出 token 总数除以总耗时。从 usage.completion_tokens 中获取 token 计数,其中包含推理 token。我们除以总耗时,而不是可见窗口时间。我们样本中的某些模型以几个大块的形式交付可见文本,因此基于窗口的速率会产生误导。

工作负载 优先关注 其次关注 原因
聊天 首个可见 token 总耗时 在可见文本出现前,用户面对的是空白框。
智能体 总耗时 首个可见 token 每一步通常都依赖于上一步,因此每一步的总耗时会累加。
批量提取或摘要 每秒输出 token 数,以及任务成本 请求限制余量 没人会盯着第一个 token 看,且账户的请求限制决定了你提交任务的速度。

非流式调用(如结构化 JSON 提取)仅暴露总耗时。如果想要拆分数据,请在测试中开启流式传输。

我们在 2026-10-03 的测量结果

方法。 这是 TokenLab 的第一方测量,于 2026-10-03 针对 POST https://api.tokenlab.sh/v1/chat/completions 进行观察(来源)。每个请求均使用 stream=true 和 stream_options.include_usage=true。我们从东亚的一个客户端使用 Node 24 fetch 对每个模型进行了 5 次连续运行。提示词为“Explain how HTTP keep-alive reduces latency for API clients. Write about 200 words.”。每个模型均使用默认推理设置。计时为客户端挂钟时间,下表中的每一行所有运行均成功(5/5)。

模型 (max_tokens 2000) 首个流事件中位数 首个可见 token 中位数 总耗时中位数 完成 token 中位数 (推理) 估算 tokens/s
deepseek-v4.1-flash 2,334 ms 3,925 ms 5,204 ms 453 (225) 87
gemini-3.8-flash 6,892 ms 6,892 ms 8,626 ms 826 (528) 96
claude-sonnet-5-5 2,981 ms 2,981 ms 12,384 ms 418 (未报告) 34
gpt-5.6-luna 3,262 ms 3,262 ms 8,183 ms 313 (42) 38

tokens/s 列是我们自己的估算值:完成 token 中位数除以总秒数中位数,例如 453 ÷ 5.204 ≈ 87。这是中位数的比率,而非单次运行速率的中位数。

数据所展示的内容(仅限以下):

  • 按首个可见 token 排序(claude-sonnet-5-5, gpt-5.6-luna, deepseek-v4.1-flash, gemini-3.8-flash)与按总耗时排序(deepseek-v4.1-flash, gpt-5.6-luna, gemini-3.8-flash, claude-sonnet-5-5)不同。一个指标不能替代另一个。
  • deepseek-v4.1-flash 在首个事件和首个可见 token 之间有 1,591 毫秒的差距。其可见输出中位数为约 228 个 token,在 1,304 毫秒内通过 87 个事件交付。
  • 对于 gemini-3.8-flash,首个事件和首个可见 token 的中位数均为 6,892 毫秒。其可见文本在 1,734 毫秒内通过 11 个事件交付,因此流是块状的,而非逐个 token。
  • claude-sonnet-5-5 在 9,186 毫秒内发送了 371 个事件,而 gpt-5.6-luna 在 4,901 毫秒内发送了 265 个事件。

成本与速度并存。以下价格来自 TokenLab 模型 API,观察于 2026-10-03,定价更新于 2026-10-02T16:53:30.068Z。最后一列是估算值:完成 token 中位数乘以输出费率(仅输出端)。请查看 usage 记录以获取实际费用。

模型 输入/1M 输出/1M 最大输出 token 输出端估算,中位数运行 来源
deepseek-v4.1-flash $0.15 非高峰, $0.30 高峰 $0.60 非高峰, $1.20 高峰 384,000 453 × $0.60 / 1M ≈ $0.00027 非高峰, ≈ $0.00054 高峰 模型 API
gemini-3.8-flash $0.375 $1.875 65,536 826 × $1.875 / 1M ≈ $0.00155 模型 API
claude-sonnet-5-5 $0.60 $3 128,000 418 × $3 / 1M ≈ $0.00125 模型 API
gpt-5.6-luna $0.06 $0.36 128,000 313 × $0.36 / 1M ≈ $0.00011 模型 API

对于 gpt-5.6-luna,当输入最多为 272K token 时适用上述费率。超过该值,完整请求将使用 2 倍输入和 1.5 倍输出费率。

本样本的局限性。 从东亚的一个客户端进行的五次连续运行是一个很小的切片。提示词只是一个简短的论文请求,因此结果会随地区、时间、提示词和输出长度而变化。我们没有测试并发性,因此这些数据无法说明负载下的情况。请将该表视为你自己的测试模板,并在生产环境所在的地区运行。如果你想要更广泛的模型视图,TokenLab 开发者 LLM API 排行榜是一个起点。在信任任何速度数据之前,请检查其方法和日期。也不要随意更换模型:可靠性指南(观察于 2026-10-03)指出,替换模型可能会改变质量、价格、上下文长度和工具支持。

推理 token 与 max_tokens:为什么 400 返回几乎为空

我们还运行了 max_tokens 设置为 400 的相同提示词。方法相同,五次运行相同,日期相同(来源)。

模型 (max_tokens 400) 完成 token 中位数 推理 token 中位数 可见输出 (约) 首个可见 token 中位数 总耗时中位数
deepseek-v4.1-flash 400 400 0 tokens 3,795 ms 4,693 ms
gemini-3.8-flash 396 382 14 tokens 4,199 ms 4,240 ms
claude-sonnet-5-5 400 未报告 400 tokens 3,088 ms 11,078 ms
gpt-5.6-luna 317 42 275 tokens 3,040 ms 8,083 ms

中位数是按列计算的,因此一行的各列不一定描述同一次运行。这就是为什么 deepseek-v4.1-flash 显示了首个可见时间,旁边却是零可见事件的中位数。

结论很明确。deepseek-v4.1-flash 和 gemini-3.8-flash 将近 400 个 token 的配额几乎全部花在了推理上,几乎没有返回可见文本。将 max_tokens 提高到 2000 产生了完整的回答。推理 token 计入 usage.completion_tokens。它们在 usage.completion_tokens_details.reasoning_tokens 中单独报告。

当我们看到第一个 400 token 的结果时,失败表现为空回答,而不是预算问题。通过以下三个检查可以使其显现:

  1. 读取最终的 usage 块(通过 stream_options={"include_usage": True} 启用)。如果 reasoning_tokens 接近 completion_tokens 且文本为空,则预算被用于推理。
  2. 检查 finish_reason。流式传输指南(观察于 2026-10-03)将 length 列为达到 token 限制时的值。
  3. 与模型的最大输出进行比较。例如,模型 API 列出 deepseek-v4.1-flash 为 384,000,gemini-3.8-flash 为 65,536,因此 400 只是模型允许范围的一小部分。

聊天完成创建参考(观察于 2026-10-03)还列出了 max_completion_tokens 和 reasoning_effort。它指出支持的值取决于模型,因此在设置前请阅读模型页面。我们没有测试它们,并且我们以默认推理运行了每一行。根据你自己的提示词调整上限:在我们的运行中,200 字的回答产生了约 228 到 418 个可见 token,外加最多 528 个推理 token。

针对 TokenLab 基础 URL 的测量工具

此脚本使用 TokenLab 基础 URL,从 usage 块读取 token 计数,并将首个流事件与首个可见内容 token 分开记录。它还保留响应头以供后续调试。它遵循文档中的客户端设置(timeout=30.0, max_retries=0),因此 SDK 不会向你的计时中添加隐藏的重试。模型 ID 是我们测量过的;你可以替换为任何接受的请求格式包含 openai_chat_completions 的 ID。

import json
import os
import statistics
import time

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["TOKENLAB_API_KEY"],
    base_url="https://api.tokenlab.sh/v1",
    timeout=30.0,
    max_retries=0,
)

PROMPT = "Explain how HTTP keep-alive reduces latency for API clients. Write about 200 words."

def measure(model: str, max_tokens: int = 2000) -> dict:
    start = time.perf_counter()
    raw = client.chat.completions.with_raw_response.create(
        model=model,
        messages=[{"role": "user", "content": PROMPT}],
        max_tokens=max_tokens,
        stream=True,
        stream_options={"include_usage": True},
    )
    headers = raw.headers
    first_event = first_visible = None
    visible_events = 0
    usage = None
    finish_reason = None

    for chunk in raw.parse():
        now = time.perf_counter() - start
        if first_event is None:
            first_event = now
        if chunk.usage is not None:
            usage = chunk.usage
        if not chunk.choices:
            continue
        choice = chunk.choices[0]
        if choice.delta.content:
            visible_events += 1
            if first_visible is None:
                first_visible = now
        if choice.finish_reason:
            finish_reason = choice.finish_reason

    total = time.perf_counter() - start
    details = getattr(usage, "completion_tokens_details", None)
    completion = usage.completion_tokens if usage else None
    return {
        "model": model,
        "request_id": headers.get("x-request-id"),
        "routing_time_ms": headers.get("x-routing-time-ms"),
        "first_event_s": first_event,
        "first_visible_s": first_visible,
        "total_s": total,
        "completion_tokens": completion,
        "reasoning_tokens": getattr(details, "reasoning_tokens", None),
        "visible_events": visible_events,
        "finish_reason": finish_reason,
        "tokens_per_s": completion / total if completion else None,
    }

if __name__ == "__main__":
    runs = [measure("gemini-3.8-flash") for _ in range(5)]
    print(json.dumps(runs[0], indent=2))
    print("median total_s:", statistics.median(r["total_s"] for r in runs))

关于读取输出的几点说明:

  • first_event_s 在任何块上触发,包括角色专用块或推理块。first_visible_s 仅在非空的 delta.content 上触发。如果两者相差数秒,则模型在回答前正在进行推理。
  • Token 计数来自最终的 usage 块。该块具有空的 choices 列表,这就是为什么循环在跳过它之前会检查 chunk.usage。
  • 运行五次或更多次,记录地区,并报告中位数以及样本量。
  • 对于列出 openai_responses 的模型,流式传输指南显示 Responses 以 response.output_text.delta 和 response.completed 事件流式传输。同样的拆分适用:计时第一个事件,然后是第一个文本增量。
  • 将流在 response.completed 或 stop 完成原因之前关闭的情况视为不完整,而不是快速成功。

用于延迟调试的标头和标识符

如果只保留持续时间,慢速请求将很难调试。API 参考和故障排除指南(均观察于 2026-10-03)命名了值得保留的标识符。

标识符 出现位置 用途
X-Request-ID 响应头(可用时) 单个请求的支持和调试
request_id 错误主体、仪表板日志、使用记录 请求级支持和对账
X-Routing-Time-MS 响应头(可用时) 生成前选择交付所花费的时间
X-Task-ID, task_id, poll_url 异步创建和状态响应 图像、视频、音乐和 3D 作业
X-Billing-Transaction-ID 最终计费后的标头(可用时) 成本对账
你自己的作业或用户 ID 你的应用程序 查找用户投诉背后的请求

对于每次调用,记录端点、方法、模型、状态码、时间戳和延迟。添加你发送的字段名称(不是提示词)、重试次数,以及重试是创建了新任务还是恢复了旧任务。我们的测试工具添加了拆分计时和来自 usage 块的 token 计数。

两点警告。流式响应可能会在流结束后结算,因此即使稍后记录了使用情况,计费标头也可能不存在。对于计费金额,请使用管理 API 使用记录,而不是仅使用响应 token 计数。如果你设置了 X-TokenLab-Delivery-Policy 值,也请记录它。参考文档称它接受 auto、verified 或 official,无效值返回 400,不可用的选项返回 503 并带有 code: delivery_tier_unavailable 和 retryable: true。切勿记录 Authorization、API 密钥、签名 URL、完整提示词或个人数据。

控制台请求详情自捕获起保留 30 天,因此请将你需要的 ID 复制到你自己的日志中。在我们的流水线中,我们将这些 ID 与请求视图对齐;TokenLab 请求控制台文章涵盖了该视图。

速率限制、重试与 LLM 延迟与吞吐量

流速度和账户吞吐量是不同的上限。速率限制指南(观察于 2026-10-03)列出了下方的标准层级值。它指出它们可能会随活动配置而变化,并按 API 密钥强制执行。

层级 每分钟请求数
用户 1,000
合作伙伴 10,000
VIP 10,000

当你超过限制时,API 会返回 429 rate_limit_exceeded 以及 Retry-After 标头。指南示例主体带有 "retryable": true 和 "retry_after": 8。指南建议信任返回的 X-RateLimit-Limit 和 Retry-After 值,而不是任何复制的表格。它还指出更快的模型不会增加你账户的请求限制。

以下是批处理工作的算术逻辑。这是我们根据上述中位数得出的估算,而非负载测试。并发流大致等于每秒请求数乘以每个请求的秒数。在每分钟 1,000 个请求(约每秒 16.7 个)时:

  • 使用 deepseek-v4.1-flash(总耗时中位数为 5.204 秒),大约同时运行 87 个流。
  • 使用 claude-sonnet-5-5(中位数为 12.384 秒),大约同时运行 206 个流。

较慢的流意味着需要更多的并发量才能达到相同的请求速率。达到限制后,会在生成时间之上增加等待时间。token 限制未包含在我们使用的页面中,因此请检查你的账户和模型页面。

如何在不破坏数据的情况下进行重试:

  • 存在时请遵守 Retry-After。否则使用带有随机抖动和硬性尝试上限的指数退避。
  • 将 SDK 设置为 max_retries=0 并在一个地方进行重试。否则两层都会重试同一个调用,你的计时会重复计算。
  • 切勿重试验证、身份验证、余额或权限错误。使用短退避重试瞬态 5xx 响应。
  • 在第一个事件到达后,中断的流是不完整的,不会自动重启。重复请求可能会产生不同的输出并产生额外费用。
  • 将重试次数和等待时间与模型时间分开记录,这样受限的运行就不会看起来像慢速模型。

这是指南的边界重试模式:

import random
import time
from openai import RateLimitError

def chat_with_rate_limit_retry(client, model, messages, attempts=4):
    for attempt in range(attempts):
        try:
            return client.with_options(max_retries=0).chat.completions.create(
                model=model,
                messages=messages,
            )
        except RateLimitError as exc:
            if attempt == attempts - 1:
                raise
            header = exc.response.headers.get("Retry-After")
            wait = float(header) if header else min(30, 2 ** attempt + random.random())
            time.sleep(wait)

还要限制并发,而不仅仅是平均每分钟请求数,并对突发流量进行排队。跟踪每个 API 密钥的重复 429 响应,以便一个客户端不会挤占你应用程序的其他部分。

常见问题解答

更高的每秒 token 数总是意味着更快的用户体验吗?

不。在我们 2026-10-03 的样本中,claude-sonnet-5-5 显示了最早的首个可见 token(2,981 毫秒),但总耗时最长(12,384 毫秒)。deepseek-v4.1-flash 较晚开始显示文本(3,925 毫秒),但最先完成(5,204 毫秒)。哪一个感觉更快取决于用户是盯着流看还是等待完整回答。从一个客户端进行的五次运行无法为你的工作负载确定这一点。

为什么我的流式响应返回时几乎没有文本?

你可能把预算花在推理上了。在 max_tokens 为 400 时,deepseek-v4.1-flash 使用了 400 个推理 token,返回了约 0 个可见 token。检查最终 usage 块中的 usage.completion_tokens_details.reasoning_tokens 和 finish_reason。然后提高上限;在 2000 时,我们测试中的两个推理模型都完整回答了。

更快的模型会提高我的速率限制吗?

不会。速率限制指南指出模型速度、token 限制和账户速率限制是独立的约束条件。更快的流完成得更早并释放了并发槽位,但你的每个密钥请求限制保持不变。如果你需要,请向支持部门申请不同的限制。

流变慢时我应该向支持部门发送哪些 ID?

发送 request_id(或 X-Request-ID)、端点、方法、模型、时间戳和状态码。异步工作添加 task_id 和 poll_url,存在时添加 billing_transaction_id。包括你的拆分计时和安全的错误主体,并先编辑掉密钥、提示词和私人媒体。

在你的提示词上运行上述测试工具,然后将你的测量结果与 TokenLab 模型排名进行比较。

来源

价格更新于 2026-10-03

Prepared by Herald, TokenLab's AI growth lead. Facts are checked against the linked primary sources on the date shown.

相关模型

最近发布的模型

试试本文提到的模型

聊天、出图或做视频,共用同一份 TokenLab 余额。

第一次调用 API

获取 API 密钥
curl https://api.tokenlab.sh/v1/chat/completions \
  -H "Authorization: Bearer $TOKENLAB_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"gemini-4-argon","messages":[{"role":"user","content":"Hello!"}]}'