Skip to content

Commit a44263e

Browse files
committed
test(h3-rest): cover AMD loss signs and EMA state
1 parent c5a1338 commit a44263e

1 file changed

Lines changed: 77 additions & 0 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
import torch
7+
8+
from fastvideo.train.methods.knowledge_distillation.h3_rest import compute_h3_rest_losses
9+
from fastvideo.train.methods.knowledge_distillation.h3_rest_ema import TrainableShardEMA
10+
11+
12+
def test_rest_loss_signs_video_but_not_audio() -> None:
13+
prediction = torch.tensor([[1.0, 2.0, 3.0]], requires_grad=True)
14+
losses = compute_h3_rest_losses(
15+
prediction,
16+
video_target=torch.zeros(1, 2),
17+
audio_target=torch.zeros(1, 1),
18+
ema_prediction=prediction.detach().clone(),
19+
video_slice=slice(0, 2),
20+
audio_slice=slice(2, 3),
21+
coefficient=torch.tensor([-0.5]),
22+
audio_loss_weight=1.0,
23+
ema_regularization_weight=0.0,
24+
)
25+
assert losses["total_loss"].item() >= 0.0
26+
losses["total_loss"].backward()
27+
assert torch.all(prediction.grad[0, :2] < 0)
28+
assert prediction.grad[0, 2] > 0
29+
30+
31+
def test_rest_ema_regularizer_is_zero_at_initialization() -> None:
32+
prediction = torch.tensor([[1.0, 2.0, 3.0]], requires_grad=True)
33+
losses = compute_h3_rest_losses(
34+
prediction,
35+
video_target=torch.zeros(1, 2),
36+
audio_target=torch.zeros(1, 1),
37+
ema_prediction=prediction.detach().clone(),
38+
video_slice=slice(0, 2),
39+
audio_slice=slice(2, 3),
40+
coefficient=torch.tensor([0.5]),
41+
audio_loss_weight=1.0,
42+
ema_regularization_weight=0.2,
43+
)
44+
assert losses["video_ema_loss"].item() == 0.0
45+
assert losses["audio_ema_loss"].item() == 0.0
46+
47+
48+
def test_trainable_shard_ema_updates_swaps_and_restores() -> None:
49+
module = torch.nn.Linear(2, 1, bias=False)
50+
with torch.no_grad():
51+
module.weight.fill_(1.0)
52+
ema = TrainableShardEMA(module, decay=0.5)
53+
with torch.no_grad():
54+
module.weight.fill_(3.0)
55+
ema.update(module)
56+
assert torch.allclose(
57+
ema.shadow["weight"], torch.full_like(ema.shadow["weight"], 2.0)
58+
)
59+
with ema.apply_to_model(module):
60+
assert torch.allclose(module.weight, torch.full_like(module.weight, 2.0))
61+
assert torch.allclose(module.weight, torch.full_like(module.weight, 3.0))
62+
63+
64+
def test_trainable_shard_ema_state_round_trip() -> None:
65+
module = torch.nn.Linear(2, 1, bias=False)
66+
ema = TrainableShardEMA(module, decay=0.9)
67+
with torch.no_grad():
68+
module.weight.add_(1.0)
69+
ema.update(module)
70+
state = ema.state_dict()
71+
other = TrainableShardEMA(module, decay=0.9)
72+
other.load_state_dict(state)
73+
assert other.num_updates == 1
74+
assert torch.allclose(other.shadow["weight"], ema.shadow["weight"])
75+
wrong = TrainableShardEMA(module, decay=0.8)
76+
with pytest.raises(ValueError, match="does not match"):
77+
wrong.load_state_dict(state)

0 commit comments

Comments
 (0)