Upload tests
Browse files- tests/test_atc_fusion.py +187 -0
- tests/test_confidence_lr_schedule.py +41 -0
- tests/test_confidence_token.py +60 -0
- tests/test_disca_input_variant.py +60 -0
- tests/test_naive_vbench_policies.py +61 -0
- tests/test_predictor_stage2_dmd.py +157 -0
- tests/test_vbench8_protocol.py +99 -0
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()
|