entropack 0.1.0__py3-none-any.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.
Files changed (40) hide show
  1. entropack/__init__.py +15 -0
  2. entropack/backends/__init__.py +19 -0
  3. entropack/backends/cuda/__init__.py +16 -0
  4. entropack/backends/cuda/device.py +96 -0
  5. entropack/backends/cuda/kernels.py +70 -0
  6. entropack/compression/__init__.py +4 -0
  7. entropack/compression/api.py +76 -0
  8. entropack/compression/compressed_tensor.py +205 -0
  9. entropack/linear/__init__.py +5 -0
  10. entropack/linear/linear.py +444 -0
  11. entropack/linear/quant_kernels.py +108 -0
  12. entropack/linear/utils.py +41 -0
  13. entropack/registry.py +121 -0
  14. entropack/schemes/__init__.py +12 -0
  15. entropack/schemes/base.py +121 -0
  16. entropack/schemes/checks.py +33 -0
  17. entropack/schemes/config.py +111 -0
  18. entropack/schemes/dfloat11/__init__.py +52 -0
  19. entropack/schemes/dfloat11/cuda.py +223 -0
  20. entropack/schemes/dfloat11/dfloat11.cu +740 -0
  21. entropack/schemes/dfloat11/eager.py +239 -0
  22. entropack/schemes/dfloat11/format.py +146 -0
  23. entropack/schemes/lattice_rans/__init__.py +60 -0
  24. entropack/schemes/lattice_rans/cuda.py +670 -0
  25. entropack/schemes/lattice_rans/eager.py +405 -0
  26. entropack/schemes/lattice_rans/format.py +309 -0
  27. entropack/schemes/lattice_rans/lattice_rans.cu +1140 -0
  28. entropack/schemes/lattice_rans/rans.py +209 -0
  29. entropack/schemes/lattice_rans/rdo.py +162 -0
  30. entropack/schemes/tile_ans/__init__.py +55 -0
  31. entropack/schemes/tile_ans/cuda.py +170 -0
  32. entropack/schemes/tile_ans/device.cuh +90 -0
  33. entropack/schemes/tile_ans/eager.py +307 -0
  34. entropack/schemes/tile_ans/format.py +164 -0
  35. entropack/schemes/tile_ans/tile_ans.cu +469 -0
  36. entropack-0.1.0.dist-info/METADATA +158 -0
  37. entropack-0.1.0.dist-info/RECORD +40 -0
  38. entropack-0.1.0.dist-info/WHEEL +5 -0
  39. entropack-0.1.0.dist-info/licenses/LICENSE +201 -0
  40. entropack-0.1.0.dist-info/top_level.txt +1 -0
