Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions test/prototype/safetensors/test_safetensors_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,64 @@ def test_safetensors_sharded(self, config, device, act_pre_scale=False):
assert key in leftover_tensor_data_dict


class TestSafeTensorsRootParameters(TestCase):
@parametrize("depth", [0, 1, 2])
@parametrize("quantized", [False, True])
def test_root_and_nested_parameters_round_trip(self, depth, quantized):
model = torch.nn.Linear(32, 16)
for _ in range(depth):
model = torch.nn.Sequential(model)
if quantized:
quantize_(model, Int8WeightOnlyConfig(version=2))
inputs = torch.randn(2, 32)
expected = model(inputs)
state_dict = model.state_dict()
tensors_data, metadata = flatten_tensor_state_dict(state_dict)
prefix = "0." * depth
if quantized:
self.assertIn(f"{prefix}_weight_qdata", tensors_data)
self.assertIn(f"{prefix}bias", tensors_data)

with tempfile.TemporaryDirectory() as directory:
path = f"{directory}/model.safetensors"
save_file(tensors_data, path, metadata=metadata)
loaded_tensors, loaded_metadata = load_data(path, "cpu")
restored, leftover = unflatten_tensor_state_dict(
loaded_tensors, loaded_metadata
)

self.assertFalse(leftover)
self.assertEqual(set(restored), set(state_dict))
restored_model = torch.nn.Linear(32, 16)
for _ in range(depth):
restored_model = torch.nn.Sequential(restored_model)
restored_model.load_state_dict(restored, assign=True)
self.assertEqual(restored_model(inputs), expected)
self.assertEqual(
type(restored[f"{prefix}weight"]), type(state_dict[f"{prefix}weight"])
)

@parametrize("depth", [0, 1])
def test_partial_quantized_state_waits_for_remaining_data(self, depth):
model = torch.nn.Linear(32, 16)
for _ in range(depth):
model = torch.nn.Sequential(model)
quantize_(model, Int8WeightOnlyConfig(version=2))
tensors_data, metadata = flatten_tensor_state_dict(model.state_dict())
prefix = "0." * depth
qdata = tensors_data.pop(f"{prefix}_weight_qdata")

restored, leftover = unflatten_tensor_state_dict(tensors_data, metadata)
self.assertEqual(set(restored), {f"{prefix}bias"})
self.assertEqual(set(leftover), set(tensors_data) - {f"{prefix}bias"})
remaining = {**leftover, f"{prefix}_weight_qdata": qdata}
restored_weight, leftover = unflatten_tensor_state_dict(remaining, metadata)
self.assertEqual(set(restored_weight), {f"{prefix}weight"})
self.assertFalse(leftover)
self.assertEqual(restored_weight[f"{prefix}weight"].qdata, qdata)


instantiate_parametrized_tests(TestSafeTensorsRootParameters)
instantiate_parametrized_tests(TestSafeTensors)

if __name__ == "__main__":
Expand Down
12 changes: 8 additions & 4 deletions torchao/prototype/safetensors/safetensors_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
logger: logging.Logger = logging.getLogger(__name__)


def _tensor_data_prefix(tensor_name: str) -> str:
module_fqn, separator, weight_name = tensor_name.rpartition(".")
return f"{module_fqn}{separator}_{weight_name}_"


def unflatten_tensor_state_dict(
tensors_data_dict: Dict[str, Any],
metadata: Dict[str, Any],
Expand Down Expand Up @@ -71,9 +76,7 @@ def unflatten_tensor_state_dict(
for tensor_name in tensor_names:
processed_tensors = []

module_fqn, weight_name = tensor_name.rsplit(".", 1)

prefix = f"{module_fqn}._{weight_name}_"
prefix = _tensor_data_prefix(tensor_name)
tensor_tensors = {}

for key, value in combined_data.items():
Expand Down Expand Up @@ -181,8 +184,9 @@ def flatten_tensor_state_dict(
tensor_metadata = json.dumps(tensor, cls=TensorSubclassAttributeJSONEncoder)

# Clone tensors to avoid memory sharing issues
prefix = _tensor_data_prefix(tensor_name)
tensors_dict_to_save = {
f"{tensor_name.rsplit('.', 1)[0]}._{tensor_name.rsplit('.', 1)[1]}_{key}": (
f"{prefix}{key}": (
value.detach().clone() if isinstance(value, torch.Tensor) else value
)
for key, value in tensor_dict.items()
Expand Down