mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-11-03 08:14:29 +08:00
### What does this PR do? Type of change: Bug fix Bumps the Megatron-Bridge examples, tests and launcher configs to `nemo:26.08` and removes the version-gated fallbacks they carried, plus the fixes needed to make the suites green on that container. **26.08 bump and shim removal** - Examples, CI workflows, `noxfile.py` and the `mbridge_*` launcher configs move to `nemo:26.08`. - `examples/megatron_bridge/_distillation_provider.py` is deleted — 26.08's Megatron-Bridge ships `convert_to_distillation_provider(..., distill_submodule=...)` natively, so `distill.py` imports it directly. - `prune_minitron.py` drops the `AutoBridge.from_hf_config` / config-only-export probing; `--no_moe_grouped_gemm` is no longer needed in the MoE pruning tests, and the Qwen3.5-MoE `skipif` is gone (native MoE expert mappings are in 26.08). - `_DynamicMambaMixer` targets only the raw `conv1d_weight` / `conv1d_bias` parameters that replaced the `conv1d` module in Megatron-Core. **MambaModel / MambaModelProvider removal** Megatron-Core has shipped `HybridModel` since 26.06 and `MambaModel` is a deprecated subclass that shares its `forward`, so `DMRegistry` resolves those instances to the `HybridModel` registration and the separate entry is redundant. Same for `MambaModelProvider` vs `HybridModelProvider` on the bridge side. `MambaMixer` / `MambaLayer` / `ExtendedRMSNorm` are untouched — the layers still exist. The deprecated `get_te_mamba_stack_spec` is removed; use `get_te_hybrid_stack_spec`. **Bug fix: compressed output_layer extra state** `mtq.compress` converts even a *disabled* `output_layer` into a `RealQuantLinear` (its weight is left uncompressed, since `pack_real_quantize_weight` skips disabled quantizers). The guard added in #2112 excluded `RealQuantLinear` by class, so the layer emitted extra state and every worker died in `GPTModel.sharded_state_dict`: ``` RuntimeError: Boolean value of Tensor with more than one value is ambiguous megatron/core/models/gpt/gpt_model.py:896, in sharded_state_dict output_extra_state and output_extra_state.data ``` The guard now keys off whether the weight was actually compressed (`QTensorWrapper`) instead of the class. This took out all 12 `test_homogeneous_compressed_sharded_state_dict` params, and the crashed workers poisoned the pool, which surfaced as unrelated timeouts and NCCL errors in `test_layer_sync_moe_local_experts_amax`, `test_kv_cache_quant`, `test_kv_cache_amax_sync`, `test_convert_mcore_te_gpt_model` and `test_homogeneous_sharded_state_dict_te_spec` — 21 tests in total. The e2e coverage is `skip_flaky_on_blackwell`, so CI never ran it; `test_output_layer_extra_state_empty_when_nothing_quantized` now asserts the contract directly and is not skipped. **Checkpoint import entry point** 26.08 replaced `examples/conversion/convert_checkpoints.py` with `scripts/conversion/convert.sh`, so `tools/launcher/common/megatron_bridge/import/import.sh` and the three README snippets are retargeted. `import.sh` uses the distributed GPU backend with `GPUS_PER_NODE` / `TP` / `PP` / `EP` knobs. **Megatron-LM on nemo:26.06** keeps working: `_get_mamba_conv1d` still dispatches between the `conv1d` module (26.06 and earlier) and the raw parameters (26.08+), so `import_mcore_gpt_from_hf` / `export_mcore_gpt_to_hf` handle NemotronH on both. Only the Megatron-Bridge examples and Minitron pruning of Mamba/hybrid models require 26.08. **Test consolidation** `test_export_distilled_megatron_to_hf.py` is merged into `test_distill.py`: `test_distill_llm` becomes `test_distill_llm_hf_export` and covers the standalone `--export_iterations all` run on the checkpoints it already produces, saving one full distillation (~185 s of CI time). The two mamba-named gpu test files are renamed to `hybrid`. ### Usage ```bash # HF -> Megatron import, via Megatron-Bridge's 26.08 conversion entry point bash /opt/Megatron-Bridge/scripts/conversion/convert.sh import \ --executor local \ --device gpu \ --gpus-per-node 8 \ --hf-model Qwen/Qwen3-8B \ --megatron-path /tmp/Qwen3-8B-megatron ``` ### Testing All on `nvcr.io/nvidia/nemo:26.08`, 2x RTX 6000 Ada, no timeout overrides: - `tests/examples/megatron_bridge`: 16 passed, 1 skipped (28m14s). The skip is the `gemma3vl` QAD param, now `@pytest.mark.manual` since `qwen3_5_moe_vl` covers the VLM QAD path. - `tests/gpu_megatron` (`_extensions`, `distill`, `export`, `opt`, `peft`, `sparsity`, `speculative`, `utils`): 61 passed, 5 xpassed. - `tests/gpu_megatron/torch/export` re-run after the conv1d dispatch change: 27 passed. - The 21 previously failing/hanging quantization tests: 21 passed (12 + 9). - `tests/gpu_megatron/torch/{nas,prune}`: verified separately. `import.sh` equivalence on a toy `qwen3_moe`, comparing all 12 weight tensors after flattening each dist checkpoint with `dcp_to_torch_save` — the GPU backend at 1 GPU, `--tp 2`, `--pp 2`, `--ep 2`, and `import.sh` end-to-end (`GPUS_PER_NODE=2 EP=2`) are all byte-identical to `--device cpu`. `nemo:26.06` compatibility was checked directly in that image: `megatron.core.models.hybrid.HybridModel`, the modelopt hybrid spec and `hybrid_layer_pattern` are all present, while `megatron.bridge.models.hybrid` and the bridge's `distill_submodule` are not. The NemotronH round-trip test failed there before the conv1d dispatch was restored and the dispatch is back in place; per project convention the suites themselves only run on 26.08. ### Before your PR is "*Ready for review*" - Is this change backward compatible?: ⚠️ Megatron-Bridge examples plus Minitron pruning of Mamba/hybrid models now require `nemo:26.08`. Megatron-LM quantization and checkpoint export still run on `nemo:26.06`. - If you copied code from any other sources or added a new PIP dependency, did you follow guidance in `CONTRIBUTING.md`: N/A - Did you write any new necessary tests?: ✅ — `test_output_layer_extra_state_empty_when_nothing_quantized` for the compress fix; existing tests extended for the merged export coverage. - Did you update [Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?: ✅ - Did you get Claude approval on this PR?: ❌ 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added guidance for importing Hugging Face checkpoints into Megatron distributed format. * Expanded distillation workflows to export selected or all checkpoint iterations. * **Improvements** * Expanded Hybrid model support across Megatron workflows. * Updated distributed import tooling with GPU and parallelism options. * Updated supported environments and examples to NVIDIA NeMo 26.08. * **Bug Fixes** * Corrected output-layer quantization state handling when quantization is disabled. * **Documentation** * Added compatibility guidance for current and legacy NeMo containers. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
203 lines
8.5 KiB
YAML
203 lines
8.5 KiB
YAML
name: Example tests
|
|
|
|
on:
|
|
push:
|
|
branches: ["pull-request/[0-9]+"]
|
|
# NOTE: paths cannot be used since push happens to copied PR and only latest commit to PR is used
|
|
schedule:
|
|
- cron: "0 0 * * *" # Nightly
|
|
workflow_dispatch:
|
|
# On-demand
|
|
|
|
concurrency:
|
|
# Cancel previous runs if new commit is pushed to the same PR
|
|
group: ${{ github.workflow }}-${{ startsWith(github.ref, 'refs/heads/pull-request/') && github.ref || github.sha }}
|
|
cancel-in-progress: true
|
|
|
|
# Each job's `allow_failure` reads the repo variable ALLOW_FAILURE_EXAMPLE_TESTS:
|
|
# a comma-separated list of example names whose test failures should be non-blocking, e.g. "torch_trt,llm_qat"
|
|
|
|
jobs:
|
|
# One changed-files pass decides which lanes run. Lane granularity, not per-example: a job's
|
|
# `if` cannot read `matrix`, so gating an individual example would need a job per example.
|
|
pr-gate:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
checks: read
|
|
contents: read
|
|
outputs:
|
|
any_changed: ${{ steps.lanes.outputs.any_changed }}
|
|
torch: ${{ steps.lanes.outputs.torch }}
|
|
trtllm: ${{ steps.lanes.outputs.trtllm }}
|
|
megatron: ${{ steps.lanes.outputs.megatron }}
|
|
onnx: ${{ steps.lanes.outputs.onnx }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
- if: startsWith(github.ref, 'refs/heads/pull-request/')
|
|
id: base
|
|
uses: ./.github/actions/pr-merge-base
|
|
- if: startsWith(github.ref, 'refs/heads/pull-request/')
|
|
id: changed
|
|
uses: step-security/changed-files@v47.0.5
|
|
with:
|
|
base_sha: ${{ steps.base.outputs.merge_base }}
|
|
sha: ${{ steps.base.outputs.head_sha }}
|
|
fail_on_initial_diff_error: true
|
|
# `common` runs every lane. Each lane lists the example directories it covers, plus any
|
|
# extra directory those tests reach into (e.g. hf_ptq's script runs lm_eval from
|
|
# ../llm_eval, and the speculative_decoding test drives hf_ptq).
|
|
files_yaml: |
|
|
common:
|
|
- .github/actions/**
|
|
- .github/workflows/_example_tests_runner.yml
|
|
- .github/workflows/example_tests.yml
|
|
- modelopt/**
|
|
- modelopt_recipes/**
|
|
- pyproject.toml
|
|
- tests/_test_utils/**
|
|
- tests/conftest.py
|
|
- tests/examples/conftest.py
|
|
- "!**/*.{md,rst,png,ipynb}"
|
|
torch:
|
|
- "{examples,tests/examples}/{llm_distill,llm_qat,llm_sparsity,specdec_bench,speculative_decoding}/**"
|
|
- examples/dataset/** # data prep for llm_qat and speculative_decoding
|
|
- examples/hf_ptq/** # the speculative_decoding test drives hf_ptq
|
|
- "!**/*.{md,rst,png,ipynb}"
|
|
trtllm:
|
|
- "{examples,tests/examples}/{gpt-oss,hf_ptq,llm_eval}/**"
|
|
- examples/dataset/** # data prep for gpt-oss
|
|
- "!**/*.{md,rst,png,ipynb}"
|
|
megatron:
|
|
- "{examples,tests/examples}/megatron_bridge/**"
|
|
- "!**/*.{md,rst,png,ipynb}"
|
|
onnx:
|
|
- "{examples,tests/examples}/{diffusers,torch_onnx,torch_trt}/**"
|
|
- examples/onnx_ptq/** # torch_trt reuses onnx_ptq
|
|
- "!**/*.{md,rst,png,ipynb}"
|
|
- id: lanes
|
|
env:
|
|
# Nightly and on-demand runs have no diff to inspect, so they run everything.
|
|
NON_PR: ${{ !startsWith(github.ref, 'refs/heads/pull-request/') }}
|
|
COMMON: ${{ steps.changed.outputs.common_any_modified }}
|
|
TORCH: ${{ steps.changed.outputs.torch_any_modified }}
|
|
TRTLLM: ${{ steps.changed.outputs.trtllm_any_modified }}
|
|
MEGATRON: ${{ steps.changed.outputs.megatron_any_modified }}
|
|
ONNX: ${{ steps.changed.outputs.onnx_any_modified }}
|
|
run: |
|
|
any=false
|
|
for lane in TORCH TRTLLM MEGATRON ONNX; do
|
|
if [[ "$NON_PR" == "true" || "$COMMON" == "true" || "${!lane}" == "true" ]]; then
|
|
run=true
|
|
any=true
|
|
else
|
|
run=false
|
|
fi
|
|
echo "${lane,,}=$run" | tee --append "$GITHUB_OUTPUT"
|
|
done
|
|
echo "any_changed=$any" | tee --append "$GITHUB_OUTPUT"
|
|
- name: Wait for unit tests before spending GPU runners
|
|
if: ${{ steps.lanes.outputs.any_changed == 'true' && startsWith(github.ref, 'refs/heads/pull-request/') }}
|
|
uses: poseidon/wait-for-status-checks@v0.7.0
|
|
with:
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
match_pattern: "^linux$" # Wait for Unit tests / linux
|
|
delay: 300s
|
|
|
|
##### PyTorch Example Tests #####
|
|
torch:
|
|
needs: [pr-gate]
|
|
if: needs.pr-gate.outputs.torch == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
example: [llm_distill, llm_qat, llm_sparsity, specdec_bench, speculative_decoding]
|
|
uses: ./.github/workflows/_example_tests_runner.yml
|
|
permissions:
|
|
contents: read
|
|
secrets: inherit
|
|
with:
|
|
docker_image: "nvcr.io/nvidia/pytorch:26.07-py3"
|
|
example: ${{ matrix.example }}
|
|
timeout_minutes: 30
|
|
pip_install_extras: "[hf,dev-test]"
|
|
runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }}
|
|
allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }}
|
|
|
|
##### TensorRT-LLM Example Tests #####
|
|
trtllm:
|
|
needs: [pr-gate]
|
|
if: needs.pr-gate.outputs.trtllm == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
example: [gpt-oss, hf_ptq, llm_eval]
|
|
uses: ./.github/workflows/_example_tests_runner.yml
|
|
permissions:
|
|
contents: read
|
|
secrets: inherit
|
|
with:
|
|
docker_image: "nvcr.io/nvidia/tensorrt-llm/release:1.3.0rc20"
|
|
example: ${{ matrix.example }}
|
|
pip_install_extras: "[hf,dev-test]"
|
|
runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }}
|
|
allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }}
|
|
|
|
##### Megatron Example Tests #####
|
|
megatron:
|
|
needs: [pr-gate]
|
|
if: needs.pr-gate.outputs.megatron == 'true'
|
|
uses: ./.github/workflows/_example_tests_runner.yml
|
|
permissions:
|
|
contents: read
|
|
secrets: inherit
|
|
with:
|
|
docker_image: "nvcr.io/nvidia/nemo:26.08"
|
|
example: megatron_bridge
|
|
timeout_minutes: 60
|
|
pip_install_extras: "[hf,puzzletron,dev-test]"
|
|
runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }}
|
|
allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), ',megatron_bridge,') }}
|
|
|
|
##### ONNX/TensorRT Example Tests #####
|
|
onnx:
|
|
needs: [pr-gate]
|
|
if: needs.pr-gate.outputs.onnx == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
example: [diffusers, torch_onnx, torch_trt]
|
|
uses: ./.github/workflows/_example_tests_runner.yml
|
|
permissions:
|
|
contents: read
|
|
secrets: inherit
|
|
with:
|
|
# Pinned to 26.05 (TensorRT 10): torch-tensorrt is capped at <2.13 (== 2.12.1),
|
|
# which needs libnvinfer.so.10; newer tensorrt containers drop it. Bump only once
|
|
# a torch-tensorrt build for the newer TensorRT is available.
|
|
docker_image: "nvcr.io/nvidia/tensorrt:26.05-py3"
|
|
example: ${{ matrix.example }}
|
|
timeout_minutes: 45
|
|
pip_install_extras: "[onnx,hf,dev-test]"
|
|
runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }}
|
|
allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }}
|
|
|
|
##### Required Check for PR #####
|
|
example-pr-required-check:
|
|
# Run even if example tests are skipped
|
|
if: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && always() }}
|
|
needs: [pr-gate, torch, trtllm, megatron, onnx]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Required example tests did not succeed
|
|
if: |
|
|
needs.pr-gate.result != 'success' ||
|
|
(needs.pr-gate.outputs.any_changed == 'true' && (
|
|
!contains(fromJSON('["success", "skipped"]'), needs.torch.result) ||
|
|
!contains(fromJSON('["success", "skipped"]'), needs.trtllm.result) ||
|
|
!contains(fromJSON('["success", "skipped"]'), needs.megatron.result) ||
|
|
!contains(fromJSON('["success", "skipped"]'), needs.onnx.result)
|
|
))
|
|
run: exit 1
|