Views
No views yet
1from gptqmodel import GPTQModel
2from transformers import AutoTokenizer
3
4# Use the local directory or JustJaro/Rombos-LLM-V2.6-Qwen-14b-GPTQ-G32-W4A16-KVFP8 after upload
5quantized_model_id = "/home/jaro/models/quantized/Rombos-LLM-V2.6-Qwen-14b-GPTQ-G32-W4A16-KVFP8" # or "JustJaro/Rombos-LLM-V2.6-Qwen-14b-GPTQ-G32-W4A16-KVFP8"
6tokenizer = AutoTokenizer.from_pretrained(quantized_model_id)
7model = GPTQModel.load(quantized_model_id, device="cuda:0") # or "cpu"
8
9input_text = "This is a test prompt"
10inputs = tokenizer(input_text, return_tensors="pt").to("cuda:0")
11outputs = model.generate(**inputs)
12print(tokenizer.decode(outputs[0], skip_special_tokens=True))pyproject.toml for the exact UV project file. See the GPTQModel repo for more details on how to install the package.pyproject.toml:1uv venv
2source venv/bin/activate
3uv syncquantize.py script used to generate this model:1#!/usr/bin/env python3
2"""
3This script loads a source Hugging Face model and a calibration dataset,
4quantizes the model using GPTQModel (with 4-bit precision and a dynamic group size),
5saves the quantized model with Transformers’ safe serialization under ~/models/quantized/,
6and then creates/updates a Hugging Face repository by uploading the model, tokenizer,
7and an auto–generated README.md that includes proper foldable sections, badges, and warnings.
8
9Usage example:
10 python quantize.py --source-model TinyLlama/TinyLlama-1.1B-Chat-v1.0 \
11 --calibration-dataset wikitext/wikitext-2-raw-v1 \
12 --seq-len 1024 --nsamples 256 --hf-token <YOUR_HF_TOKEN>
13"""
14
15import os
16import shutil
17import subprocess
18from enum import Enum
19from pathlib import Path
20from typing import List
21
22import torch
23import typer
24from datasets import load_dataset
25from dotenv import load_dotenv, find_dotenv
26from gptqmodel import GPTQModel, QuantizeConfig
27from gptqmodel.utils import Perplexity
28# For later pushing to the model hub
29from huggingface_hub import HfApi
30from transformers import AutoTokenizer, PreTrainedTokenizerBase
31
32load_dotenv(find_dotenv())
33HF_TOKEN = os.getenv("HF_TOKEN")
34
35app = typer.Typer()
36
37
38class GroupSize(str, Enum):
39 accurate: int = 32
40 balanced: int = 64
41 fast: int = 128
42
43
44def get_text_from_example(example: dict) -> str:
45 """
46 Returns text from a dataset example.
47 If the example contains a "text" field, that text is used.
48 Otherwise, if it has a "messages" field (a list of dicts with a "content" key),
49 the contents of all messages are concatenated.
50 """
51 if "text" in example and example["text"]:
52 return example["text"]
53 elif "messages" in example:
54 contents = [msg.get("content", "").strip() for msg in example["messages"]]
55 return " ".join([s for s in contents if s])
56 else:
57 return ""
58
59
60def get_calibration_dataset(
61 tokenizer: PreTrainedTokenizerBase,
62 nsamples: int,
63 seqlen: int,
64 calibration_dataset: str
65) -> List[dict]:
66 """
67 Loads and tokenizes a calibration dataset from the HF Hub (or a local file).
68 Only examples with at least 80% of seqlen characters (after extraction) are kept.
69 """
70 ds = None
71 try:
72 try:
73 if "/" in calibration_dataset:
74 parts = calibration_dataset.split("/", 1)
75 ds = load_dataset(parts[0], parts[1], split="train")
76 else:
77 ds = load_dataset(calibration_dataset, split="train")
78 except Exception as e:
79 print(f"Error loading dataset '{calibration_dataset}' via load_dataset: {e}")
80 ds = load_dataset(calibration_dataset, split="train")
81 print(f"Loaded calibration dataset from full remote path {calibration_dataset}.")
82 except Exception as e:
83 print(f"Error loading dataset '{calibration_dataset}' via load_dataset: {e}")
84 if os.path.exists(calibration_dataset):
85 try:
86 ds = load_dataset("json", data_files=calibration_dataset, split="train")
87 print(f"Loaded calibration dataset from local file {calibration_dataset}.")
88 except Exception as e2:
89 print(f"Error loading local json dataset from '{calibration_dataset}': {e2}")
90 return []
91 else:
92 return []
93
94 print(f"Dataset features: {ds.features}")
95
96 ds = ds.filter(lambda x: len(get_text_from_example(x)) <= int(seqlen * 0.8))
97 sample_range = min(nsamples, len(ds))
98 calibration_data = []
99 for i in range(sample_range):
100 example = ds[i]
101 text = get_text_from_example(example)
102 tokenized = tokenizer(text, truncation=True, max_length=seqlen, return_tensors="pt")
103 tokenized = {k: v.squeeze(0) for k, v in tokenized.items()}
104 calibration_data.append(tokenized)
105 return calibration_data
106
107
108def calculate_avg_ppl(model, tokenizer, dataset_name="wikitext-2-raw-v1"):
109 """
110 Computes the average perplexity on the wikitext-2-raw-v1 training split.
111 """
112 ppl = Perplexity(
113 model=model,
114 tokenizer=tokenizer,
115 dataset_path="wikitext",
116 dataset_name=dataset_name,
117 split="train",
118 text_column="text",
119 )
120 ppl_values = ppl.calculate(n_ctx=512, n_batch=512)
121 avg = sum(ppl_values) / len(ppl_values)
122 return avg, dataset_name
123
124
125def get_pinned_package_versions():
126 """
127 Retrieves pinned package versions via 'uv pip freeze'.
128 """
129 try:
130 result = subprocess.run(["uv", "pip", "freeze"], capture_output=True, text=True, check=True)
131 packages_output = result.stdout.strip()
132 versions = {}
133 for line in packages_output.splitlines():
134 if "==" in line:
135 package_name, package_version = line.split("==", 1)
136 versions[package_name.lower()] = package_version
137 return versions
138 except subprocess.CalledProcessError as e:
139 typer.echo(f"Error running 'uv pip freeze': {e}", err=True)
140 return {}
141 except FileNotFoundError:
142 typer.echo("uv command not found. Make sure uv is installed and in your PATH.", err=True)
143 return {}
144
145
146def prepare_model_dir(model_dir: str):
147 """Removes the given directory if it exists and creates a new one."""
148 if os.path.exists(model_dir):
149 shutil.rmtree(model_dir)
150 os.makedirs(model_dir, exist_ok=True)
151
152
153def self_read_script():
154 """Returns the full text of this script."""
155 try:
156 script_path = os.path.abspath(__file__)
157 with open(script_path, "r") as f:
158 script_content = f.read()
159 except Exception as e:
160 script_content = "Error reading script content: " + str(e)
161 return script_content
162
163
164def get_my_user(hf_token):
165 """Retrieves your Hugging Face username from your token."""
166 api = HfApi(token=hf_token)
167 user_info = api.whoami()
168 try:
169 username = user_info.get("name") or user_info.get("username")
170 except Exception as e:
171 typer.echo(f"Error retrieving username from Hugging Face API: {e}. Using default username.")
172 username = api.whoami()
173 if not username:
174 typer.echo("Could not determine your Hugging Face username from the token. Using default username.", err=True)
175 username = "JustJaro"
176 return username
177
178
179def make_details_section(title: str, content: str) -> str:
180 """
181 Returns a markdown string for a collapsible section.
182 The format is:
183 <details>
184 <summary><strong>{title}</strong></summary>
185
186 {content}
187
188 </details>
189 """
190 return f"<details>\n <summary><strong>{title}</strong></summary>\n\n{content}\n\n</details>\n"
191
192
193def generate_readme(
194 calibration_dataset: str,
195 nsamples: int,
196 quantized_model_dir: str,
197 quantized_model_name: str,
198 script_content: str,
199 seq_len: int,
200 source_model: str,
201 username: str,
202 avg_ppl: float,
203 group_size_int: int,
204 ppl_dataset: str,
205) -> None:
206 """
207 Creates a README.md with a YAML front matter, title (with a warning if perplexity is high),
208 and a series of foldable sections.
209 """
210 import random
211
212 # Pick a random emoji for the title
213 chosen_emoji = random.choice(["⚡️", "🐣", "🦾", "🤖", "🧠", "🧐", "🚀"])
214
215 # Warning if average perplexity is above 30
216 warning_text = ""
217 if avg_ppl > 30:
218 warning_text = f"\n**⚠️ WARNING: High Perplexity Detected!** The average perplexity is {avg_ppl:.2f}, which exceeds the recommended threshold.\n"
219
220 # YAML front matter and top header
221 front_matter = (
222 "---\n"
223 'company: "ConfidentialMind"\n'
224 'emoji: "🧠"\n'
225 'colorFrom: "blue"\n'
226 'colorTo: "purple"\n'
227 'pinned: true\n'
228 'authors: "JustJaro"\n'
229 "---\n\n"
230 "# ConfidentialMind 🚀🧠\n\n"
231 "Generative AI Software Infrastructure Simplified 🎉\n\n"
232 "[](https://confidentialmind.com) \n"
233 "[](mailto:info@confidentialmind.com)\n\n"
234 )
235
236 # Main title block for the quantized model
237 title = f"# 🔥 Quantized Model: {quantized_model_name} {chosen_emoji} 🔥\n{warning_text}\n"
238
239 # Build each collapsible section using the helper:
240
241 model_details_content = (
242 f"- **Original Model:** [{source_model}](https://huggingface.co/{source_model})\n"
243 f"- **Quantized Model:** {quantized_model_name} (this repository)\n"
244 f"- **Quantization Method:** GPTQ (4-bit, group size {group_size_int})\n"
245 f"- **Quantization Library:** [GPTQModel](https://github.com/ModelCloud/GPTQModel/tree/main)\n"
246 f"- **Calibration Dataset:** {calibration_dataset} (using {nsamples} samples with seq len {seq_len})\n"
247 f"- **Quantized by:** [ConfidentialMind.com](https://www.confidentialmind.com)"
248 )
249 model_details_section = make_details_section("Model Details", model_details_content)
250
251 usage_content = (
252 f"```python\n"
253 f"from gptqmodel import GPTQModel\n"
254 f"from transformers import AutoTokenizer\n\n"
255 f"# Use the local directory or {username}/{quantized_model_name} after upload\n"
256 f'quantized_model_id = "{quantized_model_dir}" # or "{username}/{quantized_model_name}"\n'
257 f"tokenizer = AutoTokenizer.from_pretrained(quantized_model_id)\n"
258 f'model = GPTQModel.load(quantized_model_id, device="cuda:0") # or "cpu"\n\n'
259 f'input_text = "This is a test prompt"\n'
260 f'inputs = tokenizer(input_text, return_tensors="pt").to("cuda:0")\n'
261 f"outputs = model.generate(**inputs)\n"
262 f"print(tokenizer.decode(outputs[0], skip_special_tokens=True))\n"
263 f"```"
264 )
265 usage_section = make_details_section("Usage", usage_content)
266
267 package_content = (
268 "See `pyproject.toml` for the exact UV project file. See the "
269 "[GPTQModel](https://github.com/ModelCloud/GPTQModel/tree/main) repo for more details on how to install the package.\n\n"
270 "Use the provided `pyproject.toml`:\n\n"
271 "```bash\n"
272 "uv venv\n"
273 "source venv/bin/activate\n"
274 "uv sync\n"
275 "```"
276 )
277 package_section = make_details_section("Package Versions and Installation Instructions", package_content)
278
279 script_content_md = (
280 "Below is the exact `quantize.py` script used to generate this model:\n\n"
281 "```python\n"
282 f"{script_content}\n"
283 "```"
284 )
285 script_section = make_details_section("Quantization Script", script_content_md)
286
287 performance_content = f"**Average perplexity (PPL) on {ppl_dataset} dataset:** {avg_ppl:.2f}"
288 performance_section = make_details_section("Quantization Performance", performance_content)
289
290 disclaimer_content = (
291 "This model is for research purposes only. It may inherit limitations and biases from the original model "
292 "and the quantization process. Please use responsibly and refer to the original model card for more details."
293 )
294 disclaimer_section = make_details_section("Disclaimer", disclaimer_content)
295
296 contact_content = (
297 "For any questions or support, please visit [ConfidentialMind](https://www.confidentialmind.com) or contact us directly.\n\n"
298 "[](https://www.linkedin.com/company/confidentialmind/)"
299 )
300 contact_section = make_details_section("Contact", contact_content)
301
302 license_content = (
303 "This model inherits the license from the original model. Please refer to the original model card for more details.\n\n"
304 f"Original model card: `{source_model}`"
305 )
306 license_section = make_details_section("License", license_content)
307
308 author_content = (
309 "This model was quantized by [](https://www.linkedin.com/in/jaroai/)"
310 )
311 author_section = make_details_section("Author", author_content)
312
313 ack_content = (
314 "Quantization performed using the GPTQModel pipeline.\n\n"
315 "**TODO:**\n"
316 "- HELMET\n"
317 "- Eluther evaluation harness"
318 )
319 ack_section = make_details_section("Acknowledgements", ack_content)
320
321 # Combine everything into one README content string.
322 readme_content = (
323 front_matter +
324 title + "\n" +
325 model_details_section +
326 usage_section +
327 package_section +
328 script_section +
329 performance_section +
330 disclaimer_section +
331 contact_section +
332 license_section +
333 author_section +
334 ack_section
335 )
336
337 readme_path = os.path.join(quantized_model_dir, "README.md")
338 with open(readme_path, "w") as f:
339 f.write(readme_content)
340 typer.echo("README.md created with detailed information.")
341 typer.echo(f"README.md saved to {readme_path}")
342
343
344@app.command()
345def main(
346 seq_len: int = typer.Option(4096, help="Sequence length for tokenization and calibration."),
347 nsamples: int = typer.Option(512, help="Number of samples to use for calibration."),
348 source_model: str = typer.Option("rombodawg/Rombos-LLM-V2.6-Qwen-14b",
349 help="Source model HF repository identifier."),
350 calibration_dataset: str = typer.Option("wikitext/wikitext-2-raw-v1",
351 help="Calibration dataset identifier (in 'dataset/config' format) or local file path."),
352 hf_token: str = typer.Option(HF_TOKEN, help="Hugging Face token for creating/updating your repo."),
353 upload_only: bool = typer.Option(False, help="Only upload the quantized model to the Hugging Face Hub."),
354 # Allow for 32, 64, 128 only using typer:
355 group_size: GroupSize = typer.Option(GroupSize.accurate, help="Group size for quantization: accurate (32), balanced (64), fast (128)."),
356 mse: bool = typer.Option(False, help="Use MSE instead of MAE for the loss function."),
357 size_multi: float = typer.Option(3.5, help="Model size multiplier depends on the source model. Default: 1."),
358):
359 # Prepare destination directory and model names.
360 model_name = source_model.split("/")[-1]
361 if size_multi != 1:
362 size_multiplier = size_multi
363 size_multiplier_len = size_multiplier / 2
364 else:
365 size_multiplier = 1
366 size_multiplier_len = 1
367
368 nsamples = int(nsamples * size_multiplier)
369 seq_len = int(seq_len * size_multiplier_len)
370 quantized_model_name = f"{model_name}-GPTQ-G{int(group_size.value)}-W4A16-KVFP8"
371 quantized_model_dir = os.path.expanduser(os.path.join("~/models/quantized", quantized_model_name))
372
373 if not upload_only:
374 prepare_model_dir(quantized_model_dir)
375
376 typer.echo("Loading tokenizer from source model...")
377 tokenizer_obj = AutoTokenizer.from_pretrained(source_model, use_fast=True)
378
379 typer.echo("Loading calibration dataset...")
380 typer.echo(f"Calibration dataset: {calibration_dataset}")
381 calibration_data = get_calibration_dataset(tokenizer_obj, nsamples, seq_len, calibration_dataset)
382 if not calibration_data:
383 typer.echo("Calibration dataset is empty. Aborting.", err=True)
384 raise typer.Exit(code=1)
385
386 if mse:
387 mse_val = 0.01
388 quantize_config = QuantizeConfig(bits=4, group_size=int(group_size.value), damp_percent=0.015, mse=mse_val)
389 else:
390 quantize_config = QuantizeConfig(bits=4, group_size=int(group_size.value), damp_percent=0.01)
391
392 device = "cuda:0" if torch.cuda.is_available() else "cpu"
393 typer.echo(f"Loading model in {device} mode...")
394 model = GPTQModel.load(source_model, quantize_config)
395
396 typer.echo("Quantizing model...")
397 group_size_factor = int(128 / int(group_size.value))
398 batch_size = max(
399 1, int(int((nsamples * 0.1) / group_size_factor) * int(size_multiplier_len))
400 )
401 model.quantize(calibration_data, auto_gc=False, batch_size=batch_size, calibration_enable_gpu_cache=True)
402
403 package_versions = get_pinned_package_versions()
404 username = get_my_user(hf_token)
405 script_content = self_read_script()
406
407 typer.echo(f"Saving quantized model to {quantized_model_dir} using Transformers safe serialization...")
408 try:
409 model.save_pretrained(quantized_model_dir)
410 tokenizer_obj.save_pretrained(quantized_model_dir)
411 except Exception as ex:
412 typer.echo(f"Error during saving: {ex}. Aborting.")
413 raise
414 typer.echo(f"Model saved successfully to {quantized_model_dir}.")
415 else:
416 tokenizer_obj = AutoTokenizer.from_pretrained(source_model, use_fast=True)
417 package_versions = get_pinned_package_versions()
418 username = get_my_user(hf_token)
419 script_content = self_read_script()
420 device = "cuda:0" if torch.cuda.is_available() else "cpu"
421
422 # Load the (possibly quantized) model for evaluation.
423 model = GPTQModel.load(quantized_model_dir, device=device)
424 avg_ppl, ppl_dataset = calculate_avg_ppl(model, tokenizer_obj)
425 typer.echo(f"Average perplexity (PPL) on wikitext-2-raw-v1 dataset: {avg_ppl:.2f}")
426
427 deps = Path("./pyproject.toml")
428 shutil.copy(deps, quantized_model_dir)
429
430 # Note: pass the dynamic group size as an integer.
431 generate_readme(calibration_dataset, nsamples, quantized_model_dir,
432 quantized_model_name, script_content, seq_len,
433 source_model, username, avg_ppl, int(group_size.value), ppl_dataset)
434 GPTQModel.push_to_hub(quantized_path=quantized_model_dir, private=False,
435 repo_id=quantized_model_name, token=HF_TOKEN)
436 typer.echo(f"Model pushed to Hugging Face repo: {quantized_model_name}")
437
438 demo_input = tokenizer_obj("test is", return_tensors="pt").to(device)
439 generated_ids = model.generate(**demo_input)
440 output_text = tokenizer_obj.decode(generated_ids[0])
441 typer.echo(f"Inference demo output: {output_text}")
442 typer.echo(f"Average perplexity (PPL) on calibration dataset: {avg_ppl:.2f}")
443
444
445if __name__ == "__main__":
446 app()rombodawg/Rombos-LLM-V2.6-Qwen-14b