gemma-e2b-rlcd / tests /test_cache.py
larkooo's picture
Publish Gemma E2B RLCD with multimodal checkpoint and parallel scoring
53e24ca verified
Raw
History Blame Contribute Delete
3.89 kB
from types import SimpleNamespace
import pytest
from gemma_rlcd.cached_backend import CachedMLXBackend, PreparedState
from gemma_rlcd.core import ScoringRequest
mx = pytest.importorskip("mlx.core")
cache_module = pytest.importorskip("mlx_vlm.models.cache")
KVCache = cache_module.KVCache
RotatingKVCache = cache_module.RotatingKVCache
@pytest.mark.parametrize("kind", ["full", "rotating"])
def test_cache_forks_preserve_prefix_and_isolate_rows(kind):
backend = CachedMLXBackend.__new__(CachedMLXBackend)
backend.mx = mx
original = KVCache() if kind == "full" else RotatingKVCache(max_size=8)
keys = mx.arange(48).reshape(1, 1, 12, 4).astype(mx.float32)
original.update_and_fetch(keys, keys + 100)
before_keys, before_values = mx.array(original.keys), mx.array(original.values)
before_offset = original.offset
before_index = getattr(original, "_idx", None)
branch = backend.fork_cache([original], 3)[0]
updates = mx.array([1000, 2000, 3000], dtype=mx.float32)[:, None, None, None]
updates = mx.broadcast_to(updates, (3, 1, 2, 4))
output_keys, _ = branch.update_and_fetch(updates, updates + 50)
mx.eval(output_keys, original.keys, before_keys)
assert original.offset == before_offset
assert getattr(original, "_idx", None) == before_index
assert bool(mx.array_equal(original.keys, before_keys).item())
assert bool(mx.array_equal(original.values, before_values).item())
assert output_keys[:, 0, -1, 0].tolist() == [1000, 2000, 3000]
assert branch.offset == before_offset + 2
def test_unrecognized_cache_type_fails_explicitly():
backend = CachedMLXBackend.__new__(CachedMLXBackend)
backend.mx = mx
with pytest.raises(TypeError, match="Unsupported cache type"):
backend.fork_cache([object()], 2)
def test_batched_trim_selects_each_real_answer_before_padding():
backend = CachedMLXBackend.__new__(CachedMLXBackend)
backend.mx = mx
backend.branch_batch_size = 4
backend.last_stats = {}
backend.tokenizer = SimpleNamespace(pad_token_id=0)
backend._ids = {"A": 0, "B": 1}
backend.fork_cache = lambda prefix, size: []
calls = []
def decoder(*, inputs, cache, logits_to_keep):
calls.append((inputs.shape, logits_to_keep))
return inputs[:, -logits_to_keep:, None].astype(mx.float32)
backend.model = SimpleNamespace(
language_model=SimpleNamespace(
model=decoder,
logits_from_hidden=lambda hidden: mx.concatenate([hidden, -hidden], axis=-1),
)
)
prepared = PreparedState({}, [[2, 3, 7, 9], [1, 11], [5, 13, 17]], 100)
requests = [ScoringRequest("test", ("A", "B"))] * 3
scores = backend.branches(prepared, [], requests)
assert calls == [((3, 4), 3)]
assert [score.logits for score in scores] == [(9, -9), (11, -11), (17, -17)]
assert [score.input_tokens for score in scores] == [104, 102, 103]
assert backend.last_stats["branch_batch_sizes"] == [3]
def test_gathered_causal_mask_preserves_each_answer_position():
from gemma_rlcd.answer_positions import select_answer_mask
mask = select_answer_mask("causal", mx.array([3, 1]), 4, 10)
assert mask.shape == (2, 1, 1, 10)
assert mask.sum(axis=-1).tolist() == [[[10]], [[8]]]
@pytest.mark.parametrize("batched", [False, True])
def test_gathered_window_mask_preserves_existing_mask(batched):
from gemma_rlcd.answer_positions import select_answer_mask
mask = (mx.arange(10)[None, :] <= mx.arange(6, 10)[:, None]) & (
mx.arange(10)[None, :] >= mx.arange(4, 8)[:, None]
)
if batched:
mask = mask[None, None, :, :]
selected = select_answer_mask(mask, mx.array([3, 1]), 4, 10)
assert selected.shape == (2, 1, 1, 10)
assert selected[0, 0, 0].tolist() == [False] * 7 + [True] * 3
assert selected[1, 0, 0].tolist() == [False] * 5 + [True] * 3 + [False] * 2