Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
fd7267f
drgrpo
Oct 14, 2025
cb6f7a9
get vllm logps
Oct 14, 2025
d6acc63
Update _wandb.py
jacklanchantin Oct 14, 2025
7b72df9
remove beta check
Oct 14, 2025
7fc3b2f
Merge branch 'jacklanchantin/drgrpo' of github.com:facebookresearch/f…
Oct 14, 2025
502fa69
format
Oct 14, 2025
79382d3
revert
Oct 14, 2025
97e8dca
add importance sampling correction
Oct 16, 2025
54c9d98
dont run ref model forward if beta==0
Oct 20, 2025
acb0840
add tis ratio clamp = 2
Oct 20, 2025
50d21dd
clean up
Oct 20, 2025
ccfa63b
configs
Oct 21, 2025
bb49312
clean up
Oct 22, 2025
bd4b073
default
Oct 22, 2025
6919a4c
var name
Oct 22, 2025
d910891
var name
Oct 22, 2025
b762625
only use tis_imp_ratio_cap
Oct 22, 2025
5dff68a
batched inputs
Oct 23, 2025
cce97ce
use tis_drgrpo files
Oct 23, 2025
178fb69
size
Oct 24, 2025
536ce2b
match tis_grpo
Oct 24, 2025
a036e92
fix batching/microbatching bugs
Oct 24, 2025
ca043a5
black/isort
Oct 24, 2025
55dc39a
Merge branch 'online_training' of github.com:facebookresearch/fairseq…
Oct 29, 2025
bdf6e4b
revert qwen card
Oct 29, 2025
cdbec3c
bypass reference_model if None
Oct 29, 2025
2645498
add SelfAugmentingExtractor for llm judge
Oct 31, 2025
7011bf9
sa judge
Nov 1, 2025
bfede6f
new metrics
Nov 3, 2025
9539d29
.
Nov 5, 2025
1f8bc99
ppl
Nov 6, 2025
b59f10b
clip outputs
jacklanchantin Nov 12, 2025
d500503
.
jacklanchantin Nov 13, 2025
b152d8b
fix rank=0 bug
jacklanchantin Nov 13, 2025
634a039
grpo
jacklanchantin Nov 13, 2025
071ff0f
remove ppl
jacklanchantin Nov 13, 2025
cd5ee91
remove ppl
jacklanchantin Nov 13, 2025
5cc0610
tokenizer
jacklanchantin Nov 13, 2025
7f7b50d
set VLLM_ALLOW_INSECURE_SERIALIZATION=1 for newer vllm versions and a…
lydiadli Oct 29, 2025
14d2571
comment out unused
jacklanchantin Nov 13, 2025
fcb24b2
logging
jacklanchantin Nov 14, 2025
8d40e31
logging
jacklanchantin Nov 14, 2025
424338a
logging
jacklanchantin Nov 16, 2025
45cd0aa
fix src_key_text
jacklanchantin Nov 17, 2025
214be19
logging
jacklanchantin Nov 18, 2025
fe061c3
maybe sync fix
jacklanchantin Nov 18, 2025
7bbd503
do not throttle client-server port
uralik Nov 19, 2025
58b1df7
Merge branch 'kulikov/init_fix' of github.com:facebookresearch/fairse…
jacklanchantin Nov 19, 2025
1641e5c
maybe_sync_fix
jacklanchantin Nov 19, 2025
9068b24
move tokenizer
jacklanchantin Nov 19, 2025
a39ea52
ppl/logp reward (#1415)
lydiadli Nov 19, 2025
399464d
move tokenizer
jacklanchantin Nov 19, 2025
ba27796
Merge branch 'jacklanchantin/drgrpo' of github.com:facebookresearch/f…
jacklanchantin Nov 19, 2025
a03edf6
comment
jacklanchantin Nov 20, 2025
c57b70d
divide by 0 protection in ppl loss
jacklanchantin Nov 21, 2025
5adcb1d
stability
jacklanchantin Nov 21, 2025
ba349a6
clip if no think
jacklanchantin Nov 21, 2025
c5f6cf6
print prompt text
jacklanchantin Nov 21, 2025
d14b610
fixes
jacklanchantin Nov 21, 2025
4d61714
entropy stabilization
jacklanchantin Nov 21, 2025
fdef776
clipping
jacklanchantin Nov 22, 2025
6c6048c
clean
jacklanchantin Nov 24, 2025
a6013ce
start nll loss
jacklanchantin Nov 24, 2025
ad3a5c0
wandb
jacklanchantin Dec 3, 2025
551d4f5
fix synchronization bug - no avg_think_rollout_length metrics for ran…
lydiadli Feb 2, 2026
74f0be8
fix wrong importance sampling ratio logging. (#1489)
lydiadli Feb 3, 2026
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
8 changes: 5 additions & 3 deletions src/fairseq2/assets/cards/models/llama.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,17 @@ num_shards: 8

name: llama3
model_family: llama
checkpoint: "https://ai.meta.com/llama/;gated=true"
tokenizer: "https://ai.meta.com/llama/;gated=true"
checkpoint: "/datasets/pretrained-llms/Llama-3.1-8B/"
tokenizer: "/datasets/pretrained-llms/Llama-3.1-8B/"
tokenizer_family: llama
use_v2_tokenizer: true

---

name: llama3_instruct
base: llama3
checkpoint: "/datasets/pretrained-llms/Llama-3.1-8B-Instruct/"
tokenizer: "/datasets/pretrained-llms/Llama-3.1-8B-Instruct/"
use_eot: true # instruct tokenizer to use EOT instead of EOS

---
Expand Down Expand Up @@ -168,4 +170,4 @@ model_arch: llama3_1_8b
checkpoint: "hg://deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
tokenizer: "hg://deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
tokenizer_family: llama
use_v2_tokenizer: true
use_v2_tokenizer: true
4 changes: 2 additions & 2 deletions src/fairseq2/assets/cards/models/qwen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ use_im_end: true
name: qwen3_8b_base
model_family: qwen
model_arch: qwen3_8b
checkpoint: "hg://qwen/qwen3-8b-base"
tokenizer: "hg://qwen/qwen3-8b-base"
checkpoint: "/checkpoint/data/jacklanchantin/pretrained-llms/Qwen3-8B-Base/"
tokenizer: "/checkpoint/data/jacklanchantin/pretrained-llms/Qwen3-8B-Base/"
tokenizer_family: qwen

---
Expand Down
5 changes: 5 additions & 0 deletions src/fairseq2/datasets/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,13 @@ def create_reader(
def copy_prompt_text(example: dict[str, Any]) -> dict[str, Any]:
example[f"{src_key}_text"] = example[f"{src_key}"]
return example

def copy_prompt_raw_text(example: dict[str, Any]) -> dict[str, Any]:
example[f"raw_prompt_text"] = example[f"{src_key}"]
return example

builder.map(copy_prompt_text, num_parallel_calls=npc)
builder.map(copy_prompt_raw_text, num_parallel_calls=npc)

# Encode source and target texts.
source_encoder = tokenizer.create_encoder(mode=options.source_encode_mode)
Expand Down
4 changes: 4 additions & 0 deletions src/fairseq2/metrics/recorders/_wandb.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ def create(
run_id = self._get_run_id(output_dir, config)

try:
# Increase timeout for fairwandb.org which can be slow
settings = wandb.Settings(init_timeout=1200) # 20 minutes

run = wandb.init(
entity=config.entity,
project=config.project,
Expand All @@ -160,6 +163,7 @@ def create(
group=config.group,
job_type=config.job_type,
resume=config.resume_mode,
settings=settings,
)
except (RuntimeError, ValueError) as ex:
raise MetricRecordError(
Expand Down
2 changes: 1 addition & 1 deletion src/fairseq2/recipes/_trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def __init__(
device_stat_tracker: DeviceStatTracker,
wall_watch: Stopwatch,
progress_reporter: ProgressReporter,
fp16_loss_scale: tuple[float, float] = (128.0, 0.0001),
fp16_loss_scale: tuple[float, float] = (16, 0.0001),
no_sync_grad_accumulation: bool = False,
max_grad_norm: float | None = None,
grad_check: bool = False,
Expand Down
2 changes: 1 addition & 1 deletion src/fairseq2/recipes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class TrainerSection:
max_grad_norm: float | None = None
"""The maximum gradient norm. If ``None``, no clipping will be applied."""

fp16_loss_scale: tuple[float, float] = (128.0, 0.0001)
fp16_loss_scale: tuple[float, float] = (16, 0.0001)
"""The initial and minimum loss scale for fp16 training."""

gc_every_n_steps: int | None = None
Expand Down
18 changes: 18 additions & 0 deletions src/fairseq2/recipes/lm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@
from fairseq2.recipes.lm._online_finetune._generative_judge import (
J1PointwiseExtractorHandler as J1PointwiseExtractorHandler,
)
from fairseq2.recipes.lm._online_finetune._generative_judge import (
SelfAugmentingExtractor as SelfAugmentingExtractor,
)
from fairseq2.recipes.lm._online_finetune._generative_judge import (
SelfAugmentingExtractorHandler as SelfAugmentingExtractorHandler,
)
from fairseq2.recipes.lm._online_finetune._generative_judge import (
SelfAugmentingExtractor2 as SelfAugmentingExtractor2,
)
from fairseq2.recipes.lm._online_finetune._generative_judge import (
SelfAugmentingExtractorHandler2 as SelfAugmentingExtractorHandler2,
)
from fairseq2.recipes.lm._online_finetune._generative_judge import (
JudgmentExtractorHandler as JudgmentExtractorHandler,
)
Expand Down Expand Up @@ -116,6 +128,12 @@
from fairseq2.recipes.lm._online_finetune._rewards import (
MathVerifyHandler as MathVerifyHandler,
)
from fairseq2.recipes.lm._online_finetune._rewards import (
PplDerivedVerifier as PplDerivedVerifier,
)
from fairseq2.recipes.lm._online_finetune._rewards import (
PplDerivedVerifierHandler as PplDerivedVerifierHandler,
)
from fairseq2.recipes.lm._online_finetune._rewards import (
VLLMOutputRewardHandler as VLLMOutputRewardHandler,
)
Expand Down
114 changes: 108 additions & 6 deletions src/fairseq2/recipes/lm/_online_finetune/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def generate_rollouts(
flat_request_list.extend(rank_prompts)

rollouts = vllm_model.rollout_from_model(
flat_request_list, sampling_params=sampling_params
prompt_list=flat_request_list, sampling_params=sampling_params
)

rollouts_to_scatter = []
Expand Down Expand Up @@ -361,6 +361,7 @@ def combine_prompts_responses_for_scoring(

def get_vllm_logprobs(
vllm_outputs: List[RequestOutput],
model_logps: Tensor,
gangs,
rollout_start_end: tuple[int, int] | None = None,
):
Expand Down Expand Up @@ -405,6 +406,9 @@ def get_vllm_logprobs(
for i, t in enumerate(sequences):
padded[i, : t.size(0)] = t

# clip outputs to be same size as model_logps
if padded.size() != model_logps.size():
padded = padded[:, : model_logps.size(1)]
return padded


Expand All @@ -421,11 +425,47 @@ def convert_vllm_output_to_ref_score(vllm_outputs: List[RequestOutput], gangs):


def compute_token_level_entropy(logits: torch.Tensor, target_mask: torch.Tensor):
"""Calculate entropy from logits. Returns sum of entropies averages for each sequence."""
pd = torch.nn.functional.softmax(logits, dim=-1)
entropy = torch.logsumexp(logits, dim=-1) - torch.sum(pd * logits, dim=-1)
"""Calculate entropy from logits. Returns sum of entropies averages for each sequence.

Uses numerically stable entropy calculation: H = log(Z) - E[logits]
where Z = sum(exp(logits)) and E is the expectation over softmax distribution.

Note: Entropy is computed without gradients to avoid numerical instability.
Memory-efficient implementation processes sequences in chunks.
"""
# Compute entropy without gradients to avoid backprop instability
with torch.no_grad():
logits_clamped = torch.clamp(logits, min=-100, max=100)

# Compute logsumexp for normalization constant
log_z = torch.logsumexp(logits_clamped, dim=-1) # [batch, seq_len]

# Compute expected logits: E[logits] = sum(softmax(logits) * logits)
# Process in smaller chunks to avoid OOM
batch_size, seq_len, vocab_size = logits_clamped.shape
chunk_size = 128 # Process 128 positions at a time

expected_logits = torch.zeros(batch_size, seq_len, device=logits.device)
for i in range(0, seq_len, chunk_size):
end_i = min(i + chunk_size, seq_len)
chunk_logits = logits_clamped[:, i:end_i, :]
chunk_probs = torch.softmax(chunk_logits, dim=-1)
expected_logits[:, i:end_i] = torch.sum(chunk_probs * chunk_logits, dim=-1)
del chunk_probs, chunk_logits # Free memory immediately

# Entropy = log(Z) - E[logits]
entropy = log_z - expected_logits

# Clamp entropy to prevent NaN values
entropy = torch.clamp(entropy, min=0.0, max=100.0)

entropy_target_only = entropy * target_mask
entropy_per_seq = entropy_target_only.sum(dim=-1) / target_mask.sum(dim=-1)

# Compute target token counts and avoid division by zero
target_counts = target_mask.sum(dim=-1)
# Add small epsilon to avoid division by zero
target_counts = torch.clamp(target_counts, min=1e-8)
entropy_per_seq = entropy_target_only.sum(dim=-1) / target_counts

return entropy_per_seq

Expand All @@ -438,6 +478,10 @@ def log_rollouts(prompt_batch: PromptBatch, rollouts, split_name, num_rollouts=1
prompt = prompt_batch.meta_info.get("prompt_raw")[0]
elif "raw_prompt" in prompt_batch.meta_info:
prompt = prompt_batch.meta_info.get("raw_prompt")[0]
elif "raw_prompt_text" in prompt_batch.meta_info:
prompt = prompt_batch.meta_info.get("raw_prompt_text")[0]
elif "prefix_text" in prompt_batch.meta_info:
prompt = prompt_batch.meta_info.get("prefix_text")[0]
else:
# raw text prompt doesn't exist for this dataset
prompt = "DUMMY PROMPT"
Expand All @@ -459,6 +503,46 @@ def get_rollout_lengths(rollouts: List[SequenceData]):
return rollout_lengths


def get_think_rollout_lengths(rollouts: List[SequenceData]):
"""Get the lengths of tokens before the </think> tag in rollouts.

This function calculates the approximate number of tokens generated before
the </think> closing tag in each rollout. It uses a proportional approximation
based on character positions to estimate token counts.

Args:
rollouts: List of SequenceData containing rollout outputs

Returns:
List of token lengths before </think> tag for rollouts that contain the tag
"""
think_rollout_lengths = []
think_tag = "</think>"

for rollout in rollouts:
for sample in rollout.outputs:
rollout_text = sample.text
if think_tag in rollout_text:
# Find the position of </think> in the text
think_end_pos = rollout_text.find(think_tag) + len(think_tag)
# Count tokens up to and including </think>
# We need to find how many tokens correspond to the text before </think>
# Since we have token_ids, we'll approximate by finding the proportion
text_before_think = rollout_text[:think_end_pos]
total_text = rollout_text
total_tokens = len(sample.token_ids)
# Approximate token count proportionally (rough estimate)
# A better approach would be to tokenize text_before_think, but we use approximation
think_token_length = (
int((len(text_before_think) / len(total_text)) * total_tokens)
if len(total_text) > 0
else 0
)
think_rollout_lengths.append(think_token_length)

return think_rollout_lengths


class StatefulRolloutBag:
"""A stateful container for managing and reusing model rollouts across multiple micro-batches.

Expand Down Expand Up @@ -559,6 +643,13 @@ def update_avg_rollout_length(metric_bag: MetricBag, avg_rollout_length):
metric_bag.get(Mean, "avg_rollout_length").update(avg_rollout_length, weight=1)


@torch.inference_mode()
def update_avg_think_rollout_length(metric_bag: MetricBag, avg_think_rollout_length):
metric_bag.get(Mean, "avg_think_rollout_length").update(
avg_think_rollout_length, weight=1
)


@torch.inference_mode()
def update_avg_reward_len_norm(metric_bag: MetricBag, avg_reward_len_norm):
metric_bag.get(Mean, "avg_reward_len_norm").update(avg_reward_len_norm, weight=1)
Expand Down Expand Up @@ -599,7 +690,9 @@ def update_grpo_batch_metrics(


@torch.inference_mode()
def update_grpo_loss(metric_bag: MetricBag, batch: PromptBatch, loss: Tensor) -> None:
def update_grpo_loss(
metric_bag: MetricBag, batch: PromptBatch, loss: Tensor, tis_imp_ratio: Tensor
) -> None:
"""Update the GRPO loss metric.

:param batch:
Expand All @@ -611,6 +704,8 @@ def update_grpo_loss(metric_bag: MetricBag, batch: PromptBatch, loss: Tensor) ->
loss / batch.batch_size, weight=batch.batch_size
)

metric_bag.get(Mean, "tis_imp_ratio").update(tis_imp_ratio)


def compute_reference_logps(
gangs: Gangs,
Expand Down Expand Up @@ -651,3 +746,10 @@ def get_parameter_converter(model_config):
raise RuntimeError(f"{model_config} not supported in online recipe")

return _convert_parameter


def masked_mean(x, mask, eps=1e-8):
# mask: boolean tensor with same shape as x
masked_x = x * mask
denom = mask.sum().clamp_min(eps)
return masked_x.sum() / denom
Loading