Skip to main content

torchhd-sparsr

A Torchhd backend that runs Vector Symbolic Architecture / Hyperdimensional Computing operations on the Sparsr processor instead of the host CPU.

Importing the package registers "sparsr" as a real PyTorch device (via PrivateUse1) and loads the bundled Sparsr host runtime automatically. From there, standard Torchhd code runs on Sparsr the same way it would run on CUDA -- no torchhd_sparsr-specific API, just .to("sparsr"):

import torch
import torchhd
import torchhd_sparsr

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")

result = torchhd.bind(a, b)              # Sparsr's WXOR instruction
similarity = torchhd.cosine_similarity(a, b)  # intersection on Sparsr, counting on the host
bundled = torchhd.bundle(a, b)           # raises: see "Why bundle() refuses" below

By default this targets the Sparsr VM (SPARSR_BACKEND=vm), the software device model published as libsparsr_vm.so. SPARSR_BACKEND picks the device. The Sparsr host library accepts these names, and the table says what each one needs from this wheel:

SPARSR_BACKEND What runs In this wheel
vm The Sparsr VM, in your process. The default. Yes
softemu The original MIPS software emulator. Not usable here: see below. Yes, but it runs the wrong instruction set for these kernels
vmproc The Sparsr VM in a process of its own, reached over its wire protocol. The client is. The sparsr-vm executable is not: point SPARSR_VM_BINARY at one from the Sparsr SDK, or SPARSR_VM_ENDPOINT at a VM already running
fpgasim Sparsr on the FPGA simulator. No. The host library falls back to softemu with a warning on stderr
fpgaf2 Sparsr on a real F2 card. No. Same fallback

Not softemu, and the difference is not cosmetic. The two backends execute different instruction sets: softemu runs MIPS words, the VM runs RV32I, and the kernels behind these operations are RV32I. The same image on softemu decodes to something else entirely, so the package selects vm at import time rather than accepting the loader's default. It only fills the variable in when it is unset, so an explicit choice still wins.

Examples

  • examples/basic runs each supported operation once, checks the result against the CPU, and shows what the unsupported ones say when they refuse.
  • examples/mnist recognises handwritten digits at 78.8% accuracy, with every comparison against a class prototype running on Sparsr.

What this package computes: nothing

Every HDC operation here is a call into libsparsr_hdc, the Sparsr HDC/VSA library, which is bundled in this package. That library owns the algorithms and the device kernels that run them; this package owns the PyTorch side alone -- registering the device, converting tensors, and dispatching.

It used to own kernels too, a bind and a bundle written independently of the library's. Two implementations of one operation drift apart, and the first sign of it would have been torchhd and libsparsr_hdc disagreeing about what bind means. So the dependency runs one way: torchhd is a helper library for HDC, and the HDC operations are the HDC library's.

The mapping is direct in every case but one:

This package libsparsr_hdc
torchhd.bind() hdc_bind() -- one WXOR
torchhd.bundle() hdc_bundle_majority() over three members
dot_similarity() / cosine_similarity() hdc_similarity() -- one WAND, then counts

The bundle is the one worth explaining. torchhd defines the BSC bundle as where(a == b, a, tiebreak), and that is exactly a majority vote over the three of them: where a and b agree they outvote the tiebreak two to one, and where they disagree the tiebreak decides. So it needs no kernel of its own. A test in the HDC library's own suite pins that identity, because the library's threshold could change to "at least half" and still pass every other majority test it has -- while silently turning this package's bundle into a union.

What's supported

Torchhd call Runs on Sparsr hardware?
torchhd.bind() Yes -- a single WXOR instruction, via genuine logical_xor op dispatch.
torchhd.bundle() No -- raises RuntimeError whenever the two operands disagree anywhere, which is every real pair. Sparsr cannot store the fair-coin tiebreak torchhd's semantics need; see below.
torchhd.dot_similarity() / cosine_similarity() Partially -- the intersection is a WAND on Sparsr; counting its bits runs on the host, inside libsparsr_hdc (see below). Only single pairs, not batches of stored class vectors.
torchhd.permute() No -- raises NotImplementedError. Needs a wide bit-rotate instruction Sparsr doesn't have yet.
torchhd.multiset() / multibundle() No -- needs a batch of hypervectors, and the "sparsr" device only supports single hypervectors (see below), so it can never be reached on this device. The device-side half is no longer the blocker: libsparsr_hdc bundles any number of members today, with a bit-plane carry-save adder.

