Skip to content

Commit 44a01fe

Browse files
committed
Bug 1940906 - Vendor mudler/parakeet.cpp for cache-aware streaming speech recognition. r=firefox-build-system-reviewers,glandium,media-playback-reviewers,alwu
This is mudler/parakeet.cpp, vendored for real-time, cache-aware streaming speech recognition of a Parakeet-family (RNN-T/joint) model via a streaming C API (parakeet_capi.h). It uses libggml (that we already vendor in llama.cpp) as its backend for most computations. This is compiled into `libmozinference`. Two patches are applied on top: - parakeet-fd-loading.patch: load a speech recognition model from a `FILE*` instead of a file path. This is passed down by the main process when starting recognition, similarly to what we do for llama.cpp. - parakeet-no-drwav.patch: drop the vendored dr_wav.h dependency, which is unused because Firefox only ever feeds PCM directly. Differential Revision: https://phabricator.services.mozilla.com/D268402
1 parent c461801 commit 44a01fe

67 files changed

Lines changed: 9719 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎config/external/mozinference/mozinference.symbols‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,27 @@ llama_memory_seq_pos_max
3838
llama_decode
3939
llama_vocab_is_eog
4040
llama_token_to_piece
41+
parakeet_capi_abi_version
42+
parakeet_capi_free
43+
parakeet_capi_free_events
44+
parakeet_capi_free_string
45+
parakeet_capi_last_error
46+
parakeet_capi_load
47+
parakeet_capi_load_fd
48+
parakeet_capi_stream_begin
49+
parakeet_capi_stream_begin_lang
50+
parakeet_capi_stream_drain_events
51+
parakeet_capi_stream_feed
52+
parakeet_capi_stream_feed_json
53+
parakeet_capi_stream_finalize
54+
parakeet_capi_stream_finalize_json
55+
parakeet_capi_stream_free
56+
parakeet_capi_transcribe_path
57+
parakeet_capi_transcribe_path_json
58+
parakeet_capi_transcribe_path_lang
59+
parakeet_capi_transcribe_pcm
60+
parakeet_capi_transcribe_pcm_batch
61+
parakeet_capi_transcribe_pcm_batch_json
62+
parakeet_capi_transcribe_pcm_batch_json_lang
63+
parakeet_capi_transcribe_pcm_batch_lang
64+
parakeet_capi_transcribe_pcm_lang

‎dom/media/webspeech/recognition/moz.build‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ LOCAL_INCLUDES += [
5858
"/toolkit/components/jsoncpp/include",
5959
]
6060

61+
# Bug 1244242 is about enabling on Android
62+
if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
63+
DIRS += ["/third_party/parakeet.cpp"]
64+
6165
include("/ipc/chromium/chromium-config.mozbuild")
6266

6367
FINAL_LIBRARY = "xul"

‎third_party/parakeet.cpp/LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 the parakeet.cpp authors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#ifndef PARAKEET_H
2+
#define PARAKEET_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
// Returns a static version string. Never null.
7+
const char* parakeet_version(void);
8+
9+
// Transcribe a wav file with the given GGUF model using the arch-default decoder
10+
// (TDT head for tdt/hybrid_tdt_ctc/rnnt archs; CTC head for ctc archs).
11+
// On success returns 0 and writes a malloc'd, NUL-terminated UTF-8 string to
12+
// *out (the transcript; may be the empty string ""). The caller must release
13+
// it with parakeet_free_string. On error returns nonzero and leaves *out
14+
// unchanged. No C++ exceptions cross this boundary.
15+
int parakeet_transcribe_file(const char* model_path, const char* wav_path, char** out);
16+
17+
// Frees a string previously returned via parakeet_transcribe_file. Safe on NULL.
18+
void parakeet_free_string(char* s);
19+
#ifdef __cplusplus
20+
}
21+
#endif
22+
23+
#ifdef __cplusplus
24+
#include <string>
25+
namespace pk {
26+
27+
// Decoder selector for pk::transcribe.
28+
// kDefault – choose by arch: TDT/RNNT-family archs use the TDT head (matching
29+
// NeMo's cur_decoder='rnnt' default); CTC-only archs use CTC.
30+
// kCTC – force the CTC head regardless of arch.
31+
// kTDT – force the TDT/RNNT head regardless of arch.
32+
enum class Decoder { kDefault, kCTC, kTDT };
33+
34+
// End-to-end transcription. Routes between the TDT greedy decoder and the CTC
35+
// greedy decoder based on the GGUF 'parakeet.arch' metadata:
36+
// arch ∈ {tdt, hybrid_tdt_ctc, rnnt, hybrid_rnnt_ctc} → TDT head (default)
37+
// arch == ctc → CTC head
38+
// The optional 'decoder' parameter overrides the arch-based selection.
39+
// Throws std::runtime_error on failure (model/audio load, unsupported arch, etc.).
40+
std::string transcribe(const std::string& model_path, const std::string& wav_path,
41+
Decoder decoder = Decoder::kDefault);
42+
43+
} // namespace pk
44+
#endif
45+
46+
#endif // PARAKEET_H

