Positional Encoding¶
Position-aware layers for hyperbolic Transformers on the hyperboloid. HOPE
(hope / HyperbolicRoPE) is a deterministic rotary encoding with no learnable
parameters that preserves relative position (⟨HOPE(q,i), HOPE(k,j)⟩_L depends only
on i-j); HypformerPositionalEncoding is learnable (HTCLinear + Lorentzian
residual). For when to prefer each, see the
NN Layers guide.
The lorentz_residual / lorentz_scale functions that back the residual skip
connection are documented under Primitives; the LorentzResidual
NNX module wrapper is here.
HOPE (Hyperbolic Rotary Positional Encoding)¶
hyperbolix.nn_layers.hope ¶
hope(
z: Float[Array, "... seq d_plus_1"],
positions: Float[Array, seq],
c: float = 1.0,
base: float = 10000.0,
eps: float = 1e-07,
) -> Float[Array, "... seq d_plus_1"]
Hyperbolic Rotary Positional Encoding (HOPE).
Applies RoPE-style rotation to the spatial components of hyperboloid
points, then reconstructs the time component to satisfy the manifold
constraint. Equivalent to hrc(z, R_{i,Theta}, c, c) where R is a
block-diagonal rotation matrix.
Since rotation preserves norms, the Minkowski inner product between encoded points depends only on the relative position offset, giving the standard RoPE relative-position property on the hyperboloid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
z
|
(Array, shape(..., seq_len, d + 1))
|
Points on hyperboloid (d must be even). |
required |
positions
|
(Array, shape(seq_len))
|
Integer position indices. |
required |
c
|
float
|
Curvature parameter (default: 1.0). |
1.0
|
base
|
float
|
Frequency base for rotation angles (default: 10000.0). |
10000.0
|
eps
|
float
|
Numerical stability floor (default: 1e-7). |
1e-07
|
Returns:
| Type | Description |
|---|---|
(Array, shape(..., seq_len, d + 1))
|
Rotated points on hyperboloid with curvature c. |
References
Chen et al., "Hyperbolic Embeddings for Learning on Manifolds" (HELM), 2024.
Source code in hyperbolix/nn_layers/hyperboloid_positional.py
hyperbolix.nn_layers.HyperbolicRoPE ¶
Bases: Module
NNX module wrapper for HOPE (Hyperbolic Rotary Positional Encoding).
This is a stateless module (no learnable parameters) that wraps the
functional :func:hope for convenient use in NNX model definitions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
int
|
Spatial dimension d (must be even). |
required |
max_seq_len
|
int
|
Maximum sequence length (for documentation; not enforced, default: 2048). |
2048
|
base
|
float
|
Frequency base for rotation angles (default: 10000.0). |
10000.0
|
eps
|
float
|
Numerical stability floor (default: 1e-7). |
1e-07
|
References
Chen et al., "Hyperbolic Embeddings for Learning on Manifolds" (HELM), 2024.
Source code in hyperbolix/nn_layers/hyperboloid_positional.py
__call__ ¶
__call__(
z: Float[Array, "... seq d_plus_1"],
positions: Float[Array, seq],
c: float = 1.0,
) -> Float[Array, "... seq d_plus_1"]
Apply HOPE positional encoding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
z
|
(Array, shape(..., seq_len, d + 1))
|
Points on hyperboloid (spatial dim must equal self.dim, must be even). |
required |
positions
|
(Array, shape(seq_len))
|
Integer position indices. |
required |
c
|
float
|
Curvature parameter (default: 1.0). |
1.0
|
Returns:
| Type | Description |
|---|---|
(Array, shape(..., seq_len, d + 1))
|
Rotated points on hyperboloid. |
Source code in hyperbolix/nn_layers/hyperboloid_positional.py
Hypformer Positional Encoding¶
hyperbolix.nn_layers.HypformerPositionalEncoding ¶
HypformerPositionalEncoding(
in_features: int,
out_features: int,
*,
rngs: Rngs,
epsilon: float = 1.0,
init_bound: float = 0.02,
eps: float = 1e-07,
)
Bases: Module
Relative positional encoding from Hypformer.
Computes a position vector via HTCLinear, then combines it with the input using a Lorentzian residual connection:
p = HTCLinear(x)
result = lorentz_residual(x, p, w_y=epsilon, c=c)
where epsilon is a FIXED scalar weight on the position contribution. The
Hypformer reference keeps it a plain (non-learnable) tensor fixed at 1.0;
making it a trainable parameter is unsafe because gradient descent can
drive it below -1, where x + epsilon * p leaves the upper hyperboloid
sheet and the abs() in the residual normalizer silently masks the
violation (see :func:~hyperbolix.nn_layers.hyperboloid_core.lorentz_residual).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
in_features
|
int
|
Input ambient dimension (d+1, including time component). |
required |
out_features
|
int
|
Output spatial dimension (d). The HTCLinear output will have ambient dimension d+1 (= out_features + 1), matching the input. |
required |
rngs
|
Rngs
|
Random number generators for parameter initialization. |
required |
epsilon
|
float
|
Fixed scalar weight for the position encoding contribution (default: 1.0, matching the Hypformer reference). Must be >= 0 so the Lorentzian residual stays on the upper hyperboloid sheet. |
1.0
|
init_bound
|
float
|
Bound for HTCLinear uniform weight initialization (default: 0.02). |
0.02
|
eps
|
float
|
Numerical stability floor for lorentz_residual (default: 1e-7). |
1e-07
|
Attributes:
| Name | Type | Description |
|---|---|---|
htc_linear |
HTCLinear
|
Linear transformation producing the position encoding vector. |
epsilon |
float
|
Fixed scalar weight for the position encoding contribution. |
eps |
float
|
Numerical stability parameter. |
References
Chen et al., "Hyperbolic Embeddings for Learning on Manifolds" (HELM), 2024. Hypformer paper (citation to be added).
Source code in hyperbolix/nn_layers/hyperboloid_positional.py
__call__ ¶
Apply learnable positional encoding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
(Array, shape(..., d + 1))
|
Points on hyperboloid with curvature c. |
required |
c
|
float
|
Curvature parameter (default: 1.0). |
1.0
|
Returns:
| Type | Description |
|---|---|
(Array, shape(..., d + 1))
|
Positionally-encoded points on hyperboloid with curvature c. |
Source code in hyperbolix/nn_layers/hyperboloid_positional.py
Why epsilon is not learnable
The Hypformer reference keeps epsilon a plain (non-trainable) tensor fixed at
1.0. Making it a parameter is unsafe: gradient descent can drive it below -1,
where x + epsilon·p leaves the upper hyperboloid sheet and the abs() in the
Lorentzian residual normalizer silently masks the violation instead of raising.
For the same reason, lorentz_residual's w_y must always be non-negative.
Lorentzian residual connection¶
hyperbolix.nn_layers.LorentzResidual ¶
LorentzResidual(
*,
init_w_y: float = 1.0,
learnable_weight: bool = True,
scale: bool = False,
init_gamma: float = 2.0,
learnable_scale: bool = False,
param_dtype: DTypeLike = jnp.float32,
eps: float = 1e-07,
)
Bases: Module
LResNet residual (skip) connection with a safely-constrained weight.
Computes the weighted Lorentzian midpoint of a skip branch x and a
residual branch y (the latter weighted by w_y), optionally rescaling
the result along the geodesic ray from the origin (LResNet Eq. 10)::
out = lorentz_residual(x, y, w_y, c) # weighted Lorentzian midpoint
if scale:
out = lorentz_scale(out, gamma, c) # optional Eq. 10 norm control
Both x and y are points on the hyperboloid with curvature c; the
caller produces y (e.g. via a conv / linear layer). This mirrors how
:class:~hyperbolix.nn_layers.HypformerPositionalEncoding composes a
transform with lorentz_residual -- except that layer keeps its weight
fixed (a trainable position weight is unsafe there), whereas this module
makes a trainable w_y safe via the softplus reparameterization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
init_w_y
|
float
|
Initial value of the residual-branch weight |
1.0
|
learnable_weight
|
bool
|
If |
True
|
scale
|
bool
|
If |
False
|
init_gamma
|
float
|
Initial value of the scaling constant |
2.0
|
learnable_scale
|
bool
|
If |
False
|
param_dtype
|
DTypeLike
|
Storage dtype of any learnable raw parameter (default: |
float32
|
eps
|
float
|
Numerical stability floor passed to |
1e-07
|
Attributes:
| Name | Type | Description |
|---|---|---|
w_y_raw |
Param or None
|
Raw (pre-softplus) weight when |
gamma_raw |
Param or None
|
Raw (pre-softplus) scaling constant when |
use_scale |
bool
|
Whether the Eq. 10 scaling is applied. |
References
He, Neil, Menglin Yang, and Rex Ying. "Lorentzian residual neural networks."
Proceedings of the 31st ACM SIGKDD Conference on Knowledge Discovery and Data Mining V. 1. 2025.
Residual: :func:~hyperbolix.nn_layers.hyperboloid_core.lorentz_residual;
Eq. 10 scaling: :func:~hyperbolix.nn_layers.hyperboloid_core.lorentz_scale.
Source code in hyperbolix/nn_layers/hyperboloid_residual.py
__call__ ¶
__call__(
x: Float[Array, "... dim_plus_1"],
y: Float[Array, "... dim_plus_1"],
c: float = 1.0,
) -> Float[Array, "... dim_plus_1"]
Apply the (optionally scaled) Lorentzian residual connection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
(Array, shape(..., d + 1))
|
Skip / main branch on the hyperboloid with curvature |
required |
y
|
(Array, shape(..., d + 1))
|
Residual branch on the hyperboloid with curvature |
required |
c
|
float
|
Curvature parameter (default: 1.0). |
1.0
|
Returns:
| Type | Description |
|---|---|
(Array, shape(..., d + 1))
|
Points on the hyperboloid with curvature |
Source code in hyperbolix/nn_layers/hyperboloid_residual.py
Example¶
import jax, jax.numpy as jnp
from flax import nnx
from hyperbolix.nn_layers import hope, HyperbolicRoPE
from hyperbolix.manifolds import Hyperboloid
batch, seq_len, d = 4, 16, 8
spatial = jax.random.normal(jax.random.PRNGKey(42), (batch, seq_len, d)) * 0.1
time = jnp.sqrt(jnp.sum(spatial**2, axis=-1, keepdims=True) + 1.0)
z = jnp.concatenate([time, spatial], axis=-1) # (4, 16, 9)
positions = jnp.arange(seq_len)
z_enc = hope(z, positions, c=1.0) # functional interface
rope = HyperbolicRoPE(dim=d, max_seq_len=64, base=10000.0) # or the NNX module
z_enc = rope(z, positions, c=1.0)
print(z_enc.shape) # (4, 16, 9)