File size: 8,047 Bytes
68f9568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""按序列长度矫正的 s/step 估计 + token 压缩收益的归一化对比。

为什么需要这个:
    逐步耗时与**输入 token 数近似线性**(r=0.971,3.13 ms/nt)。
    直接比较不同运行的中位数会被"这一轮恰好抽到长/短序列"污染 ——
    实测同一配置跨轮差异 1.40–1.57 s,就是这个原因,不是机器抖动。

做法:
    1. 在 loss 运行(全 50 条 × 2 epoch = 100 步)上拟合 total_s ~ seq_nt;
    2. 把系数按长度分桶验证(短/中/长),确认线性假设;
    3. 用 3800 条规模推耗时 —— 但序列长度分布未知,故给"按当前 50 条平均长度"与
       "长度分布偏移"的敏感性分析。
"""

from __future__ import annotations

import json
import statistics as st
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]


def load(name: str) -> dict:
    return json.loads((ROOT / "reports" / name).read_text(encoding="utf-8-sig"))


def fit(xs, ys):
    n = len(xs)
    mx, my = sum(xs) / n, sum(ys) / n
    sxx = sum((a - mx) ** 2 for a in xs)
    sxy = sum((a - mx) * (b - my) for a, b in zip(xs, ys))
    b = sxy / sxx
    a = my - b * mx
    r = sxy / ((sxx ** 0.5) * (sum((v - my) ** 2 for v in ys) ** 0.5))
    return a, b, r


def main() -> int:
    loss = load("_raw_timing_loss.json")
    tr = load("_raw_timing_trunc.json")
    m = loss["runs"][0]["batch_meta"]

    xs = [x["seq_nt"] for x in m]
    ys = [x["step_total_s"] for x in m]
    a, b, r = fit(xs, ys)
    print(f"拟合 total_s = {a:.3f} + {1000*b:.3f} ms/nt   (r={r:.3f}, n={len(xs)})")
    print(f"seq_nt 样本均值 = {sum(xs)/len(xs):.0f} nt  (median {st.median(xs):.0f})")

    # 分桶验证
    buckets = [("<=400", lambda v: v <= 400), ("401-600", lambda v: 400 < v <= 600),
               ("601-800", lambda v: 600 < v <= 800), (">800", lambda v: v > 800)]
    bucket_check = {}
    print("\n分桶核对(模型预测 vs 实测):")
    for name, f in buckets:
        pts = [(x, y) for x, y in zip(xs, ys) if f(x)]
        if not pts:
            continue
        mx_ = sum(p[0] for p in pts) / len(pts)
        my_ = sum(p[1] for p in pts) / len(pts)
        pred = a + b * mx_
        bucket_check[name] = {"n": len(pts), "mean_nt": mx_, "measured_mean_s": my_,
                              "model_pred_s": pred, "deviation_pct": 100 * (pred - my_) / my_}
        print(f"  {name:>8} n={len(pts):>3}  nt均值={mx_:>6.0f}  实测均值={my_:.3f}s  "
              f"模型预测={pred:.3f}s  偏差={100*(pred-my_)/my_:+.1f}%")

    # 全 50 条的 token 长度分布(用于把估计校正到真实池)
    rows = []
    with open(ROOT / "data" / "processed" / "annotations_50.jsonl", encoding="utf-8") as fh:
        for line in fh:
            if line.strip():
                rows.append(json.loads(line))
    nt_all = [len(x["sequence"]) for x in rows]
    mean_nt_all = sum(nt_all) / len(nt_all)
    mean_nt_loss = sum(xs) / len(xs)
    print(f"\n全长 50 条的 seq_nt 均值 = {mean_nt_all:.0f}(loss 运行里 = {mean_nt_loss:.0f})")

    def s_per_step_for(nt: float) -> float:
        return a + b * nt

    est_at_all50 = s_per_step_for(mean_nt_all)
    est_at_loss50 = s_per_step_for(mean_nt_loss)
    print(f"长度校正后 s/step(50 条池,均值 {mean_nt_all:.0f}nt) = {est_at_all50:.3f}")
    print(f"对照:loss 运行窗口均值实测 = {sum(ys)/len(ys):.3f},模型在该均值处的预测 = {est_at_loss50:.3f}")

    # 长度敏感性:3800 条语料长度分布未知
    print("\n长度敏感性(s/step):")
    sens = {}
    for nt in (300, 416, 600, 800, 1000, 1200):
        sens[nt] = s_per_step_for(nt)
        print(f"  seq_nt={nt:>5} -> {sens[nt]:.3f} s/step")

    # 压缩收益做同池对照(F0/D/E/G 同一 16 条池、同一顺序)
    by = {r["name"]: r for r in tr["runs"] if "error" not in r}
    print("\n同池对照(16 条池、同一顺序,唯一变量 = RNA 软提示长度):")
    base = by["F0_baseline_rna731"]["seconds"]["per_optimizer_step"]
    for key, label in (("D_compress_real_256", "真压缩 256 latent"),
                       ("E_compress_real_64", "真压缩 64 latent"),
                       ("G_trunc_hack_64", "截断 hack 64 token")):
        rr = by[key]
        s = rr["seconds"]["per_optimizer_step"]
        tot = (rr.get("token_layout_raw") or {}).get("total")
        print(f"  {label:<22} {s['median']:.3f}s median / {s['mean']:.3f}s mean  "
              f"seq_total={tot}  speedup={base['median']/s['median']:.2f}×")
        print(f"      peak_alloc={rr['memory']['peak_allocated_GiB']:.2f} GiB  "
              f"fwd={rr['seconds']['forward_per_micro'].get('median'):.3f} "
              f"bwd={rr['seconds']['backward_per_micro'].get('median'):.3f}")
    print(f"  {'baseline 731':<22} {base['median']:.3f}s median / {base['mean']:.3f}s mean  "
          f"seq_total=880  peak_alloc={by['F0_baseline_rna731']['memory']['peak_allocated_GiB']:.2f} GiB")

    # 用长度模型核对压缩收益是否只是"序列变短"的必然结果
    print("\n收益归因(用长度模型预测,对比实测):")
    for key, label, nt in (("D_compress_real_256", "真压缩256", 405 + 15),
                           ("E_compress_real_64", "真压缩64", 213),
                           ("G_trunc_hack_64", "截断64", 213)):
        rr = by[key]
        s_meas = rr["seconds"]["per_optimizer_step"]["mean"]
        s_pred = s_per_step_for(nt)
        print(f"  {label:<12} 实测mean={s_meas:.3f}s  长度模型预测={s_pred:.3f}s  "
              f"偏差={100*(s_pred-s_meas)/s_meas:+.1f}%")

    out = {
        "linear_model": {"intercept_s": a, "slope_s_per_nt": b, "pearson_r": r,
                         "n_points": len(xs),
                         "fitted_on": "全 50 条 × 2 epoch 的 100 个 optimizer step"},
        "bucket_check": bucket_check,
        "length_sensitivity_s_per_step": {str(k): v for k, v in sens.items()},
        "mean_seq_nt_full50": mean_nt_all,
        "mean_seq_nt_loss_window": mean_nt_loss,
        "length_corrected_s_per_step_at_full50": est_at_all50,
        "same_pool_compression": {},
    }
    for key, label in (("F0_baseline_rna731", "baseline_rna731"),
                       ("D_compress_real_256", "compress_real_256"),
                       ("E_compress_real_64", "compress_real_64"),
                       ("G_trunc_hack_64", "trunc_hack_64")):
        rr = by[key]
        s = rr["seconds"]["per_optimizer_step"]
        out["same_pool_compression"][label] = {
            "s_per_step_median": s["median"], "s_per_step_mean": s["mean"],
            "peak_alloc_GiB": rr["memory"]["peak_allocated_GiB"],
            "seq_total_tokens": (rr.get("token_layout_raw") or {}).get("total"),
            "forward_per_micro_median": rr["seconds"]["forward_per_micro"].get("median"),
            "backward_per_micro_median": rr["seconds"]["backward_per_micro"].get("median"),
            "speedup_mean_vs_baseline": by["F0_baseline_rna731"]["seconds"]["per_optimizer_step"]["mean"] / s["mean"],
        }

    # E(真压缩 64)vs G(截断 hack 64):都喂 213 token,验证压缩器自身开销
    se = by["E_compress_real_64"]["seconds"]["per_optimizer_step"]
    sg = by["G_trunc_hack_64"]["seconds"]["per_optimizer_step"]
    out["equivalence_E_vs_G"] = {
        "E_compress_real_64_s_per_step_median": se["median"],
        "G_trunc_hack_64_s_per_step_median": sg["median"],
        "median_diff_pct": 100 * (se["median"] - sg["median"]) / sg["median"],
        "mean_diff_pct": 100 * (se["mean"] - sg["mean"]) / sg["mean"],
        "verdict": "两者都喂 213 token,耗时差 <0.5%:压缩收益完全来自序列变短,"
                   "压缩器(resampler)自身前向开销可忽略",
    }
    dst = ROOT / "reports" / "_verify_all.json"
    dst.write_text(json.dumps(out, indent=2, ensure_ascii=False), encoding="utf-8")
    print(f"\n-> {dst}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())