orbitquant 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 (60) hide show
  1. orbitquant/__init__.py +30 -0
  2. orbitquant/adaln.py +174 -0
  3. orbitquant/artifacts/__init__.py +32 -0
  4. orbitquant/artifacts/assets.py +59 -0
  5. orbitquant/artifacts/benchmark.py +133 -0
  6. orbitquant/artifacts/checksums.py +132 -0
  7. orbitquant/artifacts/comparisons.py +140 -0
  8. orbitquant/artifacts/loader.py +109 -0
  9. orbitquant/artifacts/manifest.py +138 -0
  10. orbitquant/artifacts/model_card.py +389 -0
  11. orbitquant/artifacts/refresh.py +65 -0
  12. orbitquant/artifacts/repair.py +130 -0
  13. orbitquant/artifacts/validator.py +449 -0
  14. orbitquant/artifacts/writer.py +229 -0
  15. orbitquant/benchmarks.py +494 -0
  16. orbitquant/cli/__init__.py +1 -0
  17. orbitquant/cli/main.py +1867 -0
  18. orbitquant/codebooks/__init__.py +13 -0
  19. orbitquant/codebooks/lloyd_max.py +260 -0
  20. orbitquant/config.py +148 -0
  21. orbitquant/errors.py +6 -0
  22. orbitquant/eval/__init__.py +65 -0
  23. orbitquant/eval/assets.py +88 -0
  24. orbitquant/eval/external_export.py +326 -0
  25. orbitquant/eval/external_metrics.py +190 -0
  26. orbitquant/eval/external_plan.py +383 -0
  27. orbitquant/eval/metrics.py +44 -0
  28. orbitquant/eval/native_plan.py +362 -0
  29. orbitquant/eval/native_runner.py +483 -0
  30. orbitquant/eval/native_settings.py +87 -0
  31. orbitquant/eval/prompts.py +291 -0
  32. orbitquant/eval/report.py +670 -0
  33. orbitquant/functional.py +22 -0
  34. orbitquant/hub.py +2501 -0
  35. orbitquant/kernels/__init__.py +13 -0
  36. orbitquant/kernels/dispatch.py +308 -0
  37. orbitquant/kernels/mps.py +147 -0
  38. orbitquant/kernels/native_packed_matmul.py +125 -0
  39. orbitquant/kernels/triton_cuda.py +1316 -0
  40. orbitquant/layers.py +567 -0
  41. orbitquant/modeling.py +443 -0
  42. orbitquant/packing/__init__.py +3 -0
  43. orbitquant/packing/bitpack.py +72 -0
  44. orbitquant/pipeline.py +236 -0
  45. orbitquant/policies/__init__.py +7 -0
  46. orbitquant/policies/flux.py +5 -0
  47. orbitquant/policies/flux2.py +5 -0
  48. orbitquant/policies/generic_dit.py +255 -0
  49. orbitquant/policies/wan.py +5 -0
  50. orbitquant/policies/z_image.py +5 -0
  51. orbitquant/quantizer.py +390 -0
  52. orbitquant/rotations/__init__.py +3 -0
  53. orbitquant/rotations/fwht.py +26 -0
  54. orbitquant/rotations/rpbh.py +119 -0
  55. orbitquant/transformers_ops.py +95 -0
  56. orbitquant-0.1.0.dist-info/METADATA +519 -0
  57. orbitquant-0.1.0.dist-info/RECORD +60 -0
  58. orbitquant-0.1.0.dist-info/WHEEL +4 -0
  59. orbitquant-0.1.0.dist-info/entry_points.txt +2 -0
  60. orbitquant-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from orbitquant.artifacts.assets import record_artifact_asset
