Skip to content
Back to skills

Svs Variational Problem Synthesis

ASecurity

Generate problem variants from correct model solutions while preserving answer equivalence, enabling self-play training that maintains output diversity and prevents entropy collapse.

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

Works with

  • cli

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill svs-variational-problem-synthesis --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Svs Variational Problem Synthesis?

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

Security grade badge for Svs Variational Problem Synthesis
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-svs-variational-problem-synthesis/badge)](https://www.skillsdirectory.com/skills/adu2021-svs-variational-problem-synthesis)

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: svs-variational-problem-synthesis
title: "Self-Play with Variational Problem Synthesis for Sustained Reasoning Diversity"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: https://arxiv.org/abs/2508.14029
keywords: [reinforcement-learning, problem-synthesis, entropy-preservation, diversity, self-play]
description: "Generate problem variants from correct model solutions while preserving answer equivalence, enabling self-play training that maintains output diversity and prevents entropy collapse."
---

# Self-Play with Variational Problem Synthesis (SvS)

## Core Concept

SvS addresses entropy collapse in reasoning RL by synthesizing problem variants derived from correct model solutions. The key insight is that while Pass@1 improves with RL training, Pass@k degrades due to reduced output diversity. By generating new problems from correct solutions (maintaining identical answers), SvS sustains policy entropy during training. This approach achieves 18-22% improvements on AIME benchmarks while preserving the model's ability to generate diverse high-quality solutions.

## Architecture Overview

- **Correct Solution Extraction**: Identify and isolate correct reasoning steps
- **Invariant Answer Identification**: Determine what must remain constant across variants
- **Problem Variant Generation**: Create mathematically equivalent problems
- **Self-Play Training Loop**: Iteratively improve on synthetic problems
- **Entropy Preservation**: Monitor and maintain output diversity metrics

## Implementation Steps

### 1. Extract and Analyze Correct Solutions

Identify successful reasoning traces for variant generation:

```python
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass

@dataclass
class Solution:
    """Representation of a model solution."""
    problem: str
    reasoning_steps: List[str]
    final_answer: str
    intermediate_values: Dict[str, any]
    confidence: float
    is_correct: bool

class SolutionAnalyzer:
    def __init__(self, verifier: "AnswerVerifier"):
        self.verifier = verifier

    def extract_correct_solutions(
        self,
        samples: List[Tuple[str, str, str]],  # (problem, response, answer)
        num_best: int = 5
    ) -> List[Solution]:
        """
        Extract solutions verified as correct.
        """
        correct_solutions = []

        for problem, response, predicted_answer in samples:
            # Verify correctness
            is_correct = self.verifier.verify(predicted_answer, problem)

            if is_correct:
                # Parse solution structure
                solution = Solution(
                    problem=problem,
                    reasoning_steps=self._parse_steps(response),
                    final_answer=predicted_answer,
                    intermediate_values=self._extract_values(response),
                    confidence=1.0 if is_correct else 0.0,
                    is_correct=is_correct
                )
                correct_solutions.append(solution)

        # Return top solutions by confidence/quality
        correct_solutions.sort(key=lambda s: s.confidence, reverse=True)
        return correct_solutions[:num_best]

    def _parse_steps(self, response: str) -> List[str]:
        """Extract reasoning steps from response."""
        # Split by logical boundaries (Step, Therefore, etc.)
        import re
        steps = re.split(r'Step|Therefore|Thus|So', response)
        return [s.strip() for s in steps if s.strip()]

    def _extract_values(self, response: str) -> Dict[str, any]:
        """Extract intermediate numerical values."""
        import re
        # Match patterns like "x = 5", "area = 20", etc.
        pattern = r'(\w+)\s*=\s*([\d\.\-]+)'
        matches = re.findall(pattern, response)
        return {k: float(v) for k, v in matches}
```

### 2. Implement Problem Variant Generation

Create mathematically equivalent problems from solutions:

```python
class ProblemVariantGenerator:
    """Generate problem variants maintaining answer equivalence."""

    def __init__(self, problem_template_library: Dict[str, List[str]]):
        self.templates = problem_template_library

    def generate_variants(
        self,
        solution: Solution,
        num_variants: int = 3,
        variant_type: str = "substitution"
    ) -> List[Tuple[str, str]]:
        """
        Generate problem variants from correct solution.

        Returns: [(new_problem, expected_answer), ...]
        """
        variants = []

        if variant_type == "substitution":
            # Change numbers but preserve answer
            for i in range(num_variants):
                new_problem = self._substitute_values(
                    solution.problem,
                    solution.intermediate_values,
                    variant_index=i
                )
                variants.append((new_problem, solution.final_answer))

        elif variant_type == "reformulation":
            # Rephrase problem while preserving structure
            for i in range(num_variants):
                new_problem = self._reformulate_problem(
                    solution.problem,
                    solution.reasoning_steps,
                    variant_index=i
                )
                variants.append((new_problem, solution.final_answer))

        elif variant_type == "context_swap":
            # Use different context but same mathematical structure
            for i in range(num_variants):
                new_problem = self._swap_context(
                    solution.problem,
                    solution.final_answer,
                    variant_index=i
                )
                variants.append((new_problem, solution.final_answer))

        return variants

    def _substitute_values(
        self,
        original_problem: str,
        known_values: Dict[str, float],
        variant_index: int = 0
    ) -> str:
        """
        Create variant by substituting different numbers.

        Key: keep mathematical relationships intact.
        """
        import re

        problem = original_problem
        seed = variant_index

        for var_name, original_val in known_values.items():
            # Generate new value preserving answer
            if isinstance(original_val, float) and original_val != 0:
                # Scale by small factor
                scale = 1.0 + (seed * 0.1) % 0.5
                new_val = original_val * scale

                # Replace in problem
                pattern = re.escape(str(int(original_val) if original_val == int(original_val) else original_val))
                problem = re.sub(pattern, str(int(new_val) if new_val == int(new_val) else new_val), problem)

        return problem

    def _reformulate_problem(
        self,
        problem: str,
        reasoning_steps: List[str],
        variant_index: int = 0
    ) -> str:
        """
        Reformulate problem preserving mathematical structure.

        Example: "Find x such that..." -> "Solve for x where..."
        """
        reformulations = {
            "find": "calculate",
            "what is": "determine",
            "compute": "evaluate",
            "how many": "count the number of",
            "if": "suppose",
        }

        problem_lower = problem.lower()
        for original, replacement in reformulations.items():
            if original in problem_lower:
                problem = problem.replace(original, replacement)
                break

        return problem

    def _swap_context(
        self,
        original_problem: str,
        answer: str,
        variant_index: int = 0
    ) -> str:
        """
        Create variant with different real-world context.
        """
        contexts = [
            "In a classroom with",
            "At a store with",
            "During a game with",
            "In a garden with",
            "For a project with",
        ]

        # Extract numerical content from original
        import re
        numbers = re.findall(r'\d+', original_problem)

        if variant_index < len(contexts):
            context = contexts[variant_index]
        else:
            context = contexts[0]

        # Reconstruct with new context
        new_problem = f"{context} {' and '.join(numbers)} items..."
        return new_problem
```

### 3. Implement Self-Play Training Loop

Train model on generated variants:

```python
class SelfPlayTrainer:
    """Trains model on self-generated problem variants."""

    def __init__(
        self,
        model: "LLM",
        variant_generator: ProblemVariantGenerator,
        solution_analyzer: SolutionAnalyzer,
        reward_model: "RewardModel"
    ):
        self.model = model
        self.variant_generator = variant_generator
        self.analyzer = solution_analyzer
        self.reward_model = reward_model
        self.training_history = []

    def self_play_iteration(
        self,
        base_problems: List[str],
        num_solutions_per_problem: int = 8,
        num_variants_per_solution: int = 2,
        num_training_steps: int = 100
    ) -> Dict[str, float]:
        """
        Execute single self-play iteration.
        """

        iteration_metrics = {}

        # Step 1: Generate solutions on base problems
        print("Generating solutions on base problems...")
        base_solutions = []
        for problem in base_problems:
            solutions = self._generate_n_solutions(
                problem,
                num_solutions_per_problem
            )
            base_solutions.extend(solutions)

        # Step 2: Extract correct solutions
        print("Extracting correct solutions...")
        correct_solutions = self.analyzer.extract_correct_solutions(
            [(s.problem, s.reasoning_steps, s.final_answer) for s in base_solutions],
            num_best=len(base_solutions) // 2
        )

        iteration_metrics["num_correct"] = len(correct_solutions)
        iteration_metrics["base_accuracy"] = len(correct_solutions) / len(base_solutions)

        # Step 3: Generate variants
        print("Generating problem variants...")
        variant_dataset = []
        for solution in correct_solutions:
            variants = self.variant_generator.generate_variants(
                solution,
                num_variants=num_variants_per_solution,
                variant_type="substitution"
            )
            variant_dataset.extend(variants)

        # Step 4: Train on variants
        print(f"Training on {len(variant_dataset)} variants...")
        variant_loss = self._train_on_variants(
            variant_dataset,
            num_steps=num_training_steps
        )

        iteration_metrics["variant_loss"] = variant_loss

        # Step 5: Measure entropy preservation
        print("Measuring output diversity...")
        base_entropy, variant_entropy = self._measure_entropy(
            base_problems,
            num_solutions_per_problem
        )

        iteration_metrics["base_entropy"] = base_entropy
        iteration_metrics["variant_entropy"] = variant_entropy
        iteration_metrics["entropy_preservation"] = variant_entropy / (base_entropy + 1e-8)

        self.training_history.append(iteration_metrics)
        return iteration_metrics

    def _generate_n_solutions(
        self,
        problem: str,
        n: int = 8
    ) -> List[Solution]:
        """Generate n diverse solutions for a problem."""
        solutions = []

        for i in range(n):
            # Generate with different random seeds for diversity
            response = self.model.generate(
                problem,
                temperature=0.7 + (i % 3) * 0.1,  # Vary temperature
                max_tokens=500
            )

            solution = Solution(
                problem=problem,
                reasoning_steps=self.analyzer._parse_steps(response),
                final_answer=self._extract_answer(response),
                intermediate_values=self.analyzer._extract_values(response),
                confidence=0.5,  # Will be verified later
                is_correct=False
            )
            solutions.append(solution)

        return solutions

    def _train_on_variants(
        self,
        variant_dataset: List[Tuple[str, str]],
        num_steps: int = 100,
        batch_size: int = 4
    ) -> float:
        """
        Train model on variant dataset using RL.
        """
        total_loss = 0.0

        for step in range(num_steps):
            # Sample batch
            batch_variants = variant_dataset[
                (step * batch_size) % len(variant_dataset):
                ((step + 1) * batch_size) % len(variant_dataset)
            ]

            # Collect responses
            batch_loss = 0.0
            for problem, expected_answer in batch_variants:
                response = self.model.generate(problem, max_tokens=500)
                predicted_answer = self._extract_answer(response)

                # Compute reward
                reward = self.reward_model.compute_reward(
                    problem,
                    response,
                    expected_answer
                )

                # Policy gradient loss
                loss = -reward * self.model.log_prob(response)
                batch_loss += loss.item()

            avg_batch_loss = batch_loss / len(batch_variants)
            total_loss += avg_batch_loss

            # Optimize
            self.model.optimizer.zero_grad()
            avg_batch_loss.backward()
            torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
            self.model.optimizer.step()

        return total_loss / num_steps

    def _measure_entropy(
        self,
        problems: List[str],
        num_solutions: int = 8
    ) -> Tuple[float, float]:
        """
        Measure output diversity (Shannon entropy of solution tokens).
        """
        import torch
        from scipy.stats import entropy

        all_solutions = []
        for problem in problems:
            for _ in range(num_solutions):
                response = self.model.generate(problem, max_tokens=300)
                all_solutions.append(response)

        # Compute token entropy
        # Collect all unique tokens
        token_counts = {}
        for solution in all_solutions:
            tokens = solution.split()
            for token in set(tokens):  # Unique tokens per solution
                token_counts[token] = token_counts.get(token, 0) + 1

        token_probs = list(token_counts.values())
        token_probs = [p / sum(token_probs) for p in token_probs]

        avg_entropy = entropy(token_probs)
        return avg_entropy, avg_entropy  # Placeholder for variant_entropy

    def _extract_answer(self, response: str) -> str:
        """Extract final answer from response."""
        # Find numerical answer or final statement
        import re
        numbers = re.findall(r'\d+(?:\.\d+)?', response)
        return numbers[-1] if numbers else response.split()[-1]
```

### 4. Track and Monitor Entropy Collapse

Monitor diversity metrics:

```python
class EntropyMonitor:
    """Monitor output diversity and entropy metrics."""

    def __init__(self, check_interval: int = 10):
        self.check_interval = check_interval
        self.entropy_history = []
        self.pass_k_history = []

    def compute_pass_k(
        self,
        problems: List[str],
        model: "LLM",
        k_values: List[int] = [1, 3, 5],
        num_generations: int = 32
    ) -> Dict[int, float]:
        """
        Compute Pass@k metric (k solutions needed for one correct).
        """
        pass_k_scores = {k: 0.0 for k in k_values}

        for problem in problems:
            # Generate multiple solutions
            solutions = []
            for _ in range(num_generations):
                response = model.generate(problem, max_tokens=500)
                solutions.append(response)

            # Check which have correct answers
            correct_mask = [self._verify_solution(s, problem) for s in solutions]

            # Compute Pass@k
            for k in k_values:
                if any(correct_mask[:k]):
                    pass_k_scores[k] += 1.0 / len(problems)

        return pass_k_scores

    def compute_output_entropy(
        self,
        problems: List[str],
        model: "LLM",
        num_generations: int = 8
    ) -> float:
        """
        Compute Shannon entropy of outputs.

        High entropy = diverse outputs
        Low entropy = repetitive outputs
        """
        from scipy.stats import entropy as scipy_entropy

        all_solutions = []
        for problem in problems:
            for _ in range(num_generations):
                response = model.generate(problem, max_tokens=300)
                all_solutions.append(response)

        # Tokenize and compute entropy over token vocabulary
        token_freq = {}
        for solution in all_solutions:
            for token in set(solution.split()):
                token_freq[token] = token_freq.get(token, 0) + 1

        probs = list(token_freq.values())
        probs = [p / sum(probs) for p in probs]

        return scipy_entropy(probs)

    def check_entropy_collapse(self, current_entropy: float) -> bool:
        """Detect if entropy is dropping (collapse pattern)."""
        if len(self.entropy_history) < 2:
            return False

        recent_entropy = self.entropy_history[-1]
        entropy_drop = recent_entropy - current_entropy

        # Collapse threshold: >10% entropy drop
        return entropy_drop > 0.1 * recent_entropy

    def _verify_solution(self, solution: str, problem: str) -> bool:
        """Verify if solution is correct."""
        # In practice: use answer verification
        pass
```

## Practical Guidance

### When to Use SvS

- Mathematical reasoning benchmarks (AIME, high school math)
- Tasks with clear correct/incorrect answers
- Scenarios where output diversity matters (Pass@k evaluation)
- Self-play learning scenarios
- Models where entropy collapse is observed

### When NOT to Use

- Creative generation (poetry, stories)
- Tasks without canonical correct answers
- Low-diversity tasks where Pass@1 is sole metric
- Real-time inference (variant generation adds latency)

### Key Hyperparameters

- **num_variants_per_solution**: 2-5 (more = better coverage)
- **variant_generation_type**: "substitution" recommended for math
- **entropy_threshold**: 10-20% drop before intervention
- **self_play_iterations**: 3-10 per model size
- **temperature_variation**: 0.7-1.0 range for diversity

### Performance Expectations

- AIME24 Improvement: +18.3%
- AIME25 Improvement: +22.8%
- Entropy Preservation: 80-95% of baseline diversity
- Pass@k Ceiling: Sustained rather than degrading
- Model Scales: Works across 3B-32B parameter models

## Reference

Researchers. (2024). Beyond Pass@1: Self-Play with Variational Problem Synthesis. arXiv preprint arXiv:2508.14029.

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.