n
nvidia/NVLM-D-72B
模型介绍文件和版本分析
下载使用量0

Image Description

模型概述

描述

该系列模型可执行视觉-语言及纯文本任务,包括光学字符识别、多模态推理、定位、常识推理、世界知识运用和代码生成。

本模型已准备就绪,可供非商业用途使用。

许可协议/使用条款

管辖条款:协议文本 - 知识共享署名-非商业性使用4.0国际许可协议。

补充信息:Qwen2-72B-Instruct 遵循 LICENSE · Qwen/Qwen2-72B-Instruct at main,InternViT-6B-448px-V1-2 遵循 MIT 许可协议 – 开放源代码促进会。

模型详情

今日(2024年9月17日),我们正式推出 NVLM 1.0,这是一系列前沿级多模态大型语言模型(LLMs)。该系列模型在视觉-语言任务上取得了最先进的成果,可与领先的专有模型(如 GPT-4o)及开源模型(如 Llama 3-V 405B 和 InternVL 2)相媲美。值得注意的是,经过多模态训练后,NVLM 1.0 在纯文本性能上相较于其基础语言模型有显著提升。

在本代码库中,我们开源了 NVLM-1.0-D-72B(纯解码器架构),即面向社区开放纯解码器模型的权重与代码。

参考资料

论文   推理代码(HF)   训练代码   官方网站

基准测试结果

我们使用 legacy Megatron-LM 训练模型,并将代码库适配至 Huggingface,以便进行模型托管、结果复现和推理。 我们观察到 Megatron 与 Huggingface 代码库之间存在数值差异,但这些差异均在预期的变动范围内。 为便于复现及与其他模型进行比较,我们同时提供了基于 Huggingface 代码库和 Megatron 代码库的结果。

截至2024年9月17日,多模态基准测试结果如下:

视觉-语言基准测试

基准测试MMMU(验证集/测试集)MathVistaOCRBenchAI2DChartQADocVQATextVQARealWorldQAVQAv2
NVLM-D 1.0 72B(Huggingface)58.7 / 54.965.285294.286.092.682.669.585.4
NVLM-D 1.0 72B(Megatron)59.7 / 54.665.285394.286.092.682.169.785.4
Llama 3.2 90B60.3 / -57.3-92.385.590.1--78.1
Llama 3-V 70B60.6 / ---93.083.292.283.4-79.1
Llama 3-V 405B64.5 / ---94.185.892.684.8-80.2
InternVL2-Llama3-76B55.2 / -65.583994.888.494.184.472.2-
GPT-4V56.8 / 55.749.964578.278.588.478.061.477.2
GPT-4o69.1 / -63.873694.285.792.8---
Claude 3.5 Sonnet68.3 / -67.778894.790.895.2---
Gemini 1.5 Pro(2024年8月)62.2 / -63.975494.487.293.178.770.480.2

纯文本基准测试

任务基础大语言模型MMLUGSM8KMATHHumanEval平均准确率
专有模型
GPT-4.0N/A88.7-76.690.2-
Gemini Pro 1.5(2024年8月)N/A85.990.867.784.182.1
Claude 3.5 SonnetN/A88.796.471.192.087.0
开源大语言模型
(a) Nous-Hermes-2-Yi-34BN/A75.578.621.843.354.8
(b) Qwen-72B-InstructN/A82.391.159.786.079.8
(c) Llama-3-70B-InstructN/A82.093.051.081.776.6
(d) Llama-3.1-70B-InstructN/A83.695.168.080.581.8
(e) Llama-3.1-405B-InstructN/A87.396.873.889.086.7
开源多模态大语言模型
VILA-1.5 40B(a)73.367.516.834.1🥶 47.9 (-6.9)
LLaVA-OneVision 72B(b)80.689.949.274.4🥶 73.5 (-6.3)
InternVL-2-Llama3-76B(c)78.587.142.571.3🥶 69.9 (-6.7)
*Llama 3-V 70B(d)83.695.168.080.5🙂 81.8 (0)
*Llama 3-V 405B(e)87.396.873.889.0🙂 86.7 (0)
NVLM-D 1.0 72B(Megatron)(b)82.092.973.188.4🥳 84.1 (+4.3)
NVLM-D 1.0 72B(Huggingface)(b)81.793.273.189.0🥳 84.3 (+4.5)

模型架构

网络架构: 仅解码器 Transformer

纯文本 LLM 基础模型: Qwen2-72B-Instruct

视觉编码器: InternViT-6B

鲁棒性

