tf-bao commited on
Commit
fdec8d5
·
verified ·
1 Parent(s): bcf3bbe

Upload BERTc-315M release

Browse files
README.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - zh
5
+ - en
6
+ tags:
7
+ - bert
8
+ - fill-mask
9
+ - chinese
10
+ - modernbert
11
+ - masked-language-modeling
12
+ pipeline_tag: fill-mask
13
+ library_name: pytorch
14
+ ---
15
+
16
+ # BERTc-315M
17
+
18
+ BERTc-315M is a char-level Chinese Modern BERTc masked language model trained from scratch.
19
+ It uses a custom ModernBERT-style PyTorch architecture from the BERTc repository, with
20
+ ScaledSinusoidal positional embeddings, GeGLU MLPs, no linear biases, tied input/output
21
+ embeddings, and a SentencePiece-based char/BPE tokenizer.
22
+
23
+ ## Model Details
24
+
25
+ - Parameters: 315M
26
+ - Architecture: 24L / 1024H / 2752I / 16 heads
27
+ - Vocabulary size: 12,536
28
+ - Max position length: 1,024
29
+ - Pretraining data: 17.65B-token BERTc mixed corpus
30
+ - License: Apache-2.0
31
+
32
+ ## Reported Downstream Results
33
+
34
+ These are internal BERTc evaluations using fine-tuned heads:
35
+
36
+ - PD-1998 CWS/POS/NER multi-task: score 1.4712 (CWS 0.9840 / POS 0.9800 / NER 0.9660)
37
+ - SIGHAN-15 Chinese spelling correction: SIGHAN-15 sentence F1 0.8346
38
+
39
+ Current strongest Modern BERTc backbone, trained on the full 17.65B-token corpus.
40
+
41
+ ## Files
42
+
43
+ - `model.safetensors`: `ModernBertForMLM` state dict.
44
+ - `config.json`: architecture configuration.
45
+ - `model.py`: model implementation used by the original training code.
46
+ - `piece.model`: tokenizer model; load with `piece_tokenizer` using `cn_dict="no"`.
47
+ - `mask_token_id.txt`: mask token id.
48
+
49
+ ## Loading
50
+
51
+ ```python
52
+ import json
53
+ import torch
54
+ from safetensors.torch import load_file
55
+ from model import ModernBertConfig, ModernBertForMLM
56
+
57
+ with open("config.json") as f:
58
+ cfg = ModernBertConfig(**json.load(f))
59
+
60
+ model = ModernBertForMLM(cfg)
61
+ state = load_file("model.safetensors")
62
+ model.load_state_dict(state, strict=True)
63
+ model.eval()
64
+ ```
65
+
66
+ Tokenization in the original code uses the sibling `piece_tokenizer` package:
67
+
68
+ ```python
69
+ import piece_tokenizer as pt
70
+
71
+ tok = pt.Tokenizer()
72
+ tok.load("piece.model", cn_dict="no")
73
+ ids = tok.encode_as_ids("中文测试")
74
+ ```
75
+
76
+ ## Intended Use
77
+
78
+ Use this model as a Chinese encoder/MLM backbone for fine-tuning tasks such as CWS,
79
+ POS, NER, and Chinese spelling correction. This release is not an instruction model
80
+ and is not intended for text generation.
__pycache__/model.cpython-314.pyc ADDED
Binary file (32.5 kB). View file
 
