[Dev] ci: guard examples/multimodal_dev with a unit-test bucket - #6083
[Dev] ci: guard examples/multimodal_dev with a unit-test bucket#6083BestJuly wants to merge 6 commits into
Conversation
The fused-mRoPE change (NVIDIA#5962) made apply_rotary_pos_emb read config.mrope_section and config.apply_rope_fusion directly, but test_vision_rope_wrapper_forwards_max_seqlen_to_thd drives it with a SimpleNamespace stub written before those fields existed, so the test fails with AttributeError on current dev. Give the stub both attributes, pinning the test to the unfused legacy THD path it was written for. Signed-off-by: Li Tao <lit@nvidia.com>
…ests Four files in examples/multimodal_dev/tests were argparse + main() scripts with no test_* functions: pytest imported them and ran nothing, so the BSHD-vs-THD, CP-vs-CP=1 and patch-merger parity checks they implement were only ever exercised by hand. Factor each comparison into a reusable function and add pytest entry points on top of it; main() calls the same code, so the CLI and the tests cannot drift. Per-module DEFAULTS dicts back both the argparse defaults and the test parameters. CP sizes that do not divide the world size skip instead of failing, and every module leaves the model-parallel groups torn down, so the files run at any --nproc-per-node and share one pytest process. Adds 7 executing tests; the __main__ entry points are unchanged in behaviour. Signed-off-by: Li Tao <lit@nvidia.com>
examples/multimodal_dev tracks megatron.core APIs closely (rope_utils, GPTModel, TransformerConfig, the GPT layer specs) but lives outside tests/unit_tests, so core changes have landed without anyone noticing they broke the example — the stale mRoPE config stub fixed in this branch is one such case. Add the directory as its own H100 unit-test bucket (environment dev, tag latest) so it runs in the same pipeline as the change that breaks it. tag latest only: the legacy ref pins an older mcore and an older copy of the example, so the pairing this guards does not exist there. The bucket is collected outside tests/unit_tests, so tests/unit_tests/conftest.py does not apply to it. A local conftest supplies what run_ci_test.sh depends on: the --experimental option its second pytest pass passes, and the sessionfinish hook that maps pytest's "no tests collected" exit code 5 to success. Signed-off-by: Li Tao <lit@nvidia.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
The suite runs as its own CI bucket, so tests/unit_tests/conftest.py is not an ancestor of the collected files and none of its hooks apply here. The session-scoped `cleanup` fixture was not reproduced, so the NCCL process group was still alive at interpreter exit and torch reported a resource leak on every run. Reproduce that fixture, and document the two hooks that are deliberately not reproduced: `set_env` (this suite needs the fused and flash attention backends enabled, as the THD paths under test are only reachable through them, whereas tests/unit_tests pins them off) and `reset_env_vars`. Also record that `--experimental` is registered by both conftests, so the two directories cannot be passed to a single pytest invocation. In the recipe, note that GitHub CI always runs dev+latest and ignores the environment/tag matrix; that restriction is only expanded by GitLab. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Li Tao <lit@nvidia.com>
…teardown test_cp_correctness.py and test_vision_patch_merger_parity.py initialised and destroyed the model-parallel groups through parallel_state directly, while the rest of the suite goes through Utils. That leaves Utils.inited stale, which silently turns a later Utils.destroy_model_parallel() into a no-op. Route both modules through Utils. Collapse the CP=1 baseline and the CP>1 trial into a single run_cp(). They differed only in who built the batch -- a pure function of the seed, so it needs no threading between calls -- and in whether reference weights were supplied, neither of which is a property of CP=1. One code path keeps the baseline and the trial differing only in cp_size, and lets a module-scoped fixture reuse a single CP=1 run across every CP size under test. Drop the cp_size key that the run_cp_comparison parameter shadowed in test_cp_thd_correctness.py, and collect the remaining hard-coded sizes and tolerances into DEFAULTS as the sibling modules already do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Li Tao <lit@nvidia.com>
|
/claude review |
|
Light review. The core of this looks right — I verified the claims that matter:
Two non-blocking notes: 1. The docstring says it rounds up "to a multiple of import math # at top of module
def aligned_seq_len(seq_len, cp_sizes):
align = 2 * math.lcm(*cp_sizes)
return ((seq_len + align - 1) // align) * alignAlternatively just narrow the docstring to say it aligns to the largest CP size. 2. The repaired stub reproduces the drift it exists to catch ( This is exactly the failure mode the PR is built to prevent: a hand-rolled |
What does this PR do?
Runs the
examples/multimodal_dev/tests/suite as its own H100 unit-test bucket, and repairs the two things that stopped that suite from being a usable guard.examples/multimodal_devtracksmegatron.coreAPIs closely —rope_utils,GPTModel,TransformerConfig, the GPT layer specs — but it lives outsidetests/unit_tests, so nothing in CI exercises it. Core changes have therefore landed without anyone noticing they broke the example.That is not hypothetical: it is broken on
devtoday. The fused-mRoPE change (#5962) madeapply_rotary_pos_embreadconfig.mrope_sectionandconfig.apply_rope_fusiondirectly, andtest_mrope_parity.py::test_vision_rope_wrapper_forwards_max_seqlen_to_thddrives that code with aSimpleNamespacestub written before those fields existed:Measured on
dev@9304a2564, 1×8×H100: 1 failed, 45 passed (identical at 1 rank and at 8).Three commits:
Repair the stale config stub. The stub now carries
mrope_section=None, apply_rope_fusion=False, pinning the test to the unfused legacy THD path it was written for. No change tomegatron/core.Make the standalone harnesses real tests. Four of the eight files (
test_cp_correctness.py,test_cp_thd_correctness.py,test_thd_correctness.py,test_vision_patch_merger_parity.py) wereargparse+main()scripts with notest_*functions — pytest imported them and collected nothing, so the BSHD-vs-THD, CP-vs-CP=1 and patch-merger parity checks they implement only ever ran by hand. Each comparison body is factored into a reusable function that both a pytest entry point and the originalmain()call, so the CLI and the tests cannot drift; a per-moduleDEFAULTSdict backs both the argparse defaults and the test parameters. CP sizes that do not divide the world sizeskiprather than fail, and every module leaves the model-parallel groups torn down, so the files work at any--nproc-per-nodeand share one pytest process. The__main__entry points are unchanged in behaviour and were re-verified after the refactor. Net: +7 executing tests.Wire up the bucket. New
test_case: [examples/multimodal_dev/tests/**/*.py]intests/test_utils/recipes/h100/unit-tests.yaml, plus a localconftest.py. The bucket is collected outsidetests/unit_tests/, sotests/unit_tests/conftest.pydoes not apply to it; the local conftest supplies exactly whattests/unit_tests/run_ci_test.shdepends on — the--experimentaloption its second pytest pass passes, and thepytest_sessionfinishhook that maps pytest's "no tests collected" (exit 5) to success.Scope of the bucket
environment: [dev],tag: [latest], deliberately:tag: latestonly — the legacy ref pins an older mcore and an older copy of this example, so the pairing this bucket guards does not exist there.environment: devonly — validated against the dev container;ltsis unverified and an unverified entry risks a red bucket on older TE. Widening is a one-line change once it has been observed green.gb200 is not wired up: selection there is marker-driven (
launch_on_gb200) and no file in this suite carries the marker, so the bucket would launch an empty job.Validation (1×8 H100)
dev@9304a2564, baselineThe exact CI entry point was then run with the tree bind-mounted at
/opt/megatron-lmso the script's hardcoded paths resolve as they do in CI:Pass 2 is what the new conftest exists for:
--experimentalis accepted and the resulting exit code 5 is mapped to 0. Recipe resolution was checked throughrecipe_parser.load_workloads(scope='unit-tests', model='unit-tests', test_case='examples/multimodal_dev/tests/**/*.py', environment='dev', tag='latest'), which returns the workload plus itsmcore-pyt-devbuild dependency like every other bucket, andfind_test_cases.pyreturns no--ignoreargs (no child buckets). Bucket wall clock ~40 s.Issue tracking
Linked issue: none. This is a test/CI change with no runtime impact.
Contribution process
Pre-checks
dev/latestscoping, and each converted module documents its pytest invocation.pyfiles undermegatron/coreandtests/, and this PR's onlytests/change is a YAML🤖 Generated with Claude Code