Rust Core (batchalign_core)
Status: Current Last updated: 2026-09-16 03:36 EDT
For new contributors, start with:
Repository structure
The PyO3 bridge lives at crates/batchalign-pyo3/ inside the
talkbank-tools workspace. It’s a single-crate project that builds
the batchalign_core Python extension module that worker processes
import. Its workspace deps are batchalign-types (domain newtypes
- V2 IPC types) and
talkbank-transform(Cantonese ASR projection, tokenizer realignment, morphosyntax sentence mapping, ASR post-processing). The runtimebatchaligncrate is NOT a pyo3 dependency.
Editable installs use the maturin build backend declared in
pyproject.toml (build-backend = "maturin", [tool.maturin] manifest-path = "crates/batchalign-pyo3/Cargo.toml", feature
pyo3/extension-module, profile = "dev" for fast rebuild).
For a packaged install you build the wheel via make batchalign-build-wheel, which compiles batchalign3 in release
mode, copies it into batchalign/_bin/, and then runs uv build --wheel --out-dir dist/ to produce the maturin-built wheel that
bundles both the extension and the native binary.
| Crate | Location | Purpose |
|---|---|---|
batchalign-pyo3 | crates/batchalign-pyo3/ | Worker runtime, what Python imports as batchalign_core. Deps: batchalign-types + talkbank-transform |
batchalign | crates/batchalign/ | The big Rust crate: CLI, axum server, dispatch, FA, morphosyntax, Rev.AI client, batchalign-specific CHAT extraction/injection in chat_ops/. NOT consumed by batchalign-pyo3. |
batchalign-types | crates/batchalign-types/ | Shared domain types and worker IPC types (depended on by both batchalign and batchalign-pyo3) |
batchalign-transform | crates/batchalign-transform/ | Pipelines, CHAT↔JSON, morphosyntax, Cantonese normalization, tokenizer realignment, asr_postprocess: the shared CHAT-aware logic the pyo3 worker calls into |
Module organization
The PyO3 crate is a slim worker runtime. All CHAT manipulation lives
in the sibling batchalign crate and is called directly by the Rust
runtime, never via callbacks from Python.
| Module | Purpose |
|---|---|
lib.rs | Module registration |
worker_protocol.rs | IPC message dispatch (health, capabilities, infer, execute_v2) |
worker_execute.rs | Shared executor control plane: request validation, failure taxonomy, response building |
worker_text_exec.rs | Batched text-task executors (morphosyntax, utseg, translate, coref) |
worker_asr_exec.rs | ASR execution (Whisper, Cantonese providers) |
worker_fa_exec.rs | Forced alignment execution |
worker_media_exec.rs | Speaker diarization, OpenSMILE, AVQI |
worker_text_results.rs | Text task normalization + align_tokens |
worker_artifacts.rs | Prepared artifact loading from IPC attachments |
cantonese_asr_bridge.rs | Cantonese provider projection + normalization |
py_json_bridge.rs | Python ↔ JSON conversion utility |
For exact line counts and the current module-by-module shape, read
the source under crates/batchalign-pyo3/src/.
Key PyO3 entry points
Worker protocol
prepare_protocol_message(...): admit a constructor-private pending request or return an immediate reader reply for rejection/shutdown.dispatch_protocol_message(...): execute only an admitted pending request through typed Python handlers and return its response payload. Shutdown has no executable operation variant.
Worker V2 executors
| Function | Purpose |
|---|---|
execute_asr_request_v2(...) | Load prepared audio, call Whisper / Cantonese provider |
execute_forced_alignment_request_v2(...) | Load prepared audio + text, call FA model |
execute_speaker_request_v2(...) | Load prepared audio, call pyannote / NeMo |
execute_opensmile_request_v2(...) | Load prepared audio, extract acoustic features |
execute_avqi_request_v2(...) | Load paired audio, calculate voice quality |
execute_morphosyntax_request_v2(...), execute_utseg_request_v2(...), execute_translate_request_v2(...), execute_coref_request_v2(...) | Load the prepared text batch and check its item count, call the Python runner adapter, then normalize the host’s BatchInferResponse into the typed V2 result (normalize_<task>_result in worker_text_results.rs) |
Utilities
| Function | Purpose |
|---|---|
align_tokens(...) | Map Stanza tokenizer output back to CHAT words |
| Cantonese bridge functions | Project FunASR / Tencent / Aliyun output into common shapes |
Cantonese normalization is not on this list any more. It was exported as
normalize_cantonese and cantonese_char_tokens until 2026-09-16, for Python
callers production did not have; it now has one owner,
AlignedNormalization in batchalign-transform, applied by the server.
These functions are internal, they exist for the Rust runtime to call into the worker process. They are not part of any public API surface and external code should not import them.
What was removed (historical context)
The PyO3 surface previously exposed a much larger set of callback methods and standalone Python-facing functions. They were removed as the Rust runtime grew enough to own the CHAT lifecycle directly:
ParsedChatclass and all its callback methods (parse,serialize,add_morphosyntax,add_forced_alignment, etc.)run_provider_pipeline()and provider-pipeline helpers- Standalone functions:
build_chat,parse_and_serialize,extract_nlp_words,wer_compute,wer_metrics,dp_align, etc. - All inner-function modules:
morphosyntax_ops,fa_ops,text_ops,speaker_ops,cleanup_ops,tier_ops
All domain logic now lives in the Rust batchalign crate (with
inline tests there) and is exercised through the Rust runtime.
Tree-sitter grammar
The CHAT grammar lives in this same workspace at grammar/. After
editing grammar/grammar.js, regenerate the C parser:
cd grammar && tree-sitter generate
This regenerates parser.c, which the tree-sitter parser depends on.
Forgetting this step causes the parser to use a stale grammar.
After grammar changes, always test against real corpus data in
addition to the curated test suite. See the parent
talkbank-tools/CLAUDE.md “Grammar Change Workflow” section for the
full mandatory sequence.
Building for development
When you change the PyO3 bridge or the shared Rust logic that worker processes consume, rebuild the extension and reinstall it into the dev environment:
make batchalign-python-prepare
This depends on batchalign-build-wheel, which rebuilds the native
batchalign3 binary in release mode, copies it into
batchalign/_bin/, and produces a maturin-built wheel via uv build --wheel. The prepare target then runs uv sync --group dev --no-install-project and uv pip install --reinstall --no-deps dist/*.whl to install the freshly built wheel into the active
uv environment.
For day-to-day editable iteration on the PyO3 layer, plain
uv run <anything> will trigger an incremental rebuild against
pyproject.toml’s maturin backend (profile = "dev").
If you also plan to run the standalone Rust CLI directly after a shared-crate change, rebuild that binary too:
cargo build -p batchalign
Running Rust tests
cargo test --manifest-path crates/batchalign-pyo3/Cargo.toml
The parser integration suite lives in the chatter repository now, not here:
talkbank-parser-tests is consumed as a git dependency, so -p cannot
select it from this workspace. Run it from a chatter checkout.
GIL release strategy
All pure-Rust batchalign_core entry points release the Python GIL
via py.detach() (pyo3 0.29). This lets other Python threads run
while Rust does CPU-bound work.
The few entry points that take Python callbacks hold the GIL only during the callback invocation; outside of that they release. The pattern is:
- Release GIL, walk Rust data, collect inputs.
- Acquire GIL, call the Python callback.
- Release GIL, process the callback’s result.
This means CPU-bound Rust work doesn’t block other Python threads, while the callback (which runs Python model inference) holds the GIL as expected.
Workflow: adding a new worker-side capability
The “add a new CHAT transformation” workflow used to involve adding
#[pymethods] on a ParsedChat class that no longer exists. The
current workflow for adding capability to the worker side is:
- Decide where the work belongs. Pure CHAT/AST work belongs in
the sibling
batchaligncrate (Rust), not inbatchalign-pyo3. Only ML inference and provider-side glue go in the PyO3 layer. - If the work is ML inference, add the executor in the appropriate
worker_*_exec.rsfile undercrates/batchalign-pyo3/src/and wire it into the IPC dispatch. - Add Rust tests in the same crate.
- If you changed shared types in
batchalign-types, regenerate any IPC type mirrors. - Rebuild
batchalign_core(make batchalign-python-prepare, or justuv run …for incremental dev rebuilds). - Test against real corpus data, not just unit tests.
If you find yourself wanting to call the new function from Python “directly” rather than through the worker IPC dispatch, that’s a sign the work belongs in the Rust runtime instead.
This page last changed: 2026-09-16 (commit 197c81e6). The whole book last changed: 2026-09-16 (commit 34d249d8).