keras-hexagdly
Keras 3 port of HexagDLy: convolution and pooling methods for hexagonally sampled data, originally written for PyTorch by Tim Lukas Holch and Constantin Steppa (ai4iacts).
This port reproduces HexagDLy's hexagonal addressing scheme and sub-kernel
decomposition exactly (bit-for-bit equivalent outputs, cross-checked against the
upstream PyTorch package in
hexagdly-oracle), but
is built on Keras 3 so it runs on any backend
(TensorFlow, JAX, PyTorch) and uses a channels-last (NHWC/NDHWC) tensor
layout instead of PyTorch's channels-first.
It also adds three functionalities that do not exist in upstream HexagDLy:
share_neighbors(Conv2d,Conv3d): ties the weights of several cells of a hexagonal kernel together instead of giving every cell its own independent weight, which cuts the parameter count and imposes a geometric symmetry on the learned kernel. Three modes --"ring","diag","sym"-- are illustrated below.depth_padding="same"(Conv3donly): zero-pads the depth/time axis so the temporal kernel is centred on each time step and the output depth equals the input depth, instead of HexagDLy's"valid"-only behaviour (output depth shrinks bykernel - 1).- hls4ml export for FPGA synthesis, currently
limited to
io_streamand the 2D layers.
Weights trained with pytorch-hexagdly
can be loaded into these layers -- see
keras_hexagdly.torch_interop and
notebooks/pytorch_to_keras_example.ipynb.
No torch dependency is required: a plain .npz of the state dict works.
See NOTICE.md for attribution details and citation information.
Installation
pip install keras-hexagdly
For development (running the test suite, which also checks parity against upstream PyTorch HexagDLy, and the example notebooks):
pip install keras-hexagdly[dev]
Usage
import keras
import keras_hexagdly as hgly
kernel_size, stride = 1, 4
in_channels, out_channels = 1, 3
hexconv = hgly.Conv2d(in_channels, out_channels, kernel_size, stride)
x = keras.random.uniform((1, 21, 21, 1)) # channels-last: (N, H, W, C)
y = hexconv(x)
in_channels can be omitted; it is then inferred from the input on first
call, like a standard Keras layer: hgly.Conv2d(out_channels, kernel_size=kernel_size, stride=stride).
New: share_neighbors -- weight sharing across kernel cells
Available on Conv2d and Conv3d. share_neighbors reduces the number of
learnable parameters by grouping kernel cells that share a single weight.
Three modes are available, illustrated below for kernel_size=2 (19 cells):
share_neighbors="ring" |
share_neighbors="diag" |
share_neighbors="sym" |
|---|---|---|
| 3 weights -- cells at the same hex distance from center share one weight (concentric rings). | 10 weights -- visually opposite (antipodal) cells share one weight. | 10 weights -- geometrically adjacent 60 degree pairs share one weight. |
"ring": the most aggressive reduction. All 6 direct neighbours share one weight, all 12 outer cells share another. Enforces exact 6-fold rotational symmetry of the learned kernel."diag": antipodal symmetry -- each cell and its mirror image through the center share a weight. Useful when the kernel should be point-symmetric."sym": 60 degree adjacent pairs -- consecutive neighbours along the kernel boundary share a weight. Useful when the kernel should reflect local rotational symmetry.
For kernel_size=1 (7 cells): ring=2 weights, diag=4, sym=4.
For kernel_size=2 (19 cells): ring=3 weights, diag=10, sym=10.
Compare to the default share_neighbors=None, which gives 7 and 19 independent
weights. "ring" works at any kernel size; "diag" and "sym" are defined for
kernel_size 1 and 2 only, matching
pytorch-hexagdly, which does not
define them beyond n=2 either.
hexconv = hgly.Conv2d(in_channels, out_channels, kernel_size=2, share_neighbors="ring")
share_neighbors=True is accepted as an alias for "ring".
New: same-padded temporal convolution (Conv3d)
conv3d = hgly.Conv3d(in_channels, out_channels, kernel_size=(depth_k, hex_k),
depth_padding="same") # output depth == input depth
Before applying these layers, your data must already be arranged on the square-tensor layout HexagDLy expects (zig-zag columns); see notebooks/keras_hexagdly_addressing_scheme.ipynb for how to get there from raw detector coordinates, and notebooks/keras_hexagdly_2d_example.ipynb for a worked convolution/pooling example, including the new features above.
Notebooks
Ported from HexagDLy's own notebooks, one-to-one where the content is framework-specific, lightly adapted where it depends on a torch-specific dataloader/training loop:
keras_hexagdly_2d_example.ipynb-- basicConv2d/MaxPool2dusage, hex-vs-square symmetry, and the newshare_neighbors/depth_paddingfeatures.keras_hexagdly_addressing_scheme.ipynb-- how to map raw hexagonal detector coordinates onto the square-tensor layout the layers expect (backend-independent; near-identical to upstream).keras_hexagdly_custom_kernels_example.ipynb-- building a custom Gaussian smoothing kernel withConv2d_CustomKernel.keras_hexagdly_cnn_example.ipynb-- a small CNN classifying toy hexagonal shapes, trained withmodel.fit.keras_hexagdly_hex_vs_square.ipynb-- parameter-count and timing benchmark of hex vs. square kernels, plus a hex-CNN-vs-square-CNN classification comparison.
FPGA export via hls4ml
The hex layers can be synthesised to HLS C++ through hls4ml. The layers are replaced by a fused line-buffer kernel that keeps only the resident rows of the frame in a shift register, rather than materialising the whole gathered tensor.
import hls4ml
from keras_hexagdly.hls4ml_handler import register_hex_gather_layers
from keras_hexagdly.hls4ml_ext import patch_model_for_hls, hex_reuse_config, check_hls_config
register_hex_gather_layers("Vitis")
hls_ready = patch_model_for_hls(model) # strategy="linebuffer"
config = hls4ml.utils.config_from_keras_model(hls_ready, granularity="name")
hex_reuse_config(config, hls_ready) # per-layer ReuseFactor
check_hls_config(config, hls_ready, io_type="io_stream")
hls_model = hls4ml.converters.convert_from_keras_model(
hls_ready, hls_config=config, io_type="io_stream", backend="Vitis",
)
Supported scope
Only io_stream and the 2D layers (Conv2d, MaxPool2d) are supported for
now. That is the combination that is covered by C-simulation and validated by
RTL co-simulation.
io_stream |
io_parallel |
|
|---|---|---|
Conv2d, MaxPool2d |
supported | not supported |
Conv3d, MaxPool3d |
not supported | not supported |
The unsupported paths are not silently wrong -- they raise. patch_model_for_hls
raises NotImplementedError on a 3D layer, and check_hls_config raises on
io_type="io_parallel". Both accept allow_unvalidated=True if you want to
experiment with them anyway, but nothing about their numerics or resource usage
is guaranteed.
hex_reuse_config matters more than it looks: hls4ml's
config_from_keras_model(granularity="name") writes ReuseFactor=1 into every
layer entry it recognises, which overrides the model-level value -- but custom
layers get no entry and inherit the model-level one instead. Without an explicit
per-layer setting, a single model ends up mixing two different reuse factors.
Testing
pip install -e .[dev] --no-build-isolation # see note below
pytest tests/
(--no-build-isolation: only needed if your pip is old -- pip 22.0.2's
isolated build environment was observed to pick up a setuptools version
that mis-names the built wheel UNKNOWN. Verified clean with a modern pip
(>=23) in a fresh venv: plain pip install . works with no workaround.
Either way, pytest tests/ works without installing anything -- conftest.py
puts src/ and tests/ on sys.path.)
Most of the suite no longer lives here. It has moved to
hexagdly-oracle, the shared
test repo for this library and
pytorch-hexagdly: hand-verified
layer outputs, share_neighbors weight-sharing oracles, depth_padding,
mixed precision, serialization, edge cases, indexed-equivalence and the hls4ml
export tests (including C-simulation). tests/ here keeps only what is
genuinely local.
To run the full suite, check the oracle out as a sibling directory:
git clone https://github.com/YugnatD/hexagdly-oracle
PYTHONPATH=hexagdly-oracle/src pytest tests/ hexagdly-oracle/tests/
Verified to pass on all three Keras 3 backends (set KERAS_BACKEND=tensorflow|torch|jax
before importing keras; tensorflow is the default if unset):
KERAS_BACKEND=tensorflow # 891 passed, 7 skipped
KERAS_BACKEND=torch # 881 passed, 17 skipped
KERAS_BACKEND=jax # 806 passed, 92 skipped (slower: per-shape JIT compile)
A GitHub Actions workflow (.github/workflows/test.yml)
runs this matrix (3 backends x 3 Python versions) plus ruff check/ruff format --check
on every push and PR, checking out the oracle repo as part of the job.
Note for GPU users on the torch backend: the Keras torch backend runs on CUDA
when a GPU is visible, and PyTorch defaults to cudnn.allow_tf32 = True, so
convolutions are computed in TF32 (~1e-3 relative precision). That is enough to
break equivalence assertions on hex kernels, which are wide by construction
because dilation is done by zero insertion. The oracle's conftest.py pins the
flag off for the test session; the library itself never touches global torch
settings.
Disclaimer
Like upstream HexagDLy, this is a prototyping tool: it favors flexibility over performance. Once a model's architecture (kernel size, stride, input shape) is fixed, hard-coding those parameters would yield a faster implementation.
Performance
See benchmarks/ for a speed comparison against upstream
PyTorch HexagDLy. Short version: run eagerly on CPU, this port is 1-7x
slower than upstream for the same reason upstream itself is slow (the hex
sub-kernel decomposition costs several op-dispatches per call -- a design
choice, not a regression). Wrapped in a compiled call (jax.jit/
tf.function, which model.fit/model.predict do automatically) it is
typically faster than upstream's eager PyTorch, sometimes by an order of
magnitude. torch.compile support is currently unreliable for this layer
(see the benchmarks README for why); eager execution on a GPU is the
recommended way to get speed on the torch backend.
Changelog
See CHANGELOG.md.
License
MIT, see LICENSE. This is a derivative work of HexagDLy (Copyright (c) 2018 ai4iacts); see NOTICE.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file keras_hexagdly-0.3.0.tar.gz.
File metadata
- Download URL: keras_hexagdly-0.3.0.tar.gz
- Upload date:
- Size: 2.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f816d912580b0ae6a3b3c49ad067820d84ae3bbe31aa68688d4014ae30e58927
|
|
| MD5 |
b4747ddc70645b6590e3f39f19fed7a9
|
|
| BLAKE2b-256 |
3073ffc59077e441cc87129abc560cf09d966ead14e631523361597e4db5b8eb
|
Provenance
The following attestation bundles were made for keras_hexagdly-0.3.0.tar.gz:
Publisher:
publish.yml on YugnatD/keras-hexagdly
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
keras_hexagdly-0.3.0.tar.gz -
Subject digest:
f816d912580b0ae6a3b3c49ad067820d84ae3bbe31aa68688d4014ae30e58927 - Sigstore transparency entry: 2290474181
- Sigstore integration time:
-
Permalink:
YugnatD/keras-hexagdly@b8109336cf616418a07c402bc04b68ee50f3682f -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/YugnatD
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8109336cf616418a07c402bc04b68ee50f3682f -
Trigger Event:
push
-
Statement type:
File details
Details for the file keras_hexagdly-0.3.0-py3-none-any.whl.
File metadata
- Download URL: keras_hexagdly-0.3.0-py3-none-any.whl
- Upload date:
- Size: 85.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e89c4a3114e1d3f4261f9b681edd51adf5c41aaf3f98db5cf1e9adb92074b2e
|
|
| MD5 |
811e0ba20c07ae08beaa7a5ea3923bac
|
|
| BLAKE2b-256 |
cb2f220f6eacd6bddb668b6165fdd0b75867f55746878f5dc9c1baf623232800
|
Provenance
The following attestation bundles were made for keras_hexagdly-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on YugnatD/keras-hexagdly
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
keras_hexagdly-0.3.0-py3-none-any.whl -
Subject digest:
9e89c4a3114e1d3f4261f9b681edd51adf5c41aaf3f98db5cf1e9adb92074b2e - Sigstore transparency entry: 2290474233
- Sigstore integration time:
-
Permalink:
YugnatD/keras-hexagdly@b8109336cf616418a07c402bc04b68ee50f3682f -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/YugnatD
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8109336cf616418a07c402bc04b68ee50f3682f -
Trigger Event:
push
-
Statement type: