Skip to content

Initial release: safe Rust bindings for whisper.cpp (0.1.0)#1

Merged
uqio merged 1 commit intomainfrom
0.1.0
May 7, 2026
Merged

Initial release: safe Rust bindings for whisper.cpp (0.1.0)#1
uqio merged 1 commit intomainfrom
0.1.0

Conversation

@uqio
Copy link
Copy Markdown
Collaborator

@uqio uqio commented May 7, 2026

Summary

Initial release of whispercpp and whispercpp-sys: safe Rust bindings for whisper.cpp speech-to-text inference.

  • whispercpp — safe API: Context, State, Params, Lang, WhisperError. Panic-free setters; FFI throws contained behind a C++ exception-catching shim.
  • whispercpp-sys — bindgen output + build.rs that cmake-builds a vendored, patched whisper.cpp submodule. No pkg-config / system path; the Rust safety surface depends on patches that only the bundled build supplies.

Highlights

  • Always-bundled build. Submodule pinned to Findit-AI/whisper.cpp@rust, a fork branch that carries each memory-safety fix as a reviewable commit on top of v1.8.4.
  • Send + Sync Context, per-Context State is Send. Concurrent inference is serialised through a per-Context mutex so the leak budget on caught-exception sentinels is structural, not documentary.
  • No-throw FFI surface. Bindgen allowlist enumerated symbol-by-symbol — only no-throw raw whisper_* accessors are exposed; every throwing constructor / inference entry point routes through a whispercpp_* shim that catches and surfaces the exception class as a sentinel (WhisperError::ConstructorLost, StateLost, …).
  • Backend matrix. Metal, CoreML, Vulkan, OpenCL, CUDA, ROCm (HIP), oneAPI (SYCL), Moore Threads (MUSA), OpenVINO, OpenBLAS — all opt-in via Cargo features; serde for Lang's ISO-639-1 wire format.
  • Build-time canary. build.rs::verify_patched_source scans the linked source for required patch markers and hard-fails the build if any are missing — catches submodule drift.

Memory-safety fixes carried on the fork's rust branch

  • whisper_kv_cache_free made idempotent (closes a multi-decoder OOM double-free of a ggml backend buffer).
  • RAII guards around whisper_init_state, whisper_init_with_params_no_state, whisper_vad_init_with_params so a throw mid-init releases the partial allocation rather than leaking the whisper_context / whisper_state.
  • Tensor headers fully validated: n_dims ∈ [0, 4], name length bounded, ttype < GGML_TYPE_COUNT, per-dim positivity, 64-bit overflow check on nelements.
  • Hparams validated against bounded ranges; min n_text_ctx = 64 so the decode batch can hold the worst-case prompt.
  • Special-token ids verified to fit n_vocab after the multilingual shift (closes a corrupt-vocab OOB into logits[]).
  • File / buffer loaders throw on partial reads; peek-based EOF detection so clean end-of-tensor-list still terminates.
  • Tensor-name set tracking rejects models that satisfy the loaded-count check by repeating one name.
  • ggml_log_set installed once per process via std::atomic — closes a race between concurrent create_state and State::full.
  • vocab.num_languages() synthesis null-checks whisper_lang_str (closes std::string(nullptr) UB).
  • Abort callback wired through every sched-based graph compute so cancellation interrupts the long-running encoder / decoder paths, not just the gaps between them.
  • whisper_batch default member initializers + whisper_batch_init OOM-safe alloc chain.
  • path_model assignment guarded so a std::string OOM at the tail of Context::new doesn't leak the model.
  • whisper_kv_cache_init frees its ggml_context on alloc failure.

CI

  • rustfmt, clippy --workspace --all-targets -- -D warnings
  • build-bundled on ubuntu-latest / macos-latest / windows-latest
  • build-macos-gpu (Metal + CoreML)
  • test --lib on Linux + macOS
  • sanitizer — Linux ASan + HWAddress sanitizer against the bundled FFI smoke test (nightly + -Zbuild-std)
  • miri --strict-provenance against the safe wrapper's tests; FFI-touching tests gated via #[cfg_attr(miri, ignore)]
  • doc — rustdoc with -Dwarnings

Test plan

  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo test -p whispercpp --features serde --lib (24/24)
  • Real-model end-to-end smoke: large-v3-turbo (1.6 GB) loads + transcribes a fixture wav correctly
  • Whispery downstream consumer builds clean
  • Build hard-fails when submodule is reset to unpatched upstream (verified via local git checkout v1.8.4 + rebuild)

🤖 Generated with Claude Code

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Introduces the initial whispercpp safe wrapper crate and the whispercpp-sys FFI crate, wiring in a vendored/patch-verified whisper.cpp build, exception-catching shims, and a documented safe API surface.

Changes:

  • Added safe Rust API (Context, State, Params, Lang, WhisperError) plus an example smoke binary.
  • Added whispercpp-sys build.rs to cmake-build a vendored whisper.cpp submodule, build an exception-catching shim, and generate bindgen output into OUT_DIR.
  • Restructured the repo into a workspace; updated README/CI and removed template scaffolding.

Reviewed changes

Copilot reviewed 35 out of 36 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
whispercpp/src/sys.rs Re-exports the raw whispercpp-sys bindings under whispercpp::sys.
whispercpp/src/state.rs Adds State, segment/token accessors, and full() inference logic with poisoning semantics.
whispercpp/src/params.rs Adds Params builder with owned C strings, clamps, abort callback trampoline, and unit tests.
whispercpp/src/lib.rs Defines crate module layout and public re-exports.
whispercpp/src/lang.rs Adds typed Lang enum with optional serde wire format and tests.
whispercpp/src/error.rs Adds WhisperError and WhisperResult definitions.
whispercpp/src/context.rs Adds Context loader/state creation, poisoning model, locks, and system_info caching.
whispercpp/examples/smoke.rs Adds an end-to-end smoke example reading WAV and printing segments.
whispercpp/TODO.md Documents intentionally-unsupported whisper.cpp surface and audit policy.
whispercpp/README.md Points crate readme to workspace README.
whispercpp/Cargo.toml Defines whispercpp crate metadata, features, deps, and example.
whispercpp/.gitignore Ignores per-crate target/.
whispercpp-sys/wrapper.h Bindgen entry point including whisper.h and shim header.
whispercpp-sys/whispercpp_shim.h Declares C-ABI exception-catching shim API + sentinel codes.
whispercpp-sys/whispercpp_shim.cpp Implements the exception-catching shims and TLS sentinel.
whispercpp-sys/whisper.cpp Adds the whisper.cpp submodule pin reference.
whispercpp-sys/src/lib.rs Exposes generated bindings via include!(OUT_DIR/generated.rs).
whispercpp-sys/build.rs CMake-builds bundled whisper.cpp, verifies patch markers, builds shim, runs bindgen.
whispercpp-sys/README.md Points sys crate readme to workspace README.
whispercpp-sys/Cargo.toml Defines sys crate features and build dependencies (cmake/bindgen/cc).
whispercpp-sys/.gitignore Ignores sys crate target/.
tests/foo.rs Removes template test placeholder.
src/lib.rs Removes template root crate lib.
examples/foo.rs Removes template example placeholder.
ci/sanitizer.sh Removes template sanitizer script.
ci/miri_tb.sh Removes template miri script.
ci/miri_sb.sh Removes template miri script.
benches/foo.rs Removes template benchmark placeholder.
README.md Replaces template README with whispercpp docs (features/backends/safety).
README-zh_CN.md Removes template Chinese README.
Cargo.toml Converts repo into a workspace with shared package metadata/lints.
CHANGELOG.md Removes template changelog placeholder.
.gitmodules Adds whisper.cpp submodule pointing at the fork rust branch.
.github/workflows/loc.yml Updates LoC gist key to whispercpp.
.github/workflows/ci.yml Replaces template CI with a matrix that builds bundled whisper.cpp, runs tests, sanitizer, and miri.
Comments suppressed due to low confidence (5)

whispercpp/src/params.rs:1

  • owned.len() as i32 can wrap/truncate for slices longer than i32::MAX, potentially passing a negative/garbled prompt_n_tokens into whisper.cpp. Since whisper.cpp expects an int, this should be validated with i32::try_from(...) and handled explicitly (e.g., return a WhisperError variant) before updating raw.prompt_n_tokens.
    whispercpp/src/params.rs:1
  • This expect(...) introduces a panic in a public setter in a module that otherwise aims to be panic-free. You can avoid the unwrap entirely by computing user_data from the newly-created outer before it is moved into self._abort_callback (the heap address is already stable), then store outer and publish the raw fields.
    whispercpp/src/params.rs:1
  • The doc comment doesn't match the actual storage type (AbortCallback = Box<UnsafeCell<Box<dyn FnMut() -> bool>>>). Please update the docs to reflect the real layout (including the UnsafeCell) since the safety argument depends on it.
    whispercpp/src/state.rs:1
  • This doc comment contains several grammatical fragments (e.g., "surfaced the original" mid-sentence) and placeholder artifacts (e.g., "'s"). Since this is safety-critical documentation explaining poisoning/double-free behavior, it should be cleaned up to be fully readable and unambiguous.
    whispercpp/src/params.rs:1
  • The name MAX_INITIAL_TS_S suggests seconds, but the docs describe a frame/token index limit (1500 frames = 30s). If whisper.cpp interprets max_initial_ts as seconds (common in its CLI/API), clamping to 1500.0 seconds is far beyond the 30s chunk width and may interact badly with downstream timestamp/token logic. Please confirm the unit expected by whisper.cpp and either (a) change the constant to 30.0 seconds, or (b) rename/re-document the constant and setter to make it explicit that the value is an index (not seconds), and ensure the conversion logic matches upstream expectations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread whispercpp/src/context.rs Outdated
