如何测量本地大模型每秒生成多少 Token
不要直接套用云厂商数据。用并发扫描测量 TTFT、输出 tok/s 和饱和点,再判断租用 GPU 何时比按 token 计费更划算。
每秒 token 数决定 GPU 是否划算
每秒 token 数表示服务器生成输出文本的速率。这个数值决定租用 GPU 是否比按 token 支付 API 费用更便宜。GPU 服务器按小时计费,无论处于繁忙还是空闲状态。托管 API 按 token 计费。因此,只有在付费的大多数小时内保持足够高的输出速率,GPU 才更划算。
因此,您需要实际测量结果,而不是某处读取的数字。本节定义值得记录的 4 个数值,然后提供生成这些数值的命令,以及将它们换算为决策依据的计算方法。
为什么已公布的每秒 token 数不是您的实际数值
DigitalOcean 在 2026 年 7 月公布了单块 NVIDIA H200 在 vLLM 下使用 llama3.3-70b-instruct FP8(8 位浮点数)时的吞吐量数据。这些数据有参考价值,但不等于您的实际数据。
此图表背后的数据
[
{
"config": "H200, one stream",
"tok_s": "47"
},
{
"config": "H100, saturated",
"tok_s": "236"
},
{
"config": "H200, saturated",
"tok_s": "2,036"
},
{
"config": "H200, saturated, in+out",
"tok_s": "4,071.6"
}
]上表每一行都引用自该页面,其中两行是页面给出的范围下限,因此应将这两行视为最低值。表中的数据均未由我们实测。
先看最后两行。标题数据为 4,071.6 tok/s,而仅统计输出的速率为 2,036 tok/s。标题数据同时统计输入和输出 token。该测试使用了 1,024 个输入 token 和 1,024 个输出 token,因此输出速率几乎正好是标题数据的一半。区分两者很重要,因为输出阶段速度较慢。Prefill(读取提示词)一次处理所有输入 token。Decode(生成答案)每次只生成一个 token。总吞吐量数据将处理成本较低的阶段与处理成本较高的阶段取平均,因此不能代表纯输出速率。
再看第一行。同一块 H200 每次只处理一个请求时,速率为 47 tok/s,因此在相同硬件上,饱和吞吐量超过该数值的 40 倍。出现这一差距,是因为单个 decode 步骤的大部分时间都在等待显存,而并发请求可以填补这段空闲时间。第二行的 236 tok/s 来自同一模型上的单块 H100,其性能受到 KV cache(key 和 value cache,即服务中的会话为每个请求保留在显卡上的内存)限制。对于 70B 模型,80 GB 显卡能容纳的并发请求更少,因此更早达到饱和,吞吐量也更低。
更换模型,或改变输入与输出的比例,上述每个数值都会变化。已公布的数据只能帮助您建立预期,不能直接用于确定预算。这与在磁盘和网络方面诚实地测试 VPS 性能时遵循的规则相同。
真正重要的4个数字
- 首个 token 延迟(TTFT)。 从发送请求到收到第一个输出 token 之间的延迟。它由预填充时间和排队时间组成。用户会直接感受到这项指标。
- 每个流每秒输出的 token 数。 响应开始生成后,单个回答的生成速度。超过大约20 tok/s 后,速度已经快于大多数人的阅读速度,因此继续提高这项指标的收益很小。
- 饱和状态下的总输出吞吐量。 服务器完全加载时所有并发流的输出速度总和。这是容量指标,也是直接影响 GPU 成本的指标。
- 并发条件下的 p50 和 p99 TTFT。 p50 表示中位请求。p99 表示100个请求中有99个低于该值。排队问题总会先体现在 p99 上。
服务器空闲时,前两项指标会变好。服务器繁忙时,第三项指标会变好。它们彼此制约,因此无法用单个数字描述推理服务服务器。
测量前固定输入和输出长度
吞吐量取决于流量的形态。包含 4,000 个 token 的提示词和 50 个 token 的回答属于预填充密集型工作。包含 200 个 token 的提示词和 2,000 个 token 的回答属于解码密集型工作。同一台服务器在这两种情况下报告的每秒 token 数会有很大差异。因此,应选择一个比例,将其写在记录的每个数值旁边,并且不要跨比例比较。输入 1,024 个 token、输出 1,024 个 token 是合理的默认值,因为多家供应商会发布这一比例下的数据。如果了解实际流量,应使用实际流量。
还要固定输出长度。模型在生成 60 个 token 后遇到停止 token 时,运行时间会短于完整运行,二者无法比较:TTFT 在运行时间中所占的比例会更大。基准测试会据此计算出更低的输出吞吐量,尽管每个请求更快完成。vLLM 基准测试客户端中的 --ignore-eos 标志会让每个请求准确生成指定数量的 token,从而保持两次运行结果的可比性。模型选择对这些数值的影响大于任何标志:将 Qwen 3 模型装入单个 VPS GPU介绍了这一选择涉及的内存问题。
先测量单个流
从最简单的情况开始。这既是合理性检查,也是上限测试。Ollama 会输出自身的计时信息。
ollama run llama3.1:8b --verbose "Write 200 words about disk scheduling."需要查看的字段是 eval rate,表示每秒输出的 token 数。prompt eval rate 是预填充速率,load duration 是将模型加载到 VRAM 所用的时间。冷启动后的第一次调用中,load duration 通常很大,因此 total duration 会产生误导。将命令运行两次,然后读取第二次的结果。Ollama 默认会在模型空闲五分钟后将其卸载,因此两次运行之间如果间隔较长,就会重新进入冷启动状态。
API 也会返回相同的字段,而且更容易编写脚本处理。
curl -s http://127.0.0.1:11434/api/generate -d '{
"model": "llama3.1:8b",
"prompt": "Write 200 words about disk scheduling.",
"stream": false
}' | jq '{eval_count, eval_duration,
tok_s: (.eval_count / (.eval_duration / 1000000000))}'eval_duration 的单位是纳秒,除以 1,000,000,000 即可得到秒数。这正是 Ollama 的 API 文档为计算每秒 token 数所规定的换算方式。如果服务器尚未启动,请参阅 使用 Ollama 在 VPS 上自行托管 LLM,其中介绍了安装过程和 systemd 单元。
TTFT 需要使用流式请求,curl 可以为你测量这段时间。
curl -s -o /dev/null -N \
-w 'ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"Qwen/Qwen3-8B",
"messages":[{"role":"user","content":"Write 200 words about disk scheduling."}],
"stream":true,"max_tokens":256}'time_starttransfer 表示响应正文的第一个字节到达的时刻。在流式聊天补全中,该字节属于第一个服务器发送事件;它可能是第一个内容 token,也可能是在该 token 前发送的仅包含角色信息的增量。因此,应将该值视为近似 TTFT,误差约为一个事件。对于比较同一服务器上的两次运行,这个精度已经足够。
单流数据会在两个方面高估服务器能力。TTFT 是理论上的最佳值,因为前面没有排队请求。单流速率也是理论上的最佳值,因为整块显卡只处理一个请求。这两个指标都不能说明服务器实际可以承载多少请求。
如何执行并发度扫描?
扫描会在逐步增加并发度的情况下运行同一个固定工作负载,并记录每一步的结果。vLLM 提供了用于此操作的客户端,而且该客户端使用 OpenAI API,因此也适用于 Ollama 及其他兼容 OpenAI API 的服务。
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts 160 \
--max-concurrency 16 \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99--max-concurrency限制同时处理中的请求数,也是要扫描的变量。--num-prompts是发送请求的总数,因此应将其设为并发度的约 10 倍,以获得稳定的平均值。摘要会打印 Output token throughput (tok/s): 和 Total token throughput (tok/s):,然后打印 Mean TTFT (ms):、Median TTFT (ms): 和 P99 TTFT (ms):,其中 Mean TTFT (ms):、Median TTFT (ms): 和 P99 TTFT (ms): 位于 Time to First Token 标题下。
该输出不提供每个流的速率,但只需进行一次除法即可得到。Mean TPOT (ms):表示首个输出 token 之后每个输出 token 的平均耗时,因此每个 token 耗时 25 ms 就表示每个流每秒 40 个 token。用输出吞吐量除以并发度也会得到相同结果。
然后循环执行,并保存每次运行的结果。
for C in 1 4 16 32 64 128; do
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts $(( C * 10 )) \
--max-concurrency "$C" \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99 \
--save-result --result-filename "sweep-c$C.json"
done读取保存的 JSON 文件
每次运行都会写入一个文件,因此可以一次性从所有文件中提取所需字段。
for f in sweep-c*.json; do
jq -r --arg f "$f" \
'[$f, .output_throughput, .total_token_throughput,
.median_ttft_ms, .p99_ttft_ms] | @tsv' "$f"
doneoutput_throughput表示每秒输出的 token 数。total_token_throughput会将输入 token 也计入,因此在输入输出比例为 1:1 时,该值接近翻倍。即使没有 --metric-percentiles,也会有 p99_ttft_ms,因为该标志的默认值为 99。命令中的 50 会在每个 --percentile-metrics 中指定的指标旁边添加 p50_ttft_ms,而 median_ttft_ms 是每个指标都会有的内容。请求一个未指定的百分位数(例如 .p90_ttft_ms)时,jq 会打印 null。
并发扫描实际显示什么?
此图表背后的数据
[
{
"label": "1 stream",
"per_stream_tok_s": 92,
"total_tok_s": 92,
"ttft_p50_ms": 48,
"ttft_p99_ms": 61
},
{
"label": "4 streams",
"per_stream_tok_s": 88,
"total_tok_s": 352,
"ttft_p50_ms": 71,
"ttft_p99_ms": 96
},
{
"label": "16 streams",
"per_stream_tok_s": 71,
"total_tok_s": 1136,
"ttft_p50_ms": 152,
"ttft_p99_ms": 244
},
{
"label": "32 streams",
"per_stream_tok_s": 54,
"total_tok_s": 1728,
"ttft_p50_ms": 287,
"ttft_p99_ms": 498
},
{
"label": "64 streams",
"per_stream_tok_s": 34,
"total_tok_s": 2176,
"ttft_p50_ms": 611,
"ttft_p99_ms": 1240
},
{
"label": "128 streams",
"per_stream_tok_s": 18,
"total_tok_s": 2304,
"ttft_p50_ms": 1490,
"ttft_p99_ms": 3820
}
]这 6 行数据展示了小型可租用 GPU 服务器执行扫描时产生的曲线形态,数值范围符合实际量级。它们不是对您的服务器进行的测量,也不是供应商提供的数据。请运行上面的循环,并替换为您自己的数据。
请关注曲线形态,因为可泛化的是形态。在单流情况下,整台服务器每秒生成 92 个 token,p99 TTFT 为 61 ms。在 128 streams 时,总吞吐量达到每秒 2304 个 token,是前者的 25 倍;但每个独立流的速度降至每秒 18 个 token,p99 TTFT 升至 3820 ms。批处理将原本空闲的内存等待转化为有效工作,因此总吞吐量上升。相同的计算资源需要共享,因此单流速度下降。
最后一次翻倍最能说明问题。从 64 个流增加到 128 个流时,总吞吐量仅增加不到 6%,而 p99 TTFT 大约增加到原来的 3 倍。这表示请求正在排队,而不是执行中。原因可能是 KV cache 已满,也可能是服务器达到 vLLM 的 max_num_seqs 等调度上限。更合适的运行点在更早的位置:32 个流时,服务器仍可达到每秒 1728 个 token,即峰值的 75%;每个流的速度为每秒 54 个 token,p99 TTFT 为 498 ms。请将该点报告为您的容量。曲线峰值并不是可以据此为用户提供服务的数值。
Ollama 和 vLLM 衡量的不是同一项指标
对默认 Ollama 服务器执行这组测试时,总耗时几乎不会变化。OLLAMA_NUM_PARALLEL 的默认值为 1,因此只有一个请求在运行,其余请求都在等待。正是这个队列导致 p99 TTFT 上升,而总输出量保持不变。在开始测量前,先提高该值。
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_NUM_PARALLEL=8"使用 sudo systemctl restart ollama 重启,然后确认模型仍能装入显存。每个并行槽都会分配自己的上下文窗口,因此 Ollama 文档指出,4 个并行请求使用 2K 上下文时,会分配 8K 上下文。将并行槽数量提高到一定程度后,模型就会超出 VRAM。检查 ollama ps:如果 PROCESSOR 列显示类似 48%/52% CPU/GPU 的值,表示模型的一部分位于 CPU 上。此时,随着并发数增加,吞吐量会下降,而不是上升。超过并行槽数量后,请求会排队,队列上限由 OLLAMA_MAX_QUEUE 控制,默认值为 512;超过该上限后,服务器会返回 503。
vLLM 使用连续批处理,因此会在槽位释放后,将新请求加入正在运行的批次。其曲线会持续上升,直到某项资源耗尽,通常是 KV cache;不过,max_num_seqs 等配置上限或 CPU 调度开销也可能使曲线提前趋于平缓。Ollama 针对单个模型、单台机器和较低的部署成本进行了优化。因此,这两个引擎对同一组测试会给出不同结果,这正是Ollama 与 vLLM 作为服务引擎的对比的核心内容。记录每个数值对应的引擎和版本。
五种测量错误指标的方式
- 客户端距离服务器太远。 通过互联网从笔记本电脑进行基准测试时,每次 TTFT 都会包含你本地网络的往返时间,因此测到的是家庭网络连接。应在与服务器相同的区域运行客户端。
- 模型处于冷启动状态。 第一个请求需要加载权重;在 vLLM 中,还可能需要执行图捕获。先发送一批预热请求,然后丢弃结果。
- 前缀缓存替你返回了结果。 vLLM 默认启用自动前缀缓存,因此反复发送相同提示词时,测量的是缓存,而不是 prefill;TTFT 会降到真实值的一小部分。
--dataset-name random可避免此问题,因为每个提示词都不同。要确保这一点,请使用--no-enable-prefix-caching启动服务器。 - 输出过短。 如果答案只有 32 个 token,每个请求的 TTFT 占比会很高,因此你测得的每秒 token 数实际上主要反映 prefill。请将
--ignore-eos与符合实际的输出长度结合使用。 - 只报告并发数为 1 的结果。 这会得到表面上最理想的 TTFT;只有在实际请求从不重叠时,它才是正确的成本输入。如果请求会重叠,成本取决于实际流量产生的并发数下能够维持的吞吐量,因此也应在该并发水平进行测量。
将测得的数值转化为决策
取并发扫描结果中、与实际流量达到的并发数相对应的输出吞吐量,并将其与每 token 价格进行比较。只有当有足够多的请求重叠、能够持续填满批次时,这才是饱和吞吐量;一次只发送一个请求的工作负载必须使用单流速率。将每个输出 token 随附的输入 token 计入价格后,盈亏平衡点只需进行一次除法即可得到:
cost_per_million_output = price_out + price_in * (input_tokens / output_tokens)
break_even_tok_s = (price_per_hour / cost_per_million_output) * 1000000 / 3600以 DigitalOcean 2026 年 7 月的价格为例。其 H200 专用推理端点的价格为每小时 $4.47,serverless Llama 3.3 70B 的输入和输出价格均为每百万 token $0.65。按照上文使用的 1:1 比例,每百万个输出 token 会伴随一百万个输入 token,因此有效价格为每百万个输出 token $1.30。4.47 除以 1.30 约等于每小时 3.44 百万个输出 token;再除以 3,600 秒,约为每秒 955 个输出 token,若将输入和输出合计计算,则约为每秒 1,910 个 token。这些价格来自 DigitalOcean,除法计算则是我们完成的。我们在 2026 年 10 月 25 日核查时,H200 端点的价格仍为每小时 $4.47;但 DigitalOcean 已于 2026 年 8 月 18 日弃用 serverless Llama 3.3 70B,并引导用户改用 Llama 4 Maverick。后者的价格为每百万个输入 token $0.25、每百万个输出 token $0.87。这是不同的模型,因此必须先对其单独进行并发扫描,比较才有意义。
决定结果的关键词是持续。在饱和状态下达到每秒 955 个输出 token,但每天只运行 2 小时,并不等于持续达到每秒 955 个输出 token,因为其余 22 小时也需要付费。将显卡租用为 GPU Droplet 也不会降低门槛:截至 2026 年 10 月 25 日,DigitalOcean 的 Droplet 价格中,H200 按需价格为每小时 $4.47,与端点相同;其价格最低的 NVIDIA Droplet——每小时 $0.76 的 RTX 4000,以及每小时 $1.57 的 L40S 或 RTX 6000——容量都不足以使用 FP8 承载 70B 模型。因此,按上述数据计算,一台在饱和状态下达到每秒 2,036 个输出 token 的 H200,必须在每个付费小时内平均达到其峰值的约 47%,才能与输入和输出均为每百万 token $0.65 的价格持平;低于这一利用率时,按 token 计费的价格更有优势。通常导致自托管失败的不是 token 速度低,而是 GPU 空闲小时。
因此,决策需要两个输入。并发扫描给出吞吐上限。流量模式给出实际获得的上限比例。将二者相乘,然后转到GPU VPS 与按 token 计费 API 的盈亏平衡点,即可根据你的用量读出答案。
FAQ
自托管 LLM 的每秒令牌数达到多少才算不错?
这个问题有两个答案,因为该指标有两种用途。对于阅读输出的用户,单个流每秒输出令牌数达到约 20 已经超过阅读速度,因此更高的速度没有实际帮助。对于成本,真正重要的是在实际负载下能够持续达到的总输出吞吐量;只要能覆盖盈亏平衡点,就可以称为合格。按输入和输出均为每百万令牌 $0.65、服务器成本为每小时 $4.47,以及 2026 年 7 月的价格计算,在输入输出比例为 1:1 时,持续输出吞吐量的盈亏平衡点约为每秒 955 个令牌。单个大型模型流通常无法达到这个水平,这正是需要批处理的原因。
为什么我增加并发请求后,Ollama 的吞吐量仍然不变?
OLLAMA_NUM_PARALLEL 的默认值为 1,因此服务器会让每个模型一次只处理一个请求,其余请求进入队列;队列最多容纳 OLLAMA_MAX_QUEUE 个请求(默认为 512),超过后返回 503。总输出量保持不变,而 p99 TTFT 持续上升,这表明瓶颈在队列,而不是 GPU 已经繁忙。请在 systemd drop-in 中设置该变量并重启,然后检查 ollama ps,因为每个并行槽位都会增加分配的上下文大小,并可能导致部分模型卸载到 CPU。
我应该测量首个令牌延迟,还是每秒令牌数?
两者都应测量,因为随着负载增加,它们的变化方向相反。TTFT 反映用户的实际等待时间,而达到饱和时的输出吞吐量会直接影响账单。在每个并发级别记录 p50 和 p99 TTFT,然后选择 p99 TTFT 仍能接受的最高并发级别。将该级别的吞吐量报告为容量,而不是使用曲线顶部的最大值。
每秒令牌数越高,单个令牌的成本就一定越低吗?
不一定。单个令牌的成本等于每小时价格除以服务器在该小时实际生成的令牌数,因此,服务器速度很快但一天中大部分时间处于空闲状态时,单个令牌的成本仍然很高。决定成本的是利用率,而不是峰值速度。还要注意单位:报告的总令牌吞吐量包含输入令牌,因此在输入输出比例为 1:1 时,它接近输出速率的两倍。如果对比的 API 同时对输入令牌和输出令牌收费,请先分别计算两部分的价格,再进行比较。