大模型低秩适配器LoRA源码解读

news2024/11/11 4:51:32

一、引言

关于 LoRA 的具体理论原理可以参考:图解大模型微调系列之:大模型低秩适配器 LoRA(原理篇)

关于 LoRA 的源码解读实操可以参考:图解大模型微调系列之:大模型低秩适配器 LoRA(源码解读与实操篇)

以上两篇博客写的非常好,强烈建议仔细阅读,本篇博客是对上面第二篇博客中的 LoRA 微软官方源码的进一步解读。包含对源码框架的解读,对源码中训练过程和推理过程的分析,以及对源码一句一句的汉语注释。

重新整理后带汉语注释的 LoRA 源码 Git 链接。

二、LoRA 模型搭建

关于如何通过 LoRA 原理搭建 small GPT2 模型的代码主要分布在 layers.py 和 model.py 两个文件中,其中相关类的调用和继承关系如下图所示,utils.py 中是与模型应用相关的两个函数。

Model/GPT2LMModel():small GPT2 的完整定义类,包括完整结构定义、loss 计算和上载权重功能。

Model/GPT2Config():small GPT2 的配置类,仅仅包含众多配置参数。

Model/GPT2LMHead():small GPT2 的 head,仅仅包含一个反 embedding 层,且这个 embedding 层需要与模型输入端的 emdedding 进行参数绑定。

Model/GPT2Model():small GPT2 的 backbone,是由位置嵌入层、特征嵌入层和一系列 transformer decoder 层搭建的模型,位置嵌入层负责进行位置编码,特征嵌入层负责将输入的整形序列的中每个整型值转换为词向量特征,所以模型的输入必须是完成切词和映射后的整形序列,例如 [123, 126, 345, ...]。经过嵌入层后送入 decoder 的特征尺寸为 in_features = n_heads*size_embding。

Model/Block():transformer decoder 模块,包含自注意力机制层、线性映射层和两个层归一化层,是标准的 decoder 结构。

Model/Attention():transformer decoder 中的自注意力模块,transformer 的核心类,整个注意力机制包括两部分。第一部分是 q k v 的计算,第二部分是注意力结果计算。第一部分融合进了 LoRA 训练策略,可以实现 LoRA 训练,可以合并参数也可以拆分参数。第二部分不在乎有没有 LoRA,就是纯注意力计算的结果。且这里的实现与标准 transformer 不太一样,标准 transformer 在计算 q k v 时每个 head 独自计算,相互不干扰,例如 head1_q=head1_Q*head1_x,不需要 head2_x 参与,而该类在计算时不同 head 相互干扰,具体说就是 head1_q 在计算时不仅仅通过 head1_x 计算,还需要 head2_x,... 等参与计算,例如 [head1_q, head2_q]=Q*[head1_x, head2_x],这会造成 Q 参数量和计算量的二次幂级增加,原本不同 head 独立计算时,只需要 n_head 个矩阵即可完成不同 head 的 q 的计算,但该类则需要 n_head*n_head 个矩阵组成的大 Q 进行计算。decoder 中的前向注意力机制是通过一个下三角矩阵实现的,只是将后向的注意力部分乘以了系数 0,所以相比 encoder 并没有减少计算量。在计算注意力系数时,所有的 q 和 k 都会参与计算,不会在意输入序列中的实际有效长度,即 padding 部分也会完整的参与到注意力结果的计算中,所以如果想要减少这部分的计算量,需要尽量少的加入 padding 字符。在其 forward 函数中存在一个参数 layer_past,layer_past 在模型训练时为空,在模型推理时,负责缓存前面已完成计算的 q k v。