Everything else (arbitrary tensor ops, printing/repr, arithmetic) isn't implemented for the "sparsr" device -- move a tensor back with .to("cpu") first.

What fits in WMEM, and what does not

Sparsr's WMEM always transfers data through its native LIL-32b sparse compression codec. That codec splits each 512-byte (4096-bit) block into 128 four-byte lanes and stores one entry per non-zero lane -- a one-byte lane index plus the whole four-byte lane value -- with room for 48 entries in a 240-byte row. Moving a tensor to "sparsr" checks that and raises a clear RuntimeError rather than letting data corrupt silently.

The ceiling counts non-zero lanes, not set bits. A lane is stored whole, so once a lane is occupied the bits inside it are free. That gives two quite different ways to fit, and only one of them is "sparse":

  • Bits spread across all 4096 positions have to be genuinely sparse. Every set bit tends to occupy a fresh lane, so the practical ceiling is around 1.5% density -- e.g. torchhd.random(..., vsa="BSC", sparsity=0.998). This is the Sparse Distributed Representation (SDR) style of VSA that Sparsr's compression hardware is built for, and it is what this package's own examples and tests use.
  • Bits confined to 48 of the 128 lanes can be at any density at all, including 50%. That is a fully dense hypervector 1536 bits wide, and it always fits.

So a full-width dense 4096-bit BSC hypervector does not fit -- essentially all 128 lanes are occupied -- but a dense 1536-bit one does. The MNIST example measures what that costs: 78.79% accuracy with the 1536-bit code against 81.01% with a full-width 4096-bit one, on the same algorithm and seed over all 60,000 training and 10,000 test images. So the ceiling costs a little over two points of accuracy rather than blocking dense codes outright. An uncompressed WMEM path would lift it; it is planned but not built.

Why bundle() refuses

torchhd's BSC bundle(a, b) is where(a == b, a, tiebreak): keep the shared value wherever the two hypervectors agree, and resolve every position where they disagree with a fair coin flip. That tiebreak vector is dense by construction, and by the section above dense data cannot be stored in WMEM at all.

Sparsr therefore has no way to compute a faithful bundle today. Drawing the tiebreak at the low density WMEM can store makes the coin overwhelmingly biased towards 0, so every disagreeing position resolves to 0 and the bundle of two sparse hypervectors comes back as the all-zero hypervector -- a valid-looking tensor carrying no information at all. Measured at sparsity=0.998:

Tiebreak set bits in a set bits in b agreeing set bits disagreeing positions bundled set bits
CPU, fair coin 10 7 0 17 12
Sparsr, sparse coin 10 7 0 17 0

So bundle() raises a RuntimeError naming the limit it hit, instead of returning that. Two operands that agree everywhere consult no tiebreak, so that case is exact and still runs on Sparsr. Bundle on the host (.to("cpu")) in the meantime.

Where a tensor's bits live

In host memory. A "sparsr" tensor holds its 4096 bits on the host, and each operation sends its operands to the device and reads the result back.

They used to be resident: a tensor's storage pointer encoded a WMEM row, and the bits stayed on the device between operations. That could not survive moving onto libsparsr_hdc, and it should not have. The library reserves WMEM rows 0 to 31 -- every row there is -- from hdc_init() onwards, and nothing on a Sparsr device arbitrates who owns a row. Residency here meant two libraries writing the same rows with no error on either side, which is the collision the HDC library's own device memory layout names as the one it cannot prevent. One owner of WMEM is the only arrangement that works today.

What it costs is a host round trip per operation, which matters for chained work: bind() then bundle() no longer keeps the intermediate on the device. A host-side memory manager would let residency come back, for both libraries at once; it is planned but not built.

Independently of that, .to("sparsr") supports exactly one 4096-bit hypervector per tensor -- a batch has to be moved one at a time. Giving WMEM real capacity is planned but not built.

