#!/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_*_.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 _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("| `c_ffff` / `naive2step_c_c0FFFF`(n = 16 / 32) | 长视频 FFFF 参考 / naive 2 步(首 chunk FFFF),253 / 509 帧 | FFFF / FF | — | — | — | " "hycache,同进程配对 FFFF |") lines.append("| `atc_s2_s{500,1000,2000}_c_fppf_c0FFFF`、`disca_s{500,1000,2000}_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`,¶),像素指标对照 `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` 是搜索时的目标加速比,实际 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_*` 为同一 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_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()