[PaperReading] Qwen2.5-VL Technical Report

Qwen2.5-VL Technical Report

link
时间:25.02
单位:Qween
相关领域:多模态理解
作者相关工作:shuai bai
被引次数:1330
项目主页:https://github.com/QwenLM/Qwen2.5-VL

TL;DR

Qween2.5VL具备杰出的bounding boxes or points定位物体 以及 从表格与发票等文档中提取结构化信息的能力。能够处理动态分辨率图片、编码绝对时序信息,据有事件定位能力。该模型具备处理空间多尺度,时序动态信息能力。通过动态分辨率的ViT以及整合Window Attention,可以降低计算量并保持原生分辨率。Qween2.5VL擅长静态图片与文档理解,也可作为一个具有推理能力的交互视觉代理。

Method

  • LLM:复用Qween2.5 LLM,使用1D MRoPE来编码位置信息。
  • Vision Encoder: 2D RoPE + window attention,图像的长宽被Resize为28的倍数。
  • MLP-based Vision-Language Merger: Vision Encoder之后的视觉特征,先压缩相邻4个patch,再过两个MLP,之后与text embedding融合。
    image

Fast and Efficient Vision Encoder

Window Attention通过​​将二次复杂度降为线性复杂度​​来提升效率:对于一个有N个patch的输入,全局自注意力的计算复杂度为O(N²)。而Window Attention将其划分为M个不重叠的窗口(每个窗口有固定数量的K个patch),每个窗口内部独立计算自注意力,总复杂度变为O(M * K²)。由于K是固定值,复杂度最终与patch数量N呈线性关系(O(N)),从而大幅减少了计算量。

MRoPE对齐绝对时间信息

将时序位置ID与​​绝对时间戳(Absolute Time)​​ 进行对齐。不像Qween2-VL使用帧序号作为Temperal ID,本工作(Qween2.5-VL)将Temporal ID转换为帧所对应的​​真实时间戳​​(单位为秒)。例如,一个10 FPS的视频,其第5帧(帧号为4,从0开始计算)的Temporal ID不再是4,而是0.4(秒)。

Pre-Training

从1.2T提升至4T,一些数据源的介绍:

Interleaved Image-Text Data

步骤一:标准清洗方法参考

  1. 进行主体内容提取,去除广告和无关元素;
  2. 进行文本初步过滤,剔除低质量文本;
  3. 进行文档去重和图像下载,并应用尺寸、美学及安全过滤;
  4. 通过详细文本过滤和人工反馈机制进一步提升质量;
    image

步骤二:

  1. ​​Text-Only Quality(纯文本质量)​​
    采用与纯文本模型(Qwen2.5)对齐的过滤标准,包括:使用BERT类模型评估文本流畅性、语法正确性 以及 剔除广告与重复文本。
  2. ​​Image-Text Relevance(图文相关性)​​
    计算图像与相邻文本的CLIP相似度得分,保留高分样本。
  3. ​​Image-Text Complementarity(图文互补性)​​
    检测图像与文本的信息互补程度,例如,文本描述“制作项链的步骤”,图像展示项链的原材料以及项链成品效果(如何用模型检查暂未想到好办法)。
  4. ​​Information Density Balance(信息密度平衡)​​
  • ​​量化配比​​:控制图文数量比,避免单模态主导。
  • 内容密度检测​​:过滤低信息图像(如纯色背景)或冗余文本(重复描述)。

Grounding Data with Absolute Position Coordinates

  1. 公开数据集(Public Datasets)​​
    直接利用了现有的大规模目标检测、实例分割和指代理解数据集。这些数据集已包含图像、边界框坐标及对应的文本描述。
  2. 专有数据(Proprietary Data)​​
    团队内部收集和标注的数据,通常用于弥补公开数据在特定领域或场景下的不足。
  3. 合成数据(Synthetic Data)​​
  • 基础模型​​:利用现成的先进模型,如​​,Grounding DINO​(用于开放词汇检测)、​​SAM(用于精准分割)对海量无标注图像进行自动标注,生成初步的边界框/轮廓和粗略描述。
  • 数据增强技术​​:采用Copy-Paste Augmentation​​等技术,将物体实例从一个图像复制并粘贴到另一个背景图像中,从而低成本地创造大量新的、多样化的训练样本。

OCR Data

主要靠合成

Video Data

通过帧率下采样来动态控制FPS,同时每帧记录了 秒 或者 hour-minute-second-frame 格式的时间戳信息。

