ChatGLM2-INT4 + Lora 结构适配和改造

简介: ChatGLM2-INT4 + Lora 结构适配和改造

Lora 是目前公认的最好的微调方法,一方面,它并不像AdapterTuning 一样,改变原有模型的架构,不便于在不同框架之间迁移;另一方面,它不像 PTuning 一样改变所有任务下的单词生成概率,严重破坏已习得的知识。

ChatGLM2-INT4 这个量化版本使用自定义的QuantizedLinear作为线性模块。如果我们要使用 PEFT 库添加 Lora 参数时,它就会不认识,因为它是为torch.nn.Linear准备的,于是我们只能自己写个模块来实现这件事情。

一、编写LoraQuantizedLinear

LoraQuantizedLinear是我们自己的带Lora的线性层,包含QuantizedLinear所有参数/配置和Lora的主要配置项。

通过阅读quantization.py,我们确定QuantizedLinear的参数或配置有四个:

  • weight:量化后权重,形状为[OutDim, InDim]。注意在 INT4 模式下,一个 INT8 元素当两个 INT4 使用,InDim是 FP16 版本的一半。
  • weight_scale:量化的缩放系数,形状为[OutDim]。也就是说这个量化针对每一个隐藏状态确定一个范围,而不是整个参数。
  • bias:不量化的偏置,形状为[OutDim]
  • weight_bit_width:量化位数,4 或者 8。

新的线性层应该原样保存这些参数,并且应该包含Lora的三个主要配置:

  • r:较低的维度大小
  • alpha:和r一起组成缩放系数
  • dropout_rate:前置 Dropout 的比例

新的线性层创建如下:

class LoraQuantizedLinear(torch.nn.Module):
    def __init__(self, q_linear, lora_r=32, lora_alpha=32, lora_dropout_rate=0.0):
        super().__init__()
        # 保存原始参数和Lora配置
        self.lora_r = lora_r
        self.lora_alpha = lora_alpha
        self.lora_dropout_rate = lora_dropout_rate
        self.weight_bit_width = q_linear.weight_bit_width
        self.weight = q_linear.weight
        self.weight_scale = q_linear.weight_scale
        self.bias = q_linear.bias
        # 冻结原始参数
        self.weight.requires_grad = False
        self.weight_scale.requires_grad = False
        if self.bias is not None: self.bias.requires_grad = False
        # 创建 Lora 参数,FP16
        out_dim, in_dim = self.weight.shape
        # INT4 模型下,InDim 是原始大小的一半
        if self.weight_bit_width == 4: in_dim *= 2
        # LoraA 正态初始化
        self.lora_a = torch.nn.Parameter(torch.empty(
            [self.lora_r, in_dim],
            device=self.weight.device,
            dtype=torch.float16,
        ))
        torch.nn.init.kaiming_normal_(self.lora_a)
        # LoraB 全零初始化
        self.lora_b = torch.nn.Parameter(torch.zeros(
            [out_dim, self.lora_r],
            device=self.weight.device,
            dtype=torch.float16,
        ))
        self.lora_dropout = torch.nn.Dropout(self.lora_dropout_rate)
        self.lora_scale = self.lora_alpha / self.lora_r

正向传播过程中,先用之前的forward()方法完成原始输出的计算,之后再手动编写 Lora 输出的计算,然后相加并返回:

# class LoraQuantizedLinear
    def forward(self, input):
        ori_output = QuantizedLinear.forward(self, input)
        lora_output = (
            self.lora_dropout(input.half()) @ 
            self.lora_a.transpose(0, 1) @ 
            self.lora_b.transpose(0, 1) * 
            self.lora_scale
        )
        return ori_output + lora_output.to(ori_output.dtype)

合并方法,我们将原始参数解量化,将Lora两个参数相乘后与之相加,然后再次量化。

