Skip to content
Merged
Show file tree
Hide file tree
Changes from 79 commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
1851c3a
init
pggPL Apr 30, 2025
657cbbe
offloading
pggPL Apr 30, 2025
44f6494
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 30, 2025
728b174
fixes
pggPL Apr 30, 2025
57e7869
all types
pggPL Apr 30, 2025
ad68f91
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 30, 2025
001b23c
typo
pggPL Apr 30, 2025
037a89c
Merge remote-tracking branch 'upstream/main' into quantized_tensor_of…
pggPL May 5, 2025
8b00c6f
fix
pggPL May 5, 2025
4c8a06d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 5, 2025
4702364
init
pggPL May 8, 2025
35dbe1d
api change
pggPL May 9, 2025
ba3a4e4
code drop
pggPL May 29, 2025
ec50bf0
refactor
pggPL Jul 9, 2025
346f35b
tests
pggPL Jul 9, 2025
9809729
code drop
pggPL Jul 9, 2025
1cc72fd
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Jul 9, 2025
9a63e22
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 9, 2025
ed0ee4c
fixes
pggPL Jul 9, 2025
d470da0
fix
pggPL Jul 9, 2025
4b3865c
fix
pggPL Jul 9, 2025
05f8e2f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 9, 2025
644f204
example
pggPL Jul 10, 2025
1c04938
fixes
Jul 10, 2025
8440075
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Jul 11, 2025
994bd46
cpu offload + debug warning
pggPL Jul 11, 2025
9becb49
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 11, 2025
f181517
fixes
pggPL Jul 11, 2025
c281f70
fix
pggPL Jul 11, 2025
713cec8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 11, 2025
bd6b599
test
pggPL Jul 11, 2025
e6d9d86
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 11, 2025
55b2085
fix
pggPL Jul 11, 2025
667fcaa
fixes
pggPL Jul 21, 2025
828088a
fix
pggPL Jul 21, 2025
2ec4fc3
fixes
pggPL Jul 22, 2025
675bbbd
fix
pggPL Jul 23, 2025
a150521
fix
pggPL Jul 23, 2025
00382b9
fix
pggPL Jul 30, 2025
4e775bd
fixes
pggPL Jul 31, 2025
665d321
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Aug 7, 2025
8bf9e27
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 7, 2025
4138750
change empty_like implementation to use make_like
pggPL Aug 7, 2025
684320f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 7, 2025
97d6619
fix
pggPL Aug 7, 2025
b4f9d77
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 7, 2025
0aa6084
main_grad fix
pggPL Aug 13, 2025
92007a7
manual synchornization
pggPL Aug 22, 2025
8d51b9b
fix
pggPL Aug 22, 2025
18c7f19
old path
pggPL Aug 22, 2025
142bc56
remove example
pggPL Aug 22, 2025
2e79b9f
api changes
pggPL Aug 27, 2025
09eb633
reverted grouped linear
pggPL Aug 27, 2025
40f4d82
make odl code path work for modules
pggPL Aug 27, 2025
c43658e
attention old code path
pggPL Aug 27, 2025
945872d
legacy tests
pggPL Aug 27, 2025
f7539b0
legacy tests
pggPL Aug 27, 2025
f154dc4
fix
pggPL Aug 27, 2025
6129362
fix
pggPL Sep 2, 2025
e9186c7
updated code path
pggPL Sep 2, 2025
749be31
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Sep 15, 2025
00a0a1e
fix
pggPL Sep 15, 2025
d16fa99
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 15, 2025
20096bf
fix
pggPL Sep 15, 2025
590b6ab
fix
pggPL Sep 22, 2025
006257e
fix
pggPL Sep 22, 2025
a5a92a1
fix
pggPL Sep 22, 2025
5b5bd4f
fix
pggPL Sep 22, 2025
6f99c57
fixes
pggPL Sep 25, 2025
2caf8d4
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Sep 25, 2025
7e105f0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2025
4860bce
Update transformer_engine/pytorch/tensor/quantized_tensor.py
pggPL Sep 25, 2025
ef8a5a1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2025
d39a262
fix
pggPL Sep 30, 2025
3ab009f
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Sep 30, 2025
ca85b26
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 30, 2025
83e07c2
nvfp4 support
pggPL Sep 30, 2025
6e614ad
fix
pggPL Sep 30, 2025
934570d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 30, 2025
ee2f50c
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Oct 21, 2025
6ed41be
fix
pggPL Oct 21, 2025
7d6bb25
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2025
1290a33
fix
pggPL Oct 21, 2025
a6d82a8
fixes
pggPL Oct 21, 2025
348d7fa
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2025
4356bfa
fixes
pggPL Oct 21, 2025
047883f
fix
pggPL Oct 21, 2025
5a36f94
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2025
79ff5bc
fixes
pggPL Oct 21, 2025
6ddb662
fix
pggPL Oct 21, 2025
f7b958c
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Nov 6, 2025
f5a2d23
fixes
pggPL Nov 6, 2025
815bce6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2025
180bbf1
Update tests/pytorch/test_cpu_offloading.py
pggPL Nov 6, 2025
6f4c04a
small fix
pggPL Nov 6, 2025
c727baf
fix
pggPL Nov 6, 2025
366db40
fix
pggPL Nov 6, 2025
9284ae6
fix
pggPL Nov 6, 2025
0842562
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2025
e848cb7
docs change
pggPL Nov 14, 2025
57af48f
Merge remote-tracking branch 'upstream/main' into start_offloading_fa…
pggPL Nov 14, 2025
4e2e8b2
fix
pggPL Nov 14, 2025
322d4aa
fix
pggPL Nov 14, 2025
f93e270
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 14, 2025
f41cbce
fix
pggPL Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
813 changes: 644 additions & 169 deletions tests/pytorch/test_cpu_offloading.py

Large diffs are not rendered by default.

159 changes: 159 additions & 0 deletions tests/pytorch/test_cpu_offloading_old_code_path.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update this test with the changes in #2158. The old test didn't make the input with requires_grad=True, so the memory estimates were incorrect. It turns out memory usage is not the same between the 1 layer case and the CPU offloading case because the input tensor caching is different between those two cases.

@timmoon10 timmoon10 Sep 23, 2025 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, _old_code_path is a pretty bad name. Maybe test_cpu_offloading_v1.py and test_cpu_offloading_v2.py?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We plan to remove old code path at the end of the year - this is needed as a fallback option for DL training folks, if something will break with new code path. Since mcore imports from transformer_engine.pytorch.cpu_offloading, I think that we should use this name for new path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporary code has a tendency to stick around, and I'd rather eliminate the possibility of names like _old_old_code_path. We can keep test_cpu_offloading.py, but let's change old test to test_cpu_offloading_v1.py. Also, as long as we are keeping the old test we should keep the changes from #2158.

Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# See LICENSE for license information.

import os
from contextlib import nullcontext
import pytest
import torch

import transformer_engine.pytorch as te
from transformer_engine.common import recipe
from transformer_engine.pytorch.fp8 import FP8GlobalStateManager
from transformer_engine.pytorch.attention.dot_product_attention import _attention_backends
from utils import ModelConfig, get_available_attention_backends

# Check if FP8 is supported
fp8_available, _ = FP8GlobalStateManager.is_fp8_available()

fp8_recipes = [None]
if fp8_available:
fp8_recipes.append(recipe.Float8CurrentScaling())
fp8_recipes.append(recipe.DelayedScaling())

model_config = {
"small": ModelConfig(8, 512, 8, 64, num_layers=5, eps=0.1),
}
SIZE = model_config["small"].hidden_size
NUM_HEADS = model_config["small"].num_heads
NUM_LAYERS = model_config["small"].num_layers
EPSILON = model_config["small"].eps

# Flash attention saves some internal tensor for the backward pass
# that cannot be offloaded to CPU.
assert os.getenv("NVTE_FLASH_ATTN") == "0"

assert os.environ["NVTE_CPU_OFFLOAD_LEGACY_CODE_PATH"] == "1"

