[verified] feat: complete Ralph task workstreams
This commit is contained in:
63
tests/test_activation_compression.py
Normal file
63
tests/test_activation_compression.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Trace-driven activation-compression policy units."""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from meshnet_node.activation_compression import (
|
||||
CompressionPolicies,
|
||||
CompressionPolicy,
|
||||
compress_activation,
|
||||
decompress_activation,
|
||||
)
|
||||
|
||||
|
||||
def test_compressible_body_uses_zstd_when_it_clears_savings_policy():
|
||||
body = b"activation" * 20_000
|
||||
result = compress_activation(body, CompressionPolicy(min_input_bytes=1, min_savings_bytes=100))
|
||||
assert result.encoding == "zstd"
|
||||
assert result.output_bytes < result.input_bytes
|
||||
assert decompress_activation(result.body, result.encoding).body == body
|
||||
|
||||
|
||||
def test_incompressible_body_stays_raw_after_measured_trial():
|
||||
body = os.urandom(32 * 1024)
|
||||
result = compress_activation(body, CompressionPolicy(min_input_bytes=1, min_savings_bytes=1))
|
||||
assert result.encoding is None
|
||||
assert result.body == body
|
||||
assert result.decision == "below_savings"
|
||||
|
||||
|
||||
def test_small_body_uses_raw_fast_path_without_zstd_trial():
|
||||
body = b"x" * 1024
|
||||
result = compress_activation(body, CompressionPolicy(min_input_bytes=2048))
|
||||
assert result.encoding is None
|
||||
assert result.decision == "below_min_input"
|
||||
assert result.elapsed_seconds >= 0
|
||||
|
||||
|
||||
def test_threshold_requires_both_byte_and_ratio_savings():
|
||||
body = b"a" * 4096
|
||||
result = compress_activation(
|
||||
body, CompressionPolicy(min_input_bytes=1, min_savings_bytes=len(body), min_savings_ratio=0),
|
||||
)
|
||||
assert result.encoding is None
|
||||
assert result.decision == "below_savings"
|
||||
|
||||
|
||||
def test_malformed_zstd_and_legacy_raw_bodies_are_handled_explicitly():
|
||||
assert decompress_activation(b"legacy", None).body == b"legacy"
|
||||
with pytest.raises(ValueError, match="invalid zstd activation body"):
|
||||
decompress_activation(b"not a zstd frame", "zstd")
|
||||
with pytest.raises(ValueError, match="unsupported"):
|
||||
decompress_activation(b"body", "gzip")
|
||||
|
||||
|
||||
def test_prefill_decode_and_route_conditions_have_independent_config(monkeypatch):
|
||||
policies = CompressionPolicies()
|
||||
assert policies.for_condition("relay", "prefill").min_input_bytes < policies.for_condition("relay", "decode").min_input_bytes
|
||||
monkeypatch.setenv("MESHNET_COMPRESSION_RELAY_DECODE_MIN_INPUT_BYTES", "123")
|
||||
monkeypatch.setenv("MESHNET_COMPRESSION_LAN_PREFILL_ENABLED", "false")
|
||||
assert policies.for_condition("relay", "decode").min_input_bytes == 123
|
||||
assert not policies.for_condition("lan", "prefill").enabled
|
||||
assert policies.for_condition("benchmark", "prefill").enabled
|
||||
@@ -15,6 +15,7 @@ from meshnet_tracker.routing_stats import (
|
||||
route_signature,
|
||||
route_table,
|
||||
)
|
||||
from meshnet_tracker.capability import absent_state
|
||||
from meshnet_tracker.server import TrackerServer, _enumerate_routes
|
||||
|
||||
|
||||
@@ -49,6 +50,9 @@ def _fake_node(node_id, shard_start, shard_end, benchmark=100.0, endpoint=None):
|
||||
proxy_inflight=0,
|
||||
wallet_address=None,
|
||||
relay_addr=None,
|
||||
# A pre-capability node (NCA-004): routable only under the `compat`
|
||||
# policy, which is what these route-enumeration tests exercise.
|
||||
capability=absent_state(),
|
||||
)
|
||||
|
||||
|
||||
@@ -270,6 +274,7 @@ def test_proxy_head_is_route_head_and_routing_endpoint_lists_routes():
|
||||
"shard_start": 0,
|
||||
"shard_end": shard_end,
|
||||
"tracker_mode": True,
|
||||
"quantization": "bfloat16",
|
||||
"benchmark_tokens_per_sec": bench,
|
||||
"hardware_profile": {},
|
||||
"score": 1.0},
|
||||
|
||||
@@ -454,6 +454,137 @@ def test_relay_rpc_reuses_connection_for_sequential_requests(monkeypatch):
|
||||
assert connection_attempts == 1
|
||||
|
||||
|
||||
def test_relay_hop_client_preserves_binary_json_and_closes_uncertain_legacy_socket(monkeypatch):
|
||||
"DIP-002: persistent clients correlate both frame formats and never replay a lost response.\n\nTags: gossip, network, relay"
|
||||
|
||||
import websockets.sync.client as wsc # type: ignore[import]
|
||||
from meshnet_node.relay_bridge import decode_binary_frame, encode_binary_frame
|
||||
from meshnet_node.torch_server import _RelayHopClient, _RelayRequestUncertainError
|
||||
|
||||
class FakeSocket:
|
||||
def __init__(self, responses):
|
||||
self.responses = iter(responses)
|
||||
self.sent = []
|
||||
self.closed = False
|
||||
|
||||
def send(self, frame):
|
||||
self.sent.append(frame)
|
||||
|
||||
def recv(self, timeout):
|
||||
response = next(self.responses)
|
||||
if isinstance(response, BaseException):
|
||||
raise response
|
||||
request, _ = decode_binary_frame(self.sent[-1])
|
||||
if isinstance(response, bytes):
|
||||
return encode_binary_frame({
|
||||
"request_id": request["request_id"], "status": 200, "headers": {},
|
||||
}, response)
|
||||
return json.dumps({"request_id": request["request_id"], "status": 200,
|
||||
"headers": {}, "body": response})
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
healthy = FakeSocket([b"binary", "json"])
|
||||
legacy = FakeSocket([b"first", TimeoutError("legacy relay closed")])
|
||||
session_a = FakeSocket([b"a"])
|
||||
session_b = FakeSocket([b"b"])
|
||||
sockets = iter([healthy, legacy, session_a, session_b])
|
||||
monkeypatch.setattr(wsc, "connect", lambda *args, **kwargs: next(sockets))
|
||||
|
||||
client = _RelayHopClient("ws://relay/rpc/peer", timeout=0.01)
|
||||
assert client.request("/forward", b"a", {})[2] == b"binary"
|
||||
assert client.request("/forward", b"b", {})[2] == b"json"
|
||||
first_id = decode_binary_frame(healthy.sent[0])[0]["request_id"]
|
||||
second_id = decode_binary_frame(healthy.sent[1])[0]["request_id"]
|
||||
assert first_id != second_id
|
||||
client.close()
|
||||
|
||||
client = _RelayHopClient("ws://relay/rpc/peer", timeout=0.01)
|
||||
assert client.request("/forward", b"first", {})[2] == b"first"
|
||||
try:
|
||||
client.request("/forward", b"second", {})
|
||||
except _RelayRequestUncertainError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("a post-send disconnect must not be retried")
|
||||
assert legacy.closed is True
|
||||
assert client._ws is None
|
||||
|
||||
# Route Sessions own their own requester socket; serialising one client's
|
||||
# calls must not accidentally make another session share it.
|
||||
first = _RelayHopClient("ws://relay/rpc/peer", timeout=0.01)
|
||||
second = _RelayHopClient("ws://relay/rpc/peer", timeout=0.01)
|
||||
assert first.request("/forward", b"a", {})[2] == b"a"
|
||||
assert second.request("/forward", b"b", {})[2] == b"b"
|
||||
assert first._ws is session_a
|
||||
assert second._ws is session_b
|
||||
|
||||
|
||||
def test_relay_rpc_cleans_pending_on_timeout_disconnect_and_cancellation():
|
||||
"DIP-002: every terminal relay RPC path releases its pending response queue.\n\nTags: gossip, network, relay"
|
||||
|
||||
import asyncio
|
||||
|
||||
from meshnet_relay.server import RelayServer
|
||||
|
||||
class Requester:
|
||||
def __init__(self):
|
||||
self.messages = [json.dumps({
|
||||
"request_id": "legacy-id", "method": "POST", "path": "/forward",
|
||||
"headers": {}, "body": "{}",
|
||||
})]
|
||||
self.sent = []
|
||||
|
||||
async def recv(self):
|
||||
if self.messages:
|
||||
return self.messages.pop(0)
|
||||
raise EOFError
|
||||
|
||||
async def send(self, message):
|
||||
self.sent.append(json.loads(message))
|
||||
|
||||
async def close(self, *args):
|
||||
pass
|
||||
|
||||
class Target:
|
||||
async def send(self, message):
|
||||
pass
|
||||
|
||||
async def wait_for_pending(relay):
|
||||
for _ in range(100):
|
||||
if relay._pending_rpc:
|
||||
return
|
||||
await asyncio.sleep(0)
|
||||
raise AssertionError("relay RPC did not register pending state")
|
||||
|
||||
async def exercise(kind):
|
||||
relay = RelayServer(rpc_timeout=0.02, rpc_idle_timeout=0.01)
|
||||
relay.registry.register("peer", "", Target())
|
||||
requester = Requester()
|
||||
task = asyncio.create_task(relay._handle_rpc(requester, "peer"))
|
||||
await wait_for_pending(relay)
|
||||
if kind == "disconnect":
|
||||
relay._fail_pending_for_peer("peer")
|
||||
elif kind == "cancel":
|
||||
task.cancel()
|
||||
if kind == "cancel":
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
else:
|
||||
await task
|
||||
assert relay._pending_rpc == {}
|
||||
if kind == "timeout":
|
||||
assert requester.sent[-1]["status"] == 504
|
||||
if kind == "disconnect":
|
||||
assert requester.sent[-1]["status"] == 503
|
||||
|
||||
for kind in ("timeout", "disconnect", "cancel"):
|
||||
asyncio.run(exercise(kind))
|
||||
|
||||
|
||||
def test_binary_relay_frame_codecs_interoperate():
|
||||
"Node and relay ship the same binary frame format as separate copies.\n\nTags: gossip, network, relay"
|
||||
|
||||
@@ -478,6 +609,24 @@ def test_binary_relay_frame_codecs_interoperate():
|
||||
raise AssertionError("garbage bytes must not decode as a binary frame")
|
||||
|
||||
|
||||
def test_binary_relay_frame_layout_remains_byte_for_byte_compatible():
|
||||
"""Framing optimizations preserve the MRF1 header-length-body contract.
|
||||
|
||||
Tags: gossip, network, relay, wire
|
||||
"""
|
||||
import json
|
||||
|
||||
from meshnet_node.relay_bridge import BINARY_FRAME_MAGIC, encode_binary_frame
|
||||
|
||||
header = {"request_id": "r-1", "headers": {"X-Meshnet-Shape": "1,1,2"}}
|
||||
body = b"\x01\x02\x03\x04"
|
||||
header_bytes = json.dumps(header, separators=(",", ":")).encode()
|
||||
|
||||
assert encode_binary_frame(header, body) == (
|
||||
BINARY_FRAME_MAGIC + len(header_bytes).to_bytes(4, "big") + header_bytes + body
|
||||
)
|
||||
|
||||
|
||||
def test_activation_compression_round_trips_and_skips_small_bodies():
|
||||
"Pipeline hops zstd-compress large activations; tiny decode bodies pass raw.\n\nTags: gossip, network, relay"
|
||||
|
||||
|
||||
134
tests/test_http_keepalive.py
Normal file
134
tests/test_http_keepalive.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""DIP-003 keep-alive ownership and framing-adjacent transport tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class _Response:
|
||||
def __init__(self, status: int = 200, body: bytes = b"ok", headers: dict | None = None):
|
||||
self.status = status
|
||||
self._body = body
|
||||
self.headers = headers or {"Content-Type": "application/octet-stream", "Content-Length": str(len(body))}
|
||||
|
||||
def read(self) -> bytes:
|
||||
return self._body
|
||||
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class _Connection:
|
||||
instances: list["_Connection"] = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.requests: list[tuple] = []
|
||||
self.responses: list[object] = [_Response(), _Response()]
|
||||
self.closed = False
|
||||
self.__class__.instances.append(self)
|
||||
|
||||
def request(self, *args, **kwargs) -> None:
|
||||
self.requests.append((args, kwargs))
|
||||
|
||||
def getresponse(self):
|
||||
response = self.responses.pop(0)
|
||||
if isinstance(response, BaseException):
|
||||
raise response
|
||||
return response
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
def test_direct_hop_client_reuses_one_connection_and_discards_uncertain_socket(monkeypatch):
|
||||
"""A Route Session owns one serialized direct socket and never replays it.
|
||||
|
||||
Tags: performance, routing
|
||||
"""
|
||||
from meshnet_node import torch_server
|
||||
|
||||
_Connection.instances = []
|
||||
monkeypatch.setattr(torch_server.http.client, "HTTPConnection", _Connection)
|
||||
client = torch_server._DirectHopClient("http://tail.example:8001")
|
||||
|
||||
assert client.request("/forward", b"one", {})[2] == b"ok"
|
||||
assert client.request("/forward", b"two", {})[2] == b"ok"
|
||||
assert len(_Connection.instances) == 1
|
||||
assert [call[0][1] for call in _Connection.instances[0].requests] == ["/forward", "/forward"]
|
||||
|
||||
_Connection.instances[0].responses.append(ConnectionResetError("stale peer"))
|
||||
with pytest.raises(torch_server._DirectRequestUncertainError):
|
||||
client.request("/forward", b"three", {})
|
||||
assert _Connection.instances[0].closed is True
|
||||
assert client._connection is None
|
||||
|
||||
|
||||
def test_bridge_pool_reuses_a_worker_connection_and_invalidates_stale_one(monkeypatch):
|
||||
"""A bridge worker keeps its own loopback client; broken clients are dropped.
|
||||
|
||||
Tags: performance, relay
|
||||
"""
|
||||
from meshnet_node import relay_bridge
|
||||
|
||||
_Connection.instances = []
|
||||
monkeypatch.setattr(relay_bridge.http.client, "HTTPConnection", _Connection)
|
||||
bridge = relay_bridge.RelayHttpBridge(
|
||||
relay_url="ws://relay.example/ws",
|
||||
peer_id="peer",
|
||||
local_base_url="http://127.0.0.1:8001",
|
||||
advertised_addr="",
|
||||
)
|
||||
frames: list[dict] = []
|
||||
bridge._send_response_frame = lambda frame: (frames.append(frame), True)[1]
|
||||
|
||||
request = {"request_id": "one", "method": "POST", "path": "/forward", "headers": {}, "body": ""}
|
||||
bridge._process_request(request)
|
||||
bridge._process_request({**request, "request_id": "two"})
|
||||
assert len(_Connection.instances) == 1
|
||||
assert len(_Connection.instances[0].requests) == 2
|
||||
|
||||
_Connection.instances[0].responses.append(ConnectionResetError("stale loopback"))
|
||||
bridge._process_request({**request, "request_id": "broken"})
|
||||
assert _Connection.instances[0].closed is True
|
||||
bridge._process_request({**request, "request_id": "replacement"})
|
||||
assert len(_Connection.instances) == 2
|
||||
bridge.stop()
|
||||
|
||||
|
||||
def test_node_sse_uses_chunked_framing_and_tolerates_client_cancellation():
|
||||
"""HTTP/1.1 streams terminate without EOF and ignore a cancelled client.
|
||||
|
||||
Tags: performance, streaming
|
||||
"""
|
||||
from meshnet_node.torch_server import _TorchHandler
|
||||
|
||||
handler = object.__new__(_TorchHandler)
|
||||
headers: list[tuple[str, str]] = []
|
||||
handler.send_response = lambda status: None
|
||||
handler.send_header = lambda name, value: headers.append((name, value))
|
||||
handler.end_headers = lambda: None
|
||||
handler.wfile = io.BytesIO()
|
||||
emit = handler._start_openai_stream("model")
|
||||
emit(None)
|
||||
wire = handler.wfile.getvalue()
|
||||
|
||||
assert _TorchHandler.protocol_version == "HTTP/1.1"
|
||||
assert ("Transfer-Encoding", "chunked") in headers
|
||||
assert wire.endswith(b"0\r\n\r\n")
|
||||
assert b"data: [DONE]" in wire
|
||||
|
||||
class _CancelledWriter:
|
||||
def write(self, _body):
|
||||
raise BrokenPipeError
|
||||
|
||||
def flush(self):
|
||||
raise BrokenPipeError
|
||||
|
||||
cancelled = object.__new__(_TorchHandler)
|
||||
cancelled.send_response = lambda status: None
|
||||
cancelled.send_header = lambda name, value: None
|
||||
cancelled.end_headers = lambda: None
|
||||
cancelled.wfile = _CancelledWriter()
|
||||
cancelled._start_openai_stream("model")(None)
|
||||
@@ -9,6 +9,10 @@ import types
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# A fake node server has no real backend to prove capability with; say so
|
||||
# explicitly rather than bypassing startup's fail-closed admission.
|
||||
from meshnet_node.testing import assume_capability
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# model_catalog tests
|
||||
@@ -336,6 +340,7 @@ def test_startup_auto_detects_shard_range(monkeypatch, tmp_path):
|
||||
# shard_start and shard_end intentionally omitted
|
||||
quantization="bfloat16",
|
||||
host="127.0.0.1",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
assert calls == ["Qwen/Qwen2.5-0.5B-Instruct"]
|
||||
assert isinstance(node, FakeNode)
|
||||
|
||||
396
tests/test_node_admission.py
Normal file
396
tests/test_node_admission.py
Normal file
@@ -0,0 +1,396 @@
|
||||
"""NCA-003: startup fails closed — no registration without a fresh matching proof.
|
||||
|
||||
Two layers are covered here:
|
||||
|
||||
* the gate itself (`meshnet_node.admission.admit`) — which reports admit a
|
||||
selection, and which are refused as failed, stale, or about something else;
|
||||
* `run_startup` — that a refused report means the tracker is never called, and
|
||||
that an admitted one travels with the registration payload.
|
||||
|
||||
Torch is a stub in the dev venv, so the backend is faked by duck-typing
|
||||
`TorchModelShard` (see `_FakeBackend`); the *production* validator still runs a
|
||||
real `doctor` forward against it, so the fail-closed path is exercised without a
|
||||
bypass. Tests that cannot supply an executable backend pass the explicit
|
||||
test-safe validator from `meshnet_node.testing`.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import struct
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from meshnet_node.admission import (
|
||||
REASON_BACKEND_MISMATCH,
|
||||
REASON_MODEL_MISMATCH,
|
||||
REASON_NO_REPORT,
|
||||
REASON_NOT_PASSED,
|
||||
REASON_RECIPE_MISMATCH,
|
||||
REASON_SHARD_MISMATCH,
|
||||
REASON_STALE,
|
||||
AdmissionRequirement,
|
||||
CapabilityAdmissionError,
|
||||
CapabilityContext,
|
||||
admit,
|
||||
probe_capability,
|
||||
)
|
||||
from meshnet_node.capability import STATUS_FAILED, STATUS_SKIPPED
|
||||
from meshnet_node.doctor import DoctorSelection
|
||||
from meshnet_node.recipe_manifest import DEFAULT_RECIPE_ID, load_recipe_manifest
|
||||
from meshnet_node.startup import run_startup
|
||||
from meshnet_node.testing import capability_report_for, capability_stub
|
||||
|
||||
MODEL = "acme/opaque-model-7b"
|
||||
|
||||
|
||||
class _FakeDevice:
|
||||
def __init__(self, type_: str = "cpu"):
|
||||
self.type = type_
|
||||
|
||||
|
||||
class _FakeOutput:
|
||||
def __init__(self, hidden_size: int, tokens: int = 4):
|
||||
self.body = b"\x00" * (tokens * hidden_size * 2)
|
||||
self.shape = [1, tokens, hidden_size]
|
||||
self.attention_mask_header = _int64_header([[1] * tokens])
|
||||
self.position_ids_header = _int64_header([list(range(tokens))])
|
||||
|
||||
|
||||
def _int64_header(rows):
|
||||
flat = [int(v) for row in rows for v in row]
|
||||
raw = struct.pack(f"<{len(flat)}q", *flat)
|
||||
return f"{len(rows)},{len(rows[0])}:{base64.b64encode(raw).decode('ascii')}"
|
||||
|
||||
|
||||
class _FakeBackend:
|
||||
"""Duck-types the parts of `TorchModelShard` the doctor probe touches."""
|
||||
|
||||
total_layers = 24
|
||||
hidden_size = 8
|
||||
|
||||
def __init__(self, *, shard_start=0, shard_end=23, device="cpu", forward_error=None):
|
||||
self.shard_start = shard_start
|
||||
self.shard_end = shard_end
|
||||
self.is_head = shard_start == 0
|
||||
self.is_tail = shard_end == self.total_layers - 1
|
||||
self.device = _FakeDevice(device)
|
||||
self.model_id = MODEL
|
||||
self._forward_error = forward_error
|
||||
|
||||
def encode_prompt(self, _prompt):
|
||||
if self._forward_error:
|
||||
raise self._forward_error
|
||||
return _FakeOutput(self.hidden_size)
|
||||
|
||||
def forward_bytes(self, body, shape, attention_mask_header, position_ids_header, start_layer=None):
|
||||
if self._forward_error:
|
||||
raise self._forward_error
|
||||
return _FakeOutput(self.hidden_size)
|
||||
|
||||
|
||||
def _context(backend=None, *, model_id=MODEL, shard_start=0, shard_end=23, device="cpu"):
|
||||
manifest = load_recipe_manifest()
|
||||
return CapabilityContext(
|
||||
backend=backend,
|
||||
selection=DoctorSelection(
|
||||
model_id=model_id,
|
||||
shard_start=shard_start,
|
||||
shard_end=shard_end,
|
||||
quantization="bfloat16",
|
||||
),
|
||||
recipe=manifest.require(DEFAULT_RECIPE_ID),
|
||||
manifest=manifest,
|
||||
device=device,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The gate: which reports admit a selection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_a_fresh_matching_passing_report_admits_the_selection():
|
||||
"The proof covers exactly what is about to be advertised, so the node may register.\n\nTags: node, admission"
|
||||
ctx = _context()
|
||||
report = capability_report_for(ctx)
|
||||
|
||||
assert admit(AdmissionRequirement.for_context(ctx), report) is report
|
||||
|
||||
|
||||
def test_a_missing_report_is_refused():
|
||||
"No proof at all is the default state, and it must not register.\n\nTags: node, admission"
|
||||
ctx = _context()
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), None)
|
||||
|
||||
assert exc.value.reason == REASON_NO_REPORT
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", [STATUS_FAILED, STATUS_SKIPPED])
|
||||
def test_a_report_that_did_not_pass_is_refused(status):
|
||||
"A failed or skipped validation is evidence against admission, not for it.\n\nTags: node, admission"
|
||||
ctx = _context()
|
||||
report = capability_report_for(
|
||||
ctx, status=status, diagnostics=["the shard forward returned no output"]
|
||||
)
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), report)
|
||||
|
||||
assert exc.value.reason == REASON_NOT_PASSED
|
||||
assert "the shard forward returned no output" in str(exc.value)
|
||||
|
||||
|
||||
def test_a_passing_report_for_another_model_is_refused():
|
||||
"A proof about one model says nothing about another — no reuse across models.\n\nTags: node, admission"
|
||||
ctx = _context()
|
||||
report = capability_report_for(ctx, model_id="other/model-1b")
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), report)
|
||||
|
||||
assert exc.value.reason == REASON_MODEL_MISMATCH
|
||||
assert "other/model-1b" in str(exc.value)
|
||||
|
||||
|
||||
def test_a_passing_report_for_another_shard_range_is_refused():
|
||||
"Layers 0–11 running is no proof that layers 12–23 fit.\n\nTags: node, admission"
|
||||
ctx = _context(shard_start=12, shard_end=23)
|
||||
report = capability_report_for(ctx, shard_start=0, shard_end=11)
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), report)
|
||||
|
||||
assert exc.value.reason == REASON_SHARD_MISMATCH
|
||||
|
||||
|
||||
def test_a_passing_report_for_another_recipe_version_is_refused():
|
||||
"A recipe's execution params changed, so its old proof no longer applies.\n\nTags: node, admission"
|
||||
ctx = _context()
|
||||
report = capability_report_for(ctx, recipe_version="0")
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), report)
|
||||
|
||||
assert exc.value.reason == REASON_RECIPE_MISMATCH
|
||||
|
||||
|
||||
def test_a_passing_report_from_another_backend_or_device_is_refused():
|
||||
"A CUDA proof does not admit a node that would serve the shard on CPU.\n\nTags: node, admission"
|
||||
ctx = _context(device="cpu")
|
||||
report = capability_report_for(ctx, device="cuda")
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), report)
|
||||
|
||||
assert exc.value.reason == REASON_BACKEND_MISMATCH
|
||||
|
||||
other_backend = capability_report_for(ctx, backend_id="some-other-runtime")
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), other_backend)
|
||||
assert exc.value.reason == REASON_BACKEND_MISMATCH
|
||||
|
||||
|
||||
def test_a_report_older_than_the_freshness_window_is_refused():
|
||||
"Hardware, drivers and weights move; an old proof is not a current one.\n\nTags: node, admission"
|
||||
ctx = _context()
|
||||
requirement = AdmissionRequirement.for_context(ctx, max_age_seconds=900)
|
||||
report = capability_report_for(ctx, age_seconds=901)
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(requirement, report)
|
||||
|
||||
assert exc.value.reason == REASON_STALE
|
||||
|
||||
still_fresh = capability_report_for(ctx, age_seconds=899)
|
||||
assert admit(requirement, still_fresh) is still_fresh
|
||||
|
||||
|
||||
def test_a_future_dated_report_is_refused():
|
||||
"A report from the future is a broken clock, not a fresh proof.\n\nTags: node, admission"
|
||||
ctx = _context()
|
||||
report = capability_report_for(ctx, validated_at=time.time() + 3600)
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), report)
|
||||
|
||||
assert exc.value.reason == REASON_STALE
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The production validator: a real forward through the loaded backend
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_the_production_validator_proves_a_working_backend_with_a_real_forward():
|
||||
"probe_capability runs the doctor's bounded forward on the backend that would serve.\n\nTags: node, admission"
|
||||
ctx = _context(_FakeBackend())
|
||||
|
||||
report = probe_capability(ctx)
|
||||
|
||||
assert report.passed
|
||||
assert admit(AdmissionRequirement.for_context(ctx), report) is report
|
||||
|
||||
|
||||
def test_the_production_validator_fails_a_backend_that_cannot_execute():
|
||||
"A shard that loads but cannot run a forward yields a failed report, not a pass.\n\nTags: node, admission"
|
||||
ctx = _context(_FakeBackend(forward_error=RuntimeError("CUDA out of memory")))
|
||||
|
||||
report = probe_capability(ctx)
|
||||
|
||||
assert not report.passed
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
admit(AdmissionRequirement.for_context(ctx), report)
|
||||
assert exc.value.reason == REASON_NOT_PASSED
|
||||
|
||||
|
||||
def test_the_production_validator_fails_a_node_with_no_backend_at_all():
|
||||
"A server with no model backend cannot prove anything, so it is not admitted.\n\nTags: node, admission"
|
||||
ctx = _context(None)
|
||||
|
||||
assert not probe_capability(ctx).passed
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# run_startup: a refused report means the tracker is never called
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _FakeTorchNodeServer:
|
||||
started = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
self.backend = kwargs.pop("_backend", None) or _FakeBackend()
|
||||
|
||||
def start(self):
|
||||
type(self).started = True
|
||||
return 7099
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def startup_env(monkeypatch):
|
||||
"""Fake hardware, wallet and tracker; records registrations *for this model*.
|
||||
|
||||
Heartbeat threads that other tests leave running are daemon threads that
|
||||
outlive their test and re-register through this same patched `_post_json`, so
|
||||
a plain call log would be polluted by whatever ran before. Only posts naming
|
||||
this test's model can have come from this test's node.
|
||||
"""
|
||||
import meshnet_node.startup as startup_mod
|
||||
|
||||
posted: list[tuple[str, dict]] = []
|
||||
_FakeTorchNodeServer.started = False
|
||||
|
||||
def _record(url, payload, timeout=10.0):
|
||||
if url.endswith("/v1/nodes/register") and payload.get("hf_repo") == MODEL:
|
||||
posted.append((url, payload))
|
||||
return {"node_id": "node-nca"}
|
||||
|
||||
monkeypatch.setattr(
|
||||
startup_mod,
|
||||
"detect_hardware",
|
||||
lambda: {"device": "cpu", "gpu_name": None, "vram_mb": 0, "ram_mb": 16384},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
startup_mod, "benchmark_throughput_checked", lambda _device: (10.0, True, None)
|
||||
)
|
||||
monkeypatch.setattr(startup_mod, "TorchNodeServer", _FakeTorchNodeServer)
|
||||
monkeypatch.setattr(
|
||||
startup_mod, "load_or_create_wallet", lambda **_kw: (b"", b"", "wallet-nca")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
startup_mod, "_get_json", lambda _url, timeout=10.0: {"relay_url": None, "nodes": []}
|
||||
)
|
||||
monkeypatch.setattr(startup_mod, "_post_json", _record)
|
||||
monkeypatch.setattr(startup_mod, "_start_heartbeat", lambda *a, **kw: None)
|
||||
return posted
|
||||
|
||||
|
||||
def _start(**kwargs):
|
||||
return run_startup(
|
||||
tracker_url="http://127.0.0.1:8080",
|
||||
model_id=MODEL,
|
||||
shard_start=0,
|
||||
shard_end=23,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def test_backend_validation_failure_registers_nothing(startup_env, monkeypatch):
|
||||
"A shard that cannot run a forward must never reach /v1/nodes/register.\n\nTags: node, admission, startup"
|
||||
import meshnet_node.startup as startup_mod
|
||||
|
||||
broken = _FakeBackend(forward_error=RuntimeError("CUDA out of memory"))
|
||||
monkeypatch.setattr(
|
||||
startup_mod,
|
||||
"TorchNodeServer",
|
||||
lambda **kw: _FakeTorchNodeServer(_backend=broken, **kw),
|
||||
)
|
||||
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
_start() # no validator: the production real-forward path
|
||||
|
||||
assert exc.value.reason == REASON_NOT_PASSED
|
||||
assert startup_env == [], "the tracker was called despite a failed validation"
|
||||
assert not _FakeTorchNodeServer.started, "a failed node still opened an endpoint"
|
||||
|
||||
|
||||
def test_a_report_for_a_different_model_cannot_be_reused_to_register(startup_env):
|
||||
"A success for one model must not admit another — the shape of a replayed proof.\n\nTags: node, admission, startup"
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
_start(capability_validator=capability_stub(model_id="other/model-1b"))
|
||||
|
||||
assert exc.value.reason == REASON_MODEL_MISMATCH
|
||||
assert startup_env == []
|
||||
|
||||
|
||||
def test_a_stale_report_cannot_be_reused_to_register(startup_env):
|
||||
"An aged-out proof is refused before the node advertises itself.\n\nTags: node, admission, startup"
|
||||
with pytest.raises(CapabilityAdmissionError) as exc:
|
||||
_start(capability_validator=capability_stub(age_seconds=86_400))
|
||||
|
||||
assert exc.value.reason == REASON_STALE
|
||||
assert startup_env == []
|
||||
|
||||
|
||||
def test_a_matching_passing_report_registers_and_travels_with_the_payload(startup_env):
|
||||
"Registration carries the proof for exactly the model/shard/recipe it advertises.\n\nTags: node, admission, startup"
|
||||
node = _start() # production validator against a working fake backend
|
||||
node.stop()
|
||||
|
||||
assert len(startup_env) == 1
|
||||
url, payload = startup_env[0]
|
||||
assert url.endswith("/v1/nodes/register")
|
||||
|
||||
report = payload["capability_report"]
|
||||
assert report["status"] == "passed"
|
||||
assert report["model"]["model_id"] == MODEL
|
||||
assert (report["shard"]["start"], report["shard"]["end"]) == (0, 23)
|
||||
assert report["recipe"]["recipe_id"] == DEFAULT_RECIPE_ID
|
||||
assert report["backend"]["device"] == "cpu"
|
||||
|
||||
|
||||
def test_the_served_backend_is_loaded_with_the_recipe_that_was_validated(startup_env):
|
||||
"The recipe named in the report is the one the serving backend actually ran.\n\nTags: node, admission, startup"
|
||||
node = _start(recipe_id="eager-attention")
|
||||
node.stop()
|
||||
|
||||
assert node.kwargs["recipe_params"] == {"attn_implementation": "eager"}
|
||||
report = startup_env[0][1]["capability_report"]
|
||||
assert report["recipe"]["recipe_id"] == "eager-attention"
|
||||
|
||||
|
||||
def test_an_unknown_recipe_fails_before_any_weights_are_loaded(startup_env):
|
||||
"A typo'd recipe id is caught at resolution, not after a multi-minute load.\n\nTags: node, admission, startup"
|
||||
from meshnet_node.recipe_manifest import RecipeManifestError
|
||||
|
||||
with pytest.raises(RecipeManifestError):
|
||||
_start(recipe_id="does-not-exist")
|
||||
|
||||
assert startup_env == []
|
||||
assert not _FakeTorchNodeServer.started
|
||||
644
tests/test_node_doctor.py
Normal file
644
tests/test_node_doctor.py
Normal file
@@ -0,0 +1,644 @@
|
||||
"""NCA-002 tests for `meshnet-node doctor`.
|
||||
|
||||
The unit tests inject a fake backend, so none of them download a model, import
|
||||
Torch, or need a GPU. The one test that runs a real model is `integration`-marked
|
||||
and takes its model identity from the environment — it has no model default, on
|
||||
purpose: the doctor is model-agnostic and so is its test.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from meshnet_node import doctor
|
||||
from meshnet_node.capability import STATUS_FAILED, STATUS_PASSED, CapabilityReport
|
||||
from meshnet_node.doctor import (
|
||||
CATEGORY_FORWARD_FAILED,
|
||||
CATEGORY_INSUFFICIENT_MEMORY,
|
||||
CATEGORY_INVALID_SHARD,
|
||||
CATEGORY_MISSING_DEPENDENCY,
|
||||
CATEGORY_NO_MODEL,
|
||||
CATEGORY_UNSUPPORTED_RECIPE,
|
||||
PROBE_TOKENS,
|
||||
DoctorError,
|
||||
DoctorSelection,
|
||||
build_probe_input,
|
||||
classify_failure,
|
||||
probe_forward,
|
||||
render_result,
|
||||
resolve_selection,
|
||||
run_doctor,
|
||||
select_recipes,
|
||||
write_reports,
|
||||
)
|
||||
from meshnet_node.model_backend import (
|
||||
InsufficientVRAMError,
|
||||
MissingModelDependencyError,
|
||||
UnsupportedRecipeParam,
|
||||
validate_recipe_params,
|
||||
)
|
||||
from meshnet_node.recipe_manifest import parse_recipe_manifest
|
||||
|
||||
# Deliberately not a model this project ships against: nothing here may special-case it.
|
||||
FIXTURE_MODEL = "acme-labs/Widget-9000-Instruct"
|
||||
|
||||
MANIFEST = parse_recipe_manifest(
|
||||
{
|
||||
"schema_version": 1,
|
||||
"catalogue_version": "test-1",
|
||||
"recipes": [
|
||||
{"id": "baseline", "version": "1", "backend_id": "torch-transformers"},
|
||||
{
|
||||
"id": "stateless",
|
||||
"version": "2",
|
||||
"backend_id": "torch-transformers",
|
||||
"params": {"use_cache": False},
|
||||
},
|
||||
],
|
||||
},
|
||||
source="<test manifest>",
|
||||
)
|
||||
|
||||
|
||||
class _Payload:
|
||||
"""Stands in for model_backend.TensorPayload."""
|
||||
|
||||
def __init__(self, body: bytes, shape: list[int]) -> None:
|
||||
self.body = body
|
||||
self.shape = shape
|
||||
self.attention_mask_header = None
|
||||
self.position_ids_header = None
|
||||
|
||||
|
||||
class _TailToken:
|
||||
"""Stands in for model_backend.TailTokenResult."""
|
||||
|
||||
def __init__(self, token_id: int = 7) -> None:
|
||||
self.token_id = token_id
|
||||
self.text = "ok"
|
||||
|
||||
|
||||
class _Device:
|
||||
def __init__(self, type_: str = "cpu") -> None:
|
||||
self.type = type_
|
||||
|
||||
|
||||
class _FakeBackend:
|
||||
"""A backend that loads but records exactly how it was driven."""
|
||||
|
||||
hidden_size = 8
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
is_head: bool = True,
|
||||
is_tail: bool = False,
|
||||
shard_start: int = 0,
|
||||
shard_end: int = 3,
|
||||
forward_error: Exception | None = None,
|
||||
) -> None:
|
||||
self.model_id = FIXTURE_MODEL
|
||||
self.is_head = is_head
|
||||
self.is_tail = is_tail
|
||||
self.shard_start = shard_start
|
||||
self.shard_end = shard_end
|
||||
self.device = _Device("cpu")
|
||||
self.forward_error = forward_error
|
||||
self.encoded_prompts: list[str] = []
|
||||
self.forwards: list[dict] = []
|
||||
|
||||
def encode_prompt(self, prompt: str):
|
||||
if self.forward_error is not None:
|
||||
raise self.forward_error
|
||||
self.encoded_prompts.append(prompt)
|
||||
return _Payload(b"\x00" * (PROBE_TOKENS * self.hidden_size * 2),
|
||||
[1, PROBE_TOKENS, self.hidden_size])
|
||||
|
||||
def forward_bytes(
|
||||
self,
|
||||
body,
|
||||
shape,
|
||||
attention_mask_header,
|
||||
position_ids_header,
|
||||
start_layer=None,
|
||||
**kwargs,
|
||||
):
|
||||
if self.forward_error is not None:
|
||||
raise self.forward_error
|
||||
self.forwards.append(
|
||||
{
|
||||
"body_len": len(body),
|
||||
"shape": shape,
|
||||
"start_layer": start_layer,
|
||||
"attention_mask_header": attention_mask_header,
|
||||
"position_ids_header": position_ids_header,
|
||||
}
|
||||
)
|
||||
if self.is_tail:
|
||||
return _TailToken()
|
||||
return _Payload(body, shape)
|
||||
|
||||
|
||||
def _loader(backend=None, *, error: Exception | None = None):
|
||||
"""A load_backend stub that records the (selection, recipe) pairs it saw."""
|
||||
calls: list[tuple[DoctorSelection, object]] = []
|
||||
|
||||
def load(selection, recipe):
|
||||
calls.append((selection, recipe))
|
||||
if error is not None:
|
||||
raise error
|
||||
return backend if backend is not None else _FakeBackend()
|
||||
|
||||
load.calls = calls # type: ignore[attr-defined]
|
||||
return load
|
||||
|
||||
|
||||
def _selection(**overrides) -> DoctorSelection:
|
||||
kwargs = dict(model_id=FIXTURE_MODEL, shard_start=0, shard_end=3)
|
||||
kwargs.update(overrides)
|
||||
return DoctorSelection(**kwargs)
|
||||
|
||||
|
||||
# --- selection resolves the same as startup ---------------------------------
|
||||
|
||||
|
||||
def test_resolve_selection_uses_the_configured_repo_shard_and_quantization():
|
||||
selection = resolve_selection(
|
||||
{
|
||||
"model_hf_repo": FIXTURE_MODEL,
|
||||
"model_name": "Widget-9000-Instruct",
|
||||
"shard_start": 4,
|
||||
"shard_end": 11,
|
||||
"quantization": "bf16", # startup normalizes this to bfloat16
|
||||
"download_dir": "/models",
|
||||
}
|
||||
)
|
||||
|
||||
assert selection.model_id == FIXTURE_MODEL
|
||||
assert (selection.shard_start, selection.shard_end) == (4, 11)
|
||||
assert selection.quantization == "bfloat16"
|
||||
assert selection.cache_dir == Path("/models")
|
||||
|
||||
|
||||
def test_resolve_selection_defaults_to_the_whole_model_like_startup():
|
||||
"""With no pinned shard, startup serves layers 0..n-1 — so doctor validates that."""
|
||||
seen: list[tuple[str, Path | None]] = []
|
||||
|
||||
def detect(model_id, cache_dir):
|
||||
seen.append((model_id, cache_dir))
|
||||
return 24
|
||||
|
||||
selection = resolve_selection(
|
||||
{"model_hf_repo": FIXTURE_MODEL}, detect_layers=detect
|
||||
)
|
||||
|
||||
assert (selection.shard_start, selection.shard_end) == (0, 23)
|
||||
assert seen == [(FIXTURE_MODEL, None)]
|
||||
|
||||
|
||||
def test_resolve_selection_without_a_model_is_actionable():
|
||||
with pytest.raises(DoctorError) as exc:
|
||||
resolve_selection({"model_hf_repo": "", "model_name": ""})
|
||||
|
||||
assert exc.value.category == CATEGORY_NO_MODEL
|
||||
assert "--model" in exc.value.hint
|
||||
|
||||
|
||||
def test_resolve_selection_rejects_an_inverted_shard_range():
|
||||
with pytest.raises(DoctorError) as exc:
|
||||
resolve_selection(
|
||||
{"model_hf_repo": FIXTURE_MODEL, "shard_start": 9, "shard_end": 2}
|
||||
)
|
||||
|
||||
assert exc.value.category == CATEGORY_INVALID_SHARD
|
||||
|
||||
|
||||
def test_resolve_selection_reports_an_unreadable_model_config():
|
||||
with pytest.raises(DoctorError) as exc:
|
||||
resolve_selection(
|
||||
{"model_hf_repo": FIXTURE_MODEL}, detect_layers=lambda *_: None
|
||||
)
|
||||
|
||||
assert exc.value.category == doctor.CATEGORY_MODEL_UNAVAILABLE
|
||||
assert "--shard-start" in str(exc.value)
|
||||
|
||||
|
||||
# --- the bounded real forward ------------------------------------------------
|
||||
|
||||
|
||||
def test_a_pass_requires_a_real_forward_through_the_selected_shard():
|
||||
"""Hardware being fine is not the bar: the shard itself has to execute."""
|
||||
backend = _FakeBackend(is_head=True)
|
||||
|
||||
result = run_doctor(
|
||||
_selection(), manifest=MANIFEST, load_backend=_loader(backend), now=lambda: 1000.0
|
||||
)
|
||||
|
||||
assert result.passed
|
||||
assert backend.encoded_prompts == [doctor.PROBE_PROMPT]
|
||||
report = result.reports[0]
|
||||
assert report.status == STATUS_PASSED
|
||||
assert report.model.model_id == FIXTURE_MODEL
|
||||
assert (report.shard.start, report.shard.end) == (0, 3)
|
||||
|
||||
|
||||
def test_a_backend_that_loads_but_cannot_forward_never_passes():
|
||||
"""The regression this story exists for: a load is not a validation."""
|
||||
backend = _FakeBackend(forward_error=RuntimeError("kernel exploded"))
|
||||
|
||||
result = run_doctor(
|
||||
_selection(), manifest=MANIFEST, load_backend=_loader(backend), now=lambda: 1.0
|
||||
)
|
||||
|
||||
assert not result.passed
|
||||
assert result.exit_code == 1
|
||||
report = result.reports[0]
|
||||
assert report.status == STATUS_FAILED
|
||||
assert result.results[0].category == CATEGORY_FORWARD_FAILED
|
||||
assert any("kernel exploded" in d for d in report.diagnostics)
|
||||
|
||||
|
||||
def test_a_mid_shard_is_probed_with_peer_shaped_hidden_states():
|
||||
backend = _FakeBackend(is_head=False, shard_start=4, shard_end=7)
|
||||
|
||||
detail = probe_forward(backend)
|
||||
|
||||
assert detail["probe"] == "hidden-states"
|
||||
assert backend.encoded_prompts == []
|
||||
forward = backend.forwards[0]
|
||||
assert forward["shape"] == [1, PROBE_TOKENS, backend.hidden_size]
|
||||
# bfloat16 == 2 bytes per element, and the probe stays bounded to PROBE_TOKENS.
|
||||
assert forward["body_len"] == PROBE_TOKENS * backend.hidden_size * 2
|
||||
assert forward["start_layer"] == 4
|
||||
|
||||
|
||||
def test_a_head_and_tail_shard_also_decodes_so_the_lm_head_is_covered():
|
||||
backend = _FakeBackend(is_head=True, is_tail=True, shard_end=5)
|
||||
|
||||
detail = probe_forward(backend)
|
||||
|
||||
assert detail["probe"] == "prompt+decode"
|
||||
assert detail["output"] == "token"
|
||||
# Re-entering above the last layer decodes without re-running any layer.
|
||||
assert backend.forwards[0]["start_layer"] == 6
|
||||
|
||||
|
||||
def test_a_tail_shard_that_decodes_a_token_passes():
|
||||
backend = _FakeBackend(is_head=False, is_tail=True, shard_start=8, shard_end=11)
|
||||
|
||||
detail = probe_forward(backend)
|
||||
|
||||
assert detail == {
|
||||
"probe": "hidden-states",
|
||||
"tokens": PROBE_TOKENS,
|
||||
"output": "token",
|
||||
"token_id": 7,
|
||||
}
|
||||
|
||||
|
||||
def test_an_empty_forward_result_is_a_failure_not_a_pass():
|
||||
backend = _FakeBackend(is_head=False)
|
||||
backend.forward_bytes = lambda *a, **k: _Payload(b"", []) # type: ignore[assignment]
|
||||
|
||||
with pytest.raises(DoctorError) as exc:
|
||||
probe_forward(backend)
|
||||
|
||||
assert exc.value.category == CATEGORY_FORWARD_FAILED
|
||||
|
||||
|
||||
def test_a_backend_with_no_hidden_size_cannot_be_probed():
|
||||
with pytest.raises(DoctorError) as exc:
|
||||
build_probe_input(0)
|
||||
|
||||
assert exc.value.category == CATEGORY_FORWARD_FAILED
|
||||
|
||||
|
||||
def test_probe_headers_decode_as_int64_tensors():
|
||||
probe = build_probe_input(hidden_size=8, tokens=3)
|
||||
|
||||
shape, encoded = probe.position_ids_header.split(":", 1)
|
||||
raw = base64.b64decode(encoded)
|
||||
assert shape == "1,3"
|
||||
assert list(struct.unpack("<3q", raw)) == [0, 1, 2]
|
||||
|
||||
|
||||
# --- recipes -----------------------------------------------------------------
|
||||
|
||||
|
||||
def test_the_default_run_validates_only_the_selected_recipe():
|
||||
"""Onboarding must not pay to validate recipes the node was not asked to serve."""
|
||||
load = _loader()
|
||||
|
||||
result = run_doctor(_selection(), manifest=MANIFEST, load_backend=load)
|
||||
|
||||
assert [r.recipe.id for r in result.results] == ["baseline"]
|
||||
assert len(load.calls) == 1
|
||||
|
||||
|
||||
def test_all_recipes_is_explicit_and_validates_every_recipe():
|
||||
load = _loader()
|
||||
|
||||
result = run_doctor(
|
||||
_selection(), manifest=MANIFEST, load_backend=load, all_recipes=True
|
||||
)
|
||||
|
||||
assert [r.recipe.id for r in result.results] == ["baseline", "stateless"]
|
||||
assert len(load.calls) == 2
|
||||
assert result.passed
|
||||
|
||||
|
||||
def test_each_recipe_reaches_the_backend_that_runs_it():
|
||||
"""A recipe that never reaches the loader was not really validated."""
|
||||
load = _loader()
|
||||
|
||||
run_doctor(_selection(), manifest=MANIFEST, load_backend=load, all_recipes=True)
|
||||
|
||||
params = [recipe.params for _, recipe in load.calls]
|
||||
assert params == [{}, {"use_cache": False}]
|
||||
|
||||
|
||||
def test_an_unknown_recipe_names_the_ones_that_exist():
|
||||
with pytest.raises(DoctorError) as exc:
|
||||
select_recipes(MANIFEST, recipe_id="does-not-exist")
|
||||
|
||||
assert exc.value.category == CATEGORY_UNSUPPORTED_RECIPE
|
||||
assert "baseline" in str(exc.value)
|
||||
|
||||
|
||||
def test_recipe_and_all_recipes_are_mutually_exclusive():
|
||||
with pytest.raises(DoctorError):
|
||||
select_recipes(MANIFEST, recipe_id="baseline", all_recipes=True)
|
||||
|
||||
|
||||
def test_a_recipe_the_backend_cannot_apply_is_a_failure_not_a_silent_pass():
|
||||
validate_recipe_params({"use_cache": False, "attn_implementation": "eager"})
|
||||
|
||||
with pytest.raises(UnsupportedRecipeParam) as exc:
|
||||
validate_recipe_params({"sparkle_mode": True})
|
||||
|
||||
assert "sparkle_mode" in str(exc.value)
|
||||
assert classify_failure(exc.value) == CATEGORY_UNSUPPORTED_RECIPE
|
||||
|
||||
|
||||
def test_the_shipped_recipes_are_all_applicable_by_the_backend():
|
||||
"""recipes.json and the backend's supported params must not drift apart."""
|
||||
from meshnet_node.recipe_manifest import load_recipe_manifest
|
||||
|
||||
for recipe in load_recipe_manifest().recipes:
|
||||
validate_recipe_params(recipe.params)
|
||||
|
||||
|
||||
# --- failure reporting -------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exc, category",
|
||||
[
|
||||
(MissingModelDependencyError("no torch"), CATEGORY_MISSING_DEPENDENCY),
|
||||
(InsufficientVRAMError("too big"), CATEGORY_INSUFFICIENT_MEMORY),
|
||||
(UnsupportedRecipeParam("nope"), CATEGORY_UNSUPPORTED_RECIPE),
|
||||
(ValueError("shard_end 99 exceeds last layer index 23"), CATEGORY_INVALID_SHARD),
|
||||
(FileNotFoundError("config.json"), doctor.CATEGORY_MODEL_UNAVAILABLE),
|
||||
(RuntimeError("something else"), doctor.CATEGORY_LOAD_FAILED),
|
||||
],
|
||||
)
|
||||
def test_load_failures_are_classified_into_actionable_categories(exc, category):
|
||||
result = run_doctor(
|
||||
_selection(), manifest=MANIFEST, load_backend=_loader(error=exc)
|
||||
)
|
||||
|
||||
assert not result.passed
|
||||
item = result.results[0]
|
||||
assert item.category == category
|
||||
assert item.hint # every category tells the operator what to do next
|
||||
assert item.report.status == STATUS_FAILED
|
||||
|
||||
|
||||
def test_a_failure_report_carries_the_hint_and_no_traceback():
|
||||
result = run_doctor(
|
||||
_selection(),
|
||||
manifest=MANIFEST,
|
||||
load_backend=_loader(error=InsufficientVRAMError("insufficient VRAM to load")),
|
||||
)
|
||||
|
||||
diagnostics = " ".join(result.reports[0].diagnostics)
|
||||
assert "insufficient VRAM to load" in diagnostics
|
||||
assert "--shard-start" in diagnostics # the actionable next step
|
||||
assert "Traceback" not in diagnostics
|
||||
assert ".py" not in diagnostics # no file/line noise from a stack
|
||||
|
||||
|
||||
def test_a_failure_report_still_identifies_what_was_being_validated():
|
||||
"""NCA-003 refuses to register without a matching report — including a failed one."""
|
||||
result = run_doctor(
|
||||
_selection(shard_start=4, shard_end=9, quantization="int8"),
|
||||
manifest=MANIFEST,
|
||||
load_backend=_loader(error=RuntimeError("boom")),
|
||||
now=lambda: 4242.0,
|
||||
)
|
||||
|
||||
report = result.reports[0]
|
||||
assert report.identity_key() == (
|
||||
FIXTURE_MODEL, 4, 9, "baseline", "1", "torch-transformers", "unknown",
|
||||
)
|
||||
assert report.validated_at == 4242.0
|
||||
assert report.recipe.catalogue_version == "test-1"
|
||||
|
||||
|
||||
def test_the_report_records_the_device_the_forward_actually_ran_on():
|
||||
result = run_doctor(
|
||||
_selection(), manifest=MANIFEST, load_backend=_loader(_FakeBackend())
|
||||
)
|
||||
|
||||
assert result.reports[0].backend.device == "cpu"
|
||||
assert result.reports[0].backend.backend_id == "torch-transformers"
|
||||
|
||||
|
||||
def test_reports_round_trip_through_the_written_json(tmp_path):
|
||||
result = run_doctor(
|
||||
_selection(), manifest=MANIFEST, load_backend=_loader(), all_recipes=True
|
||||
)
|
||||
|
||||
path = write_reports(result.reports, tmp_path / "nested" / "capability.json")
|
||||
payload = json.loads(path.read_text())
|
||||
|
||||
assert [CapabilityReport.from_dict(d).recipe.recipe_id for d in payload] == [
|
||||
"baseline",
|
||||
"stateless",
|
||||
]
|
||||
|
||||
|
||||
def test_a_single_report_is_written_as_one_object():
|
||||
"""One selected recipe writes one report — the shape NCA-003 will read."""
|
||||
import tempfile
|
||||
|
||||
result = run_doctor(_selection(), manifest=MANIFEST, load_backend=_loader())
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_reports(result.reports, Path(tmp) / "capability.json")
|
||||
report = CapabilityReport.from_json(path.read_text())
|
||||
|
||||
assert report.passed
|
||||
|
||||
|
||||
def test_the_summary_tells_a_failing_operator_what_to_fix():
|
||||
result = run_doctor(
|
||||
_selection(),
|
||||
manifest=MANIFEST,
|
||||
load_backend=_loader(error=MissingModelDependencyError("torch is not installed")),
|
||||
)
|
||||
|
||||
text = render_result(result, report_path=Path("/tmp/capability.json"))
|
||||
|
||||
assert "FAIL" in text
|
||||
assert CATEGORY_MISSING_DEPENDENCY in text
|
||||
assert "torch is not installed" in text
|
||||
assert "/tmp/capability.json" in text
|
||||
assert "Traceback" not in text
|
||||
|
||||
|
||||
def test_the_summary_names_the_shard_that_passed():
|
||||
result = run_doctor(_selection(), manifest=MANIFEST, load_backend=_loader())
|
||||
|
||||
text = render_result(result)
|
||||
|
||||
assert "PASS" in text
|
||||
assert FIXTURE_MODEL in text
|
||||
assert "layers 0–3" in text
|
||||
|
||||
|
||||
# --- the CLI wiring ----------------------------------------------------------
|
||||
|
||||
|
||||
def _run_cli(monkeypatch, argv, backend=None, error=None):
|
||||
"""Drive `meshnet-node doctor` end to end with an injected backend."""
|
||||
import sys
|
||||
|
||||
from meshnet_node import cli, config
|
||||
|
||||
monkeypatch.setattr(
|
||||
config, "load_config", lambda *a, **k: {
|
||||
"model_hf_repo": FIXTURE_MODEL,
|
||||
"shard_start": 0,
|
||||
"shard_end": 3,
|
||||
"quantization": "auto",
|
||||
}
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
doctor, "default_load_backend", _loader(backend, error=error)
|
||||
)
|
||||
monkeypatch.setattr(doctor, "load_recipe_manifest", lambda *a, **k: MANIFEST)
|
||||
monkeypatch.setattr(sys, "argv", ["meshnet-node", *argv])
|
||||
|
||||
with pytest.raises(SystemExit) as exit_info:
|
||||
cli.main()
|
||||
return exit_info.value.code
|
||||
|
||||
|
||||
def test_cli_doctor_exits_zero_and_writes_a_passing_report(monkeypatch, capsys, tmp_path):
|
||||
report = tmp_path / "capability.json"
|
||||
|
||||
code = _run_cli(monkeypatch, ["doctor", "--report", str(report)], backend=_FakeBackend())
|
||||
|
||||
assert code == 0
|
||||
assert capsys.readouterr().out.count("PASS") == 1
|
||||
assert CapabilityReport.from_json(report.read_text()).passed
|
||||
|
||||
|
||||
def test_cli_doctor_exits_non_zero_and_writes_the_failed_report(monkeypatch, capsys, tmp_path):
|
||||
report = tmp_path / "capability.json"
|
||||
|
||||
code = _run_cli(
|
||||
monkeypatch,
|
||||
["doctor", "--report", str(report)],
|
||||
error=InsufficientVRAMError("insufficient VRAM to load 24 layers"),
|
||||
)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert code == 1
|
||||
assert "FAIL" in out
|
||||
assert CATEGORY_INSUFFICIENT_MEMORY in out
|
||||
assert "Traceback" not in out # no raw traceback by default
|
||||
assert CapabilityReport.from_json(report.read_text()).status == STATUS_FAILED
|
||||
|
||||
|
||||
def test_cli_doctor_all_recipes_is_opt_in(monkeypatch, capsys, tmp_path):
|
||||
report = tmp_path / "capability.json"
|
||||
|
||||
code = _run_cli(
|
||||
monkeypatch,
|
||||
["doctor", "--all-recipes", "--report", str(report)],
|
||||
backend=_FakeBackend(),
|
||||
)
|
||||
|
||||
assert code == 0
|
||||
assert capsys.readouterr().out.count("PASS") == 2
|
||||
assert len(json.loads(report.read_text())) == 2
|
||||
|
||||
|
||||
def test_cli_doctor_json_prints_the_capability_report(monkeypatch, capsys, tmp_path):
|
||||
code = _run_cli(
|
||||
monkeypatch,
|
||||
["doctor", "--json", "--report", str(tmp_path / "c.json")],
|
||||
backend=_FakeBackend(),
|
||||
)
|
||||
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert code == 0
|
||||
assert payload[0]["model"]["model_id"] == FIXTURE_MODEL
|
||||
|
||||
|
||||
def test_cli_doctor_flags_select_what_is_validated(monkeypatch, capsys, tmp_path):
|
||||
"""`doctor --shard-start/--shard-end` validates the shard startup would load."""
|
||||
report = tmp_path / "capability.json"
|
||||
|
||||
code = _run_cli(
|
||||
monkeypatch,
|
||||
["doctor", "--shard-start", "2", "--shard-end", "5", "--report", str(report)],
|
||||
backend=_FakeBackend(),
|
||||
)
|
||||
|
||||
written = CapabilityReport.from_json(report.read_text())
|
||||
assert code == 0
|
||||
assert (written.shard.start, written.shard.end) == (2, 5)
|
||||
|
||||
|
||||
# --- the real-model smoke test ----------------------------------------------
|
||||
|
||||
# Model identity comes from the environment; there is no default, so this test
|
||||
# never smuggles a vendor-specific assumption into the suite.
|
||||
DOCTOR_MODEL = os.environ.get("MESHNET_DOCTOR_MODEL")
|
||||
DOCTOR_SHARD_START = int(os.environ.get("MESHNET_DOCTOR_SHARD_START", "0"))
|
||||
DOCTOR_SHARD_END = os.environ.get("MESHNET_DOCTOR_SHARD_END")
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.skipif(
|
||||
not DOCTOR_MODEL,
|
||||
reason="set MESHNET_DOCTOR_MODEL (and optionally MESHNET_DOCTOR_SHARD_START/END) to run",
|
||||
)
|
||||
def test_doctor_smoke_runs_a_real_forward_on_a_real_model(tmp_path):
|
||||
cfg = {
|
||||
"model_hf_repo": DOCTOR_MODEL,
|
||||
"quantization": os.environ.get("MESHNET_DOCTOR_QUANTIZATION", "auto"),
|
||||
"download_dir": os.environ.get("MESHNET_DOWNLOAD_DIR") or None,
|
||||
"shard_start": DOCTOR_SHARD_START,
|
||||
"shard_end": int(DOCTOR_SHARD_END) if DOCTOR_SHARD_END else None,
|
||||
"force_cpu": os.environ.get("MESHNET_DOCTOR_CPU") == "1",
|
||||
}
|
||||
selection = resolve_selection(cfg)
|
||||
|
||||
result = run_doctor(selection)
|
||||
|
||||
report = result.reports[0]
|
||||
assert result.passed, f"doctor failed: {report.diagnostics}"
|
||||
assert report.status == STATUS_PASSED
|
||||
assert report.model.model_id == DOCTOR_MODEL
|
||||
assert report.duration_ms > 0
|
||||
assert report.model.config_fingerprint.startswith("sha256:")
|
||||
|
||||
path = write_reports(result.reports, tmp_path / "capability.json")
|
||||
assert CapabilityReport.from_json(path.read_text()).passed
|
||||
@@ -26,6 +26,10 @@ from meshnet_node.startup import (
|
||||
_tracker_http_error_message,
|
||||
run_startup,
|
||||
)
|
||||
# Startup admits a node only on a capability report from a real forward, which a
|
||||
# fake backend cannot perform. These tests say so explicitly rather than bypassing
|
||||
# admission; the fail-closed path itself is covered in tests/test_node_admission.py.
|
||||
from meshnet_node.testing import assume_capability
|
||||
from meshnet_node.wallet import _b58encode, load_or_create_wallet
|
||||
from meshnet_contracts import LocalSolanaContracts
|
||||
from meshnet_tracker.server import TrackerServer
|
||||
@@ -333,6 +337,7 @@ def test_benchmark_throughput_is_registered_in_payload(monkeypatch, tmp_path):
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
torch_threads=8,
|
||||
torch_interop_threads=1,
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
node.stop()
|
||||
|
||||
@@ -394,6 +399,7 @@ def test_real_model_startup_passes_download_dir_and_kimi_metadata(monkeypatch, t
|
||||
shard_end=60,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
cache_dir=cache_dir,
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
node.stop()
|
||||
|
||||
@@ -448,6 +454,7 @@ def test_cuda_benchmark_failure_is_registered_for_inventory_only_gpu(monkeypatch
|
||||
shard_start=0,
|
||||
shard_end=23,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
node.stop()
|
||||
|
||||
@@ -1164,6 +1171,7 @@ def test_public_https_tracker_infers_relay_when_network_map_omits_relay_url(
|
||||
model_id="Qwen/Qwen2.5-0.5B-Instruct",
|
||||
advertise_host="172.29.104.23",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
pass
|
||||
@@ -1216,6 +1224,7 @@ def test_real_model_startup_summary_shows_total_layers(tmp_path, monkeypatch, ca
|
||||
vram_mb_override=6144,
|
||||
max_loaded_shards=2,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
|
||||
assert node.backend.total_layers == 24
|
||||
@@ -1275,6 +1284,7 @@ def test_real_model_startup_autodetects_cpu_memory_budget_and_logs_shard_budget(
|
||||
shard_start=0,
|
||||
shard_end=23,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
pass
|
||||
@@ -1359,6 +1369,7 @@ def test_public_tracker_model_node_registers_relay_metadata_from_tracker_url_onl
|
||||
tracker_url=tracker_url,
|
||||
model_id="Qwen/Qwen2.5-0.5B-Instruct",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
network_map = _get_json(f"{tracker_url}/v1/network/map")
|
||||
@@ -1444,6 +1455,7 @@ def test_public_tracker_relay_suppresses_virtual_ip_warning(
|
||||
model_id="Qwen/Qwen2.5-0.5B-Instruct",
|
||||
advertise_host="172.29.104.23",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
network_map = _get_json(f"{tracker_url}/v1/network/map")
|
||||
@@ -1523,6 +1535,7 @@ def test_later_node_auto_joins_existing_public_hf_model_with_only_tracker_url(
|
||||
tracker_url=tracker_url,
|
||||
advertise_host="203.0.113.21",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
route_resp = _get_json(
|
||||
@@ -1607,6 +1620,7 @@ def test_later_node_auto_joins_redundant_copy_when_model_is_fully_covered(
|
||||
tracker_url=tracker_url,
|
||||
advertise_host="203.0.113.32",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
assert captured["model_id"] == "Qwen/Qwen2.5-0.5B-Instruct"
|
||||
@@ -1637,6 +1651,7 @@ def test_full_startup_sequence(tmp_path):
|
||||
model="stub-model",
|
||||
wallet_path=wallet_path,
|
||||
cache_dir=cache_dir,
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
# Wallet was created on disk
|
||||
@@ -1699,6 +1714,7 @@ def test_preset_model_startup_starts_heartbeat(tmp_path, monkeypatch):
|
||||
model="stub-model",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
cache_dir=tmp_path / "shards",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
assert len(heartbeat_calls) == 1
|
||||
@@ -1739,6 +1755,7 @@ def test_preset_model_startup_honors_pinned_shard_range(tmp_path, monkeypatch):
|
||||
shard_end=5,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
cache_dir=tmp_path / "shards",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
assert len(heartbeat_calls) == 1
|
||||
@@ -1782,6 +1799,7 @@ def test_preset_startup_rejects_pinned_shard_above_memory_budget(tmp_path, monke
|
||||
shard_end=39,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
cache_dir=tmp_path / "shards",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
finally:
|
||||
tracker.stop()
|
||||
@@ -1835,6 +1853,7 @@ def test_network_auto_join_clips_oversized_cpu_assignment(tmp_path, monkeypatch,
|
||||
tracker_url="http://127.0.0.1:8080",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
tracker_source_disabled=True,
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
assert torch_calls[0]["shard_start"] == 0
|
||||
@@ -1895,6 +1914,7 @@ def test_preset_model_with_hf_repo_loads_torch_backend(tmp_path, monkeypatch, ca
|
||||
model="tiny-llama",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
cache_dir=tmp_path / "node-shards",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
assert len(torch_calls) == 1
|
||||
@@ -1972,6 +1992,7 @@ def test_torch_startup_retries_registration_when_tracker_unreachable(
|
||||
tracker_url=tracker_url,
|
||||
model_id="Qwen/Qwen2.5-0.5B-Instruct",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
assert register_calls["count"] == 1
|
||||
@@ -2052,6 +2073,7 @@ def test_real_model_startup_registers_downloaded_inventory_without_checksum(
|
||||
model="tiny-llama",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
cache_dir=tmp_path / "node-shards",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
assert len(hf_calls) == 1
|
||||
@@ -2342,6 +2364,7 @@ def test_startup_cpu_fallback(tmp_path, monkeypatch):
|
||||
model="stub-model",
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
cache_dir=tmp_path / "shards",
|
||||
capability_validator=assume_capability,
|
||||
)
|
||||
try:
|
||||
# Node is running even on CPU
|
||||
|
||||
93
tests/test_prefill_backpressure.py
Normal file
93
tests/test_prefill_backpressure.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Coverage for bounded, ordered prefill transfer."""
|
||||
|
||||
from threading import Event
|
||||
|
||||
import pytest
|
||||
|
||||
from meshnet_gateway.prefill_backpressure import (
|
||||
BoundedPrefillSender,
|
||||
DEFAULT_PREFILL_CHUNK_TOKENS,
|
||||
DEFAULT_PREFILL_MAX_IN_FLIGHT,
|
||||
PrefillTransferLimits,
|
||||
)
|
||||
from meshnet_gateway.server import _BinaryActivation, _post_binary_forward
|
||||
|
||||
|
||||
def test_limits_have_safe_defaults_and_keep_legacy_chunk_setting(monkeypatch):
|
||||
monkeypatch.delenv("MESHNET_PREFILL_CHUNK_TOKENS", raising=False)
|
||||
monkeypatch.delenv("MESHNET_PREFILL_MAX_IN_FLIGHT", raising=False)
|
||||
monkeypatch.setenv("MESHNET_CHUNK_TOKENS", "64")
|
||||
|
||||
limits = PrefillTransferLimits.from_env()
|
||||
|
||||
assert limits.chunk_tokens == 64
|
||||
assert limits.max_in_flight == DEFAULT_PREFILL_MAX_IN_FLIGHT
|
||||
assert PrefillTransferLimits().chunk_tokens == DEFAULT_PREFILL_CHUNK_TOKENS
|
||||
assert limits.max_buffered_bytes == limits.max_chunk_bytes
|
||||
|
||||
|
||||
def test_slow_consumer_applies_backpressure_preserves_order_and_bounds_bytes():
|
||||
sender = BoundedPrefillSender(
|
||||
PrefillTransferLimits(chunk_tokens=2, max_in_flight=4, max_chunk_bytes=8)
|
||||
)
|
||||
sent: list[int] = []
|
||||
produced: list[int] = []
|
||||
|
||||
def chunks():
|
||||
for index in range(3):
|
||||
produced.append(index)
|
||||
yield bytes([index]) * 8
|
||||
|
||||
def forward(chunk: bytes) -> int:
|
||||
sent.append(chunk[0])
|
||||
# The next item cannot have been constructed while this consumer waits.
|
||||
assert produced[-1] == chunk[0]
|
||||
assert len(produced) == chunk[0] + 1
|
||||
assert sender.buffered_bytes == 8
|
||||
assert sender.in_flight == 1
|
||||
return chunk[0]
|
||||
|
||||
assert sender.send(chunks(), body_size=len, forward=forward) == [0, 1, 2]
|
||||
assert sent == [0, 1, 2]
|
||||
assert sender.peak_buffered_bytes <= sender.limits.max_buffered_bytes
|
||||
assert sender.peak_in_flight == 1
|
||||
assert sender.buffered_bytes == sender.in_flight == 0
|
||||
|
||||
|
||||
def test_failure_and_cancellation_release_owned_buffers():
|
||||
sender = BoundedPrefillSender(PrefillTransferLimits())
|
||||
with pytest.raises(RuntimeError, match="route lost"):
|
||||
sender.send([b"one", b"two"], body_size=len, forward=lambda _: (_ for _ in ()).throw(RuntimeError("route lost")))
|
||||
assert sender.closed
|
||||
assert sender.buffered_bytes == sender.in_flight == 0
|
||||
|
||||
cancelled = Event()
|
||||
cancelled.set()
|
||||
sender = BoundedPrefillSender(PrefillTransferLimits())
|
||||
assert sender.send([b"never"], body_size=len, forward=lambda _: None, cancelled=cancelled) == []
|
||||
assert sender.buffered_bytes == sender.in_flight == 0
|
||||
|
||||
|
||||
def test_legacy_single_chunk_peer_response_uses_outgoing_metadata(monkeypatch):
|
||||
class Response:
|
||||
headers = {"Content-Type": "application/octet-stream"}
|
||||
|
||||
def read(self):
|
||||
return b"\0" * (1 * 1 * 64 * 2)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr("urllib.request.urlopen", lambda *_args, **_kwargs: Response())
|
||||
activation = _BinaryActivation(
|
||||
body=b"\0" * (1 * 1 * 64 * 2), shape=[1, 1, 64], dtype="bfloat16",
|
||||
session="legacy-session", chunk_index=0, chunk_total=1, encoding=None, headers={},
|
||||
)
|
||||
|
||||
response = _post_binary_forward("http://legacy/forward", activation, hop_index=0, timeout=1.0)
|
||||
|
||||
assert response.session == activation.session
|
||||
assert response.chunk_index == response.chunk_total - 1 == 0
|
||||
@@ -24,6 +24,7 @@ from meshnet_node.model_backend import (
|
||||
_should_partial_materialize_shard,
|
||||
_decoder_attention_mask,
|
||||
_int_tensor_header,
|
||||
_tensor_from_bfloat16_bytes,
|
||||
_torch_cuda_is_executable,
|
||||
build_quantization_config,
|
||||
validate_quantization,
|
||||
@@ -538,6 +539,20 @@ def test_int_tensor_header_serializes_torch_tensors():
|
||||
assert header.startswith("1,3:")
|
||||
|
||||
|
||||
def test_bfloat16_wire_decode_views_owned_bytes_without_float32_round_trip():
|
||||
"""Activation decode stays bf16 and does not clone bytes into bytearray.
|
||||
|
||||
Tags: model, performance, wire
|
||||
"""
|
||||
torch = pytest.importorskip("torch")
|
||||
body = torch.tensor([[1, 2]], dtype=torch.bfloat16).view(torch.uint8).numpy().tobytes()
|
||||
|
||||
decoded = _tensor_from_bfloat16_bytes(body, [1, 2], torch)
|
||||
|
||||
assert decoded.dtype == torch.bfloat16
|
||||
assert decoded.tolist() == [[1.0, 2.0]]
|
||||
|
||||
|
||||
def test_decoder_attention_mask_is_causal_float_mask():
|
||||
"Decoder attention mask is causal float mask\n\nTags: model, node, real-inference"
|
||||
torch = pytest.importorskip("torch")
|
||||
|
||||
126
tests/test_route_session_benchmark.py
Normal file
126
tests/test_route_session_benchmark.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""DIP-001 deterministic Route Session benchmark coverage."""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from meshnet_node.route_session_benchmark import (
|
||||
BenchmarkScenario,
|
||||
PerformanceThresholds,
|
||||
assert_benchmark,
|
||||
assert_performance_gate,
|
||||
format_summary,
|
||||
main,
|
||||
run_benchmark_matrix,
|
||||
run_real_model_lan_benchmark,
|
||||
run_route_session_benchmark,
|
||||
)
|
||||
|
||||
|
||||
def test_matrix_reports_direct_relay_prefill_decode_and_machine_readable_metrics():
|
||||
"""The baseline reports every required scenario and transport metric.
|
||||
|
||||
Tags: performance, routing
|
||||
"""
|
||||
report = run_benchmark_matrix()
|
||||
assert {(run["mode"], run["cache_mode"]) for run in report["runs"]} == {
|
||||
("direct", "cached"), ("direct", "stateless"),
|
||||
("relay", "cached"), ("relay", "stateless"),
|
||||
}
|
||||
for run in report["runs"]:
|
||||
assert set(run["phases"]) == {"prefill", "decode"}
|
||||
assert "head->tail" in run["seams"]
|
||||
assert {"p50_latency_ms", "p95_latency_ms", "payload_bytes", "compression_ratio",
|
||||
"connection_attempts", "p95_queue_wait_ms"} <= set(run["phases"]["decode"])
|
||||
sample = run["samples"][0]
|
||||
assert sample["framing_ms"] > 0
|
||||
assert sample["metadata_ms"] > 0
|
||||
assert sample["copy_allocation_ms"] > 0
|
||||
assert sample["copy_allocation_bytes"] >= sample["payload_bytes"]
|
||||
assert len(run["samples"]) == 1 + len(run["output_tokens"])
|
||||
assert {"tokens_per_sec", "bytes_per_token", "compression_cpu_ms", "peak_buffered_bytes"} <= set(run["phases"]["decode"])
|
||||
|
||||
|
||||
def test_cached_sessions_reuse_one_connection_and_preserve_stub_tokens():
|
||||
"""Cached Route Sessions keep one direct or relay connection per seam.
|
||||
|
||||
Tags: performance, relay
|
||||
"""
|
||||
scenario = BenchmarkScenario(output_tokens=(" one", " two", " three"))
|
||||
for mode in ("direct", "relay"):
|
||||
run = run_route_session_benchmark(mode, "cached", scenario)
|
||||
assert_benchmark(run, expected_tokens=scenario.output_tokens, expected_connection_attempts=1)
|
||||
if mode == "relay":
|
||||
assert all(sample.queue_wait_ms > 0 for sample in run.samples)
|
||||
|
||||
|
||||
def test_stateless_baselines_make_each_activation_a_connection_attempt():
|
||||
"""Stateless comparison mode does not accidentally inherit Route Session state.
|
||||
|
||||
Tags: performance, routing
|
||||
"""
|
||||
scenario = BenchmarkScenario(output_tokens=(" one", " two"))
|
||||
for mode in ("direct", "relay"):
|
||||
run = run_route_session_benchmark(mode, "stateless", scenario)
|
||||
assert_benchmark(run, expected_tokens=scenario.output_tokens, expected_connection_attempts=3)
|
||||
|
||||
|
||||
def test_cli_writes_json_artifact_and_human_summary(tmp_path, capsys):
|
||||
"""The CLI emits both CI-ready JSON and an operator-readable summary.
|
||||
|
||||
Tags: performance
|
||||
"""
|
||||
output = tmp_path / "route-session-benchmark.json"
|
||||
assert main(["--json-out", str(output)]) == 0
|
||||
report = json.loads(output.read_text())
|
||||
assert report["schema_version"] == 1
|
||||
assert "Route Session benchmark" in capsys.readouterr().out
|
||||
assert "relay" in format_summary(report)
|
||||
|
||||
|
||||
def test_performance_gate_checks_comparison_identity_session_and_cleanup():
|
||||
"""CI gate accepts the fixed matrix and rejects a meaningful slowdown.
|
||||
|
||||
Tags: performance, routing
|
||||
"""
|
||||
report = run_benchmark_matrix()
|
||||
assert_performance_gate(report)
|
||||
report["runs"][0]["phases"]["decode"]["tokens_per_sec"] = 0.1
|
||||
try:
|
||||
assert_performance_gate(report, thresholds=PerformanceThresholds())
|
||||
except AssertionError as exc:
|
||||
assert "throughput regressed" in str(exc)
|
||||
else: # pragma: no cover - makes the intended threshold explicit
|
||||
raise AssertionError("gate did not catch the throughput regression")
|
||||
|
||||
|
||||
def test_performance_gate_rejects_session_or_cleanup_leaks():
|
||||
"""Exact resource/session invariants are not subject to variance tolerance.
|
||||
|
||||
Tags: performance, routing
|
||||
"""
|
||||
report = run_benchmark_matrix()
|
||||
report["runs"][0]["samples"][1]["session_id"] = "wrong-session"
|
||||
try:
|
||||
assert_performance_gate(report)
|
||||
except AssertionError as exc:
|
||||
assert "Route Session changed" in str(exc)
|
||||
else: # pragma: no cover
|
||||
raise AssertionError("gate did not catch session instability")
|
||||
|
||||
|
||||
def test_real_model_lan_capture_uses_the_shared_report_schema():
|
||||
"""The opt-in LAN command is client-measurable and needs no real model in CI.
|
||||
|
||||
Tags: performance
|
||||
"""
|
||||
response = MagicMock()
|
||||
response.read.return_value = json.dumps({"choices": [{"message": {"content": "amber birch"}}]}).encode()
|
||||
response.headers.get.return_value = "lan-session"
|
||||
response.__enter__.return_value = response
|
||||
with patch("meshnet_node.route_session_benchmark.urllib.request.urlopen", return_value=response):
|
||||
report = run_real_model_lan_benchmark("http://lan-node:7000", model="test-model")
|
||||
run = report["runs"][0]
|
||||
assert report["source"] == "real-model-lan-client"
|
||||
assert run["session_id"] == "lan-session"
|
||||
assert run["phases"]["decode"]["tokens_per_sec"] > 0
|
||||
assert run["cleanup"]["open_connections"] == 0
|
||||
78
tests/test_seam_telemetry.py
Normal file
78
tests/test_seam_telemetry.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Unit coverage for bounded Activation Seam telemetry."""
|
||||
|
||||
from meshnet_node.seam_telemetry import GenerationTelemetry
|
||||
|
||||
|
||||
def test_seam_telemetry_aggregates_bytes_latency_and_correlates_ids():
|
||||
telemetry = GenerationTelemetry("route-session-1", report_every=2, report_interval=100, now=0.0)
|
||||
|
||||
assert telemetry.record_seam(
|
||||
activation_id="activation-1", phase="prefill", hop=0, node="node-a",
|
||||
latency_seconds=0.012, wire_bytes=120, response_bytes=240,
|
||||
connection_reused=False, now=0.1,
|
||||
)
|
||||
telemetry.mark_reported(now=0.1)
|
||||
assert telemetry.record_seam(
|
||||
activation_id="activation-2", phase="prefill", hop=0, node="node-a",
|
||||
latency_seconds=0.008, wire_bytes=80, response_bytes=160,
|
||||
connection_reused=True, now=0.2,
|
||||
)
|
||||
telemetry.note_tokens(4)
|
||||
|
||||
snapshot = telemetry.snapshot(now=2.0)
|
||||
assert snapshot["session_id"] == "route-session-1"
|
||||
assert snapshot["tokens_per_sec"] == 2.0
|
||||
assert snapshot["seams"] == [{
|
||||
"phase": "prefill", "hop": 0, "node": "node-a", "activations": 2,
|
||||
"latency_ms": 20.0, "avg_latency_ms": 10.0, "wire_bytes": 200,
|
||||
"response_bytes": 400, "connection_reuse": 1,
|
||||
"compression_input_bytes": 0, "compression_output_bytes": 0, "compression_ms": 0.0,
|
||||
"decompression_input_bytes": 0, "decompression_output_bytes": 0, "decompression_ms": 0.0,
|
||||
"last_activation_id": "activation-2",
|
||||
}]
|
||||
|
||||
|
||||
def test_seam_telemetry_includes_compression_work_and_byte_counters():
|
||||
telemetry = GenerationTelemetry("route-session-compression", now=0.0)
|
||||
telemetry.record_compression(
|
||||
phase="prefill", hop=0, node="node-a", input_bytes=1000, output_bytes=200,
|
||||
elapsed_seconds=0.003,
|
||||
)
|
||||
telemetry.record_compression(
|
||||
phase="prefill", hop=0, node="node-a", input_bytes=200, output_bytes=1000,
|
||||
elapsed_seconds=0.001, decompression=True,
|
||||
)
|
||||
seam = telemetry.snapshot(now=1.0)["seams"][0]
|
||||
assert seam["compression_input_bytes"] == 1000
|
||||
assert seam["compression_output_bytes"] == 200
|
||||
assert seam["compression_ms"] == 3.0
|
||||
assert seam["decompression_input_bytes"] == 200
|
||||
assert seam["decompression_output_bytes"] == 1000
|
||||
assert seam["decompression_ms"] == 1.0
|
||||
|
||||
|
||||
def test_seam_telemetry_reports_on_bounded_cadence_and_cleans_up():
|
||||
telemetry = GenerationTelemetry("route-session-2", report_every=3, report_interval=5, now=0.0)
|
||||
|
||||
for count in range(1, 4):
|
||||
due = telemetry.record_seam(
|
||||
activation_id=f"activation-{count}", phase="decode", hop=1, node="node-b",
|
||||
latency_seconds=0.001, wire_bytes=10, response_bytes=20,
|
||||
connection_reused=True, now=float(count),
|
||||
)
|
||||
if due:
|
||||
telemetry.mark_reported(now=float(count))
|
||||
assert not telemetry.report_due
|
||||
assert telemetry.record_seam(
|
||||
activation_id="activation-4", phase="decode", hop=1, node="node-b",
|
||||
latency_seconds=0.001, wire_bytes=10, response_bytes=20,
|
||||
connection_reused=True, now=9.0,
|
||||
)
|
||||
|
||||
telemetry.close()
|
||||
assert telemetry.snapshot(now=10.0)["seams"] == []
|
||||
assert not telemetry.record_seam(
|
||||
activation_id="late", phase="decode", hop=1, node="node-b",
|
||||
latency_seconds=0.001, wire_bytes=10, response_bytes=20,
|
||||
connection_reused=True, now=10.0,
|
||||
)
|
||||
525
tests/test_tracker_capability_admission.py
Normal file
525
tests/test_tracker_capability_admission.py
Normal file
@@ -0,0 +1,525 @@
|
||||
"""NCA-004 tests: the tracker records a node's capability proof and routes only
|
||||
to nodes whose proof admits what they advertise (ADR-0023).
|
||||
|
||||
Model ids here are arbitrary and made up on purpose: nothing in the admission or
|
||||
routing path may branch on a vendor, model or kernel name.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import pytest
|
||||
|
||||
from meshnet_tracker.capability import (
|
||||
MIN_CATALOGUE_VERSION,
|
||||
POLICY_COMPAT,
|
||||
POLICY_ENFORCE,
|
||||
STATE_ABSENT,
|
||||
STATE_ADMITTED,
|
||||
STATE_CATALOGUE_INCOMPATIBLE,
|
||||
STATE_FAILED,
|
||||
STATE_INVALID,
|
||||
STATE_MODEL_MISMATCH,
|
||||
STATE_RECIPE_MISMATCH,
|
||||
STATE_SHARD_MISMATCH,
|
||||
STATE_STALE,
|
||||
evaluate_report,
|
||||
policy_from_env,
|
||||
)
|
||||
from meshnet_tracker.server import (
|
||||
TrackerServer,
|
||||
_NodeEntry,
|
||||
_capability_routable,
|
||||
_node_admission,
|
||||
_select_route,
|
||||
)
|
||||
|
||||
MODEL = "arbitrary-labs/oracle-9b"
|
||||
SHORT = "oracle-9b"
|
||||
LAYERS = 32
|
||||
|
||||
|
||||
def _post_json(url: str, payload: dict) -> dict:
|
||||
data = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(
|
||||
url, data=data, headers={"Content-Type": "application/json"}, method="POST"
|
||||
)
|
||||
with urllib.request.urlopen(req) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
|
||||
def _get_json(url: str) -> dict:
|
||||
with urllib.request.urlopen(url) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
|
||||
def _report(
|
||||
*,
|
||||
model_id: str = MODEL,
|
||||
start: int = 0,
|
||||
end: int = 15,
|
||||
status: str = "passed",
|
||||
validated_at: float | None = None,
|
||||
recipe_id: str = "baseline",
|
||||
recipe_version: str = "1",
|
||||
catalogue_version: str = MIN_CATALOGUE_VERSION,
|
||||
schema_version: int = 1,
|
||||
device: str = "cpu",
|
||||
diagnostics: list | None = None,
|
||||
) -> dict:
|
||||
"""A capability report shaped exactly as `meshnet_node.capability` emits it."""
|
||||
return {
|
||||
"schema_version": schema_version,
|
||||
"model": {"model_id": model_id, "revision": None, "config_fingerprint": None},
|
||||
"shard": {"start": start, "end": end},
|
||||
"recipe": {
|
||||
"recipe_id": recipe_id,
|
||||
"recipe_version": recipe_version,
|
||||
"catalogue_version": catalogue_version,
|
||||
},
|
||||
"backend": {
|
||||
"backend_id": "torch-transformers",
|
||||
"device": device,
|
||||
"device_name": None,
|
||||
"quantization": "bfloat16",
|
||||
"runtime": {},
|
||||
},
|
||||
"status": status,
|
||||
"validated_at": time.time() if validated_at is None else validated_at,
|
||||
"duration_ms": 42,
|
||||
"diagnostics": list(diagnostics or []),
|
||||
}
|
||||
|
||||
|
||||
def _registration(
|
||||
port: int,
|
||||
*,
|
||||
start: int = 0,
|
||||
end: int = 15,
|
||||
report: dict | None = "default", # type: ignore[assignment]
|
||||
recipe_id: str | None = "baseline",
|
||||
recipe_version: str | None = "1",
|
||||
benchmark_tokens_per_sec: float = 10.0,
|
||||
) -> dict:
|
||||
payload: dict = {
|
||||
"endpoint": f"http://127.0.0.1:{port}",
|
||||
"model": SHORT,
|
||||
"hf_repo": MODEL,
|
||||
"num_layers": LAYERS,
|
||||
"shard_start": start,
|
||||
"shard_end": end,
|
||||
"hardware_profile": {},
|
||||
"score": 1.0,
|
||||
"tracker_mode": start == 0,
|
||||
"benchmark_tokens_per_sec": benchmark_tokens_per_sec,
|
||||
}
|
||||
if report == "default":
|
||||
report = _report(start=start, end=end)
|
||||
if report is not None:
|
||||
payload["capability_report"] = report
|
||||
if recipe_id is not None:
|
||||
payload["recipe_id"] = recipe_id
|
||||
if recipe_version is not None:
|
||||
payload["recipe_version"] = recipe_version
|
||||
return payload
|
||||
|
||||
|
||||
def _always(_reported: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _evaluate(report, **kwargs) -> object:
|
||||
kwargs.setdefault("model_matches", _always)
|
||||
kwargs.setdefault("advertised_model", MODEL)
|
||||
kwargs.setdefault("shard_start", 0)
|
||||
kwargs.setdefault("shard_end", 15)
|
||||
return evaluate_report(report, **kwargs)
|
||||
|
||||
|
||||
# --------------------------------------------------------------- report verdicts
|
||||
|
||||
|
||||
def test_a_passing_report_that_covers_the_registration_is_admitted():
|
||||
"A proof matching the advertised model and shard admits the node.\n\nTags: routing, tracker"
|
||||
state = _evaluate(_report())
|
||||
assert state.state == STATE_ADMITTED
|
||||
assert state.proven
|
||||
assert state.model_id == MODEL
|
||||
assert (state.shard_start, state.shard_end) == (0, 15)
|
||||
|
||||
|
||||
def test_a_missing_report_is_absent_not_admitted():
|
||||
"No proof is recorded as `absent` — never silently treated as proven.\n\nTags: routing, tracker"
|
||||
state = _evaluate(None)
|
||||
assert state.state == STATE_ABSENT
|
||||
assert not state.proven
|
||||
|
||||
|
||||
def test_a_failed_report_is_recorded_as_failed():
|
||||
"A node that failed its own validation is not routable.\n\nTags: routing, tracker"
|
||||
state = _evaluate(_report(status="failed", diagnostics=["out of memory on device"]))
|
||||
assert state.state == STATE_FAILED
|
||||
assert not state.proven
|
||||
assert "out of memory" in state.detail
|
||||
|
||||
|
||||
def test_a_report_for_a_different_model_is_a_model_mismatch():
|
||||
"A proof for another artifact proves nothing about this one.\n\nTags: routing, tracker"
|
||||
state = evaluate_report(
|
||||
_report(model_id="other-org/unrelated-3b"),
|
||||
model_matches=lambda reported: reported == MODEL,
|
||||
advertised_model=MODEL,
|
||||
shard_start=0,
|
||||
shard_end=15,
|
||||
)
|
||||
assert state.state == STATE_MODEL_MISMATCH
|
||||
|
||||
|
||||
def test_a_report_for_a_different_shard_is_a_shard_mismatch():
|
||||
"A proof for layers 0–15 does not admit a node advertising 16–31.\n\nTags: routing, tracker"
|
||||
state = _evaluate(_report(start=0, end=15), shard_start=16, shard_end=31)
|
||||
assert state.state == STATE_SHARD_MISMATCH
|
||||
|
||||
|
||||
def test_a_report_for_a_different_recipe_than_the_node_declares_is_a_recipe_mismatch():
|
||||
"The proof must be for the recipe the node says it serves with.\n\nTags: routing, tracker"
|
||||
state = _evaluate(_report(recipe_id="eager-attention"), declared_recipe_id="baseline")
|
||||
assert state.state == STATE_RECIPE_MISMATCH
|
||||
|
||||
versioned = _evaluate(
|
||||
_report(recipe_version="1"),
|
||||
declared_recipe_id="baseline",
|
||||
declared_recipe_version="2",
|
||||
)
|
||||
assert versioned.state == STATE_RECIPE_MISMATCH
|
||||
|
||||
|
||||
def test_an_older_recipe_catalogue_is_incompatible():
|
||||
"Recipe ids from a catalogue older than the tracker's minimum cannot be matched.\n\nTags: routing, tracker"
|
||||
state = _evaluate(_report(catalogue_version="2025.01.1"))
|
||||
assert state.state == STATE_CATALOGUE_INCOMPATIBLE
|
||||
assert MIN_CATALOGUE_VERSION in state.detail
|
||||
|
||||
newer = _evaluate(_report(catalogue_version="2099.12.9"))
|
||||
assert newer.state == STATE_ADMITTED
|
||||
|
||||
|
||||
def test_an_unparseable_catalogue_version_is_incompatible():
|
||||
"A catalogue version that cannot be compared cannot be shown to be new enough.\n\nTags: routing, tracker"
|
||||
assert _evaluate(_report(catalogue_version="rolling")).state == STATE_CATALOGUE_INCOMPATIBLE
|
||||
|
||||
|
||||
def test_a_stale_report_is_not_admitted():
|
||||
"A proof older than the freshness bound must be re-validated before routing.\n\nTags: routing, tracker"
|
||||
state = _evaluate(_report(validated_at=time.time() - 3600), max_age_seconds=900.0)
|
||||
assert state.state == STATE_STALE
|
||||
|
||||
|
||||
def test_a_future_dated_report_is_not_admitted():
|
||||
"A proof from the future is a broken clock, not a fresh proof.\n\nTags: routing, tracker"
|
||||
state = _evaluate(_report(validated_at=time.time() + 3600))
|
||||
assert state.state == STATE_STALE
|
||||
assert "clock" in state.detail
|
||||
|
||||
|
||||
def test_a_report_from_an_unknown_schema_version_is_invalid():
|
||||
"The tracker refuses to interpret a report layout it does not read.\n\nTags: routing, tracker"
|
||||
assert _evaluate(_report(schema_version=99)).state == STATE_INVALID
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[
|
||||
"not-an-object",
|
||||
{},
|
||||
{"schema_version": 1},
|
||||
{**_report(), "model": {"model_id": ""}},
|
||||
{**_report(), "shard": {"start": -1, "end": 3}},
|
||||
{**_report(), "validated_at": "yesterday"},
|
||||
{**_report(), "status": None},
|
||||
],
|
||||
ids=["not-object", "empty", "header-only", "blank-model", "negative-shard",
|
||||
"bad-timestamp", "missing-status"],
|
||||
)
|
||||
def test_a_malformed_report_is_invalid_and_never_admitted(payload):
|
||||
"Malformed proof is rejected by the schema check, not by a later coincidence.\n\nTags: routing, tracker"
|
||||
state = _evaluate(payload)
|
||||
assert state.state == STATE_INVALID
|
||||
assert not state.proven
|
||||
|
||||
|
||||
def test_recorded_detail_carries_no_credentials_from_node_diagnostics():
|
||||
"Operator-facing admission detail is sanitized; a leaked token never reaches it.\n\nTags: routing, security, tracker"
|
||||
state = _evaluate(
|
||||
_report(
|
||||
status="failed",
|
||||
diagnostics=["download failed: authorization=hf_abcdefghijklmnopqrstuvwxyz01"],
|
||||
)
|
||||
)
|
||||
assert state.state == STATE_FAILED
|
||||
assert "hf_abcdefghijklmnopqrstuvwxyz01" not in state.detail
|
||||
assert "hf_abcdefghijklmnopqrstuvwxyz01" not in json.dumps(state.to_dict())
|
||||
assert "[redacted]" in state.detail
|
||||
|
||||
|
||||
# ---------------------------------------------------------- compatibility policy
|
||||
|
||||
|
||||
def test_compat_policy_routes_a_legacy_node_but_never_a_broken_proof():
|
||||
"Older nodes (no proof) keep routing under `compat`; a bad proof never does.\n\nTags: routing, tracker"
|
||||
absent = _evaluate(None)
|
||||
failed = _evaluate(_report(status="failed"))
|
||||
|
||||
assert absent.routable_under(POLICY_COMPAT)
|
||||
assert not absent.routable_under(POLICY_ENFORCE)
|
||||
assert not failed.routable_under(POLICY_COMPAT)
|
||||
assert not failed.routable_under(POLICY_ENFORCE)
|
||||
|
||||
|
||||
def test_the_policy_is_read_from_the_environment_and_defaults_to_compat(monkeypatch):
|
||||
"The rollout switch is explicit and defaults to the compatible behaviour.\n\nTags: config, routing, tracker"
|
||||
monkeypatch.delenv("MESHNET_TRACKER_CAPABILITY_POLICY", raising=False)
|
||||
assert policy_from_env() == POLICY_COMPAT
|
||||
monkeypatch.setenv("MESHNET_TRACKER_CAPABILITY_POLICY", "enforce")
|
||||
assert policy_from_env() == POLICY_ENFORCE
|
||||
monkeypatch.setenv("MESHNET_TRACKER_CAPABILITY_POLICY", "nonsense")
|
||||
assert policy_from_env() == POLICY_COMPAT
|
||||
|
||||
|
||||
# ------------------------------------------------------------- the routing gate
|
||||
|
||||
|
||||
def _entry(node_id: str, start: int, end: int, report: dict | None, **kwargs) -> _NodeEntry:
|
||||
from meshnet_tracker.capability import evaluate_report as _eval
|
||||
|
||||
entry = _NodeEntry(
|
||||
node_id=node_id,
|
||||
endpoint=f"http://127.0.0.1:{9000 + int(node_id[-1])}",
|
||||
shard_start=start,
|
||||
shard_end=end,
|
||||
model=SHORT,
|
||||
shard_checksum=None,
|
||||
hardware_profile={},
|
||||
wallet_address=None,
|
||||
score=1.0,
|
||||
hf_repo=MODEL,
|
||||
num_layers=LAYERS,
|
||||
capability=_eval(
|
||||
report,
|
||||
model_matches=lambda reported: reported == MODEL,
|
||||
advertised_model=MODEL,
|
||||
shard_start=start,
|
||||
shard_end=end,
|
||||
),
|
||||
**kwargs,
|
||||
)
|
||||
return entry
|
||||
|
||||
|
||||
def test_route_selection_drops_every_unadmitted_candidate_under_enforce():
|
||||
"Absent, failed, stale and mismatched candidates are all excluded.\n\nTags: routing, tracker"
|
||||
good = _entry("node-1", 0, 31, _report(start=0, end=31))
|
||||
unproven = _entry("node-2", 0, 31, None)
|
||||
failed = _entry("node-3", 0, 31, _report(start=0, end=31, status="failed"))
|
||||
stale = _entry("node-4", 0, 31, _report(start=0, end=31, validated_at=time.time() - 86400))
|
||||
wrong_model = _entry("node-5", 0, 31, _report(start=0, end=31, model_id="someone/else-1b"))
|
||||
|
||||
route, error = _select_route(
|
||||
[unproven, failed, stale, wrong_model, good], 0, 31, policy=POLICY_ENFORCE
|
||||
)
|
||||
assert not error
|
||||
assert [n.node_id for n in route] == ["node-1"]
|
||||
|
||||
only_bad, error = _select_route([unproven, failed, stale], 0, 31, policy=POLICY_ENFORCE)
|
||||
assert only_bad == []
|
||||
assert "no route available" in error
|
||||
|
||||
|
||||
def test_a_node_reassigned_to_a_shard_it_never_proved_stops_routing():
|
||||
"The proof does not travel with a tracker reassignment.\n\nTags: routing, tracker"
|
||||
node = _entry("node-1", 0, 15, _report(start=0, end=15))
|
||||
assert _node_admission(node).state == STATE_ADMITTED
|
||||
|
||||
node.shard_start, node.shard_end = 16, 31 # tracker rebalanced it
|
||||
assert _node_admission(node).state == STATE_SHARD_MISMATCH
|
||||
assert not _node_admission(node).routable_under(POLICY_COMPAT)
|
||||
|
||||
|
||||
def test_admitted_candidates_keep_coverage_first_and_throughput_routing():
|
||||
"Gating removes candidates; among the survivors routing is unchanged.\n\nTags: routing, tracker"
|
||||
head = _entry("node-1", 0, 15, _report(start=0, end=15))
|
||||
slow_tail = _entry(
|
||||
"node-2", 16, 31, _report(start=16, end=31), benchmark_tokens_per_sec=5.0
|
||||
)
|
||||
fast_tail = _entry(
|
||||
"node-3", 16, 31, _report(start=16, end=31), benchmark_tokens_per_sec=50.0
|
||||
)
|
||||
|
||||
route, error = _select_route(
|
||||
[head, slow_tail, fast_tail], 0, 31, policy=POLICY_ENFORCE
|
||||
)
|
||||
assert not error
|
||||
# Coverage first (head, then a tail), and the faster of the two tied tails.
|
||||
assert [n.node_id for n in route] == ["node-1", "node-3"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- over the wire
|
||||
|
||||
|
||||
def test_an_enforcing_tracker_routes_a_proven_node_and_excludes_an_unproven_one():
|
||||
"End to end: a proof is required to appear in a route.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(capability_policy=POLICY_ENFORCE)
|
||||
port = tracker.start()
|
||||
try:
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
_post_json(f"{base}/v1/nodes/register", _registration(9101, start=0, end=15))
|
||||
# A tail that presents no proof at all: the route cannot complete.
|
||||
_post_json(
|
||||
f"{base}/v1/nodes/register",
|
||||
_registration(9102, start=16, end=31, report=None),
|
||||
)
|
||||
|
||||
with pytest.raises(urllib.error.HTTPError) as exc:
|
||||
_get_json(f"{base}/v1/route?model={SHORT}")
|
||||
assert exc.value.code == 503
|
||||
|
||||
# Now the tail proves itself and the same route resolves.
|
||||
_post_json(f"{base}/v1/nodes/register", _registration(9102, start=16, end=31))
|
||||
route = _get_json(f"{base}/v1/route?model={SHORT}")["route"]
|
||||
assert route == ["http://127.0.0.1:9101", "http://127.0.0.1:9102"]
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bad_report",
|
||||
[
|
||||
_report(start=16, end=31), # proves the wrong shard
|
||||
_report(model_id="unrelated/other-7b"), # proves the wrong model
|
||||
_report(status="failed"),
|
||||
_report(validated_at=time.time() - 86400), # stale
|
||||
{"schema_version": 1, "model": {}}, # malformed
|
||||
],
|
||||
ids=["shard-mismatch", "model-mismatch", "failed", "stale", "invalid"],
|
||||
)
|
||||
def test_an_enforcing_tracker_never_routes_a_node_whose_proof_does_not_cover_it(bad_report):
|
||||
"A proof for something else is worth exactly as much as no proof.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(capability_policy=POLICY_ENFORCE)
|
||||
port = tracker.start()
|
||||
try:
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
resp = _post_json(
|
||||
f"{base}/v1/nodes/register",
|
||||
_registration(9111, start=0, end=31, report=bad_report),
|
||||
)
|
||||
# Registration still succeeds — the operator must be able to see the node.
|
||||
assert resp["node_id"]
|
||||
assert resp["capability"]["routable"] is False
|
||||
assert resp["capability"]["state"] != STATE_ADMITTED
|
||||
|
||||
with pytest.raises(urllib.error.HTTPError) as exc:
|
||||
_get_json(f"{base}/v1/route?model={SHORT}")
|
||||
assert exc.value.code in (404, 503)
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_a_compat_tracker_routes_a_legacy_node_that_sends_no_report():
|
||||
"Documented rollout policy: pre-capability nodes keep working under `compat`.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(capability_policy=POLICY_COMPAT)
|
||||
port = tracker.start()
|
||||
try:
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
_post_json(
|
||||
f"{base}/v1/nodes/register",
|
||||
_registration(9121, start=0, end=31, report=None, recipe_id=None, recipe_version=None),
|
||||
)
|
||||
route = _get_json(f"{base}/v1/route?model={SHORT}")["route"]
|
||||
assert route == ["http://127.0.0.1:9121"]
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_a_compat_tracker_still_refuses_a_node_that_presents_a_failed_proof():
|
||||
"Compatibility grandfathers silence, not a proof of failure.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(capability_policy=POLICY_COMPAT)
|
||||
port = tracker.start()
|
||||
try:
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
_post_json(
|
||||
f"{base}/v1/nodes/register",
|
||||
_registration(9131, start=0, end=31, report=_report(start=0, end=31, status="failed")),
|
||||
)
|
||||
with pytest.raises(urllib.error.HTTPError) as exc:
|
||||
_get_json(f"{base}/v1/route?model={SHORT}")
|
||||
assert exc.value.code == 503
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_a_replicated_registration_carries_its_verdict_to_a_follower():
|
||||
"A proven node must not be routable on the leader and dark on every follower.\n\nTags: cluster, routing, tracker"
|
||||
tracker = TrackerServer(capability_policy=POLICY_ENFORCE)
|
||||
proven = _registration(9151, start=0, end=31, report=_report(start=0, end=31))
|
||||
proven["node_id"] = "follower-node-1"
|
||||
unproven = _registration(9152, start=0, end=31, report=None)
|
||||
unproven["node_id"] = "follower-node-2"
|
||||
|
||||
tracker._raft_apply("register", proven)
|
||||
tracker._raft_apply("register", unproven)
|
||||
|
||||
admitted = tracker._registry["follower-node-1"]
|
||||
assert admitted.capability.state == STATE_ADMITTED
|
||||
assert _capability_routable(admitted, POLICY_ENFORCE)
|
||||
|
||||
absent = tracker._registry["follower-node-2"]
|
||||
assert absent.capability.state == STATE_ABSENT
|
||||
assert not _capability_routable(absent, POLICY_ENFORCE)
|
||||
|
||||
|
||||
def test_the_network_map_exposes_the_admission_state_of_every_node():
|
||||
"The operator view answers 'why is my node not routing' without raw internals.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(capability_policy=POLICY_ENFORCE)
|
||||
port = tracker.start()
|
||||
try:
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
_post_json(f"{base}/v1/nodes/register", _registration(9141, start=0, end=15))
|
||||
_post_json(
|
||||
f"{base}/v1/nodes/register",
|
||||
_registration(
|
||||
9142,
|
||||
start=16,
|
||||
end=31,
|
||||
report=_report(
|
||||
start=16,
|
||||
end=31,
|
||||
status="failed",
|
||||
diagnostics=["load failed: token=hf_abcdefghijklmnopqrstuvwx1234"],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
network = _get_json(f"{base}/v1/network/map")
|
||||
assert network["capability_policy"] == POLICY_ENFORCE
|
||||
by_endpoint = {n["endpoint"]: n["capability"] for n in network["nodes"]}
|
||||
|
||||
proven = by_endpoint["http://127.0.0.1:9141"]
|
||||
assert proven["state"] == STATE_ADMITTED
|
||||
assert proven["routable"] is True
|
||||
assert proven["model_id"] == MODEL
|
||||
assert (proven["shard_start"], proven["shard_end"]) == (0, 15)
|
||||
assert proven["recipe_id"] == "baseline"
|
||||
assert proven["device"] == "cpu"
|
||||
|
||||
broken = by_endpoint["http://127.0.0.1:9142"]
|
||||
assert broken["state"] == STATE_FAILED
|
||||
assert broken["routable"] is False
|
||||
assert broken["detail"]
|
||||
|
||||
raw = json.dumps(network)
|
||||
assert "hf_abcdefghijklmnopqrstuvwx1234" not in raw
|
||||
assert "Traceback" not in raw
|
||||
finally:
|
||||
tracker.stop()
|
||||
Reference in New Issue
Block a user