ptwm 1.0.0__cp313-cp313-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ptwm/__init__.py +52 -0
- ptwm/_config.py +162 -0
- ptwm/_core.cpython-313-darwin.so +0 -0
- ptwm/_exceptions.py +26 -0
- ptwm/_loader.py +48 -0
- ptwm/_rust/__init__.py +313 -0
- ptwm/_rust/_errors.py +38 -0
- ptwm/_rust/ext.py +19 -0
- ptwm/_rust/host.py +7 -0
- ptwm/_rust/policy.py +7 -0
- ptwm/_rust/trust.py +37 -0
- ptwm/ablation/__init__.py +34 -0
- ptwm/ablation/_attribute.py +192 -0
- ptwm/ablation/_bench.py +171 -0
- ptwm/ablation/_compare.py +55 -0
- ptwm/ablation/_replay.py +85 -0
- ptwm/ablation/_resolve.py +71 -0
- ptwm/classify/__init__.py +24 -0
- ptwm/classify/_audit.py +143 -0
- ptwm/classify/_chain.py +32 -0
- ptwm/classify/_flags.py +50 -0
- ptwm/classify/_heuristic.py +63 -0
- ptwm/classify/_hf_quant.py +91 -0
- ptwm/classify/_protocol.py +24 -0
- ptwm/classify/_ptwm_config.py +53 -0
- ptwm/classify/_role.py +26 -0
- ptwm/cli/README.md +81 -0
- ptwm/cli/__init__.py +2 -0
- ptwm/cli/bench.py +156 -0
- ptwm/cli/chains.py +550 -0
- ptwm/cli/compress.py +1185 -0
- ptwm/cli/decompress.py +466 -0
- ptwm/cli/ext.py +298 -0
- ptwm/cli/main.py +39 -0
- ptwm/cli/policy.py +56 -0
- ptwm/cli/trust.py +188 -0
- ptwm/cli/utils.py +64 -0
- ptwm/codecs/__init__.py +205 -0
- ptwm/core/__init__.py +9 -0
- ptwm/core/_compressor.py +202 -0
- ptwm/core/_decompressor.py +158 -0
- ptwm/delta/__init__.py +88 -0
- ptwm/entropy/__init__.py +98 -0
- ptwm/ext_tooling/__init__.py +0 -0
- ptwm/ext_tooling/_templates/assemblyscript/manifest.toml +14 -0
- ptwm/ext_tooling/_templates/assemblyscript/package.json +11 -0
- ptwm/ext_tooling/_templates/assemblyscript/src/index.ts +28 -0
- ptwm/ext_tooling/_templates/c/Makefile +14 -0
- ptwm/ext_tooling/_templates/c/manifest.toml +14 -0
- ptwm/ext_tooling/_templates/c/src/lib.c +37 -0
- ptwm/ext_tooling/_templates/rust/Cargo.toml +11 -0
- ptwm/ext_tooling/_templates/rust/manifest.toml +14 -0
- ptwm/ext_tooling/_templates/rust/src/lib.rs +44 -0
- ptwm/ext_tooling/_templates/zig/build.zig +19 -0
- ptwm/ext_tooling/_templates/zig/manifest.toml +14 -0
- ptwm/ext_tooling/_templates/zig/src/main.zig +30 -0
- ptwm/ext_tooling/build.py +145 -0
- ptwm/ext_tooling/init.py +58 -0
- ptwm/ext_tooling/pack.py +57 -0
- ptwm/ext_tooling/sign.py +118 -0
- ptwm/integrations/__init__.py +14 -0
- ptwm/integrations/_compress_safetensors.py +401 -0
- ptwm/integrations/_hf.py +302 -0
- ptwm/integrations/_safetensors.py +163 -0
- ptwm/preprocessing/__init__.py +73 -0
- ptwm/preprocessing/_cache.py +188 -0
- ptwm/preprocessing/_chains.py +1077 -0
- ptwm/preprocessing/_explorer.py +410 -0
- ptwm/preprocessing/_plane.py +26 -0
- ptwm/random_access/__init__.py +132 -0
- ptwm/sharding/__init__.py +15 -0
- ptwm/sharding/_index.py +45 -0
- ptwm/sharding/_reader.py +64 -0
- ptwm/sharding/_sharder.py +42 -0
- ptwm/sharding/_writer.py +100 -0
- ptwm/stores/__init__.py +100 -0
- ptwm/stores/_common.py +204 -0
- ptwm/stores/_lmdb.py +415 -0
- ptwm/stores/_webdataset.py +330 -0
- ptwm/utils/__init__.py +34 -0
- ptwm/utils/_decode.py +93 -0
- ptwm/utils/_patch.py +37 -0
- ptwm/utils/_safetensors.py +53 -0
- ptwm/utils/_torch.py +240 -0
- ptwm-1.0.0.dist-info/METADATA +461 -0
- ptwm-1.0.0.dist-info/RECORD +90 -0
- ptwm-1.0.0.dist-info/WHEEL +4 -0
- ptwm-1.0.0.dist-info/entry_points.txt +2 -0
- ptwm-1.0.0.dist-info/licenses/LICENSE +21 -0
- ptwm-1.0.0.dist-info/sboms/ptwm-py.cyclonedx.json +13061 -0
ptwm/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""PTWM — lossless compression for PyTorch model weights.
|
|
2
|
+
|
|
3
|
+
A typed preprocessing graph (bit reorder, byte/nibble split) feeds two
|
|
4
|
+
peer entropy coders — Huffman and rANS — alongside a Zstd plane codec
|
|
5
|
+
and a dedicated microscaling-format codec. The on-disk format is a
|
|
6
|
+
multi-tensor ``.ptwm`` container with per-tensor random-access lookup.
|
|
7
|
+
|
|
8
|
+
Public entry points: :class:`Compressor`, :class:`Decompressor`,
|
|
9
|
+
:class:`CompressionConfig`, :class:`DecompressionConfig`,
|
|
10
|
+
:class:`Method`, :class:`Format`. Third-party integrations
|
|
11
|
+
(HuggingFace `transformers`, `safetensors`) live in :mod:`ptwm.integrations`
|
|
12
|
+
and are imported from there explicitly, not re-exported here.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
16
|
+
|
|
17
|
+
from ._config import (
|
|
18
|
+
CompressionConfig,
|
|
19
|
+
DecompressionConfig,
|
|
20
|
+
Format,
|
|
21
|
+
Lossy,
|
|
22
|
+
Method,
|
|
23
|
+
)
|
|
24
|
+
from ._exceptions import (
|
|
25
|
+
CompressionMethodNotSupportedError,
|
|
26
|
+
Error,
|
|
27
|
+
HeaderParseError,
|
|
28
|
+
InvalidDTypeError,
|
|
29
|
+
)
|
|
30
|
+
from .core import Compressor, Decompressor
|
|
31
|
+
from .integrations import materialize_hf_cache
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
__version__ = version("ptwm")
|
|
35
|
+
except PackageNotFoundError:
|
|
36
|
+
__version__ = "0.0.0+unknown"
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"CompressionConfig",
|
|
40
|
+
"CompressionMethodNotSupportedError",
|
|
41
|
+
"Compressor",
|
|
42
|
+
"DecompressionConfig",
|
|
43
|
+
"Decompressor",
|
|
44
|
+
"Format",
|
|
45
|
+
"Lossy",
|
|
46
|
+
"Method",
|
|
47
|
+
"Error",
|
|
48
|
+
"HeaderParseError",
|
|
49
|
+
"InvalidDTypeError",
|
|
50
|
+
"__version__",
|
|
51
|
+
"materialize_hf_cache",
|
|
52
|
+
]
|
ptwm/_config.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ptwm._rust.policy import ResolvedPolicy
|
|
9
|
+
|
|
10
|
+
from .codecs import CodecId
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Method(Enum):
|
|
14
|
+
AUTO = 0
|
|
15
|
+
HUFFMAN = 1
|
|
16
|
+
ZSTD = 2
|
|
17
|
+
IDENTITY = 3
|
|
18
|
+
RANS = 4
|
|
19
|
+
MICROSCALE = 5
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def _missing_(cls, value):
|
|
23
|
+
if isinstance(value, str):
|
|
24
|
+
value = value.upper()
|
|
25
|
+
if value in cls.__members__:
|
|
26
|
+
return cls.__members__[value]
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Format(Enum):
|
|
31
|
+
BYTE = 1
|
|
32
|
+
TORCH = 2
|
|
33
|
+
NUMPY = 3
|
|
34
|
+
FILE = 4
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def _missing_(cls, value):
|
|
38
|
+
if isinstance(value, str):
|
|
39
|
+
value = value.upper()
|
|
40
|
+
if value in cls.__members__:
|
|
41
|
+
return cls.__members__[value]
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Lossy(Enum):
|
|
46
|
+
NONE = 0
|
|
47
|
+
INTEGER = 1
|
|
48
|
+
UNSIGN = 2
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def _missing_(cls, value):
|
|
52
|
+
if isinstance(value, str):
|
|
53
|
+
value = value.upper()
|
|
54
|
+
if value in cls.__members__:
|
|
55
|
+
return cls.__members__[value]
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
60
|
+
class CompressionConfig:
|
|
61
|
+
method: Method = Method.AUTO
|
|
62
|
+
input_format: Format = Format.BYTE
|
|
63
|
+
bytearray_dtype: str = "bfloat16"
|
|
64
|
+
is_monotonic: int = 0
|
|
65
|
+
threads: int | None = None
|
|
66
|
+
compression_threshold: float = 0.95
|
|
67
|
+
check_th_after_percent: int = 10
|
|
68
|
+
reorder_signbit: int = 0
|
|
69
|
+
delta_compressed_type: str | None = None
|
|
70
|
+
lossy_compressed_type: Lossy = Lossy.NONE
|
|
71
|
+
lossy_compressed_factor: int = 27
|
|
72
|
+
compression_chunk: int = 256 * 1024
|
|
73
|
+
is_streaming: bool = False
|
|
74
|
+
streaming_chunk: int = 1024 * 1024
|
|
75
|
+
input_file: str | None = None
|
|
76
|
+
compressed_file: str | None = None
|
|
77
|
+
decompressed_file: str | None = None
|
|
78
|
+
zstd_level: int = 3
|
|
79
|
+
lz4_compression_level: int = 0
|
|
80
|
+
codec_menu: list[CodecId] | None = None
|
|
81
|
+
"""Restrict per-role trial-encode menus to this set of codecs.
|
|
82
|
+
|
|
83
|
+
None → use the full role-applicable menu. An empty intersection on a
|
|
84
|
+
role raises ``InvalidContainer`` — an Identity fallback would silently
|
|
85
|
+
falsify ablation measurements. Ignored when ``method`` selects a forced-
|
|
86
|
+
codec path (ZSTD / RANS / IDENTITY).
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def from_resolved_policy(
|
|
91
|
+
cls,
|
|
92
|
+
rp: ResolvedPolicy,
|
|
93
|
+
*,
|
|
94
|
+
base: CompressionConfig | None = None,
|
|
95
|
+
) -> CompressionConfig:
|
|
96
|
+
"""Build a constrained ``CompressionConfig`` from a ``ResolvedPolicy``.
|
|
97
|
+
|
|
98
|
+
The returned config's ``codec_menu`` is the intersection of the
|
|
99
|
+
resolved policy's ``effective_global`` set with the wire-stable
|
|
100
|
+
built-in ``CodecId`` variants.
|
|
101
|
+
|
|
102
|
+
The resolved policy lists every contribution (built-in or installed
|
|
103
|
+
third-party) that is currently trusted *and* allowed by the policy
|
|
104
|
+
file. Restricting the trial-encode menu to that intersection is how
|
|
105
|
+
an ablation run actually exercises a variant policy: tensors that
|
|
106
|
+
previously round-tripped through a now-excluded codec will fall
|
|
107
|
+
through to the next-best allowed coder instead.
|
|
108
|
+
|
|
109
|
+
``base`` is the starting config (defaults to a default-constructed
|
|
110
|
+
``CompressionConfig``); every field other than ``codec_menu`` is
|
|
111
|
+
preserved.
|
|
112
|
+
"""
|
|
113
|
+
# Imported locally — `ptwm.codecs` reaches back through
|
|
114
|
+
# `ptwm.preprocessing → ptwm.utils → ptwm._config` at top level,
|
|
115
|
+
# so a module-level import here is a real circular-import bug
|
|
116
|
+
# (not just a ruff style preference).
|
|
117
|
+
from ptwm._rust import builtin_canonical_id # noqa: PLC0415
|
|
118
|
+
|
|
119
|
+
from .codecs import CodecId # noqa: PLC0415
|
|
120
|
+
|
|
121
|
+
# Map every wire-stable built-in codec to the short name used in
|
|
122
|
+
# `crates/ptwm-core/src/extension/builtins.rs`.
|
|
123
|
+
_BUILTIN_CODEC_NAMES: dict[CodecId, str] = {
|
|
124
|
+
CodecId.Identity: "identity",
|
|
125
|
+
CodecId.Huffman: "huffman",
|
|
126
|
+
CodecId.Rans: "rans",
|
|
127
|
+
CodecId.Zstd: "zstd",
|
|
128
|
+
CodecId.PerGroupCodebook: "per_group_codebook",
|
|
129
|
+
CodecId.Order1ScaleAC: "order1_scale_ac",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
allowed_hex = set(rp.effective_global_ids())
|
|
133
|
+
|
|
134
|
+
# Empty `effective_global` means "no policy applied" (the default
|
|
135
|
+
# `PolicyFile.default_empty()` case): preserve the base's
|
|
136
|
+
# codec_menu rather than collapsing to an empty list, which the
|
|
137
|
+
# Rust dispatcher would treat as "no codecs allowed → error".
|
|
138
|
+
if not allowed_hex:
|
|
139
|
+
return base if base is not None else cls()
|
|
140
|
+
|
|
141
|
+
menu: list[CodecId] = []
|
|
142
|
+
for codec_id, short_name in _BUILTIN_CODEC_NAMES.items():
|
|
143
|
+
if builtin_canonical_id(short_name).hex() in allowed_hex:
|
|
144
|
+
menu.append(codec_id)
|
|
145
|
+
|
|
146
|
+
starting = base if base is not None else cls()
|
|
147
|
+
return replace(starting, codec_menu=menu)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
151
|
+
class DecompressionConfig:
|
|
152
|
+
# Most properties are inferred from the header; this struct only carries
|
|
153
|
+
# runtime specifics like thread count and the delta reference.
|
|
154
|
+
threads: int | None = None
|
|
155
|
+
delta_second_data: bytes | None = None
|
|
156
|
+
skip_missing: bool = False
|
|
157
|
+
"""If ``True``, open containers that reference non-builtin extensions
|
|
158
|
+
that are not installed on this host. Tensors needing those extensions
|
|
159
|
+
will fail when decoded, but the container can still be opened and
|
|
160
|
+
interrogated (e.g. listing tensor names). When ``False`` (the default),
|
|
161
|
+
opening such a container raises ``ValueError``.
|
|
162
|
+
"""
|
|
Binary file
|
ptwm/_exceptions.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
# Standard logger for the package
|
|
4
|
+
logger = logging.getLogger("ptwm")
|
|
5
|
+
# Default handler setup could be left to the user, but we can provide a NullHandler
|
|
6
|
+
logger.addHandler(logging.NullHandler())
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Error(Exception):
|
|
10
|
+
"""Base exception for all errors originating from the PTWM library."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CompressionMethodNotSupportedError(Error):
|
|
14
|
+
"""Raised when an unsupported compression method is requested."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InvalidDTypeError(Error):
|
|
18
|
+
"""Raised when a tensor or numpy array has an unsupported dtype."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HeaderParseError(Error):
|
|
22
|
+
"""Raised when the binary header is invalid or corrupt."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class UnsupportedQuantConfigError(ValueError):
|
|
26
|
+
"""Raised when ``hf_quant_config.json`` carries an unsupported algo."""
|
ptwm/_loader.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Discover Python-resident extensions via entry points.
|
|
2
|
+
|
|
3
|
+
Entry points under the `ptwm.extensions` group are loaded at PTWM
|
|
4
|
+
startup. Each one is a callable that, when invoked, calls
|
|
5
|
+
ContributionRegistry.register(...) for each contribution it provides.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from importlib.metadata import EntryPoint, entry_points
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def discover_host_extensions() -> Iterable[tuple[str, EntryPoint]]:
|
|
16
|
+
"""Yield (entry_point_name, ep) for each ptwm.extensions entry point."""
|
|
17
|
+
eps = entry_points(group="ptwm.extensions")
|
|
18
|
+
for ep in eps:
|
|
19
|
+
yield ep.name, ep
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_all_host_extensions() -> int:
|
|
23
|
+
"""Invoke every discovered entry point.
|
|
24
|
+
|
|
25
|
+
Returns the number of entry points successfully loaded. Errors from
|
|
26
|
+
a single entry point are logged (via print) but don't stop the
|
|
27
|
+
loader — a broken extension shouldn't take down the whole runtime.
|
|
28
|
+
"""
|
|
29
|
+
count = 0
|
|
30
|
+
for name, ep in discover_host_extensions():
|
|
31
|
+
try:
|
|
32
|
+
register_fn = ep.load()
|
|
33
|
+
except Exception as exc: # noqa: BLE001
|
|
34
|
+
print( # noqa: T201
|
|
35
|
+
f"warning: ptwm.extensions[{name}] failed to load: {exc}",
|
|
36
|
+
file=sys.stderr,
|
|
37
|
+
)
|
|
38
|
+
continue
|
|
39
|
+
try:
|
|
40
|
+
register_fn()
|
|
41
|
+
except Exception as exc: # noqa: BLE001
|
|
42
|
+
print( # noqa: T201
|
|
43
|
+
f"warning: ptwm.extensions[{name}] failed to register: {exc}",
|
|
44
|
+
file=sys.stderr,
|
|
45
|
+
)
|
|
46
|
+
continue
|
|
47
|
+
count += 1
|
|
48
|
+
return count
|
ptwm/_rust/__init__.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""Thin wrapper around the ``weights._core`` PyO3 extension.
|
|
2
|
+
|
|
3
|
+
The ``weights._rust`` package is the ONLY place in the codebase that imports
|
|
4
|
+
``weights._core``. Every other module goes through the wrappers defined here
|
|
5
|
+
(and its submodules), so the underlying binding can swap (e.g. for a
|
|
6
|
+
subprocess backend) without touching the rest of the codebase.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from ptwm import _core as _ext
|
|
12
|
+
|
|
13
|
+
from ._errors import translate_errors
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"blake3_hash",
|
|
17
|
+
"builtin_canonical_id",
|
|
18
|
+
"codec_id_registry",
|
|
19
|
+
"compress_model",
|
|
20
|
+
"decode_model",
|
|
21
|
+
"decode_tensor",
|
|
22
|
+
"decode_tensor_delta_hash",
|
|
23
|
+
"decode_tensor_info",
|
|
24
|
+
"decode_tensor_shape",
|
|
25
|
+
"delta_float_sub_decode",
|
|
26
|
+
"delta_float_sub_encode",
|
|
27
|
+
"delta_xor_decode",
|
|
28
|
+
"delta_xor_encode",
|
|
29
|
+
"dtype_element_size",
|
|
30
|
+
"explode_ptwm",
|
|
31
|
+
"huffman_decode",
|
|
32
|
+
"huffman_encode",
|
|
33
|
+
"identity_decode",
|
|
34
|
+
"identity_encode",
|
|
35
|
+
"implode_ptwm",
|
|
36
|
+
"list_plane_summaries",
|
|
37
|
+
"list_tensor_names",
|
|
38
|
+
"rans_decode",
|
|
39
|
+
"rans_encode",
|
|
40
|
+
"shannon",
|
|
41
|
+
"translate_errors",
|
|
42
|
+
"xxhash64",
|
|
43
|
+
"zstd_decode",
|
|
44
|
+
"zstd_encode",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@translate_errors
|
|
49
|
+
def xxhash64(data: bytes | memoryview) -> int:
|
|
50
|
+
"""Return the 64-bit xxhash of ``data`` (seed 0)."""
|
|
51
|
+
return _ext.xxhash64(data)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@translate_errors
|
|
55
|
+
def shannon(data: bytes | memoryview) -> float:
|
|
56
|
+
"""Return the Shannon entropy of ``data`` in bits/byte."""
|
|
57
|
+
return _ext.shannon(data)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# --- Per-plane codec encode / decode ---------------------------------------
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@translate_errors
|
|
64
|
+
def identity_encode(data: bytes | memoryview) -> bytes:
|
|
65
|
+
return bytes(_ext.identity_encode(data))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@translate_errors
|
|
69
|
+
def identity_decode(blob: bytes | memoryview, expected_len: int) -> bytes:
|
|
70
|
+
return bytes(_ext.identity_decode(blob, expected_len))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@translate_errors
|
|
74
|
+
def huffman_encode(data: bytes | memoryview) -> bytes:
|
|
75
|
+
return bytes(_ext.huffman_encode(data))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@translate_errors
|
|
79
|
+
def huffman_decode(blob: bytes | memoryview, expected_len: int) -> bytes:
|
|
80
|
+
return bytes(_ext.huffman_decode(blob, expected_len))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@translate_errors
|
|
84
|
+
def rans_encode(data: bytes | memoryview) -> bytes:
|
|
85
|
+
return bytes(_ext.rans_encode(data))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@translate_errors
|
|
89
|
+
def rans_decode(blob: bytes | memoryview, expected_len: int) -> bytes:
|
|
90
|
+
return bytes(_ext.rans_decode(blob, expected_len))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@translate_errors
|
|
94
|
+
def zstd_encode(data: bytes | memoryview, level: int = 3) -> bytes:
|
|
95
|
+
return bytes(_ext.zstd_encode(data, level))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@translate_errors
|
|
99
|
+
def zstd_decode(blob: bytes | memoryview) -> bytes:
|
|
100
|
+
return bytes(_ext.zstd_decode(blob))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# --- Delta primitives ------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@translate_errors
|
|
107
|
+
def delta_xor_encode(raw: bytes | memoryview, reference: bytes | memoryview) -> bytes:
|
|
108
|
+
return bytes(_ext.delta_xor_encode(raw, reference))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@translate_errors
|
|
112
|
+
def delta_xor_decode(
|
|
113
|
+
residual: bytes | memoryview, reference: bytes | memoryview
|
|
114
|
+
) -> bytes:
|
|
115
|
+
return bytes(_ext.delta_xor_decode(residual, reference))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@translate_errors
|
|
119
|
+
def delta_float_sub_encode(
|
|
120
|
+
raw: bytes | memoryview, reference: bytes | memoryview
|
|
121
|
+
) -> bytes:
|
|
122
|
+
return bytes(_ext.delta_float_sub_encode(raw, reference))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@translate_errors
|
|
126
|
+
def delta_float_sub_decode(
|
|
127
|
+
residual: bytes | memoryview, reference: bytes | memoryview
|
|
128
|
+
) -> bytes:
|
|
129
|
+
return bytes(_ext.delta_float_sub_decode(residual, reference))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@translate_errors
|
|
133
|
+
def blake3_hash(data: bytes | memoryview) -> bytes:
|
|
134
|
+
"""BLAKE3 digest (32 bytes) of ``data``."""
|
|
135
|
+
return bytes(_ext.blake3_hash(data))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def codec_id_registry() -> list[tuple[str, int]]:
|
|
139
|
+
"""Authoritative ``(name, wire_id)`` list from the Rust ``CodecId``.
|
|
140
|
+
|
|
141
|
+
The regression test that locks the Python ``CodecId`` IntEnum to the
|
|
142
|
+
Rust truth consumes this list.
|
|
143
|
+
"""
|
|
144
|
+
return [(str(n), int(v)) for n, v in _ext.codec_id_registry()]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def builtin_canonical_id(name: str) -> bytes:
|
|
148
|
+
r"""Return the 32-byte canonical id for the named built-in contribution.
|
|
149
|
+
|
|
150
|
+
``name`` is the short name used in `builtins.rs` (e.g. ``"huffman"``,
|
|
151
|
+
``"rans"``, ``"order1_scale_ac"``). The derivation matches
|
|
152
|
+
``CanonicalId::derive``:
|
|
153
|
+
``blake3(BUILTIN_PUBKEY || 0x00 || name || 0x00 || CARGO_PKG_VERSION)``
|
|
154
|
+
— the zero separators are part of the derivation so authors
|
|
155
|
+
reproducing IDs in other languages get the same bytes.
|
|
156
|
+
"""
|
|
157
|
+
return bytes(_ext.builtin_canonical_id(name))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# --- Dtype lookups (Rust-authoritative) -----------------------------------
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@translate_errors
|
|
164
|
+
def dtype_element_size(dtype_code: int) -> int:
|
|
165
|
+
"""Bytes per scalar element for ``dtype_code``."""
|
|
166
|
+
return _ext.dtype_element_size(dtype_code)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# --- Container API (PPG chain-based) ------------------------------------
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@translate_errors
|
|
173
|
+
def compress_model(
|
|
174
|
+
records: list[
|
|
175
|
+
tuple[
|
|
176
|
+
str,
|
|
177
|
+
int,
|
|
178
|
+
int,
|
|
179
|
+
bytes,
|
|
180
|
+
list[int] | None,
|
|
181
|
+
str | None,
|
|
182
|
+
bytes | None,
|
|
183
|
+
]
|
|
184
|
+
],
|
|
185
|
+
chains_per_tensor: list[list[bytes]],
|
|
186
|
+
emit_payload_hash: bool,
|
|
187
|
+
emit_plane_crc: bool,
|
|
188
|
+
method_hint: int = 3,
|
|
189
|
+
streaming_chunk: int | None = None,
|
|
190
|
+
codec_menu: list[int] | None = None,
|
|
191
|
+
) -> bytes:
|
|
192
|
+
"""Compress a list of tensors into a ``.ptwm`` container.
|
|
193
|
+
|
|
194
|
+
Each element of ``records`` is a 7-tuple:
|
|
195
|
+
``(name, dtype_code, input_format, raw_bytes, shape,
|
|
196
|
+
dtype_name, delta_reference_blake3)``.
|
|
197
|
+
``chains_per_tensor`` — one list of chain wire-byte blobs per tensor;
|
|
198
|
+
each blob is produced by ``Chain.to_bytes()`` from ``ptwm.preprocessing``.
|
|
199
|
+
Pass a single-element list when no multi-chain trial-encode is needed.
|
|
200
|
+
``method_hint``: 1=HUFFMAN, 2=ZSTD, 3=MICROSCALE, 4=RANS, 5=IDENTITY.
|
|
201
|
+
``streaming_chunk``: when set, split each plane into fixed-size chunks.
|
|
202
|
+
``codec_menu``: optional list of wire-stable ``CodecId`` integers
|
|
203
|
+
restricting the trial-encode menu. ``None`` uses the full menu;
|
|
204
|
+
empty intersection on a plane raises ``InvalidContainer``.
|
|
205
|
+
"""
|
|
206
|
+
return bytes(
|
|
207
|
+
_ext.compress_model(
|
|
208
|
+
records,
|
|
209
|
+
chains_per_tensor,
|
|
210
|
+
emit_payload_hash,
|
|
211
|
+
emit_plane_crc,
|
|
212
|
+
method_hint,
|
|
213
|
+
streaming_chunk,
|
|
214
|
+
codec_menu,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@translate_errors
|
|
220
|
+
def decode_tensor(blob: bytes, name: str, skip_missing: bool = False) -> bytes:
|
|
221
|
+
"""Decode a single tensor from a ``.ptwm`` container blob.
|
|
222
|
+
|
|
223
|
+
Returns raw decompressed bytes.
|
|
224
|
+
"""
|
|
225
|
+
return bytes(_ext.decode_tensor(blob, name, skip_missing))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@translate_errors
|
|
229
|
+
def decode_model(blob: bytes, skip_missing: bool = False) -> list[tuple[str, bytes]]:
|
|
230
|
+
"""Decode every tensor from a ``.ptwm`` container blob in parallel.
|
|
231
|
+
|
|
232
|
+
Returns ``(name, raw_bytes)`` per tensor in container index order. Prefer
|
|
233
|
+
this over per-tensor :func:`decode_tensor` for a full-model load — the
|
|
234
|
+
decodes run across the rayon pool with the GIL released.
|
|
235
|
+
"""
|
|
236
|
+
return _ext.decode_model(blob, skip_missing)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@translate_errors
|
|
240
|
+
def decode_tensor_shape(blob: bytes, name: str) -> tuple[list[int], str] | None:
|
|
241
|
+
"""Return ``(shape, dtype_name)`` for a tensor, or ``None``."""
|
|
242
|
+
return _ext.decode_tensor_shape(blob, name)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@translate_errors
|
|
246
|
+
def decode_tensor_delta_hash(
|
|
247
|
+
blob: bytes, name: str, skip_missing: bool = False
|
|
248
|
+
) -> bytes | None:
|
|
249
|
+
"""Return the 32-byte BLAKE3 hash of the delta reference for ``name``.
|
|
250
|
+
|
|
251
|
+
Returns ``None`` if the tensor has no delta dependency.
|
|
252
|
+
"""
|
|
253
|
+
got = _ext.decode_tensor_delta_hash(blob, name, skip_missing)
|
|
254
|
+
return bytes(got) if got is not None else None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@translate_errors
|
|
258
|
+
def list_tensor_names(blob: bytes, skip_missing: bool = False) -> list[str]:
|
|
259
|
+
"""Return every tensor name stored in a ``.ptwm`` container."""
|
|
260
|
+
return list(_ext.list_tensor_names(blob, skip_missing))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@translate_errors
|
|
264
|
+
def list_plane_summaries(
|
|
265
|
+
blob: bytes,
|
|
266
|
+
) -> list[tuple[str, int, int, int, int, int]]:
|
|
267
|
+
"""Return per-plane summaries for every tensor in a container.
|
|
268
|
+
|
|
269
|
+
Each entry: ``(tensor_name, plane_index, role, codec_id,
|
|
270
|
+
state_source, payload_len)``.
|
|
271
|
+
"""
|
|
272
|
+
return list(_ext.list_plane_summaries(blob))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@translate_errors
|
|
276
|
+
def decode_tensor_info(
|
|
277
|
+
blob: bytes, name: str, skip_missing: bool = False
|
|
278
|
+
) -> tuple[list[int], str, int, int] | None:
|
|
279
|
+
"""Return ``(shape, dtype_name, dtype_code, input_format)`` or ``None``."""
|
|
280
|
+
return _ext.decode_tensor_info(blob, name, skip_missing)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# --- Container ⇄ canonical PTWM-JSON manifest transcoder -------------------
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@translate_errors
|
|
287
|
+
def explode_ptwm(
|
|
288
|
+
blob: bytes,
|
|
289
|
+
) -> tuple[str, list[tuple[str, str, str]], dict[str, bytes]]:
|
|
290
|
+
"""Explode a ``.ptwm`` container into its canonical PTWM-JSON projection.
|
|
291
|
+
|
|
292
|
+
Returns ``(registry_json, tensors, members)`` where ``registry_json`` is
|
|
293
|
+
the canonical (JCS) archive-scoped registry manifest, ``tensors`` is a list
|
|
294
|
+
of ``(name, key, tensor_manifest_json)`` in file order, and ``members`` maps
|
|
295
|
+
member name → bytes. ``implode_ptwm`` reproduces ``blob`` byte-for-byte.
|
|
296
|
+
|
|
297
|
+
The extension already returns ``str``/``bytes`` of the correct type, so the
|
|
298
|
+
result is returned without re-wrapping (avoids copying large payloads).
|
|
299
|
+
"""
|
|
300
|
+
return _ext.explode_ptwm(blob)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@translate_errors
|
|
304
|
+
def implode_ptwm(
|
|
305
|
+
registry_json: str,
|
|
306
|
+
tensor_jsons: list[str],
|
|
307
|
+
members: dict[str, bytes],
|
|
308
|
+
) -> bytes:
|
|
309
|
+
"""Reconstruct a ``.ptwm`` container from a manifest projection.
|
|
310
|
+
|
|
311
|
+
``tensor_jsons`` must be in file order. Inverse of :func:`explode_ptwm`.
|
|
312
|
+
"""
|
|
313
|
+
return _ext.implode_ptwm(registry_json, tensor_jsons, members)
|
ptwm/_rust/_errors.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Error translation at the PyO3 boundary.
|
|
2
|
+
|
|
3
|
+
The `_core` extension raises stdlib `ValueError`/`RuntimeError`/`MemoryError`/
|
|
4
|
+
`BufferError` subclasses (mapped from `WeightsCoreError` variants on the Rust
|
|
5
|
+
side). The `translate_errors` decorator normalizes those into
|
|
6
|
+
`Error` subclasses so callers can `except Error` without caring
|
|
7
|
+
which Python exception type PyO3 used.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import functools
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
from .._exceptions import Error, HeaderParseError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def translate_errors[**P, R](fn: Callable[P, R]) -> Callable[P, R]:
|
|
19
|
+
"""Wrap a callable so `_core` exceptions surface as :class:`Error`.
|
|
20
|
+
|
|
21
|
+
Header-parse errors raised by `_core.parse_header` are specifically mapped
|
|
22
|
+
to :class:`HeaderParseError`; everything else to a plain
|
|
23
|
+
:class:`Error`.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@functools.wraps(fn)
|
|
27
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
28
|
+
try:
|
|
29
|
+
return fn(*args, **kwargs)
|
|
30
|
+
except Error:
|
|
31
|
+
raise
|
|
32
|
+
except (BufferError, MemoryError, ValueError, RuntimeError) as e:
|
|
33
|
+
message = str(e)
|
|
34
|
+
if fn.__name__ == "parse_header":
|
|
35
|
+
raise HeaderParseError(message) from e
|
|
36
|
+
raise Error(message) from e
|
|
37
|
+
|
|
38
|
+
return wrapped
|
ptwm/_rust/ext.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Thin shim exposing the install/discovery surface as ptwm._rust.ext."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ptwm._core import ( # type: ignore[import]
|
|
6
|
+
InstalledContribution,
|
|
7
|
+
ext_install,
|
|
8
|
+
ext_list,
|
|
9
|
+
ext_remove,
|
|
10
|
+
ext_user_dir,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"InstalledContribution",
|
|
15
|
+
"ext_install",
|
|
16
|
+
"ext_list",
|
|
17
|
+
"ext_remove",
|
|
18
|
+
"ext_user_dir",
|
|
19
|
+
]
|
ptwm/_rust/host.py
ADDED