Skip to content

fix: stop get_cache handing out the stored KV cache by reference - #2303

Open
jeojdi1 wants to merge 2 commits into
MemTensor:mainfrom
jeojdi1:fix/kv-cache-mutated-in-place-by-generate
Open

fix: stop get_cache handing out the stored KV cache by reference#2303
jeojdi1 wants to merge 2 commits into
MemTensor:mainfrom
jeojdi1:fix/kv-cache-mutated-in-place-by-generate

Conversation

@jeojdi1

@jeojdi1 jeojdi1 commented Aug 28, 2026

Copy link
Copy Markdown

Description

_concat_caches returns caches[0] unchanged when a single cache id is requested, so get_cache() hands the caller the stored KVCacheItem.memory object itself. The caller passes that cache to generate(), which appends to it in place — so the saved activation memory grows on every chat turn.

Measured across three turns, the stored cache length went 6 → 19 → 32 → 45. Nothing in the API suggests that retrieving a memory mutates it, and the multi-cache path already builds a fresh container, so only this early return leaks the reference.

This PR returns a new DynamicCache sharing the same tensors. The tensors are deliberately not cloned: generate() appends along the sequence axis rather than writing into existing rows, so a fresh container is sufficient to protect the stored item without paying to duplicate the cache.

Related Issue (Required): #2301

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Unit Test

test_get_cache_does_not_alias_stored_memory asserts the returned object is not the stored one, then simulates generate() by appending a step to the returned cache and asserts the stored length is unchanged.

Verified fails-before / passes-after on transformers 5.16.1:

# upstream
FAILED tests/memories/activation/test_kv.py::test_get_cache_does_not_alias_stored_memory
1 failed

# with this change
1 passed

Checklist

A note on the target branch

CONTRIBUTING.md says to open PRs against dev, but no dev branch exists — only main and dev-v2.0.28dev-v2.0.32. This is against main (185ebdb, "Dev v2.0.32"). Happy to retarget.

`_concat_caches` returns `caches[0]` unchanged when a single cache id is
requested, so `get_cache` hands the caller the stored `KVCacheItem.memory`
object itself. The caller passes that cache to `generate`, which appends to it
in place, so the saved activation memory grows on every chat turn -- measured
stored length 6 -> 19 -> 32 -> 45 over three turns.

Nothing in the API suggests that retrieving a memory mutates it, and the
multi-cache path already builds a fresh container, so only this early return
leaked the reference.

Returns a new `DynamicCache` sharing the same tensors instead. The tensors are
not cloned: `generate` appends along the sequence axis rather than writing into
existing rows, so a fresh container is enough to protect the stored item without
paying to duplicate the cache.

Adds `test_get_cache_does_not_alias_stored_memory`, which simulates `generate`
by appending to the returned cache and asserts the stored length is unchanged.
It fails on the current code and passes with this change.
@Memtensor-AI Memtensor-AI added area:memory 记忆存储、检索、更新、召回逻辑 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 28, 2026
@Memtensor-AI

Memtensor-AI commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

🤖 Open Code Review

Target: PR #2303
Task: 110bb783284bc7e7
Base: main
Head: fix/kv-cache-mutated-in-place-by-generate

🔍 OpenCodeReview found 1 issue(s) in this PR.


1. src/memos/memories/activation/kv.py (L92)

copy shadows the standard-library module of the same name. While harmless here since copy.copy/copy.deepcopy are not used inside this function, it can confuse readers and static analysis. Prefer a name like new_cache or dst_cache.

💡 Suggested Change

Before:

        copy = DynamicCache()

After:

        new_cache = DynamicCache()

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: ENV ISSUE

The test environment encountered an issue that requires manual attention.

Details: Test collection failed because the torch module is not installed in the test environment, preventing the test file from being imported. [advisory, non-gating] AI-generated tests on branch test/auto-gen-c9892125fee30ed9-20260829013054: 36/36 passed — these do NOT affect the PR verdict; review the branch manually.
Branch: fix/kv-cache-mutated-in-place-by-generate

Review feedback: _copy_cache performs only Python-level attribute copies,
so the local torch import was never referenced.
@jeojdi1

jeojdi1 commented Sep 1, 2026

Copy link
Copy Markdown
Author

Thanks for the review — one accepted, one I'd push back on.

1. Unused import torch — correct, removed in a8b1bbf.

2. The conditional lazy_initialization call — this one needs to stay, and removing it would reintroduce the bug in #2313.

lazy_initialization does more than set keys / values. From transformers v5.16.1 cache_utils.py:121-125:

def lazy_initialization(self, key_states, value_states) -> None:
    self.dtype, self.device = key_states.dtype, key_states.device
    self.keys = torch.tensor([], dtype=self.dtype, device=self.device)
    self.values = torch.tensor([], dtype=self.dtype, device=self.device)
    self.is_initialized = True

The load-bearing line is the last one. DynamicLayer.get_seq_length() at :154-158 reads:

if not self.is_initialized or self.keys.numel() == 0:
    return 0

So a layer that gets .keys / .values assigned without is_initialized being set reports length 0 and has its contents discarded on the first forward pass. That is precisely the defect in #2313 / #2314. It also sets dtype and device, and DynamicSlidingWindowLayer overrides it to move _sliding_window_tensor onto the right device (:225-227).

On the second option — an else branch so the assignments only run when lazy_initialization did not — that would be worse: lazy_initialization sets keys / values to empty tensors, so the copy would come out empty. The call has to run first and the assignments have to overwrite it, which is what the current code does.

I've left the ordering as-is, but it clearly isn't obvious from reading it — happy to add a comment making the dependency explicit if you'd like.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: ENV ISSUE

The test environment encountered an issue that requires manual attention.

Details: Test collection failed because the torch module is not installed in the test environment, preventing the test file from being imported.
Branch: fix/kv-cache-mutated-in-place-by-generate

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

Labels

area:memory 记忆存储、检索、更新、召回逻辑 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants