关于LLM+LoRa微调加速技术原理

如何花费较少的算力成本来进行微调训练,十分重要,当前关于llama、alpaca、instruct微调、lora微调等多个概念大家讲的很多,最近也在学习,也看到几个有趣的话题。
首先,来看关于instruct微调和lora微调
instruct微调和lora微调是两种不同的技术。instruct微调是指在深度神经网络训练过程中调整模型参数的过程,以优化模型的性能。在微调过程中,使用一个预先训练好的模型作为基础模型,然后在新的数据集上对该模型进行微调。instruct微调是一种通过更新预训练模型的所有参数来完成的微调方法,通过微调使其适用于多个下游应用。
lora微调则是指对低功耗广域网(lorawan)中的lora节点参数进行微调的过程,以提高节点的传输效率。在lora微调中,需要了解节点的硬件和网络部署情况,并通过对节点参数进行微小调整来优化传输效率。
与instruct微调相比,lora在每个transformer块中注入可训练层,因为不需要为大多数模型权重计算梯度,大大减少了需要训练参数的数量并且降低了gpu内存的要求。研究发现,使用lora进行的微调质量与全模型微调相当,速度更快并且需要更少的计算。因此,如果有低延迟和低内存需求的情况,建议使用lora微调。
其次,我们再来看看为什么会有llama模型和lora两种模型
如上所述,模型的微调方式有很多种,基于lora的微调产生保存了新的权重,可以将生成的lora权重认为是一个原来llama模型的补丁权重 。至于llama 权重,它则是由mean公司开源的大模型预训练权重。
最后,我们来看看关于词表扩充,为什么要扩充词表,直接在原版llama上用中文预训练不行?
本身llama对中文支持不是很好,大多数相关衍生工作是直接在原版上进行pretrain/finetune的,从而采取了更大胆的策略——增加中文词表,可能进一步加剧中文训练不充分的问题,但从长远看是否有利于后续进一步预训练就得靠时间检验了,加入词表是有一定破坏性的,一是破坏原有分词体系,二是增加了未训练的权重。所以如果不能进行充分训练的话,可能会有比较大的问题。如果不是特别专的领域(比如生物医学等涉及很多专业词汇的领域)没有太大必要去扩充英文词表。
原版llama模型的词表大小是32k,其主要针对英语进行训练(具体详见llama论文),对多语种支持不是特别理想(可以对比一下多语言经典模型xlm-r的词表大小为250k)。通过初步统计发现,llama词表中仅包含很少的中文字符,所以在切词时会把中文切地更碎,需要多个byte token才能拼成一个完整的汉字,进而导致信息密度降低。
比如,在扩展词表后的模型中,单个汉字倾向于被切成1个token,而在原版llama中可能就需要2-3个才能组合成一个汉字,显著降低编解码的效率。
由于原版llama对中文的支持非常有限,chinese-llama-alpaca项目在原版llama的基础上进一步扩充了中文词表。在通用中文语料上训练了基于sentencepiece的20k中文词表并与原版llama模型的32k词表进行合并,排除重复的token后,得到的最终中文llama词表大小为49953。需要注意的是,在fine-tune阶段alpaca比llama多一个pad token,所以中文alpaca的词表大小为49954。
为了进一步加深对lora的理解,本文主要从lora基本原理及peft中的实现、基于mt0-large+lora的完整实践两方面进行介绍,供大家一起参考。
一、lora基本原理及peft中的实现
当前,已经出现了很多lora作为adapter的微调模型,如alpaca lora,chinese-llama-alpaca等,其在公开时会注明:中文llama/alpaca lora模型无法单独使用,需要搭配原版llama模型,发布的是lora权重,可以理解为原llama模型上的一个“补丁”,两者进行合并即可获得完整版权重。
lora的实现原理在于,冻结预训练模型权重,并将可训练的秩分解矩阵注入到transformer层的每个权重中,大大减少了下游任务的可训练参数数量。直白的来说,实际上是增加了右侧的“旁支”,也就是先用一个linear层a,将数据从 d维降到r,再用第二个linear层b,将数据从r变回d维。最后再将左右两部分的结果相加融合,得到输出的hidden_state。
如上图所示,左边是预训练模型的权重,输入输出维度都是d,在训练期间被冻结,不接受梯度更新。右边部分对a使用随机的高斯初始化,b在训练开始时为零,r是秩,会对△wx做缩放 α/r。
幸运的是,huggingface的peft(parameter-efficient fine-tuning,地址:https://github.com/huggingface/peft)中提供了模型微调加速的方法,参数高效微调(peft)方法能够使预先训练好的语言模型(plms)有效地适应各种下游应用,而不需要对模型的所有参数进行微调。
对大规模的plm进行微调往往成本过高,在这方面,peft方法只对少数(额外的)模型参数进行微调,基本思想在于仅微调少量 (额外) 模型参数,同时冻结预训练 llm 的大部分参数,从而大大降低了计算和存储成本,这也克服了灾难性遗忘的问题,这是在 llm 的全参数微调期间观察到的一种现象peft 方法也显示出在低数据状态下比微调更好,可以更好地泛化到域外场景。
例如,使用peft-lora进行加速微调的效果如下,从中我们可以看到该方案的优势:
例如,其对lora做了封装支持,几步即可使用:
from peft import get_peft_model, loraconfig, tasktypepeft_config = loraconfig(    task_type=tasktype.causal_lm,     inference_mode=false,     r=8,     lora_alpha=32,     lora_dropout=0.1,    target_modules=['query_key_value'])model = 加载的模型model = get_peft_model(model, peft_config)model.print_trainable_parameters() 论文中提到了lora的一些优势:
1)一个预先训练好的模型可以被共享,并用于为不同的任务建立许多小的lora模块。可以冻结共享模型,并通过替换图中的矩阵a和b来有效地切换任务,大大降低了存储需求和任务切换的难度。
2)在使用自适应优化器时,lora使训练更加有效,并将硬件进入门槛降低了3倍,因为我们不需要计算梯度或维护大多数参数的优化器状态。相反,我们只优化注入的、小得多的低秩矩阵。
3)简单的线性设计允许在部署时将可训练矩阵与冻结权重合并,与完全微调的模型相比,在结构上没有引入推理延迟。
4)lora与许多先前的方法是正交的,可以与许多方法结合,如前缀调整。我们在附录e中提供了一个例子。
1、引入开源组件
”+”表示增加代码:
  from transformers import automodelforseq2seqlm+ from peft import get_peft_model, loraconfig, tasktype   model_name_or_path = bigscience/mt0-large  tokenizer_name_or_path = bigscience/mt0-large 2、引入lora配置信息
