Broulaye Doumbia commited on
Commit
da3a060
·
1 Parent(s): 76aac1b

fix read me

Browse files
README.md CHANGED
@@ -13,38 +13,261 @@ tags:
13
  - bambara
14
  - fula
15
  - speech-recognition
 
 
 
16
  - language-learning
17
  - west-africa
18
  - low-resource-nlp
19
  - memory
20
  ---
21
 
22
- # 🌍 Sahel-Voice-Lab — Internal Edition
23
 
24
- **Phase 1 · The Memory Loop**
25
 
26
- A self-learning voice assistant for Bambara and Fula. Teach it words — it remembers them forever.
27
 
28
- ## Stack (100% non-Meta)
29
- | Component | Model |
30
- |-----------|-------|
31
- | STT | `openai/whisper-large-v3-turbo` |
32
- | LLM | `Qwen/Qwen2.5-72B-Instruct` (set `LLM_MODEL_ID` env var to override) |
33
- | TTS | Waxal — Phase 2 |
34
- | Memory | HF Dataset `vocabulary.jsonl` |
35
 
36
- ## How it works
37
- 1. Press Push-to-Talk → speak in Bambara, Fula, French, or English
38
- 2. Whisper transcribes your speech
39
- 3. Gemma reads the vocabulary it has learned so far, then:
40
- - **Teaching mode**: detects "X means Y" → saves the word pair to the Hub
41
- - **Question mode**: answers using vocabulary as source of truth
42
- - **Conversation mode**: replies naturally
43
- 4. The last 5 learned words are always visible
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- ## Space secrets required
46
  | Key | Value |
47
  |-----|-------|
48
- | `HF_TOKEN` | Your HF write-access token |
49
  | `FEEDBACK_REPO_ID` | `ous-sow/sahel-agri-feedback` |
50
- | `LLM_MODEL_ID` | `Qwen/Qwen2.5-72B-Instruct` (or any HF Serverless-supported model) |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  - bambara
14
  - fula
15
  - speech-recognition
16
+ - text-to-speech
17
+ - agriculture
18
+ - iot
19
  - language-learning
20
  - west-africa
21
  - low-resource-nlp
22
  - memory
23
  ---
24
 
25
+ # 🌍 Sahel-Voice-Lab
26
 
27
+ **A voice-first AI assistant for Bambara (Mali) and Fula/Pular (Guinea, Senegal).**
28
 
29
+ Two intertwined jobs:
30
 
31
+ 1. **Memory loop** — users *teach* the assistant new words; it persists them to a HuggingFace dataset and uses them as the source of truth in future answers.
32
+ 2. **Agricultural IoT voice interface** — Sahelian farmers query soil, weather, irrigation, and pest data in their own language, short answers, ≤ 6 words per sentence for clean TTS.
 
 
 
 
 
33
 
