Skip to content

[PaliGemma2]: add PaliGemma2 multimodal model support#4770

Open
cf-icehzgzh wants to merge 6 commits into
PaddlePaddle:developfrom
cf-icehzgzh:feat/paligemma2
Open

[PaliGemma2]: add PaliGemma2 multimodal model support#4770
cf-icehzgzh wants to merge 6 commits into
PaddlePaddle:developfrom
cf-icehzgzh:feat/paligemma2

Conversation

@cf-icehzgzh

Copy link
Copy Markdown
Contributor

本 PR 完成 Google PaliGemma2-3B-PT-448 从 Hugging Face Transformers 到 PaddleFormers 的迁移,包含多模态模型组网、HF checkpoint 自动注册、图文 Processor、前向精度、生成和文本训练 loss 对齐验证。

1. 模型实现

  • 模型:google/paligemma2-3b-pt-448
  • 架构:SigLIP Vision Encoder + Multi-Modal Projector + Gemma2 Text Decoder
  • 支持:
    • 图像 + 文本多模态前向;
    • 纯文本前向;
    • PaliGemma2ForConditionalGeneration
    • greedy generation;
    • 训练 forward、shifted causal loss 和 backward。

2. 标准接口支持

  • AutoConfig.from_pretrained() 可识别原始 HF checkpoint 的:
    • model_type: "paligemma"
    • 并解析为 PaliGemma2Config
  • AutoModelForConditionalGeneration 可根据:
    • architectures: ["PaliGemmaForConditionalGeneration"]
    • 解析为 PaliGemma2ForConditionalGeneration
  • AutoProcessor.from_pretrained() 可识别:
    • processor_class: "PaliGemmaProcessor"
    • SiglipImageProcessor
  • PaliGemmaProcessor 输出:
    • input_ids
    • attention_mask
    • token_type_ids
    • pixel_values
  • 单张 448×448 图像会展开为 1024 个 image token。

说明:原始 HF safetensors 需要先转换为 Paddle 权重后再进行模型权重加载;本 PR 不包含转换后的权重文件。

3. 权重转换

使用官方 PaliGemma2 3B 权重进行转换验证:

  • 权重键匹配:727 / 727
  • 缺失键:0
  • 意外键:0
  • lm_head.weight 与 embedding tied weight 已正确处理。
  • 转换权重数值最大误差:0

4. 前向精度对齐

使用相同 FP32 输入,对比 Hugging Face Transformers 与 PaddleFormers logits。

场景 指标 结果
纯文本前向 mean abs diff 5.372047e-06
纯文本前向 max abs diff 8.201599e-05
图像 + 文本完整前向末尾 20 token mean abs diff 3.489878e-05
图像 + 文本完整前向末尾 20 token max abs diff 9.927750e-04
多模态首个生成位置 argmax PyTorch / Paddle 2915 / 2915

验收阈值为 mean abs logits diff < 1e-2,纯文本和多模态前向均通过。

多模态验证覆盖完整路径:

image
  -> SigLIP vision tower
  -> multimodal projector
  -> 1024 image tokens 注入
  -> Gemma2 decoder
  -> logits

5. 模型生成对齐

使用同一转换权重、FP32、greedy decoding 对比新增 token。

纯文本生成

PyTorch 与 Paddle 前 10 个新增 token 完全一致:

[235248, 235284, 235336, 235248, 235284,
 235336, 235248, 235284, 235336, 235248]

结论:Match: True

多模态生成

输入为同一固定图像和 prompt:

What is in this image?

新增 token:

PyTorch: [2915, 235265, 1]
Paddle:  [2915, 235265, 1]

结论:Match: True

说明:当前 generation 使用正确的完整序列前向路径,尚未实现 KV cache;本 PR 验证功能正确性,不声明缓存生成性能。

6. 纯文本训练 loss 对齐

使用 GSM8K 官方训练集进行纯文本训练对比。

两端保持以下条件一致:

  • 固定官方 GSM8K train 前 300 条样本;
  • 固定数据顺序,无 shuffle;
  • 相同 tokenizer、prompt、EOS;
  • 相同预移 labels 和 -100 ignore mask;
  • 相同 token_type_ids
  • BF16;
  • batch size = 1
  • max sequence length = 512
  • AdamW;
  • learning rate = 2e-5
  • seed = 23
  • 300 training steps。

Paddle 与 PyTorch loss 差异:

指标 Paddle loss - PyTorch loss
steps 300
mean diff -0.00332930
median diff 0.00081176
mean abs diff 0.02247441
max abs diff 0.17506409

结论:两端纯文本训练 loss 曲线整体接近;由于 BF16 GEMM 与 AdamW 数值实现路径存在跨框架差异,不宣称逐 step loss 完全一致。

7. 多模态训练说明

本 PR 已完成并通过:

  • 多模态 FP32 前向 logits 对齐;
  • 图像编码、projector、image token 注入和 decoder 联合计算对齐;
  • 多模态 greedy generation 新增 token 对齐。

本 PR 未声称完成真实图文训练集上的多模态训练 loss 曲线对齐。当前训练 loss 验证范围是 GSM8K 纯文本训练,用于验证 PaliGemma2 的文本训练、causal loss、label shift、mask、backward 和 AdamW 更新链路。

多模态训练集、图文 SFT 模板和端到端图文训练 loss 对齐可作为后续 CE / 扩展验证工作。

8. 单元测试

新增测试覆盖:

  • 配置;
  • 纯文本前向;
  • 多模态前向;
  • attention mask;
  • shifted causal loss;
  • -100 ignore label;
  • backward;
  • HF paligemma 的 AutoConfig / AutoModel 类解析;
  • PaliGemmaProcessor;
  • AutoProcessor 注册与解析;
  • 单图 1024 image token 展开。

执行:

PYTHONNOUSERSITE=1 PYTHONPATH=. \
python -m unittest \
  tests.transformers.paligemma2.test_modeling \
  tests.transformers.paligemma2.test_processor

结果:

Ran 10 tests
OK

git diff --check 已通过。

9. 提交范围

本 PR 仅包含:

  • PaliGemma2 模型、配置、Processor 和图像处理器;
  • AutoConfig、AutoModel、AutoProcessor、AutoImageProcessor 必要注册;
  • PaliGemma2 单测。

Add PaliGemma2 modeling, processor registration, automatic checkpoint class
resolution, and validation coverage for multimodal inference and training.

@risemeup1111 risemeup1111 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复查当前提交,详细问题已放在行级评论里。当前还有会影响 PaliGemma2 初始化语义、权重绑定、attention mask 处理和多模态 logits 的问题;其中 P1 项需要修复后再推送新的 commit,P2 项请按评论回复或调整。

Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.

