Install hyper-connections
mainInstall the library using pip:
$ pip install hyper-connectionsrepository·main·Indexed 41 days ago
https://github.com/lucidrains/hyper-connectionsA PyTorch library implementing multiple parallel residual streams and Fractionated Feature Dimensions based on research from Bytedance AI lab. It provides tools to replace standard residual streams with hyper-connections, including support for manifold constraints (mHC), channel-first data, and multi-branch inputs. Version 0.4.11.
Install the library using pip:
$ pip install hyper-connectionsHyper-Connections allow you to replace standard residual streams with multiple parallel streams as proposed in the Hyper-Connections paper.
To implement this, follow these four steps:
get_init_and_expand_reduce_stream_functions to get helper functions, then wrap your existing branch layer (e.g., nn.Linear) using the returned init_hyper_conn function.expand_stream on your residual tensor to expand it into multiple streams.reduce_stream to sum the multiple streams back into a single residual tensor.Note: For Transformers, the exact placement of the reduce_stream (before or after the final norm) is a research consideration.
import torch
from torch import nn
from hyper_connections import get_init_and_expand_reduce_stream_functions
# Setup
dim = 512
branch = nn.Linear(dim, dim)
residual = torch.randn(2, 1024, dim)
num_streams = 4
# Get helper functions
init_hyper_conn, expand_stream, reduce_stream = get_init_and_expand_reduce_stream_functions(num_streams)
# 1. Wrap the branch
hyper_conn_branch = init_hyper_conn(dim = dim, branch = branch)
# 2. Expand to multiple streams
residual = expand_stream(residual)
# 3. Forward through wrapped branch
residual = hyper_conn_branch(residual)
# 4. Reduce streams with summation
residual = reduce_stream(residual)The Residual class is a base implementation for simple residual connections. It provides a decorate_branch method that wraps a standard branch function into a forward_and_add_residual function. This ensures that the output of the branch is automatically added back to the residual stream.
It supports:
branch: The module to apply.residual_transform: A module (like a projection) applied to the residual before the connection.width_connection: Defines how residuals are prepared for the branch.depth_connection: Defines how the branch output is merged back into the residuals.from hyper_connections.manifold_constrained_hyper_connections import Residual
import torch.nn as nn
class MyResidualBlock(Residual):
def __init__(self, dim, **kwargs):
super().__init__(dim = dim, **kwargs)
def forward(self, residual, *args, **kwargs):
# This uses the decorated branch logic
return self.forward_and_add_residual(residual, *args, **kwargs)
# Usage
block = MyResidualBlock(dim=512, branch=nn.Linear(512, 512))
res = torch.randn(8, 512)
new_res = block(res)To implement the hyper-connection pattern in a model (e.g., a Transformer), follow these steps:
HyperConnections module or its decorated functions.for loop or a single expand_fn call.Note for Transformers: It is currently unclear whether the reduction should occur before or after the final normalization layer.
Hyper-connections operate through two primary phases: Width Connection and Depth Connection.
Before the branch module is executed, the multiple residual streams are mixed into a single input stream for the branch.
alpha weights. These weights determine how much each residual stream contributes to the branch_input.additional_input_paths are provided, these extra streams are also projected and mixed into the process.branch_input is produced, and the original residuals are transformed into a mixed state.After the branch module produces an output, it must be distributed back to the individual residual streams.
beta weights.beta and added back to each of the individual residual streams.(b*s, ..., d) format.The hyper-connection mechanism follows these steps:
num_branch_inputs. This creates a set of inputs that are then expanded to be processed by the branches.Mental Model for Implementation:
expand_fn).reduce_fn).When using the HyperConnections class directly, it encapsulates these steps via forward().
mHCv2 manages the flow of information between multiple residual streams and a central branch module using two types of connections:
Width Connection (Residuals $\to$ Branch):
Depth Connection (Branch $\to$ Residuals):
This mechanism allows the model to dynamically decide which parts of the residual memory are most relevant for the current computation and how the computation should update that memory.
If you need more control than the wrapped branch approach, you can implement the connection manually. This allows you to access the learned betas used for the residual addition.
init_hyper_conn(dim=...).expand_stream.branch_input (the input to your branch layer) and add_residual (a function that performs the learned residual addition).branch_input through your branch layer, then pass the result to add_residual.reduce_stream after your trunk loop.Alternatively, you can use the shorthand: residual = hyper_conn.decorate_branch(branch)(residual).
from hyper_connections import get_init_and_expand_reduce_stream_functions
# ... setup dim, branch, residual, num_streams ...
init_hyper_conn, expand_stream, reduce_stream = get_init_and_expand_reduce_stream_functions(4)
# 1. Instantiate
hyper_conn = init_hyper_conn(dim = 512)
# 2. Expand
residual = expand_stream(residual)
# 3. Manual forward with learned betas
branch_input, add_residual = hyper_conn(residual)
branch_output = branch(branch_input)
residual = add_residual(branch_output)
# 4. Reduce
residual = reduce_stream(residual)The get_init_and_expand_reduce_stream_functions function is the primary entry point for configuring Hyper-Connections. It accepts the following arguments:
num_streams (int): The number of parallel residual streams to create.disable (bool, default: False): If set to True, the returned functions will behave like standard plain residuals, allowing you to compare Hyper-Connections against a baseline without changing your model architecture code.num_fracs (int, default: 1): Used to implement Fractionated Feature Dimensions. Setting this to a value greater than 1 allows you to mix streams and fractions of the feature dimension, as proposed in the Frac-Connections paper.# Disable hyper-connections to use plain residuals for comparison
init, expand, reduce = get_init_and_expand_reduce_stream_functions(4, disable = True)
# Use fractionated feature dimensions
init, expand, reduce = get_init_and_expand_reduce_stream_functions(1, num_fracs = 4)The ManifoldConstrainedHyperConnections module uses Sinkhorn-Knopp iterations to constrain the mixing weights $\alpha$.
sinkhorn_iters: Controls the precision of the manifold constraint. Higher values lead to more accurate doubly stochastic matrices but increase computational cost.log_domain_sinkhorn: If set to True, the Sinkhorn algorithm is performed in the log-domain using F.log_softmax, which is more numerically stable.use_triton_sinkhorn: If available, uses a Triton-based implementation for faster Sinkhorn iterations.The ViT class supports different pooling strategies and embedding configurations:
pool: Determines how the sequence is aggregated before the MLP head. Supported values are:'cls': Uses the [CLS] token (default if num_cls_tokens is 1).'mean': Uses mean pooling across the sequence dimension.channels: The number of input image channels (default is 3).emb_dropout: Dropout applied to the patch and position embeddings.The StreamEmbed class is used to expand a single residual stream into multiple streams while adding unique, learnable embeddings to each stream. This helps the model distinguish between different residual lanes.
expand_to_streams=True: Repeats the input and adds the embedding.channel_first: If True, handles dimensions as (b, d, ...) instead of (b, ..., d).from hyper_connections.manifold_constrained_hyper_connections import StreamEmbed
import torch
num_streams = 4
dim = 512
embedder = StreamEmbed(num_streams = num_streams, dim = dim)
# Input: (batch, seq, dim)
res = torch.randn(8, 10, 512)
# Output: (batch * num_streams, seq, dim)
res_expanded = embedder(res)