O
OpenGVLab/InternVL3-1B-Instruct
模型介绍
文件和版本
Pull Requests
讨论
分析

InternVL3-1B-Instruct

[

📂GitHub📂 GitHub📂GitHub

](https://github.com/OpenGVLab/InternVL) [

📜InternVL1.0📜 InternVL 1.0📜InternVL1.0

](https://huggingface.co/papers/2312.14238) [

📜InternVL1.5📜 InternVL 1.5📜InternVL1.5

](https://huggingface.co/papers/2404.16821) [

📜InternVL2.5📜 InternVL 2.5📜InternVL2.5

](https://huggingface.co/papers/2412.05271) [

📜InternVL2.5−MPO📜 InternVL2.5-MPO📜InternVL2.5−MPO

](https://huggingface.co/papers/2411.10442) [

📜InternVL3📜 InternVL3📜InternVL3

](https://huggingface.co/papers/2504.10479)

[

🆕博客🆕 博客🆕博客

](https://internvl.github.io/blog/) [

🗨️聊天演示🗨️ 聊天演示🗨️聊天演示

](https://internvl.opengvlab.com/) [

🤗HF演示🤗 HF 演示🤗HF演示

](https://huggingface.co/spaces/OpenGVLab/InternVL) [

🚀快速开始🚀 快速开始🚀快速开始

](#quick-start) [

📖文档📖 文档📖文档

](https://internvl.readthedocs.io/en/latest/)

image

简介

这是 InternVL3-1B 的 SFT 版本,已完成原生多模态预训练和 SFT,但尚未进行 MPO。如果不确定使用哪个版本,请使用 InternVL3-1B 版本。

我们推出 InternVL3,一个先进的多模态大语言模型(MLLM)系列,展现出卓越的综合性能。 与 InternVL 2.5 相比,InternVL3 展现出更强的多模态感知与推理能力,并进一步将其多模态能力拓展至工具调用、GUI 智能体、工业图像分析、3D 视觉感知等更多领域。 此外,我们将 InternVL3 与 Qwen2.5 Chat 模型进行对比;这些模型对应的预训练基座模型被用作 InternVL3 语言部分的初始化。得益于原生多模态预训练,InternVL3 系列在整体文本性能上甚至优于 Qwen2.5 系列。

image/png

InternVL3 家族

下表提供了 InternVL3 系列的概览。

模型名称视觉部分语言部分HF 链接
InternVL3-1BInternViT-300M-448px-V2_5Qwen2.5-0.5B🤗 链接
InternVL3-2BInternViT-300M-448px-V2_5Qwen2.5-1.5B🤗 链接
InternVL3-8BInternViT-300M-448px-V2_5Qwen2.5-7B🤗 链接
InternVL3-9BInternViT-300M-448px-V2_5internlm3-8b-instruct🤗 链接
InternVL3-14BInternViT-300M-448px-V2_5Qwen2.5-14B🤗 链接
InternVL3-38BInternViT-6B-448px-V2_5Qwen2.5-32B🤗 链接
InternVL3-78BInternViT-6B-448px-V2_5Qwen2.5-72B🤗 链接

image/png

模型架构

如下图所示,InternVL3 延续了 InternVL 2.5 及其前代模型 InternVL 1.5 和 2.0 的相同模型架构,遵循 “ViT-MLP-LLM” 范式。在新版本中,我们将新近增量预训练的 InternViT 与多种预训练 LLM 相结合,包括 InternLM 3 和 Qwen 2.5,并采用随机初始化的 MLP projector。

image/png

与上一版本相同,我们应用了 pixel unshuffle 操作,将视觉 token 数量降至原来的四分之一。此外,我们采用了与 InternVL 1.5 类似的 dynamic resolution strategy,将图像划分为 448×448 像素的图像块。从 InternVL 2.0 开始,关键区别在于我们还额外引入对多图像和视频数据的支持。

值得注意的是,在 InternVL3 中,我们集成了 Variable Visual Position Encoding (V2PE),它为视觉 token 使用更小、更灵活的位置增量。得益于 V2PE,InternVL3 相比前代模型展现出更强的长上下文理解能力。

训练策略

Native Multimodal Pre-Training

我们提出了一种 Native Multimodal Pre-Training 方法,将语言学习和视觉学习整合到同一个预训练阶段中。 与先训练纯语言模型、再适配处理额外模态的标准范式不同,我们的方法将多模态数据(例如图像-文本、视频-文本,或图像-文本交错序列)与大规模文本语料交错混合。这种统一训练方案使模型能够同时学习语言表征和多模态表征,从而无需单独的对齐或桥接模块,即可增强其处理视觉-语言任务的能力。 更多细节请参见 我们的论文。

监督微调

在 InternVL3 系列中,同样采用了 InternVL2.5 提出的随机 JPEG 压缩、平方损失重加权和多模态数据打包技术。 与 InternVL2.5 相比,InternVL3 在 SFT 阶段的主要进步在于使用了质量更高、更多样化的训练数据。 具体而言,我们进一步扩展了工具使用、3D 场景理解、GUI 操作、长上下文任务、视频理解、科学图表、创意写作和多模态推理的训练样本。

混合偏好优化

在预训练和 SFT 阶段,模型被训练以在前序 ground-truth tokens 的条件下预测下一个 token。 然而,在推理时,模型基于自身此前的输出来预测每个 token。 真实 token 与模型预测 token 之间的差异会引入分布偏移,从而可能削弱模型的 Chain-of-Thought (CoT) 推理能力。 为缓解这一问题,我们采用 MPO,通过引入正样本和负样本带来的额外监督,使模型响应分布与真实分布保持一致,从而提升推理性能。 具体而言,MPO 的训练目标是 偏好损失 $\mathcal{L}{\text{p}}$、 质量损失 $\mathcal{L}{\text{q}}$ 和生成损失 $\mathcal{L}_{\text{g}}$ 的组合,可表述如下:

L=wp⋅Lp+wq⋅Lq+wg⋅Lg,\mathcal{L}=w_{p}\cdot\mathcal{L}_{\text{p}} + w_{q}\cdot\mathcal{L}_{\text{q}} + w_{g}\cdot\mathcal{L}_{\text{g}},L=wp​⋅Lp​+wq​⋅Lq​+wg​⋅Lg​,

其中 $w_{*}$ 表示分配给每个损失分量的权重。关于 MPO 的更多细节,请参见 我们的论文。

测试时扩展

已有研究表明,测试时扩展能够有效提升 LLMs 和 MLLMs 的推理能力。 在本工作中,我们使用 Best-of-N 评估策略,并采用 VisualPRM-8B 作为 critic model,为推理和数学评估挑选最佳响应。

多模态能力评估

多模态推理与数学

image/png

OCR、图表与文档理解

image/png

多图与真实世界理解

image/png

综合多模态与幻觉评估

image/png

视觉定位

image/png

多模态多语言理解

image/png

视频理解

image/png

GUI 定位

image/png

空间推理

image/png

语言能力评估

我们将 InternVL3 与 Qwen2.5 Chat 模型进行比较,其对应的预训练基座模型被用于 InternVL3 中语言组件的初始化。 得益于原生多模态预训练,InternVL3 系列在整体文本表现上甚至优于 Qwen2.5 系列。 请注意,Qwen2.5 系列的评估得分可能与官方公布的数值存在差异,因为在 OpenCompass 评估中,我们针对所有数据集采用了表格中提供的提示版本。

image/png

消融实验

原生多模态预训练

我们在 InternVL2-8B 模型上进行实验,并完全保持其架构、初始化参数和训练数据不变。传统上,InternVL2-8B 采用的训练流程以用于特征对齐的 MLP 热身阶段开始,随后进入指令微调阶段。在我们的实验中,用原生多模态预训练过程替换了常规的 MLP 热身阶段。这一改动将原生多模态预训练对模型整体多模态能力的贡献单独分离出来。

下图中的评估结果表明,经过原生多模态预训练的模型在大多数评测基准上的表现与完整多阶段训练的 InternVL2-8B 基线相当。此外,当在更高质量的数据上进行指令微调后,该模型在所评估的多模态任务上进一步取得性能提升。这些发现凸显了原生多模态预训练赋予 MLLMs 强大多模态能力的高效性。

image/png

混合偏好优化

如下表所示,经过 MPO 微调的模型在七项多模态推理评测基准上的推理性能,均优于未使用 MPO 的对应模型。具体而言,InternVL3-78B 和 InternVL3-38B 分别比对应模型高 4.1 和 4.5 分。值得注意的是,MPO 使用的训练数据是 SFT 训练数据的子集,这表明性能提升主要源于训练算法,而非训练数据。

image/png

可变视觉位置编码

如下表所示,引入 V2PE 后,大多数评估指标均出现显著提升。此外,我们通过调整位置增量 $ \delta $ 进行的消融实验发现,即便在主要涉及常规上下文的任务中,相对较小的 $ \delta $ 值也能取得最佳性能。这些发现为未来优化 MLLMs 中视觉 token 的位置编码策略提供了重要参考。

image/png

快速开始

我们提供一个示例代码,使用 transformers 运行 InternVL3-1B。

请使用 transformers>=4.37.2,以确保模型正常运行。

模型加载

16 位(bf16 / fp16)

import torch
from transformers import AutoTokenizer, AutoModel
path = "OpenGVLab/InternVL3-1B"
model = AutoModel.from_pretrained(
    path,
    torch_dtype=torch.bfloat16,
    low_cpu_mem_usage=True,
    use_flash_attn=True,
    trust_remote_code=True).eval().cuda()

BNB 8 位量化

import torch
from transformers import AutoTokenizer, AutoModel
path = "OpenGVLab/InternVL3-1B"
model = AutoModel.from_pretrained(
    path,
    torch_dtype=torch.bfloat16,
    load_in_8bit=True,
    low_cpu_mem_usage=True,
    use_flash_attn=True,
    trust_remote_code=True).eval()

多 GPU

之所以这样编写代码,是为了避免在多 GPU 推理过程中,因张量未位于同一设备而导致报错。通过确保大语言模型(LLM)的首层与末层位于同一设备上,可防止此类错误。

import math
import torch
from transformers import AutoTokenizer, AutoModel

def split_model(model_name):
    device_map = {}
    world_size = torch.cuda.device_count()
    config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
    num_layers = config.llm_config.num_hidden_layers
    # 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.model.rotary_emb'] = 0
    device_map['language_model.lm_head'] = 0
    device_map[f'language_model.model.layers.{num_layers - 1}'] = 0

    return device_map

path = "OpenGVLab/InternVL3-1B"
device_map = split_model('InternVL3-1B')
model = AutoModel.from_pretrained(
    path,
    torch_dtype=torch.bfloat16,
    low_cpu_mem_usage=True,
    use_flash_attn=True,
    trust_remote_code=True,
    device_map=device_map).eval()

使用 Transformers 进行推理

import math
import numpy as np
import torch
import torchvision.transforms as T
from decord import VideoReader, cpu
from PIL import Image
from torchvision.transforms.functional import InterpolationMode
from transformers import AutoModel, AutoTokenizer

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

def split_model(model_name):
    device_map = {}
    world_size = torch.cuda.device_count()
    config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
    num_layers = config.llm_config.num_hidden_layers
    # 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.model.rotary_emb'] = 0
    device_map['language_model.lm_head'] = 0
    device_map[f'language_model.model.layers.{num_layers - 1}'] = 0

    return device_map

# If you set `load_in_8bit=True`, you will need two 80GB GPUs.
# If you set `load_in_8bit=False`, you will need at least three 80GB GPUs.
path = 'OpenGVLab/InternVL3-1B'
device_map = split_model('InternVL3-1B')
model = AutoModel.from_pretrained(
    path,
    torch_dtype=torch.bfloat16,
    load_in_8bit=False,
    low_cpu_mem_usage=True,
    use_flash_attn=True,
    trust_remote_code=True,
    device_map=device_map).eval()
tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True, use_fast=False)

# set the max number of tiles in `max_num`
pixel_values = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda()
generation_config = dict(max_new_tokens=1024, do_sample=True)

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

question = 'Can you tell me a story?'
response, history = model.chat(tokenizer, None, question, generation_config, history=history, return_history=True)
print(f'User: {question}\nAssistant: {response}')

# single-image single-round conversation (单图单轮对话)
question = '<image>\nPlease describe the image shortly.'
response = model.chat(tokenizer, pixel_values, question, generation_config)
print(f'User: {question}\nAssistant: {response}')

# single-image multi-round conversation (单图多轮对话)
question = '<image>\nPlease describe the image in detail.'
response, history = model.chat(tokenizer, pixel_values, question, generation_config, history=None, return_history=True)
print(f'User: {question}\nAssistant: {response}')

question = 'Please write a poem according to the image.'
response, history = model.chat(tokenizer, pixel_values, question, generation_config, history=history, return_history=True)
print(f'User: {question}\nAssistant: {response}')

# multi-image multi-round conversation, combined images (多图多轮对话,拼接图像)
pixel_values1 = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda()
pixel_values2 = load_image('./examples/image2.jpg', max_num=12).to(torch.bfloat16).cuda()
pixel_values = torch.cat((pixel_values1, pixel_values2), dim=0)

question = '<image>\nDescribe the two images in detail.'
response, history = model.chat(tokenizer, pixel_values, question, generation_config,
                               history=None, return_history=True)
print(f'User: {question}\nAssistant: {response}')

question = 'What are the similarities and differences between these two images.'
response, history = model.chat(tokenizer, pixel_values, question, generation_config,
                               history=history, return_history=True)
print(f'User: {question}\nAssistant: {response}')

# multi-image multi-round conversation, separate images (多图多轮对话,独立图像)
pixel_values1 = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda()
pixel_values2 = load_image('./examples/image2.jpg', max_num=12).to(torch.bfloat16).cuda()
pixel_values = torch.cat((pixel_values1, pixel_values2), dim=0)
num_patches_list = [pixel_values1.size(0), pixel_values2.size(0)]

question = 'Image-1: <image>\nImage-2: <image>\nDescribe the two images in detail.'
response, history = model.chat(tokenizer, pixel_values, question, generation_config,
                               num_patches_list=num_patches_list,
                               history=None, return_history=True)
print(f'User: {question}\nAssistant: {response}')

question = 'What are the similarities and differences between these two images.'
response, history = model.chat(tokenizer, pixel_values, question, generation_config,
                               num_patches_list=num_patches_list,
                               history=history, return_history=True)
print(f'User: {question}\nAssistant: {response}')

# batch inference, single image per sample (单图批处理)
pixel_values1 = load_image('./examples/image1.jpg', max_num=12).to(torch.bfloat16).cuda()
pixel_values2 = load_image('./examples/image2.jpg', max_num=12).to(torch.bfloat16).cuda()
num_patches_list = [pixel_values1.size(0), pixel_values2.size(0)]
pixel_values = torch.cat((pixel_values1, pixel_values2), dim=0)

questions = ['<image>\nDescribe the image in detail.'] * len(num_patches_list)
responses = model.batch_chat(tokenizer, pixel_values,
                             num_patches_list=num_patches_list,
                             questions=questions,
                             generation_config=generation_config)
for question, response in zip(questions, responses):
    print(f'User: {question}\nAssistant: {response}')

# video multi-round conversation (视频多轮对话)
def get_index(bound, fps, max_frame, first_idx=0, num_segments=32):
    if bound:
        start, end = bound[0], bound[1]
    else:
        start, end = -100000, 100000
    start_idx = max(first_idx, round(start * fps))
    end_idx = min(round(end * fps), max_frame)
    seg_size = float(end_idx - start_idx) / num_segments
    frame_indices = np.array([
        int(start_idx + (seg_size / 2) + np.round(seg_size * idx))
        for idx in range(num_segments)
    ])
    return frame_indices

def load_video(video_path, bound=None, input_size=448, max_num=1, num_segments=32):
    vr = VideoReader(video_path, ctx=cpu(0), num_threads=1)
    max_frame = len(vr) - 1
    fps = float(vr.get_avg_fps())

    pixel_values_list, num_patches_list = [], []
    transform = build_transform(input_size=input_size)
    frame_indices = get_index(bound, fps, max_frame, first_idx=0, num_segments=num_segments)
    for frame_index in frame_indices:
        img = Image.fromarray(vr[frame_index].asnumpy()).convert('RGB')
        img = dynamic_preprocess(img, image_size=input_size, use_thumbnail=True, max_num=max_num)
        pixel_values = [transform(tile) for tile in img]
        pixel_values = torch.stack(pixel_values)
        num_patches_list.append(pixel_values.shape[0])
        pixel_values_list.append(pixel_values)
    pixel_values = torch.cat(pixel_values_list)
    return pixel_values, num_patches_list

video_path = './examples/red-panda.mp4'
pixel_values, num_patches_list = load_video(video_path, num_segments=8, max_num=1)
pixel_values = pixel_values.to(torch.bfloat16).cuda()
video_prefix = ''.join([f'Frame{i+1}: <image>\n' for i in range(len(num_patches_list))])
question = video_prefix + 'What is the red panda doing?'
# Frame1: <image>\nFrame2: <image>\n...\nFrame8: <image>\n{question}
response, history = model.chat(tokenizer, pixel_values, question, generation_config,
                               num_patches_list=num_patches_list, history=None, return_history=True)
print(f'User: {question}\nAssistant: {response}')

question = 'Describe this video in detail.'
response, history = model.chat(tokenizer, pixel_values, question, generation_config,
                               num_patches_list=num_patches_list, history=history, return_history=True)
print(f'User: {question}\nAssistant: {response}')

流式输出

除该方法外,您还可以使用以下代码获取流式输出。

from transformers import TextIteratorStreamer
from threading import Thread

# Initialize the streamer
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True, timeout=10)
# Define the generation configuration
generation_config = dict(max_new_tokens=1024, do_sample=False, streamer=streamer)
# Start the model chat in a separate thread
thread = Thread(target=model.chat, kwargs=dict(
    tokenizer=tokenizer, pixel_values=pixel_values, question=question,
    history=None, return_history=False, generation_config=generation_config,
))
thread.start()

