voice: recommend Canary for the final words, at a third of Parakeet large's size

This commit is contained in:
Seb3thehacker committed 2026-10-13 15:15:41 -07:00
1 parent 9100dac290
commit e13b0618db
4 files changed
+10 -8

No files matched your search

@@ -67,7 +67,7 @@ object SpeechEngine {
private val worker = Executors.newSingleThreadExecutor { Thread(it, "gearslip-speech") }
/**
* Parakeet large takes seconds to load. On [worker], the first press after connecting
* The final model takes seconds to load. On [worker], the first press after connecting
* waited behind it before listening at all; here it loads while the driver talks.
*/
private val finalWorker = Executors.newSingleThreadExecutor { Thread(it, "gearslip-speech-final") }
@@ -80,7 +80,7 @@ object SpeechEngine {
@Volatile private var loadedFinal: SpeechModels.Model? = null
/**
* Parakeet large holds most of the ~900 MB the two models take, so it goes after a few
* The final model holds most of the memory the two models take, so it goes after a few
* minutes without a command. Kroko stays: it's small, and keeps listening instant.
*/
private val idleUnload = Runnable { unloadFinal("after ${IDLE_UNLOAD_MS / 60_000} minutes without use") }
@@ -51,12 +51,12 @@ object SpeechModels {
plainFiles = false,
),
CANARY(
Role.FINAL, "canary-180m-en", "Canary", "In between: better than Parakeet small, quicker than large. NVIDIA NeMo, CC BY 4.0", 198,
Role.FINAL, "canary-180m-en", "Canary", "Recommended. Quick, and hears names and places well. NVIDIA NeMo, CC BY 4.0", 198,
"sherpa-onnx-nemo-canary-180m-flash-en-es-de-fr-int8",
"encoder.int8.onnx", "decoder.int8.onnx", "", modelType = "canary",
),
PARAKEET_LARGE(
Role.FINAL, "parakeet-0.6b-en", "Parakeet large", "Recommended. Hears names and places best. NVIDIA NeMo, CC BY 4.0", 631,
Role.FINAL, "parakeet-0.6b-en", "Parakeet large", "Hears names best, but slower and three times the size. NVIDIA NeMo, CC BY 4.0", 631,
"sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8",
"encoder.int8.onnx", "decoder.int8.onnx", "joiner.int8.onnx", modelType = "nemo_transducer",
),
@@ -82,7 +82,7 @@ object SpeechModels {
private val states = Model.entries.associateWith { MutableStateFlow<State>(State.Missing) }
private val _selected = MutableStateFlow(Model.KROKO)
val selected: StateFlow<Model> = _selected.asStateFlow()
private val _final = MutableStateFlow<Model?>(Model.PARAKEET_LARGE)
private val _final = MutableStateFlow<Model?>(Model.CANARY)
/** The model that rereads the whole phrase at the end; null to keep the live model's words. */
val final: StateFlow<Model?> = _final.asStateFlow()
@@ -91,7 +91,9 @@ object SpeechModels {
fun init(context: Context) {
val id = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(KEY_MODEL, null)
_selected.value = Model.entries.firstOrNull { it.id == id && it.role == Role.LIVE } ?: Model.KROKO
val finalId = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(KEY_FINAL, Model.PARAKEET_LARGE.id)
// Canary is the default. A driver who never chose but already has Parakeet large keeps it.
val finalId = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(KEY_FINAL, null)
?: if (isReady(context, Model.PARAKEET_LARGE) && !isReady(context, Model.CANARY)) Model.PARAKEET_LARGE.id else Model.CANARY.id
_final.value = Model.entries.firstOrNull { it.id == finalId && it.role == Role.FINAL }
Model.entries.forEach { m -> if (isReady(context, m)) states.getValue(m).value = State.Ready }
// Models an older build offered and this one doesn't: hundreds of MB nobody can use.
@@ -402,7 +402,7 @@ private fun BadgeStep(
private fun VoiceStep(c: IntroColors, cq: (Float) -> Dp, cqSp: (Float) -> TextUnit, key: IntroStep) {
val context = LocalContext.current
remember { SpeechModels.init(context) }
val needed = listOf(SpeechModels.Model.KROKO, SpeechModels.Model.PARAKEET_LARGE)
val needed = listOf(SpeechModels.Model.KROKO, SpeechModels.Model.CANARY)
val states = needed.map { SpeechModels.state(it).collectAsState().value }
val ready = states.all { it == SpeechModels.State.Ready }
val label = when {
@@ -38,7 +38,7 @@ internal fun VoiceModelSection() {
SettingsSection(title = "Voice: live words") {
Text(
"The voice assistant turns speech into text on this phone, and nothing you say leaves it. " +
"It needs two models, about 700 MB together, downloaded once. Use Wi-Fi if you can. " +
"It needs two models, about 270 MB together, downloaded once. " +
"This one writes your words as you speak.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,