Installing

pip install torchhd-sparsr

That is the whole install step. The wheel already contains:

  • the Sparsr host runtime,
  • the Sparsr VM, which executes the operations,
  • the HDC library and its device kernels,
  • the compiled PyTorch extension that registers the "sparsr" device.

Operations run on the software emulator by default, so Sparsr hardware is optional. You do not need a RISC-V toolchain, a compiler, or any other SDK. A wheel is published per CPython version, for Linux on x86-64.

Each wheel is built against one PyTorch minor version and uses libtorch's C++ ABI directly, the same constraint torchvision has. Install the wheel that matches the PyTorch you run.

There is no source distribution on PyPI, because a build needs the Sparsr runtime as binaries. To build the package yourself, unpack the Sparsr HDC tarball from the Developer Zone and point the build at it. It holds the runtime and libsparsr_hdc, which is all the build takes from outside:

SPARSR_HDC_ROOT=/path/to/sparsr-hdc pip wheel --no-deps --no-build-isolation .

That needs a C++ compiler and the PyTorch you intend to run against, and nothing else.

Licence

The sources of this package are MIT, and so is libsparsr_hdc, the library every operation calls into. Both are on GitHub: torchhd-sparsr and libsparsr-hdc. The wheel also bundles four proprietary binaries, the Sparsr host runtime and the device model: libsparsr_host.so, libsparsr_vm.so, libsparsr_vmproc.so and libsparsr_softemu.so. Their terms are in LICENSE-RUNTIME, which is packaged inside the wheel beside LICENSE. The package metadata says the same thing in one line: MIT AND LicenseRef-Proprietary.

libsparsr_vm.so is built with .NET Native AOT, which links the .NET runtime into the binary. The .NET runtime is MIT, and the notices for it and for the components inside it are in THIRD-PARTY-NOTICES, the third file packaged beside the two above.

Metadata

Release files for torchhd-sparsr 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for torchhd-sparsr 0.2.1
File
torchhd_sparsr-0.2.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ x86-64, Linux glibc 2.24+ x86-64 Details
torchhd_sparsr-0.2.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details
torchhd_sparsr-0.2.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.28+ x86-64, Linux glibc 2.24+ x86-64 Details
torchhd_sparsr-0.2.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.10 CPython 3.10 Linux glibc 2.28+ x86-64, Linux glibc 2.24+ x86-64 Details

Total release size: 3.4 MB

Release files / torchhd_sparsr-0.2.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL torchhd_sparsr-0.2.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 846.5 kB
Tags CPython 3.13 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
1a0ffd41236416c07c9c9956b623f69bac9ae366d0d492947ec905f0a10d9a51
BLAKE2b-256 checksum
How to use checksums
b11c01b131e095a767fb70fd90af49d20962c89d6fa9315a1980969cea1fbe02
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / torchhd_sparsr-0.2.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL torchhd_sparsr-0.2.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 846.4 kB
Tags CPython 3.12 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
4ce1967cb37f2941e77169903ac6009d72a2f9e499415d4c2f8c0554b51e552e
BLAKE2b-256 checksum
How to use checksums
9af0c896251f0877c8f2a009b067437c8dbbc3171854cb860f7aa4390ce2bb46
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / torchhd_sparsr-0.2.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL torchhd_sparsr-0.2.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 846.1 kB
Tags CPython 3.11 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
1dc24866bd675bf49535a1bf0bf0b6409510e609b88caddde4c24942d35a87a6
BLAKE2b-256 checksum
How to use checksums
598334fcd29f5e2be3dd535408c86a2a0901db2dc0a3f2cfd140c9a471b40aed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / torchhd_sparsr-0.2.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL torchhd_sparsr-0.2.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 843.5 kB
Tags CPython 3.10 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
60baae64c4ab3c1bc37cc3d2dbb57948e7d4ea6cf45040cd4846f3c843592dd0
BLAKE2b-256 checksum
How to use checksums
fbc3e8564995c3c609c00c110291838f9d7807548b1986e1d7908b301308d1cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.1 This release

4 release files

0.2.0

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page