torchhd-sparsr

torchhd-sparsr

torchhd-sparsr makes Sparsr a PyTorch device. Importing it registers "sparsr" the way CUDA registers "cuda", so standard Torchhd code runs on Sparsr with .to("sparsr") and nothing else changes:

import torch
import torchhd
import torchhd_sparsr  # registers the "sparsr" device

a = torchhd.random(1, 4096, vsa="BSC", sparsity=0.998).squeeze().to("sparsr")
b = torchhd.random(1, 4096, vsa="BSC", sparsity=0.998).squeeze().to("sparsr")

bound = torchhd.bind(a, b)                    # one wide XOR on the device
similarity = torchhd.cosine_similarity(a, b)  # a wide AND on the device, counted on the host

What it runs on

The Sparsr VM. The kernels underneath are RV32I, and the VM runs RV32I. The package sets SPARSR_BACKEND=vm when it is imported, if the variable is unset, so you set nothing. An explicit SPARSR_BACKEND of your own still wins, which is how you point it at other hardware.

How you get it

pip install torchhd-sparsr

It is on PyPI as a wheel for Linux x86-64 and CPython 3.10 to 3.13. The runtime and the HDC library are bundled inside the wheel, so there is nothing else to install. It needs no hardware: it runs on the Sparsr VM, which ships in the same wheel.

Each release is built against one version of PyTorch and requires exactly that version, the same way torchvision does, because the extension uses libtorch's C++ ABI. pip resolves it for you; if you already have a different torch installed, pip tells you which version this package needs.

It computes nothing itself

Every operation is a call into the HDC library, which is bundled inside the package together with the runtime. The algorithms and the device kernels are in the library. What is in this package is the PyTorch side: registering the device, converting tensors, dispatching. So the two can never disagree about what bind means, because there is one bind.

Torchhd call In the HDC library
torchhd.bind() hdc_bind(), one wide XOR
torchhd.bundle() hdc_bundle_majority() over the two operands and the tiebreak
dot_similarity(), cosine_similarity() hdc_similarity(), one wide AND, then a count

What runs, and what refuses

Torchhd call On Sparsr
torchhd.bind() Yes. One wide XOR.
torchhd.dot_similarity(), torchhd.cosine_similarity() Partly. The intersection is a wide AND on the device, and the host counts its bits. Single pairs only.
torchhd.bundle() Refuses with RuntimeError for every real pair. Torchhd's BSC bundle needs a fair-coin tiebreak vector, which is dense across all 4,096 bits, and a dense full-width vector does not fit on the device (see below).
torchhd.permute() Refuses with NotImplementedError. It needs a wide rotate, which Sparsr does not have yet.
torchhd.multiset(), torchhd.multibundle() Refuses. The "sparsr" device holds single hypervectors, and these take a batch.

Anything else on a "sparsr" tensor, arithmetic and printing included, is not implemented. Move the tensor back with .to("cpu") first.

What fits on the device

The device stores a hypervector through a compression codec that counts non-zero 32-bit lanes, and a stored row holds 48 of the 128 lanes in a 4,096-bit register. Moving a tensor to "sparsr" checks this and raises a clear RuntimeError when it does not fit, instead of letting data corrupt quietly.

  • Bits spread over all 4,096 positions have to be sparse, about 1.5% density, which is torchhd.random(..., vsa="BSC", sparsity=0.998). That is the sparse distributed representation the device is built for, and it is what the package's own tests use.
  • Bits confined to 48 lanes can be at any density. That is a fully dense 1,536-bit hypervector, and it always fits. On MNIST that narrower dense code is worth about 65% accuracy with a plain encoding, and 79.6% with the HDC library's own example encoding.

So a dense full-width 4,096-bit BSC hypervector does not fit, and that is the whole reason bundle() refuses.