LIMA
Libre Multilingual Analyzer — C++ API
Loading...
Searching...
No Matches
dependency_parser.h
Go to the documentation of this file.
1// Copyright 2021 CEA LIST
2// SPDX-FileCopyrightText: 2022 CEA LIST <gael.de-chalendar@cea.fr>
3//
4// SPDX-License-Identifier: MIT
5
6#ifndef DEEPLIMA_DEPENDENCY_PARSER
7#define DEEPLIMA_DEPENDENCY_PARSER
8
9#include <vector>
10#include <string>
11#include <memory>
12#include <chrono>
13#include <thread>
14#include <algorithm>
15
16#include "token_type.h"
17#include "utils/str_index.h"
19#include "deeplima/graph_dp.h"
20#include "deeplima/token_type.h"
21// #include "graph_dp/impl/graph_dp_impl.h"
24
25#include "graph_dp.h"
26
27namespace deeplima
28{
29
31{
32public:
33
35 {
36 uint32_t m_lemm_idx = 0;
37 std::vector<uint8_t> m_classes;
38 uint16_t m_head_idx = 0; // => max sentence length == 2^16
39 uint16_t m_rel_type = 0;
40
41 token_with_analysis_t(size_t num_classes)
42 : impl::token_t(), m_classes(num_classes, 0)
43 {
44 }
45
48 virtual ~token_with_analysis_t() = default;
49 };
50
51 struct tokens_with_analysis_t : public token_buffer_t<token_with_analysis_t>
52 {
55
56 tokens_with_analysis_t(size_t size, size_t num_classes)
57 : Parent(size, token_with_analysis_t(num_classes))
58 {
59 }
60 };
61
63 {
64 friend class DependencyParser;
65
66 protected:
69
70 inline void set_token(const typename tokens_with_analysis_t::token_t* p)
71 {
72 m_ptoken = p;
73 }
74 public:
75
77 : m_stridx(stridx),
78 m_ptoken(nullptr)
79 { }
80
81 inline token_flags_t flags() const
82 {
83 assert(nullptr != m_ptoken);
84 return m_ptoken->m_flags;
85 }
86
87 inline bool eos() const
88 {
89 assert(nullptr != m_ptoken);
91 }
92
93 inline uint32_t cls(size_t idx) const
94 {
95 assert(nullptr != m_ptoken);
96 return m_ptoken->m_classes[idx];
97 }
98
99 inline const std::string& form() const
100 {
101 assert(nullptr != m_ptoken);
102 const std::string& f = m_stridx.get_str(m_ptoken->m_form_idx);
103 return f;
104 }
105 };
106
108 {
109 const tokens_with_analysis_t& m_data;
110 mutable enriched_token_t m_token; // WARNING: only one iterator is supported
111
112 public:
114
116 : m_data(data), m_token(stridx) { }
117
118 inline typename tokens_with_analysis_t::size_type size() const
119 {
120 return m_data.size();
121 }
122
123 inline const enriched_token_t& operator[](size_t idx) const
124 {
125 m_token.set_token(m_data.data() + idx);
126 return m_token;
127 }
128 };
129
131 {
132 const StringIndex& m_stridx;
133 const std::vector<token_with_analysis_t>& m_buffer;
134 std::shared_ptr< StdMatrix<uint32_t> > m_heads;
135 size_t m_current;
136 size_t m_offset;
137 size_t m_end;
138 const std::vector<std::string>* m_rel_names; // deprel id -> string (may be null)
139
140 public:
142 const std::vector<token_with_analysis_t>& buffer,
143 std::shared_ptr< StdMatrix<uint32_t> > heads,
144 size_t offset,
145 size_t end,
146 const std::vector<std::string>* rel_names = nullptr)
147 : m_stridx(stridx), m_buffer(buffer), m_heads(heads),
148 m_current(0), m_offset(offset), m_end(end - offset), m_rel_names(rel_names)
149 {
150 assert(end > offset + 1);
151 }
152
153 inline bool end() const
154 {
155 return m_current >= m_end;
156 }
157
158 inline token_flags_t flags() const
159 {
160 assert(! end());
161 return m_buffer[m_current].m_flags;
162 }
163
164 inline uint16_t token_offset() const
165 {
166 return m_buffer[m_current].m_offset;
167 }
168
169 inline uint16_t token_len() const
170 {
171 return m_buffer[m_current].m_len;
172 }
173
174 inline uint32_t form_idx() const
175 {
176 return m_buffer[m_current].m_form_idx;
177 }
178
179 inline uint32_t lemma_idx() const
180 {
181 throw std::runtime_error("TokenIterator::lemma_idx");
182 return 0;
183 }
184
185 inline uint32_t head() const
186 {
187 return m_heads->get(m_current, 0);
188 }
189
190 // Predicted deprel (relation label) id, or 0 if the model has no label decoder.
191 inline uint32_t rel() const
192 {
193 return (m_heads->size() >= 2) ? m_heads->get(m_current, 1) : 0;
194 }
195
196 // Predicted deprel as a string ("dep" if no vocabulary is available).
197 inline const char* deprel() const
198 {
199 if (nullptr != m_rel_names && rel() < m_rel_names->size())
200 {
201 return (*m_rel_names)[rel()].c_str();
202 }
203 return "dep";
204 }
205
206 inline const char* form() const
207 {
208 assert(! end());
209 const std::string& f = m_stridx.get_str(m_buffer[m_current].m_form_idx);
210 return f.c_str();
211 }
212
213 inline const char* lemma() const
214 {
215 assert(! end());
216 const std::string& f = m_stridx.get_str(m_buffer[m_current].m_lemm_idx);
217 return f.c_str();
218 }
219
220 // MWT: number of sub-words if this token starts an expanded multiword token
221 // (0 otherwise), and the surface form for the "N-M surface" range line.
222 inline uint8_t mwt_len() const
223 {
224 return m_buffer[m_current].m_mwt_len;
225 }
226
227 inline const char* mwt_surface() const
228 {
229 return m_stridx.get_str(m_buffer[m_current].m_mwt_surface_idx).c_str();
230 }
231
232 inline void next()
233 {
234 m_current++;
235 }
236
237 inline void reset(size_t position = 0)
238 {
239 m_current = position;
240 }
241
242 inline size_t position() const
243 {
244 return m_current;
245 }
246
247 inline uint8_t token_class(size_t cls_idx) const
248 {
249 assert(m_offset == 0);
250 uint8_t val = m_buffer[m_current].m_classes[cls_idx];
251 return val;
252 }
253 };
254
255 typedef std::function < void (const StringIndex& stridx,
256 const std::vector<token_with_analysis_t>& tokens,
257 std::shared_ptr< StdMatrix<uint32_t> > heads,
258 size_t begin,
259 size_t end) > output_callback_t;
260
261 DependencyParser(const std::string& model_fn,
262 const PathResolver& path_resolver,
263 std::shared_ptr< StringIndex > stridx,
264 const std::vector<std::string>& input_class_names,
265 size_t buffer_size,
266 size_t num_buffers,
267 size_t threads = 1)
268 : m_buffer_size(buffer_size),
271 m_stridx_ptr(stridx),
272 // m_stridx(*stridx),
273 m_impl()
274 {
275 assert(m_buffer_size > 0);
276 assert(num_buffers > 0);
277 m_buffers.reserve(num_buffers);
278
279 m_impl.load(model_fn, path_resolver);
280 // Sentence-level parallelism: run several inference workers, each parsing a
281 // different slot single-threaded (Eigen intra-op parallelism is disabled at
282 // startup, see Eigen::setNbThreads(1)). Workers must not exceed the number
283 // of slots (num_buffers), otherwise the thread pool would starve/deadlock.
284 size_t eff_threads = std::max<size_t>(1, std::min(threads, num_buffers));
285 m_impl.init(eff_threads, num_buffers, buffer_size, *m_stridx_ptr, input_class_names);
286
287 for (size_t i = 0; i < num_buffers; ++i)
288 {
289 m_buffers.emplace_back(m_buffer_size, input_class_names.size());
290 }
291
293 std::shared_ptr< StdMatrix<uint32_t> > heads,
294 size_t begin, size_t end, size_t slot_idx) {
295 // std::cerr << "handler called (dp): " << slot_idx << std::endl;
296
298 m_buffers[slot_idx],
299 heads,
300 begin,
301 end);
302
303 m_buffers[slot_idx].unlock();
304 });
305
306 }
307
309 {
310 // std::cerr << "~DependencyParser" << std::endl;
311 }
312
315 }
316
317 // deprel id -> string mapping from the model; empty if the model has no
318 // label decoder (in that case TokenIterator::deprel() falls back to "dep").
319 const std::vector<std::string>& get_rel_class_names() const {
320 return m_impl.get_rel_class_names();
321 }
322
323 void setStringIndex(std::shared_ptr<deeplima::StringIndex> stringIndexPtr) {
324 m_stridx_ptr = stringIndexPtr;
325 // m_stridx = StringIndex(*m_stridx_ptr;
326 }
327
328 void set_classes(size_t idx, const std::string& class_name, const std::vector<std::string>& data)
329 {
330 m_class_names.push_back(class_name);
331
332 if (idx + 1 > m_classes.size())
333 {
334 m_classes.resize(idx + 1);
335 }
336 assert(0 == m_classes[idx].size());
337 m_classes[idx] = data;
338
339 if (m_classes.size() > 1)
340 {
341 //m_has_feats = true;
342 for (size_t i = 0; i < m_class_names.size(); ++i)
343 {
344 const std::string& feat_name = m_class_names[i];
345 if (feat_name == "upos" || feat_name == "xpos" || feat_name == "eos")
346 {
347 continue;
348 }
349 //m_first_feature_to_print = i;
350 break;
351 }
352 }
353 }
354
355 // Apply the model to the sequence of tokens given by iter from the tagger
357 {
358 // std::cerr << "DependencyParser::operator(TokenSequenceAnalyzer<>::TokenIterator& iter)" << std::endl;
360 {
362 }
363
364 size_t first_timepoint_idx = 0;
365 // count_max_tokens_until_eos() APPENDS, so m_lengths must start empty. This
366 // handler is called once per tagger batch; without clearing here, a batch's
367 // per-sentence lengths were appended to the previous batch's leftover lengths,
368 // so predict() iterated far more (sum_lengths) than the buffer held (count) and
369 // every sentence in such a buffer was parsed at the wrong offset.
370 m_lengths.clear();
371 m_lengths.reserve(256);
372 size_t tokens_to_process = count_max_tokens_until_eos(iter, m_lengths);
373 // std::cerr << "DependencyParser::operator() tokens_to_process: " << tokens_to_process << std::endl;
374 bool insert_root = true;
375 while (!iter.end())
376 {
378 assert(m_current_buffer < m_buffers.size());
379
381 // std::cerr << "DependencyParser::operator() processing token: " << iter.form() << std::endl;
382
383 if (insert_root)
384 {
385 token.m_offset = 0;
386 token.m_len = 0;
387 token.m_form_idx = m_stridx_ptr->get_idx("<ROOT>");
388 // std::cerr << "<ROOT>" << std::endl;
390 token.m_lemm_idx = token.m_form_idx;
391 // The synthetic <ROOT> is never a multiword token; clear stale metadata
392 // since the buffer slot is reused across calls.
393 token.m_mwt_len = 0;
394 token.m_mwt_surface_idx = 0;
395 insert_root = false;
396 tokens_to_process--;
398 continue;
399 }
400 else
401 {
402 token.m_offset = iter.token_offset();
403 token.m_len = iter.token_len();
404 token.m_form_idx = iter.form_idx();
405 // std::cerr << iter.form() << std::endl;
406 // std::cerr << m_stridx_ptr->get_str(token.m_form_idx) << std::endl;
407 token.m_flags = iter.flags();
408 token.m_lemm_idx = iter.lemma_idx();
409 // Carry MWT metadata (surface idx is valid: DP shares the analyzer's
410 // StringIndex) so the dumper can emit the "N-M surface" range line.
411 token.m_mwt_len = iter.mwt_len();
412 token.m_mwt_surface_idx = iter.mwt_surface_idx();
413 // token.m_head_idx = iter.head();
414 // token.m_rel_type = 0; // TODO where is stored the rel type ???
415 // TODO lines below were commented out to avoid crash. Does it really work?
416 token.m_head_idx = iter.position();
417 token.m_rel_type = 0; // TODO where is stored the rel type ???
418 for (size_t i = 0; i < m_classes.size(); ++i)
419 {
420 token.m_classes[i] = iter.token_class(i);
421 }
422
423 if (iter.flags() & token_flags_t::sentence_brk ||
425 {
426 insert_root = true;
427 }
428 }
429
431 tokens_to_process--;
432 iter.next();
433
434 if (m_current_timepoint >= m_buffer_size || 0 == tokens_to_process)
435 {
436 // std::cerr << "DependencyParser::operator() call start_analysis " << m_current_buffer << ", "
437 // << first_timepoint_idx << "," << m_current_timepoint << std::endl;
439 m_started = true;
440
441 // if we can't wait until next call
443
444 if (!iter.end())
445 {
446 m_lengths.clear();
447 tokens_to_process = count_max_tokens_until_eos(iter, m_lengths);
448 insert_root = true;
449 first_timepoint_idx = m_current_timepoint;
450 }
451 }
452 }
453
454 m_started = true;
455 }
456
457 void finalize()
458 {
459 // std::cerr << "DependencyParser::finalize" << std::endl;
460 while (!m_started)
461 {
462 // std::cerr << "DependencyParser::finalize sleeping" << std::endl;
463 std::this_thread::sleep_for(std::chrono::milliseconds(500));
464 }
465
466 if (m_current_timepoint > 0)
467 {
469 {
470 // std::cerr << "DependencyParser::finalize call start_analysis" << std::endl;
471 // Analyse exactly the tokens accumulated in this (final, partial) buffer,
472 // not the whole buffer_size: passing -1 made handle_token_buffer fall back
473 // to buffer.size(), processing a stale tail of uninitialised timepoints.
475 }
476 else
477 {
478 //m_cls.no_more_data(m_current_buffer);
479 }
480 }
481
482 // std::cerr << "DependencyParser::finalize call send_all_results" << std::endl;
484 }
485
486protected:
488 {
489 // std::cerr << "DependencyParser::acquire_buffer" << std::endl;
490 size_t next_buffer_idx = (m_current_buffer + 1 < m_buffers.size()) ? (m_current_buffer + 1) : 0;
491 const token_buffer_t<token_with_analysis_t>& next_buffer = m_buffers[next_buffer_idx];
492
493 // wait for buffer
494 while (next_buffer.locked())
495 {
496 // std::cerr << "DependencyParser::acquire_buffer locked" << std::endl;
498 }
499 assert(!next_buffer.locked());
500
501 m_current_buffer = next_buffer_idx;
503 // std::cerr << "DependencyParser::acquire_buffer DONE:" << m_current_buffer << std::endl;
504 }
505
506 void start_analysis(size_t buffer_idx,
507 size_t first_timepoint_idx,
508 const std::vector<size_t>& lengths,
509 int count = -1)
510 {
511 // std::cerr << "DependencyParser::start_analysis buffer_idx=" << buffer_idx << ", first_timepoint_idx=" << first_timepoint_idx
512 // << ", lengths=" << lengths << ", count=" << count << std::endl;
513 assert(!m_buffers[buffer_idx].locked());
514 m_buffers[buffer_idx].lock();
515
516 const tokens_with_analysis_t& current_buffer = m_buffers[buffer_idx];
517 m_impl.handle_token_buffer(buffer_idx,
518 first_timepoint_idx,
519 enriched_token_buffer_t(current_buffer, *m_stridx_ptr),
520 lengths,
521 count);
522 }
523
524 size_t count_max_tokens_until_eos(TokenSequenceAnalyzer<>::TokenIterator& iter, std::vector<size_t>& lengths)
525 {
526 size_t current_iter_pos = iter.position();
527 size_t tokens_counter = 0;
528 size_t this_sentence_tokens = m_current_timepoint > 0 ? 0 : 1;
529
530 // std::cerr << "DependencyParser::count_max_tokens_until_eos m_current_timepoint=" << m_current_timepoint
531 // << "; tokens_counter=" << tokens_counter << "; this_sentence_tokens=" << this_sentence_tokens
532 // << "; m_buffer_size=" << m_buffer_size << std::endl;
533 if (m_current_timepoint + tokens_counter + this_sentence_tokens >= m_buffer_size)
534 {
535 throw std::runtime_error("No place for new tokens in this buffer");
536 }
537
538 while (!iter.end())
539 {
540 this_sentence_tokens++;
541 // std::cerr << "DependencyParser::count_max_tokens_until_eos m_current_timepoint=" << m_current_timepoint
542 // << "; tokens_counter=" << tokens_counter << "; this_sentence_tokens=" << this_sentence_tokens
543 // << "; m_buffer_size=" << m_buffer_size
544 // << "; token=" << iter.form() << std::endl;
545
546 if (iter.flags() & token_flags_t::sentence_brk ||
548 {
549 // End of a sentence. Record its length (including its synthetic root) and
550 // keep accumulating the following sentences into the SAME buffer, each
551 // with its own root. They are then analysed together in one pass and
552 // written to distinct output regions (predict() advances by length),
553 // instead of one slot per sentence all writing at output offset 0 (which
554 // made a shorter following sentence overwrite the previous one).
555 lengths.push_back(this_sentence_tokens);
556 tokens_counter += this_sentence_tokens;
557 iter.next();
558 if (iter.end())
559 {
560 this_sentence_tokens = 0;
561 break;
562 }
563 // Need room for at least the next sentence's root + one token.
564 if (m_current_timepoint + tokens_counter + 2 > m_buffer_size)
565 {
566 this_sentence_tokens = 0;
567 break;
568 }
569 this_sentence_tokens = 1; // synthetic root of the next sentence
570 continue;
571 }
572
573 if (m_current_timepoint + tokens_counter + this_sentence_tokens >= m_buffer_size)
574 {
575 // The buffer just filled in the MIDDLE of a sentence. Splitting it across
576 // two buffers parses each half separately (each with its own synthetic
577 // root), corrupting both — and, because predict() advances by length, every
578 // following sentence's heads end up shifted. If at least one whole sentence
579 // is already in this buffer, leave the current (incomplete) sentence for the
580 // next buffer, where it starts fresh and fits. Only a single sentence that
581 // on its own exceeds buffer_size has to be cut (kept here, rare).
582 if (tokens_counter > 0)
583 {
584 this_sentence_tokens = 0;
585 }
586 break;
587 }
588
589 iter.next();
590
591 }
592 if (this_sentence_tokens > 0)
593 {
594 lengths.push_back(this_sentence_tokens);
595 tokens_counter += this_sentence_tokens;
596 }
597
598 iter.reset(current_iter_pos);
599 // std::cerr << "DependencyParser::count_max_tokens_until_eos lengths=" << lengths
600 // << "; tokens_counter=" << tokens_counter << std::endl;
601 return tokens_counter;
602 }
603
604 std::vector<std::string> m_class_names;
605 std::vector<std::vector<std::string>> m_classes;
606
610 bool m_started = false;
611
612 std::shared_ptr</*const*/ StringIndex> m_stridx_ptr;
613 // /*const*/ StringIndex& m_stridx;
615 std::vector<tokens_with_analysis_t> m_buffers;
616 std::vector<size_t> m_lengths;
617
618#ifdef DP_VECTORIZER_WITH_PRECOMPUTING
619 using Vectorizer = FeaturesVectorizerWithPrecomputing<
623#elif DP_VECTORIZER_WITH_CACHE
624 using Vectorizer = FeaturesVectorizerWithCache<
627#else
631#endif
634{
635public:
636
638 m_fastText(std::make_shared<FastTextVectorizer<eigen_wrp::EigenMatrixXf::matrix_t, Eigen::Index>>()),
643 {}
644
645 // GraphDpImpl(
646 // size_t threads,
647 // size_t buffer_size_per_thread
648 // )
649 // : deeplima::graph_dp::impl::GraphDependencyParser(
650 // 0 /* TODO: FIX ME */, 4, threads * 2, buffer_size_per_thread, threads),
651 // m_fastText(std::make_shared<FastTextVectorizer<eigen_wrp::EigenMatrixXf::matrix_t, Eigen::Index>>()),
652 // m_current_timepoint(deeplima::graph_dp::impl::GraphDependencyParser::get_start_timepoint())
653 // {
654 // }
655
656 std::shared_ptr<EmbdUInt64Float> convert(const EmbdStrFloat& src)
657 {
658 auto d = src.get_int_dict<EmbdUInt64Float::value_t>();
659 auto t = src.get_tensor().transpose();
660 auto p = std::make_shared<EmbdUInt64Float>();
661 p->init(d, t);
662
663 return p;
664 }
665
666 virtual void load(const std::string& fn, const PathResolver& path_resolver)
667 {
669
670 m_featVectorizers.resize(this->get_input_str_dicts().size());
671 for (size_t i = 0; i < this->get_input_str_dicts().size(); ++i)
672 {
673 //auto d = (this->get_input_str_dicts()[i]);
674 m_featVectorizers[i] = convert(this->get_input_str_dicts()[i]);
675 }
676
677 m_fastText->load(path_resolver.resolve("embd", deeplima::graph_dp::impl::GraphDependencyParser::get_embd_fn(0), {"bin", "ftz"}));
678 }
679
680 void init(size_t threads,
681 size_t num_buffers,
682 size_t buffer_size_per_thread,
683 StringIndex& stridx,
684 const std::vector<std::string>& class_names)
685 {
686 m_fastText->get_words([&stridx](const std::string& word){ stridx.get_idx(word); });
687
688 std::vector<typename Vectorizer::feature_descr_t> feats;
689 feats.reserve(1/* + m_featVectorizers.size()*/);
690 feats.emplace_back(Vectorizer::str_feature, "form", m_fastText);
691
692 // Match the parser's required input features to the tagger's output columns
693 // by NAME rather than by position. The parser's input dimension is fixed by
694 // the model, so we iterate the features it expects (index 0 is "raw"/form,
695 // handled above). For each, we look up the tagger column producing it. Morph
696 // classes the tagger does not produce are fed UNK; tagger classes the parser
697 // does not need (e.g. xpos) are simply ignored. This tolerates tagger/parser
698 // feature-set and ordering drift between independently trained models.
699 const auto& dp_input_names =
700 deeplima::graph_dp::impl::GraphDependencyParser::get_input_str_dicts_names();
701 for (size_t j = 0; j + 1 < dp_input_names.size(); ++j)
702 {
703 const std::string& feat_name = dp_input_names[j + 1];
704
705 size_t tagger_col = Vectorizer::uint_feat_extractor_t::NO_COLUMN;
706 for (size_t i = 0; i < class_names.size(); ++i)
707 {
708 if (class_names[i] == feat_name)
709 {
710 tagger_col = i;
711 break;
712 }
713 }
714
715 if (Vectorizer::uint_feat_extractor_t::NO_COLUMN == tagger_col)
716 {
717 std::cerr << "Warning: tagger does not produce feature '" << feat_name
718 << "' expected by the dependency parser; using UNK." << std::endl;
719 }
720
721 feats.emplace_back(Vectorizer::int_feature, feat_name, m_featVectorizers[j]);
722 m_vectorizer.get_uint_feat_extractor().add_feature(feat_name, tagger_col);
723 }
724 // for (const auto& class_name: class_names)
725 // {
726 // int i = 0;
727 // for (const auto& input_str_dicts_names: deeplima::graph_dp::impl::GraphDependencyParser::get_input_str_dicts_names())
728 // {
729 // if (class_name == input_str_dicts_names)
730 // {
731 // feats.emplace_back(Vectorizer::int_feature,
732 // class_name,
733 // m_featVectorizers[i]);
734 //
735 // m_vectorizer.get_uint_feat_extractor().add_feature(class_name, i);
736 // break;
737 // }
738 // i++;
739 // }
740 // }
742
744
746 0, num_buffers, buffer_size_per_thread, threads,
748
750 }
751
752 void precompute_inputs(const typename Vectorizer::dataset_t& buffer)
753 {
754 m_vectorizer.precompute(buffer);
755 }
756
757 typedef std::function < void (std::shared_ptr< StdMatrix<uint32_t> > classes,
758 size_t begin, size_t end, size_t slot_idx) > tagging_callback_t;
759
761 {
762 m_callback = fn;
763 }
764
765 virtual ~GraphDpImpl()
766 {
767 // std::cerr << "~GraphDpImpl" << std::endl;
768 }
769
770protected:
771
778
779 inline void send_results(int32_t slot_idx)
780 {
781 // uint8_t lock_count = deeplima::graph_dp::impl::GraphDependencyParser::get_lock_count(slot_idx);
782 // std::cerr << "GraphDpImpl::send_results " << slot_idx+1
783 // << " (lock_count=" << int(lock_count) << ")\n";
786 // std::cerr << "GraphDpImpl::send_results " << slot_idx+1 << ", from=" << from << ", to=" << to << std::endl;
787
789
791 m_last_completed_slot = slot_idx;
792 }
793
794public:
795 inline void send_next_results()
796 {
797 auto slot_idx = m_last_completed_slot;
798 // std::cerr << "GraphDpImpl::send_next_results " << slot_idx+1 << std::endl;
799 if (-1 == slot_idx)
800 {
801 slot_idx = 0;
802 }
803 else
804 {
806 }
807 // int a=0;
809 // std::cerr << "GraphDpImpl::send_next_results " << slot_idx+1
810 // << " (lock_count=" << int(lock_count) << ")" << std::endl;
811
812 while (lock_count > 1)
813 {
814 // Worker still uses this slot. Waiting...
815 // std::cerr << "GraphDpImpl::send_next_results: waiting for slot " << slot_idx+1
816 // << " (lock_count==" << int(lock_count) << ")\n";
817 // deeplima::graph_dp::impl::GraphDependencyParser::pretty_print();
820 }
821 if (1 == lock_count)
822 {
823 // Data is ready. We can return it to caller
824 send_results(slot_idx);
825 }
826 }
827
828 inline void send_all_results()
829 {
830 auto slot_idx = m_last_completed_slot;
831 // std::cerr << "GraphDpImpl::send_all_results" << slot_idx << std::endl;
832 while (true)
833 {
834 if (-1 == slot_idx)
835 {
836 slot_idx = 0;
837 }
838 else
839 {
841 }
842
844 if (0 == lock_count)
845 {
846 // std::cerr << "GraphDpImpl::send_all_results DONE" << std::endl;
847 return;
848 }
849
850 while (lock_count > 1)
851 {
852 // Worker still uses this slot. Waiting...
853 // std::cerr << "GraphDpImpl::send_all_results: waiting for slot " << slot_idx+1
854 // << " (lock_count==" << int(lock_count) << ")\n";
855 // deeplima::graph_dp::impl::GraphDependencyParser::pretty_print();
858 }
859 if (1 == lock_count)
860 {
861 send_results(slot_idx);
862 }
863 }
864 }
865
866protected:
868 {
869 auto slot_idx = m_last_completed_slot;
870 if (-1 == slot_idx)
871 {
872 slot_idx = 0;
873 }
874 else
875 {
877 }
878
880
881 if (1 == lock_count)
882 {
883 // Data is ready. We can return it to caller
884 send_results(slot_idx);
885 }
886 }
887
888 inline void acquire_slot(size_t slot_no)
889 {
890 // m_current_slot_no = deeplima::graph_dp::impl::GraphDependencyParser::get_slot_idx(m_current_timepoint);
892 // std::cerr << "GraphDpImpl::acquiring_slot: " << (slot_no+1) << "; current lock count: " << int(lock_count) << std::endl;
893 // deeplima::graph_dp::impl::GraphDependencyParser::pretty_print();
894
895 while (lock_count > 1)
896 {
897 // Worker still uses this slot. Waiting...
898 // std::cerr << "GraphDpImpl::acquiring_slot, waiting for slot " << (slot_no+1)
899 // << " lock_count=" << int(lock_count) << std::endl;
900 // deeplima::graph_dp::impl::GraphDependencyParser::pretty_print();
903 }
904 if (1 == lock_count)
905 {
906 // Data is ready. We can return it to caller
907 send_results(slot_no);
908 }
909
911 // std::cerr << "GraphDpImpl::acquiring_slot: " << (slot_no+1) << "; before increment_lock_count: " << int(lock_count) << std::endl;
912 // deeplima::graph_dp::impl::GraphDependencyParser::pretty_print();
915 // std::cerr << "GraphDpImpl::acquiring_slot: " << (slot_no+1) << "; after increment_lock_count: " << int(lock_count) << std::endl;
916 // deeplima::graph_dp::impl::GraphDependencyParser::pretty_print();
917 }
918
919public:
920 virtual void handle_token_buffer(size_t slot_no,
921 size_t first_timepoint_idx,
922 const typename Vectorizer::dataset_t& buffer,
923 const std::vector<size_t>& lengths,
924 int timepoints_to_analyze = -1)
925 {
926 // int lock_count = deeplima::graph_dp::impl::GraphDependencyParser::get_lock_count(slot_no);
927 // std::cerr << "GraphDpImpl::handle_token_buffer " << (slot_no+1) << ", "
928 // << first_timepoint_idx
929 // << ", lengths=" << lengths
930 // << ", " << timepoints_to_analyze
931 // << "; lock_count=" << lock_count << std::endl;
933 acquire_slot(slot_no);
934 size_t count = (timepoints_to_analyze > 0) ? timepoints_to_analyze : buffer.size();
935 for (size_t i = 0; i < count; i++)
936 {
938 }
939
940 // Tokens are vectorised at local columns [0,count) and the TokenIterator reads
941 // heads back at the same local index, so the slot's input/output must begin at
942 // 0 too. Using first_timepoint_idx here shifted the decoder's writes by that
943 // amount whenever a buffer didn't start at timepoint 0, corrupting those
944 // sentences' heads (head ≈ gold − first_timepoint_idx).
945 (void) first_timepoint_idx;
949
950 // auto& slot = deeplima::graph_dp::impl::GraphDependencyParser::m_slots[slot_no];
951
952 // lock_count = deeplima::graph_dp::impl::GraphDependencyParser::get_lock_count(slot_no);
953 // std::cerr << "GraphDpImpl::handle_token_buffer slot " << (slot_no+1) << " retrieved: input="
954 // << slot.m_input_begin << ", " << slot.m_input_end << "; output="
955 // << slot.m_output_begin << ", " << slot.m_output_end << ", lock_count=" << lock_count
956 // << std::endl;
957
958 deeplima::graph_dp::impl::GraphDependencyParser::start_job(slot_no, timepoints_to_analyze > 0);
959 // std::cerr << "Slot " << slot_no << " sent to inference engine (graph_dp)" << std::endl;
960
961 // EAGER consumption. There are only num_slots output regions in the shared
962 // tensor but arbitrarily many 128-token buffers, so a later buffer reuses this
963 // slot's region. The lazy send (at slot reuse / finalize) let a following
964 // buffer overwrite this one's heads before they were read -> corrupted parses
965 // for inputs > one buffer and an OOB crash in the dumper. Wait for THIS slot's
966 // job to finish and emit its results now, before any other buffer touches the
967 // tensor. The deferred send_results_if_available()/send_all_results() then find
968 // nothing left and become no-ops, so each slot is still emitted exactly once.
970 {
972 }
974 {
975 send_results(slot_no);
976 }
977 }
978
990
991protected:
993 std::shared_ptr<FastTextVectorizer<eigen_wrp::EigenMatrixXf::matrix_t, Eigen::Index>> m_fastText;
994 std::vector<std::shared_ptr<FeatureVectorizerBase<Eigen::Index>>> m_featVectorizers;
995
997
1000
1003
1005};
1006
1008
1010};
1011
1012
1013
1014} // deeplima
1015
1016#endif // DEEPLIMA_DEPENDENCY_PARSER
virtual void load(const std::string &fn, const PathResolver &path_resolver)
virtual void handle_token_buffer(size_t slot_no, size_t first_timepoint_idx, const typename Vectorizer::dataset_t &buffer, const std::vector< size_t > &lengths, int timepoints_to_analyze=-1)
void precompute_inputs(const typename Vectorizer::dataset_t &buffer)
virtual void register_handler(const tagging_callback_t fn)
std::shared_ptr< EmbdUInt64Float > convert(const EmbdStrFloat &src)
std::shared_ptr< FastTextVectorizer< eigen_wrp::EigenMatrixXf::matrix_t, Eigen::Index > > m_fastText
void increment_timepoint(uint64_t &timepoint)
std::function< void(std::shared_ptr< StdMatrix< uint32_t > > classes, size_t begin, size_t end, size_t slot_idx) > tagging_callback_t
void init(size_t threads, size_t num_buffers, size_t buffer_size_per_thread, StringIndex &stridx, const std::vector< std::string > &class_names)
std::vector< std::shared_ptr< FeatureVectorizerBase< Eigen::Index > > > m_featVectorizers
uint8_t token_class(size_t cls_idx) const
TokenIterator(const StringIndex &stridx, const std::vector< token_with_analysis_t > &buffer, std::shared_ptr< StdMatrix< uint32_t > > heads, size_t offset, size_t end, const std::vector< std::string > *rel_names=nullptr)
const enriched_token_t & operator[](size_t idx) const
tokens_with_analysis_t::size_type size() const
enriched_token_buffer_t(const tokens_with_analysis_t &data, const StringIndex &stridx)
const tokens_with_analysis_t::token_t * m_ptoken
void set_token(const typename tokens_with_analysis_t::token_t *p)
std::shared_ptr< StringIndex > m_stridx_ptr
const std::vector< std::string > & get_rel_class_names() const
std::vector< std::string > m_class_names
void setStringIndex(std::shared_ptr< deeplima::StringIndex > stringIndexPtr)
void register_handler(const output_callback_t fn)
void set_classes(size_t idx, const std::string &class_name, const std::vector< std::string > &data)
std::vector< std::vector< std::string > > m_classes
void start_analysis(size_t buffer_idx, size_t first_timepoint_idx, const std::vector< size_t > &lengths, int count=-1)
output_callback_t m_output_callback
void operator()(TokenSequenceAnalyzer<>::TokenIterator &iter)
size_t count_max_tokens_until_eos(TokenSequenceAnalyzer<>::TokenIterator &iter, std::vector< size_t > &lengths)
DependencyParser(const std::string &model_fn, const PathResolver &path_resolver, std::shared_ptr< StringIndex > stridx, const std::vector< std::string > &input_class_names, size_t buffer_size, size_t num_buffers, size_t threads=1)
std::vector< tokens_with_analysis_t > m_buffers
std::vector< size_t > m_lengths
std::function< void(const StringIndex &stridx, const std::vector< token_with_analysis_t > &tokens, std::shared_ptr< StdMatrix< uint32_t > > heads, size_t begin, size_t end) > output_callback_t
const M & get_tensor() const
Definition embd_dict.h:91
std::string resolve(const std::string &prefix, const std::string &path, const std::vector< std::string > &accepted_ext={}) const
Handles multithreading.
bool get_slot_started(uint32_t idx) const
int32_t next_slot(uint32_t idx)
uint64_t get_slot_end(uint32_t idx) const
virtual void init(uint32_t max_feat, uint32_t overlap, uint32_t num_slots, uint32_t slot_len, uint32_t num_threads, bool precomputed_input=false)
void load(const std::string &fn)
void increment_timepoint(uint64_t &timepoint)
uint8_t get_lock_count(uint32_t idx) const
std::shared_ptr< StdMatrix< Out > > get_output()
void set_slot_begin(uint32_t idx, uint64_t slot_begin)
void set_slot_end(uint32_t idx, uint64_t slot_end)
void increment_lock_count(uint32_t idx, uint8_t v=1)
void decrement_lock_count(uint32_t idx)
uint64_t get_slot_begin(uint32_t idx) const
void start_job(uint32_t idx, bool no_more_data=false)
void set_slot_lengths(uint32_t idx, const std::vector< size_t > &lengths)
const S & get_str(const idx_t idx) const
Definition str_index.h:59
idx_t get_idx(const char *p, size_t len)
Definition str_index.h:24
A kind of RnnSequenceClassifier, used for named entities tagging (?), but also the parent of TaggingI...
Definition ner.h:129
void vectorize_timepoint(MatrixFloat &target, uint64_t timepoint, const typename DataSet::token_t &token) const
void init_features(const std::vector< feature_descr_t > &features)
EigenMatrix< Eigen::MatrixXf > EigenMatrixXf
@ paragraph_brk
Definition token_type.h:22
@ sentence_brk
Definition token_type.h:21
STL namespace.
token_with_analysis_t(const token_with_analysis_t &t)=default
token_with_analysis_t & operator=(const token_with_analysis_t &t)=default
token_buffer_t< token_with_analysis_t > Parent
tokens_with_analysis_t(size_t size, size_t num_classes)
token_flags_t m_flags
Definition token_type.h:44
token_t(uint16_t offset=0, uint16_t len=0, uint32_t idx=0, token_flags_t flags=token_flags_t::none)
Definition token_type.h:37