Skip to content

Commit 4118820

Browse files
fluffy314fluffy314
authored andcommitted
Merge pull request #42 from FluffyAIcode/AgentMemory/v030-pr-a3-remove-adr-0007-deadcode-8e7f
2 parents 0ab8105 + 1d9732b commit 4118820

10 files changed

Lines changed: 57 additions & 1198 deletions

File tree

docs/adr/0008-session-bound-runtime-and-grpc-protocol.md

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -694,13 +694,40 @@ parallelize.
694694
`SessionStore` with create/append/close, in-memory only, no
695695
scheduler binding yet. INV-1, INV-2 enforced inside. Pure Python,
696696
no gRPC. 100% unit coverage.
697-
- **PR-A3**: Refactor the verifier (CPU + MLX) so its KV cache state
698-
is constructed and owned by `SessionStore` rather than by the
699-
scheduler / pool. Slab-pool integration (ADR 0003) becomes "slab
700-
per session" instead of "slab per scheduler slot". Internal-only
701-
refactor; the existing HTTP shim still works against the new
702-
internal shape. 100% unit coverage; no behavior change to the
703-
HTTP surface.
697+
- **PR-A3** *(scope split, recorded 2026-06-01 during implementation
698+
of PR-A3)*: This phase originally proposed two coupled changes —
699+
(a) remove the ADR 0007 `path_select` / `prefill_incremental`
700+
machinery from both verifiers, and (b) refactor slab ownership so
701+
the KV cache state is constructed and owned by `SessionStore`
702+
rather than by the scheduler / pool. (a) requires only Linux unit
703+
tests; (b) crosses into MLX-runtime hardware paths and pulls in
704+
scheduler / pool API redesign. Atomic merge of (a) is essential
705+
for coherence: the moment ADR 0007's `path_select` is removed
706+
from the verifier, every caller has to be removed too, or the
707+
speculative-decoding loop breaks. (b) does not have that
708+
coherence pressure — it can land independently after (a).
709+
Therefore PR-A3 is split:
710+
- **PR-A3** (this PR): pure removal of ADR 0007 dead code.
711+
Deletes `kv_cache_proposer/path_plan.py`,
712+
`tests/core/test_path_plan.py`,
713+
`tests/core/test_determinism_gate.py` (depends on
714+
`path_select`); strips `path_select` /
715+
`prefill_incremental` / `_cached_global_positions` /
716+
`_prompt_matches_cached_positions` from both verifiers; reverts
717+
`kv_cache_proposer/speculative.py`'s `generate()` dispatch to a
718+
single always-prefill path. Inert defaults are retained on
719+
`SpeculativeRunResult.path_selection` / `tokens_skipped` /
720+
`prefill_duration_seconds` so the server-side observability
721+
surface (which §6.6 rows server/* scope to PR-D1) keeps reading
722+
valid values without code change. 100% Linux unit coverage.
723+
- **PR-A3b** (next, queued after this PR): the slab-ownership
724+
refactor proper — verifier KV state is constructed and owned by
725+
`SessionStore`. Slab-pool integration becomes "slab per
726+
session" instead of "slab per scheduler slot". This is the PR
727+
where the verifier becomes a `CacheInspector` (PR-A2) for
728+
`SessionStore`. First PR with a mandatory Mac M4 integration
729+
test report (cf. §9), since it touches MLX runtime paths in a
730+
non-deletion way.
704731

705732
### 6.2 Phase B — gRPC server + Python SDK
706733

@@ -767,10 +794,10 @@ through `0a31ee9` PR 7-6):
767794
| ------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
768795
| `kv_cache_proposer/path_plan.py` | added | Deleted in PR-A3 (verifier session-store rewiring) — `PathPlan` is replaced by `SessionStore` lookup. |
769796
| `tests/core/test_path_plan.py` | added | Deleted alongside the implementation in PR-A3. |
770-
| `tests/core/test_determinism_gate.py` | added | Replaced by `tests/integration/test_inv3_session_determinism_gate.py` in PR-E1; old file deleted in PR-E1. |
797+
| `tests/core/test_determinism_gate.py` | added | Deleted in **PR-A3** (the test depends on `path_select`, which PR-A3 removes; cannot wait for PR-E1). Replaced by `tests/integration/test_inv3_session_determinism_gate.py` in PR-E1, which is created from scratch rather than refactored from the deleted file. |
771798
| `kv_cache_proposer/verifier.py` | modified | `path_select` / `prefill_incremental` removed in PR-A3; `cached_token_sequence` retained (still useful for INV-1 inside `SessionStore`). |
772799
| `inference_engine/backends/mlx/verifier.py` | modified | Same as above. |
773-
| `kv_cache_proposer/speculative.py` | modified | `path_select` dispatch removed in PR-B2 (`AppendTokens` handler subsumes the role). |
800+
| `kv_cache_proposer/speculative.py` | modified | `path_select` dispatch removed in **PR-A3** (atomic with the verifier-side removal — leaving the dispatch but removing `verifier.path_select` would break the speculative loop). `generate()` reverts to single always-prefill. PR-B2 is now scoped to "the gRPC `AppendTokens` handler subsumes the *role* that `generate`'s dispatch used to play, this time at the protocol layer not the speculative-decoder layer." |
774801
| `inference_engine/server/app.py` | modified | `_emit_path_selection_metric` and `_session_acceptance_rate` paths removed in PR-D1 (deprecated-shim refactor).|
775802
| `inference_engine/server/engine.py` | modified | `EngineResult` `path_selection` / `tokens_skipped` / `prefill_duration_seconds` fields removed in PR-D1. |
776803
| `inference_engine/server/metrics.py` | modified | `path_selection_total`, `continuation_tokens_skipped_total`, `verifier_prefill_duration_seconds`, `cache_invariant_violations_total` are removed in PR-D1; replaced by §2.9's `session_*` metrics in PR-B1/B3. |

inference_engine/backends/mlx/verifier.py

Lines changed: 0 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -199,70 +199,6 @@ def commit_or_truncate(self, forwarded: int, accepted: int) -> None:
199199
self._record_peak_kv()
200200
self._assert_cache_invariant_1()
201201

202-
def path_select(self, prompt: List[int]) -> "PathPlan":
203-
"""Select between continuation and new-session paths for ``prompt``.
204-
205-
Same contract as the CPU verifier; see
206-
:meth:`kv_cache_proposer.verifier.SinkWindowVerifier.path_select`
207-
for full semantics. Implements ADR 0007 §2.4 and asserts
208-
INV-2 (§2.9).
209-
"""
210-
from kv_cache_proposer.path_plan import ( # avoid circular
211-
ContinuationPlan,
212-
NewSession,
213-
)
214-
215-
if not prompt:
216-
raise ValueError("prompt must be non-empty")
217-
prompt_list = list(prompt)
218-
219-
if self.cache is None or self.cache_logical_size == 0:
220-
return NewSession(prompt=prompt_list)
221-
222-
cache_end = self.next_global_position
223-
if len(prompt_list) < cache_end:
224-
return NewSession(prompt=prompt_list)
225-
if not self._prompt_matches_cached_positions(prompt_list):
226-
return NewSession(prompt=prompt_list)
227-
228-
skip_n = cache_end
229-
new_tokens = prompt_list[skip_n:]
230-
231-
if skip_n != self.next_global_position:
232-
raise AssertionError(
233-
f"INV-2 violated (position monotonicity): planned "
234-
f"skip_n={skip_n} but next_global_position="
235-
f"{self.next_global_position}. Continuation must "
236-
f"extend exactly from the cache's logical end. This "
237-
f"is a bug in path_select; ADR 0007 §2.9 forbids "
238-
f"silent recovery. cache_logical_size="
239-
f"{self.cache_logical_size}, "
240-
f"cached_token_sequence_len="
241-
f"{len(self.cached_token_sequence)}."
242-
)
243-
244-
return ContinuationPlan(skip_n=skip_n, new_tokens=new_tokens)
245-
246-
def prefill_incremental(self, new_tokens: List[int]) -> None:
247-
"""Run incremental prefill on ``new_tokens``, reusing cached state.
248-
249-
Same contract as the CPU verifier; see
250-
:meth:`kv_cache_proposer.verifier.SinkWindowVerifier.prefill_incremental`.
251-
"""
252-
if self.cache is None:
253-
raise RuntimeError(
254-
"prefill_incremental called before any prefill; cache "
255-
"is None. Call path_select first and route NewSession "
256-
"to prefill() instead."
257-
)
258-
if not new_tokens:
259-
return
260-
block_logits = self.forward_block(list(new_tokens))
261-
self.commit_or_truncate(
262-
forwarded=len(new_tokens), accepted=len(new_tokens)
263-
)
264-
self.next_token_logits = block_logits[-1].clone()
265-
266202
def append_token(self, token_id: int) -> torch.Tensor:
267203
logits = self.forward_block([token_id])
268204
self.commit_or_truncate(forwarded=1, accepted=1)
@@ -277,42 +213,6 @@ def _cache_buffer_size(self) -> int:
277213
return 0
278214
return cache_ops.cache_seq_length(self.cache)
279215

280-
def _cached_global_positions(self) -> List[int]:
281-
"""Global token positions currently held in the cache.
282-
283-
See :meth:`kv_cache_proposer.verifier.SinkWindowVerifier._cached_global_positions`
284-
for semantics.
285-
"""
286-
n = self.next_global_position
287-
if n == 0:
288-
return []
289-
budget = self.config.sink_size + self.config.window_size
290-
if n <= budget:
291-
return list(range(n))
292-
sink_positions = list(range(self.config.sink_size))
293-
window_start = n - self.config.window_size
294-
window_positions = list(range(window_start, n))
295-
return sink_positions + window_positions
296-
297-
def _prompt_matches_cached_positions(self, prompt: List[int]) -> bool:
298-
"""Token-id-level check for ADR 0007 §2.4.a.2."""
299-
positions = self._cached_global_positions()
300-
if len(positions) != len(self.cached_token_sequence):
301-
raise AssertionError(
302-
f"_prompt_matches_cached_positions: position list of "
303-
f"length {len(positions)} disagrees with parallel "
304-
f"sequence of length {len(self.cached_token_sequence)}; "
305-
f"INV-1 should have caught this earlier"
306-
)
307-
for cache_idx, global_pos in enumerate(positions):
308-
if global_pos >= len(prompt):
309-
return False
310-
if int(prompt[global_pos]) != int(
311-
self.cached_token_sequence[cache_idx]
312-
):
313-
return False
314-
return True
315-
316216
def _sink_window_slice(self, sequence: List[int]) -> List[int]:
317217
"""Return ``sequence`` after the sink+window trim that the K/V
318218
cache applies.

kv_cache_proposer/path_plan.py

Lines changed: 0 additions & 92 deletions
This file was deleted.

kv_cache_proposer/speculative.py

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,13 @@ class SpeculativeRunResult:
7373
verifier_weight_bytes: int
7474
verifier_final_kv_token_count: int
7575
wall_time_seconds: float
76-
# ADR 0007 §2.10 path-selection observability. Populated by
77-
# SpeculativeDecoder.generate based on the path it dispatched to.
78-
path_selection: str = "new_session" # "continuation" | "new_session"
79-
tokens_skipped: int = 0 # ContinuationPlan.skip_n if continuation, 0 else
76+
# Historically ADR 0007 §2.10 path-selection observability fields.
77+
# After ADR 0008 PR-A3 the path-select dispatch was removed; these
78+
# are kept as inert defaults so server/engine.py and server/app.py
79+
# (which still surface them on /metrics) keep working unchanged
80+
# until ADR 0008 PR-D1 deletes them at the deprecated-shim refactor.
81+
path_selection: str = "new_session"
82+
tokens_skipped: int = 0
8083
prefill_duration_seconds: float = 0.0
8184

8285
@property
@@ -143,28 +146,18 @@ def _emit(tokens: List[int]) -> bool:
143146
self.verifier.stats.peak_kv_bytes = 0
144147
self.verifier.stats.peak_activation_bytes = 0
145148

146-
# ADR 0007 §2.4: dispatch on path-selection. ContinuationPlan
147-
# reuses cached prefix; NewSession runs full prefill (the
148-
# v0.3.0-rc1 behavior). Output is bit-identical between the
149-
# two paths for the same input (§2.7); the only difference
150-
# is the prefill cost. We record the decision + the prefill
151-
# wall time on the result so the route handler can populate
152-
# the §2.10 observability metrics.
153-
from .path_plan import ContinuationPlan, NewSession
154-
plan = self.verifier.path_select(prompt_ids)
149+
# ADR 0008 PR-A3 (supersedes ADR 0007 §2.4): the path_select /
150+
# ContinuationPlan / NewSession dispatch was removed when ADR
151+
# 0008 replaced ADR 0007's automatic-prefix-matching design with
152+
# an explicit session_id protocol. The verifier always does a
153+
# full prefill here; cross-request cache reuse is now the SDK's
154+
# responsibility (it calls Generate against an existing
155+
# session_id with only the new tokens). prefill_duration_seconds
156+
# is still measured for observability; path_selection is left
157+
# at its default of "new_session" since this entry point is
158+
# always the new-session path.
155159
prefill_t0 = time.perf_counter()
156-
if isinstance(plan, ContinuationPlan):
157-
self.verifier.prefill_incremental(plan.new_tokens)
158-
path_selection = "continuation"
159-
tokens_skipped = int(plan.skip_n)
160-
else:
161-
assert isinstance(plan, NewSession), (
162-
f"path_select must return ContinuationPlan or NewSession, "
163-
f"got {type(plan).__name__}"
164-
)
165-
self.verifier.prefill(plan.prompt)
166-
path_selection = "new_session"
167-
tokens_skipped = 0
160+
self.verifier.prefill(prompt_ids)
168161
prefill_duration_seconds = time.perf_counter() - prefill_t0
169162
committed: List[int] = list(prompt_ids)
170163
generated: List[int] = []
@@ -269,9 +262,10 @@ def _emit(tokens: List[int]) -> bool:
269262
verifier_weight_bytes=self.verifier.stats.weight_bytes,
270263
verifier_final_kv_token_count=self.verifier.cache_logical_size,
271264
wall_time_seconds=elapsed,
272-
path_selection=path_selection,
273-
tokens_skipped=tokens_skipped,
274265
prefill_duration_seconds=prefill_duration_seconds,
266+
# path_selection / tokens_skipped use SpeculativeRunResult's
267+
# dataclass defaults ("new_session" / 0). After ADR 0008
268+
# PR-A3 there is only one path through this entry point.
275269
)
276270

277271
@staticmethod

0 commit comments

Comments
 (0)