Skip to main content
SCRIBED Care Back to Care

Scribed Care · Research

How we chose the on-device models

This page records how the speech-to-text, speaker-labelling, assistant and identifier-flagging models for Scribed Care — the iPhone and Apple-silicon Mac app — were chosen on October 11 and 12, 2026: what was required, which candidates were considered and why most were dropped, how the kept ones were measured, the numbers, the decision rules that were written down, and what has not been measured yet.

Written and revised Source: docs/reports/care-model-selection-2026-10.md in the Scribed repository

Every measurement below was taken on 2026-10-11 on one Mac (Apple M5 Pro, 48 GB, macOS 26.5.2, Xcode 26.6) unless a different device is named. No iPhone has been measured yet. Two words are used strictly: measured means a number that is in a result file under the evaluation harness; designed means a rule or mechanism written on 2026-10-12 that has not yet been built or measured. Nothing in this report is a compliance claim; “identifier flagging” names a tested capability with the recall figures given, not a regulatory status, and the per-span clinician review before a share is the safeguard the capability feeds.

1. Summary

One line per job: what was chosen, the evidence, and the gate that would reverse it.

  • Speech-to-text, iPhone and Mac — NVIDIA Parakeet TDT 0.6B v3 (CC-BY-4.0), FluidInference’s Core ML build, through FluidAudio 0.15.5 (Apache-2.0).Measured: on the 57 PriMock57 mock consultations it scored 10.8 % mean word error rate at 255 MiB peak host memory — ahead of Whisper tiny, base and small by mean word error rate over the full 57-file set (12.8–18.6 %), ahead of Whisper medium and of the app’s current GGUF q4_0 Parakeet on the 26 files those two completed (13.9 % and 13.8 % against 12.1 % for v3 on the same files), and ahead of WhisperKit large-v3-turbo on its 40 (15.6 % vs 11.5 %); it beat Parakeet v2 on 43 of the 57 files (mean +0.7 points). It was not ahead of everything: FluidAudio 0.17.7’s build of the same weights was 1.1 points better on the 13 files it completed, sherpa-onnx’s int8 Parakeet v2 led the clean synthetic dictation tables but was not run on PriMock57, and Whisper large-v3 (non-turbo) and whisper.cpp’s turbo were never run. The Core ML build is the only candidate that requests the Neural Engine on iPhone; whether iOS schedules it there, and the resulting footprint, are the first device measurements. Gate: the written WhisperKit fallback rule (§6.1), not met on the data to hand.
  • Drug names — a conservative RxNorm phonetic post-correction on a copy of the transcript, plus a mandatory medication-verification strip before Finalize.Measured (Mac): the tightened pass costs 0.0 points of WER on PriMock57 and adds 14.3 points of medication-term recall on synthetic dictation — but its thresholds were tuned on those same files (§4.7), so the figures are in-sample. Every engine tested mis-spelled medication names, the failure class the Research Square preprint of 2026-10-05 names as safety-critical; no engine eliminates it, although engines differ by up to 20 points of term recall. Designed: the TypeScript port, the strip, the reconciliation at Finalize. Gate: the held-out acceptance in §6.2.
  • Speaker labels — FluidAudio’s Sortformer v2.1 (NVIDIA Open Model License), run offline after transcription over the whole visit, shipped only if a written gate passes.Measured: one manual 109-second two-voice synthetic visit (100 % speaker accuracy, against 50 % for the pyannote community-1 pipeline; neither run is in the logged results) and a 21-dictation single-speaker sanity run through the harness. That is thin evidence for both candidates, so pyannote is deferred pending the DER run rather than dropped. Gate: DER ≤ 20 % on PriMock57 (not yet computed), ≥ 90 % speaker accuracy on the six synthetic visits (not yet run through the harness), and an iPhone 15 memory rule (§6.3); otherwise the app ships without automatic labels and keeps per-segment hand labelling.
  • Assistant — Gemma 4 E2B QAT Q4_0 (Apache-2.0, 3.35 GB, already pinned in the catalog) as the one model.Measured: the 2026-10-02 twelve-case iOS-Simulator comparison, in which E2B preserved the principal facts where LFM2.5-1.2B made material errors and Qwen3.5 2B inferred absent facts (one run, short text cases, an agent’s review, no clinician adjudication). No iPhone footprint exists for the GGUF through llama.rn; “fits an 8 GB phone” is the §6.4 derivation (3.35 GB memory-mapped + KV cache < 4.5 GB < ≈ 6 GB entitled budget) pending the entitled-build measurement. Designed: the speech engine released for the duration of a draft and re-warmed after it, the long-visit map → reduce pass. Gate: the long-visit omission gate (§6.4); E4B with a 12 GB floor only if E2B fails it.
  • Identifier flagging for review before anything may leave the device — designed as five layers whose model layer is OpenMed-PII-SuperClinical-Small-44M-v1 (Apache-2.0, ONNX).Measured: the model alone found 46 of the 65 patient-identifier spans in the 30-case synthetic corpus (70.8 %, 95 % CI 59–80 %) against 13.8 % for the app’s current rules and 32.3 % for Apple’s NLTagger; the union of the layers that actually ship (rules + known patient tokens + OpenMed) found 47 of 65 (72.3 %). Designed, unmeasured: the spoken-number normalisation, extended rules and known-patient fuzzy match that are meant to cover the model’s misses — the shipped stack has no end-to-end recall figure (§7, item 12). Gate: the device go / no-go in §6.5.
  • Hardware — the design replaces the model picker with two tiers derived from proxies: transcribe (iOS 17, Neural Engine, 6 GB phones: iPhone 12 Pro through 15) and draft (8 GB phones from the 15 Pro on, and every Apple-silicon Mac on macOS 14 with 8 GB or more), 4 GB phones unsupported. Measured: nothing on a device. Gate: the three tier rules in §6.6, decided with recorded device numbers.

Terms used

WER — word error rate, the share of reference words that must be substituted, inserted or deleted to reach the hypothesis, after both sides are normalised (§4.5); lower is better. Term recall — the share of an item’s annotated clinical terms (drug + dose, vitals, labs) found verbatim; negation kept — the same for negated findings. Speaker accuracy — the share of reference utterances that received the right speaker label under the best label mapping. DER — diarization error rate, the share of audio time with a missed, extra or wrong speaker. RTF — real-time factor, processing time ÷ audio duration (0.01 = one minute of audio in 0.6 s); §4.6 says which clock each engine’s RTF uses. RSS — peak resident set size, the process’s memory high-water mark on the Mac; it bounds nothing on an iPhone.

2. What we were choosing and why it is hard

2.1 The requirements

The owner’s task statement (2026-10-11) set seven requirements. The ones that drive model choice:

  1. One model per job, chosen by us. The best local speech-to-text for iPhone, the best for Apple-silicon Macs, and the best local assistant if note drafting stays. The in-app model picker goes away. Minimum hardware (chip, memory, OS version) is set from measurements, with a clear message on unsupported devices.
  2. Accurate transcription of full-length multi-speaker visits and of dictation: drug names, doses, numbers and negation must come through; speaker labels if a reliable local option exists.
  3. The raw record is always retrievable: the original audio and the model’s unedited, time-aligned transcript, never overwritten, separate from edits and AI notes.
  4. Offline by default: recording, transcription and local patient data with no network.
  5. Opt-in cloud only with identifiers removed on the device before anything leaves, only through scribed-api, never a fleet worker directly, and the app is never called “HIPAA compliant” (the spec’s MED-031, MED-040 and MED-045).
  6. Connected to the web app like the Chat and Tel apps (same account, workspace switching).
  7. Looks like the rest of Scribed.

Requirements 2, 4 and 5 are the ones a model has to satisfy; 1 says the choice must be measured, not configured.

2.2 Why it is hard

Medication names are a known failure class. The Research Square preprint “Medication Names Are a Safety-Critical Failure Mode in Clinical Speech Recognition” (Faulkenberry, Emory, posted 2026-10-05, rs-10804600/v1) benchmarked 12 on-device models and 5 cloud APIs on 400 simulated consultations, including PriMock57, and found medication names the highest-error clinical-term category for most systems. Pooled across the on-device configurations, lisinopril was misrecognized in 92 % and amoxicillin in 93 % of PriMock57 occurrences, against 3–30 % for the same drugs across cloud services, while overall clinical-term recall stayed within 1–3 points of the cloud. Our first smoke item (2026-10-11, a 47-second synthetic dictation) reproduced this: every engine mis-spelled at least two of lisinopril, atorvastatin and empagliflozin (“lysinopril”, “a torvostatin”, “empoglyflasin”) while every vital sign and number was correct on every engine and only whisper small.en-tdrz dropped a negation (66.7 % kept). No engine eliminates the failure — engines differ by up to 20 points of medication-term recall (§5.2) — so the preprint’s conclusion applies regardless of model: explicit medication verification before note finalization is required, and a single model suffices (fusion did not help).

Offline means no fallback. The app may not send audio anywhere, so there is no cloud second opinion, no server-side re-transcription and no remote vocabulary service. Whatever accuracy the device model has is the accuracy the clinician gets.

Memory on a phone is the real constraint. Apple publishes no per-app memory limit. Community measurements (read 2026-10-11) put the budget at about 2.1 GB on 4 GB phones, about 3.3 GB on 8 GB phones without the increased-memory-limit entitlement and about 6 GB with it. The ONNX-runtime path for Parakeet (sherpa-onnx int8) was measured by a user at 1.23 GB on load on an iPhone 15 and out of memory at 2.9 GB with the Core ML provider — the one data point behind the “≈ 2.9 GB on a 6 GB phone” figure in §6.6; the assistant is 3.35 GB on disk and memory-mapped. Nobody — not FluidAudio, Argmax or Apple — has published an iPhone memory or speed figure for FluidAudio’s Core ML Parakeet, so the floors had to be derived from proxies and are provisional until a device run is recorded (§7).

The runtime has to fit an Expo app. Care is an Expo SDK 54 / React Native 0.81 app. FluidAudio is a Swift package; Expo’s CocoaPods build cannot add SwiftPM products natively, so asking for the Neural Engine means vendoring the Apache-2.0 sources into the app’s own native module (§6.1).

Licences carry clinical clauses. Several of the strongest-looking candidates come with terms that matter for a clinical product: Google’s MedASR and MedGemma under the Health AI Developer Foundations terms (a “medical device manufacturer” clause and pass-through use restrictions), Apple’s Foundation Models under an acceptable-use rule against unsupervised decisions in medical domains, LFM2.5 under a revenue threshold, and the offline Sortformer v1 under CC-BY-NC. Each was checked against its primary text (§3.3, §9).

3. Research: candidates per job

The research ran on 2026-10-11 as eight “lens” reports (iPhone speech-to-text, Mac speech-to-text, diarization, assistant model, identifier removal, evaluation datasets, hardware and licensing, Mac runtime), followed by 48 verifier passes that re-read the primary source behind every decision-driving claim. Links in the full report are to the sources as read on 2026-10-11.

Speech-to-text on iPhone

