VLLM 部署 Qwen2VL 后,客户端用 OpenAI 兼容格式请求时,图片 token 插入位置的处理机制
2025-07-19
深度学习
00

目录

VLLM 部署 Qwen2VL 后,客户端用 OpenAI 兼容格式请求时,图片 token 插入位置的处理机制
1. 核心处理流程
2. Qwen2VL 的 Prompt 更新策略
3. Token 替换机制
3.1 替换策略
3.2 位置匹配
4. 具体处理步骤
4.1 Token 匹配查找
4.2 Token 替换执行
5. OpenAI 兼容格式的处理
5.1 输入格式
5.2 VLLM 处理流程
6. 关键特点
6.1 位置精确性
6.2 动态计算
6.3 顺序保证
7. 总结

VLLM 部署 Qwen2VL 后,客户端用 OpenAI 兼容格式请求时,图片 token 插入位置的处理机制

1. 核心处理流程

VLLM 处理多模态请求的核心流程在 BaseMultiModalProcessor.apply() 方法中(第 1810-1870 行):

python
展开代码
def apply( self, prompt: Union[str, list[int]], mm_data: MultiModalDataDict, hf_processor_mm_kwargs: Mapping[str, object], tokenization_kwargs: Optional[Mapping[str, object]] = None, return_mm_hashes: bool = False, ) -> MultiModalInputs: """ 处理多模态输入的主要步骤: 1. 对提示文本和多模态数据一起应用 HF Processor 2. 在 token IDs 中查找并更新序列,用占位符 token 替换 3. 从处理后的 token IDs 中提取占位符 token 的信息 """

2. Qwen2VL 的 Prompt 更新策略

Qwen2VLMultiModalProcessor._get_prompt_updates() 方法中(第 1040-1075 行):

python
展开代码
def _get_prompt_updates( self, mm_items: MultiModalDataItems, hf_processor_mm_kwargs: Mapping[str, Any], out_mm_kwargs: MultiModalKwargs, ) -> Sequence[PromptUpdate]: hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs) image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs) tokenizer = self.info.get_tokenizer() vocab = tokenizer.get_vocab() placeholder = { "image": vocab[hf_processor.image_token], # <|image_pad|> 的 token ID "video": vocab[hf_processor.video_token], # <|video_pad|> 的 token ID } merge_length = image_processor.merge_size**2 def get_replacement_qwen2vl(item_idx: int, modality: str): grid_thw = out_mm_kwargs[f"{modality}_grid_thw"][item_idx] assert isinstance(grid_thw, torch.Tensor) # 计算每个图片/视频需要的 token 数量 num_tokens = int(grid_thw.prod()) // merge_length return [placeholder[modality]] * num_tokens return [ PromptReplacement( modality=modality, target=[placeholder[modality]], # 要替换的目标 token replacement=partial(get_replacement_qwen2vl, modality=modality), ) for modality in ("image", "video") ]

3. Token 替换机制

VLLM 使用 PromptReplacement 策略来处理图片 token 的插入:

3.1 替换策略

  • 目标:将输入中的单个 <|image_pad|> token 替换为多个相同的 token
  • 数量计算:根据图片的 grid_thw(网格尺寸)和 merge_size 计算需要的 token 数量
  • 公式num_tokens = (grid_t * grid_h * grid_w) // merge_length

3.2 位置匹配

_apply_prompt_updates() 方法中(第 1654-1725 行):

python
展开代码
def _apply_prompt_updates( self, token_ids: list[int], mm_prompt_updates: Mapping[str, Sequence[BoundPromptUpdate]], mm_item_counts: Mapping[str, int], ) -> tuple[list[int], str, Mapping[str, list[PlaceholderFeaturesInfo]]]: tokenizer = self.info.get_tokenizer() # 1. 查找 token 匹配 mm_token_matches = { modality: find_token_matches(token_ids, updates) for modality, updates in mm_prompt_updates.items() } # 2. 应用 token 匹配 if all(mm_match_counts.get(modality, 0) >= item_count for modality, item_count in mm_item_counts.items()): token_ids = self._apply_token_matches( token_ids, mm_token_matches, mm_item_counts, )

4. 具体处理步骤

4.1 Token 匹配查找

find_token_matches() 函数中(第 669-694 行):

python
展开代码
def find_token_matches( prompt: list[int], prompt_updates: Sequence[BoundPromptUpdate], ) -> Sequence[PromptTargetMatch]: """在 prompt 中查找所有匹配的 token 序列""" matches = [] for update in prompt_updates: target = update.target if isinstance(target, _BoundPromptSequence): target_ids = target.token_ids for match in iter_token_matches(prompt, target_ids): matches.append(_PromptTargetTokenMatch( modality=update.modality, start_idx=match.start_idx, end_idx=match.end_idx, )) return matches

4.2 Token 替换执行

apply_token_matches() 函数中(第 794-803 行):

python
展开代码
def apply_token_matches( prompt: list[int], mm_matches: Mapping[str, Sequence[PromptTargetMatch]], mm_item_counts: Mapping[str, int], ) -> list[int]: """应用 mm_matches 中的更新到 prompt""" if not mm_matches: return prompt token_id_seqs = _apply_matches(prompt, mm_matches, mm_item_counts) return flatten_2d_lists(token_id_seqs)

5. OpenAI 兼容格式的处理

5.1 输入格式

客户端发送的 OpenAI 兼容格式:

json
展开代码
{ "messages": [ { "role": "user", "content": [ {"type": "text", "text": "请描述这张图片:"}, {"type": "image", "image": "base64_encoded_image"}, {"type": "text", "text": "请详细说明。"} ] } ] }

5.2 VLLM 处理流程

  1. 解析多模态数据:从 content 数组中提取图片数据
  2. 构建 prompt:将文本部分拼接成完整的 prompt 字符串
  3. Token 化:将 prompt 转换为 token IDs
  4. 查找占位符:在 token IDs 中查找 <|image_pad|> token
  5. 替换占位符:将单个 <|image_pad|> token 替换为多个相同的 token
  6. 特征插入:将图片特征插入到对应的 token 位置

6. 关键特点

6.1 位置精确性

  • VLLM 严格按照 <|image_pad|> token 在文本中的位置进行替换
  • 多个图片按 token 出现的顺序依次处理
  • 替换后的 token 数量与图片特征维度完全匹配

6.2 动态计算

  • 每个图片的 token 数量根据其分辨率动态计算
  • 公式:num_tokens = (grid_t * grid_h * grid_w) // merge_length
  • 不同尺寸的图片会有不同数量的 token

6.3 顺序保证

  • 图片 token 的插入顺序与输入中 <|image_pad|> token 的出现顺序一致
  • 确保图片特征与 token 位置的一一对应关系

7. 总结

VLLM 在处理 Qwen2VL 的多模态请求时:

  1. 不固定位置:图片 token 可以出现在文本的任何位置
  2. 精确替换:将输入中的 <|image_pad|> token 替换为相应数量的相同 token
  3. 动态计算:根据图片分辨率计算需要的 token 数量
  4. 顺序匹配:严格按照 token 在文本中的出现顺序处理多个图片
  5. 特征对齐:确保图片特征与替换后的 token 位置完全对齐

这种设计使得 VLLM 能够灵活处理各种复杂的多模态输入场景,同时保持与训练时的一致性。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:Dong

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!