CV算法的LLM领域日志
目前维护的CV方向开源项目暂时暂停,原因是现在在做LLM方向的研发工作,所以需要时间消化前沿技术和总结经验,最近看到了一个非常简单的LLM训练Trick 分享一下,后续会逐渐把自己使用的一些LLM范式技术原理和代码分享。
文章目录
- CV算法的LLM领域日志
- Neft-tune
- 一、动机
- 二、代码
- 1.引入真实上下文的代码
- 总结
Neft-tune
Noisy embeddings improve instruction finetune,官方文章点此,简单来说,这是一种嵌入噪声的微调方法,已经被HuggingFace收录进了TRL库,只要import再加一行代码就能调用。
from datasets import load_dataset
from trl import SFTTrainer
dataset = load_dataset("your data path ", split=“train”)
trainer = SFTTrainer(
“facebook/opt-350m”,
train_dataset=dataset,
dataset_text_field=“text”,
max_seq_length=512,
)
trainer.train()
NEFT不仅操作简便,而且没有显著的成本增加,作者称看起来是个“免费的午餐”。
一、动机
在做LLM的微调时候,往往都容易在finetune数据集上过拟合,作者目的就是希望在训练阶段向嵌入层中加入噪声的方式作为一种正则化方式来改善这个情况,从而提高性能,仅针对训练阶段,所以非常适合微调工程,下面是算法逻辑:
1.分词器Embedding。
2.随机生成一个噪声, 然后,系统会随机生成一个噪声向量,并根据给定的超参数进行缩放。
3.缩放后的向量会和embedding相加,在训练的embedding层的forward过程中存在
4. 训练每轮迭代1-3步骤。
在此Trick使用后,作者团队在LLAMA2-7B和Mistral-7B上分别实现了翻倍和25%的提升,按原话说至少提升10%的"免费午餐”,且进一步验证在文本生成任务的质和量上也Work.
二、代码
1.引入真实上下文的代码
NEFT的代码很简单,相当于是对模型的embedding层进行了一次改动和额外引入噪声系数的超参数(默认为5),实际在使用中发现,NEFT的改动是在模型加载后一个结构的后调整,这样在代码逻辑中非常容易实现,不会影响其他上下文的程序逻辑,下面给出一个模拟实际上下文的代码和注释参考:
代码如下(示例):
import transformers
from transformers import Trainer#, GPTQConfig
model = transformers.AutoModelForCausalLM.from_pretrained(
model_args.model_name_or_path,
config=config,
cache_dir=training_args.cache_dir,
device_map=device_map,
trust_remote_code=True,
# quantization_config=GPTQConfig(
# bits=4, disable_exllama=True
# )
if training_args.use_lora and lora_args.q_lora
else None,
)
tokenizer = transformers.AutoTokenizer.from_pretrained(
model_args.model_name_or_path,
cache_dir=training_args.cache_dir,
model_max_length=training_args.model_max_length,
padding_side="right",
use_fast=False,
trust_remote_code=True,
)
##首先进行模型和分词器的官方库加载即可
##加入NEFT的模型结构设置即可,下面是核心代码,给定一个噪声强度系数默认值为5
neft_alpha=5
print("strat neft!")
if neft_alpha > 1e-6:
# 如果模型处于训练模式,则对嵌入矩阵的每个元素加上一个服从均匀分布的随机噪声,噪声范围由finetuning_args.neft_alpha和嵌入矩阵维度决定,在训练模式下,首先计算出噪声的幅度因子(mag_norm),然后torch.zeros_like(embeddings).uniform_(-mag_norm, mag_norm)生成与原始嵌入向量相同形状的张量,该张量的元素取值范围在[-mag_norm, mag_norm]之间,再将该张量加到原始嵌入向量上。最后,函数返回添加了噪声的嵌入向量。核心思路是这个写法改动了模型原始input embedding层的FORWARD操作。
input_embed = model.get_input_embeddings()
if isinstance(input_embed, torch.nn.Embedding):
def noisy_forward(self: torch.nn.Embedding, x: torch.Tensor) -> torch.Tensor:
embeddings = input_embed.__class__.forward(self, x)
if self.training:
dims = self.num_embeddings * self.embedding_dim
mag_norm = neft_alpha / (dims ** 0.5)
embeddings+=torch.zeros_like(embeddings).uniform_(-mag_norm, mag_norm)
return embeddings
input_embed.forward = noisy_forward(input_embed)
print("Using noisy embedding with alpha={:.2f}".format(neft_alpha))
else:
print("Input embeddings are not normal nn.Embedding, cannot transform into noisy embedding.")
这样模型在训练阶段输入的embedding层在前向传播中会执行我们设定好的Noise embedding funetune方法,推理中则按照原始结构进行,后面可以接入我们常用的lora等微调模型的方法。
总结
后面也会对LORA\QLORA\LONGLORA等微调技术以及更多原理方法逐一解析分享。