comparison / experiment_md.py
Cccccz's picture
Add files using upload-large-folder tool
38a51ff verified
Raw History Blame Contribute Delete
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()