zeroquantz 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 (105) hide show
  1. zeroquantz/__init__.py +14 -0
  2. zeroquantz/__main__.py +8 -0
  3. zeroquantz/agent/__init__.py +16 -0
  4. zeroquantz/agent/dispatcher.py +520 -0
  5. zeroquantz/agent/intents.py +46 -0
  6. zeroquantz/agent/parser.py +255 -0
  7. zeroquantz/benchmark/__init__.py +7 -0
  8. zeroquantz/benchmark/latency.py +66 -0
  9. zeroquantz/benchmark/memory.py +41 -0
  10. zeroquantz/benchmark/quality.py +38 -0
  11. zeroquantz/benchmark/runner.py +151 -0
  12. zeroquantz/cli/__init__.py +7 -0
  13. zeroquantz/cli/app.py +98 -0
  14. zeroquantz/cli/commands.py +459 -0
  15. zeroquantz/cli/interactive.py +56 -0
  16. zeroquantz/core/__init__.py +7 -0
  17. zeroquantz/core/artifacts.py +179 -0
  18. zeroquantz/core/context.py +127 -0
  19. zeroquantz/core/events.py +30 -0
  20. zeroquantz/core/exceptions.py +105 -0
  21. zeroquantz/core/session.py +202 -0
  22. zeroquantz/core/subenv.py +202 -0
  23. zeroquantz/deploy/__init__.py +25 -0
  24. zeroquantz/deploy/assets.py +161 -0
  25. zeroquantz/deploy/launcher.py +80 -0
  26. zeroquantz/deploy/runtime_env.py +66 -0
  27. zeroquantz/deploy/targets.py +154 -0
  28. zeroquantz/export/__init__.py +8 -0
  29. zeroquantz/export/exporter.py +68 -0
  30. zeroquantz/export/report.py +203 -0
  31. zeroquantz/hardware/__init__.py +15 -0
  32. zeroquantz/hardware/capabilities.py +152 -0
  33. zeroquantz/hardware/detector.py +200 -0
  34. zeroquantz/hardware/gpu.py +31 -0
  35. zeroquantz/models/__init__.py +8 -0
  36. zeroquantz/models/architecture.py +168 -0
  37. zeroquantz/models/downloader.py +161 -0
  38. zeroquantz/models/hf_auth.py +105 -0
  39. zeroquantz/models/inspector.py +249 -0
  40. zeroquantz/models/metadata.py +108 -0
  41. zeroquantz/models/search.py +71 -0
  42. zeroquantz/optimization/__init__.py +22 -0
  43. zeroquantz/optimization/candidate.py +272 -0
  44. zeroquantz/optimization/constraints.py +70 -0
  45. zeroquantz/optimization/fit.py +203 -0
  46. zeroquantz/optimization/pareto.py +66 -0
  47. zeroquantz/optimization/planner.py +297 -0
  48. zeroquantz/optimization/recommender.py +149 -0
  49. zeroquantz/profiling/__init__.py +18 -0
  50. zeroquantz/profiling/calibration.py +74 -0
  51. zeroquantz/profiling/sensitivity.py +234 -0
  52. zeroquantz/quantization/__init__.py +17 -0
  53. zeroquantz/quantization/backends/__init__.py +8 -0
  54. zeroquantz/quantization/backends/bitsandbytes.py +210 -0
  55. zeroquantz/quantization/backends/torchao.py +198 -0
  56. zeroquantz/quantization/base.py +136 -0
  57. zeroquantz/quantization/catalog.py +321 -0
  58. zeroquantz/quantization/config.py +106 -0
  59. zeroquantz/quantization/gguf_pipeline.py +210 -0
  60. zeroquantz/quantization/isolated.py +248 -0
  61. zeroquantz/quantization/memory.py +133 -0
  62. zeroquantz/quantization/native.py +91 -0
  63. zeroquantz/quantization/registry.py +101 -0
  64. zeroquantz/render.py +341 -0
  65. zeroquantz/runtimes/__init__.py +18 -0
  66. zeroquantz/runtimes/base.py +64 -0
  67. zeroquantz/runtimes/compatibility.py +91 -0
  68. zeroquantz/runtimes/registry.py +70 -0
  69. zeroquantz/runtimes/transformers.py +53 -0
  70. zeroquantz/runtimes/vllm.py +83 -0
  71. zeroquantz/tui/__init__.py +13 -0
  72. zeroquantz/tui/app.py +77 -0
  73. zeroquantz/tui/banner.py +47 -0
  74. zeroquantz/tui/screens/__init__.py +25 -0
  75. zeroquantz/tui/screens/confirm.py +41 -0
  76. zeroquantz/tui/screens/execute.py +194 -0
  77. zeroquantz/tui/screens/model_select.py +206 -0
  78. zeroquantz/tui/screens/plan.py +177 -0
  79. zeroquantz/tui/screens/quantize_select.py +272 -0
  80. zeroquantz/tui/screens/settings.py +219 -0
  81. zeroquantz/tui/screens/token.py +94 -0
  82. zeroquantz/tui/screens/welcome.py +128 -0
  83. zeroquantz/tui/screens/workspace.py +175 -0
  84. zeroquantz/tui/styles/app.tcss +424 -0
  85. zeroquantz/tui/widgets/__init__.py +9 -0
  86. zeroquantz/tui/widgets/chip.py +36 -0
  87. zeroquantz/tui/widgets/sidebar.py +107 -0
  88. zeroquantz/tui/widgets/status_bar.py +43 -0
  89. zeroquantz/utils/__init__.py +8 -0
  90. zeroquantz/utils/config.py +46 -0
  91. zeroquantz/utils/env.py +78 -0
  92. zeroquantz/utils/logging.py +73 -0
  93. zeroquantz/utils/metrics.py +98 -0
  94. zeroquantz/utils/paths.py +57 -0
  95. zeroquantz/utils/units.py +134 -0
  96. zeroquantz/verification/__init__.py +17 -0
  97. zeroquantz/verification/logits.py +55 -0
  98. zeroquantz/verification/report.py +186 -0
  99. zeroquantz/verification/weights.py +44 -0
  100. zeroquantz/version.py +8 -0
  101. zeroquantz-0.1.0.dist-info/METADATA +72 -0
  102. zeroquantz-0.1.0.dist-info/RECORD +105 -0
  103. zeroquantz-0.1.0.dist-info/WHEEL +4 -0
  104. zeroquantz-0.1.0.dist-info/entry_points.txt +2 -0
  105. zeroquantz-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,136 @@
