Instructions to use chris320211/flatquant-phi3-mini-4k-g52xlarge with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use chris320211/flatquant-phi3-mini-4k-g52xlarge with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="chris320211/flatquant-phi3-mini-4k-g52xlarge", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("chris320211/flatquant-phi3-mini-4k-g52xlarge", trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained("chris320211/flatquant-phi3-mini-4k-g52xlarge", trust_remote_code=True, device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.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=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use chris320211/flatquant-phi3-mini-4k-g52xlarge with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "chris320211/flatquant-phi3-mini-4k-g52xlarge" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "chris320211/flatquant-phi3-mini-4k-g52xlarge", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/chris320211/flatquant-phi3-mini-4k-g52xlarge
- SGLang
How to use chris320211/flatquant-phi3-mini-4k-g52xlarge 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 "chris320211/flatquant-phi3-mini-4k-g52xlarge" \ --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": "chris320211/flatquant-phi3-mini-4k-g52xlarge", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'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 "chris320211/flatquant-phi3-mini-4k-g52xlarge" \ --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": "chris320211/flatquant-phi3-mini-4k-g52xlarge", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use chris320211/flatquant-phi3-mini-4k-g52xlarge with Docker Model Runner:
docker model run hf.co/chris320211/flatquant-phi3-mini-4k-g52xlarge
Download overlay.patch from chris320211/flatquant-phi3-mini-4k-g52xlarge: direct link, hf CLI and curl.
- Browser
- Download file 38.7 kB
-
https://huggingface.co/chris320211/flatquant-phi3-mini-4k-g52xlarge/resolve/main/overlay.patch
- Command line
-
hf download hf://chris320211/flatquant-phi3-mini-4k-g52xlarge/overlay.patch
-
curl -L -o overlay.patch https://huggingface.co/chris320211/flatquant-phi3-mini-4k-g52xlarge/resolve/main/overlay.patch
38.7 kB
| diff --git a/deploy/functional/__init__.py b/deploy/functional/__init__.py | |
| index 29951ec..2d74dcc 100644 | |
| --- a/deploy/functional/__init__.py | |
| +++ b/deploy/functional/__init__.py | |
| from .quantization import pack_i4, unpack_i4, asym_quant_dequant, sym_quant_dequant | |
| -from .online_trans import ( | |
| - matmul_hadU_cuda, get_decompose_dim) | |
| + | |
| +try: | |
| + from .online_trans import matmul_hadU_cuda, get_decompose_dim | |
| +except ImportError: # Hadamard extra is optional; packed W4A4 uses CUDA matmul | |
| + matmul_hadU_cuda = None | |
| + | |
| + def get_decompose_dim(n): | |
| + a = int(n ** 0.5) | |
| + if a * a < n: | |
| + a += 1 | |
| + while True: | |
| + tmp = a * a - n | |
| + b = int(tmp ** 0.5) | |
| + if b * b == tmp: | |
| + break | |
| + a += 1 | |
| + return a - b, a + b | |
| diff --git a/deploy/nn/linear.py b/deploy/nn/linear.py | |
| index 026cb5d..cb7a16b 100644 | |
| --- a/deploy/nn/linear.py | |
| +++ b/deploy/nn/linear.py | |
| class Linear4bit(torch.nn.Module): | |
| super().__init__() | |
| self.in_features = in_features | |
| self.out_features = out_features | |
| + # CUDA sym_dequant takes half* scale_col; default zeros() is float32. | |
| self.register_buffer('weight_scales', | |
| - torch.zeros((self.out_features, 1), requires_grad=False)) | |
| + torch.zeros((self.out_features, 1), dtype=torch.float16, requires_grad=False)) | |
| self.register_buffer('weight', (torch.randint(1, 7, (self.out_features, self.in_features // 2), | |
| # SubByte weight | |
| dtype=torch.uint8, requires_grad=False))) | |
| class Linear4bit(torch.nn.Module): | |
| #shape_handler = ShapeHandler(quantized_x) | |
| #quantized_x = shape_handler.flatten(quantized_x) | |
| x = deploy.matmul(x, self.weight) | |
| - #out = shape_handler.unflatten( | |
| - # deploy.sym_dequant(int_result, scales_x, self.weight_scales)) | |
| + scales_x = scales_x.to(dtype=torch.float16) | |
| + weight_scales = self.weight_scales.to(dtype=torch.float16) | |
| if self.bias is not None: | |
| - return deploy.sym_dequant(x, scales_x, self.weight_scales) + self.bias | |
| + return deploy.sym_dequant(x, scales_x, weight_scales) + self.bias | |
| else: | |
| - return deploy.sym_dequant(x, scales_x, self.weight_scales) | |
| + return deploy.sym_dequant(x, scales_x, weight_scales) | |
| @staticmethod | |
| def from_float(module: torch.nn.Linear, weight_scales=None,): | |
| class Linear4bit(torch.nn.Module): | |
| weight_matrix = module.weight.data | |
| - int_module = Linear4bit(module.in_features, module.out_features, bias=module.bias is not None, dtype=weight_matrix.dtype).to(weight_matrix.dtype) | |
| + int_module = Linear4bit(module.in_features, module.out_features, bias=module.bias is not None, dtype=torch.float16) | |
| if weight_scales is not None: | |
| assert weight_scales.shape == (module.out_features, 1), 'weight_scales should have shape (out_features, 1)' | |
| weight_matrix = weight_matrix.cuda() | |
| - int_module.weight_scales.copy_(weight_scales.to(weight_matrix.dtype)) | |
| - int_rounded_weight = (weight_matrix/weight_scales.cuda()).round() | |
| + scales = weight_scales.detach().to(device=weight_matrix.device, dtype=torch.float16) | |
| + int_module.weight_scales.copy_(scales.cpu() if int_module.weight_scales.device.type == "cpu" else scales) | |
| + int_rounded_weight = (weight_matrix / scales).round() | |
| int_module.weight.copy_(deploy.functional.pack_i4(int_rounded_weight.to(torch.int8)).cpu()) | |
| if module.bias is not None: | |
| - int_module.bias.copy_(module.bias) | |
| + int_module.bias.copy_(module.bias.to(dtype=torch.float16)) | |
| return int_module | |
| diff --git a/deploy/nn/quantization.py b/deploy/nn/quantization.py | |
| index 31e6a49..91dedd5 100644 | |
| --- a/deploy/nn/quantization.py | |
| +++ b/deploy/nn/quantization.py | |
| class Quantizer(torch.nn.Module): | |
| def forward(self, x): | |
| if not isinstance(x, deploy.PackedQuantizedTensor): | |
| + x = x.to(dtype=torch.float16) | |
| if self.lac: | |
| reshaped_x = x.reshape((-1, x.shape[-1])) | |
| xmax, xmin = reshaped_x.amax(1, keepdim=True), reshaped_x.amin(1, keepdim=True) | |
| diff --git a/flatquant/args_utils.py b/flatquant/args_utils.py | |
| index 1cda369..ee49361 100644 | |
| --- a/flatquant/args_utils.py | |
| +++ b/flatquant/args_utils.py | |
| supported_models = [ | |
| './modelzoo/meta-llama/Llama-3.3-70B-Instruct', | |
| './modelzoo/Qwen/Qwen2.5-7B-Instruct', | |
| './modelzoo/Qwen/Qwen2.5-32B-Instruct', | |
| + './modelzoo/microsoft/Phi-3-mini-4k-instruct', | |
| + 'microsoft/Phi-3-mini-4k-instruct', | |
| ] | |
| supported_datasets = ['wikitext2', 'c4', 'pile'] | |
| def parser_gen(): | |
| # General Arguments | |
| parser.add_argument('--model', type=str, default='meta-llama/Llama-2-7b-hf', | |
| - help='Model to load.', choices=supported_models) | |
| + help='Model to load.') | |
| parser.add_argument('--seed', type=int, default=0, help='Random seed for HuggingFace and PyTorch.') | |
| parser.add_argument('--hf_token', type=str, default=None, help='HuggingFace token for model access.') | |
| diff --git a/flatquant/data_utils.py b/flatquant/data_utils.py | |
| index 5ddc680..ffc53dc 100644 | |
| --- a/flatquant/data_utils.py | |
| +++ b/flatquant/data_utils.py | |
| class TokenizerWrapper: | |
| self.input_ids = input_ids | |
| +def _load_wikitext(split): | |
| + local_dir = './datasets/wikitext' | |
| + if os.path.isdir(local_dir): | |
| + return datasets.load_dataset(local_dir, 'wikitext-2-raw-v1', split=split) | |
| + return datasets.load_dataset('wikitext', 'wikitext-2-raw-v1', split=split) | |
| + | |
| + | |
| def get_wikitext2(nsamples, seqlen, tokenizer, eval_mode=False): | |
| if eval_mode: | |
| - testdata = datasets.load_dataset('./datasets/wikitext', 'wikitext-2-raw-v1', split='test') | |
| + testdata = _load_wikitext('test') | |
| testenc = tokenizer("\n\n".join(testdata['text']), return_tensors='pt') | |
| return testenc | |
| else: | |
| - traindata = datasets.load_dataset('./datasets/wikitext', 'wikitext-2-raw-v1', split='train') | |
| + traindata = _load_wikitext('train') | |
| traindata = traindata.filter(lambda x: len(x) > 0) | |
| traindata = traindata.map(lambda x : {'text': x['text'].strip()}) | |
| trainenc = tokenizer("\n\n".join(traindata['text']), return_tensors='pt') | |
| diff --git a/flatquant/flat_linear.py b/flatquant/flat_linear.py | |
| index 6e142de..45172d9 100644 | |
| --- a/flatquant/flat_linear.py | |
| +++ b/flatquant/flat_linear.py | |
| class FlatQuantizedLinear(nn.Module): | |
| self.sigmoid = nn.Sigmoid() | |
| self._eval_mode = False | |
| + self._packed = False | |
| def apply_wclip(self, weight): | |
| wmin, wmax = weight.min(1, keepdim=True)[0], weight.max(1, keepdim=True)[0] | |
| class FlatQuantizedLinear(nn.Module): | |
| return self._eval_forward(hidden_states) | |
| def _eval_forward(self, hidden_states): | |
| + if getattr(self, "_packed", False): | |
| + return self.linear(self.a4(hidden_states)) | |
| x_dtype = hidden_states.dtype | |
| hidden_states = self.act_quantizer(hidden_states).to(x_dtype) | |
| diff --git a/flatquant/model_tools/phi3_utils.py b/flatquant/model_tools/phi3_utils.py | |
| new file mode 100644 | |
| index 0000000..cc62529 | |
| --- /dev/null | |
| +++ b/flatquant/model_tools/phi3_utils.py | |
| +import math | |
| + | |
| +import torch | |
| +import torch.nn as nn | |
| +import torch.nn.functional as F | |
| + | |
| +from flatquant.quant_utils import ActivationQuantizer | |
| +from flatquant.utils import skip_initialization | |
| +from flatquant.function_utils import get_init_scale, get_decompose_dim | |
| +from flatquant.trans_utils import SVDSingleTransMatrix, SVDDecomposeTransMatrix | |
| +from flatquant.trans_utils import InvSingleTransMatrix, InvDecomposeTransMatrix | |
| +from flatquant.flat_linear import FlatQuantizedLinear | |
| + | |
| +from transformers.models.phi3.modeling_phi3 import apply_rotary_pos_emb, repeat_kv | |
| + | |
| + | |
| +def _clip_scalar(value): | |
| + """Triton kron_matmul needs a Python float; 0-d tensors compile as pointers.""" | |
| + if value is None: | |
| + return 1.0 | |
| + if torch.is_tensor(value): | |
| + return float(value.detach().reshape(-1)[0].cpu()) | |
| + return float(value) | |
| + | |
| + | |
| +def _eval_proj(module, hidden_states, trans=None): | |
| + """Eval projection: fuse decompose T + act quant into repo kron_matmul when packed. | |
| + | |
| + Import deploy.kernels.kron_matmul, not deploy.functional.online_trans (that | |
| + module imports optional fast_hadamard_transform at load). | |
| + """ | |
| + packed = getattr(module, "_packed", False) | |
| + if ( | |
| + packed | |
| + and trans is not None | |
| + and getattr(trans, "_eval_mode", False) | |
| + and hasattr(trans, "matrix_left") | |
| + and hasattr(trans, "matrix_right") | |
| + and hidden_states.dim() == 3 | |
| + ): | |
| + from deploy.kernels.kron_matmul import kron_matmul | |
| + | |
| + a4 = module.a4 | |
| + bsz, seq_len, _ = hidden_states.shape | |
| + left = trans.matrix_left.detach().to(dtype=hidden_states.dtype).T.contiguous() | |
| + right = trans.matrix_right.detach().to(dtype=hidden_states.dtype).contiguous() | |
| + x = hidden_states.contiguous().reshape(-1, left.shape[0], right.shape[0]) | |
| + packed_x = kron_matmul( | |
| + left, | |
| + x, | |
| + right, | |
| + seq_len, | |
| + _clip_scalar(getattr(a4, "clip_factor_a_max", 1.0)), | |
| + _clip_scalar(getattr(a4, "clip_factor_a_min", 1.0)), | |
| + ) | |
| + packed_x.quantized_x = packed_x.quantized_x.reshape(bsz, seq_len, -1) | |
| + packed_x.scales_x = packed_x.scales_x.reshape(bsz, 1, seq_len) | |
| + return module.linear(packed_x) | |
| + if trans is not None: | |
| + hidden_states = trans(hidden_states) | |
| + if packed: | |
| + return module.linear(module.a4(hidden_states)) | |
| + return module(hidden_states) | |
| + | |
| + | |
| +class FlatQuantPhi3MLP(nn.Module): | |
| + """FlatQuant wrapper for Phi-3 fused gate_up_proj + down_proj.""" | |
| + | |
| + def __init__(self, args, module): | |
| + super().__init__() | |
| + self.args = args | |
| + self.config = getattr(module, "config", None) | |
| + self.hidden_size = getattr(module, "hidden_size", module.gate_up_proj.in_features) | |
| + self.intermediate_size = module.down_proj.in_features | |
| + self.act_fn = module.activation_fn | |
| + self.gate_up_proj = FlatQuantizedLinear(args, module.gate_up_proj) | |
| + self.down_proj = FlatQuantizedLinear(args, module.down_proj) | |
| + self.add_fq_trans() | |
| + | |
| + self._ori_mode = False | |
| + self.diag_init = args.diag_init | |
| + if self.diag_init == "sq_style": | |
| + self.up_smax = torch.ones_like(self.gate_up_proj.linear.weight.abs().max(dim=0)[0]).cuda() * 1e-5 | |
| + self.down_smax = torch.ones_like(self.down_proj.linear.weight.abs().max(dim=0)[0]).cuda() * 1e-5 | |
| + | |
| + def add_fq_trans(self): | |
| + if self.args.direct_inv: | |
| + DecomposeTransMatrix = InvDecomposeTransMatrix | |
| + else: | |
| + DecomposeTransMatrix = SVDDecomposeTransMatrix | |
| + if self.args.w_bits < 16 or self.args.a_bits < 16: | |
| + up_dim_left, up_dim_right = get_decompose_dim(self.gate_up_proj.linear.weight.shape[1]) | |
| + self.up_gate_trans = DecomposeTransMatrix(up_dim_left, up_dim_right, add_diag=self.args.add_diag) | |
| + down_dim_left, down_dim_right = get_decompose_dim(self.down_proj.linear.weight.shape[1]) | |
| + self.down_trans = DecomposeTransMatrix(down_dim_left, down_dim_right, add_diag=self.args.add_diag) | |
| + else: | |
| + self.up_gate_trans, self.down_trans = None, None | |
| + | |
| + def _trans_forward(self, x): | |
| + up_states = _eval_proj(self.gate_up_proj, x, self.up_gate_trans) | |
| + gate, up_states = up_states.chunk(2, dim=-1) | |
| + x_act_fn = self.act_fn(gate) * up_states | |
| + return _eval_proj(self.down_proj, x_act_fn, self.down_trans) | |
| + | |
| + def _ori_forward(self, x): | |
| + if self.diag_init == "sq_style": | |
| + self.up_smax = torch.maximum( | |
| + self.up_smax, x.reshape(-1, x.shape[-1]).abs().max(0)[0].clone().detach() | |
| + ) | |
| + up_states = self.gate_up_proj._ori_forward(x) | |
| + gate, up_states = up_states.chunk(2, dim=-1) | |
| + x = self.act_fn(gate) * up_states | |
| + if self.diag_init == "sq_style": | |
| + self.down_smax = torch.maximum( | |
| + self.down_smax, x.reshape(-1, x.shape[-1]).abs().max(0)[0].clone().detach() | |
| + ) | |
| + return self.down_proj._ori_forward(x) | |
| + | |
| + def forward(self, x): | |
| + if self._ori_mode: | |
| + return self._ori_forward(x) | |
| + return self._trans_forward(x) | |
| + | |
| + def reparameterize(self): | |
| + if self.up_gate_trans is not None: | |
| + self.up_gate_trans.to_eval_mode() | |
| + self.down_trans.to_eval_mode() | |
| + self.gate_up_proj.reparameterize(qa_trans=self.up_gate_trans) | |
| + self.down_proj.reparameterize(qa_trans=self.down_trans) | |
| + if self.up_gate_trans is not None: | |
| + self.up_gate_trans.use_diag = False | |
| + if self.down_trans is not None and self.down_trans.add_diag: | |
| + up_weight = self.gate_up_proj.linear.weight | |
| + ori_dtype = up_weight.dtype | |
| + # Fuse down-channel scales into the up half of the fused gate_up matrix. | |
| + up_half = up_weight[self.intermediate_size :].to(torch.float64) | |
| + up_half = up_half.T.mul(self.down_trans.diag_scale.to(torch.float64)).T | |
| + up_weight = up_weight.clone() | |
| + up_weight[self.intermediate_size :] = up_half.to(ori_dtype) | |
| + self.gate_up_proj.linear.weight.data = up_weight | |
| + self.down_trans.use_diag = False | |
| + | |
| + def init_diag_scale(self, alpha=0.5): | |
| + assert hasattr(self, "up_smax") and hasattr(self, "down_smax") | |
| + upw_smax = self.gate_up_proj.linear.weight.abs().max(dim=0)[0] | |
| + downw_smax = self.down_proj.linear.weight.abs().max(dim=0)[0] | |
| + if self.up_gate_trans is not None: | |
| + self.up_gate_trans.diag_scale.data = get_init_scale(upw_smax, self.up_smax, alpha) | |
| + if self.down_trans is not None: | |
| + self.down_trans.diag_scale.data = get_init_scale(downw_smax, self.down_smax, alpha) | |
| + del self.up_smax, self.down_smax | |
| + self.diag_init = None | |
| + | |
| + def rep_matrix_only(self): | |
| + if self.up_gate_trans is not None: | |
| + self.up_gate_trans.to_eval_mode() | |
| + self.down_trans.to_eval_mode() | |
| + | |
| + | |
| +class FlatQuantPhi3Attention(nn.Module): | |
| + """FlatQuant wrapper for Phi-3 fused qkv_proj + o_proj.""" | |
| + | |
| + def __init__(self, args, module): | |
| + super().__init__() | |
| + self.args = args | |
| + self.config = module.config | |
| + self.layer_idx = module.layer_idx | |
| + self.hidden_size = module.hidden_size | |
| + self.num_heads = module.num_heads | |
| + self.head_dim = module.head_dim | |
| + self.num_key_value_heads = module.num_key_value_heads | |
| + self.num_key_value_groups = module.num_key_value_groups | |
| + self.attention_dropout = module.attention_dropout | |
| + self.rotary_emb = module.rotary_emb | |
| + self.query_pos = self.num_heads * self.head_dim | |
| + self.kv_dim = self.num_key_value_heads * self.head_dim | |
| + | |
| + self.qkv_proj = FlatQuantizedLinear(args, module.qkv_proj) | |
| + self.o_proj = FlatQuantizedLinear(args, module.o_proj) | |
| + self.add_fq_trans() | |
| + | |
| + if args.q_bits < 16: | |
| + self.q_cache_quantizer = ActivationQuantizer( | |
| + bits=args.q_bits, sym=not (args.q_asym), lac=args.lac, groupsize=-1 | |
| + ) | |
| + if args.k_bits < 16: | |
| + self.k_cache_quantizer = ActivationQuantizer( | |
| + bits=args.k_bits, sym=not (args.k_asym), lac=args.lac, groupsize=-1 | |
| + ) | |
| + if args.v_bits < 16: | |
| + self.v_cache_quantizer = ActivationQuantizer( | |
| + bits=args.v_bits, sym=not (args.v_asym), lac=args.lac, groupsize=-1 | |
| + ) | |
| + | |
| + self._ori_mode = False | |
| + self._eval_mode = False | |
| + self.diag_init = args.diag_init | |
| + if self.diag_init == "sq_style": | |
| + self.ln_smax = torch.ones_like(self.qkv_proj.linear.weight.abs().max(dim=0)[0]).cuda() * 1e-5 | |
| + | |
| + def add_fq_trans(self): | |
| + if self.args.direct_inv: | |
| + SingleTransMatrix, DecomposeTransMatrix = InvSingleTransMatrix, InvDecomposeTransMatrix | |
| + else: | |
| + SingleTransMatrix, DecomposeTransMatrix = SVDSingleTransMatrix, SVDDecomposeTransMatrix | |
| + if self.args.w_bits < 16 or self.args.a_bits < 16: | |
| + ln_dim_left, ln_dim_right = get_decompose_dim(self.qkv_proj.linear.weight.shape[1]) | |
| + self.ln_trans = DecomposeTransMatrix(ln_dim_left, ln_dim_right, add_diag=self.args.add_diag) | |
| + self.o_trans = SingleTransMatrix(self.config.num_attention_heads) | |
| + else: | |
| + self.ln_trans, self.o_trans = None, None | |
| + | |
| + if self.args.k_bits < 16 or self.args.q_bits < 16: | |
| + self.kcache_trans = SingleTransMatrix(self.head_dim) | |
| + else: | |
| + self.kcache_trans = None | |
| + if self.args.v_bits < 16 or self.args.w_bits < 16 or self.args.a_bits < 16: | |
| + self.vcache_trans = SingleTransMatrix(self.head_dim) | |
| + else: | |
| + self.vcache_trans = None | |
| + | |
| + def _split_qkv(self, qkv): | |
| + query_states = qkv[..., : self.query_pos] | |
| + key_states = qkv[..., self.query_pos : self.query_pos + self.kv_dim] | |
| + value_states = qkv[..., self.query_pos + self.kv_dim :] | |
| + return query_states, key_states, value_states | |
| + | |
| + def _trans_forward_after_ln(self, hidden_states): | |
| + qkv = _eval_proj(self.qkv_proj, hidden_states, self.ln_trans) | |
| + query_states, key_states, value_states = self._split_qkv(qkv) | |
| + # Official llama applies T_v as out_trans on V weights, then fuses at | |
| + # reparameterize. After _eval_mode, do not transform V activations again. | |
| + if ( | |
| + self.vcache_trans is not None | |
| + and not self.args.separate_vtrans | |
| + and not self._eval_mode | |
| + ): | |
| + value_states = self.vcache_trans(value_states) | |
| + return query_states, key_states, value_states | |
| + | |
| + def _ori_forward_after_ln(self, hidden_states): | |
| + if self.diag_init == "sq_style" and hasattr(self, "ln_smax"): | |
| + self.ln_smax = torch.maximum( | |
| + self.ln_smax, | |
| + hidden_states.reshape(-1, hidden_states.shape[-1]).abs().max(0)[0].clone().detach(), | |
| + ) | |
| + qkv = self.qkv_proj._ori_forward(hidden_states) | |
| + return self._split_qkv(qkv) | |
| + | |
| + def quant_vcache(self, value_states): | |
| + if self.args.separate_vtrans and self.vcache_trans is not None: | |
| + value_states = self.vcache_trans(value_states) | |
| + if self.args.v_bits < 16: | |
| + value_states = self.v_cache_quantizer(value_states) | |
| + return value_states | |
| + | |
| + def quant_kcache(self, q, k): | |
| + if not (self.args.k_bits < 16 or self.args.q_bits < 16): | |
| + return q, k | |
| + if self.kcache_trans is not None: | |
| + q = self.kcache_trans(q, inv_t=True) | |
| + k = self.kcache_trans(k) | |
| + if self.args.q_bits < 16: | |
| + q = self.q_cache_quantizer(q).to(q) | |
| + if self.args.k_bits < 16: | |
| + k = self.k_cache_quantizer(k).to(q) | |
| + return q, k | |
| + | |
| + def forward( | |
| + self, | |
| + hidden_states, | |
| + attention_mask=None, | |
| + position_ids=None, | |
| + past_key_value=None, | |
| + output_attentions=False, | |
| + use_cache=False, | |
| + cache_position=None, | |
| + **kwargs, | |
| + ): | |
| + bsz, q_len, _ = hidden_states.size() | |
| + if self._ori_mode: | |
| + query_states, key_states, value_states = self._ori_forward_after_ln(hidden_states) | |
| + else: | |
| + query_states, key_states, value_states = self._trans_forward_after_ln(hidden_states) | |
| + | |
| + query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2) | |
| + key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) | |
| + value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) | |
| + | |
| + kv_seq_len = key_states.shape[-2] | |
| + if past_key_value is not None: | |
| + if self.layer_idx is None: | |
| + raise ValueError( | |
| + f"The cache structure has changed since version v4.36. If you are using {self.__class__.__name__} " | |
| + "for auto-regressive decoding with k/v caching, please make sure to initialize the attention class " | |
| + "with a layer index." | |
| + ) | |
| + kv_seq_len += past_key_value.get_usable_length(kv_seq_len, self.layer_idx) | |
| + cos, sin = self.rotary_emb(value_states, position_ids, seq_len=kv_seq_len) | |
| + query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids) | |
| + | |
| + if not self._ori_mode: | |
| + query_states, key_states = self.quant_kcache(query_states, key_states) | |
| + value_states = self.quant_vcache(value_states) | |
| + | |
| + if past_key_value is not None: | |
| + cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position} | |
| + key_states, value_states = past_key_value.update( | |
| + key_states, value_states, self.layer_idx, cache_kwargs | |
| + ) | |
| + | |
| + key_states = repeat_kv(key_states, self.num_key_value_groups) | |
| + value_states = repeat_kv(value_states, self.num_key_value_groups) | |
| + dropout_p = self.attention_dropout if self.training else 0.0 | |
| + kv_len = key_states.shape[-2] | |
| + if attention_mask is not None: | |
| + causal_mask = attention_mask[:, :, :, :kv_len] | |
| + attn_output = F.scaled_dot_product_attention( | |
| + query_states, | |
| + key_states, | |
| + value_states, | |
| + attn_mask=causal_mask, | |
| + dropout_p=dropout_p, | |
| + is_causal=False, | |
| + ) | |
| + else: | |
| + # is_causal requires q_len == kv_len. Decode with KV cache is q_len=1. | |
| + attn_output = F.scaled_dot_product_attention( | |
| + query_states, | |
| + key_states, | |
| + value_states, | |
| + dropout_p=dropout_p, | |
| + is_causal=(q_len == kv_len), | |
| + ) | |
| + attn_output = attn_output.transpose(1, 2).contiguous() | |
| + attn_output = attn_output.reshape(bsz, q_len, self.hidden_size) | |
| + | |
| + if self._ori_mode: | |
| + attn_output = self.o_proj._ori_forward(attn_output) | |
| + else: | |
| + if self.o_trans is None and self.vcache_trans is not None: | |
| + init_shape = attn_output.shape | |
| + attn_output = attn_output.reshape( | |
| + -1, self.config.num_attention_heads, self.config.hidden_size // self.config.num_attention_heads | |
| + ) | |
| + attn_output = torch.matmul( | |
| + attn_output, self.vcache_trans.get_matrix(inv_t=True).T.to(attn_output) | |
| + ).reshape(init_shape) | |
| + attn_output = self.o_proj(attn_output) | |
| + else: | |
| + init_shape = attn_output.shape | |
| + attn_output = attn_output.reshape( | |
| + -1, self.config.num_attention_heads, self.config.hidden_size // self.config.num_attention_heads | |
| + ) | |
| + attn_output = torch.matmul( | |
| + self.o_trans.get_matrix().T.to(attn_output), attn_output | |
| + ).reshape(init_shape) | |
| + if not self._eval_mode: | |
| + attn_o_og_it = self.o_trans.get_matrix(inv_t=True) | |
| + attn_v_og_it = self.vcache_trans.get_matrix(inv_t=True) | |
| + attn_output = self.o_proj(attn_output, qa_trans=[attn_o_og_it, attn_v_og_it]) | |
| + else: | |
| + attn_output = self.o_proj(attn_output) | |
| + | |
| + if not output_attentions: | |
| + attn_weights = None | |
| + else: | |
| + attn_weights = None | |
| + return attn_output, attn_weights, past_key_value | |
| + | |
| + def reparameterize(self): | |
| + if self.ln_trans is not None: | |
| + self.ln_trans.to_eval_mode() | |
| + if self.kcache_trans is not None: | |
| + self.kcache_trans.to_eval_mode() | |
| + if self.vcache_trans is not None: | |
| + self.vcache_trans.to_eval_mode() | |
| + if self.o_trans is not None: | |
| + self.o_trans.to_eval_mode() | |
| + self.qkv_proj.reparameterize(qa_trans=self.ln_trans) | |
| + if self.vcache_trans is not None and not self.args.separate_vtrans: | |
| + weight = self.qkv_proj.linear.weight.data | |
| + ori_dtype = weight.dtype | |
| + v_weight = weight[self.query_pos + self.kv_dim :].to(torch.float64) | |
| + v_weight = self.vcache_trans(v_weight.T).T | |
| + weight = weight.clone() | |
| + weight[self.query_pos + self.kv_dim :] = v_weight.to(ori_dtype) | |
| + self.qkv_proj.linear.weight.data = weight | |
| + if self.o_trans is not None and self.vcache_trans is not None: | |
| + attn_o_og_it = self.o_trans.get_matrix(inv_t=True) | |
| + attn_v_og_it = self.vcache_trans.get_matrix(inv_t=True) | |
| + self.o_proj.reparameterize(qa_trans=[attn_o_og_it, attn_v_og_it]) | |
| + else: | |
| + self.o_proj.reparameterize() | |
| + self._eval_mode = True | |
| + | |
| + def init_diag_scale(self, alpha=0.5): | |
| + assert hasattr(self, "ln_smax") | |
| + qkvw_smax = self.qkv_proj.linear.weight.abs().max(dim=0)[0] | |
| + if self.ln_trans is not None: | |
| + self.ln_trans.diag_scale.data = get_init_scale(qkvw_smax, self.ln_smax, alpha) | |
| + del self.ln_smax | |
| + self.diag_init = None | |
| + | |
| + def rep_matrix_only(self): | |
| + if self.ln_trans is not None: | |
| + self.ln_trans.to_eval_mode() | |
| + if self.kcache_trans is not None: | |
| + self.kcache_trans.to_eval_mode() | |
| + if self.vcache_trans is not None: | |
| + self.vcache_trans.to_eval_mode() | |
| + if self.o_trans is not None: | |
| + self.o_trans.to_eval_mode() | |
| + | |
| + | |
| +def apply_flatquant_to_phi3(args, model): | |
| + skip_initialization() | |
| + for layer in range(model.config.num_hidden_layers): | |
| + model.model.layers[layer].self_attn = FlatQuantPhi3Attention( | |
| + args, model.model.layers[layer].self_attn | |
| + ) | |
| + model.model.layers[layer].mlp = FlatQuantPhi3MLP(args, model.model.layers[layer].mlp) | |
| + return model | |
| diff --git a/flatquant/model_utils.py b/flatquant/model_utils.py | |
| index 2d64c98..b181a02 100644 | |
| --- a/flatquant/model_utils.py | |
| +++ b/flatquant/model_utils.py | |
| +import os | |
| import torch | |
| import transformers | |
| import logging | |
| def get_qwen2(model_name, hf_token): | |
| return model, apply_flatquant_to_qwen | |
| +def get_phi3(model_name, hf_token): | |
| + skip_initialization() | |
| + from transformers import Phi3Config, Phi3ForCausalLM | |
| + from flatquant.model_tools.phi3_utils import apply_flatquant_to_phi3 | |
| + | |
| + is_local = os.path.isdir(model_name) | |
| + config = Phi3Config.from_pretrained( | |
| + model_name, use_auth_token=hf_token, local_files_only=is_local | |
| + ) | |
| + config._attn_implementation = "eager" | |
| + config._attn_implementation_internal = "eager" | |
| + model = Phi3ForCausalLM.from_pretrained( | |
| + model_name, | |
| + torch_dtype="auto", | |
| + config=config, | |
| + use_auth_token=hf_token, | |
| + low_cpu_mem_usage=True, | |
| + attn_implementation="eager", | |
| + local_files_only=is_local, | |
| + ) | |
| + model.seqlen = 2048 | |
| + logging.info(f"---> Loading {model_name} Model with seq_len: {model.seqlen}") | |
| + return model, apply_flatquant_to_phi3 | |
| + | |
| + | |
| def get_opt(model_name): | |
| skip_initialization() | |
| model = transformers.OPTForCausalLM.from_pretrained(model_name, | |
| def get_model(model_name, hf_token=None): | |
| return get_llama(model_name, hf_token) | |
| elif 'qwen-2.5' in model_name: | |
| return get_qwen2(model_name, hf_token) | |
| + elif 'phi-3' in model_name.lower() or 'phi3' in model_name.lower(): | |
| + return get_phi3(model_name, hf_token) | |
| else: | |
| raise ValueError(f'Unknown model {model_name}') | |
| diff --git a/flatquant/pack_utils.py b/flatquant/pack_utils.py | |
| new file mode 100644 | |
| index 0000000..ff8be0a | |
| --- /dev/null | |
| +++ b/flatquant/pack_utils.py | |
| +"""Convert a reparameterized FlatQuant model onto official packed W4A4 Linear4bit.""" | |
| +from __future__ import annotations | |
| + | |
| + | |
| +def _kernel_scale_dtype(): | |
| + import torch | |
| + | |
| + return torch.float16 | |
| + | |
| + | |
| +def cast_linear4bit_kernel_dtypes(model) -> int: | |
| + """Match CUDA sym_quant/sym_dequant: scale_row/scale_col must be float16. | |
| + | |
| + Empty Linear4bit buffers default to float32, so load_state_dict keeps | |
| + float32 even when the packed file stored fp16 scales. | |
| + """ | |
| + import torch | |
| + from deploy.nn.linear import Linear4bit | |
| + | |
| + dtype = _kernel_scale_dtype() | |
| + converted = 0 | |
| + for module in model.modules(): | |
| + if not isinstance(module, Linear4bit): | |
| + continue | |
| + scales = module.weight_scales.detach().to(dtype=dtype) | |
| + module.register_buffer("weight_scales", scales) | |
| + if module.bias is not None: | |
| + module.register_buffer("bias", module.bias.detach().to(dtype=dtype)) | |
| + converted += 1 | |
| + return converted | |
| + | |
| + | |
| +def prepare_packed_slots(model) -> None: | |
| + """Replace inner nn.Linear with empty Linear4bit so a packed state_dict can load.""" | |
| + import torch | |
| + from deploy.nn.linear import Linear4bit | |
| + from deploy.nn.quantization import Quantizer | |
| + from flatquant.flat_linear import FlatQuantizedLinear | |
| + | |
| + dtype = _kernel_scale_dtype() | |
| + for module in model.modules(): | |
| + if not isinstance(module, FlatQuantizedLinear): | |
| + continue | |
| + if getattr(module, "_packed", False) and isinstance(module.linear, Linear4bit): | |
| + continue | |
| + linear = module.linear | |
| + weight_dtype = linear.weight.dtype if linear.weight is not None else dtype | |
| + packed = Linear4bit( | |
| + linear.in_features, | |
| + linear.out_features, | |
| + bias=linear.bias is not None, | |
| + dtype=weight_dtype, | |
| + ) | |
| + module.linear = packed | |
| + if not hasattr(module, "a4"): | |
| + module.add_module("a4", Quantizer(lac=True)) | |
| + module._packed = True | |
| + module._eval_mode = True | |
| + cast_linear4bit_kernel_dtypes(model) | |
| + | |
| + | |
| +def restore_packed_eval_runtime(model) -> None: | |
| + """Restore eval-only Python flags that are not stored in state_dict. | |
| + | |
| + Packed / reparameterized weights already fused T and diag(T). The adapter | |
| + must call transform.to_eval_mode() *before* load so matrix buffers exist, | |
| + then set these flags *after* load. Do not run reparameterize_model again | |
| + on Linear4bit weights. | |
| + """ | |
| + from flatquant.flat_linear import FlatQuantizedLinear | |
| + | |
| + for layer in model.model.layers: | |
| + attn = layer.self_attn | |
| + mlp = layer.mlp | |
| + attn._eval_mode = True | |
| + for trans in ( | |
| + getattr(attn, "ln_trans", None), | |
| + getattr(attn, "o_trans", None), | |
| + getattr(attn, "kcache_trans", None), | |
| + getattr(attn, "vcache_trans", None), | |
| + getattr(mlp, "up_gate_trans", None), | |
| + getattr(mlp, "down_trans", None), | |
| + ): | |
| + if trans is None: | |
| + continue | |
| + if hasattr(trans, "use_diag"): | |
| + trans.use_diag = False | |
| + trans._eval_mode = True | |
| + for module in layer.modules(): | |
| + if isinstance(module, FlatQuantizedLinear): | |
| + module._eval_mode = True | |
| + | |
| + | |
| +def convert_model_to_w4a4(model, quantizers: dict) -> int: | |
| + """Pack RTN-quantized inner linears with official pack_i4 / Linear4bit.""" | |
| + import torch | |
| + from deploy.nn.linear import Linear4bit | |
| + from deploy.nn.quantization import Quantizer | |
| + from flatquant.flat_linear import FlatQuantizedLinear | |
| + | |
| + dtype = _kernel_scale_dtype() | |
| + converted = 0 | |
| + named = dict(model.named_modules()) | |
| + for name, module in named.items(): | |
| + if not isinstance(module, FlatQuantizedLinear): | |
| + continue | |
| + key = f"{name}.linear" | |
| + quantizer = quantizers.get(key) | |
| + if quantizer is None: | |
| + raise RuntimeError(f"missing RTN quantizer for {key}") | |
| + scale = quantizer.scale.detach().reshape(-1, 1).to(dtype=dtype) | |
| + packed = Linear4bit.from_float(module.linear, weight_scales=scale) | |
| + module.linear = packed | |
| + a4 = Quantizer(lac=True) | |
| + act = module.act_quantizer | |
| + if hasattr(act, "clip_factor_a_max"): | |
| + with torch.no_grad(): | |
| + a4.clip_factor_a_max.fill_(float(act.clip_factor_a_max.detach().reshape(-1)[0])) | |
| + a4.clip_factor_a_min.fill_(float(act.clip_factor_a_min.detach().reshape(-1)[0])) | |
| + module.add_module("a4", a4) | |
| + module._packed = True | |
| + module._eval_mode = True | |
| + converted += 1 | |
| + if converted == 0: | |
| + raise RuntimeError("no FlatQuantizedLinear modules to pack") | |
| + cast_linear4bit_kernel_dtypes(model) | |
| + return converted | |
| diff --git a/flatquant/utils.py b/flatquant/utils.py | |
| index 6de9dbe..b23faac 100644 | |
| --- a/flatquant/utils.py | |
| +++ b/flatquant/utils.py | |
| def cleanup_memory(verbose=True) -> None: | |
| def distribute_model(model) -> None: | |
| """Distribute the model across available GPUs. NB: only implemented for Llama-2/3/Qwen-2.""" | |
| - no_split_module_classes = ['LlamaDecoderLayer', 'Qwen2DecoderLayer'] | |
| + no_split_module_classes = ['LlamaDecoderLayer', 'Qwen2DecoderLayer', 'Phi3DecoderLayer'] | |
| max_memory = get_balanced_memory(model, no_split_module_classes=no_split_module_classes) | |
| device_map = infer_auto_device_map(model, max_memory=max_memory, no_split_module_classes=no_split_module_classes) | |
| diff --git a/gptq_utils.py b/gptq_utils.py | |
| index 714849f..c28f78d 100644 | |
| --- a/gptq_utils.py | |
| +++ b/gptq_utils.py | |
| def gptq_fwrd(model, dataloader, dev, args): | |
| position_ids = cache['position_ids'] | |
| quantizers = {} | |
| - sequential = [ | |
| - ['self_attn.k_proj.linear', 'self_attn.v_proj.linear', 'self_attn.q_proj.linear'], | |
| - ['self_attn.o_proj.linear'], | |
| - ['mlp.up_proj.linear', 'mlp.gate_proj.linear'], | |
| - ['mlp.down_proj.linear'] | |
| - ] | |
| + first_attn = layers[0].self_attn | |
| + if hasattr(first_attn, "qkv_proj"): | |
| + sequential = [ | |
| + ['self_attn.qkv_proj.linear'], | |
| + ['self_attn.o_proj.linear'], | |
| + ['mlp.gate_up_proj.linear'], | |
| + ['mlp.down_proj.linear'] | |
| + ] | |
| + else: | |
| + sequential = [ | |
| + ['self_attn.k_proj.linear', 'self_attn.v_proj.linear', 'self_attn.q_proj.linear'], | |
| + ['self_attn.o_proj.linear'], | |
| + ['mlp.up_proj.linear', 'mlp.gate_proj.linear'], | |
| + ['mlp.down_proj.linear'] | |
| + ] | |
| # sequential = [ | |
| # ['self_attn.k_proj', 'self_attn.v_proj', 'self_attn.q_proj'], | |
| # ['self_attn.o_proj'], | |
| diff --git a/quant_agent_inference_adapter.py b/quant_agent_inference_adapter.py | |
| new file mode 100644 | |
| index 0000000..7326b82 | |
| --- /dev/null | |
| +++ b/quant_agent_inference_adapter.py | |
| +"""Reload packed FlatQuant W4A4 weights (Linear4bit + transforms) from the artifact.""" | |
| +from __future__ import annotations | |
| + | |
| +import json | |
| +import os | |
| +import sys | |
| +from argparse import Namespace | |
| + | |
| +import torch | |
| +from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer | |
| + | |
| +QUANT_AGENT_ADAPTER_API = 1 | |
| + | |
| + | |
| +def load_model_and_tokenizer(model_path, model_id, dtype, device, trust_remote_code): | |
| + method_repo = os.environ.get("QUANT_AGENT_METHOD_REPO") | |
| + if method_repo: | |
| + sys.path.insert(0, method_repo) | |
| + import flatquant.utils as utils | |
| + from flatquant.model_tools.phi3_utils import apply_flatquant_to_phi3 | |
| + import flatquant.pack_utils as pack_utils | |
| + | |
| + packed_path = os.path.join(model_path, "packed_w4a4.pt") | |
| + if not os.path.isfile(packed_path): | |
| + raise FileNotFoundError("packed W4A4 artifact missing: %s" % packed_path) | |
| + dtypes = {"float16": torch.float16, "bfloat16": torch.bfloat16, "auto": "auto"} | |
| + torch_dtype = dtypes.get(dtype, torch.float16) | |
| + tokenizer = AutoTokenizer.from_pretrained( | |
| + model_path, trust_remote_code=trust_remote_code, local_files_only=True | |
| + ) | |
| + if tokenizer.pad_token is None: | |
| + tokenizer.pad_token = tokenizer.eos_token | |
| + config = AutoConfig.from_pretrained( | |
| + model_path, trust_remote_code=trust_remote_code, local_files_only=True | |
| + ) | |
| + config._attn_implementation = "eager" | |
| + config._attn_implementation_internal = "eager" | |
| + utils.skip_initialization() | |
| + model = AutoModelForCausalLM.from_config( | |
| + config, | |
| + torch_dtype=torch_dtype if device == "cuda" else torch.float32, | |
| + trust_remote_code=trust_remote_code, | |
| + attn_implementation="eager", | |
| + ) | |
| + model.seqlen = 2048 | |
| + with open(os.path.join(model_path, "flatquant_args.json"), "r", encoding="utf-8") as handle: | |
| + args = Namespace(**json.load(handle)) | |
| + model = apply_flatquant_to_phi3(args, model) | |
| + for layer in model.model.layers: | |
| + layer.self_attn.rep_matrix_only() | |
| + layer.mlp.rep_matrix_only() | |
| + pack_utils.prepare_packed_slots(model) | |
| + payload = torch.load(packed_path, map_location="cpu", weights_only=False) | |
| + missing, unexpected = model.load_state_dict(payload, strict=False) | |
| + packed_missing = [key for key in missing if "linear.weight" in key or key.endswith("a4.clip_factor_a_max")] | |
| + if packed_missing: | |
| + raise RuntimeError( | |
| + "packed_w4a4.pt did not restore Linear4bit weights: missing=%s unexpected=%s" | |
| + % (packed_missing[:12], list(unexpected)[:12]) | |
| + ) | |
| + pack_utils.cast_linear4bit_kernel_dtypes(model) | |
| + pack_utils.restore_packed_eval_runtime(model) | |
| + model.to(device) | |
| + pack_utils.cast_linear4bit_kernel_dtypes(model) | |
| + model.eval() | |
| + return model, tokenizer | |