Skip to content

SFT learner fixes: training mode for gradient checkpointing, Ascend OOM detection, real-loss autobatch probe - #331

Merged
michaellchung merged 5 commits into
mainfrom
sft-night-fixes
Nov 3, 2026
Merged

michaellchung merged 5 commits into
mainfrom
sft-night-fixes

Conversation

@michaellchung

@michaellchung michaellchung commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Learner: enter training mode before enabling gradient checkpointing. from_pretrained returns an eval-mode module and the learner never called model.train(). Transformers' GradientCheckpointingLayer only recomputes when self.training is true, so --gradient-checkpointing on was accepted and logged but silently ineffective, and dropout (LoRA's included) was inert. Setup now lives in enter_training_mode, with the ordering covered by tests. On one Ascend 910B3 with Qwen3-0.6B LoRA, activation peak at 16k tokens drops from 59.0 GB to 10.1 GB, and 28k tokens goes from OOM to 10.9 GB.
  • accel: recognize an allocator OOM without a vendor exception class. torch.npu exports no OutOfMemoryError, so Ascend's bare RuntimeError("NPU out of memory ...") escaped both memory probes (causal-LM auto-batch and diffusion). accel.is_oom(exc, device) matches the vendor class, torch's generic OutOfMemoryError and the message-only form; anything else is re-raised.
  • Autobatch: probe the real sft_loss. The stand-in logits[:, :-1].float().sum() ignored the log-softmax tensor that F.cross_entropy keeps for backward, so the probe admitted a micro-batch about one f32 logits tensor too large. The probe now runs the native loss with all-ones weights.
  • Learner: name the chunked loss kernel in the recipe log. Each backend is mapped explicitly and unknown values are rejected.
  • Learner: keep first-step warm-up out of the s/step telemetry. The first logging window included the one-off first-step cost (dataset build, compilation, collective setup); the first step's duration is now logged on its own and the first window starts after it.

Tests

  • tests/test_learner_units.py, tests/test_accel.py, tests/test_autobatch.py, tests/test_chunked_ce.py.

🤖 Generated with Claude Code

@michaellchung
michaellchung force-pushed the sft-chunked-ce branch 2 times, most recently from f96f591 to 5b677b5 Compare November 3, 2026 04:09
@michaellchung
michaellchung changed the base branch from sft-chunked-ce to main November 3, 2026 06:24
MichaelChung and others added 5 commits November 3, 2026 14:24
`torch.npu` exports no `OutOfMemoryError`, so `oom_error(device)` fell back
to `torch.cuda.OutOfMemoryError` and `except oom_error(device)` never matched
Ascend's refusal, which arrives as a bare `RuntimeError("NPU out of memory.
Tried to allocate ...")`. Both memory probes -- the causal-LM auto-batch
resolver and the diffusion one -- were therefore not probes at all on Ascend:
the first size that did not fit killed the run instead of fixing the ceiling.

Observed on a 910B3 with Qwen3-0.6B at 8k tokens: `--micro-batch-size auto
--grad-accum 8` died in `_probe_once` with "Tried to allocate 37.09 GiB"
rather than settling on a smaller micro-batch.

`accel.is_oom(exc, device)` answers the question the probes actually have --
is this the allocator refusing an allocation -- across the vendor class,
torch's generic `OutOfMemoryError`, and the message-only form. The probes
re-raise anything else, so an aborted collective still fails loudly instead
of being mistaken for a memory ceiling. The existing test suite missed this
because its fake npu module defines an `OutOfMemoryError` that the real
torch_npu does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_probe_once` approximated the native loss with `logits[:, :-1].float().sum()`
on the stated assumption that "the shifted f32 logits copy dominates". It
does not: `sft_loss` hands that copy to `F.cross_entropy`, which keeps its
log_softmax output -- a second tensor of the same full (B, T-1, V) shape --
alive for the backward. The probe therefore reported a ceiling about one
logits-sized f32 tensor too high.

On a 910B3 with Qwen3-0.6B (151936-token vocabulary) at `--seq-len 8192
--grad-accum 8`, the probe admitted micro-batch 4 and the first real training
step died asking for another 18.54 GiB. Running the real loss in the probe --
with all-ones weights, the worst case over packed blocks -- makes the probe
answer the question it claims to answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recipe line mapped every non-liger backend to torch-cross-entropy, so
--kernel-backend chunked runs were archived as native-loss runs. Map each
backend explicitly and reject unknown values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`from_pretrained` returns an eval-mode module and the learner never left it.
Transformers' GradientCheckpointingLayer only recomputes when `self.training`
is true, so `--gradient-checkpointing on` was accepted, logged, and silently
ineffective: every activation stayed alive for backward. Dropout, LoRA's
included, was inert for the same reason.

Measured on one Ascend 910B3 card with Qwen3-0.6B, LoRA all-linear, one
sequence per micro-batch, activation peak per sequence length:

  tokens | eval mode | train mode
    4096 |   21.5 GB |     9.3 GB
    8192 |   33.9 GB |     9.6 GB
   16384 |   59.0 GB |    10.1 GB
   28000 |      OOM  |    10.9 GB

Which is the difference between OOM and training at long context: a 28k-token
SFT run on a 64 GB card is impossible in eval mode and comfortable in train
mode. The checkpointing setup moves into `enter_training_mode`, where the
ordering is stated once and covered by tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first logging window included the one-off first-step cost (streaming
dataset build, compilation, collective setup). On a 2.9 GB dataset that
reported 25 s/step for a run whose steady state is 4.75 s/step. Log the
first step's duration on its own and start the first window after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@michaellchung
michaellchung merged commit 5db5a0f into main Nov 3, 2026
2 checks passed
@michaellchung
michaellchung deleted the sft-night-fixes branch November 3, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant