Download model.py from bmnzyb/CampGPT_X: direct link, hf CLI and curl.
- Browser
- Download file 26.2 kB
-
https://huggingface.co/bmnzyb/CampGPT_X/resolve/main/model.py
- Command line
-
hf download hf://bmnzyb/CampGPT_X/model.py
-
curl -L -o model.py https://huggingface.co/bmnzyb/CampGPT_X/resolve/main/model.py
26.2 kB
| import inspect | |
| import torch | |
| import math | |
| import torch.nn as nn | |
| from torch.nn import functional as F | |
| from config import GPTConfig | |
| # ============================================================================= | |
| # ========================= 基础组件: RMSNorm ================================== | |
| # ============================================================================= | |
| class RMSNorm(nn.Module): | |
| """ | |
| RMSNorm: 去掉 mean centering 和 bias, | |
| 只保留 RMS 归一化 + 可学习缩放因子 γ。 | |
| 参考: https://arxiv.org/abs/1910.07467 | |
| """ | |
| def __init__(self, dim: int, eps: float = 1e-6): | |
| super().__init__() | |
| self.eps = eps | |
| self.weight = nn.Parameter(torch.ones(dim)) | |
| def _norm(self, x): | |
| return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) | |
| def forward(self, x): | |
| output = self._norm(x.float()).type_as(x) | |
| return output * self.weight | |
| # ============================================================================= | |
| # ========================= 基础组件: RoPE ==================================== | |
| # ============================================================================= | |
| class RotaryPositionalEmbedding(nn.Module): | |
| """ | |
| RoPE (Rotary Positional Embedding): | |
| 将相对位置信息通过旋转变换注入 Q/K,支持长度外推。 | |
| 参考: https://arxiv.org/abs/2104.09864 | |
| """ | |
| def __init__(self, dim: int, max_seq_len: int = 8192, base: float = 10000.0): | |
| super().__init__() | |
| self.dim = dim | |
| self.max_seq_len = max_seq_len | |
| self.base = base | |
| inv_freq = 1.0 / (self.base ** (torch.arange(0, self.dim, 2).float() / self.dim)) | |
| self.register_buffer("inv_freq", inv_freq, persistent=False) | |
| self._build_cache(max_seq_len) | |
| def _build_cache(self, seq_len: int): | |
| t = torch.arange(seq_len, dtype=self.inv_freq.dtype, device=self.inv_freq.device) | |
| freqs = torch.outer(t, self.inv_freq) | |
| emb = torch.cat((freqs, freqs), dim=-1) | |
| self.register_buffer("cos_cached", emb.cos(), persistent=False) | |
| self.register_buffer("sin_cached", emb.sin(), persistent=False) | |
| def forward(self, x, seq_len: int): | |
| if seq_len > self.max_seq_len: | |
| self._build_cache(seq_len) | |
| self.max_seq_len = seq_len | |
| return ( | |
| self.cos_cached[:seq_len].to(x.dtype), | |
| self.sin_cached[:seq_len].to(x.dtype), | |
| ) | |
| def rotate_half(x): | |
| x1 = x[..., : x.shape[-1] // 2] | |
| x2 = x[..., x.shape[-1] // 2 :] | |
| return torch.cat((-x2, x1), dim=-1) | |
| def apply_rotary_pos_emb(q, k, cos, sin): | |
| cos = cos.unsqueeze(0).unsqueeze(0) | |
| sin = sin.unsqueeze(0).unsqueeze(0) | |
| q_embed = (q * cos) + (rotate_half(q) * sin) | |
| k_embed = (k * cos) + (rotate_half(k) * sin) | |
| return q_embed, k_embed | |
| # ============================================================================= | |
| # ========================= GQA 注意力 (支持 KV Cache) ========================= | |
| # ============================================================================= | |
| class GroupedQueryAttention(nn.Module): | |
| """ | |
| GQA (Grouped-Query Attention): | |
| Q heads 分组共享 K/V heads,推理时大幅减少 KV Cache 显存。 # 训练技巧:降显存,提token吞吐量 | |
| 训练时正常计算,推理时支持 KV Cache。 | |
| 参考: https://arxiv.org/abs/2305.13245 | |
| """ | |
| def __init__(self, config, layer_idx: int = 0): | |
| super().__init__() | |
| assert config.n_embd % config.n_head == 0 | |
| assert config.n_head % config.n_kv_head == 0 | |
| self.layer_idx = layer_idx | |
| self.n_head = config.n_head | |
| self.n_kv_head = config.n_kv_head | |
| self.n_rep = self.n_head // self.n_kv_head # 每个 KV head 需要被重复的次数 | |
| self.head_dim = config.n_embd // config.n_head | |
| self.n_embd = config.n_embd | |
| self.wq = nn.Linear(config.n_embd, config.n_head * self.head_dim, bias=False) | |
| self.wk = nn.Linear(config.n_embd, config.n_kv_head * self.head_dim, bias=False) | |
| self.wv = nn.Linear(config.n_embd, config.n_kv_head * self.head_dim, bias=False) | |
| self.wo = nn.Linear(config.n_head * self.head_dim, config.n_embd, bias=False) | |
| self.wo.NANOGPT_SCALE_INIT = 1 | |
| self.rotary_emb = RotaryPositionalEmbedding( | |
| self.head_dim, max_seq_len=config.block_size, | |
| ) | |
| def _repeat_kv(self, x: torch.Tensor) -> torch.Tensor: | |
| if self.n_rep == 1: | |
| return x | |
| B, n_kv_head, T, head_dim = x.shape | |
| x = x[:, :, None, :, :].expand(B, n_kv_head, self.n_rep, T, head_dim) | |
| return x.reshape(B, n_kv_head * self.n_rep, T, head_dim) | |
| def forward(self, x, kv_cache=None, start_pos: int = 0): | |
| """ | |
| Args: | |
| x: (B, T, C) | |
| kv_cache: KVCache 对象 (推理时使用, 训练时为 None) | |
| start_pos: 当前 token 在完整序列中的起始位置 (推理用) | |
| """ | |
| B, T, C = x.size() | |
| q = self.wq(x).view(B, T, self.n_head, self.head_dim).transpose(1, 2) | |
| k = self.wk(x).view(B, T, self.n_kv_head, self.head_dim).transpose(1, 2) | |
| v = self.wv(x).view(B, T, self.n_kv_head, self.head_dim).transpose(1, 2) | |
| # RoPE (位置偏移处理) | |
| if kv_cache is not None: | |
| cos, sin = self.rotary_emb(q, start_pos + T) | |
| cos = cos[start_pos:start_pos + T] | |
| sin = sin[start_pos:start_pos + T] | |
| else: | |
| cos, sin = self.rotary_emb(q, T) | |
| q, k = apply_rotary_pos_emb(q, k, cos, sin) | |
| # KV Cache | |
| if kv_cache is not None: | |
| k, v = kv_cache.update(self.layer_idx, k, v) | |
| k = self._repeat_kv(k) | |
| v = self._repeat_kv(v) | |
| # # 标准注意力计算: att = softmax((q @ k^T) / sqrt(d_k)) @ v | |
| # att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1))) | |
| # k_len = k.size(-2) | |
| # mask = torch.tril( | |
| # torch.ones(T, k_len, device=x.device, dtype=torch.bool) | |
| # ).view(1, 1, T, k_len) | |
| # att = att.masked_fill(~mask, torch.finfo(att.dtype).min) | |
| # att = F.softmax(att, dim=-1) | |
| # y = att @ v # 计算加权和,得到注意力输出(B, nh, T, T)x (B, nh, T, hs) -> (B, nh, T, hs) | |
| # Flash Attention (is_causal: 推理decode阶段T=1时不需要causal mask) # 训练技巧:降显存,提token吞吐量 | |
| is_causal = True if kv_cache is None else (T > 1) | |
| y = F.scaled_dot_product_attention(q, k, v, is_causal=is_causal) | |
| y = y.transpose(1, 2).contiguous().view(B, T, C) | |
| y = self.wo(y) | |
| return y | |
| # ============================================================================= | |
| # ========================= KV Cache ========================================== | |
| # ============================================================================= | |
| class KVCache: | |
| """ | |
| KV Cache: 自回归生成时缓存已计算的 K/V。 | |
| 预分配显存,避免反复 cat 造成碎片化。 | |
| GQA 优势: 缓存大小 = n_kv_head * head_dim (而非 n_head * head_dim) | |
| """ | |
| def __init__(self, config, batch_size: int, max_seq_len: int, | |
| device: torch.device, dtype=torch.bfloat16): | |
| self.n_layers = config.n_layer | |
| self.n_kv_head = config.n_kv_head | |
| self.head_dim = config.n_embd // config.n_head | |
| self.max_seq_len = max_seq_len | |
| # 预分配: (n_layers, B, n_kv_head, max_seq_len, head_dim) | |
| self.k_cache = torch.zeros( | |
| self.n_layers, batch_size, self.n_kv_head, max_seq_len, self.head_dim, | |
| device=device, dtype=dtype | |
| ) | |
| self.v_cache = torch.zeros( | |
| self.n_layers, batch_size, self.n_kv_head, max_seq_len, self.head_dim, | |
| device=device, dtype=dtype | |
| ) | |
| self.seq_len = 0 | |
| def update(self, layer_idx: int, k_new: torch.Tensor, v_new: torch.Tensor): | |
| """写入新 K/V 并返回完整缓存""" | |
| new_len = k_new.size(2) | |
| end = self.seq_len + new_len | |
| self.k_cache[layer_idx, :, :, self.seq_len:end, :] = k_new | |
| self.v_cache[layer_idx, :, :, self.seq_len:end, :] = v_new | |
| return ( | |
| self.k_cache[layer_idx, :, :, :end, :], | |
| self.v_cache[layer_idx, :, :, :end, :], | |
| ) | |
| def advance(self, n: int = 1): | |
| self.seq_len += n | |
| def reset(self): | |
| self.seq_len = 0 | |
| self.k_cache.zero_() | |
| self.v_cache.zero_() | |
| def memory_footprint(config, batch_size=1, seq_len=2048, dtype=torch.bfloat16): | |
| """分析 KV Cache 显存占用""" | |
| bpe = 2 if dtype in (torch.bfloat16, torch.float16) else 4 | |
| head_dim = config.n_embd // config.n_head | |
| gqa = 2 * config.n_layer * batch_size * config.n_kv_head * seq_len * head_dim * bpe | |
| mha = 2 * config.n_layer * batch_size * config.n_head * seq_len * head_dim * bpe | |
| return gqa, mha | |
| # ============================================================================= | |
| # ========================= SwiGLU FFN ======================================== | |
| # ============================================================================= | |
| class SwiGLUFFN(nn.Module): | |
| """ | |
| SwiGLU FFN: SiLU 门控 + 3 个权重矩阵 | |
| SwiGLU(x) = (SiLU(xW1) ⊙ xW3) W2 | |
| 参考: https://arxiv.org/abs/2002.05202 | |
| """ | |
| def __init__(self, config): | |
| super().__init__() | |
| hidden_dim = int(4 * config.n_embd * 2 / 3) | |
| if hasattr(config, 'multiple_of'): | |
| hidden_dim = config.multiple_of * ((hidden_dim + config.multiple_of - 1) // config.multiple_of) | |
| self.w1 = nn.Linear(config.n_embd, hidden_dim, bias=False) | |
| self.w3 = nn.Linear(config.n_embd, hidden_dim, bias=False) | |
| self.w2 = nn.Linear(hidden_dim, config.n_embd, bias=False) | |
| self.w2.NANOGPT_SCALE_INIT = 1 | |
| def forward(self, x): | |
| return self.w2(F.silu(self.w1(x)) * self.w3(x)) | |
| # ============================================================================= | |
| # ========================= MoE 层 ============================================ | |
| # ============================================================================= | |
| class MoEGate(nn.Module): | |
| """MoE 路由门控: Top-K 路由 + 辅助负载均衡损失""" | |
| def __init__(self, config): | |
| super().__init__() | |
| self.n_experts = config.n_experts | |
| self.top_k = config.n_experts_per_tok | |
| self.gate = nn.Linear(config.n_embd, config.n_experts, bias=False) | |
| self.aux_loss_coeff = config.aux_loss_coeff | |
| def forward(self, x): | |
| logits = self.gate(x) | |
| weights, indices = torch.topk(logits, self.top_k, dim=-1) | |
| weights = F.softmax(weights, dim=-1) | |
| if self.training: | |
| probs = F.softmax(logits, dim=-1) | |
| mask = F.one_hot(indices, num_classes=self.n_experts).sum(dim=1) | |
| f = mask.float().mean(dim=0) | |
| P = probs.mean(dim=0) | |
| aux_loss = self.aux_loss_coeff * self.n_experts * (f * P).sum() | |
| else: | |
| aux_loss = torch.tensor(0.0, device=x.device) | |
| return weights, indices, aux_loss | |
| class MoEFFN(nn.Module): | |
| """ | |
| MoE FFN: 共享专家 + 路由专家 | |
| 参考: DeepSeekMoE (https://arxiv.org/abs/2401.06066) | |
| """ | |
| def __init__(self, config): | |
| super().__init__() | |
| self.n_experts = config.n_experts | |
| self.top_k = config.n_experts_per_tok | |
| self.n_embd = config.n_embd | |
| self.gate = MoEGate(config) | |
| self.experts = nn.ModuleList([SwiGLUFFN(config) for _ in range(config.n_experts)]) | |
| self.n_shared_experts = getattr(config, 'n_shared_experts', 1) | |
| if self.n_shared_experts > 0: | |
| self.shared_experts = nn.ModuleList( | |
| [SwiGLUFFN(config) for _ in range(self.n_shared_experts)] | |
| ) | |
| def forward(self, x): | |
| B, T, C = x.shape | |
| x_flat = x.view(-1, C) | |
| # 共享专家 | |
| shared_output = torch.zeros_like(x_flat) | |
| if self.n_shared_experts > 0: | |
| for se in self.shared_experts: | |
| shared_output = shared_output + se(x_flat) | |
| if self.n_shared_experts > 1: | |
| shared_output = shared_output / self.n_shared_experts | |
| # 路由专家 | |
| weights, indices, aux_loss = self.gate(x_flat) | |
| routed_output = torch.zeros_like(x_flat) | |
| for i in range(self.n_experts): | |
| token_idx, slot_idx = torch.where(indices == i) | |
| if token_idx.numel() == 0: | |
| continue | |
| expert_input = x_flat[token_idx] | |
| expert_output = self.experts[i](expert_input) | |
| expert_weights = weights[token_idx, slot_idx].unsqueeze(-1) | |
| # routed_output.index_add_(0, token_idx, expert_output * expert_weights) | |
| contrib = (expert_output * expert_weights).to(routed_output.dtype) | |
| routed_output.index_add_(0, token_idx, contrib) | |
| output = (shared_output + routed_output).view(B, T, C) | |
| return output, aux_loss | |
| # ============================================================================= | |
| # ========================= Transformer Block ================================== | |
| # ============================================================================= | |
| class Block(nn.Module): | |
| """ | |
| Transformer Block: | |
| Pre-RMSNorm → GQA → 残差 | |
| Pre-RMSNorm → SwiGLU (MoE) → 残差 | |
| 支持 Gradient Checkpointing | |
| """ | |
| def __init__(self, config, layer_idx: int = 0): | |
| super().__init__() | |
| self.ln_1 = RMSNorm(config.n_embd, eps=config.norm_eps) | |
| self.attn = GroupedQueryAttention(config, layer_idx=layer_idx) | |
| self.ln_2 = RMSNorm(config.n_embd, eps=config.norm_eps) | |
| self.use_moe = config.use_moe | |
| if self.use_moe: | |
| self.ffn = MoEFFN(config) | |
| else: | |
| self.ffn = SwiGLUFFN(config) | |
| self.use_checkpoint = config.use_gradient_checkpointing | |
| def _attn_block(self, x): | |
| """注意力子块 (可被 checkpoint 包裹)""" | |
| return self.attn(self.ln_1(x)) | |
| def _ffn_block(self, x): | |
| """FFN 子块 (可被 checkpoint 包裹)""" | |
| if self.use_moe: | |
| return self.ffn(self.ln_2(x)) | |
| else: | |
| return self.ffn(self.ln_2(x)), torch.tensor(0.0, device=x.device) | |
| def forward(self, x, kv_cache=None, start_pos: int = 0): | |
| """ | |
| 前向传播,支持三种模式: | |
| 1. 训练 + checkpoint: 使用 gradient checkpointing 节省显存 | |
| 2. 训练 - checkpoint: 标准前向 | |
| 3. 推理 (kv_cache is not None): 使用 KV Cache | |
| """ | |
| # --- 注意力 --- | |
| if self.use_checkpoint and self.training and kv_cache is None: | |
| attn_out = torch.utils.checkpoint.checkpoint( | |
| self._attn_block, x, | |
| use_reentrant=False, | |
| preserve_rng_state=True, | |
| ) | |
| else: | |
| attn_out = self.attn(self.ln_1(x), kv_cache=kv_cache, start_pos=start_pos) | |
| x = x + attn_out | |
| # --- FFN --- | |
| if self.use_checkpoint and self.training and kv_cache is None: | |
| if self.use_moe: | |
| ffn_out, aux_loss = torch.utils.checkpoint.checkpoint( | |
| self._ffn_block, x, | |
| use_reentrant=False, | |
| preserve_rng_state=True, | |
| ) | |
| else: | |
| def _dense_ffn(x_in): | |
| out = self.ffn(self.ln_2(x_in)) | |
| return out, torch.tensor(0.0, device=x_in.device) | |
| ffn_out, aux_loss = torch.utils.checkpoint.checkpoint( | |
| _dense_ffn, x, | |
| use_reentrant=False, | |
| preserve_rng_state=True, | |
| ) | |
| else: | |
| if self.use_moe: | |
| ffn_out, aux_loss = self.ffn(self.ln_2(x)) | |
| else: | |
| ffn_out = self.ffn(self.ln_2(x)) | |
| aux_loss = torch.tensor(0.0, device=x.device) | |
| x = x + ffn_out | |
| return x, aux_loss | |
| # ============================================================================= | |
| # ========================= GPT 模型 ========================================== | |
| # ============================================================================= | |
| class GPT(nn.Module): | |
| def __init__(self, config: GPTConfig): | |
| super().__init__() | |
| self.config = config | |
| self.transformer = nn.ModuleDict(dict( | |
| wte=nn.Embedding(config.vocab_size, config.n_embd), | |
| h=nn.ModuleList([Block(config, layer_idx=i) for i in range(config.n_layer)]), | |
| ln_f=RMSNorm(config.n_embd, eps=config.norm_eps), | |
| )) | |
| self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False) | |
| self.transformer.wte.weight = self.lm_head.weight # 权重共享 | |
| self.apply(self._init_weights) | |
| self._print_model_info() | |
| # ------------------------------------------------------------------------- | |
| def _init_weights(self, module): # 权重初始化: Linear 正态分布, Embedding 正态分布, bias 置零 | |
| if isinstance(module, nn.Linear): | |
| std = 0.02 | |
| if hasattr(module, 'NANOGPT_SCALE_INIT'): | |
| std *= (2 * self.config.n_layer) ** -0.5 | |
| torch.nn.init.normal_(module.weight, mean=0.0, std=std) | |
| if module.bias is not None: | |
| torch.nn.init.zeros_(module.bias) | |
| elif isinstance(module, nn.Embedding): | |
| torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) | |
| # ------------------------------------------------------------------------- | |
| def _print_model_info(self): # 模型参数统计和配置信息展示 | |
| cfg = self.config | |
| total = sum(p.numel() for p in self.parameters()) | |
| print(f"\n{'='*60}") | |
| print(f" Layers={cfg.n_layer}, Heads={cfg.n_head}, KV Heads={cfg.n_kv_head}") | |
| print(f" Embed={cfg.n_embd}, BlockSize={cfg.block_size}") | |
| if cfg.use_moe: | |
| print(f" MoE: {cfg.n_experts} experts, top-{cfg.n_experts_per_tok}, " | |
| f"{cfg.n_shared_experts} shared") | |
| print(f" GradCheckpoint={cfg.use_gradient_checkpointing}") | |
| print(f" Total Parameters: {total:,} ({total/1e6:.1f}M)") | |
| if cfg.use_moe: | |
| active = self._estimate_active_params() | |
| print(f" Active Parameters/token: ~{active:,} ({active/1e6:.1f}M)") | |
| print(f"{'='*60}\n") | |
| # ------------------------------------------------------------------------- | |
| def _estimate_active_params(self): # 估算每 token 活跃参数量 (MoE 模型) | |
| cfg = self.config | |
| emb = cfg.vocab_size * cfg.n_embd | |
| hd = cfg.n_embd // cfg.n_head | |
| attn = cfg.n_embd * cfg.n_head * hd + cfg.n_embd * cfg.n_kv_head * hd * 2 + cfg.n_head * hd * cfg.n_embd | |
| norms = cfg.n_embd * 2 | |
| hidden = int(4 * cfg.n_embd * 2 / 3) | |
| hidden = cfg.multiple_of * ((hidden + cfg.multiple_of - 1) // cfg.multiple_of) | |
| ffn_single = 3 * cfg.n_embd * hidden | |
| if cfg.use_moe: | |
| active_ffn = cfg.n_experts_per_tok * ffn_single + cfg.n_shared_experts * ffn_single | |
| active_ffn += cfg.n_embd * cfg.n_experts # gate | |
| else: | |
| active_ffn = ffn_single | |
| per_layer = attn + norms + active_ffn | |
| return emb + per_layer * cfg.n_layer + cfg.n_embd | |
| # ------------------------------------------------------------------------- | |
| def forward(self, idx, targets=None, kv_cache=None, start_pos: int = 0): | |
| B, T = idx.size() | |
| assert T <= self.config.block_size, \ | |
| f"Sequence length {T} exceeds block_size {self.config.block_size}" | |
| x = self.transformer.wte(idx) | |
| total_aux_loss = torch.tensor(0.0, device=idx.device) | |
| for block in self.transformer.h: | |
| x, aux_loss = block(x, kv_cache=kv_cache, start_pos=start_pos) | |
| total_aux_loss = total_aux_loss + aux_loss | |
| x = self.transformer.ln_f(x) | |
| logits = self.lm_head(x) | |
| loss = None | |
| if targets is not None: | |
| loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1)) | |
| if self.config.use_moe: | |
| loss = loss + total_aux_loss | |
| return logits, loss | |
| # ------------------------------------------------------------------------- | |
| def configure_optimizers(self, weight_decay, learning_rate, device_type, master_process=True): | |
| # 只对需要梯度更新的参数进行优化器配置,区分权重衰减和非权重衰减参数 | |
| param_dict = {pn: p for pn, p in self.named_parameters() if p.requires_grad} | |
| decay_params = [p for n, p in param_dict.items() if p.dim() >= 2] | |
| nodecay_params = [p for n, p in param_dict.items() if p.dim() < 2] | |
| optim_groups = [ | |
| {'params': decay_params, 'weight_decay': weight_decay}, | |
| {'params': nodecay_params, 'weight_decay': 0.0}, | |
| ] | |
| if master_process: | |
| nd = sum(p.numel() for p in decay_params) | |
| nn_ = sum(p.numel() for p in nodecay_params) | |
| print(f" Decayed tensors: {len(decay_params)}, params: {nd:,}") | |
| print(f" Non-decayed tensors: {len(nodecay_params)}, params: {nn_:,}") | |
| # Fused AdamW 的原理是将多个小的内核调用合并为一个大内核,减少内核启动和内存访问开销,提升训练速度。 | |
| fused_available = 'fused' in inspect.signature(torch.optim.AdamW).parameters | |
| use_fused = fused_available and device_type == "cuda" | |
| if master_process: | |
| print(f" Fused AdamW: {use_fused}") | |
| optimizer = torch.optim.AdamW( | |
| optim_groups, lr=learning_rate, | |
| betas=(0.9, 0.95), eps=1e-8, fused=use_fused # 训练技巧:显存不变,提token吞吐量 | |
| ) | |
| return optimizer | |
| # ------------------------------------------------------------------------- | |
| def set_gradient_checkpointing(self, enabled: bool): | |
| """训练/推理切换时动态开关 checkpointing,推理时关闭以节省计算开销""" | |
| for block in self.transformer.h: | |
| block.use_checkpoint = enabled | |
| # ------------------------------------------------------------------------- | |
| def generate(self, idx, max_new_tokens, temperature=1.0, top_k=50, top_p=0.9): | |
| """ | |
| 使用 KV Cache 的高效自回归生成 | |
| """ | |
| B, prompt_len = idx.shape | |
| total_len = prompt_len + max_new_tokens | |
| assert total_len <= self.config.block_size | |
| # 推理模式: 关闭 checkpoint | |
| self.set_gradient_checkpointing(False) | |
| self.eval() | |
| kv_cache = KVCache( | |
| self.config, B, total_len, idx.device, dtype=torch.bfloat16 | |
| ) | |
| # --- Prefill: 处理整个 prompt --- | |
| x = self.transformer.wte(idx) | |
| for block in self.transformer.h: | |
| x, _ = block(x, kv_cache=kv_cache, start_pos=0) | |
| kv_cache.advance(prompt_len) | |
| x = self.transformer.ln_f(x) | |
| logits = self.lm_head(x[:, -1, :]) | |
| next_token = self._sample(logits, temperature, top_k, top_p) | |
| generated = [next_token] | |
| # --- Decode: 逐 token 生成 --- | |
| for step in range(1, max_new_tokens): | |
| x = self.transformer.wte(next_token.unsqueeze(1)) | |
| pos = prompt_len + step - 1 | |
| for block in self.transformer.h: | |
| x, _ = block(x, kv_cache=kv_cache, start_pos=pos) | |
| kv_cache.advance(1) | |
| x = self.transformer.ln_f(x) | |
| logits = self.lm_head(x[:, -1, :]) | |
| next_token = self._sample(logits, temperature, top_k, top_p) | |
| generated.append(next_token) | |
| generated = torch.stack(generated, dim=1) | |
| return torch.cat([idx, generated], dim=1) | |
| # ------------------------------------------------------------------------- | |
| def _sample1(self, logits, temperature, top_k, top_p): | |
| # 从 logits 中采样下一个 token,支持 temperature、top-k 和 top-p 策略 | |
| if temperature == 0: | |
| return logits.argmax(dim=-1) | |
| logits = logits / temperature | |
| if top_k > 0: | |
| v, _ = torch.topk(logits, min(top_k, logits.size(-1))) | |
| logits[logits < v[:, [-1]]] = -float('Inf') | |
| if top_p < 1.0: | |
| sorted_logits, sorted_idx = torch.sort(logits, descending=True) | |
| cum_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) | |
| remove = cum_probs > top_p | |
| remove[..., 1:] = remove[..., :-1].clone() | |
| remove[..., 0] = False | |
| indices_to_remove = remove.scatter(1, sorted_idx, remove) | |
| logits[indices_to_remove] = -float('Inf') | |
| probs = F.softmax(logits, dim=-1) | |
| return torch.multinomial(probs, num_samples=1).squeeze(-1) | |
| def _sample(self, logits, temperature, top_k, top_p): | |
| # 转为 float32 避免 bfloat16 精度问题 | |
| logits = logits.float() | |
| if temperature == 0: | |
| return logits.argmax(dim=-1) | |
| logits = logits / temperature | |
| if top_k > 0: | |
| v, _ = torch.topk(logits, min(top_k, logits.size(-1))) | |
| logits[logits < v[:, [-1]]] = -float('Inf') | |
| if top_p < 1.0: | |
| sorted_logits, sorted_idx = torch.sort(logits, descending=True) | |
| cum_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) | |
| remove = cum_probs > top_p | |
| remove[..., 1:] = remove[..., :-1].clone() | |
| remove[..., 0] = False | |
| indices_to_remove = remove.scatter(1, sorted_idx, remove) | |
| logits[indices_to_remove] = -float('Inf') | |
| probs = F.softmax(logits, dim=-1) | |
| # 安全检查:将可能的 nan/inf 替换为 0,避免 multinomial 崩溃 | |
| probs = torch.nan_to_num(probs, nan=0.0, posinf=0.0, neginf=0.0) | |
| # 如果某行全为 0(极端情况),给均匀分布 | |
| zero_rows = (probs.sum(dim=-1) == 0) | |
| if zero_rows.any(): | |
| probs[zero_rows] = 1.0 / probs.size(-1) | |
| return torch.multinomial(probs, num_samples=1).squeeze(-1) |