feat: checkpoint batching and release-gate stories

This commit is contained in:
Dobromir Popov
2026-07-16 17:24:36 +03:00
parent 737bade989
commit 02b3709311
18 changed files with 4580 additions and 1 deletions

View File

@@ -0,0 +1,611 @@
"""Bounded failure, cancellation, and restart semantics (DGR-013).
These tests drive the hardened per-session decode stream with the *same*
pure-numpy KV-cached dense-Llama reference the Hot KV State manager (DGR-007) and
the continuous-batch scheduler (DGR-012) use, imported from ``test_hot_kv_state``.
The whole matrix stays deterministic, download-free, GPU-free, and API-credit-free
while exercising the real KV isolation path (``KvBoundaryAdapter`` +
``HotKvStateManager``) rather than a mock.
Coverage maps to the story's acceptance criteria:
* deadlines and heartbeat/health loss terminate blocked stream operations,
* cancellation propagates across every Shard and releases KV + queued buffers,
* duplicate steps are idempotent; uncertain mutations are never replayed silently,
* alpha failover restarts from token zero rather than importing unverified KV,
* worker death / stream reset / malformed bundle / stale epoch / cache miss,
* billing/work records distinguish completed, cancelled, failed, and unverified.
"""
from __future__ import annotations
import json
import numpy as np
import pytest
from meshnet_node.batch_scheduler import (
ContinuousBatchScheduler,
DoneReason,
GenerationRequest,
KvBatchEngine,
NodeBudget,
)
from meshnet_node.boundary_adapter import BoundaryBundle, BoundaryContractError
from meshnet_node.hot_kv_state import (
CacheMiss,
CacheMissReason,
HotKvStateConfig,
HotKvStateManager,
KvBoundaryAdapter,
StaleRouteEpochError,
kv_recipe_for,
)
from meshnet_node.failure_semantics import (
CancellationToken,
DeadlineGuard,
FailureKind,
HardenedSessionRunner,
IdempotencyLedger,
OperationCancelled,
RestartController,
ShardCancellationGroup,
StepKey,
StreamTerminated,
UncertainMutationError,
WorkLedger,
WorkRecord,
WorkStatus,
classify_exception,
work_status_for,
)
# Reuse the certified numpy dense-Llama reference and shard from the DGR-007 gate.
from test_hot_kv_state import _KvDenseLlama, _KvReferenceShard
# --------------------------------------------------------------------------- #
# Helpers.
# --------------------------------------------------------------------------- #
class _FakeClock:
def __init__(self) -> None:
self.now = 0.0
def __call__(self) -> float:
return self.now
def advance(self, delta: float) -> None:
self.now += delta
class _FaultyShard(_KvReferenceShard):
"""A full-shard reference that raises on the Nth ``run_layers_cached`` call.
``run_layers_cached`` is invoked once per stream step, so ``fail_at_call=k``
simulates a worker dying at step ``k-1`` (calls are 1-indexed). The call
counter persists across attempts, so a restart on a fresh epoch keeps counting
and does not re-trip the same fault.
"""
def __init__(self, model, start, end, *, fail_at_call=None, error=None):
super().__init__(model, start, end)
self._fail_at_call = fail_at_call
self._error = error or RuntimeError("worker died mid-step")
self.calls = 0
def run_layers_cached(self, hidden, *, positions, past_kv):
self.calls += 1
if self._fail_at_call is not None and self.calls == self._fail_at_call:
raise self._error
return super().run_layers_cached(hidden, positions=positions, past_kv=past_kv)
def _make_adapter(model=None, *, config=None, shard=None):
"""A full-shard KV boundary adapter over the deterministic numpy dense-Llama."""
model = model or _KvDenseLlama()
shard = shard or _KvReferenceShard(model, 0, model.n_layers - 1)
manager = HotKvStateManager(kv_recipe_for(shard), config=config)
adapter = KvBoundaryAdapter(shard, manager)
return adapter
def _generation(session_id, prompt, n_new, epoch=0):
return GenerationRequest(
session_id=session_id,
route_epoch=epoch,
prompt_token_ids=tuple(prompt),
max_new_tokens=n_new,
)
# --------------------------------------------------------------------------- #
# Happy path (the baseline the failure paths deviate from).
# --------------------------------------------------------------------------- #
def test_clean_run_matches_stateless_reference_and_is_billable():
"A clean stream reproduces the stateless tokens and records completed work.\n\nTags: node, failure, billing"
model = _KvDenseLlama()
adapter = _make_adapter(model)
runner = HardenedSessionRunner(adapter)
prompt = [1, 2, 3, 4]
n_new = 8
outcome = runner.run(_generation("clean", prompt, n_new))
assert outcome.status is WorkStatus.COMPLETED
assert list(outcome.tokens) == model.stateless_greedy(prompt, n_new)
record = runner.work_ledger.records_for("clean")[0]
assert record.billable
assert record.tokens == n_new
assert runner.work_ledger.billable_tokens() == n_new
# --------------------------------------------------------------------------- #
# Deadlines and heartbeat/health loss terminate blocked operations.
# --------------------------------------------------------------------------- #
def test_deadline_terminates_a_blocked_stream_and_releases_kv():
"A deadline reached mid-stream terminates the run and frees its KV.\n\nTags: node, failure, deadline"
clock = _FakeClock()
adapter = _make_adapter()
manager = adapter.manager
runner = HardenedSessionRunner(adapter, clock=clock)
# Each step advances the clock by 1.0; the deadline fires at t=3.
def before_step(_step):
clock.advance(1.0)
outcome = runner.run(
_generation("slow", [5, 6, 7], 20),
deadline=3.0,
before_step=before_step,
)
assert outcome.status is WorkStatus.FAILED
assert outcome.failure_kind is FailureKind.DEADLINE_EXCEEDED
# The stream did not hang and did not finish: only the steps before the
# deadline committed, and the session's KV was released.
assert outcome.token_count < 20
assert isinstance(manager.resolve("slow", 0), CacheMiss)
def test_heartbeat_loss_terminates_a_blocked_stream():
"Losing the peer heartbeat past the timeout terminates the stream.\n\nTags: node, failure, heartbeat"
clock = _FakeClock()
adapter = _make_adapter()
runner = HardenedSessionRunner(adapter, clock=clock)
def before_step(_step):
clock.advance(1.0)
# Heartbeats stop arriving after step 2; with a timeout of 1.5 the gap grows
# past the bound and the stream is terminated (health loss).
def heartbeat(step):
return step < 2
outcome = runner.run(
_generation("hb", [9, 8, 7], 20),
heartbeat_timeout=1.5,
heartbeat=heartbeat,
before_step=before_step,
)
assert outcome.status is WorkStatus.FAILED
assert outcome.failure_kind is FailureKind.HEARTBEAT_LOST
assert outcome.token_count < 20
def test_deadline_guard_reports_remaining_and_resets_on_heartbeat():
"The guard exposes remaining time and a heartbeat resets the health timer.\n\nTags: node, failure, deadline"
clock = _FakeClock()
guard = DeadlineGuard(deadline=10.0, heartbeat_timeout=2.0, clock=clock)
guard.start()
guard.check()
assert guard.remaining() == 10.0
clock.advance(1.5)
guard.heartbeat() # health refreshed at t=1.5
clock.advance(1.0) # gap since heartbeat is 1.0 < 2.0
guard.check()
clock.advance(2.5) # gap since heartbeat is now 3.5 > 2.0
with pytest.raises(StreamTerminated) as exc:
guard.check()
assert exc.value.kind is FailureKind.HEARTBEAT_LOST
# --------------------------------------------------------------------------- #
# Cancellation propagates across shards and releases KV + queued buffers.
# --------------------------------------------------------------------------- #
def test_cancellation_token_terminates_stream_and_releases_kv():
"A client cancel mid-stream stops the run and releases the session KV.\n\nTags: node, failure, cancel"
adapter = _make_adapter()
manager = adapter.manager
token = CancellationToken()
runner = HardenedSessionRunner(adapter)
# Cancel after two steps have run.
def before_step(step):
if step == 2:
token.cancel("client-hangup")
outcome = runner.run(
_generation("cancelme", [1, 2, 3], 20),
cancel_token=token,
before_step=before_step,
)
assert outcome.status is WorkStatus.CANCELLED
assert outcome.failure_kind is FailureKind.CANCELLED
assert outcome.token_count == 2 # steps 0 and 1 committed before the cancel
assert isinstance(manager.resolve("cancelme", 0), CacheMiss)
def test_shard_cancellation_group_releases_every_shard_and_queued_buffers():
"One cancel frees KV on every node-local shard and releases queued buffers.\n\nTags: node, failure, cancel"
model = _KvDenseLlama()
# Three node-local shards of the same route, each with its own KV manager.
managers = []
for start, end in ((0, 1), (2, 3), (4, 5)):
shard = _KvReferenceShard(model, start, end)
mgr = HotKvStateManager(kv_recipe_for(shard))
mgr.open("route", 0) # each holds live state for the session
managers.append(mgr)
released_buffers = []
group = ShardCancellationGroup("route", 0)
for mgr in managers:
group.add_shard(mgr)
group.add_queued_buffer(lambda: released_buffers.append("bundle-a"))
group.add_queued_buffer(lambda: released_buffers.append("bundle-b"))
outcome = group.cancel()
assert outcome.shards_released == 3
assert outcome.buffers_released == 2
assert released_buffers == ["bundle-a", "bundle-b"]
# Every shard's KV is gone: a lookup now yields an explicit released miss.
for mgr in managers:
miss = mgr.resolve("route", 0)
assert isinstance(miss, CacheMiss)
assert miss.reason is CacheMissReason.RELEASED
# Cancellation is idempotent.
again = group.cancel()
assert again.shards_released == 0
assert again.buffers_released == 0
def test_scheduler_cancel_drains_queue_and_releases_active_kv():
"The scheduler cancel drops queued work and frees an active session's KV.\n\nTags: node, scheduler, cancel"
model = _KvDenseLlama()
shard = _KvReferenceShard(model, 0, model.n_layers - 1)
manager = HotKvStateManager(kv_recipe_for(shard))
engine = KvBatchEngine(KvBoundaryAdapter(shard, manager))
scheduler = ContinuousBatchScheduler(
engine, NodeBudget(max_active_sessions=1, max_batch_size=1, max_queue_depth=4)
)
assert scheduler.submit(_generation("active", [1, 2, 3], 8)).running
assert scheduler.submit(_generation("waiting", [4, 5, 6], 8)).reason.value == "queued"
scheduler.run_tick() # 'active' prefills and starts decoding, holding KV
# Cancel the queued one: it leaves the queue without ever taking a slot.
assert scheduler.cancel("waiting") is True
# Cancel the active one: its KV is released and it is recorded as cancelled.
assert scheduler.cancel("active") is True
assert manager.total_bytes == 0
telem = scheduler.telemetry()
assert telem.cancelled_sessions == 2
assert telem.completed_sessions == 0
assert telem.active_sessions == 0
assert telem.queue_depth == 0
# Cancelling an unknown / already-finished session is a no-op.
assert scheduler.cancel("active") is False
assert scheduler.cancel("never-seen") is False
def test_scheduler_cancel_rejects_a_completed_reason():
"cancel() refuses a non-terminal reason so completed work is never faked.\n\nTags: node, scheduler, cancel"
model = _KvDenseLlama()
shard = _KvReferenceShard(model, 0, model.n_layers - 1)
manager = HotKvStateManager(kv_recipe_for(shard))
engine = KvBatchEngine(KvBoundaryAdapter(shard, manager))
scheduler = ContinuousBatchScheduler(engine)
scheduler.submit(_generation("x", [1, 2], 4))
with pytest.raises(Exception):
scheduler.cancel("x", reason=DoneReason.COMPLETED)
# --------------------------------------------------------------------------- #
# Idempotency: duplicate steps are no-ops; uncertain mutations never replay.
# --------------------------------------------------------------------------- #
def test_duplicate_step_delivery_is_idempotent_no_remutation():
"Replaying a committed step returns the recorded token without re-mutating KV.\n\nTags: node, failure, idempotency"
ledger = IdempotencyLedger()
key = StepKey("s", 0, 5)
disposition = ledger.begin(key)
assert disposition.fresh
ledger.commit(key, 42)
# A duplicate delivery of the same step returns the recorded token and is a
# no-op — the caller must not re-run the mutation.
replay = ledger.begin(key)
assert replay.duplicate
assert replay.token == 42
def test_idempotent_run_replays_tokens_without_advancing_kv():
"Re-running a completed stream on the same ledger/epoch re-mutates nothing.\n\nTags: node, failure, idempotency"
model = _KvDenseLlama()
adapter = _make_adapter(model)
ledger = IdempotencyLedger()
runner = HardenedSessionRunner(adapter, idempotency=ledger)
request = _generation("idem", [3, 1, 4], 6)
first = runner.run(request)
assert first.status is WorkStatus.COMPLETED
kv_len_after_first = adapter.manager.get("idem", 0).seq_len
# A duplicate delivery of the entire stream: every step is a committed
# duplicate, so the runner replays the identical tokens and the KV length is
# unchanged (no double-append).
second = runner.run(request)
assert second.status is WorkStatus.COMPLETED
assert list(second.tokens) == list(first.tokens)
assert adapter.manager.get("idem", 0).seq_len == kv_len_after_first
def test_uncertain_mutation_is_never_replayed_silently():
"A step marked uncertain refuses a silent replay; it must be verified/restarted.\n\nTags: node, failure, idempotency"
ledger = IdempotencyLedger()
key = StepKey("s", 0, 3)
ledger.begin(key)
ledger.mark_uncertain(key, "worker died before ack")
# Replaying an uncertain mutation is refused rather than silently re-applied.
with pytest.raises(UncertainMutationError):
ledger.begin(key)
assert ledger.has_uncertain()
def test_in_flight_duplicate_is_treated_as_uncertain():
"A second begin before commit is refused (concurrent duplicate is unverified).\n\nTags: node, failure, idempotency"
ledger = IdempotencyLedger()
key = StepKey("s", 0, 1)
ledger.begin(key) # in-flight, not yet committed
with pytest.raises(UncertainMutationError):
ledger.begin(key)
# --------------------------------------------------------------------------- #
# Worker death, stream reset, malformed bundle, stale epoch, cache miss.
# --------------------------------------------------------------------------- #
def test_worker_death_midstream_is_unverified_and_marks_step_uncertain():
"A worker dying mid-step yields unverified work and an unreplayable step.\n\nTags: node, failure, worker-death"
model = _KvDenseLlama()
# Fail on the 3rd step call (step index 2), after two tokens committed.
shard = _FaultyShard(model, 0, model.n_layers - 1, fail_at_call=3)
adapter = _make_adapter(model, shard=shard)
ledger = IdempotencyLedger()
runner = HardenedSessionRunner(adapter, idempotency=ledger)
outcome = runner.run(_generation("dead", [1, 2, 3], 8))
assert outcome.status is WorkStatus.UNVERIFIED
assert outcome.failure_kind is FailureKind.WORKER_DEATH
assert outcome.token_count == 2 # the two committed steps
assert not outcome.completed
# The failed step is uncertain and can never be silently replayed.
assert ledger.has_uncertain()
with pytest.raises(UncertainMutationError):
ledger.begin(StepKey("dead", 0, 2))
# KV was released on failure.
assert isinstance(adapter.manager.resolve("dead", 0), CacheMiss)
def test_stream_reset_is_restartable_failure():
"A stream reset injected mid-stream fails the run as a restartable transport loss.\n\nTags: node, failure, stream-reset"
adapter = _make_adapter()
runner = HardenedSessionRunner(adapter)
def before_step(step):
if step == 2:
raise StreamTerminated(FailureKind.STREAM_RESET, "peer reset the stream")
outcome = runner.run(_generation("reset", [1, 2, 3], 8), before_step=before_step)
assert outcome.status is WorkStatus.FAILED
assert outcome.failure_kind is FailureKind.STREAM_RESET
assert outcome.restartable
def test_malformed_bundle_is_classified_and_does_not_corrupt_kv():
"A malformed activation bundle is rejected and leaves the KV context empty.\n\nTags: node, failure, malformed-bundle"
model = _KvDenseLlama()
mid = _KvReferenceShard(model, 2, 3) # middle range: not head, not tail
manager = HotKvStateManager(kv_recipe_for(mid))
adapter = KvBoundaryAdapter(mid, manager)
assert not adapter.is_head and not adapter.is_tail
# A bundle that hands over at the wrong layer is malformed.
bad = BoundaryBundle(
architecture_adapter=adapter.architecture.adapter,
schema_version=adapter.architecture.boundary_schema_version,
tensor_name=adapter.architecture.boundary_tensor_name,
residual=np.zeros((1, 3, model.hidden), dtype=np.float32),
positions=np.arange(3, dtype=np.int64)[None, :],
next_layer=adapter.start_layer + 5, # wrong handover layer
normalized=False,
)
with pytest.raises(BoundaryContractError) as exc:
adapter.prefill("mal", 0, boundary=bad)
assert classify_exception(exc.value) is FailureKind.MALFORMED_BUNDLE
# The malformed step never appended KV: the context is empty, not corrupted.
assert manager.get("mal", 0).seq_len == 0
def test_stale_epoch_reference_is_rejected_and_classified():
"A reference to a superseded epoch is rejected as stale, never silently reused.\n\nTags: node, failure, stale-epoch"
model = _KvDenseLlama()
adapter = _make_adapter(model)
manager = adapter.manager
manager.open("sess", 5) # current epoch is now 5
with pytest.raises(StaleRouteEpochError) as exc:
manager.resolve("sess", 4) # epoch 4 is stale
assert classify_exception(exc.value) is FailureKind.STALE_EPOCH
# Driving the hardened runner on the stale epoch fails closed as STALE_EPOCH.
runner = HardenedSessionRunner(adapter)
outcome = runner.run(_generation("sess", [1, 2, 3], 4, epoch=3))
assert outcome.status is WorkStatus.FAILED
assert outcome.failure_kind is FailureKind.STALE_EPOCH
def test_cache_miss_midstream_is_restartable():
"A KV eviction mid-stream surfaces an explicit cache miss the head can restart.\n\nTags: node, failure, cache-miss"
adapter = _make_adapter()
manager = adapter.manager
runner = HardenedSessionRunner(adapter)
# Evict the session's KV just before step 3's decode.
def before_step(step):
if step == 3:
manager.release("evict", 0)
outcome = runner.run(_generation("evict", [1, 2, 3], 10), before_step=before_step)
assert outcome.failure_kind is FailureKind.CACHE_MISS
assert outcome.restartable
assert outcome.token_count == 3 # steps 0..2 committed before the eviction
# --------------------------------------------------------------------------- #
# Alpha failover: restart from token zero, never import unverified KV.
# --------------------------------------------------------------------------- #
def test_alpha_failover_restarts_from_token_zero_and_completes():
"A transient worker death fails over to a fresh epoch and reproduces the tokens.\n\nTags: node, failure, failover"
model = _KvDenseLlama()
# Die on the 3rd step of the first attempt; the persistent call counter means
# the restart (which keeps counting) does not re-trip the fault.
shard = _FaultyShard(model, 0, model.n_layers - 1, fail_at_call=3)
adapter = _make_adapter(model, shard=shard)
manager = adapter.manager
runner = HardenedSessionRunner(adapter)
controller = RestartController([manager])
prompt = [7, 3, 9, 1]
n_new = 6
result = runner.run_with_failover(
_generation("alpha", prompt, n_new, epoch=0), controller, max_restarts=2
)
assert result.completed
assert result.restarts == 1
# The restart began on a fresh epoch and reproduced the full stateless stream
# from token zero — no half-computed KV was imported.
assert result.outcome.route_epoch == 1
assert list(result.outcome.tokens) == model.stateless_greedy(prompt, n_new)
# The failed epoch's KV is gone and the epoch is now stale.
with pytest.raises(StaleRouteEpochError):
manager.resolve("alpha", 0)
# First attempt was unverified, the restart completed: only the restart bills.
statuses = [a.status for a in result.attempts]
assert statuses == [WorkStatus.UNVERIFIED, WorkStatus.COMPLETED]
assert runner.work_ledger.billable_tokens() == n_new
def test_failover_refuses_to_import_unverified_kv():
"assert_fresh_start fails closed if any shard still holds new-epoch KV.\n\nTags: node, failure, failover"
model = _KvDenseLlama()
adapter = _make_adapter(model)
manager = adapter.manager
controller = RestartController([manager])
new_epoch = controller.failover("s", 0)
assert new_epoch == 1
# A clean fresh start passes.
controller.assert_fresh_start("s", new_epoch)
# If unverified KV were present under the new epoch, the guard refuses it.
manager.open("s", new_epoch)
manager.append(
"s",
new_epoch,
{i: (np.zeros((1, model.n_heads, model.head_dim), dtype=np.float32),
np.zeros((1, model.n_heads, model.head_dim), dtype=np.float32))
for i in range(model.n_layers)},
)
with pytest.raises(Exception):
controller.assert_fresh_start("s", new_epoch)
def test_non_restartable_failure_is_not_retried():
"A deterministic failure (deadline) returns immediately without a restart.\n\nTags: node, failure, failover"
clock = _FakeClock()
adapter = _make_adapter()
runner = HardenedSessionRunner(adapter, clock=clock)
controller = RestartController([adapter.manager])
def before_step(_step):
clock.advance(1.0)
result = runner.run_with_failover(
_generation("bounded", [1, 2, 3], 20),
controller,
max_restarts=3,
deadline=2.0,
before_step=before_step,
)
assert not result.completed
assert result.restarts == 0
assert result.outcome.failure_kind is FailureKind.DEADLINE_EXCEEDED
# --------------------------------------------------------------------------- #
# Billing / work records distinguish completed, cancelled, failed, unverified.
# --------------------------------------------------------------------------- #
def test_work_ledger_distinguishes_all_four_statuses():
"The work ledger keeps completed/cancelled/failed/unverified distinct.\n\nTags: node, failure, billing"
ledger = WorkLedger()
ledger.record(WorkRecord("a", 0, WorkStatus.COMPLETED, tokens=8))
ledger.record(WorkRecord("b", 0, WorkStatus.CANCELLED, tokens=3,
failure_kind=FailureKind.CANCELLED))
ledger.record(WorkRecord("c", 0, WorkStatus.FAILED, tokens=1,
failure_kind=FailureKind.DEADLINE_EXCEEDED))
ledger.record(WorkRecord("d", 0, WorkStatus.UNVERIFIED, tokens=2,
failure_kind=FailureKind.WORKER_DEATH))
counts = ledger.counts_by_status()
assert counts == {
"completed": 1, "cancelled": 1, "failed": 1, "unverified": 1,
}
# Only completed work is billable — cancelled/failed/unverified tokens are
# recorded for observability but never charged.
assert ledger.billable_tokens() == 8
assert [r.session_id for r in ledger.billable_records()] == ["a"]
# JSON-safe for durable evidence.
payload = ledger.to_dict()
assert payload["billable_tokens"] == 8
assert payload["counts_by_status"]["unverified"] == 1
json.dumps(payload)
def test_work_status_and_classification_mapping():
"Failure kinds map to the right billing status and exception classes.\n\nTags: node, failure, billing"
assert work_status_for(FailureKind.CANCELLED) is WorkStatus.CANCELLED
assert work_status_for(FailureKind.WORKER_DEATH) is WorkStatus.UNVERIFIED
# A stream reset detected at a step boundary is a certain failure (nothing
# committed for that step) — only an unexpected mid-step error is unverified.
assert work_status_for(FailureKind.STREAM_RESET) is WorkStatus.FAILED
assert work_status_for(FailureKind.DEADLINE_EXCEEDED) is WorkStatus.FAILED
assert work_status_for(FailureKind.MALFORMED_BUNDLE) is WorkStatus.FAILED
assert work_status_for(FailureKind.STALE_EPOCH) is WorkStatus.FAILED
assert work_status_for(FailureKind.CACHE_MISS) is WorkStatus.FAILED
assert classify_exception(OperationCancelled()) is FailureKind.CANCELLED
assert classify_exception(StaleRouteEpochError("x")) is FailureKind.STALE_EPOCH
assert classify_exception(BoundaryContractError("x")) is FailureKind.MALFORMED_BUNDLE
assert classify_exception(RuntimeError("boom")) is FailureKind.WORKER_DEATH
assert (
classify_exception(StreamTerminated(FailureKind.HEARTBEAT_LOST))
is FailureKind.HEARTBEAT_LOST
)