Text Classification
Transformers
Safetensors
dihya
feature-extraction
berber
amazigh
kabyle
tashelhit
tarifit
tamasheq
tamazight
shawiya
language-identification
conformal-prediction
low-resource
custom_code
Eval Results (legacy)
Instructions to use agbalu/Dihya-5M with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use agbalu/Dihya-5M with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="agbalu/Dihya-5M", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("agbalu/Dihya-5M", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Upload folder using huggingface_hub
Browse files- README.md +241 -242
- __init__.py +4 -0
- config.json +67 -47
- configuration_dihya.py +86 -0
- export.stats.json +64 -0
- model.safetensors +3 -0
- modeling_dihya.py +320 -0
- tokenizer.json +341 -0
- tokenizer_config.json +7 -0
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
|
| 33 |
dataset:
|
| 34 |
-
type:
|
| 35 |
-
name:
|
| 36 |
metrics:
|
| 37 |
- type: accuracy
|
| 38 |
value: 0.8552
|
| 39 |
-
name:
|
| 40 |
- type: f1
|
| 41 |
value: 0.8562
|
| 42 |
-
name: Macro-F1 over
|
| 43 |
---
|
| 44 |
|
| 45 |
# Dihya-5M
|
| 46 |
|
| 47 |
-
A 5.19M-parameter byte-level Conv-Transformer
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
|
| 57 |
-
It ships with
|
| 58 |
-
|
| 59 |
|
| 60 |
-
|
| 61 |
-
conformal safety guarantees for the Berber language family.**
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
|
| 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 |
-
|
| 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 |
-
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
its recall to 22% or below.
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
| 96 |
|
| 97 |
-
|
|
|
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
| unstratified baseline | 72.40% | 0.7023 |
|
| 106 |
|
| 107 |
-
|
| 108 |
|
| 109 |
-
``
|
| 110 |
-
make lid TASK=evaluate
|
| 111 |
-
```
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
|
| 119 |
-
|
|
| 120 |
-
| `
|
| 121 |
-
| `
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
-
|
|
|
|
| 149 |
|
| 150 |
-
|
| 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 |
-
|
| 160 |
-
|
| 161 |
-
of the six classes listed.
|
| 162 |
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
```python
|
| 168 |
-
from
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
"
|
| 176 |
-
"
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
print(
|
| 180 |
-
# kab_Latn 0.
|
| 181 |
-
# rif_Latn
|
| 182 |
-
# NOT_AMAZIGH 0.
|
| 183 |
```
|
| 184 |
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
| 186 |
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
```
|
| 191 |
|
| 192 |
-
**Prediction sets widen
|
| 193 |
-
|
| 194 |
-
singleton with
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
-
**
|
| 197 |
-
|
| 198 |
|
| 199 |
## Architecture
|
| 200 |
|
| 201 |
| | |
|
| 202 |
|---|---|
|
| 203 |
-
| Parameters |
|
|
|
|
| 204 |
| Encoder layers | 6 |
|
| 205 |
| Hidden / feed-forward | 256 / 704 (SwiGLU) |
|
| 206 |
-
| Attention heads
|
| 207 |
-
|
|
| 208 |
-
|
|
| 209 |
-
|
|
| 210 |
-
|
|
| 211 |
-
|
|
| 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 |
-
|
|
|
|
| 235 |
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 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 |
-
|
|
| 269 |
-
|
|
| 270 |
-
|
|
| 271 |
-
|
|
| 272 |
-
|
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
## Limitations
|
| 279 |
|
| 280 |
-
**
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
over-claiming what the text contains.
|
| 284 |
|
| 285 |
-
**
|
| 286 |
-
from a Tifinagh sentence will produce a prediction, but that prediction has not been measured and
|
| 287 |
-
should not be used.
|
| 288 |
|
| 289 |
-
**
|
| 290 |
-
|
| 291 |
-
are not the input shape it was built for.
|
| 292 |
|
| 293 |
-
**
|
| 294 |
-
handled. A sentence mixing Kabyle and French will receive a single class label, which may be wrong.
|
| 295 |
|
| 296 |
-
**
|
| 297 |
|
| 298 |
-
|
|
|
|
| 299 |
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 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 |
|
| 311 |
-
|---|---|
|
| 312 |
-
| `
|
| 313 |
-
| `config.json` |
|
| 314 |
-
| `
|
| 315 |
-
| `
|
|
|
|
|
|
|
| 316 |
|
| 317 |
-
|
| 318 |
-
no scheduler state, no training curve. Training cannot be resumed from the published file.
|
| 319 |
|
| 320 |
## Reproduction
|
| 321 |
|
| 322 |
```bash
|
| 323 |
-
make
|
| 324 |
-
make modal-lid
|
| 325 |
-
make lid
|
| 326 |
-
make lid
|
| 327 |
-
make
|
|
|
|
| 328 |
```
|
| 329 |
|
| 330 |
-
|
| 331 |
-
baseline computed elsewhere on a different sample is not a comparison.
|
| 332 |
|
| 333 |
## The name
|
| 334 |
|
| 335 |
-
**Dihya** (also
|
| 336 |
-
|
| 337 |
-
the
|
| 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
|
| 345 |
|
| 346 |
## Citation
|
| 347 |
|
| 348 |
```bibtex
|
| 349 |
@software{agbalu_dihya_2026,
|
| 350 |
-
title = {Dihya-5M: language identification
|
| 351 |
author = {AƔBALU},
|
| 352 |
year = {2026},
|
| 353 |
url = {https://huggingface.co/agbalu/Dihya-5M},
|
| 354 |
-
note = {5
|
| 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 |
-
"
|
| 3 |
-
"
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 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 |
-
"
|
| 30 |
-
|
| 31 |
-
"
|
| 32 |
-
"
|
| 33 |
-
"
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 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 |
+
}
|