1
+ """The quantization backend protocol and its result types.
2
+
3
+ Every quantization implementation — the built-in bitsandbytes/TorchAO backends
4
+ and any third-party plugin — implements :class:`QuantizationBackend`. Core code
5
+ only ever talks to this interface, so there is never an ``if method == "awq"``
6
+ ladder anywhere in ZeroQuantz; new methods arrive by registering a backend.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import ABC, abstractmethod
12
+ from dataclasses import dataclass, field
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from pydantic import BaseModel
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
20
+ from zeroquantz.core.exceptions import ZeroQuantzError
21
+ from zeroquantz.hardware.capabilities import HardwareProfile
22
+ from zeroquantz.models.metadata import ModelProfile
23
+ from zeroquantz.optimization.constraints import OptimizationGoal
24
+ from zeroquantz.quantization.config import QuantizationConfig
25
+
26
+ ProgressCallback = "Callable[[str, float], None]"
27
+
28
+
29
+ class MemoryEstimate(BaseModel):
30
+ """Structured, clearly-*estimated* memory footprint for a config."""
31
+
32
+ weight_gb: float
33
+ vram_gb: float
34
+ measured: bool = False
35
+
36
+ def __float__(self) -> float: # keeps the spec's "returns a float" contract usable
37
+ return self.vram_gb
38
+
39
+
40
+ @dataclass
41
+ class QuantizationResult:
42
+ """Outcome of a :meth:`QuantizationBackend.quantize` call.
43
+
44
+ ``model`` holds the live quantized module (not serializable) so it can be
45
+ benchmarked/verified/exported without a round-trip to disk.
46
+ """
47
+
48
+ backend: str
49
+ method: str
50
+ config: QuantizationConfig
51
+ model: Any = None
52
+ elapsed_s: float = 0.0
53
+ original_size_gb: float | None = None
54
+ quantized_size_gb: float | None = None
55
+ output_dir: str | None = None
56
+ warnings: list[str] = field(default_factory=list)
57
+
58
+
59
+ class QuantizationBackend(ABC):
60
+ """Abstract base every quantization backend implements."""
61
+
62
+ #: Stable identifier used in configs and the registry (e.g. ``"bitsandbytes"``).
63
+ name: str = "base"
64
+ #: Human-facing description.
65
+ description: str = ""
66
+ #: Method identifiers this backend can produce (values of :class:`QuantMethod`).
67
+ methods: tuple[str, ...] = ()
68
+
69
+ # ---- availability -------------------------------------------------------
70
+
71
+ @abstractmethod
72
+ def is_available(self) -> bool:
73
+ """True if this backend's dependencies are importable on this machine."""
74
+
75
+ def availability_error(self) -> ZeroQuantzError | None:
76
+ """An actionable error explaining why the backend is unavailable, or None."""
77
+ return None
78
+
79
+ # ---- capability queries -------------------------------------------------
80
+
81
+ @abstractmethod
82
+ def supports(
83
+ self,
84
+ model: ModelProfile,
85
+ hardware: HardwareProfile,
86
+ runtime: str | None = None,
87
+ config: QuantizationConfig | None = None,
88
+ ) -> bool:
89
+ """Whether this backend can quantize ``model`` on ``hardware`` for ``runtime``."""
90
+
91
+ @abstractmethod
92
+ def default_config(
93
+ self,
94
+ method: str,
95
+ model: ModelProfile,
96
+ hardware: HardwareProfile,
97
+ goal: OptimizationGoal | None = None,
98
+ ) -> QuantizationConfig:
99
+ """Build a sensible :class:`QuantizationConfig` for ``method``."""
100
+
101
+ @abstractmethod
102
+ def estimate_memory(
103
+ self, model: ModelProfile, config: QuantizationConfig
104
+ ) -> MemoryEstimate:
105
+ """Estimate the post-quantization footprint (weights + runtime VRAM)."""
106
+
107
+ # ---- execution ----------------------------------------------------------
108
+
109
+ @abstractmethod
110
+ def quantize(
111
+ self,
112
+ model: str | Any,
113
+ config: QuantizationConfig,
114
+ *,
115
+ progress: Callable[[str, float], None] | None = None,
116
+ ) -> QuantizationResult:
117
+ """Quantize a model.
118
+
119
+ ``model`` may be a Hub id / local path (the backend loads it) or an
120
+ already-loaded module. Heavy dependencies are imported lazily here so that
121
+ importing the backend never pulls in torch/transformers.
122
+ """
123
+
124
+ @abstractmethod
125
+ def export(self, result: QuantizationResult, output_dir: str) -> str:
126
+ """Write a quantized model to ``output_dir``; return the path written."""
127
+
128
+ # ---- introspection ------------------------------------------------------
129
+
130
+ def describe(self) -> dict[str, Any]:
131
+ return {
132
+ "name": self.name,
133
+ "description": self.description,
134
+ "methods": list(self.methods),
135
+ "available": self.is_available(),
136
+ }
@@ -0,0 +1,321 @@
1
+ """The ZeroQuantz quantization *format* catalog.
2
+
3
+ A single, data-only source of truth describing every quantization method/format
4
+ ZeroQuantz knows about: its effective bits-per-weight, the tool that produces it,
5
+ the runtimes that can run it, whether it needs calibration, and how ZeroQuantz can
6
+ execute it (in the main env, in an isolated env, or not yet).
7
+
8
+ Numbers are drawn from published sources (llama.cpp quantize docs, method papers,
9
+ library docs). GGUF bits-per-weight are whole-tensor averages for an ~8B model.
10
+ Everything here is a heuristic *estimate* used for planning — never a measurement.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum
17
+
18
+
19
+ class Execution(str, Enum):
20
+ """How ZeroQuantz can run a format."""
21
+
22
+ IN_ENV = "in_env" # runs in the main environment (deps light/compatible)
23
+ ISOLATED = "isolated" # run its toolchain in a dedicated sub-environment
24
+ EXTERNAL = "external" # produced by an external binary/flow (e.g. llama.cpp)
25
+ PLANNED = "planned" # catalogued/estimable, execution not wired in v0.1
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class QuantFormat:
30
+ """Static description of one quantization format."""
31
+
32
+ id: str
33
+ label: str
34
+ family: str
35
+ bits_per_weight: float
36
+ produced_by: str # pip package or tool that creates it
37
+ runtimes: tuple[str, ...] # runtimes that can serve it
38
+ execution: Execution
39
+ weight_only: bool = True
40
+ activation_bits: int = 16
41
+ calibration: str = "none" # none | optional | required
42
+ requires_precision: str | None = None # hardware precision needed to RUN, or None (CPU ok)
43
+ variable_bpw: bool = False
44
+ #: True if bits_per_weight is a *whole-model* average (embeddings included), as
45
+ #: for GGUF; False if it applies only to quantized linear layers (embeddings kept
46
+ #: at compute precision), as for AWQ/GPTQ/bitsandbytes/TorchAO.
47
+ whole_model: bool = False
48
+ #: For IN_ENV formats: the registered backend name + method that executes it.
49
+ backend: str | None = None
50
+ method: str | None = None
51
+ quality_tier: int = 3 # 1 (poor) .. 5 (near-lossless), rough prior
52
+ speed_tier: int = 3 # 1 (slow) .. 5 (fast) on a consumer GPU, rough prior
53
+ note: str = ""
54
+ pip: tuple[str, ...] = field(default_factory=tuple) # deps for an isolated env
55
+
56
+ @property
57
+ def bytes_per_weight(self) -> float:
58
+ return self.bits_per_weight / 8.0
59
+
60
+ @property
61
+ def needs_isolation(self) -> bool:
62
+ return self.execution is Execution.ISOLATED
63
+
64
+ @property
65
+ def executable(self) -> bool:
66
+ return self.execution in (Execution.IN_ENV, Execution.ISOLATED)
67
+
68
+
69
+ # Runtime short-names used across the catalog.
70
+ RUNTIMES = (
71
+ "transformers", "vllm", "sglang", "llamacpp", "exllamav2",
72
+ "tensorrt_llm", "mlx", "bitnet_cpp", "ollama",
73
+ )
74
+
75
+ _C = "compatible"
76
+
77
+ # --- the catalog -------------------------------------------------------------
78
+
79
+ CATALOG: tuple[QuantFormat, ...] = (
80
+ # ---- bitsandbytes (in-env) ----
81
+ QuantFormat("bnb_int8", "bitsandbytes INT8", "bitsandbytes", 8.0, "bitsandbytes",
82
+ ("transformers", "vllm"), Execution.IN_ENV, calibration="none",
83
+ requires_precision="int8", quality_tier=5, speed_tier=2,
84
+ backend="bitsandbytes", method="int8_bnb",
85
+ note="LLM.int8() outlier-aware; robust, slower kernels.",
86
+ pip=("bitsandbytes", "transformers", "accelerate")),
87
+ QuantFormat("nf4", "bitsandbytes NF4", "bitsandbytes", 4.13, "bitsandbytes",
88
+ ("transformers",), Execution.IN_ENV, calibration="none",
89
+ requires_precision="int4", quality_tier=3, speed_tier=3,
90
+ backend="bitsandbytes", method="nf4",
91
+ note="Best 4-bit dtype for QLoRA finetuning; not a fast-serving format.",
92
+ pip=("bitsandbytes", "transformers", "accelerate")),
93
+ QuantFormat("fp4", "bitsandbytes FP4", "bitsandbytes", 4.13, "bitsandbytes",
94
+ ("transformers",), Execution.IN_ENV, calibration="none",
95
+ requires_precision="int4", quality_tier=3, speed_tier=3,
96
+ note="Slightly below NF4 quality.",
97
+ pip=("bitsandbytes", "transformers", "accelerate")),
98
+
99
+ # ---- TorchAO (in-env) ----
100
+ QuantFormat("torchao_int8", "TorchAO INT8", "torchao", 8.0, "torchao",
101
+ ("transformers", "vllm"), Execution.IN_ENV, calibration="none",
102
+ requires_precision="int8", quality_tier=5, speed_tier=4,
103
+ backend="torchao", method="int8_torchao",
104
+ note="PyTorch-native int8 weight-only.", pip=("torchao", "transformers")),
105
+ QuantFormat("torchao_int4", "TorchAO INT4 W4A16", "torchao", 4.25, "torchao",
106
+ ("transformers", "vllm"), Execution.IN_ENV, calibration="none",
107
+ requires_precision="int4", quality_tier=3, speed_tier=4,
108
+ backend="torchao", method="int4_torchao",
109
+ note="tinygemm int4, group 128; good CUDA speed.", pip=("torchao", "transformers")),
110
+ QuantFormat("torchao_fp8", "TorchAO FP8", "torchao", 8.0, "torchao",
111
+ ("transformers", "vllm", "sglang"), Execution.PLANNED, weight_only=False,
112
+ activation_bits=8, calibration="optional", requires_precision="fp8",
113
+ quality_tier=5, speed_tier=5, note="Needs FP8 tensor cores (Hopper/Blackwell).",
114
+ pip=("torchao", "transformers")),
115
+
116
+ # ---- ZeroQuantz mixed precision (planning strategy) ----
117
+ QuantFormat("mixed", "Mixed INT4/INT8", "mixed", 5.0, "zeroquantz planner",
118
+ ("transformers", "vllm"), Execution.PLANNED, calibration="none",
119
+ requires_precision="int4", quality_tier=4, speed_tier=3,
120
+ note="Per-layer plan: sensitive layers INT8, the rest INT4 (see /plan).",
121
+ pip=()),
122
+
123
+ # ---- AWQ / GPTQ / AutoRound (isolated) ----
124
+ QuantFormat("awq", "AWQ W4A16", "awq", 4.15, "autoawq",
125
+ ("transformers", "vllm", "sglang", "tensorrt_llm"), Execution.ISOLATED,
126
+ calibration="required", requires_precision="int4", quality_tier=4, speed_tier=5,
127
+ note="Activation-aware; fast Marlin kernels; strong 4-bit quality.",
128
+ pip=("autoawq", "transformers", "accelerate")),
129
+ QuantFormat("gptq", "GPTQ W4A16", "gptq", 4.15, "gptqmodel",
130
+ ("transformers", "vllm", "sglang", "exllamav2", "tensorrt_llm"), Execution.ISOLATED,
131
+ calibration="required", requires_precision="int4", quality_tier=4, speed_tier=5,
132
+ note="Hessian-based; GPTQModel is the maintained producer.",
133
+ pip=("gptqmodel", "transformers", "accelerate")),
134
+ QuantFormat("gptq_3bit", "GPTQ 3-bit", "gptq", 3.25, "gptqmodel",
135
+ ("transformers", "vllm"), Execution.ISOLATED, calibration="required",
136
+ requires_precision="int4", quality_tier=2, speed_tier=4,
137
+ note="Aggressive; noticeable quality drop.", pip=("gptqmodel", "transformers")),
138
+ QuantFormat("autoround", "AutoRound W4A16", "autoround", 4.15, "auto-round",
139
+ ("transformers", "vllm", "sglang"), Execution.ISOLATED, calibration="required",
140
+ requires_precision="int4", quality_tier=5, speed_tier=5,
141
+ note="Sign-gradient rounding; near-lossless 4-bit; exports GPTQ/AWQ/GGUF.",
142
+ pip=("auto-round", "transformers", "accelerate")),
143
+ QuantFormat("autoround_2bit", "AutoRound 2-bit", "autoround", 2.3, "auto-round",
144
+ ("transformers",), Execution.ISOLATED, calibration="required",
145
+ requires_precision="int4", quality_tier=2, speed_tier=4,
146
+ note="SOTA-ish at 2-bit but still lossy.", pip=("auto-round", "transformers")),
147
+
148
+ # ---- HQQ (isolated; no calibration) ----
149
+ QuantFormat("hqq_4bit", "HQQ 4-bit", "hqq", 4.5, "hqq",
150
+ ("transformers",), Execution.ISOLATED, calibration="none",
151
+ requires_precision="int4", quality_tier=3, speed_tier=3,
152
+ note="Calibration-free optimization; very fast to quantize.",
153
+ pip=("hqq", "transformers", "accelerate")),
154
+ QuantFormat("hqq_2bit", "HQQ 2-bit", "hqq", 2.5, "hqq",
155
+ ("transformers",), Execution.ISOLATED, calibration="none",
156
+ requires_precision="int4", quality_tier=1, speed_tier=3,
157
+ note="Low-bit; pair with HQQ+ LoRA to recover quality.",
158
+ pip=("hqq", "transformers", "accelerate")),
159
+
160
+ # ---- sub-3-bit specialists (isolated / heavy) ----
161
+ QuantFormat("aqlm", "AQLM 2-bit", "aqlm", 2.0, "aqlm",
162
+ ("transformers", "vllm"), Execution.ISOLATED, calibration="required",
163
+ requires_precision="int4", quality_tier=3, speed_tier=2,
164
+ note="Best accuracy <3bpw; very expensive to produce.",
165
+ pip=("aqlm[gpu]", "transformers", "accelerate")),
166
+ QuantFormat("quip", "QuIP# 2-bit", "quip", 2.0, "quip-sharp",
167
+ ("transformers",), Execution.PLANNED, calibration="required",
168
+ requires_precision="int4", quality_tier=3, speed_tier=2,
169
+ note="E8 lattice + incoherence; near-AQLM at 2-bit; hard to deploy.",
170
+ pip=("quip-sharp",)),
171
+ QuantFormat("spinquant", "SpinQuant W4A8", "spinquant", 4.1, "SpinQuant",
172
+ ("executorch", "transformers"), Execution.PLANNED, weight_only=False,
173
+ activation_bits=8, calibration="required", requires_precision="int4",
174
+ quality_tier=4, speed_tier=4, note="Learned rotations; used in official Llama 3.2.",
175
+ pip=()),
176
+
177
+ # ---- ExLlamaV2 EXL2 (isolated; Linux-friendly) ----
178
+ QuantFormat("exl2_4.0", "EXL2 4.0 bpw", "exl2", 4.0, "exllamav2",
179
+ ("exllamav2",), Execution.ISOLATED, calibration="required",
180
+ requires_precision="int4", variable_bpw=True, quality_tier=4, speed_tier=5,
181
+ note="Mixed per-layer bits; excellent single-GPU speed/quality.",
182
+ pip=("exllamav2",)),
183
+ QuantFormat("exl2_5.0", "EXL2 5.0 bpw", "exl2", 5.0, "exllamav2",
184
+ ("exllamav2",), Execution.ISOLATED, calibration="required",
185
+ requires_precision="int4", variable_bpw=True, quality_tier=5, speed_tier=5,
186
+ note="Near-lossless on a single consumer GPU.", pip=("exllamav2",)),
187
+ QuantFormat("exl2_2.5", "EXL2 2.5 bpw", "exl2", 2.5, "exllamav2",
188
+ ("exllamav2",), Execution.ISOLATED, calibration="required",
189
+ requires_precision="int4", variable_bpw=True, quality_tier=2, speed_tier=5,
190
+ note="Fits big models on small VRAM; graceful degradation.", pip=("exllamav2",)),
191
+
192
+ # ---- compressed-tensors via llmcompressor (isolated) ----
193
+ QuantFormat("ct_fp8", "compressed-tensors FP8 W8A8", "compressed_tensors", 8.0, "llmcompressor",
194
+ ("vllm", "sglang", "tensorrt_llm"), Execution.ISOLATED, weight_only=False,
195
+ activation_bits=8, calibration="optional", requires_precision="fp8",
196
+ quality_tier=5, speed_tier=5, note="Near-lossless on FP8 hardware; fastest 8-bit.",
197
+ pip=("llmcompressor", "transformers")),
198
+ QuantFormat("ct_int8", "compressed-tensors INT8 W8A8", "compressed_tensors", 8.0, "llmcompressor",
199
+ ("vllm", "sglang"), Execution.ISOLATED, weight_only=False, activation_bits=8,
200
+ calibration="required", requires_precision="int8", quality_tier=4, speed_tier=4,
201
+ note="SmoothQuant-style W8A8 for Ampere-class GPUs.",
202
+ pip=("llmcompressor", "transformers")),
203
+ QuantFormat("ct_int4", "compressed-tensors INT4 W4A16", "compressed_tensors", 4.15, "llmcompressor",
204
+ ("vllm", "sglang"), Execution.ISOLATED, calibration="required",
205
+ requires_precision="int4", quality_tier=4, speed_tier=5,
206
+ note="GPTQ/AWQ-Marlin family with native vLLM serialization.",
207
+ pip=("llmcompressor", "transformers")),
208
+ QuantFormat("mxfp4", "MXFP4", "mxfp4", 4.25, "llmcompressor",
209
+ ("vllm", "sglang", "tensorrt_llm"), Execution.PLANNED, calibration="optional",
210
+ requires_precision="fp8", quality_tier=4, speed_tier=5,
211
+ note="OCP microscaling FP4 (blk32); Blackwell/MI355; used for gpt-oss.",
212
+ pip=("llmcompressor",)),
213
+ QuantFormat("nvfp4", "NVFP4 W4A4", "nvfp4", 4.5, "modelopt",
214
+ ("tensorrt_llm", "vllm"), Execution.PLANNED, weight_only=False, activation_bits=4,
215
+ calibration="required", requires_precision="fp8", quality_tier=4, speed_tier=5,
216
+ note="NVIDIA FP4 (blk16); Blackwell only; better than MXFP4 at 4-bit.",
217
+ pip=("nvidia-modelopt",)),
218
+
219
+ # ---- Unsloth Dynamic ----
220
+ QuantFormat("unsloth_dynamic_4bit", "Unsloth Dynamic 4-bit", "unsloth", 4.8, "unsloth",
221
+ ("transformers",), Execution.ISOLATED, calibration="none",
222
+ requires_precision="int4", quality_tier=4, speed_tier=3,
223
+ note="NF4 that skips outlier-sensitive modules; QLoRA-focused (<10% extra VRAM).",
224
+ pip=("unsloth", "bitsandbytes", "transformers")),
225
+ QuantFormat("unsloth_ud_q4_k_xl", "Unsloth UD-Q4_K_XL (GGUF)", "unsloth", 5.1, "llama.cpp+imatrix",
226
+ ("llamacpp", "ollama"), Execution.EXTERNAL, calibration="required",
227
+ quality_tier=5, speed_tier=4, whole_model=True,
228
+ note="Dynamic K-quant with curated imatrix; strong at low-bit & MoE.",
229
+ pip=("gguf",)),
230
+ QuantFormat("unsloth_ud_q2_k_xl", "Unsloth UD-Q2_K_XL (GGUF)", "unsloth", 2.6, "llama.cpp+imatrix",
231
+ ("llamacpp", "ollama"), Execution.EXTERNAL, calibration="required",
232
+ quality_tier=3, speed_tier=4, whole_model=True,
233
+ note="Dynamic 2-bit; usable quality for its size.", pip=("gguf",)),
234
+
235
+ # ---- BitNet 1.58 (ternary, training-time) ----
236
+ QuantFormat("bitnet_b158", "BitNet b1.58 (ternary)", "bitnet", 1.58, "native-training",
237
+ ("bitnet_cpp", "llamacpp"), Execution.EXTERNAL, weight_only=False,
238
+ activation_bits=8, calibration="none", quality_tier=3, speed_tier=5,
239
+ whole_model=True,
240
+ note="Ternary {-1,0,1}; requires native ternary training, not post-hoc PTQ.",
241
+ pip=()),
242
+
243
+ # ---- FP8 (generic) ----
244
+ QuantFormat("fp8", "FP8 (E4M3)", "fp8", 8.0, "llmcompressor/torchao",
245
+ ("vllm", "sglang", "tensorrt_llm", "transformers"), Execution.ISOLATED,
246
+ weight_only=False, activation_bits=8, calibration="optional",
247
+ requires_precision="fp8", quality_tier=5, speed_tier=5,
248
+ note="Near-lossless 8-bit on FP8 hardware.", pip=("llmcompressor", "transformers")),
249
+ )
250
+
251
+ # ---- GGUF family (llama.cpp), generated compactly (external, CPU-capable) ----
252
+ # (id_suffix, label, bpw, quality_tier, calibration, note)
253
+ _GGUF: tuple[tuple[str, str, float, int, str, str], ...] = (
254
+ ("q8_0", "GGUF Q8_0", 8.50, 5, "none", "Effectively lossless; large."),
255
+ ("q6_k", "GGUF Q6_K", 6.56, 5, "none", "Near-lossless."),
256
+ ("q5_k_m", "GGUF Q5_K_M", 5.70, 5, "optional", "High quality, common pick."),
257
+ ("q5_k_s", "GGUF Q5_K_S", 5.57, 4, "optional", "Slightly smaller Q5."),
258
+ ("q4_k_m", "GGUF Q4_K_M", 4.89, 4, "optional", "The default sweet spot."),
259
+ ("q4_k_s", "GGUF Q4_K_S", 4.67, 4, "optional", "Smaller Q4."),
260
+ ("q4_0", "GGUF Q4_0", 4.50, 3, "none", "Legacy fixed 4-bit."),
261
+ ("iq4_nl", "GGUF IQ4_NL", 4.68, 4, "optional", "Non-linear 4-bit i-quant."),
262
+ ("iq4_xs", "GGUF IQ4_XS", 4.46, 4, "optional", "Efficient 4-bit i-quant."),
263
+ ("q3_k_l", "GGUF Q3_K_L", 4.30, 3, "optional", "Larger 3-bit K-quant."),
264
+ ("q3_k_m", "GGUF Q3_K_M", 4.00, 3, "optional", "Medium 3-bit."),
265
+ ("iq3_s", "GGUF IQ3_S", 3.66, 3, "required", "3-bit i-quant."),
266
+ ("q3_k_s", "GGUF Q3_K_S", 3.64, 2, "optional", "Small 3-bit."),
267
+ ("iq3_xxs", "GGUF IQ3_XXS", 3.25, 2, "required", "Aggressive 3-bit."),
268
+ ("q2_k", "GGUF Q2_K", 3.16, 2, "optional", "2-bit K-quant."),
269
+ ("iq2_s", "GGUF IQ2_S", 2.74, 2, "required", "2-bit i-quant."),
270
+ ("iq2_xs", "GGUF IQ2_XS", 2.59, 1, "required", "Very small 2-bit."),
271
+ ("iq2_xxs", "GGUF IQ2_XXS", 2.38, 1, "required", "Extremely small 2-bit."),
272
+ ("iq1_m", "GGUF IQ1_M", 2.15, 1, "required", "~1.75-bit; heavy loss."),
273
+ ("tq2_0", "GGUF TQ2_0", 2.06, 1, "none", "Ternary 2-bit (BitNet-style)."),
274
+ ("iq1_s", "GGUF IQ1_S", 1.78, 1, "required", "~1.5–2-bit; extreme loss."),
275
+ ("tq1_0", "GGUF TQ1_0", 1.69, 1, "none", "Ternary ~1.69-bit."),
276
+ )
277
+
278
+
279
+ def _gguf_formats() -> list[QuantFormat]:
280
+ formats: list[QuantFormat] = []
281
+ for suffix, label, bpw, qtier, calib, note in _GGUF:
282
+ formats.append(
283
+ QuantFormat(
284
+ id=f"gguf_{suffix}",
285
+ label=label,
286
+ family="gguf",
287
+ bits_per_weight=bpw,
288
+ produced_by="llama.cpp (llama-quantize)",
289
+ runtimes=("llamacpp", "ollama"),
290
+ execution=Execution.EXTERNAL,
291
+ calibration=calib,
292
+ requires_precision=None, # llama.cpp runs on CPU + many GPUs
293
+ quality_tier=qtier,
294
+ speed_tier=4,
295
+ whole_model=True,
296
+ note=note,
297
+ pip=("gguf",),
298
+ )
299
+ )
300
+ return formats
301
+
302
+
303
+ ALL_FORMATS: tuple[QuantFormat, ...] = tuple(list(CATALOG) + _gguf_formats())
304
+
305
+ _BY_ID = {f.id: f for f in ALL_FORMATS}
306
+
307
+
308
+ def get_format(format_id: str) -> QuantFormat | None:
309
+ return _BY_ID.get(format_id)
310
+
311
+
312
+ def families() -> list[str]:
313
+ seen: list[str] = []
314
+ for f in ALL_FORMATS:
315
+ if f.family not in seen:
316
+ seen.append(f.family)
317
+ return seen
318
+
319
+
320
+ def formats_by_family(family: str) -> list[QuantFormat]:
321
+ return [f for f in ALL_FORMATS if f.family == family]
@@ -0,0 +1,106 @@
1
+ """Quantization configuration types.
2
+
3
+ A :class:`QuantizationConfig` is the *actionable* description a backend consumes.
4
+ It is deliberately backend-agnostic: ``backend`` + ``method`` select the
5
+ implementation, and the remaining fields describe the numeric scheme. Mixed
6
+ precision is expressed through ``module_overrides`` (a mapping of module-name glob
7
+ -> weight bits) so the same config type covers both uniform and per-layer plans.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from enum import Enum
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+
17
+ class QuantMethod(str, Enum):
18
+ """Identifiers for the quantization schemes ZeroQuantz knows about.
19
+
20
+ v0.1 *implements* the bitsandbytes and TorchAO methods; the rest are declared
21
+ so the planner/recommender can reason about them and so future backends slot
22
+ in without touching core enums.
23
+ """
24
+
25
+ # bitsandbytes
26
+ BNB_NF4 = "nf4"
27
+ BNB_INT8 = "int8_bnb"
28
+ # TorchAO
29
+ TORCHAO_INT8 = "int8_torchao"
30
+ TORCHAO_INT4 = "int4_torchao"
31
+ # future backends (declared, not yet implemented)
32
+ AWQ = "awq"
33
+ GPTQ = "gptq"
34
+ AUTOROUND = "autoround"
35
+ FP8 = "fp8"
36
+ MXFP4 = "mxfp4"
37
+ NVFP4 = "nvfp4"
38
+ MIXED = "mixed"
39
+
40
+ def __str__(self) -> str: # nicer display
41
+ return self.value
42
+
43
+
44
+ class QuantizationConfig(BaseModel):
45
+ """A concrete, backend-ready quantization configuration."""
46
+
47
+ backend: str
48
+ method: str
49
+ weight_bits: int = Field(ge=1, le=16)
50
+ activation_bits: int = 16
51
+ group_size: int | None = 128
52
+ compute_dtype: str = "bfloat16"
53
+ double_quant: bool = False
54
+ quant_type: str | None = None # bitsandbytes: "nf4" | "fp4"
55
+ skip_modules: list[str] = Field(default_factory=lambda: ["lm_head"])
56
+ # module-name glob -> weight bits, for mixed-precision plans
57
+ module_overrides: dict[str, int] = Field(default_factory=dict)
58
+ extra: dict = Field(default_factory=dict)
59
+
60
+ @property
61
+ def is_mixed_precision(self) -> bool:
62
+ return bool(self.module_overrides)
63
+
64
+ @property
65
+ def weight_precision(self) -> str:
66
+ """Canonical precision name (for the units/memory math) of the *weights*."""
67
+ return bits_to_precision(self.weight_bits, self.method)
68
+
69
+ @property
70
+ def label(self) -> str:
71
+ """Short human label, e.g. ``"bitsandbytes NF4"`` or ``"Mixed INT4/INT8"``."""
72
+ method_names = {
73
+ "nf4": "NF4",
74
+ "int8_bnb": "INT8",
75
+ "int8_torchao": "INT8",
76
+ "int4_torchao": "W4A16",
77
+ "awq": "AWQ W4A16",
78
+ "gptq": "GPTQ W4A16",
79
+ "fp8": "FP8",
80
+ "mixed": "Mixed INT4/INT8",
81
+ }
82
+ name = method_names.get(self.method, self.method.upper())
83
+ # Only prefix with the backend for the real quantization tools; roadmap /
84
+ # synthetic methods (awq, gptq, fp8, mixed) carry their name alone.
85
+ if self.backend in ("bitsandbytes", "torchao"):
86
+ return f"{_backend_display(self.backend)} {name}"
87
+ return name
88
+
89
+
90
+ def bits_to_precision(weight_bits: int, method: str) -> str:
91
+ """Map (bits, method) -> a precision key understood by :mod:`zeroquantz.utils.units`."""
92
+ m = method.lower()
93
+ if m in ("nf4", "fp4"):
94
+ return "nf4" if m == "nf4" else "fp4"
95
+ if m == "fp8" or weight_bits == 8 and "fp8" in m:
96
+ return "fp8"
97
+ return {16: "float16", 8: "int8", 4: "int4", 3: "int3", 2: "int2"}.get(
98
+ weight_bits, "int8"
99
+ )
100
+
101
+
102
+ def _backend_display(backend: str) -> str:
103
+ return {
104
+ "bitsandbytes": "bitsandbytes",
105
+ "torchao": "TorchAO",
106
+ }.get(backend, backend)