Comment thread whispercpp-sys/src/lib.rs Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread whispercpp/Cargo.toml
Comment on lines +1 to +4
[package]
name = "whispercpp"
version = "0.0.1"
edition.workspace = true
Comment thread .gitmodules Outdated
@uqio uqio changed the title 0.1.0 Initial release: safe Rust bindings for whisper.cpp (0.1.0) May 7, 2026
@al8n al8n requested a review from Copilot May 7, 2026 03:09
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 36 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (2)

whispercpp/src/sys.rs:1

  • This module doc claims a pkg-config/system-install path exists when bundled is off, but the PR description and the whispercpp-sys build script/documentation state there is no system/pkg-config path (bundled-only to preserve patch-dependent safety). Update this docstring to match the actual build contract to avoid misleading API consumers.
    whispercpp/src/params.rs:1
  • There are multiple leftover placeholder fragments like Codex and broken punctuation in public-facing docs/comments. These reduce readability and make the safety rationale harder to audit; please remove the placeholders and repair the sentences (same issue appears in other modules too).

Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml
bash ci/miri_sb.sh "${{ matrix.target }}"
submodules: recursive
- run: rustup update stable && rustup default stable
- run: cargo doc --workspace --no-deps --features whispercpp/serde
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
Initial release of `whispercpp` and `whispercpp-sys`:

- `whispercpp` — safe API: `Context`, `State`, `Params`, `Lang`,
  `WhisperError`. Panic-free setters; FFI throws contained behind
  a C++ exception-catching shim. `Send + Sync` `Context`,
  per-`Context` `Mutex` serializing concurrent inference.
- `whispercpp-sys` — bindgen output + `build.rs` that cmake-builds
  a vendored, patched whisper.cpp submodule. Submodule pinned to
  the `Findit-AI/whisper.cpp@rust` fork branch; `build.rs` carries
  a build-time canary that hard-fails if required patch markers
  are missing from the linked source.

Memory-safety patches on the fork's `rust` branch close upstream
hazards reachable from safe Rust:

- `whisper_kv_cache_free` made idempotent (closes a multi-decoder
  OOM double-free of a ggml backend buffer).
- RAII guards around `whisper_init_state`,
  `whisper_init_with_params_no_state`, `whisper_vad_init_with_params`
  so a throw mid-init releases the partial allocation.
- Tensor headers fully validated: `n_dims ∈ [0, 4]`, name length
  bounded, `ttype < GGML_TYPE_COUNT`, per-dim positivity, 64-bit
  overflow check on `nelements`.
- Hparams validated against bounded ranges; min `n_text_ctx = 64`
  so the decode batch can hold the worst-case prompt.
- Special-token ids verified to fit `n_vocab` after the
  multilingual shift.
- File / buffer loaders throw on partial reads; peek-based EOF.
- Tensor-name set tracking rejects models that satisfy the
  loaded-count check by repeating one name.
- `ggml_log_set` installed once per process via `std::atomic` —
  closes a race between concurrent `create_state` and `State::full`.
- `vocab.num_languages()` synthesis null-checks `whisper_lang_str`.
- Abort callback wired through every sched-based graph compute.
- `whisper_batch` default member initializers + OOM-safe alloc
  chain in `whisper_batch_init`.
- `path_model` assignment guarded so a `std::string` OOM at the
  tail of `Context::new` doesn't leak the model.
- `whisper_kv_cache_init` frees its `ggml_context` on alloc
  failure.

CI gates:

- `rustfmt`, `clippy --workspace --all-targets -- -D warnings`
- `build-bundled` on ubuntu-latest / macos-latest / windows-latest
- `build-macos-gpu` (Metal + CoreML)
- `test --lib` on Linux + macOS
- `sanitizer (asan)` on x86_64-linux
- `sanitizer (asan, aarch64-linux)` on ubuntu-24.04-arm
- `ubsan (C++ side)` against `-fsanitize=undefined`
- `miri --strict-provenance` against the safe-wrapper crate's
  tests; FFI-touching tests gated via `#[cfg_attr(miri, ignore)]`
- `doc` — rustdoc with `-Dwarnings`
@uqio uqio merged commit c488c93 into main May 7, 2026
15 checks passed
@uqio uqio deleted the 0.1.0 branch May 7, 2026 05:51
@uqio uqio restored the 0.1.0 branch May 7, 2026 06:26
@uqio uqio deleted the 0.1.0 branch May 7, 2026 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants