Skip to content

[BUG] replay_increment fails mid-write after cache flush expansion #494

Description

@honlzl

Describe the bug
We've been following this project for a while. We hit the problem below while testing replay_increment with a caching target.

replay_increment plans against the target's layout, flushes the cache, then writes each key at its source slot (batched_dynamicemb_tables.py:1934-1943). The problem: flush_cache can grow and rehash the backing store (_expand_storage_for_cache_flush_if_needed, key_value_table.py:3949), so the slots the plan just validated go stale and the write fails midway:

RuntimeError: replay_increment: 82 of 100 keys could not be written at their source slot on table 0 ... or the slot was held by another writer, meaning something ran against this table concurrently with the replay.

Nothing ran concurrently -- it was the flush itself.

Steps/Code to reproduce bug
L40. Both tables start at capacity 512; the flush inside replay_increment grows the target to 1152.

import torch
from dynamicemb import (DynamicEmbCheckMode, DynamicEmbPoolingMode,
    DynamicEmbScoreStrategy, DynamicEmbTableOptions, EmbOptimType, EvictedItemMode)
from dynamicemb.batched_dynamicemb_tables import BatchedDynamicEmbeddingTablesV2

def make_model(dev):
    opt = DynamicEmbTableOptions(
        index_type=torch.int64, embedding_dtype=torch.float32, device_id=dev,
        dim=8, max_capacity=4096, init_capacity=512, bucket_capacity=128,
        safe_check_mode=DynamicEmbCheckMode.IGNORE, local_hbm_for_values=65536,
        score_strategy=DynamicEmbScoreStrategy.TIMESTAMP, caching=True,
        evicted_item_mode=EvictedItemMode.DISCARD)
    return BatchedDynamicEmbeddingTablesV2(
        table_options=[opt], output_dtype=torch.float32, table_names=["t_0"],
        feature_table_map=[0], pooling_mode=DynamicEmbPoolingMode.SUM,
        use_index_dedup=False, optimizer=EmbOptimType.SGD)

def touch(m, keys, device):
    idx = torch.tensor(keys, dtype=torch.int64, device=device)
    off = torch.arange(0, len(keys) + 1, dtype=torch.int64, device=device)
    m(idx, off)
    torch.cuda.synchronize()

device = torch.device("cuda:0")
src, dst = make_model(0), make_model(0)
delta_keys, extra_keys = list(range(1001, 1101)), list(range(5001, 5451))

touch(src, delta_keys, device)
delta = src.incremental_dump({"t_0": 0})

all_keys = delta_keys + extra_keys
for i in range(0, len(all_keys), 50):  # small batches, or prefetch expands the table first
    touch(dst, all_keys[i : i + 50], device)

dst.replay_increment(delta)

Expected behavior
replay_increment either replays the delta or rejects it before writing anything -- the docstring promises ValueError "before this module writes anything" for layout mismatches. Here it fails midway and leaves a partial replay.

Environment details (please complete the following information):
Ubuntu 22.04, NVIDIA L40
Python 3.10, torch 2.10.0+cuda 12.8

Additional context
The fix is one line: re-run _plan_replay after the flush, so a changed layout rejects up front with the normal ValueError before anything is written. Verified: the repro rejects cleanly, the 26 existing replay tests pass, and a new regression test fails on the old code.

Easy to miss: with the same cache pressure on both sides, the source expands at its own dump first, so the target gets a clean rejection instead of a crash.

Can send a PR.

Thanks for the great work on this project!


By submitting this issue, you agree to follow our code of conduct and our contributing guidelines.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions