拓展阅读

并行Trace:墙钟与操作总量不同

在人工构造的叶子Span上计算墙钟时间、操作时长总和及峰值并发。相邻区间按左闭右开处理。

状态:自检已实际通过 · 环境:Python标准库 · 性质:合成/模拟教学实验
对应讲义 14.3 · 保存Python脚本

沿着对象与状态理解本章

性能图先用实际起止时间画,而不是将所有工具耗时直接相加。下图中缩短非关键路径的A不会缩短总时间;同样,降低单次Token价也未必降低成功任务成本。

下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。

动态图解 · F38

Trace要告诉你因果,不只是时间戳

为何优化一个最显眼的步骤,整体却没变快?

执行轨迹像工程里的时间账,但更重要的是依赖关系。串行阶段会相加,并行阶段往往取关键路径;只有知道下一步在等谁,才能知道哪里值得优化。还要把排队、推理、工具、验证和重试分开,不能全算成模型慢。

Trace一条任务中各步骤及关联关系的记录。
墙钟时间从任务开始到结束真正经过多久。
关键路径决定完成时间的依赖链。
吞吐单位时间完成多少任务。

输入与适用条件:固定教学秒数,无真实产品耗时数据。第4帧只改变工具A时长,其他条件保持不变。

1 / 5 · 先看一条完整任务

第1步,共5步

窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。

Trace要告诉你因果,不只是时间戳:先看一条完整任务排队2s模型调用13s并行工具A3s并行工具B8s模型调用22s核验1s016秒

构造的任务从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不是同一指标"
}

输入关键词即可检索。

↑ ↓ 选择 Enter 打开Esc 关闭 · 仅本地搜索

阅读设置

阅读设置仅保存在本机浏览器,不上传任何数据。