多模态模型(VLM)部署方法抛砖引玉

极市平台

共 38283字,需浏览 77分钟

 ·

2024-07-10 22:00

↑ 点击蓝字 关注极市平台
作者丨oldpan
来源丨oldpan博客
编辑丨极市平台

极市导读

 

文章详细讨论了几种多模态模型,如LLaVA、InternLM-XComposer2、QWen-VL等,并解释了它们的架构和训练流程。以及介绍了几种多模态模型的部署框架,包括TensorRT-LLM、lmdeploy和vLLM,并讨论了它们对VLM模型的支持程度。 >>加入极市CV技术交流群,走在计算机视觉的最前沿

去年年初LLM刚起步的时候,大模型的部署方案还不是很成熟,如今仅仅过了一年多,LLM部署方案已经遍地都是了。

而多模态模型相比大语言模型来说,发展的还没有很“特别”成熟,不过由于两者结构很相似,LLMs的经验还是可以很好地利用到VLMs中。

本篇文章中提到的多模态指的是视觉多模态,即VLM(Vision Language Models)。

以下用一张图展示下简单多模态模型的运行流程:

  • Text Embeddings即文本输入,就是常见LLM中的输入;
  • 而Multomode projector则是多模态模型额外一个模态的输入,这里指的是视觉输入信息,当然是转换维度之后的;

将这个转换维度之后的视觉特征和Text Embeddings执行concat操作合并起来,输入decoder中(例如llama)就完成推理流程了;

Multomode projector负责将原始的图像特征转换下维度,输出转换后的图像特征;所以有个中文叫投射层,这个名字有点抽象,理解就行,其实就是个mlp层,转换下视觉特征的维度

多模态运作流程 from https://huggingface.co/blog/zh/vlms

引入VLM

VLM就是视觉encoder加上语言decoder,这么说可能有点抽象,我们先简单回顾下transformer的基本结构:

from huggingface

由编码器和解码器组成,最开始的transformer主要是处理机器翻译任务,结构是encoder+decoder。除了这个结构,还有一些其他结构适用于各种任务,比如

  • Encoder-only models:适用于需要理解输入的任务,例如句子分类和命名实体识别。
  • Decoder-only models:适用于生成性任务,如文本生成。
  • Encoder-decoder models or sequence-to-sequence models:适用于需要输入的生成性任务,例如翻译或摘要。

我们常见的llama属于Decoder-only models,只有decoder层;bert属于encoder-only;T5属于encoder-decoder。llama不多说了,BERT 是一种仅包含编码器的模型,它通过学习双向的上下文来更好地理解语言的深层含义。

BERT 通常用于理解任务,如情感分析、命名实体识别、问题回答等。T5 模型包括编码器和解码器,适用于同时需要理解和生成语言的任务。T5 被设计来处理各种“文本到文本”的任务,比如机器翻译、文摘生成、文本分类等。这种结构允许模型首先通过编码器理解输入文本,然后通过解码器生成相应的输出文本。基础知识就过到这里,我们先说enc-dec结构。

Encoder-Decoder (Enc-Dec)

注意,Enc-Dec模型需要区别于多模态模型,目前TensorRT-LLM官方支持的enc-dec模型如下:

  • T5[1]
  • T5v1.1[2] and Flan-T5[3]
  • mT5[4]
  • BART[5]
  • mBART[6]
  • FairSeq NMT[7]
  • ByT5[8]

第一个就是T5也就是上述提到的编码器+解码器结构。需要注意这些模型都是基于Transformer架构的自然语言处理(NLP)模型,区别于多模态模型,这些模型主要是处理文本,算是单模态。

VLM or multimodal

而多模态除了本文,就带上了图像:

VILA架构和训练流程

VILA[9]是nvidia推出的一个视觉语言模型,上图很清楚地介绍了其推理和训练流程。我知道的一些多模态模型有(这些模型来自lmdeploy官方github介绍):

  • LLaVA(1.5,1.6) (7B-34B)
  • InternLM-XComposer2 (7B, 4khd-7B)
  • QWen-VL (7B)
  • DeepSeek-VL (7B)
  • InternVL-Chat (v1.1-v1.5)
  • MiniGeminiLlama (7B)
  • CogVLM-Chat (17B)
  • CogVLM2-Chat (19B)
  • MiniCPM-Llama3-V-2_5

这里我只简单介绍下llava,毕竟llava是较早的做多模态的模型,之后很多多模态的架构和llava基本都差不多。剩下的多模态模型大家可以自行查阅,结构基本都类似。

LLAVA

LLaVA\(2304\)[10]作为VLMs的代表性工作,号称只需要几个小时的训练,即可让一个LLM转变为VLM:

image

训练方式比较简单,主要操作是将视觉图像视作一种“外语”(相比于之前纯nlp,图像可以当做额外输入的外语),利用vision-encoder和projection[11]将“图像翻译成文本信号”,并微调LLM从而可以适应图像任务,我们简单看下其推理代码:

llava的整体运行过程

其中processor就是图像预处理,处理后得到的input为:inputs.pixel_values,其shape是torch.Size([1, 3, 336, 336])。然后进入generate阶段:

image

第一步(stage-1)是执行encoder部分:

  • input_ids(torch.Size([1, 17])) -> input_embeds(torch.Size([1, 17, 4096]))
  • pixel_values(torch.Size([1, 3, 336, 336]))经过视觉模型 输出视觉特征(torch.Size([1, 576, 1024]))
  • 视觉特征经过投影层(mlp)输出最终的图像特征(torch.Size([1, 576, 4096]))

第二步(stage-2)是执行文字embed和图像embed合并过程,也就是合并inputs_embeds + image_features,最终得到的inputs_embeds维度为torch.Size([1, 592, 4096]),具体在llava中是pre_prompt + image + post_prompt一起输入进来,其中image的特征替换prompt中的标记,对应的token id为32000。

而这个_merge_input_ids_with_image_features函数的作用主要是将image_featuresinputs_embeds合并起来:在合并完两者之后,其余部分就和普通的decoder基本一致了。整体运行流程如下:

其他多模态模型拼输入的方式和llava类似,比如InternVLChat[12],输入的prompt是这样的:

image

转换为token_ids如下,其图像特征占位,img_context_token_id是92546:

image

上述prompt中的<IMG_CONTEXT>之后会被实际的图像特征填上,目前只是占个位子。运行过程中的维度变化:

  • pixel_values.shape   torch.Size([1, 3, 224, 224])  ->  vision model -> torch.Size([1, 64, 2048])
  • input_ids   torch.Size([1, 293])  -> language model embed -> torch.Size([1, 293, 2048])

核心代码如下,需要注意这里是先占位再填(input_embeds[selected]=...)的形式,不是llava中concat的形式,最终实现效果是一样的。

最终也是将合并后的embeds送入language-decoder中。

包含cross-attention的多模态

上述介绍的多模态模型都是文本输入和图像输入转换为embeds合并后,输入language decoder中,这个decoder可以是常见的decoder-only models,比如llama。

还有些特殊的多模态模型的decoder部分会有cross-attention结构,比如meta推出的nougat[13]。其视觉特征不会和input_ids合并,而是单独输入decoder,在decoder中和文本特征进行cross attention:

这种结构相对稍微复杂一些。

部署方案

部署方案的话,我们参考主流开源(TensorRT-LLM不完全开源)框架来窥探下。虽然一些大厂有自己的LLM部署方案,不过技术其实也多是“借鉴”,大同小异。这几个LLM框架应该是用的比较多的:

  • TensorRT-LLM
  • vLLM
  • lmdeploy

目前这三个框架都支持VLM模型,只不过支持程度不同,我们先看trt-llm。

TensorRT-LLM

trt-llm中多模态部分在Multimodal示例中,如果是跑demo或者实际中使用trt-llm的python session跑的话,直接看官方的example即可:

如果我们更进一步,想要部署在生产环境,也是可以搞的。如果我们想要使用triton inference server部署的话,TensorRT-LLM对多模态模型的支持限于将cv-encoder和decoder分离开,搞成两个模型服务。

即通过ensemble或者tensorrt_llm_bls(python backend)的方式串起来,整体运行流程和在普通模型中是一致的(先输入image和text,然后两者经过tokenizer转换为token id,最终拼接和encoder输出特征图一并传入decoder中)。

视觉端

视觉端模型转换和普通CV模型一致,可以通过onnx的方式或者直接通过trt-python-api搭建。

以llava举例子,在TensorRT-LLM中,首先把视觉模型(encoder)使用Wrapper类套一层,其中:

  • self.vision_tower是视觉模型,
  • multi_modal_projector是投影层,将特征维度转换为和input_embeds相匹配的维度:

转换好之后,模型信息如下:

[I] ==== TensorRT Engine ====  
    Name: Unnamed Network 0 | Explicit Batch Engine  
      
    ---- 1 Engine Input(s) ----  
    {input [dtype=float16, shape=(-1, 3, 336, 336)]}  
      
    ---- 1 Engine Output(s) ----  
    {output [dtype=float16, shape=(-1, 576, 4096)]}  
      
    ---- Memory ----  
    Device Memory: 56644608 bytes  
      
    ---- 1 Profile(s) (2 Tensor(s) Each) ----  
    - Profile: 0  
        Tensor: input           (Input), Index: 0 | Shapes: min=(1, 3, 336, 336), opt=(2, 3, 336, 336), max=(4, 3, 336, 336)  
        Tensor: output         (Output), Index: 1 | Shape: (-1, 576, 4096)  
      
    ---- 398 Layer(s) ----  

语言端

decoder端需要额外加入一个合并input_ids 和prompt_table_data的embedding层(这里称为PromptTuningEmbedding),其余的和普通llama一致:

通过设置 use_prompt_tuning来确定该decoder是否需要额外的 prompt_tuning 输入:

class LLaMAModel(Module):  
    def __init__(self,  
                 num_layers,  
                 num_heads,  
                 ...# 省略参数  
                 use_prompt_tuning: bool = False,   # !!!  
                 enable_pos_shift=False,  
                 dense_context_fmha=False,  
                 max_lora_rank=None):  
        super().__init__()  
        self.mapping = mapping  
        self.use_prompt_tuning = use_prompt_tuning  
        EmbeddingCls = PromptTuningEmbedding if use_prompt_tuning else Embedding  

其中PromptTuningEmbedding的forward代码如下,这个使用trt-python-api搭出来的layer主要作用就是将input_ids和视觉特征prompt_embedding_table进行embed并且concat,和上述一开始提到的concat流程大差不差:

# PromptTuningEmbedding  
def forward(self, tokens, prompt_embedding_table, tasks, task_vocab_size):  
    # do not use ">=" because internally the layer works with floating points  
    prompt_tokens_mask = tokens > (self.vocab_size - 1)  
  
    # clip tokens in the [0, vocab_size) range  
    normal_tokens = where(prompt_tokens_mask, self.vocab_size - 1, tokens)  
    normal_embeddings = embedding(normal_tokens, self.weight.value,  
                                  self.tp_size, self.tp_group,  
                                  self.sharding_dim, self.tp_rank)  
  
    # put virtual tokens in the [0, max_prompt_vocab_size) range  
    prompt_tokens = where(prompt_tokens_mask, tokens - self.vocab_size, 0)  
  
    # add offsets to match the concatenated embedding tables  
    tasks = tasks * task_vocab_size  
  
    # tasks: [batch_size, seq_len]  
    # prompt_tokens: [batch_size, seq_len]  
    prompt_tokens = prompt_tokens + tasks  
    prompt_embeddings = embedding(prompt_tokens, prompt_embedding_table)  
  
    # prompt_tokens_mask: [batch_size, seq_len] -> [batch_size, seq_len, 1]  
    # combine the correct sources of embedding: normal/prompt  
    return where(unsqueeze(prompt_tokens_mask, -1), prompt_embeddings,  
                 normal_embeddings)  

服务整合

服务整合其实很容易,只不过官方一开始并没有给出示例,只有在最近才在tutorial中给出实际例子:

  • https://github.com/triton-inference-server/tutorials/blob/main/Popular\_Models\_Guide/Llava1.5/llava\_trtllm\_guide.md[14]

其实我们可以很早发现tensorrt_llm/config.pbtxt中已经包含了这两个输入:

  • prompt_embedding_table
  • prompt_vocab_size

意味着在trt-llm的executor中包装着decoder-engine,可以接受这两个输入。而trt-llm提供的executor,在triton-trt-llm-backend去通过调用这个API实现inflight batching:

# inflight_batcher_llm/tensorrt_llm/config.pbtxt  
  {  
    name: "prompt_embedding_table"  
    data_type: TYPE_FP16  
    dims: [ -1, -1 ]  
    optional: true  
    allow_ragged_batch: true  
  },  
  {  
    name: "prompt_vocab_size"  
    data_type: TYPE_INT32  
    dims: [ 1 ]  
    reshape: { shape: [ ] }  
    optional: true  
  },  

而之前介绍的llava的decoder结构,则可以看到有prompt_embedding_table和prompt_vocab_size这两个输入:

[03/04/2024-03:23:39] [TRT-LLM] [I] Engine:name=Unnamed Network 0, refittable=False, num_layers=495, device_memory_size=1291851264, nb_profiles=1  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=input_ids, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=position_ids, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=prompt_embedding_table, mode=TensorIOMode.INPUT, shape=(-1, 4096), dtype=DataType.HALF, tformat=Row major linear FP16 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=tasks, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=prompt_vocab_size, mode=TensorIOMode.INPUT, shape=(1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=last_token_ids, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=kv_cache_block_offsets, mode=TensorIOMode.INPUT, shape=(-1, 2, -1), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=host_kv_cache_block_offsets, mode=TensorIOMode.INPUT, shape=(-1, 2, -1), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=host_kv_cache_pool_pointers, mode=TensorIOMode.INPUT, shape=(2,), dtype=DataType.INT64, tformat=Row major linear INT8 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=sequence_length, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=host_request_types, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=host_past_key_value_lengths, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=context_lengths, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=host_context_lengths, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=host_max_attention_window_sizes, mode=TensorIOMode.INPUT, shape=(32,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=host_sink_token_length, mode=TensorIOMode.INPUT, shape=(1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=cache_indirection, mode=TensorIOMode.INPUT, shape=(-1, 1, -1), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[03/04/2024-03:23:39] [TRT-LLM] [I] Tensor:name=logits, mode=TensorIOMode.OUTPUT, shape=(-1, 32064), dtype=DataType.FLOAT, tformat=Row major linear FP32 format (kLINEAR)  

这是普通decoder-only server中的服务文件组织:

而最终整体的目录文件,我们再多一个encoder文件夹就行,具体我们可以在preprocessing中调用encoder,然后去处理prompt合并,最终通过prompt_embedding_table和prompt_vocab_size送入tensorrt_llm中即可。不过需要注意,trt-llm不支持cross attention这种多模态的inflight batching,因为在config.pbtxt没有暴露出相关的接口,cross attention这种多模态不需要prompt_embedding_table和prompt_vocab_size,而是需要类似于encoder_output这种原始的图像特征输入,以下是官方nougat模型decoder部分转为trt的结构:

[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=input_ids, mode=TensorIOMode.INPUT, shape=(-1, 1), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=position_ids, mode=TensorIOMode.INPUT, shape=(-1, 1), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=encoder_input_lengths, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=encoder_max_input_length, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=encoder_output, mode=TensorIOMode.INPUT, shape=(-1, -1, 1024), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=host_past_key_value_lengths, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=sequence_length, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=context_lengths, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=host_request_types, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=last_token_ids, mode=TensorIOMode.INPUT, shape=(-1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cache_indirection, mode=TensorIOMode.INPUT, shape=(-1, 1, -1), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=host_max_attention_window_sizes, mode=TensorIOMode.INPUT, shape=(10,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=host_sink_token_length, mode=TensorIOMode.INPUT, shape=(1,), dtype=DataType.INT32, tformat=Row major linear INT32 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_0, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_0, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_1, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_1, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_2, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_2, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_3, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_3, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_4, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_4, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_5, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_5, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_6, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_6, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_7, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_7, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_8, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_8, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=past_key_value_9, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_past_key_value_9, mode=TensorIOMode.INPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_kv_cache_gen, mode=TensorIOMode.INPUT, shape=(1,), dtype=DataType.BOOL, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_0, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_0, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_1, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_1, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_2, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_2, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_3, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_3, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_4, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_4, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_5, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_5, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_6, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_6, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_7, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_7, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_8, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_8, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=present_key_value_9, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=cross_present_key_value_9, mode=TensorIOMode.OUTPUT, shape=(-1, 2, 16, -1, 64), dtype=DataType.BF16, tformat=Row major linear INT8 format (kLINEAR)  
[04/23/2024-03:56:36] [TRT-LLM] [I] Tensor:name=logits, mode=TensorIOMode.OUTPUT, shape=(-1, 50000), dtype=DataType.FLOAT, tformat=Row major linear FP32 format (kLINEAR)  

lmdeploy

lmdeploy对多模态的支持方式也在预料之中,decoder使用自家的turbomind或者pytorch engine去跑,然后cv-encoder端复用原始transformers库中的代码去跑,整理流程和trt-llm中的相似。

vLLM

vllm对多模态模型的支持尚可,官方展示的不是很多,但实际上支持了不少(https://github.com/vllm-project/vllm/issues/4194[15]);而且鉴于vllm的易接入性,自己增加模型还是比较简单的:

另外,vLLM近期也在修改相关VLM的架构,正在进行重构[16],以及vllm对vlm的一些后续优化:

  • Make VLMs work with chunked prefill
  • Unify tokenizer & multi-modal processor (so that we can leverage AutoProcessor from transformers)
  • Prefix caching for images
  • Streaming inputs of multi-modal data

简单测试了llava,测试性能在同样fp16的情况下性能不如trt-llm,原因表现可以参考在llama上的对比。

优化点TODO

优化点其实有很多,不过占大头的就是量化[17]

量化

因为多模态的decoder部分就是普通的decode模型,我们可以复用现有的成熟的量化技术量化decoder部分就可以拿到很大的收益。整个pipeline当中视觉encoder部分的耗时占比一般都很小5%左右,encoder部分可以按照我们平常的小模型的方式去优化即可,量化也可以上。需要注意的就是量化方法,大模型量化方法很多,需要选对。

The AWQ quantization algorithm is suitable for multi-modal applications since AWQ does not require backpropagation or reconstruction, while GPTQ does. Thus, it has better generalization ability to new modalities and does not overfit to a specific calibration set. We only quantized the language part of the model as it dominates the model size and inference latency. The visual part takes less than 4% of the latency. AWQ outperforms existing methods (RTN, GPTQ) under zero-shot and various few-shot settings, demonstrating the generality of different modalities and in-context learning workloads.

Runtime

还有cv-encoder需要和decoder最好放到一个runtime当中,要不然会有一些冗余的显存拷贝,不过这部分对吞吐影响不大,主要是latency。trt-llm中对enc-dec结构已经做了这样的优化,在trt-llm-0607版本中将这俩放到了同一个runtime中,这里指的是Executor,可以看到多出了一个encoder的model path:

可以看到triton-llm-backend中多了一个encoder_model_path的输入,这里将encoder和decoder放到同一个runtime中了:

还有一些其他的优化空间,这里暂时不谈了,后续有新的结论了再补充。

这里的讨论还不是很全很细,算是抛砖引玉,之后有更新也会再发篇文章单独介绍。各位读者如果有比较好的方法或者建议也欢迎留言,我们一起讨论。

参考

  • https://github.com/triton-inference-server/tutorials/pull/100[18]
  • https://huggingface.co/blog/vlms[19]
  • https://github.com/InternLM/lmdeploy/issues/1309[20]
  • https://developer.nvidia.com/blog/visual-language-intelligence-and-edge-ai-2-0/[21]
参考资料
[1]

T5: https://huggingface.co/docs/transformers/main/en/model_doc/t5

[2]

T5v1.1: https://huggingface.co/docs/transformers/model_doc/t5v1.1

[3]

Flan-T5: https://huggingface.co/docs/transformers/model_doc/flan-t5

[4]

mT5: https://huggingface.co/docs/transformers/model_doc/mt5

[5]

BART: https://huggingface.co/docs/transformers/model_doc/bart

[6]

mBART: https://huggingface.co/docs/transformers/model_doc/mbart

[7]

FairSeq NMT: https://pytorch.org/hub/pytorch_fairseq_translation/

[8]

ByT5: https://huggingface.co/docs/transformers/main/en/model_doc/byt5

[9]

VILA: https://developer.nvidia.com/blog/visual-language-models-on-nvidia-hardware-with-vila/

[10]

LLaVA(2304): https://link.zhihu.com/?target=https%3A//arxiv.org/abs/2304.08485

[11]

projection: https://www.zhihu.com/search?q=projection&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22article%22%2C%22sourceId%22%3A%22702811733%22%7D

[12]

InternVLChat: https://huggingface.co/OpenGVLab/InternVL-Chat-V1-5

[13]

nougat: https://github.com/facebookresearch/nougat

[14]

https://github.com/triton-inference-server/tutorials/blob/main/Popular_Models_Guide/Llava1.5/llava_trtllm_guide.md: https://github.com/triton-inference-server/tutorials/blob/main/Popular_Models_Guide/Llava1.5/llava_trtllm_guide.md

[15]

https://github.com/vllm-project/vllm/issues/4194: https://github.com/vllm-project/vllm/issues/4194

[16]

重构: https://github.com/vllm-project/vllm/issues/4194

[17]

undefined: undefined

[18]

https://github.com/triton-inference-server/tutorials/pull/100: https://github.com/triton-inference-server/tutorials/pull/100

[19]

https://huggingface.co/blog/vlms: https://huggingface.co/blog/vlms

[20]

https://github.com/InternLM/lmdeploy/issues/1309: https://github.com/InternLM/lmdeploy/issues/1309

[21]

https://developer.nvidia.com/blog/visual-language-intelligence-and-edge-ai-2-0/: https://developer.nvidia.com/blog/visual-language-intelligence-and-edge-ai-2-0/

公众号后台回复“数据集”获取100+深度学习各方向资源整理

极市干货

技术专栏:多模态大模型超详细解读专栏搞懂Tranformer系列ICCV2023论文解读极市直播
极视角动态欢迎高校师生申报极视角2023年教育部产学合作协同育人项目新视野+智慧脑,「无人机+AI」成为道路智能巡检好帮手!
技术综述:四万字详解Neural ODE:用神经网络去刻画非离散的状态变化transformer的细节到底是怎么样的?Transformer 连环18问!

点击阅读原文进入CV社区

收获更多技术干货

浏览 8
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报