Text Generation
Transformers
Safetensors
qwen3
llama-factory
full
Generated from Trainer
conversational
text-generation-inference
Instructions to use ayh015/myLightningOPD with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ayh015/myLightningOPD with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="ayh015/myLightningOPD") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("ayh015/myLightningOPD") model = AutoModelForCausalLM.from_pretrained("ayh015/myLightningOPD", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use ayh015/myLightningOPD with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "ayh015/myLightningOPD" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ayh015/myLightningOPD", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/ayh015/myLightningOPD
- SGLang
How to use ayh015/myLightningOPD with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "ayh015/myLightningOPD" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ayh015/myLightningOPD", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "ayh015/myLightningOPD" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ayh015/myLightningOPD", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use ayh015/myLightningOPD with Docker Model Runner:
docker model run hf.co/ayh015/myLightningOPD
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import logging | |
| import os | |
| import random | |
| from argparse import Namespace | |
| from itertools import accumulate | |
| import ray | |
| import torch | |
| import torch.distributed as dist | |
| import torch.nn.functional as F | |
| from ring_flash_attn import substitute_hf_flash_attn, update_ring_flash_attn_params | |
| from tqdm import tqdm | |
| from transformers import AutoConfig | |
| from slime.ray.train_actor import TrainRayActor | |
| from slime.utils import train_dump_utils, train_metric_utils | |
| from slime.utils.context_utils import with_defer | |
| from slime.utils.data import get_minimum_num_micro_batch_size, process_rollout_data | |
| from slime.utils.distributed_utils import get_gloo_group | |
| from slime.utils.memory_utils import clear_memory, print_memory | |
| from slime.utils.metric_utils import compute_rollout_step | |
| from slime.utils.misc import load_function | |
| from slime.utils.ppo_utils import ( | |
| compute_approx_kl, | |
| compute_gspo_kl, | |
| compute_opsm_mask, | |
| compute_policy_loss, | |
| vanilla_tis_function, | |
| ) | |
| from slime.utils.processing_utils import load_processor, load_tokenizer | |
| from slime.utils.ray_utils import Box | |
| from slime.utils.timer import Timer, inverse_timer, timer | |
| from slime.utils.tracking_utils import init_tracking | |
| from ...utils import tracking_utils | |
| from ...utils.profile_utils import TrainProfiler | |
| from . import checkpoint | |
| from .data_packing import pack_sequences, pad_packed_sequence_with_cp, unpack_sequences | |
| from .lr_scheduler import get_lr_scheduler | |
| from .update_weight_utils import UpdateWeightFromDistributed, UpdateWeightFromTensor | |
| logger = logging.getLogger(__name__) | |
| class FSDPTrainRayActor(TrainRayActor): | |
| """Simplified TrainRayActor for pure HF+FSDP training. | |
| Responsibilities: | |
| * Initialize model/tokenizer on rank0 sequentially to avoid race on cache | |
| * Wrap model with FSDP | |
| * Provide minimal train / save / update_weights hooks compatible with existing RayTrainGroup | |
| Weight update strategy: | |
| * Rank0 gathers state_dict (full) and broadcasts tensor-by-tensor. | |
| * For small models this is fine; for larger models consider sharded state_dict type. | |
| """ | |
| def init(self, args: Namespace, role: str, with_ref: bool = False) -> int: # type: ignore[override] | |
| super().init(args, role, with_ref) | |
| # Setup device mesh for parallelism (handles both CP and non-CP cases) | |
| self._setup_device_mesh() | |
| torch.manual_seed(args.seed) | |
| self.train_parallel_config = { | |
| "dp_size": self.dp_size, | |
| } | |
| if self.args.debug_rollout_only: | |
| return 0 | |
| self.fsdp_cpu_offload = getattr(self.args, "fsdp_cpu_offload", False) | |
| # Offload train and fsdp cpu offload cannot be used together, fsdp_cpu_offload is more aggressive | |
| if self.args.offload_train and self.fsdp_cpu_offload: | |
| self.args.offload_train = False | |
| self._enable_true_on_policy_optimizations(args) | |
| if dist.get_rank() == 0: | |
| init_tracking(args, primary=False) | |
| if getattr(self.args, "start_rollout_id", None) is None: | |
| self.args.start_rollout_id = 0 | |
| self.prof = TrainProfiler(args) | |
| for i in range(dist.get_world_size()): | |
| if i == dist.get_rank(): | |
| self.hf_config = AutoConfig.from_pretrained(self.args.hf_checkpoint, trust_remote_code=True) | |
| self.tokenizer = load_tokenizer(self.args.hf_checkpoint, trust_remote_code=True) | |
| # Vision models have `vision_config` in the config | |
| if hasattr(self.hf_config, "vision_config"): | |
| self.processor = load_processor(self.args.hf_checkpoint, trust_remote_code=True) | |
| dist.barrier(group=get_gloo_group()) | |
| init_context = self._get_init_weight_context_manager() | |
| with init_context(): | |
| model = self.get_model_cls().from_pretrained( | |
| self.args.hf_checkpoint, | |
| trust_remote_code=True, | |
| attn_implementation=self.args.attn_implementation, | |
| ) | |
| model.train() | |
| full_state = model.state_dict() | |
| model = apply_fsdp2(model, mesh=self.dp_mesh, cpu_offload=self.fsdp_cpu_offload, args=self.args) | |
| model = self._fsdp2_load_full_state_dict( | |
| model, full_state, self.dp_mesh, cpu_offload=True if self.fsdp_cpu_offload else None | |
| ) | |
| self.model = model | |
| if args.gradient_checkpointing: | |
| self.model.gradient_checkpointing_enable() | |
| if args.optimizer == "adam": | |
| self.optimizer = torch.optim.AdamW( | |
| self.model.parameters(), | |
| lr=args.lr, | |
| betas=(args.adam_beta1, args.adam_beta2), | |
| eps=args.adam_eps, | |
| weight_decay=args.weight_decay, | |
| ) | |
| else: | |
| raise ValueError(f"Unsupported optimizer: {args.optimizer}. Supported options: 'adam'") | |
| # Initialize LR scheduler | |
| self.lr_scheduler = get_lr_scheduler(args, self.optimizer) | |
| self.global_step = 0 | |
| self.micro_step = 0 | |
| checkpoint_payload = checkpoint.load(self) | |
| # Create separate ref model if needed (kept in CPU until needed) | |
| self.ref_model = None | |
| if with_ref: | |
| self.ref_model = self._create_ref_model(args.ref_load) | |
| self.weight_updater = ( | |
| UpdateWeightFromTensor(self.args, self.model) | |
| if self.args.colocate | |
| else UpdateWeightFromDistributed(self.args, self.model) | |
| ) | |
| checkpoint.finalize_load(self, checkpoint_payload) | |
| # Initialize data packing parameters | |
| self.max_tokens_per_gpu = args.max_tokens_per_gpu # From main arguments | |
| if self.args.offload_train: | |
| self.sleep() | |
| self.prof.on_init_end() | |
| return int(getattr(self.args, "start_rollout_id", 0)) | |
| def get_model_cls(self): | |
| # Vision models have `vision_config` in the config | |
| if hasattr(self.hf_config, "vision_config"): | |
| from transformers import AutoModelForImageTextToText | |
| return AutoModelForImageTextToText | |
| else: | |
| from transformers import AutoModelForCausalLM | |
| return AutoModelForCausalLM | |
| def _enable_true_on_policy_optimizations(self, args): | |
| if args.true_on_policy_mode: | |
| from sglang.srt.batch_invariant_ops import enable_batch_invariant_mode | |
| from .models.qwen3_moe import apply_true_on_policy_patch_for_qwen3_moe | |
| logger.info("FSDPTrainRayActor call enable_batch_invariant_mode for true-on-policy") | |
| enable_batch_invariant_mode( | |
| # In Qwen3, rope `inv_freq_expanded.float() @ position_ids_expanded.float()` uses bmm | |
| # and disabling it will make it aligned | |
| enable_bmm=False, | |
| ) | |
| apply_true_on_policy_patch_for_qwen3_moe() | |
| else: | |
| from .models.qwen3_moe_hf import apply_fsdp_moe_patch | |
| apply_fsdp_moe_patch() | |
| def _setup_device_mesh(self) -> None: | |
| """Setup device mesh for parallelism (always called, handles both CP and non-CP cases). | |
| Creates 2D mesh (dp_size, cp_size) for all cases: | |
| - When context_parallel_size > 1: hybrid CP + DP | |
| - When context_parallel_size = 1: pure DP (equivalent to 1D mesh) | |
| This ensures consistent group management across all parallelism modes. | |
| """ | |
| from torch.distributed.device_mesh import init_device_mesh | |
| world_size = dist.get_world_size() | |
| rank = dist.get_rank() | |
| # Use context_parallel_size directly (defaults to 1 for pure DP) | |
| self.cp_size = self.args.context_parallel_size | |
| self.dp_size = world_size // self.cp_size | |
| # Create 2D device mesh: (dp_size, cp_size) | |
| # Ranks laid out in row-major: mesh[dp_idx, cp_idx] = dp_idx * cp_size + cp_idx | |
| # - CP groups: consecutive ranks along dim 1, e.g., [0,1], [2,3], [4,5], [6,7] | |
| # - DP groups: striped ranks along dim 0, e.g., [0,2,4,6], [1,3,5,7] | |
| # When cp_size=1, this degenerates to pure DP | |
| self.mesh = init_device_mesh("cuda", mesh_shape=(self.dp_size, self.cp_size), mesh_dim_names=("dp", "cp")) | |
| # Extract process groups from mesh | |
| self.dp_group = self.mesh.get_group("dp") # For FSDP gradient sync, metric reduction | |
| self.cp_group = self.mesh.get_group("cp") # For Ring Flash Attention, logit gathering | |
| self.dp_mesh = self.mesh["dp"] # For FSDP | |
| # Compute local ranks within each dimension | |
| self.dp_rank = rank // self.cp_size | |
| self.cp_rank = rank % self.cp_size | |
| logger.info( | |
| f"[Rank {rank}] Device mesh (2D): world_size={world_size}, " | |
| f"cp_size={self.cp_size}, dp_size={self.dp_size}" | |
| ) | |
| logger.info(f"[Rank {rank}] Mesh shape: {self.mesh.shape}, " f"dp_rank={self.dp_rank}, cp_rank={self.cp_rank}") | |
| # Setup Ring Flash Attention with CP group from mesh (only when cp_size > 1) | |
| if self.cp_size > 1: | |
| substitute_hf_flash_attn(self.cp_group, heads_k_stride=1) | |
| logger.info(f"[Rank {rank}] CP initialized via device mesh") | |
| else: | |
| logger.info(f"[Rank {rank}] Pure DP mode (cp_size=1)") | |
| def _get_init_weight_context_manager(self): | |
| """Get context manager for model initialization. | |
| Returns a callable that creates a context manager. | |
| Uses meta device (no memory allocation) for non-rank-0 processes, | |
| UNLESS tie_word_embeddings=True (which causes hangs with meta tensors). | |
| Ref: verl/utils/fsdp_utils.py::get_init_weight_context_manager | |
| NOTE: tie_word_embedding causes meta_tensor init to hang | |
| """ | |
| from accelerate import init_empty_weights | |
| # Check if model uses tied word embeddings (which doesn't work with meta tensors) | |
| use_meta_tensor = not self.hf_config.tie_word_embeddings | |
| def cpu_init_weights(): | |
| return torch.device("cpu") | |
| if use_meta_tensor: | |
| # Rank 0: CPU, others: meta device (memory efficient for large models) | |
| return init_empty_weights if dist.get_rank() != 0 else cpu_init_weights | |
| else: | |
| logger.info(f"[Rank {dist.get_rank()}] tie_word_embeddings=True, loading full model to CPU on all ranks") | |
| return cpu_init_weights | |
| def _fsdp2_load_full_state_dict(self, model, full_state, device_mesh, cpu_offload): | |
| """Load full state dict into FSDP2 model with efficient broadcast from rank 0. | |
| This function loads weights from rank 0 and broadcasts to all other ranks, | |
| avoiding the need for each rank to load the full model from disk. | |
| Args: | |
| model: FSDP2-wrapped model | |
| full_state: State dict (only rank 0 has real weights, others have empty dict) | |
| device_mesh: Device mesh for FSDP | |
| cpu_offload: If not None, enables StateDictOptions cpu_offload | |
| Ref:verl/utils/fsdp_utils.py::fsdp2_load_full_state_dict | |
| """ | |
| from torch.distributed.checkpoint.state_dict import StateDictOptions, set_model_state_dict | |
| # Rank 0: move with weights, others: allocate empty tensors on device | |
| if dist.get_rank() == 0: | |
| model = model.to(device=torch.cuda.current_device(), non_blocking=True) | |
| else: | |
| # to_empty creates tensors on device without initializing memory | |
| model = model.to_empty(device=torch.cuda.current_device()) | |
| is_cpu_offload = cpu_offload is not None | |
| options = StateDictOptions(full_state_dict=True, cpu_offload=is_cpu_offload, broadcast_from_rank0=True) | |
| set_model_state_dict(model, full_state, options=options) | |
| # set_model_state_dict will not broadcast buffers, so we need to broadcast them manually. | |
| for _name, buf in model.named_buffers(): | |
| dist.broadcast(buf, src=0) | |
| if is_cpu_offload: | |
| model.to("cpu", non_blocking=True) | |
| for buf in model.buffers(): | |
| buf.data = buf.data.to(torch.cuda.current_device()) | |
| return model | |
| def sleep(self) -> None: | |
| """Pause CUDA memory for all tracked tensors.""" | |
| if not self.args.offload_train: | |
| return | |
| print_memory("before offload model") | |
| self.model.cpu() | |
| move_torch_optimizer(self.optimizer, "cpu") | |
| clear_memory() | |
| dist.barrier(group=get_gloo_group()) | |
| print_memory("after offload model") | |
| def wake_up(self) -> None: | |
| """Resume CUDA memory for all tracked tensors.""" | |
| if not self.args.offload_train: | |
| return | |
| self.model.cuda() | |
| move_torch_optimizer(self.optimizer, "cuda") | |
| dist.barrier(group=get_gloo_group()) | |
| print_memory("after wake_up model") | |
| def save_model(self, rollout_id: int, force_sync: bool = False) -> None: | |
| """Delegate checkpoint saving to the shared checkpoint utilities.""" | |
| if self.args.debug_rollout_only or self.args.save is None: | |
| return | |
| assert not self.args.async_save, "FSDPTrainRayActor does not support async_save yet." | |
| checkpoint.save(self, rollout_id) | |
| def _compute_log_prob( | |
| self, | |
| model_tag: str, | |
| packed_batches: list[dict[str, torch.Tensor]], | |
| store_prefix: str = "", | |
| ) -> dict[str, list[torch.Tensor]]: | |
| """Compute token log-probabilities for a list of packed batches. | |
| Parameters: | |
| model_tag: Which parameters to use, e.g. "actor" or "ref". | |
| packed_batches: A list of packed batch dictionaries produced by | |
| `pack_sequences`, each containing at least `tokens` and | |
| `position_ids`; may also include multimodal keys like `pixel_values`. | |
| store_prefix: Prefix to use for keys in outputs (e.g., "ref_"). | |
| Returns: | |
| A lightweight dictionary keyed by f"{store_prefix}log_probs". The | |
| actual per-sequence results are written in-place into each element of | |
| `packed_batches` under the same key and can be read back by callers. | |
| Note: | |
| Uses separate ref model when model_tag == "ref". The ref model is | |
| loaded from CPU to GPU on-demand and offloaded back after use. | |
| """ | |
| # Select which model to use | |
| if model_tag == "ref" and self.ref_model is not None: | |
| if not self.fsdp_cpu_offload: | |
| self.model.cpu() | |
| torch.cuda.empty_cache() | |
| dist.barrier(group=get_gloo_group()) | |
| active_model = self.ref_model | |
| active_model.eval() | |
| else: | |
| active_model = self.model | |
| try: | |
| rollout_data = {f"{store_prefix}log_probs": []} | |
| with timer(f"{store_prefix}log_probs"), torch.no_grad(): | |
| for batch in self.prof.iterate_train_log_probs( | |
| tqdm(packed_batches, desc=f"{store_prefix}log_probs", disable=dist.get_rank() != 0) | |
| ): | |
| model_args = self._get_model_inputs_args(batch) | |
| logits = active_model(**model_args).logits.squeeze(0).float() | |
| log_probs_result, entropy_result = get_logprob_and_entropy_with_cp( | |
| logits=logits, | |
| target_tokens=batch["tokens"], | |
| cp_rank=self.cp_rank, | |
| cp_size=self.cp_size, | |
| cp_group=self.cp_group, | |
| model_input_ids=model_args["input_ids"], | |
| allow_compile=not self.args.true_on_policy_mode, | |
| temperature=self.args.rollout_temperature, | |
| ) | |
| batch[f"{store_prefix}log_probs"] = log_probs_result | |
| if store_prefix == "": | |
| batch["entropy"] = entropy_result | |
| return rollout_data | |
| finally: | |
| # Restore actor model if it was offloaded | |
| if model_tag == "ref" and self.ref_model is not None: | |
| torch.cuda.empty_cache() | |
| dist.barrier(group=get_gloo_group()) | |
| if not self.fsdp_cpu_offload: | |
| self.model.cuda() | |
| dist.barrier(group=get_gloo_group()) | |
| def _packed_data( | |
| self, rollout_data: dict[str, list[torch.Tensor]] | |
| ) -> tuple[list[dict[str, torch.Tensor]], list[int]]: | |
| """Pack variable-length sequences for efficient processing. | |
| Parameters: | |
| rollout_data: Dictionary of lists containing sequence-level tensors | |
| such as `tokens`, `loss_masks`, `rewards`, `response_lengths`, | |
| `advantages`, `returns`, and optional `rollout_log_probs`. | |
| Returns: | |
| A pair `(packed_batches, grad_accum)` where `packed_batches` is a list | |
| of packed batch dictionaries and `grad_accum` lists the micro-batch | |
| indices at which to perform optimizer steps. | |
| """ | |
| # Pack sequences efficiently | |
| tokens = rollout_data["tokens"] | |
| packed_batches = [] | |
| mbs_size_list = [] | |
| local_batch_size = self.args.global_batch_size // self.dp_size | |
| assert ( | |
| self.args.global_batch_size % self.dp_size == 0 | |
| ), f"global_batch_size {self.args.global_batch_size} is not divisible by dp_world_size {self.dp_size}" | |
| # Use global_batch_size for splitting when max_tokens_per_gpu is enabled | |
| if self.args.use_dynamic_batch_size: | |
| # In CP mode, CP group shares sequences, so total capacity is max_tokens_per_gpu * cp_size | |
| max_tokens = self.args.max_tokens_per_gpu | |
| if self.cp_size > 1: | |
| max_tokens = max_tokens * self.cp_size | |
| for i in range(0, len(tokens), local_batch_size): | |
| mbs_size_list.append( | |
| get_minimum_num_micro_batch_size( | |
| [len(t) for t in rollout_data["tokens"][i : i + local_batch_size]], | |
| max_tokens, | |
| ) | |
| ) | |
| num_microbatches = torch.tensor(mbs_size_list, dtype=torch.int, device=torch.cuda.current_device()) | |
| dist.all_reduce(num_microbatches, op=dist.ReduceOp.MAX, group=self.dp_group) | |
| num_microbatches = num_microbatches.tolist() | |
| else: | |
| num_microbatches = [self.args.global_batch_size // (self.args.micro_batch_size * self.dp_size)] * ( | |
| len(tokens) // local_batch_size | |
| ) | |
| start = 0 | |
| for mbs_size in num_microbatches: | |
| end = start + local_batch_size | |
| packed_batches.extend( | |
| pack_sequences( | |
| rollout_data["tokens"][start:end], | |
| rollout_data["loss_masks"][start:end], | |
| rollout_data["rewards"][start:end], | |
| rollout_data["raw_reward"][start:end], | |
| rollout_data["response_lengths"][start:end], | |
| rollout_data["advantages"][start:end], | |
| rollout_data["returns"][start:end], | |
| rollout_log_probs=( | |
| rollout_data["rollout_log_probs"][start:end] if "rollout_log_probs" in rollout_data else None | |
| ), | |
| multimodal_train_inputs=( | |
| rollout_data["multimodal_train_inputs"][start:end] | |
| if "multimodal_train_inputs" in rollout_data | |
| else None | |
| ), | |
| num_packs=mbs_size, | |
| ) | |
| ) | |
| start = end | |
| grad_accum = list(accumulate(num_microbatches)) | |
| return packed_batches, grad_accum | |
| def train(self, rollout_id: int, rollout_data_ref: Box) -> None: | |
| """Run one training update over a rollout batch. | |
| Parameters: | |
| rollout_id: Monotonic id for logging. | |
| rollout_data_ref: A Box handle wrapping a Ray object reference to a | |
| dictionary with rollout tensors and metadata (e.g., `tokens`, | |
| `loss_masks`, `rewards`, `response_lengths`, optional | |
| `rollout_log_probs`, etc.). It will be fetched and partitioned | |
| by `process_rollout_data` based on data-parallel rank/size. | |
| """ | |
| if self.args.offload_train: | |
| self.wake_up() | |
| with inverse_timer("train_wait"), timer("train"): | |
| rollout_data = process_rollout_data(self.args, rollout_data_ref, self.dp_rank, self.dp_size) | |
| if self.args.debug_rollout_only: | |
| return | |
| self._train_core(rollout_id=rollout_id, rollout_data=rollout_data) | |
| train_metric_utils.log_perf_data_raw( | |
| rollout_id=rollout_id, | |
| args=self.args, | |
| is_primary_rank=dist.get_rank() == 0, | |
| compute_total_fwd_flops=None, | |
| ) | |
| def _log_rollout_data(self, rollout_id: int, rollout_data, packed_batches): | |
| log_dict = {} | |
| if "raw_reward" in rollout_data and dist.get_rank() == 0: | |
| raw_reward_list = rollout_data["raw_reward"] | |
| if raw_reward_list: | |
| log_dict["rollout/raw_reward"] = sum(raw_reward_list) / len(raw_reward_list) | |
| for metric_key in ["log_probs", "rollout_log_probs", "ref_log_probs", "advantages", "returns"]: | |
| if metric_key not in packed_batches[0]: | |
| continue | |
| val = torch.tensor([0.0], device=torch.cuda.current_device()) | |
| for _mbs_id, batches in enumerate(packed_batches): | |
| unpacked_batches = unpack_sequences(batches) | |
| for unpacked_batch in unpacked_batches: | |
| if isinstance(unpacked_batch[metric_key], torch.Tensor): | |
| loss_masks_tensor = unpacked_batch["loss_masks"].to(device=torch.cuda.current_device()) | |
| metric_tensor = unpacked_batch[metric_key].to(device=torch.cuda.current_device()) | |
| val += (metric_tensor * loss_masks_tensor).sum() / loss_masks_tensor.sum().clamp_min(1) | |
| else: | |
| val += unpacked_batch[metric_key] | |
| dist.all_reduce(val, op=dist.ReduceOp.SUM, group=self.dp_group) | |
| log_dict[f"rollout/{metric_key}"] = ( | |
| val / (self.args.n_samples_per_prompt * self.args.rollout_batch_size) | |
| ).item() | |
| if dist.get_rank() == 0: | |
| logger.info(f"rollout {rollout_id}: {log_dict}") | |
| log_dict["rollout/step"] = compute_rollout_step(self.args, rollout_id) | |
| tracking_utils.log(self.args, log_dict, step_key="rollout/step") | |
| if self.args.ci_test and self.args.true_on_policy_mode: | |
| assert log_dict["rollout/log_probs"] == log_dict["rollout/rollout_log_probs"], ( | |
| f"CI check failed: true_on_policy_mode is enabled, but log_probs " | |
| f"({log_dict['rollout/log_probs']}) != rollout_log_probs " | |
| f"({log_dict['rollout/rollout_log_probs']})" | |
| ) | |
| def _train_core(self, rollout_id: int, rollout_data) -> None: | |
| if self.args.advantage_estimator in ["grpo", "gspo"]: | |
| rollout_data["advantages"] = rollout_data["returns"] = [ | |
| torch.tensor([rollout_data["rewards"][i]] * rollout_data["response_lengths"][i]) | |
| for i in range(len(rollout_data["rewards"])) | |
| ] | |
| else: | |
| raise NotImplementedError(f"Unsupported advantage_estimator {self.args.advantage_estimator}") | |
| packed_batches, grad_accum = self._packed_data(rollout_data) | |
| assert ( | |
| len(grad_accum) > 0 | |
| ), f"Invalid grad_accum {grad_accum} for micro_batch_size {self.args.micro_batch_size} and global_batch_size {self.args.global_batch_size}" | |
| if self.ref_model is not None: | |
| self._compute_log_prob("ref", packed_batches, store_prefix="ref_") | |
| self._compute_log_prob("actor", packed_batches) | |
| self._log_rollout_data(rollout_id, rollout_data, packed_batches) | |
| with timer("actor_train"): | |
| reported_accum: dict[str, list[torch.Tensor]] = {} | |
| self.optimizer.zero_grad(set_to_none=True) | |
| for mbs_id, packed_batch in self.prof.iterate_train_actor( | |
| enumerate(tqdm(packed_batches, desc="actor_train", disable=dist.get_rank() != 0)) | |
| ): | |
| self._train_step( | |
| packed_batch=packed_batch, | |
| reported_accum=reported_accum, | |
| mbs_id=mbs_id, | |
| grad_accum=grad_accum, | |
| ) | |
| self.prof.step(rollout_id=rollout_id) | |
| train_dump_utils.save_debug_train_data(self.args, rollout_id=rollout_id, rollout_data=rollout_data) | |
| # Update ref model if needed (copy actor weights to ref) | |
| if ( | |
| self.args.ref_update_interval is not None | |
| and (rollout_id + 1) % self.args.ref_update_interval == 0 | |
| and self.ref_model is not None | |
| ): | |
| if dist.get_rank() == 0: | |
| logger.info(f"Updating ref model at rollout_id {rollout_id}") | |
| # Copy actor model state to ref model | |
| actor_state = self.model.state_dict() | |
| self.ref_model.load_state_dict(actor_state) | |
| self.ref_model.cpu() | |
| def _train_step(self, packed_batch, reported_accum, mbs_id, grad_accum): | |
| # Prepare model inputs | |
| model_args = self._get_model_inputs_args(packed_batch) | |
| logits = self.model(**model_args).logits.squeeze(0).float() | |
| # Compute log probs and entropy (unified for both CP and non-CP modes) | |
| log_probs, entropy_result = get_logprob_and_entropy_with_cp( | |
| logits=logits, | |
| target_tokens=packed_batch["tokens"], | |
| cp_rank=self.cp_rank, | |
| cp_size=self.cp_size, | |
| cp_group=self.cp_group, | |
| model_input_ids=model_args["input_ids"], | |
| allow_compile=not self.args.true_on_policy_mode, | |
| temperature=self.args.rollout_temperature, | |
| ) | |
| packed_batch["cur_log_probs"] = log_probs | |
| packed_batch["entropy"] = entropy_result | |
| unpacked_batches = unpack_sequences(packed_batch) | |
| old_log_prob_key = "rollout_log_probs" if self.args.use_rollout_logprobs else "log_probs" | |
| missing_old_log_probs = [ | |
| idx | |
| for idx, batch in enumerate(unpacked_batches) | |
| if old_log_prob_key not in batch or not isinstance(batch[old_log_prob_key], torch.Tensor) | |
| ] | |
| if missing_old_log_probs: | |
| raise KeyError( | |
| f"{old_log_prob_key} must be provided as torch.Tensor for all microbatches when " | |
| f"use_rollout_logprobs is set to {self.args.use_rollout_logprobs}. Missing in batches: {missing_old_log_probs}" | |
| ) | |
| old_log_probs = torch.cat([batch[old_log_prob_key] for batch in unpacked_batches], dim=0) | |
| log_probs = torch.cat([batch["cur_log_probs"] for batch in unpacked_batches], dim=0) | |
| advantages = torch.cat([batch["advantages"] for batch in unpacked_batches], dim=0) | |
| loss_masks = [batch["loss_masks"].to(device=log_probs.device) for batch in unpacked_batches] | |
| response_lengths = [batch["response_lengths"] for batch in unpacked_batches] | |
| advantages = advantages.to(device=log_probs.device) | |
| old_log_probs = old_log_probs.to(device=log_probs.device) | |
| ppo_kl = old_log_probs - log_probs | |
| if self.args.use_opsm: | |
| opsm_mask, opsm_clipfrac = compute_opsm_mask( | |
| args=self.args, | |
| full_log_probs=[batch["cur_log_probs"] for batch in unpacked_batches], | |
| full_old_log_probs=[batch[old_log_prob_key] for batch in unpacked_batches], | |
| advantages=[batch["advantages"] for batch in unpacked_batches], | |
| loss_masks=loss_masks, | |
| ) | |
| if self.args.advantage_estimator == "gspo": | |
| ppo_kl = compute_gspo_kl( | |
| full_log_probs=[batch["cur_log_probs"] for batch in unpacked_batches], | |
| full_old_log_probs=[batch[old_log_prob_key] for batch in unpacked_batches], | |
| local_log_probs=[batch["cur_log_probs"] for batch in unpacked_batches], | |
| loss_masks=loss_masks, | |
| ) | |
| pg_loss, pg_clipfrac = compute_policy_loss(ppo_kl, advantages, self.args.eps_clip, self.args.eps_clip_high) | |
| if self.args.use_opsm: | |
| pg_loss = pg_loss * opsm_mask | |
| def _has_rollout_log_probs(batch) -> bool: | |
| rollout_tensor = batch.get("rollout_log_probs") | |
| return isinstance(rollout_tensor, torch.Tensor) and rollout_tensor.numel() > 0 | |
| has_rollout_log_probs = all(_has_rollout_log_probs(batch) for batch in unpacked_batches) | |
| rollout_log_probs = ( | |
| torch.cat([batch["rollout_log_probs"] for batch in unpacked_batches], dim=0) | |
| if has_rollout_log_probs | |
| else None | |
| ) | |
| # Apply off-policy correction using importance sampling if enabled | |
| if self.args.use_tis: | |
| assert ( | |
| has_rollout_log_probs and rollout_log_probs is not None | |
| ), "rollout_log_probs must be provided as non-empty torch.Tensor for TIS/MIS" | |
| train_log_probs_list = list(log_probs.split(response_lengths, dim=0)) | |
| rollout_log_probs_list = list(rollout_log_probs.split(response_lengths, dim=0)) | |
| ois = (-ppo_kl).exp() | |
| tis_kwargs = { | |
| "args": self.args, | |
| "pg_loss": pg_loss, | |
| "train_log_probs": train_log_probs_list, | |
| "rollout_log_probs": rollout_log_probs_list, | |
| "loss_masks": loss_masks, | |
| "response_lengths": response_lengths, | |
| "cp_rank": self.cp_rank, | |
| "cp_size": self.cp_size, | |
| "cp_group": self.cp_group, | |
| } | |
| if self.args.custom_tis_function_path is not None: | |
| tis_func = load_function(self.args.custom_tis_function_path) | |
| else: | |
| tis_func = vanilla_tis_function | |
| pg_loss, loss_masks, tis_metrics = tis_func(**tis_kwargs) | |
| if self.args.calculate_per_token_loss: | |
| pg_loss = sum_of_token(pg_loss, response_lengths, loss_masks) | |
| pg_clipfrac = sum_of_token(pg_clipfrac, response_lengths, loss_masks) | |
| ppo_kl = sum_of_token(ppo_kl.abs(), response_lengths, loss_masks) | |
| else: | |
| pg_loss = sum_of_sample_mean(pg_loss, response_lengths, loss_masks) | |
| pg_clipfrac = sum_of_sample_mean(pg_clipfrac, response_lengths, loss_masks) | |
| ppo_kl = sum_of_sample_mean(ppo_kl.abs(), response_lengths, loss_masks) | |
| # Only compare rollout vs. train log probs when they originate from different stages. | |
| train_rollout_logprob_abs_diff = None | |
| if not self.args.use_rollout_logprobs and rollout_log_probs is not None: | |
| train_rollout_logprob_abs_diff = (old_log_probs - rollout_log_probs).abs() | |
| train_rollout_logprob_abs_diff = sum_of_sample_mean( | |
| train_rollout_logprob_abs_diff, response_lengths, loss_masks | |
| ).detach() | |
| entropy = torch.cat([batch["entropy"] for batch in unpacked_batches], dim=0) | |
| entropy_loss = sum_of_sample_mean(entropy, response_lengths, loss_masks) | |
| loss = pg_loss - self.args.entropy_coef * entropy_loss | |
| if self.args.use_kl_loss: | |
| ref_log_probs = torch.cat([batch["ref_log_probs"] for batch in unpacked_batches], dim=0) | |
| importance_ratio = None | |
| if self.args.use_unbiased_kl: | |
| importance_ratio = torch.exp(log_probs - old_log_probs) | |
| kl = compute_approx_kl( | |
| log_probs, | |
| ref_log_probs, | |
| kl_loss_type=self.args.kl_loss_type, | |
| importance_ratio=importance_ratio, | |
| ) | |
| kl_loss = sum_of_sample_mean(kl, response_lengths, loss_masks) | |
| loss = loss + self.args.kl_loss_coef * kl_loss | |
| reported = { | |
| "loss": loss.detach(), | |
| "pg_loss": pg_loss.detach(), | |
| "pg_clipfrac": pg_clipfrac.detach(), | |
| "ppo_kl": ppo_kl.detach(), | |
| "entropy_loss": entropy_loss.detach(), | |
| } | |
| if train_rollout_logprob_abs_diff is not None: | |
| reported["train_rollout_logprob_abs_diff"] = train_rollout_logprob_abs_diff | |
| if self.args.use_kl_loss: | |
| reported["kl_loss"] = kl_loss.detach() | |
| if self.args.use_opsm: | |
| reported["opsm_clipfrac"] = opsm_clipfrac | |
| if self.args.use_tis and tis_metrics: | |
| reported["ois"] = sum_of_sample_mean(ois, response_lengths, loss_masks).detach() | |
| for k, v in tis_metrics.items(): | |
| if self.args.calculate_per_token_loss: | |
| reported[k] = sum_of_token(v, response_lengths, loss_masks).detach() | |
| else: | |
| reported[k] = sum_of_sample_mean(v, response_lengths, loss_masks).detach() | |
| # Scale loss for gradient accumulation | |
| loss = loss * self.dp_size / self.args.global_batch_size | |
| loss.backward() | |
| # Accumulate reported metrics (store tensors for later mean) | |
| for k, v in reported.items(): | |
| reported_accum.setdefault(k, []).append(v) | |
| if (mbs_id + 1) in grad_accum: | |
| # TODO: check if the grad norm is global grad norm. | |
| grad_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.args.clip_grad) | |
| # the grad norm used to be of DTensor | |
| grad_norm = float(grad_norm) | |
| self.optimizer.step() | |
| # Update learning rate | |
| self.lr_scheduler.step() | |
| self.optimizer.zero_grad(set_to_none=True) | |
| # Aggregate logs | |
| aggregated = {k: torch.stack(v).sum().item() for k, v in reported_accum.items()} | |
| # TODO: change this, this is slow. | |
| reduced_aggregated = [None] * self.dp_size | |
| dist.all_gather_object(reduced_aggregated, aggregated, group=self.dp_group) | |
| aggregated = {} | |
| for k in reported_accum.keys(): | |
| aggregated[k] = sum([r[k] for r in reduced_aggregated]) / (self.args.global_batch_size) | |
| reported_accum.clear() | |
| if dist.get_rank() == 0: | |
| log_dict = { | |
| f"train/{k}": (val.item() if torch.is_tensor(val) else val) for k, val in aggregated.items() | |
| } | |
| log_dict["train/grad_norm"] = grad_norm | |
| # Log learning rate per parameter group; use scheduler's last computed LRs | |
| lr_values = self.lr_scheduler.get_last_lr() | |
| for gid, _group in enumerate(self.optimizer.param_groups): | |
| log_dict[f"train/lr-pg_{gid}"] = lr_values[gid] | |
| kl_info = "" | |
| if self.args.use_kl_loss and "kl_loss" in aggregated: | |
| kl_info = f", kl_loss: {aggregated['kl_loss']:.4f}, kl_penalty: {aggregated['kl_loss'] * self.args.kl_loss_coef:.4f}" | |
| logger.info(kl_info) | |
| logger.info(f"step {self.global_step}: {log_dict}") | |
| log_dict["train/step"] = self.global_step | |
| tracking_utils.log(self.args, log_dict, step_key="train/step") | |
| self.global_step += 1 | |
| def update_weights(self) -> None: # type: ignore[override] | |
| """Synchronize actor weights to rollout engines. | |
| Handles both colocated and distributed update modes. In offload mode, | |
| wakes up parameters as needed to perform the update. | |
| """ | |
| if self.args.debug_train_only or self.args.debug_rollout_only: | |
| return | |
| rollout_engines, rollout_engine_lock, num_new_engines = ray.get( | |
| self.rollout_manager.get_rollout_engines_and_lock.remote() | |
| ) | |
| if num_new_engines > 0: | |
| self.weight_updater.connect_rollout_engines(rollout_engines, rollout_engine_lock) | |
| dist.barrier(group=get_gloo_group()) | |
| self.weight_updater.update_weights() | |
| if self.args.ci_test and len(rollout_engines) > 0: | |
| engine = random.choice(rollout_engines) | |
| engine_version = ray.get(engine.get_weight_version.remote()) | |
| if str(engine_version) != str(self.weight_updater.weight_version): | |
| raise RuntimeError( | |
| f"Weight version mismatch! Engine: {engine_version}, Updater: {self.weight_updater.weight_version}" | |
| ) | |
| clear_memory() | |
| def _create_ref_model(self, ref_load_path: str | None): | |
| """Create and initialize a separate reference model with FSDP2 CPUOffloadPolicy. | |
| Parameters: | |
| ref_load_path: Path to a directory containing a HF checkpoint. If | |
| None, a ValueError is raised. | |
| Returns: | |
| FSDP2-wrapped ref model with CPU offload enabled | |
| Note: | |
| Creates a separate FSDP2 model instance for the reference model. | |
| ALWAYS uses CPUOffloadPolicy for the reference model to save memory, | |
| regardless of the actor model's CPU offload setting. | |
| """ | |
| if ref_load_path is None: | |
| raise ValueError("ref_load_path must be provided when loading reference model") | |
| if os.path.isdir(ref_load_path): | |
| logger.info(f"[Rank {dist.get_rank()}] Creating separate ref model from {ref_load_path}") | |
| init_context = self._get_init_weight_context_manager() | |
| with init_context(): | |
| ref_model = self.get_model_cls().from_pretrained( | |
| ref_load_path, | |
| trust_remote_code=True, | |
| attn_implementation=self.args.attn_implementation, | |
| ) | |
| full_state = ref_model.state_dict() | |
| # Always use CPUOffloadPolicy for reference, let FSDP2 handle the offload. It is faster than model.cpu(). | |
| ref_model = apply_fsdp2(ref_model, mesh=self.dp_mesh, cpu_offload=True, args=self.args) | |
| ref_model = self._fsdp2_load_full_state_dict(ref_model, full_state, self.dp_mesh, cpu_offload=True) | |
| logger.info(f"[Rank {dist.get_rank()}] Reference model created with FSDP2 CPUOffloadPolicy") | |
| return ref_model | |
| else: | |
| raise NotImplementedError(f"Loading from checkpoint file {ref_load_path} not yet implemented") | |
| def _get_model_inputs_args(self, packed_sequence: dict) -> dict: | |
| input_ids = packed_sequence["tokens"].unsqueeze(0) | |
| position_ids = packed_sequence["position_ids"].unsqueeze(0) | |
| if self.cp_size > 1: | |
| packed_sequence = pad_packed_sequence_with_cp(packed_sequence, self.cp_size) | |
| if not packed_sequence["cu_seqlens"].is_cuda: | |
| packed_sequence["cu_seqlens"] = packed_sequence["cu_seqlens"].cuda() | |
| cu_seqlens = packed_sequence["cu_seqlens"] | |
| update_ring_flash_attn_params(cu_seqlens, self.cp_group) | |
| input_ids = torch.chunk(packed_sequence["tokens"].unsqueeze(0), self.cp_size, dim=1)[self.cp_rank] | |
| position_ids = torch.chunk(packed_sequence["position_ids"].unsqueeze(0), self.cp_size, dim=1)[self.cp_rank] | |
| model_args = { | |
| "input_ids": input_ids, | |
| "position_ids": position_ids, | |
| "attention_mask": None, | |
| } | |
| if packed_sequence.get("multimodal_train_inputs"): | |
| model_args.update(packed_sequence["multimodal_train_inputs"]) | |
| return model_args | |
| def selective_log_softmax_raw(logits: torch.Tensor, input_ids: torch.Tensor) -> torch.Tensor: | |
| """Fused version of the common `log_softmax -> gather` operation. | |
| The fused version of this operation avoids the (potentially large) memory overhead | |
| of allocating a new tensor to store the full logprobs. | |
| Parameters: | |
| logits: Tensor of shape [..., V] containing model logits. | |
| input_ids: Tensor of shape [...] of token indices whose log-probabilities are gathered. | |
| Returns: | |
| Tensor of shape [...] containing the log-probabilities corresponding to `input_ids`. | |
| """ | |
| logprobs = logits.log_softmax(dim=-1) | |
| return torch.gather(logprobs, dim=-1, index=input_ids.unsqueeze(-1)).squeeze(-1) | |
| selective_log_softmax_compiled = torch.compile(dynamic=True)(selective_log_softmax_raw) | |
| def gather_log_probs_packed( | |
| shifted_logits: torch.Tensor, | |
| input_ids: torch.Tensor, | |
| allow_compile: bool, | |
| cu_seqlens: torch.Tensor | float | None = None, | |
| temperature: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| """Gather next-token log probabilities for packed sequences. | |
| Parameters: | |
| logits: Model logits of shape [B, T, V] or [T, V]. | |
| input_ids: Token ids of shape [B, T] or [T]. | |
| cu_seqlens: Optional cumulative sequence lengths (unused here). Present | |
| for API compatibility with callers. | |
| Returns: | |
| A tensor of shape [T-1] (or [B, T-1]) with log-probabilities of targets. | |
| """ | |
| # Handle batch dimension - logits should be [batch_size, seq_len, vocab_size] | |
| if shifted_logits.dim() == 3: | |
| # Remove batch dimension for packed sequences | |
| shifted_logits = shifted_logits.squeeze(0) | |
| input_ids = input_ids.squeeze(0) | |
| if temperature is not None: | |
| shifted_logits = shifted_logits.div(temperature) | |
| targets = input_ids[1:].to(device=shifted_logits.device) | |
| # Gather log probs for targets | |
| selective_log_softmax = selective_log_softmax_compiled if allow_compile else selective_log_softmax_raw | |
| return selective_log_softmax(shifted_logits, targets) | |
| def get_logprob_and_entropy_with_cp( | |
| logits: torch.Tensor, | |
| target_tokens: torch.Tensor, | |
| cp_rank: int, | |
| cp_size: int, | |
| cp_group, | |
| model_input_ids: torch.Tensor, | |
| allow_compile: bool, | |
| temperature: float | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Compute log probabilities and entropy in Context Parallel mode. | |
| Parameters: | |
| logits: Model output logits with shape [chunk_size, vocab_size] | |
| target_tokens: Target tokens with shape [total_seq_len] | |
| cp_rank: Current CP rank | |
| cp_size: CP world size | |
| cp_group: CP communication group | |
| model_input_ids: Model input_ids (used for the last rank) | |
| allow_compile: Whether to allow compilation | |
| temperature: Temperature parameter (optional) | |
| Returns: | |
| log_probs: Aggregated log probabilities with shape [total_seq_len - 1] | |
| entropy: Aggregated entropy with shape [total_seq_len - 1] | |
| """ | |
| # Fast path for non-CP mode (cp_size=1): avoid unnecessary communication | |
| if cp_size == 1: | |
| shifted_logits = logits[:-1, :] | |
| local_log_probs = gather_log_probs_packed( | |
| shifted_logits, target_tokens, allow_compile=allow_compile, temperature=temperature | |
| ) | |
| log_probs_full = torch.log_softmax(shifted_logits, dim=-1) | |
| probs = torch.softmax(shifted_logits, dim=-1) | |
| entropy = -(probs * log_probs_full).sum(dim=-1) | |
| return local_log_probs, entropy | |
| chunk_size = logits.shape[0] | |
| tokens_start_index = chunk_size * cp_rank | |
| tokens_end_index = ( | |
| tokens_start_index + chunk_size + 1 if cp_rank < cp_size - 1 else tokens_start_index + chunk_size | |
| ) | |
| # For the last rank, remove the last logit | |
| logits = logits if cp_rank < cp_size - 1 else logits[:-1, :] | |
| # Get local tokens for current rank | |
| local_tokens = ( | |
| target_tokens[tokens_start_index:tokens_end_index] if cp_rank < cp_size - 1 else model_input_ids.squeeze(0) | |
| ) | |
| # Compute local log probs | |
| local_log_probs = gather_log_probs_packed( | |
| logits, local_tokens, allow_compile=allow_compile, temperature=temperature | |
| ) | |
| # Pad for the last rank | |
| if cp_rank == cp_size - 1: | |
| local_log_probs = F.pad(local_log_probs, (0, chunk_size - local_log_probs.shape[0]), value=0) | |
| # Compute entropy | |
| shifted_logits = logits[:-1, :] if cp_rank == cp_size - 1 else logits | |
| log_probs_full = torch.log_softmax(shifted_logits, dim=-1) | |
| probs = torch.softmax(shifted_logits, dim=-1) | |
| entropy = -(probs * log_probs_full).sum(dim=-1) | |
| # Pad entropy for the last rank | |
| if cp_rank == cp_size - 1: | |
| entropy = F.pad(entropy, (0, chunk_size - entropy.shape[0]), value=0) | |
| # Merge with a single all_gather: stack as [2, chunk_size] | |
| stacked_local = torch.stack([local_log_probs, entropy], dim=0) | |
| gathered_stacked = torch.distributed.nn.functional.all_gather(stacked_local, group=cp_group) | |
| # Concatenate by effective length (non-last rank=chunk_size, last rank=chunk_size-1) | |
| lp_parts, ent_parts = [], [] | |
| for r in range(cp_size): | |
| eff_len = chunk_size if r < cp_size - 1 else max(0, chunk_size - 1) | |
| if eff_len > 0: | |
| lp_parts.append(gathered_stacked[r][0][:eff_len]) | |
| ent_parts.append(gathered_stacked[r][1][:eff_len]) | |
| log_probs = torch.cat(lp_parts, dim=0) if lp_parts else local_log_probs.new_zeros((0,)) | |
| entropy_result = torch.cat(ent_parts, dim=0) if ent_parts else entropy.new_zeros((0,)) | |
| # Truncate to global effective length T-1 (packed tokens length is T) | |
| log_probs = log_probs[: len(target_tokens) - 1] | |
| entropy_result = entropy_result[: len(target_tokens) - 1] | |
| return log_probs, entropy_result | |
| def sum_of_sample_mean(x: torch.Tensor, response_lengths: list[int], loss_masks: list[torch.Tensor]) -> torch.Tensor: | |
| """Compute sum of per-sample means across variable-length responses. | |
| Parameters: | |
| x: Flat tensor containing concatenated per-token values across samples. | |
| response_lengths: Lengths of each sample's response segment in `x`. | |
| loss_masks: Per-sample masks aligned with `response_lengths`. | |
| Returns: | |
| A scalar tensor equal to the sum over samples of the mean value within | |
| each sample's response segment. | |
| """ | |
| return sum( | |
| [ | |
| (x_i * loss_mask_i).sum() / torch.clamp_min(loss_mask_i.sum(), 1) | |
| for x_i, loss_mask_i in zip(x.split(response_lengths, dim=0), loss_masks, strict=False) | |
| ] | |
| ) | |
| def move_torch_optimizer(optimizer, device): | |
| """ref: https://github.com/volcengine/verl/blob/main/verl/utils/fsdp_utils.py""" | |
| if not optimizer.state: | |
| return | |
| for param_group in optimizer.param_groups: | |
| for param in param_group["params"]: | |
| state = optimizer.state[param] | |
| for key, value in state.items(): | |
| if isinstance(value, torch.Tensor): | |
| state[key] = value.to(device, non_blocking=True) | |
| torch.cuda.synchronize() | |
| def apply_fsdp2(model, mesh=None, cpu_offload=False, args=None): | |
| """Apply FSDP v2 to the model. | |
| Args: | |
| model: The model to wrap with FSDP | |
| mesh: Optional DeviceMesh for FSDP. If None, uses all ranks. | |
| cpu_offload: If True, offload parameters, gradients, and optimizer states | |
| to CPU. The optimizer step will run on CPU. (Default: False) | |
| args: Arguments containing precision settings (fp16/bf16) | |
| Ref: https://github.com/volcengine/verl/blob/main/verl/utils/fsdp_utils.py | |
| """ | |
| from torch.distributed.fsdp import CPUOffloadPolicy, MixedPrecisionPolicy, fully_shard | |
| offload_policy = CPUOffloadPolicy() if cpu_offload else None | |
| layer_cls_to_wrap = model._no_split_modules | |
| assert len(layer_cls_to_wrap) > 0 and layer_cls_to_wrap[0] is not None | |
| modules = [ | |
| module | |
| for name, module in model.named_modules() | |
| if module.__class__.__name__ in layer_cls_to_wrap | |
| or (isinstance(module, torch.nn.Embedding) and not model.config.tie_word_embeddings) | |
| ] | |
| # Determine precision policy based on args | |
| param_dtype = torch.bfloat16 # Default to bf16 as before | |
| reduce_dtype = torch.float32 | |
| if args.fp16: | |
| param_dtype = torch.float16 | |
| logger.info(f"FSDP MixedPrecision Policy: param_dtype={param_dtype}, reduce_dtype={reduce_dtype}") | |
| fsdp_kwargs = { | |
| "mp_policy": MixedPrecisionPolicy( | |
| param_dtype=param_dtype, | |
| reduce_dtype=reduce_dtype, | |
| ), | |
| "offload_policy": offload_policy, | |
| "mesh": mesh, | |
| } | |
| # Apply FSDP to each module (offload_policy=None is equivalent to not passing it) | |
| for module in modules: | |
| fully_shard(module, **fsdp_kwargs) | |
| # Apply FSDP to the top-level model | |
| fully_shard(model, **fsdp_kwargs) | |
| return model | |
| def sum_of_token(x: torch.Tensor, response_lengths: list[int], loss_masks: list[torch.Tensor]) -> torch.Tensor: | |
| return sum( | |
| [ | |
| (x_i * loss_mask_i).sum() | |
| for x_i, loss_mask_i in zip(x.split(response_lengths, dim=0), loss_masks, strict=False) | |
| ] | |
| ) | |