Text Generation
Transformers
Safetensors
qwen3_5_text
tinycenn
cenn
language-modeling
research
conversational
Instructions to use vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32") model = AutoModelForCausalLM.from_pretrained("vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32
- SGLang
How to use vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32 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 "vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32" \ --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": "vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32", "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 "vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32" \ --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": "vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32 with Docker Model Runner:
docker model run hf.co/vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32
Download src/tinycenn_lm/pdelta2_flash.py from vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32: direct link, hf CLI and curl.
- Browser
- Download file 14.4 kB
-
https://huggingface.co/vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32/resolve/a38f16360c3cb10c703d5ad743022f5421d31a1e/src/tinycenn_lm/pdelta2_flash.py
- Command line
-
hf download hf://vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32@a38f16360c3cb10c703d5ad743022f5421d31a1e/src/tinycenn_lm/pdelta2_flash.py
-
curl -L -o pdelta2_flash.py https://huggingface.co/vtava/Qwen3.5-0.8B-PDelta3-CLVR-Local32/resolve/a38f16360c3cb10c703d5ad743022f5421d31a1e/src/tinycenn_lm/pdelta2_flash.py
14.4 kB
| """PDelta2-Flash research ingredients for a stronger/faster single-layer replacement. | |
| The module keeps the proven P-Delta2 recurrent core and tests lightweight ideas | |
| suggested by recent hybrid efficient-attention models: | |
| - function-preserving per-head output gating; | |
| - a short causal value convolution (kernel 4 by default); | |
| - compact content-indexed block summaries for long-range recall; | |
| - FP16 persistent recurrent-memory storage with FP32 curvature. | |
| The indexed path stores one K/V summary per completed block, not every token. | |
| It is therefore a compact growing memory, while the P-Delta2 recurrent state | |
| remains bounded. This is an independent TinyCeNN experiment. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from dataclasses import dataclass | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import Tensor, nn | |
| from tinycenn_lm.pdelta2_features import PDelta2Core, PDeltaState | |
| class FlashState: | |
| recurrent: PDeltaState | |
| conv_tail: Tensor | None = None | |
| block_keys: Tensor | None = None | |
| block_values: Tensor | None = None | |
| pending_keys: Tensor | None = None | |
| pending_values: Tensor | None = None | |
| def causal_depthwise_value_conv(v: Tensor, weight: Tensor) -> Tensor: | |
| """Causal depthwise 1-D convolution over KV-head/value channels.""" | |
| if weight.ndim != 3 or weight.shape[1] != 1: | |
| raise ValueError("weight must be [channels, 1, kernel]") | |
| b, h, t, d = v.shape | |
| channels = h * d | |
| if weight.shape[0] != channels: | |
| raise ValueError("weight channel count does not match values") | |
| x = v.transpose(1, 2).reshape(b, t, channels).transpose(1, 2) | |
| kernel = weight.shape[-1] | |
| x = F.pad(x, (kernel - 1, 0)) | |
| y = F.conv1d(x, weight, groups=channels) | |
| return y.transpose(1, 2).reshape(b, t, h, d).transpose(1, 2) | |
| def indexed_block_attention(q: Tensor, k: Tensor, v: Tensor, groups: int, | |
| block_size: int = 16, topk: int = 4): | |
| """Attend to compact summaries of completed causal blocks. | |
| A query at token ``t`` may only see blocks whose final token is < ``t``. | |
| Each block contributes one mean-key and one mean-value summary, reducing | |
| long-range storage by roughly ``block_size`` versus tokenwise KV storage. | |
| """ | |
| if block_size < 2 or topk < 1: | |
| raise ValueError("block_size must be >=2 and topk positive") | |
| b, h, t, d = q.shape | |
| k = k.repeat_interleave(groups, dim=1) | |
| v = v.repeat_interleave(groups, dim=1) | |
| blocks = t // block_size | |
| if blocks == 0: | |
| return q.new_zeros(q.shape), torch.zeros((b, h, t, 1), dtype=torch.bool, device=q.device) | |
| usable = blocks * block_size | |
| bk = k[:, :, :usable].reshape(b, h, blocks, block_size, d).mean(dim=3) | |
| bv = v[:, :, :usable].reshape(b, h, blocks, block_size, d).mean(dim=3) | |
| bk = F.normalize(bk, dim=-1) | |
| qn = F.normalize(q, dim=-1) | |
| scores = torch.einsum("bhtd,bhnd->bhtn", qn, bk) / math.sqrt(d) | |
| positions = torch.arange(t, device=q.device) | |
| block_ends = torch.arange(blocks, device=q.device) * block_size + (block_size - 1) | |
| valid = block_ends[None, :] < positions[:, None] | |
| scores = scores.masked_fill(~valid[None, None], float("-inf")) | |
| ksel = min(topk, blocks) | |
| top_scores, index = torch.topk(scores, k=ksel, dim=-1) | |
| top_valid = torch.isfinite(top_scores) | |
| safe = top_scores.masked_fill(~top_valid, -1e4) | |
| weights = safe.softmax(dim=-1) * top_valid | |
| weights = weights / weights.sum(dim=-1, keepdim=True).clamp_min(1e-8) | |
| bank = bv[:, :, None].expand(-1, -1, t, -1, -1) | |
| selected = torch.gather(bank, 3, index.unsqueeze(-1).expand(-1, -1, -1, -1, d)) | |
| output = (weights.unsqueeze(-1) * selected).sum(dim=3) | |
| available = top_valid.any(dim=-1, keepdim=True) | |
| return output, available | |
| def indexed_summary_attention(q: Tensor, block_keys: Tensor | None, | |
| block_values: Tensor | None, groups: int, topk: int): | |
| """One-step indexed lookup against already-completed block summaries.""" | |
| if block_keys is None or block_keys.shape[2] == 0: | |
| shape = (q.shape[0], q.shape[1], q.shape[2], q.shape[3]) | |
| return q.new_zeros(shape), torch.zeros( | |
| (q.shape[0], q.shape[1], q.shape[2], 1), dtype=torch.bool, device=q.device | |
| ) | |
| k = block_keys.float().repeat_interleave(groups, dim=1) | |
| v = block_values.float().repeat_interleave(groups, dim=1) | |
| scores = torch.einsum("bhtd,bhnd->bhtn", F.normalize(q.float(), dim=-1), | |
| F.normalize(k, dim=-1)) / math.sqrt(q.shape[-1]) | |
| ksel = min(topk, scores.shape[-1]) | |
| top_scores, index = torch.topk(scores, k=ksel, dim=-1) | |
| bank = v[:, :, None].expand(-1, -1, q.shape[2], -1, -1) | |
| selected = torch.gather( | |
| bank, 3, index.unsqueeze(-1).expand(-1, -1, -1, -1, q.shape[-1]) | |
| ) | |
| weights = top_scores.softmax(dim=-1) | |
| return (weights.unsqueeze(-1) * selected).sum(dim=3), torch.ones( | |
| (q.shape[0], q.shape[1], q.shape[2], 1), dtype=torch.bool, device=q.device | |
| ) | |
| class FlashPDelta2Layer(nn.Module): | |
| """P-Delta2 plus cheap gating, short causal convolution and indexed recall.""" | |
| def __init__(self, num_heads: int, num_kv_heads: int, head_dim: int, | |
| feature_dim: int = 96, chunk_size: int = 32, | |
| output_gate: bool = False, conv_kernel: int = 1, | |
| indexed_retrieval: bool = False, block_size: int = 16, | |
| index_topk: int = 4, state_dtype: str = "fp16"): | |
| super().__init__() | |
| if state_dtype not in {"fp16", "fp32"}: | |
| raise ValueError("state_dtype must be fp16 or fp32") | |
| if conv_kernel < 1: | |
| raise ValueError("conv_kernel must be positive") | |
| if num_heads % num_kv_heads: | |
| raise ValueError("num_heads must be divisible by num_kv_heads") | |
| self.num_heads = num_heads | |
| self.num_kv_heads = num_kv_heads | |
| self.head_dim = head_dim | |
| self.feature_dim = feature_dim | |
| self.groups = num_heads // num_kv_heads | |
| self.chunk_size = chunk_size | |
| self.output_gate = bool(output_gate) | |
| self.conv_kernel = int(conv_kernel) | |
| self.indexed_retrieval = bool(indexed_retrieval) | |
| self.block_size = int(block_size) | |
| self.index_topk = int(index_topk) | |
| self.state_dtype = state_dtype | |
| self.core = PDelta2Core( | |
| num_heads, num_kv_heads, head_dim, feature_dim=feature_dim, chunk_size=chunk_size | |
| ) | |
| if self.conv_kernel > 1: | |
| channels = num_kv_heads * head_dim | |
| kernel = torch.zeros(channels, 1, self.conv_kernel) | |
| kernel[:, 0, -1] = 1.0 | |
| self.conv_weight = nn.Parameter(kernel) | |
| else: | |
| self.register_parameter("conv_weight", None) | |
| if self.output_gate: | |
| self.output_gate_w = nn.Parameter(torch.zeros(num_heads, head_dim)) | |
| self.output_gate_b = nn.Parameter(torch.zeros(num_heads)) | |
| else: | |
| self.register_parameter("output_gate_w", None) | |
| self.register_parameter("output_gate_b", None) | |
| if self.indexed_retrieval: | |
| self.index_mix_w = nn.Parameter(torch.zeros(num_heads, head_dim)) | |
| self.index_mix_b = nn.Parameter(torch.full((num_heads,), -2.0)) | |
| else: | |
| self.register_parameter("index_mix_w", None) | |
| self.register_parameter("index_mix_b", None) | |
| def config(self): | |
| return { | |
| "num_heads": self.num_heads, | |
| "num_kv_heads": self.num_kv_heads, | |
| "head_dim": self.head_dim, | |
| "feature_dim": self.feature_dim, | |
| "chunk_size": self.chunk_size, | |
| "output_gate": self.output_gate, | |
| "conv_kernel": self.conv_kernel, | |
| "indexed_retrieval": self.indexed_retrieval, | |
| "block_size": self.block_size, | |
| "index_topk": self.index_topk, | |
| "state_dtype": self.state_dtype, | |
| } | |
| def _convolve_values(self, v: Tensor, tail: Tensor | None = None): | |
| v = v.float() | |
| if self.conv_weight is None: | |
| return v | |
| if tail is None: | |
| return causal_depthwise_value_conv(v, self.conv_weight) | |
| joined = torch.cat((tail.float(), v), dim=2) | |
| return causal_depthwise_value_conv(joined, self.conv_weight)[:, :, -v.shape[2]:] | |
| def _pack_recurrent_state(self, state: PDeltaState): | |
| memory = state.memory | |
| if self.state_dtype == "fp16": | |
| memory = memory.to(torch.float16) | |
| else: | |
| memory = memory.float() | |
| return PDeltaState(memory=memory, curvature=state.curvature.float()) | |
| def _unpack_recurrent_state(self, state: PDeltaState | None): | |
| if state is None: | |
| return None | |
| return PDeltaState(memory=state.memory.float(), curvature=state.curvature.float()) | |
| def _build_index_state(self, k: Tensor, v: Tensor): | |
| if not self.indexed_retrieval: | |
| return None, None, None, None | |
| complete = (k.shape[2] // self.block_size) * self.block_size | |
| # A completed block is only available to queries after its last token. | |
| bk = bv = None | |
| if complete: | |
| blocks = complete // self.block_size | |
| bk = k[:, :, :complete].reshape( | |
| k.shape[0], k.shape[1], blocks, self.block_size, k.shape[-1] | |
| ).mean(dim=3).to(torch.float16) | |
| bv = v[:, :, :complete].reshape( | |
| v.shape[0], v.shape[1], blocks, self.block_size, v.shape[-1] | |
| ).mean(dim=3).to(torch.float16) | |
| pk = k[:, :, complete:].float() | |
| pv = v[:, :, complete:].float() | |
| return bk, bv, pk, pv | |
| def _advance_index_state(self, state: FlashState, k: Tensor, v: Tensor): | |
| pk = k.float() if state.pending_keys is None else torch.cat((state.pending_keys.float(), k.float()), dim=2) | |
| pv = v.float() if state.pending_values is None else torch.cat((state.pending_values.float(), v.float()), dim=2) | |
| bk, bv = state.block_keys, state.block_values | |
| while pk.shape[2] >= self.block_size: | |
| new_k = pk[:, :, :self.block_size].mean(dim=2, keepdim=True).to(torch.float16) | |
| new_v = pv[:, :, :self.block_size].mean(dim=2, keepdim=True).to(torch.float16) | |
| bk = new_k if bk is None else torch.cat((bk, new_k), dim=2) | |
| bv = new_v if bv is None else torch.cat((bv, new_v), dim=2) | |
| pk, pv = pk[:, :, self.block_size:], pv[:, :, self.block_size:] | |
| return bk, bv, pk, pv | |
| def forward(self, q: Tensor, k: Tensor, v: Tensor, state: FlashState | None = None, | |
| return_state: bool = False, implementation: str = "chunk"): | |
| if implementation != "chunk": | |
| raise ValueError("PDelta2-Flash uses the chunk implementation") | |
| if state is not None and self.indexed_retrieval and q.shape[2] != 1: | |
| raise ValueError("stateful indexed retrieval currently supports one decode token at a time") | |
| tail = None if state is None else state.conv_tail | |
| conv_v = self._convolve_values(v, tail) | |
| recurrent_state = None if state is None else self._unpack_recurrent_state(state.recurrent) | |
| recurrent, new_recurrent = self.core( | |
| q, k, conv_v, state=recurrent_state, return_state=True | |
| ) | |
| output = recurrent | |
| if self.output_gate: | |
| raw = ( | |
| torch.einsum("bhtd,hd->bht", F.normalize(q.float(), dim=-1), self.output_gate_w) | |
| + self.output_gate_b[None, :, None] | |
| ) | |
| # Exactly 1.0 at initialization; bounded to [0.75, 1.25]. | |
| gain = 1.0 + 0.25 * torch.tanh(raw) | |
| output = output * gain.unsqueeze(-1) | |
| if self.indexed_retrieval: | |
| if state is None: | |
| indexed, available = indexed_block_attention( | |
| q.float(), k.float(), conv_v, self.groups, self.block_size, self.index_topk | |
| ) | |
| else: | |
| indexed, available = indexed_summary_attention( | |
| q.float(), state.block_keys, state.block_values, self.groups, self.index_topk | |
| ) | |
| mix = ( | |
| torch.einsum("bhtd,hd->bht", F.normalize(q.float(), dim=-1), self.index_mix_w) | |
| + self.index_mix_b[None, :, None] | |
| ).sigmoid().unsqueeze(-1) | |
| mix = mix * available.to(mix.dtype) | |
| output = (1.0 - mix) * output + mix * indexed | |
| if not return_state: | |
| return output | |
| keep = self.conv_kernel - 1 | |
| if keep: | |
| raw = v.float() if tail is None else torch.cat((tail.float(), v.float()), dim=2) | |
| new_tail = raw[:, :, -keep:].clone() | |
| else: | |
| new_tail = None | |
| if self.indexed_retrieval: | |
| if state is None: | |
| bk, bv, pk, pv = self._build_index_state(k.float(), conv_v) | |
| else: | |
| bk, bv, pk, pv = self._advance_index_state(state, k.float(), conv_v) | |
| else: | |
| bk = bv = pk = pv = None | |
| new_state = FlashState( | |
| recurrent=self._pack_recurrent_state(new_recurrent), | |
| conv_tail=new_tail, | |
| block_keys=bk, | |
| block_values=bv, | |
| pending_keys=pk, | |
| pending_values=pv, | |
| ) | |
| return output, new_state | |
| def recurrent_state_bytes(self, batch_size: int = 1, context: int | None = None): | |
| memory_elements = self.num_kv_heads * self.feature_dim * self.head_dim | |
| curvature_elements = self.num_kv_heads * self.feature_dim | |
| memory_bytes = 2 if self.state_dtype == "fp16" else 4 | |
| total = batch_size * (memory_elements * memory_bytes + curvature_elements * 4) | |
| if self.conv_kernel > 1: | |
| total += batch_size * (self.conv_kernel - 1) * self.num_kv_heads * self.head_dim * 4 | |
| if self.indexed_retrieval and context is not None: | |
| blocks = context // self.block_size | |
| total += batch_size * 2 * blocks * self.num_kv_heads * self.head_dim * 2 | |
| pending = context % self.block_size | |
| total += batch_size * 2 * pending * self.num_kv_heads * self.head_dim * 4 | |
| return total | |
| def index_pairs_per_token(self, context: int): | |
| if not self.indexed_retrieval: | |
| return 0 | |
| blocks = context // self.block_size | |
| return min(self.index_topk, blocks) | |