mirror of
https://github.com/TaterTotterson/microWakeWord-Trainer-Nvidia-Docker.git
synced 2026-08-12 07:55:33 -06:00
Release NVIDIA WakeWord Trainer v11
This commit is contained in:
48
README.md
48
README.md
@@ -7,7 +7,7 @@
|
|||||||
<a href="https://taterassistant.com">taterassistant.com</a>
|
<a href="https://taterassistant.com">taterassistant.com</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
Train custom microWakeWord models in Docker with NVIDIA/CUDA acceleration, generated Piper samples, device-captured samples, reviewed false-wake negatives, live training logs, and prebuilt Tater firmware flashing.
|
Train custom microWakeWord models in Docker with NVIDIA/CUDA acceleration, generated Piper samples, device-captured samples, reviewed false-wake negatives, live training logs, and local wake-word links for Tater Native satellites.
|
||||||
|
|
||||||
Real samples come from device-captured wake audio, close misses, or manual uploads. Every saved sample is normalized to `16 kHz / mono / 16-bit PCM WAV` before training.
|
Real samples come from device-captured wake audio, close misses, or manual uploads. Every saved sample is normalized to `16 kHz / mono / 16-bit PCM WAV` before training.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ docker pull ghcr.io/tatertotterson/microwakeword:latest
|
|||||||
Tagged releases also publish matching immutable image tags:
|
Tagged releases also publish matching immutable image tags:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ghcr.io/tatertotterson/microwakeword:v10
|
docker pull ghcr.io/tatertotterson/microwakeword:v11
|
||||||
```
|
```
|
||||||
|
|
||||||
RTX 50-series / Blackwell GPUs use a separate image with CUDA 12.8 and a
|
RTX 50-series / Blackwell GPUs use a separate image with CUDA 12.8 and a
|
||||||
@@ -30,7 +30,7 @@ Python 3.13 TensorFlow build for `sm_120`:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ghcr.io/tatertotterson/microwakeword:blackwell
|
docker pull ghcr.io/tatertotterson/microwakeword:blackwell
|
||||||
docker pull ghcr.io/tatertotterson/microwakeword:v10-blackwell
|
docker pull ghcr.io/tatertotterson/microwakeword:v11-blackwell
|
||||||
```
|
```
|
||||||
|
|
||||||
Use the Blackwell image only for RTX 50-series cards. It includes the
|
Use the Blackwell image only for RTX 50-series cards. It includes the
|
||||||
@@ -51,19 +51,19 @@ docker run -d \
|
|||||||
ghcr.io/tatertotterson/microwakeword:latest
|
ghcr.io/tatertotterson/microwakeword:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Use a version tag such as `ghcr.io/tatertotterson/microwakeword:v10` when you want to pin a known release instead of tracking `latest`.
|
Use a version tag such as `ghcr.io/tatertotterson/microwakeword:v11` when you want to pin a known release instead of tracking `latest`.
|
||||||
For RTX 50-series cards, use `ghcr.io/tatertotterson/microwakeword:blackwell`
|
For RTX 50-series cards, use `ghcr.io/tatertotterson/microwakeword:blackwell`
|
||||||
or a pinned tag such as `ghcr.io/tatertotterson/microwakeword:v10-blackwell`
|
or a pinned tag such as `ghcr.io/tatertotterson/microwakeword:v11-blackwell`
|
||||||
in the same `docker run` command.
|
in the same `docker run` command.
|
||||||
|
|
||||||
The flags:
|
The flags:
|
||||||
|
|
||||||
- `--gpus all` enables GPU acceleration.
|
- `--gpus all` enables GPU acceleration.
|
||||||
- `--network host` lets the container receive mDNS/zeroconf traffic for device auto-detect.
|
- `--network host` exposes the trainer server directly so satellites can send captured audio and load trained wake-word files.
|
||||||
- `-e REC_PORT=8789` sets the trainer web UI and captured-audio port. Change this value if `8789` is already in use.
|
- `-e REC_PORT=8789` sets the trainer web UI and captured-audio port. Change this value if `8789` is already in use.
|
||||||
- `-v $(pwd):/data` persists models, downloaded voices, datasets, samples, and firmware caches.
|
- `-v $(pwd):/data` persists models, downloaded voices, datasets, samples, and generated wake-word artifacts.
|
||||||
|
|
||||||
Host networking is recommended for the Firmware tab's mDNS device discovery. Manual IP flashing and captured-audio uploads can still work without host networking if the trainer port is reachable, but auto-detect may not see devices from Docker bridge networking.
|
If you do not use host networking, publish the trainer port and make sure satellites can reach it from your LAN.
|
||||||
|
|
||||||
Open:
|
Open:
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ If you change `REC_PORT`, open that port instead and use the same port in the sa
|
|||||||
- `Trainer` starts a wake-word session, shows positive/negative sample counts, and launches training.
|
- `Trainer` starts a wake-word session, shows positive/negative sample counts, and launches training.
|
||||||
- `Captured Audio` reviews clips sent by Tater Native or ESPHome sats, including wake hits, close misses, and false wakes.
|
- `Captured Audio` reviews clips sent by Tater Native or ESPHome sats, including wake hits, close misses, and false wakes.
|
||||||
- `Samples` plays, removes, clears, and manually imports personal or negative samples.
|
- `Samples` plays, removes, clears, and manually imports personal or negative samples.
|
||||||
- `Firmware` pulls verified prebuilt Tater firmware images from GitHub and flashes supported satellites over OTA.
|
- `Wake Words` lists locally trained JSON/model links for live wake-word switching in Tater.
|
||||||
- Popup consoles show colorized training and firmware logs while long-running jobs are active.
|
- Popup consoles show colorized training logs while long-running jobs are active.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -207,23 +207,16 @@ After those assets are prepared, later runs reuse the local copies unless the mo
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Firmware Flashing
|
## Trained Wake Words
|
||||||
|
|
||||||
The `Firmware` tab flashes prebuilt Tater firmware for supported satellites.
|
The `Wake Words` tab lists locally trained wake-word packages from `/data/trained_wake_words/`.
|
||||||
|
|
||||||
- Downloads the latest prebuilt firmware manifest plus OTA and USB factory images from [`TaterTotterson/Tater-Native-Firmware`](https://github.com/TaterTotterson/Tater-Native-Firmware).
|
- Copy the JSON URL into the Tater Native satellite settings to switch wake words live.
|
||||||
- Verifies downloaded images by size and SHA before upload.
|
- Open the JSON or model links directly for quick inspection.
|
||||||
- Auto-detects compatible devices with mDNS when the container is running with host networking.
|
- The JSON includes the matching model path plus Tater tuning metadata.
|
||||||
- Allows manual IP or hostname entry if discovery does not find the device.
|
- No firmware flashing happens from this trainer app anymore.
|
||||||
- Saves the selected OTA target for each firmware family.
|
|
||||||
- Flashes the prebuilt factory image over Browser USB for first installs or recovery when opened in Chrome or Edge.
|
|
||||||
- Leaves Wi-Fi, Tater server, and pairing setup to the satellite setup portal after USB flash.
|
|
||||||
- Lists locally trained wake words from `/data/trained_wake_words/` for live model switching.
|
|
||||||
- Streams download, verification, and OTA upload progress in a colorized firmware console.
|
|
||||||
|
|
||||||
> **Tater only:** these native firmware images connect to Tater. They are not Home Assistant or ESPHome satellite firmware.
|
Use the main Tater app for satellite firmware updates and USB flashing.
|
||||||
|
|
||||||
You usually only flash for firmware updates. New satellites, or devices not already running Tater Native Firmware `v1`, need one USB flash first before OTA updates and live wake-word switching are available.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -236,14 +229,14 @@ Successful runs produce timestamped training output folders such as:
|
|||||||
/data/output/<timestamp>-<wake_word>-<samples>-<steps>/<wake_word>.json
|
/data/output/<timestamp>-<wake_word>-<samples>-<steps>/<wake_word>.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The trainer also syncs firmware-ready artifacts into:
|
The trainer also syncs Tater-ready wake-word artifacts into:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/data/trained_wake_words/<wake_word>.tflite
|
/data/trained_wake_words/<wake_word>.tflite
|
||||||
/data/trained_wake_words/<wake_word>.json
|
/data/trained_wake_words/<wake_word>.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The firmware tab uses `/data/trained_wake_words/` to populate the wake-word dropdown.
|
The `Wake Words` tab uses `/data/trained_wake_words/` to populate the local wake-word links.
|
||||||
|
|
||||||
The JSON keeps the standard microWakeWord fields for compatibility:
|
The JSON keeps the standard microWakeWord fields for compatibility:
|
||||||
|
|
||||||
@@ -296,7 +289,6 @@ That removes:
|
|||||||
- cached datasets
|
- cached datasets
|
||||||
- training environments
|
- training environments
|
||||||
- trained models
|
- trained models
|
||||||
- downloaded firmware images
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -306,7 +298,7 @@ That removes:
|
|||||||
- Negative samples are optional but useful for reducing false wakes.
|
- Negative samples are optional but useful for reducing false wakes.
|
||||||
- The UI server is `trainer_server.py`.
|
- The UI server is `trainer_server.py`.
|
||||||
- The launcher is `run.sh`.
|
- The launcher is `run.sh`.
|
||||||
- Firmware capture settings live in Tater for Tater Native satellites, and on device entities for older ESPHome satellites.
|
- Trainer capture settings live in Tater for Tater Native satellites, and on device entities for older ESPHome satellites.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ from microwakeword.data import FeatureHandler
|
|||||||
from microwakeword.inference import Model
|
from microwakeword.inference import Model
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_WINDOW_SIZES = [3, 4, 5, 6, 7]
|
DEFAULT_WINDOW_SIZES = [4, 5, 6, 7]
|
||||||
DEFAULT_TARGET_FAPH = float(os.environ.get("MWW_CALIBRATION_TARGET_FAPH", "1.0"))
|
DEFAULT_TARGET_FAPH = float(os.environ.get("MWW_CALIBRATION_TARGET_FAPH", "0.25"))
|
||||||
DEFAULT_COOLDOWN_SLICES = int(os.environ.get("MWW_CALIBRATION_COOLDOWN_SLICES", "25"))
|
DEFAULT_COOLDOWN_SLICES = int(os.environ.get("MWW_CALIBRATION_COOLDOWN_SLICES", "25"))
|
||||||
DEFAULT_POSITIVE_SKIP_SLICES = int(
|
DEFAULT_POSITIVE_SKIP_SLICES = int(
|
||||||
os.environ.get("MWW_CALIBRATION_POSITIVE_SKIP_SLICES", "25")
|
os.environ.get("MWW_CALIBRATION_POSITIVE_SKIP_SLICES", "25")
|
||||||
)
|
)
|
||||||
DEFAULT_CUTOFF_STEP = float(os.environ.get("MWW_CALIBRATION_CUTOFF_STEP", "0.01"))
|
DEFAULT_CUTOFF_STEP = float(os.environ.get("MWW_CALIBRATION_CUTOFF_STEP", "0.01"))
|
||||||
DEFAULT_CUTOFF_MIN = float(os.environ.get("MWW_CALIBRATION_CUTOFF_MIN", "0.00"))
|
DEFAULT_CUTOFF_MIN = float(os.environ.get("MWW_CALIBRATION_CUTOFF_MIN", "0.85"))
|
||||||
DEFAULT_CUTOFF_MAX = float(os.environ.get("MWW_CALIBRATION_CUTOFF_MAX", "1.00"))
|
DEFAULT_CUTOFF_MAX = float(os.environ.get("MWW_CALIBRATION_CUTOFF_MAX", "1.00"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -302,11 +302,11 @@ TRAIN_ARGS=(
|
|||||||
--test_tflite_streaming_quantized 1
|
--test_tflite_streaming_quantized 1
|
||||||
--use_weights best_weights
|
--use_weights best_weights
|
||||||
mixednet
|
mixednet
|
||||||
--pointwise_filters "64,64,64,64"
|
--pointwise_filters "128,128,128,128"
|
||||||
--repeat_in_block "1,1,1,1"
|
--repeat_in_block "1,1,1,1"
|
||||||
--mixconv_kernel_sizes "[5], [7,11], [9,15], [23]"
|
--mixconv_kernel_sizes "[5], [7,11], [9,15], [23]"
|
||||||
--residual_connection "0,0,0,0"
|
--residual_connection "0,0,0,0"
|
||||||
--first_conv_filters 32
|
--first_conv_filters 64
|
||||||
--first_conv_kernel_size 5
|
--first_conv_kernel_size 5
|
||||||
--stride 2
|
--stride 2
|
||||||
)
|
)
|
||||||
@@ -386,6 +386,7 @@ fi
|
|||||||
TRAINING_DONE="false"
|
TRAINING_DONE="false"
|
||||||
|
|
||||||
echo "🏋️ Starting model training and TFLite export (this is the longest stage)…"
|
echo "🏋️ Starting model training and TFLite export (this is the longest stage)…"
|
||||||
|
echo "🧠 Model quality: high_accuracy_plus"
|
||||||
if run_attempt "Attempt 1/3: GPU training (default runtime profile)" ; then
|
if run_attempt "Attempt 1/3: GPU training (default runtime profile)" ; then
|
||||||
echo "✅ Training complete (GPU path)."
|
echo "✅ Training complete (GPU path)."
|
||||||
TRAINING_DONE="true"
|
TRAINING_DONE="true"
|
||||||
@@ -495,8 +496,9 @@ from pathlib import Path
|
|||||||
json_path = Path(os.environ["JSON_PATH"])
|
json_path = Path(os.environ["JSON_PATH"])
|
||||||
calibration_path = Path(os.environ.get("CALIBRATION_PATH", ""))
|
calibration_path = Path(os.environ.get("CALIBRATION_PATH", ""))
|
||||||
language = (os.environ.get("LANGUAGE", "en") or "en").strip().lower()
|
language = (os.environ.get("LANGUAGE", "en") or "en").strip().lower()
|
||||||
probability_cutoff = 0.97
|
probability_cutoff = 0.85
|
||||||
sliding_window_size = 5
|
sliding_window_size = 4
|
||||||
|
strict_min_close_miss_threshold = 0.68
|
||||||
calibration = {}
|
calibration = {}
|
||||||
|
|
||||||
if calibration_path.exists():
|
if calibration_path.exists():
|
||||||
@@ -515,7 +517,10 @@ probability_cutoff = round(probability_cutoff, 3)
|
|||||||
sliding_window_size = max(1, min(10, int(sliding_window_size)))
|
sliding_window_size = max(1, min(10, int(sliding_window_size)))
|
||||||
selected_metrics = calibration.get("selected_metrics") if isinstance(calibration.get("selected_metrics"), dict) else {}
|
selected_metrics = calibration.get("selected_metrics") if isinstance(calibration.get("selected_metrics"), dict) else {}
|
||||||
evaluation = calibration.get("evaluation") if isinstance(calibration.get("evaluation"), dict) else {}
|
evaluation = calibration.get("evaluation") if isinstance(calibration.get("evaluation"), dict) else {}
|
||||||
close_miss_threshold = max(0.01, min(0.99, round(max(0.01, probability_cutoff - 0.19), 3)))
|
close_miss_threshold = max(
|
||||||
|
0.01,
|
||||||
|
min(0.99, round(max(strict_min_close_miss_threshold, probability_cutoff - 0.17), 3)),
|
||||||
|
)
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
"type": "micro",
|
"type": "micro",
|
||||||
|
|||||||
2
run.sh
2
run.sh
@@ -30,7 +30,6 @@ install_ui_deps() {
|
|||||||
"fastapi==${FASTAPI_VERSION}" \
|
"fastapi==${FASTAPI_VERSION}" \
|
||||||
"uvicorn[standard]==${UVICORN_VERSION}" \
|
"uvicorn[standard]==${UVICORN_VERSION}" \
|
||||||
"python-multipart==${PY_MULTIPART_VERSION}" \
|
"python-multipart==${PY_MULTIPART_VERSION}" \
|
||||||
"zeroconf>=0.132.2" \
|
|
||||||
"silero-vad>=5.0.0" \
|
"silero-vad>=5.0.0" \
|
||||||
"numpy>=1.24.0"
|
"numpy>=1.24.0"
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,6 @@ exact = {
|
|||||||
minimum = {
|
minimum = {
|
||||||
"silero-vad": "5.0.0",
|
"silero-vad": "5.0.0",
|
||||||
"numpy": "1.24.0",
|
"numpy": "1.24.0",
|
||||||
"zeroconf": "0.132.2",
|
|
||||||
}
|
}
|
||||||
present = ("torch",)
|
present = ("torch",)
|
||||||
|
|
||||||
|
|||||||
1455
static/index.html
1455
static/index.html
File diff suppressed because it is too large
Load Diff
1692
trainer_server.py
1692
trainer_server.py
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user