Skip to content

docs: match speech-to-text docs to the validated scope - #639

Merged
leehack merged 6 commits into
mainfrom
docs/325-speech-validated-scope
Sep 24, 2026
Merged

leehack merged 6 commits into
mainfrom
docs/325-speech-validated-scope

Conversation

@leehack

@leehack leehack commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Part of #325 (gate 5: docs, examples and the support matrix match the validated scope).

Docs only. Runtime behaviour is unchanged. Outside docs: dartdoc text in speech_platform_stub.dart and the validation package's speech_runner.dart, the covers string of one matrix row, and the edge_long_boundary rationale string that speech-results.json records.

  • Speech-to-text guide
    • Fix the LiteRT-LM snippet: StateError(capabilities.unsupportedReason) passed a String? as a String. Also print completion.state so the snippet has no unused local.
    • New Validated behavior section. It covers what validation-speech-stt runs and the required outcome of each edge fixture: silence gives the typed empty-transcript error; a truncated RIFF gives an inexact transcript or LlamaAudioFormatException; 44.1 kHz stereo gives the reference; 33 s gives the reference three times. It also states the 500 ms cancel and 1.10x memory budgets, what each one measures, and where the pack has passed.
    • Known limits: WAV only, at most 33 s. On native, a long input can fill the context and complete truncated with no error (#636). Names the native targets and backends with no Qwen3-ASR run, and cites the earlier device smokes (#328, #462). It drops the "chat-app/manual smoke on Metal" line, which the Metal pack runs replace.
    • MP3/FLAC: accepted, but only WAV is validated with a real model. Native tests check only the accepted-format set.
  • Support matrix: native WAV/MP3/FLAC now says real-model validation covers WAV only.
  • speech_platform_stub.dart: dartdoc "Encoded formats validated" becomes "Encoded formats accepted".
  • Chat app README and docs page:
    • The five-minute recording limit now carries the 33 s validated scope and what #636 measured on native macOS arm64 CPU. The incidental "five-minute" before Stop & transcribe is dropped.
    • Web is added to the transcription feature line.
    • The README's orphaned fragment gets back its opening, lost in 53f53be.
  • Changelogs (CHANGELOG.md, recent-releases.md): the #325 edge-fixture entry no longer says the LiteRT-ASR and TTS packs "keep their existing check count". A #594 entry follows it (8 to 15 lifecycle checks).
  • Maintainer docs: doc/cross_platform_validation.md and the validation-speech row in test_matrix.dart now name the edge fixtures, and the row also names the bounds.
  • Fixes after independent review of 35074e592 (2bd6c969e, e6849aa81):
    • Drop the false 30-second chunk claim from the guide's 33 s row, the #325 changelog bullet in both changelogs, the validation speech README, and the edge_long_boundary rationale (now "Audio over 30 seconds must be transcribed in full."). The fixture itself is unchanged.
    • Scope the guide's "no run covers" list to Qwen3-ASR: LiteRT-LM Moonshine ASR passed on Windows x64 in #332.
    • README and chat-app.md now state what #636 measured (297 s, native macOS arm64 CPU, 4,096-token context); the guide's truncation sentence is qualified "On native".
    • The guide and the speechWordErrorRate dartdoc now say exactly what the WER normalizes: lowercase, .,!?:;"—– replaced with spaces, split on whitespace.
  • Update to main at e5cedd250 (merge b5d743615, then 5022c0b53):
    • The merge brings in #637 (budget pins) and #638 (typed projector-load errors), with no conflicts. The merged guide and changelogs contradict neither.
    • 5022c0b53: the guide now cites the Linux x64 CPU runs at native v0.4.1-1 instead of the earlier native v0.4.1 run, and rewraps two long lines.

Production-readiness scope

  • User-facing scope: docs state the validated STT scope exactly. No API or behaviour change.
  • Supported platforms/paths: docs only.
  • Unsupported or intentionally unavailable paths: unchanged; the docs now name them. No Qwen3-ASR run covers:
    • Android x64, Linux arm64, Windows arm64/x64, macOS x86_64 or a physical iPhone;
    • the Vulkan, CUDA, HIP, OpenCL or BLAS backends;
    • MP3 or FLAC with a real model;
    • input over 33 s.
  • Out of scope / follow-ups:
    • Long-input truncation: #636.

Validation sources for the new claims

Claim Source
macOS arm64 CPU pack 20/20 4 runs at ae7b745 on native v0.4.1-1. Worst in-flight cancel 60.2 to 180.5 ms, immediate cancel 0.92 ms or less, RSS growth 1.0081 to 1.0110. Truncated RIFF gave Answer.
macOS arm64 Metal pack 20/20 2 runs at ae7b745. The runtime reported Metal with 99 GPU layers and accelerator_execution_verified: false. Worst in-flight cancel 2.7 and 2.9 ms. Truncated RIFF gave And so.
Linux x64 CPU pack 20/20 2 runs on GCE e2-standard-8 (AMD EPYC 7B12) at main 1584e7d4f, native v0.4.1-1. The linux-x64 archive SHA-256 3f139762a1a38deafbe4a08ae76ea317e386423f66729696cfbbc98bcb528463 (176,608,623 bytes) matches the release asset. The runtime reported CPU with 0 GPU layers. Worst in-flight cancel 289.6 and 183.6 ms, worst immediate cancel 1.475 and 1.579 ms, RSS growth 1.0554 and 1.0267. Truncated RIFF gave And so.; the 33 s fixture gave the reference three times (WER 0).
Pack inputs and budgets speech_edge_fixtures.dart (3 s silence; jfk.wav cut to its first 20,044 bytes; 44.1 kHz stereo; 3 repeats = 33 s). In speech_runner.dart: 500 ms / 500 ms / 1.10x, lead fraction 0.5, 3 cycles. speechLifecycleCheckCount is 8 before 49ee245 (#594) and 15 after.
No chunk claim for the 33 s fixture At llama.cpp b29c606 (native v0.4.1 and v0.4.1-1), PROJECTOR_TYPE_QWEN3A uses mtmd_audio_preprocessor_qwen3a (mtmd.cpp:939-943), which cuts audio into 800-mel-frame (8 s) windows (mtmd-audio.cpp:697-722); each window is its own audio chunk (mtmd.cpp:1605-1638). clip.cpp:1781 sets audio_chunk_len = 30, but only the log line at clip.cpp:2072 and the dots3note preprocessor (mtmd-audio.cpp:753) read it. The independent review's CPU run logged 2 audio chunks for the 11 s jfk.wav (104 + 52 tokens) and 5 for the 33 s fixture (4 x 104, then 26).
297 s: 3,890 prompt tokens, truncated, no error #636. Local run at ae7b745, macOS arm64 CPU, native v0.4.1-1, ctx 4096, 179/594 words, state completed.
WER normalization speechWordErrorRate (speech_runner.dart:14-20): toLowerCase(), then .,!?:;"—– replaced with spaces, then split on whitespace.
Windows x64 ASR outside Qwen3-ASR #332 body: windows-x64-ci-runtime PASS, Moonshine final text containing country.
Pixel, iOS Simulator, iPad smokes Result tables in the #328 and #462 bodies.
MP3/FLAC coverage speech_to_text_test.dart:50 and speech_platform_stub_test.dart:15 assert the native format set. speech_to_text_web_test.dart:242 checks that Web rejects MP3 bytes. No test under test/, example/chat_app or packages/llamadart_validation decodes MP3 or FLAC.
Chat app Web STT and cap downloadable_model.dart sets webSupportsSpeechToText: true. In chat_provider.dart, the Web picker accepts wav, and maxAudioRecordingDuration (5 min) auto-stops and transcribes. The preset has contextSize: 4096.

The ASR and chat-app tests do not reach these doc edits. The static-format-analyze, root-vm and validation-harness checks guard only the dartdoc, matrix-string and rationale-string edits.

Completeness checklist

  • Declared scope is fully implemented, or explicitly reduced above.
  • Unsupported platform/option combinations fail loudly with actionable diagnostics or are clearly documented as unavailable.
  • Public API docs, README/website docs, examples, support matrices, and changelog entries are updated where relevant.
  • Regression coverage covers the original issue plus key negative/version-skew paths where relevant. (N/A for behaviour: docs only. The snippet fix is checked by analysis, below.)
  • Security/privacy review completed: no secrets, tokens, signed URLs or local paths added.
  • Follow-up work that is useful but not required for this PR is tracked in GitHub Issues, or explicitly marked "None" above.

PR type guidance

  • Docs-only PR: runtime behaviour is unchanged. The lib/ change is dartdoc text only; the validation package changes one dartdoc and one rationale string. Docs were validated with ./tool/docs/build_site.sh via ./tool/docs/validate_links.sh.

Test Plan

All at 5022c0b53, macOS arm64, Flutter 3.47.1 / Dart 3.13.1. The tree was clean before and after. e6849aa81 passed the same gates earlier.

  • Light gates, 00:32:18Z to 00:32:34Z (loadavg 21.74 at start, 20.38 at end):
    • dart run tool/prepare_workspace.dart: rc 0; 0 tracked files changed.
    • dart format --output=none --set-exit-if-changed .: 741 files, 0 changed.
    • dart analyze --fatal-infos: no issues.
    • dart run tool/testing/check_platform_boundaries.dart: OK.
    • dart run tool/testing/verify_release_docs_versions.dart: rc 0. It reports the pending llamadart_llama_cpp_flutter companion bump, which already exists on main and is not changed here.
  • Holding both local heavy-work locks, 00:37:39Z to 00:40:56Z (loadavg 62.66 at start, 79.82 at end):
    • ./tool/docs/validate_links.sh (runs build_site.sh): build passed with broken links set to throw.
    • packages/llamadart_validation: dart pub get, dart analyze --fatal-infos (no issues), dart test (+177, all passed).
    • dart test -p vm -j 1 --exclude-tags local-only: +2974 ~78, all passed.
  • dart test -p chrome --exclude-tags local-only: N/A. No web-reachable change. speech_platform_stub.dart is the non-js_interop branch of a conditional import, and its edit is a comment.
  • Guide snippets (at 35074e592; later commits and the merge touch no dart block): I extracted all four dart blocks, wrapped each in a function, and ran dart analyze --fatal-infos in the clone (then deleted the file). Before the fix: argument_type_not_assignable (String? to String). After: no issues.

Matrix Evidence

Matrix row Scope covered Platform / model / backend Result Evidence / notes
static-format-analyze format, analyzer, import boundaries macOS arm64 PASS See Test Plan.
root-vm VM suite macOS arm64 PASS +2974 ~78
validation-harness validation package analyze and tests macOS arm64 PASS dart analyze --fatal-infos no issues; dart test +177
root-chrome browser suite N/A N/A No web-reachable change.
coverage-lib lib coverage N/A N/A No lib/ behaviour change (dartdoc only).
docs-site website build, links macOS arm64, Docusaurus PASS validate_links.sh
validation-speech STT pack results the docs cite macOS arm64 CPU/Metal; Linux x64 CPU PASS (cited, not rerun) Results listed under "Validation sources", including the Linux x64 runs at 1584e7d4f; none rerun at this head: docs-only PR.
speech-to-text-smoke, web-speech-to-text-smoke, physical-ios-speech-e2e N/A N/A N/A Runtime unchanged.
high-risk-exact-head-independent-qa independent review N/A PASS Accepted at 5022c0b53 / e5cedd250 by a fresh independent reviewer, 0 blocking findings; readiness evaluator exit 2 (unverifiedPrerequisites); see the high-risk block.

Mutations

Mutation What went red
Guide snippet at :54 restored to StateError(capabilities.unsupportedReason) The scratch dart analyze of the extracted snippets: argument_type_not_assignable. No repository test covers doc text.

No repository test pins the other edits: guide, README and changelog prose, the dartdoc wording, the covers string, or the edge_long_boundary rationale string. No mutation of them can turn a test red.

Review Notes

  • Independent review status: accepted at 5022c0b53 against e5cedd250 by a fresh independent reviewer, 0 blocking findings; see the high-risk block. Earlier, e6849aa81 was accepted against 7556d2681, and 35074e592 was rejected for the false 30-second chunk claim, fixed in 2bd6c969e and e6849aa81.
  • CI status / head SHA: 5022c0b536410862e950394354009d4968e057e2: 28 check runs, 23 success, 5 skipped, 0 failed.

High-risk regression review

  • Status: accepted at 5022c0b536410862e950394354009d4968e057e2 / e5cedd250d3a9c29598d01cae97227aba73013cb. It supersedes the acceptance at e6849aa81 / 7556d2681.
  • Classification: high-risk (backendRuntime, regressionPolicy). classify_high_risk_changes.dart on git diff --name-only --no-renames e5cedd250...HEAD at the head:
    Classification: high-risk
    Surfaces: backendRuntime, regressionPolicy
    Required matrix: dart run tool/testing/test_matrix.dart --tier high-risk
    
    Only lib/src/core/speech/speech_platform_stub.dart (dartdoc text only) and tool/testing/test_matrix.dart (the validation-speech covers string only) classify as high-risk; the other 10 paths classify as standard.
  • Implementation task: the author of this PR.
  • Independent blocking QA task: accepted at 5022c0b53, 0 blocking findings. The review is by a fresh independent reviewer with no part in writing this PR, its fix-ups or the earlier reviews. It covered every changed line of the 12 files against e5cedd250, with extra care on the merge and 5022c0b53. One attempt with a second review tool, at high reasoning effort, also found no blocking defect; the review does not rely on it.
    • Merge: the tree of b5d743615 equals the automatic merge of e6849aa81 and e5cedd250 (git merge-tree, no conflicts). Nothing merged contradicts the PR. #637 changes only speech_runner_test.dart, pinning the 500 ms, 1.10x and 0.5 values the docs state. From #638, the guide's loadMultimodalProjector exception paragraph agrees with "Projector load success alone does not prove audio support", the silence row's message still matches speech_to_text.dart:1004, and its new real-model checks only attempt projector loads and transcribe nothing.
    • 5022c0b53: both Linux speech-results.json files show functional_pass true, 20/20 PASS, linux_x64, backend_name CPU with 0 GPU layers, the same model_lock_hash, truncated RIFF And so., and the 33 s fixture at WER 0 with 3 repeats. Worst in-flight cancel is 289.597 and 183.595 ms, worst immediate cancel 1.475 and 1.579 ms, RSS growth 1.0554 and 1.0267. Both files match the SHA-256 digests the run recorded. The run log shows clone 1584e7d4f on main, AMD EPYC 7B12, and the native_bundles/v0.4.1-1/linux-x64 archive, whose SHA-256 and size equal the llamadart-native v0.4.1-1 release asset. The runs used validation.dart speech --pack stt --backend cpu, the command the validation-speech-stt scenario runs, and the validation package at 1584e7d4f differs from the head only in the edge_long_boundary rationale string and one dartdoc; the library has since gained #620 and #638. The macOS runs at ae7b745 also pin v0.4.1-1. The two rewrapped lines change no words.
    • Claims rechecked at the head: the four edge fixtures and their contracts, the 15 lifecycle checks, the 500 ms budgets, lead fraction 0.5, the 1.10x bound from the RSS sample after generate, the 3 cycles, and the WER normalization; the chat-app preset (contextSize: 4096, five-minute auto-stop, Web wav picker, bridge v0.1.30+ gate); the silent currentPos >= nCtx stop in llama_cpp_service.dart; the #636 numbers. Both changelog bullets sit under Unreleased and mirror each other; #594 (49ee24594) is not in v0.8.24. git diff --check is clean.
    • Gates at the head, both heavy-work locks held (01:03:40Z to 01:08:01Z, loadavg 28.18 at start, 18.71 at end): prepare_workspace rc 0 and clean; format 741 files, 0 changed; dart analyze --fatal-infos and check_platform_boundaries clean; test/unit/tooling/ plus test/unit/core/speech/ +540 ~4; packages/llamadart_validation analyze clean and +177; full VM +2974 ~78; verify_release_docs_versions rc 0 with the known pending companion bump. validate_links.sh found no website/node_modules in that batch, then passed in a second locked run (01:11:43Z to 01:13:02Z, loadavg 10.38 to 11.35) with node_modules copied from an earlier clone at the same lock file. The guide's four dart blocks, unchanged since 35074e592, analyze clean (00:57:55Z, loadavg 19.49).
    • Mutations of the code the docs describe, each alone then reverted, all red: WER without toLowerCase (1 red), WER without the comma (1 red), truncated-RIFF contract wer >= 0 (2 red), memory baseline at load (1 red), 2 repeats, 22 s (1 red), native format set without mp3 (4 red), cancel budget 501 ms (1 red), immediate-cancel budget 501 ms (1 red), memory budget 1.11x (1 red), lead fraction 0.6 (1 red). Renaming the validation-speech row's command scenario to one the runner does not define turns rows name local E2E scenarios that the runner defines red.
    • CI at the head: 28 check runs: 23 success, 5 skipped, 0 failed.
    • Non-blocking notes:
      • No test pins the WER dash characters, the covers string or the edge_long_boundary rationale: the dash and rationale mutations survived, and no test references the covers text.
      • The validation-speech-stt scenario always passes --backend cpu; the Metal runs the guide cites pass --backend metal to validation.dart speech --pack stt directly.
      • The rationale "Audio over 30 seconds must be transcribed in full." is a requirement, not a native boundary; #636 shows 297 s is not transcribed in full at the preset's context.
  • Exact head / current base: 5022c0b536410862e950394354009d4968e057e2 / e5cedd250d3a9c29598d01cae97227aba73013cb (GitHub baseRefOid, a parent of the merge b5d743615), read from pulls/639 and GraphQL at the start of the review, before evaluation and before this edit; draft, 0 review threads, 0 reviews.
  • Production-branch deletion, bypass, or miswire proof: N/A: no production branch changes. The two trigger paths are covered by speech_platform_stub_test.dart (native format set without mp3: native platform accepts file input and unencoded audio red) and test_matrix_test.dart (the renamed scenario: rows name local E2E scenarios that the runner defines red).
  • Readiness evaluator: dart run tool/testing/high_risk_readiness.dart at 01:14:24Z (loadavg 11.09), with repository, PR, author, head and base from a fresh GitHub API query: exit 2, unverifiedPrerequisites (externalPrerequisitesUnavailable), over 12 changed files. That is the best local result: the evidence is internally consistent, and the external prerequisites do not exist.
  • Affected-family real-model/artifact evidence: existing Qwen3-ASR 0.6B Q8_0 validation-speech-stt results cited above (macOS arm64 CPU and Metal at ae7b745, Linux x64 CPU at 1584e7d4f, all on native v0.4.1-1); none rerun at this head: docs-only PR, runtime unchanged.
  • Explicit unavailable-family or other N/A evidence: N/A.
  • Known PR-caused P1 regressions: 0
  • Unresolved review threads: 0

State where the STT pack has passed and which native targets and
backends have no STT run, document the edge-fixture outcomes and the
cancel/memory budgets, narrow MP3/FLAC and five-minute recordings to
what real-model checks cover, fix the LiteRT snippet that failed
analysis, restore the orphaned chat-app README sentence, and add the
missing [#594](#594) changelog
entry.

Part of [#325](#325).
validation-speech-stt passed 20/20 twice on Linux x64 CPU (AMD EPYC 7B12)
at 1584e7d with native v0.4.1-1, replacing the earlier native v0.4.1 run
in the guide's validated scope. Also rewraps two long guide lines.
@leehack
leehack marked this pull request as ready for review September 24, 2026 01:39
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Chat app preview removed for leehack/llamadart-chat-pr-639.

@leehack
leehack merged commit a0ac411 into main Sep 24, 2026
31 checks passed
@leehack
leehack deleted the docs/325-speech-validated-scope branch September 24, 2026 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant