zero_nlp icon indicating copy to clipboard operation
zero_nlp copied to clipboard

Lora导致推理时长增加70%

Open airsyuan opened this issue 1 year ago • 4 comments

如题,虽然知道Lora不可避免的会增加推理时长,但这个幅度也有点太大了,请问有什么可以提速的方法吗。

airsyuan avatar Apr 12 '23 07:04 airsyuan

如果你用的是zero_nlp/simple_thu_chatglm6b/infer.ipynb,原因大概有3点

  1. LoraConfig 里inference_mode=False ,即使后面执行了 model.eval() 新加的那些 lora 参数依然会算梯度。正常情况下推理模式的模型所有的参数应该都不需要算梯度。解决办法就是设置inference_mode=True或者手动设置一遍
for n, p in model.named_parameters():
    p.requires_grad = False
  1. lora方法在训练阶段会给需要调节的权重矩阵添加额外的两个权重矩阵,具体原理参考原论文这篇笔记。因此原本一次矩阵乘法会变成三次矩阵乘法和一次矩阵加法。 而在推理阶段应该应该把B@A加到权重里,这样可以实现与原模型相同的推理速度,这一操作在perf的lora实现中与方法merge_adapter有关,但是这个方法也需要自己手动调用,不会在设置为推理模式时自动调用。
  2. 调用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()

执行结果如下

image

benzenesulfonic-acid avatar Apr 18 '23 06:04 benzenesulfonic-acid

如果你用的是zero_nlp/simple_thu_chatglm6b/infer.ipynb,原因大概有3点

  1. LoraConfig 里inference_mode=False ,即使后面执行了 model.eval() 新加的那些 lora 参数依然会算梯度。正常情况下推理模式的模型所有的参数应该都不需要算梯度。解决办法就是设置inference_mode=True或者手动设置一遍
for n, p in model.named_parameters():
    p.requires_grad = False
  1. lora方法在训练阶段会给需要调节的权重矩阵添加额外的两个权重矩阵,具体原理参考原论文这篇笔记。因此原本一次矩阵乘法会变成三次矩阵乘法和一次矩阵加法。 而在推理阶段应该应该把B@A加到权重里,这样可以实现与原模型相同的推理速度,这一操作在perf的lora实现中与方法merge_adapter有关,但是这个方法也需要自己手动调用,不会在设置为推理模式时自动调用。
  2. 调用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()

执行结果如下

image

感谢感谢,非常有帮助的解答,我尝试一下~

airsyuan avatar Apr 18 '23 07:04 airsyuan

@benzenesulfonic-acid 请教一下,用了lora之后,ChatGLM还能用int8量化吗

nghuyong avatar Apr 19 '23 06:04 nghuyong

@benzenesulfonic-acid 请教一下,用了lora之后,ChatGLM还能用int8量化吗

可以,merge_and_unload返回值的类型就是ChatGLMForConditionalGeneration,接下来执行model.quantize(8)即可

benzenesulfonic-acid avatar Apr 20 '23 06:04 benzenesulfonic-acid