Lit/qwen35 benchmark - #4
Conversation
Adds a standalone VLM training playground under ``examples/multimodal_dev/`` with Qwen3.5-VL end-to-end. Highlights - Model-agnostic entry point (``pretrain_multimodal.py``) with a ``MODEL_REGISTRY`` so adding a new architecture is just a registry entry plus a backing module. - Qwen3.5-VL model: vision encoder, MRoPE, decoder, factory, specs, configurations covering proxy / 9B / 397B-A17B variants. - Datasets: mock data and CORD-V2 VLM dataset, with THD pack/pad in the collate function. - THD + CP support consolidated in ``forward_step.py`` and the model layer (uses MRoPE THD pre-computation and ``cu_seqlens_q_padded`` CP partitioning). - Run script + README, plus tests for MRoPE parity, CP correctness, CP support, and THD correctness / e2e. Also gates the torch DataLoader vanilla-collate path on the new ``use_vanilla_collate_fn`` arg (one-line change to ``megatron/training/datasets/data_samplers.py``) so CORD-V2 works under BSHD. Functional dependency: the new model arch sets ``mrope_interleaved=True`` in its config and relies on the core MRoPE interleaved layout introduced in a separate PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: BestJuly <19769279+BestJuly@users.noreply.github.com>
… preprocessing
Fixes 8 issues in vlm_dataset.py found by review against Megatron-Bridge's
qwen2_5_collate_fn reference implementation.
- loss_mask off-by-one (Bug 1): the previous mask was built on input_ids
while labels were shifted, dropping the image->text supervision signal
at the boundary. Now masks structural tokens on the shifted labels and
also shifts loss_mask itself left by 1.
- missing SFT prompt masking (Bug 2): user-turn and chat-template tokens
were trained on. Now uses backward substring token search (mirroring
create_multiturn_loss_mask_by_search) to unmask only the assistant
answer span.
- seq_length not enforced (Bug 3): long CORD-V2 samples could overflow.
Now end-truncates input_ids in __getitem__ with a warning.
- unsafe pad_token_id fallback (Bug 4): falling back to 0 silently masked
a real vocab token. Now falls back to EOS and raises if neither is set.
- silent image_token_id miss (Bug 6): fallback could return None, causing
dataset / model disagreement. Now raises ValueError.
- stale docstrings (Bug 8): updated Qwen2.5-VL / --image-size references
to Qwen3.5-VL / --total-seq-length.
- narrow skipped_tokens set (Bug 14): vision_start/end, im_start/end,
video_pad, endoftext were not masked on labels. Now uses
tok.all_special_ids union {pad_id, image_token_id}.
- lost Qwen-VL dynamic resolution (Bugs 15/17/19): fixed-square resize
removed; conversation content carries the image object;
qwen_vl_utils.process_vision_info extracts images; processor is called
with min_pixels / max_pixels.
- pixel_values bf16 conversion (Bug 18): moved from forward_step into the
dataset so per-step dtype checks become no-ops.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- raise --manual-gc-interval 5 → 50 to cut GC pause frequency on long runs. - enable --moe-permute-fusion and --moe-router-fusion in the MoE branch (no-op for dense variants since MOE_ARGS is gated on NUM_EXPERTS>0). - enable grad-accumulation fusion under FSDP by dropping --no-gradient-accumulation-fusion from FSDP_ARGS. - add --log-timers-to-tensorboard and --log-params-norm to surface timer breakdown and parameter L2 norm in TB/wandb. - drop the hardcoded CKPT_LOAD path from the in-script example invocations so the comment reflects from-scratch CP correctness runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update the 'Copyright (c) 2025, NVIDIA CORPORATION' line to 2026 across all newly-added Python files under examples/multimodal_dev/ for the Qwen3.5-VL training example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…1 vs CP=4 correctness - test_thd_e2e.py: rewrite TestPackBatch -> TestPackOrPadBatchPacked/Padded using the per-sample dict input shape produced by the dataset; drop attention_mask / position_ids / MRoPE / user cu_seqlens cases that are no longer the helper's concern. Add TestPackOrPadBatchDivisibleBy4 covering per-sample alignment when cp_size=2 forces divisible_by=4 (via monkeypatched mpu). - test_thd_correctness.py: swap _pack_batch(batch_dict) for pack_or_pad_batch(per-sample list, use_packed_sequence=True); compute THD position_ids locally since the helper no longer carries them. - test_cp_thd_correctness.py (new): single-torchrun script comparing CP=1 and CP=4 in one process via destroy + re-initialize model_parallel, with weights pinned by a state_dict snapshot. Uses MultimodalModel with a stub vision encoder (vision branch skipped via pixel_values=None); loss aggregated by AllReduce-SUM of (num, den) on the CP group; grad_norm aggregated by AllReduce-SUM of gradients on the CP group then dividing by cp_size, so each rank holds the CP-mean gradient (equivalent to CP=1's backward on the full-batch mean loss). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ess + small cleanups
- forward_step.py:pack_or_pad_batch — previously crashed for TP>1 because
the per-sample loop dereferenced data on non-source ranks (which receive
data=None from get_batch), and cu_seqlens/max_seqlen used to build
PackedSeqParams were local Python lists never broadcast. Now: gate the
build loop on TP rank 0, broadcast cu_seqlens / cu_seqlens_padded as
part of the data dict, and derive max_seqlen / total_tokens from the
(broadcast) cu_seqlens on every rank — no extra collective.
- models/base.py — add public MultimodalModel.cp_split_loss_mask
(staticmethod) so the post-forward loss path doesn't need to import the
module's private _cp_split_tensor / _thd_cp_partition_index.
forward_step.py uses it instead of duplicating the slicing logic.
- forward_step.py — replace bare `except Exception` around get_args() with
`getattr(get_args(), 'sequence_parallel', False)` + AssertionError-only
fallback (matches what megatron's get_args() actually raises when args
are uninitialised in tests). Strip three WHAT/TODO comments that
narrated intent rather than explaining a non-obvious why.
- tests/_helpers.py (new) — shared grad_norm / mean_loss helpers.
test_thd_correctness.py uses them in place of its local copies.
test_cp_thd_correctness.py keeps its CP-aware _global_loss /
_global_grad_norm (genuinely different — they add AllReduce on the CP
group); the duplication noted in review was overstated.
- tests/test_cp_thd_correctness.py — drop _StubVisionEncoder's dummy
nn.Linear(1,1); MegatronModule does not need a parameter for state_dict
round-tripping (verified by re-running the CP=1 vs CP=4 suite — numbers
identical to the previous commit).
Verified locally:
- test_thd_e2e.py: 20/20 passed
- test_thd_correctness.py: ALL PASSED (BSHD vs THD equal-length parity)
- test_cp_thd_correctness.py: ALL PASSED (CP=1 vs CP=4, same numbers as
previous commit ec6d2d3: BSHD/THD loss + grad_norm)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(pylint 10/10)
Run `bash tools/autoformat.sh` toolchain (black --skip-magic-trailing-comma
--skip-string-normalization, isort, ruff check, pylint, mypy) directly on
the changed files (autoformat.sh only scans megatron/core + tests/, so
these don't normally go through the gate):
- black: reformat 6 files for line length / wrapping
- pylint: drop unused imports (test_thd_correctness.py: parallel_state,
_build_packed_seq_params; test_cp_thd_correctness.py: parallel_state);
add docstrings to 9 small functions / methods (test methods,
_NoCPGroup.size/rank, _StubVisionEncoder.__init__/forward, main()
entrypoints); add module-level `# pylint: disable=bad-builtin` to the
two stdout-reporting standalone scripts (test_thd_correctness.py,
test_cp_thd_correctness.py) where the many `print()`s are intentional.
- mypy: replace implicit Optional defaults — `seq_length: int = None` →
`Optional[int] = None` in pack_or_pad_batch; same for `mrope_section`
and `mtp_block_spec` in MultimodalModel.__init__; tighten
`get_batch(data_iterator: Iterator[Dict[str, Any]])` to
`Iterator[list[Dict[str, Any]]]` so the call to pack_or_pad_batch
type-checks.
Remaining mypy diagnostic — `transformer_engine.pytorch` missing
library-stub marker — is repo-wide (also flagged on `megatron/core/` files
in the main run) and tolerated because autoformat.sh runs mypy with
`|| true`.
Verified locally:
- test_thd_e2e.py: 20/20 passed
- test_thd_correctness.py: ALL PASSED
- test_cp_thd_correctness.py: ALL PASSED (CP=1 vs CP=4, numbers
identical to ec6d2d3 / 4332813)
- pylint score: 10.00/10 (was 9.48/10 → 9.95/10 → 10.00/10)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-line follow-up to 0211665 — `_helpers.py` was missed in the preceding bulk lint commit's `git add`. Black removes the spaces around `**` (`total ** 0.5` -> `total**0.5`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… overlap profiling Wrap the 6 execution entry points in SharedExpertMLP with nvtx_range_push/pop so the dedicated shared-expert stream shows up as named slices in nsys profiles. Useful for inspecting where shared-expert FFN sits relative to the hybrid-ep dispatch A2A when --moe-shared-expert-overlap is on.
The hardware-limit guardrail in fused_a2a.py asserts tx_depth < 65536, but this fires on configurations that run cleanly in practice (e.g. Qwen3.5-VL 397B-A17B at MBS=6 / seq=4096 / 128 GPU produces tx_depth=73729 and the collective still completes correctly). Per expert guidance, downgrade from raise to commented-out — re-enable only if a real RDMA QP failure is reproduced. This is a temporary patch for the qwen35-vl-hybridep deployment branch; should be replaced with a proper fix upstream once root cause is identified.
Reviewer's GuideIntegrates a Triton-based fused multimodal RoPE (mRoPE) path into Megatron for both standard and THD-packed layouts, wires it through GPT and Qwen3.5-VL vision models with new configuration/CLI knobs and NVTX profiling support, and adds extensive unit tests while relaxing a HybridEP RDMA guard. Sequence diagram for runtime selection of fused vs unfused mRoPEsequenceDiagram
participant Caller
participant apply_rotary_pos_emb
participant fused_mrope
participant TE_fused as fused_apply_rotary_pos_emb
participant Unfused as _apply_rotary_pos_emb_bshd
Caller->>apply_rotary_pos_emb: apply_rotary_pos_emb(t, freqs, config,...)
apply_rotary_pos_emb->>apply_rotary_pos_emb: is_raw_mrope_freqs
alt raw mRoPE and cu_seqlens is None
apply_rotary_pos_emb->>fused_mrope: get_fused_mrope_unavailable_reason
alt fused available
apply_rotary_pos_emb->>fused_mrope: fused_apply_mrope(t, freqs,...)
fused_mrope-->>apply_rotary_pos_emb: rotated_t
else fused unavailable or options unsupported
apply_rotary_pos_emb->>fused_mrope: mrope_freqs_to_rotary_emb
fused_mrope-->>apply_rotary_pos_emb: rotary_emb
apply_rotary_pos_emb->>Unfused: _apply_rotary_pos_emb_bshd(t, rotary_emb,...)
Unfused-->>apply_rotary_pos_emb: rotated_t
end
else TE RoPE fusion path
apply_rotary_pos_emb->>TE_fused: fused_apply_rotary_pos_emb or fused_apply_rotary_pos_emb_thd
TE_fused-->>apply_rotary_pos_emb: rotated_t
end
apply_rotary_pos_emb-->>Caller: rotated_t
Flow diagram for fused mRoPE integration across componentsflowchart LR
GPT[GPTModel._preprocess]
MRE[MultimodalRotaryEmbedding.forward]
RU[apply_rotary_pos_emb]
FM[fused_mrope module]
GPT -->|position_embedding_type=mrope\nuse_raw_mrope_freqs| MRE
MRE -->|return_raw_freqs=True\npacked_seq flag| RU
RU -->|is_raw_mrope_freqs| FM
FM -->|fused_apply_mrope /\nfused_apply_mrope_thd| RU
RU -->|rotated tensor| GPT
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
_apply_rope_fp32_no_cpyou callnvtx_range_pop(range_name), butnvtx_range_popis typically a no-arg function that pops the last range; passing the name may be ignored or error-prone, so consider dropping the argument and just callingnvtx_range_pop(). - The new validation helpers in
fused_mrope.py(e.g._validate_mrope_section,_validate_mrope_inputs,_validate_mrope_thd_inputs) rely heavily onassert, which will be stripped when Python is run with optimizations; consider converting these to explicitValueError/RuntimeErrorchecks so invalid shapes and sections still generate clear errors in production.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_apply_rope_fp32_no_cp` you call `nvtx_range_pop(range_name)`, but `nvtx_range_pop` is typically a no-arg function that pops the last range; passing the name may be ignored or error-prone, so consider dropping the argument and just calling `nvtx_range_pop()`.
- The new validation helpers in `fused_mrope.py` (e.g. `_validate_mrope_section`, `_validate_mrope_inputs`, `_validate_mrope_thd_inputs`) rely heavily on `assert`, which will be stripped when Python is run with optimizations; consider converting these to explicit `ValueError`/`RuntimeError` checks so invalid shapes and sections still generate clear errors in production.
## Individual Comments
### Comment 1
<location path="examples/multimodal_dev/tests/test_vision_rope_fusion.py" line_range="71" />
<code_context>
+ torch.testing.assert_close(converted, expected)
+
+
+def test_vision_fp32_wrapper_dispatches_raw_freqs_to_fused_mrope_thd(monkeypatch):
+ calls = {}
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a complementary test for the fallback path when fused THD mRoPE is unavailable in the vision FP32 wrapper.
The current test only exercises the case where `fused_apply_mrope_thd` is available. Please also add a test where `get_fused_mrope_thd_unavailable_reason` returns a non-None value so the code takes the unfused `apply_rotary_pos_emb` path. That test should verify that `fused_apply_mrope_thd` is not called, no `fp32_fused` flag is propagated incorrectly, and the numerical output matches the unfused implementation, so both branches of the wrapper are covered.
Suggested implementation:
```python
def test_vision_fp32_wrapper_falls_back_to_unfused_when_fused_thd_unavailable(
monkeypatch,
):
# Import the wrapper and rotary helpers from the module under test.
# The exact import path / names may need to be aligned with the rest of this file.
from examples.multimodal_dev.vision_rope_fusion import ( # type: ignore[import]
vision_fp32_wrapper,
apply_rotary_pos_emb,
fused_apply_mrope_thd,
get_fused_mrope_thd_unavailable_reason,
)
calls = {"fused": 0, "unfused": 0}
# Force the wrapper down the unfused path by reporting that the fused kernel
# is unavailable.
def fake_unavailable_reason(*args, **kwargs):
return "no fused kernel"
# If the fused path is taken in this test, fail loudly.
def fake_fused_apply(*args, **kwargs):
calls["fused"] += 1
raise AssertionError("fused_apply_mrope_thd must not be called in fallback path")
# Keep a handle to the real unfused implementation so we can both wrap it and
# use it to compute the reference output.
real_apply_rotary_pos_emb = apply_rotary_pos_emb
def wrapped_apply_rotary_pos_emb(q, k, freqs, *args, fp32_fused=None, **kwargs):
# The fallback path must not propagate an fp32_fused flag.
assert fp32_fused is None
calls["unfused"] += 1
return real_apply_rotary_pos_emb(q, k, freqs, *args, **kwargs)
# Patch the module-under-test symbols so the wrapper sees the fake behavior.
monkeypatch.setattr(
"examples.multimodal_dev.vision_rope_fusion.get_fused_mrope_thd_unavailable_reason",
fake_unavailable_reason,
raising=True,
)
monkeypatch.setattr(
"examples.multimodal_dev.vision_rope_fusion.fused_apply_mrope_thd",
fake_fused_apply,
raising=True,
)
monkeypatch.setattr(
"examples.multimodal_dev.vision_rope_fusion.apply_rotary_pos_emb",
wrapped_apply_rotary_pos_emb,
raising=True,
)
device = "cuda" if torch.cuda.is_available() else "cpu"
# Build small but non‑trivial test inputs; shapes can be adjusted to match
# the rest of the vision tests if needed.
q = torch.randn(2, 1, 4, 8, dtype=torch.float16, device=device)
k = torch.randn_like(q)
freqs = torch.randn(1, 1, 4, 4, dtype=torch.float16, device=device)
# Reference output using the real unfused implementation.
ref_q, ref_k = real_apply_rotary_pos_emb(q, k, freqs)
# Call the FP32 wrapper, which should dispatch to the unfused path.
out_q, out_k = vision_fp32_wrapper(q, k, freqs)
# Verify dispatch behavior.
assert calls["fused"] == 0
assert calls["unfused"] == 1
# And verify numerics match the unfused implementation.
torch.testing.assert_close(out_q, ref_q)
torch.testing.assert_close(out_k, ref_k)
"""Tests for Qwen3.5-VL vision RoPE fusion dispatch."""
```
1. Adjust the import in the new test to use the actual module and symbol names for:
- The vision FP32 wrapper under test (`vision_fp32_wrapper` is a placeholder).
- `apply_rotary_pos_emb`, `fused_apply_mrope_thd`, and `get_fused_mrope_thd_unavailable_reason`.
If these are already imported at module scope in this test file, you can drop the local `from examples... import ...` and just use the existing names.
2. Update the `monkeypatch.setattr` target strings (`"examples.multimodal_dev.vision_rope_fusion.*"`) to match the real module path where the wrapper and helpers are defined.
3. If the wrapper’s calling convention differs (e.g., additional arguments, different tensor shapes, or named parameters for `raw_freqs`), adjust the construction of `q`, `k`, `freqs` and the `vision_fp32_wrapper` call accordingly, mirroring the existing `test_vision_fp32_wrapper_dispatches_raw_freqs_to_fused_mrope_thd` test.
4. If the unfused helper already accepts or requires an `fp32_fused` keyword, adapt the assertion in `wrapped_apply_rotary_pos_emb` so that it checks for the expected value in the fallback path (the key requirement is that the “fp32_fused” flag is not incorrectly forced to True when taking the unfused path).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| torch.testing.assert_close(converted, expected) | ||
|
|
||
|
|
||
| def test_vision_fp32_wrapper_dispatches_raw_freqs_to_fused_mrope_thd(monkeypatch): |
There was a problem hiding this comment.
suggestion (testing): Add a complementary test for the fallback path when fused THD mRoPE is unavailable in the vision FP32 wrapper.
The current test only exercises the case where fused_apply_mrope_thd is available. Please also add a test where get_fused_mrope_thd_unavailable_reason returns a non-None value so the code takes the unfused apply_rotary_pos_emb path. That test should verify that fused_apply_mrope_thd is not called, no fp32_fused flag is propagated incorrectly, and the numerical output matches the unfused implementation, so both branches of the wrapper are covered.
Suggested implementation:
def test_vision_fp32_wrapper_falls_back_to_unfused_when_fused_thd_unavailable(
monkeypatch,
):
# Import the wrapper and rotary helpers from the module under test.
# The exact import path / names may need to be aligned with the rest of this file.
from examples.multimodal_dev.vision_rope_fusion import ( # type: ignore[import]
vision_fp32_wrapper,
apply_rotary_pos_emb,
fused_apply_mrope_thd,
get_fused_mrope_thd_unavailable_reason,
)
calls = {"fused": 0, "unfused": 0}
# Force the wrapper down the unfused path by reporting that the fused kernel
# is unavailable.
def fake_unavailable_reason(*args, **kwargs):
return "no fused kernel"
# If the fused path is taken in this test, fail loudly.
def fake_fused_apply(*args, **kwargs):
calls["fused"] += 1
raise AssertionError("fused_apply_mrope_thd must not be called in fallback path")
# Keep a handle to the real unfused implementation so we can both wrap it and
# use it to compute the reference output.
real_apply_rotary_pos_emb = apply_rotary_pos_emb
def wrapped_apply_rotary_pos_emb(q, k, freqs, *args, fp32_fused=None, **kwargs):
# The fallback path must not propagate an fp32_fused flag.
assert fp32_fused is None
calls["unfused"] += 1
return real_apply_rotary_pos_emb(q, k, freqs, *args, **kwargs)
# Patch the module-under-test symbols so the wrapper sees the fake behavior.
monkeypatch.setattr(
"examples.multimodal_dev.vision_rope_fusion.get_fused_mrope_thd_unavailable_reason",
fake_unavailable_reason,
raising=True,
)
monkeypatch.setattr(
"examples.multimodal_dev.vision_rope_fusion.fused_apply_mrope_thd",
fake_fused_apply,
raising=True,
)
monkeypatch.setattr(
"examples.multimodal_dev.vision_rope_fusion.apply_rotary_pos_emb",
wrapped_apply_rotary_pos_emb,
raising=True,
)
device = "cuda" if torch.cuda.is_available() else "cpu"
# Build small but non‑trivial test inputs; shapes can be adjusted to match
# the rest of the vision tests if needed.
q = torch.randn(2, 1, 4, 8, dtype=torch.float16, device=device)
k = torch.randn_like(q)
freqs = torch.randn(1, 1, 4, 4, dtype=torch.float16, device=device)
# Reference output using the real unfused implementation.
ref_q, ref_k = real_apply_rotary_pos_emb(q, k, freqs)
# Call the FP32 wrapper, which should dispatch to the unfused path.
out_q, out_k = vision_fp32_wrapper(q, k, freqs)
# Verify dispatch behavior.
assert calls["fused"] == 0
assert calls["unfused"] == 1
# And verify numerics match the unfused implementation.
torch.testing.assert_close(out_q, ref_q)
torch.testing.assert_close(out_k, ref_k)
"""Tests for Qwen3.5-VL vision RoPE fusion dispatch."""- Adjust the import in the new test to use the actual module and symbol names for:
- The vision FP32 wrapper under test (
vision_fp32_wrapperis a placeholder). apply_rotary_pos_emb,fused_apply_mrope_thd, andget_fused_mrope_thd_unavailable_reason.
If these are already imported at module scope in this test file, you can drop the localfrom examples... import ...and just use the existing names.
- The vision FP32 wrapper under test (
- Update the
monkeypatch.setattrtarget strings ("examples.multimodal_dev.vision_rope_fusion.*") to match the real module path where the wrapper and helpers are defined. - If the wrapper’s calling convention differs (e.g., additional arguments, different tensor shapes, or named parameters for
raw_freqs), adjust the construction ofq,k,freqsand thevision_fp32_wrappercall accordingly, mirroring the existingtest_vision_fp32_wrapper_dispatches_raw_freqs_to_fused_mrope_thdtest. - If the unfused helper already accepts or requires an
fp32_fusedkeyword, adapt the assertion inwrapped_apply_rotary_pos_embso that it checks for the expected value in the fallback path (the key requirement is that the “fp32_fused” flag is not incorrectly forced to True when taking the unfused path).
340259d to
3ec49ed
Compare
79f6060 to
ea678f6
Compare
What does this PR do ?
Contribution process
flowchart LR A[Pre-checks] --> B[PR Tests] subgraph Code Review/Approval C1[Expert Review] --> C2[Final Review] end B --> C1 C2 --> D[Merge]Pre-checks
Core 0.8)Code review
The following process is enforced via the CODEOWNERS file for changes into
megatron/core. For changes outside ofmegatron/core, it is up to the PR author whether or not to tag the Final Reviewer team.For MRs into `main` branch
Feel free to message or comment the @megatron-oncall to help accelerate your merge into main. The less complex your PR is, the faster it will be approved and merged!
(Step 1): Add PR label
Expert Review(Step 2): Collect the expert reviewers reviews
Expert Reviewlabel when your PR is ready for review.Final Review might get declined if these requirements are not fulfilled.
(Step 3): Final Review
Final Reviewlabel(Optional Step 4): Cherry-pick into release branch
If this PR also needs to be merged into
core_r*release branches, after this PR has been merged, selectCherry-pickto open a new PR into the release branch.For MRs into `dev` branch
The proposed review process for `dev` branch is under active discussion.MRs are mergable after one approval by either
eharper@nvidia.comorzijiey@nvidia.com.Merging your PR
Any member of core-adlr and
core-nemowill be able to merge your PR.Summary by Sourcery
Integrate Triton-based fused multimodal RoPE (mRoPE) kernels into Megatron, wire them through GPT and Qwen3.5-VL vision paths, and extend scripts and configs to benchmark fused vs unfused RoPE while relaxing an overly-strict HybridEP safety check.
New Features:
Bug Fixes:
Enhancements:
Tests: