ztensor 1.1.1__py3-none-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ztensor/__init__.py +710 -0
- ztensor/numpy.py +192 -0
- ztensor/torch.py +472 -0
- ztensor/ztensor/__init__.py +7 -0
- ztensor/ztensor/ffi.py +10 -0
- ztensor/ztensor/ztensor.dll +0 -0
- ztensor-1.1.1.dist-info/METADATA +274 -0
- ztensor-1.1.1.dist-info/RECORD +10 -0
- ztensor-1.1.1.dist-info/WHEEL +4 -0
- ztensor-1.1.1.dist-info/licenses/LICENSE +21 -0
ztensor/numpy.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NumPy convenience functions for ztensor.
|
|
3
|
+
|
|
4
|
+
This module provides a safetensors-compatible API for saving and loading
|
|
5
|
+
NumPy arrays in the ztensor format.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from ztensor.numpy import save_file, load_file
|
|
9
|
+
>>> import numpy as np
|
|
10
|
+
>>> tensors = {"embedding": np.zeros((512, 1024))}
|
|
11
|
+
>>> save_file(tensors, "model.zt")
|
|
12
|
+
>>> loaded = load_file("model.zt")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import tempfile
|
|
17
|
+
from typing import Dict, Optional, Union
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from . import Reader, Writer, ZTensorError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save_file(
|
|
25
|
+
tensor_dict: Dict[str, np.ndarray],
|
|
26
|
+
filename: Union[str, os.PathLike],
|
|
27
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
28
|
+
compression: Union[bool, int] = False,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Saves a dictionary of tensors into `filename` in ztensor format.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
tensor_dict: The incoming tensors. Tensors need to be contiguous and dense.
|
|
35
|
+
filename: The filename we're saving into.
|
|
36
|
+
metadata: Optional text only metadata you might want to save in your
|
|
37
|
+
header. For instance it can be useful to specify more about the
|
|
38
|
+
underlying tensors. This is purely informative and does not
|
|
39
|
+
affect tensor loading.
|
|
40
|
+
NOTE: ztensor does not currently support custom metadata; this
|
|
41
|
+
parameter is accepted for API compatibility with safetensors
|
|
42
|
+
but will be ignored.
|
|
43
|
+
compression: Compression settings. False/0 (raw), True (level 3), or int > 0 (level).
|
|
44
|
+
Default: False.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
None
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> from ztensor.numpy import save_file
|
|
51
|
+
>>> import numpy as np
|
|
52
|
+
>>> tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
|
|
53
|
+
>>> save_file(tensors, "model.zt")
|
|
54
|
+
"""
|
|
55
|
+
_validate_tensors(tensor_dict)
|
|
56
|
+
|
|
57
|
+
with Writer(str(filename)) as writer:
|
|
58
|
+
for name, tensor in tensor_dict.items():
|
|
59
|
+
tensor = np.ascontiguousarray(tensor)
|
|
60
|
+
writer.add_tensor(name, tensor, compress=compression)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def save(
|
|
64
|
+
tensor_dict: Dict[str, np.ndarray],
|
|
65
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
66
|
+
compression: Union[bool, int] = False,
|
|
67
|
+
) -> bytes:
|
|
68
|
+
"""
|
|
69
|
+
Saves a dictionary of tensors into raw bytes in ztensor format.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
tensor_dict: The incoming tensors. Tensors need to be contiguous and dense.
|
|
73
|
+
metadata: Optional text only metadata you might want to save in your
|
|
74
|
+
header. This is purely informative and does not affect tensor loading.
|
|
75
|
+
NOTE: ztensor does not currently support custom metadata; this
|
|
76
|
+
parameter is accepted for API compatibility with safetensors
|
|
77
|
+
but will be ignored.
|
|
78
|
+
compression: Compression settings. False/0 (raw), True (level 3), or int > 0 (level).
|
|
79
|
+
Default: False.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The raw bytes representing the format.
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> from ztensor.numpy import save
|
|
86
|
+
>>> import numpy as np
|
|
87
|
+
>>> tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
|
|
88
|
+
>>> byte_data = save(tensors)
|
|
89
|
+
"""
|
|
90
|
+
_validate_tensors(tensor_dict)
|
|
91
|
+
|
|
92
|
+
# Create a temporary file, write to it, read the bytes, then clean up
|
|
93
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".zt") as tmp:
|
|
94
|
+
tmp_path = tmp.name
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
save_file(tensor_dict, tmp_path, metadata=metadata, compression=compression)
|
|
98
|
+
with open(tmp_path, "rb") as f:
|
|
99
|
+
return f.read()
|
|
100
|
+
finally:
|
|
101
|
+
if os.path.exists(tmp_path):
|
|
102
|
+
os.unlink(tmp_path)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def load_file(
|
|
106
|
+
filename: Union[str, os.PathLike],
|
|
107
|
+
) -> Dict[str, np.ndarray]:
|
|
108
|
+
"""
|
|
109
|
+
Loads a ztensor file into numpy format.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
filename: The name of the file which contains the tensors.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Dictionary that contains name as key, value as `np.ndarray`.
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
>>> from ztensor.numpy import load_file
|
|
119
|
+
>>> file_path = "./my_folder/bert.zt"
|
|
120
|
+
>>> loaded = load_file(file_path)
|
|
121
|
+
"""
|
|
122
|
+
# Create Reader instance directly without context manager
|
|
123
|
+
# This is crucial for zero-copy views: if the reader is closed (via __exit__),
|
|
124
|
+
# the memory-mapped data becomes invalid.
|
|
125
|
+
# The reader is kept alive by the returned arrays via their `_reader_ref`.
|
|
126
|
+
reader = Reader(str(filename))
|
|
127
|
+
try:
|
|
128
|
+
names = reader.tensor_names
|
|
129
|
+
if not names:
|
|
130
|
+
return {}
|
|
131
|
+
# Use batch API for efficiency
|
|
132
|
+
tensors = reader.read_tensors(names, to='numpy')
|
|
133
|
+
return dict(zip(names, tensors))
|
|
134
|
+
except Exception:
|
|
135
|
+
# If an error occurs during loading, we should close the reader
|
|
136
|
+
# to prevent resource leaks (though GC would eventually do it).
|
|
137
|
+
lib.ztensor_reader_free(reader._ptr)
|
|
138
|
+
reader._ptr = None
|
|
139
|
+
raise
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def load(data: bytes) -> Dict[str, np.ndarray]:
|
|
143
|
+
"""
|
|
144
|
+
Loads a ztensor file into numpy format from pure bytes.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
data: The content of a ztensor file.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Dictionary that contains name as key, value as `np.ndarray`.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> from ztensor.numpy import load
|
|
154
|
+
>>> file_path = "./my_folder/bert.zt"
|
|
155
|
+
>>> with open(file_path, "rb") as f:
|
|
156
|
+
... data = f.read()
|
|
157
|
+
>>> loaded = load(data)
|
|
158
|
+
"""
|
|
159
|
+
# Write bytes to a temporary file and then load
|
|
160
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".zt") as tmp:
|
|
161
|
+
tmp.write(data)
|
|
162
|
+
tmp_path = tmp.name
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
return load_file(tmp_path)
|
|
166
|
+
finally:
|
|
167
|
+
if os.path.exists(tmp_path):
|
|
168
|
+
os.unlink(tmp_path)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# --- Helper Functions ---
|
|
172
|
+
|
|
173
|
+
def _validate_tensors(tensor_dict: Dict[str, np.ndarray]) -> None:
|
|
174
|
+
"""Validates that all tensors are valid for saving."""
|
|
175
|
+
if not isinstance(tensor_dict, dict):
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"Expected a dict of [str, np.ndarray] but received {type(tensor_dict)}"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
for k, v in tensor_dict.items():
|
|
181
|
+
if not isinstance(v, np.ndarray):
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"Key `{k}` is invalid, expected np.ndarray but received {type(v)}"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
__all__ = [
|
|
188
|
+
"save_file",
|
|
189
|
+
"save",
|
|
190
|
+
"load_file",
|
|
191
|
+
"load",
|
|
192
|
+
]
|
ztensor/torch.py
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyTorch convenience functions for ztensor.
|
|
3
|
+
|
|
4
|
+
This module provides a safetensors-compatible API for saving and loading
|
|
5
|
+
PyTorch tensors in the ztensor format.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from ztensor.torch import save_file, load_file
|
|
9
|
+
>>> import torch
|
|
10
|
+
>>> tensors = {"embedding": torch.zeros((512, 1024))}
|
|
11
|
+
>>> save_file(tensors, "model.zt")
|
|
12
|
+
>>> loaded = load_file("model.zt")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import tempfile
|
|
17
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import torch
|
|
21
|
+
except ImportError:
|
|
22
|
+
raise ImportError(
|
|
23
|
+
"PyTorch is required to use ztensor.torch. "
|
|
24
|
+
"Please install it with: pip install torch"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from . import Reader, Writer, ZTensorError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def save_file(
|
|
31
|
+
tensors: Dict[str, torch.Tensor],
|
|
32
|
+
filename: Union[str, os.PathLike],
|
|
33
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
34
|
+
compression: Union[bool, int] = False,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Saves a dictionary of tensors into `filename` in ztensor format.
|
|
38
|
+
|
|
39
|
+
There is no mechanism in place to prevent the caller from modifying
|
|
40
|
+
the data while a file save occurs. Please be wary when calling
|
|
41
|
+
`save_file` and modifying tensors referenced in the `tensors` dict
|
|
42
|
+
concurrently; it may lead to corrupted files.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
tensors: The incoming tensors. Tensors need to be contiguous and dense.
|
|
46
|
+
filename: The filename we're saving into.
|
|
47
|
+
metadata: Optional text only metadata you might want to save in your
|
|
48
|
+
header. For instance it can be useful to specify more about the
|
|
49
|
+
underlying tensors. This is purely informative and does not
|
|
50
|
+
affect tensor loading.
|
|
51
|
+
NOTE: ztensor does not currently support custom metadata; this
|
|
52
|
+
parameter is accepted for API compatibility with safetensors
|
|
53
|
+
binary format.
|
|
54
|
+
compression: Compression settings. False/0 (raw), True (level 3), or int > 0 (level).
|
|
55
|
+
Default: False.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
None
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
>>> from ztensor.torch import save_file
|
|
62
|
+
>>> import torch
|
|
63
|
+
>>> tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
|
|
64
|
+
>>> save_file(tensors, "model.zt")
|
|
65
|
+
"""
|
|
66
|
+
_validate_tensors(tensors)
|
|
67
|
+
|
|
68
|
+
with Writer(str(filename)) as writer:
|
|
69
|
+
for name, tensor in tensors.items():
|
|
70
|
+
if tensor.is_cuda:
|
|
71
|
+
tensor = tensor.cpu()
|
|
72
|
+
if not tensor.is_contiguous():
|
|
73
|
+
tensor = tensor.contiguous()
|
|
74
|
+
writer.add_tensor(name, tensor, compress=compression)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def save(
|
|
78
|
+
tensors: Dict[str, torch.Tensor],
|
|
79
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
80
|
+
compression: Union[bool, int] = False,
|
|
81
|
+
) -> bytes:
|
|
82
|
+
"""
|
|
83
|
+
Saves a dictionary of tensors into raw bytes in ztensor format.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
tensors: The incoming tensors. Tensors need to be contiguous and dense.
|
|
87
|
+
metadata: Optional text only metadata you might want to save in your
|
|
88
|
+
header. This is purely informative and does not affect tensor loading.
|
|
89
|
+
NOTE: ztensor does not currently support custom metadata; this
|
|
90
|
+
parameter is accepted for API compatibility with safetensors
|
|
91
|
+
but will be ignored.
|
|
92
|
+
compression: Compression settings. False/0 (raw), True (level 3), or int > 0 (level).
|
|
93
|
+
Default: False.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The raw bytes representing the format.
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> from ztensor.torch import save
|
|
100
|
+
>>> import torch
|
|
101
|
+
>>> tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
|
|
102
|
+
>>> byte_data = save(tensors)
|
|
103
|
+
"""
|
|
104
|
+
_validate_tensors(tensors)
|
|
105
|
+
|
|
106
|
+
# Create a temporary file, write to it, read the bytes, then clean up
|
|
107
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".zt") as tmp:
|
|
108
|
+
tmp_path = tmp.name
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
save_file(tensors, tmp_path, metadata=metadata, compression=compression)
|
|
112
|
+
with open(tmp_path, "rb") as f:
|
|
113
|
+
return f.read()
|
|
114
|
+
finally:
|
|
115
|
+
if os.path.exists(tmp_path):
|
|
116
|
+
os.unlink(tmp_path)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def load_file(
|
|
120
|
+
filename: Union[str, os.PathLike],
|
|
121
|
+
device: Union[str, int] = "cpu",
|
|
122
|
+
) -> Dict[str, torch.Tensor]:
|
|
123
|
+
"""
|
|
124
|
+
Loads a ztensor file into torch format.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
filename: The name of the file which contains the tensors.
|
|
128
|
+
device: The device where the tensors need to be located after load.
|
|
129
|
+
Available options are all regular torch device locations.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Dictionary that contains name as key, value as `torch.Tensor`.
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
>>> from ztensor.torch import load_file
|
|
136
|
+
>>> file_path = "./my_folder/bert.zt"
|
|
137
|
+
>>> loaded = load_file(file_path)
|
|
138
|
+
"""
|
|
139
|
+
# Normalize device
|
|
140
|
+
if isinstance(device, int):
|
|
141
|
+
device = f"cuda:{device}"
|
|
142
|
+
target_device = torch.device(device)
|
|
143
|
+
|
|
144
|
+
# Create Reader instance directly without context manager to support zero-copy
|
|
145
|
+
reader = Reader(str(filename))
|
|
146
|
+
try:
|
|
147
|
+
names = reader.tensor_names
|
|
148
|
+
if not names:
|
|
149
|
+
return {}
|
|
150
|
+
# Use batch API for efficiency
|
|
151
|
+
tensors = reader.read_tensors(names, to='torch')
|
|
152
|
+
result = {}
|
|
153
|
+
for name, tensor in zip(names, tensors):
|
|
154
|
+
if target_device.type != "cpu":
|
|
155
|
+
# This performs a copy to the device
|
|
156
|
+
tensor = tensor.to(target_device)
|
|
157
|
+
result[name] = tensor
|
|
158
|
+
return result
|
|
159
|
+
except Exception:
|
|
160
|
+
lib.ztensor_reader_free(reader._ptr)
|
|
161
|
+
reader._ptr = None
|
|
162
|
+
raise
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def load(data: bytes) -> Dict[str, torch.Tensor]:
|
|
166
|
+
"""
|
|
167
|
+
Loads a ztensor file into torch format from pure bytes.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
data: The content of a ztensor file.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dictionary that contains name as key, value as `torch.Tensor` on cpu.
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
>>> from ztensor.torch import load
|
|
177
|
+
>>> file_path = "./my_folder/bert.zt"
|
|
178
|
+
>>> with open(file_path, "rb") as f:
|
|
179
|
+
... data = f.read()
|
|
180
|
+
>>> loaded = load(data)
|
|
181
|
+
"""
|
|
182
|
+
# Write bytes to a temporary file and then load
|
|
183
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".zt") as tmp:
|
|
184
|
+
tmp.write(data)
|
|
185
|
+
tmp_path = tmp.name
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
return load_file(tmp_path, device="cpu")
|
|
189
|
+
finally:
|
|
190
|
+
if os.path.exists(tmp_path):
|
|
191
|
+
os.unlink(tmp_path)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def save_model(
|
|
195
|
+
model: torch.nn.Module,
|
|
196
|
+
filename: Union[str, os.PathLike],
|
|
197
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
198
|
+
force_contiguous: bool = True,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Saves a given torch model to specified filename.
|
|
202
|
+
|
|
203
|
+
This method handles tensor sharing issues by detecting shared tensors
|
|
204
|
+
and only saving each unique tensor once, recording the mapping in
|
|
205
|
+
metadata so it can be restored on load.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
model: The model to save on disk.
|
|
209
|
+
filename: The filename location to save the file.
|
|
210
|
+
metadata: Extra information to save along with the file.
|
|
211
|
+
Some metadata will be added for each dropped tensors.
|
|
212
|
+
This information will not be enough to recover the entire
|
|
213
|
+
shared structure but might help understanding things.
|
|
214
|
+
NOTE: ztensor does not currently support custom metadata; this
|
|
215
|
+
parameter is accepted for API compatibility with safetensors.
|
|
216
|
+
force_contiguous: Forcing the state_dict to be saved as contiguous
|
|
217
|
+
tensors. This has no effect on the correctness of the model,
|
|
218
|
+
but it could potentially change performance if the layout of
|
|
219
|
+
the tensor was chosen specifically for that reason.
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
>>> from ztensor.torch import save_model
|
|
223
|
+
>>> import torch
|
|
224
|
+
>>> model = torch.nn.Linear(10, 5)
|
|
225
|
+
>>> save_model(model, "model.zt")
|
|
226
|
+
"""
|
|
227
|
+
state_dict = model.state_dict()
|
|
228
|
+
|
|
229
|
+
# Handle tensor sharing by removing duplicates
|
|
230
|
+
to_removes = _remove_duplicate_names(state_dict)
|
|
231
|
+
|
|
232
|
+
if metadata is None:
|
|
233
|
+
metadata = {}
|
|
234
|
+
|
|
235
|
+
for kept_name, to_remove_group in to_removes.items():
|
|
236
|
+
for to_remove in to_remove_group:
|
|
237
|
+
if to_remove not in metadata:
|
|
238
|
+
# Record which tensor this was mapped to
|
|
239
|
+
metadata[to_remove] = kept_name
|
|
240
|
+
del state_dict[to_remove]
|
|
241
|
+
|
|
242
|
+
if force_contiguous:
|
|
243
|
+
state_dict = {k: v.contiguous() for k, v in state_dict.items()}
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
save_file(state_dict, filename, metadata=metadata)
|
|
247
|
+
except ValueError as e:
|
|
248
|
+
msg = str(e)
|
|
249
|
+
msg += " Or use save_model(..., force_contiguous=True), read the docs for potential caveats."
|
|
250
|
+
raise ValueError(msg)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def load_model(
|
|
254
|
+
model: torch.nn.Module,
|
|
255
|
+
filename: Union[str, os.PathLike],
|
|
256
|
+
strict: bool = True,
|
|
257
|
+
device: Union[str, int] = "cpu",
|
|
258
|
+
) -> Tuple[List[str], List[str]]:
|
|
259
|
+
"""
|
|
260
|
+
Loads a given filename onto a torch model.
|
|
261
|
+
|
|
262
|
+
This method handles tensor sharing issues which are not allowed in
|
|
263
|
+
ztensor format.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
model: The model to load onto.
|
|
267
|
+
filename: The filename location to load the file from.
|
|
268
|
+
strict: Whether to fail if you're missing keys or having unexpected ones.
|
|
269
|
+
When false, the function simply returns missing and unexpected names.
|
|
270
|
+
device: The device where the tensors need to be located after load.
|
|
271
|
+
Available options are all regular torch device locations.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
(missing, unexpected): A tuple of two lists.
|
|
275
|
+
`missing` are names in the model which were not modified during loading.
|
|
276
|
+
`unexpected` are names that are on the file, but weren't used during
|
|
277
|
+
the load.
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
>>> from ztensor.torch import load_model
|
|
281
|
+
>>> import torch
|
|
282
|
+
>>> model = torch.nn.Linear(10, 5)
|
|
283
|
+
>>> missing, unexpected = load_model(model, "model.zt")
|
|
284
|
+
"""
|
|
285
|
+
state_dict = load_file(filename, device=device)
|
|
286
|
+
model_state_dict = model.state_dict()
|
|
287
|
+
|
|
288
|
+
# Handle tensor sharing
|
|
289
|
+
to_removes = _remove_duplicate_names(
|
|
290
|
+
model_state_dict, preferred_names=list(state_dict.keys())
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
missing, unexpected = model.load_state_dict(state_dict, strict=False)
|
|
294
|
+
missing = set(missing)
|
|
295
|
+
|
|
296
|
+
for to_remove_group in to_removes.values():
|
|
297
|
+
for to_remove in to_remove_group:
|
|
298
|
+
if to_remove not in missing:
|
|
299
|
+
unexpected.append(to_remove)
|
|
300
|
+
else:
|
|
301
|
+
missing.remove(to_remove)
|
|
302
|
+
|
|
303
|
+
if strict and (missing or unexpected):
|
|
304
|
+
missing_keys = ", ".join([f'"{k}"' for k in sorted(missing)])
|
|
305
|
+
unexpected_keys = ", ".join([f'"{k}"' for k in sorted(unexpected)])
|
|
306
|
+
error = f"Error(s) in loading state_dict for {model.__class__.__name__}:"
|
|
307
|
+
if missing:
|
|
308
|
+
error += f"\n Missing key(s) in state_dict: {missing_keys}"
|
|
309
|
+
if unexpected:
|
|
310
|
+
error += f"\n Unexpected key(s) in state_dict: {unexpected_keys}"
|
|
311
|
+
raise RuntimeError(error)
|
|
312
|
+
|
|
313
|
+
return list(missing), unexpected
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# --- Helper Functions ---
|
|
317
|
+
|
|
318
|
+
def _validate_tensors(tensors: Dict[str, torch.Tensor]) -> None:
|
|
319
|
+
"""Validates that all tensors are valid for saving."""
|
|
320
|
+
if not isinstance(tensors, dict):
|
|
321
|
+
raise ValueError(
|
|
322
|
+
f"Expected a dict of [str, torch.Tensor] but received {type(tensors)}"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
sparse_tensors = []
|
|
326
|
+
for k, v in tensors.items():
|
|
327
|
+
if not isinstance(v, torch.Tensor):
|
|
328
|
+
raise ValueError(
|
|
329
|
+
f"Key `{k}` is invalid, expected torch.Tensor but received {type(v)}"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if v.layout != torch.strided:
|
|
333
|
+
sparse_tensors.append(k)
|
|
334
|
+
|
|
335
|
+
if sparse_tensors:
|
|
336
|
+
raise ValueError(
|
|
337
|
+
f"You are trying to save sparse tensors: `{sparse_tensors}` which this library does not support."
|
|
338
|
+
" You can make it a dense tensor before saving with `.to_dense()` but be aware this might"
|
|
339
|
+
" make a much larger file than needed."
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Check for shared tensors
|
|
343
|
+
shared_pointers = _find_shared_tensors(tensors)
|
|
344
|
+
failing = []
|
|
345
|
+
for names in shared_pointers:
|
|
346
|
+
if len(names) > 1:
|
|
347
|
+
failing.append(names)
|
|
348
|
+
|
|
349
|
+
if failing:
|
|
350
|
+
failing_info = ", ".join([str(sorted(names)) for names in failing])
|
|
351
|
+
raise ValueError(
|
|
352
|
+
f"Some tensors share memory, this will lead to duplicate data being saved: {failing_info}."
|
|
353
|
+
" Use `save_model` if you want to handle shared tensors automatically."
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _storage_ptr(tensor: torch.Tensor) -> int:
|
|
358
|
+
"""Get the storage pointer of a tensor."""
|
|
359
|
+
try:
|
|
360
|
+
return tensor.untyped_storage().data_ptr()
|
|
361
|
+
except Exception:
|
|
362
|
+
# Fallback for older torch versions
|
|
363
|
+
try:
|
|
364
|
+
return tensor.storage().data_ptr()
|
|
365
|
+
except NotImplementedError:
|
|
366
|
+
# Fallback for meta storage
|
|
367
|
+
return 0
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _storage_size(tensor: torch.Tensor) -> int:
|
|
371
|
+
"""Get the storage size of a tensor in bytes."""
|
|
372
|
+
try:
|
|
373
|
+
return tensor.untyped_storage().nbytes()
|
|
374
|
+
except AttributeError:
|
|
375
|
+
# Fallback for older torch versions
|
|
376
|
+
try:
|
|
377
|
+
return tensor.storage().size() * tensor.element_size()
|
|
378
|
+
except NotImplementedError:
|
|
379
|
+
# Fallback for meta storage
|
|
380
|
+
return tensor.nelement() * tensor.element_size()
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _find_shared_tensors(state_dict: Dict[str, torch.Tensor]) -> List[set]:
|
|
384
|
+
"""Find tensors that share storage."""
|
|
385
|
+
from collections import defaultdict
|
|
386
|
+
|
|
387
|
+
tensors = defaultdict(set)
|
|
388
|
+
for k, v in state_dict.items():
|
|
389
|
+
if (
|
|
390
|
+
v.device != torch.device("meta")
|
|
391
|
+
and _storage_ptr(v) != 0
|
|
392
|
+
and _storage_size(v) != 0
|
|
393
|
+
):
|
|
394
|
+
# Need to add device as key because of multiple GPU
|
|
395
|
+
tensors[(v.device, _storage_ptr(v), _storage_size(v))].add(k)
|
|
396
|
+
|
|
397
|
+
return list(sorted(tensors.values()))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _is_complete(tensor: torch.Tensor) -> bool:
|
|
401
|
+
"""Check if a tensor covers its entire storage."""
|
|
402
|
+
return (
|
|
403
|
+
tensor.data_ptr() == _storage_ptr(tensor) and
|
|
404
|
+
tensor.nelement() * tensor.element_size() == _storage_size(tensor)
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _remove_duplicate_names(
|
|
409
|
+
state_dict: Dict[str, torch.Tensor],
|
|
410
|
+
*,
|
|
411
|
+
preferred_names: Optional[List[str]] = None,
|
|
412
|
+
discard_names: Optional[List[str]] = None,
|
|
413
|
+
) -> Dict[str, List[str]]:
|
|
414
|
+
"""
|
|
415
|
+
Find duplicate tensor names that share storage and determine which to keep.
|
|
416
|
+
|
|
417
|
+
Returns a dict mapping kept_name -> [names_to_remove].
|
|
418
|
+
"""
|
|
419
|
+
from collections import defaultdict
|
|
420
|
+
|
|
421
|
+
if preferred_names is None:
|
|
422
|
+
preferred_names = []
|
|
423
|
+
preferred_names = set(preferred_names)
|
|
424
|
+
if discard_names is None:
|
|
425
|
+
discard_names = []
|
|
426
|
+
discard_names = set(discard_names)
|
|
427
|
+
|
|
428
|
+
shareds = _find_shared_tensors(state_dict)
|
|
429
|
+
to_remove = defaultdict(list)
|
|
430
|
+
|
|
431
|
+
for shared in shareds:
|
|
432
|
+
if len(shared) <= 1:
|
|
433
|
+
continue
|
|
434
|
+
|
|
435
|
+
complete_names = set(
|
|
436
|
+
[name for name in shared if _is_complete(state_dict[name])]
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
if not complete_names:
|
|
440
|
+
raise RuntimeError(
|
|
441
|
+
"Error while trying to find names to remove to save state dict, but found no suitable name to keep"
|
|
442
|
+
f" for saving amongst: {shared}. None is covering the entire storage. Refusing to save/load the model"
|
|
443
|
+
" since you could be storing much more memory than needed."
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
keep_name = sorted(list(complete_names))[0]
|
|
447
|
+
|
|
448
|
+
# Mechanism to preferentially select keys to keep
|
|
449
|
+
preferred = complete_names.difference(discard_names)
|
|
450
|
+
if preferred:
|
|
451
|
+
keep_name = sorted(list(preferred))[0]
|
|
452
|
+
|
|
453
|
+
if preferred_names:
|
|
454
|
+
preferred = preferred_names.intersection(complete_names)
|
|
455
|
+
if preferred:
|
|
456
|
+
keep_name = sorted(list(preferred))[0]
|
|
457
|
+
|
|
458
|
+
for name in sorted(shared):
|
|
459
|
+
if name != keep_name:
|
|
460
|
+
to_remove[keep_name].append(name)
|
|
461
|
+
|
|
462
|
+
return to_remove
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
__all__ = [
|
|
466
|
+
"save_file",
|
|
467
|
+
"save",
|
|
468
|
+
"load_file",
|
|
469
|
+
"load",
|
|
470
|
+
"save_model",
|
|
471
|
+
"load_model",
|
|
472
|
+
]
|
ztensor/ztensor/ffi.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# auto-generated file
|
|
2
|
+
import _cffi_backend
|
|
3
|
+
|
|
4
|
+
ffi = _cffi_backend.FFI('ffi',
|
|
5
|
+
_version = 0x2601,
|
|
6
|
+
_types = b'\x00\x00\x5C\x0D\x00\x00\x78\x03\x00\x00\x7B\x03\x00\x00\x00\x0F\x00\x00\x5C\x0D\x00\x00\x01\x11\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x5F\x0D\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x62\x0D\x00\x00\x78\x03\x00\x00\x02\x11\x00\x00\x02\x11\x00\x00\x00\x0F\x00\x00\x62\x0D\x00\x00\x0C\x11\x00\x00\x02\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x62\x0D\x00\x00\x01\x11\x00\x00\x02\x11\x00\x00\x00\x0F\x00\x00\x65\x0D\x00\x00\x0C\x11\x00\x00\x02\x03\x00\x00\x1C\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x0C\x0D\x00\x00\x02\x11\x00\x00\x00\x0F\x00\x00\x2B\x0D\x00\x00\x02\x11\x00\x00\x00\x0F\x00\x00\x6E\x0D\x00\x00\x74\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x79\x03\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x2B\x11\x00\x00\x02\x11\x00\x00\x7C\x03\x00\x00\x1C\x01\x00\x00\x02\x11\x00\x00\x7D\x03\x00\x00\x1C\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x2B\x11\x00\x00\x02\x11\x00\x00\x30\x11\x00\x00\x1C\x01\x00\x00\x02\x11\x00\x00\x33\x11\x00\x00\x1C\x01\x00\x00\x30\x11\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x2B\x11\x00\x00\x02\x11\x00\x00\x30\x11\x00\x00\x1C\x01\x00\x00\x02\x11\x00\x00\x33\x11\x00\x00\x1C\x01\x00\x00\x30\x11\x00\x00\x1C\x01\x00\x00\x30\x11\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x06\x0D\x00\x00\x26\x11\x00\x00\x00\x0F\x00\x00\x06\x0D\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x71\x0D\x00\x00\x26\x11\x00\x00\x00\x0F\x00\x00\x7C\x0D\x00\x00\x26\x11\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x74\x03\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x75\x03\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x76\x03\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x77\x03\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x0C\x11\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x2B\x11\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x7B\x03\x00\x00\x00\x0F\x00\x00\x7F\x0D\x00\x00\x7C\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x04\x09\x00\x00\x05\x09\x00\x00\x6E\x03\x00\x00\x02\x01\x00\x00\x18\x01\x00\x00\x04\x01\x00\x00\x7F\x03\x00\x00\x00\x01',
|
|
7
|
+
_globals = (b'\xFF\xFF\xFF\x1FALIGNMENT',64,b'\xFF\xFF\xFF\x1FMAX_MANIFEST_SIZE',1073741824,b'\x00\x00\x6D\x23ztensor_free_string',0,b'\x00\x00\x5E\x23ztensor_free_string_array',0,b'\x00\x00\x61\x23ztensor_free_tensor_view',0,b'\x00\x00\x64\x23ztensor_free_tensor_view_array',0,b'\x00\x00\x70\x23ztensor_free_u64_array',0,b'\x00\x00\x28\x23ztensor_last_error_message',0,b'\x00\x00\x5B\x23ztensor_metadata_free',0,b'\x00\x00\x25\x23ztensor_metadata_get_checksum_str',0,b'\x00\x00\x25\x23ztensor_metadata_get_data_endianness_str',0,b'\x00\x00\x25\x23ztensor_metadata_get_dtype_str',0,b'\x00\x00\x25\x23ztensor_metadata_get_encoding_str',0,b'\x00\x00\x25\x23ztensor_metadata_get_layout_str',0,b'\x00\x00\x25\x23ztensor_metadata_get_name',0,b'\x00\x00\x58\x23ztensor_metadata_get_offset',0,b'\x00\x00\x55\x23ztensor_metadata_get_shape_data',0,b'\x00\x00\x4F\x23ztensor_metadata_get_shape_len',0,b'\x00\x00\x58\x23ztensor_metadata_get_size',0,b'\x00\x00\x67\x23ztensor_reader_free',0,b'\x00\x00\x08\x23ztensor_reader_get_all_tensor_names',0,b'\x00\x00\x04\x23ztensor_reader_get_metadata_by_index',0,b'\x00\x00\x00\x23ztensor_reader_get_metadata_by_name',0,b'\x00\x00\x52\x23ztensor_reader_get_metadata_count',0,b'\x00\x00\x15\x23ztensor_reader_get_tensor_slice',0,b'\x00\x00\x1F\x23ztensor_reader_open',0,b'\x00\x00\x10\x23ztensor_reader_read_tensor',0,b'\x00\x00\x0B\x23ztensor_reader_read_tensor_component',0,b'\x00\x00\x19\x23ztensor_reader_read_tensors',0,b'\x00\x00\x37\x23ztensor_writer_add_sparse_coo',0,b'\x00\x00\x42\x23ztensor_writer_add_sparse_csr',0,b'\x00\x00\x2D\x23ztensor_writer_add_tensor',0,b'\x00\x00\x22\x23ztensor_writer_create',0,b'\x00\x00\x2A\x23ztensor_writer_finalize',0,b'\x00\x00\x6A\x23ztensor_writer_free',0),
|
|
8
|
+
_struct_unions = ((b'\x00\x00\x00\x74\x00\x00\x00\x10CObjectMetadata',),(b'\x00\x00\x00\x75\x00\x00\x00\x02CStringArray',b'\x00\x00\x7A\x11strings',b'\x00\x00\x06\x11len'),(b'\x00\x00\x00\x76\x00\x00\x00\x02CTensorDataView',b'\x00\x00\x33\x11data',b'\x00\x00\x06\x11len',b'\x00\x00\x7E\x11_owner'),(b'\x00\x00\x00\x77\x00\x00\x00\x02CTensorDataViewArray',b'\x00\x00\x62\x11views',b'\x00\x00\x06\x11len'),(b'\x00\x00\x00\x78\x00\x00\x00\x10CZTensorReader',),(b'\x00\x00\x00\x79\x00\x00\x00\x10ZTensorWriter_BufWriter_File',)),
|
|
9
|
+
_typenames = (b'\x00\x00\x00\x74CObjectMetadata',b'\x00\x00\x00\x75CStringArray',b'\x00\x00\x00\x76CTensorDataView',b'\x00\x00\x00\x77CTensorDataViewArray',b'\x00\x00\x00\x78CZTensorReader',b'\x00\x00\x00\x79CZTensorWriter',b'\x00\x00\x00\x79ZTensorWriter_BufWriter_File'),
|
|
10
|
+
)
|
|
Binary file
|