Speech-to-text candidates on iPhone (“runtime” is how the Expo app can reach the model)
CandidateLicenceRuntime in CareWhy kept or dropped
Parakeet TDT 0.6B v3, FluidInference Core ML build via FluidAudioCC-BY-4.0 weights; FluidAudio Apache-2.0Vendored Swift sources in the native module; iOS 17+Kept. The only candidate that requests the Neural Engine on iPhone (FluidAudio sets .cpuAndNeuralEngine for the int8 encoder — a request Core ML may decline; no iPhone run has confirmed it); FluidAudio’s own benchmarks 2.5 % WER on LibriSpeech test-clean at 155.6× on an M4 Pro; the Scribed model hub already pins the 21-file bundle; best PriMock57 WER among the engines run on all 57 files (§5.1).
Parakeet TDT 0.6B v3, GGUF q4_0 via whisper.rn (the app’s current path)CC-BY-4.0 weights; whisper.rn MITAlready wiredDropped. CPU/Metal only; 13.8 % PriMock57 WER against 12.1 % for the Core ML build on the same 26 files (1.7 points), at about 3.4 times the wall-time RTF and five times the memory (1.2 GB RSS); the app capped it at 90 s of audio because memory grew with duration.
Parakeet v3 int8 ONNX via sherpa-onnx / react-native-sherpa-onnx 0.4.4CC-BY-4.0; Apache-2.0 / MITPublished RN package, iOS 13+Dropped for the phone, kept as a comparator. 1.23 GB on load on an iPhone 15, OOM with the Core ML provider; on our Mac 1.9–2.5 GB RSS; its Parakeet export crashed on a 16-minute file (fixed positional table). Only the v2 export was run (synthetic sets only, §4.4).
Parakeet TDT 0.6B v2 (English)CC-BY-4.0FluidAudio .v2Dropped in favour of v3. 11.5 % vs 10.8 % on PriMock57, v3 better on 43 of 57 files; its small edge on synthetic dictation (5.8 % vs 6.2 %) disappears after the lexicon pass (4.2 % vs 4.2 %).
Parakeet Ultra / Redux / Phonon-2 via FluidAudio ≥ 0.17CC-BY-4.0FluidAudio 0.17.3+, which links a binary NemoTextProcessing xcframework; Redux/Phonon-2 need iOS 18Deferred. Ultra: 2.9 % WER with vocabulary boosting on the one smoke dictation (a list that overlaps the corpus, §4.4); 0.15.5 is the last pure-source FluidAudio tag and is what Scribed Talk and Tel pin; the upgrade is one change across three hosts (owner decision §6.8). On matched PriMock57 files, 0.17.7’s Parakeet v3 was 1.1 points better than 0.15.5’s (§5.1).
Whisper large-v3-turbo (WhisperKit _632MB)MITSwift module (Talk’s WhisperTranscriber)Written fallback only. 15.6 % PriMock57 WER on 40/57 items (11.5 % for v3 on the same 40) at 0.113 RTF; best term recall after the lexicon pass on synthetic dictation (96.8 %) but about eight times the Core ML engine’s processing time and no diarization in the same SDK.
Whisper tiny.en … medium.en (whisper.cpp)MITwhisper.rnDropped. Medium: 13.9 % on its 26 PriMock57 files (v3 12.1 % on the same files) at 2.3 GB RSS; small: 12.8 % on all 57 (v3 10.8 %) at 1 GB.
Apple SpeechTranscriber (iOS 26 / macOS 26)Apple SDKSwift moduleDropped, on published evidence and one smoke item; not run on PriMock57 although the runner exists (§7, item 15). No custom vocabulary on SpeechTranscriber (only the legacy DictationTranscriber takes contextualStrings), OS-updated model under a pinned app, isAvailable false on A13 and in the Simulator; good on read speech (Lyonesse 2.12 % LibriSpeech, M2 Pro) but Argmax measured 14.0 % vs Parakeet v2’s 11.7 % on Earnings-22. Zero in-app memory was its only advantage.
Qwen3-ASR 0.6B / 1.7BApache-2.0sherpa-onnx RN (0.6B int8 ≈ 982 MB)Dropped. No Core ML build; the 1.7B that leads Omi’s medical board (3.14 % medical WER) has no mobile export and needs 2.7 GB; the 0.6B ranks behind Parakeet on PriMock57 in the preprint (14.74 % vs 13.85 %).
Google MedASR (105M, dictation model)HAI-DEF termssherpa-onnx RNDropped. 88.8 % WER on PriMock57 conversations in the preprint (42.8 % on the doctor channel in MedWER); strong only on dictation; licence needs counsel (§3.3).
Nemotron Speech Streaming 0.6B, Moonshine v2, Voxtral, Kyutai, CanaryNVIDIA OML / MIT / Apache / CC-BYVariousDropped. No medical evidence or no RN/Apple path; the preprint’s “Nemotron” int8 row scored 35–38 % WER.

Speech-to-text on Apple-silicon Macs

The Mac runs the unmodified iOS binary (“Designed for iPad” on the Mac App Store), so the Mac engine is the iPhone engine with parallelChunkConcurrency: 2 and no jetsam. That single-binary rule is why the Mac-only alternatives — WhisperKit (427 MB RSS, 11.9× on an M5 Pro per speech-swift), MLX Parakeet (2.8 GB RSS), Qwen3-ASR 1.7B 8-bit (2.7 GB), Voxtral (≥ 9.5 GB), SpeechAnalyzer on macOS 26 — were not taken: each would be a second engine behind a second native seam, and none of them runs on the phone. (Memory is not the reason for the Parakeet and Qwen rows: an 8 GB Mac that will hold a 4.5 GB assistant phase could hold a 2.8 GB speech engine.) A native Swift Mac app (Talk’s stack) was the strongest speech stack but a second UI; Mac Catalyst would drop onnxruntime (no Catalyst slice) and is unsupported by Expo.

Speaker labels (2–4 speakers)

Speaker-labelling candidates
CandidateLicenceRuntimeWhy kept or dropped
Sortformer v2.1 streaming, FluidAudio Core MLNVIDIA Open Model License (commercial permitted, notice + guardrail conditions)FluidAudio 0.15.5 SortformerDiarizer, palettized on iPhone (104 MB on disk; ≈ 330 MB RAM is a FluidAudio developer estimate in a source comment, not a measurement)Kept, gated. 100 % speaker accuracy on one 109-second synthetic visit — a manual run on 2026-10-11 that is not in the logged results (§5.4) — and 100 % on a 21-dictation single-speaker sanity run through the harness; same SDK as the ASR; AMI-SDM 20.6 % DER on NVIDIA’s card. Unmeasured on real clinic audio and unmeasured on the six synthetic visits through the harness.
Nemotron-3-Diarization, FluidAudio ≥ 0.17OpenMDW-1.1FluidAudio 0.17.0+, iOS 18 presets (95 MB W8A8)Deferred with the FluidAudio upgrade. Best DER of any commercially licensed local option (9.36–10.07 % AMI-MHM per FluidAudio’s PR #883); 100 % on the one smoke visit.
pyannote community-1 offline, FluidAudio pipelineCC-BY-4.0 (scoped to seven Core ML artifacts; the legacy pyannote_segmentation / wespeaker* files are not covered)FluidAudio OfflineDiarizerManagerDeferred pending the DER run. It collapsed the same 109-second two-voice synthetic visit to one speaker (50 %) in a manual, unlogged run — one item is not a fair basis for dropping a pipeline FluidAudio’s own benchmark scores at 10.6 % DER, so it stays a candidate until both diarizers have been run through the harness on the six synthetic visits and the 57 PriMock57 files. Every streaming configuration of it is 38–56 % DER, which is why only the offline pipeline is in question.
whisper.cpp tinydiarize (small.en-tdrz)MITwhisper.rnDropped. Turn boundaries only (recall 70.8 % on earnings calls), never identifies who; lost a negation on the smoke item.
Sortformer v1 (offline, 4 spk)CC-BY-NC-4.0—Excluded by licence.
Apple Speech frameworkApple—No speaker identification symbol in the framework index (iOS 26 or the 27.0 additions).

Assistant (local note drafting)

Assistant candidates
CandidateLicenceRuntimeWhy kept or dropped
Gemma 4 E2B-it, Google QAT Q4_0 GGUFApache-2.0llama.rn 0.12.9 (already pinned as gemma4-e2b-qat-q4, 3,349,516,256 B)Kept. Our own evidence is the 2026-10-02 twelve-case iOS-Simulator comparison (§5.8): E2B preserved the principal facts, LFM2.5 made material errors, Qwen3.5 2B inferred absent facts. No published iPhone figure exists for this artifact through llama.rn. The publisher’s iPhone 17 Pro figures via LiteRT-LM — 1,450 MB GPU / 607 MB CPU phys_footprint, 56.5 tok/s — are for a different, ~1 GB LiteRT artifact and bound the architecture from below only. “Fits an 8 GB phone” is the §6.4 derivation, pending the entitled-build measurement.
Gemma 4 E4B-itApache-2.0llama.rnDropped for v1. Only small open model with a source-grounded SOAP evaluation (composite 0.498 vs GPT-5.4’s 0.515, arXiv 2605.24902) but 4.2–5.2 GB on disk, 3,380 MB GPU footprint on a 17 Pro (LiteRT, the same caveat), and a report that a GGUF load needs the entitlement; it would force a 12 GB floor. Becomes the fallback only if E2B fails the long-visit omission gate (§6.4).
MedGemma 1.5 4BHAI-DEF termsllama.rnDropped. No note-generation evaluation; “not evaluated for multi-turn”; pass-through licence obligations.
Qwen3.5 4B / 2B, Phi-4-mini / MediPhiApache-2.0 / MITllama.rnDropped. No scribe evaluation; the 2B card says prototyping only; the 2B lost the 2026-10-02 comparison.
LFM2.5-1.2BLFM Open License v1.0llama.rnExcluded by licence (§3.3); it also lost the 2026-10-02 comparison.
Apple Foundation Models (on-device ~3B)Apple AURexpo-foundation-modelsDropped as a drafter. 4,096-token session window shared by instructions, schema and output (TN3193); the acceptable-use rule bars unsupervised decisions in medical domains.

Identifier flagging (on spoken transcripts)

Identifier-flagging candidates
CandidateLicenceRuntimeWhy kept or dropped
Rules + known patient tokens (the app’s existing lib/privacy/candidate.ts)oursJSKept as the first layers, to be extended with spoken-number normalisation and gazetteers (designed, §6.5); 13.8–16.9 % recall alone as they exist today (§5.5).
OpenMed-PII-SuperClinical-Small-44M-v1 (DeBERTa-v3-small, 54 labels incl. MRN, DOB, age)Apache-2.0onnxruntime-react-native 1.24.3 CPU EP, as the app’s Kokoro TTS already doesKept. 70.8 % recall (46/65, 95 % CI 59–80 %) at 12.5 % decoy false positives — four year-only dates and the age “eighty nine” (§5.5); 283 MB fp16. Its only published evaluation is on an in-distribution synthetic split (micro-F1 0.954), so the on-spoken-text number above is ours.
Apple NLTagger(.nameType)OSSwiftDropped. 32.3 % recall and 20 % decoy false positives: it tags Parkinson, Lewy, Bell, Hodgkin, Crohn, Flomax and Tylenol as names.
GLiNER2-PII (0.3B)Apache-2.0community ONNX, 446–590 MBDropped. No age, no MRN label; SPY medical exact-span F1 0.48.
obi ClinicalBERT / Stanford deidentifierMITneeds Core ML conversionDropped for v1. No published i2b2 numbers on the cards; conversion work.
An LLM (Gemma 4, Apple FM) as redactor——Advisory only. The one independent clinical benchmark found small LLMs hallucinating redactions (Kuo 2025).

3.2 Claims we verified and the ones we corrected

48 verifier passes re-read the primary source behind every decision-driving claim. Among the corrections that mattered: FluidAudio’s iPhone encoder compile times (3,361 ms cold / 162 ms warm on an iPhone 16 Pro Max; 4,396 ms on an iPhone 13) are CPU+GPU first-load compiles, not Neural Engine compiles, and no iPhone speed or memory figure exists; Parakeet Ultra ran 457× vs 335× (about 36 % faster) on the 60-minute Earnings-22 file, so “same speed” holds only for LibriSpeech; the ~6 GB per-app ceiling is one unreplied developer post, not a rule; MedASR’s 88.8 % PriMock57 WER is the preprint’s figure (int8 ONNX via sherpa-onnx, CPU), not its model card’s; the on-device medication misrecognition of 85–93 % is for the highest-error drugs pooled across 12 on-device configurations, while overall on-device clinical-term recall was 87–94 %; only FluidAudio 0.15.5 (2026-07-07) and earlier are pure source; FluidAudio’s 10.6 % DER for the offline community-1 pipeline is under its own scoring (collar 0.25 s, overlap ignored) while pyannote’s stricter protocol gives 19.9 % for the same pipeline; Google’s QAT GGUFs were published with the Gemma 4 release and the ~1 GB mobile figure is the LiteRT format, not a GGUF (the E2B QAT GGUF is 3.35 GB); Lyonesse’s round 2 added Parakeet v2 (2.01 % / 3.40 %), which beats SpeechAnalyzer (2.12 % / 4.56 %) on both LibriSpeech splits.

Confirmed without change, among others: Parakeet v3’s CC-BY-4.0 licence and its “ready for commercial/non-commercial use” wording; Gemma 4’s plain Apache-2.0 text (the applicability of the separate Prohibited Use Policy stayed unverified, §9); sherpa-onnx’s 1.23 GB / OOM figures on an iPhone 15; the WhisperKit dashboard’s per-iPhone speed factors; Argmax’s 11.7 % (Parakeet v2) vs 14.0 % (SpeechAnalyzer) on Earnings-22.

3.3 What we deliberately did not use

  • sherpa-onnx on the iPhone, because of memory: 1.23 GB on load with the CPU provider and out of memory with the Core ML provider on an iPhone 15; on our Mac 1.9–2.5 GB RSS; and its Parakeet export crashed on a 16-minute file. It stays a comparator because it is the exact engine of the preprint — once it is run on PriMock57, which it has not been.
  • Apple SpeechAnalyzer / SpeechTranscriber, because SpeechTranscriber takes no custom vocabulary, its model is updated by the OS underneath a pinned app, and it is unavailable on A13 phones and in the Simulator. Its zero in-app memory (19 MiB in our runner; the model runs in a system daemon) is noted as a future “memory is tight” path, not a v1 engine.
  • MedASR on conversation: a dictation model (5.4–9.9 % on physician dictation in its paper vs 25–45 % for Whisper large-v3) that scores 88.8 % WER on PriMock57 conversations in the preprint and 42.8 % on the doctor channel in MedWER, under terms whose commercial grant is neither explicit nor denied and which carry a medical-device-manufacturer clause.
  • pyannote (FluidAudio’s community-1 offline pipeline) is deferred, not rejected: it collapsed one two-speaker smoke visit to one speaker (50 % speaker accuracy against Sortformer’s 100 %, a manual unlogged run on a single 109 s item), and the Core ML repository’s CC-BY-4.0 covers only seven named artifacts. The DER run decides.
  • Apple NLTagger as an identifier layer, because its 20 % decoy false-positive rate would corrupt clinical text (eponyms and brand names tagged as people) for 32.3 % recall.
  • LFM2.5-1.2B, because its licence grants use only to entities under US $10 million annual revenue.
  • Sortformer v1 (CC-BY-NC-4.0), DiariZen (CC-BY-NC), Rev’s diarizer (non-commercial), Argmax Pro and pyannoteAI (paid, gated), Picovoice (online licence check).
  • macOS say voices for anything that ships or is redistributed: the macOS SLA §2F limits System Voices to “your own original content and projects for your personal, non-commercial use”. Whether generating evaluation audio for a commercial product’s internal testing is “personal, non-commercial use” is arguable and this report does not settle it. The rendered evaluation audio is therefore never committed — only the text corpus, the scripts and the result JSON are — and Kokoro-82M (Apache-2.0) or Piper replace say for the next corpus.
  • Real patient data of any kind for evaluation: MIMIC-IV-Note and other PhysioNet credentialed sets (the data-use agreement forbids sharing and API use), AfriSpeech (CC-BY-NC-SA), MedDialog-Audio (CC-BY-NC).

4. How we measured

The harness lives in scribed-medical-mobile/scripts/model-evaluation/ (asr/ and phi/), with results under results/2026-10-11-mac/. This section describes what the code actually does.

4.1 Corpus design

Thirty-one authored, fictional items: 25 clinician dictations covering primary care, cardiology, paediatrics, pulmonology, anticoagulation, emergency, psychiatry, orthopaedics, insulin, geriatrics, nephrology, obstetrics, dermatology, gastroenterology, rheumatology, post-operative, neurology, asthma, heart failure, anaphylaxis, thyroid, opioid pain, pneumonia, urology and one identifier-dense item, and 6 multi-speaker visit scripts (four with two speakers, two with three — clinician, patient and a family member). The text is deliberately dense in medication names, doses, units, vital signs, lab values, numbers and negation. Each item carries two annotations the ASR scorer uses — terms (tokens that must survive verbatim after normalisation: drug name plus dose, vitals, labs, intervals) and negations (negated findings whose cue must survive next to the finding) — and a third, identifiers, reserved for a check it does not yet run. No real patient, clinician, clinic, date of birth, record number or phone number appears anywhere in the corpus.

4.2 PriMock57

PriMock57 (babylonhealth/primock57, CC BY 4.0) is 57 mock UK primary-care consultations — seven Babylon clinicians and employees acting as patients from case cards, 8 h 38 min 06 s in total, so 9 min 05 s on average — with the doctor and the patient on separate 16 kHz mono channels and a Praat TextGrid per speaker giving utterance start and end times. The preparation script sums both channels into one mono WAV per consultation — a single-channel mix of two close microphones, which has no room acoustics, distance, noise or cross-talk bleed and is therefore cleaner than any phone on a desk — clips at 16-bit range, parses both TextGrids, drops unintelligible markers, keeps the text inside unsure tags, sorts the utterances by start time and writes a reference JSON with clinician / patient speaker labels taken from the channel. PriMock57 has no term annotations, so term recall and negation columns are empty for it; only about 2.7 % of its tokens are medical terms, which is why the synthetic set exists beside it.

4.3 Rendering

Each synthetic item is rendered with macOS say at 175 words per minute, one voice per speaker (six voices in all, with US, UK, Irish, Australian, Indian and South African accents, picked per item so that the two or three speakers in a visit always differ), converted to 16 kHz mono 16-bit PCM, silence-trimmed to 80 ms of lead and tail, and concatenated with 0.9 s gaps. Because the renderer assembles the file itself, the reference JSON carries the exact start and end of every utterance and its speaker. Two long files are also written: long-form-dictations (every dictation back to back with 2 s gaps, 16.8 min, one speaker) and long-form-all (every item, 28.5 min, up to three speakers) for long-recording stability.

4.4 Runners

asr/run-mac.ts runs every selected engine over every manifest item, wraps each engine call in /usr/bin/time -l to record wall time and peak resident set size, writes one hypothesis JSON per (engine, item) plus a run log, and is resumable. Engines run through it: whisper.cpp (tiny.en, base.en, small.en, medium.en; small.en-tdrz smoke only), whisper.cpp’s parakeet-cli on the GGUF Parakeet v3 q4_0 (the exact file the app pins today), Scribed Talk’s debug CLI for WhisperKit large-v3-turbo, an 89-line Swift tool over macOS 26’s SpeechAnalyzer (smoke only), fluid-cli (a 12-line Package.swift pinning FluidAudio exact 0.15.5; asr --version v2|v3 and diarize --engine sortformer|pyannote — every fluidaudio-parakeet row, including all 57 PriMock57 files with timestamp coverage, came from this tool), and FluidAudio 0.17.7’s own CLI with word timestamps (13 PriMock57 files and the synthetic sets; vocabulary boosting, Parakeet Ultra and Nemotron-3 smoke only). asr/run-sherpa.py runs sherpa-onnx 1.13.8 over the same manifests; only Parakeet v2 int8 was run, on the synthetic sets, with transducer inputs windowed at ≤ 5 min because the export’s positional table covers about ten minutes.

The vocabulary list is not independent of the corpus. medical-vocab.txt (100 drug names) was written as a generic formulary, but 67 of its 100 names appear verbatim in the corpus and 59 of the roughly 85 drug-like annotated term heads are on it. Boosting and the prompt variants were therefore evaluated in-sample, which inflates the 60 → 100 % term-recall lift in §5.4.

4.5 The normaliser

Reference and hypothesis go through the same function: hyphens and underscores become spaces, a slash is kept joined to its neighbours (“mg/dl”), sentence punctuation is dropped, spoken decimals (“seven point one”) are written as digits and shielded through Whisper’s EnglishTextNormalizer, then a clinical layer of 44 regular-expression rules runs: units, “per” and “slash” → “/”, “over” before a number → “/”, percent, degrees, A1c, BP, PHQ, GAD, TSH, T4, x-ray, INR, CHA2DS2-VASc, CURB-65, L4-L5, PRN → “as needed”, BID / TID → “twice / three times daily”, q-N-h → “every N hours”, SC / IV / IM, okay / ok, yeah → yes, gonna / wanna, can’t / cannot, “one” → “1”. Fillers are removed from both sides. This is close in spirit to the preprint’s meaning-preserving normalisation but is not the same rule set, so WER values are comparable in ranking, not digit for digit.

4.6 The metrics

Per (engine, item): WER with jiwer on the normalised text; term recall; negation kept; coverage (the share of reference speech time covered by hypothesis segments, 0.5 s tolerance) and outside speech (the share of hypothesis segment time that falls in reference silence — a drift / hallucination proxy); repeat loops (a 4-gram repeated four or more times back to back); speaker accuracy under the best permutation of hypothesis labels onto reference speakers; RTF; peak RSS.

RTF has two bases, and the tables mix them. Where an engine reports its own processing time the scorer uses it: the FluidAudio engines (after model load) and WhisperKit via Talk (including its load). Every whisper.cpp and parakeet-cli run carries no processing field (0 of 89 runs), so its RTF is wall time including process start-up and model load. On 47-second items load dominates — on synthetic dictation the Core ML v3’s wall-time RTF is 0.027 against its processing-time 0.015 — so the whisper and GGUF RTFs are inflated relative to the Core ML engines in the per-dataset tables. §5.1 therefore also gives every engine’s PriMock57 RTF on the one basis the run log supports for all of them, wall time.

4.7 The lexicon pass

The post-correction experiment rewrites each hypothesis by snapping tokens that are not ordinary English words onto the phonetically closest medication name and writes the result as a parallel engine <engine>+lex. The lexicon is RxNorm Current Prescribable Content (US National Library of Medicine, public domain, release 10052026): ingredient and brand names of 5+ letters and at most three words, minus anything made only of dictionary words — 7,638 names. Matching is Metaphone keys plus Jaro-Winkler similarity over 1–3-word windows. The rules after the 2026-10-11 23:44 tightening: never rewrite a single word that is an English dictionary word (stem-aware), shorter than six letters or already a lexicon entry; never rewrite a window made only of English words; a multi-word window must contain a non-English fragment of four or more letters and may not cross punctuation or contain numbers; a same-key match needs Jaro-Winkler ≥ 0.76 (8+ letters) or ≥ 0.84 (6–7 letters), raised to ≥ 0.90 for brand names; a dropped leading sound (“torvastatin” → atorvastatin) needs ≥ 0.85; a single generic with the same first letter and ≥ 0.94 similarity matches without a key.

The tuning was in-sample. The earlier, looser version of these rules cost about 1.3 points of WER on PriMock57 (10.8 % → 12.1 %) through false corrections of ordinary conversational words; the rules were tightened against those same files and the cost re-measured on them, giving 0.0 points. The 0.0-point cost and the +14.3-point recall gain are therefore in-sample figures. §6.2 now holds data out: the rules are frozen, the synthetic set and half of PriMock57 (by consultation id) may be used for tuning, and the other half is scored blind.

4.8 The identifier-flagging harness

phi/corpus.ts holds 30 synthetic transcript-like passages with 65 patient-identifier spans across 15 categories (names 13, relatives’ names 8, dates 13, ages over 89 2, phones 4, email 1, addresses 4, small locations 7, record numbers 4, SSN 1, insurance ids 2, employers 3, vehicle 1, device id 1, URL 1), 4 provider-name spans reported separately, and 40 clinical decoys that must not be flagged (eponyms such as Parkinson, Lewy, Bell, Hodgkin, Crohn; brands such as Lasix, Flomax, Zoloft, Tylenol; conditions such as Rocky Mountain spotted fever; year-only dates; an age of eighty-nine). The categories follow the Safe Harbor list only as a scoring vocabulary. Identifiers are written the way they are spoken. phi/score.ts scores layers: the app’s existing rules without and with the case’s known patient tokens, Apple’s NLTagger, any external NER output, and the union; a span counts as found when at least 80 % of its UTF-16 range is covered, a decoy as a false positive when any finding overlaps it. phi/run-onnx-ner.py runs the OpenMed model’s published ONNX graphs (fp16 and int8, opset 18) through onnxruntime on the CPU with the model’s own tokenizer, keeps labels at ≥ 0.5 confidence, merges BIO pieces into entities and maps the 29 relevant labels of the model’s 54 onto the corpus categories. It is the same graph onnxruntime-react-native would load on a phone, so the Mac run speaks to accuracy, not speed.

4.9 The conditions every number was taken under

  • Mac host, not a phone. Every number is from one M5 Pro. RTF and RSS on this host bound nothing about an iPhone; they rank engines against each other.
  • Shared machine. Several engines ran concurrently with other work, and the runs were stopped at 23:42 because the host was shared. Accuracy figures are unaffected; speed figures are indicative.
  • Partial runs. The PriMock57 run completed for FluidAudio v2 and v3 (57/57) and whisper tiny / base / small (57/57); the GGUF q4_0 and whisper medium stopped at 26, WhisperKit turbo at 40, FluidAudio 0.17.7 at 13; sherpa-onnx ran only the synthetic sets. Every partial subset is the first N files in sorted order, not a random sample, and those files are harder than the set’s average: the Core ML v3 scores 12.1 % on the first 26, 11.5 % on the first 40 and 13.0 % on the first 13 against 10.8 % on all 57. Means over different item counts are therefore not comparable.
  • Synthetic audio. The dictation and visit sets are macOS text-to-speech voices, not clinicians: clean, evenly paced, six voices, with no overlap, noise or distance. They exercise vocabulary, numbers, negation and speaker turns; they do not predict real-room WER. PriMock57 is the only real-voice set, and it is actors in a studio with UK accents, mixed from two close microphones.
  • The audio is not committed (macOS SLA §2F); the text corpus, scripts and result files are to be.
  • No physical iPhone. FluidAudio Parakeet, Sortformer, Gemma 4 through llama.rn and OpenMed through onnxruntime-react-native have not been run on a device by us; the device decision rules in §6 exist for that reason.
  • The 0.17.7 smoke is two items. Vocabulary boosting, Parakeet Ultra and Nemotron-3 were measured on dict-01 and visit-01 only, with a vocabulary list that overlaps the corpus.
  • Two diarization runs are manual. The Sortformer and pyannote runs on visit-01 were made by hand on 2026-10-11 and are not in the logged results.

5. Results

All tables: 2026-10-11, Apple M5 Pro 48 GB, macOS 26.5.2. “+lex” is the same hypotheses after the tightened RxNorm pass. RTF is the median over items on the basis §4.6 gives per engine; RSS is the maximum peak over items, in MiB; “-” means the engine does not emit that signal.

5.1 PriMock57 (57 mock consultations, two channels summed, real actors’ voices)

PriMock57, every engine run on it
engineitemsmean WERmedian WERcoverageoutside speechloopsmedian RTF (recorded basis)median RTF (wall)peak RSS MiB
fluidaudio-parakeet-v3 (Core ML, FluidAudio 0.15.5)5710.8 %10.4 %99.5 %0.3 %00.014 (processing)0.016255
fluidaudio-parakeet-v3+lex5710.8 %10.4 %99.5 %0.3 %00.0140.016255
fluidaudio-parakeet-v25711.5 %11.5 %99.6 %0.3 %00.005 (processing)0.006199
fluidaudio-parakeet-v2+lex5711.5 %11.4 %99.6 %0.3 %00.0050.006199
fluidaudio17-parakeet-v3 (FluidAudio 0.17.7)13 (first 13)11.9 %12.2 %99.8 %0.2 %00.046 (processing)0.053232
fluidaudio17-parakeet-v3+lex1311.8 %12.2 %99.8 %0.2 %00.0460.053232
whisper-small.en5712.8 %13.0 %99.6 %0.8 %00.026 (wall)0.026996
whisper-small.en+lex5712.8 %12.8 %99.6 %0.8 %00.0260.026996
parakeet-tdt-v3-q4_0 (GGUF, the app’s current file)26 (first 26)13.8 %14.0 %98.9 %0.2 %00.055 (wall)0.0551,210
parakeet-tdt-v3-q4_0+lex2613.7 %14.0 %98.9 %0.2 %00.0550.0551,210
whisper-medium.en26 (first 26)13.9 %13.4 %99.7 %0.5 %00.077 (wall)0.0772,313
whisper-medium.en+lex2613.9 %13.4 %99.7 %0.5 %00.0770.0772,313
whisper-base.en5715.2 %14.8 %99.7 %0.9 %00.016 (wall)0.016537
whisper-base.en+lex5715.1 %14.8 %99.7 %0.9 %00.0160.016537
talk-whisperkit-large-v3-turbo (632 MB)40 (first 40)15.6 %14.7 %--00.113 (processing, incl. load)0.123449
talk-whisperkit-large-v3-turbo+lex4015.6 %14.5 %--00.1130.123449
whisper-tiny.en5718.6 %18.8 %99.6 %0.9 %00.014 (wall)0.014420
whisper-tiny.en+lex5718.6 %18.8 %99.6 %0.9 %00.0140.014420

Matched-file comparison. Because every partial subset is the first N files alphabetically and those files are harder (§4.9), each partial engine is compared with the Core ML v3 on exactly the files it completed:

Each engine against the Core ML v3 on the same files
enginefilesengine mean WERCore ML v3 on the same filesdifferenceengine better on
parakeet-tdt-v3-q4_0 (GGUF)first 2613.8 %12.1 %+1.77 / 26
whisper-medium.enfirst 2613.9 %12.1 %+1.87 / 26
whisper-small.en, on those 26first 2614.3 %12.1 %+2.2—
talk-whisperkit-large-v3-turbofirst 4015.6 %11.5 %+4.14 / 40
fluidaudio17-parakeet-v3 (0.17.7)first 1311.9 %13.0 %−1.111 / 13
whisper-small.enall 5712.8 %10.8 %+2.07 / 57
whisper-base.enall 5715.2 %10.8 %+4.30 / 57
whisper-tiny.enall 5718.6 %10.8 %+7.80 / 57

v3 against v2, paired per file: v3 was better on 43 of the 57 files, tied on one and worse on 13; mean difference 0.71 points in v3’s favour (standard error 0.15; bootstrap 95 % interval 0.4–1.0 points). Spread: v3’s per-file WER ran from 4.9 % to 19.3 % (median 10.4 %, standard deviation 3.1 points), so a difference of under a point between two engines is only meaningful paired, never from the means alone.

Reading: the Core ML Parakeet v3 is the most accurate engine among those run on all 57 real-conversation files, at the lowest memory of any engine run on all 57 files apart from its own v2 predecessor (199 MiB). On matched files the app’s current GGUF q4_0 path is 1.7 points worse (not the 3 points the unmatched means suggest), at about 3.4 times the wall-time RTF and five times the memory; Whisper medium is 1.8 points worse on the same 26 files and on those files it beats Whisper small (13.9 % vs 14.3 %) — the opposite of the unmatched table’s order; WhisperKit turbo is 4.1 points worse on its 40. FluidAudio 0.17.7’s build of the same v3 weights was 1.1 points better than 0.15.5’s on the 13 files it completed (better on 11 of 13). On speed, on the one basis the log supports for every engine (wall time), v2 is the fastest (0.006), then whisper tiny (0.014), whisper base and the Core ML v3 (0.016 each), whisper small (0.026), 0.17.7 (0.053), the GGUF (0.055), medium (0.077) and WhisperKit (0.123). No engine produced a repeat loop on any of the 57 files. The lexicon pass changes PriMock57 WER by 0.0–0.1 points: on the v3 hypotheses it made 45 rewrites across the 57 files at 0.0 points of WER, mostly genuine mis-spellings (“ventilin” → ventolin, “neproxin” → naproxen) with a few wrong snaps (“flatmate” → flutamide, “cetraben” → cytarabine, “statin” → nystatin, “locartin” → alcortin), too few to move WER at one decimal over ~75,000 words.

5.2 Synthetic dictation (25 items, one TTS voice each)

Synthetic dictation
engineitemsmean WERmedian WERterm recallnegation keptloopsmedian RTFpeak RSS MiB
sherpa-parakeet-v2+lex (int8 ONNX)253.1 %2.6 %93.3 %91.0 %00.041 (wall)2,270
whisper-medium.en+lex253.3 %3.8 %90.3 %94.7 %00.094 (wall)2,188
whisper-medium.en253.9 %4.0 %87.4 %94.7 %00.094 (wall)2,188
talk-whisperkit-large-v3-turbo+lex253.9 %2.6 %96.8 %93.0 %00.114304
parakeet-tdt-v3-q4_0+lex254.0 %2.7 %91.0 %90.3 %00.046 (wall)460
fluidaudio17-parakeet-v3+lex254.1 %4.0 %89.5 %91.3 %00.031206
fluidaudio-parakeet-v3+lex254.2 %4.0 %91.6 %91.3 %00.015 (processing; 0.027 wall)210
fluidaudio-parakeet-v2+lex254.2 %3.8 %91.6 %92.3 %00.010 (processing; 0.023 wall)135
whisper-small.en+lex254.3 %3.3 %88.3 %93.0 %00.040 (wall)883
sherpa-parakeet-v2254.9 %4.0 %81.0 %91.0 %00.041 (wall)2,270
talk-whisperkit-large-v3-turbo255.1 %4.0 %87.5 %93.0 %00.114304
whisper-small.en255.2 %4.0 %83.8 %93.0 %00.040 (wall)883
fluidaudio-parakeet-v2255.8 %5.3 %79.8 %92.3 %00.010135
fluidaudio17-parakeet-v3255.9 %5.3 %76.8 %91.3 %00.031206
parakeet-tdt-v3-q4_0255.9 %5.3 %77.7 %90.3 %00.046 (wall)460
fluidaudio-parakeet-v3256.2 %5.4 %77.3 %91.3 %00.015210
whisper-base.en+lex257.4 %6.2 %88.0 %88.7 %00.039 (wall)409
whisper-base.en259.3 %9.2 %74.5 %88.7 %00.039 (wall)409
whisper-tiny.en+lex2511.3 %8.5 %82.2 %82.8 %00.014 (wall)298
whisper-tiny.en2512.9 %10.0 %71.5 %82.8 %00.014 (wall)298

Reading: on clean single-voice dictation the ranking is different from PriMock57 — Whisper medium and the int8 Parakeet v2 are 1.3–2.3 points better than the Core ML v3 before the lexicon pass (medium 3.9 % and sherpa v2 4.9 % against 6.2 %) — but the memory and time differences are an order of magnitude (2.2 GB and 0.094 wall-time RTF for medium against 210 MiB and 0.027 wall / 0.015 processing), the gap closes to about a point after the lexicon pass (0.9 for medium at 3.3 % vs 4.2 %, 1.1 for the int8 v2 at 3.1 % vs 4.2 %), and Whisper medium loses on real conversation (§5.1). The int8 Parakeet v2 was not run on PriMock57, so nothing here says whether it loses there. Negation is kept at 90–95 % by every engine except tiny (82.8 %) and base (88.7 %), and the lexicon pass never changes it. Term recall is where the engines differ — 71.5 % to 87.5 % before the pass, 82.2 % to 96.8 % after it — and every engine’s misses are medication names.

The lexicon pass, before and after (same runs, 2026-10-11 23:44; in-sample, §4.7)
enginedictation WERdictation term recallPriMock57 WERnegation kept
fluidaudio-parakeet-v36.2 % → 4.2 %77.3 % → 91.6 %10.8 % → 10.8 %91.3 % → 91.3 %
fluidaudio-parakeet-v25.8 % → 4.2 %79.8 % → 91.6 %11.5 % → 11.5 %92.3 % → 92.3 %
fluidaudio17-parakeet-v35.9 % → 4.1 %76.8 % → 89.5 %11.9 % → 11.8 % (13 items)91.3 % → 91.3 %
parakeet-tdt-v3-q4_05.9 % → 4.0 %77.7 % → 91.0 %13.8 % → 13.7 % (26 items)90.3 % → 90.3 %
sherpa-parakeet-v24.9 % → 3.1 %81.0 % → 93.3 %not run on PriMock5791.0 % → 91.0 %
talk-whisperkit-large-v3-turbo5.1 % → 3.9 %87.5 % → 96.8 %15.6 % → 15.6 % (40 items)93.0 % → 93.0 %
whisper-medium.en3.9 % → 3.3 %87.4 % → 90.3 %13.9 % → 13.9 % (26 items)94.7 % → 94.7 %
whisper-small.en5.2 % → 4.3 %83.8 % → 88.3 %12.8 % → 12.8 %93.0 % → 93.0 %
whisper-base.en9.3 % → 7.4 %74.5 % → 88.0 %15.2 % → 15.1 %88.7 % → 88.7 %
whisper-tiny.en12.9 % → 11.3 %71.5 % → 82.2 %18.6 % → 18.6 %82.8 % → 82.8 %

The rewrite log shows what the pass does: for the Core ML v3 it made 185 rewrites over 100 distinct forms, the most frequent “ventilin” → ventolin (7), “levothyroxin” → levothyroxine (6), “furosimide” → furosemide (6), “neproxin” → naproxen, “duloxidine” → duloxetine, “ferrosulfate” → ferrous sulfate, “a pixabin” → apixaban, “shiotropium” → tiotropium, “butesinide” → budesonide, “moxicillin” → amoxicillin, “montelucast” → montelukast, “trimethoprin” → trimethoprim (3 each). The pass is not perfect: on the v3 output itself it snapped “flatmate” → flutamide, “cetraben” → cytarabine (Cetraben is a UK emollient brand), “statin” → nystatin (twice), “locartin” → alcortin (three times), “cetroline” / “cetraline” → citrulline, “depine” → depen, “clindus” → clindesse and “pyritin” → pyrithione — a dozen or so of its 185 rewrites. That is why the design runs it on a copy, shows every substitution to the clinician as revertible, and adds the unmatched near-misses to a verification strip rather than silently rewriting them (§6.2).

First smoke item before the tightening (dict-01-htn-dm, 47 s, 2026-10-11): every engine missed lisinopril, “atorvastatin forty milligrams” and “empagliflozin ten milligrams”; after the first lexicon pass the GGUF Parakeet q4_0 went 3.8 % → 0.0 % WER and 72.7 % → 100 % term recall, Talk’s Core ML Parakeet 6.7 % → 0.0 %, Apple SpeechAnalyzer 5.7 % → 0.9 % (19 MiB in-process), whisper-base 6.7 % → 2.9 %; whisper small.en-tdrz lost the negation “denies shortness of breath” (66.7 % negation kept) with or without the pass.

5.3 Synthetic visits and long-form

Synthetic visits (6 items, two or three TTS voices; RTF on the recorded basis of §4.6)
enginemean WERterm recallnegation keptmedian RTFpeak RSS MiB
sherpa-parakeet-v2+lex0.9 %91.4 %100.0 %0.0582,467
whisper-small.en+lex1.1 %93.4 %100.0 %0.049919
talk-whisperkit-large-v3-turbo+lex1.2 %96.5 %100.0 %0.100311
fluidaudio17-parakeet-v3+lex1.3 %90.9 %100.0 %0.029217
whisper-medium.en+lex1.5 %94.8 %94.4 %0.0652,204
fluidaudio-parakeet-v3+lex1.6 %93.4 %94.4 %0.012224
sherpa-parakeet-v21.6 %80.9 %100.0 %0.0582,467
talk-whisperkit-large-v3-turbo1.7 %89.1 %100.0 %0.100311
whisper-medium.en1.7 %91.4 %94.4 %0.0652,204
fluidaudio-parakeet-v2+lex1.8 %89.9 %94.4 %0.007149
whisper-small.en1.8 %85.0 %100.0 %0.049919
fluidaudio17-parakeet-v32.4 %75.7 %100.0 %0.029217
fluidaudio-parakeet-v22.6 %81.1 %94.4 %0.007149
fluidaudio-parakeet-v32.7 %76.9 %94.4 %0.012224
whisper-base.en+lex2.9 %86.5 %100.0 %0.043415
parakeet-tdt-v3-q4_0+lex3.0 %85.3 %100.0 %0.040509
parakeet-tdt-v3-q4_03.8 %69.7 %100.0 %0.040509
whisper-base.en3.8 %75.2 %100.0 %0.043415
whisper-tiny.en4.2 %69.2 %100.0 %0.012339
Long-form: long-form-dictations (16.8 min, one voice) and long-form-all (28.5 min, up to three voices). Only the FluidAudio engines and the GGUF ran both files; an engine marked “1 (dictations)” ran the dictations file only, so its row is that one file and is not comparable with a two-file mean.
engineitemsmean WERterm recallnegation keptoutside speechloopsmedian RTFpeak RSS MiB
whisper-medium.en+lex1 (dictations)3.3 %92.2 %96.2 %2.4 %00.0572,340
fluidaudio-parakeet-v2+lex23.3 %92.4 %93.3 %1.8 %00.007262
fluidaudio-parakeet-v3+lex23.5 %91.7 %94.4 %1.8 %00.008299
whisper-medium.en1 (dictations)3.9 %88.5 %96.2 %2.4 %00.0572,340
fluidaudio17-parakeet-v3+lex24.2 %90.3 %93.3 %2.0 %00.024292
fluidaudio-parakeet-v224.7 %81.6 %93.3 %1.8 %00.007262
parakeet-tdt-v3-q4_0+lex24.7 %91.3 %92.7 %1.6 %00.030827
fluidaudio-parakeet-v325.2 %77.6 %94.4 %1.8 %00.008299
parakeet-tdt-v3-q4_026.2 %77.3 %92.7 %1.6 %00.030827
whisper-base.en1 (dictations)8.3 %78.0 %87.2 %0.4 %00.022454
whisper-small.en1 (dictations)8.8 %81.7 %88.5 %2.4 %00.027995
talk-whisperkit-large-v3-turbo+lex1 (dictations)9.3 %90.0 %84.6 %-00.103370
talk-whisperkit-large-v3-turbo1 (dictations)10.2 %83.2 %84.6 %-00.103370
whisper-tiny.en1 (dictations)12.0 %74.4 %85.9 %2.4 %00.012436

On the 16.8-minute dictations file alone, where every engine has a row: whisper medium 3.9 % (3.3 % +lex), Parakeet v2 Core ML 5.3 % (3.8 %), Parakeet v3 Core ML 5.7 % (3.9 %), 0.17.7’s v3 6.6 % (5.0 %), the GGUF 7.7 % (6.1 %), whisper base 8.3 %, whisper small 8.8 %, WhisperKit turbo 10.2 % (9.3 %), whisper tiny 12.0 %. On the 28.5-minute file the Core ML v3 scored 4.6 % (3.1 % +lex), v2 4.0 % (2.8 %), the GGUF 4.6 % (3.2 %), 0.17.7 4.9 % (3.4 %); WhisperKit and the whisper.cpp models were never run on it.

Reading: on the long files the Core ML Parakeet’s memory stays flat (299 MiB on 28.5 minutes against 210–224 MiB on one-minute items). The mechanism is in the code the harness exercised: fluid-cli calls AsrManager.transcribe(_ url:), which for any file over 30 s takes a disk-backed path — a memory-mapped source read in 15 s windows with 2 s overlap. The GGUF path’s memory grows with duration (827 MiB). Nothing looped. Whisper small and base degrade on the long dictations file (8.8 % and 8.3 %) where they were at 1.8 % and 3.8 % on the short visits and 5.2 % and 9.3 % on the short dictations; WhisperKit turbo drops to 84.6 % negation kept on it. sherpa-onnx’s Parakeet export crashed on the 16-minute file and had to be windowed, and its long-form rows are not in the committed results.

5.4 Vocabulary boosting, Parakeet Ultra and diarization (smoke runs)

FluidAudio 0.17.7’s CLI, two items only (2026-10-11 23:42), --custom-vocab with the 100-name list that overlaps the corpus (§4.4)
engineitemWERterm recallnegation keptspeaker accmedian RTFpeak RSS MiB
fluidaudio17-parakeet-v3dict-019.5 %63.6 %100 %-0.031198
fluidaudio17-parakeet-v3+vocabdict-015.7 %81.8 %100 %-0.024213
fluidaudio17-parakeet-ultradict-018.6 %63.6 %100 %-0.027190
fluidaudio17-parakeet-ultra+vocabdict-012.9 %90.9 %100 %-0.028193
fluidaudio17-parakeet-ultra+nemotron3dict-018.6 %63.6 %100 %100 %0.177338
fluidaudio17-parakeet-v3visit-013.0 %60.0 %100 %-0.018216
fluidaudio17-parakeet-v3+vocabvisit-012.3 %100 %66.7 %-0.031218
fluidaudio17-parakeet-ultravisit-013.0 %60.0 %100 %-0.021199
fluidaudio17-parakeet-ultra+vocabvisit-012.6 %100 %66.7 %-0.019212
fluidaudio17-parakeet-ultra+nemotron3visit-013.0 %60.0 %100 %100 %0.170248

Reading: boosting lifts term recall from 60–64 % to 82–100 % on these two items and Ultra with boosting reaches 2.9 % on the dictation — but the list contains most of the corpus’s drugs, so this is the lift of a vocabulary that already knows the answers; on the visit both boosted runs lost the negation “no chest pain” (100 % → 66.7 %). The cost is unresolved: a working note recorded “3–5× host processing time” with the vocabulary on the two smoke items, while the RTF column above, under host load, does not show it. Boosting also requires a CTC model (parakeet-ctc-110m-coreml, 98 MiB) the Scribed hub does not serve, and FluidAudio ≥ 0.17. It is therefore a v1.1 experiment behind a numeric gate (§6.2), not a v1 feature; the lexicon pass, which needs no model and never touched a negation, is what ships.

Diarization (2026-10-11). Rows marked manual were run by hand on the day and are not in the committed scores or run log; only the last two rows went through the harness.
runaudiospeaker accuracyRTFpeak RSS MiBrecorded?
FluidAudio 0.15.5 Parakeet v3 + Sortformer v2.1 (fluid-cli diarize --engine sortformer)visit-01, 109 s, two voices100 % (turns at the right utterances)--manual, unlogged
FluidAudio 0.15.5 Parakeet v3 + pyannote community-1 offline pipeline (--engine pyannote)visit-0150 % (collapsed to one speaker)--manual, unlogged
FluidAudio 0.17.7 Parakeet Ultra + Nemotron-3visit-01100 %0.170248smoke summary only
fluidaudio-parakeet-v3+sortformer over 21 synthetic dictations (single speaker — a sanity run, not a diarization test)21 items100 %0.189506in the scores
whisper small.en-tdrz (turn flags only)dict-01100 % (one speaker)0.114858smoke summary only

The choice between Sortformer v2.1 and pyannote community-1 therefore rests, today, on one 109-second two-voice synthetic item run by hand. That is not enough to drop a pipeline FluidAudio’s own benchmark scores at 10.6 % DER, and it is not enough to trust the kept one either; both are run through the harness on the six synthetic visits and the 57 PriMock57 files before the gate is applied (8.6 h of audio at RTF ≈ 0.19 is under two hours of machine time). No PriMock57 DER has been computed yet. For scale, the literature’s clinical-conversation DER for off-the-shelf diarizers is 21–40 % (pyannote 3.1: 22.7 % on ten real home-care visits; 31.5 % clinical vs 19.0 % general on AfriSpeech-Dialog), well above the meeting benchmarks vendors quote.

5.5 The identifier-flagging layers

30 synthetic cases, 65 patient-identifier spans, 4 provider-name spans, 40 decoys (2026-10-11 23:03). With 65 spans a recall figure carries roughly ±11 points at 95 % confidence; the Wilson intervals are given:

Identifier-flagging layers on the 30-case synthetic corpus
layerpatient-identifier recall (95 % CI)provider-name recalldecoy false positivesspans missed
current rules (lib/privacy/candidate.ts)13.8 % (9/65)0 %0 % (0/40)56
rules + known patient tokens16.9 % (11/65)0 %0 %54
Apple NLTagger (names / places / organisations)32.3 % (21/65)100 %20.0 % (8/40)44
OpenMed PII 44M, fp16 ONNX70.8 % (46/65; 59–80 %)100 %12.5 % (5/40; 5–26 %)19
OpenMed PII 44M, int8 ONNX67.7 % (44/65)100 %12.5 %21
rules + known + OpenMed fp16 — the layers that ship, as they exist today72.3 % (47/65; 60–82 %)100 %12.5 %18
rules + known + NLTagger + OpenMed fp16 (the recorded union, incl. the dropped NLTagger)75.4 % (49/65; 64–84 %)100 %32.5 % (13/40)16

Recall by category for the OpenMed fp16 layer: names 13/13, relatives’ names 7/8, provider names 4/4, dates 9/13, phones 4/4, email 1/1, addresses 3/4, small locations 6/7, SSN 1/1, employers 1/3, record numbers 1/4, ages over 89 0/2, insurance ids 0/2, vehicle 0/1, device id 0/1, URL 0/1. The five decoy false positives are four year-only dates spoken as words (“twenty twenty seven”, “nineteen sixty four”, “twenty fifteen”, “twenty sixteen”) and the age “eighty nine”. The design treats a year alone and an age under 90 as non-identifiers, following the Safe Harbor enumeration as a vocabulary, and its clinical allow-list keeps them; that is a design choice, not a determination that the output meets any de-identification standard — the list’s catch-all element and the combination risk (a year plus a rare diagnosis plus a small town can identify a person) are exactly what the per-span clinician review before a share is meant to catch. The misses are spoken letter-and-digit strings (a record number “four four seven one nine zero two”, an insurance id, a device serial, a licence plate), ages over 89 in words (“ninety two”), relative dates (“last March”, “the fourteenth”), a Spanish date and name, and a hospital name — the cases the designed spoken-number rules layer and known-patient fuzzy match are meant to cover, which is why the design is layered rather than model-only; those layers are not built, so the shipped stack’s end-to-end recall is unmeasured, and the capability is best described as identifier flagging for review, not identifier removal. Speed on the Mac CPU: median 118 ms per ~40-word passage for int8, 250 ms for fp16.

5.6 External cross-check against the preprint

The preprint’s Table 1 (standard WER, Whisper normaliser alone; every on-device model as its published int8 build through sherpa-onnx on CPU) against our PriMock57 run — not digit-for-digit comparable, but the order should agree
modelpreprint PriMock57 Std WER (2026-10-05, Table 1)our PriMock57 mean WER (2026-10-11)
Parakeet-TDT-0.6b-v213.85 % (int8 ONNX)11.5 % (Core ML, 57/57); int8 ONNX not run
Parakeet-TDT-0.6b-v3not in the preprint10.8 % (Core ML, 57/57); 13.8 % (GGUF q4_0, 26/57 — 12.1 % for Core ML on those 26)
Whisper distil-v3.514.19 %not run on PriMock57
Whisper turbo15.53 %15.6 % (WhisperKit 632 MB, 40/57)
Qwen3-ASR 0.6B14.74 %not run
Whisper base.en20.27 %15.2 % (whisper.cpp, 57/57)
MedASR88.83 %excluded (§3.3)
AWS Transcribe Medical (cloud)8.68 %—

The order agrees: Parakeet first, Whisper turbo behind it, base.en behind that. Our base.en is 5 points better than the preprint’s, plausibly because of full precision and filler stripping; our Parakeet v2 is 2.3 points better than their int8 ONNX run of the same weights. The medication finding also agrees: the preprint’s pooled on-device error on lisinopril (92 %) and amoxicillin (93 %) on PriMock57 and 83–100 % for Parakeet v2 on its highest-error drugs matches what every one of our engines did on the synthetic sets. Omi’s medical-STT board gives the same picture from a third direction: Parakeet v3 at 5.90 % medical WER but 20.6 % drug-name error. The preprint’s two conclusions — a single model suffices, and explicit medication verification before note finalization is required regardless of model — are what the design adopts.

5.7 Speed and memory on this host, for the record

Speed and memory on the M5 Pro host
engineRTF (single items, host loaded; basis per §4.6)peak RSSrecorded?
FluidAudio Parakeet v3 Core ML (.cpuAndNeuralEngine requested; scheduling not verified on this host)0.006–0.014 processing (0.016–0.027 wall)125–255 MiB (299 MiB on 28.5 min)yes
FluidAudio Parakeet v2 Core ML0.005–0.010 processing (0.006–0.023 wall)135–262 MiByes
Parakeet GGUF q4_0 (whisper.cpp, CPU/Metal)0.017–0.055 wall460 MiB (827–1,210 MiB on long files / PriMock57)yes
whisper-base.en0.016–0.043 wall396–537 MiByes
whisper-small.en0.026–0.049 wall883–996 MiByes
whisper-medium.en0.057–0.094 wall2.2–2.3 GiByes
Apple SpeechAnalyzer (macOS 26)~0.03619 MiB in-process (model in a system daemon)smoke summary, one item
WhisperKit large-v3-turbo 632 MB (via Talk, incl. load)0.100–0.114304–449 MiByes
sherpa-onnx Parakeet v2 int8 (Python)0.041–0.058 wall2.2–2.4 GiByes (synthetic)
sherpa-onnx Whisper distil-large-v3.5 int8 (Python)~0.25~2.5 GiBnotebook figure, not logged — no row in any committed file
OpenMed PII 44M (onnxruntime CPU)118 ms int8 / 250 ms fp16 per ~40 words—yes

Whether Core ML actually scheduled the Parakeet encoder on the M5 Pro’s Neural Engine was not verified (no compute-plan report or powermetrics capture was taken); the request is in the code, the low RSS and processing time are consistent with it, and that is all this report claims.

5.8 The 2026-10-02 Simulator comparison of assistants

Twelve identical short fictional cases per model through native llama.rn 0.12.9 in Hermes on the iOS 26.5 Simulator (iPhone 17 Pro profile, CPU, two threads, 2,048-token context, 384-token cap, temperature 0.2, seed 42) — Gemma 4 E2B QAT Q4_0, Liquid LFM2.5-1.2B QAD Q4_0 and Qwen3.5 2B Q6_K. E2B passed 12 of 12 structurally (median 35 s per case, 3.7 GB sampled Simulator RSS) and, in the provisional review, “preserved the principal source facts” and ignored an injected instruction, with incomplete exact-quote coverage; Liquid (11/12, 18 s, 1.1 GB) made material errors in family history, conflicting history and temporal coverage and followed the injected instruction; Qwen (9/12, 33 s, 2.0 GB) inferred absent insulin and unmeasured vital signs from missing information and reversed an appendix removal into a denial. Its limitations are its own: a single sequential run, no confidence intervals, short authored text (no audio, no long encounter), Simulator RSS sampled once a second, and an agent’s review, not independent human or clinician adjudication. The review’s decision line: “Prioritize Gemma 4 E2B for further documentation evaluation. No production default or clinical-quality approval.” It is the only quality evidence the team holds on the assistant.

6. The decision and its rules

The design of 2026-10-12 writes each choice down with the rule that would reverse it. The verbs matter: everything in this section is what the design chooses and will build; the table says which parts have a measurement behind them.

Measured on 2026-10-11 (Mac unless stated) versus designed on 2026-10-12, unmeasured
part of the designmeasured on 2026-10-11designed on 2026-10-12, unmeasured
Parakeet v3 Core ML through FluidAudio 0.15.5PriMock57 / synthetic WER, term recall, negation, loops, host RTF and RSS; the 30 s disk-backed window (code read)the vendored pods compiling in Care’s Pods graph; Neural Engine scheduling and phys_footprint on any iPhone; the ≤ 3 min / ≤ 3 s processing-time rule
the lexicon passthe Python rules’ WER cost and recall gain (in-sample)the TypeScript port, the near-miss list, the short-brand rule, the verification strip, held-out acceptance
Sortformer v2.1one manual two-voice item; a 21-dictation single-speaker sanity runDER on PriMock57, speaker accuracy on the six visits through the harness, the iPhone 15 memory rule, the whole diarizer path in the app
Gemma 4 E2Bthe 2026-10-02 twelve-case Simulator comparison (text only)footprint on an entitled build, n_ctx 8192 on Metal, the map → reduce pass, the completeness check, the omission gate on long fixtures
identifier flaggingOpenMed fp16 / int8 and the existing rules on the 30-case corpus; the shipped-layer union as the layers exist todayspoken-number normalisation, the extended rules, the known-patient fuzzy match, the allow-list, the fp16-weight / float32-I/O re-export, session.run on an iPhone
the two hardware tiersnothing on a devicethe floors, the readiness card, the three tier rules

6.1 Speech-to-text: Parakeet TDT 0.6B v3 Core ML through FluidAudio 0.15.5, one binary for iPhone and Mac

The artifact is FluidInference/parakeet-tdt-0.6b-v3-coreml at revision 7dd20fe6b1797d35f5e3307e8b1732d9a178edfe, 21 files, 483,105,645 bytes, already pinned in the Scribed model hub and served with per-file size and digest verification; encoder precision int8. The design vendors FluidAudio 0.15.5’s sources into the app’s native module as three CocoaPods with a byte-exact mirror test against the tag; the only permitted edit is a logging switch, and a host test asserts no print / os_log / NSLog call survives outside the logger. Long audio goes through FluidAudio’s disk-backed path at constant memory — one chunk at a time on iPhone and two on the Mac; the app’s own 90 s cap and the whole whisper.rn / GGML path are deleted. The Mac runs the unmodified iOS binary on Apple silicon, so Core ML is asked to schedule the same model on the Mac’s Neural Engine; whether it does, on either platform, is unverified.

v2 vs v3 is closed: 10.8 % vs 11.5 % on PriMock57 (57/57), v3 better on 43 of 57 files with one tie (mean +0.71 points, SE 0.15, bootstrap 95 % interval 0.4–1.0), and v2’s small edge on synthetic dictation (5.8 % vs 6.2 %) is inside the lexicon gain (both 4.2 % after it); the hub pins v3.

The written fallback to WhisperKit. Outcome A (Parakeet v3) stays unless WhisperKit large-v3-turbo, after the lexicon pass, beats it by more than 2 points of WER on BOTH PriMock57 and the synthetic dictation set, AND by more than 3 points of term recall on the synthetic dictation set, AND shows zero repeat loops on the 28.5-minute file. Evidence on 2026-10-11: turbo 15.6 % vs 10.8 % on PriMock57 (worse — 15.6 % vs 11.5 % on its 40 matched files), 3.9 % vs 4.2 % on synthetic dictation after the pass (better, by 0.3 points, under the 2-point bar); 96.8 % vs 91.6 % term recall on dictation after the pass (better by 5.2 points); the 28.5-minute file has not been run through WhisperKit — one of three conditions, so outcome A. The rule was written after the numbers it is first tested against, so its first application is not blind; the next dataset on which it will be applied blind is the held-out half of PriMock57 and the next synthetic corpus.

Processing-time rule (the first device task): a 30-minute visit transcribes and diarizes in ≤ 3 minutes on an iPhone 15 Pro; a 90 s dictation shows first text ≤ 3 s after Stop with the engine resident.

6.2 Drug names: the lexicon layer and the medication-verification strip

The design ports the tightened rules to TypeScript over a build-time RxNorm Prescribable Content table (≈ 7,600 names with Metaphone keys, ≈ 300 KB, release pinned in the file name) plus an English guard. It runs on a copy of the raw transcript and writes a lexicon layer with every substitution and every near-miss it did not substitute (0.80 ≤ Jaro-Winkler < 0.90, or an out-of-vocabulary token within three words of a dose or frequency); each substitution renders with a dotted underline and “corrected from …” and is revertible; the raw record is never modified. One addition over the measured rules: a 4–5-letter brand (Lasix, Coreg, Zocor, Paxil, Cipro, Xanax, Norco, Advil, Aleve, Mobic, Imdur, Actos, Soma) is admitted only under dose context; those items join the corpus and the PriMock57 cost row is re-run before that rule is accepted.

The medication-verification strip has three classes: substituted (the pass changed it), recognised (an exact RxNorm match left alone), and unrecognised near a dose (the near-miss list — “empoglyflasin 10 mg daily” at Jaro-Winkler 0.86 to empagliflozin); every entry shows its surrounding text with Accept / Fix; at Finalize the strip is reconciled against the note — a drug in the note but not the transcript is flagged unsupported, a drug in the transcript but not the note is flagged omitted.

Acceptance, held out. The measured figures (91.6 % term recall, 0.0 points of WER cost for the Core ML v3) were tuned on the same files and do not count. The rules are frozen as of 2026-10-11 23:44; the synthetic dictation set and the odd-numbered PriMock57 consultations are the tuning half, and the acceptance is measured once, blind, on the even-numbered half and on the next synthetic corpus: term recall ≥ 90 % on the synthetic set with ≤ 1 collateral edit per 1,000 words, and ≤ 0.2 points of WER cost on the held-out PriMock57 half; the strip’s unrecognised class catches every unmatched mis-spelling on the six synthetic visits.

The v1.1 gate for vocabulary boosting: adopt only if (a) the hub gains parakeet-ctc-110m-coreml (98 MiB), (b) with a vocabulary list rebuilt from an independent source with the corpus’s drugs excluded, boosting adds ≥ 2 points of term recall on PriMock57 + synthetic after the lexicon pass without raising WER and without losing a negation, and (c) an iPhone 15 Pro’s wall time for a 30-minute visit stays ≤ 2× the unboosted run.

6.3 Speaker labels: Sortformer v2.1, offline, gated

ASR runs first (per capture part), then the diarizer runs over the whole visit’s audio; each word takes the speaker slot with the most activity over its span; labels in the raw record are Speaker 1…4 in arrival order; the clinician assigns roles per visit with chips (Clinician / Patient / Family / Interpreter / Other) and can change, split or merge per segment, all stored in the edit layer, never in the raw record. Visits only, never dictation. No voice enrolment in v1.

The DER gate. Labels ship in v1 only if, on PriMock57 mixed-channel audio with the TextGrid gold, DER (collar 0.25 s, overlap ignored) ≤ 20 % AND speaker accuracy on the six synthetic visits ≥ 90 % AND the iPhone 15 memory rule holds. The design spec writes that memory rule twice with two numbers — phys_footprint < 1.8 GB in the DER rule and ≤ 2.2 GB in the transcribe-tier rule, for the same iPhone 15, 60-minute, ASR-then-diarization run; this report reads 1.8 GB as the diarization step’s own peak and 2.2 GB as the whole run’s peak, and the spec must say so before either number is applied (§7, item 14). Otherwise v1 ships without automatic labels, the diarizer bundle is not downloaded, the per-segment chips remain for hand labelling, and the copy is identical (“Speaker labels are automatic and unverified”).

6.4 The assistant: Gemma 4 E2B, the one model

gemma4-e2b-qat-q4 (3,349,516,256 B, Apache-2.0) through llama.rn 0.12.9. Not E4B: 4.2–5.2 GB on disk, the only iPhone figures are LiteRT’s (3,380 MB GPU, a different artifact), and a GGUF load reportedly needs the entitlement. The speech engine and the assistant are never resident together: the speech engine (Parakeet int8 + Sortformer palettized, < 1.5 GB by proxy) stays resident for the whole unlock session and is released only immediately before the assistant loads, and it is re-warmed in the background after the assistant is released. Budget: speech phase < 2 GB; assistant phase ≈ 3.35 GB memory-mapped + KV cache (< 4.5 GB at n_ctx 8192) with the increased-memory-limit and extended-virtual-addressing entitlements pinned for every build — today the llama.rn plugin adds them only under a production profile the app’s prebuild never sets, so every memory figure so far was taken un-entitled.

Long visits. n_ctx 8192 (n_batch 256, n_predict 768, temperature 0.2) is to be measured on Metal on an iPhone 15 Pro; a 25-minute transcript fits in one pass; above it a map → reduce pass (per-section structured extraction of problems, medications with dose / change / negation, allergies and plan items citing source spans, then one synthesis from the extractions) and a deterministic completeness check: every RxNorm term and every negated clause in the transcript must appear in the note or in a “Not carried into the note” list the strip shows as omitted. Release gate: the omission rate on allergies and medications on 30–40-minute fixtures through the native runner on a phone must not exceed the clinician reviewer’s threshold; E4B with a 12 GB floor only if E2B fails it.

6.5 Identifier flagging for review: five layers with OpenMed as the model layer

The design is: spoken-number normalisation → rules (phone, fax, email, SSN, MRN, ZIP, addresses, dates, ages ≥ 90) → known-patient fuzzy match → OpenMed-PII-SuperClinical-Small-44M-v1 → a clinical allow-list evaluated last (eponyms, brands, conditions, year-only dates, treated as non-identifiers by design). Of these, only the model layer and the rules as they exist today have been measured; the stack’s end-to-end recall is unknown (the union of the two measured shipping layers is 72.3 %). The shipped ONNX graph is re-exported once with fp16 weights but float32 / int64 inputs and outputs (≈ 283 MB), because onnxruntime-react-native maps a FLOAT16 output to Float16Array, which Hermes on React Native 0.81 does not define. Erratum 2026-10-12: the publisher’s own model_fp16.onnx already declares int64 inputs and a float32 logits output; Scribed Care pins that file unmodified (spec MED-031), so no re-exported file ships and the §9 “modified file” note does not apply. The tokenizer is an own port of the DeBERTa Unigram model over array-shaped assets. Every span is reviewed per span before a share — the review is the safeguard, the layers feed it; the device runs the server’s exact rule set as its final pre-send check; provider names are removed by default with a switch to keep them. The model is optional for offline use and required only for the one share route. Go / no-go: a full session.run on a physical iPhone asserting a float32 logits tensor of the expected shape — not session creation alone; fallback is the ORT C API from Swift; if neither runs, the share ships disabled and nothing else waits.

6.6 Hardware: two tiers, derived from the measurements, provisional until recorded

The two tiers
tierrequirementiPhonesMacswhat runs
unsupportediOS < 17, no Neural Engine, physical memory < 5.5 GiB, Android, Intel8 / X / XS / XR / 11 / SE / 12 / 12 mini / 13 / 13 mini (4 GB)Intelthe unsupported screen; sign-in and “Open Scribed” still work
transcribeiOS 17+, Neural Engine (MLComputeDevice), ≥ 5.5 GiB12 Pro / Pro Max, 13 Pro / Pro Max, 14 family, 15 / 15 Plus (6 GB)—recording, Parakeet, speaker labels, lexicon, patients, raw record, export, sharing, the note editor, dictated notes, the medication strip, Finalize
drafttranscribe + ≥ 7.5 GiB15 Pro / Pro Max, 16 family, 16e, 17, 17e, Air, 17 Pro, 18 Pro (8–12 GB)Apple silicon, macOS 14, ≥ 8 GB (16 recommended)+ Gemma 4 E2B drafting and Ask

Thresholds are 5.5 and 7.5 GiB because 6 GB and 8 GB phones under-report physicalMemory. Derivation: FluidAudio’s iOS 17 / macOS 14 floors; per-app budgets ≈ 2.1 GB (4 GB phones), ≈ 2.9 GB (6 GB — the single sherpa-onnx observation of §2.2, an iPhone 15 killed at 2.9 GB, read as the ceiling; one OOM data point, not a published limit), ≈ 3.3 GB / ≈ 6 GB with the entitlement (8 GB); the speech phase < 2 GB, the assistant phase < 4.5 GB. The device check is a pure function over platform, OS major, Neural Engine presence, Mac-compat flag and physical memory, run before sign-in and again before every model load; each catalog entry carries a minimum available-memory figure (Parakeet 1.5 GiB, Sortformer 0.5 GiB, Gemma 4.5 GiB, OpenMed 0.6 GiB); a failure is a fixed code and never a cloud fallback. The unsupported screen states the measured facts (“This device reports N GB.”) and offers “Open Scribed” and “Sign out”.

The tier rules (device-measured on entitled builds, each over three runs with no jetsam): the transcribe tier keeps 6 GB phones only if a physical iPhone 15 completes a 60-minute visit (ASR, then diarization) with peak phys_footprint ≤ 2.2 GB (the whole-run peak); otherwise its floor rises to 7.5 GiB; 4 GB phones gain a tier later only if a physical iPhone 13 completes the same run with peak ≤ 1.6 GB; the draft tier lowers to 6 GB only if an iPhone 15 drafts a 25-minute transcript with phys_footprint ≤ 2.6 GB without the entitlement.

6.7 What replaces the model picker

A fixed per-tier readiness card on the locked screen: “Speech engine — Parakeet TDT 0.6B v3 · 483 MB”, “Speaker labels — Sortformer v2.1 · 104 MB · optional”, “Assistant — Gemma 4 E2B · 3.35 GB” (draft tier only), “Read aloud — Kokoro · optional”, “Identifier flagging — OpenMed PII 44M · 283 MB · needed for sharing”, each with its licence and version. The catalog loses whisper-tiny-en, whisper-base-en, parakeet-tdt-v3-q4, qwen3-0.6b-q8 and qwen3.5-2b-q6; the stored transcription- and assistant-model preferences are deleted by the vault migration.

6.8 Decisions left to the owner that touch the models

Confirm the two tiers rather than a single 8 GB floor; FluidAudio 0.15.5 now versus 0.17.x now (Ultra, Nemotron-3 and boosting live in 0.17, at the price of a binary xcframework and a three-host upgrade with Talk and Tel — and, a fact to weigh, on the 13 PriMock57 files both completed, 0.17.7’s build of the same Parakeet v3 was 1.1 points more accurate than 0.15.5’s, better on 11 of the 13); the DER gate at 20 %; long-visit drafting on E2B with the omission gate rather than E4B with a 12 GB floor; provider names removed by default in shares.

7. Open items — what is still to measure, build or decide

  1. No device numbers. No iPhone memory or speed measurement of FluidAudio Parakeet or Sortformer exists anywhere, and every memory figure in this report is from a Mac. Whether iOS schedules the int8 encoder on the Neural Engine at all is unmeasured; so is Neural Engine use on the Mac host. The tier floors are derived from proxies and marked provisional; the first TestFlight build shows the live available-memory and entitlement state on the readiness card, and the three tier rules decide with recorded numbers. The 0.17.7 vocabulary-cost figure is also unresolved until a device run.
  2. Long visits. The 60-minute part cap and 90-minute visit total mean 173 MB of PCM in the vault and a 173 MB cached WAV; materialising the audio, the per-visit export and a 200-visit bulk export on an iPhone 15 are to be measured. The longest file transcribed so far is 28.5 minutes of synthetic audio; 60-minute thermal and battery behaviour on a phone is unmeasured.
  3. Clinical DER, and the diarizer choice. Sortformer has not been scored on PriMock57. The tool that can do it exists and is in the repository (fluid-cli diarize), so the only reason the number is missing is that the run has not been made: both diarizers over the six synthetic visits and the 57 PriMock57 files, scored against the TextGrid gold with DER added to the scorer; under two hours of machine time at RTF ≈ 0.19. No post-3.1 diarizer has a published clinical evaluation; the literature’s 21–40 % on real or clinical-style visits is why the ≤ 20 % gate may fail and why shipping without automatic labels is designed in.
  4. Spanish. Nothing has been measured in Spanish: OpenMed missed the Spanish date and name in the one such case, the lexicon is English RxNorm, and the share route is English-only for that reason; an interpreter-chipped visit is shareable only after an explicit “this transcript is in English” confirmation.
  5. The assistant on long transcripts, and on a device. The only quality evidence on E2B is the 2026-10-02 twelve-case Simulator comparison on short text; there is no quality measurement on a long visit or on a device. The n_ctx 8192 setting, the map → reduce pass and the completeness check are designed, not tested. The omission-rate gate in §6.4 is the release number.
  6. Synthetic voices and the next corpus. The dictation and visit sets are macOS TTS: clean, no noise, no overlap, no distance, six voices. Real-room numbers will be worse than §5.2–5.3; PriMock57 is the number to plan on for conversation, and even it is actors with UK accents in a studio. A noise-condition sweep (four noise types × five levels) and a term-dense real-voice set remain to be built; Kokoro-82M (Apache-2.0) or Piper en_US-libritts_r-medium (CC BY 4.0) are the licensed synthesisers for that. The next corpus is also the first blind application of the §6.1 fallback rule and the §6.2 acceptance.
  7. The lexicon pass’s edge cases and its bookkeeping. The short-brand dose-context rule is designed but not yet measured; the PriMock57 cost row is re-run before it is accepted. The pass makes wrong snaps on every engine’s output, which is why every substitution is revertible and shown. The rewrite log is per engine across both datasets and the per-item hypothesis JSONs are not committed, so the “45 rewrites on PriMock57” figure comes from an uncommitted inspection; the log should be split per dataset, or the hypotheses committed, before that figure is quoted again.
  8. OpenMed on the device. onnxruntime-react-native running the re-exported DeBERTa graph with float32 outputs, and the own-port tokenizer under Hermes, are unverified on iOS; the go / no-go in §6.5 runs in week one.
  9. The vendored pods (Swift 6 mode, C and C++17 wrapper modules) have never been compiled inside Care’s Pods graph; the iOS build job is the first commit of that work package, with Swift 5.9 and minimal strict concurrency as the fallback.
  10. Partial runs to finish. Whisper medium (26/57), WhisperKit turbo (40/57), the GGUF q4_0 (26/57) and FluidAudio 0.17.7 (13/57) did not finish PriMock57. The matched-file comparison in §5.1 is the only fair reading of them today, and it already reverses two of the unmatched table’s orderings (medium over small; 0.17.7 over 0.15.5), so the incomplete rows are to be completed before any number in §5.1 is quoted as final.
  11. Numbers as digits or words. Whether the engines emit doses as digits or words is handled by the normaliser for scoring, but the identifier rules need the real output format from the device engine before they are finalised.
  12. The shipped identifier stack is unmeasured as a whole. Only the model layer (70.8 %, 46/65, 95 % CI 59–80 %) and two unions (the shipping layers as they exist today, 72.3 %; the recorded union including the dropped NLTagger, 75.4 %) are measured; the spoken-number normalisation, the extended rules, the known-patient fuzzy match and the allow-list are designed. Once they are built, the union of the layers that actually ship is scored on the corpus and on a held-out set, with intervals, before the share route is enabled.
  13. In-sample tuning. The lexicon thresholds and the vocabulary-boosting list were tuned on, or overlap, the data they were scored on. §6.2 holds the PriMock57 even-numbered half and the next corpus out; nothing from the 2026-10-11 lexicon or boosting rows is an acceptance figure.
  14. One memory rule, two numbers. The design spec’s 1.8 GB (DER rule) and 2.2 GB (tier rule) for the same iPhone 15, 60-minute, ASR-then-diarization run must be reconciled in the spec — stated as the diarization step’s peak versus the run’s peak, as §6.3 reads them, or collapsed to one.
  15. Engines defined and never run, and cheap runs still owed. Apple SpeechAnalyzer on PriMock57 (the runner exists); sherpa-onnx Parakeet v2 on PriMock57 (the runner exists; it leads the synthetic tables and it calibrates §5.6 against the preprint’s identical runtime); Whisper large-v3 and whisper.cpp’s turbo (never run, so “every Whisper size” is not a claim this report makes); the sherpa v3, distil-large-v3.5 and Moonshine exports; the Parakeet q8_0 / f16 GGUFs.
  16. Nothing is committed yet. The harness, the results and this report were untracked in git as of 2026-10-12. “Recorded” in this report means present in the working tree; it becomes “committed” when they are.

8. Reproduce it

The harness is bun (TypeScript) plus a Python 3 virtual environment and runs on macOS only (say, afconvert, the Swift tools); nothing in it touches the app’s vault or network except each runner’s own first model download. The full report in the repository carries the exact commands: render the synthetic corpus to 16 kHz WAVs and reference JSON; clone PriMock57 with Git LFS and sum its channels; build the engines (whisper.cpp, Scribed Talk’s debug CLI, the Apple SpeechAnalyzer tool, fluid-cli, FluidAudio 0.17.7’s CLI) and run asr/run-mac.ts over both manifests; run the sherpa-onnx comparators with asr/run-sherpa.py; apply the lexicon pass with RxNorm Current Prescribable Content (release 10052026); score with asr/score.py; export the identifier corpus, run OpenMed’s published ONNX graphs, build the NLTagger probe and score every layer with phi/score.ts.

The recorded results for the 2026-10-11 run are under scripts/model-evaluation/results/2026-10-11-mac/: the ASR summary (the tables in §5), the per-item scores (1,468 rows), the run log (703 runs with wall time, processing time where the engine reports it, peak RSS and exit code), the rewrite log (every rewrite per engine with counts, both datasets together), the two smoke summaries, and the five phi JSON files (the NER outputs and the layer scores). The 2026-10-02 Simulator results are under results/2026-10-02-simulator/. The audio directories are not part of the repository by design.

9. Licences and attributions

Attribution mechanics: CC-BY-4.0 §3(a)(1) requires retaining the creator identification, copyright and licence notices, a link and a modification notice (this applies to Core ML and GGUF conversions); Apache-2.0 requires carrying LICENSE and NOTICE and marking modified files; MIT requires keeping the copyright and permission notice. The app’s Licences screen carries the notices for everything that ships. Read 2026-10-11.

Licences and attributions
componentlicenceobligation / note
NVIDIA Parakeet TDT 0.6B v3 (and v2); FluidInference’s Core ML buildsCC-BY-4.0 (“ready for commercial/non-commercial use”)attribution in the app; the Core ML card’s front-matter says cc-by-4.0 while its text says Apache 2.0 — treated as CC-BY-4.0
FluidAudio 0.15.5 (Swift)Apache-2.0LICENSE + NOTICE vendored with the sources; the one logging edit is marked
NVIDIA Sortformer v2.1 streaming (FluidInference Core ML)NVIDIA Open Model License (2025-10-24)commercially usable; distributed copies carry “Licensed by NVIDIA Corporation under the NVIDIA Open Model License”; rights terminate on circumventing a guardrail; the Core ML repo’s cc-by-4.0 tag is ignored in favour of the upstream licence
NVIDIA Nemotron-3-Diarization (deferred)OpenMDW-1.1retain the agreement and notices; terminates on a patent or copyright suit; no restriction on outputs
Gemma 4 E2B-it QAT Q4_0Apache-2.0LICENSE + NOTICE; the Apache-2.0 text carries no use restriction; the Gemma 1–3 Terms of Use and Prohibited Use Policy are a separate document, and whether Google intends it to bind Gemma 4 users is UNVERIFIED (the model-card page still links it)
OpenMed-PII-SuperClinical-Small-44M-v1Apache-2.0LICENSE + NOTICE; the fp16-weight / float32-I/O re-export is a modified file and is marked
llama.rn, onnxruntime-react-native, whisper.cpp, WhisperKit (fallback), Whisper weightsMITnotices
RxNorm Current Prescribable Content (US NLM)public domain (US government work)release pinned in the asset file name; no UMLS licence needed for the prescribable subset
PriMock57 (Babylon Health)CC BY 4.0attribution in the evaluation results; not redistributed by us
Research Square preprint rs-10804600/v1 (Faulkenberry)CC BY 4.0quoted figures attributed above
macOS System Voices (say)macOS SLA §2F: System Voices only “to create your own original content and projects for your personal, non-commercial use”the rendered evaluation audio is never committed or shared; only text, scripts and result JSON are. The clause was read in the macOS 27 SLA; the host runs macOS 26.5.2, so the macOS 26 SLA is the governing text, to be located from Apple’s SLA index and re-read; say is replaced for the next corpus
Apple Speech, NaturalLanguage, Core ML, Metal frameworksApple SDK agreementevaluation tools only (SpeechAnalyzer and NLTagger were not adopted)
Kokoro-82M (read-aloud, unchanged)Apache-2.0notices
Not used, for the record: MedASR / MedGemma (HAI-DEF terms), LFM2.5 (revenue threshold), Sortformer v1 and DiariZen (CC-BY-NC), Apple Foundation Models (acceptable-use rule), pyannote community-1 (CC-BY-4.0 scoped; deferred pending the DER run), XTTS-v2 (CPML), MIMIC / PhysioNet data (credentialed DUA)—see §3.3

10. Revision of 2026-10-12

The first text of this report was checked against the per-item scores, the run log, the rewrite log, the smoke summaries, the phi JSON files, the harness code and the companion research and design notes. What changed, so that a reader of the first version knows which sentences not to rely on:

  • Matched files. The partial PriMock57 subsets are the first N files alphabetically and are harder than the set; every partial engine is now compared with the Core ML v3 on its own files (§5.1). The GGUF gap is 1.7 points, not 3; Whisper medium beats small on their shared 26 files; FluidAudio 0.17.7’s v3 is 1.1 points better than 0.15.5’s on their shared 13; the first version’s “the accuracy ranking is stable across the subsets that did finish” was false and is withdrawn.
  • The WhisperKit rule. The first version claimed two of three conditions met and counted one; the 28.5-minute file was never run through WhisperKit; the rule is restated so that every term can be evaluated (§6.1).
  • Neural Engine. “The only path that reaches the Neural Engine” became “the only path that requests it”; the “(ANE)” label on the Mac figures was dropped.
  • Gemma 4 E2B. The LiteRT iPhone footprint is for a different artifact; no iPhone figure exists for the GGUF; the 2026-10-02 Simulator comparison, the team’s only quality evidence, was added.
  • Diarization. Both visit-01 runs were manual and unlogged; pyannote is deferred, not dropped. The claim that Talk’s CLI was needed for the PriMock57 DER was wrong; fluid-cli, now in the repository, is the tool.
  • Identifiers. The capability is “flagging for review”; the shipped stack is unmeasured as a whole; the shipped-layer union (72.3 %) and confidence intervals were added; the false positives are four years and one age; the Safe Harbor sentence is a design choice, not a determination.
  • In-sample tuning of the lexicon pass and the corpus-overlapping vocabulary list are disclosed, and the acceptance is held out.
  • RTF bases are stated per engine; the speed claims were reworded on wall time.
  • Smaller corrections — the repository file’s §10 lists them; they are not transcribed here.
  • Editorial clarifications on this page — §1 states the Whisper comparison by mean over the set; §5.1’s memory clause is restated to match its own table (the repository file’s sentence reads “of any engine that emits timestamps”); §6.5 carries a dated erratum on the identifier model’s file.

The source of this page is docs/reports/care-model-selection-2026-10.md in the Scribed repository; where the two differ, the repository file is the record.