144 lines
5.4 KiB
Python
144 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Quantize nomic-embed-text-v1.5 with AMD Quark for NPU (XDNA) inference.
|
|
Targets int8 QDQ format with enable_npu_transformer=True so VAIML can map
|
|
attention MatMul ops to the NPU.
|
|
"""
|
|
|
|
import json
|
|
import numpy as np
|
|
import onnxruntime
|
|
from pathlib import Path
|
|
|
|
MODEL_PATH = Path.home() / ".cache/breadsearch/models/model.onnx"
|
|
TOKENIZER_PATH = Path.home() / ".cache/breadsearch/models/tokenizer.json"
|
|
OUTPUT_PATH = Path.home() / ".cache/breadsearch/models/model_quantized.onnx"
|
|
|
|
CALIB_SENTENCES = [
|
|
"search_document: The quick brown fox jumps over the lazy dog.",
|
|
"search_document: Machine learning is a branch of artificial intelligence.",
|
|
"search_document: The Eiffel Tower is located in Paris, France.",
|
|
"search_document: Python is a high-level programming language.",
|
|
"search_document: The speed of light is approximately 299,792,458 meters per second.",
|
|
"search_document: Climate change is one of the greatest challenges facing humanity.",
|
|
"search_document: Quantum computing leverages quantum mechanical phenomena.",
|
|
"search_document: The human genome contains approximately 3 billion base pairs.",
|
|
"search_document: Rust is a systems programming language focused on safety.",
|
|
"search_document: Neural networks are inspired by the structure of the brain.",
|
|
"search_document: The Milky Way galaxy contains over 100 billion stars.",
|
|
"search_document: Cryptography is the practice of secure communication.",
|
|
"search_document: The Linux kernel was first released in 1991 by Linus Torvalds.",
|
|
"search_document: Databases store and retrieve structured data efficiently.",
|
|
"search_document: The TCP/IP protocol suite is the backbone of the internet.",
|
|
"search_document: Embedded systems run on resource-constrained hardware.",
|
|
"search_document: The ONNX format provides a standard for machine learning models.",
|
|
"search_document: Transformer models use attention mechanisms for sequence tasks.",
|
|
"search_document: File systems organize and manage data storage on disks.",
|
|
"search_document: Async programming allows concurrent execution without threads.",
|
|
"search_query: What is machine learning?",
|
|
"search_query: How does attention work in transformers?",
|
|
"search_query: Where is the Eiffel Tower?",
|
|
"search_query: What programming language should I learn first?",
|
|
"search_query: How fast is the speed of light?",
|
|
]
|
|
|
|
MAX_SEQ_LEN = 512
|
|
|
|
def load_tokenizer(path: Path):
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
|
|
def simple_tokenize(tok_data: dict, text: str, max_len: int) -> dict[str, np.ndarray]:
|
|
vocab = tok_data["model"]["vocab"]
|
|
unk_id = vocab.get("[UNK]", 100)
|
|
cls_id = vocab.get("[CLS]", 101)
|
|
sep_id = vocab.get("[SEP]", 102)
|
|
pad_id = vocab.get("[PAD]", 0)
|
|
|
|
words = text.lower().split()
|
|
token_ids = [cls_id]
|
|
for w in words:
|
|
token_ids.append(vocab.get(w, unk_id))
|
|
token_ids.append(sep_id)
|
|
|
|
if len(token_ids) > max_len:
|
|
token_ids = token_ids[:max_len - 1] + [sep_id]
|
|
|
|
seq_len = len(token_ids)
|
|
padded = token_ids + [pad_id] * (max_len - seq_len)
|
|
mask = [1] * seq_len + [0] * (max_len - seq_len)
|
|
type_ids = [0] * max_len
|
|
|
|
return {
|
|
"input_ids": np.array([padded], dtype=np.int64),
|
|
"attention_mask": np.array([mask], dtype=np.int64),
|
|
"token_type_ids": np.array([type_ids], dtype=np.int64),
|
|
}
|
|
|
|
|
|
class NomicCalibrationReader:
|
|
def __init__(self, sentences, tok_data, max_len=MAX_SEQ_LEN):
|
|
self.inputs = [simple_tokenize(tok_data, s, max_len) for s in sentences]
|
|
self.idx = 0
|
|
|
|
def get_next(self):
|
|
if self.idx >= len(self.inputs):
|
|
return None
|
|
sample = self.inputs[self.idx]
|
|
self.idx += 1
|
|
return sample
|
|
|
|
def rewind(self):
|
|
self.idx = 0
|
|
|
|
|
|
def main():
|
|
print(f"Loading tokenizer from {TOKENIZER_PATH}")
|
|
tok_data = load_tokenizer(TOKENIZER_PATH)
|
|
|
|
calib_reader = NomicCalibrationReader(CALIB_SENTENCES, tok_data)
|
|
|
|
# Verify model input names match what we provide
|
|
sess = onnxruntime.InferenceSession(str(MODEL_PATH), providers=["CPUExecutionProvider"])
|
|
input_names = [inp.name for inp in sess.get_inputs()]
|
|
print(f"Model inputs: {input_names}")
|
|
del sess
|
|
|
|
from quark.onnx import ModelQuantizer, QuantizationConfig
|
|
from quark.onnx.quantization.config.config import Config
|
|
from onnxruntime.quantization import CalibrationMethod, QuantFormat, QuantType
|
|
|
|
quant_config = QuantizationConfig(
|
|
calibrate_method=CalibrationMethod.MinMax,
|
|
quant_format=QuantFormat.QDQ,
|
|
activation_type=QuantType.QInt8,
|
|
weight_type=QuantType.QInt8,
|
|
per_channel=False,
|
|
reduce_range=False,
|
|
optimize_model=True,
|
|
enable_npu_transformer=True,
|
|
include_cle=True,
|
|
print_summary=True,
|
|
extra_options={
|
|
"ActivationSymmetric": True,
|
|
"WeightSymmetric": True,
|
|
},
|
|
)
|
|
|
|
config = Config(global_quant_config=quant_config)
|
|
quantizer = ModelQuantizer(config)
|
|
print(f"Quantizing {MODEL_PATH} → {OUTPUT_PATH}")
|
|
print("This may take several minutes...")
|
|
|
|
quantizer.quantize_model(
|
|
model_input=str(MODEL_PATH),
|
|
model_output=str(OUTPUT_PATH),
|
|
calibration_data_reader=calib_reader,
|
|
)
|
|
|
|
print(f"\nQuantized model saved to {OUTPUT_PATH}")
|
|
print(f"Size: {OUTPUT_PATH.stat().st_size / 1024 / 1024:.1f} MB")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|