34
+ The core stack is explicitly **100% non-Meta** (Whisper / Qwen / F5-TTS / VITS); MMS-TTS is only used as a baseline fallback.
35
+
36
+ ---
37
+
38
+ ## Status
39
+
40
+ | Phase | Feature | State |
41
+ |------:|---------|-------|
42
+ | 1 | Memory loop (JSONL + HF Hub) | ✅ shipped |
43
+ | 2 | Waxal VITS TTS — Bambara | ✅ shipped |
44
+ | 2 | Waxal VITS TTS — Fula | ⏳ placeholder until `ous-sow/fula-tts` is trained |
45
+ | 3 | Voice-to-voice S2S (F5-TTS + CER) | 🚧 merged, stabilizing |
46
+ | — | Adlam ↔ Latin round-trip, per-language prompts | ✅ landed |
47
+
48
+ See `docs/roadmap_2026-04.md` for the full plan and `docs/baseline_rebuild.md` for the parallel minimal-track strategy.
49
+
50
+ ---
51
+
52
+ ## Stack
53
+
54
+ | Layer | Tool |
55
+ |-------|------|
56
+ | STT | `openai/whisper-large-v3-turbo` + PEFT LoRA hot-swap (~50 MB adapter per language, ~50 ms switch) |
57
+ | LLM | `Qwen/Qwen2.5-7B-Instruct` (prod default) via HF Serverless InferenceClient — overridable to `Qwen2.5-72B-Instruct`, Mistral, Zephyr |
58
+ | TTS (baseline) | `facebook/mms-tts-bam`, `facebook/mms-tts-ful` |
59
+ | TTS (Bambara) | `ynnov/ekodi-bambara-tts-female` (Waxal VITS) |
60
+ | TTS (Fula) | placeholder → `ous-sow/fula-tts` when published |
61
+ | Voice cloning | F5-TTS + OpenVoice V2 (Phase 3, GPU-only) |
62
+ | Speaker ID | SpeechBrain ECAPA-TDNN, 192-d embeddings, cosine ≥ 0.75 |
63
+ | Fast path | RapidFuzz over `data/phrases/{lang}.json` for greetings / thanks / farewells |
64
+ | Persistence | JSONL on disk + HF Hub datasets (no ORM) |
65
+ | Training | PEFT LoRA + `Seq2SeqTrainer` on FLEURS, Jeli-ASR, SLR 105/106 |
66
+
67
+ ---
68
+
69
+ ## Three entry points (do not conflate)
70
+
71
+ | File | Purpose | Lifecycle |
72
+ |------|---------|-----------|
73
+ | `app.py` | **Production Gradio UI** on HF Spaces. Single-file (~99 KB) by design. Tabs: Conversation / Teaching / Knowledge Base / Self-Teaching. | `python app.py` |
74
+ | `app_lab.py` | **Experimental Gradio UI** for prototyping (e.g. `CuriosityEngine`) before folding into `app.py`. | `python app_lab.py` |
75
+ | `src/api/app.py` | **FastAPI service** — loads Whisper once, registers `bam`/`ful` adapters via `AdapterManager`, preloads `bam`, attaches `Transcriber` + `SensorBridge` to `app.state`. | `python scripts/run_server.py` |
76
+
77
+ ---
78
+
79
+ ## Repository layout
80
+
81
+ ```
82
+ app.py # Gradio (production, HF Spaces)
83
+ app_lab.py # Gradio (experimental)
84
+ requirements.txt # Spaces runtime — do NOT pin torch/torchaudio
85
+ packages.txt # apt deps (ffmpeg)
86
+ configs/
87
+ base_config.yaml # shared settings
88
+ api_config.yaml # FastAPI-specific
89
+ lora_bambara.yaml # Bambara LoRA hyperparams
90
+ lora_fula.yaml # Fula LoRA hyperparams
91
+ data/
92
+ phrases/ # RapidFuzz shortcut phrase JSONs per language
93
+ vocabulary.jsonl # local mirror of the HF Hub memory dataset
94
+ docs/
95
+ roadmap_2026-04.md # full architectural walkthrough + action plan
96
+ baseline_rebuild.md # parallel minimal-track plan (non-destructive)
97
+ notebook_collaboration.md # Kaggle push/pull workflow for contributors
98
+ kaggle_mcp_setup.md # optional Kaggle MCP for Claude Desktop
99
+ notebooks/
100
+ kaggle_master_trainer/ # -> oussow/kaggle-master-trainer (LoRA fine-tune)
101
+ train_fula_tts/ # -> oussow/sahel-voice-fula-tts-trainer (TBD)
102
+ bootstrap_repos.ipynb
103
+ train_colab.ipynb # legacy Colab trainer
104
+ scripts/
105
+ train_bambara.py # LoRA fine-tune entrypoint (Kaggle/RunPod)
106
+ train_fula.py # LoRA fine-tune entrypoint (Kaggle/RunPod)
107
+ export_onnx.py # merge LoRA -> ONNX -> TFLite
108
+ verify_baseline.py # eval harness
109
+ run_server.py # FastAPI launcher
110
+ run_data_pipeline.py # dataset prep
111
+ push_to_hf.sh # deploy helpers
112
+ push_to_kaggle.sh # deploy helpers
113
+ runpod_setup.sh
114
+ src/
115
+ api/ # FastAPI app, schemas, routes, middleware
116
+ conversation/ # memory_manager, gemma_client, phrase_matcher, intent_parser
117
+ data/ # dataset loading + normalization (Adlam, Bambara)
118
+ engine/ # adapter_manager, transcriber, stt_processor, curiosity
119
+ iot/ # intent_parser, voice_responder, sensor_bridge
120
+ llm/ # LLM client wrappers
121
+ memory/ # vocabulary persistence
122
+ optimization/ # ONNX / quantization helpers
123
+ training/ # trainer, callbacks, augmenters
124
+ tts/ # mms_tts, waxal_tts, f5_tts, voice_cloner
125
+ voice/ # speaker_profiles (ECAPA-TDNN + OpenVoice SE)
126
+ tests/ # pytest — api, data pipeline, engine, iot
127
+ ```
128
+
129
+ ---
130
+
131
+ ## How the memory loop works
132
+
133
+ 1. Press **Push-to-Talk** → speak in Bambara, Fula, French, or English.
134
+ 2. **Whisper** transcribes. If the language has a LoRA adapter loaded, `AdapterManager` hot-swaps to it (~50 ms).
135
+ 3. **Qwen** reads the vocabulary it has learned so far (`MemoryManager.get_vocabulary_context()`), then returns a structured JSON reply with `intent ∈ {teaching, question, conversation, error}`.
136
+ 4. If `teaching`: the word pair is appended to `data/vocabulary.jsonl` and async-pushed to `ous-sow/sahel-agri-feedback → vocabulary.jsonl`.
137
+ 5. If `question`: Qwen answers using the remembered vocabulary as source of truth.
138
+ 6. If `conversation`: Qwen replies naturally.
139
+ 7. TTS speaks the reply (Waxal VITS for Bambara, MMS-TTS fallback elsewhere).
140
+
141
+ The last 5 learned words are always visible in the UI.
142
+
143
+ ---
144
+
145
+ ## How the agricultural voice interface works
146
+
147
+ 1. User asks, e.g., *"A bɛ di wa?"* ("Is it OK?") referring to their field.
148
+ 2. `intent_parser.py` (keyword-based) classifies the request: `check_soil` / `check_weather` / `irrigation_status` / `pest_alert` / etc.
149
+ 3. `SensorBridge` calls the configured `SENSOR_API_URL` and returns a typed `SensorData`.
150
+ 4. `voice_responder.py` maps `(Intent, SensorData)` → a short (≤ 6 words/sentence) Bambara or Fula reply + English translation. Alert thresholds are encoded here (`SOIL_MOISTURE_LOW=30`, `TEMP_HIGH=38`, pH bounds).
151
+ 5. TTS speaks the reply.
152
+
153
+ ---
154
+
155
+ ## Environment variables
156
+
157
+ All variables have sensible defaults, so you can boot the Space without any of them — but without `HF_TOKEN` the memory loop cannot push.
158
+
159
+ ### Core
160
+ | Key | Default | Purpose |
161
+ |-----|---------|---------|
162
+ | `HF_TOKEN` | — | HF write token. Required for Hub push and gated models. |
163
+ | `FEEDBACK_REPO_ID` | `ous-sow/sahel-agri-feedback` | Memory-loop target dataset. |
164
+ | `ADAPTER_REPO_ID` | `ous-sow/sahel-agri-adapters` | Published LoRA adapters. |
165
+ | `WHISPER_MODEL_ID` | `openai/whisper-large-v3-turbo` | STT base model. |
166
+ | `LLM_MODEL_ID` | `Qwen/Qwen2.5-7B-Instruct` | LLM via HF Serverless. |
167
+ | `LOG_LEVEL` | `INFO` | Standard Python logging level. |
168
+ | `DEVICE` | `cuda` (FastAPI) | Torch device for inference. |
169
+
170
+ ### Adapters & TTS
171
+ | Key | Default |
172
+ |-----|---------|
173
+ | `BAMBARA_ADAPTER_PATH` | `./adapters/bambara` |
174
+ | `FULA_ADAPTER_PATH` | `./adapters/fula` |
175
+ | `BAMBARA_TTS_REPO` | `ynnov/ekodi-bambara-tts-female` |
176
+ | `FULA_TTS_REPO` | `ous-sow/fula-tts` |
177
+
178
+ ### IoT
179
+ | Key | Default |
180
+ |-----|---------|
181
+ | `SENSOR_API_URL` | *(unset → mock sensor)* |
182
+
183
+ ### Self-Teaching tab (triggers Kaggle training runs)
184
+ | Key | Default |
185
+ |-----|---------|
186
+ | `KAGGLE_USERNAME` | — |
187
+ | `KAGGLE_KEY` | — |
188
+ | `KAGGLE_KERNEL_SLUG` | `ous-sow/sahel-voice-master-trainer` *(override in prod to `oussow/kaggle-master-trainer` — the actual Kaggle owner slug)* |
189
+ | `AUTO_TRAIN_THRESHOLD` | `50` |
190
+
191
+ ---
192
+
193
+ ## Run locally
194
+
195
+ ```bash
196
+ # Gradio production UI
197
+ pip install -r requirements.txt
198
+ python app.py
199
+
200
+ # FastAPI service
201
+ python scripts/run_server.py
202
+
203
+ # Experimental lab UI
204
+ python app_lab.py
205
+ ```
206
+
207
+ System-level dependency: **ffmpeg** (see `packages.txt`).
208
+
209
+ ---
210
+
211
+ ## Training
212
+
213
+ LoRA fine-tuning runs on **Kaggle T4** or **RunPod** — not locally. Pick one entrypoint:
214
+
215
+ | Target | Script | Notebook |
216
+ |--------|--------|----------|
217
+ | Bambara LoRA | `scripts/train_bambara.py` | `notebooks/kaggle_master_trainer/` |
218
+ | Fula LoRA | `scripts/train_fula.py` | `notebooks/kaggle_master_trainer/` |
219
+ | Fula TTS | — | `notebooks/train_fula_tts/` *(planned)* |
220
+
221
+ **Contributor workflow:** edit notebooks locally in `notebooks/<slug>/`, commit with `nbstripout` keeping diffs clean, then `cd notebooks/<slug> && kaggle kernels push` to run on Kaggle GPU. Full walkthrough in `docs/notebook_collaboration.md`.
222
+
223
+ `docs/kaggle_mcp_setup.md` documents the optional Kaggle MCP for Claude Desktop if you'd rather drive Kaggle from an LLM.
224
+
225
+ ---
226
+
227
+ ## Export for edge
228
+
229
+ ```bash
230
+ python scripts/export_onnx.py # merges LoRA into the backbone, exports ONNX
231
+ # then onnx-tf → TFLite for Android
232
+ ```
233
+
234
+ ONNX does not support LoRA hot-swap, so export one file per language. `bitsandbytes` NF4 / 8-bit quantization is available for GPU-constrained deploys but is a training-only dep (not in `requirements.txt`).
235
+
236
+ ---
237
+
238
+ ## Tests
239
+
240
+ ```bash
241
+ pytest tests/
242
+ ```
243
+
244
+ Covers: FastAPI routes, data pipeline, engine (adapter manager + transcriber), IoT (intent parser + voice responder).
245
+
246
+ ---
247
+
248
+ ## Space secrets (HF UI → Settings → Secrets)
249
+
250
+ At minimum:
251
 
 
252
  | Key | Value |