peft_config = loraconfig(    task_type=tasktype.seq_2_seq_lm,     inference_mode=false,     r=8,     lora_alpha=32,     lora_dropout=0.1) 3、进行推理
  from transformers import automodelforseq2seqlm+ from peft import peftmodel, peftconfig  peft_model_id = smangrul/twitter_complaints_bigscience_t0_3b_lora_seq_2_seq_lm  config = peftconfig.from_pretrained(peft_model_id)  model = automodelforseq2seqlm.from_pretrained(config.base_model_name_or_path)+ model = peftmodel.from_pretrained(model, peft_model_id)  tokenizer = autotokenizer.from_pretrained(config.base_model_name_or_path)  model = model.to(device)  model.eval()  inputs = tokenizer(tweet text : @hondacustsvc your customer service has been horrible during the recall process. i will never purchase a honda again. label :, return_tensors=pt)  with torch.no_grad():      outputs = model.generate(input_ids=inputs[input_ids].to(cuda), max_new_tokens=10)      print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=true)[0])# 'complaint' 二、基于mt0-large+lora的完整实践
接下来,我们来使用huggingface-peft库来进行一个lora的实践。
首先,在模型方面,我们选用mt0-large模型为例(只有1.2b),进行实验,模型地址:https://huggingface.co/bigscience/mt0-large。
模型权重地址:https://huggingface.co/bigscience/mt0-large/tree/main
先看看mt0-large是什么。多任务提示微调(mtf)已被证明可以帮助大型语言模型在zero-shot的环境下生成新的任务,但到目前为止,mtf的探索主要集中在英语数据和模型上,将mtf应用于预训练的多语言bloom和mt5模型系列,就产生称为bloomz和mt0的微调变体。
具体的,总共生产了三种不同尺寸的核心型号:
bloomz-p3 / mt0-p3:在纯英语的p3上进行微调的模型。
bloomz / mt0: 在xp3上进行微调的模型,xp3由带有英语提示的多语言数据集组成。
bloomz-mt / mt0-mt: 在xp3mt上进行模型微调,xp3mt由多语言数据集和机器翻译的提示语组成。
其次,在任务方面,我们选用金融领域情感分析任务financial_sentiment_analysis,给定一个句子,要求识别出该句子是negative、positive还是neutral三个中的哪一个,其中的数据样式如下:
{'sentence': the 10,000-odd square metre plot that stockmann has bought for the nevsky center shopping center is located on nevsky prospect , st petersburg 's high street , next to the vosstaniya square underground station , in the immediate vicinity of moscow station ., 'label': 1, 'text_label': 'neutral'} 我们可以通过datasests组件进行调用。
1、引入组件并设置参数
from transformers import automodelforseq2seqlmfrom peft import get_peft_config, get_peft_model, get_peft_model_state_dict, loraconfig, tasktypeimport torchfrom datasets import load_datasetimport osos.environ[tokenizers_parallelism] = falsefrom transformers import autotokenizerfrom torch.utils.data import dataloaderfrom transformers import default_data_collator, get_linear_schedule_with_warmupfrom tqdm import tqdmfrom datasets import load_datasetdevice = cudamodel_name_or_path = bigscience/mt0-largetokenizer_name_or_path = bigscience/mt0-largecheckpoint_name = financial_sentiment_analysis_lora_v1.pttext_column = sentencelabel_column = text_labelmax_length = 128lr = 1e-3num_epochs = 3batch_size = 8 2、搭建模型
peft_config = loraconfig(task_type=tasktype.seq_2_seq_lm, inference_mode=false, r=8, lora_alpha=32, lora_dropout=0.1)model = automodelforseq2seqlm.from_pretrained(model_name_or_path)model = get_peft_model(model, peft_config)model.print_trainable_parameters() 3、加载数据
dataset = load_dataset(financial_phrasebank, sentences_allagree)dataset = dataset[train].train_test_split(test_size=0.1)dataset[validation] = dataset[test]del dataset[test]classes = dataset[train].features[label].namesdataset = dataset.map(    lambda x: {text_label: [classes[label] for label in x[label]]},    batched=true,    num_proc=1,) 4、训练数据预处理
tokenizer = autotokenizer.from_pretrained(model_name_or_path)def preprocess_function(examples):    inputs = examples[text_column]    targets = examples[label_column]    model_inputs = tokenizer(inputs, max_length=max_length, padding=max_length, truncation=true, return_tensors=pt)    labels = tokenizer(targets, max_length=3, padding=max_length, truncation=true, return_tensors=pt)    labels = labels[input_ids]    labels[labels == tokenizer.pad_token_id] = -100    model_inputs[labels] = labels    return model_inputsprocessed_datasets = dataset.map(    preprocess_function,    batched=true,    num_proc=1,    remove_columns=dataset[train].column_names,    load_from_cache_file=false,    desc=running tokenizer on dataset,)train_dataset = processed_datasets[train]eval_dataset = processed_datasets[validation]train_dataloader = dataloader(    train_dataset, shuffle=true, collate_fn=default_data_collator, batch_size=batch_size, pin_memory=true)eval_dataloader = dataloader(eval_dataset, collate_fn=default_data_collator, batch_size=batch_size, pin_memory=true) 5、设定优化器和正则项
optimizer = torch.optim.adamw(model.parameters(), lr=lr)lr_scheduler = get_linear_schedule_with_warmup(    optimizer=optimizer,    num_warmup_steps=0,    num_training_steps=(len(train_dataloader) * num_epochs),) 6、训练与评估
model = model.to(device)for epoch in range(num_epochs):    model.train()    total_loss = 0    for step, batch in enumerate(tqdm(train_dataloader)):        batch = {k: v.to(device) for k, v in batch.items()}        outputs = model(**batch)        loss = outputs.loss        total_loss += loss.detach().float()        loss.backward()        optimizer.step()        lr_scheduler.step()        optimizer.zero_grad()    model.eval()    eval_loss = 0    eval_preds = []    for step, batch in enumerate(tqdm(eval_dataloader)):        batch = {k: v.to(device) for k, v in batch.items()}        with torch.no_grad():            outputs = model(**batch)        loss = outputs.loss        eval_loss += loss.detach().float()        eval_preds.extend(            tokenizer.batch_decode(torch.argmax(outputs.logits, -1).detach().cpu().numpy(), skip_special_tokens=true)        )    eval_epoch_loss = eval_loss / len(eval_dataloader)    eval_ppl = torch.exp(eval_epoch_loss)    train_epoch_loss = total_loss / len(train_dataloader)    train_ppl = torch.exp(train_epoch_loss)    print(f{epoch=}: {train_ppl=} {train_epoch_loss=} {eval_ppl=} {eval_epoch_loss=}) 执行训练日志输出如下:
100%|████████████████████████████████████████████████████████████████████████████████████████| 255/255 [02:21<00:00,  1.81it/s]100%|██████████████████████████████████████████████████████████████████████████████████████████| 29/29 [00:07<00:00,  4.13it/s]epoch=0: train_ppl=tensor(14.6341, device='cuda:0') train_epoch_loss=tensor(2.6834, device='cuda:0') eval_ppl=tensor(1.0057, device='cuda:0') eval_epoch_loss=tensor(0.0057, device='cuda:0')100%|████████████████████████████████████████████████████████████████████████████████████████| 255/255 [02:00<00:00,  2.11it/s]100%|██████████████████████████████████████████████████████████████████████████████████████████| 29/29 [00:05<00:00,  5.66it/s]epoch=1: train_ppl=tensor(1.7576, device='cuda:0') train_epoch_loss=tensor(0.5640, device='cuda:0') eval_ppl=tensor(1.0052, device='cuda:0') eval_epoch_loss=tensor(0.0052, device='cuda:0')100%|████████████████████████████████████████████████████████████████████████████████████████| 255/255 [01:33<00:00,  2.74it/s]100%|██████████████████████████████████████████████████████████████████████████████████████████| 29/29 [00:04<00:00,  6.23it/s]epoch=2: train_ppl=tensor(1.3830, device='cuda:0') train_epoch_loss=tensor(0.3243, device='cuda:0') eval_ppl=tensor(1.0035, device='cuda:0') eval_epoch_loss=tensor(0.0035, device='cuda:0') 7、模型保存
peft_model_id = f{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}model.save_pretrained(peft_model_id) 8、模型推理预测
from peft import peftmodel, peftconfigpeft_model_id = f{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}config = peftconfig.from_pretrained(peft_model_id)model = automodelforseq2seqlm.from_pretrained(config.base_model_name_or_path)model = peftmodel.from_pretrained(model, peft_model_id)model.eval()inputs = tokenizer(dataset[validation][text_column][i], return_tensors=pt)print(dataset[validation][text_column][i])print(inputs)with torch.no_grad():    outputs = model.generate(input_ids=inputs[input_ids], max_new_tokens=10)    print(outputs)    print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=true))     运行实例,例如输入:
demand for fireplace products was lower than expected , especially in germany .
输出:
{'input_ids': tensor([[  259,   264,   259, 82903,   332,  1090, 10040, 10371,   639,   259,         19540,  2421,   259, 25505,   259,   261,   259, 21230,   281, 17052,           259,   260,     1]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}tensor([[    0,   259, 32588,     1]])['negative'] 总结
本文主要从lora基本原理及peft中的实现、基于mt0-large+lora的完整实践两方面进行了介绍。关于进一步的细节,我们可以熟悉原理后,可以进行动手实践,加深理解。


半导体推力测试仪优化了测试模块
发展中国家需要一种怎样的加密货币
芯片现在还短缺吗
评测ANKER 65W快充充电器:小体积大功率,支持PPS、QC等多协议
中国科技企业创新成果井喷:汉威多款创新产品震撼发布
关于LLM+LoRa微调加速技术原理
RT-MSPD1-30/F1控制不带位移反馈比例放大器
Merlin HugeCTR第三代 Embedding 功能优化
线路高频保护停用对重合闸的使用有什么影响?
大规模智能网联汽车示范应用发布会议在长沙举行
led显示器故障维修方法
美国法官认定苹果侵权,建议颁发禁令
数控机床高速电主轴有哪几种工作原理?
红米note8系列和荣耀9X相比,谁才是你心中的购机标准呢?
服务器在升级RAID卡固件版本后无法正常启动
胶管用钢丝绳标准GB/T12756-91
交流电压信号调理电路分析
智能配变综合监测终端的介绍,它都有哪些特色功能
在C++中使用OpenVINO工具包部署YOLOv5-Seg模型
拥抱eSIM技术,英飞凌引领新时代发展