Text Generation
Transformers
Safetensors
English
fwkv
feature-extraction
rnn
conversational
custom_code
Instructions to use FWKV/Fables with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use FWKV/Fables with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="FWKV/Fables", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("FWKV/Fables", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use FWKV/Fables with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "FWKV/Fables" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "FWKV/Fables", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/FWKV/Fables
- SGLang
How to use FWKV/Fables 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 "FWKV/Fables" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "FWKV/Fables", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'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 "FWKV/Fables" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "FWKV/Fables", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use FWKV/Fables with Docker Model Runner:
docker model run hf.co/FWKV/Fables
Download modeling_fwkv.py from FWKV/Fables: direct link, hf CLI and curl.
- Browser
- Download file 14.7 kB
-
https://huggingface.co/FWKV/Fables/resolve/main/modeling_fwkv.py
- Command line
-
hf download hf://FWKV/Fables/modeling_fwkv.py
-
curl -L -o modeling_fwkv.py https://huggingface.co/FWKV/Fables/resolve/main/modeling_fwkv.py
14.7 kB
| # modeling_fwkv.py | |
| import math | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from transformers import PreTrainedModel, AutoModel, AutoConfig | |
| from transformers.modeling_outputs import CausalLMOutputWithPast | |
| from transformers.generation import GenerationMixin | |
| from transformers.cache_utils import Cache # needed to detect Cache objects | |
| from .configuration_fwkv import FWKVConfig | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # utility functions | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def parallel_scan_decay(a: torch.Tensor, W: torch.Tensor) -> torch.Tensor: | |
| """REFERENCE implementation (Hillis-Steele). y_t = a_t + W * y_{t-1}. | |
| Kept for parity/testing with decay_scan; not used at inference time.""" | |
| W = W.to(dtype=a.dtype) | |
| val = a | |
| T = a.shape[1] | |
| d = 1 | |
| while d < T: | |
| shifted = F.pad(val[:, :-d, :], (0, 0, d, 0)) | |
| val = val + (W ** d) * shifted | |
| d *= 2 | |
| return val | |
| def decay_scan(a: torch.Tensor, W: torch.Tensor, state: torch.Tensor | None = None) -> torch.Tensor: | |
| """y_t = a_t + W * y_{t-1}, y_{-1} = state, as a causal FFT convolution in fp32. | |
| a: (B, T, d), W: (d,) with 0 < W < 1, state: (B, d) or None. | |
| Matches the training-time implementation exactly (same math as | |
| parallel_scan_decay, but computed as an FFT convolution for speed). | |
| """ | |
| with torch.autocast(device_type=a.device.type, enabled=False): | |
| a = a.float() | |
| W = W.float() | |
| B, T, d = a.shape | |
| if state is not None: | |
| first = a[:, :1] + W.view(1, 1, d) * state.float().unsqueeze(1) | |
| a = torch.cat([first, a[:, 1:]], dim=1) | |
| if T == 1: | |
| return a | |
| n = 1 << (2 * T - 1).bit_length() # >= 2T-1, avoids circular wrap | |
| t = torch.arange(T, device=a.device, dtype=a.dtype) | |
| K = torch.exp(t.unsqueeze(1) * torch.log(W).unsqueeze(0)) # (T, d) = W^t | |
| Fa = torch.fft.rfft(a, n=n, dim=1) | |
| Fk = torch.fft.rfft(K, n=n, dim=0) | |
| y = torch.fft.irfft(Fa * Fk.unsqueeze(0), n=n, dim=1)[:, :T] | |
| return y.contiguous() | |
| def chunked_cross_entropy(x, weight, labels, chunk_size=512, ignore_index=-100): | |
| x_2d = x.reshape(-1, x.size(-1)) | |
| labels_1d = labels.reshape(-1) | |
| N = x_2d.size(0) | |
| total_loss = x_2d.new_zeros((), dtype=torch.float32) | |
| total_count = labels_1d.new_zeros(()) | |
| for start in range(0, N, chunk_size): | |
| end = min(start + chunk_size, N) | |
| label_chunk = labels_1d[start:end] | |
| count = (label_chunk != ignore_index).sum() | |
| if count == 0: | |
| continue | |
| logits_chunk = F.linear(x_2d[start:end], weight) | |
| loss_chunk = F.cross_entropy(logits_chunk.float(), label_chunk, ignore_index=ignore_index, reduction="sum") | |
| total_loss = total_loss + loss_chunk | |
| total_count = total_count + count | |
| return total_loss / total_count.clamp(min=1) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # modules β mirror the trained Myosotis v2 / FWKVBlock exactly | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class FactorizedTiedHead(nn.Module): | |
| def __init__(self, vocab_size, d_model, d_emb): | |
| super().__init__() | |
| self.d_model = d_model | |
| self.d_emb = d_emb | |
| self.weight = nn.Parameter(torch.empty(vocab_size, d_emb)) | |
| self.proj = nn.Linear(d_emb, d_model, bias=False) | |
| nn.init.normal_(self.weight, std=0.02) | |
| nn.init.normal_(self.proj.weight, std=0.02) | |
| def embed(self, input_ids): | |
| return self.proj(F.embedding(input_ids, self.weight)) | |
| # kept as an alias so either calling convention works | |
| def forward(self, input_ids): | |
| return self.embed(input_ids) | |
| def to_emb_space(self, x): | |
| return F.linear(x, self.proj.weight.t()) | |
| def logits(self, x_emb): | |
| return F.linear(x_emb, self.weight) | |
| def resize_vocab(self, new_vocab_size): | |
| old = self.weight.shape[0] | |
| if new_vocab_size == old: | |
| return | |
| new_w = torch.empty(new_vocab_size, self.d_emb, device=self.weight.device, dtype=self.weight.dtype) | |
| nn.init.normal_(new_w, std=0.02) | |
| new_w[:old] = self.weight.data | |
| self.weight = nn.Parameter(new_w) | |
| class FWKVBlock(nn.Module): | |
| """Pre-norm block: token-shifted WKV mixer + token-shifted FFN. | |
| Per-layer recurrent state = (wkv_state, shift_state_timemix, shift_state_chanmix), | |
| each (B, d). Must match the training-time block bit-for-bit (module names and | |
| math), since checkpoints are loaded by state_dict key. | |
| """ | |
| def __init__(self, d, layer_id, n_layers, ffn_mult=4, floor=0.1, ceil=0.999, | |
| head_size=64, tau_min=2.0, tau_max=1000.0): | |
| super().__init__() | |
| assert d % head_size == 0, "d_model must be divisible by head_size" | |
| self.d = d | |
| self.floor = floor | |
| self.ceil = ceil | |
| self.ln1 = nn.LayerNorm(d) | |
| self.ln2 = nn.LayerNorm(d) | |
| self.proj_k = nn.Linear(d, d, bias=False) | |
| self.proj_v = nn.Linear(d, d, bias=False) | |
| self.proj_r = nn.Linear(d, d, bias=False) | |
| self.proj_out = nn.Linear(d, d, bias=False) | |
| self.proj_out._is_out_proj = True | |
| self.ln_x = nn.GroupNorm(d // head_size, d) # per-head norm on mixer output | |
| self.ffn = nn.Sequential( | |
| nn.Linear(d, ffn_mult * d, bias=False), | |
| nn.GELU(), | |
| nn.Linear(ffn_mult * d, d, bias=False), | |
| ) | |
| self.ffn[2]._is_out_proj = True | |
| # Decay: channels span timescales tau in [tau_min, tau_max] (W = 1 - 1/tau). | |
| tau = torch.exp(torch.linspace(math.log(tau_min), math.log(tau_max), d)) | |
| W0 = (1.0 - 1.0 / tau).clamp(floor + 1e-3, ceil - 1e-4) | |
| p = (W0 - floor) / (ceil - floor) | |
| self.w = nn.Parameter(torch.log(p / (1.0 - p))) | |
| # Token-shift mix coefficients (RWKV-4 style init). | |
| ratio_1_to_almost0 = 1.0 - (layer_id / n_layers) | |
| ratio_0_to_1 = layer_id / max(1, n_layers - 1) | |
| ramp = torch.arange(d, dtype=torch.float32) / d | |
| self.mu_k = nn.Parameter(ramp ** ratio_1_to_almost0) | |
| self.mu_v = nn.Parameter(ramp ** ratio_1_to_almost0 + 0.3 * ratio_0_to_1) | |
| self.mu_r = nn.Parameter(ramp ** (0.5 * ratio_1_to_almost0)) | |
| self.mu_c = nn.Parameter(ramp ** ratio_1_to_almost0) | |
| def W(self): | |
| return self.floor + (self.ceil - self.floor) * torch.sigmoid(self.w) | |
| def forward(self, x, state=None): | |
| B, T, d = x.shape | |
| if state is None: | |
| wkv_s = None | |
| sh_tm = x.new_zeros(B, d) | |
| sh_cm = x.new_zeros(B, d) | |
| else: | |
| wkv_s, sh_tm, sh_cm = state | |
| # ββ time mix (WKV) ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| h = self.ln1(x) | |
| hp = torch.cat([sh_tm.unsqueeze(1).to(h.dtype), h[:, :-1]], dim=1) # x_{t-1} | |
| xk = h * self.mu_k + hp * (1 - self.mu_k) | |
| xv = h * self.mu_v + hp * (1 - self.mu_v) | |
| xr = h * self.mu_r + hp * (1 - self.mu_r) | |
| k = self.proj_k(xk) | |
| v = self.proj_v(xv) | |
| r = torch.sigmoid(self.proj_r(xr)) | |
| W = self.W | |
| gain = torch.sqrt(1.0 - W * W) # unit stationary variance per channel | |
| a = k.float() * v.float() * gain | |
| y = decay_scan(a, W, wkv_s) # (B, T, d) fp32 | |
| new_wkv = y[:, -1].detach() | |
| yn = self.ln_x(y.reshape(B * T, d)).reshape(B, T, d) | |
| x = x + self.proj_out(r * yn.to(r.dtype)) | |
| # ββ channel mix (FFN) ββββββββββββββββββββββββββββββββββββββββββββ | |
| h2 = self.ln2(x) | |
| h2p = torch.cat([sh_cm.unsqueeze(1).to(h2.dtype), h2[:, :-1]], dim=1) | |
| xc = h2 * self.mu_c + h2p * (1 - self.mu_c) | |
| x = x + self.ffn(xc) | |
| new_state = (new_wkv, h[:, -1].detach(), h2[:, -1].detach()) | |
| return x, new_state | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # HF wrapper | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class FWKVPreTrainedModel(PreTrainedModel): | |
| config_class = FWKVConfig | |
| base_model_prefix = "model" | |
| supports_gradient_checkpointing = False | |
| def _init_weights(self, module): | |
| if isinstance(module, nn.Linear): | |
| std = 0.02 | |
| if getattr(module, "_is_out_proj", False): | |
| std = 0.02 / math.sqrt(2 * self.config.n_layers) | |
| nn.init.normal_(module.weight, mean=0.0, std=std) | |
| if module.bias is not None: | |
| nn.init.zeros_(module.bias) | |
| elif isinstance(module, (nn.LayerNorm, nn.GroupNorm)): | |
| nn.init.ones_(module.weight) | |
| nn.init.zeros_(module.bias) | |
| elif isinstance(module, FactorizedTiedHead): | |
| pass | |
| class FWKVLanguageModel(FWKVPreTrainedModel, GenerationMixin): | |
| # This model does NOT use the standard KV-Cache machinery (DynamicCache/ | |
| # StaticCache). Without this, newer transformers versions (4.45+/5.x) | |
| # auto-wrap past_key_values in a Cache object during generate(), which this | |
| # custom recurrent-state architecture can't consume. | |
| _supports_cache_class = False | |
| _supports_static_cache = False | |
| _supports_quantized_cache = False | |
| def __init__(self, config): | |
| super().__init__(config) | |
| self.config = config | |
| self.amp_dtype = None # optionally set by a training/inference driver | |
| self.shared = FactorizedTiedHead(config.vocab_size, config.d_model, config.d_emb) | |
| self.ln0 = nn.LayerNorm(config.d_model) | |
| self.blocks = nn.ModuleList([ | |
| FWKVBlock(config.d_model, i, config.n_layers, config.ffn_mult, | |
| config.wkv_floor, config.wkv_ceil, config.head_size, | |
| config.tau_min, config.tau_max) | |
| for i in range(config.n_layers) | |
| ]) | |
| self.norm = nn.LayerNorm(config.d_model) | |
| self.post_init() | |
| def get_input_embeddings(self): | |
| return self.shared | |
| def set_input_embeddings(self, value): | |
| self.shared = value | |
| def resize_vocab(self, new_vocab_size): | |
| self.shared.resize_vocab(new_vocab_size) | |
| self.config.vocab_size = new_vocab_size | |
| def count_params(self): | |
| return sum(p.numel() for p in self.parameters()) | |
| def forward( | |
| self, | |
| input_ids=None, | |
| past_key_values=None, | |
| attention_mask=None, | |
| labels=None, | |
| use_cache=True, | |
| **kwargs | |
| ): | |
| if input_ids is None: | |
| raise ValueError("input_ids must be provided") | |
| if not isinstance(input_ids, torch.Tensor): | |
| input_ids = torch.tensor(input_ids) | |
| if input_ids.dtype != torch.long: | |
| input_ids = input_ids.long() | |
| if input_ids.dim() == 1: | |
| input_ids = input_ids.unsqueeze(0) | |
| x = self.ln0(self.shared.embed(input_ids)) | |
| # If transformers handed us a Cache object (DynamicCache, etc.) instead | |
| # of our custom list/tuple of per-layer state tensors, treat it as | |
| # "no state yet" rather than trying to iterate/index into it. | |
| if past_key_values is None or isinstance(past_key_values, Cache): | |
| states_in = [None] * len(self.blocks) | |
| else: | |
| states_in = past_key_values | |
| states_out = [] | |
| for block, state in zip(self.blocks, states_in): | |
| x, new_state = block(x, state) | |
| states_out.append(new_state) | |
| x = self.norm(x) | |
| x_emb = self.shared.to_emb_space(x) | |
| loss = None | |
| logits = None | |
| if labels is not None: | |
| if not isinstance(labels, torch.Tensor): | |
| labels = torch.tensor(labels) | |
| if labels.dtype != torch.long: | |
| labels = labels.long() | |
| loss = chunked_cross_entropy( | |
| x_emb, self.shared.weight, labels, | |
| chunk_size=self.config.ce_chunk, ignore_index=-100 | |
| ) | |
| else: | |
| logits = self.shared.logits(x_emb) | |
| past_key_values_out = tuple(states_out) if use_cache else None | |
| return CausalLMOutputWithPast( | |
| loss=loss, | |
| logits=logits, | |
| past_key_values=past_key_values_out, | |
| ) | |
| def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs): | |
| if past_key_values is not None: | |
| input_ids = input_ids[:, -1:] if input_ids.dim() > 1 else input_ids[-1:] | |
| # Don't coerce a Cache object into a tuple β pass it through as-is, | |
| # forward() knows how to treat it as "empty" state. | |
| if past_key_values is not None and not isinstance(past_key_values, (tuple, Cache)): | |
| past_key_values = tuple(past_key_values) | |
| return { | |
| "input_ids": input_ids, | |
| "past_key_values": past_key_values, | |
| "use_cache": True, | |
| } | |
| def _reorder_cache(self, past_key_values, beam_idx): | |
| if past_key_values is None or isinstance(past_key_values, Cache): | |
| return past_key_values | |
| reordered = [] | |
| for state in past_key_values: | |
| if state is None: | |
| reordered.append(None) | |
| continue | |
| # each per-layer state is itself a 3-tuple (wkv_state, sh_tm, sh_cm) | |
| reordered.append(tuple(s.index_select(0, beam_idx) for s in state)) | |
| return tuple(reordered) | |
| # Register the model with AutoModel | |
| AutoConfig.register("fwkv", FWKVConfig) | |
| AutoModel.register(FWKVConfig, FWKVLanguageModel) |