‎third_party/parakeet.cpp/include/parakeet_capi.h‎

Lines changed: 286 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2+
/* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
4+
* You can obtain one at http://mozilla.org/MPL/2.0/. */
5+
6+
#include <cstdlib>
7+
8+
#pragma once
9+
10+
/*
11+
* parakeet.cpp uses C++ exceptions. Override them here so the library can be
12+
* compiled with -fno-exceptions. This file is included in every affected
13+
* translation unit via a direct #include at the top. This technique allows for
14+
* minimal patching of the vendored library.
15+
*/
16+
17+
// Inline function to replace throw. We can't use a regular define because some
18+
// compilers warn about unreachable code (e.g. the ctor call).
19+
[[noreturn]] inline void abort_with_suppression() {
20+
std::abort();
21+
}
22+
23+
#define throw abort_with_suppression(); if (false)
24+
25+
// Replace try blocks by ifs.
26+
#define try if (true)
27+
28+
// parakeet_capi.cpp uses `e` as the exception variable name.
29+
#define catch(x) \
30+
if (static const std::exception e; false)

‎third_party/parakeet.cpp/moz.build‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
2+
# vim: set filetype=python:
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
7+
with Files('**'):
8+
BUG_COMPONENT = ('Core', 'Machine Learning')
9+
10+
# Built into the mozinference shared library alongside the vendored ggml (from
11+
# llama.cpp). Only the parakeet_capi_* symbols are exported, via
12+
# config/external/mozinference/mozinference.symbols.
13+
SOURCES = [
14+
"src/audio_io.cpp",
15+
"src/backend.cpp",
16+
"src/common.cpp",
17+
"src/conformer.cpp",
18+
"src/ctc_decoder.cpp",
19+
"src/encoder.cpp",
20+
"src/fft.cpp",
21+
"src/ggml_graph.cpp",
22+
"src/joint.cpp",
23+
"src/mel.cpp",
24+
"src/mel_gpu.cpp",
25+
"src/model.cpp",
26+
"src/model_loader.cpp",
27+
"src/parakeet_capi.cpp",
28+
# src/parakeet.cpp is intentionally omitted: it is a thin convenience
29+
# wrapper (parakeet_version / parakeet_transcribe_file) whose C symbols
30+
# collide with the other vendored backend. We use the parakeet_capi_* API.
31+
"src/pos_enc.cpp",
32+
"src/prediction.cpp",
33+
"src/prompt_kernel.cpp",
34+
"src/relpos_attention.cpp",
35+
"src/rnnt.cpp",
36+
"src/search.cpp",
37+
"src/streaming.cpp",
38+
"src/streaming_encoder.cpp",
39+
"src/subsampling.cpp",
40+
"src/tdt.cpp",
41+
"src/tokenizer.cpp",
42+
"src/transcription.cpp",
43+
"src/transducer_batch.cpp",
44+
]
45+
46+
EXPORTS += [
47+
"include/parakeet_capi.h",
48+
]
49+
50+
LOCAL_INCLUDES += [
51+
'/third_party/llama.cpp/',
52+
'/third_party/llama.cpp/ggml/',
53+
'/third_party/llama.cpp/ggml/include/',
54+
'/third_party/llama.cpp/ggml/src/',
55+
'/third_party/llama.cpp/ggml/src/ggml-cpu/',
56+
'/third_party/parakeet.cpp/',
57+
'/third_party/parakeet.cpp/include/',
58+
'/third_party/parakeet.cpp/src/',
59+
]
60+
61+
# Vendored third-party code; quiet the warnings it triggers under our flags.
62+
AllowCompilerWarnings()
63+
64+
# The parakeet_capi_* entry points are plain extern "C" without an export
65+
# attribute, so drop Gecko's -fvisibility=hidden for this directory; otherwise
66+
# they end up GLOBAL HIDDEN and the symbols file can't export them. The set
67+
# actually exported is still restricted by mozinference.symbols.
68+
NoVisibilityFlags()
69+
70+
FINAL_LIBRARY = 'mozinference'

