OpenVINO™赋能BLIP实现视觉语言AI边缘部署

openlab_4276841a 更新于 7月前

作者:武卓博士  英特尔AI布道师

人类通过视觉和语言感知世界。人工智能的一个长期目标是构建智能体,通过视觉和语言输入来理解世界,并通过自然语言与人类交流。比如,在《几行代码加速 Stable Diffusion,使用 OpenVINO 轻松文生图》中,我们介绍了利用OpenVINO 运行Stable Diffusion模型,快速实现文生图应用。让人人可以成为绘画**,利用AI随心作画。

随着计算机视觉和自然语言处理领域的快速发展,视觉与语言的融合越来越受到研究人员的重视。在这个背景下,BLIP(Bootstrapping Language-Image Pre-training)作为一种创新的预训练模型,引起了广泛关注。该模型在大规模的图像文本数据集上预训练深度神经网络模型,以提高下游视觉语言任务的性能,如图像文本检索、图像字幕和视觉问答。通过联合训练图像和文本数据,为视觉与语言的融合提供了强大的基础。

图1. BLIP推理结果示例

BLIP 的预训练过程涉及两个关键组件:图像编码器和文本编码器。图像编码器负责将输入的图像转换为低维向量表示,而文本编码器则将输入的文本转换为另一个低维向量表示。为了实现统一的视觉-语言预训练,BLIP 采用了一种跨模态约束策略,即在预训练阶段,图像编码器和文本编码器被设计成相互约束的。这样的约束机制强制模型学习将视觉信息和语言信息进行对齐,从而使得模型在后续任务中能够更好地处理视觉与语言之间的联合信息。

除了视觉-语言理解任务,BLIP 还在视觉-语言生成任务中表现出色。在这个任务中,模型需要根据输入的图像和文本生成相关的描述或回答问题。BLIP 通过联合训练和引入了图像-文本生成任务,使得模型具备了更强大的图像描述和问题回答能力。这使得 BLIP 在图像描述生成和视觉问答等任务上取得了优异的成绩。

接下来,我们一起来看看如何在研扬科技(AAEON)的新品 UP Squared Pro 7000 Edge运行利用OpenVINO™来优化加速BLIP的推理有哪些重点步骤吧。

作为研扬UP Squared Pro系列的第三代产品,Upsquared Pro 7000 系列 (up-shop.org) 透过高性能计算能力、升级的电路板设计和扩展的显示接口,提供更大的开发潜力。作为该系列中首款采用Intel® Core™/Atom®/N系列处理器(原 Alder Lake-N)的产品,UP Squared Pro 7000是首款配备板载LPDDR5内存的产品,提高了I/O的运行速度。此外,UP Squared Pro 7000 在图像处理和显示功能方面都有显著提升,支持MIPI CSI照相机,并搭配Intel® UHD显卡,可同时进行三台4K显示器。

1.4倍以上CPU性能提升

UP Squared Pro 7000采用Intel® Core™/Atom®/N-系列处理器,CPU性能是上一代的1.4倍。 UP Squared Pro 7000拥有多达8个Gracemont内核,支持OpenVINO™ Toolkit,以及第12代Intel®处理器的UHD显卡,拥有强大的计算能力、优化的推理引擎和图像处理功能,提供绝佳的智能解决方案。

同步支持34K显示器

UP Squared Pro 7000配备HDMI 2.0b、DP 1.2埠和透过USB Type-C的DP 1.4a,拥有出色的显示接口。UP Squared Pro 7000整合了GPU和多重输出,可以同步支持三个4K显示器,非常适合用于数字广告牌等视觉导向型的相关应用。

双倍的高速系统内存

作为UP Squared Pro系列中第一块配备板载LPDDR5系统内存的板卡,UP Squared Pro 7000搭载了16GB的系统内存,是上一代的两倍。此外,快达4800MHz的内存速度让用户的带宽和数据传输速度加倍,同时也更加省电。

全面的I/O升级

除了维持UP Squared Pro系列4" x 4"的紧凑外形之外,UP Squared Pro 7000 在电路板设计上更为精实。UP Squared Pro 7000配备了两个2.5GbE、三个 USB 3.2和一个FPC端口,可外接更多像是MIPI CSI 相机的外围设备。将这些特色与板载 LPDDR5 及性能强大的CPU相结合,非常适合用于智慧工厂机器人方面的视觉解决方案。

注意:以下步骤中的所有代码来自OpenVINO Notebooks开源仓库中的233-blip-visual-language-processing notebook 代码示例,您可以点击以下链接直达源代码。https://github.com/openvinotoolkit/openvino_notebooks/tree/main/notebooks/233-blip-visual-language-processing  

第一步: 安装相应工具包、加载模型并转换为OpenVINO IR格式

本次代码示例需要首先安装BLIP相应工具包。

1. !pip install "transformers >= 4.26.0"

然后下载及加载相应的PyTorch模型。在本问中,您将使用可从Hugging Face下载的blip-vqa-base基本模型。同样的操作也适用于BLIP系列中的其它模型。尽管该模型类是为执行问答而设计的,但其组件也可以用于图像字幕。要开始使用该模型,需要使用from_pretrained方法实例化BlipForQuestionAnswering类。BlipProcessor是一个助手类,用于准备文本和视觉模态的输入数据以及生成结果的后处理。

1. import sys

2. import time

3. from PIL import Image

4. from transformers import BlipProcessor, BlipForQuestionAnswering

5. 
6. sys.path.append("../utils")

7. from notebook_utils import download_file

8. 
9. # Get model and processor

10. processor = BlipProcessor.from_pretrained("Salesforce/blip-vqa-base")

11. model = BlipForQuestionAnswering.from_pretrained("Salesforce/blip-vqa-base")

接下来,我们看看如何将原始模型转换为OpenVINO IR格式的模型,并利用OpenVINO进行相应的优化以及部署推理加速。

第二步: 将模型转换为OpenVINO IR格式

根据我们前面的介绍,BLIP模型包含视觉模型、文本编码和文本解码三个模型,因此我们需要分别将这三个模型转换为OpenVINO IR格式。视觉模型的转换操作比较常规,具体代码可以参考我们的notebook,这里重点介绍一下文本编码和文本解码模型的转换部分。

· 文本编码器转换:视觉问答任务使用文本编码器来构建问题的嵌入表示。它采用经过分词后的问题的input_ids,并输出从视觉模型获得的图像嵌入和它们的注意力掩码。根据问题文本的不同,标记化输入后的标记数量可能不同。因此,为使用标记的模型输入保留动态形状,dynamic_axes参数负责在torch.onx.export中保留输入的动态特定维度。代码如下:

TEXT_ENCODER_OV = Path("blip_text_encoder.xml")

TEXT_ENCODER_ONNX = TEXT_ENCODER_OV.with_suffix(".onnx")

text_encoder = model.text_encoder

text_encoder.eval()

# if openvino model does not exist, convert it to onnx and then to IR

if not TEXT_ENCODER_OV.exists():

    if not TEXT_ENCODER_ONNX.exists():

        # prepare example inputs for ONNX export

        image_embeds = vision_outputs[0]

        image_attention_mask = torch.ones(image_embeds.size()[:-1], dtype=torch.long)

        input_dict = {"input_ids": inputs["input_ids"], "attention_mask": inputs["attention_mask"], "encoder_hidden_states": image_embeds, "encoder_attention_mask": image_attention_mask}

        # specify variable length axes

        dynamic_axes = {"input_ids": {1: "seq_len"}, "attention_mask": {1: "seq_len"}}

        # export PyTorch model to ONNX

        with torch.no_grad():

            torch.onnx.export(text_encoder, input_dict, TEXT_ENCODER_ONNX, input_names=list(input_dict), dynamic_axes=dynamic_axes)

    # convert ONNX model to IR using model conversion Python API, u******press_to_fp16=True for compressing model weights to FP16 precision

    ov_text_encoder = mo.convert_model(TEXT_ENCODER_ONNX, compress_to_fp16=True)

    # save model on disk for next usages

    serialize(ov_text_encoder, str(TEXT_ENCODER_OV))

    print(f"Text encoder successfuly converted and saved to {TEXT_ENCODER_OV}")

else:

    print(f"Text encoder will be loaded from {TEXT_ENCODER_OV}")

· 文本解码器转换:文本解码器负责使用图像(以及问题,如果需要的话)的表示来生成模型输出(问题的答案或标题)的分词token序列。生成方法基于这样的假设,即单词序列的概率分布可以分解为下一个单词条件分布的乘积。换言之,模型预测由先前生成的token引导循环生成下一个token,直到达到停止生成的条件(生成达到最大长度序列或获得的字符串结束的token)。在预测概率之上选择下一个token的方式由所选择的解码方法来驱动。与文本编码器类似,文本解码器可以处理不同长度的输入序列,并且需要保留动态输入形状。这部分特殊的处理可由如下代码完成:

1. text_decoder = model.text_decoder

2. text_decoder.eval()

3. 
4. TEXT_DECODER_OV = Path("blip_text_decoder.xml")

5. TEXT_DECODER_ONNX = TEXT_DECODER_OV.with_suffix(".onnx")

6. 
7. # prepare example inputs for ONNX export

8. input_ids = torch.tensor([[30522]])  # begin of sequence token id

