[PaliGemma2]: add PaliGemma2 multimodal model support#4770
Conversation
Add PaliGemma2 modeling, processor registration, automatic checkpoint class resolution, and validation coverage for multimodal inference and training.
5714acc to
5a75bc8
Compare
risemeup1111
left a comment
There was a problem hiding this comment.
已复查当前提交,详细问题已放在行级评论里。当前还有会影响 PaliGemma2 初始化语义、权重绑定、attention mask 处理和多模态 logits 的问题;其中 P1 项需要修复后再推送新的 commit,P2 项请按评论回复或调整。
| self.weight = paddle.create_parameter( | ||
| shape=[dim], | ||
| dtype="float32", | ||
| default_initializer=nn.initializer.Constant(1.0), |
There was a problem hiding this comment.
优先级:P1
处理要求:请针对该评论修复并提交新的 commit。
Gemma2RMSNorm.forward() 使用的是 output * (1.0 + self.weight) 形式,因此这里的参数初始化应表示 0 偏移量。当前初始化为 1.0 会让从 config 初始化的模型、或任何未加载到 checkpoint 的 norm 权重默认缩放变成 2 倍,和 Gemma 系列 checkpoint 语义不一致;同仓库 Gemma3RMSNorm 在相同公式下也是 Constant(0.0)。请改为 0.0。
| default_initializer=nn.initializer.Constant(1.0), | |
| default_initializer=nn.initializer.Constant(0.0), |
| if attention_mask is None: | ||
| attention_mask = paddle.triu( | ||
| paddle.full((seq_len, seq_len), float("-inf")), | ||
| diagonal=1, | ||
| ) |
There was a problem hiding this comment.
优先级:P1
处理要求:请针对该评论修复并提交新的 commit。
PaliGemma2ForCausalLM.forward(..., attention_mask=...) 会把常规二维 [B, S] padding mask 传到这里;当前只在 attention_mask is None 时创建 causal mask,非空时会把 [B, S] 原样传给 Gemma2Attention,再和 [B, H, S, S] 的 attention logits 相加。常规 B != S 会广播失败,B == S 时语义也不是 additive causal/padding mask。请始终构造 4D causal mask,并在有 padding mask 时叠加进去。
| if attention_mask is None: | |
| attention_mask = paddle.triu( | |
| paddle.full((seq_len, seq_len), float("-inf")), | |
| diagonal=1, | |
| ) | |
| attention_mask_2d = attention_mask | |
| attention_mask = paddle.triu( | |
| paddle.full([seq_len, seq_len], float("-inf"), dtype="float32"), | |
| diagonal=1, | |
| ).unsqueeze([0, 1]).expand([bsz, 1, seq_len, seq_len]) | |
| if attention_mask_2d is not None: | |
| padding_mask = paddle.where( | |
| attention_mask_2d.unsqueeze([1, 2]).astype("bool"), | |
| paddle.zeros([bsz, 1, 1, seq_len], dtype="float32"), | |
| paddle.full([bsz, 1, 1, seq_len], float("-inf"), dtype="float32"), | |
| ) | |
| attention_mask = attention_mask + padding_mask |
| self.vision_tower = SiglipVisionTransformer(config.vision_config) | ||
| self.multi_modal_projector = PaliGemma2MultiModalProjector(config) | ||
| self.language_model = Gemma2Model(config.text_config) | ||
| self.lm_head = nn.Linear( |
There was a problem hiding this comment.
优先级:P1
处理要求:请针对该评论修复并提交新的 commit。
PaliGemma2Config.tie_word_embeddings 默认为 True,PR 描述也说明 lm_head.weight 与 embedding tied weight 已处理;但这里新建 lm_head 后没有调用 tie_weights(),条件生成类也没有实现 get_input_embeddings(),因此 PretrainedModel.tie_weights() 找不到 language_model.embed_tokens。从 config 初始化、resize token embeddings 或部分加载权重时,输出头会和输入 embedding 分离。请按现有 VL 模型模式补齐权重绑定,并同步覆盖下面的 PaliGemma2ForCausalLM。
参考实现形态:
class PaliGemma2ForConditionalGeneration(PaliGemma2PreTrainedModel):
_tied_weights_keys = {"lm_head.weight": "language_model.embed_tokens.weight"}
def __init__(self, config):
...
self.lm_head = nn.Linear(...)
self.tie_weights()
def get_input_embeddings(self):
return self.language_model.embed_tokens
def set_input_embeddings(self, value):
self.language_model.embed_tokens = value
class PaliGemma2ForCausalLM(PaliGemma2PreTrainedModel):
_tied_weights_keys = ["lm_head.weight"]
def __init__(self, config):
...
self.lm_head = nn.Linear(...)
self.tie_weights()| hidden_states = self.language_model.norm(hidden_states) | ||
|
|
||
| # Compute logits | ||
| logits = self.lm_head(hidden_states) |
There was a problem hiding this comment.
优先级:P1
处理要求:请针对该评论修复并提交新的 commit。
条件生成路径在 lm_head 后直接返回 logits,遗漏了 Gemma2 的 final_logit_softcapping。同文件的纯文本 PaliGemma2ForCausalLM 已在 logits 后应用该 softcap;这里不做会让多模态 forward/generation 在大 logits 时和 Gemma2/HF 语义不一致,默认 final_logit_softcapping=30.0 时尤其明显。请在条件生成路径也应用同一逻辑。
| logits = self.lm_head(hidden_states) | |
| logits = self.lm_head(hidden_states) | |
| if self.config.text_config.final_logit_softcapping is not None: | |
| logits = logits / self.config.text_config.final_logit_softcapping | |
| logits = paddle.tanh(logits) * self.config.text_config.final_logit_softcapping |
| image_token_mask = (input_ids == self.config.image_token_index) | ||
| num_image_tokens = image_features.shape[1] | ||
|
|
||
| if int(image_token_mask.astype("int64").sum().item()) != bsz * num_image_tokens: |
There was a problem hiding this comment.
优先级:P2
处理要求:请针对该评论进行回复(同意并已修改请回复 Done,不同意请说明理由)。
PaliGemmaProcessor._normalize_images() 支持嵌套 image batch,并且会为同一个 prompt 的多张图插入 image_seq_length * len(sample_images) 个 <image> token;但模型这里按 bsz * num_image_tokens 校验,并在下面用 image_features[batch_idx] 注入,实际只支持每条样本一张图。单条 prompt 搭配两张图时,processor 会生成 2 * num_image_tokens 个 image token,而这里会拿它和 1 * num_image_tokens 比较后报错。请明确只支持单图并在 processor 中拒绝嵌套多图,或让模型按扁平化后的 image_features 逐张注入。
如果要支持多图,修复形态可以类似:
feature_idx = 0
for batch_idx in range(bsz):
positions = paddle.nonzero(image_token_mask[batch_idx]).flatten()
if len(positions) % num_image_tokens != 0:
raise ValueError("Image features and image tokens do not match")
for offset in range(0, len(positions), num_image_tokens):
inputs_embeds[batch_idx, positions[offset : offset + num_image_tokens]] = image_features[feature_idx]
feature_idx += 1
if feature_idx != image_features.shape[0]:
raise ValueError("Image features and image tokens do not match")There was a problem hiding this comment.
Done。当前 PaliGemma2 实现明确仅支持每个 prompt 输入一张图。
已在 Processor 侧于图像预处理前拒绝嵌套多图 batch,保证 Processor 生成的 image token 数量与模型侧单图 image feature 注入逻辑一致,避免多图场景下出现 token / feature 数量不匹配。
多图支持需要额外定义扁平化图片顺序、每个样本图片数量、placeholder 对应关系,并使用支持多图的 checkpoint 完成端到端前向和生成对齐验证。当前 paligemma2-3b-pt-448 的验收仅覆盖单图,因此本 PR 暂不扩大到未验证的多图语义。
There was a problem hiding this comment.
这个处理方向可以接受:当前 PR 明确收敛为单图语义,并已在 processor 侧拒绝嵌套多图输入,避免和模型侧单图 feature 注入逻辑不一致。该 P2 事项我这里视为已解决。
Align normalization, masking, tied embeddings, logits softcapping, and the supported single-image processor contract with PaliGemma2 behavior.
risemeup1111
left a comment
There was a problem hiding this comment.
已复查新提交,之前的 P1 问题在代码层面已经修复:RMSNorm 初始化、纯文本 attention mask、权重绑定和条件生成 logits softcap 都已补齐,并增加了对应测试。没有新增行级问题。
剩余事项:原 P2 多图处理意见已改为在 processor 侧拒绝嵌套多图,代码方向可接受;但该线程还缺少作者回复。请在对应线程回复 Done 或说明理由后再继续审批。
risemeup1111
left a comment
There was a problem hiding this comment.
已复查当前提交和最新回复,之前提出的问题均已处理:P1 项已通过新提交修复,P2 多图范围说明也合理,当前实现明确收敛为单图输入并在 processor 侧提前拒绝未验证的多图输入。未发现需要继续阻塞合入的问题。
|
Thanks for your contribution! |
Resolve AutoConfig and AutoModel registrations by retaining both upstream models and PaliGemma2 support.
risemeup1111
left a comment
There was a problem hiding this comment.
已复查当前提交,之前提出的 PaliGemma2 模型语义问题仍保持已修复状态,P2 多图范围说明也仍可接受。本次新的阻塞点来自 CI lint,需要先修复后再继续审批。
优先级:P0
非行级:当前是 CI lint 失败,涉及 formatter、import 排序、copyright hook 和多个文件的 flake8 报错,无法安全用单个连续 diff 行级 suggestion 表达。
处理要求:请修复 lint 并提交新的 commit。
证据:Lintjob 在make lint阶段失败;black会重排paddleformers/transformers/paligemma2/processor.py、tests/transformers/paligemma2/test_modeling.py、paddleformers/transformers/auto/image_processing.py、paddleformers/transformers/paligemma2/modeling.py;isort会修改 PaliGemma2 相关 import;flake8报modeling.py:24 F401 'math' imported but unused、modeling.py:71 F841 local variable 'batch_size' is assigned to but never used、test_modeling.py:31/35 F401 PaliGemma2PreTrainedModel imported but unused,并且copyright_checker也修改了文件。请删除未使用符号并运行仓库 lint/pre-commit 格式化后推送。
Apply repository pre-commit formatting and remove unused imports and variables required by lint.
risemeup1111
left a comment
There was a problem hiding this comment.
已复查最新提交,之前的 PaliGemma2 模型语义问题仍保持已解决状态;这次格式化修复已处理大部分 lint 问题,但当前 Lint 仍因一个 black 格式化差异失败。剩余阻塞点已放在行级评论里,请修复后提交新的 commit。
| raise initial_exception | ||
|
|
||
| image_processor_type = config_dict.get("image_processor_type", None) | ||
| if image_processor_type == "SiglipImageProcessor" and config_dict.get("processor_class") == "PaliGemmaProcessor": |
There was a problem hiding this comment.
优先级:P0
处理要求:请针对该评论修复并提交新的 commit。
当前最新 Lint job 仍失败,原因已经收敛到 black 会重排 paddleformers/transformers/auto/image_processing.py。日志显示其余 isort、flake8、copyright_checker 已通过,但 black 修改了这个文件后 make lint 退出失败。请按 black 格式拆分这个条件,或本地运行 make lint/pre-commit 后提交格式化结果。
| if image_processor_type == "SiglipImageProcessor" and config_dict.get("processor_class") == "PaliGemmaProcessor": | |
| if ( | |
| image_processor_type == "SiglipImageProcessor" | |
| and config_dict.get("processor_class") == "PaliGemmaProcessor" | |
| ): |
Apply the repository Black formatting to the AutoImageProcessor registration added by PaliGemma2.
risemeup1111
left a comment
There was a problem hiding this comment.
已复查最新提交,上一轮 black 格式化阻塞点已通过新的 commit 修复,Lint 也已通过。之前提出的 PaliGemma2 模型语义问题和多图输入范围问题仍保持已解决状态。当前未发现需要继续阻塞合入的问题;其余仍在运行的 CI 请以最终结果为准。
PaddleFormers Log Analysis
日志分析报告
失败的测试case: 根本原因分析: 修复建议:
🔄 每次 Re-run 后自动更新 |
Verify AutoProcessor forwards the requested Hugging Face download source independently of the CI environment default.
|
risemeup1111
left a comment
There was a problem hiding this comment.
已复查本次测试修复,processor hub 默认值导致的单测失败点已通过显式 download_hub="huggingface" 固定下来,未发现新的阻塞问题。代码层面可以继续推进;仍请以最终 CI 结果为准。
|
/re-run all-failed |
本 PR 完成 Google PaliGemma2-3B-PT-448 从 Hugging Face Transformers 到 PaddleFormers 的迁移,包含多模态模型组网、HF checkpoint 自动注册、图文 Processor、前向精度、生成和文本训练 loss 对齐验证。
1. 模型实现
google/paligemma2-3b-pt-448PaliGemma2ForConditionalGeneration;2. 标准接口支持
AutoConfig.from_pretrained()可识别原始 HF checkpoint 的:model_type: "paligemma"PaliGemma2Config。AutoModelForConditionalGeneration可根据:architectures: ["PaliGemmaForConditionalGeneration"]PaliGemma2ForConditionalGeneration。AutoProcessor.from_pretrained()可识别:processor_class: "PaliGemmaProcessor";SiglipImageProcessor。input_idsattention_masktoken_type_idspixel_values说明:原始 HF safetensors 需要先转换为 Paddle 权重后再进行模型权重加载;本 PR 不包含转换后的权重文件。
3. 权重转换
使用官方 PaliGemma2 3B 权重进行转换验证:
727 / 72700lm_head.weight与 embedding tied weight 已正确处理。0。4. 前向精度对齐
使用相同 FP32 输入,对比 Hugging Face Transformers 与 PaddleFormers logits。
5.372047e-068.201599e-053.489878e-059.927750e-042915 / 2915验收阈值为
mean abs logits diff < 1e-2,纯文本和多模态前向均通过。多模态验证覆盖完整路径:
5. 模型生成对齐
使用同一转换权重、FP32、greedy decoding 对比新增 token。
纯文本生成
PyTorch 与 Paddle 前 10 个新增 token 完全一致:
结论:
Match: True。多模态生成
输入为同一固定图像和 prompt:
新增 token:
结论:
Match: True。说明:当前 generation 使用正确的完整序列前向路径,尚未实现 KV cache;本 PR 验证功能正确性,不声明缓存生成性能。
6. 纯文本训练 loss 对齐
使用 GSM8K 官方训练集进行纯文本训练对比。
两端保持以下条件一致:
-100ignore mask;token_type_ids;1;512;2e-5;23;Paddle 与 PyTorch loss 差异:
300-0.003329300.000811760.022474410.17506409结论:两端纯文本训练 loss 曲线整体接近;由于 BF16 GEMM 与 AdamW 数值实现路径存在跨框架差异,不宣称逐 step loss 完全一致。
7. 多模态训练说明
本 PR 已完成并通过:
本 PR 未声称完成真实图文训练集上的多模态训练 loss 曲线对齐。当前训练 loss 验证范围是 GSM8K 纯文本训练,用于验证 PaliGemma2 的文本训练、causal loss、label shift、mask、backward 和 AdamW 更新链路。
多模态训练集、图文 SFT 模板和端到端图文训练 loss 对齐可作为后续 CE / 扩展验证工作。
8. 单元测试
新增测试覆盖:
-100ignore label;paligemma的 AutoConfig / AutoModel 类解析;执行:
结果:
git diff --check已通过。9. 提交范围
本 PR 仅包含: