| """Packed on-disk storage for LTX-2.5 encoder precision plans. |
| |
| `ltx_fake_quantize` and `ltx_gptq` store quantized values expanded back to |
| BF16, so a 4.5-bit plan still costs 26 GB on disk. This module is the missing |
| second half: the same values in their native widths. |
| |
| The format is one safetensors file. A raw tensor keeps its checkpoint name. A |
| quantized tensor is split into named parts: |
| |
| * nvfp4 - ``{name}::nvfp4_codes`` (uint8, two 4-bit codes per byte, even |
| column in the low nibble; a code is ``sign << 3 | magnitude`` indexing |
| ``E2M1_LEVELS``), ``{name}::nvfp4_group_scale`` (float8_e4m3fn, |
| [out, in/16]), ``{name}::nvfp4_global_scale`` (float32 scalar), and - when |
| the GPTQ path smoothed the tensor - ``{name}::pre_quant_scale`` (float32, |
| [in], the AWQ scale the stored weight was *divided* by). |
| * int8 - ``{name}::int8`` and ``{name}::int8_scale`` (float32, [out]). |
| |
| Exactness is the design constraint, not an aspiration: unpacking replays the |
| producers' arithmetic - ``(sign * level) * effective`` then the pre-scale |
| division, in float32, cast to the stored dtype last - so a packed tensor |
| dequantizes to the same values the BF16 fake-quant file would have carried. |
| The one tolerated difference is that int8 cannot store a negative zero, so a |
| ``-0.0`` produced by ``round()`` collapses to ``+0.0``; ``torch.equal`` treats |
| the two as equal and the packer counts them rather than hiding them. |
| |
| Group scales are recorded as the exact float8 bytes the quantizer used, not |
| recomputed from the output. Recomputing them is impossible in general: the |
| GPTQ column loop derives each group's scale from weights that were already |
| compensated, and the AWQ division afterwards destroys the grid alignment that |
| recovery would need. That is why packing happens inside the build |
| (`ltx_gptq --packed-output`) instead of as a post-pass over the artifact. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import sys |
| from pathlib import Path |
|
|
| import torch |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parent)) |
|
|
| |
| |
| |
| E2M1_LEVELS = (0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0) |
| E2M1_MIDPOINTS = (0.25, 0.75, 1.25, 1.75, 2.5, 3.5, 5.0) |
| NVFP4_GROUP = 16 |
| |
| |
| E4M3_MAX = 448.0 |
| GPU_TENSOR_LIMIT = 1_000_000_000 |
|
|
| |
| |
| |
| |
| |
| |
| LTX_RENAMES = [ |
| (r"^model\.", "language_model."), |
| (r"^vision_model\.", "embed_vision."), |
| (r"^audio_projector\.", "embed_audio."), |
| (r"^multi_modal_projector\.embedding_projection", |
| "embed_vision.multimodal_embedder.embedding_projection"), |
| ] |
|
|
|
|
| def register_ltx_renames() -> None: |
| from transformers.conversion_mapping import ( |
| get_checkpoint_conversion_mapping, |
| register_checkpoint_conversion_mapping, |
| ) |
| from transformers.core_model_loading import WeightRenaming |
|
|
| existing = get_checkpoint_conversion_mapping("gemma4_unified") or [] |
| rules = [WeightRenaming(source_patterns=s, target_patterns=t) |
| for s, t in LTX_RENAMES] |
| register_checkpoint_conversion_mapping( |
| "gemma4_unified", rules + existing, overwrite=True) |
| |
|
|
| FORMAT_VERSION = "haverbex-packed-v1" |
|
|
| |
| |
| CODES = "::nvfp4_codes" |
| GROUP_SCALE = "::nvfp4_group_scale" |
| GLOBAL_SCALE = "::nvfp4_global_scale" |
| PRE_SCALE = "::pre_quant_scale" |
| INT8 = "::int8" |
| INT8_SCALE = "::int8_scale" |
|
|
| _SUFFIXES = (CODES, GROUP_SCALE, GLOBAL_SCALE, PRE_SCALE, INT8, INT8_SCALE) |
|
|
| |
| |
| _ROW_SLICEABLE = (CODES, GROUP_SCALE, INT8, INT8_SCALE) |
|
|
| |
| |
| _BUFFER_NAMES = {CODES: "codes", GROUP_SCALE: "group_scale", |
| GLOBAL_SCALE: "global_scale", PRE_SCALE: "pre_scale", |
| INT8: "ints", INT8_SCALE: "int8_scale"} |
|
|
|
|
| def nibble_pack(codes: torch.Tensor) -> torch.Tensor: |
| """[out, in] uint8 (values 0..15) -> [out, in/2], even column low nibble.""" |
| if codes.shape[-1] % 2: |
| raise ValueError(f"odd inner width {codes.shape[-1]} cannot nibble-pack") |
| return codes[..., 0::2] | (codes[..., 1::2] << 4) |
|
|
|
|
| def nibble_unpack(packed: torch.Tensor) -> torch.Tensor: |
| out = torch.empty(*packed.shape[:-1], packed.shape[-1] * 2, dtype=torch.uint8, |
| device=packed.device) |
| out[..., 0::2] = packed & 0x0F |
| out[..., 1::2] = packed >> 4 |
| return out |
|
|
|
|
| def encode_nvfp4_column(normalized: torch.Tensor, codes: torch.Tensor) -> torch.Tensor: |
| """4-bit code for one already-bucketized column: sign bit over magnitude. |
| |
| `torch.sign` maps an exact zero to 0, which multiplies out to ``+0.0``; the |
| encoding gives it sign bit 0 so decode lands on the same ``+0.0``. |
| """ |
| return codes.to(torch.uint8) | ((normalized < 0).to(torch.uint8) << 3) |
|
|
|
|
| def decode_nvfp4(codes: torch.Tensor, group_scale: torch.Tensor, |
| global_scale: torch.Tensor, |
| pre_scale: torch.Tensor | None) -> torch.Tensor: |
| """Replay of `NVFP4Column.quantize` / `quantize_nvfp4`, in float32. |
| |
| `levels` is built on the codes' device: at load time everything is on the |
| CPU, but a `PackedLinear` decodes wherever its weight lives. |
| """ |
| levels = torch.tensor(E2M1_LEVELS, dtype=torch.float32, device=codes.device) |
| sign = torch.where((codes & 0x8) != 0, -1.0, 1.0) |
| magnitude = levels[(codes & 0x7).long()] |
|
|
| effective = group_scale.float() * global_scale.float() |
| effective = torch.where(effective > 0, effective, torch.ones_like(effective)) |
| out_features, in_features = codes.shape |
| grid = (sign * magnitude).reshape(out_features, in_features // NVFP4_GROUP, |
| NVFP4_GROUP) |
| grid = (grid * effective.unsqueeze(-1)).reshape(out_features, in_features) |
| if pre_scale is not None: |
| grid = grid / pre_scale.float() |
| return grid |
|
|
|
|
| def decode_int8(ints: torch.Tensor, scale: torch.Tensor, |
| pre_scale: torch.Tensor | None) -> torch.Tensor: |
| """Replay of `quantize_int8` / `Int8Column.quantize`, in float32.""" |
| grid = ints.float() * scale.float().unsqueeze(-1) |
| if pre_scale is not None: |
| grid = grid / pre_scale.float() |
| return grid |
|
|
|
|
| def pack_rtn_nvfp4(w: torch.Tensor) -> tuple[torch.Tensor, dict]: |
| """`ltx_fake_quantize.quantize_nvfp4` with the codes and scales kept. |
| |
| Returns the dequantized float32 tensor (identical to what the fake |
| quantizer produces) and the packed parts. |
| """ |
| out_features, in_features = w.shape |
| if in_features % NVFP4_GROUP: |
| raise ValueError(f"nvfp4 needs a multiple of {NVFP4_GROUP}, got {in_features}") |
| amax = w.abs().amax() |
| if amax == 0: |
| raise ValueError("all-zero tensor should be stored raw, not packed") |
| global_scale = amax / (E2M1_LEVELS[-1] * E4M3_MAX) |
|
|
| groups = w.reshape(out_features, in_features // NVFP4_GROUP, NVFP4_GROUP) |
| group_amax = groups.abs().amax(dim=-1, keepdim=True) |
| scale = (group_amax / E2M1_LEVELS[-1] / global_scale).to(torch.float8_e4m3fn) |
| effective = scale.float() * global_scale |
| effective = torch.where(effective > 0, effective, torch.ones_like(effective)) |
|
|
| levels = torch.tensor(E2M1_LEVELS, device=w.device, dtype=w.dtype) |
| midpoints = torch.tensor(E2M1_MIDPOINTS, device=w.device, dtype=w.dtype) |
| normalized = groups / effective |
| codes = torch.bucketize(normalized.abs(), midpoints, out_int32=True) |
| dequant = (torch.sign(normalized) * levels[codes] * effective).reshape( |
| out_features, in_features) |
|
|
| packed_codes = (codes.to(torch.uint8) |
| | ((normalized < 0).to(torch.uint8) << 3)).reshape( |
| out_features, in_features) |
| parts = { |
| CODES: nibble_pack(packed_codes).cpu(), |
| GROUP_SCALE: scale.squeeze(-1).cpu(), |
| GLOBAL_SCALE: global_scale.detach().float().reshape(1).cpu(), |
| } |
| return dequant, parts |
|
|
|
|
| def pack_rtn(writer: PackWriter, name: str, tensor: torch.Tensor, |
| width: float, device: str) -> torch.Tensor: |
| """`ltx_fake_quantize.quantize_tensor`, but keeping the codes. |
| |
| Returns the fake-quantized tensor in the original dtype so the caller's |
| BF16 write path is unchanged. Same device policy as the original: a tensor |
| whose float32 form exceeds `GPU_TENSOR_LIMIT` is quantized on the CPU. |
| """ |
| original = tensor.dtype |
| where = "cpu" if tensor.numel() * 4 > GPU_TENSOR_LIMIT else device |
| w = tensor.to(where, torch.float32) |
| if width == 4.5: |
| if w.abs().amax() == 0: |
| |
| writer.store_raw(name, tensor) |
| return tensor |
| dequant, parts = pack_rtn_nvfp4(w) |
| result = dequant.to("cpu", original) |
| writer.store_nvfp4(name, parts, result) |
| else: |
| dequant, parts = pack_rtn_int8(w) |
| result = dequant.to("cpu", original) |
| writer.store_int8(name, parts, result) |
| return result |
|
|
|
|
| def pack_rtn_int8(w: torch.Tensor) -> tuple[torch.Tensor, dict]: |
| """`ltx_fake_quantize.quantize_int8` with the integers kept.""" |
| scale = w.abs().amax(dim=-1, keepdim=True) / 127.0 |
| scale = torch.where(scale > 0, scale, torch.ones_like(scale)) |
| ints = (w / scale).round().clamp_(-127, 127) |
| dequant = ints * scale |
| parts = { |
| INT8: ints.to(torch.int8).cpu(), |
| INT8_SCALE: scale.squeeze(-1).float().cpu(), |
| } |
| return dequant, parts |
|
|
|
|
| class PackWriter: |
| """Accumulates packed parts and raw tensors, then writes one file. |
| |
| Held in RAM rather than streamed: the whole point of the format is that |
| the payload is ~8.5 GB, which fits beside the build. Every quantized store |
| verifies round-trip equality against the reference tensor before |
| accepting it - a packed file that does not reproduce its builder's values |
| must not be creatable through this class. |
| """ |
|
|
| def __init__(self) -> None: |
| self.tensors: dict[str, torch.Tensor] = {} |
| self.kinds: dict[str, str] = {} |
| self.negative_zero_collapses = 0 |
|
|
| def store_raw(self, name: str, tensor: torch.Tensor) -> None: |
| if any(s in name for s in _SUFFIXES): |
| raise ValueError(f"raw name collides with a part suffix: {name}") |
| self.tensors[name] = tensor.detach().contiguous().cpu() |
| self.kinds[name] = "raw" |
|
|
| def _verify(self, name: str, reference: torch.Tensor) -> None: |
| got = unpack_tensor(self.tensors, name, self.kinds[name], |
| reference.dtype) |
| if not torch.equal(got, reference.cpu()): |
| diff = (got.float() - reference.float().cpu()).abs() |
| raise SystemExit( |
| f"packed round-trip mismatch on {name}: max {diff.max():.3e} at " |
| f"{int(diff.argmax())} - refusing to write a lossy pack") |
| signs = got.signbit() != reference.cpu().signbit() |
| self.negative_zero_collapses += int(signs.sum()) |
|
|
| def store_nvfp4(self, name: str, parts: dict, reference: torch.Tensor, |
| pre_scale: torch.Tensor | None = None) -> None: |
| for suffix, tensor in parts.items(): |
| self.tensors[name + suffix] = tensor.contiguous() |
| if pre_scale is not None: |
| self.tensors[name + PRE_SCALE] = pre_scale.detach().float().cpu() |
| self.kinds[name] = "nvfp4" |
| self._verify(name, reference) |
|
|
| def store_int8(self, name: str, parts: dict, reference: torch.Tensor, |
| pre_scale: torch.Tensor | None = None) -> None: |
| for suffix, tensor in parts.items(): |
| self.tensors[name + suffix] = tensor.contiguous() |
| if pre_scale is not None: |
| self.tensors[name + PRE_SCALE] = pre_scale.detach().float().cpu() |
| self.kinds[name] = "int8" |
| self._verify(name, reference) |
|
|
| def save(self, path: Path, metadata: dict[str, str]) -> None: |
| from safetensors.torch import save_file |
| payload = { |
| "format": FORMAT_VERSION, |
| "kinds": json.dumps(self.kinds, separators=(",", ":")), |
| "negative_zero_collapses": str(self.negative_zero_collapses), |
| **metadata, |
| } |
| path.parent.mkdir(parents=True, exist_ok=True) |
| save_file(self.tensors, str(path), metadata=payload) |
|
|
|
|
| def unpack_tensor(tensors: dict, name: str, kind: str, |
| dtype: torch.dtype) -> torch.Tensor: |
| if kind == "raw": |
| return tensors[name] |
| pre = tensors.get(name + PRE_SCALE) |
| if kind == "nvfp4": |
| grid = decode_nvfp4(nibble_unpack(tensors[name + CODES]), |
| tensors[name + GROUP_SCALE], |
| tensors[name + GLOBAL_SCALE], |
| pre) |
| elif kind == "int8": |
| grid = decode_int8(tensors[name + INT8], tensors[name + INT8_SCALE], pre) |
| else: |
| raise ValueError(f"unknown kind {kind!r} for {name}") |
| return grid.to(dtype) |
|
|
|
|
| class PackedCheckpoint: |
| """Read-side API mirroring `safetensors.safe_open` for packed files.""" |
|
|
| def __init__(self, path: Path | str) -> None: |
| from safetensors import safe_open |
| self.path = Path(path) |
| self._file = safe_open(str(self.path), framework="pt") |
| self.metadata = self._file.metadata() or {} |
| if self.metadata.get("format") != FORMAT_VERSION: |
| raise SystemExit( |
| f"{path}: format {self.metadata.get('format')!r}, " |
| f"this reader speaks {FORMAT_VERSION}") |
| self.kinds: dict[str, str] = json.loads(self.metadata["kinds"]) |
| self._present = set(self._file.keys()) |
|
|
| def __enter__(self) -> PackedCheckpoint: |
| return self |
|
|
| def __exit__(self, *exc) -> None: |
| return None |
|
|
| def keys(self) -> list[str]: |
| return list(self.kinds) |
|
|
| def get_tensor(self, name: str, dtype: torch.dtype = torch.bfloat16): |
| kind = self.kinds[name] |
| if kind == "raw": |
| return self._file.get_tensor(name) |
| parts = {name + s: self._file.get_tensor(name + s) |
| for s in _SUFFIXES if name + s in self._present} |
| return unpack_tensor(parts, name, kind, dtype) |
|
|
| def get_parts(self, name: str) -> dict: |
| """The stored parts of a packed tensor, keyed by bare suffix, undecoded. |
| |
| What a resident module needs: the bytes as written, with no decode and |
| no reassembly. |
| """ |
| return {s: self._file.get_tensor(name + s) |
| for s in _SUFFIXES if name + s in self._present} |
|
|
| def row_count(self, name: str) -> int: |
| """Output rows of a packed tensor, without decoding it.""" |
| kind = self.kinds[name] |
| key = name if kind == "raw" else name + (CODES if kind == "nvfp4" else INT8) |
| return self._file.get_slice(key).get_shape()[0] |
|
|
| def get_tensor_rows(self, name: str, start: int, stop: int, |
| dtype: torch.dtype = torch.bfloat16): |
| """Output rows `[start, stop)` of a tensor, decoding only those rows. |
| |
| Lets a caller work through a table that does not fit beside the model - |
| the LTX aggregates are 3.08 GB each in float32. Row-sliced parts are |
| the ones indexed by output channel; the global scale is a scalar and |
| the AWQ pre-scale is per *input* channel, so both are read whole. |
| """ |
| kind = self.kinds[name] |
| if kind == "raw": |
| return self._file.get_slice(name)[start:stop].to(dtype) |
| parts = {} |
| for suffix in _SUFFIXES: |
| key = name + suffix |
| if key not in self._present: |
| continue |
| parts[key] = (self._file.get_slice(key)[start:stop] |
| if suffix in _ROW_SLICEABLE |
| else self._file.get_tensor(key)) |
| return unpack_tensor(parts, name, kind, dtype) |
|
|
|
|
| def open_maybe_packed(path: Path | str): |
| """`safe_open` for BF16 checkpoints, `PackedCheckpoint` for packed ones. |
| |
| Both expose `keys()` and `get_tensor(name)`, which is all the aggregate |
| and asset readers use. Detection is by content, not extension: a packed |
| file carries `format` metadata that a plain checkpoint does not. |
| """ |
| from safetensors import safe_open |
| handle = safe_open(str(path), framework="pt") |
| if (handle.metadata() or {}).get("format") == FORMAT_VERSION: |
| return PackedCheckpoint(path) |
| return handle |
|
|
|
|
| def install_resident(model, reader: PackedCheckpoint, wanted: dict, rename, |
| device: str | None = None, fold_pre_scale: bool = False): |
| """Replace the quantized modules of a meta-device model with packed ones. |
| |
| Two passes, because a module has to exist before its bias can be fed into |
| it: first swap every module whose weight is packed, then feed the raw |
| tensors - norms, layer scalars, and any bias - into whatever module now |
| sits at that path. |
| |
| The coverage guard is the same one the dequantized path uses, and it is |
| what makes a silent rename failure impossible: every parameter the model |
| declared must be accounted for, either by a swap or by a raw feed. |
| """ |
| from accelerate.utils import set_module_tensor_to_device |
|
|
| if device is None: |
| device = "cuda" if torch.cuda.is_available() else "cpu" |
|
|
| swapped, raw_names = set(), [] |
| for source_name in reader.keys(): |
| param_name = rename(source_name) |
| if param_name not in wanted: |
| continue |
| if reader.kinds[source_name] == "raw": |
| raw_names.append((source_name, param_name)) |
| continue |
| if not param_name.endswith(".weight"): |
| raise SystemExit( |
| f"{source_name} is packed but renames to {param_name}, which is " |
| "not a module weight; a resident build has nowhere to put it") |
| path = param_name[: -len(".weight")] |
| module = model.get_submodule(path) |
| parts = {k: v.to(device) for k, v in reader.get_parts(source_name).items()} |
|
|
| if isinstance(module, torch.nn.Embedding): |
| |
| |
| extra = set(dict(module.named_buffers()))- {"embed_scale"} |
| if type(module) is not torch.nn.Embedding and extra: |
| raise SystemExit( |
| f"{path} is a {type(module).__name__} carrying {sorted(extra)}; " |
| "PackedEmbedding only reproduces embed_scale") |
| packed = PackedEmbedding(parts[INT8], parts[INT8_SCALE], |
| embed_scale=getattr(module, "embed_scale", None), |
| padding_idx=module.padding_idx) |
| elif isinstance(module, torch.nn.Linear): |
| packed = PackedLinear(reader.kinds[source_name], parts, |
| module.out_features, module.in_features, |
| fold_pre_scale=fold_pre_scale) |
| else: |
| raise SystemExit(f"{path} is a {type(module).__name__}, and this " |
| "build only knows how to pack Linear and Embedding") |
| parent_path, _, attribute = path.rpartition(".") |
| setattr(model.get_submodule(parent_path) if parent_path else model, |
| attribute, packed) |
| swapped.add(param_name) |
|
|
| |
| |
| for source_name, param_name in raw_names: |
| value = reader.get_tensor(source_name, wanted[param_name].dtype).to(device) |
| path, _, attribute = param_name.rpartition(".") |
| owner = model.get_submodule(path) if path else model |
| if isinstance(owner, (PackedLinear, PackedEmbedding)): |
| |
| |
| owner.register_buffer(attribute, value, persistent=False) |
| else: |
| set_module_tensor_to_device(model, param_name, device, value=value) |
|
|
| assigned = swapped | {p for _, p in raw_names} |
| missing = [n for n in wanted if n not in assigned and "rotary" not in n] |
| if missing: |
| raise SystemExit( |
| f"{len(missing)} parameters were not fed from the packed file, " |
| f"e.g. {missing[:5]}. The packed names do not match the model; " |
| "extend LTX_RENAMES.") |
|
|
| model.eval() |
| print(f"resident packed model on {device}: " |
| f"{resident_bytes(model) / 2**30:.3f} GiB", flush=True) |
| return model |
|
|
|
|
| def resident_bytes(model) -> int: |
| """Everything the model holds: packed buffers plus whatever stayed dense. |
| |
| Counted once. A packed module's parts are registered buffers, so walking |
| `model.buffers()` after adding `packed_bytes()` counts them twice - which |
| is what the first A7 run reported (13.4 GiB against an allocator peak of |
| 7.75 GiB). Packed buffers are collected by identity first and skipped in |
| the dense pass. |
| """ |
| seen, total = set(), 0 |
| for module in model.modules(): |
| if isinstance(module, (PackedLinear, PackedEmbedding)): |
| total += module.packed_bytes() |
| for name in (*_BUFFER_NAMES.values(), "bias"): |
| tensor = getattr(module, name, None) |
| if tensor is not None: |
| seen.add(id(tensor)) |
| for tensor in list(model.parameters()) + list(model.buffers()): |
| if id(tensor) in seen or tensor.device.type == "meta": |
| continue |
| seen.add(id(tensor)) |
| total += tensor.numel() * tensor.element_size() |
| return total |
|
|
|
|
| def check_gpu_kernels(device: str | None = None) -> None: |
| """Refuse a wheel that has no kernels for this card, and say what to install. |
| |
| Nothing in this format needs anything unusual from a GPU - no fp8 units, no |
| minimum compute capability, no bf16 tensor cores. What can be missing is |
| PyTorch's own kernels: the current default wheel on PyPI is a cu130 build, |
| and cu130 dropped Volta. |
| |
| Left alone, that surfaces as |
| |
| CUDA error: no kernel image is available for execution on the device |
| |
| raised from the first kernel launch - which is after an 8.46 GB load, inside |
| somebody else's library, and long after `torch.cuda.is_available()` returned |
| True. Checking `get_arch_list` costs nothing and moves the failure to the |
| place where the fix makes sense. |
| """ |
| if device is not None and not str(device).startswith("cuda"): |
| return |
| if not torch.cuda.is_available(): |
| return |
|
|
| major, minor = torch.cuda.get_device_capability(0) |
| arch = f"sm_{major}{minor}" |
| compiled = torch.cuda.get_arch_list() |
| |
| if not compiled or arch in compiled: |
| return |
|
|
| name = torch.cuda.get_device_name(0) |
| raise SystemExit( |
| f"this torch ({torch.__version__}) has no kernels for {name} ({arch}).\n" |
| f"It was built for {', '.join(compiled)}, and the first CUDA op would " |
| f"fail with 'no kernel image is available for execution on the device'.\n" |
| f"The model is fine - it needs no custom kernels. Install a torch built " |
| f"for your card, e.g. for {arch}:\n" |
| f" pip install torch --index-url https://download.pytorch.org/whl/cu128\n" |
| f"or pass device='cpu' to load without touching the GPU.") |
|
|
|
|
| def load_packed_model(model_dir: str, packed_path: Path | str, |
| gpu_budget: str | None = None, resident: bool = False, |
| fold_pre_scale: bool = False, device: str | None = None): |
| """Build the encoder from a packed file, split across GPU and CPU. |
| |
| Mirrors what `AutoModel.from_pretrained(..., device_map="auto")` does for |
| the BF16 checkpoint: same conversion-mapping renames, same guard that |
| every parameter was actually fed from the file. Dequantization happens |
| tensor-by-tensor, so peak memory is one dequantized tensor above the |
| final footprint - the 26 GB BF16 file never exists. |
| |
| Note what the default does and does not save. The weights land as BF16, so |
| the *resident* footprint is the same 26 GB the BF16 checkpoint would take; |
| what packing buys there is disk and the load-time peak. `gpu_budget` |
| defaults to 13 GiB - what is left of a 16 GB card - and `LTX_PACKED_GPU_BUDGET` |
| raises it on a larger card, where the whole encoder fits and the CPU |
| offload that budget forces is pure slowdown. |
| |
| With `resident`, the quantized tensors stay in their native widths behind |
| `PackedLinear` / `PackedEmbedding` and no device map is needed: the model |
| costs what the file costs. `fold_pre_scale` is passed through to the |
| linears and is not bit-identical - see `PackedLinear`. |
| """ |
| import os |
| import re |
|
|
| check_gpu_kernels(device) |
|
|
| if gpu_budget is None: |
| gpu_budget = os.environ.get("LTX_PACKED_GPU_BUDGET", "13GiB") |
|
|
| from accelerate import infer_auto_device_map, init_empty_weights |
| from accelerate.utils import set_module_tensor_to_device |
| from transformers import AutoConfig, AutoModel |
| from transformers.conversion_mapping import get_checkpoint_conversion_mapping |
|
|
| register_ltx_renames() |
| rules = get_checkpoint_conversion_mapping("gemma4_unified") or [] |
|
|
| def rename(name: str) -> str: |
| for rule in rules: |
| sources = rule.source_patterns |
| targets = rule.target_patterns |
| if isinstance(sources, str): |
| sources, targets = [sources], [targets] |
| for source, target in zip(sources, targets, strict=True): |
| new = re.sub(source, target, name) |
| if new != name: |
| return new |
| return name |
|
|
| config = AutoConfig.from_pretrained(model_dir) |
| |
| |
| |
| config._attn_implementation = "sdpa" |
| if hasattr(config, "text_config"): |
| config.text_config._attn_implementation = "sdpa" |
| with init_empty_weights(): |
| model = AutoModel.from_config(config) |
| model = model.to(torch.bfloat16) |
|
|
| reader = PackedCheckpoint(packed_path) |
| wanted = dict(model.state_dict()) |
| if resident: |
| return install_resident(model, reader, wanted, rename, |
| device=device, fold_pre_scale=fold_pre_scale) |
|
|
| device_map = infer_auto_device_map( |
| model, max_memory={0: gpu_budget, "cpu": "40GiB"}, |
| dtype=torch.bfloat16, no_split_module_classes=["Gemma4UnifiedTextDecoderLayer"]) |
|
|
| |
| |
| |
| |
| placements = set(device_map.values()) |
| if len(placements) > 1: |
| raise SystemExit( |
| f"the packed model does not fit in {gpu_budget} and would be split " |
| |
| |
| |
| f"across {sorted(map(str, placements))}. This loader dispatches no " |
| "hooks, so a " |
| "split model raises mid-forward. Raise LTX_PACKED_GPU_BUDGET, or use " |
| "resident=True, which needs only what the file costs.") |
|
|
| def target_device(param_name: str): |
| candidate = param_name |
| while candidate: |
| if candidate in device_map: |
| return device_map[candidate] |
| candidate = candidate.rsplit(".", 1)[0] if "." in candidate else "" |
| return device_map.get("", "cpu") |
|
|
| assigned = set() |
| for source_name in reader.keys(): |
| param_name = rename(source_name) |
| if param_name not in wanted: |
| continue |
| value = reader.get_tensor(source_name, wanted[param_name].dtype) |
| set_module_tensor_to_device(model, param_name, target_device(param_name), |
| value=value) |
| assigned.add(param_name) |
|
|
| missing = [name for name in wanted |
| if name not in assigned and "rotary" not in name] |
| if missing: |
| raise SystemExit( |
| f"{len(missing)} parameters were not fed from {packed_path}, " |
| f"e.g. {missing[:5]}. The packed names do not match the model; " |
| "extend LTX_RENAMES.") |
| model.eval() |
| return model |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| class PackedLinear(torch.nn.Module): |
| """`nn.Linear` whose weight is stored in its native widths. |
| |
| `forward` rebuilds the weight with the same arithmetic `unpack_tensor` uses |
| at load time, so its output is bit-identical to the dequantized model's. |
| That equality is the point: without it a resident run cannot be compared |
| against any figure recorded from the dequantized path. |
| |
| `fold_pre_scale` trades the equality for speed. AWQ smoothing is |
| ``W' = W * s`` and the stored weight carries the closing ``/ s``, so |
| ``x @ (Q/s).T == (x/s) @ Q.T`` - the division can move onto the activation |
| and shrink an ``[out, in]`` elementwise pass to a ``[tokens, in]`` one. It |
| changes float rounding order, so it is off by default and its difference is |
| measured rather than assumed. |
| """ |
|
|
| def __init__(self, kind: str, parts: dict, out_features: int, |
| in_features: int, bias: torch.Tensor | None = None, |
| dtype: torch.dtype = torch.bfloat16, |
| fold_pre_scale: bool = False) -> None: |
| super().__init__() |
| if kind not in ("nvfp4", "int8"): |
| raise ValueError(f"{kind!r} is not a packed weight kind") |
| self.kind = kind |
| self.out_features = out_features |
| self.in_features = in_features |
| self.compute_dtype = dtype |
|
|
| for suffix, buffer in _BUFFER_NAMES.items(): |
| tensor = parts.get(name_part(suffix, parts)) |
| self.register_buffer(buffer, tensor, persistent=False) |
| self.register_buffer("bias", None if bias is None else bias.detach(), |
| persistent=False) |
| self.fold_pre_scale = bool(fold_pre_scale) and self.pre_scale is not None |
|
|
| def packed_bytes(self) -> int: |
| """What this module actually costs, for a device map that cannot infer it.""" |
| total = 0 |
| for buffer in (*_BUFFER_NAMES.values(), "bias"): |
| tensor = getattr(self, buffer, None) |
| if tensor is not None: |
| total += tensor.numel() * tensor.element_size() |
| return total |
|
|
| @property |
| def weight(self) -> torch.Tensor: |
| """The dequantized weight. |
| |
| A property rather than a buffer so that code reaching for |
| `module.weight` - `transformers` does, in places - still works, at the |
| cost of materializing it for that one call. |
| """ |
| return self.dequantize() |
|
|
| def dequantize(self, pre_scale: bool = True) -> torch.Tensor: |
| pre = self.pre_scale if (pre_scale and not self.fold_pre_scale) else None |
| if self.kind == "nvfp4": |
| grid = decode_nvfp4(nibble_unpack(self.codes), self.group_scale, |
| self.global_scale, pre) |
| else: |
| grid = decode_int8(self.ints, self.int8_scale, pre) |
| return grid.to(self.compute_dtype) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| if self.fold_pre_scale: |
| x = x / self.pre_scale.to(x.dtype) |
| return torch.nn.functional.linear(x, self.dequantize(), self.bias) |
|
|
| def extra_repr(self) -> str: |
| return (f"in_features={self.in_features}, out_features={self.out_features}, " |
| f"kind={self.kind}, packed={self.packed_bytes() / 2**20:.1f} MiB" |
| + (", folded" if self.fold_pre_scale else "")) |
|
|
|
|
| class PackedEmbedding(torch.nn.Module): |
| """`nn.Embedding` over int8 rows with a per-row scale. |
| |
| `model.embed_tokens` is [262144, 3840] - 1.008 GB packed against 2.016 GB |
| BF16, the largest single tensor in the file, and the one DmitryDB's recipe |
| keeps in BF16 as a precision island. Quantizing it is most of our disk |
| advantage, and half of that is given back if it dequantizes at load. |
| |
| Gathering before scaling means only the rows a prompt touches are ever |
| dequantized, which is a few thousand of 262144. |
| |
| `embed_scale` is not optional in practice. Gemma's embedding is a |
| `Gemma4UnifiedTextScaledWordEmbedding`, whose forward is |
| ``super().forward(ids) * embed_scale`` with `embed_scale` around sqrt(3840). |
| Replacing the module without carrying that factor would drop every |
| embedding by ~62x and still produce plausible-looking tensors, so the caller |
| must read it off the module it is replacing. |
| """ |
|
|
| def __init__(self, ints: torch.Tensor, scale: torch.Tensor, |
| dtype: torch.dtype = torch.bfloat16, |
| embed_scale: torch.Tensor | None = None, |
| padding_idx: int | None = None) -> None: |
| super().__init__() |
| self.num_embeddings, self.embedding_dim = ints.shape |
| self.compute_dtype = dtype |
| self.padding_idx = padding_idx |
| self.register_buffer("ints", ints, persistent=False) |
| self.register_buffer("int8_scale", scale, persistent=False) |
| self.register_buffer("embed_scale", embed_scale, persistent=False) |
|
|
| def packed_bytes(self) -> int: |
| return (self.ints.numel() * self.ints.element_size() |
| + self.int8_scale.numel() * self.int8_scale.element_size()) |
|
|
| @property |
| def weight(self) -> torch.Tensor: |
| return decode_int8(self.ints, self.int8_scale, None).to(self.compute_dtype) |
|
|
| def forward(self, index: torch.Tensor) -> torch.Tensor: |
| rows = self.ints[index].float() |
| scale = self.int8_scale[index].unsqueeze(-1).float() |
| out = (rows * scale).to(self.compute_dtype) |
| if self.embed_scale is not None: |
| |
| |
| out = out * self.embed_scale.to(self.compute_dtype) |
| return out |
|
|
| def extra_repr(self) -> str: |
| scaled = "" if self.embed_scale is None else f", embed_scale={float(self.embed_scale):.4g}" |
| return (f"{self.num_embeddings}, {self.embedding_dim}, kind=int8, " |
| f"packed={self.packed_bytes() / 2**20:.1f} MiB{scaled}") |
|
|
|
|
| def name_part(suffix: str, parts: dict) -> str: |
| """The key in `parts` carrying `suffix`, or a miss the caller tolerates. |
| |
| `parts` comes either straight from a packer (keys are bare suffixes) or |
| from a checkpoint read (keys are `name + suffix`), and both are worth |
| supporting so a test does not have to fabricate tensor names. |
| """ |
| if suffix in parts: |
| return suffix |
| for key in parts: |
| if key.endswith(suffix): |
| return key |
| return suffix |
|
|
|
|
| def packed_linear_apply(reader, name: str, x: torch.Tensor, |
| bias: torch.Tensor | None = None, rows: int = 512, |
| dtype: torch.dtype = torch.float32) -> torch.Tensor: |
| """`F.linear(x, W, bias)` for a packed W, `rows` output rows at a time. |
| |
| The two LTX aggregate tables are [4096, 188160] and [2048, 188160]. Reading |
| one whole and casting it to float32 costs 3.08 GB, which is why |
| `apply_aggregates` only runs after the encoder has been unloaded. Chunking |
| is over *output* rows, so the contraction over `in_features` is untouched |
| and the result is bit-identical to decoding the table in one piece. |
| """ |
| out_features = reader.row_count(name) |
| pieces = [] |
| for start in range(0, out_features, rows): |
| stop = min(start + rows, out_features) |
| weight = reader.get_tensor_rows(name, start, stop, dtype) |
| piece = None if bias is None else bias[start:stop] |
| pieces.append(torch.nn.functional.linear(x, weight, piece)) |
| del weight |
| return torch.cat(pieces, dim=-1) |
|
|
|
|
| def cli_info(args) -> int: |
| reader = PackedCheckpoint(args.packed) |
| from collections import Counter |
| counts = Counter(reader.kinds.values()) |
| size = Path(args.packed).stat().st_size |
| print(f"{args.packed}: {size / 1e9:.3f} GB, {dict(counts)}") |
| print(f"negative-zero collapses at pack time: " |
| f"{reader.metadata.get('negative_zero_collapses')}") |
| for key in sorted(set(reader.metadata) - {"kinds"}): |
| print(f" {key}: {reader.metadata[key][:100]}") |
| return 0 |
|
|
|
|
| def cli_verify(args) -> int: |
| """Compare every tensor against a reference BF16 checkpoint, streaming.""" |
| from safetensors import safe_open |
| reader = PackedCheckpoint(args.packed) |
| reference = safe_open(str(args.reference), framework="pt") |
| names = set(reference.keys()) |
| missing = sorted(set(reader.kinds) - names) |
| extra = sorted(names - set(reader.kinds)) |
| if missing or extra: |
| raise SystemExit(f"tensor sets differ: missing {missing[:5]}, extra {extra[:5]}") |
| worst = 0 |
| for i, name in enumerate(sorted(names)): |
| want = reference.get_tensor(name) |
| got = reader.get_tensor(name, want.dtype) |
| if not torch.equal(got, want): |
| raise SystemExit(f"{name}: dequantized values differ from reference") |
| worst += int((got.signbit() != want.signbit()).sum()) |
| if i % 100 == 0: |
| print(f" {i}/{len(names)} verified", flush=True) |
| print(f"all {len(names)} tensors value-exact; {worst} zero-sign differences") |
| return 0 |
|
|
|
|
| def cli_emit_bf16(args) -> int: |
| """Materialize a plain BF16 checkpoint a stock loader can read.""" |
| from safetensors.torch import save_file |
| reader = PackedCheckpoint(args.packed) |
| tensors = {} |
| for name in sorted(reader.kinds): |
| tensors[name] = reader.get_tensor(name, torch.bfloat16) |
| save_file(tensors, str(args.output)) |
| print(f"wrote {args.output} ({Path(args.output).stat().st_size / 1e9:.2f} GB)") |
| return 0 |
|
|
|
|
| def main() -> int: |
| parser = argparse.ArgumentParser() |
| sub = parser.add_subparsers(dest="command", required=True) |
| sub.add_parser("info").add_argument("packed") |
| p = sub.add_parser("verify") |
| p.add_argument("packed") |
| p.add_argument("--reference", required=True) |
| p = sub.add_parser("emit-bf16") |
| p.add_argument("packed") |
| p.add_argument("--output", required=True) |
| args = parser.parse_args() |
| return {"info": cli_info, "verify": cli_verify, |
| "emit-bf16": cli_emit_bf16}[args.command](args) |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|