如何測量首Token時間(TTFT)
本文介紹瞭如何測量AI系統中的首Token時間(TTFT),解釋了其與傳統HTTP響應時間的本質區別,並提供了使用Python、Node.js和Apache JMeter進行LLM工作負載測量的代碼示例。
在AI系統中,首Token時間(TTFT)是衡量用户感知響應速度的關鍵指標。與傳統的HTTP響應時間不同,TTFT測量的是從發送提示請求到接收響應流中第一個token之間的時間差。對於流式LLM API而言,這種區別至關重要,因為用户感知的響應速度取決於輸出何時開始,而非何時結束。
傳統的Web API性能指標,如響應時間、吞吐量和錯誤率,在應用於LLM時完全失效。當您調用OpenAI、Anthropic或本地託管的Ollama端點時,它看起來和感覺上就像調用任何其他REST API。但那個響應時間在欺騙您。LLM並非一次性計算整個響應,而是逐個生成token並流式傳輸。傳統性能工具記錄的是最後一個token到達的時間,而非第一個。用户盯着空白屏幕四秒後才看到內容,即使總耗時只有5秒,體驗也很糟糕。
為了全面理解LLM性能,需要跟蹤多個指標:TTFT(感知響應速度)、TTLT(總完成時間)、Token吞吐量(生成速度)、Token間延遲(流式平滑度)、Goodput(實際系統容量)和抖動(併發負載下的一致性)。TTFT是首要指標,但並非孤立存在。
LLM API通過服務器發送事件(SSE)或分塊HTTP傳輸編碼交付token。服務器在生成時刷新每個token,而不是緩衝完整響應。例如,OpenAI API的原始SSE流如下:
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{"content":"The"}}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{"content":" capital"}}]}
data: [DONE]
每個data:行是一個離散塊。第一條包含實際內容的data:行的時間戳就是您的TTFT測量點。
以下是使用Python和httpx庫(針對Anthropic Messages API)的準確TTFT測量示例:
import httpx
import time
import json
def measure_ttft(prompt: str, api_key: str) -> dict:
url = "https://api.anthropic.com/v1/messages"
headers = {
"x-api-key": api_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
payload = {
"model": "claude-sonnet-4-20250514",
"max_tokens": 512,
"stream": True,
"messages": [{"role": "user", "content": prompt}],
}
ttft = None
ttlt = None
token_count = 0
request_start = time.perf_counter()
with httpx.Client(timeout=60) as client:
with client.stream("POST", url, headers=headers, json=payload) as response:
for line in response.iter_lines():
if not line.startswith("data:"):
continue
raw = line[len("data:"):].strip()
if raw == "[DONE]":
break
try:
chunk = json.loads(raw)
except json.JSONDecodeError:
continue
event_type = chunk.get("type", "")
if event_type == "content_block_delta":
now = time.perf_counter()
if ttft is None:
ttft = now - request_start
token_count += 1
ttlt = now - request_start
return {
"ttft_ms": round(ttft * 1000, 2) if ttft else None,
"ttlt_ms": round(ttlt * 1000, 2) if ttlt else None,
"token_count": token_count,
"throughput_tokens_per_sec": round(token_count / ttlt, 2) if ttlt else None,
}關鍵點是使用time.perf_counter()進行高精度計時。time.time()不適合亞秒級精度。
在Node.js中,使用Anthropic SDK的流式接口:
import Anthropic from "@anthropic-ai/sdk";
interface LLMMetrics {
ttft_ms: number | null;
ttlt_ms: number | null;
token_count: number;
throughput_tokens_per_sec: number | null;
}
async function measureTTFT(prompt: string): Promise<LLMMetrics> {
const client = new Anthropic();
let ttft: number | null = null;
let ttlt: number | null = null;
let tokenCount = 0;
const requestStart = performance.now();
const stream = client.messages.stream({
model: "claude-sonnet-4-20250514",
max_tokens: 512,
messages: [{ role: "user", content: prompt }],
});
for await (const chunk of stream) {
if (chunk.type === "content_block_delta") {
const now = performance.now();
if (ttft === null) {
ttft = now - requestStart;
}
tokenCount++;
ttlt = now - requestStart;
}
}
return {
ttft_ms: ttft !== null ? Math.round(ttft * 100) / 100 : null,
ttlt_ms: ttlt !== null ? Math.round(ttlt * 100) / 100 : null,
token_count: tokenCount,
throughput_tokens_per_sec: ttlt && tokenCount ? Math.round((tokenCount / (ttlt / 1000)) * 100) / 100 : null,
};
}使用performance.now()而非Date.now()以獲得亞毫秒級精度。
關於TTFT的基準:低於200毫秒為優秀,200-500毫秒為良好,500-1秒為一般,1-3秒為較差,超過3秒對於交互式用例不可接受。在併發負載下,這些數字會顯著上升。因此,建議設置p95 TTFT目標,而非平均值。
常見陷阱包括:測量HTTP響應時間而非TTFT、未啓用流式模式、使用低分辨率時間函數、僅測試單用户併發、忽略提示長度作為變量、混淆模型延遲與網絡延遲。