253
  |-----|-------|
254
+ | `HF_TOKEN` | write-scope token |
255
  | `FEEDBACK_REPO_ID` | `ous-sow/sahel-agri-feedback` |
256
+ | `LLM_MODEL_ID` | `Qwen/Qwen2.5-7B-Instruct` (or any HF Serverless-supported model) |
257
+
258
+ ---
259
+
260
+ ## Design constraints (deliberate — do not change without discussion)
261
+
262
+ - **Adapter hot-swap** via PEFT's multi-adapter API — one backbone in VRAM, ~50 MB adapters per language, `set_adapter` ≈ 50 ms.
263
+ - **Qwen "adult-child" JSON contract** — structured `intent`/`reply`/`english`/`teaching_pair` output, parsed out of optional markdown fences.
264
+ - **JSONL + Hub push memory** — no ORM, thread-safe `MemoryManager`, async push so UI never blocks.
265
+ - **≤ 6 words per sentence** in `voice_responder.py` for clean MMS-TTS.
266
+ - **Adlam ↔ Latin dual-script** handling in `adlam.py` + `bam_normalize.py`.
267
+ - **Single-file `app.py`** — intentional for now; do not split without a plan.
268
+
269
+ ---
270
+
271
+ ## License
272
+
273
+ MIT.
notebooks/kaggle_master_trainer/kaggle_master_trainer.ipynb CHANGED
@@ -27,7 +27,16 @@
27
  {
28
  "cell_type": "code",
29
  "execution_count": null,
30
- "id": "1",
 
 
 
 
 
 
 
 
 
31
  "metadata": {},
32
  "outputs": [],
33
  "source": [
@@ -54,7 +63,7 @@
54
  {
55
  "cell_type": "code",
56
  "execution_count": null,
57
- "id": "2",
58
  "metadata": {},
59
  "outputs": [],
60
  "source": [
@@ -67,22 +76,6 @@
67
  " sys.executable, '-m', 'pip', 'install', '-q', 'jiwer==3.0.4',\n",
68
  "])\n",
69
  "\n",
70
- "# datasets >= 4.0 uses torchcodec for audio decoding. Install if missing.\n",
71
- "try:\n",
72
- " import torchcodec # noqa\n",
73
- "except ImportError:\n",
74
- " print('torchcodec not found — installing to match torch ...')\n",
75
- " try:\n",
76
- " subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-q', 'torchcodec'])\n",
77
- " except subprocess.CalledProcessError:\n",
78
- " import torch as _t\n",
79
- " _tv = _t.__version__.split('+')[0]\n",
80
- " _pin = {'2.4': '0.1.*', '2.5': '0.2.*', '2.6': '0.3.*', '2.7': '0.4.*', '2.8': '0.4.*'}.get(_tv[:3])\n",
81
- " if _pin:\n",
82
- " subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-q', f'torchcodec=={_pin}'])\n",
83
- " else:\n",
84
- " print(f'⚠️ Unknown torch {_tv}; install torchcodec manually if audio decoding fails')\n",
85
- "\n",
86
  "import torch\n",
87
  "print(f\"torch : {torch.__version__}\")\n",
88
  "print(f\"CUDA avail : {torch.cuda.is_available()}\")\n",
@@ -98,67 +91,40 @@
98
  {
99
  "cell_type": "code",
100
  "execution_count": null,
101
- "id": "3",
102
  "metadata": {},
103
  "outputs": [],
104
  "source": [
105
  "# ── Cell 3: CONFIGURATION — edit these before each run ───────────────────────\n",
106
  "import os\n",
107
- "from pathlib import Path\n",
108
- "\n",
109
- "# ─── Environment detection (Kaggle / Colab / RunPod / local) ─────────────────\n",
110
- "if Path('/kaggle/working').exists():\n",
111
- " _ENV = 'kaggle'\n",
112
- " WORKING_DIR = '/kaggle/working'\n",
113
- "elif Path('/content').exists() and not Path('/workspace').exists():\n",
114
- " _ENV = 'colab'\n",
115
- " WORKING_DIR = '/content'\n",
116
- "elif Path('/workspace').exists():\n",
117
- " _ENV = 'runpod'\n",
118
- " WORKING_DIR = '/workspace'\n",
119
- "else:\n",
120
- " _ENV = 'local'\n",
121
- " WORKING_DIR = os.environ.get('WORKING_DIR', os.path.expanduser('~/sahel-voice-work'))\n",
122
- " Path(WORKING_DIR).mkdir(parents=True, exist_ok=True)\n",
123
  "\n",
124
  "# ─── Language to train ───────────────────────────────────────────────────────\n",
125
  "# 'bam' = Bambara 'ful' = Fula\n",
126
- "TRAIN_LANG = os.environ.get('TRAIN_LANG', 'ful')\n",
127
  "\n",
128
  "# ─── Model ───────────────────────────────────────────────────────────────────\n",
129
- "# whisper-large-v3-turbo (128 mel bins) matches the Space base model.\n",
130
- "# On T4 (Kaggle, 16 GB) drop to 'openai/whisper-small' — turbo is tight there.\n",
131
- "_DEFAULT_MODEL = 'openai/whisper-small' if _ENV == 'kaggle' else 'openai/whisper-large-v3-turbo'\n",
132
- "WHISPER_MODEL_ID = os.environ.get('WHISPER_MODEL_ID', _DEFAULT_MODEL)\n",
133
  "TARGET_SR = 16_000\n",
134
  "\n",
135
  "# ─── HuggingFace repos ───────────────────────────────────────────────────────\n",
136
- "HF_USERNAME = os.environ.get('HF_USERNAME', 'ous-sow')\n",
137
  "FEEDBACK_REPO_ID = f'{HF_USERNAME}/sahel-agri-feedback'\n",
138
  "ADAPTER_REPO_ID = f'{HF_USERNAME}/sahel-agri-adapters'\n",
139
  "\n",
140
- "# ─── Training hyper-parameters (defaults tuned per environment) ──────────────\n",
141
- "if _ENV == 'runpod':\n",
142
- " # 24 GB RTX 3090 / A40: turbo-v3 fits with batch 8, grad_accum 4 (eff. 32)\n",
143
- " MAX_STEPS = 2_000\n",
144
- " BATCH_SIZE = 8\n",
145
- " GRAD_ACCUM = 4\n",
146
- " MAX_WAXAL_TRAIN = 5_000\n",
147
- "else:\n",
148
- " # T4 (Kaggle free) / local CPU fallback\n",
149
- " MAX_STEPS = 4_000\n",
150
- " BATCH_SIZE = 16\n",
151
- " GRAD_ACCUM = 2\n",
152
- " MAX_WAXAL_TRAIN = 5_000\n",
153
- "\n",
154
  "LEARNING_RATE = 1e-3\n",
155
- "WARMUP_STEPS = max(100, MAX_STEPS // 20)\n",
156
  "SAVE_STEPS = 500\n",
157
- "EVAL_STEPS = 500\n",
158
  "LOGGING_STEPS = 50\n",
 
159
  "CORRECTION_REPEAT= 3 # upsample user corrections Nx for emphasis\n",
160
  "\n",
161
- "# ─── Paths ────────────────────────────────────────────────────────────────────\n",
 
162
  "OUTPUT_DIR = f'{WORKING_DIR}/adapter_{TRAIN_LANG}'\n",
163
  "DATA_DIR = f'{WORKING_DIR}/data'\n",
164
  "AUDIO_DIR = f'{WORKING_DIR}/audio_feedback'\n",
@@ -170,19 +136,17 @@
170
  " 'ful': 'Pular (Labé/Mamou dialects) — Guinean orthography',\n",
171
  "}.get(TRAIN_LANG, '')\n",
172
  "\n",
173
- "print(f'Environment : {_ENV}')\n",
174
- "print(f'Language : {TRAIN_LANG} ({LANG_NAME}) — {LANG_COUNTRY}')\n",
175
- "print(f'Dialect : {LANG_DIALECT}')\n",
176
- "print(f'Model : {WHISPER_MODEL_ID}')\n",
177
- "print(f'Working dir : {WORKING_DIR}')\n",
178
- "print(f'Output : {OUTPUT_DIR}')\n",
179
- "print(f'Max steps : {MAX_STEPS} (batch={BATCH_SIZE}, grad_accum={GRAD_ACCUM}, eff={BATCH_SIZE*GRAD_ACCUM})')\n"
180
  ]
181
  },
182
  {
183
  "cell_type": "code",
184
  "execution_count": null,
185
- "id": "4",
186
  "metadata": {},
187
  "outputs": [],
188
  "source": [
@@ -240,7 +204,7 @@
240
  {
241
  "cell_type": "code",
242
  "execution_count": null,
243
- "id": "5",
244
  "metadata": {},
245
  "outputs": [],
246
  "source": [
@@ -267,27 +231,8 @@
267
  " except Exception:\n",
268
  " pass\n",
269
  "\n",
270
- "# .env file (RunPod / local) - look in common locations\n",
271
- "if not HF_TOKEN:\n",
272
- " for _env_path in ['/workspace/sahel-voice/.env', './.env', '../.env', os.path.expanduser('~/.env')]:\n",
273
- " if os.path.isfile(_env_path):\n",
274
- " with open(_env_path, encoding='utf-8') as _f:\n",
275
- " for _line in _f:\n",
276
- " _line = _line.strip()\n",
277
- " if _line.startswith('HF_TOKEN='):\n",
278
- " _val = _line.split('=', 1)[1].strip()\n",
279
- " if _val and len(_val) >= 2 and _val[0] in ('\"', \"'\") and _val[-1] == _val[0]:\n",
280
- " _val = _val[1:-1]\n",
281
- " HF_TOKEN = _val\n",
282
- " print(f'HF_TOKEN loaded from {_env_path}')\n",
283
- " break\n",
284
- " if HF_TOKEN:\n",
285
- " break\n",
286
- "\n",
287
  "if not HF_TOKEN:\n",
288
  " HF_TOKEN = os.environ.get('HF_TOKEN', '')\n",
289
- " if HF_TOKEN:\n",
290
- " print('HF_TOKEN loaded from environment variable.')\n",
291
  "\n",
292
  "if not HF_TOKEN:\n",
293
  " raise ValueError(\n",
@@ -310,7 +255,7 @@
310
  {
311
  "cell_type": "code",
312
  "execution_count": null,
313
- "id": "6",
314
  "metadata": {},
315
  "outputs": [],
316
  "source": [
@@ -352,7 +297,7 @@
352
  {
353
  "cell_type": "code",
354
  "execution_count": null,
355
- "id": "7",
356
  "metadata": {},
357
  "outputs": [],
358
  "source": [
@@ -423,7 +368,7 @@
423
  {
424
  "cell_type": "code",
425
  "execution_count": null,
426
- "id": "8",
427
  "metadata": {},
428
  "outputs": [],
429
  "source": [
@@ -490,7 +435,7 @@
490
  {
491
  "cell_type": "code",
492
  "execution_count": null,
493
- "id": "9",
494
  "metadata": {},
495
  "outputs": [],
496
  "source": [
@@ -532,7 +477,7 @@
532
  },
533
  {
534
  "cell_type": "markdown",
535
- "id": "10",
536
  "metadata": {},
537
  "source": [
538
  "---\n",
@@ -544,7 +489,7 @@
544
  {
545
  "cell_type": "code",
546
  "execution_count": null,
547
- "id": "11",
548
  "metadata": {},
549
  "outputs": [],
550
  "source": [
@@ -639,7 +584,7 @@
639
  {
640
  "cell_type": "code",
641
  "execution_count": null,
642
- "id": "12",
643
  "metadata": {},
644
  "outputs": [],
645
  "source": [
@@ -713,7 +658,7 @@
713
  {
714
  "cell_type": "code",
715
  "execution_count": null,
716
- "id": "13",
717
  "metadata": {},
718
  "outputs": [],
719
  "source": [
@@ -808,7 +753,7 @@
808
  {
809
  "cell_type": "code",
810
  "execution_count": null,
811
- "id": "14",
812
  "metadata": {},
813
  "outputs": [],
814
  "source": [
@@ -871,7 +816,7 @@
871
  },
872
  {
873
  "cell_type": "markdown",
874
- "id": "15",
875
  "metadata": {},
876
  "source": [
877
  "---\n",
@@ -886,7 +831,7 @@
886
  {
887
  "cell_type": "code",
888
  "execution_count": null,
889
- "id": "16",
890
  "metadata": {},
891
  "outputs": [],
892
  "source": [
@@ -947,7 +892,7 @@
947
  {
948
  "cell_type": "code",
949
  "execution_count": null,
950
- "id": "17",
951
  "metadata": {},
952
  "outputs": [],
953
  "source": [
@@ -1036,7 +981,7 @@
1036
  },
1037
  {
1038
  "cell_type": "markdown",
1039
- "id": "18",
1040
  "metadata": {},
1041
  "source": [
1042
  "---\n",
@@ -1050,7 +995,7 @@
1050
  {
1051
  "cell_type": "code",
1052
  "execution_count": null,
1053
- "id": "19",
1054
  "metadata": {},
1055
  "outputs": [],
1056
  "source": [
@@ -1108,7 +1053,7 @@
1108
  {
1109
  "cell_type": "code",
1110
  "execution_count": null,
1111
- "id": "20",
1112
  "metadata": {},
1113
  "outputs": [],
1114
  "source": [
@@ -1145,7 +1090,7 @@
1145
  },
1146
  {
1147
  "cell_type": "markdown",
1148
- "id": "21",
1149
  "metadata": {},
1150
  "source": [
1151
  "---\n",
@@ -1159,7 +1104,7 @@
1159
  {
1160
  "cell_type": "code",
1161
  "execution_count": null,
1162
- "id": "22",
1163
  "metadata": {},
1164
  "outputs": [],
1165
  "source": [
@@ -1195,7 +1140,7 @@
1195
  },
1196
  {
1197
  "cell_type": "markdown",
1198
- "id": "23",
1199
  "metadata": {},
1200
  "source": [
1201
  "---\n",
@@ -1211,7 +1156,7 @@
1211
  {
1212
  "cell_type": "code",
1213
  "execution_count": null,
1214
- "id": "24",
1215
  "metadata": {},
1216
  "outputs": [],
1217
  "source": [
@@ -1248,7 +1193,7 @@
1248
  {
1249
  "cell_type": "code",
1250
  "execution_count": null,
1251
- "id": "25",
1252
  "metadata": {},
1253
  "outputs": [],
1254
  "source": [
@@ -1292,7 +1237,7 @@
1292
  {
1293
  "cell_type": "code",
1294
  "execution_count": null,
1295
- "id": "26",
1296
  "metadata": {},
1297
  "outputs": [],
1298
  "source": [
@@ -1337,6 +1282,15 @@
1337
  }
1338
  ],
1339
  "metadata": {
 
 
 
 
 
 
 
 
 
1340
  "kernelspec": {
1341
  "display_name": "Python 3",
1342
  "language": "python",
@@ -1348,5 +1302,5 @@
1348
  }
1349
  },
1350
  "nbformat": 4,
1351
- "nbformat_minor": 5
1352
  }
 
27
  {
28
  "cell_type": "code",
29
  "execution_count": null,
30
+ "metadata": {},
31
+ "outputs": [],
32
+ "source": [
33
+ "print(\"test\")"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": null,
39
+ "id": "2",
40
  "metadata": {},
41
  "outputs": [],
42
  "source": [
 
63
  {
64
  "cell_type": "code",
65
  "execution_count": null,
66
+ "id": "3",
67
  "metadata": {},
68
  "outputs": [],
69
  "source": [
 
76
  " sys.executable, '-m', 'pip', 'install', '-q', 'jiwer==3.0.4',\n",
77
  "])\n",
78
  "\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  "import torch\n",
80
  "print(f\"torch : {torch.__version__}\")\n",
81
  "print(f\"CUDA avail : {torch.cuda.is_available()}\")\n",
 
91
  {
92
  "cell_type": "code",
93
  "execution_count": null,
94
+ "id": "4",
95
  "metadata": {},
96
  "outputs": [],
97
  "source": [
98
  "# ── Cell 3: CONFIGURATION — edit these before each run ───────────────────────\n",
99
  "import os\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  "\n",
101
  "# ─── Language to train ───────────────────────────────────────────────────────\n",
102
  "# 'bam' = Bambara 'ful' = Fula\n",
103
+ "TRAIN_LANG = 'ful'\n",
104
  "\n",
105
  "# ─── Model ───────────────────────────────────────────────────────────────────\n",
106
+ "WHISPER_MODEL_ID = 'openai/whisper-large-v3-turbo'\n",
 
 
 
107
  "TARGET_SR = 16_000\n",
108
  "\n",
109
  "# ─── HuggingFace repos ───────────────────────────────────────────────────────\n",
110
+ "HF_USERNAME = 'ous-sow'\n",
111
  "FEEDBACK_REPO_ID = f'{HF_USERNAME}/sahel-agri-feedback'\n",
112
  "ADAPTER_REPO_ID = f'{HF_USERNAME}/sahel-agri-adapters'\n",
113
  "\n",
114
+ "# ─── Training hyper-parameters ───────────────────────────────────────────────\n",
115
+ "MAX_STEPS = 100 # T4 ~45 min; set 8000 for a deeper run\n",
116
+ "BATCH_SIZE = 16\n",
117
+ "GRAD_ACCUM = 2 # effective batch = 32\n",
 
 
 
 
 
 
 
 
 
 
118
  "LEARNING_RATE = 1e-3\n",
119
+ "WARMUP_STEPS = 10\n",
120
  "SAVE_STEPS = 500\n",
121
+ "EVAL_STEPS = 100\n",
122
  "LOGGING_STEPS = 50\n",
123
+ "MAX_WAXAL_TRAIN = 500 # cap WaxalNLP samples (streaming budget)\n",
124
  "CORRECTION_REPEAT= 3 # upsample user corrections Nx for emphasis\n",
125
  "\n",
126
+ "# ─── Paths (Kaggle working dir) ───────────────────────────────────────────────\n",
127
+ "WORKING_DIR = '/kaggle/working'\n",
128
  "OUTPUT_DIR = f'{WORKING_DIR}/adapter_{TRAIN_LANG}'\n",
129
  "DATA_DIR = f'{WORKING_DIR}/data'\n",
130
  "AUDIO_DIR = f'{WORKING_DIR}/audio_feedback'\n",
 
136
  " 'ful': 'Pular (Labé/Mamou dialects) — Guinean orthography',\n",
137
  "}.get(TRAIN_LANG, '')\n",
138
  "\n",
139
+ "print(f'Language : {TRAIN_LANG} ({LANG_NAME}) — {LANG_COUNTRY}')\n",
140
+ "print(f'Dialect : {LANG_DIALECT}')\n",
141
+ "print(f'Model : {WHISPER_MODEL_ID}')\n",
142
+ "print(f'Output : {OUTPUT_DIR}')\n",
143
+ "print(f'Max steps : {MAX_STEPS}')"
 
 
144
  ]
145
  },
146
  {
147
  "cell_type": "code",
148
  "execution_count": null,
149
+ "id": "5",
150
  "metadata": {},
151
  "outputs": [],
152
  "source": [
 
204
  {
205
  "cell_type": "code",
206
  "execution_count": null,
207
+ "id": "6",
208
  "metadata": {},
209
  "outputs": [],
210
  "source": [
 
231
  " except Exception:\n",
232
  " pass\n",
233
  "\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  "if not HF_TOKEN:\n",
235
  " HF_TOKEN = os.environ.get('HF_TOKEN', '')\n",
 
 
236
  "\n",
237
  "if not HF_TOKEN:\n",
238
  " raise ValueError(\n",
 
255
  {
256
  "cell_type": "code",
257
  "execution_count": null,
258
+ "id": "7",
259
  "metadata": {},
260
  "outputs": [],
261
  "source": [
 
297
  {
298
  "cell_type": "code",
299
  "execution_count": null,
300
+ "id": "8",
301
  "metadata": {},
302
  "outputs": [],
303
  "source": [
 
368
  {
369
  "cell_type": "code",
370
  "execution_count": null,
371
+ "id": "9",
372
  "metadata": {},
373
  "outputs": [],
374
  "source": [
 
435
  {
436
  "cell_type": "code",
437
  "execution_count": null,
438
+ "id": "10",
439
  "metadata": {},
440
  "outputs": [],
441
  "source": [
 
477
  },
478
  {
479
  "cell_type": "markdown",
480
+ "id": "11",
481
  "metadata": {},
482
  "source": [
483
  "---\n",
 
489
  {
490
  "cell_type": "code",
491
  "execution_count": null,
492
+ "id": "12",
493
  "metadata": {},
494
  "outputs": [],
495
  "source": [
 
584
  {
585
  "cell_type": "code",
586
  "execution_count": null,
587
+ "id": "13",
588
  "metadata": {},
589
  "outputs": [],
590
  "source": [
 
658
  {
659
  "cell_type": "code",
660
  "execution_count": null,
661
+ "id": "14",
662
  "metadata": {},
663
  "outputs": [],
664
  "source": [
 
753
  {
754
  "cell_type": "code",
755
  "execution_count": null,
756
+ "id": "15",
757
  "metadata": {},
758
  "outputs": [],
759
  "source": [
 
816
  },
817
  {
818
  "cell_type": "markdown",
819
+ "id": "16",
820
  "metadata": {},
821
  "source": [
822
  "---\n",
 
831
  {
832
  "cell_type": "code",
833
  "execution_count": null,
834
+ "id": "17",
835
  "metadata": {},
836
  "outputs": [],
837
  "source": [
 
892
  {
893
  "cell_type": "code",
894
  "execution_count": null,
895
+ "id": "18",
896
  "metadata": {},
897
  "outputs": [],
898
  "source": [
 
981
  },
982
  {
983
  "cell_type": "markdown",
984
+ "id": "19",
985
  "metadata": {},
986
  "source": [
987
  "---\n",
 
995
  {
996
  "cell_type": "code",
997
  "execution_count": null,
998
+ "id": "20",
999
  "metadata": {},
1000
  "outputs": [],
1001
  "source": [
 
1053
  {
1054
  "cell_type": "code",
1055
  "execution_count": null,
1056
+ "id": "21",
1057
  "metadata": {},
1058
  "outputs": [],
1059
  "source": [
 
1090
  },
1091
  {
1092
  "cell_type": "markdown",
1093
+ "id": "22",
1094
  "metadata": {},
1095
  "source": [
1096
  "---\n",
 
1104
  {
1105
  "cell_type": "code",
1106
  "execution_count": null,
1107
+ "id": "23",
1108
  "metadata": {},
1109
  "outputs": [],
1110
  "source": [
 
1140
  },
1141
  {
1142
  "cell_type": "markdown",
1143
+ "id": "24",
1144
  "metadata": {},
1145
  "source": [
1146
  "---\n",
 
1156
  {
1157
  "cell_type": "code",
1158
  "execution_count": null,
1159
+ "id": "25",
1160
  "metadata": {},
1161
  "outputs": [],
1162
  "source": [
 
1193
  {
1194
  "cell_type": "code",
1195
  "execution_count": null,
1196
+ "id": "26",
1197
  "metadata": {},
1198
  "outputs": [],
1199
  "source": [
 
1237
  {
1238
  "cell_type": "code",
1239
  "execution_count": null,
1240
+ "id": "27",
1241
  "metadata": {},
1242
  "outputs": [],
1243
  "source": [
 
1282
  }
1283
  ],
1284
  "metadata": {
1285
+ "kaggle": {
1286
+ "accelerator": "nvidiaTeslaT4",
1287
+ "dataSources": [],
1288
+ "dockerImageVersionId": 31329,
1289
+ "isGpuEnabled": true,
1290
+ "isInternetEnabled": true,
1291
+ "language": "python",
1292
+ "sourceType": "notebook"
1293
+ },
1294
  "kernelspec": {
1295
  "display_name": "Python 3",
1296
  "language": "python",
 
1302
  }
1303
  },
1304
  "nbformat": 4,
1305
+ "nbformat_minor": 4
1306
  }
notebooks/kaggle_master_trainer/kernel-metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
- "id": "ous-sow/sahel-voice-master-trainer",
3
- "title": "Sahel Voice Master Trainer",
4
  "code_file": "kaggle_master_trainer.ipynb",
5
  "language": "python",
6
  "kernel_type": "notebook",
 
1
  {
2
+ "id": "oussow/kaggle-master-trainer",
3
+ "title": "Kaggle Master Trainer",
4
  "code_file": "kaggle_master_trainer.ipynb",
5
  "language": "python",
6
  "kernel_type": "notebook",
notebooks/train_fula_tts/kernel-metadata.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "id": "ous-sow/sahel-voice-fula-tts-trainer",
3
  "title": "Sahel Voice Fula TTS Trainer",
4
  "code_file": "train_fula_tts.ipynb",
5
  "language": "python",
 
1
  {
2
+ "id": "oussow/sahel-voice-fula-tts-trainer",
3
  "title": "Sahel Voice Fula TTS Trainer",
4
  "code_file": "train_fula_tts.ipynb",
5
  "language": "python",