Skip to content

Commit 9558d91

Browse files
sayakpauldg845
andauthored
[tests] tighten keep_in_32 modules tests (#14399)
* tighten keep_in_32 modules tests * remove unneeded tests --------- Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>
1 parent 614ae4b commit 9558d91

1 file changed

Lines changed: 62 additions & 7 deletions

File tree

tests/models/testing_utils/common.py

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,6 @@ def test_getattr_is_correct(self, caplog):
499499

500500
assert str(error.value) == f"'{type(model).__name__}' object has no attribute 'does_not_exist'"
501501

502-
@require_accelerator
503-
@pytest.mark.skipif(
504-
torch_device not in ["cuda", "xpu"],
505-
reason="float16 and bfloat16 can only be used with an accelerator",
506-
)
507502
def test_keep_in_fp32_modules(self, tmp_path):
508503
model = self.model_class(**self.get_init_dict())
509504
fp32_modules = model._keep_in_fp32_modules
@@ -516,11 +511,71 @@ def test_keep_in_fp32_modules(self, tmp_path):
516511
model.save_pretrained(tmp_path)
517512
model = self.model_class.from_pretrained(tmp_path, torch_dtype=torch.float16).to(torch_device)
518513

514+
# The rule is applied to every floating point checkpoint tensor, so persistent buffers are covered too.
515+
# Non-persistent buffers are not in the checkpoint — they are regenerated by `__init__` and left alone.
516+
for name, tensor in named_persistent_module_tensors(model, recurse=True):
517+
if not tensor.is_floating_point():
518+
continue
519+
if any(module_to_keep_in_fp32 in name.split(".") for module_to_keep_in_fp32 in fp32_modules):
520+
assert tensor.dtype == torch.float32, f"{name} should be float32 but got {tensor.dtype}"
521+
else:
522+
assert tensor.dtype == torch.float16, f"{name} should be float16 but got {tensor.dtype}"
523+
524+
def test_keep_in_fp32_modules_as_str(self, tmp_path, monkeypatch):
525+
model = self.model_class(**self.get_init_dict())
526+
fp32_modules = model._keep_in_fp32_modules
527+
528+
if fp32_modules is None or len(fp32_modules) == 0:
529+
pytest.skip("Model does not have _keep_in_fp32_modules defined.")
530+
531+
# Pick an entry that owns at least one parameter of the tiny test config, otherwise the assertions below
532+
# would hold trivially.
533+
parameter_name_parts = [name.split(".") for name, _ in model.named_parameters()]
534+
fp32_module = next(
535+
(module for module in fp32_modules if any(module in parts for parts in parameter_name_parts)), None
536+
)
537+
if fp32_module is None:
538+
pytest.skip("No _keep_in_fp32_modules entry owns a parameter of this model.")
539+
540+
# `from_pretrained` also accepts `_keep_in_fp32_modules` declared as a bare string.
541+
monkeypatch.setattr(self.model_class, "_keep_in_fp32_modules", fp32_module)
542+
543+
model.save_pretrained(tmp_path)
544+
model = self.model_class.from_pretrained(tmp_path, torch_dtype=torch.float16).to(torch_device)
545+
546+
for name, param in model.named_parameters():
547+
expected_dtype = torch.float32 if fp32_module in name.split(".") else torch.float16
548+
assert param.dtype == expected_dtype, f"Parameter {name} should be {expected_dtype} but got {param.dtype}"
549+
550+
def test_keep_in_fp32_modules_layerwise_casting(self):
551+
# Lives here rather than next to the other layerwise casting tests because it asserts
552+
# `_keep_in_fp32_modules` semantics (`enable_layerwise_casting` folds it into the skip patterns) and needs
553+
# no accelerator, while the layerwise casting mixin is accelerator-gated.
554+
model = self.model_class(**self.get_init_dict())
555+
fp32_modules = model._keep_in_fp32_modules
556+
557+
if fp32_modules is None or len(fp32_modules) == 0:
558+
pytest.skip("Model does not have _keep_in_fp32_modules defined.")
559+
560+
if all(
561+
any(module_to_keep_in_fp32 in name.split(".") for module_to_keep_in_fp32 in fp32_modules)
562+
for name, _ in model.named_parameters()
563+
):
564+
pytest.skip("Every parameter is kept in fp32, so layerwise casting has nothing to cast.")
565+
566+
# float16 storage instead of float8 so the assertions hold on every device — the skip patterns are applied
567+
# the same way whatever the storage dtype is.
568+
model.enable_layerwise_casting(storage_dtype=torch.float16, compute_dtype=torch.float32)
569+
519570
for name, param in model.named_parameters():
520571
if any(module_to_keep_in_fp32 in name.split(".") for module_to_keep_in_fp32 in fp32_modules):
521572
assert param.dtype == torch.float32, f"Parameter {name} should be float32 but got {param.dtype}"
522-
else:
523-
assert param.dtype == torch.float16, f"Parameter {name} should be float16 but got {param.dtype}"
573+
574+
# The skip patterns keep several other modules in fp32 as well, so the loop above cannot check the
575+
# complement. Assert that casting happened at all instead, otherwise it would pass on an untouched model.
576+
assert any(param.dtype == torch.float16 for param in model.parameters()), (
577+
"No parameter was cast to the storage dtype, so the assertions above hold trivially"
578+
)
524579

525580
def test_to_keep_in_fp32_modules_warns(self, caplog):
526581
fp32_modules = self.model_class._keep_in_fp32_modules

0 commit comments

Comments
 (0)