ainouche-abderahmane commited on
Commit
d435fba
·
verified ·
1 Parent(s): b954cde

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -7,6 +7,7 @@ language:
7
  - tzm
8
  - shy
9
  license: apache-2.0
 
10
  tags:
11
  - berber
12
  - amazigh
@@ -17,7 +18,6 @@ tags:
17
  - tamazight
18
  - shawiya
19
  - language-identification
20
- - metric-learning
21
  - conformal-prediction
22
  - low-resource
23
  pipeline_tag: text-classification
@@ -29,335 +29,334 @@ model-index:
29
  results:
30
  - task:
31
  type: text-classification
32
- name: Berber language identification across six Latin-script varieties
33
  dataset:
34
- type: agbalu/BerberLID-Eval
35
- name: Held-out benchmark, 1,500 sentences balanced across six varieties
36
  metrics:
37
  - type: accuracy
38
  value: 0.8552
39
- name: Overall accuracy (7 classes)
40
  - type: f1
41
  value: 0.8562
42
- name: Macro-F1 over six varieties and NOT_AMAZIGH
43
  ---
44
 
45
  # Dihya-5M
46
 
47
- A 5.19M-parameter byte-level Conv-Transformer for **Berber Language Identification**, trained from
48
- scratch to distinguish six Latin-script Amazigh varieties against each other and an explicit
49
- `NOT_AMAZIGH` rejection class: Kabyle (`kab_Latn`), Tashelhit (`shi_Latn`), Tarifit (`rif_Latn`),
50
- Tamasheq (`taq_Latn`), Central Atlas Tamazight (`tzm_Latn`), and Shawiya (`shy_Latn`).
51
 
52
- It reaches **85.52% overall accuracy** and **0.8562 macro-F1** on a balanced, source-stratified
53
- held-out benchmark of 1,500 sentences against 72.40% accuracy and 0.7023 macro-F1 for an
54
- unstratified baseline. The decisive gain is on **Shawiya: from 0.2222 to 0.7923 F1, a 3.
55
- improvement on the language the baseline abandons.**
56
 
57
- It ships with pre-calibrated **split-conformal prediction sets** (98.78% empirical coverage, mean
58
- set size 1.098) for pipelines where a silent misclassification is worse than a widened label.
59
 
60
- It is, as far as we can establish, **the first multi-dialect language identifier published with
61
- conformal safety guarantees for the Berber language family.**
62
 
63
- Named after **Dihya** (also known as *al-Kāhina*, c. 650–702 CE), Amazigh queen and military
64
- commander of the Aurès Mountains who unified the Berber tribes across North Africa.
 
 
65
 
66
- ## The task
 
 
 
67
 
68
- Berber language identification is not classifying typologically distant languages. The six varieties
69
- here share 80–90% of their core vocabulary: *aman* (water), *argaz* (man), *taddart* (village),
70
- *awal* (word). A naive model that reads shared pan-Berber roots will be confidently wrong on
71
- anything short of a long, idiom-dense sentence.
72
 
73
- | Variety | Code | Diagnostic signal |
74
- |---|---|---|
75
- | Kabyle | `kab_Latn` | Spirantized stops `ḍ ṭ ṛ`, Schwa written `e`, interrogative `amek` |
76
- | Tashelhit | `shi_Latn` | Unspirantized stops, intensifier `bahra`, vocative `aytma` |
77
- | Tarifit | `rif_Latn` | Spirantized stops, interrogative `mayemmi` / `mamec`, clitics `nnecm` |
78
- | Tamasheq | `taq_Latn` | Explicit short vowels `ə`, Saharan particle `ahal`, root `əmərəw` |
79
- | Central Atlas | `tzm_Latn` | Preposition `g` (not `deg`), root `wammas`, shared Morocco phonotactics |
80
- | Shawiya | `shy_Latn` | Relative `mḍa`, double clitics `acek-acem`, Aurès toponym `Awras` |
81
- | Non-Amazigh | `NOT_AMAZIGH` | French, English, Arabic, Spanish, Latin, other |
82
 
83
- The three structural traps that collapse standard models on this task:
84
 
85
- **The long-tail imbalance trap.** Kabyle has tens of thousands of open-corpus lines; Shawiya has a
86
- few hundred. Under empirical risk minimisation the gradient from Kabyle overwhelms Shawiya, driving
87
- its recall to 22% or below.
88
 
89
- **The domain confound trap.** If one variety is represented entirely by Bible translations and
90
- another entirely by dictionary glosses, a model learns biblical syntax vs. internet vocabulary —
91
- not dialect phonotactics. It scores well on training data and collapses when the domain shifts.
 
 
92
 
93
- **The subword fragmentation trap.** BPE tokenizers trained on Kabyle fragment cognates in Tamasheq
94
- or Shawiya into arbitrary byte-pair shards, destroying the morphological alignment that carries the
95
- discriminative signal.
 
96
 
97
- ## Results
 
98
 
99
- **1,500 sentences drawn from the held-out benchmark** `data/processed/bench/lid-eval.jsonl` — 250
100
- per variety, disjoint from train (77,756) and dev (4,093), across balanced multi-domain sources.
 
 
 
 
 
101
 
102
- | system | accuracy | macro-F1 |
103
- |---|---|---|
104
- | **Dihya-5M** | **85.52%** | **0.8562** |
105
- | unstratified baseline | 72.40% | 0.7023 |
106
 
107
- Reproduce both rows in about fifteen seconds on a laptop, no GPU:
108
 
109
- ```bash
110
- make lid TASK=evaluate
111
- ```
112
 
113
- **The second row is the row that makes the first one mean something.** A classifier that does nothing
114
- unusual achieves 72.40% by getting Tamasheq and Tarifit right and abandoning Shawiya entirely.
115
- Dihya-5M's contribution is the +13.12 points above that floor, and the honest way to read 0.8562
116
- macro-F1 is as a **uniform lift across the tail, not a drift in the head classes**.
117
-
118
- | label | support | P | R | F1 | baseline F1 |
119
- |---|---|---|---|---|---|
120
- | `taq_Latn` | 250 | 0.888 | 0.948 | **0.917** | 0.943 |
121
- | `rif_Latn` | 250 | 0.865 | 0.925 | **0.894** | 0.777 |
122
- | `shi_Latn` | 250 | 0.885 | 0.885 | **0.885** | 0.874 |
123
- | `shy_Latn` | 250 | 0.785 | 0.800 | **0.792** | 0.222 |
124
- | `tzm_Latn` | 250 | 0.775 | 0.784 | **0.780** | 0.751 |
125
- | `kab_Latn` | 250 | 0.762 | 0.754 | **0.758** | 0.648 |
126
- | `NOT_AMAZIGH` | 250 | 0.970 | 0.965 | **0.967** | 0.000 |
127
-
128
- **Tamasheq is the single class where the baseline has an edge.** The baseline's 0.943 rests on
129
- Tamasheq being lexically the most distinct variety — Saharan vocabulary, explicit short vowels —
130
- so a bag-of-words signal is already strong there. Dihya-5M's 0.917 trades one point on the easy
131
- class for 57 points on Shawiya.
132
-
133
- **Shawiya is the deciding result.** 0.2222 baseline F1 means the variety is functionally
134
- unclassified. 0.7923 means 80% recall on held-out text, from Group-DRO reweighting that refuses
135
- to let the optimiser satisfy the average by ignoring the tail.
136
-
137
- **The conformal prediction sets carry their own guarantee.** Calibrated on 4,093 development
138
- sentences at target coverage 99.0%, empirical coverage on the test set is **98.78%**, with a mean
139
- set size of **1.098 labels**. A sentence of ambiguous shared pan-Berber proverb receives a set of
140
- two or three labels; a sentence with a clear dialectal marker receives a singleton. The guarantee
141
- is distribution-free; it does not depend on the model's calibration being correct.
142
-
143
- **The checkpoint shipped is `best.pt`**, selected on the development composite score
144
- ½(Macro-F1 + Worst-Group Recall) — ensuring the test set was not touched during model selection.
145
 
146
- ## Intended use
 
 
 
 
147
 
148
- Identifying Berber varieties and filtering non-Amazigh text in uncurated data pipelines:
 
149
 
