Skip to content

Commit 1ddcd43

Browse files
Dokotelaclaude
andcommitted
Move vocab_log_probs into result structs per reviewer feedback
- Add vocab_log_probs and vocab_size fields to both SherpaOnnxOnlineRecognizerResult and SherpaOnnxOfflineRecognizerResult, following the same pattern as timestamps - Remove the separate SherpaOnnxVocabLogProbs struct and its getter/destroy functions from the C API - Update Dart bindings to read vocab_log_probs from the result struct via recognizer.getVocabLogProbs(stream) instead of stream.getVocabLogProbs() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ce9cb3b commit 1ddcd43

7 files changed

Lines changed: 182 additions & 127 deletions

File tree

flutter/sherpa_onnx/lib/src/offline_recognizer.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,29 @@ class OfflineRecognizer {
13131313
SherpaOnnxBindings.decodeOfflineStream?.call(ptr, stream.ptr);
13141314
}
13151315

1316+
/// Fetch the full vocabulary log-probability distributions for [stream].
1317+
///
1318+
/// Returns a list of lists, where each inner list is the log-probability
1319+
/// distribution over the vocabulary for one emitted token. Returns `null`
1320+
/// if no vocab_log_probs are available.
1321+
List<List<double>>? getVocabLogProbs(OfflineStream stream) {
1322+
final getFunc = SherpaOnnxBindings.getOfflineStreamResult;
1323+
final destroyFunc = SherpaOnnxBindings.destroyOfflineRecognizerResult;
1324+
if (getFunc == null || destroyFunc == null) return null;
1325+
1326+
if (ptr == nullptr || stream.ptr == nullptr) return null;
1327+
1328+
final resultPtr = getFunc(stream.ptr);
1329+
if (resultPtr == nullptr) return null;
1330+
1331+
final ref = resultPtr.ref;
1332+
final probs = readVocabLogProbsFromResult(
1333+
ref.vocabLogProbs, ref.count, ref.vocabSize);
1334+
1335+
destroyFunc(resultPtr);
1336+
return probs;
1337+
}
1338+
13161339
/// Fetch the current recognition result for [stream].
13171340
OfflineRecognizerResult getResult(OfflineStream stream) {
13181341
if (SherpaOnnxBindings.getOfflineStreamResultAsJson == null) {

flutter/sherpa_onnx/lib/src/offline_stream.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,6 @@ class OfflineStream {
5656
calloc.free(p);
5757
}
5858

59-
List<List<double>>? getVocabLogProbs() {
60-
final getFunc = SherpaOnnxBindings.getOfflineStreamVocabLogProbs;
61-
final destroyFunc = SherpaOnnxBindings.destroyVocabLogProbs;
62-
if (getFunc == null || destroyFunc == null) return null;
63-
64-
return readAndFreeVocabLogProbs(getFunc(ptr), destroyFunc);
65-
}
66-
6759
/// Set a string option on the underlying stream.
6860
void setOption({required String key, required String value}) {
6961
if (SherpaOnnxBindings.offlineStreamSetOption == null) {

flutter/sherpa_onnx/lib/src/online_recognizer.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,29 @@ class OnlineRecognizer {
607607
);
608608
}
609609

610+
/// Fetch the full vocabulary log-probability distributions for [stream].
611+
///
612+
/// Returns a list of lists, where each inner list is the log-probability
613+
/// distribution over the vocabulary for one emitted token. Returns `null`
614+
/// if no vocab_log_probs are available.
615+
List<List<double>>? getVocabLogProbs(OnlineStream stream) {
616+
final getFunc = SherpaOnnxBindings.getOnlineStreamResult;
617+
final destroyFunc = SherpaOnnxBindings.destroyOnlineRecognizerResult;
618+
if (getFunc == null || destroyFunc == null) return null;
619+
620+
if (ptr == nullptr || stream.ptr == nullptr) return null;
621+
622+
final resultPtr = getFunc(ptr, stream.ptr);
623+
if (resultPtr == nullptr) return null;
624+
625+
final ref = resultPtr.ref;
626+
final probs = readVocabLogProbsFromResult(
627+
ref.vocabLogProbs, ref.count, ref.vocabSize);
628+
629+
destroyFunc(resultPtr);
630+
return probs;
631+
}
632+
610633
/// Reset stream state after an endpoint or utterance boundary.
611634
void reset(OnlineStream stream) {
612635
if (SherpaOnnxBindings.reset == null) {

flutter/sherpa_onnx/lib/src/online_stream.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,5 @@ class OnlineStream {
6767
SherpaOnnxBindings.onlineStreamInputFinished?.call(ptr);
6868
}
6969

70-
List<List<double>>? getVocabLogProbs() {
71-
final getFunc = SherpaOnnxBindings.getOnlineStreamVocabLogProbs;
72-
final destroyFunc = SherpaOnnxBindings.destroyVocabLogProbs;
73-
if (getFunc == null || destroyFunc == null) return null;
74-
75-
return readAndFreeVocabLogProbs(getFunc(ptr), destroyFunc);
76-
}
77-
7870
Pointer<SherpaOnnxOnlineStream> ptr;
7971
}

flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart

Lines changed: 77 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -824,44 +824,59 @@ final class SherpaOnnxSpokenLanguageIdentificationResult extends Struct {
824824

825825
final class SherpaOnnxSpokenLanguageIdentification extends Opaque {}
826826

827-
final class SherpaOnnxVocabLogProbs extends Struct {
828-
// Flattened 2D array
829-
external Pointer<Float> logProbs;
830-
827+
/// FFI struct for the online recognizer result (mirrors C API struct layout).
828+
final class SherpaOnnxOnlineRecognizerResultNative extends Struct {
829+
external Pointer<Utf8> text;
830+
external Pointer<Utf8> tokens;
831+
external Pointer<Pointer<Utf8>> tokensArr;
832+
external Pointer<Float> timestamps;
833+
@Int32()
834+
external int count;
835+
external Pointer<Utf8> json;
836+
external Pointer<Float> vocabLogProbs;
831837
@Int32()
832-
external int numTokens;
838+
external int vocabSize;
839+
}
833840

841+
/// FFI struct for the offline recognizer result (mirrors C API struct layout).
842+
final class SherpaOnnxOfflineRecognizerResultNative extends Struct {
843+
external Pointer<Utf8> text;
844+
external Pointer<Float> timestamps;
845+
@Int32()
846+
external int count;
847+
external Pointer<Utf8> tokens;
848+
external Pointer<Pointer<Utf8>> tokensArr;
849+
external Pointer<Utf8> json;
850+
external Pointer<Utf8> lang;
851+
external Pointer<Utf8> emotion;
852+
external Pointer<Utf8> event;
853+
external Pointer<Float> durations;
854+
external Pointer<Float> ysLogProbs;
855+
external Pointer<Float> segmentTimestamps;
856+
external Pointer<Float> segmentDurations;
857+
external Pointer<Utf8> segmentTexts;
858+
external Pointer<Pointer<Utf8>> segmentTextsArr;
859+
@Int32()
860+
external int segmentCount;
861+
external Pointer<Float> vocabLogProbs;
834862
@Int32()
835863
external int vocabSize;
836864
}
837865

838-
/// Read a native [SherpaOnnxVocabLogProbs] pointer into a Dart list-of-lists
839-
/// and free the native memory via [destroyFunc]. Returns `null` when
840-
/// [vocabPtr] is null or the sizes look invalid.
841-
List<List<double>>? readAndFreeVocabLogProbs(
842-
Pointer<SherpaOnnxVocabLogProbs> vocabPtr,
843-
void Function(Pointer<SherpaOnnxVocabLogProbs>) destroyFunc,
866+
/// Read vocab_log_probs from a native result struct into a Dart list-of-lists.
867+
/// Returns `null` when vocab_log_probs is null or sizes are invalid.
868+
List<List<double>>? readVocabLogProbsFromResult(
869+
Pointer<Float> vocabLogProbs,
870+
int count,
871+
int vocabSize,
844872
) {
845-
if (vocabPtr == nullptr) {
846-
return null;
847-
}
848-
849-
final ref = vocabPtr.ref;
850-
final numTokens = ref.numTokens;
851-
final vocabSize = ref.vocabSize;
852-
853-
if (numTokens <= 0 || vocabSize <= 0 ||
854-
numTokens > 10000 || vocabSize > 100000) {
855-
destroyFunc(vocabPtr);
873+
if (vocabLogProbs == nullptr || count <= 0 || vocabSize <= 0) {
856874
return null;
857875
}
858876

859-
final flat = ref.logProbs.asTypedList(numTokens * vocabSize);
860-
final result = List.generate(numTokens, (i) =>
877+
final flat = vocabLogProbs.asTypedList(count * vocabSize);
878+
return List.generate(count, (i) =>
861879
List<double>.generate(vocabSize, (j) => flat[i * vocabSize + j]));
862-
863-
destroyFunc(vocabPtr);
864-
return result;
865880
}
866881

867882
final class SherpaOnnxOfflineSpeechDenoiser extends Opaque {}
@@ -1886,19 +1901,24 @@ typedef SherpaOnnxGetGitSha1 = SherpaOnnxGetGitSha1Native;
18861901
typedef SherpaOnnxGetGitDateNative = Pointer<Utf8> Function();
18871902
typedef SherpaOnnxGetGitDate = SherpaOnnxGetGitDateNative;
18881903

1889-
typedef SherpaOnnxOnlineStreamGetVocabLogProbsNative
1890-
= Pointer<SherpaOnnxVocabLogProbs> Function(
1904+
typedef SherpaOnnxGetOnlineStreamResultNative
1905+
= Pointer<SherpaOnnxOnlineRecognizerResultNative> Function(
1906+
Pointer<SherpaOnnxOnlineRecognizer> recognizer,
18911907
Pointer<SherpaOnnxOnlineStream> stream);
18921908

1893-
typedef SherpaOnnxOfflineStreamGetVocabLogProbsNative
1894-
= Pointer<SherpaOnnxVocabLogProbs> Function(
1895-
Pointer<SherpaOnnxOfflineStream> stream);
1909+
typedef SherpaOnnxDestroyOnlineRecognizerResultNative = Void Function(
1910+
Pointer<SherpaOnnxOnlineRecognizerResultNative> r);
1911+
typedef SherpaOnnxDestroyOnlineRecognizerResultDart = void Function(
1912+
Pointer<SherpaOnnxOnlineRecognizerResultNative> r);
18961913

1897-
typedef SherpaOnnxDestroyVocabLogProbsNative = Void Function(
1898-
Pointer<SherpaOnnxVocabLogProbs> logProbs);
1914+
typedef SherpaOnnxGetOfflineStreamResultNative
1915+
= Pointer<SherpaOnnxOfflineRecognizerResultNative> Function(
1916+
Pointer<SherpaOnnxOfflineStream> stream);
18991917

1900-
typedef SherpaOnnxDestroyVocabLogProbsDart = void Function(
1901-
Pointer<SherpaOnnxVocabLogProbs>);
1918+
typedef SherpaOnnxDestroyOfflineRecognizerResultNative = Void Function(
1919+
Pointer<SherpaOnnxOfflineRecognizerResultNative> r);
1920+
typedef SherpaOnnxDestroyOfflineRecognizerResultDart = void Function(
1921+
Pointer<SherpaOnnxOfflineRecognizerResultNative> r);
19021922

19031923
class SherpaOnnxBindings {
19041924
static SherpaOnnxCreateOfflineSpeechDenoiser?
@@ -2141,11 +2161,12 @@ class SherpaOnnxBindings {
21412161
static SherpaOnnxGetGitSha1? getGitSha1;
21422162
static SherpaOnnxGetGitDate? getGitDate;
21432163

2144-
static SherpaOnnxOnlineStreamGetVocabLogProbsNative?
2145-
getOnlineStreamVocabLogProbs;
2146-
static SherpaOnnxOfflineStreamGetVocabLogProbsNative?
2147-
getOfflineStreamVocabLogProbs;
2148-
static SherpaOnnxDestroyVocabLogProbsDart? destroyVocabLogProbs;
2164+
static SherpaOnnxGetOnlineStreamResultNative? getOnlineStreamResult;
2165+
static SherpaOnnxDestroyOnlineRecognizerResultDart?
2166+
destroyOnlineRecognizerResult;
2167+
static SherpaOnnxGetOfflineStreamResultNative? getOfflineStreamResult;
2168+
static SherpaOnnxDestroyOfflineRecognizerResultDart?
2169+
destroyOfflineRecognizerResult;
21492170

21502171
static void init(DynamicLibrary dynamicLibrary) {
21512172
sherpaOnnxCreateOfflineSpeechDenoiser ??= dynamicLibrary
@@ -2921,21 +2942,27 @@ class SherpaOnnxBindings {
29212942
)
29222943
.asFunction();
29232944

2924-
getOnlineStreamVocabLogProbs ??= dynamicLibrary
2925-
.lookup<NativeFunction<SherpaOnnxOnlineStreamGetVocabLogProbsNative>>(
2926-
'SherpaOnnxOnlineStreamGetVocabLogProbs',
2945+
getOnlineStreamResult ??= dynamicLibrary
2946+
.lookup<NativeFunction<SherpaOnnxGetOnlineStreamResultNative>>(
2947+
'SherpaOnnxGetOnlineStreamResult',
2948+
)
2949+
.asFunction();
2950+
2951+
destroyOnlineRecognizerResult ??= dynamicLibrary
2952+
.lookup<NativeFunction<SherpaOnnxDestroyOnlineRecognizerResultNative>>(
2953+
'SherpaOnnxDestroyOnlineRecognizerResult',
29272954
)
29282955
.asFunction();
29292956

2930-
getOfflineStreamVocabLogProbs ??= dynamicLibrary
2931-
.lookup<NativeFunction<SherpaOnnxOfflineStreamGetVocabLogProbsNative>>(
2932-
'SherpaOnnxOfflineStreamGetVocabLogProbs',
2957+
getOfflineStreamResult ??= dynamicLibrary
2958+
.lookup<NativeFunction<SherpaOnnxGetOfflineStreamResultNative>>(
2959+
'SherpaOnnxGetOfflineStreamResult',
29332960
)
29342961
.asFunction();
29352962

2936-
destroyVocabLogProbs ??= dynamicLibrary
2937-
.lookup<NativeFunction<SherpaOnnxDestroyVocabLogProbsNative>>(
2938-
'SherpaOnnxDestroyVocabLogProbs',
2963+
destroyOfflineRecognizerResult ??= dynamicLibrary
2964+
.lookup<NativeFunction<SherpaOnnxDestroyOfflineRecognizerResultNative>>(
2965+
'SherpaOnnxDestroyOfflineRecognizerResult',
29392966
)
29402967
.asFunction();
29412968
}

sherpa-onnx/c-api/c-api.cc

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,22 @@ const SherpaOnnxOnlineRecognizerResult *SherpaOnnxGetOnlineStreamResult(
317317
r->tokens_arr = nullptr;
318318
}
319319

320+
// Copy vocab_log_probs (flattened 2D: count * vocab_size)
321+
if (!result.vocab_log_probs.empty() &&
322+
static_cast<int32_t>(result.vocab_log_probs.size()) == r->count) {
323+
int32_t vocab_size = result.vocab_log_probs[0].size();
324+
r->vocab_size = vocab_size;
325+
float *flat = new float[r->count * vocab_size];
326+
for (int32_t i = 0; i < r->count; ++i) {
327+
std::copy(result.vocab_log_probs[i].begin(),
328+
result.vocab_log_probs[i].end(), flat + i * vocab_size);
329+
}
330+
r->vocab_log_probs = flat;
331+
} else {
332+
r->vocab_log_probs = nullptr;
333+
r->vocab_size = 0;
334+
}
335+
320336
return r;
321337
}
322338

@@ -328,6 +344,7 @@ void SherpaOnnxDestroyOnlineRecognizerResult(
328344
delete[] r->tokens;
329345
delete[] r->tokens_arr;
330346
delete[] r->timestamps;
347+
delete[] r->vocab_log_probs;
331348
delete r;
332349
}
333350
}
@@ -848,6 +865,22 @@ const SherpaOnnxOfflineRecognizerResult *SherpaOnnxGetOfflineStreamResult(
848865
r->ys_log_probs = nullptr;
849866
}
850867

868+
// Copy vocab_log_probs (flattened 2D: count * vocab_size)
869+
if (!result.vocab_log_probs.empty() &&
870+
static_cast<int32_t>(result.vocab_log_probs.size()) == r->count) {
871+
int32_t vocab_size = result.vocab_log_probs[0].size();
872+
r->vocab_size = vocab_size;
873+
float *flat = new float[r->count * vocab_size];
874+
for (int32_t i = 0; i < r->count; ++i) {
875+
std::copy(result.vocab_log_probs[i].begin(),
876+
result.vocab_log_probs[i].end(), flat + i * vocab_size);
877+
}
878+
r->vocab_log_probs = flat;
879+
} else {
880+
r->vocab_log_probs = nullptr;
881+
r->vocab_size = 0;
882+
}
883+
851884
// Copy segment-level timestamps (from Whisper with segment timestamps)
852885
auto segment_count = result.segment_texts.size();
853886
if (segment_count > 0 && result.segment_timestamps.size() == segment_count &&
@@ -911,6 +944,7 @@ void SherpaOnnxDestroyOfflineRecognizerResult(
911944
delete[] r->segment_durations;
912945
delete[] r->segment_texts;
913946
delete[] r->segment_texts_arr;
947+
delete[] r->vocab_log_probs;
914948
delete r;
915949
}
916950
}
@@ -931,54 +965,6 @@ void SherpaOnnxDestroyOfflineStreamResultJson(const char *s) {
931965
delete[] s;
932966
}
933967

934-
static const SherpaOnnxVocabLogProbs *FlattenVocabLogProbs(
935-
const std::vector<std::vector<float>> &vocab_log_probs) {
936-
if (vocab_log_probs.empty()) {
937-
return nullptr;
938-
}
939-
940-
int32_t num_tokens = vocab_log_probs.size();
941-
int32_t vocab_size = vocab_log_probs[0].size();
942-
943-
auto vocab_probs = new SherpaOnnxVocabLogProbs;
944-
vocab_probs->num_tokens = num_tokens;
945-
vocab_probs->vocab_size = vocab_size;
946-
947-
float *flat_probs = new float[num_tokens * vocab_size];
948-
for (int32_t i = 0; i < num_tokens; ++i) {
949-
if (static_cast<int32_t>(vocab_log_probs[i].size()) != vocab_size) {
950-
SHERPA_ONNX_LOGE("vocab_log_probs[%d] size %d != expected %d", i,
951-
static_cast<int32_t>(vocab_log_probs[i].size()),
952-
vocab_size);
953-
delete[] flat_probs;
954-
delete vocab_probs;
955-
return nullptr;
956-
}
957-
std::copy(vocab_log_probs[i].begin(), vocab_log_probs[i].end(),
958-
flat_probs + i * vocab_size);
959-
}
960-
vocab_probs->log_probs = flat_probs;
961-
962-
return vocab_probs;
963-
}
964-
965-
const struct SherpaOnnxVocabLogProbs *SherpaOnnxOnlineStreamGetVocabLogProbs(
966-
const SherpaOnnxOnlineStream *stream) {
967-
return FlattenVocabLogProbs(stream->impl->GetResult().vocab_log_probs);
968-
}
969-
970-
const struct SherpaOnnxVocabLogProbs *SherpaOnnxOfflineStreamGetVocabLogProbs(
971-
const SherpaOnnxOfflineStream *stream) {
972-
return FlattenVocabLogProbs(stream->impl->GetResult().vocab_log_probs);
973-
}
974-
975-
void SherpaOnnxDestroyVocabLogProbs(
976-
const struct SherpaOnnxVocabLogProbs *log_probs) {
977-
if (log_probs) {
978-
delete[] log_probs->log_probs;
979-
delete log_probs;
980-
}
981-
}
982968

983969
// ============================================================
984970
// For Keyword Spot

0 commit comments

Comments
 (0)