Skip to content
Back to skills

T3d Trajectory Distillation

ASecurity

Accelerate diffusion language models from many to few generation steps using trajectory self-distillation. Collect clean-noisy state pairs along teacher trajectories, apply reverse-KL mode-seeking distillation, and weight losses by decoding order to prioritize early predictions where cascading errors compound.

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

Works with

  • cli

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill t3d-trajectory-distillation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of T3d Trajectory Distillation?

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

Security grade badge for T3d Trajectory Distillation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-t3d-trajectory-distillation/badge)](https://www.skillsdirectory.com/skills/adu2021-t3d-trajectory-distillation)

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: t3d-trajectory-distillation
title: "T3D: Few-Step Diffusion LMs via Trajectory Self-Distillation"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2602.12262"
keywords: [Diffusion Language Models, Distillation, Few-Step Generation, Self-Supervision, Mode-Seeking]
description: "Accelerate diffusion language models from many to few generation steps using trajectory self-distillation. Collect clean-noisy state pairs along teacher trajectories, apply reverse-KL mode-seeking distillation, and weight losses by decoding order to prioritize early predictions where cascading errors compound."
---

# T3D: Few-Step Diffusion LMs via Trajectory Self-Distillation

## Problem Context

Diffusion language models require many sampling steps (25-50+) for high-quality generation. Few-step variants are needed for deployment, but naive training on random corruption schedules causes train-test mismatch: inference uses confidence-based schedules while training sees uniform random masking. T3D solves this by collecting trajectory pairs from teacher's actual inference schedule and using mode-seeking distillation.

## Core Concept

T3D operates in two phases: (1) collect (clean, intermediate) state pairs along teacher trajectories during inference, (2) train student via reverse-KL objective that promotes mode-seeking (concentrating on high-probability outputs) with path-consistency weighting. This addresses the multimodal posterior problem where forward-KL spreads mass across modes.

## Architecture Overview

- **Trajectory collection**: Run full teacher steps, save clean (x₀) and intermediate (xₜ) states
- **Reverse-KL distillation**: Use discriminative KL divergence promoting mode concentration
- **Path-consistency weighting**: Weight token losses by decoding order (early tokens > later tokens)
- **Mode-seeking loss**: Prevent mode-covering behavior in highly multimodal posteriors
- **Few-step inference**: Student generates quality outputs in 2-4 steps

## Implementation

### Step 1: Collect trajectory pairs from teacher

```python
import torch
import torch.nn.functional as F
from typing import List, Tuple, Dict

class TrajectoryCollector:
    """Collect (clean, corrupted) state pairs from teacher inference."""

    def __init__(
        self,
        teacher_model,
        num_collection_steps: int = 50,
        corruption_schedule: str = 'linear'
    ):
        self.teacher_model = teacher_model
        self.num_collection_steps = num_collection_steps
        self.corruption_schedule = corruption_schedule

    def get_corruption_schedule(self, num_steps: int) -> torch.Tensor:
        """
        Get corruption level schedule for diffusion.

        Args:
            num_steps: Number of denoising steps

        Returns:
            sigma_t: Noise level at each step [num_steps]
        """
        if self.corruption_schedule == 'linear':
            # Linear schedule from max to min
            return torch.linspace(1.0, 0.0, num_steps)
        elif self.corruption_schedule == 'cosine':
            # Cosine schedule
            t = torch.linspace(0, 1, num_steps)
            return torch.cos(t * torch.pi / 2)
        else:
            raise ValueError(f"Unknown schedule: {self.corruption_schedule}")

    def corrupt_tokens(
        self,
        clean_tokens: torch.Tensor,  # [seq_len]
        sigma: float,
        mask_token_id: int
    ) -> torch.Tensor:
        """
        Add noise by masking tokens.

        Args:
            clean_tokens: Original tokens
            sigma: Noise level (0-1, where 1=all masked)
            mask_token_id: Mask token ID

        Returns:
            corrupted: Noisy version with sigma fraction masked
        """
        num_mask = int(sigma * len(clean_tokens))
        mask_positions = torch.randperm(len(clean_tokens))[:num_mask]

        corrupted = clean_tokens.clone()
        corrupted[mask_positions] = mask_token_id

        return corrupted

    def collect_trajectories(
        self,
        prompt: str,
        num_trajectories: int = 4
    ) -> List[Dict[str, torch.Tensor]]:
        """
        Generate trajectories and collect (clean, intermediate) pairs.

        Args:
            prompt: Input prompt
            num_trajectories: Number of trajectories to collect

        Returns:
            trajectories: List of {clean: Tensor, intermediates: List[Tensor]}
        """
        trajectories = []
        corruption_schedule = self.get_corruption_schedule(self.num_collection_steps)

        for traj_idx in range(num_trajectories):
            # Generate clean sample with teacher
            clean_tokens = self.teacher_model.generate(prompt, num_steps=50)

            # Collect intermediate corrupted versions
            intermediates = []
            for step_idx, sigma in enumerate(corruption_schedule):
                corrupted = self.corrupt_tokens(
                    clean_tokens,
                    sigma.item(),
                    mask_token_id=self.teacher_model.mask_token_id
                )
                intermediates.append(corrupted)

            trajectories.append({
                'clean': clean_tokens,
                'intermediates': intermediates,
                'schedule': corruption_schedule
            })

        return trajectories
```

### Step 2: Implement reverse-KL distillation objective

```python
class ReverseKLDistillation:
    """Reverse-KL (mode-seeking) distillation for few-step training."""

    def __init__(
        self,
        teacher_model,
        student_model,
        temperature: float = 1.0
    ):
        self.teacher_model = teacher_model
        self.student_model = student_model
        self.temperature = temperature

    def compute_teacher_logits(
        self,
        corrupted_tokens: torch.Tensor,
        clean_tokens: torch.Tensor,
        sigma: float
    ) -> torch.Tensor:
        """Get teacher's predicted distribution for clean tokens."""
        with torch.no_grad():
            teacher_logits = self.teacher_model.forward(
                corrupted_tokens,
                step=sigma,
                return_logits=True
            )
        return teacher_logits

    def compute_reverse_kl_loss(
        self,
        student_logits: torch.Tensor,   # [seq_len, vocab_size]
        teacher_logits: torch.Tensor,   # [seq_len, vocab_size]
        clean_tokens: torch.Tensor      # [seq_len]
    ) -> torch.Tensor:
        """
        Compute reverse-KL loss: E_q[log q - log p].

        Promotes mode concentration on teacher's high-probability outputs.
        q = student (learner), p = teacher (reference)
        """
        student_log_probs = F.log_softmax(student_logits / self.temperature, dim=-1)
        teacher_log_probs = F.log_softmax(teacher_logits / self.temperature, dim=-1)

        # Sample from student
        student_probs = F.softmax(student_logits / self.temperature, dim=-1)
        student_samples = torch.multinomial(
            student_probs.view(-1, student_probs.shape[-1]),
            num_samples=1
        ).squeeze(-1)

        # Compute KL: E_q[log q - log p]
        # In practice, approximate with cross-entropy from student samples
        kl_loss = (
            student_log_probs[range(len(clean_tokens)), student_samples] -
            teacher_log_probs[range(len(clean_tokens)), student_samples]
        ).mean()

        return kl_loss

    def compute_mode_seeking_loss(
        self,
        student_logits: torch.Tensor,
        teacher_logits: torch.Tensor,
        clean_tokens: torch.Tensor
    ) -> torch.Tensor:
        """
        Mode-seeking objective: penalize student spreading mass.

        Uses reverse-KL which avoids mode-covering behavior.
        """
        student_probs = F.softmax(student_logits, dim=-1)
        teacher_probs = F.softmax(teacher_logits, dim=-1)

        # Reverse-KL: sum over positions
        student_log_probs = F.log_softmax(student_logits, dim=-1)

        # For each position, compute KL(student || teacher)
        # = sum_v student_probs[v] * (log student_probs[v] - log teacher_probs[v])
        kl_per_position = (
            student_probs *
            (student_log_probs - F.log_softmax(teacher_logits, dim=-1))
        ).sum(dim=-1)

        return kl_per_position.mean()
```

### Step 3: Path-consistency weighting

```python
class PathConsistencyWeighting:
    """Weight losses by token decoding order."""

    @staticmethod
    def compute_order_weights(
        seq_len: int,
        schedule: torch.Tensor,
        order: str = 'confidence'
    ) -> torch.Tensor:
        """
        Compute per-token loss weights based on decoding order.

        Early tokens (where cascading errors start) get higher weight.

        Args:
            seq_len: Sequence length
            schedule: Corruption schedule
            order: 'confidence' (teacher confidence order) or 'sequential'

        Returns:
            weights: [seq_len] weight for each position
        """
        if order == 'sequential':
            # Linear decay: first token gets highest weight
            weights = torch.linspace(1.0, 0.3, seq_len)
        elif order == 'confidence':
            # Weights derived from teacher's confidence ranking
            # Higher confidence tokens earlier; they cascade errors more
            confidence_ranks = torch.arange(seq_len, dtype=torch.float32)
            weights = (seq_len - confidence_ranks) / seq_len
        else:
            weights = torch.ones(seq_len)

        return weights

    @staticmethod
    def apply_path_consistency_loss(
        loss_per_position: torch.Tensor,  # [seq_len]
        weights: torch.Tensor              # [seq_len]
    ) -> torch.Tensor:
        """Weight loss by position importance."""
        weighted_loss = (loss_per_position * weights).mean()
        return weighted_loss
```

### Step 4: Full training step with distillation

```python
class T3DTrainer:
    """Trajectory-based few-step distillation trainer."""

    def __init__(
        self,
        teacher_model,
        student_model,
        optimizer,
        num_student_steps: int = 4
    ):
        self.teacher_model = teacher_model
        self.student_model = student_model
        self.optimizer = optimizer
        self.num_student_steps = num_student_steps

        self.collector = TrajectoryCollector(teacher_model)
        self.kl_distiller = ReverseKLDistillation(teacher_model, student_model)
        self.path_weighting = PathConsistencyWeighting()

    def training_step(
        self,
        prompt: str,
        num_trajectories: int = 4
    ) -> Dict[str, float]:
        """
        Single training step on trajectories.

        Args:
            prompt: Input prompt
            num_trajectories: Trajectories to collect and train on

        Returns:
            metrics: Loss and auxiliary metrics
        """
        # Step 1: Collect trajectories
        trajectories = self.collector.collect_trajectories(
            prompt, num_trajectories=num_trajectories
        )

        total_loss = 0.0
        num_samples = 0

        for traj in trajectories:
            clean_tokens = traj['clean']
            intermediates = traj['intermediates']
            schedule = traj['schedule']

            # Compute path-consistency weights
            path_weights = self.path_weighting.compute_order_weights(
                len(clean_tokens), schedule, order='confidence'
            )

            # Sample corruption levels from schedule
            num_train_steps = len(schedule) // max(1, len(schedule) // self.num_student_steps)

            for step_idx in range(0, len(schedule), num_train_steps):
                corrupted = intermediates[step_idx]
                sigma = schedule[step_idx]

                # Forward pass: student predicts denoising
                student_logits = self.student_model.forward(
                    corrupted, step=sigma.item(), return_logits=True
                )

                # Teacher prediction for mode-seeking target
                teacher_logits = self.kl_distiller.compute_teacher_logits(
                    corrupted, clean_tokens, sigma.item()
                )

                # Reverse-KL mode-seeking loss
                kl_loss = self.kl_distiller.compute_mode_seeking_loss(
                    student_logits, teacher_logits, clean_tokens
                )

                # Apply path-consistency weighting
                weighted_loss = self.path_weighting.apply_path_consistency_loss(
                    kl_loss.view(-1), path_weights
                )

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

                total_loss += weighted_loss.item()
                num_samples += 1

        avg_loss = total_loss / max(1, num_samples)

        return {'loss': avg_loss}
```

### Step 5: Few-step inference and evaluation

```python
def generate_few_step(
    student_model,
    prompt: str,
    num_steps: int = 4,
    schedule: str = 'cosine'
) -> torch.Tensor:
    """
    Generate with few-step student model.

    Args:
        student_model: Distilled few-step model
        prompt: Input prompt
        num_steps: Number of generation steps (2-4)
        schedule: Corruption schedule

    Returns:
        generated_tokens: Final prediction
    """
    # Get schedule
    if schedule == 'cosine':
        sigmas = torch.linspace(1.0, 0.0, num_steps)
    else:
        sigmas = torch.linspace(1.0, 0.0, num_steps)

    # Initialize with full masking
    seq_len = 128  # Default
    corrupted = torch.full((seq_len,), student_model.mask_token_id)

    for step_idx, sigma in enumerate(sigmas):
        # Denoise at current corruption level
        logits = student_model.forward(corrupted, step=sigma.item(), return_logits=True)
        probs = F.softmax(logits, dim=-1)
        corrupted = torch.multinomial(probs, num_samples=1).squeeze(-1)

    return corrupted


def evaluate_few_step_quality(
    student_model,
    teacher_model,
    test_prompts: List[str],
    verifier,
    num_steps: int = 4
) -> Dict[str, float]:
    """Benchmark few-step vs full student performance."""
    few_step_scores = []
    full_step_scores = []

    for prompt in test_prompts:
        # Few-step
        few_tokens = generate_few_step(student_model, prompt, num_steps=num_steps)
        few_text = student_model.decode(few_tokens)
        few_score = verifier(few_text)

        # Full-step (teacher)
        full_tokens = teacher_model.generate(prompt, num_steps=50)
        full_text = teacher_model.decode(full_tokens)
        full_score = verifier(full_text)

        few_step_scores.append(few_score)
        full_step_scores.append(full_score)

    return {
        'few_step_accuracy': sum(few_step_scores) / len(few_step_scores),
        'full_step_accuracy': sum(full_step_scores) / len(full_step_scores),
        'speedup': 50 / num_steps  # Approximate steps reduction
    }
```

## Practical Guidance

**When to use**: Diffusion language models needing deployment-ready few-step variants; trading some quality for 10-15× speedup

**Hyperparameters**:
- **num_student_steps**: 2-4 (speedup-quality tradeoff)
- **temperature**: 1.0-1.5 (higher = softer distributions)
- **path_consistency_order**: 'confidence' (early predictions matter more)
- **num_collection_trajectories**: 4-8 per batch

**Key advantages**:
- Addresses train-test mismatch via trajectory collection
- Mode-seeking prevents spread across multiple outputs
- Path-consistency weighting prioritizes early predictions
- Maintains quality under aggressive step reduction

**Common pitfalls**:
- Reverse-KL can underflow on very multimodal tasks
- Path weights too extreme → ignores later predictions
- Too few trajectories → limited training signal
- Not validating that early predictions actually cascade errors

**Scaling**: Linear in number of trajectories collected.

## Reference

Paper: https://arxiv.org/abs/2602.12262
Related work: Diffusion models, distillation, few-step inference, mode-seeking losses
Benchmarks: Text generation, code synthesis, reasoning tasks

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.