Add Phi-4 multimodal support#4648
Conversation
|
Thanks for your contribution! |
risemeup1111
left a comment
There was a problem hiding this comment.
已完成首轮检查,发现几处需要修复后再合入的问题,详情见行内评论。CI 目前仍有任务在运行,且 H20 集成测试出现失败/取消状态,建议作者后续一并确认最新 CI 结果。
| ("phi4_multimodal", "Phi4Multimodal"), | ||
| ("phi4_multimodal_audio", "Phi4MultimodalAudio"), | ||
| ("phi4_multimodal_vision", "Phi4MultimodalVision"), |
There was a problem hiding this comment.
优先级:P1
处理要求:请针对该评论修复并提交新的 commit。
这里把 AutoModel 的 model_type 映射到了 Phi4Multimodal / Phi4MultimodalAudio / Phi4MultimodalVision,但新增的 modeling.py 里没有这些类;顶层 paddleformers.transformers.__init__ 也把 Phi4MultimodalAudioPreTrainedModel、Phi4MultimodalVisionPreTrainedModel 等不存在的名字加入了 lazy import。_LazyAutoMapping 会按这里的名字执行 getattr,用户通过 AutoModel.from_config(...) 或顶层 import 访问这些符号时会直接失败,导致 PR 描述里的 Auto 注册不可用。
请把 Auto/lazy 注册统一到实际存在且可实例化的类名,或者补齐这些公开别名/包装类。修复形态可以类似:
# paddleformers/transformers/auto/configuration.py
("phi4_multimodal", "Phi4MultimodalModel")
# 如果 audio/vision 子配置也要暴露给 AutoModel,需要同时提供对应的 PretrainedModel 包装类;
# 否则不要把未定义的 Phi4MultimodalAudio / Phi4MultimodalVision 注册进 MODEL_NAMES_MAPPING。同时请同步清理或补齐 paddleformers/transformers/__init__.py 中 phi4_multimodal.modeling 的 import structure,确保其中每个名字都能从新模块实际导入。
There was a problem hiding this comment.
已在 bd71d2f 中修复。phi4_multimodal 现在映射到实际存在的 Phi4MultimodalModel;同时移除了不支持的 audio/vision 子配置 AutoModel 映射,并清理了顶层 lazy import 中不存在的类名。此外补充了 AutoModel 映射回归测试。
There was a problem hiding this comment.
已确认,bd71d2f1 中的修复和你描述一致:当前 MODEL_NAMES_MAPPING 只保留 phi4_multimodal -> Phi4MultimodalModel,audio/vision 子配置不再注册到 AutoModel,顶层 lazy import 里不存在的类名也已清理,并补充了对应回归测试。这个问题已在当前 head 复查通过。
| if cls != cls.base_model_class: | ||
| stmts.append("model.embed_tokens.weight -> lm_head.weight") |
There was a problem hiding this comment.
优先级:P1
处理要求:请针对该评论修复并提交新的 commit。
Phi4MultimodalConfig 默认 tie_word_embeddings=False,而原始 Phi-4 multimodal checkpoint 也包含独立的 lm_head.weight。这里无条件把 model.embed_tokens.weight 写到 lm_head.weight,直接加载原始 safetensors 时会用词表 embedding 覆盖输出头,导致 logits/生成结果错误。请按 tie_word_embeddings 分支加载:未 tie 时读取 checkpoint 里的 lm_head.weight。
| if cls != cls.base_model_class: | |
| stmts.append("model.embed_tokens.weight -> lm_head.weight") | |
| if cls != cls.base_model_class: | |
| if config.tie_word_embeddings: | |
| stmts.append("model.embed_tokens.weight -> lm_head.weight") | |
| else: | |
| stmts.append("lm_head.weight -> lm_head.weight") |
There was a problem hiding this comment.
已在 bd71d2f 中修复。现在会根据 tie_word_embeddings 选择权重映射:未绑定时加载 checkpoint 中独立的 lm_head.weight,绑定时才使用 model.embed_tokens.weight。同时补充了两种配置的回归测试。
|
|
||
|
|
||
| def adaptive_enc_mask(x_len, chunk_start_idx, left_window=0, right_window=0): | ||
| import torch |
There was a problem hiding this comment.
优先级:P1
处理要求:请针对该评论修复并提交新的 commit。
adaptive_enc_mask 在音频 forward 路径里执行 import torch,但 PaddleFormers 默认会在导入时屏蔽 torch/torchvision,并且运行依赖也不能假设 Paddle-only 环境安装了 PyTorch。音频输入会经过 _streaming_mask -> adaptive_enc_mask,因此这里会在 speech/vision-speech 路径上触发 ModuleNotFoundError,使新增音频能力不可用。
请用 Paddle 张量操作重写这段 mask 构造,不要在模型 forward 里依赖 PyTorch。可参考下面的实现形态:
def adaptive_enc_mask(x_len, chunk_start_idx, left_window=0, right_window=0):
chunk_start_idx_t = paddle.to_tensor(chunk_start_idx, dtype="int64")
start_pad = paddle.concat([paddle.zeros([1], dtype="int64"), chunk_start_idx_t])
end_pad = paddle.concat([chunk_start_idx_t, paddle.full([1], x_len, dtype="int64")])
seq_range = paddle.arange(x_len, dtype="int64").unsqueeze(-1)
idx = paddle.nonzero((seq_range < end_pad) & (seq_range >= start_pad))[:, 1]
seq_range_expand = paddle.arange(x_len, dtype="int64").unsqueeze(0).expand([x_len, x_len])
idx_left = paddle.clip(idx - left_window, min=0)
idx_right = paddle.clip(idx + right_window, max=len(chunk_start_idx))
boundary_left = paddle.gather(start_pad, idx_left)
boundary_right = paddle.gather(end_pad, idx_right)
return (seq_range_expand >= boundary_left.unsqueeze(-1)) & (seq_range_expand < boundary_right.unsqueeze(-1))There was a problem hiding this comment.
已在 bd71d2f 中修复。adaptive_enc_mask 已改为纯 Paddle 张量实现,音频 forward 路径不再依赖 PyTorch。同时补充了基础 chunk mask 和 left-window mask 的回归测试。
There was a problem hiding this comment.
已确认,bd71d2f1 中的修复和你描述一致:当前 adaptive_enc_mask 已改为 paddle.to_tensor / paddle.concat / paddle.nonzero / paddle.gather 等 Paddle 实现,音频 forward 路径不再依赖 torch。新增 chunk mask 与 left-window mask 回归测试也覆盖了这部分逻辑。这个问题已在当前 head 复查通过。
PaddleFormers Log Analysis
日志分析报告
失败的测试case: 根本原因分析: 修复建议:
🔄 每次 Re-run 后自动更新 |
risemeup1111
left a comment
There was a problem hiding this comment.
已复查新提交,之前的 P1 问题均已通过后续 commit 修复:Auto/lazy 注册已指向实际存在的类,lm_head AOA 映射已按 tie_word_embeddings 分支处理,音频 mask 构造也已移除 PyTorch 依赖。未发现新的需要阻塞合入的问题。
当前 CI 仍在运行中;我本地尝试运行新增的聚焦测试时,环境缺少 colorlog,因此测试结果请以后续线上 CI 为准。
PR 新增模型支持:Phi-4-multimodal
本 PR 新增
microsoft/Phi-4-multimodal-instruct支持。复现对齐对象为 Microsoft/HuggingFace remote-code 实现以及 ModelScope 上的原始 safetensors 权重。主要改动
paddleformers/transformers/phi4_multimodal/Phi4MultimodalConfigPhi4MultimodalModelPhi4MultimodalForCausalLMPhi4MultimodalForCausalLMPipePhi4MultimodalProcessorPhi4MultimodalImageProcessorPhi4MultimodalFeatureExtractorpaddleformers.transformersAutoConfigAutoModelAutoModelForCausalLMAutoProcessorAutoImageProcessorAutoFeatureExtractorphi4_multimodaltemplate / MM pluginmm_collate_fn支持image_pixel_values、image_sizes、image_attention_maskmm_collate_fn支持audio_input_features、audio_attention_mask、audio_embed_sizesinput_mode,用于区分 text / vision / speech / vision-speech adapter routePhi4MultimodalPreTrainedModel._gen_aoa_config中实现 HF/ModelScope safetensors 到 PaddleFormers 权重的自动转换规则phi4mmconfig 转 PaddlePhi4MultimodalConfigdocs/zh/model_capability.md能力矩阵前向对齐验证
模型:
microsoft/Phi-4-multimodal-instruct环境设置:
FLAGS_use_accuracy_compatible_kernel=1、FLAGS_cudnn_deterministic=1、NVIDIA_TF32_OVERRIDE=0torch.backends.cudnn.deterministic=True、torch.backends.cudnn.allow_tf32=Falsefp32 最终 logits:
text bf16 补充:
生成对齐
Text-only 生成:
[[100, 101]][220, 17, 87, 659, 220, 18, 88, 314, 220, 899][[100, 101]][220, 17, 87, 659, 220, 18, 88, 314, 220, 899]3.6239624e-052.6317712e-06Vision 生成:
[200, 200, 200, 200, 200, 200, 200, 200, 200, 200]Audio 说明:
Conv1D算子差异。训练验证
1. 文本
使用 GSM8K 做 BF16 full-SFT,Paddle 侧使用 sharding stage3,Torch 侧使用 ms-swift ZeRO-3。
共同设置:
global batch size = 4learning_rate = 1e-5warmup_steps = 0weight_decay = 0loss曲线如下:

2. 视觉多模态
使用小多模态 SFT 数据做 BF16 full-SFT,Paddle 侧使用 sharding stage3,Torch 侧使用 ms-swift ZeRO-3。
loss曲线如下:
