harshit2312 commited on
Commit
cf06ce2
·
verified ·
1 Parent(s): 6eeddd3

stt-gemma_embedding-2: Core ML package (fp16, multifunction), tokenizer, subword 6-gram LM

Browse files
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
NOTICE ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ V1 speech recognition on Core ML
2
+ Copyright Harshit B.
3
+
4
+ The repository as a whole is licensed under the Apache License, Version 2.0 (LICENSE).
5
+
6
+ 1. Core ML package (v1.mlpackage), derived from google/embeddinggemma-2
7
+
8
+ It contains layers 0-11 of the audio tower of EmbeddingGemma 2 by Google DeepMind,
9
+ https://huggingface.co/google/embeddinggemma-2 (revision 914f7f89142e33e77833254d9c9b90c3cef7303b),
10
+ licensed under the Apache License, Version 2.0
11
+ (https://ai.google.dev/gemma/docs/gemma_4_license).
12
+
13
+ Modifications made by Harshit B.:
14
+ - the audio tower truncated after layer 11 and converted to Core ML ML programs,
15
+ one function per fixed input window (5, 10, 20, 35 s) sharing one set of weights;
16
+ - blocked local attention rewritten as dense banded attention (identical in fp32);
17
+ - RMSNorm inputs scaled by powers of two so fp16 norms on the CPU / GPU do not
18
+ overflow, and constant padding fill (identical in fp32);
19
+ - log-softmax rewritten in a numerically stable form; static shapes;
20
+ - a per-frame int8 quantize/dequantize step, standardization and a CTC head appended;
21
+ - weights stored in float16.
22
+
23
+ The upstream model card states that deployments must adhere to the Gemma Prohibited
24
+ Use Policy: https://ai.google.dev/gemma/prohibited_use_policy
25
+
26
+ config.json embeds the upstream preprocessor_config.json unchanged.
27
+
28
+ 2. CTC head (inside v1.mlpackage) and tokenizer (spm_unigram256.model)
29
+
30
+ Trained by Harshit B. on the LibriSpeech ASR corpus, OpenSLR SLR12,
31
+ https://www.openslr.org/12/ (license stated there: CC BY 4.0).
32
+ V. Panayotov, G. Chen, D. Povey and S. Khudanpur, "LibriSpeech: an ASR corpus based
33
+ on public domain audio books", ICASSP 2015.
34
+
35
+ 3. Language model (sp256_6gram.bin)
36
+
37
+ Built with KenLM (https://github.com/kpu/kenlm; no KenLM code is included) from
38
+ librispeech-lm-norm.txt.gz of "LibriSpeech language models, vocabulary and G2P models",
39
+ OpenSLR SLR11, https://www.openslr.org/11/ (license stated there: Public domain).
40
+
41
+ 4. Decoder (not included; used by the usage example)
42
+
43
+ ctc-beam-decoder 0.2.0, https://pypi.org/project/ctc-beam-decoder/,
44
+ Apache License, Version 2.0.
README.md ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - en
4
+ license: apache-2.0
5
+ library_name: coreml
6
+ pipeline_tag: automatic-speech-recognition
7
+ base_model: google/embeddinggemma-2
8
+ datasets:
9
+ - openslr/librispeech_asr
10
+ tags:
11
+ - speech-recognition
12
+ - streaming
13
+ - ctc
14
+ - coreml
15
+ - apple-neural-engine
16
+ - kenlm
17
+ - embeddinggemma
18
+ model-index:
19
+ - name: stt-gemma_embedding-2
20
+ results:
21
+ - task:
22
+ type: automatic-speech-recognition
23
+ name: Automatic Speech Recognition
24
+ dataset:
25
+ name: LibriSpeech (clean)
26
+ type: openslr/librispeech_asr
27
+ config: clean
28
+ split: test
29
+ args:
30
+ language: en
31
+ metrics:
32
+ - type: wer
33
+ value: 4.18
34
+ name: Test WER (Core ML fp16, beam search + subword 6-gram LM)
35
+ - task:
36
+ type: automatic-speech-recognition
37
+ name: Automatic Speech Recognition
38
+ dataset:
39
+ name: LibriSpeech (other)
40
+ type: openslr/librispeech_asr
41
+ config: other
42
+ split: test
43
+ args:
44
+ language: en
45
+ metrics:
46
+ - type: wer
47
+ value: 10.71
48
+ name: Test WER (Core ML fp16, beam search + subword 6-gram LM)
49
+ ---
50
+
51
+ # stt-gemma_embedding-2
52
+
53
+ **English speech recognition for the Apple Neural Engine, built by training a 13M-parameter CTC head on top of the frozen audio encoder of Google's [EmbeddingGemma 2](https://huggingface.co/google/embeddinggemma-2).**
54
+
55
+ stt-gemma_embedding-2 keeps EmbeddingGemma 2's 305M-parameter audio encoder completely frozen and trains only a small causal head that turns the encoder's frames into text. The encoder never looks at future audio and the head looks ahead by just 160 ms, so the model can transcribe while someone is speaking: in streaming mode, words appear a median of **207 ms** after they are spoken. Exported to Core ML in fp16, it runs on the Neural Engine. Offline, the full pipeline (encoder, head, and beam search with a language model) transcribes at **233× real time** on a MacBook Pro with an M4 Pro; in streaming mode, each 160 ms step takes 5.6 ms of compute.
56
+
57
+ | | test-clean | test-other | Streaming latency (median / p90) | Offline speed (M4 Pro) |
58
+ |---|---|---|---|---|
59
+ | **stt-gemma_embedding-2, beam search + subword 6-gram LM** | **4.18%** | **10.71%** | 207 / 287 ms | **233× real time** |
60
+
61
+ Word error rate on LibriSpeech, corpus-level, of the released Core ML package. Latency is from the end of a spoken word to its final text on screen, measured on dev-clean. See [Evaluation protocol](#evaluation-protocol) for how every number was measured.
62
+
63
+ ## Why this model
64
+
65
+ Many fast on-device speech recognizers either work on fixed windows of audio, which rules out true streaming, or train a large encoder from scratch. This model takes a different route: it reuses an audio encoder that Google has already trained at scale, freezes it, and trains only what is needed to read text out of it. The encoder is EmbeddingGemma 2's audio encoder (a Gemma 4 audio tower, `gemma4_audio`), which also makes it possible to produce a transcript and a search embedding from a single encoder pass.
66
+
67
+ The trainable part is small (13.3M parameters), it was trained on a single laptop, and it runs on the Neural Engine, leaving the GPU free for other work.
68
+
69
+ ## Results
70
+
71
+ ### Accuracy
72
+
73
+ WER in percent. Decoder settings were tuned on dev-clean only; the test sets were each evaluated once with the final settings.
74
+
75
+ | Pipeline | dev-clean | dev-other | test-clean | test-other |
76
+ |---|---|---|---|---|
77
+ | Reference (PyTorch), greedy, no LM | 5.92 | 15.47 | 6.30 | 15.61 |
78
+ | Reference (PyTorch), beam 32 + subword 6-gram LM | 3.73 | 10.47 | 4.14 | 10.70 |
79
+ | **This release (Core ML fp16), beam 32 + subword 6-gram LM** | **3.74** | **10.50** | **4.18** | **10.71** |
80
+
81
+ The released Core ML package reproduces the reference beam + LM results to within 0.04 points (dev-clean +0.011, dev-other +0.026, test-clean +0.038, test-other +0.009). It gives the same dev-clean WER under all three Core ML compute settings: 3.737% on the Neural Engine, 3.746% on the GPU, 3.731% on the CPU only.
82
+
83
+ ### Streaming
84
+
85
+ The streaming build processes 160 ms of audio per step and keeps the encoder's and head's state between steps; the decoder re-decodes the open segment after every step. Its feature extraction is bitwise identical to the offline pipeline, and 100% of its operations run on the Neural Engine. The streaming build is not part of this repository yet; it lives in the code repository.
86
+
87
+ | Split | Latency median / p90 | WER, streaming | WER, reference | Transcripts identical to reference | Revised words |
88
+ |---|---|---|---|---|---|
89
+ | dev-clean | 207 / 287 ms | 3.731% | 3.726% | 2,617 / 2,703 | 4.6% |
90
+ | dev-other | 207 / 288 ms | 10.479% | 10.470% | 2,561 / 2,864 | 10.2% |
91
+
92
+ Latency is measured from the end of a word in the audio to the moment its final text is displayed, and includes the head's 160 ms lookahead, the wait for each 160 ms chunk, and compute. Every decoded word is shown as soon as it appears; "revised words" counts displayed words that are later replaced by a different word as more audio arrives (a word growing while it is spelled out does not count). Each 160 ms step takes 5.62 ms of compute (median; p99 6.03 ms), about 3.5% of real time.
93
+
94
+ ### Speed
95
+
96
+ Offline transcription of this package, measured on a MacBook Pro (M4 Pro, 24 GB) on dev-clean, single stream, after a 20-file warm-up, with model loading excluded.
97
+
98
+ | Component | Hardware | Real-time factor |
99
+ |---|---|---|
100
+ | End to end (16 kHz audio in memory → text) | Neural Engine + CPU | 233.3× |
101
+ | Encoder + head (Core ML, fp16) | Neural Engine | 253.9× |
102
+ | Beam search + language model | CPU | 8,452× |
103
+
104
+ | Compute units | End to end | Peak memory | Load, first / later |
105
+ |---|---|---|---|
106
+ | CPU_AND_NE (recommended) | 233.3× | 3.3 GB | 54.6 s / 3.3 s |
107
+ | CPU_AND_GPU | 194.2× | 4.7 GB | 11.3 s / 3.5 s |
108
+ | CPU_ONLY | 111.7× | 5.2 GB | 7.5 s / 2.7 s |
109
+
110
+ The first load on the Neural Engine includes the device compile; later loads reuse the compiled copy.
111
+
112
+ ### Comparison with Phonon-2
113
+
114
+ For context, we ran [FluidInference/phonon-2-coreml](https://huggingface.co/FluidInference/phonon-2-coreml), a strong Core ML speech recognizer for Apple silicon, on the same Mac, scored it with the same scorer, and timed it with the same method.
115
+
116
+ | Model | test-clean | test-other | Offline speed (M4 Pro) |
117
+ |---|---|---|---|
118
+ | Phonon-2 (FluidAudio, Neural Engine) | **2.47%** | **4.66%** | 187× (test-clean) |
119
+ | stt-gemma_embedding-2 (beam + LM, Neural Engine) | 4.18% | 10.71% | **233×** (dev-clean) |
120
+
121
+ Phonon-2 is clearly more accurate, especially on harder audio. This table shows the trade-off rather than claiming better accuracy: stt-gemma_embedding-2 streams with about 0.2 s of latency and is somewhat faster offline, at a higher error rate. Phonon-2 was run with FluidAudio at commit 90c1b07e; because it outputs punctuation and casing, punctuation other than apostrophes was mapped to spaces before scoring.
122
+
123
+ ## How it works
124
+
125
+ ```
126
+ 16 kHz mono audio
127
+ → log-mel features (128 bins, 100 frames per second)
128
+ → EmbeddingGemma 2 audio encoder, frozen
129
+ 12 layers, 305M parameters, causal (no right context)
130
+ → output of the 12th layer: 1024 numbers per frame, 25 frames per second
131
+ → causal CTC head, trained
132
+ 4 blocks, 13.3M parameters, 160 ms lookahead
133
+ → scores for 256 SentencePiece tokens + a CTC blank, every 40 ms
134
+ → CTC beam search with a subword 6-gram language model
135
+ → text
136
+ ```
137
+
138
+ The head first rounds each encoder frame to int8 (matching how the training features were cached), standardizes it with statistics from the training set, and projects it from 1024 to 512 dimensions. Each of its four blocks then applies masked self-attention (8 heads, up to 64 past frames, or 2.56 s), a causal depthwise convolution (kernel size 5) and a 2048-wide feed-forward layer, each with a residual connection. Only the last block's attention may see future frames, 4 of them (160 ms), so the head's total lookahead is exactly 160 ms; this was checked when the head was trained and again on the Core ML package. The encoder itself has no lookahead: changing the audio after any point never changes its output before that point.
139
+
140
+ The output is CTC over a 256-piece SentencePiece unigram vocabulary. A beam search ([ctc-beam-decoder](https://pypi.org/project/ctc-beam-decoder/), a C++ CTC prefix beam search that reproduces pyctcdecode's behaviour) combines the head's scores with a KenLM 6-gram over the same SentencePiece pieces.
141
+
142
+ The Core ML package is the offline build: one package with shared weights and four functions for input windows of 5, 10, 20 and 35 s (`win_5s` … `win_35s`); longer audio is split at pauses.
143
+
144
+ ## Usage
145
+
146
+ Requirements: a Mac with Apple silicon and macOS 15 or later, Python 3.12, `coremltools==9.0`, `transformers==5.19.0`, `numpy==2.5.4`, `sentencepiece==0.2.2`, `soundfile==0.14.0`, `ctc-beam-decoder==0.2.0`, `huggingface_hub`.
147
+
148
+ ```python
149
+ import json, os
150
+ import numpy as np
151
+ import soundfile as sf
152
+ import coremltools as ct
153
+ import sentencepiece as spm
154
+ import transformers
155
+ from ctc_beam_decoder import BeamSearchDecoder
156
+ from huggingface_hub import snapshot_download
157
+
158
+ root = snapshot_download("harshit2312/stt-gemma_embedding-2")
159
+ cfg = json.load(open(f"{root}/config.json"))
160
+
161
+ # 1. Mel features (CPU), exactly as in training.
162
+ fe_cfg = cfg["feature_extractor"]
163
+ fe = getattr(transformers, fe_cfg["class"]).from_dict(dict(fe_cfg["preprocessor_config"]))
164
+ wav, sr = sf.read("speech.flac", dtype="float32") # 16 kHz mono
165
+ assert sr == 16000 and wav.ndim == 1
166
+ f = fe([wav], sampling_rate=16000, padding="longest", truncation=False, return_tensors="np")
167
+ n = int(f["input_features_mask"][0].sum())
168
+ mel = np.asarray(f["input_features"][0, :n], dtype=np.float32)
169
+
170
+ # 2. Compile the package once and keep the compiled model.
171
+ compiled = os.path.expanduser("~/Library/Caches/stt-gemma_embedding-2/v1.mlmodelc")
172
+ if not os.path.isdir(compiled):
173
+ os.makedirs(os.path.dirname(compiled), exist_ok=True)
174
+ ct.utils.compile_model(f"{root}/{cfg['package']['file']}", compiled)
175
+
176
+ # 3. Encoder + CTC head in the smallest window that fits (<= 35 s here).
177
+ win = min((w for w in cfg["windows"] if w["mel_frames"] >= n), key=lambda w: w["mel_frames"])
178
+ model = ct.models.CompiledMLModel(compiled, compute_units=ct.ComputeUnit.CPU_AND_NE, # or CPU_AND_GPU / CPU_ONLY
179
+ function_name=win["function"])
180
+ x = np.zeros((1, win["mel_frames"], 128), np.float32)
181
+ x[0, :n] = mel
182
+ out = model.predict({"mel": x, "valid_len": np.array([n], np.int32)})
183
+ log_probs = np.array(out["log_probs"], np.float32)[0, : -(-n // 4)] # [frames, 257]
184
+
185
+ # 4. Beam search + subword 6-gram: every piece is an LM "word" t<id>.
186
+ d = cfg["decoder"]
187
+ sp = spm.SentencePieceProcessor(model_file=f"{root}/{cfg['vocab']['tokenizer']}")
188
+ pieces = [f"t{i}" for i in range(sp.get_piece_size())]
189
+ dec = BeamSearchDecoder([f"\u2581{p}" for p in pieces], kenlm_model_path=f"{root}/{d['lm']}",
190
+ unigrams=pieces, alpha=d["alpha"], beta=d["beta"],
191
+ unk_score_offset=d["unk_score_offset"], score_boundary=d["lm_score_boundary"])
192
+ best = dec.decode_beams(log_probs, beam_width=d["beam"], beam_prune_logp=d["beam_prune_logp"],
193
+ token_min_logp=d["token_min_logp"], prune_history=d["prune_history"])[0][0]
194
+ print(" ".join(sp.decode([int(w[1:]) for w in best.split()]).lower().split()))
195
+ ```
196
+
197
+ **Compile once.** `ct.models.MLModel("v1.mlpackage")` compiles the package to a new temporary `.mlmodelc` on every load. Compile it once, as in step 2, and load the compiled model with `CompiledMLModel`; one `.mlmodelc` holds all four functions, picked with `function_name=`.
198
+
199
+ **Long-running processes: keep the input arrays alive.** Core ML releases a prediction's input arrays later, on its own queue; if Python frees them first, a process that keeps predicting can crash. Allocate one `mel` / `valid_len` pair per window, keep it for the model's lifetime, overwrite it in place for each call (float32 / int32; fp16 inputs are cached by Core ML), serialize calls with a lock if you use threads, and copy the outputs before the next call.
200
+
201
+ ## Files
202
+
203
+ | File | Contents |
204
+ |---|---|
205
+ | `v1.mlpackage` | Core ML package: frozen encoder + trained head, fp16, functions `win_5s`, `win_10s`, `win_20s`, `win_35s` with shared weights |
206
+ | `spm_unigram256.model` | SentencePiece unigram tokenizer, 256 pieces (blank = id 256) |
207
+ | `sp256_6gram.bin` | KenLM 6-gram over the tokenizer's pieces, 8-bit quantized trie (762 MB) |
208
+ | `config.json` | Windows → functions, inputs / outputs, feature extractor settings, decoder settings (beam 32, α = 0.8, β = 1, pruning), minimum OS |
209
+ | `LICENSE`, `NOTICE` | Apache License 2.0; upstream attribution, modifications and data sources |
210
+ | `SHA256SUMS` | SHA-256 of every file (`shasum -a 256 -c SHA256SUMS`) |
211
+
212
+ ## Training
213
+
214
+ **Data.** LibriSpeech 960 h (train-clean-100, train-clean-360 and train-other-500; 281,241 utterances), with lowercased transcripts.
215
+
216
+ **Features.** The frozen encoder was run once over all training audio (in bf16 on the GPU), and the output of its 12th layer was cached on disk as per-frame int8 (about 91 GB for all splits); the 961 h of training audio took 4.1 hours, at 234× real time.
217
+
218
+ **Head.** 15 epochs on the cached features with CTC loss, AdamW (learning rate 1e-3, weight decay 1e-4), 1,000 warmup steps followed by cosine decay, dropout 0.1, and batches of up to 12,000 frames. Training took about 11 hours on one MacBook Pro M4 Pro (24 GB). The released head is from epoch 15, the best on dev-clean (5.92% greedy WER).
219
+
220
+ **Language model.** A KenLM 6-gram over SentencePiece pieces, trained only on the OpenSLR `librispeech-lm-norm` corpus (no dev or test transcripts; checked by searching the corpus for dev and test sentences) and quantized to 8 bits.
221
+
222
+ **Decoder settings.** Beam width 32, LM weight α = 0.8 and word bonus β = 1, tuned on dev-clean only.
223
+
224
+ ## Evaluation protocol
225
+
226
+ WER is corpus-level: total word edits divided by total reference words. Before scoring, text is lowercased and whitespace is collapsed (for Phonon-2, punctuation other than apostrophes was also mapped to spaces). Offline speed is the audio duration divided by the processing time, with 16 kHz samples already in memory, model loading excluded, a 20-file warm-up, and a single stream. Streaming latency runs each utterance through the streaming build 160 ms at a time and measures, for every word, the time from the word's end in the audio (from the CTC alignment of the final transcript) to the moment the displayed text up to that word stops changing.
227
+
228
+ ## Limitations
229
+
230
+ This release is English only and was trained only on read audiobook speech. Expect noticeably higher error rates on conversational, accented, noisy or far-field audio; its test-other error rate is about 2.3 times Phonon-2's, so the gap to stronger models widens on harder audio. In streaming mode, harder audio also means more on-screen corrections: about 10% of displayed words are revised on dev-other, against 4.6% on dev-clean. Names, numbers and rare words are weak points, since both the tokenizer and the language model come from book text, and the output is lowercase without punctuation. The language model needs about 0.8 GB of memory, so the default setup targets desktop Macs rather than phones. The model should not be used for high-stakes transcription (legal, medical or similar) without human review.
231
+
232
+ ## License and attribution
233
+
234
+ This model includes, and depends on, the audio encoder of [google/embeddinggemma-2](https://huggingface.co/google/embeddinggemma-2), redistributed under that model's Apache 2.0 license; deployments must also follow the [Gemma Prohibited Use Policy](https://ai.google.dev/gemma/prohibited_use_policy). The trained head, tokenizer, language model and code are released under the license declared above. Training data: [LibriSpeech](https://www.openslr.org/12) (CC BY 4.0). Language-model text: the LibriSpeech LM corpus ([OpenSLR 11](https://www.openslr.org/11), public domain). The decoder, ctc-beam-decoder (Apache-2.0), links KenLM (LGPL) dynamically.
235
+
236
+ This project is independent and is not affiliated with or endorsed by Google or FluidInference.
237
+
238
+ ## Citation
239
+
240
+ ```bibtex
241
+ @misc{stt_gemma_embedding_2_2026,
242
+ title = {stt-gemma\_embedding-2: streaming CTC speech recognition on a frozen EmbeddingGemma 2 audio encoder},
243
+ author = {Harshit B.},
244
+ year = {2026},
245
+ url = {https://huggingface.co/harshit2312/stt-gemma_embedding-2}
246
+ }
247
+
248
+ @inproceedings{panayotov2015librispeech,
249
+ title = {Librispeech: an {ASR} corpus based on public domain audio books},
250
+ author = {Panayotov, Vassil and Chen, Guoguo and Povey, Daniel and Khudanpur, Sanjeev},
251
+ booktitle = {2015 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)},
252
+ pages = {5206--5210},
253
+ year = {2015}
254
+ }
255
+
256
+ @inproceedings{graves2006ctc,
257
+ title = {Connectionist temporal classification: labelling unsegmented sequence data with recurrent neural networks},
258
+ author = {Graves, Alex and Fern{\'a}ndez, Santiago and Gomez, Faustino and Schmidhuber, J{\"u}rgen},
259
+ booktitle = {Proceedings of the 23rd International Conference on Machine Learning},
260
+ pages = {369--376},
261
+ year = {2006}
262
+ }
263
+
264
+ @inproceedings{heafield2011kenlm,
265
+ title = {{KenLM}: Faster and smaller language model queries},
266
+ author = {Heafield, Kenneth},
267
+ booktitle = {Proceedings of the Sixth Workshop on Statistical Machine Translation},
268
+ pages = {187--197},
269
+ year = {2011}
270
+ }
271
+ ```
SHA256SUMS ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ 339dff58281aa2956050b09204c5f74f3acfef8b24d70d7f487fc3593681f402 ./config.json
2
+ cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 ./LICENSE
3
+ 0f3fef6d25d4c3121b7c68d58052adda26c5128adb9a831230c6e91eef528bab ./NOTICE
4
+ dbab7fdfbd5f5cb579d44d490a455e3278f85b6aac0ef023a367eb0368826ffa ./README.md
5
+ 381c408f36fb5ea3cbece7eba408deb2b2320f0fe4679fd45d6bd394395fb371 ./sp256_6gram.bin
6
+ 50002ddf56a79f19c6598240e684cf065a4a94b1fc88c5aff25b05962cccfe05 ./spm_unigram256.model
7
+ de945eb076ccf0a4db01847e224ed561a49d8d4c7febb72e1d039455d5a107d1 ./v1.mlpackage/Data/com.apple.CoreML/model.mlmodel
8
+ 075ca55dc69f1688d2d6dab6cea3225e5164f32b9a8ab0221cd5a586df0d3909 ./v1.mlpackage/Data/com.apple.CoreML/weights/weight.bin
9
+ 8a1768f199c7aba04043bd8254bd54fd2fa12867856b9cc6a4b04d77b410491d ./v1.mlpackage/Manifest.json
config.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "V1: EmbeddingGemma 2 audio tower (layers 0-11) + causal CTC head, Core ML",
3
+ "version": "v1",
4
+ "license": "apache-2.0",
5
+ "base_model": {
6
+ "id": "google/embeddinggemma-2",
7
+ "revision": "914f7f89142e33e77833254d9c9b90c3cef7303b",
8
+ "license": "apache-2.0",
9
+ "component": "audio_tower",
10
+ "layers": [
11
+ 0,
12
+ 11
13
+ ]
14
+ },
15
+ "package": {
16
+ "file": "v1.mlpackage",
17
+ "format": "mlprogram (multifunction, shared weights)",
18
+ "precision": "fp16",
19
+ "source_build": "fp16safe/fp16",
20
+ "specification_version": 9,
21
+ "minimum_os": "macOS 15 / iOS 18",
22
+ "compute_units": {
23
+ "recommended": "CPU_AND_NE",
24
+ "supported": [
25
+ "CPU_AND_NE",
26
+ "CPU_AND_GPU",
27
+ "CPU_ONLY"
28
+ ]
29
+ },
30
+ "default_function": "win_10s"
31
+ },
32
+ "windows": [
33
+ {
34
+ "function": "win_5s",
35
+ "seconds": 5.0,
36
+ "mel_frames": 500,
37
+ "output_frames": 125
38
+ },
39
+ {
40
+ "function": "win_10s",
41
+ "seconds": 10.0,
42
+ "mel_frames": 1000,
43
+ "output_frames": 250
44
+ },
45
+ {
46
+ "function": "win_20s",
47
+ "seconds": 20.0,
48
+ "mel_frames": 2000,
49
+ "output_frames": 500
50
+ },
51
+ {
52
+ "function": "win_35s",
53
+ "seconds": 35.0,
54
+ "mel_frames": 3500,
55
+ "output_frames": 875
56
+ }
57
+ ],
58
+ "window_choice": "smallest window with mel_frames >= valid mel frames",
59
+ "inputs": [
60
+ {
61
+ "name": "mel",
62
+ "shape": [
63
+ "1",
64
+ "T_mel",
65
+ "128"
66
+ ],
67
+ "dtype": "float32",
68
+ "description": "log-mel frames (see feature_extractor), zero-padded to the function's window"
69
+ },
70
+ {
71
+ "name": "valid_len",
72
+ "shape": [
73
+ "1"
74
+ ],
75
+ "dtype": "int32",
76
+ "description": "number of valid mel frames"
77
+ }
78
+ ],
79
+ "outputs": [
80
+ {
81
+ "name": "log_probs",
82
+ "shape": [
83
+ "1",
84
+ "T_mel/4",
85
+ "257"
86
+ ],
87
+ "dtype": "float16",
88
+ "description": "CTC log-probabilities (natural log), 256 pieces + blank"
89
+ },
90
+ {
91
+ "name": "l11",
92
+ "shape": [
93
+ "1",
94
+ "T_mel/4",
95
+ "1024"
96
+ ],
97
+ "dtype": "float16",
98
+ "description": "layer-11 features after the int8 round trip"
99
+ }
100
+ ],
101
+ "valid_output_frames": "ceil(valid_len / 4)",
102
+ "feature_extractor": {
103
+ "class": "Gemma4AudioFeatureExtractor",
104
+ "preprocessor_config": {
105
+ "dither": 0.0,
106
+ "feature_extractor_type": "Gemma4AudioFeatureExtractor",
107
+ "feature_size": 128,
108
+ "fft_length": 512,
109
+ "fft_overdrive": false,
110
+ "frame_length": 320,
111
+ "hop_length": 160,
112
+ "input_scale_factor": 1.0,
113
+ "max_frequency": 8000.0,
114
+ "mel_floor": 0.001,
115
+ "min_frequency": 0.0,
116
+ "padding_side": "right",
117
+ "padding_value": 0.0,
118
+ "per_bin_mean": null,
119
+ "per_bin_stddev": null,
120
+ "preemphasis": 0.0,
121
+ "preemphasis_htk_flavor": true,
122
+ "return_attention_mask": true,
123
+ "sampling_rate": 16000
124
+ },
125
+ "source": "google/embeddinggemma-2@914f7f89142e33e77833254d9c9b90c3cef7303b/preprocessor_config.json (unchanged)",
126
+ "call": {
127
+ "sampling_rate": 16000,
128
+ "padding": "longest",
129
+ "truncation": false
130
+ },
131
+ "valid_mel_frames": "sum(input_features_mask)",
132
+ "mel_frame_rate_hz": 100
133
+ },
134
+ "frame_rate_hz": 25,
135
+ "subsampling": 4,
136
+ "lookahead_ms": 160,
137
+ "encoder_lookahead_ms": 0,
138
+ "ctc_head": {
139
+ "params": 13283585,
140
+ "future_frames": 4,
141
+ "causal_conv": true
142
+ },
143
+ "vocab": {
144
+ "tokenizer": "spm_unigram256.model",
145
+ "type": "sentencepiece-unigram",
146
+ "size": 256,
147
+ "blank_id": 256,
148
+ "num_classes": 257
149
+ },
150
+ "decoder": {
151
+ "library": "ctc-beam-decoder",
152
+ "library_version": "0.2.0",
153
+ "lm": "sp256_6gram.bin",
154
+ "lm_order": 6,
155
+ "piece_word_prefix": "t",
156
+ "alpha": 0.8,
157
+ "beta": 1.0,
158
+ "beam": 32,
159
+ "units": "piece",
160
+ "beam_prune_logp": -10.0,
161
+ "token_min_logp": -5.0,
162
+ "unk_score_offset": -10.0,
163
+ "lm_score_boundary": true,
164
+ "prune_history": true
165
+ },
166
+ "long_audio": {
167
+ "max_mel_frames": 3500,
168
+ "left_context_s": 2.0,
169
+ "cut": "quietest point in the last 10 s (code repo: coreml/transcribe.py)"
170
+ },
171
+ "text_normalization": "lowercase, collapse whitespace"
172
+ }
sp256_6gram.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:381c408f36fb5ea3cbece7eba408deb2b2320f0fe4679fd45d6bd394395fb371
3
+ size 761681354
spm_unigram256.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:50002ddf56a79f19c6598240e684cf065a4a94b1fc88c5aff25b05962cccfe05
3
+ size 243583
v1.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:de945eb076ccf0a4db01847e224ed561a49d8d4c7febb72e1d039455d5a107d1
3
+ size 5701462
v1.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:075ca55dc69f1688d2d6dab6cea3225e5164f32b9a8ab0221cd5a586df0d3909
3
+ size 646897778
v1.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "40950C18-BA78-47BF-8E28-EA22171D289B": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "A976F9F4-38B8-441C-AF08-A672DFE4B336": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "A976F9F4-38B8-441C-AF08-A672DFE4B336"
18
+ }