VLM 系列——MiniCPM-Llama3-V 2.6——论文解读——前瞻(源码解读)

news2024/12/26 9:29:13

一、概述

1、是什么

     是一款面向终端设备的多模态大型语言模型(MLLM),论文暂未发布 ,它专注于实现在手机等资源受限设备上的高级AI功能,参数8B(qwen2 7B + SigLIP ViT-400m/14 + 视觉标记压缩层 )。该模型能够处理包括文本、图像在内的多种数据类型,具备图片描述、单图文问答、代码编写和debug、 多图问对话、视频理解对话 、json格式、高清OCR解析(函数调用论文暂时未提)。

2、亮点

    🔥 领先的性能。 在最新版本 OpenCompass 榜单上(综合 8 个主流多模态评测基准)平均得分 65.2,以8B量级的大小在单图理解方面超越了 GPT-4o mini、GPT-4V、Gemini 1.5 Pro 和 Claude 3.5 Sonnet 等主流商用闭源多模态大模型。
    🖼️ 多图理解和上下文学习。支持多图对话和推理。它在 Mantis-Eval、BLINK、Mathverse mv 和 Sciverse mv 等主流多图评测基准中取得了最佳水平,并展现出了优秀的上下文学习能力。
    🎬 视频理解。接受视频输入,进行对话和提供涵盖时序和空间信息的详细视频描述。模型在 有/无字幕 评测场景下的 Video-MME 表现均超过了 GPT-4V、Claude 3.5 Sonnet 和 LLaVA-NeXT-Video-34B等商用闭源模型。
    💪 强大的 OCR 能力及其他功能。 可以处理任意长宽比的图像,像素数可达 180 万(如 1344x1344)。在 OCRBench 上取得最佳水平,超过 GPT-4o、GPT-4V 和 Gemini 1.5 Pro 等商用闭源模型。基于最新的 RLAIF-V 和 VisCPM 技术,其具备了可信的多模态行为,在 Object HalBench 上的幻觉率显著低于 GPT-4o 和 GPT-4V,并支持英语、中文、德语、法语、意大利语、韩语等多种语言。
    🚀 卓越的效率。 除了对个人用户友好的模型大小,MiniCPM-V 2.6 还表现出最先进的视觉 token 密度(即每个视觉 token 编码的像素数量)。它仅需 640 个 token 即可处理 180 万像素图像,比大多数模型少 75%。这一特性优化了模型的推理速度、首 token 延迟、内存占用和功耗。因此,MiniCPM-V 2.6 可以支持 iPad 等终端设备上的高效实时视频理解。
    💫 易于使用。 MiniCPM-V 2.6 可以通过多种方式轻松使用:(1) llama.cpp 和 ollama 支持在本地设备上进行高效的 CPU 推理,(2) int4 和 GGUF 格式的量化模型,有 16 种尺寸,(3) vLLM 支持高吞吐量和内存高效的推理,(4) 针对新领域和任务进行微调,(5) 使用 Gradio 快速设置本地 WebUI 演示,(6) 在线demo即可体验。

PS

     目前20240815官方没有更新论文,按照2.5的时间估计论文得年底了,主要是一些宣传PR,只能通过源码来窥探模型结构,至于训练数据还要等官方论文。
    目前从源码看除了LLM换成了qwen2 7B, 其他模型结构和图片的处理等没有改变,主要修改了文本数据的输入格式,以支持多图和视频对话理解。
    建议配合上一篇解读来看,互补,上一篇讲了模型数据,这一片重点讲 图片预处理、文本token格式、训练推理加速中padding的处理方法。

二、模型

    1、模型结构

    由三部分组成:视觉编码器、压缩层、大语言模型,一共8B参数。具体如下:

    视觉编码器

    采用SigLIP SoViT-400m/14,将输入图像转换为视觉标记。但是对SigLIP源码进行了修改,主要是支持后面数据标签部分讲的slice和改进版的padding。
    这里需要注意,图片的分割方式(和上一篇2.5是一样的,那篇没看源码,所以详细步骤以这个为准),如上面figure3 a 首先有一张完整的原图直接进行缩放,然后根据如下公式来匹配最终要切成几个子图( 为了处理具有不同纵横比的原始高分辨率图像,将 图像分割成切片,每个切片在分辨率和纵横比方面更好地匹配ViT的预训练设置,并且有利于处理密集的文本任务)。 给定一个分辨率为(WI, HI)的图像,和一个在分辨率为(Wv, Hv)的图像上预训练的ViT:
    1)计算切片大致数量: N = min (⌈WI×HI / (Wv×Hv)⌉, max_slice_nums ),这里 是向上取整的意思, max_slice_nums 这里设置的是9
    2)将N进行扩充,如果N <= 1 或者超参数 nerver_split = True,则不进行后面的切片。
                                    否则将 N 扩充为N-1、N、N+1,但那是依旧需要 1<= N’ <= max_slice_nums,其中N’ 是扩充后的N的意思,也就是 N-1、N、N+1 都需要满足这个条件。
    3)对扩从后的每个N’从集合CN = {(m, n)|m × n = N, m ∈ N, n ∈ N}中选择行数n和列数m的组合,选择策略:
    

    注意这里:
    *最终输入vit 的不是标准的448*448,是根据长宽比缩放的分辨率,对于一张图来说,假设slice图有9个,那么最终加上原图的整图有十个输入,其中原图整图一个分辨率,其余9个slice一个分辨率,就会产生2中分辨率。
    主要涉及源码如下,省略 self.find_best_resize、 self.get_refine_siz、 self.split_to_patches函数。    
def get_sliced_grid(self, image_size, max_slice_nums, nerver_split=False):
    original_width, original_height = image_size
    log_ratio = math.log(original_width / original_height)
    ratio = original_width * original_height / (self.scale_resolution * self.scale_resolution)
    multiple = min(math.ceil(ratio), max_slice_nums)
    if multiple <= 1 or nerver_split:
        return None
    candidate_split_grids_nums = []
    for i in [multiple - 1, multiple, multiple + 1]:
        if i == 1 or i > max_slice_nums:
            continue
        candidate_split_grids_nums.append(i)

    candidate_grids = []
    for split_grids_nums in candidate_split_grids_nums:
        m = 1
        while m <= split_grids_nums:
            if split_grids_nums % m == 0:
                candidate_grids.append([m, split_grids_nums // m])
            m += 1

    best_grid = [1, 1]
    min_error = float("inf")
    for grid in candidate_grids:
        error = abs(log_ratio - math.log(grid[0] / grid[1]))
        if error < min_error:
            best_grid = grid
            min_error = error

    return best_grid

def slice_image(
    self, image, max_slice_nums=9, scale_resolution=448, patch_size=14, never_split=False
):
    original_size = image.size
    source_image = None
    best_grid = self.get_sliced_grid(original_size, max_slice_nums, never_split)
    patches = []

    if best_grid is None:
        # dont need to slice, upsample
        best_size = self.find_best_resize(
            original_size, scale_resolution, patch_size, allow_upscale=True
        )
        source_image = image.resize(best_size, resample=Image.Resampling.BICUBIC)
    else:
        # source image, down-sampling and ensure divided by patch_size
        best_resize = self.find_best_resize(original_size, scale_resolution, patch_size)
        source_image = image.copy().resize(best_resize, resample=Image.Resampling.BICUBIC)
        refine_size = self.get_refine_size(
            original_size, best_grid, scale_resolution, patch_size, allow_upscale=True
        )
        refine_image = image.resize(refine_size, resample=Image.Resampling.BICUBIC)
        patches = self.split_to_patches(refine_image, best_grid)

    return source_image, patches, best_grid

    压缩层

    具有一层交叉注意的感知器重采样结构,其中包含64(2.5版本是96个)个可学习的query,将每个图像切片 压缩成64个,大大降低token数。(注意如果图像产生了1个总图+9个分图共10个切片,那么将需要64*10=640 个token输入LLM)

    大语言模型

    采用Qwen2 7B,接收压缩后的视觉和文本标记,生成输出。这里注意后面数据标签部分提到的对图片token的特殊处理方式。

    2、模型亮点

    自适应视觉编码:能够处理高达1.8M像素的高分辨率图像,适应任意纵横比。
    多图理解和上下文学习。支持多图对话和推理。
    视频理解。接受视频输入,进行对话和提供涵盖时序和空间信息的详细视频描述。

    PS

    没有提具体LLM版本,应该是基础版Qwen2 7B,源码是:self.llm = Qwen2ForCausalLM(config)

三、数据

    这部分因为还没有论文发布,目前的官方信息都还没有这部分内容。

    1、数据标签

    因为支持多图和视频外加一张图本身有多个切片,所以最终的数据反应到token 维度是:
    
    <|im_start|>system
    You are a helpful assistant.<|im_end|>
    <|im_start|>user
    <image_id>0</image_id><image><unk>...<unk></image><slice><unk>...<unk></slice><slice><unk>...<unk></slice><slice><unk>...<unk></slice>
    <slice><unk>...<unk></slice><slice><unk>...<unk></slice><slice><unk>...<unk></slice>
    <slice><unk>...<unk></slice><slice><unk>...<unk></slice><slice><unk>...<unk></slice>
    Describe this image<|im_end|>
    <|im_start|>assistant

    注意

    1、开始标志变为<|im_start|>,对应的结束标志变为<|im_end|>。
    2、因为有多图和视频所以有标志图片顺序的: <image_id>0</image_id>。
    3、因为有切片图和整图,所以要区分: 整图的标识 <image></image> 和 slice 的标识 <slice></slice>。其中对于切片图每行后面有一个额外的标志“换行符”。
    4、其中的<unk>是占位符,因为2.6版本每个图片原图或切片被重新采样编码为64个token,所以其实有64个占位符。最终LLM推理的时候这个占位符是要替换为真是的图像token的。

    PS

    关于为什么有这个 <unk>占位符:
    1、实现方式:源码实现的时候,是先通过计算图片的切片等数,使用占位符填充LLM的输入,然后进行文本的token 化,然后再将图片batch 化进入视觉模型进行请求,然后再将真实的视觉重采样后的token 输入LLM。特别注意:前面说的batch化不是prompt 自己的batch请求,包含:prompt本身batch、本身一条prompt里面有多个图、一个图又有本身+切片导致其实有三个维度会造成很多图,统一成一个超大batch。
    2、猜测原因:1)如上面说,为了batch化请求视觉模型,提升吞吐。2)作者再2.5版本的论文中也提到,这样可以解耦视觉和LLM,必要时可以部署到不同的硬件(CPU、NPU)或者说不同时加载来降低内存的需要。
    3、继续进阶一下,这么大的batch,因为上面看到每一个最终输入视觉模型的图片的输入分辨率并不同(一张图的原图缩放和切片都不同,但是一张图的所有切片图是相同的,有点绕),但是batch推理的时候需要分辨率相同,那就需要padding,如何高效的padding?作者的做法就是把图片变换为h_des=patch_size,w_des = h_src * w_src / patch_size 的尺寸,也就是变成了一个长条,然后当成语言模型一样,只在一维填充,这样就降低了填充量,提高填充效率。如下图:
    

    相关源码

    占位符涉及到的源码主要在
    MiniCPM-V-2_6/processing_minicpmv.py 和 MiniCPM-V-2_6/image_processing_minicpmv.py两个文件,因为涉及函数确实比较多,建议可以看两个源码,比较容易看懂。
