Skip to content

recipe: safetensors 0.8.0#108

Merged
ndonkoHenri merged 7 commits into
mainfrom
machine/safetensors
Jul 17, 2026
Merged

recipe: safetensors 0.8.0#108
ndonkoHenri merged 7 commits into
mainfrom
machine/safetensors

Conversation

@ndonkoHenri

Copy link
Copy Markdown

Adds a safetensors recipe — safe, fast tensor serialization on both platforms.

Closes flet-dev/flet#6688.

Shape

safetensors is a Rust/PyO3 (maturin) package with a published sdist, so this is a plain minimal-Rust recipe: zero patches, no source override, no excluded_arches — it builds for every Android ABI (incl. armeabi-v7a) and all three iOS slices. The only meta beyond name/version is the standard _PYTHON_SYSCONFIGDATA_NAME cross-compile env var.

Testing

tests/test_safetensors.py runs three on-device checks: a bytes-level serialize/deserialize round-trip through the Rust core (TensorSpec, no framework dep), a serialize_file round-trip, and the numpy + safe_open(framework="numpy") round-trip that downstream consumers (model2vec, ONNX inference) actually use. numpy is an upstream extra (safetensors[numpy]), not a Requires-Dist, so it's declared as a test-only dep via meta.yaml test.requires — installed for the on-device run without becoming a wheel dependency.

Consumer notes (usage & recommendations)

safetensors loads and saves model weights/embeddings as a safe, zero-copy file format — none of pickle's arbitrary-code-execution risk, and fast mmap reads that pull one tensor at a time.

Install

safetensors itself has no required dependencies, but the usable mobile path needs numpy, so add both:

dependencies = [
    "flet",
    "safetensors",
    "numpy",
]

Example

Save a couple of tensors, reload them via safe_open, and show the result on screen. Note the file goes into Flet's storage: the app bundle is read-only on device, so write to FLET_APP_STORAGE_TEMP/_DATA, not the working directory.

import os
import flet as ft
import numpy as np
from safetensors.numpy import save_file
from safetensors import safe_open


def main(page: ft.Page):
    path = os.path.join(os.environ.get("FLET_APP_STORAGE_TEMP", "."), "w.safetensors")
    save_file(
        {
            "emb": np.arange(6, dtype=np.float32).reshape(2, 3),
            "bias": np.ones(3, dtype=np.float32),
        },
        path,
    )

    rows = []
    with safe_open(path, framework="numpy") as f:  # mmap; one tensor at a time
        for k in f.keys():
            t = f.get_tensor(k)
            rows.append(ft.Text(f"{k}: shape {list(t.shape)}, sum {t.sum():.1f}"))

    page.add(*rows)


ft.run(main)

On device this shows emb: shape [2, 3], sum 15.0 / bias: shape [3], sum 3.0.

Recommendations

  • On mobile the usable framework backend is numpytorch / tensorflow / paddle aren't available as mobile wheels. Loading with framework="pt" fails at runtime because torch isn't installed; convert weights to the numpy view (or ONNX) ahead of time.
  • Prefer safe_open + get_tensor over load_file for large files — it mmaps and reads tensors on demand instead of pulling everything into RAM at once.
  • .safetensors is the weights/embeddings format for the on-device ML stack — pair it with onnxruntime + numpy for inference, or model2vec for static embeddings.

Rust/PyO3 (maturin) recipe, tokenizers archetype minus the C++: the 0.8.0
sdist vendors the Rust core (safetensors/) next to the pyo3 bindings
(bindings/python/), builds abi3-py310, and needs zero patches. Pure-Rust
dep tree (memmap2, serde_json) - no libc++_shared host dep; the metal.rs
Apple-silicon path is cfg-gated to macOS and compiles out on iOS/Android.
Full 6-slice matrix green; android .so links only libpython/libdl/libc,
16KB-aligned. Unlocks model2vec (numpy-only static embeddings).

Tests cover the 0.8.0 TensorSpec bytes API (constructor takes
python-style dtype names, headers report format codes), the mmap
safe_open path, and the numpy round-trip downstream consumers use.
The tester app installs flet + pytest + the recipe's Requires-Dist only,
so a zero-runtime-dep recipe (safetensors) can't exercise integration
paths on-device — its numpy test could only ever skip. stage_recipe.sh
now expands an optional recipes/<name>/tests/requirements.txt (one
PEP 508 spec per line, comments/blanks skipped) into the generated
pyproject via a __TEST_DEPS__ token line in the template.

Template expansion is a bash line loop with printf rather than sed:
specs legitimately contain double quotes in markers, which have no safe
portable sed-RHS escaping. Deps are emitted as TOML literal strings, so
single quotes inside a spec are rejected with an error. Token matches
are exact-line, so comments mentioning a token pass through verbatim.
Empty-array expansion is guarded for macOS bash 3.2 under set -u.
# Conflicts:
#	tests/recipe-tester/README.md
#	tests/recipe-tester/pyproject.toml.tpl
#	tests/recipe-tester/stage_recipe.sh
@ndonkoHenri
ndonkoHenri merged commit f25cabd into main Jul 17, 2026
15 of 16 checks passed
@ndonkoHenri
ndonkoHenri deleted the machine/safetensors branch July 17, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant