Skip to content

Commit eeb4cee

Browse files
WhiteSwan1claude
andcommitted
[review] SID base: second round of github-actions feedback on PR alibaba#538
- ResidualQuantizer.__init__: assert n_layers >= 1, making the boundary self-guarding (matches the "n_layers >= 1 is guaranteed" comment in decode_codes; a future n_layers=0 subclass would otherwise index OOB). - BaseSidModel.update_train_metric: type predictions as Dict[str, Tensor] instead of bare dict, matching BaseModel / sibling models. - sid_model_test.py (new): unit-test _update_unique_sid_ratio via __new__ — empty-batch no-op (guard) and the exact 0.75 ratio on a known-duplicate batch. - residual_quantizer_test: make the normalize_residuals test behavioral — toggle the flag on the same input/codebook and assert the assignments differ, so the normalization branch is actually validated (the old shape assertions passed even with the branch deleted). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4b6e9b0 commit eeb4cee

4 files changed

Lines changed: 72 additions & 9 deletions

File tree

tzrec/models/sid_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
"""BaseSidModel: shared base for semantic-ID generation models."""
1313

14-
from typing import Any, List, Optional
14+
from typing import Any, Dict, List, Optional
1515

1616
import torch
1717
import torchmetrics
@@ -105,7 +105,7 @@ def init_metric(self) -> None:
105105

106106
def update_train_metric(
107107
self,
108-
predictions: dict,
108+
predictions: Dict[str, torch.Tensor],
109109
batch: Batch,
110110
) -> None:
111111
"""Update train-path metric state.

tzrec/models/sid_model_test.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright (c) 2026, Alibaba Group;
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
import unittest
13+
14+
import torch
15+
import torchmetrics
16+
17+
from tzrec.models.sid_model import BaseSidModel
18+
19+
20+
class UpdateUniqueSidRatioTest(unittest.TestCase):
21+
"""Unit-test the codebook-coverage metric helper.
22+
23+
``_update_unique_sid_ratio`` is pure tensor logic over
24+
``self._metric_modules`` (no proto dependency), so it is testable without
25+
a full ``BaseSidModel`` config — full model coverage waits on the concrete
26+
subclasses in the follow-up PRs.
27+
"""
28+
29+
def _bare_model(self) -> BaseSidModel:
30+
# Bypass __init__ (which needs a pipeline config); only the metric
31+
# module the helper touches needs to exist.
32+
model = BaseSidModel.__new__(BaseSidModel)
33+
model._metric_modules = {"unique_sid_ratio": torchmetrics.MeanMetric()}
34+
return model
35+
36+
def test_empty_batch_is_noop(self) -> None:
37+
model = self._bare_model()
38+
model._update_unique_sid_ratio(torch.empty(0, 3, dtype=torch.long))
39+
# The B == 0 guard returns early -> no sample recorded.
40+
self.assertEqual(
41+
model._metric_modules["unique_sid_ratio"].weight.item(), 0.0
42+
)
43+
44+
def test_ratio_on_known_duplicates(self) -> None:
45+
model = self._bare_model()
46+
# 3 unique rows out of 4 -> ratio 0.75.
47+
codes = torch.tensor([[1, 2], [1, 2], [3, 4], [5, 6]])
48+
model._update_unique_sid_ratio(codes)
49+
self.assertAlmostEqual(
50+
model._metric_modules["unique_sid_ratio"].compute().item(), 0.75, places=6
51+
)
52+
53+
54+
if __name__ == "__main__":
55+
unittest.main()

tzrec/modules/sid_generation/residual_quantizer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(
7878
normalize_residuals: bool = False,
7979
) -> None:
8080
super().__init__()
81+
assert n_layers >= 1, f"n_layers must be >= 1, got {n_layers}"
8182
self.embed_dim = embed_dim
8283
self.n_layers = n_layers
8384
self.normalize_residuals = normalize_residuals

tzrec/modules/sid_generation/residual_quantizer_test.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,21 @@ def test_detach_invariant(self) -> None:
119119
self.assertIsNotNone(fq.books[0].grad)
120120
self.assertIsNone(x.grad)
121121

122-
def test_normalize_residuals_branch(self) -> None:
123-
torch.manual_seed(0)
124-
fq = _FakeQuantizer(
125-
embed_dim=4, n_layers=2, n_embed=5, normalize_residuals=True
122+
def test_normalize_residuals_changes_assignment(self) -> None:
123+
# Same input and same codebook (re-seeded before each build), so the
124+
# only difference is the normalize_residuals branch — it must change
125+
# the residual a later layer sees and hence the codes it assigns.
126+
x = torch.randn(8, 4)
127+
torch.manual_seed(1)
128+
fq_off = _FakeQuantizer(embed_dim=4, n_layers=2, n_embed=6)
129+
torch.manual_seed(1)
130+
fq_on = _FakeQuantizer(
131+
embed_dim=4, n_layers=2, n_embed=6, normalize_residuals=True
126132
)
127-
ids, agg, _ = fq._residual_pass(torch.randn(5, 4))
128-
self.assertEqual(ids.shape, (5, 2))
129-
self.assertEqual(agg.shape, (5, 4))
133+
ids_off, _, _ = fq_off._residual_pass(x)
134+
ids_on, _, _ = fq_on._residual_pass(x)
135+
self.assertEqual(ids_on.shape, (8, 2))
136+
self.assertFalse(torch.equal(ids_off, ids_on))
130137

131138
def test_decode_codes_sum_and_dtype(self) -> None:
132139
torch.manual_seed(0)

0 commit comments

Comments
 (0)