yungisimon commited on
Commit
3e44fc8
·
verified ·
1 Parent(s): 503f2a0

pii-detect interim (ckpt-4000, epoch 0.23) + public-eval leaderboard

Browse files
Files changed (7) hide show
  1. README.md +130 -0
  2. config.json +289 -0
  3. label_schema.json +255 -0
  4. model.safetensors +3 -0
  5. modeling_lfm2_tc.py +449 -0
  6. tokenizer.json +0 -0
  7. tokenizer_config.json +23 -0
README.md ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ language: [en, es, fr, de, it, pt, nl, ru, ar, hi, ja, ko, zh, th, id, vi, tr, pl]
4
+ pipeline_tag: token-classification
5
+ tags: [pii, ner, token-classification, lfm2, multilingual, privacy]
6
+ ---
7
+
8
+ # LiquidAI/pii-detect
9
+
10
+ Multilingual PII detection — a **full fine-tune of the bidirectional LFM2.5-350M MLM
11
+ encoder** (`v13_encoder_mlm`) with a BIOES token-classification head. Detects ~27
12
+ canonical PII types across 7 domains and 18 languages.
13
+
14
+ > **Status:** pilot / interim checkpoint (trained on 10M synthetic multilingual docs).
15
+ > Private — LiquidAI internal.
16
+
17
+ ## Usage
18
+
19
+ ```python
20
+ from transformers import AutoTokenizer, AutoModelForTokenClassification
21
+ import torch
22
+
23
+ tok = AutoTokenizer.from_pretrained("LiquidAI/pii-detect", trust_remote_code=True)
24
+ model = AutoModelForTokenClassification.from_pretrained(
25
+ "LiquidAI/pii-detect", trust_remote_code=True).eval()
26
+
27
+ text = "Email Jane Doe at jane@acme.com or call +1-202-555-0142."
28
+ enc = tok(text, return_offsets_mapping=True, return_tensors="pt")
29
+ off = enc.pop("offset_mapping")[0]
30
+ with torch.no_grad():
31
+ pred = model(**enc).logits[0].argmax(-1)
32
+ labels = [model.config.id2label[i] for i in pred.tolist()]
33
+ # merge adjacent same-type tokens into spans using `off` + BIOES `labels`
34
+ ```
35
+
36
+ - **Labels:** 109 BIOES classes (27 types × B/I/E/S + O). See `id2label` in `config.json`.
37
+ - **Backbone:** bidirectional LFM2.5-350M (MLM-pretrained), full fine-tune (no LoRA).
38
+ - **Training data:** 10M synthetic multilingual PII documents (entity-first generation).
39
+
40
+ ## Public benchmark leaderboard
41
+
42
+ Type-level **micro** scores, **partial-match**. P=precision, R=recall (PII caught),
43
+ F1, F2 (recall-weighted β=2 — the metric that matters most for PII). Ours is an
44
+ **interim checkpoint (≈¼ of epoch 1)** vs **fully-trained** released baselines.
45
+
46
+ ### SPY (969 samples)
47
+ _clean · synthetic · tests PII-vs-NER discrimination_
48
+
49
+ | Model | P | R | F1 | F2 |
50
+ |---|---|---|---|---|
51
+ | **LiquidAI/pii-detect (ours)** | 0.387 | 0.799 | 0.521 | 0.658 |
52
+ | regex baseline | 0.259 | 0.579 | 0.358 | 0.464 |
53
+ | openai/privacy-filter | 0.169 | 0.595 | 0.264 | 0.396 |
54
+ | VAGOsolutions/SauerkrautLM-GLiNER | 0.160 | 0.513 | 0.244 | 0.356 |
55
+ | iiiorg/piiranha-v1 | 0.159 | 0.422 | 0.230 | 0.317 |
56
+ | OpenMed/privacy-filter-nemotron | 0.143 | 0.545 | 0.226 | 0.348 |
57
+ | LiquidAI demo (LFM2.5-PII-Demo) | 0.132 | 0.350 | 0.192 | 0.263 |
58
+
59
+ ### GRETEL (500 samples)
60
+ _clean · synthetic English_
61
+
62
+ | Model | P | R | F1 | F2 |
63
+ |---|---|---|---|---|
64
+ | LiquidAI demo (LFM2.5-PII-Demo) | 0.887 | 0.735 | 0.804 | 0.761 |
65
+ | OpenMed/privacy-filter-nemotron | 0.762 | 0.773 | 0.767 | 0.770 |
66
+ | VAGOsolutions/SauerkrautLM-GLiNER | 0.722 | 0.686 | 0.703 | 0.693 |
67
+ | **LiquidAI/pii-detect (ours)** | 0.705 | 0.621 | 0.660 | 0.636 |
68
+ | iiiorg/piiranha-v1 | 0.633 | 0.490 | 0.552 | 0.513 |
69
+ | openai/privacy-filter | 0.455 | 0.462 | 0.459 | 0.461 |
70
+ | regex baseline | 0.500 | 0.254 | 0.337 | 0.281 |
71
+
72
+ ### TAB (127 samples)
73
+ _clean · human gold · English legal (ECHR)_
74
+
75
+ | Model | P | R | F1 | F2 |
76
+ |---|---|---|---|---|
77
+ | OpenMed/privacy-filter-nemotron | 0.728 | 0.625 | 0.673 | 0.643 |
78
+ | VAGOsolutions/SauerkrautLM-GLiNER | 0.984 | 0.406 | 0.575 | 0.460 |
79
+ | openai/privacy-filter | 0.933 | 0.383 | 0.543 | 0.434 |
80
+ | LiquidAI demo (LFM2.5-PII-Demo) | 0.995 | 0.325 | 0.490 | 0.375 |
81
+ | iiiorg/piiranha-v1 | 0.721 | 0.160 | 0.262 | 0.189 |
82
+ | **LiquidAI/pii-detect (ours)** | 0.857 | 0.138 | 0.238 | 0.166 |
83
+ | regex baseline | 0.000 | 0.000 | 0.000 | 0.000 |
84
+
85
+ ### MAPA (370 samples)
86
+ _clean · human gold · multilingual EU legal_
87
+
88
+ | Model | P | R | F1 | F2 |
89
+ |---|---|---|---|---|
90
+ | VAGOsolutions/SauerkrautLM-GLiNER | 0.527 | 0.194 | 0.283 | 0.222 |
91
+ | iiiorg/piiranha-v1 | 0.465 | 0.181 | 0.261 | 0.206 |
92
+ | openai/privacy-filter | 0.372 | 0.186 | 0.248 | 0.207 |
93
+ | LiquidAI demo (LFM2.5-PII-Demo) | 0.274 | 0.140 | 0.185 | 0.155 |
94
+ | OpenMed/privacy-filter-nemotron | 0.081 | 0.180 | 0.111 | 0.144 |
95
+ | **LiquidAI/pii-detect (ours)** | 0.325 | 0.058 | 0.099 | 0.070 |
96
+ | regex baseline | 0.000 | 0.000 | 0.000 | 0.000 |
97
+
98
+
99
+ ### Contaminated benchmarks (reference only)
100
+ ### NEMOTRON (1000 samples)
101
+ _⚠ in-distribution for OpenMed-nemotron & nvidia GLiNER (read as ceilings)_
102
+
103
+ | Model | P | R | F1 | F2 |
104
+ |---|---|---|---|---|
105
+ | OpenMed/privacy-filter-nemotron | 0.970 | 0.872 | 0.918 | 0.890 |
106
+ | LiquidAI demo (LFM2.5-PII-Demo) | 0.894 | 0.518 | 0.656 | 0.566 |
107
+ | iiiorg/piiranha-v1 | 0.841 | 0.538 | 0.656 | 0.580 |
108
+ | VAGOsolutions/SauerkrautLM-GLiNER | 0.815 | 0.533 | 0.644 | 0.572 |
109
+ | openai/privacy-filter | 0.699 | 0.484 | 0.572 | 0.516 |
110
+ | **LiquidAI/pii-detect (ours)** | 0.754 | 0.429 | 0.547 | 0.470 |
111
+ | regex baseline | 0.645 | 0.227 | 0.335 | 0.260 |
112
+
113
+ ### AI4PRIVACY (660 samples)
114
+ _⚠ in OUR backbone's pretraining — contaminated for us; reference only_
115
+
116
+ | Model | P | R | F1 | F2 |
117
+ |---|---|---|---|---|
118
+ | iiiorg/piiranha-v1 | 0.969 | 0.924 | 0.946 | 0.932 |
119
+ | LiquidAI demo (LFM2.5-PII-Demo) | 0.533 | 0.464 | 0.496 | 0.476 |
120
+ | VAGOsolutions/SauerkrautLM-GLiNER | 0.399 | 0.614 | 0.484 | 0.554 |
121
+ | OpenMed/privacy-filter-nemotron | 0.341 | 0.541 | 0.418 | 0.484 |
122
+ | openai/privacy-filter | 0.316 | 0.492 | 0.385 | 0.443 |
123
+ | **LiquidAI/pii-detect (ours)** | 0.280 | 0.301 | 0.290 | 0.296 |
124
+ | regex baseline | 0.269 | 0.147 | 0.190 | 0.162 |
125
+
126
+
127
+ ## Notes
128
+ - **Strength:** best-in-class on **SPY** (PII-vs-NER discrimination) with the highest recall of any model tested.
129
+ - **Gap:** legal-domain benchmarks (TAB/MAPA) — high precision but low recall; targeted for the next data iteration.
130
+ - Benchmarks use differing entity taxonomies; our fine-grained types are mapped to each via an identity/crosswalk, so type-level F1 understates raw detection (binary detection is higher).
config.json ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Lfm2BidirForTokenClassification"
4
+ ],
5
+ "auto_map": {
6
+ "AutoModel": "modeling_lfm2_tc.Lfm2BidirModel",
7
+ "AutoModelForTokenClassification": "modeling_lfm2_tc.Lfm2BidirForTokenClassification"
8
+ },
9
+ "block_auto_adjust_ff_dim": true,
10
+ "block_dim": 1024,
11
+ "block_ffn_dim_multiplier": 1.0,
12
+ "block_mlp_init_scale": 1.0,
13
+ "block_multiple_of": 256,
14
+ "block_norm_eps": 1e-05,
15
+ "block_out_init_scale": 1.0,
16
+ "block_use_swiglu": true,
17
+ "block_use_xavier_init": true,
18
+ "bos_token_id": 1,
19
+ "classifier_dropout": 0.1,
20
+ "conv_L_cache": 3,
21
+ "conv_bias": false,
22
+ "conv_dim": 1024,
23
+ "conv_dim_out": 1024,
24
+ "conv_use_xavier_init": true,
25
+ "dtype": "float32",
26
+ "eos_token_id": 7,
27
+ "full_attn_idxs": null,
28
+ "hidden_size": 1024,
29
+ "id2label": {
30
+ "0": "O",
31
+ "1": "B-contact.address",
32
+ "2": "I-contact.address",
33
+ "3": "E-contact.address",
34
+ "4": "S-contact.address",
35
+ "5": "B-contact.email",
36
+ "6": "I-contact.email",
37
+ "7": "E-contact.email",
38
+ "8": "S-contact.email",
39
+ "9": "B-contact.ip_address",
40
+ "10": "I-contact.ip_address",
41
+ "11": "E-contact.ip_address",
42
+ "12": "S-contact.ip_address",
43
+ "13": "B-contact.phone",
44
+ "14": "I-contact.phone",
45
+ "15": "E-contact.phone",
46
+ "16": "S-contact.phone",
47
+ "17": "B-contact.postal_code",
48
+ "18": "I-contact.postal_code",
49
+ "19": "E-contact.postal_code",
50
+ "20": "S-contact.postal_code",
51
+ "21": "B-credential.api_key",
52
+ "22": "I-credential.api_key",
53
+ "23": "E-credential.api_key",
54
+ "24": "S-credential.api_key",
55
+ "25": "B-device.mac_address",
56
+ "26": "I-device.mac_address",
57
+ "27": "E-device.mac_address",
58
+ "28": "S-device.mac_address",
59
+ "29": "B-financial.bank_account",
60
+ "30": "I-financial.bank_account",
61
+ "31": "E-financial.bank_account",
62
+ "32": "S-financial.bank_account",
63
+ "33": "B-financial.credit_card",
64
+ "34": "I-financial.credit_card",
65
+ "35": "E-financial.credit_card",
66
+ "36": "S-financial.credit_card",
67
+ "37": "B-financial.iban",
68
+ "38": "I-financial.iban",
69
+ "39": "E-financial.iban",
70
+ "40": "S-financial.iban",
71
+ "41": "B-financial.swift_bic",
72
+ "42": "I-financial.swift_bic",
73
+ "43": "E-financial.swift_bic",
74
+ "44": "S-financial.swift_bic",
75
+ "45": "B-healthcare.condition",
76
+ "46": "I-healthcare.condition",
77
+ "47": "E-healthcare.condition",
78
+ "48": "S-healthcare.condition",
79
+ "49": "B-healthcare.medical_record",
80
+ "50": "I-healthcare.medical_record",
81
+ "51": "E-healthcare.medical_record",
82
+ "52": "S-healthcare.medical_record",
83
+ "53": "B-healthcare.medication",
84
+ "54": "I-healthcare.medication",
85
+ "55": "E-healthcare.medication",
86
+ "56": "S-healthcare.medication",
87
+ "57": "B-identity.date_of_birth",
88
+ "58": "I-identity.date_of_birth",
89
+ "59": "E-identity.date_of_birth",
90
+ "60": "S-identity.date_of_birth",
91
+ "61": "B-identity.drivers_license",
92
+ "62": "I-identity.drivers_license",
93
+ "63": "E-identity.drivers_license",
94
+ "64": "S-identity.drivers_license",
95
+ "65": "B-identity.national_id",
96
+ "66": "I-identity.national_id",
97
+ "67": "E-identity.national_id",
98
+ "68": "S-identity.national_id",
99
+ "69": "B-identity.passport",
100
+ "70": "I-identity.passport",
101
+ "71": "E-identity.passport",
102
+ "72": "S-identity.passport",
103
+ "73": "B-identity.person_name",
104
+ "74": "I-identity.person_name",
105
+ "75": "E-identity.person_name",
106
+ "76": "S-identity.person_name",
107
+ "77": "B-identity.ssn",
108
+ "78": "I-identity.ssn",
109
+ "79": "E-identity.ssn",
110
+ "80": "S-identity.ssn",
111
+ "81": "B-legal.case_number",
112
+ "82": "I-legal.case_number",
113
+ "83": "E-legal.case_number",
114
+ "84": "S-legal.case_number",
115
+ "85": "B-location.gps_coordinates",
116
+ "86": "I-location.gps_coordinates",
117
+ "87": "E-location.gps_coordinates",
118
+ "88": "S-location.gps_coordinates",
119
+ "89": "B-online.url",
120
+ "90": "I-online.url",
121
+ "91": "E-online.url",
122
+ "92": "S-online.url",
123
+ "93": "B-online.username",
124
+ "94": "I-online.username",
125
+ "95": "E-online.username",
126
+ "96": "S-online.username",
127
+ "97": "B-org.company_name",
128
+ "98": "I-org.company_name",
129
+ "99": "E-org.company_name",
130
+ "100": "S-org.company_name",
131
+ "101": "B-special.political",
132
+ "102": "I-special.political",
133
+ "103": "E-special.political",
134
+ "104": "S-special.political",
135
+ "105": "B-special.religion",
136
+ "106": "I-special.religion",
137
+ "107": "E-special.religion",
138
+ "108": "S-special.religion"
139
+ },
140
+ "initializer_range": 0.02,
141
+ "intermediate_size": 6656,
142
+ "label2id": {
143
+ "B-contact.address": 1,
144
+ "B-contact.email": 5,
145
+ "B-contact.ip_address": 9,
146
+ "B-contact.phone": 13,
147
+ "B-contact.postal_code": 17,
148
+ "B-credential.api_key": 21,
149
+ "B-device.mac_address": 25,
150
+ "B-financial.bank_account": 29,
151
+ "B-financial.credit_card": 33,
152
+ "B-financial.iban": 37,
153
+ "B-financial.swift_bic": 41,
154
+ "B-healthcare.condition": 45,
155
+ "B-healthcare.medical_record": 49,
156
+ "B-healthcare.medication": 53,
157
+ "B-identity.date_of_birth": 57,
158
+ "B-identity.drivers_license": 61,
159
+ "B-identity.national_id": 65,
160
+ "B-identity.passport": 69,
161
+ "B-identity.person_name": 73,
162
+ "B-identity.ssn": 77,
163
+ "B-legal.case_number": 81,
164
+ "B-location.gps_coordinates": 85,
165
+ "B-online.url": 89,
166
+ "B-online.username": 93,
167
+ "B-org.company_name": 97,
168
+ "B-special.political": 101,
169
+ "B-special.religion": 105,
170
+ "E-contact.address": 3,
171
+ "E-contact.email": 7,
172
+ "E-contact.ip_address": 11,
173
+ "E-contact.phone": 15,
174
+ "E-contact.postal_code": 19,
175
+ "E-credential.api_key": 23,
176
+ "E-device.mac_address": 27,
177
+ "E-financial.bank_account": 31,
178
+ "E-financial.credit_card": 35,
179
+ "E-financial.iban": 39,
180
+ "E-financial.swift_bic": 43,
181
+ "E-healthcare.condition": 47,
182
+ "E-healthcare.medical_record": 51,
183
+ "E-healthcare.medication": 55,
184
+ "E-identity.date_of_birth": 59,
185
+ "E-identity.drivers_license": 63,
186
+ "E-identity.national_id": 67,
187
+ "E-identity.passport": 71,
188
+ "E-identity.person_name": 75,
189
+ "E-identity.ssn": 79,
190
+ "E-legal.case_number": 83,
191
+ "E-location.gps_coordinates": 87,
192
+ "E-online.url": 91,
193
+ "E-online.username": 95,
194
+ "E-org.company_name": 99,
195
+ "E-special.political": 103,
196
+ "E-special.religion": 107,
197
+ "I-contact.address": 2,
198
+ "I-contact.email": 6,
199
+ "I-contact.ip_address": 10,
200
+ "I-contact.phone": 14,
201
+ "I-contact.postal_code": 18,
202
+ "I-credential.api_key": 22,
203
+ "I-device.mac_address": 26,
204
+ "I-financial.bank_account": 30,
205
+ "I-financial.credit_card": 34,
206
+ "I-financial.iban": 38,
207
+ "I-financial.swift_bic": 42,
208
+ "I-healthcare.condition": 46,
209
+ "I-healthcare.medical_record": 50,
210
+ "I-healthcare.medication": 54,
211
+ "I-identity.date_of_birth": 58,
212
+ "I-identity.drivers_license": 62,
213
+ "I-identity.national_id": 66,
214
+ "I-identity.passport": 70,
215
+ "I-identity.person_name": 74,
216
+ "I-identity.ssn": 78,
217
+ "I-legal.case_number": 82,
218
+ "I-location.gps_coordinates": 86,
219
+ "I-online.url": 90,
220
+ "I-online.username": 94,
221
+ "I-org.company_name": 98,
222
+ "I-special.political": 102,
223
+ "I-special.religion": 106,
224
+ "O": 0,
225
+ "S-contact.address": 4,
226
+ "S-contact.email": 8,
227
+ "S-contact.ip_address": 12,
228
+ "S-contact.phone": 16,
229
+ "S-contact.postal_code": 20,
230
+ "S-credential.api_key": 24,
231
+ "S-device.mac_address": 28,
232
+ "S-financial.bank_account": 32,
233
+ "S-financial.credit_card": 36,
234
+ "S-financial.iban": 40,
235
+ "S-financial.swift_bic": 44,
236
+ "S-healthcare.condition": 48,
237
+ "S-healthcare.medical_record": 52,
238
+ "S-healthcare.medication": 56,
239
+ "S-identity.date_of_birth": 60,
240
+ "S-identity.drivers_license": 64,
241
+ "S-identity.national_id": 68,
242
+ "S-identity.passport": 72,
243
+ "S-identity.person_name": 76,
244
+ "S-identity.ssn": 80,
245
+ "S-legal.case_number": 84,
246
+ "S-location.gps_coordinates": 88,
247
+ "S-online.url": 92,
248
+ "S-online.username": 96,
249
+ "S-org.company_name": 100,
250
+ "S-special.political": 104,
251
+ "S-special.religion": 108
252
+ },
253
+ "label_smoothing": 0.1,
254
+ "layer_types": [
255
+ "conv",
256
+ "conv",
257
+ "full_attention",
258
+ "conv",
259
+ "conv",
260
+ "full_attention",
261
+ "conv",
262
+ "conv",
263
+ "full_attention",
264
+ "conv",
265
+ "full_attention",
266
+ "conv",
267
+ "full_attention",
268
+ "conv",
269
+ "full_attention",
270
+ "conv"
271
+ ],
272
+ "max_position_embeddings": 128000,
273
+ "model_type": "lfm2",
274
+ "norm_eps": 1e-05,
275
+ "num_attention_heads": 16,
276
+ "num_heads": 16,
277
+ "num_hidden_layers": 16,
278
+ "num_key_value_heads": 8,
279
+ "pad_token_id": 0,
280
+ "rope_parameters": {
281
+ "rope_theta": 1000000.0,
282
+ "rope_type": "default"
283
+ },
284
+ "tie_word_embeddings": true,
285
+ "transformers_version": "5.11.0",
286
+ "use_cache": false,
287
+ "use_pos_enc": true,
288
+ "vocab_size": 65536
289
+ }
label_schema.json ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "num_labels": 109,
3
+ "scheme": "BIOES",
4
+ "types_in_order": [
5
+ "contact.address",
6
+ "contact.email",
7
+ "contact.ip_address",
8
+ "contact.phone",
9
+ "contact.postal_code",
10
+ "credential.api_key",
11
+ "device.mac_address",
12
+ "financial.bank_account",
13
+ "financial.credit_card",
14
+ "financial.iban",
15
+ "financial.swift_bic",
16
+ "healthcare.condition",
17
+ "healthcare.medical_record",
18
+ "healthcare.medication",
19
+ "identity.date_of_birth",
20
+ "identity.drivers_license",
21
+ "identity.national_id",
22
+ "identity.passport",
23
+ "identity.person_name",
24
+ "identity.ssn",
25
+ "legal.case_number",
26
+ "location.gps_coordinates",
27
+ "online.url",
28
+ "online.username",
29
+ "org.company_name",
30
+ "special.political",
31
+ "special.religion"
32
+ ],
33
+ "id2label": {
34
+ "0": "O",
35
+ "1": "B-contact.address",
36
+ "2": "I-contact.address",
37
+ "3": "E-contact.address",
38
+ "4": "S-contact.address",
39
+ "5": "B-contact.email",
40
+ "6": "I-contact.email",
41
+ "7": "E-contact.email",
42
+ "8": "S-contact.email",
43
+ "9": "B-contact.ip_address",
44
+ "10": "I-contact.ip_address",
45
+ "11": "E-contact.ip_address",
46
+ "12": "S-contact.ip_address",
47
+ "13": "B-contact.phone",
48
+ "14": "I-contact.phone",
49
+ "15": "E-contact.phone",
50
+ "16": "S-contact.phone",
51
+ "17": "B-contact.postal_code",
52
+ "18": "I-contact.postal_code",
53
+ "19": "E-contact.postal_code",
54
+ "20": "S-contact.postal_code",
55
+ "21": "B-credential.api_key",
56
+ "22": "I-credential.api_key",
57
+ "23": "E-credential.api_key",
58
+ "24": "S-credential.api_key",
59
+ "25": "B-device.mac_address",
60
+ "26": "I-device.mac_address",
61
+ "27": "E-device.mac_address",
62
+ "28": "S-device.mac_address",
63
+ "29": "B-financial.bank_account",
64
+ "30": "I-financial.bank_account",
65
+ "31": "E-financial.bank_account",
66
+ "32": "S-financial.bank_account",
67
+ "33": "B-financial.credit_card",
68
+ "34": "I-financial.credit_card",
69
+ "35": "E-financial.credit_card",
70
+ "36": "S-financial.credit_card",
71
+ "37": "B-financial.iban",
72
+ "38": "I-financial.iban",
73
+ "39": "E-financial.iban",
74
+ "40": "S-financial.iban",
75
+ "41": "B-financial.swift_bic",
76
+ "42": "I-financial.swift_bic",
77
+ "43": "E-financial.swift_bic",
78
+ "44": "S-financial.swift_bic",
79
+ "45": "B-healthcare.condition",
80
+ "46": "I-healthcare.condition",
81
+ "47": "E-healthcare.condition",
82
+ "48": "S-healthcare.condition",
83
+ "49": "B-healthcare.medical_record",
84
+ "50": "I-healthcare.medical_record",
85
+ "51": "E-healthcare.medical_record",
86
+ "52": "S-healthcare.medical_record",
87
+ "53": "B-healthcare.medication",
88
+ "54": "I-healthcare.medication",
89
+ "55": "E-healthcare.medication",
90
+ "56": "S-healthcare.medication",
91
+ "57": "B-identity.date_of_birth",
92
+ "58": "I-identity.date_of_birth",
93
+ "59": "E-identity.date_of_birth",
94
+ "60": "S-identity.date_of_birth",
95
+ "61": "B-identity.drivers_license",
96
+ "62": "I-identity.drivers_license",
97
+ "63": "E-identity.drivers_license",
98
+ "64": "S-identity.drivers_license",
99
+ "65": "B-identity.national_id",
100
+ "66": "I-identity.national_id",
101
+ "67": "E-identity.national_id",
102
+ "68": "S-identity.national_id",
103
+ "69": "B-identity.passport",
104
+ "70": "I-identity.passport",
105
+ "71": "E-identity.passport",
106
+ "72": "S-identity.passport",
107
+ "73": "B-identity.person_name",
108
+ "74": "I-identity.person_name",
109
+ "75": "E-identity.person_name",
110
+ "76": "S-identity.person_name",
111
+ "77": "B-identity.ssn",
112
+ "78": "I-identity.ssn",
113
+ "79": "E-identity.ssn",
114
+ "80": "S-identity.ssn",
115
+ "81": "B-legal.case_number",
116
+ "82": "I-legal.case_number",
117
+ "83": "E-legal.case_number",
118
+ "84": "S-legal.case_number",
119
+ "85": "B-location.gps_coordinates",
120
+ "86": "I-location.gps_coordinates",
121
+ "87": "E-location.gps_coordinates",
122
+ "88": "S-location.gps_coordinates",
123
+ "89": "B-online.url",
124
+ "90": "I-online.url",
125
+ "91": "E-online.url",
126
+ "92": "S-online.url",
127
+ "93": "B-online.username",
128
+ "94": "I-online.username",
129
+ "95": "E-online.username",
130
+ "96": "S-online.username",
131
+ "97": "B-org.company_name",
132
+ "98": "I-org.company_name",
133
+ "99": "E-org.company_name",
134
+ "100": "S-org.company_name",
135
+ "101": "B-special.political",
136
+ "102": "I-special.political",
137
+ "103": "E-special.political",
138
+ "104": "S-special.political",
139
+ "105": "B-special.religion",
140
+ "106": "I-special.religion",
141
+ "107": "E-special.religion",
142
+ "108": "S-special.religion"
143
+ },
144
+ "label2id": {
145
+ "O": 0,
146
+ "B-contact.address": 1,
147
+ "I-contact.address": 2,
148
+ "E-contact.address": 3,
149
+ "S-contact.address": 4,
150
+ "B-contact.email": 5,
151
+ "I-contact.email": 6,
152
+ "E-contact.email": 7,
153
+ "S-contact.email": 8,
154
+ "B-contact.ip_address": 9,
155
+ "I-contact.ip_address": 10,
156
+ "E-contact.ip_address": 11,
157
+ "S-contact.ip_address": 12,
158
+ "B-contact.phone": 13,
159
+ "I-contact.phone": 14,
160
+ "E-contact.phone": 15,
161
+ "S-contact.phone": 16,
162
+ "B-contact.postal_code": 17,
163
+ "I-contact.postal_code": 18,
164
+ "E-contact.postal_code": 19,
165
+ "S-contact.postal_code": 20,
166
+ "B-credential.api_key": 21,
167
+ "I-credential.api_key": 22,
168
+ "E-credential.api_key": 23,
169
+ "S-credential.api_key": 24,
170
+ "B-device.mac_address": 25,
171
+ "I-device.mac_address": 26,
172
+ "E-device.mac_address": 27,
173
+ "S-device.mac_address": 28,
174
+ "B-financial.bank_account": 29,
175
+ "I-financial.bank_account": 30,
176
+ "E-financial.bank_account": 31,
177
+ "S-financial.bank_account": 32,
178
+ "B-financial.credit_card": 33,
179
+ "I-financial.credit_card": 34,
180
+ "E-financial.credit_card": 35,
181
+ "S-financial.credit_card": 36,
182
+ "B-financial.iban": 37,
183
+ "I-financial.iban": 38,
184
+ "E-financial.iban": 39,
185
+ "S-financial.iban": 40,
186
+ "B-financial.swift_bic": 41,
187
+ "I-financial.swift_bic": 42,
188
+ "E-financial.swift_bic": 43,
189
+ "S-financial.swift_bic": 44,
190
+ "B-healthcare.condition": 45,
191
+ "I-healthcare.condition": 46,
192
+ "E-healthcare.condition": 47,
193
+ "S-healthcare.condition": 48,
194
+ "B-healthcare.medical_record": 49,
195
+ "I-healthcare.medical_record": 50,
196
+ "E-healthcare.medical_record": 51,
197
+ "S-healthcare.medical_record": 52,
198
+ "B-healthcare.medication": 53,
199
+ "I-healthcare.medication": 54,
200
+ "E-healthcare.medication": 55,
201
+ "S-healthcare.medication": 56,
202
+ "B-identity.date_of_birth": 57,
203
+ "I-identity.date_of_birth": 58,
204
+ "E-identity.date_of_birth": 59,
205
+ "S-identity.date_of_birth": 60,
206
+ "B-identity.drivers_license": 61,
207
+ "I-identity.drivers_license": 62,
208
+ "E-identity.drivers_license": 63,
209
+ "S-identity.drivers_license": 64,
210
+ "B-identity.national_id": 65,
211
+ "I-identity.national_id": 66,
212
+ "E-identity.national_id": 67,
213
+ "S-identity.national_id": 68,
214
+ "B-identity.passport": 69,
215
+ "I-identity.passport": 70,
216
+ "E-identity.passport": 71,
217
+ "S-identity.passport": 72,
218
+ "B-identity.person_name": 73,
219
+ "I-identity.person_name": 74,
220
+ "E-identity.person_name": 75,
221
+ "S-identity.person_name": 76,
222
+ "B-identity.ssn": 77,
223
+ "I-identity.ssn": 78,
224
+ "E-identity.ssn": 79,
225
+ "S-identity.ssn": 80,
226
+ "B-legal.case_number": 81,
227
+ "I-legal.case_number": 82,
228
+ "E-legal.case_number": 83,
229
+ "S-legal.case_number": 84,
230
+ "B-location.gps_coordinates": 85,
231
+ "I-location.gps_coordinates": 86,
232
+ "E-location.gps_coordinates": 87,
233
+ "S-location.gps_coordinates": 88,
234
+ "B-online.url": 89,
235
+ "I-online.url": 90,
236
+ "E-online.url": 91,
237
+ "S-online.url": 92,
238
+ "B-online.username": 93,
239
+ "I-online.username": 94,
240
+ "E-online.username": 95,
241
+ "S-online.username": 96,
242
+ "B-org.company_name": 97,
243
+ "I-org.company_name": 98,
244
+ "E-org.company_name": 99,
245
+ "S-org.company_name": 100,
246
+ "B-special.political": 101,
247
+ "I-special.political": 102,
248
+ "E-special.political": 103,
249
+ "S-special.political": 104,
250
+ "B-special.religion": 105,
251
+ "I-special.religion": 106,
252
+ "E-special.religion": 107,
253
+ "S-special.religion": 108
254
+ }
255
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ad4d7a4e9e465578b475961d312c4928a2a97448d9a67271be8536e691ade16
3
+ size 1250624876
modeling_lfm2_tc.py ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Self-contained inference modeling for the Lfm2 bidirectional encoder MLM.
3
+
4
+ Designed to be SHIPPED ALONGSIDE THE CHECKPOINT via trust_remote_code so that:
5
+
6
+ >>> from transformers import AutoModelForMaskedLM, AutoTokenizer, AutoModel
7
+ >>> tok = AutoTokenizer.from_pretrained("LiquidAI/v13_encoder_mlm_s131200", trust_remote_code=True)
8
+ >>> mlm = AutoModelForMaskedLM.from_pretrained("LiquidAI/v13_encoder_mlm_s131200", trust_remote_code=True)
9
+ >>> body = AutoModel.from_pretrained("LiquidAI/v13_encoder_mlm_s131200", trust_remote_code=True)
10
+
11
+ works on a clean transformers install WITHOUT importing the training-time
12
+ `encoder_lfm` package, AND without dead/unused parameters from the stripped
13
+ `shortconv.in_proj` / `shortconv.out_proj` weights.
14
+
15
+ The trained Lfm2ForMaskedLM (training-time) wraps upstream Lfm2Model + a
16
+ runtime monkey-patch that replaces Lfm2ShortConv.forward with a symmetric
17
+ depthwise conv on hidden_states — completely bypassing `in_proj`, `B*x`
18
+ gating, `c*conv_out` gating, and `out_proj`. Those projections receive zero
19
+ gradient during training and stay at random init. They are not used at
20
+ inference. This file makes that explicit by declaring a fresh
21
+ `Lfm2ShortConvBidir` module that has ONLY `conv.weight`, and swaps it into
22
+ every shortconv slot at __init__.
23
+
24
+ Bidirectional attention: replace the upstream `create_causal_mask` with a
25
+ padding-only additive mask (no triangular component). MLM forward never has
26
+ KV cache.
27
+
28
+ Compatible with:
29
+ - transformers >= 5.0 (uses processing_class etc.)
30
+ - sentence-transformers (via AutoModel + mean pooling)
31
+ """
32
+ from __future__ import annotations
33
+
34
+ from dataclasses import dataclass
35
+ from typing import Optional
36
+
37
+ import torch
38
+ import torch.nn as nn
39
+ import torch.nn.functional as F
40
+ from transformers import PreTrainedModel
41
+ from transformers.models.lfm2 import Lfm2Config
42
+ from transformers.models.lfm2 import modeling_lfm2 as _lfm2_mod
43
+ from transformers.modeling_outputs import BaseModelOutput, MaskedLMOutput
44
+
45
+ # --------------------------------------------------------------------------- #
46
+ # Bidirectional attention mask #
47
+ # --------------------------------------------------------------------------- #
48
+
49
+
50
+ def _bidirectional_mask(*args, **kwargs):
51
+ """Replacement for `transformers.models.lfm2.modeling_lfm2.create_causal_mask`.
52
+
53
+ Builds a NON-causal (padding-only) mask. On transformers >=5.1 this delegates
54
+ to the official, attention-impl-aware `create_bidirectional_mask` (handles
55
+ eager / sdpa / flash mask formats correctly). On older versions it falls back
56
+ to a hand-rolled (B, 1, Q, K) additive bias (zero everywhere, -1e9 at padding
57
+ key positions; no triangular component).
58
+
59
+ Accepts arbitrary args/kwargs because the upstream `create_causal_mask` call
60
+ signature changed across transformers releases (5.11 passes `inputs_embeds=`,
61
+ `position_ids=` by keyword).
62
+ """
63
+ try:
64
+ from transformers.masking_utils import create_bidirectional_mask
65
+ return create_bidirectional_mask(*args, **kwargs)
66
+ except ImportError:
67
+ pass
68
+ # ---- fallback for transformers without create_bidirectional_mask ----
69
+ config = kwargs.get("config", args[0] if len(args) > 0 else None)
70
+ input_embeds = (kwargs.get("inputs_embeds") or kwargs.get("input_embeds")
71
+ or (args[1] if len(args) > 1 else None))
72
+ attention_mask = kwargs.get("attention_mask", args[2] if len(args) > 2 else None)
73
+ cache_position = kwargs.get("cache_position")
74
+ bsz, q_len = input_embeds.shape[0], input_embeds.shape[1]
75
+ past = int(cache_position[0]) if cache_position is not None else 0
76
+ kv_len = past + q_len
77
+ device, dtype = input_embeds.device, input_embeds.dtype
78
+ mask = torch.zeros((bsz, 1, q_len, kv_len), device=device, dtype=dtype)
79
+ if attention_mask is not None:
80
+ if attention_mask.dim() == 2:
81
+ pad_vec = (attention_mask == 0).to(dtype) * -1e9
82
+ mask = mask + pad_vec[:, None, None, :]
83
+ elif attention_mask.dim() == 4:
84
+ mask = attention_mask.to(dtype)
85
+ return mask
86
+
87
+
88
+ # --------------------------------------------------------------------------- #
89
+ # Symmetric depthwise shortconv — replaces upstream Lfm2ShortConv #
90
+ # --------------------------------------------------------------------------- #
91
+
92
+
93
+ class Lfm2ShortConvBidir(nn.Module):
94
+ """Inference-only bidirectional shortconv.
95
+
96
+ Holds ONLY `conv.weight` (depthwise kernel) — no in_proj, no out_proj, no
97
+ chunking, no B*x or c*conv_out gating. Symmetric center-pad so each
98
+ position sees a [t-K/2, ..., t+K/2] window.
99
+
100
+ Tensor name compatibility: state_dict keys remain `conv.weight` (matching
101
+ the trained safetensors) so checkpoints saved by the training-time class
102
+ load directly into this class without renaming.
103
+ """
104
+
105
+ def __init__(self, config: Lfm2Config, layer_idx: int):
106
+ super().__init__()
107
+ self.config = config
108
+ self.layer_idx = layer_idx
109
+ self.L_cache = int(config.conv_L_cache)
110
+ self.conv = nn.Conv1d(
111
+ in_channels=config.hidden_size,
112
+ out_channels=config.hidden_size,
113
+ kernel_size=self.L_cache,
114
+ groups=config.hidden_size,
115
+ bias=False,
116
+ padding=self.L_cache // 2,
117
+ )
118
+
119
+ def forward(
120
+ self,
121
+ hidden_states: torch.Tensor,
122
+ past_key_values=None,
123
+ cache_position=None,
124
+ attention_mask=None,
125
+ ): # signature mirrors upstream Lfm2ShortConv.forward
126
+ x = hidden_states # (B, T, C)
127
+ T = x.shape[1]
128
+ Bx = x.transpose(1, 2).contiguous() # (B, C, T)
129
+ y = self.conv(Bx)
130
+ if y.shape[-1] != T: # even kernel -> trim trailing position
131
+ y = y[..., :T]
132
+ return y.transpose(1, 2).contiguous()
133
+
134
+
135
+ def _swap_shortconv_to_bidir(module: nn.Module, config: Lfm2Config) -> None:
136
+ """Recursively replace every upstream Lfm2ShortConv with Lfm2ShortConvBidir.
137
+
138
+ Walks the module tree top-down, identifies modules with the same attribute
139
+ surface as Lfm2ShortConv (has `.conv` (Conv1d) AND `.in_proj` (Linear)),
140
+ and rebinds the parent's attribute to a fresh Lfm2ShortConvBidir of the
141
+ same layer_idx.
142
+ """
143
+ Lfm2ShortConv = _lfm2_mod.Lfm2ShortConv
144
+ for parent_name, parent in list(module.named_modules()):
145
+ for child_name, child in list(parent.named_children()):
146
+ if isinstance(child, Lfm2ShortConv):
147
+ layer_idx = getattr(child, "layer_idx", 0)
148
+ new = Lfm2ShortConvBidir(config, layer_idx)
149
+ # transfer trained conv.weight if it exists on the source
150
+ if hasattr(child, "conv") and hasattr(child.conv, "weight"):
151
+ with torch.no_grad():
152
+ new.conv.weight.data.copy_(child.conv.weight.data.to(new.conv.weight.dtype))
153
+ setattr(parent, child_name, new)
154
+
155
+
156
+ # --------------------------------------------------------------------------- #
157
+ # Bidirectional base model #
158
+ # --------------------------------------------------------------------------- #
159
+
160
+
161
+ def _install_bidirectional_patches_once():
162
+ """Idempotently install the bidirectional attention-mask replacement on
163
+ the upstream `transformers.models.lfm2.modeling_lfm2` module.
164
+
165
+ This runs at IMPORT TIME of this file. It is a no-op for trained ckpts
166
+ that already loaded with the same patch installed (e.g. via the
167
+ training-time `encoder_lfm.modeling.lfm2_bidirectional` module).
168
+ """
169
+ if getattr(_lfm2_mod, "_lfm2_bidir_inference_patch_installed", False):
170
+ return
171
+ _lfm2_mod.create_causal_mask = _bidirectional_mask
172
+ _lfm2_mod._lfm2_bidir_inference_patch_installed = True
173
+
174
+
175
+ _install_bidirectional_patches_once()
176
+
177
+
178
+ # --------------------------------------------------------------------------- #
179
+ # Lfm2 bidirectional MLM head #
180
+ # --------------------------------------------------------------------------- #
181
+
182
+
183
+ class Lfm2BidirForMaskedLM(PreTrainedModel):
184
+ """Lfm2 encoder MLM, bidirectional attention + symmetric shortconv.
185
+
186
+ Tied lm_head -> embed_tokens.weight. No KV cache. Single forward pass.
187
+
188
+ For sentence-transformers compatibility, the base model is reachable via
189
+ `.lfm2` and can be passed to AutoModel-style consumers (mean-pool the
190
+ last hidden state).
191
+ """
192
+
193
+ config_class = Lfm2Config
194
+ base_model_prefix = "lfm2"
195
+ supports_gradient_checkpointing = False
196
+ _tied_weights_keys = {"lm_head.weight": "lfm2.embed_tokens.weight"}
197
+ # Defensive: stripped slim ckpts won't contain these. Tell HF to skip
198
+ # missing-key errors for them in case anyone re-ships a non-stripped variant
199
+ # later.
200
+ _keys_to_ignore_on_load_missing = [
201
+ r"lfm2\.layers\.\d+\.conv\.in_proj\.weight",
202
+ r"lfm2\.layers\.\d+\.conv\.out_proj\.weight",
203
+ r"lfm2\.layers\.\d+\.conv\.in_proj\.bias",
204
+ r"lfm2\.layers\.\d+\.conv\.out_proj\.bias",
205
+ ]
206
+ # And don't error on attempts to load the same in unexpected-keys mode.
207
+ _keys_to_ignore_on_load_unexpected = list(_keys_to_ignore_on_load_missing)
208
+
209
+ def __init__(self, config: Lfm2Config):
210
+ super().__init__(config)
211
+ # Ensure attention mask is bidirectional (no-op if installed)
212
+ _install_bidirectional_patches_once()
213
+
214
+ # MLM has no KV cache
215
+ config = type(config).from_dict({**config.to_dict(), "use_cache": False})
216
+ self.config = config
217
+
218
+ # Use upstream Lfm2Model body (with conv + full_attention hybrid blocks)
219
+ self.lfm2 = _lfm2_mod.Lfm2Model(config)
220
+
221
+ # Swap every Lfm2ShortConv -> Lfm2ShortConvBidir BEFORE state_dict load.
222
+ # This removes in_proj/out_proj from the module tree so missing keys in
223
+ # the slim safetensors aren't a problem.
224
+ _swap_shortconv_to_bidir(self.lfm2, config)
225
+
226
+ # MLM head: tied linear projection to vocab.
227
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
228
+ self.post_init()
229
+ self.tie_weights()
230
+
231
+ # ----- HF wiring --------------------------------------------------------
232
+
233
+ def tie_weights(self, *a, **k):
234
+ try:
235
+ super().tie_weights(*a, **k)
236
+ except TypeError:
237
+ super().tie_weights()
238
+ self.lm_head.weight = self.lfm2.embed_tokens.weight
239
+
240
+ def get_input_embeddings(self):
241
+ return self.lfm2.embed_tokens
242
+
243
+ def set_input_embeddings(self, value):
244
+ self.lfm2.embed_tokens = value
245
+ self.tie_weights()
246
+
247
+ def get_output_embeddings(self):
248
+ return self.lm_head
249
+
250
+ # ----- forward ----------------------------------------------------------
251
+
252
+ def forward(
253
+ self,
254
+ input_ids: Optional[torch.LongTensor] = None,
255
+ attention_mask: Optional[torch.Tensor] = None,
256
+ position_ids: Optional[torch.LongTensor] = None,
257
+ labels: Optional[torch.LongTensor] = None,
258
+ inputs_embeds: Optional[torch.FloatTensor] = None,
259
+ output_hidden_states: Optional[bool] = None,
260
+ output_attentions: Optional[bool] = None,
261
+ return_dict: Optional[bool] = None,
262
+ **kwargs,
263
+ ) -> MaskedLMOutput:
264
+ return_dict = True if return_dict is None else return_dict
265
+ outputs = self.lfm2(
266
+ input_ids=input_ids,
267
+ attention_mask=attention_mask,
268
+ position_ids=position_ids,
269
+ inputs_embeds=inputs_embeds,
270
+ use_cache=False,
271
+ output_attentions=output_attentions,
272
+ output_hidden_states=output_hidden_states,
273
+ return_dict=True,
274
+ )
275
+ hidden = outputs.last_hidden_state
276
+ logits = self.lm_head(hidden)
277
+
278
+ loss = None
279
+ if labels is not None:
280
+ loss = F.cross_entropy(
281
+ logits.view(-1, self.config.vocab_size),
282
+ labels.view(-1),
283
+ ignore_index=-100,
284
+ )
285
+
286
+ if not return_dict:
287
+ out = (logits,) + outputs[1:]
288
+ return ((loss,) + out) if loss is not None else out
289
+ return MaskedLMOutput(
290
+ loss=loss,
291
+ logits=logits,
292
+ hidden_states=outputs.hidden_states,
293
+ attentions=outputs.attentions,
294
+ )
295
+
296
+
297
+ # --------------------------------------------------------------------------- #
298
+ # Optional "AutoModel" entry: returns the base Lfm2Model (for embeddings) #
299
+ # --------------------------------------------------------------------------- #
300
+
301
+
302
+ class Lfm2BidirModel(PreTrainedModel):
303
+ """Lfm2 bidirectional encoder base — no MLM head.
304
+
305
+ Useful for sentence-transformers / embedding pipelines. Returns hidden
306
+ states; downstream can mean-pool, CLS-pool, etc.
307
+ """
308
+
309
+ config_class = Lfm2Config
310
+ base_model_prefix = "lfm2"
311
+
312
+ _keys_to_ignore_on_load_missing = Lfm2BidirForMaskedLM._keys_to_ignore_on_load_missing
313
+ _keys_to_ignore_on_load_unexpected = Lfm2BidirForMaskedLM._keys_to_ignore_on_load_unexpected
314
+
315
+ def __init__(self, config: Lfm2Config):
316
+ super().__init__(config)
317
+ _install_bidirectional_patches_once()
318
+ config = type(config).from_dict({**config.to_dict(), "use_cache": False})
319
+ self.config = config
320
+ self.lfm2 = _lfm2_mod.Lfm2Model(config)
321
+ _swap_shortconv_to_bidir(self.lfm2, config)
322
+ self.post_init()
323
+
324
+ def forward(
325
+ self,
326
+ input_ids: Optional[torch.LongTensor] = None,
327
+ attention_mask: Optional[torch.Tensor] = None,
328
+ position_ids: Optional[torch.LongTensor] = None,
329
+ inputs_embeds: Optional[torch.FloatTensor] = None,
330
+ output_hidden_states: Optional[bool] = None,
331
+ output_attentions: Optional[bool] = None,
332
+ return_dict: Optional[bool] = None,
333
+ **kwargs,
334
+ ) -> BaseModelOutput:
335
+ return_dict = True if return_dict is None else return_dict
336
+ outputs = self.lfm2(
337
+ input_ids=input_ids,
338
+ attention_mask=attention_mask,
339
+ position_ids=position_ids,
340
+ inputs_embeds=inputs_embeds,
341
+ use_cache=False,
342
+ output_attentions=output_attentions,
343
+ output_hidden_states=output_hidden_states,
344
+ return_dict=True,
345
+ )
346
+ if not return_dict:
347
+ return (outputs.last_hidden_state,) + tuple(
348
+ v for v in (outputs.hidden_states, outputs.attentions) if v is not None
349
+ )
350
+ return BaseModelOutput(
351
+ last_hidden_state=outputs.last_hidden_state,
352
+ hidden_states=outputs.hidden_states,
353
+ attentions=outputs.attentions,
354
+ )
355
+
356
+
357
+ # ---- token-classification head (inlined; self-contained) ----
358
+ from transformers.modeling_outputs import TokenClassifierOutput
359
+
360
+ class Lfm2BidirForTokenClassification(PreTrainedModel):
361
+ config_class = Lfm2Config
362
+ base_model_prefix = "lfm2"
363
+ supports_gradient_checkpointing = True
364
+ # slim ckpt lacks the stripped conv projections; the ckpt's lm_head is unused here
365
+ _keys_to_ignore_on_load_missing = list(Lfm2BidirForMaskedLM._keys_to_ignore_on_load_missing)
366
+ _keys_to_ignore_on_load_unexpected = list(
367
+ Lfm2BidirForMaskedLM._keys_to_ignore_on_load_missing
368
+ ) + [r"lm_head\.weight"]
369
+
370
+ def __init__(self, config: Lfm2Config):
371
+ super().__init__(config)
372
+ _install_bidirectional_patches_once()
373
+
374
+ # no KV cache for an encoder
375
+ config = type(config).from_dict({**config.to_dict(), "use_cache": False})
376
+ self.config = config
377
+ self.num_labels = config.num_labels
378
+
379
+ self.lfm2 = _lfm2_mod.Lfm2Model(config)
380
+ # swap shortconv -> symmetric bidirectional BEFORE state_dict load so the
381
+ # module tree matches the slim safetensors (conv.weight only)
382
+ _swap_shortconv_to_bidir(self.lfm2, config)
383
+
384
+ drop = (getattr(config, "classifier_dropout", None)
385
+ or getattr(config, "hidden_dropout", None) or 0.1)
386
+ self.dropout = nn.Dropout(drop)
387
+ self.classifier = nn.Linear(config.hidden_size, config.num_labels)
388
+ self.label_smoothing = float(getattr(config, "label_smoothing", 0.0))
389
+
390
+ # inverse-frequency class weights; set by the trainer, defaults to ones
391
+ self.register_buffer("class_weights", torch.ones(config.num_labels), persistent=False)
392
+ self.post_init()
393
+
394
+ def set_class_weights(self, weights: torch.Tensor) -> None:
395
+ if weights.shape != (self.num_labels,):
396
+ raise ValueError(f"class_weights must be ({self.num_labels},), got {tuple(weights.shape)}")
397
+ self.class_weights = weights.to(device=self.class_weights.device, dtype=torch.float32)
398
+
399
+ def get_input_embeddings(self):
400
+ return self.lfm2.embed_tokens
401
+
402
+ def set_input_embeddings(self, value):
403
+ self.lfm2.embed_tokens = value
404
+
405
+ def forward(
406
+ self,
407
+ input_ids: Optional[torch.LongTensor] = None,
408
+ attention_mask: Optional[torch.Tensor] = None,
409
+ labels: Optional[torch.LongTensor] = None,
410
+ position_ids: Optional[torch.LongTensor] = None,
411
+ inputs_embeds: Optional[torch.FloatTensor] = None,
412
+ output_hidden_states: Optional[bool] = None,
413
+ output_attentions: Optional[bool] = None,
414
+ return_dict: Optional[bool] = None,
415
+ **kwargs,
416
+ ) -> TokenClassifierOutput:
417
+ return_dict = True if return_dict is None else return_dict
418
+ outputs = self.lfm2(
419
+ input_ids=input_ids,
420
+ attention_mask=attention_mask,
421
+ position_ids=position_ids,
422
+ inputs_embeds=inputs_embeds,
423
+ use_cache=False,
424
+ output_attentions=output_attentions,
425
+ output_hidden_states=output_hidden_states,
426
+ return_dict=True,
427
+ )
428
+ hidden = self.dropout(outputs.last_hidden_state)
429
+ logits = self.classifier(hidden)
430
+
431
+ loss = None
432
+ if labels is not None:
433
+ loss = F.cross_entropy(
434
+ logits.view(-1, self.num_labels).float(),
435
+ labels.view(-1),
436
+ weight=self.class_weights.float(),
437
+ ignore_index=-100,
438
+ label_smoothing=self.label_smoothing,
439
+ )
440
+
441
+ if not return_dict:
442
+ out = (logits,) + outputs[1:]
443
+ return ((loss,) + out) if loss is not None else out
444
+ return TokenClassifierOutput(
445
+ loss=loss,
446
+ logits=logits,
447
+ hidden_states=outputs.hidden_states,
448
+ attentions=outputs.attentions,
449
+ )
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|startoftext|>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "eos_token": "<|im_end|>",
6
+ "extra_special_tokens": [],
7
+ "is_local": false,
8
+ "legacy": false,
9
+ "local_files_only": false,
10
+ "mask_token": "<|mask|>",
11
+ "model_input_names": [
12
+ "input_ids",
13
+ "attention_mask"
14
+ ],
15
+ "model_max_length": 1000000000000000019884624838656,
16
+ "model_specific_special_tokens": {},
17
+ "pad_token": "<|pad|>",
18
+ "sp_model_kwargs": {},
19
+ "spaces_between_special_tokens": false,
20
+ "tokenizer_class": "TokenizersBackend",
21
+ "use_default_system_prompt": false,
22
+ "use_fast": true
23
+ }