[feat] support probabilistic admission strategy for dynamicemb - #680
tiankongdeguiji merged 4 commits into
Conversation
dynamicemb now admits a missing key by a coin toss as well as by counting it: a key gets a fresh draw every time it turns up and is not yet in the table, so it takes 1 / probability appearances on average to get in. That filters by frequency without a counter, so the table costs no extra HBM and writes no counter into the checkpoint. The same upstream change moves the frequency counter onto the strategy that counts with it and deprecates DynamicEmbTableOptions.admission_counter, so the counter is passed to FrequencyAdmissionStrategy and the shard storage estimator reads it from there. This requires the bumped dynamicemb wheel, hence the requirements pin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8YACeFxVxCDHMXB9a2j2
7ea81c5 to
904389e
Compare
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8YACeFxVxCDHMXB9a2j2
| admit_strategy, dynamicemb_util.ProbabilisticAdmissionStrategy | ||
| ) | ||
| self.assertEqual(admit_strategy.probability, 0.25) | ||
| self.assertFalse(hasattr(admit_strategy, "counter")) |
There was a problem hiding this comment.
Minor, non-blocking: assertFalse(hasattr(admit_strategy, "counter")) pins upstream's attribute layout rather than tzrec behavior. FrequencyAdmissionStrategy.counter is already Optional, so a benign upstream refactor exposing a shared counter property on both strategies (None for probabilistic) would fail this test while tzrec stays correct. The guarantee that matters — probabilistic admission allocates no counter HBM — is already pinned behaviorally by test_counter_hbm_lands_only_for_frequency_admission. Suggest dropping this line; the isinstance + probability assertions above are sufficient.
| initializer_args=non_admitted_initializer_args, | ||
| ) | ||
| elif admission_strategy_type == "probabilistic_admission_strategy": | ||
| admit_strategy = ProbabilisticAdmissionStrategy( |
There was a problem hiding this comment.
Minor, non-blocking: the [0, 1] range check for probability lives only in the pinned wheel (verified present at 9643985: ProbabilisticAdmissionStrategy.__init__ raises ValueError outside the range, and NaN fails the same check — good fail-fast behavior at plan time). But nothing in tzrec's own suite pins that contract: AdmissionStrategyTest only exercises 0.25/0.5, and the wheel is built from a moving upstream branch, so a future pin bump could silently lose the validation. A cheap boundary case would lock it in:
def test_out_of_range_probability_rejected(self):
for bad in (-0.1, 1.5):
with self.assertRaises(ValueError):
self._options(
probabilistic_admission_strategy=(
feature_pb2.DynamicEmbProbabilisticAdmissionStrategy(
probability=bad
)
)
)(optionally also asserting 0.0/1.0 construct successfully).
| pip install dynamicemb==0.1.0+20260920.9643985.${DEVICE} -f https://tzrec.oss-accelerate.aliyuncs.com/third_party/dynamicemb/${DEVICE}/repo.html | ||
| ``` | ||
|
|
||
| 注:同一个 FeatureGroup 中若存在多个配置了 DynamicEmbedding 的特征,底层 dynamicemb 会自动将这些表融合到同一份存储里(table fusion),共享 cache/admission counter,降低显存占用并减少内存碎片,无需额外配置。 |
There was a problem hiding this comment.
Optional wording nit: this note predates the counter-less strategy — probabilistic tables have no admission counter (as the new bullet below says), so fused probabilistic tables share only the cache. Something like "共享 cache(配置 frequency 准入时还包含 admission counter,probabilistic 准入无 counter)" would keep it accurate. For clarity: mixing the two strategies in one FeatureGroup remains fine — different grouping keys just means those tables don't fuse (this PR's integration config relies on that), no error.
Code review — 5-area static review + upstream verificationOverall LGTM: clean, well-scoped change with strong test coverage. Three minor non-blocking inline comments; nothing blocking. Independently verified (against NVIDIA/recsys-examples @
Minor comments (all optional):
Reviewed areas: code quality, performance, test coverage, documentation accuracy, security (supply chain, input validation, multi-process safety). All five reviews completed. |
hasattr(strategy, "counter") pins upstream's attribute layout, which tzrec does not depend on -- the estimator branches on the strategy type -- and the guarantee it stood for, that a probabilistic table adds no counter bytes to the HBM estimate, is already pinned by the shard storage test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8YACeFxVxCDHMXB9a2j2
|
Thanks — went through all three. Applied (1/3): dropped Not applying (2/3): the out-of-range Not applying (3/3): the fusion note wording. The observation is correct, but that line is |
What
dynamicemb gained a second admission strategy, and this exposes it on
dynamicemb { }:Admission is consulted only for a key that is missing, so a key gets a fresh coin toss every
time it turns up and is not yet in the table: it takes
1 / probabilityappearances on averageto be admitted. That filters by frequency the way
frequency_admission_strategydoes, butwithout counting anything — no
KVCounter, so no second hash table in HBM and no counter columnin the checkpoint. The cheap option when a table only needs long-tail ids kept out.
frequency_admission_strategyis unchanged for users, but its plumbing moved: upstream(NVIDIA/recsys-examples#488) put the counter on the strategy that counts with it and deprecated
DynamicEmbTableOptions.admission_counter, so theKVCounteris now passed toFrequencyAdmissionStrategy(threshold=..., counter=...)and the shard storage estimator reads itfrom there. A probabilistic table contributes no counter bytes to the estimate.
One behavior change falls out of the same upstream refactor: admission strategies are now
compared by value rather than by object identity, so two dynamicemb tables configured alike fuse
into one storage where they previously could not (each table still keeps its own
counter_capacity).This needs a dynamicemb built from recsys-examples
9643985, hence the pins inrequirements/cu1{26,29,30}.txtand the install line in the doc.Test Plan
Everything below ran against a dynamicemb wheel built locally from
9643985(
0.1.0+20260920.9643985.cu130, cp311) on 4x A10.tzrec/utils/dynamicemb_util_test.py— 26/26, including 8 newAdmissionStrategyTestcases:both oneof arms translate, the counter lands on the frequency strategy at the per-rank capacity,
admission_counterstays unset and noDeprecationWarningis raised, the non-admittedinitializer defaults to
CONSTANT 0.0and honors an explicit one, identically configured tablesproduce equal grouping keys, and the shard storage estimate grows by exactly the counter's size
for frequency admission and not at all for probabilistic.
tzrec/tests/rank_integration_test.py::test_multi_tower_din_with_dynamicemb_train_eval— themock config's second dynamicemb feature (
id_8) now carriesprobabilistic_admission_strategy, so the 2-rank train_eval + eval covers one frequency-admittedand one probabilistic table in the same model, including dump/load.
admission rate a given
probabilityproduces.python tzrec/tests/run.py --scope gpu— full lane, as the dependency-upgrade check, since thewheel also picks up recsys-examples#487 (FTRL optimizer).
pre-commit runon the changed files andpyrefly check— clean.🤖 Generated with Claude Code
https://claude.ai/code/session_019t8YACeFxVxCDHMXB9a2j2