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

PaperReading:VLM 训练逻辑

paperreading
本文字数:1k 字 | 阅读时长 ≈ 4 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
]
8月 26, 2025