residual-v2-tiny / modeling_residualnet_v2.py
if001's picture
Upload folder using huggingface_hub
178a1fe verified
# coding: utf-8
from typing import Optional, List, Tuple
import torch
import torch.nn.functional as F
from torch import nn
from transformers.models.phi3.configuration_phi3 import Phi3Config
from transformers.models.phi3.modeling_phi3 import (
Phi3PreTrainedModel,
Phi3RMSNorm,
Phi3MLP,
Phi3Attention,
# Phi3SdpaAttention,
Phi3RotaryEmbedding,
)
#from models.phi3_config import Phi3Config
#from models.phi3 import (
# Phi3PreTrainedModel,
# Phi3RMSNorm,
# Phi3MLP,
# # Phi3SdpaAttention,
# Phi3Attention,
# Phi3RotaryEmbedding,
#)
from transformers.modeling_attn_mask_utils import _prepare_4d_causal_attention_mask
from transformers.modeling_outputs import BaseModelOutput, CausalLMOutputWithPast
from transformers.generation.utils import GenerationMixin
class ResidualNetV2Config(Phi3Config):
model_type = "ResidualNetV2Config"
def __init__(self, **kwargs):
super().__init__(**kwargs)
# ==============
# helpers (差分/マスク, 形状ユーティリティ, optional RoPE)
# ==============
def first_order_diff(x: torch.Tensor) -> torch.Tensor:
# (B,L,H) -> (B,L-1,H)
return x[:, 1:, :] - x[:, :-1, :]
def second_order_diff(x: torch.Tensor) -> torch.Tensor:
# (B,L,H) -> (B,L-2,H): x_{t+2} - 2 x_{t+1} + x_t
return x[:, 2:, :] - 2.0 * x[:, 1:-1, :] + x[:, :-2, :]
def mask_and(*tensors: torch.Tensor) -> torch.Tensor:
# AND を丁寧に(float/bool いずれでも可)
out = tensors[0].bool()
for t in tensors[1:]:
out = out & t.bool()
return out.to(tensors[0].dtype)
def build_mask_for_diff(mask2d: Optional[torch.Tensor], order: int) -> Optional[torch.Tensor]:
if mask2d is None:
return None
if order == 0:
return mask2d
elif order == 1:
return mask_and(mask2d[:, 1:], mask2d[:, :-1])
elif order == 2:
return mask_and(mask2d[:, 2:], mask2d[:, 1:-1], mask2d[:, :-2])
else:
raise ValueError("order must be 0,1,2")
def shape_qkv(x: torch.Tensor, num_heads: int) -> torch.Tensor:
# (B,L,H) -> (B,heads,L,head_dim)
B, L, H = x.shape
head_dim = H // num_heads
x = x.view(B, L, num_heads, head_dim).transpose(1, 2) # (B,heads,L,head_dim)
return x
def unshape_ctx(x: torch.Tensor) -> torch.Tensor:
# (B,heads,L,head_dim) -> (B,L,H)
B, nH, L, d = x.shape
return x.transpose(1, 2).contiguous().view(B, L, nH * d)
# RoPE helper(必要な場合のみ使用)
def rotate_half(x):
x1, x2 = x[..., : x.shape[-1] // 2], x[..., x.shape[-1] // 2 :]
return torch.cat((-x2, x1), dim=-1)
def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
cos = cos.unsqueeze(unsqueeze_dim)
sin = sin.unsqueeze(unsqueeze_dim)
rotary_dim = cos.shape[-1]
q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
q_embed = torch.cat([(q_rot * cos) + (rotate_half(q_rot) * sin), q_pass], dim=-1)
k_embed = torch.cat([(k_rot * cos) + (rotate_half(k_rot) * sin), k_pass], dim=-1)
return q_embed, k_embed
# ==============
# Self-Block (Phi-3 部品そのまま)
# ==============
class Phi3SelfBlock(nn.Module):
"""
PreNorm -> Self-Attn(SDPA+RoPE) -> resid -> PreNorm -> MLP -> resid
"""
def __init__(self, config: ResidualNetV2Config, layer_idx: int, rotary_emb: Phi3RotaryEmbedding):
super().__init__()
self.config = config
self.layer_idx = layer_idx
self.in_norm = Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
# self.attn = Phi3SdpaAttention(config, layer_idx=layer_idx)
self.attn = Phi3Attention(config, layer_idx=layer_idx)
self.dropout_attn = nn.Dropout(config.resid_pdrop)
self.ff_norm = Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.mlp = Phi3MLP(config)
self.dropout_mlp = nn.Dropout(config.resid_pdrop)
self.rotary_emb = rotary_emb
def _prepare_4d_mask(self, mask2d, bsz, seqlen, hidden_states):
if mask2d is None:
return None
return _prepare_4d_causal_attention_mask(
mask2d, (bsz, seqlen), hidden_states, past_key_values_length=0, sliding_window=self.config.sliding_window
)
def forward(
self,
hidden_states: torch.Tensor, # (B,L,H)
mask2d: Optional[torch.Tensor], # (B,L)
position_ids: Optional[torch.Tensor],# unused here; we re-make per len
output_attentions: bool = False,
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
x = self.in_norm(hidden_states)
B, L, _ = x.shape
pos = torch.arange(L, device=x.device).unsqueeze(0).expand(B, -1)
attn_mask = self._prepare_4d_mask(mask2d, B, L, x)
position_embeddings = self.rotary_emb(hidden_states, pos)
attn_out, attn_weights = self.attn(
hidden_states=x,
attention_mask=attn_mask,
position_ids=pos,
position_embeddings=position_embeddings,
past_key_value=None,
output_attentions=output_attentions,
use_cache=False,
)
x = hidden_states + self.dropout_attn(attn_out)
h = self.ff_norm(x)
h = self.mlp(h)
x = x + self.dropout_mlp(h)
return x, (attn_weights if output_attentions else None)
# ==============
# Cross-Attention (シンプル実装/GQA対応。既定で RoPE 無し)
# ==============
class SimpleCrossAttention(nn.Module):
"""
Query: x_q (B,Lq,H) Key/Value: x_kv (B,Lk,H)
- num_heads / num_key_value_heads は Phi-3 と同じ設定に合わせる(GQA対応)
- 既定: RoPE 適用なし(decoder-encoder cross は相対位置の意味付けが曖昧なため)。
use_rope_in_cross_attn=True で RoPE を適用可能。
"""
def __init__(self, config: ResidualNetV2Config, rotary_emb: Phi3RotaryEmbedding, use_rope_in_cross_attn: bool = False):
super().__init__()
self.config = config
self.rotary_emb = rotary_emb
self.use_rope = use_rope_in_cross_attn
H = config.hidden_size
nH = config.num_attention_heads
nKV = getattr(config, "num_key_value_heads", nH)
self.nH = nH
self.nKV = nKV
self.groups = nH // nKV
self.head_dim = H // nH
self.q_proj = nn.Linear(H, H, bias=False)
self.k_proj = nn.Linear(H, nKV * self.head_dim, bias=False)
self.v_proj = nn.Linear(H, nKV * self.head_dim, bias=False)
self.o_proj = nn.Linear(H, H, bias=False)
self.dropout = nn.Dropout(config.attention_dropout)
# 追加正規化(安定のため)
self.q_norm = Phi3RMSNorm(H, eps=config.rms_norm_eps)
self.kv_norm = Phi3RMSNorm(H, eps=config.rms_norm_eps)
def _kv_repeat(self, x: torch.Tensor) -> torch.Tensor:
# (B, nKV, Lk, d) -> (B, nH, Lk, d) へ繰り返し
if self.nKV == self.nH:
return x
return x.repeat_interleave(self.groups, dim=1)
def _make_attn_mask_bool(self, enc_mask2d: Optional[torch.Tensor], Lq: int, Lk: int, B: int) -> Optional[torch.Tensor]:
# enc_mask2d: (B,Lk) in {0,1} -> broadcastable bool mask of shape (B,1,Lq,Lk)
if enc_mask2d is None:
return None
m = (~enc_mask2d.bool()).unsqueeze(1).unsqueeze(2) # True=mask
return m.expand(B, 1, Lq, Lk)
def forward(
self,
x_q: torch.Tensor, # (B,Lq,H)
x_kv: torch.Tensor, # (B,Lk,H)
enc_mask2d: Optional[torch.Tensor] = None, # (B,Lk)
) -> torch.Tensor:
B, Lq, H = x_q.shape
Lk = x_kv.size(1)
q = self.q_proj(self.q_norm(x_q)) # (B,Lq,H)
k = self.k_proj(self.kv_norm(x_kv)) # (B,Lk, nKV*Hd)
v = self.v_proj(self.kv_norm(x_kv)) # (B,Lk, nKV*Hd)
q = shape_qkv(q, self.nH) # (B,nH,Lq,Hd)
k = k.view(B, Lk, self.nKV, self.head_dim).transpose(1, 2) # (B,nKV,Lk,Hd)
v = v.view(B, Lk, self.nKV, self.head_dim).transpose(1, 2) # (B,nKV,Lk,Hd)
k = self._kv_repeat(k) # (B,nH,Lk,Hd)
v = self._kv_repeat(v) # (B,nH,Lk,Hd)
if self.use_rope:
# 参考実装:各系列長に合わせて cos/sin を取り、q/k に適用
# Phi3RotaryEmbedding の forward は (x, position_ids) -> (cos, sin) を返す前提
pos_q = torch.arange(Lq, device=x_q.device).unsqueeze(0).expand(B, -1)
pos_k = torch.arange(Lk, device=x_q.device).unsqueeze(0).expand(B, -1)
# ダミーの [B,L,head_dim] を渡して cos/sin を得る(実装に依存するため try/except)
try:
dummy_q = torch.zeros(B, Lq, self.head_dim, device=x_q.device, dtype=x_q.dtype)
dummy_k = torch.zeros(B, Lk, self.head_dim, device=x_q.device, dtype=x_q.dtype)
cos_q, sin_q = self.rotary_emb(dummy_q, pos_q)
cos_k, sin_k = self.rotary_emb(dummy_k, pos_k)
q, k = apply_rotary_pos_emb(q, k, cos_q, sin_k, position_ids=None, unsqueeze_dim=2)
except Exception:
# RoPE 未対応環境では静かにスキップ(Self-Attn 側で RoPE が効いていれば全体としては相対位置信号を保持)
pass
# scaled dot-product attention
attn_mask = self._make_attn_mask_bool(enc_mask2d, Lq, Lk, B) # True=mask
y = F.scaled_dot_product_attention(
q, k, v, attn_mask=attn_mask, dropout_p=self.dropout.p if self.training else 0.0, is_causal=False
) # (B,nH,Lq,Hd)
y = unshape_ctx(y) # (B,Lq,H)
y = self.o_proj(y)
return y
# ==============
# v2: 3本の枝を「各3層」通してから、0階に Cross-Attn(←1階) → Cross-Attn(←2階)
# ==============
class ResidualNetV2Model(Phi3PreTrainedModel):
"""
1) embedding -> 0/1/2階差分 3枝
2) 各枝: [SelfBlock] x 3 (同一枝内で3層)
3) x0 に CrossAttn(x1_final) → residual、続けて CrossAttn(x2_final) → residual
出力は x0(原系列長 L)
"""
def __init__(self, config: ResidualNetV2Config, use_rope_in_cross_attn: bool = False):
super().__init__(config)
self.padding_idx = config.pad_token_id
self.vocab_size = config.vocab_size
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
self.rotary_emb = Phi3RotaryEmbedding(config=config)
self.norm_out = Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
# 各枝 3 層
self.branch0 = nn.ModuleList([Phi3SelfBlock(config, layer_idx=i, rotary_emb=self.rotary_emb) for i in range(3)])
self.branch1 = nn.ModuleList([Phi3SelfBlock(config, layer_idx=100+i, rotary_emb=self.rotary_emb) for i in range(3)])
self.branch2 = nn.ModuleList([Phi3SelfBlock(config, layer_idx=200+i, rotary_emb=self.rotary_emb) for i in range(3)])
# Cross-Attn × 2 (0階 <- 1階, 0階 <- 2階)
self.cross01_norm = Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.cross01 = SimpleCrossAttention(config, self.rotary_emb, use_rope_in_cross_attn)
self.cross12_norm = Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.cross02 = SimpleCrossAttention(config, self.rotary_emb, use_rope_in_cross_attn)
self.dropout = nn.Dropout(config.resid_pdrop)
self.post_init()
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
attention_mask: Optional[torch.Tensor] = None, # (B,L)
inputs_embeds: Optional[torch.FloatTensor] = None,
output_attentions: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
) -> BaseModelOutput:
output_attentions = False if output_attentions is None else output_attentions
output_hidden_states = False if output_hidden_states is None else output_hidden_states
return_dict = True if return_dict is None else return_dict
if inputs_embeds is None:
x0 = self.embed_tokens(input_ids) # (B,L,H) 0階
else:
x0 = inputs_embeds
B, L, H = x0.shape
m0 = attention_mask if attention_mask is not None else torch.ones(B, L, device=x0.device, dtype=torch.long)
# 1階/2階差分
x1 = first_order_diff(x0) # (B,L-1,H)
x2 = second_order_diff(x0) # (B,L-2,H)
m1 = build_mask_for_diff(m0, 1)
m2 = build_mask_for_diff(m0, 2)
# 各枝 3 層
for blk in self.branch0:
x0, _ = blk(x0, m0, None, output_attentions=False)
for blk in self.branch1:
x1, _ = blk(x1, m1, None, output_attentions=False)
for blk in self.branch2:
x2, _ = blk(x2, m2, None, output_attentions=False)
# CrossAttn: x0 <- x1
x0 = x0 + self.dropout(self.cross01(self.cross01_norm(x0), x1, enc_mask2d=m1))
# CrossAttn: x0 <- x2
x0 = x0 + self.dropout(self.cross02(self.cross12_norm(x0), x2, enc_mask2d=m2))
x_out = self.norm_out(x0)
if not return_dict:
return (x_out,)
return BaseModelOutput(
last_hidden_state=x_out,
hidden_states=None,
attentions=None,
)
class ResidualNetV2ForCausalLM(Phi3PreTrainedModel, GenerationMixin):
_tied_weights_keys = ["lm_head.weight"]
def __init__(self, config: ResidualNetV2Config, use_rope_in_cross_attn: bool = False):
super().__init__(config)
self.model = ResidualNetV2Model(config, use_rope_in_cross_attn=use_rope_in_cross_attn)
self.vocab_size = config.vocab_size
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
# weight tying
self.lm_head.weight = self.model.embed_tokens.weight
self.post_init()
def forward(
self,
input_ids=None,
attention_mask=None,
inputs_embeds=None,
labels: Optional[torch.LongTensor] = None,
**kwargs,
) -> CausalLMOutputWithPast:
out = self.model(
input_ids=input_ids,
attention_mask=attention_mask,
inputs_embeds=inputs_embeds,
output_attentions=kwargs.get("output_attentions", False),
output_hidden_states=kwargs.get("output_hidden_states", False),
return_dict=True,
)
logits = self.lm_head(out.last_hidden_state).float()
loss = None
if labels is not None:
shift_logits = logits[:, :-1, :].contiguous()
shift_labels = labels[:, 1:].contiguous()
loss_fct = nn.CrossEntropyLoss()
loss = loss_fct(shift_logits.view(-1, self.vocab_size), shift_labels.view(-1))
return CausalLMOutputWithPast(
loss=loss, logits=logits, past_key_values=None, hidden_states=None, attentions=None
)
@property
def base_model(self):
return self.model
# ==============
# v3: 「各枝1層 + x0<-x1 Cross + x0<-x2 Cross」を1ブロックとして **3回** 反復
# ==============
class ResidualNetV3Model(Phi3PreTrainedModel):
"""
1 block = { 3枝: SelfBlock各1層 → x0<-x1 Cross → x0<-x2 Cross }
これを 3 回繰り返す(早期融合 + 反復洗練)
"""
def __init__(self, config: ResidualNetV2Config, use_rope_in_cross_attn: bool = False):
super().__init__(config)
self.padding_idx = config.pad_token_id
self.vocab_size = config.vocab_size
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
self.rotary_emb = Phi3RotaryEmbedding(config=config)
self.norm_out = Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.dropout = nn.Dropout(config.resid_pdrop)
# 3 ブロック分の層を用意(枝それぞれ + CrossAttn×2)
self.blocks_branch0 = nn.ModuleList([Phi3SelfBlock(config, layer_idx=10+i, rotary_emb=self.rotary_emb) for i in range(3)])
self.blocks_branch1 = nn.ModuleList([Phi3SelfBlock(config, layer_idx=110+i, rotary_emb=self.rotary_emb) for i in range(3)])
self.blocks_branch2 = nn.ModuleList([Phi3SelfBlock(config, layer_idx=210+i, rotary_emb=self.rotary_emb) for i in range(3)])
self.cross_norm_01 = nn.ModuleList([Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps) for _ in range(3)])
self.cross_01 = nn.ModuleList([SimpleCrossAttention(config, self.rotary_emb, use_rope_in_cross_attn) for _ in range(3)])
self.cross_norm_02 = nn.ModuleList([Phi3RMSNorm(config.hidden_size, eps=config.rms_norm_eps) for _ in range(3)])
self.cross_02 = nn.ModuleList([SimpleCrossAttention(config, self.rotary_emb, use_rope_in_cross_attn) for _ in range(3)])
self.post_init()
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
attention_mask: Optional[torch.Tensor] = None,
inputs_embeds: Optional[torch.FloatTensor] = None,
output_attentions: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
) -> BaseModelOutput:
output_attentions = False if output_attentions is None else output_attentions
output_hidden_states = False if output_hidden_states is None else output_hidden_states
return_dict = True if return_dict is None else return_dict
if inputs_embeds is None:
x0 = self.embed_tokens(input_ids)
else:
x0 = inputs_embeds
B, L, H = x0.shape
m0 = attention_mask if attention_mask is not None else torch.ones(B, L, device=x0.device, dtype=torch.long)
# 初回の差分(1,2階)は x0 から
def mk_x1x2(x0, m0):
return first_order_diff(x0), second_order_diff(x0), build_mask_for_diff(m0, 1), build_mask_for_diff(m0, 2)
x1, x2, m1, m2 = mk_x1x2(x0, m0)
# 3 ブロック反復
for i in range(3):
# 各枝 1 層
x0, _ = self.blocks_branch0[i](x0, m0, None, output_attentions=False)
x1, _ = self.blocks_branch1[i](x1, m1, None, output_attentions=False)
x2, _ = self.blocks_branch2[i](x2, m2, None, output_attentions=False)
# Cross: x0 <- x1, ついで x0 <- x2
x0 = x0 + self.dropout(self.cross_01[i](self.cross_norm_01[i](x0), x1, enc_mask2d=m1))
x0 = x0 + self.dropout(self.cross_02[i](self.cross_norm_02[i](x0), x2, enc_mask2d=m2))
# 次ブロック用に 1/2階差分を「最新の x0」から再計算する手もある
# (Down(2) 的な早期融合→再分解の設計に合わせたい場合)
# ここでは「枝連鎖の継続」を優先し、x1/x2 は枝内の連続層として進める。
# もし再分解を望むなら下記を有効化:
# x1, x2, m1, m2 = mk_x1x2(x0, m0)
x_out = self.norm_out(x0)
if not return_dict:
return (x_out,)
return BaseModelOutput(
last_hidden_state=x_out,
hidden_states=None,
attentions=None,
)
class ResidualNetV3ForCausalLM(Phi3PreTrainedModel, GenerationMixin):
_tied_weights_keys = ["lm_head.weight"]
def __init__(self, config: ResidualNetV2Config, use_rope_in_cross_attn: bool = False):
super().__init__(config)
self.model = ResidualNetV3Model(config, use_rope_in_cross_attn=use_rope_in_cross_attn)
self.vocab_size = config.vocab_size
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
self.lm_head.weight = self.model.embed_tokens.weight
self.post_init()
def forward(
self,
input_ids=None,
attention_mask=None,
inputs_embeds=None,
labels: Optional[torch.LongTensor] = None,
**kwargs,
) -> CausalLMOutputWithPast:
out = self.model(
input_ids=input_ids,
attention_mask=attention_mask,
inputs_embeds=inputs_embeds,
output_attentions=kwargs.get("output_attentions", False),
output_hidden_states=kwargs.get("output_hidden_states", False),
return_dict=True,
)
logits = self.lm_head(out.last_hidden_state).float()
loss = None
if labels is not None:
shift_logits = logits[:, :-1, :].contiguous()
shift_labels = labels[:, 1:].contiguous()
loss_fct = nn.CrossEntropyLoss()
loss = loss_fct(shift_logits.view(-1, self.vocab_size), shift_labels.view(-1))
return CausalLMOutputWithPast(
loss=loss, logits=logits, past_key_values=None, hidden_states=None, attentions=None
)
@property
def base_model(self):
return self.model