Qwen变体新成员加一,英伟达训练 NVLM-D-72B 视觉大模型

在这里插入图片描述
今天(2024 年 9 月 17 日),我们推出了前沿级多模态大语言模型(LLM)系列 NVLM 1.0,它在视觉语言任务上取得了最先进的结果,可与领先的专有模型(如 GPT-4o)和开放存取模型(如 Llama 3-V 405B 和 InternVL 2)相媲美。值得注意的是,NVLM 1.0 在多模态训练后的纯文本性能比其 LLM 骨干模型有所提高。

在此版本库中,我们将向社区开源 NVLM-1.0-D-72B(纯解码器架构)、纯解码器模型权重和代码。

我们使用传统的 Megatron-LM 训练我们的模型,并将代码库调整为 Huggingface,以实现模型托管、可重现性和推理。我们观察到 Megatron 代码库和 Huggingface 代码库之间存在数值差异,这些差异在预期的变化范围之内。我们提供 Huggingface 代码库和 Megatron 代码库的结果,以便与其他模型进行重现和比较。

视觉语言基准

BenchmarkMMMU (val / test)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 (Aug 2024)62.2 / -63.975494.487.293.178.770.480.2

纯文字基准

TasksBackbone LLMMMLUGSM8KMATHHumanEvalAvg. Accuracy
Proprietary
GPT-4.0N/A88.7-76.690.2-
Gemini Pro 1.5 (Aug 2024)N/A85.990.867.784.182.1
Claude 3.5 SonnetN/A88.796.471.192.087.0
Open LLM
(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
Open Multimodal LLM
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)

在将 Megatron checkpoint 转换为 Huggingface 时,我们调整了 InternVL 代码库,以支持 HF 中的模型加载和多 GPU 推理。在将 Qwen2.5-72B-Instruct 中的标记符转换为 Huggingface 时,我们还使用了 Qwen2.5-72B-Instruct 中的标记符,因为它包含用于视觉任务的额外特殊标记符,例如 <|vision_pad|>。我们在 Qwen2-72B-Instruct 纯文本模型和 InternViT-6B-448px-V1-5 ViT 模型的基础上,利用大规模高质量多模态数据集训练 NVLM-1.0-D-72B。有关训练代码,请参阅 Megatron-LM(即将发布)。

准备环境

我们在 Dockerfile 中提供了一个用于复制的 docker 生成文件。

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

注:我们注意到,不同的转换器版本/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()

多显卡

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[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[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}')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值