# Initialize an empty string to store the generated text
generated_text = ''
# Loop through the streamer to get the new text as it is generated
for new_text in streamer:
    if new_text == model.conv_template.sep:
        break
    generated_text += new_text
    print(new_text, end='', flush=True)  # Print each new chunk of generated text on the same line

微调

目前已有多个仓库支持对 InternVL 系列模型进行微调,包括 InternVL、SWIFT、XTurner 等。如需了解微调的更多细节,请查阅它们的相关文档。

部署

LMDeploy

LMDeploy 是一套面向 LLMs 和 VLMs 的压缩、部署与推理服务工具集。

# if lmdeploy<0.7.3, you need to explicitly set chat_template_config=ChatTemplateConfig(model_name='internvl2_5')
pip install lmdeploy>=0.7.3

LMDeploy 将多模态视觉-语言模型(VLM)复杂的推理过程抽象为一个易用的流水线,类似于大语言模型(LLM)的推理流水线。

一个“Hello, world”示例

from lmdeploy import pipeline, TurbomindEngineConfig, ChatTemplateConfig
from lmdeploy.vl import load_image

model = 'OpenGVLab/InternVL3-1B'
image = load_image('https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg')
pipe = pipeline(model, backend_config=TurbomindEngineConfig(session_len=16384, tp=1), chat_template_config=ChatTemplateConfig(model_name='internvl2_5'))
response = pipe(('describe this image', image))
print(response.text)

如果执行此示例时出现 ImportError,请按照提示安装所需的依赖包。

多图推理

当处理多张图片时,可以将它们全部放入一个列表中。请注意,多张图片会增加输入 token 的数量,因此通常需要将上下文窗口大小调大。

from lmdeploy import pipeline, TurbomindEngineConfig, ChatTemplateConfig
from lmdeploy.vl import load_image
from lmdeploy.vl.constants import IMAGE_TOKEN

model = 'OpenGVLab/InternVL3-1B'
pipe = pipeline(model, backend_config=TurbomindEngineConfig(session_len=16384, tp=1), chat_template_config=ChatTemplateConfig(model_name='internvl2_5'))

image_urls=[
    'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/human-pose.jpg',
    'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/det.jpg'
]

images = [load_image(img_url) for img_url in image_urls]
# Numbering images improves multi-image conversations
response = pipe((f'Image-1: {IMAGE_TOKEN}\nImage-2: {IMAGE_TOKEN}\ndescribe these two images', images))
print(response.text)

Batch Prompts 推理

使用 batch prompts 进行推理非常简单,只需将它们放入列表结构中:

from lmdeploy import pipeline, TurbomindEngineConfig, ChatTemplateConfig
from lmdeploy.vl import load_image

model = 'OpenGVLab/InternVL3-1B'
pipe = pipeline(model, backend_config=TurbomindEngineConfig(session_len=16384, tp=1), chat_template_config=ChatTemplateConfig(model_name='internvl2_5'))

