From 04a36fbf68b218199667b1b915f12cbcf884d925 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Tue, 25 Aug 2020 12:08:14 +0200 Subject: [PATCH 01/17] The CTC decoder timesteps now corresponds to the timesteps of the most probable CTC path, instead of the earliest timesteps of all possible paths. --- .../ctcdecode/ctc_beam_search_decoder.cpp | 46 ++++++++++++++++--- native_client/ctcdecode/path_trie.cpp | 34 ++++++-------- native_client/ctcdecode/path_trie.h | 11 +++-- native_client/ctcdecode/scorer.cpp | 6 +-- 4 files changed, 64 insertions(+), 33 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index d46b6893..823313c8 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -96,24 +96,52 @@ DecoderState::next(const double *probs, if (full_beam && log_prob_c + prefix->score < min_cutoff) { break; } + if (prefix->score == -NUM_FLT_INF) { + continue; + } + if (!prefix->is_empty() && prefix->timesteps.empty()) { + // This should never happen. But we report it if it does. + std::cerr<<"error: non-empty prefix has empty timestep sequence"<score; + + // combine current path with previous ones with the same prefix + // the blank label comes last, so we can compare log_prob_nb_cur with log_p + if (prefix->log_prob_nb_cur < log_p) { + prefix->timesteps_cur = prefix->timesteps; + } prefix->log_prob_b_cur = - log_sum_exp(prefix->log_prob_b_cur, log_prob_c + prefix->score); + log_sum_exp(prefix->log_prob_b_cur, log_p); continue; } // repeated character if (c == prefix->character) { + // compute probability of current path + float log_p = log_prob_c + prefix->log_prob_nb_prev; + + // combine current path with previous ones with the same prefix + if (prefix->log_prob_nb_cur < log_p) { + prefix->timesteps_cur = prefix->timesteps; + } prefix->log_prob_nb_cur = log_sum_exp( - prefix->log_prob_nb_cur, log_prob_c + prefix->log_prob_nb_prev); + prefix->log_prob_nb_cur, log_p); } // get new prefix - auto prefix_new = prefix->get_path_trie(c, abs_time_step_, log_prob_c); + auto prefix_new = prefix->get_path_trie(c, log_prob_c); if (prefix_new != nullptr) { + // compute timesteps of current path + std::vector timesteps_new=prefix->timesteps; + timesteps_new.push_back(abs_time_step_); + + // compute probability of current path float log_p = -NUM_FLT_INF; if (c == prefix->character && @@ -144,6 +172,10 @@ DecoderState::next(const double *probs, } } + // combine current path with previous ones with the same prefix + if (prefix_new->log_prob_nb_cur < log_p) { + prefix_new->timesteps_cur = timesteps_new; + } prefix_new->log_prob_nb_cur = log_sum_exp(prefix_new->log_prob_nb_cur, log_p); } @@ -205,11 +237,13 @@ DecoderState::decode(size_t num_results) const std::vector outputs; outputs.reserve(num_returned); - for (size_t i = 0; i < num_returned; ++i) { + for (PathTrie* prefix : prefixes_copy) { Output output; - prefixes_copy[i]->get_path_vec(output.tokens, output.timesteps); - output.confidence = scores[prefixes_copy[i]]; + output.tokens = prefix->get_path_vec(); + output.timesteps = prefix->timesteps; + output.confidence = scores[prefix]; outputs.push_back(output); + if(outputs.size()>=num_returned) break; } return outputs; diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index 7a04f693..55a81437 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -18,7 +18,6 @@ PathTrie::PathTrie() { ROOT_ = -1; character = ROOT_; - timestep = 0; exists_ = true; parent = nullptr; @@ -35,7 +34,7 @@ PathTrie::~PathTrie() { } } -PathTrie* PathTrie::get_path_trie(unsigned int new_char, unsigned int new_timestep, float cur_log_prob_c, bool reset) { +PathTrie* PathTrie::get_path_trie(unsigned int new_char, float cur_log_prob_c, bool reset) { auto child = children_.begin(); for (; child != children_.end(); ++child) { if (child->first == new_char) { @@ -67,7 +66,6 @@ PathTrie* PathTrie::get_path_trie(unsigned int new_char, unsigned int new_timest } else { PathTrie* new_path = new PathTrie; new_path->character = new_char; - new_path->timestep = new_timestep; new_path->parent = this; new_path->dictionary_ = dictionary_; new_path->has_dictionary_ = true; @@ -93,7 +91,6 @@ PathTrie* PathTrie::get_path_trie(unsigned int new_char, unsigned int new_timest } else { PathTrie* new_path = new PathTrie; new_path->character = new_char; - new_path->timestep = new_timestep; new_path->parent = this; new_path->log_prob_c = cur_log_prob_c; children_.push_back(std::make_pair(new_char, new_path)); @@ -102,20 +99,18 @@ PathTrie* PathTrie::get_path_trie(unsigned int new_char, unsigned int new_timest } } -void PathTrie::get_path_vec(std::vector& output, std::vector& timesteps) { - // Recursive call: recurse back until stop condition, then append data in - // correct order as we walk back down the stack in the lines below. - if (parent != nullptr) { - parent->get_path_vec(output, timesteps); +std::vector PathTrie::get_path_vec() { + if (parent == nullptr) { + return std::vector{}; } + std::vector output_tokens=parent->get_path_vec(); if (character != ROOT_) { - output.push_back(character); - timesteps.push_back(timestep); + output_tokens.push_back(character); } + return output_tokens; } PathTrie* PathTrie::get_prev_grapheme(std::vector& output, - std::vector& timesteps, const Alphabet& alphabet) { PathTrie* stop = this; @@ -125,10 +120,9 @@ PathTrie* PathTrie::get_prev_grapheme(std::vector& output, // Recursive call: recurse back until stop condition, then append data in // correct order as we walk back down the stack in the lines below. if (!byte_is_codepoint_boundary(alphabet.DecodeSingle(character)[0])) { - stop = parent->get_prev_grapheme(output, timesteps, alphabet); + stop = parent->get_prev_grapheme(output, alphabet); } output.push_back(character); - timesteps.push_back(timestep); return stop; } @@ -147,7 +141,6 @@ int PathTrie::distance_to_codepoint_boundary(unsigned char *first_byte, } PathTrie* PathTrie::get_prev_word(std::vector& output, - std::vector& timesteps, const Alphabet& alphabet) { PathTrie* stop = this; @@ -157,10 +150,9 @@ PathTrie* PathTrie::get_prev_word(std::vector& output, // Recursive call: recurse back until stop condition, then append data in // correct order as we walk back down the stack in the lines below. if (parent != nullptr) { - stop = parent->get_prev_word(output, timesteps, alphabet); + stop = parent->get_prev_word(output, alphabet); } output.push_back(character); - timesteps.push_back(timestep); return stop; } @@ -173,6 +165,10 @@ void PathTrie::iterate_to_vec(std::vector& output) { log_prob_nb_cur = -NUM_FLT_INF; score = log_sum_exp(log_prob_b_prev, log_prob_nb_prev); + + timesteps = std::move(timesteps_cur); + timesteps_cur.clear(); + output.push_back(this); } for (auto child : children_) { @@ -229,8 +225,8 @@ void PathTrie::print(const Alphabet& a) { } } printf("\ntimesteps:\t "); - for (PathTrie* el : chain) { - printf("%d ", el->timestep); + for (unsigned int timestep : timesteps) { + printf("%d ", timestep); } printf("\n"); printf("transcript:\t %s\n", tr.c_str()); diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 0a4374fc..0e832f15 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -21,14 +21,13 @@ public: ~PathTrie(); // get new prefix after appending new char - PathTrie* get_path_trie(unsigned int new_char, unsigned int new_timestep, float log_prob_c, bool reset = true); + PathTrie* get_path_trie(unsigned int new_char, float log_prob_c, bool reset = true); // get the prefix data in correct time order from root to current node - void get_path_vec(std::vector& output, std::vector& timesteps); + std::vector get_path_vec(); // get the prefix data in correct time order from beginning of last grapheme to current node PathTrie* get_prev_grapheme(std::vector& output, - std::vector& timesteps, const Alphabet& alphabet); // get the distance from current node to the first codepoint boundary, and the byte value at the boundary @@ -36,7 +35,6 @@ public: // get the prefix data in correct time order from beginning of last word to current node PathTrie* get_prev_word(std::vector& output, - std::vector& timesteps, const Alphabet& alphabet); // update log probs @@ -65,7 +63,10 @@ public: float score; float approx_ctc; unsigned int character; - unsigned int timestep; + std::vector timesteps; + // `timesteps_cur` is a temporary storage for each decoding step. + // At the end of a decoding step, it is moved to `timesteps`. + std::vector timesteps_cur; PathTrie* parent; private: diff --git a/native_client/ctcdecode/scorer.cpp b/native_client/ctcdecode/scorer.cpp index ad41dd8e..c637cdd6 100644 --- a/native_client/ctcdecode/scorer.cpp +++ b/native_client/ctcdecode/scorer.cpp @@ -293,12 +293,12 @@ std::vector Scorer::make_ngram(PathTrie* prefix) } std::vector prefix_vec; - std::vector prefix_steps; + std::vector prefix_steps = current_node->timesteps; if (is_utf8_mode_) { - new_node = current_node->get_prev_grapheme(prefix_vec, prefix_steps, alphabet_); + new_node = current_node->get_prev_grapheme(prefix_vec, alphabet_); } else { - new_node = current_node->get_prev_word(prefix_vec, prefix_steps, alphabet_); + new_node = current_node->get_prev_word(prefix_vec, alphabet_); } current_node = new_node->parent; From c3d6f8d9230d08b430a2da598b67d6ec41ebdd94 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 31 Aug 2020 08:53:26 +0200 Subject: [PATCH 02/17] PR #3279 - replaced tabulations by spaces --- .../ctcdecode/ctc_beam_search_decoder.cpp | 40 +++++++++---------- native_client/ctcdecode/path_trie.cpp | 8 ++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index 823313c8..216adff1 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -99,22 +99,22 @@ DecoderState::next(const double *probs, if (prefix->score == -NUM_FLT_INF) { continue; } - if (!prefix->is_empty() && prefix->timesteps.empty()) { - // This should never happen. But we report it if it does. - std::cerr<<"error: non-empty prefix has empty timestep sequence"<is_empty() && prefix->timesteps.empty()) { + // This should never happen. But we report it if it does. + std::cerr<<"error: non-empty prefix has empty timestep sequence"<score; - // combine current path with previous ones with the same prefix - // the blank label comes last, so we can compare log_prob_nb_cur with log_p - if (prefix->log_prob_nb_cur < log_p) { - prefix->timesteps_cur = prefix->timesteps; - } + // combine current path with previous ones with the same prefix + // the blank label comes last, so we can compare log_prob_nb_cur with log_p + if (prefix->log_prob_nb_cur < log_p) { + prefix->timesteps_cur = prefix->timesteps; + } prefix->log_prob_b_cur = log_sum_exp(prefix->log_prob_b_cur, log_p); continue; @@ -122,10 +122,10 @@ DecoderState::next(const double *probs, // repeated character if (c == prefix->character) { - // compute probability of current path + // compute probability of current path float log_p = log_prob_c + prefix->log_prob_nb_prev; - // combine current path with previous ones with the same prefix + // combine current path with previous ones with the same prefix if (prefix->log_prob_nb_cur < log_p) { prefix->timesteps_cur = prefix->timesteps; } @@ -137,11 +137,11 @@ DecoderState::next(const double *probs, auto prefix_new = prefix->get_path_trie(c, log_prob_c); if (prefix_new != nullptr) { - // compute timesteps of current path - std::vector timesteps_new=prefix->timesteps; - timesteps_new.push_back(abs_time_step_); + // compute timesteps of current path + std::vector timesteps_new=prefix->timesteps; + timesteps_new.push_back(abs_time_step_); - // compute probability of current path + // compute probability of current path float log_p = -NUM_FLT_INF; if (c == prefix->character && @@ -172,7 +172,7 @@ DecoderState::next(const double *probs, } } - // combine current path with previous ones with the same prefix + // combine current path with previous ones with the same prefix if (prefix_new->log_prob_nb_cur < log_p) { prefix_new->timesteps_cur = timesteps_new; } @@ -240,10 +240,10 @@ DecoderState::decode(size_t num_results) const for (PathTrie* prefix : prefixes_copy) { Output output; output.tokens = prefix->get_path_vec(); - output.timesteps = prefix->timesteps; + output.timesteps = prefix->timesteps; output.confidence = scores[prefix]; outputs.push_back(output); - if(outputs.size()>=num_returned) break; + if(outputs.size()>=num_returned) break; } return outputs; diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index 55a81437..50aca873 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -101,11 +101,11 @@ PathTrie* PathTrie::get_path_trie(unsigned int new_char, float cur_log_prob_c, b std::vector PathTrie::get_path_vec() { if (parent == nullptr) { - return std::vector{}; + return std::vector{}; } std::vector output_tokens=parent->get_path_vec(); if (character != ROOT_) { - output_tokens.push_back(character); + output_tokens.push_back(character); } return output_tokens; } @@ -166,8 +166,8 @@ void PathTrie::iterate_to_vec(std::vector& output) { score = log_sum_exp(log_prob_b_prev, log_prob_nb_prev); - timesteps = std::move(timesteps_cur); - timesteps_cur.clear(); + timesteps = std::move(timesteps_cur); + timesteps_cur.clear(); output.push_back(this); } From 78c4ef17b11fe681702cb0619a0b938a0b59f5bd Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 31 Aug 2020 09:33:22 +0200 Subject: [PATCH 03/17] PR #3279 - removed unrelated code --- native_client/ctcdecode/ctc_beam_search_decoder.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index 216adff1..e35e0257 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -96,9 +96,6 @@ DecoderState::next(const double *probs, if (full_beam && log_prob_c + prefix->score < min_cutoff) { break; } - if (prefix->score == -NUM_FLT_INF) { - continue; - } if (!prefix->is_empty() && prefix->timesteps.empty()) { // This should never happen. But we report it if it does. std::cerr<<"error: non-empty prefix has empty timestep sequence"< Date: Mon, 31 Aug 2020 09:38:54 +0200 Subject: [PATCH 04/17] PR #3279 - assert instead of reporting error to std::cerr --- native_client/ctcdecode/ctc_beam_search_decoder.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index e35e0257..f65888c4 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -96,11 +96,7 @@ DecoderState::next(const double *probs, if (full_beam && log_prob_c + prefix->score < min_cutoff) { break; } - if (!prefix->is_empty() && prefix->timesteps.empty()) { - // This should never happen. But we report it if it does. - std::cerr<<"error: non-empty prefix has empty timestep sequence"<is_empty() || !prefix->timesteps.empty()); // blank if (c == blank_id_) { From e9466160c7e862b0e1eacf3c7574702c73b023d1 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 31 Aug 2020 09:54:38 +0200 Subject: [PATCH 05/17] PR #3279 - revert to non RVO code --- .../ctcdecode/ctc_beam_search_decoder.cpp | 2 +- native_client/ctcdecode/path_trie.cpp | 14 ++++++-------- native_client/ctcdecode/path_trie.h | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index f65888c4..c28dacd2 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -232,7 +232,7 @@ DecoderState::decode(size_t num_results) const for (PathTrie* prefix : prefixes_copy) { Output output; - output.tokens = prefix->get_path_vec(); + prefix->get_path_vec(output.tokens); output.timesteps = prefix->timesteps; output.confidence = scores[prefix]; outputs.push_back(output); diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index 50aca873..f4c77320 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -99,15 +99,13 @@ PathTrie* PathTrie::get_path_trie(unsigned int new_char, float cur_log_prob_c, b } } -std::vector PathTrie::get_path_vec() { - if (parent == nullptr) { - return std::vector{}; +void PathTrie::get_path_vec(std::vector& output) { + // Recursive call: recurse back until stop condition, then append data in + // correct order as we walk back down the stack in the lines below. + if (parent != nullptr) { + parent->get_path_vec(output); + output.push_back(character); } - std::vector output_tokens=parent->get_path_vec(); - if (character != ROOT_) { - output_tokens.push_back(character); - } - return output_tokens; } PathTrie* PathTrie::get_prev_grapheme(std::vector& output, diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 0e832f15..a3ef7bdb 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -24,7 +24,7 @@ public: PathTrie* get_path_trie(unsigned int new_char, float log_prob_c, bool reset = true); // get the prefix data in correct time order from root to current node - std::vector get_path_vec(); + void get_path_vec(std::vector& output); // get the prefix data in correct time order from beginning of last grapheme to current node PathTrie* get_prev_grapheme(std::vector& output, From 363121235e50e3ccd429996e09b6280df0cb8507 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 31 Aug 2020 10:15:34 +0200 Subject: [PATCH 06/17] PR #3279 - revert to non RVO code (fix) --- native_client/ctcdecode/path_trie.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index f4c77320..ebb4d924 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -104,6 +104,8 @@ void PathTrie::get_path_vec(std::vector& output) { // correct order as we walk back down the stack in the lines below. if (parent != nullptr) { parent->get_path_vec(output); + } + if (character != ROOT_) { output.push_back(character); } } From 1f89bef5f0f58c1d348b3fa534694481623b6be7 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 31 Aug 2020 19:01:47 +0200 Subject: [PATCH 07/17] PR #3279 - avoid unnecessary copies of timesteps vectors --- .../ctcdecode/ctc_beam_search_decoder.cpp | 15 ++++++++------- native_client/ctcdecode/path_trie.cpp | 15 ++++++++++----- native_client/ctcdecode/path_trie.h | 8 +++++--- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index c28dacd2..0fe7784c 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -106,7 +106,8 @@ DecoderState::next(const double *probs, // combine current path with previous ones with the same prefix // the blank label comes last, so we can compare log_prob_nb_cur with log_p if (prefix->log_prob_nb_cur < log_p) { - prefix->timesteps_cur = prefix->timesteps; + // keep current timesteps + prefix->previous_timesteps = nullptr; } prefix->log_prob_b_cur = log_sum_exp(prefix->log_prob_b_cur, log_p); @@ -120,7 +121,8 @@ DecoderState::next(const double *probs, // combine current path with previous ones with the same prefix if (prefix->log_prob_nb_cur < log_p) { - prefix->timesteps_cur = prefix->timesteps; + // keep current timesteps + prefix->previous_timesteps = nullptr; } prefix->log_prob_nb_cur = log_sum_exp( prefix->log_prob_nb_cur, log_p); @@ -130,10 +132,6 @@ DecoderState::next(const double *probs, auto prefix_new = prefix->get_path_trie(c, log_prob_c); if (prefix_new != nullptr) { - // compute timesteps of current path - std::vector timesteps_new=prefix->timesteps; - timesteps_new.push_back(abs_time_step_); - // compute probability of current path float log_p = -NUM_FLT_INF; @@ -167,7 +165,10 @@ DecoderState::next(const double *probs, // combine current path with previous ones with the same prefix if (prefix_new->log_prob_nb_cur < log_p) { - prefix_new->timesteps_cur = timesteps_new; + // record data needed to update timesteps + // the actual update will be done if nothing better is found + prefix_new->previous_timesteps = &prefix->timesteps; + prefix_new->new_timestep = abs_time_step_; } prefix_new->log_prob_nb_cur = log_sum_exp(prefix_new->log_prob_nb_cur, log_p); diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index ebb4d924..cca89a93 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -157,6 +157,11 @@ PathTrie* PathTrie::get_prev_word(std::vector& output, } void PathTrie::iterate_to_vec(std::vector& output) { + // previous_timesteps might point to ancestors' timesteps + // therefore, children must be uptaded first + for (auto child : children_) { + child.second->iterate_to_vec(output); + } if (exists_) { log_prob_b_prev = log_prob_b_cur; log_prob_nb_prev = log_prob_nb_cur; @@ -166,14 +171,14 @@ void PathTrie::iterate_to_vec(std::vector& output) { score = log_sum_exp(log_prob_b_prev, log_prob_nb_prev); - timesteps = std::move(timesteps_cur); - timesteps_cur.clear(); + if (previous_timesteps != nullptr) { + timesteps = *previous_timesteps; + timesteps.push_back(new_timestep); + } + previous_timesteps=nullptr; output.push_back(this); } - for (auto child : children_) { - child.second->iterate_to_vec(output); - } } void PathTrie::remove() { diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index a3ef7bdb..00e0c3bf 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -64,9 +64,11 @@ public: float approx_ctc; unsigned int character; std::vector timesteps; - // `timesteps_cur` is a temporary storage for each decoding step. - // At the end of a decoding step, it is moved to `timesteps`. - std::vector timesteps_cur; + + // timestep temporary storage for each decoding step. + std::vector* previous_timesteps=nullptr; + unsigned int new_timestep; + PathTrie* parent; private: From ec55597412da8b43d079b650811321b82d60c588 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 7 Sep 2020 13:37:27 +0200 Subject: [PATCH 08/17] PR #3279 - use a tree structure to store timesteps --- .../ctcdecode/ctc_beam_search_decoder.cpp | 13 +++-- .../ctcdecode/ctc_beam_search_decoder.h | 1 + native_client/ctcdecode/path_trie.cpp | 14 ++++-- native_client/ctcdecode/path_trie.h | 47 ++++++++++++++++++- native_client/ctcdecode/scorer.cpp | 1 - 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index 0fe7784c..f317b0ec 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -36,6 +36,8 @@ DecoderState::init(const Alphabet& alphabet, root->score = root->log_prob_b_prev = 0.0; prefix_root_.reset(root); prefixes_.push_back(root); + + timestep_tree_root_=std::make_shared(nullptr, 0); if (ext_scorer && (bool)(ext_scorer_->dictionary)) { // no need for std::make_shared<>() since Copy() does 'new' behind the doors @@ -96,7 +98,7 @@ DecoderState::next(const double *probs, if (full_beam && log_prob_c + prefix->score < min_cutoff) { break; } - assert(prefix->is_empty() || !prefix->timesteps.empty()); + assert(prefix->is_empty() || prefix->timesteps!=nullptr); // blank if (c == blank_id_) { @@ -167,7 +169,7 @@ DecoderState::next(const double *probs, if (prefix_new->log_prob_nb_cur < log_p) { // record data needed to update timesteps // the actual update will be done if nothing better is found - prefix_new->previous_timesteps = &prefix->timesteps; + prefix_new->previous_timesteps = prefix->timesteps; prefix_new->new_timestep = abs_time_step_; } prefix_new->log_prob_nb_cur = @@ -179,6 +181,11 @@ DecoderState::next(const double *probs, // update log probs prefixes_.clear(); prefix_root_->iterate_to_vec(prefixes_); + if (abs_time_step_ == 0) { + for (PathTrie* prefix:prefixes_) { + prefix->timesteps = timestep_tree_root_; + } + } // only preserve top beam_size prefixes if (prefixes_.size() > beam_size_) { @@ -234,7 +241,7 @@ DecoderState::decode(size_t num_results) const for (PathTrie* prefix : prefixes_copy) { Output output; prefix->get_path_vec(output.tokens); - output.timesteps = prefix->timesteps; + output.timesteps = get_history(prefix->timesteps); output.confidence = scores[prefix]; outputs.push_back(output); if(outputs.size()>=num_returned) break; diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.h b/native_client/ctcdecode/ctc_beam_search_decoder.h index 73bdfcc7..e8324f5e 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.h +++ b/native_client/ctcdecode/ctc_beam_search_decoder.h @@ -21,6 +21,7 @@ class DecoderState { std::shared_ptr ext_scorer_; std::vector prefixes_; std::unique_ptr prefix_root_; + std::shared_ptr timestep_tree_root_; public: DecoderState() = default; diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index cca89a93..3f9ab91f 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -172,8 +172,16 @@ void PathTrie::iterate_to_vec(std::vector& output) { score = log_sum_exp(log_prob_b_prev, log_prob_nb_prev); if (previous_timesteps != nullptr) { - timesteps = *previous_timesteps; - timesteps.push_back(new_timestep); + timesteps = nullptr; + for (auto const& child : previous_timesteps->children) { + if (child->data == new_timestep) { + timesteps=child; + break; + } + } + if (timesteps == nullptr){ + timesteps = add_child(previous_timesteps, new_timestep); + } } previous_timesteps=nullptr; @@ -230,7 +238,7 @@ void PathTrie::print(const Alphabet& a) { } } printf("\ntimesteps:\t "); - for (unsigned int timestep : timesteps) { + for (unsigned int timestep : get_history(timesteps)) { printf("%d ", timestep); } printf("\n"); diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 00e0c3bf..8c3ae0a7 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -10,6 +10,27 @@ #include "fst/fstlib.h" #include "alphabet.h" +/* Tree structure with parent and children information + * It is used to store the timesteps data for the PathTrie below + */ +template +struct TreeNode{ + std::shared_ptr> parent; + std::vector>> children; + + DataT data; + + TreeNode(std::shared_ptr> const& parent_, DataT const& data_): parent{parent_}, data{data_} {} +}; + +template +std::shared_ptr> add_child(std::shared_ptr> const& node, ChildDataT&& data_); + +template +std::vector get_history(TreeNode*); + +using TimestepTreeNode=TreeNode; + /* Trie tree for prefix storing and manipulating, with a dictionary in * finite-state transducer for spelling correction. */ @@ -63,10 +84,10 @@ public: float score; float approx_ctc; unsigned int character; - std::vector timesteps; + std::shared_ptr timesteps; // timestep temporary storage for each decoding step. - std::vector* previous_timesteps=nullptr; + std::shared_ptr previous_timesteps=nullptr; unsigned int new_timestep; PathTrie* parent; @@ -84,4 +105,26 @@ private: std::shared_ptr> matcher_; }; +// TreeNode implementation +template +std::shared_ptr> add_child(std::shared_ptr> const& node, ChildDataT&& data_){ + node->children.push_back(std::make_shared>(node, std::forward(data_))); + return node->children.back(); +} + +template +void get_history_helper(std::shared_ptr> const& tree_node, std::vector* output){ + if(tree_node==nullptr) return; + assert(tree_node->parent != tree_node); + get_history_helper(tree_node->parent, output); + output->push_back(tree_node->data); +} +template +std::vector get_history(std::shared_ptr> const& tree_node){ + std::vector output; + get_history_helper(tree_node, &output); + return output; +} + + #endif // PATH_TRIE_H diff --git a/native_client/ctcdecode/scorer.cpp b/native_client/ctcdecode/scorer.cpp index c637cdd6..7c86f9d5 100644 --- a/native_client/ctcdecode/scorer.cpp +++ b/native_client/ctcdecode/scorer.cpp @@ -293,7 +293,6 @@ std::vector Scorer::make_ngram(PathTrie* prefix) } std::vector prefix_vec; - std::vector prefix_steps = current_node->timesteps; if (is_utf8_mode_) { new_node = current_node->get_prev_grapheme(prefix_vec, alphabet_); From 3a49344ccbaf0e6a56367ead15288247305f6287 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Tue, 8 Sep 2020 14:12:39 +0200 Subject: [PATCH 09/17] PR #3279 - use an object pool to store timesteps tree nodes --- native_client/BUILD | 1 + native_client/ctcdecode/build_archive.py | 3 +- native_client/ctcdecode/path_trie.h | 4 +- .../third_party/object_pool/object_pool.h | 97 +++++++++++++++++++ .../third_party/object_pool/unique_ptr.h | 39 ++++++++ 5 files changed, 142 insertions(+), 2 deletions(-) create mode 100755 native_client/ctcdecode/third_party/object_pool/object_pool.h create mode 100755 native_client/ctcdecode/third_party/object_pool/unique_ptr.h diff --git a/native_client/BUILD b/native_client/BUILD index 61bfb19f..55c2dfc1 100644 --- a/native_client/BUILD +++ b/native_client/BUILD @@ -100,6 +100,7 @@ cc_library( includes = [ ".", "ctcdecode/third_party/ThreadPool", + "ctcdecode/third_party/object_pool", ] + OPENFST_INCLUDES_PLATFORM, deps = [":kenlm"], linkopts = [ diff --git a/native_client/ctcdecode/build_archive.py b/native_client/ctcdecode/build_archive.py index 8a689ac0..f94d1adf 100644 --- a/native_client/ctcdecode/build_archive.py +++ b/native_client/ctcdecode/build_archive.py @@ -26,7 +26,8 @@ INCLUDES = [ '..', '../kenlm', OPENFST_DIR + '/src/include', - 'third_party/ThreadPool' + 'third_party/ThreadPool', + 'third_party/object_pool' ] KENLM_FILES = (glob.glob('../kenlm/util/*.cc') diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 8c3ae0a7..20060a3e 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -9,6 +9,7 @@ #include "fst/fstlib.h" #include "alphabet.h" +#include "object_pool.h" /* Tree structure with parent and children information * It is used to store the timesteps data for the PathTrie below @@ -108,7 +109,8 @@ private: // TreeNode implementation template std::shared_ptr> add_child(std::shared_ptr> const& node, ChildDataT&& data_){ - node->children.push_back(std::make_shared>(node, std::forward(data_))); + static godefv::memory::object_pool_t> tree_node_pool; + node->children.emplace_back(tree_node_pool.make_unique(node, std::forward(data_))); return node->children.back(); } diff --git a/native_client/ctcdecode/third_party/object_pool/object_pool.h b/native_client/ctcdecode/third_party/object_pool/object_pool.h new file mode 100755 index 00000000..0c0a3287 --- /dev/null +++ b/native_client/ctcdecode/third_party/object_pool/object_pool.h @@ -0,0 +1,97 @@ +#ifndef GODEFV_MEMORY_OBJECT_POOL_H +#define GODEFV_MEMORY_OBJECT_POOL_H + +#include "unique_ptr.h" +#include +#include +#include + +namespace godefv{ namespace memory{ + +//! Allocates instances of Object efficiently (constant time and log((maximum number of Objects used at the same time)/ChunkSize) calls to malloc in the whole lifetime of the object pool). +//! When an instance returned by the object pool is destroyed, its allocated memory is recycled by the object pool. Defragmenting the object pool to free memory is not possible. +template class Allocator = std::allocator, std::size_t ChunkSize = 1024> +class object_pool_t{ + //! An object slot is an uninitialized memory space of the same size as Object. + //! It is initially "free". It can then be "used" to construct an Object in place and the pointer to it is returned by the object pool. When the pointer is destroyed, the object slot is "recycled" and can be used again but it is not "free" anymore because "free" object slots are contiguous in memory. + using object_slot_t=std::array; + + //! To minimize calls to malloc, the object slots are allocated in chunks. + //! For example, if ChunkSize=8, a chunk may look like this : |used|recycled|used|used|recycled|free|free|free|. In this example, if more than 5 new Object are now asked from the object pool, at least one new chunk of 8 object slots will be allocated. + using chunk_t=std::array; + Allocator chunk_allocator; //!< This allocator can be used to have aligned memory if required. + std::vector> memory_chunks; + + //! Recycled object slots are tracked using a stack of pointers to them. When an object slot is recycled, a pointer to it is pushed in constant time. When a new object is constructed, a recycled object slot can be found and poped in constant time. + std::vector recycled_object_slots; + + object_slot_t* free_object_slots_begin; + object_slot_t* free_object_slots_end; + + //! Custom deleter to recycle the deleted pointers. + struct deleter_t{ + private: + object_pool_t* object_pool_ptr; + public: + explicit deleter_t(decltype(object_pool_ptr) input_object_pool_ptr) : + object_pool_ptr(input_object_pool_ptr) + {} + + //! When a pointer provided by the ObjectPool is deleted, its memory is converted to an object slot to be recycled. + void operator()(Object* object_ptr) + { + object_ptr->~Object(); + object_pool_ptr->recycled_object_slots.push_back(reinterpret_cast(object_ptr)); + } + }; + +public: + using object_t = Object; + using object_unique_ptr_t = std::unique_ptr; //!< The type returned by the object pool. + + object_pool_t(Allocator const& allocator = Allocator{}) : + free_object_slots_begin{ free_object_slots_end }, // At the begining, set the 2 iterators at the same value to simulate a full pool. + chunk_allocator{ allocator } + {} + + //! Returns a unique pointer to an object_t using an unused object slot from the object pool. + template object_unique_ptr_t make_unique(Args&&... vars){ + auto construct_object_unique_ptr=[&](object_slot_t* object_slot){ + return object_unique_ptr_t{ new (reinterpret_cast(object_slot)) object_t{ std::forward(vars)... } , deleter_t{ this } }; + }; + + // If a recycled object slot is available, use it. + if (!recycled_object_slots.empty()) + { + auto object_slot = recycled_object_slots.back(); + recycled_object_slots.pop_back(); + return construct_object_unique_ptr(object_slot); + } + + // If the pool is full: add a new chunk. + if (free_object_slots_begin == free_object_slots_end) + { + memory_chunks.emplace_back(chunk_allocator); + auto& new_chunk = memory_chunks.back(); + free_object_slots_begin=new_chunk->data(); + free_object_slots_end =free_object_slots_begin+new_chunk->size(); + } + + // We know that there is now at least one free object slot, use it. + return construct_object_unique_ptr(free_object_slots_begin++); + } + + //! Returns the total number of object slots (free, recycled, or used). + std::size_t capacity() const{ + return memory_chunks.size()*ChunkSize; + } + + //! Returns the number of currently used object slots. + std::size_t size() const{ + return capacity() - static_cast(free_object_slots_end-free_object_slots_begin) - recycled_object_slots.size(); + } +}; + +}} /* namespace godefv::memory */ + +#endif /* GODEFV_MEMORY_OBJECT_POOL_H */ diff --git a/native_client/ctcdecode/third_party/object_pool/unique_ptr.h b/native_client/ctcdecode/third_party/object_pool/unique_ptr.h new file mode 100755 index 00000000..b1f00950 --- /dev/null +++ b/native_client/ctcdecode/third_party/object_pool/unique_ptr.h @@ -0,0 +1,39 @@ +#ifndef GODEFV_MEMORY_ALLOCATED_UNIQUE_PTR_H +#define GODEFV_MEMORY_ALLOCATED_UNIQUE_PTR_H + +#include + +namespace godefv{ namespace memory{ + +//! A deleter to deallocate memory which have been allocated by the given allocator. +template struct allocator_deleter_t +{ + allocator_deleter_t(Allocator const& allocator) : + mAllocator{ allocator } + {} + + void operator()(typename Allocator::value_type* ptr) + { + mAllocator.deallocate(ptr, 1); + } + +private: + Allocator mAllocator; +}; + +//! A smart pointer like std::unique_ptr but templated on an allocator instead of a deleter. +//! The deleter is deduced from the given allocator. +template > +class unique_ptr_t : public std::unique_ptr> +{ + using base_t = std::unique_ptr>; + +public: + unique_ptr_t(Allocator allocator = Allocator{}) : + base_t{ allocator.allocate(1), allocator_deleter_t{ allocator } } + {} +}; + +}} // namespace godefv::memory + +#endif // GODEFV_MEMORY_ALLOCATED_UNIQUE_PTR_H From f07c10452bfea8be7c1bdaa4fda925dd48e77ecc Mon Sep 17 00:00:00 2001 From: godeffroy Date: Wed, 9 Sep 2020 11:04:37 +0200 Subject: [PATCH 10/17] PR #3279 - use unique_ptr instead of shared_ptr in the timestep tree --- .../ctcdecode/ctc_beam_search_decoder.cpp | 4 +- .../ctcdecode/ctc_beam_search_decoder.h | 2 +- native_client/ctcdecode/path_trie.cpp | 2 +- native_client/ctcdecode/path_trie.h | 22 +++++----- .../third_party/object_pool/object_pool.h | 44 ++++++++++++------- 5 files changed, 41 insertions(+), 33 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index f317b0ec..7efaaaa1 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -37,8 +37,6 @@ DecoderState::init(const Alphabet& alphabet, prefix_root_.reset(root); prefixes_.push_back(root); - timestep_tree_root_=std::make_shared(nullptr, 0); - if (ext_scorer && (bool)(ext_scorer_->dictionary)) { // no need for std::make_shared<>() since Copy() does 'new' behind the doors auto dict_ptr = std::shared_ptr(ext_scorer->dictionary->Copy(true)); @@ -183,7 +181,7 @@ DecoderState::next(const double *probs, prefix_root_->iterate_to_vec(prefixes_); if (abs_time_step_ == 0) { for (PathTrie* prefix:prefixes_) { - prefix->timesteps = timestep_tree_root_; + prefix->timesteps = ×tep_tree_root_; } } diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.h b/native_client/ctcdecode/ctc_beam_search_decoder.h index e8324f5e..65e7497d 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.h +++ b/native_client/ctcdecode/ctc_beam_search_decoder.h @@ -21,7 +21,7 @@ class DecoderState { std::shared_ptr ext_scorer_; std::vector prefixes_; std::unique_ptr prefix_root_; - std::shared_ptr timestep_tree_root_; + TimestepTreeNode timestep_tree_root_{nullptr, 0}; public: DecoderState() = default; diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index 3f9ab91f..2b572fa8 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -175,7 +175,7 @@ void PathTrie::iterate_to_vec(std::vector& output) { timesteps = nullptr; for (auto const& child : previous_timesteps->children) { if (child->data == new_timestep) { - timesteps=child; + timesteps = child.get(); break; } } diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 20060a3e..4c8c82b3 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -16,16 +16,16 @@ */ template struct TreeNode{ - std::shared_ptr> parent; - std::vector>> children; + TreeNode* parent; + std::vector, godefv::memory::object_pool_deleter_t> >> children; DataT data; - TreeNode(std::shared_ptr> const& parent_, DataT const& data_): parent{parent_}, data{data_} {} + TreeNode(TreeNode* parent_, DataT const& data_): parent{parent_}, data{data_} {} }; template -std::shared_ptr> add_child(std::shared_ptr> const& node, ChildDataT&& data_); +TreeNode* add_child(TreeNode* node, ChildDataT&& data_); template std::vector get_history(TreeNode*); @@ -85,10 +85,10 @@ public: float score; float approx_ctc; unsigned int character; - std::shared_ptr timesteps; + TimestepTreeNode* timesteps=nullptr; // timestep temporary storage for each decoding step. - std::shared_ptr previous_timesteps=nullptr; + TimestepTreeNode* previous_timesteps=nullptr; unsigned int new_timestep; PathTrie* parent; @@ -108,21 +108,21 @@ private: // TreeNode implementation template -std::shared_ptr> add_child(std::shared_ptr> const& node, ChildDataT&& data_){ +TreeNode* add_child(TreeNode* node, ChildDataT&& data_){ static godefv::memory::object_pool_t> tree_node_pool; - node->children.emplace_back(tree_node_pool.make_unique(node, std::forward(data_))); - return node->children.back(); + node->children.push_back(tree_node_pool.make_unique(node, std::forward(data_))); + return node->children.back().get(); } template -void get_history_helper(std::shared_ptr> const& tree_node, std::vector* output){ +void get_history_helper(TreeNode* tree_node, std::vector* output){ if(tree_node==nullptr) return; assert(tree_node->parent != tree_node); get_history_helper(tree_node->parent, output); output->push_back(tree_node->data); } template -std::vector get_history(std::shared_ptr> const& tree_node){ +std::vector get_history(TreeNode* tree_node){ std::vector output; get_history_helper(tree_node, &output); return output; diff --git a/native_client/ctcdecode/third_party/object_pool/object_pool.h b/native_client/ctcdecode/third_party/object_pool/object_pool.h index 0c0a3287..e91a4f51 100755 --- a/native_client/ctcdecode/third_party/object_pool/object_pool.h +++ b/native_client/ctcdecode/third_party/object_pool/object_pool.h @@ -8,9 +8,29 @@ namespace godefv{ namespace memory{ +template class Allocator = std::allocator, std::size_t ChunkSize = 1024> +class object_pool_t; + +//! Custom deleter to recycle the deleted pointers. +template class Allocator = std::allocator, std::size_t ChunkSize = 1024> +struct object_pool_deleter_t{ +private: + object_pool_t* object_pool_ptr; +public: + explicit object_pool_deleter_t(decltype(object_pool_ptr) input_object_pool_ptr) : + object_pool_ptr(input_object_pool_ptr) + {} + + //! When a pointer provided by the ObjectPool is deleted, its memory is converted to an object slot to be recycled. + void operator()(Object* object_ptr) + { + object_pool_ptr->delete_object(object_ptr); + } +}; + //! Allocates instances of Object efficiently (constant time and log((maximum number of Objects used at the same time)/ChunkSize) calls to malloc in the whole lifetime of the object pool). //! When an instance returned by the object pool is destroyed, its allocated memory is recycled by the object pool. Defragmenting the object pool to free memory is not possible. -template class Allocator = std::allocator, std::size_t ChunkSize = 1024> +template class Allocator, std::size_t ChunkSize> class object_pool_t{ //! An object slot is an uninitialized memory space of the same size as Object. //! It is initially "free". It can then be "used" to construct an Object in place and the pointer to it is returned by the object pool. When the pointer is destroyed, the object slot is "recycled" and can be used again but it is not "free" anymore because "free" object slots are contiguous in memory. @@ -28,25 +48,15 @@ class object_pool_t{ object_slot_t* free_object_slots_begin; object_slot_t* free_object_slots_end; - //! Custom deleter to recycle the deleted pointers. - struct deleter_t{ - private: - object_pool_t* object_pool_ptr; - public: - explicit deleter_t(decltype(object_pool_ptr) input_object_pool_ptr) : - object_pool_ptr(input_object_pool_ptr) - {} - - //! When a pointer provided by the ObjectPool is deleted, its memory is converted to an object slot to be recycled. - void operator()(Object* object_ptr) - { - object_ptr->~Object(); - object_pool_ptr->recycled_object_slots.push_back(reinterpret_cast(object_ptr)); - } - }; + void delete_object(Object* object_ptr){ + object_ptr->~Object(); + recycled_object_slots.push_back(reinterpret_cast(object_ptr)); + } + friend object_pool_deleter_t; public: using object_t = Object; + using deleter_t = object_pool_deleter_t; using object_unique_ptr_t = std::unique_ptr; //!< The type returned by the object pool. object_pool_t(Allocator const& allocator = Allocator{}) : From 15ce05aa01f86ce129b5b03240b1bd33491042cc Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 14 Sep 2020 14:40:56 +0200 Subject: [PATCH 11/17] PR #3279 - Fixed spaces --- .../ctcdecode/ctc_beam_search_decoder.cpp | 4 ++-- native_client/ctcdecode/path_trie.cpp | 4 ++-- native_client/ctcdecode/path_trie.h | 24 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index 7efaaaa1..33238037 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -96,7 +96,7 @@ DecoderState::next(const double *probs, if (full_beam && log_prob_c + prefix->score < min_cutoff) { break; } - assert(prefix->is_empty() || prefix->timesteps!=nullptr); + assert(prefix->is_empty() || prefix->timesteps != nullptr); // blank if (c == blank_id_) { @@ -242,7 +242,7 @@ DecoderState::decode(size_t num_results) const output.timesteps = get_history(prefix->timesteps); output.confidence = scores[prefix]; outputs.push_back(output); - if(outputs.size()>=num_returned) break; + if (outputs.size() >= num_returned) break; } return outputs; diff --git a/native_client/ctcdecode/path_trie.cpp b/native_client/ctcdecode/path_trie.cpp index 2b572fa8..e68b1ca7 100644 --- a/native_client/ctcdecode/path_trie.cpp +++ b/native_client/ctcdecode/path_trie.cpp @@ -179,11 +179,11 @@ void PathTrie::iterate_to_vec(std::vector& output) { break; } } - if (timesteps == nullptr){ + if (timesteps == nullptr) { timesteps = add_child(previous_timesteps, new_timestep); } } - previous_timesteps=nullptr; + previous_timesteps = nullptr; output.push_back(this); } diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 4c8c82b3..92dd288b 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -15,8 +15,8 @@ * It is used to store the timesteps data for the PathTrie below */ template -struct TreeNode{ - TreeNode* parent; +struct TreeNode { + TreeNode* parent; std::vector, godefv::memory::object_pool_deleter_t> >> children; DataT data; @@ -30,7 +30,7 @@ TreeNode* add_child(TreeNode* node, ChildDataT&& data_); template std::vector get_history(TreeNode*); -using TimestepTreeNode=TreeNode; +using TimestepTreeNode = TreeNode; /* Trie tree for prefix storing and manipulating, with a dictionary in * finite-state transducer for spelling correction. @@ -85,10 +85,10 @@ public: float score; float approx_ctc; unsigned int character; - TimestepTreeNode* timesteps=nullptr; + TimestepTreeNode* timesteps = nullptr; // timestep temporary storage for each decoding step. - TimestepTreeNode* previous_timesteps=nullptr; + TimestepTreeNode* previous_timesteps = nullptr; unsigned int new_timestep; PathTrie* parent; @@ -108,21 +108,21 @@ private: // TreeNode implementation template -TreeNode* add_child(TreeNode* node, ChildDataT&& data_){ - static godefv::memory::object_pool_t> tree_node_pool; - node->children.push_back(tree_node_pool.make_unique(node, std::forward(data_))); +TreeNode* add_child(TreeNode* node, ChildDataT&& data_) { + static godefv::memory::object_pool_t> tree_node_pool; + node->children.push_back(tree_node_pool.make_unique(node, std::forward(data_))); return node->children.back().get(); } template -void get_history_helper(TreeNode* tree_node, std::vector* output){ - if(tree_node==nullptr) return; - assert(tree_node->parent != tree_node); +void get_history_helper(TreeNode* tree_node, std::vector* output) { + if (tree_node == nullptr) return; + assert(tree_node->parent != tree_node); get_history_helper(tree_node->parent, output); output->push_back(tree_node->data); } template -std::vector get_history(TreeNode* tree_node){ +std::vector get_history(TreeNode* tree_node) { std::vector output; get_history_helper(tree_node, &output); return output; From 14bd9033d6b133c1157eb737e7257a7984b79a51 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Mon, 14 Sep 2020 22:45:42 +0200 Subject: [PATCH 12/17] Revert "PR #3279 - removed unrelated code" This reverts commit 78c4ef17b11fe681702cb0619a0b938a0b59f5bd. --- native_client/ctcdecode/ctc_beam_search_decoder.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index 33238037..b533d3dc 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -96,6 +96,9 @@ DecoderState::next(const double *probs, if (full_beam && log_prob_c + prefix->score < min_cutoff) { break; } + if (prefix->score == -NUM_FLT_INF) { + continue; + } assert(prefix->is_empty() || prefix->timesteps != nullptr); // blank From 1fa2e4ebccd6102b5e4ff837147854a2c0369f72 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Tue, 15 Sep 2020 21:30:06 +0200 Subject: [PATCH 13/17] PR #3279 - Fixed buggy timestep tree root --- native_client/ctcdecode/ctc_beam_search_decoder.cpp | 11 ++++------- native_client/ctcdecode/path_trie.h | 13 +++++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index b533d3dc..874dc7c1 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -35,6 +35,7 @@ DecoderState::init(const Alphabet& alphabet, PathTrie *root = new PathTrie; root->score = root->log_prob_b_prev = 0.0; prefix_root_.reset(root); + prefix_root_->timesteps = ×tep_tree_root_; prefixes_.push_back(root); if (ext_scorer && (bool)(ext_scorer_->dictionary)) { @@ -99,7 +100,7 @@ DecoderState::next(const double *probs, if (prefix->score == -NUM_FLT_INF) { continue; } - assert(prefix->is_empty() || prefix->timesteps != nullptr); + assert(prefix->timesteps != nullptr); // blank if (c == blank_id_) { @@ -182,11 +183,6 @@ DecoderState::next(const double *probs, // update log probs prefixes_.clear(); prefix_root_->iterate_to_vec(prefixes_); - if (abs_time_step_ == 0) { - for (PathTrie* prefix:prefixes_) { - prefix->timesteps = ×tep_tree_root_; - } - } // only preserve top beam_size prefixes if (prefixes_.size() > beam_size_) { @@ -242,7 +238,8 @@ DecoderState::decode(size_t num_results) const for (PathTrie* prefix : prefixes_copy) { Output output; prefix->get_path_vec(output.tokens); - output.timesteps = get_history(prefix->timesteps); + output.timesteps = get_history(prefix->timesteps, ×tep_tree_root_); + assert(output.tokens.size() == output.timesteps.size()); output.confidence = scores[prefix]; outputs.push_back(output); if (outputs.size() >= num_returned) break; diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 92dd288b..eac0d7b4 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -28,7 +28,7 @@ template TreeNode* add_child(TreeNode* node, ChildDataT&& data_); template -std::vector get_history(TreeNode*); +std::vector get_history(TreeNode const*, TreeNode const* = nullptr); using TimestepTreeNode = TreeNode; @@ -115,16 +115,17 @@ TreeNode* add_child(TreeNode* node, ChildDataT&& data_) { } template -void get_history_helper(TreeNode* tree_node, std::vector* output) { - if (tree_node == nullptr) return; +void get_history_helper(TreeNode const* tree_node, TreeNode const* root, std::vector* output) { + if (tree_node == root) return; + assert(tree_node != nullptr); assert(tree_node->parent != tree_node); - get_history_helper(tree_node->parent, output); + get_history_helper(tree_node->parent, root, output); output->push_back(tree_node->data); } template -std::vector get_history(TreeNode* tree_node) { +std::vector get_history(TreeNode const* tree_node, TreeNode const* root) { std::vector output; - get_history_helper(tree_node, &output); + get_history_helper(tree_node, root, &output); return output; } From 23944b97dbdf739909a1eb88e8e40f65999677e7 Mon Sep 17 00:00:00 2001 From: godeffroy Date: Wed, 16 Sep 2020 14:03:59 +0200 Subject: [PATCH 14/17] PR #3279 - Made the timestep tree thread safe --- native_client/ctcdecode/path_trie.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index eac0d7b4..d4d1e7cc 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -109,7 +109,7 @@ private: // TreeNode implementation template TreeNode* add_child(TreeNode* node, ChildDataT&& data_) { - static godefv::memory::object_pool_t> tree_node_pool; + static thread_local godefv::memory::object_pool_t> tree_node_pool; node->children.push_back(tree_node_pool.make_unique(node, std::forward(data_))); return node->children.back().get(); } From 5bf5124366ac14d030607d2b8e1a26e85edb4fcc Mon Sep 17 00:00:00 2001 From: godeffroy Date: Thu, 17 Sep 2020 14:27:33 +0200 Subject: [PATCH 15/17] PR #3279 - Added some comments, harmonized a few names, removed unneeded spaces --- .../ctcdecode/ctc_beam_search_decoder.cpp | 2 +- native_client/ctcdecode/path_trie.h | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index 874dc7c1..3d412155 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -37,7 +37,7 @@ DecoderState::init(const Alphabet& alphabet, prefix_root_.reset(root); prefix_root_->timesteps = ×tep_tree_root_; prefixes_.push_back(root); - + if (ext_scorer && (bool)(ext_scorer_->dictionary)) { // no need for std::make_shared<>() since Copy() does 'new' behind the doors auto dict_ptr = std::shared_ptr(ext_scorer->dictionary->Copy(true)); diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index d4d1e7cc..cab1b4ab 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -24,11 +24,17 @@ struct TreeNode { TreeNode(TreeNode* parent_, DataT const& data_): parent{parent_}, data{data_} {} }; +/* Creates a new TreeNode with given data as a child to the given node. + * Returns a pointer to the created node. This pointer remains valid as long as the child is not destroyed. + */ template -TreeNode* add_child(TreeNode* node, ChildDataT&& data_); +TreeNode* add_child(TreeNode* tree_node, ChildDataT&& data); +/* Returns the sequence of tree node's data from the given root (exclusive) to the given tree_node (inclusive). + * By default (if no root is provided), the full sequence from the root of the tree is returned. + */ template -std::vector get_history(TreeNode const*, TreeNode const* = nullptr); +std::vector get_history(TreeNode const* tree_node, TreeNode const* root = nullptr); using TimestepTreeNode = TreeNode; @@ -108,10 +114,10 @@ private: // TreeNode implementation template -TreeNode* add_child(TreeNode* node, ChildDataT&& data_) { +TreeNode* add_child(TreeNode* tree_node, ChildDataT&& data) { static thread_local godefv::memory::object_pool_t> tree_node_pool; - node->children.push_back(tree_node_pool.make_unique(node, std::forward(data_))); - return node->children.back().get(); + tree_node->children.push_back(tree_node_pool.make_unique(tree_node, std::forward(data))); + return tree_node->children.back().get(); } template From 371ddb84e5113e75846c43fe0455c06124da369b Mon Sep 17 00:00:00 2001 From: godeffroy Date: Thu, 17 Sep 2020 17:55:45 +0200 Subject: [PATCH 16/17] PR #3279 - Added README.mozilla to tell where the object pool code is from and updated the object pool code from this origin (minor update). --- native_client/ctcdecode/path_trie.h | 4 ++-- .../third_party/object_pool/README.mozilla | 1 + .../ctcdecode/third_party/object_pool/object_pool.h | 13 +++++++------ .../ctcdecode/third_party/object_pool/unique_ptr.h | 12 ++++++------ 4 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 native_client/ctcdecode/third_party/object_pool/README.mozilla diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index cab1b4ab..93a09437 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -17,7 +17,7 @@ template struct TreeNode { TreeNode* parent; - std::vector, godefv::memory::object_pool_deleter_t> >> children; + std::vector, godefv::object_pool_deleter_t> >> children; DataT data; @@ -115,7 +115,7 @@ private: // TreeNode implementation template TreeNode* add_child(TreeNode* tree_node, ChildDataT&& data) { - static thread_local godefv::memory::object_pool_t> tree_node_pool; + static thread_local godefv::object_pool_t> tree_node_pool; tree_node->children.push_back(tree_node_pool.make_unique(tree_node, std::forward(data))); return tree_node->children.back().get(); } diff --git a/native_client/ctcdecode/third_party/object_pool/README.mozilla b/native_client/ctcdecode/third_party/object_pool/README.mozilla new file mode 100644 index 00000000..2db63215 --- /dev/null +++ b/native_client/ctcdecode/third_party/object_pool/README.mozilla @@ -0,0 +1 @@ +This code was imported from https://github.com/godefv/memory on September 17th 2020, commit 5ff1af8ee09ced04990b4863b2c02a8d07f4356a. It's licensed under "CC0 1.0 Universal" license. diff --git a/native_client/ctcdecode/third_party/object_pool/object_pool.h b/native_client/ctcdecode/third_party/object_pool/object_pool.h index e91a4f51..3a1e2810 100755 --- a/native_client/ctcdecode/third_party/object_pool/object_pool.h +++ b/native_client/ctcdecode/third_party/object_pool/object_pool.h @@ -6,12 +6,13 @@ #include #include -namespace godefv{ namespace memory{ +namespace godefv{ +// Forward declaration template class Allocator = std::allocator, std::size_t ChunkSize = 1024> class object_pool_t; -//! Custom deleter to recycle the deleted pointers. +//! Custom deleter to recycle the deleted pointers of the object_pool_t. template class Allocator = std::allocator, std::size_t ChunkSize = 1024> struct object_pool_deleter_t{ private: @@ -21,7 +22,6 @@ public: object_pool_ptr(input_object_pool_ptr) {} - //! When a pointer provided by the ObjectPool is deleted, its memory is converted to an object slot to be recycled. void operator()(Object* object_ptr) { object_pool_ptr->delete_object(object_ptr); @@ -48,6 +48,7 @@ class object_pool_t{ object_slot_t* free_object_slots_begin; object_slot_t* free_object_slots_end; + //! When a pointer provided by the ObjectPool is deleted, its memory is converted to an object slot to be recycled. void delete_object(Object* object_ptr){ object_ptr->~Object(); recycled_object_slots.push_back(reinterpret_cast(object_ptr)); @@ -60,8 +61,8 @@ public: using object_unique_ptr_t = std::unique_ptr; //!< The type returned by the object pool. object_pool_t(Allocator const& allocator = Allocator{}) : - free_object_slots_begin{ free_object_slots_end }, // At the begining, set the 2 iterators at the same value to simulate a full pool. - chunk_allocator{ allocator } + chunk_allocator{ allocator }, + free_object_slots_begin{ free_object_slots_end } // At the begining, set the 2 iterators at the same value to simulate a full pool. {} //! Returns a unique pointer to an object_t using an unused object slot from the object pool. @@ -102,6 +103,6 @@ public: } }; -}} /* namespace godefv::memory */ +} /* namespace godefv */ #endif /* GODEFV_MEMORY_OBJECT_POOL_H */ diff --git a/native_client/ctcdecode/third_party/object_pool/unique_ptr.h b/native_client/ctcdecode/third_party/object_pool/unique_ptr.h index b1f00950..44f21cb8 100755 --- a/native_client/ctcdecode/third_party/object_pool/unique_ptr.h +++ b/native_client/ctcdecode/third_party/object_pool/unique_ptr.h @@ -3,10 +3,11 @@ #include -namespace godefv{ namespace memory{ +namespace godefv{ //! A deleter to deallocate memory which have been allocated by the given allocator. -template struct allocator_deleter_t +template +struct allocator_deleter_t { allocator_deleter_t(Allocator const& allocator) : mAllocator{ allocator } @@ -23,17 +24,16 @@ private: //! A smart pointer like std::unique_ptr but templated on an allocator instead of a deleter. //! The deleter is deduced from the given allocator. -template > -class unique_ptr_t : public std::unique_ptr> +template> +struct unique_ptr_t : public std::unique_ptr> { using base_t = std::unique_ptr>; -public: unique_ptr_t(Allocator allocator = Allocator{}) : base_t{ allocator.allocate(1), allocator_deleter_t{ allocator } } {} }; -}} // namespace godefv::memory +} // namespace godefv #endif // GODEFV_MEMORY_ALLOCATED_UNIQUE_PTR_H From 188501a333451b4765a4660d8ce11796b3a07b7c Mon Sep 17 00:00:00 2001 From: godeffroy Date: Thu, 17 Sep 2020 19:10:43 +0200 Subject: [PATCH 17/17] PR #3279 - Reverted unrelated and unwanted change. --- native_client/ctcdecode/ctc_beam_search_decoder.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/native_client/ctcdecode/ctc_beam_search_decoder.cpp b/native_client/ctcdecode/ctc_beam_search_decoder.cpp index 3d412155..564ca60a 100644 --- a/native_client/ctcdecode/ctc_beam_search_decoder.cpp +++ b/native_client/ctcdecode/ctc_beam_search_decoder.cpp @@ -235,14 +235,13 @@ DecoderState::decode(size_t num_results) const std::vector outputs; outputs.reserve(num_returned); - for (PathTrie* prefix : prefixes_copy) { + for (size_t i = 0; i < num_returned; ++i) { Output output; - prefix->get_path_vec(output.tokens); - output.timesteps = get_history(prefix->timesteps, ×tep_tree_root_); + prefixes_copy[i]->get_path_vec(output.tokens); + output.timesteps = get_history(prefixes_copy[i]->timesteps, ×tep_tree_root_); assert(output.tokens.size() == output.timesteps.size()); - output.confidence = scores[prefix]; + output.confidence = scores[prefixes_copy[i]]; outputs.push_back(output); - if (outputs.size() >= num_returned) break; } return outputs;