Commit 9a3bf5d
Feat/dynamicemb retain evicted keys (#443)
* feat(dynamicemb): retain evicted keys at last tier + pop_evicted_keys API
Add an opt-in retain_evicted_keys table option so the last-tier storage records the keys it evicts (instead of silently dropping them), plus a pop_evicted_keys API to read them back per table, unique and incrementally.
C++: insert_body gains a CollectEvicted template sink that compacts each Evict victim's (key, table_id); wired through a new AoT table_insert_collect_kernel and a dyn_emb_insert_collect_entry LruLfu cubin entry (reusing EvictParams, no ABI change), exposed as table_insert_collect_evicted. Covers all score policies. Only InsertResult::Evict is collected, not Busy.
Python: retain_evicted_keys config (in get_grouped_key so retain-differing tables don't share storage); the last-tier state accumulates evicted (key, table_id) chunks, de-duplicated only on pop; DynamicEmbStorage / HybridStorage pop_evicted_keys; module- and model-level pop_evicted_keys with optional pg all_gather (else per-rank local, disjoint). Design doc under docs/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(dynamicemb): retain_evicted_keys suite + fix HBM-direct forward retain gap
Non-caching training forward inserts through _prefetch_hbm_direct_path, which calls key_index_map.insert() directly (bypassing _insert_key_values), so its last-tier eviction was never retained. Add the collect_evicted branch there too. (Prefetched keys are ref-counter protected, so eviction only materializes across forward+backward steps.)
New self-contained suite under test/unit_tests/retain_evicted_keys/ (12 cases): table (whole-set oracle for insert(collect_evicted=True) on LruLfu cubin + AoT, table_id routing, no-eviction, determinism raise); storage (DynamicEmbStorage end-to-end collect->pop unique->gone->incremental, retain=False empty, dedup/table_id-filter/clear-isolation unit); module (HBM-direct training retains = the gap above, disabled omitted, table_names filter); distributed model (row-wise sharded: pg=None disjoint per rank / pg union). Wired into unit_test.sh (fwd_bwd group).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(dynamicemb): translate retain_evicted_keys_design.md to English
Also sync a few stale spots to the as-built code: retain buffers are tensor chunk lists (not ExtendableBuffer), pop returns host tensors, tests live under test/unit_tests/retain_evicted_keys/ (distributed test included), and the API lives in incremental_dump.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(dynamicemb): return DeltaDumpResult map from incremental_dump
Refactor incremental_dump's return from (ret_tensors, ret_scores) to
Dict[collection_path, DeltaDumpResult], packing per-collection
column-aligned lists (table_names/keys/values/evicted_keys/meta) with a
slot_index that pins each key to its physical row for a future precise
replay_increment.
- slot_index encoding: single-tier normal = key_slot (== value row);
NO_EVICTION = (key_slot << 32) | value_row; HybridStorage = bit63 tier
| bits0-62 key_slot.
- HybridStorage + NO_EVICTION now raises (non-caching partial-HBM storage
cannot host the NO_EVICTION auto-increment score policy).
- pop_evicted_keys returns a host tensor (CUDA-accelerated internally).
- incremental_dump accepts an optional pg; keys/values/evicted/slot_index
are all_gathered within it (dist_type "continuous" unsupported -> raise).
- meta.world_size records the table's ROW_WISE shard fan-out (global
dist.get_world_size(), which equals input_dist's pg.size()).
- Tests: DeltaDumpResult adaptations, HybridStorage tier slot_index test,
skip NO_EVICTION+partial-HBM combo; docs: DeltaDumpResult design +
DynamicEmb_APIs updates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(dynamicemb): make hybrid tier slot_index test world_size-agnostic
test_hybrid_incremental_dump_slot_index_tier inserted a fixed 256 keys and
asserted both tiers contribute dumped keys. But the HBM tier's per-rank
capacity is global_capacity / world_size, so on a single GPU (world_size 1)
the HBM tier holds all 256 keys, nothing is evicted, and the host tier stays
empty -> the "host tier must contribute dumped keys" assert fails. The test
only passed at world_size >= 2.
Derive the insert count from the actual HBM capacity instead:
n = hbm_cap + min(hbm_cap, host_cap). This fills the HBM tier and overflows
enough to guarantee a spill into the host tier on any world_size. Verified
passing on both nproc=1 and nproc=2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(dynamicemb): apply black formatting via pre-commit
Run black 23.9.1 (pre-commit) over the dynamicemb files this branch adds or
modifies -- pure formatting, no logic changes. They were committed earlier
without running black (local dev had no pre-commit hook).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(dynamicemb): fix stale need_incremental_dump refs + guard score_function closures
Small PR#437-debt cleanups (these files landed via #437; touched here because
this branch builds directly on them):
- score.cuh: the LruLfu comment said tables are "created with
need_incremental_dump=True", but that parameter no longer exists -- they are
created with a compound (TIMESTAMP, LFU) score_strategy.
- lru_lfu_score_strategy_design.md: replace all 13 stale need_incremental_dump
references (a removed parameter, incl. a wrong get_score_policy signature)
with the actual "(TIMESTAMP, LFU) / existing incremental-dump LruLfu" terms.
- score_jit.py: _remap_score_function recompiles against fn.__globals__ only,
silently dropping closure captures, so a factory/closure score_function later
failed with an opaque numba NameError. Reject co_freevars up front with a
clear ValueError. Verified on EOS: closure raises, plain fns unaffected,
test_lru_lfu 19 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(dynamicemb): keep pop_evicted_keys empty/non-empty dtype consistent
_pop_state_evicted_keys' empty guard hardcoded torch.int64, but the non-empty
path returns torch.cat(chunks) whose dtype is key_index_map.key_type (== the
table's index_type, which can be int32/uint32). For int32-key tables the two
paths returned different dtypes, so a caller concatenating or comparing across
empty and non-empty pops would hit a RuntimeError. Return
state.key_index_map.key_type from the empty guard -- the same dtype the
non-empty path carries.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(dynamicemb): note incremental_dump does not support continuous dist_type
incremental_dump's slot_index targets precise replay_increment, which
reconstructs each key's owning rank via (key or hash(key)) % world_size --
only defined for roundrobin / hash_roundrobin. A table sharded with
dist_type="continuous" uses a range-based key->rank mapping this path does not
implement and raises NotImplementedError. Document the limitation in the
incremental_dump API section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(dynamicemb): replace retain_evicted_keys bool with EvictedItemMode enum
Replace the boolean DynamicEmbTableOptions.retain_evicted_keys with an
extensible enum EvictedItemMode { DISCARD (default), RETAIN_KEY }, leaving room
for future modes (e.g. RETAIN_VALUE) without another API change. Unreleased in
this branch, so no bool alias -- all usages updated: config (enum + field +
get_grouped_key), state field + branches (key_value_table,
batched_dynamicemb_{tables,function}), scored_hashtable message,
incremental_dump docstrings, __init__ export, the three retain tests, and the
API + design docs.
Also finalizes the empty-pop dtype test from the earlier dtype fix (d53eb76):
parametrized int64/uint64, empty-only -- a uint64 table's non-empty pop hits
torch's unimplemented UInt64 GPU indexing, tracked separately.
Verified on EOS: retain suite 8 + 5 + 1 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 3118b5f commit 9a3bf5d
30 files changed
Lines changed: 2619 additions & 130 deletions
File tree
- corelib/dynamicemb
- docs
- dynamicemb
- jit
- example
- src
- jit
- table_operation
- test
- unit_tests
- incremental_dump
- retain_evicted_keys
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
615 | 616 | | |
616 | 617 | | |
617 | 618 | | |
618 | | - | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
619 | 628 | | |
620 | 629 | | |
621 | 630 | | |
| |||
654 | 663 | | |
655 | 664 | | |
656 | 665 | | |
| 666 | + | |
657 | 667 | | |
658 | 668 | | |
659 | 669 | | |
| |||
747 | 757 | | |
748 | 758 | | |
749 | 759 | | |
| 760 | + | |
| 761 | + | |
750 | 762 | | |
751 | 763 | | |
752 | 764 | | |
| |||
756 | 768 | | |
757 | 769 | | |
758 | 770 | | |
759 | | - | |
760 | | - | |
761 | | - | |
762 | | - | |
763 | | - | |
764 | | - | |
765 | | - | |
| 771 | + | |
766 | 772 | | |
767 | 773 | | |
768 | 774 | | |
769 | 775 | | |
770 | | - | |
| 776 | + | |
771 | 777 | | |
772 | 778 | | |
773 | | - | |
| 779 | + | |
774 | 780 | | |
775 | 781 | | |
776 | 782 | | |
777 | | - | |
778 | | - | |
779 | | - | |
780 | | - | |
781 | | - | |
782 | | - | |
783 | | - | |
784 | | - | |
785 | | - | |
786 | | - | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
787 | 805 | | |
788 | 806 | | |
789 | 807 | | |
790 | 808 | | |
791 | 809 | | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
792 | 852 | | |
793 | 853 | | |
794 | 854 | | |
| |||
0 commit comments