Repository navigation
Faster core (0.2.1) - #7
Merged
Merged
Conversation
Profiling showed the Rust core, not the Python wrapper, dominated the time per pair: a String per token, two hash maps per n-gram order, and an O(nm) table for rougeL. - Tokens are written into one buffer per text; stemming works in a reused byte buffer. - rouge1 counts in a dense array indexed by token id, rouge2 packs bigrams into u64 keys, and both use one map with decrementing counts. - rougeL uses Hyyro's bit-parallel LCS length; rougeLsum keeps the table because it needs rouge-score's exact backtracking. - The binding returns float triples and Python adds the cached names. Scores are unchanged: the parity suite against rouge-score and NLTK passes, and new tests compare the LCS and n-gram counts with the straightforward algorithms on random sequences.
Building the result dicts in Rust removes the Python-side loop that wrapped each row in a Score; the binding creates the namedtuples with tuple.__new__ and interned keys. Reserving the token buffers and the vocabulary up front, an ASCII fast path in the rouge-score tokenizer and a single index array for the LCS masks remove most allocations, which dominated short texts. Review fixes: - The LCS mask table only holds tokens present in both texts and falls back to the linear-memory table above 8 MiB, instead of growing with the number of distinct tokens times the length. - Scorers pickled by 0.2.0 load again: the score class is optional in the native constructor. - score_documents interns sentences only when rougeLsum is requested. - Occurrence counts are usize again.
- Hash maps reserve for at most 4096 entries, since repetitive texts have far fewer distinct tokens than occurrences. - The LCS mask-size check uses checked_mul, so it cannot wrap on 32-bit targets and skip the table fallback. - The native scorer reports its Python references to the cycle collector. - Scorers pickled by 0.1.0, which had no cache key, load and score. - The type stub matches the optional constructor arguments.
Real-data results after the rewrite: 30-80x faster than rouge-score on one core, 80-350x with eight threads, and faster than rouge-rust on every workload of its supported subset.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
About 2.5× faster on one core with identical scores. Profiling showed the Rust core, not the Python wrapper, dominated the time per pair.
rouge-scoretokenizer.rouge1uses dense counts,rouge2packed bigram keys, both with a single decrementing map.rougeLuses Hyyrö's bit-parallel LCS. Masks exist only for tokens present in both texts, with a linear-memory fallback above 8 MiB.rougeLsumkeeps the table because it needsrouge-score's exact backtracking.Scoreviatuple.__new__).checked_mulin the mask-size guard, GC traversal for the native scorer, pickles from 0.1.0 and 0.2.0 load.Parity: the full suite against
rouge-scoreand NLTK passes; new tests compare the bit-parallel LCS and the n-gram counts with the straightforward algorithms on random sequences and unpickle real 0.1.0 and 0.2.0 pickles.Real data (CNN/DailyMail, XSum, PubMed, 2,000 pairs each, M1 Pro): 29.7–80.6× faster than
rouge-scoreon one core (was 10.6–39.4×), 82.6–352.5× with 8 threads (was 39.7–201×). On the ROUGE-1/2/L subset without stemming it is now faster thanrouge-ruston every workload: 1.3–4.6× per call and 1.4–3.3× with 8 threads.