9. attention_mask = torch.tensor([[1]])  # attention mask for input_ids

10. encoder_hidden_states = torch.rand((1, 10, 768))  # encoder last hidden state from text_encoder

11. encoder_attention_mask = torch.ones((1, 10), dtype=torch.long)  # attention mask for encoder hidden states

12. 
13. input_dict = {"input_ids": input_ids, "attention_mask": attention_mask, "encoder_hidden_states": encoder_hidden_states, "encoder_attention_mask": encoder_attention_mask}

14. # specify variable length axes

15. dynamic_axes = {"input_ids": {1: "seq_len"}, "attention_mask": {1: "seq_len"}, "encoder_hidden_states": {1: "enc_seq_len"}, "encoder_attention_mask": {1: "enc_seq_len"}}

16. 
17. # specify output names, logits i***ain output of model

18. output_names = ["logits"]

19. 
20. # past key values outputs are output for caching model hidden state

21. past_key_values_outs = []

22. text_decoder_outs = text_decoder(**input_dict)

23. for idx, _ in enumerate(text_decoder_outs["past_key_values"]):

24.     past_key_values_outs.extend([f"out_past_key_value.{idx}.key", f"out_past_key_value.{idx}.value"])

接下来,对于文本解码器的转换,还有来自前一步骤的隐藏状态的额外输入。与输出类似,在模型导出为ONNX格式后,它们将被展平。需要使用新的输入层更新dynamic_axies和input_names。因此,其后面的转换过程与前面的文本编码器的转换过程类似,在本文中不再赘述。

第三步:运行OpenVINO 推理

如前所述,在这里我们将主要展示BLIP进行视觉问答以及图像字幕的流水线如何搭建、以及如何运行OpenVINO来进行推理的情况。

· 图像字幕

视觉模型接受BlipProcessor预处理的图像作为输入,并生成图像嵌入,这些图像嵌入直接传递给文本解码器以生成字幕标记。生成完成后,分词tokenizer的输出序列被提供给BlipProcessor,用于使用tokenizer解码为文本。

定义OVBLIPModel类:

class OVBlipModel:

    """ 

    Model class for inference BLIP model with OpenVINO

    """

    def __init__(self, config, decoder_start_token_id:int, vision_model, text_encoder, text_decoder):

        """

        Initialization class parameters

        """

        self.vision_model = vision_model

        self.vision_model_out = vision_model.output(0)

        self.text_encoder = text_encoder

        self.text_encoder_out = text_encoder.output(0)

        self.text_decoder = text_decoder

        self.config = config

        self.decoder_start_token_id = decoder_start_token_id

        self.decoder_input_ids = config.text_config.bos_token_id

定义图像字幕函数如下,

    

def generate_caption(self, pixel_values:torch.Tensor, input_ids:torch.Tensor = None, attention_mask:torch.Tensor = None, **generate_kwargs):

        """

        Image Captioning prediction

        Parameters:

          pixel_values (torch.Tensor): preprocessed image pixel values

          input_ids (torch.Tensor, *optional*, None): pregenerated caption token ids after tokenization, if provided caption generation continue provided text

          attention_mask (torch.Tensor): attention mask for caption tokens, used only if input_ids provided

        Retruns:

          generation output (torch.Tensor): tensor which represents sequence of generated caption token ids

        """

        batch_size = pixel_values.shape[0]

        image_embeds = self.vision_model(pixel_values.detach().numpy())[self.vision_model_out]

        image_attention_mask = torch.ones(image_embeds.shape[:-1], dtype=torch.long)

        if isinstance(input_ids, list):

            input_ids = torch.LongTensor(input_ids)

        elif input_ids is None:

            input_ids = (

                torch.LongTensor([[self.config.text_config.bos_token_id, self.config.text_config.eos_token_id]])

                .repeat(batch_size, 1)

            )

        input_ids[:, 0] = self.config.text_config.bos_token_id

        attention_mask = attention_mask[:, :-1] if attention_mask is not None else None

        outputs = self.text_decoder.generate(

            input_ids=input_ids[:, :-1],

            eos_token_id=self.config.text_config.sep_token_id,

            pad_token_id=self.config.text_config.pad_token_id,

            attention_mask=attention_mask,

            encoder_hidden_states=torch.from_numpy(image_embeds),

            encoder_attention_mask=image_attention_mask,

            **generate_kwargs,

        )

        return outputs

· 视觉问答

视觉回答的流水线看起来很相似,但有额外的问题处理。在这种情况下,由BlipProcessor标记的图像嵌入和问题被提供给文本编码器,然后多模态问题嵌入被传递给文本解码器以执行答案的生成。

在OVBLIPModel类内部同理可定义视觉问答函数如下:

    

 def generate_answer(self, pixel_values:torch.Tensor, input_ids:torch.Tensor, attention_mask:torch.Tensor, **generate_kwargs):

        """

        Visual Question Answering prediction

        Parameters:

          pixel_values (torch.Tensor): preprocessed image pixel values

          input_ids (torch.Tensor): question token ids after tokenization

          attention_mask (torch.Tensor): attention mask for question tokens

        Retruns:

          generation output (torch.Tensor): tensor which represents sequence of generated answer token ids

        """

        image_embed = self.vision_model(pixel_values.detach().numpy())[self.vision_model_out]

        image_attention_mask = np.ones(image_embed.shape[:-1], dtype=int)

        if isinstance(input_ids, list):

            input_ids = torch.LongTensor(input_ids)

        question_embeds = self.text_encoder([input_ids.detach().numpy(), attention_mask.detach().numpy(), image_embed, image_attention_mask])[self.text_encoder_out]

        question_attention_mask = np.ones(question_embeds.shape[:-1], dtype=int)

        bos_ids = np.full((question_embeds.shape[0], 1), fill_value=self.decoder_start_token_id)

        outputs = self.text_decoder.generate(

            input_ids=torch.from_numpy(bos_ids),

            eos_token_id=self.config.text_config.sep_token_id,

            pad_token_id=self.config.text_config.pad_token_id,

            encoder_hidden_states=torch.from_numpy(question_embeds),

            encoder_attention_mask=torch.from_numpy(question_attention_mask),

            **generate_kwargs,

        )

        return outputs

· 初始化OpenVINO运行时并运行推理

初始化OpenVINO Core对象,选择推理设备,并加载、编译模型

# create OpenVINO Core object instance

core = Core()

import ipywidgets as widgets

device = widgets.Dropdown(

    options=core.available_devices + ["AUTO"],

    value='AUTO',

    description='Device:',

    disabled=False,

)

device

# load models on device

ov_vision_model = core.compile_model(VISION_MODEL_OV, device.value)

ov_text_encoder = core.compile_model(TEXT_ENCODER_OV, device.value)

ov_text_decoder = core.compile_model(TEXT_DECODER_OV, device.value)

ov_text_decoder_with_past = core.compile_model(TEXT_DECODER_WITH_PAST_OV, device.value)

· 运行图像字幕推理

out = ov_model.generate_caption(inputs["pixel_value******ax_length=20)

caption = processor.decode(out[0], skip_special_tokens=True)

fig = visualize_results(raw_image, caption)

运行效果如下图所示:

· 运行视觉问答推理

start = time.perf_counter()

out = ov_model.generate_answer(**input****ax_length=20)

end = time.perf_counter() - start

answer = processor.decode(out[0], skip_special_tokens=True)

fig = visualize_results(raw_image, answer, question)

运行效果如下图所示:

小结:

整个的步骤就是这样!现在就开始跟着我们提供的代码和步骤,动手试试用Open VINO和BLIP吧。

关于英特尔OpenVINOTM开源工具套件的详细资料,包括其中我们提供的三百多个经验证并优化的预训练模型的详细资料,请您点击https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html

除此之外,为了方便大家了解并快速掌握OpenVINOTM的使用,我们还提供了一系列开源的Jupyter notebook demo。运行这些notebook,就能快速了解在不同场景下如何利用OpenVINOTM实现一系列、包括计算机视觉、语音及自然语言处理任务。OpenVINOTM notebooks的资源可以在GitHub这里下载安装:https://github.com/openvinotoolkit/openvino_notebooks 。

研扬科技简介

研扬科技成立于1992年,是工业物联网和人工智能边缘解决方案的领先设计商和制造商之一。以不断创新为核心价值观,研扬科技为市场带来可靠、高质量的计算平台,包括工业主板和系统、强固式平板电脑、嵌入式人工智能系统、uCPE网络设备以及LoRaWAN/WWAN解决方案。研扬科技还带来行业领先的经验和知识,以在全球范围内提供OEM/ODM服务。 此外,研扬科技与诸多城市和**紧密合作,开发和部署智能城市生态系统,提供个性化平台和端到端解决方案。研扬科技与顶级芯片设计商紧密合作,提供稳定、可靠的平台,并被认可为Intel®物联网解决方案联盟的钛金级成员。欲了解更多研扬科技的产品线和服务,请访问www.aaeon.com。

通知和免责声明

Intel技术可能需要启用硬件、软件或服务激活。

任何产品或组件都不能绝对安全。

您的成本和结果可能有所不同。

©英特尔公司。Intel、Intel徽标和其他Intel标志是Intel Corporation或其子公司的商标。其他名称和品牌可能被称为他人的财产。

0个评论