Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,18 +233,19 @@ Megatron Bridge provides out-of-the-box bridges and training recipes for a wide
| [**DeepSeek**](docs/models/deepseek/index.md) | DeepSeek V2 / V2 Lite (deprecated), DeepSeek V3, DeepSeek V4 / V4 Flash |
| [**Diffusion**](https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main/src/megatron/bridge/diffusion/models) | FLUX, LLaDA 1.5, Nemotron-Labs Diffusion, WAN |
| **Ernie** | [Ernie 4.5 MoE](https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main/src/megatron/bridge/models/ernie), [Ernie 4.5 VL MoE](https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main/src/megatron/bridge/models/ernie_vl) |
| [**EXAONE**](docs/models/exaone/exaone.md) | EXAONE 4.0, EXAONE 4.5 VL, K-EXAONE MoE |
| [**Falcon**](docs/models/falcon/index.md) | Falcon H1 |
| [**Gemma**](docs/models/gemma/index.md) | Gemma / Gemma 2 (deprecated), Gemma 3, Gemma 3-VL, Gemma 4 (26B-A4B MoE / 31B dense), Gemma 4-VL (26B-A4B MoE) |
| [**GLM**](docs/models/glm/index.md) | GLM-4.5 / GLM-4.7 / GLM-4.7-Flash, GLM-4.5V, GLM-5 / GLM-5.1 / GLM-5.2 |
| [**GPT-OSS**](docs/models/gpt_oss/index.md) | GPT-oss |
| [**HY V3**](https://huggingface.co/tencent/Hy3-preview-Base) | Hy3 preview-Base (HF → Megatron checkpoint conversion) |
| [**Kimi**](docs/models/kimi/index.md) | Kimi K2, Kimi-K2.5-VL |
| [**Kimi**](docs/models/kimi/index.md) | Kimi K2, Kimi-K2.5-VL, Kimi K3 |
| [**Llama**](docs/models/llama/index.md) | Llama 2 (deprecated), Llama 3 / 3.1 / 3.2 / 3.3 |
| [**MiniMax**](docs/models/minimax/index.md) | MiniMax-M2 / M2.5 / M2.7 |
| [**MiniMax**](docs/models/minimax/index.md) | MiniMax-M2 / M2.5 / M2.7, MiniMax-M3 |
| [**Mistral**](docs/models/mistral/index.md) | Mistral 7B / Small 3 24B (deprecated), Ministral 3 (3B/8B/14B) |
| [**Xiaomi-MiMo**](docs/models/mimo/index.md) | Xiaomi-MiMo, MiMo-V2-Flash |
| [**Moonlight**](docs/models/moonlight/index.md) | Moonlight |
| [**Nemotron**](docs/models/nemotron/index.md) | Nemotron H v1 (deprecated), Nemotron Nano v2 (deprecated), Nemotron-3 Nano, Nemotron-3 Super, Llama Nemotron (deprecated), Nemotron Nano v2 VL (deprecated), Nemotron-3 Nano Omni |
| [**Nemotron**](docs/models/nemotron/index.md) | Nemotron H v1 (deprecated), Nemotron Nano v2 (deprecated), Nemotron-3 Nano, Nemotron-3 Super, Nemotron-3 Ultra, Llama Nemotron (deprecated), Nemotron Nano v2 VL (deprecated), Nemotron-3 Nano Omni |
| [**OLMoE**](docs/models/olmoe/index.md) | OLMoE |
| [**Qwen**](docs/models/qwen/index.md) | Qwen2 / Qwen2.5, Qwen3, Qwen3-MoE, Qwen3 Next, Qwen3.5 (dense/MoE), Qwen2.5-VL, Qwen3-VL, Qwen3.5-VL, Qwen3.6-VL, Qwen2 Audio, Qwen2.5-Omni, Qwen3-Omni, Qwen3-ASR |
| [**Sarvam**](docs/models/sarvam/index.md) | Sarvam |
Expand Down
78 changes: 78 additions & 0 deletions tests/unit_tests/doc_consistency/test_readme_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@


REPO_ROOT = Path(__file__).resolve().parents[3]
README = REPO_ROOT / "README.md"
DOCS_MODELS = REPO_ROOT / "docs" / "models"
SUPPORTED_MODELS_HEADING = "## Supported Models"
RECIPES_DIR = REPO_ROOT / "src" / "megatron" / "bridge" / "recipes"
PERF_RECIPES_DIR = REPO_ROOT / "src" / "megatron" / "bridge" / "perf_recipes"
TRAINING_README = REPO_ROOT / "scripts" / "training" / "README.md"
Expand Down Expand Up @@ -830,6 +833,81 @@ def test_qwen3_recipe_examples_match_source_signatures_and_nested_owners():
assert expected_topologies <= set(qwen3_docs.splitlines())


def _normalize(text: str) -> str:
"""Lowercase and drop every non-alphanumeric character, so separators and case do not matter."""
return re.sub(r"[^a-z0-9]", "", text.lower())


def _supported_models_rows() -> list[tuple[str, str]]:
"""`(family_cell, variants_cell)` for every data row of the README Supported Models table."""
text = _read(README)
assert SUPPORTED_MODELS_HEADING in text, f"README.md has no {SUPPORTED_MODELS_HEADING!r} section"
body = text.split(SUPPORTED_MODELS_HEADING, 1)[1]
following = re.search(r"^## ", body, flags=re.MULTILINE)
table = body[: following.start()] if following else body

rows: list[tuple[str, str]] = []
for line in table.splitlines():
line = line.strip()
if not line.startswith("|") or "---" in line or line.count("|") < 3:
continue
cells = [cell.strip() for cell in line.strip("|").split("|")]
if not cells[0].lower().startswith("family"):
rows.append((cells[0], cells[1] if len(cells) > 1 else ""))
return rows


def _row_for_family(rows: list[tuple[str, str]], family: str) -> tuple[str, str] | None:
"""The row claiming `family`, matched by its docs link or by name, or None."""
for cell, variants in rows:
if f"docs/models/{family}/" in cell:
return cell, variants
for cell, variants in rows:
if _normalize(family) in _normalize(cell):
return cell, variants
return None


def test_readme_supported_models_table_lists_every_shipped_model_family():
"""Every documented model family with code in the tree has a Supported Models row."""
rows = _supported_models_rows()
assert len(rows) >= 15, f"only {len(rows)} Supported Models rows parsed — the table layout has changed"

missing = []
for family_dir in sorted(path for path in DOCS_MODELS.iterdir() if path.is_dir()):
name = family_dir.name
shipped = (REPO_ROOT / "examples" / "models" / name).is_dir() or (
REPO_ROOT / "src" / "megatron" / "bridge" / "models" / name
).is_dir()
if shipped and _row_for_family(rows, name) is None:
missing.append(f"docs/models/{name}/")
assert not missing, f"documented, shipped model families have no Supported Models row: {missing}"


def test_readme_supported_models_rows_name_every_shipped_model_variant():
"""Every documented variant page with runnable examples is named in its family's row."""
rows = _supported_models_rows()
checked, missing = [], []
for page in sorted(DOCS_MODELS.glob("*/*.md")):
if page.name == "index.md":
continue
examples = re.findall(r"(examples/[A-Za-z0-9_./-]+)", _read(page))
if not any((REPO_ROOT / path.rstrip("/.")).exists() for path in examples):
continue # nothing runnable behind this page yet, so the table cannot be expected to name it
row = _row_for_family(rows, page.parent.name)
if row is None:
continue # already reported at family granularity
checked.append(page.name)
named = _normalize(" ".join(row))
tokens = [token for token in re.split(r"[-_]", page.stem) if token]
if _normalize(page.stem) in named or all(_normalize(token) in named for token in tokens):
continue
missing.append(f"docs/models/{page.parent.name}/{page.name}")

assert checked, "no documented variant page cleared the shipped gate — the docs layout has changed"
assert not missing, f"documented, shipped model variants are not named in their README row: {missing}"


def test_sphinx_docs_link_out_of_tree_tutorials_as_urls():
"""Sphinx must not treat repository-root tutorials as source documents."""
for path in SPHINX_TUTORIAL_LINK_DOCS:
Expand Down
Loading