Skip to content

MInf: Add hooks to preprocess and stage - #7015

Merged
tdene merged 25 commits into
NVIDIA:mainfrom
tdene:tde/ledger_capture
Sep 19, 2026
Merged

tdene merged 25 commits into
NVIDIA:mainfrom
tdene:tde/ledger_capture

Conversation

@tdene

@tdene tdene commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor
  • I, the PR author, have personally reviewed every line of this PR.

What does this PR do?

⚠️ For major changes (either in lines of code or in its impact), please make sure to first share a design doc with the team. If you're unsure what's the best way to do so, contact @NVIDIA/mcore-oncall.

Issue tracking

For PRs from open-source community contributors:

  • New features: a linked issue is required. Please open a feature request and reference it here before submitting the PR.
  • Small updates (bug fixes, minor improvements): a linked issue is recommended and will accelerate the PR review process.

Linked issue:

Contribution process

Pre-checks

  • I have added relevant unit tests
  • I have added relevant functional tests
  • I have added proper typing to my code Typing guidelines
  • I have added relevant documentation
  • I have run the autoformatter.sh on my PR

Code review

Feel free to message or comment @NVIDIA/mcore-oncall to help accelerate your merge into main. The less complex your PR is, the faster it will be approved and merged!

All PRs start as draft. If you open a non-draft PR, it will be automatically converted to draft.

Step 1: Mark PR as "Ready for Review"

  1. When your PR is ready, click Ready for Review.
  2. An oncall reviewer is auto-assigned and expert reviewers are notified based on your changes.
    • Some PRs may jump straight to step 2. This is determined by .github/CODEOWNERS.

⚠️ Only mark as ready once merge-conflicts are resolved and the CI is passing.
Final Review might get declined if these requirements are not fulfilled.

Step 2: Final Review

For PRs that change megatron/core, once all expert reviewers have approved, the Final Review label is applied automatically and final reviewers are assigned.

For PRs outside megatron/core, this step is skipped.

Step 3: Approved

Once all required reviewers have approved, the Approved label is applied automatically.

Merge

Any member of mcore-engineers will be able to merge your PR.

@tdene
tdene requested review from a team as code owners September 1, 2026 09:37
@svcnvidia-nemo-ci
svcnvidia-nemo-ci marked this pull request as draft September 1, 2026 09:37
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This PR has been automatically converted to draft because all PRs must start as drafts.

When you are ready for review, click Ready for Review to begin the review process. This will:

  1. Add the oncall reviewer (optional reviewer)
  2. Add required review teams based on your changes

See the contribution guide for more details.

@copy-pr-bot

copy-pr-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

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.

@sidsingh-nvidia

Copy link
Copy Markdown
Contributor

/claude strict-review

Comment thread megatron/rl/rl_utils.py Outdated
Comment thread megatron/rl/rl_utils.py Outdated
Comment thread megatron/rl/inference/megatron.py
Comment thread megatron/core/inference/inference_request.py Outdated
Comment thread megatron/core/inference/inference_request.py Outdated
Comment thread megatron/core/inference/engines/dynamic_engine.py Outdated
Comment thread megatron/core/inference/engines/dynamic_engine.py Outdated
Comment thread megatron/rl/agent/reward_only_agent.py Outdated
Comment thread megatron/core/inference/inference_request.py Outdated
Comment thread megatron/core/inference/inference_request.py Outdated
Comment thread megatron/core/inference/engines/dynamic_engine.py Outdated
Comment thread megatron/rl/rl_utils.py Outdated
Comment thread megatron/rl/inference/megatron.py
@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Strict Review Summary — PR #7015 "MInf: Move metadata out of RESTful wires"

Findings: CRITICAL: 5 · IMPORTANT: 9 · SUGGESTION: 2 (16 inline comments)