self.weight = paddle.create_parameter(
shape=[dim],
dtype="float32",
default_initializer=nn.initializer.Constant(1.0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 优先级:P1
处理要求:请针对该评论修复并提交新的 commit。

Gemma2RMSNorm.forward() 使用的是 output * (1.0 + self.weight) 形式,因此这里的参数初始化应表示 0 偏移量。当前初始化为 1.0 会让从 config 初始化的模型、或任何未加载到 checkpoint 的 norm 权重默认缩放变成 2 倍,和 Gemma 系列 checkpoint 语义不一致;同仓库 Gemma3RMSNorm 在相同公式下也是 Constant(0.0)。请改为 0.0。

Suggested change
default_initializer=nn.initializer.Constant(1.0),
default_initializer=nn.initializer.Constant(0.0),

Comment on lines +397 to +401
if attention_mask is None:
attention_mask = paddle.triu(
paddle.full((seq_len, seq_len), float("-inf")),
diagonal=1,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 优先级: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 时叠加进去。

Suggested change
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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 优先级: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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 优先级:P1
处理要求:请针对该评论修复并提交新的 commit。

条件生成路径在 lm_head 后直接返回 logits,遗漏了 Gemma2 的 final_logit_softcapping。同文件的纯文本 PaliGemma2ForCausalLM 已在 logits 后应用该 softcap;这里不做会让多模态 forward/generation 在大 logits 时和 Gemma2/HF 语义不一致,默认 final_logit_softcapping=30.0 时尤其明显。请在条件生成路径也应用同一逻辑。

Suggested change
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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 优先级: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")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done。当前 PaliGemma2 实现明确仅支持每个 prompt 输入一张图。

已在 Processor 侧于图像预处理前拒绝嵌套多图 batch,保证 Processor 生成的 image token 数量与模型侧单图 image feature 注入逻辑一致,避免多图场景下出现 token / feature 数量不匹配。

多图支持需要额外定义扁平化图片顺序、每个样本图片数量、placeholder 对应关系,并使用支持多图的 checkpoint 完成端到端前向和生成对齐验证。当前 paligemma2-3b-pt-448 的验收仅覆盖单图,因此本 PR 暂不扩大到未验证的多图语义。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个处理方向可以接受:当前 PR 明确收敛为单图语义,并已在 processor 侧拒绝嵌套多图输入,避免和模型侧单图 feature 注入逻辑不一致。该 P2 事项我这里视为已解决。

Align normalization, masking, tied embeddings, logits softcapping, and the supported single-image processor contract with PaliGemma2 behavior.

@risemeup1111 risemeup1111 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复查新提交,之前的 P1 问题在代码层面已经修复:RMSNorm 初始化、纯文本 attention mask、权重绑定和条件生成 logits softcap 都已补齐,并增加了对应测试。没有新增行级问题。

剩余事项:原 P2 多图处理意见已改为在 processor 侧拒绝嵌套多图,代码方向可接受;但该线程还缺少作者回复。请在对应线程回复 Done 或说明理由后再继续审批。

Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.

@risemeup1111 risemeup1111 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复查当前提交和最新回复,之前提出的问题均已处理:P1 项已通过新提交修复,P2 多图范围说明也合理,当前实现明确收敛为单图输入并在 processor 侧提前拒绝未验证的多图输入。未发现需要继续阻塞合入的问题。

Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.

@paddle-bot

paddle-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Thanks for your contribution!

Resolve AutoConfig and AutoModel registrations by retaining both upstream models and PaliGemma2 support.

@risemeup1111 risemeup1111 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复查当前提交,之前提出的 PaliGemma2 模型语义问题仍保持已修复状态,P2 多图范围说明也仍可接受。本次新的阻塞点来自 CI lint,需要先修复后再继续审批。

  • P0 优先级:P0
    非行级:当前是 CI lint 失败,涉及 formatter、import 排序、copyright hook 和多个文件的 flake8 报错,无法安全用单个连续 diff 行级 suggestion 表达。
    处理要求:请修复 lint 并提交新的 commit。
    证据:Lint job 在 make lint 阶段失败;black 会重排 paddleformers/transformers/paligemma2/processor.pytests/transformers/paligemma2/test_modeling.pypaddleformers/transformers/auto/image_processing.pypaddleformers/transformers/paligemma2/modeling.pyisort 会修改 PaliGemma2 相关 import;flake8modeling.py:24 F401 'math' imported but unusedmodeling.py:71 F841 local variable 'batch_size' is assigned to but never usedtest_modeling.py:31/35 F401 PaliGemma2PreTrainedModel imported but unused,并且 copyright_checker 也修改了文件。请删除未使用符号并运行仓库 lint/pre-commit 格式化后推送。
Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.

Apply repository pre-commit formatting and remove unused imports and variables required by lint.

@risemeup1111 risemeup1111 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复查最新提交,之前的 PaliGemma2 模型语义问题仍保持已解决状态;这次格式化修复已处理大部分 lint 问题,但当前 Lint 仍因一个 black 格式化差异失败。剩余阻塞点已放在行级评论里,请修复后提交新的 commit。

Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.

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":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 优先级:P0
处理要求:请针对该评论修复并提交新的 commit。

当前最新 Lint job 仍失败,原因已经收敛到 black 会重排 paddleformers/transformers/auto/image_processing.py。日志显示其余 isortflake8copyright_checker 已通过,但 black 修改了这个文件后 make lint 退出失败。请按 black 格式拆分这个条件,或本地运行 make lint/pre-commit 后提交格式化结果。

Suggested change
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 risemeup1111 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复查最新提交,上一轮 black 格式化阻塞点已通过新的 commit 修复,Lint 也已通过。之前提出的 PaliGemma2 模型语义问题和多图输入范围问题仍保持已解决状态。当前未发现需要继续阻塞合入的问题;其余仍在运行的 CI 请以最终结果为准。

Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.

@Paddle-CI-Bot

Paddle-CI-Bot commented Jul 15, 2026

Copy link
Copy Markdown

PaddleFormers Log Analysis

Run #29407020746 · Attempt 1

日志分析报告

流水线名称 问题标签 修复建议 日志片段
Fleet Model Test / Integration test (A100) 依赖包损坏 paddlepaddle-gpu wheel 文件(3GB)校验失败,需要重新发布或修复该 nightly whl 报错代码

失败的测试case:

Install PaddleFormers(步骤级失败,未进入任何测试 case)
- GLM4.5 pre-train  [skipped - 未运行]
- GLM4.5 sft        [skipped - 未运行]
- GLM4.5 lora       [skipped - 未运行]
- GLM4.5 dpo        [skipped - 未运行]
- GLM4.5 dpo_lora   [skipped - 未运行]
- Qwen pre-train    [skipped - 未运行]
- Qwen sft          [skipped - 未运行]
- Qwen lora         [skipped - 未运行]
- Qwen vl sft       [skipped - 未运行]
- Qwen vl lora      [skipped - 未运行]
- Qwen vl moe       [skipped - 未运行]

根本原因分析:
paddlefleet==0.4.0.post20260711+899c0267246 依赖 paddlepaddle-gpu==3.4.0.post20260710+af44bbcdf39,该 2999.5MB 的 whl 在 pip 解包验证阶段报 Wheel is invalid,说明该 nightly 版本的 wheel 文件本身损坏(可能是发布时打包不完整或上传截断),与本 PR 的 paligemma2 功能改动无关。

修复建议:

  1. 确认 paddlepaddle_gpu-3.4.0.post20260710+af44bbcdf39-cp312-cp312-linux_x86_64.whl 在镜像源上的 MD5/SHA256,若文件损坏,重新上传或回退到上一个有效的 nightly 版本。
  2. 在 PaddleFleet 侧更新 get_fleet_commit.py 指向的 commit,使其依赖一个已验证可用的 paddle wheel,然后 rerun 本次 CI。
  3. 若短期无法修复 paddle nightly,可临时在 install_ops_wheel.sh 中 pin 到上一个已知好的 paddle 版本规避。

🔍 准确性记录:请点击评论底部 😊 图标,选择 👍(准确)或 👎(有误),将自动记录到 CI 监控系统

🔄 每次 Re-run 后自动更新

Verify AutoProcessor forwards the requested Hugging Face download source independently of the CI environment default.
@cf-icehzgzh

cf-icehzgzh commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

PaddleFormers Log Analysis

Run #29404061915 · Attempt 1

日志分析报告

流水线名称 问题标签 修复建议 日志片段
Unittest GPU CI 兼容性 Bug 测试断言期望 download_hub='huggingface',但实际 AutoProcessor 分发时传入了 download_hub='aistudio',需修正测试用例中的期望值或在 PaliGemma2 处理器注册逻辑中统一默认 hub 来源 报错代码
失败的测试 case:

tests/transformers/paligemma2/test_processor.py::PaliGemmaProcessorTest::test_auto_processor_resolves_paligemma_processor_from_preprocessor_config

根本原因分析: PR 新增 PaliGemma2 处理器后,test_processor.py:103 断言 AutoProcessor.from_pretrained 应以 download_hub='huggingface' 调用 PaliGemmaProcessor.from_pretrained,但运行时实际传入的是 download_hub='aistudio'(由 image_processing_utils.py:190 的日志 Using download source: aistudio 证实),说明 PaliGemma2 的 AutoImageProcessor 注册路径覆盖了默认 hub,与测试预期不符。

修复建议:

  1. 检查新增的 paddleformers/transformers/paligemma2/image_processing_paligemma2.py(或对应注册代码),确认是否在类定义或 AutoImageProcessor 映射中写死了 download_hub='aistudio',若是则移除该硬编码,保持与其他处理器一致的默认行为。
  2. 若确认运行时 hub 来源是 aistudio 是预期行为,则修改 tests/transformers/paligemma2/test_processor.py:103 中断言的期望值,将 download_hub='huggingface' 改为 download_hub='aistudio'
  3. 本地执行 pytest tests/transformers/paligemma2/test_processor.py::PaliGemmaProcessorTest::test_auto_processor_resolves_paligemma_processor_from_preprocessor_config -v 验证修复通过后再提交。

🔍 准确性记录:请点击评论底部 😊 图标,选择 👍(准确)或 👎(有误),将自动记录到 CI 监控系统

🔄 每次 Re-run 后自动更新

@risemeup1111 risemeup1111 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复查本次测试修复,processor hub 默认值导致的单测失败点已通过显式 download_hub="huggingface" 固定下来,未发现新的阻塞问题。代码层面可以继续推进;仍请以最终 CI 结果为准。

Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.

@cf-icehzgzh

Copy link
Copy Markdown
Contributor Author

/re-run all-failed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants