Skip to content
Back to skills

Rl Verifiable Rewards

ASecurity

RLVR extends reasoning capabilities by proving answer-only rewards implicitly incentivize correct intermediate reasoning via the Logic Prior principle.

  • 6 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 9, 2026
testingpythontestinggit

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill rl-verifiable-rewards --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rl Verifiable Rewards?

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

Security grade badge for Rl Verifiable Rewards
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-rl-verifiable-rewards/badge)](https://www.skillsdirectory.com/skills/adu2021-rl-verifiable-rewards)

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: rl-verifiable-rewards
title: "Reinforcement Learning with Verifiable Rewards Implicitly Incentivizes Correct Reasoning"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2506.14245"
keywords: [reinforcement-learning, reasoning, verifiable-rewards, logic-prior, chain-of-thought]
description: "RLVR extends reasoning capabilities by proving answer-only rewards implicitly incentivize correct intermediate reasoning via the Logic Prior principle."
---

# Reinforcement Learning with Verifiable Rewards Implicitly Incentivizes Correct Reasoning

## Core Concept

This work investigates whether reinforcement learning with verifiable rewards (RLVR) genuinely enhances reasoning or merely improves sampling efficiency. The key finding: RLVR can extend the reasoning boundary for both mathematical and coding tasks. The theoretical contribution shows why answer-only rewards work through the "Logic Prior" assumption—that correct reasoning chains more reliably produce correct answers. A novel CoT-Pass@K metric evaluates both answer and reasoning correctness.

## Architecture Overview

- **RLVR Framework**: Apply RL optimization to LLM outputs using verifiable (answer-only) rewards
- **Logic Prior Principle**: Theoretical explanation that correct reasoning is correlated with correct answers
- **CoT-Pass@K Metric**: Evaluates both final answer and intermediate reasoning steps for correctness
- **Training Dynamics**: Track P(CA) [probability correct answer] and P(CC|CA) [probability correct chain given correct answer]
- **Generalization Analysis**: Demonstrate that RL creates valid reasoning improvements, not just sampling artifacts

## Implementation

### Step 1: Define Verifiable Reward Function

Create reward signal based on answer correctness alone:

```python
import torch
from typing import List, Dict

class VerifiableRewardModel:
    """
    Verifiable rewards: only correct/incorrect answer classification.
    No intermediate step supervision—testing if RLVR alone improves reasoning.
    """
    def __init__(self, task_type='math'):
        self.task_type = task_type

    def compute_reward(self, answers: List[str],
                      ground_truth: List[str]) -> torch.Tensor:
        """
        Args:
            answers: [batch_size] generated answers
            ground_truth: [batch_size] correct answers

        Returns:
            rewards: [batch_size] binary (1.0 or -1.0)
        """
        rewards = []

        for ans, gt in zip(answers, ground_truth):
            # Simple answer matching (can be more sophisticated)
            is_correct = self._check_correctness(ans, gt)
            reward = 1.0 if is_correct else -1.0
            rewards.append(reward)

        return torch.tensor(rewards, dtype=torch.float32)

    def _check_correctness(self, answer: str, ground_truth: str) -> bool:
        """Check if answer matches ground truth"""
        # Strip whitespace, handle numeric answers
        answer_clean = answer.strip().lower()
        gt_clean = ground_truth.strip().lower()

        # Extract final numeric answer if applicable
        if self.task_type == 'math':
            import re
            answer_num = re.findall(r'-?\d+\.?\d*', answer_clean)
            gt_num = re.findall(r'-?\d+\.?\d*', gt_clean)

            if answer_num and gt_num:
                return float(answer_num[-1]) == float(gt_num[-1])

        return answer_clean == gt_clean
```

### Step 2: Extract Chain-of-Thought from Generations

Parse reasoning chains for CoT-Pass@K evaluation:

```python
class ChainOfThoughtExtractor:
    """
    Extracts reasoning chains and final answers from model outputs.
    """
    def __init__(self, reasoning_markers=None):
        if reasoning_markers is None:
            self.reasoning_markers = ['<|thinking|>', '```']
        else:
            self.reasoning_markers = reasoning_markers

    def extract_reasoning_and_answer(self, text: str) -> tuple:
        """
        Args:
            text: full model output

        Returns:
            (reasoning_chain, final_answer)
        """
        lines = text.split('\n')

        reasoning_lines = []
        answer_lines = []
        in_reasoning = False

        for line in lines:
            # Detect reasoning blocks
            if any(marker in line for marker in self.reasoning_markers):
                in_reasoning = not in_reasoning
                continue

            if in_reasoning:
                reasoning_lines.append(line)
            else:
                answer_lines.append(line)

        reasoning_chain = '\n'.join(reasoning_lines).strip()
        final_answer = '\n'.join(answer_lines).strip()

        return reasoning_chain, final_answer

    def extract_step_sequence(self, reasoning_chain: str) -> List[str]:
        """Break reasoning into logical steps"""
        # Split by common step markers
        import re
        steps = re.split(
            r'(Step \d+:|Therefore|Thus|So|Finally)',
            reasoning_chain
        )
        steps = [s.strip() for s in steps if s.strip()]

        return steps
```

### Step 3: Implement CoT-Pass@K Metric

Evaluate both answer and reasoning quality:

```python
class CoTPassAtK:
    """
    Comprehensive metric: Pass@K on both answer and reasoning.
    """
    def __init__(self, judge_model=None):
        # Can use LLM-as-judge or rule-based verification
        self.judge_model = judge_model

    def compute_pass_at_k(self, generations: List[List[str]],
                          ground_truth: List[str],
                          verify_reasoning=True) -> Dict[str, float]:
        """
        Args:
            generations: [batch_size, k_samples] list of generated answers
            ground_truth: [batch_size] correct answers
            verify_reasoning: whether to verify reasoning quality

        Returns:
            metrics: dict with Pass@K values
        """
        batch_size = len(ground_truth)
        k = len(generations[0])

        # Pass@K for final answers
        answer_pass_at_k = 0
        reasoning_pass_at_k = 0

        extractor = ChainOfThoughtExtractor()
        verifier = VerifiableRewardModel()

        for i in range(batch_size):
            sample_gens = generations[i]

            # Check if any sample has correct answer
            answer_correct = False
            reasoning_correct = False

            for gen in sample_gens:
                reasoning, answer = extractor.extract_reasoning_and_answer(gen)

                # Verify answer
                if verifier._check_correctness(answer, ground_truth[i]):
                    answer_correct = True

                    # If answer correct, verify reasoning if specified
                    if verify_reasoning and self.judge_model:
                        reasoning_quality = self._verify_reasoning_quality(
                            reasoning, ground_truth[i]
                        )
                        if reasoning_quality > 0.8:  # 80% quality threshold
                            reasoning_correct = True

            answer_pass_at_k += 1 if answer_correct else 0
            reasoning_pass_at_k += 1 if reasoning_correct else 0

        # Normalize to [0, 1]
        metrics = {
            'answer_pass@k': answer_pass_at_k / batch_size,
            'reasoning_pass@k': (reasoning_pass_at_k / batch_size
                                 if verify_reasoning else None)
        }

        return metrics

    def _verify_reasoning_quality(self, reasoning: str,
                                  ground_truth: str) -> float:
        """Use LLM judge to evaluate reasoning quality"""
        if self.judge_model is None:
            return 1.0

        prompt = f"""
        Is the following reasoning chain correct for this answer?

        Ground truth: {ground_truth}
        Reasoning: {reasoning}

        Score from 0 to 1.
        """

        score = self.judge_model.score(prompt)
        return score
```

### Step 4: Implement GRPO Training with RLVR

Train using Group Relative Policy Optimization with verifiable rewards:

```python
class GRPOTrainer:
    """
    Group Relative Policy Optimization trainer with verifiable rewards.
    Simpler than full PPO; groups samples for relative advantage estimation.
    """
    def __init__(self, model, reward_model, device='cuda'):
        self.model = model
        self.reward_model = reward_model
        self.device = device

    def compute_advantages_from_rewards(
        self,
        generations: torch.Tensor,  # [batch, num_groups, seq_len]
        log_probs: torch.Tensor,    # [batch, num_groups, seq_len]
        rewards: torch.Tensor       # [batch, num_groups]
    ) -> torch.Tensor:
        """
        Compute group-relative advantages.
        A(g) = r(g) - mean(r) for group g
        """
        batch_size, num_groups = rewards.shape

        # Compute mean reward across group
        group_mean_reward = rewards.mean(dim=1, keepdim=True)

        # Compute relative advantages
        advantages = rewards - group_mean_reward  # [batch, num_groups]

        # Expand for sequence dimension
        advantages_expanded = advantages.unsqueeze(-1).expand(
            batch_size, num_groups, log_probs.shape[-1]
        )  # [batch, num_groups, seq_len]

        return advantages_expanded

    def training_step(self, batch_prompts, batch_generations,
                      batch_ground_truth):
        """
        Single training step with verifiable rewards.
        """
        batch_size = len(batch_prompts)
        num_groups = len(batch_generations[0])

        # Get log probabilities from model
        log_probs_list = []

        for prompts, gens in zip(batch_prompts, batch_generations):
            with torch.no_grad():
                outputs = self.model(prompts)
                lp = torch.log_softmax(outputs.logits, dim=-1)
                log_probs_list.append(lp)

        # Compute verifiable rewards for all generations
        all_rewards = []

        for i in range(batch_size):
            sample_rewards = self.reward_model.compute_reward(
                batch_generations[i],
                [batch_ground_truth[i]] * num_groups
            )
            all_rewards.append(sample_rewards)

        rewards = torch.stack(all_rewards)  # [batch, num_groups]

        # Compute advantages
        log_probs = torch.stack(log_probs_list)
        advantages = self.compute_advantages_from_rewards(
            None, log_probs, rewards
        )

        # Compute policy gradient loss
        # L = -sum(advantages * log_probs)
        loss = -(advantages * log_probs).sum() / batch_size

        return loss, rewards.mean().item()
```

### Step 5: Analyze Training Dynamics

Track probability metrics throughout training:

```python
def analyze_training_dynamics(model, train_dataloader, num_epochs=10):
    """
    Analyze P(CA) and P(CC|CA) throughout training.
    Shows if RLVR creates genuine reasoning improvements.

    Returns:
        history: dict of metrics over time
    """
    history = {
        'epoch': [],
        'p_correct_answer': [],
        'p_correct_chain_given_answer': [],
        'pass_at_k': []
    }

    extractor = ChainOfThinkingExtractor()
    verifier = VerifiableRewardModel()
    metric_computer = CoTPassAtK()

    for epoch in range(num_epochs):
        p_ca_samples = []
        p_cc_ca_samples = []
        pass_k_samples = []

        for batch in train_dataloader:
            # Generate from model
            generations = model.generate(
                batch['prompts'],
                num_return_sequences=4,
                max_length=512
            )

            # Extract answers and reasoning
            answers = []
            reasonings = []

            for gen in generations:
                reasoning, answer = extractor.extract_reasoning_and_answer(gen)
                answers.append(answer)
                reasonings.append(reasoning)

            # Compute P(CA): probability of correct answer
            correct_answers = [
                verifier._check_correctness(ans, gt)
                for ans, gt in zip(answers, batch['ground_truth'])
            ]
            p_ca = sum(correct_answers) / len(correct_answers)
            p_ca_samples.append(p_ca)

            # Compute P(CC|CA): probability of correct chain given answer
            if sum(correct_answers) > 0:
                correct_chains_given_correct = 0
                total_correct_answers = sum(correct_answers)

                for i, is_correct_ans in enumerate(correct_answers):
                    if is_correct_ans:
                        # Judge if reasoning is correct
                        is_correct_chain = len(reasonings[i]) > 20
                        if is_correct_chain:
                            correct_chains_given_correct += 1

                p_cc_ca = (correct_chains_given_correct /
                          total_correct_answers)
                p_cc_ca_samples.append(p_cc_ca)

            # Compute Pass@K
            pass_k = metric_computer.compute_pass_at_k(
                generations, batch['ground_truth'],
                verify_reasoning=True
            )
            pass_k_samples.append(pass_k['answer_pass@k'])

        # Record epoch statistics
        history['epoch'].append(epoch)
        history['p_correct_answer'].append(sum(p_ca_samples) /
                                          len(p_ca_samples))
        history['p_correct_chain_given_answer'].append(
            sum(p_cc_ca_samples) / len(p_cc_ca_samples)
            if p_cc_ca_samples else 0
        )
        history['pass_at_k'].append(sum(pass_k_samples) /
                                    len(pass_k_samples))

    return history
```

## Practical Guidance

- **Verifiable Tasks**: Use domains where correctness is objectively verifiable (math, code) rather than subjective (writing)
- **Reward Design**: Binary rewards work; can add reward scaling by confidence if needed
- **Sampling Strategy**: Generate multiple samples (k=4-8) per prompt for robust Pass@K evaluation
- **Judge Model**: Use larger model as judge for verification (e.g., DeepSeek-R1 for math)
- **Training Stability**: Use GRPO over PPO for stability with group-relative advantages
- **Evaluation Metrics**: Always measure both answer correctness AND reasoning quality separately
- **Baseline Comparison**: Compare against supervised fine-tuning on reasoning to isolate RL contribution

## Reference

Paper: arXiv:2506.14245
Key metrics: Extended reasoning boundaries for code/math; CoT-Pass@K confirms reasoning quality
Logic Prior: P(correct answer | correct reasoning) > P(correct answer | incorrect reasoning)
Related work: RLHF, verifiable rewards, chain-of-thought, policy optimization

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.