Control LLM reasoning effort through discrete modes (High/Medium/Low) using budget-aware supervised fine-tuning and adaptive reward shaping, enabling compression-performance tradeoffs.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add ADu2021/skillXiv --skill thinkdial-reasoning-effort-control --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Thinkdial Reasoning Effort Control?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adu2021-thinkdial-reasoning-effort-control)More formats (shields.io, HTML) on the badges page.
---
name: thinkdial-reasoning-effort-control
title: "ThinkDial: Controlling Reasoning Effort in Large Language Models"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: https://arxiv.org/abs/2508.18773
keywords: [reasoning-budget, token-compression, controllable-inference, budget-aware-training, reward-shaping]
description: "Control LLM reasoning effort through discrete modes (High/Medium/Low) using budget-aware supervised fine-tuning and adaptive reward shaping, enabling compression-performance tradeoffs."
---
# ThinkDial: Controlling Reasoning Effort in LLMs
## Core Concept
ThinkDial enables dynamic control over reasoning computation in LLMs through discrete modes: High (full capability), Medium (50% token reduction), and Low (75% token reduction). The approach combines budget-aware supervised fine-tuning (embedding budget constraints early) with two-phase reinforcement learning (offline stability + online refinement). This enables practical compression-performance tradeoffs comparable to proprietary systems while remaining open-source.
## Architecture Overview
- **Discrete Reasoning Modes**: Three effort levels with clear token budgets
- **Budget-Aware SFT**: Fine-tuning with explicit budget constraints
- **Two-Phase RL**: Offline + online for stable training
- **Adaptive Reward Shaping**: Budget-dependent rewards
- **Mode Switching**: Runtime selection of reasoning effort
## Implementation Steps
### 1. Define Reasoning Modes and Budgets
Create discrete effort levels:
```python
from dataclasses import dataclass
from enum import Enum
from typing import Dict
class ReasoningMode(Enum):
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
@dataclass
class BudgetConfig:
"""Token budget for reasoning mode."""
mode: ReasoningMode
max_tokens: int
thinking_proportion: float # What fraction should be thinking vs answering
description: str
class ThinkDialConfig:
"""Configuration for ThinkDial reasoning modes."""
BUDGETS = {
ReasoningMode.HIGH: BudgetConfig(
mode=ReasoningMode.HIGH,
max_tokens=1000,
thinking_proportion=0.7,
description="Full reasoning capability, highest quality"
),
ReasoningMode.MEDIUM: BudgetConfig(
mode=ReasoningMode.MEDIUM,
max_tokens=500, # 50% reduction
thinking_proportion=0.5,
description="Balanced reasoning, moderate compression"
),
ReasoningMode.LOW: BudgetConfig(
mode=ReasoningMode.LOW,
max_tokens=250, # 75% reduction
thinking_proportion=0.3,
description="Minimal reasoning, maximum compression"
)
}
@staticmethod
def get_budget(mode: ReasoningMode) -> BudgetConfig:
return ThinkDialConfig.BUDGETS[mode]
@staticmethod
def get_budget_embedding(mode: ReasoningMode) -> str:
"""Textual embedding of budget constraint for prompting."""
budget = ThinkDialConfig.get_budget(mode)
return f"[BUDGET: {mode.value.upper()} - {budget.max_tokens} tokens max]"
```
### 2. Implement Budget-Aware SFT
Fine-tune models with explicit budget constraints:
```python
import torch
import torch.nn.functional as F
class BudgetAwareSFTTrainer:
"""Supervised fine-tuning with budget constraints."""
def __init__(
self,
model: "LLM",
learning_rate: float = 1e-5
):
self.model = model
self.optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
def compute_budget_aware_loss(
self,
input_ids: torch.Tensor,
target_ids: torch.Tensor,
mode: ReasoningMode,
current_length: int = 0
) -> torch.Tensor:
"""
Compute SFT loss with budget awareness.
"""
budget_config = ThinkDialConfig.get_budget(mode)
remaining_budget = budget_config.max_tokens - current_length
# Forward pass
outputs = self.model(input_ids, labels=target_ids)
logits = outputs.logits
# Standard language modeling loss
token_loss = F.cross_entropy(
logits.view(-1, logits.shape[-1]),
target_ids.view(-1),
reduction='none'
).view(target_ids.shape)
# Budget penalty: penalize generating beyond budget
sequence_length = target_ids.shape[1]
if sequence_length > remaining_budget:
# Penalize tokens exceeding budget
excess_tokens = sequence_length - remaining_budget
budget_penalty = excess_tokens * 0.5 # Penalty weight
token_loss = token_loss + budget_penalty / sequence_length
# Mode-specific weighting: emphasize thinking for High mode
if mode == ReasoningMode.HIGH:
# Less penalty for longer reasoning
token_loss = token_loss * 0.8
elif mode == ReasoningMode.LOW:
# More penalty for verbose reasoning
token_loss = token_loss * 1.2
return token_loss.mean()
def train_budget_aware_sft(
self,
train_data: List[Dict], # {"input": ..., "target": ..., "mode": ...}
num_epochs: int = 3,
batch_size: int = 8
) -> Dict[str, list]:
"""Train SFT with budget awareness."""
losses = {"high": [], "medium": [], "low": []}
for epoch in range(num_epochs):
for batch_idx in range(0, len(train_data), batch_size):
batch = train_data[batch_idx:batch_idx + batch_size]
batch_loss_by_mode = {m: [] for m in ReasoningMode}
for example in batch:
input_ids = torch.tensor(example["input_ids"])
target_ids = torch.tensor(example["target_ids"])
mode = example.get("mode", ReasoningMode.HIGH)
# Compute budget-aware loss
loss = self.compute_budget_aware_loss(
input_ids.unsqueeze(0),
target_ids.unsqueeze(0),
mode
)
batch_loss_by_mode[mode].append(loss.item())
# Optimize
self.optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
self.optimizer.step()
# Record losses
for mode in ReasoningMode:
if batch_loss_by_mode[mode]:
avg = sum(batch_loss_by_mode[mode]) / len(batch_loss_by_mode[mode])
losses[mode.value].append(avg)
return losses
```
### 3. Implement Two-Phase RL Training
Offline + online RL with budget adaptation:
```python
class TwoPhaseRLTrainer:
"""Two-phase RL: offline stable + online refined."""
def __init__(
self,
model: "LLM",
offline_steps: int = 500,
online_steps: int = 200,
learning_rate: float = 1e-5
):
self.model = model
self.offline_steps = offline_steps
self.online_steps = online_steps
self.optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
def compute_budget_aware_reward(
self,
response: str,
ground_truth: str,
mode: ReasoningMode,
actual_tokens: int
) -> float:
"""
Compute reward considering both accuracy and budget compliance.
"""
# Accuracy component
accuracy = self._compute_accuracy(response, ground_truth)
# Budget compliance component
budget_config = ThinkDialConfig.get_budget(mode)
budget_used_ratio = actual_tokens / budget_config.max_tokens
# Penalize exceeding budget
if budget_used_ratio > 1.0:
budget_compliance = 1.0 - (budget_used_ratio - 1.0) * 0.5
else:
# Reward efficiency: using less tokens is good
budget_compliance = 1.0 + (1.0 - budget_used_ratio) * 0.1
budget_compliance = max(0.0, budget_compliance)
# Combined reward
reward = 0.8 * accuracy + 0.2 * budget_compliance
return reward
def offline_rl_phase(
self,
dataset: List[Dict],
mode: ReasoningMode
) -> Dict[str, float]:
"""
Offline RL: learn from fixed dataset.
Prioritize stability and budget constraint satisfaction.
"""
print(f"Offline RL Phase for {mode.value} mode")
metrics = {"loss": [], "reward": []}
for step in range(self.offline_steps):
# Sample batch
batch = self._sample_batch(dataset, batch_size=4)
batch_loss = 0.0
batch_reward = 0.0
for example in batch:
# Generate response in specific mode
response = self.model.generate(
example["input"],
mode=mode,
max_tokens=ThinkDialConfig.get_budget(mode).max_tokens
)
# Compute reward with budget awareness
reward = self.compute_budget_aware_reward(
response,
example["ground_truth"],
mode,
len(response.split())
)
# Conservative policy loss (offline RL)
log_prob = self.model.get_log_prob(response)
loss = -log_prob * reward
batch_loss += loss.item()
batch_reward += reward
# Optimize
avg_loss = batch_loss / len(batch)
avg_reward = batch_reward / len(batch)
self.optimizer.zero_grad()
(torch.tensor(avg_loss)).backward()
torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
self.optimizer.step()
metrics["loss"].append(avg_loss)
metrics["reward"].append(avg_reward)
if (step + 1) % 100 == 0:
print(f" Step {step+1}: Loss={avg_loss:.4f}, Reward={avg_reward:.4f}")
return metrics
def online_rl_phase(
self,
tasks: List[Dict],
mode: ReasoningMode,
reward_model: "RewardModel" = None
) -> Dict[str, float]:
"""
Online RL: interact with environment.
Refine policy based on real feedback.
"""
print(f"Online RL Phase for {mode.value} mode")
metrics = {"loss": [], "reward": [], "budget_compliance": []}
for step in range(self.online_steps):
# Sample task
task = tasks[step % len(tasks)]
# Generate with current policy
response = self.model.generate(
task["prompt"],
mode=mode,
max_tokens=ThinkDialConfig.get_budget(mode).max_tokens
)
# Get reward
if reward_model:
reward = reward_model.score(response, task["expected"])
else:
reward = self.compute_budget_aware_reward(
response,
task["expected"],
mode,
len(response.split())
)
# Policy gradient update
log_prob = self.model.get_log_prob(response)
loss = -log_prob * reward
self.optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
self.optimizer.step()
# Track budget compliance
budget_config = ThinkDialConfig.get_budget(mode)
tokens_used = len(response.split())
compliance = 1.0 if tokens_used <= budget_config.max_tokens else 0.0
metrics["loss"].append(loss.item())
metrics["reward"].append(reward)
metrics["budget_compliance"].append(compliance)
if (step + 1) % 50 == 0:
avg_loss = sum(metrics["loss"][-50:]) / 50
avg_reward = sum(metrics["reward"][-50:]) / 50
avg_compliance = sum(metrics["budget_compliance"][-50:]) / 50
print(f" Step {step+1}: Loss={avg_loss:.4f}, Reward={avg_reward:.4f}, Compliance={avg_compliance:.2%}")
return metrics
```
### 4. Implement Mode Selection and Deployment
Enable runtime mode switching:
```python
class ThinkDialModel:
"""ThinkDial model with mode control."""
def __init__(self, base_model: "LLM"):
self.model = base_model
self.mode_selector = ModeSelector()
def generate_with_mode(
self,
prompt: str,
mode: ReasoningMode = ReasoningMode.HIGH,
return_mode_info: bool = False
) -> str:
"""Generate response in specified mode."""
budget_config = ThinkDialConfig.get_budget(mode)
budget_embedding = ThinkDialConfig.get_budget_embedding(mode)
# Add mode indicator to prompt
augmented_prompt = f"{budget_embedding}\n{prompt}"
# Generate with budget
response = self.model.generate(
augmented_prompt,
max_tokens=budget_config.max_tokens,
temperature=0.7
)
if return_mode_info:
return response, {
"mode": mode.value,
"tokens_used": len(response.split()),
"budget": budget_config.max_tokens
}
return response
def auto_select_mode(
self,
prompt: str,
time_budget_ms: int = None,
compute_budget: float = None
) -> ReasoningMode:
"""Automatically select mode based on constraints."""
if time_budget_ms:
# Low latency -> Low mode
if time_budget_ms < 100:
return ReasoningMode.LOW
elif time_budget_ms < 500:
return ReasoningMode.MEDIUM
else:
return ReasoningMode.HIGH
if compute_budget:
# Low compute -> Low mode
if compute_budget < 0.3:
return ReasoningMode.LOW
elif compute_budget < 0.7:
return ReasoningMode.MEDIUM
else:
return ReasoningMode.HIGH
return ReasoningMode.HIGH
class ModeSelector(torch.nn.Module):
"""Learn to select appropriate mode for tasks."""
def __init__(self, task_embedding_dim: int = 256):
super().__init__()
self.task_encoder = torch.nn.Linear(task_embedding_dim, 128)
self.mode_classifier = torch.nn.Sequential(
torch.nn.Linear(128, 64),
torch.nn.ReLU(),
torch.nn.Linear(64, len(ReasoningMode))
)
def select_mode(self, task_embedding: torch.Tensor) -> ReasoningMode:
"""Select mode based on task."""
encoded = self.task_encoder(task_embedding)
logits = self.mode_classifier(encoded)
mode_idx = torch.argmax(logits, dim=-1).item()
return list(ReasoningMode)[mode_idx]
```
## Practical Guidance
### When to Use ThinkDial
- Deployment with variable latency constraints
- Mobile/edge devices with compute limits
- Cost-sensitive inference (pay-per-token models)
- Applications requiring quality-latency tradeoffs
- Research into compression-performance curves
### When NOT to Use
- Scenarios requiring always maximum quality
- Real-time applications (<10ms latency)
- Models without budget-aware training
### Key Hyperparameters
- **High budget**: 1000 tokens (baseline)
- **Medium budget**: 500 tokens (50% compression)
- **Low budget**: 250 tokens (75% compression)
- **accuracy_weight**: 0.8 in reward
- **budget_weight**: 0.2 in reward
### Performance Expectations
- Medium Mode: ~95% of High mode quality
- Low Mode: ~85-90% of High mode quality
- Token Reduction: 50% (Medium), 75% (Low)
- Latency Improvement: 2-3x (Medium), 4-5x (Low)
- Inference Cost: Proportional to token reduction
## Reference
Researchers. (2024). ThinkDial: Controlling Reasoning Effort in LLMs. arXiv preprint arXiv:2508.18773.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!