Transformers
Safetensors
English
mla
deepseek-moe
mtp
custom-code
tinystories
from-scratch
Eval Results (legacy)
Instructions to use nowordsxiaomu/DeepSeek-Flash-Mini with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use nowordsxiaomu/DeepSeek-Flash-Mini with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("nowordsxiaomu/DeepSeek-Flash-Mini", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 10,054 Bytes
5e6d9f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | """模型主体:Transformer Block、MTP 多 token 预测头、完整模型。"""
import math
from typing import Optional, Tuple, List
import torch
import torch.nn as nn
import torch.nn.functional as F
from .layers import RMSNorm, build_rope_cache
from .mla import MLA
from .moe import MoE, DenseFFN
def _shift_left(x: torch.Tensor, k: int, fill: int):
"""y[:, i] = x[:, i+k],越界位置填 fill。"""
if k == 0:
return x.clamp_min(0) if fill == 0 else x
y = torch.full_like(x, fill)
if k < x.size(1):
y[:, :-k] = x[:, k:]
if fill == 0:
y = y.clamp_min(0)
return y
class Block(nn.Module):
"""Pre-Norm 残差块:x + MLA(norm(x)) → x + FFN(norm(x))。"""
def __init__(self, cfg, layer_id: int):
super().__init__()
self.attn_norm = RMSNorm(cfg.dim, cfg.norm_eps)
self.attn = MLA(cfg)
self.ffn_norm = RMSNorm(cfg.dim, cfg.norm_eps)
# V3 的做法:靠前的层用稠密 FFN,后面的层才换成 MoE,训练早期更稳
self.is_moe = layer_id >= cfg.n_dense_layers
self.ffn = MoE(cfg) if self.is_moe else DenseFFN(cfg)
def forward(self, x, cos, sin, start_pos=0, mask=None):
x = x + self.attn(self.attn_norm(x), cos, sin, start_pos, mask)
h, aux = self.ffn(self.ffn_norm(x))
return x + h, aux
class MTPModule(nn.Module):
"""Multi-Token Prediction 模块(DeepSeek-V3)。
普通语言模型每个位置只预测下一个 token,训练信号比较稀疏。
MTP 再挂一个轻量模块,让位置 i 同时预测 i+2:
h'_i = Block( W · [ RMSNorm(h_i) ; RMSNorm(Emb(t_{i+1})) ] )
p(t_{i+2}) = Head(RMSNorm(h'_i))
嵌入层和输出头与主干共享,所以额外开销只有一层 Block。
好处有二:训练时数据效率更高(模型被迫规划更远);
推理时这个头天然就是个 draft model,可以做自投机解码,实测约 1.8x 加速。
"""
def __init__(self, cfg, layer_id: int):
super().__init__()
self.h_norm = RMSNorm(cfg.dim, cfg.norm_eps)
self.e_norm = RMSNorm(cfg.dim, cfg.norm_eps)
self.proj = nn.Linear(2 * cfg.dim, cfg.dim, bias=False)
self.block = Block(cfg, layer_id)
def forward(self, h, emb, cos, sin, start_pos=0, mask=None):
z = self.proj(torch.cat([self.h_norm(h), self.e_norm(emb)], dim=-1))
return self.block(z, cos, sin, start_pos, mask)
class DeepSeekFlashMini(nn.Module):
def __init__(self, cfg):
super().__init__()
self.cfg = cfg
self.embed = nn.Embedding(cfg.vocab_size, cfg.dim)
self.layers = nn.ModuleList([Block(cfg, i) for i in range(cfg.n_layers)])
self.norm = RMSNorm(cfg.dim, cfg.norm_eps)
self.head = nn.Linear(cfg.dim, cfg.vocab_size, bias=False)
if cfg.tie_embeddings:
self.head.weight = self.embed.weight
self.mtp = nn.ModuleList([MTPModule(cfg, cfg.n_layers + i) for i in range(cfg.n_mtp)])
self.mtp_norm = RMSNorm(cfg.dim, cfg.norm_eps) if cfg.n_mtp > 0 else None
cos, sin = build_rope_cache(cfg.qk_rope_head_dim, cfg.max_seq_len,
cfg.rope_theta, cfg.rope_scaling)
self.register_buffer("rope_cos", cos, persistent=False)
self.register_buffer("rope_sin", sin, persistent=False)
self.apply(self._init_weights)
# 残差分支的输出投影按深度缩放初始化,防止深层激活方差爆掉
for name, p in self.named_parameters():
if name.endswith("wo.weight") or name.endswith("w_down.weight"):
nn.init.normal_(p, mean=0.0, std=0.02 / math.sqrt(2 * cfg.n_layers))
self._cache_ready = False
@staticmethod
def _init_weights(m):
if isinstance(m, nn.Linear):
nn.init.normal_(m.weight, mean=0.0, std=0.02)
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Embedding):
nn.init.normal_(m.weight, mean=0.0, std=0.02)
# ================= 前向 =================
def _rope(self, start_pos: int, T: int):
if start_pos + T > self.cfg.max_seq_len:
raise ValueError(
f"位置 {start_pos + T} 超出上下文上限 max_seq_len={self.cfg.max_seq_len}。"
f"请加大配置里的 max_seq_len(并考虑设置 rope_scaling 做外推)。")
return self.rope_cos[start_pos:start_pos + T], self.rope_sin[start_pos:start_pos + T]
def _mask(self, T: int, start_pos: int, device):
"""带 cache 的增量解码时,为多 token 输入构造对齐的因果 mask。"""
if T == 1:
return None
if not self._cache_ready:
return None # 训练路径:交给 SDPA 的 is_causal
S = start_pos + T
return torch.ones(T, S, dtype=torch.bool, device=device).tril(S - T)
def forward_trunk(self, tokens: torch.Tensor, start_pos: int = 0):
"""主干前向,返回 (最后一层隐状态 h, logits, 路由辅助损失)。"""
B, T = tokens.shape
cos, sin = self._rope(start_pos, T)
mask = self._mask(T, start_pos, tokens.device)
h = self.embed(tokens)
aux_total = h.new_zeros(())
for layer in self.layers:
h, aux = layer(h, cos, sin, start_pos, mask)
aux_total = aux_total + aux
logits = self.head(self.norm(h))
return h, logits, aux_total
def mtp_forward(self, h: torch.Tensor, next_tokens: torch.Tensor,
start_pos: int = 0, depth: int = 0):
"""MTP 第 depth 层:给定主干隐状态和「下一个 token」,预测再下一个。"""
B, T = next_tokens.shape
cos, sin = self._rope(start_pos, T)
mask = self._mask(T, start_pos, next_tokens.device)
emb = self.embed(next_tokens)
h2, aux = self.mtp[depth](h, emb, cos, sin, start_pos, mask)
logits = self.head(self.mtp_norm(h2))
return h2, logits, aux
def forward(self, tokens: torch.Tensor, targets: Optional[torch.Tensor] = None,
start_pos: int = 0):
"""训练入口。targets[i] 应为 tokens[i+1](由 dataset 准备好)。"""
h, logits, aux = self.forward_trunk(tokens, start_pos)
if targets is None:
return {"logits": logits}
loss_main = F.cross_entropy(logits.reshape(-1, logits.size(-1)),
targets.reshape(-1), ignore_index=-100)
out = {"logits": logits, "loss_main": loss_main, "aux_loss": aux}
# ---- MTP:第 d 个模块用 t_{i+1+d} 当输入,监督目标是 t_{i+2+d} ----
loss_mtp = logits.new_zeros(())
if self.cfg.n_mtp > 0:
cur_h = h
for d in range(self.cfg.n_mtp):
inp = _shift_left(targets, d, fill=0) # t_{i+1+d}
tgt = _shift_left(targets, d + 1, fill=-100) # t_{i+2+d}
cur_h, mtp_logits, aux_d = self.mtp_forward(cur_h, inp, start_pos, d)
aux = aux + aux_d
loss_mtp = loss_mtp + F.cross_entropy(
mtp_logits.reshape(-1, mtp_logits.size(-1)),
tgt.reshape(-1), ignore_index=-100)
loss_mtp = loss_mtp / self.cfg.n_mtp
out["loss_mtp"] = loss_mtp
out["aux_loss"] = aux
out["loss"] = loss_main + self.cfg.mtp_loss_weight * loss_mtp + aux
return out
# ================= KV cache 管理 =================
def setup_cache(self, max_batch: int, max_seq_len: int, device=None, dtype=None):
device = device or self.embed.weight.device
dtype = dtype or self.embed.weight.dtype
for m in self.modules():
if isinstance(m, MLA):
m.setup_cache(max_batch, max_seq_len, device, dtype)
self._cache_ready = True
def clear_cache(self):
for m in self.modules():
if isinstance(m, MLA):
m.clear_cache()
self._cache_ready = False
def set_attn_impl(self, impl: str):
for m in self.modules():
if isinstance(m, MLA):
m.attn_impl = impl
# ================= 统计 =================
def num_params(self) -> dict:
cfg = self.cfg
total = sum(p.numel() for p in self.parameters())
if cfg.tie_embeddings:
total_unique = total
else:
total_unique = total
# 激活参数量:每个 token 实际参与计算的那部分
inactive = 0
for layer in self.layers:
if layer.is_moe:
per_expert = sum(p.numel() for p in layer.ffn.experts[0].parameters())
skipped = cfg.n_routed_experts - cfg.n_activated_experts
inactive += per_expert * skipped
mtp_params = sum(p.numel() for p in self.mtp.parameters())
return {
"total": total_unique,
"activated": total_unique - inactive - mtp_params, # 推理时 MTP 可不用
"mtp": mtp_params,
"embedding": self.embed.weight.numel(),
}
def kv_cache_bytes_per_token(self, dtype_size: int = 2) -> dict:
"""对比 MLA 与等规模 MHA 的 KV cache 开销。"""
cfg = self.cfg
mla = (cfg.kv_lora_rank + cfg.qk_rope_head_dim) * cfg.n_layers * dtype_size
mha = 2 * cfg.n_heads * cfg.v_head_dim * cfg.n_layers * dtype_size
return {"mla": mla, "mha_equivalent": mha, "ratio": mha / mla}
def expert_load_stats(self) -> List[float]:
return [layer.ffn.gate.load_stats()["imbalance"]
for layer in self.layers if layer.is_moe]
@torch.no_grad()
def update_expert_bias(self):
"""训练循环每个 optimizer step 后调用一次。"""
speed = self.cfg.bias_update_speed
if speed <= 0:
return
for m in self.modules():
if isinstance(m, MoE):
m.gate.update_bias(speed)
|