Download experiment_md.py from Cccccz/comparison: direct link, hf CLI and curl.
- Browser
- Download file 56.3 kB
-
https://huggingface.co/Cccccz/comparison/resolve/main/experiment_md.py
- Command line
-
hf download hf://Cccccz/comparison/experiment_md.py
-
curl -L -o experiment_md.py https://huggingface.co/Cccccz/comparison/resolve/main/experiment_md.py
56.3 kB
| #!/usr/bin/env python | |
| """Build Experiment.md from the evaluation summary and the settled operating points. | |
| Sections: | |
| 1. the Extended-251 evaluation table for every strategy that has been fully | |
| scored (eval_out/summaries/final_summary.csv); | |
| 2. operating points found but not yet evaluated (results/sweep_*_<schedule>.json | |
| for the non-default schedules), with search-stage speed numbers only. | |
| python experiment_md.py # writes Experiment.md | |
| """ | |
| import csv | |
| import glob | |
| import json | |
| import os | |
| import re | |
| import sys | |
| ROOT = os.path.dirname(os.path.abspath(__file__)) | |
| SUMMARY = os.path.join(ROOT, "eval_out/summaries/final_summary.csv") | |
| OUT = os.path.join(ROOT, "Experiment.md") | |
| BASE_LABEL = {"self_forcing": "Self-Forcing", "causal_forcing": "Causal-Forcing", | |
| "causal_forcing_a": "Causal-Forcing-a(Predictor)"} | |
| BASES = ["self_forcing", "causal_forcing", "causal_forcing_a"] | |
| PREFIX = {"self_forcing": "sf_", "causal_forcing": "cf_", "causal_forcing_a": "cfa_"} | |
| METHOD_LABEL = {"none": "FFFF", "teacache": "TeaCache", "flowcache": "FlowCache", | |
| "taylorseer": "TaylorSeer", "motioncache": "MotionCache"} | |
| METHOD_ORDER = ["none", "teacache", "flowcache", "taylorseer", "motioncache"] | |
| FORWARDS = 28 | |
| CHUNKS = 7 | |
| STEPS = 4 | |
| HEADER = ("| 策略 | VBench-251 (%) | 平均 P | Full | 模型路径耗时 (ms) | 加速比 " | |
| "| PSNR | SSIM | LPIPS |\n|---|---:|---:|---:|---:|---:|---:|---:|---:|") | |
| def strategy_label(method, schedule, target): | |
| if target in (None, 0.0): # naive / reuse baselines carry no target | |
| return f"`{method}_{schedule}`" if method != "none" else f"`naive_{schedule}`" | |
| """Strategy id as used in eval_out/, minus the base prefix: ``ffff``, | |
| ``teacache_x1.3`` (FxxF study), ``taylorseer_FFxx_x1.6``.""" | |
| if method == "none": | |
| return "`ffff`" | |
| tag = "" if schedule.upper() == "FXXF" else f"_{schedule}" | |
| return f"`{method}{tag}_x{target:g}`" | |
| def eval_rows(): | |
| if not os.path.exists(SUMMARY): | |
| return {} | |
| rows = list(csv.DictReader(open(SUMMARY))) | |
| by_base = {} | |
| for r in rows: | |
| # schedule is not in the summary; the FxxF study has unsuffixed names | |
| m = re.match(r"^(sf|cf)_(\w+?)(?:_(F[Fx]{3}))?_x([\d.]+)$", r["strategy"]) | |
| if r.get("schedule"): | |
| sched = r["schedule"] | |
| elif r["method"] == "none": | |
| sched = "FFFF" | |
| elif r["strategy"].startswith("cfa_"): | |
| sched = r["strategy"].rsplit("_", 1)[-1].upper() # cfa_disca_fppf -> FPPF | |
| else: | |
| sched = (m.group(3) if m and m.group(3) else "FxxF") | |
| r["schedule"] = sched | |
| # naive / naive-cache rows are baselines, not operating points of a method | |
| r["is_baseline"] = ("naive" in r["strategy"]) or ("_reuse_" in r["strategy"]) | |
| r["id"] = r["strategy"].split("_", 1)[1] | |
| by_base.setdefault(r["base_model"], []).append(r) | |
| return by_base | |
| def forward_counts(strategy): | |
| """(Full, P) for one strategy. Predictor strategies record full_forwards / | |
| predictor_forwards per prompt and are counted that way (a predictor call is | |
| ~0.03 of a forward and is not folded into Full); cache strategies use the | |
| compute-equivalent forwards, P = 28 - Full.""" | |
| recs = [json.load(open(p)) for p in | |
| glob.glob(os.path.join(ROOT, "eval_out/per_prompt", strategy, "*.json"))] | |
| diag = [r.get("cache_diagnostics", {}) for r in recs] | |
| if diag and all("full_forwards" in d for d in diag): | |
| full = sum(d["full_forwards"] for d in diag) / len(diag) | |
| pred = sum(d["predictor_forwards"] for d in diag) / len(diag) | |
| return full, pred | |
| ce = sum(d["compute_equivalent_forwards"] for d in diag) / len(diag) | |
| return ce, FORWARDS - ce | |
| PARTIAL_LATENCY = [] # (strategy id, timed prompts, total prompts) collected while formatting | |
| # ---- row grouping: sections (schedule family / chunk count) x speed tiers ---- | |
| TIERS = [(1.3, "≈1.3×"), (1.6, "≈1.6×"), (1.75, "≈1.75×"), (2.0, "≈2×"), (2.85, "≈2.85–3×"), (4.0, "≈3.3–4×")] | |
| def tier_label(r, n_full_steps): | |
| """Nominal speed tier of a row: the search target for cache operating points, | |
| otherwise the compute ratio (full forwards for Predictor rows) snapped to the | |
| nearest tier. FFFF rows get their own label.""" | |
| if r["strategy"].endswith("_ffff"): | |
| return "FFFF" | |
| target = float(r.get("target_speedup") or 0) | |
| is_pred = r["method"].startswith("predictor_") | |
| if target > 0 and not r.get("is_baseline") and not is_pred: | |
| x = target | |
| else: | |
| full = float(r.get("mean_full_forwards") or 0) if is_pred else 0.0 | |
| ce = full if full > 0 else float(r.get("mean_compute_equivalent_forwards") or 0) | |
| x = n_full_steps / ce if ce > 0 else 1.0 | |
| if x < 1.15: # e.g. a full-compute variant (euler_FFFF): no speed tier | |
| return "FFFF" | |
| return min(TIERS, key=lambda t: abs(t[0] - x))[1] | |
| TIER_ORDER = ["FFFF"] + [lab for _, lab in TIERS] | |
| def grouped(rows, section_of, section_order, n_full_steps, row_key, ncols): | |
| """Yield table lines: a blank spacer row + bold section row, then per tier a | |
| spacer + italic tier row, then the rows (ordered by row_key).""" | |
| blank = "|" + " |" * ncols | |
| out = [] | |
| by_sec = {} | |
| for r in rows: | |
| by_sec.setdefault(section_of(r), []).append(r) | |
| for sec in section_order: | |
| if sec not in by_sec: | |
| continue | |
| if out: | |
| out.append(blank) | |
| out.append(f"| **{sec}**" + " |" * ncols) | |
| tiers = {} | |
| for r in by_sec[sec]: | |
| tiers.setdefault(tier_label(r, n_full_steps), []).append(r) | |
| for lab in TIER_ORDER: | |
| if lab not in tiers: | |
| continue | |
| if lab != "FFFF": | |
| out.append(blank) | |
| out.append(f"| *{lab}*" + " |" * ncols) | |
| for r in sorted(tiers[lab], key=row_key): | |
| out.append(("row", r)) | |
| return out | |
| def section_sf_cf(r): | |
| return "首 chunk FFFF(`_c0FFFF`)" if r["strategy"].endswith("_c0FFFF") else "原始调度(chunk 0 与其它 chunk 相同)" | |
| SECTIONS_SF_CF = ["原始调度(chunk 0 与其它 chunk 相同)", "首 chunk FFFF(`_c0FFFF`)"] | |
| def section_cfa(r): | |
| m = re.search(r"_(\d+)c(?:_|$)", r["strategy"]) | |
| n = int(m.group(1)) if m else 7 | |
| return f"{n} chunk({n * 12 - 3} 帧)" | |
| def n_full_steps_cfa(r): | |
| m = re.search(r"_(\d+)c(?:_|$)", r["strategy"]) | |
| return 4 * (int(m.group(1)) if m else 7) | |
| def section_hy(r): | |
| if (r["strategy"].endswith("_c0FFFF") or r.get("first_chunk_schedule") == "FFFF" | |
| or r["method"].startswith("predictor_")): | |
| return "首 chunk FFFF(`_c0FFFF`;Predictor 行 chunk 0 固定全算)" | |
| return "原始调度(chunk 0 与其它 chunk 相同)" | |
| SECTIONS_HY = ["原始调度(chunk 0 与其它 chunk 相同)", "首 chunk FFFF(`_c0FFFF`;Predictor 行 chunk 0 固定全算)"] | |
| def hy_chunks(r): | |
| """Chunk count of an HY row: 8 (125 frames) unless the id carries _<n>c.""" | |
| m = re.search(r"_(\d+)c(?:_|$)", r["strategy"]) | |
| return int(m.group(1)) if m else 8 | |
| def section_hy_long(r): | |
| n = hy_chunks(r) | |
| return f"{n} chunk({(4 * n - 1) * 4 + 1} 帧,chunk 0 FFFF)" | |
| import re as _re | |
| def _natural(text): | |
| """Sort key that orders embedded integers numerically (s500 < s1000 < s2000).""" | |
| return [int(t) if t.isdigit() else t for t in _re.split(r"(\d+)", text)] | |
| def method_key(r): | |
| return (r.get("is_baseline", False), | |
| METHOD_ORDER.index(r["method"]) if r["method"] in METHOD_ORDER else 99, | |
| r["schedule"] != "FxxF", float(r.get("target_speedup") or 0), _natural(r["strategy"])) | |
| def fmt_eval_table(rows): | |
| ffff = next(r for r in rows if r["method"] == "none") | |
| ffff_ms = float(ffff["policy_latency_ms"]) | |
| is_cfa = any(r["strategy"].startswith("cfa_") for r in rows) | |
| if is_cfa: | |
| secs = sorted({section_cfa(r) for r in rows}, key=lambda t: int(t.split(" ")[0])) | |
| # n_full_steps differs per chunk count: tier inside each section | |
| plan = [] | |
| for sec in secs: | |
| sub = [r for r in rows if section_cfa(r) == sec] | |
| part = grouped(sub, section_cfa, [sec], n_full_steps_cfa(sub[0]), method_key, 9) | |
| plan += (["|" + " |" * 9] if plan else []) + part | |
| else: | |
| plan = grouped(rows, section_sf_cf, SECTIONS_SF_CF, 28, method_key, 9) | |
| lines = [HEADER] | |
| for item in plan: | |
| if isinstance(item, str): | |
| lines.append(item) | |
| continue | |
| r = item[1] | |
| full, pred = forward_counts(r["strategy"]) | |
| ms = float(r["policy_latency_ms"]) | |
| if r["strategy"].endswith("_ffff"): | |
| psnr, ssim, lpips = 120.0, 1.0, 0.0 | |
| else: | |
| psnr, ssim, lpips = (float(r[k] or "nan") for k in ("psnr", "ssim", "lpips")) | |
| # "P" counts forwards served from cache. A naive-N-step row runs a | |
| # *shorter* schedule -- those forwards are never issued, not reused -- so | |
| # the column would be misleading there. | |
| naive_steps = (r["method"] == "none" and not r["strategy"].endswith("_ffff")) | |
| p_cell = "—" if naive_steps else f"{pred:.3f}" | |
| # A strategy added after the first generation run whose latency has not | |
| # yet been paired with FFFF in the same process is only provisional. | |
| # (only the sf_/cf_ strategies added after their FFFF run can be provisional; | |
| # the predictor strategies were generated together with their own FFFF) | |
| provisional = (r["strategy"].startswith(("sf_", "cf_")) | |
| and r["schedule"] not in ("FxxF", "FFFF") | |
| and not r.get("latency_source", "").startswith(("paired", "strategy retime"))) | |
| # Any strategy whose latency mean covers fewer prompts than its quality | |
| # metrics (records flagged latency_excluded) is provisional as well. | |
| lat_n = r.get("latency_num_videos") | |
| if "latency_excluded" in r.get("latency_source", "") and lat_n: | |
| provisional = True | |
| PARTIAL_LATENCY.append((r["id"], int(float(lat_n)), int(float(r["num_videos"])))) | |
| # Timed on the one prompt shard that had its GPU to itself (aggregate.py | |
| # --latency-subsets); quality still covers every prompt. | |
| shard_timed = (r.get("latency_source", "").startswith("generation run, shard") | |
| or "re-timed" in r.get("latency_source", "")) | |
| mark = " ‡" if shard_timed else (" †" if provisional else "") | |
| mp4 = int(float(r.get("mp4_reference_records") or 0)) | |
| pmark = " §" if mp4 > 0 else "" | |
| vb = r.get("selected_vbench_percent") or "" | |
| vb = f"{float(vb):.4f}" if vb else "待打分" | |
| lines.append( | |
| f"| `{r['id']}` " | |
| f"| {vb} " | |
| f"| {p_cell} " | |
| f"| {full:.3f} " | |
| f"| {ms:.2f}{mark} " | |
| f"| {1.0 / float(r['latency_ratio_vs_ffff']):.3f}×{mark} " | |
| f"| {psnr:.3f}{pmark} | {ssim:.4f}{pmark} | {lpips:.4f}{pmark} |") | |
| return "\n".join(lines) | |
| PARAM_LABEL = {"thresh": "`thresh`", "interval": "`interval`"} | |
| def hy_config_table(): | |
| """Parameter table for the HY-WorldPlay strategies (from hy_strategies.load_strategies).""" | |
| try: | |
| sys.path.insert(0, ROOT) | |
| from hy_strategies import load_strategies | |
| strategies = load_strategies() | |
| except Exception as e: # noqa: BLE001 | |
| return "" | |
| label = {"teacache": "TeaCache", "taylorseer": "TaylorSeer", "motioncache": "MotionCache", | |
| "flowcache": "FlowCache", "none": "无缓存", "reuse": "reuse"} | |
| lines = ["| 策略 | 方法 | 调度 | 参数 | 取值 | 目标加速比 | 来源 |", "|---|---|---|---|---:|---:|---|"] | |
| for st in strategies: | |
| sched = st["schedule"] | |
| if st.get("first_chunk_schedule"): | |
| sched += "(首 chunk FFFF)" | |
| param = f"`{st['param']}`" if st.get("param") else "—" | |
| val = f"{st['value']:g}" if st.get("value") is not None else "—" | |
| tgt = f"{st['target']:g}×" if st.get("target") else "—" | |
| src = st.get("source", "—") | |
| if st["method"] == "teacache" and st.get("coefficients"): | |
| src += ";rescale 系数 hy_coeff.json" | |
| lines.append(f"| `{st['name'].split('_', 1)[1]}` | {label.get(st['method'], st['method'])} | {sched} " | |
| f"| {param} | {val} | {tgt} | {src} |") | |
| lines.append("| `atc_s1_fppf` / `atc_s1_fppp` | ATC Stage-1 predictor | FPPF / FPPP | — | — | — | " | |
| "HY-WorldPlay-DEV-Predictor validation25(¶) |") | |
| lines.append("| `atc_s2_s{500,800,1000,1500,2000}_fppf` / `_fppp` | ATC Stage-1 + on-policy detached chunk+timestep FPPP rollout(step 500/800/1000/1500/2000) | FPPF / FPPP | — | — | — | " | |
| "HY-WorldPlay-DEV-Predictor validation25(¶) |") | |
| lines.append("| `disca_s1_fppf` / `disca_s1_fppp` | DisCa Stage-1 predictor(双 block,step 2000) | FPPF / FPPP | — | — | — | " | |
| "HY-WorldPlay-DEV-Predictor validation25(¶) |") | |
| lines.append("| `disca_s1000_fppf` / `disca_s1000_fppp` | DisCa Stage-1 predictor(step 1000) | FPPF / FPPP | — | — | — | " | |
| "HY-WorldPlay-DEV-Predictor validation25(¶) |") | |
| lines.append("| `disca_s500_fppf` | DisCa Stage-1 predictor(step 500) | FPPF | — | — | — | " | |
| "HY-WorldPlay-DEV-Predictor validation25(¶) |") | |
| lines.append("| `<n>c_ffff` / `naive2step_<n>c_c0FFFF`(n = 16 / 32) | 长视频 FFFF 参考 / naive 2 步(首 chunk FFFF),253 / 509 帧 | FFFF / FF | — | — | — | " | |
| "hycache,同进程配对 FFFF |") | |
| lines.append("| `atc_s2_s{500,1000,2000}_<n>c_fppf_c0FFFF`、`disca_s{500,1000,2000}_<n>c_fppf`(n = 16 / 32) | ATC rollout / DisCa Stage-1 predictor 各 step 的长视频行(64 / 128 latent,相机动作等比拉长,pose-retrieved memory 上限 20 latent) | FPPF | — | — | — | " | |
| "HY-WorldPlay-DEV-Predictor 生成器(`hy_dev_gen_long.py`,¶),像素指标对照 `<n>c_ffff` 的 MP4 |") | |
| return "\n".join(lines) | |
| def lb_config_table(): | |
| """Parameter table for the LingBot-World-V2 strategies (from lb_strategies.load_strategies).""" | |
| try: | |
| sys.path.insert(0, ROOT) | |
| from lb_strategies import load_strategies | |
| strategies = load_strategies() | |
| except Exception: # noqa: BLE001 | |
| return "" | |
| label = {"teacache": "TeaCache", "taylorseer": "TaylorSeer", "motioncache": "MotionCache", | |
| "none": "无缓存", "reuse": "reuse(velocity)"} | |
| lines = ["| 策略 | 方法 | 调度 | 参数 | 取值 | 目标加速比 | 来源 |", "|---|---|---|---|---:|---:|---|"] | |
| for st in strategies: | |
| sched = st["schedule"] | |
| if st.get("first_chunk_schedule"): | |
| sched += "(首 chunk FFFF)" | |
| param = f"`{st['param']}`" if st.get("param") else "—" | |
| val = f"{st['value']:g}" if st.get("value") is not None else "—" | |
| tgt = f"{st['target']:g}×" if st.get("target") else "—" | |
| src = st.get("source", "—") | |
| if st["method"] == "teacache" and st.get("coefficients"): | |
| src += ";rescale 系数 lb_coeff.json" | |
| lines.append(f"| `{st['name'].split('_', 1)[1]}` | {label.get(st['method'], st['method'])} | {sched} " | |
| f"| {param} | {val} | {tgt} | {src} |") | |
| # Layer-17 Predictor rows (lingbot-world-v2-a, Stage-1 only): names follow | |
| # lb_generate_predictor_eval.py, one row per model with the evaluated patterns. | |
| try: | |
| import csv as _csv | |
| rows = list(_csv.DictReader(open(LB_SUMMARY))) if os.path.exists(LB_SUMMARY) else [] | |
| except Exception: # noqa: BLE001 | |
| rows = [] | |
| by_model = {} | |
| for r in rows: | |
| if r["method"].startswith("predictor_"): | |
| by_model.setdefault(r["method"][len("predictor_"):], []).append(r["strategy"].split("_")[-1].upper()) | |
| desc = {"atc_chunk": "ATC Stage-1 predictor(block 17,previous_scope=chunk)", | |
| "disca": "DisCa Stage-1 predictor(block 17,无 previous-chunk 通道)"} | |
| for m, pats in sorted(by_model.items()): | |
| names = " / ".join(f"`{m}_{p.lower()}`" for p in sorted(set(pats))) | |
| lines.append(f"| {names} | {desc.get(m, m)} | {' / '.join(sorted(set(pats)))}(首 chunk FFFF) | — | — | — | " | |
| "lingbot-world-v2-a Stage-1,100 组图 × 4 动作离线集,2000 步 |") | |
| return "\n".join(lines) | |
| def config_tables(): | |
| """One row per strategy: the knob and its settled value, with provenance.""" | |
| sys.path.insert(0, ROOT) | |
| from eval.strategies import load_strategies | |
| out = {} | |
| for base in ["self_forcing", "causal_forcing"]: | |
| finals = {} | |
| for fp in glob.glob(os.path.join(ROOT, f"results/final_{base}*.json")): | |
| for r in json.load(open(fp))["rows"]: | |
| key = r.get("name") or (r["method"], r.get("schedule", "FxxF"), | |
| r["target_speedup"]) | |
| finals[key] = (r, os.path.basename(fp)) | |
| coeff_path = os.path.join(ROOT, f"results/coeff_{base}.json") | |
| coeff = json.load(open(coeff_path))["coefficients"] if os.path.exists(coeff_path) else None | |
| lines = ["| 策略 | 方法 | 调度 | 参数 | 取值 | 目标加速比 | 搜索时 Full | 来源 |", | |
| "|---|---|---|---|---:|---:|---:|---|"] | |
| for st in load_strategies(base): | |
| if st["is_reference"]: | |
| lines.append("| `ffff` | 无缓存 | FFFF | — | — | 1.0× | 28.000 | — |") | |
| continue | |
| key = st["name"] if st["name"] in finals else (st["method"], st["schedule"], st["target"]) | |
| r, src = finals[key] | |
| note = src | |
| if "heldout_flops_drift" in r: | |
| note += f",held-out 漂移 {r['heldout_flops_drift']:+.3f}" | |
| elif "band_note" in r: | |
| note += f",按实测加速比定点(扫描 {r['scan_measured_speedup']:.3f}×" | |
| if r.get("scan_active_step_ratio"): | |
| note += f",活跃步 {100*r['scan_active_step_ratio']:.0f}%" | |
| note += f",选中 {100*r['scan_mean_selected_fraction']:.0f}%" | |
| note += ")" + (f";{r['band_note']}" if r["band_note"] else "") | |
| elif "note" in r: | |
| note += ",3-prompt 搜索点,未经 finalize" | |
| if st.get("target") in (None, 0.0): | |
| lines.append(f"| `{st['name'].split('_', 1)[1]}` | {METHOD_LABEL.get(st['method'], st['method'])} " | |
| f"| {st['schedule']} | — | — | — | — | 基线(无搜索参数) |") | |
| continue | |
| lines.append( | |
| f"| {strategy_label(st['method'], st['schedule'], st['target'])} " | |
| f"| {METHOD_LABEL[st['method']]} " | |
| f"| {st['schedule']}{'(首 chunk ' + st['first_chunk_schedule'] + ')' if st.get('first_chunk_schedule') else ''} " | |
| f"| {PARAM_LABEL.get(st['param'], st['param'])} | {st['value']:.6g} " | |
| f"| {st['target']:g}× " | |
| f"| {r.get('compute_equivalent_forwards', r.get('scan_compute_equivalent', float('nan'))):.3f} " | |
| f"| {note} |") | |
| out[base] = ("\n".join(lines), coeff) | |
| return out | |
| HY_SUMMARY = os.path.join(ROOT, "eval_out_hy/summaries/final_summary.csv") | |
| LB_SUMMARY = os.path.join(ROOT, "eval_out_lb/summaries/final_summary.csv") | |
| HY_HEADER = ("| 策略 | VBench-I2V-100 原始 (%) | VBench-I2V-100 归一 (%) | 平均 P | Full " | |
| "| 模型路径耗时 (ms) | 加速比 | PSNR | SSIM | LPIPS |" | |
| "\n|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|") | |
| HY_ACT_HEADER = ("| 策略 | 参数 | 调度 | 活跃步 % | 空步 % | 平均选中 token % |" | |
| "\n|---|---|---|---:|---:|---:|") | |
| def fmt_hy_table(summary=HY_SUMMARY): | |
| """The I2V-100 table (HY-WorldPlay by default; the LingBot table has the same shape).""" | |
| if not os.path.exists(summary): | |
| return None | |
| rows = list(csv.DictReader(open(summary))) | |
| if not rows: | |
| return None | |
| for r in rows: | |
| r["is_baseline"] = ("naive" in r["strategy"]) or ("_reuse_" in r["strategy"]) | |
| lines = [HY_HEADER] | |
| # 8-chunk rows in the two first-chunk sections, then one section per longer video | |
| plan = grouped([r for r in rows if hy_chunks(r) == 8], section_hy, SECTIONS_HY, 32, method_key, 10) | |
| for n in sorted({hy_chunks(r) for r in rows if hy_chunks(r) != 8}): | |
| sub = [r for r in rows if hy_chunks(r) == n] | |
| plan += ["|" + " |" * 10] + grouped(sub, section_hy_long, [section_hy_long(sub[0])], 4 * n, method_key, 10) | |
| for item in plan: | |
| if isinstance(item, str): | |
| lines.append(item) | |
| continue | |
| r = item[1] | |
| fw = float(r["denoise_forwards"]) | |
| ce = float(r["mean_compute_equivalent_forwards"]) | |
| ms = float(r["policy_latency_ms"]) | |
| if r["strategy"].endswith("_ffff"): | |
| psnr, ssim, lpips = 120.0, 1.0, 0.0 | |
| else: | |
| psnr, ssim, lpips = (float(r[k] or "nan") for k in ("psnr", "ssim", "lpips")) | |
| # "P" counts forwards served from cache. A naive-N-step row runs a | |
| # *shorter* schedule -- those forwards are never issued, not reused -- so | |
| # the column would be misleading there. | |
| naive_steps = (r["method"] == "none" and not r["strategy"].endswith("_ffff")) | |
| p_cell = "—" if naive_steps else f"{fw - ce:.3f}" | |
| # Predictor rows: same convention as the Causal-Forcing-a table -- P counts | |
| # Predictor steps, Full counts full DiT forwards (the 1/54 Predictor cost is | |
| # only used for the compute-equivalent speedup target). | |
| if r["method"].startswith("predictor_") and r.get("mean_predictor_forwards"): | |
| p_cell = f"{float(r['mean_predictor_forwards']):.3f}" | |
| ce = float(r["mean_full_forwards"]) | |
| raw = r.get("core5_percent") or "" | |
| nrm = r.get("score_5d_percent") or "" | |
| raw = f"{float(raw):.4f}" if raw else "待打分" | |
| nrm = f"{float(nrm):.4f}" if nrm else "待打分" | |
| pmark = " §" if int(float(r.get("mp4_reference_records") or 0)) > 0 else "" | |
| # DEV-project imports carry host-side stage timing, not CUDA-event timing | |
| tmark = " ¶" if r.get("latency_source", "").startswith("DEV generator") else "" | |
| lines.append( | |
| f"| `{r['strategy'].split('_', 1)[1]}` | {raw} | {nrm} | {p_cell} | {ce:.3f} | {ms:.2f}{tmark} " | |
| f"| {1.0 / float(r['latency_ratio_vs_ffff']):.3f}×{tmark} | {psnr:.3f}{pmark} | {ssim:.4f}{pmark} | {lpips:.4f}{pmark} |") | |
| return "\n".join(lines) | |
| def pending_points(): | |
| """Operating points from non-default-schedule sweeps that have no eval row yet.""" | |
| evaluated = {r["strategy"] for rows in eval_rows().values() for r in rows} | |
| out = {} | |
| for p in sorted(glob.glob(os.path.join(ROOT, "results/sweep_*_F*.json"))): | |
| d = json.load(open(p)) | |
| sched = d.get("schedule", "FxxF") | |
| short = {"self_forcing": "sf", "causal_forcing": "cf"}[d["base"]] | |
| for pt in d["operating_points"]: | |
| name = f"{short}_{d['method']}_{sched}_x{pt['target_speedup']:g}" | |
| if name in evaluated: | |
| continue | |
| out.setdefault(d["base"], []).append(dict(pt, method=d["method"], schedule=sched, | |
| param=d["param"], name=name)) | |
| return out | |
| def fmt_pending_table(pts): | |
| lines = ["| 策略 | 参数 | 平均 P | Full | compute 加速比 | 配对实测加速比 (3 prompt) | VBench / PSNR / SSIM / LPIPS |", | |
| "|---|---|---:|---:|---:|---:|---|"] | |
| pts = sorted(pts, key=lambda p: (METHOD_ORDER.index(p["method"]), p["schedule"], | |
| p["target_speedup"])) | |
| for p in pts: | |
| ce = p["mean_compute_equivalent_forwards"] | |
| lines.append( | |
| f"| {strategy_label(p['method'], p['schedule'], p['target_speedup'])} " | |
| f"| `{p['param']}={p[p['param']]:.4g}` " | |
| f"| {FORWARDS - ce:.3f} | {ce:.3f} " | |
| f"| {p['flops_speedup']:.3f}× " | |
| f"| {p['measured_speedup']:.2f}× (±{p['measured_speedup_stdev']:.2f}) " | |
| f"| 待评测 |") | |
| return "\n".join(lines) | |
| def main(): | |
| by_base = eval_rows() | |
| pending = pending_points() | |
| doc = [] | |
| doc.append("# Experiment: training-free caching on 4-step block-causal video DiTs\n") | |
| doc.append("四个 training-free 缓存方法(TeaCache / FlowCache / TaylorSeer / MotionCache)" | |
| "移植到两个 4-step 自回归视频 DiT(Self-Forcing、Causal-Forcing,均为 Wan2.1-1.3B " | |
| "block-causal)上,在匹配的 compute 预算下比较速度与质量。\n") | |
| doc.append("## 列定义\n") | |
| doc.append("- **策略**:方法 + 调度 + 目标加速比。调度字符串按 chunk 内 4 个去噪步写," | |
| "`F` = 必算完整 DiT,`x` = 允许方法从缓存服务。`FxxF` 是原始设定" | |
| "(等价于上游 `ret_steps=1` / `cutoff_steps=num_steps-1`)," | |
| "`FFxx` 让 TaylorSeer 在第一次预测前有两个已算点,`Fxxx` 去掉最后一步必算的限制," | |
| "算术上限从 2.0× 提到 4.0×。") | |
| doc.append("- **VBench-251 (%)**:Extended-251 协议的 VBench-8 selected 分(4×quality + semantic)/5," | |
| "251 条 prompt(subject_consistency 72 / overall_consistency 93 / scene 86)。") | |
| doc.append("- **Full**:一条视频(7 chunk × 4 步 = 28 次去噪 DiT forward)中实际执行的完整 DiT forward " | |
| "等效数,251 条 prompt 的平均;MotionCache / FlowCache 这类部分重算的步按 compute-equivalent " | |
| "折算。所有方法按这一列匹配 compute。") | |
| doc.append("- **平均 P**:一条视频中被**缓存复用**的 forward 数 = 28 − Full。" | |
| "`naiveNstep` 基线走的是更短的去噪调度,缺的 forward 是**根本没有发起**而不是被复用," | |
| "所以这一列对它们记为 —(它们的 Full 仍按 28 步的标尺给出,便于等算力对照)。" | |
| "Predictor 策略(`cfa_*`,Causal-Forcing-a)按记录里的 `full_forwards` / `predictor_forwards` 计:" | |
| "Full 为完整 DiT 次数,P 为 predictor 调用次数(每次约 0.03 个 forward,不计入 Full)。") | |
| doc.append("- **模型路径耗时 (ms)**:251 条 prompt 上 denoise policy 路径的 GPU 平均耗时," | |
| "含缓存方法自己的控制开销;不含 context/KV-cache 刷新 DiT(另计,~790 ms/视频)、" | |
| "文本编码、VAE 解码。") | |
| doc.append("- **加速比**:matched FFFF 平均耗时 / 该策略平均耗时(同 prompt、同 seed、同一次运行内配对)。" | |
| "原始 26 个策略与 FFFF 同批生成;后加的策略由 `eval/retime_eval.py` 与 FFFF 在同一进程内" | |
| "逐 prompt 配对重新计时(只跑 denoise 路径),其\"模型路径耗时\"来自这次配对计时," | |
| "绝对 ms 与首批不在同一争用条件下,只有加速比可跨行比较。" | |
| "首 chunk FFFF 变体(`_c0FFFF`,`teacache_x2_c0FFFF` 除外)的耗时来自 Sep 7 在空闲 GPU 上" | |
| "只重跑策略 denoise 路径的单次重计时(FFFF 不重跑,以各基模 FFFF 首批记录为分母;逐条核对算量一致," | |
| "视频与质量指标不变)。") | |
| doc.append("- **PSNR / SSIM / LPIPS**:相对同 prompt、同 seed 的 FFFF,在解码后的 81 帧 RGB 上计算," | |
| "编码 MP4 之前。FFFF 对自身固定为 120 dB / 1.0 / 0.0。" | |
| "Sep 7 起新增的策略不再重新生成 FFFF,参考帧读自 FFFF 那次运行写出的 MP4(H.264),表中以 § 标出;" | |
| "在同一批视频上实测该替换使 PSNR +0.10 dB、SSIM −0.024、LPIPS +0.012(MP4 抹掉了参考的高频细节)," | |
| "与其它行比较时按此修正。") | |
| doc.append("- 策略 id 与 `eval_out/` 目录一致(去掉 `sf_`/`cf_`/`cfa_` 前缀):不带调度后缀的是原始 `FxxF` 研究," | |
| "`_FFxx_`/`_Fxxx_` 为放宽调度后的操作点,`_x<N>` 是搜索时的目标加速比,实际 compute 看 Full 列;" | |
| "`cfa_*` 为 Causal-Forcing-a 上的 Predictor 策略(DisCa / ATC-chunk / ATC-last-frame,均为 Stage-1 监督蒸馏;" | |
| "`atc_chunk_s2_*` 为 ATC-chunk 再经 Stage-2 随机出口 DMD(Wan-14B real score、LoRA rank-128 fake score,2000 步)后的 EMA 权重)," | |
| "后缀 `fppf`/`fppp` 是 chunk 内的调度模式(P = predictor 步)。\n") | |
| doc.append("视频规格:81 帧、480×832、16 FPS、seed 0、bf16、每 prompt 一个样本。\n") | |
| doc.append("## 1. Extended-251 评测结果(已完成)\n") | |
| for base in BASES: | |
| if base not in by_base: | |
| continue | |
| doc.append(f"### {BASE_LABEL[base]}\n") | |
| doc.append(fmt_eval_table(by_base[base]) + "\n") | |
| hy = fmt_hy_table() | |
| if hy: | |
| doc.append("### HY-WorldPlay(I2V,VBench-I2V-100)\n") | |
| doc.append("第三个基模:HY-WorldPlay 的 4-step 蒸馏自回归 I2V 模型(HunyuanVideo-1.5 架构,54 个 " | |
| "double-stream block,480×832,125 帧 = 8 chunk × 4 步 = **32 次去噪 forward**)。" | |
| "评测集为 DEV 项目的 validation25:25 张 VBench-I2V 图 × 4 组双向相机动作 = 100 个视频," | |
| "seed 0。两列 VBench 都基于 `custom_input` 模式下的同一组 **Core5** 五个画质维度" | |
| "(subject_consistency、background_consistency、motion_smoothness、aesthetic_quality、" | |
| "imaging_quality,后者先除以 100):**原始**列是五项原始分的简单平均,口径与 " | |
| "HY-WorldPlay-DEV-Predictor 项目 experiment.md 第 8 节的 VBench Core5 一致,便于直接对照;" | |
| "**归一**列先按 VBench 官方 min-max 归一再平均," | |
| "即 `((sc−0.1462)/0.8538 + (bc−0.2615)/0.7385 + (ms−0.7060)/0.2915 + aq + iq) / 5`," | |
| "与 251 表的质量组口径一致。平均 P = 32 − Full。" | |
| "耗时列为去噪路径(32 次 forward)的 GPU 时间,不含每 chunk 一次的 context KV pass " | |
| "和文本 KV pass;加速比与同进程内同一 job 的 FFFF 配对。\n") | |
| doc.append(hy + "\n") | |
| lb = fmt_hy_table(LB_SUMMARY) | |
| if lb: | |
| doc.append("### LingBot-World-V2-1.3B-Causal-Fast(I2V,VBench-I2V-100)\n") | |
| doc.append("第四个基模:LingBot-World-V2 的 1.3B causal-fast 蒸馏自回归 I2V 世界模型(Wan2.1-1.3B block-causal " | |
| "架构,30 个 block,相机 Plucker 条件,480×832,16 fps,125 帧 = 8 chunk × 4 latent × 4 步 = " | |
| "**32 次去噪 forward**,KV 窗口 18 latent、sink 6)。评测协议与 HY-WorldPlay 完全相同:" | |
| "validation25 的 25 张图 × 4 组双向相机动作 = 100 个视频,seed 0,Core5 五维、原始/归一两列同上;" | |
| "相机动作把 HY 的逐 latent 位姿(前进 0.08 单位/latent、旋转 3°/latent,前半程正向、后半程反向)" | |
| "换算成 LingBot 需要的逐帧 OpenCV c2w 位姿(4 帧/latent),内参取模型 480×832 示例数据的 " | |
| "fx = fy ≈ 415.6。**这里的 `reuse_*` 复用的是 velocity 而不是 block 残差**:R 步完全不调用 DiT," | |
| "直接把上一次算出的 flow 预测当作本步预测(`x0 = x_t − σ_t·v_prev`),再照常 renoise;" | |
| "因此 R 步耗时≈0,FRFF/FRRF/FRRR 的 Full 精确为 24/16/8。三个缓存方法(TeaCache / TaylorSeer / MotionCache)按 `cachelib` 的同一套移植接在 30 个 block 的循环上(block 级重实现与官方 forward 逐位一致,`lb_test_exact.py`),操作点沿用 HY 的流程:TeaCache 的 rescale 多项式用 3 个 case × 125 帧标定(`lb_coeff.json`,一次多项式,R² 0.63);`x1.3` 由 compute fraction 二分得到(`lb_sweep_*.json`,61 帧);`x1.75` / `Fxxx_x2.85` 及其 `_c0FFFF` 变体在 61 帧、配对 FFFF 的实测加速比网格上定点(`lb_point_v2.json` / `lb_point_c0.json`,选点规则与 HY 相同)。naive 减步按 Self-Forcing 的规则取 4 步网格的均匀子集(4 步网格 t = 999/937/833/624;3 步 = 999/833/624、2 步 = 999/624、1 步 = 999)。`atc_chunk_*` / `disca_*` 为 Layer-17 Predictor(`lingbot-world-v2-a`,Causal-Forcing-a 的同款移植:复制教师 block 17 + 融合模块,预测 head 输入的 final hidden;离线集 = 100 组 HY 训练图 × 4 动作 × 7 chunk × 3 相邻转移 = 8400 个样本,Stage-1 监督蒸馏 2000 步、全局 batch 64、fusion lr 1e-4 / block lr 1e-5,未做 Stage-2);chunk 0 固定 FFFF,FPPF / FPPP 中 P 步锚定同 chunk 上一步的 hidden,FPF 只走 t = 999 / 937 / 624 三步(P 锚定 t = 999);P 列为 Predictor 调用次数,Full 为完整 DiT 次数,Predictor 按 1/30 次 forward 计入算量。耗时列为去噪路径的 GPU 时间,不含每 chunk " | |
| "一次的 context KV pass(t=0 的 KV 重写);加速比与同进程内同一 job 的 FFFF 配对。\n") | |
| doc.append(lb + "\n") | |
| doc.append("† 耗时来自该策略自己的生成运行(独占 GPU、单进程),与各基模 FFFF 首批记录相比得到加速比," | |
| "未做同进程配对计时;按约定不再做 FFFF 重计时。" | |
| + (("其中 " + "、".join(f"`{i}`({n}/{t} 条)" for i, n, t in PARTIAL_LATENCY) | |
| + " 的耗时只来自生成时计时未被破坏的那部分 prompt:其余记录的原始计时被一次中断且遭遇 GPU 争用的" | |
| "配对重计时覆盖,已在记录中标记 `latency_excluded` 并从耗时均值剔除,VBench 与像素指标仍用全部 251 条。") | |
| if PARTIAL_LATENCY else "") | |
| + "\n") | |
| doc.append("‡ 该行有部分记录在生成时 GPU 被其他进程短暂共用(Sep 7 的 `disca_s1000_*`:GPU 4/7 上的 gpu_burn 占位," | |
| "06:22–06:40,每策略约 84 条),这些记录事后在空闲 GPU 上只重跑策略路径重新计时(FFFF 不重跑,逐条核对算量一致," | |
| "旧值保留在记录的 `generation_run_latency`);其余记录为独占 GPU 的生成运行值。所有记录均为单进程计时。\n") | |
| doc.append("§ 像素指标以 FFFF 运行写出的 MP4 解码帧为参考(其余行以 MP4 编码前的原始帧为参考);同一批视频上实测差异:" | |
| "PSNR +0.10 dB、SSIM −0.024、LPIPS +0.012。`cf_teacache_x2_c0FFFF` 为混合(75/251 条用 MP4 参考)。\n") | |
| doc.append("¶ `hy_atc_s1_*` / `hy_disca_s1_*` 来自 HY-WorldPlay-DEV-Predictor 的 validation25 评测(ATC Stage-1," | |
| "`predictor_v4_atc-full_transport-global-chunk_…_4gpu_bs8x2_steps2000`,step 2000,FPPF/FPPP,chunk 0 四步全算——DEV 的 rollout 固定如此,Predictor 需要前一 chunk;FPPF = 18 full + 14 Predictor,FPPP = 11 + 21)," | |
| "耗时为该项目生成器的 host 端分段计时(`ar_step_transformer + ar_step_predictor`,即去噪路径,不含 context/history KV)," | |
| "与本表其它行的 CUDA event 计时口径接近但不完全相同;像素指标对照 DEV 项目 validation 的 FFFF 视频(与 `hy_ffff` 同源)。" | |
| "DisCa 为双 block Predictor(blocks [0,53],Sep 8 用 `--force_first_chunk_full` 重跑)。" | |
| "`hy_atc_s2_s<N>_*` 为同一 ATC Stage-1 再经 on-policy detached chunk+timestep FPPP rollout 微调(4 卡、每卡 1 条轨迹 × 累积 2、flr 5e-5 / blr 5e-6、2000 steps)后第 N 步的权重," | |
| "Sep 10 评测(DEV experiment.md 第 12 节),生成设置与 `hy_atc_s1_*` 相同。" | |
| "Predictor 步按 1/54(DisCa 2/54)次 forward 计入 Full。\n") | |
| doc.append("### 读数备注\n") | |
| doc.append("- 2.0× 是 `FxxF` 的算术上限(14/28),到了这一档 TeaCache / FlowCache / MotionCache " | |
| "全部退化成\"中间两步整体跳过、残差拷贝\",走的是同一条代码路径,所以三者数字一致;" | |
| "TaylorSeer 在 2.0× 上不同(逐模块拷贝 + 当前步调制),且略差。") | |
| doc.append("- `FxxF` 下 TaylorSeer 的一阶预测从未被触发:跳步前 chunk 内只算过 step 0," | |
| "差分导数不存在,实际走的是 order-0(拷贝)。`FFxx` 调度就是为了让预测真正生效。") | |
| doc.append("- 像素指标(PSNR 12–15 dB)与 VBench(掉 0–1.4 分)严重脱钩;在这个设定里 PSNR/SSIM " | |
| "只能当\"偏离 FFFF 的程度\"读,不是质量代理。") | |
| doc.append("- **第一个 chunk 不该被缓存。** `teacache_x2_c0FFFF` 与 `teacache_x2` 用同一个阈值," | |
| "唯一区别是 chunk 0 走满 FFFF 四步(算力 16/28 vs 14/28)。Self-Forcing 上 VBench " | |
| "从 78.7743 升到 79.7515(**+0.98**,把相对 FFFF 的 1.76 分差距砍掉 55%)," | |
| "像素保真度改善更大:PSNR 13.02→16.61(**+3.59 dB**)、SSIM 0.4222→0.6511、" | |
| "LPIPS 0.4854→0.1883(几乎减半)。逐维增益集中在外观类" | |
| "(imaging_quality +0.021、aesthetic_quality +0.021、subject_consistency +0.014、" | |
| "scene +0.011),dynamic_degree −0.014(画面更稳)。机理很直接:第一个 chunk 没有" | |
| "前序 chunk 可复用,且整段视频的外观都继承自它,缓存它等于把误差灌进后面所有 chunk。" | |
| "值得注意的是它同时**又快又好于** `teacache_x1.3`(1.788× vs 1.240×,79.75 vs 79.59," | |
| "PSNR 16.61 vs 15.16)——「首 chunk 全算 + 后续激进缓存」比「全程温和缓存」是更优的算力分配。") | |
| doc.append("- **首 chunk FFFF 对三种方法、三个档位都成立(SF,`_c0FFFF` 行,Sep 6)。** 同参数下 VBench " | |
| "一律上升:TeaCache 1.3× 79.59→80.39、1.6× 78.99→80.06(+1.07,1.353×)、`Fxxx` 3× 58.68→61.31;TaylorSeer 1.3× 79.83→80.44、" | |
| "1.75× 78.63→79.44、`Fxxx` 2.85× 60.30→65.54(**+5.2**);MotionCache 1.3× 79.23→79.97、" | |
| "1.75× 79.03→80.34(**+1.31**,已到 FFFF 的 80.54 只差 0.2)、`Fxxx` 2.85× 58.60→62.04。" | |
| "像素保真度更是整体抬升一个台阶:`FxxF` 各档 PSNR 从 13.0–15.2 dB 升到 16.7–20.4 dB、LPIPS 从 " | |
| "0.32–0.52 降到 0.09–0.21;`Fxxx` 档 PSNR 也从 11.7–11.9 升到 14.0–14.3。代价是首 chunk 多算 " | |
| "1–3 次 forward,实测加速比回落 0.08–0.6×(1.3× 档几乎不亏:1.24→1.16、1.30→1.26、1.14→1.13;" | |
| "3× 档亏最多:3.47→2.89、2.95→2.37、2.54→1.90)。两点值得写进论文:(i) `motioncache_x1.75_c0FFFF` " | |
| "在 1.445× 上达到 80.34 / PSNR 16.75,是 SF 上所有非 FFFF 配置里最接近 FFFF 的一档;(ii) 即便首 chunk " | |
| "全算,`Fxxx` 仍停在 61–66 分——最后一步的损失是逐 chunk 局部的,不是从首 chunk 传下来的," | |
| "所以两条规则相互独立:首 chunk 全算、最后一步全算。") | |
| doc.append("- **首 chunk FFFF 在 Causal-Forcing 上同样成立,但 1.3× 档没有余量。** CF 的 FFFF 是 81.17," | |
| "而三种方法的 1.3× 原版已经在 81.2–81.6(与 FFFF 持平或更高,属评测噪声),所以 `_c0FFFF` 在这一档 " | |
| "VBench 变化在 ±0.3 内(TeaCache 81.22→81.04、TaylorSeer 81.62→81.31、MotionCache 81.25→81.39);" | |
| "但只要原版有损失,首 chunk 全算就把损失补回来:TeaCache 2× 79.97→**81.18**(+1.21,1.789× 下回到 FFFF 的 81.17;" | |
| "PSNR 11.90→14.43),1.75× TaylorSeer 79.95→80.74、MotionCache 80.67→81.11(回到 FFFF 水平),`Fxxx` 档 TaylorSeer 60.96→65.46、MotionCache 59.58→64.08(各 **+4.5**)、" | |
| "TeaCache 65.02→65.66。像素保真度则和 SF 一样全面抬升:`FxxF` 各档 PSNR 12.0–13.2→14.4–16.5、" | |
| "LPIPS 0.34–0.52→0.14–0.27。SF/CF 两个基模合起来,16 组同参数对照中 VBench 14 升 2 平" | |
| "(2 平的都是原本已到 FFFF 的 CF 1.3× 档),PSNR 16/16 上升。") | |
| doc.append("- **naive 减步是很强的对照。** 在 Causal-Forcing 上按等算力配对(Full 完全相同):" | |
| "21/28 档 `naive3step` 81.40 vs `reuse_FRFF` 81.31,14/28 档 80.57 vs 80.04," | |
| "7/28 档 **75.52 vs 58.86**——直接少走几步全面不输于、低算力时远好于固定位置的残差复用," | |
| "因为 `FRRR` 只算首步、其余三步复用同一个残差,误差累积到崩溃。" | |
| "更关键的是 `naive2step`(14/28、80.57)优于所有缓存方法在同一算力下的成绩" | |
| "(`teacache_x2` / `motioncache_x2` 均为 79.97–79.99)," | |
| "即在这个基模上「直接少走两步」比「走满四步再缓存两步」更划算。") | |
| doc.append("- **HY-WorldPlay 上 naive 减步几乎免费,直接复用不是(Sep 6,Core5 原始分)。** `naive3step` 84.29、" | |
| "`naive2step` 84.26 与 FFFF 84.26 持平,`naive1step` 83.11(−1.1)却拿到 **3.90×**;像素上 " | |
| "naive3/2/1 的 PSNR 19.4 / 18.0 / 16.1。等算力对照:24/32 档 `naive3step` 84.29 ≈ `reuse_FRFF` 84.29 " | |
| "≈ `teacache_x1.3` 84.37;16/32 档 `naive2step` 84.26 ≈ `reuse_FRRF` 84.23;8/32 档 `naive1step` 83.11 " | |
| "vs `reuse_FRRR` 81.27(**+1.8**,PSNR 16.1 vs 15.0)。也就是说 HY 这类蒸馏 AR 模型对步数极不敏感" | |
| "(4 步→2 步无损、→1 步只掉 1 分),三种缓存方法在 `FxxF` 下做到的 1.5–1.7× 都被 `naive2step` 的 1.98× " | |
| "无损覆盖;缓存方法唯一还能争的是 2.5–2.9× 那一档,而那一档它们(81.3–82.9)也输给 `naive1step`(83.1,3.9×)。" | |
| "把 Self-Forcing/Causal-Forcing 上「naive 减步强于缓存」的结论推广到 HY 是成立的,而且更极端。") | |
| doc.append("- **首 chunk FFFF + naive 减步是 CF 上目前最强的算力分配(Sep 7)。** 后 6 个 chunk 减步、chunk 0 仍走 4 步:" | |
| "`naive3step_c0FFFF` 81.39(22/28,1.284×)、`naive2step_c0FFFF` **81.71**(16/28,1.807×,高于 FFFF 的 81.17)、" | |
| "`naive1step_c0FFFF` **79.15**(10/28,3.048×)。对比同算力的其它配置:16/28 档 `teacache_x2_c0FFFF` = " | |
| "`reuse_FRRF_c0FFFF` 81.18、`taylorseer_x1.75_c0FFFF`(17) 80.74;10/28 档所有 `Fxxx` 缓存方法 61–66、" | |
| "`reuse_FRRR_c0FFFF` 59.5,而 `naive1step_c0FFFF` 79.15 领先 13 分以上——原因同前:减步让最后一次预测仍在 " | |
| "t=1000 这一训练过的时间步上做(对 1 步就是唯一一步),而 `Fxxx` 是把最后一步换成过期残差。首 chunk FFFF 对 naive " | |
| "的增益随减步幅度放大:3 步 −0.01、2 步 +1.14、1 步 **+3.63**(75.52→79.15),像素指标也从 11.5–11.9 dB 抬到 14.1–14.4 dB。" | |
| "直接复用(reuse)在同算力下始终不如减步:22/28 档 81.34 vs 81.39,16/28 档 81.18 vs 81.71,10/28 档 59.5 vs 79.2。") | |
| doc.append("- **HY 上首 chunk FFFF 对基线的作用小得多(Sep 7)。** naive/reuse 六个 `_c0FFFF` 变体里,VBench 只有本来有损的两个动了:" | |
| "`naive1step` 83.11→83.63(+0.51,3.90×→3.33×)、`reuse_FRRR` 81.27→81.53(+0.26,3.77×→3.27×);" | |
| "已在 FFFF 水平的 3 步 / 2 步 / FRFF / FRRF 保持不变(±0.05)。像素指标仍全面上升(PSNR +1.6–2.4 dB,如 " | |
| "`naive2step` 18.0→19.7、`naive3step` 19.4→21.7)。这与 HY 是 I2V 一致:chunk 0 有输入图像做强条件," | |
| "外观不像 T2V 那样完全由首 chunk 决定,所以 SF/CF 上 +1~+3.6 的 VBench 增益在 HY 上只剩零点几分。" | |
| "ATC Stage-1 predictor(`atc_s1_fppf`,DEV 项目 validation25)在 1.67× 下 84.18 / PSNR 20.1," | |
| "像素保真度高于同速的 `teacache_x1.75`(PSNR 17.6)、与 `naive2step_c0FFFF`(1.87×,PSNR 19.7)相当。") | |
| doc.append("- **HY 缓存方法在 c0FFFF 下重新搜参后达到了目标档位(Sep 8,`*_x1.75_c0FFFF` / `*_Fxxx_x2.85_c0FFFF`)。** " | |
| "实测加速比 TeaCache 1.857× / 2.535×、TaylorSeer 1.704× / 2.870×、MotionCache 1.634× / 2.517×" | |
| "(之前为 1.54 / 2.51、1.68 / 2.52、1.53 / 2.49)。`FxxF` 档三者 VBench 全部保持 FFFF 水平(84.26–84.28 vs 84.26)," | |
| "像素指标比原版高 1–2 dB(PSNR 18.6 vs 16.9–17.6);但这一档 TeaCache 与 TaylorSeer 落到的都是「后 7 chunk 中间两步全跳」" | |
| "的量化上限点(Full = 18/32),与 `reuse_FRRF_c0FFFF` 算力等价、结果也几乎一样(84.28 / PSNR 18.6)," | |
| "只有 MotionCache 仍保留 27% 的 token 级活跃步。`Fxxx` 档 VBench 82.6 / 81.7 / 81.4,与原版持平或略低" | |
| "(TaylorSeer 82.43→81.69 是换了更激进的 interval 3.9 以换取 2.52×→2.87×),像素指标同样 +1.2–1.6 dB。" | |
| "同算力对照仍然是 naive 减步占优:16–18/32 档 `naive2step_c0FFFF` 84.26 / 1.87× / PSNR 19.7 不输任何缓存方法," | |
| "11/32 档 `naive1step_c0FFFF` 83.63 / 3.33× 高出所有 `Fxxx` 缓存点 1–2 分。ATC Stage-1 predictor " | |
| "`atc_s1_fppp` 在 2.59× 下 81.98 / PSNR 19.3——VBench 与 `Fxxx` 缓存点相当,像素保真度高 2–3 dB。" | |
| "DisCa Stage-1(双 block,blocks [0,53],同样 chunk 0 全算)`disca_s1_fppf` 84.17 / PSNR 19.7 / LPIPS 0.135,1.80×:" | |
| "VBench 与 ATC 持平,像素保真度介于 ATC(20.1 / 0.126)与 naive2step_c0FFFF(19.7 / 0.148)之间,高于残差复用类" | |
| "(18.6 / 0.167)。FPPP 档 `disca_s1_fppp` 81.98 / PSNR 19.0(3.00×)与 `atc_s1_fppp` 81.98 / 19.3(2.59×)持平," | |
| "仍低于 `naive1step_c0FFFF` 83.63。DisCa 的 step-1000 与 step-2000 checkpoint 几乎无差别" | |
| "(FPPF 84.15 vs 84.17,FPPP 82.02 vs 81.98,像素指标差 <0.1 dB)——HY 上 DisCa 在 1000 步已收敛," | |
| "与 CF 上 DisCa 1000→2000 步 FPPP +3.7 的情况不同。") | |
| doc.append("- **长视频(Sep 8,`*_14c_*` / `*_28c_*`:14 / 28 chunk = 165 / 333 帧,超过 21 帧训练上下文后 KV 走 21 帧滚动窗口)。** " | |
| "14 chunk 时 FFFF 自身从 81.17 降到 81.01(`14c_ffff`;subject_consistency 95.7→92.7、background 95.8→94.1," | |
| "scene 反而 56.0→61.3),说明滚动窗口下长程一致性本身在退化。ATC-chunk FPPF 在 14 chunk 上 1.766×:" | |
| "Stage-1 80.57(相对 `14c_ffff` −0.43,7 chunk 时是 +0.38)、Stage-2 **80.97**(−0.03);像素指标从 7 chunk 的 " | |
| "PSNR 14.6 / LPIPS 0.22 降到 12.8 / 0.34——Predictor 的误差随 chunk 累积(前 7 chunk 之后没有 FFFF 的「重锚定」)," | |
| "Stage-2(随机出口 DMD)在长视频上比 Stage-1 稳得多(VBench +0.40,主要是 subject/background consistency)。" | |
| "28 chunk(333 帧)时趋势放大:FFFF 自身掉到 **77.99**(subject 95.7→88.3、background 95.8→89.6、aesthetic 66.6→62.2——" | |
| "滚动窗口下的长程漂移是基模本身的问题),ATC Stage-1 FPPF 77.48(−0.51)、**Stage-2 78.65(+0.67,高于同长度 FFFF;" | |
| "subject 89.1 vs 88.3、background 91.3 vs 89.6)**,加速比 1.793×;像素指标继续下滑到 PSNR 11.0 / LPIPS 0.47–0.48。" | |
| "三种长度合起来:Stage-1 相对同长度 FFFF 为 +0.38 / −0.43 / −0.51,Stage-2 为 +0.08 / −0.03 / +0.67——" | |
| "随机出口 DMD 训练出来的 Predictor 不会随视频变长而退化,反而在 FFFF 自己漂移最严重的 28 chunk 上略优于 FFFF。") | |
| doc.append("- **长视频上的 checkpoint 扫描(Sep 11–12,CF 14 / 28 chunk,FPPF,chunk 0 FFFF;HY 16 / 32 chunk 进行中)。** " | |
| "对比 DisCa Stage-1 的 step 500 / 1000 / 2000、ATC Stage-2(随机出口 DMD,EMA)的 step 500 / 1000 / 2000 与 naive 2 步" | |
| "(`naive2step_<n>c`:chunk 0 四步全算,之后每 chunk 只走 t=1000 / 250,与 `cf_naive2step_c0FFFF` 同一子集)," | |
| "三者 Full 完全相同(14 chunk 30/56、28 chunk 58/112),参考帧读同长度 FFFF 的 MP4(§)。" | |
| "14 chunk:ATC Stage-2 s500 / s1000 / s2000 = 81.08 / **81.11** / 80.97(FFFF 81.01,1.77–1.78×)," | |
| "DisCa s500 / s1000 / s2000 = 80.12 / 80.51 / 80.60(1.82×),`naive2step_14c` 80.79(1.91×);" | |
| "28 chunk:ATC Stage-2 78.55 / **78.84** / 78.65(FFFF 77.99,1.80×),DisCa 75.46 / 77.40 / 77.87(1.84×)," | |
| "`naive2step_28c` 78.74(1.95×)。三点读数:(i) ATC Stage-2 三个 step 相差不到 0.15(14c)/ 0.3(28c)," | |
| "s500 就已经到达平台并且不低于同长度 FFFF——Stage-2 对长视频的增益来得很早,2000 步不是必要条件;" | |
| "(ii) DisCa 随步数单调上升,但 2000 步仍低于 FFFF 与 naive,且视频越长早期权重掉得越多(28c 的 s500 比 FFFF 低 2.5)——" | |
| "没有前一 chunk 通道的 Predictor 在长视频上误差累积更快;(iii) 等 Full 的 naive 2 步与最好的 ATC Stage-2 在 14c 只差 0.3、" | |
| "28c 持平(78.74 vs 78.84),像素指标也几乎相同(PSNR 12.6 vs 12.7–12.8、11.0 vs 11.1),而 naive 的实测加速比更高" | |
| "(Predictor 调用的真实开销高于按 1/30 forward 记的算量)——在 CF 的长视频上,ATC Stage-2 相对 naive 减步的优势只剩零点几分," | |
| "主要收益是不低于 FFFF 的一致性指标(subject / background consistency)。HY 的 16 / 32 chunk 行(64 / 128 latent," | |
| "相机动作等比拉长)生成中,完成后补入 HY 表。") | |
| doc.append("- **`FPF`(Sep 8):chunk 内只走 3 个时间步——t=1000 全算、t=750 一步 Predictor(锚定 t=1000,与训练一致)、跳过 t=500、" | |
| "t=250 全算。** ATC-chunk Stage-1 的 `atc_chunk_fpf` 在 **1.773×** 下 81.30(FFFF 81.17,同算力 16 full 的 `atc_chunk_fppf` " | |
| "81.55 / 1.697×,`naive3step` 81.40 / 1.327×),像素指标 PSNR 14.5 / LPIPS 0.24 与 FPPF 相当。也就是说少调用一次 " | |
| "Predictor、直接跳过一个时间步,质量几乎不变而更快——和 naive 减步同一逻辑:中间步的价值主要在于让最后一步有一个好的 " | |
| "起点,而不在于步数本身。") | |
| doc.append("- MotionCache 在 1.3×/1.6× 的实测加速比明显低于 compute 折算(差 9–11 个百分点):" | |
| "逐 token 选择与 selective forward 的簿记开销是真实存在的;`Fxxx` 下更甚,compute 3.0× 只换来 " | |
| "实测 1.85–1.95×。") | |
| doc.append("- **最后一步不能缓存。** 凡是让 step 3 走缓存的配置——`Fxxx` 全部、`FFxx` 在 ≥1.6× 时——" | |
| "VBench 掉 15–25 分:imaging/aesthetic 从 ~0.66 掉到 0.41–0.50,scene 从 ~0.57 掉到 0.03–0.37," | |
| "而 dynamic_degree 从 0.69–0.82 **升**到 0.89–0.96,即残留噪声没有被去掉、画面在抖。" | |
| "`FFxx ×1.3` 实际是 FFxF(最后一步仍算),质量与 `FxxF ×1.3` 相当。") | |
| doc.append("- TaylorSeer 的一阶预测在 `FFxx` 下确实被触发了,但 ≥1.6× 时把预测用在了最后一步," | |
| "\"预测 vs 拷贝\"与\"跳不跳最后一步\"在这组数据里是混在一起的;" | |
| "要单独检验预测本身,需要最后一步必算、中间步用一阶预测的调度(如 FFxF,上限 1.33×)。") | |
| doc.append("- `Fxxx` 下 TeaCache/FlowCache 的阈值在 3 条 prompt 上搜到 ~3.0× compute,251 条上实际为 " | |
| "3.4–3.6×(Self-Forcing)/ 2.8–2.9×(Causal-Forcing):indicator 跨 chunk 几乎不变," | |
| "可达点是 {1, 2, 4}× 的台阶,中间值靠少数 chunk 落在台阶另一侧凑出,随 prompt 集漂移。" | |
| "MotionCache(逐 token)和 TaylorSeer(确定性调度)不漂。\n") | |
| doc.append("## 2. 各策略的配置参数\n") | |
| doc.append("阈值类方法(TeaCache / FlowCache / MotionCache)的 `thresh` 作用在**重标定后**的累计相对 L1 距离上" | |
| "(`calibrate.py` 按 TeaCache 的拟合流程得到的 1 次多项式,见下方系数),indicator 为" | |
| "时间步调制后的噪声输入(TeaCache 原始定义,非 Wan 移植的 `e0`)。" | |
| "TaylorSeer 的 `interval` 为平均刷新间隔,小数部分用 Bresenham 交替在相邻整数间隔间实现," | |
| "`max_order=1`。固定超参:FlowCache `group_size=1`(3 帧组/chunk);" | |
| "MotionCache `weight_norm=mean`、`min_update_ratio=0`、无 temporal consistency。" | |
| "\"搜索时 Full\"是定参数时在搜索/配对 prompt 上的 compute,251 条上的实际值见第 1 节。\n") | |
| for base, (table, coeff) in config_tables().items(): | |
| doc.append(f"### {BASE_LABEL[base]}\n") | |
| if coeff: | |
| doc.append(f"重标定多项式系数(最高次在前):`{', '.join(f'{c:.6g}' for c in coeff)}`\n") | |
| doc.append(table + "\n") | |
| hy_cfg = hy_config_table() | |
| if hy_cfg: | |
| doc.append("### HY-WorldPlay\n") | |
| doc.append(hy_cfg + "\n") | |
| lb_cfg = lb_config_table() | |
| if lb_cfg and os.path.exists(LB_SUMMARY): | |
| doc.append("### LingBot-World-V2-1.3B-Causal-Fast\n") | |
| doc.append(lb_cfg + "\n") | |
| if pending: | |
| doc.append("## 3. 新调度下的操作点(已搜索,待 251 评测)\n") | |
| doc.append("参数由 `sweep.py --schedule ...` 在 3 条 prompt 上按 compute fraction 搜索得到;" | |
| "\"配对实测加速比\"来自 3 对 A/B 计时,仅供参考,不与上表的 251-prompt 耗时直接比较。" | |
| "质量列在跑完 Extended-251 评测后由 `experiment_md.py` 自动填入上表。\n") | |
| for base in ["self_forcing", "causal_forcing"]: | |
| if base in pending: | |
| doc.append(f"### {BASE_LABEL[base]}\n") | |
| doc.append(fmt_pending_table(pending[base]) + "\n") | |
| with open(OUT, "w") as f: | |
| # Footnotes for markers no row uses any more are dropped. | |
| body = "\n".join(doc) | |
| for mark in ("†", "‡", "§", "¶"): | |
| if not any(f" {mark} |" in line for line in body.splitlines()): | |
| doc = [d for d in doc if not d.startswith(mark + " ")] | |
| f.write("\n".join(doc)) | |
| print(f"wrote {OUT}") | |
| if __name__ == "__main__": | |
| main() | |