【多模态&LLM】英伟达NVLM多模态大模型细节和数据集

大模型向量数据库数据库

前期笔者介绍了OCR-free的多模态大模型,可以参考: 【多模态&文档智能】OCR-free感知多模态大模型技术链路及训练数据细节 ,其更偏向于训练模型对于密集文本的感知能力。本文看一看英伟达出品的多模态大模型NVLM-1.0系列,虽然暂未开源,但该文章给出了NVLM的详细细节,值得一读。

NVLM-1.0方法

picture.image

NVLM-1.0包括 三种 不同的架构:

  1. NVLM-D,一种解码器架构;
  2. NVLM-X,一种基于交叉注意力(X-attention)的架构;
  3. NVLM-H,一种混合架构。

共享视觉路径

所有NVLM模型共享一个视觉路径。使用InternViT-6B-448px-V1-5作为默认的视觉编码器,并在整个训练阶段保持其冻结状态。该视觉编码器以固定的448x448像素分辨率处理图像,生成1024个输出标记。采用动态高分辨率(DHR)方法来处理不同分辨率的图像输入。具体的如下图,图像被分割成最多6个瓦片(tile),每个瓦片对应448x448像素。然后,每个瓦片被送入InternViT-6B进行处理,生成1024个标记。这些标记通过下采样操作减少到256个标记,这么做可以降低处理开销。

picture.image

picture.image

上述两张图都是动态DHR的处理过程,围绕图像的预处理,包括归一化、缩放、裁剪、根据宽高比动态处理等操作,构建了一套完整的流程,代码逻辑如下:


        
        
            

          
 import
 
           torch
            

          
 from
 
           PIL 
          
 import
 
           Image
            

          
 import
 
           torchvision.transforms 
          
 as
 
           T
            

          
 from
 
           torchvision.transforms.functional 
          
 import
 
           InterpolationMode
            

            

          IMAGENET\_MEAN = (
          
 0.485
 
          , 
          
 0.456
 
          , 
          
 0.406
 
          )
            

          IMAGENET\_STD = (
          
 0.229
 
          , 
          
 0.224
 
          , 
          
 0.225
 
          )
            

            

            

          
 
 def
 
  
 
 build\_transform
 
 
 (input\_size)
 
 :
 
            

              MEAN, STD = IMAGENET\_MEAN, IMAGENET\_STD
            

              transform = T.Compose([
            

                  T.Lambda(
          
 lambda
 
           img: img.convert(
          
 'RGB'
 
          ) 
          
 if
 
           img.mode != 
          
 'RGB'
 
          
 else
 
           img),
            

                  T.Resize((input\_size, input\_size), interpolation=InterpolationMode.BICUBIC),
            

                  T.ToTensor(),
            

                  T.Normalize(mean=MEAN, std=STD)
            

              ])
            

              
          
 return
 
           transform
            

            

            

          
 
 def
 
  
 
 find\_closest\_aspect\_ratio
 
 
 (aspect\_ratio, target\_ratios, width, height, image\_size)
 
 :
 
            

              best\_ratio\_diff = float(
          
 'inf'
 
          )
            

              best\_ratio = (
          
 1
 
          , 
          
 1
 
          )
            

              area = width * height
            

              
          
 for
 
           ratio 
          
 in
 
           target\_ratios:
            

                  target\_aspect\_ratio = ratio[
          
 0
 
          ] / ratio[
          
 1
 
          ]
            

                  ratio\_diff = abs(aspect\_ratio - target\_aspect\_ratio)
            

                  
          
 if
 
           ratio\_diff < best\_ratio\_diff:
            

                      best\_ratio\_diff = ratio\_diff
            

                      best\_ratio = ratio
            

                  
          
 elif
 
           ratio\_diff == best\_ratio\_diff:
            

                      
          
 if
 
           area > 
          
 0.5
 
           * image\_size * image\_size * ratio[
          
 0
 
          ] * ratio[
          
 1
 
          ]:
            

                          best\_ratio = ratio
            

              
          
 return
 
           best\_ratio
            

            

            

          
 
 def
 
  
 
 dynamic\_preprocess
 
 
 (image, min\_num=
 
 1
 
 , max\_num=
 
 6
 
 , image\_size=
 
 448
 
 , use\_thumbnail=True)
 
 :
 
            

              orig\_width, orig\_height = image.size
            

              aspect\_ratio = orig\_width / orig\_height
            

            

              target\_ratios = set(
            

                  (i, j) 
          
 for
 
           n 
          
 in
 
           range(min\_num, max\_num + 
          
 1
 
          ) 
          
 for
 
           i 
          
 in
 
           range(
          
 1
 
          , n + 
          
 1
 
          ) 
          
 for
 
           j 
          
 in
 
           range(
          
 1
 
          , n + 
          
 1
 
          ) 
          
 if
 
            

                  i * j <= max\_num 
          
 and
 
           i * j >= min\_num)
            

              target\_ratios = sorted(target\_ratios, key=
          
 lambda
 
           x: x[
          
 0
 
          ] * x[
          
 1
 
          ])
            

            

              target\_aspect\_ratio = find\_closest\_aspect\_ratio(
            

                  aspect\_ratio, target\_ratios, orig\_width, orig\_height, image\_size)
            

            

              target\_width = image\_size * target\_aspect\_ratio[
          
 0
 
          ]
            

              target\_height = image\_size * target\_aspect\_ratio[
          
 1
 
          ]
            

              blocks = target\_aspect\_ratio[
          
 0
 
          ] * target\_aspect\_ratio[
          
 1
 
          ]
            

            

              resized\_img = image.resize((target\_width, target\_height))
            

              processed\_images = []
            

              
          
 for
 
           i 
          
 in
 
           range(blocks):
            

                  box = (
            

                      (i % (target\_width // image\_size)) * image\_size,
            

                      (i // (target\_width // image\_size)) * image\_size,
            

                      ((i % (target\_width // image\_size)) + 
          
 1
 
          ) * image\_size,
            

                      ((i // (target\_width // image\_size)) + 
          
 1
 
          ) * image\_size
            

                  )
            

                  split\_img = resized\_img.crop(box)
            

                  processed\_images.append(split\_img)
            

              
          
 assert
 
           len(processed\_images) == blocks
            

              
          
 if
 
           use\_thumbnail 
          
 and
 
           len(processed\_images) != 
          
 1
 
          :
            

                  thumbnail\_img = image.resize((image\_size, image\_size))
            

                  processed\_images.append(thumbnail\_img)
            

              
          
 return
 
           processed\_images
            

            

            

          
 
 def
 
  
 
 load\_image
 
 
 (image\_file, input\_size=
 
 448
 
 , max\_num=
 
 6
 
 )
 
 :
 
            

              image = Image.open(image\_file).convert(
          
 'RGB'
 
          )
            

              transform = build\_transform(input\_size=input\_size)
            

              images = dynamic\_preprocess(image, image\_size=input\_size, use\_thumbnail=
          
 True
 
          , max\_num=max\_num)
            

              pixel\_values = [transform(image) 
          
 for
 
           image 
          
 in
 
           images]
            

              pixel\_values = torch.stack(pixel\_values)
            

              
          
 return
 
           pixel\_values
            

            

        
      

文中引入了三种tile标签:

  • 无标签:简单连接,没有tile标签,这是InternVL-1.5的设计。
  • 一维扁平化tile tag:<tile_1>、<tile_2>、...、<tile_6>、<tile_global>。
  • 二维网格tag:<tile_x0_y0>、<tile_x1_y0>、...、<tile_xW_yH>、<tile_global>,其中<tile_xi_yj>的{i:j}可以是{1:1, 1:2, 1:3, 1:4, 1:5, 1:6, 2:1, 2:2, 2:3, 3:1, 3:2, 4:1, 5:1, 6:1}中的任何一个。
  • 二维边界框标签: (x0, y0), (x1, y1) 、...、 (xW, yH), (xW+1, yH+1) ,其中(xi, yj)和(xi+1, yj+1)分别是整个高分辨率图像中该特定tile的(左、上)和(右、下)坐标。

实验可以看到,其中DHR + 1-D tag取得了最佳的性能。

picture.image

picture.image

NVLM-D: 解码器架构

NVLM-D模型类似于之前的解码器架构多模态LLMs(如:)。 通过一个两层MLP将预训练的视觉编码器连接到LLM 。训练NVLM-D涉及两个阶段: 预训练和SFT 。在预训练阶段,MLP需要先进行训练,同时保持视觉编码器和LLM主干冻结。在SFT阶段,MLP和LLM都被训练以学习新的视觉-语言任务,而视觉编码器保持冻结状态。为了防止LLM在多模态SFT训练期间退化文本性能,引入了一个高质量的文本SFT数据集。

NVLM-X: 基于X-attention的模型

NVLM-X使用门控交叉注意力来处理图像token。与Flamingo模型不同,NVLM-X不使用感知重采样器,而是直接通过交叉注意力层处理图像标记。在SFT阶段,解冻LLM主干,并混合高质量文本SFT数据集以保持强大的文本性能。

NVLM-H: 混合模型

NVLM-H结合了解码器架构和基于X-attention的架构的优点。将图像token分为两部分:缩略图token和常规瓦片token。缩略图标记通过自注意力层处理,而常规瓦片标记通过交叉注意力层处理。这种设计提高了高分辨率图像的处理能力,同时显著提高了计算效率。

模型配置和训练方法

所有NVLM模型的训练过程包括两个阶段:预训练和监督微调(SFT)。在预训练阶段,冻结LLM主干和视觉编码器,只训练模态对齐模块。在SFT阶段,保持视觉编码器冻结,同时训练LLM和模态对齐模块。

LLM和视觉模型选择

  • LLM :对于NVLM-D、NVLM-X和NVLM-H 72B模型,使用Qwen2-72B-Instruct作为LLM。为了计算效率,还使用了较小的Nous-Hermes-2-Yi-34B进行更快的消融研究和实验。
  • 视觉编码器 :所有NVLM模型都使用InternViT-6B-448px-V1-5作为视觉编码器。

模态对齐模块

  • NVLM-D : 使用两层MLP将视觉编码器和背景语言模型连接起来。隐藏维度为12800→20480→7168(34B模型)和12800→29568→8192(72B模型)。
  • NVLM-X : 图像特征首先通过一层MLP投影到背景语言模型的隐藏维度,然后插入门控X-attention层。具体配置为12800→7168(34B模型)和12800→8192(72B模型)。
  • NVLM-H : 使用两层MLP和X-attention层作为模态对齐模块。缩略图图像标记直接输入到背景语言模型解码器中,而常规图像块则通过X-attention层进行处理。

训练超参数

  • 预训练阶段

picture.image

  • SFT阶段

picture.image

训练数据

  • 预训练数据集

picture.image

  • SFT数据集

picture.image

  • 文本SFT数据集

包括ShareGPT、SlimOrca、EvolInstruct、GPTeacher、AlpacaGPT4、UltraInteract、OrcaMathWordProblems、MathInstruct、MetaMath、GlaiveCodeAssistant、Magicoder、WizardCoder、GlaiveCodeAssistant等。并使用OpenAI模型GPT-4o和GPT-4o-mini进一步优化响应质量,并进行数据去污染,确保不包含基准测试数据集中的提示。

  • SFT数据构建格式

picture.image

预训练中使用的各种任务的训练格式示例。绿色< image >tag表示插入视觉特征的位置。蓝色文本代表与损失相关的真实值。

picture.image

SFT中使用的ChatML模板示例。绿色< image >标签指示插入视觉特征的位置。蓝色文本代表与损失相关的真实值。

实验结果

重点关注多模态推理、视觉上下文中的数学推理、自然图像理解、场景-文本阅读、图表理解、文档理解、现实世界感知和OCR能力。

picture.image

picture.image

picture.image

参考文献

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论