config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vocab_size": 12536,
3
+ "hidden_size": 1024,
4
+ "num_hidden_layers": 24,
5
+ "num_attention_heads": 16,
6
+ "intermediate_size": 2752,
7
+ "max_position_embeddings": 1024,
8
+ "pad_token_id": 12531,
9
+ "mask_token_id": 12535,
10
+ "pe_theta": 10000.0,
11
+ "layer_norm_eps": 1e-05,
12
+ "initializer_range": 0.02,
13
+ "tie_word_embeddings": true,
14
+ "embed_dropout": 0.0,
15
+ "mlp_dropout": 0.0,
16
+ "attn_out_dropout": 0.0,
17
+ "attn_probs_dropout": 0.0,
18
+ "embed_norm": true,
19
+ "skip_first_prenorm": true,
20
+ "final_norm": true,
21
+ "init_method": "megatron"
22
+ }
example_load.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import torch
3
+ from safetensors.torch import load_file
4
+ from model import ModernBertConfig, ModernBertForMLM
5
+
6
+
7
+ def load_bertc(model_dir="."):
8
+ with open(f"{model_dir}/config.json") as f:
9
+ cfg = ModernBertConfig(**json.load(f))
10
+ model = ModernBertForMLM(cfg)
11
+ state = load_file(f"{model_dir}/model.safetensors")
12
+ model.load_state_dict(state, strict=True)
13
+ model.eval()
14
+ return model
15
+
16
+
17
+ if __name__ == "__main__":
18
+ model = load_bertc(".")
19
+ ids = torch.tensor([[1, 2, 3]], dtype=torch.long)
20
+ with torch.no_grad():
21
+ out = model(ids)
22
+ print(out["logits"].shape)
mask_token_id.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 12535
model.py ADDED
@@ -0,0 +1,447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Modern BERTc v3 — ModernBERT release-aligned + Cramming-style ScaledSinusoidal PE.
2
+
3
+ 主要按 release `modernbert-base-pretrain.yaml` 对齐(除 Alt Attn 和 PE):
4
+ - 架构: 22L / 768H / 1152I (GLU) / 12 heads,head_dim=64
5
+ - **ScaledSinusoidal 位置编码**(Hua et al. 2022 FLASH;Cramming 实测短 seq 比
6
+ RoPE 更值:计算几乎免费,RoPE 收益被 5-10% 速度损失抵消)
7
+ - GeGLU FFN(glu + gelu)
8
+ - LayerNorm 无 bias(eps=1e-5),非 RMSNorm
9
+ - pre-norm 布局 + skip_first_prenorm
10
+ - embed_norm + final_norm
11
+ - Megatron-style init:残差层 W 缩 1/sqrt(2L)
12
+ - 全无 Linear bias
13
+ - Dropout: 全 0(Cramming 论据:short single-epoch 无 overfit risk)
14
+ - tied word embedding
15
+ - flex_attention compiled(支持 cross-doc 隔离 via seg_ids)
16
+
17
+ 不上的 ModernBERT 特性:
18
+ - Alternating Attention(我们走全局 attention)
19
+ - Unpadded packing + cu_seqlens(我们定长 pack)
20
+ - RoPE(换 ScaledSinusoidal,见 Cramming Section 4.2)
21
+
22
+ 参数量(默认 22L/768H/1152I,V=12536):
23
+ emb (tied) : 12536 × 768 ≈ 9.6M
24
+ embed_norm : 768 × 1 ≈ 1K(no bias)
25
+ per layer:
26
+ norm1/2 : 768 × 2 ≈ 1.5K
27
+ Q K V O : 4 × 768² ≈ 2.36M
28
+ GeGLU (W_in=2I, W_out): 768×2304 + 1152×768 ≈ 2.66M
29
+ total per layer : ≈ 5.0M
30
+ × 22 layers : ≈ 110M
31
+ final_norm : 768 × 1
32
+ head: dense + norm + gelu: 768×768 + 768 ≈ 0.59M
33
+ head_bias (V,) : 12.5K
34
+ total ≈ 130M
35
+ """
36
+ from dataclasses import dataclass, field
37
+ from typing import Optional
38
+ import math
39
+
40
+ import torch
41
+ import torch.nn as nn
42
+ import torch.nn.functional as F
43
+ from torch.nn.attention.flex_attention import (
44
+ flex_attention as _flex_attention_raw,
45
+ create_block_mask,
46
+ )
47
+
48
+ # torch.compile 是 lazy 的 — module import 不触发 trace,first call 时才编译。
49
+ # ModernBERT 源码用 mode="max-autotune-no-cudagraphs",我们用 default mode 平衡
50
+ # (first-call 编译几秒,vs max-autotune 可能几十秒)。
51
+ # 训练时默认调 _flex_attention(compiled);smoke 也走这条路径,保证统一。
52
+ _flex_attention = torch.compile(_flex_attention_raw)
53
+
54
+
55
+ # ============ Config ============
56
+
57
+ @dataclass
58
+ class ModernBertConfig:
59
+ vocab_size: int = 12536
60
+ hidden_size: int = 768
61
+ num_hidden_layers: int = 22
62
+ num_attention_heads: int = 12
63
+ intermediate_size: int = 1152
64
+ max_position_embeddings: int = 1024
65
+ pad_token_id: int = 12531
66
+ mask_token_id: int = 12535
67
+ pe_theta: float = 10000.0 # ScaledSinusoidal 频率 base(Vaswani 2017 默认)
68
+ layer_norm_eps: float = 1e-5
69
+ initializer_range: float = 0.02
70
+ tie_word_embeddings: bool = True
71
+ # Dropout(对齐 ModernBERT release)
72
+ embed_dropout: float = 0.0
73
+ mlp_dropout: float = 0.0
74
+ attn_out_dropout: float = 0.1
75
+ attn_probs_dropout: float = 0.0
76
+ # 架构开关(对齐 release)
77
+ embed_norm: bool = True # embedding 后立刻 LayerNorm
78
+ skip_first_prenorm: bool = True # 第 1 层不做 pre-norm
79
+ final_norm: bool = True # 最后一层后 LayerNorm
80
+ # init
81
+ init_method: str = "megatron" # "megatron"(残差层 ×1/sqrt(2L))或 "normal"
82
+
83
+ @property
84
+ def head_dim(self) -> int:
85
+ assert self.hidden_size % self.num_attention_heads == 0
86
+ return self.hidden_size // self.num_attention_heads
87
+
88
+
89
+ # ============ LayerNorm(no bias)============
90
+
91
+ class LayerNormNoBias(nn.Module):
92
+ """LayerNorm with weight only (no bias). 对齐 ModernBERT release。"""
93
+ def __init__(self, hidden_size: int, eps: float = 1e-5):
94
+ super().__init__()
95
+ self.weight = nn.Parameter(torch.ones(hidden_size))
96
+ self.eps = eps
97
+ self.normalized_shape = (hidden_size,)
98
+
99
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
100
+ return F.layer_norm(x, self.normalized_shape, self.weight, None, self.eps)
101
+
102
+
103
+ # ============ ScaledSinusoidal Position Embedding(Hua et al. 2022 / Cramming)============
104
+
105
+ class ScaledSinusoidalPE(nn.Module):
106
+ """Scaled sinusoidal positional embedding(Hua 2022 FLASH paper)。
107
+
108
+ 标准 sinusoidal:PE[pos, 2i]=sin(pos/θ^(2i/d)), PE[pos, 2i+1]=cos(...)。
109
+ `scale_factor` 是一个 learnable 标量,初始 1/sqrt(d)。
110
+ 用法:embedding 之后直接 `x = embed + pos_emb(input_ids)`,跟所有层共享。
111
+ 比 RoPE 便宜:只在 embedding 层 fire 一次,attention 里 0 开销。
112
+ """
113
+ def __init__(self, embedding_dim: int, max_seq_length: int, theta: float = 10000.0):
114
+ super().__init__()
115
+ pe = torch.zeros(max_seq_length, embedding_dim)
116
+ position = torch.arange(0, max_seq_length, dtype=torch.float).unsqueeze(1)
117
+ div_term = torch.exp(
118
+ torch.arange(0, embedding_dim, 2).float() * (-math.log(theta) / embedding_dim)
119
+ )
120
+ pe[:, 0::2] = torch.sin(position * div_term)
121
+ pe[:, 1::2] = torch.cos(position * div_term)
122
+ pe = pe.unsqueeze(0) # [1, L, d]
123
+ self.register_buffer("pe", pe, persistent=False)
124
+ self.scale_factor = nn.Parameter(torch.tensor([1.0 / embedding_dim ** 0.5]))
125
+
126
+ def forward(self, seq_len: int) -> torch.Tensor:
127
+ return self.scale_factor * self.pe[:, :seq_len, :]
128
+
129
+
130
+ # ============ Attention(bidirectional,无 RoPE,位置走 ScaledSinusoidal)============
131
+
132
+ class ModernBertAttention(nn.Module):
133
+ def __init__(self, config: ModernBertConfig):
134
+ super().__init__()
135
+ self.num_heads = config.num_attention_heads
136
+ self.head_dim = config.head_dim
137
+ self.scale = self.head_dim ** -0.5
138
+ self.attn_probs_dropout = config.attn_probs_dropout
139
+ # 无 bias
140
+ self.qkv = nn.Linear(config.hidden_size, 3 * config.hidden_size, bias=False)
141
+ self.o = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
142
+ self.out_dropout = nn.Dropout(config.attn_out_dropout)
143
+
144
+ def forward(self, x: torch.Tensor,
145
+ block_mask=None,
146
+ attention_mask: Optional[torch.Tensor] = None) -> torch.Tensor:
147
+ """三种 attention 模式:
148
+ - block_mask 非空 → flex_attention(block-diag,跨 doc 隔离,训练时)
149
+ - block_mask 空,attention_mask 非空 → SDPA + pad mask(fine-tune)
150
+ - 都空 → SDPA 全可见
151
+ 位置信息走 ScaledSinusoidal,已在 embedding 层加,attention 里无 cos/sin 计算。
152
+ """
153
+ B, L, H = x.shape
154
+ qkv = self.qkv(x).reshape(B, L, 3, self.num_heads, self.head_dim)
155
+ q, k, v = qkv.unbind(dim=2) # 各 [B, L, h, d]
156
+ q = q.transpose(1, 2)
157
+ k = k.transpose(1, 2)
158
+ v = v.transpose(1, 2)
159
+
160
+ if block_mask is not None:
161
+ # flex_attention(compiled):任意 mask + flash 速度;不支持 dropout_p
162
+ out = _flex_attention(q, k, v, block_mask=block_mask)
163
+ else:
164
+ sdpa_mask = None
165
+ if attention_mask is not None:
166
+ sdpa_mask = attention_mask[:, None, None, :].to(torch.bool)
167
+ out = F.scaled_dot_product_attention(
168
+ q, k, v,
169
+ attn_mask=sdpa_mask,
170
+ dropout_p=self.attn_probs_dropout if self.training else 0.0,
171
+ is_causal=False,
172
+ ) # [B, h, L, d]
173
+ out = out.transpose(1, 2).reshape(B, L, H)
174
+ return self.out_dropout(self.o(out))
175
+
176
+
177
+ # ============ GeGLU MLP ============
178
+
179
+ class GeGLU(nn.Module):
180
+ """Linear(H, 2*I) → split → GELU(gate) * up → Linear(I, H).
181
+ 全无 bias。
182
+ """
183
+ def __init__(self, config: ModernBertConfig):
184
+ super().__init__()
185
+ I = config.intermediate_size
186
+ self.w_in = nn.Linear(config.hidden_size, 2 * I, bias=False)
187
+ self.w_out = nn.Linear(I, config.hidden_size, bias=False)
188
+ self.dropout = nn.Dropout(config.mlp_dropout)
189
+
190
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
191
+ gate, up = self.w_in(x).chunk(2, dim=-1)
192
+ return self.dropout(self.w_out(F.gelu(gate) * up))
193
+
194
+
195
+ # ============ Layer(pre-norm,支持 skip_first_prenorm)============
196
+
197
+ class ModernBertLayer(nn.Module):
198
+ def __init__(self, config: ModernBertConfig, is_first: bool = False):
199
+ super().__init__()
200
+ # is_first + skip_first_prenorm:第 1 层 attention 前不做 pre-norm
201
+ # (因为 embed_norm 已经 norm 过一次了)
202
+ self.skip_norm1 = is_first and config.skip_first_prenorm
203
+ self.norm1 = nn.Identity() if self.skip_norm1 else LayerNormNoBias(config.hidden_size, eps=config.layer_norm_eps)
204
+ self.attn = ModernBertAttention(config)
205
+ self.norm2 = LayerNormNoBias(config.hidden_size, eps=config.layer_norm_eps)
206
+ self.mlp = GeGLU(config)
207
+
208
+ def forward(self, x, block_mask=None, attention_mask=None):
209
+ x = x + self.attn(self.norm1(x), block_mask, attention_mask)
210
+ x = x + self.mlp(self.norm2(x))
211
+ return x
212
+
213
+
214
+ # ============ Backbone ============
215
+
216
+ class ModernBertModel(nn.Module):
217
+ def __init__(self, config: ModernBertConfig):
218
+ super().__init__()
219
+ self.config = config
220
+ self.embed = nn.Embedding(config.vocab_size, config.hidden_size,
221
+ padding_idx=config.pad_token_id)
222
+ # ScaledSinusoidal PE(Cramming-style),加在 embedding 后
223
+ self.pos_emb = ScaledSinusoidalPE(
224
+ embedding_dim=config.hidden_size,
225
+ max_seq_length=config.max_position_embeddings,
226
+ theta=config.pe_theta,
227
+ )
228
+ self.embed_norm = (LayerNormNoBias(config.hidden_size, eps=config.layer_norm_eps)
229
+ if config.embed_norm else nn.Identity())
230
+ self.embed_dropout = nn.Dropout(config.embed_dropout)
231
+ self.layers = nn.ModuleList(
232
+ [ModernBertLayer(config, is_first=(i == 0))
233
+ for i in range(config.num_hidden_layers)]
234
+ )
235
+ self.final_norm = (LayerNormNoBias(config.hidden_size, eps=config.layer_norm_eps)
236
+ if config.final_norm else nn.Identity())
237
+
238
+ # init: Megatron-style 残差缩放
239
+ self.apply(self._init_weights)
240
+ if config.init_method == "megatron":
241
+ self._megatron_residual_init()
242
+
243
+ def _init_weights(self, m):
244
+ if isinstance(m, nn.Linear):
245
+ nn.init.normal_(m.weight, std=self.config.initializer_range)
246
+ if m.bias is not None:
247
+ nn.init.zeros_(m.bias)
248
+ elif isinstance(m, nn.Embedding):
249
+ nn.init.normal_(m.weight, std=self.config.initializer_range)
250
+ if m.padding_idx is not None:
251
+ with torch.no_grad():
252
+ m.weight[m.padding_idx].zero_()
253
+ elif isinstance(m, LayerNormNoBias):
254
+ nn.init.ones_(m.weight)
255
+
256
+ def _megatron_residual_init(self):
257
+ """对每个 residual 路径的输出 W 缩 1/sqrt(2*L)。
258
+ 防止深层网络早期 forward variance 爆炸。
259
+ residual outputs: attn.o, mlp.w_out。
260
+ """
261
+ L = self.config.num_hidden_layers
262
+ scale = (2.0 * L) ** -0.5
263
+ for layer in self.layers:
264
+ with torch.no_grad():
265
+ layer.attn.o.weight.mul_(scale)
266
+ layer.mlp.w_out.weight.mul_(scale)
267
+
268
+ def forward(self, input_ids: torch.Tensor,
269
+ seg_ids: Optional[torch.Tensor] = None,
270
+ attention_mask: Optional[torch.Tensor] = None) -> torch.Tensor:
271
+ """seg_ids: [B, L] int32/uint8,同 doc 同 id;非空时走 flex_attention 跨 doc 隔离。
272
+ attention_mask: [B, L] 0/1,只在 seg_ids=None 时使用(fine-tune 路径)。
273
+ 位置信息:ScaledSinusoidal 加在 embedding 后,attention 内部无位置计算。
274
+ """
275
+ B, L = input_ids.shape
276
+ x = self.embed(input_ids)
277
+ x = x + self.pos_emb(L).to(x.dtype) # 加 scaled sinusoidal PE
278
+ x = self.embed_norm(x)
279
+ x = self.embed_dropout(x)
280
+
281
+ block_mask = self._build_block_mask(seg_ids, B, L) if seg_ids is not None else None
282
+
283
+ for layer in self.layers:
284
+ x = layer(x, block_mask, attention_mask)
285
+ x = self.final_norm(x)
286
+ return x
287
+
288
+ def _build_block_mask(self, seg_ids: torch.Tensor, B: int, L: int):
289
+ """seg_ids: [B, L] 用 flex_attention 构造 doc-internal mask。
290
+ mask_mod 闭包捕获 seg_ids,在 batch/query/kv 索引下查 doc 是否一致。
291
+ """
292
+ seg_ids_long = seg_ids.to(torch.int32)
293
+
294
+ def mask_mod(b, h, q_idx, kv_idx):
295
+ return seg_ids_long[b, q_idx] == seg_ids_long[b, kv_idx]
296
+
297
+ # H=None 让 mask 跨 head 共享(同 doc 隔离与 head 无关)
298
+ return create_block_mask(mask_mod, B=B, H=None, Q_LEN=L, KV_LEN=L,
299
+ device=seg_ids.device)
300
+
301
+
302
+ # ============ MLM head(tied embedding)============
303
+
304
+ class ModernBertForMLM(nn.Module):
305
+ """MLM head 简化版(Cramming Section 4.2 推荐):
306
+ - 无 nonlinear head(去 Dense + LN + GeLU)— "without ill effect"
307
+ - 无 decoder bias(去 head_bias)— "drop the decoder bias"
308
+ - 仅 tied embedding projection:logits = h @ embed.weight.T
309
+ - final LayerNorm 已经在 bert.final_norm 提供,这里不需重复
310
+ 省参数 ~0.6M,forward 略快。"""
311
+ def __init__(self, config: ModernBertConfig):
312
+ super().__init__()
313
+ self.config = config
314
+ self.bert = ModernBertModel(config)
315
+
316
+ def get_input_embeddings(self):
317
+ return self.bert.embed
318
+
319
+ def forward(self, input_ids, seg_ids=None, attention_mask=None, labels=None):
320
+ h = self.bert(input_ids, seg_ids=seg_ids,
321
+ attention_mask=attention_mask) # [B, L, H],bert 内已 final_norm
322
+ # 直接 tied embedding projection,无 nonlinear head 也无 bias
323
+ logits = F.linear(h, self.bert.embed.weight) # [B, L, V]
324
+ loss = None
325
+ if labels is not None:
326
+ loss = F.cross_entropy(
327
+ logits.view(-1, self.config.vocab_size),
328
+ labels.view(-1),
329
+ ignore_index=-100,
330
+ )
331
+ return {"logits": logits, "loss": loss}
332
+
333
+ def num_parameters(self):
334
+ return sum(p.numel() for p in self.parameters() if p.requires_grad)
335
+
336
+
337
+ # ============ Quick sanity test ============
338
+
339
+ if __name__ == "__main__":
340
+ print("=== Test 1: default config (22L/768H release-aligned) ===")
341
+ cfg = ModernBertConfig()
342
+ print(f"Config: V={cfg.vocab_size} H={cfg.hidden_size} L={cfg.num_hidden_layers} "
343
+ f"head={cfg.num_attention_heads} d={cfg.head_dim} I={cfg.intermediate_size} "
344
+ f"pe_theta={cfg.pe_theta} ln_eps={cfg.layer_norm_eps}")
345
+ print(f"embed_norm={cfg.embed_norm} skip_first_prenorm={cfg.skip_first_prenorm} "
346
+ f"final_norm={cfg.final_norm} init={cfg.init_method}")
347
+ print(f"dropout: embed={cfg.embed_dropout} mlp={cfg.mlp_dropout} "
348
+ f"attn_out={cfg.attn_out_dropout} attn_probs={cfg.attn_probs_dropout}")
349
+ model = ModernBertForMLM(cfg)
350
+ n = model.num_parameters()
351
+ print(f"params: {n:,} = {n / 1e6:.1f}M")
352
+
353
+ # forward smoke
354
+ B, L = 2, 128
355
+ ids = torch.randint(0, cfg.vocab_size, (B, L))
356
+ mask = torch.ones(B, L, dtype=torch.long)
357
+ labels = torch.randint(0, cfg.vocab_size, (B, L))
358
+ out = model(ids, attention_mask=mask, labels=labels)
359
+ print(f"logits: {out['logits'].shape} loss: {out['loss'].item():.4f}")
360
+
361
+ # backward smoke
362
+ out["loss"].backward()
363
+ # 检查所有 trainable param 都有 grad
364
+ no_grad = [n for n, p in model.named_parameters() if p.requires_grad and p.grad is None]
365
+ if no_grad:
366
+ print(f"ERROR: 缺 grad 的参数: {no_grad}")
367
+ else:
368
+ print("backward OK (所有参数都有 grad)")
369
+
370
+ # 验证 skip_first_prenorm 生效
371
+ assert model.bert.layers[0].skip_norm1, "layer[0].skip_norm1 应为 True"
372
+ assert not model.bert.layers[1].skip_norm1, "layer[1].skip_norm1 应为 False"
373
+ assert isinstance(model.bert.layers[0].norm1, nn.Identity), "layer[0].norm1 应为 Identity"
374
+ assert isinstance(model.bert.layers[1].norm1, LayerNormNoBias), "layer[1].norm1 应为 LayerNormNoBias"
375
+ print("skip_first_prenorm 配置正确")
376
+
377
+ # 验证 embed_norm + final_norm
378
+ assert isinstance(model.bert.embed_norm, LayerNormNoBias), "embed_norm 应为 LayerNormNoBias"
379
+ assert isinstance(model.bert.final_norm, LayerNormNoBias), "final_norm 应为 LayerNormNoBias"
380
+ print("embed_norm + final_norm 配置正确")
381
+
382
+ # 验证 Megatron init
383
+ L_layers = cfg.num_hidden_layers
384
+ expected_scale = (2.0 * L_layers) ** -0.5
385
+ # 一个未缩的初始值 std ≈ 0.02,缩后 std ≈ 0.02 * expected_scale
386
+ o_std = model.bert.layers[5].attn.o.weight.std().item()
387
+ expected_std = cfg.initializer_range * expected_scale
388
+ # 允许 ±50% 容差(单 tensor std 估计有 noise)
389
+ assert 0.5 * expected_std < o_std < 1.5 * expected_std, \
390
+ f"Megatron init: attn.o.weight.std={o_std:.6f}, expected≈{expected_std:.6f}"
391
+ print(f"Megatron init OK: attn.o.weight.std={o_std:.6f} ≈ {expected_std:.6f}")
392
+
393
+ # 验证 no bias
394
+ for name, p in model.named_parameters():
395
+ if "bias" in name and name != "head_bias":
396
+ print(f"ERROR: 不该有的 bias: {name}")
397
+ print("Linear/Norm no-bias 配置正确")
398
+
399
+ print("\n=== Test 1b: seg_ids → flex_attention 路径(cross-doc 隔离)===")
400
+ if torch.cuda.is_available():
401
+ # flex_attention 需要 CUDA
402
+ model_cuda = ModernBertForMLM(cfg).cuda().to(torch.bfloat16)
403
+ ids_c = ids.cuda()
404
+ labels_c = labels.cuda()
405
+ # 构造 seg_ids:chunk 内 0 0 0 ... 0 | 1 1 ... 1 (前半 doc0, 后半 doc1)
406
+ seg_ids = torch.zeros(B, L, dtype=torch.int32, device="cuda")
407
+ seg_ids[:, L // 2:] = 1
408
+ out_flex = model_cuda(ids_c, seg_ids=seg_ids, labels=labels_c)
409
+ print(f" flex_attention forward OK loss={out_flex['loss'].item():.4f} "
410
+ f"logits={out_flex['logits'].shape} dtype={out_flex['logits'].dtype}")
411
+ out_flex["loss"].backward()
412
+ no_grad = [n for n, p in model_cuda.named_parameters() if p.requires_grad and p.grad is None]
413
+ if no_grad:
414
+ print(f" ERROR: 缺 grad: {no_grad[:5]}")
415
+ else:
416
+ print(f" flex_attention backward OK")
417
+
418
+ # 验证 attention 真正隔离了:用相同 ids 但 seg_ids 不同,前半 token 应该输出不同
419
+ # (因为 doc0 token 在 seg=0 时只 attend 同 seg=0;改成 seg=2 后 attend 不同的 kv 集)
420
+ with torch.no_grad():
421
+ seg_v1 = torch.zeros(B, L, dtype=torch.int32, device="cuda")
422
+ seg_v2 = torch.zeros(B, L, dtype=torch.int32, device="cuda")
423
+ seg_v2[:, :L // 2] = 0
424
+ seg_v2[:, L // 2:] = 1 # 前半 vs 后半 隔离
425
+ h1 = model_cuda.bert(ids_c, seg_ids=seg_v1) # 全 attend(单 doc)
426
+ h2 = model_cuda.bert(ids_c, seg_ids=seg_v2) # 前后半隔离
427
+ # 前半 token 在 v1 attend 全 chunk,在 v2 只 attend 前半 → 输出应不同
428
+ front_diff = (h1[:, :L // 2] - h2[:, :L // 2]).abs().mean().item()
429
+ print(f" 前半 token 输出差异(应 > 0,验证隔离生效):{front_diff:.6f}")
430
+ assert front_diff > 1e-3, f"隔离未生效:front_diff={front_diff}"
431
+ print(f" 跨 doc 隔离工作正常 ✓")
432
+ else:
433
+ print(" 跳过(无 CUDA)")
434
+
435
+ print("\n=== Test 2: legacy v1 config (12L/1024H) — 验证向后兼容 ===")
436
+ cfg_v1 = ModernBertConfig(
437
+ hidden_size=1024, num_hidden_layers=12, num_attention_heads=16,
438
+ intermediate_size=2752,
439
+ embed_norm=False, skip_first_prenorm=False, init_method="normal",
440
+ )
441
+ model_v1 = ModernBertForMLM(cfg_v1)
442
+ n_v1 = model_v1.num_parameters()
443
+ print(f"v1-style params: {n_v1 / 1e6:.1f}M")
444
+ out_v1 = model_v1(ids, attention_mask=mask, labels=labels)
445
+ print(f"v1 forward OK loss={out_v1['loss'].item():.4f}")
446
+
447
+ print("\n=== All smoke tests passed ===")
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d64caabb7048bdf9adf521cad748614e5998575080eae00d8a20c0fabe863bd3
3
+ size 632914778
piece.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8ecc3774ba1afc0225c22147e4ff719acfa1aa9df8616befcb6c12f730ac6e07
3
+ size 249668
release_metadata.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "BERTc-315M",
3
+ "source_checkpoint": "pretrain/modern_bertc/output_v4_large/checkpoint-8500",
4
+ "step": 8500,
5
+ "metrics": {
6
+ "mt": "score 1.4712 (CWS 0.9840 / POS 0.9800 / NER 0.9660)",
7
+ "csc": "SIGHAN-15 sentence F1 0.8346"
8
+ }
9
+ }