Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ca8e366
initial context_parallel_size support in trainer
kashif Aug 15, 2025
29b22cf
Merge branch 'main' into trainer-cp
kashif Aug 15, 2025
d70fef4
For context parallelism, use AVG instead of SUM to avoid over-account…
kashif Aug 15, 2025
dd58dd0
git pushMerge branch 'trainer-cp' of https://github.com/huggingface/t…
kashif Aug 15, 2025
ecc2366
use parallelism_config.cp_enabled
kashif Aug 15, 2025
a629ff0
add parallelism_config to trainer state
kashif Aug 15, 2025
66d4273
warn when auto-enabling FSDP
kashif Aug 15, 2025
ffa4699
fix some reviews
kashif Aug 15, 2025
361f122
WIP: somewhat matching loss
S1ro1 Aug 18, 2025
3d426c1
Merge branch 'main' into trainer-cp
kashif Aug 18, 2025
3efe69b
Merge branch 'main' into trainer-cp
kashif Aug 19, 2025
eca52ac
Feat: add back nested_gather
S1ro1 Aug 19, 2025
951527b
Feat: cleanup
S1ro1 Aug 19, 2025
412c15e
Fix: raise on non-sdpa attn
S1ro1 Aug 19, 2025
be60c40
Merge branch 'main' into trainer-cp
S1ro1 Aug 19, 2025
2c357aa
remove context_parallel_size from TrainingArguments
kashif Aug 19, 2025
71e082f
if we have parallelism_config, we defer to get_state_dict from accele…
kashif Aug 20, 2025
37e6fdf
Merge branch 'main' into trainer-cp
kashif Aug 20, 2025
4f6fe15
Merge branch 'main' into trainer-cp
kashif Aug 21, 2025
485d7fa
fix form review
kashif Aug 22, 2025
3d16def
Feat: add parallelism config support
S1ro1 Aug 22, 2025
25a308e
Chore: revert some unwanted formatting changes
S1ro1 Aug 22, 2025
6d41365
Fix: check None
S1ro1 Aug 22, 2025
d82022c
Check none 2
S1ro1 Aug 22, 2025
ae9f878
Fix: remove duplicate import
S1ro1 Aug 22, 2025
531924e
Merge branch 'main' into trainer-cp
S1ro1 Aug 22, 2025
64d7336
Merge branch 'main' into trainer-cp
SunMarc Aug 22, 2025
52cb3bc
Update src/transformers/trainer.py
S1ro1 Aug 22, 2025
6e9fb30
Update src/transformers/training_args.py
S1ro1 Aug 22, 2025
bf187b2
Merge branch 'main' into trainer-cp
kashif Aug 25, 2025
33817f3
Fin
S1ro1 Aug 25, 2025
2294506
require accerelate 1.10.1 and higer
kashif Aug 25, 2025
f956348
Merge branch 'main' into trainer-cp
SunMarc Aug 26, 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
68 changes: 65 additions & 3 deletions src/transformers/trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@ def __init__(
stateful_callbacks=[
cb for cb in self.callback_handler.callbacks + [self.control] if isinstance(cb, ExportableState)
],
parallelism_config=getattr(self, "parallelism_config", None),
)
# Internal variable to count flos in each process, will be accumulated in `self.state.total_flos` then
# returned to 0 every time flos need to be logged
Expand Down Expand Up @@ -2424,7 +2425,8 @@ def _inner_training_loop(
self.state = TrainerState(
stateful_callbacks=[
cb for cb in self.callback_handler.callbacks + [self.control] if isinstance(cb, ExportableState)
]
],
parallelism_config=self.parallelism_config,
)
self.state.is_hyper_param_search = trial is not None
self.state.train_batch_size = self._train_batch_size
Expand Down Expand Up @@ -3870,7 +3872,34 @@ def training_step(
return loss_mb.reduce_mean().detach().to(self.args.device)

with self.compute_loss_context_manager():
loss = self.compute_loss(model, inputs, num_items_in_batch=num_items_in_batch)
# Wrap forward pass with context parallelism if enabled
if getattr(self, "is_cp_enabled", False):
Comment thread
kashif marked this conversation as resolved.
Outdated
# Prepare buffers for context parallelism
buffers = []
buffer_seq_dims = []

# position_ids are already provided by the data collator, no need to create them manually

if "input_ids" in inputs:
buffers.append(inputs["input_ids"])
buffer_seq_dims.append(1) # Sequence dimension
if "labels" in inputs:
buffers.append(inputs["labels"])
buffer_seq_dims.append(1)
if "attention_mask" in inputs:
Comment thread
kashif marked this conversation as resolved.
Outdated
buffers.append(inputs["attention_mask"])
buffer_seq_dims.append(1)
# Include position_ids in context parallelism splitting (key for Flash Attention compatibility)
if "position_ids" in inputs and inputs["position_ids"] is not None:
buffers.append(inputs["position_ids"])
buffer_seq_dims.append(1)

with self.accelerator.maybe_context_parallel(
buffers=buffers, buffer_seq_dims=buffer_seq_dims, no_restore_buffers=set(buffers)
):
loss = self.compute_loss(model, inputs, num_items_in_batch=num_items_in_batch)
else:
loss = self.compute_loss(model, inputs, num_items_in_batch=num_items_in_batch)

del inputs
if (
Expand Down Expand Up @@ -4150,6 +4179,11 @@ def _save(self, output_dir: Optional[str] = None, state_dict=None):
os.makedirs(output_dir, exist_ok=True)
logger.info(f"Saving model checkpoint to {output_dir}")

# Handle context parallelism state dict properly
if state_dict is None and self.is_cp_enabled:
Comment thread
kashif marked this conversation as resolved.
Outdated
# Use accelerator.get_state_dict() for proper gathering when context parallelism is enabled
state_dict = self.accelerator.get_state_dict(self.model)

supported_classes = (PreTrainedModel,) if not is_peft_available() else (PreTrainedModel, PeftModel)
# Save a trained model and configuration using `save_pretrained()`.
# They can then be reloaded using `from_pretrained()`
Expand Down Expand Up @@ -5324,6 +5358,14 @@ def create_accelerator_and_postprocess(self):
args = {
"deepspeed_plugin": self.args.deepspeed_plugin,
}

# Add parallelism_config if context parallelism is enabled
if getattr(self.args, "parallelism_config", None) is not None:
args["parallelism_config"] = self.args.parallelism_config

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 add a accelerate version check + an import error or a warning


# Add fsdp_plugin if it was created for context parallelism
if getattr(self.args, "fsdp_plugin", None) is not None:
args["fsdp_plugin"] = self.args.fsdp_plugin

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.

maybe here also but not sure

if is_accelerate_available("0.28.0"):
args["dataloader_config"] = dataloader_config
else:
Expand Down Expand Up @@ -5351,6 +5393,11 @@ def create_accelerator_and_postprocess(self):
self.is_deepspeed_enabled = getattr(self.accelerator.state, "deepspeed_plugin", None) is not None
self.is_fsdp_enabled = getattr(self.accelerator.state, "fsdp_plugin", None) is not None
self.is_tp_enabled = getattr(self.accelerator.state, "torch_tp_plugin", None) is not None
self.parallelism_config = getattr(self.accelerator.state, "parallelism_config", None)
self.is_cp_enabled = self.parallelism_config and self.parallelism_config.cp_enabled

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.

not used anywhere

# Update state with parallelism config for callbacks and logging (if state exists)
if hasattr(self, "state") and self.state is not None:
self.state.parallelism_config = self.parallelism_config
# post accelerator creation setup
if self.is_fsdp_enabled:
fsdp_plugin = self.accelerator.state.fsdp_plugin
Expand Down Expand Up @@ -5457,7 +5504,22 @@ def get_batch_samples(
pass

if num_items_in_batch is not None:
if self.args.average_tokens_across_devices:
# Handle context parallelism token counting
if self.is_cp_enabled:
Comment thread
kashif marked this conversation as resolved.
Outdated
# For context parallelism, use AVG instead of SUM to avoid over-accounting tokens
import torch.distributed as dist

if dist.is_initialized():
# Convert to tensor for all_reduce
if not torch.is_tensor(num_items_in_batch):
num_items_in_batch = torch.tensor(num_items_in_batch, dtype=torch.long)
num_items_in_batch = num_items_in_batch.to(device)

# All-reduce with AVG across context parallel group
# Note: We use all processes here as we don't have easy access to CP group
dist.all_reduce(num_items_in_batch, op=dist.ReduceOp.AVG)
Comment thread
kashif marked this conversation as resolved.
Outdated
num_items_in_batch = int(num_items_in_batch.item())
elif self.args.average_tokens_across_devices:
num_items_in_batch = self.accelerator.gather(num_items_in_batch).sum()

if torch.is_tensor(num_items_in_batch):
Expand Down
9 changes: 8 additions & 1 deletion src/transformers/trainer_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
import json
import math
from dataclasses import dataclass
from typing import Optional, Union
from typing import TYPE_CHECKING, Optional, Union

import numpy as np


if TYPE_CHECKING:
from accelerate import ParallelismConfig
from tqdm.auto import tqdm

from .trainer_utils import HPSearchBackend, IntervalStrategy, SaveStrategy, has_length
Expand Down Expand Up @@ -91,6 +95,8 @@ class TrainerState:
stateful_callbacks (`list[StatefulTrainerCallback]`, *optional*):
Callbacks attached to the `Trainer` that should have their states be saved or restored.
Relevant callbacks should implement a `state` and `from_state` function.
parallelism_config (`ParallelismConfig`, *optional*):
Configuration for parallel training modes including context parallelism.
"""

epoch: Optional[float] = None
Expand All @@ -113,6 +119,7 @@ class TrainerState:
trial_name: Optional[str] = None
trial_params: dict[str, Union[str, float, int, bool]] = None
stateful_callbacks: list["TrainerCallback"] = None
parallelism_config: Optional["ParallelismConfig"] = None

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.

why do we put that here ? do we really want to save that ?


def __post_init__(self):
if self.log_history is None:
Expand Down
52 changes: 52 additions & 0 deletions src/transformers/training_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,16 @@ class TrainingArguments:
label_smoothing_factor: float = field(
default=0.0, metadata={"help": "The label smoothing epsilon to apply (zero means no label smoothing)."}
)
context_parallel_size: int = field(
Comment thread
kashif marked this conversation as resolved.
Outdated
default=1,
metadata={
"help": (
"Context Parallelism size for splitting long sequences across devices. "
"Requires FSDP v2 and accelerate >= 1.10.0. Default is 1 (disabled). "
"When enabled, sequences are split across multiple GPUs to handle longer contexts."
)
},
)

default_optim = "adamw_torch"
if is_torch_available():
Expand Down Expand Up @@ -2070,6 +2080,48 @@ def __post_init__(self):
self.deepspeed_plugin.set_mixed_precision(mixed_precision)
self.deepspeed_plugin.set_deepspeed_weakref()

# Initialize parallelism_config and fsdp_plugin for context parallelism
self.parallelism_config = None
self.fsdp_plugin = None

# Handle context parallelism setup
if self.context_parallel_size > 1:
from accelerate import __version__ as accelerate_version
from packaging import version

if version.parse(accelerate_version) < version.parse("1.10.0"):
raise ValueError(f"Context parallelism requires accelerate >= 1.10.0, but found {accelerate_version}")

from accelerate.parallelism_config import ParallelismConfig

# Create ParallelismConfig
self.parallelism_config = ParallelismConfig(cp_size=self.context_parallel_size)

# Context parallelism requires FSDP v2
# Only create if not already using FSDP
if not self.fsdp:
from accelerate.utils import FullyShardedDataParallelPlugin

self.fsdp_plugin = FullyShardedDataParallelPlugin(
fsdp_version=2,
auto_wrap_policy="transformer_based_wrap",
state_dict_type="FULL_STATE_DICT",
)
else:
# Ensure FSDP v2 is used when context parallelism is enabled
Comment thread
kashif marked this conversation as resolved.
Outdated
if self.fsdp_config.get("version", 1) != 2:
logger.warning("Context parallelism requires FSDP v2. Updating FSDP config to use version 2.")
self.fsdp_config["version"] = 2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shouldn't it warn the user when it's enabling FSDP without explicit configuration from the user?


# Validation for context parallelism
if self.per_device_train_batch_size != 1:
logger.warning(
f"Context parallelism typically requires batch_size=1, "
f"but got {self.per_device_train_batch_size}. "
f"Setting per_device_train_batch_size=1."
)
self.per_device_train_batch_size = 1

if self.use_cpu:
self.dataloader_pin_memory = False

Expand Down