From 6544bf21bfa416999ea4c304d11e1d2b9379518d Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Tue, 24 Jun 2025 07:33:58 +0000 Subject: [PATCH 1/7] Rust release upgrade (cache v1 is discontinued). (#627) --- .github/workflows/rust-release.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 03341944..0556a906 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -15,13 +15,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - + - uses: dtolnay/rust-toolchain@stable - name: Cache Cargo Registry - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.cargo/registry key: ubuntu-latest-cargo-registry-${{ hashFiles('**/Cargo.toml') }} From 8814598e41e2f840bef3a8f491a952ae999bf7ca Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Thu, 3 Jul 2025 20:22:13 +0200 Subject: [PATCH 2/7] Re-adding support for u16, u32, u64. (#629) --- bindings/python/py_src/safetensors/torch.py | 47 +++++++++++++++------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/bindings/python/py_src/safetensors/torch.py b/bindings/python/py_src/safetensors/torch.py index 568d4aa7..4f5c801f 100644 --- a/bindings/python/py_src/safetensors/torch.py +++ b/bindings/python/py_src/safetensors/torch.py @@ -41,7 +41,9 @@ def storage_size(tensor: torch.Tensor) -> int: return tensor.nelement() * _SIZE[tensor.dtype] -def _filter_shared_not_shared(tensors: List[Set[str]], state_dict: Dict[str, torch.Tensor]) -> List[Set[str]]: +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: @@ -69,7 +71,11 @@ def _filter_shared_not_shared(tensors: List[Set[str]], state_dict: Dict[str, tor 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: + 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())) @@ -78,7 +84,9 @@ def _find_shared_tensors(state_dict: Dict[str, torch.Tensor]) -> List[Set[str]]: def _is_complete(tensor: torch.Tensor) -> bool: - return tensor.data_ptr() == storage_ptr(tensor) and tensor.nelement() * _SIZE[tensor.dtype] == storage_size(tensor) + return tensor.data_ptr() == storage_ptr(tensor) and tensor.nelement() * _SIZE[ + tensor.dtype + ] == storage_size(tensor) def _remove_duplicate_names( @@ -97,7 +105,9 @@ def _remove_duplicate_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])]) + 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" @@ -207,7 +217,9 @@ def load_model( """ 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()) + to_removes = _remove_duplicate_names( + model_state_dict, preferred_names=state_dict.keys() + ) reverse_to_remove = {} for key, to_remove_group in to_removes.items(): @@ -273,7 +285,9 @@ def load_model( return missing, unexpected -def save(tensors: Dict[str, torch.Tensor], metadata: Optional[Dict[str, str]] = None) -> bytes: +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. @@ -337,7 +351,9 @@ def save_file( serialize_file(_flatten(tensors), filename, metadata=metadata) -def load_file(filename: Union[str, os.PathLike], device: Union[str, int] = "cpu") -> Dict[str, torch.Tensor]: +def load_file( + filename: Union[str, os.PathLike], device: Union[str, int] = "cpu" +) -> Dict[str, torch.Tensor]: """ Loads a safetensors file into torch format. @@ -402,11 +418,14 @@ def load(data: bytes) -> Dict[str, torch.Tensor]: _SIZE = { torch.int64: 8, + torch.uint64: 8, torch.float32: 4, torch.int32: 4, + torch.uint32: 4, torch.bfloat16: 2, torch.float16: 2, torch.int16: 2, + torch.uint16: 2, torch.uint8: 1, torch.int8: 1, torch.bool: 1, @@ -423,11 +442,11 @@ def load(data: bytes) -> Dict[str, torch.Tensor]: "F16": torch.float16, "BF16": torch.bfloat16, "I64": torch.int64, - # "U64": torch.uint64, + "U64": torch.uint64, "I32": torch.int32, - # "U32": torch.uint32, + "U32": torch.uint32, "I16": torch.int16, - # "U16": torch.uint16, + "U16": torch.uint16, "I8": torch.int8, "U8": torch.uint8, "BOOL": torch.bool, @@ -517,12 +536,16 @@ def _tobytes(tensor: torch.Tensor, name: str) -> bytes: 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)}") + 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)}") + raise ValueError( + f"Key `{k}` is invalid, expected torch.Tensor but received {type(v)}" + ) if v.layout != torch.strided: invalid_tensors.append(k) From 48933f3822e6980ec01ff866b1b412f7efa84620 Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Fri, 1 Aug 2025 17:25:35 +0200 Subject: [PATCH 3/7] Adding _safe_open_handle. (#608) * [WIP]. Adding safe_handle. * Adding S3. * File handle becomes private for merge. --- .pre-commit-config.yaml | 15 ++- .../python/py_src/safetensors/__init__.py | 1 + bindings/python/src/lib.rs | 113 ++++++++++++++++++ bindings/python/tests/test_handle.py | 65 ++++++++++ 4 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 bindings/python/tests/test_handle.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70e9ba35..27dc28c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,12 +38,11 @@ repos: name: "Python (black)" args: ["--line-length", "119", "--target-version", "py35"] types: ["python"] - - repo: https://github.com/pycqa/flake8 - rev: 7.2.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.11.11 hooks: - - id: flake8 - args: ["--config", "bindings/python/setup.cfg"] - - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.7.0 # Use the revision sha / tag you want to point at - hooks: - - id: isort + # Run the linter. + - id: ruff-check + # Run the formatter. + - id: ruff-format diff --git a/bindings/python/py_src/safetensors/__init__.py b/bindings/python/py_src/safetensors/__init__.py index c9a5d2ca..0aea2154 100644 --- a/bindings/python/py_src/safetensors/__init__.py +++ b/bindings/python/py_src/safetensors/__init__.py @@ -4,6 +4,7 @@ __version__, deserialize, safe_open, + _safe_open_handle, serialize, serialize_file, ) diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 527767f4..36f784b4 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -1258,6 +1258,118 @@ pyo3::create_exception!( "Custom Python Exception for Safetensor errors." ); +#[pyclass] +#[allow(non_camel_case_types)] +struct _safe_open_handle { + inner: Option, +} + +impl _safe_open_handle { + fn inner(&self) -> PyResult<&Open> { + let inner = self + .inner + .as_ref() + .ok_or_else(|| SafetensorError::new_err("File is closed".to_string()))?; + Ok(inner) + } +} + +#[pymethods] +impl _safe_open_handle { + #[new] + #[pyo3(signature = (f, framework, device=Some(Device::Cpu)))] + fn new(f: PyObject, framework: Framework, device: Option) -> PyResult { + let filename = Python::with_gil(|py| -> PyResult { + let _ = f.getattr(py, "fileno")?; + let filename = f.getattr(py, "name")?; + let filename: PathBuf = filename.extract(py)?; + Ok(filename) + })?; + let inner = Some(Open::new(filename, framework, device)?); + Ok(Self { inner }) + } + + /// Return the special non tensor information in the header + /// + /// Returns: + /// (`Dict[str, str]`): + /// The freeform metadata. + pub fn metadata(&self) -> PyResult>> { + Ok(self.inner()?.metadata()) + } + + /// Returns the names of the tensors in the file. + /// + /// Returns: + /// (`List[str]`): + /// The name of the tensors contained in that file + pub fn keys(&self) -> PyResult> { + self.inner()?.keys() + } + + /// Returns the names of the tensors in the file, ordered by offset. + /// + /// Returns: + /// (`List[str]`): + /// The name of the tensors contained in that file + pub fn offset_keys(&self) -> PyResult> { + self.inner()?.offset_keys() + } + + /// 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") + /// + /// ``` + pub fn get_tensor(&self, name: &str) -> PyResult { + self.inner()?.get_tensor(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] + /// + /// ``` + pub fn get_slice(&self, name: &str) -> PyResult { + self.inner()?.get_slice(name) + } + + /// Start the context manager + pub fn __enter__(slf: Py) -> Py { + slf + } + + /// Exits the context manager + pub fn __exit__(&mut self, _exc_type: PyObject, _exc_value: PyObject, _traceback: PyObject) { + self.inner = None; + } +} + /// A Python module implemented in Rust. #[pymodule(gil_used = false)] fn _safetensors_rust(m: &PyBound<'_, PyModule>) -> PyResult<()> { @@ -1265,6 +1377,7 @@ fn _safetensors_rust(m: &PyBound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(serialize_file, m)?)?; m.add_function(wrap_pyfunction!(deserialize, m)?)?; m.add_class::()?; + m.add_class::<_safe_open_handle>()?; m.add("SafetensorError", m.py().get_type::())?; m.add("__version__", env!("CARGO_PKG_VERSION"))?; Ok(()) diff --git a/bindings/python/tests/test_handle.py b/bindings/python/tests/test_handle.py new file mode 100644 index 00000000..aaf0717b --- /dev/null +++ b/bindings/python/tests/test_handle.py @@ -0,0 +1,65 @@ +import unittest + +import numpy as np + +from safetensors import _safe_open_handle +from safetensors.numpy import save_file, save + + +class ReadmeTestCase(unittest.TestCase): + def assertTensorEqual(self, tensors1, tensors2, equality_fn): + self.assertEqual(tensors1.keys(), tensors2.keys(), "tensor keys don't match") + + for k, v1 in tensors1.items(): + v2 = tensors2[k] + + self.assertTrue(equality_fn(v1, v2), f"{k} tensors are different") + + def test_numpy_example(self): + tensors = {"a": np.zeros((2, 2)), "b": np.zeros((2, 3), dtype=np.uint8)} + + save_file(tensors, "./out_np.safetensors") + + # Now loading + loaded = {} + with open("./out_np.safetensors", "r") as f: + with safe_open_handle(f, framework="np", device="cpu") as g: + for key in g.keys(): + loaded[key] = g.get_tensor(key) + self.assertTensorEqual(tensors, loaded, np.allclose) + + def test_fsspec(self): + import fsspec + + tensors = {"a": np.zeros((2, 2)), "b": np.zeros((2, 3), dtype=np.uint8)} + + fs = fsspec.filesystem("file") + byts = save(tensors) + with fs.open("fs.safetensors", "wb") as f: + f.write(byts) + # Now loading + loaded = {} + with fs.open("fs.safetensors", "rb") as f: + with safe_open_handle(f, framework="np", device="cpu") as g: + for key in g.keys(): + loaded[key] = g.get_tensor(key) + self.assertTensorEqual(tensors, loaded, np.allclose) + + @unittest.skip("Will not work without s3 access") + def test_fsspec_s3(self): + import s3fs + + tensors = {"a": np.zeros((2, 2)), "b": np.zeros((2, 3), dtype=np.uint8)} + + s3 = s3fs.S3FileSystem(anon=True) + byts = save(tensors) + print(s3.ls("my-bucket")) + with s3.open("out/fs.safetensors", "wb") as f: + f.write(byts) + # Now loading + loaded = {} + with s3.open("out/fs.safetensors", "rb") as f: + with safe_open_handle(f, framework="np", device="cpu") as g: + for key in g.keys(): + loaded[key] = g.get_tensor(key) + self.assertTensorEqual(tensors, loaded, np.allclose) From 7dfa63cf0f62ac74500d92b15844fcd910eadbab Mon Sep 17 00:00:00 2001 From: Alexander Lent Date: Mon, 4 Aug 2025 03:40:07 -0400 Subject: [PATCH 4/7] Fix test_simple.py for 0.6.0 (#634) These tests look for error messages which changed in commit 3012241/PR#616. Fixes: 3012241 ("Better error handling through improved `Display` and `Error` impls (#616)") --- bindings/python/tests/test_simple.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/python/tests/test_simple.py b/bindings/python/tests/test_simple.py index d198391f..1db8c711 100644 --- a/bindings/python/tests/test_simple.py +++ b/bindings/python/tests/test_simple.py @@ -175,7 +175,7 @@ def test_file_not_found(self): with self.assertRaises(FileNotFoundError) as ctx: with safe_open("notafile", framework="pt"): pass - self.assertEqual(str(ctx.exception), 'No such file or directory: "notafile"') + self.assertEqual(str(ctx.exception), 'No such file or directory: notafile') class ReadmeTestCase(unittest.TestCase): @@ -345,19 +345,19 @@ def test_numpy_slice(self): tensor = slice_[2:, 20] self.assertEqual( str(cm.exception), - "Error during slicing [2:, 20] with shape [10, 5]: SliceOutOfRange { dim_index: 1, asked: 20, dim_size: 5 }", + "Error during slicing [2:, 20] with shape [10, 5]: index 20 out of bounds for tensor dimension #1 of size 5", ) with self.assertRaises(SafetensorError) as cm: tensor = slice_[:20] self.assertEqual( str(cm.exception), - "Error during slicing [:20] with shape [10, 5]: SliceOutOfRange { dim_index: 0, asked: 19, dim_size: 10 }", + "Error during slicing [:20] with shape [10, 5]: index 19 out of bounds for tensor dimension #0 of size 10", ) with self.assertRaises(SafetensorError) as cm: tensor = slice_[:, :20] self.assertEqual( str(cm.exception), - "Error during slicing [:, :20] with shape [10, 5]: SliceOutOfRange { dim_index: 1, asked: 19, dim_size: 5 }", + "Error during slicing [:, :20] with shape [10, 5]: index 19 out of bounds for tensor dimension #1 of size 5", ) From b9ccdc69c0ba1f3fad785960981600a6a7553969 Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Tue, 5 Aug 2025 18:42:32 +0200 Subject: [PATCH 5/7] GH action... once again. (#635) * GH action... once again. * Using ruff instead of * Moving to ruff instead of black * Fixing the stub to not use black either. * . * Forgot to flush. * Old torch version dont have unsigned variants. * Fixing the handle tests. * Installing missing numpy. * Use numpy > 2 * ... * Yaml. * .. * ... * ..... * So annoying. * Fix uv lock. * Fix the fp4 tests. * Download hdf5 library. * .... * .. * .. * .. * .. * .. * .. * .. * . * .. * Fixing MLX implementation with newer versions. * Split macos into x86 legacy and latest aarch64. * .. * .. * Lock. * .. * . * Numpy version. --- .github/workflows/python.yml | 49 +++- bindings/python/benches/test_pt.py | 10 +- bindings/python/convert.py | 139 ++++++++--- bindings/python/convert_all.py | 9 +- .../python/py_src/safetensors/__init__.pyi | 19 +- bindings/python/py_src/safetensors/mlx.py | 4 +- bindings/python/py_src/safetensors/numpy.py | 18 +- bindings/python/py_src/safetensors/paddle.py | 12 +- .../python/py_src/safetensors/tensorflow.py | 4 +- bindings/python/py_src/safetensors/torch.py | 23 +- bindings/python/pyproject.toml | 14 +- bindings/python/stub.py | 56 +++-- bindings/python/tests/test_handle.py | 6 +- bindings/python/tests/test_mlx_comparison.py | 8 +- bindings/python/tests/test_pt_comparison.py | 2 +- bindings/python/tests/test_pt_model.py | 36 ++- bindings/python/tests/test_simple.py | 10 +- bindings/python/uv.lock | 227 ++++++------------ 18 files changed, 391 insertions(+), 255 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index fa99ae17..29dbb1d1 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -9,10 +9,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-13, windows-latest] + os: [ubuntu-latest, windows-latest] # Lowest and highest, no version specified so that # new releases get automatically tested against - version: [{torch: torch==1.10, python: "3.9", arch: "x64"}, {torch: torch, python: "3.12", arch: "x64"}] + version: [{torch: torch==1.10, python: "3.9", arch: "x64", numpy: numpy==1.26.4}, {torch: torch, python: "3.12", arch: "x64", numpy: numpy}] # TODO this would include macos ARM target. # however jax has an illegal instruction issue # that exists only in CI (probably difference in instruction support). @@ -26,7 +26,20 @@ jobs: version: torch: torch python: "3.13" + numpy: numpy arch: "x64-freethreaded" + - os: macos-13 + version: + torch: torch==1.10 + numpy: "numpy==1.26" + python: "3.9" + arch: "x64" + - os: macos-latest + version: + torch: torch + python: "3.12" + numpy: numpy + arch: "arm64" defaults: run: working-directory: ./bindings/python @@ -63,14 +76,14 @@ jobs: - name: Run Audit run: cargo audit -D warnings - - name: Install - run: | - pip install -U pip - pip install .[numpy] + # - name: Install + # run: | + # pip install -U pip - name: Install (torch) if: matrix.version.arch != 'x64-freethreaded' run: | + pip install ${{ matrix.version.numpy }} pip install ${{ matrix.version.torch }} shell: bash @@ -80,14 +93,22 @@ jobs: pip install ${{ matrix.version.torch }} --index-url https://download.pytorch.org/whl/cu126 shell: bash + - name: Install (hdf5 non windows) + if: matrix.os == 'ubuntu-latest' && matrix.version.arch != 'x64-freethreaded' + run: | + sudo apt-get update + sudo apt-get install libhdf5-dev + - name: Install (tensorflow) if: matrix.version.arch != 'x64-freethreaded' run: | pip install .[tensorflow] + # Force reinstall of numpy, tensorflow uses numpy 2 even on 3.9 + pip install ${{ matrix.version.numpy }} shell: bash - name: Install (jax, flax) - if: matrix.os != 'windows-latest' && matrix.version.arch != "x64-freethreaded" + if: matrix.os != 'windows-latest' && matrix.version.arch != 'x64-freethreaded' run: pip install .[jax] shell: bash @@ -101,14 +122,24 @@ jobs: - name: Check style run: | pip install .[quality] - black --check --line-length 119 --target-version py35 py_src/safetensors tests + ruff format --check . - name: Run tests + if: matrix.version.arch != 'x64-freethreaded' run: | cargo test - pip install .[testing] + pip install ".[testing]" pytest -sv tests/ + - name: Run tests (freethreaded) + if: matrix.version.arch == 'x64-freethreaded' + run: | + cargo test + pip install ".[testingfree]" + pip install pytest numpy + pytest -sv tests/test_pt* + pytest -sv tests/test_simple.py + test_s390x_big_endian: runs-on: ubuntu-latest permissions: diff --git a/bindings/python/benches/test_pt.py b/bindings/python/benches/test_pt.py index e9c06fcf..b19db48a 100644 --- a/bindings/python/benches/test_pt.py +++ b/bindings/python/benches/test_pt.py @@ -118,7 +118,10 @@ def test_pt_sf_load_gpu(benchmark): assert torch.allclose(v, tv) -@pytest.mark.skipif(not hasattr(torch.backends, "mps") or not torch.backends.mps.is_available(), reason="requires mps") +@pytest.mark.skipif( + not hasattr(torch.backends, "mps") or not torch.backends.mps.is_available(), + reason="requires mps", +) def test_pt_pt_load_mps(benchmark): # benchmark something weights = create_gpt2(12) @@ -133,7 +136,10 @@ def test_pt_pt_load_mps(benchmark): assert torch.allclose(v, tv) -@pytest.mark.skipif(not hasattr(torch.backends, "mps") or not torch.backends.mps.is_available(), reason="requires mps") +@pytest.mark.skipif( + not hasattr(torch.backends, "mps") or not torch.backends.mps.is_available(), + reason="requires mps", +) def test_pt_sf_load_mps(benchmark): # benchmark something weights = create_gpt2(12) diff --git a/bindings/python/convert.py b/bindings/python/convert.py index 3a2e4d1f..8088fa90 100644 --- a/bindings/python/convert.py +++ b/bindings/python/convert.py @@ -8,7 +8,13 @@ import torch -from huggingface_hub import CommitInfo, CommitOperationAdd, Discussion, HfApi, hf_hub_download +from huggingface_hub import ( + CommitInfo, + CommitOperationAdd, + Discussion, + HfApi, + hf_hub_download, +) from huggingface_hub.file_download import repo_folder_name from safetensors.torch import _find_shared_tensors, _is_complete, load_file, save_file @@ -49,7 +55,9 @@ def _remove_duplicate_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])]) + complete_names = set( + [name for name in shared if _is_complete(state_dict[name])] + ) if not complete_names: if len(shared) == 1: # Force contiguous @@ -81,14 +89,20 @@ def _remove_duplicate_names( return to_remove -def get_discard_names(model_id: str, revision: Optional[str], folder: str, token: Optional[str]) -> List[str]: +def get_discard_names( + model_id: str, revision: Optional[str], folder: str, token: Optional[str] +) -> List[str]: try: import json import transformers config_filename = hf_hub_download( - model_id, revision=revision, filename="config.json", token=token, cache_dir=folder + model_id, + revision=revision, + filename="config.json", + token=token, + cache_dir=folder, ) with open(config_filename, "r") as f: config = json.load(f) @@ -129,10 +143,19 @@ def rename(pt_filename: str) -> str: def convert_multi( - model_id: str, *, revision=Optional[str], folder: str, token: Optional[str], discard_names: List[str] + model_id: str, + *, + revision=Optional[str], + folder: str, + token: Optional[str], + discard_names: List[str], ) -> ConversionResult: filename = hf_hub_download( - repo_id=model_id, revision=revision, filename="pytorch_model.bin.index.json", token=token, cache_dir=folder + repo_id=model_id, + revision=revision, + filename="pytorch_model.bin.index.json", + token=token, + cache_dir=folder, ) with open(filename, "r") as f: data = json.load(f) @@ -140,7 +163,9 @@ def convert_multi( filenames = set(data["weight_map"].values()) local_filenames = [] for filename in filenames: - pt_filename = hf_hub_download(repo_id=model_id, filename=filename, token=token, cache_dir=folder) + pt_filename = hf_hub_download( + repo_id=model_id, filename=filename, token=token, cache_dir=folder + ) sf_filename = rename(pt_filename) sf_filename = os.path.join(folder, sf_filename) @@ -156,7 +181,8 @@ def convert_multi( local_filenames.append(index) operations = [ - CommitOperationAdd(path_in_repo=os.path.basename(local), path_or_fileobj=local) for local in local_filenames + CommitOperationAdd(path_in_repo=os.path.basename(local), path_or_fileobj=local) + for local in local_filenames ] errors: List[Tuple[str, "Exception"]] = [] @@ -164,10 +190,19 @@ def convert_multi( def convert_single( - model_id: str, *, revision: Optional[str], folder: str, token: Optional[str], discard_names: List[str] + model_id: str, + *, + revision: Optional[str], + folder: str, + token: Optional[str], + discard_names: List[str], ) -> ConversionResult: pt_filename = hf_hub_download( - repo_id=model_id, revision=revision, filename="pytorch_model.bin", token=token, cache_dir=folder + repo_id=model_id, + revision=revision, + filename="pytorch_model.bin", + token=token, + cache_dir=folder, ) sf_name = "model.safetensors" @@ -219,20 +254,30 @@ def create_diff(pt_infos: Dict[str, List[str]], sf_infos: Dict[str, List[str]]) sf_only = sf_set - pt_set if pt_only: - errors.append(f"{key} : PT warnings contain {pt_only} which are not present in SF warnings") + errors.append( + f"{key} : PT warnings contain {pt_only} which are not present in SF warnings" + ) if sf_only: - errors.append(f"{key} : SF warnings contain {sf_only} which are not present in PT warnings") + errors.append( + f"{key} : SF warnings contain {sf_only} which are not present in PT warnings" + ) return "\n".join(errors) -def previous_pr(api: "HfApi", model_id: str, pr_title: str, revision=Optional[str]) -> Optional["Discussion"]: +def previous_pr( + api: "HfApi", model_id: str, pr_title: str, revision=Optional[str] +) -> Optional["Discussion"]: try: revision_commit = api.model_info(model_id, revision=revision).sha discussions = api.get_repo_discussions(repo_id=model_id) except Exception: return None for discussion in discussions: - if discussion.status in {"open", "closed"} and discussion.is_pull_request and discussion.title == pr_title: + if ( + discussion.status in {"open", "closed"} + and discussion.is_pull_request + and discussion.title == pr_title + ): commits = api.list_repo_commits(model_id, revision=discussion.git_reference) if revision_commit == commits[1].commit_id: @@ -241,7 +286,12 @@ def previous_pr(api: "HfApi", model_id: str, pr_title: str, revision=Optional[st def convert_generic( - model_id: str, *, revision=Optional[str], folder: str, filenames: Set[str], token: Optional[str] + model_id: str, + *, + revision=Optional[str], + folder: str, + filenames: Set[str], + token: Optional[str], ) -> ConversionResult: operations = [] errors = [] @@ -251,7 +301,11 @@ def convert_generic( prefix, ext = os.path.splitext(filename) if ext in extensions: pt_filename = hf_hub_download( - model_id, revision=revision, filename=filename, token=token, cache_dir=folder + model_id, + revision=revision, + filename=filename, + token=token, + cache_dir=folder, ) dirname, raw_filename = os.path.split(filename) if raw_filename == "pytorch_model.bin": @@ -263,7 +317,11 @@ def convert_generic( sf_filename = os.path.join(folder, sf_in_repo) try: convert_file(pt_filename, sf_filename, discard_names=[]) - operations.append(CommitOperationAdd(path_in_repo=sf_in_repo, path_or_fileobj=sf_filename)) + operations.append( + CommitOperationAdd( + path_in_repo=sf_in_repo, path_or_fileobj=sf_filename + ) + ) except Exception as e: errors.append((pt_filename, e)) return operations, errors @@ -285,28 +343,50 @@ def convert( pr = previous_pr(api, model_id, pr_title, revision=revision) library_name = getattr(info, "library_name", None) - if any(filename.endswith(".safetensors") for filename in filenames) and not force: - raise AlreadyExists(f"Model {model_id} is already converted, skipping..") + if ( + any(filename.endswith(".safetensors") for filename in filenames) + and not force + ): + raise AlreadyExists( + f"Model {model_id} is already converted, skipping.." + ) elif pr is not None and not force: url = f"https://huggingface.co/{model_id}/discussions/{pr.num}" new_pr = pr - raise AlreadyExists(f"Model {model_id} already has an open PR check out {url}") + raise AlreadyExists( + f"Model {model_id} already has an open PR check out {url}" + ) elif library_name == "transformers": - - discard_names = get_discard_names(model_id, revision=revision, folder=folder, token=api.token) + discard_names = get_discard_names( + model_id, revision=revision, folder=folder, token=api.token + ) if "pytorch_model.bin" in filenames: operations, errors = convert_single( - model_id, revision=revision, folder=folder, token=api.token, discard_names=discard_names + model_id, + revision=revision, + folder=folder, + token=api.token, + discard_names=discard_names, ) elif "pytorch_model.bin.index.json" in filenames: operations, errors = convert_multi( - model_id, revision=revision, folder=folder, token=api.token, discard_names=discard_names + model_id, + revision=revision, + folder=folder, + token=api.token, + discard_names=discard_names, ) else: - raise RuntimeError(f"Model {model_id} doesn't seem to be a valid pytorch model. Cannot convert") + raise RuntimeError( + f"Model {model_id} doesn't seem to be a valid pytorch model. Cannot convert" + ) else: operations, errors = convert_generic( - model_id, revision=revision, folder=folder, filenames=filenames, token=api.token + model_id, + revision=revision, + folder=folder, + filenames=filenames, + token=api.token, ) if operations: @@ -366,7 +446,9 @@ def convert( " Continue [Y/n] ?" ) if txt.lower() in {"", "y"}: - commit_info, errors = convert(api, model_id, revision=args.revision, force=args.force) + commit_info, errors = convert( + api, model_id, revision=args.revision, force=args.force + ) string = f""" ### Success 🔥 Yay! This model was successfully converted and a PR was open using your token, here: @@ -375,7 +457,8 @@ def convert( if errors: string += "\nErrors during conversion:\n" string += "\n".join( - f"Error while converting {filename}: {e}, skipped conversion" for filename, e in errors + f"Error while converting {filename}: {e}, skipped conversion" + for filename, e in errors ) print(string) else: diff --git a/bindings/python/convert_all.py b/bindings/python/convert_all.py index 39fe9b4e..a2f97715 100644 --- a/bindings/python/convert_all.py +++ b/bindings/python/convert_all.py @@ -1,4 +1,5 @@ """Simple utility tool to convert automatically most downloaded models""" + from convert import AlreadyExists, convert from huggingface_hub import HfApi, ModelFilter, ModelSearchArguments from transformers import AutoConfig @@ -10,7 +11,11 @@ total = 50 models = list( - api.list_models(filter=ModelFilter(library=args.library.Transformers), sort="downloads", direction=-1) + api.list_models( + filter=ModelFilter(library=args.library.Transformers), + sort="downloads", + direction=-1, + ) )[:total] correct = 0 @@ -40,4 +45,4 @@ print(f"Errors: {errors}") print(f"File size is difference {len(errors)}") - print(f"Correct rate {correct}/{total} ({correct/total * 100:.2f}%)") + print(f"Correct rate {correct}/{total} ({correct / total * 100:.2f}%)") diff --git a/bindings/python/py_src/safetensors/__init__.pyi b/bindings/python/py_src/safetensors/__init__.pyi index fa74b755..a89e0655 100644 --- a/bindings/python/py_src/safetensors/__init__.pyi +++ b/bindings/python/py_src/safetensors/__init__.pyi @@ -49,7 +49,7 @@ def serialize_file(tensor_dict, filename, metadata=None): Returns: (`NoneType`): - On success return None. + On success return None """ pass @@ -68,19 +68,21 @@ class safe_open: 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 @@ -102,6 +104,7 @@ class safe_open: ``` """ pass + def get_tensor(self, name): """ Returns a full tensor @@ -124,6 +127,7 @@ class safe_open: ``` """ pass + def keys(self): """ Returns the names of the tensors in the file. @@ -133,6 +137,7 @@ class safe_open: The name of the tensors contained in that file """ pass + def metadata(self): """ Return the special non tensor information in the header @@ -143,6 +148,16 @@ class safe_open: """ 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. diff --git a/bindings/python/py_src/safetensors/mlx.py b/bindings/python/py_src/safetensors/mlx.py index df04e710..985b73b9 100644 --- a/bindings/python/py_src/safetensors/mlx.py +++ b/bindings/python/py_src/safetensors/mlx.py @@ -7,7 +7,9 @@ from safetensors import numpy, safe_open -def save(tensors: Dict[str, mx.array], metadata: Optional[Dict[str, str]] = None) -> bytes: +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. diff --git a/bindings/python/py_src/safetensors/numpy.py b/bindings/python/py_src/safetensors/numpy.py index 89e00e11..282f2871 100644 --- a/bindings/python/py_src/safetensors/numpy.py +++ b/bindings/python/py_src/safetensors/numpy.py @@ -13,7 +13,9 @@ def _tobytes(tensor: np.ndarray) -> bytes: return tensor.tobytes() -def save(tensor_dict: Dict[str, np.ndarray], metadata: Optional[Dict[str, str]] = None) -> bytes: +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. @@ -38,14 +40,19 @@ def save(tensor_dict: Dict[str, np.ndarray], metadata: Optional[Dict[str, str]] byte_data = save(tensors) ``` """ - flattened = {k: {"dtype": v.dtype.name, "shape": v.shape, "data": _tobytes(v)} for k, v in tensor_dict.items()} + 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 + 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. @@ -73,7 +80,10 @@ def save_file( save_file(tensors, "model.safetensors") ``` """ - flattened = {k: {"dtype": v.dtype.name, "shape": v.shape, "data": _tobytes(v)} for k, v in tensor_dict.items()} + 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) diff --git a/bindings/python/py_src/safetensors/paddle.py b/bindings/python/py_src/safetensors/paddle.py index cec36866..5ec459e3 100644 --- a/bindings/python/py_src/safetensors/paddle.py +++ b/bindings/python/py_src/safetensors/paddle.py @@ -7,7 +7,9 @@ from safetensors import numpy -def save(tensors: Dict[str, paddle.Tensor], metadata: Optional[Dict[str, str]] = None) -> bytes: +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. @@ -98,7 +100,9 @@ def load(data: bytes, device: str = "cpu") -> Dict[str, paddle.Tensor]: return _np2paddle(flat, device) -def load_file(filename: Union[str, os.PathLike], device="cpu") -> Dict[str, paddle.Tensor]: +def load_file( + filename: Union[str, os.PathLike], device="cpu" +) -> Dict[str, paddle.Tensor]: """ Loads a safetensors file into paddle format. @@ -126,7 +130,9 @@ def load_file(filename: Union[str, os.PathLike], device="cpu") -> Dict[str, padd return output -def _np2paddle(numpy_dict: Dict[str, np.ndarray], device: str = "cpu") -> Dict[str, paddle.Tensor]: +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 diff --git a/bindings/python/py_src/safetensors/tensorflow.py b/bindings/python/py_src/safetensors/tensorflow.py index 8513f803..c2bfc936 100644 --- a/bindings/python/py_src/safetensors/tensorflow.py +++ b/bindings/python/py_src/safetensors/tensorflow.py @@ -7,7 +7,9 @@ from safetensors import numpy, safe_open -def save(tensors: Dict[str, tf.Tensor], metadata: Optional[Dict[str, str]] = None) -> bytes: +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. diff --git a/bindings/python/py_src/safetensors/torch.py b/bindings/python/py_src/safetensors/torch.py index 4f5c801f..49405bf3 100644 --- a/bindings/python/py_src/safetensors/torch.py +++ b/bindings/python/py_src/safetensors/torch.py @@ -2,6 +2,7 @@ import sys from collections import defaultdict from typing import Any, Dict, List, Optional, Set, Tuple, Union +from packaging.version import Version import torch @@ -418,14 +419,11 @@ def load(data: bytes) -> Dict[str, torch.Tensor]: _SIZE = { torch.int64: 8, - torch.uint64: 8, torch.float32: 4, torch.int32: 4, - torch.uint32: 4, torch.bfloat16: 2, torch.float16: 2, torch.int16: 2, - torch.uint16: 2, torch.uint8: 1, torch.int8: 1, torch.bool: 1, @@ -435,6 +433,14 @@ def load(data: bytes) -> Dict[str, torch.Tensor]: _float8_e8m0: 1, _float4_e2m1_x2: 1, } +if Version(torch.__version__) > Version("2.0.0"): + _SIZE.update( + { + torch.uint64: 8, + torch.uint32: 4, + torch.uint16: 2, + } + ) _TYPES = { "F64": torch.float64, @@ -442,17 +448,22 @@ def load(data: bytes) -> Dict[str, torch.Tensor]: "F16": torch.float16, "BF16": torch.bfloat16, "I64": torch.int64, - "U64": torch.uint64, "I32": torch.int32, - "U32": torch.uint32, "I16": torch.int16, - "U16": torch.uint16, "I8": torch.int8, "U8": torch.uint8, "BOOL": torch.bool, "F8_E4M3": _float8_e4m3fn, "F8_E5M2": _float8_e5m2, } +if Version(torch.__version__) > Version("2.0.0"): + _TYPES.update( + { + "U64": torch.uint64, + "U32": torch.uint32, + "U16": torch.uint16, + } + ) def _getdtype(dtype_str: str) -> torch.dtype: diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 1409126a..47dbb19d 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -59,10 +59,7 @@ paddlepaddle = [ "paddlepaddle>=2.4.1", ] quality = [ - "black==22.3", # after updating to black 2023, also update Python version in pyproject.toml to 3.7 - "click==8.0.4", - "isort>=5.5.4", - "flake8>=3.8.3", + "ruff", # after updating to black 2023, also update Python version in pyproject.toml to 3.7 ] testing = [ "safetensors[numpy]", @@ -74,6 +71,15 @@ testing = [ # "python-afl>=0.7.3", "hypothesis>=6.70.2", ] +testingfree = [ + "safetensors[numpy]", + "huggingface_hub>=0.12.1", + "setuptools_rust>=1.5.2", + "pytest>=7.2.0", + "pytest-benchmark>=4.0.0", + # "python-afl>=0.7.3", + "hypothesis>=6.70.2", +] all = [ "safetensors[torch]", "safetensors[numpy]", diff --git a/bindings/python/stub.py b/bindings/python/stub.py index 71fef2cc..a87f6bca 100644 --- a/bindings/python/stub.py +++ b/bindings/python/stub.py @@ -1,9 +1,8 @@ import argparse import inspect import os - -import black - +import subprocess +import tempfile INDENT = " " * 4 GENERATED_COMMENT = "# Generated content DO NOT EDIT\n" @@ -42,7 +41,10 @@ def fn_predicate(obj): return ( obj.__doc__ and obj.__text_signature__ - and (not obj.__name__.startswith("_") or obj.__name__ in {"__enter__", "__exit__"}) + and ( + not obj.__name__.startswith("_") + or obj.__name__ in {"__enter__", "__exit__"} + ) ) if inspect.isgetsetdescriptor(obj): return obj.__doc__ and not obj.__name__.startswith("_") @@ -78,7 +80,9 @@ def pyi_file(obj, indent=""): body = "" if obj.__doc__: - body += f'{indent}"""\n{indent}{do_indent(obj.__doc__, indent)}\n{indent}"""\n' + body += ( + f'{indent}"""\n{indent}{do_indent(obj.__doc__, indent)}\n{indent}"""\n' + ) fns = inspect.getmembers(obj, fn_predicate) @@ -86,7 +90,7 @@ def pyi_file(obj, indent=""): if obj.__text_signature__: signature = obj.__text_signature__.replace("(", "(self, ") body += f"{indent}def __init__{signature}:\n" - body += f"{indent+INDENT}pass\n" + body += f"{indent + INDENT}pass\n" body += "\n" for name, fn in fns: @@ -126,39 +130,41 @@ def py_file(module, origin): return string -def do_black(content, is_pyi): - mode = black.Mode( - target_versions={black.TargetVersion.PY35}, - line_length=119, - is_pyi=is_pyi, - string_normalization=True, - experimental_string_processing=False, - ) - try: - content = content.replace("$self", "self") - return black.format_file_contents(content, fast=True, mode=mode) - except black.NothingChanged: - return content +def do_black(content): + content = content.replace("$self", "self") + with tempfile.NamedTemporaryFile(mode="w+", suffix=".pyi") as f: + f.write(content) + f.flush() + _ = subprocess.check_output(["ruff", "format", f.name]) + f.seek(0) + new_content = f.read() + return new_content def write(module, directory, origin, check=False): - submodules = [(name, member) for name, member in inspect.getmembers(module) if inspect.ismodule(member)] + submodules = [ + (name, member) + for name, member in inspect.getmembers(module) + if inspect.ismodule(member) + ] filename = os.path.join(directory, "__init__.pyi") pyi_content = pyi_file(module) - pyi_content = do_black(pyi_content, is_pyi=True) + pyi_content = do_black(pyi_content) os.makedirs(directory, exist_ok=True) if check: with open(filename, "r") as f: data = f.read() - assert data == pyi_content, f"The content of {filename} seems outdated, please run `python stub.py`" + assert data == pyi_content, ( + f"The content of {filename} seems outdated, please run `python stub.py`" + ) else: with open(filename, "w") as f: f.write(pyi_content) filename = os.path.join(directory, "__init__.py") py_content = py_file(module, origin) - py_content = do_black(py_content, is_pyi=False) + py_content = do_black(py_content) os.makedirs(directory, exist_ok=True) is_auto = False @@ -174,7 +180,9 @@ def write(module, directory, origin, check=False): if check: with open(filename, "r") as f: data = f.read() - assert data == py_content, f"The content of {filename} seems outdated, please run `python stub.py`" + assert data == py_content, ( + f"The content of {filename} seems outdated, please run `python stub.py`" + ) else: with open(filename, "w") as f: f.write(py_content) diff --git a/bindings/python/tests/test_handle.py b/bindings/python/tests/test_handle.py index aaf0717b..2528ce55 100644 --- a/bindings/python/tests/test_handle.py +++ b/bindings/python/tests/test_handle.py @@ -23,7 +23,7 @@ def test_numpy_example(self): # Now loading loaded = {} with open("./out_np.safetensors", "r") as f: - with safe_open_handle(f, framework="np", device="cpu") as g: + with _safe_open_handle(f, framework="np", device="cpu") as g: for key in g.keys(): loaded[key] = g.get_tensor(key) self.assertTensorEqual(tensors, loaded, np.allclose) @@ -40,7 +40,7 @@ def test_fsspec(self): # Now loading loaded = {} with fs.open("fs.safetensors", "rb") as f: - with safe_open_handle(f, framework="np", device="cpu") as g: + with _safe_open_handle(f, framework="np", device="cpu") as g: for key in g.keys(): loaded[key] = g.get_tensor(key) self.assertTensorEqual(tensors, loaded, np.allclose) @@ -59,7 +59,7 @@ def test_fsspec_s3(self): # Now loading loaded = {} with s3.open("out/fs.safetensors", "rb") as f: - with safe_open_handle(f, framework="np", device="cpu") as g: + with _safe_open_handle(f, framework="np", device="cpu") as g: for key in g.keys(): loaded[key] = g.get_tensor(key) self.assertTensorEqual(tensors, loaded, np.allclose) diff --git a/bindings/python/tests/test_mlx_comparison.py b/bindings/python/tests/test_mlx_comparison.py index 1f21469a..6557cc9a 100644 --- a/bindings/python/tests/test_mlx_comparison.py +++ b/bindings/python/tests/test_mlx_comparison.py @@ -23,13 +23,13 @@ class LoadTestCase(unittest.TestCase): def setUp(self): data = { - "test": mx.randn((1024, 1024), dtype=mx.float32), - "test2": mx.randn((1024, 1024), dtype=mx.float32), - "test3": mx.randn((1024, 1024), dtype=mx.float32), + "test": mx.random.uniform(shape=(1024, 1024), dtype=mx.float32), + "test2": mx.random.uniform(shape=(1024, 1024), dtype=mx.float32), + "test3": mx.random.uniform(shape=(1024, 1024), dtype=mx.float32), # This doesn't work because bfloat16 is not implemented # with similar workarounds as jax/tensorflow. # https://github.com/ml-explore/mlx/issues/1296 - # "test4": mx.randn((1024, 1024), dtype=mx.bfloat16), + # "test4": mx.random.uniform(shape=(1024, 1024), dtype=mx.bfloat16), } self.mlx_filename = "./tests/data/mlx_load.npz" self.sf_filename = "./tests/data/mlx_load.safetensors" diff --git a/bindings/python/tests/test_pt_comparison.py b/bindings/python/tests/test_pt_comparison.py index 441fb351..64782204 100644 --- a/bindings/python/tests/test_pt_comparison.py +++ b/bindings/python/tests/test_pt_comparison.py @@ -95,7 +95,7 @@ def test_odd_dtype_fp8(self): self.assertEqual(reloaded["test2"].item(), -0.5) def test_odd_dtype_fp4(self): - if Version(torch.__version__) <= Version("2.7"): + if Version(torch.__version__) < Version("2.8"): return # torch.float4 requires 2.8 test1 = torch.tensor([0.0], dtype=torch.float8_e8m0fnu) diff --git a/bindings/python/tests/test_pt_model.py b/bindings/python/tests/test_pt_model.py index 7614c33b..4ad82ee7 100644 --- a/bindings/python/tests/test_pt_model.py +++ b/bindings/python/tests/test_pt_model.py @@ -100,7 +100,9 @@ def test_find_shared_non_shared_tensors(self): C = A[2:] D = A[:1] # Shared storage but *do* overlap - self.assertEqual(_find_shared_tensors({"B": B, "C": C, "D": D}), [{"B", "D"}, {"C"}]) + self.assertEqual( + _find_shared_tensors({"B": B, "C": C, "D": D}), [{"B", "D"}, {"C"}] + ) def test_end_ptr(self): A = torch.zeros((4,)) @@ -133,7 +135,9 @@ def test_remove_duplicate_names(self): B = A[:1, :] self.assertEqual(_remove_duplicate_names({"A": A, "B": B}), {"A": ["B"]}) - self.assertEqual(_remove_duplicate_names({"A": A, "B": B, "C": A}), {"A": ["B", "C"]}) + self.assertEqual( + _remove_duplicate_names({"A": A, "B": B, "C": A}), {"A": ["B", "C"]} + ) with self.assertRaises(RuntimeError): self.assertEqual(_remove_duplicate_names({"B": B}), []) @@ -216,7 +220,8 @@ def test_workaround_non_contiguous(self): def test_workaround_copy(self): model = CopyModel() self.assertEqual( - _find_shared_tensors(model.state_dict()), [{"a.weight"}, {"a.bias"}, {"b.weight"}, {"b.bias"}] + _find_shared_tensors(model.state_dict()), + [{"a.weight"}, {"a.bias"}, {"b.weight"}, {"b.bias"}], ) save_model(model, "tmp.safetensors") @@ -237,11 +242,13 @@ def test_difference_with_torch(self): # The model happily loads the tensors, and ends up *not* sharing the tensors by. # doing copies self.assertEqual( - _find_shared_tensors(model2.state_dict()), [{"a.weight"}, {"a.bias"}, {"b.weight"}, {"b.bias"}] + _find_shared_tensors(model2.state_dict()), + [{"a.weight"}, {"a.bias"}, {"b.weight"}, {"b.bias"}], ) model2.load_state_dict(torch.load("tmp2.bin")) self.assertEqual( - _find_shared_tensors(model2.state_dict()), [{"a.weight"}, {"a.bias"}, {"b.weight"}, {"b.bias"}] + _find_shared_tensors(model2.state_dict()), + [{"a.weight"}, {"a.bias"}, {"b.weight"}, {"b.bias"}], ) # However safetensors cannot save those, so we cannot @@ -249,7 +256,9 @@ def test_difference_with_torch(self): save_model(model, "tmp2.safetensors") with self.assertRaises(RuntimeError) as ctx: load_model(model2, "tmp2.safetensors") - self.assertIn("""Missing key(s) in state_dict: "b.bias", "b.weight""", str(ctx.exception)) + self.assertIn( + """Missing key(s) in state_dict: "b.bias", "b.weight""", str(ctx.exception) + ) def test_difference_torch_odd(self): model = NoSharedModel() @@ -259,14 +268,20 @@ def test_difference_torch_odd(self): torch.save(model.state_dict(), "tmp3.bin") model2 = Model() - self.assertEqual(_find_shared_tensors(model2.state_dict()), [{"a.weight", "b.weight"}, {"b.bias", "a.bias"}]) + self.assertEqual( + _find_shared_tensors(model2.state_dict()), + [{"a.weight", "b.weight"}, {"b.bias", "a.bias"}], + ) # Torch will affect either `b` or `a` to the shared tensor in the `model2` model2.load_state_dict(torch.load("tmp3.bin")) # XXX: model2 uses only the B weight not the A weight anymore. self.assertFalse(torch.allclose(model2.a.weight, model.a.weight)) torch.testing.assert_close(model2.a.weight, model.b.weight) - self.assertEqual(_find_shared_tensors(model2.state_dict()), [{"a.weight", "b.weight"}, {"b.bias", "a.bias"}]) + self.assertEqual( + _find_shared_tensors(model2.state_dict()), + [{"a.weight", "b.weight"}, {"b.bias", "a.bias"}], + ) # Everything is saved as-is save_model(model, "tmp3.safetensors") @@ -275,4 +290,7 @@ def test_difference_torch_odd(self): with self.assertRaises(RuntimeError) as ctx: load_model(model2, "tmp3.safetensors") # Safetensors properly warns the user that some ke - self.assertIn("""Unexpected key(s) in state_dict: "b.bias", "b.weight""", str(ctx.exception)) + self.assertIn( + """Unexpected key(s) in state_dict: "b.bias", "b.weight""", + str(ctx.exception), + ) diff --git a/bindings/python/tests/test_simple.py b/bindings/python/tests/test_simple.py index 1db8c711..f3e77d90 100644 --- a/bindings/python/tests/test_simple.py +++ b/bindings/python/tests/test_simple.py @@ -175,7 +175,7 @@ def test_file_not_found(self): with self.assertRaises(FileNotFoundError) as ctx: with safe_open("notafile", framework="pt"): pass - self.assertEqual(str(ctx.exception), 'No such file or directory: notafile') + self.assertEqual(str(ctx.exception), "No such file or directory: notafile") class ReadmeTestCase(unittest.TestCase): @@ -244,7 +244,9 @@ def test_torch_slice(self): save_file_pt(tensors, f"./slice_{ident}.safetensors") # Now loading - with safe_open(f"./slice_{ident}.safetensors", framework="pt", device="cpu") as f: + with safe_open( + f"./slice_{ident}.safetensors", framework="pt", device="cpu" + ) as f: slice_ = f.get_slice("a") tensor = slice_[:] self.assertEqual(list(tensor.shape), [10, 5]) @@ -335,7 +337,9 @@ def test_numpy_slice(self): with self.assertRaises(SafetensorError) as cm: tensor = slice_[2:, -6] - self.assertEqual(str(cm.exception), "Invalid index -6 for dimension 1 of size 5") + self.assertEqual( + str(cm.exception), "Invalid index -6 for dimension 1 of size 5" + ) with self.assertRaises(SafetensorError) as cm: tensor = slice_[[0, 1]] diff --git a/bindings/python/uv.lock b/bindings/python/uv.lock index ed80f7ea..7e375fcd 100644 --- a/bindings/python/uv.lock +++ b/bindings/python/uv.lock @@ -63,33 +63,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, ] -[[package]] -name = "black" -version = "22.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ee/1f/b29c7371958ab41a800f8718f5d285bf4333b8d0b5a5a8650234463ee644/black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79", size = 554277 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/1b/3ba8128f0b6e86d87343a1275e17baeeeee1a89e02d2a0d275f472be3310/black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09", size = 2392131 }, - { url = "https://files.pythonhosted.org/packages/31/1a/0233cdbfbcfbc0864d815cf28ca40cdb65acf3601f3bf943d6d04e867858/black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb", size = 1339315 }, - { url = "https://files.pythonhosted.org/packages/4f/98/8f775455f99a8e4b16d8d26efdc8292f99b1c0ebfe04357d800ff379c0ae/black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a", size = 1212888 }, - { url = "https://files.pythonhosted.org/packages/93/98/6f7c2f7f81d87b5771febcee933ba58640fca29a767262763bc353074f63/black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968", size = 1474258 }, - { url = "https://files.pythonhosted.org/packages/5f/10/613ddfc646a1f51f24ad9173e4969025210fe9034a69718f08297ecb9b76/black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d", size = 1137867 }, - { url = "https://files.pythonhosted.org/packages/a4/43/940f848d7d1ecf0be18453a293e5736e9ce60fd6197386a791bcc491f232/black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20", size = 2390442 }, - { url = "https://files.pythonhosted.org/packages/e5/b7/e4e8907dffdac70f018ddb89681dbc77cbc7ac78d1f8a39259110a7e7943/black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a", size = 1338106 }, - { url = "https://files.pythonhosted.org/packages/56/74/c27c496223168af625d6bba8a14bc581e7742bf7248504a4b5743c374767/black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad", size = 1212264 }, - { url = "https://files.pythonhosted.org/packages/51/ec/c87695b087b7525fd9c7732c630455f231d3df9a300b730bd0e04ea00f84/black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21", size = 1473733 }, - { url = "https://files.pythonhosted.org/packages/5b/f9/e7aca76001a702dc258fb0443ca2553d5db13a3515c09062fbf344184363/black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265", size = 1137278 }, - { url = "https://files.pythonhosted.org/packages/2e/ef/a38a2189959246543e60859fb65bd3143129f6d18dfc7bcdd79217f81ca2/black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72", size = 153859 }, -] - [[package]] name = "certifi" version = "2025.4.26" @@ -193,18 +166,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/6c/309972937d931069816dc8b28193a650485bc35cca92c04c8c15c4bd181e/chex-0.1.89-py3-none-any.whl", hash = "sha256:145241c27d8944adb634fb7d472a460e1c1b643f561507d4031ad5156ef82dfa", size = 99908 }, ] -[[package]] -name = "click" -version = "8.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dd/cf/706c1ad49ab26abed0b77a2f867984c1341ed7387b8030a6aa914e2942a0/click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb", size = 329520 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/a8/0b2ced25639fb20cc1c9784de90a8c25f9504a7f18cd8b5397bd61696d7d/click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1", size = 97486 }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -289,20 +250,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, ] -[[package]] -name = "flake8" -version = "7.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786 }, -] - [[package]] name = "flatbuffers" version = "25.2.10" @@ -605,15 +552,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, ] -[[package]] -name = "isort" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186 }, -] - [[package]] name = "jax" version = "0.4.30" @@ -864,15 +802,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, -] - [[package]] name = "mdurl" version = "0.1.2" @@ -911,24 +840,42 @@ wheels = [ [[package]] name = "mlx" -version = "0.25.1" +version = "0.27.1" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mlx-metal", marker = "sys_platform == 'darwin'" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/42/6ce1ce83a8d7b600a69bde314b4c1b3e6aaeb6acd83d762124162dd8a751/mlx-0.25.1-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:d13aa885d38a04d32f9818fffbceaa17555eb67fea02d16e53d2217ab4d8eb5d", size = 30723244 }, - { url = "https://files.pythonhosted.org/packages/fc/cd/bdbc33a51fa5ada9d593e8c4c491331ce57e5ab49e0d107f214e340c4417/mlx-0.25.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:d07082c35e4196fdc17813f344a529dbc6c7210edb35aaa5833cf3509fecf183", size = 30134290 }, - { url = "https://files.pythonhosted.org/packages/df/88/60f88cf4717ee497f0ba1f3ae77ace23cef486eaed1c85bbc65d81e2b687/mlx-0.25.1-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:1fdafc0b247e22d324699245e2a339225265d3b6d12e669fe02402eb73f46216", size = 30134777 }, - { url = "https://files.pythonhosted.org/packages/0d/99/24b7c3622aab9eef8875fd96ae9183923ef8da00ecfe0997c9979c9a4f84/mlx-0.25.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:c647a242a7880ad89215a118df2f7d97b554fa54e55cc1b90ef7506e593a83eb", size = 30723278 }, - { url = "https://files.pythonhosted.org/packages/33/a0/9694271393ec8f135f4a3b1f16a9b475f3ea42e8fe4a18397b5680e6f162/mlx-0.25.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:d6da7ab9e51fd780083a3003c96cdd538b465d2b1a16e2fd77217348c70dfb44", size = 30134574 }, - { url = "https://files.pythonhosted.org/packages/60/7e/16c84be009c0c37862aa328915c2d188abd05756819668e87d138dabe353/mlx-0.25.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:db0caac2c9dd6fe0ae15448b4dcb40cd3bd28d6ad9f935e2c383eb1844af77a2", size = 30135217 }, - { url = "https://files.pythonhosted.org/packages/8c/44/868009d072fca94fabf1cdb55a6c61d49ce72bc829a560d05ec94714cd0c/mlx-0.25.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:8f6c0754570a94be4a1b25ce10986665ed0b57ee8ad8f50bc713b4801264eb98", size = 30717669 }, - { url = "https://files.pythonhosted.org/packages/64/8e/f84e1fada4f64d9a88974e3b208eedd5dfa94f06d542c86ff1a7a9d1197c/mlx-0.25.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bc9a387819fc214dc915b2fff9164346bf9c3aa90aabcc38dbf1c232329861f7", size = 30135893 }, - { url = "https://files.pythonhosted.org/packages/b1/19/7174aeea051995d10d0b09383c6b2b88a1222e17d7a41151becf87dc3c46/mlx-0.25.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cb6e5fb85535c3438bcb16e616e3aa2ec47844fcadf67d85ee862b256369f185", size = 30136426 }, - { url = "https://files.pythonhosted.org/packages/02/1b/7da8f1d224a4287cdd5eda77d878a73ff13c22e2c89097bc6effcc5c318a/mlx-0.25.1-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:f2ca5c2f60804bbb3968ee3e087ce4cf5789065f4c927f76b025b3f5f122a63a", size = 30717676 }, - { url = "https://files.pythonhosted.org/packages/50/0f/1b4752c9325716ae7d1b8050442fe28372390cf2a4d993456535c4f50e16/mlx-0.25.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:59d233b12dc0c003d63883c73abbf40e29fe0a3176dd9d2095399c5f3688eb58", size = 30135902 }, - { url = "https://files.pythonhosted.org/packages/53/06/10f2465ea3b1fa2d7ca31e79b19869f8df0de4b8b57a932eab607aa36e3f/mlx-0.25.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:67d192dbd0677ea365f3305592ddb449488f3486f4791a7ac2e76494bea668a5", size = 30136384 }, - { url = "https://files.pythonhosted.org/packages/41/c8/3b872d4a32042973d1d24900215e1b299a8e648fd95de8b1c0144e423819/mlx-0.25.1-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:324de0673922309fe0ab3a230fa66a7d0ce4f4b9b2e9d273f2816adddc99e285", size = 30723570 }, - { url = "https://files.pythonhosted.org/packages/b0/4e/da5e5cd2afe1e429c2893c7ed9bfb7ea9181f17e711b5515bca3e28a691b/mlx-0.25.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:b7bb2e7e996c27a5e6ad19bbfc336bde71e0d4df23b5673a35087b321dd05b3b", size = 30134765 }, - { url = "https://files.pythonhosted.org/packages/b1/71/fc94cbe575b52ff1f0f95074b970271e93ff9a5d9e42864ea8f664c93e77/mlx-0.25.1-cp39-cp39-macosx_15_0_arm64.whl", hash = "sha256:138b0e7daf1f4014a184d8fc05df1f0a92a50fd99c9aeac8fef0b9bc31358cc1", size = 30135576 }, + { url = "https://files.pythonhosted.org/packages/c9/ed/07a7145c500914ed8ab704a7a3828a5134e85f8b4458e1fee887306867d6/mlx-0.27.1-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:a033b65fe46425ad5032867d5a71a556a5168108d89aa7092b457556c70d84fc", size = 557679 }, + { url = "https://files.pythonhosted.org/packages/aa/ae/b3e46904ea04b5badb77195169880733f09976a3dfc81c954b99f9adbbc4/mlx-0.27.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:2836c6fd9803dc0c6cd06f204e31b3e0191e6c5b6bc8570b28661d926908cba3", size = 530556 }, + { url = "https://files.pythonhosted.org/packages/6e/24/85f5a7dfcf0b4974aa43a2f6dc0d59dfc04e4a1051aacebd7e7810b823ff/mlx-0.27.1-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:8b0566054c46d84c470cc99cda2afc3914ad6c7808fbb724dc1ec235e5b2a98c", size = 530559 }, + { url = "https://files.pythonhosted.org/packages/14/bd/91ad900837a3760ba056863c1350f0deabc3e462c3999b4d5ea875368177/mlx-0.27.1-cp310-cp310-manylinux_2_35_x86_64.whl", hash = "sha256:91ef93ce09900c9a8ca662cf34e3c39ab5af2762822ecd6b12fecae518be167f", size = 628183 }, + { url = "https://files.pythonhosted.org/packages/ac/63/88cb9a5019bea21244385eacc4c64deb4d074a8bc3b4b6d2d97cdacf97a2/mlx-0.27.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:d2e5dedbfbcbe558e51a5c476ca6a18e307676f9e49854eb27e53778bc474699", size = 557582 }, + { url = "https://files.pythonhosted.org/packages/fa/87/c2d7343e7b054481c52e23a190911c40aed4f8c77a8dfeda1f48d5cb520a/mlx-0.27.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:9f04b9778897a879c9ca22e5413dfa1efc192d86d7211b184e079efec49dfb8b", size = 530820 }, + { url = "https://files.pythonhosted.org/packages/06/bf/20497ca7411028baa56372c20e94a3eaddac973155b415f08fc12592c2cf/mlx-0.27.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:01d794f9e390438ab4f942a18d9a8ca65bef10c2c2007ef38ca988d039d6d9d3", size = 530821 }, + { url = "https://files.pythonhosted.org/packages/48/68/234a0d846576502ea1bf8ea478c764d87eec9042349e298d2aea58c4e8e9/mlx-0.27.1-cp311-cp311-manylinux_2_35_x86_64.whl", hash = "sha256:fae11432d0639789f1e172b19b35ac8987c8ab9716e55a23fc7a170d6545fc33", size = 628500 }, + { url = "https://files.pythonhosted.org/packages/65/43/125102bbb2be6825880ae2dc8d8702f99cfa7753407f574457b36e422218/mlx-0.27.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:0c570c9afb57c697bd864504115be8a7c4de97f0b80557a597d496ee426a6812", size = 549869 }, + { url = "https://files.pythonhosted.org/packages/f3/79/0bf681700fc8b165517e907f9ec777b5a5d628004a65a777148f68c6baa0/mlx-0.27.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ccff7bbbd9df302b26e79013ef6d0c3531c9ba5963ead521e2d85856811b86a0", size = 531671 }, + { url = "https://files.pythonhosted.org/packages/ec/97/f1367b4892bef7f78e38737d3a28094e93124f11684a28a9e92ed5a13b2b/mlx-0.27.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9ccadaed449c07dfeae484620992b904c17dfea7564f8df63095c60eed3af02b", size = 531672 }, + { url = "https://files.pythonhosted.org/packages/9c/0f/3ebec0806ed2c5ba8ba151394cd62a46b4d83ea347da33af91b86d8969d4/mlx-0.27.1-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:742c413e75605b71db69379a176da63e32ba19b9e9ad03b8763adbd1fcfcd394", size = 621661 }, + { url = "https://files.pythonhosted.org/packages/86/f6/4324386b0764deb692e14a97282a348a9a938aa8b441bf8b6c7599f418d4/mlx-0.27.1-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:803669a28031766c2b0fe94c0a3bfd030184e706092f0a831b33620c1e2ef865", size = 549847 }, + { url = "https://files.pythonhosted.org/packages/cf/4b/3194ccb03527a050c04d837d731a11599f8620e6ce16d3971798caae1d44/mlx-0.27.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e9649b3d86ce564000797384510c9d07af38a9ce2a07df8e2f7c6a3e0f0f059e", size = 531664 }, + { url = "https://files.pythonhosted.org/packages/cc/57/a6e0d8dc6e7ba08a64d71fb89d743e77446040113ea1dbb7950be8f60f39/mlx-0.27.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:5c501ceec7c6aa2ea1c850dd4e14e679f5416a142264b9d5d405a4e0aeb991b2", size = 531663 }, + { url = "https://files.pythonhosted.org/packages/48/96/6932e0fa866d08cdf77a3fc37cf624e4cac8960d2e297c3fd80afb49417d/mlx-0.27.1-cp313-cp313-manylinux_2_35_x86_64.whl", hash = "sha256:42bfbabdcd0b7aa1c94748c8963e66680a3cf3f599b01036db14e496bf276eb8", size = 621609 }, + { url = "https://files.pythonhosted.org/packages/b0/34/bb2d1a7bdd27384f282d97e5add37a1f7d22c8e355b4011682d22f5ecdc5/mlx-0.27.1-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:18e9a12ce91c034d11a348f479e9cf6d13b7af64541fdcc4ef32a67903b19d3c", size = 557489 }, + { url = "https://files.pythonhosted.org/packages/3c/da/944d821aae2e5aa5440be824ab110db39b290c164bbbca2c8148f350b106/mlx-0.27.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:fe94c860df8fb803e6330b46901c94cbe0f63b7e9e9ed94ca8920925ac671177", size = 530801 }, + { url = "https://files.pythonhosted.org/packages/6e/26/0e2e62c9798eb47fa981af469813c06259c73696813220b417b4039fbe95/mlx-0.27.1-cp39-cp39-macosx_15_0_arm64.whl", hash = "sha256:4384c06ce8d8eed23018e99331a612ff9837c7ae1ca953aef9dee056174465db", size = 530798 }, + { url = "https://files.pythonhosted.org/packages/86/13/a4864945059b8277f370661d2bab86146fc7e97ee29f0f17a1fdf9f5d239/mlx-0.27.1-cp39-cp39-manylinux_2_35_x86_64.whl", hash = "sha256:3ab919caa2b1f605d4dcea543dcbe7be43f3a4a5102232959d5659c9108b6fb4", size = 628882 }, +] + +[[package]] +name = "mlx-metal" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/77/89fa3327011f018638c9e943e1edc081ce9ca0ed296fe64d6daf93c6ff51/mlx_metal-0.27.1-py3-none-macosx_13_0_arm64.whl", hash = "sha256:c66d9b1adb3c0ea19492fba6493f672bc7542e65dd65f7e2995918815fbeb907", size = 33523035 }, + { url = "https://files.pythonhosted.org/packages/d7/a8/ac706ad6ce834834762d5146d791f77710efc896c13ef47fd7d672099056/mlx_metal-0.27.1-py3-none-macosx_14_0_arm64.whl", hash = "sha256:fe4415ddd242974d91c7ca0699cd01507d17da8a5ba304122ef137cdb5e7fff4", size = 32926383 }, + { url = "https://files.pythonhosted.org/packages/78/77/6963681fb54ecaa0ae5de4209c15504a803a0edd1a33fd074e6c558fd5e0/mlx_metal-0.27.1-py3-none-macosx_15_0_arm64.whl", hash = "sha256:d025dea30bda8baa32c928cfa333eac64a5adc8d07656f8fc55072d99403ebc9", size = 32897065 }, ] [[package]] @@ -1003,15 +950,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/e8/2162621e18dbc36e2bc8492fd0e97b3975f5d89fe0472ae6d5f7fbdd8cf7/msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325", size = 74787 }, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, -] - [[package]] name = "namex" version = "0.0.9" @@ -1463,15 +1401,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/ee/eef6cfb98a44e11891bda8e898a9620df1303664f45e64285edc6ac93536/paddlepaddle-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:4b078cee4f6da995e4d2b1c46d1d94545f31a487b02ec1c61cfbb9e7d6d082f2", size = 96937322 }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, -] - [[package]] name = "pillow" version = "11.2.1" @@ -1560,15 +1489,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751 }, ] -[[package]] -name = "platformdirs" -version = "4.3.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, -] - [[package]] name = "pluggy" version = "1.5.0" @@ -1603,24 +1523,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335 }, ] -[[package]] -name = "pycodestyle" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424 }, -] - -[[package]] -name = "pyflakes" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164 }, -] - [[package]] name = "pygments" version = "2.19.1" @@ -1742,21 +1644,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, ] +[[package]] +name = "ruff" +version = "0.12.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189 }, + { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389 }, + { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384 }, + { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759 }, + { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028 }, + { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209 }, + { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353 }, + { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555 }, + { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556 }, + { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784 }, + { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356 }, + { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124 }, + { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945 }, + { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677 }, + { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687 }, + { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365 }, + { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083 }, +] + [[package]] name = "safetensors" source = { editable = "." } [package.optional-dependencies] all = [ - { name = "black" }, - { name = "click" }, - { name = "flake8" }, { name = "flax", version = "0.8.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "flax", version = "0.10.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "h5py" }, { name = "huggingface-hub" }, { name = "hypothesis" }, - { name = "isort" }, { name = "jax", version = "0.4.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "jax", version = "0.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "jaxlib", version = "0.4.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -1765,20 +1688,17 @@ all = [ { name = "paddlepaddle" }, { name = "pytest" }, { name = "pytest-benchmark" }, + { name = "ruff" }, { name = "setuptools-rust" }, { name = "tensorflow" }, { name = "torch" }, ] dev = [ - { name = "black" }, - { name = "click" }, - { name = "flake8" }, { name = "flax", version = "0.8.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "flax", version = "0.10.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "h5py" }, { name = "huggingface-hub" }, { name = "hypothesis" }, - { name = "isort" }, { name = "jax", version = "0.4.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "jax", version = "0.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "jaxlib", version = "0.4.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -1787,6 +1707,7 @@ dev = [ { name = "paddlepaddle" }, { name = "pytest" }, { name = "pytest-benchmark" }, + { name = "ruff" }, { name = "setuptools-rust" }, { name = "tensorflow" }, { name = "torch" }, @@ -1815,10 +1736,7 @@ pinned-tf = [ { name = "tensorflow" }, ] quality = [ - { name = "black" }, - { name = "click" }, - { name = "flake8" }, - { name = "isort" }, + { name = "ruff" }, ] tensorflow = [ { name = "numpy" }, @@ -1833,6 +1751,14 @@ testing = [ { name = "pytest-benchmark" }, { name = "setuptools-rust" }, ] +testingfree = [ + { name = "huggingface-hub" }, + { name = "hypothesis" }, + { name = "numpy" }, + { name = "pytest" }, + { name = "pytest-benchmark" }, + { name = "setuptools-rust" }, +] torch = [ { name = "numpy" }, { name = "torch" }, @@ -1840,21 +1766,22 @@ torch = [ [package.metadata] requires-dist = [ - { name = "black", marker = "extra == 'quality'", specifier = "==22.3" }, - { name = "click", marker = "extra == 'quality'", specifier = "==8.0.4" }, - { name = "flake8", marker = "extra == 'quality'", specifier = ">=3.8.3" }, { name = "flax", marker = "extra == 'jax'", specifier = ">=0.6.3" }, { name = "h5py", marker = "extra == 'testing'", specifier = ">=3.7.0" }, { name = "huggingface-hub", marker = "extra == 'testing'", specifier = ">=0.12.1" }, + { name = "huggingface-hub", marker = "extra == 'testingfree'", specifier = ">=0.12.1" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = ">=6.70.2" }, - { name = "isort", marker = "extra == 'quality'", specifier = ">=5.5.4" }, + { name = "hypothesis", marker = "extra == 'testingfree'", specifier = ">=6.70.2" }, { name = "jax", marker = "extra == 'jax'", specifier = ">=0.3.25" }, { name = "jaxlib", marker = "extra == 'jax'", specifier = ">=0.3.25" }, { name = "mlx", marker = "extra == 'mlx'", specifier = ">=0.0.9" }, { name = "numpy", marker = "extra == 'numpy'", specifier = ">=1.21.6" }, { name = "paddlepaddle", marker = "extra == 'paddlepaddle'", specifier = ">=2.4.1" }, { name = "pytest", marker = "extra == 'testing'", specifier = ">=7.2.0" }, + { name = "pytest", marker = "extra == 'testingfree'", specifier = ">=7.2.0" }, { name = "pytest-benchmark", marker = "extra == 'testing'", specifier = ">=4.0.0" }, + { name = "pytest-benchmark", marker = "extra == 'testingfree'", specifier = ">=4.0.0" }, + { name = "ruff", marker = "extra == 'quality'" }, { name = "safetensors", extras = ["all"], marker = "extra == 'dev'" }, { name = "safetensors", extras = ["jax"], marker = "extra == 'all'" }, { name = "safetensors", extras = ["numpy"], marker = "extra == 'all'" }, @@ -1863,6 +1790,7 @@ requires-dist = [ { name = "safetensors", extras = ["numpy"], marker = "extra == 'pinned-tf'" }, { name = "safetensors", extras = ["numpy"], marker = "extra == 'tensorflow'" }, { name = "safetensors", extras = ["numpy"], marker = "extra == 'testing'" }, + { name = "safetensors", extras = ["numpy"], marker = "extra == 'testingfree'" }, { name = "safetensors", extras = ["numpy"], marker = "extra == 'torch'" }, { name = "safetensors", extras = ["paddlepaddle"], marker = "extra == 'all'" }, { name = "safetensors", extras = ["pinned-tf"], marker = "extra == 'all'" }, @@ -1870,11 +1798,12 @@ requires-dist = [ { name = "safetensors", extras = ["testing"], marker = "extra == 'all'" }, { name = "safetensors", extras = ["torch"], marker = "extra == 'all'" }, { name = "setuptools-rust", marker = "extra == 'testing'", specifier = ">=1.5.2" }, + { name = "setuptools-rust", marker = "extra == 'testingfree'", specifier = ">=1.5.2" }, { name = "tensorflow", marker = "extra == 'pinned-tf'", specifier = "==2.18.0" }, { name = "tensorflow", marker = "extra == 'tensorflow'", specifier = ">=2.11.0" }, { name = "torch", marker = "extra == 'torch'", specifier = ">=1.10" }, ] -provides-extras = ["numpy", "torch", "tensorflow", "pinned-tf", "jax", "mlx", "paddlepaddle", "quality", "testing", "all", "dev"] +provides-extras = ["numpy", "torch", "tensorflow", "pinned-tf", "jax", "mlx", "paddlepaddle", "quality", "testing", "testingfree", "all", "dev"] [[package]] name = "scipy" From 94f5d38794fdedde2cf0e756914b8250533a6c1a Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Wed, 6 Aug 2025 11:11:45 +0200 Subject: [PATCH 6/7] Preparing for patch 6.0.1. (#638) * Preparing for patch 6.0.1. * Tune down false positives. --- .github/workflows/trufflehog.yml | 4 ++++ bindings/python/Cargo.toml | 2 +- safetensors/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/trufflehog.yml b/.github/workflows/trufflehog.yml index 9cbbf680..8a15d8c9 100644 --- a/.github/workflows/trufflehog.yml +++ b/.github/workflows/trufflehog.yml @@ -13,3 +13,7 @@ jobs: fetch-depth: 0 - name: Secret Scanning uses: trufflesecurity/trufflehog@main + with: + extra_args: --results=verified,unknown + + diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index 646312bf..1eb786b3 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "safetensors-python" -version = "0.6.0-dev.0" +version = "0.6.2-dev.0" edition = "2021" rust-version = "1.74" diff --git a/safetensors/Cargo.toml b/safetensors/Cargo.toml index 68ebac64..839d06e2 100644 --- a/safetensors/Cargo.toml +++ b/safetensors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "safetensors" -version = "0.6.0-dev.0" +version = "0.6.2-dev.0" edition = "2021" rust-version = "1.80" homepage = "https://github.com/huggingface/safetensors" From e2b2ea62523d9ed4b940acae069aa7221fb50264 Mon Sep 17 00:00:00 2001 From: Nicolas Patry Date: Wed, 6 Aug 2025 11:33:40 +0200 Subject: [PATCH 7/7] Releasing 0.6.1 --- bindings/python/Cargo.lock | 234 +++++++++++++++++++++++++++++++++++++ bindings/python/Cargo.toml | 2 +- safetensors/Cargo.toml | 2 +- 3 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 bindings/python/Cargo.lock diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock new file mode 100644 index 00000000..99ef26e9 --- /dev/null +++ b/bindings/python/Cargo.lock @@ -0,0 +1,234 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +dependencies = [ + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "safetensors" +version = "0.6.1" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "safetensors-python" +version = "0.6.1" +dependencies = [ + "memmap2", + "pyo3", + "safetensors", + "serde_json", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index 1eb786b3..c060031f 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "safetensors-python" -version = "0.6.2-dev.0" +version = "0.6.1" edition = "2021" rust-version = "1.74" diff --git a/safetensors/Cargo.toml b/safetensors/Cargo.toml index 839d06e2..7db11124 100644 --- a/safetensors/Cargo.toml +++ b/safetensors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "safetensors" -version = "0.6.2-dev.0" +version = "0.6.1" edition = "2021" rust-version = "1.80" homepage = "https://github.com/huggingface/safetensors"