尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
05-28 周二 TTFT、ITL、TGS 计算过程以及 LLama2 推理代码调试过程:用 TaoToken 统一 Key 跑通本地推理链路
1. LLama2 推理延迟指标到底在测什么TTFT、ITL、TGS 这三个词如果你在做 LLama2 推理服务基本绕不开。TTFT 是 Time To First Token也就是从请求发出到第一个 token 吐出来的时间ITL 是 Inter-Token Latency指第一个 token 之后每两个相邻 token 之间的平均间隔TGS 是 Tokens per Second每秒能生成多少个 token。这三个指标合在一起基本能描述一个推理服务的响应快不快、流式输出顺不顺、吞吐高不高。适合谁看如果你正在本地跑 LLama2-7b-hf或者用 transformers 的model.generate做推理压测想搞清楚每个指标怎么算、代码埋点埋在哪、跑出来数字对不对那这篇就是给你写的。我试过在单卡环境里反复调这套埋点踩过不少坑比如忘了 warmup 导致 TTFT 虚高、torch.cuda.synchronize()没加导致计时偏小、输出 token 数把输入也算进去等等。先说清楚这三个指标的计算逻辑不然后面代码你看着会懵。TTFT 的测量方式让模型只生成 1 个 tokenmax_new_tokens1记录从调用generate到返回的时间。这个时间包含了输入 prompt 的 prefill 阶段和第一个 token 的 decode。注意第一次调用一定要做 warmup因为 CUDA kernel 首次加载、显存分配都会让第一次特别慢。ITL 的测量方式让模型生成 N 个 token比如 50 个记录总时间减去 TTFT再除以生成的 token 数。这里有个细节分母到底是 N 还是 N-1不同人有不同算法。原始代码里用的是out_tokens_num也就是实际生成的 token 数没有减 1。严格来说第一个 token 的时间已经被 TTFT 覆盖了后面 N-1 个间隔才对应 ITL但工程上为了简单很多实现直接除以 N。你只要前后一致就行。TGS 的测量方式总时间除以生成 token 数单位是 tokens/ms乘以 1000 就是 tokens/s。它和 ITL 是倒数关系ITL 是每 token 多少毫秒TGS 是每毫秒多少 token。这三个指标里TTFT 受输入长度影响最大prompt 越长prefill 越慢TTFT 线性增长。ITL 和 TGS 主要受模型大小、batch size、显存带宽影响输入长度对它们影响相对小。所以你在压测时要按不同输入长度分组统计不能混在一起平均。还有一个容易忽略的点do_sampleFalse时temperature和top_p参数其实不生效transformers 会给你一堆 warning。这些 warning 不影响结果但会刷屏建议在代码里显式不传这两个参数或者用warnings.filterwarnings压掉。理解了指标定义接下来就是怎么把 TaoToken 的统一 Key 接进来让本地推理链路和 API 调用用同一套凭证管理。这样你在调试本地 LLama2 的同时也能随时对比线上模型的延迟表现。2. TaoToken 统一 Key 的前置准备与 Base URL 配置在开始写埋点代码之前先把 TaoToken 的接入配置搞定。为什么要用 TaoToken因为你在本地调试 LLama2 推理时经常需要对比不同模型、不同参数下的延迟表现如果每个模型都单独配一套 Key 和地址管理起来很乱。TaoToken 提供统一的 API Key 和 Base URL你可以在同一个环境里切换模型不用改代码结构。先拿 Key。打开 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台里创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 进去之后点「API Keys」新建一个复制出来。这个 Key 就是你后面所有请求的凭证。拿到 Key 之后Base URL 统一用 https://taotoken.net/api 注意这个地址不加 UTM 参数直接写就行。模型 ID 根据你要调的模型填比如gpt-4o、claude-3-5-sonnet这些。如果你要调 Claude 系列文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有详细的模型列表和参数说明。配置方式有两种看你习惯哪种。第一种是环境变量适合在终端里跑脚本export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api第二种是写进配置文件适合在 IDE 里调试。如果你用 VS Code 的 launch.json可以这样写{ version: 0.2.0, configurations: [ { name: LLaMa2 推理, type: debugpy, request: launch, program: /workspace/infer/test_dql_fp16.py, console: integratedTerminal, cwd: /workspace/infer/, args: [], env: { TAOTOKEN_API_KEY: sk-你的key, TAOTOKEN_BASE_URL: https://taotoken.net/api } } ] }这样你在调试本地 LLama2 推理代码时环境变量已经就绪后面如果要加一个 API 对比测试直接读这两个变量就行。如果你用 Cline 或者 Claude Code 这类工具配置方式类似。Cline 的 MCP 配置里Base URL 填https://taotoken.net/apiAPI Key 填你创建的那个Model ID 填你要用的模型。Claude Code 的话在 settings 里配ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY具体可以参考 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里的接入说明。这里有个坑要注意Base URL 后面不要多加/v1或者/chat/completionsTaoToken 的 SDK 会自动拼接路径。你如果手动加了会变成https://taotoken.net/api/v1/v1/chat/completions直接 404。配置好之后你可以先用一个最简单的 curl 验证一下 Key 能不能用curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-4o, messages: [{role: user, content: hello}], max_tokens: 10 }如果返回正常说明 Key 和 Base URL 都没问题。接下来就可以把精力放在 LLama2 本地的埋点代码上了。3. 可复制的计时埋点片段与 launch.json 配置这一节直接给你能跑的代码。我把原始代码里的埋点逻辑整理了一遍补上了 warmup、torch.cuda.synchronize()、以及 TTFT/ITL/TGS 三个指标的完整计算。你可以直接复制到你的test_dql_fp16.py里。先看完整的埋点片段import os import time import pickle import torch import transformers from transformers import AutoTokenizer, AutoModel from datetime import datetime data_dir /workspace/models/ model_name Llama-2-7b-hf model_path data_dir model_name def measure_latency(model, tokenizer, query, max_new_tokens50, warmupTrue): inputs tokenizer(query, return_tensorspt).to(cuda) input_len inputs.input_ids.shape[1] # warmup第一次调用不计入统计 if warmup: with torch.no_grad(): _ model.generate( inputs.input_ids, max_new_tokens1, max_lengthNone, do_sampleFalse ) torch.cuda.synchronize() # TTFT只生成 1 个 token ttft_list [] for _ in range(3): torch.cuda.synchronize() start time.time() with torch.no_grad(): _ model.generate( inputs.input_ids, max_new_tokens1, max_lengthNone, do_sampleFalse ) torch.cuda.synchronize() end time.time() ttft_list.append((end - start) * 1000) ttft sum(ttft_list) / len(ttft_list) # ITL / TGS生成 max_new_tokens 个 token itl_list [] tgs_list [] out_tokens_num 0 for _ in range(3): torch.cuda.synchronize() start time.time() with torch.no_grad(): generate_ids model.generate( inputs.input_ids, max_new_tokensmax_new_tokens, max_lengthNone, do_sampleFalse ) torch.cuda.synchronize() end time.time() outputs generate_ids.tolist()[0][input_len:] out_tokens_num len(outputs) total_ms (end - start) * 1000 itl (total_ms - ttft) / out_tokens_num tgs out_tokens_num / (total_ms / 1000) itl_list.append(itl) tgs_list.append(tgs) itl sum(itl_list) / len(itl_list) tgs sum(tgs_list) / len(tgs_list) return { input_len: input_len, out_tokens_num: out_tokens_num, ttft_ms: round(ttft, 2), itl_ms: round(itl, 2), tgs: round(tgs, 2) } def main(): tokenizer AutoTokenizer.from_pretrained( model_path, trust_remote_codeTrue, device_mapauto ) model AutoModelForCausalLM.from_pretrained( model_path, trust_remote_codeTrue, device_mapauto ) model.eval() current_time datetime.now().strftime(%Y%m%d%H%M%S) res_file fresult_{model_name}_fp16_{current_time}.txt with open(input_request_list, rb) as f: input_request_list pickle.load(f) with open(res_file, w) as f_result: for input_request in input_request_list: query, prompt_len, output_len input_request print(ftesting len: {prompt_len}...) result measure_latency(model, tokenizer, query) print(result) f_result.write( fIn len:{result[input_len]} fOut len:{result[out_tokens_num]} fTTFT:{result[ttft_ms]} fITL:{result[itl_ms]} fTGS:{result[tgs]}\n ) f_result.flush() if __name__ __main__: main()这段代码和原始版本比有几个关键改动。第一加了torch.cuda.synchronize()因为 CUDA 是异步执行不加的话time.time()测到的是 kernel 入队时间不是实际完成时间结果会偏小很多。第二warmup 单独抽出来不计入统计。第三TTFT 和 ITL 各跑 3 次取平均减少抖动。第四TGS 直接按 tokens/s 算不用再换算。launch.json 配置如下路径和原始代码保持一致{ version: 0.2.0, configurations: [ { name: LLaMa2 推理, type: debugpy, request: launch, program: /workspace/infer/test_dql_fp16.py, console: integratedTerminal, cwd: /workspace/infer/, args: [], env: { TAOTOKEN_API_KEY: sk-你的key, TAOTOKEN_BASE_URL: https://taotoken.net/api, TF_ENABLE_ONEDNN_OPTS: 0 } } ] }TF_ENABLE_ONEDNN_OPTS0是为了关掉 TensorFlow 的 oneDNN 优化提示那个 warning 在日志里很烦关掉之后输出干净很多。如果你要在同一份代码里加一个 TaoToken API 的对比测试可以这样写import os import time import requests def measure_api_latency(query, modelgpt-4o, max_tokens50): api_key os.environ[TAOTOKEN_API_KEY] base_url os.environ[TAOTOKEN_BASE_URL] url f{base_url}/v1/chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json } payload { model: model, messages: [{role: user, content: query}], max_tokens: max_tokens, stream: False } start time.time() resp requests.post(url, headersheaders, jsonpayload, timeout60) end time.time() data resp.json() total_ms (end - start) * 1000 out_tokens data[usage][completion_tokens] ttft_approx total_ms * 0.3 # 非流式只能估算 itl (total_ms - ttft_approx) / out_tokens tgs out_tokens / (total_ms / 1000) return { total_ms: round(total_ms, 2), out_tokens: out_tokens, itl_ms: round(itl, 2), tgs: round(tgs, 2) }注意非流式请求拿不到精确的 TTFT只能估算。要精确测 TTFT得用streamTrue然后记录第一个 chunk 到达的时间。这个后面验证环节会讲。配置和代码都齐了接下来跑一次完整请求看看指标对不对。4. 验证请求与成功结果解读跑之前先确认环境。进入你的 conda 环境确保 torch 和 transformers 版本匹配。原始日志里用的是python38_torch201_cuda你可以用conda activate python38_torch201_cuda激活。然后确认input_request_list文件存在格式是 pickle 序列化的列表每个元素是(query, prompt_len, output_len)三元组。如果你没有这个文件可以自己造一个import pickle requests [ (I have an interview about product speccing with the company Weekend Health. Give me an example of a question they might ask with regards about a new feature, 32, 39), (In Java, I want to replace string like \This is a new {object} at {place}\ with a Map, {object: \student\, \point 3, 4\}, and get a result \This is a new student at point 3, 4\. How can I do?, 64, 494), (MK runs out of the palace and opens the gates, allowing Macaque and the freedom fighters to come in and begin taking out Wukongs guards., 128, 701), ] with open(input_request_list, wb) as f: pickle.dump(requests, f)然后按 F5 启动调试或者直接命令行跑cd /workspace/infer/ python test_dql_fp16.py正常输出应该类似这样testing len: 32... {input_len: 32, out_tokens_num: 50, ttft_ms: 77.99, itl_ms: 65.19, tgs: 15.34} testing len: 64... {input_len: 64, out_tokens_num: 50, ttft_ms: 83.05, itl_ms: 65.98, tgs: 15.16} testing len: 128... {input_len: 128, out_tokens_num: 50, ttft_ms: 102.17, itl_ms: 65.86, tgs: 15.18} testing len: 256... {input_len: 256, out_tokens_num: 50, ttft_ms: 129.98, itl_ms: 65.37, tgs: 15.30} testing len: 512... {input_len: 512, out_tokens_num: 50, ttft_ms: 282.71, itl_ms: 66.42, tgs: 15.05} testing len: 1024... {input_len: 1024, out_tokens_num: 50, ttft_ms: 532.08, itl_ms: 66.78, tgs: 14.97} testing len: 2048... {input_len: 2048, out_tokens_num: 50, ttft_ms: 1189.91, itl_ms: 68.40, tgs: 14.62}从这组数据能看出几个规律。TTFT 随输入长度增长很明显32 token 时 78ms2048 token 时 1190ms差不多是线性关系。ITL 基本稳定在 65-68ms 之间说明 decode 阶段每个 token 的计算量跟输入长度关系不大。TGS 在 15 tokens/s 左右换算成 ITL 就是 1000/15 ≈ 66.7ms和实测吻合。如果你要对比 TaoToken API 的延迟可以用流式请求测 TTFTimport os import time import requests import json def measure_stream_ttft(query, modelgpt-4o, max_tokens50): api_key os.environ[TAOTOKEN_API_KEY] base_url os.environ[TAOTOKEN_BASE_URL] url f{base_url}/v1/chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json } payload { model: model, messages: [{role: user, content: query}], max_tokens: max_tokens, stream: True } start time.time() first_token_time None token_count 0 with requests.post(url, headersheaders, jsonpayload, streamTrue, timeout60) as resp: for line in resp.iter_lines(): if not line: continue line line.decode(utf-8) if line.startswith(data: ): data_str line[6:] if data_str [DONE]: break try: chunk json.loads(data_str) delta chunk[choices][0].get(delta, {}) if delta.get(content): if first_token_time is None: first_token_time time.time() token_count 1 except json.JSONDecodeError: continue end time.time() ttft (first_token_time - start) * 1000 if first_token_time else None total_ms (end - start) * 1000 itl (total_ms - ttft) / (token_count - 1) if token_count 1 else None tgs token_count / (total_ms / 1000) return { ttft_ms: round(ttft, 2) if ttft else None, itl_ms: round(itl, 2) if itl else None, tgs: round(tgs, 2), token_count: token_count }跑一下result measure_stream_ttft(用一句话解释什么是 Transformer) print(result)输出类似{ttft_ms: 320.45, itl_ms: 18.32, tgs: 54.6, token_count: 48}可以看到 API 的 TTFT 比本地 LLama2-7b 高一些因为网络往返有开销但 ITL 和 TGS 明显更好因为服务端用的是更大的集群和优化过的推理引擎。这个对比数据对你做容量规划很有参考价值。验证通过之后接下来看看常见报错怎么排查。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth调试过程中最容易卡住的几个报错我按出现频率排一下。401 Unauthorized。这个最常见原因就三种Key 没填、Key 填错、Key 过期。先检查环境变量echo $TAOTOKEN_API_KEY如果输出为空说明没 export 成功。如果你是在 launch.json 里配的检查 JSON 格式有没有写错特别是逗号和引号。还有一种情况是 Key 复制的时候带了空格用echo $TAOTOKEN_API_KEY | xxd | head看一下有没有多余字符。local proxy failed。这个报错通常出现在你本地配了代理但代理没启动或者端口不对。检查你的http_proxy和https_proxy环境变量env | grep -i proxy如果有输出但代理服务没跑就会报这个错。解决办法是 unset 掉unset http_proxy unset https_proxy然后重新跑请求。如果你确实需要走代理确保代理地址和端口正确并且代理服务在运行。reading choices 相关报错。这个一般出现在解析 API 响应的时候比如KeyError: choices或者IndexError: list index out of range。原因是响应结构和你预期的不一样。先打印完整响应看看resp requests.post(url, headersheaders, jsonpayload) print(resp.status_code) print(resp.text)常见情况是返回了错误信息比如{error: {message: model not found}}这时候resp.json()[choices]就会 KeyError。检查 model ID 是否拼写正确TaoToken 支持的模型列表在文档里有。OAuth 相关报错。如果你用 Claude Code 或者某些 CLI 工具可能会遇到 OAuth token 过期的问题。这类工具通常有自己的认证流程你需要重新登录或者刷新 token。具体操作看工具的文档TaoToken 这边只要 Base URL 和 API Key 配对不涉及 OAuth。CUDA out of memory。跑 LLama2-7b 的时候如果显存不够会报这个。解决办法是减小 batch size或者用device_mapauto让 transformers 自动分片。如果还是不够可以试load_in_8bitTrue量化加载model AutoModelForCausalLM.from_pretrained( model_path, trust_remote_codeTrue, device_mapauto, load_in_8bitTrue )do_sampleFalse但 temperature 和 top_p 的 warning 刷屏。这个不影响结果但很烦。在代码开头加import warnings warnings.filterwarnings(ignore, categoryUserWarning)或者显式不传这两个参数。TTFT 测出来特别大比如好几秒。检查是不是没做 warmup。第一次调用要加载 CUDA kernel、分配显存时间会特别长。warmup 一次之后后续调用就正常了。ITL 算出来是负数。这个通常是因为 TTFT 比总时间还大说明你的 TTFT 测量有问题。检查torch.cuda.synchronize()有没有加以及 TTFT 和 ITL 是不是在同一组输入上测的。排查完这些基本就能跑通了。最后说一下怎么把 TaoToken 的 Key 管理融入到日常调试流程里。6. 用 TaoToken 统一 Key 管理本地与线上推理链路本地 LLama2 推理调试和线上 API 调用本质上是在做同一件事给定输入测量输出延迟。区别在于本地你能控制所有变量线上你只能控制请求参数。TaoToken 的价值在于它让你用同一套 Key 和 Base URL 管理所有线上模型不用为每个模型单独配凭证。具体怎么用我建议你在项目里建一个config.py把 TaoToken 的配置集中管理import os TAOTOKEN_API_KEY os.environ.get(TAOTOKEN_API_KEY, ) TAOTOKEN_BASE_URL os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) MODELS { gpt-4o: gpt-4o, claude: claude-3-5-sonnet-20241022, llama: meta-llama/Llama-2-7b-chat-hf } def get_headers(): return { Authorization: fBearer {TAOTOKEN_API_KEY}, Content-Type: application/json }然后你的本地推理脚本和 API 对比脚本都 import 这个 configKey 只在一个地方维护。换 Key 的时候只改环境变量不用动代码。如果你用 Coding Plan 做长期编码任务比如让 AI 帮你写推理压测脚本、分析延迟数据可以在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 看看套餐。Coding Plan 适合需要频繁调用模型做代码生成、调试、重构的场景按量计费比单独买 token 划算。日常调试流程我建议这样安排本地 LLama2 跑基准测试拿到 TTFT/ITL/TGS 的 baseline然后用 TaoToken API 跑同样的输入对比线上模型的延迟如果线上模型在某个指标上明显更好再决定要不要把部分请求切到线上。这样你既有本地的可控性又有线上的弹性。最后提醒一点测延迟的时候一定要控制变量。同一组输入、同样的 max_tokens、同样的 warmup 次数这样对比才有意义。如果你今天测本地用 50 token明天测线上用 100 token那 ITL 和 TGS 没法直接比。代码和配置都在上面了你可以直接复制到自己的环境里跑。遇到报错先看第 5 节的排查清单大部分问题都能定位。
RELATED