Most impactful

  1. Guaranteed unit-test failure. rl_utils.py:775 asserts "...has no logprob payload." but tests/unit_tests/rl/test_rl_utils.py:2193 matches "no log-prob payload". pytest.raises(match=...) is a re.search against the message, so test_backfill_inference_logprobs fails deterministically. A one-character fix, but it blocks CI.

  2. Restored rollout groups silently train on zero-filled IS logprobs. rollout_pipeline.py:491 persists the group to the RolloutBank inside stage_assemble, which runs before backfill_inference_logprobs (rl_utils.py:923). RolloutBank._decode clears both logprobs and completion_ids on recovery, so a restored group has no completion id to look up and no payload to join. Downstream, prepare_trajectories builds inf_logprobs = [], and align_unpacked_inference_logprobs truncates via n = min(len(inf_logprobs), target.numel()) — producing an all-zero inference-logprob tensor and a silently wrong importance-sampling ratio, with no error raised. This is a wrong-gradients path, not a crash path.

  3. Wire stripping and ledger capture are gated on different conditions. In _send_request_records_to_coordinator (dynamic_engine.py:1278-1285), capture requires local_metadata_ledger_enabled, but request.serialize(ledger_offload=self.local_metadata_ledger_offload_enabled) is gated on the offload flag alone. With offload on and the ledger off, payloads are dropped from the wire and never captured anywhere. Status.FAILED requests hit the continue before capture yet are still serialized with stripping applied — same unrecoverable outcome.

  4. Streaming clients lose logprobs with no marker and nothing to join. _try_send_streaming_partials (dynamic_engine.py:2922-2929) omits new_log_probs/prompt_log_probs under offload, but partial frames carry no ledger_offload field and precede the ledger record. openai_streaming.py therefore accumulates nothing and emits logprob: null in the SSE logprobs object — an OpenAI-API-visible regression for any streaming consumer.

  5. ReturnsLogProbs gate is unconditionally False. reward_only_agent.py:199-205 guards logprob propagation on isinstance(request.inference_interface, ReturnsLogProbs), but that mix-in has no subclasses anywhere in the tree — MegatronLocal and InferenceInterfaceServer both inherit only ReturnsTokens/ReturnsRaw. Logprobs are silently discarded on every path, including the InferenceInterfaceServer path that has no ledger to fall back on.

Other themes

  • Performance: all_gather_object in merge_global_request_ledgers now pickles the newly-captured heavy payload and amplifies it by world size; FinishedRequestRecord.from_request does a blocking value.cpu().tolist() per completion on the engine hot path.
  • Unused new state: 4 of the 5 new FinishedRequestRecord fields have no read site in production code (only generated_log_probs is consumed), while routing_indices and prompt_log_probs are simultaneously stripped from the wire — making moe_topk_indices and echo prompt logprobs unrecoverable by any client.
  • Scope: required_prefix_token_ids in chat_completions.py is unrelated to the stated PR goal, undocumented, untested, and bypasses both the prior-generation guard and compact_prompt_token_ids validation. Suggest splitting it out.
  • Contract/lifecycle: serialize()s mutate-restore is not exception-safe across the field-nulling loop and is not reentrant; the ledger is no longer cleared by reset() and can grow unboundedly, and its two accessor modes (custody vs. barrier) are documented only in prose.

Risk assessment: High

One deterministic CI failure plus several silent-failure paths, all on the configuration this PR makes the MRL default (megatron/rl/inference/megatron.py:108-109 enables both ledger flags). The dangerous class here is not crashes but silently degraded training signal and silently dropped API fields. Recommend fixing (1) and (2) before merge, resolving the flag-gating asymmetry in (3), and adding an explicit length/consistency assertion at the backfill join so a missing or mismatched payload fails loudly rather than truncating to zeros.

