|
8 | 8 | import pytest |
9 | 9 | import pytest_asyncio |
10 | 10 |
|
| 11 | +from inference_engine.backends.mlx.prefill_worker import MLXPrefillComputeEngine |
11 | 12 | from inference_engine.distributed.capability import ( |
12 | 13 | CacheCompatibility, |
13 | 14 | CompressionCodec, |
@@ -55,6 +56,55 @@ def compute_prefill(self, token_ids, block_hashes, *, compression, cancelled): |
55 | 56 | ] |
56 | 57 |
|
57 | 58 |
|
| 59 | +def test_mlx_worker_exports_only_final_snapshot(monkeypatch): |
| 60 | + class Logit: |
| 61 | + def clone(self): |
| 62 | + return self |
| 63 | + |
| 64 | + class Verifier: |
| 65 | + def __init__(self): |
| 66 | + self.next_token_logits = None |
| 67 | + self.forwarded = [] |
| 68 | + |
| 69 | + def prefill(self, tokens): |
| 70 | + self.forwarded.extend(tokens) |
| 71 | + |
| 72 | + def forward_block(self, tokens): |
| 73 | + self.forwarded.extend(tokens) |
| 74 | + return [Logit() for _ in tokens] |
| 75 | + |
| 76 | + def commit_or_truncate(self, *, forwarded, accepted): |
| 77 | + assert forwarded == accepted |
| 78 | + |
| 79 | + verifier = Verifier() |
| 80 | + engine = MLXPrefillComputeEngine(verifier, COMPAT) |
| 81 | + snapshots = [] |
| 82 | + |
| 83 | + def snapshot(**kwargs): |
| 84 | + snapshots.append(kwargs) |
| 85 | + return CacheBlock.create( |
| 86 | + kwargs["block_hash"], |
| 87 | + kwargs["token_count"], |
| 88 | + b"final", |
| 89 | + ) |
| 90 | + |
| 91 | + monkeypatch.setattr(engine, "_snapshot", snapshot) |
| 92 | + hashes = [b"a" * 32, b"b" * 32, b"c" * 32] |
| 93 | + blocks = engine.compute_prefill( |
| 94 | + [1, 2, 3, 4, 5], |
| 95 | + hashes, |
| 96 | + compression=CompressionCodec.NONE, |
| 97 | + cancelled=threading.Event(), |
| 98 | + ) |
| 99 | + assert verifier.forwarded == [1, 2, 3, 4, 5] |
| 100 | + assert len(blocks) == 1 |
| 101 | + assert snapshots == [{ |
| 102 | + "token_count": 5, |
| 103 | + "block_hash": hashes[-1], |
| 104 | + "compression": CompressionCodec.NONE, |
| 105 | + }] |
| 106 | + |
| 107 | + |
58 | 108 | @pytest_asyncio.fixture |
59 | 109 | async def worker(): |
60 | 110 | engine = _Engine() |
@@ -282,6 +332,36 @@ def test_job_reservation_preserves_unrelated_restore_snapshot(): |
282 | 332 | jobs.close() |
283 | 333 |
|
284 | 334 |
|
| 335 | +def test_job_accepts_final_snapshot_only(): |
| 336 | + class FinalOnlyEngine: |
| 337 | + def compute_prefill(self, token_ids, block_hashes, **_kwargs): |
| 338 | + return [ |
| 339 | + CacheBlock.create( |
| 340 | + block_hashes[-1], |
| 341 | + len(token_ids), |
| 342 | + b"final-only", |
| 343 | + ), |
| 344 | + ] |
| 345 | + |
| 346 | + cache = PrefixCacheStore(COMPAT, max_bytes=1024, node_id="final-only") |
| 347 | + jobs = PrefillJobStore(FinalOnlyEngine(), cache) |
| 348 | + try: |
| 349 | + job = jobs.submit( |
| 350 | + request_id="final-only", |
| 351 | + tenant_id="tenant", |
| 352 | + token_ids=[1, 2, 3, 4], |
| 353 | + block_hashes=[b"a" * 32, b"b" * 32], |
| 354 | + compatibility=COMPAT, |
| 355 | + compression=CompressionCodec.NONE, |
| 356 | + ) |
| 357 | + job.future.result(timeout=1) |
| 358 | + assert job.state == PrefillJobState.COMPLETED |
| 359 | + assert cache.block_hashes() == (b"b" * 32,) |
| 360 | + assert cache.fetch(job.lease_id)[0].payload == b"final-only" |
| 361 | + finally: |
| 362 | + jobs.close() |
| 363 | + |
| 364 | + |
285 | 365 | def test_factory_engine_is_warmed_and_used_on_same_compute_thread(): |
286 | 366 | cache = PrefixCacheStore(COMPAT, max_bytes=1024, node_id="w") |
287 | 367 | created_on = [] |
|
0 commit comments