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 | |
| from argparse import Namespace | |
| from collections.abc import Sequence | |
| import numpy as np | |
| import torch | |
| import torch.distributed as dist | |
| import torch.nn.functional as F | |
| from megatron.core import mpu | |
| from megatron.core.packed_seq_params import PackedSeqParams | |
| from slime.utils import train_metric_utils | |
| from slime.utils.data import get_minimum_num_micro_batch_size | |
| from slime.utils.flops_utils import calculate_fwd_flops | |
| from slime.utils.metric_utils import compute_pass_rate, compute_rollout_step | |
| from slime.utils.seqlen_balancing import get_seqlen_balanced_partitions | |
| from slime.utils.types import RolloutBatch | |
| from ...utils import tracking_utils | |
| from .cp_utils import get_sum_of_sample_mean, slice_with_cp | |
| logger = logging.getLogger(__name__) | |
| def get_batch( | |
| data_iterator: "DataIterator", | |
| keys: Sequence[str], | |
| pad_multiplier: int = 128, | |
| ) -> dict[str, torch.Tensor | PackedSeqParams | list[torch.Tensor] | None]: | |
| """ | |
| Generate a CP-ready micro-batch with packed sequence parameters. | |
| Steps: | |
| - Fetch raw fields via iterator. | |
| - Save original token tensors under "unconcat_tokens". | |
| - Slice tokens into two chunks for Context Parallelism (CP), concatenate, and pad to a configurable multiple. | |
| - Build cu_seqlens and `PackedSeqParams` with T-H-D layout (T: sequence length, H: attention heads, D: head dimension). | |
| Args: | |
| data_iterator: Iterator providing micro-batch data. | |
| keys: List of keys to fetch from the iterator. | |
| pad_multiplier: Multiplier for padding size calculation (default: 128). | |
| Returns a dict including: | |
| - "tokens": torch.LongTensor of shape [1, T_padded] on the current CUDA device | |
| - "unconcat_tokens": list[torch.LongTensor] for the micro-batch before CP slicing/concat | |
| - "packed_seq_params": PackedSeqParams with T-H-D settings (cu_seqlens on CUDA, dtype=int) | |
| Plus any other requested keys forwarded from the iterator. | |
| """ | |
| assert "tokens" in keys | |
| batch = data_iterator.get_next(keys) | |
| tokens = batch["tokens"] | |
| # use 0 as the pad token id should be fine? | |
| pad_token_id = 0 | |
| # for cp, we need all tokens to calculate logprob | |
| batch["unconcat_tokens"] = tokens | |
| cp_size = mpu.get_context_parallel_world_size() | |
| tokens = [slice_with_cp(t, pad_token_id) for t in tokens] | |
| cu_seqlens = [0] | |
| for t in tokens: | |
| cu_seqlens.append(cu_seqlens[-1] + t.size(0)) | |
| tokens = torch.cat(tokens) | |
| # Always pad to reduce memory fragmentation and maybe make the computation faster | |
| pad_size = mpu.get_tensor_model_parallel_world_size() * pad_multiplier | |
| pad = (pad_size - tokens.size(0) % pad_size) % pad_size | |
| if pad != 0: | |
| tokens = F.pad(tokens, (0, pad), value=pad_token_id) | |
| cu_seqlens.append(cu_seqlens[-1] + pad) | |
| # thd requires the cu_seqlens to be of the origin length | |
| cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int).cuda() * cp_size | |
| max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max().item() | |
| packed_seq_params = PackedSeqParams( | |
| cu_seqlens_q=cu_seqlens, | |
| cu_seqlens_kv=cu_seqlens, | |
| max_seqlen_q=max_seqlen, | |
| max_seqlen_kv=max_seqlen, | |
| qkv_format="thd", | |
| ) | |
| tokens = tokens.unsqueeze(0) | |
| batch["tokens"] = tokens | |
| batch["packed_seq_params"] = packed_seq_params | |
| # loss masks | |
| loss_masks = [] | |
| for loss_mask, total_length, response_length in zip( | |
| batch["loss_masks"], | |
| batch["total_lengths"], | |
| batch["response_lengths"], | |
| strict=True, | |
| ): | |
| prompt_length = total_length - response_length | |
| loss_mask = F.pad(loss_mask, (prompt_length - 1, 1), value=0) | |
| loss_mask = slice_with_cp(loss_mask, 0) | |
| loss_masks.append(loss_mask) | |
| loss_masks = torch.cat(loss_masks) | |
| loss_masks = F.pad(loss_masks, (0, pad), value=0).unsqueeze(0) | |
| assert loss_masks.shape == tokens.shape, f"loss_masks.shape: {loss_masks.shape}, tokens.shape: {tokens.shape}" | |
| batch["full_loss_masks"] = loss_masks | |
| # Process multimodal training tensors if present | |
| multimodal_train_inputs = batch.get("multimodal_train_inputs", None) | |
| if multimodal_train_inputs is not None: | |
| multimodal_data = {} # key -> concatenated tensor | |
| multimodal_num_items = {} # key -> list of item counts per sequence | |
| for mm_input_dict in multimodal_train_inputs: | |
| if mm_input_dict is not None: | |
| for key, mm_tensor in mm_input_dict.items(): | |
| if key not in multimodal_data: | |
| multimodal_data[key] = mm_tensor | |
| multimodal_num_items[key] = [mm_tensor.size(0)] | |
| else: | |
| multimodal_data[key] = torch.cat([multimodal_data[key], mm_tensor], dim=0) | |
| multimodal_num_items[key].append(mm_tensor.size(0)) | |
| batch["multimodal_train_inputs"] = multimodal_data | |
| batch["multimodal_num_items"] = multimodal_num_items | |
| return batch | |
| def gather_log_data( | |
| metric_name: str, | |
| args: Namespace, | |
| rollout_id: int, | |
| log_dict: dict[str, float], | |
| ) -> dict[str, float] | None: | |
| """ | |
| Gather per-rank metrics, reduce by mean on the DP source rank, and log. | |
| Expects `log_dict` to contain plain scalars. The DP source rank prints and | |
| optionally logs to WandB/TensorBoard with a step derived from `rollout_id` and | |
| batch sizes. Returns the reduced dict on the DP source rank; returns None on others. | |
| """ | |
| if mpu.get_data_parallel_rank(with_context_parallel=True) == 0: | |
| dp_size = mpu.get_data_parallel_world_size(with_context_parallel=True) | |
| gathered_log_dict = [None] * dp_size | |
| # Not sure if this will be a performance bottleneck. | |
| dist.gather_object( | |
| log_dict, | |
| gathered_log_dict, | |
| dst=mpu.get_data_parallel_src_rank(with_context_parallel=True), | |
| group=mpu.get_data_parallel_group_gloo(with_context_parallel=True), | |
| ) | |
| reduced_log_dict = { | |
| f"{metric_name}/{key}": sum([d[key] for d in gathered_log_dict]) / dp_size for key in log_dict | |
| } | |
| logger.info(f"{metric_name} {rollout_id}: {reduced_log_dict}") | |
| # Calculate step once to avoid duplication | |
| step = compute_rollout_step(args, rollout_id) | |
| reduced_log_dict["rollout/step"] = step | |
| tracking_utils.log(args, reduced_log_dict, step_key="rollout/step") | |
| return reduced_log_dict | |
| else: | |
| dist.gather_object( | |
| log_dict, | |
| None, | |
| dst=mpu.get_data_parallel_src_rank(with_context_parallel=True), | |
| group=mpu.get_data_parallel_group_gloo(with_context_parallel=True), | |
| ) | |
| return None | |
| class DataIterator: | |
| """Micro-batch iterator over rollout dicts. | |
| Supports either fixed contiguous micro-batches or an explicit per-step | |
| index schedule (for dynamic batch sizing / sequence-length balancing). | |
| """ | |
| def __init__( | |
| self, | |
| rollout_data: RolloutBatch, | |
| micro_batch_size: int | None = None, | |
| micro_batch_indices: list[list[int]] | None = None, | |
| ) -> None: | |
| """Initialize an iterator over `rollout_data`. | |
| Args: | |
| rollout_data: Dict of per-sample fields for the local step. | |
| micro_batch_size: Fixed contiguous slice size when not using dynamic scheduling. | |
| micro_batch_indices: Explicit indices per micro-batch when using dynamic balancing. | |
| Must be mutually exclusive with `micro_batch_size`. | |
| """ | |
| self.rollout_data = rollout_data | |
| self.micro_batch_size = micro_batch_size | |
| self.micro_batch_indices = micro_batch_indices | |
| assert micro_batch_size is None or micro_batch_indices is None | |
| self.offset = 0 | |
| # Keys that are batch-level (not per-sample) and should be passed through as-is | |
| BATCH_LEVEL_KEYS = set() | |
| def get_next(self, keys: Sequence[str]) -> dict[str, list[object] | None]: | |
| """Return the next micro-batch for the requested keys. | |
| - If `micro_batch_indices` is provided, selects rows according to the current | |
| index list for each requested key. | |
| - Otherwise, slices a contiguous window of size `micro_batch_size` starting | |
| at the current offset. | |
| Returns a dict mapping each key to a list subset (or None if absent). | |
| """ | |
| batch = {} | |
| for key in keys: | |
| vals = self.rollout_data.get(key, None) | |
| if vals is None: | |
| batch[key] = None | |
| elif key in self.BATCH_LEVEL_KEYS: | |
| # Batch-level keys are not per-sample, pass through as-is | |
| batch[key] = vals | |
| else: | |
| if self.micro_batch_indices is not None: | |
| indices = self.micro_batch_indices[self.offset] | |
| batch[key] = [vals[i] for i in indices] | |
| else: | |
| assert self.offset + self.micro_batch_size <= len( | |
| vals | |
| ), f"offset: {self.offset}, micro_batch_size: {self.micro_batch_size}, len(vals): {len(vals)}" | |
| batch[key] = vals[self.offset : self.offset + self.micro_batch_size] | |
| if self.micro_batch_indices is not None: | |
| self.offset += 1 | |
| else: | |
| self.offset += self.micro_batch_size | |
| return batch | |
| def reset(self) -> "DataIterator": | |
| """Reset internal offset to the start and return self.""" | |
| self.offset = 0 | |
| return self | |
| def get_data_iterator( | |
| args: Namespace, | |
| model: torch.nn.Module | Sequence[torch.nn.Module], | |
| rollout_data: RolloutBatch, | |
| ) -> tuple[list[DataIterator], list[int]]: | |
| """ | |
| Create iterators and a micro-batch schedule for a rollout step. | |
| - If `use_dynamic_batch_size` is False, splits into fixed-size contiguous | |
| micro-batches of `micro_batch_size`. | |
| - If True, computes the number of micro-batches per local step based on | |
| `max_tokens_per_gpu` and per-sample lengths, all-reduces to a DP-wide | |
| maximum, optionally enforces divisibility for Virtual Pipeline Parallelism (VPP), and builds a balanced | |
| index schedule to equalize token counts across micro-batches. | |
| Returns `(data_iterators, num_microbatches)` where: | |
| - `data_iterators`: list of `DataIterator`, one per VPP stage (size 1 if VPP disabled) | |
| - `num_microbatches`: list[int], one per local step in the rollout (length = steps) | |
| """ | |
| dp_size = mpu.get_data_parallel_world_size(with_context_parallel=False) | |
| dp_group = mpu.get_data_parallel_group() | |
| vpp_size = mpu.get_virtual_pipeline_model_parallel_world_size() | |
| if vpp_size is None: | |
| vpp_size = 1 | |
| if vpp_size > 1: | |
| from megatron.core.utils import get_model_config | |
| config = get_model_config(model[0]) | |
| microbatch_group_size_per_vp_stage = config.microbatch_group_size_per_vp_stage | |
| cp_size = mpu.get_context_parallel_world_size() | |
| num_local_samples = len(rollout_data["total_lengths"]) | |
| num_local_gbs = args.global_batch_size // dp_size | |
| num_steps_per_rollout = num_local_samples // num_local_gbs | |
| def _generate_data_iterator(rollout_data, micro_batch_size, micro_batch_indices=None): | |
| data_iterator = [] | |
| for _ in range(vpp_size): | |
| data_iterator.append(DataIterator(rollout_data, micro_batch_size, micro_batch_indices)) | |
| return data_iterator | |
| if not args.use_dynamic_batch_size: | |
| num_microbatches = [num_local_gbs // args.micro_batch_size for _ in range(num_steps_per_rollout)] | |
| data_iterator = _generate_data_iterator(rollout_data, args.micro_batch_size) | |
| else: | |
| assert args.max_tokens_per_gpu is not None | |
| # calculate the number of mirobatches for each step | |
| samples = rollout_data["total_lengths"] | |
| assert len(samples) == num_local_samples | |
| num_microbatches = [] | |
| for i in range(num_steps_per_rollout): | |
| start, end = i * num_local_gbs, (i + 1) * num_local_gbs | |
| num_microbatches.append( | |
| get_minimum_num_micro_batch_size(samples[start:end], args.max_tokens_per_gpu * cp_size) | |
| ) | |
| num_microbatches = torch.tensor(num_microbatches, dtype=torch.int, device=torch.cuda.current_device()) | |
| dist.all_reduce(num_microbatches, op=dist.ReduceOp.MAX, group=dp_group) | |
| if vpp_size > 1: | |
| # vpp requies the number of microbatches to be divisible by vpp_size | |
| num_microbatches = torch.clamp( | |
| num_microbatches // microbatch_group_size_per_vp_stage * microbatch_group_size_per_vp_stage, | |
| min=1, | |
| ) | |
| num_microbatches = num_microbatches.tolist() | |
| # balance the each micro batch | |
| samples = rollout_data["total_lengths"] | |
| # balance the number of mirobatches across steps | |
| micro_batch_indices = [] | |
| for i, num_mbs in enumerate(num_microbatches): | |
| start, end = i * num_local_gbs, (i + 1) * num_local_gbs | |
| samples = rollout_data["total_lengths"][start:end] | |
| partitions = get_seqlen_balanced_partitions(samples, num_mbs, equal_size=False) | |
| for j in range(num_mbs): | |
| for k in range(len(partitions[j])): | |
| partitions[j][k] += start | |
| micro_batch_indices.extend(partitions) | |
| assert len(set(sum(micro_batch_indices, []))) == num_local_samples | |
| data_iterator = _generate_data_iterator(rollout_data, None, micro_batch_indices) | |
| return ( | |
| data_iterator, | |
| num_microbatches, | |
| ) | |
| def log_rollout_data(rollout_id: int, args: Namespace, rollout_data: RolloutBatch) -> None: | |
| """ | |
| Summarize rollout fields and log reduced metrics on PP last stage, TP rank 0. | |
| - Tensor-valued lists are concatenated and averaged. For token-level metrics | |
| like log-probs/returns/advantages/values, computes a CP-correct sample mean | |
| using `loss_masks` and total/response lengths. | |
| - Non-tensor lists are averaged elementwise. | |
| - Scalars are converted to Python numbers. | |
| """ | |
| if mpu.get_tensor_model_parallel_rank() == 0 and mpu.is_pipeline_last_stage(): | |
| cp_size = mpu.get_context_parallel_world_size() | |
| log_dict = {} | |
| response_lengths = rollout_data["response_lengths"] | |
| loss_masks = rollout_data["loss_masks"] | |
| total_lengths = rollout_data["total_lengths"] | |
| for key, val in rollout_data.items(): | |
| if key in [ | |
| "tokens", | |
| "multimodal_train_inputs", | |
| "loss_masks", | |
| "sample_indices", | |
| "rollout_routed_experts", | |
| ]: | |
| continue | |
| # Skip None values | |
| if val is None: | |
| continue | |
| # Upload per sample mean for each rollout value | |
| # There are the following assumptions: | |
| # - Each dp rank has the same number of samples | |
| if isinstance(val, (list, tuple)): | |
| # Filter out None entries before processing. | |
| val = [v for v in val if v is not None] | |
| if not val: | |
| continue | |
| if all(isinstance(v, torch.Tensor) for v in val): | |
| # NOTE: Here we have to do the clone().detach(), otherwise the tensor will be | |
| # modified in place and will cause problem for the next rollout. | |
| val = torch.cat(val).clone().detach() | |
| if key in ["log_probs", "ref_log_probs", "rollout_log_probs", "returns", "advantages", "values"]: | |
| sum_of_sample_mean = get_sum_of_sample_mean(total_lengths, response_lengths, loss_masks) | |
| val = cp_size * sum_of_sample_mean(val) / len(loss_masks) | |
| else: | |
| val = val.mean() * cp_size | |
| else: | |
| # Mixed Tensor/scalar list. | |
| # Convert everything to float scalar for logging. | |
| val = sum(float(v.mean()) if isinstance(v, torch.Tensor) else float(v) for v in val) / len(val) | |
| elif isinstance(val, torch.Tensor): | |
| val = val.float().mean() | |
| else: | |
| raise ValueError(f"Unsupported type: {type(val)} for key: {key}") | |
| log_dict[key] = val.item() if isinstance(val, torch.Tensor) else val | |
| reduced_log_dict = gather_log_data("rollout", args, rollout_id, log_dict) | |
| if args.ci_test and reduced_log_dict is not None: | |
| if ( | |
| rollout_id == 0 | |
| and "rollout/log_probs" in reduced_log_dict | |
| and "rollout/ref_log_probs" in reduced_log_dict | |
| ): | |
| assert reduced_log_dict["rollout/log_probs"] == reduced_log_dict["rollout/ref_log_probs"] | |
| if "rollout/log_probs" in reduced_log_dict: | |
| assert -0.5 < reduced_log_dict["rollout/log_probs"] < 0 | |
| if "rollout/entropy" in reduced_log_dict: | |
| assert 0 < reduced_log_dict["rollout/entropy"] < 0.5 | |
| if args.log_multi_turn: | |
| log_multi_turn_data(rollout_id, args, rollout_data) | |
| if args.log_passrate: | |
| log_passrate(rollout_id, args, rollout_data) | |
| if args.log_correct_samples: | |
| if mpu.get_tensor_model_parallel_rank() == 0 and mpu.is_pipeline_last_stage(): | |
| cp_size = mpu.get_context_parallel_world_size() | |
| log_dict = {} | |
| response_lengths = rollout_data["response_lengths"] | |
| loss_masks = rollout_data["loss_masks"] | |
| total_lengths = rollout_data["total_lengths"] | |
| def quantile(total_value, n_quantiles, data) -> dict: | |
| import math | |
| assert n_quantiles > 1, f"n_quantiles({n_quantiles}) must be greater than 1." | |
| quantiles = [((i + 1) / n_quantiles) for i in range(n_quantiles)] | |
| cut_points = [total_value * q for q in quantiles] | |
| cut_points[-1] = total_value | |
| count = [0] * n_quantiles | |
| for d in data: | |
| for i, point in enumerate(cut_points): | |
| if d <= point: | |
| count[i] += 1 | |
| break | |
| total = sum(count) + 1e-9 | |
| percentile = [c / total for c in count] | |
| percentile = {f"p{min(math.ceil(q*100),100)}": p for q, p in zip(quantiles, percentile, strict=True)} | |
| return percentile | |
| raw_rewards = rollout_data["raw_reward"] | |
| # Additional metrics for correct cases are calculated separately below. | |
| correct_response_lengths = [] | |
| correct_total_lengths = [] | |
| correct_loss_masks = [] | |
| correct_entropy = [] | |
| for i, raw_reward in enumerate(raw_rewards): | |
| if raw_reward == 1: | |
| correct_response_lengths.append(response_lengths[i]) | |
| correct_total_lengths.append(total_lengths[i]) | |
| correct_loss_masks.append(loss_masks[i]) | |
| correct_entropy.append(-rollout_data["log_probs"][i]) | |
| num_correct_responses = len(correct_total_lengths) | |
| rollout_data["correct_response_lengths"] = correct_response_lengths | |
| correct_response_length_percentile = quantile( | |
| args.rollout_max_response_len, 4, rollout_data["correct_response_lengths"] | |
| ) | |
| for p, val in correct_response_length_percentile.items(): | |
| rollout_data[f"correct_length/{p}"] = [val] * num_correct_responses | |
| if len(correct_entropy) > 0: | |
| sum_of_sample_mean = get_sum_of_sample_mean( | |
| correct_total_lengths, correct_response_lengths, correct_loss_masks | |
| ) | |
| correct_entropy = sum_of_sample_mean(torch.cat(correct_entropy, dim=0)) | |
| rollout_data["correct_entropy"] = [correct_entropy.item()] * num_correct_responses | |
| else: | |
| rollout_data["correct_entropy"] = [0] * num_correct_responses | |
| def log_multi_turn_data(rollout_id: int, args: Namespace, rollout_data: RolloutBatch) -> None: | |
| """ | |
| Log multi-turn auxiliary metrics such as raw/observed response lengths and rounds. | |
| Operates only on PP last stage and TP rank 0. Uses GPU tensors when available | |
| to compute statistics without host transfers. | |
| """ | |
| if mpu.get_tensor_model_parallel_rank() == 0 and mpu.is_pipeline_last_stage(): | |
| log_dict = {} | |
| for key, val in rollout_data.items(): | |
| if key == "loss_masks": | |
| if val: # Check if val is not empty | |
| device = val[0].device # Get device from first tensor | |
| # Vectorized length calculation using torch | |
| raw_response_lengths = torch.tensor([v.shape[0] for v in val], dtype=torch.float32, device=device) | |
| log_dict["raw_response_length/response_length_mean"] = raw_response_lengths.mean().item() | |
| log_dict["raw_response_length/response_length_max"] = raw_response_lengths.max().item() | |
| log_dict["raw_response_length/response_length_min"] = raw_response_lengths.min().item() | |
| log_dict["raw_response_length/response_length_clip_ratio"] = ( | |
| (raw_response_lengths >= args.rollout_max_response_len).float().mean().item() | |
| ) | |
| # Vectorized sum calculation using torch - stay on GPU | |
| wo_obs_response_lengths = torch.tensor( | |
| [v.sum().item() for v in val], dtype=torch.float32, device=device | |
| ) | |
| log_dict["wo_obs_response_length/response_length_mean"] = wo_obs_response_lengths.mean().item() | |
| log_dict["wo_obs_response_length/response_length_max"] = wo_obs_response_lengths.max().item() | |
| log_dict["wo_obs_response_length/response_length_min"] = wo_obs_response_lengths.min().item() | |
| if key == "round_number": | |
| # Use numpy for vectorized round number statistics | |
| round_number_array = np.array(val) | |
| log_dict["multi_turn_metric/round_number_mean"] = np.mean(round_number_array) | |
| log_dict["multi_turn_metric/round_number_max"] = np.max(round_number_array) | |
| log_dict["multi_turn_metric/round_number_min"] = np.min(round_number_array) | |
| gather_log_data("multi_turn", args, rollout_id, log_dict) | |
| def log_passrate(rollout_id: int, args: Namespace, rollout_data: RolloutBatch) -> None: | |
| """ | |
| Compute pass@k metrics from `raw_reward` groups and log the results. | |
| `raw_reward` is reshaped to `[group_number, group_size]`, then pass@k is | |
| estimated per problem and averaged. | |
| """ | |
| if mpu.get_tensor_model_parallel_rank() == 0 and mpu.is_pipeline_last_stage(): | |
| log_dict = {} | |
| for key, val in rollout_data.items(): | |
| if key != "raw_reward": | |
| continue | |
| log_dict |= compute_pass_rate( | |
| flat_rewards=val, | |
| group_size=args.n_samples_per_prompt, | |
| num_groups=args.rollout_batch_size, | |
| ) | |
| gather_log_data("passrate", args, rollout_id, log_dict) | |
| def log_perf_data(rollout_id: int, args: Namespace) -> None: | |
| train_metric_utils.log_perf_data_raw( | |
| rollout_id=rollout_id, | |
| args=args, | |
| is_primary_rank=( | |
| mpu.get_tensor_model_parallel_rank() == 0 | |
| and mpu.is_pipeline_last_stage() | |
| and mpu.get_data_parallel_rank(with_context_parallel=True) == 0 | |
| ), | |
| compute_total_fwd_flops=lambda seq_lens: calculate_fwd_flops(seqlens=seq_lens, args=args) | |
| / dist.get_world_size() | |
| / 1e12, | |
| ) | |
| def sync_actor_critic_data( | |
| args: Namespace, | |
| rollout_data: RolloutBatch | None = None, | |
| group: dist.ProcessGroup | None = None, | |
| ) -> None: | |
| """ | |
| Broadcast `values` (from critic) and optionally `log_probs`/`ref_log_probs` | |
| (from actor) across PP ranks to align data dependencies. | |
| - Values are broadcast from src=1. | |
| - Log-probs and ref-log-probs are broadcast from src=0 when KL is used. | |
| Updates `rollout_data` in place with the synchronized tensors. | |
| """ | |
| log_probs_key = "log_probs" if not args.use_rollout_logprobs else "rollout_log_probs" | |
| values, log_probs, ref_log_probs = map(rollout_data.get, ("values", log_probs_key, "ref_log_probs")) | |
| # return when not the pp last stage | |
| if not values and not log_probs: | |
| return | |
| handles = [] | |
| if not values: | |
| values = [torch.empty_like(log_prob) for log_prob in log_probs] | |
| for value in values: | |
| handles.append(dist.broadcast(value, src=1, group=group, async_op=True)) | |
| if args.kl_coef != 0 or args.use_kl_loss: | |
| if not log_probs: | |
| log_probs = [torch.empty_like(value) for value in values] | |
| if not ref_log_probs: | |
| ref_log_probs = [torch.empty_like(value) for value in values] | |
| for ref_log_prob, log_prob in zip(ref_log_probs, log_probs, strict=False): | |
| handles.append(dist.broadcast(log_prob, src=0, group=group, async_op=True)) | |
| handles.append(dist.broadcast(ref_log_prob, src=0, group=group, async_op=True)) | |
| for handle in handles: | |
| handle.wait() | |
| rollout_data.update( | |
| { | |
| k: v | |
| for k, v in { | |
| "values": values, | |
| log_probs_key: log_probs, | |
| "ref_log_probs": ref_log_probs, | |
| }.items() | |
| if v is not None | |
| } | |
| ) | |