拓展阅读
并行Trace:墙钟与操作总量不同
在人工构造的叶子Span上计算墙钟时间、操作时长总和及峰值并发。相邻区间按左闭右开处理。
沿着对象与状态理解本章
性能图先用实际起止时间画,而不是将所有工具耗时直接相加。下图中缩短非关键路径的A不会缩短总时间;同样,降低单次Token价也未必降低成功任务成本。
下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。
动态图解 · F38
Trace要告诉你因果,不只是时间戳
为何优化一个最显眼的步骤,整体却没变快?
执行轨迹像工程里的时间账,但更重要的是依赖关系。串行阶段会相加,并行阶段往往取关键路径;只有知道下一步在等谁,才能知道哪里值得优化。还要把排队、推理、工具、验证和重试分开,不能全算成模型慢。
Trace一条任务中各步骤及关联关系的记录。
墙钟时间从任务开始到结束真正经过多久。
关键路径决定完成时间的依赖链。
吞吐单位时间完成多少任务。
输入与适用条件:固定教学秒数,无真实产品耗时数据。第4帧只改变工具A时长,其他条件保持不变。
1 / 5 · 先看一条完整任务
第1步,共5步
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
构造的任务从0到16秒:排队2秒、第一次模型3秒、并行工具A3秒/B8秒、第二次模型2秒、核验1秒。
基准墙钟16秒;工具工作量11秒默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
把优化投入到确实支配成本或完成时间的环节。
怎样实现
按依赖关系画时间线,再用同负载实测对照。
可能的代价与误解
只看平均值会隐藏长尾;把并行跨度相加会错误夸大总耗时。
如何判断是否做对
按时间戳与依赖重算16秒,检查是否把等待和重试遗漏。
怎样观察与验证
默认墙钟9、操作总量12、峰值并发2。不要同时把父Span与子Span重复加总,也不要把合成时间当成实际性能测量。
运行命令
python3 reading-notes/experiments/10_trace_timing.py完整教学代码
"""用合成叶子Span说明并行耗时。没有真实计时或外部遥测。"""
from dataclasses import dataclass,asdict
import math
@dataclass(frozen=True)
class Span:
name: str
start: float
end: float
def __post_init__(self):
if not self.name or not all(math.isfinite(x) for x in [self.start,self.end]) or self.end<self.start:
raise ValueError("Span需有名称、有限时间与非负时长")
def summarize(spans: list[Span]) -> dict:
if not spans:
return {"wall_time":0.0,"sum_leaf_durations":0.0,"peak_concurrency":0}
events=[]
for s in spans:
if s.end>s.start:
events.extend([(s.start,1),(s.end,-1)])
current=peak=0
# 同时发生时先处理结束;区间按[start,end)解释。
for _,delta in sorted(events,key=lambda x:(x[0],x[1])):
current+=delta; peak=max(peak,current)
return {"wall_time":max(s.end for s in spans)-min(s.start for s in spans),
"sum_leaf_durations":sum(s.end-s.start for s in spans),
"peak_concurrency":peak}
def self_test() -> None:
s=[Span('prepare',0,1),Span('searchA',1,5),Span('searchB',1,4),Span('verify',5,9)]
m=summarize(s)
assert m=={"wall_time":9,"sum_leaf_durations":12,"peak_concurrency":2}
assert summarize([])["wall_time"]==0
assert summarize([Span('zero',1,1)])["peak_concurrency"]==0
def run_demo() -> dict:
spans=[Span('准备',0,1),Span('搜索A',1,5),Span('搜索B',1,4),Span('验证',5,9)]
return {"性质":"人工时间线,只统计叶子Span,不混入父Span重复计时",
"时间线":[asdict(s) for s in spans],"统计":summarize(spans),
"说明":"时间单位任意;墙钟9与操作总量12不是同一指标"}
if __name__ == "__main__":
import json
self_test()
print(json.dumps(run_demo(),ensure_ascii=False,indent=2))
运行结果示例
以下结果由随包脚本产生。它们不是LLM或产品性能数据。
{
"性质": "人工时间线,只统计叶子Span,不混入父Span重复计时",
"时间线": [
{
"name": "准备",
"start": 0,
"end": 1
},
{
"name": "搜索A",
"start": 1,
"end": 5
},
{
"name": "搜索B",
"start": 1,
"end": 4
},
{
"name": "验证",
"start": 5,
"end": 9
}
],
"统计": {
"wall_time": 9,
"sum_leaf_durations": 12,
"peak_concurrency": 2
},
"说明": "时间单位任意;墙钟9与操作总量12不是同一指标"
}