C12: move offload_params out of the metadata frame into a fifth client frame that the coordinator forwards verbatim (never decoded), so the frame it unpacks and repacks per request stays bounded; the engine reads it from its own frame at admission and the preparer rewrites only the prompt and offload frames.
C11: schedule_requests logs a warning and drops a SUBMIT_REQUEST with the wrong frame or metadata-field count instead of raising, so one version-skewed client cannot take every MP rank down; the skip is collective because all ranks see the same broadcast list.
C13: _prepare_submit_request_message returns the message untouched, without decoding any frame, when the offload frame is msgpack nil, since the preparer only has work when the client sent params.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Laura Dang <laurad@nvidia.com>
lauradang and others added 2 commits September 16, 2026 23:41
…contract

- serialize(): an offloaded reply never carries the prompt tensors (the stager holds the prompt ids); otherwise they stay opt-in via return_prompt_tokens and are dropped when sampling_params is None, as on main.
- _send_requests_to_coordinator(): build FinishedRequestRecord once per completed request and share it between the ledger and _serialize_finished_request().
- OffloadedRequestPayload.from_request(): document the prompt_tokens host-copy side effect.
- payload_offloaded / payload_stage_metadata: reword the field comment; they are wire-only keys kept for deserialize().
- checkpoint()/merge(): share offload_params instead of deep-copying; nothing mutates it in place.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Laura Dang <laurad@nvidia.com>
The finished-request rename left two references to the old merged record in the GPU-only offload test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Laura Dang <laurad@nvidia.com>
… in offload_params

E19: /v1/completions now collects each reply's payload_stage_metadata, rejects conflicting values across a batch, and merges it into the top-level response body with the same reserved-field check as /v1/chat/completions; the shared logic lives in endpoints/common.py (collect_stage_metadata, attach_stage_metadata). The endpoint also accepts and forwards offload_params, which it previously ignored.
E20: both endpoints reject client-supplied offload_params whose top-level keys start with '_' (HTTP 400) via endpoints.common.validate_offload_params, so the engine-owned _request_prompt_preparation_error field cannot be forged from a request body.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Laura Dang <laurad@nvidia.com>
Comment thread megatron/core/inference/inference_request.py Outdated
Signed-off-by: Laura Dang <laurad@nvidia.com>
@sidsingh-nvidia

Copy link
Copy Markdown
Contributor

Tested TQ + Router replay in nemo-rl, using this branch. Seems to work.

Resolve conflicts with NVIDIA#7352 (raw-tensor serialization and vision caching):
- dynamic_engine.py: keep both offload_params and media_cache_key on
  add_request/_build_vlm_request; unpack the offload frame inside the
  nvtx-wrapped SUBMIT_REQUEST decode.
- chat_completions.py: pass prepared_multimodal_data together with
  offload_params at both submission sites.
- test_inference_request.py: compare unwrapped tensor values while keeping
  the payload_offloaded case.
- test_chat_completions.py: let the fake client accept offload_params.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Laura Dang <laurad@nvidia.com>
@tdene

tdene commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test dd5439b

Two tests in test_dynamic_engine.py were left inconsistent by the merge of
main into this branch:

- test_schedule_requests_skips_cached_media_payload_and_preprocessing came
  from main with a three-frame SUBMIT_REQUEST. This branch moves
  offload_params into a fourth frame, so the engine dropped the message as
  malformed and add_request was never called. Send the packed-None offload
  frame and expect offload_params=None in the add_request kwargs.

- test_payload_offload_stages_only_eligible_completed_replies compared the
  wire prompt tensors against the legacy ("tensor", [1, 2, 3]) list form.
  Main now serializes tensors as binary dicts, so compare the unwrapped
  values via unwrap_serialized_tensors instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Laura Dang <laurad@nvidia.com>
@tdene

tdene commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 6dc0789

Resolve the conflict in chat_completions.py against NVIDIA#7312: keep this branch's
restructured prefix-replacement block and fold in main's fallback to
prompt_token_ids when compact_prompt_token_ids are absent and no media is present.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Laura Dang <laurad@nvidia.com>
@nemo-automation-bot

Copy link
Copy Markdown

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/35426178916

This branch was successfully deployed

2 active deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved All necessary approvals have been made complexity: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants