Skip to content

Conversation

alubbe
Copy link
Contributor

@alubbe alubbe commented Aug 28, 2025

This PR is bringing over the --carry-initial-prompt flag from the python library (openai/whisper#2343)

By default, an --prompt (initial prompt) is only used for the first decoding window; subsequent windows rely on the text generated so far for continuity. When you pass --carry-initial-prompt, the initial prompt tokens are explicitly prepended to every internal decode window. This mirrors the Python reference implementation's carry_initial_prompt behavior and can help enforce custom vocabulary or style throughout long transcriptions. Trade‑off: it may slightly reduce the model's ability to adapt dynamically to newly generated context (can increase risk of repetitions if the prompt is long). If the combined size of the carried initial prompt and the rolling context exceeds half the model text context, the leftmost (oldest) part of the initial prompt is truncated to fit.

Copy link
Collaborator

@KitaitiMakoto KitaitiMakoto left a comment

Choose a reason for hiding this comment

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

Patches for Ruby are nice, though I'm not sure the essential changes and API are accepted. Let me point just a thing.

@alubbe
Copy link
Contributor Author

alubbe commented Sep 8, 2025

Changes applied - let me know what you think of this PR

@alubbe alubbe requested a review from KitaitiMakoto September 8, 2025 11:02
@alubbe
Copy link
Contributor Author

alubbe commented Sep 25, 2025

@ggerganov could I ask you to review this PR?

@alubbe
Copy link
Contributor Author

alubbe commented Oct 7, 2025

any update on this? Happy to resolve the conflict in cli.cpp if I can get a general feeling of whether you're interested in merging this functionality or not. We've been using this branch for weeks now and have observed a much improved transcription from whisper when it comes to unusual names (people, places, companies, etc.) from carrying the initial prompt.

@ggerganov
Copy link
Member

Hi, apologies for the long wait. I'm interested in adding this functionality, but I am having difficulty following the implemented logic for prepending the initial prompt. Would like to see this simplified in some way. I'll try to add some suggestions how to improve it.

Copy link
Member

@ggerganov ggerganov left a comment

Choose a reason for hiding this comment

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

I think the main complexity comes from using a single prompt_past vector in the whisper_state which results in some convoluted logic for deduplicating and slicing the tokens.

I expect that the logic can become much simpler if you replace prompt_past with 2 vectors: prompt_past0 and prompt_past1. The full prompt is a concatenation of prompt_past0 + prompt_past1. The prompt_past0 can be utilized to store some static prefix - i.e. the original prompt that is being carried.

@alubbe
Copy link
Contributor Author

alubbe commented Oct 8, 2025

That's a good point. I tried taking a bit further and simplifying it as much as I could - what do you think?

@alubbe
Copy link
Contributor Author

alubbe commented Oct 8, 2025

Pushed PR fixes - let me know what you think

@alubbe
Copy link
Contributor Author

alubbe commented Oct 10, 2025

I've included your suggestion, and made two more further simplification

  1. Since n_take0 = std::min<int>(max_ctx_half - 1, prompt_past0.size()), we know n_take0 <= prompt_past0.size(), so we can get rid of that ternary.
  2. Inserting from an empty range is a no-op, so these checks are redundant, so we can remove the if (n_take0 > 0) and if (n_take1 > 0) checks

@alubbe
Copy link
Contributor Author

alubbe commented Oct 10, 2025

I did another check of all the logic in here and I think I found another issue - we only want to run line 7569 outside the carry_initial_prompt mode because prompt_past1 contains part of prompt_past0, so we're duplicating content

Comment on lines +7567 to +7570
// update prompt_past1
prompt_past1.clear();
if (!params.carry_initial_prompt && !prompt.empty() && prompt.front() == whisper_token_prev(ctx)) {
prompt_past1.insert(prompt_past1.end(), prompt.begin() + 1, prompt.end() - prompt_init.size());
Copy link
Member

Choose a reason for hiding this comment

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

An alternative would be to preserve the original behaviour by using the fact that prompt.size() > prompt_past0.size():

Suggested change
// update prompt_past1
prompt_past1.clear();
if (!params.carry_initial_prompt && !prompt.empty() && prompt.front() == whisper_token_prev(ctx)) {
prompt_past1.insert(prompt_past1.end(), prompt.begin() + 1, prompt.end() - prompt_init.size());
// update prompt_past1
prompt_past1.clear();
if (!prompt.empty() && prompt.front() == whisper_token_prev(ctx)) {
prompt_past1.insert(prompt_past1.end(), prompt.begin() + 1 + prompt_past0.size(), prompt.end() - prompt_init.size());

Though make sure to double-check this works as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that this assumes the entire prompt_past0 is always included in the prompt, but that's not guaranteed. For example, if max_ctx_half - 1 < prompt_past0.size(), we only take a tail of prompt_past0, not all of it.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, good point. Should we truncate prompt_past0 upon initialization so that it does not exceed the max_ctx_half?

Co-authored-by: Georgi Gerganov <[email protected]>
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