image_urls=[
    "https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/human-pose.jpg",
    "https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/det.jpg"
]
prompts = [('describe this image', load_image(img_url)) for img_url in image_urls]
response = pipe(prompts)
print(response)

多轮对话

通过 pipeline 进行多轮对话有两种方式。一种是按照 OpenAI 的格式构造 messages,并使用上文介绍的方法;另一种是使用 pipeline.chat 接口。

from lmdeploy import pipeline, TurbomindEngineConfig, GenerationConfig, ChatTemplateConfig
from lmdeploy.vl import load_image

model = 'OpenGVLab/InternVL3-1B'
pipe = pipeline(model, backend_config=TurbomindEngineConfig(session_len=16384, tp=1), chat_template_config=ChatTemplateConfig(model_name='internvl2_5'))

image = load_image('https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/human-pose.jpg')
gen_config = GenerationConfig(top_k=40, top_p=0.8, temperature=0.8)
sess = pipe.chat(('describe this image', image), gen_config=gen_config)
print(sess.response.text)
sess = pipe.chat('What is the woman doing?', session=sess, gen_config=gen_config)
print(sess.response.text)

服务

LMDeploy 的 api_server 可以通过一条命令,轻松将模型打包为服务。所提供的 RESTful API 与 OpenAI 接口兼容。以下为服务启动示例:

