ianshank's picture
Upload folder using huggingface_hub
0080d0b verified
|
Raw
History Blame Contribute Delete
6.13 kB
---
license: mit
library_name: alpha-zero-hexcrystal
tags:
- pytorch
- hexagonal-grid
- spatial-encoder
- reinforcement-learning
- alphazero
---
# Hex-Crystalline Spatial Encoder
A spatial encoder for **hexagonal grids represented on a rectangular array**,
trained inside an AlphaZero-style agent for a simplified 4X strategy game. It
depends on nothing but PyTorch, so it can be reused for any hex-lattice problem β€”
board games, hex-tiled sensor arrays, or other six-neighbour topologies β€” without
the surrounding game code.
```python
from alpha_zero.hexcrystal.hub_mixin import HexSpatialEncoderForHub
encoder = HexSpatialEncoderForHub.from_pretrained("<repo-id>")
tokens = encoder(spatial) # (B, C, H, W) -> (B, num_tokens, embed_dim)
```
Install with `pip install -e '.[hub]'` from the source repository.
## What this model actually does
Read this section before assuming behaviour from the name. Earlier documentation
in the source repository described features this encoder does **not** have; that
drift is corrected here and in the code.
The forward pass, in order:
1. **Even-q offset hexagonal convolution.** A 7-tap kernel β€” the centre tile plus
its six neighbours β€” applied by gathering pre-computed adjacency indices.
Off-board neighbours carry a `-1` sentinel and are masked. The stack runs at
constant resolution: there is no pyramid, no dilation, and no configurable
ring radius.
2. **Adaptive average pooling to a fixed token grid, then a linear projection.**
This is the encoder's most consequential property: **pooling discards fine
spatial structure** before tokenization. If your task needs per-tile fidelity
at the output, this bottleneck is the first thing to reconsider.
3. **Optional crystalline lattice layer.** A set of learned transforms
initialised to a symmetry group (hexagonal, cubic, or tetrahedral), combined
by a softmax-weighted sum or a small attention over the **transform axis**.
This is not spatial attention and involves no hex-neighbour masking.
What it does **not** contain, despite claims that circulated in earlier docs:
| Claimed | Reality |
|---|---|
| Axial coordinates | Even-q offset only; other systems raise `ValueError` |
| Ring-distance positional encoding | No positional encoding at all β€” position comes from the downstream transformer's learned `pos_embed` |
| Multi-scale convolutions | Flat, same-resolution stack |
| Attention over hex neighbourhoods | Attention is over transforms, not tiles |
## Intended use
**Intended.** A drop-in replacement for patch embedding when your input has hex
topology; a starting point for hex-aware architectures; a reference
implementation of even-q offset adjacency in PyTorch.
**Not intended.** This is **not** a strong game-playing model. It is one
component of an agent trained at small scale for research purposes. Do not deploy
it as a Civilization VI opponent or treat its weights as competitive.
## Training
Trained as the spatial encoder of a transformer policy/value network, via
AlphaZero-style self-play with Gumbel MCTS on **MicroCiv**, a simplified 4X
environment with a 13-channel state encoding on a square board. Training used
self-play trajectories only; there is no human gameplay data, and no external
dataset is involved.
## Evaluation
The two encoders were trained from identical code, seed, and budget, differing
only in encoder type, then played head to head. Full methodology and the
generating script are in the source repository (`scripts/run_encoder_ab.py`).
| hex_crystalline vs patch_embed | Value |
|---|---|
| Games | 800 |
| W/D/L | 142/588/70 |
| Score rate | 0.545 |
| Wilson 95% CI | [0.510, 0.579] |
| Elo difference | +31.4 |
### Evaluation scope and limitations
Read the confidence interval, not just the point estimate.
- **Reduced scale.** MicroCiv with a 13-channel encoder on a small board, a
modest iteration budget, and CPU training. These are not full-Civilization VI
results and do not extrapolate to one.
- **A single training run per arm.** Training-run variance is therefore **not
captured**: the interval reflects game-sampling noise only, and a different
seed could plausibly reorder the arms.
- **Differences smaller than the interval half-width are not resolved.** Where
the interval spans 0.5, the honest reading is that this run does not
distinguish the two encoders β€” not that they are equal, and not that the
point estimate is real.
- **Draws are scored 0.5** in the score rate; win/draw/loss counts are reported
separately so the raw outcome is recoverable.
The value of this evaluation is that it is reproducible, same-code, same-seed,
and bounded β€” not that it settles which encoder is better.
## Architecture details
| Property | Value |
|---|---|
| Coordinate system | Even-q offset (flat-top hexes) |
| Kernel | 7-tap (centre + 6 neighbours) via gather |
| Symmetry groups | `hexagonal` (6), `cubic` (8), `tetrahedral` (12) transforms |
| Tokenization | Adaptive average pool β†’ linear projection |
| Output shape | `(batch, num_output_tokens, embed_dim)` |
| Weights format | safetensors |
| Dependencies | PyTorch only |
Configuration is a flat `config.json` and round-trips through
`HexEncoderConfig.from_dict`. The adjacency indices are registered buffers, so
they travel with `.to(device)` and appear in the `state_dict`.
## Reproducing
```bash
python scripts/run_encoder_ab.py train --iterations N --games-per-iter M
python scripts/run_encoder_ab.py arena --num_games K
python scripts/run_encoder_ab.py report
```
Both arms take the same seed, and the environment seed is threaded into
self-play so game streams are reproducible β€” seeding the global RNGs alone is
not sufficient, because the environment holds its own generator.
## Citation
If you use this encoder, please cite the repository. The exact commit that
produced these weights is recorded in the evidence report alongside the
training budget.