Skip to content
Back to skills

Openvlthinker Vision Reasoning

ASecurity

Train vision-language models for complex reasoning by alternating SFT (supervised fine-tuning via text-only reasoning models) and curriculum RL (Group Relative Policy Optimization). Progressively improve through iterative cycles where each iteration generates better training data.

  • 6 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 9, 2026
ai-agentspython

Works with

  • cli

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill openvlthinker-vision-reasoning --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Openvlthinker Vision Reasoning?

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

Security grade badge for Openvlthinker Vision Reasoning
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-openvlthinker-vision-reasoning/badge)](https://www.skillsdirectory.com/skills/adu2021-openvlthinker-vision-reasoning)

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: openvlthinker-vision-reasoning
title: "OpenVLThinker: An Early Exploration to Complex Vision-Language Reasoning via Iterative Self-Improvement"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2503.17352"
keywords: [Vision-Language, Reasoning, SFT, RL, Self-Improvement, Chain-of-Thought]
description: "Train vision-language models for complex reasoning by alternating SFT (supervised fine-tuning via text-only reasoning models) and curriculum RL (Group Relative Policy Optimization). Progressively improve through iterative cycles where each iteration generates better training data."
---

## Core Concept

Vision-language models struggle with complex reasoning tasks because distilling reasoning from text-only models like DeepSeek-R1 often degrades visual understanding. OpenVLThinker solves this through **iterative SFT-RL cycles**: alternate between lightweight SFT (using text models to generate reasoning demonstrations) and curriculum RL (applying Group Relative Policy Optimization with difficulty-based scheduling). Each cycle improves training data quality, enabling self-improvement with minimal total examples.

## Architecture Overview

- **SFT Phase**: Text-only reasoning models (QwQ-32B) generate chain-of-thought demos from image captions
- **RL Phase**: Group Relative Policy Optimization (GRPO) with difficulty-based curriculum learning
- **Curriculum Scheduler**: Progressively increase difficulty: medium → hard → hardest data
- **Iterative Refinement**: Each cycle's improved model generates better training data for next cycle
- **Minimal Data Requirement**: Only 12K total examples across three cycles

## Implementation Steps

### Step 1: Prepare Training Data with Text-Only Reasoning Models

Use a capable text-only model to generate chain-of-thought demonstrations from image captions and questions.

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

class ReasoningDataGeneration:
    """
    Generate chain-of-thought reasoning using a text-only model
    (e.g., QwQ-32B) from image captions and task questions.
    """
    def __init__(self, text_model, device='cuda'):
        self.text_model = text_model
        self.device = device

    def generate_reasoning(
        self,
        image_caption: str,
        question: str,
        max_length: int = 512
    ) -> str:
        """
        Generate step-by-step reasoning for a VQA task.
        image_caption: Textual description of image
        question: Question about the image
        Returns: Chain-of-thought reasoning text
        """
        prompt = f"""Given the following image description and question,
provide step-by-step reasoning to answer the question.

Image: {image_caption}
Question: {question}

Let me think through this step by step:"""

        # Generate with text model
        inputs = self.text_model.tokenize(prompt)
        with torch.no_grad():
            outputs = self.text_model.generate(
                inputs,
                max_length=max_length,
                temperature=0.7,
                top_p=0.9
            )

        reasoning = self.text_model.decode(outputs[0])
        return reasoning

    def create_sft_examples(
        self,
        captions: List[str],
        questions: List[str],
        answers: List[str]
    ) -> List[Dict]:
        """
        Create SFT training examples with reasoning.
        Returns list of (instruction, reasoning+answer) pairs.
        """
        sft_examples = []

        for caption, question, answer in zip(captions, questions, answers):
            reasoning = self.generate_reasoning(caption, question)

            example = {
                'image_caption': caption,
                'instruction': question,
                'reasoning': reasoning,
                'answer': answer,
                'full_response': f"{reasoning}\n\nAnswer: {answer}"
            }
            sft_examples.append(example)

        return sft_examples
```

### Step 2: Identify Reasoning Keywords and SFT Efficacy

Analyze whether SFT successfully induces reasoning behaviors by examining keyword frequencies in model outputs.

```python
import re
from collections import Counter

def analyze_reasoning_keywords(model_outputs: List[str]) -> Dict[str, float]:
    """
    Count reasoning-related keywords to verify SFT effectiveness.
    Keywords indicate reflection, step-by-step thinking, and uncertainty.
    """
    reasoning_keywords = [
        'let me', 'think', 'step', 'first', 'next', 'then',
        'because', 'thus', 'therefore', 'however', 'although',
        'observe', 'notice', 'identify', 'reason', 'conclusion'
    ]

    keyword_counts = Counter()

    for output in model_outputs:
        output_lower = output.lower()
        for keyword in reasoning_keywords:
            count = len(re.findall(r'\b' + keyword + r'\b', output_lower))
            keyword_counts[keyword] += count

    total_keywords = sum(keyword_counts.values())
    keyword_frequencies = {
        k: v / max(total_keywords, 1) for k, v in keyword_counts.items()
    }

    return keyword_frequencies

def sft_efficacy_score(
    base_model_outputs: List[str],
    sft_model_outputs: List[str]
) -> float:
    """
    Measure SFT effectiveness by comparing keyword distribution.
    Higher score indicates SFT successfully induced reasoning.
    """
    base_keywords = analyze_reasoning_keywords(base_model_outputs)
    sft_keywords = analyze_reasoning_keywords(sft_model_outputs)

    # Compute keyword improvement ratio
    improvements = [
        sft_keywords.get(k, 0) / max(base_keywords.get(k, 0), 1e-6)
        for k in base_keywords.keys()
    ]

    avg_improvement = sum(improvements) / len(improvements) if improvements else 1.0
    return min(avg_improvement, 2.0)  # Cap at 2x improvement
```

### Step 3: Implement Curriculum RL with GRPO

Apply Group Relative Policy Optimization with progressive difficulty scheduling.

```python
import torch.nn.functional as F

class CurriculumGRPO:
    """
    Group Relative Policy Optimization with curriculum learning.
    Progressively train on harder examples: medium → hard → hardest.
    """
    def __init__(self, model, reference_model, device='cuda'):
        self.model = model
        self.reference_model = reference_model
        self.device = device

    def compute_difficulty_score(
        self,
        base_model_accuracy: float,
        gpt4_rating: float
    ) -> float:
        """
        Composite difficulty score combining two signals:
        - Base model error rate (1 - accuracy)
        - GPT-4 quality rating (normalized to [0,1])
        """
        error_rate = 1.0 - base_model_accuracy
        difficulty = (error_rate + (1 - gpt4_rating)) / 2
        return difficulty

    def group_by_difficulty(
        self,
        examples: List[Dict],
        base_model_accuracy: List[float],
        gpt4_ratings: List[float]
    ) -> Dict[str, List[Dict]]:
        """
        Partition examples into difficulty tiers.
        Returns: {'easy': [...], 'medium': [...], 'hard': [...]}
        """
        difficulties = [
            self.compute_difficulty_score(acc, rating)
            for acc, rating in zip(base_model_accuracy, gpt4_ratings)
        ]

        # Sort by difficulty and partition into thirds
        sorted_indices = sorted(range(len(difficulties)), key=lambda i: difficulties[i])
        n = len(sorted_indices)

        easy_idx = sorted_indices[:n // 3]
        medium_idx = sorted_indices[n // 3:2 * n // 3]
        hard_idx = sorted_indices[2 * n // 3:]

        return {
            'easy': [examples[i] for i in easy_idx],
            'medium': [examples[i] for i in medium_idx],
            'hard': [examples[i] for i in hard_idx]
        }

    def grpo_loss(
        self,
        log_probs: torch.Tensor,
        ref_log_probs: torch.Tensor,
        rewards: torch.Tensor,
        beta: float = 0.5
    ) -> torch.Tensor:
        """
        GRPO loss: scales log probability ratios by reward advantage.
        log_probs: current model log probabilities (batch,)
        ref_log_probs: reference model log probabilities (batch,)
        rewards: reward signal (batch,) - higher is better
        """
        log_ratio = log_probs - ref_log_probs

        # Normalize rewards for stability
        rewards = (rewards - rewards.mean()) / (rewards.std() + 1e-8)

        # GRPO: maximize log(pi/pi_ref) * advantage
        advantage = rewards
        loss = -torch.mean(log_ratio * advantage)

        # KL regularization to prevent divergence from reference
        kl_div = (log_probs - ref_log_probs).mean()
        total_loss = loss + beta * kl_div

        return total_loss

    def train_curriculum_rl(
        self,
        training_examples: List[Dict],
        base_accuracies: List[float],
        gpt4_ratings: List[float],
        num_epochs: int = 3,
        batch_size: int = 8
    ):
        """
        Train with curriculum: easy → medium → hard.
        Each stage trains for one epoch on increasingly difficult examples.
        """
        grouped = self.group_by_difficulty(
            training_examples, base_accuracies, gpt4_ratings
        )

        curriculum_order = ['medium', 'hard']  # Start with medium, skip easy

        for stage_idx, difficulty in enumerate(curriculum_order):
            print(f"Training on {difficulty} examples (stage {stage_idx + 1})")

            examples = grouped[difficulty]

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

                for i in range(0, len(examples), batch_size):
                    batch = examples[i:i + batch_size]

                    # Prepare batch
                    prompts = [ex['instruction'] for ex in batch]
                    responses = [ex['full_response'] for ex in batch]
                    rewards = torch.tensor(
                        [ex.get('reward', 1.0) for ex in batch],
                        dtype=torch.float32,
                        device=self.device
                    )

                    # Forward pass
                    log_probs = self._compute_log_probs(prompts, responses)
                    with torch.no_grad():
                        ref_log_probs = self._compute_log_probs_ref(prompts, responses)

                    # Compute loss
                    loss = self.grpo_loss(log_probs, ref_log_probs, rewards)

                    # Backward pass
                    self.model.zero_grad()
                    loss.backward()
                    torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
                    self.model.optimizer.step()

                    total_loss += loss.item()
                    num_batches += 1

                avg_loss = total_loss / max(num_batches, 1)
                print(f"  Epoch {epoch + 1} - Loss: {avg_loss:.4f}")

    def _compute_log_probs(self, prompts: List[str], responses: List[str]) -> torch.Tensor:
        """Compute log probabilities from current model."""
        # Placeholder: actual implementation tokenizes and computes log probs
        return torch.randn(len(prompts), device=self.device)

    def _compute_log_probs_ref(self, prompts: List[str], responses: List[str]) -> torch.Tensor:
        """Compute log probabilities from reference model."""
        with torch.no_grad():
            return torch.randn(len(prompts), device=self.device)
```

### Step 4: Iterative Cycle Management

Orchestrate multiple SFT-RL cycles where each iteration improves training data and model quality.

```python
class IterativeSelfImprovement:
    """
    Manage iterative SFT-RL cycles: cycle 1, 2, 3.
    Each cycle generates better training data from improved model.
    """
    def __init__(self, vlm_model, text_model, num_cycles: int = 3):
        self.vlm_model = vlm_model
        self.text_model = text_model
        self.num_cycles = num_cycles
        self.cycle_metrics = []

    def run_full_pipeline(
        self,
        initial_examples: List[Dict],
        validation_set: List[Dict]
    ):
        """
        Execute full iterative pipeline: SFT → RL → evaluate → repeat.
        initial_examples: Starting training data (images + captions + questions)
        validation_set: Hold-out validation benchmarks
        """
        training_data = initial_examples

        for cycle in range(self.num_cycles):
            print(f"\n===== Cycle {cycle + 1} / {self.num_cycles} =====")

            # Phase 1: SFT
            print(f"Phase 1: Supervised Fine-Tuning on {len(training_data)} examples")
            self.run_sft(training_data)

            # Evaluate after SFT
            sft_metrics = self.evaluate(validation_set)
            print(f"  SFT Metrics: {sft_metrics}")

            # Phase 2: RL
            print(f"Phase 2: Curriculum RL")
            self.run_curriculum_rl(training_data)

            # Evaluate after RL
            rl_metrics = self.evaluate(validation_set)
            print(f"  RL Metrics: {rl_metrics}")

            # Record cycle metrics
            self.cycle_metrics.append({
                'cycle': cycle + 1,
                'sft_metrics': sft_metrics,
                'rl_metrics': rl_metrics
            })

            # Generate new training data for next cycle (if not last)
            if cycle < self.num_cycles - 1:
                print(f"Phase 3: Generating training data for cycle {cycle + 2}")
                training_data = self.generate_next_cycle_data(
                    training_data, rl_metrics
                )

    def run_sft(self, training_data: List[Dict]):
        """Supervised fine-tuning phase."""
        # Tokenize and train standard language model loss
        pass

    def run_curriculum_rl(self, training_data: List[Dict]):
        """Curriculum RL phase."""
        grpo = CurriculumGRPO(self.vlm_model, self.vlm_model)
        # Implement curriculum training
        pass

    def evaluate(self, validation_set: List[Dict]) -> Dict:
        """Evaluate on validation benchmarks."""
        accuracies = []

        for example in validation_set:
            # Generate answer
            output = self.vlm_model.generate(
                image=example['image'],
                prompt=example['question']
            )

            # Compare with ground truth
            correct = self._match_answer(output, example['answer'])
            accuracies.append(float(correct))

        return {
            'accuracy': sum(accuracies) / len(accuracies) if accuracies else 0.0,
            'count': len(validation_set)
        }

    def generate_next_cycle_data(
        self,
        current_data: List[Dict],
        metrics: Dict
    ) -> List[Dict]:
        """
        Generate training data for next cycle using improved model.
        Reuse successful examples and add new harder examples.
        """
        # Keep ~70% of current data, generate ~30% new data
        num_new = len(current_data) // 3

        new_data = current_data[:2 * len(current_data) // 3]

        # Generate new harder examples
        data_gen = ReasoningDataGeneration(self.text_model)
        new_examples = data_gen.create_sft_examples(
            captions=[ex['caption'] for ex in current_data[-num_new:]],
            questions=[ex['question'] for ex in current_data[-num_new:]],
            answers=[ex['answer'] for ex in current_data[-num_new:]]
        )

        new_data.extend(new_examples)
        return new_data

    def _match_answer(self, output: str, ground_truth: str) -> bool:
        """Simple string matching for answer evaluation."""
        return output.strip().lower() == ground_truth.strip().lower()
```

### Step 5: Inference Integration

Combine trained components for efficient inference on reasoning tasks.

```python
class OpenVLThinkerInference:
    """
    Production inference pipeline combining trained VLM with optional
    reasoning generation for transparent answer production.
    """
    def __init__(self, vlm_model, generate_reasoning: bool = True):
        self.vlm_model = vlm_model
        self.generate_reasoning = generate_reasoning

    def answer_question(
        self,
        image: torch.Tensor,
        question: str,
        return_reasoning: bool = False
    ) -> Dict:
        """
        Answer visual question with optional reasoning.
        image: PIL Image or tensor (batch, 3, H, W)
        question: Text question about image
        """
        if self.generate_reasoning:
            # Generate reasoning-based answer
            prompt = f"Question: {question}\n\nLet me think through this step by step:"
        else:
            prompt = f"Question: {question}\nAnswer:"

        # Prepare input
        inputs = self.vlm_model.process(image, prompt)

        # Generate response
        with torch.no_grad():
            outputs = self.vlm_model.generate(
                **inputs,
                max_length=256,
                temperature=0.7,
                top_p=0.9
            )

        response = self.vlm_model.decode(outputs[0])

        # Parse answer from response
        if self.generate_reasoning:
            parts = response.split('Answer:')
            reasoning = parts[0] if len(parts) > 0 else ""
            answer = parts[1].strip() if len(parts) > 1 else response.strip()
        else:
            reasoning = ""
            answer = response.strip()

        result = {
            'answer': answer,
            'full_response': response
        }

        if return_reasoning:
            result['reasoning'] = reasoning

        return result
```

## Practical Guidance

**When to Use:**
- Training vision-language models for complex reasoning tasks (MathVista, visual reasoning)
- Scenarios with limited labeled reasoning data (few thousand examples)
- Cases where reasoning transparency is important (education, scientific analysis)
- Projects where iterative self-improvement through synthetic data generation is viable

**When NOT to Use:**
- Simple visual classification or object detection (overkill)
- Scenarios where text-only reasoning models aren't available or high quality
- Real-time inference systems where chain-of-thought verbosity is unacceptable
- Domain-specific reasoning where text models haven't seen your specific domain

**Hyperparameter Tuning:**
- **num_cycles**: 3 standard; 2 for fast iteration, 4-5 for maximum quality if data is abundant
- **difficulty_ratio**: 70% easy / medium, 30% hard; adjust based on validation plateau
- **SFT_epochs**: 1-2; use 1 for computational efficiency, 2 for better convergence
- **RL_beta (KL weight)**: 0.5 standard; increase to 1.0 if model diverges from reference
- **curriculum_order**: ['medium', 'hard']; skip 'easy' unless validation accuracy is very low

**Common Pitfalls:**
- Weak text-only reasoning model produces garbage reasoning demonstrations; use QwQ-32B or DeepSeek-R1
- Insufficient validation data; GPT-4 ratings required for accurate difficulty scoring
- Over-training on narrow difficulty tier; rotate through curriculum stages gradually
- RL divergence: monitor KL divergence between model and reference; increase beta if KL > 0.2

## References

- arXiv:2503.17352 - OpenVLThinker paper
- https://arxiv.org/abs/2412.15729 - Group Relative Policy Optimization (GRPO)
- https://arxiv.org/abs/2103.01734 - Chain-of-Thought Prompting
- QwQ-32B model: https://huggingface.co/Qwen/QwQ-32B

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.