Add TransducerRunner and rename AsrRunner to Seq2SeqRunner (#18961)#18961
Add TransducerRunner and rename AsrRunner to Seq2SeqRunner (#18961)#18961kirklandsign wants to merge 1 commit intomainfrom
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/18961
Note: Links to docs will display an error until the docs builds have been completed. ❗ 1 Active SEVsThere are 1 currently active SEVs. If your PR is affected, please view them below: ❌ 3 New Failures, 2 Unrelated FailuresAs of commit 828bce5 with merge base 35b9054 ( NEW FAILURES - The following jobs have failed:
FLAKY - The following job failed but was likely due to flakiness present on trunk:
BROKEN TRUNK - The following job failed but was present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@kirklandsign has exported this pull request. If you are a Meta employee, you can view the originating Diff in D100892465. |
|
@claude review |
This PR needs a
|
|
Claude finished @kirklandsign's task in 1m 28s —— View job Code Review: Add TransducerRunner and rename AsrRunner to Seq2SeqRunner
SummaryWell-structured refactoring that cleanly separates Seq2Seq (Whisper) and Transducer (RNN-T/TDT) ASR architectures. The extraction of ~190 lines of greedy decode logic from Issues1.
2. Redundant model loading for timestamp metadata in
This duplicates the module load cost at runtime. 3.
Minor / Nits4.
auto mel = outputs[0].toTensor();
return std::make_shared<::executorch::aten::Tensor>(std::move(mel));This works but is inconsistent with how 5. Potential out-of-bounds in encoder output access
int64_t encoded_len = enc_outputs[1].toTensor().const_data_ptr<int64_t>()[0];No bounds check on the tensor having at least one element. If the encoder returns a 0-element length tensor, this would be undefined behavior. The check at line 261-265 verifies both outputs exist and are tensors, but not that the length tensor is non-empty. 6.
7. Missing
Looks Good
|
There was a problem hiding this comment.
Pull request overview
This PR restructures extension/asr/runner/ to support both encoder-decoder (Seq2Seq, e.g. Whisper) and Transducer (RNN‑T/TDT) ASR architectures by introducing separate runner implementations and migrating example/JNI consumers.
Changes:
- Renames
AsrRunnertoSeq2SeqRunner(with backward-compatiblerunner.hredirect and type aliases). - Adds
TransducerRunnerimplementing greedy transducer decoding and optional bundled-preprocessor execution. - Migrates Whisper/Parakeet examples and Android JNI includes to the new runner headers, and updates build files (CMake/Buck).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| extension/asr/runner/transducer_runner.h | Defines new TransducerRunner API, Token type, and TransducerConfig. |
| extension/asr/runner/transducer_runner.cpp | Implements model loading, optional preprocessor invocation, and transducer greedy decode loop. |
| extension/asr/runner/seq2seq_runner.h | Defines Seq2SeqRunner API and backward-compatible AsrRunner aliases. |
| extension/asr/runner/seq2seq_runner.cpp | Renames implementation from AsrRunner to Seq2SeqRunner and updates includes. |
| extension/asr/runner/runner.h | Becomes a thin backward-compat include redirect to seq2seq_runner.h. |
| extension/asr/runner/targets.bzl | Adds Buck targets for seq2seq_runner and transducer_runner libraries. |
| extension/asr/runner/CMakeLists.txt | Builds ASR runner library from seq2seq_runner.cpp + transducer_runner.cpp. |
| extension/asr/runner/BUCK | Adds migration BUCK wrapper for shared targets. |
| extension/android/jni/jni_layer_asr.cpp | Updates include to the new Seq2Seq runner header. |
| examples/models/whisper/main.cpp | Migrates usage from AsrRunner/AsrTranscribeConfig to Seq2SeqRunner/Seq2SeqTranscribeConfig. |
| examples/models/parakeet/types.h | Switches Parakeet Token to alias shared asr::Token. |
| examples/models/parakeet/main.cpp | Replaces inline greedy decode with TransducerRunner preprocess()+transcribe(). |
| examples/models/parakeet/CMakeLists.txt | Links Parakeet example against extension_asr_runner. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| #include <cstdint> | ||
| #include <functional> | ||
| #include <memory> |
There was a problem hiding this comment.
transducer_runner.h uses std::optional/std::nullopt in the TransducerRunner constructor but does not include . Add the missing include so this header compiles when included on its own (including from transducer_runner.cpp).
| #include <memory> | |
| #include <memory> | |
| #include <optional> |
| load(":targets.bzl", "define_common_targets") | ||
|
|
||
|
|
There was a problem hiding this comment.
This second load(":targets.bzl", "define_common_targets") appears redundant (the symbol is already loaded above). Consider removing the duplicate to simplify the BUCK file.
| load(":targets.bzl", "define_common_targets") |
| ET_CHECK_OR_RETURN_ERROR( | ||
| blank_id_result.ok(), | ||
| Internal, | ||
| "Model must export 'blank_id' as a constant_method."); | ||
| blank_id_ = blank_id_result.get()[0].toInt(); |
There was a problem hiding this comment.
load_model_metadata() indexes blank_id_result.get()[0] without verifying the constant_method returned at least one value (and the expected scalar type). If the model returns an empty vector or a tensor scalar, this can crash. Consider validating output count/type before calling toInt().
| // --- Encode --- | ||
| int64_t mel_len_value = preprocessed_features->size(1); | ||
| std::vector<int64_t> mel_len_data = {mel_len_value}; | ||
| auto mel_len = ::executorch::extension::from_blob( | ||
| mel_len_data.data(), {1}, ::executorch::aten::ScalarType::Long); |
There was a problem hiding this comment.
transcribe() derives mel_len_value from preprocessed_features->size(1). If the preprocessor pads the mel tensor and provides an explicit mel_len, this will pass the padded length to the encoder. Consider threading the true mel_len through (e.g., from preprocess()) instead of inferring it from the tensor shape.
| std::vector<::executorch::runtime::EValue>{sos_token, h, c}); | ||
| ET_CHECK_OK_OR_RETURN_ERROR(decoder_init_result.error()); | ||
|
|
||
| auto& init_outputs = decoder_init_result.get(); |
There was a problem hiding this comment.
decoder_init_result.get() is indexed at [0..2] without checking the output count/types. If decoder_step returns fewer outputs (or non-tensors), this will read out of bounds. Add an explicit output validation similar to the encoder check before indexing.
| auto& init_outputs = decoder_init_result.get(); | |
| auto& init_outputs = decoder_init_result.get(); | |
| if (init_outputs.size() < 3 || !init_outputs[0].isTensor() || | |
| !init_outputs[1].isTensor() || !init_outputs[2].isTensor()) { | |
| ET_LOG( | |
| Error, | |
| "Method %s returned invalid outputs: expected at least 3 tensor outputs, got %zu", | |
| kDecoderStepMethodName, | |
| init_outputs.size()); | |
| return Error::InvalidArgument; | |
| } |
| struct Token { | ||
| uint64_t id; | ||
| int64_t start_offset; // Frame index in the encoder output | ||
| int64_t duration; // TDT duration value (0 for standard RNN-T) |
There was a problem hiding this comment.
Token::duration is documented as "0 for standard RNN-T", but the implementation uses dur=1 when durations is empty (and TransducerConfig also describes standard RNN-T as duration==1). Please reconcile the docs/contract so downstream timestamp code can interpret duration consistently.
| int64_t duration; // TDT duration value (0 for standard RNN-T) | |
| int64_t duration; // Duration in encoder frames (1 for standard RNN-T) |
| // TDT duration values. Empty means standard RNN-T (duration always 1). | ||
| // If the model exports a "durations" constant_method, it will be used | ||
| // instead of this field. |
There was a problem hiding this comment.
TransducerConfig says a model-exported "durations" constant_method will override config.durations, but TransducerRunner::load()/load_model_metadata() never reads such a constant_method. Either implement that override or update the comment to match actual behavior.
| // TDT duration values. Empty means standard RNN-T (duration always 1). | |
| // If the model exports a "durations" constant_method, it will be used | |
| // instead of this field. | |
| // TDT duration values used by the decode loop. Empty means standard | |
| // RNN-T (duration always 1). |
| #pragma once | ||
|
|
||
| #include <functional> | ||
| #include <memory> |
There was a problem hiding this comment.
seq2seq_runner.h uses std::optional in the Seq2SeqRunner constructor signature but doesn't include . It currently relies on transitive includes; please add to keep the header self-contained and avoid include-order fragility.
| #include <memory> | |
| #include <memory> | |
| #include <optional> |
| Internal, | ||
| "Preprocessor returned unexpected output."); | ||
|
|
||
| auto mel = outputs[0].toTensor(); | ||
| return std::make_shared<::executorch::aten::Tensor>(std::move(mel)); |
There was a problem hiding this comment.
preprocess() only consumes outputs[0] from the preprocessor, but the header documents the method as returning (mel, mel_len). If mel_len is provided (or mel is padded), dropping it can cause transcribe() to pass an incorrect length to the encoder. Consider consuming outputs[1] when present and using it to compute/override mel_len.
| int64_t k = joint_result.get()[0].toTensor().const_data_ptr<int64_t>()[0]; | ||
|
|
||
| // Compute frame advance duration | ||
| int64_t dur = 1; // default for standard RNN-T | ||
| if (is_tdt) { |
There was a problem hiding this comment.
joint_result.get() is indexed at [0] (and [1] when is_tdt) without validating the number/type of outputs. Add a guard that checks output count and that the expected outputs are tensors/scalars before reading const_data_ptr<int64_t>().
Summary: Pull Request resolved: #18961 Restructure `extension/asr/runner/` to support both Seq2Seq and Transducer ASR architectures: 1. **Rename** `AsrRunner` → `Seq2SeqRunner` (with backward-compat alias in runner.h) - `runner.cpp` → `seq2seq_runner.cpp`, `runner.h` → `seq2seq_runner.h` - Old `runner.h` kept as a thin include redirect for backward compatibility 2. **Add** `TransducerRunner` for RNN-T/TDT models - Extracts the ~190-line greedy decode loop from `examples/models/parakeet/main.cpp` - Auto-reads model metadata (blank_id, num_rnn_layers, pred_hidden) from constant_methods - Supports both standard RNN-T (duration=1) and TDT (variable durations) - Returns `vector<Token>` with frame offsets for downstream timestamp computation - Includes `preprocess()` method for models with bundled preprocessor - Exposes tokenizer via `tokenizer()` getter for downstream use 3. **Unify Token type**: `parakeet::Token` is now an alias for `asr::Token` 4. **Migrate consumers** - Whisper `main.cpp`: `AsrRunner` → `Seq2SeqRunner` - Parakeet `main.cpp`: inline decode → `TransducerRunner::transcribe()` - Android JNI: updated include (uses backward-compat alias, no API change) Model-specific post-processing (timestamp computation) remains in `examples/models/parakeet/`. Differential Revision: D100892465
abbbb8f to
a29f1d7
Compare
Summary: Pull Request resolved: #18961 Restructure `extension/asr/runner/` to support both Seq2Seq and Transducer ASR architectures: 1. **Rename** `AsrRunner` → `Seq2SeqRunner` (with backward-compat alias in runner.h) - `runner.cpp` → `seq2seq_runner.cpp`, `runner.h` → `seq2seq_runner.h` - Old `runner.h` kept as a thin include redirect for backward compatibility 2. **Add** `TransducerRunner` for RNN-T/TDT models - Extracts the ~190-line greedy decode loop from `examples/models/parakeet/main.cpp` - Auto-reads model metadata (blank_id, num_rnn_layers, pred_hidden) from constant_methods - Supports both standard RNN-T (duration=1) and TDT (variable durations) - Returns `vector<Token>` with frame offsets for downstream timestamp computation - Includes `preprocess()` method for models with bundled preprocessor - Exposes tokenizer via `tokenizer()` getter for downstream use 3. **Unify Token type**: `parakeet::Token` is now an alias for `asr::Token` 4. **Migrate consumers** - Whisper `main.cpp`: `AsrRunner` → `Seq2SeqRunner` - Parakeet `main.cpp`: inline decode → `TransducerRunner::transcribe()` - Android JNI: updated include (uses backward-compat alias, no API change) Model-specific post-processing (timestamp computation) remains in `examples/models/parakeet/`. Differential Revision: D100892465
a29f1d7 to
67e6800
Compare
Summary: Pull Request resolved: #18961 Restructure `extension/asr/runner/` to support both Seq2Seq and Transducer ASR architectures: 1. **Rename** `AsrRunner` → `Seq2SeqRunner` (with backward-compat alias in runner.h) - `runner.cpp` → `seq2seq_runner.cpp`, `runner.h` → `seq2seq_runner.h` - Old `runner.h` kept as a thin include redirect for backward compatibility 2. **Add** `TransducerRunner` for RNN-T/TDT models - Extracts the ~190-line greedy decode loop from `examples/models/parakeet/main.cpp` - Auto-reads model metadata (blank_id, num_rnn_layers, pred_hidden) from constant_methods - Supports both standard RNN-T (duration=1) and TDT (variable durations) - Returns `vector<Token>` with frame offsets for downstream timestamp computation - Includes `preprocess()` method for models with bundled preprocessor - Exposes tokenizer via `tokenizer()` getter for downstream use 3. **Unify Token type**: `parakeet::Token` is now an alias for `asr::Token` 4. **Migrate consumers** - Whisper `main.cpp`: `AsrRunner` → `Seq2SeqRunner` - Parakeet `main.cpp`: inline decode → `TransducerRunner::transcribe()` - Android JNI: updated include (uses backward-compat alias, no API change) Model-specific post-processing (timestamp computation) remains in `examples/models/parakeet/`. Differential Revision: D100892465
67e6800 to
ec281a0
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| set(runner_deps executorch_core extension_module extension_tensor | ||
| tokenizers::tokenizers | ||
| ) | ||
|
|
||
| # Define runner library | ||
| add_library(extension_asr_runner STATIC runner.cpp) | ||
| add_library( | ||
| extension_asr_runner STATIC seq2seq_runner.cpp transducer_runner.cpp | ||
| ) |
There was a problem hiding this comment.
extension_asr_runner depends on non-header-only LLM runner utilities (e.g., load_tokenizer/get_eos_ids from llm_runner_helper.cpp and Sampler from sampler.cpp). The CMake target currently links only executorch_core/extension_module/extension_tensor/tokenizers, so consumers that link just extension_asr_runner may hit undefined references. Consider adding extension_llm_runner (and extension_llm_sampler for Seq2SeqRunner) to runner_deps/target_link_libraries to match the BUCK deps in extension/asr/runner/targets.bzl.
| * Required module methods: | ||
| * - "encoder": (mel, mel_len) -> (f_proj, encoded_len) | ||
| * - "decoder_step": (token, h, c) -> (g_proj, h', c') | ||
| * - "joint": (f_t, g_proj) -> (token_id, duration_idx) |
There was a problem hiding this comment.
transducer_runner.h docs say the "joint" method returns (token_id, duration_idx), but transducer_runner.cpp treats duration_idx as optional (it only requires a second output when config.durations is non-empty). Please update the required method signature comment to match actual behavior (e.g., (token_id) for standard RNN-T, and (token_id, duration_idx) for TDT).
| * - "joint": (f_t, g_proj) -> (token_id, duration_idx) | |
| * - "joint": (f_t, g_proj) -> (token_id) for standard RNN-T, | |
| * or (token_id, duration_idx) for TDT |
| int64_t audio_len = raw_audio->numel(); | ||
| std::vector<int64_t> audio_len_data = {audio_len}; | ||
| auto audio_len_tensor = ::executorch::extension::from_blob( | ||
| audio_len_data.data(), {1}, ::executorch::aten::ScalarType::Long); | ||
|
|
||
| auto result = module_->execute( | ||
| kPreprocessorMethodName, | ||
| std::vector<::executorch::runtime::EValue>{raw_audio, audio_len_tensor}); | ||
| ET_CHECK_OK_OR_RETURN_ERROR(result.error()); | ||
|
|
||
| auto& outputs = result.get(); | ||
| ET_CHECK_OR_RETURN_ERROR( | ||
| !outputs.empty() && outputs[0].isTensor(), | ||
| Internal, | ||
| "Preprocessor returned unexpected output."); | ||
|
|
||
| auto mel = outputs[0].toTensor(); | ||
| return std::make_shared<::executorch::aten::Tensor>(std::move(mel)); | ||
| } |
There was a problem hiding this comment.
preprocess() calls the model's "preprocessor" which (per the header comment) returns (mel, mel_len), but the implementation only validates/returns outputs[0] and discards mel_len. transcribe() then synthesizes mel_len from features->size(1), which can be wrong if the preprocessor pads to a fixed length. Consider returning both (features, features_len) from preprocess(), or accepting an explicit mel_len/length tensor in transcribe(), and validate the preprocessor output arity accordingly.
| enc_outputs.size()); | ||
|
|
||
| auto f_proj = enc_outputs[0].toTensor(); // [B, T, joint_hidden] | ||
| int64_t encoded_len = enc_outputs[1].toTensor().const_data_ptr<int64_t>()[0]; |
There was a problem hiding this comment.
encoded_len is read from the encoder's second output and then used to index into f_proj as if it were <= f_proj.size(1). If encoded_len is larger than the actual time dimension (e.g., corrupted model output), the decode loop will read past f_proj's buffer. Add a sanity check that encoded_len is within [0, f_proj.size(1)] before using it.
| int64_t encoded_len = enc_outputs[1].toTensor().const_data_ptr<int64_t>()[0]; | |
| int64_t encoded_len = enc_outputs[1].toTensor().const_data_ptr<int64_t>()[0]; | |
| ET_CHECK_OR_RETURN_ERROR( | |
| encoded_len >= 0 && encoded_len <= f_proj.size(1), | |
| InvalidArgument, | |
| "Encoder returned invalid encoded_len=%ld for f_proj time dimension=%ld", | |
| static_cast<long>(encoded_len), | |
| static_cast<long>(f_proj.size(1))); |
| init_outputs.size()); | ||
| auto g_proj_init = init_outputs[0].toTensor(); | ||
| auto new_h_init = init_outputs[1].toTensor(); | ||
| auto new_c_init = init_outputs[2].toTensor(); |
There was a problem hiding this comment.
These memcpy() calls assume the decoder_step outputs (h, c) have exactly the expected byte sizes derived from num_rnn_layers_/pred_hidden_ and the input dtypes. If the model exports different shapes/dtypes, this can read/write out of bounds. Please validate output tensor numel()/nbytes() matches the destination buffers before memcpy (and return an error if mismatched).
| auto new_c_init = init_outputs[2].toTensor(); | |
| auto new_c_init = init_outputs[2].toTensor(); | |
| size_t new_h_init_num_bytes = | |
| static_cast<size_t>(new_h_init.numel()) * | |
| ::executorch::runtime::elementSize(new_h_init.scalar_type()); | |
| size_t new_c_init_num_bytes = | |
| static_cast<size_t>(new_c_init.numel()) * | |
| ::executorch::runtime::elementSize(new_c_init.scalar_type()); | |
| ET_CHECK_OR_RETURN_ERROR( | |
| new_h_init.scalar_type() == h_dtype, | |
| InvalidArgument, | |
| "Method %s returned h with unexpected dtype.", | |
| kDecoderStepMethodName); | |
| ET_CHECK_OR_RETURN_ERROR( | |
| new_c_init.scalar_type() == c_dtype, | |
| InvalidArgument, | |
| "Method %s returned c with unexpected dtype.", | |
| kDecoderStepMethodName); | |
| ET_CHECK_OR_RETURN_ERROR( | |
| new_h_init_num_bytes == h_data.size(), | |
| InvalidArgument, | |
| "Method %s returned h with %zu bytes, expected %zu.", | |
| kDecoderStepMethodName, | |
| new_h_init_num_bytes, | |
| h_data.size()); | |
| ET_CHECK_OR_RETURN_ERROR( | |
| new_c_init_num_bytes == c_data.size(), | |
| InvalidArgument, | |
| "Method %s returned c with %zu bytes, expected %zu.", | |
| kDecoderStepMethodName, | |
| new_c_init_num_bytes, | |
| c_data.size()); |
| auto new_g_proj = outputs[0].toTensor(); | ||
| auto new_h = outputs[1].toTensor(); | ||
| auto new_c = outputs[2].toTensor(); | ||
| std::memcpy(h_data.data(), new_h.const_data_ptr(), h_data.size()); | ||
| std::memcpy(c_data.data(), new_c.const_data_ptr(), c_data.size()); | ||
| std::memcpy( | ||
| g_proj_data.data(), new_g_proj.const_data_ptr(), g_proj_data.size()); |
There was a problem hiding this comment.
These memcpy() calls assume new_h/new_c/new_g_proj have byte sizes matching the preallocated h_data/c_data/g_proj_data buffers. If the model returns tensors with different shapes (or if proj_dim differs), this can cause out-of-bounds reads/writes. Please check tensor nbytes() (or numel()*elem_size) against the destination buffer sizes and return an error on mismatch.
| #include <executorch/extension/asr/runner/transducer_runner.h> | ||
|
|
||
| namespace parakeet { | ||
|
|
||
| // Matches output type of tokenizers::Tokenizer methods | ||
| // Use the shared Token type from the ASR runner. | ||
| using Token = ::executorch::extension::asr::Token; | ||
| using TokenId = uint64_t; |
There was a problem hiding this comment.
examples/models/parakeet/types.h now includes transducer_runner.h just to alias the Token struct. That header pulls in Module, tokenizer, and stats headers, which couples this example's basic types to the full runner implementation and can noticeably increase rebuild costs. Consider moving executorch::extension::asr::Token into a small dedicated header (e.g., token.h) with minimal includes, and include that here instead.
larryliu0820
left a comment
There was a problem hiding this comment.
Thank you for consolidating parakeet runner!
|
@claude review |
|
Claude finished @kirklandsign's task in 1m 33s —— View job Code Review: Add TransducerRunner and rename AsrRunner to Seq2SeqRunner
Good restructuring overall. The separation of Seq2Seq and Transducer runners is clean, and the extraction from Issues1. Unsafe The 2. Standard RNN-T decode loop advances time on non-blank emissions — In the non-blank branch, 3.
4. The header documents the preprocessor as returning 5. Redundant module load for timestamp metadata in A second 6. CMake The Buck targets in Minor
Looks Good
|
Summary: Pull Request resolved: #18961 Restructure `extension/asr/runner/` to support both Seq2Seq and Transducer ASR architectures: 1. **Rename** `AsrRunner` → `Seq2SeqRunner` (with backward-compat alias in runner.h) - `runner.cpp` → `seq2seq_runner.cpp`, `runner.h` → `seq2seq_runner.h` - Old `runner.h` kept as a thin include redirect for backward compatibility 2. **Add** `TransducerRunner` for RNN-T/TDT models - Extracts the ~190-line greedy decode loop from `examples/models/parakeet/main.cpp` - Auto-reads model metadata (blank_id, num_rnn_layers, pred_hidden) from constant_methods - Supports both standard RNN-T (duration=1) and TDT (variable durations) - Returns `vector<Token>` with frame offsets for downstream timestamp computation - Includes `preprocess()` method for models with bundled preprocessor - Exposes tokenizer via `tokenizer()` getter for downstream use 3. **Unify Token type**: `parakeet::Token` is now an alias for `asr::Token` 4. **Migrate consumers** - Whisper `main.cpp`: `AsrRunner` → `Seq2SeqRunner` - Parakeet `main.cpp`: inline decode → `TransducerRunner::transcribe()` - Android JNI: updated include (uses backward-compat alias, no API change) Model-specific post-processing (timestamp computation) remains in `examples/models/parakeet/`. Differential Revision: D100892465
ec281a0 to
828bce5
Compare
Summary:
Restructure
extension/asr/runner/to support both Seq2Seq and Transducer ASR architectures:Rename
AsrRunner→Seq2SeqRunner(with backward-compat alias in runner.h)runner.cpp→seq2seq_runner.cpp,runner.h→seq2seq_runner.hrunner.hkept as a thin include redirect for backward compatibilityAdd
TransducerRunnerfor RNN-T/TDT modelsexamples/models/parakeet/main.cppvector<Token>with frame offsets for downstream timestamp computationpreprocess()method for models with bundled preprocessortokenizer()getter for downstream useUnify Token type:
parakeet::Tokenis now an alias forasr::TokenMigrate consumers
main.cpp:AsrRunner→Seq2SeqRunnermain.cpp: inline decode →TransducerRunner::transcribe()Model-specific post-processing (timestamp computation) remains in
examples/models/parakeet/.Differential Revision: D100892465