RNAQwen / scripts /_scratch /_length_correct.py
RomanCohort's picture
add scripts/_scratch
68f9568 verified
Raw History Blame Contribute Delete
8.05 kB
"""按序列长度矫正的 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())