Layers/MergedLinear():LoRA 的核心类,输入是 x 输出是 q k v,包含 LoRA 中权重参数 A 和 B 的定义。可以实现 A B 与原始 Weight 的合并和拆分。LoRA 的权重参数 A 和 B 的定义和计算过程也比较特殊,A 和 B 的合并计算是通过分组 conv1D 完成的,仔细分析计算过程,发现其实这个等效于矩阵乘法,其中分组的目的是让 Q K V 的计算相互独立,但不同 head 的计算不独立。A 与 B 的乘法比较特殊是一种交叉乘法,相比原来的不同 head 独立计算,这二次幂级增加了计算量和输出矩阵尺寸(与二次幂增加参数量后的 W 相匹配),但却不增加 A 和 B 的参数量。所谓交叉计算指的是不同 head 的 A 和 B 的计算不独立,会存在 head_i_A * head_j_B 的情况,这也是计算量和输出参数量增加的原因。该类继承自 nn.Linear,所以只能替换原模型中的 nn.Linear 层。

Layers/LoRALayer():LoRA 的配置类,只是定义了所需参数,可以配置原模型中哪些层需要加 LoRA Adapter。

Model/MLP():transformer decoder 中的线性映射层,包含两个线性层(先升维再降维)和一个激活函数。

这里仅仅展示 LoRA 核心类 MergedLinear() 的源码:

# 该类为 LoRA 的核心类,forward 函数输入是某一个注意力模块中的 x,输出是对应 q k v 组成的矩阵。类中定义了原注意力参数 W 和对应的 LoRA 权重 A 和 B。当 merge_weights=True 时,可以通过 train() 函数实现总体参数的合并和拆分,当其等于 True 时,参数默认被拆分。
# 原注意力模块参数 W 在定义后被冻结,不能被训练,train() 函数不影响它是否可训练,该类不修改 W。W 定义在 nn.Linear 中,注意 W 的尺寸为 (in_features, out_features),其中 in_features=n_heads*size_embding,out_features=3*n_heads*size_embding。
# 这意味着原注意模块中不同 head 的计算是不独立的,这与原 Transformer 不太一样,原来是先不同 head 独立计算,完成计算后再通过全连接层合并多头特征,而这里直接第一步就是非独立计算,这样会极大地增大参数量和计算量,它们随 head 数量二次幂增加,原本是线性增加。
# LoRA 的权重参数 A 和 B 的定义和计算过程也比较特殊,A 和 B 的合并计算是通过分组 conv1D 计算的,仔细分析计算过程,发现其实这个等效于矩阵乘法,其中分组的目的是让 Q K V 的计算相互独立,但不同 head 的计算是不独立的。A 与 B 的乘法比较特殊是一种交叉乘法,
# 相比原来的不同 head 独立计算,这二次幂级增加了计算量和输出矩阵尺寸(与二次幂增加参数量后的 W 相匹配),但却不增加 A 和 B 的参数量。所谓交叉计算指的是不同 head 的 A 和 B 的计算不独立,会存在 head_i_A * head_j_B 的情况,这也是计算量和输出参数量增加的原因。
class MergedLinear(nn.Linear, LoRALayer):
    # LoRA implemented in a dense layer
    def __init__(
        self, 
        in_features: int,                     # 原注意力模块 Q/K/V 矩阵的行/列尺寸(行列尺寸相等,Q K V 尺寸相等) * n_heads,              [head1.., head2.., head3..]
        out_features: int,                    # 原注意力模块 Q K V 的输出尺寸之和(等于 3 倍的独自尺寸) * n_heads,先分 Q K V,再分 heads  [Q_head1.., Q_head2.., Q_head3.., K_head1.., K_head2.., K_head3.., V_head1.., V_head2.., Q_head3..]
        r: int = 0,                           # LoRA 的秩                                           # 从上面 in_features 和 out_features 的展示形式很容易判断,输入不区分 Q K V 但输出区分 Q K V,输入和输出都要区分不同 head
        lora_alpha: int = 1,                  # LoRA 的 alpha                                       # 所以从 in_features 映射到 out_features 的矩阵 W 是 Q K V 独立,但不同 head 不独立的
        lora_dropout: float = 0.,             # LoRA 的 Dropout,在 LoRA 分支上是,先 Dropout 再 A 和 B
        enable_lora: List[bool] = [False],    # 长度为 3,决定 Q K V 是否使用 LoRA,一般定义为 [True, False, True],即 K 不使用 LoRA
        fan_in_fan_out: bool = False,         # 决定是否部分矩阵转置一下,设置为 True
        merge_weights: bool = True,           # 为 True 时,训练时分开参数,测试时合并参数;为 False 时,训练时和测试时均分开参数;决定是训练还是测试由 train 和 eval 模式决定
        **kwargs
    ):
        nn.Linear.__init__(self, in_features, out_features, **kwargs)                                                  # 调用 nn.Linear 的基函数,实现对原有参数的定义,注意 in_features=feature*n_heads out_features=3*feature*n_heads
        LoRALayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout, merge_weights=merge_weights)   # 调用 LoRA 基函数,实现相关参数初始化输入
        assert out_features % len(enable_lora) == 0, 'The length of enable_lora must divide out_features'              # out_features=3*feature*n_heads,Q K V 放在一起以及所有 head 放在一起了,len(enable_lora)=3 所以必须要能整除
        self.enable_lora = enable_lora                                                                                 # 长度为 3,决定 Q K V 是否使用 LoRA,一般定义为 [True, False, True],即 K 不使用 LoRA
        self.fan_in_fan_out = fan_in_fan_out                                                                           # 决定是否部分矩阵转置一下,设置为 True
        # Actual trainable parameters                                                                  # !!!【状态 1】 !!!           # !!!【状态 2】 !!!
        if r > 0 and any(enable_lora):                                                                 # enable_lora 包含 3 True         # enable_lora 包含 2 True
            self.lora_A = nn.Parameter(
                self.weight.new_zeros((r * sum(enable_lora), in_features))                             # (3*r, in_features)              # (2*r, in_features)          对于 LoRA A 是 Q K V 独自享有自己的部分,计算时相互独立
            )
            self.lora_B = nn.Parameter(
                self.weight.new_zeros((out_features // len(enable_lora) * sum(enable_lora), r))        # (out_features, r)               # (2/3*out_features, r)       对于 LoRA B 是 Q K V 独自享有自己的部分,计算时相互独立
            )
            self.scaling = self.lora_alpha / self.r
            # Freezing the pre-trained weight matrix
            self.weight.requires_grad = False
            # Compute the indices
            self.lora_ind = self.weight.new_zeros(
                (out_features, ), dtype=torch.bool
            ).view(len(enable_lora), -1)                                                               # (3, out_features/3)             # (3, out_features/3)         全 0 矩阵
            self.lora_ind[enable_lora, :] = True                                                       # 所有元素全 True                  # 对应行所有元素全 True
            self.lora_ind = self.lora_ind.view(-1)                                                     # (out_features)                  # (out_features)
        self.reset_parameters()
        if fan_in_fan_out:
            self.weight.data = self.weight.data.transpose(0, 1)                                        # (in_features, out_features)     # (in_features, out_features)

    # 参数初始化,尤其注意所有 LoRA 的初始化为全 0
    def reset_parameters(self):
        nn.Linear.reset_parameters(self)
        if hasattr(self, 'lora_A'):
            # initialize A the same way as the default for nn.Linear and B to zero
            nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
            nn.init.zeros_(self.lora_B)

    # 当 Q K V 均添加 LoRA 时,该函数无意义,否则,该函数负责 padding 增加没有 LoRA 的部分
    def zero_pad(self, x):                                                                             # (out_features, in_features)     # (2/3*out_features, in_features)
        result = x.new_zeros((len(self.lora_ind), *x.shape[1:]))                                       # (out_features, in_features)     # (out_features, in_features)         
        result[self.lora_ind] = x                                                                      # (out_features, in_features)     # (out_features, in_features)  对应部分有值,其他部分为 0
        return result
    
    # LoRA 权重参数 A 和 B 的合并是通过分组 conv1D 计算的,仔细分析计算过程,发现其实这个等效于矩阵乘法,其中分组的目的是让 Q K V 的计算相互独立,但不同 head 的计算不独立。与原本不同 head 独立计算相比,A 和 B 的参数量并没有增加,
    def merge_AB(self):               # 但 A*B 的计算量和输出矩阵尺寸却二次幂级增加了,原因是 A 与 B 的乘法比较特殊是一种交叉乘法,会存在 A_head_i * B_head_j 的情况,并非仅仅包含 A_head_i * B_head_i 和 A_head_j * B_head_j
        def T(w):
            return w.transpose(0, 1) if self.fan_in_fan_out else w
        delta_w = F.conv1d(                 # F.conv1d                                                 # enable_lora 包含 3 True          # enable_lora 包含 2 True
            self.lora_A.unsqueeze(0),       # input: (Batch_Size, In_Channel, Length)                 # (1, 3*r, in_features)            # (1, 2*r, in_features)
            self.lora_B.unsqueeze(-1),      # weight:(Out_Channel, In_Channel/Group, Kernel_Size)     # (out_features, r, 1)             # (2/3*out_features, r, 1)
            groups=sum(self.enable_lora)    # group: Group                                            # 3                                # 2  
        ).squeeze(0)                        # output:(1, Batch_Size, Out_Channel, In_Channel)         # (1, out_features, in_features)   # (1, 2/3*out_features, in_features)  无 squeeze()
        return T(self.zero_pad(delta_w))                                                               # (in_features, out_features)      # (in_features, out_features)

    # 简单点总结,这个函数的作用是:merge_weights 为 False 时不做任何处理,此时参数一定是拆分状态(前提是 merge_weights 在完成定义后不再被修改),因为整个代码中,只有这个函数会合并参数,如果这个函数没有去合并参数,那参数一定处于拆分状态
    # merge_weights 为 True 时,如果 train mode 为 True,则使得参数处于拆分状态,如果 mode 为 False,则使得参数处于合并状态,所谓参数拆分是指将总 W 拆分为 Pretrain_W 和 LoRA_W
    def train(self, mode: bool = True):  # mode 决定是训练模式还是测试默认,默认是 True,一般都是使用默认模式,但当设置 false 时,则等效于调用 eval() 函数,此时一般直接调用 eval() 函数
        # 没用到的函数
        def T(w):
            return w.transpose(0, 1) if self.fan_in_fan_out else w
        # 让原有参数变为 train 模式,我理解这里其实没有造成任何影响,train 只对 BN、 Dropout 和 LoRA 产生影响,对 LoRA 产生影响时还需要结合 merge_weights 状态的设置,设置为 True 时才产生影响
        nn.Linear.train(self, mode)
        # 如果是训练模式,这里的模式不影响参数是否可训练,只对 BN、 Dropout 和 LoRA 产生影响,对 LoRA 产生影响时还需要结合 merge_weights 状态的设置,设置为 True 时才产生影响
        if mode:
            # 如果 merge_weights 为 Ture (意味着训练拆分|测试合并),当前是训练模式,所以如果当前是合并状态则需要拆分参数,如果当前是拆分状态不需要做任何处理;如果 merge_weights 为 False,则无需做任何处理,因为默认就是拆分状态
            if self.merge_weights and self.merged:
                # Make sure that the weights are not merged
                if self.r > 0 and any(self.enable_lora):
                    self.weight.data -= self.merge_AB() * self.scaling                                 # (in_features, out_features)      # (in_features, out_features)
                self.merged = False
        # 如果是测试模式,这里的模式不影响参数是否可训练,只对 BN、 Dropout 和 LoRA 产生影响,对 LoRA 产生影响时还需要结合 merge_weights 状态的设置,设置为 True 时才产生影响
        else:
            if self.merge_weights and not self.merged:
                # Merge the weights and mark it
                if self.r > 0 and any(self.enable_lora):
                    self.weight.data += self.merge_AB() * self.scaling                                 # (in_features, out_features)      # (in_features, out_features)
                self.merged = True        

    # 执行函数
    def forward(self, x: torch.Tensor):
        def T(w):
            return w.transpose(0, 1) if self.fan_in_fan_out else w
        # self.merged 描述的是参数当前合并状态
        if self.merged:  # 如果参数已经合并,那直接单次计算即可完成输出
            return F.linear(x, T(self.weight), bias=self.bias)                                         # (batch, seq_length, out_features) = (batch, seq_length, in_features) * (out_features, in_features)^T
        else:            # 如果参数还没有合并,那需要分别经过 W 和 delta_W 的计算,然后合并结果
            result = F.linear(x, T(self.weight), bias=self.bias)                                       # (batch, seq_length, out_features) = (batch, seq_length, in_features) * (out_features, in_features)^T
            if self.r > 0:
                result += self.lora_dropout(x) @ T(self.merge_AB().T) * self.scaling                   # (batch, seq_length, out_features) = (batch, seq_length, out_features) + (batch, seq_length, in_features) * (in_features, out_features)
            return result

三、数据集

这里并不是从零起训练模型,而是采用 SFT 进行模型微调,如下所示数据集中包含输入和人工标注的输出部分,“||” 分割两部分。

name : Blue Spice | Type : coffee shop | area : city centre||A coffee shop in the city centre area called Blue Spice . 
name : Blue Spice | Type : coffee shop | area : city centre||Blue Spice is a coffee shop in city centre . 
name : Blue Spice | Type : coffee shop | area : riverside||There is a coffee shop Blue Spice in the riverside area . 
name : Blue Spice | Type : coffee shop | area : riverside||At the riverside , there is a coffee shop called The Blue Spice . 
name : Blue Spice | Type : coffee shop | customer rating : 5 out of 5 | near : Crowne Plaza Hotel||The coffee shop Blue Spice is based near Crowne Plaza Hotel and has a high customer rating of 5 out of 5 . 

Data_utils/FT_Dataset() 负责数据读取,数据需要事先经过分词和映射为整形序列,并在输入数据 conditions 和输出数据 completion 后面都添加一个序列 id 为 50256 的特殊字符,所以送入 Data_utils/FT_Dataset() 的数据如下所示。

{"context": [3672, 1058, 4518, 43537, 930, 5994, 1058, 6891, 6128, 930, 1989, 1058, 1748, 7372, 50256], "completion": [317, 6891, 6128, 287, 262, 1748, 7372, 1989, 1444, 4518, 43537, 764, 50256]}
{"context": [3672, 1058, 4518, 43537, 930, 5994, 1058, 6891, 6128, 930, 1989, 1058, 1748, 7372, 50256], "completion": [4518, 43537, 318, 257, 6891, 6128, 287, 1748, 7372, 764, 50256]}
{"context": [3672, 1058, 4518, 43537, 930, 5994, 1058, 6891, 6128, 930, 1989, 1058, 18180, 485, 50256], "completion": [1318, 318, 257, 6891, 6128, 4518, 43537, 287, 262, 18180, 485, 1989, 764, 50256]}
{"context": [3672, 1058, 4518, 43537, 930, 5994, 1058, 6891, 6128, 930, 1989, 1058, 18180, 485, 50256], "completion": [1629, 262, 18180, 485, 837, 612, 318, 257, 6891, 6128, 1444, 383, 4518, 43537, 764, 50256]}
{"context": [3672, 1058, 4518, 43537, 930, 5994, 1058, 6891, 6128, 930, 6491, 7955, 1058, 642, 503, 286, 642, 930, 1474, 1058, 12223, 68, 23280, 12696, 50256], "completion": [383, 6891, 6128, 4518, 43537, 318, 1912, 1474, 12223, 68, 23280, 12696, 290, 468, 257, 1029, 6491, 7955, 286, 642, 503, 286, 642, 764, 50256]}

Data_utils/FT_Dataset() 的输出数据格式是一个样本对应一个字典,字典的键包括 "query","query_len","input","target","mask"。模型训练时使用 "input","target","mask",模型推理时使用 "query","query_len"。"input" 是 conditions+completion+补长字符,不包含其他特殊字符,总长度是 max_seq_length,"target" 是 "input" 向前错一位,"mask" 是 0|1 的序列,用于标识 "input" 中的有效长度。"query" 是 conditions+补长字符,不包含其他特殊字符,总长度是 max_seq_length,"query_len" 是一个整形数值表示 "query" 中的有效长度。所以不管是模型训练时还是模型推理时,送入模型的整形序列长度都是固定的 max_seq_length,与实际有效数据长度无关。

四、LoRA 模型训练

gpt2_ft.py 负责 LoRA 模型训练,其会调用 utils.pyoptimizer.pygpu.py 中的部分函数。训练部分的核心代码在 gpt2_ft/train_validate() 中,其训练过程与标准的 torch 模型训练过程无异,只是采用了分布式训练。

LoRA 模型训练使用的输入是 [conditions+completion+补长字符],目标输出是错一位的 [conditions+completion+补长字符],在计算 loss 时基于 "mask" 仅仅计算有效长度 conditions+completion 部分的 loss,不会计算 padding 部分的 loss。模型训练时的输入数据 batch 中不要求所有样本的有效长度都相等,但所有样本最终都会被 padding 到固定长度 max_seq_length,这会存在大量的算力浪费,因为在模型中并非按照有效长度计算注意力结果,而是按照输入数据长度计算注意力结果。在 HuggingFace 的 dataset 库中就实现了一些功能可以节省算力和内存,这些功能是:1. 在组成输入数据 batch 时尽量挑选长度相差不大的样本;2. 允许挑选长度过短的样本组成一个样本进行训练;3. batch 中的样本在 padding 时,不是 padding 到固定长度,而是 padding 到本 batch 中最长样本的长度。当然前提是这里的 small GPT2 和 HuggingFace 中的模型都是不要求输入数据必须满足固定长度的。

五、LoRA 模型推理

gpt2_beam.py 负责 LoRA 模型推理,其会调用 gpu.py 中的部分函数。推理部分的核心代码在 gpt2_beam/beam() 中。

LoRA 模型推理时,首先一次性输入长度为 max_seq_length 的输入序列 [conditions+补长字符],一个 batch 中不要求所有 conditions 都等长,并有个参数 len 记录 batch 中每个样本的真实长度。模型推理过程中,与训练过程类似,存在大量算力浪费。在第一次推理结束后,模型会输出一个 layer_past 用于缓存本轮计算所得到的 k v。从第二轮推理开始,每个样本依据 len 记录的有效长度从模型上一轮最后一个有效输出端处拿到一个输出字符(作为上一轮推理的输出),然后将这个字符(长度固定为 1)再送入模型进行推理得到新一个输出字符,这里依然存在小量算力浪费,计算注意力系数时,依然需要与整个 max_seq_length 做计算,而不是仅仅与有效长度做计算。从第二轮推理开始,原已经计算过 k v 的字符不用再次计算,只需从缓存中提取即可,每次仅计算单个新字符的 q k v,计算完成后需要将新 k v 缓存以备下一个新字符的查询,缓存 k v 的数据结构 layer_past 的长度在模型针对这个样本的第一次推理时就固定为输入序列的最大长度了,后续更新缓存时,只需要在上一步真实长度 +1 处填上新的 k v 即可。

LoRA 模型推理时采用的是束搜索方法。具体来说,模型第一次推理时,输入序列尺寸为 [batch*n_beams, max_seq_length],其中包含 batch 个不同的样本组,一个样本组中的 n_beams 个样本完全相同,是通过复制得来,这里存在大量算力浪费。输出序列尺寸为 [batch, n_beams*vocab],依然是 batch 个样本组,从每个样本组中挑选 n_beams 个得分最高的样本(得分通过预测的每个新字符的概率累加得到),得到最终输出序列尺寸为 [batch, n_beams]。从模型第二次推理开始,输入序列尺寸为 [batch*n_beams, 1],并搭配 layer_past 一起送入模型进行推理。输出序列尺寸为 [batch, n_beams*vocab],依然是 batch 个样本组,从每个样本组中挑选 n_beams 个得分最高的样本(得分通过预测的每个新字符的概率累加得到),得到最终输出序列尺寸为 [batch, n_beams],同时需要根据挑选结果更新 layer_past  中跟踪的样本,即重新组建 beams。循环往复直至达到终止条件,在循环过程中,有可能某个 beam 输出了终止字符 [eos],那可以把这个 beam 对应的 sentence 作为对应样本的一个输出结果,同时将该 beam 的得分设置为 -inf,这样下次迭代更新 beams 时就会把这个 beam 删除,继续跟踪新的 beam。所以理想情况下,完成所有迭代后,每一个样本都会得到 n 个不同的输出结果,每个结果也会搭配一个得分,这里的 n 一般大于等于 n_beams,达到终止条件后,即使没有输出 [eod] 字符,也认为该样本生成结束。

在每个样本的束搜索过程中,按照得分挑选新一轮 beams 时,提前设置了 3 种后处理条件,以增加生成结果的多样性和真实性。第一,对前面已经被预测过的字符,在本轮的得分上乘以小于 1 的系数,进行惩罚;第二,对于紧挨着前面预测的字符如果与本轮所预测的字符组合起来的新字符组,如果在前面已经被预测过了,则可以对对应字符的得分进行惩罚;第三,如果当前预测句子太短,可以对 [eos] 字符对应的得分进行惩罚。

在模型推理过程中,除了按照 HuggingFace 的方式修改样本 padding 方式可以节省计算量外,还可以将 layer_past  的长度修改可变长度,让长度等于有效长度,避免当前字符的 q 在与所有字符的 k 计算注意力系数时,浪费算力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1715145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

四川汇聚荣聚荣科技有限公司是正规的吗?

在当今社会,随着科技的飞速发展,越来越多的科技公司如雨后春笋般涌现。然而,在这个信息爆炸的时代,如何判断一家公司是否正规成为了许多人关注的焦点。本文将围绕“四川汇聚荣聚荣科技有限公司是否正规”这一问题展开讨论&#xf…

【漏洞复现】大华智能物联综合管理平台 log4j远程代码执行漏洞

0x01 产品简介 大华ICC智能物联综合管理平台对技术组件进行模块化和松耦合,将解决方案分层分级,提高面向智慧物联的数据接入与生态合作能力。 0x02 漏洞概述 大华ICC智能物联综合管理平台/evo-apigw/evo-brm/1.2.0/user/is-exist 接口处存在 l0g4i远程…

Spring Cache自定义缓存key和过期时间

一、自定义全局缓存key和双冒号替换 使用 Redis的客户端 Spring Cache时,会发现生成 key中会多出一个冒号,而且有一个空节点的存在。 查看源码可知,这是因为 Spring Cache默认生成key的策略就是通过两个冒号来拼接。 同时 Spring Cache缓存…

展现市场布局雄心,ATFX再度亮相非洲峰会,开启区域市场新篇章

自2023年全球市场营销战略部署实施以来,ATFX在全球各区域市场取得了丰硕成果,其品牌实力、知名度、影响力均有大幅提升。在这场全球扩张的征程中,非洲市场日益成为集团关注的焦点。自2023年首次踏上这片充满潜力的市场以来,ATFX持…

技术架构设计指南:从需求到实现

技术架构是软件系统的骨架,它决定了系统的性能、可靠性、扩展性等关键特性。本文将介绍技术架构设计的一般步骤和方法。 第一步:需求分析 在设计技术架构之前,首先要对系统需求进行全面深入的分析。这包括功能需求、非功能需求(如…

用户接入和认证技术

一、用户接入和认证配置 称为网络接入控制,通过对接入网络的客NAC (Network Admission Control)户端和用户的认证保证网络的安全,是一种“端到端”的安全技术。包括802.1x认证、MAC认证与Portal认证。 二、三种认证方式简介 1、Portal认证 Portal认证通…

最新上市公司控制变量大全(1413+指标)1990-2023年

数据介绍:根据2023年上市公司年报数据进行更新,包括基本信息、财务指标、环境、社会与治理、数字化转型、企业发展、全要素生产率等1413指标。数据范围:A股上市公司数据年份:1990-2023年指标数目:1413个指标&#xff0…

Linux服务器配置ssh证书登录

1、ssh证书登录介绍 Linux服务器ssh登录有密码登录和证书登录两种。如果使用密码登录,容易遭受密码泄露或者暴力破解,我们可以使用ssh证书登录并禁止使用密码登录,ssh证书登录通过公钥和私钥来完成整个连接过程,公钥保存在服务器…

MVC和Filter

目录 MVC和三层架构模型的联系 Filter 概念 作用 应用场景 步骤 简单入门 MVC和三层架构模型的联系 m-->model即模型是三层架构模型的业务层(service)和持久层(dao) v-->views即视图是三层架构模型的表现层(web) c-->controller即控制器也…

多模态模型入门:BLIP与OWL-ViT

BLIP 数据预处理 CapFilt:标题和过滤 由于多模态模型需要大量数据集,因此通常必须使用图像和替代文本 (alt-text) 对从互联网上抓取这些数据集。然而,替代文本通常不能准确描述图像的视觉内容,使其成为噪声信号,对于…

Matlab|基于PMU相量测量单元进行电力系统电压幅值和相角状态估计

主要内容 程序采用三种方法对14节点和30节点电力系统状态进行评估: ①PMU同步相量测量单元结合加权最小二乘法(WLS)分析电力系统的电压幅值和相角状态; ②并采用牛顿-拉夫逊方法进行系统潮流计算,结果作为理论分…

动态规划part03 Day43

LC343整数拆分(未掌握) 未掌握分析:dp数组的含义没有想清楚,dp[i]表示分解i能够达到的最大乘积,i能够如何分解呢,从1开始遍历,直到i-1;每次要不是j和i-j两个数,要不是j和…

MySQL建库

删除数据库 新建数据库 右键-新建数据库 字符集选中utf8(支持中文) 修改字符集 右键--数据库的属性 将字符集支持的数量变少可以修改

AIOps在线评测基准首阶段建设完成,面向社区发布真实运维数据!

本文根据必示科技算法研究员、产品总监聂晓辉博士在2024 CCF国际AIOps挑战赛线下宣讲会上的演讲整理成文。 2024年1月份OpenAIOps社区成立,随着越来越多的社区成员加入,各项工作在有条不紊的推进中。在线评测基准系统(AIOps Live Benchmark&a…

【刷题(13)】二分查找

一、二分查找基础 &#xff08;1&#xff09;int mid ((right - left) >> 1) left; &#xff08;2&#xff09;lower_bound的底层实现 int lower_bound(vector<int>& nums, int x) {int left 0;int right nums.size() - 1;// 区间为 左闭右闭while (lef…

JAVA 17

文章目录 概述一 语法层面变化1_JEP 409&#xff1a;密封类2_JEP 406&#xff1a;switch模式匹配&#xff08;预览&#xff09; 二 API层面变化1_JEP 414&#xff1a;Vector API&#xff08;第二个孵化器&#xff09;2_JEP 415&#xff1a;特定于上下文的反序列化过滤器 三 其他…

【Linux进程篇】Linux进程管理——进程创建与终止

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 目录 进程创建 fork函数初识 写时拷贝 fork常规用法 fork调用失败的原因 进程终止 进程退出场景 _exit函数 exit函数 return退出 进程创建 fork函数初识 在linux中fork函数时非常重要的函数&#xff0c;它从已…

Javascript 基础知识 —— 重写数组方法

1、写一个函数&#xff0c;实现深度克隆对象 const obj {name: "LIYUFAN",age: 25,career: "初级前端工程师",info: {field: ["JS", "CSS", "HTML"],framework: ["React", "Vue", "Angular"…

jmeter多用户并发登录教程

有时候为了模拟更真实的场景&#xff0c;在项目中需要多用户登录操作&#xff0c;大致参考如下 jmx脚本&#xff1a;百度网盘链接 提取码&#xff1a;0000 一&#xff1a; 单用户登录 先使用1个用户登录&#xff08;先把1个请求调试通过&#xff09; 发送一个登录请求&…

DSM驾驶行为分析系统在渣土车管理中的应用

随着科技的不断进步&#xff0c;智能交通系统正逐渐成为现代交通管理的重要工具。其中&#xff0c;DSM驾驶行为分析系统以其独特的功能和优势&#xff0c;在提升驾驶安全性、优化驾驶员管理等方面发挥着重要作用。索迪迈科技将DSM驾驶行为分析系统成功应用于渣土车管理中&#…