177 lines
4.8 KiB
Python
177 lines
4.8 KiB
Python
|
import os
|
||
|
import sys
|
||
|
from typing import Dict, Optional, Union
|
||
|
|
||
|
import numpy as np
|
||
|
|
||
|
from safetensors import deserialize, safe_open, serialize, serialize_file
|
||
|
|
||
|
|
||
|
def _tobytes(tensor: np.ndarray) -> bytes:
|
||
|
if not _is_little_endian(tensor):
|
||
|
tensor = tensor.byteswap(inplace=False)
|
||
|
return tensor.tobytes()
|
||
|
|
||
|
|
||
|
def save(tensor_dict: Dict[str, np.ndarray], metadata: Optional[Dict[str, str]] = None) -> bytes:
|
||
|
"""
|
||
|
Saves a dictionary of tensors into raw bytes in safetensors format.
|
||
|
|
||
|
Args:
|
||
|
tensor_dict (`Dict[str, np.ndarray]`):
|
||
|
The incoming tensors. Tensors need to be contiguous and dense.
|
||
|
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
|
||
|
Optional text only metadata you might want to save in your header.
|
||
|
For instance it can be useful to specify more about the underlying
|
||
|
tensors. This is purely informative and does not affect tensor loading.
|
||
|
|
||
|
Returns:
|
||
|
`bytes`: The raw bytes representing the format
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```python
|
||
|
from safetensors.numpy import save
|
||
|
import numpy as np
|
||
|
|
||
|
tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
|
||
|
byte_data = save(tensors)
|
||
|
```
|
||
|
"""
|
||
|
flattened = {k: {"dtype": v.dtype.name, "shape": v.shape, "data": _tobytes(v)} for k, v in tensor_dict.items()}
|
||
|
serialized = serialize(flattened, metadata=metadata)
|
||
|
result = bytes(serialized)
|
||
|
return result
|
||
|
|
||
|
|
||
|
def save_file(
|
||
|
tensor_dict: Dict[str, np.ndarray], filename: Union[str, os.PathLike], metadata: Optional[Dict[str, str]] = None
|
||
|
) -> None:
|
||
|
"""
|
||
|
Saves a dictionary of tensors into raw bytes in safetensors format.
|
||
|
|
||
|
Args:
|
||
|
tensor_dict (`Dict[str, np.ndarray]`):
|
||
|
The incoming tensors. Tensors need to be contiguous and dense.
|
||
|
filename (`str`, or `os.PathLike`)):
|
||
|
The filename we're saving into.
|
||
|
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
|
||
|
Optional text only metadata you might want to save in your header.
|
||
|
For instance it can be useful to specify more about the underlying
|
||
|
tensors. This is purely informative and does not affect tensor loading.
|
||
|
|
||
|
Returns:
|
||
|
`None`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```python
|
||
|
from safetensors.numpy import save_file
|
||
|
import numpy as np
|
||
|
|
||
|
tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
|
||
|
save_file(tensors, "model.safetensors")
|
||
|
```
|
||
|
"""
|
||
|
flattened = {k: {"dtype": v.dtype.name, "shape": v.shape, "data": _tobytes(v)} for k, v in tensor_dict.items()}
|
||
|
serialize_file(flattened, filename, metadata=metadata)
|
||
|
|
||
|
|
||
|
def load(data: bytes) -> Dict[str, np.ndarray]:
|
||
|
"""
|
||
|
Loads a safetensors file into numpy format from pure bytes.
|
||
|
|
||
|
Args:
|
||
|
data (`bytes`):
|
||
|
The content of a safetensors file
|
||
|
|
||
|
Returns:
|
||
|
`Dict[str, np.ndarray]`: dictionary that contains name as key, value as `np.ndarray` on cpu
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```python
|
||
|
from safetensors.numpy import load
|
||
|
|
||
|
file_path = "./my_folder/bert.safetensors"
|
||
|
with open(file_path, "rb") as f:
|
||
|
data = f.read()
|
||
|
|
||
|
loaded = load(data)
|
||
|
```
|
||
|
"""
|
||
|
flat = deserialize(data)
|
||
|
return _view2np(flat)
|
||
|
|
||
|
|
||
|
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, np.ndarray]:
|
||
|
"""
|
||
|
Loads a safetensors file into numpy format.
|
||
|
|
||
|
Args:
|
||
|
filename (`str`, or `os.PathLike`)):
|
||
|
The name of the file which contains the tensors
|
||
|
|
||
|
Returns:
|
||
|
`Dict[str, np.ndarray]`: dictionary that contains name as key, value as `np.ndarray`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```python
|
||
|
from safetensors.numpy import load_file
|
||
|
|
||
|
file_path = "./my_folder/bert.safetensors"
|
||
|
loaded = load_file(file_path)
|
||
|
```
|
||
|
"""
|
||
|
result = {}
|
||
|
with safe_open(filename, framework="np") as f:
|
||
|
for k in f.keys():
|
||
|
result[k] = f.get_tensor(k)
|
||
|
return result
|
||
|
|
||
|
|
||
|
_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,
|
||
|
"I8": np.int8,
|
||
|
"U8": np.uint8,
|
||
|
"BOOL": bool,
|
||
|
}
|
||
|
|
||
|
|
||
|
def _getdtype(dtype_str: str) -> np.dtype:
|
||
|
return _TYPES[dtype_str]
|
||
|
|
||
|
|
||
|
def _view2np(safeview) -> Dict[str, np.ndarray]:
|
||
|
result = {}
|
||
|
for k, v in safeview:
|
||
|
dtype = _getdtype(v["dtype"])
|
||
|
arr = np.frombuffer(v["data"], dtype=dtype).reshape(v["shape"])
|
||
|
result[k] = arr
|
||
|
return result
|
||
|
|
||
|
|
||
|
def _is_little_endian(tensor: np.ndarray) -> bool:
|
||
|
byteorder = tensor.dtype.byteorder
|
||
|
if byteorder == "=":
|
||
|
if sys.byteorder == "little":
|
||
|
return True
|
||
|
else:
|
||
|
return False
|
||
|
elif byteorder == "|":
|
||
|
return True
|
||
|
elif byteorder == "<":
|
||
|
return True
|
||
|
elif byteorder == ">":
|
||
|
return False
|
||
|
raise ValueError(f"Unexpected byte order {byteorder}")
|