Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +70 -0
- assets/lb_actions/a_d_125f/intrinsics.npy +3 -0
- assets/lb_actions/a_d_125f/poses.npy +3 -0
- assets/lb_actions/left_right_125f/intrinsics.npy +3 -0
- assets/lb_actions/left_right_125f/poses.npy +3 -0
- assets/lb_actions/up_down_125f/intrinsics.npy +3 -0
- assets/lb_actions/up_down_125f/poses.npy +3 -0
- assets/lb_actions/w_s_125f/intrinsics.npy +3 -0
- assets/lb_actions/w_s_125f/poses.npy +3 -0
- assets/lb_actions/w_s_61f/intrinsics.npy +3 -0
- assets/lb_actions/w_s_61f/poses.npy +3 -0
- cachelib/selective.py +148 -0
- eval/aggregate.py +331 -0
- eval/build_mapping.py +139 -0
- eval/generate_eval.py +259 -0
- eval/generate_predictor_eval.py +532 -0
- eval/pixel_metrics.py +85 -0
- eval/retime_eval.py +188 -0
- eval/retime_until_clean.sh +96 -0
- eval/run_all_phases.sh +23 -0
- eval/run_generation.sh +33 -0
- eval/run_predictor_eval.sh +39 -0
- eval/run_predictor_eval_s2.sh +59 -0
- eval/run_predictor_retime_s2.sh +21 -0
- eval/run_predictor_vbench_phase.sh +13 -0
- eval/run_retime.sh +42 -0
- eval/run_vbench.py +163 -0
- eval/run_vbench_all.sh +45 -0
- eval/setup_vbench_models.py +29 -0
- eval/strategies.py +82 -0
- eval/test_protocol.py +172 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/000.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/001.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/002.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/003.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/004.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/005.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/006.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/007.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/008.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/009.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/010.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/011.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/012.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/013.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/014.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/015.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/016.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/017.mp4 +3 -0
- eval_out/generated_videos/cf_ffff/overall_consistency/019.mp4 +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,73 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/041.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/044.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/078.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/064.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/000.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/049.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/005.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/007.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/001.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/002.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/003.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/010.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/012.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/083.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/014.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/013.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/009.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/015.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/047.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/016.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/011.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/021.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/004.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/062.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/008.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/019.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 62 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/023.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 63 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/061.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 64 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/022.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/028.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 66 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/027.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/017.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/054.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/034.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 70 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/033.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 71 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/032.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 72 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/092.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 73 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/037.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 74 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/026.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 75 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/035.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 76 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/040.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 77 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/091.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 78 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/020.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 79 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/039.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 80 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/042.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 81 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/006.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 82 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/024.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 83 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/050.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 84 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/048.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 85 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/059.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 86 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/060.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 87 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/057.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 88 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/088.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 89 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/051.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 90 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/053.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 91 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/090.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 92 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/052.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 93 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/058.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 94 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/055.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 95 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/056.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 96 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/089.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 97 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/079.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 98 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/063.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 99 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/030.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 100 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/068.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 101 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/066.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 102 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/065.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 103 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/070.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 104 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/067.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 105 |
+
eval_out/generated_videos/cf_ffff/overall_consistency/072.mp4 filter=lfs diff=lfs merge=lfs -text
|
assets/lb_actions/a_d_125f/intrinsics.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a526a861f16b3bdd64d12b1946d2438925b0f68d88822599fc1d6b14a324d905
|
| 3 |
+
size 2128
|
assets/lb_actions/a_d_125f/poses.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f4cebbc48954133ce27c8adc7c71327f3ab6e6b8f1d735441dc6931bb931b5a9
|
| 3 |
+
size 8128
|
assets/lb_actions/left_right_125f/intrinsics.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a526a861f16b3bdd64d12b1946d2438925b0f68d88822599fc1d6b14a324d905
|
| 3 |
+
size 2128
|
assets/lb_actions/left_right_125f/poses.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03161c72fae5167c67aed4e3fa61cddb4dee23f008712d1ebc6a73f4848c631f
|
| 3 |
+
size 8128
|
assets/lb_actions/up_down_125f/intrinsics.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a526a861f16b3bdd64d12b1946d2438925b0f68d88822599fc1d6b14a324d905
|
| 3 |
+
size 2128
|
assets/lb_actions/up_down_125f/poses.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f9b64db0d0cea64c0b9b587f065bd6c2d0d76a1512f0ba128dcb4b41b75c04c0
|
| 3 |
+
size 8128
|
assets/lb_actions/w_s_125f/intrinsics.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a526a861f16b3bdd64d12b1946d2438925b0f68d88822599fc1d6b14a324d905
|
| 3 |
+
size 2128
|
assets/lb_actions/w_s_125f/poses.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f0624f5ca69206ecd7045c2f2b863b8e80bb8b5792d6b09f67370dab41d0dbb5
|
| 3 |
+
size 8128
|
assets/lb_actions/w_s_61f/intrinsics.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d5b972022c390258bd820986b146a9cfb86654b84be31ef161cd5c51b0ff746b
|
| 3 |
+
size 1104
|
assets/lb_actions/w_s_61f/poses.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:423844e4fd3f7900c8e07acf72544399c9ec1efe2f0ffe2243e0c66a64c56be3
|
| 3 |
+
size 4032
|
cachelib/selective.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Selective-token forward for the block-causal Wan DiT.
|
| 2 |
+
|
| 3 |
+
MotionCache (token granularity) and FlowCache (frame-group granularity) both need
|
| 4 |
+
to run the transformer stack on a *subset* of the current chunk's tokens while the
|
| 5 |
+
remaining tokens keep the activations cached from the previous computed step.
|
| 6 |
+
|
| 7 |
+
For a KV-cached causal model that means, per self-attention layer:
|
| 8 |
+
* compute q/k/v only for the selected rows,
|
| 9 |
+
* scatter the fresh k/v into the slots the current chunk owns in the KV cache,
|
| 10 |
+
leaving the unselected slots holding the previous step's k/v,
|
| 11 |
+
* attend the selected queries against the *full* cache (history + current chunk).
|
| 12 |
+
|
| 13 |
+
This mirrors ``forward_selective`` in
|
| 14 |
+
``MotionCache4SkyReels-V2/skyreels_v2_infer/modules/transformer_motion.py``,
|
| 15 |
+
adapted from SkyReels' persistent ``k_cache/v_cache`` buffers to Self-Forcing's
|
| 16 |
+
rolling ``kv_cache[block]`` dict.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import math
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
|
| 23 |
+
from wan.modules.attention import attention
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def build_causal_freqs(grid_sizes, freqs, start_frame):
|
| 27 |
+
"""Return the per-token RoPE multipliers [seq_len, 1, c] for one chunk.
|
| 28 |
+
|
| 29 |
+
Same construction as ``causal_rope_apply`` in ``wan/modules/causal_model.py``,
|
| 30 |
+
factored out so it can be indexed by a token subset and reused across layers.
|
| 31 |
+
"""
|
| 32 |
+
c = freqs.size(1)
|
| 33 |
+
parts = freqs.split([c - 2 * (c // 3), c // 3, c // 3], dim=1)
|
| 34 |
+
f, h, w = grid_sizes[0].tolist()
|
| 35 |
+
seq_len = f * h * w
|
| 36 |
+
freqs_i = torch.cat(
|
| 37 |
+
[
|
| 38 |
+
parts[0][start_frame:start_frame + f].view(f, 1, 1, -1).expand(f, h, w, -1),
|
| 39 |
+
parts[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
|
| 40 |
+
parts[2][:w].view(1, 1, w, -1).expand(f, h, w, -1),
|
| 41 |
+
],
|
| 42 |
+
dim=-1,
|
| 43 |
+
).reshape(seq_len, 1, -1)
|
| 44 |
+
return freqs_i
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def rope_apply_rows(x, freqs_rows):
|
| 48 |
+
"""Apply precomputed RoPE multipliers to x [B, S, n, d] given freqs [S, 1, d/2]."""
|
| 49 |
+
b, s, n, _ = x.shape
|
| 50 |
+
x_c = torch.view_as_complex(x.to(torch.float64).reshape(b, s, n, -1, 2))
|
| 51 |
+
out = torch.view_as_real(x_c * freqs_rows.unsqueeze(0)).flatten(3)
|
| 52 |
+
return out.type_as(x)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def self_attn_selective(attn, x_sel, sel_idx, freqs_rows, kv_cache,
|
| 56 |
+
current_start, block_seq_len):
|
| 57 |
+
"""Self-attention over a token subset, refreshing only those KV slots.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
attn: a ``CausalWanSelfAttention`` module.
|
| 61 |
+
x_sel: [B, n_sel, C] modulated hidden states for the selected tokens.
|
| 62 |
+
sel_idx: LongTensor [n_sel], token offsets inside the current chunk.
|
| 63 |
+
freqs_rows: [block_seq_len, 1, d/2] RoPE multipliers for the whole chunk.
|
| 64 |
+
kv_cache: this layer's ``{"k", "v", "global_end_index", "local_end_index"}``.
|
| 65 |
+
current_start: token offset of the chunk in global sequence coordinates.
|
| 66 |
+
block_seq_len: number of tokens the chunk owns (all frames of the chunk).
|
| 67 |
+
"""
|
| 68 |
+
b, n_sel = x_sel.shape[:2]
|
| 69 |
+
n, d = attn.num_heads, attn.head_dim
|
| 70 |
+
|
| 71 |
+
q = attn.norm_q(attn.q(x_sel)).view(b, n_sel, n, d)
|
| 72 |
+
k = attn.norm_k(attn.k(x_sel)).view(b, n_sel, n, d)
|
| 73 |
+
v = attn.v(x_sel).view(b, n_sel, n, d)
|
| 74 |
+
|
| 75 |
+
rows = freqs_rows[sel_idx]
|
| 76 |
+
roped_q = rope_apply_rows(q, rows)
|
| 77 |
+
roped_k = rope_apply_rows(k, rows).type_as(v)
|
| 78 |
+
|
| 79 |
+
# Index arithmetic must use the *full* chunk length: the chunk still occupies
|
| 80 |
+
# the same KV slots, we are only refreshing some of them.
|
| 81 |
+
current_end = current_start + block_seq_len
|
| 82 |
+
local_end_index = (kv_cache["local_end_index"].item() + current_end
|
| 83 |
+
- kv_cache["global_end_index"].item())
|
| 84 |
+
local_start_index = local_end_index - block_seq_len
|
| 85 |
+
|
| 86 |
+
slots = local_start_index + sel_idx
|
| 87 |
+
kv_cache["k"].index_copy_(1, slots, roped_k)
|
| 88 |
+
kv_cache["v"].index_copy_(1, slots, v)
|
| 89 |
+
|
| 90 |
+
lo = max(0, local_end_index - attn.max_attention_size)
|
| 91 |
+
out = attention(roped_q, kv_cache["k"][:, lo:local_end_index],
|
| 92 |
+
kv_cache["v"][:, lo:local_end_index])
|
| 93 |
+
|
| 94 |
+
kv_cache["global_end_index"].fill_(current_end)
|
| 95 |
+
kv_cache["local_end_index"].fill_(local_end_index)
|
| 96 |
+
|
| 97 |
+
return attn.o(out.flatten(2))
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def block_forward_selective(block, x_sel, sel_idx, frame_of_sel, e0, freqs_rows,
|
| 101 |
+
context, kv_cache, crossattn_cache,
|
| 102 |
+
current_start, block_seq_len):
|
| 103 |
+
"""One ``CausalWanAttentionBlock`` evaluated on a token subset.
|
| 104 |
+
|
| 105 |
+
``frame_of_sel`` maps each selected token to its frame index inside the chunk,
|
| 106 |
+
so the per-frame AdaLN modulation can be gathered for the selected rows only.
|
| 107 |
+
"""
|
| 108 |
+
# e0: [B, F, 6, C] -> six tensors of [B, F, 1, C]; gather per selected token.
|
| 109 |
+
e = (block.modulation.unsqueeze(1) + e0).chunk(6, dim=2)
|
| 110 |
+
e = [t.squeeze(2)[:, frame_of_sel] for t in e] # each [B, n_sel, C]
|
| 111 |
+
|
| 112 |
+
y = self_attn_selective(
|
| 113 |
+
block.self_attn,
|
| 114 |
+
block.norm1(x_sel) * (1 + e[1]) + e[0],
|
| 115 |
+
sel_idx, freqs_rows, kv_cache, current_start, block_seq_len,
|
| 116 |
+
)
|
| 117 |
+
x_sel = x_sel + y * e[2]
|
| 118 |
+
|
| 119 |
+
x_sel = x_sel + block.cross_attn(block.norm3(x_sel), context, None,
|
| 120 |
+
crossattn_cache=crossattn_cache)
|
| 121 |
+
y = block.ffn(block.norm2(x_sel) * (1 + e[4]) + e[3])
|
| 122 |
+
x_sel = x_sel + y * e[5]
|
| 123 |
+
return x_sel
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def run_blocks_selective(model, x, sel_idx, e0, kwargs, kv_cache, crossattn_cache,
|
| 127 |
+
current_start, cache_start, grid_sizes):
|
| 128 |
+
"""Run all transformer blocks on ``x[:, sel_idx]`` and return the updated rows.
|
| 129 |
+
|
| 130 |
+
Returns a tensor [B, n_sel, C] holding the new hidden states for the selected
|
| 131 |
+
tokens (the caller merges them into the cached full-length tensor).
|
| 132 |
+
"""
|
| 133 |
+
f, h, w = grid_sizes[0].tolist()
|
| 134 |
+
frame_seqlen = h * w
|
| 135 |
+
block_seq_len = f * frame_seqlen
|
| 136 |
+
frame_of_sel = torch.div(sel_idx, frame_seqlen, rounding_mode="floor")
|
| 137 |
+
|
| 138 |
+
freqs_rows = build_causal_freqs(grid_sizes, kwargs["freqs"],
|
| 139 |
+
current_start // frame_seqlen)
|
| 140 |
+
|
| 141 |
+
x_sel = x[:, sel_idx]
|
| 142 |
+
for i, block in enumerate(model.blocks):
|
| 143 |
+
x_sel = block_forward_selective(
|
| 144 |
+
block, x_sel, sel_idx, frame_of_sel, e0, freqs_rows,
|
| 145 |
+
kwargs["context"], kv_cache[i], crossattn_cache[i],
|
| 146 |
+
current_start, block_seq_len,
|
| 147 |
+
)
|
| 148 |
+
return x_sel
|
eval/aggregate.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Final summary for the Self-Forcing Extended-251 Full Evaluation.
|
| 3 |
+
|
| 4 |
+
Implements protocol sections 7 (normalize + aggregate), 8.3 (pixel aggregation),
|
| 5 |
+
9.4 (matched-FFFF speedup) and 10 (output tables), plus the section 11/15
|
| 6 |
+
completeness gate: a strategy that is not a full 251 is never summarised.
|
| 7 |
+
|
| 8 |
+
python eval/aggregate.py --out-root eval_out
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import re
|
| 13 |
+
import csv
|
| 14 |
+
import glob
|
| 15 |
+
import hashlib
|
| 16 |
+
import json
|
| 17 |
+
import math
|
| 18 |
+
import os
|
| 19 |
+
import platform
|
| 20 |
+
import sys
|
| 21 |
+
from collections import defaultdict
|
| 22 |
+
|
| 23 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 24 |
+
sys.path.insert(0, ROOT)
|
| 25 |
+
|
| 26 |
+
MAPPING = os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json")
|
| 27 |
+
|
| 28 |
+
DIMENSIONS = ["subject_consistency", "background_consistency", "motion_smoothness",
|
| 29 |
+
"dynamic_degree", "aesthetic_quality", "imaging_quality", "scene",
|
| 30 |
+
"overall_consistency"]
|
| 31 |
+
|
| 32 |
+
# Protocol section 7.1 -- fixed empirical ranges, never re-estimated from the run.
|
| 33 |
+
NORMALIZE_RANGE = {
|
| 34 |
+
"subject_consistency": (0.1462, 1.0),
|
| 35 |
+
"background_consistency": (0.2615, 1.0),
|
| 36 |
+
"motion_smoothness": (0.7060, 0.9975),
|
| 37 |
+
"dynamic_degree": (0.0, 1.0),
|
| 38 |
+
"aesthetic_quality": (0.0, 1.0),
|
| 39 |
+
"imaging_quality": (0.0, 1.0),
|
| 40 |
+
"scene": (0.0, 0.8222),
|
| 41 |
+
"overall_consistency": (0.0, 0.3640),
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
REFERENCE_OF = {"sf": "sf_ffff", "cf": "cf_ffff", "cfa": "cfa_ffff"}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def normalize(raw):
|
| 48 |
+
out = {}
|
| 49 |
+
for d, v in raw.items():
|
| 50 |
+
lo, hi = NORMALIZE_RANGE[d]
|
| 51 |
+
out[d] = (v - lo) / (hi - lo) # protocol does not clip
|
| 52 |
+
return out
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def quality_score(n):
|
| 56 |
+
return (n["subject_consistency"] + n["background_consistency"]
|
| 57 |
+
+ n["motion_smoothness"] + 0.5 * n["dynamic_degree"]
|
| 58 |
+
+ n["aesthetic_quality"] + n["imaging_quality"]) / 5.5
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def semantic_score(n):
|
| 62 |
+
return (n["scene"] + n["overall_consistency"]) / 2.0
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def selected_score(q, s):
|
| 66 |
+
return (4.0 * q + s) / 5.0
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def sha256(path):
|
| 70 |
+
h = hashlib.sha256()
|
| 71 |
+
with open(path, "rb") as f:
|
| 72 |
+
for chunk in iter(lambda: f.read(1 << 20), b""):
|
| 73 |
+
h.update(chunk)
|
| 74 |
+
return h.hexdigest()
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def load_per_prompt(out_root, strategy):
|
| 78 |
+
recs = []
|
| 79 |
+
for p in sorted(glob.glob(os.path.join(out_root, "per_prompt", strategy, "*.json"))):
|
| 80 |
+
with open(p) as f:
|
| 81 |
+
r = json.load(f)
|
| 82 |
+
if r.get("status") == "complete":
|
| 83 |
+
recs.append(r)
|
| 84 |
+
return recs
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def main():
|
| 88 |
+
ap = argparse.ArgumentParser()
|
| 89 |
+
ap.add_argument("--out-root", default="eval_out")
|
| 90 |
+
ap.add_argument("--expect", type=int, default=251)
|
| 91 |
+
ap.add_argument("--allow-incomplete", action="store_true",
|
| 92 |
+
help="Report partial strategies instead of refusing (diagnostics only)")
|
| 93 |
+
ap.add_argument("--latency-subsets", default=os.path.join(ROOT, "results/latency_subsets.json"),
|
| 94 |
+
help="JSON {strategy_regex: {shard, num_shards, note}}: time these "
|
| 95 |
+
"strategies on one prompt shard only (the one that ran on an "
|
| 96 |
+
"uncontended GPU); quality still uses every record")
|
| 97 |
+
ap.add_argument("--only", default=None,
|
| 98 |
+
help="Regex: summarise only strategies whose name matches (other "
|
| 99 |
+
"strategies in per_prompt/ are ignored, e.g. while they are "
|
| 100 |
+
"still being generated)")
|
| 101 |
+
args = ap.parse_args()
|
| 102 |
+
|
| 103 |
+
out_root = (args.out_root if os.path.isabs(args.out_root)
|
| 104 |
+
else os.path.join(ROOT, args.out_root))
|
| 105 |
+
|
| 106 |
+
with open(MAPPING) as f:
|
| 107 |
+
mapping = json.load(f)
|
| 108 |
+
|
| 109 |
+
strategies = sorted(os.path.basename(p) for p in
|
| 110 |
+
glob.glob(os.path.join(out_root, "per_prompt", "*")))
|
| 111 |
+
if args.only:
|
| 112 |
+
strategies = [s for s in strategies if re.search(args.only, s)]
|
| 113 |
+
if not strategies:
|
| 114 |
+
print("no per-prompt records found")
|
| 115 |
+
return 1
|
| 116 |
+
|
| 117 |
+
per_strategy, problems = {}, []
|
| 118 |
+
for s in strategies:
|
| 119 |
+
recs = load_per_prompt(out_root, s)
|
| 120 |
+
if len(recs) != args.expect:
|
| 121 |
+
problems.append(f"{s}: {len(recs)} complete records, expected {args.expect}")
|
| 122 |
+
per_strategy[s] = recs
|
| 123 |
+
|
| 124 |
+
if problems and not args.allow_incomplete:
|
| 125 |
+
print("REFUSING TO SUMMARISE -- incomplete strategies (protocol 11.9):")
|
| 126 |
+
for p in problems:
|
| 127 |
+
print(" -", p)
|
| 128 |
+
return 1
|
| 129 |
+
|
| 130 |
+
# --- latency, matched to each base model's own FFFF -----------------------
|
| 131 |
+
mean_latency, mean_context, timed_records = {}, {}, {}
|
| 132 |
+
for s, recs in per_strategy.items():
|
| 133 |
+
# Records flagged latency_excluded keep their pixel metrics but carry no
|
| 134 |
+
# usable timing (see their latency_note); they stay out of the means.
|
| 135 |
+
lat = [r for r in recs if not r.get("latency_excluded")]
|
| 136 |
+
if not lat:
|
| 137 |
+
continue
|
| 138 |
+
timed_records[s] = lat
|
| 139 |
+
mean_latency[s] = sum(r["policy_latency_ms"] for r in lat) / len(lat)
|
| 140 |
+
mean_context[s] = sum(r["excluded_context_kv_latency_ms"] for r in lat) / len(lat)
|
| 141 |
+
|
| 142 |
+
# Strategies generated while another job shared the GPU are timed only on the
|
| 143 |
+
# prompt shard that ran alone (sharding is rows[shard::num_shards] over the
|
| 144 |
+
# mapping order, see generate_eval.py); their FFFF reference is restricted to
|
| 145 |
+
# the same prompts so the ratio stays paired.
|
| 146 |
+
subsets = {}
|
| 147 |
+
if args.latency_subsets and os.path.exists(args.latency_subsets):
|
| 148 |
+
with open(args.latency_subsets) as f:
|
| 149 |
+
subsets = json.load(f)
|
| 150 |
+
with open(os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json")) as f:
|
| 151 |
+
mapping_rows = json.load(f)["rows"]
|
| 152 |
+
subset_of = {}
|
| 153 |
+
for s in per_strategy:
|
| 154 |
+
for pat, spec in subsets.items():
|
| 155 |
+
if re.search(pat, s):
|
| 156 |
+
keep = {r["global_index"] for r in mapping_rows[spec["shard"]::spec["num_shards"]]}
|
| 157 |
+
subset_of[s] = (keep, spec)
|
| 158 |
+
break
|
| 159 |
+
|
| 160 |
+
rows = []
|
| 161 |
+
for s, recs in per_strategy.items():
|
| 162 |
+
if not recs:
|
| 163 |
+
continue
|
| 164 |
+
ref = recs[0].get("reference_strategy") or REFERENCE_OF[s.split("_")[0]]
|
| 165 |
+
if ref not in mean_latency:
|
| 166 |
+
problems.append(f"{s}: reference {ref} missing")
|
| 167 |
+
continue
|
| 168 |
+
|
| 169 |
+
# Ratio of means (protocol 9.4). Equal sample counts make this the same
|
| 170 |
+
# as the ratio of sums; a mean of per-prompt percentages is not used.
|
| 171 |
+
# A strategy whose records carry the FFFF latency measured in the same
|
| 172 |
+
# process (retime_eval.py) is compared against that, not against the
|
| 173 |
+
# FFFF run's own records: contention differs between runs, and only a
|
| 174 |
+
# same-run pairing is "matched FFFF" in the protocol's sense.
|
| 175 |
+
timed = timed_records[s]
|
| 176 |
+
matched = [r.get("matched_ffff_policy_latency_ms") for r in timed]
|
| 177 |
+
strat_latency = mean_latency[s]
|
| 178 |
+
latency_n = len(timed)
|
| 179 |
+
if s in subset_of:
|
| 180 |
+
keep, spec = subset_of[s]
|
| 181 |
+
sub = [r for r in recs if r["global_index"] in keep]
|
| 182 |
+
ref_sub = [r for r in per_strategy[ref] if r["global_index"] in keep]
|
| 183 |
+
assert sub and ref_sub, f"{s}: empty latency subset"
|
| 184 |
+
strat_latency = sum(r["policy_latency_ms"] for r in sub) / len(sub)
|
| 185 |
+
ref_latency = sum(r["policy_latency_ms"] for r in ref_sub) / len(ref_sub)
|
| 186 |
+
latency_n = len(sub)
|
| 187 |
+
latency_source = (f"generation run, shard {spec['shard']}/{spec['num_shards']} only "
|
| 188 |
+
f"({len(sub)} prompts; {spec.get('note', '')})")
|
| 189 |
+
elif all(m is not None for m in matched):
|
| 190 |
+
ref_latency = sum(matched) / len(matched)
|
| 191 |
+
latency_source = "paired retime (same process as FFFF)"
|
| 192 |
+
elif all(r.get("latency_retimed") for r in recs):
|
| 193 |
+
ref_latency = mean_latency[ref]
|
| 194 |
+
latency_source = ("strategy retime: all records from one denoise-only pass on an idle GPU "
|
| 195 |
+
"(FFFF from its own records)")
|
| 196 |
+
elif any(r.get("latency_retimed") for r in recs):
|
| 197 |
+
n_re = sum(1 for r in recs if r.get("latency_retimed"))
|
| 198 |
+
ref_latency = mean_latency[ref]
|
| 199 |
+
latency_source = (f"generation run; {n_re}/{len(recs)} records re-timed "
|
| 200 |
+
"(strategy-only denoise rerun on an idle GPU; FFFF from its own records)")
|
| 201 |
+
else:
|
| 202 |
+
ref_latency = mean_latency[ref]
|
| 203 |
+
latency_source = "generation run (FFFF from its own records)"
|
| 204 |
+
if len(timed) < len(recs):
|
| 205 |
+
latency_source += (f"; {len(timed)}/{len(recs)} prompts with intact timing "
|
| 206 |
+
"(latency_excluded records dropped)")
|
| 207 |
+
speedup_pct = 100.0 * (1.0 - strat_latency / ref_latency)
|
| 208 |
+
|
| 209 |
+
# PSNR from the mean of per-prompt mean MSE, then converted once (8.3).
|
| 210 |
+
mses = [r["pixel_metrics_vs_ffff"]["mean_mse"] for r in recs]
|
| 211 |
+
mean_mse = sum(mses) / len(mses)
|
| 212 |
+
psnr = -10.0 * math.log10(max(mean_mse, 1e-12))
|
| 213 |
+
ssim = sum(r["pixel_metrics_vs_ffff"]["ssim"] for r in recs) / len(recs)
|
| 214 |
+
lpips = sum(r["pixel_metrics_vs_ffff"]["lpips"] for r in recs) / len(recs)
|
| 215 |
+
frames = {r["pixel_metrics_vs_ffff"]["num_frames"] for r in recs}
|
| 216 |
+
if frames != {81}:
|
| 217 |
+
problems.append(f"{s}: pixel metrics used frame counts {sorted(frames)}, expected 81")
|
| 218 |
+
|
| 219 |
+
row = {
|
| 220 |
+
"strategy": s,
|
| 221 |
+
"base_model": recs[0]["base_model"],
|
| 222 |
+
"method": recs[0]["method"],
|
| 223 |
+
"target_speedup": recs[0]["target_speedup"],
|
| 224 |
+
"schedule": recs[0].get("schedule"),
|
| 225 |
+
"num_inference_steps": recs[0].get("num_inference_steps"),
|
| 226 |
+
"num_videos": len(recs),
|
| 227 |
+
"mp4_reference_records": sum(1 for r in recs if r.get("reference_source") == "ffff_mp4"),
|
| 228 |
+
"policy_latency_ms": strat_latency,
|
| 229 |
+
"latency_num_videos": latency_n,
|
| 230 |
+
"speedup_percent_vs_ffff": 0.0 if s == ref else speedup_pct,
|
| 231 |
+
"latency_ratio_vs_ffff": strat_latency / ref_latency,
|
| 232 |
+
"matched_ffff_policy_latency_ms": ref_latency,
|
| 233 |
+
"latency_source": latency_source,
|
| 234 |
+
"excluded_context_kv_latency_ms": mean_context[s],
|
| 235 |
+
"psnr": psnr,
|
| 236 |
+
"ssim": ssim,
|
| 237 |
+
"lpips": lpips,
|
| 238 |
+
"mean_compute_equivalent_forwards": (
|
| 239 |
+
sum(r["cache_diagnostics"]["compute_equivalent_forwards"] for r in recs)
|
| 240 |
+
/ len(recs)),
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
score_path = os.path.join(out_root, "vbench", "scores", f"{s}.json")
|
| 244 |
+
if os.path.exists(score_path):
|
| 245 |
+
with open(score_path) as f:
|
| 246 |
+
sc = json.load(f)
|
| 247 |
+
raw = {d: sc["raw"][d] for d in DIMENSIONS if d in sc["raw"]}
|
| 248 |
+
if len(raw) == len(DIMENSIONS):
|
| 249 |
+
bad = {d: v for d, v in raw.items() if not (0.0 <= v <= 1.0)}
|
| 250 |
+
if bad:
|
| 251 |
+
problems.append(f"{s}: raw scores outside [0,1]: {bad}")
|
| 252 |
+
n = normalize(raw)
|
| 253 |
+
q, sem = quality_score(n), semantic_score(n)
|
| 254 |
+
sel = selected_score(q, sem)
|
| 255 |
+
row.update({f"raw_{d}": raw[d] for d in DIMENSIONS})
|
| 256 |
+
row.update({f"normalized_{d}": n[d] for d in DIMENSIONS})
|
| 257 |
+
row.update({"quality_score": q, "semantic_score": sem,
|
| 258 |
+
"selected_vbench_score": sel,
|
| 259 |
+
"selected_vbench_percent": sel * 100.0})
|
| 260 |
+
else:
|
| 261 |
+
problems.append(f"{s}: only {len(raw)}/8 VBench dimensions")
|
| 262 |
+
else:
|
| 263 |
+
problems.append(f"{s}: no VBench scores yet")
|
| 264 |
+
rows.append(row)
|
| 265 |
+
|
| 266 |
+
rows.sort(key=lambda r: (r["base_model"], r["method"], r["target_speedup"]))
|
| 267 |
+
|
| 268 |
+
summary = {
|
| 269 |
+
"protocol": "Self-Forcing Extended-251 Full Evaluation",
|
| 270 |
+
"vbench_long": False,
|
| 271 |
+
"num_strategies": len(rows),
|
| 272 |
+
"prompts_per_strategy": args.expect,
|
| 273 |
+
"seed": 0,
|
| 274 |
+
"video_spec": {"frames": 81, "height": 480, "width": 832, "fps": 16},
|
| 275 |
+
"precision": "bfloat16",
|
| 276 |
+
"normalize_range": NORMALIZE_RANGE,
|
| 277 |
+
"aggregation": {
|
| 278 |
+
"quality": "(n_sc + n_bc + n_ms + 0.5*n_dd + n_aq + n_iq) / 5.5",
|
| 279 |
+
"semantic": "(n_scene + n_oc) / 2",
|
| 280 |
+
"selected": "(4*quality + semantic) / 5",
|
| 281 |
+
"psnr": "-10*log10(mean(per_prompt_mean_mse)), clamp 1e-12",
|
| 282 |
+
"speedup": "100 * (1 - mean(strategy) / mean(matched_ffff))",
|
| 283 |
+
},
|
| 284 |
+
"latency_includes": "all denoise policy GPU compute incl. cache control modules",
|
| 285 |
+
"latency_excludes": "context/KV-cache DiT, text encoder, VAE decode, metrics, I/O",
|
| 286 |
+
"sources": mapping["sources"],
|
| 287 |
+
"mapping": {"path": MAPPING, "sha256": sha256(MAPPING)},
|
| 288 |
+
"environment": {
|
| 289 |
+
"generation_python": "/local/zoubin/cz/envs/self_forcing/bin/python",
|
| 290 |
+
"vbench_python": "/local/zoubin/cz/envs/vbench_eval/bin/python",
|
| 291 |
+
"vbench_source": "/local/zoubin/cz/projects/VBench",
|
| 292 |
+
"vbench_cache_dir": "/local/zoubin/cz/.cache/vbench",
|
| 293 |
+
"platform": platform.platform(),
|
| 294 |
+
},
|
| 295 |
+
"problems": problems,
|
| 296 |
+
"rows": rows,
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
os.makedirs(os.path.join(out_root, "summaries"), exist_ok=True)
|
| 300 |
+
jpath = os.path.join(out_root, "summaries", "final_summary.json")
|
| 301 |
+
with open(jpath, "w") as f:
|
| 302 |
+
json.dump(summary, f, indent=2)
|
| 303 |
+
|
| 304 |
+
cpath = os.path.join(out_root, "summaries", "final_summary.csv")
|
| 305 |
+
if rows:
|
| 306 |
+
keys = sorted({k for r in rows for k in r},
|
| 307 |
+
key=lambda k: (k not in ("strategy", "base_model", "method"), k))
|
| 308 |
+
with open(cpath, "w", newline="") as f:
|
| 309 |
+
w = csv.DictWriter(f, fieldnames=keys)
|
| 310 |
+
w.writeheader()
|
| 311 |
+
w.writerows(rows)
|
| 312 |
+
|
| 313 |
+
hdr = (f"{'strategy':24s} {'videos':>6s} {'lat_ms':>9s} {'speedup%':>9s} "
|
| 314 |
+
f"{'psnr':>7s} {'ssim':>7s} {'lpips':>7s} {'sel_vbench%':>12s}")
|
| 315 |
+
print(hdr)
|
| 316 |
+
print("-" * len(hdr))
|
| 317 |
+
for r in rows:
|
| 318 |
+
sel = r.get("selected_vbench_percent")
|
| 319 |
+
print(f"{r['strategy']:24s} {r['num_videos']:6d} {r['policy_latency_ms']:9.1f} "
|
| 320 |
+
f"{r['speedup_percent_vs_ffff']:9.2f} {r['psnr']:7.2f} {r['ssim']:7.4f} "
|
| 321 |
+
f"{r['lpips']:7.4f} " + (f"{sel:12.3f}" if sel is not None else f"{'-':>12s}"))
|
| 322 |
+
if problems:
|
| 323 |
+
print("\nnotes:")
|
| 324 |
+
for p in problems:
|
| 325 |
+
print(" -", p)
|
| 326 |
+
print(f"\nwrote {jpath}\nwrote {cpath}")
|
| 327 |
+
return 0
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
if __name__ == "__main__":
|
| 331 |
+
sys.exit(main())
|
eval/build_mapping.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Build and validate assets/vbench8_extended_subset_mapping.json (Extended-251).
|
| 3 |
+
|
| 4 |
+
Protocol section 4: take the subject_consistency (72), overall_consistency (93)
|
| 5 |
+
and scene (86) suites out of the canonical 946-prompt VBench order, and carry the
|
| 6 |
+
Self-Forcing rewritten extended prompt for each.
|
| 7 |
+
|
| 8 |
+
Selection is strictly by index into VBench_full_info.json -- the short prompt list
|
| 9 |
+
contains two duplicate texts, so text matching is not safe, and the protocol
|
| 10 |
+
forbids it anyway.
|
| 11 |
+
|
| 12 |
+
python eval/build_mapping.py --out assets/vbench8_extended_subset_mapping.json
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import hashlib
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 22 |
+
|
| 23 |
+
SUITES = ["subject_consistency", "overall_consistency", "scene"]
|
| 24 |
+
EXPECTED = {"subject_consistency": 72, "overall_consistency": 93, "scene": 86}
|
| 25 |
+
|
| 26 |
+
DEFAULT_FULL_INFO = "/local/zoubin/cz/projects/VBench/vbench/VBench_full_info.json"
|
| 27 |
+
DEFAULT_PROMPT_DIR = "/local/zoubin/cz/projects/Self-Forcing/prompts/vbench"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def sha256(path):
|
| 31 |
+
h = hashlib.sha256()
|
| 32 |
+
with open(path, "rb") as f:
|
| 33 |
+
for chunk in iter(lambda: f.read(1 << 20), b""):
|
| 34 |
+
h.update(chunk)
|
| 35 |
+
return h.hexdigest()
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def main():
|
| 39 |
+
ap = argparse.ArgumentParser()
|
| 40 |
+
ap.add_argument("--full-info", default=DEFAULT_FULL_INFO)
|
| 41 |
+
ap.add_argument("--prompt-dir", default=DEFAULT_PROMPT_DIR)
|
| 42 |
+
ap.add_argument("--out", default=os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json"))
|
| 43 |
+
args = ap.parse_args()
|
| 44 |
+
|
| 45 |
+
short_path = os.path.join(args.prompt_dir, "all_dimension.txt")
|
| 46 |
+
ext_path = os.path.join(args.prompt_dir, "all_dimension_extended.txt")
|
| 47 |
+
|
| 48 |
+
with open(args.full_info) as f:
|
| 49 |
+
info = json.load(f)
|
| 50 |
+
with open(short_path, encoding="utf-8") as f:
|
| 51 |
+
short = [l.rstrip("\n") for l in f]
|
| 52 |
+
with open(ext_path, encoding="utf-8") as f:
|
| 53 |
+
ext = [l.rstrip("\n") for l in f]
|
| 54 |
+
|
| 55 |
+
errors = []
|
| 56 |
+
if len(short) != 946:
|
| 57 |
+
errors.append(f"short prompts == {len(short)}, expected 946")
|
| 58 |
+
if len(ext) != 946:
|
| 59 |
+
errors.append(f"extended prompts == {len(ext)}, expected 946")
|
| 60 |
+
if len(info) != 946:
|
| 61 |
+
errors.append(f"VBench_full_info rows == {len(info)}, expected 946")
|
| 62 |
+
for i, (row, s) in enumerate(zip(info, short)):
|
| 63 |
+
if row["prompt_en"].strip() != s.strip():
|
| 64 |
+
errors.append(f"short prompt order differs from VBench_full_info at line {i}")
|
| 65 |
+
break
|
| 66 |
+
for i, e in enumerate(ext):
|
| 67 |
+
if not e.strip():
|
| 68 |
+
errors.append(f"empty extended prompt at line {i}")
|
| 69 |
+
break
|
| 70 |
+
|
| 71 |
+
rows = []
|
| 72 |
+
for suite in SUITES:
|
| 73 |
+
suite_index = 0
|
| 74 |
+
for gi, row in enumerate(info):
|
| 75 |
+
if suite not in row["dimension"]:
|
| 76 |
+
continue
|
| 77 |
+
entry = {
|
| 78 |
+
"global_index": gi,
|
| 79 |
+
"prompt_suite": suite,
|
| 80 |
+
"suite_index": suite_index,
|
| 81 |
+
"original_prompt": short[gi],
|
| 82 |
+
"extended_prompt": ext[gi],
|
| 83 |
+
"official_dimensions": list(row["dimension"]),
|
| 84 |
+
}
|
| 85 |
+
# Standard VBench 0.1.5 needs the official scene keywords to score `scene`.
|
| 86 |
+
for key in ("auxiliary_info",):
|
| 87 |
+
if key in row:
|
| 88 |
+
entry[key] = row[key]
|
| 89 |
+
rows.append(entry)
|
| 90 |
+
suite_index += 1
|
| 91 |
+
|
| 92 |
+
counts = {s: sum(1 for r in rows if r["prompt_suite"] == s) for s in SUITES}
|
| 93 |
+
for s, n in EXPECTED.items():
|
| 94 |
+
if counts.get(s) != n:
|
| 95 |
+
errors.append(f"{s} == {counts.get(s)}, expected {n}")
|
| 96 |
+
if len(rows) != 251:
|
| 97 |
+
errors.append(f"mapping rows == {len(rows)}, expected 251")
|
| 98 |
+
if len({r["global_index"] for r in rows}) != len(rows):
|
| 99 |
+
errors.append("duplicate global_index")
|
| 100 |
+
for s in SUITES:
|
| 101 |
+
idx = [r["suite_index"] for r in rows if r["prompt_suite"] == s]
|
| 102 |
+
if idx != list(range(len(idx))):
|
| 103 |
+
errors.append(f"{s} suite_index not contiguous")
|
| 104 |
+
|
| 105 |
+
if errors:
|
| 106 |
+
print("MAPPING VALIDATION FAILED:")
|
| 107 |
+
for e in errors:
|
| 108 |
+
print(" -", e)
|
| 109 |
+
return 1
|
| 110 |
+
|
| 111 |
+
payload = {
|
| 112 |
+
"protocol": "Self-Forcing Extended-251 Full Evaluation",
|
| 113 |
+
"num_rows": len(rows),
|
| 114 |
+
"counts": counts,
|
| 115 |
+
"sources": {
|
| 116 |
+
"vbench_full_info": {"path": args.full_info, "sha256": sha256(args.full_info)},
|
| 117 |
+
"short_prompts": {"path": short_path, "sha256": sha256(short_path)},
|
| 118 |
+
"extended_prompts": {"path": ext_path, "sha256": sha256(ext_path)},
|
| 119 |
+
},
|
| 120 |
+
"rows": rows,
|
| 121 |
+
}
|
| 122 |
+
os.makedirs(os.path.dirname(args.out), exist_ok=True)
|
| 123 |
+
with open(args.out, "w") as f:
|
| 124 |
+
json.dump(payload, f, indent=2)
|
| 125 |
+
|
| 126 |
+
print("mapping OK")
|
| 127 |
+
print(f" rows {len(rows)}")
|
| 128 |
+
for s in SUITES:
|
| 129 |
+
print(f" {s:22s} {counts[s]}")
|
| 130 |
+
for k, v in payload["sources"].items():
|
| 131 |
+
print(f" sha256 {k:18s} {v['sha256'][:16]}...")
|
| 132 |
+
print(f" scene rows with auxiliary_info: "
|
| 133 |
+
f"{sum(1 for r in rows if r['prompt_suite'] == 'scene' and 'auxiliary_info' in r)}")
|
| 134 |
+
print(f"wrote {args.out}")
|
| 135 |
+
return 0
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
if __name__ == "__main__":
|
| 139 |
+
sys.exit(main())
|
eval/generate_eval.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Generate the Extended-251 videos for every strategy of one base model.
|
| 3 |
+
|
| 4 |
+
Work is sharded by prompt, not by strategy, because the protocol requires each
|
| 5 |
+
strategy's clip to be paired with the FFFF clip for the *same* prompt and seed
|
| 6 |
+
(section 8.1). A shard therefore generates FFFF first, keeps its decoded frames,
|
| 7 |
+
and immediately scores every cache strategy against them -- no 25 GB of reference
|
| 8 |
+
frames on disk, and the pairing cannot get mismatched.
|
| 9 |
+
|
| 10 |
+
CUDA_VISIBLE_DEVICES=0 python eval/generate_eval.py \
|
| 11 |
+
--base self_forcing --shard 0 --num-shards 4 --out-root eval_out
|
| 12 |
+
|
| 13 |
+
Re-running skips prompts whose per-strategy record is already complete.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import argparse
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
import time
|
| 21 |
+
|
| 22 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 23 |
+
sys.path.insert(0, ROOT)
|
| 24 |
+
|
| 25 |
+
from eval.strategies import load_strategies # noqa: E402
|
| 26 |
+
from harness import denoising_steps_for, BASES, enter_base, load_pipeline, use_denoising_steps # noqa: E402
|
| 27 |
+
|
| 28 |
+
MAPPING = os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def parse_args():
|
| 32 |
+
p = argparse.ArgumentParser()
|
| 33 |
+
p.add_argument("--base", choices=sorted(BASES), required=True)
|
| 34 |
+
p.add_argument("--shard", type=int, default=0)
|
| 35 |
+
p.add_argument("--num-shards", type=int, default=1)
|
| 36 |
+
p.add_argument("--out-root", default="eval_out")
|
| 37 |
+
p.add_argument("--seed", type=int, default=0)
|
| 38 |
+
p.add_argument("--num-latent-frames", type=int, default=21)
|
| 39 |
+
p.add_argument("--fps", type=int, default=16)
|
| 40 |
+
p.add_argument("--limit", type=int, default=None, help="Smoke-test: first N prompts")
|
| 41 |
+
p.add_argument("--only-strategy", default=None, help="Smoke-test: one strategy name")
|
| 42 |
+
p.add_argument("--strategies", default=None,
|
| 43 |
+
help="Comma-separated subset to generate (the FFFF reference is always kept)")
|
| 44 |
+
p.add_argument("--overwrite", action="store_true")
|
| 45 |
+
p.add_argument("--regenerate-ffff", action="store_true",
|
| 46 |
+
help="Regenerate the FFFF reference instead of reading its MP4")
|
| 47 |
+
return p.parse_args()
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def main():
|
| 51 |
+
args = parse_args()
|
| 52 |
+
out_root = (args.out_root if os.path.isabs(args.out_root)
|
| 53 |
+
else os.path.join(ROOT, args.out_root))
|
| 54 |
+
|
| 55 |
+
with open(MAPPING) as f:
|
| 56 |
+
mapping = json.load(f)
|
| 57 |
+
rows = mapping["rows"]
|
| 58 |
+
if args.limit:
|
| 59 |
+
rows = rows[:args.limit]
|
| 60 |
+
shard_rows = rows[args.shard::args.num_shards]
|
| 61 |
+
|
| 62 |
+
strategies = load_strategies(args.base)
|
| 63 |
+
if args.only_strategy:
|
| 64 |
+
keep = {strategies[0]["name"], args.only_strategy}
|
| 65 |
+
strategies = [s for s in strategies if s["name"] in keep]
|
| 66 |
+
if args.strategies:
|
| 67 |
+
keep = set(args.strategies.split(","))
|
| 68 |
+
strategies = [s for s in strategies if s["is_reference"] or s["name"] in keep]
|
| 69 |
+
ref_name = strategies[0]["name"]
|
| 70 |
+
assert strategies[0]["is_reference"], "first strategy must be FFFF"
|
| 71 |
+
|
| 72 |
+
print(f"base={args.base} shard {args.shard}/{args.num_shards}: "
|
| 73 |
+
f"{len(shard_rows)} prompts x {len(strategies)} strategies", flush=True)
|
| 74 |
+
|
| 75 |
+
base = enter_base(args.base)
|
| 76 |
+
|
| 77 |
+
import torch
|
| 78 |
+
from einops import rearrange
|
| 79 |
+
from torchvision.io import read_video, write_video
|
| 80 |
+
from utils.misc import set_seed
|
| 81 |
+
|
| 82 |
+
from cachelib import (CacheController, build_method, cached_inference, install,
|
| 83 |
+
parse_schedule)
|
| 84 |
+
from eval.pixel_metrics import PixelMetrics
|
| 85 |
+
|
| 86 |
+
torch.set_grad_enabled(False)
|
| 87 |
+
device = torch.device("cuda")
|
| 88 |
+
pipeline = load_pipeline(base)
|
| 89 |
+
num_steps = len(pipeline.denoising_step_list)
|
| 90 |
+
metrics = PixelMetrics(device)
|
| 91 |
+
|
| 92 |
+
def record_path(strategy, row):
|
| 93 |
+
d = os.path.join(out_root, "per_prompt", strategy)
|
| 94 |
+
os.makedirs(d, exist_ok=True)
|
| 95 |
+
return os.path.join(d, f"{row['prompt_suite']}_{row['suite_index']:03d}.json")
|
| 96 |
+
|
| 97 |
+
def video_path(strategy, row):
|
| 98 |
+
d = os.path.join(out_root, "generated_videos", strategy, row["prompt_suite"])
|
| 99 |
+
os.makedirs(d, exist_ok=True)
|
| 100 |
+
return os.path.join(d, f"{row['suite_index']:03d}.mp4")
|
| 101 |
+
|
| 102 |
+
def ref_cache_path(row):
|
| 103 |
+
d = os.path.join(out_root, "ref_cache", args.base)
|
| 104 |
+
os.makedirs(d, exist_ok=True)
|
| 105 |
+
return os.path.join(d, f"{row['prompt_suite']}_{row['suite_index']:03d}.pt")
|
| 106 |
+
|
| 107 |
+
def is_done(strategy, row):
|
| 108 |
+
p = record_path(strategy, row)
|
| 109 |
+
if args.overwrite or not os.path.exists(p):
|
| 110 |
+
return False
|
| 111 |
+
try:
|
| 112 |
+
with open(p) as f:
|
| 113 |
+
rec = json.load(f)
|
| 114 |
+
return rec.get("status") == "complete" and os.path.exists(rec.get("video", ""))
|
| 115 |
+
except Exception:
|
| 116 |
+
return False
|
| 117 |
+
|
| 118 |
+
def write_json(path, payload):
|
| 119 |
+
tmp = path + ".tmp"
|
| 120 |
+
with open(tmp, "w") as f:
|
| 121 |
+
json.dump(payload, f, indent=2)
|
| 122 |
+
os.replace(tmp, path) # atomic: a partial file is never seen as complete
|
| 123 |
+
|
| 124 |
+
def run_one(strategy, row):
|
| 125 |
+
kw = dict(indicator=strategy["indicator"], coefficients=strategy["coefficients"])
|
| 126 |
+
if strategy["method"] != "none" and strategy.get("param"):
|
| 127 |
+
kw[strategy["param"]] = strategy["value"]
|
| 128 |
+
n_steps = strategy.get("num_inference_steps") or num_steps
|
| 129 |
+
original_steps = use_denoising_steps(pipeline, n_steps)
|
| 130 |
+
sched = strategy.get("schedule") or ("F" * n_steps)
|
| 131 |
+
first = strategy.get("first_chunk_schedule")
|
| 132 |
+
# Chunk 0 may have a longer schedule than the rest (e.g. FFFF over naive 2-step).
|
| 133 |
+
first_steps = (denoising_steps_for(original_steps, len(first))
|
| 134 |
+
if first and len(first) != n_steps else None)
|
| 135 |
+
ctrl = CacheController(
|
| 136 |
+
build_method(strategy["method"], **kw), num_steps=n_steps,
|
| 137 |
+
forced_steps=parse_schedule(sched, n_steps),
|
| 138 |
+
first_chunk_forced_steps=(parse_schedule(first, len(first)) if first else None))
|
| 139 |
+
install(pipeline.generator.model, ctrl)
|
| 140 |
+
|
| 141 |
+
# Identical noise for every strategy: same seed, same draw order, same shape.
|
| 142 |
+
set_seed(args.seed)
|
| 143 |
+
noise = torch.randn([1, args.num_latent_frames, 16, 60, 104],
|
| 144 |
+
device=device, dtype=torch.bfloat16)
|
| 145 |
+
wall0 = time.time()
|
| 146 |
+
video, latents, timing = cached_inference(
|
| 147 |
+
pipeline, ctrl, noise, [row["extended_prompt"]], decode=True,
|
| 148 |
+
first_chunk_steps=first_steps, sampler=strategy.get("sampler", "renoise"))
|
| 149 |
+
wall = time.time() - wall0
|
| 150 |
+
pipeline.vae.model.clear_cache()
|
| 151 |
+
pipeline.denoising_step_list = original_steps
|
| 152 |
+
return video, timing, ctrl.summary(), wall
|
| 153 |
+
|
| 154 |
+
# Warm up every strategy once on a throwaway prompt and discard the result.
|
| 155 |
+
# Without this the first recorded prompt carries one-off costs -- notably
|
| 156 |
+
# TaylorSeer's torch.compile of the forecast, which made its first video look
|
| 157 |
+
# slower than FFFF.
|
| 158 |
+
if shard_rows:
|
| 159 |
+
print("warm-up pass (not recorded)", flush=True)
|
| 160 |
+
for strategy in strategies:
|
| 161 |
+
run_one(strategy, shard_rows[0])
|
| 162 |
+
torch.cuda.empty_cache()
|
| 163 |
+
|
| 164 |
+
t_start = time.time()
|
| 165 |
+
for n, row in enumerate(shard_rows):
|
| 166 |
+
todo = [s for s in strategies if not is_done(s["name"], row)]
|
| 167 |
+
if not todo:
|
| 168 |
+
continue
|
| 169 |
+
# The reference is needed for pixel metrics even when only some strategies
|
| 170 |
+
# are outstanding.
|
| 171 |
+
need_ref = any(not s["is_reference"] for s in todo)
|
| 172 |
+
ref_frames = None
|
| 173 |
+
# The FFFF reference frames (pre-MP4, fp16) are cached on disk after their
|
| 174 |
+
# first generation, so later strategy runs do not regenerate FFFF at all.
|
| 175 |
+
rc = ref_cache_path(row)
|
| 176 |
+
ref_from_mp4 = False
|
| 177 |
+
if need_ref and os.path.exists(rc):
|
| 178 |
+
ref_frames = torch.load(rc, map_location=device)
|
| 179 |
+
need_ref = False
|
| 180 |
+
elif need_ref and os.path.exists(video_path(ref_name, row)) and not args.regenerate_ffff:
|
| 181 |
+
# Reference from the FFFF run's MP4 (H.264, ~40+ dB above the 12-20 dB
|
| 182 |
+
# strategy PSNRs, so the compression adds <1 % to the MSE); never
|
| 183 |
+
# regenerate FFFF just to compare against it.
|
| 184 |
+
vid, _, _ = read_video(video_path(ref_name, row), pts_unit="sec", output_format="TCHW")
|
| 185 |
+
ref_frames = (vid.to(device, torch.float16) / 255.0)
|
| 186 |
+
ref_from_mp4 = True
|
| 187 |
+
need_ref = False
|
| 188 |
+
|
| 189 |
+
for strategy in strategies:
|
| 190 |
+
if strategy["is_reference"]:
|
| 191 |
+
if strategy not in todo and not need_ref:
|
| 192 |
+
continue
|
| 193 |
+
elif strategy not in todo:
|
| 194 |
+
continue
|
| 195 |
+
|
| 196 |
+
video, timing, summary, wall = run_one(strategy, row)
|
| 197 |
+
frames = video[0] # [T, C, H, W] in [0, 1]
|
| 198 |
+
|
| 199 |
+
if strategy["is_reference"]:
|
| 200 |
+
ref_frames = frames.detach().to(torch.float16)
|
| 201 |
+
if not os.path.exists(rc):
|
| 202 |
+
torch.save(ref_frames.cpu(), rc + ".tmp")
|
| 203 |
+
os.replace(rc + ".tmp", rc)
|
| 204 |
+
pix = PixelMetrics.identity(frames.shape[0])
|
| 205 |
+
else:
|
| 206 |
+
pix = metrics.compute(frames, ref_frames)
|
| 207 |
+
|
| 208 |
+
if strategy in todo:
|
| 209 |
+
vp = video_path(strategy["name"], row)
|
| 210 |
+
arr = (255.0 * rearrange(frames, "t c h w -> t h w c")).clamp(0, 255)
|
| 211 |
+
write_video(vp, arr.to(torch.uint8).cpu(), fps=args.fps)
|
| 212 |
+
|
| 213 |
+
write_json(record_path(strategy["name"], row), {
|
| 214 |
+
"status": "complete",
|
| 215 |
+
"protocol": "Self-Forcing Extended-251 Full Evaluation",
|
| 216 |
+
"strategy": strategy["name"],
|
| 217 |
+
"base_model": args.base,
|
| 218 |
+
"method": strategy["method"],
|
| 219 |
+
"param": strategy["param"],
|
| 220 |
+
"param_value": strategy["value"],
|
| 221 |
+
"target_speedup": strategy["target"],
|
| 222 |
+
"schedule": strategy.get("schedule") or ("F" * (strategy.get("num_inference_steps") or 4)),
|
| 223 |
+
"num_inference_steps": strategy.get("num_inference_steps") or 4,
|
| 224 |
+
"first_chunk_schedule": strategy.get("first_chunk_schedule"),
|
| 225 |
+
"sampler": strategy.get("sampler", "renoise"),
|
| 226 |
+
"global_index": row["global_index"],
|
| 227 |
+
"prompt_suite": row["prompt_suite"],
|
| 228 |
+
"suite_index": row["suite_index"],
|
| 229 |
+
"prompt": row["extended_prompt"],
|
| 230 |
+
"original_prompt": row["original_prompt"],
|
| 231 |
+
"seed": args.seed,
|
| 232 |
+
"video": vp,
|
| 233 |
+
"num_frames": int(frames.shape[0]),
|
| 234 |
+
"height": int(frames.shape[2]),
|
| 235 |
+
"width": int(frames.shape[3]),
|
| 236 |
+
"fps": args.fps,
|
| 237 |
+
"policy_latency_ms": timing["denoise_dit_ms"],
|
| 238 |
+
"excluded_context_kv_latency_ms": timing["context_kv_dit_ms"],
|
| 239 |
+
"wall_generation_s": wall,
|
| 240 |
+
"reference_strategy": ref_name,
|
| 241 |
+
"pixel_metrics_vs_ffff": pix,
|
| 242 |
+
"reference_source": "ffff_mp4" if ref_from_mp4 else "ffff_frames",
|
| 243 |
+
"cache_diagnostics": dict(summary),
|
| 244 |
+
})
|
| 245 |
+
del frames, video
|
| 246 |
+
|
| 247 |
+
ref_frames = None
|
| 248 |
+
done = n + 1
|
| 249 |
+
rate = (time.time() - t_start) / done
|
| 250 |
+
print(f"[{args.base} shard {args.shard}] {done}/{len(shard_rows)} prompts "
|
| 251 |
+
f"({row['prompt_suite']}/{row['suite_index']:03d}) "
|
| 252 |
+
f"{rate:.1f}s/prompt eta {(len(shard_rows) - done) * rate / 60:.0f} min",
|
| 253 |
+
flush=True)
|
| 254 |
+
|
| 255 |
+
print(f"shard {args.shard} done in {(time.time() - t_start) / 60:.1f} min", flush=True)
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
if __name__ == "__main__":
|
| 259 |
+
main()
|
eval/generate_predictor_eval.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Extended-251 videos for the Causal-Forcing-a single-block Predictors.
|
| 3 |
+
|
| 4 |
+
Same protocol as generate_eval.py: work is sharded by prompt, FFFF is generated
|
| 5 |
+
first in the same process and kept as the pixel-metric reference, every strategy
|
| 6 |
+
sees the identical noise draw, and the per-prompt record has the layout
|
| 7 |
+
aggregate.py / run_vbench.py expect.
|
| 8 |
+
|
| 9 |
+
Strategies (prefix ``cfa`` = Causal-Forcing-a stack):
|
| 10 |
+
|
| 11 |
+
cfa_ffff reference, 28 full DiT forwards
|
| 12 |
+
cfa_<model>_fpff chunks 1..6: F P F F (22 full + 6 Predictor)
|
| 13 |
+
cfa_<model>_fppf chunks 1..6: F P P F (16 full + 12 Predictor)
|
| 14 |
+
cfa_<model>_fppp chunks 1..6: F P P P (10 full + 18 Predictor)
|
| 15 |
+
|
| 16 |
+
where <model> is one of atc_chunk, atc_last_frame, disca (Stage-1 Layer-17
|
| 17 |
+
Predictors) or atc_chunk_s2 (Stage-2 DMD EMA). Chunk 0 is always fully denoised.
|
| 18 |
+
``<model>@<step>`` selects another checkpoint of that run and names the strategy
|
| 19 |
+
cfa_<model>_s<step>..., e.g. ``disca@500`` -> cfa_disca_s500_fppf; a bare model
|
| 20 |
+
uses --checkpoint-step (default 2000) and the unsuffixed name.
|
| 21 |
+
|
| 22 |
+
cfa_naive2step chunks 1..: the 2-step subset of the grid (t = 1000 / 250,
|
| 23 |
+
the same subset as cf_naive2step_c0FFFF), no Predictor
|
| 24 |
+
|
| 25 |
+
CUDA_VISIBLE_DEVICES=3 python eval/generate_predictor_eval.py --shard 0 --num-shards 4
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
import argparse
|
| 29 |
+
import json
|
| 30 |
+
import os
|
| 31 |
+
import sys
|
| 32 |
+
import time
|
| 33 |
+
|
| 34 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 35 |
+
REPO = os.environ.get("CFA_REPO", "/local/zoubin/cz/projects/Causal-Forcing-a")
|
| 36 |
+
MAPPING = os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json")
|
| 37 |
+
|
| 38 |
+
BASE = {
|
| 39 |
+
"repo": REPO,
|
| 40 |
+
"config": "configs/causal_forcing_dmd_chunkwise.yaml",
|
| 41 |
+
"checkpoint": "checkpoints/chunkwise/causal_forcing.pt",
|
| 42 |
+
}
|
| 43 |
+
MODELS = {
|
| 44 |
+
"atc_chunk": "output/layer17_stage1_atc_chunk_1000p_21f_seed0_4gpu_b16_acc1_2000steps",
|
| 45 |
+
"atc_last_frame": "output/layer17_stage1_atc_last_frame_1000p_21f_seed0_4gpu_b16_acc1_2000steps",
|
| 46 |
+
"disca": "output/layer17_stage1_disca_1000p_21f_seed0_4gpu_b16_acc1_2000steps",
|
| 47 |
+
# Stage-2 random-exit DMD (LoRA critic), EMA weights (a (run dir, file name) pair;
|
| 48 |
+
# a plain directory gets checkpoint_step_<N>/predictor.safetensors).
|
| 49 |
+
"atc_chunk_s2": ("output/layer17_stage2_dmd_atc_chunk_lora128_4gpu_b2_2000steps", "predictor_ema.safetensors"),
|
| 50 |
+
}
|
| 51 |
+
NAIVE = {"naive2step": [0, 3]} # pseudo-models: reduced step grid, no Predictor
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def checkpoint_weights(model, step):
|
| 55 |
+
"""Weights file of ``model`` at training step ``step`` (Stage-1 runs zero-pad the
|
| 56 |
+
checkpoint directory, Stage-2 does not)."""
|
| 57 |
+
spec = MODELS[model]
|
| 58 |
+
run_dir, fname = spec if isinstance(spec, tuple) else (spec, "predictor.safetensors")
|
| 59 |
+
for d in (f"checkpoint_step_{step:04d}", f"checkpoint_step_{step}"):
|
| 60 |
+
w = os.path.join(REPO, run_dir, d, fname)
|
| 61 |
+
if os.path.exists(w):
|
| 62 |
+
return w
|
| 63 |
+
raise FileNotFoundError(f"{model} step {step}: no checkpoint under {os.path.join(REPO, run_dir)}")
|
| 64 |
+
PATTERNS = {"FPFF": {1}, "FPPF": {1, 2}, "FPPP": {1, 2, 3}}
|
| 65 |
+
# Reduced schedules: which step indices of the 4-step grid a chunk (after chunk 0)
|
| 66 |
+
# actually visits. FPF keeps steps 0 and 3 (t = 1000 / 250) as Full and runs one
|
| 67 |
+
# Predictor step at step 1 (t = 750, anchored on step 0 as in training), skipping
|
| 68 |
+
# step 2 entirely: 3 denoising positions per chunk, 2 Full + 1 Predictor.
|
| 69 |
+
RUN_STEPS = {"FPF": [0, 1, 3]}
|
| 70 |
+
PATTERNS["FPF"] = {1}
|
| 71 |
+
NUM_CHUNKS, FRAMES_PER_CHUNK, NUM_STEPS = 7, 3, 4
|
| 72 |
+
# A Predictor step runs 1 of the 30 Teacher blocks (plus a small fusion MLP);
|
| 73 |
+
# its compute-equivalent cost is booked as 1/30 of a full forward.
|
| 74 |
+
PREDICTOR_COMPUTE_EQUIV = 1.0 / 30.0
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def parse_args():
|
| 78 |
+
p = argparse.ArgumentParser()
|
| 79 |
+
p.add_argument("--shard", type=int, default=0)
|
| 80 |
+
p.add_argument("--num-shards", type=int, default=1)
|
| 81 |
+
p.add_argument("--out-root", default="eval_out")
|
| 82 |
+
p.add_argument("--seed", type=int, default=0)
|
| 83 |
+
p.add_argument("--fps", type=int, default=16)
|
| 84 |
+
p.add_argument("--models", default=",".join(MODELS))
|
| 85 |
+
p.add_argument("--patterns", default="FPPF,FPPP")
|
| 86 |
+
p.add_argument("--checkpoint-step", type=int, default=2000)
|
| 87 |
+
p.add_argument("--num-chunks", type=int, default=NUM_CHUNKS,
|
| 88 |
+
help="Video length in 3-latent-frame chunks (7 = the 81-frame protocol; longer "
|
| 89 |
+
"videos use a rolling 21-frame KV window like Self-Forcing's long-video mode)")
|
| 90 |
+
p.add_argument("--ref-tag", default="",
|
| 91 |
+
help="Inserted into the reference name, e.g. _14c -> cfa_14c_ffff (a separate FFFF "
|
| 92 |
+
"reference for a different video length)")
|
| 93 |
+
p.add_argument("--tag", default="",
|
| 94 |
+
help="Inserted into strategy names after the model, e.g. _s1000 -> "
|
| 95 |
+
"cfa_disca_s1000_fppf, so another checkpoint does not overwrite the default rows")
|
| 96 |
+
p.add_argument("--regenerate-ffff", action="store_true",
|
| 97 |
+
help="Regenerate the FFFF reference instead of reading cfa_ffff's MP4")
|
| 98 |
+
p.add_argument("--limit", type=int, default=None, help="Smoke-test: first N prompts")
|
| 99 |
+
p.add_argument("--overwrite", action="store_true")
|
| 100 |
+
p.add_argument("--retime-only", action="store_true",
|
| 101 |
+
help="Do not regenerate videos/metrics; re-time FFFF and each strategy in the same "
|
| 102 |
+
"process per prompt and update policy_latency_ms / matched_ffff_policy_latency_ms.")
|
| 103 |
+
p.add_argument("--retime-max-ffff-ms", type=float, default=3500.0,
|
| 104 |
+
help="With --retime-only, records whose paired FFFF exceeds this are re-timed again "
|
| 105 |
+
"(up to --retime-passes passes); clean records are left alone.")
|
| 106 |
+
p.add_argument("--retime-passes", type=int, default=3)
|
| 107 |
+
p.add_argument("--retime-strategy-only", action="store_true",
|
| 108 |
+
help="Re-time only the strategies (no FFFF run): rerun each selected record's "
|
| 109 |
+
"rollout on an idle GPU and replace policy_latency_ms; the generation-run "
|
| 110 |
+
"value is kept under generation_run_latency.")
|
| 111 |
+
p.add_argument("--written-before", type=float, default=None,
|
| 112 |
+
help="With --retime-strategy-only: only records whose file mtime is before this "
|
| 113 |
+
"epoch second (the window in which another job shared the GPU)")
|
| 114 |
+
return p.parse_args()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def build_strategies(args):
|
| 118 |
+
out = [{"name": f"cfa{args.ref_tag}_ffff", "model": None, "pattern": "FFFF", "is_reference": True}]
|
| 119 |
+
for token in args.models.split(","):
|
| 120 |
+
if token in NAIVE:
|
| 121 |
+
out.append({"name": f"cfa_{token}{args.tag}", "model": None, "pattern": token.upper(),
|
| 122 |
+
"naive": token, "is_reference": False})
|
| 123 |
+
continue
|
| 124 |
+
model, _, step = token.partition("@")
|
| 125 |
+
step = int(step) if step else args.checkpoint_step
|
| 126 |
+
key = f"{model}@{step}" # one loaded Predictor per (model, step)
|
| 127 |
+
label = f"{model}_s{step}" if "@" in token else model
|
| 128 |
+
for pattern in args.patterns.split(","):
|
| 129 |
+
out.append({"name": f"cfa_{label}{args.tag}_{pattern.lower()}", "model": key,
|
| 130 |
+
"base_model": model, "checkpoint_step": step,
|
| 131 |
+
"pattern": pattern.upper(), "is_reference": False})
|
| 132 |
+
return out
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
class FinalHiddenCapture:
|
| 136 |
+
"""Grab the Teacher's final hidden (input of its output head) on a Full step."""
|
| 137 |
+
|
| 138 |
+
def __init__(self, teacher):
|
| 139 |
+
self.enabled, self.value = False, None
|
| 140 |
+
self.handle = teacher.head.register_forward_pre_hook(self._hook)
|
| 141 |
+
|
| 142 |
+
def _hook(self, _module, inputs):
|
| 143 |
+
if self.enabled:
|
| 144 |
+
self.value = inputs[0].detach()
|
| 145 |
+
|
| 146 |
+
def start(self):
|
| 147 |
+
self.value, self.enabled = None, True
|
| 148 |
+
|
| 149 |
+
def finish(self):
|
| 150 |
+
self.enabled = False
|
| 151 |
+
if self.value is None:
|
| 152 |
+
raise RuntimeError("Teacher final hidden was not captured")
|
| 153 |
+
value, self.value = self.value, None
|
| 154 |
+
return value
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def main():
|
| 158 |
+
args = parse_args()
|
| 159 |
+
out_root = args.out_root if os.path.isabs(args.out_root) else os.path.join(ROOT, args.out_root)
|
| 160 |
+
with open(MAPPING) as f:
|
| 161 |
+
rows = json.load(f)["rows"]
|
| 162 |
+
if args.limit:
|
| 163 |
+
rows = rows[:args.limit]
|
| 164 |
+
shard_rows = rows[args.shard::args.num_shards]
|
| 165 |
+
strategies = build_strategies(args)
|
| 166 |
+
ref_name = strategies[0]["name"]
|
| 167 |
+
print(f"cfa shard {args.shard}/{args.num_shards}: {len(shard_rows)} prompts x "
|
| 168 |
+
f"{len(strategies)} strategies", flush=True)
|
| 169 |
+
|
| 170 |
+
os.chdir(REPO)
|
| 171 |
+
sys.path.insert(0, ROOT)
|
| 172 |
+
sys.path.insert(0, REPO) # pipeline/, utils/, predictor_training/ from Causal-Forcing-a
|
| 173 |
+
|
| 174 |
+
import torch
|
| 175 |
+
from einops import rearrange
|
| 176 |
+
from safetensors.torch import load_file
|
| 177 |
+
from torchvision.io import read_video, write_video
|
| 178 |
+
|
| 179 |
+
from eval.pixel_metrics import PixelMetrics
|
| 180 |
+
from harness import load_pipeline
|
| 181 |
+
from predictor_training.metadata import build_single_block_predictor, read_predictor_config
|
| 182 |
+
from predictor_training.online import online_predictor_step as _online_predictor_step
|
| 183 |
+
from predictor_training.offline_data import TOKENS_PER_CHUNK
|
| 184 |
+
|
| 185 |
+
def online_predictor_step(*, history_cache, chunk, **kw):
|
| 186 |
+
"""Stage-1 contract: the Predictor sees the clean K/V of the *earlier* chunks.
|
| 187 |
+
With the rolling 21-frame window (long videos) the cache holds the last six
|
| 188 |
+
clean chunks plus the current noisy one, so the history is everything before
|
| 189 |
+
the current chunk's tokens rather than ``chunk * TOKENS_PER_CHUNK`` (which
|
| 190 |
+
the stock helper asserts and which exceeds the window from chunk 7 on).
|
| 191 |
+
For chunk <= 6 the two are identical."""
|
| 192 |
+
if chunk * TOKENS_PER_CHUNK <= int(history_cache["local_end_index"].item()):
|
| 193 |
+
return _online_predictor_step(history_cache=history_cache, chunk=chunk, **kw)
|
| 194 |
+
end = int(history_cache["local_end_index"].item()) - TOKENS_PER_CHUNK
|
| 195 |
+
window = {"k": history_cache["k"][:, :end], "v": history_cache["v"][:, :end],
|
| 196 |
+
"local_end_index": torch.tensor([end], device=history_cache["k"].device)}
|
| 197 |
+
# left-pad to the absolute length the stock helper slices; the attention
|
| 198 |
+
# window (max_attention_size = 21 frames) never reaches the padding
|
| 199 |
+
pad = chunk * TOKENS_PER_CHUNK - end
|
| 200 |
+
if pad > 0:
|
| 201 |
+
z = history_cache["k"].new_zeros(history_cache["k"].shape[0], pad, *history_cache["k"].shape[2:])
|
| 202 |
+
window = {"k": torch.cat([z, window["k"]], 1), "v": torch.cat([z, window["v"]], 1),
|
| 203 |
+
"local_end_index": torch.tensor([chunk * TOKENS_PER_CHUNK], device=z.device)}
|
| 204 |
+
return _online_predictor_step(history_cache=window, chunk=chunk, **kw)
|
| 205 |
+
from utils.misc import set_seed
|
| 206 |
+
|
| 207 |
+
torch.set_grad_enabled(False)
|
| 208 |
+
device = torch.device("cuda")
|
| 209 |
+
pipeline = load_pipeline(BASE)
|
| 210 |
+
n_chunks = args.num_chunks
|
| 211 |
+
if n_chunks * FRAMES_PER_CHUNK > 21:
|
| 212 |
+
# Longer than the 21-latent-frame training context: roll the KV cache over a
|
| 213 |
+
# 21-frame local window (the model's long-video mechanism; RoPE is relative
|
| 214 |
+
# so positions inside the window stay in the trained range).
|
| 215 |
+
model = pipeline.generator.model
|
| 216 |
+
for m in [model] + [b for b in model.blocks] + [b.self_attn for b in model.blocks]:
|
| 217 |
+
m.local_attn_size = 21
|
| 218 |
+
if hasattr(m, "max_attention_size"):
|
| 219 |
+
m.max_attention_size = 21 * pipeline.frame_seq_length
|
| 220 |
+
pipeline.local_attn_size = 21
|
| 221 |
+
pipeline.kv_cache1 = None
|
| 222 |
+
print(f"long video: {n_chunks} chunks = {n_chunks * FRAMES_PER_CHUNK} latent frames, "
|
| 223 |
+
f"rolling KV window 21 frames", flush=True)
|
| 224 |
+
teacher = pipeline.generator.model
|
| 225 |
+
capture = FinalHiddenCapture(teacher)
|
| 226 |
+
metrics = PixelMetrics(device)
|
| 227 |
+
timesteps = pipeline.denoising_step_list.to(device)
|
| 228 |
+
assert len(timesteps) == NUM_STEPS and pipeline.num_frame_per_block == FRAMES_PER_CHUNK
|
| 229 |
+
|
| 230 |
+
predictors, predictor_configs = {}, {}
|
| 231 |
+
for model in {s["model"] for s in strategies if s["model"]}:
|
| 232 |
+
base, step = model.split("@")
|
| 233 |
+
weights = checkpoint_weights(base, int(step))
|
| 234 |
+
config = read_predictor_config(weights)
|
| 235 |
+
predictor = build_single_block_predictor(teacher, config, gradient_checkpointing=False)
|
| 236 |
+
predictor.load_state_dict(load_file(weights, device="cpu"), strict=True)
|
| 237 |
+
predictors[model] = predictor.to(device).eval().requires_grad_(False)
|
| 238 |
+
predictor_configs[model] = {**config, "weights": weights}
|
| 239 |
+
print(f"loaded {model}: {config}", flush=True)
|
| 240 |
+
|
| 241 |
+
def reset_caches():
|
| 242 |
+
if pipeline.kv_cache1 is None:
|
| 243 |
+
pipeline._initialize_kv_cache(1, torch.bfloat16, device)
|
| 244 |
+
pipeline._initialize_crossattn_cache(1, torch.bfloat16, device)
|
| 245 |
+
for cache in pipeline.kv_cache1:
|
| 246 |
+
cache["global_end_index"] = torch.tensor([0], dtype=torch.long, device=device)
|
| 247 |
+
cache["local_end_index"] = torch.tensor([0], dtype=torch.long, device=device)
|
| 248 |
+
for cache in pipeline.crossattn_cache:
|
| 249 |
+
cache["is_init"] = False
|
| 250 |
+
|
| 251 |
+
def rollout(conditional, noise, predictor=None, predictor_steps=frozenset(), run_steps=None):
|
| 252 |
+
"""Mirror of cachelib.runner.cached_inference with Predictor steps spliced in."""
|
| 253 |
+
reset_caches()
|
| 254 |
+
output = torch.zeros_like(noise)
|
| 255 |
+
denoise_events, context_events = [], []
|
| 256 |
+
full_calls = predictor_calls = 0
|
| 257 |
+
previous_hidden = None
|
| 258 |
+
for chunk in range(n_chunks):
|
| 259 |
+
start_frame = chunk * FRAMES_PER_CHUNK
|
| 260 |
+
token_start = start_frame * pipeline.frame_seq_length
|
| 261 |
+
noisy_input = noise[:, start_frame:start_frame + FRAMES_PER_CHUNK]
|
| 262 |
+
current_hidden = [None] * NUM_STEPS
|
| 263 |
+
# chunk 0 always runs the full grid; later chunks may visit a subset
|
| 264 |
+
steps = list(range(NUM_STEPS)) if (run_steps is None or chunk == 0) else list(run_steps)
|
| 265 |
+
for si, step in enumerate(steps):
|
| 266 |
+
timestep = torch.ones([1, FRAMES_PER_CHUNK], device=device, dtype=torch.int64) * timesteps[step]
|
| 267 |
+
start_ev = torch.cuda.Event(enable_timing=True)
|
| 268 |
+
end_ev = torch.cuda.Event(enable_timing=True)
|
| 269 |
+
start_ev.record()
|
| 270 |
+
if predictor is not None and chunk > 0 and step in predictor_steps:
|
| 271 |
+
hidden, flow = online_predictor_step(
|
| 272 |
+
predictor=predictor, teacher=teacher, noisy_input=noisy_input,
|
| 273 |
+
timestep=timestep, anchor_timestep=torch.ones_like(timestep) * timesteps[step - 1],
|
| 274 |
+
anchor_hidden=current_hidden[step - 1], previous_hidden=previous_hidden[step],
|
| 275 |
+
history_cache=pipeline.kv_cache1[predictor.source_layer],
|
| 276 |
+
cross_cache=pipeline.crossattn_cache[predictor.source_layer], chunk=chunk)
|
| 277 |
+
denoised_pred = pipeline.generator._convert_flow_pred_to_x0(
|
| 278 |
+
flow_pred=flow.flatten(0, 1), xt=noisy_input.flatten(0, 1),
|
| 279 |
+
timestep=timestep.flatten(0, 1)).unflatten(0, flow.shape[:2])
|
| 280 |
+
current_hidden[step] = hidden
|
| 281 |
+
predictor_calls += 1
|
| 282 |
+
else:
|
| 283 |
+
capture.start()
|
| 284 |
+
_, denoised_pred = pipeline.generator(
|
| 285 |
+
noisy_image_or_video=noisy_input, conditional_dict=conditional,
|
| 286 |
+
timestep=timestep, kv_cache=pipeline.kv_cache1,
|
| 287 |
+
crossattn_cache=pipeline.crossattn_cache, current_start=token_start)
|
| 288 |
+
current_hidden[step] = capture.finish()
|
| 289 |
+
full_calls += 1
|
| 290 |
+
end_ev.record()
|
| 291 |
+
denoise_events.append((start_ev, end_ev))
|
| 292 |
+
if si < len(steps) - 1:
|
| 293 |
+
# Same RNG draw order as the protocol runner, so every strategy
|
| 294 |
+
# re-noises with identical samples.
|
| 295 |
+
noisy_input = pipeline.scheduler.add_noise(
|
| 296 |
+
denoised_pred.flatten(0, 1), torch.randn_like(denoised_pred.flatten(0, 1)),
|
| 297 |
+
timesteps[steps[si + 1]] * torch.ones([FRAMES_PER_CHUNK], device=device, dtype=torch.long),
|
| 298 |
+
).unflatten(0, denoised_pred.shape[:2])
|
| 299 |
+
output[:, start_frame:start_frame + FRAMES_PER_CHUNK] = denoised_pred
|
| 300 |
+
# Clean-context KV refresh: full DiT, timed separately, never added in.
|
| 301 |
+
ctx_start = torch.cuda.Event(enable_timing=True)
|
| 302 |
+
ctx_end = torch.cuda.Event(enable_timing=True)
|
| 303 |
+
ctx_start.record()
|
| 304 |
+
pipeline.generator(
|
| 305 |
+
noisy_image_or_video=denoised_pred, conditional_dict=conditional,
|
| 306 |
+
timestep=torch.ones_like(timestep) * pipeline.args.context_noise,
|
| 307 |
+
kv_cache=pipeline.kv_cache1, crossattn_cache=pipeline.crossattn_cache,
|
| 308 |
+
current_start=token_start)
|
| 309 |
+
ctx_end.record()
|
| 310 |
+
context_events.append((ctx_start, ctx_end))
|
| 311 |
+
previous_hidden = current_hidden
|
| 312 |
+
torch.cuda.synchronize()
|
| 313 |
+
timing = {
|
| 314 |
+
"denoise_dit_ms": sum(s.elapsed_time(e) for s, e in denoise_events),
|
| 315 |
+
"context_kv_dit_ms": sum(s.elapsed_time(e) for s, e in context_events),
|
| 316 |
+
"num_denoise_forwards": len(denoise_events),
|
| 317 |
+
}
|
| 318 |
+
video = pipeline.vae.decode_to_pixel(output, use_cache=False)
|
| 319 |
+
video = (video * 0.5 + 0.5).clamp(0, 1)
|
| 320 |
+
pipeline.vae.model.clear_cache()
|
| 321 |
+
return video, timing, {"full_forwards": full_calls, "predictor_forwards": predictor_calls}
|
| 322 |
+
|
| 323 |
+
def run_one(strategy, row):
|
| 324 |
+
set_seed(args.seed)
|
| 325 |
+
noise = torch.randn([1, n_chunks * FRAMES_PER_CHUNK, 16, 60, 104], device=device, dtype=torch.bfloat16)
|
| 326 |
+
conditional = pipeline.text_encoder(text_prompts=[row["extended_prompt"]])
|
| 327 |
+
wall0 = time.time()
|
| 328 |
+
if strategy["is_reference"]:
|
| 329 |
+
video, timing, counts = rollout(conditional, noise)
|
| 330 |
+
elif strategy.get("naive"):
|
| 331 |
+
video, timing, counts = rollout(conditional, noise, None, frozenset(),
|
| 332 |
+
NAIVE[strategy["naive"]])
|
| 333 |
+
else:
|
| 334 |
+
video, timing, counts = rollout(conditional, noise, predictors[strategy["model"]],
|
| 335 |
+
PATTERNS[strategy["pattern"]],
|
| 336 |
+
RUN_STEPS.get(strategy["pattern"]))
|
| 337 |
+
return video, timing, counts, time.time() - wall0
|
| 338 |
+
|
| 339 |
+
def record_path(strategy, row):
|
| 340 |
+
d = os.path.join(out_root, "per_prompt", strategy)
|
| 341 |
+
os.makedirs(d, exist_ok=True)
|
| 342 |
+
return os.path.join(d, f"{row['prompt_suite']}_{row['suite_index']:03d}.json")
|
| 343 |
+
|
| 344 |
+
def video_path(strategy, row):
|
| 345 |
+
d = os.path.join(out_root, "generated_videos", strategy, row["prompt_suite"])
|
| 346 |
+
os.makedirs(d, exist_ok=True)
|
| 347 |
+
return os.path.join(d, f"{row['suite_index']:03d}.mp4")
|
| 348 |
+
|
| 349 |
+
def is_done(strategy, row):
|
| 350 |
+
p = record_path(strategy, row)
|
| 351 |
+
if args.overwrite or not os.path.exists(p):
|
| 352 |
+
return False
|
| 353 |
+
try:
|
| 354 |
+
with open(p) as f:
|
| 355 |
+
rec = json.load(f)
|
| 356 |
+
return rec.get("status") == "complete" and os.path.exists(rec.get("video", ""))
|
| 357 |
+
except Exception:
|
| 358 |
+
return False
|
| 359 |
+
|
| 360 |
+
def write_json(path, payload):
|
| 361 |
+
tmp = path + ".tmp"
|
| 362 |
+
with open(tmp, "w") as f:
|
| 363 |
+
json.dump(payload, f, indent=2)
|
| 364 |
+
os.replace(tmp, path)
|
| 365 |
+
|
| 366 |
+
if shard_rows:
|
| 367 |
+
print("warm-up pass (not recorded)", flush=True)
|
| 368 |
+
for strategy in strategies:
|
| 369 |
+
run_one(strategy, shard_rows[0])
|
| 370 |
+
torch.cuda.empty_cache()
|
| 371 |
+
|
| 372 |
+
if args.retime_strategy_only:
|
| 373 |
+
targets = [s for s in strategies if not s["is_reference"]]
|
| 374 |
+
n_done = 0
|
| 375 |
+
for n, row in enumerate(shard_rows):
|
| 376 |
+
for strategy in targets:
|
| 377 |
+
rp = record_path(strategy["name"], row)
|
| 378 |
+
if not os.path.exists(rp):
|
| 379 |
+
continue
|
| 380 |
+
if args.written_before and os.path.getmtime(rp) >= args.written_before:
|
| 381 |
+
continue
|
| 382 |
+
rec = json.load(open(rp))
|
| 383 |
+
if rec.get("status") != "complete" or "generation_run_latency" in rec:
|
| 384 |
+
continue
|
| 385 |
+
_, timing, counts, _ = run_one(strategy, row)
|
| 386 |
+
assert counts["full_forwards"] == rec["cache_diagnostics"]["full_forwards"], rp
|
| 387 |
+
rec["generation_run_latency"] = {
|
| 388 |
+
"policy_latency_ms": rec["policy_latency_ms"],
|
| 389 |
+
"excluded_context_kv_latency_ms": rec["excluded_context_kv_latency_ms"]}
|
| 390 |
+
rec["policy_latency_ms"] = timing["denoise_dit_ms"]
|
| 391 |
+
rec["excluded_context_kv_latency_ms"] = timing["context_kv_dit_ms"]
|
| 392 |
+
rec["latency_retimed"] = "strategy-only rerun on an idle GPU (FFFF not re-run)"
|
| 393 |
+
write_json(rp, rec)
|
| 394 |
+
n_done += 1
|
| 395 |
+
if (n + 1) % 20 == 0:
|
| 396 |
+
print(f"[strategy retime shard {args.shard}] {n + 1}/{len(shard_rows)}", flush=True)
|
| 397 |
+
print(f"shard {args.shard}: {n_done} records re-timed", flush=True)
|
| 398 |
+
return
|
| 399 |
+
|
| 400 |
+
if args.retime_only:
|
| 401 |
+
# Protocol "matched FFFF": pair every strategy with an FFFF run of the same
|
| 402 |
+
# prompt in the same process, seconds apart, so contention cancels in the
|
| 403 |
+
# ratio. Only records whose pairing is missing or dirty are touched.
|
| 404 |
+
targets = [s for s in strategies if not s["is_reference"]]
|
| 405 |
+
for pass_index in range(args.retime_passes):
|
| 406 |
+
dirty = 0
|
| 407 |
+
for n, row in enumerate(shard_rows):
|
| 408 |
+
todo = []
|
| 409 |
+
for strategy in targets:
|
| 410 |
+
rp = record_path(strategy["name"], row)
|
| 411 |
+
if not os.path.exists(rp):
|
| 412 |
+
continue
|
| 413 |
+
rec = json.load(open(rp))
|
| 414 |
+
m = rec.get("matched_ffff_policy_latency_ms")
|
| 415 |
+
if m is None or m > args.retime_max_ffff_ms:
|
| 416 |
+
todo.append((strategy, rp, rec))
|
| 417 |
+
if not todo:
|
| 418 |
+
continue
|
| 419 |
+
dirty += len(todo)
|
| 420 |
+
_, ref_timing, _, _ = run_one(strategies[0], row)
|
| 421 |
+
for strategy, rp, rec in todo:
|
| 422 |
+
_, timing, _, _ = run_one(strategy, row)
|
| 423 |
+
rec["policy_latency_ms"] = timing["denoise_dit_ms"]
|
| 424 |
+
rec["excluded_context_kv_latency_ms"] = timing["context_kv_dit_ms"]
|
| 425 |
+
rec["matched_ffff_policy_latency_ms"] = ref_timing["denoise_dit_ms"]
|
| 426 |
+
rec["matched_ffff_context_kv_latency_ms"] = ref_timing["context_kv_dit_ms"]
|
| 427 |
+
rec["latency_source"] = "paired retime (same process as FFFF)"
|
| 428 |
+
write_json(rp, rec)
|
| 429 |
+
if (n + 1) % 10 == 0:
|
| 430 |
+
print(f"[retime pass {pass_index} shard {args.shard}] {n + 1}/{len(shard_rows)} "
|
| 431 |
+
f"ffff={ref_timing['denoise_dit_ms']:.0f}ms", flush=True)
|
| 432 |
+
print(f"[retime pass {pass_index} shard {args.shard}] re-timed {dirty} records", flush=True)
|
| 433 |
+
if dirty == 0:
|
| 434 |
+
break
|
| 435 |
+
return
|
| 436 |
+
|
| 437 |
+
t_start = time.time()
|
| 438 |
+
for n, row in enumerate(shard_rows):
|
| 439 |
+
todo = [s for s in strategies if not is_done(s["name"], row)]
|
| 440 |
+
if not todo:
|
| 441 |
+
continue
|
| 442 |
+
need_ref = any(not s["is_reference"] for s in todo)
|
| 443 |
+
ref_frames = None
|
| 444 |
+
ref_timing = None
|
| 445 |
+
ref_from_mp4 = False
|
| 446 |
+
if need_ref and os.path.exists(video_path(ref_name, row)) and not args.regenerate_ffff:
|
| 447 |
+
# Reference from the cfa_ffff run's MP4 (H.264, 40+ dB above the strategy
|
| 448 |
+
# PSNRs); FFFF is not regenerated, so there is no same-process FFFF timing
|
| 449 |
+
# and the speedup is taken against cfa_ffff's own records (aggregate.py).
|
| 450 |
+
vid, _, _ = read_video(video_path(ref_name, row), pts_unit="sec", output_format="TCHW")
|
| 451 |
+
ref_frames = vid.to(device, torch.float16) / 255.0
|
| 452 |
+
ref_from_mp4 = True
|
| 453 |
+
need_ref = False
|
| 454 |
+
for strategy in strategies:
|
| 455 |
+
if strategy["is_reference"]:
|
| 456 |
+
if strategy not in todo and not need_ref:
|
| 457 |
+
continue
|
| 458 |
+
elif strategy not in todo:
|
| 459 |
+
continue
|
| 460 |
+
video, timing, counts, wall = run_one(strategy, row)
|
| 461 |
+
frames = video[0]
|
| 462 |
+
if strategy["is_reference"]:
|
| 463 |
+
ref_frames = frames.detach().to(torch.float16)
|
| 464 |
+
ref_timing = timing
|
| 465 |
+
pix = PixelMetrics.identity(frames.shape[0])
|
| 466 |
+
else:
|
| 467 |
+
pix = metrics.compute(frames, ref_frames)
|
| 468 |
+
if strategy in todo:
|
| 469 |
+
vp = video_path(strategy["name"], row)
|
| 470 |
+
arr = (255.0 * rearrange(frames, "t c h w -> t h w c")).clamp(0, 255)
|
| 471 |
+
write_video(vp, arr.to(torch.uint8).cpu(), fps=args.fps)
|
| 472 |
+
compute_equiv = counts["full_forwards"] + PREDICTOR_COMPUTE_EQUIV * counts["predictor_forwards"]
|
| 473 |
+
model = strategy["model"]
|
| 474 |
+
naive = strategy.get("naive")
|
| 475 |
+
schedule = "F" * len(NAIVE[naive]) if naive else strategy["pattern"]
|
| 476 |
+
write_json(record_path(strategy["name"], row), {
|
| 477 |
+
"status": "complete",
|
| 478 |
+
"protocol": "Self-Forcing Extended-251 Full Evaluation",
|
| 479 |
+
"strategy": strategy["name"],
|
| 480 |
+
"base_model": "causal_forcing_a",
|
| 481 |
+
"method": "none" if model is None else f"predictor_{strategy['base_model']}",
|
| 482 |
+
"param": None if model is None else "pattern",
|
| 483 |
+
"param_value": None if model is None else strategy["pattern"],
|
| 484 |
+
"target_speedup": (n_chunks * NUM_STEPS) / compute_equiv,
|
| 485 |
+
"schedule": schedule,
|
| 486 |
+
"num_inference_steps": len(NAIVE[naive]) if naive else NUM_STEPS,
|
| 487 |
+
"first_chunk_schedule": "FFFF" if (naive or model) else None,
|
| 488 |
+
"predictor_config": None if model is None else predictor_configs[model],
|
| 489 |
+
"global_index": row["global_index"],
|
| 490 |
+
"prompt_suite": row["prompt_suite"],
|
| 491 |
+
"suite_index": row["suite_index"],
|
| 492 |
+
"prompt": row["extended_prompt"],
|
| 493 |
+
"original_prompt": row["original_prompt"],
|
| 494 |
+
"seed": args.seed,
|
| 495 |
+
"video": vp,
|
| 496 |
+
"num_frames": int(frames.shape[0]),
|
| 497 |
+
"height": int(frames.shape[2]),
|
| 498 |
+
"width": int(frames.shape[3]),
|
| 499 |
+
"fps": args.fps,
|
| 500 |
+
"policy_latency_ms": timing["denoise_dit_ms"],
|
| 501 |
+
"excluded_context_kv_latency_ms": timing["context_kv_dit_ms"],
|
| 502 |
+
# Same-process FFFF timing of this prompt (protocol "matched FFFF").
|
| 503 |
+
"matched_ffff_policy_latency_ms": ref_timing["denoise_dit_ms"] if ref_timing else None,
|
| 504 |
+
"matched_ffff_context_kv_latency_ms": ref_timing["context_kv_dit_ms"] if ref_timing else None,
|
| 505 |
+
"checkpoint_step": strategy.get("checkpoint_step"),
|
| 506 |
+
"num_chunks": n_chunks,
|
| 507 |
+
"wall_generation_s": wall,
|
| 508 |
+
"reference_strategy": ref_name,
|
| 509 |
+
"pixel_metrics_vs_ffff": pix,
|
| 510 |
+
"reference_source": "ffff_mp4" if ref_from_mp4 else "ffff_frames",
|
| 511 |
+
"cache_diagnostics": {
|
| 512 |
+
"denoise_forwards": timing["num_denoise_forwards"],
|
| 513 |
+
"full_forwards": counts["full_forwards"],
|
| 514 |
+
"predictor_forwards": counts["predictor_forwards"],
|
| 515 |
+
"predictor_compute_equivalent": PREDICTOR_COMPUTE_EQUIV,
|
| 516 |
+
"compute_equivalent_forwards": compute_equiv,
|
| 517 |
+
"middle_steps": n_chunks * 2,
|
| 518 |
+
"middle_compute_equivalent": compute_equiv - n_chunks * 2.0,
|
| 519 |
+
},
|
| 520 |
+
})
|
| 521 |
+
del frames, video
|
| 522 |
+
ref_frames = None
|
| 523 |
+
done = n + 1
|
| 524 |
+
rate = (time.time() - t_start) / done
|
| 525 |
+
print(f"[cfa shard {args.shard}] {done}/{len(shard_rows)} prompts "
|
| 526 |
+
f"({row['prompt_suite']}/{row['suite_index']:03d}) {rate:.1f}s/prompt "
|
| 527 |
+
f"eta {(len(shard_rows) - done) * rate / 60:.0f} min", flush=True)
|
| 528 |
+
print(f"shard {args.shard} done in {(time.time() - t_start) / 60:.1f} min", flush=True)
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
if __name__ == "__main__":
|
| 532 |
+
main()
|
eval/pixel_metrics.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PSNR / SSIM / LPIPS between a strategy's frames and its matched FFFF frames.
|
| 2 |
+
|
| 3 |
+
Computed on the decoded RGB tensors in [0, 1], before MP4 encoding, as the
|
| 4 |
+
protocol requires (section 8.1) -- video compression noise would otherwise leak
|
| 5 |
+
into the numbers.
|
| 6 |
+
|
| 7 |
+
SSIM is implemented here rather than taken from skimage: skimage needs ~30 ms per
|
| 8 |
+
480x832 frame on CPU, and this evaluation compares ~500k frames.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _gaussian_window(size=11, sigma=1.5, device=None, dtype=torch.float32):
|
| 16 |
+
coords = torch.arange(size, device=device, dtype=dtype) - (size - 1) / 2.0
|
| 17 |
+
g = torch.exp(-(coords ** 2) / (2 * sigma ** 2))
|
| 18 |
+
g = g / g.sum()
|
| 19 |
+
return g[:, None] @ g[None, :]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class PixelMetrics:
|
| 23 |
+
"""Frame-wise metrics against a reference clip. Call once per (strategy, prompt)."""
|
| 24 |
+
|
| 25 |
+
def __init__(self, device, lpips_net="alex", ssim_window=11, ssim_sigma=1.5):
|
| 26 |
+
self.device = device
|
| 27 |
+
self.window = _gaussian_window(ssim_window, ssim_sigma, device=device)[None, None]
|
| 28 |
+
self.window_size = ssim_window
|
| 29 |
+
import lpips
|
| 30 |
+
self.lpips = lpips.LPIPS(net=lpips_net).to(device).eval()
|
| 31 |
+
for p in self.lpips.parameters():
|
| 32 |
+
p.requires_grad_(False)
|
| 33 |
+
|
| 34 |
+
def ssim(self, x, y):
|
| 35 |
+
"""x, y: [T, C, H, W] in [0, 1]. Returns the mean over frames and channels."""
|
| 36 |
+
t, c, h, w = x.shape
|
| 37 |
+
win = self.window.expand(c, 1, self.window_size, self.window_size)
|
| 38 |
+
pad = self.window_size // 2
|
| 39 |
+
mu_x = F.conv2d(x, win, padding=pad, groups=c)
|
| 40 |
+
mu_y = F.conv2d(y, win, padding=pad, groups=c)
|
| 41 |
+
mu_x2, mu_y2, mu_xy = mu_x * mu_x, mu_y * mu_y, mu_x * mu_y
|
| 42 |
+
sig_x = F.conv2d(x * x, win, padding=pad, groups=c) - mu_x2
|
| 43 |
+
sig_y = F.conv2d(y * y, win, padding=pad, groups=c) - mu_y2
|
| 44 |
+
sig_xy = F.conv2d(x * y, win, padding=pad, groups=c) - mu_xy
|
| 45 |
+
c1, c2 = 0.01 ** 2, 0.03 ** 2
|
| 46 |
+
s = ((2 * mu_xy + c1) * (2 * sig_xy + c2)) / ((mu_x2 + mu_y2 + c1) * (sig_x + sig_y + c2))
|
| 47 |
+
# Drop the padded border, as skimage does with its default crop.
|
| 48 |
+
s = s[..., pad:-pad, pad:-pad]
|
| 49 |
+
return s.mean(dim=(1, 2, 3)) # per frame
|
| 50 |
+
|
| 51 |
+
@torch.no_grad()
|
| 52 |
+
def compute(self, frames, ref, chunk=16):
|
| 53 |
+
"""frames, ref: [T, C, H, W] float in [0, 1] on any device.
|
| 54 |
+
|
| 55 |
+
Returns per-prompt mean MSE (for the protocol's ratio-of-means PSNR),
|
| 56 |
+
plus mean SSIM and mean LPIPS over frames.
|
| 57 |
+
"""
|
| 58 |
+
frames = frames.to(self.device, torch.float32)
|
| 59 |
+
ref = ref.to(self.device, torch.float32)
|
| 60 |
+
assert frames.shape == ref.shape, (frames.shape, ref.shape)
|
| 61 |
+
|
| 62 |
+
mse_terms, ssim_terms, lpips_terms = [], [], []
|
| 63 |
+
for i in range(0, frames.shape[0], chunk):
|
| 64 |
+
a, b = frames[i:i + chunk], ref[i:i + chunk]
|
| 65 |
+
mse_terms.append(((a - b) ** 2).mean(dim=(1, 2, 3)))
|
| 66 |
+
ssim_terms.append(self.ssim(a, b))
|
| 67 |
+
lpips_terms.append(self.lpips(a * 2 - 1, b * 2 - 1).flatten())
|
| 68 |
+
|
| 69 |
+
mse = torch.cat(mse_terms)
|
| 70 |
+
ssim = torch.cat(ssim_terms)
|
| 71 |
+
lp = torch.cat(lpips_terms)
|
| 72 |
+
mean_mse = mse.mean().item()
|
| 73 |
+
return {
|
| 74 |
+
"mean_mse": mean_mse,
|
| 75 |
+
"psnr": -10.0 * torch.log10(torch.tensor(max(mean_mse, 1e-12))).item(),
|
| 76 |
+
"ssim": ssim.mean().item(),
|
| 77 |
+
"lpips": lp.mean().item(),
|
| 78 |
+
"num_frames": int(frames.shape[0]),
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
@staticmethod
|
| 82 |
+
def identity(num_frames):
|
| 83 |
+
"""Fixed self-comparison values the protocol pins for FFFF vs itself."""
|
| 84 |
+
return {"mean_mse": 1e-12, "psnr": 120.0, "ssim": 1.0, "lpips": 0.0,
|
| 85 |
+
"num_frames": int(num_frames)}
|
eval/retime_eval.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Paired re-timing of evaluated strategies against FFFF, prompt by prompt.
|
| 3 |
+
|
| 4 |
+
``generate_eval.py`` records each strategy's denoise latency, but when it is
|
| 5 |
+
re-run for *additional* strategies the FFFF reference is regenerated only for
|
| 6 |
+
the pixel metrics and its latency is not recorded, so the new records would be
|
| 7 |
+
compared against FFFF timings from a different run under different contention.
|
| 8 |
+
The protocol's speedup is "matched FFFF" (section 9.4): same prompt, same seed,
|
| 9 |
+
same conditions. This pass restores that: for every prompt it runs FFFF and
|
| 10 |
+
then each requested strategy back to back (denoise only, no decode, no metrics)
|
| 11 |
+
and writes the strategy's latency together with the FFFF latency measured
|
| 12 |
+
seconds earlier into the strategy's per-prompt record.
|
| 13 |
+
|
| 14 |
+
CUDA_VISIBLE_DEVICES=0 python eval/retime_eval.py --base self_forcing \
|
| 15 |
+
--strategies sf_taylorseer_FFxx_x1.3,sf_teacache_Fxxx_x3 \
|
| 16 |
+
--shard 0 --num-shards 4 --out-root eval_out
|
| 17 |
+
|
| 18 |
+
Records already carrying ``latency_source`` are skipped, so the pass resumes.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import json
|
| 23 |
+
import os
|
| 24 |
+
import sys
|
| 25 |
+
import time
|
| 26 |
+
|
| 27 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 28 |
+
sys.path.insert(0, ROOT)
|
| 29 |
+
|
| 30 |
+
from eval.strategies import load_strategies # noqa: E402
|
| 31 |
+
from harness import BASES, enter_base, load_pipeline # noqa: E402
|
| 32 |
+
|
| 33 |
+
MAPPING = os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json")
|
| 34 |
+
SOURCE = "retime_eval: paired with FFFF in the same process, denoise only"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def parse_args():
|
| 38 |
+
p = argparse.ArgumentParser()
|
| 39 |
+
p.add_argument("--base", choices=sorted(BASES), required=True)
|
| 40 |
+
p.add_argument("--strategies", required=True,
|
| 41 |
+
help="Comma-separated strategy names, or 'all' for every "
|
| 42 |
+
"non-reference strategy of the base")
|
| 43 |
+
p.add_argument("--shard", type=int, default=0)
|
| 44 |
+
p.add_argument("--num-shards", type=int, default=1)
|
| 45 |
+
p.add_argument("--out-root", default="eval_out")
|
| 46 |
+
p.add_argument("--seed", type=int, default=0)
|
| 47 |
+
p.add_argument("--num-latent-frames", type=int, default=21)
|
| 48 |
+
p.add_argument("--limit", type=int, default=None)
|
| 49 |
+
p.add_argument("--force", action="store_true", help="Re-time records already re-timed")
|
| 50 |
+
p.add_argument("--redo-ratio-bounds", default=None,
|
| 51 |
+
help="lo,hi: also re-time records whose measured/compute speedup "
|
| 52 |
+
"ratio falls outside this band -- a spike that hit the strategy "
|
| 53 |
+
"half of the pair rather than the FFFF half")
|
| 54 |
+
p.add_argument("--redo-above-ms", type=float, default=None,
|
| 55 |
+
help="Also re-time records whose matched FFFF latency exceeds this: "
|
| 56 |
+
"a neighbour's job hit that pair, and the pair is only fair "
|
| 57 |
+
"when both halves ran under the same conditions")
|
| 58 |
+
return p.parse_args()
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def main():
|
| 62 |
+
args = parse_args()
|
| 63 |
+
out_root = (args.out_root if os.path.isabs(args.out_root)
|
| 64 |
+
else os.path.join(ROOT, args.out_root))
|
| 65 |
+
|
| 66 |
+
with open(MAPPING) as f:
|
| 67 |
+
rows = json.load(f)["rows"]
|
| 68 |
+
if args.limit:
|
| 69 |
+
rows = rows[:args.limit]
|
| 70 |
+
shard_rows = rows[args.shard::args.num_shards]
|
| 71 |
+
|
| 72 |
+
all_strategies = load_strategies(args.base)
|
| 73 |
+
reference = all_strategies[0]
|
| 74 |
+
assert reference["is_reference"]
|
| 75 |
+
if args.strategies == "all":
|
| 76 |
+
wanted = [s for s in all_strategies if not s["is_reference"]]
|
| 77 |
+
else:
|
| 78 |
+
names = set(args.strategies.split(","))
|
| 79 |
+
wanted = [s for s in all_strategies if s["name"] in names]
|
| 80 |
+
missing = names - {s["name"] for s in wanted}
|
| 81 |
+
if missing:
|
| 82 |
+
sys.exit(f"unknown strategies for {args.base}: {sorted(missing)}")
|
| 83 |
+
|
| 84 |
+
print(f"base={args.base} shard {args.shard}/{args.num_shards}: "
|
| 85 |
+
f"{len(shard_rows)} prompts x ({reference['name']} + {len(wanted)} strategies)",
|
| 86 |
+
flush=True)
|
| 87 |
+
|
| 88 |
+
base = enter_base(args.base)
|
| 89 |
+
|
| 90 |
+
import torch
|
| 91 |
+
from utils.misc import set_seed
|
| 92 |
+
|
| 93 |
+
from cachelib import (CacheController, build_method, cached_inference, install,
|
| 94 |
+
parse_schedule)
|
| 95 |
+
|
| 96 |
+
torch.set_grad_enabled(False)
|
| 97 |
+
device = torch.device("cuda")
|
| 98 |
+
pipeline = load_pipeline(base)
|
| 99 |
+
num_steps = len(pipeline.denoising_step_list)
|
| 100 |
+
|
| 101 |
+
def record_path(strategy, row):
|
| 102 |
+
return os.path.join(out_root, "per_prompt", strategy,
|
| 103 |
+
f"{row['prompt_suite']}_{row['suite_index']:03d}.json")
|
| 104 |
+
|
| 105 |
+
def time_one(strategy, row):
|
| 106 |
+
kw = dict(indicator=strategy["indicator"], coefficients=strategy["coefficients"])
|
| 107 |
+
if strategy["method"] != "none":
|
| 108 |
+
kw[strategy["param"]] = strategy["value"]
|
| 109 |
+
first = strategy.get("first_chunk_schedule")
|
| 110 |
+
ctrl = CacheController(
|
| 111 |
+
build_method(strategy["method"], **kw), num_steps=num_steps,
|
| 112 |
+
forced_steps=parse_schedule(strategy.get("schedule", "FxxF"), num_steps),
|
| 113 |
+
first_chunk_forced_steps=(parse_schedule(first, num_steps) if first else None))
|
| 114 |
+
install(pipeline.generator.model, ctrl)
|
| 115 |
+
set_seed(args.seed)
|
| 116 |
+
noise = torch.randn([1, args.num_latent_frames, 16, 60, 104],
|
| 117 |
+
device=device, dtype=torch.bfloat16)
|
| 118 |
+
_, _, timing = cached_inference(pipeline, ctrl, noise, [row["extended_prompt"]],
|
| 119 |
+
decode=False)
|
| 120 |
+
return timing, ctrl.summary()
|
| 121 |
+
|
| 122 |
+
def write_json(path, payload):
|
| 123 |
+
tmp = path + ".tmp"
|
| 124 |
+
with open(tmp, "w") as f:
|
| 125 |
+
json.dump(payload, f, indent=2)
|
| 126 |
+
os.replace(tmp, path)
|
| 127 |
+
|
| 128 |
+
def needs(strategy, row):
|
| 129 |
+
p = record_path(strategy["name"], row)
|
| 130 |
+
if not os.path.exists(p):
|
| 131 |
+
return False # nothing to attach the timing to; generation must run first
|
| 132 |
+
with open(p) as f:
|
| 133 |
+
rec = json.load(f)
|
| 134 |
+
if rec.get("status") != "complete":
|
| 135 |
+
return False
|
| 136 |
+
if args.force or "latency_source" not in rec:
|
| 137 |
+
return True
|
| 138 |
+
if (args.redo_above_ms is not None
|
| 139 |
+
and rec.get("matched_ffff_policy_latency_ms", 0) > args.redo_above_ms):
|
| 140 |
+
return True
|
| 141 |
+
if args.redo_ratio_bounds:
|
| 142 |
+
lo, hi = (float(v) for v in args.redo_ratio_bounds.split(","))
|
| 143 |
+
d = rec["cache_diagnostics"]
|
| 144 |
+
compute_x = d["denoise_forwards"] / max(d["compute_equivalent_forwards"], 1e-6)
|
| 145 |
+
measured_x = rec["matched_ffff_policy_latency_ms"] / rec["policy_latency_ms"]
|
| 146 |
+
if not (lo <= measured_x / compute_x <= hi):
|
| 147 |
+
return True
|
| 148 |
+
return False
|
| 149 |
+
|
| 150 |
+
if shard_rows:
|
| 151 |
+
print("warm-up pass (not recorded)", flush=True)
|
| 152 |
+
for s in [reference] + wanted:
|
| 153 |
+
time_one(s, shard_rows[0])
|
| 154 |
+
torch.cuda.empty_cache()
|
| 155 |
+
|
| 156 |
+
t0 = time.time()
|
| 157 |
+
for n, row in enumerate(shard_rows):
|
| 158 |
+
todo = [s for s in wanted if needs(s, row)]
|
| 159 |
+
if not todo:
|
| 160 |
+
continue
|
| 161 |
+
ref_timing, _ = time_one(reference, row)
|
| 162 |
+
for s in todo:
|
| 163 |
+
timing, summary = time_one(s, row)
|
| 164 |
+
p = record_path(s["name"], row)
|
| 165 |
+
with open(p) as f:
|
| 166 |
+
rec = json.load(f)
|
| 167 |
+
rec["policy_latency_ms_generation_run"] = rec.get("policy_latency_ms")
|
| 168 |
+
rec["policy_latency_ms"] = timing["denoise_dit_ms"]
|
| 169 |
+
rec["excluded_context_kv_latency_ms"] = timing["context_kv_dit_ms"]
|
| 170 |
+
rec["matched_ffff_policy_latency_ms"] = ref_timing["denoise_dit_ms"]
|
| 171 |
+
rec["latency_source"] = SOURCE
|
| 172 |
+
# The compute fraction is deterministic; recording it again guards
|
| 173 |
+
# against a mismatch between the two runs.
|
| 174 |
+
rec["cache_diagnostics"]["compute_equivalent_forwards_retime"] = summary.get(
|
| 175 |
+
"compute_equivalent_forwards")
|
| 176 |
+
write_json(p, rec)
|
| 177 |
+
done = n + 1
|
| 178 |
+
rate = (time.time() - t0) / done
|
| 179 |
+
print(f"[{args.base} retime shard {args.shard}] {done}/{len(shard_rows)} "
|
| 180 |
+
f"({row['prompt_suite']}/{row['suite_index']:03d}) ffff={ref_timing['denoise_dit_ms']:.0f}ms "
|
| 181 |
+
f"{rate:.1f}s/prompt eta {(len(shard_rows) - done) * rate / 60:.0f} min",
|
| 182 |
+
flush=True)
|
| 183 |
+
|
| 184 |
+
print(f"retime shard {args.shard} done in {(time.time() - t0) / 60:.1f} min", flush=True)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
if __name__ == "__main__":
|
| 188 |
+
main()
|
eval/retime_until_clean.sh
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Self-healing driver for the paired re-timing on a shared node.
|
| 3 |
+
#
|
| 4 |
+
# Neighbours' jobs both inflate timings and, twice now, took our processes down
|
| 5 |
+
# with them. So: loop. Each pass counts the records that still need a fair
|
| 6 |
+
# pairing (never timed, FFFF half contended, or measured/compute ratio outside a
|
| 7 |
+
# sane band), launches one shard per *currently free* GPU among $GPUS, waits, and
|
| 8 |
+
# repeats until nothing is left or MAX_PASSES is hit. Then aggregate.
|
| 9 |
+
set -u
|
| 10 |
+
C=/local/zoubin/cz/projects/comparison
|
| 11 |
+
PY=/local/zoubin/cz/envs/self_forcing/bin/python
|
| 12 |
+
LOG="$1"
|
| 13 |
+
GPUS=(${GPUS:-4 5 6 7})
|
| 14 |
+
THRESH_MS=${THRESH_MS:-3500}
|
| 15 |
+
RATIO=${RATIO:-0.5,1.1}
|
| 16 |
+
MAX_PASSES=${MAX_PASSES:-12}
|
| 17 |
+
MIN_FREE=${MIN_FREE:-2} # do not start a pass on fewer free GPUs than this
|
| 18 |
+
WAIT=${WAIT:-120} # seconds between scans while waiting for GPUs
|
| 19 |
+
NEW_SF=sf_teacache_Fxxx_x3,sf_flowcache_Fxxx_x3,sf_taylorseer_FFxx_x1.3,sf_taylorseer_FFxx_x1.6,sf_taylorseer_FFxx_x2,sf_motioncache_Fxxx_x3
|
| 20 |
+
NEW_CF=cf_teacache_Fxxx_x3,cf_flowcache_Fxxx_x3,cf_taylorseer_FFxx_x1.3,cf_taylorseer_FFxx_x1.6,cf_taylorseer_FFxx_x2,cf_motioncache_Fxxx_x3
|
| 21 |
+
|
| 22 |
+
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
|
| 23 |
+
export HF_HOME=/local/zoubin/cz/.cache/huggingface HF_HUB_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 24 |
+
export TRANSFORMERS_CACHE=/local/zoubin/cz/.cache/huggingface/hub TORCH_HOME=/local/zoubin/cz/.cache/torch
|
| 25 |
+
export XDG_CACHE_HOME=/local/zoubin/cz/.cache
|
| 26 |
+
cd "$C"
|
| 27 |
+
|
| 28 |
+
pending() { # how many records of $1's strategies still need work
|
| 29 |
+
$PY - "$1" "$THRESH_MS" "$RATIO" <<'PYEOF'
|
| 30 |
+
import glob, json, sys
|
| 31 |
+
strats, thresh, ratio = sys.argv[1].split(","), float(sys.argv[2]), [float(v) for v in sys.argv[3].split(",")]
|
| 32 |
+
n = 0
|
| 33 |
+
for s in strats:
|
| 34 |
+
for p in glob.glob(f"eval_out/per_prompt/{s}/*.json"):
|
| 35 |
+
r = json.load(open(p))
|
| 36 |
+
if "latency_source" not in r or r["matched_ffff_policy_latency_ms"] > thresh:
|
| 37 |
+
n += 1; continue
|
| 38 |
+
d = r["cache_diagnostics"]
|
| 39 |
+
cx = d["denoise_forwards"] / max(d["compute_equivalent_forwards"], 1e-6)
|
| 40 |
+
mx = r["matched_ffff_policy_latency_ms"] / r["policy_latency_ms"]
|
| 41 |
+
if not (ratio[0] <= mx / cx <= ratio[1]):
|
| 42 |
+
n += 1
|
| 43 |
+
print(n)
|
| 44 |
+
PYEOF
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
free_gpus() { # members of GPUS that are actually quiet: little memory AND no compute
|
| 48 |
+
# Memory alone is not enough -- a VBench scorer holds 2 GB and saturates the SMs,
|
| 49 |
+
# and that alone inflated a whole pass by 25-80%. Sample utilisation 3x over 3 s.
|
| 50 |
+
for g in "${GPUS[@]}"; do
|
| 51 |
+
used=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits -i "$g" 2>/dev/null | tr -d ' ')
|
| 52 |
+
[ -z "$used" ] || [ "$used" -ge 3000 ] && continue
|
| 53 |
+
peak=0
|
| 54 |
+
for _ in 1 2 3; do
|
| 55 |
+
u=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits -i "$g" 2>/dev/null | tr -d ' ')
|
| 56 |
+
[ -n "$u" ] && [ "$u" -gt "$peak" ] && peak=$u
|
| 57 |
+
sleep 1
|
| 58 |
+
done
|
| 59 |
+
[ "$peak" -lt 10 ] && echo "$g"
|
| 60 |
+
done
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
# Only GPUs nobody else is using are ever touched; waiting never counts as a pass.
|
| 64 |
+
pass=0
|
| 65 |
+
while [ "$pass" -lt "$MAX_PASSES" ]; do
|
| 66 |
+
n_sf=$(pending "$NEW_SF"); n_cf=$(pending "$NEW_CF")
|
| 67 |
+
echo "=== scan ($(date +%H:%M:%S)): pending sf=$n_sf cf=$n_cf, passes so far $pass ==="
|
| 68 |
+
[ "$n_sf" -eq 0 ] && [ "$n_cf" -eq 0 ] && break
|
| 69 |
+
mapfile -t FREE < <(free_gpus)
|
| 70 |
+
if [ "${#FREE[@]}" -lt "$MIN_FREE" ]; then
|
| 71 |
+
echo "only ${#FREE[@]} free GPU(s) among ${GPUS[*]} (need $MIN_FREE); waiting ${WAIT}s"
|
| 72 |
+
sleep "$WAIT"; continue
|
| 73 |
+
fi
|
| 74 |
+
pass=$((pass+1))
|
| 75 |
+
echo "using GPUs: ${FREE[*]}"
|
| 76 |
+
for BASE in self_forcing causal_forcing; do
|
| 77 |
+
if [ "$BASE" = self_forcing ]; then S=$NEW_SF; n=$n_sf; else S=$NEW_CF; n=$n_cf; fi
|
| 78 |
+
[ "$n" -eq 0 ] && continue
|
| 79 |
+
pids=()
|
| 80 |
+
for i in "${!FREE[@]}"; do
|
| 81 |
+
CUDA_VISIBLE_DEVICES=${FREE[$i]} $PY eval/retime_eval.py --base "$BASE" --strategies "$S" \
|
| 82 |
+
--shard "$i" --num-shards "${#FREE[@]}" --out-root eval_out \
|
| 83 |
+
--redo-above-ms "$THRESH_MS" --redo-ratio-bounds "$RATIO" \
|
| 84 |
+
> "$LOG/retime_${BASE}_pass${pass}_$i.log" 2>&1 &
|
| 85 |
+
pids+=($!)
|
| 86 |
+
done
|
| 87 |
+
for p in "${pids[@]}"; do wait "$p"; done
|
| 88 |
+
echo " $BASE pass $pass finished ($(date +%H:%M:%S))"
|
| 89 |
+
done
|
| 90 |
+
done
|
| 91 |
+
echo "=== final pending: sf=$(pending "$NEW_SF") cf=$(pending "$NEW_CF") ==="
|
| 92 |
+
echo "=== aggregating ($(date +%H:%M:%S)) ==="
|
| 93 |
+
# --only: the predictor evaluation shares eval_out/ and may still be incomplete.
|
| 94 |
+
$PY eval/aggregate.py --out-root "$C/eval_out" --only '^(sf|cf)_' 2>&1 | tee "$LOG/aggregate.log"
|
| 95 |
+
python3 experiment_md.py 2>&1 | tee "$LOG/experiment_md.log"
|
| 96 |
+
echo "ALL RETIME DONE $(date +%H:%M:%S)"
|
eval/run_all_phases.sh
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Waits for the running generation phase, then scores and aggregates.
|
| 3 |
+
set -u
|
| 4 |
+
C=/local/zoubin/cz/projects/comparison
|
| 5 |
+
LOG="$1"
|
| 6 |
+
|
| 7 |
+
echo "waiting for generation to finish ..."
|
| 8 |
+
while ! grep -q "ALL GENERATION DONE" "$LOG/generation_driver.log" 2>/dev/null; do
|
| 9 |
+
if ! pgrep -f "[g]enerate_eval.py" > /dev/null 2>&1 \
|
| 10 |
+
&& ! pgrep -f "[r]un_generation.sh" > /dev/null 2>&1; then
|
| 11 |
+
echo "generation processes gone without the done marker; stopping."
|
| 12 |
+
exit 1
|
| 13 |
+
fi
|
| 14 |
+
sleep 60
|
| 15 |
+
done
|
| 16 |
+
echo "generation complete $(date +%H:%M:%S)"
|
| 17 |
+
|
| 18 |
+
"$C/eval/run_vbench_all.sh" "$LOG" 2>&1 | tee "$LOG/vbench_driver.log"
|
| 19 |
+
|
| 20 |
+
echo "=== aggregating $(date +%H:%M:%S) ==="
|
| 21 |
+
/local/zoubin/cz/envs/self_forcing/bin/python "$C/eval/aggregate.py" \
|
| 22 |
+
--out-root "$C/eval_out" 2>&1 | tee "$LOG/aggregate.log"
|
| 23 |
+
echo "ALL PHASES DONE $(date +%H:%M:%S)"
|
eval/run_generation.sh
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Phase 1: generate the Extended-251 videos for every strategy, one base at a time,
|
| 3 |
+
# sharded by prompt across the GPUs in $GPUS (default 0-3). Generation runs in the base self_forcing env --
|
| 4 |
+
# the same interpreter and package set the speed study used -- so the eval videos
|
| 5 |
+
# are produced by exactly the model stack that was benchmarked.
|
| 6 |
+
set -u
|
| 7 |
+
C=/local/zoubin/cz/projects/comparison
|
| 8 |
+
PY=/local/zoubin/cz/envs/self_forcing/bin/python
|
| 9 |
+
LOG="$1"; shift
|
| 10 |
+
NSHARDS=${NSHARDS:-4}
|
| 11 |
+
GPUS=(${GPUS:-0 1 2 3})
|
| 12 |
+
|
| 13 |
+
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
|
| 14 |
+
export TORCH_HOME=/local/zoubin/cz/.cache/torch
|
| 15 |
+
export HF_HOME=/local/zoubin/cz/.cache/huggingface
|
| 16 |
+
export HF_HUB_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 17 |
+
export TRANSFORMERS_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 18 |
+
export XDG_CACHE_HOME=/local/zoubin/cz/.cache
|
| 19 |
+
|
| 20 |
+
cd "$C"
|
| 21 |
+
for BASE in ${BASES:-self_forcing causal_forcing}; do
|
| 22 |
+
echo "=== generation: $BASE ($(date +%H:%M:%S)) ==="
|
| 23 |
+
pids=()
|
| 24 |
+
for i in $(seq 0 $((NSHARDS-1))); do
|
| 25 |
+
CUDA_VISIBLE_DEVICES=${GPUS[$i]} nohup $PY eval/generate_eval.py \
|
| 26 |
+
--base "$BASE" --shard $i --num-shards $NSHARDS --out-root eval_out \
|
| 27 |
+
> "$LOG/gen_${BASE}_$i.log" 2>&1 &
|
| 28 |
+
pids+=($!)
|
| 29 |
+
done
|
| 30 |
+
for p in "${pids[@]}"; do wait "$p"; done
|
| 31 |
+
echo "=== generation done: $BASE ($(date +%H:%M:%S)) ==="
|
| 32 |
+
done
|
| 33 |
+
echo "ALL GENERATION DONE"
|
eval/run_predictor_eval.sh
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Extended-251 evaluation of the Causal-Forcing-a Predictors (cfa_* strategies):
|
| 3 |
+
# phase 1 generation sharded across $GPUS, phase 2 VBench on the not-yet-scored
|
| 4 |
+
# strategies (run_vbench_all.sh skips scored ones), phase 3 aggregate.
|
| 5 |
+
# GPUS="0 1 2 3" eval/run_predictor_eval.sh logs_cfa
|
| 6 |
+
set -u
|
| 7 |
+
C=/local/zoubin/cz/projects/comparison
|
| 8 |
+
PY=/local/zoubin/cz/envs/self_forcing/bin/python
|
| 9 |
+
LOG="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"; shift
|
| 10 |
+
GPUS=(${GPUS:-0 1 2 3})
|
| 11 |
+
NSHARDS=${#GPUS[@]}
|
| 12 |
+
mkdir -p "$LOG"
|
| 13 |
+
|
| 14 |
+
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
|
| 15 |
+
export TORCH_HOME=/local/zoubin/cz/.cache/torch
|
| 16 |
+
export HF_HOME=/local/zoubin/cz/.cache/huggingface
|
| 17 |
+
export HF_HUB_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 18 |
+
export TRANSFORMERS_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 19 |
+
export XDG_CACHE_HOME=/local/zoubin/cz/.cache
|
| 20 |
+
|
| 21 |
+
cd "$C"
|
| 22 |
+
echo "=== cfa generation ($(date +%H:%M:%S)) gpus=${GPUS[*]} ==="
|
| 23 |
+
pids=()
|
| 24 |
+
for i in $(seq 0 $((NSHARDS-1))); do
|
| 25 |
+
CUDA_VISIBLE_DEVICES=${GPUS[$i]} $PY eval/generate_predictor_eval.py \
|
| 26 |
+
--shard $i --num-shards $NSHARDS --out-root eval_out "$@" \
|
| 27 |
+
> "$LOG/gen_cfa_$i.log" 2>&1 &
|
| 28 |
+
pids+=($!)
|
| 29 |
+
done
|
| 30 |
+
status=0
|
| 31 |
+
for p in "${pids[@]}"; do wait "$p" || status=1; done
|
| 32 |
+
echo "=== cfa generation done ($(date +%H:%M:%S)) status=$status ==="
|
| 33 |
+
[[ $status == 0 ]] || { echo "generation failed; not scoring"; exit 1; }
|
| 34 |
+
|
| 35 |
+
GPUS="${GPUS[*]}" NGPU=$NSHARDS bash "$C/eval/run_vbench_all.sh" "$LOG" 2>&1 | tee "$LOG/vbench_driver.log"
|
| 36 |
+
|
| 37 |
+
echo "=== aggregating ($(date +%H:%M:%S)) ==="
|
| 38 |
+
$PY "$C/eval/aggregate.py" --out-root "$C/eval_out" 2>&1 | tee "$LOG/aggregate.log"
|
| 39 |
+
echo "ALL CFA PHASES DONE $(date +%H:%M:%S)"
|
eval/run_predictor_eval_s2.sh
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Extended-251 evaluation of the Stage-2 EMA Predictor: generation sharded over
|
| 3 |
+
# $GPUS, VBench only for the strategies generated here, then aggregate.
|
| 4 |
+
# GPUS="0 1 2" bash eval/run_predictor_eval_s2.sh logs_cfa_s2
|
| 5 |
+
set -u
|
| 6 |
+
C=/local/zoubin/cz/projects/comparison
|
| 7 |
+
V=/local/zoubin/cz/projects/VBench
|
| 8 |
+
PY=/local/zoubin/cz/envs/self_forcing/bin/python
|
| 9 |
+
VPY=/local/zoubin/cz/envs/vbench_eval/bin/python
|
| 10 |
+
LOG="$(mkdir -p "$1" && cd "$1" && pwd)"
|
| 11 |
+
MODELS="${MODELS:-atc_chunk_s2}"
|
| 12 |
+
PATTERNS="${PATTERNS:-FPFF,FPPF,FPPP}"
|
| 13 |
+
GPUS=(${GPUS:-0 1 2})
|
| 14 |
+
NSHARDS=${#GPUS[@]}
|
| 15 |
+
|
| 16 |
+
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
|
| 17 |
+
export TORCH_HOME=/local/zoubin/cz/.cache/torch
|
| 18 |
+
export HF_HOME=/local/zoubin/cz/.cache/huggingface
|
| 19 |
+
export HF_HUB_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 20 |
+
export TRANSFORMERS_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 21 |
+
export XDG_CACHE_HOME=/local/zoubin/cz/.cache
|
| 22 |
+
export VBENCH_CACHE_DIR=/local/zoubin/cz/.cache/vbench
|
| 23 |
+
|
| 24 |
+
cd "$C"
|
| 25 |
+
echo "=== generation ($(date +%H:%M:%S)) gpus=${GPUS[*]} models=$MODELS patterns=$PATTERNS ==="
|
| 26 |
+
pids=()
|
| 27 |
+
for i in $(seq 0 $((NSHARDS-1))); do
|
| 28 |
+
CUDA_VISIBLE_DEVICES=${GPUS[$i]} $PY eval/generate_predictor_eval.py \
|
| 29 |
+
--shard $i --num-shards $NSHARDS --out-root eval_out --models "$MODELS" --patterns "$PATTERNS" \
|
| 30 |
+
> "$LOG/gen_$i.log" 2>&1 &
|
| 31 |
+
pids+=($!)
|
| 32 |
+
done
|
| 33 |
+
status=0; for p in "${pids[@]}"; do wait "$p" || status=1; done
|
| 34 |
+
echo "=== generation done ($(date +%H:%M:%S)) status=$status ==="
|
| 35 |
+
[[ $status == 0 ]] || { echo "generation failed; not scoring"; exit 1; }
|
| 36 |
+
|
| 37 |
+
strats=()
|
| 38 |
+
for m in ${MODELS//,/ }; do for pat in ${PATTERNS//,/ }; do strats+=("cfa_${m}_$(echo "$pat" | tr 'A-Z' 'a-z')"); done; done
|
| 39 |
+
echo "=== vbench ($(date +%H:%M:%S)) strategies=${strats[*]} ==="
|
| 40 |
+
export PYTHONPATH="$V:$C"
|
| 41 |
+
cd "$V"
|
| 42 |
+
pids=()
|
| 43 |
+
for g in $(seq 0 $((NSHARDS-1))); do
|
| 44 |
+
(
|
| 45 |
+
for i in $(seq $g $NSHARDS $(( ${#strats[@]} - 1 ))); do
|
| 46 |
+
s="${strats[$i]}"
|
| 47 |
+
[[ -f "$C/eval_out/vbench/scores/$s.json" ]] && { echo "skip $s"; continue; }
|
| 48 |
+
echo "[gpu ${GPUS[$g]}] $s $(date +%H:%M:%S)"
|
| 49 |
+
CUDA_VISIBLE_DEVICES=${GPUS[$g]} $VPY "$C/eval/run_vbench.py" --strategy "$s" --out-root "$C/eval_out" \
|
| 50 |
+
> "$LOG/vbench_$s.log" 2>&1 || echo "[gpu ${GPUS[$g]}] FAILED $s"
|
| 51 |
+
done
|
| 52 |
+
) &
|
| 53 |
+
pids+=($!)
|
| 54 |
+
done
|
| 55 |
+
for p in "${pids[@]}"; do wait "$p"; done
|
| 56 |
+
echo "=== aggregating ($(date +%H:%M:%S)) ==="
|
| 57 |
+
cd "$C"
|
| 58 |
+
$PY eval/aggregate.py --out-root "$C/eval_out" --allow-incomplete > "$LOG/aggregate.log" 2>&1
|
| 59 |
+
echo "ALL S2 PHASES DONE $(date +%H:%M:%S)"
|
eval/run_predictor_retime_s2.sh
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Paired re-timing of the Stage-2 predictor strategies against FFFF (same process).
|
| 3 |
+
set -u
|
| 4 |
+
C=/local/zoubin/cz/projects/comparison
|
| 5 |
+
PY=/local/zoubin/cz/envs/self_forcing/bin/python
|
| 6 |
+
LOG="$(mkdir -p "$1" && cd "$1" && pwd)"
|
| 7 |
+
GPUS=(${GPUS:-0 1 2}); NSHARDS=${#GPUS[@]}
|
| 8 |
+
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt TORCH_HOME=/local/zoubin/cz/.cache/torch HF_HOME=/local/zoubin/cz/.cache/huggingface HF_HUB_CACHE=/local/zoubin/cz/.cache/huggingface/hub XDG_CACHE_HOME=/local/zoubin/cz/.cache
|
| 9 |
+
cd "$C"
|
| 10 |
+
echo "=== retime ($(date +%H:%M:%S)) gpus=${GPUS[*]} ==="
|
| 11 |
+
pids=()
|
| 12 |
+
for i in $(seq 0 $((NSHARDS-1))); do
|
| 13 |
+
CUDA_VISIBLE_DEVICES=${GPUS[$i]} $PY eval/generate_predictor_eval.py --shard $i --num-shards $NSHARDS --out-root eval_out \
|
| 14 |
+
--models "${MODELS:-atc_chunk_s2}" --patterns "${PATTERNS:-FPFF,FPPF,FPPP}" --retime-only > "$LOG/retime_$i.log" 2>&1 &
|
| 15 |
+
pids+=($!)
|
| 16 |
+
done
|
| 17 |
+
status=0; for p in "${pids[@]}"; do wait "$p" || status=1; done
|
| 18 |
+
echo "=== retime done ($(date +%H:%M:%S)) status=$status ==="
|
| 19 |
+
$PY eval/aggregate.py --out-root "$C/eval_out" --allow-incomplete > "$LOG/aggregate_retime.log" 2>&1
|
| 20 |
+
$PY experiment_md.py > "$LOG/experiment_md.log" 2>&1
|
| 21 |
+
echo "RETIME DONE $(date +%H:%M:%S)"
|
eval/run_predictor_vbench_phase.sh
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# VBench + aggregate only (generation already complete).
|
| 3 |
+
set -u
|
| 4 |
+
C=/local/zoubin/cz/projects/comparison
|
| 5 |
+
PY=/local/zoubin/cz/envs/self_forcing/bin/python
|
| 6 |
+
LOG="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
|
| 7 |
+
GPUS="${GPUS:-0 1 2 3}"
|
| 8 |
+
read -r -a gpu_list <<< "$GPUS"
|
| 9 |
+
cd "$C"
|
| 10 |
+
GPUS="$GPUS" NGPU=${#gpu_list[@]} bash "$C/eval/run_vbench_all.sh" "$LOG" 2>&1 | tee "$LOG/vbench_driver.log"
|
| 11 |
+
echo "=== aggregating ($(date +%H:%M:%S)) ==="
|
| 12 |
+
$PY "$C/eval/aggregate.py" --out-root "$C/eval_out" 2>&1 | tee "$LOG/aggregate.log"
|
| 13 |
+
echo "ALL CFA PHASES DONE $(date +%H:%M:%S)"
|
eval/run_retime.sh
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Phase 3: paired re-timing of strategies added after the first generation run
|
| 3 |
+
# (their FFFF reference was regenerated for the pixel metrics but not timed).
|
| 4 |
+
# Runs after VBench so the GPUs carry nothing else. Usage:
|
| 5 |
+
# eval/run_retime.sh <logdir> <comma-separated strategy names, per base via
|
| 6 |
+
# STRATS_SF / STRATS_CF env, or "all">
|
| 7 |
+
set -u
|
| 8 |
+
# DISABLED (Sep 6): the user asked for no more FFFF re-timing passes. Speedups
|
| 9 |
+
# are now the ratio of mean latencies against the FFFF strategy's own records;
|
| 10 |
+
# where those come from a different run the absolute ms are not comparable and
|
| 11 |
+
# the table says so. Kept for reference only.
|
| 12 |
+
echo "eval/run_retime.sh is disabled; re-timing is no longer part of the pipeline." >&2
|
| 13 |
+
exit 1
|
| 14 |
+
C=/local/zoubin/cz/projects/comparison
|
| 15 |
+
PY=/local/zoubin/cz/envs/self_forcing/bin/python
|
| 16 |
+
LOG="$1"; shift
|
| 17 |
+
NSHARDS=${NSHARDS:-4}
|
| 18 |
+
GPUS=(${GPUS:-0 1 2 3})
|
| 19 |
+
EXTRA=${EXTRA:-}
|
| 20 |
+
|
| 21 |
+
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
|
| 22 |
+
export HF_HOME=/local/zoubin/cz/.cache/huggingface
|
| 23 |
+
export HF_HUB_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 24 |
+
export TRANSFORMERS_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 25 |
+
export TORCH_HOME=/local/zoubin/cz/.cache/torch
|
| 26 |
+
export XDG_CACHE_HOME=/local/zoubin/cz/.cache
|
| 27 |
+
|
| 28 |
+
cd "$C"
|
| 29 |
+
for BASE in self_forcing causal_forcing; do
|
| 30 |
+
if [ "$BASE" = self_forcing ]; then S="${STRATS_SF:-all}"; else S="${STRATS_CF:-all}"; fi
|
| 31 |
+
echo "=== retime: $BASE [$S] ($(date +%H:%M:%S)) ==="
|
| 32 |
+
pids=()
|
| 33 |
+
for i in $(seq 0 $((NSHARDS-1))); do
|
| 34 |
+
CUDA_VISIBLE_DEVICES=${GPUS[$i]} nohup $PY eval/retime_eval.py \
|
| 35 |
+
--base "$BASE" --strategies "$S" --shard $i --num-shards $NSHARDS \
|
| 36 |
+
--out-root eval_out $EXTRA > "$LOG/retime_${BASE}_$i.log" 2>&1 &
|
| 37 |
+
pids+=($!)
|
| 38 |
+
done
|
| 39 |
+
for p in "${pids[@]}"; do wait "$p"; done
|
| 40 |
+
echo "=== retime done: $BASE ($(date +%H:%M:%S)) ==="
|
| 41 |
+
done
|
| 42 |
+
echo "ALL RETIME DONE"
|
eval/run_vbench.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Score one strategy on the eight protocol VBench dimensions.
|
| 3 |
+
|
| 4 |
+
Standard VBench 0.1.5 metric code is used unmodified. What is built here is only
|
| 5 |
+
the evaluation metadata file, because the protocol needs two things the stock
|
| 6 |
+
`vbench_standard` mode cannot express:
|
| 7 |
+
|
| 8 |
+
* `prompt_en` must be the *extended* prompt actually fed to the generator
|
| 9 |
+
(section 6.1), not the short canonical one;
|
| 10 |
+
* `scene` must keep the canonical `auxiliary_info` keywords.
|
| 11 |
+
|
| 12 |
+
The file has exactly the shape `build_full_info_json` produces, and each
|
| 13 |
+
`compute_<dimension>` selects the rows that list it, so per-suite scoping
|
| 14 |
+
(72 / 93 / 86 videos) falls out of the mapping rather than being hard-coded.
|
| 15 |
+
|
| 16 |
+
python eval/run_vbench.py --strategy sf_ffff --out-root eval_out
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import argparse
|
| 20 |
+
import json
|
| 21 |
+
import os
|
| 22 |
+
import sys
|
| 23 |
+
import time
|
| 24 |
+
|
| 25 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 26 |
+
sys.path.insert(0, ROOT)
|
| 27 |
+
|
| 28 |
+
MAPPING = os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json")
|
| 29 |
+
|
| 30 |
+
SUITE_DIMENSIONS = {
|
| 31 |
+
"subject_consistency": ["subject_consistency", "motion_smoothness", "dynamic_degree"],
|
| 32 |
+
"overall_consistency": ["overall_consistency", "aesthetic_quality", "imaging_quality"],
|
| 33 |
+
"scene": ["scene", "background_consistency"],
|
| 34 |
+
}
|
| 35 |
+
DIMENSIONS = [d for dims in SUITE_DIMENSIONS.values() for d in dims]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def parse_args():
|
| 39 |
+
p = argparse.ArgumentParser()
|
| 40 |
+
p.add_argument("--strategy", required=True)
|
| 41 |
+
p.add_argument("--out-root", default="eval_out")
|
| 42 |
+
p.add_argument("--dimensions", default=None,
|
| 43 |
+
help="Comma-separated subset (default: all eight)")
|
| 44 |
+
p.add_argument("--allow-partial", action="store_true",
|
| 45 |
+
help="Score even if fewer than 251 videos exist (smoke tests only)")
|
| 46 |
+
p.add_argument("--overwrite", action="store_true",
|
| 47 |
+
help="Recompute dimensions whose raw result file already exists")
|
| 48 |
+
p.add_argument("--threads", type=int, default=None,
|
| 49 |
+
help="torch/OpenMP CPU threads for this process (scoring is CPU-bound; "
|
| 50 |
+
"several scorers on one node oversubscribe the cores)")
|
| 51 |
+
return p.parse_args()
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def main():
|
| 55 |
+
args = parse_args()
|
| 56 |
+
out_root = (args.out_root if os.path.isabs(args.out_root)
|
| 57 |
+
else os.path.join(ROOT, args.out_root))
|
| 58 |
+
dims = args.dimensions.split(",") if args.dimensions else list(DIMENSIONS)
|
| 59 |
+
|
| 60 |
+
with open(MAPPING) as f:
|
| 61 |
+
mapping = json.load(f)
|
| 62 |
+
|
| 63 |
+
video_root = os.path.join(out_root, "generated_videos", args.strategy)
|
| 64 |
+
rows, missing = [], []
|
| 65 |
+
for r in mapping["rows"]:
|
| 66 |
+
vp = os.path.join(video_root, r["prompt_suite"], f"{r['suite_index']:03d}.mp4")
|
| 67 |
+
if not os.path.exists(vp):
|
| 68 |
+
missing.append(vp)
|
| 69 |
+
continue
|
| 70 |
+
entry = {
|
| 71 |
+
# The extended prompt is what the video was generated from, so it is
|
| 72 |
+
# what overall_consistency and scene must be scored against.
|
| 73 |
+
"prompt_en": r["extended_prompt"],
|
| 74 |
+
"dimension": [d for d in SUITE_DIMENSIONS[r["prompt_suite"]]],
|
| 75 |
+
"video_list": [vp],
|
| 76 |
+
"global_index": r["global_index"],
|
| 77 |
+
"prompt_suite": r["prompt_suite"],
|
| 78 |
+
"suite_index": r["suite_index"],
|
| 79 |
+
}
|
| 80 |
+
if "auxiliary_info" in r:
|
| 81 |
+
entry["auxiliary_info"] = r["auxiliary_info"]
|
| 82 |
+
rows.append(entry)
|
| 83 |
+
|
| 84 |
+
if missing and not args.allow_partial:
|
| 85 |
+
print(f"ERROR: {len(missing)} of {len(mapping['rows'])} videos missing for "
|
| 86 |
+
f"{args.strategy}; refusing to score an incomplete strategy.")
|
| 87 |
+
for m in missing[:5]:
|
| 88 |
+
print(" ", m)
|
| 89 |
+
return 1
|
| 90 |
+
print(f"{args.strategy}: {len(rows)} videos, dimensions={dims}", flush=True)
|
| 91 |
+
|
| 92 |
+
scores_dir = os.path.join(out_root, "vbench", "raw_results", args.strategy)
|
| 93 |
+
os.makedirs(scores_dir, exist_ok=True)
|
| 94 |
+
info_path = os.path.join(scores_dir, "full_info.json")
|
| 95 |
+
# Atomic: several per-dimension processes may start on the same strategy.
|
| 96 |
+
with open(info_path + f".{os.getpid()}", "w") as f:
|
| 97 |
+
json.dump(rows, f, indent=2)
|
| 98 |
+
os.replace(info_path + f".{os.getpid()}", info_path)
|
| 99 |
+
|
| 100 |
+
import importlib
|
| 101 |
+
|
| 102 |
+
import torch
|
| 103 |
+
if args.threads:
|
| 104 |
+
torch.set_num_threads(args.threads)
|
| 105 |
+
from vbench.utils import init_submodules
|
| 106 |
+
|
| 107 |
+
device = torch.device("cuda")
|
| 108 |
+
|
| 109 |
+
def raw_path(d):
|
| 110 |
+
return os.path.join(scores_dir, f"{d}.json")
|
| 111 |
+
|
| 112 |
+
# A dimension already scored (its raw file exists) is reused, so dimensions can
|
| 113 |
+
# be spread over several processes and a killed run resumes where it stopped.
|
| 114 |
+
todo = [d for d in dims if args.overwrite or not os.path.exists(raw_path(d))]
|
| 115 |
+
submodules = init_submodules(todo, local=True, read_frame=False) if todo else {}
|
| 116 |
+
|
| 117 |
+
for d in todo:
|
| 118 |
+
t0 = time.time()
|
| 119 |
+
module = importlib.import_module(f"vbench.{d}")
|
| 120 |
+
fn = getattr(module, f"compute_{d}")
|
| 121 |
+
score, per_video = fn(info_path, device, submodules[d])
|
| 122 |
+
tmp = raw_path(d) + ".tmp"
|
| 123 |
+
with open(tmp, "w") as f:
|
| 124 |
+
json.dump({"dimension": d, "score": score, "video_results": per_video,
|
| 125 |
+
"seconds": time.time() - t0}, f, indent=2)
|
| 126 |
+
os.replace(tmp, raw_path(d))
|
| 127 |
+
print(f" {d:24s} {score:.6f} ({len(per_video)} videos, "
|
| 128 |
+
f"{time.time() - t0:.0f}s)", flush=True)
|
| 129 |
+
torch.cuda.empty_cache()
|
| 130 |
+
|
| 131 |
+
# The strategy's score file is written only once all eight dimensions exist.
|
| 132 |
+
missing_dims = [d for d in DIMENSIONS if not os.path.exists(raw_path(d))]
|
| 133 |
+
if missing_dims:
|
| 134 |
+
print(f"{args.strategy}: {len(DIMENSIONS) - len(missing_dims)}/{len(DIMENSIONS)} "
|
| 135 |
+
f"dimensions scored; missing {missing_dims}", flush=True)
|
| 136 |
+
return 0
|
| 137 |
+
results = {}
|
| 138 |
+
for d in DIMENSIONS:
|
| 139 |
+
with open(raw_path(d)) as f:
|
| 140 |
+
raw = json.load(f)
|
| 141 |
+
results[d] = {"score": raw["score"], "num_videos": len(raw["video_results"]),
|
| 142 |
+
"seconds": raw.get("seconds")}
|
| 143 |
+
|
| 144 |
+
out_path = os.path.join(out_root, "vbench", "scores", f"{args.strategy}.json")
|
| 145 |
+
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
| 146 |
+
payload = {
|
| 147 |
+
"strategy": args.strategy,
|
| 148 |
+
"vbench_long": False,
|
| 149 |
+
"num_videos": len(rows),
|
| 150 |
+
"raw": {d: results[d]["score"] for d in DIMENSIONS},
|
| 151 |
+
"detail": results,
|
| 152 |
+
"full_info": info_path,
|
| 153 |
+
}
|
| 154 |
+
tmp = out_path + ".tmp"
|
| 155 |
+
with open(tmp, "w") as f:
|
| 156 |
+
json.dump(payload, f, indent=2)
|
| 157 |
+
os.replace(tmp, out_path)
|
| 158 |
+
print(f"wrote {out_path}", flush=True)
|
| 159 |
+
return 0
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
if __name__ == "__main__":
|
| 163 |
+
sys.exit(main())
|
eval/run_vbench_all.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Phase 2: score every strategy on the eight protocol dimensions.
|
| 3 |
+
# VBench runs in the vbench_eval venv (torchao 0.7.0 / transformers 4.46.3 /
|
| 4 |
+
# fairscale), because the base env's torchao 0.17.0 is unimportable under torch
|
| 5 |
+
# 2.5.1 and takes transformers' BertModel -- and therefore Tag2Text/scene -- with
|
| 6 |
+
# it. Generation deliberately stays in the base env.
|
| 7 |
+
set -u
|
| 8 |
+
C=/local/zoubin/cz/projects/comparison
|
| 9 |
+
V=/local/zoubin/cz/projects/VBench
|
| 10 |
+
PY=/local/zoubin/cz/envs/vbench_eval/bin/python
|
| 11 |
+
LOG="$1"; shift
|
| 12 |
+
NGPU=${NGPU:-4}
|
| 13 |
+
GPUS=(${GPUS:-0 1 2 3})
|
| 14 |
+
|
| 15 |
+
export PYTHONPATH="$V:$C"
|
| 16 |
+
export VBENCH_CACHE_DIR=/local/zoubin/cz/.cache/vbench
|
| 17 |
+
export HF_HOME=/local/zoubin/cz/.cache/huggingface
|
| 18 |
+
export HF_HUB_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 19 |
+
export TRANSFORMERS_CACHE=/local/zoubin/cz/.cache/huggingface/hub
|
| 20 |
+
export XDG_CACHE_HOME=/local/zoubin/cz/.cache
|
| 21 |
+
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
|
| 22 |
+
export TORCH_HOME=/local/zoubin/cz/.cache/torch
|
| 23 |
+
|
| 24 |
+
mapfile -t STRATS < <(ls "$C/eval_out/per_prompt")
|
| 25 |
+
echo "scoring ${#STRATS[@]} strategies across $NGPU GPUs"
|
| 26 |
+
|
| 27 |
+
cd "$V"
|
| 28 |
+
pids=()
|
| 29 |
+
for g in $(seq 0 $((NGPU-1))); do
|
| 30 |
+
(
|
| 31 |
+
for i in $(seq $g $NGPU $(( ${#STRATS[@]} - 1 ))); do
|
| 32 |
+
s="${STRATS[$i]}"
|
| 33 |
+
if [ -f "$C/eval_out/vbench/scores/$s.json" ]; then
|
| 34 |
+
echo "skip $s (already scored)"; continue
|
| 35 |
+
fi
|
| 36 |
+
echo "[gpu $g] $s $(date +%H:%M:%S)"
|
| 37 |
+
CUDA_VISIBLE_DEVICES=${GPUS[$g]} $PY "$C/eval/run_vbench.py" \
|
| 38 |
+
--strategy "$s" --out-root "$C/eval_out" > "$LOG/vbench_$s.log" 2>&1 \
|
| 39 |
+
|| echo "[gpu $g] FAILED $s"
|
| 40 |
+
done
|
| 41 |
+
) &
|
| 42 |
+
pids+=($!)
|
| 43 |
+
done
|
| 44 |
+
for p in "${pids[@]}"; do wait "$p"; done
|
| 45 |
+
echo "ALL VBENCH DONE"
|
eval/setup_vbench_models.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Download the VBench weights the eight protocol dimensions need.
|
| 3 |
+
|
| 4 |
+
Everything lands in $VBENCH_CACHE_DIR so the run does not depend on whatever a
|
| 5 |
+
user's ~/.cache happens to hold. `local=True` is used wherever VBench supports
|
| 6 |
+
it, so CLIP and DINO come from the cache directory too rather than from the clip
|
| 7 |
+
package's own download path.
|
| 8 |
+
"""
|
| 9 |
+
import os
|
| 10 |
+
import sys
|
| 11 |
+
|
| 12 |
+
DIMS = ["subject_consistency", "background_consistency", "motion_smoothness",
|
| 13 |
+
"dynamic_degree", "aesthetic_quality", "imaging_quality", "scene",
|
| 14 |
+
"overall_consistency"]
|
| 15 |
+
|
| 16 |
+
def main():
|
| 17 |
+
from vbench.utils import init_submodules, CACHE_DIR
|
| 18 |
+
print("CACHE_DIR =", CACHE_DIR, flush=True)
|
| 19 |
+
for d in DIMS:
|
| 20 |
+
print(f"--- {d} ---", flush=True)
|
| 21 |
+
try:
|
| 22 |
+
got = init_submodules([d], local=True, read_frame=False)
|
| 23 |
+
print(f" ok: {got.get(d)}", flush=True)
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f" FAILED: {type(e).__name__}: {e}", flush=True)
|
| 26 |
+
print("done", flush=True)
|
| 27 |
+
|
| 28 |
+
if __name__ == "__main__":
|
| 29 |
+
sys.exit(main())
|
eval/strategies.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The strategy list for the Extended-251 evaluation.
|
| 2 |
+
|
| 3 |
+
FFFF (every denoising step full) is the reference for each base model; the cache
|
| 4 |
+
strategies are the operating points settled by finalize.py / retime.py, read back
|
| 5 |
+
from results/ so the evaluation cannot silently drift from the speed study.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import glob
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 13 |
+
|
| 14 |
+
METHOD_ORDER = ["teacache", "flowcache", "taylorseer", "motioncache"]
|
| 15 |
+
# FlowCache is frozen: finished operating points stay in the tables, no new ones
|
| 16 |
+
# are added. ``final_*_v2.json`` files (the measured-speedup targeted points)
|
| 17 |
+
# therefore never contain it.
|
| 18 |
+
DROPPED_FOR_NEW = {"flowcache"}
|
| 19 |
+
BASES = ["self_forcing", "causal_forcing"]
|
| 20 |
+
SHORT = {"self_forcing": "sf", "causal_forcing": "cf"}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def load_strategies(base):
|
| 24 |
+
"""[{name, base, method, param, value, target, coefficients, indicator}, ...]
|
| 25 |
+
|
| 26 |
+
The first entry is always FFFF.
|
| 27 |
+
"""
|
| 28 |
+
coeff_path = os.path.join(ROOT, f"results/coeff_{base}.json")
|
| 29 |
+
coefficients = None
|
| 30 |
+
if os.path.exists(coeff_path):
|
| 31 |
+
with open(coeff_path) as f:
|
| 32 |
+
coefficients = json.load(f)["coefficients"]
|
| 33 |
+
|
| 34 |
+
out = [{
|
| 35 |
+
"name": f"{SHORT[base]}_ffff",
|
| 36 |
+
"base": base, "method": "none", "param": None, "value": None,
|
| 37 |
+
"target": 1.0, "coefficients": None, "indicator": "modulated_input",
|
| 38 |
+
"schedule": "FxxF", "is_reference": True, "sampler": "renoise",
|
| 39 |
+
}]
|
| 40 |
+
|
| 41 |
+
rows = []
|
| 42 |
+
for p in sorted(glob.glob(os.path.join(ROOT, f"results/final_{base}*.json"))):
|
| 43 |
+
with open(p) as f:
|
| 44 |
+
rows += json.load(f)["rows"]
|
| 45 |
+
rows.sort(key=lambda r: (METHOD_ORDER.index(r["method"])
|
| 46 |
+
if r["method"] in METHOD_ORDER else 9,
|
| 47 |
+
r.get("schedule", "FxxF") != "FxxF",
|
| 48 |
+
r["target_speedup"]))
|
| 49 |
+
for r in rows:
|
| 50 |
+
# The original FxxF study keeps its unsuffixed names; other schedules
|
| 51 |
+
# carry the schedule so they never collide with those records.
|
| 52 |
+
sched = r.get("schedule", "FxxF")
|
| 53 |
+
tag = "" if sched.upper() == "FXXF" else f"_{sched}"
|
| 54 |
+
out.append({
|
| 55 |
+
"name": r.get("name") or f"{SHORT[base]}_{r['method']}{tag}_x{r['target_speedup']:g}",
|
| 56 |
+
"schedule": sched,
|
| 57 |
+
"num_inference_steps": r.get("num_inference_steps"),
|
| 58 |
+
"first_chunk_schedule": r.get("first_chunk_schedule"),
|
| 59 |
+
"sampler": r.get("sampler", "renoise"),
|
| 60 |
+
"base": base,
|
| 61 |
+
"method": r["method"],
|
| 62 |
+
"param": r["param"],
|
| 63 |
+
"value": r["value"],
|
| 64 |
+
"target": r["target_speedup"],
|
| 65 |
+
"coefficients": coefficients,
|
| 66 |
+
"indicator": "modulated_input",
|
| 67 |
+
"is_reference": False,
|
| 68 |
+
"compute_equivalent_forwards": r.get("compute_equivalent_forwards"),
|
| 69 |
+
})
|
| 70 |
+
return out
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def all_strategies():
|
| 74 |
+
return {b: load_strategies(b) for b in BASES}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
if __name__ == "__main__":
|
| 78 |
+
for b, s in all_strategies().items():
|
| 79 |
+
print(f"{b}: {len(s)} strategies")
|
| 80 |
+
for x in s:
|
| 81 |
+
v = "-" if x["value"] is None else f"{x['param']}={x['value']:.6g}"
|
| 82 |
+
print(f" {x['name']:28s} {v}")
|
eval/test_protocol.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Unit tests for the evaluation protocol's section 12 checklist.
|
| 3 |
+
|
| 4 |
+
python eval/test_protocol.py
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import tempfile
|
| 11 |
+
|
| 12 |
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 13 |
+
sys.path.insert(0, ROOT)
|
| 14 |
+
|
| 15 |
+
from eval import aggregate as agg # noqa: E402
|
| 16 |
+
|
| 17 |
+
MAPPING = os.path.join(ROOT, "assets/vbench8_extended_subset_mapping.json")
|
| 18 |
+
FULL_INFO = "/local/zoubin/cz/projects/VBench/vbench/VBench_full_info.json"
|
| 19 |
+
PROMPT_DIR = "/local/zoubin/cz/projects/Self-Forcing/prompts/vbench"
|
| 20 |
+
|
| 21 |
+
FAILURES = []
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def check(name, cond, detail=""):
|
| 25 |
+
print(f" {'PASS' if cond else 'FAIL'} {name}" + (f" {detail}" if detail else ""))
|
| 26 |
+
if not cond:
|
| 27 |
+
FAILURES.append(name)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_mapping():
|
| 31 |
+
print("946 -> 251 mapping")
|
| 32 |
+
with open(MAPPING) as f:
|
| 33 |
+
m = json.load(f)
|
| 34 |
+
rows = m["rows"]
|
| 35 |
+
check("mapping has 251 rows", len(rows) == 251, f"got {len(rows)}")
|
| 36 |
+
counts = {s: sum(1 for r in rows if r["prompt_suite"] == s)
|
| 37 |
+
for s in ("subject_consistency", "overall_consistency", "scene")}
|
| 38 |
+
check("suite counts 72/93/86",
|
| 39 |
+
counts == {"subject_consistency": 72, "overall_consistency": 93, "scene": 86},
|
| 40 |
+
str(counts))
|
| 41 |
+
check("global_index unique",
|
| 42 |
+
len({r["global_index"] for r in rows}) == len(rows))
|
| 43 |
+
for s in counts:
|
| 44 |
+
idx = [r["suite_index"] for r in rows if r["prompt_suite"] == s]
|
| 45 |
+
check(f"{s} suite_index contiguous", idx == list(range(len(idx))))
|
| 46 |
+
check("every scene row keeps auxiliary_info",
|
| 47 |
+
all("auxiliary_info" in r for r in rows if r["prompt_suite"] == "scene"))
|
| 48 |
+
check("extended prompts non-empty",
|
| 49 |
+
all(r["extended_prompt"].strip() for r in rows))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def test_canonical_order():
|
| 53 |
+
print("canonical prompt order is enforced")
|
| 54 |
+
with open(FULL_INFO) as f:
|
| 55 |
+
info = json.load(f)
|
| 56 |
+
with open(os.path.join(PROMPT_DIR, "all_dimension.txt"), encoding="utf-8") as f:
|
| 57 |
+
short = [l.rstrip("\n") for l in f]
|
| 58 |
+
check("946 short prompts", len(short) == 946, f"got {len(short)}")
|
| 59 |
+
check("short prompt order == VBench_full_info",
|
| 60 |
+
all(a["prompt_en"].strip() == b.strip() for a, b in zip(info, short)))
|
| 61 |
+
|
| 62 |
+
# A mismatched prompt file must be refused, not silently fuzzy-matched.
|
| 63 |
+
with open(MAPPING) as f:
|
| 64 |
+
rows = json.load(f)["rows"]
|
| 65 |
+
with open(os.path.join(PROMPT_DIR, "all_dimension_extended.txt"), encoding="utf-8") as f:
|
| 66 |
+
ext = [l.rstrip("\n") for l in f]
|
| 67 |
+
check("mapping extended prompt matches its global_index",
|
| 68 |
+
all(r["extended_prompt"] == ext[r["global_index"]] for r in rows))
|
| 69 |
+
check("mapping original prompt matches its global_index",
|
| 70 |
+
all(r["original_prompt"] == short[r["global_index"]] for r in rows))
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def test_normalize_and_aggregate():
|
| 74 |
+
print("normalize + Quality/Semantic/Selected aggregation")
|
| 75 |
+
lo_raw = {d: agg.NORMALIZE_RANGE[d][0] for d in agg.DIMENSIONS}
|
| 76 |
+
hi_raw = {d: agg.NORMALIZE_RANGE[d][1] for d in agg.DIMENSIONS}
|
| 77 |
+
check("normalize maps min -> 0",
|
| 78 |
+
all(abs(v) < 1e-12 for v in agg.normalize(lo_raw).values()))
|
| 79 |
+
check("normalize maps max -> 1",
|
| 80 |
+
all(abs(v - 1.0) < 1e-12 for v in agg.normalize(hi_raw).values()))
|
| 81 |
+
|
| 82 |
+
n = agg.normalize(hi_raw)
|
| 83 |
+
q, s = agg.quality_score(n), agg.semantic_score(n)
|
| 84 |
+
check("quality of all-ones == 1", abs(q - 1.0) < 1e-12, f"got {q}")
|
| 85 |
+
check("semantic of all-ones == 1", abs(s - 1.0) < 1e-12, f"got {s}")
|
| 86 |
+
check("selected of all-ones == 1", abs(agg.selected_score(q, s) - 1.0) < 1e-12)
|
| 87 |
+
|
| 88 |
+
# dynamic_degree carries weight 0.5, everything else in Quality carries 1.0.
|
| 89 |
+
n0 = {d: 0.0 for d in agg.DIMENSIONS}
|
| 90 |
+
n_dd = dict(n0, dynamic_degree=1.0)
|
| 91 |
+
check("dynamic_degree weight is 0.5/5.5",
|
| 92 |
+
abs(agg.quality_score(n_dd) - 0.5 / 5.5) < 1e-12)
|
| 93 |
+
n_sc = dict(n0, subject_consistency=1.0)
|
| 94 |
+
check("other quality dims weight 1/5.5",
|
| 95 |
+
abs(agg.quality_score(n_sc) - 1.0 / 5.5) < 1e-12)
|
| 96 |
+
check("selected weights Quality:Semantic 4:1",
|
| 97 |
+
abs(agg.selected_score(1.0, 0.0) - 0.8) < 1e-12
|
| 98 |
+
and abs(agg.selected_score(0.0, 1.0) - 0.2) < 1e-12)
|
| 99 |
+
check("selected is not the plain mean of 8 normalized dims",
|
| 100 |
+
abs(agg.selected_score(agg.quality_score(n_dd), agg.semantic_score(n_dd))
|
| 101 |
+
- sum(n_dd.values()) / 8.0) > 1e-6)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _rec(strategy, idx, lat, ctx, mse, frames=81):
|
| 105 |
+
return {
|
| 106 |
+
"status": "complete", "strategy": strategy, "base_model": "self_forcing",
|
| 107 |
+
"method": "none" if strategy.endswith("ffff") else "teacache",
|
| 108 |
+
"target_speedup": 1.0, "global_index": idx, "prompt_suite": "scene",
|
| 109 |
+
"suite_index": idx, "policy_latency_ms": lat,
|
| 110 |
+
"excluded_context_kv_latency_ms": ctx,
|
| 111 |
+
"pixel_metrics_vs_ffff": {"mean_mse": mse, "psnr": 0.0, "ssim": 1.0,
|
| 112 |
+
"lpips": 0.0, "num_frames": frames},
|
| 113 |
+
"cache_diagnostics": {"compute_equivalent_forwards": 28.0},
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def test_latency_and_completeness():
|
| 118 |
+
print("latency accounting and the completeness gate")
|
| 119 |
+
import math
|
| 120 |
+
|
| 121 |
+
# ratio of means, not the mean of per-prompt ratios
|
| 122 |
+
ffff = [_rec("sf_ffff", i, lat, 800.0, 1e-12) for i, lat in enumerate([100.0, 300.0])]
|
| 123 |
+
cand = [_rec("sf_x", i, lat, 800.0, 1e-4) for i, lat in enumerate([50.0, 250.0])]
|
| 124 |
+
ratio_of_means = 100.0 * (1 - (sum(r["policy_latency_ms"] for r in cand) / 2)
|
| 125 |
+
/ (sum(r["policy_latency_ms"] for r in ffff) / 2))
|
| 126 |
+
mean_of_ratios = 100.0 * (1 - ((50.0 / 100.0) + (250.0 / 300.0)) / 2)
|
| 127 |
+
check("speedup uses ratio of means", abs(ratio_of_means - 25.0) < 1e-9,
|
| 128 |
+
f"{ratio_of_means:.3f}%")
|
| 129 |
+
check("ratio of means differs from mean of ratios",
|
| 130 |
+
abs(ratio_of_means - mean_of_ratios) > 1.0)
|
| 131 |
+
|
| 132 |
+
check("context/KV DiT is a separate field, never inside policy latency",
|
| 133 |
+
all("excluded_context_kv_latency_ms" in r
|
| 134 |
+
and r["policy_latency_ms"] != r["policy_latency_ms"]
|
| 135 |
+
+ r["excluded_context_kv_latency_ms"] for r in cand))
|
| 136 |
+
|
| 137 |
+
mse = sum(r["pixel_metrics_vs_ffff"]["mean_mse"] for r in cand) / len(cand)
|
| 138 |
+
check("PSNR aggregates MSE first, then converts once",
|
| 139 |
+
abs(-10.0 * math.log10(max(mse, 1e-12)) - 40.0) < 1e-9)
|
| 140 |
+
check("pixel metrics span all 81 frames",
|
| 141 |
+
{r["pixel_metrics_vs_ffff"]["num_frames"] for r in cand} == {81})
|
| 142 |
+
|
| 143 |
+
with tempfile.TemporaryDirectory() as td:
|
| 144 |
+
for name, recs in (("sf_ffff", ffff), ("sf_x", cand)):
|
| 145 |
+
d = os.path.join(td, "per_prompt", name)
|
| 146 |
+
os.makedirs(d)
|
| 147 |
+
for r in recs:
|
| 148 |
+
with open(os.path.join(d, f"scene_{r['suite_index']:03d}.json"), "w") as f:
|
| 149 |
+
json.dump(r, f)
|
| 150 |
+
argv = sys.argv
|
| 151 |
+
sys.argv = ["aggregate", "--out-root", td, "--expect", "251"]
|
| 152 |
+
try:
|
| 153 |
+
rc = agg.main()
|
| 154 |
+
finally:
|
| 155 |
+
sys.argv = argv
|
| 156 |
+
check("aggregation refuses a strategy that is not 251", rc == 1, f"rc={rc}")
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def main():
|
| 160 |
+
for fn in (test_mapping, test_canonical_order, test_normalize_and_aggregate,
|
| 161 |
+
test_latency_and_completeness):
|
| 162 |
+
fn()
|
| 163 |
+
print()
|
| 164 |
+
if FAILURES:
|
| 165 |
+
print(f"{len(FAILURES)} FAILED: {FAILURES}")
|
| 166 |
+
return 1
|
| 167 |
+
print("all protocol unit tests passed")
|
| 168 |
+
return 0
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
if __name__ == "__main__":
|
| 172 |
+
sys.exit(main())
|
eval_out/generated_videos/cf_ffff/overall_consistency/000.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d3b9313a8d35ed942c8056953a8a24e8ad5c2eb6d51b84dbbb7263b3a135d9ee
|
| 3 |
+
size 718017
|
eval_out/generated_videos/cf_ffff/overall_consistency/001.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:97434fa492a782f473db53f5325943455e38f44c2ec0b765a9e94facc1b41f64
|
| 3 |
+
size 678798
|
eval_out/generated_videos/cf_ffff/overall_consistency/002.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8cc8240a9fc4555de544e0131304dd301803dab2a1867f8efe73d36a6686ed27
|
| 3 |
+
size 665068
|
eval_out/generated_videos/cf_ffff/overall_consistency/003.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dfeeb51a34c8db083f840497bc64bd6850f361e57b03435d436c0625cf1aa614
|
| 3 |
+
size 652636
|
eval_out/generated_videos/cf_ffff/overall_consistency/004.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1bcf14ef30751d4cc7ca646615758207443e7848e99c61e91a62566a17301b3a
|
| 3 |
+
size 624330
|
eval_out/generated_videos/cf_ffff/overall_consistency/005.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5a003819fae2c958584f41480ae48397ed99cb2afcb0bee20a30aa8359adf985
|
| 3 |
+
size 619500
|
eval_out/generated_videos/cf_ffff/overall_consistency/006.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:510c4b85f51b3605bdb24602644f27c2865ad8cfb7d0f6d105fc1c2fe5027bbf
|
| 3 |
+
size 667321
|
eval_out/generated_videos/cf_ffff/overall_consistency/007.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cb5b16a9bdf0ba7990fcf6ad75f18a2a0dc8142ca10f2276716c8136ace79e3c
|
| 3 |
+
size 692210
|
eval_out/generated_videos/cf_ffff/overall_consistency/008.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:93f6f41136b394e1e642a060d2783281692481f08e725a4933e872cf321be58d
|
| 3 |
+
size 538371
|
eval_out/generated_videos/cf_ffff/overall_consistency/009.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5669c892dd2fdbce0fcfb2ee2f0221645ad620c72562b1bfd3ad1d8a3ad40202
|
| 3 |
+
size 649393
|
eval_out/generated_videos/cf_ffff/overall_consistency/010.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d05a3ee90af4a46d5cef630591125ad542ec7695b9c25f5279572b9d536d6568
|
| 3 |
+
size 640856
|
eval_out/generated_videos/cf_ffff/overall_consistency/011.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9aceedf023cd61aac971d5933f802ad3e75770296b10888a2bd5d8fb2294f211
|
| 3 |
+
size 673335
|
eval_out/generated_videos/cf_ffff/overall_consistency/012.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1742fc927aee1bea4a58cfb703a771181f5558ac95eec019bf82bbb60684ebe6
|
| 3 |
+
size 643474
|
eval_out/generated_videos/cf_ffff/overall_consistency/013.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8dc175733a2dd86a2a86a5cd181f2990003b1532149aaec82623d841fa710276
|
| 3 |
+
size 697151
|
eval_out/generated_videos/cf_ffff/overall_consistency/014.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:53b34417aa633a8a16f67d5569cd9acdaae30016bca4b0a12e89297af99a09ab
|
| 3 |
+
size 564467
|
eval_out/generated_videos/cf_ffff/overall_consistency/015.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5a0bbec98e10b7de4a5edb3ba14d7318683afef3c89587c44f66468c9bbfd705
|
| 3 |
+
size 607298
|
eval_out/generated_videos/cf_ffff/overall_consistency/016.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d9feefa47ae677af86818c9e3dee84155fe4cc7374a425c9a6d828195fb72361
|
| 3 |
+
size 693258
|
eval_out/generated_videos/cf_ffff/overall_consistency/017.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c1f29a4bb28547296142bcde8935031005095a0d0fd35f7d46c9c4f0c7ef77f4
|
| 3 |
+
size 647002
|
eval_out/generated_videos/cf_ffff/overall_consistency/019.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:938b5f61acfd80ca568e800a618032bcb090ed37130378b32e51284d41979519
|
| 3 |
+
size 635121
|