Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
897649d
Add mixed-precision (FP32) LM output logits (#6252)
mkhona-nvidia Aug 12, 2026
14346b6
Eliminate the ShardedObject all_gather_object on the FullyParallel lo…
asolergi-nv Aug 12, 2026
15c83d2
chore: rotate oncall schedule
github-actions[bot] Aug 12, 2026
81fe7c7
GTP + PartialCG: Classify latent projections for partial CUDA graphs …
JF-D Aug 12, 2026
8534490
Balance LayerWise optimizer shards by Newton-Schulz cost, not paramet…
deepakn94 Aug 12, 2026
fad6111
Suppress noisy external-library log lines on non-rank-0 processes (#5…
deepakn94 Aug 12, 2026
b2f2888
feat(ci): enhance triage script to assign on-call assignee dynamicall…
balasaajay Aug 12, 2026
5436289
Skip attention mask materialization in RL training (#5885)
tdene Aug 13, 2026
a34880f
Avoid unnecessary MoE router host synchronization (#6432)
JF-D Aug 13, 2026
3cd8cf3
Refactor GDP dynamic inference onto the shared SSM interface (#6443)
santhnm2 Aug 13, 2026
3ce4b4a
test(fsdp): mark default overlap check flaky (#6493)
wujingyue Aug 13, 2026
301e0d6
Paged stashing no longer assumes single layer config (#6419)
Phlip79 Aug 13, 2026
5714a20
Respect `profile_ranks` on log interval memory snapshots (#6354)
maanug-nv Aug 13, 2026
fecb29f
Add unit tests for GDP inference (#6505)
santhnm2 Aug 13, 2026
8ba5c60
[fix] fix GTP+DCP ckpt saving/loading for GDP module (#6503)
fanshiqing Aug 13, 2026
281075d
feat(fsdp): support expert parallelism in MFSDP v2 adapter (#6450)
wujingyue Aug 13, 2026
2a75ac1
Complete clamped SwiGLU across expert paths (#5940)
FDecaYed Aug 13, 2026
87bf520
RL: unify rollout generation into one pipeline (#6495)
tdene Aug 13, 2026
487bb39
Avoid passing -1 sentinels into inference kernels (#6481)
tdene Aug 13, 2026
09921c0
RL: bound per-env wandb keys to the char limit (#6477)
tdene Aug 13, 2026
2119719
Remove torch cuda empty cache (#5678)
shurkat-nvidia Aug 13, 2026
87cc590
[fix] GTP+DDP potential data-race: publish DDP params before GTP pref…
fanshiqing Aug 13, 2026
d7430d5
Cover symmetric memory in CUDA graph test (#5987)
wujingyue Aug 13, 2026
f7b2209
Fix tool call reasoning boundary (#6411)
nvcsathe Aug 13, 2026
0ee415b
Update Nemotron 3.5 Lightning golden values (#6445)
Phlip79 Aug 13, 2026
94939b8
Handle hidden chunked prefill during async overlap (#6442)
lmcafee-nvidia Aug 13, 2026
f9cc544
Thread socket argument through MInf API (#6513)
tdene Aug 13, 2026
69e282e
Update release notes page (#6520)
Phlip79 Aug 13, 2026
3d0956f
test(fsdp): verify optimizer CUDA graphs in v2 (#6498)
wujingyue Aug 13, 2026
de4a82e
Clean up SSM ops directory structure (#6444)
santhnm2 Aug 13, 2026
d918967
Add read/write ledger for bulky inference metadata (#6125)
tdene Aug 13, 2026
d6d48cc
fix(ci): prevent queue stalls and stale merge-group runs (#6523)
ko3n1g Aug 14, 2026
ad3ecd9
Streaming tool call deltas support (#6226)
nvcsathe Aug 14, 2026
6c34429
save tokenizer assets inside torch_dist checkpoint (#6280)
dimapihtar Aug 14, 2026
c8b2a4a
docs: Add 0.19.0 to version picker (#6536)
chtruong814 Aug 14, 2026
41ba4d9
Miscellaneous fixes to the RL loop (#6044)
tdene Aug 14, 2026
cac6b7b
Add disaggregated KV state handoff (#6222)
wdykas Aug 14, 2026
d347eef
ci: Do not upload docker oci artifact attestations (#6542)
chtruong814 Aug 14, 2026
4dc5c96
fix(ci): AUT-1427 skip Claude labeling for external authors (#6398)
svcnemo-autobot Aug 14, 2026
814300c
Fix hidden checkpoint parameter handling in local CUDA graphs (#6373)
JF-D Aug 14, 2026
78ad108
Track DBuffer allocation stream (#6441)
wujingyue Aug 14, 2026
883533e
[Main] Support device-init grouped linear module too with when not us…
zhongbozhu Aug 14, 2026
0615ac9
fix(datasets): use len(lengths) for default modes in multimodal add_d…
winklemad Aug 14, 2026
a4417a5
Fix argument order in multi-chunk MoE upcycling state dict conversion…
mlzoo Aug 14, 2026
669d6ed
ci: AUT-1408 replace cicd PAT with GitHub App tokens (#6360)
svcnemo-autobot Aug 15, 2026
51f6a39
fix: AUT-1501 abort when SSO list download fails (#6516)
svcnemo-autobot Aug 15, 2026
e79110c
Fix missing golden values updates (#6528)
janEbert Aug 15, 2026
e60e42f
Support HFSDP deferred DP-outer gradient reduction (#5972)
Achyuthan-S Aug 15, 2026
9e96f45
Fix redundant contiguous() call in save_dist_checkpoint_full (#5751)
Chasing1020 Aug 16, 2026
c95b0da
Cache the FullyParallel save distribution on disk (--ckpt-pg-tensors-…
asolergi-nv Aug 16, 2026
b164d74
cp: build: bump transformer-engine to 2.17.1 (#6433)
svcnemo-autobot Aug 16, 2026
c374e5b
Autotune RL lag (#4127)
tdene Aug 16, 2026
d262dbe
fix(gtp+mtp): zero the placeholder wgrad when zero_out_wgrad is set (…
fanshiqing Aug 17, 2026
34c3741
Enable context parallelism for RL training (#5882)
tdene Aug 17, 2026
32e7eda
docs(version-picker): preserve 0.19.0 and add patch releases (#6578)
ko3n1g Aug 17, 2026
62a6173
test(mfsdp): cover v2 training-step peak memory (#6551)
wujingyue Aug 17, 2026
afb55dd
Complete explicit GTP RNG seeding (#6569)
yashaswikarnati Aug 17, 2026
43c0e02
fix(core): warn on invalid RANK env values (#4993)
fallintoplace Aug 17, 2026
8638022
build(container): support non-root CI execution (#6421)
ko3n1g Aug 17, 2026
51270a3
Add flash-linear-attention GDP kernels (#6538)
santhnm2 Aug 17, 2026
fcc3319
Re-enable group filtering in RL (#5824)
tdene Aug 17, 2026
dd8c825
Instrument Megatron-LM with OpenTelemetry traces and metrics (#6438)
deepakn94 Aug 17, 2026
39d0896
Release all high-precision init value in CPU host memory (#6507)
xrennvidia Aug 17, 2026
e4ba3a3
Explicit safe MLA RoPE primitives and consumers (#5944)
FDecaYed Aug 17, 2026
7cdba42
[Split 1/N of #3430] feat(mHC): basic pytorch implementation of manif…
Connor-XY Aug 17, 2026
d7e2c13
ci: Update workflow to include NeMo RL testing (#6548)
balasaajay Aug 18, 2026
574e04e
Add data-schedule utilities and GPTDatasetConfig fields for sequence …
ilml Aug 18, 2026
59ff171
Enhance optimizer state loading with runtime overrides (#3720)
yhgalaxy Aug 18, 2026
35ae46c
test(mfsdp): cover v2 training memory (#6567)
wujingyue Aug 18, 2026
5d50b16
fix(ci): avoid JET expansion of HOME in setup scripts (#6599)
balasaajay Aug 18, 2026
32756e3
RL: isolate placeholder rollouts from GRPO rewards (#6475)
tdene Aug 18, 2026
ae5ec14
tests: Update cerno.yml with linear issue status (#6304)
balasaajay Aug 18, 2026
cfb8e46
Enable GTP for heterogeneous MIMO training (#6570)
yashaswikarnati Aug 18, 2026
dcb2bfd
test(mfsdp): separate overlap test (#6591)
wujingyue Aug 18, 2026
bd72caf
GDP perf optimizations (#6553)
xrennvidia Aug 18, 2026
c9676f4
RL: compress per-token staleness metrics (#6376)
tdene Aug 18, 2026
3126ff2
Guard against divide/multiply by 0 in RL task distribution (#4362)
tdene Aug 18, 2026
d5a019a
Support PackedSeqParams CP CG as in #2282 (#6571)
tdene Aug 18, 2026
aaa4dfd
Use named module logger in dynamic inference engine (#6001)
ArEsKay3 Aug 18, 2026
74a4894
fix(ci): route scoped tokens through community bot (#6632)
ko3n1g Aug 18, 2026
aeae5d7
Correct the split-PR skill's stacking rules for copy-pr-bot CI (#6593)
ilml Aug 18, 2026
0980313
fix(tests): restrict result extraction to rank zero (#6620)
ko3n1g Aug 18, 2026
54c7918
test(mfsdp): separate memory tests (#6568)
wujingyue Aug 18, 2026
e5542e8
fix(gtp+grad-reduce-in-fp32): emit the wgrad in main_grad's dtype so …
fanshiqing Aug 18, 2026
bf51cf4
Honor high-priority streams in MIMO topology (#6604)
yashaswikarnati Aug 18, 2026
3ad8c2a
Share prefill in sequence packing (#5887)
tdene Aug 19, 2026
5f9b2bb
Update copy-pr-bot.yaml [skip ci]
github-actions[bot] Aug 19, 2026
342faa4
[MFSDPv1] Fix gradient sharding / allocation schedule with ZeRO-2, an…
cspades Aug 18, 2026
540f420
Add opt-in MFSDP unified communication stream (#6563)
wujingyue Aug 19, 2026
1d69364
tests: allow trusted TE pickle extra state in affected tests (#6602)
maanug-nv Aug 19, 2026
afa707e
Reflect #4440 onto GPT models, not just hybrid (#6476)
tdene Aug 19, 2026
0961f12
ci: Remove scheduled CI workflow (#6640)
chtruong814 Aug 19, 2026
e7d58db
fix(ci): request pull request permission for queue comments (#6641)
ko3n1g Aug 19, 2026
29361d6
Remove core teams from FSDP owners (#6623)
Phlip79 Aug 19, 2026
c452d5a
Add THD-format per-rank batch fetch with padding mask for sequence pa…
ilml Aug 19, 2026
f481e63
Optimize MIMO pipeline communication (#6606)
yashaswikarnati Aug 19, 2026
cab7116
chore: rotate oncall schedule
github-actions[bot] Aug 19, 2026
01dc20b
Support no-shard, ZeRO-1, and ZeRO-2 in MFSDP v2 (#6137)
Autumn1998 Aug 19, 2026
731b791
Add hybrid SSM state handoff (#6584)
wdykas Aug 19, 2026
19deef6
Checkpoint shared storage when inputs are returned (#4497)
CarlosGomes98 Aug 19, 2026
1eaec47
chore: nightly sync main into dev (2026-10-13)
svcnemo-autobot Oct 16, 2026
4a534df
fix: reconcile main slice 19deef67 with dev for nightly sync (2026-10…
svcnemo-autobot Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/copy-pr-bot.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
enabled: true
auto_sync_draft: false
auto_sync_ready: true
trustees_override: ["AAnoosheh", "ArEsKay3", "Autumn1998", "BestJuly", "BoxiangW", "CarlosGomes98", "ChenhanYu", "Connor-XY", "FDecaYed", "HaochenYuan", "ISEEKYAN", "JF-D", "JRD971000", "Leili", "Mellonta", "Phlip79", "QiZhangNV", "RPrenger", "ShriyaRishab", "WanZzzzzz", "Wohox", "YangFei1990", "ZhiyuLi-Nvidia", "adistomar", "ahmadki", "aklife97", "alokpathy", "ananthsub", "anlthms", "aroshanghias-nvd", "ashehper", "asolergi-nv", "athitten", "balasaajay", "buptzyb", "chtruong814", "cjld", "cspades", "cuichenx", "deepakn94", "desh2608", "dimapihtar", "dingqingy-nv", "duncanriach", "ehosseiniasl", "erhoo82", "ericharper", "fanshiqing", "faradawn", "fitsumreda", "freewym", "frsun-nvda", "gautham-kollu", "gdengk", "goelarushi", "guihong-nv", "guyueh1", "hexinw-nvidia", "huvunvidia", "hxbai", "ilml", "jalbericiola", "janEbert", "jaredcasper", "jenchen13", "jiaji-huang", "jiemingz", "jingqiny-99", "jkamalu", "jon-barker", "jstjohn", "kajalj22", "kamran-nvidia", "kevalmorabia97", "kevjshih", "kingformatty", "ko3n1g", "ksivaman", "kunlunl", "kvareddy", "kwyss-nvidia", "lauradang", "layalir", "lhb8125", "liding-nv", "lmcafee-nvidia", "maanug-nv", "macandro96", "mathemakitten", "matthieule", "mchrzanowski", "mehraakash", "minitu", "mkhona-nvidia", "nanz-nv", "ntajbakhsh", "nvcsathe", "parthmannan", "philipcmonk", "prajwal1210", "pthombre", "rapatel", "rhewett-nv", "rogerwaleffe", "sajadn", "sanandaraj5597", "sancha", "santhnm2", "sbak5", "shanmugamr1992", "sharathts", "sheliang-nv", "shengf-nv", "shifangx", "shjwudp", "sidsingh-nvidia", "skyw", "sraman-rgb", "sudhakarsingh27", "tdene", "theothermike", "thomasdhc", "tomlifu", "trintamaki", "tylerpoon", "wdykas", "wplf", "wujingyue", "xiaoyao0115", "xuantengh", "xuwchen", "yaox12", "yaoyu-33", "yashaswikarnati", "yeyu-nvidia", "yobibyte", "youngeunkwon0405", "yqwangustc", "yueshen2016", "yuzhongw-nvidia", "zhehuaichen", "zhongbozhu", "DanialTaheri", "niyunsheng", "svcnvidia-nemo-ci", "vasunvidia"]
trustees_override: ["AAnoosheh", "ArEsKay3", "Autumn1998", "BestJuly", "BoxiangW", "CarlosGomes98", "ChenhanYu", "Connor-XY", "FDecaYed", "HaochenYuan", "ISEEKYAN", "JF-D", "JRD971000", "Leili", "Mellonta", "Phlip79", "QiZhangNV", "RPrenger", "ShriyaRishab", "WanZzzzzz", "Wohox", "YangFei1990", "ZhiyuLi-Nvidia", "adistomar", "ahmadki", "aklife97", "alokpathy", "ananthsub", "anlthms", "aroshanghias-nvd", "ashehper", "asolergi-nv", "athitten", "balasaajay", "buptzyb", "chtruong814", "cjld", "cspades", "cuichenx", "deepakn94", "desh2608", "dimapihtar", "dingqingy-nv", "duncanriach", "ehosseiniasl", "erhoo82", "ericharper", "fanshiqing", "faradawn", "fitsumreda", "freewym", "frsun-nvda", "gautham-kollu", "gdengk", "goelarushi", "guihong-nv", "guyueh1", "hexinw-nvidia", "huvunvidia", "hxbai", "ilml", "jalbericiola", "janEbert", "jaredcasper", "jenchen13", "jiaji-huang", "jiemingz", "jingqiny-99", "jkamalu", "jon-barker", "jstjohn", "kajalj22", "kamran-nvidia", "kevalmorabia97", "kevjshih", "kingformatty", "ko3n1g", "ksivaman", "kunlunl", "kvareddy", "kwangjunahn", "kwyss-nvidia", "lauradang", "layalir", "lhb8125", "liding-nv", "lmcafee-nvidia", "maanug-nv", "macandro96", "mathemakitten", "matthieule", "mchrzanowski", "mehraakash", "minitu", "mkhona-nvidia", "nanz-nv", "ntajbakhsh", "nvcsathe", "parthmannan", "philipcmonk", "prajwal1210", "pthombre", "rapatel", "rhewett-nv", "rogerwaleffe", "sajadn", "sanandaraj5597", "sancha", "santhnm2", "sbak5", "shanmugamr1992", "sharathts", "sheliang-nv", "shengf-nv", "shifangx", "shjwudp", "sidsingh-nvidia", "skyw", "sraman-rgb", "sudhakarsingh27", "tdene", "theothermike", "thomasdhc", "tomlifu", "trintamaki", "tylerpoon", "wdykas", "wplf", "wujingyue", "xiaoyao0115", "xuantengh", "xuwchen", "yaox12", "yaoyu-33", "yashaswikarnati", "yeyu-nvidia", "yobibyte", "youngeunkwon0405", "yqwangustc", "yueshen2016", "yuzhongw-nvidia", "zhehuaichen", "zhongbozhu", "DanialTaheri", "niyunsheng", "svcnvidia-nemo-ci", "vasunvidia"]
16 changes: 8 additions & 8 deletions .github/oncall_schedule.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
[
{
"user": "janEbert",
"date": "2026-08-05"
},
{
"user": "maanug-nv",
"date": "2026-08-12"
},
{
"user": "Phlip79",
"date": "2026-08-19"
Expand Down Expand Up @@ -46,5 +38,13 @@
{
"user": "janEbert",
"date": "2026-10-21"
},
{
"user": "maanug-nv",
"date": "2026-10-28"
},
{
"user": "Phlip79",
"date": "2026-11-04"
}
]
210 changes: 210 additions & 0 deletions .github/scripts/cancel_superseded_merge_group_runs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#!/usr/bin/env python3
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

"""Cancel active workflow runs for superseded generations of one queued PR."""

import json
import os
import re
import time
import urllib.error
import urllib.parse
import urllib.request

_ACTIVE_STATUSES = ("requested", "queued", "in_progress", "waiting", "pending")
_MERGE_GROUP_REF = re.compile(r"^gh-readonly-queue/.+/pr-(\d+)-[0-9a-f]+$", re.IGNORECASE)


class GitHubApiError(RuntimeError):
"""Report a failed GitHub REST API request."""

def __init__(self, status: int, message: str) -> None:
super().__init__(message)
self.status = status


class GitHubClient:
"""Minimal GitHub Actions REST API client."""

def __init__(self, api_url: str, repository: str, token: str) -> None:
owner, name = repository.split("/", maxsplit=1)
self._repository_path = f"repos/{urllib.parse.quote(owner)}/{urllib.parse.quote(name)}"
self._api_url = api_url.rstrip("/")
self._headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"X-GitHub-Api-Version": "2022-11-28",
}

def _request(self, path: str, method: str = "GET") -> dict[str, object] | None:
for attempt in range(5):
request = urllib.request.Request(
f"{self._api_url}/{self._repository_path}/{path}",
data=b"" if method == "POST" else None,
headers=self._headers,
method=method,
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
payload = response.read()
return json.loads(payload) if payload else None
except urllib.error.HTTPError as error:
details = error.read().decode("utf-8", errors="replace")
if error.code != 429 and error.code < 500:
raise GitHubApiError(
error.code, f"GitHub API {method} {path} failed: {details}"
) from error
if attempt == 4:
raise GitHubApiError(
error.code, f"GitHub API {method} {path} failed after retries: {details}"
) from error
delay = min(int(error.headers.get("Retry-After", 2**attempt)), 30)
except (TimeoutError, urllib.error.URLError) as error:
if attempt == 4:
raise RuntimeError(f"GitHub API {method} {path} failed after retries: {error}") from error
delay = 2**attempt

print(f"GitHub API {method} {path} failed transiently; retrying in {delay}s")
time.sleep(delay)

raise AssertionError("unreachable")

def _list_runs(self, endpoint: str, parameters: dict[str, str]) -> list[dict[str, object]]:
runs: list[dict[str, object]] = []
page = 1
while True:
query = urllib.parse.urlencode({**parameters, "per_page": "100", "page": str(page)})
response = self._request(f"{endpoint}?{query}")
if not isinstance(response, dict) or not isinstance(response.get("workflow_runs"), list):
raise RuntimeError(f"GitHub API returned an invalid workflow-runs response for {endpoint}")

page_runs = response["workflow_runs"]
runs.extend(page_runs)
if len(page_runs) < 100:
return runs
page += 1

def list_trigger_workflow_runs(
self, workflow_id: int, head_branches: set[str]
) -> list[dict[str, object]]:
"""List merge-group runs that establish the ordering of active generations."""

runs: list[dict[str, object]] = []
for head_branch in sorted(head_branches):
runs.extend(
self._list_runs(
f"actions/workflows/{workflow_id}/runs",
{"branch": head_branch, "event": "merge_group"},
)
)
return runs

def list_active_merge_group_runs(self) -> list[dict[str, object]]:
"""List all non-terminal merge-group workflow runs in the repository."""

runs_by_id: dict[int, dict[str, object]] = {}
for status in _ACTIVE_STATUSES:
for run in self._list_runs("actions/runs", {"event": "merge_group", "status": status}):
runs_by_id[int(run["id"])] = run
return list(runs_by_id.values())

def cancel_run(self, run_id: int) -> None:
"""Request cancellation of one workflow run."""

self._request(f"actions/runs/{run_id}/cancel", method="POST")


def _pr_number(head_branch: object) -> int | None:
match = _MERGE_GROUP_REF.fullmatch(str(head_branch))
return int(match.group(1)) if match else None


def _run_order(run: dict[str, object]) -> tuple[str, int]:
return str(run["created_at"]), int(run["id"])


def _select_superseded_runs(
active_runs: list[dict[str, object]],
trigger_workflow_runs: list[dict[str, object]],
current_run: dict[str, object],
) -> list[dict[str, object]]:
current_head = str(current_run["head_branch"])
current_pr = _pr_number(current_head)
if current_pr is None:
raise ValueError(f"Unexpected merge-group head branch: {current_head}")

generation_order: dict[str, tuple[str, int]] = {}
for run in trigger_workflow_runs:
head_branch = str(run.get("head_branch", ""))
if _pr_number(head_branch) != current_pr:
continue
run_order = _run_order(run)
previous_order = generation_order.get(head_branch)
if previous_order is None or run_order < previous_order:
generation_order[head_branch] = run_order

current_order = _run_order(current_run)
generation_order[current_head] = min(generation_order.get(current_head, current_order), current_order)
if any(head != current_head and order > current_order for head, order in generation_order.items()):
print(f"A newer merge-group generation exists for PR #{current_pr}; this cleanup is stale")
return []

superseded = []
for run in active_runs:
head_branch = str(run.get("head_branch", ""))
if head_branch == current_head or _pr_number(head_branch) != current_pr:
continue

old_generation_order = generation_order.get(head_branch)
if old_generation_order is None:
print(f"Skipping run {run.get('id')} because its merge-group generation was not found")
continue
if old_generation_order < current_order:
superseded.append(run)

return sorted(superseded, key=_run_order)


def main() -> None:
"""Cancel active runs belonging to older merge-group generations of the triggering PR."""

current_run = {
"id": int(os.environ["TRIGGER_RUN_ID"]),
"head_branch": os.environ["TRIGGER_HEAD_BRANCH"],
"created_at": os.environ["TRIGGER_CREATED_AT"],
}
client = GitHubClient(
api_url=os.environ.get("GITHUB_API_URL", "https://api.github.com"),
repository=os.environ["GITHUB_REPOSITORY"],
token=os.environ["GITHUB_TOKEN"],
)

active_runs = client.list_active_merge_group_runs()
current_pr = _pr_number(current_run["head_branch"])
if current_pr is None:
raise ValueError(f"Unexpected merge-group head branch: {current_run['head_branch']}")
active_heads = {
str(run.get("head_branch", "")) for run in active_runs if _pr_number(run.get("head_branch")) == current_pr
}
active_heads.add(str(current_run["head_branch"]))
trigger_runs = client.list_trigger_workflow_runs(int(os.environ["TRIGGER_WORKFLOW_ID"]), active_heads)
superseded_runs = _select_superseded_runs(active_runs, trigger_runs, current_run)

canceled = 0
for run in superseded_runs:
run_id = int(run["id"])
try:
client.cancel_run(run_id)
except GitHubApiError as error:
if error.status == 409:
print(f"Run {run_id} became terminal before cancellation")
continue
raise
print(f"Canceled superseded {run.get('name', 'workflow')} run {run_id} ({run.get('head_branch')})")
canceled += 1

print(f"Canceled {canceled} superseded merge-group run(s)")


if __name__ == "__main__":
main()
31 changes: 30 additions & 1 deletion .github/scripts/test_approve_test_queue.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ set -euo pipefail

readonly WORKFLOW=${WORKFLOW:-.github/workflows/cicd-approve-test-queue.yml}
readonly EXTERNAL_QUEUE=' - branch: all'
readonly WORKFLOW_RUN_EVENT=' workflow_run:'
readonly CICD_COMPLETION_TRIGGER=' workflows: ["CICD Megatron-LM"]'
readonly COMPLETED_ACTIVITY=' types: [completed]'
readonly CONCURRENCY_GROUP=' group: approve-test-queue'
readonly SERIALIZED_RUNS=' cancel-in-progress: false'
readonly INTERNAL_SERVICE_ACCOUNT=' INTERNAL_SERVICE_ACCOUNTS = {"svcnemo-autobot"}'
readonly INTERNAL_SERVICE_ACCOUNT_CHECK=' return login in INTERNAL_SERVICE_ACCOUNTS or any('

if [[ $(/usr/bin/grep -c -F "$EXTERNAL_QUEUE" "$WORKFLOW") -ne 1 ]]; then
echo "Approve Test Queue must define exactly one global external worker" >&2
Expand All @@ -19,4 +26,26 @@ if [[ $(/usr/bin/grep -c -F 'if CONTRIBUTOR_TYPE == "external":' "$WORKFLOW") -n
exit 1
fi

echo "Approve Test Queue uses one global external worker"
for expected in \
"$WORKFLOW_RUN_EVENT" \
"$CICD_COMPLETION_TRIGGER" \
"$COMPLETED_ACTIVITY" \
"$CONCURRENCY_GROUP" \
"$SERIALIZED_RUNS"; do
if [[ $(/usr/bin/grep -c -F "$expected" "$WORKFLOW") -ne 1 ]]; then
echo "Approve Test Queue must wake on completed CICD runs without racing approvals: $expected" >&2
exit 1
fi
done

if [[ $(/usr/bin/grep -c -F "$INTERNAL_SERVICE_ACCOUNT" "$WORKFLOW") -ne 1 ]]; then
echo "Approve Test Queue must classify svcnemo-autobot as internal without relying on the SSO export" >&2
exit 1
fi

if [[ $(/usr/bin/grep -c -F "$INTERNAL_SERVICE_ACCOUNT_CHECK" "$WORKFLOW") -ne 1 ]]; then
echo "Approve Test Queue must apply its internal service-account allowlist" >&2
exit 1
fi

echo "Approve Test Queue uses one global external worker, recognizes its service account, and wakes on completion"
92 changes: 92 additions & 0 deletions .github/scripts/test_cancel_superseded_merge_group_runs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import unittest
from pathlib import Path

from cancel_superseded_merge_group_runs import _pr_number, _select_superseded_runs


def _run(run_id: int, head_branch: str, created_at: str, name: str = "CICD Megatron-LM") -> dict[str, object]:
return {
"id": run_id,
"event": "merge_group",
"head_branch": head_branch,
"created_at": created_at,
"name": name,
}


class TestCancelSupersededMergeGroupRuns(unittest.TestCase):
def setUp(self) -> None:
self.old_head = "gh-readonly-queue/main/pr-123-a1d1234"
self.current_head = "gh-readonly-queue/main/pr-123-b2e5678"
self.current_run = _run(200, self.current_head, "2026-08-13T22:00:00Z")
self.trigger_runs = [
self.current_run,
_run(100, self.old_head, "2026-08-13T21:00:00Z"),
]

def test_extracts_pr_number_only_from_merge_group_refs(self) -> None:
self.assertEqual(_pr_number(self.current_head), 123)
self.assertIsNone(_pr_number("pull-request/123"))
self.assertIsNone(_pr_number("feature/pr-123-not-a-merge-group"))

def test_selects_all_active_runs_from_the_older_generation(self) -> None:
old_cicd = _run(100, self.old_head, "2026-08-13T21:00:00Z")
old_release = _run(250, self.old_head, "2026-08-13T22:01:00Z", name="Release")
current_release = _run(201, self.current_head, "2026-08-13T22:00:01Z", name="Release")
other_pr = _run(50, "gh-readonly-queue/main/pr-456-a7e1234", "2026-08-13T20:00:00Z")

selected = _select_superseded_runs(
[old_cicd, old_release, self.current_run, current_release, other_pr],
self.trigger_runs,
self.current_run,
)

self.assertEqual([run["id"] for run in selected], [100, 250])

def test_stale_cleanup_does_not_cancel_a_newer_generation(self) -> None:
newer_head = "gh-readonly-queue/main/pr-123-c3f9abc"
newer_run = _run(300, newer_head, "2026-08-13T23:00:00Z")

selected = _select_superseded_runs(
[self.current_run, newer_run],
[newer_run, *self.trigger_runs],
self.current_run,
)

self.assertEqual(selected, [])

def test_rerun_does_not_change_the_generation_order(self) -> None:
old_rerun = _run(400, self.old_head, "2026-08-13T23:00:00Z")

selected = _select_superseded_runs(
[old_rerun],
[old_rerun, *self.trigger_runs],
self.current_run,
)

self.assertEqual([run["id"] for run in selected], [400])

def test_skips_an_active_generation_without_trusted_ordering_data(self) -> None:
unknown_head = "gh-readonly-queue/main/pr-123-d4a1111"
unknown_run = _run(50, unknown_head, "2026-08-13T20:00:00Z")

selected = _select_superseded_runs([unknown_run], self.trigger_runs, self.current_run)

self.assertEqual(selected, [])

def test_workflow_runs_from_the_default_branch_with_minimal_write_permission(self) -> None:
workflow = Path(".github/workflows/cicd-cancel-superseded-merge-groups.yml").read_text()

self.assertIn('workflows: ["CICD Megatron-LM"]', workflow)
self.assertIn("types: [requested]", workflow)
self.assertIn("github.event.workflow_run.event == 'merge_group'", workflow)
self.assertIn("actions: write", workflow)
self.assertIn("ref: ${{ github.event.repository.default_branch }}", workflow)
self.assertIn("persist-credentials: false", workflow)


if __name__ == "__main__":
unittest.main()
Loading
Loading