Skip to content

Commit d1fb7e2

Browse files
committed
memory
1 parent ef206c2 commit d1fb7e2

2 files changed

Lines changed: 47 additions & 47 deletions

File tree

tests/models/testing_utils/common.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,39 @@ def get_dummy_inputs(self) -> Dict[str, Any]:
262262
raise NotImplementedError("Subclasses must implement `get_dummy_inputs()`.")
263263

264264

265-
class ModelTesterMixin:
265+
class BaseModelOutputMixin:
266+
"""Provides the class-scoped `base_model_output` fixture shared across tester mixins.
267+
268+
Kept separate from `BaseModelTesterConfig` — which only declares the testing contract and performs no
269+
computation — so any mixin that needs the cached reference output (`ModelTesterMixin`, the memory
270+
offload mixins, ...) can inherit it without duplicating the build-and-forward.
271+
"""
272+
273+
@pytest.fixture(scope="class")
274+
def base_model_output(self):
275+
"""Class-scoped reference forward output, built once and reused across the class.
276+
277+
Building the model and running its forward pass is fully deterministic (`torch.manual_seed(0)`
278+
plus the deterministic `get_dummy_inputs` contract), so the reference ("base") output is
279+
identical for every test in the class. The save/load, parallelism, and memory-offload tests
280+
compare a reloaded/offloaded model against this output; computing it a single time here — instead
281+
of rebuilding the model and re-running the forward in each test — removes that redundant work and
282+
speeds up the suite.
283+
284+
The hardware-gated tests that consume this fixture use `pytest.mark.skipif` (via the `require_*`
285+
decorators), which pytest evaluates before fixture setup, so skipping on a machine without the
286+
required accelerators never triggers this forward.
287+
288+
Tests that still need a live model (e.g. to save or offload it) build their own with the same
289+
seed, so the reloaded model's weights match this cached output.
290+
"""
291+
torch.manual_seed(0)
292+
model = self.model_class(**self.get_init_dict()).eval().to(torch_device)
293+
with torch.no_grad():
294+
return model(**self.get_dummy_inputs(), return_dict=False)[0]
295+
296+
297+
class ModelTesterMixin(BaseModelOutputMixin):
266298
"""
267299
Base mixin class for model testing with common test methods.
268300
@@ -282,28 +314,6 @@ class TestMyModel(MyModelTestConfig, ModelTesterMixin):
282314
pass
283315
"""
284316

285-
@pytest.fixture(scope="class")
286-
def base_model_output(self):
287-
"""Class-scoped reference forward output, built once and reused across the class.
288-
289-
Building the model and running its forward pass is fully deterministic (`torch.manual_seed(0)`
290-
plus the deterministic `get_dummy_inputs` contract), so the reference ("base") output is
291-
identical for every test in the class. The save/load and parallelism tests compare a reloaded
292-
model against this output; computing it a single time here — instead of rebuilding the model and
293-
re-running the forward in each test — removes that redundant work and speeds up the suite.
294-
295-
The hardware-gated tests that consume this fixture use `pytest.mark.skipif` (via the
296-
`require_*` decorators), which pytest evaluates before fixture setup, so skipping on a machine
297-
without the required accelerators never triggers this forward.
298-
299-
Tests that still need a live model (e.g. to save it) build their own with the same seed, so the
300-
reloaded model's weights match this cached output.
301-
"""
302-
torch.manual_seed(0)
303-
model = self.model_class(**self.get_init_dict()).eval().to(torch_device)
304-
with torch.no_grad():
305-
return model(**self.get_dummy_inputs(), return_dict=False)[0]
306-
307317
@torch.no_grad()
308318
def test_from_save_pretrained(self, base_model_output, tmp_path, atol=5e-5, rtol=5e-5):
309319
torch.manual_seed(0)

tests/models/testing_utils/memory.py

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
require_accelerator,
3838
torch_device,
3939
)
40-
from .common import cast_inputs_to_dtype, check_device_map_is_respected
40+
from .common import BaseModelOutputMixin, cast_inputs_to_dtype, check_device_map_is_respected
4141

4242

4343
def require_offload_support(func):
@@ -69,7 +69,7 @@ def wrapper(self, *args, **kwargs):
6969

7070

7171
@is_cpu_offload
72-
class CPUOffloadTesterMixin:
72+
class CPUOffloadTesterMixin(BaseModelOutputMixin):
7373
"""
7474
Mixin class for testing CPU offloading functionality.
7575
@@ -94,16 +94,14 @@ def model_split_percents(self) -> list[float]:
9494

9595
@require_offload_support
9696
@torch.no_grad()
97-
def test_cpu_offload(self, tmp_path, atol=1e-5, rtol=0):
97+
def test_cpu_offload(self, base_model_output, tmp_path, atol=1e-5, rtol=0):
98+
torch.manual_seed(0)
9899
config = self.get_init_dict()
99100
inputs_dict = self.get_dummy_inputs()
100101
model = self.model_class(**config).eval()
101102

102103
model = model.to(torch_device)
103104

104-
torch.manual_seed(0)
105-
base_output = model(**inputs_dict)
106-
107105
model_size = compute_module_sizes(model)[""]
108106
# We test several splits of sizes to make sure it works
109107
max_gpu_sizes = [int(p * model_size) for p in self.model_split_percents]
@@ -120,21 +118,19 @@ def test_cpu_offload(self, tmp_path, atol=1e-5, rtol=0):
120118
new_output = new_model(**inputs_dict)
121119

122120
assert_tensors_close(
123-
base_output[0], new_output[0], atol=atol, rtol=rtol, msg="Output should match with CPU offloading"
121+
base_model_output, new_output[0], atol=atol, rtol=rtol, msg="Output should match with CPU offloading"
124122
)
125123

126124
@require_offload_support
127125
@torch.no_grad()
128-
def test_disk_offload_without_safetensors(self, tmp_path, atol=1e-5, rtol=0):
126+
def test_disk_offload_without_safetensors(self, base_model_output, tmp_path, atol=1e-5, rtol=0):
127+
torch.manual_seed(0)
129128
config = self.get_init_dict()
130129
inputs_dict = self.get_dummy_inputs()
131130
model = self.model_class(**config).eval()
132131

133132
model = model.to(torch_device)
134133

135-
torch.manual_seed(0)
136-
base_output = model(**inputs_dict)
137-
138134
model_size = compute_module_sizes(model)[""]
139135
max_size = int(self.model_split_percents[0] * model_size)
140136
# Force disk offload by setting very small CPU memory
@@ -154,21 +150,19 @@ def test_disk_offload_without_safetensors(self, tmp_path, atol=1e-5, rtol=0):
154150
new_output = new_model(**inputs_dict)
155151

156152
assert_tensors_close(
157-
base_output[0], new_output[0], atol=atol, rtol=rtol, msg="Output should match with disk offloading"
153+
base_model_output, new_output[0], atol=atol, rtol=rtol, msg="Output should match with disk offloading"
158154
)
159155

160156
@require_offload_support
161157
@torch.no_grad()
162-
def test_disk_offload_with_safetensors(self, tmp_path, atol=1e-5, rtol=0):
158+
def test_disk_offload_with_safetensors(self, base_model_output, tmp_path, atol=1e-5, rtol=0):
159+
torch.manual_seed(0)
163160
config = self.get_init_dict()
164161
inputs_dict = self.get_dummy_inputs()
165162
model = self.model_class(**config).eval()
166163

167164
model = model.to(torch_device)
168165

169-
torch.manual_seed(0)
170-
base_output = model(**inputs_dict)
171-
172166
model_size = compute_module_sizes(model)[""]
173167
model.cpu().save_pretrained(str(tmp_path))
174168

@@ -183,7 +177,7 @@ def test_disk_offload_with_safetensors(self, tmp_path, atol=1e-5, rtol=0):
183177
new_output = new_model(**inputs_dict)
184178

185179
assert_tensors_close(
186-
base_output[0],
180+
base_model_output,
187181
new_output[0],
188182
atol=atol,
189183
rtol=rtol,
@@ -192,7 +186,7 @@ def test_disk_offload_with_safetensors(self, tmp_path, atol=1e-5, rtol=0):
192186

193187

194188
@is_group_offload
195-
class GroupOffloadTesterMixin:
189+
class GroupOffloadTesterMixin(BaseModelOutputMixin):
196190
"""
197191
Mixin class for testing group offloading functionality.
198192
@@ -209,10 +203,9 @@ class GroupOffloadTesterMixin:
209203

210204
@require_group_offload_support
211205
@pytest.mark.parametrize("record_stream", [False, True])
212-
def test_group_offloading(self, record_stream, atol=1e-5, rtol=0):
206+
def test_group_offloading(self, base_model_output, record_stream, atol=1e-5, rtol=0):
213207
init_dict = self.get_init_dict()
214208
inputs_dict = self.get_dummy_inputs()
215-
torch.manual_seed(0)
216209

217210
@torch.no_grad()
218211
def run_forward(model):
@@ -224,10 +217,7 @@ def run_forward(model):
224217
model.eval()
225218
return model(**inputs_dict)[0]
226219

227-
model = self.model_class(**init_dict)
228-
229-
model.to(torch_device)
230-
output_without_group_offloading = run_forward(model)
220+
output_without_group_offloading = base_model_output
231221

232222
torch.manual_seed(0)
233223
model = self.model_class(**init_dict)

0 commit comments

Comments
 (0)