-
Notifications
You must be signed in to change notification settings - Fork 56
n best beam search #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Niduank
wants to merge
2
commits into
senstella:master
Choose a base branch
from
synth-inc:feat/n-best-beam-search
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| from parakeet_mlx.alignment import ( | ||
| AlignedResult, | ||
| AlignedToken, | ||
| NBestHypothesis, | ||
| SentenceConfig, | ||
| merge_longest_common_subsequence, | ||
| merge_longest_contiguous, | ||
|
|
@@ -81,6 +82,7 @@ class Greedy: | |
| @dataclass | ||
| class Beam: | ||
| beam_size: int = 5 | ||
| n_best: int = 1 # Number of hypotheses to return | ||
| length_penalty: float = 1.0 | ||
| patience: float = 1.0 | ||
| duration_reward: float = 0.7 # TDT-only | ||
|
|
@@ -292,15 +294,20 @@ def decode( | |
| hidden_state: Optional[list[Optional[tuple[mx.array, mx.array]]]] = None, | ||
| *, | ||
| config: DecodingConfig = DecodingConfig(), | ||
| ) -> tuple[list[list[AlignedToken]], list[Optional[tuple[mx.array, mx.array]]]]: | ||
| ) -> tuple[ | ||
| list[list[AlignedToken]], | ||
| list[list[NBestHypothesis]] | None, | ||
| list[Optional[tuple[mx.array, mx.array]]], | ||
| ]: | ||
| """Run TDT decoder with features, optional length and decoder state. Outputs list[list[AlignedToken]] and updated hidden state""" | ||
| mx.eval(features) | ||
|
|
||
| match config.decoding: | ||
| case Greedy(): | ||
| return self.decode_greedy( | ||
| tokens, hidden = self.decode_greedy( | ||
| features, lengths, last_token, hidden_state, config=config | ||
| ) | ||
| return tokens, None, hidden | ||
| case Beam(): | ||
| return self.decode_beam( | ||
| features, lengths, last_token, hidden_state, config=config | ||
|
|
@@ -318,7 +325,11 @@ def decode_beam( | |
| hidden_state: Optional[list[Optional[tuple[mx.array, mx.array]]]] = None, | ||
| *, | ||
| config: DecodingConfig = DecodingConfig(), | ||
| ) -> tuple[list[list[AlignedToken]], list[Optional[tuple[mx.array, mx.array]]]]: | ||
| ) -> tuple[ | ||
| list[list[AlignedToken]], | ||
| list[list[NBestHypothesis]], | ||
| list[Optional[tuple[mx.array, mx.array]]], | ||
| ]: | ||
| assert isinstance(config.decoding, Beam) # type guarntee | ||
|
|
||
| beam_token = min(config.decoding.beam_size, len(self.vocabulary) + 1) | ||
|
|
@@ -348,7 +359,10 @@ def __hash__(self) -> int: | |
| if last_token is None: | ||
| last_token = list([None] * B) | ||
|
|
||
| n_best = config.decoding.n_best | ||
|
|
||
| results = [] | ||
| results_nbest = [] | ||
| results_hidden = [] | ||
| for batch in range(B): | ||
| feature = features[batch : batch + 1] | ||
|
|
@@ -507,21 +521,47 @@ def __hash__(self) -> int: | |
|
|
||
| if not finished_hypothesis: | ||
| results.append([]) | ||
| results_nbest.append([]) | ||
| results_hidden.append(hidden_state[batch]) | ||
| else: | ||
| length_penalty = ( | ||
| config.decoding.length_penalty | ||
| ) # mypy assumes weirdly so we go in safe way | ||
|
|
||
| best = max( | ||
| # Sort hypotheses by normalized score | ||
| sorted_hyps = sorted( | ||
| finished_hypothesis, | ||
| key=lambda x: x.score | ||
| / (max(1, len(x.hypothesis)) ** length_penalty), | ||
| reverse=True, | ||
| ) | ||
|
|
||
| # Get the best hypothesis for backward compatibility | ||
| best = sorted_hyps[0] | ||
| results.append(best.hypothesis) | ||
| results_hidden.append(best.hidden_state) | ||
|
|
||
| return results, results_hidden | ||
| # Build N-best hypotheses list | ||
| n_best_hyps = [] | ||
| for hyp in sorted_hyps[:n_best]: | ||
| hyp_text = "".join( | ||
| token.text for token in hyp.hypothesis | ||
| ).strip() | ||
| hyp_len = max(1, len(hyp.hypothesis)) | ||
| normalized_score = hyp.score / hyp_len | ||
| confidence = math.exp(normalized_score) | ||
| # Clamp confidence to [0, 1] range | ||
| confidence = min(1.0, max(0.0, confidence)) | ||
| n_best_hyps.append( | ||
| NBestHypothesis( | ||
| text=hyp_text, | ||
| score=hyp.score, | ||
| confidence=confidence, | ||
| ) | ||
| ) | ||
| results_nbest.append(n_best_hyps) | ||
|
|
||
| return results, results_nbest, results_hidden | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we could have an interface other than |
||
|
|
||
| def decode_greedy( | ||
| self, | ||
|
|
@@ -626,14 +666,21 @@ def generate( | |
| features, lengths = self.encoder(mel) | ||
| mx.eval(features, lengths) | ||
|
|
||
| result, _ = self.decode(features, lengths, config=decoding_config) | ||
| tokens_result, nbest_result, _ = self.decode( | ||
| features, lengths, config=decoding_config | ||
| ) | ||
|
|
||
| return [ | ||
| sentences_to_result( | ||
| results = [] | ||
| for i, hypothesis in enumerate(tokens_result): | ||
| aligned_result = sentences_to_result( | ||
| tokens_to_sentences(hypothesis, decoding_config.sentence) | ||
| ) | ||
| for hypothesis in result | ||
| ] | ||
| # Add N-best hypotheses if available (beam search) | ||
| if nbest_result is not None and i < len(nbest_result): | ||
| aligned_result.hypotheses = nbest_result[i] | ||
| results.append(aligned_result) | ||
|
|
||
| return results | ||
|
|
||
|
|
||
| class ParakeetRNNT(BaseParakeet): | ||
|
|
@@ -660,8 +707,12 @@ def decode( | |
| hidden_state: Optional[list[Optional[tuple[mx.array, mx.array]]]] = None, | ||
| *, | ||
| config: DecodingConfig = DecodingConfig(), | ||
| ) -> tuple[list[list[AlignedToken]], list[Optional[tuple[mx.array, mx.array]]]]: | ||
| """Run TDT decoder with features, optional length and decoder state. Outputs list[list[AlignedToken]] and updated hidden state""" | ||
| ) -> tuple[ | ||
| list[list[AlignedToken]], | ||
| list[list[NBestHypothesis]] | None, | ||
| list[Optional[tuple[mx.array, mx.array]]], | ||
| ]: | ||
| """Run RNNT decoder with features, optional length and decoder state. Outputs list[list[AlignedToken]] and updated hidden state""" | ||
| assert isinstance(config.decoding, Greedy), ( | ||
| "Only greedy decoding is supported for RNNT decoder now" | ||
| ) | ||
|
|
@@ -740,7 +791,7 @@ def decode( | |
|
|
||
| results.append(hypothesis) | ||
|
|
||
| return results, hidden_state | ||
| return results, None, hidden_state | ||
|
|
||
| def generate( | ||
| self, mel: mx.array, *, decoding_config: DecodingConfig = DecodingConfig() | ||
|
|
@@ -751,14 +802,21 @@ def generate( | |
| features, lengths = self.encoder(mel) | ||
| mx.eval(features, lengths) | ||
|
|
||
| result, _ = self.decode(features, lengths, config=decoding_config) | ||
| tokens_result, nbest_result, _ = self.decode( | ||
| features, lengths, config=decoding_config | ||
| ) | ||
|
|
||
| return [ | ||
| sentences_to_result( | ||
| results = [] | ||
| for i, hypothesis in enumerate(tokens_result): | ||
| aligned_result = sentences_to_result( | ||
| tokens_to_sentences(hypothesis, decoding_config.sentence) | ||
| ) | ||
| for hypothesis in result | ||
| ] | ||
| # Add N-best hypotheses if available | ||
| if nbest_result is not None and i < len(nbest_result): | ||
| aligned_result.hypotheses = nbest_result[i] | ||
| results.append(aligned_result) | ||
|
|
||
| return results | ||
|
|
||
|
|
||
| class ParakeetCTC(BaseParakeet): | ||
|
|
@@ -1059,7 +1117,7 @@ def add_audio(self, audio: mx.array) -> None: | |
| finalized_length = max(0, length - self.drop_size) | ||
|
|
||
| if isinstance(self.model, ParakeetTDT) or isinstance(self.model, ParakeetRNNT): | ||
| finalized_tokens, finalized_state = self.model.decode( | ||
| finalized_tokens, _, finalized_state = self.model.decode( | ||
| features, | ||
| mx.array([finalized_length]), | ||
| [self.last_token], | ||
|
|
@@ -1072,7 +1130,7 @@ def add_audio(self, audio: mx.array) -> None: | |
| finalized_tokens[0][-1].id if len(finalized_tokens[0]) > 0 else None | ||
| ) | ||
|
|
||
| draft_tokens, _ = self.model.decode( | ||
| draft_tokens, _, _ = self.model.decode( | ||
| features[:, finalized_length:], | ||
| mx.array( | ||
| [ | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think implementing this as other
dataclassis a bit redundant. Perhaps we can havelist[AlignedSentence]instead?