Text Generation
Transformers
Safetensors
Chinese
English
granite
mixture-of-experts
Mixture of Experts
looped-transformer
recurrent-depth
attention-residuals
Instructions to use tysgydke/Granite-1B-LoopAttnRes with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use tysgydke/Granite-1B-LoopAttnRes with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="tysgydke/Granite-1B-LoopAttnRes")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("tysgydke/Granite-1B-LoopAttnRes", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use tysgydke/Granite-1B-LoopAttnRes with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "tysgydke/Granite-1B-LoopAttnRes" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "tysgydke/Granite-1B-LoopAttnRes", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/tysgydke/Granite-1B-LoopAttnRes
- SGLang
How to use tysgydke/Granite-1B-LoopAttnRes 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 "tysgydke/Granite-1B-LoopAttnRes" \ --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": "tysgydke/Granite-1B-LoopAttnRes", "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 "tysgydke/Granite-1B-LoopAttnRes" \ --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": "tysgydke/Granite-1B-LoopAttnRes", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use tysgydke/Granite-1B-LoopAttnRes with Docker Model Runner:
docker model run hf.co/tysgydke/Granite-1B-LoopAttnRes
Download modeling_granite_loop.py from tysgydke/Granite-1B-LoopAttnRes: direct link, hf CLI and curl.
- Browser
- Download file 22.6 kB
-
https://huggingface.co/tysgydke/Granite-1B-LoopAttnRes/resolve/main/modeling_granite_loop.py
- Command line
-
hf download hf://tysgydke/Granite-1B-LoopAttnRes/modeling_granite_loop.py
-
curl -L -o modeling_granite_loop.py https://huggingface.co/tysgydke/Granite-1B-LoopAttnRes/resolve/main/modeling_granite_loop.py
22.6 kB
| """Granite-1B-A400M 的 Looped × AttnRes 改造(消融实验 G0-G3 共用一份代码)。 | |
| 开关(通过 from_pretrained 的 kwargs 直接传给 __init__): | |
| - loop_rounds: int = 1 循环轮数; 1 = G0 基线 | |
| - attnres: bool = False 轮内 Block AttnRes (G2+) | |
| - cross_round: bool = False 源集合加入之前所有轮次的块摘要 (G3) | |
| - attnres_blocks: int = 8 AttnRes 块数 (48 子层 / 8 块 = 6 子层每块) | |
| 结构要点(依据 DESIGN.md 与两篇论文): | |
| - 循环: 24 层 × R 轮, 权重跨轮硬共享; 每轮输出过共享 lm_head 算 CE, 取各轮均值 | |
| - AttnRes: 每子层(attn/MoE 各算一个)一个零初始化可学习伪查询, 按轮独立; | |
| key=value=子层输出, 核 exp(q·RMSNorm(k)) (RMSNorm 无可学习参数), softmax 在源维度 | |
| - residual_multiplier μ=0.22 折进 value (v_i = μ·f_i) | |
| - 块内: 部分和按标准残差累加; 块边界: 块摘要加入源集合 | |
| - 每轮末尾: 用独立的终读出伪查询对 [b0] + (跨轮时含历史块) + 本轮块摘要 做最后读出 | |
| - 跨轮(G3): 源 = [b0(embedding)] + [之前所有轮次的块摘要] + [本轮已完成块] + [本块部分和] | |
| 注意: 训练前向 (input_ids/labels) 不支持 KV cache; 推理用 greedy_generate(手动贪心 + KV cache)。 | |
| """ | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| from torch.utils.checkpoint import checkpoint | |
| from types import SimpleNamespace | |
| from transformers.models.granitemoe.configuration_granitemoe import GraniteMoeConfig | |
| from transformers.models.granitemoe.modeling_granitemoe import ( | |
| GraniteMoeForCausalLM, | |
| load_balancing_loss_func, | |
| ) | |
| from transformers.masking_utils import create_causal_mask | |
| def _rms_norm(x, eps=1e-6): | |
| """无可学习参数的 RMSNorm(AttnRes 核里用在 key 上)。""" | |
| return x * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + eps).to(x.dtype) | |
| class GraniteLoopForCausalLM(GraniteMoeForCausalLM): | |
| def __init__(self, config: GraniteMoeConfig, loop_rounds: int = 1, attnres: bool = False, | |
| cross_round: bool = False, attnres_blocks: int = 8, **kwargs): | |
| super().__init__(config) | |
| # 开关: kwargs 优先, 否则读 config(兼容加载我们自己 save_pretrained 的 checkpoint) | |
| self.loop_rounds = getattr(config, "loop_rounds", None) or loop_rounds | |
| self.use_attnres = getattr(config, "attnres", None) | |
| if self.use_attnres is None: | |
| self.use_attnres = attnres | |
| self.cross_round = getattr(config, "cross_round", None) | |
| if self.cross_round is None: | |
| self.cross_round = cross_round | |
| self.attnres_blocks = getattr(config, "attnres_blocks", None) or attnres_blocks | |
| # 写回 config, 使 save_pretrained 能持久化开关 | |
| config.loop_rounds = self.loop_rounds | |
| config.attnres = self.use_attnres | |
| config.cross_round = self.cross_round | |
| config.attnres_blocks = self.attnres_blocks | |
| L = config.num_hidden_layers | |
| assert (2 * L) % self.attnres_blocks == 0, "子层数需被块数整除" | |
| self.sub_per_block = (2 * L) // self.attnres_blocks # 每块子层数 | |
| assert self.sub_per_block % 2 == 0 | |
| self.layers_per_block = self.sub_per_block // 2 | |
| d = config.hidden_size | |
| self.alpha_recorder = None # 分析钩子: list 时 _attnres_step 会记录各源注意力权重 | |
| self.round_recorder = None # 分析钩子: list 时 forward 会记录每轮结束后的 hidden | |
| if self.use_attnres: | |
| # 伪查询: (轮数, 子层数 + 1, d), 零初始化 -> 初始为等权平均 | |
| # +1 = 每轮结束时的终读出查询(对 [b0] + 各块摘要 做最后一次注意力, | |
| # 否则最终 hidden 只剩最后一个块的部分和, 丢失 b0 主干 -> 初始 loss 崩到随机水平) | |
| self.pseudo_queries = nn.Parameter( | |
| torch.zeros(self.loop_rounds, 2 * L + 1, d) | |
| ) | |
| def reset_pseudo_queries(self): | |
| """from_pretrained 后必须调用: transformers 5 的加载管线会把 checkpoint 里 | |
| 缺失的 pseudo_queries 重置为未初始化内存(可能含 NaN), 这里重新归零, | |
| 恢复论文要求的零初始化(初始 = 等权平均)。自己 save_pretrained 的 | |
| checkpoint 含训练后的值, 加载后无需调用。""" | |
| if self.use_attnres: | |
| self.pseudo_queries.zero_() | |
| # ---------- 单个子层前向 ---------- | |
| def _sublayer(self, layer, kind, h, mask, pos_emb): | |
| if kind == "attn": | |
| return layer.self_attn( | |
| hidden_states=layer.input_layernorm(h), | |
| attention_mask=mask, | |
| position_embeddings=pos_emb, | |
| )[0] | |
| # moe: 复制 GraniteMoeMoE.forward 但保留 router_logits(aux loss 用), | |
| # 作为 checkpoint 段的返回值传出, 避免用 hook 持有段内张量引用(会阻止激活释放) | |
| moe = layer.block_sparse_moe | |
| normed = layer.post_attention_layernorm(h) | |
| b, t, d = normed.shape | |
| flat = normed.reshape(-1, d) | |
| top_idx, top_w, rlogits = moe.router(flat) | |
| out = moe.experts(flat, top_idx, top_w).view(b, t, d) | |
| return out, rlogits | |
| def _aggregate(self, q, sources): | |
| """AttnRes 聚合: q (d,), sources list of (b,s,d) -> (b,s,d)""" | |
| V = torch.stack(sources) # (S,b,t,d) | |
| logits = torch.einsum("d,Sbtd->Sbt", q, _rms_norm(V)) | |
| alpha = logits.softmax(dim=0) # 源维度归一化 | |
| return torch.einsum("Sbt,Sbtd->btd", alpha, V) | |
| # ---------- checkpoint 段(显存优化: 粒度从整轮细化到单层/单块) ---------- | |
| # 整轮一个段时, 反向重算会把 24 层(AttnRes 时还有每子层 stack 出的全部源副本 V) | |
| # 同时留在显存里 -> OOM。细化后每段反向只重算一层/一个块。 | |
| def _layer_fwd(self, layer, h, mask, pos_emb, mu): | |
| """非 AttnRes 路径: 一个完整层(attn + moe), 返回 (h, router_logits)。""" | |
| f = self._sublayer(layer, "attn", h, mask, pos_emb) | |
| h = h + mu * f | |
| f, rl = self._sublayer(layer, "moe", h, mask, pos_emb) | |
| return h + mu * f, rl | |
| def _attnres_step(self, layer, kind, q, cat, inv_rms, block_sum, mask, pos_emb, mu): | |
| """AttnRes 路径: 聚合 + 单个子层 + 块内累加, 返回 (新 block_sum, router_logits 或 None)。 | |
| 性能优化版聚合(profile 显示旧的"每子层 stack 全部源 + 对全部源做 RMSNorm" | |
| 贡献了 ~60% 的 CUDA 时间, 全是 elementwise/cat): | |
| - cat: (S,b,t,d) 块内固定源的堆叠, 每块只 stack 一次(块级缓存) | |
| - inv_rms: (S,b,t) 各固定源的 RMSNorm 系数, 利用 q·RMSNorm(k) = (q·k)·inv_rms | |
| - softmax 拆成 [固定源 | block_sum] 两段, 每子层只需对 block_sum 单张量求范数 | |
| 数学上与 _aggregate 完全等价(仅浮点运算次序不同)。""" | |
| logits = torch.einsum("d,sbtd->sbt", q, cat) * inv_rms | |
| if block_sum is not None: | |
| bs_inv = torch.rsqrt(block_sum.float().pow(2).mean(-1) + 1e-6) | |
| bs_logit = torch.einsum("d,btd->bt", q, block_sum) * bs_inv | |
| alpha = torch.cat([logits, bs_logit.unsqueeze(0)], dim=0).softmax(dim=0) | |
| else: | |
| alpha = logits.softmax(dim=0) | |
| # 分析钩子: 仅分析脚本设置 alpha_recorder 时生效(训练时恒为 None, 零开销)。 | |
| # 记录 (上下文(轮,块,层序,类型), 各源平均权重), 用于检验"跨轮检索是否偷懒"。 | |
| rec = getattr(self, "alpha_recorder", None) | |
| if rec is not None: | |
| with torch.no_grad(): | |
| rec.append((self._rec_ctx, alpha.detach().mean(dim=(1, 2)).cpu())) | |
| alpha = alpha.to(cat.dtype) | |
| if block_sum is not None: | |
| h = torch.einsum("sbt,sbtd->btd", alpha[:-1], cat) + alpha[-1].unsqueeze(-1) * block_sum | |
| else: | |
| h = torch.einsum("sbt,sbtd->btd", alpha, cat) | |
| f = self._sublayer(layer, kind, h, mask, pos_emb) | |
| rl = None | |
| if kind == "moe": | |
| f, rl = f | |
| upd = mu * f | |
| return (upd if block_sum is None else block_sum + upd), rl | |
| def _run_block(self, blk_layers, q_blk, fixed, mask, pos_emb, mu): | |
| """一个 AttnRes 块(3 层 / 6 个子层), 作为一个 checkpoint 段。 | |
| 源堆叠 cat 与 inv_rms 在段内构建(而非段外传入): 段内张量在前向结束后即释放, | |
| 反向重算时重建, 避免 32 个块的 cat(~0.5GB each)同时常驻显存。 | |
| profile 显示旧的"每子层 stack 全部源 + 对全部源 RMSNorm"贡献 ~60% CUDA 时间, | |
| 这里每块只 stack/归一化一次, 块内 6 个子层复用, 并用 | |
| q·RMSNorm(k) = (q·k)·inv_rms 把归一化折成标量乘法(数学上与 _aggregate 等价)。""" | |
| cat = torch.stack(fixed) # (S,b,t,d) | |
| inv_rms = torch.rsqrt(cat.float().pow(2).mean(-1) + 1e-6) # (S,b,t) fp32 | |
| block_sum = None | |
| rlogits = [] | |
| for j, layer in enumerate(blk_layers): | |
| for kind in ("attn", "moe"): | |
| q = q_blk[2 * j] if kind == "attn" else q_blk[2 * j + 1] | |
| self._rec_ctx = (*self._rec_ctx_base, j, kind) # 分析钩子用, 训练时无读取方 | |
| block_sum, rl = self._attnres_step( | |
| layer, kind, q, cat, inv_rms, block_sum, mask, pos_emb, mu) | |
| if rl is not None: | |
| rlogits.append(rl) | |
| return block_sum, rlogits | |
| def _ce(self, h, labels): | |
| """单轮 CE, 单独成段: 避免 4 轮的 logits(fp32 上转后每轮 ~1.2GB)常驻反传图。""" | |
| logits = self.lm_head(self.model.norm(h)) / self.logits_scaling | |
| return self.loss_function(logits, labels, vocab_size=self.config.vocab_size) | |
| # ---------- 一轮循环 ---------- | |
| def _run_round(self, h, round_idx, b0, prev_blocks, mask, pos_emb, mu, use_ckpt): | |
| """返回 (本轮最终 hidden, 块摘要列表, 各 MoE 层的 router_logits)。""" | |
| layers = self.model.layers | |
| rlogits = [] | |
| if not self.use_attnres: | |
| for layer in layers: | |
| if use_ckpt: | |
| h, rl = checkpoint( | |
| lambda hh, l=layer: self._layer_fwd(l, hh, mask, pos_emb, mu), | |
| h, use_reentrant=False) | |
| else: | |
| h, rl = self._layer_fwd(layer, h, mask, pos_emb, mu) | |
| rlogits.append(rl) | |
| block_sums = [] | |
| else: | |
| fixed = [b0] + (list(prev_blocks) if self.cross_round else []) | |
| block_sums = [] | |
| for blk in range(self.attnres_blocks): | |
| blk_layers = layers[blk * self.layers_per_block:(blk + 1) * self.layers_per_block] | |
| q_blk = self.pseudo_queries[round_idx, | |
| blk * self.sub_per_block:(blk + 1) * self.sub_per_block] | |
| fix_snap = tuple(fixed) # fixed 之后会 append, 必须快照, 否则重算时形状对不上 | |
| self._rec_ctx_base = (round_idx, blk) # 分析钩子用 | |
| if use_ckpt: | |
| block_sum, rls = checkpoint( | |
| lambda _q, bl=blk_layers, qb=q_blk, fs=fix_snap: | |
| self._run_block(bl, qb, fs, mask, pos_emb, mu), | |
| q_blk, use_reentrant=False) | |
| else: | |
| block_sum, rls = self._run_block(blk_layers, q_blk, fix_snap, mask, pos_emb, mu) | |
| rlogits.extend(rls) | |
| fixed.append(block_sum) | |
| block_sums.append(block_sum) | |
| # 终读出: 对 [b0] + (跨轮时的历史块) + 本轮各块摘要 做最后一次注意力, | |
| # 而不是只取最后一个块的部分和 | |
| final_sources = [b0] + (list(prev_blocks) if self.cross_round else []) + block_sums | |
| q_final = self.pseudo_queries[round_idx, 2 * len(layers)] | |
| if use_ckpt: | |
| h = checkpoint( | |
| lambda _q, qq=q_final, src=tuple(final_sources): self._aggregate(qq, list(src)), | |
| q_final, use_reentrant=False) | |
| else: | |
| h = self._aggregate(q_final, final_sources) | |
| return h, block_sums, rlogits | |
| # ---------- 训练前向 ---------- | |
| def forward(self, input_ids=None, labels=None, attention_mask=None, **kwargs): | |
| model = self.model | |
| bsz, seq = input_ids.shape | |
| position_ids = torch.arange(seq, device=input_ids.device).unsqueeze(0) | |
| h = model.embed_tokens(input_ids) * model.embedding_multiplier | |
| mask = create_causal_mask( | |
| config=self.config, inputs_embeds=h, attention_mask=attention_mask, | |
| past_key_values=None, position_ids=position_ids, | |
| ) | |
| pos_emb = model.rotary_emb(h, position_ids) | |
| mu = self.config.residual_multiplier | |
| b0 = h | |
| use_ckpt = self.training and torch.is_grad_enabled() | |
| ce_losses = [] | |
| all_rlogits = [] | |
| prev_blocks = [] # cross_round 时跨轮累积的块摘要 | |
| for r in range(self.loop_rounds): | |
| h, round_blocks, rlogits = self._run_round( | |
| h, r, b0, prev_blocks, mask, pos_emb, mu, use_ckpt) | |
| if self.round_recorder is not None: | |
| self.round_recorder.append(h.detach()) | |
| if self.cross_round: | |
| prev_blocks = prev_blocks + round_blocks | |
| if labels is not None: | |
| if use_ckpt: | |
| ce = checkpoint(lambda hh=h, lb=labels: self._ce(hh, lb), | |
| h, use_reentrant=False) | |
| else: | |
| ce = self._ce(h, labels) | |
| ce_losses.append(ce) | |
| all_rlogits.extend(rlogits) | |
| loss = None | |
| aux = None | |
| round_losses = None | |
| logits = None | |
| if labels is None: | |
| # 评测路径: 返回最终轮的 logits (训练路径 labels 恒非 None, 不受影响) | |
| logits = self.lm_head(self.model.norm(h)) / self.logits_scaling | |
| if labels is not None: | |
| loss = torch.stack(ce_losses).mean() | |
| round_losses = [c.detach() for c in ce_losses] # 监控用: 各轮 CE 分开看 | |
| if all_rlogits: | |
| aux = load_balancing_loss_func( | |
| tuple(all_rlogits), self.num_experts, self.num_experts_per_tok) | |
| loss = loss + self.router_aux_loss_coef * aux | |
| return SimpleNamespace(loss=loss, logits=logits, aux_loss=aux, round_losses=round_losses) | |
| # ---------- 推理: KV cache 贪心生成 ---------- | |
| # 思路: 循环架构下, 过去位置在第 r 轮第 l 层的注意力输入是静态的(因果性保证 | |
| # 之后不再改变), 因此可以按 (round, layer) 缓存 K/V。预填充阶段复用标准前向, | |
| # 用 pre-hook 捕获每个注意力子层的输入并现算 K/V; 解码阶段每 token 只算当前 | |
| # 位置, 数学上与整序列重算等价(仅浮点运算次序差异)。 | |
| def _prefill_kv(self, input_ids): | |
| """跑一遍标准前向, 返回 (最后一位的 logits, cache[r][l]=(K,V))。""" | |
| captured = [] | |
| hooks = [] | |
| for layer in self.model.layers: | |
| hooks.append(layer.self_attn.register_forward_pre_hook( | |
| lambda m, args, kwargs: captured.append(kwargs["hidden_states"].detach()), | |
| with_kwargs=True)) | |
| try: | |
| out = self.forward(input_ids=input_ids) | |
| finally: | |
| for hk in hooks: | |
| hk.remove() | |
| L = len(self.model.layers) | |
| assert len(captured) == self.loop_rounds * L, \ | |
| f"hook 捕获数 {len(captured)} != 轮数×层数 {self.loop_rounds * L}" | |
| from transformers.models.granitemoe.modeling_granitemoe import apply_rotary_pos_emb | |
| bsz, seq = input_ids.shape | |
| pos_ids = torch.arange(seq, device=input_ids.device).unsqueeze(0) | |
| dummy = torch.zeros(1, seq, self.config.hidden_size, device=input_ids.device, | |
| dtype=self.model.embed_tokens.weight.dtype) | |
| cos, sin = self.model.rotary_emb(dummy, pos_ids) | |
| cache = [] | |
| for r in range(self.loop_rounds): | |
| rc = [] | |
| for l, layer in enumerate(self.model.layers): | |
| x = captured[r * L + l] | |
| sa = layer.self_attn | |
| hd, kvh = sa.head_dim, self.config.num_key_value_heads | |
| k = sa.k_proj(x).view(bsz, seq, kvh, hd).transpose(1, 2) | |
| v = sa.v_proj(x).view(bsz, seq, kvh, hd).transpose(1, 2) | |
| _, k = apply_rotary_pos_emb( | |
| torch.zeros(bsz, self.config.num_attention_heads, seq, hd, | |
| device=x.device, dtype=k.dtype), k, cos, sin) | |
| rc.append((k, v)) | |
| cache.append(rc) | |
| return out.logits[:, -1], cache | |
| def _attn_decode(self, layer, x, k_cache, v_cache, cos, sin): | |
| """解码阶段的单 token 注意力: x (b,1,d) 已加 cache 的 K/V (b,kvh,t,hd)。""" | |
| from transformers.models.granitemoe.modeling_granitemoe import apply_rotary_pos_emb | |
| sa = layer.self_attn | |
| b = x.shape[0] | |
| nh, kvh, hd = self.config.num_attention_heads, self.config.num_key_value_heads, sa.head_dim | |
| q = sa.q_proj(x).view(b, 1, nh, hd).transpose(1, 2) | |
| k = sa.k_proj(x).view(b, 1, kvh, hd).transpose(1, 2) | |
| v = sa.v_proj(x).view(b, 1, kvh, hd).transpose(1, 2) | |
| q, k = apply_rotary_pos_emb(q, k, cos, sin) | |
| K = torch.cat([k_cache, k], dim=2) | |
| V = torch.cat([v_cache, v], dim=2) | |
| g = nh // kvh | |
| Kf = K.repeat_interleave(g, dim=1) | |
| Vf = V.repeat_interleave(g, dim=1) | |
| w = (q @ Kf.transpose(-1, -2)) * sa.scaling | |
| o = w.softmax(-1) @ Vf # (b,nh,1,hd) | |
| o = o.transpose(1, 2).reshape(b, 1, nh * hd) | |
| return sa.o_proj(o), K, V | |
| def greedy_generate(self, input_ids, max_new_tokens=64, eos_token_id=None): | |
| """KV cache 贪心生成, 返回生成的 id 列表(不含 prompt)。仅支持 batch=1。""" | |
| assert input_ids.shape[0] == 1 | |
| dev = input_ids.device | |
| layers = self.model.layers | |
| mu = self.config.residual_multiplier | |
| L = len(layers) | |
| logits, cache = self._prefill_kv(input_ids) | |
| nxt = logits[0].argmax().item() | |
| out_ids = [] | |
| pos = input_ids.shape[1] | |
| for _ in range(max_new_tokens): | |
| if eos_token_id is not None and nxt == eos_token_id: | |
| break | |
| out_ids.append(nxt) | |
| pos_ids = torch.tensor([[pos]], device=dev) | |
| cos, sin = self.model.rotary_emb( | |
| torch.zeros(1, 1, self.config.hidden_size, device=dev, | |
| dtype=self.model.embed_tokens.weight.dtype), pos_ids) | |
| h = self.model.embed_tokens( | |
| torch.tensor([[nxt]], device=dev)) * self.model.embedding_multiplier | |
| b0 = h | |
| prev_blocks = [] | |
| for r in range(self.loop_rounds): | |
| if not self.use_attnres: | |
| for l, layer in enumerate(layers): | |
| f, K, V = self._attn_decode( | |
| layer, layer.input_layernorm(h), cache[r][l][0], cache[r][l][1], cos, sin) | |
| cache[r][l] = (K, V) | |
| h = h + mu * f | |
| f, _ = self._sublayer(layer, "moe", h, None, None) | |
| h = h + mu * f | |
| else: | |
| fixed = [b0] + (list(prev_blocks) if self.cross_round else []) | |
| block_sums = [] | |
| for blk in range(self.attnres_blocks): | |
| blk_layers = layers[blk * self.layers_per_block:(blk + 1) * self.layers_per_block] | |
| q_blk = self.pseudo_queries[r, | |
| blk * self.sub_per_block:(blk + 1) * self.sub_per_block] | |
| cat = torch.stack(fixed) | |
| inv_rms = torch.rsqrt(cat.float().pow(2).mean(-1) + 1e-6) | |
| block_sum = None | |
| for j, layer in enumerate(blk_layers): | |
| for kind in ("attn", "moe"): | |
| q = q_blk[2 * j] if kind == "attn" else q_blk[2 * j + 1] | |
| block_sum, _ = self._attnres_step_decode( | |
| layer, kind, q, cat, inv_rms, block_sum, cos, sin, mu, | |
| cache, r, blk * self.layers_per_block + j) | |
| fixed.append(block_sum) | |
| block_sums.append(block_sum) | |
| final_sources = [b0] + (list(prev_blocks) if self.cross_round else []) + block_sums | |
| q_final = self.pseudo_queries[r, 2 * L] | |
| h = self._aggregate(q_final, final_sources) | |
| if self.cross_round: | |
| prev_blocks = prev_blocks + block_sums | |
| logits = self.lm_head(self.model.norm(h)) / self.logits_scaling | |
| nxt = logits[0, -1].argmax().item() | |
| pos += 1 | |
| return out_ids | |
| def _attnres_step_decode(self, layer, kind, q, cat, inv_rms, block_sum, cos, sin, mu, | |
| cache, r, l): | |
| """_attnres_step 的解码版: 聚合 + 单个子层(单 token, 注意力走 cache)。""" | |
| logits = torch.einsum("d,sbtd->sbt", q, cat) * inv_rms | |
| if block_sum is not None: | |
| bs_inv = torch.rsqrt(block_sum.float().pow(2).mean(-1) + 1e-6) | |
| bs_logit = torch.einsum("d,btd->bt", q, block_sum) * bs_inv | |
| alpha = torch.cat([logits, bs_logit.unsqueeze(0)], dim=0).softmax(dim=0) | |
| else: | |
| alpha = logits.softmax(dim=0) | |
| alpha = alpha.to(cat.dtype) | |
| if block_sum is not None: | |
| h = torch.einsum("sbt,sbtd->btd", alpha[:-1], cat) + alpha[-1].unsqueeze(-1) * block_sum | |
| else: | |
| h = torch.einsum("sbt,sbtd->btd", alpha, cat) | |
| if kind == "attn": | |
| f, K, V = self._attn_decode( | |
| layer, layer.input_layernorm(h), cache[r][l][0], cache[r][l][1], cos, sin) | |
| cache[r][l] = (K, V) | |
| rl = None | |
| else: | |
| f, rl = self._sublayer(layer, "moe", h, None, None) | |
| upd = mu * f | |
| return (upd if block_sum is None else block_sum + upd), rl | |