01Yassine commited on
Commit
01a8664
·
verified ·
1 Parent(s): 76bab86

Public Darija adapter + Hub-first infer.py

Browse files
Files changed (2) hide show
  1. README.md +27 -18
  2. infer.py +66 -34
README.md CHANGED
@@ -14,43 +14,52 @@ tags:
14
  pipeline_tag: automatic-speech-recognition
15
  ---
16
 
17
- # Cohere Transcribe — Moroccan Darija
18
 
19
- Adapter on [Cohere Transcribe Arabic](https://huggingface.co/CohereLabs/cohere-transcribe-arabic-07-2026). Frozen 2B encoder, a small conv adapter, decoder LoRA. Trained on 3 hours of YouTube Darija ([`01Yassine/darija-asr-3h`](https://huggingface.co/datasets/01Yassine/darija-asr-3h)).
20
 
21
- Eval: [`atlasia/darija-asr-benchmark`](https://huggingface.co/datasets/atlasia/darija-asr-benchmark) (114 clips, human labels).
22
 
23
  | | CER | WER |
24
  | --- | ---: | ---: |
25
  | base | 20.2 | 49.1 |
26
- | **this model** | **14.4** | **38.3** |
27
 
28
- You also need the base Cohere checkpoint (and its license).
29
 
30
- ## Inference
31
 
32
  ```bash
33
  pip install "transformers>=5.4" peft torch torchaudio soundfile huggingface_hub
34
  ```
35
 
36
- Download this repo (weights + `infer.py` + `adapters.py`), then:
 
 
 
 
 
 
 
 
37
 
38
  ```bash
39
- python infer.py clip.wav --model .
 
40
  ```
41
 
42
- Or from Python, after adding this folder to `PYTHONPATH`:
43
 
44
- ```python
45
- from infer import transcribe
46
- print(transcribe("clip.wav", model_id="."))
47
- ```
48
 
49
- From anywhere, pointing at the Hub id:
 
 
 
 
 
50
 
51
  ```python
52
- from infer import transcribe
53
- print(transcribe("clip.wav", model_id="01Yassine/cohere-transcribe-darija"))
54
  ```
55
-
56
- Writeup: the training notebook in the companion code repo.
 
14
  pipeline_tag: automatic-speech-recognition
15
  ---
16
 
17
+ # Cohere Transcribe — Moroccan Darija (hybrid)
18
 
19
+ Public adapter on [Cohere Transcribe Arabic](https://huggingface.co/CohereLabs/cohere-transcribe-arabic-07-2026). **Hybrid:** MultiConv on encoder layers 15–47 + LoRA on the decoder. Trained on 3h YouTube Darija ([`01Yassine/darija-asr-3h`](https://huggingface.co/datasets/01Yassine/darija-asr-3h)).
20
 
21
+ Eval: [`atlasia/darija-asr-benchmark`](https://huggingface.co/datasets/atlasia/darija-asr-benchmark) (114 clips, human).
22
 
23
  | | CER | WER |
24
  | --- | ---: | ---: |
25
  | base | 20.2 | 49.1 |
26
+ | **hybrid (this repo)** | **14.4** | **38.3** |
27
 
28
+ You need the Cohere base weights (and its license). This repo is only the adapter.
29
 
30
+ ## Inference (from the Hub, no training clone)
31
 
32
  ```bash
33
  pip install "transformers>=5.4" peft torch torchaudio soundfile huggingface_hub
34
  ```
35
 
36
+ ```python
37
+ from huggingface_hub import snapshot_download
38
+ import sys
39
+ sys.path.insert(0, snapshot_download("01Yassine/cohere-transcribe-darija"))
40
+ from infer import transcribe
41
+ print(transcribe("clip.wav"))
42
+ ```
43
+
44
+ Or, if you already have `infer.py` from this repo:
45
 
46
  ```bash
47
+ python infer.py clip.wav --model hybrid
48
+ python infer.py clip.wav --model 01Yassine/cohere-transcribe-darija
49
  ```
50
 
51
+ ## Other open checkpoints
52
 
53
+ Same data and seed, different trainable slice:
 
 
 
54
 
55
+ | recipe | Hub | AtlasIA CER |
56
+ | --- | --- | ---: |
57
+ | **hybrid** (MultiConv + LoRA) | [`01Yassine/cohere-transcribe-darija`](https://huggingface.co/01Yassine/cohere-transcribe-darija) | **14.4** |
58
+ | full LoRA | [`01Yassine/cohere-transcribe-darija-full-lora`](https://huggingface.co/01Yassine/cohere-transcribe-darija-full-lora) | 16.5 |
59
+ | encoder LoRA | [`01Yassine/cohere-transcribe-darija-encoder-lora`](https://huggingface.co/01Yassine/cohere-transcribe-darija-encoder-lora) | 17.4 |
60
+ | decoder LoRA | [`01Yassine/cohere-transcribe-darija-decoder-lora`](https://huggingface.co/01Yassine/cohere-transcribe-darija-decoder-lora) | 20.2 |
61
 
62
  ```python
63
+ print(transcribe("clip.wav", model_id="full_lora"))
64
+ print(transcribe("clip.wav", model_id="01Yassine/cohere-transcribe-darija-encoder-lora"))
65
  ```
 
 
infer.py CHANGED
@@ -1,14 +1,22 @@
1
  #!/usr/bin/env python3
2
- """Transcribe Darija audio with the adapted Cohere checkpoint.
 
 
 
3
 
4
  python infer.py clip.wav
5
- python infer.py clip.wav --model checkpoints/cohere-method/best
6
- python infer.py clip.wav --model 01Yassine/cohere-transcribe-darija
 
 
 
 
7
  """
8
 
9
  from __future__ import annotations
10
 
11
  import argparse
 
12
  import json
13
  import sys
14
  from pathlib import Path
@@ -18,11 +26,19 @@ import soundfile as sf
18
  import torch
19
  import torchaudio
20
 
21
- HUB_ID = "01Yassine/cohere-transcribe-darija"
22
  BASE_MODEL = "CohereLabs/cohere-transcribe-arabic-07-2026"
23
  SAMPLE_RATE = 16000
24
  LANGUAGE = "ar"
25
 
 
 
 
 
 
 
 
 
 
26
 
27
  def _load_wav(path: str) -> np.ndarray:
28
  wav, sr = sf.read(path, dtype="float32")
@@ -35,53 +51,63 @@ def _load_wav(path: str) -> np.ndarray:
35
  return np.asarray(wav, dtype=np.float32)
36
 
37
 
38
- def _resolve(model_id: str) -> Path:
 
 
 
 
 
39
  local = Path(model_id)
40
- if local.is_dir():
41
- return local
42
  from huggingface_hub import snapshot_download
43
 
44
- return Path(snapshot_download(model_id))
45
 
46
 
47
- def _attach(model, root: Path):
48
- meta = json.loads((root / "adapter_meta.json").read_text())
49
- try:
50
- from src.adapters import attach_multiconv_adapters
51
- except ImportError:
52
  sys.path.insert(0, str(root))
53
- from adapters import attach_multiconv_adapters # type: ignore
54
-
55
- if meta.get("attached_layers"):
56
- attach_multiconv_adapters(
57
- model,
58
- bottleneck=meta["bottleneck"],
59
- kernels=tuple(meta["kernels"]),
60
- dropout=meta["dropout"],
61
- skip_bottom_frac=meta.get("skip_bottom_frac", 0.33),
62
- fusion=meta.get("fusion", "concat_fusion"),
63
- merge_kernel=meta.get("merge_kernel", 31),
64
- )
65
- return meta
66
 
67
 
68
  def load_model(model_id: str = HUB_ID, device: str | None = None):
69
- """Load base Cohere + conv adapters + decoder LoRA."""
70
  from transformers import AutoProcessor, CohereAsrForConditionalGeneration
71
  from peft import PeftModel
72
 
73
  device = device or ("cuda" if torch.cuda.is_available() else "cpu")
74
- root = _resolve(model_id)
 
 
 
75
  processor = AutoProcessor.from_pretrained(BASE_MODEL)
76
  model = CohereAsrForConditionalGeneration.from_pretrained(
77
  BASE_MODEL, dtype=torch.bfloat16
78
  )
79
- _attach(model, root)
80
  if (root / "adapter_config.json").exists():
81
  model = PeftModel.from_pretrained(model, str(root))
82
  extra = root / "encoder_adapters.pt"
83
- if extra.exists():
84
- model.load_state_dict(torch.load(extra, map_location="cpu", weights_only=True), strict=False)
 
 
85
  model.to(device).eval()
86
  return model, processor, device
87
 
@@ -105,7 +131,9 @@ def transcribe(
105
  out = model.generate(**inputs, max_new_tokens=128)
106
  chunk = inputs.get("audio_chunk_index") if hasattr(inputs, "get") else None
107
  try:
108
- text = processor.decode(out, skip_special_tokens=True, audio_chunk_index=chunk, language=LANGUAGE)
 
 
109
  if isinstance(text, (list, tuple)):
110
  text = text[0]
111
  except TypeError:
@@ -114,9 +142,13 @@ def transcribe(
114
 
115
 
116
  def main() -> None:
117
- parser = argparse.ArgumentParser(description="Darija ASR (adapted Cohere Transcribe Arabic)")
118
  parser.add_argument("audio", help="wav / flac / ogg path")
119
- parser.add_argument("--model", default=HUB_ID)
 
 
 
 
120
  parser.add_argument("--device", default=None)
121
  args = parser.parse_args()
122
  model, processor, device = load_model(args.model, args.device)
 
1
  #!/usr/bin/env python3
2
+ """Darija ASR from a Hugging Face adapter repo.
3
+
4
+ The adapter repo holds LoRA (and MultiConv weights if hybrid). The 2B
5
+ Cohere base is pulled automatically.
6
 
7
  python infer.py clip.wav
8
+ python infer.py clip.wav --model hybrid
9
+ python infer.py clip.wav --model 01Yassine/cohere-transcribe-darija-full-lora
10
+
11
+ from infer import transcribe
12
+ print(transcribe("clip.wav"))
13
+ print(transcribe("clip.wav", model_id="full_lora"))
14
  """
15
 
16
  from __future__ import annotations
17
 
18
  import argparse
19
+ import importlib
20
  import json
21
  import sys
22
  from pathlib import Path
 
26
  import torch
27
  import torchaudio
28
 
 
29
  BASE_MODEL = "CohereLabs/cohere-transcribe-arabic-07-2026"
30
  SAMPLE_RATE = 16000
31
  LANGUAGE = "ar"
32
 
33
+ # Short names → public adapter repos. Each ships infer.py + adapters.py.
34
+ MODELS = {
35
+ "hybrid": "01Yassine/cohere-transcribe-darija",
36
+ "full_lora": "01Yassine/cohere-transcribe-darija-full-lora",
37
+ "encoder_lora": "01Yassine/cohere-transcribe-darija-encoder-lora",
38
+ "decoder_lora": "01Yassine/cohere-transcribe-darija-decoder-lora",
39
+ }
40
+ HUB_ID = MODELS["hybrid"]
41
+
42
 
43
  def _load_wav(path: str) -> np.ndarray:
44
  wav, sr = sf.read(path, dtype="float32")
 
51
  return np.asarray(wav, dtype=np.float32)
52
 
53
 
54
+ def resolve_id(model_id: str) -> str:
55
+ return MODELS.get(model_id, model_id)
56
+
57
+
58
+ def _snapshot(model_id: str) -> Path:
59
+ """Local dir, or download the Hub adapter repo (weights + adapters.py)."""
60
  local = Path(model_id)
61
+ if local.is_dir() and (local / "adapter_config.json").exists():
62
+ return local.resolve()
63
  from huggingface_hub import snapshot_download
64
 
65
+ return Path(snapshot_download(resolve_id(model_id)))
66
 
67
 
68
+ def _attach_conv(model, root: Path, meta: dict) -> None:
69
+ if not meta.get("attached_layers") and not meta.get("conv_adapter"):
70
+ return
71
+ adapters_py = root / "adapters.py"
72
+ if adapters_py.exists():
73
  sys.path.insert(0, str(root))
74
+ attach = importlib.import_module("adapters").attach_multiconv_adapters
75
+ else:
76
+ from src.adapters import attach_multiconv_adapters as attach
77
+
78
+ attach(
79
+ model,
80
+ bottleneck=meta["bottleneck"],
81
+ kernels=tuple(meta["kernels"]),
82
+ dropout=meta["dropout"],
83
+ skip_bottom_frac=meta.get("skip_bottom_frac", 0.33),
84
+ fusion=meta.get("fusion", "concat_fusion"),
85
+ merge_kernel=meta.get("merge_kernel", 31),
86
+ )
87
 
88
 
89
  def load_model(model_id: str = HUB_ID, device: str | None = None):
90
+ """Load Cohere Arabic + this Hub adapter. No local training files needed."""
91
  from transformers import AutoProcessor, CohereAsrForConditionalGeneration
92
  from peft import PeftModel
93
 
94
  device = device or ("cuda" if torch.cuda.is_available() else "cpu")
95
+ root = _snapshot(model_id)
96
+ meta_path = root / "adapter_meta.json"
97
+ meta = json.loads(meta_path.read_text()) if meta_path.exists() else {}
98
+
99
  processor = AutoProcessor.from_pretrained(BASE_MODEL)
100
  model = CohereAsrForConditionalGeneration.from_pretrained(
101
  BASE_MODEL, dtype=torch.bfloat16
102
  )
103
+ _attach_conv(model, root, meta)
104
  if (root / "adapter_config.json").exists():
105
  model = PeftModel.from_pretrained(model, str(root))
106
  extra = root / "encoder_adapters.pt"
107
+ if extra.exists() and extra.stat().st_size > 2048:
108
+ model.load_state_dict(
109
+ torch.load(extra, map_location="cpu", weights_only=True), strict=False
110
+ )
111
  model.to(device).eval()
112
  return model, processor, device
113
 
 
131
  out = model.generate(**inputs, max_new_tokens=128)
132
  chunk = inputs.get("audio_chunk_index") if hasattr(inputs, "get") else None
133
  try:
134
+ text = processor.decode(
135
+ out, skip_special_tokens=True, audio_chunk_index=chunk, language=LANGUAGE
136
+ )
137
  if isinstance(text, (list, tuple)):
138
  text = text[0]
139
  except TypeError:
 
142
 
143
 
144
  def main() -> None:
145
+ parser = argparse.ArgumentParser(description="Darija ASR from a Hugging Face adapter")
146
  parser.add_argument("audio", help="wav / flac / ogg path")
147
+ parser.add_argument(
148
+ "--model",
149
+ default="hybrid",
150
+ help="Hub id, local dir, or one of: " + ", ".join(MODELS),
151
+ )
152
  parser.add_argument("--device", default=None)
153
  args = parser.parse_args()
154
  model, processor, device = load_model(args.model, args.device)