zero_nlp
zero_nlp copied to clipboard
Lora导致推理时长增加70%
如题,虽然知道Lora不可避免的会增加推理时长,但这个幅度也有点太大了,请问有什么可以提速的方法吗。
如果你用的是zero_nlp/simple_thu_chatglm6b/infer.ipynb,原因大概有3点
- LoraConfig 里
inference_mode=False
,即使后面执行了model.eval()
新加的那些 lora 参数依然会算梯度。正常情况下推理模式的模型所有的参数应该都不需要算梯度。解决办法就是设置inference_mode=True
或者手动设置一遍
for n, p in model.named_parameters():
p.requires_grad = False
- lora方法在训练阶段会给需要调节的权重矩阵添加额外的两个权重矩阵,具体原理参考原论文或这篇笔记。因此原本一次矩阵乘法会变成三次矩阵乘法和一次矩阵加法。
而在推理阶段应该应该把B@A加到权重里,这样可以实现与原模型相同的推理速度,这一操作在perf的lora实现中与方法
merge_adapter
有关,但是这个方法也需要自己手动调用,不会在设置为推理模式时自动调用。 - 调用
merge_adapter
后模型依然是lora模型,forword部分的代码需要先判断是否进行过merge,然后才执行,这点判断可能对推理速度有点小影响,你可以尝试使用merge_and_unload
方法获得一个加完权重的ChatGLM模型,这样就一定能保证推理速度不下降,也方便使用其他适用于ChatGLM的工具。
#Lora模型里线性层的forword方法
def forward(self, x: torch.Tensor):
previous_dtype = x.dtype
if self.active_adapter not in self.lora_A.keys():
return F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
if self.disable_adapters:
if self.r[self.active_adapter] > 0 and self.merged:
self.unmerge()
result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
elif self.r[self.active_adapter] > 0 and not self.merged:
result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
x = x.to(self.lora_A[self.active_adapter].weight.dtype)
result += (
self.lora_B[self.active_adapter](
self.lora_A[self.active_adapter](self.lora_dropout[self.active_adapter](x))
)
* self.scaling[self.active_adapter]
)
else:
result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
result = result.to(previous_dtype)
return result
这三种方法具体处理的代码和推理速度测试如下:
注意,我用的是perf的0.3.0测试版 pip install git+https://github.com/huggingface/peft.git
与0.2.0版在实现上有较大差别
from transformers import AutoTokenizer,AutoModel
# from thuglm.modeling_chatglm import ChatGLMForConditionalGeneration
import torch
from peft import get_peft_model, LoraConfig, TaskType
import time
model = AutoModel.from_pretrained(
"yuanzhoulvpi/chatglm6b-dddd", trust_remote_code=True).half().cuda()
peft_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 = get_peft_model(model, peft_config)
# 在这里加载lora模型,注意修改chekpoint
# peft_path = "test004/checkpoint-100/chatglm-lora.pt"
# model.load_state_dict(torch.load(peft_path), strict=False)
model.to('cuda')
model.eval()
tokenizer = AutoTokenizer.from_pretrained("yuanzhoulvpi/chatglm6b-dddd", trust_remote_code=True)
def test_time():
s=time.time()
text ="为什么冰红茶和柠檬茶的味道一样?"
with torch.autocast("cuda"):
res, history = model.chat(tokenizer=tokenizer, query=text,max_length=300)
e=time.time()
print("Used time: {}".format(e-s))
print("-------原始版本-------")
for i in range(5):
test_time()
#把 A @ B 加到权重里
model.base_model.merge_adapter()
#全部参数都不需要求梯度
print("model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad ",model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad)
for n, p in model.named_parameters():
p.requires_grad = False
print("model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad ",model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad)
print("-------LoRA推理版本-------")
for i in range(5):
test_time()
print("-------merge_and_unload返回的整合后的ChatGLM版本-------")
model=model.merge_and_unload()
print(model._get_name())
for i in range(5):
test_time()
执行结果如下
如果你用的是zero_nlp/simple_thu_chatglm6b/infer.ipynb,原因大概有3点
- LoraConfig 里
inference_mode=False
,即使后面执行了model.eval()
新加的那些 lora 参数依然会算梯度。正常情况下推理模式的模型所有的参数应该都不需要算梯度。解决办法就是设置inference_mode=True
或者手动设置一遍for n, p in model.named_parameters(): p.requires_grad = False
- lora方法在训练阶段会给需要调节的权重矩阵添加额外的两个权重矩阵,具体原理参考原论文或这篇笔记。因此原本一次矩阵乘法会变成三次矩阵乘法和一次矩阵加法。 而在推理阶段应该应该把B@A加到权重里,这样可以实现与原模型相同的推理速度,这一操作在perf的lora实现中与方法
merge_adapter
有关,但是这个方法也需要自己手动调用,不会在设置为推理模式时自动调用。- 调用
merge_adapter
后模型依然是lora模型,forword部分的代码需要先判断是否进行过merge,然后才执行,这点判断可能对推理速度有点小影响,你可以尝试使用merge_and_unload
方法获得一个加完权重的ChatGLM模型,这样就一定能保证推理速度不下降,也方便使用其他适用于ChatGLM的工具。#Lora模型里线性层的forword方法 def forward(self, x: torch.Tensor): previous_dtype = x.dtype if self.active_adapter not in self.lora_A.keys(): return F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias) if self.disable_adapters: if self.r[self.active_adapter] > 0 and self.merged: self.unmerge() result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias) elif self.r[self.active_adapter] > 0 and not self.merged: result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias) x = x.to(self.lora_A[self.active_adapter].weight.dtype) result += ( self.lora_B[self.active_adapter]( self.lora_A[self.active_adapter](self.lora_dropout[self.active_adapter](x)) ) * self.scaling[self.active_adapter] ) else: result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias) result = result.to(previous_dtype) return result
这三种方法具体处理的代码和推理速度测试如下: 注意,我用的是perf的0.3.0测试版
pip install git+https://github.com/huggingface/peft.git
与0.2.0版在实现上有较大差别from transformers import AutoTokenizer,AutoModel # from thuglm.modeling_chatglm import ChatGLMForConditionalGeneration import torch from peft import get_peft_model, LoraConfig, TaskType import time model = AutoModel.from_pretrained( "yuanzhoulvpi/chatglm6b-dddd", trust_remote_code=True).half().cuda() peft_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 = get_peft_model(model, peft_config) # 在这里加载lora模型,注意修改chekpoint # peft_path = "test004/checkpoint-100/chatglm-lora.pt" # model.load_state_dict(torch.load(peft_path), strict=False) model.to('cuda') model.eval() tokenizer = AutoTokenizer.from_pretrained("yuanzhoulvpi/chatglm6b-dddd", trust_remote_code=True) def test_time(): s=time.time() text ="为什么冰红茶和柠檬茶的味道一样?" with torch.autocast("cuda"): res, history = model.chat(tokenizer=tokenizer, query=text,max_length=300) e=time.time() print("Used time: {}".format(e-s)) print("-------原始版本-------") for i in range(5): test_time() #把 A @ B 加到权重里 model.base_model.merge_adapter() #全部参数都不需要求梯度 print("model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad ",model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad) for n, p in model.named_parameters(): p.requires_grad = False print("model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad ",model.base_model.model.transformer.layers[0].attention.query_key_value.lora_A.default.weight.requires_grad) print("-------LoRA推理版本-------") for i in range(5): test_time() print("-------merge_and_unload返回的整合后的ChatGLM版本-------") model=model.merge_and_unload() print(model._get_name()) for i in range(5): test_time()
执行结果如下
感谢感谢,非常有帮助的解答,我尝试一下~
@benzenesulfonic-acid 请教一下,用了lora之后,ChatGLM还能用int8量化吗
@benzenesulfonic-acid 请教一下,用了lora之后,ChatGLM还能用int8量化吗
可以,merge_and_unload
返回值的类型就是ChatGLMForConditionalGeneration
,接下来执行model.quantize(8)
即可