padding涉及到的源码片段如下
""" MiniCPM-V-2_6/image_processing_minicpmv.py """
def reshape_by_patch(self, image):
     """
     :param image: shape [3, H, W]
     :param patch_size:
     :return: [3, patch_size, HW/patch_size]
     """
     image = torch.from_numpy(image)
     patch_size = self.patch_size
     patches = torch.nn.functional.unfold(
         image,
         (patch_size, patch_size),
         stride=(patch_size, patch_size)
     )

     patches = patches.reshape(image.size(0), patch_size, patch_size, -1)
     patches = patches.permute(0, 1, 3, 2).reshape(image.size(0), patch_size, -1)
     return patches.numpy()

for slice_image in image_patches:
     new_images.append(self.reshape_by_patch(slice_image))
     tgt_sizes.append(np.array((slice_image.shape[1] // self.patch_size, slice_image.shape[2] // self.patch_size)))
new_images_list.append(new_images)
tgt_sizes_list.append(tgt_sizes)


"""MiniCPM-V-2_6/modeling_minicpmv.py"""
all_pixel_values = []
for pixel_values in pixel_values_list:
    img_cnt.append(len(pixel_values))
    all_pixel_values.extend([i.flatten(end_dim=1).permute(1, 0) for i in pixel_values])

# exist image
if all_pixel_values:
    tgt_sizes = [tgt_size for tgt_size in tgt_sizes if isinstance(tgt_size, torch.Tensor)]
    tgt_sizes = torch.vstack(tgt_sizes).type(torch.int32)

    max_patches = torch.max(tgt_sizes[:, 0] * tgt_sizes[:, 1])

    all_pixel_values = torch.nn.utils.rnn.pad_sequence(all_pixel_values, batch_first=True,
                                                       padding_value=0.0)
    B, L, _ = all_pixel_values.shape
    all_pixel_values = all_pixel_values.permute(0, 2, 1).reshape(B, 3, -1, L)

    patch_attn_mask = torch.zeros((B, 1, max_patches), dtype=torch.bool, device=device)
    for i in range(B):
        patch_attn_mask[i, 0, :tgt_sizes[i][0] * tgt_sizes[i][1]] = True

    vision_batch_size = self.config.vision_batch_size
    all_pixel_values = all_pixel_values.type(dtype)
    if B > vision_batch_size:
        hs = []
        for i in range(0, B, vision_batch_size):
            start_idx = i
            end_idx = i + vision_batch_size
            tmp_hs = self.vpm(all_pixel_values[start_idx:end_idx], patch_attention_mask=patch_attn_mask[start_idx:end_idx], tgt_sizes=tgt_sizes[start_idx:end_idx]).last_hidden_state
            hs.append(tmp_hs)
        vision_embedding = torch.cat(hs, dim=0)
    else:
        vision_embedding = self.vpm(all_pixel_values, patch_attention_mask=patch_attn_mask, tgt_sizes=tgt_sizes).last_hidden_state


""" MiniCPM-V-2_6/modeling_navit_siglip.py """
self.patch_embedding = nn.Conv2d(
   in_channels=config.num_channels,
   out_channels=self.embed_dim,
   kernel_size=self.patch_size,
   stride=self.patch_size,
   padding="valid",
)

batch_size = pixel_values.size(0)

patch_embeds = self.patch_embedding(pixel_values)
embeddings = patch_embeds.flatten(2).transpose(1, 2)

max_im_h, max_im_w = pixel_values.size(2), pixel_values.size(3)
max_nb_patches_h, max_nb_patches_w = max_im_h // self.patch_size, max_im_w // self.patch_size

    2、数据构成

    暂无。

    3、数据清洗

    暂无。

四、策略

    这部分因为还没有论文发布,目前的官方信息都还没有这部分内容。

    1、训练过程

    几个阶段训练、冻结哪个网络模块、训练超参。

    2、推理过程

    可以参见官方的推理教程,包含单图、多图、视频,这里需要注意视频的处理逻辑是每秒一帧,最高支持64帧,也就是差不多一分钟的视频理解:
     魔搭社区

五、结果

    1、多维度对比

    N边型战士   

    单图评测结果   

    多图评测结果   

    视频评测结果

    少样本评测结果

    典型case

    主要是OCR识别(格式化输出和计算里面的数字)、多图对话、代码debug、视频理解。参见官方README。    
    
    

    2、消融实验

    暂无

六、使用方法

官方给出了单图、多图、视频的实例code,可以参考: 魔搭社区

七、待解决

训练数据源,训练策略。

八、参考链接

官方魔塔社区README: 魔搭社区
部署指南: Docs 
训练指南: Docs
图片处理方式: Docs 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2044476.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HOW - 用腾讯蓝盾部署一个 Web 应用

目录 一、介绍二、项目、流水线和 Stage1. 项目&#xff08;Project&#xff09;2. 流水线&#xff08;Pipeline&#xff09;3. Stage&#xff08;阶段&#xff09;示例&#xff1a;配置一个简单的 CI/CD 流程的基本步骤总结 三、一条完整的流水线包括什么 Stage1. 源代码管理&…

29岁的服务员到网络安全工程师,大龄转行成功逆袭

大龄转行&#xff0c;一直在网络上备受争议。 转换职业赛道&#xff0c;从学习能力和试错成本角度来看&#xff0c;确实越早行动越有利&#xff0c;而大龄转行无疑伴随着较高的风险。 然而&#xff0c;大龄转行并非无路可走&#xff1a;古有苏老泉&#xff0c;年近三十方才开始…

数据结构之---堆(2)

一、出堆 出堆是指将堆顶数据出堆。出堆完成后要保证剩余数据还是满足原来堆的性质。所以我们把堆顶数据和堆底最后一个数据交换&#xff0c;取出新堆底数据&#xff0c;之后通过向下调整算法将剩下的数据重新排列成一个堆。 大堆经过出堆操作得出的数据是升序的 小堆经过出堆…

聚观早报 | 一加13配置细节曝光;谷歌首推人工智能手机

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 8月15日消息 一加13配置细节曝光 谷歌首推人工智能手机 MONA M03汽车即将上市 iPhone SE 4将升级8GB运行内存 R…

redis事件机制

redis服务器是一个由事件驱动(死循环)的程序&#xff0c;它总共就干两件事&#xff1a; 文件事件&#xff1a;利用I/O复用机制&#xff0c;监听Socket等文件描述符发生的事件&#xff0c;如网络请求时间事件&#xff1a;定时触发的事件&#xff0c;负责完成redis内部定时任务&…

MCU复位RAM会保持吗,如何实现复位时变量数据保持

在使用MCU时&#xff0c;通常大家默认MCU复位时RAM会被复位清零&#xff0c;那实际MCU复位时RAM是什么状态&#xff1f;如何让mcu复位时RAM保持不变呢&#xff1f; MCU复位有电源复位、Standby复位、内核复位、看门狗复位、引脚复位等。 其中内部会有掉电动作的复位有电源复位…

Linux知识复习第5期

目录 1、实验环境 2、日志存放 3、自定义日志采集路径 1、实验环境 hostnamectl hostname node1.zx.org # 设置主机名 vim /etc/hosts # 域名解析修改 hostname -I # 显示ip地址 2、日志存放 /var/log/messages 系统服务日志&#xff0c;常规…

快速MD5强碰撞生成器:fastcoll

问&#xff1a;可以制作两个具有相同哈希值的不同文件吗&#xff1f; 答&#xff1a;可以。 在密码学中&#xff0c;哈希函数将输入数据转换成固定长度的字符串。但由于输入的无限性和输出的固定性&#xff0c;不可避免地会有不同输入产生相同的哈希值&#xff0c;这就是碰撞。…

【ITK】图像分割算法:FastGrowCut详解

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享图像分割算法FastGrowCut的详细解析过程,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 目录…

单元训练09:定时器实现秒闪功能

蓝桥杯 小蜜蜂 单元训练09&#xff1a;定时器实现秒闪功能 #include "stc15f2k60s2.h"#define LED(x) \{ \P0 x; \P2 P2 & 0x1f | 0x80; \P2 & 0x1f; \}#define L1 0xFE; // 定义L1 …

线程锁(2)

线程的资源回收 int pthread_join(pthread_t thread, void **retval); 功能: 等待线程结束 参数: thread --- 线程tid retval --- 用来保存&#xff0c;退出状态值&#xff0c;所在空间的地址 返回值&#xff1a; 成功 0 失败 错误…

[240816] 【超级大牛】kovidgoyal:calibre 和 kitty 的作者 | Go 发布 1.23.0 版本

目录 【超级大牛】kovidgoyal&#xff1a;calibre 和 kitty 的作者calibrekitty Go 发布 1.23.0 版本Go 1.23 版本更新说明 【超级大牛】kovidgoyal&#xff1a;calibre 和 kitty 的作者 calibre calibre 是由 github.com/kovidgoyal 使用 C 和 Python 开发的跨平台电子书管理…

开源AI智能名片系统与高级机器学习技术的融合应用:重塑商务交流的未来

摘要&#xff1a;在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;技术&#xff0c;尤其是机器学习领域的快速发展&#xff0c;正深刻改变着各行各业的面貌。开源AI智能名片系统作为这一变革的先锋&#xff0c;通过集成并优化多种高级机器学习技术&#xf…

Word文档怎么批量加密保存

Word的文件批量设置密码是工作中比较常用的功能&#xff0c;它可以对文档进行保护&#xff0c;平时我们都是单个对文件进行加密&#xff0c;那么多个文件如何一键批量设置&#xff0c;下面通过一些方法&#xff0c;我们可以进行批量设置密码保存起来。 一、使用Word软件内置功能…

郑州市政协副主席翟政莅临中创算力开展重点企业实地调研

2024年8月13日&#xff0c;围绕“落实中央和省委、市委政协工作会议精神情况”郑州市政协副主席翟政一行莅临河南中创算力信息科技有限公司进行实地调研。中创算力董事长许伟威全程陪同。此次调研不仅是对中创算力在数字经济领域发展成就的肯定&#xff0c;更是对中创如何积极响…

《机器学习by周志华》学习笔记-决策树-03连续值与缺失值

1、连续值处理 到目前为止,我们在决策树01、02中仅讨论了基于离散属性来生成决策树,而现实任务中常会遇到连续属性,所以在本章的学习中,我们将会讨论如何在决策树学习中使用连续属性。 1.1、概念 取值范围是连续的实数值或者整数值的属性就是「连续属性」,与离散属性相对…

VSCode自动保存文件

off&#xff1a;关闭自动保存&#xff0c;这是默认选项afterDelay&#xff1a;会每隔若干秒保存一次OnFocusChange&#xff1a; 编辑器是去焦点时自动保存文件&#xff0c;比如说你打开了多个文件&#xff0c;你编辑好了A&#xff0c;然后切换到B文件&#xff0c;那么此时A文件…

信息流广告预估技术在美团外卖的实践

本文整理自美团技术沙龙第81期《美团在广告算法领域的探索及实践》(B站视频)。文章首先介绍了美团信息流广告业务以及预估技术的现状&#xff0c;然后重点分享了信息流广告预估在美团的具体实践&#xff0c;围绕决策路径、超长超宽建模和全还原建模等多个维度进行了分享&#x…

Taos 常用命令工作笔记(二)

最近测试创建一个涛思的数据库和一堆表进行测试&#xff0c;通过json配置文件配置字段的类型、名称等&#xff0c;程序通过解析json文件的配置&#xff0c;动态创建数据库的表。 其中表字段为驼峰结构的规则命名&#xff0c;创建表也是成功的&#xff0c;插入的测试数据也是成功…

实用性强的高安全涉密内网文件传输工具

在数字化浪潮中&#xff0c;数据安全和合规性成为企业和组织极为关注的焦点&#xff0c;特别是在处理敏感和机密信息时。高安全级别的内网文件传输工具因此变得至关重要&#xff0c;它们是保护数据不被未授权访问或泄露的关键技术之一。这类工具不仅需要满足严格的安全标准&…