# class LoraQuantizedLinear
    def merge(self):
        # H = XW + b + XAB * s => H = X(W + AB * s) + b
        # 将 int 原始参数转成 fp16
        weight = extract_weight_to_half(self.weight, self.weight_scale, self.weight_bit_width)
        # 合并 lora 参数
        weight += self.lora_b @ self.lora_a * self.lora_scale
        # 再转回 int
        weight, weight_scale = half_weight_to_int(weight, self.weight_bit_width)
        self.weight = torch.nn.Parameter(weight, requires_grad=False)
        self.weight_scale = torch.nn.Parameter(weight_scale, requires_grad=False)
        # 重新初始化 lora 两个矩阵
        torch.nn.init.kaiming_normal_(self.lora_a)
        torch.nn.init.zeros_(self.lora_b)

half_weight_to_int取自QuantizedLinear类的构造器:

def half_weight_to_int(weight: torch.Tensor, weight_bit_width: int):
    assert weight_bit_width in [4, 8]
    assert weight.ndim == 2
    weight_scale = weight.abs().max(dim=-1).values / ((2 ** (weight_bit_width - 1)) - 1)
    weight = torch.round(weight / weight_scale[:, None]).to(torch.int8)
    if weight_bit_width == 4:
        weight = compress_int4_weight(weight)
    return weight, weight_scale

二、辅助方法

之后实现几个辅助方法,完成参数挂载卸载和合并:

首先是attach_lora:将所有的QuantizedLinear改成LoraQuantizedLinear

我们搜索模型的所有模块,再搜索它的直接子模块,如果任何东西是QuantizedLinear,就把它替换掉。之后把非 Lora 参数冻结。

def attach_lora(model, lora_r=32, lora_alpha=32, lora_dropout_rate=0.0):
    if model.lora_attached: return model
    lora_conf = dict(lora_r=lora_r, lora_alpha=lora_alpha, lora_dropout_rate=lora_dropout_rate)
    for mod in model.modules():
        for name in dir(mod):
            submod = getattr(mod, name, None)
            if not isinstance(submod, QuantizedLinear):
                continue
            new_submod = LoraQuantizedLinear(submod, **lora_conf)
            setattr(mod, name, new_submod)
    for name, param in model.named_parameters():
        if 'lora_' not in name:
            param.requires_grad = False
    model.lora_attached = True
    return model

lora_state_dict:导出所有 Lora 参数。

def lora_state_dict(model):
    return {   
        k:v
        for k, v in model.state_dict().items()
        if 'lora_' in k
    }

merge_lora:将所有LoraQuantizedLinear的参数合并。

def merge_lora(model):
    for mod in model.modules():
        if isinstance(mod, LoraQuantizedLinear):
            mod.merge()
    return model

detach_lora:搜索模型的所有模块,再搜索它的直接子模块,如果任何东西是LoraQuantizedLinear,就把它替换回QuantizedLinear

def detach_lora(model):
    if not model.lora_attached: return model
    for mod in model.modules():
        for name in dir(mod):
            submod = getattr(mod, name, None)
            if not isinstance(submod, LoraQuantizedLinear):
                continue
            new_submod = QuantizedLinear.from_params(
                submod.weight_bit_width,
                submod.weight,
                submod.weight_scale,
                submod.bias,
            )
            setattr(mod, name, new_submod)
    model.lora_attached = False
    return model

这就需要给QuantizedLinear加一个工厂方法,接受它的四个参数并直接保存。原有的构造器与之不兼容,于是实现为类方法:

# class QuantizedLinear
    @classmethod
    def from_params(cls, weight_bit_width, weight, weight_scale, bias):
        obj = cls.__new__(cls)
        super(cls, obj).__init__()
        obj.weight_bit_width = weight_bit_width
        obj.weight = weight
        obj.weight_scale = weight_scale
        obj.bias = bias
        return obj

由于这些方法是实现在单独的文件中的(例如lora.py),我们在modeling_chatglm.py中导入这个文件,然后添加到ChatGLMForConditionalGeneration类里面,便于使用:

from .lora import attach_lora, detach_lora, merge_lora, lora_state_dict
# class ChatGLMForConditionalGeneration
    # def __init__
        self.lora_attached = False
    attach_lora = attach_lora
    detach_lora = detach_lora
    merge_lora = merge_lora
    lora_state_dict = lora_state_dict

然后一切就完成了。

相关文章
|
6月前
|
数据采集 自然语言处理 文字识别
大模型升级与设计之道:ChatGLM、LLAMA、Baichuan及LLM结构解析(下)
大模型升级与设计之道:ChatGLM、LLAMA、Baichuan及LLM结构解析(下)
626 0
|
6月前
|
机器学习/深度学习 数据采集 人工智能
大模型升级与设计之道:ChatGLM、LLAMA、Baichuan及LLM结构解析(上)
大模型升级与设计之道:ChatGLM、LLAMA、Baichuan及LLM结构解析(上)
917 0
|
5月前
|
机器学习/深度学习 消息中间件 人工智能
人工智能平台PAI产品使用合集之vLLM是否支持模型长度扩展
阿里云人工智能平台PAI是一个功能强大、易于使用的AI开发平台,旨在降低AI开发门槛,加速创新,助力企业和开发者高效构建、部署和管理人工智能应用。其中包含了一系列相互协同的产品与服务,共同构成一个完整的人工智能开发与应用生态系统。以下是对PAI产品使用合集的概述,涵盖数据处理、模型开发、训练加速、模型部署及管理等多个环节。
|
6月前
|
机器学习/深度学习 搜索推荐 物联网
微软开源创新LoRA组合方法,增强文生图复杂细节控制
微软研究团队推出Multi-LoRA Composition技术,改善文本到图像模型的细节控制。利用低秩适应(LoRA)提升图像生成精度,通过LORA SWITCH和LORA COMPOSITE解决组合复杂图像的挑战。新方法在ComposLoRA平台上测试,性能优于基线,为图像生成和个性化内容创作开辟新途径。尽管有学习曲线和定制需求优化的问题,但该研究仍为领域带来显著进步。
419 3
微软开源创新LoRA组合方法,增强文生图复杂细节控制
|
6月前
|
机器学习/深度学习 搜索推荐 算法
直接调用通用大模型开发应用与基于开源大模型“自研”两种方式比较
【1月更文挑战第23天】直接调用通用大模型开发应用与基于开源大模型“自研”两种方式比较
239 1
直接调用通用大模型开发应用与基于开源大模型“自研”两种方式比较
|
6月前
|
机器学习/深度学习 数据可视化 计算机视觉
YOLOv5改进 | Conv篇 | 利用DualConv二次创新C3提出一种轻量化结构(降低参数30W)
YOLOv5改进 | Conv篇 | 利用DualConv二次创新C3提出一种轻量化结构(降低参数30W)
376 1
|
6月前
|
机器学习/深度学习 存储 算法
YOLO落地部署 | 让YOLO5和YOLO7等方法都可以用上4-bit/3-bit的超快部署方案
YOLO落地部署 | 让YOLO5和YOLO7等方法都可以用上4-bit/3-bit的超快部署方案
129 0
|
运维 物联网 Serverless
在函数计算FC云端,可以使用SD来训练LORA模型
在函数计算FC云端,可以使用SD来训练LORA模型
698 8
|
存储 自然语言处理 并行计算
PyTorch 新库 TorchMultimodal 使用说明:将多模态通用模型 FLAVA 扩展到 100 亿参数
PyTorch 新库 TorchMultimodal 使用说明:将多模态通用模型 FLAVA 扩展到 100 亿参数
263 0
|
文字识别 算法 测试技术
DeepSORT再升级 | Deep OC-SORT引入目标外观信息,大幅领先SOTA
DeepSORT再升级 | Deep OC-SORT引入目标外观信息,大幅领先SOTA
1026 0
下一篇
无影云桌面