Skip to content

Add recognition context support for Qwen3-ASR - #144

Open
yukukotani wants to merge 1 commit into
handy-computer:mainfrom
yuku-contrib:branch
Open

Add recognition context support for Qwen3-ASR#144
yukukotani wants to merge 1 commit into
handy-computer:mainfrom
yuku-contrib:branch

Conversation

@yukukotani

@yukukotani yukukotani commented Aug 25, 2026

Copy link
Copy Markdown

Adds a shared recognition-context option across the public API, CLI, and language bindings, with Qwen3-ASR as the only model family that currently advertises and consumes it.

Why

Qwen3-ASR can use background text such as names, jargon, and domain terminology to improve recognition, but transcribe.cpp did not expose a way to provide that context. Reusing Whisper's initial_prompt would conflate two model-specific semantics and would not provide a consistent option across single, batch, and streaming APIs.

What

Core API and Qwen3-ASR

  • Append context to transcribe_run_params and add the independent TRANSCRIBE_FEATURE_CONTEXT capability while preserving size-aware ABI compatibility.
  • Copy streaming context into session-owned storage and warn, ignore, and continue when a model does not support recognition context.
  • Insert Qwen3-ASR context into the system turn for single and batch decoding without normalization or truncation; context tokens count toward the decoder window.

CLI and bindings

  • Add --context TEXT to the CLI.
  • Expose recognition context through Python, TypeScript/JavaScript, Rust, Swift, and the Objective-C-compatible C module.
  • Regenerate FFI layouts and update ABI hashes.

Tests and documentation

  • Cover defaults, legacy struct prefixes, forwarding, unsupported-model behavior, streaming lifetime, prompt-token parity, input limits, CLI paths, and binding surfaces.
  • Document recognition-context semantics and Qwen3-ASR input-limit behavior.

Notes

  • Recognition context is intentionally separate from Whisper's initialPrompt / initial_prompt. Whisper's option is a Whisper-specific decoder prompt: it conditions decoding as transcript text preceding the current audio and can carry vocabulary, capitalization, punctuation, or style into the output. Qwen3-ASR recognition context is background information placed in the chat template's system turn to bias recognition toward names, jargon, and domain terminology. It is not prior transcript text, and it does not promise instruction following, translation, output formatting, punctuation, or style control.
  • context is implemented in the common transcribe_run_params API rather than as a Qwen3-ASR-specific extension. This follows the earlier review discussion on PR #83, which noted that multiple model families have analogous recognition-context capabilities and that the option therefore belongs in the main API. Qwen3-ASR is currently the only family that advertises and consumes the capability; unsupported families warn and ignore it.

@yukukotani
yukukotani marked this pull request as ready for review August 25, 2026 15:03
@yukukotani
yukukotani requested a review from cjpais as a code owner August 25, 2026 15:03
@cjpais

cjpais commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

To be honest, this isn't going to be pulled in unless it supports all of the other families that also support similar kinds of things. We need to have a very good API for this that's exposed to users that's not really model specific. So I need someone to think about this more concretely before I pull it in. This is something I'm also thinking about. I just don't have time to write the code at the moment

@yukukotani

Copy link
Copy Markdown
Author

@cjpais
Thanks. I agree that the current const char * context is too narrow for a common API.

I initially limited the implementation to Qwen3-ASR because the other “similar kinds of things” have different model contracts. Some accept recognition background, some accept hotword lists, and others consume transcript text associated with previous or current audio. Treating all of them as one string would hide those differences.

I reviewed the relevant model and publisher interfaces:

Family Model input
Qwen3-ASR Free form recognition prompt and hotwords in the system turn
Granite Speech AR Keyword list biasing
Granite Speech Plus Keyword list biasing and a known transcript prefix for the same supplied audio
FunASR-Nano hotwords: list[str] rendered into the user prompt
MOSS Hotwords appended to the default transcription prompt
Whisper Transcript text preceding the current audio

General task instructions are intentionally excluded. Voxtral audio understanding prompts and MOSS custom prompts can change the task or output format. Canary-Qwen does not document a free form recognition context in ASR mode.

NeMo also provides decoder phrase boosting for Canary and Parakeet. However, transcribe.cpp uses its own decoders and does not implement NeMo’s boosting tree or score fusion. Supporting those families requires a new decoder feature, so I propose leaving them out of the initial implementation. They can use the same public hotword field later.

The remaining supported families expose only existing controls such as language, translation, PNC, ITN, timestamps, and diarization, or have no comparable text input.

A single string is insufficient because these inputs carry different information. In particular, hotword boundaries cannot be recovered from a value such as "New York, ACME, Inc.".

I propose the following flat fields:

Field Meaning Initial families
prompt Free form recognition prompt or background, not a general task instruction Qwen3-ASR
hotwords Ordered recognition bias terms or multi word phrases Qwen3-ASR, Granite AR, FunASR-Nano, MOSS
previousTranscript Transcript corresponding to audio before the supplied PCM Whisper
transcriptPrefix Known transcript corresponding to the beginning of the supplied PCM Granite Speech Plus

C

struct transcribe_context {
    uint64_t struct_size;

    const char * prompt;

    const char * const * hotwords;
    size_t                 n_hotwords;

    const char * previous_transcript;
    const char * transcript_prefix;
};

TRANSCRIBE_API void transcribe_context_init(
    struct transcribe_context * context
);

struct transcribe_run_params {
    /* existing fields */
    const struct transcribe_context * context;
};

Capabilities would be defined for each field:

TRANSCRIBE_FEATURE_CONTEXT_PROMPT
TRANSCRIBE_FEATURE_CONTEXT_HOTWORDS
TRANSCRIBE_FEATURE_CONTEXT_PREVIOUS_TRANSCRIPT
TRANSCRIBE_FEATURE_CONTEXT_TRANSCRIPT_PREFIX

TypeScript

export interface TranscriptionContext {
  prompt?: string;
  hotwords?: readonly string[];
  previousTranscript?: string;
  transcriptPrefix?: string;
}

export interface TranscribeOptions {
  context?: TranscriptionContext;
}

Whisper’s existing initialPrompt can remain for compatibility.

If this direction seems useful, I am happy to continue with it. If you already have another API direction in mind, or would prefer that I wait for your design, I am also happy to pause here. I have not pushed any redesign yet.

@moribm

moribm commented Aug 27, 2026

Copy link
Copy Markdown

Wouldn't it be simpler to expose two top-level options and leave everything else as family extensions?

  • Prompt - free form recognition text (Qwen3-ASR / Whisper / etc)

  • Keyword biasing - Keyword/hotword biasing (Granite / MOSS / FunASR / etc)

Does the end user of transcribe.cpp really care about the distinction between previousTranscript vs transcriptPrefix vs recognitionPrompt? In practice those all reduce to "here's some text to condition on". The details can be explained in the documentation.

@yukukotani

Copy link
Copy Markdown
Author

@moribm Thanks, that is a valid option. My concern is that a future model could support both a recognition prompt and a transcript prefix. If that distinction exists only in family extensions, the meaning of the common prompt becomes ambiguous across families. So I see the appeal, but I am not yet sure it gives us a clear common contract.

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.

3 participants