相关推荐

Java图片拷贝实战:从字节流到NIO零拷贝的进阶指南

Java图片拷贝实战:从字节流到NIO零拷贝的进阶指南

前阵子接了一个跟图片存储有关的活,用户上传的图片要先落到临时目录,再由后端拷贝到正式存储目录。需求很简单,但真正写代码的时候才发现,Java文件输入输出这一块的讲究比想象中多得多。一开始我用了一次性读入字节再整体写出的方…

📅 2026/10/12 10:08:13
仿采精灵:采摘机器人从实验台到温室的技术复盘

仿采精灵:采摘机器人从实验台到温室的技术复盘

仿采精灵这个代号,是我们琏雾团队给一台果园采摘实验样机起的名字。四个月下来,如果只让我保留一条感想,我会说:模仿人摘果子的动作,远比给机械臂写一版漂亮的运动规划更难。这篇文章不打算讲什么宏大成果,…

📅 2026/10/12 10:08:13
LwIP UDP Socket编程实战:从创建绑定到收发调试的完整Demo

LwIP UDP Socket编程实战:从创建绑定到收发调试的完整Demo

1. 从一个真实调试场景说起:为什么UDP收发值得单独写一个Demo很多做嵌入式网络开发的朋友,第一次接触LwIP协议栈时,往往是从TCP开始入手的——毕竟TCP有连接、有重传、有确认,看起来"更可靠"。但实际项目里,…

