Inkling-Small-MXFP4 / quantize_quark.py
lightseek's picture
Upload MXFP4 model weights
44efba0 verified
Raw
History Blame Contribute Delete
43.3 kB
#
# Copyright (C) 2023, Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
# Adopted from https://github.com/amd/Quark/blob/release/0.12/examples/torch/language_modeling/llm_ptq/quantize_quark.py
import argparse
import json
import os
import sys
import warnings
from pathlib import Path
import torch
from huggingface_hub import snapshot_download
from quark.common.profiler import GlobalProfiler, ProfileStep
from quark.common.utils.log import ScreenLogger
from quark.torch import (
LLMTemplate,
ModelQuantizer,
RuntimeOptions,
export_gguf,
export_onnx,
export_safetensors,
import_model_from_safetensors,
load_params,
save_params,
)
from quark.torch.export.api import _move_quantizer_to_dict
from quark.torch.quantization.config.config import load_quant_algo_config_from_file
from quark.torch.quantization import file2file_quantization
from quark.torch.utils import TPDeviceManager
# TODO: Using sys.path.append is bad practice.
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from quark.contrib.llm_eval import eval_model
from quark.torch.utils.llm import (
check_compatibility_before_quantization,
get_calib_dataloader,
get_model,
get_tokenizer,
maybe_save_preprocessors,
preprocess_for_quantization,
)
logger = ScreenLogger(__name__)
quark_is_linear_weight_tensor = file2file_quantization._is_linear_weight_tensor
quark_quantize_and_save_safetensor_shard = file2file_quantization._quantize_and_save_safetensor_shard
# set CUDA_VISIBLE_DEVICES for profiling
if "CUDA_VISIBLE_DEVICES" not in os.environ:
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
# The code below demonstrates how to register custom model templates and
# quantization schemes. If you need to add support for a new model architecture
# or define custom quantization configurations, uncomment and modify this section.
#
# To use:
# 1. Uncomment the code below
# 2. Modify the templates and/or schemes to match your model's architecture and/or quantization scheme
# 3. Run quantize_quark.py with your custom --quant_scheme name if new quantization schemes are registered
#
# from quark.torch.quantization.config.config import (
# Int8PerTensorSpec,
# QLayerConfig,
# )
# # --- Custom Model Templates ---
# # Define templates for model architectures not in the built-in list.
# # Model: internlm/internlm2-chat-7b
# internlm2_template = LLMTemplate(
# model_type="internlm2",
# kv_layers_name=["*wqkv"],
# q_layer_name="*wqkv",
# exclude_layers_name=["lm_head"],
# )
# LLMTemplate.register_template(internlm2_template)
# print(f"[INFO]: Registered template '{internlm2_template.model_type}'")
if "inkling_mm_model" not in LLMTemplate.list_available():
inkling_template = LLMTemplate(
model_type="inkling_mm_model",
kv_layers_name=None,
q_layer_name=None,
exclude_layers_name=[],
)
LLMTemplate.register_template(inkling_template)
print("[INFO]: Registered template 'inkling_mm_model'")
def _is_inkling_file2file_weight_tensor(tensor_name: str) -> bool:
if quark_is_linear_weight_tensor(tensor_name):
return True
parts = tensor_name.split(".")
return (
len(parts) == 7
and parts[0] == "model"
and parts[1] == "llm"
and parts[2] == "layers"
and parts[3].isdigit()
and parts[4] == "mlp"
and parts[5] == "experts"
and parts[6] in ("w13_weight", "w2_weight")
)
def _is_inkling_routed_expert_weight(tensor_name: str) -> bool:
parts = tensor_name.split(".")
return (
len(parts) == 7
and parts[0] == "model"
and parts[1] == "llm"
and parts[2] == "layers"
and parts[3].isdigit()
and 3 <= int(parts[3])
and parts[4] == "mlp"
and parts[5] == "experts"
and parts[6] in ("w13_weight", "w2_weight")
)
def _inkling_expert_chunk_size() -> int:
raw = os.environ.get("INKLING_QUARK_EXPERT_CHUNK_SIZE", "8")
try:
chunk_size = int(raw)
except ValueError as exc:
raise ValueError(f"INKLING_QUARK_EXPERT_CHUNK_SIZE must be an integer, got {raw!r}") from exc
if chunk_size < 1:
raise ValueError(f"INKLING_QUARK_EXPERT_CHUNK_SIZE must be >= 1, got {chunk_size}")
return chunk_size
def _fp4_nonzero_code_fraction(packed_weight: torch.Tensor) -> float:
flat = packed_weight.detach().reshape(-1)
if flat.numel() == 0:
return 0.0
max_sample = 1_000_000
if flat.numel() > max_sample:
stride = (flat.numel() + max_sample - 1) // max_sample
flat = flat[::stride][:max_sample]
low = flat & 0x0F
high = (flat >> 4) & 0x0F
nonzero = ((low != 0) & (low != 8)).sum() + ((high != 0) & (high != 8)).sum()
return float(nonzero.detach().cpu().item()) / float(2 * flat.numel())
def _quantize_weight_tensor(
tensor: torch.Tensor,
tensor_name: str,
layer_name: str,
weight_config,
) -> tuple[torch.Tensor, torch.Tensor]:
quantized_tensors: dict[str, torch.Tensor] = {}
file2file_quantization._single_stage_quantize_weight(
tensor=tensor,
tensor_name=tensor_name,
layer_name=layer_name,
weight_config=weight_config,
quantized_tensors=quantized_tensors,
output_weight_map=None,
safetensor_filename="",
)
return quantized_tensors[tensor_name].contiguous(), quantized_tensors[tensor_name + "_scale"].contiguous()
def _quantize_inkling_routed_expert_tensor(
tensor_name: str,
tensor: torch.Tensor,
layer_config,
) -> tuple[torch.Tensor, torch.Tensor]:
weight_config = layer_config.weight
assert isinstance(weight_config, file2file_quantization.QTensorConfig), (
f"weight config for {tensor_name} must be QTensorConfig"
)
if tensor.dim() != 3:
raise ValueError(f"{tensor_name}: expected stacked expert tensor with 3 dims, got {tuple(tensor.shape)}")
num_experts = tensor.shape[0]
rows = tensor.shape[1]
chunk_size = min(_inkling_expert_chunk_size(), num_experts)
print(
"[INKLING-F2F] chunked MXFP4 quantization "
f"tensor={tensor_name} shape={tuple(tensor.shape)} dtype={tensor.dtype} "
f"chunk_size={chunk_size}"
)
packed_out = None
scale_out = None
for expert_start in range(0, num_experts, chunk_size):
expert_end = min(expert_start + chunk_size, num_experts)
chunk = tensor[expert_start:expert_end].contiguous()
packed_chunk, scale_chunk = _quantize_weight_tensor(
chunk,
tensor_name,
".".join(tensor_name.split(".")[:-1]),
weight_config,
)
scale_chunk = scale_chunk.reshape((expert_end - expert_start) * rows, -1).contiguous()
if packed_out is None:
packed_out = torch.empty(
(num_experts, *packed_chunk.shape[1:]),
dtype=packed_chunk.dtype,
device=packed_chunk.device,
)
scale_out = torch.empty(
(num_experts * rows, scale_chunk.shape[1]),
dtype=scale_chunk.dtype,
device=scale_chunk.device,
)
print(
"[INKLING-F2F] allocated output "
f"tensor={tensor_name} packed_shape={tuple(packed_out.shape)} "
f"scale_shape={tuple(scale_out.shape)}"
)
packed_out[expert_start:expert_end].copy_(packed_chunk)
scale_out[expert_start * rows : expert_end * rows].copy_(scale_chunk)
nonzero_frac = _fp4_nonzero_code_fraction(packed_chunk)
scale_min = int(scale_chunk.detach().min().cpu().item())
scale_max = int(scale_chunk.detach().max().cpu().item())
print(
"[INKLING-F2F] chunk done "
f"tensor={tensor_name} experts={expert_start}:{expert_end} "
f"fp4_nonzero_code_frac={nonzero_frac:.6f} scale_min={scale_min} scale_max={scale_max}"
)
if nonzero_frac < 0.1:
print(
"[INKLING-F2F][WARN] suspicious mostly-zero FP4 chunk "
f"tensor={tensor_name} experts={expert_start}:{expert_end} "
f"fp4_nonzero_code_frac={nonzero_frac:.6f}"
)
del chunk, packed_chunk, scale_chunk
file2file_quantization._empty_cache_if_cuda(tensor.device)
assert packed_out is not None and scale_out is not None
sentinels = [0, 28, 29, 64, 127, 255]
for expert_id in sentinels:
if expert_id >= num_experts:
continue
nonzero_frac = _fp4_nonzero_code_fraction(packed_out[expert_id : expert_id + 1])
print(
"[INKLING-F2F] sentinel "
f"tensor={tensor_name} expert={expert_id} fp4_nonzero_code_frac={nonzero_frac:.6f}"
)
if nonzero_frac < 0.1:
print(
"[INKLING-F2F][WARN] suspicious mostly-zero sentinel "
f"tensor={tensor_name} expert={expert_id} fp4_nonzero_code_frac={nonzero_frac:.6f}"
)
return packed_out, scale_out
def _inkling_quantize_and_save_safetensor_shard(
safetensor_path: str,
export_path: str,
quant_config,
device: str | torch.device,
*,
keep_excluded_layers_as_original_model_state: bool,
model_dtype: torch.dtype,
keep_original_model_state_tensor_names_set: set[str] | None = None,
weight_converters: list | None = None,
output_weight_map: dict[str, str] | None = None,
input_scale_dict: dict[str, torch.Tensor] | None = None,
hf_model_config: dict | None = None,
source_weight_map: dict[str, str] | None = None,
scale_inv_cache: dict[str, torch.Tensor] | None = None,
presharded_weights: dict[str, int] | None = None,
**kwargs,
) -> None:
if kwargs:
print(f"[INKLING-F2F] ignoring Quark shard kwargs: {sorted(kwargs)}")
safetensor_filename = os.path.basename(safetensor_path)
logger.info(f"Loading {safetensor_filename}...")
tensors = file2file_quantization._load_safetensor_with_recover(
safetensor_path=safetensor_path,
quant_config=quant_config,
device=device,
keep_excluded_layers_as_original_model_state=keep_excluded_layers_as_original_model_state,
hf_model_config=hf_model_config,
weight_map=source_weight_map,
scale_inv_cache=scale_inv_cache,
keep_original_model_state_tensor_names_set=keep_original_model_state_tensor_names_set,
model_dtype=model_dtype,
presharded_weights=presharded_weights,
)
if weight_converters:
tensors = file2file_quantization._apply_weight_converters(tensors, weight_converters)
quantized_tensors: dict[str, torch.Tensor] = {}
for tensor_name, tensor in tensors.items():
if tensor_name.endswith((".weight_packed", ".weight_scale", ".weight_shape")):
continue
if output_weight_map is not None:
output_weight_map[tensor_name] = safetensor_filename
layer_name = ".".join(tensor_name.split(".")[:-1])
layer_config = file2file_quantization._get_layer_quant_config_by_tensor_name(
tensor_name=tensor_name,
quant_config=quant_config,
tensor_loaded=tensor,
)
if layer_config is not None and _is_inkling_routed_expert_weight(tensor_name):
packed_weight, scale = _quantize_inkling_routed_expert_tensor(tensor_name, tensor, layer_config)
quantized_tensors[tensor_name] = packed_weight
quantized_tensors[tensor_name + "_scale"] = scale
if output_weight_map is not None:
output_weight_map[tensor_name + "_scale"] = safetensor_filename
elif layer_config is not None:
weight_config = layer_config.weight
assert isinstance(weight_config, file2file_quantization.QTensorConfig), (
f"weight config for {layer_name} must be QTensorConfig"
)
packed_weight, scale = _quantize_weight_tensor(tensor, tensor_name, layer_name, weight_config)
quantized_tensors[tensor_name] = packed_weight
quantized_tensors[tensor_name + "_scale"] = scale
if output_weight_map is not None:
output_weight_map[tensor_name + "_scale"] = safetensor_filename
if input_scale_dict is not None:
if layer_name in input_scale_dict:
input_scale_key = layer_name + ".input_scale"
quantized_tensors[input_scale_key] = input_scale_dict[layer_name].contiguous()
if output_weight_map is not None:
output_weight_map[input_scale_key] = safetensor_filename
else:
logger.warning(f"Input scale not found for layer: {layer_name}")
else:
quantized_tensors[tensor_name] = tensor
del tensors
file2file_quantization._empty_cache_if_cuda(device)
output_path = os.path.join(export_path, safetensor_filename)
file2file_quantization.save_file(quantized_tensors, output_path)
output_size_mb = os.path.getsize(output_path) / (1024 * 1024)
logger.info(f"Saved {safetensor_filename} ({output_size_mb:.1f}MB)")
def _patch_inkling_file2file_weight_matcher() -> None:
file2file_quantization._is_linear_weight_tensor = _is_inkling_file2file_weight_tensor
file2file_quantization._quantize_and_save_safetensor_shard = _inkling_quantize_and_save_safetensor_shard
def _inkling_exclude_layers(hf_model_config: dict) -> list[str]:
text_config = hf_model_config.get("text_config") or hf_model_config
num_layers = int(text_config["num_hidden_layers"])
dense_mlp_idx = int(text_config.get("dense_mlp_idx", 2))
if num_layers <= 3 or dense_mlp_idx != 2:
raise RuntimeError(
f"Unexpected model config: num_hidden_layers={num_layers}, dense_mlp_idx={dense_mlp_idx}. "
)
return [
"model.audio*",
"model.visual*",
"model.mtp*",
"model.llm.embed*",
"model.llm.unembed",
"model.llm.norm",
"model.llm.embed_norm",
"model.llm.layers.0.*",
"model.llm.layers.1.*",
"model.llm.layers.2.*",
"model.llm.layers.*.attn*",
"model.llm.layers.*.*sconv",
"model.llm.layers.*.mlp.gate",
"model.llm.layers.*.mlp.shared_experts*",
"model.llm.layers.*.*norm",
]
# # --- Custom Quantization Schemes ---
# # Define custom quantization schemes using Quark's public QuantizationSpec classes.
# # These schemes can then be used via --quant_scheme <scheme_name>.
# # INT8 weight-only quantization
# int8_wo_scheme = QLayerConfig(weight=Int8PerTensorSpec().to_quantization_spec())
# LLMTemplate.register_scheme("int8_wo", config=int8_wo_scheme)
# print(f"[INFO]: Registered quantization scheme 'int8_wo'")
def _get_hf_model_config(model_dir: str) -> dict:
"""Read config.json from the model directory without loading the model."""
config_path = os.path.join(model_dir, "config.json")
with open(config_path) as f:
return json.load(f)
def _build_quant_config(args: argparse.Namespace, model_config_type: str):
"""Build quant_config from args and model_config_type (shared by normal and file-to-file paths)."""
if model_config_type not in LLMTemplate.list_available():
error_msg = (
f"\n[ERROR]: Model type '{model_config_type}' is not supported.\n\n"
f"Available templates: {LLMTemplate.list_available()}\n\n"
f"To add support for this model, uncomment and modify the 'Custom Model Templates'\n"
f"section at the top of this file to register a template for '{model_config_type}'.\n"
)
raise ValueError(error_msg)
template = LLMTemplate.get(model_config_type)
# Load algorithm configs from files if provided
algo_configs = {}
if args.quant_algo_config_file is not None:
for algo_name, algo_config_file in args.quant_algo_config_file:
algo_configs[algo_name] = load_quant_algo_config_from_file(algo_config_file)
print(f"[INFO]: Loaded algorithm configuration for {algo_name} from {algo_config_file}.")
# Build layer_config if --layer_quant_scheme is provided
layer_config = {}
if args.layer_quant_scheme is not None:
for layer_info in args.layer_quant_scheme:
layer_name = layer_info[0]
layer_scheme = layer_info[1]
layer_config[layer_name] = layer_scheme
quant_config = template.get_config(
scheme=args.quant_scheme,
algorithm=args.quant_algo,
kv_cache_scheme=args.kv_cache_dtype,
min_kv_scale=args.min_kv_scale,
layer_config=layer_config,
attention_scheme=args.attention_dtype,
exclude_layers=args.exclude_layers,
algo_configs=algo_configs if algo_configs else None,
)
quant_config.keep_prequantized_layers = not args.no_keep_prequantized_layers
return quant_config
def main(args: argparse.Namespace) -> None:
if args.revision is not None and os.path.isdir(args.model_dir):
raise ValueError(
f"The argument --revision {args.revision} is not supported using a local directory: {args.model_dir}"
)
elif not os.path.isdir(args.model_dir):
args.model_dir = snapshot_download(args.model_dir, revision=args.revision)
# Initialize global profiler
profiler = GlobalProfiler(output_path=os.path.join(args.output_dir, "quark_profile.yaml"))
# File-to-file quantization mode: bypass model loading, calibration and quantization,
# directly quantize safetensors files shard-by-shard and export.
if args.file2file_quantization:
print("\n[INFO]: File-to-file quantization mode enabled.")
hf_model_config = _get_hf_model_config(args.model_dir)
architectures = hf_model_config.get("architectures", [])
model_config_type = hf_model_config.get("model_type", architectures[0] if architectures else None)
if model_config_type == "inkling_mm_model":
_patch_inkling_file2file_weight_matcher()
args.exclude_layers = _inkling_exclude_layers(hf_model_config)
print(
f"[INFO]: Using hardcoded Inkling exclude_layers "
f"({len(args.exclude_layers)} patterns)."
)
text_config = hf_model_config.get("text_config") or hf_model_config
print(
"[INFO]: Inkling file-to-file matcher will quantize routed experts "
f"in layers 3-{int(text_config['num_hidden_layers']) - 1}."
)
quant_config = _build_quant_config(args, model_config_type)
print("\n[INFO]: Quantizing safetensors shards directly (file-to-file) ...")
weight_converters = LLMTemplate.get(model_config_type).f2f_weight_converters
if weight_converters:
logger.info(f"Applying {len(weight_converters)} weight converter(s) for model type '{model_config_type}'")
with profiler.scope(ProfileStep.FILE_TO_FILE_QUANTIZATION):
quantizer = ModelQuantizer(quant_config)
quantizer.direct_quantize_checkpoint(
pretrained_model_path=args.model_dir,
save_path=args.output_dir,
weight_converters=weight_converters,
keep_excluded_layers_as_original_model_state=args.keep_excluded_layers_as_original_model_state,
)
print(f"[INFO]: File-to-file quantization output saved to {args.output_dir}")
return
# 1. Define original model
model = None
# Load the pretrained model for quantization or for reload later (the old way).
if not args.model_reload or args.import_model_dir:
print("\n[INFO]: Loading model ...")
# We currently use CPU memory to load large models because GPU memory is typically smaller.
# The model will be dispatched to different GPUs based on the total number of GPUs specified by torchrun --nproc-per-node.
# TODO:
# The current method results in high CPU memory consumption due to multiple copies of the same model.
# We plan to address this in the future by implementing a more efficient way to dispatch the model to devices.
if args.use_tp:
device = "cpu"
else:
device = args.device
try:
with profiler.scope(ProfileStep.MODEL_LOADING):
model, _ = get_model(
args.model_dir,
args.data_type,
device,
args.multi_gpu,
args.multi_device,
args.model_attn_implementation,
trust_remote_code=args.trust_remote_code,
)
except torch.OutOfMemoryError as exception:
if torch.cuda.device_count() <= 1:
raise torch.OutOfMemoryError(
f"Out of memory error when loading the model {args.model_dir}. Only one device visible; this model does not fit on a single GPU."
) from exception
elif not args.multi_gpu:
raise torch.OutOfMemoryError(
f"Out of memory error when loading the model {args.model_dir}. Consider using `--multi_gpu` as {torch.cuda.device_count()} devices are available."
) from exception
else:
raise torch.OutOfMemoryError(
f"Out of memory error when loading the model {args.model_dir}. The model does not fit even with `--multi_gpu` across {torch.cuda.device_count()} devices. Consider using file-to-file quantization with `--file2file_quantization`, or make more GPU memory available."
) from exception
# Check model compatibility with current Transformers version
print("\n[INFO]: Checking model compatibility ...")
check_compatibility_before_quantization(model, raise_on_error=False)
if args.use_tp:
TPDeviceManager.tp_mesh_init()
# 2. (Optional) Reload quantized model
if args.params_load:
print("\nRestore quantized model from json and safetensors file ...")
model = load_params(model, json_path=args.json_path, safetensors_path=args.safetensors_path)
args.skip_quantization = True
elif args.model_reload:
# Use import_model_dir if provided (separate quantized checkpoint), otherwise model_dir is the checkpoint itself.
reload_dir = args.import_model_dir or args.model_dir
print("\nRestore quantized model from hf_format safetensors file ...")
model = import_model_from_safetensors(
model=model,
model_dir=reload_dir,
multi_device=args.multi_device,
trust_remote_code=args.trust_remote_code,
attn_implementation=args.model_attn_implementation,
device="cpu" if args.use_tp else args.device,
multi_gpu=args.multi_gpu,
)
args.skip_quantization = True
architectures = getattr(model.config, "architectures", None) or []
model_type = (
model.config.model_type
if hasattr(model.config, "model_type")
else (architectures[0] if architectures else None)
)
tokenizer = get_tokenizer(
args.model_dir, max_seq_len=args.seq_len, model_type=model_type, trust_remote_code=args.trust_remote_code
)
# Detect multimodality from the model config's sub-modality keys instead of a
# hardcoded model_type whitelist — every HF VLM/ALM config exposes one of these
# (vision_config / audio_config / image_config / video_config).
multimodal = any(
getattr(model.config, k, None) is not None
for k in ("vision_config", "audio_config", "image_config", "video_config")
)
if args.use_tp:
if TPDeviceManager._tp_mesh is not None:
_move_quantizer_to_dict(model.model)
device = TPDeviceManager._device
tp_mesh = TPDeviceManager._tp_mesh
model.tensor_parallel(tp_mesh)
model.to(device)
else:
warnings.warn(
"Quark tensor parallelism is not initialized properly. Please check the torchrun settings.",
UserWarning,
stacklevel=2,
)
return
# 3. Define calibration dataloader(still need this step for weight only and dynamic quantization in Quark for current version.)
print("\n[INFO]: Loading dataset ...")
# When the model is small, accelerate will place it on the last device
main_device = model.device if args.multi_gpu or args.multi_device else args.device
with profiler.scope(ProfileStep.DATASET_LOADING):
calib_dataloader = get_calib_dataloader(
dataset_name=args.dataset,
tokenizer=tokenizer,
batch_size=args.batch_size,
num_calib_data=args.num_calib_data,
seqlen=args.seq_len,
device=main_device,
)
# 4. Quantization
if not args.skip_quantization:
preprocess_for_quantization(model)
architectures = getattr(model.config, "architectures", None) or []
model_config_type = (
model.config.model_type
if hasattr(model.config, "model_type")
else (architectures[0] if architectures else None)
)
quant_config = _build_quant_config(args, model_config_type)
if getattr(args, "kv_cache_post_rope", False):
if hasattr(quant_config, "kv_cache_post_rope"):
quant_config.kv_cache_post_rope = True
else:
warnings.warn(
"--kv_cache_post_rope specified but quant_config has no 'kv_cache_post_rope' field; flag ignored.",
RuntimeWarning,
stacklevel=2,
)
# In-place replacement of model modules with quantized versions
quantizer = ModelQuantizer(quant_config, args.multi_device)
model = quantizer.quantize_model(model, calib_dataloader)
args.exclude_layers = quantizer.config.exclude
# After quantization, freeze models - moving from soft weights that are quantized on the fly
# to e.g. `QuantLinear.weight` actually holding the fake quantized weights.
runtime_options = None
if args.enable_native_inference:
runtime_options = RuntimeOptions(
native_linear_mode=args.native_linear_mode,
)
model = quantizer.freeze(model, runtime_options=runtime_options)
if args.model_export is not None:
# Save pre-processors (tokenizer, image processor, etc.).
export_dir = Path(args.output_dir)
export_dir.mkdir(parents=True, exist_ok=True)
maybe_save_preprocessors(
args.model_dir,
export_dir,
trust_remote_code=args.trust_remote_code,
)
if args.custom_mode != "quark" and args.export_weight_format == "fake_quantized":
raise ValueError("Exporting with 'fake_quantized' only supports custom_mode=quark")
# Export option 1: hugging-face safetensors format
if "hf_format" in args.model_export:
print("\n[INFO]: Exporting hugging face format safetensors...")
with profiler.scope(ProfileStep.EXPORT_HF_SAFETENSORS), torch.no_grad():
export_safetensors(
model=model,
output_dir=args.output_dir,
custom_mode=args.custom_mode,
weight_format=args.export_weight_format,
pack_method=args.pack_method,
)
# Export option 2: onnx
if "onnx" in args.model_export:
print("\n[INFO]: Exporting onnx graph...")
with profiler.scope(ProfileStep.EXPORT_ONNX), torch.inference_mode():
batch_iter = iter(calib_dataloader)
input_args = next(batch_iter)
if "uint4" in args.quant_scheme or "int4" in args.quant_scheme:
uint4_int4_flag = True
else:
uint4_int4_flag = False
export_onnx(
model=model, output_dir=args.output_dir, input_args=input_args, uint4_int4_flag=uint4_int4_flag
)
# Export option 3: gguf
if "gguf" in args.model_export:
print("\n[INFO]: Exporting gguf model...")
with profiler.scope(ProfileStep.EXPORT_GGUF), torch.inference_mode():
export_gguf(model, output_dir=args.output_dir, model_type=model_type, tokenizer_path=args.model_dir)
if args.torch_compile:
print("\n[INFO]: Calling PyTorch 2 torch.compile...")
# Note: The model after torch.compile may not be able to export to other format
model = torch.compile(model)
if args.params_save:
save_params(model, model_type=model_type, export_dir=args.save_dir)
if not args.skip_evaluation:
print("\n[INFO]: Evaluating ...")
with profiler.scope(ProfileStep.MODEL_EVALUATION):
args.use_ppl_eval_model = True
eval_model(
args,
model,
main_device,
save_metrics_to_csv=args.save_metrics_to_csv,
output_dir=args.metrics_output_dir,
multimodal=multimodal,
)
if args.use_tp:
TPDeviceManager.tp_cleanup()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
# Argument for model
parser.add_argument(
"--model_dir",
help="Specify where the HuggingFace model is. This example support Llama, OPT models",
required=True,
)
parser.add_argument(
"--revision",
help="HuggingFace Hub revision (branch, tag, or commit) to download when --model_dir is a Hub model ID. "
"Triggers snapshot_download so all files come from the same revision.",
default=None,
)
parser.add_argument("--device", help="Device for running the quantizer", default="cuda", choices=["cuda", "cpu"])
parser.add_argument(
"--multi_gpu",
nargs="?",
const="auto",
default=None,
choices=["auto", "balanced"],
help="Enable multi-GPU mode. 'auto': default accelerate device map. "
"'balanced': use auto-adjusted device map for better GPU memory balance.",
)
parser.add_argument(
"--model_attn_implementation",
help="The attention implementation to use in the model",
default="eager",
choices=["eager", "sdpa", "flash_attention_2"],
)
parser.add_argument(
"--multi_device",
action="store_true",
help="we allow you to use this mode to run a model quantization that exceeds the size of your gpu memory if you use args.multi_gpu and still run into OOM "
"now it only supports thr common quantization without algorithms, please note that this can lead to very slow quantization.",
)
# Argument for calibration dataset
parser.add_argument(
"--dataset",
help="Dataset for calibration",
default="pileval",
choices=[
"pileval",
"wikitext",
"cnn_dailymail",
"pileval_for_awq_benchmark",
"wikitext_for_gptq_benchmark",
"HuggingFaceH4/ultrachat_200k",
"ScienceQA",
],
)
parser.add_argument(
"--data_type", help="Datatype of the model", default="auto", choices=["auto", "float16", "bfloat16", "float32"]
)
parser.add_argument("--seq_len", type=int, help="Sequence length of data", default=512)
parser.add_argument("--batch_size", help="Batch size for calibration.", type=int, default=1)
parser.add_argument("--num_calib_data", help="Number of samples for calibration.", type=int, default=512)
# Argument for quantization
parser.add_argument("--skip_quantization", action="store_true")
parser.add_argument(
"--file2file_quantization",
action="store_true",
help="Enable file-to-file quantization mode. Quantizes safetensors shards directly without loading the full model into memory. "
"Bypasses model loading, calibration, and standard quantization flow. Requires --model_export hf_format.",
)
parser.add_argument(
"--quant_scheme",
help="Quantization scheme to use. Supported schemes: all built-in schemes and custom schemes registered."
"For the built-in schemes and their detailed configuration, see https://quark.docs.amd.com/latest/pytorch/user_guide_config_for_llm.html. "
"To register custom schemes, please uncomment and modify the 'Custom Quantization Schemes' section at the top of this file.",
choices=LLMTemplate.get_supported_schemes(),
default=None,
type=str,
)
parser.add_argument(
"--layer_quant_scheme",
action="append",
nargs=2,
metavar=("PATTERN", "QUANT_SCHEME"),
help="Directly specify a quantization scheme for layers matching the given pattern. "
"Can be repeated for multiple patterns. "
"Example: --quant_scheme int4_wo_128 --layer_quant_scheme lm_head int8 "
"(results in lm_head using int8 while other layers use int4_wo_128). "
"Supports wildcards: --layer_quant_scheme '*down_proj' fp8",
)
parser.add_argument(
"--kv_cache_dtype", "--kv_cache_quant_scheme", help="KV Cache dtype.", default=None, choices=["fp8", None]
)
parser.add_argument("--min_kv_scale", help="Minimum value of KV Cache scale.", type=float, default=0.0)
parser.add_argument(
"--kv_cache_post_rope",
action="store_true",
help="If set, quantize KV cache after RoPE (inside cache) instead of at k_proj/v_proj outputs.",
)
parser.add_argument(
"--attention_dtype", help="The dtype of attention quantization.", type=str, default=None, choices=["fp8"]
)
parser.add_argument(
"--quant_algo",
default=None,
type=lambda s: s.split(","),
metavar="alg1,alg2",
help="Comma-separated list of algorithms. Options include awq, gptq, smoothquant, rotation.",
)
parser.add_argument(
"--quant_algo_config_file",
action="append",
nargs=2,
metavar=("ALGO_NAME", "CONFIG_FILE"),
help="Specify a configuration file for a specific quantization algorithm. "
"Can be repeated for multiple algorithms. "
"Example: --quant_algo_config_file awq ./awq_config.json --quant_algo_config_file gptq ./gptq_config.json "
"(provides custom config files for AWQ and GPTQ algorithms).",
)
parser.add_argument(
"--exclude_layers",
type=str,
nargs="*", # Allows to pass a list of strings
default=None, # Default is None to allow model-specific layer exclusion
help='List of layers to exclude from quantization. Default depends on model type. Usage: `--exclude_layers "*down_proj*" "*31.fc*" "*k_proj"`. To avoid excluding layers at all, simply use `--exclude_layers` without any argument.',
)
parser.add_argument(
"--enable_native_inference",
action="store_true",
help="Enable native inference layer conversion during freeze().",
)
parser.add_argument(
"--native_linear_mode",
type=str,
default="auto",
choices=["auto", "fp8_per_tensor"],
help="Native linear implementation mode used when native inference is enabled.",
)
# Argument for reloading
parser.add_argument("--model_reload", help="safetensors or pth model reload", action="store_true")
parser.add_argument(
"--import_model_dir",
help="[Deprecated: use --model_dir instead] directory of hf or quark model, override model directory for reload, if not provided, --model_dir is used.",
)
parser.add_argument("--params_load", help="Model parameters load", action="store_true")
parser.add_argument("--json_path", help="Specify the path of saved json file")
parser.add_argument("--safetensors_path", help="Specify the path of saved safetensors file")
# Argument for export
parser.add_argument(
"--model_export",
help="Model export format",
default=None,
action="append",
choices=[None, "onnx", "hf_format", "gguf"],
)
parser.add_argument(
"--custom_mode",
help="When selecting `--custom_mode awq` or `--custom_mode fp8`, this legacy argument allows to export FP8 and AWQ models in the custom format they were exported with with quark<1.0, with custom config saved in the config.json, and config checkpoint format (AWQ uses `qzeros`, `qweight`, transposed `scales`).",
default="quark",
type=str,
choices=["quark", "awq", "fp8"],
)
parser.add_argument("--torch_compile", help="Model torch compile", action="store_true")
parser.add_argument(
"--pack_method", type=str, help="Pack method for awq_export", default="reorder", choices=["order", "reorder"]
)
parser.add_argument("--output_dir", default="exported_model")
parser.add_argument(
"--export_weight_format",
type=str,
help="Whether to export weights compressed or uncompressed",
default="real_quantized",
choices=["fake_quantized", "real_quantized"],
)
parser.add_argument(
"--no_keep_prequantized_layers",
action="store_true",
help="Force dequantization of excluded pre-quantized layers to bf16/fp16 on export. "
"By default (flag omitted), such layers are preserved in their original quantized format "
"(converted to Quark format); unsupported formats fall back to dequantization with a warning.",
)
parser.add_argument(
"--keep_excluded_layers_as_original_model_state",
action="store_true",
help="File-to-file mode only: keep already-quantized excluded layers (e.g. FP8 attention "
"in the official DeepSeek-V4 checkpoint) in their original on-disk format instead of "
"dequantizing them to bf16/fp16. Off by default; only enable for source checkpoints whose "
"quantization_config declares the excluded layers' format.",
)
# Argument for saving
parser.add_argument("--params_save", help="Model parameters save", action="store_true")
parser.add_argument(
"--save_dir",
help="Directory to save model parameters as safetensors or pth, in the case when --params_save is used.",
default="model_params",
)
# Argument for evaluation
parser.add_argument("--skip_evaluation", action="store_true")
parser.add_argument(
"--evaluation_dataset",
help="Dataset for evaluation",
default="wikitext",
choices=["wikitext", "wikitext_gpt_oss_120b", "wikitext_gpt_oss_20b"],
)
parser.add_argument("--use_ppl_eval_model", action="store_true")
parser.add_argument("--save_metrics_to_csv", action="store_true")
parser.add_argument("--metrics_output_dir", default="metrics_output_dir", help="Output path of csv with metrics.")
parser.add_argument(
"--tasks",
default=None,
type=str,
metavar="task1,task2",
help="Comma-separated list of task names or task groupings to evaluate on.",
)
parser.add_argument("--use_ppl_eval_for_kv_cache", action="store_true")
parser.add_argument(
"--ppl_eval_for_kv_cache_context_size",
type=int,
help="Context size used in PPL evaluation for KV cache.",
default=1024,
)
parser.add_argument(
"--ppl_eval_for_kv_cache_sample_size",
type=int,
help="Sample size used in PPL evaluation for KV cache.",
default=512,
)
parser.add_argument(
"--ppl_eval_for_kv_cache_patch_size",
type=int,
help="Patch size used in PPL evaluation for KV cache.",
default=None,
)
parser.add_argument(
"--eval_batch_size",
type=str,
default=1,
metavar="auto|auto:N|N",
help="Batch size used for evaluation. Acceptable values are 'auto', 'auto:N' or N, where N is a positive integer. Default is `1`.",
)
parser.add_argument(
"--max_eval_batch_size",
type=int,
default=64,
metavar="P",
help="Maximal batch size to try with `--batch_size auto`.",
)
parser.add_argument(
"--num_eval_data",
help="Number of samples for evaluation. The default value is -1, which means the entire dataset is used for evaluation.",
type=int,
default=-1,
)
parser.add_argument(
"--num_fewshot", type=int, default=None, metavar="N", help="Number of examples in few-shot context"
)
parser.add_argument(
"--apply_chat_template",
action="store_true",
help="Providing `--apply_chat_template` without an argument will apply the default chat template to the prompt.",
)
parser.add_argument("--use_mlperf_rouge", action="store_true")
parser.add_argument("--eval_data_dir", help="Dataset for evaluation", type=str, default=None)
parser.add_argument(
"--use_tp", action="store_true", help="Enable tensor parallelism exclusively for model evaluation."
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--trust_remote_code",
action="store_true",
dest="trust_remote_code",
help="Enable execution of custom model code from the Hub (use only with repositories you fully trust).",
)
group.add_argument(
"--no_trust_remote_code",
action="store_false",
dest="trust_remote_code",
help="Disable execution of custom model code from the Hub (safer, recommended if unsure).",
)
parser.set_defaults(trust_remote_code=True)
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
if args.layer_quant_scheme is not None:
for layer_info in args.layer_quant_scheme:
if len(layer_info) != 2:
raise ValueError(
f"Invalid --layer_quant_scheme argument: {layer_info}. "
f"Expected exactly 2 values (PATTERN, QUANT_SCHEME), but got {len(layer_info)}."
)
if args.quant_algo_config_file is not None:
for algo_config in args.quant_algo_config_file:
if len(algo_config) != 2:
raise ValueError(
f"Invalid --quant_algo_config_file argument: {algo_config}. "
f"Expected exactly 2 values (ALGO_NAME, CONFIG_FILE), but got {len(algo_config)}."
)
algo_name, config_file = algo_config
if not os.path.isfile(config_file):
raise ValueError(
f"Configuration file '{config_file}' for algorithm '{algo_name}' does not exist. "
f"Please provide a valid config file path."
)
main(args)