‎third_party/parakeet.cpp/moz.yaml‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
schema: 1
2+
3+
bugzilla:
4+
product: Core
5+
component: "Machine Learning: On Device"
6+
7+
origin:
8+
name: parakeet.cpp
9+
description: Parakeet (FastConformer) speech recognition in C++ with ggml, incl. cache-aware streaming
10+
11+
url: https://github.com/mudler/parakeet.cpp
12+
13+
release: f469a57270a1cc4554acb15febf60e56619673b9
14+
revision: f469a57270a1cc4554acb15febf60e56619673b9
15+
16+
license: MIT
17+
18+
vendoring:
19+
url: https://github.com/mudler/parakeet.cpp
20+
source-hosting: github
21+
tracking: commit
22+
23+
skip-vendoring-steps:
24+
- update-moz-build
25+
26+
exclude:
27+
- "**"
28+
29+
include:
30+
- LICENSE
31+
- src
32+
- include/parakeet.h
33+
- include/parakeet_capi.h
34+
35+
patches:
36+
- parakeet-fd-loading.patch # load a model from an fd (sandboxed process)
37+
- parakeet-no-drwav.patch # drop the vendored dr_wav.h dependency (unused: Firefox only feeds PCM)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Firefox-local: load Parakeet GGUF models from an already-open file
2+
# descriptor, for the sandboxed HWInference utility process which cannot
3+
# open paths.
4+
# Upstreamable to https://github.com/mudler/parakeet.cpp
5+
#
6+
--- a/src/model_loader.hpp
7+
+++ b/src/model_loader.hpp
8+
@@ -77,6 +77,8 @@
9+
ModelLoader() = default;
10+
~ModelLoader();
11+
bool load(const std::string& path);
12+
+ // Firefox-local: load from an already-open fd (sandboxed process).
13+
+ bool load_fd(int fd);
14+
const ParakeetConfig& config() const { return cfg_; }
15+
const std::vector<std::string>& tokenizer_pieces() const { return cfg_.tokenizer_pieces; }
16+
ggml_tensor* tensor(const std::string& name) const; // nullptr if absent
17+
@@ -94,6 +96,9 @@
18+
bool realize_weights(ggml_backend_t backend);
19+
bool weights_realized() const { return weights_buf_ != nullptr; }
20+
private:
21+
+ // Parse metadata + tensors from an already-opened gguf_. Shared by load()
22+
+ // and load_fd().
23+
+ bool parse_gguf();
24+
ParakeetConfig cfg_;
25+
gguf_context* gguf_ = nullptr;
26+
ggml_context* ctx_ = nullptr;
27+
--- a/src/model_loader.cpp
28+
+++ b/src/model_loader.cpp
29+
@@ -6,6 +6,7 @@
30+
#include "ggml-cpu.h"
31+
#include "gguf.h"
32+
#include <cstring>
33+
+#include <cstdio>
34+
#include <vector>
35+
#include <utility>
36+
#include <stdexcept>
37+
@@ -125,6 +126,24 @@
38+
struct gguf_init_params p{ /*no_alloc*/false, /*ctx*/&ctx_ };
39+
gguf_ = gguf_init_from_file(path.c_str(), p);
40+
if(!gguf_){ PK_LOG("gguf open failed: %s", path.c_str()); return false; }
41+
+ return parse_gguf();
42+
+}
43+
+
44+
+// Firefox-local addition (upstreamable): load the GGUF from an already-open
45+
+// file descriptor. The sandboxed HWInference utility process is handed an fd by
46+
+// the broker and cannot open paths itself. The fd stays owned by the caller.
47+
+bool ModelLoader::load_fd(int fd){
48+
+ struct gguf_init_params p{ /*no_alloc*/false, /*ctx*/&ctx_ };
49+
+ FILE* f = fdopen(fd, "rb");
50+
+ if(!f){ PK_LOG("fdopen failed for parakeet model fd"); return false; }
51+
+ // gguf reads the whole file (no_alloc=false). Do NOT fclose(f): that would
52+
+ // close the caller-owned fd.
53+
+ gguf_ = gguf_init_from_file_ptr(f, p);
54+
+ if(!gguf_){ PK_LOG("gguf open from fd failed"); return false; }
55+
+ return parse_gguf();
56+
+}
57+
+
58+
+bool ModelLoader::parse_gguf(){
59+
cfg_.arch = kv_str(gguf_, "parakeet.arch");
60+
cfg_.feat_in = kv_u32(gguf_, "parakeet.encoder.feat_in");
61+
cfg_.d_model = kv_u32(gguf_, "parakeet.encoder.d_model");
62+
--- a/src/model.hpp
63+
+++ b/src/model.hpp
64+
@@ -24,6 +24,10 @@
65+
// Loads the GGUF at `gguf_path`. Returns nullptr on failure (no throw).
66+
static std::unique_ptr<Model> load(const std::string& gguf_path);
67+
68+
+ // Firefox-local: load the GGUF from an already-open fd, for the sandboxed
69+
+ // HWInference process. Returns nullptr on failure (no throw).
70+
+ static std::unique_ptr<Model> load_fd(int fd);
71+
+
72+
// Transcribe raw mono float PCM. If `sample_rate != 16000` the audio is
73+
// linearly resampled to 16 kHz (via pk::resample_linear) before inference.
74+
// `target_lang` selects the language prompt for multilingual (nemotron)
75+
--- a/src/model.cpp
76+
+++ b/src/model.cpp
77+
@@ -52,6 +52,18 @@
78+
return m;
79+
}
80+
81+
+// Firefox-local: identical to load() but reads the GGUF from an already-open
82+
+// file descriptor (the sandboxed HWInference process cannot open paths).
83+
+std::unique_ptr<Model> Model::load_fd(int fd) {
84+
+ std::unique_ptr<Model> m(new (std::nothrow) Model());
85+
+ if (!m) return nullptr;
86+
+ if (!m->loader_.load_fd(fd)) {
87+
+ return nullptr;
88+
+ }
89+
+ ensure_weights_realized(m->loader_);
90+
+ return m;
91+
+}
92+
+
93+
// Forward declarations: subsampling-tiling helpers are defined below (after the
94+
// batched staging helpers) but used by the single-clip transcribe entry points.
95+
static int safe_mel_window(const pk::ParakeetConfig& cfg);
96+
--- a/include/parakeet_capi.h
97+
+++ b/include/parakeet_capi.h
98+
@@ -46,6 +46,11 @@
99+
// The returned context must be released with parakeet_capi_free.
100+
parakeet_ctx* parakeet_capi_load(const char* gguf_path);
101+
102+
+// Firefox-local: load a GGUF model from an already-open file descriptor (for
103+
+// sandboxed hosts that cannot open paths). The fd remains owned by the caller.
104+
+// Returns an owning context, or NULL on failure.
105+
+parakeet_ctx* parakeet_capi_load_fd(int fd);
106+
+
107+
// Free a context obtained from parakeet_capi_load. Safe on NULL.
108+
void parakeet_capi_free(parakeet_ctx* ctx);
109+
110+
--- a/src/parakeet_capi.cpp
111+
+++ b/src/parakeet_capi.cpp
112+
@@ -225,6 +225,21 @@
113+
}
114+
}
115+
116+
+// Firefox-local: load from an already-open fd (sandboxed host).
117+
+extern "C" parakeet_ctx* parakeet_capi_load_fd(int fd) {
118+
+ if (fd < 0) return nullptr;
119+
+ try {
120+
+ std::unique_ptr<pk::Model> model = pk::Model::load_fd(fd);
121+
+ if (!model) return nullptr;
122+
+ auto* ctx = new (std::nothrow) parakeet_ctx();
123+
+ if (!ctx) return nullptr;
124+
+ ctx->model = std::move(model);
125+
+ return ctx;
126+
+ } catch (...) {
127+
+ return nullptr;
128+
+ }
129+
+}
130+
+
131+
extern "C" void parakeet_capi_free(parakeet_ctx* ctx) {
132+
delete ctx; // safe on nullptr; ~unique_ptr releases the model.
133+
}

0 commit comments

Comments
 (0)