SpXMerlin1D's picture
Upload folder using huggingface_hub
09ccad2 verified
Raw
History Blame Contribute Delete
9.73 kB
"""H3 文本编码器替换适配器(接口蒸馏):source_projection + CrossAttention Resampler + TokenRefiner。
前向:
h3_ids [B, S_T] --QueryEmbedding--> Q [B, S_T, 5376]
student_hidden [B, S_S, 2560] --source_projection--> KV [B, S_S, 5376]
CrossAttentionBlock(Q, KV) -> [B, S_T, 5376]
TokenRefiner(2 层, 原权重初始化) -> [B, S_T, 5376] # 与教师 target 同坐标系
TokenRefiner 结构严格复刻原始 checkpoint(已源码核实):
- fused qkv (chunk(3)) + per-head qk_norm + 双向注意力 + out_proj,全部 bias=False
- SwiGLU MLP: fc1 为 fused [gate; value](gate 在前),fc2(silu(gate)*value)
- 2 个 pre-norm block + final RMSNorm,eps 全部 1e-5
"""
from __future__ import annotations
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
H3_VOCAB = 151936
H3_HIDDEN = 5376
REFINER_HEADS = 56
REFINER_HEAD_DIM = 128
REFINER_FFN = 14336
STUDENT_HIDDEN = 2560
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-5):
super().__init__()
self.weight = nn.Parameter(torch.ones(dim))
self.eps = eps
def forward(self, x: torch.Tensor) -> torch.Tensor:
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
class SwiGLUFFN(nn.Module):
def __init__(self, hidden: int, ffn: int):
super().__init__()
self.fc1 = nn.Linear(hidden, 2 * ffn, bias=False)
self.fc2 = nn.Linear(ffn, hidden, bias=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
gate, value = self.fc1(x).chunk(2, dim=-1)
return self.fc2(F.silu(gate) * value)
class TokenRefinerAttention(nn.Module):
def __init__(self, hidden: int, heads: int, dim_head: int):
super().__init__()
self.heads = heads
self.head_dim = dim_head
self.inner_dim = heads * dim_head
self.qkv_proj = nn.Linear(hidden, 3 * self.inner_dim, bias=False)
self.q_norm = RMSNorm(dim_head)
self.k_norm = RMSNorm(dim_head)
self.out_proj = nn.Linear(self.inner_dim, hidden, bias=False)
self.use_sdpa = True # SDPA flash: 注意力内存 O(S²)->O(S),数值与手写注意力差 ~1e-3(蒸馏噪声级)
def forward(self, x: torch.Tensor) -> torch.Tensor:
q, k, v = self.qkv_proj(x).chunk(3, dim=-1)
q = self.q_norm(q.unflatten(-1, (self.heads, self.head_dim)).transpose(1, 2))
k = self.k_norm(k.unflatten(-1, (self.heads, self.head_dim)).transpose(1, 2))
v = v.unflatten(-1, (self.heads, self.head_dim)).transpose(1, 2)
if self.use_sdpa:
out = F.scaled_dot_product_attention(q, k, v, scale=self.head_dim ** -0.5)
else:
attn = torch.softmax((q @ k.transpose(-2, -1)) * (self.head_dim ** -0.5), dim=-1)
out = attn @ v
return self.out_proj(out.transpose(1, 2).flatten(2))
class TokenRefinerBlock(nn.Module):
def __init__(self, hidden: int, heads: int, dim_head: int, ffn: int):
super().__init__()
self.norm1 = RMSNorm(hidden)
self.attn = TokenRefinerAttention(hidden, heads, dim_head)
self.norm2 = RMSNorm(hidden)
self.mlp = SwiGLUFFN(hidden, ffn)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x + self.attn(self.norm1(x))
x = x + self.mlp(self.norm2(x))
return x
class TokenRefiner(nn.Module):
def __init__(self, num_layers: int = 2, hidden: int = H3_HIDDEN,
heads: int = REFINER_HEADS, dim_head: int = REFINER_HEAD_DIM, ffn: int = REFINER_FFN):
super().__init__()
self.blocks = nn.ModuleList([TokenRefinerBlock(hidden, heads, dim_head, ffn) for _ in range(num_layers)])
self.final_norm = RMSNorm(hidden)
def forward(self, x: torch.Tensor) -> torch.Tensor:
for block in self.blocks:
x = block(x)
return self.final_norm(x)
class CrossAttentionBlock(nn.Module):
"""跨空间交叉注意(学生 2560 投影空间 <-> 教师 5376 表示空间)。
训练稳定性升级(兼容旧权重加载):
- QK-norm: to_q/to_k 投影后对每头做 RMSNorm(Llama 3.2 vision 交叉注意惯例,
稳定跨空间注意力的 q/k 尺度失配)
- gated tanh: out_proj 输出经 tanh(gate) 门控(Flamingo 惯例),gate 初始 0 => 恒等
- attn dropout: 交叉注意 dropout 0.1(Emu3 防后期 collapse)
"""
def __init__(self, hidden: int = H3_HIDDEN, heads: int = 32, dim_head: int = 128, ffn: int = REFINER_FFN,
attn_dropout: float = 0.1, use_qk_norm: bool = True, use_gate: bool = True):
super().__init__()
self.heads = heads
self.head_dim = dim_head
self.inner_dim = heads * dim_head
self.norm_q = RMSNorm(hidden)
self.norm_kv = RMSNorm(hidden)
self.to_q = nn.Linear(hidden, self.inner_dim, bias=False)
self.to_k = nn.Linear(hidden, self.inner_dim, bias=False)
self.to_v = nn.Linear(hidden, self.inner_dim, bias=False)
self.out_proj = nn.Linear(self.inner_dim, hidden, bias=False)
self.norm2 = RMSNorm(hidden)
self.mlp = SwiGLUFFN(hidden, ffn)
self.use_sdpa = True
self.attn_dropout = attn_dropout
self.use_qk_norm = use_qk_norm
if use_qk_norm:
self.qk_norm = RMSNorm(dim_head)
self.use_gate = use_gate
if use_gate:
self.gate = nn.Parameter(torch.zeros(1)) # tanh(0)=0 -> 恒等,兼容旧权重
def forward(self, q: torch.Tensor, kv: torch.Tensor) -> torch.Tensor:
qn = self.norm_q(q)
kvn = self.norm_kv(kv)
qh = self.to_q(qn).unflatten(-1, (self.heads, self.head_dim)).transpose(1, 2)
kh = self.to_k(kvn).unflatten(-1, (self.heads, self.head_dim)).transpose(1, 2)
vh = self.to_v(kvn).unflatten(-1, (self.heads, self.head_dim)).transpose(1, 2)
if self.use_qk_norm:
qh = self.qk_norm(qh)
kh = self.qk_norm(kh)
if self.use_sdpa:
out = F.scaled_dot_product_attention(qh, kh, vh, scale=self.head_dim ** -0.5,
dropout_p=self.attn_dropout if self.training else 0.0)
else:
attn = torch.softmax((qh @ kh.transpose(-2, -1)) * (self.head_dim ** -0.5), dim=-1)
out = attn @ vh
proj = self.out_proj(out.transpose(1, 2).flatten(2))
x = q + (torch.tanh(self.gate) * proj if self.use_gate else proj)
x = x + self.mlp(self.norm2(x))
return x
class QueryEmbedding(nn.Module):
def __init__(self, vocab: int = H3_VOCAB, dim: int = 256, out: int = H3_HIDDEN):
super().__init__()
self.embed = nn.Embedding(vocab, dim)
self.proj = nn.Linear(dim, out, bias=True)
def forward(self, ids: torch.Tensor) -> torch.Tensor:
return self.proj(self.embed(ids))
class H3Adapter(nn.Module):
"""完整适配器。param 约 1.14B(source_proj 13.8M + query_embed 40.3M + crossattn 319M + refiner 751M)。"""
def __init__(self):
super().__init__()
self.source_projection = nn.Linear(STUDENT_HIDDEN, H3_HIDDEN, bias=True)
self.query_embedding = QueryEmbedding()
self.cross_attention = CrossAttentionBlock()
self.token_refiner = TokenRefiner()
def forward(self, h3_ids: torch.Tensor, student_hidden: torch.Tensor) -> torch.Tensor:
kv = self.source_projection(student_hidden)
q = self.query_embedding(h3_ids)
x = self.cross_attention(q, kv)
x = self.token_refiner(x)
return x
def load_token_refiner(self, state_dict: dict[str, torch.Tensor], strict: bool = True) -> None:
refiner_state = {
k[len("token_refiner."):]: v
for k, v in state_dict.items()
if k.startswith("token_refiner.")
}
missing, unexpected = self.token_refiner.load_state_dict(refiner_state, strict=strict)
assert not missing and not unexpected, f"refiner load: missing={missing} unexpected={unexpected}"
def trainable_modules(self, stage: int) -> list[nn.Parameter]:
if stage == 1:
return (
list(self.source_projection.parameters())
+ list(self.query_embedding.parameters())
+ list(self.cross_attention.parameters())
)
return list(self.parameters())
class TeacherHead(nn.Module):
"""教师 target 计算: h50 -> condition_proj(5120->5376) -> token_refiner -> [B, S_T, 5376]。"""
def __init__(self):
super().__init__()
self.condition_proj = nn.Linear(5120, H3_HIDDEN, bias=True)
self.token_refiner = TokenRefiner()
def forward(self, h50: torch.Tensor) -> torch.Tensor:
return self.token_refiner(self.condition_proj(h50))
def compute_query_embed_init(embed_tokens: torch.Tensor, dim: int = 256, seed: int = 0) -> torch.Tensor:
"""query embedding 初始化: embed.weight = E @ P,P 为 5120->dim 的随机投影。
仅用于一次性初始化,后续可训练。embed_tokens 为 [151936, 5120](BF16 或 FP32)。
"""
assert embed_tokens.dim() == 2 and embed_tokens.shape[1] == 5120, embed_tokens.shape
rng = torch.Generator().manual_seed(seed)
p = torch.randn(5120, dim, generator=rng) * (1.0 / math.sqrt(5120))
embed_tokens = embed_tokens.to(torch.float32)
chunks = 8
out = torch.empty(embed_tokens.shape[0], dim, dtype=torch.float32)
for i in range(chunks):
lo = i * embed_tokens.shape[0] // chunks
hi = (i + 1) * embed_tokens.shape[0] // chunks
out[lo:hi] = embed_tokens[lo:hi] @ p
return out.to(torch.bfloat16)