Skip to content

Commit d06c130

Browse files
[PEFT] feat: reduce LoRA sequence-parallel activation memory (NVIDIA-NeMo#4581)
Signed-off-by: Chen Cui <chcui@nvidia.com> Signed-off-by: nvskills-svc-account <svc-nvskills-signing@nvidia.com> Co-authored-by: nvskills-svc-account <svc-nvskills-signing@nvidia.com>
1 parent 8d945b3 commit d06c130

11 files changed

Lines changed: 718 additions & 75 deletions

File tree

‎docs/performance-guide.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,14 @@ Additionally, because CP shards activations, it also partitions optimizer states
320320
> > 2. `TransformerConfig.recompute_method=block`
321321
> > 3. `TransformerConfig.recompute_num_layers=<int:≤num_layers_in_the_model>`
322322
323+
> 3. LoRA training with tensor and sequence parallelism can retain the sequence-local LoRA-A input for
324+
> column-parallel `linear_qkv` and `linear_fc1` adapters. Set `LoRA(sequence_parallel_input_regather=True)` to
325+
> gather the full LayerNorm output temporarily in forward, release it after LoRA-A, and gather it again during
326+
> backward for the LoRA-A weight gradient. MCore overlaps the backward all-gather with dgrad computation when
327+
> possible. The option has no effect when sequence parallelism is disabled and falls back to the existing path
328+
> for row-parallel or expert adapters, CPU activation offload, CUDA graphs, full-layer recompute, and `linear_fc1`
329+
> covered by selective MLP recompute.
330+
323331
2. Activation offloading to host memory
324332

325333
> 1. Megatron-Bridge supports offloading activation memory to host memory, essential for training tasks constrained by activation memory. This is particularly useful for scenarios like (1) FSDP, where model state memory is minimized through sharding but activation memory remains high, (2) LoRA, which has frozen parameters but significant activation memory demands, and (3) the training with a large sequence length. The efficiency of activation offloading depends on both the interconnect bandwidth between the GPU and host and the host memory bandwidth. From this perspective, Grace-based systems like the GB200 enhance offloading performance by optimizing these bandwidths.

‎skills/nemo-mbridge-perf-memory-tuning/BENCHMARK.md‎

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ This benchmark summarizes 3-Tier Evaluation from NVSkills-Eval results for the s
77
## Evaluation Summary
88

99
- Skill: `nemo-mbridge-perf-memory-tuning`
10-
- Evaluation date: 2026-06-02
10+
- Evaluation date: 2026-06-30
1111
- NVSkills-Eval profile: `external`
12-
- Environment: `local`
12+
- Environment: `astra-sandbox`
1313
- Dataset: 1 evaluation tasks
14-
- Attempts per task: 2
14+
- Attempts per task: 1
1515
- Pass threshold: 50%
1616
- Overall verdict: PASS
1717

@@ -54,34 +54,28 @@ Task composition is derived from the evaluation dataset when possible. Entries w
5454

5555
| Dimension | Num | `claude-code` | `codex` |
5656
|---|---:|---:|---:|
57-
| Security | 2 | 100% (+0%) | 100% (+0%) |
58-
| Correctness | 2 | 100% (+0%) | 97% (+0%) |
59-
| Discoverability | 2 | 100% (+0%) | 72% (+0%) |
60-
| Effectiveness | 2 | 94% (-1%) | 93% (-4%) |
61-
| Efficiency | 2 | 92% (-0%) | 60% (-0%) |
57+
| Security | 1 | 100% (+0%) | 100% (+0%) |
58+
| Correctness | 1 | 100% (+100%) | 97% (+72%) |
59+
| Discoverability | 1 | 100% (+100%) | 97% (+47%) |
60+
| Effectiveness | 1 | 98% (+86%) | 100% (+90%) |
61+
| Efficiency | 1 | 94% (+67%) | 96% (+35%) |
6262

6363
Score values show skill-assisted performance. Values in parentheses show uplift versus the no-skill baseline when baseline data is available.
6464

6565
## Tier 1: Static Validation Summary
6666

67-
Tier 1 validation passed with observations. NVSkills-Eval ran 9 checks and found 10 total findings.
67+
Tier 1 validation passed with observations. NVSkills-Eval ran 1 checks and found 4 total findings.
6868

6969
Top findings:
7070

71-
- MEDIUM QUALITY/quality_correctness: SKILL_SPEC recommended field missing: 'metadata.author' (`skills/nemo-mbridge-perf-memory-tuning/SKILL.md`)
72-
- MEDIUM QUALITY/quality_correctness: SKILL_SPEC recommended field missing: 'metadata.tags' (`skills/nemo-mbridge-perf-memory-tuning/SKILL.md`)
7371
- MEDIUM SCHEMA/body_recommended_section: Missing recommended section: '## Instructions' (`skills/nemo-mbridge-perf-memory-tuning/SKILL.md`)
7472
- MEDIUM SCHEMA/body_recommended_section: Missing recommended section: '## Examples' (`skills/nemo-mbridge-perf-memory-tuning/SKILL.md`)
7573
- MEDIUM SCHEMA/author_missing: Author not specified in metadata (`skills/nemo-mbridge-perf-memory-tuning/SKILL.md`)
74+
- LOW SCHEMA/unexpected_file: Unexpected 'card.yaml' in skill root (`skills/nemo-mbridge-perf-memory-tuning/card.yaml`)
7675

7776
## Tier 2: Deduplication Summary
7877

79-
Tier 2 validation passed. NVSkills-Eval ran 2 checks and found 0 total findings.
80-
81-
Notable observations:
82-
83-
- Context Deduplication: Collected 1 file(s)
84-
- Inter-Skill Deduplication: Parsed skill 'nemo-mbridge-perf-memory-tuning': 175 char description
78+
This tier was not run or did not produce findings in this report.
8579

8680
## Publication Recommendation
8781

‎skills/nemo-mbridge-perf-memory-tuning/SKILL.md‎

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
name: nemo-mbridge-perf-memory-tuning
3-
description: Techniques for reducing peak GPU memory in Megatron Bridge — expandable segments, parallelism resizing, activation recompute, CPU offloading constraints, and common OOM fixes.
3+
description: Techniques for reducing peak GPU memory in Megatron Bridge — expandable segments, PEFT + SP input re-gather, parallelism resizing, activation recompute, CPU offloading constraints, and common OOM fixes.
44
license: Apache-2.0
5-
when_to_use: GPU OOM errors, reducing peak memory, or tracing an OOM regression to a specific commit or config change; 'out of memory', 'OOM', 'memory fragmentation', 'expandable_segments', 'reduce GPU memory', 'PYTORCH_CUDA_ALLOC_CONF'.
5+
when_to_use: GPU OOM errors, reducing peak memory, reducing LoRA or PEFT activation memory with sequence parallelism, or tracing an OOM regression to a specific commit or config change; 'out of memory', 'OOM', 'memory fragmentation', 'expandable_segments', 'reduce GPU memory', 'LoRA memory', 'PEFT memory', 'sequence_parallel_input_regather', 'PYTORCH_CUDA_ALLOC_CONF'.
66
---
77

88
# Memory Tuning
@@ -29,7 +29,7 @@ Beyond fragmentation, actual peak memory is determined by:
2929
- **Parameter + optimizer state memory** — controlled by TP, PP, DP sharding
3030
(distributed optimizer, FSDP)
3131
- **Activation memory** — controlled by activation recompute, sequence length,
32-
micro-batch size
32+
micro-batch size, and PEFT-specific retention of gathered inputs
3333
- **Temporary / workspace memory** — CUDA kernels, NCCL buffers, CUDA graphs
3434

3535
For configuration planning, use the Bridge theoretical estimator before launching
@@ -53,15 +53,19 @@ When a training run OOMs or is close to the memory limit:
5353
1. **Set `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True` first.** This fixes
5454
fragmentation-induced OOM with zero performance cost. Most Slurm launch
5555
templates already include it.
56-
2. **Add selective activation recompute** (`recompute_modules=[core_attn]`) if
56+
2. **For LoRA with sequence parallelism, enable input re-gather**
57+
(`LoRA(sequence_parallel_input_regather=True)`). This avoids retaining the
58+
full gathered LoRA-A input in every eligible layer; it has no effect when SP
59+
is disabled.
60+
3. **Add selective activation recompute** (`recompute_modules=[core_attn]`) if
5761
not already enabled. See @skills/nemo-mbridge-perf-activation-recompute/SKILL.md.
58-
3. **Avoid increasing TP** as a memory fix — doubling TP dramatically increases
62+
4. **Avoid increasing TP** as a memory fix — doubling TP dramatically increases
5963
NVLink all-reduce volume and often kills throughput (-28% on Llama3 70B).
60-
4. **Avoid increasing PP at the cost of DP** — halving DP doubles gradient
64+
5. **Avoid increasing PP at the cost of DP** — halving DP doubles gradient
6165
accumulation steps and hurts throughput (~6%).
62-
5. Consider `mlp` recompute if still OOM. Saves ~3 GB but costs ~16% GPU
66+
6. Consider `mlp` recompute if still OOM. Saves ~3 GB but costs ~16% GPU
6367
utilization on large dense models (Llama3 70B).
64-
6. CPU offloading is **blocked when PP > 1**.
68+
7. CPU offloading is **blocked when PP > 1**.
6569

6670
## Enablement
6771

@@ -98,6 +102,36 @@ If the model genuinely does not fit (not fragmentation), adjust parallelism:
98102

99103
See @skills/nemo-mbridge-perf-activation-recompute/SKILL.md for full details.
100104

105+
### PEFT + sequence-parallel input re-gather
106+
107+
For `LoRA` training with sequence parallelism, eligible column-parallel
108+
`linear_qkv` and `linear_fc1` adapters consume a gathered LayerNorm output.
109+
Because LoRA-A is trainable, the default path retains that full gathered input
110+
until backward for the LoRA-A weight gradient.
111+
112+
Enable input re-gather when constructing the PEFT config:
113+
114+
```python
115+
from megatron.bridge.peft.lora import LoRA
116+
117+
cfg.peft = LoRA(
118+
# Keep the recipe's existing LoRA settings here.
119+
sequence_parallel_input_regather=True,
120+
)
121+
```
122+
123+
With this option, forward still materializes the full input temporarily for the
124+
LoRA-A GEMM, but MCore autograd retains only its sequence-local shard. Backward
125+
asynchronously gathers the full input again, overlaps the collective with
126+
dgrad when possible, computes the LoRA-A weight gradient, and then reuses the
127+
temporary communication buffer.
128+
129+
This is a memory-for-communication tradeoff, not conventional activation
130+
checkpointing: no LayerNorm, attention, MLP, or LoRA GEMM is rerun. Some
131+
throughput degradation is expected, and the benefit grows with the amount of
132+
eligible LoRA-A activation retained. The option has no effect when sequence
133+
parallelism is disabled.
134+
101135
### CPU offloading
102136

103137
```python
@@ -132,6 +166,10 @@ not as a memory fix.
132166
- CPU offloading requires `pipeline_model_parallel_size = 1`.
133167
- Distributed optimizer requires `use_distributed_optimizer = True` in the
134168
optimizer config.
169+
- `sequence_parallel_input_regather` applies only to eligible non-expert
170+
column-parallel LoRA-A projections. Row-parallel adapters, expert adapters,
171+
TP=1, CUDA graphs, CPU activation offload, and overlapping full-layer or
172+
selective MLP activation recompute fall back to the existing path.
135173

136174
## Measured Results
137175

@@ -177,8 +215,34 @@ Selective activation recompute with `mlp` saved ~3 GB peak memory but cost
177215
~16% GPU utilization on this workload. See
178216
@skills/nemo-mbridge-perf-activation-recompute/SKILL.md for full results.
179217

218+
### LoRA + SP input re-gather
219+
220+
Real-checkpoint H100 training with SQuAD showed lower peak memory in all tested
221+
configurations, with workload-dependent throughput cost:
222+
223+
| Model/config | Baseline peak | Input re-gather peak | Memory saved | Throughput change |
224+
|---|---:|---:|---:|---:|
225+
| Qwen3-8B, TP2, seq 8192 | 47.545 GB | 42.814 GB | 4.731 GB (10.0%) | -6.74% |
226+
| Qwen3-30B-A3B, TP4/EP4 | 29.890 GB | 28.321 GB | 1.569 GB (5.2%) | -2.89% |
227+
| GPT-OSS-120B, TP2/EP8 | 52.185 GB | 51.371 GB | 0.814 GB (1.6%) | -0.34% |
228+
229+
All runs had finite losses with zero skipped or NaN iterations. Two-rank BF16
230+
and FP32 checks matched the baseline for outputs, input gradients, LoRA-A and
231+
LoRA-B gradients, and two-microbatch fused `main_grad` accumulation.
232+
180233
## Code Anchors
181234

235+
### LoRA sequence-parallel input re-gather
236+
237+
```text
238+
src/megatron/bridge/peft/lora.py
239+
LoRA.sequence_parallel_input_regather
240+
241+
src/megatron/bridge/peft/utils.py
242+
ParallelLinearAdapter._sequence_parallel_input_regather_eligibility()
243+
ParallelLinearAdapter.forward()
244+
```
245+
182246
### CPU offloading PP incompatibility (MCore)
183247

184248
```1303:1306:3rdparty/Megatron-LM/megatron/core/transformer/transformer_config.py
@@ -224,6 +288,7 @@ model_config = GPTModelProvider(
224288
| OOM on a single rank despite headroom on others | Memory fragmentation | check if `expandable_segments:True` is set | set `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True` |
225289
| OOM with `expandable_segments` already set | Genuine capacity limit | check `nvidia-smi` for param/optimizer memory | increase PP, use distributed optimizer, or add recompute |
226290
| Estimated memory exceeds GPU capacity before launch | model state or activations genuinely too large | run `estimate_training_memory` and inspect the largest component | adjust PP/TP/CP/EP, distributed optimizer, or recompute before launching |
291+
| LoRA + SP retains unexpectedly high activation memory | full gathered LoRA-A inputs are retained until backward | check whether `cfg.peft.sequence_parallel_input_regather` is enabled and the target is eligible | set `LoRA(sequence_parallel_input_regather=True)`; verify fallback constraints |
227292
| `ValueError: PP + CPU offloading` | using cpu_offloading with PP > 1 | check PP config | disable CPU offloading or set PP=1 |
228293
| `RuntimeError` with `--use-nccl-ub` + expandable segments | NCCL UB incompatible with expandable allocator | check env vars | remove `expandable_segments:True` or disable `--use-nccl-ub` |
229294

@@ -233,6 +298,8 @@ model_config = GPTModelProvider(
233298
- Parallelism resizing (TP/PP) often has significant throughput costs
234299
- The theoretical estimator is formula-based and does not replace runtime
235300
profiling or CUDA memory reports
301+
- LoRA input re-gather does not cover row-parallel or expert adapters and may
302+
have negligible benefit when few eligible LoRA-A activations dominate memory
236303

237304
## Verification
238305

@@ -245,3 +312,15 @@ assert "expandable_segments:True" in os.environ.get("PYTORCH_CUDA_ALLOC_CONF", "
245312

246313
For Slurm jobs, verify the env var is exported before the training command
247314
in the launch script.
315+
316+
For LoRA + SP input re-gather, run the focused configuration tests and the real
317+
two-rank MCore backward-parity test:
318+
319+
```bash
320+
uv run python -m pytest \
321+
tests/unit_tests/peft/test_utils.py -k "sequence_parallel_input_regather" \
322+
tests/unit_tests/peft/test_lora.py -k "sequence_parallel_input_regather"
323+
324+
uv run python -m torch.distributed.run --nproc_per_node=2 -m pytest \
325+
tests/unit_tests/peft/test_lora_sp_input_regather_distributed.py
326+
```

0 commit comments

Comments
 (0)