187 lines
5.9 KiB
Python
187 lines
5.9 KiB
Python
"""Tests for the GGUF backend adapter and recipe-gated startup seam."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
from meshnet_node.gguf_backend import GgufNodeBackend, build_gguf_backend
|
|
from meshnet_node.model_backend import TailTokenResult, TensorPayload
|
|
from meshnet_node.recipe_manifest import DEFAULT_RECIPE_ID, load_recipe_manifest
|
|
from meshnet_node.startup import _gguf_backend_for_recipe
|
|
|
|
|
|
class _RecordingTransport:
|
|
def __init__(self) -> None:
|
|
self.calls: list[tuple[str, tuple, dict]] = []
|
|
|
|
def encode_prompt(self, prompt: str, session_id: str | None = None):
|
|
self.calls.append(("encode_prompt", (prompt, session_id), {}))
|
|
return TensorPayload(
|
|
body=b"\x00" * 16,
|
|
shape=[1, 2, 4],
|
|
attention_mask_header=None,
|
|
position_ids_header=None,
|
|
)
|
|
|
|
def encode_next_token(self, token_id: int, session_id: str):
|
|
self.calls.append(("encode_next_token", (token_id, session_id), {}))
|
|
return TensorPayload(
|
|
body=b"\x00" * 8,
|
|
shape=[1, 1, 4],
|
|
attention_mask_header=None,
|
|
position_ids_header=None,
|
|
past_len=2,
|
|
)
|
|
|
|
def forward_bytes(
|
|
self,
|
|
body: bytes,
|
|
shape: list[int],
|
|
attention_mask_header: str | None,
|
|
position_ids_header: str | None,
|
|
*,
|
|
start_layer: int | None = None,
|
|
session_id: str | None = None,
|
|
cache_mode: str | None = None,
|
|
past_len: int | None = None,
|
|
):
|
|
self.calls.append(
|
|
(
|
|
"forward_bytes",
|
|
(body, tuple(shape), attention_mask_header, position_ids_header),
|
|
{
|
|
"start_layer": start_layer,
|
|
"session_id": session_id,
|
|
"cache_mode": cache_mode,
|
|
"past_len": past_len,
|
|
},
|
|
)
|
|
)
|
|
if cache_mode == "decode":
|
|
return TailTokenResult(text=" done", token_id=17)
|
|
return TensorPayload(
|
|
body=b"\x00" * 16,
|
|
shape=[1, 2, 4],
|
|
attention_mask_header=attention_mask_header,
|
|
position_ids_header=position_ids_header,
|
|
past_len=past_len,
|
|
)
|
|
|
|
def decode_tail_token(self, hidden_states):
|
|
self.calls.append(("decode_tail_token", (hidden_states.shape,), {}))
|
|
return TailTokenResult(text=" tail", token_id=19)
|
|
|
|
def generate_text(self, messages, max_new_tokens=5120, temperature=1.0, top_p=1.0):
|
|
self.calls.append(("generate_text", (tuple(messages), max_new_tokens, temperature, top_p), {}))
|
|
return "ok"
|
|
|
|
def generate_text_streaming(self, messages, max_new_tokens=5120, temperature=1.0, top_p=1.0):
|
|
self.calls.append(("generate_text_streaming", (tuple(messages), max_new_tokens, temperature, top_p), {}))
|
|
yield "ok"
|
|
|
|
def count_prompt_tokens(self, messages):
|
|
self.calls.append(("count_prompt_tokens", (tuple(messages),), {}))
|
|
return 3
|
|
|
|
def count_text_tokens(self, text):
|
|
self.calls.append(("count_text_tokens", (text,), {}))
|
|
return 2
|
|
|
|
def eos_token_ids(self):
|
|
self.calls.append(("eos_token_ids", (), {}))
|
|
return [19]
|
|
|
|
def release_session(self, session_id: str) -> None:
|
|
self.calls.append(("release_session", (session_id,), {}))
|
|
|
|
|
|
def test_build_gguf_backend_delegates_to_transport():
|
|
transport = _RecordingTransport()
|
|
backend = build_gguf_backend(
|
|
model_id="meshnet/native-model",
|
|
shard_start=0,
|
|
shard_end=1,
|
|
quantization="bfloat16",
|
|
transport=transport,
|
|
total_layers=2,
|
|
device_type="cpu",
|
|
)
|
|
|
|
assert isinstance(backend, GgufNodeBackend)
|
|
assert backend.backend_id == "llama.cpp"
|
|
assert backend.is_head is True
|
|
assert backend.is_tail is True
|
|
assert backend.model.config.to_dict()["architecture_adapter"] == "dense-llama"
|
|
assert backend.loaded_tensor_names[0] == "blk.0.weight"
|
|
|
|
prompt = backend.encode_prompt("hello", session_id="session-1")
|
|
assert prompt.shape == [1, 2, 4]
|
|
|
|
decode = backend.forward_bytes(
|
|
b"\x00" * 16,
|
|
[1, 2, 4],
|
|
None,
|
|
None,
|
|
session_id="session-1",
|
|
cache_mode="decode",
|
|
past_len=2,
|
|
)
|
|
assert isinstance(decode, TailTokenResult)
|
|
assert decode.token_id == 17
|
|
|
|
backend.release_session("session-1")
|
|
|
|
assert [call[0] for call in transport.calls] == [
|
|
"encode_prompt",
|
|
"forward_bytes",
|
|
"release_session",
|
|
]
|
|
assert transport.calls[0][1] == ("hello", "session-1")
|
|
assert transport.calls[1][2]["cache_mode"] == "decode"
|
|
assert transport.calls[1][2]["past_len"] == 2
|
|
|
|
|
|
def test_recipe_gates_native_backend_selection(monkeypatch):
|
|
manifest = load_recipe_manifest()
|
|
torch_recipe = manifest.require(DEFAULT_RECIPE_ID)
|
|
native_recipe = manifest.require("llama-cpp-native")
|
|
|
|
sentinel_backend = object()
|
|
calls: list[dict] = []
|
|
|
|
def fake_build_gguf_backend(**kwargs):
|
|
calls.append(kwargs)
|
|
return sentinel_backend
|
|
|
|
monkeypatch.setattr(
|
|
"meshnet_node.startup.build_gguf_backend",
|
|
fake_build_gguf_backend,
|
|
)
|
|
|
|
assert _gguf_backend_for_recipe(
|
|
torch_recipe,
|
|
model_id="meshnet/native-model",
|
|
shard_start=0,
|
|
shard_end=1,
|
|
quantization="bfloat16",
|
|
total_layers=2,
|
|
device="cpu",
|
|
) is None
|
|
|
|
backend = _gguf_backend_for_recipe(
|
|
native_recipe,
|
|
model_id="meshnet/native-model",
|
|
shard_start=0,
|
|
shard_end=1,
|
|
quantization="bfloat16",
|
|
total_layers=2,
|
|
device="cpu",
|
|
)
|
|
|
|
assert backend is sentinel_backend
|
|
assert calls[0]["model_id"] == "meshnet/native-model"
|
|
assert calls[0]["shard_start"] == 0
|
|
assert calls[0]["shard_end"] == 1
|
|
assert calls[0]["quantization"] == "bfloat16"
|
|
assert calls[0]["total_layers"] == 2
|