Instructions to use arnauld-a/griot-lora-ewe-turbo with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use arnauld-a/griot-lora-ewe-turbo with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Configuration Parsing Warning:In adapter_config.json: "peft.task_type" must be a string
Griot ASR — LoRA ewe (Whisper large-v3-turbo)
Adaptateur LoRA pour l'ewe (ee, famille Gbe, Ghana et Togo), entraîné sur
50 h de google/WaxalNLP config ewe_asr, splits ASR_v2 speaker-disjoint.
Premier volet d'une étude comparant LoRA sur turbo, LoRA sur large-v3 et full fine-tuning à budget de données constant, menée par Bivariant dans le cadre du projet Griot.
Résultats — test v2 (1 883 énoncés, 9,97 h), beam 4
| checkpoint | CER | WER | sub | del | ins |
|---|---|---|---|---|---|
| 1000 | 29,99 | 52,46 | 16 549 | 14 455 | 2 457 |
| 1500 (publié) | 29,28 | 51,29 | 16 288 | 13 815 | 2 610 |
| 2000 | 30,37 | 51,83 | 15 893 | 14 930 | 2 235 |
| 2500 | 30,16 | 51,55 | 15 915 | 14 666 | 2 300 |
| 2995 | 29,97 | 51,62 | 16 026 | 14 599 | 2 304 |
Le classement est identique en greedy (validation) et en beam 4 (test).
diac_gap vaut 0,16–0,17 sur tous les checkpoints. Ce n'est pas un
indicateur de qualité tonale : le corpus ewe de Waxal ne marque le ton que sur
~1,3 % des mots. Les diacritiques y sont dominés par le tilde combinant U+0303,
qui note la nasalisation (230 des 306 diacritiques du test set).
Ces chiffres comportent un artefact identifié, décrit plus bas.
Utilisation
from peft import PeftModel
from transformers import (WhisperForConditionalGeneration, WhisperProcessor,
WhisperTokenizer)
REPO = "arnauld-a/griot-lora-ewe-turbo"
BASE = "openai/whisper-large-v3-turbo"
tok = WhisperTokenizer.from_pretrained(REPO) # vocabulaire étendu (51 882)
proc = WhisperProcessor.from_pretrained(REPO)
m = WhisperForConditionalGeneration.from_pretrained(BASE)
m.resize_token_embeddings(len(tok)) # AVANT l'adaptateur
m = PeftModel.from_pretrained(m, REPO).merge_and_unload()
out = m.generate(features, language="<|ewe|>", task="transcribe",
num_beams=4, repetition_penalty=1.0, no_repeat_ngram_size=14,
max_new_tokens=440, length_penalty=1.2)
Trois points obligatoires. Le tokenizer doit venir de ce dépôt, pas du modèle
de base. resize_token_embeddings doit précéder le chargement de l'adaptateur,
qui porte une table [51882, 1280]. Et generate(language="<|ewe|>") ne
fonctionne que si generation_config.lang_to_id contient le token — il est
inclus dans generation_config.json.
length_penalty=1.2 doit retomber à 1.0 sur le chemin long-form (>30 s) :
sur une fenêtre ne portant que quelques secondes de parole, il pousse le
décodeur à combler le vide (166 % de WER mesuré contre 42 % à 1.0). Sans objet
ici, MAX_DURATION=30 s garantissant qu'aucun énoncé n'emprunte ce chemin.
Données
ewe_asr filtré à 1–30 s, sous-échantillonné à 50 h par locuteurs entiers
(264 des 431 locuteurs, 9 572 énoncés, seed 20260906).
Les splits v2 sont indispensables : le split v1 présente
v1_speaker_leak_max = 0,9975, et le rebrassage est quasi total — seuls 176
des 1 884 énoncés du test v2 proviennent du test v1, les 80 % restants sont
dans le train v1. On ne peut donc pas se contenter des splits d'origine.
ewe_asr est le seul corpus Waxal en stéréo 44,1 kHz : downmix mono et
rééchantillonnage 16 kHz explicites, aucun débruitage.
Extension du vocabulaire
51 866 → 51 882 tokens : 14 caractères ewe (ŋ Ŋ ɖ Ɖ ƒ Ƒ ɣ Ɣ ɔ Ɔ ɛ Ɛ ʋ Ʋ), le
tilde combinant U+0303, et <|ewe|>.
Le tokenizer Whisper traite l'ewe à 0,624 token par caractère contre 0,407 pour le shona — 53 % de surcoût dû au repli byte-level sur ces 14 caractères. L'extension n'est donc pas cosmétique.
Chaque nouvelle ligne est initialisée depuis un token existant proche : ɖ←d,
ɔ←o, ŋ←n, et <|ewe|>←<|yo|> (yoruba : branche Volta-Niger, tonale, Afrique
de l'Ouest), suivant Song et al. 2024.
Liste blanche explicite, pas d'extraction automatique. Une extraction naïve
des caractères du corpus ajoute les artefacts de nettoyage : sur un modèle
wolof antérieur, elle avait introduit \r, \n, €, œ et surtout l'espace
comme token régulier — Whisper encodant l'espace en préfixe Ġ, cela crée une
ambiguïté de segmentation.
Embeddings pré-entraînés gelés
resize_token_embeddings impose modules_to_save=["decoder.embed_tokens", "proj_out"], ce qui rend la table entière entraînable — 132,8 M de
paramètres contre 49,2 M pour les adaptateurs LoRA.
Mesuré sur un modèle wolof antérieur sans protection :
| zone du vocabulaire | dérive |
|---|---|
| ids 0–199 (BPE octets, jamais utilisés) | 22,22 % |
| ids 20000–20199 (BPE courants) | 10,33 % |
| ids 50000–50199 (BPE rares) | 6,69 % |
écart embed_tokens / proj_out |
0,00 % → 46,89 % |
Whisper a tie_word_embeddings=True ; PEFT en crée deux copies indépendantes
qui divergent librement.
Deux protections combinées ici, l'une ne suffisant pas sans l'autre :
- masque de gradient sur les lignes 0..51 865 (approche Soft Language Code Tuning, Qian et al., arXiv:2407.06800) ;
- weight decay nul sur les embeddings — AdamW applique
p ← p − lr·wd·pindépendamment du gradient, donc le masque seul laisserait les lignes gelées rétrécir à chaque pas. C'est ce mécanisme qui produit les 22 % ci-dessus.
Plus ensure_weight_tying=True (peft ≥ 0.20), qui préserve le partage de poids
et évite les complications au merge_and_unload().
Résultat vérifié sur ce modèle :
| zone | dérive |
|---|---|
| ids 0–199 | 0,000 % |
| ids 20000–20199 | 0,000 % |
| ids 50000–50199 | 0,000 % |
écart embed_tokens / proj_out |
0,000 % |
| tokens ajoutés ewe | 40,76 % |
Les nouveaux tokens apprennent autant que sans protection (40,76 % contre 41,49 % sur le wolof) : le masque supprime les dégâts collatéraux sans brider l'apprentissage utile. Il a en revanche un effet de bord, décrit ci-dessous.
Limite connue — boucle sur les tokens ajoutés (13 % des énoncés)
Le ratio mots générés / mots de référence est de 0,823. Le déficit se concentre sur une population précise :
| population | part des énoncés | ratio | ponctuation finale |
|---|---|---|---|
| effondrés (≤ 10 mots générés) | 13,3 % | 0,169 | 0 % |
| reste | 86,7 % | 0,920 | 78 % |
Les hypothèses effondrées s'arrêtent en plein milieu d'un mot (Akɔɖ pour
Akɔɖu, Enudzɔdzɔŋ pour Enudzɔdzɔ ŋɔŋlɔ ɖi).
Diagnostic — cause identifiée
100 % des séquences effondrées se terminent sur un token ajouté, contre 26 % chez les témoins et 25,4 % en base de comparaison (part des tokens ajoutés toutes positions confondues). Sur 25 séquences, la probabilité d'un tel écart par hasard est de l'ordre de 10⁻¹⁵.
L'inspection des logits au point de coupure donne le mécanisme. Le modèle ne
cherche pas à s'arrêter — <|endoftext|> est à −8 à −10 logprob de marge,
absent du top 6. Ce qu'il veut, c'est redoubler le token qu'il vient
d'émettre :
| contexte se termine par | token le plus probable | logprob |
|---|---|---|
ɖ |
ɖ |
−0,184 |
ƒ |
ƒ |
−0,598 |
̃ |
̃ |
−0,111 |
ƒ |
ƒ |
−0,627 |
ɔ |
ɔ |
−0,822 |
Cinq cas sur cinq, avec des probabilités de 0,44 à 0,89 — massivement
dominantes. Le modèle part en boucle sur les nouveaux tokens (ɖɖɖɖ…).
no_repeat_ngram_size=14 interdit cette répétition ; il ne reste que des
candidats à −3,5 ou pire, et le faisceau termine la séquence.
C'est pourquoi retirer no_repeat_ngram_size ne change rien : sans lui la
sortie partirait en boucle et serait tout aussi fausse, length_penalty=1.2
finissant de toute façon par préférer une séquence courte. Deux chemins, même
résultat.
Origine — un effet de bord du gel sélectif
Les 16 tokens ajoutés sont les seules lignes entraînables de la table d'embeddings, tout le reste étant gelé par le masque de gradient décrit plus haut. Ils ont dérivé de 40,76 %.
Avec ensure_weight_tying=True, leur ligne de sortie dans proj_out est
partagée avec l'embedding d'entrée. Elle est donc poussée vers le haut à chaque
occurrence du caractère, sans que les tokens anciens puissent se réajuster en
compensation puisqu'ils sont gelés. Après avoir émis ɖ, l'état caché
ressemble à celui qui prédit ɖ, et rien ne vient contrebalancer.
Le gel supprime la dérive des embeddings pré-entraînés — 22 % → 0,000 % — mais crée un déséquilibre entre lignes gelées et lignes libres. Ce compromis n'est, à notre connaissance, documenté nulle part dans la littérature sur l'extension de vocabulaire en ASR.
Hypothèses écartées par la mesure
| hypothèse | test | verdict |
|---|---|---|
no_repeat_ngram_size=14 |
ablation du paramètre | identique au centième |
| interaction beam × contrainte | greedy | ratio 0,169 → 0,254 seulement |
| troncature du décodage MP3 | durée décodée vs octets | ratio 1,000 |
| référence plus longue que l'audio | VAD + mots/s de parole | 2,49 vs 2,55 — identique au témoin |
| voix mal généralisées | répartition par locuteur | 38 locuteurs sur 54, 8,7–26,8 % |
| mécanisme appris (label smoothing) | taux par checkpoint | plat, 12,9–13,5 % de 1 000 à 2 995 pas |
| norme des nouveaux embeddings | normes L2 | 0,659 vs 0,635 — normale |
| plafond de 448 tokens | ~100 tokens par énoncé | hors de cause |
| masquage EOS / labels | inspection du collator | 1 seul EOT par séquence, bien placé |
TimeStampLogitsProcessor |
décodage sans skip_special_tokens |
0 token d'horodatage |
| logits processors en général | génération sans aucun processeur | ratio 0,829 → 0,829 |
Le modèle de base non fine-tuné, sur ces mêmes énoncés, produit un ratio de 1,085 à 1,173. L'audio contient donc bien la parole sur toute sa durée, et les références sont plausibles.
Pistes de correction
Le correctif est à l'entraînement. min_new_tokens est inopérant : forcer la
génération produirait ɖɖɖ…, le modèle voulant précisément répéter.
| piste | principe | risque |
|---|---|---|
| LR séparé sur les embeddings — 1e-5 au lieu de 2e-4 | limite la dérive des lignes libres sans les figer | le plus simple, à tester en premier |
| dégeler la table, weight decay nul | les anciens tokens se réajustent en compensation | réintroduit une dérive, à mesurer |
sans ensure_weight_tying |
proj_out évolue indépendamment de l'embedding d'entrée |
complications documentées au merge_and_unload() |
| renormalisation périodique des lignes libres sur la moyenne des gelées | contrainte explicite plutôt qu'implicite | non testé |
Conséquence pratique
Le WER de 51,29 comporte une composante d'artefact estimée à plusieurs points. Il reste valide comme point de comparaison à condition que les modèles comparés subissent le même protocole, ce qui est le cas des autres volets de l'étude.
Les fichiers hyp_*.jsonl (hypothèses par checkpoint, alignées sur les
références) sont disponibles sur demande.
Entraînement
| Base | openai/whisper-large-v3-turbo |
| LoRA | r=64, α=128, dropout 0.1 |
| Cibles | q_proj, k_proj, v_proj, o_proj, fc1, fc2 |
| Paramètres entraînables | 115,6 M / 924,5 M (12,50 %) |
| LR | 2e-4, cosine, 149 pas de warmup |
| Batch effectif | 16 (16 × 1) |
| Epochs | 5 (2 995 pas) |
| Précision | bf16, gradient checkpointing |
| Label smoothing | 0.05 |
| Seed | 42 (données : 20260906) |
| Matériel | A100-SXM4 40 Go, 1 h 20 |
Configuration complète dans griot_run.json.
L'évaluation en cours d'entraînement est en greedy pour ne pas allonger le run ; les checkpoints ont été réévalués en beam 4 a posteriori.
Licence
CC-BY-4.0, suivant google/WaxalNLP (Université du Ghana).
Modèle de base sous Apache-2.0.
Références
- Qian et al., Learn and Don't Forget: Adding a New Language to ASR Foundation Models, arXiv:2407.06800
- Song et al., LoRA-Whisper: Parameter-Efficient and Extensible Multilingual ASR, 2024
- Nahabwe et al., Data efficiency benchmark for African ASR, arXiv:2512.10968
- Downloads last month
- 21
Model tree for arnauld-a/griot-lora-ewe-turbo
Dataset used to train arnauld-a/griot-lora-ewe-turbo
Papers for arnauld-a/griot-lora-ewe-turbo
Benchmarking Automatic Speech Recognition Models for African Languages
Learn and Don't Forget: Adding a New Language to ASR Foundation Models
Evaluation results
- cer on WaxalNLP ewe_asr (ASR_v2 test, speaker-disjoint)test set self-reported29.280
- wer on WaxalNLP ewe_asr (ASR_v2 test, speaker-disjoint)test set self-reported51.290