Ericu950 commited on
Commit
35464ff
·
verified ·
1 Parent(s): 96b4fd6

Upload modeling_char_bert_meter.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. modeling_char_bert_meter.py +243 -0
modeling_char_bert_meter.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HF-Hub-compatible model for CharDiff-grc-meter (macronization + metrical scansion).
2
+
3
+ Self-contained: vendors the same transformer primitives as modeling_char_bert.py, plus
4
+ the fine-tune-only additions meter/model.py::MeterModel and meter/backbone.py::
5
+ CharBertWithHidden make on top of the plain backbone:
6
+ - a zero-init `cap_emb` capitalization input embedding (fine-tune-only; base
7
+ pretraining treats capitalization as output-only)
8
+ - an ELMo-style learned scalar mix over every block's output (+ the final normed
9
+ hidden state) instead of using only the last layer
10
+ - two extra per-letter heads: `head_mac` (2-way: long/short vowel quantity) and
11
+ `head_scan` (4-way: none/heavy/light/verse-final syllable weight)
12
+
13
+ The submodule layout (`self.encoder.*` for the frozen backbone, `head_mac`/
14
+ `head_scan`/`mix_w` at the top level) matches meter.model.MeterModel's real state
15
+ dict exactly -- converted checkpoints load with strict=True and no key remapping.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from typing import Optional
21
+
22
+ import torch
23
+ import torch.nn as nn
24
+ import torch.nn.functional as F
25
+ from transformers import PreTrainedModel
26
+ from transformers.modeling_outputs import ModelOutput
27
+
28
+ from .configuration_char_bert_meter import CharBertMeterConfig
29
+
30
+
31
+ class RMSNorm(nn.Module):
32
+ def __init__(self, d, eps=1e-6):
33
+ super().__init__()
34
+ self.w = nn.Parameter(torch.ones(d))
35
+ self.eps = eps
36
+
37
+ def forward(self, x):
38
+ x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
39
+ return x * self.w
40
+
41
+
42
+ class RoPE(nn.Module):
43
+ def __init__(self, dim, base=10000.0):
44
+ super().__init__()
45
+ self.dim = dim
46
+ self.base = base
47
+
48
+ def cos_sin(self, pos):
49
+ # Recomputed on every call rather than cached in a registered buffer: a
50
+ # persistent=False buffer is never covered by the checkpoint's state dict,
51
+ # so it depends entirely on __init__-time materialization -- which some
52
+ # transformers versions' meta-device/low_cpu_mem_usage loading path can
53
+ # skip, silently leaving this tensor uninitialized. Recomputing here is
54
+ # immune to that regardless of how the model was constructed/loaded.
55
+ inv = 1.0 / (self.base ** (torch.arange(0, self.dim, 2, device=pos.device).float() / self.dim))
56
+ f = torch.outer(pos.float(), inv)
57
+ emb = torch.cat([f, f], -1)
58
+ return emb.cos(), emb.sin()
59
+
60
+
61
+ def _rotate_half(x):
62
+ d = x.shape[-1] // 2
63
+ return torch.cat([-x[..., d:], x[..., :d]], -1)
64
+
65
+
66
+ def apply_rope(q, k, cos, sin):
67
+ cos = cos[None, None]
68
+ sin = sin[None, None]
69
+ return q * cos + _rotate_half(q) * sin, k * cos + _rotate_half(k) * sin
70
+
71
+
72
+ class Attention(nn.Module):
73
+ def __init__(self, d, n_heads, rope: RoPE, qk_norm=False):
74
+ super().__init__()
75
+ self.h = n_heads
76
+ self.dh = d // n_heads
77
+ self.qkv = nn.Linear(d, 3 * d, bias=False)
78
+ self.o = nn.Linear(d, d, bias=False)
79
+ self.rope = rope
80
+ self.qk_norm = qk_norm
81
+ if qk_norm:
82
+ self.q_norm = RMSNorm(self.dh)
83
+ self.k_norm = RMSNorm(self.dh)
84
+
85
+ def forward(self, x, pos, attn_mask):
86
+ B, T, D = x.shape
87
+ qkv = self.qkv(x).view(B, T, 3, self.h, self.dh).permute(2, 0, 3, 1, 4)
88
+ q, k, v = qkv[0], qkv[1], qkv[2]
89
+ if self.qk_norm:
90
+ q, k = self.q_norm(q), self.k_norm(k)
91
+ cos, sin = self.rope.cos_sin(pos)
92
+ cos, sin = cos.to(x.dtype), sin.to(x.dtype)
93
+ q, k = apply_rope(q, k, cos, sin)
94
+ out = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask)
95
+ out = out.transpose(1, 2).reshape(B, T, D)
96
+ return self.o(out)
97
+
98
+
99
+ class GeGLU(nn.Module):
100
+ def __init__(self, d, mult=8 / 3):
101
+ super().__init__()
102
+ hidden = int(d * mult)
103
+ hidden = (hidden + 63) // 64 * 64
104
+ self.wi = nn.Linear(d, 2 * hidden, bias=False)
105
+ self.wo = nn.Linear(hidden, d, bias=False)
106
+
107
+ def forward(self, x):
108
+ a, b = self.wi(x).chunk(2, -1)
109
+ return self.wo(F.gelu(a) * b)
110
+
111
+
112
+ class Block(nn.Module):
113
+ def __init__(self, d, n_heads, rope, window=0, qk_norm=False):
114
+ super().__init__()
115
+ self.n1 = RMSNorm(d)
116
+ self.attn = Attention(d, n_heads, rope, qk_norm=qk_norm)
117
+ self.n2 = RMSNorm(d)
118
+ self.mlp = GeGLU(d)
119
+ self.window = window # 0 = global; >0 = local sliding window (characters)
120
+
121
+ def forward(self, x, pos, base_mask):
122
+ x = x + self.attn(self.n1(x), pos, base_mask)
123
+ x = x + self.mlp(self.n2(x))
124
+ return x
125
+
126
+
127
+ def build_attn_mask(seg_id, window, device, dtype):
128
+ """Additive mask (B,1,T,T): same-segment AND (window==0 or |i-j|<window)."""
129
+ B, T = seg_id.shape
130
+ same = seg_id[:, None, :] == seg_id[:, :, None]
131
+ if window and window > 0:
132
+ idx = torch.arange(T, device=device)
133
+ near = (idx[None, :] - idx[:, None]).abs() < window
134
+ same = same & near[None]
135
+ mask = torch.zeros(B, 1, T, T, dtype=dtype, device=device)
136
+ mask.masked_fill_(~same[:, None], float("-inf"))
137
+ return mask
138
+
139
+
140
+ class _MeterEncoder(nn.Module):
141
+ """Same submodule names/shapes as a plain CharBertEncoder (so a pretraining
142
+ backbone loads into it with no remapping), plus an optional zero-init cap_emb
143
+ and per-layer output collection for the scalar mix -- mirrors
144
+ meter.backbone.CharBertWithHidden exactly."""
145
+
146
+ def __init__(self, config: CharBertMeterConfig):
147
+ super().__init__()
148
+ self.e_char = nn.Embedding(config.n_char_ids, config.d_model)
149
+ self.e_bnd = nn.Embedding(config.n_boundary, config.d_model)
150
+ self.e_dia = nn.Embedding(config.n_dia, config.d_model)
151
+ self.e_punct = nn.Embedding(config.n_punct, config.d_model)
152
+ if config.use_cap:
153
+ self.cap_emb = nn.Embedding(2, config.d_model)
154
+ rope = RoPE(config.d_model // config.n_heads)
155
+ blocks = []
156
+ for i in range(config.depth):
157
+ win = 0 if i % 4 == 3 else config.char_window # 3 local : 1 global
158
+ blocks.append(Block(config.d_model, config.n_heads, rope, window=win, qk_norm=config.qk_norm))
159
+ self.blocks = nn.ModuleList(blocks)
160
+ self.norm_out = RMSNorm(config.d_model)
161
+ # frozen pretraining output heads: not used by the meter heads, but part of
162
+ # the backbone's real state dict (kept so a pretraining checkpoint -- or this
163
+ # converted meter checkpoint -- loads with strict=True)
164
+ self.head_char = nn.Linear(config.d_model, config.n_char_ids, bias=False)
165
+ self.head_bnd = nn.Linear(config.d_model, 3, bias=False)
166
+ self.head_dia = nn.Linear(config.d_model, 48, bias=False)
167
+ self.head_cap = nn.Linear(config.d_model, 2, bias=False)
168
+ self.head_punct = nn.Linear(config.d_model, 6, bias=False)
169
+ self.cfg = config
170
+
171
+ def forward(self, input_ids, boundary, dia, punct, cap=None, seg_id=None, collect_layers=False):
172
+ cfg = self.cfg
173
+ B, T = input_ids.shape
174
+ pos = torch.arange(T, device=input_ids.device)
175
+ seg = seg_id if seg_id is not None else torch.zeros(B, T, dtype=torch.long, device=input_ids.device)
176
+
177
+ x = self.e_char(input_ids) + self.e_bnd(boundary) + self.e_dia(dia) + self.e_punct(punct)
178
+ cap_emb = getattr(self, "cap_emb", None)
179
+ if cap_emb is not None and cap is not None:
180
+ x = x + cap_emb(cap)
181
+
182
+ attn_mask = build_attn_mask(seg, cfg.char_window, input_ids.device, x.dtype)
183
+ glob_mask = build_attn_mask(seg, 0, input_ids.device, x.dtype)
184
+
185
+ layers = []
186
+ for blk in self.blocks:
187
+ m = glob_mask if blk.window == 0 else attn_mask
188
+ x = blk(x, pos, m)
189
+ if collect_layers:
190
+ layers.append(x)
191
+ x = self.norm_out(x)
192
+ return layers, x
193
+
194
+
195
+ @dataclass
196
+ class CharBertMeterOutput(ModelOutput):
197
+ mac: torch.FloatTensor = None
198
+ scan: torch.FloatTensor = None
199
+
200
+
201
+ class CharBertMeterModel(PreTrainedModel):
202
+ config_class = CharBertMeterConfig
203
+
204
+ def __init__(self, config: CharBertMeterConfig):
205
+ super().__init__(config)
206
+ self.encoder = _MeterEncoder(config)
207
+ self.head_mac = nn.Linear(config.d_model, 2, bias=False) # 0=long, 1=short
208
+ self.head_scan = nn.Linear(config.d_model, 4, bias=False) # 0=none,1=heavy,2=light,3=verse-final
209
+ if config.scalar_mix:
210
+ self.mix_w = nn.Parameter(torch.zeros(config.depth + 1))
211
+ self.post_init()
212
+
213
+ def _init_weights(self, module):
214
+ if isinstance(module, nn.Linear):
215
+ nn.init.normal_(module.weight, std=0.02)
216
+ elif isinstance(module, nn.Embedding):
217
+ nn.init.normal_(module.weight, std=0.02)
218
+
219
+ def forward(
220
+ self,
221
+ input_ids: torch.LongTensor,
222
+ boundary: torch.LongTensor,
223
+ dia: torch.LongTensor,
224
+ punct: torch.LongTensor,
225
+ cap: Optional[torch.LongTensor] = None,
226
+ seg_id: Optional[torch.LongTensor] = None,
227
+ return_dict: bool = True,
228
+ **kwargs,
229
+ ):
230
+ collect = bool(self.config.scalar_mix)
231
+ layers, x = self.encoder(input_ids, boundary, dia, punct, cap=cap, seg_id=seg_id,
232
+ collect_layers=collect)
233
+ if self.config.scalar_mix:
234
+ h = torch.stack(layers + [x]) # (L+1, B, T, D)
235
+ mix = torch.softmax(self.mix_w, 0)
236
+ h = torch.einsum("l,lbtd->btd", mix.to(h.dtype), h)
237
+ else:
238
+ h = x
239
+ mac = self.head_mac(h)
240
+ scan = self.head_scan(h)
241
+ if not return_dict:
242
+ return (mac, scan)
243
+ return CharBertMeterOutput(mac=mac, scan=scan)