Companion illustration for
NVIDIA-NeMo/RL #4108
β all permalinks pinned to
d044f40.
d044f40 after the rebase; all still present. Each has its own review comment, linked from its section.Three pages on this PR, one question each β this one: the nine things that have to change first, one section each.
The review left fourteen comments. Five are design cleanups that can land afterwards. The nine below stop this from merging: three that a CI job refuses, and six that no job checks at all β those only appear once someone runs a real training job.
RL's main pins an ordinary commit on Bridge's main line. This PR moves the pin
onto 3961f399, which is the head of the chcui/sage/nmvl feature branch β
Bridge PR #5894 was merged into that branch, not into main. Taking it adds 42 commits
and drops 45, including a Nemotron VL config fix. CI's submodule fast-forward job refuses it.
Fix: say which Bridge changes this needs, land them on Bridge main, then repin to something that fast-forwards from 5ed97996.
π΄ review comment β Megatron-Bridge submodule
secrets-detector redBoth flags are false positives β the detector trips on the long hyphenated path, not on a real secret. But the paths are only there because the file is one person's scratch config: it hardcodes a single filesystem for model, dataset, cache, logs and checkpoints, ships a one-off run name, and leaves the debug flag on. Its commit message is "working config?".
Fix: drop the file. That clears the job with no baseline edit. Regenerating the detector baseline would hide the warning and leave the paths in public source.
π΄ review comment β n16soak.yaml:104
Three of us ran the suite in three separate virtualenvs and got identical counts. It is not
an environment artifact: two of the five files pass 100% in the same interpreter. Eighteen of the
failures come from one cause β a new @stateless(restore_seeds=True) makes
preencode_sample unusable outside a live Energon worker, and the tests call it
directly. Five are source bugs with their own sections below.
Fix: run the suite locally, then push and comment /ok to test. For the eighteen, an autouse fixture activating a WorkerConfig beats editing 25 call sites β the same pattern this PR already uses elsewhere.
π΄ review comment β the failing suite
Energon counts consecutive failures and raises past a limit. This PR sets that limit to
0, which Energon reads as "stop counting" β its own docstring says so, and its own
default is 100. The same number also switches off a second stop in the packing loop.
The run is noisy, not quiet: it prints per failure, including a failed N/0 times in a
row counter that can never trip. It simply never ends. The PR also deletes the comment that
explained all this.
Fix: set it to 100 and restore the comment. Energon already has SkipSample for dropping bad samples cleanly, so 0 was never needed for that.
π΄ review comment β generic_sft.py:259
An "omni" model reads text, images and sound. Turning a sound file into something it can read takes three settings, and they come from the processor β the object that converts raw media into model inputs. The three are: what rate to resample the audio to, the step between analysis windows, and how much the audio part of the model shrinks the result. A processor built only for text and images carries none of them.
Those three settings are checked in two places, and the checks are identical. At startup the
loader asks whether all three are there, gets "no" β and then uses that answer only to decide
whether to run an unrelated check on one of the numbers. It never refuses the run. The very same
question is asked again on every audio sample, deep inside encoding, and there it does raise. By
then it is useless: the error is caught, printed, and the sample is thrown away, and because the
tolerance in section 4 is 0 it never adds up to a stop.
So a run pointed at the wrong processor starts cleanly, prints a line per sound file, and
finishes having trained on the text and the images with every piece of audio missing. The PR's
own test tries to catch this β it blanks one of the three settings and expects startup to fail β
and it currently reports DID NOT RAISE.
Fix: refuse at startup when any of the three is missing. The loader has already worked out the answer; it just throws it away. Fix the code rather than deleting the test, since deleting it ships the quiet half-run.
π΄ review comment β nemotron_multimodal.py:460
The file is opened before anything decides whether it is needed, and the result is dropped when there is no cache pool. On a WebDataset run without one, that is every image, video and audio member of every sample β and for a path-valued member it hits the filesystem during cooking. Two of the 29 test failures land here.
Fix: open only on the paths that use the handle. Do not just move the call below the cache check β the metadata step also consumes opened, so a straight move breaks it.
π΄ review comment β cookers/nemotron.py:371
The guard rejects fewer than two markers. The loop then walks the list and reads
boundaries[index + 1] on even positions, so three markers means it reaches for a
fourth that is not there. Unusual in curated data, entirely normal in a scraped corpus. This is a
loud crash, not corrupted training β the point is that a guard already exists and catches too
few cases.
Fix: reject an odd count in the guard that is already there β one condition added to the existing raise.
π΄ review comment β nemotron_tokenization.py:399
The field was int | None; it is now typed so that only null passes.
That is deliberate β the PR's own test asserts an integer is rejected β but the recipes were not
moved to the replacement task_encoder.packing block. Two set the value directly and a
third inherits from one of them. No CI job catches it: the functional test runs the non-packing
recipe, and these three are disabled for GPU-hour budget with a note saying to run them by hand.
Fix: move the two files onto task_encoder.packing. The docs guide still describes the old field too, and needs the same edit.
π΄ review comment β energon/config.py:154
The import is bare, so the user sees a raw ModuleNotFoundError from inside the
loader rather than a message naming what to install. It is not only the resampling branch: per the
comment two lines above it, the feature extractor needs librosa too, so any audio
path needs it. The only thing that installs it today is this PR's own dev script, which no CI
path runs. soxr is missing the same way.
It does show up in the lock file, which is misleading if you grep for it β it resolves there
through an optional audio extra of Megatron-Bridge and through TensorRT-LLM, while
the project asks for Megatron-Bridge's te and ssm extras instead. So it
is resolved in the lock and never installed on this path.
Fix β one word. Megatron-Bridge already packages this: its optional audio extra is exactly ["librosa"], and its own Nemotron Omni code imports librosa. So ask for the extra instead of declaring anything new β megatron-bridge[te,ssm] becomes megatron-bridge[te,ssm,audio] β then refresh the lock file. soxr needs no entry; librosa requires it.
Not energon, and nothing to file upstream. Energon's audio surface is decoding only β its av-decode extra is just av, and librosa appears in none of its extras. Energon reads bytes and decodes them; resampling to one model's rate belongs to whoever owns that model, which is why the extra already lives in Bridge. And not the audio install script: that exists because the av wheel ships FFmpeg itself β libavcodec, libavformat, libavutil, libswresample, libswscale β which is what makes redistributing it a licensing question. librosa is pure Python and bundles no binaries, so that reason does not apply.
π΄ review comment β nemotron_multimodal.py:679
/ok to test. The other six
are the reason to care beyond the gate β no job checks them, so merging with them in place means
the first person to run a real Nemotron training job finds them. Five further comments on the PR
are design cleanups and can land afterwards.