在该数据集上训练的模型无法再生其训练数据:

  1. 该模型不具备图像生成能力,因其输出仅为文本。因此,它无法再生训练期间可能见过的任何图像。

  2. 该模型无法再生训练文本数据:在训练过程中,模型将文本和图像作为输入,且模型输出(文本)同时以这两种输入为条件。在推理过程中,若没有训练图像作为输入,模型将无法重现训练文本数据的任何部分。

输入

输入类型: 文本、图像
输入格式: 字符串、Pillow 库支持的格式
输入维度: 一维(1D)、二维(2D)
与输入相关的其他属性: 最大令牌长度 = 128K 令牌

输出

输出类型: 文本
输出格式: 字符串
模型输出: 1D
与输出相关的其他属性: 无

如何使用

将 Megatron checkpoint 转换为 Huggingface 格式时,我们适配了 InternVL 代码库,以支持在 HF 中进行模型加载和多 GPU 推理。 在将分词器适配到 Huggingface 时,我们还使用了来自 Qwen2.5-72B-Instruct 的分词器,因为它包含用于视觉任务的额外特殊令牌,例如 <|vision_pad|>。 我们基于 Qwen2-72B-Instruct 纯文本模型和 InternViT-6B-448px-V1-5 ViT 模型,并使用我们的大规模高质量多模态数据集训练了 NVLM-1.0-D-72B。 关于训练代码,请参考 Megatron-Core。

环境准备

我们在Dockerfile中提供了用于复现的docker构建文件。

该docker镜像基于nvcr.io/nvidia/pytorch:23.09-py3。

注意:我们发现不同的transformer版本/CUDA版本/docker版本可能会导致基准测试数值存在细微差异。为确保精确复现,建议使用上述Dockerfile。

模型加载

import torch
from transformers import AutoModel

path = "nvidia/NVLM-D-72B"
model = AutoModel.from_pretrained(
    path,
    torch_dtype=torch.bfloat16,
    low_cpu_mem_usage=True,
    use_flash_attn=False,
    trust_remote_code=True).eval()

多GPU支持

可按以下方式在多个GPU上加载模型:

import torch
import math
from transformers import AutoModel

def split_model():
    device_map = {}
    world_size = torch.cuda.device_count()
    num_layers = 80
    # Since the first GPU will be used for ViT, treat it as half a GPU.
    num_layers_per_gpu = math.ceil(num_layers / (world_size - 0.5))
    num_layers_per_gpu = [num_layers_per_gpu] * world_size
    num_layers_per_gpu[0] = math.ceil(num_layers_per_gpu[0] * 0.5)
    layer_cnt = 0
    for i, num_layer in enumerate(num_layers_per_gpu):
        for j in range(num_layer):
            device_map[f'language_model.model.layers.{layer_cnt}'] = i
            layer_cnt += 1
    device_map['vision_model'] = 0
    device_map['mlp1'] = 0
    device_map['language_model.model.tok_embeddings'] = 0
    device_map['language_model.model.embed_tokens'] = 0
    device_map['language_model.output'] = 0
    device_map['language_model.model.norm'] = 0
    device_map['language_model.lm_head'] = 0
    device_map['language_model.model.rotary_emb'] = 0
    device_map[f'language_model.model.layers.{num_layers - 1}'] = 0

    return device_map

path = "nvidia/NVLM-D-72B"
device_map = split_model()
model = AutoModel.from_pretrained(
    path,
    torch_dtype=torch.bfloat16,
    low_cpu_mem_usage=True,
    use_flash_attn=False,
    trust_remote_code=True,
    device_map=device_map).eval()

推理

import torch
from transformers import AutoTokenizer, AutoModel
import math
from PIL import Image
import torchvision.transforms as T
from torchvision.transforms.functional import InterpolationMode


def split_model():
    device_map = {}
    world_size = torch.cuda.device_count()
    num_layers = 80
    # Since the first GPU will be used for ViT, treat it as half a GPU.
    num_layers_per_gpu = math.ceil(num_layers / (world_size - 0.5))
    num_layers_per_gpu = [num_layers_per_gpu] * world_size
    num_layers_per_gpu[0] = math.ceil(num_layers_per_gpu[0] * 0.5)
    layer_cnt = 0
    for i, num_layer in enumerate(num_layers_per_gpu):
        for j in range(num_layer):
            device_map[f'language_model.model.layers.{layer_cnt}'] = i
            layer_cnt += 1
    device_map['vision_model'] = 0
    device_map['mlp1'] = 0
    device_map['language_model.model.tok_embeddings'] = 0
    device_map['language_model.model.embed_tokens'] = 0
    device_map['language_model.output'] = 0
    device_map['language_model.model.norm'] = 0
    device_map['language_model.lm_head'] = 0
    device_map['language_model.model.rotary_emb'] = 0
    device_map[f'language_model.model.layers.{num_layers - 1}'] = 0

    return device_map


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=12, image_size=448, use_thumbnail=False):
    orig_width, orig_height = image.size
    aspect_ratio = orig_width / orig_height

    # calculate the existing image aspect ratio
    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])

    # find the closest aspect ratio to the target
    target_aspect_ratio = find_closest_aspect_ratio(
        aspect_ratio, target_ratios, orig_width, orig_height, image_size)

    # calculate the target width and height
    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]

    # resize the image
    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 the image
        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=12):
    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

path = "nvidia/NVLM-D-72B"
device_map = split_model()
model = AutoModel.from_pretrained(
    path,
    torch_dtype=torch.bfloat16,
    low_cpu_mem_usage=True,
    use_flash_attn=False,
    trust_remote_code=True,
    device_map=device_map).eval()

print(model)

tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True, use_fast=False)
generation_config = dict(max_new_tokens=1024, do_sample=False)

# pure-text conversation
question = 'Hello, who are you?'
response, history = model.chat(tokenizer, None, question, generation_config, history=None, return_history=True)
print(f'User: {question}\nAssistant: {response}')

# single-image single-round conversation
pixel_values = load_image('path/to/your/example/image.jpg', max_num=6).to(
    torch.bfloat16)
question = '<image>\nPlease describe the image shortly.'
response = model.chat(tokenizer, pixel_values, question, generation_config)
print(f'User: {question}\nAssistant: {response}')

基准测试评估

要在基准数据集上测试我们的 NVLM-1.0 模型,您可以使用以下代码:

python run_eval.py --config-path eval/full_eval.yaml \
 --result-save-path path/to/eval_results/ \
 --zero-shot-eval-tasks chartqa coco_caption flickr30k_caption vqav2 mmmu textvqa mathvista mmbench chartqa docvqa realworldqa ocrbench ai2diagram ai2diagram_nomask mmmu_pro docvqa_test

具体而言,

  • --config-path eval/full_eval.yaml 文件包含评估配置,包括评估提示词、评估数据集路径以及生成超参数。
  • --result-save-path path/to/eval_results/ 指定用于保存评估结果的路径。
  • --zero-shot-eval-tasks 指定要评估的任务。

软件集成

运行时引擎

  • PyTorch

支持的硬件微架构兼容性:

  • NVIDIA Hopper

[首选/支持的] 操作系统:

  • Linux

推理

引擎: PyTorch
测试硬件:

  • H100

模型版本

  • v1.0-D (NVLM-D)

训练、测试与评估数据集

预训练数据集

链接

  • 参见表 4

按数据集的数据收集方法

  • 混合:自动化、人工、合成、未知

按数据集的标注方法

  • 混合:自动化、人工、合成、未知

特性

  • 训练数据包括图像标题、图像-文本对、自然图像、图表、文档、场景描述以及数学推理。

监督微调数据集

链接

  • 参见表 6

按数据集的数据收集方法

  • 混合:自动化、人工、合成、未知

按数据集的标注方法

  • 混合:自动化、人工、合成、未知

特性

  • 训练数据包括图像标题;通用知识;图像-文本对;自然图像;图表;示意图;文档;场景描述;科学图表、课程、教科书数据及问答对;视觉指令调优;以及数学推理。

评估数据集

链接

  • 参见 6.1 节“基准测试”

按数据集的数据收集方法

  • 人工

按数据集的标注方法

  • 人工

特性

  • 评估内容包括通用知识、视觉问答、图表理解、表格、光学字符识别以及数学推理。

通信作者

戴文亮*(wdai@nvidia.com)、李奈妍*(nayeonl@nvidia.com)、王博欣*(boxinw@nvidia.com)、杨卓林*(zhuoliny@nvidia.com)、平伟*(wping@nvidia.com)

*贡献均等

引用格式

@article{nvlm2024,
  title={NVLM: Open Frontier-Class Multimodal LLMs},
  author={Dai, Wenliang and Lee, Nayeon and Wang, Boxin and Yang, Zhuolin and Liu, Zihan and Barker, Jon and Rintamaki, Tuomas and Shoeybi, Mohammad and Catanzaro, Bryan and Ping, Wei},
  journal={arXiv preprint},
  year={2024}}

伦理考量

NVIDIA 坚信可信 AI 是一项共同责任,我们已制定相关政策和实践,以支持各类 AI 应用的开发。当开发者按照我们的服务条款下载或使用本模型时,应与其支持的模型团队合作,确保该模型满足相关行业和用例的要求,并应对未预见的产品误用问题。

请通过此处报告安全漏洞或 NVIDIA AI 相关问题。