Token Classification
Transformers
Safetensors
Ancient Greek (to 1453)
char_bert_meter
ancient-greek
classical-philology
character-level
macronization
metrical-scansion
custom_code
Instructions to use Ericu950/CharDiff-grc-meter with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Ericu950/CharDiff-grc-meter with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="Ericu950/CharDiff-grc-meter", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Ericu950/CharDiff-grc-meter", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Upload modeling_char_bert_meter.py with huggingface_hub
Browse files- 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)
|