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,249 @@
|
|
|
1
|
+
"""Inspect a model's metadata *without downloading its weights*.
|
|
2
|
+
|
|
3
|
+
Strategy, in order of preference:
|
|
4
|
+
|
|
5
|
+
1. **safetensors headers** — exact parameter counts and dtypes, read from the
|
|
6
|
+
file headers only (a few KB over HTTP range requests on the Hub, or a local
|
|
7
|
+
header parse). This is the ideal path and never downloads multi-GB weights.
|
|
8
|
+
2. **config.json** — architecture, layer/hidden dimensions, and (as a fallback)
|
|
9
|
+
an *estimated* parameter count derived from those dimensions.
|
|
10
|
+
|
|
11
|
+
The two never masquerade as each other: :attr:`ModelProfile.param_count_source`
|
|
12
|
+
records whether the count is exact or estimated.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import struct
|
|
19
|
+
from math import prod
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from zeroquantz.core.exceptions import DependencyError, ModelInspectionError
|
|
23
|
+
from zeroquantz.models import architecture as arch
|
|
24
|
+
from zeroquantz.models.metadata import ModelProfile, ParamCountSource
|
|
25
|
+
from zeroquantz.utils.logging import get_logger
|
|
26
|
+
|
|
27
|
+
log = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ModelInspector:
|
|
31
|
+
"""Produce a :class:`ModelProfile` from a Hub id or a local directory."""
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def inspect(
|
|
35
|
+
model_id: str,
|
|
36
|
+
*,
|
|
37
|
+
revision: str | None = None,
|
|
38
|
+
token: str | None = None,
|
|
39
|
+
) -> ModelProfile:
|
|
40
|
+
path = Path(model_id)
|
|
41
|
+
if path.exists() and path.is_dir():
|
|
42
|
+
return ModelInspector._inspect_local(path)
|
|
43
|
+
return ModelInspector._inspect_hub(model_id, revision=revision, token=token)
|
|
44
|
+
|
|
45
|
+
# ---- construction from an already-parsed config (pure, offline) ---------
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def from_config(
|
|
49
|
+
config: dict,
|
|
50
|
+
*,
|
|
51
|
+
model_id: str,
|
|
52
|
+
source: str = "hub",
|
|
53
|
+
exact_param_count: int | None = None,
|
|
54
|
+
dtype: str | None = None,
|
|
55
|
+
) -> ModelProfile:
|
|
56
|
+
"""Build a profile from a raw config dict. Used by the Hub/local paths and
|
|
57
|
+
directly by tests (no network, no weights)."""
|
|
58
|
+
dims = arch.extract_dimensions(config)
|
|
59
|
+
|
|
60
|
+
if exact_param_count is not None:
|
|
61
|
+
count = exact_param_count
|
|
62
|
+
source_kind = ParamCountSource.SAFETENSORS
|
|
63
|
+
else:
|
|
64
|
+
estimated = arch.estimate_parameter_count(config)
|
|
65
|
+
if estimated is None:
|
|
66
|
+
raise ModelInspectionError(
|
|
67
|
+
f"Could not determine a parameter count for '{model_id}'.",
|
|
68
|
+
detail=(
|
|
69
|
+
"No safetensors headers were available and the config did not "
|
|
70
|
+
"contain enough dimensions (layers, hidden size, heads, vocab) "
|
|
71
|
+
"to estimate one."
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
count = estimated
|
|
75
|
+
source_kind = ParamCountSource.ESTIMATED
|
|
76
|
+
|
|
77
|
+
resolved_dtype = dtype or arch.normalize_st_dtype(
|
|
78
|
+
config.get("torch_dtype", "float16")
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return ModelProfile(
|
|
82
|
+
model_id=model_id,
|
|
83
|
+
architecture=dims["architecture"],
|
|
84
|
+
model_type=dims["model_type"],
|
|
85
|
+
parameter_count=count,
|
|
86
|
+
param_count_source=source_kind,
|
|
87
|
+
dtype=resolved_dtype,
|
|
88
|
+
num_layers=dims["num_layers"],
|
|
89
|
+
hidden_size=dims["hidden_size"],
|
|
90
|
+
intermediate_size=dims["intermediate_size"],
|
|
91
|
+
num_attention_heads=dims["num_attention_heads"],
|
|
92
|
+
num_key_value_heads=dims["num_key_value_heads"],
|
|
93
|
+
head_dim=dims["head_dim"],
|
|
94
|
+
vocab_size=dims["vocab_size"],
|
|
95
|
+
max_position_embeddings=dims["max_position_embeddings"],
|
|
96
|
+
tie_word_embeddings=dims["tie_word_embeddings"],
|
|
97
|
+
source=source,
|
|
98
|
+
config=config,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# ---- Hub path -----------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def _inspect_hub(
|
|
105
|
+
model_id: str, *, revision: str | None, token: str | None
|
|
106
|
+
) -> ModelProfile:
|
|
107
|
+
try:
|
|
108
|
+
from huggingface_hub import hf_hub_download
|
|
109
|
+
from huggingface_hub.utils import (
|
|
110
|
+
EntryNotFoundError,
|
|
111
|
+
GatedRepoError,
|
|
112
|
+
RepositoryNotFoundError,
|
|
113
|
+
)
|
|
114
|
+
except ImportError as exc: # pragma: no cover - hf_hub is a base dep
|
|
115
|
+
raise DependencyError.for_extra(
|
|
116
|
+
"huggingface_hub", "torch", purpose="inspect models from the Hub"
|
|
117
|
+
) from exc
|
|
118
|
+
|
|
119
|
+
# 1) config.json — cheap and gives us architecture + dimensions.
|
|
120
|
+
config: dict = {}
|
|
121
|
+
try:
|
|
122
|
+
config_path = hf_hub_download(
|
|
123
|
+
model_id, "config.json", revision=revision, token=token
|
|
124
|
+
)
|
|
125
|
+
config = json.loads(Path(config_path).read_text(encoding="utf-8"))
|
|
126
|
+
except GatedRepoError as exc:
|
|
127
|
+
raise ModelInspectionError(
|
|
128
|
+
f"'{model_id}' is a gated repository.",
|
|
129
|
+
detail="You must accept its license on the Hub and provide a token.",
|
|
130
|
+
suggestions=[
|
|
131
|
+
"huggingface-cli login",
|
|
132
|
+
f"open https://huggingface.co/{model_id} and accept the terms",
|
|
133
|
+
],
|
|
134
|
+
) from exc
|
|
135
|
+
except RepositoryNotFoundError as exc:
|
|
136
|
+
raise ModelInspectionError(
|
|
137
|
+
f"No model repository found for '{model_id}'.",
|
|
138
|
+
detail="Check the spelling, or whether the repo is private.",
|
|
139
|
+
suggestions=["zeroquantz inspect Qwen/Qwen3-8B"],
|
|
140
|
+
) from exc
|
|
141
|
+
except EntryNotFoundError:
|
|
142
|
+
log.debug("no config.json for %s; relying on safetensors alone", model_id)
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
raise ModelInspectionError(
|
|
145
|
+
f"Could not fetch metadata for '{model_id}'.",
|
|
146
|
+
detail=str(exc),
|
|
147
|
+
suggestions=["check your network connection", "verify the model id"],
|
|
148
|
+
) from exc
|
|
149
|
+
|
|
150
|
+
# 2) safetensors headers — exact params + dtype, no weight download.
|
|
151
|
+
exact_count, dtype = ModelInspector._hub_safetensors_stats(
|
|
152
|
+
model_id, revision=revision, token=token
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if not config and exact_count is None:
|
|
156
|
+
raise ModelInspectionError(
|
|
157
|
+
f"'{model_id}' exposes neither a config.json nor safetensors headers.",
|
|
158
|
+
detail="ZeroQuantz cannot inspect it without one of those.",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return ModelInspector.from_config(
|
|
162
|
+
config,
|
|
163
|
+
model_id=model_id,
|
|
164
|
+
source="hub",
|
|
165
|
+
exact_param_count=exact_count,
|
|
166
|
+
dtype=dtype,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def _hub_safetensors_stats(
|
|
171
|
+
model_id: str, *, revision: str | None, token: str | None
|
|
172
|
+
) -> tuple[int | None, str | None]:
|
|
173
|
+
try:
|
|
174
|
+
from huggingface_hub import get_safetensors_metadata
|
|
175
|
+
except ImportError:
|
|
176
|
+
return None, None
|
|
177
|
+
try:
|
|
178
|
+
meta = get_safetensors_metadata(model_id, revision=revision, token=token)
|
|
179
|
+
except Exception as exc:
|
|
180
|
+
log.debug("safetensors metadata unavailable for %s: %s", model_id, exc)
|
|
181
|
+
return None, None
|
|
182
|
+
|
|
183
|
+
by_dtype = getattr(meta, "parameter_count", None)
|
|
184
|
+
if by_dtype:
|
|
185
|
+
total = int(sum(by_dtype.values()))
|
|
186
|
+
dtype = arch.dominant_weight_dtype(by_dtype)
|
|
187
|
+
return total, dtype
|
|
188
|
+
return None, None
|
|
189
|
+
|
|
190
|
+
# ---- local path ---------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _inspect_local(directory: Path) -> ModelProfile:
|
|
194
|
+
config_file = directory / "config.json"
|
|
195
|
+
config: dict = {}
|
|
196
|
+
if config_file.exists():
|
|
197
|
+
config = json.loads(config_file.read_text(encoding="utf-8"))
|
|
198
|
+
|
|
199
|
+
exact_count, dtype = ModelInspector._local_safetensors_stats(directory)
|
|
200
|
+
|
|
201
|
+
if not config and exact_count is None:
|
|
202
|
+
raise ModelInspectionError(
|
|
203
|
+
f"'{directory}' contains neither config.json nor *.safetensors.",
|
|
204
|
+
suggestions=["point ZeroQuantz at a model directory or a Hub id"],
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
model_id = config.get("_name_or_path") or directory.name
|
|
208
|
+
return ModelInspector.from_config(
|
|
209
|
+
config,
|
|
210
|
+
model_id=str(model_id),
|
|
211
|
+
source="local",
|
|
212
|
+
exact_param_count=exact_count,
|
|
213
|
+
dtype=dtype,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def _local_safetensors_stats(directory: Path) -> tuple[int | None, str | None]:
|
|
218
|
+
files = sorted(directory.glob("*.safetensors"))
|
|
219
|
+
if not files:
|
|
220
|
+
return None, None
|
|
221
|
+
counts_by_dtype: dict[str, int] = {}
|
|
222
|
+
for file in files:
|
|
223
|
+
for name, info in _read_safetensors_header(file).items():
|
|
224
|
+
if name == "__metadata__":
|
|
225
|
+
continue
|
|
226
|
+
shape = info.get("shape", [])
|
|
227
|
+
dtype = info.get("dtype", "F16")
|
|
228
|
+
counts_by_dtype[dtype] = counts_by_dtype.get(dtype, 0) + int(prod(shape))
|
|
229
|
+
if not counts_by_dtype:
|
|
230
|
+
return None, None
|
|
231
|
+
total = int(sum(counts_by_dtype.values()))
|
|
232
|
+
return total, arch.dominant_weight_dtype(counts_by_dtype)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _read_safetensors_header(path: Path) -> dict:
|
|
236
|
+
"""Parse only the JSON header of a .safetensors file (no tensor data).
|
|
237
|
+
|
|
238
|
+
Layout: ``<u64 header_len><header_len bytes of JSON>``.
|
|
239
|
+
"""
|
|
240
|
+
with path.open("rb") as fh:
|
|
241
|
+
raw_len = fh.read(8)
|
|
242
|
+
if len(raw_len) < 8:
|
|
243
|
+
return {}
|
|
244
|
+
(header_len,) = struct.unpack("<Q", raw_len)
|
|
245
|
+
header_bytes = fh.read(header_len)
|
|
246
|
+
try:
|
|
247
|
+
return json.loads(header_bytes.decode("utf-8"))
|
|
248
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
249
|
+
return {}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""The :class:`ModelProfile` domain model — everything ZeroQuantz knows about a
|
|
2
|
+
model *without* having loaded its weights."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from zeroquantz.utils import units
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ParamCountSource(str, Enum):
|
|
14
|
+
"""How ``parameter_count`` was determined — never conflate the two."""
|
|
15
|
+
|
|
16
|
+
SAFETENSORS = "safetensors" # exact, summed from tensor headers
|
|
17
|
+
ESTIMATED = "estimated" # derived from config dimensions
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ModelProfile(BaseModel):
|
|
21
|
+
"""Static, weight-free description of a causal language model.
|
|
22
|
+
|
|
23
|
+
``estimated_weight_size_gb`` is always an estimate of the *original* footprint
|
|
24
|
+
at ``dtype``. ``parameter_count`` may be exact (from safetensors headers) or
|
|
25
|
+
estimated (from config) — ``param_count_source`` says which.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
model_id: str
|
|
29
|
+
architecture: str | None = None
|
|
30
|
+
model_type: str | None = None
|
|
31
|
+
parameter_count: int = Field(ge=0)
|
|
32
|
+
param_count_source: ParamCountSource = ParamCountSource.ESTIMATED
|
|
33
|
+
dtype: str = "float16"
|
|
34
|
+
|
|
35
|
+
num_layers: int | None = None
|
|
36
|
+
hidden_size: int | None = None
|
|
37
|
+
intermediate_size: int | None = None
|
|
38
|
+
num_attention_heads: int | None = None
|
|
39
|
+
num_key_value_heads: int | None = None
|
|
40
|
+
head_dim: int | None = None
|
|
41
|
+
vocab_size: int | None = None
|
|
42
|
+
max_position_embeddings: int | None = None
|
|
43
|
+
tie_word_embeddings: bool = True
|
|
44
|
+
|
|
45
|
+
source: str = "hub" # "hub" or "local"
|
|
46
|
+
config: dict = Field(default_factory=dict, repr=False)
|
|
47
|
+
|
|
48
|
+
# ---- derived sizes ------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def estimated_weight_size_gb(self) -> float:
|
|
52
|
+
"""Footprint of all parameters at the model's native ``dtype`` (GiB)."""
|
|
53
|
+
return round(units.param_gb(self.parameter_count, self.dtype), 2)
|
|
54
|
+
|
|
55
|
+
def size_gb_at(self, precision: str) -> float:
|
|
56
|
+
"""Footprint if *every* parameter were stored at ``precision`` (GiB)."""
|
|
57
|
+
return round(units.param_gb(self.parameter_count, precision), 2)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def embedding_params(self) -> int:
|
|
61
|
+
"""Input embedding parameter count (``vocab * hidden``), 0 if unknown."""
|
|
62
|
+
if self.vocab_size and self.hidden_size:
|
|
63
|
+
return self.vocab_size * self.hidden_size
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def lm_head_params(self) -> int:
|
|
68
|
+
"""Output projection params — 0 when weights are tied to the embedding."""
|
|
69
|
+
if self.tie_word_embeddings:
|
|
70
|
+
return 0
|
|
71
|
+
return self.embedding_params
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def non_quantizable_params(self) -> int:
|
|
75
|
+
"""Params that quantization backends keep at base precision by default.
|
|
76
|
+
|
|
77
|
+
Embeddings and (when untied) the LM head. Norm/bias params are negligible
|
|
78
|
+
and folded into the quantizable remainder.
|
|
79
|
+
"""
|
|
80
|
+
return self.embedding_params + self.lm_head_params
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def quantizable_params(self) -> int:
|
|
84
|
+
"""Linear-layer params that a weight-only backend will actually quantize."""
|
|
85
|
+
return max(self.parameter_count - self.non_quantizable_params, 0)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def is_estimated(self) -> bool:
|
|
89
|
+
return self.param_count_source is ParamCountSource.ESTIMATED
|
|
90
|
+
|
|
91
|
+
def summary_rows(self) -> list[tuple[str, str]]:
|
|
92
|
+
"""(label, value) pairs for tabular display."""
|
|
93
|
+
rows = [
|
|
94
|
+
("Architecture", self.architecture or "unknown"),
|
|
95
|
+
("Parameters", units.humanize_params(self.parameter_count)),
|
|
96
|
+
("Original dtype", self.dtype.upper()),
|
|
97
|
+
("Estimated weight size", f"{self.estimated_weight_size_gb:.1f} GB"),
|
|
98
|
+
]
|
|
99
|
+
if self.num_layers is not None:
|
|
100
|
+
rows.append(("Layers", str(self.num_layers)))
|
|
101
|
+
if self.hidden_size is not None:
|
|
102
|
+
rows.append(("Hidden size", str(self.hidden_size)))
|
|
103
|
+
if self.num_attention_heads is not None:
|
|
104
|
+
heads = str(self.num_attention_heads)
|
|
105
|
+
if self.num_key_value_heads and self.num_key_value_heads != self.num_attention_heads:
|
|
106
|
+
heads += f" (KV: {self.num_key_value_heads})"
|
|
107
|
+
rows.append(("Attention heads", heads))
|
|
108
|
+
return rows
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Live Hugging Face Hub model search (metadata only).
|
|
2
|
+
|
|
3
|
+
Used by the model-selection screen to offer a popularity-ranked list of matching
|
|
4
|
+
text-generation models as the user types. Network-dependent and imported lazily;
|
|
5
|
+
callers handle the offline case.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from zeroquantz.utils import units
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class ModelHit:
|
|
17
|
+
"""One search result from the Hub."""
|
|
18
|
+
|
|
19
|
+
id: str
|
|
20
|
+
downloads: int | None = None
|
|
21
|
+
likes: int | None = None
|
|
22
|
+
pipeline_tag: str | None = None
|
|
23
|
+
|
|
24
|
+
def meta_line(self) -> str:
|
|
25
|
+
bits: list[str] = []
|
|
26
|
+
if self.downloads is not None:
|
|
27
|
+
bits.append(f"↓ {units.humanize_params(self.downloads)}")
|
|
28
|
+
if self.likes is not None:
|
|
29
|
+
bits.append(f"♥ {self.likes}")
|
|
30
|
+
if self.pipeline_tag:
|
|
31
|
+
bits.append(self.pipeline_tag)
|
|
32
|
+
return " ".join(bits)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def search_models(query: str, *, limit: int = 20, token: str | None = None) -> list[ModelHit]:
|
|
36
|
+
"""Return Hub models matching ``query``, most-downloaded first.
|
|
37
|
+
|
|
38
|
+
Raises on network/Hub errors so the caller can distinguish "offline" from
|
|
39
|
+
"no matches".
|
|
40
|
+
"""
|
|
41
|
+
from huggingface_hub import HfApi
|
|
42
|
+
|
|
43
|
+
api = HfApi(token=token)
|
|
44
|
+
# list_models()'s accepted kwargs vary across huggingface_hub versions; try the
|
|
45
|
+
# richest signature first and fall back until one is accepted.
|
|
46
|
+
attempts = (
|
|
47
|
+
{"search": query, "sort": "downloads", "direction": -1, "limit": limit, "pipeline_tag": "text-generation"},
|
|
48
|
+
{"search": query, "sort": "downloads", "limit": limit, "pipeline_tag": "text-generation"},
|
|
49
|
+
{"search": query, "sort": "downloads", "limit": limit, "filter": "text-generation"},
|
|
50
|
+
{"search": query, "sort": "downloads", "limit": limit},
|
|
51
|
+
{"search": query, "limit": limit},
|
|
52
|
+
)
|
|
53
|
+
last_error: Exception | None = None
|
|
54
|
+
for kwargs in attempts:
|
|
55
|
+
try:
|
|
56
|
+
infos = list(api.list_models(**kwargs))
|
|
57
|
+
except TypeError as exc: # unsupported kwarg on this version
|
|
58
|
+
last_error = exc
|
|
59
|
+
continue
|
|
60
|
+
return [
|
|
61
|
+
ModelHit(
|
|
62
|
+
id=info.id,
|
|
63
|
+
downloads=getattr(info, "downloads", None),
|
|
64
|
+
likes=getattr(info, "likes", None),
|
|
65
|
+
pipeline_tag=getattr(info, "pipeline_tag", None),
|
|
66
|
+
)
|
|
67
|
+
for info in infos
|
|
68
|
+
]
|
|
69
|
+
if last_error is not None:
|
|
70
|
+
raise last_error
|
|
71
|
+
return []
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Optimization goals, candidate generation, ranking, and planning."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from zeroquantz.optimization.candidate import CandidateGenerator, QuantizationCandidate
|
|
6
|
+
from zeroquantz.optimization.constraints import Objective, OptimizationGoal
|
|
7
|
+
from zeroquantz.optimization.pareto import dominates, pareto_frontier
|
|
8
|
+
from zeroquantz.optimization.planner import MixedPrecisionPlan, MixedPrecisionPlanner
|
|
9
|
+
from zeroquantz.optimization.recommender import Recommendation, Recommender
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"CandidateGenerator",
|
|
13
|
+
"MixedPrecisionPlan",
|
|
14
|
+
"MixedPrecisionPlanner",
|
|
15
|
+
"Objective",
|
|
16
|
+
"OptimizationGoal",
|
|
17
|
+
"QuantizationCandidate",
|
|
18
|
+
"Recommendation",
|
|
19
|
+
"Recommender",
|
|
20
|
+
"dominates",
|
|
21
|
+
"pareto_frontier",
|
|
22
|
+
]
|