📅 2026/10/12 10:08:13
MORE NEWS

更多资讯

📰

Django购物商城开发全解析:从ORM建模到订单支付与答辩加分项

1. 项目整体设计与技术选型:为什么用 Django 做购物商城 做电商类毕设,很多同学第一个纠结的问题就是:语言和框架到底选什么。我给出的建议很直接——如果你没有特别强烈的偏好,Django 是综合性价比最高的选择。这个结论不是拍脑袋…

📰

Django蛋糕商城毕设实战:需求拆解、购物车与订单实现要点

这标题一看就是最常见的毕业设计款式——基于Django的蛋糕购物商城网站的设计与实现。它听起来简单,但实际上是Django全栈项目里相当典型的一套组合:要做用户体系、商品展示、购物车、订单流转,还要有一个后台管理界面来维护商品库存和订单状…

📰

PCA图像压缩实战:从原理到工业级落地

1. 图像压缩这件事,为什么非得用PCA不可?我第一次在某高校图像处理课上看到PCA压缩效果时,心里是存疑的:一张512512的灰度图,原始大小262KB,用PCA降到前30个主成分重建后,视觉几乎无损&#xff…

📰

Intel X710网卡数据手册:驱动调试、寄存器定位与硬件丢包根因分析指南

简介:本资源是Intel官方发布的以太网控制器X710/XXV710/XL710数据手册(Datasheet)PDF文档,面向网络硬件工程师、驱动开发人员、云计算与数据中心系统架构师等技术人群,用于深入理解该系列高性能网卡的电气特性、寄存器…

📰

CycleGAN Keras实现:无配对图像翻译原理与工程落地

简介:本资源是CycleGAN在Keras框架下的完整无监督图像转换实现,面向深度学习初学者与计算机视觉实践者,解决无配对样本条件下的跨域图像生成问题,如风格迁移、季节变换、马→斑马等典型任务。压缩包共7个文件,含4个核心…

📰

Vue3路由基础:Vue Router 4核心链路全解析

学 Vue3 逃不开路由这一关。我在带项目的时候有个很深的感触:组件的语法可以边写边查,响应式和状态管理也有明确的套路,但路由是整个应用的地基——地基没打好,后面做动态菜单、页面权限、多级嵌套布局时,每一步都在还…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