9
+ from orbitquant.artifacts.validator import validate_orbitquant_artifact
10
+
11
+ _IMAGE_SUFFIXES = {".jpeg", ".jpg", ".png", ".webp"}
12
+ _SAFE_NAME_RE = re.compile(r"[^A-Za-z0-9_.-]+")
13
+
14
+
15
+ def _read_jsonl(path: Path) -> list[dict[str, Any]]:
16
+ if not path.is_file():
17
+ return []
18
+ return [
19
+ json.loads(line)
20
+ for line in path.read_text(encoding="utf-8").splitlines()
21
+ if line.strip()
22
+ ]
23
+
24
+
25
+ def _safe_name(value: Any) -> str:
26
+ return _SAFE_NAME_RE.sub("-", str(value)).strip("-") or "unknown"
27
+
28
+
29
+ def _prompt_id(metadata: dict[str, Any]) -> str:
30
+ prompt_record = metadata.get("prompt_record")
31
+ if isinstance(prompt_record, dict) and prompt_record.get("id") is not None:
32
+ return str(prompt_record["id"])
33
+ return "prompt"
34
+
35
+
36
+ def _output_path(artifact_path: Path, metadata: dict[str, Any]) -> Path | None:
37
+ value = metadata.get("output_path")
38
+ if not value:
39
+ return None
40
+ path = Path(str(value))
41
+ return path if path.is_absolute() else artifact_path / path
42
+
43
+
44
+ def _artifact_path(artifact_path: Path, value: Any) -> Path:
45
+ path = Path(str(value))
46
+ return path if path.is_absolute() else artifact_path / path
47
+
48
+
49
+ def _comparison_source_path(artifact_path: Path, metadata: dict[str, Any]) -> Path | None:
50
+ output_path = _output_path(artifact_path, metadata)
51
+ if output_path is not None and output_path.suffix.lower() in _IMAGE_SUFFIXES:
52
+ return output_path
53
+
54
+ contact_sheet_path = metadata.get("contact_sheet_path")
55
+ if contact_sheet_path:
56
+ path = _artifact_path(artifact_path, contact_sheet_path)
57
+ if path.suffix.lower() in _IMAGE_SUFFIXES:
58
+ return path
59
+
60
+ for asset_path in metadata.get("asset_paths", []):
61
+ path = _artifact_path(artifact_path, asset_path)
62
+ if "contact_sheet" in path.name and path.suffix.lower() in _IMAGE_SUFFIXES:
63
+ return path
64
+ return None
65
+
66
+
67
+ def _comparison_key(metadata: dict[str, Any]) -> tuple[str, str, str]:
68
+ return (
69
+ str(metadata.get("suite", "")),
70
+ str(metadata.get("seed", "")),
71
+ _prompt_id(metadata),
72
+ )
73
+
74
+
75
+ def _indexed_records(artifact_path: Path, split: str) -> dict[tuple[str, str, str], dict[str, Any]]:
76
+ records = {}
77
+ for record in _read_jsonl(artifact_path / "benchmark" / f"{split}.metrics.jsonl"):
78
+ metadata = dict(record.get("metadata", {}))
79
+ path = _comparison_source_path(artifact_path, metadata)
80
+ if path is None or not path.is_file():
81
+ continue
82
+ records[_comparison_key(metadata)] = {"metadata": metadata, "path": path}
83
+ return records
84
+
85
+
86
+ def create_artifact_image_comparisons(
87
+ artifact_dir: str | Path,
88
+ *,
89
+ comparison_keys: set[tuple[Any, Any, Any]] | None = None,
90
+ validate_checksums_enabled: bool = True,
91
+ refresh_checksums_enabled: bool = True,
92
+ ) -> list[str]:
93
+ from orbitquant.eval.assets import create_image_comparison_sheet
94
+
95
+ artifact_path = Path(artifact_dir)
96
+ validate_orbitquant_artifact(
97
+ artifact_path,
98
+ validate_checksums_enabled=validate_checksums_enabled,
99
+ validate_tensors=validate_checksums_enabled,
100
+ )
101
+ original_records = _indexed_records(artifact_path, "original")
102
+ orbitquant_records = _indexed_records(artifact_path, "orbitquant")
103
+ selected_keys = (
104
+ None
105
+ if comparison_keys is None
106
+ else {(str(suite), str(seed), str(prompt_id)) for suite, seed, prompt_id in comparison_keys}
107
+ )
108
+ created = []
109
+ for key, orbitquant in sorted(orbitquant_records.items()):
110
+ if selected_keys is not None and key not in selected_keys:
111
+ continue
112
+ original = original_records.get(key)
113
+ if original is None:
114
+ continue
115
+ suite, seed, prompt_id = key
116
+ bit_setting = orbitquant["metadata"].get("bit_setting", "orbitquant")
117
+ output_path = (
118
+ artifact_path
119
+ / "assets"
120
+ / (
121
+ "original_vs_orbitquant_"
122
+ f"{_safe_name(suite)}_seed{_safe_name(seed)}_"
123
+ f"{_safe_name(bit_setting)}_{_safe_name(prompt_id)}.webp"
124
+ )
125
+ )
126
+ create_image_comparison_sheet(
127
+ original["path"],
128
+ orbitquant["path"],
129
+ output_path,
130
+ labels=("BF16", f"OrbitQuant {bit_setting}"),
131
+ )
132
+ created.append(
133
+ record_artifact_asset(
134
+ artifact_path,
135
+ output_path,
136
+ validate_checksums_enabled=validate_checksums_enabled,
137
+ refresh_checksums_enabled=refresh_checksums_enabled,
138
+ )
139
+ )
140
+ return created
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import torch
7
+ from safetensors.torch import load_file
8
+
9
+ from orbitquant.adaln import RTNInt4Linear
10
+ from orbitquant.artifacts.checksums import (
11
+ validate_checksums as validate_artifact_checksums,
12
+ )
13
+ from orbitquant.artifacts.checksums import (
14
+ validate_sha256sums,
15
+ )
16
+ from orbitquant.artifacts.manifest import OrbitQuantManifest
17
+ from orbitquant.artifacts.validator import (
18
+ _validate_config_manifest,
19
+ validate_required_artifact_files,
20
+ )
21
+ from orbitquant.config import OrbitQuantConfig
22
+ from orbitquant.layers import OrbitQuantLinear
23
+ from orbitquant.modeling import _parent_and_child, _set_child
24
+
25
+
26
+ def _get_module(model: torch.nn.Module, module_name: str) -> torch.nn.Module:
27
+ module = model
28
+ for part in module_name.split("."):
29
+ if part.isdigit() and isinstance(module, torch.nn.ModuleList | torch.nn.Sequential):
30
+ module = module[int(part)]
31
+ elif isinstance(module, torch.nn.ModuleDict):
32
+ module = module[part]
33
+ else:
34
+ module = getattr(module, part)
35
+ return module
36
+
37
+
38
+ def _first_tensor_device(model: torch.nn.Module) -> torch.device:
39
+ for parameter in model.parameters():
40
+ return parameter.device
41
+ for buffer in model.buffers():
42
+ return buffer.device
43
+ return torch.device("cpu")
44
+
45
+
46
+ def _safetensors_load_device(device: str | torch.device | None, model: torch.nn.Module) -> str:
47
+ target_device = _first_tensor_device(model) if device is None else torch.device(device)
48
+ if target_device.type == "cuda":
49
+ return str(target_device)
50
+ return "cpu"
51
+
52
+
53
+ def load_orbitquant_artifact(
54
+ model: torch.nn.Module,
55
+ artifact_dir: str | Path,
56
+ *,
57
+ strict: bool = True,
58
+ validate_checksums: bool = True,
59
+ device: str | torch.device | None = None,
60
+ runtime_mode: str | None = None,
61
+ activation_kernel_backend: str | None = None,
62
+ ) -> OrbitQuantManifest:
63
+ artifact_path = Path(artifact_dir)
64
+ validate_required_artifact_files(artifact_path)
65
+ config = OrbitQuantConfig.from_dict(
66
+ json.loads((artifact_path / "quantization_config.json").read_text(encoding="utf-8"))
67
+ )
68
+ manifest = OrbitQuantManifest.from_dict(
69
+ json.loads((artifact_path / "orbitquant_manifest.json").read_text(encoding="utf-8"))
70
+ )
71
+ _validate_config_manifest(config, manifest)
72
+ if runtime_mode is not None or activation_kernel_backend is not None:
73
+ config_payload = config.to_dict()
74
+ if runtime_mode is not None:
75
+ config_payload["runtime_mode"] = runtime_mode
76
+ if activation_kernel_backend is not None:
77
+ config_payload["activation_kernel_backend"] = activation_kernel_backend
78
+ config = OrbitQuantConfig.from_dict(config_payload)
79
+ if validate_checksums:
80
+ validate_artifact_checksums(artifact_path, manifest.checksums)
81
+ validate_sha256sums(
82
+ artifact_path,
83
+ required_paths=tuple(manifest.checksums) + ("README.md", "orbitquant_manifest.json"),
84
+ )
85
+
86
+ for name in manifest.quantized_modules:
87
+ module = _get_module(model, name)
88
+ if not isinstance(module, torch.nn.Linear):
89
+ raise TypeError(f"expected Linear at {name}, got {type(module).__name__}")
90
+ replacement = OrbitQuantLinear.empty_from_linear(module, config=config, module_name=name)
91
+ parent, child_name = _parent_and_child(model, name)
92
+ _set_child(parent, child_name, replacement)
93
+
94
+ for name in manifest.adaln_modules:
95
+ module = _get_module(model, name)
96
+ if not isinstance(module, torch.nn.Linear):
97
+ raise TypeError(f"expected Linear at {name}, got {type(module).__name__}")
98
+ replacement = RTNInt4Linear.empty_from_linear(module, config=config, module_name=name)
99
+ parent, child_name = _parent_and_child(model, name)
100
+ _set_child(parent, child_name, replacement)
101
+
102
+ state_dict = load_file(
103
+ artifact_path / "model.safetensors",
104
+ device=_safetensors_load_device(device, model),
105
+ )
106
+ missing, unexpected = model.load_state_dict(state_dict, strict=strict)
107
+ if strict and (missing or unexpected):
108
+ raise RuntimeError(f"artifact state mismatch: missing={missing}, unexpected={unexpected}")
109
+ return manifest
@@ -0,0 +1,138 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ from orbitquant.config import OrbitQuantConfig
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class OrbitQuantManifest:
11
+ source_model_id: str
12
+ source_revision: str
13
+ source_license: str
14
+ weight_bits: int
15
+ activation_bits: int
16
+ rotation_seed: int
17
+ block_size: int | str
18
+ block_size_policy: str
19
+ codebook_version: int
20
+ target_policy: str
21
+ runtime_mode: str
22
+ activation_kernel_backend: str
23
+ activation_eps: float = 1e-10
24
+ adaln_group_size: int = 64
25
+ quantization_device: str = "unknown"
26
+ weight_quantization_backend: str = "unknown"
27
+ quantization_staging_mode: str = "unknown"
28
+ quantized_modules: list[str] = field(default_factory=list)
29
+ adaln_modules: list[str] = field(default_factory=list)
30
+ skipped_modules: list[str] = field(default_factory=list)
31
+ module_shapes: dict[str, list[int]] = field(default_factory=dict)
32
+ checksums: dict[str, str] = field(default_factory=dict)
33
+
34
+ @classmethod
35
+ def from_config(
36
+ cls,
37
+ config: OrbitQuantConfig,
38
+ *,
39
+ source_model_id: str,
40
+ source_revision: str,
41
+ source_license: str,
42
+ quantized_modules: list[str],
43
+ skipped_modules: list[str],
44
+ adaln_modules: list[str] | None = None,
45
+ module_shapes: dict[str, list[int]] | None = None,
46
+ checksums: dict[str, str] | None = None,
47
+ quantization_device: str = "unknown",
48
+ weight_quantization_backend: str = "unknown",
49
+ quantization_staging_mode: str = "unknown",
50
+ ) -> OrbitQuantManifest:
51
+ return cls(
52
+ source_model_id=source_model_id,
53
+ source_revision=source_revision,
54
+ source_license=source_license,
55
+ weight_bits=config.weight_bits,
56
+ activation_bits=config.activation_bits,
57
+ rotation_seed=config.rotation_seed,
58
+ block_size=config.block_size,
59
+ block_size_policy="largest_power_of_two_dividing_dim"
60
+ if config.block_size == "paper"
61
+ else "explicit",
62
+ codebook_version=1,
63
+ target_policy=config.target_policy,
64
+ runtime_mode=config.runtime_mode,
65
+ activation_kernel_backend=config.activation_kernel_backend,
66
+ activation_eps=config.activation_eps,
67
+ adaln_group_size=config.adaln_group_size,
68
+ quantization_device=quantization_device,
69
+ weight_quantization_backend=weight_quantization_backend,
70
+ quantization_staging_mode=quantization_staging_mode,
71
+ quantized_modules=list(quantized_modules),
72
+ adaln_modules=[] if adaln_modules is None else list(adaln_modules),
73
+ skipped_modules=list(skipped_modules),
74
+ module_shapes={} if module_shapes is None else dict(module_shapes),
75
+ checksums={} if checksums is None else dict(checksums),
76
+ )
77
+
78
+ def to_dict(self) -> dict[str, Any]:
79
+ return {
80
+ "artifact_format": "orbitquant-v1",
81
+ "source_model_id": self.source_model_id,
82
+ "source_revision": self.source_revision,
83
+ "source_license": self.source_license,
84
+ "quant_method": "orbitquant",
85
+ "paper": "https://arxiv.org/abs/2607.02461",
86
+ "weight_bits": self.weight_bits,
87
+ "activation_bits": self.activation_bits,
88
+ "rotation": "rpbh",
89
+ "rotation_seed": self.rotation_seed,
90
+ "block_size": self.block_size,
91
+ "block_size_policy": self.block_size_policy,
92
+ "codebook": "lloyd_max",
93
+ "codebook_version": self.codebook_version,
94
+ "row_norm_dtype": "bfloat16",
95
+ "runtime_mode": self.runtime_mode,
96
+ "activation_kernel_backend": self.activation_kernel_backend,
97
+ "activation_eps": self.activation_eps,
98
+ "adaln_group_size": self.adaln_group_size,
99
+ "quantization_device": self.quantization_device,
100
+ "weight_quantization_backend": self.weight_quantization_backend,
101
+ "quantization_staging_mode": self.quantization_staging_mode,
102
+ "target_policy": self.target_policy,
103
+ "adaln_policy": f"int4_rtn_group{self.adaln_group_size}_bf16_activation",
104
+ "quantized_modules": self.quantized_modules,
105
+ "adaln_modules": self.adaln_modules,
106
+ "skipped_modules": self.skipped_modules,
107
+ "module_shapes": self.module_shapes,
108
+ "checksums": self.checksums,
109
+ }
110
+
111
+ @classmethod
112
+ def from_dict(cls, data: dict[str, Any]) -> OrbitQuantManifest:
113
+ return cls(
114
+ source_model_id=data["source_model_id"],
115
+ source_revision=data["source_revision"],
116
+ source_license=data["source_license"],
117
+ weight_bits=int(data["weight_bits"]),
118
+ activation_bits=int(data["activation_bits"]),
119
+ rotation_seed=int(data.get("rotation_seed", 0)),
120
+ block_size=data.get("block_size", "paper"),
121
+ block_size_policy=data.get(
122
+ "block_size_policy", "largest_power_of_two_dividing_dim"
123
+ ),
124
+ codebook_version=int(data.get("codebook_version", 1)),
125
+ target_policy=data.get("target_policy", "auto"),
126
+ runtime_mode=data.get("runtime_mode", "dequant_bf16"),
127
+ activation_kernel_backend=data.get("activation_kernel_backend", "auto"),
128
+ activation_eps=float(data.get("activation_eps", 1e-10)),
129
+ adaln_group_size=int(data.get("adaln_group_size", 64)),
130
+ quantization_device=data.get("quantization_device", "unknown"),
131
+ weight_quantization_backend=data.get("weight_quantization_backend", "unknown"),
132
+ quantization_staging_mode=data.get("quantization_staging_mode", "unknown"),
133
+ quantized_modules=list(data.get("quantized_modules", [])),
134
+ adaln_modules=list(data.get("adaln_modules", [])),
135
+ skipped_modules=list(data.get("skipped_modules", [])),
136
+ module_shapes=dict(data.get("module_shapes", {})),
137
+ checksums=dict(data.get("checksums", {})),
138
+ )