Image-Text-to-Text
Transformers
Safetensors
inkling_mm_model
amd-quark
mxfp4
rocm
tokenspeed
conversational
8-bit precision
quark
Instructions to use lightseekorg/Inkling-Small-MXFP4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use lightseekorg/Inkling-Small-MXFP4 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="lightseekorg/Inkling-Small-MXFP4") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("lightseekorg/Inkling-Small-MXFP4") model = AutoModelForMultimodalLM.from_pretrained("lightseekorg/Inkling-Small-MXFP4", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use lightseekorg/Inkling-Small-MXFP4 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "lightseekorg/Inkling-Small-MXFP4" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "lightseekorg/Inkling-Small-MXFP4", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/lightseekorg/Inkling-Small-MXFP4
- SGLang
How to use lightseekorg/Inkling-Small-MXFP4 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "lightseekorg/Inkling-Small-MXFP4" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "lightseekorg/Inkling-Small-MXFP4", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "lightseekorg/Inkling-Small-MXFP4" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "lightseekorg/Inkling-Small-MXFP4", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use lightseekorg/Inkling-Small-MXFP4 with Docker Model Runner:
docker model run hf.co/lightseekorg/Inkling-Small-MXFP4
| # | |
| # 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) | |