lmdeploy serve api_server OpenGVLab/InternVL3-1B --chat-template internvl2_5 --server-port 23333 --tp 1

若需使用 OpenAI 风格接口,请安装 OpenAI:

pip install openai

然后,使用以下代码发起 API 调用:

from openai import OpenAI

client = OpenAI(api_key='YOUR_API_KEY', base_url='http://0.0.0.0:23333/v1')
model_name = client.models.list().data[0].id
response = client.chat.completions.create(
    model=model_name,
    messages=[{
        'role':
        'user',
        'content': [{
            'type': 'text',
            'text': 'describe this image',
        }, {
            'type': 'image_url',
            'image_url': {
                'url':
                'https://modelscope.oss-cn-beijing.aliyuncs.com/resource/tiger.jpeg',
            },
        }],
    }],
    temperature=0.8,
    top_p=0.8)
print(response)

许可

本项目基于 MIT License 发布。本项目使用预训练的 Qwen2.5 作为组件,其采用 Apache-2.0 License 授权。

引用

如果您觉得本项目对您的研究有帮助,请考虑引用:

@article{chen2024expanding,
  title={Expanding Performance Boundaries of Open-Source Multimodal Models with Model, Data, and Test-Time Scaling},
  author={Chen, Zhe and Wang, Weiyun and Cao, Yue and Liu, Yangzhou and Gao, Zhangwei and Cui, Erfei and Zhu, Jinguo and Ye, Shenglong and Tian, Hao and Liu, Zhaoyang and others},
  journal={arXiv preprint arXiv:2412.05271},
  year={2024}
}
@article{wang2024mpo,
  title={Enhancing the Reasoning Ability of Multimodal Large Language Models via Mixed Preference Optimization},
  author={Wang, Weiyun and Chen, Zhe and Wang, Wenhai and Cao, Yue and Liu, Yangzhou and Gao, Zhangwei and Zhu, Jinguo and Zhu, Xizhou and Lu, Lewei and Qiao, Yu and Dai, Jifeng},
  journal={arXiv preprint arXiv:2411.10442},
  year={2024}
}
@article{chen2024far,
  title={How Far Are We to GPT-4V? Closing the Gap to Commercial Multimodal Models with Open-Source Suites},
  author={Chen, Zhe and Wang, Weiyun and Tian, Hao and Ye, Shenglong and Gao, Zhangwei and Cui, Erfei and Tong, Wenwen and Hu, Kongzhi and Luo, Jiapeng and Ma, Zheng and others},
  journal={arXiv preprint arXiv:2404.16821},
  year={2024}
}
@inproceedings{chen2024internvl,
  title={Internvl: Scaling up vision foundation models and aligning for generic visual-linguistic tasks},
  author={Chen, Zhe and Wu, Jiannan and Wang, Wenhai and Su, Weijie and Chen, Guo and Xing, Sen and Zhong, Muyan and Zhang, Qinglong and Zhu, Xizhou and Lu, Lewei and others},
  booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
  pages={24185--24198},
  year={2024}
}