Skip to content

Commit ce9cb3b

Browse files
Dokotelaclaude
andcommitted
Add vocab_log_probs to CTC and offline NeMo transducer decoders
Captures full vocabulary distribution for CTC (SenseVoice, MedASR, paraformer, zipformer) and offline NeMo transducer (parakeet-tdt, nemotron, nemo-fastconformer) decoders. Propagates through Convert functions and exposes via Python pybind11 binding. Addresses reviewer feedback to put log_probs inside the result struct. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eb83f41 commit ce9cb3b

8 files changed

Lines changed: 27 additions & 1 deletion

sherpa-onnx/csrc/offline-ctc-decoder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ struct OfflineCtcDecoderResult {
3131
/// May be empty if not provided by the decoder.
3232
/// If populated, token_log_probs.size() == tokens.size()
3333
std::vector<float> token_log_probs;
34+
35+
/// Full vocabulary log probabilities at each decoded token position.
36+
/// vocab_log_probs[i] has vocab_size entries for tokens[i].
37+
/// May be empty if not captured by the decoder.
38+
std::vector<std::vector<float>> vocab_log_probs;
3439
};
3540

3641
class OfflineCtcDecoder {

sherpa-onnx/csrc/offline-ctc-greedy-search-decoder.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ std::vector<OfflineCtcDecoderResult> OfflineCtcGreedySearchDecoder::Decode(
3535
int32_t idx = MaxElementIndex(p_log_probs, vocab_size);
3636
auto y = static_cast<int64_t>(idx);
3737
float log_prob = p_log_probs[idx];
38-
p_log_probs += vocab_size;
3938

4039
if (y != blank_id_ && y != prev_id) {
4140
r.tokens.push_back(y);
4241
r.timestamps.push_back(t);
4342
r.token_log_probs.push_back(log_prob);
43+
r.vocab_log_probs.emplace_back(p_log_probs, p_log_probs + vocab_size);
4444
}
45+
46+
p_log_probs += vocab_size;
4547
prev_id = y;
4648
} // for (int32_t t = 0; ...)
4749

sherpa-onnx/csrc/offline-recognizer-ctc-impl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ OfflineRecognitionResult Convert(const OfflineCtcDecoderResult &src,
6262
if (i < static_cast<int32_t>(src.token_log_probs.size())) {
6363
r.token_log_probs.push_back(src.token_log_probs[i]);
6464
}
65+
if (i < static_cast<int32_t>(src.vocab_log_probs.size())) {
66+
r.vocab_log_probs.push_back(src.vocab_log_probs[i]);
67+
}
6568
}
6669

6770
if (sym_table.IsByteBpe()) {

sherpa-onnx/csrc/offline-recognizer-sense-voice-impl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ OfflineRecognitionResult ConvertSenseVoiceResult(
5858
for (int32_t i = start; i < static_cast<int32_t>(src.token_log_probs.size()); ++i) {
5959
r.token_log_probs.push_back(src.token_log_probs[i]);
6060
}
61+
for (int32_t i = start; i < static_cast<int32_t>(src.vocab_log_probs.size()); ++i) {
62+
r.vocab_log_probs.push_back(src.vocab_log_probs[i]);
63+
}
6164

6265
if (!is_funasr_nano) {
6366
// parse lang, emotion and event from tokens.

sherpa-onnx/csrc/offline-recognizer-transducer-impl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ static OfflineRecognitionResult Convert(
7575
// Copy token log probabilities (confidence scores)
7676
r.ys_log_probs = src.ys_log_probs;
7777

78+
// Copy full vocabulary log probabilities
79+
r.vocab_log_probs = src.vocab_log_probs;
80+
7881
return r;
7982
}
8083

sherpa-onnx/csrc/offline-transducer-decoder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ struct OfflineTransducerDecoderResult {
2727

2828
/// ys_log_probs[i] contains the log probability (confidence) for tokens[i].
2929
std::vector<float> ys_log_probs;
30+
31+
/// Full vocabulary log probabilities at each decoded token position.
32+
/// vocab_log_probs[i] has vocab_size entries for tokens[i].
33+
/// May be empty if not captured by the decoder.
34+
std::vector<std::vector<float>> vocab_log_probs;
3035
};
3136

3237
class OfflineTransducerDecoder {

sherpa-onnx/csrc/offline-transducer-greedy-search-nemo-decoder.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ static OfflineTransducerDecoderResult DecodeOne(
8585
ans.tokens.push_back(y);
8686
ans.timestamps.push_back(t);
8787
ans.ys_log_probs.push_back(log_prob);
88+
ans.vocab_log_probs.emplace_back(p_logit, p_logit + vocab_size);
8889

8990
decoder_input_pair = BuildDecoderInput(y, model->Allocator());
9091

@@ -169,6 +170,8 @@ static OfflineTransducerDecoderResult DecodeOneTDT(
169170
ans.timestamps.push_back(t);
170171
ans.durations.push_back(skip);
171172
ans.ys_log_probs.push_back(log_prob);
173+
ans.vocab_log_probs.emplace_back(
174+
token_logits_copy.begin(), token_logits_copy.end());
172175

173176
decoder_input_pair = BuildDecoderInput(y, model->Allocator());
174177

sherpa-onnx/python/csrc/offline-stream.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ static void PybindOfflineRecognitionResult(py::module *m) { // NOLINT
4848
[](const PyClass &self) { return self.durations; })
4949
.def_property_readonly("ys_log_probs",
5050
[](const PyClass &self) { return self.ys_log_probs; })
51+
.def_property_readonly("vocab_log_probs",
52+
[](const PyClass &self) { return self.vocab_log_probs; })
5153
.def_property_readonly("segment_timestamps",
5254
[](const PyClass &self) { return self.segment_timestamps; })
5355
.def_property_readonly("segment_durations",

0 commit comments

Comments
 (0)