Files
wehub-resource-sync 2aaeece67c
Codestyle Check / Lint (push) Has been cancelled
Codestyle Check / Check bypass (push) Has been cancelled
Pipelines-Test / Pipelines-Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:14 +08:00

567 lines
21 KiB
Python

# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
import paddle
from paddle import nn
from paddle.distributed.fleet.meta_parallel import (
ColumnParallelLinear,
RowParallelLinear,
)
from paddle.quantization import PTQ, QAT, QuantConfig
from paddle.quantization.base_observer import BaseObserver
from paddleslim.common.wrapper_function import FuncWrapper
from paddleslim.quant.advanced import (
GPTQ,
AutoClip,
AWQSearch,
EMASampler,
MultiStepSampler,
PieceWiseSearch,
Shift,
Smooth,
)
from paddleslim.quant.advanced.utils import find_parent_layer_and_sub_name
from paddleslim.quant.layers import (
QuantizedColumnParallelLinear,
QuantizedRowParallelLinear,
)
from paddleslim.quant.layers.custom_attention import QuantizedCustomAttentionLayer
from paddleslim.quant.observers import (
AbsMaxChannelWiseWeightObserver,
GroupWiseWeightObserver,
)
from paddleslim.quant.observers.abs_max import AbsmaxObserver
from paddleslim.quant.observers.abs_max_headwise import AbsMaxHeadwiseObserver
from paddleslim.quant.observers.avg import AVGObserver
from paddleslim.quant.observers.avg_headwise import AvgHeadwiseObserver
from paddleslim.quant.observers.channel_wise import ChannelWiseObserver
from paddlenlp.peft import PrefixModelForCausalLM
from paddlenlp.peft.lora import (
ColumnParallelLoRALinear,
LoRALinear,
RowParallelLoRALinear,
)
from paddlenlp.peft.lora.lora_quant_layers import (
ColumnParallelQuantedLoRALinear,
QuantedLoRALinear,
RowParallelQuantedLoRALinear,
)
from paddlenlp.utils.log import logger
ACT_OBSERVER = dict(
abs_max=AbsmaxObserver,
avg=AVGObserver,
)
WEIGHT_OBSERVER = dict(
abs_max_channel_wise=AbsMaxChannelWiseWeightObserver,
groupwise=GroupWiseWeightObserver,
)
CACHEKV_OBSERVER = dict(
abs_max_headwise=AbsMaxHeadwiseObserver,
avg_headwise=AvgHeadwiseObserver,
)
FP8_OBSERVER = dict(
abs_max=AbsmaxObserver,
avg=AVGObserver,
)
def create_qat_model(quant_args, model, dtype):
from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
from paddleslim.quant.quanters import (
FakeQuanterChannelWiseAbsMaxObserver,
PACTQuanter,
)
q_config = QuantConfig(activation=None, weight=None)
q_config.add_qat_layer_mapping(LoRALinear, QuantedLoRALinear)
q_config.add_qat_layer_mapping(RowParallelLoRALinear, RowParallelQuantedLoRALinear)
q_config.add_qat_layer_mapping(ColumnParallelLoRALinear, ColumnParallelQuantedLoRALinear)
if quant_args.quant_type == "a8w8":
activation = PACTQuanter(quanter=FakeQuanterWithAbsMaxObserver(), init_value=20.0, dtype=dtype)
weight = FakeQuanterChannelWiseAbsMaxObserver(bit_length=8, dtype="float32")
elif quant_args.quant_type == "weight_only_int4":
activation = None
weight = FakeQuanterChannelWiseAbsMaxObserver(bit_length=4, dtype="float32")
elif quant_args.quant_type == "weight_only_int8":
activation = None
weight = FakeQuanterChannelWiseAbsMaxObserver(bit_length=8, dtype="float32")
else:
raise ValueError("quant_type should be one of ['a8w8', 'weight_only_int4', 'weight_only_int8']")
q_config.add_type_config(RowParallelLoRALinear, weight=weight, activation=activation)
q_config.add_type_config(ColumnParallelLoRALinear, weight=weight, activation=activation)
q_config.add_type_config(LoRALinear, weight=weight, activation=activation)
q_config.add_type_config(nn.Linear, weight=weight, activation=activation)
qat = QAT(q_config)
model = qat.quantize(model, inplace=True)
return model
def apply_shift(quant_args, trainer, ptq_dataloader, ptq_model_config):
logger.info("***** Running Shift *****")
shift_sampler = EMASampler() if quant_args.shift_sampler == "ema" else None
shift = Shift(
model=trainer.model,
model_config=ptq_model_config,
sample_function=shift_sampler,
shift_all_linears=quant_args.shift_all_linears,
)
with paddle.no_grad():
trainer.ptq_loop(
ptq_dataloader,
description="Shift",
max_eval_iters=quant_args.shift_step,
)
shift.update_weight()
del shift, shift_sampler
logger.info("***** Shift done *****")
def apply_smooth(quant_args, trainer, ptq_dataloader, ptq_model_config):
if quant_args.do_awq:
logger.info("***** Running AWQ *****")
else:
logger.info("***** Running Smooth *****")
smooth_sampler = MultiStepSampler() if quant_args.smooth_sampler == "multi_step" else None
if quant_args.smooth_piecewise_search:
search_func = PieceWiseSearch(
k_piece=quant_args.smooth_k_piece,
bits_length=8,
search_piece=quant_args.smooth_search_piece,
search_alpha_min=quant_args.search_alpha_min,
search_alpha_max=quant_args.search_alpha_max,
search_scale_min=quant_args.search_scale_min,
search_scale_max=quant_args.search_scale_max,
weight_quant_method=quant_args.weight_quant_method,
act_quant_method=quant_args.act_quant_method,
)
elif quant_args.do_awq:
search_func = AWQSearch(
n_grid=20,
bits_length=4,
weight_quant_method=quant_args.weight_quant_method,
)
else:
search_func = None
smooth = Smooth(
trainer.model,
ptq_model_config,
alpha=0.5,
smooth_all_linears=quant_args.smooth_all_linears,
sample_function=smooth_sampler,
search_function=search_func,
smooth_method="awq" if quant_args.do_awq else "smoothquant",
)
with paddle.no_grad():
trainer.ptq_loop(
ptq_dataloader,
description="Smooth",
max_eval_iters=quant_args.smooth_step,
)
smooth.update_weight()
del smooth, smooth_sampler, search_func
logger.info("***** Smooth done *****")
def apply_autoclip(quant_args, trainer, ptq_dataloader):
"""
AutoClip
"""
print("-------------------Start AutoClip------------------")
sampler = MultiStepSampler()
auto_clip = AutoClip(
trainer.model,
weight_bits=4,
weight_quant_method=quant_args.weight_quant_method,
sample_function=sampler,
n_grid=20,
max_shrink=0.5,
)
with paddle.no_grad():
trainer.ptq_loop(
ptq_dataloader,
description="AutoClip",
max_eval_iters=quant_args.autoclip_step,
)
auto_clip.auto_clip()
del sampler, auto_clip
logger.info("***** AutoClip done *****")
def prepare_qconfig(args):
"""
Prepare qconfig
"""
args.quant_type = args.quant_type.lower()
if args.quant_type in ["a8w8_fp8"]:
use_fp8 = "aw"
args.quant_type = args.quant_type.replace("_fp8", "")
else:
use_fp8 = ""
weight_observer = (
WEIGHT_OBSERVER.get(args.weight_quant_method, None)
if "w" not in use_fp8
else FP8_OBSERVER.get(args.weight_quant_method, None)
)
act_observer = (
ACT_OBSERVER.get(args.act_quant_method, None)
if "a" not in use_fp8
else FP8_OBSERVER.get(args.act_quant_method, None)
)
cachekv_observer = CACHEKV_OBSERVER.get(args.cachekv_quant_method, None)
if "c8" in args.quant_type:
quant_type = args.quant_type.replace("c8", "")
cachekv_quant = True
cachekv_quant_bits = "int8"
else:
quant_type = args.quant_type.replace("c16", "")
cachekv_quant = False
q_config = QuantConfig(activation=None, weight=None)
if quant_type in ["a8w8", "w8a8"]:
if "w" in use_fp8:
w_quant_bit = (4, 3) if args.fp8_type[use_fp8.index("w")] == "e4m3" else (5, 2)
else:
w_quant_bit = 8
if "a" in use_fp8:
a_quant_bit = (4, 3) if args.fp8_type[use_fp8.index("a")] == "e4m3" else (5, 2)
else:
a_quant_bit = 8
activation = act_observer(quant_bits=a_quant_bit)
weight = weight_observer(quant_bits=w_quant_bit)
elif quant_type in ["wint4", "w4a16", "weight_only_int4"]:
activation = None
weight = weight_observer(quant_bits=4)
elif quant_type in ["wint8", "w8a16", "weight_only_int8"]:
activation = None
if "w" in use_fp8:
weight = weight_observer(quant_bits=(4, 3))
else:
weight = weight_observer(quant_bits=8)
else:
raise ValueError(
"quant_type should be in ['weight_only_int8/wint8', 'weight_only_int4/wint4', 'a8w8', 'a8w8c8', 'a8w8_fp8']"
)
q_config.add_qat_layer_mapping(ColumnParallelLinear, QuantizedColumnParallelLinear)
q_config.add_qat_layer_mapping(RowParallelLinear, QuantizedRowParallelLinear)
cachekv = None
if cachekv_quant:
if cachekv_quant_bits == "int8":
cachekv_quant_bit = 8
if "headwise" in args.cachekv_quant_method:
cachekv = [
cachekv_observer(quant_bits=cachekv_quant_bit, quant_axis=1),
cachekv_observer(quant_bits=cachekv_quant_bit, quant_axis=1),
]
else:
cachekv = [
cachekv_observer(quant_bits=cachekv_quant_bit),
cachekv_observer(quant_bits=cachekv_quant_bit),
]
q_config.add_qat_layer_mapping(FuncWrapper, QuantizedCustomAttentionLayer)
else:
raise ValueError("cachekv_quant_bits should be int8")
return activation, weight, cachekv, q_config
def load_quant_model(model, quant_args, load_quant_path, dtype="float32"):
"""
Load quantized model and its scales
"""
activation, weight, cachekv, q_config = prepare_qconfig(quant_args)
if cachekv is not None:
set_wrapper_for_attn(model)
skip_list_names = [] if quant_args.skip_list_names is None else quant_args.skip_list_names
for cur_name, cur_layer in model.named_sublayers():
skip = False
for k in skip_list_names:
if k in cur_name:
logger.info(f"Skip layer {cur_name}")
skip = True
if skip:
continue
if type(cur_layer) in [paddle.nn.Linear, ColumnParallelLinear, RowParallelLinear]:
logger.info(f"PTQ layer: {cur_name}")
q_config.add_name_config([cur_layer.full_name()], activation=activation, weight=weight)
if type(cur_layer) in [FuncWrapper] and cachekv is not None:
logger.info(f"PTQ layer: {cur_name}")
# set both act and weight for attention, actually act-k and act-v are quantized
q_config.add_name_config([cur_layer.full_name()], weight=cachekv[0], activation=cachekv[1])
ptq = PTQ(q_config)
model = ptq.quantize(model, inplace=True)
logger.info("Load quant model...")
if activation is not None:
with open(f"{load_quant_path}/act_scales.json") as outfile:
act_scales = json.load(outfile)
else:
act_scales = {}
if cachekv is not None:
with open(f"{load_quant_path}/cachekv_scales.json") as outfile:
cachekv_scales = json.load(outfile)
else:
cachekv_scales = {}
with open(f"{load_quant_path}/weight_scales.json") as outfile:
weight_scales = json.load(outfile)
for cur_name, cur_layer in model.named_sublayers():
if hasattr(cur_layer, "scales"):
if isinstance(cur_layer, ChannelWiseObserver) or isinstance(cur_layer, BaseObserver):
logger.info(f"Load scale for layer {cur_name}")
if "attn_func" in cur_name:
cur_name = cur_name.replace("attn_func.activation_quanter_v", "cachev_matmul.activation_quanter")
cur_name = cur_name.replace("attn_func.activation_quanter_k", "cachek_matmul.activation_quanter")
if cur_name in cachekv_scales:
cur_layer._scale = paddle.to_tensor(cachekv_scales[cur_name], dtype=dtype)
if cur_name + ".zero_point" in cachekv_scales:
cur_layer._zero_point = paddle.to_tensor(
cachekv_scales[cur_name + ".zero_point"], dtype=dtype
)
else:
cur_layer._zero_point = paddle.to_tensor(0.0, dtype=dtype)
else:
logger.info(f"No scale found for layer {cur_name}, remove it")
parent_layer, sub_name = find_parent_layer_and_sub_name(model, cur_name)
setattr(parent_layer, sub_name, None)
elif "activation_quanter" in cur_name:
if cur_name in act_scales:
cur_layer._scale = paddle.to_tensor(act_scales[cur_name], dtype=dtype)
cur_layer._zero_point = paddle.to_tensor(0.0, dtype=dtype)
else:
logger.info(f"No scale found for layer {cur_name}, remove it")
parent_layer, sub_name = find_parent_layer_and_sub_name(model, cur_name)
setattr(parent_layer, sub_name, None)
elif "weight_quanter" in cur_name:
if cur_name in weight_scales:
cur_layer._scale = paddle.to_tensor(weight_scales[cur_name], dtype=dtype)
else:
logger.info(f"No scale found for layer {cur_name}, remove it")
parent_layer, sub_name = find_parent_layer_and_sub_name(model, cur_name)
setattr(parent_layer, sub_name, None)
model = ptq.convert(model, inplace=True)
if os.path.exists(os.path.join(load_quant_path, "model_state.pdparams")):
logger.info(f"Load model checkpoint from {load_quant_path}")
model_path = os.path.join(load_quant_path, "model_state.pdparams")
model_dict = paddle.load(model_path, return_numpy=True)
model.set_dict(model_dict)
else:
raise Exception("Only support load model from pdparams now")
def apply_ptq(quant_args, trainer, ptq_dataloader):
logger.info("***** Running PTQ *****")
activation, weight, cachekv, q_config = prepare_qconfig(quant_args)
if cachekv is not None:
set_wrapper_for_attn(trainer.model)
skip_list_names = [] if quant_args.skip_list_names is None else quant_args.skip_list_names
for cur_name, cur_layer in trainer.model.named_sublayers():
skip = False
for k in skip_list_names:
if k in cur_name:
logger.info(f"Skip layer {cur_name}")
skip = True
if skip:
continue
if type(cur_layer) in [paddle.nn.Linear, ColumnParallelLinear, RowParallelLinear]:
logger.info(f"PTQ layer: {cur_name}")
q_config.add_name_config([cur_layer.full_name()], activation=activation, weight=weight)
if cachekv is not None and type(cur_layer) in [FuncWrapper]:
logger.info(f"PTQ layer: {cur_name}")
# set both act and weight for attention, actually act-k and act-v are quantized
q_config.add_name_config([cur_layer.full_name()], weight=cachekv[0], activation=cachekv[1])
ptq = PTQ(q_config)
trainer.model = ptq.quantize(trainer.model, inplace=True)
# enable observer
enable_observer(trainer.model)
logger.info("***** PTQ loop start *****")
trainer.ptq_loop(
ptq_dataloader,
description="PTQ",
max_eval_iters=quant_args.ptq_step,
)
# disable observer
disable_observer(trainer.model)
weight_scales = {}
act_scales = {}
cachekv_scales = {}
for cur_name, cur_layer in trainer.model.named_sublayers():
if isinstance(cur_layer, ChannelWiseObserver) or isinstance(cur_layer, BaseObserver):
if "_observer" not in cur_name:
if "attn_func" in cur_name:
cur_name = cur_name.replace("attn_func.activation_quanter_v", "cachev_matmul.activation_quanter")
cur_name = cur_name.replace("attn_func.activation_quanter_k", "cachek_matmul.activation_quanter")
cachekv_scales[cur_name] = cur_layer.scales().cast("float32").numpy().tolist()
elif "activation_quanter" in cur_name:
act_scales[cur_name] = cur_layer.scales().cast("float32").numpy().tolist()
elif "weight_quanter" in cur_name:
weight_scales[cur_name] = cur_layer.scales().cast("float32").numpy().tolist()
weight_scales_path = os.path.join(trainer.args.output_dir, "weight_scales.json")
with open(weight_scales_path, "w") as f:
json.dump(weight_scales, f)
logger.info(f"Weight scales saved in {weight_scales_path}.")
act_scales_path = os.path.join(trainer.args.output_dir, "act_scales.json")
with open(act_scales_path, "w") as f:
json.dump(act_scales, f)
logger.info(f"Activation scales saved in {act_scales_path}.")
cachekv_scales_path = os.path.join(trainer.args.output_dir, "cachekv_scales.json")
with open(cachekv_scales_path, "w") as f:
json.dump(cachekv_scales, f)
logger.info(f"CacheKV scales saved in {cachekv_scales_path}.")
trainer.model = ptq.convert(trainer.model, inplace=True)
logger.info("***** PTQ done *****")
def apply_gptq(quant_args, trainer, ptq_dataloader):
logger.info("***** Running GPTQ *****")
num_layer = 0
model = trainer.model
for cur_name, cur_layer in model.named_sublayers():
if type(cur_layer) in [paddle.nn.Linear, ColumnParallelLinear, RowParallelLinear]:
num_layer += 1
logger.info(f"GPTQ layer: {num_layer}, {cur_name}")
parent_layer, sub_name = find_parent_layer_and_sub_name(model, cur_name)
cur_quant_layer = GPTQ(cur_layer)
setattr(parent_layer, sub_name, cur_quant_layer)
with paddle.no_grad():
trainer.ptq_loop(
ptq_dataloader,
description="GPTQ",
max_eval_iters=quant_args.gptq_step,
)
cur_quant_layer.fasterquant(percdamp=0.1, groupsize=-1, actorder=True)
del cur_quant_layer
setattr(parent_layer, sub_name, cur_layer)
logger.info("***** GPTQ done *****")
def set_wrapper_for_attn(model: nn.Layer, attn_name="attn_func"):
for cur_name, cur_layer in model.named_sublayers():
if hasattr(cur_layer, attn_name):
logger.info(f"Set wrapper for {attn_name} in {cur_name}")
cur_layer.attn_func = FuncWrapper(cur_layer.attn_func)
def get_ptq_model_config(model):
if isinstance(model, PrefixModelForCausalLM):
base_model_prefix = model.model.base_model_prefix
else:
base_model_prefix = model.base_model_prefix
if base_model_prefix in ["chatglm"]:
raise NotImplementedError(f"{model} does not support Shift or Smooth.")
elif base_model_prefix == "chatglm_v2":
model_config = {"fused_qkv": False, "parallel_ffn": False, "skip_norm_list": ["rms_norm_56"]}
elif base_model_prefix == "bloom":
model_config = {"fused_qkv": True, "parallel_ffn": False}
elif base_model_prefix == "llama":
model_config = {"fused_qkv": False, "parallel_ffn": True}
elif base_model_prefix == "qwen2":
model_config = {"fused_qkv": False, "parallel_ffn": True}
else:
raise ValueError(
f"Unknown base_model_prefix: {model.base_model_prefix}. Supported base_model_prefix list: chatglm_V2, bloom, llama, qwen2."
)
return model_config
def enable_observer(model: nn.Layer):
# TODO maybe not support pp,tp etc.
for mod in model.sublayers():
if hasattr(mod, "observer_enabled"):
mod.observer_enabled = True
def disable_observer(model: nn.Layer):
# TODO maybe not support pp,tp etc.
for mod in model.sublayers():
if hasattr(mod, "observer_enabled"):
mod.observer_enabled = False
def add_quant_inp_out_hook(model: nn.Layer, tag_func):
def get_hook():
inp_ret = []
out_ret = []
def hook(layer, inp, out):
nonlocal inp_ret, out_ret
inp_ret.append(inp[0].flatten().numpy())
out_ret.append(out.flatten().numpy())
return out
return hook, inp_ret, out_ret
inp_dict = dict()
out_dict = dict()
handlers = []
for cur_name, cur_layer in model.named_sublayers():
if tag_func(cur_name):
hook, inp_ret, out_ret = get_hook()
handle = cur_layer.register_forward_post_hook(hook)
inp_dict[cur_name] = inp_ret
out_dict[cur_name] = out_ret
handlers.append(handle)
return inp_dict, out_dict
def save_dict(inp_dict, file_path):
import pickle
with open(file_path, "wb") as f:
pickle.dump(inp_dict, f)