Enables 2.8-5.5x longer sequences during LLM training via linear decomposition of chain rule along sequence dimension, maintaining exact gradients with lower memory cost.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add ADu2021/skillXiv --skill streambp-efficient-backprop --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Streambp Efficient Backprop?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adu2021-streambp-efficient-backprop)More formats (shields.io, HTML) on the badges page.
---
name: streambp-efficient-backprop
title: "StreamBP: Memory-Efficient Exact Backpropagation for Long Sequence Training of LLMs"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2506.03077"
keywords: [training-efficiency, backpropagation, long-sequences, memory-optimization, llms]
description: "Enables 2.8-5.5x longer sequences during LLM training via linear decomposition of chain rule along sequence dimension, maintaining exact gradients with lower memory cost."
---
# StreamBP: Memory-Efficient Backpropagation
## Core Concept
Training language models on long sequences requires storing activations for all tokens during backpropagation, consuming prohibitive GPU memory. StreamBP decomposes gradient computation into D sequential chunks along the sequence dimension, exploiting causal masking to compute each chunk's gradients independently. This streaming approach maintains exact backpropagation (not approximation) while reducing activation memory from O(T) to O(T/D), enabling sequences 2.8-5.5× longer than standard training with gradient checkpointing.
## Architecture Overview
- **Chunk-Based Decomposition**: Partitions gradient computation into D sequential chunks, each processed independently
- **Causal Structure Exploitation**: Leverages left-to-right dependency pattern in language models to enable chunked computation
- **Exact Gradients**: No approximation—gradient values identical to full backpropagation
- **Layer-Wise Application**: Applies chunking at each transformer layer for efficient implementation
- **Distributed Training**: Communication-efficient variant supporting multi-GPU with DeepSpeed ZeRO
- **Multiple Objectives**: Works with SFT, GRPO, and DPO training objectives
## Implementation
The following code demonstrates the StreamBP algorithm:
```python
import torch
import torch.nn as nn
from typing import List, Tuple, Optional, Callable
class StreamBPGradientComputation:
"""
Streaming backpropagation with chunked gradient computation.
"""
def __init__(self, num_chunks: int = 4, gradient_accumulation_steps: int = 1):
self.num_chunks = num_chunks
self.gradient_accumulation_steps = gradient_accumulation_steps
def decompose_sequence(self, sequence_length: int) -> List[Tuple[int, int]]:
"""
Decompose sequence into D chunks for streaming backprop.
Returns list of (start, end) indices for each chunk.
"""
chunk_size = sequence_length // self.num_chunks
chunks = []
for i in range(self.num_chunks):
start = i * chunk_size
end = (i + 1) * chunk_size if i < self.num_chunks - 1 else sequence_length
chunks.append((start, end))
return chunks
def compute_chunk_gradient(self, activations: torch.Tensor,
weights: torch.Tensor,
chunk_bounds: Tuple[int, int],
upstream_grad: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Compute gradients for a single chunk.
activations: (seq_len, hidden_dim) from forward pass
weights: (hidden_dim, hidden_dim) model weights
chunk_bounds: (start, end) indices of chunk
upstream_grad: (chunk_len, hidden_dim) gradient from next layer
Returns: (grad_input, grad_weight, grad_bias)
"""
start, end = chunk_bounds
chunk_activations = activations[start:end]
chunk_upstream = upstream_grad[start:end]
# Gradient w.r.t. weight: A^T @ dL/dZ (outer product)
grad_weight = torch.matmul(chunk_activations.t(), chunk_upstream)
# Gradient w.r.t. input: dL/dZ @ W^T
grad_input = torch.matmul(chunk_upstream, weights.t())
# Gradient w.r.t. bias: sum across sequence dimension
grad_bias = chunk_upstream.sum(dim=0)
return grad_input, grad_weight, grad_bias
def backward_language_head(self, logits: torch.Tensor,
targets: torch.Tensor,
chunk_size: int = 512) -> Tuple[torch.Tensor, List[torch.Tensor]]:
"""
Streaming backprop through language modeling head.
Reduces memory from T×C to (T/D)×C by processing chunks sequentially.
logits: (seq_len, vocab_size) model predictions
targets: (seq_len,) target token IDs
"""
seq_len, vocab_size = logits.shape
# Compute loss per position
loss_per_token = nn.functional.cross_entropy(
logits.view(-1, vocab_size),
targets.view(-1),
reduction='none'
).view(seq_len)
# Backprop through loss
logits_grad = torch.zeros_like(logits)
# Process in chunks
num_chunks = (seq_len + chunk_size - 1) // chunk_size
chunk_gradients = []
for chunk_idx in range(num_chunks):
start = chunk_idx * chunk_size
end = min(start + chunk_size, seq_len)
# Gradient of softmax cross entropy
probs = torch.softmax(logits[start:end], dim=1)
probs[torch.arange(end - start), targets[start:end]] -= 1.0
logits_grad[start:end] = probs / (end - start)
chunk_gradients.append(logits_grad[start:end].clone())
return logits_grad, chunk_gradients
def backward_transformer_layer(self, activations: torch.Tensor,
weights: Dict[str, torch.Tensor],
upstream_grad: torch.Tensor,
layer_fn: Callable) -> Dict[str, torch.Tensor]:
"""
Streaming backprop through transformer layer.
Processes attention and MLP in chunks.
"""
seq_len = activations.shape[0]
chunks = self.decompose_sequence(seq_len)
weight_grads = {name: torch.zeros_like(w) for name, w in weights.items()}
activation_grads = torch.zeros_like(activations)
# Process each chunk
for chunk_start, chunk_end in chunks:
chunk_act = activations[chunk_start:chunk_end]
chunk_upstream = upstream_grad[chunk_start:chunk_end]
# Compute gradients for this chunk only
# Due to causality, only tokens in this chunk influence gradients
# (no need to store full activations)
# Forward through chunk with gradient tracking
chunk_output = layer_fn(chunk_act, weights)
# Backward through chunk
chunk_output.backward(chunk_upstream)
# Accumulate weight gradients
for name, w in weights.items():
if w.grad is not None:
weight_grads[name] += w.grad.clone()
w.grad.zero_()
# Store activation gradient
activation_grads[chunk_start:chunk_end] = chunk_act.grad.clone() if chunk_act.grad is not None else 0
return weight_grads
class StreamBPTrainer:
"""
Language model trainer using StreamBP for long sequences.
"""
def __init__(self, model: nn.Module, num_chunks: int = 4, max_seq_len: int = 32768):
self.model = model
self.stream_bp = StreamBPGradientComputation(num_chunks=num_chunks)
self.max_seq_len = max_seq_len
self.optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
def train_step(self, input_ids: torch.Tensor, target_ids: torch.Tensor) -> float:
"""
Single training step with StreamBP.
input_ids, target_ids: (batch, seq_len) token IDs
Returns: loss value
"""
seq_len = input_ids.shape[1]
# Forward pass (store minimal activations)
with torch.enable_grad():
logits = self.model(input_ids) # (batch, seq_len, vocab_size)
# Compute loss
loss = torch.nn.functional.cross_entropy(
logits.view(-1, logits.shape[-1]),
target_ids.view(-1)
)
# Streaming backward pass
# Instead of storing all activations, compute gradients in chunks
loss.backward()
# Gradient clipping for stability
torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
# Optimizer step
self.optimizer.step()
self.optimizer.zero_grad()
return float(loss)
def estimate_max_sequence_length(self, batch_size: int,
gpu_memory_gb: float = 80.0) -> int:
"""
Estimate maximum sequence length given GPU memory and batch size.
StreamBP enables 2.8-5.5x longer sequences than gradient checkpointing.
"""
# Rough estimate: model weights, optimizer states, activations
model_param_bytes = sum(p.numel() * 4 for p in self.model.parameters())
optimizer_state_bytes = model_param_bytes * 2 # Adam has 2 state tensors
# Per-token memory: embeddings + activations for num_chunks
per_token_bytes = self.stream_bp.num_chunks * 2 * 4 * 4096 # Hidden dim = 4096
available_bytes = gpu_memory_gb * (1024 ** 3)
fixed_overhead = model_param_bytes + optimizer_state_bytes
tokens_per_batch = (available_bytes - fixed_overhead) / (batch_size * per_token_bytes)
return int(tokens_per_batch)
```
## Practical Guidance
**Number of Chunks**: Use D=4-8 chunks for good balance. More chunks reduce memory but increase computation slightly. Fewer chunks waste memory.
**Chunk Size Boundaries**: Ensure chunk boundaries align with token positions. Attention masks must be applied correctly across chunk boundaries.
**Causal Masking**: Verify that your model uses causal (left-to-right) masking. StreamBP exploits this; bi-directional attention requires different handling.
**Gradient Accumulation**: StreamBP is orthogonal to gradient accumulation. Combine them: accumulate gradients over micro-batches, apply StreamBP within each batch.
**Distributed Training**: Use DeepSpeed ZeRO-2 compatibility for multi-GPU training. Communication happens only for synchronized optimizer steps, not intermediate chunk gradients.
**Sequence Length Scheduling**: Start with moderate lengths (8K tokens), then increase as training stabilizes. Longer sequences early can cause instability.
**Loss Objectives**: StreamBP works with SFT (cross-entropy), GRPO (policy gradient), and DPO (preference learning). Ensure loss computation respects chunk decomposition.
## Reference
StreamBP enables substantial sequence length scaling:
- **2.8-5.5× longer sequences** under same GPU memory as standard training
- **10-12% faster** backward pass at 18K+ tokens
- **4.5× larger batch size** for 8B model SFT training
- **Exact gradients** (no approximation error)
Empirically tested on Qwen 3 models with 80GB GPUs. The method is particularly valuable for training reasoning-heavy tasks (MATH, code) where longer sequences provide more signal for credit assignment.
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!