Initial import: grid-bot — grid trading bot for BTC-USDT on Cifra Markets

This commit is contained in:
Kolp
2026-09-24 13:22:23 +07:00
commit 642cc11a9f
18968 changed files with 5683248 additions and 0 deletions
@@ -0,0 +1,10 @@
# Re-export this
from ._safetensors_rust import ( # noqa: F401
SafetensorError,
__version__,
deserialize,
safe_open,
_safe_open_handle,
serialize,
serialize_file,
)
@@ -0,0 +1,164 @@
# Generated content DO NOT EDIT
@staticmethod
def deserialize(bytes):
"""
Opens a safetensors lazily and returns tensors as asked
Args:
data (`bytes`):
The byte content of a file
Returns:
(`List[str, Dict[str, Dict[str, any]]]`):
The deserialized content is like:
[("tensor_name", {"shape": [2, 3], "dtype": "F32", "data": b"\0\0.." }), (...)]
"""
pass
@staticmethod
def serialize(tensor_dict, metadata=None):
"""
Serializes raw data.
Args:
tensor_dict (`Dict[str, Dict[Any]]`):
The tensor dict is like:
{"tensor_name": {"dtype": "F32", "shape": [2, 3], "data": b"\0\0"}}
metadata (`Dict[str, str]`, *optional*):
The optional purely text annotations
Returns:
(`bytes`):
The serialized content.
"""
pass
@staticmethod
def serialize_file(tensor_dict, filename, metadata=None):
"""
Serializes raw data into file.
Args:
tensor_dict (`Dict[str, Dict[Any]]`):
The tensor dict is like:
{"tensor_name": {"dtype": "F32", "shape": [2, 3], "data": b"\0\0"}}
filename (`str`, or `os.PathLike`):
The name of the file to write into.
metadata (`Dict[str, str]`, *optional*):
The optional purely text annotations
Returns:
(`NoneType`):
On success return None
"""
pass
class safe_open:
"""
Opens a safetensors lazily and returns tensors as asked
Args:
filename (`str`, or `os.PathLike`):
The filename to open
framework (`str`):
The framework you want you tensors in. Supported values:
`pt`, `tf`, `flax`, `numpy`.
device (`str`, defaults to `"cpu"`):
The device on which you want the tensors.
"""
def __init__(self, filename, framework, device=...):
pass
def __enter__(self):
"""
Start the context manager
"""
pass
def __exit__(self, _exc_type, _exc_value, _traceback):
"""
Exits the context manager
"""
pass
def get_slice(self, name):
"""
Returns a full slice view object
Args:
name (`str`):
The name of the tensor you want
Returns:
(`PySafeSlice`):
A dummy object you can slice into to get a real tensor
Example:
```python
from safetensors import safe_open
with safe_open("model.safetensors", framework="pt", device=0) as f:
tensor_part = f.get_slice("embedding")[:, ::8]
```
"""
pass
def get_tensor(self, name):
"""
Returns a full tensor
Args:
name (`str`):
The name of the tensor you want
Returns:
(`Tensor`):
The tensor in the framework you opened the file for.
Example:
```python
from safetensors import safe_open
with safe_open("model.safetensors", framework="pt", device=0) as f:
tensor = f.get_tensor("embedding")
```
"""
pass
def keys(self):
"""
Returns the names of the tensors in the file.
Returns:
(`List[str]`):
The name of the tensors contained in that file
"""
pass
def metadata(self):
"""
Return the special non tensor information in the header
Returns:
(`Dict[str, str]`):
The freeform metadata.
"""
pass
def offset_keys(self):
"""
Returns the names of the tensors in the file, ordered by offset.
Returns:
(`List[str]`):
The name of the tensors contained in that file
"""
pass
class SafetensorError(Exception):
"""
Custom Python Exception for Safetensor errors.
"""
@@ -0,0 +1,138 @@
import os
from typing import Dict, Optional, Union
import numpy as np
import jax.numpy as jnp
from jax import Array
from safetensors import numpy, safe_open
def save(tensors: Dict[str, Array], metadata: Optional[Dict[str, str]] = None) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, Array]`):
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.flax import save
from jax import numpy as jnp
tensors = {"embedding": jnp.zeros((512, 1024)), "attention": jnp.zeros((256, 256))}
byte_data = save(tensors)
```
"""
np_tensors = _jnp2np(tensors)
return numpy.save(np_tensors, metadata=metadata)
def save_file(
tensors: Dict[str, Array],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, Array]`):
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.flax import save_file
from jax import numpy as jnp
tensors = {"embedding": jnp.zeros((512, 1024)), "attention": jnp.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
np_tensors = _jnp2np(tensors)
return numpy.save_file(np_tensors, filename, metadata=metadata)
def load(data: bytes) -> Dict[str, Array]:
"""
Loads a safetensors file into flax format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, Array]`: dictionary that contains name as key, value as `Array` on cpu
Example:
```python
from safetensors.flax import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = numpy.load(data)
return _np2jnp(flat)
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, Array]:
"""
Loads a safetensors file into flax format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
Returns:
`Dict[str, Array]`: dictionary that contains name as key, value as `Array`
Example:
```python
from safetensors.flax import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="flax") as f:
for k in f.offset_keys():
result[k] = f.get_tensor(k)
return result
def _np2jnp(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, Array]:
for k, v in numpy_dict.items():
numpy_dict[k] = jnp.array(v)
return numpy_dict
def _jnp2np(jnp_dict: Dict[str, Array]) -> Dict[str, np.array]:
for k, v in jnp_dict.items():
jnp_dict[k] = np.asarray(v)
return jnp_dict
@@ -0,0 +1,140 @@
import os
from typing import Dict, Optional, Union
import numpy as np
import mlx.core as mx
from safetensors import numpy, safe_open
def save(
tensors: Dict[str, mx.array], metadata: Optional[Dict[str, str]] = None
) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, mx.array]`):
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.mlx import save
import mlx.core as mx
tensors = {"embedding": mx.zeros((512, 1024)), "attention": mx.zeros((256, 256))}
byte_data = save(tensors)
```
"""
np_tensors = _mx2np(tensors)
return numpy.save(np_tensors, metadata=metadata)
def save_file(
tensors: Dict[str, mx.array],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, mx.array]`):
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.mlx import save_file
import mlx.core as mx
tensors = {"embedding": mx.zeros((512, 1024)), "attention": mx.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
np_tensors = _mx2np(tensors)
return numpy.save_file(np_tensors, filename, metadata=metadata)
def load(data: bytes) -> Dict[str, mx.array]:
"""
Loads a safetensors file into MLX format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, mx.array]`: dictionary that contains name as key, value as `mx.array`
Example:
```python
from safetensors.mlx import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = numpy.load(data)
return _np2mx(flat)
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, mx.array]:
"""
Loads a safetensors file into MLX format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
Returns:
`Dict[str, mx.array]`: dictionary that contains name as key, value as `mx.array`
Example:
```python
from safetensors.flax import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="mlx") as f:
for k in f.offset_keys():
result[k] = f.get_tensor(k)
return result
def _np2mx(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, mx.array]:
for k, v in numpy_dict.items():
numpy_dict[k] = mx.array(v)
return numpy_dict
def _mx2np(mx_dict: Dict[str, mx.array]) -> Dict[str, np.array]:
new_dict = {}
for k, v in mx_dict.items():
new_dict[k] = np.asarray(v)
return new_dict
@@ -0,0 +1,187 @@
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.offset_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,
"C64": np.complex64,
}
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}")
@@ -0,0 +1,290 @@
import os
import sys
from typing import Any, Dict, Optional, Union
import numpy as np
import paddle
from safetensors import numpy, deserialize, safe_open, serialize, serialize_file
def save(
tensors: Dict[str, paddle.Tensor], metadata: Optional[Dict[str, str]] = None
) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, paddle.Tensor]`):
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.paddle import save
import paddle
tensors = {"embedding": paddle.zeros((512, 1024)), "attention": paddle.zeros((256, 256))}
byte_data = save(tensors)
```
"""
serialized = serialize(_flatten(tensors), metadata=metadata)
result = bytes(serialized)
return result
def save_file(
tensors: Dict[str, paddle.Tensor],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, paddle.Tensor]`):
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.paddle import save_file
import paddle
tensors = {"embedding": paddle.zeros((512, 1024)), "attention": paddle.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
serialize_file(_flatten(tensors), filename, metadata=metadata)
def load(data: bytes, device: str = "cpu") -> Dict[str, paddle.Tensor]:
"""
Loads a safetensors file into paddle format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, paddle.Tensor]`: dictionary that contains name as key, value as `paddle.Tensor` on cpu
Example:
```python
from safetensors.paddle import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
if paddle.__version__ >= "3.2.0":
flat = deserialize(data)
return _view2paddle(flat, device)
else:
flat = numpy.load(data)
return _np2paddle(flat, device)
def load_file(
filename: Union[str, os.PathLike], device="cpu"
) -> Dict[str, paddle.Tensor]:
"""
Loads a safetensors file into paddle format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
device (`Union[Dict[str, any], str]`, *optional*, defaults to `cpu`):
The device where the tensors need to be located after load.
available options are all regular paddle device locations
Returns:
`Dict[str, paddle.Tensor]`: dictionary that contains name as key, value as `paddle.Tensor`
Example:
```python
from safetensors.paddle import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
if paddle.__version__ >= "3.2.0":
with safe_open(filename, framework="paddle", device=device) as f:
for k in f.offset_keys():
result[k] = f.get_tensor(k)
else:
flat = numpy.load_file(filename)
result = _np2paddle(flat, device)
return result
def _np2paddle(
numpy_dict: Dict[str, np.ndarray], device: str = "cpu"
) -> Dict[str, paddle.Tensor]:
for k, v in numpy_dict.items():
numpy_dict[k] = paddle.to_tensor(v, place=device)
return numpy_dict
def _paddle2np(paddle_dict: Dict[str, paddle.Tensor]) -> Dict[str, np.array]:
for k, v in paddle_dict.items():
paddle_dict[k] = v.detach().cpu().numpy()
return paddle_dict
_SIZE = {
paddle.int64: 8,
paddle.float32: 4,
paddle.int32: 4,
paddle.bfloat16: 2,
paddle.float16: 2,
paddle.int16: 2,
paddle.uint8: 1,
paddle.int8: 1,
paddle.bool: 1,
paddle.float64: 8,
paddle.float8_e4m3fn: 1,
paddle.float8_e5m2: 1,
paddle.complex64: 8,
# XXX: These are not supported yet in paddle
# paddle.uint64: 8,
# paddle.uint32: 4,
# paddle.uint16: 2,
# paddle.float8_e8m0: 1,
# paddle.float4_e2m1_x2: 1,
}
_TYPES = {
"F64": paddle.float64,
"F32": paddle.float32,
"F16": paddle.float16,
"BF16": paddle.bfloat16,
"I64": paddle.int64,
"I32": paddle.int32,
"I16": paddle.int16,
"I8": paddle.int8,
"U8": paddle.uint8,
"BOOL": paddle.bool,
"F8_E4M3": paddle.float8_e4m3fn,
"F8_E5M2": paddle.float8_e5m2,
}
NPDTYPES = {
paddle.int64: np.int64,
paddle.float32: np.float32,
paddle.int32: np.int32,
# XXX: This is ok because both have the same width
paddle.bfloat16: np.float16,
paddle.float16: np.float16,
paddle.int16: np.int16,
paddle.uint8: np.uint8,
paddle.int8: np.int8,
paddle.bool: bool,
paddle.float64: np.float64,
# XXX: This is ok because both have the same width and byteswap is a no-op anyway
paddle.float8_e4m3fn: np.uint8,
paddle.float8_e5m2: np.uint8,
}
def _getdtype(dtype_str: str) -> paddle.dtype:
return _TYPES[dtype_str]
def _view2paddle(safeview, device) -> Dict[str, paddle.Tensor]:
result = {}
for k, v in safeview:
dtype = _getdtype(v["dtype"])
if len(v["data"]) == 0:
# Workaround because frombuffer doesn't accept zero-size tensors
assert any(x == 0 for x in v["shape"])
arr = paddle.empty(v["shape"], dtype=dtype)
else:
arr = paddle.base.core.frombuffer(v["data"], dtype).reshape(v["shape"])
if device != "cpu":
arr = arr.to(device)
if sys.byteorder == "big":
arr = paddle.to_tensor(arr.numpy().byteswap(inplace=False), place=device)
result[k] = arr
return result
def _tobytes(tensor: paddle.Tensor, name: str) -> bytes:
if not tensor.is_contiguous():
raise ValueError(
f"You are trying to save a non contiguous tensor: `{name}` which is not allowed. It either means you"
" are trying to save tensors which are reference of each other in which case it's recommended to save"
" only the full tensors, and reslice at load time, or simply call `.contiguous()` on your tensor to"
" pack it before saving."
)
if not tensor.place.is_cpu_place():
# Moving tensor to cpu before saving
tensor = tensor.cpu()
import ctypes
import numpy as np
# When shape is empty (scalar), np.prod returns a float
# we need a int for the following calculations
length = int(np.prod(tensor.shape).item())
bytes_per_item = _SIZE[tensor.dtype]
total_bytes = length * bytes_per_item
ptr = tensor.data_ptr()
if ptr == 0:
return b""
newptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte))
data = np.ctypeslib.as_array(newptr, (total_bytes,)) # no internal copy
if sys.byteorder == "big":
npdtype = NPDTYPES[tensor.dtype]
# Not in place as that would potentially modify a live running model
data = data.view(npdtype).byteswap(inplace=False)
return data.tobytes()
def _flatten(tensors: Dict[str, paddle.Tensor]) -> Dict[str, Dict[str, Any]]:
if not isinstance(tensors, dict):
raise ValueError(
f"Expected a dict of [str, paddle.Tensor] but received {type(tensors)}"
)
for k, v in tensors.items():
if not isinstance(v, paddle.Tensor):
raise ValueError(
f"Key `{k}` is invalid, expected paddle.Tensor but received {type(v)}"
)
return {
k: {
"dtype": str(v.dtype).split(".")[-1],
"shape": v.shape,
"data": _tobytes(v, k),
}
for k, v in tensors.items()
}
@@ -0,0 +1,139 @@
import os
from typing import Dict, Optional, Union
import numpy as np
import tensorflow as tf
from safetensors import numpy, safe_open
def save(
tensors: Dict[str, tf.Tensor], metadata: Optional[Dict[str, str]] = None
) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, tf.Tensor]`):
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.tensorflow import save
import tensorflow as tf
tensors = {"embedding": tf.zeros((512, 1024)), "attention": tf.zeros((256, 256))}
byte_data = save(tensors)
```
"""
np_tensors = _tf2np(tensors)
return numpy.save(np_tensors, metadata=metadata)
def save_file(
tensors: Dict[str, tf.Tensor],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, tf.Tensor]`):
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.tensorflow import save_file
import tensorflow as tf
tensors = {"embedding": tf.zeros((512, 1024)), "attention": tf.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
np_tensors = _tf2np(tensors)
return numpy.save_file(np_tensors, filename, metadata=metadata)
def load(data: bytes) -> Dict[str, tf.Tensor]:
"""
Loads a safetensors file into tensorflow format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, tf.Tensor]`: dictionary that contains name as key, value as `tf.Tensor` on cpu
Example:
```python
from safetensors.tensorflow import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = numpy.load(data)
return _np2tf(flat)
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, tf.Tensor]:
"""
Loads a safetensors file into tensorflow format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
Returns:
`Dict[str, tf.Tensor]`: dictionary that contains name as key, value as `tf.Tensor`
Example:
```python
from safetensors.tensorflow import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="tf") as f:
for k in f.offset_keys():
result[k] = f.get_tensor(k)
return result
def _np2tf(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, tf.Tensor]:
for k, v in numpy_dict.items():
numpy_dict[k] = tf.convert_to_tensor(v)
return numpy_dict
def _tf2np(tf_dict: Dict[str, tf.Tensor]) -> Dict[str, np.array]:
for k, v in tf_dict.items():
tf_dict[k] = v.numpy()
return tf_dict
@@ -0,0 +1,550 @@
import os
import sys
from collections import defaultdict
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from packaging.version import Version
import torch
from safetensors import deserialize, safe_open, serialize, serialize_file
def storage_ptr(tensor: torch.Tensor) -> int:
try:
return tensor.untyped_storage().data_ptr()
except Exception:
# Fallback for torch==1.10
try:
return tensor.storage().data_ptr()
except NotImplementedError:
# Fallback for meta storage
return 0
def _end_ptr(tensor: torch.Tensor) -> int:
if tensor.nelement():
stop = tensor.view(-1)[-1].data_ptr() + _SIZE[tensor.dtype]
else:
stop = tensor.data_ptr()
return stop
def storage_size(tensor: torch.Tensor) -> int:
try:
return tensor.untyped_storage().nbytes()
except AttributeError:
# Fallback for torch==1.10
try:
return tensor.storage().size() * _SIZE[tensor.dtype]
except NotImplementedError:
# Fallback for meta storage
# On torch >=2.0 this is the tensor size
return tensor.nelement() * _SIZE[tensor.dtype]
def _filter_shared_not_shared(
tensors: List[Set[str]], state_dict: Dict[str, torch.Tensor]
) -> List[Set[str]]:
filtered_tensors = []
for shared in tensors:
if len(shared) < 2:
filtered_tensors.append(shared)
continue
areas = []
for name in shared:
tensor = state_dict[name]
areas.append((tensor.data_ptr(), _end_ptr(tensor), name))
areas.sort()
_, last_stop, last_name = areas[0]
filtered_tensors.append({last_name})
for start, stop, name in areas[1:]:
if start >= last_stop:
filtered_tensors.append({name})
else:
filtered_tensors[-1].add(name)
last_stop = stop
return filtered_tensors
def _find_shared_tensors(state_dict: Dict[str, torch.Tensor]) -> List[Set[str]]:
tensors = defaultdict(set)
for k, v in state_dict.items():
if (
v.device != torch.device("meta")
and storage_ptr(v) != 0
and storage_size(v) != 0
):
# Need to add device as key because of multiple GPU.
tensors[(v.device, storage_ptr(v), storage_size(v))].add(k)
tensors = list(sorted(tensors.values()))
tensors = _filter_shared_not_shared(tensors, state_dict)
return tensors
def _is_complete(tensor: torch.Tensor) -> bool:
return tensor.data_ptr() == storage_ptr(tensor) and tensor.nelement() * _SIZE[
tensor.dtype
] == storage_size(tensor)
def _remove_duplicate_names(
state_dict: Dict[str, torch.Tensor],
*,
preferred_names: Optional[List[str]] = None,
discard_names: Optional[List[str]] = None,
) -> Dict[str, List[str]]:
if preferred_names is None:
preferred_names = []
preferred_names = set(preferred_names)
if discard_names is None:
discard_names = []
discard_names = set(discard_names)
shareds = _find_shared_tensors(state_dict)
to_remove = defaultdict(list)
for shared in shareds:
complete_names = set(
[name for name in shared if _is_complete(state_dict[name])]
)
if not complete_names:
raise RuntimeError(
"Error while trying to find names to remove to save state dict, but found no suitable name to keep"
f" for saving amongst: {shared}. None is covering the entire storage.Refusing to save/load the model"
" since you could be storing much more memory than needed. Please refer to"
" https://huggingface.co/docs/safetensors/torch_shared_tensors for more information. Or open an"
" issue."
)
keep_name = sorted(list(complete_names))[0]
# Mechanism to preferentially select keys to keep
# coming from the on-disk file to allow
# loading models saved with a different choice
# of keep_name
preferred = complete_names.difference(discard_names)
if preferred:
keep_name = sorted(list(preferred))[0]
if preferred_names:
preferred = preferred_names.intersection(complete_names)
if preferred:
keep_name = sorted(list(preferred))[0]
for name in sorted(shared):
if name != keep_name:
to_remove[keep_name].append(name)
return to_remove
def save_model(
model: torch.nn.Module,
filename: str,
metadata: Optional[Dict[str, str]] = None,
force_contiguous: bool = True,
):
"""
Saves a given torch model to specified filename.
This method exists specifically to avoid tensor sharing issues which are
not allowed in `safetensors`. [More information on tensor sharing](../torch_shared_tensors)
Args:
model (`torch.nn.Module`):
The model to save on disk.
filename (`str`):
The filename location to save the file
metadata (`Dict[str, str]`, *optional*):
Extra information to save along with the file.
Some metadata will be added for each dropped tensors.
This information will not be enough to recover the entire
shared structure but might help understanding things
force_contiguous (`boolean`, *optional*, defaults to True):
Forcing the state_dict to be saved as contiguous tensors.
This has no effect on the correctness of the model, but it
could potentially change performance if the layout of the tensor
was chosen specifically for that reason.
"""
state_dict = model.state_dict()
to_removes = _remove_duplicate_names(state_dict)
for kept_name, to_remove_group in to_removes.items():
for to_remove in to_remove_group:
if metadata is None:
metadata = {}
if to_remove not in metadata:
# Do not override user data
metadata[to_remove] = kept_name
del state_dict[to_remove]
if force_contiguous:
state_dict = {k: v.contiguous() for k, v in state_dict.items()}
try:
save_file(state_dict, filename, metadata=metadata)
except ValueError as e:
msg = str(e)
msg += " Or use save_model(..., force_contiguous=True), read the docs for potential caveats."
raise ValueError(msg)
def load_model(
model: torch.nn.Module,
filename: Union[str, os.PathLike],
strict: bool = True,
device: Union[str, int] = "cpu",
) -> Tuple[List[str], List[str]]:
"""
Loads a given filename onto a torch model.
This method exists specifically to avoid tensor sharing issues which are
not allowed in `safetensors`. [More information on tensor sharing](../torch_shared_tensors)
Args:
model (`torch.nn.Module`):
The model to load onto.
filename (`str`, or `os.PathLike`):
The filename location to load the file from.
strict (`bool`, *optional*, defaults to True):
Whether to fail if you're missing keys or having unexpected ones.
When false, the function simply returns missing and unexpected names.
device (`Union[str, int]`, *optional*, defaults to `cpu`):
The device where the tensors need to be located after load.
available options are all regular torch device locations.
Returns:
`(missing, unexpected): (List[str], List[str])`
`missing` are names in the model which were not modified during loading
`unexpected` are names that are on the file, but weren't used during
the load.
"""
state_dict = load_file(filename, device=device)
model_state_dict = model.state_dict()
to_removes = _remove_duplicate_names(
model_state_dict, preferred_names=state_dict.keys()
)
missing, unexpected = model.load_state_dict(state_dict, strict=False)
missing = set(missing)
for to_remove_group in to_removes.values():
for to_remove in to_remove_group:
if to_remove not in missing:
unexpected.append(to_remove)
else:
missing.remove(to_remove)
if strict and (missing or unexpected):
missing_keys = ", ".join([f'"{k}"' for k in sorted(missing)])
unexpected_keys = ", ".join([f'"{k}"' for k in sorted(unexpected)])
error = f"Error(s) in loading state_dict for {model.__class__.__name__}:"
if missing:
error += f"\n Missing key(s) in state_dict: {missing_keys}"
if unexpected:
error += f"\n Unexpected key(s) in state_dict: {unexpected_keys}"
raise RuntimeError(error)
return missing, unexpected
def save(
tensors: Dict[str, torch.Tensor], metadata: Optional[Dict[str, str]] = None
) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, torch.Tensor]`):
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.torch import save
import torch
tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
byte_data = save(tensors)
```
"""
serialized = serialize(_flatten(tensors), metadata=metadata)
result = bytes(serialized)
return result
def save_file(
tensors: Dict[str, torch.Tensor],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
):
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, torch.Tensor]`):
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.torch import save_file
import torch
tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
serialize_file(_flatten(tensors), filename, metadata=metadata)
def load_file(
filename: Union[str, os.PathLike], device: Union[str, int] = "cpu"
) -> Dict[str, torch.Tensor]:
"""
Loads a safetensors file into torch format.
Args:
filename (`str`, or `os.PathLike`):
The name of the file which contains the tensors
device (`Union[str, int]`, *optional*, defaults to `cpu`):
The device where the tensors need to be located after load.
available options are all regular torch device locations.
Returns:
`Dict[str, torch.Tensor]`: dictionary that contains name as key, value as `torch.Tensor`
Example:
```python
from safetensors.torch import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="pt", device=device) as f:
for k in f.offset_keys():
result[k] = f.get_tensor(k)
return result
def load(data: bytes) -> Dict[str, torch.Tensor]:
"""
Loads a safetensors file into torch format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, torch.Tensor]`: dictionary that contains name as key, value as `torch.Tensor` on cpu
Example:
```python
from safetensors.torch 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 _view2torch(flat)
# torch.float8 formats require 2.1; we do not support these dtypes on earlier versions
_float8_e4m3fn = getattr(torch, "float8_e4m3fn", None)
_float8_e5m2 = getattr(torch, "float8_e5m2", None)
_float8_e8m0 = getattr(torch, "float8_e8m0fnu", None)
_float4_e2m1_x2 = getattr(torch, "float4_e2m1fn_x2", None)
_SIZE = {
torch.int64: 8,
torch.float32: 4,
torch.int32: 4,
torch.bfloat16: 2,
torch.float16: 2,
torch.int16: 2,
torch.uint8: 1,
torch.int8: 1,
torch.bool: 1,
torch.float64: 8,
torch.complex64: 8,
_float8_e4m3fn: 1,
_float8_e5m2: 1,
_float8_e8m0: 1,
_float4_e2m1_x2: 1,
}
if Version(torch.__version__) >= Version("2.3.0"):
_SIZE.update(
{
torch.uint64: 8,
torch.uint32: 4,
torch.uint16: 2,
}
)
_TYPES = {
"F64": torch.float64,
"F32": torch.float32,
"F16": torch.float16,
"BF16": torch.bfloat16,
"I64": torch.int64,
"I32": torch.int32,
"I16": torch.int16,
"I8": torch.int8,
"U8": torch.uint8,
"BOOL": torch.bool,
"F8_E4M3": _float8_e4m3fn,
"F8_E5M2": _float8_e5m2,
"C64": torch.complex64,
}
if Version(torch.__version__) >= Version("2.3.0"):
_TYPES.update(
{
"U64": torch.uint64,
"U32": torch.uint32,
"U16": torch.uint16,
}
)
def _getdtype(dtype_str: str) -> torch.dtype:
return _TYPES[dtype_str]
def _view2torch(safeview) -> Dict[str, torch.Tensor]:
result = {}
for k, v in safeview:
dtype = _getdtype(v["dtype"])
if len(v["data"]) == 0:
# Workaround because frombuffer doesn't accept zero-size tensors
assert any(x == 0 for x in v["shape"])
arr = torch.empty(v["shape"], dtype=dtype)
else:
arr = torch.frombuffer(v["data"], dtype=dtype).reshape(v["shape"])
if sys.byteorder == "big":
arr = torch.from_numpy(arr.numpy().byteswap(inplace=False))
result[k] = arr
return result
def _tobytes(tensor: torch.Tensor, name: str) -> bytes:
if tensor.layout != torch.strided:
raise ValueError(
f"You are trying to save a sparse tensor: `{name}` which this library does not support."
" You can make it a dense tensor before saving with `.to_dense()` but be aware this might"
" make a much larger file than needed."
)
if not tensor.is_contiguous():
raise ValueError(
f"You are trying to save a non contiguous tensor: `{name}` which is not allowed. It either means you"
" are trying to save tensors which are reference of each other in which case it's recommended to save"
" only the full tensors, and reslice at load time, or simply call `.contiguous()` on your tensor to"
" pack it before saving."
)
if tensor.device.type != "cpu":
# Moving tensor to cpu before saving
tensor = tensor.to("cpu")
import ctypes
import numpy as np
# When shape is empty (scalar), np.prod returns a float
# we need a int for the following calculations
length = int(np.prod(tensor.shape).item())
bytes_per_item = _SIZE[tensor.dtype]
total_bytes = length * bytes_per_item
ptr = tensor.data_ptr()
if ptr == 0:
return b""
newptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte))
data = np.ctypeslib.as_array(newptr, (total_bytes,)) # no internal copy
if sys.byteorder == "big":
NPDTYPES = {
torch.int64: np.int64,
torch.float32: np.float32,
torch.int32: np.int32,
# XXX: This is ok because both have the same width
torch.bfloat16: np.float16,
torch.float16: np.float16,
torch.int16: np.int16,
torch.uint8: np.uint8,
torch.int8: np.int8,
torch.bool: bool,
torch.float64: np.float64,
# XXX: This is ok because both have the same width and byteswap is a no-op anyway
_float8_e4m3fn: np.uint8,
_float8_e5m2: np.uint8,
torch.complex64: np.complex64,
}
npdtype = NPDTYPES[tensor.dtype]
# Not in place as that would potentially modify a live running model
data = data.view(npdtype).byteswap(inplace=False)
return data.tobytes()
def _flatten(tensors: Dict[str, torch.Tensor]) -> Dict[str, Dict[str, Any]]:
if not isinstance(tensors, dict):
raise ValueError(
f"Expected a dict of [str, torch.Tensor] but received {type(tensors)}"
)
invalid_tensors = []
for k, v in tensors.items():
if not isinstance(v, torch.Tensor):
raise ValueError(
f"Key `{k}` is invalid, expected torch.Tensor but received {type(v)}"
)
if v.layout != torch.strided:
invalid_tensors.append(k)
if invalid_tensors:
raise ValueError(
f"You are trying to save a sparse tensors: `{invalid_tensors}` which this library does not support."
" You can make it a dense tensor before saving with `.to_dense()` but be aware this might"
" make a much larger file than needed."
)
shared_pointers = _find_shared_tensors(tensors)
failing = []
for names in shared_pointers:
if len(names) > 1:
failing.append(names)
if failing:
raise RuntimeError(
f"""
Some tensors share memory, this will lead to duplicate memory on disk and potential differences when loading them again: {failing}.
A potential way to correctly save your model is to use `save_model`.
More information at https://huggingface.co/docs/safetensors/torch_shared_tensors
"""
)
return {
k: {
"dtype": str(v.dtype).split(".")[-1],
"shape": v.shape,
"data": _tobytes(v, k),
}
for k, v in tensors.items()
}