Cccccz commited on
Commit
ebad435
·
verified ·
1 Parent(s): bc29ee3

Upload tests

Browse files
tests/test_atc_fusion.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ from unittest.mock import patch
3
+
4
+ import torch
5
+ import torch.nn.functional as F
6
+ from torch import nn
7
+
8
+ from predictor_training.atc_fusion import ATCFusion, MultiInputMLP, StateTransport
9
+ from predictor_training.single_block import SingleBlockPredictor
10
+ from wan.modules.model import rope_params
11
+
12
+
13
+ def small_freqs(head_dim: int = 8) -> torch.Tensor:
14
+ return torch.cat(
15
+ [
16
+ rope_params(32, head_dim - 4 * (head_dim // 6)),
17
+ rope_params(32, 2 * (head_dim // 6)),
18
+ rope_params(32, 2 * (head_dim // 6)),
19
+ ],
20
+ dim=1,
21
+ )
22
+
23
+
24
+ def reference_attention(
25
+ q: torch.Tensor, k: torch.Tensor, v: torch.Tensor
26
+ ) -> torch.Tensor:
27
+ scores = torch.einsum("blhd,bshd->bhls", q, k) / q.shape[-1] ** 0.5
28
+ probability = scores.float().softmax(dim=-1).to(v.dtype)
29
+ return torch.einsum("bhls,bshd->blhd", probability, v)
30
+
31
+
32
+ class DummyBlock(nn.Module):
33
+ num_heads = 1
34
+
35
+ def forward(self, value: torch.Tensor, **_kwargs) -> torch.Tensor:
36
+ return value
37
+
38
+
39
+ class ATCFusionTest(unittest.TestCase):
40
+ def setUp(self) -> None:
41
+ torch.manual_seed(0)
42
+ self.grid = torch.tensor([[2, 2, 2]], dtype=torch.long)
43
+ self.freqs = small_freqs()
44
+ self.current = torch.randn(1, 8, 8)
45
+ self.previous = torch.randn(1, 8, 8)
46
+
47
+ def test_multi_input_mlp_matches_explicit_concat(self) -> None:
48
+ module = MultiInputMLP(3, dim=8, hidden_dim=12)
49
+ values = [torch.randn(2, 5, 8) for _ in range(3)]
50
+ combined_weight = torch.cat(
51
+ [layer.weight for layer in module.inputs], dim=1
52
+ )
53
+ combined_bias = sum(layer.bias for layer in module.inputs)
54
+ reference_hidden = F.linear(
55
+ torch.cat(values, dim=-1), combined_weight, combined_bias
56
+ )
57
+ reference = module.out(module.activation(reference_hidden))
58
+ torch.testing.assert_close(module(*values), reference)
59
+
60
+ @patch(
61
+ "predictor_training.atc_fusion.attention",
62
+ side_effect=reference_attention,
63
+ )
64
+ def test_last_frame_scope_cannot_read_earlier_frames(self, _mock) -> None:
65
+ transport = StateTransport(
66
+ dim=8,
67
+ num_heads=1,
68
+ previous_scope="last_frame",
69
+ gradient_checkpointing=False,
70
+ diagnostic_queries=4,
71
+ ).eval()
72
+ first, _ = transport(
73
+ self.current, self.previous, self.grid, self.freqs, current_start=8
74
+ )
75
+ perturbed_early = self.previous.clone()
76
+ perturbed_early[:, :4] += torch.randn_like(perturbed_early[:, :4])
77
+ second, _ = transport(
78
+ self.current,
79
+ perturbed_early,
80
+ self.grid,
81
+ self.freqs,
82
+ current_start=8,
83
+ )
84
+ torch.testing.assert_close(first, second, rtol=0, atol=0)
85
+
86
+ perturbed_last = self.previous.clone()
87
+ perturbed_last[:, 4:] += torch.randn_like(perturbed_last[:, 4:])
88
+ third, _ = transport(
89
+ self.current,
90
+ perturbed_last,
91
+ self.grid,
92
+ self.freqs,
93
+ current_start=8,
94
+ )
95
+ self.assertFalse(torch.equal(first, third))
96
+
97
+ @patch(
98
+ "predictor_training.atc_fusion.attention",
99
+ side_effect=reference_attention,
100
+ )
101
+ def test_zero_initialized_correction_bypass(self, _mock) -> None:
102
+ fusion = ATCFusion(
103
+ dim=8,
104
+ num_heads=1,
105
+ freq_dim=8,
106
+ mlp_hidden_dim=16,
107
+ gate_hidden_dim=4,
108
+ previous_scope="chunk",
109
+ gradient_checkpointing=False,
110
+ ).eval()
111
+ condition = torch.randn_like(self.current)
112
+ backbone, gated_correction = fusion(
113
+ self.current,
114
+ torch.randn_like(self.current),
115
+ self.previous,
116
+ condition,
117
+ torch.tensor([62.5]),
118
+ self.grid,
119
+ self.freqs,
120
+ current_start=8,
121
+ )
122
+ self.assertEqual(backbone.shape, self.current.shape)
123
+ torch.testing.assert_close(
124
+ gated_correction,
125
+ torch.zeros_like(gated_correction),
126
+ rtol=0,
127
+ atol=0,
128
+ )
129
+ self.assertAlmostEqual(float(fusion.last_gate.mean()), 0.3, delta=0.01)
130
+
131
+ def test_atc_token_gate_is_a_stage1_parameter(self) -> None:
132
+ predictor = SingleBlockPredictor(
133
+ DummyBlock(),
134
+ dim=8,
135
+ gradient_checkpointing=False,
136
+ input_variant="atc",
137
+ atc_freq_dim=8,
138
+ atc_mlp_hidden_dim=16,
139
+ atc_gate_hidden_dim=4,
140
+ )
141
+ token_gate_ids = {
142
+ id(parameter)
143
+ for parameter in predictor.fusion.token_gate.parameters()
144
+ }
145
+ stage1_ids = {
146
+ id(parameter) for parameter in predictor.stage1_input_parameters()
147
+ }
148
+ self.assertTrue(token_gate_ids)
149
+ self.assertTrue(token_gate_ids.issubset(stage1_ids))
150
+ self.assertEqual(predictor.gate_parameters(), [])
151
+
152
+ @patch(
153
+ "predictor_training.atc_fusion.attention",
154
+ side_effect=reference_attention,
155
+ )
156
+ def test_zero_initialized_predictor_returns_anchor(self, _mock) -> None:
157
+ predictor = SingleBlockPredictor(
158
+ DummyBlock(),
159
+ dim=8,
160
+ gradient_checkpointing=False,
161
+ input_variant="atc",
162
+ atc_freq_dim=8,
163
+ atc_mlp_hidden_dim=16,
164
+ atc_gate_hidden_dim=4,
165
+ ).eval()
166
+ anchor = torch.randn_like(self.current)
167
+ empty_history = torch.empty(1, 0, 1, 8)
168
+ prediction = predictor(
169
+ current_tokens=self.current,
170
+ anchor_hidden=anchor,
171
+ previous_hidden=self.previous,
172
+ timestep_modulation=torch.randn(1, 2, 6, 8),
173
+ grid_sizes=self.grid,
174
+ freqs=self.freqs,
175
+ history_k=empty_history,
176
+ history_v=empty_history,
177
+ cross_k=torch.empty(1, 0, 1, 8),
178
+ cross_v=torch.empty(1, 0, 1, 8),
179
+ current_start=8,
180
+ condition_tokens=torch.randn_like(self.current),
181
+ anchor_distance=torch.tensor([62.5]),
182
+ )
183
+ torch.testing.assert_close(prediction, anchor, rtol=0, atol=0)
184
+
185
+
186
+ if __name__ == "__main__":
187
+ unittest.main()
tests/test_confidence_lr_schedule.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ from scripts.train_confidence_token_lazy_ddp import learning_rate_at_step
4
+
5
+
6
+ class ConfidenceLearningRateTest(unittest.TestCase):
7
+ def value(self, step: int) -> float:
8
+ return learning_rate_at_step(
9
+ step=step,
10
+ total_steps=1440,
11
+ schedule="warmup_cosine",
12
+ peak_lr=1e-4,
13
+ warmup_steps=36,
14
+ warmup_start_lr=1e-5,
15
+ min_lr=1e-6,
16
+ )
17
+
18
+ def test_warmup_endpoints(self) -> None:
19
+ self.assertAlmostEqual(self.value(0), 1e-5)
20
+ self.assertAlmostEqual(self.value(35), 1e-4)
21
+
22
+ def test_cosine_is_monotonic_and_reaches_floor(self) -> None:
23
+ values = [self.value(step) for step in range(35, 1440)]
24
+ self.assertTrue(all(left >= right for left, right in zip(values, values[1:])))
25
+ self.assertAlmostEqual(values[-1], 1e-6)
26
+
27
+ def test_constant_schedule_is_unchanged(self) -> None:
28
+ value = learning_rate_at_step(
29
+ step=503,
30
+ total_steps=1440,
31
+ schedule="constant",
32
+ peak_lr=3e-4,
33
+ warmup_steps=0,
34
+ warmup_start_lr=1e-5,
35
+ min_lr=1e-6,
36
+ )
37
+ self.assertEqual(value, 3e-4)
38
+
39
+
40
+ if __name__ == "__main__":
41
+ unittest.main()
tests/test_confidence_token.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ import torch
4
+
5
+ from predictor_training.confidence import ConfidenceTokenHead
6
+
7
+
8
+ class ConfidenceTokenHeadTest(unittest.TestCase):
9
+ def setUp(self) -> None:
10
+ torch.manual_seed(0)
11
+ self.head = ConfidenceTokenHead(
12
+ dim=16,
13
+ token_dim=8,
14
+ context_dim=4,
15
+ num_heads=2,
16
+ ffn_dim=32,
17
+ dropout=0.0,
18
+ num_steps=3,
19
+ )
20
+
21
+ def test_output_shape_and_predictor_features_are_detached(self) -> None:
22
+ transformed = torch.randn(2, 7, 16, requires_grad=True)
23
+ predicted = torch.randn(2, 7, 16, requires_grad=True)
24
+ anchor = torch.randn(2, 7, 16, requires_grad=True)
25
+ output = self.head(
26
+ transformed_hidden=transformed,
27
+ pred_hidden=predicted,
28
+ anchor_hidden=anchor,
29
+ chunk_position=torch.tensor([0.0, 1.0]),
30
+ step_id=torch.tensor([1, 3]),
31
+ )
32
+ self.assertEqual(output.shape, (2,))
33
+ output.sum().backward()
34
+ self.assertIsNone(transformed.grad)
35
+ self.assertIsNone(predicted.grad)
36
+ self.assertIsNone(anchor.grad)
37
+ self.assertTrue(any(parameter.grad is not None for parameter in self.head.parameters()))
38
+
39
+ def test_rejects_unsupported_step(self) -> None:
40
+ value = torch.randn(1, 7, 16)
41
+ with self.assertRaisesRegex(ValueError, "supports step_id"):
42
+ self.head(
43
+ transformed_hidden=value,
44
+ pred_hidden=value,
45
+ anchor_hidden=value,
46
+ chunk_position=torch.tensor([0.0]),
47
+ step_id=torch.tensor([4]),
48
+ )
49
+
50
+ def test_production_dimensions_and_parameter_count(self) -> None:
51
+ head = ConfidenceTokenHead()
52
+ self.assertEqual(head.token_projection[0].in_features, 1536)
53
+ self.assertEqual(head.token_projection[0].out_features, 512)
54
+ self.assertEqual(head.output[0].in_features, 1156)
55
+ count = sum(parameter.numel() for parameter in head.parameters())
56
+ self.assertEqual(count, 4_872_065)
57
+
58
+
59
+ if __name__ == "__main__":
60
+ unittest.main()
tests/test_disca_input_variant.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ import torch
4
+ from torch import nn
5
+
6
+ from predictor_training.single_block import SingleBlockPredictor
7
+
8
+
9
+ class EchoBlock(nn.Module):
10
+ def forward(self, value: torch.Tensor, **kwargs) -> torch.Tensor:
11
+ return value
12
+
13
+
14
+ def build_model(input_variant: str) -> SingleBlockPredictor:
15
+ model = SingleBlockPredictor(
16
+ EchoBlock(),
17
+ dim=8,
18
+ gradient_checkpointing=False,
19
+ input_variant=input_variant,
20
+ ).eval()
21
+ with torch.no_grad():
22
+ model.residual_out.weight.copy_(torch.eye(8))
23
+ return model
24
+
25
+
26
+ def inputs() -> dict[str, object]:
27
+ return {
28
+ "current_tokens": torch.randn(2, 3, 8),
29
+ "anchor_hidden": torch.randn(2, 3, 8),
30
+ "timestep_modulation": torch.randn(2, 6, 8),
31
+ "grid_sizes": torch.tensor([[1, 1, 3], [1, 1, 3]]),
32
+ "freqs": torch.empty(0),
33
+ "history_k": torch.randn(2, 2, 1, 8),
34
+ "history_v": torch.randn(2, 2, 1, 8),
35
+ "cross_k": torch.randn(2, 1, 1, 8),
36
+ "cross_v": torch.randn(2, 1, 1, 8),
37
+ "current_start": 2,
38
+ }
39
+
40
+
41
+ class DiscaInputVariantTest(unittest.TestCase):
42
+ def test_disca_output_ignores_previous_chunk_hidden(self) -> None:
43
+ torch.manual_seed(0)
44
+ model = build_model(input_variant="disca")
45
+ common = inputs()
46
+ first = model(previous_hidden=torch.randn(2, 3, 8), **common)
47
+ second = model(previous_hidden=torch.randn(2, 3, 8), **common)
48
+ self.assertTrue(torch.equal(first, second))
49
+
50
+ def test_disca_physically_removes_previous_chunk_channel(self) -> None:
51
+ baseline = build_model(input_variant="self_forcing")
52
+ disca = build_model(input_variant="disca")
53
+ self.assertEqual(baseline.fusion.proj_in.weight.shape, (16, 24))
54
+ self.assertEqual(disca.fusion.proj_in.weight.shape, (16, 16))
55
+ self.assertTrue(hasattr(baseline.fusion, "previous_norm"))
56
+ self.assertFalse(hasattr(disca.fusion, "previous_norm"))
57
+
58
+
59
+ if __name__ == "__main__":
60
+ unittest.main()
tests/test_naive_vbench_policies.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import unittest
3
+
4
+ from scripts.naive_vbench_policies import (
5
+ ALL_STRATEGY_NAMES,
6
+ EVALUATION_POLICIES,
7
+ POLICIES_BY_NAME,
8
+ raw_timestep_schedule,
9
+ )
10
+
11
+
12
+ class NaiveVBenchPoliciesTest(unittest.TestCase):
13
+ def test_timestep_skipping_schedules(self) -> None:
14
+ self.assertEqual(raw_timestep_schedule(3), (1000, 666, 333))
15
+ self.assertEqual(raw_timestep_schedule(2), (1000, 500))
16
+ self.assertEqual(raw_timestep_schedule(1), (1000,))
17
+ with self.assertRaises(ValueError):
18
+ raw_timestep_schedule(0)
19
+
20
+ def test_strategy_names_are_stable_and_unique(self) -> None:
21
+ self.assertEqual(
22
+ ALL_STRATEGY_NAMES,
23
+ (
24
+ "ffff",
25
+ "naive_3step",
26
+ "naive_2step",
27
+ "naive_1step",
28
+ "naive_cache_3step_frff",
29
+ "naive_cache_2step_frrf",
30
+ "naive_cache_1step_frrr",
31
+ ),
32
+ )
33
+ self.assertEqual(len(POLICIES_BY_NAME), len(ALL_STRATEGY_NAMES))
34
+
35
+ def test_cache_patterns_and_call_counts(self) -> None:
36
+ expected = {
37
+ "naive_cache_3step_frff": ("FRFF", 3, 1, 21, 7),
38
+ "naive_cache_2step_frrf": ("FRRF", 2, 2, 14, 14),
39
+ "naive_cache_1step_frrr": ("FRRR", 1, 3, 7, 21),
40
+ }
41
+ for name, (pattern, full, reuse, full_video, reuse_video) in expected.items():
42
+ policy = POLICIES_BY_NAME[name]
43
+ self.assertEqual(policy.cache_pattern, pattern)
44
+ self.assertEqual(policy.full_calls_per_chunk, full)
45
+ self.assertEqual(policy.reuse_calls_per_chunk, reuse)
46
+ self.assertEqual(7 * policy.full_calls_per_chunk, full_video)
47
+ self.assertEqual(7 * policy.reuse_calls_per_chunk, reuse_video)
48
+
49
+ def test_skipping_full_call_counts(self) -> None:
50
+ expected = {
51
+ "naive_3step": 21,
52
+ "naive_2step": 14,
53
+ "naive_1step": 7,
54
+ }
55
+ for policy in EVALUATION_POLICIES[:3]:
56
+ self.assertEqual(7 * policy.full_calls_per_chunk, expected[policy.name])
57
+ self.assertEqual(policy.reuse_calls_per_chunk, 0)
58
+
59
+
60
+ if __name__ == "__main__":
61
+ unittest.main()
tests/test_predictor_stage2_dmd.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+ from unittest import mock
6
+
7
+ import torch
8
+ from safetensors.torch import save_file
9
+
10
+ from scripts import train_layer17_predictor_stage2_dmd as stage2
11
+ from scripts.train_confidence_token_lazy_ddp import (
12
+ read_predictor_config as read_confidence_predictor_config,
13
+ )
14
+ from utils.wan_wrapper import resolve_wan_model_path
15
+
16
+
17
+ class PredictorStage2DMDTest(unittest.TestCase):
18
+ def test_wan_model_path_accepts_names_and_absolute_paths(self) -> None:
19
+ self.assertEqual(
20
+ resolve_wan_model_path("Wan2.1-T2V-1.3B"),
21
+ Path("wan_models/Wan2.1-T2V-1.3B"),
22
+ )
23
+ self.assertEqual(
24
+ resolve_wan_model_path("/data1/chenzhuo/Wan2.1/Wan2.1-T2V-14B"),
25
+ Path("/data1/chenzhuo/Wan2.1/Wan2.1-T2V-14B"),
26
+ )
27
+
28
+ def test_prompt_pool_uses_seeded_full_file_permutation(self) -> None:
29
+ with tempfile.TemporaryDirectory() as directory:
30
+ path = Path(directory) / "prompts.txt"
31
+ source = [f"prompt {index}" for index in range(20)]
32
+ path.write_text("\n".join(source) + "\n", encoding="utf-8")
33
+
34
+ first = stage2.load_prompt_pool(path, prompt_count=0, prompt_seed=0)
35
+ second = stage2.load_prompt_pool(path, prompt_count=0, prompt_seed=0)
36
+
37
+ self.assertEqual(first, second)
38
+ self.assertEqual(set(first), set(source))
39
+ self.assertNotEqual(first, source)
40
+
41
+ def test_prompt_draws_do_not_overlap_between_critic_and_student(self) -> None:
42
+ sample_ids = []
43
+ for step in range(2):
44
+ for batch_slot in range(6):
45
+ for accumulation_index in range(2):
46
+ for rank in range(4):
47
+ sample_ids.append(
48
+ stage2.prompt_sample_id(
49
+ student_step=step,
50
+ batch_slot=batch_slot,
51
+ global_batch_size=8,
52
+ batches_per_step=6,
53
+ accumulation_index=accumulation_index,
54
+ world_size=4,
55
+ rank=rank,
56
+ prompt_pool_size=248_221,
57
+ )
58
+ )
59
+
60
+ self.assertEqual(sample_ids, list(range(96)))
61
+
62
+ def test_reads_atc_metadata_and_validates_variant(self) -> None:
63
+ config = {
64
+ "source_layer": 17,
65
+ "input_variant": "atc",
66
+ "atc_previous_scope": "last_frame",
67
+ }
68
+ with tempfile.TemporaryDirectory() as directory:
69
+ path = Path(directory) / "predictor.safetensors"
70
+ save_file(
71
+ {"weight": torch.zeros(1)},
72
+ path,
73
+ metadata={"predictor_config": json.dumps(config)},
74
+ )
75
+ self.assertEqual(stage2.read_predictor_config(path, "atc"), config)
76
+ self.assertEqual(read_confidence_predictor_config(path, "atc"), config)
77
+ with self.assertRaisesRegex(ValueError, "variant mismatch"):
78
+ stage2.read_predictor_config(path, "self_forcing")
79
+
80
+ def test_stage2_metadata_is_confidence_trainer_compatible(self) -> None:
81
+ config = {
82
+ "source_layer": 17,
83
+ "input_variant": "atc",
84
+ "atc_previous_scope": "chunk",
85
+ }
86
+ with tempfile.TemporaryDirectory() as directory:
87
+ path = Path(directory) / "predictor_ema.safetensors"
88
+ save_file(
89
+ {"weight": torch.zeros(1)},
90
+ path,
91
+ metadata=stage2.predictor_metadata(
92
+ step=2000, predictor_config=config, ema_decay=0.99
93
+ ),
94
+ )
95
+ self.assertEqual(read_confidence_predictor_config(path, "atc"), config)
96
+
97
+ def test_online_step_matches_stage1_anchor_distance_definition(self) -> None:
98
+ noisy = torch.zeros(1, 3, 1, 1, 1)
99
+ timestep = torch.full((1, 3), 625.0)
100
+ anchor_timestep = torch.full((1, 3), 833.0)
101
+ hidden = torch.zeros(1, 2, 4)
102
+ history = {
103
+ "k": torch.zeros(1, 5 * stage2.TOKENS_PER_CHUNK, 1, 1),
104
+ "v": torch.zeros(1, 5 * stage2.TOKENS_PER_CHUNK, 1, 1),
105
+ "local_end_index": torch.tensor([5 * stage2.TOKENS_PER_CHUNK]),
106
+ }
107
+ cross = {"k": torch.zeros(1), "v": torch.zeros(1)}
108
+ expected = (hidden, noisy)
109
+
110
+ with mock.patch.object(
111
+ stage2, "forward_predictor", return_value=expected
112
+ ) as forward:
113
+ actual = stage2.online_predictor_step(
114
+ predictor=mock.Mock(),
115
+ teacher=mock.Mock(),
116
+ noisy_input=noisy,
117
+ timestep=timestep,
118
+ anchor_timestep=anchor_timestep,
119
+ anchor_hidden=hidden,
120
+ previous_hidden=hidden,
121
+ history_cache=history,
122
+ cross_cache=cross,
123
+ chunk=4,
124
+ )
125
+
126
+ self.assertIs(actual, expected)
127
+ batch = forward.call_args.args[1]
128
+ torch.testing.assert_close(batch["anchor_distance"], torch.tensor([208.0]))
129
+ self.assertEqual(batch["history_k"].shape[1], 4 * stage2.TOKENS_PER_CHUNK)
130
+ self.assertEqual(batch["chunk"], 4)
131
+
132
+ def test_dmd_loss_excludes_full_only_first_chunk(self) -> None:
133
+ generated = torch.zeros(
134
+ 1,
135
+ stage2.NUM_CHUNKS * stage2.FRAMES_PER_CHUNK,
136
+ 1,
137
+ 1,
138
+ 1,
139
+ dtype=torch.float64,
140
+ requires_grad=True,
141
+ )
142
+ gradient = torch.ones_like(generated)
143
+ loss = stage2.dmd_loss(generated, gradient)
144
+ loss.backward()
145
+
146
+ self.assertAlmostEqual(float(loss), 0.5)
147
+ self.assertTrue(
148
+ torch.equal(
149
+ generated.grad[:, : stage2.FRAMES_PER_CHUNK],
150
+ torch.zeros_like(generated.grad[:, : stage2.FRAMES_PER_CHUNK]),
151
+ )
152
+ )
153
+ self.assertTrue(torch.all(generated.grad[:, stage2.FRAMES_PER_CHUNK :] > 0))
154
+
155
+
156
+ if __name__ == "__main__":
157
+ unittest.main()
tests/test_vbench8_protocol.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import unittest
3
+
4
+ from scripts.build_vbench8_extended_mapping import build_mapping
5
+ from scripts.summarize_vbench8_generation import (
6
+ denoise_dit_latency_ms,
7
+ policy_latency_ms,
8
+ speedup_percent,
9
+ )
10
+ from scripts.vbench8_protocol import aggregate_selected_score, normalize_scores
11
+
12
+
13
+ class VBench8ProtocolTest(unittest.TestCase):
14
+ def test_mapping_counts_and_extended_prompt_alignment(self) -> None:
15
+ short = [f"short prompt {index}" for index in range(946)]
16
+ extended = [f"extended prompt {index}" for index in range(946)]
17
+ info = [{"prompt_en": prompt, "dimension": ["temporal_style"]} for prompt in short]
18
+ positions = {
19
+ "subject_consistency": range(0, 72),
20
+ "overall_consistency": range(72, 165),
21
+ "scene": range(165, 251),
22
+ }
23
+ for suite, indices in positions.items():
24
+ for index in indices:
25
+ info[index] = {
26
+ "prompt_en": short[index],
27
+ "dimension": [suite],
28
+ }
29
+ if suite == "scene":
30
+ info[index]["auxiliary_info"] = {
31
+ "scene": {"scene": {"scene": f"scene-{index}"}}
32
+ }
33
+ mapping = build_mapping(
34
+ short_prompts=short,
35
+ extended_prompts=extended,
36
+ vbench_info=info,
37
+ )
38
+ self.assertEqual(len(mapping), 251)
39
+ self.assertEqual(
40
+ {suite: sum(row["prompt_suite"] == suite for row in mapping)
41
+ for suite in positions},
42
+ {"subject_consistency": 72, "overall_consistency": 93, "scene": 86},
43
+ )
44
+ self.assertEqual(mapping[0]["original_prompt"], "short prompt 0")
45
+ self.assertEqual(mapping[0]["extended_prompt"], "extended prompt 0")
46
+ self.assertIn("auxiliary_info", mapping[-1])
47
+
48
+ def test_mapping_rejects_order_mismatch(self) -> None:
49
+ short = [f"short prompt {index}" for index in range(946)]
50
+ extended = [f"extended prompt {index}" for index in range(946)]
51
+ info = [{"prompt_en": prompt, "dimension": ["temporal_style"]} for prompt in short]
52
+ info[10]["prompt_en"] = "wrong order"
53
+ with self.assertRaises(ValueError):
54
+ build_mapping(short_prompts=short, extended_prompts=extended, vbench_info=info)
55
+
56
+ def test_normalization_motion(self) -> None:
57
+ raw = {dimension: 0.0 for dimension in (
58
+ "subject_consistency", "background_consistency", "motion_smoothness",
59
+ "dynamic_degree", "aesthetic_quality", "imaging_quality", "scene",
60
+ "overall_consistency",
61
+ )}
62
+ raw["motion_smoothness"] = 0.95
63
+ normalized = normalize_scores(raw)
64
+ expected = (0.95 - 0.7060) / (0.9975 - 0.7060)
65
+ self.assertAlmostEqual(normalized["motion_smoothness"], expected)
66
+
67
+ def test_aggregate_all_normalized_one(self) -> None:
68
+ raw = {
69
+ "subject_consistency": 1.0,
70
+ "background_consistency": 1.0,
71
+ "motion_smoothness": 0.9975,
72
+ "dynamic_degree": 1.0,
73
+ "aesthetic_quality": 1.0,
74
+ "imaging_quality": 1.0,
75
+ "scene": 0.8222,
76
+ "overall_consistency": 0.3640,
77
+ }
78
+ aggregate = aggregate_selected_score(raw)
79
+ self.assertAlmostEqual(aggregate["quality_score"], 1.0)
80
+ self.assertAlmostEqual(aggregate["semantic_score"], 1.0)
81
+ self.assertAlmostEqual(aggregate["selected_vbench_score"], 1.0)
82
+ self.assertAlmostEqual(aggregate["selected_vbench_percent"], 100.0)
83
+
84
+ def test_latency_excludes_context_and_includes_confidence(self) -> None:
85
+ generation = {
86
+ "full_dit_time_ms": 100.0,
87
+ "predictor_time_ms": 20.0,
88
+ "confidence_head_time_ms": 3.0,
89
+ "context_dit_time_ms": 500.0,
90
+ }
91
+ self.assertEqual(denoise_dit_latency_ms(generation), 120.0)
92
+ self.assertEqual(policy_latency_ms(generation), 123.0)
93
+
94
+ def test_policy_latency_speedup_uses_ratio_of_means(self) -> None:
95
+ self.assertAlmostEqual(speedup_percent([60.0, 100.0], [100.0, 100.0]), 20.0)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ unittest.main()