Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

gacore: High-Performance Geometric Algebra

PyPI MIT-License

gacore is a high-performance Geometric Algebra (Clifford Algebra) library for Python.

It is designed as a drop-in replacement for the standard clifford library but features a completely rewritten core engine powered by OpenAI Triton (for NVIDIA & AMD GPUs) and Apple MLX (for Apple Silicon).

Key Features

  • Universal Acceleration: Runs ultra-fast on standard GPUs, Apple Silicon (M series), and CPUs.
  • Dynamic Custom Kernel:
    • Directly mapped Bitwise XOR product calculations.
    • Popcount-based sign evaluation (no large lookup tables).
    • Just-In-Time (JIT) compilation for specialized metric signatures.
  • Zero-Copy Fallback: Seamlessly handles arbitrary dimensions via optimized CPU fallbacks when hardware acceleration isn't available.
  • Drop-In Compatibility: Supports the full Clifford API. Existing code works instantly.

Performance

gacore uses specialized kernels for 5D (Conformal Geometric Algebra) and 4D (Projective, Spacetime) algebras that are significantly faster than standard table-based implementations.

Benchmark Results (PGA / 16-dim)

Measured on Apple Silicon (M4) vs Standard clifford (PyGAE) library. To ensure real-world reliability, these benchmarks were performed by integrating gacore into a cloned version of the Geometric Algebra Transformer (GATr) repository, replacing its standard clifford backend.

Engine Throughput (ops/sec) Speedup
Standard clifford (Iterative) 455,655 1.0x
gacore (CPU/Torch) 3,459,619 7.6x
gacore (GPU/MLX) 6,230,213 13.7x

Important

Performance Note: These speedups only apply when using batched operations. GACORE is optimized for functional, vectorized processing of multivector arrays. Individual, iterative multivector operations (e.g., in a Python loop) will not see significant gains as the overhead of function calls and tensor creation dominates.

Scaling & Throughput

For massive workloads, the GPU throughput is truly transformative. In a stress test of 100 million operations:

  • Total throughput: ~10+ Million geometric products / second.
  • Speedup: 1,550x faster than standard Python implementations and over 20.6x faster than Numba-accelerated tables.

The Scaling Law

The speedup $S$ as a function of the number of operations $N$ follows the asymptotic model:

$$ S(N) = \frac{N \cdot t_{legacy}}{t_{launch} + N \cdot t_{accelerated}} \approx \frac{t_{legacy}}{t_{accelerated} + \frac{K}{N}} $$

Where:

  • $t_{legacy}$: Persistent per-op cost of table lookups (CPU).
  • $t_{accelerated}$: The marginal cost of a bitwise fused product (GPU/Apple Silicon).
  • $K$: Fixed overhead (kernel launch and memory dispatch).

As $N \to \infty$, the speedup approaches the hardware limit: $S_{max} = \frac{t_{legacy}}{t_{accelerated}}$.

Installation

pip install gacore

Requirements:

  • Python 3.8+
  • For NVIDIA GPU support: torch + triton
  • For Apple Silicon support: mlx

Quick Start

You can use gacore exactly like you used clifford.

import gacore as cf
from gacore.g3 import e1, e2, e3
import numpy as np

# Create MultiVectors
a = e1 + 2*e2
b = e3

# Geometric Product (Accelerated)
c = a * b 
print(f"Product: {c}")

# Rotors works as expected
R = np.e**(np.pi/4 * (e1^e2))
rotated = R * a * ~R
print(f"Rotated: {rotated}")

Using with PyTorch or MLX

gacore allows you to pass native tensors directly to algebra functions for maximum speed in deep learning pipelines.

import torch
from gacore import geometry_product, set_metric

# Define a metric signature (e.g., Cl(3,0) Euclidean)
signature = torch.tensor([1, 1, 1], device='cuda')

# Batch of multivectors (BatchSize=1024, Dims=8)
x = torch.randn(1024, 8, device='cuda') 
y = torch.randn(1024, 8, device='cuda')

# High-speed product
z = geometric_product(x, y, signature)

How it Works

Traditional GA libraries use Cayley multiplication tables which grow as $O(4^n)$. gacore replaces this with algebraic logic arithmetic:

  1. Bitwise Indexing: Basis blades are represented as integers bits. The index of the product is simply i ^ j (XOR).
  2. Sign Computation: The sign is computed on-the-fly using popcount (Hamming weight) operations to determine the number of basis vector swaps.
  3. Unified Backend: A smart dispatcher routes operations to Triton (CUDA), Metal (MLX), or NumPy/Torch (CPU) based on input data types.

