Skip to content

fix: reduce Trie and newmm peak memory; add tcc_pos_array() - #1323

Merged
bact merged 5 commits into
devfrom
copilot/fix-memory-usage-newmm-tokenization
Mar 10, 2026
Merged

fix: reduce Trie and newmm peak memory; add tcc_pos_array()#1323
bact merged 5 commits into
devfrom
copilot/fix-memory-usage-newmm-tokenization

Conversation

Copilot AI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Trie construction dominates newmm's peak memory since word_dict_trie() became lazy-loaded via @lru_cache (PR #1186) — the first tokenization call now pays the full trie-build cost. Three redundant allocations in Trie and one in the tokenizer hot path are addressed.

What do these changes do

  • Trie.Node.children lazy allocationNone by default; dict created on first child insert, reset to None when last child is deleted. Eliminates ~52 K empty dicts × 64 B ≈ 3.25 MiB for the default word-dict trie.
  • self.words: set[str]_word_count: int — the set duplicated every word string and added ~2 MiB of set-object overhead. A plain counter is incremented/decremented in add()/remove() on end flag transitions. __contains__ traverses the trie; __iter__ is a recursive DFS with a shared mutable prefix (avoids O(k²) list copies); __len__ returns the counter.
  • tcc_pos_array(text) → bytearray — new function in tcc_p.py alongside tcc_pos(). Returns bytearray(len(text)+1) with arr[i] = 1 at each valid TCC boundary. newmm._onecut uses this instead of set[int]; all four in valid_poss set-membership tests become valid_poss[i] array-index lookups.
  • mypy union-attr fixTrie.remove() prune loop adds an explicit if parent.children is not None: guard before del parent.children[ch] to narrow Optional[dict] for the type checker (always true at runtime).

What was wrong

  • Every Trie.Node allocated an empty {} dict at construction, even leaf nodes (52 K of them in the default trie).
  • self.words stored a full copy of every word string in a set, doubling string-reference overhead and adding ~2 MiB of set bookkeeping.
  • tcc_pos() returned a set[int], requiring ~80 B per position; newmm then did Python in-set tests in the inner loop.
  • Trie.remove() accessed parent.children[ch] without narrowing Optional[dict], causing a mypy union-attr error.

How this fixes it

Lazy children avoids allocating dicts that are never populated. Replacing words with _word_count removes the per-word string duplication. tcc_pos_array trades the set for a flat bytearray — O(1) indexed lookup with storage proportional to text length in characters rather than cluster count. The if parent.children is not None: guard gives mypy the type narrowing it needs.

Your checklist for this pull request

  • Passed code styles and structures
  • Passed code linting checks and unit test

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI assigned Copilot and bact Mar 9, 2026
@bact bact added the bug bugs in the library label Mar 9, 2026
@bact bact added this to the 5.3 milestone Mar 9, 2026
@bact bact added this to PyThaiNLP Mar 9, 2026
@bact bact moved this to In progress in PyThaiNLP Mar 9, 2026
Copilot AI changed the title [WIP] Fix high memory usage in newmm tokenization fix(newmm): reduce peak memory in safe_mode=False tokenization Mar 9, 2026
Copilot finished work on behalf of bact March 9, 2026 22:04
- trie.py: lazy Node.children (None instead of {}); saves ~3.25 MiB
  (52 K leaf nodes × 64 B) for the default word-dict trie
- trie.py: remove self.words set; add _word_count int; saves ~2 MiB
  of set-object overhead per Trie; rewrite __contains__, __iter__,
  __len__, add, remove to use trie traversal / DFS
- trie.py: __iter__ uses recursive DFS with shared mutable prefix to
  avoid O(k^2) list copies
- tcc_p.py: add tcc_pos_array() returning bytearray indexed by position;
  O(1) array lookup with len(text)+1 bytes vs. ~80 B/position in a set
- newmm.py: use tcc_pos_array() instead of tcc_pos(); replace all four
  'in valid_poss' set-membership tests with valid_poss[i] array indexing
- tests: add _word_count accuracy tests (re-add, double-remove, iter);
  add tcc_pos_array edge-case tests (empty/None/int input, array length)

Co-authored-by: bact <128572+bact@users.noreply.github.com>
Copilot AI changed the title fix(newmm): reduce peak memory in safe_mode=False tokenization fix: reduce Trie and newmm tokenizer peak memory usage Mar 10, 2026
Copilot finished work on behalf of bact March 10, 2026 05:13
@coveralls

coveralls commented Mar 10, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 65.638% (+0.05%) from 65.589%
when pulling 7562b15 on copilot/fix-memory-usage-newmm-tokenization
into 621f8f7 on dev.

Add explicit `if parent.children is not None:` guard before
`del parent.children[ch]` in Trie.remove() so mypy can narrow the
Optional[dict] type. The guard is always true at runtime but
mypy cannot infer this from the path-traversal logic above.

Add CHANGELOG.md entry for the Trie memory and tcc_pos_array changes.

Co-authored-by: bact <128572+bact@users.noreply.github.com>
Copilot AI changed the title fix: reduce Trie and newmm tokenizer peak memory usage fix: reduce Trie and newmm peak memory; add tcc_pos_array() Mar 10, 2026
Copilot finished work on behalf of bact March 10, 2026 05:42
@bact

bact commented Mar 10, 2026

Copy link
Copy Markdown
Member

Peak memory at first run is now 76.7 MB, reduced from 81.9 MB (-5.2MB or 6.3%).

@bact
bact marked this pull request as ready for review March 10, 2026 06:14
@sonarqubecloud

Copy link
Copy Markdown

@bact bact added enhancement enhance functionalities refactoring a technical improvement which does not add any new features or change existing features. and removed bug bugs in the library enhancement enhance functionalities labels Mar 10, 2026
@bact
bact merged commit ef81afc into dev Mar 10, 2026
27 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in PyThaiNLP Mar 10, 2026
@bact
bact deleted the copilot/fix-memory-usage-newmm-tokenization branch March 10, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring a technical improvement which does not add any new features or change existing features.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants