relay working with qwen2.5;
relay anounced on node ready
This commit is contained in:
@@ -30,6 +30,14 @@ HF_HOME=/run/media/popov/d/DEV/models .venv/bin/meshnet-node start --m
|
|||||||
meshnet-node.exe start --tracker http://192.168.0.179:8080 --model Qwen/Qwen2.5-0.5B-Instruct --shard-start 0 --shard-end 20
|
meshnet-node.exe start --tracker http://192.168.0.179:8080 --model Qwen/Qwen2.5-0.5B-Instruct --shard-start 0 --shard-end 20
|
||||||
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model Qwen/Qwen2.5-0.5B-Instruct
|
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model Qwen/Qwen2.5-0.5B-Instruct
|
||||||
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model qwen3.6-35b-a3b --cpu
|
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model qwen3.6-35b-a3b --cpu
|
||||||
|
|
||||||
|
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model qwen3.6-35b-a3b --shard-start 0 --shard-end 21 --node-name gpu-head
|
||||||
|
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model qwen3.6-35b-a3b --shard-start 22 --shard-end 39 --cpu --node-name cpu-tail
|
||||||
|
|
||||||
|
|
||||||
|
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model Qwen/Qwen2.5-0.5B-Instruct --shard-end 20 --node-name gpu-head
|
||||||
|
meshnet-node.exe start --tracker https://meshnet.2.d-popov.com --model Qwen/Qwen2.5-0.5B-Instruct --shard-start 12 --cpu --node-name cpu-tail
|
||||||
|
|
||||||
# win
|
# win
|
||||||
meshnet-node start --tracker http://ai.neuron.d-popov.com --model Qwen/Qwen2.5-0.5B-Instruct --shard-start 10
|
meshnet-node start --tracker http://ai.neuron.d-popov.com --model Qwen/Qwen2.5-0.5B-Instruct --shard-start 10
|
||||||
meshnet-node start --tracker http://192.168.0.179:8081 --model Qwen/Qwen2.5-0.5B-Instruct --shard-start 10
|
meshnet-node start --tracker http://192.168.0.179:8081 --model Qwen/Qwen2.5-0.5B-Instruct --shard-start 10
|
||||||
|
|||||||
@@ -308,7 +308,11 @@ class TorchModelShard:
|
|||||||
self._norm = _final_norm(self.model) if self.is_tail else None
|
self._norm = _final_norm(self.model) if self.is_tail else None
|
||||||
self._lm_head = getattr(self.model, "lm_head", None) if self.is_tail else None
|
self._lm_head = getattr(self.model, "lm_head", None) if self.is_tail else None
|
||||||
# Per-session KV/recurrent-state cache for this shard's layer range.
|
# Per-session KV/recurrent-state cache for this shard's layer range.
|
||||||
self.supports_kv_cache = True
|
# Hybrid/linear-attention models such as Qwen3.6 can dispatch Triton
|
||||||
|
# recurrent-cache kernels when use_cache=True. Those kernels cannot
|
||||||
|
# consume CPU tensors ("Pointer argument cannot be accessed from Triton"),
|
||||||
|
# so CPU shards intentionally stay on the stateless prefill path.
|
||||||
|
self.supports_kv_cache = self.device.type != "cpu"
|
||||||
self.kv_sessions = SessionCacheStore(
|
self.kv_sessions = SessionCacheStore(
|
||||||
max_sessions=int(os.environ.get("MESHNET_KV_MAX_SESSIONS", "8")),
|
max_sessions=int(os.environ.get("MESHNET_KV_MAX_SESSIONS", "8")),
|
||||||
ttl_seconds=float(os.environ.get("MESHNET_KV_TTL_SECONDS", "600")),
|
ttl_seconds=float(os.environ.get("MESHNET_KV_TTL_SECONDS", "600")),
|
||||||
@@ -612,9 +616,13 @@ class TorchModelShard:
|
|||||||
hidden_states, attention_mask, position_ids,
|
hidden_states, attention_mask, position_ids,
|
||||||
start_layer=start_layer, cache=cache, past_len=0,
|
start_layer=start_layer, cache=cache, past_len=0,
|
||||||
)
|
)
|
||||||
except TypeError as exc:
|
except Exception as exc:
|
||||||
|
if not _cache_unsupported_for_shard(exc):
|
||||||
|
raise
|
||||||
# Layers reject cache kwargs (exotic architecture) — disable caching
|
# Layers reject cache kwargs (exotic architecture) — disable caching
|
||||||
# for this backend and stay on the stateless path.
|
# for this backend and stay on the stateless path. Some hybrid
|
||||||
|
# CPU paths also accept cache kwargs but fail at runtime inside
|
||||||
|
# Triton-only kernels; treat those as cache-unsupported too.
|
||||||
self.supports_kv_cache = False
|
self.supports_kv_cache = False
|
||||||
print(f" [node] kv cache unsupported by {self.model_id}: {exc}", flush=True)
|
print(f" [node] kv cache unsupported by {self.model_id}: {exc}", flush=True)
|
||||||
return self._run_layers(
|
return self._run_layers(
|
||||||
@@ -1146,3 +1154,13 @@ def _looks_like_oom(exc: BaseException) -> bool:
|
|||||||
return True
|
return True
|
||||||
current = current.__cause__ or current.__context__
|
current = current.__cause__ or current.__context__
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _cache_unsupported_for_shard(exc: BaseException) -> bool:
|
||||||
|
"""True when a layer failure means session cache is unsupported, not fatal."""
|
||||||
|
text = str(exc).lower()
|
||||||
|
return (
|
||||||
|
isinstance(exc, TypeError)
|
||||||
|
or "pointer argument cannot be accessed from triton" in text
|
||||||
|
or ("triton" in text and "cpu tensor" in text)
|
||||||
|
)
|
||||||
|
|||||||
@@ -140,6 +140,13 @@ def _hardware_label(device: str, gpu_name: str | None = None) -> str:
|
|||||||
return "CPU"
|
return "CPU"
|
||||||
|
|
||||||
|
|
||||||
|
def _relay_ready_line(relay_fields: dict) -> str:
|
||||||
|
relay_addr = relay_fields.get("relay_addr")
|
||||||
|
if not relay_addr:
|
||||||
|
return ""
|
||||||
|
return f" Relay: {relay_addr}\n"
|
||||||
|
|
||||||
|
|
||||||
def _positive_int(value: int | str | None, name: str) -> int | None:
|
def _positive_int(value: int | str | None, name: str) -> int | None:
|
||||||
if value is None or value == "":
|
if value is None or value == "":
|
||||||
return None
|
return None
|
||||||
@@ -917,6 +924,7 @@ def run_startup(
|
|||||||
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, total_layers, quantization)}\n"
|
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, total_layers, quantization)}\n"
|
||||||
f" Quantization: {quantization}\n"
|
f" Quantization: {quantization}\n"
|
||||||
f" Endpoint: {endpoint}\n"
|
f" Endpoint: {endpoint}\n"
|
||||||
|
f"{_relay_ready_line(relay_fields)}"
|
||||||
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
||||||
f" Hardware: {_hardware_label(device, gpu_name)}\n"
|
f" Hardware: {_hardware_label(device, gpu_name)}\n"
|
||||||
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
||||||
@@ -1072,6 +1080,7 @@ def run_startup(
|
|||||||
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, assigned_num_layers, quantization, bytes_per_layer=assigned_bytes_per_layer, safety_fraction=_runtime_shard_safety_fraction(device))}\n"
|
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, assigned_num_layers, quantization, bytes_per_layer=assigned_bytes_per_layer, safety_fraction=_runtime_shard_safety_fraction(device))}\n"
|
||||||
f" Quantization: {quantization}\n"
|
f" Quantization: {quantization}\n"
|
||||||
f" Endpoint: {endpoint}\n"
|
f" Endpoint: {endpoint}\n"
|
||||||
|
f"{_relay_ready_line(relay_fields)}"
|
||||||
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
||||||
f" Hardware: {_hardware_label(device, gpu_name)}\n"
|
f" Hardware: {_hardware_label(device, gpu_name)}\n"
|
||||||
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
||||||
@@ -1237,6 +1246,7 @@ def run_startup(
|
|||||||
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, total_layers, quantization, bytes_per_layer=assignment_bytes_per_layer, safety_fraction=_runtime_shard_safety_fraction(device))}\n"
|
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, total_layers, quantization, bytes_per_layer=assignment_bytes_per_layer, safety_fraction=_runtime_shard_safety_fraction(device))}\n"
|
||||||
f" Quantization: {quantization}\n"
|
f" Quantization: {quantization}\n"
|
||||||
f" Endpoint: {endpoint}\n"
|
f" Endpoint: {endpoint}\n"
|
||||||
|
f"{_relay_ready_line(relay_fields)}"
|
||||||
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
||||||
f" Hardware: {_hardware_label(device, gpu_name)}\n"
|
f" Hardware: {_hardware_label(device, gpu_name)}\n"
|
||||||
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
||||||
@@ -1315,6 +1325,7 @@ def run_startup(
|
|||||||
f" Shard: {shard_label}\n"
|
f" Shard: {shard_label}\n"
|
||||||
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, assigned_total_layers, quantization, bytes_per_layer=assignment_bytes_per_layer, safety_fraction=_runtime_shard_safety_fraction(device))}\n"
|
f" {_shard_budget_line(memory_budget_mb, memory_budget_source, assigned_total_layers, quantization, bytes_per_layer=assignment_bytes_per_layer, safety_fraction=_runtime_shard_safety_fraction(device))}\n"
|
||||||
f" Endpoint: {endpoint}\n"
|
f" Endpoint: {endpoint}\n"
|
||||||
|
f"{_relay_ready_line(relay_fields)}"
|
||||||
f" Node ID: {node_id}\n"
|
f" Node ID: {node_id}\n"
|
||||||
f" Hardware: {hw_str}\n"
|
f" Hardware: {hw_str}\n"
|
||||||
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
||||||
|
|||||||
@@ -141,6 +141,18 @@ def _is_cache_miss_body(body: bytes) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _response_error_snippet(body: bytes, limit: int = 500) -> str:
|
||||||
|
"""Return a compact error string from a downstream JSON/text response body."""
|
||||||
|
try:
|
||||||
|
payload = json.loads(body)
|
||||||
|
if isinstance(payload, dict):
|
||||||
|
message = payload.get("error") or payload.get("detail") or payload
|
||||||
|
return str(message)[:limit]
|
||||||
|
except (json.JSONDecodeError, TypeError, UnicodeDecodeError):
|
||||||
|
pass
|
||||||
|
return body.decode("utf-8", errors="replace")[:limit]
|
||||||
|
|
||||||
|
|
||||||
class _TorchHTTPServer(http.server.HTTPServer):
|
class _TorchHTTPServer(http.server.HTTPServer):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -425,6 +437,12 @@ class _TorchHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
self._send_json(409, {"error": "cache_miss", "detail": str(exc)})
|
self._send_json(409, {"error": "cache_miss", "detail": str(exc)})
|
||||||
return
|
return
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
print(
|
||||||
|
f" [node] forward failed layers={getattr(server.backend, 'shard_start', '?')}-"
|
||||||
|
f"{getattr(server.backend, 'shard_end', '?')} session={session[:8]}: {exc}"
|
||||||
|
f"{self._request_log_suffix()}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
self._send_json(500, {"error": str(exc)})
|
self._send_json(500, {"error": str(exc)})
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -900,11 +918,12 @@ class _TorchHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
if status == 409 and _is_cache_miss_body(resp_body):
|
if status == 409 and _is_cache_miss_body(resp_body):
|
||||||
raise _PipelineCacheMiss(node_url)
|
raise _PipelineCacheMiss(node_url)
|
||||||
if status >= 400:
|
if status >= 400:
|
||||||
|
detail = _response_error_snippet(resp_body)
|
||||||
print(
|
print(
|
||||||
f" [node] relay hop {hop_index} returned {status} from {relay_addr}",
|
f" [node] relay hop {hop_index} returned {status} from {relay_addr}: {detail}",
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
return f"pipeline error at {node_url} via relay: status {status}", None
|
return f"pipeline error at {node_url} via relay: status {status}: {detail}", None
|
||||||
except _PipelineCacheMiss:
|
except _PipelineCacheMiss:
|
||||||
raise
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -929,8 +948,9 @@ class _TorchHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
body = exc.read()
|
body = exc.read()
|
||||||
if exc.code == 409 and _is_cache_miss_body(body):
|
if exc.code == 409 and _is_cache_miss_body(body):
|
||||||
raise _PipelineCacheMiss(node_url) from exc
|
raise _PipelineCacheMiss(node_url) from exc
|
||||||
print(f" [node] pipeline hop {hop_index} failed at {node_url}: {exc}", flush=True)
|
detail = _response_error_snippet(body)
|
||||||
return f"pipeline error at {node_url}: {exc}", None
|
print(f" [node] pipeline hop {hop_index} failed at {node_url}: {exc}: {detail}", flush=True)
|
||||||
|
return f"pipeline error at {node_url}: {exc}: {detail}", None
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f" [node] pipeline hop {hop_index} failed at {node_url}: {exc}", flush=True)
|
print(f" [node] pipeline hop {hop_index} failed at {node_url}: {exc}", flush=True)
|
||||||
return f"pipeline error at {node_url}: {exc}", None
|
return f"pipeline error at {node_url}: {exc}", None
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from meshnet_node.model_backend import (
|
|||||||
SessionCacheStore,
|
SessionCacheStore,
|
||||||
TailTokenResult,
|
TailTokenResult,
|
||||||
TensorPayload,
|
TensorPayload,
|
||||||
|
TorchModelShard,
|
||||||
)
|
)
|
||||||
from meshnet_node.torch_server import TorchNodeServer
|
from meshnet_node.torch_server import TorchNodeServer
|
||||||
|
|
||||||
@@ -98,6 +99,40 @@ def test_drop_removes_session():
|
|||||||
store.lookup("s1")
|
store.lookup("s1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_prefill_cache_triton_cpu_failure_disables_cache_and_retries_stateless():
|
||||||
|
"""CPU shards must recover when hybrid model cache path dispatches Triton."""
|
||||||
|
shard = object.__new__(TorchModelShard)
|
||||||
|
shard.model_id = "fake-hybrid"
|
||||||
|
shard.supports_kv_cache = True
|
||||||
|
shard._effective_start = lambda start_layer=None: 22
|
||||||
|
shard._new_session_cache = lambda: object()
|
||||||
|
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def fake_run_layers(hidden_states, attention_mask, position_ids, *, start_layer=None, cache=None, past_len=0):
|
||||||
|
calls.append({"cache": cache, "past_len": past_len})
|
||||||
|
if cache is not None:
|
||||||
|
raise RuntimeError("Pointer argument cannot be accessed from Triton (cpu tensor?)")
|
||||||
|
return "stateless-ok"
|
||||||
|
|
||||||
|
shard._run_layers = fake_run_layers
|
||||||
|
|
||||||
|
result = TorchModelShard._run_layers_session(
|
||||||
|
shard,
|
||||||
|
hidden_states=object(),
|
||||||
|
attention_mask=None,
|
||||||
|
position_ids=None,
|
||||||
|
session_id="session-1",
|
||||||
|
cache_mode="prefill",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == "stateless-ok"
|
||||||
|
assert shard.supports_kv_cache is False
|
||||||
|
assert len(calls) == 2
|
||||||
|
assert calls[0]["cache"] is not None
|
||||||
|
assert calls[1]["cache"] is None
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# HTTP session protocol with fake cached backends
|
# HTTP session protocol with fake cached backends
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1347,6 +1347,7 @@ def test_public_tracker_model_node_registers_relay_metadata_from_tracker_url_onl
|
|||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
assert "Relay advertised by tracker" in output
|
assert "Relay advertised by tracker" in output
|
||||||
assert "Cross-host pipeline hops WILL time out" not in output
|
assert "Cross-host pipeline hops WILL time out" not in output
|
||||||
|
assert f" Relay: {registered['relay_addr']}" in output
|
||||||
|
|
||||||
|
|
||||||
def test_public_tracker_relay_suppresses_virtual_ip_warning(
|
def test_public_tracker_relay_suppresses_virtual_ip_warning(
|
||||||
|
|||||||
Reference in New Issue
Block a user