Skip to content
Back to skills

Token Order Prediction

ASecurity

Improve LM performance with token order prediction (TOP) auxiliary loss using learning-to-rank instead of exact multi-token prediction, achieving gains across math, code, and NLP tasks

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

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill token-order-prediction --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Token Order Prediction?

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

Security grade badge for Token Order Prediction
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-token-order-prediction/badge)](https://www.skillsdirectory.com/skills/adu2021-token-order-prediction)

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: token-order-prediction
title: Predicting Token Order Improves Language Model Performance
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: https://arxiv.org/abs/2508.19228
keywords: [auxiliary-objective, token-ordering, learning-to-rank, language-modeling, multi-token-prediction]
description: "Improve LM performance with token order prediction (TOP) auxiliary loss using learning-to-rank instead of exact multi-token prediction, achieving gains across math, code, and NLP tasks"
---

# Predicting the Order of Upcoming Tokens Improves Language Models

## Core Concept

Token Order Prediction (TOP) is a lightweight auxiliary training objective that teaches models to rank upcoming tokens by proximity. Instead of predicting exact future tokens (which is difficult), TOP uses learning-to-rank loss to teach the model to understand token sequencing. This simple auxiliary objective improves performance across mathematics, coding, and standard NLP benchmarks while requiring only a single additional unembedding layer.

## Architecture Overview

- **Learning-to-Rank Loss**: Rank tokens by distance rather than exact prediction
- **Single Extra Layer**: Minimal architectural overhead vs. multi-token prediction
- **Auxiliary Objective**: Combined with next-token prediction during training
- **Task Generalization**: Benefits across math, code, and general NLP
- **Efficiency**: No increase in inference latency or model size

## Implementation Steps

### Stage 1: Design Token Order Prediction Objective

Formulate the learning-to-rank loss for token ordering.

```python
# Token order prediction: learning-to-rank objective
import torch
from torch import nn
import torch.nn.functional as F

class TokenOrderPredictionHead(nn.Module):
    """Predict ordering of upcoming tokens"""

    def __init__(
        self,
        hidden_dim: int,
        vocab_size: int,
        num_future_tokens: int = 5
    ):
        super().__init__()
        self.hidden_dim = hidden_dim
        self.vocab_size = vocab_size
        self.num_future = num_future_tokens

        # Projection from hidden state to ranking scores
        self.ranking_head = nn.Linear(hidden_dim, vocab_size)

    def forward(
        self,
        hidden_states: torch.Tensor,
        future_tokens: torch.Tensor
    ) -> torch.Tensor:
        """
        Compute ranking scores for upcoming tokens.

        Args:
            hidden_states: [batch, seq_len, hidden_dim]
            future_tokens: [batch, seq_len, num_future]

        Returns:
            ranking_scores: [batch, seq_len, vocab_size]
        """
        # Project hidden states to token scores
        scores = self.ranking_head(hidden_states)  # [batch, seq_len, vocab_size]

        return scores


class TopLoss(nn.Module):
    """Learning-to-rank loss for token ordering"""

    def __init__(self, loss_type: str = "listwise"):
        """
        Args:
            loss_type: "listwise", "pairwise", or "pointwise"
        """
        super().__init__()
        self.loss_type = loss_type

    def forward(
        self,
        predicted_scores: torch.Tensor,  # [batch*seq_len, vocab_size]
        target_tokens: torch.Tensor,     # [batch*seq_len, num_future]
        target_positions: torch.Tensor   # [batch*seq_len, num_future] - distance from current
    ) -> torch.Tensor:
        """
        Compute learning-to-rank loss.

        The model should score tokens higher if they appear soon.
        """
        if self.loss_type == "listwise":
            return self._listwise_loss(predicted_scores, target_tokens, target_positions)
        elif self.loss_type == "pairwise":
            return self._pairwise_loss(predicted_scores, target_tokens, target_positions)
        else:
            return self._pointwise_loss(predicted_scores, target_tokens, target_positions)

    def _listwise_loss(
        self,
        predicted_scores: torch.Tensor,
        target_tokens: torch.Tensor,
        target_positions: torch.Tensor
    ) -> torch.Tensor:
        """
        Listwise loss: maximize probability of correct ranking.

        Tokens appearing sooner should have higher scores.
        """
        batch_size = predicted_scores.shape[0]
        loss = 0

        for i in range(batch_size):
            scores = predicted_scores[i]  # [vocab_size]
            tokens = target_tokens[i]      # [num_future]
            positions = target_positions[i]  # [num_future]

            # Get scores for target tokens
            token_scores = scores[tokens]

            # Closer tokens should have higher scores
            # Create preference pairs
            for j in range(len(tokens)):
                for k in range(j + 1, len(tokens)):
                    # Token at position j should have higher score if closer (smaller position)
                    if positions[j] < positions[k]:
                        # score[j] should be > score[k]
                        margin = token_scores[j] - token_scores[k]
                    else:
                        # score[k] should be > score[j]
                        margin = token_scores[k] - token_scores[j]

                    # Hinge-like loss
                    loss += F.relu(1.0 - margin)

        return loss / batch_size

    def _pairwise_loss(
        self,
        predicted_scores: torch.Tensor,
        target_tokens: torch.Tensor,
        target_positions: torch.Tensor
    ) -> torch.Tensor:
        """
        Pairwise loss: compare tokens at different distances.
        """
        batch_size = predicted_scores.shape[0]

        # Get scores for target tokens
        batch_idx = torch.arange(batch_size).unsqueeze(1)
        token_scores = predicted_scores[batch_idx, target_tokens]  # [batch, num_future]

        # Create pairs: closer token vs farther token
        # Closer should have higher score
        loss = 0
        for dist_close in range(target_positions.shape[1]):
            for dist_far in range(dist_close + 1, target_positions.shape[1]):
                score_close = token_scores[:, dist_close]
                score_far = token_scores[:, dist_far]

                # Loss: margin between close and far
                margin_loss = F.relu(1.0 - (score_close - score_far))
                loss += margin_loss.mean()

        return loss

    def _pointwise_loss(
        self,
        predicted_scores: torch.Tensor,
        target_tokens: torch.Tensor,
        target_positions: torch.Tensor
    ) -> torch.Tensor:
        """
        Pointwise loss: score tokens relative to position.
        """
        batch_size = predicted_scores.shape[0]

        # Get scores for target tokens
        batch_idx = torch.arange(batch_size).unsqueeze(1)
        token_scores = predicted_scores[batch_idx, target_tokens]

        # Target: closer tokens should have higher scores
        # Invert positions (smaller distance = larger target score)
        max_pos = target_positions.float().max()
        position_targets = (max_pos - target_positions.float()) / max_pos

        # Regression loss
        loss = F.mse_loss(token_scores, position_targets)

        return loss
```

### Stage 2: Integrate TOP with Next-Token Prediction

Combine TOP auxiliary objective with standard language modeling.

```python
# Combined training with TOP auxiliary objective
class LanguageModelWithTOP(nn.Module):
    """LM with top-k token order prediction"""

    def __init__(
        self,
        hidden_dim: int = 4096,
        vocab_size: int = 32000,
        num_layers: int = 32
    ):
        super().__init__()

        # Standard transformer LM
        self.transformer = TransformerLM(hidden_dim, vocab_size, num_layers)

        # Next-token prediction head
        self.lm_head = nn.Linear(hidden_dim, vocab_size)

        # Token order prediction head
        self.top_head = TokenOrderPredictionHead(hidden_dim, vocab_size)

        self.top_loss_fn = TopLoss(loss_type="pairwise")

    def forward(
        self,
        input_ids: torch.Tensor,
        future_token_ids: torch.Tensor = None
    ) -> Dict:
        """
        Forward pass with both NTP and TOP.

        Args:
            input_ids: [batch, seq_len]
            future_token_ids: [batch, seq_len, num_future] (for TOP)

        Returns:
            ntp_logits, top_scores
        """
        # Get hidden states
        hidden_states = self.transformer(input_ids)

        # Next-token prediction
        ntp_logits = self.lm_head(hidden_states)

        # Token order prediction (if provided)
        top_scores = None
        if future_token_ids is not None:
            top_scores = self.top_head(hidden_states, future_token_ids)

        return {
            "ntp_logits": ntp_logits,
            "top_scores": top_scores,
            "hidden_states": hidden_states
        }


class CombinedTrainingLoop:
    """Train with NTP + TOP objectives"""

    def __init__(
        self,
        model: LanguageModelWithTOP,
        lr: float = 1e-4,
        top_weight: float = 0.1
    ):
        self.model = model
        self.optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
        self.ntp_loss_fn = nn.CrossEntropyLoss()
        self.top_loss_fn = TopLoss(loss_type="pairwise")
        self.top_weight = top_weight

    def extract_future_tokens(
        self,
        input_ids: torch.Tensor,
        num_future: int = 5
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        """
        Extract future tokens and their distances.

        Returns:
            future_token_ids: [batch, seq_len, num_future]
            future_positions: [batch, seq_len, num_future]
        """
        batch_size, seq_len = input_ids.shape

        future_tokens = torch.zeros(
            batch_size, seq_len, num_future,
            dtype=torch.long, device=input_ids.device
        )
        future_positions = torch.zeros_like(future_tokens)

        for i in range(seq_len):
            for j in range(num_future):
                if i + j + 1 < seq_len:
                    future_tokens[:, i, j] = input_ids[:, i + j + 1]
                    future_positions[:, i, j] = j + 1

        return future_tokens, future_positions

    def train_step(self, batch: Dict) -> Dict:
        """Single training step with NTP + TOP"""
        input_ids = batch["input_ids"]
        attention_mask = batch.get("attention_mask")

        # Extract future tokens for TOP
        future_tokens, future_positions = self.extract_future_tokens(input_ids)

        # Forward pass
        outputs = self.model(input_ids, future_tokens)
        ntp_logits = outputs["ntp_logits"]
        top_scores = outputs["top_scores"]

        # Next-token prediction loss
        ntp_loss = self.ntp_loss_fn(
            ntp_logits.view(-1, ntp_logits.shape[-1]),
            input_ids.view(-1)
        )

        # Token order prediction loss
        top_loss = 0
        if top_scores is not None:
            top_loss = self.top_loss_fn(
                top_scores.view(-1, top_scores.shape[-1]),
                future_tokens.view(-1, future_tokens.shape[-1]),
                future_positions.view(-1, future_positions.shape[-1])
            )

        # Combined loss
        total_loss = ntp_loss + self.top_weight * top_loss

        # Backward
        self.optimizer.zero_grad()
        total_loss.backward()
        self.optimizer.step()

        return {
            "total_loss": total_loss.item(),
            "ntp_loss": ntp_loss.item(),
            "top_loss": top_loss.item() if isinstance(top_loss, torch.Tensor) else top_loss
        }

    def set_top_weight(self, weight: float):
        """Adjust TOP weight (useful for curriculum learning)"""
        self.top_weight = weight
```

### Stage 3: Evaluation on Benchmarks

Test TOP across diverse tasks.

```python
# Benchmark evaluation
class TOPEvaluator:
    """Evaluate TOP improvements"""

    def __init__(self, model_with_top: LanguageModelWithTOP):
        self.model = model_with_top

    def evaluate_on_benchmarks(self) -> Dict:
        """
        Evaluate on standard benchmarks.
        Reproduction of paper results.
        """
        benchmarks = {
            "arc_challenge": self.evaluate_benchmark("arc_challenge"),
            "hellaswag": self.evaluate_benchmark("hellaswag"),
            "mmlu": self.evaluate_benchmark("mmlu"),
            "gsm8k": self.evaluate_benchmark("gsm8k"),
            "math": self.evaluate_benchmark("math"),
            "humanevals": self.evaluate_benchmark("humanevals"),
            "mbpp": self.evaluate_benchmark("mbpp")
        }

        return benchmarks

    def evaluate_benchmark(self, benchmark_name: str) -> Dict:
        """Evaluate on single benchmark"""
        # Load test set
        test_set = self.load_benchmark_data(benchmark_name)

        correct = 0
        total = len(test_set)

        for example in test_set:
            prompt = example["prompt"]
            answer = example["answer"]

            # Generate
            generated = self.generate(prompt, max_length=256)

            # Check correctness
            if self.check_correctness(generated, answer, benchmark_name):
                correct += 1

        accuracy = correct / total

        return {
            "benchmark": benchmark_name,
            "correct": correct,
            "total": total,
            "accuracy": accuracy
        }

    def generate(self, prompt: str, max_length: int = 256) -> str:
        """Generate with model"""
        tokens = self.model.transformer.tokenizer.encode(prompt)
        tokens = torch.tensor(tokens).unsqueeze(0)

        for _ in range(max_length):
            outputs = self.model(tokens)
            logits = outputs["ntp_logits"][:, -1, :]
            next_token = logits.argmax(dim=-1)
            tokens = torch.cat([tokens, next_token.unsqueeze(1)], dim=1)

        return self.model.transformer.tokenizer.decode(tokens[0])

    def check_correctness(self, generated: str, reference: str, benchmark: str) -> bool:
        """Check if generation matches reference"""
        if benchmark in ["gsm8k", "math"]:
            # Extract final number
            gen_num = self.extract_number(generated)
            ref_num = self.extract_number(reference)
            return gen_num == ref_num

        else:
            # Exact match or substring
            return reference in generated

    def extract_number(self, text: str) -> float:
        """Extract final number from text"""
        import re
        numbers = re.findall(r'-?\d+\.?\d*', text)
        if numbers:
            return float(numbers[-1])
        return None

    def load_benchmark_data(self, name: str):
        """Load benchmark data"""
        # Placeholder: in practice, load from HuggingFace
        return []
```

## Practical Guidance

### Hyperparameters

- **TOP Weight**: 0.1-0.3 relative to NTP loss (higher = more TOP emphasis)
- **Future Tokens**: 5 is optimal (further tokens have diminishing signal)
- **Loss Type**: Pairwise generally works best
- **Training Duration**: TOP converges quickly; add after 10-20% of pretraining

### When to Use TOP

- Improving language model performance across diverse tasks
- Math and coding tasks (TOP particularly helps here)
- General pretraining where you want better few-shot performance
- Resource-constrained training (minimal overhead)

### When NOT to Use

- Models already performing optimally on target tasks
- Very long-context scenarios (future token extraction becomes expensive)
- Real-time systems where any overhead matters

### Performance Expectations

- Math benchmarks: +2-4% improvement
- Coding benchmarks: +1-3% improvement
- General NLP: +0.5-2% improvement
- Computational overhead: <5% training time increase

### Design Insights

TOP works because predicting exact future tokens is a harder auxiliary task than necessary. By reformulating as a ranking problem ("which tokens appear first?"), the model learns token sequencing patterns without the regression difficulty. This is a goldilocks auxiliary objective—harder than next-token only, but easier than exact multi-token prediction.

## Reference

Predicting the Order of Upcoming Tokens Improves Language Models. arXiv:2508.19228
- https://arxiv.org/abs/2508.19228

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.