From 3d341d06173e7dd5ec75ae5eb279ea739406f3fa Mon Sep 17 00:00:00 2001 From: MasterPhooey Date: Sun, 12 Jul 2026 12:02:36 -0500 Subject: [PATCH] Release NVIDIA WakeWord Trainer v11 --- README.md | 48 +- cli/calibrate_detector.py | 6 +- cli/wake_word_sample_trainer | 15 +- run.sh | 2 - static/index.html | 1455 ++--------------------------- trainer_server.py | 1692 +--------------------------------- 6 files changed, 108 insertions(+), 3110 deletions(-) diff --git a/README.md b/README.md index 754491d..e70f264 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ taterassistant.com -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. @@ -22,7 +22,7 @@ docker pull ghcr.io/tatertotterson/microwakeword:latest Tagged releases also publish matching immutable image tags: ```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 @@ -30,7 +30,7 @@ Python 3.13 TensorFlow build for `sm_120`: ```bash 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 @@ -51,19 +51,19 @@ docker run -d \ 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` -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. The flags: - `--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. -- `-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: @@ -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. - `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. -- `Firmware` pulls verified prebuilt Tater firmware images from GitHub and flashes supported satellites over OTA. -- Popup consoles show colorized training and firmware logs while long-running jobs are active. +- `Wake Words` lists locally trained JSON/model links for live wake-word switching in Tater. +- 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). -- Verifies downloaded images by size and SHA before upload. -- Auto-detects compatible devices with mDNS when the container is running with host networking. -- Allows manual IP or hostname entry if discovery does not find the device. -- 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. +- Copy the JSON URL into the Tater Native satellite settings to switch wake words live. +- Open the JSON or model links directly for quick inspection. +- The JSON includes the matching model path plus Tater tuning metadata. +- No firmware flashing happens from this trainer app anymore. -> **Tater only:** these native firmware images connect to Tater. They are not Home Assistant or ESPHome satellite firmware. - -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. +Use the main Tater app for satellite firmware updates and USB flashing. --- @@ -236,14 +229,14 @@ Successful runs produce timestamped training output folders such as: /data/output/---/.json ``` -The trainer also syncs firmware-ready artifacts into: +The trainer also syncs Tater-ready wake-word artifacts into: ```text /data/trained_wake_words/.tflite /data/trained_wake_words/.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: @@ -296,7 +289,6 @@ That removes: - cached datasets - training environments - trained models -- downloaded firmware images --- @@ -306,7 +298,7 @@ That removes: - Negative samples are optional but useful for reducing false wakes. - The UI server is `trainer_server.py`. - 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. --- diff --git a/cli/calibrate_detector.py b/cli/calibrate_detector.py index 5cd56da..aad90d9 100644 --- a/cli/calibrate_detector.py +++ b/cli/calibrate_detector.py @@ -18,14 +18,14 @@ from microwakeword.data import FeatureHandler from microwakeword.inference import Model -DEFAULT_WINDOW_SIZES = [3, 4, 5, 6, 7] -DEFAULT_TARGET_FAPH = float(os.environ.get("MWW_CALIBRATION_TARGET_FAPH", "1.0")) +DEFAULT_WINDOW_SIZES = [4, 5, 6, 7] +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_POSITIVE_SKIP_SLICES = int( os.environ.get("MWW_CALIBRATION_POSITIVE_SKIP_SLICES", "25") ) 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")) diff --git a/cli/wake_word_sample_trainer b/cli/wake_word_sample_trainer index c25c290..1719ed1 100644 --- a/cli/wake_word_sample_trainer +++ b/cli/wake_word_sample_trainer @@ -302,11 +302,11 @@ TRAIN_ARGS=( --test_tflite_streaming_quantized 1 --use_weights best_weights mixednet - --pointwise_filters "64,64,64,64" + --pointwise_filters "128,128,128,128" --repeat_in_block "1,1,1,1" --mixconv_kernel_sizes "[5], [7,11], [9,15], [23]" --residual_connection "0,0,0,0" - --first_conv_filters 32 + --first_conv_filters 64 --first_conv_kernel_size 5 --stride 2 ) @@ -386,6 +386,7 @@ fi TRAINING_DONE="false" 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 echo "✅ Training complete (GPU path)." TRAINING_DONE="true" @@ -495,8 +496,9 @@ from pathlib import Path json_path = Path(os.environ["JSON_PATH"]) calibration_path = Path(os.environ.get("CALIBRATION_PATH", "")) language = (os.environ.get("LANGUAGE", "en") or "en").strip().lower() -probability_cutoff = 0.97 -sliding_window_size = 5 +probability_cutoff = 0.85 +sliding_window_size = 4 +strict_min_close_miss_threshold = 0.68 calibration = {} 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))) 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 {} -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 = { "type": "micro", diff --git a/run.sh b/run.sh index b65d833..ea9f41f 100644 --- a/run.sh +++ b/run.sh @@ -30,7 +30,6 @@ install_ui_deps() { "fastapi==${FASTAPI_VERSION}" \ "uvicorn[standard]==${UVICORN_VERSION}" \ "python-multipart==${PY_MULTIPART_VERSION}" \ - "zeroconf>=0.132.2" \ "silero-vad>=5.0.0" \ "numpy>=1.24.0" } @@ -79,7 +78,6 @@ exact = { minimum = { "silero-vad": "5.0.0", "numpy": "1.24.0", - "zeroconf": "0.132.2", } present = ("torch",) diff --git a/static/index.html b/static/index.html index 634c0aa..d95bf0a 100644 --- a/static/index.html +++ b/static/index.html @@ -348,6 +348,13 @@ font-size: 13px; } + .runtimeWakeWordActions { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + } + .runtimeWakeWordLinks { display: grid; gap: 10px; @@ -370,6 +377,12 @@ margin-bottom: 4px; } + .runtimeWakeWordUrls { + display: grid; + gap: 3px; + margin-top: 6px; + } + .runtimeWakeWordUrl { display: block; overflow: hidden; @@ -968,157 +981,6 @@ margin-top: 2px; } - .firmwareLogModal { - position: fixed; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - padding: 20px; - background: rgba(0, 0, 0, 0.64); - opacity: 0; - visibility: hidden; - pointer-events: none; - z-index: 12000; - } - - .firmwareLogModal.active { - visibility: visible; - pointer-events: auto; - animation: firmwareLogBackdropIn 280ms ease-out both; - } - - .firmwareLogDialog { - width: min(1320px, 98vw); - height: min(94vh, 1040px); - display: flex; - flex-direction: column; - gap: 12px; - padding: 18px; - border-radius: 22px; - border: 1px solid rgba(255,255,255,0.12); - background: - linear-gradient(180deg, rgba(17, 20, 28, 0.88), rgba(8, 10, 16, 0.96)), - rgba(8, 10, 16, 0.88); - box-shadow: 0 28px 84px rgba(0,0,0,0.58); - backdrop-filter: blur(18px) saturate(1.12); - overflow: hidden; - opacity: 0; - transform: translateY(14px) scale(0.97); - } - - .firmwareLogModal.active .firmwareLogDialog { - animation: firmwareLogDialogIn 300ms cubic-bezier(0.2, 0.8, 0.2, 1) both; - } - - .firmwareLogHeader { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 16px; - } - - .firmwareLogTitle { - margin: 0; - font-size: 18px; - } - - .firmwareLogMeta { - margin: 6px 0 0; - color: var(--muted); - font-size: 13px; - } - - .firmwareLogStatus { - color: #b7c7e6; - font-size: 13px; - } - - .firmwareLogConsole { - flex: 1 1 auto; - min-height: 0; - overflow: auto; - border: 1px solid rgba(255,138,42,0.22); - border-radius: 16px; - padding: 12px 14px; - background: - linear-gradient(180deg, rgba(15, 18, 25, 0.98), rgba(10, 12, 18, 0.98)), - radial-gradient(circle at top, rgba(255,138,42,0.08), transparent 40%); - box-shadow: - inset 0 1px 0 rgba(255,255,255,0.05), - inset 0 -1px 0 rgba(0,0,0,0.35); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 13px; - line-height: 1.45; - color: #dbe8ff; - } - - .firmwareLogLine { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 10px; - align-items: start; - padding: 2px 0; - } - - .firmwareLogLevel { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 58px; - border-radius: 999px; - padding: 2px 8px; - font-size: 11px; - font-weight: 800; - letter-spacing: 0.04em; - border: 1px solid rgba(255,255,255,0.12); - white-space: nowrap; - } - - .firmwareLogMessage { - white-space: pre-wrap; - word-break: break-word; - } - - .firmwareLogLine.tone-info .firmwareLogLevel { - background: rgba(70, 120, 255, 0.16); - border-color: rgba(108, 152, 255, 0.28); - color: #bdd3ff; - } - - .firmwareLogLine.tone-warn .firmwareLogLevel { - background: rgba(245, 167, 36, 0.14); - border-color: rgba(245, 167, 36, 0.32); - color: #ffd696; - } - - .firmwareLogLine.tone-error .firmwareLogLevel { - background: rgba(226, 76, 76, 0.15); - border-color: rgba(255, 111, 111, 0.34); - color: #ffc1c1; - } - - .firmwareLogLine.tone-debug .firmwareLogLevel { - background: rgba(92, 214, 178, 0.14); - border-color: rgba(92, 214, 178, 0.28); - color: #bffff0; - } - - .firmwareLogEmpty { - color: var(--muted); - } - - @keyframes firmwareLogBackdropIn { - from { opacity: 0; } - to { opacity: 1; } - } - - @keyframes firmwareLogDialogIn { - from { opacity: 0; transform: translateY(14px) scale(0.97); } - 62% { transform: translateY(-3px) scale(1.01); } - to { opacity: 1; transform: translateY(0) scale(1); } - } - .consoleMuted { color: #93a0b5; } .consoleText { color: #d8e2f1; } .consoleCmd { color: #89d4ff; } @@ -1263,22 +1125,6 @@ height: min(82vh, 760px); padding: 14px; } - .firmwareLogModal { - padding: 12px; - } - .firmwareLogDialog { - width: 100%; - height: 88vh; - padding: 14px; - } - .firmwareLogHeader { - flex-direction: column; - align-items: stretch; - } - .firmwareLogLine { - grid-template-columns: 1fr; - gap: 4px; - } .consoleHeader { flex-direction: column; align-items: stretch; @@ -1311,14 +1157,14 @@

microWakeWord Trainer Studio

-

Train wake words, review captured clips, and flash Tater Native firmware from one local workspace.

+

Train wake words, review captured clips, and publish live model URLs for Tater Native satellites.

- +
@@ -1552,22 +1398,22 @@
-
Firmware Studio
-

Prebuilt Tater Firmware Flasher

-

Flash only for firmware updates. If this is a new satellite or it is not already on Tater Native Firmware v1, do one USB flash first, then use this tab for fast OTA updates.

-
- 1 Pick firmware - 2 Select target - 3 Flash OTA or USB +
Wake Word Catalog
+

Trained Wake Words

+

Every completed training run publishes a local JSON package and model file. Copy the JSON URL into the native satellite settings in Tater to switch wake words live.

+
+ 1 Train model + 2 Copy JSON URL + 3 Paste in Tater
- Flasher idle + Catalog idle
- Tater only - These native firmware images connect to Tater. They are not Home Assistant or ESPHome satellite firmware. + Tater Native + These generated wake-word URLs are for Tater Native satellites and are loaded from the satellite settings in Tater.
@@ -1576,89 +1422,19 @@ v1
Live Model Switching
-

Tater Native satellites can swap wake words live

-

No reflash is needed after training. Copy a trained wake word JSON URL below and paste it into the native satellite settings in Tater.

+

Copy trained wake-word URLs

+

The JSON file points Tater and the satellite at the matching model plus trainer tuning values.

- No reflash needed +
+ No reflash needed + +
- -
-
-
-
- 1 -
-

Firmware Image

-

Choose a prebuilt Tater firmware image from the Tater Native Firmware releases.

-
-
-
- -
- -
-
-
- 2 -
-

Target Device

-

Auto-detect a device or enter the OTA target manually.

-
-
- Not scanned -
-
- -
-
- - - -
-
-
- - - -
-
- 3 -
-

Flash Firmware

-

The console shows download, verification, OTA upload, USB flash, and reboot progress.

-
-
-
- - - - - -
-
Use Browser USB Flash for new satellites, recovery, or devices not already running Tater Native Firmware v1. After USB flash, join the satellite setup hotspot and finish Wi-Fi, Tater server, and pairing in the device setup page. Chrome or Edge must be used on localhost or HTTPS.
-
@@ -1677,22 +1453,6 @@ - -