# Offloading is supported for attention only for fused and flash attention backends,
# so the use of bfloat16 is required.
#
# For the TransformerLayer, activation offloading with dropout is not supported,
# so we set hidden_dropout to 0.0.
model_types = {
"linear": lambda: te.Linear(SIZE, SIZE, params_dtype=torch.bfloat16),
"layernorm_mlp": lambda: te.LayerNormMLP(SIZE, SIZE, params_dtype=torch.bfloat16),
"layernorm_linear": lambda: te.LayerNormLinear(SIZE, SIZE, params_dtype=torch.bfloat16),
"multihead_attention": lambda: te.MultiheadAttention(
SIZE, NUM_HEADS, params_dtype=torch.bfloat16
),
"transformer_layer": lambda: te.TransformerLayer(
SIZE, SIZE, NUM_HEADS, params_dtype=torch.bfloat16, hidden_dropout=0.0
),
}


def _get_input():
return torch.empty((128, SIZE, SIZE), dtype=torch.bfloat16).cuda()


def _get_fp8_weight_cache_size(models, fp8_recipe):
"""
Calculate the total FP8 weight cache size (in MB) for a list of models.
"""
if fp8_recipe is None:
return 0

params_bytes = 0
for model in models:
for name, param in model.named_parameters():
if "weight" in name:
params_bytes += param.numel()

# One byte for columnwise and one byte for rowwise,
# hence multiply by 2 and convert to MB
# there is 1 byte of scale per 32 elements in mxFP8
factor_for_scale_inv_tensor = (1 + 1 / 32) if fp8_recipe.mxfp8() else 1
return (2 * params_bytes * factor_for_scale_inv_tensor) / (1024**2)


def _measure_memory_between_forward_and_backward(models, fp8_recipe, cpu_offload):
tensor = _get_input()
if cpu_offload:
offload_context, sync_function = te.get_cpu_offload_context(
enabled=True,
num_layers=len(models) - 1,
model_layers=len(models),
offload_activations=True,
offload_weights=False,
)
else:
offload_context = nullcontext()
sync_function = lambda x: x

for model in models:
with te.fp8_autocast(
enabled=fp8_recipe is not None, fp8_recipe=fp8_recipe
), offload_context:
tensor = model(tensor)
tensor = sync_function(tensor)

max_mem_used = torch.cuda.memory_allocated() / (1024**2)
torch.cuda.synchronize()

tensor.sum().backward()

return max_mem_used


@pytest.mark.parametrize("fp8_recipe", fp8_recipes)
@pytest.mark.parametrize("model_key", model_types.keys())
def test_cpu_offload(fp8_recipe, model_key) -> None:
"""
We run three configurations:
(1) No offloading: All activations remain on the GPU between forward and backward passes.
(2) No offloading (one layer): Only the first layer's activations remain on the GPU between
forward and backward passes.
(3) With offloading (all layers): Only the last layer's activations remain on the GPU
between forward and backward passes, while all other layers are offloaded to the CPU.

We expect the memory consumption of configurations (2) and (3) to be similar, with
the difference being the size of the FP8 cache that is not offloaded to the CPU.
We also expect this memory consumption to be smaller than in scenario (1).
"""
import gc

gc.collect()

model_cls = model_types[model_key]
models_list = [model_cls() for _ in range(NUM_LAYERS)]

if model_key in ["multihead_attention", "transformer_layer"]:
available_backends, *_ = get_available_attention_backends(
model_config["small"],
qkv_dtype=torch.bfloat16,
qkv_layout="sbhd_sbhd_sbhd",
)
_, fused_attn_supported, _ = available_backends
if not fused_attn_supported:
pytest.skip("Fused attention backend not available.")
os.environ["NVTE_FLASH_ATTN"] = "0"
_attention_backends["backend_selection_requires_update"] = True

without_offloading = _measure_memory_between_forward_and_backward(
models_list, fp8_recipe, False
)
without_offloading_one_layer = _measure_memory_between_forward_and_backward(
models_list[:1], fp8_recipe, False
)
with_offloading = _measure_memory_between_forward_and_backward(models_list, fp8_recipe, True)

assert with_offloading < without_offloading

# The only difference between the memory consumption of with_offloading
# and without_offloading_one_layer should be the size of the FP8 weights cache,
# which is not offloaded to the CPU.
memory_consumption_diff = abs(with_offloading - without_offloading_one_layer)
assert (
memory_consumption_diff < _get_fp8_weight_cache_size(models_list[1:], fp8_recipe) + EPSILON
)
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
)
from transformer_engine.pytorch.attention.dot_product_attention.softmax import FusedScaleMaskSoftmax
from transformer_engine.pytorch.attention.inference import InferenceParams
from transformer_engine.pytorch.cpu_offload import (
is_cpu_offload_enabled,
start_offload,
)

# Import attention utils
import transformer_engine.pytorch.attention.dot_product_attention.utils as dpa_utils
Expand Down Expand Up @@ -533,6 +537,9 @@ def forward(
cp_size *= get_distributed_world_size(group)
context_parallel = cp_size > 1

if is_cpu_offload_enabled():
start_offload(query_layer, key_layer, value_layer, offload_base_tensor=True)

# get q_format and kv_format for training and inference
qkv_format, q_format, kv_format = dpa_utils.get_qkv_format(qkv_layout, inference_params)

Expand Down Expand Up @@ -718,12 +725,12 @@ def forward(
use_flash_attn_3=use_flash_attn_3,
)
else:

Comment thread
pggPL marked this conversation as resolved.
Resolved
from transformer_engine.pytorch.cpu_offload import (
CPUOffloadEnabled,
mark_activation_offload,
)

if CPUOffloadEnabled:
if is_cpu_offload_enabled():
mark_activation_offload(
query_layer, key_layer, value_layer, cu_seqlens_q, cu_seqlens_kv
)
Expand Down Expand Up @@ -960,6 +967,9 @@ def forward(
# FP8 attn, is_output_fp8 = True: fake_dtype = torch.float8_e4m3fn
fake_dtype = q.dtype

if is_cpu_offload_enabled():
start_offload(q, k, v, offload_base_tensor=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does offload base tensor do?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If multiple tensors are views on one big tensor, this will offload this big tensor once.


QKV_quantizer, O_quantizer, S_quantizer, dQKV_quantizer, dO_quantizer, dP_quantizer = (
dpa_utils.get_attention_quantizers(fp8, quantizers, cp_specific_quantizers=False)
)
Expand Down Expand Up @@ -1096,11 +1106,11 @@ def forward(
ctx.fp8 = fp8 and int(os.getenv("NVTE_FP8_DPA_BWD", "1"))

from transformer_engine.pytorch.cpu_offload import (
CPUOffloadEnabled,
mark_activation_offload,
NVTE_CPU_OFFLOAD_LEGACY_CODE_PATH,
)

if CPUOffloadEnabled:
if is_cpu_offload_enabled() and NVTE_CPU_OFFLOAD_LEGACY_CODE_PATH:
if ctx.fp8:
tensor_list = fp8_tensors
else:
Expand All @@ -1113,6 +1123,7 @@ def forward(
ctx.is_input_fp8 = is_input_fp8
ctx.is_output_fp8 = is_output_fp8
qkvo_tensors = (q, k, v, out_save) if not ctx.fp8 else (None, None, None, None)

tensors_to_save, tensor_objects = prepare_for_saving(
*fp8_tensors,
*qkvo_tensors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import math
import os
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import warnings
import logging

import torch
Expand Down Expand Up @@ -1171,14 +1170,6 @@ def forward(
softmax_offset=softmax_offset,
)

from transformer_engine.pytorch.cpu_offload import CPUOffloadEnabled

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this warning, because it is not True and we do not want to expose old code path to new users and I think all current users make sure they do not use unfused attention.


if CPUOffloadEnabled:
warnings.warn(
"Attention activation Offloading is only implemented"
"with Flash Attention and Fused Attention!"
)

if use_unfused_attention:
if checkpoint_core_attention:
return self._checkpointed_attention_forward(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,10 @@ def forward(
# Core attention computation
# ===========================

from transformer_engine.pytorch.cpu_offload import start_offload, is_cpu_offload_enabled
Comment thread
pggPL marked this conversation as resolved.
Resolved

if is_cpu_offload_enabled():
start_offload(query_layer, key_layer, value_layer, offload_base_tensor=True)
context_layer = self.core_attention(
query_layer,
key_layer,
Expand Down
Loading