Commit 7be2757f for tesseract
commit 7be2757f1ecc67985eb317ed03e5216cc0f9d191
Author: Stefan Weil <sw@weilnetz.de>
Date: Fri Nov 13 17:30:49 2026 +0100
lstm: limit the choices per timestep in SaveMostCertainChoices
The outputs of a softmax add up to 1, so at most 100 of them can reach
the threshold 0.01. A model whose last layer is not a softmax has no
such limit. The sorted insertion was quadratic in the number of
outputs then, and a list of all outputs was kept for each timestep.
Keep the 100 most likely choices. Their order is unchanged.
Fix proposed by the Anthropic OSS Scanner (finding ANT-2026-SS02HWH1).
Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
diff --git a/src/lstm/recodebeam.cpp b/src/lstm/recodebeam.cpp
index affce775..98949946 100644
--- a/src/lstm/recodebeam.cpp
+++ b/src/lstm/recodebeam.cpp
@@ -137,6 +137,10 @@ void RecodeBeamSearch::DecodeSecondaryBeams(
void RecodeBeamSearch::SaveMostCertainChoices(const float *outputs,
int num_outputs,
const UNICHARSET *charset) {
+ // The outputs of a softmax add up to 1, so at most 100 of them can reach
+ // 0.01. A model whose last layer is not a softmax has no such limit, and
+ // without a cap the sorted insertion below is quadratic in num_outputs.
+ constexpr size_t kMaxChoices = 100;
std::vector<std::pair<const char *, float>> choices;
for (int i = 0; i < num_outputs; ++i) {
if (outputs[i] >= 0.01f) {
@@ -154,8 +158,14 @@ void RecodeBeamSearch::SaveMostCertainChoices(const float *outputs,
while (choices.size() > pos && choices[pos].second > outputs[i]) {
pos++;
}
+ if (pos >= kMaxChoices) {
+ continue; // less likely than the kMaxChoices choices already kept
+ }
choices.insert(choices.begin() + pos,
std::pair<const char *, float>(character, outputs[i]));
+ if (choices.size() > kMaxChoices) {
+ choices.pop_back();
+ }
}
}
timesteps.push_back(std::move(choices));