PaperReading:VLM 训练逻辑
paperreading
本文字数:1.3k 字 | 阅读时长 ≈ 5 min

PaperReading:VLM 训练逻辑

paperreading
本文字数:1.3k 字 | 阅读时长 ≈ 5 min

这部分讲一下 VLM 训练的逻辑,例如常见的 LLaVA,Qwen-VL 等等,从图文提取,图文处理和训练逻辑等方面进行介绍,后面的介绍还是以 LLaVA-OneVision 中基于 Qwen 的 LLM 数据准备和处理流程进行介绍

整体上大致分为下面几个部分,为了简化流程,在介绍时只介绍影响训练的关键部分,一些对整体理解影响不大的细节部分会省略

图片处理

对话处理

给定如下对话,这是一个很常见的 vlm 单轮对话,以单图为例

[[{'from': 'human', 'value': '<image>\nAre all the texture details in the picture visible?'}, {'from': 'gpt', 'value': 'No'}]]

在对话中,<image> 代表图片的位置,human 代表用户输入,gpt 代表模型回答,这里一般处理的时候吧 <image> 标签放到句首(也可以不用管),问题就从 xxx\n<image> 变成 <image>\n xxx,接下来对话进行分词处理,这里我们用的是 Qwen2 Tokenizer,我们精简一下 Qwen2 的 Template 如下

{% for message in messages -%}
{{ '<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>\\n' -}}
{%- endfor %}
{%- if add_generation_prompt -%}
{{ '<|im_start|>assistant\\n' -}}
{%- endif -%}

把上面的内容应用 template 之后,结果如下,注意下面第一行有一个\n,为了看起来方便也换行了,实际上是 “system \n You”

<|im_start|>system \n   
You are a helpful assistant.<|im_end|> \n
<|im_start|>user \n
<image>\nAre all the texture details in the picture visible?<|im_end|> \n
<|im_start|>assistant \n
No<|im_end|> \n

在获取 input_idstarget_ids 的之前,我们需要做一下预备工作,即确定哪些 token 需要计算 loss,哪些不需要计算 loss,不计算 loss 的 token 要设置为 -100。但为了让模型学会对话边界与排版,["<|im_start|>", "<|im_end|>", "\n", "\n\n"] 这类结构 token 需要计算 loss(不设为 -100)。此外模型的回复也要计算 loss,这里把这些 tokens 对应的 id 要特意记录一下。下面我们展示一下清晰的结果,就是说,需要预测的,我们将其保留,不需要预测的,我们将其设置为 -100,最终结果如下,可以从下面内容和上面的内容一一对应,看到一个很清晰的结果。

<|im_start|>-100 \n
-100 -100 -100 -100 -100 -100<|im_end|> \n
<|im_start|>-100 \n
-200 \n -100 -100 -100 -100 -100 -100 -100 -100 -100<|im_end|> \n
<|im_start|>assistant \n
N<|im_end|> \n

可以看出,特殊字符 <|im_start|>, <|im_end|>, \n 都被保留下来了,此外还有 assistant 也被保留,最后就是 assistant 回答的 No 也被保留了,其他的都被设置为 -100 了。那么为什么要训练 <|im_start|>, <|im_end|>, \n 呢?

至于为什么要保留 assistant 这个词?可以让模型更确定何时进入“助手回复”阶段,格式更稳。但这是一个可选项,若只想训练 assistant 的“正文”,可以把 assistant 这个词本身也设成 -100,这是策略选择问题。

下面给一个相应的 tokenizer 解码的代码,方便用户很好的调试,输入就是 input_id,输出就是解码的每个字符,一一对应 debug 就可以很好的理解之前的内容

# input_id 是一维 list;负数(如 -100, -200)原样保留,其它解码为字符串
pieces = [
    i if (isinstance(i, int) and i < 0)
    else tokenizer.decode([int(i)], skip_special_tokens=False, clean_up_tokenization_spaces=False)
    for i in input_id
]

模型输入处理

图片的处理

文本的处理

再了解了上面的对话处理方式之后,我们接下来对模型输入进行处理。这里我们假设对图片已经处理完毕了,这里的 batch 我们假设为 2

从 dataset 中我们可以获得输入的 input_ids 和 labels,此外还有一个 attention_mask,下面是一个示例,其中 -200 代表图片的 token id,0 代表 padding 的位置,也可以用其他数字代表 padding 的位置(由 tokenizer 来决定),-100 代表不计算 loss 的位置

input_ids = [
    [101, 2009, -200, 1037, 3722, 102, 1001, 102, 0, 0],  # 样本 1:问题较短,被 pad 到长度 10
    [101, 2129, 2024, -200, 2000, 2424, 2023, 3185, 2054, 102]  # 样本 2:问题较长,正好长度 10
]
labels = [
    [-100, -100, -100, -100, -100, -100, 1001, 102, -100, -100],  # 样本 1:前 6 个 token 是问题,屏蔽为 -100,仅保留答案部分
    [-100, -100, -100, -100, -100, -100, 2023, 3185, 2054, 102]  # 样本 2:前 6 个是问题,后 4 个是答案
]
attention_mask = [
    [1, 1, 1, 1, 1, 1, 1, 1, 0, 0],   # 样本 1 末尾两个为 pad
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]    # 样本 2 全部有效
]
8月 26, 2025