CANN GE后端CN_CLIP优化示例

CANN GE后端CN_CLIP优化示例 CN_CLIP模型优化示例【免费下载链接】triton-inference-server-ge-backendge-backend基于triton inference server框架实现对接NPU生态快速实现传统CV\NLP等模型的服务化。项目地址: https://gitcode.com/cann/triton-inference-server-ge-backendCLIP全名Contrastive Language-Image Pretraining在2021年由OpenAI提出其核心理念为图文对比学习预训练是一种多模态学习模型旨在将图像和文本进行关联它可以快速实现图文特征相似度计算、跨模态检索、零样本图片分类等任务。Chinese-CLIPCN_CLIP是一次极其朴素的开源没错就是CLIP的汉化旨在推动中文社区多模态发展原始的CLIP模型基于英文图文语料不能用于中文的图文表征提取场景。Chinese-CLIP以英文CLIP视觉侧参数和中文Roberta参数作为模型初始化值。CN_CLIP模型的优化包括小batch合并、动态图转静态图、多流并行锁核、ENSEMBLE未涉及自动融合和融合PASS。因部署采用容器化请求bs固定为256且每个容器对cpu有限制所以未考虑小batch自动融合的动态图优化方案模型导出修改模型文件CLIP模型目录如下clip ├── bert_tokenizer.py ├── configuration_bert.py ├── __init__.py ├── model_configs │ ├── RBT3-chinese.json │ ├── RN50.json │ ├── RoBERTa-wwm-ext-base-chinese.json │ ├── RoBERTa-wwm-ext-large-chinese.json │ ├── ViT-B-16.json │ ├── ViT-B-32.json │ ├── ViT-H-14.json │ ├── ViT-L-14-336.json │ └── ViT-L-14.json ├── modeling_bert.py ├── model.py ├── utils.py └── vocab.txt由于最终目的是部署推理需要model.py中的forward返回值进行修改具体如下def forward(self, image, text, mask_ratio0): assert image is not None or text is not None, text and image cannot both be None! if image is None: return self.encode_text(text) elif text is None: return self.encode_image(image) image_features self.encode_image(image, mask_ratio) text_features self.encode_text(text) image_features image_features / image_features.norm(dim-1, keepdimTrue) text_features text_features / text_features.norm(dim-1, keepdimTrue) # return image_features, text_features, self.logit_scale.exp() return image_features, text_features注由于self.logit_scale.exp()参数没有shape而在triton模型仓的config.pbtxt中需要设置输出shape会导致模型无法运行。导出模型首先创建模型需要选择model_arch如ViT-B-16和模型权重checkpoint# prepare the PyTorch implemented model and restore weights model create_model(_MODEL_INFO[args.model_arch][struct], checkpoint).float().eval()设置输入占位数据# prepare empty image and text as input placeholders for ONNX resolution _MODEL_INFO[args.model_arch][input_resolution] preprocess image_transform(resolution) image preprocess(Image.new(RGB, (resolution, resolution))).unsqueeze(0) text clip.tokenize([], context_lengthargs.context_length)注resolution需根据图像分辨率进行设置然后调用clip模型自带方法转换成相应shape的输入此输入的内容不重要起到占位作用即可。使用torch.onnx.export导出模型torch.onnx.export(model, (image, text), fusion_fp32_onnx_path, input_names[image,text], output_names[unnorm_image_features,unnorm_text_features], dynamic_axes{ image:{0:batch_size,2:height,3:width}, text:{0:batch_size,1:seq_len}, unnorm_image_features:{0:batch_size,1:unnorm_feats}, unnorm_text_features:{0:batch_size,1:unnorm_feats} }, export_paramsTrue, do_constant_foldingFalse, opset_version14, verboseTrue )其中(image.text)构成模型输入可以根据需求设置为(image,None)即只导出image分支input_names 和 output_names 用于设置模型输入输出名称导出静态图时应不设置dynamic_axes参数。动态图转静态图首先根据模型导出章节的步骤导出ONNX模型然后据此模型生成GE图并采集一次Profiling数据采集方法见Profiling文档查看整个推理过程是否全部变为静态模型中是否有dynamic情况如果有就需要具体分析是否可以消除。动态图执行Profiling如下可以看到在动态图下有好多空泡在1bs情况下明显的hostbound问题。动态图执行Profiling如下可以看到一次推理中前面一部分变成了图下沉模式而后面出现了部分图又没有下沉成功这种情况我们需要打开op_summary_xxx.csv 文件查看是啥原因导致了图下沉截断。可以看到在执行Mod操作时图从前面的static模式变成了dynamic模式而且算子本身是一个AI_CPU类算子初步定位为Mod导致问题我们可以打开onnx 文件查看Mod算子作用用Netron打开相应的onnx文件。找到这个Mod。可以看到Mod之前是一个Add计算A是一个固定值3 3-12 2Mod3 2 每一层均是一个固定值那这个Mod过程其实可以把Mod这个节点给删掉让Add的结果2直接接入到Mod的下面两个节点。删除节点的代码可以通过Deepseek等AI生成通过修改后再次跑图用profiling采集所有node均变为static。两者吞吐对比(1 instance)动态图静态图可以看出在GE静态图场景下单Instance 都有2.6倍吞吐差异。模型优化可参考 性能调优方法论 章节多流并行锁核由于代码框架已集成该功能直接添加启动命令参数启动即可开启每个Stream限制12个Cube10个Vector--backend-confignpu_ge,ge.aicoreNum12|10具体含义请参考性能调优方法论 性能优化结果如下多流并行优化后静态多流并行优化后静态图锁核可以看出在限制每条流的CV核数后8流并行情况下整体吞吐又有35%的提升。ENSEMBLE为方便分析删去CLIP模型的text分支固定batch 1则其image输入shape为 [1,3,224,224]。构建预处理 preprocess Python Model添加模型文件和相应的config.pbtxt:preprocess |-- 1 | -- model.py |-- config.pbtxt -- preprocess-py310.tar.gzconfig.pbtxt文件内容如下name: preprocess backend: python input [ { name: image_binary data_type: TYPE_STRING dims: [1] } ] output [ { name: preprocessed_image data_type: TYPE_FP32 dims: [1, 3, 224, 224] } ] instance_group [ { count: 8 } ] parameters: { key: EXECUTION_ENV_PATH, value: {string_value: $$TRITON_MODEL_DIRECTORY/preprocess-py310.tar.gz} }其中instance_group 参数用于配置preprocess模型的实例数实例数越多可以应对更高的并发参数 EXECUTION_ENV_PATH 用于指定Python Model执行依赖Python虚拟环境的路径宏“$$TRITON_MODEL_DIRECTORY”表示模型仓路径对应于模型仓中预先放置的“preprocess-py310.tar.gz”包。注Python Model虚拟环境打包方法见附录conda-pack。模型代码为import numpy as np from PIL import Image import io import triton_python_backend_utils as pb_utils class TritonPythonModel: def initialize(self, args): 初始化模型加载预处理参数 # CLIP 预训练模型的默认预处理参数ViT-B-16 为例 self.input_size (224, 224) # 图像尺寸 self.mean np.array([0.48145466, 0.4578275, 0.40821073], dtypenp.float32) # RGB 均值 self.std np.array([0.26862954, 0.26130258, 0.27577711], dtypenp.float32) # RGB 标准差 def execute(self, requests): 处理推理请求PNG 解码 → 预处理 → 输出 CLIP 输入 responses [] for request in requests: # 获取客户端输入的 PNG 字节流 png_input pb_utils.get_input_tensor_by_name(request, image_binary) png_bytes_list png_input.as_numpy() # 形状: [batch_size]元素为 bytes # 批量处理每张图像 clip_inputs [] for png_bytes in png_bytes_list: # 解码 PNG处理可能的 Alpha 通道 image Image.open(io.BytesIO(png_bytes)).convert(RGB) # 转为 RGB丢弃 Alpha # 调整尺寸到 CLIP 要求的大小 image image.resize(self.input_size, Image.BILINEAR) # 转为 numpy 数组HWC 格式 image_np np.array(image, dtypenp.float32) # 转为 CHW 格式CLIP 输入为 [C, H, W] image_np image_np.transpose(2, 0, 1) # (H, W, 3) → (3, H, W) # 归一化减去均值除以标准差 image_np (image_np / 255.0 - self.mean[:, None, None]) / self.std[:, None, None] clip_inputs.append(image_np) # 堆叠为批处理张量 clip_inputs_np np.stack(clip_inputs, axis0) # 形状: [batch_size, 3, 224, 224] # 构造输出张量 output_tensor pb_utils.Tensor(preprocessed_image, clip_inputs_np) inference_response pb_utils.InferenceResponse(output_tensors[output_tensor]) responses.append(inference_response) return responses def finalize(self): 清理资源可选 pass配置ENSEMBLE为使能ENSEMBLE能力需要添加一个ENSEMBLE模型仓其结构如下clip_ensemble/ |-- 1 -- config.pbtxtconfig文件内容为name: clip_ensemble platform: ensemble max_batch_size: 0 input [ { name: ensemble_image data_type: TYPE_STRING dims: [1] } ] output [ { name: ensemble_feats data_type: TYPE_FP32 dims: [1, 512] } ] ensemble_scheduling { step [ # 步骤 1预处理 { model_name: preprocess model_version: 1 input_map { key: image_binary; value: ensemble_image } output_map { key: preprocessed_image; value: interm_image } }, # 步骤 2CN-CLIP 推理 { model_name: cn_clip model_version: 1 input_map { key: image; value: interm_image } output_map { key: unnorm_image_features; value: ensemble_feats } } }与预处理类似我还可以在模型输出接后处理模型将模型输出转为识别类别字符串降低服务端传回数据量。CN_CLIP模型输入原始图片数据为RGB彩色图片字节数为147KB转为Numpy数组字节数增加到588KB是原来的四倍。分别测试CN_CLIP和使用的ENSEMBLE后的吞吐率 | Model | thoughput (infer/sec) | |:------:|:-----:| | CN_CLIP | 836.831 | | ENSEMBLE | 847.627 |可以看到大约有10infer/sec的性能提升。 这是由于数据量变化不是特别大。在帮客户优化Yolo11模型时发现在本地测试吞吐还不错大概能到110qps然而上到环境之后发现无法达到理论值仅有30qps左右经过问题定位发现是前处理过程中把图片通原始的jpg图片变成了[1,3,3600,3600]的Tensor导致传输过程成为bound原始图片一般只有500kB而前置经过放大拉伸转为tensor后变成74MB左右。如果能把前置过程搬到服务器通过Triton自带的Ensemble能力通过流水线方式覆盖前处理过程就可以在服务器上消除因前处理导致的网络传输bound。经过改造我们将yolo11的前、后处理均通过ensemble串接为一个新的服务这个服务只需要传原始图片即可直接得到最终Text改造后上线的吞吐由30qps提升至90qps提升了200%。附录MindStudio Insight 下载下载链接https://www.hiascend.com/developer/download/community/result?modulesto%2Bcann选择合适版本下载即可。详细使用方法见https://www.hiascend.com/document/detail/zh/mindstudio/830/GUI_baseddevelopmenttool/msascendinsightug/Insight_userguide_0002.htmlNetron 下载下载链接https://github.com/lutzroeder/netron初始界面如下选择相应模型文件打开即可查看。onnxsim 安装安装命令pip install onnxsimonnxsim优化命令onnxsim {旧模型} {新模型}conda-pack 安装conda-pack 要打包的python版本需要与ge backend 自带的版本保持一致当前ge backend中使用的python版本为3.10版本建议用户制作运行环境时使用此版本python若必须更换请参考triton inference server官方文档制作相应的stub文件。下载conda-packconda install conda-pack打包过程我们可以从最基本的conda 虚拟环境开始仅打包模型运行依赖的最小集可以最大程度减少包的体积。conda create --name clip_env python3.10下载所有依赖包之前先export PYTHONNOUSERSITETrue安装完所有包后打包环境conda pack -n clip_env -o package_name.tar.gz更详细的说明见conda-pack文档.【免费下载链接】triton-inference-server-ge-backendge-backend基于triton inference server框架实现对接NPU生态快速实现传统CV\NLP等模型的服务化。项目地址: https://gitcode.com/cann/triton-inference-server-ge-backend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考