entropack/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ from importlib import metadata
2
+
3
+ try:
4
+ __version__ = metadata.version("entropack")
5
+ except metadata.PackageNotFoundError:
6
+ __version__ = "0+unknown"
7
+
8
+ from .compression import CompressedTensor, compress, decompress
9
+ from .linear import CompressedFP8Linear, CompressedINT8Linear, CompressedLinear
10
+ from .schemes import CompressionConfig, DFloat11Config, LatticeRANSConfig, RawConfig, TileANSConfig
11
+
12
+ __all__ = [
13
+ "CompressedFP8Linear", "CompressedINT8Linear", "CompressedLinear", "CompressedTensor", "CompressionConfig",
14
+ "DFloat11Config", "LatticeRANSConfig", "RawConfig", "TileANSConfig", "__version__", "compress", "decompress",
15
+ ]
@@ -0,0 +1,19 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+
4
+ import torch
5
+
6
+ from . import cuda
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class Backend:
11
+ name: str
12
+ priority: int = 0
13
+ probe: Callable[[], str | None] | None = None
14
+ device: Callable[[], torch.device] | None = None
15
+
16
+
17
+ BACKENDS: tuple[Backend, ...] = (
18
+ Backend("cuda", priority=100, probe=cuda.probe, device=cuda.runs_on), Backend("eager", priority=0),
19
+ )
@@ -0,0 +1,16 @@
1
+ import torch
2
+
3
+
4
+ def probe() -> str | None:
5
+ if not torch.cuda.is_available():
6
+ return "torch.cuda.is_available() is False (no NVIDIA device / driver)"
7
+ try:
8
+ import cupy
9
+ except ImportError as e:
10
+ return (f"cupy is not installed ({e}); install a matching build, e.g. "
11
+ "`pip install cupy-cuda13x` for CUDA 13 or `pip install cupy-cuda12x` for CUDA 12")
12
+ return None
13
+
14
+
15
+ def runs_on() -> torch.device:
16
+ return torch.device("cuda", torch.cuda.current_device())
@@ -0,0 +1,96 @@
1
+ from dataclasses import dataclass
2
+
3
+ import cupy
4
+ import torch
5
+
6
+ from .kernels import device_index
7
+
8
+ _FALLBACK_WARP_SIZE = 32
9
+
10
+ _DEFAULT_GRID_WAVES = 8
11
+
12
+ __all__ = ["DeviceCaps", "caps", "resolve_threads", "validate_threads_per_block"]
13
+
14
+
15
+ def _shared_optin(index: int, properties) -> int:
16
+ optin = getattr(properties, "shared_memory_per_block_optin", 0)
17
+ if optin:
18
+ return int(optin)
19
+ with cupy.cuda.Device(index):
20
+ attributes = cupy.cuda.Device(index).attributes
21
+ return int(attributes["MaxSharedMemoryPerBlockOptin"])
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class DeviceCaps:
26
+ index: int
27
+ name: str
28
+ compute_capability: tuple[int, int]
29
+ sm_count: int
30
+ warp_size: int
31
+ max_threads_per_block: int
32
+ threads_per_sm: int
33
+ regs_per_sm: int
34
+ shared_per_block: int
35
+ shared_optin: int
36
+ shared_per_sm: int
37
+ l2_bytes: int
38
+
39
+ def blocks_per_sm(self, threads_per_block: int, shared_per_block: int = 0, regs_per_thread: int = 0) -> int:
40
+ blocks = self.threads_per_sm // threads_per_block
41
+ if shared_per_block > 0:
42
+ blocks = min(blocks, self.shared_per_sm // shared_per_block)
43
+ if regs_per_thread > 0:
44
+ blocks = min(blocks, self.regs_per_sm // (regs_per_thread * threads_per_block))
45
+ return max(1, blocks)
46
+
47
+ def resident_blocks(self, threads_per_block: int, shared_per_block: int = 0, regs_per_thread: int = 0) -> int:
48
+ return self.sm_count * self.blocks_per_sm(threads_per_block, shared_per_block, regs_per_thread)
49
+
50
+ def grid(self, wanted: int, threads_per_block: int, shared_per_block: int = 0, waves: int = _DEFAULT_GRID_WAVES) -> int:
51
+ limit = self.resident_blocks(threads_per_block, shared_per_block) * waves
52
+ return max(1, min(wanted, limit))
53
+
54
+ def shared_limit(self, static_slack: int = 0) -> int:
55
+ return max(0, min(self.shared_optin, self.shared_per_sm - static_slack))
56
+
57
+ def threads_per_block(self, wanted: int) -> int:
58
+ usable = min(wanted, self.max_threads_per_block)
59
+ usable -= usable % self.warp_size
60
+ return max(self.warp_size, usable)
61
+
62
+
63
+ _caps_cache: dict[int, DeviceCaps] = {}
64
+
65
+
66
+ def caps(device=None) -> DeviceCaps:
67
+ index = device_index(device)
68
+ cached = _caps_cache.get(index)
69
+ if cached is not None:
70
+ return cached
71
+ properties = torch.cuda.get_device_properties(index)
72
+ queried = DeviceCaps(
73
+ index=index, name=properties.name, compute_capability=(properties.major, properties.minor),
74
+ sm_count=properties.multi_processor_count, warp_size=getattr(properties, "warp_size", 0) or _FALLBACK_WARP_SIZE,
75
+ max_threads_per_block=properties.max_threads_per_block, threads_per_sm=properties.max_threads_per_multi_processor,
76
+ regs_per_sm=properties.regs_per_multiprocessor, shared_per_block=properties.shared_memory_per_block,
77
+ shared_optin=_shared_optin(index, properties), shared_per_sm=properties.shared_memory_per_multiprocessor,
78
+ l2_bytes=getattr(properties, "L2_cache_size", 0),
79
+ )
80
+ _caps_cache[index] = queried
81
+ return queried
82
+
83
+
84
+ def validate_threads_per_block(caps: DeviceCaps, threads_per_block: int) -> None:
85
+ if threads_per_block % caps.warp_size or threads_per_block > caps.max_threads_per_block:
86
+ raise ValueError(
87
+ f"threads_per_block={threads_per_block} is not launchable on {caps.name}: it must be "
88
+ f"a multiple of the {caps.warp_size}-thread warp size and at most {caps.max_threads_per_block}"
89
+ )
90
+
91
+
92
+ def resolve_threads(caps: DeviceCaps, requested: int | None, default: int) -> int:
93
+ if requested is None:
94
+ return caps.threads_per_block(default)
95
+ validate_threads_per_block(caps, requested)
96
+ return requested
@@ -0,0 +1,70 @@
1
+ from collections.abc import Callable, Hashable, Sequence
2
+ from pathlib import Path
3
+
4
+ import cupy
5
+ import numpy as np
6
+ import torch
7
+
8
+ _modules: dict[tuple, object] = {}
9
+ _kernels: dict[tuple, object] = {}
10
+ _streams: dict[tuple, object] = {}
11
+
12
+ __all__ = ["KernelLibrary", "device_index", "ensure_dynamic_shared", "external_stream", "pointer"]
13
+
14
+
15
+ def ensure_dynamic_shared(kernel, shared_bytes: int) -> None:
16
+ if shared_bytes > kernel.max_dynamic_shared_size_bytes:
17
+ kernel.max_dynamic_shared_size_bytes = shared_bytes
18
+
19
+
20
+ class KernelLibrary:
21
+ def __init__(
22
+ self, key: str, source: Path, defines: Callable[[int, Hashable], Sequence[str]],
23
+ includes: Sequence[Path] = (), kernel_names: Sequence[str] | None = None,
24
+ ):
25
+ self.key = key
26
+ self.source = source
27
+ self.defines = defines
28
+ self.includes = tuple(includes)
29
+ self.kernel_names = None if kernel_names is None else frozenset(kernel_names)
30
+
31
+ def kernel(self, device_index: int, variant: Hashable, name: str):
32
+ if self.kernel_names is not None and name not in self.kernel_names:
33
+ raise KeyError(name)
34
+ module_key = (self.key, device_index, variant)
35
+ module = _modules.get(module_key)
36
+ if module is None:
37
+ options = ["--std=c++17"]
38
+ options += [f"-D{definition}" for definition in self.defines(device_index, variant)]
39
+ options += [f"-I{include}" for include in self.includes]
40
+ with cupy.cuda.Device(device_index):
41
+ module = cupy.RawModule(code=self.source.read_text(), options=tuple(options))
42
+ _modules[module_key] = module
43
+ kernel_key = (self.key, device_index, variant, name)
44
+ kernel = _kernels.get(kernel_key)
45
+ if kernel is None:
46
+ kernel = module.get_function(name)
47
+ _kernels[kernel_key] = kernel
48
+ return kernel
49
+
50
+
51
+ def external_stream(stream: torch.cuda.Stream):
52
+ key = (device_index(stream.device), int(stream.cuda_stream))
53
+ wrapper = _streams.get(key)
54
+ if wrapper is None:
55
+ factory = getattr(cupy.cuda.Stream, "from_external", None)
56
+ wrapper = factory(stream) if factory else cupy.cuda.ExternalStream(key[1])
57
+ _streams[key] = wrapper
58
+ return wrapper
59
+
60
+
61
+ def pointer(tensor: torch.Tensor) -> np.uint64:
62
+ return np.uint64(tensor.data_ptr())
63
+
64
+
65
+ def device_index(target) -> int:
66
+ if isinstance(target, int):
67
+ return target
68
+ device = target.device if isinstance(target, torch.Tensor) else target
69
+ index = getattr(device, "index", None)
70
+ return torch.cuda.current_device() if index is None else index
@@ -0,0 +1,4 @@
1
+ from .api import CompressionFallbackWarning, compress, decompress
2
+ from .compressed_tensor import CompressedTensor
3
+
4
+ __all__ = ["CompressedTensor", "CompressionFallbackWarning", "compress", "decompress"]
@@ -0,0 +1,76 @@
1
+ import logging
2
+ import warnings
3
+
4
+ import torch
5
+
6
+ from ..registry import DispatchError, get_scheme, name_for_config
7
+ from ..schemes import CompressionConfig, RawConfig
8
+ from ..schemes.config import validate_config
9
+ from .compressed_tensor import CompressedTensor
10
+
11
+ logger = logging.getLogger("entropack")
12
+
13
+
14
+ class CompressionFallbackWarning(RuntimeWarning):
15
+ """Warning emitted when compression falls back to storing the tensor uncompressed."""
16
+
17
+
18
+ def compress(tensor: torch.Tensor, config: CompressionConfig) -> CompressedTensor:
19
+ """Compress a tensor using the selected scheme.
20
+
21
+ Args:
22
+ tensor: input values. The container preserves the input shape, dtype, and device.
23
+ config: configuration selecting the compression scheme and execution backend.
24
+
25
+ Returns:
26
+ A :class:`CompressedTensor` containing the encoded data and metadata.
27
+
28
+ Encoding failures can return an uncompressed ``raw`` container with a
29
+ :class:`CompressionFallbackWarning`. Its header records the requested scheme and
30
+ failure reason. Invalid configurations and backend dispatch failures raise errors.
31
+ """
32
+ if isinstance(tensor, CompressedTensor):
33
+ raise TypeError("compress expects an uncompressed tensor; decompress the container before recompressing")
34
+ scheme = get_scheme(name_for_config(config))
35
+ validate_config(config)
36
+ try:
37
+ packed = scheme.encode(tensor, config)
38
+ except DispatchError:
39
+ raise
40
+ except Exception as error:
41
+ logger.warning(
42
+ "%s could not encode a %s %s tensor, storing it uncompressed: %s",
43
+ scheme.name, tuple(tensor.shape), tensor.dtype, error,
44
+ )
45
+ return _compress_raw(tensor, scheme.name, error)
46
+ return CompressedTensor(header={"compress_method": scheme.name}, buffers=packed, shape=tuple(tensor.shape), dtype=tensor.dtype)
47
+
48
+
49
+ def _compress_raw(tensor: torch.Tensor, requested: str, error: Exception) -> CompressedTensor:
50
+ reason = f"{type(error).__name__}: {error}"
51
+ warnings.warn(
52
+ f"entropack stored a {tuple(tensor.shape)} {tensor.dtype} tensor uncompressed, at "
53
+ f"{tensor.element_size() * 8} bits per element: compress_method={requested!r} failed with {reason}",
54
+ CompressionFallbackWarning, stacklevel=3,
55
+ )
56
+ return CompressedTensor(
57
+ header={"compress_method": "raw", "requested": requested, "reason": reason},
58
+ buffers=get_scheme("raw").encode(tensor, RawConfig()), shape=tuple(tensor.shape), dtype=tensor.dtype,
59
+ )
60
+
61
+
62
+ def decompress(compressed: CompressedTensor, config: CompressionConfig) -> torch.Tensor:
63
+ """Restore a tensor from its compressed representation.
64
+
65
+ Args:
66
+ compressed: container to decode. Its header identifies the compression scheme.
67
+ config: configuration for the same scheme. Decode settings select the backend and
68
+ execution options. Encode settings such as the target bitrate do not recompress
69
+ the stored data.
70
+
71
+ Returns:
72
+ A tensor with the container's shape and dtype, on the device holding its buffers.
73
+ """
74
+ validate_config(config)
75
+ restored = compressed.scheme.decode(compressed.buffers, shape=compressed.shape, dtype=compressed.encoded_dtype, config=config)
76
+ return restored.to(compressed.dtype)
@@ -0,0 +1,205 @@
1
+ import copy
2
+ import json
3
+ import math
4
+ from collections.abc import Mapping
5
+ from typing import Any
6
+
7
+ import torch
8
+ from torch.utils._python_dispatch import return_and_correct_aliasing
9
+
10
+ from ..registry import get_scheme
11
+ from ..schemes import Scheme
12
+
13
+
14
+ def _parse_dtype(name: str) -> torch.dtype:
15
+ if not name:
16
+ raise TypeError("CompressedTensor serialized dtype must be a non-empty string")
17
+ dtype = getattr(torch, name, None)
18
+ if not isinstance(dtype, torch.dtype):
19
+ raise ValueError(f"Unsupported serialized torch dtype '{name}'")
20
+ return dtype
21
+
22
+
23
+ class CompressedTensor(torch.Tensor):
24
+ """Frozen compressed tensor with a logical output dtype.
25
+
26
+ ``encoded_dtype`` records the codec's input type. ``to(dtype=...)`` changes
27
+ the output dtype without casting packed buffers or re-encoding values.
28
+ """
29
+
30
+ header: dict[str, Any]
31
+ buffers: dict[str, torch.Tensor]
32
+
33
+ @staticmethod
34
+ def __new__(cls, header, buffers, shape, dtype, *, encoded_dtype=None):
35
+ device = next(iter(buffers.values())).device
36
+ return torch.Tensor._make_wrapper_subclass(cls, tuple(shape), dtype=dtype, device=device, requires_grad=False)
37
+
38
+ def __init__(self, header, buffers, shape, dtype, *, encoded_dtype=None):
39
+ self.header = copy.deepcopy(header)
40
+ self.buffers = dict(buffers)
41
+ self.encoded_dtype = dtype if encoded_dtype is None else encoded_dtype
42
+ self.validate()
43
+
44
+ def __repr__(self) -> str:
45
+ return f"{type(self).__name__}(shape={tuple(self.shape)}, dtype={self.dtype}, device={self.device}, scheme={self.compress_method!r})"
46
+
47
+ def __tensor_flatten__(self):
48
+ names = tuple(self.buffers)
49
+ for name, value in self.buffers.items():
50
+ setattr(self, "_packed_" + name, value)
51
+ metadata = (names, copy.deepcopy(self.header), tuple(self.shape), self.dtype, self.encoded_dtype)
52
+ return ["_packed_" + name for name in names], metadata
53
+
54
+ @classmethod
55
+ def __tensor_unflatten__(cls, inner_tensors, metadata, outer_size, outer_stride):
56
+ names, header, shape, dtype, encoded_dtype = metadata
57
+ return cls(
58
+ header=header, buffers={name: inner_tensors["_packed_" + name] for name in names},
59
+ shape=shape, dtype=dtype, encoded_dtype=encoded_dtype,
60
+ )
61
+
62
+ def _map_buffers(self, fn, *, dtype=None) -> "CompressedTensor":
63
+ return type(self)(
64
+ header=self.header, buffers={name: fn(value) for name, value in self.buffers.items()},
65
+ shape=self.shape, dtype=self.dtype if dtype is None else dtype, encoded_dtype=self.encoded_dtype,
66
+ )
67
+
68
+ def to(self, *args, copy: bool = False, **kwargs) -> "CompressedTensor":
69
+ """Move buffers or change the logical dtype, with an optional keyword-only copy."""
70
+ device, dtype, non_blocking, memory_format = torch._C._nn._parse_to(*args, **kwargs)
71
+ result = super().to(device=device, dtype=dtype, non_blocking=non_blocking, copy=copy, memory_format=memory_format)
72
+ return result.clone() if copy and result.device == self.device else result
73
+
74
+ def __copy__(self) -> "CompressedTensor":
75
+ result = self._map_buffers(lambda value: value)
76
+ if getattr(self, "_is_param", False):
77
+ result._is_param = True
78
+ return result
79
+
80
+ def __deepcopy__(self, memo) -> "CompressedTensor":
81
+ if id(self) in memo:
82
+ return memo[id(self)]
83
+ result = self._map_buffers(lambda value: copy.deepcopy(value, memo))
84
+ if getattr(self, "_is_param", False):
85
+ result._is_param = True
86
+ memo[id(self)] = result
87
+ return result
88
+
89
+ def requires_grad_(self, requires_grad: bool = False):
90
+ """Compressed values are frozen; gradients may flow through their consumers."""
91
+ if requires_grad:
92
+ raise RuntimeError("CompressedTensor cannot require gradients; decompress it to train dense values")
93
+ return torch.Tensor.requires_grad_(self, False)
94
+
95
+ @classmethod
96
+ def __torch_dispatch__(cls, func, types, args=(), kwargs=None):
97
+ kwargs = kwargs or {}
98
+ source = args[0] if args else None
99
+ aten = torch.ops.aten
100
+
101
+ if func in (aten.detach.default, aten.alias.default):
102
+ result = source._map_buffers(torch.detach)
103
+ return return_and_correct_aliasing(func, args, kwargs, result)
104
+
105
+ if func in (aten.clone.default, aten._to_copy.default):
106
+ if kwargs.get("memory_format") not in (None, torch.preserve_format, torch.contiguous_format):
107
+ raise ValueError("CompressedTensor does not support that memory format")
108
+ if func == aten.clone.default:
109
+ return source._map_buffers(torch.clone)
110
+ if kwargs.get("layout", torch.strided) not in (None, torch.strided):
111
+ raise ValueError("CompressedTensor only supports strided storage")
112
+ options = {name: value for name, value in kwargs.items() if name in ("device", "non_blocking")}
113
+ return source._map_buffers(lambda value: value.to(**options), dtype=kwargs.get("dtype"))
114
+
115
+ result = func.decompose(*args, **kwargs)
116
+ if result is not NotImplemented:
117
+ return result
118
+ raise NotImplementedError(f"CompressedTensor does not implement {func}; decompress it before numerical operations")
119
+
120
+ @property
121
+ def compress_method(self) -> str:
122
+ """The scheme name the header carries."""
123
+ return self.header["compress_method"]
124
+
125
+ @property
126
+ def scheme(self) -> Scheme:
127
+ """The codec named by the header."""
128
+ return get_scheme(self.compress_method)
129
+
130
+ @property
131
+ def lossless(self) -> bool:
132
+ """Whether the encoding scheme is lossless, before any output dtype conversion."""
133
+ return self.scheme.lossless
134
+
135
+ @property
136
+ def actual_bpp(self) -> float:
137
+ """Bits per element of :attr:`shape`, serialized header included."""
138
+ return self.storage_nbytes() * 8 / math.prod(self.shape)
139
+
140
+ def validate(self) -> None:
141
+ """Check the scheme, dtype, and buffer names without inspecting contents."""
142
+ if any(isinstance(value, CompressedTensor) for value in self.buffers.values()):
143
+ raise TypeError("CompressedTensor buffers cannot contain another CompressedTensor")
144
+ scheme = self.scheme
145
+ if not scheme.supports(self.encoded_dtype):
146
+ raise ValueError(f"'{scheme.name}' does not support format {self.encoded_dtype}")
147
+ if set(self.buffers) != set(scheme.buffer_names):
148
+ raise ValueError(
149
+ f"CompressedTensor buffers for {self.compress_method} must be "
150
+ f"{list(scheme.buffer_names)}, got {sorted(self.buffers)}"
151
+ )
152
+
153
+ def _serialized_header_tensor(self) -> torch.Tensor:
154
+ metadata = {
155
+ "compress_method": self.compress_method, "dtype": str(self.encoded_dtype).removeprefix("torch."),
156
+ "shape": list(self.shape), "buffer_names": list(self.buffers), "header": self.header,
157
+ }
158
+ encoded = json.dumps(metadata, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8")
159
+ return torch.frombuffer(bytearray(encoded), dtype=torch.uint8)
160
+
161
+ def storage_nbytes(self, include_header: bool = True) -> int:
162
+ """Bytes the buffers occupy, plus the serialized header unless ``include_header`` is false."""
163
+ total = sum(value.numel() * value.element_size() for value in self.buffers.values())
164
+ if include_header:
165
+ total += self._serialized_header_tensor().numel()
166
+ return total
167
+
168
+ def state_dict(self, prefix: str = "") -> dict[str, torch.Tensor]:
169
+ """The container as flat tensors under ``prefix``: one 1D uint8 header, then one entry per buffer."""
170
+ self.validate()
171
+ state = {f"{prefix}header": self._serialized_header_tensor()}
172
+ state.update({f"{prefix}buffers.{name}": value.detach() for name, value in self.buffers.items()})
173
+ return state
174
+
175
+ @classmethod
176
+ def from_state_dict(cls, state: Mapping[str, torch.Tensor], prefix: str = "") -> "CompressedTensor":
177
+ """Restore a container in its encoded dtype from a tensor-only checkpoint."""
178
+ header_key = f"{prefix}header"
179
+ if header_key not in state:
180
+ raise ValueError(f"CompressedTensor state is missing '{header_key}'")
181
+ header_tensor = state[header_key]
182
+ if header_tensor.dtype != torch.uint8 or header_tensor.ndim != 1:
183
+ raise TypeError("CompressedTensor serialized header must be a 1D uint8 tensor")
184
+ try:
185
+ metadata = json.loads(bytes(header_tensor.detach().cpu().tolist()).decode("utf-8"))
186
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
187
+ raise ValueError("CompressedTensor serialized header is invalid") from error
188
+ if not isinstance(metadata, dict):
189
+ raise ValueError("CompressedTensor serialized header must contain a JSON object")
190
+
191
+ required = {"compress_method", "dtype", "shape", "buffer_names", "header"}
192
+ missing = required - metadata.keys()
193
+ if missing:
194
+ raise ValueError(f"CompressedTensor serialized header is missing fields: {sorted(missing)}")
195
+ buffer_names = metadata["buffer_names"]
196
+ if not isinstance(buffer_names, list) or any(not isinstance(name, str) or not name for name in buffer_names):
197
+ raise TypeError("CompressedTensor buffer_names must be a list of strings")
198
+ buffers = {}
199
+ for name in buffer_names:
200
+ key = f"{prefix}buffers.{name}"
201
+ if key not in state:
202
+ raise ValueError(f"CompressedTensor state is missing '{key}'")
203
+ buffers[name] = state[key]
204
+
205
+ return cls(header=metadata["header"], buffers=buffers, shape=tuple(metadata["shape"]), dtype=_parse_dtype(metadata["dtype"]))
@@ -0,0 +1,5 @@
1
+ from .linear import CompressedFP8Linear, CompressedINT8Linear, CompressedLinear, QuantizedLinear
2
+
3
+ __all__ = [
4
+ "CompressedFP8Linear", "CompressedINT8Linear", "CompressedLinear", "QuantizedLinear",
5
+ ]