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.
- zeroquantz/__init__.py +14 -0
- zeroquantz/__main__.py +8 -0
- zeroquantz/agent/__init__.py +16 -0
- zeroquantz/agent/dispatcher.py +520 -0
- zeroquantz/agent/intents.py +46 -0
- zeroquantz/agent/parser.py +255 -0
- zeroquantz/benchmark/__init__.py +7 -0
- zeroquantz/benchmark/latency.py +66 -0
- zeroquantz/benchmark/memory.py +41 -0
- zeroquantz/benchmark/quality.py +38 -0
- zeroquantz/benchmark/runner.py +151 -0
- zeroquantz/cli/__init__.py +7 -0
- zeroquantz/cli/app.py +98 -0
- zeroquantz/cli/commands.py +459 -0
- zeroquantz/cli/interactive.py +56 -0
- zeroquantz/core/__init__.py +7 -0
- zeroquantz/core/artifacts.py +179 -0
- zeroquantz/core/context.py +127 -0
- zeroquantz/core/events.py +30 -0
- zeroquantz/core/exceptions.py +105 -0
- zeroquantz/core/session.py +202 -0
- zeroquantz/core/subenv.py +202 -0
- zeroquantz/deploy/__init__.py +25 -0
- zeroquantz/deploy/assets.py +161 -0
- zeroquantz/deploy/launcher.py +80 -0
- zeroquantz/deploy/runtime_env.py +66 -0
- zeroquantz/deploy/targets.py +154 -0
- zeroquantz/export/__init__.py +8 -0
- zeroquantz/export/exporter.py +68 -0
- zeroquantz/export/report.py +203 -0
- zeroquantz/hardware/__init__.py +15 -0
- zeroquantz/hardware/capabilities.py +152 -0
- zeroquantz/hardware/detector.py +200 -0
- zeroquantz/hardware/gpu.py +31 -0
- zeroquantz/models/__init__.py +8 -0
- zeroquantz/models/architecture.py +168 -0
- zeroquantz/models/downloader.py +161 -0
- zeroquantz/models/hf_auth.py +105 -0
- zeroquantz/models/inspector.py +249 -0
- zeroquantz/models/metadata.py +108 -0
- zeroquantz/models/search.py +71 -0
- zeroquantz/optimization/__init__.py +22 -0
- zeroquantz/optimization/candidate.py +272 -0
- zeroquantz/optimization/constraints.py +70 -0
- zeroquantz/optimization/fit.py +203 -0
- zeroquantz/optimization/pareto.py +66 -0
- zeroquantz/optimization/planner.py +297 -0
- zeroquantz/optimization/recommender.py +149 -0
- zeroquantz/profiling/__init__.py +18 -0
- zeroquantz/profiling/calibration.py +74 -0
- zeroquantz/profiling/sensitivity.py +234 -0
- zeroquantz/quantization/__init__.py +17 -0
- zeroquantz/quantization/backends/__init__.py +8 -0
- zeroquantz/quantization/backends/bitsandbytes.py +210 -0
- zeroquantz/quantization/backends/torchao.py +198 -0
- zeroquantz/quantization/base.py +136 -0
- zeroquantz/quantization/catalog.py +321 -0
- zeroquantz/quantization/config.py +106 -0
- zeroquantz/quantization/gguf_pipeline.py +210 -0
- zeroquantz/quantization/isolated.py +248 -0
- zeroquantz/quantization/memory.py +133 -0
- zeroquantz/quantization/native.py +91 -0
- zeroquantz/quantization/registry.py +101 -0
- zeroquantz/render.py +341 -0
- zeroquantz/runtimes/__init__.py +18 -0
- zeroquantz/runtimes/base.py +64 -0
- zeroquantz/runtimes/compatibility.py +91 -0
- zeroquantz/runtimes/registry.py +70 -0
- zeroquantz/runtimes/transformers.py +53 -0
- zeroquantz/runtimes/vllm.py +83 -0
- zeroquantz/tui/__init__.py +13 -0
- zeroquantz/tui/app.py +77 -0
- zeroquantz/tui/banner.py +47 -0
- zeroquantz/tui/screens/__init__.py +25 -0
- zeroquantz/tui/screens/confirm.py +41 -0
- zeroquantz/tui/screens/execute.py +194 -0
- zeroquantz/tui/screens/model_select.py +206 -0
- zeroquantz/tui/screens/plan.py +177 -0
- zeroquantz/tui/screens/quantize_select.py +272 -0
- zeroquantz/tui/screens/settings.py +219 -0
- zeroquantz/tui/screens/token.py +94 -0
- zeroquantz/tui/screens/welcome.py +128 -0
- zeroquantz/tui/screens/workspace.py +175 -0
- zeroquantz/tui/styles/app.tcss +424 -0
- zeroquantz/tui/widgets/__init__.py +9 -0
- zeroquantz/tui/widgets/chip.py +36 -0
- zeroquantz/tui/widgets/sidebar.py +107 -0
- zeroquantz/tui/widgets/status_bar.py +43 -0
- zeroquantz/utils/__init__.py +8 -0
- zeroquantz/utils/config.py +46 -0
- zeroquantz/utils/env.py +78 -0
- zeroquantz/utils/logging.py +73 -0
- zeroquantz/utils/metrics.py +98 -0
- zeroquantz/utils/paths.py +57 -0
- zeroquantz/utils/units.py +134 -0
- zeroquantz/verification/__init__.py +17 -0
- zeroquantz/verification/logits.py +55 -0
- zeroquantz/verification/report.py +186 -0
- zeroquantz/verification/weights.py +44 -0
- zeroquantz/version.py +8 -0
- zeroquantz-0.1.0.dist-info/METADATA +72 -0
- zeroquantz-0.1.0.dist-info/RECORD +105 -0
- zeroquantz-0.1.0.dist-info/WHEEL +4 -0
- zeroquantz-0.1.0.dist-info/entry_points.txt +2 -0
- 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)
|