Repository navigation
feat(loss): --log-probs-backend fused_lm_head, log-probs without materializing the logits - #4645
omri-manolo wants to merge 3 commits into
Conversation
…s a tile at a time
…nd the model's role The output processor is named hidden_states_output_processor, after the GPTModel kwarg it fills. It records GPTModel's scale_logits with the output layer, and project applies it to each tile, so MuP models are supported instead of rejected.
HJSang
left a comment
There was a problem hiding this comment.
Design question: everything LMHeadContext records (output_layer, output_weight, scale_logits) is constant for the model's lifetime, but lm_head_forward_kwargs builds a new context per micro-batch in both forward steps and the processor fills it at call time. Could the context be built once at model build (next to check_model_takes_output_processor) and bound to the forward, with the loss receiving that run-level LMHead once instead of batch["lm_head"] plus the forward_only callback kwarg? That would make hidden_states_output_processor stateless (gather + transpose only) and leave the forward steps and the batch dict as they were. Nice that this builds on the upstream output_processor hook (NVIDIA/Megatron-LM#4686) rather than a patch.
|
Could the testing table also carry latency? Per-step train time (and the log-probs forward time) for |
| With sequence parallelism the gather's backward only splits the gradient back: ``project`` has already | ||
| summed it over the group, tile by tile. | ||
| """ | ||
| if output_layer.sequence_parallel: |
There was a problem hiding this comment.
With a LoRA target that resolves to the output layer (unembed, the default for Inkling models in hf_lora_targets.py), Bridge wraps output_layer in LoRALinear, which has no sequence_parallel or tp_group attribute, so this raises AttributeError on the first micro-batch after build and load (project would also pass weight= and runtime_gather_output= into the adapter). Could _validate_fused_lm_head_args reject that case at startup, next to the multi-LoRA check, until the wrapped layer is supported?
There was a problem hiding this comment.
great catch! adding this check to _validate_fused_lm_head_args
| lm_head_kwargs, lm_head = lm_head_forward_kwargs(args, model_chunks) | ||
| if return_schedule_plan: | ||
| assert not args.enable_mtp_training, "MTP training should not be enabled when using combined 1f1b" | ||
| assert lm_head is None, "--log-probs-backend fused_lm_head is not supported with combined 1f1b" |
There was a problem hiding this comment.
This fires per micro-batch, after the model is built and the checkpoint loaded. Megatron selects the schedule plan from overlap_moe_expert_parallel_comm, so could the rejection move to _validate_fused_lm_head_args with the other startup checks, keeping this assert as a guard?
There was a problem hiding this comment.
agreed, will add this as a startup check as well.
| from miles.utils.types import RolloutBatch | ||
|
|
||
| # [4096, V / TP] bf16 logits: 0.5 GiB at a 128K vocabulary over TP 2 | ||
| DEFAULT_LM_HEAD_TILE_ROWS = 4096 |
There was a problem hiding this comment.
The 4096 default lives here, in fused_lm_head_log_probs_and_entropy (tile_rows: int = 4096) and in the --log-probs-backend help text. Could one of them own it, e.g. make tile_rows a required keyword and let this constant be the single source?
LoRA on the output layer and --overlap-moe-expert-parallel-comm (the combined 1F1B schedule) failed on the first micro-batch, after the model was built and the checkpoint loaded; both are now rejected while the arguments are validated. The output-layer check runs after validate_lora_args, where the LoRA targets are resolved, and covers Inkling, whose default targets include the output layer. The model.py assert stays as a guard. tile_rows is a required keyword of fused_lm_head_log_probs_and_entropy, so DEFAULT_LM_HEAD_TILE_ROWS is the only default.
Agreed, and thanks for the pointer. Two points before I rework it:
|
Implements RFC #4108: a new opt-in
--log-probs-backend fused_lm_head. The actor's model returns the output layer's input instead of its logits, and the loss runs the output layer on the rows it scores, a tile at a time under activation checkpointing, reducing each tile with #3741'sfused_log_probs_and_entropy. The micro-batch's[tokens × vocab/TP]logits and their gradient are never materialized.torchandfusedare unchanged.How it works
lm_head.pypasses Megatron'sGPTModel.forward(output_processor=...). The processor returns the decoder output as[b, s, h](gathered over TP when sequence parallel is on) and records the output layer for the loss (batch["lm_head"]). Only the actor's forwards use it; the critic keeps its value head.fused_lm_head_log_probs_and_entropy(fused_log_probs.py). Per tile of scored rows, undertorch.utils.checkpoint: the output layer with sequence parallel off, thenfused_log_probs_and_entropywithinplace_backward. Backward replays the tile, so the output layer computes its own gradients and the hidden gradient is all-reduced over TP by the layer's backward. Tile size is--log-probs-chunk-sizerows (default 4096). Extra memory is about one[tile, vocab/TP]tile, independent of the token budget.get_log_probs_and_entropyselects response rows and targets exactly asfuseddoes, so prompt rows are never multiplied by the output weight.Scope
--loss-type policy_lossandsft_loss, with entropy (on-policy distillation goes through the policy loss).--vocab-size, a train backend other than Megatron, other loss types, multi-LoRA. Models whose forward has nooutput_processor(e.g.HybridModel) are rejected at model build.--enable-mtp-traininga startup warning says the MTP heads are not covered: Megatron computes the MTP loss inGPTModel._postprocessbefore theoutput_processorruns, so each MTP layer still materializes its fp32 logits. A fused MTP loss needs a Megatron hook; follow-up.Testing
tests/fast-gpu/test_fused_lm_head_log_probs.py,tests/fast/backends/megatron_utils/test_lm_head.py, argument validation): equality againstfusedonhidden @ W.Tfor log-probs, entropy and the hidden/weight gradients; uneven tiles, no scored rows, frozen weight, two TP shards over NCCL; policy and SFT losses in thd and bshd. With the existing fused log-prob tests: 624 passed on 1× H100.--load-debug-rollout-data --debug-train-only):fusedfused_lm_headfusedfused_lm_headSteps 1–2 differ within the run-to-run variation we see within one backend.
Not in this PR
fused_lm_head(after Fused log-prob op: K targets per row; score centering trains on it #3998).process_mtp_loss).--log-probs-chunk-sizefor now).