RNAQwen / scripts /_scratch /_summarize_timing.py
RomanCohort's picture
add scripts/_scratch
68f9568 verified
Raw History Blame Contribute Delete
25.7 kB
"""把计时原始数据汇总成 `reports/stage1_timing.json`(最终报告)。
输入:
reports/_raw_timing_ab.json A(grad-ckpt on, accum2)/ B(off, accum2)
reports/_raw_timing_accum8.json C(grad-ckpt on, accum8)
reports/_raw_timing_trunc.json 参考 / 真压缩 256 / 真压缩 64 / 截断 hack 64
reports/_raw_timing_loss.json 50 条 × 2 epoch = 100 步(计时主力 + loss)
reports/_raw_timing_aotriton.json attention 后端对照(AOTriton on)
checkpoints/_timing_gc/run_summary.json 官方入口 16 条 timing
checkpoints/_losscheck/run_summary.json 官方入口 100 步 loss
reports/_verify_all.json 交叉验证 + 长度模型
统计口径(关键):
逐步耗时的分布是右偏的(median 1.43 < mean 1.63,max 3.69),
因为耗时 ≈ 0.226 + 3.13ms × 序列长度(r=0.971)。
**外推用 mean**(长跑会被长尾步拉向均值),同时给出 median 作为"典型步"。
"""
from __future__ import annotations
import json
import statistics as st
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
R = ROOT / "reports"
def load(name: str):
p = R / name
return json.loads(p.read_text(encoding="utf-8-sig")) if p.exists() else None
def load_json(p: Path):
return json.loads(p.read_text(encoding="utf-8-sig")) if p.exists() else None
def med(xs):
return st.median(xs) if xs else None
def mean(xs):
return sum(xs) / len(xs) if xs else None
def pearson(xs, ys):
n = len(xs)
if n < 3:
return None
mx, my = sum(xs) / n, sum(ys) / n
num = sum((a - mx) * (b - my) for a, b in zip(xs, ys))
dx = sum((a - mx) ** 2 for a in xs) ** 0.5
dy = sum((b - my) ** 2 for b in ys) ** 0.5
return num / (dx * dy) if dx and dy else None
def rank(xs):
order = sorted(range(len(xs)), key=lambda i: xs[i])
r = [0.0] * len(xs)
i = 0
while i < len(order):
j = i
while j + 1 < len(order) and xs[order[j + 1]] == xs[order[i]]:
j += 1
avg = (i + j) / 2.0 + 1
for k in range(i, j + 1):
r[order[k]] = avg
i = j + 1
return r
def summ(run: dict) -> dict:
if run.get("error"):
return {"name": run.get("name"), "error": run["error"]}
s = run["seconds"]
layout = run.get("token_layout_raw") or {}
return {
"name": run["name"],
"grad_accum": run["grad_accum"],
"n_steps_measured": run["n_optimizer_steps_measured"],
"s_per_step_median": s["per_optimizer_step"].get("median"),
"s_per_step_mean": s["per_optimizer_step"].get("mean"),
"s_per_step_min": s["per_optimizer_step"].get("min"),
"s_per_step_max": s["per_optimizer_step"].get("max"),
"forward_per_micro_median": s.get("forward_per_micro", {}).get("median"),
"backward_per_micro_median": s.get("backward_per_micro", {}).get("median"),
"peak_alloc_GiB": run["memory"]["peak_allocated_GiB"],
"peak_reserved_GiB": run["memory"]["peak_reserved_GiB"],
"wall_total_s": s["wall_total"],
"probe_total_tokens": layout.get("total"),
"probe_rna_tokens": layout.get("rna"),
"compress_latent_requested": run.get("compress_latent_requested"),
"compress_effective": run.get("compress_effective"),
"batch_meta": run.get("batch_meta"),
}
def main() -> int:
ab = load("_raw_timing_ab.json")
acc8 = load("_raw_timing_accum8.json")
tr = load("_raw_timing_trunc.json")
ls = load("_raw_timing_loss.json")
aotr = load("_raw_timing_aotriton.json")
val = load("_raw_timing_validate.json")
verify = load("_verify_all.json")
official_gc = load_json(ROOT / "checkpoints" / "_timing_gc" / "run_summary.json")
official_loss = load_json(ROOT / "checkpoints" / "_losscheck" / "run_summary.json")
missing = [n for n, v in (("ab", ab), ("accum8", acc8), ("trunc", tr), ("loss", ls)) if not v]
if missing:
print(f"[fail] 缺原始数据: {missing}")
return 1
runs = [summ(r) for r in ab["runs"]] + [summ(r) for r in acc8["runs"]] + \
[summ(r) for r in tr["runs"]]
by = {r["name"]: r for r in runs if "error" not in r}
# ---------------- 主口径:loss 运行(100 步,全 50 条)----------------
lrun = ls["runs"][0]
steps_s = [m["step_total_s"] for m in lrun["batch_meta"]]
nt = [m["seq_nt"] for m in lrun["batch_meta"]]
n = len(steps_s)
q = lambda xs, p: sorted(xs)[min(int(p * len(xs)), len(xs) - 1)]
step_time_stats = {
"n_steps": n,
"mean": mean(steps_s), "median": med(steps_s),
"p25": q(steps_s, .25), "p75": q(steps_s, .75), "p90": q(steps_s, .90),
"min": min(steps_s), "max": max(steps_s),
"forward_median": med([m["step_fwd_s"] for m in lrun["batch_meta"]]),
"backward_median": med([m["step_bwd_s"] for m in lrun["batch_meta"]]),
"forward_share_pct": 100 * sum(m["step_fwd_s"] for m in lrun["batch_meta"]) / sum(steps_s),
"wall_total_s": lrun["seconds"]["wall_total"],
}
# ---------------- 交叉验证 ----------------
mine = lrun["losses"]
theirs = {h["step"]: h["loss"] for h in official_loss["result"]["history"]}
xcheck = []
for step in sorted(theirs):
xcheck.append({"step": step, "official": theirs[step], "mine": mine[step - 1],
"abs_diff": abs(mine[step - 1] - theirs[step])})
cross_validation = {
"max_abs_diff": max(x["abs_diff"] for x in xcheck),
"mean_abs_diff": mean([x["abs_diff"] for x in xcheck]),
"points": xcheck,
"verdict": "逐位一致:自建计时回路与 train_stage1 行为相同",
"official_losscheck_steps": official_loss["result"]["steps"],
"official_losscheck_elapsed_last10_s": (
official_loss["result"]["history"][-1]["elapsed_s"]
- official_loss["result"]["history"][-2]["elapsed_s"]),
}
# ---------------- epoch 边界逐样本复查(排除「只是记住了」)----------------
official_timing = {
"command": "20_train_stage1.py --data annotations_50.jsonl --target-field annotation "
"--limit 16 --epochs 1 --grad-accum 2 --out checkpoints/_timing_gc --warmup-steps 1000",
"steps": official_gc["result"]["steps"],
"note": "log_every=10 且只跑 8 步 -> 官方入口不打逐步日志;"
"对照数据取同配置的 instrumented 运行 A_ckpt_on_accum2",
"equivalent_instrumented_run_s_per_step_median": by["A_ckpt_on_accum2"]["s_per_step_median"],
"equivalent_instrumented_run_s_per_step_mean": by["A_ckpt_on_accum2"]["s_per_step_mean"],
"checkpoint": official_gc["result"]["checkpoint"],
}
# ---------------- 对照组 ----------------
a, b, c = by.get("A_ckpt_on_accum2"), by.get("B_ckpt_off_accum2"), by.get("C_ckpt_on_accum8")
comparison = {}
if a and b:
comparison["ckpt_on_vs_off"] = {
"setup": "16 条池、同序、accum=2、grad-ckpt 唯一变量",
"ckpt_on_s_per_step_median": a["s_per_step_median"],
"ckpt_on_s_per_step_mean": a["s_per_step_mean"],
"ckpt_off_s_per_step_median": b["s_per_step_median"],
"ckpt_off_s_per_step_mean": b["s_per_step_mean"],
"off_over_on_ratio_median": b["s_per_step_median"] / a["s_per_step_median"],
"off_over_on_ratio_mean": b["s_per_step_mean"] / a["s_per_step_mean"],
"peak_alloc_on_GiB": a["peak_alloc_GiB"],
"peak_alloc_off_GiB": b["peak_alloc_GiB"],
"verdict": "关掉 gradient checkpointing 没有更快(中位数差 <1%),"
"峰值显存也没变(28.23 GiB)—— 8B 冻结主干下 ckpt 的额外重算"
"与省下的激活显存代价基本抵消;保留默认开启(省显存、无速度损失)",
}
if a and c:
comparison["accum2_vs_accum8"] = {
"accum2_s_per_step_median": a["s_per_step_median"],
"accum2_per_micro_median": a["s_per_step_median"] / 2,
"accum8_s_per_step_median": c["s_per_step_median"],
"accum8_per_micro_median": c["s_per_step_median"] / 8,
"ratio": c["s_per_step_median"] / a["s_per_step_median"],
"peak_alloc_accum2_GiB": a["peak_alloc_GiB"],
"peak_alloc_accum8_GiB": c["peak_alloc_GiB"],
"verdict": "accum=8 的 s/step 就是 accum=2 的约 8 倍(17.57s vs 2.60s),"
"因为每个 step 要跑 8 次前反向。accum 不省算力,只改变梯度累积粒度;"
"峰值显存完全不变(28.23 GiB)—— 显存瓶颈在激活,而激活是逐 micro 释放的",
}
# ---------------- loss ----------------
losses = lrun["losses"]
head10, tail10 = losses[:10], losses[-10:]
qq = n // 4
loss_check = {
"config": "annotations_50.jsonl, limit=50, epochs=2, bs=1, accum=1, grad-ckpt on, warmup=10",
"n_steps": n,
"head10_mean": mean(head10),
"tail10_mean": mean(tail10),
"delta": mean(tail10) - mean(head10),
"relative_drop_pct": 100 * (mean(head10) - mean(tail10)) / mean(head10),
"first_loss": losses[0], "last_loss": losses[-1],
"min": min(losses), "max": max(losses), "median": med(losses),
"pearson_r_loss_vs_step": pearson(list(range(n)), losses),
"spearman_rho_loss_vs_step": pearson(rank(list(range(n))), rank(losses)),
"quartile_means": {
"q1_steps_1_25": mean(losses[:qq]), "q2_steps_26_50": mean(losses[qq:2 * qq]),
"q3_steps_51_75": mean(losses[2 * qq:3 * qq]), "q4_steps_76_100": mean(losses[3 * qq:]),
},
"epoch_split": {
"epoch1_mean": mean(losses[:50]), "epoch2_mean": mean(losses[50:]),
"epoch1_first10": mean(losses[:10]), "epoch1_last10": mean(losses[40:50]),
"epoch2_last10": mean(losses[-10:]),
},
"loss_series": losses,
"official_cross_check": {k: xcheck[-1][k] for k in ("step", "official", "mine", "abs_diff")},
}
# ---------------- 权重是否真的被训练改动 ----------------
weights_evidence = {
"why": "checkpoints/_losscheck(100 步)与 _timing_gc(8 步)的 proj.weight 的 std "
"在小数点后 6 位完全相同,**看起来**像没训练。按 bf16 分辨率(0.023 附近 "
"ULP ≈ 1.2e-4)逐元素核对后确认:不是没训练,是 std 这个统计量太钝。",
"fresh_vs_100steps": {"abs_max": 7.996e-3, "abs_mean": 6.187e-4, "equal": False},
"fresh_vs_8steps": {"abs_max": 1.526e-5, "abs_mean": 1.292e-7, "equal": False},
"weight_std": {"fresh": 0.02283090, "after_100_steps": 0.02283631,
"after_8_steps": 0.02283090},
"bf16_ulp_near_0p023": 1.221e-4,
"verdict": "投影权重确实在动:100 步后平均改变 6.19e-4(约 5 个 bf16 ULP),"
"最大 8.0e-3;8 步时也已偏离初始值(1.5e-5),但幅度小于 1 ULP 的一半,"
"所以 std 看起来没变。**训练回路有效。**",
"caveat": "loss 从 3.26 降到 0.24(-84%)远超 2.6M 参数在 100 步内的合理泛化能力,"
"结合这批数据的 50 条只来自 7 篇文献(42 条共用同一篇),**绝大部分是记忆**,"
"不能当作泛化能力的证据。它只证明:前向/反向/优化器/梯度回传到投影这一段全通。",
}
# ---------------- epoch 边界逐样本复查 ----------------
e1_first5 = losses[:5]
e2_first5 = losses[50:55]
epoch_boundary = {
"epoch1_first5": e1_first5,
"epoch2_first5": e2_first5,
"paired_delta": [b2 - a2 for a2, b2 in zip(e1_first5, e2_first5)],
"note": "同 5 条样本在 epoch2 的 loss 全部低于 epoch1 -> 逐样本改善,不是整体平移",
}
# ---------------- 长度模型 ----------------
fit = (verify or {}).get("linear_model") or {}
slope = fit.get("slope_s_per_nt")
intercept = fit.get("intercept_s")
length_model = {
"form": "s_per_step ≈ intercept + slope × seq_nt",
"intercept_s": intercept, "slope_s_per_nt": slope,
"slope_ms_per_nt": (slope * 1000) if slope else None,
"pearson_r": fit.get("pearson_r"),
"fitted_on": fit.get("fitted_on"),
"bucket_check": (verify or {}).get("bucket_check"),
"s_per_step_by_seq_len": {
str(k): v for k, v in ((verify or {}).get("length_sensitivity_s_per_step") or {}).items()
},
}
# ---------------- token 压缩(同池对照)----------------
comp = (verify or {}).get("same_pool_compression") or {}
base_mean = (comp.get("baseline_rna731") or {}).get("s_per_step_mean")
compression = {"same_pool_baseline": comp.get("baseline_rna731")}
for key, label in (("compress_real_256", "compress_real_256_latent"),
("compress_real_64", "compress_real_64_latent"),
("trunc_hack_64", "truncate_hack_64_token")):
d = comp.get(key)
if not d:
continue
e = dict(d)
if base_mean and d.get("s_per_step_mean"):
e["speedup_vs_baseline_mean"] = base_mean / d["s_per_step_mean"]
e["time_saved_pct"] = 100 * (1 - d["s_per_step_mean"] / base_mean)
e["sequence_total_tokens_actual"] = {"compress_real_256_latent": 405,
"compress_real_64_latent": 213,
"truncate_hack_64_token": 213}.get(label)
compression[label] = e
compression["equivalence_test"] = (verify or {}).get("equivalence_E_vs_G")
compression["conclusion"] = (
"把 RNA 软提示从 731 token 压到 64 token,序列 880 → 213,"
"单步从 1.664s(mean)降到 0.652s,快 2.55×(中位数 1.400 → 0.673,快 2.08×)。"
"真压缩器(RnaTokenCompressor resampler)与纯截断 hack 耗时在 0.5% 以内一致 —— "
"压缩收益全部来自序列变短,压缩器自身前向开销可忽略。"
"⚠️ 代价:峰值显存不降反升(28.23 → 32.89 GiB),因为 resampler 要在 1024 token 上"
"做 cross-attention,且它自己的激活也要保留。")
# ---------------- 外推 ----------------
S_FULL = step_time_stats["mean"] # 1.633 s/step
c64 = compression.get("compress_real_64_latent") or {}
S_C64 = c64.get("s_per_step_mean") or 0.652
S_C256 = (compression.get("compress_real_256_latent") or {}).get("s_per_step_mean") or 1.030
def hours(steps_, s_):
return steps_ * s_ / 3600.0
def human(h):
return f"{h:.1f} h" + (f"({h/24:.2f} 天)" if h >= 24 else "")
table = []
for dn, ep in ((3800, 10), (3800, 5), (50, 10)):
steps_ = dn * ep
table.append({
"data": dn, "epochs": ep, "steps_batch1_accum1": steps_,
"hours_full_rna": hours(steps_, S_FULL), "hours_full_rna_human": human(hours(steps_, S_FULL)),
"hours_compress_256": hours(steps_, S_C256),
"hours_compress_256_human": human(hours(steps_, S_C256)),
"hours_compress_64": hours(steps_, S_C64),
"hours_compress_64_human": human(hours(steps_, S_C64)),
})
max_steps = int(24 * 3600 / S_FULL)
max_steps64 = int(24 * 3600 / S_C64)
def feasible(cap, s_):
out = []
for dn in (500, 1000, 1500, 2000, 2500, 3000, 3800, 5000):
for ep in (1, 2, 3, 5, 10):
stp = dn * ep
if stp <= cap:
out.append({"data": dn, "epochs": ep, "steps": stp,
"est_hours": hours(stp, s_)})
return out
budget = {
"hours": 24,
"s_per_step_used_full_rna": S_FULL,
"s_per_step_used_compress64": S_C64,
"max_steps_full_rna": max_steps,
"max_steps_compress64": max_steps64,
"max_epochs_at_3800_full_rna": max_steps // 3800,
"max_epochs_at_3800_compress64": max_steps64 // 3800,
"feasible_configs_full_rna": feasible(max_steps, S_FULL),
"feasible_configs_compress64": feasible(max_steps64, S_C64),
"answer_full_rna": (
f"全量 RNA(含 optimizer 摊销,1.633 s/step)24h 能跑约 {max_steps:,} 步。"
f"3,800 条 × 10 epoch = 38,000 步 = {human(hours(38000, S_FULL))},**能放进 24h**(约 {hours(38000,S_FULL)/24:.0%} 预算);"
f"3,800 条 × 5 epoch = 19,000 步 = {human(hours(19000, S_FULL))},很宽裕;"
f"50 条 × 10 epoch = 500 步 = {human(hours(500, S_FULL))},可忽略。"
f"按 24h 预算反推,3,800 条最多可跑约 {max_steps // 3800} 个 epoch。"
f"所以**算力不是 Stage 1 的瓶颈** —— 瓶颈是数据量(现在只有 50 条)"
f"和数据质量。若先做 token 压缩(64 latent,0.652 s/step),"
f"24h 可跑约 {max_steps64:,} 步,38,000 步只需 {human(hours(38000, S_C64))}。"
f"⚠️ 外推假设 3,800 条语料的序列长度分布与当前 50 条一致(均值 449 nt);"
f"由长度模型,若新语料平均 1000 nt,单步会涨到约 {intercept + slope * 1000:.2f}s,"
f"38,000 步约 {human(hours(38000, intercept + slope * 1000))}。"),
"length_assumption": "外推按当前 50 条的平均长度(449 nt → 约 880 token)折算;"
"实际耗时对序列长度近似线性(见 length_model)。",
}
# ---------------- 跨版本复测(06:38 版 vs 08:17 版)----------------
cross_version = None
if val:
vr = {x["name"]: x for x in val["runs"] if not x.get("error")}
von, voff = vr.get("V_ckpt_on_accum2_newcode"), vr.get("V_ckpt_off_accum2_newcode")
cross_version = {
"alignment_py": val.get("alignment_py_before"),
"runs": [summ(x) for x in val["runs"]],
"verdict": "在 09:06:38 版 alignment.py 上复测 accum=2/16 条池:"
f"grad-ckpt {'开' if von else '-'} {von['seconds']['per_optimizer_step']['median']:.3f}s、"
+ (f"关 {voff['seconds']['per_optimizer_step']['median']:.3f}s、" if voff else "")
+ "与旧版(2.603 / 2.616s)在 ±2% 内一致,"
"峰值显存同为 28.23 GiB —— 并发改动没有改变默认路径的计时特性。",
}
out = {
"generated_from": [p.name for p in R.glob("_raw_timing_*.json")],
"environment": ab["runs"][0]["env"],
"methodology": {
"headline_metric": "mean s/step(长跑会被长尾步拉向均值)",
"secondary_metric": "median s/step(典型步)",
"why": "逐步耗时右偏且与序列长度强线性相关(见 length_model),"
"单跑中位数会低估长跑总耗时。",
"loop_fidelity": "自建回路逐字复刻 train_stage1 的样本顺序与 step 触发条件;"
"交叉验证最大绝对 loss 差 = 0(见 cross_validation)",
"prewarm": "计时前列出 no_grad 预热步,把 HIP kernel 编译/autotune 的首步开销"
"(首步 1.4–3.7s 异常值)排除在外",
"artifacts": {
"harness": "scripts/_time_stage1.py",
"specs": "tmp/_spec_*.json",
"logs": "logs/_timing_*.log",
},
},
"code_fingerprint": {
"alignment_py_before": ab.get("alignment_py_before"),
"alignment_py_after": ab.get("alignment_py_after"),
"changed_midrun": ab.get("alignment_py_changed_midrun"),
"trunc_run_before": tr.get("alignment_py_before"),
"trunc_run_after": tr.get("alignment_py_after"),
"loss_run_before": ls.get("alignment_py_before"),
"loss_run_after": ls.get("alignment_py_after"),
"note": "并发任务在 08:48 给 alignment.py 加了 compress_latent 开关(默认 None)。"
"A/B/C 与 loss 运行用旧版(9e358e22),压缩实验用新版(4c9cec00)。"
"两者在 compress_latent=None 时逐位一致,故 cross_validation 为 0 误差。",
},
"step_time_stats": step_time_stats,
"offset_table": [
{"name": r["name"], "s_per_step_median": r.get("s_per_step_median"),
"s_per_step_mean": r.get("s_per_step_mean"),
"peak_alloc_GiB": r.get("peak_alloc_GiB"),
"n_steps_measured": r.get("n_steps_measured"),
"compress_latent": r.get("compress_latent_requested")}
for r in runs if "error" not in r],
"comparison": comparison,
"cross_version_recheck": cross_version,
"loss_check": loss_check,
"cross_validation": cross_validation,
"weights_evidence": weights_evidence,
"epoch_boundary_check": epoch_boundary,
"official_timing_run": official_timing,
"length_model": length_model,
"token_compression": compression,
"extrapolation": {"table": table, "budget_24h": budget},
"attention_backend": {
"experiment": "TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 时 SDPA 才会选 flash/mem-efficient kernel",
"with_flag_s_per_step_mean": (aotr["runs"][0]["seconds"]["per_optimizer_step"]["mean"]
if aotr and aotr["runs"] and not aotr["runs"][0].get("error")
else None),
"with_flag_peak_alloc_GiB": (aotr["runs"][0]["memory"]["peak_allocated_GiB"]
if aotr and aotr["runs"] and not aotr["runs"][0].get("error")
else None),
"without_flag_peak_alloc_GiB": a["peak_alloc_GiB"] if a else None,
"loss_side_by_side": {
"flag_on_loss_head": (aotr["runs"][0].get("loss_head") if aotr else None),
"flag_off_loss_head": a.get("loss_head") if a else None,
},
"verdict": "SDPA 默认**不**走 flash/mem-efficient kernel,只在设了 "
"TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 之后才走。设了确实更快:"
"2.287 vs 2.708 s/step(约 -15%),峰值显存也从 28.23 降到 22.51 GiB。"
"但它是**实验性**开关,而且会改变 attention 的数值路径 —— 同样 4 步的 loss 序列"
"在打开后出现可见偏差(3.154 vs 3.160 起,逐渐收敛到 1.977 vs 1.972)。"
"结论:值得作为加速候选,但**必须先验证**下游对齐指标(zero-encoder probe / ROUGE)"
"没被数值差异影响,再决定是否作为默认环境变量写进训练脚本;"
"本次只作为发现记录,不擅自改变默认。",
},
}
dst = R / "stage1_timing.json"
dst.write_text(json.dumps(out, indent=2, ensure_ascii=False), encoding="utf-8")
print(f"[report] -> {dst}\n")
print("== 每组配置(median / mean s-step,峰值显存)==")
for r in out["offset_table"]:
print(f" {r['name']:<24} {r['s_per_step_median']:.3f} / {r['s_per_step_mean']:.3f} s "
f"peak {r['peak_alloc_GiB']:.2f} GiB n={r['n_steps_measured']}"
+ (f" [compress {r['compress_latent']}]" if r['compress_latent'] else ""))
print(f"\n== 主口径(100 步)==")
for k in ("mean", "median", "p25", "p75", "p90", "min", "max"):
print(f" {k}: {step_time_stats[k]:.3f} s")
print(f" forward median {step_time_stats['forward_median']:.3f}s / "
f"backward median {step_time_stats['backward_median']:.3f}s "
f"({step_time_stats['forward_share_pct']:.0f}% fwd)")
print(f"\n== 交叉验证 ==\n 最大绝对差 {cross_validation['max_abs_diff']:.1e} -> {cross_validation['verdict']}")
print(f"\n== loss ==\n 前10步 {loss_check['head10_mean']:.4f} / 后10步 {loss_check['tail10_mean']:.4f} "
f"(-{loss_check['relative_drop_pct']:.1f}%) r={loss_check['pearson_r_loss_vs_step']:.3f} "
f"rho={loss_check['spearman_rho_loss_vs_step']:.3f}")
print(f" 四分位均值: {[round(v,3) for v in loss_check['quartile_means'].values()]}")
print(f"\n== 权重是否真的动了 ==\n {weights_evidence['verdict']}")
print(f" epoch1 前5步 {[round(v,3) for v in epoch_boundary['epoch1_first5']]}"
f"\n epoch2 前5步 {[round(v,3) for v in epoch_boundary['epoch2_first5']]}")
print(f"\n== 压缩 ==\n {compression['conclusion']}")
print(f"\n== 外推(1.633 s/step 全量 RNA)==")
for row in table:
print(f" {row['data']}×{row['epochs']}ep = {row['steps_batch1_accum1']:>6} 步 -> "
f"{row['hours_full_rna_human']:<18} | 压缩256 {row['hours_compress_256_human']:<14}"
f" | 压缩64 {row['hours_compress_64_human']}")
print(f"\n 24h 最大步数: 全量 {max_steps:,} / 压缩64 {max_steps64:,}")
print(f"\n {budget['answer_full_rna']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())