150
- - **Corpus construction**: screening web scrapes and OCR output to tag each sentence with its
151
- dialect and discard non-Berber lines before they enter training corpora for downstream models.
152
- - **Pipeline routing**: sitting before dialect-specific models to route input to the appropriate
153
- one — orthography standardisation ([`agbalu/Boulifa-48M`](https://huggingface.co/agbalu/Boulifa-48M)),
154
- ASR post-processing ([`agbalu/Belaid-31M`](https://huggingface.co/agbalu/Belaid-31M)), or TTS
155
- ([`agbalu/Matoub-82M`](https://huggingface.co/agbalu/Matoub-82M)).
156
- - **Conformal filtering**: using prediction sets rather than point predictions when a misclassified
157
- dialect would corrupt a downstream corpus.
158
 
159
- **Not suitable for**: classifying Tifinagh or Arabic-script Berber text; making decisions about
160
- individuals on the basis of their regional variety; or distinguishing sub-varieties within any one
161
- of the six classes listed.
162
 
163
- ## Usage
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
- `torch` and `agbalu.lid.Identifier`, nothing else:
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
  ```python
168
- from pathlib import Path
169
- from agbalu.lid.infer import Identifier
170
-
171
- identifier = Identifier.load(Path("artifacts/runs/dihya"))
172
-
173
- predictions = identifier.predict([
174
- "Azul fell-awen, amek tettilim?",
175
- "Mayemmi ur teccid ticemrarin nnecm?",
176
- "Bonjour tout le monde.",
177
- ])
178
- for pred in predictions:
179
- print(pred.language, pred.confidence, pred.prediction_set)
180
- # kab_Latn 0.9987 ('kab_Latn',)
181
- # rif_Latn 1.0000 ('rif_Latn',)
182
- # NOT_AMAZIGH 0.9994 ('NOT_AMAZIGH',)
183
  ```
184
 
185
- Or from the command line:
 
 
 
186
 
187
- ```bash
188
- make lid TEXT="Mayemmi ur teccid ticemrarin nnecm a gma?"
189
- # rif_Latn 1.0000 set=('rif_Latn',)
 
 
 
 
 
 
 
 
190
  ```
191
 
192
- **Prediction sets widen gracefully along the continuum.** A sentence of shared pan-Berber vocabulary
193
- without a variety-specific marker will receive a set of two or three labels rather than a forced
194
- singleton with spurious confidence.
 
 
 
 
 
 
 
195
 
196
- **Input is Latin script.** Bytes are the vocabulary; a Tifinagh byte sequence produces a label, but
197
- that label has not been evaluated and should not be trusted.
198
 
199
  ## Architecture
200
 
201
  | | |
202
  |---|---|
203
- | Parameters | **5,187,712** |
 
204
  | Encoder layers | 6 |
205
  | Hidden / feed-forward | 256 / 704 (SwiGLU) |
206
- | Attention heads / head size | 8 / 32 |
207
- | Positions | rotary (RoPE) |
208
- | Conv stem | three parallel 1D depthwise-separable convolutions, kernels 3, 5, 7 |
209
- | Pooling | parametric attentive pooling |
210
- | Classification head | cosine head with LDAM additive margins |
211
- | Contrastive head | 2-layer projection, 256 128, memory bank 2,048 |
212
- | Vocabulary | 258 slots (256 raw UTF-8 bytes + PAD + UNK) |
213
- | Dropout | 0.10 |
214
-
215
- **The byte vocabulary.** The orthographies that separate Kabyle from Shawiya from Tamasheq differ in
216
- exactly the characters where the scripts diverge — `ɛ ɣ ḥ ḍ ṣ ṭ ẓ ṛ` are two or three UTF-8 bytes
217
- each. A byte model sees these distinctions as a short byte sequence rather than as one token it may
218
- never have seen in a minority language's training shard.
219
-
220
- **The convolutional stem.** Three depthwise-separable 1D convolutions (kernels 3, 5, 7) run in
221
- parallel over the byte embeddings before the encoder's first layer. Kernel 3 covers the grapheme
222
- trigrams that spirantization spans; kernel 5 covers consonantal root skeletons; kernel 7 covers
223
- the clitic complexes that are among the clearest regional markers.
224
-
225
- **Attentive pooling.** Replaces uniform averaging with a learned scalar score per byte position,
226
- so the representation concentrates on the 10–15% of positions carrying diagnostic morphology
227
- rather than the shared vowel sequences that surround it.
228
-
229
- **LDAM margins and Group-DRO.** Both operate on the same 20 (language, domain) cells. LDAM sets
230
- the angular margin for each class as $m_k = C / n_k^{1/4}$, widening the decision boundary for
231
- low-resource varieties. Group-DRO tracks the worst-performing cell and exponentiates its loss
232
- weight at each step, preventing the optimiser from satisfying the average by abandoning Shawiya.
233
 
234
- ## Training
 
235
 
236
- | | |
237
- |---|---|
238
- | training rows | 77,756 across 20 (language, domain) cells |
239
- | epochs | 8 |
240
- | checkpoint | step 2,898 of 5,176, selected on development composite score |
241
- | development score there | 0.9363 macro-F1, 0.8125 worst-group recall (½ = 0.8744) |
242
- | hardware | one A10G, ~18.3 minutes |
243
-
244
- Selection is on the composite score ½(Macro-F1 + Worst-Group Recall) and not on loss, and here
245
- that is not a formality. The final-epoch checkpoint (step 5,176) scores 97.87% dev accuracy — two
246
- points above step 2,898 — but its worst-group recall falls from 81.25% to 68.75% on the
247
- Shawiya/lexicon cell. Selecting on accuracy would have published a model that recovered Shawiya
248
- halfway and called it done.
249
-
250
- **Two-phase schedule.** The first 50% of steps (0–2,588) train on plain cross-entropy. At step
251
- 2,588, the LDAM margins switch on at `margin_scale = 0.5` and the Group-DRO exponentiated weights
252
- activate. This deferred scheme is from the LDAM paper and measured here: margins from step 0 drove
253
- the loss to 4.01 at step 120 where the same run without them reached 0.76. The margins are useful
254
- once the features exist and are an obstacle before that.
255
-
256
- **Contrastive head.** A 2-layer projection head (256 → 128) adds a supervised contrastive loss
257
- (weight 0.5) alongside the classification objective. Hard negatives are drawn from a memory bank of
258
- 2,048 embeddings, sampling *different languages in the same domain* — which forces the trunk to
259
- discard topical and stylistic features and learn pure dialectal phonotactics.
260
-
261
- ## Calibration
262
-
263
- Split-conformal calibration was run over the full 4,093-sentence development set after model
264
- selection, with target marginal coverage $1 - \alpha = 0.99$.
265
 
266
  | | |
267
  |---|---|
268
- | calibration size | 4,093 sentences |
269
- | target coverage | 99.0% |
270
- | empirical coverage on test | **98.78%** |
271
- | calibration quantile ($\hat{q}$) | 0.9979 |
272
- | mean prediction set size | **1.098** |
273
-
274
- The guarantee is exchangeability, not distributional assumptions on the model: if the development
275
- and test sets are exchangeable draws from the same source, the coverage holds regardless of how
276
- miscalibrated the raw softmax scores are.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  ## Limitations
279
 
280
- **Dialect continuum ambiguity on shared vocabulary.** A short sentence of pan-Berber core vocabulary
281
- without a variety-specific marker cannot be deterministically assigned to one variety — and should
282
- not be. The conformal sets flag this with size > 1; calling one label correct in that case would be
283
- over-claiming what the text contains.
284
 
285
- **Tifinagh and Arabic script are out of scope.** Dihya-5M is evaluated on Latin script only. Bytes
286
- from a Tifinagh sentence will produce a prediction, but that prediction has not been measured and
287
- should not be used.
288
 
289
- **Short inputs lose diagnostic context.** A single word especially a shared cognate — can arrive
290
- as `NOT_AMAZIGH` or as any variety. The model was fitted on natural sentences; single-word queries
291
- are not the input shape it was built for.
292
 
293
- **One language per sentence.** Code-switching between Berber varieties within a sentence is not
294
- handled. A sentence mixing Kabyle and French will receive a single class label, which may be wrong.
295
 
296
- **No safety evaluation of any kind** has been performed.
297
 
298
- ## What was not measured
 
299
 
300
- - **No inter-annotator ceiling exists for Berber variety attribution.** On sentences of shared
301
- pan-Berber vocabulary without a clear regional marker, fluent native speakers disagree so no
302
- figure here can be read as a fraction of what is attainable.
303
- - **No audio or ASR evaluation.** Performance was measured on clean orthographic text. Transcribed
304
- speech carries recognition errors that may degrade classification on short sentences.
305
- - **Out-of-domain generalisation was not benchmarked** outside the six source types used in
306
- `lid-eval.jsonl`.
307
 
308
  ## Files
309
 
310
- | file | size | description |
311
- |---|---|---|
312
- | `best.pt` | 20.8 MB | weights + config dict, selected on dev composite score (step 2,898) |
313
- | `config.json` | 1.1 KB | model architecture hyperparameters |
314
- | `calibration.json` | 890 B | split-conformal calibration quantile ($\hat{q} = 0.9979$) |
315
- | `held-out-report.json` | 4.2 KB | full evaluation breakdown on the 1,500-sentence test set |
 
 
316
 
317
- The checkpoint holds the model state dict and the config dict, nothing else. No optimiser state,
318
- no scheduler state, no training curve. Training cannot be resumed from the published file.
319
 
320
  ## Reproduction
321
 
322
  ```bash
323
- make modal-lid EPOCHS=8 # train on Modal A10G (~18 minutes)
324
- make modal-lid-pull # download best checkpoint to artifacts/runs/dihya/
325
- make lid TASK=evaluate # reproduce the 1,500-sentence benchmark
326
- make lid TEXT="Mayemmi ur teccid ticemrarin nnecm?"
327
- make release REPO=dihya # stage for Hub upload
 
328
  ```
329
 
330
- The evaluation scores Dihya-5M and the baseline **in the same pass over the same rows**, because a
331
- baseline computed elsewhere on a different sample is not a comparison.
332
 
333
  ## The name
334
 
335
- **Dihya** (also known as *al-Kāhina*, c. 650–702 CE) was an Amazigh queen, religious figure, and
336
- military commander of the Jarawa tribe, from the Aurès Mountains of what is now eastern Algeria
337
- the Shawiya homeland.
338
-
339
- She led Berber resistance across Numidia and Ifriqiya and unified disparate Amazigh tribes across
340
- the same geographic continuum this model is trained to recognise: from the Atlantic coast to the
341
- Sahara, from Tarifit in the north to Tamasheq in the south. The language boundary the model draws
342
- is the one she crossed.
343
 
344
- The naming is homage; it implies no endorsement by anyone.
345
 
346
  ## Citation
347
 
348
  ```bibtex
349
  @software{agbalu_dihya_2026,
350
- title = {Dihya-5M: language identification for the Berber dialect continuum},
351
  author = {AƔBALU},
352
  year = {2026},
353
  url = {https://huggingface.co/agbalu/Dihya-5M},
354
- note = {5,187,712 parameters; 85.52% accuracy and 0.8562 macro-F1 on 1,500 held-out sentences}
355
  }
356
  ```
357
 
358
  ## Licence
359
 
360
- **Apache-2.0** on the weights and the code.
 
 
 
361
 
362
  Part of [AƔBALU](https://huggingface.co/agbalu), a Kabyle and Amazigh corpus and model collection.
363
- The naming is homage.
 
7
  - tzm
8
  - shy
9
  license: apache-2.0
10
+ library_name: transformers
11
  tags:
12
  - berber
13
  - amazigh
 
18
  - tamazight
19
  - shawiya
20
  - language-identification
 
21
  - conformal-prediction
22
  - low-resource
23
  pipeline_tag: text-classification
 
29
  results:
30
  - task:
31
  type: text-classification
32
+ name: Berber language identification, six Latin-script varieties plus rejection
33
  dataset:
34
+ type: berber-lid-heldout
35
+ name: AƔBALU Berber LID held-out set (1,050 sentences, 150 per class)
36
  metrics:
37
  - type: accuracy
38
  value: 0.8552
39
+ name: Accuracy over 7 classes
40
  - type: f1
41
  value: 0.8562
42
+ name: Macro-F1 over 7 classes
43
  ---
44
 
45
  # Dihya-5M
46
 
47
+ A 5.19M-parameter byte-level Conv-Transformer that separates six Latin-script Amazigh varieties
48
+ from each other and from an explicit `NOT_AMAZIGH` rejection class: Kabyle (`kab_Latn`),
49
+ Tashelhit (`shi_Latn`), Tarifit (`rif_Latn`), Tamasheq (`taq_Latn`), Central Atlas Tamazight
50
+ (`tzm_Latn`) and Shawiya (`shy_Latn`).
51
 
52
+ On a balanced, source-stratified held-out set of 1,050 sentences it reaches **85.52% accuracy and
53
+ 0.8562 macro-F1** over the seven classes. Scored on the six Berber classes alone the label set
54
+ the public identifiers can be asked about it reaches **84.44% accuracy**, against **41.78% for
55
+ GlotLID and 28.33% for NLLB's `lid218e`** measured in the same pass over the same rows.
56
 
57
+ It ships with a split-conformal quantile, so it can return a *set* of labels rather than one, for
58
+ pipelines where a silent misclassification costs more than a widened answer.
59
 
60
+ ## The task
 
61
 
62
+ These are not typologically distant languages. The six varieties share much of a core lexicon —
63
+ *aman* (water), *argaz* (man), *awal* (word) and one Latin orthography. The evidence that
64
+ separates them is affixal, phonotactic and clitic, and a short sentence of shared vocabulary
65
+ often carries none of it.
66
 
67
+ That matters beyond the benchmark. NLLB's own identifier mined 90.1% of this project's parallel
68
+ corpus, and it labels the siblings Kabyle: **91.3% of Shawiya, 80.7% of Tarifit, 79.3% of
69
+ Tashelhit and 73.3% of Central Atlas Tamazight**, measured here. Datasets labelled "Kabyle" or
70
+ "Tamazight" inherit that.
71
 
72
+ ## Results
 
 
 
73
 
74
+ **1,050 sentences, 150 per class**, drawn round-robin across each language's sources so that no
75
+ class is one corpus wearing a language's name. Disjoint from train (77,756) and dev (4,093).
 
 
 
 
 
 
 
76
 
77
+ ### Against the public identifiers
78
 
79
+ All three scored in one pass over the same 900 Berber rows. `NOT_AMAZIGH` is excluded here: it is
80
+ a decision this model makes and not a language GlotLID or `lid218e` has a label for.
 
81
 
82
+ | system | accuracy | classes it can name | macro-F1 over those |
83
+ |---|---|---|---|
84
+ | **Dihya-5M** | **84.44%** | **6 of 6** | 0.8493 |
85
+ | GlotLID | 41.78% | 3 of 6 | 0.6837 |
86
+ | NLLB `lid218e` | 28.33% | 2 of 6 | 0.6022 |
87
 
88
+ The last column is three different statistics each system's macro is taken over the classes it
89
+ can name, so GlotLID's 0.6837 averages three classes and Dihya's 0.8493 averages six. It is
90
+ reported that way because averaging a system over labels absent from its inventory measures the
91
+ inventory, not the discrimination. The accuracy column is the comparable number.
92
 
93
+ **The quantity that matters downstream is how much sibling text ends up labelled Kabyle**, since
94
+ that is what a filter keyed on `kab` silently ingests:
95
 
96
+ | true language | → Kabyle, Dihya | Kabyle, GlotLID | → Kabyle, `lid218e` |
97
+ |---|---|---|---|
98
+ | Tashelhit | **2.0%** | 17.3% | 79.3% |
99
+ | Tarifit | **6.7%** | 90.0% | 80.7% |
100
+ | Tamasheq | **7.3%** | 0.0% | 6.7% |
101
+ | Central Atlas Tamazight | **16.0%** | 66.7% | 73.3% |
102
+ | Shawiya | **19.3%** | 96.0% | 91.3% |
103
 
104
+ GlotLID sends no Tamasheq to Kabyle because it has a good Tamasheq label, and it is the one place
105
+ a public system is not the weaker option — its Tamasheq F1 is 0.891 against Dihya's 0.921, close
106
+ enough that the ordering should not be leaned on.
 
107
 
108
+ ### Per class, all seven
109
 
110
+ The seven-class report, which is what `held-out-report.json` in this repository holds.
 
 
111
 
112
+ | label | support | precision | recall | F1 |
113
+ |---|---|---|---|---|
114
+ | `NOT_AMAZIGH` | 150 | 0.949 | 0.987 | 0.967 |
115
+ | `taq_Latn` | 150 | 1.000 | 0.847 | 0.917 |
116
+ | `rif_Latn` | 150 | 0.916 | 0.873 | 0.894 |
117
+ | `shi_Latn` | 150 | 0.811 | 0.973 | 0.885 |
118
+ | `shy_Latn` | 150 | 0.936 | 0.687 | 0.792 |
119
+ | `tzm_Latn` | 150 | 0.952 | 0.660 | 0.780 |
120
+ | `kab_Latn` | 150 | 0.626 | 0.960 | 0.758 |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ **Kabyle's precision is the lowest number here, and it is the one to read first.** At 0.626 the
123
+ model still over-assigns Kabyle — it recovers 96.0% of the Kabyle and pays for it by pulling in
124
+ sibling text. That is the same failure direction as the systems above, an order of magnitude
125
+ smaller. Anyone building a Kabyle-only corpus should treat a `kab_Latn` label as a filter, not as
126
+ a verdict.
127
 
128
+ **Recall on Tamazight and Shawiya is the weak side.** 0.660 and 0.687 mean a third of each is
129
+ missed, mostly to Kabyle and Tashelhit.
130
 
131
+ ### Where it is worst
 
 
 
 
 
 
 
132
 
133
+ Selection and reporting are on `(language, domain)` cells, not on classes, because a class average
134
+ hides a domain. Two cells are worth naming:
 
135
 
136
+ | cell | support | recall |
137
+ |---|---|---|
138
+ | `shy_Latn/interface` | 3 | 0.000 |
139
+ | `tzm_Latn/interface` | 61 | 0.410 |
140
+
141
+ **`tzm_Latn/interface` is a real defect**: software-localisation strings in Central Atlas
142
+ Tamazight are short, formulaic, and share their register with the Tashelhit and Kabyle
143
+ localisations they were translated alongside. Two in five are missed.
144
+
145
+ **`shy_Latn/interface` at 0.000 is three examples**, and three examples do not measure anything.
146
+ It is listed because it is the worst cell in the file and omitting it would make the file
147
+ disagree with the card, not because it supports a conclusion.
148
+
149
+ ### Conformal prediction sets
150
+
151
+ Calibrated by split conformal at target coverage 99.0%. The quantile is fitted on one half of the
152
+ development set and coverage measured on the other, because measuring coverage on the rows that
153
+ set the quantile reports the fit rather than the guarantee.
154
 
155
+ | | |
156
+ |---|---|
157
+ | fitted on | 2,047 dev sentences |
158
+ | coverage measured on | 2,046 held-out dev sentences |
159
+ | target coverage | 99.0% |
160
+ | empirical coverage | **98.78%** |
161
+ | quantile `q̂` | 0.9979 |
162
+ | mean set size | **1.098 labels** |
163
+
164
+ **This is measured on development data, not on the 1,050-sentence test set.** The exchangeability
165
+ the guarantee rests on holds between the two halves of dev; whether it holds against the test set,
166
+ whose sources are stratified differently, is not established here.
167
+
168
+ ## Usage
169
 
170
  ```python
171
+ from transformers import AutoModelForSequenceClassification
172
+
173
+ model = AutoModelForSequenceClassification.from_pretrained(
174
+ "agbalu/Dihya-5M", trust_remote_code=True
175
+ )
176
+
177
+ for row in model.identify([
178
+ "Azul fell-awen, amek tettilim ass-a?",
179
+ "Mayemmi ur teccid ticemrarin nnecm a gma?",
180
+ "Bonjour tout le monde, comment allez-vous?",
181
+ ]):
182
+ print(row["language"], round(row["confidence"], 4), row["prediction_set"])
183
+ # kab_Latn 0.9959 ('kab_Latn', 'shy_Latn')
184
+ # rif_Latn 0.9998 ('rif_Latn',)
185
+ # NOT_AMAZIGH 0.9997 ('NOT_AMAZIGH',)
186
  ```
187
 
188
+ `identify` applies the logit adjustment and the conformal quantile that the numbers above were
189
+ measured with; both travel in `config.json`. The plain `forward` returns raw cosine logits and
190
+ does neither, so a pipeline that reads `logits.argmax()` is scoring a different classifier than
191
+ the one this card describes.
192
 
193
+ The tokenizer is the 256 UTF-8 byte values and is only needed for batching:
194
+
195
+ ```python
196
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
197
+
198
+ tokenizer = AutoTokenizer.from_pretrained("agbalu/Dihya-5M")
199
+ model = AutoModelForSequenceClassification.from_pretrained(
200
+ "agbalu/Dihya-5M", trust_remote_code=True
201
+ )
202
+ batch = tokenizer(["Azul fell-awen"], return_tensors="pt", padding=True, truncation=True)
203
+ logits = model(**batch).logits
204
  ```
205
 
206
+ **Prediction sets widen where the text is genuinely ambiguous.** The Kabyle line above returns
207
+ `('kab_Latn', 'shy_Latn')` Shawiya and Kabyle are adjacent on the continuum and that greeting
208
+ carries no marker separating them. A forced singleton there would be a guess with a confidence
209
+ attached.
210
+
211
+ ## Intended use
212
+
213
+ Screening and routing text in corpus pipelines: tagging web scrapes and OCR output by variety,
214
+ discarding non-Berber lines, and routing input to variety-specific models. Use the prediction set
215
+ rather than the argmax where a wrong label would enter a training corpus.
216
 
217
+ **Not suitable for** Tifinagh or Arabic-script text, for distinguishing sub-varieties inside any
218
+ of the six classes, or for any decision about a person on the basis of the variety they write.
219
 
220
  ## Architecture
221
 
222
  | | |
223
  |---|---|
224
+ | Parameters, trained | 5,187,712 |
225
+ | Parameters, published | 5,089,408 |
226
  | Encoder layers | 6 |
227
  | Hidden / feed-forward | 256 / 704 (SwiGLU) |
228
+ | Attention heads | 8 × 32, rotary positions |
229
+ | Conv stem | three parallel depthwise-separable 1D convolutions, kernels 3, 5, 7 |
230
+ | Pooling | attentive (learned score per position) |
231
+ | Head | cosine, scale 24.0 |
232
+ | Vocabulary | 258 (256 UTF-8 bytes, `[PAD]`, `[UNK]`) |
233
+ | Maximum input | 256 bytes |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
+ The published model is smaller than the trained one because the contrastive projection head
236
+ (98,304 parameters) shapes the trunk during training and no forward pass reads it at inference.
237
 
238
+ **Bytes, not characters or subwords.** The orthographies differ in exactly the characters where
239
+ they diverge — `ɣ ɛ ḥ ḍ ṣ ṭ ẓ ṛ ṯ` are two or three UTF-8 bytes each. A byte vocabulary sees those
240
+ as a short sequence rather than as one token a minority variety's training shard may never
241
+ contain. Truncation is on bytes and may cut a character in half, which is deliberate: text arrives
242
+ already truncated at inference.
243
+
244
+ **Three kernel widths** because the evidence sits at three scales — a grapheme cluster (`ţ` against
245
+ `t`), an affix (the `u-`/`w-` annexation), and a clitic chain.
246
+
247
+ ## Training
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
  | | |
250
  |---|---|
251
+ | training rows | 77,756 across 20 `(language, domain)` cells |
252
+ | dev rows | 4,093 |
253
+ | schedule | 8 epochs, 5,176 steps; batch 12 cells × 10 rows |
254
+ | published checkpoint | step 2,898, selected on dev |
255
+ | dev score there | 0.9363 macro-F1, 0.8125 worst-group recall |
256
+ | hardware | one A10G |
257
+
258
+ Three objectives address three separate failures of an earlier build that read 98.26% on a dev
259
+ split drawn from the training sources and 76.27% on held-out text:
260
+
261
+ **LDAM margins** for the long tail — `m_k = C / n_k^(1/4)`, from
262
+ [Cao et al., NeurIPS 2019](https://arxiv.org/abs/1906.07413). Shawiya has 447 training rows
263
+ against Tashelhit's 24,123, and a shared margin gives the rare class the same slack as the common
264
+ one.
265
+
266
+ **Group-DRO** for the domain confound — exponentiated-gradient weights over the twenty
267
+ `(language, domain)` cells, from [Sagawa et al., ICLR 2020](https://arxiv.org/abs/1911.08731). It
268
+ optimises the worst cell rather than the batch mean.
269
+
270
+ **Domain-aware supervised contrastive learning** — negatives mined from a different language in
271
+ the *same* domain first, over a 2,048-entry memory bank. The hard-negative scheme is
272
+ [ConLID's](https://arxiv.org/abs/2506.15304). A same-domain negative can only be separated by the
273
+ language.
274
+
275
+ **Both margins and group weights are off for the first half of the schedule.** Measured on this
276
+ corpus: 120 steps with margins from step 0 reached a loss of 4.01 where the same run without them
277
+ reached 0.76, at the same accuracy. This is LDAM's own deferred schedule.
278
+
279
+ **Selection is on the mean of dev macro-F1 and dev worst-group recall, never on the test set and
280
+ never on loss.** Macro-F1 alone picks the checkpoint that wins the large domains; worst-group
281
+ alone picks one that has learned a single hard cell and forgotten the rest.
282
 
283
  ## Limitations
284
 
285
+ **Ambiguity on shared vocabulary is real and is not an error.** A short sentence of pan-Berber
286
+ core vocabulary carrying no variety-specific marker cannot be assigned to one variety. The
287
+ conformal set reports that with size > 1.
 
288
 
289
+ **Kabyle precision is 0.626.** Sibling text still arrives labelled Kabyle at that rate on this set.
 
 
290
 
291
+ **Tifinagh and Arabic script are out of scope.** Byte input from either produces a label. That
292
+ label has not been measured.
 
293
 
294
+ **Single words are outside the input shape.** The model was fitted on sentences.
 
295
 
296
+ **Code-switching is not handled.** One label per input.
297
 
298
+ **Shawiya cannot be measured well by anyone.** 447 training rows and 150 held-out is close to the
299
+ whole of the language's digital text; its numbers carry their support and no further claim.
300
 
301
+ **No inter-annotator ceiling exists for Berber variety attribution**, so no figure here can be
302
+ read as a fraction of what is attainable — fluent speakers disagree on the ambiguous cases.
303
+
304
+ **Every source in the evaluation set is a corpus**, not a live crawl or real user input, and no
305
+ safety evaluation of any kind has been performed.
 
 
306
 
307
  ## Files
308
 
309
+ | file | description |
310
+ |---|---|
311
+ | `model.safetensors` | 20.4 MB, 5,089,408 parameters |
312
+ | `config.json` | architecture, class order, prior shift and conformal quantile |
313
+ | `modeling_dihya.py`, `configuration_dihya.py` | the architecture, `transformers` and `torch` only |
314
+ | `tokenizer.json` | the 258-entry byte vocabulary |
315
+ | `calibration.json` | the calibration, in full |
316
+ | `held-out-report.json` | per class and per `(language, domain)` cell |
317
 
318
+ No optimiser state, no scheduler state, no RNG. Training cannot be resumed from these files.
 
319
 
320
  ## Reproduction
321
 
322
  ```bash
323
+ make lid-dataset # build the splits from the source table
324
+ make modal-lid EPOCHS=8 # train on Modal A10G
325
+ make modal-lid-pull # fetch the checkpoint
326
+ make lid TASK=evaluate # the 7-class held-out report
327
+ make bench TASK=lid # Dihya, GlotLID and lid218e in one pass
328
+ make release REPO=dihya # export and stage for the Hub
329
  ```
330
 
331
+ `make bench TASK=lid` is what produced the comparison table: all three systems, one build of the
332
+ evaluation set, one pass. A baseline computed elsewhere on a different sample is not a comparison.
333
 
334
  ## The name
335
 
336
+ **Dihya** (also called *al-Kāhina*, died c. 703 CE) was an Amazigh military leader from the Aurès
337
+ Mountains of what is now eastern Algeria — the Shawiya homeland who led the Berber resistance to
338
+ the Umayyad conquest of Ifriqiya. Much of what is written about her comes from sources composed
339
+ well after her lifetime and is contested; the Aurès and the resistance are the parts that are not.
 
 
 
 
340
 
341
+ The naming is homage and implies no endorsement by anyone.
342
 
343
  ## Citation
344
 
345
  ```bibtex
346
  @software{agbalu_dihya_2026,
347
+ title = {Dihya-5M: language identification across the Berber dialect continuum},
348
  author = {AƔBALU},
349
  year = {2026},
350
  url = {https://huggingface.co/agbalu/Dihya-5M},
351
+ note = {5.19M parameters; 85.52% accuracy over seven classes on 1,050 held-out sentences}
352
  }
353
  ```
354
 
355
  ## Licence
356
 
357
+ **Apache-2.0** on the weights and the code. The training corpus is assembled from sources under
358
+ mixed licences, which a permissive grant on weights does not relicense; see
359
+ [the datasheet](https://huggingface.co/datasets/agbalu/KabBench) and
360
+ `resources/sibling_registry.yaml` in the project repository for per-source terms.
361
 
362
  Part of [AƔBALU](https://huggingface.co/agbalu), a Kabyle and Amazigh corpus and model collection.
 
__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .configuration_dihya import CLASSES, DihyaConfig
2
+ from .modeling_dihya import DihyaForSequenceClassification, DihyaPreTrainedModel
3
+
4
+ __all__ = ["CLASSES", "DihyaConfig", "DihyaForSequenceClassification", "DihyaPreTrainedModel"]
config.json CHANGED
@@ -1,49 +1,69 @@
1
  {
2
- "model": {
3
- "vocab_size": 258,
4
- "embed_dim": 256,
5
- "conv_kernels": [
6
- 3,
7
- 5,
8
- 7
9
- ],
10
- "conv_dim": 128,
11
- "num_layers": 6,
12
- "num_heads": 8,
13
- "intermediate_dim": 704,
14
- "projection_dim": 128,
15
- "max_length": 256,
16
- "dropout": 0.1,
17
- "scale": 24.0,
18
- "margin_scale": 0.5,
19
- "classes": [
20
- "kab_Latn",
21
- "shi_Latn",
22
- "rif_Latn",
23
- "taq_Latn",
24
- "tzm_Latn",
25
- "shy_Latn",
26
- "NOT_AMAZIGH"
27
- ]
28
  },
29
- "train": {
30
- "epochs": 8,
31
- "groups_per_batch": 12,
32
- "samples_per_group": 10,
33
- "learning_rate": 0.0003,
34
- "min_learning_rate": 1e-06,
35
- "weight_decay": 0.01,
36
- "max_grad_norm": 1.0,
37
- "eval_batch_size": 256,
38
- "contrastive_weight": 0.5,
39
- "contrastive_temperature": 0.05,
40
- "memory_bank_size": 2048,
41
- "group_dro_step": 0.01,
42
- "deferred_share": 0.5,
43
- "label_smoothing": 0.05,
44
- "logit_adjustment": 0.5,
45
- "phrase_augment_prob": 0.35,
46
- "case_augment_prob": 0.1,
47
- "seed": 20260823
48
- }
49
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {
2
+ "architectures": [
3
+ "DihyaForSequenceClassification"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_dihya.DihyaConfig",
7
+ "AutoModel": "modeling_dihya.DihyaForSequenceClassification",
8
+ "AutoModelForSequenceClassification": "modeling_dihya.DihyaForSequenceClassification"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  },
10
+ "byte_offset": 2,
11
+ "classes": [
12
+ "kab_Latn",
13
+ "shi_Latn",
14
+ "rif_Latn",
15
+ "taq_Latn",
16
+ "tzm_Latn",
17
+ "shy_Latn",
18
+ "NOT_AMAZIGH"
19
+ ],
20
+ "conv_dim": 128,
21
+ "conv_kernels": [
22
+ 3,
23
+ 5,
24
+ 7
25
+ ],
26
+ "dropout_prob": 0.1,
27
+ "hidden_size": 256,
28
+ "id2label": {
29
+ "0": "kab_Latn",
30
+ "1": "shi_Latn",
31
+ "2": "rif_Latn",
32
+ "3": "taq_Latn",
33
+ "4": "tzm_Latn",
34
+ "5": "shy_Latn",
35
+ "6": "NOT_AMAZIGH"
36
+ },
37
+ "intermediate_size": 704,
38
+ "label2id": {
39
+ "NOT_AMAZIGH": 6,
40
+ "kab_Latn": 0,
41
+ "rif_Latn": 2,
42
+ "shi_Latn": 1,
43
+ "shy_Latn": 5,
44
+ "taq_Latn": 3,
45
+ "tzm_Latn": 4
46
+ },
47
+ "logit_scale": 24.0,
48
+ "max_position_embeddings": 256,
49
+ "model_type": "dihya",
50
+ "num_attention_heads": 8,
51
+ "num_hidden_layers": 6,
52
+ "pad_token_id": 0,
53
+ "prior_shift": [
54
+ -0.6155660152435303,
55
+ -0.5855244994163513,
56
+ -1.1106044054031372,
57
+ -1.2838202714920044,
58
+ -2.21260666847229,
59
+ -2.5694897174835205,
60
+ -0.8174624443054199
61
+ ],
62
+ "q_hat": 0.997931957244873,
63
+ "rms_norm_eps": 1e-06,
64
+ "rope_theta": 10000.0,
65
+ "target_coverage": 0.99,
66
+ "transformers_version": "5.12.1",
67
+ "unk_token_id": 1,
68
+ "vocab_size": 258
69
+ }
configuration_dihya.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration for the Dihya-5M Berber language identifier."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from transformers import PreTrainedConfig
8
+
9
+ CLASSES = [
10
+ "kab_Latn",
11
+ "shi_Latn",
12
+ "rif_Latn",
13
+ "taq_Latn",
14
+ "tzm_Latn",
15
+ "shy_Latn",
16
+ "NOT_AMAZIGH",
17
+ ]
18
+ """Label order is the trained head's row order. Reordering it renames every prediction."""
19
+
20
+
21
+ class DihyaConfig(PreTrainedConfig):
22
+ """The classifier's shapes, its label set, and the two inference-time corrections.
23
+
24
+ `prior_shift` and `q_hat` are stored here rather than in a sidecar because they are
25
+ part of what the model predicts: without the shift the argmax minimises the training
26
+ prior's error rather than balanced error, and without the quantile there is no
27
+ prediction set. A repository that ships the weights alone ships a different model.
28
+ """
29
+
30
+ model_type = "dihya"
31
+
32
+ def __init__(
33
+ self,
34
+ vocab_size: int = 258,
35
+ hidden_size: int = 256,
36
+ intermediate_size: int = 704,
37
+ num_attention_heads: int = 8,
38
+ num_hidden_layers: int = 6,
39
+ conv_kernels: list[int] | None = None,
40
+ conv_dim: int = 128,
41
+ max_position_embeddings: int = 256,
42
+ rope_theta: float = 10000.0,
43
+ dropout_prob: float = 0.1,
44
+ logit_scale: float = 24.0,
45
+ rms_norm_eps: float = 1e-6,
46
+ classes: list[str] | None = None,
47
+ prior_shift: list[float] | None = None,
48
+ q_hat: float | None = None,
49
+ target_coverage: float = 0.99,
50
+ pad_token_id: int = 0,
51
+ unk_token_id: int = 1,
52
+ byte_offset: int = 2,
53
+ **kwargs: Any,
54
+ ) -> None:
55
+ self.vocab_size = vocab_size
56
+ self.hidden_size = hidden_size
57
+ self.intermediate_size = intermediate_size
58
+ self.num_attention_heads = num_attention_heads
59
+ self.num_hidden_layers = num_hidden_layers
60
+ self.conv_kernels = list(conv_kernels or [3, 5, 7])
61
+ self.conv_dim = conv_dim
62
+ self.max_position_embeddings = max_position_embeddings
63
+ self.rope_theta = rope_theta
64
+ self.dropout_prob = dropout_prob
65
+ self.logit_scale = logit_scale
66
+ self.rms_norm_eps = rms_norm_eps
67
+ self.unk_token_id = unk_token_id
68
+ self.byte_offset = byte_offset
69
+
70
+ labels = list(classes or CLASSES)
71
+ self.classes = labels
72
+ self.prior_shift = list(prior_shift) if prior_shift is not None else [0.0] * len(labels)
73
+ self.q_hat = q_hat
74
+ self.target_coverage = target_coverage
75
+
76
+ kwargs.setdefault("pad_token_id", pad_token_id)
77
+ kwargs.setdefault("id2label", dict(enumerate(labels)))
78
+ kwargs.setdefault("label2id", {name: i for i, name in enumerate(labels)})
79
+ super().__init__(**kwargs)
80
+
81
+ @property
82
+ def head_size(self) -> int:
83
+ return self.hidden_size // self.num_attention_heads
84
+
85
+
86
+ __all__ = ["CLASSES", "DihyaConfig"]
export.stats.json ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source": "artifacts/runs/dihya/best.pt",
3
+ "source_bytes": 63439095,
4
+ "dropped_training_only": [
5
+ "head.margins",
6
+ "projection.0.weight",
7
+ "projection.2.weight"
8
+ ],
9
+ "tensors": 69,
10
+ "parameters": 5089408,
11
+ "max_logit_delta_against_trained": 0.0,
12
+ "files": [
13
+ {
14
+ "name": "README.md",
15
+ "bytes": 15452,
16
+ "sha256": "8d230fd4847f15a5f431bf369fcd6054baec703369b717b755fc939322da93be"
17
+ },
18
+ {
19
+ "name": "__init__.py",
20
+ "bytes": 231,
21
+ "sha256": "5c3bd891c199287302180ab2a447de121dc11ca69309da252741c612ea055d0e"
22
+ },
23
+ {
24
+ "name": "calibration.json",
25
+ "bytes": 516,
26
+ "sha256": "72ab22b431e1887e4c1dbaed14f08455a0096cc6a544accc689007e05280f1eb"
27
+ },
28
+ {
29
+ "name": "config.json",
30
+ "bytes": 1451,
31
+ "sha256": "d0cc1ff28fba5f9d61ca08e7e091cf7898338005ec3661bb1ad0d066d4cb581d"
32
+ },
33
+ {
34
+ "name": "configuration_dihya.py",
35
+ "bytes": 2917,
36
+ "sha256": "b92c21f6e356261c286440799d4e3a114ae13b2e3204153cd6a41259d97378d5"
37
+ },
38
+ {
39
+ "name": "held-out-report.json",
40
+ "bytes": 3081,
41
+ "sha256": "5dedc57c1a07f1a80e9e62e79ce5ffbf15308ce00e0f9b8e147cc57dea4fcb17"
42
+ },
43
+ {
44
+ "name": "model.safetensors",
45
+ "bytes": 20364088,
46
+ "sha256": "edfbe7115393fca0a18474fcaac641afdbe69b66bc74d9381a5b54f38444ddd7"
47
+ },
48
+ {
49
+ "name": "modeling_dihya.py",
50
+ "bytes": 13221,
51
+ "sha256": "116d02ea899acd89bc05467e1097a062b851c64c5fb36d53a4386010caca959f"
52
+ },
53
+ {
54
+ "name": "tokenizer.json",
55
+ "bytes": 5648,
56
+ "sha256": "fba53450cc2250bf3d4686ec0781d52fbdee96e383142835365dc6960a051ef6"
57
+ },
58
+ {
59
+ "name": "tokenizer_config.json",
60
+ "bytes": 147,
61
+ "sha256": "c6d1937e9ca63bebaa9ad6a05df2250a67ba071d672ef88e3ef5a45e75c4df50"
62
+ }
63
+ ]
64
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:edfbe7115393fca0a18474fcaac641afdbe69b66bc74d9381a5b54f38444ddd7
3
+ size 20364088
modeling_dihya.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dihya-5M: byte-level Berber language identification.
2
+
3
+ Module attribute names are the published checkpoint's state_dict keys. Renaming one
4
+ breaks `from_pretrained` for everybody who downloaded the release.
5
+
6
+ The contrastive projection head the model was trained with is not here. It shapes the
7
+ trunk during training and is never read at inference, so shipping it would hand every
8
+ downloader 98,304 parameters that no forward pass touches.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import math
14
+ from typing import Any
15
+
16
+ import torch
17
+ from torch import Tensor, nn
18
+ from torch.nn import functional
19
+ from transformers import PreTrainedModel
20
+ from transformers.modeling_outputs import SequenceClassifierOutput
21
+
22
+ from .configuration_dihya import DihyaConfig
23
+
24
+ MASK_FILL = -1e4
25
+ """Finite rather than `-inf`: a row that is entirely padding would otherwise softmax to
26
+ NaN, and an empty string is a real input to a language identifier."""
27
+
28
+
29
+ class RMSNorm(nn.Module):
30
+ def __init__(self, dim: int, eps: float = 1e-6) -> None:
31
+ super().__init__()
32
+ self.eps = eps
33
+ self.weight = nn.Parameter(torch.ones(dim))
34
+
35
+ def forward(self, x: Tensor) -> Tensor:
36
+ variance = x.pow(2).mean(-1, keepdim=True)
37
+ normed: Tensor = x * torch.rsqrt(variance + self.eps) * self.weight
38
+ return normed
39
+
40
+
41
+ class SwiGLU(nn.Module):
42
+ def __init__(self, dim: int, intermediate_dim: int) -> None:
43
+ super().__init__()
44
+ self.w1 = nn.Linear(dim, intermediate_dim, bias=False)
45
+ self.w2 = nn.Linear(dim, intermediate_dim, bias=False)
46
+ self.w3 = nn.Linear(intermediate_dim, dim, bias=False)
47
+
48
+ def forward(self, x: Tensor) -> Tensor:
49
+ projected: Tensor = self.w3(functional.silu(self.w1(x)) * self.w2(x))
50
+ return projected
51
+
52
+
53
+ class ConvStem(nn.Module):
54
+ """Parallel depthwise-separable convolutions over the byte embeddings.
55
+
56
+ Three widths because the discriminating evidence sits at three scales: a grapheme
57
+ cluster, an affix, and a clitic chain. One kernel width picks one of the three.
58
+ """
59
+
60
+ def __init__(self, config: DihyaConfig) -> None:
61
+ super().__init__()
62
+ dim, branch_dim = config.hidden_size, config.conv_dim
63
+ self.branches = nn.ModuleList(
64
+ nn.Sequential(
65
+ nn.Conv1d(dim, dim, kernel_size=k, padding=k // 2, groups=dim, bias=False),
66
+ nn.Conv1d(dim, branch_dim, kernel_size=1, bias=False),
67
+ )
68
+ for k in config.conv_kernels
69
+ )
70
+ self.branch_norms = nn.ModuleList(
71
+ RMSNorm(branch_dim, eps=config.rms_norm_eps) for _ in config.conv_kernels
72
+ )
73
+ self.proj = nn.Linear(branch_dim * len(config.conv_kernels), dim, bias=False)
74
+ self.norm = RMSNorm(dim, eps=config.rms_norm_eps)
75
+ self.dropout = nn.Dropout(config.dropout_prob)
76
+
77
+ def forward(self, x: Tensor) -> Tensor:
78
+ transposed = x.transpose(1, 2)
79
+ outputs = [
80
+ functional.silu(norm(branch(transposed).transpose(1, 2)))
81
+ for branch, norm in zip(self.branches, self.branch_norms, strict=True)
82
+ ]
83
+ stemmed: Tensor = x + self.dropout(self.norm(self.proj(torch.cat(outputs, dim=-1))))
84
+ return stemmed
85
+
86
+
87
+ def rope_freqs(head_dim: int, length: int, base: float, device: torch.device) -> Tensor:
88
+ """Complex rotary frequencies, derived on call and never stored.
89
+
90
+ A registered non-persistent buffer comes back from `from_pretrained` as uninitialised
91
+ memory, because it is deliberately absent from the checkpoint.
92
+ """
93
+ theta = 1.0 / (base ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim))
94
+ positions = torch.arange(length, device=device).float()
95
+ angles = torch.outer(positions, theta)
96
+ return torch.polar(torch.ones_like(angles), angles)
97
+
98
+
99
+ def apply_rope(x: Tensor, freqs: Tensor) -> Tensor:
100
+ batch, heads, length, head_dim = x.shape
101
+ paired = torch.view_as_complex(x.float().reshape(batch, heads, length, -1, 2))
102
+ rotated = torch.view_as_real(paired * freqs[:length].unsqueeze(0).unsqueeze(0))
103
+ return rotated.reshape(batch, heads, length, head_dim).type_as(x)
104
+
105
+
106
+ class Attention(nn.Module):
107
+ def __init__(self, config: DihyaConfig) -> None:
108
+ super().__init__()
109
+ dim = config.hidden_size
110
+ self.num_heads = config.num_attention_heads
111
+ self.head_dim = config.head_size
112
+ self.dropout = config.dropout_prob
113
+ self.q_proj = nn.Linear(dim, dim, bias=False)
114
+ self.k_proj = nn.Linear(dim, dim, bias=False)
115
+ self.v_proj = nn.Linear(dim, dim, bias=False)
116
+ self.out_proj = nn.Linear(dim, dim, bias=False)
117
+
118
+ def forward(self, x: Tensor, freqs: Tensor, mask: Tensor | None = None) -> Tensor:
119
+ batch, length, dim = x.shape
120
+ shape = (batch, length, self.num_heads, self.head_dim)
121
+ query = apply_rope(self.q_proj(x).view(shape).transpose(1, 2), freqs)
122
+ key = apply_rope(self.k_proj(x).view(shape).transpose(1, 2), freqs)
123
+ value = self.v_proj(x).view(shape).transpose(1, 2)
124
+ attended = functional.scaled_dot_product_attention(
125
+ query,
126
+ key,
127
+ value,
128
+ attn_mask=mask.unsqueeze(1).unsqueeze(2) if mask is not None else None,
129
+ dropout_p=self.dropout if self.training else 0.0,
130
+ )
131
+ merged: Tensor = self.out_proj(attended.transpose(1, 2).reshape(batch, length, dim))
132
+ return merged
133
+
134
+
135
+ class EncoderLayer(nn.Module):
136
+ def __init__(self, config: DihyaConfig) -> None:
137
+ super().__init__()
138
+ self.norm1 = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
139
+ self.attn = Attention(config)
140
+ self.norm2 = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
141
+ self.ffn = SwiGLU(config.hidden_size, config.intermediate_size)
142
+ self.dropout = nn.Dropout(config.dropout_prob)
143
+
144
+ def forward(self, x: Tensor, freqs: Tensor, mask: Tensor | None = None) -> Tensor:
145
+ hidden: Tensor = x + self.dropout(self.attn(self.norm1(x), freqs, mask=mask))
146
+ residual: Tensor = self.dropout(self.ffn(self.norm2(hidden)))
147
+ return hidden + residual
148
+
149
+
150
+ class AttentivePooling(nn.Module):
151
+ """Weighted sum over positions, so a short discriminating affix is not averaged away."""
152
+
153
+ def __init__(self, config: DihyaConfig) -> None:
154
+ super().__init__()
155
+ self.score = nn.Linear(config.hidden_size, 1, bias=False)
156
+
157
+ def forward(self, x: Tensor, mask: Tensor | None = None) -> Tensor:
158
+ scores = self.score(x).squeeze(-1) / math.sqrt(x.size(-1))
159
+ if mask is not None:
160
+ scores = scores.masked_fill(~mask, MASK_FILL)
161
+ weights = functional.softmax(scores, dim=-1).unsqueeze(-1)
162
+ pooled: Tensor = (x * weights).sum(dim=1)
163
+ return pooled
164
+
165
+
166
+ class MarginHead(nn.Module):
167
+ """Scaled cosine classifier.
168
+
169
+ The per-class additive margins the head was trained with apply to the target logit
170
+ only, so they exist during training and are identity at inference. The margin buffer
171
+ is therefore not part of the release.
172
+ """
173
+
174
+ def __init__(self, config: DihyaConfig) -> None:
175
+ super().__init__()
176
+ self.scale = config.logit_scale
177
+ self.weight = nn.Parameter(torch.empty(len(config.classes), config.hidden_size))
178
+
179
+ def forward(self, x: Tensor) -> Tensor:
180
+ cosine = functional.linear(
181
+ functional.normalize(x, p=2, dim=1), functional.normalize(self.weight, p=2, dim=1)
182
+ )
183
+ return cosine * self.scale
184
+
185
+
186
+ class DihyaPreTrainedModel(PreTrainedModel):
187
+ config_class = DihyaConfig
188
+ base_model_prefix = "dihya"
189
+ supports_gradient_checkpointing = False
190
+
191
+ def _init_weights(self, module: nn.Module) -> None:
192
+ if isinstance(module, nn.Linear | nn.Conv1d):
193
+ nn.init.xavier_uniform_(module.weight)
194
+ if getattr(module, "bias", None) is not None:
195
+ nn.init.zeros_(module.bias)
196
+ elif isinstance(module, nn.Embedding):
197
+ nn.init.normal_(module.weight, std=0.02)
198
+ if module.padding_idx is not None:
199
+ with torch.no_grad():
200
+ module.weight[module.padding_idx].fill_(0)
201
+ elif isinstance(module, RMSNorm):
202
+ nn.init.ones_(module.weight)
203
+ elif isinstance(module, MarginHead):
204
+ nn.init.xavier_uniform_(module.weight)
205
+
206
+
207
+ class DihyaForSequenceClassification(DihyaPreTrainedModel):
208
+ """Byte-level classifier over six Berber varieties and an explicit rejection class."""
209
+
210
+ def __init__(self, config: DihyaConfig) -> None:
211
+ super().__init__(config)
212
+ self.embed = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=0)
213
+ self.stem = ConvStem(config)
214
+ self.layers = nn.ModuleList(EncoderLayer(config) for _ in range(config.num_hidden_layers))
215
+ self.final_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
216
+ self.pool = AttentivePooling(config)
217
+ self.head = MarginHead(config)
218
+ self.post_init()
219
+
220
+ def get_input_embeddings(self) -> nn.Module:
221
+ return self.embed
222
+
223
+ def set_input_embeddings(self, value: nn.Module) -> None:
224
+ self.embed = value # type: ignore[assignment]
225
+
226
+ def forward(
227
+ self,
228
+ input_ids: Tensor,
229
+ attention_mask: Tensor | None = None,
230
+ labels: Tensor | None = None,
231
+ return_dict: bool | None = None,
232
+ **kwargs: Any,
233
+ ) -> SequenceClassifierOutput | tuple[Tensor, ...]:
234
+ mask = attention_mask.bool() if attention_mask is not None else None
235
+ hidden = self.stem(self.embed(input_ids))
236
+ freqs = rope_freqs(
237
+ self.config.head_size,
238
+ input_ids.size(1),
239
+ self.config.rope_theta,
240
+ input_ids.device,
241
+ )
242
+ for layer in self.layers:
243
+ hidden = layer(hidden, freqs, mask=mask)
244
+ pooled = self.pool(self.final_norm(hidden), mask=mask)
245
+ logits = self.head(pooled)
246
+
247
+ loss = None
248
+ if labels is not None:
249
+ loss = functional.cross_entropy(logits, labels)
250
+ if return_dict is False:
251
+ return (logits,) if loss is None else (loss, logits)
252
+ return SequenceClassifierOutput(loss=loss, logits=logits, hidden_states=(pooled,))
253
+
254
+ def prior_shift(self, device: torch.device, dtype: torch.dtype) -> Tensor:
255
+ return torch.tensor(self.config.prior_shift, device=device, dtype=dtype)
256
+
257
+ @torch.inference_mode()
258
+ def identify(
259
+ self,
260
+ texts: str | list[str],
261
+ max_length: int | None = None,
262
+ batch_size: int = 128,
263
+ ) -> list[dict[str, Any]]:
264
+ """Classify text. One dict per input: `language`, `confidence`, `prediction_set`.
265
+
266
+ The tokenizer is not needed: the vocabulary is the 256 UTF-8 byte values, so the
267
+ encoding is the input's own bytes. `prediction_set` is the split-conformal set
268
+ `{k : p_k >= 1 - q_hat}` when the repository carries a calibrated `q_hat`, and the
269
+ argmax alone when it does not — never a singleton dressed up as a guarantee.
270
+ """
271
+ wanted = [texts] if isinstance(texts, str) else list(texts)
272
+ if not wanted:
273
+ return []
274
+ limit = max_length or self.config.max_position_embeddings
275
+ device = next(self.parameters()).device
276
+ classes = list(self.config.classes)
277
+ threshold = None if self.config.q_hat is None else 1.0 - float(self.config.q_hat)
278
+
279
+ results: list[dict[str, Any]] = []
280
+ for start in range(0, len(wanted), batch_size):
281
+ chunk = wanted[start : start + batch_size]
282
+ rows = [
283
+ [b + self.config.byte_offset for b in text.encode("utf-8")[:limit]]
284
+ or [self.config.pad_token_id]
285
+ for text in chunk
286
+ ]
287
+ width = max(len(row) for row in rows)
288
+ input_ids = torch.full(
289
+ (len(rows), width), self.config.pad_token_id, dtype=torch.long, device=device
290
+ )
291
+ attention = torch.zeros((len(rows), width), dtype=torch.long, device=device)
292
+ for i, row in enumerate(rows):
293
+ input_ids[i, : len(row)] = torch.tensor(row, dtype=torch.long, device=device)
294
+ attention[i, : len(row)] = 1
295
+
296
+ logits = self(input_ids, attention_mask=attention).logits
297
+ shifted = logits - self.prior_shift(logits.device, logits.dtype)
298
+ for probabilities in torch.softmax(shifted, dim=-1).tolist():
299
+ top = max(range(len(probabilities)), key=probabilities.__getitem__)
300
+ members = (
301
+ tuple(c for c, p in zip(classes, probabilities, strict=True) if p >= threshold)
302
+ if threshold is not None
303
+ else (classes[top],)
304
+ )
305
+ results.append(
306
+ {
307
+ "language": classes[top],
308
+ "confidence": probabilities[top],
309
+ "prediction_set": members or (classes[top],),
310
+ "probabilities": dict(zip(classes, probabilities, strict=True)),
311
+ }
312
+ )
313
+ return results
314
+
315
+
316
+ __all__ = [
317
+ "DihyaConfig",
318
+ "DihyaForSequenceClassification",
319
+ "DihyaPreTrainedModel",
320
+ ]
tokenizer.json ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0",
3
+ "truncation": null,
4
+ "padding": null,
5
+ "added_tokens": [
6
+ {
7
+ "id": 0,
8
+ "content": "[PAD]",
9
+ "single_word": false,
10
+ "lstrip": false,
11
+ "rstrip": false,
12
+ "normalized": false,
13
+ "special": true
14
+ },
15
+ {
16
+ "id": 1,
17
+ "content": "[UNK]",
18
+ "single_word": false,
19
+ "lstrip": false,
20
+ "rstrip": false,
21
+ "normalized": false,
22
+ "special": true
23
+ }
24
+ ],
25
+ "normalizer": null,
26
+ "pre_tokenizer": {
27
+ "type": "Sequence",
28
+ "pretokenizers": [
29
+ {
30
+ "type": "ByteLevel",
31
+ "add_prefix_space": false,
32
+ "trim_offsets": true,
33
+ "use_regex": false
34
+ },
35
+ {
36
+ "type": "Split",
37
+ "pattern": {
38
+ "Regex": "[\\s\\S]"
39
+ },
40
+ "behavior": "Isolated",
41
+ "invert": false
42
+ }
43
+ ]
44
+ },
45
+ "post_processor": {
46
+ "type": "TemplateProcessing",
47
+ "single": [
48
+ {
49
+ "Sequence": {
50
+ "id": "A",
51
+ "type_id": 0
52
+ }
53
+ }
54
+ ],
55
+ "pair": [
56
+ {
57
+ "Sequence": {
58
+ "id": "A",
59
+ "type_id": 0
60
+ }
61
+ },
62
+ {
63
+ "Sequence": {
64
+ "id": "B",
65
+ "type_id": 1
66
+ }
67
+ }
68
+ ],
69
+ "special_tokens": {}
70
+ },
71
+ "decoder": {
72
+ "type": "ByteLevel",
73
+ "add_prefix_space": true,
74
+ "trim_offsets": true,
75
+ "use_regex": true
76
+ },
77
+ "model": {
78
+ "type": "WordLevel",
79
+ "vocab": {
80
+ "[PAD]": 0,
81
+ "[UNK]": 1,
82
+ "Ā": 2,
83
+ "ā": 3,
84
+ "Ă": 4,
85
+ "ă": 5,
86
+ "Ą": 6,
87
+ "ą": 7,
88
+ "Ć": 8,
89
+ "ć": 9,
90
+ "Ĉ": 10,
91
+ "ĉ": 11,
92
+ "Ċ": 12,
93
+ "ċ": 13,
94
+ "Č": 14,
95
+ "č": 15,
96
+ "Ď": 16,
97
+ "ď": 17,
98
+ "Đ": 18,
99
+ "đ": 19,
100
+ "Ē": 20,
101
+ "ē": 21,
102
+ "Ĕ": 22,
103
+ "ĕ": 23,
104
+ "Ė": 24,
105
+ "ė": 25,
106
+ "Ę": 26,
107
+ "ę": 27,
108
+ "Ě": 28,
109
+ "ě": 29,
110
+ "Ĝ": 30,
111
+ "ĝ": 31,
112
+ "Ğ": 32,
113
+ "ğ": 33,
114
+ "Ġ": 34,
115
+ "!": 35,
116
+ "\"": 36,
117
+ "#": 37,
118
+ "$": 38,
119
+ "%": 39,
120
+ "&": 40,
121
+ "'": 41,
122
+ "(": 42,
123
+ ")": 43,
124
+ "*": 44,
125
+ "+": 45,
126
+ ",": 46,
127
+ "-": 47,
128
+ ".": 48,
129
+ "/": 49,
130
+ "0": 50,
131
+ "1": 51,
132
+ "2": 52,
133
+ "3": 53,
134
+ "4": 54,
135
+ "5": 55,
136
+ "6": 56,
137
+ "7": 57,
138
+ "8": 58,
139
+ "9": 59,
140
+ ":": 60,
141
+ ";": 61,
142
+ "<": 62,
143
+ "=": 63,
144
+ ">": 64,
145
+ "?": 65,
146
+ "@": 66,
147
+ "A": 67,
148
+ "B": 68,
149
+ "C": 69,
150
+ "D": 70,
151
+ "E": 71,
152
+ "F": 72,
153
+ "G": 73,
154
+ "H": 74,
155
+ "I": 75,
156
+ "J": 76,
157
+ "K": 77,
158
+ "L": 78,
159
+ "M": 79,
160
+ "N": 80,
161
+ "O": 81,
162
+ "P": 82,
163
+ "Q": 83,
164
+ "R": 84,
165
+ "S": 85,
166
+ "T": 86,
167
+ "U": 87,
168
+ "V": 88,
169
+ "W": 89,
170
+ "X": 90,
171
+ "Y": 91,
172
+ "Z": 92,
173
+ "[": 93,
174
+ "\\": 94,
175
+ "]": 95,
176
+ "^": 96,
177
+ "_": 97,
178
+ "`": 98,
179
+ "a": 99,
180
+ "b": 100,
181
+ "c": 101,
182
+ "d": 102,
183
+ "e": 103,
184
+ "f": 104,
185
+ "g": 105,
186
+ "h": 106,
187
+ "i": 107,
188
+ "j": 108,
189
+ "k": 109,
190
+ "l": 110,
191
+ "m": 111,
192
+ "n": 112,
193
+ "o": 113,
194
+ "p": 114,
195
+ "q": 115,
196
+ "r": 116,
197
+ "s": 117,
198
+ "t": 118,
199
+ "u": 119,
200
+ "v": 120,
201
+ "w": 121,
202
+ "x": 122,
203
+ "y": 123,
204
+ "z": 124,
205
+ "{": 125,
206
+ "|": 126,
207
+ "}": 127,
208
+ "~": 128,
209
+ "ġ": 129,
210
+ "Ģ": 130,
211
+ "ģ": 131,
212
+ "Ĥ": 132,
213
+ "ĥ": 133,
214
+ "Ħ": 134,
215
+ "ħ": 135,
216
+ "Ĩ": 136,
217
+ "ĩ": 137,
218
+ "Ī": 138,
219
+ "ī": 139,
220
+ "Ĭ": 140,
221
+ "ĭ": 141,
222
+ "Į": 142,
223
+ "į": 143,
224
+ "İ": 144,
225
+ "ı": 145,
226
+ "IJ": 146,
227
+ "ij": 147,
228
+ "Ĵ": 148,
229
+ "ĵ": 149,
230
+ "Ķ": 150,
231
+ "ķ": 151,
232
+ "ĸ": 152,
233
+ "Ĺ": 153,
234
+ "ĺ": 154,
235
+ "Ļ": 155,
236
+ "ļ": 156,
237
+ "Ľ": 157,
238
+ "ľ": 158,
239
+ "Ŀ": 159,
240
+ "ŀ": 160,
241
+ "Ł": 161,
242
+ "ł": 162,
243
+ "¡": 163,
244
+ "¢": 164,
245
+ "£": 165,
246
+ "¤": 166,
247
+ "¥": 167,
248
+ "¦": 168,
249
+ "§": 169,
250
+ "¨": 170,
251
+ "©": 171,
252
+ "ª": 172,
253
+ "«": 173,
254
+ "¬": 174,
255
+ "Ń": 175,
256
+ "®": 176,
257
+ "¯": 177,
258
+ "°": 178,
259
+ "±": 179,
260
+ "²": 180,
261
+ "³": 181,
262
+ "´": 182,
263
+ "µ": 183,
264
+ "¶": 184,
265
+ "·": 185,
266
+ "¸": 186,
267
+ "¹": 187,
268
+ "º": 188,
269
+ "»": 189,
270
+ "¼": 190,
271
+ "½": 191,
272
+ "¾": 192,
273
+ "¿": 193,
274
+ "À": 194,
275
+ "Á": 195,
276
+ "Â": 196,
277
+ "Ã": 197,
278
+ "Ä": 198,
279
+ "Å": 199,
280
+ "Æ": 200,
281
+ "Ç": 201,
282
+ "È": 202,
283
+ "É": 203,
284
+ "Ê": 204,
285
+ "Ë": 205,
286
+ "Ì": 206,
287
+ "Í": 207,
288
+ "Î": 208,
289
+ "Ï": 209,
290
+ "Ð": 210,
291
+ "Ñ": 211,
292
+ "Ò": 212,
293
+ "Ó": 213,
294
+ "Ô": 214,
295
+ "Õ": 215,
296
+ "Ö": 216,
297
+ "×": 217,
298
+ "Ø": 218,
299
+ "Ù": 219,
300
+ "Ú": 220,
301
+ "Û": 221,
302
+ "Ü": 222,
303
+ "Ý": 223,
304
+ "Þ": 224,
305
+ "ß": 225,
306
+ "à": 226,
307
+ "á": 227,
308
+ "â": 228,
309
+ "ã": 229,
310
+ "ä": 230,
311
+ "å": 231,
312
+ "æ": 232,
313
+ "ç": 233,
314
+ "è": 234,
315
+ "é": 235,
316
+ "ê": 236,
317
+ "ë": 237,
318
+ "ì": 238,
319
+ "í": 239,
320
+ "î": 240,
321
+ "ï": 241,
322
+ "ð": 242,
323
+ "ñ": 243,
324
+ "ò": 244,
325
+ "ó": 245,
326
+ "ô": 246,
327
+ "õ": 247,
328
+ "ö": 248,
329
+ "÷": 249,
330
+ "ø": 250,
331
+ "ù": 251,
332
+ "ú": 252,
333
+ "û": 253,
334
+ "ü": 254,
335
+ "ý": 255,
336
+ "þ": 256,
337
+ "ÿ": 257
338
+ },
339
+ "unk_token": "[UNK]"
340
+ }
341
+ }
tokenizer_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "model_max_length": 256,
4
+ "pad_token": "[PAD]",
5
+ "tokenizer_class": "TokenizersBackend",
6
+ "unk_token": "[UNK]"
7
+ }