Image-Text-to-Text
Transformers
Safetensors
deepseek_v41
text-generation
nvfp4
fp4
deepseek
Mixture of Experts
multimodal
libertai
8-bit precision
fp8
Instructions to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4")# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4
- SGLang
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 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 "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4" \ --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": "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", "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 "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4" \ --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": "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with Docker Model Runner:
docker model run hf.co/LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4
| import math | |
| from contextlib import contextmanager | |
| from dataclasses import dataclass | |
| from functools import lru_cache | |
| from typing import Literal | |
| import torch | |
| import torch.distributed as dist | |
| import torch.nn.functional as F | |
| from engram import EngramLayout, NgramHashState | |
| from image_processor import IMAGE, IMAGE_END, IMAGE_NEW_LINE, IMAGE_START | |
| from kernel import ( | |
| act_quant, | |
| fp4_act_quant, | |
| fp4_gemm, | |
| fp8_gemm, | |
| hc_split_sinkhorn, | |
| sparse_attn, | |
| ) | |
| from torch import nn | |
| from vision import Aligner, ViT | |
| # Set once by Transformer.__init__; one model per process, so layers just read them. | |
| world_size = 1 | |
| rank = 0 | |
| default_dtype = torch.float8_e4m3fn # storage dtype for Linear weights, from ModelArgs.dtype | |
| fp8_block_size = 32 # one fp8 scale per 32x32 weight block / 32 activations | |
| fp4_block_size = 32 # one fp4 scale per 32 elements along K | |
| scale_fmt = "ue8m0" | |
| scale_dtype = torch.float8_e8m0fnu | |
| def set_dtype(dtype): | |
| """Temporarily override torch's default dtype, restoring it even if the body raises.""" | |
| prev = torch.get_default_dtype() | |
| torch.set_default_dtype(dtype) | |
| try: | |
| yield | |
| finally: | |
| torch.set_default_dtype(prev) | |
| class ModelArgs: | |
| """Field names are exactly the config JSON keys. The defaults are a small model that | |
| `python model.py` can run, not the released shapes -- though the scale-independent | |
| values (norm_eps, score_func, hc_*, engram_*) do match it.""" | |
| # runtime limits rather than model shape: they size the KV caches | |
| max_batch_size: int = 4 | |
| max_seq_len: int = 4096 | |
| temperature: float = 1 | |
| dtype: Literal["bf16", "fp8"] = "fp8" | |
| expert_dtype: Literal["fp4"] | None = "fp4" | |
| vocab_size: int = 129280 | |
| dim: int = 1024 | |
| moe_inter_dim: int = 1024 | |
| n_layers: int = 5 | |
| n_mtp_layers: int = 1 # extra draft layers appended after the backbone, indices n_layers.. | |
| n_heads: int = 16 | |
| # moe | |
| n_routed_experts: int = 8 | |
| n_shared_experts: int = 1 | |
| n_activated_experts: int = 2 | |
| score_func: Literal["softmax", "sigmoid", "sqrtsoftplus"] = "sqrtsoftplus" | |
| gate_temp: float = 1.0 | |
| norm_topk_prob: bool = True | |
| route_scale: float = 1.0 | |
| swiglu_limit: float = 0.0 | |
| # attention: latent q/kv projections, plus a LoRA-factorised output projection over o_groups | |
| q_lora_rank: int = 256 | |
| head_dim: int = 128 | |
| rope_head_dim: int = 32 | |
| norm_eps: float = 1e-20 | |
| o_groups: int = 8 | |
| o_lora_rank: int = 256 | |
| # sparse attention: every layer attends over a sliding window, and may add compressed KV on top | |
| window_size: int = 128 | |
| # one entry per layer, MTP layers included: 0 = sliding window only, r = KV compressed r-to-1 | |
| compress_ratios: tuple[int, ...] = (0, 2, 2, 1, 1, 0) | |
| # layers sharing a ratio also share one compressed KV and one indexer, produced by the first | |
| kv_source_layers: tuple[int, ...] = (1, 3) | |
| index_source_layers: tuple[int, ...] = (1, 3) | |
| # rope, with YaRN extrapolation when original_seq_len > 0. Compressed KV rotates at its own | |
| # theta because one latent stands for compress_ratio tokens, so its positions are further apart. | |
| compress_rope_theta: float = 40000.0 | |
| original_seq_len: int = 0 | |
| rope_theta: float = 10000.0 | |
| rope_factor: float = 40 | |
| beta_fast: int = 32 | |
| beta_slow: int = 1 | |
| # the indexer: a small extra attention that scores compressed positions, so each query can keep | |
| # just `index_topk` of them. Names match DeepSeek-V3.2-Exp, where this mechanism first appeared. | |
| index_n_heads: int = 16 | |
| index_head_dim: int = 64 | |
| index_topk: int = 64 | |
| # candidate pre-filtering: candidate_source_layer < 0 turns it off and the other two are unused | |
| candidate_source_layer: int = -1 | |
| candidate_topk_blocks: int = 0 | |
| candidate_block_size: int = 0 | |
| # hyper-connections: the residual stream is carried as hc_mult parallel copies | |
| hc_mult: int = 4 | |
| hc_sinkhorn_iters: int = 20 | |
| hc_eps: float = 1e-6 | |
| # engram: n-gram hash lookups added into the residual stream at a few layers | |
| engram_layer_ids: tuple[int, ...] = () | |
| engram_num_embeddings: tuple[int, ...] = () # unpadded table rows; each rank allocates ceil(rows / world_size) | |
| engram_max_ngram_size: int = 1 | |
| engram_vocab_size: int = 0 # bucket size each (n-gram size, head) starts searching primes from | |
| engram_n_heads: int = 0 | |
| engram_head_dim: int = 0 | |
| engram_pad_id: int = 2 # token that fills n-gram slots with no history; matches training | |
| # size of the compressed tokenizer vocab; every hash multiplier is derived from it | |
| engram_compressed_vocab_size: int = 0 | |
| # vision (VL); vision_n_layers == 0 disables the vision path | |
| vision_n_layers: int = 0 | |
| vision_dim: int = 1024 | |
| vision_n_heads: int = 16 | |
| vision_inter_dim: int = 2816 | |
| vision_patch_size: int = 14 | |
| vision_rope_theta: float = 10000.0 | |
| vision_downsample_ratio: int = 3 | |
| vision_max_n_token: int = 1024 | |
| vision_min_pixels: int = 544 * 544 | |
| vision_max_wh_ratio: int | None = None | |
| # raw id of <|deepseek_image|>; every position of an image span carries this id in input_ids | |
| image_token_id: int = 129264 | |
| # dspark draft head. Only the forward pass is implemented here -- nothing calls forward_spec, | |
| # so these are read but the speculative-decoding loop itself is out of scope for this repo. | |
| dspark_block_size: int = 0 | |
| dspark_noise_token_id: int = 0 | |
| dspark_target_layer_ids: tuple[int, ...] = () | |
| dspark_markov_rank: int = 256 | |
| dspark_n_routed_experts: int = 0 | |
| dspark_n_activated_experts: int = 0 | |
| def vision_enabled(self) -> bool: | |
| return self.vision_n_layers > 0 | |
| def get_moe_config(self, layer_id: int) -> tuple[int, int]: | |
| """Return the routed/activated expert counts for a given layer.""" | |
| if layer_id < self.n_layers: | |
| return self.n_routed_experts, self.n_activated_experts | |
| return ( | |
| self.dspark_n_routed_experts or self.n_routed_experts, | |
| self.dspark_n_activated_experts or self.n_activated_experts, | |
| ) | |
| class ParallelEmbedding(nn.Module): | |
| """Embedding sharded along the vocab dimension. Each rank holds vocab_size // world_size rows. | |
| Out-of-range indices are zero-masked before all_reduce to combine partial embeddings.""" | |
| def __init__(self, vocab_size: int, dim: int): | |
| super().__init__() | |
| self.vocab_size = vocab_size | |
| self.dim = dim | |
| assert vocab_size % world_size == 0, ( | |
| f"Vocabulary size must be divisible by world size (world_size={world_size})" | |
| ) | |
| self.part_vocab_size = vocab_size // world_size | |
| self.vocab_start_idx = rank * self.part_vocab_size | |
| self.vocab_end_idx = self.vocab_start_idx + self.part_vocab_size | |
| self.weight = nn.Parameter(torch.empty(self.part_vocab_size, self.dim)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if world_size > 1: | |
| # ids off this rank read row 0 then get zeroed, so the all_reduce sums one real row | |
| mask = (x < self.vocab_start_idx) | (x >= self.vocab_end_idx) | |
| x = x - self.vocab_start_idx | |
| x[mask] = 0 | |
| y = F.embedding(x, self.weight) | |
| if world_size > 1: | |
| y[mask] = 0 | |
| dist.all_reduce(y) | |
| return y | |
| def linear(x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor | None = None) -> torch.Tensor: | |
| """Pick a GEMM from the weight dtype. Quantized weights need a quantized activation, and both | |
| fp4 and fp8 weights take an fp8 one -- for fp4 the kernel handles the mixed precision.""" | |
| assert bias is None | |
| if weight.dtype == torch.float4_e2m1fn_x2: | |
| x, s = act_quant(x, fp8_block_size, scale_fmt, scale_dtype) | |
| return fp4_gemm( | |
| x, | |
| s, | |
| weight, | |
| weight.scale, | |
| scale_dtype, | |
| act_block_size=fp8_block_size, | |
| ) | |
| elif weight.dtype == torch.float8_e4m3fn: | |
| x, s = act_quant(x, fp8_block_size, scale_fmt, scale_dtype) | |
| return fp8_gemm( | |
| x, | |
| s, | |
| weight, | |
| weight.scale, | |
| scale_dtype, | |
| block_size=fp8_block_size, | |
| ) | |
| else: | |
| return F.linear(x, weight) | |
| class Linear(nn.Module): | |
| """bf16, fp8 or fp4 weights. Quantized ones get a `scale`, also attached to `.weight` so that | |
| `linear()` can reach it.""" | |
| def __init__(self, in_features: int, out_features: int, bias: bool = False, dtype=None): | |
| super().__init__() | |
| self.in_features = in_features | |
| self.out_features = out_features | |
| dtype = dtype or default_dtype | |
| if dtype == torch.float4_e2m1fn_x2: | |
| # two values per byte: [out, in] logically, [out, in//2] stored | |
| self.weight = nn.Parameter(torch.empty(out_features, in_features // 2, dtype=torch.float4_e2m1fn_x2)) | |
| self.weight.scale = self.scale = nn.Parameter( | |
| torch.empty(out_features, in_features // fp4_block_size, dtype=torch.float8_e8m0fnu) | |
| ) | |
| elif dtype == torch.float8_e4m3fn: | |
| self.weight = nn.Parameter(torch.empty(out_features, in_features, dtype=dtype)) | |
| self.weight.scale = self.scale = nn.Parameter( | |
| torch.empty( | |
| (out_features + fp8_block_size - 1) // fp8_block_size, | |
| (in_features + fp8_block_size - 1) // fp8_block_size, | |
| dtype=torch.float8_e8m0fnu, | |
| ) | |
| ) | |
| else: | |
| self.weight = nn.Parameter(torch.empty(out_features, in_features, dtype=dtype)) | |
| self.register_parameter("scale", None) | |
| if bias: | |
| self.bias = nn.Parameter(torch.empty(out_features)) | |
| else: | |
| self.register_parameter("bias", None) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return linear(x, self.weight, self.bias) | |
| class ColumnParallelLinear(Linear): | |
| """Splits the output dim across ranks; each rank's slice of the output is already complete.""" | |
| def __init__(self, in_features: int, out_features: int, bias: bool = False, dtype=None): | |
| assert out_features % world_size == 0, ( | |
| f"Output features must be divisible by world size (world_size={world_size})" | |
| ) | |
| self.part_out_features = out_features // world_size | |
| super().__init__(in_features, self.part_out_features, bias, dtype) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return linear(x, self.weight, self.bias) | |
| class RowParallelLinear(Linear): | |
| """Splits the reduction dim, so each rank holds a partial sum: hence the fp32 all_reduce, with | |
| the bias added only after it.""" | |
| def __init__(self, in_features: int, out_features: int, bias: bool = False, dtype=None): | |
| assert in_features % world_size == 0, ( | |
| f"Input features must be divisible by world size (world_size={world_size})" | |
| ) | |
| self.part_in_features = in_features // world_size | |
| super().__init__(self.part_in_features, out_features, bias, dtype) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| y = linear(x, self.weight, None) | |
| if world_size > 1: | |
| y = y.float() | |
| dist.all_reduce(y) | |
| if self.bias is not None: | |
| y += self.bias | |
| return y.type_as(x) | |
| class RMSNorm(nn.Module): | |
| def __init__(self, dim: int, eps: float = 1e-6): | |
| super().__init__() | |
| self.dim = dim | |
| self.eps = eps | |
| self.weight = nn.Parameter(torch.ones(dim)) | |
| def forward(self, x: torch.Tensor): | |
| dtype = x.dtype | |
| x = x.float() | |
| var = x.square().mean(-1, keepdim=True) | |
| x = x * torch.rsqrt(var + self.eps) | |
| return (self.weight * x).to(dtype) | |
| class ParallelEngramEmbedding(nn.Module): | |
| """The n-gram hash table, sharded over its rows. Stays fp8: rows are dequantized on lookup.""" | |
| def __init__(self, num_embeddings: int, dim: int): | |
| super().__init__() | |
| self.num_embeddings = num_embeddings | |
| self.dim = dim | |
| self.part_num_embeddings = (num_embeddings + world_size - 1) // world_size | |
| self.vocab_start_idx = rank * self.part_num_embeddings | |
| self.vocab_end_idx = self.vocab_start_idx + self.part_num_embeddings | |
| self.block_size = fp8_block_size | |
| # the table stays fp8 as stored: rows are dequantized with `scale` on lookup | |
| self.weight = nn.Parameter(torch.empty(self.part_num_embeddings, dim, dtype=torch.float8_e4m3fn)) | |
| self.scale = nn.Parameter(torch.empty(self.part_num_embeddings, dim // self.block_size, dtype=scale_dtype)) | |
| def forward(self, indices: torch.Tensor) -> torch.Tensor: | |
| mask = (indices < self.vocab_start_idx) | (indices >= self.vocab_end_idx) | |
| local_indices = indices - self.vocab_start_idx | |
| local_indices = local_indices.masked_fill(mask, 0) | |
| values = F.embedding(local_indices, self.weight) | |
| scales = F.embedding(local_indices, self.scale) | |
| values = values.float().unflatten(-1, (-1, self.block_size)) * scales.float().unsqueeze(-1) | |
| values = values.flatten(-2).to(torch.bfloat16) | |
| values = values.masked_fill(mask.unsqueeze(-1), 0) | |
| if world_size > 1: | |
| dist.all_reduce(values) | |
| return values | |
| class Engram(nn.Module): | |
| """Writes an n-gram lookup into the residual stream, gated by how well it matches that stream. | |
| The hash ids fetch `n_hash_cols` rows; `wkv` turns them into one key per hc copy plus a shared | |
| value. The gate is a normalized dot product of stream against key. | |
| """ | |
| def __init__(self, args: ModelArgs, layer_id: int, layout: EngramLayout): | |
| super().__init__() | |
| self.layer_id = layer_id | |
| self.layer_hash_index = layout.layer_ids.index(layer_id) | |
| self.dim = args.dim | |
| self.hc_mult = args.hc_mult | |
| self.clamp_value = 1e-6 | |
| self.embed = ParallelEngramEmbedding(layout.num_embeddings[self.layer_hash_index], layout.head_dim) | |
| n_hash_cols = (layout.max_ngram_size - 1) * layout.n_heads | |
| self.wkv = Linear(n_hash_cols * layout.head_dim, args.dim * (args.hc_mult + 1)) | |
| self.eps = args.norm_eps | |
| self.q_weight = nn.Parameter(torch.ones(args.hc_mult, args.dim)) | |
| self.k_weight = nn.Parameter(torch.ones(args.hc_mult, args.dim)) | |
| def forward(self, x: torch.Tensor, hash_ids: torch.Tensor, token_mask: torch.Tensor | None = None) -> torch.Tensor: | |
| """x: [B, L, hc_mult, dim]; hash_ids: [B, L, n_hash_cols]; token_mask: [B, L], False shuts | |
| the gate so those positions pass through untouched.""" | |
| kv = self.wkv(self.embed(hash_ids).flatten(-2)) | |
| key, value = kv.split([self.hc_mult * self.dim, self.dim], dim=-1) | |
| key = key.float().unflatten(-1, (self.hc_mult, self.dim)) | |
| weight = self.q_weight.float() * self.k_weight.float() # only ever used as a product | |
| h, eps = x.float(), self.eps | |
| # normalized per (token, hc copy) over `dim`, NOT jointly over the copies | |
| rstd = torch.rsqrt(h.square().mean(-1) + eps) * torch.rsqrt(key.square().mean(-1) + eps) | |
| dot = (h * weight * key).sum(-1) * rstd * self.dim**-0.5 | |
| # signed sqrt before the sigmoid, matching the training kernel | |
| gate = torch.sigmoid(torch.copysign(dot.abs().clamp_min(self.clamp_value).sqrt(), dot)) | |
| if token_mask is not None: | |
| gate = gate.masked_fill(~token_mask.unsqueeze(-1), 0) | |
| return (h + gate.unsqueeze(-1) * value.float().unsqueeze(-2)).to(x.dtype) | |
| def precompute_freqs_cis(dim, seqlen, original_seq_len, base, factor, beta_fast, beta_slow) -> torch.Tensor: | |
| """Rotary frequencies as complex exponentials, one row per position. | |
| With original_seq_len > 0 this applies YaRN: dimensions whose wavelength already fits inside the | |
| training context keep their frequency, those far beyond it are divided by `factor`, and the | |
| `beta_fast`..`beta_slow` band in between is faded across with a linear ramp. | |
| """ | |
| freqs = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim)) | |
| if original_seq_len > 0: | |
| # the dim whose wavelength completes `rotations` turns over the training context | |
| def corrected_dim(rotations): | |
| return dim * math.log(original_seq_len / (rotations * 2 * math.pi)) / (2 * math.log(base)) | |
| low = max(math.floor(corrected_dim(beta_fast)), 0) | |
| high = min(math.ceil(corrected_dim(beta_slow)), dim - 1) | |
| ramp = ((torch.arange(dim // 2, dtype=torch.float32) - low) / max(high - low, 1e-3)).clamp(0, 1) | |
| smooth = 1 - ramp | |
| freqs = freqs / factor * (1 - smooth) + freqs * smooth | |
| freqs = torch.outer(torch.arange(seqlen), freqs) | |
| return torch.polar(torch.ones_like(freqs), freqs) | |
| def apply_rotary_emb(x: torch.Tensor, freqs_cis: torch.Tensor, inverse: bool = False) -> torch.Tensor: | |
| """Rotate `x` in place, taking adjacent element pairs as complex numbers. Accepts [b, s, d] and | |
| [b, s, h, d]; `inverse` conjugates the rotation, which is how the attention output gets the | |
| query's rotation removed again so the cache can stay in one shared rotated form.""" | |
| y = x | |
| x = torch.view_as_complex(x.float().unflatten(-1, (-1, 2))) | |
| if inverse: | |
| freqs_cis = freqs_cis.conj() | |
| if x.ndim == 3: | |
| freqs_cis = freqs_cis.view(1, x.size(1), x.size(-1)) | |
| else: | |
| freqs_cis = freqs_cis.view(1, x.size(1), 1, x.size(-1)) | |
| x = torch.view_as_real(x * freqs_cis).flatten(-2) | |
| y.copy_(x) | |
| return y | |
| def get_window_topk_idxs(window_size: int, bsz: int, seqlen: int, start_pos: int): | |
| """Which sliding-window cache slots each query attends to; -1 marks a slot holding nothing. | |
| The cache is a ring of `window_size` slots. Prefill needs one row per query, each seeing its own | |
| causal window. A decode step has a single query that sees the whole ring, listed oldest first. | |
| Order within a row does not matter to `sparse_attn`, which handles every slot independently. | |
| """ | |
| if start_pos == 0: | |
| end = torch.arange(seqlen).unsqueeze(1) | |
| idxs = (end - window_size + 1).clamp(0) + torch.arange(min(seqlen, window_size)) | |
| idxs = torch.where(idxs > end, -1, idxs) # before the sequence started | |
| else: | |
| oldest = start_pos % window_size + 1 | |
| idxs = torch.cat([torch.arange(oldest, window_size), torch.arange(oldest)]) | |
| idxs = torch.where(idxs > start_pos, -1, idxs) # ring still filling | |
| # sparse_attn needs real [b, m, topk] int32 memory, hence the materializing expand | |
| return idxs.int().unsqueeze(0).expand(bsz, -1, -1).contiguous() | |
| class Compressor(nn.Module): | |
| """Pools `compress_ratio` consecutive tokens into one KV latent with a learned softmax gate. | |
| Returns the latent before RoPE, or None while a group is still filling up -- so during decode it | |
| only yields every `compress_ratio` steps, holding the partial group in `kv_state`/`score_state`. | |
| Pre-RoPE is deliberate: the indexer needs the unrotated form, so Attention rotates afterwards. | |
| """ | |
| def __init__(self, args: ModelArgs, layer_id: int): | |
| super().__init__() | |
| compress_ratio = args.compress_ratios[layer_id] | |
| head_dim = args.head_dim | |
| self.compress_ratio = compress_ratio | |
| self.head_dim = head_dim | |
| self.norm = RMSNorm(head_dim, args.norm_eps) | |
| # ratio 1 is a plain projection, so it stays in the checkpoint's bf16; the softmax pooling | |
| # above ratio 1 runs in fp32, so those weights are promoted to fp32 to match | |
| self.wkv = Linear(args.dim, head_dim, dtype=torch.float32 if compress_ratio > 1 else torch.bfloat16) | |
| if compress_ratio > 1: | |
| self.wgate = Linear(args.dim, head_dim, dtype=torch.float32) | |
| # tail of an incomplete group, carried across decode steps | |
| self.kv_state: torch.Tensor | |
| self.score_state: torch.Tensor | |
| state_shape = (args.max_batch_size, compress_ratio, head_dim) | |
| self.register_buffer("kv_state", torch.zeros(state_shape, dtype=torch.float32), persistent=False) | |
| self.register_buffer( | |
| "score_state", torch.full(state_shape, -torch.inf, dtype=torch.float32), persistent=False | |
| ) | |
| def forward(self, x: torch.Tensor, start_pos: int) -> torch.Tensor | None: | |
| bsz, seqlen, _ = x.size() | |
| ratio, dtype = self.compress_ratio, x.dtype | |
| if ratio == 1: # one token per group: nothing to pool, so no gate and no fp32 | |
| return self.norm(self.wkv(x)) | |
| x = x.float() | |
| kv, score = self.wkv(x), self.wgate(x) | |
| if start_pos == 0: | |
| should_compress = seqlen >= ratio | |
| remainder = seqlen % ratio | |
| cutoff = seqlen - remainder | |
| if remainder: # trailing partial group waits in the state | |
| kv, self.kv_state[:bsz, :remainder] = kv.split([cutoff, remainder], dim=1) | |
| score, self.score_state[:bsz, :remainder] = score.split([cutoff, remainder], dim=1) | |
| kv = kv.unflatten(1, (-1, ratio)) | |
| score = score.unflatten(1, (-1, ratio)) | |
| kv = (kv * score.softmax(dim=2)).sum(dim=2) | |
| else: # one token per step: fill a slot, and pool only when the group just completed | |
| should_compress = (start_pos + 1) % ratio == 0 | |
| slot = start_pos % ratio | |
| self.kv_state[:bsz, slot] = kv.squeeze(1) | |
| self.score_state[:bsz, slot] = score.squeeze(1) | |
| if should_compress: | |
| kv = (self.kv_state[:bsz] * self.score_state[:bsz].softmax(dim=1)).sum(dim=1, keepdim=True) | |
| if not should_compress: | |
| return None | |
| return self.norm(kv.to(dtype)) | |
| class Indexer(torch.nn.Module): | |
| """Keeps the `index_topk` best compressed positions per query. | |
| A small side attention: fp4 query heads against one shared key per compressed position, scores | |
| rectified then combined by `weights_proj`. With a candidate source this is the second of two | |
| levels; `select_candidate_blocks` is the first. | |
| """ | |
| def __init__(self, args: ModelArgs, layer_id: int): | |
| super().__init__() | |
| # the index keys are derived from the compressor's latent, so only a layer that compresses | |
| # its own KV can produce them; every other indexer reads them from that layer's cache | |
| self.owns_k = layer_id in args.kv_source_layers | |
| self.compress_ratio = args.compress_ratios[layer_id] | |
| self.is_candidate_source = layer_id == args.candidate_source_layer | |
| self.uses_candidates = 0 <= args.candidate_source_layer < layer_id | |
| self.candidate_topk_blocks = args.candidate_topk_blocks | |
| self.candidate_block_size = args.candidate_block_size | |
| self.dim = args.dim | |
| self.n_heads = args.index_n_heads | |
| self.n_local_heads = args.index_n_heads // world_size | |
| self.index_head_dim = args.index_head_dim | |
| self.rope_head_dim = args.rope_head_dim | |
| self.index_topk = args.index_topk | |
| self.q_lora_rank = args.q_lora_rank | |
| self.softmax_scale = self.index_head_dim**-0.5 | |
| self.wq_b = ColumnParallelLinear(self.q_lora_rank, self.n_heads * self.index_head_dim) | |
| self.weights_proj = ColumnParallelLinear(self.dim, self.n_heads, dtype=torch.bfloat16) | |
| self.freqs_cis: torch.Tensor | None = None | |
| if self.owns_k: | |
| self.wk = Linear(args.head_dim, self.index_head_dim, dtype=torch.bfloat16) | |
| self.k_norm = RMSNorm(self.index_head_dim, args.norm_eps) | |
| self.k_cache: torch.Tensor | |
| self.register_buffer( | |
| "k_cache", | |
| torch.zeros(args.max_batch_size, args.max_seq_len // self.compress_ratio, args.index_head_dim), | |
| persistent=False, | |
| ) | |
| def forward(self, x: torch.Tensor, qr: torch.Tensor, latent: torch.Tensor, start_pos: int, offset: int): | |
| """`latent` is this layer's RoPE-free compressed latent, None when this layer does not | |
| compress or when its current group is still incomplete. An index-key owner turns it into | |
| index keys here, which has to happen before Attention overwrites that same storage with | |
| the RoPE'd, quantized values.""" | |
| assert self.freqs_cis is not None | |
| bsz, seqlen, _ = x.size() | |
| ratio, rd, end_pos = self.compress_ratio, self.rope_head_dim, start_pos + seqlen | |
| # latent is None while a group is still filling up, so there is nothing to publish yet | |
| if self.owns_k and latent is not None: | |
| # a latent stands for the first token of its group, so group j takes position j * ratio | |
| freqs = ( | |
| self.freqs_cis[: seqlen - seqlen % ratio : ratio] | |
| if start_pos == 0 | |
| else self.freqs_cis[start_pos + 1 - ratio].unsqueeze(0) | |
| ) | |
| k = self.k_norm(self.wk(latent)) | |
| apply_rotary_emb(k[..., -rd:], freqs) | |
| fp4_act_quant(k, fp4_block_size, True) | |
| self.k_cache[:bsz, start_pos // ratio : start_pos // ratio + k.size(1)] = k | |
| shared_attn.index_k = self.k_cache | |
| q = self.wq_b(qr).unflatten(-1, (self.n_local_heads, self.index_head_dim)) | |
| apply_rotary_emb(q[..., -rd:], self.freqs_cis[start_pos:end_pos]) | |
| fp4_act_quant(q, fp4_block_size, True) | |
| index_k = shared_attn.index_k[:bsz, : end_pos // ratio] | |
| weights = self.weights_proj(x) * (self.softmax_scale * self.n_heads**-0.5) | |
| index_score = torch.einsum("bshd,btd->bsht", q, index_k) | |
| index_score = (index_score.relu_() * weights.unsqueeze(-1)).sum(dim=2) | |
| if world_size > 1: | |
| dist.all_reduce(index_score) | |
| # how many compressed positions each query can see: a block becomes visible once the query | |
| # has passed its last token. One query per decode step, so there it is just a number. | |
| if start_pos == 0: | |
| compress_lens = (torch.arange(1, seqlen + 1, device=x.device) // ratio).unsqueeze(-1) | |
| index_score.masked_fill_(torch.arange(seqlen // ratio, device=x.device) >= compress_lens, -torch.inf) | |
| else: | |
| compress_lens = end_pos // ratio | |
| if self.is_candidate_source: | |
| shared_attn.candidates = select_candidate_blocks( | |
| index_score, compress_lens, self.candidate_topk_blocks, self.candidate_block_size | |
| ) | |
| elif self.uses_candidates: | |
| # level two: score with our own weights, but only inside the source's candidate blocks | |
| index_score = index_score.masked_fill(~shared_attn.candidates, -torch.inf) | |
| # top-k by score, re-sorted into position order; unreachable -> -1, rest shifted by offset | |
| topk = min(self.index_topk, end_pos // ratio) | |
| idxs = index_score.topk(topk, dim=-1, sorted=False).indices.sort(dim=-1).values | |
| return torch.where(idxs < compress_lens, idxs + offset, -1).int() | |
| def select_candidate_blocks( | |
| logits: torch.Tensor, | |
| compress_lens: torch.Tensor | int, | |
| topk_blocks: int, | |
| block_size: int, | |
| ) -> torch.Tensor: | |
| """Level one of the two-level top-k: keep the `topk_blocks` highest-scoring blocks per query. | |
| `logits` is [..., n_positions] with positions the query cannot reach already at -inf, which is | |
| what makes a block score of -inf mean "not reachable yet". `compress_lens` is a plain int during | |
| decode, or broadcasts against logits' leading dims during prefill. Returns a bool mask shaped | |
| like `logits`, so the layers consuming it just mask and never think about blocks again. | |
| """ | |
| width = logits.size(-1) | |
| # score each block by its best position; -inf pads the last one out to block_size | |
| scores = F.pad(logits, (0, -width % block_size), value=-torch.inf) | |
| scores = scores.unflatten(-1, (-1, block_size)).amax(dim=-1) | |
| num_blocks = scores.size(-1) | |
| # the block with this query's newest position is only partly filled, so pin it in: it holds the | |
| # most recent tokens but could otherwise be outscored by an older, full block | |
| last = (compress_lens - 1) // block_size | |
| scores = scores.masked_fill(torch.arange(num_blocks, device=logits.device) == last, torch.inf) | |
| top = scores.topk(min(topk_blocks, num_blocks), dim=-1) | |
| # fewer reachable blocks than topk_blocks means leftover picks came back -inf: drop them | |
| keep = torch.zeros_like(scores, dtype=torch.bool).scatter_(-1, top.indices, top.values > -torch.inf) | |
| return keep.repeat_interleave(block_size, dim=-1)[..., :width] | |
| class Attention(nn.Module): | |
| """Latent attention over two KV sources at once, concatenated into one `sparse_attn` call: a | |
| sliding window of raw KV, plus -- when compress_ratio > 0 -- `index_topk` compressed positions | |
| reaching further back. Q and the output projection are both low-rank, the latter grouped. | |
| compress_ratio > 0 does not mean the layer compresses its own KV: only kv_source_layers do, | |
| the rest read that same cache. | |
| """ | |
| def __init__(self, layer_id: int, args: ModelArgs): | |
| super().__init__() | |
| self.layer_id = layer_id | |
| self.dim = args.dim | |
| self.n_heads = args.n_heads | |
| self.n_local_heads = args.n_heads // world_size | |
| self.q_lora_rank = args.q_lora_rank | |
| self.o_lora_rank = args.o_lora_rank | |
| self.head_dim = args.head_dim | |
| self.rope_head_dim = args.rope_head_dim | |
| self.nope_head_dim = args.head_dim - args.rope_head_dim | |
| self.n_groups = args.o_groups | |
| self.n_local_groups = self.n_groups // world_size | |
| self.window_size = args.window_size | |
| self.compress_ratio = args.compress_ratios[layer_id] | |
| self.eps = args.norm_eps | |
| self.attn_sink = nn.Parameter(torch.empty(self.n_local_heads, dtype=torch.float32)) | |
| self.wq_a = Linear(self.dim, self.q_lora_rank) | |
| self.q_norm = RMSNorm(self.q_lora_rank, self.eps) | |
| self.wq_b = ColumnParallelLinear(self.q_lora_rank, self.n_heads * self.head_dim) | |
| self.wkv = Linear(self.dim, self.head_dim) | |
| self.kv_norm = RMSNorm(self.head_dim, self.eps) | |
| self.wo_a = ColumnParallelLinear( | |
| self.n_heads * self.head_dim // self.n_groups, | |
| self.n_groups * args.o_lora_rank, | |
| dtype=torch.bfloat16, | |
| ) | |
| self.wo_b = RowParallelLinear(self.n_groups * args.o_lora_rank, self.dim) | |
| self.softmax_scale = self.head_dim**-0.5 | |
| is_backbone = layer_id < args.n_layers | |
| self.is_kv_source = is_backbone and layer_id in args.kv_source_layers | |
| self.is_index_source = is_backbone and layer_id in args.index_source_layers | |
| self.compressor: Compressor | None = None | |
| self.indexer: Indexer | None = None | |
| if self.is_kv_source: | |
| self.compressor = Compressor(args, layer_id) | |
| if self.is_index_source: | |
| self.indexer = Indexer(args, layer_id) | |
| self.window_kv_cache: torch.Tensor | |
| self.register_buffer( | |
| "window_kv_cache", | |
| torch.zeros(args.max_batch_size, args.window_size, self.head_dim), | |
| persistent=False, | |
| ) | |
| if self.is_kv_source: | |
| self.compress_kv_cache: torch.Tensor | |
| self.register_buffer( | |
| "compress_kv_cache", | |
| torch.zeros( | |
| args.max_batch_size, | |
| args.max_seq_len // self.compress_ratio, | |
| self.head_dim, | |
| ), | |
| persistent=False, | |
| ) | |
| if self.compress_ratio: | |
| original_seq_len, rope_theta = ( | |
| args.original_seq_len, | |
| args.compress_rope_theta, | |
| ) | |
| else: | |
| # disable YaRN and use base rope_theta in pure sliding-window attention | |
| original_seq_len, rope_theta = 0, args.rope_theta | |
| freqs_cis = precompute_freqs_cis( | |
| self.rope_head_dim, | |
| args.max_seq_len, | |
| original_seq_len, | |
| rope_theta, | |
| args.rope_factor, | |
| args.beta_fast, | |
| args.beta_slow, | |
| ) | |
| self.freqs_cis: torch.Tensor | |
| self.register_buffer("freqs_cis", freqs_cis, persistent=False) | |
| def _window_kv(self, x, freqs_cis, start_pos): | |
| """This layer's sliding-window K and the window positions every query may attend to. The K | |
| stays fp8, quantized over the whole post-RoPE vector, RoPE tail included.""" | |
| bsz, seqlen, _ = x.size() | |
| win = self.window_size | |
| kv = self.kv_norm(self.wkv(x)) | |
| apply_rotary_emb(kv[..., -self.rope_head_dim :], freqs_cis) | |
| act_quant(kv, fp8_block_size, scale_fmt, scale_dtype, True) | |
| if start_pos == 0: # prefill: attend over this chunk, seeding the ring buffer for decode | |
| if seqlen <= win: | |
| self.window_kv_cache[:bsz, :seqlen] = kv | |
| else: | |
| cutoff = seqlen % win | |
| self.window_kv_cache[:bsz, cutoff:win], self.window_kv_cache[:bsz, :cutoff] = kv[:, -win:].split( | |
| [win - cutoff, cutoff], dim=1 | |
| ) | |
| window_kv = kv | |
| else: # decode: one token into the ring buffer, attend over the whole window | |
| self.window_kv_cache[:bsz, start_pos % win] = kv.squeeze(1) | |
| window_kv = self.window_kv_cache[:bsz] | |
| return window_kv, get_window_topk_idxs(win, bsz, seqlen, start_pos) | |
| def _compress_topk_idxs(self, x, qr, latent, start_pos, offset, compress_len): | |
| """Which compressed positions each query attends to. Index sources run their own indexer; | |
| the layers in between reuse the result their source published.""" | |
| if not self.is_index_source: | |
| return shared_attn.topk_idxs | |
| bsz, seqlen, _ = x.size() | |
| if compress_len == 0: | |
| idxs = torch.empty(bsz, seqlen, 0, dtype=torch.int32, device=x.device) | |
| else: | |
| assert self.indexer is not None | |
| if self.indexer.freqs_cis is None: | |
| self.indexer.freqs_cis = self.freqs_cis | |
| idxs = self.indexer(x, qr, latent, start_pos, offset) | |
| shared_attn.topk_idxs = idxs | |
| return idxs | |
| def _compress_kv(self, x, qr, start_pos, offset): | |
| """The shared compressed KV and the compressed positions every query may attend to. This | |
| layer compresses its own KV only when it is a source; otherwise it just reads the cache.""" | |
| bsz, seqlen, _ = x.size() | |
| ratio = self.compress_ratio | |
| compress_len = (start_pos + seqlen) // ratio | |
| latent = None | |
| if self.is_kv_source: | |
| latent = self.compressor(x, start_pos) | |
| shared_attn.compress_kv = self.compress_kv_cache | |
| # the indexer needs the latent before RoPE, so it runs before the cache is written | |
| idxs = self._compress_topk_idxs(x, qr, latent, start_pos, offset, compress_len) | |
| if latent is not None: | |
| # a latent stands for the first token of its group, so group j takes position j * ratio | |
| freqs = ( | |
| self.freqs_cis[: seqlen - seqlen % ratio : ratio] | |
| if start_pos == 0 | |
| else self.freqs_cis[start_pos + 1 - ratio].unsqueeze(0) | |
| ) | |
| apply_rotary_emb(latent[..., -self.rope_head_dim :], freqs) | |
| # Compressed KV uses groups of 16 with E4M3 scales; the indexer uses 32 with E8M0. | |
| fp4_act_quant(latent, 16, True, scale_dtype=torch.float8_e4m3fn) | |
| self.compress_kv_cache[:bsz, start_pos // ratio : start_pos // ratio + latent.size(1)] = latent | |
| # read after the write, so this does not depend on the slice aliasing the cache | |
| return shared_attn.compress_kv[:bsz, :compress_len], idxs | |
| def forward(self, x: torch.Tensor, start_pos: int): | |
| bsz, seqlen, _ = x.size() | |
| freqs_cis = self.freqs_cis[start_pos : start_pos + seqlen] | |
| rd = self.rope_head_dim | |
| qr = self.q_norm(self.wq_a(x)) | |
| q = self.wq_b(qr).unflatten(-1, (self.n_local_heads, self.head_dim)) | |
| apply_rotary_emb(q[..., -rd:], freqs_cis) | |
| kv, topk_idxs = self._window_kv(x, freqs_cis, start_pos) | |
| if self.compress_ratio: | |
| compress_kv, compress_idxs = self._compress_kv(x, qr, start_pos, kv.size(1)) | |
| kv = torch.cat([kv, compress_kv], dim=1) | |
| topk_idxs = torch.cat([topk_idxs, compress_idxs], dim=-1) | |
| o = sparse_attn(q, kv, self.attn_sink, topk_idxs, self.softmax_scale) | |
| apply_rotary_emb(o[..., -rd:], freqs_cis, True) | |
| # wo_a is block-diagonal over groups (each projects only its own heads), hence einsum not | |
| # Linear. convert.py dequantizes it to bf16; an fp8 grouped GEMM would halve the memory. | |
| o = o.view(bsz, seqlen, self.n_local_groups, -1) | |
| wo_a = self.wo_a.weight.view(self.n_local_groups, self.o_lora_rank, -1) | |
| o = torch.einsum("bsgd,grd->bsgr", o, wo_a) | |
| x = self.wo_b(o.flatten(2)) | |
| return x | |
| class Gate(nn.Module): | |
| """MoE gating. The correction bias steers expert selection only; the routing weights come from the | |
| unbiased scores. Image-span tokens use a separate bias (training `noaux_tc_for_vl`).""" | |
| def __init__(self, layer_id: int, args: ModelArgs): | |
| super().__init__() | |
| n_routed_experts, n_activated_experts = args.get_moe_config(layer_id) | |
| self.dim = args.dim | |
| self.topk = n_activated_experts | |
| self.score_func = args.score_func | |
| self.gate_temp = args.gate_temp | |
| self.norm_topk_prob = args.norm_topk_prob | |
| self.route_scale = args.route_scale | |
| self.weight = nn.Parameter(torch.empty(n_routed_experts, args.dim)) | |
| self.bias = nn.Parameter(torch.empty(n_routed_experts, dtype=torch.float32)) | |
| self.bias_vl = nn.Parameter(torch.empty(n_routed_experts, dtype=torch.float32)) if args.vision_enabled else None | |
| def forward(self, x: torch.Tensor, image_mask: torch.Tensor | None = None) -> tuple[torch.Tensor, torch.Tensor]: | |
| """x: [n, dim]; image_mask: [n] bool, True for tokens inside an image span.""" | |
| scores = linear(x.float(), self.weight.float()) / self.gate_temp | |
| if self.score_func == "softmax": | |
| scores = scores.softmax(dim=-1) | |
| elif self.score_func == "sigmoid": | |
| scores = scores.sigmoid() | |
| else: | |
| scores = F.softplus(scores).sqrt() | |
| bias = self.bias | |
| if image_mask is not None and self.bias_vl is not None: | |
| bias = torch.where(image_mask.unsqueeze(-1), self.bias_vl, bias) | |
| # the bias picks experts but does not scale them: weights come from the raw scores | |
| indices = (scores + bias).topk(self.topk, dim=-1)[1] | |
| weights = scores.gather(1, indices) | |
| if self.norm_topk_prob and self.topk > 1: | |
| weights /= weights.sum(dim=-1, keepdim=True) + 1e-20 # not norm_eps, matches training | |
| weights *= self.route_scale | |
| return weights, indices | |
| class Expert(nn.Module): | |
| """One SwiGLU FFN. The clamps come straight from training, where they keep fp8/fp4 activations in | |
| range: the up branch is clamped on both sides, the gate branch only from above.""" | |
| def __init__(self, dim: int, inter_dim: int, dtype=None, swiglu_limit=0.0): | |
| super().__init__() | |
| self.w1 = Linear(dim, inter_dim, dtype=dtype) | |
| self.w2 = Linear(inter_dim, dim, dtype=dtype) | |
| self.w3 = Linear(dim, inter_dim, dtype=dtype) | |
| self.swiglu_limit = swiglu_limit | |
| def forward(self, x: torch.Tensor, weights: torch.Tensor | None = None) -> torch.Tensor: | |
| dtype = x.dtype | |
| gate = self.w1(x).float() | |
| up = self.w3(x).float() | |
| if self.swiglu_limit > 0: | |
| up = torch.clamp(up, min=-self.swiglu_limit, max=self.swiglu_limit) | |
| gate = torch.clamp(gate, max=self.swiglu_limit) | |
| x = F.silu(gate) * up | |
| if weights is not None: | |
| x = weights * x | |
| return self.w2(x.to(dtype)) | |
| class MoE(nn.Module): | |
| """Top-k routed experts plus one shared expert every token goes through. Experts are split | |
| across ranks, so `self.experts` is None for those another rank owns.""" | |
| def __init__(self, layer_id: int, args: ModelArgs): | |
| super().__init__() | |
| n_routed_experts, n_activated_experts = args.get_moe_config(layer_id) | |
| self.layer_id = layer_id | |
| self.dim = args.dim | |
| assert n_routed_experts % world_size == 0, ( | |
| f"Number of experts must be divisible by world size (world_size={world_size})" | |
| ) | |
| self.n_routed_experts = n_routed_experts | |
| self.n_local_experts = n_routed_experts // world_size | |
| self.n_activated_experts = n_activated_experts | |
| self.experts_start_idx = rank * self.n_local_experts | |
| self.experts_end_idx = self.experts_start_idx + self.n_local_experts | |
| self.gate = Gate(layer_id, args) | |
| expert_dtype = torch.float4_e2m1fn_x2 if args.expert_dtype == "fp4" else None | |
| self.experts = nn.ModuleList( | |
| [ | |
| Expert( | |
| args.dim, | |
| args.moe_inter_dim, | |
| dtype=expert_dtype, | |
| swiglu_limit=args.swiglu_limit, | |
| ) | |
| if self.experts_start_idx <= i < self.experts_end_idx | |
| else None | |
| for i in range(self.n_routed_experts) | |
| ] | |
| ) | |
| assert args.n_shared_experts == 1 | |
| self.shared_experts = Expert(args.dim, args.moe_inter_dim, swiglu_limit=args.swiglu_limit) | |
| def forward(self, x: torch.Tensor, image_mask: torch.Tensor | None = None) -> torch.Tensor: | |
| shape = x.size() | |
| x = x.view(-1, self.dim) | |
| weights, indices = self.gate(x, None if image_mask is None else image_mask.flatten()) | |
| y = torch.zeros_like(x, dtype=torch.float32) | |
| counts = torch.bincount(indices.flatten(), minlength=self.n_routed_experts).tolist() | |
| for i in range(self.experts_start_idx, self.experts_end_idx): | |
| if counts[i] == 0: | |
| continue | |
| expert = self.experts[i] | |
| idx, top = torch.where(indices == i) | |
| y[idx] += expert(x[idx], weights[idx, top, None]) | |
| if world_size > 1: | |
| dist.all_reduce(y) | |
| y += self.shared_experts(x) | |
| return y.type_as(x).view(shape) | |
| class Block(nn.Module): | |
| """A block whose residual stream is `hc_mult` parallel copies (Hyper-Connections). | |
| Attention and FFN each sit between `hc_pre` (collapse the copies into one sublayer input) and | |
| `hc_post` (expand back out, mixing the residual in through `comb`). `hc_mixes` derives all three | |
| coefficient sets from the stream itself, `comb` made doubly stochastic by Sinkhorn. | |
| The coefficients a sublayer computes are used by the *next* one -- see `forward`. | |
| """ | |
| attention_cls = Attention | |
| def __init__( | |
| self, | |
| layer_id: int, | |
| args: ModelArgs, | |
| engram_layout: EngramLayout | None = None, | |
| ): | |
| super().__init__() | |
| self.layer_id = layer_id | |
| self.norm_eps = args.norm_eps | |
| self.attn = self.attention_cls(layer_id, args) | |
| self.ffn = MoE(layer_id, args) | |
| self.engram = None | |
| if engram_layout is not None and layer_id in engram_layout.layer_ids: | |
| self.engram = Engram(args, layer_id, engram_layout) | |
| self.attn_norm = RMSNorm(args.dim, self.norm_eps) | |
| self.ffn_norm = RMSNorm(args.dim, self.norm_eps) | |
| self.hc_mult = hc_mult = args.hc_mult | |
| self.hc_sinkhorn_iters = args.hc_sinkhorn_iters | |
| self.hc_eps = args.hc_eps | |
| mix_hc = (2 + hc_mult) * hc_mult | |
| hc_dim = hc_mult * args.dim | |
| with set_dtype(torch.float32): | |
| self.hc_attn_fn = nn.Parameter(torch.empty(mix_hc, hc_dim)) | |
| self.hc_ffn_fn = nn.Parameter(torch.empty(mix_hc, hc_dim)) | |
| self.hc_attn_base = nn.Parameter(torch.empty(mix_hc)) | |
| self.hc_ffn_base = nn.Parameter(torch.empty(mix_hc)) | |
| self.hc_attn_scale = nn.Parameter(torch.empty(3)) | |
| self.hc_ffn_scale = nn.Parameter(torch.empty(3)) | |
| def hc_mixes(self, x: torch.Tensor, hc_fn: torch.Tensor, hc_scale: torch.Tensor, hc_base: torch.Tensor): | |
| """x: [b,s,hc,d], hc_fn: [mix_hc, hc*d], hc_scale: [3], hc_base: [mix_hc]. Returns the | |
| pre / post / comb coefficients, split out of one projection of the flattened stream.""" | |
| # normalized over the whole flattened hc*d stream, one statistic per token | |
| x = x.flatten(2).float() | |
| rsqrt = torch.rsqrt(x.square().mean(-1, keepdim=True) + self.norm_eps) | |
| mixes = F.linear(x, hc_fn) * rsqrt | |
| return hc_split_sinkhorn(mixes, hc_scale, hc_base, self.hc_mult, self.hc_sinkhorn_iters, self.hc_eps) | |
| def hc_pre(self, x: torch.Tensor, pre_mix: torch.Tensor): | |
| """Collapse the hc copies into one, weighted by pre_mix. [b,s,hc,d] x [b,s,hc] -> [b,s,d]""" | |
| y = torch.sum(pre_mix.unsqueeze(-1) * x.float(), dim=2) | |
| return y.to(x.dtype) | |
| def hc_post(self, x: torch.Tensor, residual: torch.Tensor, post: torch.Tensor, comb: torch.Tensor): | |
| """Expand the sublayer output back to hc copies and mix the residual in through `comb`. | |
| x: [b,s,d], residual: [b,s,hc,d], post: [b,s,hc], comb: [b,s,hc,hc] -> [b,s,hc,d]""" | |
| y = post.unsqueeze(-1) * x.unsqueeze(-2) + torch.sum(comb.unsqueeze(-1) * residual.unsqueeze(-2), dim=2) | |
| return y.type_as(x) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| start_pos: int, | |
| pre_mix: torch.Tensor, | |
| image_mask: torch.Tensor | None, | |
| *attn_args, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """`pre_mix` collapses the hc_mult copies down to one input for this block's attention. Each | |
| sub-block's own `hc_mixes` produces the mix for the *next* one, so attention uses what the | |
| previous layer's FFN produced and the FFN uses what this attention produced. | |
| image_mask: [b, s] bool, True inside image spans (selects the VL routing bias).""" | |
| residual = x | |
| attn_pre, attn_post, attn_comb = self.hc_mixes(x, self.hc_attn_fn, self.hc_attn_scale, self.hc_attn_base) | |
| x = self.hc_pre(x, pre_mix) | |
| x = self.attn_norm(x) | |
| x = self.attn(x, start_pos, *attn_args) | |
| x = self.hc_post(x, residual, attn_post, attn_comb) | |
| residual = x | |
| ffn_pre, ffn_post, ffn_comb = self.hc_mixes(x, self.hc_ffn_fn, self.hc_ffn_scale, self.hc_ffn_base) | |
| x = self.hc_pre(x, attn_pre) | |
| x = self.ffn_norm(x) | |
| x = self.ffn(x, image_mask) | |
| x = self.hc_post(x, residual, ffn_post, ffn_comb) | |
| return x, ffn_pre | |
| class ParallelHead(nn.Module): | |
| def __init__(self, vocab_size: int, dim: int, norm_eps: float = 1e-6, hc_eps: float = 1e-6): | |
| super().__init__() | |
| self.vocab_size = vocab_size | |
| self.dim = dim | |
| self.norm_eps = norm_eps | |
| self.hc_eps = hc_eps | |
| self.part_vocab_size = vocab_size // world_size | |
| # bf16 in the checkpoint, kept as fp32 here so the logits come out in fp32 directly | |
| self.weight = nn.Parameter(torch.empty(self.part_vocab_size, self.dim, dtype=torch.float32)) | |
| def forward(self, x: torch.Tensor, full_logits=False): | |
| """x: [b, s, d]. Generation only needs the last position, so that is the default.""" | |
| if not full_logits: | |
| x = x[:, -1] | |
| logits = F.linear(x.float(), self.weight) | |
| if world_size > 1: | |
| all_logits = [torch.empty_like(logits) for _ in range(world_size)] | |
| dist.all_gather(all_logits, logits) | |
| logits = torch.cat(all_logits, dim=-1) | |
| return logits | |
| def get_dspark_topk_idxs(window_size: int, bsz: int, block_size: int, start_pos: int): | |
| assert start_pos > 0 | |
| matrix = torch.cat( | |
| [ | |
| torch.arange(min(window_size, start_pos + 1)), | |
| window_size + torch.arange(block_size), | |
| ] | |
| ) | |
| return matrix.int().view(1, 1, -1).expand(bsz, block_size, -1).contiguous() | |
| class DSparkAttention(Attention): | |
| def forward(self, x: torch.Tensor, start_pos: int, main_x: torch.Tensor): | |
| assert self.compress_ratio == 0 | |
| bsz, seqlen, _ = main_x.size() | |
| win = self.window_size | |
| rd = self.rope_head_dim | |
| main_freqs_cis = self.freqs_cis[start_pos : start_pos + seqlen] | |
| main_kv = self.kv_norm(self.wkv(main_x)) | |
| apply_rotary_emb(main_kv[..., -rd:], main_freqs_cis) | |
| act_quant(main_kv, fp8_block_size, scale_fmt, scale_dtype, True) | |
| if start_pos == 0: | |
| if seqlen <= win: | |
| self.window_kv_cache[:bsz, :seqlen] = main_kv | |
| else: | |
| cutoff = seqlen % win | |
| self.window_kv_cache[:bsz, cutoff:win], self.window_kv_cache[:bsz, :cutoff] = main_kv[:, -win:].split( | |
| [win - cutoff, cutoff], dim=1 | |
| ) | |
| return x | |
| bsz, block_size, _ = x.size() | |
| freqs_cis = self.freqs_cis[start_pos + seqlen : start_pos + seqlen + block_size] | |
| qr = self.q_norm(self.wq_a(x)) | |
| q = self.wq_b(qr).unflatten(-1, (self.n_local_heads, self.head_dim)) | |
| apply_rotary_emb(q[..., -rd:], freqs_cis) | |
| kv = self.kv_norm(self.wkv(x)) | |
| apply_rotary_emb(kv[..., -rd:], freqs_cis) | |
| act_quant(kv, fp8_block_size, scale_fmt, scale_dtype, True) | |
| topk_idxs = get_dspark_topk_idxs(win, bsz, block_size, start_pos) | |
| self.window_kv_cache[:bsz, start_pos % win] = main_kv.squeeze(1) | |
| kv = torch.cat([self.window_kv_cache[:bsz], kv], dim=1) | |
| o = sparse_attn(q, kv, self.attn_sink, topk_idxs, self.softmax_scale) | |
| apply_rotary_emb(o[..., -rd:], freqs_cis, True) | |
| o = o.view(bsz, block_size, self.n_local_groups, -1) | |
| wo_a = self.wo_a.weight.view(self.n_local_groups, self.o_lora_rank, -1) | |
| o = torch.einsum("bsgd,grd->bsgr", o, wo_a) | |
| x = self.wo_b(o.flatten(2)) | |
| return x | |
| class DSparkMarkovHead(nn.Module): | |
| def __init__(self, vocab_size: int, dspark_markov_rank: int): | |
| super().__init__() | |
| self.embed = ParallelEmbedding(vocab_size, dspark_markov_rank) | |
| self.head = ParallelHead(vocab_size, dspark_markov_rank) | |
| def forward(self, token_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| embed = self.embed(token_ids) | |
| logits = self.head(embed, full_logits=True) | |
| return logits, embed | |
| class DSparkConfidenceHead(nn.Module): | |
| def __init__(self, input_dim: int): | |
| super().__init__() | |
| # proj in the checkpoint is stored in bf16, while the parameter here is stored in fp32 for fp32 confidence score. | |
| self.proj = Linear(input_dim, 1, dtype=torch.float32) | |
| def forward(self, hidden: torch.Tensor, markov_embed: torch.Tensor): | |
| hidden = torch.cat([hidden, markov_embed], dim=-1) | |
| return self.proj(hidden.float()).squeeze(-1) | |
| class DSparkBlock(Block): | |
| """DSpark stage stored under the mtp.* checkpoint namespace.""" | |
| attention_cls = DSparkAttention | |
| def __init__(self, layer_id: int, args: ModelArgs): | |
| super().__init__(layer_id, args) | |
| stage_id = layer_id - args.n_layers | |
| self.block_size = args.dspark_block_size | |
| self.noise_token_id = args.dspark_noise_token_id | |
| self.temperature = args.temperature | |
| if stage_id == 0: | |
| assert len(args.dspark_target_layer_ids) > 0, "DSpark needs target layers" | |
| self.main_proj = Linear(args.dim * len(args.dspark_target_layer_ids), args.dim) | |
| self.main_norm = RMSNorm(args.dim, args.norm_eps) | |
| if stage_id == args.n_mtp_layers - 1: | |
| self.norm = RMSNorm(args.dim, args.norm_eps) | |
| self.markov_head = DSparkMarkovHead(args.vocab_size, args.dspark_markov_rank) | |
| self.confidence_head = DSparkConfidenceHead(args.dim + args.dspark_markov_rank) | |
| self.embed: ParallelEmbedding | None = None | |
| self.head: ParallelHead | None = None | |
| def forward(self, x: torch.Tensor, start_pos: int, pre_mix: torch.Tensor, main_x: torch.Tensor): | |
| if start_pos == 0: | |
| self.attn(x, start_pos, main_x) # prefill only seeds the window KV cache | |
| return x, pre_mix | |
| return super().forward(x, start_pos, pre_mix, None, main_x) # drafts are text: no VL bias | |
| def forward_embed(self, main_hidden: torch.Tensor, input_ids: torch.Tensor): | |
| assert self.embed is not None | |
| main_x = self.main_norm(self.main_proj(main_hidden)) | |
| draft_input_ids = input_ids.new_full([input_ids.size(0), self.block_size], self.noise_token_id) | |
| draft_input_ids[:, 0] = input_ids | |
| x = self.embed(draft_input_ids) | |
| x = x.unsqueeze(2).repeat(1, 1, self.hc_mult, 1) | |
| return x, main_x | |
| def forward_head( | |
| self, | |
| x: torch.Tensor, | |
| pre_mix: torch.Tensor, | |
| input_ids: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| assert self.head is not None | |
| x = self.hc_pre(x, pre_mix) | |
| logits = self.head(self.norm(x), full_logits=True) | |
| output_ids = input_ids.new_empty(input_ids.size(0), self.block_size + 1) | |
| output_ids[:, 0] = input_ids | |
| markov_embeds = [] | |
| for i in range(self.block_size): | |
| logits_bias, markov_embed = self.markov_head(output_ids[:, i]) | |
| logits[:, i].add_(logits_bias) | |
| markov_embeds.append(markov_embed) | |
| output_ids[:, i + 1] = sample(logits[:, i], self.temperature) | |
| markov_embed = torch.stack(markov_embeds, dim=1) | |
| confidence = self.confidence_head(x, markov_embed) | |
| return output_ids, logits, confidence | |
| def make_identity_pre_mix(x: torch.Tensor, hc_mult: int) -> torch.Tensor: | |
| """initial one-hot mix""" | |
| pre_mix = x.new_zeros(x.size(0), x.size(1), hc_mult, dtype=torch.float32) | |
| pre_mix[:, :, 0] = 1.0 | |
| return pre_mix | |
| class SharedAttentionRuntime: | |
| """What attention layers hand down the stack instead of recomputing. Layers run in order and | |
| every source writes before its consumers read, so one slot each is enough and nothing needs | |
| resetting between forwards. Sources: compress_kv and index_k from kv_source_layers, topk_idxs | |
| from index_source_layers, candidates from candidate_source_layer.""" | |
| def __init__(self): | |
| self.compress_kv: torch.Tensor | None = None | |
| self.index_k: torch.Tensor | None = None | |
| self.topk_idxs: torch.Tensor | None = None | |
| self.candidates: torch.Tensor | None = None | |
| # Only ever one model per process, same as world_size / rank / default_dtype above. | |
| shared_attn = SharedAttentionRuntime() | |
| class Transformer(nn.Module): | |
| """DeepSeek-V4.1: embed -> expand to hc_mult copies -> blocks -> collapse -> logits. Building | |
| this sets the globals at the top of the file. The tokenizer only feeds the engram token map.""" | |
| def __init__(self, args: ModelArgs, tokenizer=None): | |
| global world_size, rank, default_dtype | |
| world_size = dist.get_world_size() if dist.is_initialized() else 1 | |
| rank = dist.get_rank() if dist.is_initialized() else 0 | |
| default_dtype = torch.float8_e4m3fn if args.dtype == "fp8" else torch.bfloat16 | |
| super().__init__() | |
| self.max_seq_len = args.max_seq_len | |
| self.temperature = args.temperature | |
| self.norm_eps = args.norm_eps | |
| self.hc_eps = args.hc_eps | |
| self.engram_layout = EngramLayout.from_args(args) | |
| self.engram_hash = ( | |
| NgramHashState(args, self.engram_layout, tokenizer) if self.engram_layout is not None else None | |
| ) | |
| self.embed = ParallelEmbedding(args.vocab_size, args.dim) | |
| self.layers = torch.nn.ModuleList() | |
| for layer_id in range(args.n_layers): | |
| self.layers.append(Block(layer_id, args, self.engram_layout)) | |
| self.norm = RMSNorm(args.dim, self.norm_eps) | |
| self.head = ParallelHead(args.vocab_size, args.dim, self.norm_eps, self.hc_eps) | |
| self.mtp = torch.nn.ModuleList() | |
| self.target_layer_ids = args.dspark_target_layer_ids | |
| if args.dspark_block_size: | |
| for layer_id in range(args.n_mtp_layers): | |
| self.mtp.append(DSparkBlock(args.n_layers + layer_id, args)) | |
| self.mtp[-1].embed = self.embed | |
| self.mtp[-1].head = self.head | |
| self.hc_mult = args.hc_mult | |
| self.vision = None | |
| if args.vision_enabled: | |
| self.vision = ViT(args) | |
| self.aligner = Aligner(args) | |
| # learned embeddings for the image span delimiters | |
| self.image_start = nn.Parameter(torch.empty(args.dim)) | |
| self.image_end = nn.Parameter(torch.empty(args.dim)) | |
| self.image_newline = nn.Parameter(torch.empty(args.dim)) | |
| def encode_image(self, patches: torch.Tensor, n_vit_h: int, n_vit_w: int) -> torch.Tensor: | |
| return self.aligner(self.vision(patches, n_vit_h, n_vit_w), n_vit_h, n_vit_w) | |
| def merge_image_embeddings(self, images, h: torch.Tensor): | |
| """Overwrite each image's token span in h with its ViT/aligner features. The IMAGE slots take | |
| the aligner rows in row-major order; the span delimiters take learned embeddings.""" | |
| for i, sample in enumerate(images): | |
| for img in sample or (): | |
| types = img.types.to(h.device) | |
| span = h[i, img.start : img.start + types.numel()] | |
| span[types == IMAGE_START] = self.image_start.to(h.dtype) | |
| span[types == IMAGE_END] = self.image_end.to(h.dtype) | |
| span[types == IMAGE_NEW_LINE] = self.image_newline.to(h.dtype) | |
| embeds = self.encode_image(img.patches.to(h.device), img.n_vit_h, img.n_vit_w) | |
| span[types == IMAGE] = embeds.to(h.dtype) | |
| def forward( | |
| self, input_ids: torch.Tensor, start_pos: int = 0, images=None, token_types: torch.Tensor | None = None | |
| ): | |
| """input_ids: [b, s], every entry a real token id -- generate.py only ever passes positions it | |
| has already filled, so the padding it uses internally never reaches here. token_types / | |
| images carry the VL inputs built by image_processor.prepare_vl_inputs; image spans must lie | |
| inside the first (start_pos 0) chunk.""" | |
| image_mask = None if token_types is None else token_types >= 0 # TEXT is -1 | |
| # image tokens take no part in an n-gram and get no engram contribution; text-only needs no mask | |
| engram_mask = None if image_mask is None else ~image_mask | |
| engram_hashes = self.engram_hash(input_ids, start_pos, engram_mask) if self.engram_hash is not None else None | |
| h = self.embed(input_ids) | |
| if images is not None: | |
| assert start_pos == 0, "image spans must be prefilled in a single chunk" | |
| self.merge_image_embeddings(images, h) | |
| # Expand to hc_mult copies for Hyper-Connections | |
| h = h.unsqueeze(2).repeat(1, 1, self.hc_mult, 1) | |
| main_hiddens = [] | |
| pre_mix = make_identity_pre_mix(h, self.hc_mult) | |
| for i, layer in enumerate(self.layers): | |
| if layer.engram is not None: | |
| h = layer.engram(h, engram_hashes[:, :, layer.engram.layer_hash_index, :], engram_mask) | |
| # the MTP head reads the attention input of its target layers, not their output | |
| if i in self.target_layer_ids: | |
| main_hiddens.append(h.mean(dim=2)) | |
| h, pre_mix = layer(h, start_pos, pre_mix, image_mask) | |
| h = layer.hc_pre(h, pre_mix) | |
| logits = self.head(self.norm(h)) | |
| output_ids = sample(logits, self.temperature) | |
| main_hidden = torch.cat(main_hiddens, dim=-1) if main_hiddens else None | |
| return output_ids, logits, main_hidden | |
| def forward_spec(self, input_ids: torch.Tensor, main_hidden: torch.Tensor, start_pos: int = 0): | |
| h, main_x = self.mtp[0].forward_embed(main_hidden, input_ids) | |
| pre_mix = make_identity_pre_mix(h, self.hc_mult) | |
| for layer in self.mtp: | |
| h, pre_mix = layer(h, start_pos, pre_mix, main_x) | |
| if start_pos == 0: | |
| return None | |
| return self.mtp[-1].forward_head(h, pre_mix, input_ids) | |
| def sample(logits, temperature: float = 1.0): | |
| """Gumbel-max trick: equivalent to multinomial sampling but faster on GPU, | |
| since it avoids the GPU-to-CPU sync in torch.multinomial.""" | |
| if temperature == 0: | |
| return logits.argmax(dim=-1) | |
| logits = logits / max(temperature, 1e-5) | |
| probs = torch.softmax(logits, dim=-1, dtype=torch.float32) | |
| return probs.div_(torch.empty_like(probs).exponential_(1)).argmax(dim=-1) | |
| if __name__ == "__main__": | |
| torch.set_default_dtype(torch.bfloat16) | |
| torch.set_default_device("cuda") | |
| torch.manual_seed(0) | |
| args = ModelArgs(dspark_block_size=6, dspark_target_layer_ids=(3, 4)) | |
| x = torch.randint(0, args.vocab_size, (2, 150)) | |
| model = Transformer(args) | |
| output_ids, logits, main_hidden = model(x[:, :128]) | |
| model.forward_spec(output_ids, main_hidden) | |
| for i in range(128, 150): | |
| output_ids, logits, main_hidden = model(x[:, i : i + 1], i) | |
| result = model.forward_spec(output_ids, main_hidden, i) | |
| assert result is not None | |
| output_ids, logits, confidence = result | |