找人做一个小网站需要多少钱,宝塔设置加速wordpress站点,工商营业执照注册查询官网,查询友情链接心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会#xff0c;与大家一起成长。具体介绍#xff1a;仓颉专项#xff1a;飞机大炮我都会#xff0c;利器心法我还有。 2023年新一版的文章合集已经发布#xff0c;获取方式看这里#xff1a;又添十万字-CS的陋室2… 心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会与大家一起成长。具体介绍仓颉专项飞机大炮我都会利器心法我还有。 2023年新一版的文章合集已经发布获取方式看这里又添十万字-CS的陋室2023年文章合集来袭更有历史文章合集欢迎下载。 往期回顾 心法利器[102] | 大模型落地应用架构的一种模式心法利器[103] | 大模型bad case修复方案思考心法利器[104] | 基础RAG-向量检索模块含代码心法利器[105] 基础RAG-大模型和中控模块代码含代码心法利器[106] 基础RAG-调优方案 假期想着补点遗漏的知识所以选择了模型加速这块的工作这块我的深度肯定是不够的不过尝试动手做做实践收获还是不小而且形成一些自己需要的组件还是挺有用的所以记录一下。 目录 加速整体思路。环境和加速前准备。onnx加速。tensorRT加速。速度测试。 这里的很多代码都有参考这篇文章我特别摆在这里https://blog.csdn.net/m0_37576959/article/details/127123186感谢社区大佬的贡献。 加速整体思路 现在其实已经有大量的工具可以用来进行加速早在bert是主流的时代就已经有研究很多有关的技术了。今天所介绍的onnx和tensorRT也都是这个时代的产物后续成为这个时代的主流和代表性方案而且随着逐步迭代他们的封装也逐步变得简单让我们使用的难度也变低了不少。 目前这两者的加速思路其实也比较类似即主要分为两块 模型的重新编译使之转化为更适用于推理的格式并将其进行保存。加载保存的模型并用其进行推理。 因此我们核心需要做的就是上面两步的开发。 环境和加速前准备 无论是onnx和tensorrt因为加速依赖底层硬件和操作系统所以环境配置成了繁杂但不可绕开的工作此处我先把我目前的环境列举出来给大家提供参考 windows1116G内存i9-13900HXNVIDIA GeForce RTX 4070 Laptop GPU显存8G专用8G共享。CUDA Version: 12.3CUDNNcudnn-windows-x86_64-8.9.6.50_cuda12python3.9.13。torch2.1.2cu121transformers4.33.2onnx1.15.0onnxruntime1.16.3onnxruntime-gpu1.17.0基本直接pip install即可。tensorRTtensorrt-8.6.1.6.windows10.x86_64.cuda-12.0tensorrt8.6.1。 tensorrt环境配置可以参考这篇文章https://blog.csdn.net/KRISNAT/article/details/130789078核心流程如下 从NVIDIA官网上找到自己合适的版本解压。配置好环境变量同时有些文件要复制到cuda内。找到合适的python whl包进行安装。 环境配置好了肯定就还需要有原料了即需要加速的模型和原始推理方案这里我选择的是心法利器[104] | 基础RAG-向量检索模块含代码中提到的simcse模型。原始的加载和推理是这样的 import torch
import torch.nn as nn
import torch.nn.functional as F
from loguru import logger
from tqdm import tqdm
from transformers import BertConfig, BertModel, BertTokenizerclass SimcseModel(nn.Module):# https://blog.csdn.net/qq_44193969/article/details/126981581def __init__(self, pretrained_bert_path, poolingcls) - None:super(SimcseModel, self).__init__()self.pretrained_bert_path pretrained_bert_pathself.config BertConfig.from_pretrained(self.pretrained_bert_path)self.model BertModel.from_pretrained(self.pretrained_bert_path, configself.config)self.model.eval()# self.model Noneself.pooling poolingdef forward(self, input_ids, attention_mask, token_type_ids):out self.model(input_ids, attention_maskattention_mask, token_type_idstoken_type_ids)return out.last_hidden_state[:, 0]class VectorizeModel:def __init__(self, ptm_model_path, device cpu) - None:self.tokenizer BertTokenizer.from_pretrained(ptm_model_path)self.model SimcseModel(pretrained_bert_pathptm_model_path, poolingcls)self.model.eval()# self.DEVICE torch.device(cuda if torch.cuda.is_available() else cpu)self.DEVICE devicelogger.info(device)self.model.to(self.DEVICE)self.pdist nn.PairwiseDistance(2)def predict_vec(self,query):q_id self.tokenizer(query, max_length 200, truncationTrue, paddingmax_length, return_tensorspt)with torch.no_grad():q_id_input_ids q_id[input_ids].squeeze(1).to(self.DEVICE)q_id_attention_mask q_id[attention_mask].squeeze(1).to(self.DEVICE)q_id_token_type_ids q_id[token_type_ids].squeeze(1).to(self.DEVICE)q_id_pred self.model(q_id_input_ids, q_id_attention_mask, q_id_token_type_ids)return q_id_preddef predict_vec_request(self, query):q_id_pred self.predict_vec(query)return q_id_pred.cpu().numpy().tolist()def predict_sim(self, q1, q2):q1_v self.predict_vec(q1)q2_v self.predict_vec(q2)sim F.cosine_similarity(q1_v[0], q2_v[0], dim-1)return sim.cpu().numpy().tolist() 这里是有两个类分别是SimcseModel和VectorizeModel前者是模型类后者是应用类为什么这么分在这篇文章里有提及这里不赘述心法利器[104] | 基础RAG-向量检索模块含代码当然后面用加速模型推理的时候大家也会发现这个代码设计的优点。 另外值得强调的是早期版本的onnx对if的算子支持的不是很好所以大家尽量不要在模型类内增加这个if这个还是比较常见的所以特别说明。 onnx加速 加速的第一步是生成新编译好的模型文件这部分还是偏简单的直接照着写基本就可以了。 import torch
from transformers import BertTokenizer
from src.models.vec_model.simcse_model import SimcseModel# Reference: https://blog.csdn.net/m0_37576959/article/details/127123186
# ------------模型编译----------
# 1. 必要配置
MODEL_PATH C:/work/tool/huggingface/models/simcse-chinese-roberta-wwm-ext
MODEL_ONNX_PATH ./data/model_simcse_roberta_output_20240211.onnx
DEVICE torch.device(cuda if torch.cuda.is_available() else cpu)# 2. 模型加载
tokenizer BertTokenizer.from_pretrained(MODEL_PATH)
model SimcseModel(pretrained_bert_pathMODEL_PATH, poolingcls)
# OPERATOR_EXPORT_TYPE torch._C._onnx.OperatorExportTypes.ONNX
model.eval()
model.to(DEVICE)# 3. 格式定义
query 你好
encodings tokenizer(query, max_length 200, truncationTrue, paddingmax_length, return_tensorspt)
input_info (encodings[input_ids].to(DEVICE),encodings[attention_mask].to(DEVICE),encodings[token_type_ids].to(DEVICE))
# model(input_info)# 4. 模型导出
output torch.onnx.export(model,input_info,MODEL_ONNX_PATH,verboseFalse,export_paramsTrue,# operator_export_typeOPERATOR_EXPORT_TYPE,opset_version12,input_names[input_ids, attention_mask, token_type_ids], # 需要注意顺序不可随意改变, 否则结果与预期不符output_names[output], # 需要注意顺序, 否则在推理阶段可能用错output_namesdo_constant_foldingTrue,dynamic_axes{input_ids: {0: batch_size, 1: length},token_type_ids: {0: batch_size, 1: length},attention_mask: {0: batch_size, 1: length},output: {0: batch_size}})
print(Export of {} complete!.format(MODEL_ONNX_PATH))# ------------模型校验----------
import onnxruntime as ort
import onnxonnx_model onnx.load(MODEL_ONNX_PATH)
onnx.checker.check_model(onnx_model)# ------------模型校验----------
sess ort.InferenceSession(MODEL_ONNX_PATH, providers[CUDAExecutionProvider])
query 你好
encodings tokenizer(query, max_length 512, truncationTrue, paddingmax_length, return_tensorspt)def to_numpy(tensor):return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()input {sess.get_inputs()[0].name: to_numpy(encodings[input_ids]),sess.get_inputs()[1].name: to_numpy(encodings[attention_mask]),sess.get_inputs()[2].name: to_numpy(encodings[token_type_ids]),
}
print(sess.run(None, input_feedinput))
print(model(encodings[input_ids].to(DEVICE),encodings[attention_mask].to(DEVICE),encodings[token_type_ids].to(DEVICE))) 这里的必要流程我都有些写注释应该基本足够了解了不过还是把重点讲讲吧。 首先是转化这块其所有流程的中心就落在torch.onnx.export这一个函数身上这点已经非常方便了前面所有的工作都是为了这一个函数所需要的原料在准备比较核心的参数只要是这几个 model核心需要转化的模型torch.nn.Module肯定是可以支持的也是比较常见的。args这里输入的内容是预期模型的参数格式描述格式本身还比较困难格式比较多样然而这里支持的是可以往里面塞例子这里的第三步也就是格式定义就是准备了一个例子。f这里是指输出的路径最终加速后的模型的路径当然也可以用文件对象就是open读取的那个对象。input_names定义好具体模型的输入类bert模型本身是有3个输入名称要定义清楚和args中一致且注意要按照顺序。output_names输出的名字这个自己定义好就行。dynamic_axes这里是指支持动态的变量这里是可以指明的动态会为速度带来一定的影响但是也会带来较高的灵活性一般动态的比较多的就是batch_size和句子长度了注意这里输入和输出都得写在里面。 然后就是推理这里的难度其实不大基本上按照脚本走就没有什么大问题值得注意的细节就一个onnx模型的推理需要的输入是numpy格式此时转化后记得要转化回来。 另外这里有一个函数onnx.checker.check_model这个函数是负责检验模型生成是否规范onnx底层是用protobuf定义的内部的各个节点的映射关系之类的都写在这里面为了保证整体符合规范所以出了这个函数具体细节可以参考这篇文章https://blog.csdn.net/qq_43456016/article/details/130256097。虽然我们这种转化比较简单但个人还是建议在脚本里都加上。 在完成新的模型文件生成后就可以开始推理了直接看新的推理程序吧。 class VectorizeModel_onnx(VectorizeModel):def __init__(self, ptm_model_path, onnx_path) - None:self.tokenizer BertTokenizer.from_pretrained(ptm_model_path)self.model ort.InferenceSession(onnx_path, providers[CUDAExecutionProvider])self.pdist nn.PairwiseDistance(2)def _to_numpy(self, tensor):return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()def predict_vec(self,query):q_id self.tokenizer(query, max_length 200, truncationTrue, paddingmax_length, return_tensorspt)input_feed {self.model.get_inputs()[0].name: self._to_numpy(q_id[input_ids]),self.model.get_inputs()[1].name: self._to_numpy(q_id[attention_mask]),self.model.get_inputs()[2].name: self._to_numpy(q_id[token_type_ids]),}return torch.tensor(self.model.run(None, input_feedinput_feed)[0])def predict_sim(self, q1, q2):q1_v self.predict_vec(q1)q2_v self.predict_vec(q2)sim F.cosine_similarity(q1_v[0], q2_v[0], dim-1)return sim.numpy().tolist() 这里的程序为了简单我是直接继承了上面提及的VectorizeModel有些必要的额函数就不用重新写了。这里的加载和推理其实都参考了前面的“模型校验”中的内容了加载用的是ort.InferenceSession至于推理与之不同的是推理需要对tokenizer后的变量转为numpy的格式另外输出这里为了和前面的函数对齐做好无缝切换所以把输出的结果转化为torch.tensor了。 tensorRT加速 tensorRT的加速需要基于onnx是需要对onnx进行进一步编译完成流程上核心坑主要有两个 环境配置必须满足python版本、cuda版本等信息而且nvidia下载速度较慢。数据类型等细节的对齐否则很容易失败。 详细的操作和尝试大家可以参考这篇文章可以说非常详细对于详细版大家可以看这个https://blog.csdn.net/m0_37576959/article/details/127123186 而我想在这里聊的是onnx本身所具有的编译tensorRT的功能就在这行代码里 model ort.InferenceSession(onnx_path, providers[CUDAExecutionProvider]) 这里的providers中提供了3种[TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider]顾名思义分别对应tensorRT、GPU、CPU三种模式而这里的TensorrtExecutionProvider就是tensorRT编译的结果了。https://zhuanlan.zhihu.com/p/457484536 因此上面对VectorizeModel_onnx就能优化为这个形式了改个名字V2吧 class VectorizeModel_v2(VectorizeModel):def __init__(self, ptm_model_path, model_path, providers[CUDAExecutionProvider]) - None:# [TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider]self.tokenizer BertTokenizer.from_pretrained(ptm_model_path)self.model ort.InferenceSession(model_path, providersproviders)self.pdist nn.PairwiseDistance(2)def _to_numpy(self, tensor):return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()def predict_vec(self,query):q_id self.tokenizer(query, max_length 200, truncationTrue, paddingmax_length, return_tensorspt)input_feed {self.model.get_inputs()[0].name: self._to_numpy(q_id[input_ids]),self.model.get_inputs()[1].name: self._to_numpy(q_id[attention_mask]),self.model.get_inputs()[2].name: self._to_numpy(q_id[token_type_ids]),}return torch.tensor(self.model.run(None, input_feedinput_feed)[0])def predict_sim(self, q1, q2):q1_v self.predict_vec(q1)q2_v self.predict_vec(q2)sim F.cosine_similarity(q1_v[0], q2_v[0], dim-1)return sim.numpy().tolist() 速度测试 有了进行加速这事那就来对比一下加速的优化效率吧。 先来看看我的脚本 import time,random
from tqdm import tqdm
# 加载
device torch.device(cuda if torch.cuda.is_available() else cpu)
vec_model VectorizeModel(C:/work/tool/huggingface/models/simcse-chinese-roberta-wwm-ext, devicedevice)
vec_model VectorizeModel_v2(C:/work/tool/huggingface/models/simcse-chinese-roberta-wwm-ext,./data/model_simcse_roberta_output_20240211.onnx,providers[CUDAExecutionProvider])
vec_model VectorizeModel_v2(C:/work/tool/huggingface/models/simcse-chinese-roberta-wwm-ext,./data/model_simcse_roberta_output_20240211.onnx,providers[TensorrtExecutionProvider])
batch_sizes [1,2,4,8,16,32]
# 单测
# q [你好啊]
# print(vec_model.predict_vec(q))
# print(vec_model.predict_sim(你好呀,你好啊))# 开始批跑
batch_sizes [1,2,4,8,16]
tmp_queries [你好啊, 今天天气怎么样, 我要暴富]
for b in batch_sizes:for i in tqdm(range(100),descwarmup):tmp_q []for i in range(b):tmp_q.append(random.choice(tmp_queries))vec_model.predict_vec(tmp_q)for i in tqdm(range(1000),descbatch_size{}.format(b)):tmp_q []for i in range(b):tmp_q.append(random.choice(tmp_queries))vec_model.predict_vec(tmp_q) 这里是4个部分分别是加载、单测测单独一个case、预热和开始批跑时间的测试用的tqdm最终的平均时间即可。事不宜迟直接给出结果吧单位item/sitem是指每次推理而非每条数据 batch_sizepytorchonnxtensorRT1107.57167.21204.40263.99103.82126.92440.5457.8170.51821.6929.4336.051610.5114.4617.24 这里可以看到onnx和tensorRT相比原始的pytorch模型的提升还是非常大的。 补充一个实验后的发现tensorRT的推理中当输入进去的数据的batch_size变化后都会有个不短的预热时间而在batch_size固定的那段时间速度还是比稳定的不知道是不是有什么bug还是这个编译情况就是如此有了解的大佬可以在评论区里说下看有没有什么解决方案。