-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathserialization.py
More file actions
309 lines (245 loc) · 10 KB
/
Copy pathserialization.py
File metadata and controls
309 lines (245 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import io
import os
import pickle
from functools import lru_cache
from typing import Union
from zipfile import ZipFile
import numpy as np
import paddle
from _io import BufferedReader
from safetensors import deserialize
from paddlenlp.utils.env import PYTORCH_WEIGHTS_NAME, SAFE_WEIGHTS_NAME
MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30
_TYPES = {
"F64": np.float64,
"F32": np.float32,
"F16": np.float16,
"I64": np.int64,
"U64": np.uint64,
"I32": np.int32,
"U32": np.uint32,
"I16": np.int16,
"U16": np.uint16,
"BF16": np.uint16,
"I8": np.int8,
"U8": np.uint8,
"BOOL": bool,
}
class SerializationError(Exception):
"""Exception for serialization"""
pass
def seek_by_string(file_handler: BufferedReader, string: str, file_size: int) -> int:
"""seek the index of file-handler with target words
Args:
file_handler (BufferedReader): file handler
string (str): the specific string in the file
file_size (int): size of file
Returns:
int: end index of target string
"""
word_index = 0
word_bytes = string.encode("latin")
empty_byte = "".encode("latin")
while word_index < len(string) and file_handler.tell() < file_size:
content = file_handler.read(1)
if content == empty_byte:
break
if word_bytes[word_index] == content[0]:
word_index += 1
else:
word_index = 0
if file_handler.tell() >= file_size - 1:
raise SerializationError(f"can't find the find the target string<{string}> in the file")
return file_handler.tell()
def read_prefix_key(path):
file_size = os.stat(path).st_size
with open(path, "rb") as file_handler:
end_index = seek_by_string(file_handler, "data.pkl", file_size)
file_handler.seek(MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
prefix_key = file_handler.read(end_index - MZ_ZIP_LOCAL_DIR_HEADER_SIZE - len("/data.pkl"))
return prefix_key.decode("latin")
def _maybe_decode_ascii(bytes_str: Union[bytes, str]) -> str:
if isinstance(bytes_str, bytes):
return bytes_str.decode("ascii")
return bytes_str
@lru_cache(maxsize=None)
def _storage_type_to_dtype_to_map():
"""convert storage type to numpy dtype"""
return {
"DoubleStorage": np.double,
"FloatStorage": np.float32,
"HalfStorage": np.half,
"LongStorage": np.int64,
"IntStorage": np.int32,
"ShortStorage": np.int16,
"CharStorage": np.int8,
"ByteStorage": np.uint8,
"BoolStorage": np.bool_,
"ComplexDoubleStorage": np.cdouble,
"ComplexFloatStorage": np.cfloat,
"BFloat16Storage": np.uint16, # support bf16
}
class StorageType:
"""Temp Class for Storage Type"""
def __init__(self, name):
self.dtype = _storage_type_to_dtype_to_map()[name]
def __str__(self):
return f"StorageType(dtype={self.dtype})"
def _element_size(dtype: str) -> int:
"""
Returns the element size for a dtype, in bytes
"""
if dtype in [np.float16, np.float32, np.float64]:
return np.finfo(dtype).bits >> 3
elif dtype == np.bool_:
return 1
else:
return np.iinfo(dtype).bits >> 3
class UnpicklerWrapperStage(pickle.Unpickler):
def find_class(self, mod_name, name):
if type(name) is str and "Storage" in name:
try:
return StorageType(name)
except KeyError:
pass
if mod_name == "torch._utils":
# rebuild torch.nn.Papameter
if name == "_rebuild_parameter":
return _rebuild_parameter
# rebuild torch.nn.Papameter with state
if name == "_rebuild_parameter_with_state":
return _rebuild_parameter_with_state
# rebuild torch.Tensor
return _rebuild_tensor_stage
# pytorch_lightning tensor builder
if "pytorch_lightning" in mod_name:
return dumpy
return super().find_class(mod_name, name)
class SafeUnpickler(pickle.Unpickler):
"""
A safe unpickler that only allows loading of built-in basic data types.
"""
def find_class(self, module, name):
"""
Overrides the find_class method to only allow loading of built-in basic data types.
:param module: The module name.
:param name: The class name.
:return: The class if allowed, otherwise raises UnpicklingError.
"""
if module == "builtins" and name in {"int", "float", "str", "tuple", "list", "dict", "set"}:
return super().find_class(module, name)
raise pickle.UnpicklingError(f"Unsafe object loading is prohibited: {module}.{name}")
def _rebuild_tensor_stage(storage, storage_offset, size, stride, requires_grad, backward_hooks):
# if a tensor has shape [M, N] and stride is [1, N], it's column-wise / fortran-style
# if a tensor has shape [M, N] and stride is [M, 1], it's row-wise / C-style
# defaults to C-style
if stride is not None and len(stride) > 1 and stride[0] == 1 and stride[1] > 1:
order = "F"
else:
order = "C"
# fix bug when load https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth
numel = int(np.prod(size))
return storage[storage_offset : storage_offset + numel].reshape(size, order=order)
def _rebuild_parameter(data, requires_grad, backward_hooks):
return data
def _rebuild_parameter_with_state(data, requires_grad, backward_hooks, state):
return data
def dumpy(*args, **kwarsg):
return None
def load_torch(path: str, **pickle_load_args):
from paddlenlp.transformers.utils import device_guard
if path.endswith(PYTORCH_WEIGHTS_NAME) or os.path.split(path)[-1].startswith("pytorch_model-"):
import torch
state_dict = torch.load(path, map_location="cpu", weights_only=False)
for key in list(state_dict.keys()):
if isinstance(state_dict[key], torch.Tensor):
t = state_dict.pop(key)
capsule = torch.utils.dlpack.to_dlpack(t)
t = paddle.utils.dlpack.from_dlpack(capsule)
state_dict[key] = t
return state_dict
elif path.endswith(SAFE_WEIGHTS_NAME) or os.path.split(path)[-1].startswith("model-"):
# torch safetensors -> numpy -> paddle.Tensor
with open(path, "rb") as f:
data = f.read()
flat = deserialize(data)
state_dict = {}
for k, v in flat:
dtype = _TYPES[v["dtype"]]
with device_guard("cpu"):
if v["dtype"] == "BF16":
arr = paddle.Tensor(
np.frombuffer(v["data"], dtype=dtype).reshape(v["shape"]), dtype="bfloat16", zero_copy=True
)
else:
arr = paddle.Tensor(np.frombuffer(v["data"], dtype=dtype).reshape(v["shape"]), zero_copy=True)
state_dict[k] = arr
return state_dict
def load_torch_inner(path: str, **pickle_load_args):
"""
load torch weight file with the following steps:
1. load the structure of pytorch weight file
2. read the tensor data and re-construct the state-dict
Args:
path: the path of pytorch weight file
**pickle_load_args: args of pickle module
Returns:
"""
if path.endswith(PYTORCH_WEIGHTS_NAME) or os.path.split(path)[-1].startswith("pytorch_model-"):
pickle_load_args.update({"encoding": "utf-8"})
prefix_key = read_prefix_key(path)
torch_zip = ZipFile(path, "r")
loaded_storages = {}
def load_tensor(dtype, numel, key, location):
name = f"{prefix_key}/data/{key}"
typed_storage = np.frombuffer(torch_zip.open(name).read()[:numel], dtype=dtype)
return typed_storage
def persistent_load(saved_id):
assert isinstance(saved_id, tuple)
typename = _maybe_decode_ascii(saved_id[0])
data = saved_id[1:]
assert (
typename == "storage"
), f"Unknown typename for persistent_load, expected 'storage' but got '{typename}'"
storage_type, key, location, numel = data
dtype = storage_type.dtype
if key in loaded_storages:
typed_storage = loaded_storages[key]
else:
nbytes = numel * _element_size(dtype)
typed_storage = load_tensor(dtype, nbytes, key, _maybe_decode_ascii(location))
loaded_storages[key] = typed_storage
return typed_storage
data_iostream = torch_zip.open(f"{prefix_key}/data.pkl").read()
unpickler_stage = UnpicklerWrapperStage(io.BytesIO(data_iostream), **pickle_load_args)
unpickler_stage.persistent_load = persistent_load
state_dict = unpickler_stage.load()
torch_zip.close()
elif path.endswith(SAFE_WEIGHTS_NAME) or os.path.split(path)[-1].startswith("model-"):
# torch safetensors -> numpy -> paddle.Tensor
with open(path, "rb") as f:
data = f.read()
flat = deserialize(data)
state_dict = {}
for k, v in flat:
dtype = _TYPES[v["dtype"]]
if v["dtype"] == "BF16":
arr = paddle.to_tensor(np.frombuffer(v["data"], dtype=dtype).reshape(v["shape"]), dtype="bfloat16")
else:
arr = paddle.to_tensor(np.frombuffer(v["data"], dtype=dtype).reshape(v["shape"]))
state_dict[k] = arr
return state_dict