根据 vllm / vllm-omni 源码,视频请求在 Omni 里的处理方式可以总结如下。
会。 只有在请求里带上 mm_processor_kwargs: { "use_audio_in_video": true } 时才会从视频里抽音频。
vllm_omni/entrypoints/chat_utils.py 的 extract_audio_from_video_async 里完成。vllm_omni/entrypoints/openai/serving_chat.py 的 _preprocess_chat 里,若 use_audio_in_video=True 且 multi_modal_data 有 video 没有 audio,会遍历 messages 里所有 video_url,对每个 URL 调用 extract_audio_from_video_async,然后把结果写回 engine_prompt["multi_modal_data"]["audio"]。librosa.load(file_path, sr=16000),即固定 16kHz 采样率。56:58:vllm-omni-0.16.0/vllm_omni/entrypoints/chat_utils.py展开代码audio_array, sample_rate = await asyncio.to_thread(_load_audio_sync, temp_video_file_path) return audio_array, sample_rate
num_frames 表示一共抽多少帧,是均匀抽帧。
fps 表示每秒抽多少帧。
最终取的是其中的最小值。
bash展开代码vllm serve "/mnt/cpfs/cache/xiedong/20260203-q3-omni-s2v6/20260203-q3-omni-s2v6-30k-30epochs" \
--omni --port 8091 \
--stage-configs-path "/mnt/cpfs/xiedong/video-understanding-omni-vllm/stage_config_thinker_4gpu.yaml" \
--trust-remote-code \
--media-io-kwargs '{"video":{"num_frames":64,"fps":2}}'
会交叠。 当 use_audio_in_video=True 时,音视频在 token 序列里按时间对齐、交错排列。
vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py 的 get_updates_use_audio_in_video 和 _compute_interleaved_positions。video_second_per_grid_t(默认 2.0 秒)和 position_id_per_seconds 把视频格子的时间与音频 token 的时间对齐,按时间戳比较决定下一个放 video token 还是 audio token,得到交错序列。check_interleaved_audio_video 和 merge_interleaved_embeddings(见 vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py)在 embedding 阶段按这个交错顺序合并。419:432:vllm-omni-0.16.0/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py展开代码while video_data_index < len(video_token_indices) and audio_data_index < len(audio_token_indices): if video_token_indices[video_data_index] <= audio_token_indices[audio_data_index]: updates += [video_token_id] video_data_index += 1 else: updates += [audio_token_id] audio_data_index += 1 ... updates += [audio_end_token_id] return updates
所以:是“按时间交叠”的 video/audio token 序列,不是先整段视频再整段音频。
qwen_omni_utils 里 resize 用的是谁? ms-swift 框架使用qwen_omni_utils,根据qwen_omni_utils的逻辑有:
fetch_video 里只用 token 数算像素上限,没有用任何 PIXELS 变量:
406:451:usr/local/lib/python3.12/dist-packages/qwen_omni_utils/v2_5/vision_process.py展开代码VIDEO_FRAME_MIN_PIXELS = VIDEO_MIN_TOKEN_NUM * image_factor * image_factor VIDEO_FRAME_MAX_PIXELS = VIDEO_MAX_TOKEN_NUM * image_factor * image_factor ... min_pixels = ele.get("min_pixels", VIDEO_FRAME_MIN_PIXELS) ... max_pixels = max(min(VIDEO_FRAME_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR), int(min_pixels * 1.05)) max_pixels_supposed = ele.get("max_pixels", max_pixels) ... resized_height, resized_width = smart_resize( height, width, factor=image_factor, min_pixels=min_pixels, max_pixels=max_pixels, )
也就是说:
image_factor 决定。所以:在训练框架里,真正控制每帧 resize 的是 VIDEO_MAX_TOKEN_NUM,VIDEO_MAX_PIXELS 在 qwen_omni_utils 里没有起作用。
VIDEO_MAX_PIXELS 只在qwen_vl_utils里有效,在qwen_omni_utils里没有使用VIDEO_MAX_PIXELS。
Qwen3-Omni 的 image_factor 是 32
从当前 Qwen3-Omni 官方模型/配置里读到的结果是:
bash展开代码vision_config.patch_size = 16 vision_config.spatial_merge_size = 2(processor 里是 image_processor.merge_size = 2) 所以: image_factor = patch_size × spatial_merge_size = 16 × 2 = 32
Qwen3-Omni 官方模型在 HuggingFace Transformers 里的配置。
processor:Qwen3OmniMoeProcessor.from_pretrained('Qwen/Qwen3-Omni-30B-A3B-Instruct') 的 image_processor.patch_size = 16,image_processor.merge_size = 2。 Config:同一模型的 thinker_config.vision_config 里 patch_size = 16,spatial_merge_size = 2。
因此:image_factor = patch_size × spatial_merge_size = 16 × 2 = 32,这是“Qwen3-Omni 本来要用 32”的依据(来自官方模型/processor 配置)。
已按「Qwen3-Omni 用 32」修好,修改说明如下。
文件:ms-swift-main/swift/template/templates/qwen.py
类:Qwen2_5OmniTemplate(同时负责 omni_v2_5 和 omni_v3)
replace_tag 开头为 omni_v3 准备 kwargs(约 634–637 行)omni_kwargs = {}。self.version == 'omni_v3' 时设置omni_kwargs['image_patch_size'] = self.processor.image_processor.patch_sizeomni_kwargs 传给 fetch_image 和 fetch_videofetch_image({'image': ...}) 改为fetch_image({'image': inputs.images[index]}, **omni_kwargs)。fetch_video({'video': video}) 改为fetch_video({'video': video}, **omni_kwargs)。这样:
processor.image_processor.patch_size(16)→ image_factor = 32。omni_kwargs 为空,仍用默认 14 → image_factor = 28,行为不变。634:653:ms-swift-main/swift/template/templates/qwen.py展开代码# Qwen3-Omni uses patch_size=16 (image_factor=32); Qwen2.5-Omni uses 14 (28) omni_kwargs = {} if self.version == 'omni_v3': omni_kwargs['image_patch_size'] = self.processor.image_processor.patch_size if media_type == 'image': inputs.images[index] = fetch_image({'image': inputs.images[index]}, **omni_kwargs) ... elif media_type == 'video': video = inputs.videos[index] _video = fetch_video({'video': video}, **omni_kwargs)
无需改环境变量或配置,训练 Qwen3-Omni 时会自动用 32;VIDEO_MAX_TOKEN_NUM 若按像素换算,应用 1024(32×32)作为每帧像素对应的 token 数。


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