Agent Data

  • 数据来源与采集​: 原始数据获取(屏幕截图、Log日志、)​,场景多样性(移动端APP​​、通用网页)​
  • 使用方式:可以SFT也可以RL使用。训练数据集中已经包含了大量​​(截图,指令,正确动作)​​ 的三元组样本。模型的学习目标非常简单直接:​​给定截图和指令,预测出那个唯一正确的动作​​。这是一个标准的条件生成任务,使用交叉熵损失进行训练。
    image

训练流程

Pretrain

image

PostTrain

数据清洗Pipeline

阶段一:领域特定分类(Domain-Specific Categorization)​​
使用专门的分类模型Qwen2-VL-Instag(基于Qwen2-VL-72B)对问答对(QA pairs)进行分层分类。
QA对被组织成8个主要领域(如Coding、Planning等),并进一步细分为30个精细子类别(例如,Coding领域包括Code_Debugging、Code_Generation等)。这种分层结构允许针对每个领域和子类别的特点实施定制化的过滤策略,从而提升SFT数据集的质量和相关性。
​​阶段二:领域定制过滤(Domain-Tailored Filtering)​​
该阶段结合了基于规则和基于模型的过滤方法,以全面增强数据质量。针对不同领域(如,文档处理、光学字符识别OCR、视觉定位),采用独特的过滤策略:

  • 基于规则的过滤(Rule-Based Filtering)​​:使用预定义启发式规则去除低质量条目。具体包括:
    识别并移除重复模式,防止模型学习过程被扭曲。
    排除不完整、截断或格式错误的响应(常见于合成数据集和多模态上下文)。
    丢弃不相关或可能导致有害输出的查询和答案,以确保数据符合伦理准则和任务特定要求。
  • ​​基于模型的过滤(Model-Based Filtering)​​:利用基于Qwen2.5-VL系列训练的奖励模型对多模态QA对进行多维度评估:
    • 查询评估:基于复杂性和相关性,仅保留具有适当挑战性和上下文相关性的样本。
    • 答案评估:基于正确性、完整性、清晰度、查询相关性和帮助性进行评分。

Rejection Sampling for Enhanced Reasoning

  1. 生成多个候选 阶段

采样策略:采用​​多样性采样​​(如核采样、温度缩放),确保候选答案覆盖不同解题路径。

  1. 自我评估筛选 阶段
    ​​输入​​:问题 Q+ 候选答案 \({A_i}\) + ​​人工标注的高质量参考答案
    评估指令:

对比候选答案与参考样本,评估其逻辑严谨性、计算正确性和语言清晰度(1-5分)

  1. 将筛选后的 {Q, \(A_{selected}\)}加入训练集,替换原始SFT数据中的低质量样本。

Q&A

Q:Grounding与目标检测有什么关系?
image

CodeReading

Model

Qwen2_5_VLForConditionalGeneration

继承[Qwen2_5_VLPreTrainedModel、GenerationMixin]

Qwen2_5_VLForConditionalGeneration的核心代码
    def __init__(self, config):
        # 视觉Encoder,由patch_embed与ViT Transformer构成,最后有个Qwen2_5_VLPatchMerger
        self.visual = Qwen2_5_VisionTransformerPretrainedModel._from_config(config.vision_config)
        # LLM部分:
        # self.embed_tokens:nn.Embedding将input_ids转为input_embed
        # self.layers一系列Qwen2_5_VLDecoderLayer,Attention的核心实现在里面
        self.model = Qwen2_5_VLModel(config)
        self.vocab_size = config.vocab_size
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
    def forward(
        self,
        input_ids: Optional[torch.LongTensor] = None,
        attention_mask: Optional[torch.Tensor] = None,
        position_ids: Optional[torch.LongTensor] = None,
        past_key_values: Optional[List[torch.FloatTensor]] = None,
        inputs_embeds: Optional[torch.FloatTensor] = None,
        labels: Optional[torch.LongTensor] = None,
        use_cache: Optional[bool] = None,
        output_attentions: Optional[bool] = None,
        output_hidden_states: Optional[bool] = None,
        return_dict: Optional[bool] = None,
        pixel_values: Optional[torch.Tensor] = None,
        pixel_values_videos: Optional[torch.FloatTensor] = None,
        image_grid_thw: Optional[torch.LongTensor] = None,
        video_grid_thw: Optional[torch.LongTensor] = None,
        rope_deltas: Optional[torch.LongTensor] = None,
        cache_position: Optional[torch.LongTensor] = None,
        second_per_grid_ts: Optional[torch.Tensor] = None,
    ) -> Union[Tuple, Qwen2_5_VLCausalLMOutputWithPast]:
        # text通过self.model.embed_tokens抽出tokens
        inputs_embeds = self.model.embed_tokens(input_ids)
        # image通过self.visual抽出tokens
        # 两者通过下面mask融合成多模态tokens
        image_embeds = image_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
        outputs = self.model(
            input_ids=None,
            position_ids=position_ids,
            attention_mask=attention_mask,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            use_cache=use_cache,
            output_attentions=output_attentions,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
            cache_position=cache_position,
        )

        hidden_states = outputs[0]
        # 最后再过self.lm_head预测logits
        logits = self.lm_head(hidden_states)
GenerationMixin的核心代码

封装了所有与自回归生成(AR)相关的逻辑,将文本生成的核心方法通过Mixin注入到子类。

Mixin是一种设计模式(和多重继承类似,只不过mixin父类定义了与子类或其它父类不冲突的方法,将其注入到子类)。

核心是调用generate函数,代码简化如下:

        tokenizer = kwargs.pop("tokenizer", None)  # Pull this out first, we only use it for stopping criteria
        ...
        generation_config, model_kwargs = self._prepare_generation_config(
            generation_config, use_model_defaults, **kwargs
        )
        # 3. Define model inputs
        inputs_tensor, model_input_name, model_kwargs = self._prepare_model_inputs(
            inputs, generation_config.bos_token_id, model_kwargs
        )
        ...
            # 让模型永远有 mask 可用,且格式最优
            model_kwargs["attention_mask"] = self._prepare_attention_mask_for_generation(
                inputs_tensor, generation_config, model_kwargs
            )
        ...
            input_ids = inputs_tensor if model_input_name == "input_ids" else model_kwargs.pop("input_ids")
        # 提前把 KV 缓存的容器准备好,尺寸一步到位
        self._prepare_cache_for_generation(
            generation_config, model_kwargs, assistant_model, batch_size, max_cache_length, device
        )

        # 8. determine generation mode
        generation_mode = generation_config.get_generation_mode(assistant_model)

        # 9. prepare logits processors and stopping criteria
        # 把 temperature、top-k、top-p、重复惩罚等全部排好队,等着改 logits
        prepared_logits_processor = self._get_logits_processor(
            generation_config=generation_config,
            input_ids_seq_length=input_ids_length,
            encoder_input_ids=inputs_tensor,
            prefix_allowed_tokens_fn=prefix_allowed_tokens_fn,
            logits_processor=logits_processor,
            device=inputs_tensor.device,
            model_kwargs=model_kwargs,
            negative_prompt_ids=negative_prompt_ids,
            negative_prompt_attention_mask=negative_prompt_attention_mask,
        )
        # 告诉循环什么时候该 break
        prepared_stopping_criteria = self._get_stopping_criteria(
            generation_config=generation_config, stopping_criteria=stopping_criteria, tokenizer=tokenizer, **kwargs
        )
        # ... Qween-VL推理时生成方式为Greedy,训练时是并行计算“每个位置都看到正确答案的前缀,然后只预测下一个 token”
        elif generation_mode in (GenerationMode.SAMPLE, GenerationMode.GREEDY_SEARCH):
            # 11. expand input_ids with `num_return_sequences` additional sequences per batch
            input_ids, model_kwargs = self._expand_inputs_for_generation(
                input_ids=input_ids,
                expand_size=generation_config.num_return_sequences,
                is_encoder_decoder=self.config.is_encoder_decoder,
                **model_kwargs,
            )

            # 12. run sample (it degenerates to greedy search when `generation_config.do_sample=False`)
            result = self._sample(
                input_ids,
                logits_processor=prepared_logits_processor,
                stopping_criteria=prepared_stopping_criteria,
                generation_config=generation_config,
                synced_gpus=synced_gpus,
                streamer=streamer,
                **model_kwargs,
            )

Q&A

为什么Qween3的模型核心代码被放到了transformers的库里,而Qween3 github代码仓里面?
与论文中介绍的关键细节体现在哪里?
比如MRoPE、视觉Encoder与Text Encoder、Tokenizer用得什么、数据如何处理的,有没有数据增强?
LoRA是如何做的?
这里所用的flash attention2与普通的flash attention有什么区别?

Experiment

72B大模型相对于7B/3B小模型强了不少
image

保留了较强纯文本能力
image

比GPT4o/InternVL2.5等模型OCR能力更强
image

总结与思考

主要印象:

  1. 数据上做了较多工作
  2. Window Attention降低计算量同时支持了动态分辨率
  3. MRoPE编码绝对时间信息

相关链接

https://zhuanlan.zhihu.com/p/1927463592279671080
https://zhuanlan.zhihu.com/p/1917173869510373470

posted @ 2025-10-25 17:16  fariver  阅读(10)  评论(0)    收藏  举报