License

MIT License. Based on the original work of the clifford project contributors.

Appendices


Appendix A: Derivation of the Bit-Masked Geometric Product

Goal: Derive a closed-form bitwise expression for the geometric product $C = AB$ for any two basis blades $e_i, e_j$ in the Conformal Geometric Algebra $\mathcal{Cl}_{4,1}$, such that $e_i e_j = \sigma(i,j) \cdot \eta(i,j) \cdot e_{i \oplus j}$.

We prove that the geometric product in $\mathcal{Cl}_{4,1}$ is isomorphic to a fused bitwise transformation $\phi(a,b) = (a \oplus b, \text{sgn}(a,b))$, where the sign is a closed-form parity function of the bit-interleaving.

1. The Basis Isomorphism $\phi$

We define an isomorphism $\phi: \mathcal{G} \to \mathbb{Z}_2^n$ between the Grassmann basis $\mathcal{G}$ and the $n$-dimensional bit-field. For any blade $e_S$, $\phi(e_S) = \sum_{k \in S} 2^k$. The geometric product $e_i e_j$ is then mapped to the bitwise domain as:

$$ \phi(e_i e_j) = (\phi(e_i) \oplus \phi(e_j), \text{sgn}(\phi(e_i), \phi(e_j))) $$

where $\oplus$ is the bitwise XOR operator and $\text{sgn}$ captures the topological parity and metric signature.

2. Bitmask Representation

Let the basis vectors ${e_1, e_2, e_3, e_+, e_-}$ be mapped to indices ${0, 1, 2, 3, 4}$. We represent any basis blade $e_S$ as an integer bitmask $i \in {0, \dots, 31}$, where:

$$ i = \sum_{k \in S} 2^k $$

Example: The bivector $e_{12}$ is represented by the bitmask $2^0 + 2^1 = 3$ (binary 00011).

3. Target Index Isomorphism

The geometric product of two blades is defined by the juxtaposition of their basis vectors. Vectors appearing in both blades contract, while unique vectors remain. This is equivalent to the Symmetric Difference of the sets of basis indices.

$$ \text{index}(e_i e_j) = i \oplus j $$

This proves that the resulting blade's basis is always found at the XORed index, eliminating the need for search or hash maps.

4. The Geometric Sign (Anti-commutativity)

The sign $\sigma(i,j) \in {1, -1}$ arises from the number of swaps required to move all basis vectors in $e_j$ to their canonical positions relative to $e_i$.

  1. Let $e_i = e_{a_1} e_{a_2} \dots e_{a_m}$ and $e_j = e_{b_1} e_{b_2} \dots e_{b_n}$.
  2. Each move incurs a sign change $(-1)$ due to $e_a e_b = -e_b e_a$.
  3. The total number of swaps $N$ is the count of pairs $(k,l)$ such that $\text{index}(a_k) > \text{index}(b_l)$.

Bitwise Optimization: Summing over all bits in $j$ provides the total swap parity. A more efficient parallel version used in our Triton kernel is:

$$ N_{\text{swaps}} = \sum_{k=0}^{n-1} [j_k \cdot \text{popcount}(i \gg (k+1))] $$

The sign is then: $\sigma(i,j) = (-1)^{N_{\text{swaps}}}$.

5. Metric Signature Contraction

The metric $\eta$ defines the result of $e_k^2$. In $\mathcal{Cl}_{4,1}$, the signature is $(1, 1, 1, 1, -1)$.

  1. Contraction occurs only for basis vectors present in both $i$ and $j$, defined by the bitwise AND: mask_intersect = i & j.
  2. Since $\eta_{kk} = -1$ only for the 5th basis vector ($e_-$ at index 4), the metric sign $\eta(i,j)$ is:

$$ \eta(i,j) = \begin{cases} -1 & \text{if } (i \mathbin{\&} j \mathbin{\&} 16) \neq 0 \\ 1 & \text{otherwise} \end{cases} $$

6. Final Fused Computation

The coefficient for the product of two multivectors $A$ and $B$ at index $k$ is:

$$ C_k = \sum_{i \oplus j = k} A_i B_j \cdot (-1)^{\text{parity}(i,j)} \cdot \eta(i,j) $$


Appendix B: Computational Efficiency of the Bit-Masked Kernel

