Text Generation
Transformers
Safetensors
English
flygpt
connectome
fruit-fly
drosophila
malecns
recurrent
sparse
tiny-shakespeare
custom_code
Instructions to use QuixiAI/FlyGPT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use QuixiAI/FlyGPT with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="QuixiAI/FlyGPT", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("QuixiAI/FlyGPT", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use QuixiAI/FlyGPT with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "QuixiAI/FlyGPT" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "QuixiAI/FlyGPT", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/QuixiAI/FlyGPT
- SGLang
How to use QuixiAI/FlyGPT with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "QuixiAI/FlyGPT" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "QuixiAI/FlyGPT", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "QuixiAI/FlyGPT" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "QuixiAI/FlyGPT", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use QuixiAI/FlyGPT with Docker Model Runner:
docker model run hf.co/QuixiAI/FlyGPT
Whole fly nervous system: 160,514 neurons, 10.4M trainable synapses, best val 1.5957
Browse files- full-cns/README.md +230 -0
- full-cns/config.json +42 -0
- full-cns/configuration_flygpt.py +40 -0
- full-cns/generation_config.json +5 -0
- full-cns/graph_metadata.json +78 -0
- full-cns/model.safetensors +3 -0
- full-cns/modeling_flygpt.py +199 -0
- full-cns/tokenizer.json +90 -0
- full-cns/tokenizer_config.json +6 -0
full-cns/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-4.0
|
| 3 |
+
language: [en]
|
| 4 |
+
library_name: transformers
|
| 5 |
+
pipeline_tag: text-generation
|
| 6 |
+
base_model: QuixiAI/MaleCNS
|
| 7 |
+
base_model_relation: finetune
|
| 8 |
+
datasets: [karpathy/tiny_shakespeare]
|
| 9 |
+
tags: [connectome, fruit-fly, drosophila, malecns, recurrent, sparse, tiny-shakespeare, custom_code]
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# QuixiAI/FlyGPT
|
| 13 |
+
|
| 14 |
+
A character-level language model whose recurrent architecture is **a real subgraph of the fruit-fly brain
|
| 15 |
+
connectome** ([MaleCNS v1.0](https://male-cns.janelia.org/)). Unlike the earlier frozen-reservoir approach in
|
| 16 |
+
[ngxson/fly-llm-hf](https://huggingface.co/ngxson/fly-llm-hf), which keeps the connectome's synaptic weights fixed and
|
| 17 |
+
trains only the projections and readout, FlyGPT **trains one value per real synaptic connection with gradient
|
| 18 |
+
descent** while keeping the fly's edge topology fixed, and compares the result against the same neurons with
|
| 19 |
+
degree-preserving scrambled connections across paired seeds.
|
| 20 |
+
|
| 21 |
+
**Base model: [QuixiAI/MaleCNS](https://huggingface.co/QuixiAI/MaleCNS)**, the lossless packaging of the MaleCNS v1.0
|
| 22 |
+
connectivity tables. FlyGPT's graph is extracted from it deterministically (`build_graph.py`, revision pinned in
|
| 23 |
+
`data/fly/build_edges.py`); `graph.node_id` and `graph.synapse_count` map every edge back to that repository.
|
| 24 |
+
|
| 25 |
+
This checkpoint's wiring is the original MaleCNS wiring.
|
| 26 |
+
|
| 27 |
+
**Trained.** Condition `real`, seed 1, step 15375, validation loss 1.5957 nats/char on the fixed Tiny Shakespeare split.
|
| 28 |
+
|
| 29 |
+
This is not a biological simulation of a living fly. The "weights" in the MaleCNS release are anatomical synapse
|
| 30 |
+
counts; they are stored here as `graph.synapse_count` and are **not** the model's parameters.
|
| 31 |
+
|
| 32 |
+
## The graph
|
| 33 |
+
|
| 34 |
+
Every number below is produced by FlyGPT's extraction script (`build_graph.py`), not typed by hand.
|
| 35 |
+
|
| 36 |
+
| | |
|
| 37 |
+
|---|---|
|
| 38 |
+
| Source | MaleCNS v1.0 flat connectome (`gs://flyem-male-cns/v1.0/connectome-data/flat-connectome/`) |
|
| 39 |
+
| Candidate pool | central brain: `superclass` starting with `cb_` (164,587 neurons) |
|
| 40 |
+
| Minimum synapses per connection | 3 (engineering choice, not a biological claim) |
|
| 41 |
+
| Extraction | largest SCC → largest directed (k,k)-core with ≥ target nodes (k = 0) → trim by weighted degree |
|
| 42 |
+
| Neurons used | 160,514 |
|
| 43 |
+
| Directed connections used | 10,402,842 |
|
| 44 |
+
| Synaptic contacts represented | 102,352,176 |
|
| 45 |
+
| Largest SCC fraction | 1.0 |
|
| 46 |
+
| Reciprocal pairs | 1,006,326 |
|
| 47 |
+
| Input / output neurons | top 2048 by out-degree / top 4096 by in-degree |
|
| 48 |
+
| Input→output shortest path (median / p90 / max hops) | 1.0 / 1.0 / 1.0 |
|
| 49 |
+
| Graph hash | `d53f26f9bf6f7b8c9944e28fdd4721d2c0a237e7534f9160977c70e6c81ac6c2` |
|
| 50 |
+
|
| 51 |
+
`graph.node_id` holds the MaleCNS body ids, so every neuron maps back to the release.
|
| 52 |
+
|
| 53 |
+
## What is in `model.safetensors`
|
| 54 |
+
|
| 55 |
+
| tensor | shape | dtype | size |
|
| 56 |
+
|---|---|---|---|
|
| 57 |
+
| `graph.edge_index` | (2, 10402842) | int32 | 83.22 MB |
|
| 58 |
+
| `graph.synapse_count` | (10402842,) | int32 | 41.61 MB |
|
| 59 |
+
| `graph.node_id` | (160514,) | int64 | 1.28 MB |
|
| 60 |
+
| `graph.input_nodes` | (2048,) | int64 | 0.02 MB |
|
| 61 |
+
| `graph.output_nodes` | (4096,) | int64 | 0.03 MB |
|
| 62 |
+
| `recurrent.edge_values` | (10402842,) | bfloat16 | 20.81 MB |
|
| 63 |
+
| `recurrent.bias` | (160514,) | bfloat16 | 0.32 MB |
|
| 64 |
+
| `recurrent.raw_leak` | (160514,) | bfloat16 | 0.32 MB |
|
| 65 |
+
| `embed.weight` | (65, 32) | bfloat16 | 0.00 MB |
|
| 66 |
+
| `input_proj.weight` | (2048, 32) | bfloat16 | 0.13 MB |
|
| 67 |
+
| `input_proj.bias` | (2048,) | bfloat16 | 0.00 MB |
|
| 68 |
+
| `lm_head.weight` | (65, 4096) | bfloat16 | 0.53 MB |
|
| 69 |
+
| `lm_head.bias` | (65,) | bfloat16 | 0.00 MB |
|
| 70 |
+
|
| 71 |
+
`graph.*` is the anatomy (integer, never trained). `recurrent.*`, `embed.*`, `input_proj.*`, `lm_head.*` are the
|
| 72 |
+
learned state, stored in bf16. The sparse recurrent matmul is rebuilt in fp32 at runtime (rows = destination,
|
| 73 |
+
columns = source), with each incoming edge scaled by `1/sqrt(in_degree)`.
|
| 74 |
+
|
| 75 |
+
## Dynamics
|
| 76 |
+
|
| 77 |
+
```text
|
| 78 |
+
character → embedding (32) → linear → 2048 input neurons
|
| 79 |
+
proposal_i = tanh( Σ_j W_ij h_j / sqrt(in_degree_i) + external_input_i + bias_i )
|
| 80 |
+
h_i ← (1 − leak_i) h_i + leak_i · proposal_i (2 microsteps per character, leak_i = sigmoid(raw_leak_i))
|
| 81 |
+
4096 output neuron states → linear → 65 logits
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
## Inference
|
| 85 |
+
|
| 86 |
+
```python
|
| 87 |
+
import torch
|
| 88 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 89 |
+
|
| 90 |
+
tok = AutoTokenizer.from_pretrained("QuixiAI/FlyGPT")
|
| 91 |
+
model = AutoModelForCausalLM.from_pretrained("QuixiAI/FlyGPT", subfolder="full-cns", trust_remote_code=True, dtype=torch.float32)
|
| 92 |
+
|
| 93 |
+
ids = tok("ROMEO:", return_tensors="pt").input_ids
|
| 94 |
+
out = model.generate(ids, max_new_tokens=300, do_sample=True, temperature=0.8)
|
| 95 |
+
print(tok.decode(out[0]))
|
| 96 |
+
|
| 97 |
+
# The degree-preserving scrambled control (same neurons, same degrees, shuffled wiring), for comparison:
|
| 98 |
+
scrambled = AutoModelForCausalLM.from_pretrained("QuixiAI/FlyGPT", subfolder="scrambled", trust_remote_code=True, dtype=torch.float32) # the 5k control
|
| 99 |
+
print(tok.decode(scrambled.generate(ids, max_new_tokens=300, do_sample=True, temperature=0.8)[0]))
|
| 100 |
+
|
| 101 |
+
# Neuron activity, for visualization: [1, T, 5000] states after each character, plus MaleCNS body ids
|
| 102 |
+
with torch.no_grad():
|
| 103 |
+
states = model(ids).state # [B, N] after the last character
|
| 104 |
+
body_ids = model.graph.node_id # index -> MaleCNS body id, for lookup in QuixiAI/MaleCNS
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
The tokenizer is strict: only the 65 characters of Tiny Shakespeare are encodable. `generate()` carries the neuron
|
| 108 |
+
state between characters instead of a KV cache.
|
| 109 |
+
|
| 110 |
+
## Training
|
| 111 |
+
|
| 112 |
+
The recurrent core has one trainable weight per real synaptic connection. With the
|
| 113 |
+
[connectome-kernels](https://github.com/QuixiAI/connectome-kernels) package installed, the model's forward pass
|
| 114 |
+
runs on fused CUDA kernels (about 13× faster than `torch.sparse`, identical gradients); without it, it falls back
|
| 115 |
+
to `torch.sparse` automatically.
|
| 116 |
+
|
| 117 |
+
```python
|
| 118 |
+
# Fine-tune / continue training FlyGPT on Tiny Shakespeare (character-level).
|
| 119 |
+
# pip install transformers safetensors
|
| 120 |
+
# pip install --no-build-isolation git+https://github.com/QuixiAI/connectome-kernels # fused CUDA path, ~13x faster
|
| 121 |
+
import requests, torch, torch.nn.functional as F
|
| 122 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 123 |
+
|
| 124 |
+
tok = AutoTokenizer.from_pretrained("QuixiAI/FlyGPT")
|
| 125 |
+
model = AutoModelForCausalLM.from_pretrained("QuixiAI/FlyGPT", subfolder="full-cns", trust_remote_code=True, dtype=torch.float32).cuda()
|
| 126 |
+
# start from the untrained initialization instead: subfolder="init"
|
| 127 |
+
|
| 128 |
+
text = requests.get("https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt").text
|
| 129 |
+
data = torch.tensor(tok(text).input_ids)
|
| 130 |
+
train, val = data[: int(0.9 * len(data))], data[int(0.9 * len(data)):] # FlyGPT's fixed 90/10 split
|
| 131 |
+
|
| 132 |
+
def batch(split, B=32, T=64):
|
| 133 |
+
i = torch.randint(0, len(split) - T - 1, (B,))
|
| 134 |
+
x = torch.stack([split[j : j + T] for j in i]); y = torch.stack([split[j + 1 : j + T + 1] for j in i])
|
| 135 |
+
return x.cuda(), y.cuda()
|
| 136 |
+
|
| 137 |
+
recurrent = list(model.recurrent.parameters()) # one weight per real synapse, bias, leak
|
| 138 |
+
adapters = [p for n, p in model.named_parameters() if not n.startswith("recurrent.")]
|
| 139 |
+
opt = torch.optim.AdamW([{"params": adapters, "lr": 1e-3}, {"params": recurrent, "lr": 3e-4}], weight_decay=0.01)
|
| 140 |
+
|
| 141 |
+
for step in range(1, 501):
|
| 142 |
+
x, y = batch(train)
|
| 143 |
+
logits = model(x).logits # [B, T, 65]; state resets to zero per window
|
| 144 |
+
loss = F.cross_entropy(logits.reshape(-1, 65), y.reshape(-1))
|
| 145 |
+
opt.zero_grad(set_to_none=True); loss.backward()
|
| 146 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0); opt.step()
|
| 147 |
+
if step % 100 == 0:
|
| 148 |
+
with torch.no_grad():
|
| 149 |
+
vx, vy = batch(val); vl = F.cross_entropy(model(vx).logits.reshape(-1, 65), vy.reshape(-1))
|
| 150 |
+
print(f"step {step} train {loss.item():.3f} val {vl.item():.3f}")
|
| 151 |
+
|
| 152 |
+
model.save_pretrained("flygpt-finetuned"); tok.save_pretrained("flygpt-finetuned")
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
## Result: the whole nervous system
|
| 156 |
+
|
| 157 |
+
This checkpoint is the **entire traced fly nervous system**: 160,514 neurons and 10,402,842 trainable synaptic
|
| 158 |
+
connections, covering the optic lobes, central brain and ventral nerve cord. Trained for 16,700 steps at global
|
| 159 |
+
batch 192 across six GPUs (205M characters, about 200 epochs of Tiny Shakespeare).
|
| 160 |
+
|
| 161 |
+
Best validation loss **1.5957** nats/char, against a bigram floor of 2.482 on the same split.
|
| 162 |
+
|
| 163 |
+
**Scale did not improve accuracy.** The 5,000-neuron central-brain subgraph reaches 1.578 with one eighteenth of
|
| 164 |
+
the neurons. At a 64-character context on a 1.1M-character corpus the binding constraint is the context window
|
| 165 |
+
and the data, not model capacity, and the larger graph overfits sooner (train loss 1.333 against val 1.596).
|
| 166 |
+
Text quality nonetheless reads better by eye, with correctly spelled rare character names and more grammatical
|
| 167 |
+
lines.
|
| 168 |
+
|
| 169 |
+
This is **one seed with one control**, not a five-seed claim. The wiring comparison that the pre-registered rule
|
| 170 |
+
in the project's spec governs was run at 5,000 neurons and is reported in this repository's main model card.
|
| 171 |
+
|
| 172 |
+
## Citation
|
| 173 |
+
|
| 174 |
+
If you use this model, please cite it, its base model, and the MaleCNS dataset paper.
|
| 175 |
+
|
| 176 |
+
This model:
|
| 177 |
+
|
| 178 |
+
```bibtex
|
| 179 |
+
@misc{hartford2026flygpt,
|
| 180 |
+
title = {FlyGPT: a language model whose recurrent architecture is a real subgraph of the fruit-fly connectome},
|
| 181 |
+
author = {Hartford, Eric},
|
| 182 |
+
year = {2026},
|
| 183 |
+
publisher = {Hugging Face},
|
| 184 |
+
howpublished = {\url{https://huggingface.co/QuixiAI/FlyGPT}},
|
| 185 |
+
note = {Base model: QuixiAI/MaleCNS (MaleCNS v1.0, Berg et al. 2026, CC-BY 4.0). Code: https://github.com/QuixiAI/FlyGPT}
|
| 186 |
+
}
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
The base model (lossless connectome packaging):
|
| 190 |
+
|
| 191 |
+
```bibtex
|
| 192 |
+
@misc{hartford2026malecns,
|
| 193 |
+
title = {QuixiAI/MaleCNS: the MaleCNS v1.0 fruit-fly connectome as lossless Safetensors},
|
| 194 |
+
author = {Hartford, Eric},
|
| 195 |
+
year = {2026},
|
| 196 |
+
publisher = {Hugging Face},
|
| 197 |
+
doi = {10.57967/hf/10410},
|
| 198 |
+
howpublished = {\url{https://huggingface.co/QuixiAI/MaleCNS}},
|
| 199 |
+
note = {Repackaging of Berg et al. (2026), CC-BY 4.0}
|
| 200 |
+
}
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
The dataset (required by the CC-BY 4.0 license):
|
| 204 |
+
|
| 205 |
+
```bibtex
|
| 206 |
+
@article{berg2026malecns,
|
| 207 |
+
title = {Sexual dimorphism in the complete {Drosophila} male central nervous system connectome},
|
| 208 |
+
author = {Berg, Stuart and Beckett, Isabella R. and Costa, Marta and Schlegel, Philipp and Januszewski, Michał and Marin, Elizabeth C. and Nern, Aljoscha and Preibisch, Stephan and Qiu, Wei and Takemura, Shin-ya and Fragniere, Alexandra M.C. and Champion, Andrew S. and Adjavon, Diane-Yayra and Cook, Michael and Gkantia, Marina and Hayworth, Kenneth J. and Huang, Gary B. and Katz, William T. and Kämpf, Florian and Lu, Zhiyuan and Ordish, Christopher and Paterson, Tyler and Stürner, Tomke and Trautman, Eric T. and Whittle, Catherine R. and Burnett, Laura E. and Hoeller, Judith and Li, Feng and Loesche, Frank and Morris, Billy J. and Pietzsch, Tobias and Pleijzier, Markus W. and Silva, Valeria and Yin, Yijie and Ali, Iris and Badalamente, Griffin and Bates, Alexander Shakeel and Beresford, Rory J. and Bogovic, John and Brooks, Paul and Cachero, Sebastian and Canino, Brandon S. and Chaisrisawatsuk, Bhumpanya and Clements, Jody and Crowe, Arthur and de Haan Vicente, Inês and Dempsey, Georgia and Donà, Erika and Dos Santos, Márcia and Dreher, Marisa and Dunne, Christopher R. and Eichler, Katharina and Finley-May, Samantha and Flynn, Miriam A. and Hameed, Imran and Hopkins, Gary Patrick and Hubbard, Philip M. and Kiassat, Ladann and Kovalyak, Julie and Lauchie, Shirley A. and Leonard, Meghan and Lohff, Alanna and Longden, Kit D. and Maldonado, Charli A. and Moitra, Ilina and Moon, Sung Soo and Mooney, Caroline and Munnelly, Eva J. and Okeoma, Nneoma and Olbris, Donald J. and Pai, Anika and Patel, Birava and Phillips, Emily M. and Plaza, Stephen M. and Richards, Alana and Rivas Salinas, Jennifer and Roberts, Ruairí J.V. and Rogers, Edward M. and Scott, Ashley L. and Scuderi, Louis A. and Seenivasan, Pavithraa and Serratosa Capdevila, Laia and Smith, Claire and Svirskas, Rob and Takemura, Satoko and Tastekin, Ibrahim and Thomson, Alexander and Umayam, Lowell and Walsh, John J. and Whittome, Holly and Xu, C. Shan and Yakal, Emily A. and Yang, Tansy and Zhao, Arthur and George, Reed and Jain, Viren and Jayaraman, Vivek and Korff, Wyatt and Meissner, Geoffrey W. and Romani, Sandro and Funke, Jan and Knecht, Christopher and Saalfeld, Stephan and Scheffer, Louis K. and Waddell, Scott and Card, Gwyneth M. and Ribeiro, Carlos and Reiser, Michael B. and Hess, Harald F. and Rubin, Gerald M. and Jefferis, Gregory S.X.E.},
|
| 209 |
+
journal = {Cell},
|
| 210 |
+
volume = {189},
|
| 211 |
+
number = {18},
|
| 212 |
+
pages = {5504--5526.e15},
|
| 213 |
+
year = {2026},
|
| 214 |
+
month = sep,
|
| 215 |
+
publisher = {Elsevier},
|
| 216 |
+
doi = {10.1016/j.cell.2026.08.015},
|
| 217 |
+
url = {https://doi.org/10.1016/j.cell.2026.08.015},
|
| 218 |
+
note = {Preprint: bioRxiv 10.1101/2025.10.09.680999. Data: MaleCNS v1.0, CC-BY 4.0, https://male-cns.janelia.org}
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## License
|
| 223 |
+
|
| 224 |
+
The connectome is released under CC-BY 4.0 by the FlyEM Project Team (HHMI Janelia), the University of Cambridge,
|
| 225 |
+
the MRC Laboratory of Molecular Biology, and Google Research. This checkpoint is a derivative and carries the same
|
| 226 |
+
license.
|
| 227 |
+
|
| 228 |
+
Prior art: [ngxson/fly-llm-hf](https://huggingface.co/ngxson/fly-llm-hf) (frozen MaleCNS reservoir LM) and
|
| 229 |
+
[eob/gpt-fly](https://huggingface.co/eob/gpt-fly) (FlyWire-masked GPT-2). Code and experiment:
|
| 230 |
+
[github.com/QuixiAI/FlyGPT](https://github.com/QuixiAI/FlyGPT).
|
full-cns/config.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "flygpt",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"FlyGPTForCausalLM"
|
| 5 |
+
],
|
| 6 |
+
"auto_map": {
|
| 7 |
+
"AutoConfig": "configuration_flygpt.FlyGPTConfig",
|
| 8 |
+
"AutoModelForCausalLM": "modeling_flygpt.FlyGPTForCausalLM"
|
| 9 |
+
},
|
| 10 |
+
"vocab_size": 65,
|
| 11 |
+
"num_neurons": 160514,
|
| 12 |
+
"num_edges": 10402842,
|
| 13 |
+
"embedding_dim": 32,
|
| 14 |
+
"num_input_neurons": 2048,
|
| 15 |
+
"num_output_neurons": 4096,
|
| 16 |
+
"microsteps": 2,
|
| 17 |
+
"activation": "tanh",
|
| 18 |
+
"learned_leak": true,
|
| 19 |
+
"leak_init": 0.5,
|
| 20 |
+
"degree_normalization": true,
|
| 21 |
+
"init_scale": 0.65,
|
| 22 |
+
"dtype": "bfloat16",
|
| 23 |
+
"training_steps": 16700,
|
| 24 |
+
"graph": {
|
| 25 |
+
"graph_name": "cns_full",
|
| 26 |
+
"condition": "real",
|
| 27 |
+
"control_seed": 1,
|
| 28 |
+
"source": "malecns-v1.0",
|
| 29 |
+
"region_filter": "whole_cns",
|
| 30 |
+
"min_synapses": 3,
|
| 31 |
+
"hash": "d53f26f9bf6f7b8c9944e28fdd4721d2c0a237e7534f9160977c70e6c81ac6c2"
|
| 32 |
+
},
|
| 33 |
+
"training_state": {
|
| 34 |
+
"status": "trained",
|
| 35 |
+
"condition": "real",
|
| 36 |
+
"seed": 1,
|
| 37 |
+
"step": 15375,
|
| 38 |
+
"val_loss": 1.5957297205924987,
|
| 39 |
+
"checkpoint": "checkpoints/flygpt-v0-fullcns/cns_full/real_seed1.pt",
|
| 40 |
+
"project": "flygpt-v0-fullcns"
|
| 41 |
+
}
|
| 42 |
+
}
|
full-cns/configuration_flygpt.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FlyGPT configuration for Hugging Face `transformers` (loaded with trust_remote_code=True)."""
|
| 2 |
+
from transformers import PretrainedConfig
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class FlyGPTConfig(PretrainedConfig):
|
| 6 |
+
model_type = "flygpt"
|
| 7 |
+
|
| 8 |
+
def __init__(
|
| 9 |
+
self,
|
| 10 |
+
vocab_size: int = 65,
|
| 11 |
+
num_neurons: int = 0,
|
| 12 |
+
num_edges: int = 0,
|
| 13 |
+
embedding_dim: int = 32,
|
| 14 |
+
num_input_neurons: int = 256,
|
| 15 |
+
num_output_neurons: int = 512,
|
| 16 |
+
microsteps: int = 2,
|
| 17 |
+
activation: str = "tanh",
|
| 18 |
+
learned_leak: bool = True,
|
| 19 |
+
leak_init: float = 0.5,
|
| 20 |
+
degree_normalization: bool = True,
|
| 21 |
+
init_scale: float = 0.1,
|
| 22 |
+
graph: dict | None = None,
|
| 23 |
+
training_state: dict | None = None,
|
| 24 |
+
**kwargs,
|
| 25 |
+
):
|
| 26 |
+
self.vocab_size = vocab_size
|
| 27 |
+
self.num_neurons = num_neurons
|
| 28 |
+
self.num_edges = num_edges
|
| 29 |
+
self.embedding_dim = embedding_dim
|
| 30 |
+
self.num_input_neurons = num_input_neurons
|
| 31 |
+
self.num_output_neurons = num_output_neurons
|
| 32 |
+
self.microsteps = microsteps
|
| 33 |
+
self.activation = activation
|
| 34 |
+
self.learned_leak = learned_leak
|
| 35 |
+
self.leak_init = leak_init
|
| 36 |
+
self.degree_normalization = degree_normalization
|
| 37 |
+
self.init_scale = init_scale
|
| 38 |
+
self.graph = graph or {} # source, hash, region filter, stats, provenance
|
| 39 |
+
self.training_state = training_state or {} # "init" or the run that produced the values
|
| 40 |
+
super().__init__(**kwargs)
|
full-cns/generation_config.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"do_sample": true,
|
| 3 |
+
"temperature": 0.8,
|
| 4 |
+
"max_new_tokens": 300
|
| 5 |
+
}
|
full-cns/graph_metadata.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"graph_name": "cns_full",
|
| 3 |
+
"condition": "real",
|
| 4 |
+
"control_seed": 1,
|
| 5 |
+
"source": "malecns-v1.0",
|
| 6 |
+
"region_filter": "whole_cns",
|
| 7 |
+
"min_synapses": 3,
|
| 8 |
+
"selector": "directed_core",
|
| 9 |
+
"target_neurons": 200000,
|
| 10 |
+
"hash": "d53f26f9bf6f7b8c9944e28fdd4721d2c0a237e7534f9160977c70e6c81ac6c2",
|
| 11 |
+
"stats": {
|
| 12 |
+
"candidate_pool": 164587,
|
| 13 |
+
"candidate_edges": 25563096,
|
| 14 |
+
"min_synapses": 3,
|
| 15 |
+
"target_neurons": 200000,
|
| 16 |
+
"edges_after_threshold": 10510984,
|
| 17 |
+
"scc_after_threshold": 160514,
|
| 18 |
+
"note": "SCC no larger than target; no pruning or trimming applied",
|
| 19 |
+
"core_k": 0,
|
| 20 |
+
"final_neurons": 160514,
|
| 21 |
+
"final_edges": 10402842,
|
| 22 |
+
"source": "malecns-v1.0",
|
| 23 |
+
"n_neurons": 160514,
|
| 24 |
+
"n_edges": 10402842,
|
| 25 |
+
"synaptic_contacts": 102352176.0,
|
| 26 |
+
"largest_scc_fraction": 1.0,
|
| 27 |
+
"reciprocal_pairs": 1006326,
|
| 28 |
+
"in_degree": {
|
| 29 |
+
"mean": 64.80956178277285,
|
| 30 |
+
"min": 1,
|
| 31 |
+
"max": 9193
|
| 32 |
+
},
|
| 33 |
+
"out_degree": {
|
| 34 |
+
"mean": 64.80956178277285,
|
| 35 |
+
"min": 1,
|
| 36 |
+
"max": 7749
|
| 37 |
+
},
|
| 38 |
+
"density": 0.00040376518900508274,
|
| 39 |
+
"hash": "d53f26f9bf6f7b8c9944e28fdd4721d2c0a237e7534f9160977c70e6c81ac6c2",
|
| 40 |
+
"region_breakdown": {
|
| 41 |
+
"optic_lobe": 91082,
|
| 42 |
+
"central_brain": 36625,
|
| 43 |
+
"vnc": 19300,
|
| 44 |
+
"other": 13454,
|
| 45 |
+
"unannotated": 53
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
"diagnostics": {
|
| 49 |
+
"provenance": {
|
| 50 |
+
"control": "real"
|
| 51 |
+
},
|
| 52 |
+
"io": {
|
| 53 |
+
"n_input": 2048,
|
| 54 |
+
"n_output": 4096,
|
| 55 |
+
"input_rule": "top_out_degree",
|
| 56 |
+
"output_rule": "top_in_degree",
|
| 57 |
+
"overlap": 1381
|
| 58 |
+
},
|
| 59 |
+
"diagnostics": {
|
| 60 |
+
"n_neurons": 160514,
|
| 61 |
+
"n_edges": 10402842,
|
| 62 |
+
"largest_scc_fraction": 1.0,
|
| 63 |
+
"reciprocal_pairs": 1006326,
|
| 64 |
+
"io_reachable_fraction": 1.0,
|
| 65 |
+
"io_path_median": 1.0,
|
| 66 |
+
"io_path_p90": 1.0,
|
| 67 |
+
"io_path_max": 1.0
|
| 68 |
+
},
|
| 69 |
+
"gate": {
|
| 70 |
+
"microsteps": 2,
|
| 71 |
+
"max_p90_hops": 6,
|
| 72 |
+
"min_reachable_fraction": 0.95,
|
| 73 |
+
"reachable_ok": true,
|
| 74 |
+
"p90_ok": true,
|
| 75 |
+
"passed": true
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
}
|
full-cns/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:63dc73657502dad71a475dc319b0acf38a6d6b89da57954141f08fd728f7441a
|
| 3 |
+
size 148288294
|
full-cns/modeling_flygpt.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FlyGPT: a character-level language model whose recurrent core is a real subgraph of the
|
| 2 |
+
fruit-fly connectome (MaleCNS v1.0). Hugging Face `transformers` implementation; self-contained.
|
| 3 |
+
|
| 4 |
+
Dynamics (one scalar state per neuron, plan.md §7 of the FlyGPT spec):
|
| 5 |
+
|
| 6 |
+
proposal_i = tanh( sum_j W_ij h_j / sqrt(in_degree_i) + external_input_i + bias_i )
|
| 7 |
+
h_i_new = (1 - leak_i) * h_i + leak_i * proposal_i
|
| 8 |
+
|
| 9 |
+
The connectome is stored in `model.safetensors` as integer tensors (`graph.*`); only the learned
|
| 10 |
+
per-edge values and the adapters are floating point (bf16 on disk). The sparse recurrent matmul runs
|
| 11 |
+
in fp32: through the fused kernels of the `connectome-kernels` package when it is installed and a
|
| 12 |
+
CUDA device is used (training speed), else through torch.sparse COO (rows = destination). Both give
|
| 13 |
+
the same logits and gradients.
|
| 14 |
+
"""
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import math
|
| 18 |
+
from dataclasses import dataclass
|
| 19 |
+
from typing import Optional
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
import torch.nn as nn
|
| 23 |
+
import torch.nn.functional as F
|
| 24 |
+
from transformers import PreTrainedModel
|
| 25 |
+
from transformers.generation import GenerationMixin
|
| 26 |
+
from transformers.utils import ModelOutput
|
| 27 |
+
|
| 28 |
+
from .configuration_flygpt import FlyGPTConfig
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class FlyGPTOutput(ModelOutput):
|
| 33 |
+
loss: Optional[torch.FloatTensor] = None
|
| 34 |
+
logits: Optional[torch.FloatTensor] = None
|
| 35 |
+
state: Optional[torch.FloatTensor] = None # [B, N] neuron states after the last character
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class FlyGraph(nn.Module):
|
| 39 |
+
"""The anatomy. Integer buffers only; never trained."""
|
| 40 |
+
|
| 41 |
+
def __init__(self, num_neurons: int, num_edges: int, num_input: int, num_output: int):
|
| 42 |
+
super().__init__()
|
| 43 |
+
self.register_buffer("edge_index", torch.zeros(2, num_edges, dtype=torch.int32)) # [source, destination]
|
| 44 |
+
self.register_buffer("synapse_count", torch.zeros(num_edges, dtype=torch.int32)) # MaleCNS synaptic contacts
|
| 45 |
+
self.register_buffer("node_id", torch.zeros(num_neurons, dtype=torch.int64)) # MaleCNS body ids
|
| 46 |
+
self.register_buffer("input_nodes", torch.zeros(num_input, dtype=torch.int64))
|
| 47 |
+
self.register_buffer("output_nodes", torch.zeros(num_output, dtype=torch.int64))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class FlyRecurrentCore(nn.Module):
|
| 51 |
+
"""The learned state: one value per real edge, plus per-neuron bias and leak."""
|
| 52 |
+
|
| 53 |
+
def __init__(self, num_neurons: int, num_edges: int, leak_init: float, learned_leak: bool):
|
| 54 |
+
super().__init__()
|
| 55 |
+
self.edge_values = nn.Parameter(torch.zeros(num_edges))
|
| 56 |
+
self.bias = nn.Parameter(torch.zeros(num_neurons))
|
| 57 |
+
self.raw_leak = nn.Parameter(torch.full((num_neurons,), math.log(leak_init / (1 - leak_init))),
|
| 58 |
+
requires_grad=learned_leak)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class FlyGPTPreTrainedModel(PreTrainedModel):
|
| 62 |
+
config_class = FlyGPTConfig
|
| 63 |
+
base_model_prefix = "flygpt"
|
| 64 |
+
_is_stateful = True
|
| 65 |
+
_supports_cache_class = False
|
| 66 |
+
supports_gradient_checkpointing = False
|
| 67 |
+
|
| 68 |
+
def _init_weights(self, module):
|
| 69 |
+
if isinstance(module, FlyRecurrentCore):
|
| 70 |
+
nn.init.normal_(module.edge_values, std=self.config.init_scale)
|
| 71 |
+
nn.init.zeros_(module.bias)
|
| 72 |
+
elif isinstance(module, nn.Linear):
|
| 73 |
+
nn.init.normal_(module.weight, std=0.02)
|
| 74 |
+
if module.bias is not None:
|
| 75 |
+
nn.init.zeros_(module.bias)
|
| 76 |
+
elif isinstance(module, nn.Embedding):
|
| 77 |
+
nn.init.normal_(module.weight, std=1.0)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class FlyGPTForCausalLM(FlyGPTPreTrainedModel, GenerationMixin):
|
| 81 |
+
def __init__(self, config: FlyGPTConfig):
|
| 82 |
+
super().__init__(config)
|
| 83 |
+
c = config
|
| 84 |
+
self.graph = FlyGraph(c.num_neurons, c.num_edges, c.num_input_neurons, c.num_output_neurons)
|
| 85 |
+
self.recurrent = FlyRecurrentCore(c.num_neurons, c.num_edges, c.leak_init, c.learned_leak)
|
| 86 |
+
self.embed = nn.Embedding(c.vocab_size, c.embedding_dim)
|
| 87 |
+
self.input_proj = nn.Linear(c.embedding_dim, c.num_input_neurons)
|
| 88 |
+
self.lm_head = nn.Linear(c.num_output_neurons, c.vocab_size)
|
| 89 |
+
self.post_init()
|
| 90 |
+
|
| 91 |
+
# ---- sparse recurrent matrix -------------------------------------------------------------
|
| 92 |
+
@property
|
| 93 |
+
def num_neurons(self) -> int:
|
| 94 |
+
return self.config.num_neurons
|
| 95 |
+
|
| 96 |
+
def edge_scale(self) -> torch.Tensor:
|
| 97 |
+
"""1/sqrt(in_degree) per edge (degree normalization), or ones."""
|
| 98 |
+
dst = self.graph.edge_index[1].long()
|
| 99 |
+
if not self.config.degree_normalization:
|
| 100 |
+
return torch.ones_like(dst, dtype=torch.float32)
|
| 101 |
+
in_deg = torch.bincount(dst, minlength=self.num_neurons).clamp(min=1).float()
|
| 102 |
+
return 1.0 / in_deg[dst].sqrt()
|
| 103 |
+
|
| 104 |
+
def sparse_weight(self) -> torch.Tensor:
|
| 105 |
+
src, dst = self.graph.edge_index[0].long(), self.graph.edge_index[1].long()
|
| 106 |
+
values = self.recurrent.edge_values.float() * self.edge_scale()
|
| 107 |
+
return torch.sparse_coo_tensor(torch.stack([dst, src]), values, (self.num_neurons, self.num_neurons))
|
| 108 |
+
|
| 109 |
+
def dense_weight(self) -> torch.Tensor:
|
| 110 |
+
"""Convenience for analysis; [N, N] with rows = destination. Never used in the forward pass."""
|
| 111 |
+
return self.sparse_weight().to_dense()
|
| 112 |
+
|
| 113 |
+
@property
|
| 114 |
+
def leak(self) -> torch.Tensor:
|
| 115 |
+
return torch.sigmoid(self.recurrent.raw_leak.float())
|
| 116 |
+
|
| 117 |
+
# ---- dynamics ------------------------------------------------------------------------------
|
| 118 |
+
def init_state(self, batch: int, device=None) -> torch.Tensor:
|
| 119 |
+
return torch.zeros(batch, self.num_neurons, device=device or self.recurrent.edge_values.device)
|
| 120 |
+
|
| 121 |
+
def drive(self, x: torch.Tensor) -> torch.Tensor:
|
| 122 |
+
d = torch.zeros(x.shape[0], self.num_neurons, device=x.device, dtype=torch.float32)
|
| 123 |
+
d[:, self.graph.input_nodes] = self.input_proj(self.embed(x)).float()
|
| 124 |
+
return d
|
| 125 |
+
|
| 126 |
+
def step(self, state: torch.Tensor, x: torch.Tensor, W: torch.Tensor | None = None) -> torch.Tensor:
|
| 127 |
+
W = self.sparse_weight() if W is None else W
|
| 128 |
+
drive, leak, bias = self.drive(x), self.leak, self.recurrent.bias.float()
|
| 129 |
+
for _ in range(self.config.microsteps):
|
| 130 |
+
incoming = torch.sparse.mm(W, state.float().T).T
|
| 131 |
+
proposal = torch.tanh(incoming + drive + bias)
|
| 132 |
+
state = (1 - leak) * state + leak * proposal
|
| 133 |
+
return state
|
| 134 |
+
|
| 135 |
+
def logits_from_state(self, state: torch.Tensor) -> torch.Tensor:
|
| 136 |
+
return self.lm_head(state[:, self.graph.output_nodes].to(self.lm_head.weight.dtype)).float()
|
| 137 |
+
|
| 138 |
+
# ---- fused CUDA path via the connectome-kernels package (optional, used for training) ------------
|
| 139 |
+
def _fused_graph(self):
|
| 140 |
+
from connectome_kernels import SparseGraph
|
| 141 |
+
dev = self.graph.edge_index.device
|
| 142 |
+
if getattr(self, "_fg", None) is None or self._fg_device != dev:
|
| 143 |
+
self._fg = SparseGraph(self.graph.edge_index[0].long(), self.graph.edge_index[1].long(), self.num_neurons,
|
| 144 |
+
self.graph.input_nodes)
|
| 145 |
+
self._fg_device = dev
|
| 146 |
+
return self._fg
|
| 147 |
+
|
| 148 |
+
def _fused_available(self, device) -> bool:
|
| 149 |
+
if device.type != "cuda":
|
| 150 |
+
return False
|
| 151 |
+
if not hasattr(self, "_fused_ok"):
|
| 152 |
+
try:
|
| 153 |
+
from connectome_kernels import available
|
| 154 |
+
self._fused_ok = available()
|
| 155 |
+
except Exception:
|
| 156 |
+
self._fused_ok = False
|
| 157 |
+
return self._fused_ok
|
| 158 |
+
|
| 159 |
+
def _forward_fused(self, input_ids, state):
|
| 160 |
+
from connectome_kernels import sparse_recurrence
|
| 161 |
+
drives = self.input_proj(self.embed(input_ids)).float().permute(1, 2, 0).contiguous() # [T, n_in, B]
|
| 162 |
+
vals = self.recurrent.edge_values.float() * self.edge_scale()
|
| 163 |
+
out = sparse_recurrence(vals, self.leak, self.recurrent.bias.float(), drives, state,
|
| 164 |
+
self._fused_graph(), self.config.microsteps) # [T, B, N]
|
| 165 |
+
logits = self.lm_head(out[:, :, self.graph.output_nodes].to(self.lm_head.weight.dtype)).float().permute(1, 0, 2)
|
| 166 |
+
return logits, out[-1]
|
| 167 |
+
|
| 168 |
+
def forward(self, input_ids: torch.LongTensor, state: Optional[torch.Tensor] = None,
|
| 169 |
+
labels: Optional[torch.LongTensor] = None, use_cache: Optional[bool] = None,
|
| 170 |
+
return_dict: Optional[bool] = None, **kwargs) -> FlyGPTOutput:
|
| 171 |
+
B, T = input_ids.shape
|
| 172 |
+
state = self.init_state(B, input_ids.device) if state is None else state
|
| 173 |
+
if self._fused_available(input_ids.device):
|
| 174 |
+
logits, state = self._forward_fused(input_ids, state)
|
| 175 |
+
else:
|
| 176 |
+
W = self.sparse_weight()
|
| 177 |
+
outs = []
|
| 178 |
+
for t in range(T):
|
| 179 |
+
state = self.step(state, input_ids[:, t], W)
|
| 180 |
+
outs.append(self.logits_from_state(state))
|
| 181 |
+
logits = torch.stack(outs, 1)
|
| 182 |
+
loss = None
|
| 183 |
+
if labels is not None:
|
| 184 |
+
loss = F.cross_entropy(logits[:, :-1].reshape(-1, logits.shape[-1]), labels[:, 1:].reshape(-1))
|
| 185 |
+
return FlyGPTOutput(loss=loss, logits=logits, state=state)
|
| 186 |
+
|
| 187 |
+
# ---- generation: carry the neuron state instead of a KV cache ------------------------------
|
| 188 |
+
@classmethod
|
| 189 |
+
def _supports_default_dynamic_cache(cls) -> bool:
|
| 190 |
+
return False # stateful recurrent model: no KV cache, the neuron state is carried in `state`
|
| 191 |
+
|
| 192 |
+
def prepare_inputs_for_generation(self, input_ids, state=None, **kwargs):
|
| 193 |
+
if state is not None:
|
| 194 |
+
input_ids = input_ids[:, -1:]
|
| 195 |
+
return {"input_ids": input_ids, "state": state}
|
| 196 |
+
|
| 197 |
+
def _update_model_kwargs_for_generation(self, outputs, model_kwargs, is_encoder_decoder=False, **kwargs):
|
| 198 |
+
model_kwargs["state"] = outputs.state
|
| 199 |
+
return model_kwargs
|
full-cns/tokenizer.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "1.0",
|
| 3 |
+
"truncation": null,
|
| 4 |
+
"padding": null,
|
| 5 |
+
"added_tokens": [],
|
| 6 |
+
"normalizer": null,
|
| 7 |
+
"pre_tokenizer": {
|
| 8 |
+
"type": "Split",
|
| 9 |
+
"pattern": {
|
| 10 |
+
"Regex": "[\\s\\S]"
|
| 11 |
+
},
|
| 12 |
+
"behavior": "Isolated",
|
| 13 |
+
"invert": false
|
| 14 |
+
},
|
| 15 |
+
"post_processor": null,
|
| 16 |
+
"decoder": {
|
| 17 |
+
"type": "Fuse"
|
| 18 |
+
},
|
| 19 |
+
"model": {
|
| 20 |
+
"type": "WordLevel",
|
| 21 |
+
"vocab": {
|
| 22 |
+
"\n": 0,
|
| 23 |
+
" ": 1,
|
| 24 |
+
"!": 2,
|
| 25 |
+
"$": 3,
|
| 26 |
+
"&": 4,
|
| 27 |
+
"'": 5,
|
| 28 |
+
",": 6,
|
| 29 |
+
"-": 7,
|
| 30 |
+
".": 8,
|
| 31 |
+
"3": 9,
|
| 32 |
+
":": 10,
|
| 33 |
+
";": 11,
|
| 34 |
+
"?": 12,
|
| 35 |
+
"A": 13,
|
| 36 |
+
"B": 14,
|
| 37 |
+
"C": 15,
|
| 38 |
+
"D": 16,
|
| 39 |
+
"E": 17,
|
| 40 |
+
"F": 18,
|
| 41 |
+
"G": 19,
|
| 42 |
+
"H": 20,
|
| 43 |
+
"I": 21,
|
| 44 |
+
"J": 22,
|
| 45 |
+
"K": 23,
|
| 46 |
+
"L": 24,
|
| 47 |
+
"M": 25,
|
| 48 |
+
"N": 26,
|
| 49 |
+
"O": 27,
|
| 50 |
+
"P": 28,
|
| 51 |
+
"Q": 29,
|
| 52 |
+
"R": 30,
|
| 53 |
+
"S": 31,
|
| 54 |
+
"T": 32,
|
| 55 |
+
"U": 33,
|
| 56 |
+
"V": 34,
|
| 57 |
+
"W": 35,
|
| 58 |
+
"X": 36,
|
| 59 |
+
"Y": 37,
|
| 60 |
+
"Z": 38,
|
| 61 |
+
"a": 39,
|
| 62 |
+
"b": 40,
|
| 63 |
+
"c": 41,
|
| 64 |
+
"d": 42,
|
| 65 |
+
"e": 43,
|
| 66 |
+
"f": 44,
|
| 67 |
+
"g": 45,
|
| 68 |
+
"h": 46,
|
| 69 |
+
"i": 47,
|
| 70 |
+
"j": 48,
|
| 71 |
+
"k": 49,
|
| 72 |
+
"l": 50,
|
| 73 |
+
"m": 51,
|
| 74 |
+
"n": 52,
|
| 75 |
+
"o": 53,
|
| 76 |
+
"p": 54,
|
| 77 |
+
"q": 55,
|
| 78 |
+
"r": 56,
|
| 79 |
+
"s": 57,
|
| 80 |
+
"t": 58,
|
| 81 |
+
"u": 59,
|
| 82 |
+
"v": 60,
|
| 83 |
+
"w": 61,
|
| 84 |
+
"x": 62,
|
| 85 |
+
"y": 63,
|
| 86 |
+
"z": 64
|
| 87 |
+
},
|
| 88 |
+
"unk_token": "<unk>"
|
| 89 |
+
}
|
| 90 |
+
}
|
full-cns/tokenizer_config.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tokenizer_class": "PreTrainedTokenizerFast",
|
| 3 |
+
"model_max_length": 10000000,
|
| 4 |
+
"clean_up_tokenization_spaces": false,
|
| 5 |
+
"add_prefix_space": false
|
| 6 |
+
}
|