Learn when to terminate chain-of-thought reasoning by predicting answer arrival patterns. Train an early-exit mechanism on empirical first-answer positions to reduce reasoning length by 14-55% while maintaining or improving accuracy.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add ADu2021/skillXiv --skill terminator-early-stopping-cot --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Terminator Early Stopping Cot?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adu2021-terminator-early-stopping-cot)More formats (shields.io, HTML) on the badges page.
---
name: terminator-early-stopping-cot
title: "TERMINATOR: Learning Optimal Exit Points for Early Stopping in CoT Reasoning"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2603.12529"
keywords: [Early Stopping, Chain-of-Thought, Reasoning Efficiency, Token Reduction, Exit Prediction]
description: "Learn when to terminate chain-of-thought reasoning by predicting answer arrival patterns. Train an early-exit mechanism on empirical first-answer positions to reduce reasoning length by 14-55% while maintaining or improving accuracy."
---
# TERMINATOR: Learning Optimal Exit Points for Early Stopping in CoT Reasoning
Large Reasoning Models (LRMs) generate excessive intermediate tokens during chain-of-thought reasoning, continuing to compute and output reasoning steps long after arriving at the correct answer. TERMINATOR solves this by treating the problem not as hand-tuned stopping rules, but as a learnable prediction task. By observing where answers first appear in successful reasoning traces, you can train an early-exit mechanism that terminates generation at those optimal points—reducing reasoning token length by 14-55% across benchmarks while maintaining or improving accuracy.
The core insight: answer arrival patterns are predictable from problem characteristics and early reasoning steps. Rather than arbitrary length limits, you can learn task-specific stopping points empirically from data.
## Core Concept
TERMINATOR operates through three key phases:
1. **Data Collection** — Generate reasoning traces with full CoT output, identify first answer occurrence position
2. **Pattern Learning** — Train predictor on early reasoning context to forecast optimal exit point
3. **Inference** — Use predictor to signal early termination, preventing wasteful over-generation
The technique treats reasoning length not as a fixed budget, but as a learned function of problem difficulty and generation progress.
## Architecture Overview
- **Reference Dataset Generation** — Run full CoT on target tasks, mark first answer arrival position
- **Early-Context Encoder** — Processes partial reasoning (first N% of generated tokens) to predict optimal stopping point
- **Exit Signal Predictor** — Outputs confidence that answer has arrived; triggers stopping when confidence exceeds threshold
- **Length Estimation Model** — Predicts task-specific optimal reasoning length distribution
- **Inference Pipeline** — Monitor generation stream, apply early exit when signal triggers
- **Validation Framework** — Test on held-out problems, measure accuracy preservation and token reduction
## Implementation Steps
Begin by collecting reference data showing where answers first appear in successful reasoning traces.
```python
import json
from collections import defaultdict
def collect_answer_arrival_data(model, tasks, output_dir="arrivals"):
"""Generate full CoT traces and mark answer first-arrival positions."""
arrivals = []
for task_id, task in enumerate(tasks):
# Generate full CoT trace
prompt = f"Reason step-by-step:\n{task['question']}"
full_trace = model.generate(prompt, max_tokens=2000, return_token_log=True)
# Parse full trace to find answer
answer_token_start = None
answer_text = task.get('answer', '')
# Search for answer in generated tokens
for token_idx, token_data in enumerate(full_trace['tokens']):
if answer_text.lower() in token_data['text'].lower():
answer_token_start = token_idx
break
if answer_token_start is None:
continue # Skip if answer not found
arrivals.append({
'task_id': task_id,
'task_domain': task['domain'],
'total_tokens': len(full_trace['tokens']),
'answer_token_position': answer_token_start,
'answer_arrival_ratio': answer_token_start / len(full_trace['tokens']),
'full_trace': full_trace['text'],
'partial_trace_at_answer': full_trace['text'][:answer_token_start]
})
# Analyze distribution by domain
by_domain = defaultdict(list)
for arrival in arrivals:
by_domain[arrival['task_domain']].append(
arrival['answer_arrival_ratio'])
print("Answer arrival ratios by domain:")
for domain, ratios in by_domain.items():
avg_ratio = sum(ratios) / len(ratios)
print(f" {domain}: {avg_ratio:.2%} (n={len(ratios)})")
return arrivals
```
Next, train the exit predictor on early reasoning context. The key is learning from partial traces where the model hasn't yet completed reasoning.
```python
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
class ExitPredictor(nn.Module):
"""Predicts whether answer has arrived based on partial trace."""
def __init__(self, embed_dim=768, hidden_dim=1024):
super().__init__()
self.embedding = nn.Embedding(50257, embed_dim) # GPT vocab size
self.encoder = nn.TransformerEncoderLayer(
d_model=embed_dim,
nhead=8,
dim_feedforward=hidden_dim,
batch_first=True
)
self.confidence_head = nn.Sequential(
nn.Linear(embed_dim, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, 256),
nn.GELU(),
nn.Linear(256, 1),
nn.Sigmoid() # Confidence 0-1
)
def forward(self, token_ids, partial_lengths):
"""
Args:
token_ids: [batch, max_seq_len] partial reasoning token IDs
partial_lengths: [batch] actual length of each partial trace
"""
embeddings = self.embedding(token_ids)
# Mask padding
mask = torch.arange(token_ids.size(1)).unsqueeze(0) < partial_lengths.unsqueeze(1)
# Encode with attention
encoded = self.encoder(embeddings, src_key_padding_mask=~mask)
# Pool using last non-padding token
last_token_indices = (partial_lengths - 1).clamp(min=0)
last_embeddings = encoded[torch.arange(encoded.size(0)),
last_token_indices]
# Predict exit confidence
exit_confidence = self.confidence_head(last_embeddings)
return exit_confidence.squeeze(-1)
def train_exit_predictor(arrivals, model, tokenizer, epochs=10, batch_size=32):
"""Train predictor on answer-arrival data."""
# Prepare training data
X = [] # Partial traces at different completion percentages
y = [] # Binary labels: has answer arrived?
for arrival in arrivals:
full_tokens = tokenizer.encode(arrival['full_trace'])
answer_pos = arrival['answer_token_position']
# Create samples at 20%, 40%, 60%, 80% completion
for completion_pct in [0.2, 0.4, 0.6, 0.8]:
current_pos = int(len(full_tokens) * completion_pct)
partial_tokens = full_tokens[:current_pos]
X.append(torch.tensor(partial_tokens))
# Label: 1 if we've reached answer, 0 otherwise
y.append(1.0 if current_pos >= answer_pos else 0.0)
# Pad sequences
max_len = max(len(x) for x in X)
X_padded = torch.zeros(len(X), max_len, dtype=torch.long)
lengths = []
for i, x in enumerate(X):
X_padded[i, :len(x)] = x
lengths.append(len(x))
y_tensor = torch.tensor(y, dtype=torch.float32)
lengths_tensor = torch.tensor(lengths, dtype=torch.long)
# Create dataset and loader
dataset = TensorDataset(X_padded, lengths_tensor, y_tensor)
loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# Train
predictor = ExitPredictor()
optimizer = torch.optim.Adam(predictor.parameters(), lr=1e-4)
criterion = nn.BCELoss()
for epoch in range(epochs):
total_loss = 0
for X_batch, lengths_batch, y_batch in loader:
logits = predictor(X_batch, lengths_batch)
loss = criterion(logits, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}: Loss = {total_loss/len(loader):.4f}")
return predictor
```
Finally, integrate the exit predictor into your inference pipeline to terminate early.
```python
# Inference with early exit using the predictor
def generate_with_early_exit(model, prompt, predictor, tokenizer,
exit_threshold=0.7, max_tokens=2000):
"""Generate with learned early stopping."""
generated_tokens = []
token_ids = tokenizer.encode(prompt)
for step in range(max_tokens):
# Generate next token
with torch.no_grad():
logits = model(torch.tensor([token_ids]))
next_token = torch.argmax(logits[-1, -1, :])
generated_tokens.append(next_token.item())
# Check exit signal periodically (every 10 tokens)
if step > 50 and step % 10 == 0:
# Tokenize partial trace
partial_tokens = torch.tensor([token_ids + generated_tokens])
partial_length = torch.tensor([len(token_ids) + len(generated_tokens)])
# Get exit confidence
with torch.no_grad():
exit_conf = predictor(partial_tokens, partial_length)
print(f"Step {step}: Exit confidence = {exit_conf:.2%}")
# Exit if confident that answer has arrived
if exit_conf > exit_threshold:
print(f"Early exit at step {step} with confidence {exit_conf:.2%}")
break
token_ids.append(next_token.item())
# Standard stopping: end-of-sequence token
if next_token == tokenizer.eos_token_id:
break
return tokenizer.decode(generated_tokens)
def benchmark_early_exit(model, predictor, tokenizer, test_tasks,
thresholds=[0.5, 0.6, 0.7, 0.8]):
"""Measure accuracy vs token reduction across thresholds."""
results = []
for threshold in thresholds:
total_tokens = 0
correct = 0
for task in test_tasks:
output = generate_with_early_exit(model, task['prompt'],
predictor, tokenizer,
exit_threshold=threshold)
total_tokens += len(tokenizer.encode(output))
# Check correctness
if task['answer'].lower() in output.lower():
correct += 1
avg_tokens = total_tokens / len(test_tasks)
accuracy = correct / len(test_tasks)
results.append({
'threshold': threshold,
'accuracy': accuracy,
'avg_tokens': avg_tokens
})
print(f"Threshold {threshold}: Accuracy={accuracy:.1%}, "
f"Avg Tokens={avg_tokens:.0f}")
return results
```
## Practical Guidance
**Hyperparameters and When to Use:**
- Exit threshold typically ranges 0.6-0.8; lower thresholds reduce tokens more aggressively but risk premature stopping
- Collect reference data on at least 1000 examples per task domain; smaller datasets overfit exit patterns
- Use when reasoning outputs often contain extended justification after the answer is already provided
- Particularly effective for math, QA, and code generation tasks with clear answer formats
**When NOT to use:**
- For open-ended generation where there is no clear "answer" (creative writing, summarization)
- When accuracy is paramount and cannot tolerate even small degradation
- For tasks where reasoning steps after the first answer contain critical corrections or refinements
**Common Pitfalls:**
- Training on only successful traces ignores failure patterns; collect data from both correct and incorrect outputs
- Using fixed threshold across all task types; train separate predictors or dynamic thresholds per domain
- Not accounting for answer format variations (e.g., numerical vs. verbal answers); normalize answer representations
- Excessive early exit reducing reasoning quality for complex problems; use confidence thresholding, not fixed position thresholds
## Reference
Paper: [TERMINATOR: Learning Optimal Exit Points for Early Stopping in CoT Reasoning](https://arxiv.org/abs/2603.12529)
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!