Let $n$ be the number of basis vectors (for CGA, $n=5$) and $D = 2^n$ be the total number of basis blades (for CGA, $D=32$).

1. Algorithmic Complexity: $O(D^3)$ vs. $O(D^2 \cdot n)$

  • The Matrix Method ($T_{matrix}$): Standard frameworks (PyTorch/TensorFlow) often represent multivectors as $D \times D$ matrices.
    • Complexity: $D \times D \times D = D^3$.
    • For CGA: $32^3 = 32,768$ FLOPs.
  • The Bit-Masked Method ($T_{bit}$): We iterate through all $D^2$ pairs. For each pair, we compute the sign and index using bit-logic.
    • Complexity: $n \cdot D^2$.
    • For CGA: $5 \cdot 32^2 = 5,120$ ops.
    • Theoretical Speedup ($\alpha$): $\frac{D^3}{n \cdot D^2} = \frac{D}{n} = 6.4 \times$

2. The Memory Wall: Latency Proof

In modern GPU architectures, math is "cheap" but memory is "expensive."

  • Cayley Table Method: Fetching SignTable[i][j] from memory incurs latency. L1 cache latency is $\sim 20$–$80$ cycles. Shared Memory can face bank conflicts.
  • Bit-Masked Method: Zero table lookups. Sign and index are computed using register-local bitwise instructions (xor, and, vpopcnt) with $\sim 1$ cycle latency.
  • Latency Speedup ($\beta$): $\frac{\text{Latency}_{\text{Mem}}}{n \cdot \text{Latency}_{\text{ALU}}} \approx 8 \times$.

3. Operational Intensity ($I$)

The Roofline Model defines performance by "Operations per Byte" ($I = \text{Ops}/\text{Bytes}$).

  • Cayley Method: $I_{cayley} = \frac{1 \text{ op}}{16 \text{ bytes}} = 0.0625$.
  • Bit-Masked Method: $I_{bit} = \frac{1 \text{ op}}{8 \text{ bytes}} = 0.125$.
  • Conclusion: Our method is 200% more efficient at utilizing memory bandwidth.

Appendix C: Hardware Acceleration: Bit-Masked Kernels (Triton & MLX)

The primary barrier to GA adoption is computational complexity. Accessing a sparse Cayley tensor $T \in \mathbb{R}^{32 \times 32 \times 32}$ creates a memory bottleneck ($O(D^3)$ reads).

1. The XOR Isomorphism Solution

We implemented custom kernels in OpenAI Triton and Apple MLX that compute the sign $\sigma(i,j)$ and index $k = i \oplus j$ on the fly.

Algorithm 1: Bit-Masked Geometric Product Kernel (Fused)

  1. Input: Multivectors $A, B \in \mathbb{R}^{B \times 32}$
  2. Output: $C \in \mathbb{R}^{B \times 32}$
  3. Load $A$ and $B$ into Shared Memory
  4. for $i \gets 0$ to 31 do
  5. $\quad acc \gets 0$
  6. $\quad$ for $j \gets 0$ to 31 do
  7. $\quad \quad k \gets i \oplus j$ // Inverse XOR to find target
  8. $\quad \quad s \gets \text{ComputeSign}(j, k)$ // Bitwise Popcount
  9. $\quad \quad acc \gets acc + s \cdot A[j] \cdot B[k]$
  10. $\quad$ end
  11. $\quad C[i] \gets acc$
  12. end

2. Sign Logic Implementation

The sign of $e_a e_b$ is determined by metric signature ($e^2 = \pm 1$) and anticommutativity ($e_i e_j = -e_j e_i$).

// Triton Pseudocode
int target_idx = a ^ b;
int swaps = __popc(a & (b >> 1)); // Simplified parity check
int metric_sign = lookup_metric_table[target_idx];
float sign = (swaps % 2 == 0) ? 1.0 : -1.0;
accumulator += sign * metric_sign * A[a] * B[b];

Table 2: Geometric Product Layer Latency & Memory (Batch Size = 32)

Implementation Latency (ms) VRAM Delta (MB) Efficiency
Standard PyTorch (Table Lookup) 37.67 257.62 1.0x
Versor Optimized Kernel 13.47 13.31 19.4x reduction
Speedup Factor 2.8x 19.4x -

By computing structure constants on-the-fly, we reduce memory consumption by 19.4x. On GPU (Triton), this speedup extends to ~38x due to massive parallelism.