# Sparsed Sparse Attention Diffusion Lm

> Achieve up to 1.50x speedup in diffusion language models by computing head-specific sparse attention patterns once during early denoising steps and reusing them across all subsequent iterations, while preserving full attention in critical early phases to maintain generation quality and accuracy.

- Skill: `adu2021/sparsed-sparse-attention-diffusion-lm` (Agent Skill)
- Install (CLI): `npx skillmds@latest add adu2021/sparsed-sparse-attention-diffusion-lm`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adu2021/sparsed-sparse-attention-diffusion-lm/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: adu2021 (https://skillmd.com/u/adu2021)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/adu2021/sparsed-sparse-attention-diffusion-lm

---


# Accelerate Diffusion Language Models with Sparse Attention Patterns

## Problem Context

Diffusion language models (DLMs) offer a compelling alternative to autoregressive models by generating tokens in parallel rather than sequentially. However, they inherit the fundamental challenge of transformer architectures: attention's quadratic complexity O(n²) in sequence length. For a DLM performing k denoising steps on a sequence of length n, the computational cost becomes O(k·n²), making long-context inference prohibitively expensive.

Standard sparse attention techniques borrowed from autoregressive models (sliding-window, block-sparse patterns) fail to capture the unique characteristics of diffusion inference, where attention patterns evolve during the denoising process and early steps critically influence final generation quality.

## Core Concept

SparseD leverages three empirical observations about diffusion language models to enable efficient sparse attention:

1. **Head-Specific Patterns**: Different attention heads exhibit distinct sparsity structures (column-wise focusing, sliding-window behavior, or mixed patterns), requiring individualized sparse masks rather than uniform patterns across all heads.

2. **Temporal Consistency**: Attention patterns within each head remain remarkably stable across denoising steps. Once a head's sparse pattern emerges in early steps, it persists throughout the remaining iterations.

3. **Early-Step Criticality**: Initial denoising steps disproportionately impact generation quality. Applying sparse attention too early degrades output significantly, but later steps tolerate sparse computation without quality loss.

SparseD capitalizes on these insights through pattern pre-computation and reuse: compute full attention during initial "critical" steps to extract head-specific sparse patterns, then reuse these masks across all remaining steps without recalculation. This one-time pattern computation cost is amortized across many denoising iterations.

## Architecture Overview

The SparseD pipeline consists of four integrated components:

- **Isolated Selection**: For each attention head independently, compute the full attention score matrix during early steps and select the top ρ% highest-scoring query-key pairs using block-wise average pooling. This creates a head-specific sparse mask.

- **Block-Wise Pooling**: Partition the query and key sequences into fixed-size blocks. Compute average attention scores within each block to identify globally important positions without materializing the full n×n attention matrix.

- **Pattern Freezing**: After the skip% initial denoising steps complete, freeze the computed head-specific sparse masks. These masks remain constant for all subsequent denoising iterations.

- **Sparse Masking Application**: During later denoising steps, apply the precomputed sparse masks to restrict attention computation only to selected query-key pairs, implementing the sparse attention via PyTorch's FlexAttention mechanism.

## Implementation

### Step 1: Block-Wise Importance Pooling

The foundation of SparseD lies in identifying important token positions without computing full O(n²) attention matrices. This uses hierarchical pooling.

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

def compute_block_importance(attention_scores, block_size=128):
    """
    Compute block-level importance from raw attention scores.

    Args:
        attention_scores: [batch, heads, seq_len, seq_len] attention matrix
        block_size: size of blocks for pooling

    Returns:
        block_importance: [batch, heads, num_blocks] importance scores per block
    """
    batch, heads, seq_len, _ = attention_scores.shape
    num_blocks = (seq_len + block_size - 1) // block_size

    # Pad if necessary
    padded_len = num_blocks * block_size
    if padded_len != seq_len:
        pad_amount = padded_len - seq_len
        attention_scores = F.pad(attention_scores, (0, pad_amount, 0, pad_amount))

    # Reshape into blocks and compute mean importance
    blocked = attention_scores.view(batch, heads, num_blocks, block_size, num_blocks, block_size)
    # Take mean across block dimensions to get per-block importance
    block_importance = blocked.mean(dim=(3, 5))  # [batch, heads, num_blocks, num_blocks]

    return block_importance
```

### Step 2: Head-Specific Sparse Pattern Selection

After identifying block-level importance, select top-ρ% blocks for each head to create the sparse mask.

```python
def select_sparse_pattern(block_importance, selection_ratio=0.3):
    """
    Select top-ρ% blocks per head to form sparse attention mask.

    Args:
        block_importance: [batch, heads, num_blocks, num_blocks]
        selection_ratio: fraction of blocks to retain (ρ)

    Returns:
        sparse_mask: [batch, heads, num_blocks, num_blocks] boolean mask
    """
    batch, heads, num_blocks_q, num_blocks_k = block_importance.shape

    # Reshape to treat each head independently
    importance_flat = block_importance.view(batch * heads, num_blocks_q, num_blocks_k)

    # Find threshold for top-ρ% blocks
    num_keep = max(1, int(num_blocks_k * selection_ratio))

    # For each query block, select top-k key blocks
    sparse_masks = []
    for b_h in range(batch * heads):
        mask = torch.zeros(num_blocks_q, num_blocks_k, dtype=torch.bool, device=importance_flat.device)
        for q_idx in range(num_blocks_q):
            scores = importance_flat[b_h, q_idx, :]
            topk_indices = torch.topk(scores, num_keep)[1]
            mask[q_idx, topk_indices] = True
        sparse_masks.append(mask)

    sparse_mask = torch.stack(sparse_masks).view(batch, heads, num_blocks_q, num_blocks_k)
    return sparse_mask
```

### Step 3: Expand Block Mask to Token-Level Granularity

Convert the block-level sparse mask back to token-level positions for attention computation.

```python
def expand_block_mask_to_tokens(sparse_block_mask, block_size=128, seq_len=None):
    """
    Expand block-level sparse mask to token-level attention mask.

    Args:
        sparse_block_mask: [batch, heads, num_blocks_q, num_blocks_k] boolean
        block_size: original block size used
        seq_len: actual sequence length (before padding)

    Returns:
        attention_mask: [batch, heads, seq_len, seq_len] boolean
    """
    batch, heads, num_blocks_q, num_blocks_k = sparse_block_mask.shape

    # Expand blocks to tokens
    attention_mask = torch.zeros(
        batch, heads,
        num_blocks_q * block_size,
        num_blocks_k * block_size,
        dtype=torch.bool,
        device=sparse_block_mask.device
    )

    for b in range(batch):
        for h in range(heads):
            for q_block in range(num_blocks_q):
                for k_block in range(num_blocks_k):
                    if sparse_block_mask[b, h, q_block, k_block]:
                        q_start = q_block * block_size
                        q_end = q_start + block_size
                        k_start = k_block * block_size
                        k_end = k_start + block_size
                        attention_mask[b, h, q_start:q_end, k_start:k_end] = True

    # Trim to actual sequence length if needed
    if seq_len is not None:
        attention_mask = attention_mask[:, :, :seq_len, :seq_len]

    return attention_mask
```

### Step 4: Pattern Reuse Across Denoising Steps

During early denoising steps, compute and cache sparse patterns. Reuse them for all subsequent steps.

```python
class SparseAttentionCache:
    """Cache and manage sparse attention patterns across denoising steps."""

    def __init__(self, skip_ratio=0.2, selection_ratio=0.3, block_size=128):
        self.skip_ratio = skip_ratio  # Fraction of steps to use full attention
        self.selection_ratio = selection_ratio  # Sparsity level ρ
        self.block_size = block_size
        self.sparse_masks = {}  # Cache: (batch_id, head_id) -> mask
        self.pattern_frozen = False

    def should_use_full_attention(self, step, total_steps):
        """Determine if current step should use full attention."""
        skip_threshold = int(total_steps * self.skip_ratio)
        return step < skip_threshold

    def get_or_compute_mask(self, attention_scores, step, total_steps, seq_len):
        """
        Get cached sparse mask or compute new one if in early steps.

        Args:
            attention_scores: [batch, heads, seq_len, seq_len] full attention
            step: current denoising step (0-indexed)
            total_steps: total number of denoising steps
            seq_len: sequence length

        Returns:
            mask: sparse attention mask, or None if full attention should be used
        """
        if self.should_use_full_attention(step, total_steps):
            # Compute patterns during early steps
            block_importance = compute_block_importance(attention_scores, self.block_size)
            sparse_block_mask = select_sparse_pattern(block_importance, self.selection_ratio)
            sparse_mask = expand_block_mask_to_tokens(sparse_block_mask, self.block_size, seq_len)
            self.sparse_masks[step] = sparse_mask
            return None  # Use full attention
        else:
            # Reuse cached pattern from first post-skip step
            if not self.pattern_frozen:
                skip_threshold = int(total_steps * self.skip_ratio)
                if skip_threshold in self.sparse_masks:
                    self.pattern_frozen = True

            if self.pattern_frozen and skip_threshold in self.sparse_masks:
                return self.sparse_masks[skip_threshold]
            return None
```

### Step 5: Integration with Forward Pass

Apply sparse attention during the actual attention computation using PyTorch's FlexAttention.

```python
from torch.nn.attention.flex_attention import flex_attention, create_block_mask

def apply_sparse_attention(
    query, key, value,
    sparse_mask,
    block_size=128
):
    """
    Apply sparse attention using FlexAttention.

    Args:
        query: [batch, heads, seq_len, dim]
        key: [batch, heads, seq_len, dim]
        value: [batch, heads, seq_len, dim]
        sparse_mask: [batch, heads, seq_len, seq_len] boolean
        block_size: block size for FlexAttention

    Returns:
        output: [batch, heads, seq_len, dim] attention output
    """
    batch, heads, seq_len, dim = query.shape

    # Convert boolean mask to FlexAttention block mask format
    block_mask = create_block_mask(sparse_mask, batch, heads, seq_len, seq_len)

    # Compute sparse attention
    output = flex_attention(query, key, value, block_mask=block_mask)

    return output

def diffusion_forward_with_sparse_attention(
    model, input_ids, noise_level, step, total_steps,
    sparse_cache
):
    """
    Forward pass for diffusion model with optional sparse attention.

    Args:
        model: diffusion language model
        input_ids: token indices
        noise_level: diffusion noise level
        step: current denoising step
        total_steps: total denoising iterations
        sparse_cache: SparseAttentionCache instance

    Returns:
        logits: output predictions
    """
    # Get embeddings
    hidden = model.embed(input_ids)

    # Add noise level information
    hidden = model.add_noise_embedding(hidden, noise_level)

    seq_len = hidden.shape[1]

    # Apply transformer blocks with sparse/full attention
    for layer in model.transformer_blocks:
        # Standard pre-norm setup
        norm_hidden = layer.norm1(hidden)

        # Attention computation
        q, k, v = layer.attention.qkv(norm_hidden)

        # Check if we should use sparse attention
        use_full_attention = sparse_cache.should_use_full_attention(step, total_steps)

        if use_full_attention:
            # Compute full attention and extract/cache patterns
            scores = torch.matmul(q, k.transpose(-2, -1)) / (q.shape[-1] ** 0.5)
            sparse_mask = sparse_cache.get_or_compute_mask(scores, step, total_steps, seq_len)
            attn_output = torch.matmul(
                torch.softmax(scores, dim=-1), v
            )
        else:
            # Use cached sparse pattern
            sparse_mask = sparse_cache.get_or_compute_mask(None, step, total_steps, seq_len)
            if sparse_mask is not None:
                attn_output = apply_sparse_attention(q, k, v, sparse_mask)
            else:
                # Fallback to full attention
                scores = torch.matmul(q, k.transpose(-2, -1)) / (q.shape[-1] ** 0.5)
                attn_output = torch.matmul(torch.softmax(scores, dim=-1), v)

        # Projection and residual
        hidden = hidden + layer.attention.out_proj(attn_output)

        # FFN
        hidden = hidden + layer.ffn(layer.norm2(hidden))

    logits = model.output_projection(hidden)
    return logits
```

## Practical Guidance

### Hyperparameter Selection Table

| Parameter | Default | Range | Guidance |
|-----------|---------|-------|----------|
| skip | 0.20 | 0.10-0.30 | Higher values preserve quality; lower values increase speedup. Start at 0.20. |
| selection_ratio (ρ) | 0.30 | 0.15-0.50 | Lower ratios increase speedup but risk quality loss. Test on your task. |
| block_size | 128 | 64-256 | Larger blocks reduce memory but may miss fine-grained patterns. 128 is balanced. |
| total_steps | 1024 | 64-2048 | More steps allow better pattern amortization; cost-benefit peaks around 1000. |

### When to Use SparseD

- **Long-context inference** (>8k tokens): Quadratic attention becomes bottleneck; SparseD delivers maximum gains.
- **High-latency-sensitive applications**: Real-time chat, streaming generation where sub-second responses matter.
- **Batch inference at scale**: Multiple concurrent requests; sparse patterns reduce per-sample computation.
- **Models with stable attention patterns**: Dream, LLaDA, and similar diffusion models show consistent head-specific patterns.
- **Tasks tolerating small quality trade-offs**: Some domains (summarization, paraphrasing) show negligible degradation.

### When NOT to Use SparseD

- **Very short sequences** (<2k tokens): Sparse attention overhead exceeds quadratic attention cost.
- **Quality-critical tasks requiring perfect preservation**: Machine translation, instruction-following, or code generation where even 0.1% accuracy loss is unacceptable.
- **Early-stage denoising dominance**: If your model heavily depends on the first 5% of denoising steps, applying sparsity too late may still hurt.
- **Heterogeneous attention patterns**: Models where different samples exhibit completely different attention structures won't benefit from frozen masks.
- **Single-pass inference**: SparseD amortizes cost across steps; single-pass inference (e.g., length-1 generation) sees no benefit.

### Common Pitfalls and Mitigations

1. **Setting skip too low**: Applying sparse attention in early critical steps causes 3-5% accuracy degradation. If you see sharp quality loss, increase skip to 0.25-0.30.

2. **Frozen patterns on distribution shift**: If your model receives inputs very different from training distribution, cached patterns may become stale. Periodically recompute if input style changes significantly.

3. **Block size misalignment**: Using block_size that doesn't divide sequence length evenly causes padding overhead. Ensure block_size divides your typical seq_len evenly.

4. **FlexAttention compilation overhead**: First inference includes compilation cost (~100-200ms). Always run a warm-up pass to amortize this cost.

5. **Memory spikes during pattern computation**: Early steps still compute full attention. Ensure GPU memory accommodates batch_size × heads × seq_len² × 4 bytes temporarily.

6. **Attention head heterogeneity assumptions**: SparseD assumes patterns stabilize; if heads are highly noisy or unstable, quality may degrade. Validate on a small sample first.

### Typical Configuration for Different Scenarios

**Long-context summarization (64k tokens, 1024 steps)**:
```
skip=0.20, selection_ratio=0.30, block_size=128
Expected: 1.35-1.50x speedup, <0.5% accuracy loss
```

**Interactive chat (4-8k tokens, 256 steps)**:
```
skip=0.25, selection_ratio=0.35, block_size=64
Expected: 1.15-1.25x speedup, negligible quality impact
```

**Code generation (8-16k tokens, 512 steps)**:
```
skip=0.30, selection_ratio=0.25, block_size=256
Expected: 1.20-1.35x speedup, validate on benchmarks
```

## Reference

**Paper**: Wang, Z., Fang, G., Ma, X., Yang, X., & Wang, X. (2025). "SparseD: Sparse Attention for Diffusion Language Models." *ICLR 2026*.

**ArXiv**: https://arxiv.org/abs/2509.24014

**Code**: https://github.com/INV-WZQ/SparseD

**Implementation Foundation**: PyTorch FlexAttention (torch.nn.attention.flex_attention)

**Related Work**: Sliding-window attention, sparse transformers, attention pattern analysis in diffusion models

