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)# pip install -U transformers accelerate # 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
Model card: inference and training examples (both executed), five-seed result; fused connectome-kernels path in modeling code
Browse files- README.md +76 -2
- model.safetensors +1 -1
- modeling_flygpt.py +43 -8
- tokenizer_config.json +1 -1
README.md
CHANGED
|
@@ -81,7 +81,7 @@ h_i ← (1 − leak_i) h_i + leak_i · proposal_i (2 microsteps per chara
|
|
| 81 |
512 output neuron states → linear → 65 logits
|
| 82 |
```
|
| 83 |
|
| 84 |
-
##
|
| 85 |
|
| 86 |
```python
|
| 87 |
import torch
|
|
@@ -93,9 +93,83 @@ model = AutoModelForCausalLM.from_pretrained("QuixiAI/FlyGPT", trust_remote_code
|
|
| 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 |
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
## Citation
|
| 101 |
|
|
|
|
| 81 |
512 output neuron states → linear → 65 logits
|
| 82 |
```
|
| 83 |
|
| 84 |
+
## Inference
|
| 85 |
|
| 86 |
```python
|
| 87 |
import torch
|
|
|
|
| 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)
|
| 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", 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: does the wiring matter?
|
| 156 |
+
|
| 157 |
+
Pre-registered rule (before any result was seen): "wiring matters" is claimed only if all 5 paired
|
| 158 |
+
differences Δ = loss(scrambled) − loss(real) have the same sign **and** the mean Δ is at least 0.05 nats/char.
|
| 159 |
+
|
| 160 |
+
| seed | real | degree-preserving scramble | Δ |
|
| 161 |
+
|--:|--:|--:|--:|
|
| 162 |
+
| 1 | 1.7275 | 1.7450 | +0.0175 |
|
| 163 |
+
| 2 | 1.7285 | 1.7410 | +0.0125 |
|
| 164 |
+
| 3 | 1.7490 | 1.7594 | +0.0104 |
|
| 165 |
+
| 4 | 1.7516 | 1.7592 | +0.0076 |
|
| 166 |
+
| 5 | 1.7305 | 1.7377 | +0.0073 |
|
| 167 |
+
| **mean** | **1.7374** | **1.7485** | **+0.0111** |
|
| 168 |
+
|
| 169 |
+
Validation loss in nats/char at the end of 20,000 steps, same data order, batches, adapter init and edge-value RNG
|
| 170 |
+
stream per seed. Bigram reference on this split: 2.482. All five differences favour the real wiring, but the mean
|
| 171 |
+
gap is below the pre-registered minimum effect, so the verdict is: **no detectable difference at this scale.** The fly connectome learns
|
| 172 |
+
Shakespeare; whether its specific wiring helps, beyond its degree sequence, is not resolved at 5,000 neurons.
|
| 173 |
|
| 174 |
## Citation
|
| 175 |
|
model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 7495610
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d8c8b47fc37d85fb591f215a3cc54aa3d045e0edfb69a9d6116c1b1f87bbd317
|
| 3 |
size 7495610
|
modeling_flygpt.py
CHANGED
|
@@ -7,8 +7,10 @@ Dynamics (one scalar state per neuron, plan.md §7 of the FlyGPT spec):
|
|
| 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
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
"""
|
| 13 |
from __future__ import annotations
|
| 14 |
|
|
@@ -133,17 +135,50 @@ class FlyGPTForCausalLM(FlyGPTPreTrainedModel, GenerationMixin):
|
|
| 133 |
def logits_from_state(self, state: torch.Tensor) -> torch.Tensor:
|
| 134 |
return self.lm_head(state[:, self.graph.output_nodes].to(self.lm_head.weight.dtype)).float()
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
def forward(self, input_ids: torch.LongTensor, state: Optional[torch.Tensor] = None,
|
| 137 |
labels: Optional[torch.LongTensor] = None, use_cache: Optional[bool] = None,
|
| 138 |
return_dict: Optional[bool] = None, **kwargs) -> FlyGPTOutput:
|
| 139 |
B, T = input_ids.shape
|
| 140 |
state = self.init_state(B, input_ids.device) if state is None else state
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
outs
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
| 147 |
loss = None
|
| 148 |
if labels is not None:
|
| 149 |
loss = F.cross_entropy(logits[:, :-1].reshape(-1, logits.shape[-1]), labels[:, 1:].reshape(-1))
|
|
|
|
| 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 |
|
|
|
|
| 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))
|
tokenizer_config.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"tokenizer_class": "PreTrainedTokenizerFast",
|
| 3 |
-
"model_max_length":
|
| 4 |
"clean_up_tokenization_spaces": false,
|
| 5 |
"add_prefix_space": false
|
| 6 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"tokenizer_class": "PreTrainedTokenizerFast",
|
| 3 |
+
"model_max_length": 10000000,
|
| 4 |
"clean_up_tokenization_spaces": false,
|
| 5 |
"add_prefix_space": false
|
| 6 |
}
|