Text Generation
Transformers
Safetensors
caba
recurrent-language-model
kumru-tokenizer
continued-pretraining
conversational
custom_code
Instructions to use Efe2898/caba-v2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Efe2898/caba-v2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="Efe2898/caba-v2", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("Efe2898/caba-v2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Efe2898/caba-v2 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Efe2898/caba-v2" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Efe2898/caba-v2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/Efe2898/caba-v2
- SGLang
How to use Efe2898/caba-v2 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 "Efe2898/caba-v2" \ --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": "Efe2898/caba-v2", "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 "Efe2898/caba-v2" \ --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": "Efe2898/caba-v2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use Efe2898/caba-v2 with Docker Model Runner:
docker model run hf.co/Efe2898/caba-v2
Download modeling_caba.py from Efe2898/caba-v2: direct link, hf CLI and curl.
- Browser
- Download file 36 kB
-
https://huggingface.co/Efe2898/caba-v2/resolve/main/modeling_caba.py
- Command line
-
hf download hf://Efe2898/caba-v2/modeling_caba.py
-
curl -L -o modeling_caba.py https://huggingface.co/Efe2898/caba-v2/resolve/main/modeling_caba.py
36 kB
| """ÇABA v0-core: recurrent three-bank causal LM for Hugging Face Transformers. | |
| The recurrent update here is an explicit delta-rule prototype, not a claim of | |
| bit-for-bit equivalence with the official Gated DeltaNet-2 implementation. | |
| Training on TPU uses PyTorch/XLA's scan operator; cached inference keeps a | |
| constant-size recurrent state plus the unfinished fixed-size promotion window. | |
| """ | |
| from dataclasses import dataclass | |
| import math | |
| from typing import Any, Dict, List, Optional, Tuple | |
| import torch | |
| import transformers as _transformers | |
| from torch import nn | |
| from torch.nn import functional as F | |
| from torch.utils.checkpoint import checkpoint as _torch_checkpoint | |
| try: | |
| from torch_xla.utils.checkpoint import checkpoint as _xla_checkpoint | |
| except ImportError: # Keep non-XLA inference and training usable. | |
| _xla_checkpoint = None | |
| from transformers import PreTrainedModel | |
| from transformers.generation import GenerationMixin | |
| from transformers.modeling_outputs import CausalLMOutputWithPast | |
| from .configuration_caba import CabaConfig | |
| try: | |
| from torch_xla.experimental.scan import scan as _xla_scan | |
| except Exception: # Transformers inference does not require PyTorch/XLA. | |
| _xla_scan = None | |
| class CabaCausalLMOutput(CausalLMOutputWithPast): | |
| """Causal LM output with small scalar diagnostics for training logs.""" | |
| memory_metrics: Optional[Dict[str, torch.Tensor]] = None | |
| def _inverse_softplus(x: float) -> float: | |
| return math.log(math.expm1(x)) | |
| def _scan(fn, carry: torch.Tensor, xs: torch.Tensor): | |
| """Run a scan on TPU, with an eager reference path for CPU smoke use.""" | |
| if carry.device.type == "xla": | |
| if _xla_scan is None: | |
| raise RuntimeError( | |
| "TPU training needs torch_xla.experimental.scan. Install a matched " | |
| "PyTorch/PyTorch-XLA pair from the first notebook cell and restart runtime." | |
| ) | |
| return _xla_scan(fn, carry, xs) | |
| outputs = [] | |
| for index in range(xs.shape[0]): | |
| carry, output = fn(carry, xs[index]) | |
| outputs.append(output) | |
| if not outputs: | |
| raise ValueError("scan input must contain at least one step") | |
| return carry, torch.stack(outputs, dim=0) | |
| def _read(memory: torch.Tensor, query: torch.Tensor) -> torch.Tensor: | |
| # memory: [batch, heads, key_dim, value_dim]; query: [batch, heads, key_dim] | |
| return torch.einsum("bhkv,bhk->bhv", memory, query.float()) | |
| def _predict_error(memory: torch.Tensor, key: torch.Tensor, value: torch.Tensor): | |
| prediction = _read(memory, key) | |
| error = value.float() - prediction | |
| score = ( | |
| torch.linalg.vector_norm(error, dim=-1) | |
| / (torch.linalg.vector_norm(value.float(), dim=-1) + 1e-6) | |
| ) | |
| return error, score.detach() | |
| def _outer(error: torch.Tensor, key: torch.Tensor) -> torch.Tensor: | |
| return torch.einsum("bhv,bhk->bhkv", error, key.float()) | |
| def _mark_data_sharding(tensor: torch.Tensor, mesh, spec): | |
| if mesh is None: | |
| return tensor | |
| import torch_xla.distributed.spmd as xs | |
| return xs.mark_sharding(tensor, mesh, spec) | |
| class CabaRMSNorm(nn.Module): | |
| def __init__(self, hidden_size: int, eps: float): | |
| super().__init__() | |
| self.weight = nn.Parameter(torch.ones(hidden_size)) | |
| self.eps = eps | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| scale = torch.rsqrt(x.float().pow(2).mean(dim=-1, keepdim=True) + self.eps) | |
| return (x.float() * scale * self.weight.float()).to(x.dtype) | |
| class CabaBlock(nn.Module): | |
| def __init__(self, config: CabaConfig): | |
| super().__init__() | |
| self.hidden = config.hidden_size | |
| self.heads = config.num_heads | |
| self.head_dim = config.head_dim | |
| self.medium_clock = config.medium_clock | |
| self.slow_clock = config.slow_clock | |
| self.slow_span = self.medium_clock * self.slow_clock | |
| self.conv_kernel = config.conv_kernel | |
| self.eps = config.rms_norm_eps | |
| self.data_mesh = None | |
| self.input_norm = CabaRMSNorm(self.hidden, self.eps) | |
| self.local_conv = nn.Conv1d( | |
| self.hidden, | |
| self.hidden, | |
| kernel_size=self.conv_kernel, | |
| groups=self.hidden, | |
| bias=False, | |
| ) | |
| self.qkv = nn.Linear(self.hidden, 3 * self.hidden, bias=False) | |
| self.fast_write = nn.Linear(self.hidden, self.heads, bias=False) | |
| self.merge_gate = nn.Linear(self.hidden, 3 * self.heads, bias=False) | |
| self.out_proj = nn.Linear(self.hidden, self.hidden, bias=False) | |
| self.fast_score_alpha = nn.Parameter( | |
| torch.full((self.heads,), _inverse_softplus(1.0)) | |
| ) | |
| self.fast_score_tau = nn.Parameter(torch.ones(self.heads)) | |
| self.medium_score_alpha = nn.Parameter( | |
| torch.full((self.heads,), _inverse_softplus(1.0)) | |
| ) | |
| self.medium_score_tau = nn.Parameter(torch.ones(self.heads)) | |
| self.medium_admit_threshold = nn.Parameter(torch.full((self.heads,), 2.0)) | |
| self.slow_admit_threshold = nn.Parameter(torch.full((self.heads,), 3.0)) | |
| # Positive increments structurally preserve T_slow > T_medium > T_fast. | |
| self.raw_fast_timescale = nn.Parameter( | |
| torch.full((self.heads,), _inverse_softplus(7.0)) | |
| ) | |
| self.raw_medium_gap = nn.Parameter( | |
| torch.full((self.heads,), _inverse_softplus(56.0)) | |
| ) | |
| self.raw_slow_gap = nn.Parameter( | |
| torch.full((self.heads,), _inverse_softplus(448.0)) | |
| ) | |
| self.ffn_norm = CabaRMSNorm(self.hidden, self.eps) | |
| self.ffn_gate_up = nn.Linear(self.hidden, 2 * config.intermediate_size, bias=False) | |
| self.ffn_down = nn.Linear(config.intermediate_size, self.hidden, bias=False) | |
| def _decays(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| t_fast = F.softplus(self.raw_fast_timescale.float()) + 1.0 | |
| t_medium = t_fast + F.softplus(self.raw_medium_gap.float()) | |
| t_slow = t_medium + F.softplus(self.raw_slow_gap.float()) | |
| return torch.exp(-1.0 / t_fast), torch.exp(-1.0 / t_medium), torch.exp(-1.0 / t_slow) | |
| def _project(self, x: torch.Tensor): | |
| batch, length, _ = x.shape | |
| normalized = self.input_norm(x) | |
| conv_in = F.pad(normalized.transpose(1, 2), (self.conv_kernel - 1, 0)) | |
| h = F.silu(self.local_conv(conv_in).transpose(1, 2)) | |
| q, k, v = self.qkv(h).view( | |
| batch, length, 3, self.heads, self.head_dim | |
| ).unbind(dim=2) | |
| q = F.normalize(q.float(), dim=-1, eps=1e-6) | |
| k = F.normalize(k.float(), dim=-1, eps=1e-6) | |
| v = v.float() | |
| write = torch.sigmoid(self.fast_write(h).float()).unsqueeze(-1) | |
| return h, q, k, v, write | |
| def _fast_sequence(self, q, k, v, write): | |
| batch, length, heads, dim = q.shape | |
| decay_fast, _, _ = self._decays() | |
| decay_fast = decay_fast.view(1, heads, 1, 1) | |
| xs = torch.cat((q, k, v, write), dim=-1).permute(1, 0, 2, 3).contiguous() | |
| decay_steps = decay_fast.reshape(1, 1, heads, 1).expand(length, batch, heads, 1) | |
| xs = torch.cat((xs, decay_steps), dim=-1).contiguous() | |
| xs = _mark_data_sharding(xs, self.data_mesh, (None, "data", None, None)) | |
| initial = torch.zeros( | |
| batch, heads, dim, dim, device=q.device, dtype=torch.float32 | |
| ) | |
| initial = _mark_data_sharding(initial, self.data_mesh, ("data", None, None, None)) | |
| def step(memory, item): | |
| query, key, value, gate, decay = torch.split( | |
| item, (dim, dim, dim, 1, 1), dim=-1 | |
| ) | |
| key = F.normalize(key.float(), dim=-1, eps=1e-6) | |
| error, score = _predict_error(memory, key.squeeze(-1) if key.shape[-1] == 1 else key, value) | |
| next_memory = decay.unsqueeze(-1) * memory + gate.float().unsqueeze(-1) * _outer(error, key) | |
| read = _read(next_memory, query) | |
| packed = torch.cat((read, error, score.unsqueeze(-1)), dim=-1) | |
| return next_memory, packed | |
| final, packed = _scan(step, initial, xs) | |
| final = _mark_data_sharding(final, self.data_mesh, ("data", None, None, None)) | |
| packed = _mark_data_sharding(packed, self.data_mesh, (None, "data", None, None)) | |
| packed = packed.permute(1, 0, 2, 3).contiguous() | |
| read = _mark_data_sharding(packed[..., :dim], self.data_mesh, ("data", None, None, None)) | |
| error = _mark_data_sharding(packed[..., dim : 2 * dim], self.data_mesh, ("data", None, None, None)) | |
| score = _mark_data_sharding(packed[..., -1], self.data_mesh, ("data", None, None)) | |
| return final, read, error, score | |
| def _medium_sequence(self, q, k, v, scores): | |
| batch, length, heads, dim = q.shape | |
| clock = self.medium_clock | |
| if length % clock: | |
| raise ValueError( | |
| f"Training sequence length {length} must be divisible by medium_clock={clock}." | |
| ) | |
| windows = length // clock | |
| q_windows = q.reshape(batch, windows, clock, heads, dim).permute(0, 1, 3, 2, 4) | |
| k_windows = k.reshape(batch, windows, clock, heads, dim).permute(0, 1, 3, 2, 4) | |
| v_windows = v.reshape(batch, windows, clock, heads, dim).permute(0, 1, 3, 2, 4) | |
| s_windows = scores.reshape(batch, windows, clock, heads).permute(0, 1, 3, 2) | |
| selection = torch.softmax(s_windows, dim=-1) | |
| summary_k = (selection.unsqueeze(-1) * k_windows).sum(dim=-2) | |
| summary_v = (selection.unsqueeze(-1) * v_windows).sum(dim=-2) | |
| log_mean_exp = torch.logsumexp(s_windows, dim=-1) - math.log(clock) | |
| admission = torch.sigmoid( | |
| log_mean_exp - self.medium_admit_threshold.float().view(1, 1, heads) | |
| ).unsqueeze(-1) | |
| q_flat = q_windows.reshape(batch, windows, heads, clock * dim).permute(1, 0, 2, 3) | |
| xs = torch.cat( | |
| ( | |
| q_flat, | |
| summary_k.permute(1, 0, 2, 3), | |
| summary_v.permute(1, 0, 2, 3), | |
| admission.permute(1, 0, 2, 3), | |
| ), | |
| dim=-1, | |
| ).contiguous() | |
| initial = torch.zeros(batch, heads, dim, dim, device=q.device, dtype=torch.float32) | |
| _, decay_medium, _ = self._decays() | |
| decay_medium = decay_medium.pow(clock).view(1, heads, 1, 1) | |
| decay_steps = decay_medium.reshape(1, 1, heads, 1).expand(windows, batch, heads, 1) | |
| xs = torch.cat((xs, decay_steps), dim=-1).contiguous() | |
| xs = _mark_data_sharding(xs, self.data_mesh, (None, "data", None, None)) | |
| initial = _mark_data_sharding(initial, self.data_mesh, ("data", None, None, None)) | |
| def step(memory, item): | |
| offset = clock * dim | |
| query = item[..., :offset].reshape(batch, heads, clock, dim).permute(0, 2, 1, 3) | |
| key = item[..., offset : offset + dim] | |
| value = item[..., offset + dim : offset + 2 * dim] | |
| gate = item[..., -2:-1] | |
| decay = item[..., -1:] | |
| read = torch.einsum("bhkv,brhk->brhv", memory, query.float()) | |
| error, score = _predict_error(memory, key, value) | |
| next_memory = ( | |
| decay.unsqueeze(-1) * memory + gate.float().unsqueeze(-1) * _outer(error, key) | |
| ) | |
| repeated_error = error.unsqueeze(2).expand(-1, -1, clock, -1) | |
| repeated_score = score.unsqueeze(-1).unsqueeze(2).expand(-1, -1, clock, -1) | |
| packed = torch.cat( | |
| (read.permute(0, 2, 1, 3), repeated_error, repeated_score), dim=-1 | |
| ).reshape(batch, heads, clock * (2 * dim + 1)) | |
| return next_memory, packed | |
| final, packed = _scan(step, initial, xs) | |
| final = _mark_data_sharding(final, self.data_mesh, ("data", None, None, None)) | |
| packed = _mark_data_sharding(packed, self.data_mesh, (None, "data", None, None)) | |
| packed = packed.reshape(windows, batch, heads, clock, 2 * dim + 1) | |
| packed = packed.permute(1, 0, 3, 2, 4).contiguous() | |
| read = _mark_data_sharding(packed[..., :dim].reshape(batch, length, heads, dim), self.data_mesh, ("data", None, None, None)) | |
| # Every chunk repeats its single medium residual/score for fixed-shape output. | |
| error = _mark_data_sharding(packed[:, :, 0, :, dim : 2 * dim], self.data_mesh, ("data", None, None, None)) | |
| score = _mark_data_sharding(packed[:, :, 0, :, -1], self.data_mesh, ("data", None, None)) | |
| summary_k = _mark_data_sharding(summary_k, self.data_mesh, ("data", None, None, None)) | |
| summary_v = _mark_data_sharding(summary_v, self.data_mesh, ("data", None, None, None)) | |
| admission = _mark_data_sharding(admission.squeeze(-1), self.data_mesh, ("data", None, None)) | |
| return final, read, summary_k, summary_v, error, score, admission | |
| def _slow_sequence(self, q, medium_k, medium_v, medium_scores): | |
| batch, length, heads, dim = q.shape | |
| clock = self.slow_span | |
| if length % clock: | |
| raise ValueError( | |
| f"Training sequence length {length} must be divisible by slow span={clock}." | |
| ) | |
| windows = length // clock | |
| medium_windows_per_slow = self.slow_clock | |
| score_windows = medium_scores.reshape( | |
| batch, windows, medium_windows_per_slow, heads | |
| ).permute(0, 1, 3, 2) | |
| mk = medium_k.reshape(batch, windows, medium_windows_per_slow, heads, dim).permute( | |
| 0, 1, 3, 2, 4 | |
| ) | |
| mv = medium_v.reshape(batch, windows, medium_windows_per_slow, heads, dim).permute( | |
| 0, 1, 3, 2, 4 | |
| ) | |
| selection = torch.softmax(score_windows, dim=-1) | |
| summary_k = (selection.unsqueeze(-1) * mk).sum(dim=-2) | |
| summary_v = (selection.unsqueeze(-1) * mv).sum(dim=-2) | |
| log_mean_exp = torch.logsumexp(score_windows, dim=-1) - math.log( | |
| medium_windows_per_slow | |
| ) | |
| admission = torch.sigmoid( | |
| log_mean_exp - self.slow_admit_threshold.float().view(1, 1, heads) | |
| ).unsqueeze(-1) | |
| q_windows = q.reshape(batch, windows, clock, heads, dim).permute(0, 1, 3, 2, 4) | |
| q_flat = q_windows.reshape(batch, windows, heads, clock * dim).permute(1, 0, 2, 3) | |
| xs = torch.cat( | |
| ( | |
| q_flat, | |
| summary_k.permute(1, 0, 2, 3), | |
| summary_v.permute(1, 0, 2, 3), | |
| admission.permute(1, 0, 2, 3), | |
| ), | |
| dim=-1, | |
| ).contiguous() | |
| initial = torch.zeros(batch, heads, dim, dim, device=q.device, dtype=torch.float32) | |
| _, _, decay_slow = self._decays() | |
| decay_slow = decay_slow.pow(clock).view(1, heads, 1, 1) | |
| decay_steps = decay_slow.reshape(1, 1, heads, 1).expand(windows, batch, heads, 1) | |
| xs = torch.cat((xs, decay_steps), dim=-1).contiguous() | |
| xs = _mark_data_sharding(xs, self.data_mesh, (None, "data", None, None)) | |
| initial = _mark_data_sharding(initial, self.data_mesh, ("data", None, None, None)) | |
| def step(memory, item): | |
| offset = clock * dim | |
| query = item[..., :offset].reshape(batch, heads, clock, dim).permute(0, 2, 1, 3) | |
| key = item[..., offset : offset + dim] | |
| value = item[..., offset + dim : offset + 2 * dim] | |
| gate = item[..., -2:-1] | |
| decay = item[..., -1:] | |
| read = torch.einsum("bhkv,brhk->brhv", memory, query.float()) | |
| error, _ = _predict_error(memory, key, value) | |
| next_memory = ( | |
| decay.unsqueeze(-1) * memory + gate.float().unsqueeze(-1) * _outer(error, key) | |
| ) | |
| return next_memory, read.permute(0, 2, 1, 3).reshape( | |
| batch, heads, clock * dim | |
| ) | |
| final, read_flat = _scan(step, initial, xs) | |
| final = _mark_data_sharding(final, self.data_mesh, ("data", None, None, None)) | |
| read_flat = _mark_data_sharding(read_flat, self.data_mesh, (None, "data", None, None)) | |
| read = read_flat.reshape(windows, batch, heads, clock, dim) | |
| read = _mark_data_sharding(read.permute(1, 0, 3, 2, 4).reshape(batch, length, heads, dim), self.data_mesh, ("data", None, None, None)) | |
| admission = _mark_data_sharding(admission.squeeze(-1), self.data_mesh, ("data", None, None)) | |
| return final, read, admission | |
| def forward_sequence(self, x: torch.Tensor): | |
| batch, length, _ = x.shape | |
| if length % (self.medium_clock * self.slow_clock): | |
| raise ValueError( | |
| "use_cache=False training/evaluation sequence length must be divisible " | |
| f"by medium_clock*slow_clock={self.medium_clock * self.slow_clock}; " | |
| "the notebook default is 512." | |
| ) | |
| h, q, k, v, write = self._project(x) | |
| h = _mark_data_sharding(h, self.data_mesh, ("data", None, None)) | |
| q = _mark_data_sharding(q, self.data_mesh, ("data", None, None, None)) | |
| k = _mark_data_sharding(k, self.data_mesh, ("data", None, None, None)) | |
| v = _mark_data_sharding(v, self.data_mesh, ("data", None, None, None)) | |
| write = _mark_data_sharding(write, self.data_mesh, ("data", None, None, None)) | |
| _, fast_read, fast_error, fast_score = self._fast_sequence(q, k, v, write) | |
| unresolvedness = fast_score.detach().mean() | |
| fast_score = F.softplus(self.fast_score_alpha.float()).view(1, 1, -1) * ( | |
| fast_score - self.fast_score_tau.float().view(1, 1, -1) | |
| ) | |
| _, medium_read, medium_k, medium_v, medium_error, medium_score, medium_admission = ( | |
| self._medium_sequence(q, k, v, fast_score) | |
| ) | |
| medium_score = F.softplus(self.medium_score_alpha.float()).view(1, 1, -1) * ( | |
| medium_score - self.medium_score_tau.float().view(1, 1, -1) | |
| ) | |
| _, slow_read, slow_admission = self._slow_sequence( | |
| q, medium_k, medium_v, medium_score | |
| ) | |
| gates = torch.softmax( | |
| self.merge_gate(h).view(batch, length, self.heads, 3).float(), dim=-1 | |
| ) | |
| reads = torch.stack((fast_read, medium_read, slow_read), dim=-2) | |
| merged = (reads * gates.unsqueeze(-1)).sum(dim=-2).reshape(batch, length, self.hidden) | |
| x = x + self.out_proj(merged.to(x.dtype)) | |
| gate_up = self.ffn_gate_up(self.ffn_norm(x)) | |
| gate, up = gate_up.chunk(2, dim=-1) | |
| x = x + self.ffn_down(F.silu(gate) * up) | |
| metrics = { | |
| "unresolvedness": unresolvedness, | |
| "medium_admission": medium_admission.detach().mean(), | |
| "slow_admission": slow_admission.detach().mean(), | |
| } | |
| return x, metrics | |
| def _new_cache(self, batch: int, device: torch.device) -> Dict[str, Any]: | |
| h, d = self.heads, self.head_dim | |
| return { | |
| "fast": torch.zeros(batch, h, d, d, device=device, dtype=torch.float32), | |
| "medium": torch.zeros(batch, h, d, d, device=device, dtype=torch.float32), | |
| "slow": torch.zeros(batch, h, d, d, device=device, dtype=torch.float32), | |
| "conv": torch.zeros( | |
| batch, max(self.conv_kernel - 1, 0), self.hidden, device=device | |
| ), | |
| "medium_pending_k": [], | |
| "medium_pending_v": [], | |
| "medium_pending_score": [], | |
| "slow_pending_k": [], | |
| "slow_pending_v": [], | |
| "slow_pending_score": [], | |
| "position": 0, | |
| } | |
| def step(self, x: torch.Tensor, cache: Dict[str, Any], position: int): | |
| batch = x.shape[0] | |
| normalized = self.input_norm(x) | |
| context = torch.cat((cache["conv"], normalized), dim=1) | |
| conv = self.local_conv(context.transpose(1, 2)).transpose(1, 2) | |
| h = F.silu(conv) | |
| conv_state = ( | |
| context[:, -(self.conv_kernel - 1) :, :] | |
| if self.conv_kernel > 1 | |
| else context[:, :0, :] | |
| ) | |
| q, k, v = self.qkv(h).view(batch, 1, 3, self.heads, self.head_dim).unbind(dim=2) | |
| q = F.normalize(q[:, 0].float(), dim=-1, eps=1e-6) | |
| k = F.normalize(k[:, 0].float(), dim=-1, eps=1e-6) | |
| v = v[:, 0].float() | |
| write = torch.sigmoid(self.fast_write(h).float()[:, 0]).unsqueeze(-1) | |
| decay_fast, decay_medium, decay_slow = self._decays() | |
| fast_error, fast_unresolvedness = _predict_error(cache["fast"], k, v) | |
| fast_next = ( | |
| decay_fast.view(1, self.heads, 1, 1) * cache["fast"] | |
| + write.float().unsqueeze(-1) * _outer(fast_error, k) | |
| ) | |
| fast_read = _read(fast_next, q) | |
| medium_read = _read(cache["medium"], q) | |
| slow_read = _read(cache["slow"], q) | |
| score_fast = F.softplus(self.fast_score_alpha.float()).view(1, -1) * ( | |
| fast_unresolvedness - self.fast_score_tau.float().view(1, -1) | |
| ) | |
| pending_k = cache["medium_pending_k"] + [k] | |
| pending_v = cache["medium_pending_v"] + [v] | |
| pending_score = cache["medium_pending_score"] + [score_fast] | |
| medium_next = cache["medium"] | |
| slow_next = cache["slow"] | |
| slow_pending_k = cache["slow_pending_k"] | |
| slow_pending_v = cache["slow_pending_v"] | |
| slow_pending_score = cache["slow_pending_score"] | |
| medium_admission_value = torch.zeros((), device=x.device) | |
| slow_admission_value = torch.zeros((), device=x.device) | |
| if (position + 1) % self.medium_clock == 0: | |
| k_stack = torch.stack(pending_k, dim=2) | |
| v_stack = torch.stack(pending_v, dim=2) | |
| s_stack = torch.stack(pending_score, dim=2) | |
| selected = torch.softmax(s_stack, dim=2) | |
| summary_k = (selected.unsqueeze(-1) * k_stack).sum(dim=2) | |
| summary_v = (selected.unsqueeze(-1) * v_stack).sum(dim=2) | |
| log_mean_exp = torch.logsumexp(s_stack, dim=2) - math.log(self.medium_clock) | |
| medium_admission = torch.sigmoid( | |
| log_mean_exp | |
| - self.medium_admit_threshold.float().view(1, self.heads) | |
| ).unsqueeze(-1) | |
| medium_error, medium_unresolvedness = _predict_error( | |
| cache["medium"], summary_k, summary_v | |
| ) | |
| medium_next = ( | |
| decay_medium.pow(self.medium_clock).view(1, self.heads, 1, 1) | |
| * cache["medium"] | |
| + medium_admission.unsqueeze(-1) * _outer(medium_error, summary_k) | |
| ) | |
| score_medium = F.softplus(self.medium_score_alpha.float()).view(1, -1) * ( | |
| medium_unresolvedness - self.medium_score_tau.float().view(1, -1) | |
| ) | |
| slow_pending_k = slow_pending_k + [summary_k] | |
| slow_pending_v = slow_pending_v + [summary_v] | |
| slow_pending_score = slow_pending_score + [score_medium] | |
| pending_k, pending_v, pending_score = [], [], [] | |
| medium_admission_value = medium_admission.mean() | |
| if len(slow_pending_k) == self.slow_clock: | |
| sk = torch.stack(slow_pending_k, dim=2) | |
| sv = torch.stack(slow_pending_v, dim=2) | |
| ss = torch.stack(slow_pending_score, dim=2) | |
| slow_selected = torch.softmax(ss, dim=2) | |
| slow_key = (slow_selected.unsqueeze(-1) * sk).sum(dim=2) | |
| slow_value = (slow_selected.unsqueeze(-1) * sv).sum(dim=2) | |
| slow_log_mean_exp = torch.logsumexp(ss, dim=2) - math.log(self.slow_clock) | |
| slow_admission = torch.sigmoid( | |
| slow_log_mean_exp | |
| - self.slow_admit_threshold.float().view(1, self.heads) | |
| ).unsqueeze(-1) | |
| slow_error, _ = _predict_error(cache["slow"], slow_key, slow_value) | |
| slow_next = ( | |
| decay_slow.pow(self.slow_span).view(1, self.heads, 1, 1) | |
| * cache["slow"] | |
| + slow_admission.unsqueeze(-1) * _outer(slow_error, slow_key) | |
| ) | |
| slow_pending_k, slow_pending_v, slow_pending_score = [], [], [] | |
| slow_admission_value = slow_admission.mean() | |
| gates = torch.softmax( | |
| self.merge_gate(h).view(batch, 1, self.heads, 3).float(), dim=-1 | |
| )[:, 0] | |
| reads = torch.stack((fast_read, medium_read, slow_read), dim=-2) | |
| merged = (reads * gates.unsqueeze(-1)).sum(dim=-2).reshape(batch, 1, self.hidden) | |
| x = x + self.out_proj(merged.to(x.dtype)) | |
| gate_up = self.ffn_gate_up(self.ffn_norm(x)) | |
| gate, up = gate_up.chunk(2, dim=-1) | |
| x = x + self.ffn_down(F.silu(gate) * up) | |
| cache = { | |
| "fast": fast_next, | |
| "medium": medium_next, | |
| "slow": slow_next, | |
| "conv": conv_state, | |
| "medium_pending_k": pending_k, | |
| "medium_pending_v": pending_v, | |
| "medium_pending_score": pending_score, | |
| "slow_pending_k": slow_pending_k, | |
| "slow_pending_v": slow_pending_v, | |
| "slow_pending_score": slow_pending_score, | |
| "position": position + 1, | |
| } | |
| metrics = { | |
| "unresolvedness": fast_unresolvedness.detach().mean(), | |
| "medium_admission": medium_admission_value.detach(), | |
| "slow_admission": slow_admission_value.detach(), | |
| } | |
| return x, cache, metrics | |
| class CabaPreTrainedModel(PreTrainedModel): | |
| config_class = CabaConfig | |
| base_model_prefix = "caba" | |
| supports_gradient_checkpointing = False | |
| _supports_cache_class = False | |
| def _init_weights(self, module): | |
| std = self.config.initializer_range | |
| if isinstance(module, (nn.Linear, nn.Embedding, nn.Conv1d)): | |
| nn.init.normal_(module.weight, mean=0.0, std=std) | |
| if getattr(module, "bias", None) is not None: | |
| nn.init.zeros_(module.bias) | |
| if isinstance(module, nn.Embedding) and module.padding_idx is not None: | |
| with torch.no_grad(): | |
| module.weight[module.padding_idx].zero_() | |
| elif isinstance(module, CabaRMSNorm): | |
| nn.init.ones_(module.weight) | |
| class CabaModel(CabaPreTrainedModel): | |
| def __init__(self, config: CabaConfig): | |
| super().__init__(config) | |
| self.data_mesh = None | |
| self.embed_tokens = nn.Embedding( | |
| config.vocab_size, | |
| config.hidden_size, | |
| padding_idx=config.pad_token_id, | |
| ) | |
| self.layers = nn.ModuleList( | |
| [CabaBlock(config) for _ in range(config.num_hidden_layers)] | |
| ) | |
| self.norm = CabaRMSNorm(config.hidden_size, config.rms_norm_eps) | |
| def get_input_embeddings(self): | |
| return self.embed_tokens | |
| def set_input_embeddings(self, value): | |
| self.embed_tokens = value | |
| def set_data_mesh(self, mesh): | |
| self.data_mesh = mesh | |
| for layer in self.layers: | |
| layer.data_mesh = mesh | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor, | |
| past_key_values=None, | |
| use_cache: bool = False, | |
| **kwargs, | |
| ): | |
| if input_ids.ndim != 2: | |
| raise ValueError("input_ids must have shape [batch, sequence]") | |
| x = self.embed_tokens(input_ids) | |
| x = _mark_data_sharding(x, self.data_mesh, ("data", None, None)) | |
| metrics_by_layer = [] | |
| if use_cache or past_key_values is not None: | |
| caches = ( | |
| list(past_key_values) | |
| if past_key_values is not None | |
| else [ | |
| layer._new_cache(input_ids.shape[0], input_ids.device) | |
| for layer in self.layers | |
| ] | |
| ) | |
| position = caches[0]["position"] if caches else 0 | |
| pieces = [] | |
| step_metrics = [] | |
| for offset in range(input_ids.shape[1]): | |
| hidden = x[:, offset : offset + 1] | |
| new_caches = [] | |
| current_metrics = [] | |
| for layer, cache in zip(self.layers, caches): | |
| hidden, cache, metrics = layer.step(hidden, cache, position + offset) | |
| new_caches.append(cache) | |
| current_metrics.append(metrics) | |
| caches = new_caches | |
| pieces.append(hidden) | |
| step_metrics.append(current_metrics) | |
| x = torch.cat(pieces, dim=1) | |
| for layer_index in range(len(self.layers)): | |
| metrics_by_layer.append( | |
| { | |
| key: torch.stack( | |
| [m[layer_index][key] for m in step_metrics] | |
| ).mean() | |
| for key in ("unresolvedness", "medium_admission", "slow_admission") | |
| } | |
| ) | |
| cache_output = tuple(caches) | |
| else: | |
| for layer in self.layers: | |
| if self.config.gradient_checkpointing and self.training: | |
| def _run_checkpointed(hidden, _layer=layer): | |
| hidden_out, layer_metrics = _layer.forward_sequence(hidden) | |
| values = torch.stack([ | |
| layer_metrics["unresolvedness"].detach().float(), | |
| layer_metrics["medium_admission"].detach().float(), | |
| layer_metrics["slow_admission"].detach().float(), | |
| ]) | |
| return hidden_out, values | |
| checkpoint_fn = _xla_checkpoint if x.device.type == "xla" else _torch_checkpoint | |
| if checkpoint_fn is None: | |
| raise RuntimeError("TPU gradient checkpointing requires torch_xla.utils.checkpoint") | |
| x, values = checkpoint_fn(_run_checkpointed, x, use_reentrant=True) | |
| metrics_by_layer.append({ | |
| "unresolvedness": values[0], | |
| "medium_admission": values[1], | |
| "slow_admission": values[2], | |
| }) | |
| else: | |
| x, metrics = layer.forward_sequence(x) | |
| metrics_by_layer.append(metrics) | |
| x = _mark_data_sharding(x, self.data_mesh, ("data", None, None)) | |
| cache_output = None | |
| x = self.norm(x) | |
| x = _mark_data_sharding(x, self.data_mesh, ("data", None, None)) | |
| keys = ("unresolvedness", "medium_admission", "slow_admission") | |
| metrics = { | |
| key: torch.stack([entry[key] for entry in metrics_by_layer]).mean() | |
| for key in keys | |
| } | |
| return x, cache_output, metrics | |
| class CabaForCausalLM(CabaPreTrainedModel, GenerationMixin): | |
| """Hugging Face causal-LM wrapper with recurrent cached generation.""" | |
| # Transformers 4 expects a list; Transformers 5 expects tied-key -> source-key mapping. | |
| _tied_weights_keys = ( | |
| {"lm_head.weight": "model.embed_tokens.weight"} | |
| if int(_transformers.__version__.split(".", 1)[0]) >= 5 | |
| else ["lm_head.weight"] | |
| ) | |
| _supports_cache_class = False | |
| main_input_name = "input_ids" | |
| def __init__(self, config: CabaConfig): | |
| super().__init__(config) | |
| self.model = CabaModel(config) | |
| self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) | |
| self.post_init() | |
| if config.tie_word_embeddings: | |
| self.lm_head.weight = self.model.embed_tokens.weight | |
| def get_input_embeddings(self): | |
| return self.model.embed_tokens | |
| def set_input_embeddings(self, value): | |
| self.model.set_input_embeddings(value) | |
| def get_output_embeddings(self): | |
| return self.lm_head | |
| def set_output_embeddings(self, value): | |
| self.lm_head = value | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| labels: Optional[torch.LongTensor] = None, | |
| past_key_values=None, | |
| use_cache: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| **kwargs, | |
| ): | |
| if use_cache is None: | |
| use_cache = bool(self.config.use_cache and not self.training) | |
| hidden, cache, metrics = self.model( | |
| input_ids, | |
| past_key_values=past_key_values, | |
| use_cache=bool(use_cache), | |
| ) | |
| logits = self.lm_head(hidden).float() | |
| loss = None | |
| if labels is not None: | |
| shift_logits = logits[:, :-1, :].contiguous() | |
| shift_labels = labels[:, 1:].contiguous() | |
| loss = F.cross_entropy( | |
| shift_logits.view(-1, self.config.vocab_size), | |
| shift_labels.view(-1), | |
| ignore_index=-100, | |
| ) | |
| output = CabaCausalLMOutput( | |
| loss=loss, | |
| logits=logits, | |
| past_key_values=cache, | |
| memory_metrics=metrics, | |
| ) | |
| if return_dict is False: | |
| items = (logits, cache, metrics) | |
| return ((loss,) + items) if loss is not None else items | |
| return output | |
| def generate( | |
| self, | |
| input_ids: torch.LongTensor, | |
| max_new_tokens: int = 64, | |
| do_sample: bool = False, | |
| temperature: float = 1.0, | |
| top_k: int = 0, | |
| top_p: float = 1.0, | |
| eos_token_id: Optional[int] = None, | |
| pad_token_id: Optional[int] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| **kwargs, | |
| ) -> torch.LongTensor: | |
| """Generate with the recurrent state; supports greedy and top-k/top-p sampling.""" | |
| if input_ids.ndim != 2 or input_ids.shape[1] == 0: | |
| raise ValueError("input_ids must be a non-empty [batch, sequence] tensor") | |
| if temperature <= 0: | |
| raise ValueError("temperature must be > 0") | |
| if kwargs: | |
| unsupported = ", ".join(sorted(kwargs)) | |
| raise TypeError(f"Unsupported generation options in ÇABA v0-core: {unsupported}") | |
| self.eval() | |
| eos = self.config.eos_token_id if eos_token_id is None else eos_token_id | |
| pad = self.config.pad_token_id if pad_token_id is None else pad_token_id | |
| sequences = input_ids | |
| output = self(input_ids, use_cache=True) | |
| cache = output.past_key_values | |
| logits = output.logits[:, -1, :] | |
| finished = torch.zeros(input_ids.shape[0], dtype=torch.bool, device=input_ids.device) | |
| for _ in range(max_new_tokens): | |
| scores = logits.float() | |
| if do_sample: | |
| scores = scores / temperature | |
| if top_k > 0: | |
| k = min(int(top_k), scores.shape[-1]) | |
| threshold = torch.topk(scores, k, dim=-1).values[:, -1, None] | |
| scores = scores.masked_fill(scores < threshold, -float("inf")) | |
| if 0.0 < top_p < 1.0: | |
| sorted_scores, sorted_ids = torch.sort(scores, descending=True, dim=-1) | |
| cumulative = torch.softmax(sorted_scores, dim=-1).cumsum(dim=-1) | |
| remove = cumulative > top_p | |
| remove[:, 1:] = remove[:, :-1].clone() | |
| remove[:, 0] = False | |
| sorted_scores = sorted_scores.masked_fill(remove, -float("inf")) | |
| scores = torch.full_like(scores, -float("inf")).scatter( | |
| 1, sorted_ids, sorted_scores | |
| ) | |
| next_token = torch.multinomial(torch.softmax(scores, dim=-1), num_samples=1) | |
| else: | |
| next_token = scores.argmax(dim=-1, keepdim=True) | |
| if eos is not None: | |
| next_token = torch.where( | |
| finished[:, None], | |
| torch.full_like(next_token, int(pad)), | |
| next_token, | |
| ) | |
| sequences = torch.cat((sequences, next_token), dim=1) | |
| if eos is not None: | |
| finished |= next_token[:, 0].eq(int(eos)) | |
| if bool(finished.all()): | |
| break | |
| output = self(next_token, past_key_values=cache, use_cache=True) | |
| cache = output.past_key_values | |
| logits = output.logits[:, -1, :] | |
| return sequences | |