ThingsAI commited on
Commit
5434506
·
verified ·
1 Parent(s): 21eee9a

Upload ThingsLID multi-task model (epoch 3, avg acc 88.0%)

Browse files
Files changed (5) hide show
  1. README.md +133 -0
  2. label_maps.json +395 -0
  3. model.pt +3 -0
  4. model.py +86 -0
  5. task_configs.json +6 -0
README.md ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language: multilingual
3
+ license: apache-2.0
4
+ tags:
5
+ - language-identification
6
+ - text-classification
7
+ - programming-language-detection
8
+ - spam-detection
9
+ - toxicity-detection
10
+ - byte-level
11
+ - multi-task
12
+ - lightweight
13
+ pipeline_tag: text-classification
14
+ ---
15
+
16
+ # ThingsLID — Multi-Task Byte-Level Text Classifier
17
+
18
+ **4M parameters** · **5 tasks** · **No tokenizer needed** · **Runs on CPU**
19
+
20
+ ThingsLID is an ultra-compact multi-task text classification model that operates directly on raw UTF-8 bytes. A single shared backbone serves 5 classification heads simultaneously.
21
+
22
+ ## Tasks & Performance
23
+
24
+ | Task | Labels | Val Accuracy | Description |
25
+ |------|--------|-------------|-------------|
26
+ | `lang_id` | 351 | 94.3% | Language identification |
27
+ | `prog_lang` | 30 | 94.4% | Programming language detection |
28
+ | `spam` | 2 | 96.1% | Spam vs ham classification |
29
+ | `toxic` | 2 | 67.3% | Toxicity detection (multilingual) |
30
+
31
+ **Average validation accuracy: 88.0%**
32
+
33
+ ## Architecture
34
+
35
+ ThingsLID uses a byte-level hybrid architecture inspired by [CommonLingua](https://huggingface.co/PleIAs/CommonLingua):
36
+
37
+ - **Input**: Raw UTF-8 bytes (no tokenizer), padded to 512 bytes
38
+ - **Trigram hash embedding**: Polynomial rolling hash of byte 3-grams → 8192-bucket embedding table
39
+ - **Byte unigram embedding**: Standard embedding for individual bytes
40
+ - **4× Conv1D blocks**: Causal convolutions + BatchNorm + GELU + residual
41
+ - **2× Bidirectional attention**: Multi-head self-attention with RoPE
42
+ - **Global average pooling** → per-task classification heads
43
+
44
+ Total: ~4M shared parameters + small per-task linear heads.
45
+
46
+ ## Usage
47
+
48
+ ```python
49
+ import torch
50
+ from huggingface_hub import hf_hub_download
51
+
52
+ # Download and load
53
+ ckpt_path = hf_hub_download("ThingAI/ThingsLID", "model.pt")
54
+ ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
55
+
56
+ # Reconstruct model (see model.py in repo)
57
+ from model import MultiTaskLID
58
+ model = MultiTaskLID(ckpt["task_configs"]).eval()
59
+ model.load_state_dict(ckpt["model"])
60
+
61
+ # Predict
62
+ def predict(text, task="lang_id", top_k=3):
63
+ raw = text.encode("utf-8")[:512]
64
+ byte_ids = list(raw) + [0] * (512 - len(raw))
65
+ inp = torch.tensor([byte_ids], dtype=torch.long)
66
+ with torch.no_grad():
67
+ logits = model(inp, task)["logits"][0]
68
+ probs = torch.softmax(logits, dim=-1)
69
+ idx2label = {i: l for l, i in ckpt["label_maps"][task].items()}
70
+ topk = probs.topk(top_k)
71
+ return [(idx2label[topk.indices[i].item()], topk.values[i].item()) for i in range(top_k)]
72
+
73
+ # Examples
74
+ print(predict("La pizza napoletana è patrimonio UNESCO", task="lang_id"))
75
+ # [('ita', 0.98), ('cos', 0.003), ...]
76
+
77
+ print(predict("def foo(x): return x + 1", task="prog_lang"))
78
+ # [('Python', 0.95), ...]
79
+
80
+ print(predict("You won a FREE iPhone!!!", task="spam"))
81
+ # [('spam', 0.99), ...]
82
+ ```
83
+
84
+ ## Quick predict script
85
+
86
+ ```bash
87
+ python predict.py --text "Ciao, come stai?"
88
+ # lang_id: ita (98.2%)
89
+
90
+ python predict.py --task prog_lang --text "fn main() { println!("hello"); }"
91
+ # prog_lang: Rust (97.1%)
92
+
93
+ python predict.py --task spam --text "URGENT: Click here to win!"
94
+ # spam: spam (99.5%)
95
+ ```
96
+
97
+ ## Training Data
98
+
99
+ | Task | Dataset | Samples |
100
+ |------|---------|---------|
101
+ | Language ID | [PleIAs/CommonLingua-Train](https://huggingface.co/datasets/PleIAs/CommonLingua-Train) | 200K (capped) |
102
+ | Programming Language | [cakiki/rosetta-code](https://huggingface.co/datasets/cakiki/rosetta-code) | ~26K |
103
+ | Spam Detection | [ucirvine/sms_spam](https://huggingface.co/datasets/ucirvine/sms_spam) | 5.5K |
104
+ | Toxicity | [textdetox/multilingual_toxicity_dataset](https://huggingface.co/datasets/textdetox/multilingual_toxicity_dataset) | ~45K |
105
+
106
+ ## Key Design Decisions
107
+
108
+ - **Byte-level input**: No tokenizer means it works on any language, script, or encoding without preprocessing
109
+ - **Trigram hashing**: +1.2 F1 over unigram-only baseline, acts as regularization via hash collisions
110
+ - **Multi-task learning**: Shared backbone learns universal text representations; per-task heads are tiny (~130K params each)
111
+ - **No attention masking**: Bidirectional attention for classification (not causal)
112
+ - **OneCycleLR**: Fast convergence in few epochs
113
+
114
+ ## Limitations
115
+
116
+ - Designed for paragraph-level classification (50+ bytes). Short texts (<20 bytes) may be unreliable
117
+ - Toxicity detection accuracy is lower than specialized models (trained on limited multilingual data)
118
+ - Programming language detection trained on Rosetta Code samples which have a specific style
119
+
120
+ ## Citation
121
+
122
+ ```bibtex
123
+ @misc{thingslid2026,
124
+ author = {ThingAI},
125
+ title = {ThingsLID: Multi-Task Byte-Level Text Classifier},
126
+ year = {2026},
127
+ url = {https://huggingface.co/ThingAI/ThingsLID}
128
+ }
129
+ ```
130
+
131
+ ## License
132
+
133
+ Apache 2.0
label_maps.json ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "lang_id": {
3
+ "abk": 0,
4
+ "ace": 1,
5
+ "ach": 2,
6
+ "ady": 3,
7
+ "afr": 4,
8
+ "aka": 5,
9
+ "akk": 6,
10
+ "alt": 7,
11
+ "amh": 8,
12
+ "ami": 9,
13
+ "ang": 10,
14
+ "anp": 11,
15
+ "arb": 12,
16
+ "arc": 13,
17
+ "arg": 14,
18
+ "arq": 15,
19
+ "ary": 16,
20
+ "arz": 17,
21
+ "asm": 18,
22
+ "ast": 19,
23
+ "atj": 20,
24
+ "ava": 21,
25
+ "avk": 22,
26
+ "awa": 23,
27
+ "aym": 24,
28
+ "azb": 25,
29
+ "azj": 26,
30
+ "bak": 27,
31
+ "bam": 28,
32
+ "ban": 29,
33
+ "bar": 30,
34
+ "bbc": 31,
35
+ "bel": 32,
36
+ "bem": 33,
37
+ "ben": 34,
38
+ "bho": 35,
39
+ "bik": 36,
40
+ "bis": 37,
41
+ "bjn": 38,
42
+ "blk": 39,
43
+ "bod": 40,
44
+ "bos": 41,
45
+ "bpy": 42,
46
+ "bre": 43,
47
+ "bug": 44,
48
+ "bul": 45,
49
+ "bxr": 46,
50
+ "bzj": 47,
51
+ "cat": 48,
52
+ "cbk": 49,
53
+ "cdo": 50,
54
+ "ceb": 51,
55
+ "ces": 52,
56
+ "che": 53,
57
+ "chr": 54,
58
+ "chu": 55,
59
+ "chv": 56,
60
+ "chy": 57,
61
+ "ckb": 58,
62
+ "cor": 59,
63
+ "cos": 60,
64
+ "crh": 61,
65
+ "csb": 62,
66
+ "cym": 63,
67
+ "dag": 64,
68
+ "dan": 65,
69
+ "dcr": 66,
70
+ "deu": 67,
71
+ "dga": 68,
72
+ "din": 69,
73
+ "dip": 70,
74
+ "diq": 71,
75
+ "div": 72,
76
+ "dsb": 73,
77
+ "dty": 74,
78
+ "dzo": 75,
79
+ "egx": 76,
80
+ "egy": 77,
81
+ "ell": 78,
82
+ "elx": 79,
83
+ "eml": 80,
84
+ "eng": 81,
85
+ "epo": 82,
86
+ "est": 83,
87
+ "ett": 84,
88
+ "eus": 85,
89
+ "ewe": 86,
90
+ "ext": 87,
91
+ "fao": 88,
92
+ "fas": 89,
93
+ "fat": 90,
94
+ "fij": 91,
95
+ "fil": 92,
96
+ "fin": 93,
97
+ "fon": 94,
98
+ "fra": 95,
99
+ "fro": 96,
100
+ "frp": 97,
101
+ "frr": 98,
102
+ "fry": 99,
103
+ "ful": 100,
104
+ "fur": 101,
105
+ "gag": 102,
106
+ "gan": 103,
107
+ "gaz": 104,
108
+ "gcr": 105,
109
+ "gez": 106,
110
+ "gla": 107,
111
+ "gle": 108,
112
+ "glg": 109,
113
+ "glk": 110,
114
+ "glv": 111,
115
+ "gom": 112,
116
+ "gor": 113,
117
+ "got": 114,
118
+ "gpe": 115,
119
+ "grc": 116,
120
+ "gsw": 117,
121
+ "guc": 118,
122
+ "gug": 119,
123
+ "guj": 120,
124
+ "gur": 121,
125
+ "guw": 122,
126
+ "hak": 123,
127
+ "hat": 124,
128
+ "hau": 125,
129
+ "haw": 126,
130
+ "hbo": 127,
131
+ "hbs": 128,
132
+ "heb": 129,
133
+ "hif": 130,
134
+ "hin": 131,
135
+ "hit": 132,
136
+ "hrv": 133,
137
+ "hsb": 134,
138
+ "hun": 135,
139
+ "hye": 136,
140
+ "hyw": 137,
141
+ "ibo": 138,
142
+ "ido": 139,
143
+ "iku": 140,
144
+ "ile": 141,
145
+ "ilo": 142,
146
+ "ina": 143,
147
+ "ind": 144,
148
+ "inh": 145,
149
+ "ipk": 146,
150
+ "isl": 147,
151
+ "ita": 148,
152
+ "jam": 149,
153
+ "jav": 150,
154
+ "jbo": 151,
155
+ "jpn": 152,
156
+ "kaa": 153,
157
+ "kab": 154,
158
+ "kal": 155,
159
+ "kan": 156,
160
+ "kas": 157,
161
+ "kat": 158,
162
+ "kaz": 159,
163
+ "kbd": 160,
164
+ "kbp": 161,
165
+ "kcg": 162,
166
+ "khk": 163,
167
+ "khm": 164,
168
+ "kik": 165,
169
+ "kin": 166,
170
+ "kir": 167,
171
+ "kmr": 168,
172
+ "koi": 169,
173
+ "kok": 170,
174
+ "kom": 171,
175
+ "kon": 172,
176
+ "kor": 173,
177
+ "kpe": 174,
178
+ "krc": 175,
179
+ "ksh": 176,
180
+ "lad": 177,
181
+ "lao": 178,
182
+ "lat": 179,
183
+ "latex": 180,
184
+ "lav": 181,
185
+ "lbe": 182,
186
+ "lez": 183,
187
+ "lfn": 184,
188
+ "lij": 185,
189
+ "lim": 186,
190
+ "lin": 187,
191
+ "lit": 188,
192
+ "lld": 189,
193
+ "lmo": 190,
194
+ "lrc": 191,
195
+ "ltg": 192,
196
+ "ltz": 193,
197
+ "lug": 194,
198
+ "luo": 195,
199
+ "lzh": 196,
200
+ "mad": 197,
201
+ "mai": 198,
202
+ "mal": 199,
203
+ "mar": 200,
204
+ "mdf": 201,
205
+ "mhr": 202,
206
+ "min": 203,
207
+ "mkc": 204,
208
+ "mkd": 205,
209
+ "mlg": 206,
210
+ "mlt": 207,
211
+ "mni": 208,
212
+ "mnw": 209,
213
+ "mri": 210,
214
+ "mrj": 211,
215
+ "msa": 212,
216
+ "mwl": 213,
217
+ "mya": 214,
218
+ "myv": 215,
219
+ "mzn": 216,
220
+ "nah": 217,
221
+ "nan": 218,
222
+ "nap": 219,
223
+ "nav": 220,
224
+ "nds": 221,
225
+ "nep": 222,
226
+ "new": 223,
227
+ "nia": 224,
228
+ "nld": 225,
229
+ "nno": 226,
230
+ "nor": 227,
231
+ "nov": 228,
232
+ "nqo": 229,
233
+ "nrf": 230,
234
+ "nso": 231,
235
+ "nya": 232,
236
+ "nyn": 233,
237
+ "oci": 234,
238
+ "olo": 235,
239
+ "orm": 236,
240
+ "ory": 237,
241
+ "oss": 238,
242
+ "pag": 239,
243
+ "pam": 240,
244
+ "pan": 241,
245
+ "pap": 242,
246
+ "pcd": 243,
247
+ "pcm": 244,
248
+ "pdc": 245,
249
+ "peo": 246,
250
+ "pfl": 247,
251
+ "pih": 248,
252
+ "pli": 249,
253
+ "pms": 250,
254
+ "pnb": 251,
255
+ "pnt": 252,
256
+ "pol": 253,
257
+ "por": 254,
258
+ "pus": 255,
259
+ "pwn": 256,
260
+ "quy": 257,
261
+ "rcf": 258,
262
+ "rmy": 259,
263
+ "roh": 260,
264
+ "ron": 261,
265
+ "rue": 262,
266
+ "run": 263,
267
+ "rup": 264,
268
+ "rus": 265,
269
+ "sah": 266,
270
+ "san": 267,
271
+ "sas": 268,
272
+ "sat": 269,
273
+ "scn": 270,
274
+ "sgs": 271,
275
+ "shi": 272,
276
+ "shn": 273,
277
+ "sin": 274,
278
+ "skr": 275,
279
+ "slk": 276,
280
+ "slv": 277,
281
+ "sme": 278,
282
+ "smn": 279,
283
+ "smo": 280,
284
+ "sna": 281,
285
+ "snd": 282,
286
+ "som": 283,
287
+ "sot": 284,
288
+ "spa": 285,
289
+ "sqi": 286,
290
+ "srd": 287,
291
+ "srn": 288,
292
+ "srp": 289,
293
+ "ssw": 290,
294
+ "stq": 291,
295
+ "sun": 292,
296
+ "sux": 293,
297
+ "swe": 294,
298
+ "swh": 295,
299
+ "szl": 296,
300
+ "szy": 297,
301
+ "tah": 298,
302
+ "tam": 299,
303
+ "tat": 300,
304
+ "tay": 301,
305
+ "tcy": 302,
306
+ "tel": 303,
307
+ "tet": 304,
308
+ "tgk": 305,
309
+ "tgl": 306,
310
+ "tha": 307,
311
+ "tir": 308,
312
+ "tly": 309,
313
+ "ton": 310,
314
+ "tpi": 311,
315
+ "trv": 312,
316
+ "tsn": 313,
317
+ "tso": 314,
318
+ "tuk": 315,
319
+ "tum": 316,
320
+ "tur": 317,
321
+ "txb": 318,
322
+ "tyv": 319,
323
+ "udm": 320,
324
+ "uig": 321,
325
+ "ukr": 322,
326
+ "urd": 323,
327
+ "uzb": 324,
328
+ "uzn": 325,
329
+ "vec": 326,
330
+ "ven": 327,
331
+ "vep": 328,
332
+ "vie": 329,
333
+ "vls": 330,
334
+ "vol": 331,
335
+ "vro": 332,
336
+ "war": 333,
337
+ "wln": 334,
338
+ "wol": 335,
339
+ "wuu": 336,
340
+ "xal": 337,
341
+ "xcl": 338,
342
+ "xho": 339,
343
+ "xmf": 340,
344
+ "xog": 341,
345
+ "xto": 342,
346
+ "ydd": 343,
347
+ "yor": 344,
348
+ "yue": 345,
349
+ "zea": 346,
350
+ "zgh": 347,
351
+ "zha": 348,
352
+ "zho": 349,
353
+ "zul": 350
354
+ },
355
+ "prog_lang": {
356
+ "11l": 0,
357
+ "Ada": 1,
358
+ "C": 2,
359
+ "C#": 3,
360
+ "C++": 4,
361
+ "D": 5,
362
+ "Factor": 6,
363
+ "FreeBASIC": 7,
364
+ "Go": 8,
365
+ "Haskell": 9,
366
+ "J": 10,
367
+ "Java": 11,
368
+ "Julia": 12,
369
+ "Kotlin": 13,
370
+ "Lua": 14,
371
+ "Nim": 15,
372
+ "Perl": 16,
373
+ "Phix": 17,
374
+ "PicoLisp": 18,
375
+ "Python": 19,
376
+ "REXX": 20,
377
+ "Racket": 21,
378
+ "Raku": 22,
379
+ "Ruby": 23,
380
+ "Rust": 24,
381
+ "Scala": 25,
382
+ "Sidef": 26,
383
+ "Tcl": 27,
384
+ "Wren": 28,
385
+ "zkl": 29
386
+ },
387
+ "spam": {
388
+ "ham": 0,
389
+ "spam": 1
390
+ },
391
+ "toxic": {
392
+ "non-toxic": 0,
393
+ "toxic": 1
394
+ }
395
+ }
model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2f5b2455a714c855abb4cda333f3e12223bafefb6297610ce0a63a23daa6340b
3
+ size 14823241
model.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ThingsLID model definition — standalone, no dependencies beyond PyTorch."""
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+
8
+ class TrigramHashEmbedding(nn.Module):
9
+ def __init__(self, n_buckets=8192, d_embed=64, prime=31):
10
+ super().__init__()
11
+ self.n_buckets, self.prime = n_buckets, prime
12
+ self.embed = nn.Embedding(n_buckets, d_embed)
13
+ def forward(self, x):
14
+ xp = F.pad(x.long(), (2, 0), value=0)
15
+ h = (xp[:, :-2] * self.prime * self.prime + xp[:, 1:-1] * self.prime + xp[:, 2:]) % self.n_buckets
16
+ return self.embed(h)
17
+
18
+
19
+ class RoPE(nn.Module):
20
+ def __init__(self, head_dim, max_len=1024, theta=10000.0):
21
+ super().__init__()
22
+ inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2).float() / head_dim))
23
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
24
+ self._build(max_len)
25
+ def _build(self, seq_len):
26
+ t = torch.arange(seq_len, device=self.inv_freq.device).float()
27
+ freqs = torch.outer(t, self.inv_freq); emb = torch.cat([freqs, freqs], dim=-1)
28
+ self.register_buffer("cos", emb.cos()[None, None], persistent=False)
29
+ self.register_buffer("sin", emb.sin()[None, None], persistent=False); self._max = seq_len
30
+ @staticmethod
31
+ def _rotate(x):
32
+ x1, x2 = x.chunk(2, dim=-1); return torch.cat([-x2, x1], dim=-1)
33
+ def forward(self, q, k):
34
+ T = q.size(2)
35
+ if T > self._max: self._build(T)
36
+ c, s = self.cos[:,:,:T], self.sin[:,:,:T]
37
+ return q*c + self._rotate(q)*s, k*c + self._rotate(k)*s
38
+
39
+
40
+ class Attention(nn.Module):
41
+ def __init__(self, d_model, n_heads):
42
+ super().__init__()
43
+ self.n_heads, self.head_dim = n_heads, d_model // n_heads
44
+ self.qkv = nn.Linear(d_model, 3 * d_model); self.out = nn.Linear(d_model, d_model)
45
+ self.rope = RoPE(self.head_dim); self.norm = nn.LayerNorm(d_model)
46
+ def forward(self, x):
47
+ res = x; x = self.norm(x); B, T, C = x.shape
48
+ qkv = self.qkv(x).view(B, T, 3, self.n_heads, self.head_dim)
49
+ q, k, v = qkv.permute(2, 0, 3, 1, 4); q, k = self.rope(q, k)
50
+ out = F.scaled_dot_product_attention(q, k, v, is_causal=False)
51
+ return res + self.out(out.transpose(1, 2).contiguous().view(B, T, C))
52
+
53
+
54
+ class ConvBlock(nn.Module):
55
+ def __init__(self, d_in, d_out, kernel=3):
56
+ super().__init__()
57
+ self.conv = nn.Conv1d(d_in, d_out, kernel, padding=kernel // 2)
58
+ self.bn = nn.BatchNorm1d(d_out)
59
+ self.residual = nn.Conv1d(d_in, d_out, 1) if d_in != d_out else nn.Identity()
60
+ def forward(self, x): return F.gelu(self.bn(self.conv(x))) + self.residual(x)
61
+
62
+
63
+ class MultiTaskLID(nn.Module):
64
+ """
65
+ ThingsLID: Multi-task byte-level text classifier.
66
+ ~4M shared parameters + per-task classification heads.
67
+ """
68
+ def __init__(self, task_configs, max_len=512, d_byte=64, d_tri=64,
69
+ n_buckets=8192, d_model=384, n_conv=4, n_attn=2, n_heads=6, dropout=0.0):
70
+ super().__init__()
71
+ self.max_len = max_len; self.d_model = d_model
72
+ self.byte_embed = nn.Embedding(256, d_byte)
73
+ self.tri_embed = TrigramHashEmbedding(n_buckets, d_tri)
74
+ self.proj = nn.Linear(d_byte + d_tri, d_model)
75
+ self.convs = nn.ModuleList([ConvBlock(d_model, d_model, 3) for _ in range(n_conv)])
76
+ self.attns = nn.ModuleList([Attention(d_model, n_heads) for _ in range(n_attn)])
77
+ self.drop = nn.Dropout(dropout); self.norm = nn.LayerNorm(d_model)
78
+ self.heads = nn.ModuleDict({t: nn.Linear(d_model, n) for t, n in task_configs.items()})
79
+
80
+ def forward(self, x, task):
81
+ h = self.proj(torch.cat([self.byte_embed(x), self.tri_embed(x)], dim=-1))
82
+ h = h.transpose(1, 2)
83
+ for conv in self.convs: h = conv(h)
84
+ h = h.transpose(1, 2)
85
+ for attn in self.attns: h = attn(h)
86
+ return {"logits": self.heads[task](self.drop(self.norm(h).mean(dim=1)))}
task_configs.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "lang_id": 351,
3
+ "prog_lang": 30,
4
+ "spam": 2,
5
+ "toxic": 2
6
+ }