Text Generation
Transformers
Safetensors
Russian
fly
connectome
reservoir-computing
echo-state-network
fruit-fly
drosophila
malecns
russian
custom_code
Instructions to use igorktech/nanofly-decoder-ru with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use igorktech/nanofly-decoder-ru with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="igorktech/nanofly-decoder-ru", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("igorktech/nanofly-decoder-ru", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use igorktech/nanofly-decoder-ru with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "igorktech/nanofly-decoder-ru" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "igorktech/nanofly-decoder-ru", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/igorktech/nanofly-decoder-ru
- SGLang
How to use igorktech/nanofly-decoder-ru 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 "igorktech/nanofly-decoder-ru" \ --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": "igorktech/nanofly-decoder-ru", "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 "igorktech/nanofly-decoder-ru" \ --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": "igorktech/nanofly-decoder-ru", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use igorktech/nanofly-decoder-ru with Docker Model Runner:
docker model run hf.co/igorktech/nanofly-decoder-ru
Download modeling_fly.py from igorktech/nanofly-decoder-ru: direct link, hf CLI and curl.
- Browser
- Download file 14.2 kB
-
https://huggingface.co/igorktech/nanofly-decoder-ru/resolve/main/modeling_fly.py
- Command line
-
hf download hf://igorktech/nanofly-decoder-ru/modeling_fly.py
-
curl -L -o modeling_fly.py https://huggingface.co/igorktech/nanofly-decoder-ru/resolve/main/modeling_fly.py
14.2 kB
| """Fly: a fruit-fly connectome as the recurrent layer of a language model. | |
| State update, one line per neuron i, repeated `ticks` times per token: | |
| x_i <- (1 - a_i) * x_i + a_i * tanh( rho * g_i * sum_j W_ij x_j + u_i + b_i ) | |
| `W` is anatomy and is not trained here: signed, row-normalised synapse counts from MaleCNS v1.0. | |
| `u` is the input current — the token embedding fanned into sensory neurons through a delay line, | |
| plus, for the encoder-decoder model, a constant current on the olfactory neurons that carries the | |
| post being answered. The readout is a linear head on a subset of neurons (all, descending, motor…). | |
| This file is the inference/fine-tuning copy that ships with the weights. Training lives in the | |
| project repo (see `config.source_repo`), where the connectome is rebuilt from the release tables. | |
| """ | |
| from dataclasses import dataclass | |
| from typing import Optional, Tuple | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from transformers import GenerationMixin, PreTrainedModel | |
| from transformers.utils import ModelOutput | |
| from .configuration_fly import FlyConfig | |
| # This file is the single source for both published variants. `export_hf.py::write_code` ships it | |
| # unchanged for the encoder-decoder and, for the decoder, rewrites the `Fly` prefix to `Fly` | |
| # and drops the conditional tail, which starts at the top-level definition of `_hash_embed` and runs | |
| # to the end of the file. Three invariants: | |
| # 1. the prefix is spelled `Fly` (class) and `fly` (module) nowhere except where it must | |
| # be renamed — never put either literal in a URL or a message meant to survive; | |
| # 2. everything below that definition belongs to the post channel, and nothing above it refers to | |
| # anything below; | |
| # 3. the relative import above is rewritten too, so the pair always travels together. | |
| # The export writes the folder, loads it back and compares logits, so a broken rename fails loudly — | |
| # as it did the first time this very comment was written with the marker spelled out in full. | |
| class FlyCache: | |
| """What carries over between generate() steps: neuron state and the delay line of token ids.""" | |
| is_compileable = False | |
| def __init__(self, state, last_tokens, seq_len=0): | |
| self.state = state # [B, N] neuron activations | |
| self.last_tokens = last_tokens # [B, delay], most recent token first | |
| self.seq_len = seq_len | |
| def get_seq_length(self, layer_idx=0): | |
| return self.seq_len | |
| def get_max_cache_shape(self): | |
| return None | |
| def reorder_cache(self, beam_idx): | |
| self.state = self.state.index_select(0, beam_idx.to(self.state.device)) | |
| self.last_tokens = self.last_tokens.index_select(0, beam_idx.to(self.last_tokens.device)) | |
| return self | |
| class FlyOutput(ModelOutput): | |
| last_hidden_state: torch.FloatTensor = None | |
| cache_params: Optional[FlyCache] = None | |
| hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None | |
| class FlyCausalLMOutput(ModelOutput): | |
| loss: Optional[torch.FloatTensor] = None | |
| logits: torch.FloatTensor = None | |
| cache_params: Optional[FlyCache] = None | |
| hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None | |
| class _SpMM(torch.autograd.Function): | |
| """y = W @ x with W a fixed sparse CSR matrix; the backward pass reuses a cached transpose.""" | |
| def forward(ctx, x, W, WT): | |
| ctx.WT = WT | |
| return torch.sparse.mm(W, x) | |
| def backward(ctx, g): | |
| return torch.sparse.mm(ctx.WT, g.contiguous()), None, None | |
| class FlyPreTrainedModel(PreTrainedModel): | |
| config_class = FlyConfig | |
| base_model_prefix = "brain" | |
| supports_gradient_checkpointing = False | |
| _is_stateful = True | |
| _no_split_modules = [] | |
| def _supports_default_dynamic_cache(cls): | |
| # the model keeps its own FlyCache; generate() must not build a KV cache for it | |
| return False | |
| def _init_weights(self, module): | |
| if isinstance(module, nn.Linear): | |
| nn.init.normal_(module.weight, std=min(0.02, module.in_features ** -0.5)) | |
| if module.bias is not None: | |
| nn.init.zeros_(module.bias) | |
| elif isinstance(module, nn.Embedding): | |
| nn.init.normal_(module.weight, std=1.0) | |
| if module.padding_idx is not None: | |
| module.weight.data[module.padding_idx].zero_() | |
| elif isinstance(module, FlyModel): | |
| # the raw parameters need their own init: a zeroed in_proj means no current ever reaches | |
| # the neurons, so a model built from the config alone would be deaf. A reservoir wants | |
| # O(1) drive, not transformer-scale. | |
| nn.init.normal_(module.in_proj, std=module.config.d_emb ** -0.5) | |
| nn.init.ones_(module.gain) | |
| nn.init.zeros_(module.bias) | |
| nn.init.zeros_(module.leak_logit) # sigmoid(0) = 0.5 | |
| nn.init.zeros_(module.log_rho) # exp(0) = 1 | |
| class FlyModel(FlyPreTrainedModel): | |
| """The brain: connectome recurrence, token and post input, neuron-subset readout.""" | |
| def __init__(self, config: FlyConfig): | |
| super().__init__(config) | |
| N, E, d = config.n_neurons, config.n_edges, config.d_emb | |
| self.emb = nn.Embedding(config.vocab_size, d, padding_idx=config.pad_token_id) | |
| self.in_proj = nn.Parameter(torch.empty(config.n_token_input, d)) | |
| self.bias = nn.Parameter(torch.zeros(N, 1)) | |
| self.gain = nn.Parameter(torch.ones(N, 1)) | |
| self.log_rho = nn.Parameter(torch.zeros(())) | |
| self.leak_logit = nn.Parameter(torch.zeros(N, 1)) | |
| self.news_proj = None | |
| if config.conditional: | |
| out = config.news_glom if config.news_mode == "glomeruli" else config.n_news_input | |
| self.news_proj = nn.Linear(config.news_dim, out) | |
| if config.news_mode == "glomeruli": | |
| self.register_buffer("news_glom_of", torch.zeros(config.n_news_input, dtype=torch.long)) | |
| # connectome in CSR by target neuron: crow [N+1], col = source neuron, w = signed strength | |
| self.register_buffer("crow", torch.zeros(N + 1, dtype=torch.int32)) | |
| self.register_buffer("col", torch.zeros(E, dtype=torch.int32)) | |
| self.register_buffer("w", torch.zeros(E)) | |
| self.register_buffer("token_idx", torch.zeros(config.n_token_input, dtype=torch.long)) | |
| self.register_buffer("news_idx", torch.zeros(config.n_news_input, dtype=torch.long)) | |
| self.register_buffer("readout_idx", torch.zeros(config.readout_size, dtype=torch.long)) | |
| # delay line: slot j covers token_idx[bounds[j]:bounds[j+1]] and sees token t-j | |
| self.register_buffer("bounds", torch.zeros(config.delay + 1, dtype=torch.long)) | |
| self._wt_key = None | |
| self.post_init() | |
| def get_input_embeddings(self): | |
| return self.emb | |
| def set_input_embeddings(self, value): | |
| self.emb = value | |
| def connectome(self): | |
| N = self.config.n_neurons | |
| W = torch.sparse_csr_tensor(self.crow, self.col, self.w, size=(N, N)) | |
| key = (self.w.data_ptr(), self.w._version, self.w.device) | |
| if self._wt_key != key: | |
| self._wt = W.t().to_sparse_csr() | |
| self._wt_key = key | |
| return W, self._wt | |
| def drive_from_tokens(self, prev_ids, inputs_embeds): | |
| """Delay-line current: input group j receives the embedding of token t-j. -> [T, n_in, B]""" | |
| k = self.config.delay | |
| B, T, _ = inputs_embeds.shape | |
| ext = torch.cat([self.emb(prev_ids.flip(1)), inputs_embeds], dim=1) # oldest first, then new | |
| bounds = self.bounds.tolist() | |
| parts = [] | |
| for j in range(k): | |
| e_j = ext[:, k - j: k - j + T] # token t-j for t in [0, T) | |
| parts.append(e_j @ self.in_proj[bounds[j]:bounds[j + 1]].t()) | |
| return torch.cat(parts, dim=-1).permute(1, 2, 0).contiguous() | |
| def forward( | |
| self, | |
| input_ids=None, | |
| attention_mask=None, | |
| inputs_embeds=None, | |
| news_embeds=None, | |
| cache_params: Optional[FlyCache] = None, | |
| use_cache=None, | |
| output_hidden_states=None, | |
| return_dict=None, | |
| **kwargs, | |
| ): | |
| cfg = self.config | |
| use_cache = use_cache if use_cache is not None else cfg.use_cache | |
| return_dict = return_dict if return_dict is not None else getattr(cfg, "return_dict", True) | |
| if inputs_embeds is None: | |
| inputs_embeds = self.emb(input_ids) | |
| B, T, _ = inputs_embeds.shape | |
| N, k, dev = cfg.n_neurons, cfg.delay, inputs_embeds.device | |
| if cache_params is None: | |
| x = inputs_embeds.new_zeros(N, B) | |
| prev_ids = torch.full((B, k), cfg.pad_token_id, dtype=torch.long, device=dev) | |
| seq_len = 0 | |
| else: | |
| x = cache_params.state.t().contiguous() | |
| prev_ids = cache_params.last_tokens | |
| seq_len = cache_params.seq_len | |
| drive = self.drive_from_tokens(prev_ids, inputs_embeds) | |
| news_u = None | |
| if self.news_proj is not None and news_embeds is not None: | |
| news_u = self.news_proj(news_embeds.to(inputs_embeds.dtype)) | |
| if cfg.news_mode == "glomeruli": | |
| news_u = news_u.index_select(1, self.news_glom_of) | |
| news_u = news_u.t() # [n_news_input, B] | |
| W, WT = self.connectome() | |
| a = torch.sigmoid(self.leak_logit) | |
| scale = torch.exp(self.log_rho) * self.gain | |
| mask = attention_mask.to(x.dtype).t() if attention_mask is not None else None | |
| outs = [] | |
| for t in range(T): | |
| u = torch.zeros(N, B, device=dev, dtype=x.dtype) | |
| u = u.index_add(0, self.token_idx, drive[t]) | |
| if news_u is not None: | |
| u = u.index_add(0, self.news_idx, news_u) | |
| u = u + self.bias | |
| for _ in range(cfg.ticks): | |
| new = (1 - a) * x + a * torch.tanh(scale * _SpMM.apply(x, W, WT) + u) | |
| if mask is not None: # padding freezes the state | |
| m = mask[t][None, :] | |
| new = m * new + (1 - m) * x | |
| x = new | |
| outs.append(x.index_select(0, self.readout_idx)) | |
| hidden = torch.stack(outs, dim=0).permute(2, 0, 1) # [B, T, readout_size] | |
| cache = None | |
| if use_cache: | |
| ids = prev_ids if input_ids is None else torch.cat([input_ids.flip(1), prev_ids], dim=1)[:, :k] | |
| cache = FlyCache(x.t().contiguous(), ids, seq_len + T) | |
| if not return_dict: | |
| return tuple(v for v in [hidden, cache, (hidden,) if output_hidden_states else None] if v is not None) | |
| return FlyOutput(last_hidden_state=hidden, cache_params=cache, | |
| hidden_states=(hidden,) if output_hidden_states else None) | |
| def _build_head(config: FlyConfig): | |
| if config.head_type == "lowrank": | |
| return nn.Sequential( | |
| nn.Linear(config.readout_size, config.readout_rank, bias=False), | |
| nn.LayerNorm(config.readout_rank), | |
| nn.Linear(config.readout_rank, config.vocab_size), | |
| ) | |
| return nn.Sequential(nn.LayerNorm(config.readout_size), nn.Linear(config.readout_size, config.vocab_size)) | |
| class FlyForCausalLM(FlyPreTrainedModel, GenerationMixin): | |
| """Decoder-only: tokens in, tokens out. `news_embeds` is accepted but is None for this arch.""" | |
| def __init__(self, config: FlyConfig): | |
| super().__init__(config) | |
| self.brain = FlyModel(config) | |
| self.head = _build_head(config) | |
| self.post_init() | |
| def get_input_embeddings(self): | |
| return self.brain.emb | |
| def set_input_embeddings(self, value): | |
| self.brain.emb = value | |
| def get_output_embeddings(self): | |
| return self.head[-1] | |
| def prepare_inputs_for_generation(self, input_ids, cache_params=None, use_cache=None, | |
| attention_mask=None, news_embeds=None, **kwargs): | |
| if cache_params is not None: # feed only what the state has not seen | |
| input_ids = input_ids[:, cache_params.seq_len:] | |
| attention_mask = None | |
| model_inputs = { | |
| "input_ids": input_ids, | |
| "attention_mask": attention_mask, | |
| "cache_params": cache_params, | |
| "use_cache": use_cache if use_cache is not None else self.config.use_cache, | |
| } | |
| if news_embeds is not None: | |
| model_inputs["news_embeds"] = news_embeds | |
| return model_inputs | |
| def forward( | |
| self, | |
| input_ids=None, | |
| attention_mask=None, | |
| inputs_embeds=None, | |
| news_embeds=None, | |
| cache_params: Optional[FlyCache] = None, | |
| labels=None, | |
| use_cache=None, | |
| output_hidden_states=None, | |
| return_dict=None, | |
| logits_to_keep=0, | |
| **kwargs, | |
| ): | |
| return_dict = return_dict if return_dict is not None else getattr(self.config, "return_dict", True) | |
| out = self.brain( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| inputs_embeds=inputs_embeds, | |
| news_embeds=news_embeds, | |
| cache_params=cache_params, | |
| use_cache=use_cache, | |
| output_hidden_states=output_hidden_states, | |
| return_dict=True, | |
| ) | |
| h = out.last_hidden_state | |
| sl = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) and logits_to_keep > 0 else slice(None) | |
| logits = self.head(h[:, sl]) | |
| loss = None | |
| if labels is not None: | |
| loss = F.cross_entropy(logits[:, :-1].reshape(-1, logits.size(-1)).float(), | |
| labels[:, 1:].reshape(-1).to(logits.device), ignore_index=-100) | |
| if not return_dict: | |
| return tuple(v for v in [loss, logits, out.cache_params, out.hidden_states] if v is not None) | |
| return FlyCausalLMOutput(loss=loss, logits=logits, cache_params=out.cache_params, | |
| hidden_states=out.hidden_states) | |