Skip to content
Back to skills

Slow Fast Policy Optimization Rl

ASecurity

Stabilize RL for LLM reasoning via three-phase decomposition: fast inner trajectory optimization, repositioning to manage off-policy drift, slow correction for stable updates. Achieve up to 2.80-point math reasoning gains over GRPO while reducing rollouts 4.93x and wall-clock time 4.19x via improved stability without changing reward structure.

  • 6 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 9, 2026
toolspythongitperformance

Works with

  • cli

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill slow-fast-policy-optimization-rl --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Slow Fast Policy Optimization Rl?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Slow Fast Policy Optimization Rl
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-slow-fast-policy-optimization-rl/badge)](https://www.skillsdirectory.com/skills/adu2021-slow-fast-policy-optimization-rl)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: slow-fast-policy-optimization-rl
title: "Slow-Fast Policy Optimization: Reposition-Before-Update for LLM Reasoning"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2510.04072"
keywords: [policy optimization, RL stability, gradient control, off-policy divergence, reasoning models]
description: "Stabilize RL for LLM reasoning via three-phase decomposition: fast inner trajectory optimization, repositioning to manage off-policy drift, slow correction for stable updates. Achieve up to 2.80-point math reasoning gains over GRPO while reducing rollouts 4.93x and wall-clock time 4.19x via improved stability without changing reward structure."
---

# Slow-Fast Policy Optimization: Reposition-Before-Update

## Core Concept

On-policy RL for LLMs suffers gradient instability from noisy early-training rollouts. Slow-Fast Policy Optimization (SFPO) decomposes each training step into three phases: fast optimization on the current batch, repositioning to constrain off-policy divergence, and slow correction for stable updates. This decomposition is plug-compatible with existing pipelines while dramatically improving stability and efficiency.

## Architecture Overview

- **Three-Phase Framework**: (1) Fast trajectory optimization on same batch; (2) Reposition to manage divergence; (3) Slow correction phase
- **Off-Policy Divergence Control**: Track KL divergence during each phase to prevent catastrophic divergence
- **Plug-Compatible Design**: Works with existing policy gradient implementations (GRPO, PPO) without modification
- **Efficiency Gains**: 4.93× rollout reduction, 4.19× wall-clock speedup while improving performance

## Implementation Steps

### 1. Three-Phase Decomposition Framework

Structure each training iteration into coordinated phases.

```python
class SlowFastPolicyOptimizer:
    def __init__(self, policy_model, learning_rate=1e-6, max_kl=0.1):
        self.policy = policy_model
        self.optimizer = torch.optim.AdamW(self.policy.parameters(), lr=learning_rate)
        self.max_kl = max_kl  # KL divergence threshold

    def train_step_sfpo(self, batch, initial_policy_state=None):
        """
        Single SFPO training step with three phases.

        Args:
            batch: (observations, actions, rewards, values) from single rollout
            initial_policy_state: Reference policy for KL computation
        """

        # Save initial policy state for divergence tracking
        if initial_policy_state is None:
            initial_policy_state = {
                name: param.detach().clone()
                for name, param in self.policy.named_parameters()
            }

        # Phase 1: Fast optimization on current batch
        print("Phase 1: Fast inner trajectory optimization")
        fast_loss = self._phase1_fast_optimization(batch)

        # Phase 2: Repositioning to manage off-policy drift
        print("Phase 2: Repositioning mechanism")
        repositioned_state = self._phase2_repositioning(initial_policy_state)

        # Phase 3: Slow correction phase
        print("Phase 3: Slow correction for stability")
        final_loss = self._phase3_slow_correction(batch, repositioned_state)

        return {
            'phase1_loss': fast_loss,
            'phase3_loss': final_loss,
            'total_divergence': self._compute_kl_divergence(initial_policy_state)
        }

    def _phase1_fast_optimization(self, batch):
        """
        Fast inner optimization: take multiple gradient steps on same batch.
        """

        obs, actions, rewards, values = batch

        # Compute initial advantages
        advantages = rewards - values
        advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

        # Multiple inner updates (e.g., 5 steps)
        inner_losses = []

        for _ in range(5):  # Inner loop iterations
            # Forward pass
            action_logits = self.policy(obs)

            # Compute policy loss (GRPO/PPO style)
            log_probs = torch.log_softmax(action_logits, dim=-1)
            selected_log_probs = log_probs[range(len(actions)), actions]

            # Clipped objective
            ratio = torch.exp(selected_log_probs - selected_log_probs.detach())
            clipped_ratio = torch.clamp(ratio, 0.9, 1.1)

            loss = -torch.min(ratio, clipped_ratio) * advantages

            # Gradient step
            self.optimizer.zero_grad()
            loss.mean().backward()
            torch.nn.utils.clip_grad_norm_(self.policy.parameters(), 1.0)
            self.optimizer.step()

            inner_losses.append(loss.mean().item())

        return sum(inner_losses) / len(inner_losses)

    def _phase2_repositioning(self, initial_state):
        """
        Repositioning: Adjust policy to constrain KL divergence from initial state.
        Prevents divergence from accumulating across training steps.
        """

        # Compute current KL divergence
        current_kl = self._compute_kl_divergence(initial_state)

        if current_kl > self.max_kl:
            print(f"KL divergence {current_kl:.4f} exceeds threshold {self.max_kl:.4f}")
            print("Applying repositioning correction...")

            # Reposition: interpolate between current and initial state
            # α ∝ (KL - threshold) / KL
            excess_divergence = max(0, current_kl - self.max_kl)
            alpha = excess_divergence / (current_kl + 1e-8)

            # Linear interpolation toward initial state
            for (name, param), (init_name, init_param) in zip(
                self.policy.named_parameters(),
                initial_state.items()
            ):
                if name == init_name:
                    param.data = (1 - alpha) * param.data + alpha * init_param

        return {
            'kl_divergence': current_kl,
            'repositioned': current_kl > self.max_kl
        }

    def _phase3_slow_correction(self, batch, repositioned_state):
        """
        Slow correction: Final update step with reduced learning rate.
        Ensures stability after repositioning.
        """

        obs, actions, rewards, values = batch

        # Advantages
        advantages = rewards - values
        advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

        # Single slow update step
        action_logits = self.policy(obs)
        log_probs = torch.log_softmax(action_logits, dim=-1)
        selected_log_probs = log_probs[range(len(actions)), actions]

        # Clipped objective with reduced learning rate
        ratio = torch.exp(selected_log_probs - selected_log_probs.detach())
        clipped_ratio = torch.clamp(ratio, 0.95, 1.05)  # Tighter clipping than Phase 1

        loss = -torch.min(ratio, clipped_ratio) * advantages

        # Very small update
        self.optimizer.param_groups[0]['lr'] = 1e-7  # Temporarily reduce LR
        self.optimizer.zero_grad()
        loss.mean().backward()
        torch.nn.utils.clip_grad_norm_(self.policy.parameters(), 0.1)  # Tight clipping
        self.optimizer.step()
        self.optimizer.param_groups[0]['lr'] = 1e-6  # Restore LR

        return loss.mean().item()

    def _compute_kl_divergence(self, reference_state):
        """
        Compute KL divergence from reference policy parameters.
        Simplified: L2 distance in parameter space (proxy for KL).
        """

        total_kl = 0

        for (name, param), (ref_name, ref_param) in zip(
            self.policy.named_parameters(),
            reference_state.items()
        ):
            if name == ref_name:
                kl = torch.norm(param.data - ref_param) / (torch.norm(ref_param) + 1e-8)
                total_kl += kl.item()

        return total_kl / len(reference_state)
```

### 2. Integration with GRPO Pipeline

SFPO is plug-compatible; use as drop-in replacement for standard gradient steps.

```python
def grpo_training_with_sfpo(
    policy, base_model, train_loader, num_epochs=5, group_size=8
):
    """
    Standard GRPO training loop but with SFPO decomposition.
    """

    optimizer = SlowFastPolicyOptimizer(policy, learning_rate=1e-6)

    for epoch in range(num_epochs):
        total_loss = 0
        num_batches = 0

        for batch_idx, batch in enumerate(train_loader):
            # Standard GRPO: generate rollouts
            obs, actions, rewards = batch

            # Compute value estimates
            with torch.no_grad():
                values = base_model.estimate_value(obs)

            # Group rewards (GRPO)
            group_rewards = []
            for i in range(0, len(rewards), group_size):
                group = rewards[i:i+group_size]
                group_mean = group.mean()
                group_std = group.std() + 1e-8
                normalized = (group - group_mean) / group_std
                group_rewards.extend(normalized)

            group_rewards = torch.tensor(group_rewards)

            # SFPO training step (replaces standard gradient update)
            batch_data = (obs, actions, group_rewards, values)
            sfpo_result = optimizer.train_step_sfpo(batch_data)

            total_loss += sfpo_result['phase3_loss']
            num_batches += 1

            if (batch_idx + 1) % 10 == 0:
                avg_loss = total_loss / num_batches
                print(f"Epoch {epoch}, Batch {batch_idx+1}: Loss={avg_loss:.4f}")

    return policy
```

### 3. Hyperparameter Configuration

SFPO requires minimal tuning; primary hyperparameter is KL threshold.

```python
sfpo_config = {
    'learning_rate': 1e-6,              # Standard LR
    'inner_loop_steps': 5,              # Phase 1 iterations
    'max_kl_divergence': 0.1,           # Repositioning threshold
    'phase3_learning_rate': 1e-7,       # Slow correction LR
    'phase1_clipping': (0.9, 1.1),      # Standard PPO clipping
    'phase3_clipping': (0.95, 1.05),    # Tighter Phase 3 clipping
    'grad_clip_norm': 1.0               # Gradient clipping
}

# Benchmark: Math reasoning (AIME subset)
# Configuration for 1.5B Qwen model
sfpo_math_config = {
    'inner_loop_steps': 3,
    'max_kl': 0.05,                    # Conservative for safety
    'batch_size': 32,
    'num_epochs': 3
}
```

## Performance Results

Evaluation on mathematical reasoning benchmarks:

```python
results = {
    'grpo_baseline': {
        'accuracy': 'Base',
        'rollouts_per_iter': 32,
        'wall_clock': '100% (baseline)'
    },
    'sfpo': {
        'accuracy_improvement': '+2.8 points (on AIME subset)',
        'rollouts_per_iter': 32,  # Same
        'actual_rollouts_used': '6.5 (4.93x reduction)',
        'wall_clock': '23.8% of baseline (4.19x speedup)',
        'mechanism': 'Better reuse of low-quality initial rollouts via repositioning'
    }
}
```

## Practical Guidance

**Phase Balancing**: Adjust inner loop steps (Phase 1) based on rollout quality. More steps needed if rollouts are noisy.

**KL Threshold**: 0.05-0.1 works well for most domains. Start conservative; increase if divergence becomes a bottleneck.

**Clipping Adjustment**: Phase 3 should use tighter clipping (0.95-1.05) to prevent destabilization after repositioning.

**Compatibility**: SFPO works with any policy gradient objective (GRPO, PPO, REINFORCE). No changes to reward structure needed.

## When to Use / When NOT to Use

**Use When**:
- Training reasoning models where rollout quality is variable
- Early training stages with high gradient noise
- Compute budget is constrained (4x speedup is significant)
- Stability is critical (robotics, safety-sensitive tasks)

**NOT For**:
- Tasks with uniformly high-quality rollouts
- Scenarios where convergence speed matters more than stability
- Very small batch sizes (<8) where repositioning has minimal effect

## Reference

This skill synthesizes findings from "Slow-Fast Policy Optimization: Reposition-Before-Update for LLM Reasoning" (arXiv:2510.04072, ICLR 2026). Three-phase decomposition achieves stability without objective modification.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.