import os import json import glob import warnings import argparse warnings.filterwarnings("ignore") os.environ['TOKENIZERS_PARALLELISM'] = 'false' os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python' import onnx import torch from utils.model import LlmModel, EmbeddingModel from utils.tokenizer import LlmTokenizer from utils.spinner import spinner_run from utils.custom_op import FakeLinear from utils.onnx_rebuilder import OnnxRebuilder from utils.mnn_converter import MNNConverter from utils.awq_quantizer import AwqQuantizer from utils.smooth_quantizer import SmoothQuantizer from utils.omni_quantizer import OmniQuantizer from utils.torch_utils import onnx_export class LlmExporter(torch.nn.Module): ''' Base class for all llm model export. Inherits from [`torch.nn.Module`]. ''' def __init__(self, args): super().__init__() self.init_from_args(args) self.load_model(args.path) def init_from_args(self, args): self.args = args self.max_new_tokens = 1024 self.dst_name = 'llm' # load config from args self.onnx_path = os.path.join(self.args.dst_path, 'onnx') if self.args.tokenizer_path is None: self.args.tokenizer_path = self.args.path if args.lm_quant_bit is None: self.args.lm_quant_bit = self.args.quant_bit if args.lm_quant_block is None: self.args.lm_quant_block = self.args.quant_block self.args.tie_word_embeddings = False # init export dst dir if not os.path.exists(self.args.dst_path): os.makedirs(self.args.dst_path) if not os.path.exists(self.onnx_path): os.makedirs(self.onnx_path) @spinner_run(f'load pretrained model ', True) def load_model(self, model_path): self.model = LlmModel.from_pretrained(model_path, args=self.args) self.tokenizer = LlmTokenizer.from_pretrained( self.args.tokenizer_path, model_type=self.model.config.model_type ) self.model.tokenizer = self.tokenizer self.config = self.model.config self.model_type = self.config.model_type if self.args.awq or self.args.smooth: self.model.float() if self.args.export is not None: # set norm's weight as float for export def visit_module(module): if not isinstance(module, torch.nn.Linear) and hasattr(module, 'weight'): module.float() for name, child in module.named_children(): visit_module(child) visit_module(self.model) self.model_dynamic_axes = { "input_ids" : { 0: "seq_len" }, "attention_mask" : { 2: "seq_len", 3: "seq_len" }, "position_ids" : { 1: "seq_len" }, } self.llm_config = { 'model_type': self.config.model_type, 'hidden_size' : self.config.hidden_size, 'layer_nums': self.config.num_hidden_layers, 'attention_mask': 'float', # Will be determined by model later 'attention_type': self.config.attention_type, 'is_mrope': self.model.rotary.is_mrope } self.llm_config.update(self.model.get_config()) # Attention scaling (gemma4 uses 1.0 instead of 1/sqrt(head_dim)) if hasattr(self.model, 'blocks') and len(self.model.blocks) > 0: attn = self.model.blocks[0].self_attn if hasattr(attn, 'attn_scaling') and attn.attn_scaling != 1.0 / (self.config.head_dim ** 0.5): self.llm_config['attn_scale'] = attn.attn_scaling if self.config.sliding_window > 0: self.llm_config['sliding_window'] = self.config.sliding_window if hasattr(self.tokenizer, 'get_chat_template'): chat_template = self.tokenizer.get_chat_template() if chat_template is not None: self.llm_config['jinja'] = { 'chat_template': chat_template } if self.tokenizer.bos_token: self.llm_config['jinja']['bos'] = self.tokenizer.bos_token if self.tokenizer.eos_token: self.llm_config['jinja']['eos'] = self.tokenizer.eos_token # gemma4's HF template is too complex for minja parser, use simplified version if self.model_type == 'gemma4': self.llm_config['jinja'] = { 'chat_template': "{{ bos_token }}{% for message in messages %}{% if message.role == \"system\" %}<|turn>system\n{{ message.content }}\n{% elif message.role == \"user\" %}<|turn>user\n{{ message.content }}\n{% elif message.role == \"assistant\" %}<|turn>model\n{{ message.content }}\n{% endif %}{% endfor %}{% if add_generation_prompt %}<|turn>model\n{% endif %}", 'bos': '', 'eos': '' } # glm_ocr's HF template is too complex for minja parser, use simplified version if self.model_type == 'glm_ocr': self.llm_config['jinja'] = { 'chat_template': "[gMASK]{% for message in messages %}{% if message.role == \"user\" %}<|user|>\n{{ message.content }}{% elif message.role == \"assistant\" %}<|assistant|>\n{{ message.content }}{% elif message.role == \"system\" %}<|system|>\n{{ message.content }}{% endif %}{% endfor %}{% if add_generation_prompt %}<|assistant|>\n{% endif %}", 'eos': '<|endoftext|>' } # tie word embeddings self.args.tie_word_embeddings = not self.args.seperate_embed and self.model.lm.lm.weight.equal(self.model.embed.embed.weight) # Pass properties from model to exporter self.visual = self.model.visual self.audio = self.model.audio self.talker = self.model.talker self.mtp = self.model.mtp self.scale_emb = self.model.scale_emb return model_path @torch.no_grad() def response(self, query): # self.imitate_quant() self.model.decode_buffer = [] messages = [ {"role": "user", "content": query} ] prompt = self.tokenizer.apply_chat_template(messages) if query not in prompt: prompt = query # Use model's tokenizer methods for encoding # For models with both visual and audio (e.g., gemma4), check content type has_audio = self.model.audio is not None and '