Compare commits
5 Commits
e8ef2fd222
...
ralph/dgr-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d904c40f66 | ||
|
|
30dcf953fe | ||
|
|
efec84efef | ||
|
|
09af5c47f8 | ||
|
|
4cae4a6c5c |
@@ -3,7 +3,8 @@
|
||||
- [Product selling points](product-selling-points.md) — key differentiators and landing page angles for neuron-tai
|
||||
- [User profile](user-profile.md) — who Dobromir is and how to work with him
|
||||
- [Project status](project-status.md) — 35/35 stories done; alpha hardening next
|
||||
- **Alpha hardening** — `.scratch/alpha-hardening/` (22 issues, ADRs 0016–0019, [README](../.scratch/alpha-hardening/README.md), [handoff](../.scratch/alpha-hardening/handoff.md))
|
||||
- **Alpha hardening** — `.scratch/alpha-hardening/` (22 issues, ADRs 0016–0019, [README](../../.scratch/alpha-hardening/README.md), [handoff](../../.scratch/alpha-hardening/handoff.md))
|
||||
- [Alpha hardening navigation](alpha-hardening-navigation.md) — locked fraud/auth decisions, Bucket-1 order, handoff pointers
|
||||
- **Node capability admission** — `.scratch/node-capability-admission/` (P0 plan: generic doctor/real-forward validation, fail-closed readiness, tracker admission gate; [PRD](../.scratch/node-capability-admission/PRD.md), [README](../.scratch/node-capability-admission/README.md), ADR-0023)
|
||||
- **Node capability admission** — `.scratch/node-capability-admission/` (P0 plan: generic doctor/real-forward validation, fail-closed readiness, tracker admission gate; [PRD](../../.scratch/node-capability-admission/PRD.md), [README](../../.scratch/node-capability-admission/README.md), ADR-0023)
|
||||
- **Distributed relay performance** — relay `/rpc` requester sockets are persistent per Route Session and Activation Seam as of 2026-07-10; `request_id` remains unique per activation while `X-Meshnet-Session` remains stable for KV state. Next low-risk priorities: persistent direct/loopback HTTP, seam byte/latency telemetry, then trace-driven zstd tuning.
|
||||
- **Distributed GGUF direction** — benchmark-gated native runtime: compare controlled Transformers/safetensors and whole-model llama.cpp lanes before expensive work; ship only for measured speed or model-fit advantage. Public parallelism is contiguous Shards in an Inference Route; concurrency comes from per-node continuous batching across isolated Route Sessions, while tensor/expert collectives stay inside optional trusted composite providers. Native data plane uses versioned Protobuf over long-lived gRPC/HTTP2 seam streams, with existing relay carrying the same opaque frames when needed. llama.cpp/GGML remains the substrate behind a project-owned standalone worker and small pinned fork; vLLM is an optional complete managed provider and concept donor, not a fork. Nakshatra, `prima.cpp`, `llama-gguf`, LiGGUF and historical GPUStack are source/test donors only. Active plan: [README](../../.scratch/distributed-gguf-runtime/README.md), [architecture](../../.scratch/distributed-gguf-runtime/architecture.md), [PRD](../../.scratch/distributed-gguf-runtime/PRD.md), [Ralph backlog](../../.scratch/distributed-gguf-runtime/prd.json). Research: [landscape](../../docs/research/distributed-gguf-landscape.md), [GitHub follow-up](../../docs/research/distributed-gguf-github-followup.md), [vLLM](../../docs/research/vllm-distributed-gguf-assessment.md).
|
||||
|
||||
15
.codex/hooks.json
Normal file
15
.codex/hooks.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash -c 'SRC=\"/mnt/d/DEV/workspace/REPOS/git.d-popov.com/neuron-tai/.claude/memory\" && DST=\"/home/dev/.claude/projects/-mnt-d-DEV-workspace-REPOS-git-d-popov-com-neuron-tai/memory\" && mkdir -p \"$DST\" && rsync -a \"$SRC/\" \"$DST/\" 2>/dev/null; true'"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
12
.ralph-tui/config.toml
Normal file
12
.ralph-tui/config.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Ralph TUI Configuration
|
||||
# Generated by setup wizard
|
||||
# See: ralph-tui config help
|
||||
|
||||
configVersion = "2.1"
|
||||
tracker = "json"
|
||||
agent = "opencode"
|
||||
maxIterations = 0
|
||||
autoCommit = true
|
||||
|
||||
[trackerOptions]
|
||||
[agentOptions]
|
||||
@@ -5,7 +5,7 @@ Pre-release alpha audit + grilling (2026-07-04). Bucket 1 trust-boundary blocker
|
||||
**Launch-readiness grilling (2026-07-06):** locked plan is devnet dev/test run now, then real mainnet SOL/USDT for the first cohort — friends (API clients) + hired VPS/VPC hosts (own test infra, not third-party volunteers, stake-free). No new public token; TAI stays dormant per ADR-0002's existing volume/legal gates. Two new issues came out of this session:
|
||||
|
||||
- **[21 — Honest-noise calibration corpus](./issues/21-honest-noise-calibration-corpus.md)** — rescoped from "prod gate" to a hard **alpha-release blocker**. `Status: ready-for-human` — engineering (audit.py raw divergence, tracker dispatch endpoint, SQLite corpus, p99 envelope) done 2026-07-06; blocked on a human running the calibration job against the real hired-VPS fleet before launch.
|
||||
- **[23 — Dynamic HF-benchmarked pricing](./issues/23-dynamic-hf-pricing.md)** — new, high priority but not a release blocker. `Status: done` — engineering complete 2026-07-06 (hf_pricing.py, opt-in daily refresh loop, GET /v1/pricing/hf/history); real `hf_aliases` curation per model is a follow-up human sign-off, not a completion blocker.
|
||||
- **[23 — Dynamic HF-benchmarked pricing](./issues/23-dynamic-hf-pricing_completed.md)** — new, high priority but not a release blocker. `Status: done` — engineering complete 2026-07-06 (hf_pricing.py, opt-in daily refresh loop, GET /v1/pricing/hf/history); real `hf_aliases` curation per model is a follow-up human sign-off, not a completion blocker.
|
||||
|
||||
Locked scope: one settlement tracker, open node join, devnet mock-USDT, reputation carries forward → fraud must be bounded. See [ADR-0016](../../docs/adr/0016-alpha-scope-and-known-limitations.md).
|
||||
|
||||
@@ -41,22 +41,22 @@ Locked scope: one settlement tracker, open node join, devnet mock-USDT, reputati
|
||||
|
||||
| Order | Issue | ID | Depends on |
|
||||
|---|---|---|---|
|
||||
| 1 | [Unified auth boundary](./issues/02-a2-unified-auth-boundary.md) + [Validator service token](./issues/20-validator-service-token.md) | A2, — | — |
|
||||
| 2 | [C1 hive gossip auth enforcement](./issues/01-c1-gossip-auth.md) | C1 | 02 |
|
||||
| 3 | [Persist strike/ban/reputation](./issues/05-a1-a5-persist-strike-ban-reputation.md) | A1/A5 | 02 |
|
||||
| 4 | [Starting credit 0 + spend cap](./issues/03-c5-starting-credit-zero.md) | C5, M1 | 02 |
|
||||
| 5 | [Tracker-authoritative accounting](./issues/04-h2-tracker-authoritative-accounting.md) | H2 | 02 |
|
||||
| 6 | [Wallet binding proof](./issues/11-c6-wallet-binding-proof.md) | C6 | 02, 03 |
|
||||
| 1 | [Unified auth boundary](./issues/02-a2-unified-auth-boundary_completed.md) + [Validator service token](./issues/20-validator-service-token_completed.md) | A2, — | — |
|
||||
| 2 | [C1 hive gossip auth enforcement](./issues/01-c1-gossip-auth_completed.md) | C1 | 02 |
|
||||
| 3 | [Persist strike/ban/reputation](./issues/05-a1-a5-persist-strike-ban-reputation_completed.md) | A1/A5 | 02 |
|
||||
| 4 | [Starting credit 0 + spend cap](./issues/03-c5-starting-credit-zero_completed.md) | C5, M1 | 02 |
|
||||
| 5 | [Tracker-authoritative accounting](./issues/04-h2-tracker-authoritative-accounting_completed.md) | H2 | 02 |
|
||||
| 6 | [Wallet binding proof](./issues/11-c6-wallet-binding-proof_completed.md) | C6 | 02, 03 |
|
||||
|
||||
### Phase 2 — Fraud arc (after Phase 1)
|
||||
|
||||
| Order | Issue | Depends on |
|
||||
|---|---|---|
|
||||
| 6 | [TOPLOC integration](./issues/06-fraud-toploc-integration.md) | 05 |
|
||||
| 7 | [Commitment + bisection blame](./issues/07-fraud-commitment-bisection-blame.md) | 06 |
|
||||
| 8 | [Reputation model](./issues/08-fraud-reputation-model-persistence.md) | 05, 07 |
|
||||
| 9 | [Routing + adaptive audit](./issues/09-fraud-reputation-routing-adaptive-audit.md) | 08 |
|
||||
| 10 | [Penalty calibration wiring](./issues/10-fraud-penalty-calibration-wiring.md) | 07, 08, 02 |
|
||||
| 6 | [TOPLOC integration](./issues/06-fraud-toploc-integration_completed.md) | 05 |
|
||||
| 7 | [Commitment + bisection blame](./issues/07-fraud-commitment-bisection-blame_completed.md) | 06 |
|
||||
| 8 | [Reputation model](./issues/08-fraud-reputation-model-persistence_completed.md) | 05, 07 |
|
||||
| 9 | [Routing + adaptive audit](./issues/09-fraud-reputation-routing-adaptive-audit_completed.md) | 08 |
|
||||
| 10 | [Penalty calibration wiring](./issues/10-fraud-penalty-calibration-wiring_completed.md) | 07, 08, 02 |
|
||||
|
||||
**Prod gate:** [21 honest-noise calibration corpus](./issues/21-honest-noise-calibration-corpus.md) must complete before enabling production TOPLOC audit thresholds (issues 09–10 in prod). Dev/staging TOPLOC wiring (06–08) may proceed in parallel.
|
||||
|
||||
@@ -73,11 +73,11 @@ Locked scope: one settlement tracker, open node join, devnet mock-USDT, reputati
|
||||
|
||||
| Issue |
|
||||
|---|
|
||||
| [16 US-006 + fraud issue reconciliation](./issues/16-doc-us006-reconciliation.md) |
|
||||
| [16 US-006 + fraud issue reconciliation](./issues/16-doc-us006-reconciliation_completed.md) |
|
||||
| [17 Duplicate US-020 dedup](./issues/17-doc-duplicate-us020-dedup.md) |
|
||||
| [18 Operational runbooks](./issues/18-doc-operational-runbooks.md) |
|
||||
| [19 Cryptography + test env](./issues/19-doc-cryptography-test-env.md) |
|
||||
| [22 MEMORY + project-status index](./issues/22-doc-memory-project-status.md) (done) |
|
||||
| [18 Operational runbooks](./issues/18-doc-operational-runbooks_completed.md) |
|
||||
| [19 Cryptography + test env](./issues/19-doc-cryptography-test-env_completed.md) |
|
||||
| [22 MEMORY + project-status index](./issues/22-doc-memory-project-status_completed.md) (done) |
|
||||
| [21 Honest-noise calibration corpus](./issues/21-honest-noise-calibration-corpus.md) (ops; prod gate for audits) |
|
||||
|
||||
### Phase 5 — Distributed-inference performance (post-routing-fix)
|
||||
|
||||
@@ -38,4 +38,4 @@ Implement per ADR-0017 §3 using the auth helper/config from issue 02: shared hi
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `02-a2-unified-auth-boundary.md` — owns shared auth middleware/config. Implement in the same PR if simpler.
|
||||
- `02-a2-unified-auth-boundary_completed.md` — owns shared auth middleware/config. Implement in the same PR if simpler.
|
||||
@@ -16,7 +16,7 @@ Replace header-presence stubs with a single auth middleware that resolves API ke
|
||||
- `packages/tracker/meshnet_tracker/server.py` — `_session_account` (~2468+), `_handle_admin_accounts` (~2588–2608) — H4
|
||||
- `packages/tracker/meshnet_tracker/accounts.py` — `session_account()`, `create_session()` only (session store; not handler wiring)
|
||||
|
||||
Per ADR-0017 §4: forfeit → validator or admin; benchmark → admin; billing summary/settlements/registry wallets → admin session. Include the validator service token shape from `20-validator-service-token.md` in the same implementation if practical.
|
||||
Per ADR-0017 §4: forfeit → validator or admin; benchmark → admin; billing summary/settlements/registry wallets → admin session. Include the validator service token shape from `20-validator-service-token_completed.md` in the same implementation if practical.
|
||||
|
||||
## Test-first
|
||||
|
||||
@@ -39,8 +39,8 @@ Per ADR-0017 §4: forfeit → validator or admin; benchmark → admin; billing s
|
||||
|
||||
## Related
|
||||
|
||||
- `20-validator-service-token.md` — checklist for validator service token format, rotation, forfeit auth
|
||||
- `20-validator-service-token_completed.md` — checklist for validator service token format, rotation, forfeit auth
|
||||
|
||||
## Blocked by
|
||||
|
||||
None. This issue should land before `01-c1-gossip-auth.md`.
|
||||
None. This issue should land before `01-c1-gossip-auth_completed.md`.
|
||||
@@ -35,4 +35,4 @@ Per ADR-0017 §2 and ADR-0016 §3.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `02-a2-unified-auth-boundary.md` (admin credit path secured)
|
||||
- `02-a2-unified-auth-boundary_completed.md` (admin credit path secured)
|
||||
@@ -35,4 +35,4 @@ Accounting fraud = inflating tokens or shard span. Per ADR-0018 §5.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `02-a2-unified-auth-boundary.md`
|
||||
- `02-a2-unified-auth-boundary_completed.md`
|
||||
@@ -37,4 +37,4 @@ Include fields for: `strike_count`, `banned`, `completed_job_count`, graduated *
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `02-a2-unified-auth-boundary.md`
|
||||
- `02-a2-unified-auth-boundary_completed.md`
|
||||
@@ -42,6 +42,6 @@ Pin one canonical precision/quantization per model preset. Add `toploc` to valid
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `05-a1-a5-persist-strike-ban-reputation.md`
|
||||
- `05-a1-a5-persist-strike-ban-reputation_completed.md`
|
||||
|
||||
**Prod gate:** do not enable production audit thresholds until `21-honest-noise-calibration-corpus.md` completes (see README Phase 2 note).
|
||||
@@ -32,4 +32,4 @@ On audit selection, require nodes to supply TOPLOC-style fingerprints of **outpu
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `06-fraud-toploc-integration.md`
|
||||
- `06-fraud-toploc-integration_completed.md`
|
||||
@@ -35,5 +35,5 @@ Implement graduated reputation per ADR-0018 §6: score derives only from tracker
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `05-a1-a5-persist-strike-ban-reputation.md`
|
||||
- `07-fraud-commitment-bisection-blame.md` (audit outcomes feed reputation)
|
||||
- `05-a1-a5-persist-strike-ban-reputation_completed.md`
|
||||
- `07-fraud-commitment-bisection-blame_completed.md` (audit outcomes feed reputation)
|
||||
@@ -35,4 +35,4 @@ Audit selection must be unpredictable at request time (tracker RNG after commitm
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `08-fraud-reputation-model-persistence.md`
|
||||
- `08-fraud-reputation-model-persistence_completed.md`
|
||||
@@ -37,6 +37,6 @@ Per ADR-0018: **full pending forfeiture** is primary penalty; ×0.8 is routing d
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `07-fraud-commitment-bisection-blame.md`
|
||||
- `08-fraud-reputation-model-persistence.md`
|
||||
- `02-a2-unified-auth-boundary.md`
|
||||
- `07-fraud-commitment-bisection-blame_completed.md`
|
||||
- `08-fraud-reputation-model-persistence_completed.md`
|
||||
- `02-a2-unified-auth-boundary_completed.md`
|
||||
@@ -33,5 +33,5 @@ Require signed message from wallet pubkey (ed25519 via `cryptography` / solders)
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `02-a2-unified-auth-boundary.md`
|
||||
- `03-c5-starting-credit-zero.md`
|
||||
- `02-a2-unified-auth-boundary_completed.md`
|
||||
- `03-c5-starting-credit-zero_completed.md`
|
||||
@@ -9,7 +9,7 @@ Reconcile stale US-006 (Solana testnet stake contracts) with ADR-0015/0016 devne
|
||||
Also reconcile legacy fraud issues with the alpha-hardening fraud arc:
|
||||
|
||||
- `docs/issues/07-fraud-detection-slash.md` — on-chain stake slash model superseded by pending-balance forfeiture + TOPLOC (ADR-0018)
|
||||
- `docs/issues/34-forfeiture-penalty.md` — partially implemented; remaining fraud work lives in `.scratch/alpha-hardening/issues/06-fraud-toploc-integration.md` through `10-fraud-penalty-calibration-wiring.md`
|
||||
- `docs/issues/34-forfeiture-penalty.md` — partially implemented; remaining fraud work lives in `.scratch/alpha-hardening/issues/06-fraud-toploc-integration_completed.md` through `10-fraud-penalty-calibration-wiring_completed.md`
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
@@ -45,8 +45,8 @@ Per [ADR-0017 §4](../../docs/adr/0017-tracker-authentication-and-authorization.
|
||||
|
||||
## Related
|
||||
|
||||
- `02-a2-unified-auth-boundary.md` — middleware + role checks
|
||||
- `02-a2-unified-auth-boundary_completed.md` — middleware + role checks
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `02-a2-unified-auth-boundary.md`
|
||||
- `02-a2-unified-auth-boundary_completed.md`
|
||||
@@ -44,7 +44,7 @@ Research anchor: `.scratch/alpha-hardening/research-verifiable-inference.md` §8
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `06-fraud-toploc-integration.md` (TOPLOC wired; calibration uses same primitive) — done
|
||||
- `06-fraud-toploc-integration_completed.md` (TOPLOC wired; calibration uses same primitive) — done
|
||||
|
||||
## Blocks (prod gate)
|
||||
|
||||
|
||||
@@ -1,177 +1,121 @@
|
||||
# ADR-0020: Distributed GGUF/llama.cpp Runtime With Per-Shard Local KV
|
||||
# ADR-0020: Lean Native Distributed GGUF Runtime
|
||||
|
||||
Status: Proposed
|
||||
Status: Accepted
|
||||
Date: 2026-07-13
|
||||
|
||||
## Context
|
||||
|
||||
The project currently uses PyTorch/Transformers for real model shards. That decision was captured in ADR-0001 because llama.cpp RPC at the time required the primary node to load the full model and distribute weights to workers, which conflicted with the desired model where nodes independently hold shards.
|
||||
The project currently uses Transformers/safetensors as its real model execution backend. This provides broad architecture coverage and a correctness reference, but reported and observed consumer CPU/GPU inference performance motivates evaluating llama.cpp/GGML and quantized GGUF.
|
||||
|
||||
We now want to serve very large open models, including GLM-5.2 and Ornith-class MoE models, over a torrent-like inference marketplace. CPU and mixed consumer hardware matter. LM Studio and llama.cpp demonstrate much better CPU/GGUF performance than our current PyTorch CPU path. The user also has a personal relationship with Georgi Gerganov, making upstream collaboration plausible.
|
||||
The product objective is not merely local GGUF serving. It is performant concurrent inference for top open models whose weights do not fit on one consumer node. The project already owns the Tracker, Inference Route, Route Session, Activation Seam, local Hot KV State, relay/direct transport, cancellation, telemetry, billing, and capability admission.
|
||||
|
||||
The current distributed PyTorch path is not yet production-grade: it recomputes the full growing sequence for every output token and disables KV cache inside manual layer calls. It sends hidden activations across seams, not KV, but those activations currently cover the full sequence every decode step.
|
||||
Research audited llama.cpp RPC, GPUStack/llama-box, Nakshatra, prima.cpp, llama-gguf, LiGGUF, vLLM and its GGUF plugin, Petals, exo, and related projects. No repository provides the complete public-network contract. llama.cpp is the strongest GGUF execution substrate. vLLM has mature managed-cluster parallelism and scheduling concepts but its PP/TP/EP runtime assumes a static trusted distributed world and is unsuitable as the public Shard runtime.
|
||||
|
||||
The project must remain lean and avoid combining several half-integrated inference control planes.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a distributed GGUF/llama.cpp runtime track while keeping PyTorch as the reference and fast-architecture backend.
|
||||
### Primary native runtime
|
||||
|
||||
The runtime model is:
|
||||
Use llama.cpp/GGML through one standalone C++ Shard worker and a small exact-commit patch stack.
|
||||
|
||||
- GGUF/model artifacts are distributed through torrent/content-addressed storage.
|
||||
- Nodes independently acquire and verify artifacts; no root node streams model weights to workers at session start.
|
||||
- Tracker chooses a sticky route covering all layers.
|
||||
- Each node owns hot KV/state for the layers it executes.
|
||||
- Prefill sends chunked activations through the route and builds local per-shard KV.
|
||||
- Decode sends one-step activations through the route and appends local KV at every shard.
|
||||
- Cache/CDN servers store cold artifacts and optional prefix/session snapshots, not hot per-token KV.
|
||||
- Context is capped at 128K for the first serious product path.
|
||||
The patch scope is limited to:
|
||||
|
||||
## Technical Framework
|
||||
- Range-aware GGUF tensor ownership/loading.
|
||||
- Architecture-defined intermediate boundary input/output.
|
||||
- Intermediate output before tail normalization/head.
|
||||
- Layer-filtered KV and external session-to-sequence mapping.
|
||||
|
||||
The design separates five planes:
|
||||
Meshnet networking, routing, admission, billing, telemetry, and work evidence stay outside llama.cpp.
|
||||
|
||||
- **Control plane**: tracker registry, coverage map, route selection, session lifecycle, telemetry, billing, and audit.
|
||||
- **Artifact plane**: Shard Swarms, GGUF/safetensors/tokenizer files, manifests, hashes, and local node storage.
|
||||
- **Execution plane**: active Inference Route, chunked prefill, one-step decode, and hidden-state movement across activation seams.
|
||||
- **Session state plane**: per-shard Hot KV State on route nodes, plus optional Prefix Snapshots outside the hot loop.
|
||||
- **Economics/trust plane**: reward accounting, validation events, slash proofs, public/private route policy.
|
||||
Nakshatra, prima.cpp, llama-gguf, LiGGUF, and historical GPUStack are source/test donors only. Their repositories are not runtime dependencies.
|
||||
|
||||
Hard invariants:
|
||||
### Distributed parallelism
|
||||
|
||||
1. Public-network Shards are contiguous layer ranges.
|
||||
2. Hot KV State is local to the node serving that Shard in that Route Session.
|
||||
3. Artifact distribution and route execution are separate systems.
|
||||
4. Decode seam payload must be `O(hidden_size)`.
|
||||
5. Prefill may be `O(sequence_length * hidden_size)`, but only in bounded chunks.
|
||||
6. The tracker chooses routes; nodes do not negotiate route topology peer-to-peer.
|
||||
7. Model/backend-specific cache internals stay behind backend capability reports.
|
||||
8. PyTorch remains the correctness/reference backend while llama.cpp/GGUF becomes the performance backend.
|
||||
9. Streaming responses are preferred when feasible; Generation Telemetry is always required.
|
||||
The first public-network primitive is layer/pipeline parallelism through contiguous Shards in an Inference Route.
|
||||
|
||||
The full challenge register is in [technical-challenges.md](./technical-challenges.md). The open decision gates are in [decision-framework.md](./decision-framework.md).
|
||||
Per-node continuous batching combines decode steps from compatible active Route Sessions. Multiple complete routes provide data parallelism.
|
||||
|
||||
Resolved gate:
|
||||
Tensor and expert parallel collectives may later operate inside one trusted composite node or managed cluster represented as one provider. They are not public WAN routing primitives.
|
||||
|
||||
- Public-network Shards are layer ranges. Tensor-parallel/ring execution belongs inside a trusted node, colocated pod, or future composite node abstraction, not as the v1 public routing primitive.
|
||||
- Hot KV State is local to each route node for the Shard it serves. Cache servers may store Prefix Snapshots, but they are not part of the per-token decode path.
|
||||
- Distributed Route Session and Hot KV State semantics will be proven in the PyTorch route before llama.cpp/GGUF is extended for layer-boundary execution.
|
||||
- Streaming responses are preferred when feasible. Realtime Generation Telemetry is required so clients can see phase, generated token count, and tokens/sec even during prefill or non-streaming fallback paths.
|
||||
- llama.cpp/GGUF work targets upstreamable `libllama`/ggml hooks. A prototype fork is acceptable for exploration, but a permanent fork is not the plan.
|
||||
- Model targeting is two-tiered: use a small llama.cpp-supported GGUF model for the first protocol smoke test, then use `deepseek-ai/DeepSeek-V4-Flash` as the first serious large-model target. GLM-5.2 and Ornith remain later support audits.
|
||||
- Alpha fails Route Sessions on route-node loss instead of attempting automatic route repair. Repair requires compatible Prefix Snapshots and is a later capability.
|
||||
- v1 activation transfer stays on binary HTTP as defined by ADR-0008. QUIC/WebRTC/custom transport can be introduced later behind the same activation protocol.
|
||||
### Transport
|
||||
|
||||
## Non-Goals
|
||||
Use gRPC over HTTP/2 with Protocol Buffers for the native Python/C++ Shard data plane.
|
||||
|
||||
- Do not put remote cache servers in the per-token hot KV path.
|
||||
- Do not require every node to hold the full model.
|
||||
- Do not fork llama.cpp long-term if upstream APIs can support the needed layer-boundary hooks.
|
||||
- Do not target GLM-5.2 or Ornith first; prove the route/KV protocol on a simpler well-supported GGUF model, then target DeepSeek-V4-Flash as the first serious large model.
|
||||
- One long-lived bidirectional stream per Route Session Activation Seam.
|
||||
- Deadlines, cancellation, flow control, TLS/authentication hooks, structured status, and generated schemas.
|
||||
- Bounded chunks for prefill and a small decode fast path.
|
||||
- Existing relay infrastructure may carry the same versioned protobuf frames as opaque binary when direct connectivity is unavailable.
|
||||
- OpenAI client APIs remain HTTP/SSE; existing Tracker APIs remain unchanged.
|
||||
|
||||
## Options Considered
|
||||
The boundary payload is a versioned named-tensor bundle because architecture boundaries may require more than one tensor.
|
||||
|
||||
### A. Keep PyTorch-only distributed inference
|
||||
### vLLM
|
||||
|
||||
Pros:
|
||||
Do not fork vLLM for public distributed Shards and do not transplant PagedAttention, Torch process groups, or the vLLM GGUF plugin into the llama.cpp worker.
|
||||
|
||||
- Easy access to new Hugging Face architectures.
|
||||
- Transformers has mature single-process KV semantics.
|
||||
- Existing code already loads shards.
|
||||
Allow unmodified vLLM as an optional whole-model backend or managed TP/PP/EP cluster represented as one logical provider.
|
||||
|
||||
Cons:
|
||||
Adapt only small control-plane concepts:
|
||||
|
||||
- CPU inference is much slower than llama.cpp/GGUF.
|
||||
- Current distributed path bypasses `generate()` and disables cache.
|
||||
- Quantized GGUF ecosystem and LM Studio users are outside the runtime.
|
||||
- Named intermediate bundles.
|
||||
- Continuous batching and request ownership.
|
||||
- Versioned cache-transfer compatibility fingerprints.
|
||||
- Explicit transfer failure/abort lifecycle.
|
||||
- Load telemetry and fair tie-breaking.
|
||||
|
||||
### B. Use llama.cpp only as a full local model backend
|
||||
### Benchmark gate
|
||||
|
||||
Pros:
|
||||
GGUF performance is a hypothesis. Before expensive native work, compare the current Transformers/safetensors recipe with whole-model llama.cpp on controlled model, hardware, prompt, context, output, sampling, concurrency, memory, and quality lanes.
|
||||
|
||||
- Quick performance win for nodes with enough RAM/VRAM.
|
||||
- Minimal coordination with distributed protocol.
|
||||
Later distributed release gates use thresholds locked before implementation results are known. The native track stops if llama.cpp/GGUF offers neither a meaningful performance benefit nor a meaningful model-fit benefit at useful speed.
|
||||
|
||||
Cons:
|
||||
### Concurrency
|
||||
|
||||
- Does not unlock 397B/753B-class models for ordinary nodes.
|
||||
- Does not solve marketplace layer routing.
|
||||
A native worker must isolate `(Route Session ID, route epoch)` through a llama sequence or bounded context and must not serialize all generations behind one global serving sequence.
|
||||
|
||||
### C. Distributed GGUF with per-shard local KV (chosen)
|
||||
The node admits sessions against weight/KV/scratch budgets, batches compatible decode steps, prevents prefill starvation, applies backpressure, and exposes queue/batch/KV telemetry.
|
||||
|
||||
Pros:
|
||||
### Architecture certification
|
||||
|
||||
- Aligns with torrent artifact distribution.
|
||||
- Avoids root streaming weights to workers.
|
||||
- Uses llama.cpp/GGUF performance where supported.
|
||||
- Compatible with public node rewards by layer/work contribution.
|
||||
- Scales KV memory by layer range.
|
||||
Dense Llama-family is first. Qwen3/Qwen3-MoE is a separate explicit adapter. Every architecture/backend/recipe remains registered-but-dark until a real distributed forward, parity test, concurrency test, and capability admission pass.
|
||||
|
||||
Cons:
|
||||
## Alternatives rejected
|
||||
|
||||
- Requires new runtime APIs around layer-boundary hidden states and per-session KV.
|
||||
- Requires model-specific cache metadata for DSA/MLA/hybrid attention.
|
||||
- Harder to debug than single-process `generate()`.
|
||||
### Fork vLLM for the public mesh
|
||||
|
||||
### D. Centralized KV cache servers
|
||||
Rejected because extracting its PP/TP/EP stages requires replacing static process groups, rank lifecycle, scheduler, request ownership, cache layout, failure behavior, and hardware assumptions. This would create a large difficult fork while discarding much of vLLM's core architecture.
|
||||
|
||||
Pros:
|
||||
### llama.cpp RPC as the public protocol
|
||||
|
||||
- Easier apparent session failover.
|
||||
- Central accounting of active cache.
|
||||
Rejected because it exposes coordinator-owned raw GGML devices, not independent Shards. Its trust, security, failure, cache, and per-node accounting model is unsuitable for arbitrary volunteer nodes.
|
||||
|
||||
Cons:
|
||||
### Adopt Nakshatra or prima.cpp wholesale
|
||||
|
||||
- Puts remote storage in the per-token hot path.
|
||||
- Adds bandwidth and latency at the worst possible point.
|
||||
- Creates consistency and privacy problems.
|
||||
Rejected because their repositories, build reproducibility, session/concurrency semantics, architecture coverage, protocol identity, and control planes do not satisfy the project contract. Their partial-loading and boundary work remains valuable evidence.
|
||||
|
||||
Rejected for hot decode. Accepted only for cold prefix snapshots and failover checkpoints.
|
||||
### Build a custom GGUF engine
|
||||
|
||||
Rejected because llama.cpp already provides the parser, kernels, architecture graphs, KV, tokenizer, and heterogeneous backends. Reimplementing these would spread effort and increase correctness risk.
|
||||
|
||||
### Invent a custom transport
|
||||
|
||||
Rejected. gRPC/HTTP2 already provides mature streaming, flow control, deadlines, cancellation, TLS, and cross-language schema generation.
|
||||
|
||||
## Consequences
|
||||
|
||||
- ADR-0001 should eventually be amended: PyTorch remains valid, but llama.cpp/GGUF becomes a first-class backend.
|
||||
- The activation protocol must split prefill and decode explicitly.
|
||||
- Session IDs must be stable across the full request. The current fresh UUID-per-hop-call behavior must change.
|
||||
- Backends must report cache budget and cache compatibility.
|
||||
- Tracker route selection must include disk, memory pressure, cache warmth, and network latency.
|
||||
- Billing can be based on layer work, prefill tokens, decode tokens, and observed route participation.
|
||||
- Client UX should stream token deltas when feasible and must include route-session progress telemetry even when token deltas are not streamed.
|
||||
- The critical path contains Meshnet, one standalone worker, and one small pinned llama.cpp patch stack.
|
||||
- Transformers/safetensors remains the correctness reference and fallback for unsupported architectures.
|
||||
- Whole-model llama.cpp and vLLM managed clusters remain useful optional provider types.
|
||||
- The first milestone emphasizes controlled benchmark, parity, concurrent KV, and real two-machine evidence rather than a large-model demo.
|
||||
- Upstream collaboration with llama.cpp targets generic local hooks only; the project remains able to ship a narrow pinned fork if upstream acceptance takes time.
|
||||
- QUIC, public tensor parallelism, disaggregated prefill, speculative decode, route repair, and KV migration remain deferred until the core route passes release gates.
|
||||
|
||||
## Required Runtime Capabilities
|
||||
## Verification gates
|
||||
|
||||
PyTorch path:
|
||||
|
||||
- manual layer calls with `past_key_values` / model-specific cache object
|
||||
- per-shard session cache store
|
||||
- prefill chunk append
|
||||
- decode step append
|
||||
- stable session lifecycle endpoints
|
||||
|
||||
llama.cpp/GGUF path:
|
||||
|
||||
- full local GGUF serving
|
||||
- layer/tensor map extraction from GGUF
|
||||
- optional partial layer loading or mmap-backed selected execution
|
||||
- inbound hidden-state execution from arbitrary start layer
|
||||
- outbound hidden-state return at stop layer
|
||||
- per-session KV ownership for loaded layers
|
||||
- cache budget/compatibility introspection
|
||||
- GLM-5.2 DSA support when upstream/runtime supports it
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. Add full-model `LlamaCppBackend` using `llama-server` or `libllama`.
|
||||
2. Implement distributed KV in the PyTorch path to prove semantics.
|
||||
3. Add session lifecycle and prefill/decode wire protocol.
|
||||
4. Add model artifact manifest and torrent seeding metadata.
|
||||
5. Prototype localhost two-process llama.cpp layer boundary execution.
|
||||
6. Generalize to network route.
|
||||
7. Bring in GLM-5.2/Ornith once backend support and cache accounting are verified.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- A two-node localhost route can prefill once and decode N tokens without recomputing the full prompt.
|
||||
- Seam payload during decode is `O(hidden_size)`, not `O(sequence_length * hidden_size)`.
|
||||
- Per-node KV memory grows with owned layer count and context length.
|
||||
- Route loss during alpha fails cleanly with explicit reason.
|
||||
- Full local GGUF backend outperforms PyTorch CPU on a supported model.
|
||||
- Artifact manifest can identify exactly which files/chunks a node must seed for its advertised layer range.
|
||||
1. Controlled safetensors-versus-GGUF performance contract.
|
||||
2. Two-process local range parity.
|
||||
3. Four-session concurrent KV isolation.
|
||||
4. Real two-machine execution using both Shards.
|
||||
5. End-to-end performance/fit advantage over the current distributed route.
|
||||
6. Separate Qwen3-family architecture certification.
|
||||
|
||||
@@ -1,83 +1,252 @@
|
||||
# PRD: Distributed GGUF Runtime
|
||||
# PRD: Performant Concurrent Distributed GGUF Runtime
|
||||
|
||||
## Summary
|
||||
## Overview
|
||||
|
||||
Build a distributed inference runtime that can serve large, quality-first open models by combining torrent-style model artifact distribution with sticky multi-node Inference Routes and per-shard local Hot KV State.
|
||||
Build one lean native GGUF execution path that lets an Inference Route combine consumer machines to serve models larger than any one node can hold. Reuse the existing Meshnet control plane and llama.cpp/GGML execution engine. Adopt gRPC/HTTP2 and Protocol Buffers for the native Shard worker data plane rather than inventing a transport.
|
||||
|
||||
The first runtime proof uses the existing PyTorch route because it exposes model internals and cache semantics more directly. GGUF/llama.cpp becomes the performance path after the route-session contract is proven.
|
||||
The program is benchmark-gated. GGUF is not assumed faster merely because it is quantized or uses a different file format. The first story compares the current Transformers/safetensors backend against whole-model llama.cpp on controlled model/hardware/quality lanes and locks a performance contract. Native distributed work proceeds only when GGUF provides a meaningful speed or fit benefit.
|
||||
|
||||
## Goals
|
||||
|
||||
- Eliminate full-prompt recompute in distributed decode.
|
||||
- Keep decode activation seams proportional to `hidden_size`, not `context_length * hidden_size`.
|
||||
- Keep Hot KV State local to the node serving the relevant Shard.
|
||||
- Stream token deltas when feasible and always expose Generation Telemetry.
|
||||
- Add a local full-model GGUF backend for immediate CPU performance wins.
|
||||
- Define Model Artifact manifests so nodes can verify, seed, and advertise artifacts without depending on Hugging Face at request time.
|
||||
- Prototype an upstreamable llama.cpp/libllama layer-boundary API.
|
||||
- Use DeepSeek-V4-Flash as the first serious large-model target after smaller protocol smoke tests.
|
||||
- Execute one GGUF model across independently addressable contiguous Shards.
|
||||
- Retain Hot KV State locally for each Shard and isolate concurrent Route Sessions.
|
||||
- Batch compatible decode steps across active sessions for aggregate throughput.
|
||||
- Use consumer CPU, AMD, NVIDIA, Vulkan, Metal, and mixed routes only where a real certified forward passes.
|
||||
- Beat the current distributed safetensors route under a controlled performance contract or enable a larger otherwise-unroutable model at useful measured speed.
|
||||
- Keep the critical path to Meshnet plus a small pinned llama.cpp fork and standalone C++ worker.
|
||||
- Produce narrow upstream collaboration material for llama.cpp without placing Meshnet networking or economics inside upstream.
|
||||
|
||||
## Quality Gates
|
||||
|
||||
Every story must:
|
||||
|
||||
- Run its targeted `pytest` tests.
|
||||
- Run `python -m compileall packages tests` for Python changes.
|
||||
- Run `git diff --check`.
|
||||
- Keep default tests deterministic, model-download-free, API-credit-free, and GPU-free.
|
||||
- Preserve existing Transformers/safetensors behavior unless the story explicitly changes a versioned compatibility contract.
|
||||
|
||||
Stories touching the native worker must also:
|
||||
|
||||
- Build the pinned C++ target with CMake.
|
||||
- Run focused C++/protocol tests through CTest or the documented equivalent.
|
||||
- Verify the llama.cpp patch stack applies cleanly to the exact pinned commit.
|
||||
|
||||
Real-model/hardware stories must:
|
||||
|
||||
- Require `MESHNET_ENABLE_REAL_INFERENCE_TESTS=1`.
|
||||
- Use the machine-specific mounted-drive model path and the certified runtime environment; never place model artifacts under `/home`.
|
||||
- Record exact model revision, artifact hash, runtime recipe, hardware, driver/backend, commands, raw JSON metrics, and output-quality result.
|
||||
- Label synthetic tests as unit coverage rather than distributed acceptance.
|
||||
|
||||
Before a story is marked complete, run the full deterministic `pytest -q` suite or record the exact pre-existing unrelated failure with a clean-tree reproduction.
|
||||
|
||||
## User Stories
|
||||
|
||||
### DGR-001: Lock the safetensors-versus-GGUF performance contract
|
||||
**Description:** As a runtime engineer, I need a controlled baseline so that GGUF work proceeds from measured speed, memory, and quality rather than reputation.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Benchmark the same model architecture/revision, machine, prompts, context lengths, output lengths, sampling policy, and concurrency across the current Transformers/safetensors recipe and whole-model llama.cpp recipes.
|
||||
- [ ] Separate correctness/quality lanes from quantized performance/fit lanes instead of claiming BF16 and Q4 are numerically equivalent.
|
||||
- [ ] Report TTFT, prefill tok/s, decode tok/s, p50/p95 latency, aggregate throughput, RSS, VRAM, artifact size, failures, and output drift in machine-readable JSON.
|
||||
- [ ] Add concurrency levels 1 and 4 where memory permits.
|
||||
- [ ] Write a versioned performance contract consumed by later release gates, including an explicit stop condition when llama.cpp/GGUF has no meaningful speed or fit benefit.
|
||||
|
||||
### DGR-002: Adopt the versioned gRPC Shard protocol
|
||||
**Description:** As a node developer, I need a battle-proven streaming protocol so that Python and C++ Shards communicate without a custom socket protocol.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Add a Protocol Buffers schema for capability, health, session stream, release, and cancellation operations.
|
||||
- [ ] Define one long-lived bidirectional gRPC stream per Route Session Activation Seam with deadlines, cancellation, flow control, and structured errors.
|
||||
- [ ] Define bounded chunking for prefill and a small decode fast path.
|
||||
- [ ] Carry schema version, request/work ID, Route Session ID, route epoch, artifact/recipe fingerprint, Shard range/effective start, phase, position, idempotency step, cache expectation, compression, and checksum.
|
||||
- [ ] Define a versioned named-tensor bundle with per-tensor name, shape, dtype, byte order, and payload fragments.
|
||||
- [ ] Add generated-schema round-trip and compatibility tests in Python and C++.
|
||||
|
||||
### DGR-003: Define exact Artifact and runtime recipe identity
|
||||
**Description:** As the Tracker, I need exact compatibility identity so that only numerically and operationally compatible Shards form an Inference Route.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Separate weight quantization, activation dtype, compute dtype, KV dtype/layout, tokenizer revision, architecture adapter, backend, and runtime version.
|
||||
- [ ] Bind derivative or split artifacts to an exact source Model Artifact hash and Shard range.
|
||||
- [ ] Produce a stable compatibility fingerprint used by capability admission and the gRPC handshake.
|
||||
- [ ] Fail closed on mismatched artifact, tokenizer, architecture, range, boundary schema, activation recipe, or cache layout.
|
||||
- [ ] Keep unsupported recipes registered-but-dark until a real distributed forward certifies them.
|
||||
|
||||
### DGR-004: Create the reproducible pinned llama.cpp patch stack
|
||||
**Description:** As a maintainer, I need a small auditable fork boundary so that upstream updates do not turn the runtime into an unmaintainable stitched codebase.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Pin one exact llama.cpp commit through a reproducible source dependency mechanism.
|
||||
- [ ] Store a numbered minimal patch stack separately from Meshnet networking code.
|
||||
- [ ] Add a build script that applies/checks patches and builds the standalone worker without manual source copying.
|
||||
- [ ] Record upstream file/ABI assumptions and fail clearly when the pin changes.
|
||||
- [ ] Preserve upstream license and attribution notices.
|
||||
- [ ] Add a clean rebuild smoke test that does not download a model.
|
||||
|
||||
### DGR-005: Implement dense-Llama range-aware GGUF ownership
|
||||
**Description:** As a node, I need to map only my assigned dense-Llama Shard so that aggregate consumer memory can hold a model larger than one node.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Register and allocate only `blk.N.*` tensors in the assigned range.
|
||||
- [ ] Load embeddings only for the head and final norm/LM head only for the tail, including tied embeddings.
|
||||
- [ ] Prefer range-aware mapping from one exact source GGUF; if derivative sub-GGUFs are used temporarily, verify source/slice hashes and avoid claiming final artifact semantics.
|
||||
- [ ] Report authoritative loaded range and endpoint ownership from the model, not operator CLI claims.
|
||||
- [ ] Demonstrate mapped/resident memory scales with owned tensors rather than full model size.
|
||||
|
||||
### DGR-006: Implement architecture-defined boundary input/output
|
||||
**Description:** As a Shard, I need to consume and emit the correct transformer boundary state so that disjoint processes reproduce whole-model execution.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Head accepts token IDs and owns token embedding.
|
||||
- [ ] Middle/tail bypass token embedding and accept the named boundary bundle.
|
||||
- [ ] Non-tail emits the unnormalized architecture-defined residual/boundary before final norm/head and before tail-only row pruning.
|
||||
- [ ] Tail emits logits or token output through an explicit sampling contract.
|
||||
- [ ] Dense-Llama whole-model versus two-range prefill and greedy-decode parity passes the documented tolerance.
|
||||
- [ ] The adapter interface fails closed for uncertified architectures.
|
||||
|
||||
### DGR-007: Add isolated concurrent local Hot KV State
|
||||
**Description:** As a client, I need concurrent Route Sessions to retain independent per-Shard cache so that one request cannot clear or corrupt another.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Map `(Route Session ID, route epoch)` to an isolated llama sequence or bounded context.
|
||||
- [ ] Allocate KV only for owned layers.
|
||||
- [ ] Support prefill append, decode append, truncate, release, TTL/LRU eviction, and explicit cache-miss response.
|
||||
- [ ] Reject stale epochs and incompatible cache recipes.
|
||||
- [ ] At least four concurrent sessions on a small model complete without token or KV cross-talk.
|
||||
- [ ] Cancellation/release of one session leaves other sessions intact and memory returns to the configured budget.
|
||||
|
||||
### DGR-008: Build the standalone C++ gRPC Shard worker
|
||||
**Description:** As a node runtime, I need one supervised native process so that llama.cpp internals remain behind a stable project-owned protocol.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Worker exposes capability, health, session stream, release, cancellation, and metrics services from DGR-002.
|
||||
- [ ] Worker loads one exact Artifact/recipe/Shard identity and refuses mismatched requests.
|
||||
- [ ] Streaming path enforces bounded messages, flow control, deadlines, idempotency, and independent session cancellation.
|
||||
- [ ] Worker does not expose raw llama.cpp RPC or arbitrary GGML graph execution.
|
||||
- [ ] Graceful shutdown releases sessions; crash behavior is bounded and observable.
|
||||
- [ ] Python integration tests run against a fake model mode without model downloads.
|
||||
|
||||
### DGR-009: Integrate the native worker with Meshnet
|
||||
**Description:** As the existing node service, I need a GGUF Shard backend adapter so that the Tracker, relay, billing, telemetry, and capability admission remain the sole control plane.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Implement the existing model-backend surface without changing Transformers behavior.
|
||||
- [ ] Registration carries exact validated GGUF recipe, Shard, backend and concurrency/KV capacity.
|
||||
- [ ] Tracker forms only complete compatible routes and keeps uncertified recipes dark.
|
||||
- [ ] Direct routes use gRPC streams; relayed routes carry the same versioned protobuf frames as opaque binary through the existing relay seam.
|
||||
- [ ] Existing request/work IDs, cancellation, Generation Telemetry, billing, and per-node attribution remain correlated.
|
||||
- [ ] No vLLM, Nakshatra, prima.cpp, or custom-engine control plane becomes a core dependency.
|
||||
|
||||
### DGR-010: Pass local real-model two-process acceptance
|
||||
**Description:** As a release engineer, I need real local distributed parity before involving network variability.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Two local worker processes open disjoint dense-Llama ranges from the certified Artifact.
|
||||
- [ ] Prefill and at least 32 greedy decode tokens match whole-model llama.cpp within the certified tolerance.
|
||||
- [ ] Each worker retains only its own tensors and Hot KV State.
|
||||
- [ ] Four concurrent Route Sessions pass isolation and cleanup checks.
|
||||
- [ ] Report TTFT, prefill/decode throughput, seam bytes/latency, worker RSS/VRAM, KV memory, batch size, and queue time.
|
||||
- [ ] Killing one worker produces a bounded structured failure rather than a deadlock.
|
||||
|
||||
### DGR-011: Pass a real heterogeneous two-machine route
|
||||
**Description:** As a consumer-hardware operator, I need two physical machines to execute one GGUF model so that the distributed claim is real.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Tracker selects two physical nodes with disjoint Shards and one exact certified recipe/compatibility class.
|
||||
- [ ] Actual CPU/GPU execution occurs on both nodes; synthetic workers do not satisfy acceptance.
|
||||
- [ ] Prefill/decode, concurrent-session isolation, telemetry, cancellation, and cleanup pass over the real transport/relay path.
|
||||
- [ ] Exact hardware, network, backend, model hash, route, commands, and raw metrics are recorded.
|
||||
- [ ] A model or recipe larger than one participating node's admitted memory is exercised when available.
|
||||
- [ ] Output drift is measured and incompatible mixed backends fail closed.
|
||||
|
||||
### DGR-012: Implement continuous batching and bounded admission
|
||||
**Description:** As a node operator, I need active sessions batched safely so that concurrency increases aggregate throughput rather than serializing every request.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Node scheduler admits sessions against weight, KV, scratch, and queue budgets.
|
||||
- [ ] Compatible decode steps from multiple sessions form llama.cpp batches while preserving per-session positions and outputs.
|
||||
- [ ] Prefill does not starve decode; scheduling policy and bounds are explicit.
|
||||
- [ ] Backpressure prevents unbounded queued activations or KV growth.
|
||||
- [ ] Capability telemetry reports active sessions, queue depth, batch occupancy, KV pressure, prefill/decode rates, and rejected admissions.
|
||||
- [ ] Concurrency 1/2/4/8 benchmark identifies saturation and shows no cross-session corruption.
|
||||
|
||||
### DGR-013: Harden failure, cancellation, and restart semantics
|
||||
**Description:** As a client, I need failures to be bounded and explicit so that distributed speed does not come with hanging or corrupted generations.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Deadlines and heartbeat/health loss terminate blocked stream operations.
|
||||
- [ ] Cancellation propagates across every Shard and releases local KV and queued buffers.
|
||||
- [ ] Duplicate steps are idempotent; uncertain mutations are never replayed silently.
|
||||
- [ ] Alpha failover restarts from token zero on a newly compatible route rather than importing unverified KV.
|
||||
- [ ] Worker death, stream reset, malformed bundle, stale epoch, and cache miss tests pass.
|
||||
- [ ] Billing/work records distinguish completed, cancelled, failed, and unverified work.
|
||||
|
||||
### DGR-014: Enforce the GGUF-versus-safetensors release gate
|
||||
**Description:** As the product owner, I need an end-to-end comparison so that the native runtime ships only if it advances model access or performance.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Run current distributed safetensors and distributed GGUF routes on the same certified model/hardware/network scenario where technically comparable.
|
||||
- [ ] Report quality, TTFT, prefill/decode throughput, aggregate concurrency throughput, p95 latency, seam cost, memory, KV pressure, failures, and cleanup.
|
||||
- [ ] Evaluate against the DGR-001 performance contract without changing thresholds after seeing results.
|
||||
- [ ] Ship recommendation is one of: promote GGUF, optimize a measured bottleneck with a new bounded task, or stop the native track.
|
||||
- [ ] Results clearly separate quantization gains from transport/runtime gains.
|
||||
|
||||
### DGR-015: Add and certify a Qwen3/Qwen3-MoE adapter
|
||||
**Description:** As a client seeking top models, I need a separately certified MoE-capable architecture after the dense runtime proves stable.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Implement explicit tensor ownership, router/top-k, expert/shared-expert, Q/K normalization, boundary bundle, and cache semantics for the selected Qwen3 family recipe.
|
||||
- [ ] Do not reuse the dense-Llama adapter through unchecked name substitutions.
|
||||
- [ ] Whole-model versus distributed prefill/decode parity passes the architecture-specific tolerance.
|
||||
- [ ] Expert memory ownership and communication are measured.
|
||||
- [ ] Real consumer-hardware acceptance and capability admission pass before the recipe becomes routable.
|
||||
|
||||
### DGR-016: Produce the upstream llama.cpp collaboration package
|
||||
**Description:** As a maintainer, I need narrow upstreamable proposals so that our patch burden can shrink without asking llama.cpp to own Meshnet networking.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Separate generic llama.cpp hooks from Meshnet protocol/control-plane code.
|
||||
- [ ] Prepare minimal reproducible examples and tests for range-aware loading, boundary input/output, and layer-filtered KV.
|
||||
- [ ] Compare the proposal with Nakshatra and prima.cpp evidence and explain why the API is generally useful.
|
||||
- [ ] Preserve one scoped commit/patch per concern against the exact upstream pin.
|
||||
- [ ] Produce an outreach document suitable for Georgi/llama.cpp maintainers; actual sending remains a human action.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
1. The public distributed primitive is an ordered Inference Route of contiguous Shards.
|
||||
2. The native runtime uses llama.cpp/GGML; vLLM remains optional as a complete managed provider.
|
||||
3. Native worker communication uses gRPC/HTTP2 and Protocol Buffers with one stable stream per Route Session Activation Seam.
|
||||
4. Artifact identity, runtime recipe, boundary schema, activation dtype and cache layout must match exactly before routing.
|
||||
5. Hot KV State remains local to the node serving the Shard.
|
||||
6. Multiple Route Sessions must execute concurrently without shared-cache corruption.
|
||||
7. Nodes batch compatible active decode steps and enforce bounded admission/backpressure.
|
||||
8. Unsupported architectures and hardware recipes remain non-routable until real certification passes.
|
||||
9. Default tests never download models or require GPUs; real tests are explicit and preserve artifacts off `/home`.
|
||||
10. The release decision is based on measured performance, fit, quality, concurrency, and reliability relative to the safetensors baseline.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No centralized hot KV cache in the per-token decode path.
|
||||
- No automatic route repair in alpha.
|
||||
- No permanent llama.cpp fork as the intended architecture.
|
||||
- No GLM-5.2 or Ornith first; they remain follow-up support audits.
|
||||
- No transport rewrite to QUIC/WebRTC before route/session semantics are proven.
|
||||
- Forking vLLM or importing its PagedAttention/Torch distributed runtime.
|
||||
- Adopting Nakshatra, prima.cpp, llama-gguf, LiGGUF, or GPUStack as the control plane.
|
||||
- Public WAN tensor/expert parallel collectives.
|
||||
- QUIC, WebRTC, or a custom socket protocol.
|
||||
- Automatic KV migration or mid-generation route repair in the first release.
|
||||
- Speculative decoding or disaggregated prefill before the core release gate.
|
||||
- Supporting every GGUF architecture before dense Llama and Qwen3-family certification.
|
||||
- A marketing-scale model demo that bypasses parity, concurrency, admission, or performance gates.
|
||||
|
||||
## Resolved Decisions
|
||||
## Success Metrics
|
||||
|
||||
- Public-network Shards are contiguous transformer layer ranges.
|
||||
- Tensor/ring parallelism belongs inside one trusted node, one colocated pod, or a future composite node abstraction.
|
||||
- Hot KV State is local to route nodes; Prefix Snapshots are optional cold recovery/reuse artifacts.
|
||||
- PyTorch distributed KV/session semantics are proven before llama.cpp distributed execution.
|
||||
- Streaming responses are preferred; Generation Telemetry is mandatory.
|
||||
- llama.cpp/GGUF work targets upstreamable `libllama`/ggml hooks.
|
||||
- Alpha fails Route Sessions on route-node loss.
|
||||
- v1 activation transfer stays on binary HTTP.
|
||||
- A real model larger than one admitted node can execute across consumer machines when suitable hardware/artifacts are available.
|
||||
- Four or more concurrent sessions complete without cross-talk; hardware-specific saturation is measured.
|
||||
- Distributed GGUF passes the locked performance/fit contract against the existing safetensors route.
|
||||
- Worker and Tracker recover all resources after completion, cancellation, malformed input, and node failure.
|
||||
- The critical runtime remains Meshnet plus one standalone worker and a small auditable llama.cpp patch stack.
|
||||
|
||||
## Target User Experience
|
||||
## Open Questions
|
||||
|
||||
A client sends an OpenAI-compatible request. The Gateway or Tracker Node accepts the request, creates a Route Session, and streams token deltas when supported. The client receives live Generation Telemetry for route phase, prefill progress, generated token count, rolling tokens/sec, route health, and failure reason.
|
||||
|
||||
If a route node drops in alpha, the request fails clearly. A retry starts a new Route Session from scratch.
|
||||
|
||||
## Runtime Shape
|
||||
|
||||
```text
|
||||
client request
|
||||
-> Gateway / Tracker Node creates Route Session
|
||||
-> Tracker selects sticky Inference Route
|
||||
-> prefill:
|
||||
prompt chunks move through Shards
|
||||
each node appends local Hot KV State
|
||||
-> decode:
|
||||
one-step activation moves through Shards
|
||||
each node reads/appends local Hot KV State
|
||||
tail returns token/logits
|
||||
-> client receives streamed token deltas where possible
|
||||
-> Generation Telemetry continues until complete or failed
|
||||
```
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | Outcome | Issues |
|
||||
|---|---|---|
|
||||
| M1 — Session protocol proof | Stub route has stable Route Sessions, prefill/decode split, telemetry, and streaming contract | 01, 02, 03 |
|
||||
| M2 — PyTorch reference route | Distributed PyTorch decode uses local per-shard cache and stops full-prompt recompute | 04 |
|
||||
| M3 — Local GGUF performance path | Single-node GGUF backend serves through the node API and reports backend metadata | 05 |
|
||||
| M4 — Artifact plane | Model Artifact manifest supports verification, layer mapping, and node advertisement | 06 |
|
||||
| M5 — llama.cpp collaboration proof | Localhost layer-boundary prototype identifies upstreamable llama.cpp/libllama API | 07 |
|
||||
| M6 — Networked GGUF route | Multi-node GGUF route uses the resolved protocol and fails cleanly on node loss | 08 |
|
||||
| M7 — First large model | DeepSeek-V4-Flash support path is audited and converted into follow-up runtime tasks | 09 |
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- A two-node route can prefill once and decode without resending full prompt activations.
|
||||
- Decode seam payload is one token/hidden-state step after prefill.
|
||||
- Route Session telemetry is visible before first token and during decode.
|
||||
- Streaming token deltas work where the backend supports them.
|
||||
- Route-node loss produces a structured alpha failure and does not attempt unsafe repair.
|
||||
- A local GGUF model can serve via the node API.
|
||||
- A Model Artifact manifest can prove which Shards a node can serve.
|
||||
- DeepSeek-V4-Flash has a written support recommendation: PyTorch, vLLM/SGLang, llama.cpp/GGUF, or blocked.
|
||||
- Exact benchmark model and quantization lanes are selected by DGR-001 from currently supported, legally redistributable artifacts.
|
||||
- Final hardware-specific concurrency and useful-speed thresholds are locked by measured baselines rather than guessed globally.
|
||||
- Upstream llama.cpp acceptance is desirable but not a prerequisite for the first narrow pinned fork.
|
||||
|
||||
309
.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md
Normal file
309
.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# Ralph execution context: Performant Concurrent Distributed GGUF Runtime
|
||||
|
||||
Status: authoritative context for every fresh Ralph iteration
|
||||
Last updated: 2026-07-13
|
||||
|
||||
## Mandatory startup sequence
|
||||
|
||||
Before changing code, every Ralph agent must:
|
||||
|
||||
1. Read this file completely.
|
||||
2. Read the selected issue under `.scratch/distributed-gguf-runtime/issues/`.
|
||||
3. Read `.scratch/distributed-gguf-runtime/ADR-0020-distributed-gguf-runtime.md` and the relevant part of `architecture.md`.
|
||||
4. Read `.claude/memory/MEMORY.md` and root `CONTEXT.md` for current project vocabulary and constraints.
|
||||
5. Inspect the current implementation and tests; do not assume historical scratch text describes live code.
|
||||
6. Read the evidence/handoff directories for every declared dependency.
|
||||
7. Inspect `git status` and preserve all pre-existing working-tree changes.
|
||||
|
||||
A fresh Ralph iteration has no conversational memory. These files are the context contract.
|
||||
|
||||
## Story sizing and interruption rule
|
||||
|
||||
Each story is intended to fit one focused Ralph context. Before implementation, estimate whether every acceptance criterion can be completed and verified in the current iteration.
|
||||
|
||||
If the story is too large, an external dependency is unavailable, or the context/provider limit prevents completion:
|
||||
|
||||
- Do not weaken criteria.
|
||||
- Do not mark the issue done or set `passes: true`.
|
||||
- Avoid leaving an unverified cross-cutting partial implementation when a smaller safe spike is possible.
|
||||
- Write `evidence/<TASK-ID>/DECOMPOSITION.md` or `BLOCKED.md` with the exact blocker, current verified state, proposed child stories, dependency graph and rollback/continuation instructions.
|
||||
- Stop for supervised review.
|
||||
|
||||
If interrupted after code changes, record every changed file, command result and unresolved invariant so the next fresh loop can verify rather than guess.
|
||||
|
||||
## Product objective
|
||||
|
||||
Build performant, concurrent distributed inference that combines consumer machines to serve top open models that exceed one node's RAM/VRAM.
|
||||
|
||||
A distributed demo is not success. The product must provide:
|
||||
|
||||
- Useful measured prefill and decode speed.
|
||||
- Multiple concurrent Route Sessions.
|
||||
- No KV/token cross-talk.
|
||||
- Bounded memory, queues, cancellation and failures.
|
||||
- Real execution on every participating node.
|
||||
- A model-fit or performance advantage over the current Transformers/safetensors route.
|
||||
|
||||
## Critical-path architecture
|
||||
|
||||
```text
|
||||
Existing Meshnet control plane
|
||||
|
|
||||
Versioned Protobuf over gRPC/HTTP2
|
||||
|
|
||||
Project-owned standalone C++ Shard worker
|
||||
|
|
||||
Small exact-commit llama.cpp patch stack
|
||||
```
|
||||
|
||||
Meshnet remains the only control plane and owns:
|
||||
|
||||
- Tracker registration, Coverage Map, route selection and route epochs.
|
||||
- Route Sessions and Activation Seams.
|
||||
- Direct/relay routing.
|
||||
- Capability admission.
|
||||
- Cancellation, Generation Telemetry and backpressure.
|
||||
- Billing, validation and per-node work attribution.
|
||||
|
||||
Do not introduce another scheduler/control plane from vLLM, Nakshatra, prima.cpp, llama-gguf, GPUStack or another project.
|
||||
|
||||
## Runtime decisions that are not open
|
||||
|
||||
1. Public-network Shards are contiguous transformer layer ranges.
|
||||
2. llama.cpp/GGML is the native GGUF execution substrate.
|
||||
3. The project owns a small standalone worker and a narrow pinned llama.cpp patch stack.
|
||||
4. The native Shard protocol is Protocol Buffers over gRPC/HTTP2.
|
||||
5. One long-lived bidirectional stream serves one Route Session Activation Seam.
|
||||
6. The public activation boundary is a versioned named-tensor bundle.
|
||||
7. Hot KV State remains local to the node serving the Shard.
|
||||
8. `(Route Session ID, route epoch)` maps to an isolated llama sequence or bounded context.
|
||||
9. Concurrency uses continuous batching of compatible active sessions inside each node.
|
||||
10. Transformers/safetensors remains the correctness and performance baseline.
|
||||
11. vLLM may be an optional complete managed provider and concept donor; it is not forked into public Shards.
|
||||
12. Tensor/expert collectives are deferred to a trusted composite provider, not public WAN routes.
|
||||
13. Unsupported architectures/backends remain registered-but-dark until real certification passes.
|
||||
14. Alpha failure retries from token zero; unverified KV is never migrated silently.
|
||||
15. Model artifacts must remain on mounted-drive storage and never under `/home`.
|
||||
|
||||
Changing one of these requires an explicit ADR update and human review, not an incidental story implementation.
|
||||
|
||||
## Performance discipline
|
||||
|
||||
GGUF performance is a hypothesis. Never write “GGUF is faster” without measurements.
|
||||
|
||||
DGR-001 locks controlled benchmark lanes and thresholds. DGR-014 enforces the final distributed comparison.
|
||||
|
||||
Always distinguish:
|
||||
|
||||
- Weight quantization from activation/compute/KV dtype.
|
||||
- Runtime/kernel gains from quantization/model-fit gains.
|
||||
- Single-request latency from aggregate concurrency throughput.
|
||||
- Synthetic unit coverage from real distributed acceptance.
|
||||
|
||||
Required metrics where applicable:
|
||||
|
||||
```text
|
||||
TTFT
|
||||
prefill tokens/sec
|
||||
decode tokens/sec
|
||||
aggregate throughput
|
||||
p50/p95 latency
|
||||
seam bytes and latency
|
||||
queue and batch occupancy
|
||||
RSS and VRAM
|
||||
KV pressure
|
||||
output-quality drift
|
||||
failures and cleanup
|
||||
```
|
||||
|
||||
Do not weaken or move performance thresholds after seeing implementation results.
|
||||
|
||||
## Transport discipline
|
||||
|
||||
Do not invent a raw TCP protocol, new WebSocket protocol, QUIC layer or bespoke binary control format.
|
||||
|
||||
The `.proto` schema is the semantic contract. Direct transport uses gRPC. Existing relay infrastructure may carry the same serialized protobuf frames as opaque binary.
|
||||
|
||||
Protocol requirements:
|
||||
|
||||
- Schema/version negotiation.
|
||||
- Request/work ID.
|
||||
- Route Session ID and route epoch.
|
||||
- Exact Model Artifact/runtime recipe fingerprint.
|
||||
- Shard range and effective overlap-safe start.
|
||||
- Prefill/decode/release/cancel phases.
|
||||
- Position/token range and idempotency step.
|
||||
- Named tensors with shape, dtype, byte order and bounded fragments.
|
||||
- Compression/checksum.
|
||||
- Cache expectation/result.
|
||||
- Deadlines, cancellation, flow control and structured status.
|
||||
|
||||
Avoid per-token channel creation and unbounded unary payloads. Generated code and build tooling must be reproducible; do not require manual copying.
|
||||
|
||||
## Native runtime discipline
|
||||
|
||||
Reuse llama.cpp for GGUF, mmap, kernels, architecture graphs, tokenizer, KV, sequences and heterogeneous backends.
|
||||
|
||||
The project patch stack is limited to:
|
||||
|
||||
- Range-aware tensor registration/loading.
|
||||
- Endpoint-specific embedding/final head ownership.
|
||||
- Architecture-defined intermediate input/output.
|
||||
- Intermediate output before final norm/head.
|
||||
- Layer-filtered KV and session mapping.
|
||||
|
||||
Do not place Meshnet routing, transport, billing or authentication inside llama.cpp. Keep patches numbered, scoped, pinned and upstreamable.
|
||||
|
||||
Dense Llama-family is first. Qwen3/Qwen3-MoE is a separate adapter after the dense release gate. Do not generalize through unchecked tensor-name substitutions.
|
||||
|
||||
## Existing code seams to inspect first
|
||||
|
||||
- `packages/node/meshnet_node/model_backend.py` — backend abstraction.
|
||||
- `packages/node/meshnet_node/torch_server.py` — reference ranged execution and session behavior.
|
||||
- `packages/node/meshnet_node/activation_compression.py` — current activation framing/compression.
|
||||
- `packages/node/meshnet_node/route_session_benchmark.py` — existing benchmark infrastructure.
|
||||
- `packages/tracker/meshnet_tracker/server.py` — registration, route and proxy behavior.
|
||||
- `packages/tracker/meshnet_tracker/capability.py` — fail-closed capability admission.
|
||||
- `tests/test_real_model_backend.py` — real backend coverage.
|
||||
- `tests/test_tracker_routing.py` — route/session behavior.
|
||||
- `tests/test_tracker_capability_admission.py` — recipe admission.
|
||||
- `tests/test_route_session_benchmark.py` and `tests/test_manual_route_benchmark.py` — benchmark patterns.
|
||||
- `docs/adr/0008-binary-activation-wire-format.md` — existing wire compatibility.
|
||||
- `docs/adr/0012-start-layer-overlapping-shards.md` — effective start semantics.
|
||||
- `docs/adr/0022-sharded-per-node-kv-cache.md` — Hot KV State contract.
|
||||
- `docs/adr/0023-model-agnostic-node-capability-admission.md` — certification/admission.
|
||||
|
||||
Do not edit generated `build/`, `__pycache__`, egg-info, Ralph logs or unrelated scratch features.
|
||||
|
||||
## Planned source layout
|
||||
|
||||
Use these paths unless current code inspection proves a better project-consistent location. If changed, document the reason in task evidence.
|
||||
|
||||
```text
|
||||
packages/node/native/
|
||||
proto/shard_runtime.proto
|
||||
cmake/
|
||||
llama/
|
||||
UPSTREAM_COMMIT
|
||||
patches/
|
||||
gguf_worker/
|
||||
tests/
|
||||
|
||||
packages/node/meshnet_node/
|
||||
native_protocol/
|
||||
gguf_backend.py
|
||||
runtime_recipe.py
|
||||
|
||||
.scratch/distributed-gguf-runtime/evidence/<TASK-ID>/
|
||||
README.md
|
||||
commands.txt
|
||||
results.json or other machine-readable evidence
|
||||
```
|
||||
|
||||
Generated protobuf/C++ build outputs belong in build directories unless packaging explicitly requires checked-in generated Python modules. The story must document the generation command and version.
|
||||
|
||||
## Story output map
|
||||
|
||||
| Story | Required durable outputs |
|
||||
|---|---|
|
||||
| DGR-001 | benchmark harness/tests; `evidence/DGR-001/performance-contract.json`; raw/summary benchmark evidence |
|
||||
| DGR-002 | `packages/node/native/proto/shard_runtime.proto`; reproducible Python/C++ generation/build wiring; protocol round-trip/compatibility tests; `evidence/DGR-002/` |
|
||||
| DGR-003 | exact runtime-recipe/fingerprint implementation and admission tests; `evidence/DGR-003/` |
|
||||
| DGR-004 | exact upstream pin, numbered patch series, reproducible fetch/apply/build smoke; `evidence/DGR-004/` |
|
||||
| DGR-005 | dense-Llama range ownership loader and memory evidence; `evidence/DGR-005/` |
|
||||
| DGR-006 | architecture boundary adapter/parity tests and results; `evidence/DGR-006/` |
|
||||
| DGR-007 | concurrent session/KV manager, isolation/cleanup tests; `evidence/DGR-007/` |
|
||||
| DGR-008 | standalone C++ gRPC worker, fake-model integration tests, lifecycle evidence; `evidence/DGR-008/` |
|
||||
| DGR-009 | Meshnet backend/registration/relay integration and tests; `evidence/DGR-009/` |
|
||||
| DGR-010 | real local two-process commands, raw metrics and parity report; `evidence/DGR-010/` |
|
||||
| DGR-011 | two-machine configuration, commands, hardware/network manifest and raw results; `evidence/DGR-011/` |
|
||||
| DGR-012 | continuous scheduler/admission implementation and 1/2/4/8 concurrency report; `evidence/DGR-012/` |
|
||||
| DGR-013 | failure/cancel/restart test matrix and resource-cleanup evidence; `evidence/DGR-013/` |
|
||||
| DGR-014 | immutable final comparison against DGR-001 thresholds and ship/stop recommendation; `evidence/DGR-014/` |
|
||||
| DGR-015 | Qwen3-family adapter, architecture-specific parity/admission/performance evidence; `evidence/DGR-015/` |
|
||||
| DGR-016 | narrow upstream patches/tests, design note and human-ready outreach package; `evidence/DGR-016/` |
|
||||
|
||||
## Dependency handoff rule
|
||||
|
||||
For every dependency listed by Ralph:
|
||||
|
||||
1. Confirm its `passes` state in `prd.json`.
|
||||
2. Read `.scratch/distributed-gguf-runtime/evidence/<DEPENDENCY-ID>/README.md`.
|
||||
3. Verify referenced source paths and commands still exist.
|
||||
4. Do not repeat completed work unless verification exposes a concrete defect.
|
||||
5. If dependency evidence is missing or contradictory, stop and repair the dependency instead of guessing.
|
||||
|
||||
## Testing and hardware rules
|
||||
|
||||
Default tests must be deterministic, GPU-free, model-download-free and API-credit-free.
|
||||
|
||||
Real model tests require:
|
||||
|
||||
```text
|
||||
MESHNET_ENABLE_REAL_INFERENCE_TESTS=1
|
||||
```
|
||||
|
||||
On this machine:
|
||||
|
||||
- Use `.venv-rocm` for real Radeon 8060S ROCm execution.
|
||||
- The default Python 3.14 `.venv` is unsuitable for real ROCm inference.
|
||||
- Resolve model storage through the machine-specific `.env.<hostname>` configuration.
|
||||
- Never download model artifacts under `/home`.
|
||||
- Real acceptance must exercise actual Tracker-routed CPU/GPU computation; synthetic workers are only unit tests.
|
||||
|
||||
Record exact:
|
||||
|
||||
- Model/revision and Artifact hash.
|
||||
- Quantization and runtime recipe.
|
||||
- Host/hardware/backend/driver.
|
||||
- Commands and environment names without secrets.
|
||||
- Raw output and metrics.
|
||||
- Whether the evidence is synthetic, local-real, or multi-machine-real.
|
||||
|
||||
## Worktree and commit discipline
|
||||
|
||||
This repository may contain pre-existing changes from research or another feature.
|
||||
|
||||
- Inspect `git status` before editing.
|
||||
- Never reset, checkout over, stash, delete or reformat unrelated changes.
|
||||
- Stage only files belonging to the selected story.
|
||||
- Exclude `.ralph-tui`, iteration logs, caches, generated builds, FUSE artifacts and unrelated scratch work.
|
||||
- Keep one scoped commit per completed story when the supervising loop requests commits.
|
||||
- Do not modify `passes` for another story.
|
||||
|
||||
## Mandatory finish/handoff sequence
|
||||
|
||||
Before emitting `<promise>COMPLETE</promise>`:
|
||||
|
||||
1. Verify every acceptance criterion with real command output or file evidence.
|
||||
2. Run story-specific gates and repository quality gates.
|
||||
3. Write `.scratch/distributed-gguf-runtime/evidence/<TASK-ID>/README.md` containing:
|
||||
- Summary of changes.
|
||||
- Exact files changed.
|
||||
- Commands run and their real results.
|
||||
- Performance/correctness evidence.
|
||||
- Known limitations and deferred work.
|
||||
- Compatibility or migration notes.
|
||||
- Clear handoff for dependent stories.
|
||||
4. Save machine-readable evidence beside it when the story produces metrics or schemas.
|
||||
5. Update the source issue status to `done` only after all gates pass.
|
||||
6. Preserve failures honestly. Never fabricate model, benchmark, test or hardware output.
|
||||
|
||||
## Authoritative references
|
||||
|
||||
Active decisions:
|
||||
|
||||
- `.scratch/distributed-gguf-runtime/README.md`
|
||||
- `.scratch/distributed-gguf-runtime/implementation-strategy.md`
|
||||
- `.scratch/distributed-gguf-runtime/architecture.md`
|
||||
- `.scratch/distributed-gguf-runtime/ADR-0020-distributed-gguf-runtime.md`
|
||||
- `.scratch/distributed-gguf-runtime/PRD.md`
|
||||
- `.scratch/distributed-gguf-runtime/prd.json`
|
||||
|
||||
Source research:
|
||||
|
||||
- `docs/research/distributed-gguf-landscape.md`
|
||||
- `docs/research/distributed-gguf-github-followup.md`
|
||||
- `docs/research/vllm-distributed-gguf-assessment.md`
|
||||
|
||||
If historical notes conflict with these files, the active decisions above win.
|
||||
@@ -1,63 +1,46 @@
|
||||
# Distributed GGUF runtime — planning index
|
||||
# Performant concurrent distributed GGUF runtime
|
||||
|
||||
Status: draft scratch package.
|
||||
Status: active benchmark-gated implementation program.
|
||||
|
||||
Goal: make the node network capable of serving large, high-quality open models by distributing GGUF/model artifacts over a torrent-style swarm while executing inference over a sticky multi-node route with per-shard local KV cache.
|
||||
## Objective
|
||||
|
||||
This scratch supersedes the old assumption in [ADR-0001](../../docs/adr/0001-pytorch-over-llama-cpp.md) that llama.cpp is only a single-node leaf backend. That assumption was correct for the original llama.cpp RPC shape, but the target is now different: torrent-distributed GGUF artifacts plus an explicit route/KV protocol owned by this platform, ideally developed in collaboration with upstream llama.cpp.
|
||||
Serve top open models across consumer machines with useful performance and concurrent Route Sessions while keeping the runtime lean.
|
||||
|
||||
## Artifacts
|
||||
## Critical path
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| [architecture.md](./architecture.md) | Proposed runtime architecture, data flow, session state, and failure model |
|
||||
| [technical-challenges.md](./technical-challenges.md) | Detailed challenge/solution register with acceptance tests |
|
||||
| [decision-framework.md](./decision-framework.md) | Grilling framework for open decisions and recommended answers |
|
||||
| [research-prior-art.md](./research-prior-art.md) | Prior-art notes for Petals, exo, Distributed Llama, prima.cpp, llama.cpp, DeepSeek-V4-Flash, GLM-5.2, and Ornith |
|
||||
| [ADR-0020-distributed-gguf-runtime.md](./ADR-0020-distributed-gguf-runtime.md) | Draft decision record for the GGUF/llama.cpp distributed runtime |
|
||||
| [PRD.md](./PRD.md) | Product/runtime requirements and acceptance criteria |
|
||||
| [milestones.md](./milestones.md) | Dependency-ordered implementation milestones |
|
||||
| [issues/](./issues/) | Implementation-ready tracer-bullet issue briefs |
|
||||
```text
|
||||
Meshnet control plane
|
||||
-> versioned gRPC/Protobuf Shard protocol
|
||||
-> project-owned standalone C++ worker
|
||||
-> small pinned llama.cpp patch stack
|
||||
```
|
||||
|
||||
## Decision Summary
|
||||
Transformers/safetensors remains the correctness baseline. vLLM remains an optional complete managed provider and a design donor; it is not forked into the public mesh.
|
||||
|
||||
Adopt a hybrid runtime:
|
||||
## Planning artifacts
|
||||
|
||||
- **Weights and artifacts**: distributed by torrent / content-addressed storage / optional CDN.
|
||||
- **Hot KV cache**: local to the node that owns the corresponding layer range.
|
||||
- **Prefix snapshots**: optionally persisted to cache servers for reuse, retry, and failover.
|
||||
- **Active route**: sticky for one request/session.
|
||||
- **Context cap**: 128K hard product limit for large models unless explicitly revised.
|
||||
- **Backends**: keep PyTorch for fast model-architecture coverage and validation; add llama.cpp/GGUF as the performance path for supported models.
|
||||
- **Client feedback**: stream token deltas when feasible; always expose Generation Telemetry.
|
||||
- **First serious target model**: DeepSeek-V4-Flash after a smaller GGUF protocol smoke test.
|
||||
- **[Mandatory Ralph context](RALPH-CONTEXT.md)** — read first in every fresh iteration
|
||||
- [Task evidence contract](evidence/README.md)
|
||||
- [Implementation strategy](implementation-strategy.md)
|
||||
- [Current architecture](architecture.md)
|
||||
- [PRD](PRD.md)
|
||||
- [Ralph backlog](prd.json)
|
||||
- [ADR-0020](ADR-0020-distributed-gguf-runtime.md)
|
||||
- [Milestones](milestones.md)
|
||||
- [Issues](issues/)
|
||||
- [Distributed GGUF research](../../docs/research/distributed-gguf-landscape.md)
|
||||
- [GitHub follow-up](../../docs/research/distributed-gguf-github-followup.md)
|
||||
- [vLLM assessment](../../docs/research/vllm-distributed-gguf-assessment.md)
|
||||
|
||||
## What We Learned
|
||||
## Ralph execution
|
||||
|
||||
- Our current full-model PyTorch path uses Transformers `generate()` and gets local KV cache.
|
||||
- Our current distributed PyTorch path disables cache and recomputes the full growing sequence per token.
|
||||
- The seam today carries hidden activations, not KV cache; at 128K this becomes impossible for serious models if repeated every decode token.
|
||||
- The missing capability is not "send KV across the network"; it is **stable per-session local KV cache per shard**.
|
||||
- GGUF distribution is solved enough at the artifact layer, but GGUF/llama.cpp needs explicit layer-boundary execution APIs for our route model.
|
||||
Use supervised one-story iterations for this high-risk runtime:
|
||||
|
||||
## Recommended Order
|
||||
```bash
|
||||
ralph-tui run \
|
||||
--prd .scratch/distributed-gguf-runtime/prd.json \
|
||||
--agent claude --model opus \
|
||||
--iterations 1 --no-tui --no-setup --verify
|
||||
```
|
||||
|
||||
See [milestones.md](./milestones.md) for the full dependency map.
|
||||
|
||||
1. [01 — Route Session lifecycle](./issues/01-route-session-lifecycle.md)
|
||||
2. [02 — Prefill/decode binary HTTP protocol](./issues/02-prefill-decode-binary-http.md)
|
||||
3. [03 — Generation Telemetry and streaming response contract](./issues/03-generation-telemetry-and-streaming.md)
|
||||
4. [04 — PyTorch distributed KV reference route](./issues/04-pytorch-distributed-kv-reference.md)
|
||||
5. [05 — Local llama.cpp/GGUF backend](./issues/05-local-llamacpp-gguf-backend.md)
|
||||
6. [06 — Model Artifact manifest and Shard advertisement](./issues/06-model-artifact-manifest.md)
|
||||
7. [07 — llama.cpp layer-boundary prototype](./issues/07-llamacpp-layer-boundary-prototype.md)
|
||||
8. [08 — Networked distributed GGUF route](./issues/08-networked-distributed-gguf-route.md)
|
||||
9. [09 — DeepSeek-V4-Flash support audit](./issues/09-deepseek-v4-flash-support-audit.md)
|
||||
10. [10 — GLM-5.2 and Ornith follow-up support audit](./issues/10-glm52-ornith-followup-audit.md)
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Does upstream llama.cpp already expose enough internal API for arbitrary layer-range execution and hidden-state boundary I/O, or do we need an extension?
|
||||
- Can GGUF split metadata be made layer/tensor semantic enough for torrent placement and partial loading?
|
||||
- What is the minimum protocol needed for compressed KV formats such as GLM-5.2 DSA/MLA without exposing model-specific internals to the tracker?
|
||||
- How much reliability do we need in alpha: fail request on route loss, or support route repair with KV snapshots?
|
||||
Inspect the diff, run the story gates, and commit one verified story before the next iteration. Real-model stories require the explicit environment gate and mounted-drive model storage.
|
||||
|
||||
@@ -1,274 +1,259 @@
|
||||
# Distributed GGUF Runtime Architecture
|
||||
# Performant Concurrent Distributed GGUF Architecture
|
||||
|
||||
## Product Stance
|
||||
Status: current target architecture
|
||||
Last updated: 2026-07-13
|
||||
|
||||
The platform optimizes for access to high-quality models, not lowest latency. Latency is acceptable if the user can run models that are otherwise unavailable to them. The hard context limit for the first serious distributed runtime should be **128K tokens**. Longer context usually means the product is compensating for missing task decomposition, retrieval, or workspace summarization.
|
||||
## Product invariant
|
||||
|
||||
## Current State
|
||||
The system exists to serve high-quality models that exceed one consumer node's memory while retaining useful interactive speed and aggregate concurrency. A feature that only produces a distributed demo but is slower, globally serialized, or impossible to operate on consumer hardware is not complete.
|
||||
|
||||
The current node has two materially different inference paths:
|
||||
## Existing control plane
|
||||
|
||||
- **Full local PyTorch model**: calls Hugging Face `model.generate()`, so Transformers owns autoregressive decode and local KV cache.
|
||||
- **Distributed PyTorch route**: bypasses `model.generate()`, calls individual layers with `use_cache=False`, and recomputes the full growing sequence for every generated token.
|
||||
Meshnet remains the only public control plane:
|
||||
|
||||
Current distributed data flow:
|
||||
- Tracker registration, Coverage Map, route scoring and assignment.
|
||||
- Contiguous Shards and overlap-safe effective starts.
|
||||
- Stable Route Sessions and route epochs.
|
||||
- Local per-Shard Hot KV State in the reference backend.
|
||||
- Direct/relay transport, cancellation and backpressure.
|
||||
- Generation Telemetry, billing, validation and per-node attribution.
|
||||
- Model-agnostic capability admission.
|
||||
|
||||
No external engine replaces these responsibilities.
|
||||
|
||||
## Runtime topology
|
||||
|
||||
```text
|
||||
client request
|
||||
-> head node formats prompt
|
||||
-> for each output token:
|
||||
head tokenizes full current text
|
||||
head runs early layers over all tokens
|
||||
head sends full activation [batch, sequence, hidden] to next node
|
||||
middle nodes run their layers over all tokens
|
||||
tail returns one decoded token string
|
||||
head appends token to text
|
||||
OpenAI-compatible client
|
||||
|
|
||||
Gateway / Tracker Node
|
||||
|
|
||||
ordered Inference Route
|
||||
|
|
||||
+-- head Shard: tokenizer/embedding + early layers
|
||||
| local weights and Hot KV State
|
||||
|
|
||||
+-- middle Shard(s): architecture boundary + owned layers
|
||||
| local weights and Hot KV State
|
||||
|
|
||||
+-- tail Shard: final layers + norm/head/sampling
|
||||
local weights and Hot KV State
|
||||
```
|
||||
|
||||
This is correct for small demos but not viable for large models. For GLM-5.2, a single 128K seam activation is roughly:
|
||||
Weights never move in the per-request hot path. Every node opens and verifies its local Model Artifact before becoming routable.
|
||||
|
||||
## Primary execution substrate
|
||||
|
||||
```text
|
||||
128K tokens * hidden_size 6144 * 2 bytes ~= 1.5 GiB per hop
|
||||
project-owned C++ Shard worker
|
||||
|
|
||||
small exact-commit llama.cpp patch stack
|
||||
|
|
||||
GGUF mmap, quantized kernels, architecture graphs,
|
||||
KV/sequence operations, CPU/CUDA/HIP/Vulkan/Metal backends
|
||||
```
|
||||
|
||||
Sending that every output token is the bottleneck.
|
||||
The patch stack adds only the missing local execution seam:
|
||||
|
||||
## Target State
|
||||
1. Range-aware tensor registration/loading.
|
||||
2. Endpoint-specific embedding and final head ownership.
|
||||
3. Architecture-defined intermediate input.
|
||||
4. Architecture-defined pre-tail boundary output.
|
||||
5. Layer-filtered KV and external session mapping.
|
||||
|
||||
Target distributed data flow:
|
||||
The worker owns protocol translation and process lifecycle. llama.cpp never receives Tracker, relay, billing or volunteer-network code.
|
||||
|
||||
## Shard data plane
|
||||
|
||||
Use Protocol Buffers and gRPC over HTTP/2.
|
||||
|
||||
### Service shape
|
||||
|
||||
- Unary capability and health.
|
||||
- Bidirectional Route Session stream.
|
||||
- Explicit release and cancellation.
|
||||
- Metrics suitable for capability admission and route scoring.
|
||||
|
||||
### Session stream
|
||||
|
||||
One long-lived stream represents one Route Session Activation Seam. It amortizes connection setup and inherits HTTP/2 flow control. Every message carries enough identity to reject stale or incompatible work.
|
||||
|
||||
```text
|
||||
client request
|
||||
-> tracker selects route and pins session
|
||||
-> head node creates session_id
|
||||
-> prefill:
|
||||
prompt is chunked
|
||||
each shard computes its layer range
|
||||
each shard appends local KV/state for its own layers
|
||||
activations cross only layer seams
|
||||
-> decode loop:
|
||||
head sends one new token / one-step hidden state
|
||||
each shard reads local KV/state for session_id
|
||||
each shard appends one step to local KV/state
|
||||
only one-step activation crosses seams
|
||||
tail returns logits/token
|
||||
schema version
|
||||
request/work id
|
||||
Route Session id
|
||||
route epoch
|
||||
Model Artifact hash
|
||||
runtime recipe fingerprint
|
||||
Shard begin/end and effective start
|
||||
prefill/decode/release/cancel phase
|
||||
position and token range
|
||||
idempotency step id
|
||||
cache expectation/result
|
||||
named tensor bundle
|
||||
compression/checksum
|
||||
```
|
||||
|
||||
The KV cache remains local to the node that computed it. It is not sent to the next node and not read from a remote cache server during every decode step.
|
||||
Prefill tensors are split into bounded ordered frames. Decode messages carry one-step architecture boundary bundles and remain small.
|
||||
|
||||
## Client Feedback
|
||||
Direct nodes use gRPC. Nodes requiring the existing relay carry the same protobuf frames as opaque binary through the relay session. This preserves one semantic protocol instead of maintaining separate direct and relay payload contracts.
|
||||
|
||||
Streaming responses are desirable when the backend and client transport support them. The product should stream token deltas when possible, and it must always provide realtime Generation Telemetry while the route is working.
|
||||
## Architecture boundary
|
||||
|
||||
The fallback behavior is a non-streaming final answer plus live telemetry. That fallback is acceptable for early route proofs or models/backends that cannot expose clean token deltas yet, but the preferred client experience is streamed output plus telemetry.
|
||||
|
||||
Minimum client-visible telemetry:
|
||||
|
||||
- route/session accepted
|
||||
- selected model and quantization
|
||||
- prefill phase started/completed
|
||||
- decode phase started
|
||||
- generated token count
|
||||
- rolling tokens per second
|
||||
- route health or retry/failure reason
|
||||
- estimated billing units when available
|
||||
|
||||
Implementation options:
|
||||
|
||||
- Server-Sent Events or WebSocket for realtime progress
|
||||
- polling endpoint for simple clients
|
||||
- OpenAI-compatible streaming for clients that require token deltas
|
||||
|
||||
This means "no token streaming" is acceptable only as a fallback. "Silent wait for minutes" is not acceptable.
|
||||
|
||||
## Artifact Plane
|
||||
|
||||
Artifact distribution is separate from execution.
|
||||
The public boundary is a versioned named-tensor bundle:
|
||||
|
||||
```text
|
||||
model publisher
|
||||
-> produces model manifest
|
||||
-> creates GGUF / safetensors / tokenizer artifacts
|
||||
-> content-addresses every file/chunk
|
||||
-> publishes torrent/magnet + HTTP fallback metadata
|
||||
|
||||
node
|
||||
-> chooses model/layer range
|
||||
-> downloads needed files/chunks
|
||||
-> verifies hash
|
||||
-> advertises availability to tracker
|
||||
bundle schema/version
|
||||
architecture adapter and boundary point
|
||||
named tensors
|
||||
per-tensor shape, dtype and byte order
|
||||
payload fragments
|
||||
compression/checksum
|
||||
```
|
||||
|
||||
Required manifest fields:
|
||||
Dense Llama may use one residual tensor. Other adapters may require more. vLLM's Llama and Qwen3-MoE PP paths demonstrate a boundary with both `hidden_states` and `residual`; therefore the generic protocol must not assume one anonymous tensor.
|
||||
|
||||
- model id and version
|
||||
- upstream source repo and revision
|
||||
- license
|
||||
- architecture name
|
||||
- tokenizer files and hashes
|
||||
- quantization
|
||||
- tensor-to-layer map
|
||||
- file/chunk hashes
|
||||
- optional GGUF split files
|
||||
- supported runtime backends
|
||||
- context cap
|
||||
- KV/cache format descriptor
|
||||
Only the head owns token embedding. Only the tail owns final normalization, LM head and sampling. Middle Shards exchange the architecture-defined pre-tail boundary, not final normalized embeddings.
|
||||
|
||||
## Execution Plane
|
||||
|
||||
The tracker selects routes using layer coverage and observed performance:
|
||||
## Hot KV State and concurrency
|
||||
|
||||
```text
|
||||
route = [
|
||||
head node: embeddings + layers 0..k
|
||||
middle nodes: contiguous layer ranges
|
||||
tail node: final layers + norm + lm_head
|
||||
]
|
||||
(Route Session id, route epoch)
|
||||
-> local llama sequence or bounded context
|
||||
-> KV for owned layers only
|
||||
-> lease, memory accounting and lifecycle
|
||||
```
|
||||
|
||||
Route selection inputs:
|
||||
Required operations:
|
||||
|
||||
- model id/version/quantization
|
||||
- layer coverage
|
||||
- node hardware
|
||||
- measured prefill throughput
|
||||
- measured decode throughput
|
||||
- queue depth
|
||||
- latency to neighboring nodes
|
||||
- cache warmth for the requested prefix/session
|
||||
- reliability/reputation
|
||||
- Prefill append.
|
||||
- Decode append.
|
||||
- Truncate after rejected speculative positions if later enabled.
|
||||
- Explicit release.
|
||||
- TTL/LRU eviction.
|
||||
- Cache-miss response.
|
||||
- Stale-epoch rejection.
|
||||
|
||||
The route is sticky for the request/session. A new route means either a fresh prefill or restoring compatible KV snapshots.
|
||||
A node must not clear global KV on a new stream or serialize all requests behind one logical serving sequence.
|
||||
|
||||
## KV Cache Ownership
|
||||
## Continuous batching
|
||||
|
||||
KV/state ownership is by layer range:
|
||||
Autoregressive dependencies remain sequential inside one Route Session. Aggregate throughput comes from batching compatible decode steps across active sessions:
|
||||
|
||||
```text
|
||||
session_id = request scoped id
|
||||
node A owns layers 0..15 KV for session_id
|
||||
node B owns layers 16..31 KV for session_id
|
||||
node C owns layers 32..77 KV for session_id
|
||||
time 0: session A token 1 + session B token 8 + session C token 3
|
||||
-> one llama batch for this Shard
|
||||
|
||||
time 1: next ready positions from active sessions
|
||||
-> next llama batch
|
||||
```
|
||||
|
||||
The tracker does not own hot KV. It may know which nodes hold active KV for session accounting and failure handling.
|
||||
The node scheduler:
|
||||
|
||||
Cache servers may store:
|
||||
- Admits work against weight, KV, scratch and queue budgets.
|
||||
- Keeps per-session token positions and outputs separate.
|
||||
- Prevents long prefill from starving decode.
|
||||
- Applies bounded backpressure.
|
||||
- Reports active sessions, queue depth, batch occupancy, KV pressure and throughput.
|
||||
|
||||
- prompt-prefix snapshots
|
||||
- session checkpoints for retry
|
||||
- cold reusable context blocks
|
||||
- audit samples
|
||||
The initial deterministic gate is four concurrent sessions on a small model without cross-talk. Hardware-specific limits are measured and advertised through capability admission.
|
||||
|
||||
Cache servers must not be in the per-token hot loop unless colocated with the compute node.
|
||||
## Parallelism boundaries
|
||||
|
||||
## 128K KV Budget
|
||||
| Mechanism | First-runtime use |
|
||||
|---|---|
|
||||
| Layer/pipeline parallelism | Public Inference Route across contiguous Shards |
|
||||
| Continuous batching | Inside every node across active Route Sessions |
|
||||
| Data parallelism | Multiple complete routes for independent requests |
|
||||
| Tensor parallelism | Deferred to a trusted composite node/managed cluster |
|
||||
| Expert parallelism | Deferred to a trusted composite node/managed cluster |
|
||||
| Disaggregated prefill | Deferred until core route performance passes |
|
||||
| Speculative decoding | Deferred optimization |
|
||||
|
||||
GLM-5.2 compressed DSA/MLA-style estimate from config:
|
||||
Public WAN tensor/expert collectives are rejected for the first runtime because their per-layer communication and static rank assumptions conflict with heterogeneous volunteer nodes.
|
||||
|
||||
```text
|
||||
layers = 78
|
||||
kv_lora_rank = 512
|
||||
qk_rope_head_dim = 64
|
||||
dtype = bf16 = 2 bytes
|
||||
context = 128K
|
||||
## Optional providers
|
||||
|
||||
per_token ~= 78 * (512 + 64) * 2 = 89,856 bytes ~= 87.75 KiB
|
||||
128K total ~= 10.7 GiB
|
||||
per layer ~= 137 MiB
|
||||
```
|
||||
### Transformers/safetensors
|
||||
|
||||
This is feasible when sharded:
|
||||
Remains:
|
||||
|
||||
| Layer count | Approx active KV at 128K |
|
||||
|---:|---:|
|
||||
| 1 | 137 MiB |
|
||||
| 10 | 1.37 GiB |
|
||||
| 20 | 2.75 GiB |
|
||||
| 78 | 10.7 GiB |
|
||||
- Correctness/reference backend.
|
||||
- Fallback for unsupported architectures.
|
||||
- Baseline for performance and output quality.
|
||||
|
||||
The exact runtime value depends on implementation and cache quantization, but the order of magnitude is acceptable.
|
||||
### vLLM
|
||||
|
||||
## Protocol Sketch
|
||||
May run unmodified as a complete model or managed TP/PP/EP cluster represented as one logical provider. Its internal ranks are not independently routed or rewarded.
|
||||
|
||||
### Prefill
|
||||
Borrow only concepts such as named bundles, continuous batching, typed compatibility fingerprints, explicit transfer lifecycle and load telemetry.
|
||||
|
||||
```http
|
||||
POST /v1/sessions/{session_id}/prefill
|
||||
Content-Type: application/octet-stream
|
||||
X-Meshnet-Model: zai-org/GLM-5.2
|
||||
X-Meshnet-Route-Id: ...
|
||||
X-Meshnet-Token-Range: 0-2047
|
||||
X-Meshnet-Shape: 1,2048,6144
|
||||
X-Meshnet-Dtype: bfloat16
|
||||
### Whole-model llama.cpp
|
||||
|
||||
<activation bytes>
|
||||
```
|
||||
Provides a local proxy backend, correctness oracle and performance baseline. It is not the native distributed milestone.
|
||||
|
||||
The receiver:
|
||||
## Artifact and recipe compatibility
|
||||
|
||||
- validates route/session
|
||||
- runs assigned layer range for that chunk
|
||||
- appends local KV/state
|
||||
- forwards resulting activation to next hop
|
||||
A routable recipe identifies separately:
|
||||
|
||||
### Decode
|
||||
- Source Model Artifact hash and optional derivative/slice hash.
|
||||
- Architecture and adapter version.
|
||||
- Tokenizer revision and vocabulary.
|
||||
- Weight quantization.
|
||||
- Activation interchange dtype/schema.
|
||||
- Backend compute dtype and backend implementation.
|
||||
- KV dtype/layout.
|
||||
- RoPE/context parameters.
|
||||
- llama.cpp commit and project patch version.
|
||||
- Shard range and endpoint ownership.
|
||||
|
||||
```http
|
||||
POST /v1/sessions/{session_id}/decode-step
|
||||
Content-Type: application/octet-stream
|
||||
X-Meshnet-Model: zai-org/GLM-5.2
|
||||
X-Meshnet-Position: 131072
|
||||
X-Meshnet-Shape: 1,1,6144
|
||||
X-Meshnet-Dtype: bfloat16
|
||||
Compatibility fails closed. Similar quantization labels or model names are not enough.
|
||||
|
||||
<one-step activation bytes>
|
||||
```
|
||||
## Admission and failure
|
||||
|
||||
The receiver:
|
||||
A recipe becomes routable only after a real local and distributed forward passes. Synthetic tests remain unit coverage.
|
||||
|
||||
- loads local KV/state by `session_id`
|
||||
- runs one decode step for assigned layers
|
||||
- appends one token position to local KV/state
|
||||
- forwards one-step activation
|
||||
Alpha failure behavior:
|
||||
|
||||
## GGUF / llama.cpp Integration
|
||||
- Deadline or node loss cancels the Route Session.
|
||||
- Every node releases KV and queued buffers.
|
||||
- Uncertain mutations are not replayed silently.
|
||||
- Retry starts from token zero on a newly compatible route.
|
||||
- No cross-node KV import is trusted until a later signed/compatible snapshot protocol exists.
|
||||
|
||||
The target llama.cpp integration needs more than `llama-server`.
|
||||
## Performance release contract
|
||||
|
||||
Required capabilities:
|
||||
Before native development proceeds, compare the current Transformers/safetensors backend with whole-model llama.cpp under controlled model/hardware/quality lanes.
|
||||
|
||||
- load full GGUF locally for immediate single-node performance
|
||||
- optionally load only selected tensors/layers
|
||||
- execute a layer range against inbound hidden states
|
||||
- expose outbound hidden states at a boundary
|
||||
- own per-session KV/state for only the loaded layer range
|
||||
- support prefill chunks and decode-step calls
|
||||
- expose model-specific cache metadata for DSA/MLA without requiring the tracker to understand tensor internals
|
||||
Final release compares distributed GGUF with distributed safetensors using thresholds locked before seeing final results.
|
||||
|
||||
If llama.cpp cannot expose these as stable APIs today, the collaboration target is an upstream extension rather than a long-lived fork.
|
||||
Required measurements:
|
||||
|
||||
## Failure Model
|
||||
- TTFT.
|
||||
- Prefill and decode tokens/sec.
|
||||
- Aggregate concurrency throughput.
|
||||
- p50/p95 latency.
|
||||
- Seam bytes and latency.
|
||||
- Queue/batch occupancy.
|
||||
- RSS, VRAM and KV pressure.
|
||||
- Output-quality drift.
|
||||
- Cancellation/failure cleanup.
|
||||
|
||||
Alpha behavior:
|
||||
The GGUF path ships only if it is faster at acceptable quality or enables a larger otherwise-unroutable model at useful measured speed.
|
||||
|
||||
- Route node drops during prefill: fail request and retry from scratch.
|
||||
- Route node drops during decode: fail request unless a recent KV snapshot exists.
|
||||
- Tracker restart: active sessions may be lost; completed billing records persist.
|
||||
- Node restart: local hot KV is lost.
|
||||
## Implementation sequence
|
||||
|
||||
Later behavior:
|
||||
1. Lock benchmark/performance contract.
|
||||
2. Define gRPC/protobuf and exact recipe identity.
|
||||
3. Pin llama.cpp and create the minimal patch stack.
|
||||
4. Implement dense-Llama range loading and boundary parity.
|
||||
5. Implement concurrent local KV.
|
||||
6. Build and integrate the standalone worker.
|
||||
7. Pass local two-process real-model acceptance.
|
||||
8. Pass real heterogeneous two-machine acceptance.
|
||||
9. Add continuous batching and failure hardening.
|
||||
10. Enforce the GGUF-versus-safetensors release gate.
|
||||
11. Add Qwen3/Qwen3-MoE as a separately certified adapter.
|
||||
12. Prepare narrow upstream collaboration patches/tests.
|
||||
|
||||
- periodic KV snapshots for long sessions
|
||||
- prefix cache reuse across requests
|
||||
- route repair when a semantically equivalent node has the same model/layer range and compatible cache snapshot
|
||||
|
||||
## Security And Trust
|
||||
|
||||
Activation/KV data can reveal user prompts. Public volunteer routes are not private. For sensitive workloads:
|
||||
|
||||
- use private swarms
|
||||
- allow paid trusted nodes
|
||||
- encrypt transport
|
||||
- avoid storing hot KV on untrusted shared cache servers
|
||||
- sample outputs for fraud/audit as already planned in alpha hardening
|
||||
See [the Ralph backlog](prd.json) and [implementation strategy](implementation-strategy.md).
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Distributed GGUF Decision Framework
|
||||
|
||||
> **Superseded for active implementation decisions.** The grill was resolved on 2026-07-13. Use [implementation-strategy.md](implementation-strategy.md), [architecture.md](architecture.md), [ADR-0020](ADR-0020-distributed-gguf-runtime.md), and [prd.json](prd.json). This file remains as historical decision rationale.
|
||||
|
||||
This framework is for grilling open decisions. It keeps decisions tied to project vocabulary and implementation gates instead of vague "distributed inference" language.
|
||||
|
||||
## Core Vocabulary
|
||||
|
||||
242
.scratch/distributed-gguf-runtime/evidence/DGR-002/README.md
Normal file
242
.scratch/distributed-gguf-runtime/evidence/DGR-002/README.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# DGR-002 — Adopt the versioned gRPC Shard protocol
|
||||
|
||||
Status: **done**. Every acceptance criterion is met with real command output.
|
||||
Evidence class: **synthetic/unit** — this story defines a schema and proves both
|
||||
languages agree on it. No model, GPU, network peer or benchmark is involved, and
|
||||
none is claimed.
|
||||
|
||||
## 1. Summary
|
||||
|
||||
`packages/node/native/proto/shard_runtime.proto` is now the semantic contract for
|
||||
the native Shard data plane: Protocol Buffers over gRPC/HTTP2 (ADR-0020). Python
|
||||
and C++ both generate from it, and a shared committed conformance vector proves
|
||||
they encode it identically — byte for byte.
|
||||
|
||||
Design decisions worth carrying forward:
|
||||
|
||||
- **Everything gRPC gives you is *also* in the schema.** Deadline, cancellation,
|
||||
identity and flow control are carried as fields, not left to HTTP/2 metadata,
|
||||
because the existing relay carries these frames as **opaque binary**. A relayed
|
||||
frame has no HTTP/2 context to inherit a deadline or a channel identity from.
|
||||
If it is not in the schema, it does not survive the relay.
|
||||
- **Cancellation is both in-band and out-of-band.** `CancelSignal` rides the
|
||||
stream; `Cancel` is also a unary RPC. A cancel that can only travel down a
|
||||
stream that flow control has wedged is not a cancel.
|
||||
- **Checksums cover the uncompressed payload.** Compression is a per-hop
|
||||
transport decision (reusing the existing `activation_compression` policies), so
|
||||
a checksum over the compressed frame would be invalidated by a hop that merely
|
||||
chose differently.
|
||||
- **Application-level flow-control credits, not just HTTP/2 windows.** HTTP/2
|
||||
bounds *bytes in flight*; it does not bound how much *work* a worker has queued,
|
||||
and a relayed frame gets no window at all. Credits bound queue occupancy and KV
|
||||
pressure, and negotiation takes the strictest bound of either peer so a sender
|
||||
cannot talk a worker into unbounded queues.
|
||||
|
||||
## 2. Files changed
|
||||
|
||||
New:
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `packages/node/native/proto/shard_runtime.proto` | The schema (sha256 `9e211660…`, see `protocol.json`) |
|
||||
| `packages/node/native/CMakeLists.txt` | C++ generation + build wiring + ctest |
|
||||
| `packages/node/native/tests/test_shard_protocol_conformance.cpp` | C++ conformance test |
|
||||
| `packages/node/native/testdata/*.binpb` | Committed cross-language vectors |
|
||||
| `packages/node/native/README.md` | How to regenerate and build |
|
||||
| `packages/node/meshnet_node/native_protocol/__init__.py` | Public Python surface |
|
||||
| `packages/node/meshnet_node/native_protocol/codec.py` | Bundle encode/decode, fragmentation, CRC32C, chunking, FC negotiation |
|
||||
| `packages/node/meshnet_node/native_protocol/conformance.py` | Canonical vectors shared by both languages |
|
||||
| `packages/node/meshnet_node/native_protocol/generated/` | Generated Python stubs (committed) |
|
||||
| `scripts/generate_native_protocol.py` | Python generation, with `--check` |
|
||||
| `scripts/generate_protocol_goldens.py` | Vector generation, with `--check` |
|
||||
| `scripts/bootstrap_native_toolchain.sh` | Builds protobuf C++ from source |
|
||||
| `tests/test_native_shard_protocol.py` | 45 Python tests |
|
||||
|
||||
Modified:
|
||||
|
||||
- `packages/node/pyproject.toml` — added runtime floors `grpcio>=1.82.1` and
|
||||
`protobuf>=7.35.0`, matching the committed generated-code requirements; new
|
||||
`proto` extra pinning `grpcio-tools==1.82.1`.
|
||||
- `packages/node/meshnet_node/activation_compression.py` — optional bounded zstd
|
||||
output for untrusted protocol frames; existing callers remain compatible.
|
||||
- `packages/node/meshnet_node/native_protocol/__init__.py` — exports negotiated
|
||||
bound constants and whole-session-message validation.
|
||||
|
||||
The canonical PRD marks only DGR-002 passed. `git status` before this story was clean.
|
||||
|
||||
## 3. Commands and real results
|
||||
|
||||
See `commands.txt` for the exact ordered list. Results:
|
||||
|
||||
```
|
||||
python scripts/generate_native_protocol.py --check -> generated stubs are up to date
|
||||
python scripts/generate_protocol_goldens.py --check -> conformance vectors are up to date
|
||||
|
||||
cmake -S packages/node/native -B build/native -DCMAKE_PREFIX_PATH=/tmp/pbsrc/install
|
||||
-- gRPC C++ not found: building message types only (sufficient for the conformance test)
|
||||
cmake --build build/native -j -> Built target shard_protocol_conformance
|
||||
ctest --test-dir build/native --output-on-failure -> 1/1 Test #1: shard_protocol_conformance ... Passed
|
||||
100% tests passed out of 1
|
||||
|
||||
cmp build/native/cpp_roundtrip.binpb \
|
||||
packages/node/native/testdata/session_request_golden.binpb -> identical (exit 0)
|
||||
|
||||
pytest -q tests/test_native_shard_protocol.py -> 45 passed
|
||||
pytest -q tests/test_native_shard_protocol.py \
|
||||
tests/test_activation_compression.py -> 51 passed
|
||||
pytest -q (final full suite) -> 728 passed, 12 skipped
|
||||
pytest -q tests/test_tracker_routing.py::test_tracker_dashboard_can_cancel_inflight_proxy
|
||||
(after an earlier flaky full-suite failure) -> 1 passed, 1 passed, 1 passed
|
||||
clean minimum-runtime import + codec smoke test -> passed
|
||||
grpcio==1.82.1, protobuf==7.35.0
|
||||
compileall -q packages tests -> OK (exit 0)
|
||||
git diff --check -> clean (exit 0)
|
||||
```
|
||||
|
||||
The C++ lane was rebuilt from scratch by Ralph (`rm -rf build/native`) using only
|
||||
the documented commands, and reproduced the same result. During controller
|
||||
review the user explicitly chose not to repeat the destructive build-directory
|
||||
cleanup, so the independent controller relied on the recorded CMake/CTest run
|
||||
while reproducing every Python/generation/full-suite gate.
|
||||
|
||||
### Controller review corrections
|
||||
|
||||
Independent controller review found and fixed two classes of issue before
|
||||
integration:
|
||||
|
||||
1. Generated stubs required gRPC 1.82.1 and Protobuf 7.35.0, while the initial
|
||||
package metadata allowed much older runtimes that could fail at import time.
|
||||
2. Flow-control bounds were described but not enforced by the reference decoder.
|
||||
Tensor declarations, shape rank/dimensions, fragment/tensor counts, fragments,
|
||||
wire bodies, whole bundles, complete session messages (including envelope
|
||||
overhead), and zstd window/output expansion are now fail-closed against the
|
||||
negotiated/default bounds. Unspecified bundle versions, compression and
|
||||
checksums are rejected rather than interpreted as valid data.
|
||||
3. Negotiated initial credits could exceed `max_inflight_chunks`; credits are now
|
||||
capped by the settled in-flight limit.
|
||||
|
||||
Controller results: protocol tests `45 passed`; protocol plus shared compression
|
||||
tests `51 passed`; final full suite `728 passed, 12 skipped`. A clean environment
|
||||
at the declared minimum gRPC/Protobuf runtime versions imported both generated
|
||||
stub modules and round-tripped the codec. Generation checks, `compileall`, static
|
||||
secret scan, and `git diff --check` all passed.
|
||||
|
||||
### Full-suite note — a pre-existing flaky test
|
||||
|
||||
`tests/test_tracker_routing.py::test_tracker_dashboard_can_cancel_inflight_proxy`
|
||||
is **flaky on a clean tree, independent of this story**. Reproduction, run
|
||||
*before any DGR-002 file existed* (working tree clean, `git status` empty):
|
||||
|
||||
```
|
||||
pytest -q -> 1 failed, 682 passed, 12 skipped
|
||||
FAILED tests/test_tracker_routing.py::test_tracker_dashboard_can_cancel_inflight_proxy
|
||||
|
||||
# same test, three consecutive isolated runs on the same clean tree:
|
||||
pytest -q tests/test_tracker_routing.py::test_tracker_dashboard_can_cancel_inflight_proxy
|
||||
-> 1 passed in 1.76s
|
||||
-> 1 failed in 4.39s
|
||||
-> 1 passed in 1.10s
|
||||
```
|
||||
|
||||
It is a timing race in proxy cancellation (a 3-second in-flight generation raced
|
||||
against the cancel assertion), not a deterministic failure, and it touches no code
|
||||
this story changes. One controller full-suite run reported exactly that one failure
|
||||
(`1 failed, 719 passed, 12 skipped`); three immediate isolated retries all passed
|
||||
in 1.11 seconds, and the final exact-code full suite was green (`728 passed,
|
||||
12 skipped`). It is flagged for whoever owns the tracker cancel path and is **not**
|
||||
fixed here, since silently touching another story's code is out of scope.
|
||||
|
||||
## 4. Acceptance criteria
|
||||
|
||||
| Criterion | Where it is proven |
|
||||
|---|---|
|
||||
| Schema for capability, health, session stream, release, cancellation | `shard_runtime.proto` `service ShardRuntime`; `test_service_exposes_capability_health_session_release_and_cancel` |
|
||||
| One long-lived bidi stream per Activation Seam, with deadlines, cancellation, flow control, structured errors | `rpc Session (stream) returns (stream)`; `test_session_is_one_long_lived_bidirectional_stream`; `Envelope.deadline_unix_nanos`, `CancelSignal` + unary `Cancel`, `FlowControl`, `ShardError` |
|
||||
| Bounded chunking for prefill; small decode fast path | `ChunkInfo` + `plan_prefill_chunks` (128-token bound, ADR-0008); `DecodeStep`; `test_prefill_is_split_into_bounded_token_aligned_chunks`, `test_decode_fast_path_is_much_smaller_than_a_full_envelope_chunk` |
|
||||
| Envelope carries schema version, work id, session id, epoch, fingerprint, range/effective start, phase, position, idempotency step, cache expectation, compression, checksum | `Envelope` + `NamedTensor`; `test_envelope_carries_every_field_the_protocol_promises` asserts against the **descriptor**, so deleting a field from the `.proto` fails the test |
|
||||
| Versioned named-tensor bundle: name, shape, dtype, byte order, fragments | `TensorBundle`/`NamedTensor`/`TensorFragment`; `test_named_tensor_bundle_is_versioned_and_fully_described`, `test_bundle_round_trips_multiple_named_tensors` |
|
||||
| Round-trip + compatibility tests in Python and C++ | 45 Python tests; C++ `ctest` 1/1; cross-language byte equality |
|
||||
| Targeted pytest passes | 45 passed |
|
||||
| `compileall packages tests` | exit 0 |
|
||||
| `git diff --check` | exit 0 |
|
||||
| Default tests deterministic, download-free, credit-free, GPU-free | Pure in-memory protobuf; no model, no network, no GPU |
|
||||
| Full deterministic pytest passes, or pre-existing failure recorded | Final exact-code run: 728 passed, 12 skipped; earlier sole flaky failure documented with clean-tree reproduction and 3/3 passing retries |
|
||||
|
||||
## 5. How the cross-language claim is actually earned
|
||||
|
||||
Two codecs that each round-trip their own output prove only that each is
|
||||
self-consistent. Instead:
|
||||
|
||||
1. Python builds the canonical `SessionRequest` and commits its bytes.
|
||||
2. The C++ test parses **those** bytes, asserts every field, recomputes the CRC32C
|
||||
**from the polynomial in independent C++ code**, reassembles the multi-fragment
|
||||
tensor, and re-serializes to `cpp_roundtrip.binpb`.
|
||||
3. `test_cpp_and_python_agree_byte_for_byte` asserts that file equals the golden.
|
||||
|
||||
Compatibility is tested in both languages: an unknown field from a newer peer
|
||||
survives a parse/serialize hop (a Shard forwards activations — silently stripping
|
||||
fields would corrupt a route it is merely a waypoint on), and a sparse message
|
||||
from an older peer parses to proto3 defaults.
|
||||
|
||||
## 6. Limitations and deferred work
|
||||
|
||||
- **gRPC C++ was not built or linked.** The C++ lane verifies the *schema* (message
|
||||
types), not a running gRPC C++ server, because this machine has no gRPC C++ stack
|
||||
and building it is a large dependency the conformance test does not need.
|
||||
`CMakeLists.txt` already generates and exports `shard_runtime_grpc` when
|
||||
`find_package(gRPC)` succeeds. **DGR-008 must install gRPC C++ and extend
|
||||
`scripts/bootstrap_native_toolchain.sh`.**
|
||||
- **No wire is exercised.** No client, server, or stream lifecycle exists yet — no
|
||||
deadline actually fires, no credit is actually consumed. This story defines and
|
||||
proves the contract; DGR-008/DGR-009 implement it.
|
||||
- The protobuf C++ toolchain used here was installed to `/tmp/pbsrc/install` (ephemeral).
|
||||
`scripts/bootstrap_native_toolchain.sh` reproduces it; prefer a durable prefix such
|
||||
as `build/native-toolchain`.
|
||||
- `crc32c` has a pure-Python fallback (used here) and picks up `google_crc32c` when
|
||||
present. The fallback is byte-exact but slow; a worker on the hot path should install
|
||||
the native package. Not a correctness limitation.
|
||||
- Compression on the wire is zstd-or-none only, matching the existing seam.
|
||||
|
||||
## 7. Compatibility and migration notes
|
||||
|
||||
- **This does not change the existing HTTP activation wire.** `X-Meshnet-Wire` stays
|
||||
at `2` and the legacy `/forward` path is untouched. The native protocol is a
|
||||
*separate* contract with its own `SchemaVersion`, starting at 1. Nothing in this
|
||||
story is on any live request path — it is additive.
|
||||
- Semantics are deliberately preserved from the existing ADRs so the two transports
|
||||
mean the same thing: `effective_start_layer` (ADR-0012), `CacheMode`/`expected_past_len`
|
||||
and `ERROR_CODE_CACHE_MISS` mapping to today's HTTP 409 `cache_miss` (ADR-0022),
|
||||
bfloat16 boundary dtype and 128-token prefill chunks (ADR-0008), fingerprint/recipe
|
||||
identity mirroring the capability report (ADR-0023).
|
||||
- `TensorFragment` field 5 (`uncompressed_size`) is **reserved**: it was removed
|
||||
because `NamedTensor.total_bytes` is the single source of truth. Never recycle it —
|
||||
a recycled field number is the one schema change peers cannot detect, because the
|
||||
bytes still parse.
|
||||
- Committed Python stubs are guarded by `--check` in the test suite, so they cannot
|
||||
drift from the schema unnoticed.
|
||||
|
||||
## 8. Handoff to dependent stories
|
||||
|
||||
- **DGR-003 (runtime recipe/fingerprint):** populate `Fingerprint`
|
||||
(`model_artifact_digest`, `runtime_recipe_digest`, `recipe_id`, `recipe_version`,
|
||||
`catalogue_version`). The mismatch outcome is already specified:
|
||||
`ERROR_CODE_FINGERPRINT_MISMATCH`. Do not invent a second identity struct.
|
||||
- **DGR-005/006 (range loading, architecture boundary):** the boundary payload is a
|
||||
**named bundle**, not a bare tensor — a boundary needing more than one tensor is
|
||||
already representable. Execute `[effective_start_layer, end_layer)`, never from
|
||||
`start_layer`.
|
||||
- **DGR-007 (concurrent sessions/KV):** isolate on `(route_session_id, route_epoch)`.
|
||||
`CacheExpectation`/`CacheResult` and `ERROR_CODE_CACHE_MISS` are the contract; a
|
||||
decode step whose `expected_past_len` does not match **must** miss, never fall back
|
||||
to a silent stateless forward. `idempotency_step` means a retried step is
|
||||
acknowledged (`Ack.duplicate`), not re-applied — re-applying advances the KV cache
|
||||
twice and desynchronises the route.
|
||||
- **DGR-008 (C++ worker):** link `shard_runtime_grpc` from `CMakeLists.txt`; you must
|
||||
first install gRPC C++ (see limitations). Honour `FlowControl` credits and the
|
||||
`max_chunk_bytes` bound. Use `packages/node/meshnet_node/native_protocol/codec.py`
|
||||
as the reference for fragment reassembly and checksum validation.
|
||||
- **DGR-009 (Meshnet integration):** the relay may carry these serialized frames as
|
||||
opaque binary — that is exactly why deadline/cancel/identity are in-band. Do not add
|
||||
a second control plane.
|
||||
- **Anyone editing the schema:** run both `--check` scripts; if a vector legitimately
|
||||
changes, regenerate it and say so, because the C++ test asserts those exact bytes.
|
||||
@@ -0,0 +1,45 @@
|
||||
# DGR-002 — exact commands, in order. Run from the repository root.
|
||||
# Interpreter: <repo>/.venv/bin/python (CPython 3.14.6). Deterministic, GPU-free,
|
||||
# no model download, no API credits.
|
||||
|
||||
# --- toolchain (this machine had no protoc, no cmake, no protobuf C++ headers)
|
||||
.venv/bin/python -m pip install grpcio-tools==1.82.1 grpcio==1.82.1 cmake==4.4.0
|
||||
scripts/bootstrap_native_toolchain.sh /tmp/pbsrc/install # protobuf C++ 33.1 + abseil 20250814.1
|
||||
|
||||
# --- schema generation (Python stubs; committed)
|
||||
.venv/bin/python scripts/generate_native_protocol.py
|
||||
.venv/bin/python scripts/generate_native_protocol.py --check # -> "generated stubs are up to date"
|
||||
|
||||
# --- cross-language conformance vectors (committed)
|
||||
.venv/bin/python scripts/generate_protocol_goldens.py
|
||||
.venv/bin/python scripts/generate_protocol_goldens.py --check # -> "conformance vectors are up to date"
|
||||
|
||||
# --- C++ generation, build and conformance test
|
||||
cmake -S packages/node/native -B build/native -DCMAKE_PREFIX_PATH=/tmp/pbsrc/install
|
||||
cmake --build build/native -j"$(nproc)"
|
||||
ctest --test-dir build/native --output-on-failure # -> 1/1 Passed
|
||||
cmp build/native/cpp_roundtrip.binpb packages/node/native/testdata/session_request_golden.binpb
|
||||
|
||||
# --- Python tests
|
||||
.venv/bin/python -m pytest -q tests/test_native_shard_protocol.py # -> 29 passed
|
||||
.venv/bin/python -m pytest -q # full suite
|
||||
|
||||
# --- repository gates
|
||||
.venv/bin/python -m compileall -q packages tests
|
||||
git diff --check
|
||||
|
||||
# --- independent controller review after Ralph
|
||||
PYTHONPATH=packages/node .venv/bin/python -m pytest -q tests/test_native_shard_protocol.py
|
||||
# -> 45 passed
|
||||
PYTHONPATH=packages/node .venv/bin/python -m pytest -q \
|
||||
tests/test_native_shard_protocol.py tests/test_activation_compression.py
|
||||
# -> 51 passed
|
||||
PYTHONPATH=packages/node .venv/bin/python -m pytest -q
|
||||
# -> final exact-code run: 728 passed, 12 skipped
|
||||
for i in 1 2 3; do PYTHONPATH=packages/node .venv/bin/python -m pytest -q \
|
||||
tests/test_tracker_routing.py::test_tracker_dashboard_can_cancel_inflight_proxy; done
|
||||
# -> 1 passed, 1 passed, 1 passed
|
||||
# clean minimum-runtime venv: protobuf==7.35.0 grpcio==1.82.1
|
||||
# generated pb2 + pb2_grpc imports and one-byte codec round trip -> passed
|
||||
# The user chose to rely on Ralph's recorded successful C++ CMake/CTest run
|
||||
# rather than repeat deletion of an isolated generated build directory.
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"schema_version": "SCHEMA_VERSION_1",
|
||||
"bundle_version": 1,
|
||||
"proto_path": "packages/node/native/proto/shard_runtime.proto",
|
||||
"proto_sha256": "9e211660b3fcefc88bcdf3851c3571088c00349aacb5adc5ef45083c83d0cce2",
|
||||
"protoc": "grpc_tools 1.82.1 (python) / protobuf 33.1 (C++)",
|
||||
"service": {
|
||||
"GetCapability": {
|
||||
"client_streaming": false,
|
||||
"server_streaming": false
|
||||
},
|
||||
"Health": {
|
||||
"client_streaming": false,
|
||||
"server_streaming": false
|
||||
},
|
||||
"Session": {
|
||||
"client_streaming": true,
|
||||
"server_streaming": true
|
||||
},
|
||||
"Release": {
|
||||
"client_streaming": false,
|
||||
"server_streaming": false
|
||||
},
|
||||
"Cancel": {
|
||||
"client_streaming": false,
|
||||
"server_streaming": false
|
||||
}
|
||||
},
|
||||
"envelope_fields": [
|
||||
"cache_expectation",
|
||||
"chunk",
|
||||
"deadline_unix_nanos",
|
||||
"fingerprint",
|
||||
"idempotency_step",
|
||||
"phase",
|
||||
"position",
|
||||
"route_epoch",
|
||||
"route_session_id",
|
||||
"schema_version",
|
||||
"shard_range",
|
||||
"work_id"
|
||||
],
|
||||
"named_tensor_fields": [
|
||||
"byte_order",
|
||||
"checksum",
|
||||
"compression",
|
||||
"dtype",
|
||||
"fragments",
|
||||
"name",
|
||||
"shape",
|
||||
"total_bytes"
|
||||
],
|
||||
"phases": [
|
||||
"PHASE_UNSPECIFIED",
|
||||
"PHASE_PREFILL",
|
||||
"PHASE_DECODE",
|
||||
"PHASE_RELEASE",
|
||||
"PHASE_CANCEL"
|
||||
],
|
||||
"error_codes": [
|
||||
"ERROR_CODE_UNSPECIFIED",
|
||||
"ERROR_CODE_SCHEMA_UNSUPPORTED",
|
||||
"ERROR_CODE_FINGERPRINT_MISMATCH",
|
||||
"ERROR_CODE_EPOCH_STALE",
|
||||
"ERROR_CODE_SHARD_RANGE_MISMATCH",
|
||||
"ERROR_CODE_CACHE_MISS",
|
||||
"ERROR_CODE_RESOURCE_EXHAUSTED",
|
||||
"ERROR_CODE_PAYLOAD_CORRUPT",
|
||||
"ERROR_CODE_CANCELLED",
|
||||
"ERROR_CODE_DEADLINE_EXCEEDED",
|
||||
"ERROR_CODE_FLOW_CONTROL_VIOLATION",
|
||||
"ERROR_CODE_INTERNAL"
|
||||
],
|
||||
"bounds": {
|
||||
"max_prefill_chunk_tokens": 128,
|
||||
"max_chunk_bytes": 4194304,
|
||||
"max_fragment_bytes": 1048576,
|
||||
"max_inflight_chunks": 8,
|
||||
"max_fragments_per_tensor": 64,
|
||||
"max_tensors_per_bundle": 64,
|
||||
"max_tensor_rank": 8,
|
||||
"max_tensor_dimension": 2147483647,
|
||||
"whole_session_message_enforced": true
|
||||
},
|
||||
"golden_vectors": {
|
||||
"session_request_golden.binpb": "c2c3df8a717ddeae7bd99624d2c7f34c09a518988de990237fe313b75cff0817",
|
||||
"capability_report_golden.binpb": "71ac5f150775f398515b43a63596a5cbe8d2ad607e7e4de56bd44fbe7987080c"
|
||||
},
|
||||
"verification": {
|
||||
"python_protocol_tests": "45 passed",
|
||||
"python_protocol_and_compression_tests": "51 passed",
|
||||
"full_suite": "728 passed, 12 skipped",
|
||||
"minimum_runtime": "grpcio 1.82.1 / protobuf 7.35.0 passed import and codec smoke"
|
||||
}
|
||||
}
|
||||
15
.scratch/distributed-gguf-runtime/evidence/README.md
Normal file
15
.scratch/distributed-gguf-runtime/evidence/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Ralph task evidence
|
||||
|
||||
Each completed story creates `evidence/<TASK-ID>/README.md`. Fresh dependent iterations must read it before coding.
|
||||
|
||||
Required README sections:
|
||||
|
||||
1. Summary and acceptance decision.
|
||||
2. Exact files changed.
|
||||
3. Commands run and real exit/results.
|
||||
4. Correctness, performance and hardware evidence classification.
|
||||
5. Known limitations and deferred work.
|
||||
6. Compatibility/migration notes.
|
||||
7. Explicit handoff for each dependent story.
|
||||
|
||||
Store raw machine-readable metrics, manifests and protocol artifacts beside the README. Never store secrets, model weights, build outputs or Ralph iteration logs here.
|
||||
241
.scratch/distributed-gguf-runtime/implementation-strategy.md
Normal file
241
.scratch/distributed-gguf-runtime/implementation-strategy.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# Focused implementation strategy: performant concurrent distributed inference
|
||||
|
||||
Status: Accepted planning direction
|
||||
Last updated: 2026-07-13
|
||||
|
||||
## Product objective
|
||||
|
||||
Enable clients to run top open models that do not fit on one consumer machine by combining independently owned model Shards into performant, concurrent Inference Routes.
|
||||
|
||||
The project is not trying to reproduce every vLLM feature or support every inference engine. It is optimizing for:
|
||||
|
||||
1. Models larger than one node's RAM/VRAM.
|
||||
2. Useful interactive decode speed on consumer CPU, AMD, NVIDIA, Vulkan, and mixed routes where certified.
|
||||
3. Multiple concurrent Route Sessions without cache corruption or global serialization.
|
||||
4. A lean runtime with one control plane and one primary GGUF engine.
|
||||
5. Measured improvement over the existing Transformers/safetensors implementation.
|
||||
|
||||
## Current reality
|
||||
|
||||
The existing project already owns the differentiating distributed control plane:
|
||||
|
||||
- Tracker-selected contiguous Shards.
|
||||
- Stable Route Sessions.
|
||||
- Local per-Shard Hot KV State in the Transformers reference backend.
|
||||
- Binary Activation Seams.
|
||||
- Relay/direct routing, cancellation, telemetry, billing, and capability admission.
|
||||
- Persistent relay and direct transport optimizations.
|
||||
|
||||
The missing production path is a native GGUF execution worker that can load and execute only an assigned layer range while retaining local Hot KV State for concurrent Route Sessions.
|
||||
|
||||
Whole-model llama.cpp, vLLM, and existing Transformers serving remain baselines or optional route kinds. They are not substitutes for native distributed Shards.
|
||||
|
||||
## Performance hypothesis—not an assumption
|
||||
|
||||
GGUF itself is a format. Performance comes from llama.cpp/GGML's quantized kernels, memory layout, mmap, backend scheduling, and reduced working set.
|
||||
|
||||
Quantized GGUF may be faster or may merely fit a larger model. Comparisons against safetensors must report both speed and quality because BF16 safetensors and Q4/Q8 GGUF are not numerically equivalent.
|
||||
|
||||
Before expensive native work, establish controlled lanes:
|
||||
|
||||
- Same model architecture and upstream revision.
|
||||
- Same machine, prompt set, context, output length, sampling policy, and concurrency.
|
||||
- Transformers/safetensors BF16 or the current production recipe.
|
||||
- llama.cpp GGUF F16/BF16 or Q8 correctness lane where available.
|
||||
- Q4_K_M or selected production quantization performance/fit lane.
|
||||
- TTFT, prefill tok/s, decode tok/s, p50/p95 latency, RSS, VRAM, artifact size, energy where available, and output-quality drift.
|
||||
|
||||
The program proceeds only if llama.cpp/GGUF provides at least one meaningful advantage recorded in a machine-readable performance contract:
|
||||
|
||||
- Better decode or aggregate throughput at acceptable quality; or
|
||||
- Materially lower memory that makes the target model routable while preserving useful throughput.
|
||||
|
||||
## Parallelism we will use
|
||||
|
||||
### Public Inference Route: layer/pipeline parallelism
|
||||
|
||||
Each node independently executes one contiguous Shard. Activations cross seams; weights and Hot KV State remain local.
|
||||
|
||||
This is the only public cross-machine model-parallel primitive in the first runtime.
|
||||
|
||||
### Per-node continuous batching
|
||||
|
||||
Autoregressive tokens remain sequential within one generation. Throughput comes from batching decode steps from multiple active Route Sessions inside each node using llama.cpp batches and sequence IDs or bounded context pools.
|
||||
|
||||
This is essential. A worker that globally serializes sessions is not production-ready.
|
||||
|
||||
### Multiple complete routes: data parallelism
|
||||
|
||||
The Tracker may select multiple complete routes for independent requests. This increases network throughput and availability without requiring collectives between routes.
|
||||
|
||||
### Trusted composite node: optional tensor/expert parallelism
|
||||
|
||||
Tensor parallelism and expert parallelism require frequent collectives and tight compatibility. They may be used later inside one operator-controlled composite node or managed cluster exposed as one logical provider. They are not public WAN routing primitives.
|
||||
|
||||
### Deferred mechanisms
|
||||
|
||||
- Disaggregated prefill and KV transfer.
|
||||
- Speculative decoding.
|
||||
- Cross-route prefix snapshots.
|
||||
- Route repair with KV migration.
|
||||
- Public tensor/expert parallel collectives.
|
||||
|
||||
They remain out of the critical path until the native layer route passes performance and concurrency gates.
|
||||
|
||||
## Reuse decisions
|
||||
|
||||
### llama.cpp/GGML: primary runtime substrate
|
||||
|
||||
Reuse:
|
||||
|
||||
- GGUF parsing and mmap.
|
||||
- Quantized kernels.
|
||||
- CPU, CUDA, HIP/ROCm, Vulkan, Metal, and other supported backends.
|
||||
- Tokenizer and model architecture implementations.
|
||||
- KV and sequence operations.
|
||||
- Backend scheduler and graph execution.
|
||||
|
||||
Maintain a small exact-commit fork only for the missing local seam:
|
||||
|
||||
- Range-aware tensor ownership/loading.
|
||||
- Architecture-defined boundary input/output.
|
||||
- Intermediate boundary output without tail normalization.
|
||||
- Layer-filtered KV and sequence mapping.
|
||||
|
||||
Keep networking, Tracker logic, billing, and public protocol outside llama.cpp. Upstream generic hooks where possible.
|
||||
|
||||
### vLLM: concepts and optional managed backend
|
||||
|
||||
Use unmodified vLLM only as:
|
||||
|
||||
- A whole-model node backend.
|
||||
- A managed TP/PP/EP cluster represented as one logical provider.
|
||||
- A performance/correctness baseline.
|
||||
|
||||
Adapt concepts, not runtime code:
|
||||
|
||||
- Named intermediate tensor bundles.
|
||||
- Continuous batching and request-owner maps.
|
||||
- Versioned KV-transfer compatibility fingerprints.
|
||||
- Explicit send/receive/abort/failure lifecycle.
|
||||
- Load telemetry and unbiased route selection.
|
||||
|
||||
Do not fork vLLM for public Shards and do not transplant PagedAttention, Torch process groups, or GGUF-plugin kernels into the llama.cpp worker.
|
||||
|
||||
### Nakshatra, prima.cpp, llama-gguf, LiGGUF, GPUStack
|
||||
|
||||
Use as source and test donors only:
|
||||
|
||||
- Nakshatra: partial-GGUF patches, daemon concepts, replay cases.
|
||||
- prima.cpp: selected tensor ownership and local-layer KV evidence.
|
||||
- llama-gguf: small protocol and integration-test patterns.
|
||||
- LiGGUF: Q8 activation transport and tensor-reduction reference.
|
||||
- historical GPUStack: resource preflight and role-oriented placement.
|
||||
|
||||
Do not adopt or fork their repositories wholesale.
|
||||
|
||||
## Battle-proven transport decision
|
||||
|
||||
Use gRPC over HTTP/2 with Protocol Buffers for the native C++ Shard worker protocol.
|
||||
|
||||
Why:
|
||||
|
||||
- Mature Python and C++ implementations.
|
||||
- Bidirectional streaming.
|
||||
- HTTP/2 flow control and connection reuse.
|
||||
- Deadlines, cancellation, status codes, TLS, authentication interceptors, and generated schemas.
|
||||
- Avoids inventing a socket protocol.
|
||||
|
||||
Scope boundary:
|
||||
|
||||
- OpenAI-compatible client/Gateway APIs remain HTTP/SSE.
|
||||
- Tracker/control APIs remain existing project interfaces.
|
||||
- One long-lived bidirectional gRPC stream serves one Route Session Activation Seam.
|
||||
- Existing relay/WebSocket infrastructure may carry the same versioned protobuf frames as opaque binary when direct gRPC reachability is unavailable.
|
||||
- Large prefill tensors are chunked into bounded frames; decode bundles stay small.
|
||||
- No QUIC/WebRTC/custom transport in this milestone.
|
||||
|
||||
The public boundary uses a versioned named-tensor bundle rather than one anonymous tensor because architecture boundaries can require more than `hidden_states`.
|
||||
|
||||
Minimum identity:
|
||||
|
||||
```text
|
||||
schema version
|
||||
request/work id
|
||||
Route Session id and route epoch
|
||||
Model Artifact and runtime recipe fingerprint
|
||||
Shard range and effective start
|
||||
phase: prefill/decode/release/cancel
|
||||
position/token range
|
||||
named tensors with shape/dtype/byte order
|
||||
compression and checksum
|
||||
idempotency step id
|
||||
cache expectation/result
|
||||
```
|
||||
|
||||
## Concurrency model
|
||||
|
||||
A native worker must not use one global serving sequence or one lock around all model execution.
|
||||
|
||||
Required ownership:
|
||||
|
||||
```text
|
||||
(Route Session id, route epoch)
|
||||
-> local sequence/context
|
||||
-> Shard-local Hot KV State
|
||||
-> bounded lease and memory accounting
|
||||
```
|
||||
|
||||
The node scheduler:
|
||||
|
||||
- Admits sessions against model memory and KV budget.
|
||||
- Forms compatible decode batches from active sessions.
|
||||
- Preserves per-session position and route order.
|
||||
- Applies bounded queues and backpressure.
|
||||
- Cancels/releases independently.
|
||||
- Reports queue, batch, KV, prefill, decode, and seam telemetry.
|
||||
|
||||
Initial deterministic gate: at least four concurrent sessions on a small certified model with no token/KV cross-talk. Final concurrency targets are hardware/recipe-specific and recorded by capability admission rather than hardcoded globally.
|
||||
|
||||
## Stage gates
|
||||
|
||||
### Gate A: performance hypothesis
|
||||
|
||||
Controlled safetensors-versus-GGUF benchmark produces a signed/reproducible report and locks thresholds. Stop native work if there is no meaningful speed or fit benefit.
|
||||
|
||||
### Gate B: local range parity
|
||||
|
||||
Two local processes own disjoint GGUF ranges and match whole-model llama.cpp within the certified numerical tolerance for prefill and greedy decode.
|
||||
|
||||
### Gate C: concurrent KV
|
||||
|
||||
Multiple Route Sessions prefill/decode concurrently with isolated local KV, bounded memory, cancellation, and release.
|
||||
|
||||
### Gate D: real distributed route
|
||||
|
||||
Two physical machines execute one model that uses both Shards. Synthetic activation tests do not satisfy this gate.
|
||||
|
||||
### Gate E: consumer-hardware performance
|
||||
|
||||
On certified consumer hardware, the GGUF route beats the current distributed safetensors route under the locked performance contract or enables a larger otherwise-unroutable model at useful measured speed.
|
||||
|
||||
### Gate F: architecture expansion
|
||||
|
||||
Only after dense Llama-family gates pass, add an explicit Qwen3/Qwen3-MoE adapter and certify it independently.
|
||||
|
||||
## Scope discipline
|
||||
|
||||
The following do not block the first production candidate:
|
||||
|
||||
- New cryptocurrency/economics work.
|
||||
- New artifact P2P protocol.
|
||||
- QUIC or WebRTC.
|
||||
- vLLM fork.
|
||||
- Whole-repository Nakshatra/prima adoption.
|
||||
- Every GGUF architecture.
|
||||
- Automatic route repair.
|
||||
- Prefix snapshot migration.
|
||||
- Speculative decoding.
|
||||
- A large-model marketing demo before small-model parity and concurrency pass.
|
||||
|
||||
Every optimization must preserve output contract, session isolation, cancellation, resource cleanup, capability admission, and per-node attribution.
|
||||
@@ -0,0 +1,59 @@
|
||||
# 01 — Lock the safetensors-versus-GGUF performance contract
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-001` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a runtime engineer, I need a controlled baseline so that GGUF work proceeds from measured speed, memory, and quality rather than reputation.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Benchmark harness and deterministic tests
|
||||
- evidence/DGR-001/performance-contract.json
|
||||
- Raw and summarized safetensors/GGUF benchmark evidence
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Benchmark the same model architecture/revision, machine, prompts, context lengths, output lengths, sampling policy, and concurrency across the current Transformers/safetensors recipe and whole-model llama.cpp recipes.
|
||||
- [ ] Separate correctness/quality lanes from quantized performance/fit lanes instead of claiming BF16 and Q4 are numerically equivalent.
|
||||
- [ ] Report TTFT, prefill tok/s, decode tok/s, p50/p95 latency, aggregate throughput, RSS, VRAM, artifact size, failures, and output drift in machine-readable JSON.
|
||||
- [ ] Add concurrency levels 1 and 4 where memory permits.
|
||||
- [ ] Write a versioned performance contract consumed by later release gates, including an explicit stop condition when llama.cpp/GGUF has no meaningful speed or fit benefit.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence
|
||||
- [ ] Model artifacts remain on the configured mounted-drive storage and never under /home
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-001/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- None. This story may start immediately.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,19 +0,0 @@
|
||||
# 01 — Route Session lifecycle
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## What to build
|
||||
|
||||
Add the narrowest end-to-end Route Session lifecycle that can be used by distributed inference routes: create a session, bind it to a selected Inference Route, expose status, and close it cleanly. This slice does not need real model cache yet; it proves stable session identity across the control plane and activation plane.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] A request can create a Route Session with a stable `session_id`, `route_id`, model preset, backend id, and route membership.
|
||||
- [ ] Every downstream activation request carries the same session identity and fails clearly if the session or route id does not match.
|
||||
- [ ] Session status reports phase, route nodes, model preset, backend id, created time, and last activity time.
|
||||
- [ ] Closing a session releases all registered per-session state.
|
||||
- [ ] Tests cover create, status, close, stale-session rejection, and wrong-route rejection.
|
||||
|
||||
## Blocked by
|
||||
|
||||
None - can start immediately.
|
||||
@@ -0,0 +1,59 @@
|
||||
# 02 — Adopt the versioned gRPC Shard protocol
|
||||
|
||||
Status: done
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-002` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a node developer, I need a battle-proven streaming protocol so that Python and C++ Shards communicate without a custom socket protocol.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- packages/node/native/proto/shard_runtime.proto
|
||||
- Reproducible Python/C++ schema generation and build wiring
|
||||
- Protocol round-trip and compatibility tests
|
||||
- evidence/DGR-002/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Add a Protocol Buffers schema for capability, health, session stream, release, and cancellation operations.
|
||||
- [x] Define one long-lived bidirectional gRPC stream per Route Session Activation Seam with deadlines, cancellation, flow control, and structured errors.
|
||||
- [x] Define bounded chunking for prefill and a small decode fast path.
|
||||
- [x] Carry schema version, request/work ID, Route Session ID, route epoch, artifact/recipe fingerprint, Shard range/effective start, phase, position, idempotency step, cache expectation, compression, and checksum.
|
||||
- [x] Define a versioned named-tensor bundle with per-tensor name, shape, dtype, byte order, and payload fragments.
|
||||
- [x] Add generated-schema round-trip and compatibility tests in Python and C++.
|
||||
- [x] Targeted pytest tests pass
|
||||
- [x] python -m compileall packages tests passes for Python changes
|
||||
- [x] git diff --check passes
|
||||
- [x] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [x] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [x] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [x] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [x] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [x] Write .scratch/distributed-gguf-runtime/evidence/DGR-002/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [x] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- None. This story may start immediately.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,20 +0,0 @@
|
||||
# 02 — Prefill/decode binary HTTP protocol
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## What to build
|
||||
|
||||
Split the activation protocol into explicit prefill and decode-step calls using the existing binary HTTP direction from ADR-0008. The completed slice should work against a stub backend so payload shape, route/session headers, relay preservation, and failure behavior are testable before real KV cache work begins.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Prefill accepts chunked binary activations with route/session metadata and forwards them through the selected route.
|
||||
- [ ] Decode-step accepts a one-step binary activation and forwards a one-step activation through the selected route.
|
||||
- [ ] Decode-step payload size is independent of prompt length in protocol tests.
|
||||
- [ ] Relay forwarding preserves route/session headers, shape, dtype, position, and wire version.
|
||||
- [ ] Legacy `/forward` either remains as a compatibility wrapper or fails with a clear wire-version error.
|
||||
- [ ] Tests cover prefill chunking, decode-step shape validation, relay preservation, and malformed header rejection.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- 01 — Route Session lifecycle.
|
||||
@@ -0,0 +1,57 @@
|
||||
# 03 — Define exact Artifact and runtime recipe identity
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-003` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As the Tracker, I need exact compatibility identity so that only numerically and operationally compatible Shards form an Inference Route.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Exact runtime recipe/fingerprint implementation
|
||||
- Tracker/node fail-closed admission tests
|
||||
- evidence/DGR-003/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Separate weight quantization, activation dtype, compute dtype, KV dtype/layout, tokenizer revision, architecture adapter, backend, and runtime version.
|
||||
- [ ] Bind derivative or split artifacts to an exact source Model Artifact hash and Shard range.
|
||||
- [ ] Produce a stable compatibility fingerprint used by capability admission and the gRPC handshake.
|
||||
- [ ] Fail closed on mismatched artifact, tokenizer, architecture, range, boundary schema, activation recipe, or cache layout.
|
||||
- [ ] Keep unsupported recipes registered-but-dark until a real distributed forward certifies them.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-003/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-002` must have `passes: true`; read `../evidence/DGR-002/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,21 +0,0 @@
|
||||
# 03 — Generation Telemetry and streaming response contract
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## What to build
|
||||
|
||||
Expose realtime Generation Telemetry for active Route Sessions and stream token deltas when the serving path can produce them. This slice should make long distributed requests observable before real large-model work begins.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] A client can observe route-session phase changes: queued, loading, prefill, decode, finalizing, completed, failed.
|
||||
- [ ] Telemetry includes prefill progress, generated token count, rolling tokens/sec, average tokens/sec, active route nodes, and failure reason.
|
||||
- [ ] Telemetry is available before the first output token.
|
||||
- [ ] A streaming response can include token deltas while telemetry remains available.
|
||||
- [ ] A non-streaming fallback still exposes telemetry until final answer or failure.
|
||||
- [ ] Route-node failure reports the last known phase and reason.
|
||||
- [ ] Tests cover telemetry updates, streaming token deltas, non-streaming fallback, and structured failure closeout.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- 01 — Route Session lifecycle.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 04 — Create the reproducible pinned llama.cpp patch stack
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-004` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a maintainer, I need a small auditable fork boundary so that upstream updates do not turn the runtime into an unmaintainable stitched codebase.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Exact llama.cpp upstream pin
|
||||
- Numbered minimal patch stack
|
||||
- Reproducible fetch/apply/build smoke
|
||||
- evidence/DGR-004/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Pin one exact llama.cpp commit through a reproducible source dependency mechanism.
|
||||
- [ ] Store a numbered minimal patch stack separately from Meshnet networking code.
|
||||
- [ ] Add a build script that applies/checks patches and builds the standalone worker without manual source copying.
|
||||
- [ ] Record upstream file/ABI assumptions and fail clearly when the pin changes.
|
||||
- [ ] Preserve upstream license and attribution notices.
|
||||
- [ ] Add a clean rebuild smoke test that does not download a model.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-004/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-001` must have `passes: true`; read `../evidence/DGR-001/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,23 +0,0 @@
|
||||
# 04 — PyTorch distributed KV reference route
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## What to build
|
||||
|
||||
Fix the existing distributed PyTorch route so it uses the Route Session and prefill/decode protocol to keep Hot KV State local to each Shard node. The visible behavior is that prefill processes the prompt once, and decode no longer recomputes or resends the full growing prompt for every token.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Distributed PyTorch prefill stores per-session cache/state on each Shard node.
|
||||
- [ ] Distributed PyTorch decode-step reads and appends local per-shard cache/state.
|
||||
- [ ] Decode activation seam payload is one token/hidden-state step after prefill.
|
||||
- [ ] The old full-growing-prompt decode loop is not used for models that support the reference cache path.
|
||||
- [ ] Unsupported model/cache APIs fail with an explicit backend capability error.
|
||||
- [ ] Session close or TTL cleanup releases per-shard cache.
|
||||
- [ ] Regression tests prove decode does not call the full prompt encoder for every generated token.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- 01 — Route Session lifecycle.
|
||||
- 02 — Prefill/decode binary HTTP protocol.
|
||||
- 03 — Generation Telemetry and streaming response contract.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 05 — Implement dense-Llama range-aware GGUF ownership
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-005` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a node, I need to map only my assigned dense-Llama Shard so that aggregate consumer memory can hold a model larger than one node.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Dense-Llama range-aware ownership implementation
|
||||
- Authoritative loaded-range introspection
|
||||
- Mapped/resident memory evidence
|
||||
- evidence/DGR-005/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Register and allocate only `blk.N.*` tensors in the assigned range.
|
||||
- [ ] Load embeddings only for the head and final norm/LM head only for the tail, including tied embeddings.
|
||||
- [ ] Prefer range-aware mapping from one exact source GGUF; if derivative sub-GGUFs are used temporarily, verify source/slice hashes and avoid claiming final artifact semantics.
|
||||
- [ ] Report authoritative loaded range and endpoint ownership from the model, not operator CLI claims.
|
||||
- [ ] Demonstrate mapped/resident memory scales with owned tensors rather than full model size.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-005/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-003` must have `passes: true`; read `../evidence/DGR-003/README.md` and verify its referenced files/commands.
|
||||
- `DGR-004` must have `passes: true`; read `../evidence/DGR-004/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,20 +0,0 @@
|
||||
# 05 — Local llama.cpp/GGUF backend
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## What to build
|
||||
|
||||
Add a local full-model GGUF backend so a node that can hold a GGUF model can serve it through the existing node API. This is the immediate CPU-performance path and the baseline for later distributed llama.cpp work.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] A node can start with backend `llama.cpp` or `gguf` for a local full-model GGUF artifact.
|
||||
- [ ] The node can answer an OpenAI-compatible chat completion through the existing API.
|
||||
- [ ] Startup and registration clearly report backend, quantization/artifact metadata, context cap, and local model path.
|
||||
- [ ] The PyTorch backend remains unchanged and selectable.
|
||||
- [ ] A smoke test or script validates backend wiring with a small GGUF model or a stubbed llama.cpp process.
|
||||
- [ ] A benchmark command can compare local PyTorch CPU and local GGUF CPU for the same small supported model when both are available.
|
||||
|
||||
## Blocked by
|
||||
|
||||
None - can start immediately.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 06 — Implement architecture-defined boundary input/output
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-006` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a Shard, I need to consume and emit the correct transformer boundary state so that disjoint processes reproduce whole-model execution.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Architecture boundary adapter
|
||||
- Whole-model/two-range parity tests and results
|
||||
- evidence/DGR-006/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Head accepts token IDs and owns token embedding.
|
||||
- [ ] Middle/tail bypass token embedding and accept the named boundary bundle.
|
||||
- [ ] Non-tail emits the unnormalized architecture-defined residual/boundary before final norm/head and before tail-only row pruning.
|
||||
- [ ] Tail emits logits or token output through an explicit sampling contract.
|
||||
- [ ] Dense-Llama whole-model versus two-range prefill and greedy-decode parity passes the documented tolerance.
|
||||
- [ ] The adapter interface fails closed for uncertified architectures.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-006/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-002` must have `passes: true`; read `../evidence/DGR-002/README.md` and verify its referenced files/commands.
|
||||
- `DGR-005` must have `passes: true`; read `../evidence/DGR-005/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,20 +0,0 @@
|
||||
# 06 — Model Artifact manifest and Shard advertisement
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## What to build
|
||||
|
||||
Introduce a Model Artifact manifest that separates storage distribution from route execution. A node should be able to verify local model files, determine which Shards it can serve, and advertise artifact/layer availability to the Tracker without contacting Hugging Face at request time.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Manifest records model preset, upstream revision, license, backend support, quantization, context cap, tokenizer artifacts, file hashes, piece hashes, and tensor/layer mapping where available.
|
||||
- [ ] A node can verify local artifacts against the manifest and reject corrupt or incomplete artifacts.
|
||||
- [ ] A node can derive advertised Shard ranges from the manifest and local files.
|
||||
- [ ] Tracker registration can include artifact id, backend id, Shard range, and verification status.
|
||||
- [ ] Tracker coverage can distinguish model-layer coverage from artifact availability.
|
||||
- [ ] Tests cover valid manifest registration, corrupt artifact rejection, and missing layer/tensor metadata.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- 01 — Route Session lifecycle.
|
||||
@@ -0,0 +1,60 @@
|
||||
# 07 — Add isolated concurrent local Hot KV State
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-007` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a client, I need concurrent Route Sessions to retain independent per-Shard cache so that one request cannot clear or corrupt another.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Concurrent local KV/session manager
|
||||
- Isolation, eviction, cancellation and cleanup tests
|
||||
- evidence/DGR-007/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Map `(Route Session ID, route epoch)` to an isolated llama sequence or bounded context.
|
||||
- [ ] Allocate KV only for owned layers.
|
||||
- [ ] Support prefill append, decode append, truncate, release, TTL/LRU eviction, and explicit cache-miss response.
|
||||
- [ ] Reject stale epochs and incompatible cache recipes.
|
||||
- [ ] At least four concurrent sessions on a small model complete without token or KV cross-talk.
|
||||
- [ ] Cancellation/release of one session leaves other sessions intact and memory returns to the configured budget.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-007/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-006` must have `passes: true`; read `../evidence/DGR-006/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,25 +0,0 @@
|
||||
# 07 — llama.cpp layer-boundary prototype
|
||||
|
||||
Status: ready-for-human
|
||||
|
||||
## What to build
|
||||
|
||||
Build a local prototype that proves whether llama.cpp/libllama can support the platform's distributed execution contract: execute a selected layer range, accept inbound hidden states, emit outbound hidden states, and own per-session cache for only the loaded/served range.
|
||||
|
||||
This is the collaboration package for upstream llama.cpp. The target is an upstreamable API shape, not a permanent fork.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] A small llama.cpp-supported GGUF model can be split into a two-process localhost head/tail prototype.
|
||||
- [ ] The head process runs embeddings and early layers, then emits hidden states at an Activation Seam.
|
||||
- [ ] The tail process accepts hidden states, runs later layers plus output head, and produces logits/tokens comparable to single-process execution.
|
||||
- [ ] Prefill is performed once and decode-step seam payload is one hidden-state step per generated token.
|
||||
- [ ] Each process owns only its own per-session cache/state.
|
||||
- [ ] The prototype records the minimum upstream API needed for layer-range execution, hidden-state I/O, partial loading/introspection, and per-session KV ownership.
|
||||
- [ ] If upstream support is unavailable, the issue ends with a concrete recommendation: upstream proposal, narrow adapter fork, or keep GGUF distribution local-only for now.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- 02 — Prefill/decode binary HTTP protocol.
|
||||
- 05 — Local llama.cpp/GGUF backend.
|
||||
- 06 — Model Artifact manifest and Shard advertisement.
|
||||
@@ -0,0 +1,65 @@
|
||||
# 08 — Build the standalone C++ gRPC Shard worker
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-008` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a node runtime, I need one supervised native process so that llama.cpp internals remain behind a stable project-owned protocol.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Standalone C++ gRPC worker
|
||||
- Fake-model Python/C++ integration tests
|
||||
- Lifecycle and bounded-failure evidence
|
||||
- evidence/DGR-008/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Worker exposes capability, health, session stream, release, cancellation, and metrics services from DGR-002.
|
||||
- [ ] Worker loads one exact Artifact/recipe/Shard identity and refuses mismatched requests.
|
||||
- [ ] Streaming path enforces bounded messages, flow control, deadlines, idempotency, and independent session cancellation.
|
||||
- [ ] Worker does not expose raw llama.cpp RPC or arbitrary GGML graph execution.
|
||||
- [ ] Graceful shutdown releases sessions; crash behavior is bounded and observable.
|
||||
- [ ] Python integration tests run against a fake model mode without model downloads.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-008/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-002` must have `passes: true`; read `../evidence/DGR-002/README.md` and verify its referenced files/commands.
|
||||
- `DGR-003` must have `passes: true`; read `../evidence/DGR-003/README.md` and verify its referenced files/commands.
|
||||
- `DGR-004` must have `passes: true`; read `../evidence/DGR-004/README.md` and verify its referenced files/commands.
|
||||
- `DGR-006` must have `passes: true`; read `../evidence/DGR-006/README.md` and verify its referenced files/commands.
|
||||
- `DGR-007` must have `passes: true`; read `../evidence/DGR-007/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,24 +0,0 @@
|
||||
# 08 — Networked distributed GGUF route
|
||||
|
||||
Status: pending
|
||||
|
||||
## What to build
|
||||
|
||||
Run a GGUF-backed model over a real multi-node Inference Route using the resolved Route Session, binary HTTP prefill/decode protocol, local Hot KV State, Generation Telemetry, and alpha fail-fast behavior.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Two machines can form one GGUF-backed Inference Route over contiguous Shards.
|
||||
- [ ] Prefill builds local per-shard cache/state and decode-step uses one-step seam payloads.
|
||||
- [ ] The client receives streamed token deltas when supported by the GGUF path.
|
||||
- [ ] The client receives Generation Telemetry for phase, generated tokens, tokens/sec, route health, and failure reason.
|
||||
- [ ] Route-node loss fails the Route Session cleanly; no automatic repair is attempted in alpha.
|
||||
- [ ] Tracker metrics show prefill tokens/sec, decode tokens/sec, seam latency, queue depth, and cache memory by node.
|
||||
- [ ] Billing/audit records identify route membership and layer/token work for the completed or failed session.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- 03 — Generation Telemetry and streaming response contract.
|
||||
- 04 — PyTorch distributed KV reference route.
|
||||
- 06 — Model Artifact manifest and Shard advertisement.
|
||||
- 07 — llama.cpp layer-boundary prototype.
|
||||
@@ -1,21 +0,0 @@
|
||||
# 09 — DeepSeek-V4-Flash support audit
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## What to build
|
||||
|
||||
Audit `deepseek-ai/DeepSeek-V4-Flash` as the first serious large-model target after the small GGUF protocol smoke test. The output is a compatibility matrix and a recommended runtime path, not full production support.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Verify current PyTorch/Transformers load and generation semantics for DeepSeek-V4-Flash from primary model documentation.
|
||||
- [ ] Verify vLLM and SGLang support status from primary runtime documentation or release notes.
|
||||
- [ ] Verify whether a GGUF/llama.cpp quantization path exists or would need upstream work.
|
||||
- [ ] Estimate artifact size, active parameter behavior, and 128K cache memory by Shard range.
|
||||
- [ ] Identify required backend capability flags for the Tracker.
|
||||
- [ ] Produce a compatibility matrix: PyTorch, vLLM, SGLang, llama.cpp/GGUF.
|
||||
- [ ] End with one recommendation: first runtime path, blocked pending upstream, or defer.
|
||||
|
||||
## Blocked by
|
||||
|
||||
None - can start immediately.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 09 — Integrate the native worker with Meshnet
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-009` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As the existing node service, I need a GGUF Shard backend adapter so that the Tracker, relay, billing, telemetry, and capability admission remain the sole control plane.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Meshnet GGUF backend adapter
|
||||
- Registration, routing, relay, telemetry and billing tests
|
||||
- evidence/DGR-009/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Implement the existing model-backend surface without changing Transformers behavior.
|
||||
- [ ] Registration carries exact validated GGUF recipe, Shard, backend and concurrency/KV capacity.
|
||||
- [ ] Tracker forms only complete compatible routes and keeps uncertified recipes dark.
|
||||
- [ ] Direct routes use gRPC streams; relayed routes carry the same versioned protobuf frames as opaque binary through the existing relay seam.
|
||||
- [ ] Existing request/work IDs, cancellation, Generation Telemetry, billing, and per-node attribution remain correlated.
|
||||
- [ ] No vLLM, Nakshatra, prima.cpp, or custom-engine control plane becomes a core dependency.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-009/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-003` must have `passes: true`; read `../evidence/DGR-003/README.md` and verify its referenced files/commands.
|
||||
- `DGR-008` must have `passes: true`; read `../evidence/DGR-008/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,20 +0,0 @@
|
||||
# 10 — GLM-5.2 and Ornith follow-up support audit
|
||||
|
||||
Status: pending
|
||||
|
||||
## What to build
|
||||
|
||||
Audit GLM-5.2 and Ornith after the smaller protocol smoke path and DeepSeek-V4-Flash audit. The output is a follow-up compatibility matrix focused on architecture/runtime blockers: DSA/MLA, hybrid attention, cache accounting, and GGUF/llama.cpp support.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Verify GLM-5.2 PyTorch/Transformers serving requirements and cache semantics from primary model documentation.
|
||||
- [ ] Verify llama.cpp/GGUF support status for `glm_moe_dsa` or equivalent architecture support.
|
||||
- [ ] Verify Ornith/Qwen3.5-MoE and hybrid attention support status in the candidate runtimes.
|
||||
- [ ] Estimate artifact size and 128K cache memory by Shard range for each model.
|
||||
- [ ] Identify smallest quality-preserving quantization worth testing.
|
||||
- [ ] Convert each runtime blocker into a follow-up issue or upstream collaboration note.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- 09 — DeepSeek-V4-Flash support audit.
|
||||
@@ -0,0 +1,62 @@
|
||||
# 10 — Pass local real-model two-process acceptance
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-010` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a release engineer, I need real local distributed parity before involving network variability.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Real local two-process commands and configuration
|
||||
- Raw parity, memory and performance results
|
||||
- evidence/DGR-010/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Two local worker processes open disjoint dense-Llama ranges from the certified Artifact.
|
||||
- [ ] Prefill and at least 32 greedy decode tokens match whole-model llama.cpp within the certified tolerance.
|
||||
- [ ] Each worker retains only its own tensors and Hot KV State.
|
||||
- [ ] Four concurrent Route Sessions pass isolation and cleanup checks.
|
||||
- [ ] Report TTFT, prefill/decode throughput, seam bytes/latency, worker RSS/VRAM, KV memory, batch size, and queue time.
|
||||
- [ ] Killing one worker produces a bounded structured failure rather than a deadlock.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence
|
||||
- [ ] Model artifacts remain on the configured mounted-drive storage and never under /home
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-010/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-009` must have `passes: true`; read `../evidence/DGR-009/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -0,0 +1,62 @@
|
||||
# 11 — Pass a real heterogeneous two-machine route
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-011` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a consumer-hardware operator, I need two physical machines to execute one GGUF model so that the distributed claim is real.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Two-machine hardware/network/runtime manifest
|
||||
- Raw real-route metrics and output evidence
|
||||
- evidence/DGR-011/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Tracker selects two physical nodes with disjoint Shards and one exact certified recipe/compatibility class.
|
||||
- [ ] Actual CPU/GPU execution occurs on both nodes; synthetic workers do not satisfy acceptance.
|
||||
- [ ] Prefill/decode, concurrent-session isolation, telemetry, cancellation, and cleanup pass over the real transport/relay path.
|
||||
- [ ] Exact hardware, network, backend, model hash, route, commands, and raw metrics are recorded.
|
||||
- [ ] A model or recipe larger than one participating node's admitted memory is exercised when available.
|
||||
- [ ] Output drift is measured and incompatible mixed backends fail closed.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence
|
||||
- [ ] Model artifacts remain on the configured mounted-drive storage and never under /home
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-011/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-010` must have `passes: true`; read `../evidence/DGR-010/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -0,0 +1,63 @@
|
||||
# 12 — Implement continuous batching and bounded admission
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-012` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a node operator, I need active sessions batched safely so that concurrency increases aggregate throughput rather than serializing every request.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Continuous batching/admission scheduler
|
||||
- Concurrency 1/2/4/8 report
|
||||
- Queue, batch and KV-pressure evidence
|
||||
- evidence/DGR-012/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Node scheduler admits sessions against weight, KV, scratch, and queue budgets.
|
||||
- [ ] Compatible decode steps from multiple sessions form llama.cpp batches while preserving per-session positions and outputs.
|
||||
- [ ] Prefill does not starve decode; scheduling policy and bounds are explicit.
|
||||
- [ ] Backpressure prevents unbounded queued activations or KV growth.
|
||||
- [ ] Capability telemetry reports active sessions, queue depth, batch occupancy, KV pressure, prefill/decode rates, and rejected admissions.
|
||||
- [ ] Concurrency 1/2/4/8 benchmark identifies saturation and shows no cross-session corruption.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-012/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-007` must have `passes: true`; read `../evidence/DGR-007/README.md` and verify its referenced files/commands.
|
||||
- `DGR-009` must have `passes: true`; read `../evidence/DGR-009/README.md` and verify its referenced files/commands.
|
||||
- `DGR-010` must have `passes: true`; read `../evidence/DGR-010/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -0,0 +1,62 @@
|
||||
# 13 — Harden failure, cancellation, and restart semantics
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-013` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a client, I need failures to be bounded and explicit so that distributed speed does not come with hanging or corrupted generations.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Failure/cancel/restart test matrix
|
||||
- Resource cleanup and billing-state evidence
|
||||
- evidence/DGR-013/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Deadlines and heartbeat/health loss terminate blocked stream operations.
|
||||
- [ ] Cancellation propagates across every Shard and releases local KV and queued buffers.
|
||||
- [ ] Duplicate steps are idempotent; uncertain mutations are never replayed silently.
|
||||
- [ ] Alpha failover restarts from token zero on a newly compatible route rather than importing unverified KV.
|
||||
- [ ] Worker death, stream reset, malformed bundle, stale epoch, and cache miss tests pass.
|
||||
- [ ] Billing/work records distinguish completed, cancelled, failed, and unverified work.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-013/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-008` must have `passes: true`; read `../evidence/DGR-008/README.md` and verify its referenced files/commands.
|
||||
- `DGR-009` must have `passes: true`; read `../evidence/DGR-009/README.md` and verify its referenced files/commands.
|
||||
- `DGR-012` must have `passes: true`; read `../evidence/DGR-012/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -0,0 +1,65 @@
|
||||
# 14 — Enforce the GGUF-versus-safetensors release gate
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-014` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As the product owner, I need an end-to-end comparison so that the native runtime ships only if it advances model access or performance.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Immutable comparison against DGR-001 thresholds
|
||||
- Machine-readable final report
|
||||
- Ship/optimize/stop recommendation
|
||||
- evidence/DGR-014/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Run current distributed safetensors and distributed GGUF routes on the same certified model/hardware/network scenario where technically comparable.
|
||||
- [ ] Report quality, TTFT, prefill/decode throughput, aggregate concurrency throughput, p95 latency, seam cost, memory, KV pressure, failures, and cleanup.
|
||||
- [ ] Evaluate against the DGR-001 performance contract without changing thresholds after seeing results.
|
||||
- [ ] Ship recommendation is one of: promote GGUF, optimize a measured bottleneck with a new bounded task, or stop the native track.
|
||||
- [ ] Results clearly separate quantization gains from transport/runtime gains.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence
|
||||
- [ ] Model artifacts remain on the configured mounted-drive storage and never under /home
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-014/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-001` must have `passes: true`; read `../evidence/DGR-001/README.md` and verify its referenced files/commands.
|
||||
- `DGR-011` must have `passes: true`; read `../evidence/DGR-011/README.md` and verify its referenced files/commands.
|
||||
- `DGR-012` must have `passes: true`; read `../evidence/DGR-012/README.md` and verify its referenced files/commands.
|
||||
- `DGR-013` must have `passes: true`; read `../evidence/DGR-013/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -0,0 +1,61 @@
|
||||
# 15 — Add and certify a Qwen3/Qwen3-MoE adapter
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-015` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a client seeking top models, I need a separately certified MoE-capable architecture after the dense runtime proves stable.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Qwen3-family architecture adapter
|
||||
- Architecture-specific parity/admission/performance results
|
||||
- evidence/DGR-015/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Implement explicit tensor ownership, router/top-k, expert/shared-expert, Q/K normalization, boundary bundle, and cache semantics for the selected Qwen3 family recipe.
|
||||
- [ ] Do not reuse the dense-Llama adapter through unchecked name substitutions.
|
||||
- [ ] Whole-model versus distributed prefill/decode parity passes the architecture-specific tolerance.
|
||||
- [ ] Expert memory ownership and communication are measured.
|
||||
- [ ] Real consumer-hardware acceptance and capability admission pass before the recipe becomes routable.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence
|
||||
- [ ] Model artifacts remain on the configured mounted-drive storage and never under /home
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-015/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-014` must have `passes: true`; read `../evidence/DGR-014/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -0,0 +1,60 @@
|
||||
# 16 — Produce the upstream llama.cpp collaboration package
|
||||
|
||||
Status: ready-for-agent
|
||||
|
||||
## Mandatory fresh-session context
|
||||
|
||||
- Read [RALPH-CONTEXT.md](../RALPH-CONTEXT.md) completely before changing code.
|
||||
- This issue is `DGR-016` in [prd.json](../prd.json).
|
||||
- Read the evidence README for every dependency listed below.
|
||||
- Inspect current code and `git status`; historical text and previous agent claims are not evidence.
|
||||
|
||||
## Description
|
||||
|
||||
As a maintainer, I need narrow upstreamable proposals so that our patch burden can shrink without asking llama.cpp to own Meshnet networking.
|
||||
|
||||
## Expected durable outputs
|
||||
|
||||
- Narrow upstream patches/tests
|
||||
- Generic API design note
|
||||
- Human-ready llama.cpp outreach package
|
||||
- evidence/DGR-016/README.md
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Separate generic llama.cpp hooks from Meshnet protocol/control-plane code.
|
||||
- [ ] Prepare minimal reproducible examples and tests for range-aware loading, boundary input/output, and layer-filtered KV.
|
||||
- [ ] Compare the proposal with Nakshatra and prima.cpp evidence and explain why the API is generally useful.
|
||||
- [ ] Preserve one scoped commit/patch per concern against the exact upstream pin.
|
||||
- [ ] Produce an outreach document suitable for Georgi/llama.cpp maintainers; actual sending remains a human action.
|
||||
- [ ] Targeted pytest tests pass
|
||||
- [ ] python -m compileall packages tests passes for Python changes
|
||||
- [ ] git diff --check passes
|
||||
- [ ] Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free
|
||||
- [ ] Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction
|
||||
- [ ] Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched
|
||||
- [ ] llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched
|
||||
- [ ] Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code
|
||||
- [ ] Read and verify every dependency evidence README before relying on dependency behavior
|
||||
- [ ] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||
- [ ] Write .scratch/distributed-gguf-runtime/evidence/DGR-016/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||
|
||||
## Dependency handoff
|
||||
|
||||
- `DGR-010` must have `passes: true`; read `../evidence/DGR-010/README.md` and verify its referenced files/commands.
|
||||
|
||||
## Finish contract
|
||||
|
||||
- Create the task evidence directory and durable handoff required above.
|
||||
- Preserve real failures and blockers; never fabricate benchmark, model, test or hardware output.
|
||||
- Change this issue to `Status: done` only after all criteria pass.
|
||||
- Emit `<promise>COMPLETE</promise>` only after the evidence handoff exists.
|
||||
|
||||
## References
|
||||
|
||||
- [Ralph execution context](../RALPH-CONTEXT.md)
|
||||
- [PRD](../PRD.md)
|
||||
- [Implementation strategy](../implementation-strategy.md)
|
||||
- [Current architecture](../architecture.md)
|
||||
- [Architecture decision](../ADR-0020-distributed-gguf-runtime.md)
|
||||
@@ -1,32 +1,35 @@
|
||||
# Distributed GGUF Runtime Milestones
|
||||
# Distributed GGUF runtime milestones
|
||||
|
||||
## Proposed Breakdown
|
||||
## Gate A — measured runtime value
|
||||
|
||||
| Order | Issue | Title | Blocked by | User-visible proof |
|
||||
|---:|---|---|---|---|
|
||||
| 1 | [01](./issues/01-route-session-lifecycle.md) | Route Session lifecycle | None | Stable route/session status and cleanup |
|
||||
| 2 | [02](./issues/02-prefill-decode-binary-http.md) | Prefill/decode binary HTTP protocol | 01 | Stub route proves prefill chunks and one-step decode payloads |
|
||||
| 3 | [03](./issues/03-generation-telemetry-and-streaming.md) | Generation Telemetry and streaming response contract | 01 | Client sees route progress and streamed deltas when available |
|
||||
| 4 | [04](./issues/04-pytorch-distributed-kv-reference.md) | PyTorch distributed KV reference route | 01, 02, 03 | Distributed PyTorch decode stops full-prompt recompute |
|
||||
| 5 | [05](./issues/05-local-llamacpp-gguf-backend.md) | Local llama.cpp/GGUF backend | None | Local GGUF model serves through node API |
|
||||
| 6 | [06](./issues/06-model-artifact-manifest.md) | Model Artifact manifest and Shard advertisement | 01 | Node verifies artifacts and advertises serveable Shards |
|
||||
| 7 | [07](./issues/07-llamacpp-layer-boundary-prototype.md) | llama.cpp layer-boundary prototype | 02, 05, 06 | Local two-process GGUF route identifies upstream API |
|
||||
| 8 | [08](./issues/08-networked-distributed-gguf-route.md) | Networked distributed GGUF route | 03, 04, 06, 07 | Two machines serve one GGUF route with telemetry |
|
||||
| 9 | [09](./issues/09-deepseek-v4-flash-support-audit.md) | DeepSeek-V4-Flash support audit | None | Runtime recommendation for first serious large model |
|
||||
| 10 | [10](./issues/10-glm52-ornith-followup-audit.md) | GLM-5.2 and Ornith follow-up support audit | 09 | Follow-up compatibility matrix and upstream blockers |
|
||||
- DGR-001 locks the safetensors-versus-GGUF performance/fit/quality contract.
|
||||
- DGR-002 can proceed independently and defines the battle-proven backend-neutral wire protocol.
|
||||
- DGR-003 builds exact recipe identity on DGR-002.
|
||||
- Expensive native llama.cpp work remains gated by DGR-001.
|
||||
|
||||
## First Three To Implement
|
||||
## Gate B — minimal native execution seam
|
||||
|
||||
1. **01 — Route Session lifecycle**: makes every later cache, telemetry, and route decision concrete.
|
||||
2. **02 — Prefill/decode binary HTTP protocol**: proves the payload shape and route/session headers before model internals.
|
||||
3. **03 — Generation Telemetry and streaming response contract**: gives every later long-running route a visible user experience and failure surface.
|
||||
- DGR-004 creates the reproducible pinned fork boundary.
|
||||
- DGR-005 implements dense-Llama range ownership.
|
||||
- DGR-006 proves architecture-defined boundary parity.
|
||||
|
||||
## Parallel Work
|
||||
## Gate C — concurrent production worker
|
||||
|
||||
- **05 — Local llama.cpp/GGUF backend** can run in parallel with 01–03 because it is a full-model local backend.
|
||||
- **09 — DeepSeek-V4-Flash support audit** can run in parallel because it is research/compatibility work.
|
||||
- DGR-007 isolates concurrent Hot KV State.
|
||||
- DGR-008 exposes the native worker over gRPC.
|
||||
- DGR-009 integrates the worker without replacing Meshnet's control plane.
|
||||
- DGR-010 passes local real-model two-process acceptance.
|
||||
|
||||
## Human-Gated Work
|
||||
## Gate D — real consumer-hardware route
|
||||
|
||||
- **07 — llama.cpp layer-boundary prototype** is the collaboration point with Georgi/upstream llama.cpp.
|
||||
- **08 — Networked distributed GGUF route** should wait until the PyTorch reference route proves the cache/session contract.
|
||||
- DGR-011 passes two-physical-machine execution.
|
||||
- DGR-012 adds continuous batching and bounded admission.
|
||||
- DGR-013 hardens failure and cancellation.
|
||||
|
||||
## Gate E — product release decision
|
||||
|
||||
- DGR-014 compares distributed GGUF against the current distributed safetensors route under locked thresholds.
|
||||
- DGR-015 adds Qwen3/Qwen3-MoE only after the dense runtime passes.
|
||||
- DGR-016 prepares narrow upstream llama.cpp collaboration material.
|
||||
|
||||
No later gate may be claimed from synthetic workers or documentation-only evidence.
|
||||
|
||||
511
.scratch/distributed-gguf-runtime/prd.json
Normal file
511
.scratch/distributed-gguf-runtime/prd.json
Normal file
@@ -0,0 +1,511 @@
|
||||
{
|
||||
"name": "Performant Concurrent Distributed GGUF Runtime",
|
||||
"branchName": "ralph/performant-concurrent-distributed-gguf",
|
||||
"description": "Benchmark-gated native llama.cpp/GGUF Shards with gRPC streaming, concurrent local KV, continuous batching, real heterogeneous acceptance, and a measured release gate against Transformers/safetensors.",
|
||||
"userStories": [
|
||||
{
|
||||
"id": "DGR-001",
|
||||
"title": "Lock the safetensors-versus-GGUF performance contract",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/01-lock-the-safetensors-versus-gguf-performance-contract.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a runtime engineer, I need a controlled baseline so that GGUF work proceeds from measured speed, memory, and quality rather than reputation.",
|
||||
"acceptanceCriteria": [
|
||||
"Benchmark the same model architecture/revision, machine, prompts, context lengths, output lengths, sampling policy, and concurrency across the current Transformers/safetensors recipe and whole-model llama.cpp recipes.",
|
||||
"Separate correctness/quality lanes from quantized performance/fit lanes instead of claiming BF16 and Q4 are numerically equivalent.",
|
||||
"Report TTFT, prefill tok/s, decode tok/s, p50/p95 latency, aggregate throughput, RSS, VRAM, artifact size, failures, and output drift in machine-readable JSON.",
|
||||
"Add concurrency levels 1 and 4 where memory permits.",
|
||||
"Write a versioned performance contract consumed by later release gates, including an explicit stop condition when llama.cpp/GGUF has no meaningful speed or fit benefit.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence",
|
||||
"Model artifacts remain on the configured mounted-drive storage and never under /home",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-001/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 2,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/01-lock-the-safetensors-versus-gguf-performance-contract.md",
|
||||
"dependsOn": []
|
||||
},
|
||||
{
|
||||
"id": "DGR-002",
|
||||
"title": "Adopt the versioned gRPC Shard protocol",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/02-adopt-the-versioned-grpc-shard-protocol.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a node developer, I need a battle-proven streaming protocol so that Python and C++ Shards communicate without a custom socket protocol.",
|
||||
"acceptanceCriteria": [
|
||||
"Add a Protocol Buffers schema for capability, health, session stream, release, and cancellation operations.",
|
||||
"Define one long-lived bidirectional gRPC stream per Route Session Activation Seam with deadlines, cancellation, flow control, and structured errors.",
|
||||
"Define bounded chunking for prefill and a small decode fast path.",
|
||||
"Carry schema version, request/work ID, Route Session ID, route epoch, artifact/recipe fingerprint, Shard range/effective start, phase, position, idempotency step, cache expectation, compression, and checksum.",
|
||||
"Define a versioned named-tensor bundle with per-tensor name, shape, dtype, byte order, and payload fragments.",
|
||||
"Add generated-schema round-trip and compatibility tests in Python and C++.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-002/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 1,
|
||||
"passes": true,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/02-adopt-the-versioned-grpc-shard-protocol.md",
|
||||
"dependsOn": []
|
||||
},
|
||||
{
|
||||
"id": "DGR-003",
|
||||
"title": "Define exact Artifact and runtime recipe identity",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/03-define-exact-artifact-and-runtime-recipe-identity.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs the Tracker, I need exact compatibility identity so that only numerically and operationally compatible Shards form an Inference Route.",
|
||||
"acceptanceCriteria": [
|
||||
"Separate weight quantization, activation dtype, compute dtype, KV dtype/layout, tokenizer revision, architecture adapter, backend, and runtime version.",
|
||||
"Bind derivative or split artifacts to an exact source Model Artifact hash and Shard range.",
|
||||
"Produce a stable compatibility fingerprint used by capability admission and the gRPC handshake.",
|
||||
"Fail closed on mismatched artifact, tokenizer, architecture, range, boundary schema, activation recipe, or cache layout.",
|
||||
"Keep unsupported recipes registered-but-dark until a real distributed forward certifies them.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-003/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 3,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/03-define-exact-artifact-and-runtime-recipe-identity.md",
|
||||
"dependsOn": [
|
||||
"DGR-002"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-004",
|
||||
"title": "Create the reproducible pinned llama.cpp patch stack",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/04-create-the-reproducible-pinned-llama-cpp-patch-stack.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a maintainer, I need a small auditable fork boundary so that upstream updates do not turn the runtime into an unmaintainable stitched codebase.",
|
||||
"acceptanceCriteria": [
|
||||
"Pin one exact llama.cpp commit through a reproducible source dependency mechanism.",
|
||||
"Store a numbered minimal patch stack separately from Meshnet networking code.",
|
||||
"Add a build script that applies/checks patches and builds the standalone worker without manual source copying.",
|
||||
"Record upstream file/ABI assumptions and fail clearly when the pin changes.",
|
||||
"Preserve upstream license and attribution notices.",
|
||||
"Add a clean rebuild smoke test that does not download a model.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-004/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 4,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/04-create-the-reproducible-pinned-llama-cpp-patch-stack.md",
|
||||
"dependsOn": [
|
||||
"DGR-001"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-005",
|
||||
"title": "Implement dense-Llama range-aware GGUF ownership",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/05-implement-dense-llama-range-aware-gguf-ownership.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a node, I need to map only my assigned dense-Llama Shard so that aggregate consumer memory can hold a model larger than one node.",
|
||||
"acceptanceCriteria": [
|
||||
"Register and allocate only `blk.N.*` tensors in the assigned range.",
|
||||
"Load embeddings only for the head and final norm/LM head only for the tail, including tied embeddings.",
|
||||
"Prefer range-aware mapping from one exact source GGUF; if derivative sub-GGUFs are used temporarily, verify source/slice hashes and avoid claiming final artifact semantics.",
|
||||
"Report authoritative loaded range and endpoint ownership from the model, not operator CLI claims.",
|
||||
"Demonstrate mapped/resident memory scales with owned tensors rather than full model size.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-005/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 5,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/05-implement-dense-llama-range-aware-gguf-ownership.md",
|
||||
"dependsOn": [
|
||||
"DGR-003",
|
||||
"DGR-004"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-006",
|
||||
"title": "Implement architecture-defined boundary input/output",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/06-implement-architecture-defined-boundary-input-output.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a Shard, I need to consume and emit the correct transformer boundary state so that disjoint processes reproduce whole-model execution.",
|
||||
"acceptanceCriteria": [
|
||||
"Head accepts token IDs and owns token embedding.",
|
||||
"Middle/tail bypass token embedding and accept the named boundary bundle.",
|
||||
"Non-tail emits the unnormalized architecture-defined residual/boundary before final norm/head and before tail-only row pruning.",
|
||||
"Tail emits logits or token output through an explicit sampling contract.",
|
||||
"Dense-Llama whole-model versus two-range prefill and greedy-decode parity passes the documented tolerance.",
|
||||
"The adapter interface fails closed for uncertified architectures.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-006/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 6,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/06-implement-architecture-defined-boundary-input-output.md",
|
||||
"dependsOn": [
|
||||
"DGR-002",
|
||||
"DGR-005"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-007",
|
||||
"title": "Add isolated concurrent local Hot KV State",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/07-add-isolated-concurrent-local-hot-kv-state.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a client, I need concurrent Route Sessions to retain independent per-Shard cache so that one request cannot clear or corrupt another.",
|
||||
"acceptanceCriteria": [
|
||||
"Map `(Route Session ID, route epoch)` to an isolated llama sequence or bounded context.",
|
||||
"Allocate KV only for owned layers.",
|
||||
"Support prefill append, decode append, truncate, release, TTL/LRU eviction, and explicit cache-miss response.",
|
||||
"Reject stale epochs and incompatible cache recipes.",
|
||||
"At least four concurrent sessions on a small model complete without token or KV cross-talk.",
|
||||
"Cancellation/release of one session leaves other sessions intact and memory returns to the configured budget.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-007/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 7,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/07-add-isolated-concurrent-local-hot-kv-state.md",
|
||||
"dependsOn": [
|
||||
"DGR-006"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-008",
|
||||
"title": "Build the standalone C++ gRPC Shard worker",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/08-build-the-standalone-c-grpc-shard-worker.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a node runtime, I need one supervised native process so that llama.cpp internals remain behind a stable project-owned protocol.",
|
||||
"acceptanceCriteria": [
|
||||
"Worker exposes capability, health, session stream, release, cancellation, and metrics services from DGR-002.",
|
||||
"Worker loads one exact Artifact/recipe/Shard identity and refuses mismatched requests.",
|
||||
"Streaming path enforces bounded messages, flow control, deadlines, idempotency, and independent session cancellation.",
|
||||
"Worker does not expose raw llama.cpp RPC or arbitrary GGML graph execution.",
|
||||
"Graceful shutdown releases sessions; crash behavior is bounded and observable.",
|
||||
"Python integration tests run against a fake model mode without model downloads.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-008/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 8,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/08-build-the-standalone-c-grpc-shard-worker.md",
|
||||
"dependsOn": [
|
||||
"DGR-002",
|
||||
"DGR-003",
|
||||
"DGR-004",
|
||||
"DGR-006",
|
||||
"DGR-007"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-009",
|
||||
"title": "Integrate the native worker with Meshnet",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/09-integrate-the-native-worker-with-meshnet.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs the existing node service, I need a GGUF Shard backend adapter so that the Tracker, relay, billing, telemetry, and capability admission remain the sole control plane.",
|
||||
"acceptanceCriteria": [
|
||||
"Implement the existing model-backend surface without changing Transformers behavior.",
|
||||
"Registration carries exact validated GGUF recipe, Shard, backend and concurrency/KV capacity.",
|
||||
"Tracker forms only complete compatible routes and keeps uncertified recipes dark.",
|
||||
"Direct routes use gRPC streams; relayed routes carry the same versioned protobuf frames as opaque binary through the existing relay seam.",
|
||||
"Existing request/work IDs, cancellation, Generation Telemetry, billing, and per-node attribution remain correlated.",
|
||||
"No vLLM, Nakshatra, prima.cpp, or custom-engine control plane becomes a core dependency.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-009/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 9,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/09-integrate-the-native-worker-with-meshnet.md",
|
||||
"dependsOn": [
|
||||
"DGR-003",
|
||||
"DGR-008"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-010",
|
||||
"title": "Pass local real-model two-process acceptance",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/10-pass-local-real-model-two-process-acceptance.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a release engineer, I need real local distributed parity before involving network variability.",
|
||||
"acceptanceCriteria": [
|
||||
"Two local worker processes open disjoint dense-Llama ranges from the certified Artifact.",
|
||||
"Prefill and at least 32 greedy decode tokens match whole-model llama.cpp within the certified tolerance.",
|
||||
"Each worker retains only its own tensors and Hot KV State.",
|
||||
"Four concurrent Route Sessions pass isolation and cleanup checks.",
|
||||
"Report TTFT, prefill/decode throughput, seam bytes/latency, worker RSS/VRAM, KV memory, batch size, and queue time.",
|
||||
"Killing one worker produces a bounded structured failure rather than a deadlock.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence",
|
||||
"Model artifacts remain on the configured mounted-drive storage and never under /home",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-010/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 10,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/10-pass-local-real-model-two-process-acceptance.md",
|
||||
"dependsOn": [
|
||||
"DGR-009"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-011",
|
||||
"title": "Pass a real heterogeneous two-machine route",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/11-pass-a-real-heterogeneous-two-machine-route.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a consumer-hardware operator, I need two physical machines to execute one GGUF model so that the distributed claim is real.",
|
||||
"acceptanceCriteria": [
|
||||
"Tracker selects two physical nodes with disjoint Shards and one exact certified recipe/compatibility class.",
|
||||
"Actual CPU/GPU execution occurs on both nodes; synthetic workers do not satisfy acceptance.",
|
||||
"Prefill/decode, concurrent-session isolation, telemetry, cancellation, and cleanup pass over the real transport/relay path.",
|
||||
"Exact hardware, network, backend, model hash, route, commands, and raw metrics are recorded.",
|
||||
"A model or recipe larger than one participating node's admitted memory is exercised when available.",
|
||||
"Output drift is measured and incompatible mixed backends fail closed.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence",
|
||||
"Model artifacts remain on the configured mounted-drive storage and never under /home",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-011/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 11,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/11-pass-a-real-heterogeneous-two-machine-route.md",
|
||||
"dependsOn": [
|
||||
"DGR-010"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-012",
|
||||
"title": "Implement continuous batching and bounded admission",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/12-implement-continuous-batching-and-bounded-admission.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a node operator, I need active sessions batched safely so that concurrency increases aggregate throughput rather than serializing every request.",
|
||||
"acceptanceCriteria": [
|
||||
"Node scheduler admits sessions against weight, KV, scratch, and queue budgets.",
|
||||
"Compatible decode steps from multiple sessions form llama.cpp batches while preserving per-session positions and outputs.",
|
||||
"Prefill does not starve decode; scheduling policy and bounds are explicit.",
|
||||
"Backpressure prevents unbounded queued activations or KV growth.",
|
||||
"Capability telemetry reports active sessions, queue depth, batch occupancy, KV pressure, prefill/decode rates, and rejected admissions.",
|
||||
"Concurrency 1/2/4/8 benchmark identifies saturation and shows no cross-session corruption.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-012/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 12,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/12-implement-continuous-batching-and-bounded-admission.md",
|
||||
"dependsOn": [
|
||||
"DGR-007",
|
||||
"DGR-009",
|
||||
"DGR-010"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-013",
|
||||
"title": "Harden failure, cancellation, and restart semantics",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/13-harden-failure-cancellation-and-restart-semantics.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a client, I need failures to be bounded and explicit so that distributed speed does not come with hanging or corrupted generations.",
|
||||
"acceptanceCriteria": [
|
||||
"Deadlines and heartbeat/health loss terminate blocked stream operations.",
|
||||
"Cancellation propagates across every Shard and releases local KV and queued buffers.",
|
||||
"Duplicate steps are idempotent; uncertain mutations are never replayed silently.",
|
||||
"Alpha failover restarts from token zero on a newly compatible route rather than importing unverified KV.",
|
||||
"Worker death, stream reset, malformed bundle, stale epoch, and cache miss tests pass.",
|
||||
"Billing/work records distinguish completed, cancelled, failed, and unverified work.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-013/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 13,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/13-harden-failure-cancellation-and-restart-semantics.md",
|
||||
"dependsOn": [
|
||||
"DGR-008",
|
||||
"DGR-009",
|
||||
"DGR-012"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-014",
|
||||
"title": "Enforce the GGUF-versus-safetensors release gate",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/14-enforce-the-gguf-versus-safetensors-release-gate.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs the product owner, I need an end-to-end comparison so that the native runtime ships only if it advances model access or performance.",
|
||||
"acceptanceCriteria": [
|
||||
"Run current distributed safetensors and distributed GGUF routes on the same certified model/hardware/network scenario where technically comparable.",
|
||||
"Report quality, TTFT, prefill/decode throughput, aggregate concurrency throughput, p95 latency, seam cost, memory, KV pressure, failures, and cleanup.",
|
||||
"Evaluate against the DGR-001 performance contract without changing thresholds after seeing results.",
|
||||
"Ship recommendation is one of: promote GGUF, optimize a measured bottleneck with a new bounded task, or stop the native track.",
|
||||
"Results clearly separate quantization gains from transport/runtime gains.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence",
|
||||
"Model artifacts remain on the configured mounted-drive storage and never under /home",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-014/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 14,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/14-enforce-the-gguf-versus-safetensors-release-gate.md",
|
||||
"dependsOn": [
|
||||
"DGR-001",
|
||||
"DGR-011",
|
||||
"DGR-012",
|
||||
"DGR-013"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-015",
|
||||
"title": "Add and certify a Qwen3/Qwen3-MoE adapter",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/15-add-and-certify-a-qwen3-qwen3-moe-adapter.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a client seeking top models, I need a separately certified MoE-capable architecture after the dense runtime proves stable.",
|
||||
"acceptanceCriteria": [
|
||||
"Implement explicit tensor ownership, router/top-k, expert/shared-expert, Q/K normalization, boundary bundle, and cache semantics for the selected Qwen3 family recipe.",
|
||||
"Do not reuse the dense-Llama adapter through unchecked name substitutions.",
|
||||
"Whole-model versus distributed prefill/decode parity passes the architecture-specific tolerance.",
|
||||
"Expert memory ownership and communication are measured.",
|
||||
"Real consumer-hardware acceptance and capability admission pass before the recipe becomes routable.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Real-model execution is opt-in through MESHNET_ENABLE_REAL_INFERENCE_TESTS=1 and records exact artifact/runtime/hardware evidence",
|
||||
"Model artifacts remain on the configured mounted-drive storage and never under /home",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-015/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 15,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/15-add-and-certify-a-qwen3-qwen3-moe-adapter.md",
|
||||
"dependsOn": [
|
||||
"DGR-014"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DGR-016",
|
||||
"title": "Produce the upstream llama.cpp collaboration package",
|
||||
"description": "MANDATORY FRESH-SESSION CONTEXT: Read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md` and `.scratch/distributed-gguf-runtime/issues/16-produce-the-upstream-llama-cpp-collaboration-package.md` completely before coding. Read the evidence handoff for every dependency. The global goal is performant concurrent inference for models larger than one consumer node, using Meshnet as the sole control plane, gRPC/Protobuf as the Shard protocol, and a small pinned llama.cpp worker\u2014not a stitched collection of runtimes.\n\nAs a maintainer, I need narrow upstreamable proposals so that our patch burden can shrink without asking llama.cpp to own Meshnet networking.",
|
||||
"acceptanceCriteria": [
|
||||
"Separate generic llama.cpp hooks from Meshnet protocol/control-plane code.",
|
||||
"Prepare minimal reproducible examples and tests for range-aware loading, boundary input/output, and layer-filtered KV.",
|
||||
"Compare the proposal with Nakshatra and prima.cpp evidence and explain why the API is generally useful.",
|
||||
"Preserve one scoped commit/patch per concern against the exact upstream pin.",
|
||||
"Produce an outreach document suitable for Georgi/llama.cpp maintainers; actual sending remains a human action.",
|
||||
"Targeted pytest tests pass",
|
||||
"python -m compileall packages tests passes for Python changes",
|
||||
"git diff --check passes",
|
||||
"Default tests remain deterministic, model-download-free, API-credit-free, and GPU-free",
|
||||
"Full deterministic pytest -q passes, or the exact pre-existing unrelated failure is recorded with a clean-tree reproduction",
|
||||
"Pinned native C++ target builds and focused CTest/protocol tests pass where native code is touched",
|
||||
"llama.cpp patch stack applies cleanly to the exact pinned commit where patch code is touched",
|
||||
"Read .scratch/distributed-gguf-runtime/RALPH-CONTEXT.md and this story issue completely before changing code",
|
||||
"Read and verify every dependency evidence README before relying on dependency behavior",
|
||||
"Preserve all pre-existing working-tree changes and stage only files belonging to this story",
|
||||
"Write .scratch/distributed-gguf-runtime/evidence/DGR-016/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff",
|
||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||
],
|
||||
"priority": 16,
|
||||
"passes": false,
|
||||
"notes": "Source issue: .scratch/distributed-gguf-runtime/issues/16-produce-the-upstream-llama-cpp-collaboration-package.md",
|
||||
"dependsOn": [
|
||||
"DGR-010"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
# Prior Art: Distributed Large-Model Inference
|
||||
|
||||
> **Superseded as the current source audit.** Use [`docs/research/distributed-gguf-landscape.md`](../../docs/research/distributed-gguf-landscape.md), [`distributed-gguf-github-followup.md`](../../docs/research/distributed-gguf-github-followup.md), and [`vllm-distributed-gguf-assessment.md`](../../docs/research/vllm-distributed-gguf-assessment.md). This file remains as early historical research.
|
||||
|
||||
This note captures what existing projects appear to solve and what remains specific to this platform.
|
||||
|
||||
## Petals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Distributed GGUF Technical Challenge Register
|
||||
|
||||
> **Historical challenge register.** Route Session, binary activation, local Hot KV State, and transport performance work have advanced since this file was written. Current implementation gates live in [PRD.md](PRD.md), [implementation-strategy.md](implementation-strategy.md), [architecture.md](architecture.md), and [prd.json](prd.json). Preserve this file for detailed risk context; do not treat its “current constraint” section as live system state.
|
||||
|
||||
This document focuses on the engineering problems that decide whether the distributed GGUF path is viable. The important distinction is:
|
||||
|
||||
- **Model artifacts move like torrents.**
|
||||
|
||||
803
docs/research/distributed-gguf-github-followup.md
Normal file
803
docs/research/distributed-gguf-github-followup.md
Normal file
@@ -0,0 +1,803 @@
|
||||
# Distributed GGUF GitHub follow-up: GPUStack, Nakshatra, LiGGUF, and additional candidates
|
||||
|
||||
Status: Source audit complete
|
||||
Last updated: 2026-07-13
|
||||
|
||||
## 1. Why this follow-up exists
|
||||
|
||||
This document evaluates additional claims and repositories found after the initial distributed-GGUF landscape report:
|
||||
|
||||
- The GPUStack 0.4 multi-worker GGUF tutorial.
|
||||
- The claim that llama.cpp is the base of most practical GGUF distribution.
|
||||
- Nakshatra's patched llama.cpp layer workers.
|
||||
- LiGGUF's SARA distributed example.
|
||||
- Chameleon and Continuum.
|
||||
- Additional GitHub searches for sub-GGUF, layer-range, activation-chain, and llama.cpp RPC implementations.
|
||||
|
||||
It supplements [the main distributed-GGUF landscape report](distributed-gguf-landscape.md). The dedicated [vLLM assessment](vllm-distributed-gguf-assessment.md) remains separate.
|
||||
|
||||
## 2. Corrected terminology
|
||||
|
||||
The original research summary was directionally correct but combined several different forms of parallelism.
|
||||
|
||||
### 2.1 Layer or pipeline parallelism
|
||||
|
||||
Whole contiguous transformer-layer ranges are assigned to stages:
|
||||
|
||||
```text
|
||||
tokens
|
||||
-> layers 0..N
|
||||
-> boundary residual
|
||||
-> layers N..M
|
||||
-> logits
|
||||
```
|
||||
|
||||
This is the closest match to neuron-tai's tracker-selected route.
|
||||
|
||||
### 2.2 Tensor parallelism
|
||||
|
||||
Operations inside every layer are divided across ranks:
|
||||
|
||||
- Attention heads.
|
||||
- Matrix rows or columns.
|
||||
- FFN channels.
|
||||
- Experts.
|
||||
|
||||
Ranks exchange collectives or partial reductions inside each transformer layer. LiGGUF SARA is an example.
|
||||
|
||||
### 2.3 Local multi-device placement
|
||||
|
||||
llama.cpp's `n_gpu_layers` and `tensor_split` choose how one coordinator places work across devices. This is local offload unless some devices are llama.cpp RPC devices.
|
||||
|
||||
### 2.4 llama.cpp RPC
|
||||
|
||||
llama.cpp RPC exposes a remote GGML backend/device to the coordinator. It is real cross-machine inference, but the remote server is not an independent layer/session worker. GPUStack 0.4 and llama-box used this mechanism.
|
||||
|
||||
### 2.5 Quantization
|
||||
|
||||
Q2_K, Q4_K_M, Q8_0, and related GGUF types reduce weight storage and memory. They do not define:
|
||||
|
||||
- Activation dtype.
|
||||
- Compute dtype.
|
||||
- KV-cache dtype.
|
||||
- Parallelism topology.
|
||||
- Session or route semantics.
|
||||
|
||||
## 3. Audited source snapshots
|
||||
|
||||
```text
|
||||
GPUStack current main
|
||||
244eb0da57add11d1ce07c70f31c1a15ae65ae0d
|
||||
|
||||
GPUStack v0.4.1
|
||||
dbf71dd16cec1f42896139c3b82380cb1fd06a10
|
||||
|
||||
llama-box
|
||||
4d068484fe198a30f8ca6d6d23d9890fbd8eee8c
|
||||
|
||||
Nakshatra
|
||||
0c16119713396ec6052400f3eb049c5e7a66cd94
|
||||
|
||||
LiGGUF
|
||||
2b5ac66ca37f36600aa5101b4237e74f3becb7c4
|
||||
|
||||
Chameleon
|
||||
96fbd96a9f67d29d12292d3373c88996aba65f84
|
||||
|
||||
Continuum
|
||||
dd976df36079d75244719a23956e1c9e2dcddc27
|
||||
```
|
||||
|
||||
## 4. GPUStack 0.4 and llama-box
|
||||
|
||||
### 4.1 Verdict
|
||||
|
||||
The [GPUStack 0.4 tutorial](https://docs.gpustack.ai/0.4/tutorials/performing-distributed-inference-across-workers/) describes a real open-source deployment path. It is not a closed-source native layer-shard engine.
|
||||
|
||||
GPUStack 0.4 orchestrated llama-box, which embedded llama.cpp/ggml RPC. The execution shape was:
|
||||
|
||||
```text
|
||||
GPUStack scheduler
|
||||
-> select main worker and remote GPU devices
|
||||
-> start llama-box RPC server on each selected remote GPU
|
||||
-> launch llama-box on main worker
|
||||
--rpc remote-a,remote-b,...
|
||||
--tensor-split remote-vram...,local-vram...
|
||||
-> coordinator opens the full GGUF
|
||||
-> llama.cpp places tensors and graph operations on local and RPC devices
|
||||
```
|
||||
|
||||
This is strong real-world evidence for llama.cpp RPC. It is not the independent layer-worker topology neuron-tai needs.
|
||||
|
||||
### 4.2 Scheduler and resource estimation
|
||||
|
||||
GPUStack detects GGUF models, estimates memory using its parser/calculator, marks distributable models, selects a main worker plus remote GPU devices, and persists their resource claims.
|
||||
|
||||
Evidence from GPUStack v0.4.1:
|
||||
|
||||
- `gpustack/scheduler/scheduler.py:147-205`.
|
||||
- `gpustack/scheduler/scheduler.py:328-377`.
|
||||
- `gpustack/scheduler/scheduler.py:429-449`.
|
||||
- `gpustack/policies/utils.py:35-47`.
|
||||
- `gpustack/policies/scorers/placement_scorer.py:184-196`.
|
||||
|
||||
This control-plane work is reusable conceptually:
|
||||
|
||||
- Parse exact GGUF requirements before placement.
|
||||
- Allocate memory claims per device.
|
||||
- Include remote-device allocations in global scheduling.
|
||||
- Reject incompatible backend/runtime versions.
|
||||
|
||||
### 4.3 RPC server lifecycle
|
||||
|
||||
GPUStack workers periodically start one llama-box RPC process per GPU and publish its port in worker status.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `gpustack/worker/worker.py:153-163`.
|
||||
- `gpustack/worker/worker_manager.py:140-200`.
|
||||
- `gpustack/worker/collector.py:66-80`.
|
||||
- `gpustack/worker/rpc_server.py:31-76`.
|
||||
|
||||
The launched command uses:
|
||||
|
||||
```text
|
||||
--rpc-server-host 0.0.0.0
|
||||
--rpc-server-port <port>
|
||||
--rpc-server-main-gpu 0
|
||||
```
|
||||
|
||||
GPU selection is enforced through the vendor-specific visible-device environment.
|
||||
|
||||
### 4.4 Main server launch
|
||||
|
||||
The main worker obtains remote RPC addresses and remote VRAM claims, then launches llama-box with:
|
||||
|
||||
```text
|
||||
--rpc <host:port,...>
|
||||
--tensor-split <remote MiB...,local MiB...>
|
||||
```
|
||||
|
||||
Evidence:
|
||||
|
||||
- `gpustack/worker/backends/llama_box.py:33-95`.
|
||||
- `gpustack/worker/backends/llama_box.py:165-181`.
|
||||
|
||||
This confirms that GPUStack's worker list did not become a route of independently callable layer stages. llama-box remained the single model owner and request server.
|
||||
|
||||
### 4.5 llama-box RPC behavior
|
||||
|
||||
llama-box's RPC server serializes GGML buffers, tensors, and graph-compute requests. It exposes remote backend memory and supports optional tensor caching.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `llama-box/rpcserver.hpp:74-213`.
|
||||
- `llama-box/rpcserver.hpp:215-226`.
|
||||
- `llama-box/rpcserver.hpp:374-410`.
|
||||
- `llama-box/rpcserver.hpp:413-447`.
|
||||
|
||||
The RPC server is a low-level remote device. It does not own:
|
||||
|
||||
- A source GGUF identity.
|
||||
- A tracker layer-range lease.
|
||||
- A route session or route epoch.
|
||||
- Independent tokenization/head/tail semantics.
|
||||
- A project-compatible activation endpoint.
|
||||
- Per-request node work receipts.
|
||||
|
||||
### 4.6 Real operational evidence
|
||||
|
||||
Three GPUStack issue reports are useful:
|
||||
|
||||
1. [Issue 1233](https://github.com/gpustack/gpustack/issues/1233) records a two-Mac llama-box RPC deployment and a maintainer reproduction command. Maintainers reported roughly 5-6 token/s for a large DeepSeek-R1 GGUF on two M2 Ultra systems.
|
||||
2. [Issue 756](https://github.com/gpustack/gpustack/issues/756) records a crash when main and RPC llama-box versions differed and a remote Metal backend did not support an operation.
|
||||
3. [Issue 1269](https://github.com/gpustack/gpustack/issues/1269) includes a real ten-device `--rpc`/`--tensor-split` command and an official maintainer statement that GPUStack 2.0 deprecated llama-box and no longer supports distributed GGUF inference.
|
||||
|
||||
These reports prove practical use while also demonstrating:
|
||||
|
||||
- Exact runtime/version compatibility is mandatory.
|
||||
- Every remote backend must support every placed graph operation.
|
||||
- Remote-memory estimates can still fail.
|
||||
- Coordinator interruption can leak or strand remote resources.
|
||||
- Cross-machine operation may be slower than expected.
|
||||
|
||||
### 4.7 Current status
|
||||
|
||||
The audited current GPUStack main snapshot retains legacy GGUF RPC-placement calculations, deprecated `rpc_servers` schema fields, migration code, and fixtures. It no longer has the llama-box backend or role-specific per-GPU RPC process launcher needed to turn those placement records into a working GGUF data plane. Current GGUF defaults to a custom backend, and GPUStack's supported multi-node matrix lists vLLM, SGLang, and MindIE rather than custom/GGUF.
|
||||
|
||||
Evidence from current GPUStack main:
|
||||
|
||||
- `gpustack/policies/candidate_selectors/gguf_resource_fit_selector.py:459-473`.
|
||||
- `gpustack/policies/candidate_selectors/gguf_resource_fit_selector.py:1969-1997`.
|
||||
- `gpustack/schemas/workers.py:198-202`.
|
||||
- `docs/migration.md:157-167`.
|
||||
- `gpustack/schemas/models.py:59-65`.
|
||||
- `gpustack/schemas/models.py:266-275`.
|
||||
- `gpustack/schemas/models.py:873-880`.
|
||||
- `gpustack/worker/backends/custom.py:152-181`.
|
||||
- `docs/faq.md:9-21`.
|
||||
|
||||
The retained selector is legacy residue and compatibility evidence, not a supported runnable distributed-GGUF backend.
|
||||
|
||||
Decision:
|
||||
|
||||
- Use GPUStack v0.4 as a llama.cpp RPC baseline and scheduling reference.
|
||||
- Do not treat current GPUStack as a maintained distributed-GGUF backend.
|
||||
- Do not reuse llama.cpp RPC as the volunteer network trust boundary.
|
||||
|
||||
## 5. Nakshatra
|
||||
|
||||
### 5.1 Verdict
|
||||
|
||||
Nakshatra is the closest implementation found to neuron-tai's native distributed-GGUF target.
|
||||
|
||||
It independently implements the same core seam selected in the initial report:
|
||||
|
||||
- Patched llama.cpp.
|
||||
- Local layer-only GGUF artifacts.
|
||||
- Contiguous first/middle/last workers.
|
||||
- Residual activation transport.
|
||||
- Worker-local llama.cpp KV.
|
||||
- A long-lived C++ daemon supervised by Python.
|
||||
- Dynamic placement and recovery above the worker.
|
||||
|
||||
It changes the implementation strategy from “write the first spike from scratch” to “reproduce, collaborate, reuse, and harden.”
|
||||
|
||||
It is not a wholesale drop-in because its control plane, artifact model, concurrency, identity, accounting, and architecture coverage differ from Meshnet.
|
||||
|
||||
### 5.2 Sub-GGUF construction
|
||||
|
||||
`partial_gguf.py` creates a derivative GGUF per layer range.
|
||||
|
||||
It preserves source metadata, filters `blk.N.*` tensors to `[start,end)`, keeps embeddings only for the head unless tied output needs them, and keeps output tensors only for the tail.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `experiments/v0.0/partial_gguf.py:59-115`.
|
||||
- `experiments/v0.0/partial_gguf.py:117-143`.
|
||||
- `experiments/v0.0/partial_gguf.py:184-201`.
|
||||
|
||||
It writes:
|
||||
|
||||
```text
|
||||
nakshatra.layer_range_start
|
||||
nakshatra.layer_range_end
|
||||
nakshatra.has_token_embd
|
||||
nakshatra.has_lm_head
|
||||
```
|
||||
|
||||
This gives workers real local ownership and prevents inference-time weight transfer.
|
||||
|
||||
Meshnet differences:
|
||||
|
||||
- Meshnet prefers one exact source artifact hash plus a range/recipe identity.
|
||||
- Derived sub-GGUF files need their own hash and a signed binding to the source artifact and range.
|
||||
- Rewriting a large GGUF for every placement is expensive and duplicates storage.
|
||||
- A range-aware mmap loader from one shared artifact remains preferable long term.
|
||||
|
||||
Sub-GGUFs are still acceptable for a first integration spike because they already work.
|
||||
|
||||
### 5.3 llama.cpp patch mechanics
|
||||
|
||||
Nakshatra's patch series:
|
||||
|
||||
- Adds range and endpoint fields to the model.
|
||||
- Reads namespaced partial-model metadata.
|
||||
- Allows unowned tensors to be absent.
|
||||
- Creates/loads only owned layer tensors.
|
||||
- Iterates only the selected layer interval.
|
||||
- Returns the unnormalized residual stream from non-tail stages.
|
||||
- Applies final norm and LM head only on the tail.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `experiments/v0.0/m4_patches/llama-model.h.patch:1-17`.
|
||||
- `experiments/v0.0/m4_patches/llama-model.cpp.patch:1-73`.
|
||||
- `experiments/v0.0/m4_patches/llama-model-loader.cpp.patch:1-11`.
|
||||
- `experiments/v0.0/m4_patches/llama-graph.cpp.patch:1-24`.
|
||||
- `experiments/v0.0/m4_patches/models_llama.cpp.patch:1-70`.
|
||||
|
||||
This is direct proof that the small-fork direction is feasible. The current patch is architecture-specific and built against older llama.cpp revisions. It should be rebased rather than copied blindly.
|
||||
|
||||
### 5.4 Worker shape
|
||||
|
||||
Each Python gRPC worker supervises one long-lived C++ daemon. The daemon owns the patched llama model/context and communicates with Python through a framed stdin/stdout or shared-memory protocol.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `scripts/worker.py:1-17`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:1-52`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:166-267`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:282-396`.
|
||||
|
||||
The daemon accepts:
|
||||
|
||||
- Token IDs for a head stage.
|
||||
- F32 boundary activations for middle/tail stages.
|
||||
- `start_pos` and `keep_kv` controls.
|
||||
|
||||
It returns:
|
||||
|
||||
- F32 residual activations for non-tail stages.
|
||||
- Greedy token IDs on the tail.
|
||||
- Optional all-position top tokens for speculative verification.
|
||||
|
||||
The implementation also contains optional blockwise int8 activation transport:
|
||||
|
||||
- `experiments/v0.0/worker_daemon.cpp:105-149`.
|
||||
|
||||
This process boundary is close to the selected Meshnet worker shape and avoids exposing llama.cpp internal ABI to Python.
|
||||
|
||||
### 5.5 Protobuf and route semantics
|
||||
|
||||
Nakshatra's protobuf includes:
|
||||
|
||||
- Protocol/backend/model/range capability reporting.
|
||||
- A source-model hash field.
|
||||
- Head/tail ownership flags.
|
||||
- Stable `session_id` and idempotency `step_id`.
|
||||
- Prefix/KV position metadata.
|
||||
- Token, hidden-state, logits, and error variants.
|
||||
- Server-to-server next-hop chains.
|
||||
- KV truncation.
|
||||
- Sleep/wake lifecycle operations.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `proto/nakshatra.proto:1-55`.
|
||||
- `proto/nakshatra.proto:57-93`.
|
||||
- `proto/nakshatra.proto:95-131`.
|
||||
|
||||
Meshnet's worker contract still needs additional fields:
|
||||
|
||||
- Route epoch.
|
||||
- Effective overlap-safe start layer.
|
||||
- Exact source and derived artifact hashes.
|
||||
- Architecture/runtime/quantization/activation recipe.
|
||||
- Request/work/accounting identity.
|
||||
- Payload checksum and compression recipe.
|
||||
- Cache expectation and explicit cache-miss response.
|
||||
- Cancellation and lease identity.
|
||||
|
||||
The implementation should sit behind a Meshnet-owned protocol rather than adopting the protobuf as a permanent public API.
|
||||
|
||||
### 5.6 KV ownership and concurrency gap
|
||||
|
||||
The daemon supports:
|
||||
|
||||
- Cold prefill.
|
||||
- Incremental decode.
|
||||
- KV truncation after speculative rejection.
|
||||
- Sleep/wake and model reload.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `experiments/v0.0/worker_daemon.cpp:344-384`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:416-463`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:479-500`.
|
||||
|
||||
However, the audited worker creates one llama context with two sequence slots:
|
||||
|
||||
```text
|
||||
sequence 0: serving/verification
|
||||
sequence 1: EAGLE scratch
|
||||
```
|
||||
|
||||
The v0.5 design explicitly states that the daemon still has one logical serving session and ships with `n_concurrent_sessions = 1`:
|
||||
|
||||
- `docs/v0.5-design-lock.md:97-111`.
|
||||
|
||||
The Python idempotency cache deduplicates `(session_id,step_id)` outputs. It does not isolate independent llama KV state for multiple concurrent route sessions.
|
||||
|
||||
Meshnet integration must add:
|
||||
|
||||
```text
|
||||
(route_session_id, route_epoch)
|
||||
-> llama_seq_id or isolated context
|
||||
```
|
||||
|
||||
and verify concurrent prefill/decode, release, eviction, stale epoch rejection, and cache misses.
|
||||
|
||||
### 5.7 Real acceptance evidence
|
||||
|
||||
Nakshatra records a two-physical-machine CPU-only test over Tailscale:
|
||||
|
||||
- Head worker: layers `[0,14)`.
|
||||
- Tail worker: layers `[14,28)`.
|
||||
- Prompt: “The capital of France is”.
|
||||
- Distributed first token: `12366`, “ Paris”.
|
||||
- Matched localhost, single-process chain, and whole-model llama.cpp reference.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `experiments/v0.0/m6_findings.md:1-42`.
|
||||
|
||||
This proves real independent layer execution and cross-machine activation transport. It does not prove GPU heterogeneity because both machines used CPU for the acceptance run.
|
||||
|
||||
The repository also records a four-worker Llama-3.3-70B chain using Macs with Metal and one CPU stage:
|
||||
|
||||
- Streaming completed around 0.21 token/s.
|
||||
- Multi-hop server push completed around 0.19 token/s.
|
||||
- Push was slower because compute dominated network time.
|
||||
- First generated token was “Paris”.
|
||||
- Alternate-worker replay completed with expected post-splice divergence.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `docs/v0.5-design-lock.md:145-175`.
|
||||
- `docs/v0.5-design-lock.md:258-277`.
|
||||
|
||||
The five-machine ROCm + Metal cross-vendor acceptance remained pending in the audited snapshot. Claims of fully validated ROCm+Metal execution should therefore remain qualified.
|
||||
|
||||
### 5.8 Failure and numerical behavior
|
||||
|
||||
Nakshatra supports:
|
||||
|
||||
- Persistent streaming RPCs.
|
||||
- Server-to-server activation push.
|
||||
- Fallback from failed push to client relay.
|
||||
- Full-history replay after stream failure.
|
||||
- Alternate workers per range.
|
||||
- Drift-class-aware recovery design.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `scripts/client.py:163-269`.
|
||||
- `scripts/client.py:842-850`.
|
||||
- `docs/v0.5-design-lock.md:258-277`.
|
||||
- `docs/v1.0-fault-tolerance.md:93-103`.
|
||||
|
||||
The project correctly acknowledges that replay on another backend can numerically diverge. For Meshnet, the safe default remains:
|
||||
|
||||
1. Exact recipe and same drift class for in-session replacement.
|
||||
2. Otherwise restart from token zero on a newly consistent route.
|
||||
3. Never silently import incompatible KV or continue with an unvalidated mixed recipe.
|
||||
|
||||
### 5.9 Security and work receipts
|
||||
|
||||
The worker contains optional:
|
||||
|
||||
- TLS and SPKI pinning.
|
||||
- Ed25519 request authentication.
|
||||
- Admission and sandbox hooks.
|
||||
- Audit logging.
|
||||
- An experimental encrypted fabric.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `scripts/worker.py:49-117`.
|
||||
- `scripts/worker.py:218-319`.
|
||||
|
||||
The receipt implementation explicitly documents that:
|
||||
|
||||
- Output hash and structural consistency are independently checkable.
|
||||
- Model identity is only asserted because the live model hash is a zero stub.
|
||||
- Participation is coordinator-asserted because worker signatures are not populated.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `scripts/receipt.py:1-27`.
|
||||
- `scripts/receipt.py:34-47`.
|
||||
- `scripts/receipt.py:50-97`.
|
||||
- `scripts/receipt.py:100-160`.
|
||||
|
||||
This is not sufficient for per-node rewards. Meshnet must bind receipts to:
|
||||
|
||||
```text
|
||||
request/work ID
|
||||
route session and epoch
|
||||
source artifact hash
|
||||
layer range and effective start
|
||||
input/output activation digests
|
||||
positions/token count
|
||||
runtime recipe
|
||||
measured compute
|
||||
worker identity/signature
|
||||
```
|
||||
|
||||
### 5.10 Reproducibility and integration defects
|
||||
|
||||
The audited repository is not a self-contained llama.cpp fork or reproducible worker build:
|
||||
|
||||
- It ships patch files but no pinned llama.cpp source tree or submodule.
|
||||
- The README recommends llama.cpp commit `c46583b` “or close enough”.
|
||||
- The daemon is copied manually into an external llama.cpp checkout and its CMake target is added manually.
|
||||
- The documented copy recipe omits `shm_ring.hpp`, which the daemon includes.
|
||||
- The historical two-machine run used different llama.cpp builds on the two hosts.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `README.md:41-77`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:54-57`.
|
||||
- `experiments/v0.0/m6_findings.md:35-42`.
|
||||
|
||||
The live daemon also trusts operator-supplied range and endpoint mode rather than returning authoritative metadata from the loaded sub-GGUF. Its INFO response reports a generic full range and both endpoint flags, while Python advertises CLI values:
|
||||
|
||||
- `experiments/v0.0/worker_daemon.cpp:387-392`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:466-475`.
|
||||
- `scripts/worker.py:1141-1153`.
|
||||
|
||||
Additional integration gaps:
|
||||
|
||||
- Normal activations are little-endian F32 even though the protobuf comment says FP16 is the default; int8 activation mode is environment-global rather than negotiated.
|
||||
- The coordinator requires full-GGUF access through `llama-cpp-python` for tokenization.
|
||||
- Tail sampling is hard-coded greedy top-1 rather than returning general logits.
|
||||
- Many tests use fake daemons; no CI job builds patched llama.cpp, generates slices, starts two real workers, and tests prefill/decode/failure.
|
||||
- Nakshatra runtime dependencies are absent from the inherited Petals package metadata.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `proto/nakshatra.proto:11-12`.
|
||||
- `scripts/worker.py:340-343`.
|
||||
- `scripts/worker.py:1141-1151`.
|
||||
- `scripts/client.py:129-139`.
|
||||
- `scripts/client.py:719-722`.
|
||||
- `experiments/v0.0/worker_daemon.cpp:617-641`.
|
||||
- `tests/test_worker_eagle_sleep_rpc.py:1-31`.
|
||||
- `setup.cfg:1-16`.
|
||||
- `setup.cfg:29-72`.
|
||||
|
||||
These defects make Nakshatra a strong source donor and independent feasibility proof, but not the repository to fork as the production base.
|
||||
|
||||
### 5.11 Reuse decision
|
||||
|
||||
Do not recreate Nakshatra's working Llama-family layer patch and daemon from scratch without first attempting upstream collaboration.
|
||||
|
||||
Preferred plan:
|
||||
|
||||
1. Reproduce its two-worker path locally.
|
||||
2. Rebase its patch against the exact current llama.cpp commit already audited.
|
||||
3. Compare the patch with the proposed `llama_model_load_range` and boundary-output design.
|
||||
4. Borrow or jointly maintain narrow patch concepts and tests, but keep a project-owned standalone worker and small pinned llama.cpp fork rather than forking the Petals-derived repository wholesale.
|
||||
5. Replace or adapt its Python control plane with existing Meshnet tracker/session/relay/billing infrastructure.
|
||||
6. Add multi-session KV isolation, exact recipe identity, route epochs, cancellation, signed work receipts, and architecture certification.
|
||||
7. Upstream generic llama.cpp range-loading/boundary hooks where maintainers will accept them.
|
||||
|
||||
Classification:
|
||||
|
||||
```text
|
||||
Primary source donor and collaboration candidate
|
||||
Do not adopt or fork the repository wholesale
|
||||
```
|
||||
|
||||
## 6. LiGGUF SARA
|
||||
|
||||
### 6.1 Verdict
|
||||
|
||||
LiGGUF contains a real experimental networked distributed implementation. It is not layer pipeline parallelism.
|
||||
|
||||
Its SARA implementation performs tensor-parallel activation reduction across every transformer layer.
|
||||
|
||||
### 6.2 Mechanism
|
||||
|
||||
Every process mmaps and parses the complete GGUF and constructs pointers for all transformer blocks:
|
||||
|
||||
- `cpp/ligguf_distrib.cpp:323-485`.
|
||||
|
||||
Rank assignments split:
|
||||
|
||||
- KV heads.
|
||||
- Query heads derived from KV heads.
|
||||
- FFN channel blocks.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `cpp/ligguf_distrib.cpp:120-124`.
|
||||
- `cpp/ligguf_distrib.cpp:657-679`.
|
||||
|
||||
Each rank allocates KV for its attention-head slice across every layer:
|
||||
|
||||
- `cpp/ligguf_distrib.cpp:681-707`.
|
||||
|
||||
For every transformer layer, the master:
|
||||
|
||||
1. Normalizes the residual.
|
||||
2. Broadcasts a Q8_0 activation to all workers.
|
||||
3. Every rank computes its attention partial.
|
||||
4. Workers return Q8_0 full-width partials.
|
||||
5. The master sums partials into the residual.
|
||||
6. Repeats the same process for the FFN.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `cpp/ligguf_distrib.cpp:709-767`.
|
||||
- `cpp/ligguf_distrib.cpp:892-903`.
|
||||
- `cpp/ligguf_distrib.cpp:957-974`.
|
||||
- `cpp/ligguf_distrib.cpp:1025-1066`.
|
||||
|
||||
### 6.3 Strengths
|
||||
|
||||
- Very compact and readable.
|
||||
- Direct GGUF mmap.
|
||||
- Real raw-TCP master/worker implementation.
|
||||
- Q8_0 activation and partial transport.
|
||||
- Local KV per attention-head shard.
|
||||
- Useful reference for tensor-parallel reductions on CPU-heavy edge systems.
|
||||
|
||||
### 6.4 Mismatch
|
||||
|
||||
- Every worker needs the complete GGUF.
|
||||
- Every worker participates in every layer.
|
||||
- Two network reduction phases occur per transformer layer.
|
||||
- Static rank/world topology.
|
||||
- One master connection and one generation at a time.
|
||||
- Sequential worker receive loop.
|
||||
- No model/session/request/range identity.
|
||||
- No authentication, checksums, recovery, cancellation, or accounting.
|
||||
- Custom inference core has much narrower model/kernel coverage than llama.cpp.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `cpp/ligguf_distrib.cpp:769-974`.
|
||||
- `cpp/ligguf_distrib.cpp:1025-1046`.
|
||||
- `cpp/ligguf_distrib.cpp:1138-1241`.
|
||||
|
||||
Decision: retain as a source donor for compact Q8 activation transport, tensor-partition tests, and reduction benchmarking. Do not use it as the native Meshnet layer worker.
|
||||
|
||||
## 7. Chameleon
|
||||
|
||||
Audited snapshot:
|
||||
|
||||
```text
|
||||
megeezy/Chameleon
|
||||
commit 96fbd96a9f67d29d12292d3373c88996aba65f84
|
||||
```
|
||||
|
||||
Chameleon is a whole-model lifecycle and routing system:
|
||||
|
||||
- Coordinator selects a model and worker.
|
||||
- Python worker loads a complete llama-cpp-python, vLLM, Transformers, or ExLlamaV2 backend.
|
||||
- The model executes, can remain warm, and is later unloaded.
|
||||
|
||||
It does not split one model's layers or tensors across workers. Its README also labels it design phase.
|
||||
|
||||
Decision: exclude from the distributed-GGUF implementation list. Whole-model load/unload and warm-cache ideas may be relevant to proxy backends only.
|
||||
|
||||
## 8. Continuum
|
||||
|
||||
Audited snapshot:
|
||||
|
||||
```text
|
||||
CambrianTech/continuum
|
||||
commit dd976df36079d75244719a23956e1c9e2dcddc27
|
||||
```
|
||||
|
||||
Continuum has:
|
||||
|
||||
- A local GGUF loader.
|
||||
- A local inference backend.
|
||||
- Mesh/federation infrastructure.
|
||||
- Roadmap language about dividing models across nodes.
|
||||
|
||||
No executable distributed GGUF layer or tensor data plane was found in this snapshot. There is no layer-range protocol, boundary-activation transport, distributed KV ownership, or distributed GGUF test corresponding to the roadmap statement.
|
||||
|
||||
Decision: exclude until source and real execution evidence exist.
|
||||
|
||||
## 9. GitHub search conclusion
|
||||
|
||||
The expanded searches used terms including:
|
||||
|
||||
- `distributed GGUF`.
|
||||
- `sub-GGUF`.
|
||||
- `layer range` with llama.cpp.
|
||||
- `result_partial_hidden`.
|
||||
- `activation chain`.
|
||||
- llama.cpp RPC orchestration.
|
||||
- GGUF tensor parallelism.
|
||||
|
||||
The working implementation families found are:
|
||||
|
||||
| Family | Projects | What is real |
|
||||
|---|---|---|
|
||||
| Coordinator-owned remote devices | llama.cpp RPC, historical GPUStack/llama-box, LocalAI integrations | Full GGML graph controlled by one coordinator |
|
||||
| Independent GGUF layer pipeline | Nakshatra, prima.cpp | Local layer ownership, residual boundaries, local KV |
|
||||
| Custom Rust layer pipeline | `llama-gguf` | gRPC layers, but coordinator-streamed weights and weak session semantics |
|
||||
| Tensor parallel GGUF | LiGGUF SARA | Head/FFN partitions and per-layer partial reductions |
|
||||
| Static/custom non-GGUF tensor engines | distributed-llama/dllama and similar | Useful algorithms, different artifacts/runtime |
|
||||
| Whole-model routing | Chameleon, Ollama, most LocalAI use, current GPUStack backends | Independent complete models, not one split model |
|
||||
| Roadmap-only mesh claims | Continuum and several search hits | No executable distributed GGUF data plane found |
|
||||
|
||||
No additional mature project was found that already combines:
|
||||
|
||||
- Arbitrary tracker-selected layer intervals.
|
||||
- Exact local GGUF artifact ownership.
|
||||
- Heterogeneous volunteer nodes.
|
||||
- Concurrent route-session KV.
|
||||
- Dynamic route epochs and recovery.
|
||||
- Relay and cancellation.
|
||||
- Exact runtime/activation compatibility.
|
||||
- Worker-authenticated accounting.
|
||||
|
||||
Nakshatra is the closest and materially reduces the amount of new inference-engine work required.
|
||||
|
||||
## 10. Revised architecture decision
|
||||
|
||||
The selected runtime remains llama.cpp/GGML, but the implementation source priority changes:
|
||||
|
||||
```text
|
||||
Existing Meshnet tracker, relay, sessions, capability admission, and billing
|
||||
|
|
||||
Meshnet-owned stable shard protocol
|
||||
|
|
||||
project-owned C++ worker borrowing narrow Nakshatra concepts/tests
|
||||
|
|
||||
narrow, pinned, architecture-certified llama.cpp patch
|
||||
|
|
||||
GGUF loader, quant kernels, KV, CPU/GPU backends
|
||||
```
|
||||
|
||||
This is not an architectural pivot away from the initial decision. Nakshatra is an external implementation of nearly the same missing seam.
|
||||
|
||||
## 11. Revised implementation sequence
|
||||
|
||||
### 11.1 Reproduction and patch comparison
|
||||
|
||||
- Reproduce Nakshatra's two local workers with a small dense Llama-family GGUF.
|
||||
- Verify disjoint sub-GGUF weight ownership.
|
||||
- Verify boundary residual parity with whole-model llama.cpp.
|
||||
- Rebase against the pinned current llama.cpp commit.
|
||||
- Compare pre-sliced GGUF loading with range-aware loading from one source artifact.
|
||||
|
||||
### 11.2 Meshnet protocol adapter
|
||||
|
||||
- Keep the project-owned llama.cpp worker behind a supervised executable.
|
||||
- Translate Meshnet request/session/route metadata into worker calls.
|
||||
- Preserve BF16 or versioned named-tensor bundles on the public network boundary.
|
||||
- Add exact source/slice/runtime recipe checks.
|
||||
- Reject stale route epochs and incompatible caches.
|
||||
|
||||
### 11.3 Multi-session KV
|
||||
|
||||
- Map each route session/epoch to an isolated `llama_seq_id` or context.
|
||||
- Test concurrent prefill and decode.
|
||||
- Add bounded release, TTL, LRU, and cache-miss semantics.
|
||||
- Verify only owned layers allocate KV.
|
||||
|
||||
### 11.4 Real heterogeneous route
|
||||
|
||||
- CPU plus AMD HIP/ROCm first on the available machine.
|
||||
- Add CUDA, Vulkan, and Metal as certified lanes when hardware is available.
|
||||
- Measure numerical drift and define compatibility classes.
|
||||
- Require a clean from-token-zero restart when exact-recipe recovery is unavailable.
|
||||
|
||||
### 11.5 Trustworthy accounting
|
||||
|
||||
- Worker signs work receipts.
|
||||
- Receipt binds route, request, artifact, range, activation digests, positions, and runtime recipe.
|
||||
- Tracker validates structural consistency and completion evidence before rewards.
|
||||
|
||||
### 11.6 Architecture expansion
|
||||
|
||||
- Dense Llama-family first.
|
||||
- Explicit Qwen3/Qwen3-MoE adapter next.
|
||||
- Fail closed for all unvalidated architectures.
|
||||
|
||||
## 12. Local validation performed
|
||||
|
||||
The source audit included limited executable verification without downloading model artifacts:
|
||||
|
||||
```text
|
||||
LiGGUF distributed target:
|
||||
make ligguf-cpp-distrib
|
||||
PASS — g++ produced ligguf-cpp-distrib
|
||||
|
||||
Nakshatra dependency-free focused tests:
|
||||
47 passed in 0.42s
|
||||
```
|
||||
|
||||
The Nakshatra subset covered receipt validation, wire-version behavior, wire handshake behavior, and topology ordering.
|
||||
|
||||
The networked idempotency integration test was not collected because this audit environment does not have the optional `grpcio` package installed. This is an environment dependency blocker, not a test assertion failure. No real Nakshatra model inference was run locally during this research; the real-model conclusions remain tied to the source, recorded experiment evidence, and the future reproduction gate in section 11.
|
||||
|
||||
## 13. Final conclusions
|
||||
|
||||
1. The user's central practical observation is correct: llama.cpp is the base of most real-world GGUF distribution found.
|
||||
2. GPUStack 0.4 was open source and genuinely distributed, but through llama-box/llama.cpp RPC rather than independent shards.
|
||||
3. GPUStack 2.0 removed distributed GGUF support, so the old tutorial must be treated as historical.
|
||||
4. Nakshatra is the most important new source. It has already implemented and exercised the narrow llama.cpp layer-worker design.
|
||||
5. Nakshatra should be approached for narrow collaboration and mined for source/tests, but its repository should not be adopted or forked wholesale.
|
||||
6. Nakshatra still needs Meshnet-specific hardening: exact identity, concurrent KV, route epochs, cancellation, accounting, and architecture certification.
|
||||
7. LiGGUF SARA is real distributed GGUF tensor parallelism, but every worker loads the whole model and communicates twice per layer.
|
||||
8. Chameleon is whole-model routing; Continuum's distributed GGUF is roadmap-only in the audited snapshot.
|
||||
9. No drop-in project yet satisfies the complete tracker-routed volunteer-network contract.
|
||||
10. The implementation risk is now lower because the hardest first proof—partial llama.cpp layer loading plus boundary execution—has independent working source and cross-machine evidence.
|
||||
829
docs/research/distributed-gguf-landscape.md
Normal file
829
docs/research/distributed-gguf-landscape.md
Normal file
@@ -0,0 +1,829 @@
|
||||
# Distributed GGUF inference: existing projects, source audits, and implementation direction
|
||||
|
||||
Status: Research complete; architecture direction selected
|
||||
Last updated: 2026-07-13
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This document records the research behind native distributed GGUF inference for the neuron-tai network. It is intentionally not a design for proxying an already-running whole-model `llama-server`, Ollama, vLLM, or another OpenAI-compatible runtime. Whole-model proxying is useful as a correctness/performance baseline, but it does not solve the network's central problem.
|
||||
|
||||
The required product is a tracker-routed distributed GGUF data plane in which heterogeneous nodes independently execute model layer ranges, exchange boundary activations, retain cache state for their own layers, and receive credit for work they actually perform.
|
||||
|
||||
### 1.1 Required contract
|
||||
|
||||
A satisfactory implementation must support:
|
||||
|
||||
- Native GGUF model artifacts and llama.cpp-compatible quantizations.
|
||||
- Independently loaded, executable layer ranges on each node.
|
||||
- Tracker-selected routes rather than one static rank topology.
|
||||
- Heterogeneous CPU, CUDA, HIP/ROCm, Vulkan, Metal, and other certified lanes.
|
||||
- A versioned hidden-state activation boundary between nodes.
|
||||
- Local per-shard KV or recurrent state keyed by route session.
|
||||
- Prefill and decode phases with bounded wire and compute cost.
|
||||
- Overlap-safe execution using the tracker's effective `start_layer` semantics.
|
||||
- Explicit cache miss, eviction, route epoch, and recovery behavior.
|
||||
- Dynamic health and route failure handling.
|
||||
- Per-node work telemetry and accounting.
|
||||
- Model-agnostic infrastructure with architecture-specific certification.
|
||||
|
||||
### 1.2 Non-goals
|
||||
|
||||
- Reimplementing GGUF parsing, quantization kernels, tokenization, or mature CPU/GPU kernels from scratch.
|
||||
- Treating a whole-model node as the distributed solution.
|
||||
- Treating stock llama.cpp RPC as a safe volunteer-network protocol.
|
||||
- Claiming every GGUF architecture works without a bounded real distributed validation.
|
||||
- Mixing Transformers and GGUF shards in one route without an explicit compatible activation contract.
|
||||
|
||||
### 1.3 Parallelism taxonomy
|
||||
|
||||
These mechanisms must not be conflated:
|
||||
|
||||
- **Layer or pipeline parallelism** assigns whole contiguous transformer-layer ranges to different stages and transports boundary activations between them. This is the closest match to neuron-tai's tracker-routed shard contract.
|
||||
- **Tensor parallelism** partitions operations or tensors within each layer, such as attention heads, matrix rows/columns, or experts. It usually requires all ranks for every layer and collective or reduction traffic inside every layer.
|
||||
- **Local multi-device offload** places tensors or layers across devices controlled by one process. llama.cpp's `n_gpu_layers` and `tensor_split` belong here unless RPC devices are included.
|
||||
- **llama.cpp RPC offload** exposes remote GGML devices to a coordinator-owned graph. It is cross-machine, but remote processes are devices rather than independent model/session workers.
|
||||
- **Whole-model replication** runs one complete model per server and load-balances requests. It increases throughput and availability but does not allow one oversized model to span workers.
|
||||
|
||||
GGUF weight quantization such as Q4_K_M or Q8_0 reduces storage and memory pressure. It does not define the activation dtype, compute dtype, KV-cache dtype, or distributed topology.
|
||||
|
||||
## 2. Existing neuron-tai infrastructure
|
||||
|
||||
The repository already contains most of the control plane needed by a GGUF shard worker.
|
||||
|
||||
### 2.1 Backend and execution path
|
||||
|
||||
There is no formal backend interface yet. Production code uses the concrete `TorchModelShard`, while several call sites rely on duck typing.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `packages/node/meshnet_node/model_backend.py:72-223` — result types, session cache, and concrete backend.
|
||||
- `packages/node/meshnet_node/model_backend.py:226-345` — Torch/Hugging Face construction.
|
||||
- `packages/node/meshnet_node/model_backend.py:347-566` — effective head, middle, tail, and whole-model backend methods.
|
||||
- `packages/node/meshnet_node/model_backend.py:730-747` — Torch factory.
|
||||
- `packages/node/meshnet_node/torch_server.py:302-317` — injected backend usage.
|
||||
- `packages/node/meshnet_node/torch_server.py:717-766` — whole-model generation fast path.
|
||||
- `packages/node/meshnet_node/torch_server.py:1464-1514` — server construction.
|
||||
- `packages/node/meshnet_node/torch_server.py:1638-1659` — factory hard-wired to `load_torch_shard`.
|
||||
|
||||
The current effective distributed backend contract includes:
|
||||
|
||||
- `model_id`, `shard_start`, `shard_end`, `total_layers`, `is_head`, `is_tail`, and `device`.
|
||||
- `encode_prompt`, `encode_next_token`, `forward_bytes`, and `decode_tail_token`.
|
||||
- `eos_token_ids` and `release_session`.
|
||||
- Whole-model generation and token-count helpers.
|
||||
|
||||
A GGUF design should separate a generic backend/process lifecycle interface from an optional activation-shard interface rather than making every backend pretend to be `TorchModelShard`.
|
||||
|
||||
### 2.2 Distributed activation wire
|
||||
|
||||
The current data plane already provides a reusable envelope:
|
||||
|
||||
- Binary `POST /forward` requests.
|
||||
- `X-Meshnet-Session` route-session identity.
|
||||
- `X-Meshnet-Cache: prefill | decode`.
|
||||
- `X-Meshnet-Past-Len` cache consistency check.
|
||||
- `X-Meshnet-Start-Layer` overlap-safe execution.
|
||||
- Explicit tensor shape, dtype, position IDs, attention mask, chunk, and compression metadata.
|
||||
- Direct and relay downstream clients owned by the generation handler.
|
||||
- HTTP 409 cache-miss responses and re-prefill recovery.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `packages/node/meshnet_node/server.py:13-76` — wire contract and validation.
|
||||
- `packages/node/meshnet_node/torch_server.py:497-635` — binary handler.
|
||||
- `packages/node/meshnet_node/torch_server.py:974-1288` — route parsing and hop execution.
|
||||
- `packages/tracker/meshnet_tracker/server.py:3635-3781` — route planning and effective start layers.
|
||||
- `docs/adr/0008-binary-activation-wire-format.md`.
|
||||
- `docs/adr/0012-start-layer-overlapping-shards.md`.
|
||||
|
||||
The current payload semantics are still Torch/Hugging Face-specific:
|
||||
|
||||
- Boundary dtype is fixed to BF16.
|
||||
- Tensor conversion imports Torch.
|
||||
- Attention masks and position IDs use Torch-oriented serialization.
|
||||
- Tail-local decode reconstructs a Torch tensor.
|
||||
|
||||
The HTTP envelope is reusable; backend-neutral activation semantics need to be made explicit.
|
||||
|
||||
### 2.3 Local shard cache
|
||||
|
||||
The existing Transformers path already has the desired session behavior:
|
||||
|
||||
- One stable UUID per distributed generation.
|
||||
- Prefill establishes state on every shard.
|
||||
- Decode forwards only the new-token activation.
|
||||
- Every shard stores only its own layer state.
|
||||
- Cache lookup checks sequence length and effective start layer.
|
||||
- Cache miss, restart, or route mismatch returns HTTP 409.
|
||||
- The head re-prefills accumulated tokens after a miss.
|
||||
- TTL and LRU bound local memory.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `packages/node/meshnet_node/model_backend.py:102-193`.
|
||||
- `packages/node/meshnet_node/model_backend.py:334-345`.
|
||||
- `packages/node/meshnet_node/model_backend.py:595-662`.
|
||||
- `packages/node/meshnet_node/torch_server.py:806-944`.
|
||||
- `docs/adr/0022-sharded-per-node-kv-cache.md`.
|
||||
|
||||
A GGUF worker should preserve this product-level contract while mapping route sessions to llama.cpp sequence IDs or isolated contexts.
|
||||
|
||||
### 2.4 Tracker and capability admission
|
||||
|
||||
The tracker already supports:
|
||||
|
||||
- Model and layer-range registration.
|
||||
- Capability reports and fail-closed admission.
|
||||
- Heterogeneous route candidates.
|
||||
- Greedy interval coverage.
|
||||
- Effective start-layer injection.
|
||||
- Throughput/load/reputation-informed selection.
|
||||
- Dynamic health and route statistics.
|
||||
- Accounting and work attribution.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `packages/tracker/meshnet_tracker/server.py:592-942`.
|
||||
- `packages/tracker/meshnet_tracker/server.py:3635-3781`.
|
||||
- `packages/tracker/meshnet_tracker/server.py:4425-4595`.
|
||||
- `packages/tracker/meshnet_tracker/server.py:6200-6285`.
|
||||
- `docs/adr/0021-dynamic-statistical-routing.md`.
|
||||
- `docs/adr/0023-model-agnostic-node-capability-admission.md`.
|
||||
|
||||
GGUF gaps include:
|
||||
|
||||
- The closed `bfloat16`, `int8`, `nf4` precision vocabulary.
|
||||
- No first-class route kind or activation compatibility key.
|
||||
- No GGUF artifact descriptor.
|
||||
- No llama.cpp/Vulkan/HIP execution recipe.
|
||||
- No model-aware GGUF memory estimator.
|
||||
- No real llama.cpp doctor benchmark.
|
||||
|
||||
## 3. Ecosystem survey
|
||||
|
||||
No mature project was found that combines native GGUF, arbitrary tracker-selected layer ranges, heterogeneous cross-machine execution, independent local shard cache, dynamic route sessions, recovery, and per-node accounting.
|
||||
|
||||
A later GitHub follow-up found that [Nakshatra](https://github.com/fthrvi/nakshatra) already implements the narrow patched-llama.cpp Llama-family layer-worker seam and should be treated as the primary source donor and collaboration candidate. Its repository is not self-contained or production-ready enough to adopt as the runtime base. Historical GPUStack/llama-box and LiGGUF SARA were also audited in [the GitHub follow-up](distributed-gguf-github-followup.md). This reduces implementation risk but does not provide a drop-in Meshnet runtime.
|
||||
|
||||
### 3.1 Candidate summary
|
||||
|
||||
| Project | Native GGUF | Distributed form | Direct fit | Decision |
|
||||
|---|---:|---|---|---|
|
||||
| [llama.cpp](https://github.com/ggml-org/llama.cpp) | Yes | Device offload/RPC, local multi-GPU | Best kernel, wrong stock topology | Use as primary kernel through a small pinned fork |
|
||||
| [Nakshatra](https://github.com/fthrvi/nakshatra) | Yes, via sub-GGUFs and patched llama.cpp | Independent contiguous layer workers over gRPC | Closest implementation found | Primary source donor/collaboration candidate; do not adopt its repository wholesale |
|
||||
| [llama-gguf](https://github.com/Lexmata/llama-gguf) | Yes | gRPC layer pipeline | Architecturally close, immature custom runtime | Source donor only |
|
||||
| [prima.cpp](https://github.com/OpenCPIL/prima.cpp) | Yes | Static piped-ring layer windows | Proves partial loading and local KV | Source donor only |
|
||||
| [LiGGUF](https://github.com/matrixsmaster/ligguf) | Yes | SARA head/FFN tensor sharding and activation reduction | Every rank loads the full model; static master/workers | Compact tensor-parallel source donor only |
|
||||
| [mistral.rs](https://github.com/EricLBuehler/mistral.rs) | Yes | NCCL TP and multi-machine ring | Static/homogeneous distributed assumptions | Evaluate as optional homogeneous backend |
|
||||
| [vLLM](https://github.com/vllm-project/vllm) | Experimental/plugin | TP, PP, DP, EP via Ray/Torch distributed | Production clusters, not volunteer layer routes | Whole-model/managed-cluster lane and design donor; see [vLLM assessment](vllm-distributed-gguf-assessment.md) |
|
||||
| [distributed-llama](https://github.com/b4rtaz/distributed-llama) | No, custom format | Root/worker tensor parallelism | Power-of-two/static topology | Ideas only |
|
||||
| [Petals](https://github.com/bigscience-workshop/petals) | No | Internet layer pipeline | Strong cache/session semantics | Conceptual donor |
|
||||
| [exo](https://github.com/exo-explore/exo) | No native path | MLX tensor/pipeline sharding | MLX-centric and platform-asymmetric | Placement/cache ideas only |
|
||||
| [LocalAI](https://github.com/mudler/LocalAI) | Via llama.cpp | RPC workers and request routing | Control-plane overlap | Lifecycle ideas only |
|
||||
| [GPUStack](https://github.com/gpustack/gpustack) 0.4-0.7 | Via llama-box/RPC | Scheduler + primary/RPC workers | Proven RPC orchestration, not layer workers | Historical reference; GGUF distribution removed in GPUStack 2.0 |
|
||||
| [llama-box](https://github.com/gpustack/llama-box) | Yes | llama.cpp RPC | Archived | Historical source donor only |
|
||||
| [Chameleon](https://github.com/megeezy/Chameleon) | Via whole-model backends | Whole-model worker lifecycle/routing | No single-model sharding | Exclude from distributed-GGUF candidates |
|
||||
| [Continuum](https://github.com/CambrianTech/continuum) | Local loader/backend | Mesh orchestration; tensor distribution is roadmap text | No distributed GGUF execution path found | Exclude until executable evidence exists |
|
||||
| Ollama | Yes | Local multi-device; no cross-host model split | Whole-model only | Proxy baseline only |
|
||||
| MLC LLM | No native GGUF | Compiled local/multi-GPU | Different artifact/runtime | Separate recipe at most |
|
||||
|
||||
### 3.2 llama.cpp RPC
|
||||
|
||||
Stock RPC exposes remote GGML devices to one coordinator. Current upstream documentation says it can distribute weights and KV across local and remote devices and can cache remote tensors with `ggml-rpc-server -c`. This makes the original claim in `docs/adr/0001-pytorch-over-llama-cpp.md`—that every launch must always resend all weights—outdated.
|
||||
|
||||
RPC still does not provide:
|
||||
|
||||
- Independently loaded local GGUF layer workers.
|
||||
- Tracker-selected per-request routes.
|
||||
- A hidden-state HTTP endpoint.
|
||||
- Route-session-owned shard cache.
|
||||
- Per-worker tracker accounting.
|
||||
- A safe untrusted volunteer-node boundary.
|
||||
|
||||
Upstream explicitly calls RPC proof-of-concept, fragile, and insecure. It is not the target architecture.
|
||||
|
||||
### 3.3 Petals
|
||||
|
||||
Petals remains the strongest conceptual reference for:
|
||||
|
||||
- Hosting independent transformer block ranges.
|
||||
- Per-server cache state.
|
||||
- Session-based inference.
|
||||
- Rebuilding cache after route failure.
|
||||
- Capacity-aware block placement.
|
||||
- Public/private swarm control.
|
||||
|
||||
It is PyTorch/Transformers-based, not GGUF, and is no longer active enough to adopt as the runtime. The existing neuron-tai Transformers path already implements many of its core semantics.
|
||||
|
||||
## 4. Source audit: prima.cpp
|
||||
|
||||
Audited source snapshot:
|
||||
|
||||
```text
|
||||
OpenCPIL/prima.cpp
|
||||
commit 6f9b7c40962d777d1726456b4359340d932bef12
|
||||
```
|
||||
|
||||
### 4.1 Decision
|
||||
|
||||
Do not fork prima.cpp wholesale. Extract partial-loading, local-KV, graph-boundary, and placement ideas into a small fork of current llama.cpp.
|
||||
|
||||
prima.cpp is a full invasive llama.cpp fork. Its exact upstream ancestry could not be established from the available repository history. Distributed changes are embedded in public structures, `src/llama.cpp`, common CLI code, and the old server.
|
||||
|
||||
### 4.2 Layer ownership
|
||||
|
||||
prima.cpp adds static rank topology fields:
|
||||
|
||||
- `n_world`.
|
||||
- `rank`.
|
||||
- Fixed `n_layer_window[32]` arrays.
|
||||
- Cycle count and fixed network addresses/ports.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `include/llama.h:292-353`.
|
||||
- `src/llama.cpp:2597-2636`.
|
||||
- `common/arg.cpp:680-785`.
|
||||
|
||||
Layer ownership is a repeated cyclic window, not an arbitrary tracker-provided `[start,end)` route:
|
||||
|
||||
- `src/llama.cpp:3838-3883`.
|
||||
- `src/llama.cpp:16941-16951`.
|
||||
|
||||
This cannot directly represent dynamic tracker routes.
|
||||
|
||||
### 4.3 Partial local GGUF loading
|
||||
|
||||
This is prima.cpp's strongest reusable contribution.
|
||||
|
||||
- Rank zero creates token embedding, final norm, and output tensors.
|
||||
- Transformer tensors are created only when the layer belongs to the process.
|
||||
- Global layer IDs are compacted into local storage.
|
||||
- Unneeded GGUF tensors are erased before mapping/loading.
|
||||
- Only selected file regions are mapped and loaded.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/llama.cpp:7500-7619`.
|
||||
- `src/llama.cpp:9358-9515`.
|
||||
|
||||
This proves a llama.cpp-derived process can independently load only the GGUF tensors required by its layer range.
|
||||
|
||||
### 4.4 Graph boundaries
|
||||
|
||||
prima.cpp introduces explicit intermediate input/output tensors and skips unowned layers:
|
||||
|
||||
- `src/llama.cpp:10793-10813`.
|
||||
- `src/llama.cpp:11000-11117`.
|
||||
- `src/llama.cpp:16953-17010`.
|
||||
|
||||
The distributed graph path is hard-asserted to Llama or Qwen2 despite inherited architecture enums. Architecture support is therefore narrow.
|
||||
|
||||
### 4.5 Activation transport
|
||||
|
||||
Transport is raw ZeroMQ multipart PUSH/PULL. Messages include native shapes and raw contiguous F32 activation bytes.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/llama.cpp:18031-18077`.
|
||||
- `src/llama.cpp:18542-18563`.
|
||||
|
||||
It has no protocol version, model/session/route identity, layer range, dtype field, checksum, authentication, compression, or robust payload validation. It is incompatible with the existing Meshnet BF16 route-session protocol.
|
||||
|
||||
### 4.6 Local KV
|
||||
|
||||
Each process has one local llama.cpp context and allocates KV only for layers assigned to that process.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/llama.cpp:3889-3969`.
|
||||
- `src/llama.cpp:18268-18269`.
|
||||
|
||||
This is valuable physical behavior, but cache operations are coupled to one static ring and server slot numbering. There is no route session, route epoch, model fingerprint, range identity, lease, or idempotency contract.
|
||||
|
||||
### 4.7 Fault model and backends
|
||||
|
||||
Runtime sends and receives are blocking. Socket failures can terminate or stall a process. There is no runtime route repair, KV replay, deduplication, or accounting.
|
||||
|
||||
The inherited tree contains many backends, but the distributed profiler and placement solver primarily model CPU, CUDA, and Metal. Project documentation excludes AMD/Vulkan distributed support.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/llama.cpp:18031-18077`.
|
||||
- `src/llama.cpp:20492-20769`.
|
||||
- `common/profiler.h:329-405`.
|
||||
- `common/common.cpp:850-1183`.
|
||||
- `README.md:362-368`.
|
||||
|
||||
### 4.8 Tests
|
||||
|
||||
No meaningful distributed regression suite was found for layer windows, transport, cache propagation, heterogeneous placement, session isolation, or failure behavior.
|
||||
|
||||
### 4.9 Reusable parts
|
||||
|
||||
Port or use as design references:
|
||||
|
||||
- Selective endpoint/head/tail tensor creation.
|
||||
- Selected-layer GGUF mapping and loading.
|
||||
- Global-to-local layer indexing.
|
||||
- Per-layer KV filtering and allocation.
|
||||
- Boundary residual input/output.
|
||||
- Placement cost ideas and page prefetching.
|
||||
|
||||
Do not reuse:
|
||||
|
||||
- Static ring topology.
|
||||
- Fixed rank arrays.
|
||||
- ZeroMQ wire protocol.
|
||||
- Server patches.
|
||||
- Cache-command propagation.
|
||||
- Failure semantics.
|
||||
|
||||
## 5. Source audit: llama-gguf
|
||||
|
||||
Audited source snapshot:
|
||||
|
||||
```text
|
||||
Lexmata/llama-gguf
|
||||
commit 6e9f194206450080d47101c6f88a80f604ce69be
|
||||
```
|
||||
|
||||
### 5.1 Decision
|
||||
|
||||
Do not adopt `llama-gguf` as the inference runtime or distributed data plane. Use it as a source donor for protobuf organization, health/capability messages, explicit ranges, and synthetic multi-process tests.
|
||||
|
||||
### 5.2 Coordinator owns and streams the model
|
||||
|
||||
The coordinator loads and builds the entire GGUF model, then serializes layer tensors and streams them to shards over gRPC.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/distributed/coordinator.rs:44-53`.
|
||||
- `src/distributed/coordinator.rs:160-168`.
|
||||
- `src/distributed/coordinator.rs:196-299`.
|
||||
|
||||
Workers explicitly do not open local GGUF files:
|
||||
|
||||
- `src/distributed/shard.rs:53-59`.
|
||||
|
||||
This conflicts with independently owned local artifacts, startup efficiency, and tracker-controlled model distribution.
|
||||
|
||||
### 5.3 Explicit ranges but simple coverage validation
|
||||
|
||||
The cluster config supports explicit half-open ranges or even auto-partitioning:
|
||||
|
||||
- `src/distributed/config.rs:65-92`.
|
||||
- `src/distributed/config.rs:168-220`.
|
||||
|
||||
Manual validation checks total assigned layer count but does not prove sorted, gap-free, non-overlapping coverage. It is a static startup configuration, not a dynamic per-request route.
|
||||
|
||||
### 5.4 Shard-local KV, but only one global sequence
|
||||
|
||||
A worker allocates one KV cache for its local layer count:
|
||||
|
||||
- `src/distributed/shard.rs:30-44`.
|
||||
- `src/distributed/shard.rs:261-286`.
|
||||
- `src/model/mod.rs:62-117`.
|
||||
|
||||
There is no cache map or route-session identity. `ResetKvCache` takes an empty request and resets the one global cache:
|
||||
|
||||
- `src/distributed/shard.rs:447-458`.
|
||||
- `proto/distributed.proto:15-16`.
|
||||
- `proto/distributed.proto:95-103`.
|
||||
|
||||
Concurrent sessions, cache epochs, stale requests, and per-request eviction are unsupported.
|
||||
|
||||
### 5.5 Forward protocol
|
||||
|
||||
`ForwardRequest` contains only hidden state, position, and sequence length:
|
||||
|
||||
- `proto/distributed.proto:77-93`.
|
||||
|
||||
It has no model fingerprint, route/session/request identity, layer range, phase, chunk, expected cache length, accounting identity, idempotency key, or route epoch.
|
||||
|
||||
The tensor envelope does include shape, dtype, little-endian bytes, and a name:
|
||||
|
||||
- `proto/distributed.proto:36-46`.
|
||||
- `src/distributed/tensor_transfer.rs:10-115`.
|
||||
|
||||
The DType enum can represent BF16, but the distributed execution path and tests use F32 hidden states and F32 KV.
|
||||
|
||||
### 5.6 Token-by-token serialized execution
|
||||
|
||||
The distributed model loops over tokens one at a time and sends each hidden vector through each shard. The complete pipeline is protected by one mutex.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/distributed/model.rs:21-39`.
|
||||
- `src/distributed/model.rs:86-147`.
|
||||
- `src/distributed/pipeline.rs:45-113`.
|
||||
|
||||
This prevents efficient batched prefill and safe session multiplexing.
|
||||
|
||||
### 5.7 Architecture claims versus shard reconstruction
|
||||
|
||||
The repository has a large architecture enum, but the distributed shard reconstructs a simple dense layer consisting of RMSNorm, ordinary attention, and dense gated FFN.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/model/architecture.rs:5-155`.
|
||||
- `src/distributed/shard.rs:154-223`.
|
||||
|
||||
The worker cannot reconstruct many architecture-specific layer forms such as MoE, hybrid/recurrent layers, shared cache, architecture-specific norms, or specialized projections. Qwen3 MoE support cannot be inferred from the enum list.
|
||||
|
||||
### 5.8 Backends
|
||||
|
||||
Shard backend selection tries CUDA, Metal, DX12, Vulkan, then CPU, but the source notes GPU model-weight preloading is skipped because weights arrive as gRPC tensors. The per-operation backend path is not equivalent to llama.cpp's mature graph scheduling.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/distributed/shard.rs:53-115`.
|
||||
- `src/backend/mod.rs:25-260`.
|
||||
|
||||
### 5.9 Fault recovery
|
||||
|
||||
The repository has health monitoring and can reconnect, reconfigure, and resend layers:
|
||||
|
||||
- `src/distributed/fault.rs:83-360`.
|
||||
|
||||
It does not restore active KV, route position, partial prefill, session ownership, or accounting state. Reloading a process is not active-generation recovery.
|
||||
|
||||
### 5.10 Tests
|
||||
|
||||
Distributed integration tests start local CPU gRPC shards with synthetic dense F32 weights. They verify configure, load, forward, reset, and a two-shard pipeline.
|
||||
|
||||
The file explicitly states that no real GGUF is loaded:
|
||||
|
||||
- `tests/distributed_integration_test.rs:1-7`.
|
||||
|
||||
Missing evidence includes real quantized GGUF parity, heterogeneous devices, BF16 boundaries, batched prefill, concurrent sessions, worker loss, route epochs, local artifact loading, and cache recovery.
|
||||
|
||||
### 5.11 Reusable parts
|
||||
|
||||
Potential donors:
|
||||
|
||||
- Protobuf tensor shape/dtype/data envelope.
|
||||
- Explicit half-open layer ranges.
|
||||
- Health and capabilities messages.
|
||||
- Synthetic multi-process shard test harness.
|
||||
- Separation of configure, load, forward, reset, and health methods.
|
||||
|
||||
Do not reuse:
|
||||
|
||||
- Coordinator-side weight streaming.
|
||||
- Custom inference engine as the production kernel.
|
||||
- Single global KV cache.
|
||||
- Token-by-token pipeline.
|
||||
- Dense-only layer reconstruction.
|
||||
- Fault-reload semantics.
|
||||
- The all-reduce implementation, which currently echoes its input at `src/distributed/shard.rs:522-541`.
|
||||
|
||||
## 6. Source audit: current llama.cpp
|
||||
|
||||
Audited source snapshot:
|
||||
|
||||
```text
|
||||
ggml-org/llama.cpp
|
||||
commit 91c631b21d6e5d09e9c6659efdf6baeef5a44ddb
|
||||
```
|
||||
|
||||
### 6.1 Decision
|
||||
|
||||
The smallest credible implementation is a pinned, architecture-certified layer-worker fork of current llama.cpp. A fully generic all-architecture patch is not credible as the first implementation.
|
||||
|
||||
### 6.2 Layer-range API
|
||||
|
||||
Prefer a new range loader that isolates ABI churn:
|
||||
|
||||
```c
|
||||
llama_model * llama_model_load_range(
|
||||
const char * path,
|
||||
llama_model_params params,
|
||||
int32_t il_start,
|
||||
int32_t il_end);
|
||||
```
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `include/llama.h:295-331`.
|
||||
- `src/llama-model.cpp:2300-2319`.
|
||||
|
||||
Adding fields directly to the by-value public parameter structure changes ABI. A project-owned API around internal model state is safer for a pinned worker executable.
|
||||
|
||||
### 6.3 Partial weight loading
|
||||
|
||||
Current loader behavior already makes a small patch possible:
|
||||
|
||||
- Architectures create/register tensors.
|
||||
- Backend buffers are allocated only for created tensor contexts.
|
||||
- Mmap ranges derive from created tensors.
|
||||
- `done_getting_tensors(partial=true)` exists.
|
||||
- Data loading only processes tensors present in created contexts.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `src/llama-model.cpp:1229-1647`.
|
||||
- `src/llama-model-loader.cpp:1054-1572`.
|
||||
|
||||
For an initial Llama-family adapter:
|
||||
|
||||
- Create token embedding only if `il_start == 0`.
|
||||
- Create final norm/output only if `il_end == n_layer`.
|
||||
- Create repeating tensors only for `[il_start, il_end)`.
|
||||
- Finish model loading with partial mode.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `src/models/llama.cpp:34-92`.
|
||||
- `src/llama-model.cpp:1490`.
|
||||
|
||||
### 6.4 Residual input
|
||||
|
||||
Current input machinery supports F32 supplied embeddings:
|
||||
|
||||
- `src/llama-graph.h:120-150`.
|
||||
- `src/llama-graph.cpp:66-121`.
|
||||
- `src/llama-graph.cpp:2151-2229`.
|
||||
|
||||
A middle shard needs a dedicated residual input that bypasses token lookup, embedding scaling, LoRA, and padding behavior while still accepting position and sequence IDs for RoPE/KV.
|
||||
|
||||
### 6.5 Ranged graph execution
|
||||
|
||||
The initial Llama graph loop is in:
|
||||
|
||||
- `src/models/llama.cpp:98-247`.
|
||||
|
||||
It must:
|
||||
|
||||
- Iterate only `[il_start, il_end)`.
|
||||
- Preserve all boundary rows for intermediate shards.
|
||||
- Avoid final-token-only row pruning except on the actual tail.
|
||||
- Run final norm and LM head only on the actual tail.
|
||||
|
||||
### 6.6 Boundary output
|
||||
|
||||
The network boundary is the unnormalized residual stream after the final owned transformer layer, not llama.cpp's final embedding tensor.
|
||||
|
||||
Add a dedicated graph result such as `t_boundary` in:
|
||||
|
||||
- `src/llama-graph.h:791-865`.
|
||||
- `src/llama-graph.cpp:1190-1251`.
|
||||
- `src/llama-context.cpp:2206-2231`.
|
||||
|
||||
Existing `llama-ext` layer-input extraction is useful precedent but is explicitly staging/WIP and only populated by some architectures.
|
||||
|
||||
### 6.7 Shard-local KV
|
||||
|
||||
The standard KV cache already supports a layer filter and compact global-to-local mapping:
|
||||
|
||||
- `src/llama-kv-cache.cpp:64-100`.
|
||||
- `src/llama-kv-cache.cpp:163-248`.
|
||||
- `src/llama-kv-cache.cpp:1210-1327`.
|
||||
- `src/llama-model.cpp:2152-2266`.
|
||||
|
||||
For a standard Llama adapter, the filter is conceptually:
|
||||
|
||||
```cpp
|
||||
return il >= il_start && il < il_end;
|
||||
```
|
||||
|
||||
Do not apply this blindly to hybrid, recurrent, shared-cache, MLA, or other architecture-specific memory implementations.
|
||||
|
||||
### 6.8 Route-session mapping
|
||||
|
||||
Existing sequence APIs support:
|
||||
|
||||
- Sequence removal, copy, keep, and position operations.
|
||||
- Sequence state get/set.
|
||||
- Partial/on-device state flags.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `include/llama.h:720-913`.
|
||||
- `src/llama-context.cpp:2900-2968`.
|
||||
- `src/llama-context.cpp:3978-4024`.
|
||||
|
||||
A shard worker can map `(route_session_id, route_epoch)` to a stable `llama_seq_id` or one isolated context. Because the model contains only filtered layers, sequence state naturally represents shard-local cache.
|
||||
|
||||
### 6.9 Scheduler
|
||||
|
||||
No scheduler rewrite should be required. The current graph scheduler can allocate a new residual input and boundary output if they are registered correctly.
|
||||
|
||||
Relevant source:
|
||||
|
||||
- `src/llama-context.cpp:1286-1355`.
|
||||
- `src/llama-context.cpp:2324-2459`.
|
||||
|
||||
### 6.10 Architecture coupling
|
||||
|
||||
A fully generic minimal patch is not plausible:
|
||||
|
||||
- The audited tree has roughly 136 model implementation files.
|
||||
- More than one hundred contain architecture-specific layer loops.
|
||||
- Architectures differ in cache, residual, attention, MoE, recurrent, multimodal, and output behavior.
|
||||
|
||||
Generic infrastructure is appropriate for:
|
||||
|
||||
- Range validation.
|
||||
- Tensor filtering.
|
||||
- Residual input/output plumbing.
|
||||
- Standard KV filtering.
|
||||
- C ABI and worker protocol.
|
||||
|
||||
Each supported architecture still needs a reviewed range-aware adapter and real distributed validation. Unsupported architectures must fail closed.
|
||||
|
||||
## 7. Selected architecture
|
||||
|
||||
```text
|
||||
Existing tracker and accounting
|
||||
|
|
||||
Existing versioned activation/relay protocol
|
||||
|
|
||||
Project-owned standalone C++ GGUF shard worker
|
||||
(borrowing or jointly maintaining narrow Nakshatra patch concepts where practical)
|
||||
|
|
||||
Pinned llama.cpp fork with small architecture-specific patch series
|
||||
|
|
||||
GGUF loader, model graphs, quant kernels, KV, CPU/GPU backends
|
||||
```
|
||||
|
||||
Nakshatra's working patch and daemon should be reproduced, rebased, and used as source/test evidence before equivalent code is independently designed. Collaboration on narrow upstreamable llama.cpp hooks is preferable to duplicate patch families, but the project should keep its own small pinned fork and standalone worker rather than fork Nakshatra's whole Petals-derived repository. The project-owned boundary remains necessary because Nakshatra's build, control plane, protobuf, sub-GGUF artifact model, single-session daemon, and accounting semantics do not satisfy the full Meshnet contract.
|
||||
|
||||
### 7.1 Responsibility split
|
||||
|
||||
Python/node agent remains responsible for:
|
||||
|
||||
- Tracker registration and heartbeat.
|
||||
- Capability proof and admission.
|
||||
- Artifact distribution and verification.
|
||||
- Route selection and effective start layers.
|
||||
- Activation transport and relay.
|
||||
- Route sessions, epochs, cache-miss recovery, and process supervision.
|
||||
- Work telemetry and accounting.
|
||||
|
||||
The C++ worker is responsible for:
|
||||
|
||||
- Loading exactly one model/range/recipe.
|
||||
- Token embedding on a head shard.
|
||||
- Boundary activation input on middle/tail shards.
|
||||
- Executing only owned layers.
|
||||
- Returning boundary activations on non-tail shards.
|
||||
- Final norm/logits/sampling or token output on a tail shard.
|
||||
- Local KV/recurrent state by mapped sequence.
|
||||
- Exporting health, cache, load, compute, and memory metrics.
|
||||
|
||||
### 7.2 Stable worker boundary
|
||||
|
||||
Do not expose `ggml_tensor *`, scheduler objects, or llama.cpp structs to Python. Prefer a supervised executable with a small project-owned API, for example:
|
||||
|
||||
```text
|
||||
load_layer_range
|
||||
prefill_tokens
|
||||
prefill_activation
|
||||
decode_token
|
||||
decode_activation
|
||||
get_boundary
|
||||
get_logits_or_token
|
||||
release_session
|
||||
export_session
|
||||
import_session
|
||||
health
|
||||
metrics
|
||||
```
|
||||
|
||||
The wire carries at least:
|
||||
|
||||
```text
|
||||
protocol version
|
||||
request/work ID
|
||||
route session ID
|
||||
route epoch
|
||||
model/artifact fingerprint
|
||||
layer begin/end
|
||||
effective start layer
|
||||
prefill/decode phase
|
||||
token start/count
|
||||
hidden shape and dtype
|
||||
payload length/checksum
|
||||
```
|
||||
|
||||
### 7.3 Battle-proven transport and concurrency
|
||||
|
||||
The implementation program chooses gRPC over HTTP/2 with Protocol Buffers for the standalone Python/C++ Shard data plane rather than inventing a raw socket protocol.
|
||||
|
||||
- One long-lived bidirectional stream serves one Route Session Activation Seam.
|
||||
- HTTP/2 supplies connection reuse and flow control; gRPC supplies deadlines, cancellation, status, TLS hooks, and generated Python/C++ schemas.
|
||||
- Large prefill tensors are split into bounded frames; decode uses a small fast path.
|
||||
- Existing relay/WebSocket infrastructure may transport the same protobuf frames as opaque binary when direct gRPC reachability is unavailable.
|
||||
- OpenAI-facing HTTP/SSE and existing Tracker APIs remain unchanged.
|
||||
|
||||
The public payload is a versioned named-tensor bundle, not one anonymous activation, because architecture boundaries such as vLLM's Llama/Qwen3-MoE pipeline stages can require both `hidden_states` and `residual`.
|
||||
|
||||
Concurrency is implemented with local llama.cpp sequences or bounded contexts mapped from `(Route Session ID, route epoch)`. Compatible active decode steps are continuously batched inside each node. This adapts the useful vLLM scheduling concept without importing vLLM's Torch process groups, PagedAttention allocator, or static distributed executor.
|
||||
|
||||
Tensor/expert parallel collectives remain confined to a future trusted composite-node or managed-cluster provider. The public volunteer primitive remains contiguous layer Shards.
|
||||
|
||||
The benchmark-gated execution plan and Ralph backlog live in [the active distributed-GGUF feature](../../.scratch/distributed-gguf-runtime/README.md).
|
||||
|
||||
## 8. Implementation spikes and acceptance gates
|
||||
|
||||
### 8.1 Spike 1: reproduce and rebase Nakshatra
|
||||
|
||||
Before writing a separate worker, reproduce Nakshatra's dense Llama-family two-worker path and rebase its narrow patch onto the exact current llama.cpp commit selected by this project.
|
||||
|
||||
Run two local workers with disjoint sub-GGUF ranges, then compare sub-GGUF loading with a range-aware loader over one shared source artifact.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- The audited Nakshatra test is independently reproducible rather than accepted from repository claims.
|
||||
- Each process maps only its assigned tensors.
|
||||
- Each process allocates KV only for assigned layers.
|
||||
- Head/middle/tail module ownership is correct.
|
||||
- Boundary residuals produce bounded numerical error against whole-model llama.cpp.
|
||||
- The patch rebases cleanly onto the pinned current llama.cpp baseline.
|
||||
- Reusable worker code is isolated from Nakshatra's Petals-derived and project-specific control plane.
|
||||
- Upstream collaboration or shared maintenance is evaluated before duplicating the patch family.
|
||||
|
||||
### 8.2 Spike 2: Meshnet protocol and multi-session KV
|
||||
|
||||
Place the rebased/Nakshatra-derived worker behind Meshnet's route/session protocol.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Multiple route sessions remain isolated through separate `llama_seq_id` values or contexts.
|
||||
- Duplicate requests are idempotent and stale route epochs are rejected.
|
||||
- Exact source/slice artifact hashes and runtime recipes are checked.
|
||||
- Cache miss, release, TTL/LRU eviction, cancellation, and re-prefill are bounded.
|
||||
- Killing a worker produces a bounded failure, not a deadlock.
|
||||
- Work receipts bind worker identity, request, route, range, artifact, and activation evidence.
|
||||
|
||||
### 8.3 Spike 3: heterogeneous two-machine route
|
||||
|
||||
Use a tracker-selected two-machine route with real CPU/GPU execution.
|
||||
|
||||
Measure:
|
||||
|
||||
- Cold and warm model load.
|
||||
- Mapped tensor and KV memory per process.
|
||||
- Prefill and decode throughput.
|
||||
- Activation bytes and boundary latency.
|
||||
- Whole-model parity.
|
||||
- Process/node failure behavior.
|
||||
- Per-node completed work and compute time.
|
||||
|
||||
Synthetic tests remain unit coverage, not distributed validation.
|
||||
|
||||
### 8.4 Spike 4: Qwen3 MoE adapter
|
||||
|
||||
Qwen3 30B-A3B requires an explicit architecture adapter. It must review:
|
||||
|
||||
- Expert/router tensor ownership.
|
||||
- Top-k routing and MoE graph behavior.
|
||||
- Architecture-specific normalization and Q/K normalization.
|
||||
- Layer-local expert loading.
|
||||
- KV type and layout.
|
||||
- Boundary residual placement.
|
||||
- Supported HIP/Vulkan/CPU paths.
|
||||
|
||||
The adapter should reuse current llama.cpp's Qwen3 graph rather than reconstructing a simplified layer as `llama-gguf` does.
|
||||
|
||||
### 8.5 Architecture certification
|
||||
|
||||
Each architecture recipe should declare:
|
||||
|
||||
- Range-aware graph implementation and version.
|
||||
- Head/tail module rules.
|
||||
- Cache kind and state support.
|
||||
- Boundary dtype/layout.
|
||||
- Supported backends.
|
||||
- Required runtime commit.
|
||||
- Real distributed validation evidence.
|
||||
|
||||
Capability admission keeps unsupported combinations registered-but-dark.
|
||||
|
||||
## 9. Principal risks
|
||||
|
||||
1. **Upstream churn:** llama.cpp model/graph/cache internals change frequently. Keep a small ordered patch series and pinned revisions.
|
||||
2. **Architecture coupling:** every architecture adapter needs review and live proof.
|
||||
3. **Cache locality:** dynamic rerouting requires replay/checkpoint semantics, not socket retry.
|
||||
4. **Boundary compatibility:** shape, dtype, residual point, positions, and cache semantics must match exactly.
|
||||
5. **Artifact identity:** exact GGUF/model/tokenizer/runtime fingerprints must be part of capability proof.
|
||||
6. **Heterogeneous bottlenecks:** the slowest seam can dominate; placement must use measured end-to-end cost.
|
||||
7. **Accounting integrity:** completed-work receipts need route and worker identity and must not trust self-reported latency alone.
|
||||
8. **Security:** the worker accepts structured activations, not arbitrary GGML graphs or executable recipes.
|
||||
9. **Memory estimates:** weight quantization and KV/state type must both be represented.
|
||||
10. **Testing:** every supported lane needs real CPU/GPU tracker-routed acceptance evidence.
|
||||
|
||||
## 10. Final conclusion
|
||||
|
||||
Distributed GGUF should not be built from zero, but no existing project can be adopted as-is.
|
||||
|
||||
The reusable composition is:
|
||||
|
||||
- llama.cpp for mature GGUF parsing, architecture graphs, quantized kernels, backend scheduling, and KV/state APIs.
|
||||
- prima.cpp for proof and reference implementations of selective local GGUF loading, local layer KV, graph boundaries, and placement ideas.
|
||||
- `llama-gguf` for explicit range/protobuf/health/test-organization ideas.
|
||||
- Petals and the existing neuron-tai Transformers path for route-session, cache-locality, and recovery semantics.
|
||||
- The existing tracker, relay, capability, and accounting systems as the authoritative control plane.
|
||||
|
||||
The selected path is a small pinned llama.cpp layer-worker fork with generic shard infrastructure and explicitly certified architecture adapters.
|
||||
657
docs/research/vllm-distributed-gguf-assessment.md
Normal file
657
docs/research/vllm-distributed-gguf-assessment.md
Normal file
@@ -0,0 +1,657 @@
|
||||
# vLLM assessment for distributed GGUF inference
|
||||
|
||||
Status: Source and documentation assessment
|
||||
Last updated: 2026-07-13
|
||||
|
||||
## 1. Question
|
||||
|
||||
Can vLLM or its GGUF plugin be reused to implement neuron-tai's primary distributed GGUF data plane?
|
||||
|
||||
The required data plane is not merely a whole-model serving cluster. It requires independently loaded GGUF layer ranges on heterogeneous nodes, tracker-selected per-request routes, a versioned activation boundary, local route-session cache, dynamic failure handling, relay support, and per-node work attribution.
|
||||
|
||||
This assessment also identifies vLLM components and concepts that remain useful even if its distributed runtime is not adopted.
|
||||
|
||||
## 2. Sources and snapshots
|
||||
|
||||
Source snapshots audited:
|
||||
|
||||
```text
|
||||
vllm-project/vllm
|
||||
commit 107a03ba63e005ff03424fed9c4e6cf551b98bb2
|
||||
|
||||
vllm-project/vllm-gguf-plugin
|
||||
commit ad209df10bb1856ba53b6663745a831eb7eb09cc
|
||||
```
|
||||
|
||||
Current official documentation reviewed:
|
||||
|
||||
- [GGUF](https://docs.vllm.ai/en/latest/features/quantization/gguf/).
|
||||
- [Parallelism and Scaling](https://docs.vllm.ai/en/stable/serving/parallelism_scaling/).
|
||||
- [Disaggregated Prefilling](https://docs.vllm.ai/en/latest/features/disagg_prefill/).
|
||||
- [Data Parallel Deployment](https://docs.vllm.ai/en/latest/serving/data_parallel_deployment/).
|
||||
- [Installation and supported platforms](https://docs.vllm.ai/en/latest/getting_started/installation/).
|
||||
- [RFC #39583: Migrate bitsandbytes and GGUF quantization support to an out-of-tree plugin](https://github.com/vllm-project/vllm/issues/39583).
|
||||
|
||||
Both source projects are Apache-2.0 licensed.
|
||||
|
||||
## 3. Executive decision
|
||||
|
||||
### 3.1 Primary distributed GGUF runtime
|
||||
|
||||
Do **not** use vLLM as the primary tracker-routed heterogeneous GGUF layer-worker runtime.
|
||||
|
||||
Reasons:
|
||||
|
||||
- Current GGUF support is an experimental out-of-tree quantization plugin.
|
||||
- The plugin converts GGUF tensors into Torch parameters and uses vLLM's PyTorch model implementations; it is not a lightweight llama.cpp/GGML runtime.
|
||||
- The plugin builds CUDA/HIP Torch extensions and does not provide CPU, Vulkan, or Metal GGUF execution.
|
||||
- vLLM pipeline parallelism assumes one static Torch distributed world with rank/process-group semantics.
|
||||
- Pipeline workers communicate with neighboring ranks through Torch distributed or a trusted TCP/device communicator, not the Meshnet HTTP/relay/session protocol.
|
||||
- Requests are scheduled as one synchronized engine, not independently routed through tracker-selected workers.
|
||||
- Failure of a pipeline rank is a distributed-engine failure, not a route-local cache miss that can be recovered by rebuilding a route.
|
||||
- Multi-node guidance requires identical environments and private trusted networking.
|
||||
- External vLLM load balancing is across complete data-parallel model replicas, not model-layer workers.
|
||||
|
||||
### 3.2 Supported secondary roles
|
||||
|
||||
vLLM remains useful in three narrower roles:
|
||||
|
||||
1. **Whole-model node backend:** a node proxy can register an independent vLLM server for models/hardware that fit on that deployment. This is an execution recipe, not the core distributed GGUF feature.
|
||||
2. **Static managed-cluster backend:** a separately managed, trusted vLLM TP/PP cluster can be exposed as one logical node. The tracker accounts at cluster boundary unless a trusted internal accounting bridge is added.
|
||||
3. **Source/design donor:** architecture-aware pipeline boundaries, local layer construction, PagedAttention scheduling, KV connector lifecycles, and telemetry are valuable references.
|
||||
|
||||
### 3.3 GGUF kernel reuse
|
||||
|
||||
Do not import the vLLM GGUF plugin into the selected llama.cpp shard worker.
|
||||
|
||||
The plugin's GGUF kernels are tightly coupled to:
|
||||
|
||||
- PyTorch parameters and custom operators.
|
||||
- vLLM quantization interfaces.
|
||||
- Triton/CUDA/HIP execution.
|
||||
- vLLM's model and tensor-parallel parameter layouts.
|
||||
- vLLM release internals and monkey-patched plugin registration.
|
||||
|
||||
llama.cpp already provides broader GGUF quant coverage, CPU and edge backends, mmap loading, and the desired C/C++ runtime boundary.
|
||||
|
||||
## 4. Current GGUF support in vLLM
|
||||
|
||||
### 4.1 Support moved out of core
|
||||
|
||||
Official documentation states that GGUF support is:
|
||||
|
||||
- Highly experimental.
|
||||
- Under-optimized.
|
||||
- Potentially incompatible with other features.
|
||||
- Now provided by the separate `vllm-gguf-plugin`.
|
||||
|
||||
The migration RFC gives the maintenance rationale. A vLLM maintainer estimated GGUF usage at roughly 0.1%, described poor performance relative to llama.cpp for batch-size-one consumer-GPU workloads, and identified a large special-case burden: legacy weight-loader branches, model-specific mappings, and roughly 6,000 lines of GGUF CUDA kernels. The out-of-tree plugin preserves availability while allowing vLLM core to prioritize its native production quantization paths. This is evidence that GGUF is a compatibility lane in vLLM, not its primary runtime focus.
|
||||
|
||||
At the audited snapshot, the plugin is itself young: version `0.0.4`, with its repository created during the 2026 migration. This is not a reason to reject it as a whole-model backend, but it raises compatibility and maintenance risk for using it as foundational distributed infrastructure.
|
||||
|
||||
The plugin registers itself through the `vllm.general_plugins` entry point:
|
||||
|
||||
- `vllm-gguf-plugin/pyproject.toml:17-18`.
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/plugin.py:109-127`.
|
||||
|
||||
It patches vLLM engine argument creation and speculative-model probing at runtime:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/plugin.py:51-97`.
|
||||
|
||||
This demonstrates close coupling to vLLM internals and increases upgrade risk.
|
||||
|
||||
### 4.2 Dependencies and hardware assumptions
|
||||
|
||||
The plugin requires:
|
||||
|
||||
- `gguf>=0.17.0`.
|
||||
- `vllm`.
|
||||
- `torch>=2.9`.
|
||||
- CUDA or ROCm toolkit for normal installation.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm-gguf-plugin/pyproject.toml:1-18`.
|
||||
- `vllm-gguf-plugin/README.md:7-13`.
|
||||
|
||||
Its compiled extension is created through `torch.utils.cpp_extension.CUDAExtension`. ROCm is handled by compiling the same extension through HIP-compatible tooling:
|
||||
|
||||
- `vllm-gguf-plugin/setup.py:28-65`.
|
||||
|
||||
This gives a CUDA/ROCm lane, not the CPU/Vulkan/Metal hardware coverage expected from llama.cpp.
|
||||
|
||||
### 4.3 Model/config/tokenizer dependency
|
||||
|
||||
GGUF is treated as a weight source for a vLLM/Hugging Face model implementation.
|
||||
|
||||
The plugin determines model configuration from, in order:
|
||||
|
||||
- Explicit `hf_config_path`.
|
||||
- A non-GGUF tokenizer path.
|
||||
- The remote GGUF repository.
|
||||
- The local GGUF parent directory.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/plugin.py:34-48`.
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/plugin.py:51-79`.
|
||||
|
||||
Official docs recommend using the base Hugging Face tokenizer because converting a tokenizer from GGUF is slow and unstable. If Hugging Face cannot derive the architecture config, users must supply a compatible config path.
|
||||
|
||||
This is different from llama.cpp, which treats GGUF metadata, tensors, tokenizer, and architecture implementation as one native runtime artifact.
|
||||
|
||||
### 4.4 Artifact resolution
|
||||
|
||||
The plugin accepts:
|
||||
|
||||
- A local GGUF file.
|
||||
- A local directory plus quantization type.
|
||||
- A remote `repo_id:quant_type`.
|
||||
- A remote `repo_id/filename.gguf`.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/loader.py:41-67`.
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/weight_utils.py:18-70`.
|
||||
|
||||
It supports split GGUF artifacts named like `-00001-of-00004.gguf`:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/weights_adapter/default.py:248-264`.
|
||||
|
||||
This is file sharding, not model-layer network sharding.
|
||||
|
||||
### 4.5 GGUF-to-vLLM name mapping
|
||||
|
||||
The default adapter:
|
||||
|
||||
1. Maps a GGUF architecture to Hugging Face tensor names through the Python `gguf` package.
|
||||
2. Instantiates a Hugging Face model on the `meta` device to obtain its state-dict names.
|
||||
3. Creates architecture-specific special mappings for MoE and other variants.
|
||||
4. Maps those Hugging Face names into vLLM's packed parameter layout.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/weights_adapter/default.py:42-231`.
|
||||
- Qwen2/Qwen3 MoE mappings: `vllm-gguf-plugin/vllm_gguf_plugin/weights_adapter/default.py:81-98`.
|
||||
- vLLM packed Llama mappings: `vllm/model_executor/models/llama.py:344-354`.
|
||||
- vLLM packed Qwen3-MoE mappings: `vllm/model_executor/models/qwen3_moe.py:432-446`.
|
||||
|
||||
This provides broad model integration by relying on vLLM and Transformers model classes. It does not expose a generic independently executable GGUF layer object.
|
||||
|
||||
### 4.6 Loading behavior
|
||||
|
||||
`GGUFModelLoader` initializes an ordinary vLLM model on the target Torch device and calls its `load_weights` method with tensors yielded by the adapter:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/loader.py:78-104`.
|
||||
|
||||
The GGUF iterator:
|
||||
|
||||
- Opens each GGUF file through `gguf.GGUFReader`.
|
||||
- Enumerates every tensor.
|
||||
- Converts NumPy-backed data to a Torch tensor.
|
||||
- Emits an extra quantization-type parameter for quantized weights.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/weight_utils.py:73-135`.
|
||||
|
||||
Pipeline stages instantiate only their repeating local layers, so vLLM's `AutoWeightsLoader` can skip parameters represented by `PPMissingLayer`. However, the plugin still enumerates the artifact through Python on each worker. This is not the same as selectively registering only a layer range and mmaping only its file regions in llama.cpp.
|
||||
|
||||
### 4.7 Quantization types and kernels
|
||||
|
||||
The plugin declares support for:
|
||||
|
||||
- Unquantized F32, F16, BF16.
|
||||
- Q4_0, Q4_1, Q5_0, Q5_1, Q8_0, Q8_1.
|
||||
- Q2_K, Q3_K, Q4_K, Q5_K, Q6_K.
|
||||
- IQ1_M, IQ1_S, IQ2_XXS, IQ2_XS, IQ2_S, IQ3_XXS, IQ3_S, IQ4_XS, IQ4_NL.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/quantization/utils.py:46-75`.
|
||||
|
||||
Linear execution chooses among:
|
||||
|
||||
- Native unquantized matrix multiplication.
|
||||
- Quantized matrix-vector kernels for small token counts.
|
||||
- Quantized matrix-matrix kernels for larger token counts.
|
||||
- Full dequantization followed by Torch matrix multiplication as fallback.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/quantization/linear.py:34-57`.
|
||||
- `vllm-gguf-plugin/vllm_gguf_plugin/quantization/linear.py:79-247`.
|
||||
|
||||
There are separate Triton and compiled CUDA/HIP implementations for GEMM, dequantization, embedding, and fused MoE.
|
||||
|
||||
These kernels are useful to the vLLM ecosystem but are not portable drop-in components for a llama.cpp/GGML worker.
|
||||
|
||||
### 4.8 Test evidence
|
||||
|
||||
Plugin tests compare GGUF output against unquantized vLLM output for small models including Qwen2.5, Qwen3 dense, Phi3, GPT-2, StableLM, Gemma3, and OLMoE:
|
||||
|
||||
- `vllm-gguf-plugin/tests/test_gguf_generation.py:22-79`.
|
||||
- `vllm-gguf-plugin/tests/test_gguf_generation.py:177-226`.
|
||||
|
||||
There is a two-GPU tensor-parallel GGUF test for Qwen3-0.6B:
|
||||
|
||||
- `vllm-gguf-plugin/tests/test_gguf_generation.py:229-283`.
|
||||
|
||||
The core vLLM plugin test also covers Qwen3 dense and OLMoE at TP=1 and TP=2:
|
||||
|
||||
- `tests/plugins_tests/gguf/test_gguf_plugin_generate.py:27-132`.
|
||||
|
||||
No source test was found for:
|
||||
|
||||
- GGUF pipeline parallelism.
|
||||
- Multi-node GGUF PP.
|
||||
- Mixed hardware.
|
||||
- Qwen3 30B-A3B GGUF.
|
||||
- Tracker-selected ranges.
|
||||
- Independent route sessions or dynamic route recovery.
|
||||
|
||||
Kernel tests exercise CUDA tensors and broad quant types. One variable-length batching kernel test is explicitly skipped because the current CUDA kernel does not support that case:
|
||||
|
||||
- `vllm-gguf-plugin/tests/test_kernels.py:260-310`.
|
||||
|
||||
## 5. vLLM pipeline parallelism
|
||||
|
||||
### 5.1 What it gets right
|
||||
|
||||
vLLM has a mature architecture-aware pipeline abstraction:
|
||||
|
||||
- Model implementations construct only repeating layers assigned to the local PP rank.
|
||||
- Missing layers are represented with `PPMissingLayer` placeholders.
|
||||
- First-stage token embeddings and last-stage norm/head can be conditionally owned.
|
||||
- Non-tail stages return named intermediate tensor bundles.
|
||||
- The scheduler and model runner overlap stage communication and execution.
|
||||
- Uneven layer-count splits are supported.
|
||||
|
||||
This is substantially more mature than the static dense-layer reconstruction in `llama-gguf`.
|
||||
|
||||
### 5.2 Static stage partitioning
|
||||
|
||||
`make_layers` derives `(start_layer, end_layer)` from PP rank and PP world size, creates real modules only for that interval, and fills all other positions with `PPMissingLayer`:
|
||||
|
||||
- `vllm/model_executor/models/utils.py:674-719`.
|
||||
|
||||
The default partition is approximately even. A static `VLLM_PP_LAYER_PARTITION` environment variable can override the number of layers per rank:
|
||||
|
||||
- `vllm/distributed/utils.py:127-172`.
|
||||
|
||||
This can represent uneven contiguous ranges, but it remains a launch-time rank partition:
|
||||
|
||||
- One ordered process group.
|
||||
- One fixed world size.
|
||||
- One static set of adjacent ranks.
|
||||
- No per-request tracker route.
|
||||
- No arbitrary start/end assignment independent of rank.
|
||||
- No overlapping shard ranges or Meshnet effective-start semantics.
|
||||
|
||||
### 5.3 Llama stage ownership
|
||||
|
||||
For Llama:
|
||||
|
||||
- The first rank owns token embedding.
|
||||
- A tied output rank may also own embedding.
|
||||
- Only the local repeating layers are instantiated.
|
||||
- Only the last rank owns final RMSNorm.
|
||||
- Only the last rank owns LM head and logits processor.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm/model_executor/models/llama.py:356-395`.
|
||||
- `vllm/model_executor/models/llama.py:400-439`.
|
||||
- `vllm/model_executor/models/llama.py:466-503`.
|
||||
|
||||
This is a good design reference for explicit head, middle, and tail ownership.
|
||||
|
||||
### 5.4 Intermediate tensor bundles
|
||||
|
||||
`IntermediateTensors` is a dictionary of named Torch tensors:
|
||||
|
||||
- `vllm/sequence.py:10-62`.
|
||||
|
||||
For Llama and Qwen3-MoE, the PP boundary includes both:
|
||||
|
||||
```text
|
||||
hidden_states
|
||||
residual
|
||||
```
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm/model_executor/models/llama.py:393-395`.
|
||||
- `vllm/model_executor/models/llama.py:408-433`.
|
||||
- `vllm/model_executor/models/qwen3_moe.py:478-518`.
|
||||
|
||||
This is an important architectural lesson. Depending on where the boundary is placed, an exact pipeline stage may require more than one tensor. Meshnet's backend-neutral activation envelope should support a named tensor bundle or deliberately choose a canonical boundary after residual fusion.
|
||||
|
||||
A universal single `hidden_states` tensor contract may silently break architecture parity.
|
||||
|
||||
### 5.5 Qwen3-MoE stage behavior
|
||||
|
||||
The audited Qwen3-MoE implementation supports:
|
||||
|
||||
- Q/K normalization.
|
||||
- Routed experts.
|
||||
- Shared experts.
|
||||
- Expert/tensor parallel groups.
|
||||
- Stage-local repeating layers.
|
||||
- `hidden_states` plus `residual` PP boundaries.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm/model_executor/models/qwen3_moe.py:130-251`.
|
||||
- `vllm/model_executor/models/qwen3_moe.py:254-354`.
|
||||
- `vllm/model_executor/models/qwen3_moe.py:357-429`.
|
||||
- `vllm/model_executor/models/qwen3_moe.py:432-524`.
|
||||
|
||||
However, in this source snapshot, Qwen3-MoE constructs embedding, final norm, and LM head without the first/last-rank guards used by Llama:
|
||||
|
||||
- Embedding: `vllm/model_executor/models/qwen3_moe.py:463-471`.
|
||||
- Final norm: `vllm/model_executor/models/qwen3_moe.py:477`.
|
||||
- LM head/logits: `vllm/model_executor/models/qwen3_moe.py:566-589`.
|
||||
|
||||
Repeating layer weights remain stage-local, but endpoint parameters appear replicated. This source observation should be validated in a real PP load before using vLLM memory behavior as a reference for Qwen3 30B-A3B.
|
||||
|
||||
### 5.6 Stage communication
|
||||
|
||||
The worker receives intermediate tensor dictionaries from the previous PP rank, executes the local model, and asynchronously sends output tensors to the next rank:
|
||||
|
||||
- `vllm/v1/worker/gpu_worker.py:1045-1088`.
|
||||
|
||||
The PP group:
|
||||
|
||||
- Sends metadata through a CPU process group.
|
||||
- Sends tensors through Torch distributed device or CPU groups.
|
||||
- Defaults destination to the next static rank.
|
||||
- Defaults source to the previous static rank.
|
||||
- Can optimize TP-group slices with all-gather reconstruction.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm/distributed/parallel_state.py:960-1053`.
|
||||
- `vllm/distributed/parallel_state.py:1055-1149`.
|
||||
|
||||
A stateless coordinator variant uses a trusted TCP store and optional device communicator:
|
||||
|
||||
- `vllm/distributed/stateless_coordinator.py:300-356`.
|
||||
|
||||
This is not a versioned application protocol. It has no Meshnet route session, route epoch, model fingerprint, layer range, request work receipt, relay semantics, authentication, or HTTP cache-miss contract.
|
||||
|
||||
### 5.7 Static distributed world
|
||||
|
||||
Official vLLM deployment guidance recommends:
|
||||
|
||||
- Tensor parallelism within a multi-GPU node.
|
||||
- Pipeline parallelism across nodes.
|
||||
- Ray or multiprocessing as the distributed executor.
|
||||
- Identical model paths, packages, and execution environments across all nodes.
|
||||
- High-speed networks such as InfiniBand for cross-node TP.
|
||||
- Private networking because distributed traffic is unencrypted and may permit code execution if exposed.
|
||||
|
||||
These assumptions are appropriate for one trusted managed cluster, not an open heterogeneous volunteer route.
|
||||
|
||||
### 5.8 Cache and scheduling semantics
|
||||
|
||||
Each PP stage owns attention modules only for its local layers, so its physical KV is stage-local. However, request scheduling and block assignment are coordinated by one vLLM engine. Cache identity is internal request/block state, not an externally routable `(route_session, route_epoch, model, range)` contract.
|
||||
|
||||
A pipeline rank cannot independently accept an arbitrary activation from a tracker and safely infer whether it has the required local cache without building a new application protocol around it.
|
||||
|
||||
### 5.9 Failure behavior
|
||||
|
||||
vLLM's Ray layer can restart actors and Ray Serve can provide deployment-level fault tolerance, but a live PP engine depends on its process group and synchronized ranks. A lost stage invalidates active distributed execution and local cache for that stage.
|
||||
|
||||
No source path was found that dynamically replaces one PP rank during an active generation and resumes from another worker's independently reconstructed local cache. This is fundamentally different from Meshnet's route cache-miss and re-prefill recovery model.
|
||||
|
||||
## 6. Other vLLM distributed modes
|
||||
|
||||
### 6.1 Tensor parallelism
|
||||
|
||||
TP splits tensors and performs collectives within most transformer layers. It assumes tightly coordinated ranks and fast communication. It is a poor fit for independent heterogeneous internet nodes but can be valuable inside one trusted node or managed cluster.
|
||||
|
||||
GGUF plugin evidence currently includes a two-GPU TP test. That does not validate PP or volunteer routing.
|
||||
|
||||
### 6.2 Expert parallelism
|
||||
|
||||
Qwen3-MoE and other MoE models can partition experts across EP ranks. Every forward requires routing and collective coordination across the expert group:
|
||||
|
||||
- `vllm/model_executor/models/qwen3_moe.py:130-251`.
|
||||
|
||||
This is useful inside a high-speed managed cluster. It should not be confused with assigning whole transformer-layer ranges to independent volunteer nodes.
|
||||
|
||||
### 6.3 Data parallelism
|
||||
|
||||
vLLM DP replicates complete model weights across independent engine ranks. External load balancing can route HTTP requests to separate complete vLLM deployments, and each replica has an independent KV cache.
|
||||
|
||||
This maps cleanly to a whole-model node proxy or managed serving lane, not distributed single-model execution.
|
||||
|
||||
### 6.4 Disaggregated prefill
|
||||
|
||||
Disaggregated prefill runs separate complete vLLM instances for prefill and decode and transfers KV/results through connector implementations. Official documentation explicitly says it does not improve throughput; it separates TTFT and inter-token-latency tuning.
|
||||
|
||||
It is not pipeline layer sharding.
|
||||
|
||||
The connector interface is still a useful source of lifecycle ideas:
|
||||
|
||||
- Scheduler and worker roles.
|
||||
- Request-finished ownership transfer.
|
||||
- Asynchronous save/load completion.
|
||||
- Per-layer KV save/load hooks.
|
||||
- Block IDs and invalid-block reporting.
|
||||
- Connector metrics and events.
|
||||
- Preemption handling before blocks are overwritten.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/base.py:1-41`.
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/base.py:124-229`.
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/base.py:251-300`.
|
||||
- `vllm/v1/worker/kv_connector_model_runner_mixin.py:33-113`.
|
||||
|
||||
The API is explicitly experimental and subject to change:
|
||||
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/base.py:184-200`.
|
||||
|
||||
Importing it as a dependency would tightly couple Meshnet cache semantics to vLLM. Reusing its lifecycle concepts in the project-owned cache protocol is safer.
|
||||
|
||||
## 7. Hardware portability
|
||||
|
||||
vLLM supports multiple platform families through separate builds and platform implementations, including CUDA, ROCm, CPU, XPU, and TPU, with additional ecosystem integrations.
|
||||
|
||||
This does **not** imply that one model-parallel vLLM world can mix arbitrary platform types. PP/TP communication and model workers share:
|
||||
|
||||
- Torch distributed process groups.
|
||||
- A platform-selected device type and communicator.
|
||||
- Compatible tensor dtypes and model implementations.
|
||||
- Identical package/runtime environments.
|
||||
- Synchronization and collective assumptions.
|
||||
|
||||
The GGUF plugin itself requires CUDA or ROCm and uses a CUDAExtension/HIP build. It does not provide the CPU/Vulkan/Metal GGUF lane needed for broad volunteer hardware.
|
||||
|
||||
Therefore vLLM is multi-platform across separate deployments, not demonstrated as heterogeneous within one distributed model replica.
|
||||
|
||||
## 8. Security and trust boundary
|
||||
|
||||
Official multi-node documentation warns that distributed traffic is unencrypted and can expose code-execution risk. It recommends a private network.
|
||||
|
||||
This alone disqualifies direct exposure of vLLM's Ray/Torch distributed plane to untrusted network participants.
|
||||
|
||||
A safe Meshnet node can still supervise vLLM behind a local proxy. The external network sees only the project-owned authenticated and bounded protocol.
|
||||
|
||||
## 9. Reuse matrix
|
||||
|
||||
| vLLM component | Direct code reuse | Design reuse | Decision |
|
||||
|---|---:|---:|---|
|
||||
| GGUF Python loader | No | Limited | Whole-model vLLM recipe only |
|
||||
| GGUF CUDA/HIP/Triton kernels | No for llama.cpp worker | Performance reference | Keep in vLLM lane |
|
||||
| `make_layers` / `PPMissingLayer` | No | Yes | Reference for local stage construction |
|
||||
| `IntermediateTensors` | No | Strong yes | Add named tensor-bundle concept to activation ABI |
|
||||
| Torch PP communicator | No | Limited | Static trusted cluster only |
|
||||
| PagedAttention/block manager | No | Yes | Cache budgeting and metrics concepts |
|
||||
| KV connector lifecycle | No | Strong yes | Adapt lifecycle semantics, not dependency |
|
||||
| Disaggregated prefill connectors | No | Yes | Reference for async KV transfer/checkpointing |
|
||||
| DP external load balancing | Via HTTP proxy | Yes | Whole-model vLLM node lane |
|
||||
| Ray executor | No for volunteer mesh | Limited | Managed cluster backend only |
|
||||
| Qwen3-MoE model graph | No in C++ worker | Strong yes | Cross-check llama.cpp adapter behavior |
|
||||
| Request/KV telemetry | Possibly via proxy | Yes | Map to tracker metrics where trustworthy |
|
||||
|
||||
## 10. Implications for native GGUF design
|
||||
|
||||
### 10.1 Activation ABI must support tensor bundles
|
||||
|
||||
The first GGUF design assumed one BF16 boundary residual. vLLM shows that architecture implementations may naturally expose more than one state tensor, such as:
|
||||
|
||||
```text
|
||||
hidden_states
|
||||
residual
|
||||
```
|
||||
|
||||
Other architectures may need recurrent state, cross-attention state, or auxiliary routing data.
|
||||
|
||||
The backend-neutral envelope should support:
|
||||
|
||||
```text
|
||||
bundle schema/version
|
||||
named tensors
|
||||
each tensor's shape, dtype, byte order, compression and checksum
|
||||
architecture recipe and boundary point
|
||||
```
|
||||
|
||||
For each certified architecture, either:
|
||||
|
||||
- Define a canonical fused single-residual boundary, or
|
||||
- Define an exact named bundle schema.
|
||||
|
||||
### 10.2 Separate model artifact from execution recipe
|
||||
|
||||
A GGUF artifact fingerprint is insufficient by itself. Capability proof should include:
|
||||
|
||||
- Artifact hash.
|
||||
- Runtime family and version.
|
||||
- Architecture adapter/recipe version.
|
||||
- Boundary schema version.
|
||||
- Backend and kernel lane.
|
||||
- Quantization types actually supported.
|
||||
|
||||
This prevents a vLLM GGUF route from being mixed with a llama.cpp GGUF route merely because both opened the same file.
|
||||
|
||||
### 10.3 Keep whole-model vLLM as a distinct route kind
|
||||
|
||||
A vLLM server can be registered as:
|
||||
|
||||
```text
|
||||
route_kind = whole_model
|
||||
runtime = vllm
|
||||
artifact/model identity
|
||||
quantization = gguf | awq | gptq | fp8 | ...
|
||||
```
|
||||
|
||||
It should not claim activation-shard compatibility unless an explicit compatible worker protocol exists.
|
||||
|
||||
### 10.4 Managed vLLM cluster as one logical node
|
||||
|
||||
A trusted vLLM TP/PP/EP deployment can register as one complete provider. Internal ranks remain invisible to tracker route construction; the cluster owns its own static world, cache, and failure behavior.
|
||||
|
||||
### 10.5 Reuse the KV-transfer envelope and lifecycle concepts
|
||||
|
||||
vLLM's most portable contribution is not PagedAttention code but its control-plane treatment of external KV movement:
|
||||
|
||||
- Protocol versions change when schema, wire format, or memory layout changes.
|
||||
- Transfer IDs are distinct from engine request IDs.
|
||||
- Metadata carries producer/consumer identity, request, block IDs, topology, block size/layout, and backend details.
|
||||
- Compatibility fingerprints include model shape, dtype, KV heads/layers, cache dtype, backend, and allocator mode.
|
||||
- Send, receive, completion, abort, abort acknowledgement, and failed-block states are explicit.
|
||||
- Failed or unavailable external blocks become a cache miss and local-prefill fallback.
|
||||
- Cache ownership can be leased until asynchronous transfer completion before blocks are released.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/nixl/metadata.py:28-43`.
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/nixl/metadata.py:46-76`.
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/nixl/metadata.py:79-139`.
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/nixl/metadata.py:142-189`.
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/base.py:453-527`.
|
||||
- `vllm/distributed/kv_transfer/kv_connector/v1/base.py:547-566`.
|
||||
- `vllm/v1/kv_offload/tiering/p2p/session/protocol.py:167-247`.
|
||||
- `vllm/v1/kv_offload/tiering/p2p/manager.py:180-202`.
|
||||
|
||||
These small metadata/state-machine patterns can be reimplemented in Meshnet's Python control plane with fields such as:
|
||||
|
||||
```text
|
||||
schema_version
|
||||
request_id
|
||||
kv_transfer_id
|
||||
producer_worker_id
|
||||
consumer_worker_id
|
||||
model/cache compatibility fingerprint
|
||||
block or token range
|
||||
dtype and layout
|
||||
route/generation epoch
|
||||
transfer state
|
||||
```
|
||||
|
||||
Do not import vLLM's connector ABCs, block allocator, PagedAttention tensors, or worker classes. They are coupled to vLLM's scheduler, PyTorch/Triton layout, and static runtime.
|
||||
|
||||
## 11. Optional validation spikes
|
||||
|
||||
These spikes are optional and do not replace the llama.cpp layer-worker plan.
|
||||
|
||||
### 11.1 Whole-model vLLM GGUF baseline
|
||||
|
||||
On compatible CUDA or ROCm hardware:
|
||||
|
||||
- Install a pinned vLLM and plugin pair.
|
||||
- Run a small Qwen3 dense GGUF.
|
||||
- Compare load time, memory, prefill, decode, and output parity with llama.cpp.
|
||||
- Verify whether the target Radeon lane builds the plugin successfully.
|
||||
|
||||
This establishes a baseline and potential whole-model recipe.
|
||||
|
||||
### 11.2 Static two-stage vLLM PP experiment
|
||||
|
||||
Using a non-GGUF or small supported GGUF model:
|
||||
|
||||
- Run PP=2 in a trusted local environment.
|
||||
- Record each stage's loaded parameter names and memory.
|
||||
- Capture actual `IntermediateTensors` names, shapes, and dtypes.
|
||||
- Validate local KV allocation by stage.
|
||||
- Kill one stage and document engine/request recovery behavior.
|
||||
|
||||
This validates design assumptions but does not make vLLM suitable for volunteer routing.
|
||||
|
||||
### 11.3 Qwen3 30B-A3B compatibility probe
|
||||
|
||||
Before claiming a vLLM GGUF whole-model lane:
|
||||
|
||||
- Load the exact target GGUF and base tokenizer/config.
|
||||
- Verify fused MoE quantization type support.
|
||||
- Test TP=1 and the intended TP/EP configuration.
|
||||
- Compare deterministic output to llama.cpp.
|
||||
- Measure expert memory, endpoint replication, and ROCm behavior.
|
||||
|
||||
No current test in the audited repositories establishes this target combination.
|
||||
|
||||
## 12. Final conclusion
|
||||
|
||||
vLLM is a strong production serving engine and contains the most mature architecture-aware PyTorch pipeline implementation found in this research. Its stage-local model construction and intermediate tensor bundles are valuable design references.
|
||||
|
||||
It is not the correct primary runtime for neuron-tai's distributed GGUF layer mesh because its GGUF path is experimental and GPU-oriented, while its distributed execution assumes one static, trusted, synchronized Torch world.
|
||||
|
||||
The architecture decision remains:
|
||||
|
||||
```text
|
||||
Primary distributed GGUF layer mesh:
|
||||
small pinned llama.cpp layer-worker fork
|
||||
+ project-owned activation/session protocol
|
||||
+ existing tracker, relay and accounting
|
||||
|
||||
Secondary whole-model serving lane:
|
||||
local proxy to vLLM/Ollama/llama-server/etc.
|
||||
|
||||
Optional managed-cluster lane:
|
||||
one trusted vLLM TP/PP cluster represented as one logical provider
|
||||
```
|
||||
|
||||
The strongest new lesson from vLLM is that the distributed activation ABI should be architecture-aware and support named tensor bundles rather than assuming every model can be split at a single anonymous hidden-state tensor.
|
||||
@@ -99,7 +99,12 @@ def compress_activation(body: bytes, policy: CompressionPolicy) -> CompressionRe
|
||||
return CompressionResult(candidate, "zstd", len(body), len(candidate), time.monotonic() - started, "compressed")
|
||||
|
||||
|
||||
def decompress_activation(body: bytes, encoding: str | None) -> CompressionResult:
|
||||
def decompress_activation(
|
||||
body: bytes,
|
||||
encoding: str | None,
|
||||
*,
|
||||
max_output_bytes: int | None = None,
|
||||
) -> CompressionResult:
|
||||
"""Decode a modern zstd body or preserve a legacy raw body with metrics."""
|
||||
started = time.monotonic()
|
||||
if not encoding:
|
||||
@@ -110,8 +115,23 @@ def decompress_activation(body: bytes, encoding: str | None) -> CompressionResul
|
||||
import zstandard as zstd
|
||||
except ImportError as exc:
|
||||
raise ValueError("zstd support is unavailable") from exc
|
||||
if max_output_bytes is not None and max_output_bytes < 0:
|
||||
raise ValueError("max_output_bytes must be non-negative")
|
||||
try:
|
||||
raw = zstd.ZstdDecompressor().decompress(body)
|
||||
if max_output_bytes is None:
|
||||
raw = zstd.ZstdDecompressor().decompress(body)
|
||||
else:
|
||||
# Cap both decoder window allocation and bytes read. zstandard's
|
||||
# max_window_size unit is KiB.
|
||||
max_window_kib = max(1024, (max_output_bytes + 1023) // 1024)
|
||||
decompressor = zstd.ZstdDecompressor(max_window_size=max_window_kib)
|
||||
# `decompress(max_output_size=...)` may trust a frame's advertised
|
||||
# content size. A bounded stream read enforces the limit regardless
|
||||
# of frame metadata and detects trailing expansion with one byte.
|
||||
with decompressor.stream_reader(body) as reader:
|
||||
raw = reader.read(max_output_bytes + 1)
|
||||
if len(raw) > max_output_bytes:
|
||||
raise ValueError("zstd activation body exceeds its output limit")
|
||||
except zstd.ZstdError as exc:
|
||||
raise ValueError("invalid zstd activation body") from exc
|
||||
return CompressionResult(raw, "zstd", len(body), len(raw), time.monotonic() - started, "decompressed")
|
||||
|
||||
73
packages/node/meshnet_node/native_protocol/__init__.py
Normal file
73
packages/node/meshnet_node/native_protocol/__init__.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""The native Shard protocol: Protobuf over gRPC/HTTP2 (ADR-0020).
|
||||
|
||||
`packages/node/native/proto/shard_runtime.proto` is the contract. This package
|
||||
is how Python speaks it: the generated stubs plus the validation, framing and
|
||||
chunking rules that the stubs cannot express.
|
||||
|
||||
Import the message types from here rather than reaching into `.generated`, so
|
||||
the location of build output stays an implementation detail::
|
||||
|
||||
from meshnet_node.native_protocol import pb, encode_tensor, decode_bundle
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .generated import shard_runtime_pb2 as pb
|
||||
from .codec import (
|
||||
BUNDLE_VERSION,
|
||||
DEFAULT_MAX_CHUNK_BYTES,
|
||||
DEFAULT_MAX_FRAGMENT_BYTES,
|
||||
DEFAULT_MAX_FRAGMENTS_PER_TENSOR,
|
||||
DEFAULT_MAX_INFLIGHT_CHUNKS,
|
||||
DEFAULT_MAX_PREFILL_CHUNK_TOKENS,
|
||||
DEFAULT_MAX_TENSORS_PER_BUNDLE,
|
||||
DEFAULT_MAX_TENSOR_DIMENSION,
|
||||
DEFAULT_MAX_TENSOR_RANK,
|
||||
HIDDEN_STATES,
|
||||
SCHEMA_VERSION,
|
||||
PayloadCorrupt,
|
||||
PrefillChunk,
|
||||
ProtocolError,
|
||||
checksum_of,
|
||||
crc32c,
|
||||
decode_bundle,
|
||||
decode_tensor,
|
||||
default_flow_control,
|
||||
encode_bundle,
|
||||
encode_tensor,
|
||||
expected_bytes,
|
||||
itemsize,
|
||||
negotiate_flow_control,
|
||||
plan_prefill_chunks,
|
||||
validate_session_message_size,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BUNDLE_VERSION",
|
||||
"DEFAULT_MAX_CHUNK_BYTES",
|
||||
"DEFAULT_MAX_FRAGMENT_BYTES",
|
||||
"DEFAULT_MAX_FRAGMENTS_PER_TENSOR",
|
||||
"DEFAULT_MAX_INFLIGHT_CHUNKS",
|
||||
"DEFAULT_MAX_PREFILL_CHUNK_TOKENS",
|
||||
"DEFAULT_MAX_TENSORS_PER_BUNDLE",
|
||||
"DEFAULT_MAX_TENSOR_DIMENSION",
|
||||
"DEFAULT_MAX_TENSOR_RANK",
|
||||
"HIDDEN_STATES",
|
||||
"SCHEMA_VERSION",
|
||||
"PayloadCorrupt",
|
||||
"PrefillChunk",
|
||||
"ProtocolError",
|
||||
"checksum_of",
|
||||
"crc32c",
|
||||
"decode_bundle",
|
||||
"decode_tensor",
|
||||
"default_flow_control",
|
||||
"encode_bundle",
|
||||
"encode_tensor",
|
||||
"expected_bytes",
|
||||
"itemsize",
|
||||
"negotiate_flow_control",
|
||||
"pb",
|
||||
"plan_prefill_chunks",
|
||||
"validate_session_message_size",
|
||||
]
|
||||
527
packages/node/meshnet_node/native_protocol/codec.py
Normal file
527
packages/node/meshnet_node/native_protocol/codec.py
Normal file
@@ -0,0 +1,527 @@
|
||||
"""Encode and decode the native Shard protocol's named-tensor bundles.
|
||||
|
||||
The generated stubs give us message *structure*; they cannot enforce the
|
||||
invariants that keep a distributed forward correct. A bundle whose declared
|
||||
shape disagrees with its byte count, whose fragments leave a hole, or whose
|
||||
checksum does not match is not a slightly-wrong activation — it is silently
|
||||
wrong tokens for the rest of the generation. So decoding is validating: every
|
||||
path into a tensor's bytes goes through :func:`decode_tensor`, which refuses a
|
||||
payload it cannot fully account for.
|
||||
|
||||
Compression is a transport optimisation and is decided by the same policy layer
|
||||
the existing HTTP seam already uses (``activation_compression``), so a node's
|
||||
tuned thresholds apply to both transports.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import struct
|
||||
from typing import Iterable, Sequence
|
||||
|
||||
from ..activation_compression import (
|
||||
CompressionPolicy,
|
||||
compress_activation,
|
||||
decompress_activation,
|
||||
)
|
||||
from .generated import shard_runtime_pb2 as pb
|
||||
|
||||
# The schema generation this build speaks. A peer offering something else is
|
||||
# rejected at the handshake rather than being half-understood.
|
||||
SCHEMA_VERSION = pb.SCHEMA_VERSION_1
|
||||
|
||||
# Generation of the tensor-bundle layout, versioned independently of the
|
||||
# protocol so a boundary payload can evolve without a protocol bump.
|
||||
BUNDLE_VERSION = 1
|
||||
|
||||
# Token-aligned prefill chunk bound. 128 tokens is the size ADR-0008 already
|
||||
# uses on the HTTP seam; keeping it identical means seam bytes stay comparable
|
||||
# across transports.
|
||||
DEFAULT_MAX_PREFILL_CHUNK_TOKENS = 128
|
||||
|
||||
# gRPC's default maximum receive size. Fragmenting below it keeps us inside the
|
||||
# default limits of any conformant peer instead of requiring every client to
|
||||
# raise its window.
|
||||
DEFAULT_MAX_CHUNK_BYTES = 4 * 1024 * 1024
|
||||
|
||||
# Leave room for envelope and framing overhead inside one chunk message.
|
||||
DEFAULT_MAX_FRAGMENT_BYTES = 1024 * 1024
|
||||
|
||||
DEFAULT_MAX_INFLIGHT_CHUNKS = 8
|
||||
DEFAULT_MAX_FRAGMENTS_PER_TENSOR = 64
|
||||
DEFAULT_MAX_TENSORS_PER_BUNDLE = 64
|
||||
DEFAULT_MAX_TENSOR_RANK = 8
|
||||
DEFAULT_MAX_TENSOR_DIMENSION = (1 << 31) - 1
|
||||
|
||||
# Canonical boundary tensor name for a dense transformer hidden state.
|
||||
HIDDEN_STATES = "hidden_states"
|
||||
|
||||
_DTYPE_ITEMSIZE: dict[int, int] = {
|
||||
pb.DTYPE_BFLOAT16: 2,
|
||||
pb.DTYPE_FLOAT16: 2,
|
||||
pb.DTYPE_FLOAT32: 4,
|
||||
pb.DTYPE_INT32: 4,
|
||||
pb.DTYPE_INT64: 8,
|
||||
pb.DTYPE_UINT8: 1,
|
||||
pb.DTYPE_INT8: 1,
|
||||
pb.DTYPE_BOOL: 1,
|
||||
}
|
||||
|
||||
|
||||
class ProtocolError(Exception):
|
||||
"""A peer sent something this build cannot safely interpret."""
|
||||
|
||||
|
||||
class PayloadCorrupt(ProtocolError):
|
||||
"""A tensor payload failed validation: size, coverage, or checksum."""
|
||||
|
||||
|
||||
def itemsize(dtype: int) -> int:
|
||||
try:
|
||||
return _DTYPE_ITEMSIZE[dtype]
|
||||
except KeyError:
|
||||
raise ProtocolError(f"unsupported dtype {dtype}") from None
|
||||
|
||||
|
||||
def expected_bytes(
|
||||
shape: Sequence[int],
|
||||
dtype: int,
|
||||
*,
|
||||
max_rank: int = DEFAULT_MAX_TENSOR_RANK,
|
||||
max_dimension: int = DEFAULT_MAX_TENSOR_DIMENSION,
|
||||
max_bytes: int | None = None,
|
||||
) -> int:
|
||||
"""Byte count a tensor of `shape` and `dtype` must occupy."""
|
||||
if len(shape) > max_rank:
|
||||
raise ProtocolError(f"tensor rank {len(shape)} exceeds limit {max_rank}")
|
||||
if any(dim < 0 or dim > max_dimension for dim in shape):
|
||||
raise ProtocolError(
|
||||
f"dimension outside 0..{max_dimension} in shape {list(shape)}"
|
||||
)
|
||||
size = itemsize(dtype)
|
||||
count = 1
|
||||
for dim in shape:
|
||||
count *= dim
|
||||
if max_bytes is not None and count * size > max_bytes:
|
||||
raise ProtocolError(f"tensor shape {list(shape)} exceeds byte limit {max_bytes}")
|
||||
return count * size
|
||||
|
||||
|
||||
# --- CRC32C ----------------------------------------------------------------
|
||||
#
|
||||
# CRC32C (Castagnoli), not zlib's CRC32: it is the checksum gRPC, and the
|
||||
# storage systems these payloads pass through, already use, and hardware
|
||||
# implements it. `google_crc32c` is used when present; the table fallback keeps
|
||||
# the default test suite dependency-free.
|
||||
|
||||
_CRC32C_POLY = 0x82F63B78
|
||||
_CRC32C_TABLE: list[int] = []
|
||||
for _i in range(256):
|
||||
_c = _i
|
||||
for _ in range(8):
|
||||
_c = (_c >> 1) ^ (_CRC32C_POLY if _c & 1 else 0)
|
||||
_CRC32C_TABLE.append(_c)
|
||||
|
||||
try: # pragma: no cover - depends on an optional native package
|
||||
from google_crc32c import value as _fast_crc32c
|
||||
except ImportError: # pragma: no cover
|
||||
_fast_crc32c = None
|
||||
|
||||
|
||||
def crc32c(data: bytes) -> int:
|
||||
if _fast_crc32c is not None: # pragma: no cover - optional fast path
|
||||
return _fast_crc32c(data)
|
||||
crc = 0xFFFFFFFF
|
||||
for byte in data:
|
||||
crc = (crc >> 8) ^ _CRC32C_TABLE[(crc ^ byte) & 0xFF]
|
||||
return crc ^ 0xFFFFFFFF
|
||||
|
||||
|
||||
def checksum_of(data: bytes) -> pb.Checksum:
|
||||
return pb.Checksum(
|
||||
algorithm=pb.CHECKSUM_ALGORITHM_CRC32C,
|
||||
value=struct.pack(">I", crc32c(data)),
|
||||
)
|
||||
|
||||
|
||||
# --- Tensors ---------------------------------------------------------------
|
||||
|
||||
|
||||
def encode_tensor(
|
||||
name: str,
|
||||
data: bytes,
|
||||
shape: Sequence[int],
|
||||
dtype: int = pb.DTYPE_BFLOAT16,
|
||||
*,
|
||||
policy: CompressionPolicy | None = None,
|
||||
max_chunk_bytes: int = DEFAULT_MAX_CHUNK_BYTES,
|
||||
max_fragment_bytes: int = DEFAULT_MAX_FRAGMENT_BYTES,
|
||||
max_fragments: int = DEFAULT_MAX_FRAGMENTS_PER_TENSOR,
|
||||
) -> pb.NamedTensor:
|
||||
"""Build a NamedTensor, compressing and fragmenting as needed.
|
||||
|
||||
`data` is the uncompressed little-endian payload. The checksum is taken over
|
||||
it *before* compression so it stays valid whichever framing a hop chooses.
|
||||
"""
|
||||
if max_chunk_bytes <= 0 or max_fragment_bytes <= 0 or max_fragments <= 0:
|
||||
raise ProtocolError("tensor byte/count bounds must be positive")
|
||||
declared = expected_bytes(shape, dtype, max_bytes=max_chunk_bytes)
|
||||
if len(data) != declared:
|
||||
raise ProtocolError(
|
||||
f"tensor {name!r} declares shape {list(shape)} ({declared} bytes) "
|
||||
f"but carries {len(data)} bytes"
|
||||
)
|
||||
|
||||
body = data
|
||||
compression = pb.COMPRESSION_NONE
|
||||
if policy is not None:
|
||||
result = compress_activation(data, policy)
|
||||
if result.compressed:
|
||||
body = result.body
|
||||
compression = pb.COMPRESSION_ZSTD
|
||||
|
||||
tensor = pb.NamedTensor(
|
||||
name=name,
|
||||
shape=list(shape),
|
||||
dtype=dtype,
|
||||
byte_order=pb.BYTE_ORDER_LITTLE_ENDIAN,
|
||||
total_bytes=len(data),
|
||||
compression=compression,
|
||||
checksum=checksum_of(data),
|
||||
)
|
||||
|
||||
# Fragment the wire body (compressed if we compressed). Offsets walk the
|
||||
# wire body so a receiver can verify coverage without assuming arrival
|
||||
# order; a zstd frame is not decodable per fragment, so reassembly comes
|
||||
# first and decompression happens once, in decode_tensor.
|
||||
slices = [body[i : i + max_fragment_bytes] for i in range(0, len(body), max_fragment_bytes)]
|
||||
if not slices:
|
||||
# A zero-element tensor is legal (e.g. an empty mask) and still needs a
|
||||
# fragment, so coverage checks have something to verify.
|
||||
slices = [b""]
|
||||
if len(slices) > max_fragments:
|
||||
raise ProtocolError(
|
||||
f"tensor {name!r} needs {len(slices)} fragments, exceeding limit {max_fragments}"
|
||||
)
|
||||
|
||||
offset = 0
|
||||
for index, piece in enumerate(slices):
|
||||
tensor.fragments.append(
|
||||
pb.TensorFragment(
|
||||
fragment_index=index,
|
||||
fragment_count=len(slices),
|
||||
byte_offset=offset,
|
||||
payload=piece,
|
||||
)
|
||||
)
|
||||
offset += len(piece)
|
||||
return tensor
|
||||
|
||||
|
||||
def decode_tensor(
|
||||
tensor: pb.NamedTensor,
|
||||
*,
|
||||
max_chunk_bytes: int = DEFAULT_MAX_CHUNK_BYTES,
|
||||
max_fragment_bytes: int = DEFAULT_MAX_FRAGMENT_BYTES,
|
||||
max_fragments: int = DEFAULT_MAX_FRAGMENTS_PER_TENSOR,
|
||||
) -> bytes:
|
||||
"""Reassemble, decompress and validate a NamedTensor's payload.
|
||||
|
||||
Raises PayloadCorrupt rather than returning a payload it cannot fully
|
||||
account for: a hole in the fragments or a bad checksum means the activation
|
||||
is not what the sender computed, and continuing would corrupt the route.
|
||||
"""
|
||||
if max_chunk_bytes <= 0 or max_fragment_bytes <= 0 or max_fragments <= 0:
|
||||
raise ProtocolError("negotiated byte/count bounds must be positive")
|
||||
if tensor.total_bytes > max_chunk_bytes:
|
||||
raise ProtocolError(
|
||||
f"tensor {tensor.name!r} declares {tensor.total_bytes} bytes, exceeding "
|
||||
f"the {max_chunk_bytes}-byte negotiated chunk bound"
|
||||
)
|
||||
if tensor.byte_order == pb.BYTE_ORDER_BIG_ENDIAN:
|
||||
raise ProtocolError(f"tensor {tensor.name!r} is big-endian; wire order is little-endian")
|
||||
if tensor.byte_order != pb.BYTE_ORDER_LITTLE_ENDIAN:
|
||||
raise ProtocolError(f"tensor {tensor.name!r} declares no byte order")
|
||||
declared = expected_bytes(
|
||||
tensor.shape, tensor.dtype, max_bytes=max_chunk_bytes
|
||||
)
|
||||
if declared != tensor.total_bytes:
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} shape {list(tensor.shape)} implies {declared} bytes "
|
||||
f"but declares {tensor.total_bytes}"
|
||||
)
|
||||
|
||||
if not tensor.fragments:
|
||||
raise PayloadCorrupt(f"tensor {tensor.name!r} carries no fragments")
|
||||
if len(tensor.fragments) > max_fragments:
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} carries {len(tensor.fragments)} fragments, "
|
||||
f"exceeding limit {max_fragments}"
|
||||
)
|
||||
if any(len(fragment.payload) > max_fragment_bytes for fragment in tensor.fragments):
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} carries a fragment larger than "
|
||||
f"{max_fragment_bytes} bytes"
|
||||
)
|
||||
wire_bytes = sum(len(fragment.payload) for fragment in tensor.fragments)
|
||||
if wire_bytes > max_chunk_bytes:
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} wire body exceeds the "
|
||||
f"{max_chunk_bytes}-byte negotiated chunk bound"
|
||||
)
|
||||
|
||||
fragments = sorted(tensor.fragments, key=lambda f: f.byte_offset)
|
||||
count = fragments[0].fragment_count
|
||||
if any(f.fragment_count != count for f in fragments):
|
||||
raise PayloadCorrupt(f"tensor {tensor.name!r} has inconsistent fragment_count")
|
||||
if len(fragments) != count:
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} expects {count} fragments but carries {len(fragments)}"
|
||||
)
|
||||
if {f.fragment_index for f in fragments} != set(range(count)):
|
||||
raise PayloadCorrupt(f"tensor {tensor.name!r} has duplicate or missing fragment indices")
|
||||
|
||||
# Contiguity: offsets must tile the body exactly, with no hole and no overlap.
|
||||
body = bytearray()
|
||||
for fragment in fragments:
|
||||
if fragment.byte_offset != len(body):
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} fragment {fragment.fragment_index} starts at "
|
||||
f"{fragment.byte_offset}, expected {len(body)}"
|
||||
)
|
||||
body.extend(fragment.payload)
|
||||
|
||||
if tensor.compression == pb.COMPRESSION_ZSTD:
|
||||
try:
|
||||
data = decompress_activation(
|
||||
bytes(body), "zstd", max_output_bytes=tensor.total_bytes
|
||||
).body
|
||||
except ValueError as exc:
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} has invalid bounded zstd payload"
|
||||
) from exc
|
||||
elif tensor.compression == pb.COMPRESSION_NONE:
|
||||
data = bytes(body)
|
||||
else:
|
||||
raise ProtocolError(
|
||||
f"tensor {tensor.name!r} uses unspecified or unsupported compression"
|
||||
)
|
||||
|
||||
if len(data) != tensor.total_bytes:
|
||||
raise PayloadCorrupt(
|
||||
f"tensor {tensor.name!r} declares {tensor.total_bytes} bytes but "
|
||||
f"reassembled {len(data)}"
|
||||
)
|
||||
|
||||
algorithm = tensor.checksum.algorithm
|
||||
if algorithm == pb.CHECKSUM_ALGORITHM_CRC32C:
|
||||
if tensor.checksum.value != struct.pack(">I", crc32c(data)):
|
||||
raise PayloadCorrupt(f"tensor {tensor.name!r} failed its CRC32C check")
|
||||
elif algorithm != pb.CHECKSUM_ALGORITHM_NONE:
|
||||
raise ProtocolError(
|
||||
f"tensor {tensor.name!r} uses unspecified or unsupported checksum algorithm"
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def encode_bundle(
|
||||
tensors: Iterable[pb.NamedTensor],
|
||||
*,
|
||||
max_chunk_bytes: int = DEFAULT_MAX_CHUNK_BYTES,
|
||||
max_tensors: int = DEFAULT_MAX_TENSORS_PER_BUNDLE,
|
||||
) -> pb.TensorBundle:
|
||||
if max_chunk_bytes <= 0 or max_tensors <= 0:
|
||||
raise ProtocolError("bundle byte/count bounds must be positive")
|
||||
tensor_list = list(tensors)
|
||||
if len(tensor_list) > max_tensors:
|
||||
raise ProtocolError(
|
||||
f"bundle carries {len(tensor_list)} tensors, exceeding limit {max_tensors}"
|
||||
)
|
||||
bundle = pb.TensorBundle(bundle_version=BUNDLE_VERSION, tensors=tensor_list)
|
||||
if bundle.ByteSize() > max_chunk_bytes:
|
||||
raise ProtocolError(
|
||||
f"serialized tensor bundle exceeds the {max_chunk_bytes}-byte "
|
||||
"negotiated chunk bound"
|
||||
)
|
||||
return bundle
|
||||
|
||||
|
||||
def decode_bundle(
|
||||
bundle: pb.TensorBundle,
|
||||
*,
|
||||
max_chunk_bytes: int = DEFAULT_MAX_CHUNK_BYTES,
|
||||
max_fragment_bytes: int = DEFAULT_MAX_FRAGMENT_BYTES,
|
||||
max_fragments: int = DEFAULT_MAX_FRAGMENTS_PER_TENSOR,
|
||||
max_tensors: int = DEFAULT_MAX_TENSORS_PER_BUNDLE,
|
||||
) -> dict[str, bytes]:
|
||||
"""Validate every tensor in a bundle and return name -> payload."""
|
||||
if bundle.bundle_version != BUNDLE_VERSION:
|
||||
raise ProtocolError(
|
||||
f"bundle version {bundle.bundle_version} is not supported by this build "
|
||||
f"({BUNDLE_VERSION})"
|
||||
)
|
||||
if (
|
||||
max_chunk_bytes <= 0
|
||||
or max_fragment_bytes <= 0
|
||||
or max_fragments <= 0
|
||||
or max_tensors <= 0
|
||||
):
|
||||
raise ProtocolError("negotiated byte/count bounds must be positive")
|
||||
if len(bundle.tensors) > max_tensors:
|
||||
raise ProtocolError(
|
||||
f"bundle carries {len(bundle.tensors)} tensors, exceeding limit {max_tensors}"
|
||||
)
|
||||
if bundle.ByteSize() > max_chunk_bytes:
|
||||
raise ProtocolError(
|
||||
f"serialized tensor bundle exceeds the {max_chunk_bytes}-byte "
|
||||
"negotiated chunk bound"
|
||||
)
|
||||
payloads: dict[str, bytes] = {}
|
||||
for tensor in bundle.tensors:
|
||||
if not tensor.name:
|
||||
raise ProtocolError("bundle carries an unnamed tensor")
|
||||
if tensor.name in payloads:
|
||||
raise ProtocolError(f"bundle carries duplicate tensor {tensor.name!r}")
|
||||
payloads[tensor.name] = decode_tensor(
|
||||
tensor,
|
||||
max_chunk_bytes=max_chunk_bytes,
|
||||
max_fragment_bytes=max_fragment_bytes,
|
||||
max_fragments=max_fragments,
|
||||
)
|
||||
return payloads
|
||||
|
||||
|
||||
def validate_session_message_size(
|
||||
message: pb.SessionRequest | pb.SessionResponse,
|
||||
*,
|
||||
max_chunk_bytes: int = DEFAULT_MAX_CHUNK_BYTES,
|
||||
) -> int:
|
||||
"""Reject an oversized complete stream frame, including protobuf overhead.
|
||||
|
||||
Bundle validation alone is insufficient because the envelope and oneof
|
||||
framing are part of the same gRPC message. Senders call this immediately
|
||||
before writing; receivers configure gRPC's receive limit to the same value
|
||||
and call it again before semantic decoding.
|
||||
"""
|
||||
if max_chunk_bytes <= 0:
|
||||
raise ProtocolError("max_chunk_bytes must be positive")
|
||||
if not isinstance(message, (pb.SessionRequest, pb.SessionResponse)):
|
||||
raise ProtocolError("size validation requires a session request or response")
|
||||
size = message.ByteSize()
|
||||
if size > max_chunk_bytes:
|
||||
raise ProtocolError(
|
||||
f"serialized session message is {size} bytes, exceeding the "
|
||||
f"{max_chunk_bytes}-byte negotiated chunk bound"
|
||||
)
|
||||
return size
|
||||
|
||||
|
||||
# --- Bounded prefill chunking ----------------------------------------------
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PrefillChunk:
|
||||
"""One token-aligned slice of a prefill."""
|
||||
|
||||
chunk_index: int
|
||||
chunk_count: int
|
||||
first_position: int
|
||||
token_count: int
|
||||
|
||||
@property
|
||||
def final_chunk(self) -> bool:
|
||||
return self.chunk_index == self.chunk_count - 1
|
||||
|
||||
def chunk_info(self) -> pb.ChunkInfo:
|
||||
return pb.ChunkInfo(
|
||||
chunk_index=self.chunk_index,
|
||||
chunk_count=self.chunk_count,
|
||||
final_chunk=self.final_chunk,
|
||||
)
|
||||
|
||||
def position(self) -> pb.PositionSpan:
|
||||
return pb.PositionSpan(
|
||||
first_position=self.first_position, token_count=self.token_count
|
||||
)
|
||||
|
||||
|
||||
def plan_prefill_chunks(
|
||||
total_tokens: int,
|
||||
*,
|
||||
first_position: int = 0,
|
||||
max_tokens: int = DEFAULT_MAX_PREFILL_CHUNK_TOKENS,
|
||||
) -> list[PrefillChunk]:
|
||||
"""Split a prefill into bounded, token-aligned chunks.
|
||||
|
||||
Splits fall on token boundaries only (ADR-0008): a fragment of a token's
|
||||
hidden state is not a thing a receiver can execute.
|
||||
"""
|
||||
if total_tokens <= 0:
|
||||
raise ProtocolError("a prefill must carry at least one token")
|
||||
if max_tokens <= 0:
|
||||
raise ProtocolError("max_tokens must be positive")
|
||||
|
||||
count = (total_tokens + max_tokens - 1) // max_tokens
|
||||
chunks = []
|
||||
for index in range(count):
|
||||
offset = index * max_tokens
|
||||
chunks.append(
|
||||
PrefillChunk(
|
||||
chunk_index=index,
|
||||
chunk_count=count,
|
||||
first_position=first_position + offset,
|
||||
token_count=min(max_tokens, total_tokens - offset),
|
||||
)
|
||||
)
|
||||
return chunks
|
||||
|
||||
|
||||
def default_flow_control() -> pb.FlowControl:
|
||||
return pb.FlowControl(
|
||||
credits_granted=DEFAULT_MAX_INFLIGHT_CHUNKS,
|
||||
max_inflight_chunks=DEFAULT_MAX_INFLIGHT_CHUNKS,
|
||||
max_chunk_bytes=DEFAULT_MAX_CHUNK_BYTES,
|
||||
max_prefill_chunk_tokens=DEFAULT_MAX_PREFILL_CHUNK_TOKENS,
|
||||
)
|
||||
|
||||
|
||||
def negotiate_flow_control(
|
||||
proposed: pb.FlowControl, limits: pb.FlowControl
|
||||
) -> pb.FlowControl:
|
||||
"""Settle a stream's limits: the strictest bound of either peer wins.
|
||||
|
||||
Taking the minimum means neither peer can raise the other's ceiling, so a
|
||||
misconfigured — or hostile — sender cannot talk a worker into unbounded
|
||||
queues by proposing a large window.
|
||||
"""
|
||||
|
||||
def _min(a: int, b: int, fallback: int) -> int:
|
||||
candidates = [v for v in (a, b) if v > 0]
|
||||
return min(candidates) if candidates else fallback
|
||||
|
||||
max_inflight_chunks = _min(
|
||||
proposed.max_inflight_chunks,
|
||||
limits.max_inflight_chunks,
|
||||
DEFAULT_MAX_INFLIGHT_CHUNKS,
|
||||
)
|
||||
credits_granted = min(
|
||||
_min(
|
||||
proposed.credits_granted,
|
||||
limits.credits_granted,
|
||||
DEFAULT_MAX_INFLIGHT_CHUNKS,
|
||||
),
|
||||
max_inflight_chunks,
|
||||
)
|
||||
return pb.FlowControl(
|
||||
credits_granted=credits_granted,
|
||||
max_inflight_chunks=max_inflight_chunks,
|
||||
max_chunk_bytes=_min(
|
||||
proposed.max_chunk_bytes, limits.max_chunk_bytes, DEFAULT_MAX_CHUNK_BYTES
|
||||
),
|
||||
max_prefill_chunk_tokens=_min(
|
||||
proposed.max_prefill_chunk_tokens,
|
||||
limits.max_prefill_chunk_tokens,
|
||||
DEFAULT_MAX_PREFILL_CHUNK_TOKENS,
|
||||
),
|
||||
)
|
||||
141
packages/node/meshnet_node/native_protocol/conformance.py
Normal file
141
packages/node/meshnet_node/native_protocol/conformance.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""Canonical conformance vectors for the native Shard protocol.
|
||||
|
||||
Two independently-written codecs that each round-trip their own output prove
|
||||
nothing about each other. These vectors are the shared reference: Python builds
|
||||
the canonical message, the bytes are committed under
|
||||
`packages/node/native/testdata/`, and the C++ test parses those exact bytes and
|
||||
asserts the same field values. A change that alters the wire meaning of a field
|
||||
breaks the vector in both languages instead of drifting silently in one.
|
||||
|
||||
The vector deliberately exercises every field group the protocol promises to
|
||||
carry — identity, epoch, fingerprint, range, phase, position, idempotency,
|
||||
cache expectation, deadline, chunking, compression, checksum and a multi-
|
||||
fragment named tensor — so it doubles as an executable inventory of the
|
||||
contract.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
|
||||
from . import codec
|
||||
from .generated import shard_runtime_pb2 as pb
|
||||
|
||||
# Committed vectors live beside the schema, under `packages/node/native/`.
|
||||
# parents[2] is `packages/node`: native_protocol -> meshnet_node -> node.
|
||||
TESTDATA_DIR = pathlib.Path(__file__).resolve().parents[2] / "native/testdata"
|
||||
|
||||
GOLDEN_SESSION_REQUEST = "session_request_golden.binpb"
|
||||
GOLDEN_CAPABILITY_REPORT = "capability_report_golden.binpb"
|
||||
|
||||
# Written by the C++ conformance test into its build tree; the Python test picks
|
||||
# it up when present to prove the two languages agree byte-for-byte.
|
||||
CPP_ROUNDTRIP = "cpp_roundtrip.binpb"
|
||||
|
||||
# Fixed, non-default values. Every one is chosen to be distinguishable from a
|
||||
# proto3 default so an unset field can never masquerade as a correct one.
|
||||
WORK_ID = "work-7f3a"
|
||||
ROUTE_SESSION_ID = "rs-2b91"
|
||||
ROUTE_EPOCH = 7
|
||||
IDEMPOTENCY_STEP = 42
|
||||
FIRST_POSITION = 256
|
||||
TOKEN_COUNT = 128
|
||||
EXPECTED_PAST_LEN = 256
|
||||
DEADLINE_UNIX_NANOS = 1_800_000_000_000_000_000
|
||||
MODEL_ARTIFACT_DIGEST = "sha256:1f0c9d2e"
|
||||
RUNTIME_RECIPE_DIGEST = "sha256:ab77e410"
|
||||
RECIPE_ID = "llama-gguf-q4km-rocm"
|
||||
RECIPE_VERSION = "3"
|
||||
CATALOGUE_VERSION = "2026.07.1"
|
||||
START_LAYER = 12
|
||||
END_LAYER = 24
|
||||
EFFECTIVE_START_LAYER = 16
|
||||
HIDDEN_SIZE = 8
|
||||
|
||||
# A payload big enough to force more than one fragment at the bound below, so
|
||||
# the vector actually exercises reassembly rather than the one-fragment path.
|
||||
FRAGMENT_BYTES = 64
|
||||
TENSOR_SHAPE = [1, TOKEN_COUNT, HIDDEN_SIZE]
|
||||
|
||||
|
||||
def canonical_payload() -> bytes:
|
||||
"""Deterministic bfloat16-sized payload for the canonical tensor."""
|
||||
total = codec.expected_bytes(TENSOR_SHAPE, pb.DTYPE_BFLOAT16)
|
||||
return bytes((i * 7 + 11) % 256 for i in range(total))
|
||||
|
||||
|
||||
def canonical_session_request() -> pb.SessionRequest:
|
||||
"""The canonical prefill chunk carried on a session stream."""
|
||||
tensor = codec.encode_tensor(
|
||||
codec.HIDDEN_STATES,
|
||||
canonical_payload(),
|
||||
TENSOR_SHAPE,
|
||||
pb.DTYPE_BFLOAT16,
|
||||
max_fragment_bytes=FRAGMENT_BYTES,
|
||||
)
|
||||
envelope = pb.Envelope(
|
||||
schema_version=pb.SCHEMA_VERSION_1,
|
||||
work_id=WORK_ID,
|
||||
route_session_id=ROUTE_SESSION_ID,
|
||||
route_epoch=ROUTE_EPOCH,
|
||||
fingerprint=pb.Fingerprint(
|
||||
model_artifact_digest=MODEL_ARTIFACT_DIGEST,
|
||||
runtime_recipe_digest=RUNTIME_RECIPE_DIGEST,
|
||||
recipe_id=RECIPE_ID,
|
||||
recipe_version=RECIPE_VERSION,
|
||||
catalogue_version=CATALOGUE_VERSION,
|
||||
),
|
||||
shard_range=pb.ShardRange(
|
||||
start_layer=START_LAYER,
|
||||
end_layer=END_LAYER,
|
||||
effective_start_layer=EFFECTIVE_START_LAYER,
|
||||
),
|
||||
phase=pb.PHASE_PREFILL,
|
||||
position=pb.PositionSpan(
|
||||
first_position=FIRST_POSITION, token_count=TOKEN_COUNT
|
||||
),
|
||||
idempotency_step=IDEMPOTENCY_STEP,
|
||||
cache_expectation=pb.CacheExpectation(
|
||||
mode=pb.CACHE_MODE_PREFILL, expected_past_len=EXPECTED_PAST_LEN
|
||||
),
|
||||
deadline_unix_nanos=DEADLINE_UNIX_NANOS,
|
||||
chunk=pb.ChunkInfo(chunk_index=1, chunk_count=3, final_chunk=False),
|
||||
)
|
||||
return pb.SessionRequest(
|
||||
chunk=pb.ActivationChunk(
|
||||
envelope=envelope, bundle=codec.encode_bundle([tensor])
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def canonical_capability_report() -> pb.CapabilityReport:
|
||||
"""The canonical capability report a worker answers admission with."""
|
||||
return pb.CapabilityReport(
|
||||
schema_version=pb.SCHEMA_VERSION_1,
|
||||
fingerprint=pb.Fingerprint(
|
||||
model_artifact_digest=MODEL_ARTIFACT_DIGEST,
|
||||
runtime_recipe_digest=RUNTIME_RECIPE_DIGEST,
|
||||
recipe_id=RECIPE_ID,
|
||||
recipe_version=RECIPE_VERSION,
|
||||
catalogue_version=CATALOGUE_VERSION,
|
||||
),
|
||||
shard_range=pb.ShardRange(
|
||||
start_layer=START_LAYER,
|
||||
end_layer=END_LAYER,
|
||||
effective_start_layer=EFFECTIVE_START_LAYER,
|
||||
),
|
||||
backend="rocm",
|
||||
device="gfx1151",
|
||||
validated=True,
|
||||
max_concurrent_sessions=4,
|
||||
max_context_tokens=8192,
|
||||
flow_control=codec.default_flow_control(),
|
||||
accepted_compression=[pb.COMPRESSION_NONE, pb.COMPRESSION_ZSTD],
|
||||
supported_schema_versions=[pb.SCHEMA_VERSION_1],
|
||||
validated_at_unix_nanos=DEADLINE_UNIX_NANOS,
|
||||
)
|
||||
|
||||
|
||||
def serialize(message) -> bytes:
|
||||
"""Serialize deterministically, so committed golden bytes are stable."""
|
||||
return message.SerializeToString(deterministic=True)
|
||||
@@ -0,0 +1,2 @@
|
||||
# Generated by scripts/generate_native_protocol.py. Do not edit.
|
||||
"""Generated protobuf/gRPC stubs for the native Shard protocol."""
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,525 @@
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class SchemaVersion(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
SCHEMA_VERSION_UNSPECIFIED: _ClassVar[SchemaVersion]
|
||||
SCHEMA_VERSION_1: _ClassVar[SchemaVersion]
|
||||
|
||||
class DType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
DTYPE_UNSPECIFIED: _ClassVar[DType]
|
||||
DTYPE_BFLOAT16: _ClassVar[DType]
|
||||
DTYPE_FLOAT16: _ClassVar[DType]
|
||||
DTYPE_FLOAT32: _ClassVar[DType]
|
||||
DTYPE_INT32: _ClassVar[DType]
|
||||
DTYPE_INT64: _ClassVar[DType]
|
||||
DTYPE_UINT8: _ClassVar[DType]
|
||||
DTYPE_INT8: _ClassVar[DType]
|
||||
DTYPE_BOOL: _ClassVar[DType]
|
||||
|
||||
class ByteOrder(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
BYTE_ORDER_UNSPECIFIED: _ClassVar[ByteOrder]
|
||||
BYTE_ORDER_LITTLE_ENDIAN: _ClassVar[ByteOrder]
|
||||
BYTE_ORDER_BIG_ENDIAN: _ClassVar[ByteOrder]
|
||||
|
||||
class Compression(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
COMPRESSION_UNSPECIFIED: _ClassVar[Compression]
|
||||
COMPRESSION_NONE: _ClassVar[Compression]
|
||||
COMPRESSION_ZSTD: _ClassVar[Compression]
|
||||
|
||||
class ChecksumAlgorithm(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
CHECKSUM_ALGORITHM_UNSPECIFIED: _ClassVar[ChecksumAlgorithm]
|
||||
CHECKSUM_ALGORITHM_NONE: _ClassVar[ChecksumAlgorithm]
|
||||
CHECKSUM_ALGORITHM_CRC32C: _ClassVar[ChecksumAlgorithm]
|
||||
|
||||
class Phase(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
PHASE_UNSPECIFIED: _ClassVar[Phase]
|
||||
PHASE_PREFILL: _ClassVar[Phase]
|
||||
PHASE_DECODE: _ClassVar[Phase]
|
||||
PHASE_RELEASE: _ClassVar[Phase]
|
||||
PHASE_CANCEL: _ClassVar[Phase]
|
||||
|
||||
class CacheMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
CACHE_MODE_UNSPECIFIED: _ClassVar[CacheMode]
|
||||
CACHE_MODE_STATELESS: _ClassVar[CacheMode]
|
||||
CACHE_MODE_PREFILL: _ClassVar[CacheMode]
|
||||
CACHE_MODE_DECODE: _ClassVar[CacheMode]
|
||||
|
||||
class ErrorCode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
ERROR_CODE_UNSPECIFIED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_SCHEMA_UNSUPPORTED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_FINGERPRINT_MISMATCH: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_EPOCH_STALE: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_SHARD_RANGE_MISMATCH: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_CACHE_MISS: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_RESOURCE_EXHAUSTED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_PAYLOAD_CORRUPT: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_CANCELLED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_DEADLINE_EXCEEDED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_FLOW_CONTROL_VIOLATION: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_INTERNAL: _ClassVar[ErrorCode]
|
||||
|
||||
class ServingState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
SERVING_STATE_UNSPECIFIED: _ClassVar[ServingState]
|
||||
SERVING_STATE_SERVING: _ClassVar[ServingState]
|
||||
SERVING_STATE_DRAINING: _ClassVar[ServingState]
|
||||
SERVING_STATE_NOT_SERVING: _ClassVar[ServingState]
|
||||
SCHEMA_VERSION_UNSPECIFIED: SchemaVersion
|
||||
SCHEMA_VERSION_1: SchemaVersion
|
||||
DTYPE_UNSPECIFIED: DType
|
||||
DTYPE_BFLOAT16: DType
|
||||
DTYPE_FLOAT16: DType
|
||||
DTYPE_FLOAT32: DType
|
||||
DTYPE_INT32: DType
|
||||
DTYPE_INT64: DType
|
||||
DTYPE_UINT8: DType
|
||||
DTYPE_INT8: DType
|
||||
DTYPE_BOOL: DType
|
||||
BYTE_ORDER_UNSPECIFIED: ByteOrder
|
||||
BYTE_ORDER_LITTLE_ENDIAN: ByteOrder
|
||||
BYTE_ORDER_BIG_ENDIAN: ByteOrder
|
||||
COMPRESSION_UNSPECIFIED: Compression
|
||||
COMPRESSION_NONE: Compression
|
||||
COMPRESSION_ZSTD: Compression
|
||||
CHECKSUM_ALGORITHM_UNSPECIFIED: ChecksumAlgorithm
|
||||
CHECKSUM_ALGORITHM_NONE: ChecksumAlgorithm
|
||||
CHECKSUM_ALGORITHM_CRC32C: ChecksumAlgorithm
|
||||
PHASE_UNSPECIFIED: Phase
|
||||
PHASE_PREFILL: Phase
|
||||
PHASE_DECODE: Phase
|
||||
PHASE_RELEASE: Phase
|
||||
PHASE_CANCEL: Phase
|
||||
CACHE_MODE_UNSPECIFIED: CacheMode
|
||||
CACHE_MODE_STATELESS: CacheMode
|
||||
CACHE_MODE_PREFILL: CacheMode
|
||||
CACHE_MODE_DECODE: CacheMode
|
||||
ERROR_CODE_UNSPECIFIED: ErrorCode
|
||||
ERROR_CODE_SCHEMA_UNSUPPORTED: ErrorCode
|
||||
ERROR_CODE_FINGERPRINT_MISMATCH: ErrorCode
|
||||
ERROR_CODE_EPOCH_STALE: ErrorCode
|
||||
ERROR_CODE_SHARD_RANGE_MISMATCH: ErrorCode
|
||||
ERROR_CODE_CACHE_MISS: ErrorCode
|
||||
ERROR_CODE_RESOURCE_EXHAUSTED: ErrorCode
|
||||
ERROR_CODE_PAYLOAD_CORRUPT: ErrorCode
|
||||
ERROR_CODE_CANCELLED: ErrorCode
|
||||
ERROR_CODE_DEADLINE_EXCEEDED: ErrorCode
|
||||
ERROR_CODE_FLOW_CONTROL_VIOLATION: ErrorCode
|
||||
ERROR_CODE_INTERNAL: ErrorCode
|
||||
SERVING_STATE_UNSPECIFIED: ServingState
|
||||
SERVING_STATE_SERVING: ServingState
|
||||
SERVING_STATE_DRAINING: ServingState
|
||||
SERVING_STATE_NOT_SERVING: ServingState
|
||||
|
||||
class Checksum(_message.Message):
|
||||
__slots__ = ("algorithm", "value")
|
||||
ALGORITHM_FIELD_NUMBER: _ClassVar[int]
|
||||
VALUE_FIELD_NUMBER: _ClassVar[int]
|
||||
algorithm: ChecksumAlgorithm
|
||||
value: bytes
|
||||
def __init__(self, algorithm: _Optional[_Union[ChecksumAlgorithm, str]] = ..., value: _Optional[bytes] = ...) -> None: ...
|
||||
|
||||
class TensorFragment(_message.Message):
|
||||
__slots__ = ("fragment_index", "fragment_count", "byte_offset", "payload")
|
||||
FRAGMENT_INDEX_FIELD_NUMBER: _ClassVar[int]
|
||||
FRAGMENT_COUNT_FIELD_NUMBER: _ClassVar[int]
|
||||
BYTE_OFFSET_FIELD_NUMBER: _ClassVar[int]
|
||||
PAYLOAD_FIELD_NUMBER: _ClassVar[int]
|
||||
fragment_index: int
|
||||
fragment_count: int
|
||||
byte_offset: int
|
||||
payload: bytes
|
||||
def __init__(self, fragment_index: _Optional[int] = ..., fragment_count: _Optional[int] = ..., byte_offset: _Optional[int] = ..., payload: _Optional[bytes] = ...) -> None: ...
|
||||
|
||||
class NamedTensor(_message.Message):
|
||||
__slots__ = ("name", "shape", "dtype", "byte_order", "total_bytes", "compression", "checksum", "fragments")
|
||||
NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
SHAPE_FIELD_NUMBER: _ClassVar[int]
|
||||
DTYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
BYTE_ORDER_FIELD_NUMBER: _ClassVar[int]
|
||||
TOTAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
COMPRESSION_FIELD_NUMBER: _ClassVar[int]
|
||||
CHECKSUM_FIELD_NUMBER: _ClassVar[int]
|
||||
FRAGMENTS_FIELD_NUMBER: _ClassVar[int]
|
||||
name: str
|
||||
shape: _containers.RepeatedScalarFieldContainer[int]
|
||||
dtype: DType
|
||||
byte_order: ByteOrder
|
||||
total_bytes: int
|
||||
compression: Compression
|
||||
checksum: Checksum
|
||||
fragments: _containers.RepeatedCompositeFieldContainer[TensorFragment]
|
||||
def __init__(self, name: _Optional[str] = ..., shape: _Optional[_Iterable[int]] = ..., dtype: _Optional[_Union[DType, str]] = ..., byte_order: _Optional[_Union[ByteOrder, str]] = ..., total_bytes: _Optional[int] = ..., compression: _Optional[_Union[Compression, str]] = ..., checksum: _Optional[_Union[Checksum, _Mapping]] = ..., fragments: _Optional[_Iterable[_Union[TensorFragment, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class TensorBundle(_message.Message):
|
||||
__slots__ = ("bundle_version", "tensors")
|
||||
BUNDLE_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
TENSORS_FIELD_NUMBER: _ClassVar[int]
|
||||
bundle_version: int
|
||||
tensors: _containers.RepeatedCompositeFieldContainer[NamedTensor]
|
||||
def __init__(self, bundle_version: _Optional[int] = ..., tensors: _Optional[_Iterable[_Union[NamedTensor, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class Fingerprint(_message.Message):
|
||||
__slots__ = ("model_artifact_digest", "runtime_recipe_digest", "recipe_id", "recipe_version", "catalogue_version")
|
||||
MODEL_ARTIFACT_DIGEST_FIELD_NUMBER: _ClassVar[int]
|
||||
RUNTIME_RECIPE_DIGEST_FIELD_NUMBER: _ClassVar[int]
|
||||
RECIPE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
RECIPE_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
CATALOGUE_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
model_artifact_digest: str
|
||||
runtime_recipe_digest: str
|
||||
recipe_id: str
|
||||
recipe_version: str
|
||||
catalogue_version: str
|
||||
def __init__(self, model_artifact_digest: _Optional[str] = ..., runtime_recipe_digest: _Optional[str] = ..., recipe_id: _Optional[str] = ..., recipe_version: _Optional[str] = ..., catalogue_version: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class ShardRange(_message.Message):
|
||||
__slots__ = ("start_layer", "end_layer", "effective_start_layer")
|
||||
START_LAYER_FIELD_NUMBER: _ClassVar[int]
|
||||
END_LAYER_FIELD_NUMBER: _ClassVar[int]
|
||||
EFFECTIVE_START_LAYER_FIELD_NUMBER: _ClassVar[int]
|
||||
start_layer: int
|
||||
end_layer: int
|
||||
effective_start_layer: int
|
||||
def __init__(self, start_layer: _Optional[int] = ..., end_layer: _Optional[int] = ..., effective_start_layer: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class PositionSpan(_message.Message):
|
||||
__slots__ = ("first_position", "token_count")
|
||||
FIRST_POSITION_FIELD_NUMBER: _ClassVar[int]
|
||||
TOKEN_COUNT_FIELD_NUMBER: _ClassVar[int]
|
||||
first_position: int
|
||||
token_count: int
|
||||
def __init__(self, first_position: _Optional[int] = ..., token_count: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ChunkInfo(_message.Message):
|
||||
__slots__ = ("chunk_index", "chunk_count", "final_chunk")
|
||||
CHUNK_INDEX_FIELD_NUMBER: _ClassVar[int]
|
||||
CHUNK_COUNT_FIELD_NUMBER: _ClassVar[int]
|
||||
FINAL_CHUNK_FIELD_NUMBER: _ClassVar[int]
|
||||
chunk_index: int
|
||||
chunk_count: int
|
||||
final_chunk: bool
|
||||
def __init__(self, chunk_index: _Optional[int] = ..., chunk_count: _Optional[int] = ..., final_chunk: _Optional[bool] = ...) -> None: ...
|
||||
|
||||
class CacheExpectation(_message.Message):
|
||||
__slots__ = ("mode", "expected_past_len")
|
||||
MODE_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_PAST_LEN_FIELD_NUMBER: _ClassVar[int]
|
||||
mode: CacheMode
|
||||
expected_past_len: int
|
||||
def __init__(self, mode: _Optional[_Union[CacheMode, str]] = ..., expected_past_len: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class CacheResult(_message.Message):
|
||||
__slots__ = ("mode", "past_len", "cache_hit")
|
||||
MODE_FIELD_NUMBER: _ClassVar[int]
|
||||
PAST_LEN_FIELD_NUMBER: _ClassVar[int]
|
||||
CACHE_HIT_FIELD_NUMBER: _ClassVar[int]
|
||||
mode: CacheMode
|
||||
past_len: int
|
||||
cache_hit: bool
|
||||
def __init__(self, mode: _Optional[_Union[CacheMode, str]] = ..., past_len: _Optional[int] = ..., cache_hit: _Optional[bool] = ...) -> None: ...
|
||||
|
||||
class Envelope(_message.Message):
|
||||
__slots__ = ("schema_version", "work_id", "route_session_id", "route_epoch", "fingerprint", "shard_range", "phase", "position", "idempotency_step", "cache_expectation", "deadline_unix_nanos", "chunk")
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
WORK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_EPOCH_FIELD_NUMBER: _ClassVar[int]
|
||||
FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
SHARD_RANGE_FIELD_NUMBER: _ClassVar[int]
|
||||
PHASE_FIELD_NUMBER: _ClassVar[int]
|
||||
POSITION_FIELD_NUMBER: _ClassVar[int]
|
||||
IDEMPOTENCY_STEP_FIELD_NUMBER: _ClassVar[int]
|
||||
CACHE_EXPECTATION_FIELD_NUMBER: _ClassVar[int]
|
||||
DEADLINE_UNIX_NANOS_FIELD_NUMBER: _ClassVar[int]
|
||||
CHUNK_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
work_id: str
|
||||
route_session_id: str
|
||||
route_epoch: int
|
||||
fingerprint: Fingerprint
|
||||
shard_range: ShardRange
|
||||
phase: Phase
|
||||
position: PositionSpan
|
||||
idempotency_step: int
|
||||
cache_expectation: CacheExpectation
|
||||
deadline_unix_nanos: int
|
||||
chunk: ChunkInfo
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ..., work_id: _Optional[str] = ..., route_session_id: _Optional[str] = ..., route_epoch: _Optional[int] = ..., fingerprint: _Optional[_Union[Fingerprint, _Mapping]] = ..., shard_range: _Optional[_Union[ShardRange, _Mapping]] = ..., phase: _Optional[_Union[Phase, str]] = ..., position: _Optional[_Union[PositionSpan, _Mapping]] = ..., idempotency_step: _Optional[int] = ..., cache_expectation: _Optional[_Union[CacheExpectation, _Mapping]] = ..., deadline_unix_nanos: _Optional[int] = ..., chunk: _Optional[_Union[ChunkInfo, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class ShardError(_message.Message):
|
||||
__slots__ = ("code", "detail", "retryable", "actual_past_len")
|
||||
CODE_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
RETRYABLE_FIELD_NUMBER: _ClassVar[int]
|
||||
ACTUAL_PAST_LEN_FIELD_NUMBER: _ClassVar[int]
|
||||
code: ErrorCode
|
||||
detail: str
|
||||
retryable: bool
|
||||
actual_past_len: int
|
||||
def __init__(self, code: _Optional[_Union[ErrorCode, str]] = ..., detail: _Optional[str] = ..., retryable: _Optional[bool] = ..., actual_past_len: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class FlowControl(_message.Message):
|
||||
__slots__ = ("credits_granted", "max_inflight_chunks", "max_chunk_bytes", "max_prefill_chunk_tokens")
|
||||
CREDITS_GRANTED_FIELD_NUMBER: _ClassVar[int]
|
||||
MAX_INFLIGHT_CHUNKS_FIELD_NUMBER: _ClassVar[int]
|
||||
MAX_CHUNK_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
MAX_PREFILL_CHUNK_TOKENS_FIELD_NUMBER: _ClassVar[int]
|
||||
credits_granted: int
|
||||
max_inflight_chunks: int
|
||||
max_chunk_bytes: int
|
||||
max_prefill_chunk_tokens: int
|
||||
def __init__(self, credits_granted: _Optional[int] = ..., max_inflight_chunks: _Optional[int] = ..., max_chunk_bytes: _Optional[int] = ..., max_prefill_chunk_tokens: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class SessionOpen(_message.Message):
|
||||
__slots__ = ("schema_version", "route_session_id", "route_epoch", "fingerprint", "shard_range", "proposed_flow_control", "accepted_compression")
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_EPOCH_FIELD_NUMBER: _ClassVar[int]
|
||||
FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
SHARD_RANGE_FIELD_NUMBER: _ClassVar[int]
|
||||
PROPOSED_FLOW_CONTROL_FIELD_NUMBER: _ClassVar[int]
|
||||
ACCEPTED_COMPRESSION_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
route_session_id: str
|
||||
route_epoch: int
|
||||
fingerprint: Fingerprint
|
||||
shard_range: ShardRange
|
||||
proposed_flow_control: FlowControl
|
||||
accepted_compression: _containers.RepeatedScalarFieldContainer[Compression]
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ..., route_session_id: _Optional[str] = ..., route_epoch: _Optional[int] = ..., fingerprint: _Optional[_Union[Fingerprint, _Mapping]] = ..., shard_range: _Optional[_Union[ShardRange, _Mapping]] = ..., proposed_flow_control: _Optional[_Union[FlowControl, _Mapping]] = ..., accepted_compression: _Optional[_Iterable[_Union[Compression, str]]] = ...) -> None: ...
|
||||
|
||||
class SessionAccepted(_message.Message):
|
||||
__slots__ = ("schema_version", "route_session_id", "route_epoch", "flow_control", "accepted_compression", "fingerprint")
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_EPOCH_FIELD_NUMBER: _ClassVar[int]
|
||||
FLOW_CONTROL_FIELD_NUMBER: _ClassVar[int]
|
||||
ACCEPTED_COMPRESSION_FIELD_NUMBER: _ClassVar[int]
|
||||
FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
route_session_id: str
|
||||
route_epoch: int
|
||||
flow_control: FlowControl
|
||||
accepted_compression: _containers.RepeatedScalarFieldContainer[Compression]
|
||||
fingerprint: Fingerprint
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ..., route_session_id: _Optional[str] = ..., route_epoch: _Optional[int] = ..., flow_control: _Optional[_Union[FlowControl, _Mapping]] = ..., accepted_compression: _Optional[_Iterable[_Union[Compression, str]]] = ..., fingerprint: _Optional[_Union[Fingerprint, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class ActivationChunk(_message.Message):
|
||||
__slots__ = ("envelope", "bundle")
|
||||
ENVELOPE_FIELD_NUMBER: _ClassVar[int]
|
||||
BUNDLE_FIELD_NUMBER: _ClassVar[int]
|
||||
envelope: Envelope
|
||||
bundle: TensorBundle
|
||||
def __init__(self, envelope: _Optional[_Union[Envelope, _Mapping]] = ..., bundle: _Optional[_Union[TensorBundle, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class DecodeStep(_message.Message):
|
||||
__slots__ = ("idempotency_step", "position", "expected_past_len", "tensor", "work_id", "deadline_unix_nanos")
|
||||
IDEMPOTENCY_STEP_FIELD_NUMBER: _ClassVar[int]
|
||||
POSITION_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_PAST_LEN_FIELD_NUMBER: _ClassVar[int]
|
||||
TENSOR_FIELD_NUMBER: _ClassVar[int]
|
||||
WORK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DEADLINE_UNIX_NANOS_FIELD_NUMBER: _ClassVar[int]
|
||||
idempotency_step: int
|
||||
position: int
|
||||
expected_past_len: int
|
||||
tensor: NamedTensor
|
||||
work_id: str
|
||||
deadline_unix_nanos: int
|
||||
def __init__(self, idempotency_step: _Optional[int] = ..., position: _Optional[int] = ..., expected_past_len: _Optional[int] = ..., tensor: _Optional[_Union[NamedTensor, _Mapping]] = ..., work_id: _Optional[str] = ..., deadline_unix_nanos: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ReleaseSignal(_message.Message):
|
||||
__slots__ = ("route_session_id", "route_epoch", "work_id")
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_EPOCH_FIELD_NUMBER: _ClassVar[int]
|
||||
WORK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
route_session_id: str
|
||||
route_epoch: int
|
||||
work_id: str
|
||||
def __init__(self, route_session_id: _Optional[str] = ..., route_epoch: _Optional[int] = ..., work_id: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class CancelSignal(_message.Message):
|
||||
__slots__ = ("route_session_id", "route_epoch", "work_id", "reason")
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_EPOCH_FIELD_NUMBER: _ClassVar[int]
|
||||
WORK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
REASON_FIELD_NUMBER: _ClassVar[int]
|
||||
route_session_id: str
|
||||
route_epoch: int
|
||||
work_id: str
|
||||
reason: str
|
||||
def __init__(self, route_session_id: _Optional[str] = ..., route_epoch: _Optional[int] = ..., work_id: _Optional[str] = ..., reason: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class Ack(_message.Message):
|
||||
__slots__ = ("work_id", "idempotency_step", "cache_result", "duplicate", "execution_nanos")
|
||||
WORK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
IDEMPOTENCY_STEP_FIELD_NUMBER: _ClassVar[int]
|
||||
CACHE_RESULT_FIELD_NUMBER: _ClassVar[int]
|
||||
DUPLICATE_FIELD_NUMBER: _ClassVar[int]
|
||||
EXECUTION_NANOS_FIELD_NUMBER: _ClassVar[int]
|
||||
work_id: str
|
||||
idempotency_step: int
|
||||
cache_result: CacheResult
|
||||
duplicate: bool
|
||||
execution_nanos: int
|
||||
def __init__(self, work_id: _Optional[str] = ..., idempotency_step: _Optional[int] = ..., cache_result: _Optional[_Union[CacheResult, _Mapping]] = ..., duplicate: _Optional[bool] = ..., execution_nanos: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ShardStatus(_message.Message):
|
||||
__slots__ = ("work_id", "route_session_id", "idempotency_step", "error", "terminal")
|
||||
WORK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
IDEMPOTENCY_STEP_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
TERMINAL_FIELD_NUMBER: _ClassVar[int]
|
||||
work_id: str
|
||||
route_session_id: str
|
||||
idempotency_step: int
|
||||
error: ShardError
|
||||
terminal: bool
|
||||
def __init__(self, work_id: _Optional[str] = ..., route_session_id: _Optional[str] = ..., idempotency_step: _Optional[int] = ..., error: _Optional[_Union[ShardError, _Mapping]] = ..., terminal: _Optional[bool] = ...) -> None: ...
|
||||
|
||||
class SessionRequest(_message.Message):
|
||||
__slots__ = ("open", "chunk", "decode", "flow_control", "release", "cancel")
|
||||
OPEN_FIELD_NUMBER: _ClassVar[int]
|
||||
CHUNK_FIELD_NUMBER: _ClassVar[int]
|
||||
DECODE_FIELD_NUMBER: _ClassVar[int]
|
||||
FLOW_CONTROL_FIELD_NUMBER: _ClassVar[int]
|
||||
RELEASE_FIELD_NUMBER: _ClassVar[int]
|
||||
CANCEL_FIELD_NUMBER: _ClassVar[int]
|
||||
open: SessionOpen
|
||||
chunk: ActivationChunk
|
||||
decode: DecodeStep
|
||||
flow_control: FlowControl
|
||||
release: ReleaseSignal
|
||||
cancel: CancelSignal
|
||||
def __init__(self, open: _Optional[_Union[SessionOpen, _Mapping]] = ..., chunk: _Optional[_Union[ActivationChunk, _Mapping]] = ..., decode: _Optional[_Union[DecodeStep, _Mapping]] = ..., flow_control: _Optional[_Union[FlowControl, _Mapping]] = ..., release: _Optional[_Union[ReleaseSignal, _Mapping]] = ..., cancel: _Optional[_Union[CancelSignal, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class SessionResponse(_message.Message):
|
||||
__slots__ = ("accepted", "chunk", "ack", "flow_control", "status")
|
||||
ACCEPTED_FIELD_NUMBER: _ClassVar[int]
|
||||
CHUNK_FIELD_NUMBER: _ClassVar[int]
|
||||
ACK_FIELD_NUMBER: _ClassVar[int]
|
||||
FLOW_CONTROL_FIELD_NUMBER: _ClassVar[int]
|
||||
STATUS_FIELD_NUMBER: _ClassVar[int]
|
||||
accepted: SessionAccepted
|
||||
chunk: ActivationChunk
|
||||
ack: Ack
|
||||
flow_control: FlowControl
|
||||
status: ShardStatus
|
||||
def __init__(self, accepted: _Optional[_Union[SessionAccepted, _Mapping]] = ..., chunk: _Optional[_Union[ActivationChunk, _Mapping]] = ..., ack: _Optional[_Union[Ack, _Mapping]] = ..., flow_control: _Optional[_Union[FlowControl, _Mapping]] = ..., status: _Optional[_Union[ShardStatus, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class CapabilityRequest(_message.Message):
|
||||
__slots__ = ("schema_version",)
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ...) -> None: ...
|
||||
|
||||
class CapabilityReport(_message.Message):
|
||||
__slots__ = ("schema_version", "fingerprint", "shard_range", "backend", "device", "validated", "detail", "max_concurrent_sessions", "max_context_tokens", "flow_control", "accepted_compression", "supported_schema_versions", "validated_at_unix_nanos")
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
SHARD_RANGE_FIELD_NUMBER: _ClassVar[int]
|
||||
BACKEND_FIELD_NUMBER: _ClassVar[int]
|
||||
DEVICE_FIELD_NUMBER: _ClassVar[int]
|
||||
VALIDATED_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
MAX_CONCURRENT_SESSIONS_FIELD_NUMBER: _ClassVar[int]
|
||||
MAX_CONTEXT_TOKENS_FIELD_NUMBER: _ClassVar[int]
|
||||
FLOW_CONTROL_FIELD_NUMBER: _ClassVar[int]
|
||||
ACCEPTED_COMPRESSION_FIELD_NUMBER: _ClassVar[int]
|
||||
SUPPORTED_SCHEMA_VERSIONS_FIELD_NUMBER: _ClassVar[int]
|
||||
VALIDATED_AT_UNIX_NANOS_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
fingerprint: Fingerprint
|
||||
shard_range: ShardRange
|
||||
backend: str
|
||||
device: str
|
||||
validated: bool
|
||||
detail: str
|
||||
max_concurrent_sessions: int
|
||||
max_context_tokens: int
|
||||
flow_control: FlowControl
|
||||
accepted_compression: _containers.RepeatedScalarFieldContainer[Compression]
|
||||
supported_schema_versions: _containers.RepeatedScalarFieldContainer[SchemaVersion]
|
||||
validated_at_unix_nanos: int
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ..., fingerprint: _Optional[_Union[Fingerprint, _Mapping]] = ..., shard_range: _Optional[_Union[ShardRange, _Mapping]] = ..., backend: _Optional[str] = ..., device: _Optional[str] = ..., validated: _Optional[bool] = ..., detail: _Optional[str] = ..., max_concurrent_sessions: _Optional[int] = ..., max_context_tokens: _Optional[int] = ..., flow_control: _Optional[_Union[FlowControl, _Mapping]] = ..., accepted_compression: _Optional[_Iterable[_Union[Compression, str]]] = ..., supported_schema_versions: _Optional[_Iterable[_Union[SchemaVersion, str]]] = ..., validated_at_unix_nanos: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class HealthRequest(_message.Message):
|
||||
__slots__ = ("schema_version",)
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ...) -> None: ...
|
||||
|
||||
class HealthReport(_message.Message):
|
||||
__slots__ = ("schema_version", "state", "active_sessions", "queued_chunks", "batch_occupancy", "kv_pressure", "resident_bytes", "detail")
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
ACTIVE_SESSIONS_FIELD_NUMBER: _ClassVar[int]
|
||||
QUEUED_CHUNKS_FIELD_NUMBER: _ClassVar[int]
|
||||
BATCH_OCCUPANCY_FIELD_NUMBER: _ClassVar[int]
|
||||
KV_PRESSURE_FIELD_NUMBER: _ClassVar[int]
|
||||
RESIDENT_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
state: ServingState
|
||||
active_sessions: int
|
||||
queued_chunks: int
|
||||
batch_occupancy: int
|
||||
kv_pressure: float
|
||||
resident_bytes: int
|
||||
detail: str
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ..., state: _Optional[_Union[ServingState, str]] = ..., active_sessions: _Optional[int] = ..., queued_chunks: _Optional[int] = ..., batch_occupancy: _Optional[int] = ..., kv_pressure: _Optional[float] = ..., resident_bytes: _Optional[int] = ..., detail: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class ReleaseRequest(_message.Message):
|
||||
__slots__ = ("schema_version", "route_session_id", "route_epoch")
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_EPOCH_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
route_session_id: str
|
||||
route_epoch: int
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ..., route_session_id: _Optional[str] = ..., route_epoch: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ReleaseResponse(_message.Message):
|
||||
__slots__ = ("released", "error")
|
||||
RELEASED_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
released: bool
|
||||
error: ShardError
|
||||
def __init__(self, released: _Optional[bool] = ..., error: _Optional[_Union[ShardError, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class CancelRequest(_message.Message):
|
||||
__slots__ = ("schema_version", "route_session_id", "route_epoch", "work_id", "reason")
|
||||
SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_EPOCH_FIELD_NUMBER: _ClassVar[int]
|
||||
WORK_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
REASON_FIELD_NUMBER: _ClassVar[int]
|
||||
schema_version: SchemaVersion
|
||||
route_session_id: str
|
||||
route_epoch: int
|
||||
work_id: str
|
||||
reason: str
|
||||
def __init__(self, schema_version: _Optional[_Union[SchemaVersion, str]] = ..., route_session_id: _Optional[str] = ..., route_epoch: _Optional[int] = ..., work_id: _Optional[str] = ..., reason: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class CancelResponse(_message.Message):
|
||||
__slots__ = ("cancelled_work_items", "error")
|
||||
CANCELLED_WORK_ITEMS_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
cancelled_work_items: int
|
||||
error: ShardError
|
||||
def __init__(self, cancelled_work_items: _Optional[int] = ..., error: _Optional[_Union[ShardError, _Mapping]] = ...) -> None: ...
|
||||
@@ -0,0 +1,295 @@
|
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
import warnings
|
||||
|
||||
from . import shard_runtime_pb2 as shard__runtime__pb2
|
||||
|
||||
GRPC_GENERATED_VERSION = '1.82.1'
|
||||
GRPC_VERSION = grpc.__version__
|
||||
_version_not_supported = False
|
||||
|
||||
try:
|
||||
from grpc._utilities import first_version_is_lower
|
||||
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
||||
except ImportError:
|
||||
_version_not_supported = True
|
||||
|
||||
if _version_not_supported:
|
||||
raise RuntimeError(
|
||||
f'The grpc package installed is at version {GRPC_VERSION},'
|
||||
+ ' but the generated code in shard_runtime_pb2_grpc.py depends on'
|
||||
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
||||
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
||||
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
||||
)
|
||||
|
||||
|
||||
class ShardRuntimeStub:
|
||||
"""---------------------------------------------------------------------------
|
||||
Service
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.GetCapability = channel.unary_unary(
|
||||
'/meshnet.shard.v1.ShardRuntime/GetCapability',
|
||||
request_serializer=shard__runtime__pb2.CapabilityRequest.SerializeToString,
|
||||
response_deserializer=shard__runtime__pb2.CapabilityReport.FromString,
|
||||
_registered_method=True)
|
||||
self.Health = channel.unary_unary(
|
||||
'/meshnet.shard.v1.ShardRuntime/Health',
|
||||
request_serializer=shard__runtime__pb2.HealthRequest.SerializeToString,
|
||||
response_deserializer=shard__runtime__pb2.HealthReport.FromString,
|
||||
_registered_method=True)
|
||||
self.Session = channel.stream_stream(
|
||||
'/meshnet.shard.v1.ShardRuntime/Session',
|
||||
request_serializer=shard__runtime__pb2.SessionRequest.SerializeToString,
|
||||
response_deserializer=shard__runtime__pb2.SessionResponse.FromString,
|
||||
_registered_method=True)
|
||||
self.Release = channel.unary_unary(
|
||||
'/meshnet.shard.v1.ShardRuntime/Release',
|
||||
request_serializer=shard__runtime__pb2.ReleaseRequest.SerializeToString,
|
||||
response_deserializer=shard__runtime__pb2.ReleaseResponse.FromString,
|
||||
_registered_method=True)
|
||||
self.Cancel = channel.unary_unary(
|
||||
'/meshnet.shard.v1.ShardRuntime/Cancel',
|
||||
request_serializer=shard__runtime__pb2.CancelRequest.SerializeToString,
|
||||
response_deserializer=shard__runtime__pb2.CancelResponse.FromString,
|
||||
_registered_method=True)
|
||||
|
||||
|
||||
class ShardRuntimeServicer:
|
||||
"""---------------------------------------------------------------------------
|
||||
Service
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
|
||||
def GetCapability(self, request, context):
|
||||
"""What this worker can execute. Read before a route is built.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def Health(self, request, context):
|
||||
"""Live load and serving state.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def Session(self, request_iterator, context):
|
||||
"""One long-lived bidirectional stream per Route Session Activation Seam.
|
||||
|
||||
The stream opens with SessionOpen/SessionAccepted, then carries bounded
|
||||
prefill chunks and decode steps in both directions for the life of the
|
||||
session. Per-token channel creation is a non-goal: the handshake cost is
|
||||
paid once and the hot path carries only what changes.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def Release(self, request, context):
|
||||
"""Drop session state out of band. Idempotent.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def Cancel(self, request, context):
|
||||
"""Cancel out of band, on a fresh call.
|
||||
|
||||
In-band CancelSignal is preferred, but a sender that is blocked on flow
|
||||
control cannot write one — a cancel that can only travel down a wedged
|
||||
stream is not a cancel. This RPC always has a path to the worker.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
|
||||
def add_ShardRuntimeServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {
|
||||
'GetCapability': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetCapability,
|
||||
request_deserializer=shard__runtime__pb2.CapabilityRequest.FromString,
|
||||
response_serializer=shard__runtime__pb2.CapabilityReport.SerializeToString,
|
||||
),
|
||||
'Health': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.Health,
|
||||
request_deserializer=shard__runtime__pb2.HealthRequest.FromString,
|
||||
response_serializer=shard__runtime__pb2.HealthReport.SerializeToString,
|
||||
),
|
||||
'Session': grpc.stream_stream_rpc_method_handler(
|
||||
servicer.Session,
|
||||
request_deserializer=shard__runtime__pb2.SessionRequest.FromString,
|
||||
response_serializer=shard__runtime__pb2.SessionResponse.SerializeToString,
|
||||
),
|
||||
'Release': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.Release,
|
||||
request_deserializer=shard__runtime__pb2.ReleaseRequest.FromString,
|
||||
response_serializer=shard__runtime__pb2.ReleaseResponse.SerializeToString,
|
||||
),
|
||||
'Cancel': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.Cancel,
|
||||
request_deserializer=shard__runtime__pb2.CancelRequest.FromString,
|
||||
response_serializer=shard__runtime__pb2.CancelResponse.SerializeToString,
|
||||
),
|
||||
}
|
||||
generic_handler = grpc.method_handlers_generic_handler(
|
||||
'meshnet.shard.v1.ShardRuntime', rpc_method_handlers)
|
||||
server.add_generic_rpc_handlers((generic_handler,))
|
||||
server.add_registered_method_handlers('meshnet.shard.v1.ShardRuntime', rpc_method_handlers)
|
||||
|
||||
|
||||
# This class is part of an EXPERIMENTAL API.
|
||||
class ShardRuntime:
|
||||
"""---------------------------------------------------------------------------
|
||||
Service
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def GetCapability(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/meshnet.shard.v1.ShardRuntime/GetCapability',
|
||||
shard__runtime__pb2.CapabilityRequest.SerializeToString,
|
||||
shard__runtime__pb2.CapabilityReport.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def Health(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/meshnet.shard.v1.ShardRuntime/Health',
|
||||
shard__runtime__pb2.HealthRequest.SerializeToString,
|
||||
shard__runtime__pb2.HealthReport.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def Session(request_iterator,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.stream_stream(
|
||||
request_iterator,
|
||||
target,
|
||||
'/meshnet.shard.v1.ShardRuntime/Session',
|
||||
shard__runtime__pb2.SessionRequest.SerializeToString,
|
||||
shard__runtime__pb2.SessionResponse.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def Release(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/meshnet.shard.v1.ShardRuntime/Release',
|
||||
shard__runtime__pb2.ReleaseRequest.SerializeToString,
|
||||
shard__runtime__pb2.ReleaseResponse.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def Cancel(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/meshnet.shard.v1.ShardRuntime/Cancel',
|
||||
shard__runtime__pb2.CancelRequest.SerializeToString,
|
||||
shard__runtime__pb2.CancelResponse.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
74
packages/node/native/CMakeLists.txt
Normal file
74
packages/node/native/CMakeLists.txt
Normal file
@@ -0,0 +1,74 @@
|
||||
# Native Shard protocol: C++ schema generation, build wiring and conformance test.
|
||||
#
|
||||
# The C++ stubs are generated into the build tree at configure time — they are
|
||||
# never committed. A C++ consumer already needs a toolchain, so committing
|
||||
# generated C++ would only create a second copy of the schema that can rot.
|
||||
#
|
||||
# gRPC C++ is optional here on purpose. The conformance test only needs message
|
||||
# types, so the schema can be verified on a machine that has protobuf but not
|
||||
# the gRPC C++ stack. When gRPC *is* found, the service stubs are generated too
|
||||
# and exported as `shard_runtime_grpc` for the worker (DGR-008) to link.
|
||||
#
|
||||
# Build:
|
||||
# cmake -S packages/node/native -B build/native -DCMAKE_PREFIX_PATH=<protobuf-install>
|
||||
# cmake --build build/native -j
|
||||
# ctest --test-dir build/native --output-on-failure
|
||||
#
|
||||
# `scripts/bootstrap_native_toolchain.sh` builds a protobuf install from source
|
||||
# when the system has none.
|
||||
|
||||
cmake_minimum_required(VERSION 3.24)
|
||||
project(meshnet_shard_protocol CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(protobuf CONFIG REQUIRED)
|
||||
find_package(gRPC CONFIG QUIET)
|
||||
|
||||
set(SHARD_PROTO "${CMAKE_CURRENT_SOURCE_DIR}/proto/shard_runtime.proto")
|
||||
|
||||
# Message types: always available.
|
||||
add_library(shard_runtime_proto STATIC "${SHARD_PROTO}")
|
||||
target_link_libraries(shard_runtime_proto PUBLIC protobuf::libprotobuf)
|
||||
target_include_directories(shard_runtime_proto PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
protobuf_generate(
|
||||
TARGET shard_runtime_proto
|
||||
LANGUAGE cpp
|
||||
IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/proto"
|
||||
PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
|
||||
# Service stubs: only when the gRPC C++ stack is present.
|
||||
if(gRPC_FOUND)
|
||||
add_library(shard_runtime_grpc STATIC "${SHARD_PROTO}")
|
||||
target_link_libraries(shard_runtime_grpc PUBLIC shard_runtime_proto gRPC::grpc++)
|
||||
target_include_directories(shard_runtime_grpc PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
protobuf_generate(
|
||||
TARGET shard_runtime_grpc
|
||||
LANGUAGE grpc
|
||||
GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc
|
||||
PLUGIN "protoc-gen-grpc=$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
|
||||
IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/proto"
|
||||
PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
message(STATUS "gRPC C++ found: building ShardRuntime service stubs")
|
||||
else()
|
||||
message(STATUS "gRPC C++ not found: building message types only "
|
||||
"(sufficient for the conformance test)")
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(shard_protocol_conformance tests/test_shard_protocol_conformance.cpp)
|
||||
target_link_libraries(shard_protocol_conformance PRIVATE shard_runtime_proto)
|
||||
|
||||
# The test asserts against the same committed vectors the Python test uses, and
|
||||
# writes its own re-serialization back out so Python can prove the two languages
|
||||
# agree byte-for-byte.
|
||||
add_test(
|
||||
NAME shard_protocol_conformance
|
||||
COMMAND shard_protocol_conformance
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/testdata"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
69
packages/node/native/README.md
Normal file
69
packages/node/native/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Native Shard protocol
|
||||
|
||||
`proto/shard_runtime.proto` is the semantic contract between a Meshnet node and
|
||||
a Shard worker: Protocol Buffers over gRPC/HTTP2 (ADR-0020). It is the source of
|
||||
truth. The Python and C++ types are generated from it; neither is the contract.
|
||||
|
||||
## What lives here
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `proto/shard_runtime.proto` | The schema: capability, health, session stream, release, cancel |
|
||||
| `testdata/*.binpb` | Committed conformance vectors both languages assert against |
|
||||
| `tests/test_shard_protocol_conformance.cpp` | C++ conformance test |
|
||||
| `CMakeLists.txt` | C++ generation, build wiring, and `ctest` registration |
|
||||
|
||||
The Python stubs are generated into
|
||||
`packages/node/meshnet_node/native_protocol/generated/` and are committed, so
|
||||
installing a node needs no protoc. The C++ stubs are generated into the build
|
||||
tree and are never committed — a C++ consumer already has a toolchain, and a
|
||||
committed copy could only rot.
|
||||
|
||||
## Regenerating
|
||||
|
||||
```bash
|
||||
pip install grpcio-tools==1.82.1 # bundles protoc; no system protoc needed
|
||||
python scripts/generate_native_protocol.py # rewrite the Python stubs
|
||||
python scripts/generate_native_protocol.py --check # fail if they drifted
|
||||
python scripts/generate_protocol_goldens.py --check # fail if the vectors drifted
|
||||
```
|
||||
|
||||
Both `--check` modes run in CI via `tests/test_native_shard_protocol.py`, so a
|
||||
schema edit that is not accompanied by regenerated output fails the suite rather
|
||||
than shipping stubs that disagree with the schema they claim to implement.
|
||||
|
||||
## Building and running the C++ conformance test
|
||||
|
||||
If the machine has no protobuf C++ toolchain:
|
||||
|
||||
```bash
|
||||
scripts/bootstrap_native_toolchain.sh build/native-toolchain
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
cmake -S packages/node/native -B build/native \
|
||||
-DCMAKE_PREFIX_PATH="$PWD/build/native-toolchain"
|
||||
cmake --build build/native -j
|
||||
ctest --test-dir build/native --output-on-failure
|
||||
```
|
||||
|
||||
gRPC C++ is optional: without it, CMake builds the message types only, which is
|
||||
all the conformance test needs. When gRPC C++ *is* found, the `ShardRuntime`
|
||||
service stubs are built too and exported as `shard_runtime_grpc` for the worker
|
||||
(DGR-008) to link.
|
||||
|
||||
## How the cross-language check actually proves something
|
||||
|
||||
Two codecs that each round-trip their own output prove only that each is
|
||||
self-consistent. Instead:
|
||||
|
||||
1. Python builds the canonical message and commits its bytes to `testdata/`.
|
||||
2. The C++ test parses *those* bytes, asserts every field, independently
|
||||
recomputes the CRC32C from the polynomial, and re-serializes to
|
||||
`cpp_roundtrip.binpb` in the build tree.
|
||||
3. `test_cpp_and_python_agree_byte_for_byte` compares that file to the golden.
|
||||
|
||||
Byte equality across the two implementations is the claim; anything less is two
|
||||
parallel test suites that can drift apart.
|
||||
592
packages/node/native/proto/shard_runtime.proto
Normal file
592
packages/node/native/proto/shard_runtime.proto
Normal file
@@ -0,0 +1,592 @@
|
||||
// The native Shard data plane: Protocol Buffers over gRPC/HTTP2 (ADR-0020).
|
||||
//
|
||||
// This schema — not any Python or C++ type — is the semantic contract between a
|
||||
// Meshnet node and a Shard worker. Direct hops speak gRPC. When direct
|
||||
// connectivity is unavailable the existing relay carries these exact serialized
|
||||
// frames as opaque binary, which is why every deadline, cancellation and
|
||||
// identity field is carried *in the schema* rather than left to gRPC metadata:
|
||||
// a relayed frame has no HTTP/2 context to inherit them from.
|
||||
//
|
||||
// Meshnet remains the only control plane. Nothing here selects routes, prices
|
||||
// work, or authenticates peers; the worker executes a layer range it has been
|
||||
// admitted for and reports what it did.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package meshnet.shard.v1;
|
||||
|
||||
option cc_enable_arenas = true;
|
||||
option go_package = "github.com/meshnet/shard/v1;shardv1";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Versioning
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Wire-compatibility generation of this schema. A worker rejects a peer whose
|
||||
// SCHEMA_VERSION it cannot satisfy instead of guessing at field meaning.
|
||||
//
|
||||
// This is deliberately NOT the `X-Meshnet-Wire` version of the legacy HTTP
|
||||
// activation format (currently "2", see ADR-0008). The native protocol is a
|
||||
// separate contract with its own generation counter and starts at 1.
|
||||
enum SchemaVersion {
|
||||
SCHEMA_VERSION_UNSPECIFIED = 0;
|
||||
SCHEMA_VERSION_1 = 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tensor bundle — the public activation boundary
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Element type of a tensor payload.
|
||||
//
|
||||
// bfloat16 is the canonical activation dtype at every Shard boundary regardless
|
||||
// of the weight quantization a node uses internally (ADR-0008): weights may be
|
||||
// NF4 or INT8, compute upcasts, and the boundary stays bfloat16. The other
|
||||
// values exist for auxiliary tensors (position ids, attention masks) and for
|
||||
// architectures whose boundary is not a plain hidden state.
|
||||
enum DType {
|
||||
DTYPE_UNSPECIFIED = 0;
|
||||
DTYPE_BFLOAT16 = 1;
|
||||
DTYPE_FLOAT16 = 2;
|
||||
DTYPE_FLOAT32 = 3;
|
||||
DTYPE_INT32 = 4;
|
||||
DTYPE_INT64 = 5;
|
||||
DTYPE_UINT8 = 6;
|
||||
DTYPE_INT8 = 7;
|
||||
DTYPE_BOOL = 8;
|
||||
}
|
||||
|
||||
// Byte order of a tensor payload. Little-endian is canonical on the wire; the
|
||||
// field is explicit so a big-endian peer is rejected loudly rather than
|
||||
// silently reading byte-swapped activations.
|
||||
enum ByteOrder {
|
||||
BYTE_ORDER_UNSPECIFIED = 0;
|
||||
BYTE_ORDER_LITTLE_ENDIAN = 1;
|
||||
BYTE_ORDER_BIG_ENDIAN = 2;
|
||||
}
|
||||
|
||||
// Payload compression. Mirrors the policy-driven zstd decision the existing
|
||||
// activation seam already makes (`activation_compression.py`): compression is a
|
||||
// transport optimisation and NONE is always a legal choice for any payload.
|
||||
enum Compression {
|
||||
COMPRESSION_UNSPECIFIED = 0;
|
||||
COMPRESSION_NONE = 1;
|
||||
COMPRESSION_ZSTD = 2;
|
||||
}
|
||||
|
||||
// Integrity algorithm covering a payload.
|
||||
enum ChecksumAlgorithm {
|
||||
CHECKSUM_ALGORITHM_UNSPECIFIED = 0;
|
||||
CHECKSUM_ALGORITHM_NONE = 1;
|
||||
CHECKSUM_ALGORITHM_CRC32C = 2;
|
||||
}
|
||||
|
||||
// An integrity check over the *uncompressed* canonical payload bytes.
|
||||
//
|
||||
// Checksumming the uncompressed bytes (not the compressed frame) means the
|
||||
// value is stable whether a hop compressed the payload or not, so a relay may
|
||||
// re-frame or a peer may decline compression without invalidating it.
|
||||
message Checksum {
|
||||
ChecksumAlgorithm algorithm = 1;
|
||||
// Big-endian encoding of the checksum value.
|
||||
bytes value = 2;
|
||||
}
|
||||
|
||||
// One slice of a tensor's wire payload.
|
||||
//
|
||||
// Fragments bound individual payload pieces and make coverage independently
|
||||
// verifiable. The negotiated `FlowControl.max_chunk_bytes` bounds the enclosing
|
||||
// serialized stream message; `byte_offset` proves the pieces tile the tensor
|
||||
// exactly — no hole, no overlap — instead of trusting arrival order.
|
||||
//
|
||||
// Offsets and payloads describe the *wire* body, which is the compressed frame
|
||||
// when the parent tensor declares a compression. A zstd frame is not decodable
|
||||
// per fragment, so a receiver reassembles first and decompresses once. The
|
||||
// uncompressed size is not repeated here: `NamedTensor.total_bytes` is the
|
||||
// single source of truth, and a second copy could only ever disagree with it.
|
||||
message TensorFragment {
|
||||
uint32 fragment_index = 1;
|
||||
uint32 fragment_count = 2;
|
||||
// Offset of this fragment within the tensor's wire body.
|
||||
uint64 byte_offset = 3;
|
||||
// Fragment bytes, compressed iff the parent tensor declares a compression.
|
||||
bytes payload = 4;
|
||||
|
||||
reserved 5;
|
||||
reserved "uncompressed_size";
|
||||
}
|
||||
|
||||
// One named tensor at an architecture boundary.
|
||||
message NamedTensor {
|
||||
// Architecture-defined boundary name, e.g. "hidden_states", "position_ids".
|
||||
// A boundary may need more than one tensor, which is why the payload is a
|
||||
// named bundle rather than a bare buffer (ADR-0020).
|
||||
string name = 1;
|
||||
repeated int64 shape = 2;
|
||||
DType dtype = 3;
|
||||
ByteOrder byte_order = 4;
|
||||
// Total uncompressed payload size. A receiver sizes its buffer from this
|
||||
// before reading fragments and refuses a bundle that exceeds its budget.
|
||||
uint64 total_bytes = 5;
|
||||
Compression compression = 6;
|
||||
// Over the uncompressed payload; see Checksum.
|
||||
Checksum checksum = 7;
|
||||
// Ordered fragments. A tensor small enough for one message carries exactly
|
||||
// one fragment with fragment_count == 1.
|
||||
repeated TensorFragment fragments = 8;
|
||||
}
|
||||
|
||||
// A versioned named-tensor bundle: the public activation boundary payload.
|
||||
message TensorBundle {
|
||||
// Generation of the bundle layout itself, independent of SchemaVersion so a
|
||||
// boundary payload can evolve without a whole-protocol version bump.
|
||||
uint32 bundle_version = 1;
|
||||
repeated NamedTensor tensors = 2;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Identity: what work this is, for which route, against which artifact
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Exact identity of the executable thing a Shard runs.
|
||||
//
|
||||
// Both fingerprints must match end to end across a route. Two nodes agreeing on
|
||||
// a model name but not on quantization, kernel or tail-norm placement would
|
||||
// produce silently wrong tokens, so identity is a digest, not a label.
|
||||
message Fingerprint {
|
||||
// Digest of the exact Model Artifact (weights + config), e.g. the GGUF hash.
|
||||
string model_artifact_digest = 1;
|
||||
// Digest of the exact runtime recipe (backend, quantization, kernels, dtype).
|
||||
string runtime_recipe_digest = 2;
|
||||
// Human-readable recipe identity, mirroring the capability report an admitted
|
||||
// node already registers with (ADR-0023). Labels are for diagnosis; the
|
||||
// digests above are what a peer actually compares.
|
||||
string recipe_id = 3;
|
||||
string recipe_version = 4;
|
||||
string catalogue_version = 5;
|
||||
}
|
||||
|
||||
// The contiguous transformer layer range a Shard owns.
|
||||
message ShardRange {
|
||||
// Registered range, inclusive start, exclusive end.
|
||||
uint32 start_layer = 1;
|
||||
uint32 end_layer = 2;
|
||||
// The layer this Shard must actually begin at for this route (ADR-0012).
|
||||
//
|
||||
// Shard ranges may overlap; the Tracker resolves the overlap when it builds
|
||||
// the route and every hop is told where the previous hop stopped. Executing
|
||||
// from `start_layer` when `effective_start_layer` is higher would re-apply
|
||||
// layers already applied to the incoming activation and silently corrupt the
|
||||
// result. A worker executes [effective_start_layer, end_layer).
|
||||
uint32 effective_start_layer = 3;
|
||||
}
|
||||
|
||||
// Which part of generation a message belongs to.
|
||||
enum Phase {
|
||||
PHASE_UNSPECIFIED = 0;
|
||||
PHASE_PREFILL = 1;
|
||||
PHASE_DECODE = 2;
|
||||
PHASE_RELEASE = 3;
|
||||
PHASE_CANCEL = 4;
|
||||
}
|
||||
|
||||
// Token span carried by one chunk.
|
||||
message PositionSpan {
|
||||
// Absolute position of the first token in this chunk within the sequence.
|
||||
uint64 first_position = 1;
|
||||
// Number of token positions in this chunk. A decode step carries 1.
|
||||
uint32 token_count = 2;
|
||||
}
|
||||
|
||||
// Bounded chunking for a prefill.
|
||||
//
|
||||
// A long prompt is split into token-aligned chunks before the first forward
|
||||
// pass (ADR-0008) so peak transfer per boundary stays bounded regardless of
|
||||
// prompt length. Splits never fall mid-token.
|
||||
message ChunkInfo {
|
||||
uint32 chunk_index = 1;
|
||||
uint32 chunk_count = 2;
|
||||
// True on the final chunk of a prefill. A receiver that has not seen a final
|
||||
// chunk knows the prefill is still incomplete.
|
||||
bool final_chunk = 3;
|
||||
}
|
||||
|
||||
// How the sender expects the receiver's Hot KV State to be positioned.
|
||||
enum CacheMode {
|
||||
CACHE_MODE_UNSPECIFIED = 0;
|
||||
// No session state; the payload carries everything needed. Always a legal
|
||||
// fallback and the recovery path after a miss.
|
||||
CACHE_MODE_STATELESS = 1;
|
||||
// Establish fresh session state for this Shard's layer range.
|
||||
CACHE_MODE_PREFILL = 2;
|
||||
// Continue existing session state. `expected_past_len` must match exactly.
|
||||
CACHE_MODE_DECODE = 3;
|
||||
}
|
||||
|
||||
// The sender's expectation about receiver-side cache state (ADR-0022).
|
||||
//
|
||||
// A mismatch is always a declared cache miss, never a silent stateless forward:
|
||||
// running a single-token decode payload without the matching cache would emit
|
||||
// plausible garbage. The receiver answers a miss with ShardError.CACHE_MISS and
|
||||
// the head re-prefills the whole sequence under the same Route Session ID.
|
||||
message CacheExpectation {
|
||||
CacheMode mode = 1;
|
||||
// Decode only: the number of tokens the receiver's session cache must already
|
||||
// hold for this Shard's layer range.
|
||||
uint64 expected_past_len = 2;
|
||||
}
|
||||
|
||||
// What the receiver's cache actually did.
|
||||
message CacheResult {
|
||||
CacheMode mode = 1;
|
||||
// Tokens held for this Shard's range after the step completed.
|
||||
uint64 past_len = 2;
|
||||
// True when session state was reused rather than rebuilt.
|
||||
bool cache_hit = 3;
|
||||
}
|
||||
|
||||
// Everything required to interpret one unit of work, independent of transport.
|
||||
//
|
||||
// Carried on every request and response because a relayed frame arrives as
|
||||
// opaque binary with no gRPC metadata, no deadline and no channel identity.
|
||||
message Envelope {
|
||||
SchemaVersion schema_version = 1;
|
||||
// Unique id for this unit of work; also the unit of billing attribution.
|
||||
string work_id = 2;
|
||||
// The Route Session this work belongs to. Together with `route_epoch` it maps
|
||||
// to exactly one isolated llama sequence / bounded context (ADR-0020).
|
||||
string route_session_id = 3;
|
||||
// Bumped by the control plane whenever the route changes. A worker refuses
|
||||
// work from a stale epoch rather than mixing it into live session state.
|
||||
uint64 route_epoch = 4;
|
||||
Fingerprint fingerprint = 5;
|
||||
ShardRange shard_range = 6;
|
||||
Phase phase = 7;
|
||||
PositionSpan position = 8;
|
||||
// Monotonic step within (route_session_id, route_epoch).
|
||||
//
|
||||
// Idempotency key: a retried or duplicated step carries the same value and
|
||||
// must be acknowledged, not re-applied. Re-applying a decode step would
|
||||
// advance the KV cache twice and desynchronise the route.
|
||||
uint64 idempotency_step = 9;
|
||||
CacheExpectation cache_expectation = 10;
|
||||
// Absolute deadline. Carried in-band so a relayed frame keeps its deadline;
|
||||
// on a direct hop it is set consistently with the gRPC deadline.
|
||||
int64 deadline_unix_nanos = 11;
|
||||
// Chunking, when this message is part of a bounded prefill.
|
||||
ChunkInfo chunk = 12;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Structured errors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Why a unit of work could not be executed as asked.
|
||||
//
|
||||
// These are semantic outcomes, distinct from transport failure. They travel as
|
||||
// a ShardStatus on the stream (and as google.rpc.Status details on unary RPCs)
|
||||
// so a relayed frame carries the same diagnosis a direct gRPC hop would.
|
||||
enum ErrorCode {
|
||||
ERROR_CODE_UNSPECIFIED = 0;
|
||||
// Peer cannot satisfy the requested SchemaVersion.
|
||||
ERROR_CODE_SCHEMA_UNSUPPORTED = 1;
|
||||
// Model artifact or runtime recipe digest differs from this worker's.
|
||||
ERROR_CODE_FINGERPRINT_MISMATCH = 2;
|
||||
// Work arrived for a route epoch the worker has already moved past.
|
||||
ERROR_CODE_EPOCH_STALE = 3;
|
||||
// Requested layer range is not the range this worker serves.
|
||||
ERROR_CODE_SHARD_RANGE_MISMATCH = 4;
|
||||
// Session state absent or positioned differently than expected (ADR-0022).
|
||||
// The head recovers with one full re-prefill; this is not a fatal error.
|
||||
ERROR_CODE_CACHE_MISS = 5;
|
||||
// Admission budget (weights, KV, scratch, queue depth) would be exceeded.
|
||||
ERROR_CODE_RESOURCE_EXHAUSTED = 6;
|
||||
// Payload failed its checksum, or fragments did not cover the tensor.
|
||||
ERROR_CODE_PAYLOAD_CORRUPT = 7;
|
||||
// Work was cancelled by the control plane or the peer.
|
||||
ERROR_CODE_CANCELLED = 8;
|
||||
// Deadline in the envelope had already passed when work was dequeued.
|
||||
ERROR_CODE_DEADLINE_EXCEEDED = 9;
|
||||
// Sender exceeded its granted flow-control credit.
|
||||
ERROR_CODE_FLOW_CONTROL_VIOLATION = 10;
|
||||
// The worker failed while executing; detail is sanitized.
|
||||
ERROR_CODE_INTERNAL = 11;
|
||||
}
|
||||
|
||||
message ShardError {
|
||||
ErrorCode code = 1;
|
||||
// Sanitized, operator-facing explanation. Never a raw exception, file path or
|
||||
// credential (ADR-0023 sanitization rule).
|
||||
string detail = 2;
|
||||
// True when the same work may be retried unchanged. A CACHE_MISS is not
|
||||
// retryable unchanged — the head must re-prefill first.
|
||||
bool retryable = 3;
|
||||
// Set on CACHE_MISS so the head knows where the receiver actually is.
|
||||
uint64 actual_past_len = 4;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flow control
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Application-level credit, layered on top of HTTP/2 flow control.
|
||||
//
|
||||
// HTTP/2 bounds bytes in flight; it does not bound how much *work* a worker has
|
||||
// queued, and a relayed frame gets no HTTP/2 window at all. Credits bound the
|
||||
// number of un-acked chunks a sender may have outstanding, which is what keeps
|
||||
// worker queues and KV pressure bounded and lets prefill avoid starving decode.
|
||||
message FlowControl {
|
||||
// Additional chunks the receiver is willing to accept beyond those already
|
||||
// granted. Purely additive: a grant never reduces outstanding credit.
|
||||
uint32 credits_granted = 1;
|
||||
// Hard ceiling on un-acked chunks, independent of granted credit.
|
||||
uint32 max_inflight_chunks = 2;
|
||||
// Hard ceiling on the serialized size of one chunk message.
|
||||
uint64 max_chunk_bytes = 3;
|
||||
// Hard ceiling on token positions per prefill chunk.
|
||||
uint32 max_prefill_chunk_tokens = 4;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Session stream messages
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Opens one long-lived bidirectional stream for one Route Session Activation
|
||||
// Seam. The handshake settles version, identity and the initial flow-control
|
||||
// window before any activation is sent, so an incompatible peer fails at open
|
||||
// rather than mid-generation.
|
||||
message SessionOpen {
|
||||
// Highest schema version the initiator supports; the acceptor replies with
|
||||
// the version actually negotiated.
|
||||
SchemaVersion schema_version = 1;
|
||||
string route_session_id = 2;
|
||||
uint64 route_epoch = 3;
|
||||
Fingerprint fingerprint = 4;
|
||||
ShardRange shard_range = 5;
|
||||
// Flow-control limits the initiator can honour. The acceptor replies with the
|
||||
// limits that actually apply.
|
||||
FlowControl proposed_flow_control = 6;
|
||||
// Compressions the initiator can decode. NONE is implicitly always supported.
|
||||
repeated Compression accepted_compression = 7;
|
||||
}
|
||||
|
||||
message SessionAccepted {
|
||||
// The version both peers will use for the life of this stream.
|
||||
SchemaVersion schema_version = 1;
|
||||
string route_session_id = 2;
|
||||
uint64 route_epoch = 3;
|
||||
// Authoritative limits. The initiator must not exceed these.
|
||||
FlowControl flow_control = 4;
|
||||
repeated Compression accepted_compression = 5;
|
||||
// Fingerprint the worker actually serves, so a mismatch is visible at open.
|
||||
Fingerprint fingerprint = 6;
|
||||
}
|
||||
|
||||
// One unit of activation work: a bounded prefill chunk or a decode step.
|
||||
message ActivationChunk {
|
||||
Envelope envelope = 1;
|
||||
TensorBundle bundle = 2;
|
||||
}
|
||||
|
||||
// The small decode fast path.
|
||||
//
|
||||
// A decode step is one token: the envelope's identity fields are already fixed
|
||||
// for the life of the stream, so repeating them per token is pure overhead on
|
||||
// the hottest path. A DecodeStep carries only what changes — the step, the
|
||||
// position, and one tensor — and inherits the rest from the SessionOpen
|
||||
// handshake. A peer may always fall back to ActivationChunk with PHASE_DECODE.
|
||||
message DecodeStep {
|
||||
// Idempotency step within the session; also orders the stream.
|
||||
uint64 idempotency_step = 1;
|
||||
// Absolute position of this token.
|
||||
uint64 position = 2;
|
||||
// Tokens the receiver's cache must already hold. A mismatch is a CACHE_MISS.
|
||||
uint64 expected_past_len = 3;
|
||||
// The single boundary tensor for this token, typically [1, 1, hidden].
|
||||
NamedTensor tensor = 4;
|
||||
// Work id for attribution; the session/epoch/fingerprint/range are inherited.
|
||||
string work_id = 5;
|
||||
int64 deadline_unix_nanos = 6;
|
||||
}
|
||||
|
||||
// Drop session state for a Route Session. Bounded memory does not depend on
|
||||
// this arriving — workers also evict by TTL and LRU (ADR-0022) — but an
|
||||
// explicit release returns KV immediately instead of holding it for the TTL.
|
||||
message ReleaseSignal {
|
||||
string route_session_id = 1;
|
||||
uint64 route_epoch = 2;
|
||||
string work_id = 3;
|
||||
}
|
||||
|
||||
// Abandon in-flight work.
|
||||
//
|
||||
// Cancellation must remain possible when the stream is wedged behind flow
|
||||
// control, which is why Cancel also exists as a unary RPC on a fresh call.
|
||||
message CancelSignal {
|
||||
string route_session_id = 1;
|
||||
uint64 route_epoch = 2;
|
||||
// Cancel one unit of work; empty cancels every in-flight unit for the session.
|
||||
string work_id = 3;
|
||||
string reason = 4;
|
||||
}
|
||||
|
||||
// Acknowledges a chunk or decode step, releasing its flow-control credit and
|
||||
// recording where the receiver's cache now stands.
|
||||
message Ack {
|
||||
string work_id = 1;
|
||||
uint64 idempotency_step = 2;
|
||||
CacheResult cache_result = 3;
|
||||
// True when the step was recognised as an already-applied duplicate and was
|
||||
// therefore acknowledged without being re-applied.
|
||||
bool duplicate = 4;
|
||||
// Observed execution time, for Generation Telemetry.
|
||||
uint64 execution_nanos = 5;
|
||||
}
|
||||
|
||||
// A terminal outcome for one unit of work, or for the session.
|
||||
message ShardStatus {
|
||||
string work_id = 1;
|
||||
string route_session_id = 2;
|
||||
uint64 idempotency_step = 3;
|
||||
ShardError error = 4;
|
||||
// True when the stream itself is finished and no further work will be served.
|
||||
bool terminal = 5;
|
||||
}
|
||||
|
||||
message SessionRequest {
|
||||
oneof kind {
|
||||
SessionOpen open = 1;
|
||||
ActivationChunk chunk = 2;
|
||||
DecodeStep decode = 3;
|
||||
FlowControl flow_control = 4;
|
||||
ReleaseSignal release = 5;
|
||||
CancelSignal cancel = 6;
|
||||
}
|
||||
}
|
||||
|
||||
message SessionResponse {
|
||||
oneof kind {
|
||||
SessionAccepted accepted = 1;
|
||||
// Result activations forwarded toward the next Shard, or to the head.
|
||||
ActivationChunk chunk = 2;
|
||||
Ack ack = 3;
|
||||
FlowControl flow_control = 4;
|
||||
ShardStatus status = 5;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Capability and health
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
message CapabilityRequest {
|
||||
SchemaVersion schema_version = 1;
|
||||
}
|
||||
|
||||
// What this worker can actually execute, proven by a bounded real forward
|
||||
// (ADR-0023). Detected hardware is not a capability; only a validated recipe is.
|
||||
message CapabilityReport {
|
||||
SchemaVersion schema_version = 1;
|
||||
Fingerprint fingerprint = 2;
|
||||
ShardRange shard_range = 3;
|
||||
// Backend/device identity, e.g. "rocm:gfx1151", "cpu:avx512".
|
||||
string backend = 4;
|
||||
string device = 5;
|
||||
// True only when a bounded real forward has passed for exactly this
|
||||
// (artifact, range, recipe, device). Never set from hardware detection alone.
|
||||
bool validated = 6;
|
||||
// Sanitized reason when `validated` is false.
|
||||
string detail = 7;
|
||||
// Admission budgets this worker will enforce.
|
||||
uint64 max_concurrent_sessions = 8;
|
||||
uint64 max_context_tokens = 9;
|
||||
FlowControl flow_control = 10;
|
||||
repeated Compression accepted_compression = 11;
|
||||
repeated SchemaVersion supported_schema_versions = 12;
|
||||
int64 validated_at_unix_nanos = 13;
|
||||
}
|
||||
|
||||
message HealthRequest {
|
||||
SchemaVersion schema_version = 1;
|
||||
}
|
||||
|
||||
enum ServingState {
|
||||
SERVING_STATE_UNSPECIFIED = 0;
|
||||
// Accepting new Route Sessions.
|
||||
SERVING_STATE_SERVING = 1;
|
||||
// Alive but refusing new sessions; existing sessions continue draining.
|
||||
SERVING_STATE_DRAINING = 2;
|
||||
// Alive but not routable — e.g. registered-but-dark pending certification.
|
||||
SERVING_STATE_NOT_SERVING = 3;
|
||||
}
|
||||
|
||||
// Live load, for backpressure and Generation Telemetry.
|
||||
message HealthReport {
|
||||
SchemaVersion schema_version = 1;
|
||||
ServingState state = 2;
|
||||
uint32 active_sessions = 3;
|
||||
uint32 queued_chunks = 4;
|
||||
// Occupancy of the continuous decode batch.
|
||||
uint32 batch_occupancy = 5;
|
||||
// Fraction of the KV budget in use, 0..1.
|
||||
float kv_pressure = 6;
|
||||
uint64 resident_bytes = 7;
|
||||
string detail = 8;
|
||||
}
|
||||
|
||||
message ReleaseRequest {
|
||||
SchemaVersion schema_version = 1;
|
||||
string route_session_id = 2;
|
||||
uint64 route_epoch = 3;
|
||||
}
|
||||
|
||||
message ReleaseResponse {
|
||||
// True when session state existed and was dropped; false when there was
|
||||
// nothing to drop, which is a success, not an error — release is idempotent.
|
||||
bool released = 1;
|
||||
ShardError error = 2;
|
||||
}
|
||||
|
||||
message CancelRequest {
|
||||
SchemaVersion schema_version = 1;
|
||||
string route_session_id = 2;
|
||||
uint64 route_epoch = 3;
|
||||
// Empty cancels every in-flight unit for the session.
|
||||
string work_id = 4;
|
||||
string reason = 5;
|
||||
}
|
||||
|
||||
message CancelResponse {
|
||||
uint32 cancelled_work_items = 1;
|
||||
ShardError error = 2;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Service
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
service ShardRuntime {
|
||||
// What this worker can execute. Read before a route is built.
|
||||
rpc GetCapability(CapabilityRequest) returns (CapabilityReport);
|
||||
|
||||
// Live load and serving state.
|
||||
rpc Health(HealthRequest) returns (HealthReport);
|
||||
|
||||
// One long-lived bidirectional stream per Route Session Activation Seam.
|
||||
//
|
||||
// The stream opens with SessionOpen/SessionAccepted, then carries bounded
|
||||
// prefill chunks and decode steps in both directions for the life of the
|
||||
// session. Per-token channel creation is a non-goal: the handshake cost is
|
||||
// paid once and the hot path carries only what changes.
|
||||
rpc Session(stream SessionRequest) returns (stream SessionResponse);
|
||||
|
||||
// Drop session state out of band. Idempotent.
|
||||
rpc Release(ReleaseRequest) returns (ReleaseResponse);
|
||||
|
||||
// Cancel out of band, on a fresh call.
|
||||
//
|
||||
// In-band CancelSignal is preferred, but a sender that is blocked on flow
|
||||
// control cannot write one — a cancel that can only travel down a wedged
|
||||
// stream is not a cancel. This RPC always has a path to the worker.
|
||||
rpc Cancel(CancelRequest) returns (CancelResponse);
|
||||
}
|
||||
2
packages/node/native/testdata/capability_report_golden.binpb
vendored
Normal file
2
packages/node/native/testdata/capability_report_golden.binpb
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
F
|
||||
sha256:1f0c9d2esha256:ab77e410llama-gguf-q4km-rocm"3* 2026.07.1"rocm*gfx11510@H€@R€€€ €Zbh€€Ð<E282AC>éθý
|
||||
BIN
packages/node/native/testdata/session_request_golden.binpb
vendored
Normal file
BIN
packages/node/native/testdata/session_request_golden.binpb
vendored
Normal file
Binary file not shown.
337
packages/node/native/tests/test_shard_protocol_conformance.cpp
Normal file
337
packages/node/native/tests/test_shard_protocol_conformance.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
// C++ conformance test for the native Shard protocol.
|
||||
//
|
||||
// This test does not check that C++ can round-trip its own output — that would
|
||||
// only prove C++ is self-consistent. It parses the *Python-produced* committed
|
||||
// vectors, asserts every field the protocol promises to carry, independently
|
||||
// recomputes the CRC32C over the reassembled tensor, and re-serializes the
|
||||
// message back out for the Python test to compare byte-for-byte. Only that
|
||||
// closes the loop: both languages agree on the same bytes and the same meaning.
|
||||
//
|
||||
// Run via ctest; see packages/node/native/CMakeLists.txt.
|
||||
|
||||
#include "shard_runtime.pb.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// `pb` is already taken by protobuf's own generated headers.
|
||||
namespace sp = meshnet::shard::v1;
|
||||
|
||||
namespace {
|
||||
|
||||
int g_failures = 0;
|
||||
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL " << __FILE__ << ":" << __LINE__ << ": " << #cond \
|
||||
<< "\n"; \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_EQ(actual, expected) \
|
||||
do { \
|
||||
auto &&a_ = (actual); \
|
||||
auto &&e_ = (expected); \
|
||||
if (!(a_ == e_)) { \
|
||||
std::cerr << "FAIL " << __FILE__ << ":" << __LINE__ << ": " << #actual \
|
||||
<< " == " << #expected << "\n actual: " << a_ \
|
||||
<< "\n expected: " << e_ << "\n"; \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Independent CRC32C (Castagnoli). Written from the polynomial rather than
|
||||
// shared with the Python side on purpose: a checksum that both languages
|
||||
// compute with the *same* code proves nothing about interoperability.
|
||||
uint32_t Crc32c(const std::string &data) {
|
||||
static uint32_t table[256];
|
||||
static bool built = false;
|
||||
if (!built) {
|
||||
for (uint32_t i = 0; i < 256; ++i) {
|
||||
uint32_t c = i;
|
||||
for (int k = 0; k < 8; ++k) {
|
||||
c = (c & 1) ? (c >> 1) ^ 0x82F63B78u : (c >> 1);
|
||||
}
|
||||
table[i] = c;
|
||||
}
|
||||
built = true;
|
||||
}
|
||||
uint32_t crc = 0xFFFFFFFFu;
|
||||
for (unsigned char byte : data) {
|
||||
crc = (crc >> 8) ^ table[(crc ^ byte) & 0xFF];
|
||||
}
|
||||
return crc ^ 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
std::string ReadFile(const std::filesystem::path &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
if (!in) {
|
||||
std::cerr << "FAIL cannot read " << path << "\n";
|
||||
++g_failures;
|
||||
return {};
|
||||
}
|
||||
std::ostringstream buffer;
|
||||
buffer << in.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
// Reassemble a tensor's fragments and validate coverage, exactly as a worker
|
||||
// must before it feeds the bytes to a forward pass.
|
||||
std::string ReassembleUncompressed(const sp::NamedTensor &tensor) {
|
||||
std::string body;
|
||||
std::vector<const sp::TensorFragment *> fragments;
|
||||
for (const auto &fragment : tensor.fragments()) {
|
||||
fragments.push_back(&fragment);
|
||||
}
|
||||
CHECK(!fragments.empty());
|
||||
for (uint32_t index = 0; index < fragments.size(); ++index) {
|
||||
const sp::TensorFragment *found = nullptr;
|
||||
for (const auto *fragment : fragments) {
|
||||
if (fragment->fragment_index() == index) {
|
||||
found = fragment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found != nullptr);
|
||||
if (found == nullptr) {
|
||||
return {};
|
||||
}
|
||||
CHECK_EQ(found->fragment_count(),
|
||||
static_cast<uint32_t>(fragments.size()));
|
||||
// Offsets must tile the wire body exactly: no hole, no overlap.
|
||||
CHECK_EQ(found->byte_offset(), static_cast<uint64_t>(body.size()));
|
||||
body.append(found->payload());
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
void CheckFingerprint(const sp::Fingerprint &fingerprint) {
|
||||
CHECK_EQ(fingerprint.model_artifact_digest(), std::string("sha256:1f0c9d2e"));
|
||||
CHECK_EQ(fingerprint.runtime_recipe_digest(), std::string("sha256:ab77e410"));
|
||||
CHECK_EQ(fingerprint.recipe_id(), std::string("llama-gguf-q4km-rocm"));
|
||||
CHECK_EQ(fingerprint.recipe_version(), std::string("3"));
|
||||
CHECK_EQ(fingerprint.catalogue_version(), std::string("2026.07.1"));
|
||||
}
|
||||
|
||||
// The canonical session request, as produced by Python.
|
||||
void TestSessionRequestVector(const std::string &bytes) {
|
||||
sp::SessionRequest request;
|
||||
CHECK(request.ParseFromString(bytes));
|
||||
CHECK(request.kind_case() == sp::SessionRequest::kChunk);
|
||||
|
||||
const sp::ActivationChunk &chunk = request.chunk();
|
||||
const sp::Envelope &envelope = chunk.envelope();
|
||||
|
||||
// Every field the protocol promises to carry (acceptance criterion 4).
|
||||
CHECK_EQ(envelope.schema_version(), sp::SCHEMA_VERSION_1);
|
||||
CHECK_EQ(envelope.work_id(), std::string("work-7f3a"));
|
||||
CHECK_EQ(envelope.route_session_id(), std::string("rs-2b91"));
|
||||
CHECK_EQ(envelope.route_epoch(), 7u);
|
||||
CheckFingerprint(envelope.fingerprint());
|
||||
CHECK_EQ(envelope.shard_range().start_layer(), 12u);
|
||||
CHECK_EQ(envelope.shard_range().end_layer(), 24u);
|
||||
// Overlap-safe effective start (ADR-0012): a worker that begins at
|
||||
// start_layer instead would re-apply layers 12..15.
|
||||
CHECK_EQ(envelope.shard_range().effective_start_layer(), 16u);
|
||||
CHECK_EQ(envelope.phase(), sp::PHASE_PREFILL);
|
||||
CHECK_EQ(envelope.position().first_position(), 256u);
|
||||
CHECK_EQ(envelope.position().token_count(), 128u);
|
||||
CHECK_EQ(envelope.idempotency_step(), 42u);
|
||||
CHECK_EQ(envelope.cache_expectation().mode(), sp::CACHE_MODE_PREFILL);
|
||||
CHECK_EQ(envelope.cache_expectation().expected_past_len(), 256u);
|
||||
CHECK_EQ(envelope.deadline_unix_nanos(), 1800000000000000000LL);
|
||||
CHECK_EQ(envelope.chunk().chunk_index(), 1u);
|
||||
CHECK_EQ(envelope.chunk().chunk_count(), 3u);
|
||||
CHECK_EQ(envelope.chunk().final_chunk(), false);
|
||||
|
||||
// The versioned named-tensor bundle (acceptance criterion 5).
|
||||
const sp::TensorBundle &bundle = chunk.bundle();
|
||||
CHECK_EQ(bundle.bundle_version(), 1u);
|
||||
CHECK_EQ(bundle.tensors_size(), 1);
|
||||
|
||||
const sp::NamedTensor &tensor = bundle.tensors(0);
|
||||
CHECK_EQ(tensor.name(), std::string("hidden_states"));
|
||||
CHECK_EQ(tensor.shape_size(), 3);
|
||||
CHECK_EQ(tensor.shape(0), 1);
|
||||
CHECK_EQ(tensor.shape(1), 128);
|
||||
CHECK_EQ(tensor.shape(2), 8);
|
||||
CHECK_EQ(tensor.dtype(), sp::DTYPE_BFLOAT16);
|
||||
CHECK_EQ(tensor.byte_order(), sp::BYTE_ORDER_LITTLE_ENDIAN);
|
||||
CHECK_EQ(tensor.total_bytes(), 1u * 128u * 8u * 2u);
|
||||
CHECK_EQ(tensor.compression(), sp::COMPRESSION_NONE);
|
||||
|
||||
// Multi-fragment on purpose: reassembly is what a worker actually does.
|
||||
CHECK(tensor.fragments_size() > 1);
|
||||
const std::string payload = ReassembleUncompressed(tensor);
|
||||
CHECK_EQ(payload.size(), static_cast<size_t>(tensor.total_bytes()));
|
||||
|
||||
// The payload Python generated, recomputed here from its rule.
|
||||
std::string expected;
|
||||
expected.reserve(payload.size());
|
||||
for (size_t i = 0; i < payload.size(); ++i) {
|
||||
expected.push_back(static_cast<char>((i * 7 + 11) % 256));
|
||||
}
|
||||
CHECK(payload == expected);
|
||||
|
||||
// Checksum: computed by Python, verified by an independent C++ CRC32C.
|
||||
CHECK_EQ(tensor.checksum().algorithm(), sp::CHECKSUM_ALGORITHM_CRC32C);
|
||||
const std::string &checksum = tensor.checksum().value();
|
||||
CHECK_EQ(checksum.size(), 4u);
|
||||
if (checksum.size() == 4) {
|
||||
const uint32_t actual = Crc32c(payload);
|
||||
const uint32_t declared =
|
||||
(static_cast<uint32_t>(static_cast<unsigned char>(checksum[0])) << 24) |
|
||||
(static_cast<uint32_t>(static_cast<unsigned char>(checksum[1])) << 16) |
|
||||
(static_cast<uint32_t>(static_cast<unsigned char>(checksum[2])) << 8) |
|
||||
static_cast<uint32_t>(static_cast<unsigned char>(checksum[3]));
|
||||
CHECK_EQ(actual, declared);
|
||||
}
|
||||
}
|
||||
|
||||
void TestCapabilityReportVector(const std::string &bytes) {
|
||||
sp::CapabilityReport report;
|
||||
CHECK(report.ParseFromString(bytes));
|
||||
CHECK_EQ(report.schema_version(), sp::SCHEMA_VERSION_1);
|
||||
CheckFingerprint(report.fingerprint());
|
||||
CHECK_EQ(report.backend(), std::string("rocm"));
|
||||
CHECK_EQ(report.device(), std::string("gfx1151"));
|
||||
CHECK_EQ(report.validated(), true);
|
||||
CHECK_EQ(report.max_concurrent_sessions(), 4u);
|
||||
CHECK_EQ(report.max_context_tokens(), 8192u);
|
||||
CHECK_EQ(report.flow_control().max_prefill_chunk_tokens(), 128u);
|
||||
CHECK_EQ(report.flow_control().max_chunk_bytes(), 4u * 1024u * 1024u);
|
||||
CHECK_EQ(report.accepted_compression_size(), 2);
|
||||
CHECK_EQ(report.supported_schema_versions_size(), 1);
|
||||
}
|
||||
|
||||
// Forward compatibility: a field this build has never heard of must survive a
|
||||
// parse/serialize cycle untouched. Without this, an old Shard silently strips
|
||||
// fields a newer peer depends on as it forwards activations down the route.
|
||||
void TestUnknownFieldsArePreserved(const std::string &bytes) {
|
||||
// Field 9999, wire type 0 (varint), value 12345 — not in this schema.
|
||||
std::string forward = bytes;
|
||||
forward.push_back(static_cast<char>(0xB8)); // tag: (9999 << 3) | 0
|
||||
forward.push_back(static_cast<char>(0xE0));
|
||||
forward.push_back(static_cast<char>(0x04));
|
||||
forward.push_back(static_cast<char>(0xB9)); // value: 12345
|
||||
forward.push_back(static_cast<char>(0x60));
|
||||
|
||||
sp::SessionRequest request;
|
||||
CHECK(request.ParseFromString(forward));
|
||||
// Known fields still parse.
|
||||
CHECK_EQ(request.chunk().envelope().work_id(), std::string("work-7f3a"));
|
||||
// And the unknown field is retained rather than dropped.
|
||||
CHECK(!request.GetReflection()->GetUnknownFields(request).empty());
|
||||
|
||||
std::string reserialized;
|
||||
CHECK(request.SerializeToString(&reserialized));
|
||||
CHECK_EQ(reserialized.size(), forward.size());
|
||||
|
||||
sp::SessionRequest reparsed;
|
||||
CHECK(reparsed.ParseFromString(reserialized));
|
||||
CHECK(!reparsed.GetReflection()->GetUnknownFields(reparsed).empty());
|
||||
}
|
||||
|
||||
// Backward compatibility: a message from a peer that sets none of the optional
|
||||
// groups must still parse, yielding proto3 defaults rather than an error.
|
||||
void TestSparseMessageParses() {
|
||||
sp::SessionRequest minimal;
|
||||
minimal.mutable_chunk()->mutable_envelope()->set_work_id("w");
|
||||
std::string bytes;
|
||||
CHECK(minimal.SerializeToString(&bytes));
|
||||
|
||||
sp::SessionRequest parsed;
|
||||
CHECK(parsed.ParseFromString(bytes));
|
||||
CHECK_EQ(parsed.chunk().envelope().work_id(), std::string("w"));
|
||||
CHECK_EQ(parsed.chunk().envelope().route_epoch(), 0u);
|
||||
CHECK_EQ(parsed.chunk().envelope().phase(), sp::PHASE_UNSPECIFIED);
|
||||
CHECK_EQ(parsed.chunk().bundle().tensors_size(), 0);
|
||||
}
|
||||
|
||||
// The decode fast path must stay small: repeating the full envelope per token
|
||||
// is pure overhead on the hottest path in the system.
|
||||
void TestDecodeFastPathIsSmall() {
|
||||
sp::SessionRequest decode;
|
||||
sp::DecodeStep *step = decode.mutable_decode();
|
||||
step->set_idempotency_step(9);
|
||||
step->set_position(1024);
|
||||
step->set_expected_past_len(1024);
|
||||
step->set_work_id("work-7f3a");
|
||||
sp::NamedTensor *tensor = step->mutable_tensor();
|
||||
tensor->set_name("hidden_states");
|
||||
tensor->add_shape(1);
|
||||
tensor->add_shape(1);
|
||||
tensor->add_shape(8);
|
||||
tensor->set_dtype(sp::DTYPE_BFLOAT16);
|
||||
tensor->set_byte_order(sp::BYTE_ORDER_LITTLE_ENDIAN);
|
||||
tensor->set_total_bytes(16);
|
||||
tensor->set_compression(sp::COMPRESSION_NONE);
|
||||
sp::TensorFragment *fragment = tensor->add_fragments();
|
||||
fragment->set_fragment_index(0);
|
||||
fragment->set_fragment_count(1);
|
||||
fragment->set_byte_offset(0);
|
||||
fragment->set_payload(std::string(16, '\x01'));
|
||||
|
||||
std::string bytes;
|
||||
CHECK(decode.SerializeToString(&bytes));
|
||||
// Payload is 16 bytes; the framing around it must stay well under the
|
||||
// envelope-carrying prefill path.
|
||||
CHECK(bytes.size() < 96);
|
||||
|
||||
sp::SessionRequest parsed;
|
||||
CHECK(parsed.ParseFromString(bytes));
|
||||
CHECK(parsed.kind_case() == sp::SessionRequest::kDecode);
|
||||
CHECK_EQ(parsed.decode().position(), 1024u);
|
||||
CHECK_EQ(parsed.decode().tensor().total_bytes(), 16u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
||||
if (argc < 3) {
|
||||
std::cerr << "usage: " << argv[0] << " <testdata-dir> <output-dir>\n";
|
||||
return 2;
|
||||
}
|
||||
const std::filesystem::path testdata(argv[1]);
|
||||
const std::filesystem::path output(argv[2]);
|
||||
|
||||
const std::string session_bytes =
|
||||
ReadFile(testdata / "session_request_golden.binpb");
|
||||
const std::string capability_bytes =
|
||||
ReadFile(testdata / "capability_report_golden.binpb");
|
||||
|
||||
TestSessionRequestVector(session_bytes);
|
||||
TestCapabilityReportVector(capability_bytes);
|
||||
TestUnknownFieldsArePreserved(session_bytes);
|
||||
TestSparseMessageParses();
|
||||
TestDecodeFastPathIsSmall();
|
||||
|
||||
// Re-serialize the canonical message from the C++ object model and hand it
|
||||
// back for Python to compare against the golden bytes. If the two languages
|
||||
// disagreed about any field's encoding, this file would differ.
|
||||
sp::SessionRequest request;
|
||||
if (request.ParseFromString(session_bytes)) {
|
||||
std::string reserialized;
|
||||
if (request.SerializeToString(&reserialized)) {
|
||||
std::ofstream out(output / "cpp_roundtrip.binpb", std::ios::binary);
|
||||
out.write(reserialized.data(),
|
||||
static_cast<std::streamsize>(reserialized.size()));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_failures != 0) {
|
||||
std::cerr << g_failures << " check(s) failed\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "all C++ conformance checks passed\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -13,6 +13,11 @@ dependencies = [
|
||||
"huggingface-hub>=0.20",
|
||||
"accelerate>=0.28",
|
||||
"bitsandbytes>=0.43",
|
||||
# Keep these floors aligned with the committed grpcio-tools 1.82.1 output.
|
||||
# Older runtimes satisfy broad constraints but fail while importing the
|
||||
# generated modules before a node can report a useful protocol error.
|
||||
"grpcio>=1.82.1",
|
||||
"protobuf>=7.35.0",
|
||||
"rich>=13",
|
||||
"safetensors>=0.4",
|
||||
"torch>=2.1",
|
||||
@@ -23,6 +28,11 @@ dependencies = [
|
||||
"kernels>=0.11.1,<0.16",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# Regenerating the native Shard protocol stubs. Not needed to run a node: the
|
||||
# generated modules are committed, and `grpcio-tools` bundles its own protoc.
|
||||
proto = ["grpcio-tools==1.82.1"]
|
||||
|
||||
[project.scripts]
|
||||
meshnet-node = "meshnet_node.cli:main"
|
||||
|
||||
|
||||
66
scripts/bootstrap_native_toolchain.sh
Normal file
66
scripts/bootstrap_native_toolchain.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a protobuf C++ toolchain for the native Shard protocol.
|
||||
#
|
||||
# The Python side needs nothing beyond `pip install grpcio-tools` — it bundles
|
||||
# protoc. The C++ side needs libprotobuf headers and a protoc binary, and a
|
||||
# machine that has neither (no protobuf-devel, no cmake, no system protoc) can
|
||||
# still get a working one from source with this script. It is the exact recipe
|
||||
# DGR-002 used to build and run the C++ conformance test.
|
||||
#
|
||||
# gRPC C++ is deliberately NOT built here. The conformance test only needs
|
||||
# message types, so verifying the schema does not require the whole gRPC stack.
|
||||
# The worker (DGR-008) will need gRPC C++ and should extend this script then.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/bootstrap_native_toolchain.sh [install-prefix]
|
||||
#
|
||||
# Then:
|
||||
# cmake -S packages/node/native -B build/native -DCMAKE_PREFIX_PATH=<prefix>
|
||||
# cmake --build build/native -j
|
||||
# ctest --test-dir build/native --output-on-failure
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PREFIX="${1:-${PWD}/build/native-toolchain}"
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
|
||||
# Pinned: the C++ runtime a generated stub is compiled against must be a version
|
||||
# that stub is allowed to use, so these are exact, not floating.
|
||||
PROTOBUF_VERSION="33.1"
|
||||
ABSEIL_VERSION="20250814.1"
|
||||
|
||||
command -v cmake >/dev/null || {
|
||||
echo "cmake is required (pip install cmake==4.4.0)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "--- fetching protobuf ${PROTOBUF_VERSION} and abseil ${ABSEIL_VERSION}"
|
||||
cd "${WORK}"
|
||||
curl -sfL -o protobuf.tar.gz \
|
||||
"https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
tar xzf protobuf.tar.gz
|
||||
|
||||
# The protobuf release tarball ships utf8_range but not abseil, and its default
|
||||
# CMake provider expects abseil as a submodule, so vendor it into place.
|
||||
curl -sfL -o abseil.tar.gz \
|
||||
"https://github.com/abseil/abseil-cpp/releases/download/${ABSEIL_VERSION}/abseil-cpp-${ABSEIL_VERSION}.tar.gz"
|
||||
tar xzf abseil.tar.gz
|
||||
rm -rf "protobuf-${PROTOBUF_VERSION}/third_party/abseil-cpp"
|
||||
mv "abseil-cpp-${ABSEIL_VERSION}" "protobuf-${PROTOBUF_VERSION}/third_party/abseil-cpp"
|
||||
|
||||
echo "--- building protobuf into ${PREFIX}"
|
||||
cmake -S "protobuf-${PROTOBUF_VERSION}" -B build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PREFIX}" \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-Dprotobuf_ABSL_PROVIDER=module \
|
||||
-Dprotobuf_BUILD_TESTS=OFF \
|
||||
-Dprotobuf_BUILD_SHARED_LIBS=OFF \
|
||||
-DABSL_PROPAGATE_CXX_STD=ON
|
||||
cmake --build build -j"$(nproc)"
|
||||
cmake --install build
|
||||
|
||||
echo "--- done"
|
||||
"${PREFIX}/bin/protoc" --version
|
||||
echo "configure the protocol build with: -DCMAKE_PREFIX_PATH=${PREFIX}"
|
||||
125
scripts/generate_native_protocol.py
Normal file
125
scripts/generate_native_protocol.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the Python Shard-protocol stubs from `shard_runtime.proto`.
|
||||
|
||||
The `.proto` file is the contract; the Python modules under
|
||||
`meshnet_node/native_protocol/generated/` are build output that happens to be
|
||||
committed. They are committed so that installing the node does not require a
|
||||
protoc toolchain, and `--check` exists so a committed stub can never silently
|
||||
drift from the schema it claims to implement.
|
||||
|
||||
Usage::
|
||||
|
||||
python scripts/generate_native_protocol.py # regenerate in place
|
||||
python scripts/generate_native_protocol.py --check # fail if out of date
|
||||
|
||||
C++ stubs are *not* generated here. They are build artifacts produced by CMake
|
||||
(`packages/node/native/CMakeLists.txt`) into the build tree, because a C++ build
|
||||
already requires a toolchain and nothing is gained by committing them.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
PROTO_DIR = REPO_ROOT / "packages/node/native/proto"
|
||||
PROTO_FILE = PROTO_DIR / "shard_runtime.proto"
|
||||
OUT_DIR = REPO_ROOT / "packages/node/meshnet_node/native_protocol/generated"
|
||||
|
||||
# Regenerating with a different protoc emits different gencode headers, so the
|
||||
# generator version is part of the contract and `--check` would catch a drift.
|
||||
REQUIRED_GRPCIO_TOOLS = "1.82.1"
|
||||
|
||||
_HEADER = "# Generated by scripts/generate_native_protocol.py. Do not edit.\n"
|
||||
|
||||
|
||||
def _generate(into: pathlib.Path) -> None:
|
||||
"""Run protoc, writing generated modules into `into`."""
|
||||
try:
|
||||
from grpc_tools import protoc
|
||||
except ImportError: # pragma: no cover - exercised only without the toolchain
|
||||
sys.exit(
|
||||
"grpc_tools is required to generate stubs:\n"
|
||||
f" pip install grpcio-tools=={REQUIRED_GRPCIO_TOOLS}"
|
||||
)
|
||||
|
||||
into.mkdir(parents=True, exist_ok=True)
|
||||
# grpc_tools bundles protoc and the well-known types, so generation needs no
|
||||
# system protoc and produces identical output on any machine.
|
||||
well_known = pathlib.Path(protoc.__file__).parent / "_proto"
|
||||
args = [
|
||||
"protoc",
|
||||
f"--proto_path={PROTO_DIR}",
|
||||
f"--proto_path={well_known}",
|
||||
f"--python_out={into}",
|
||||
f"--pyi_out={into}",
|
||||
f"--grpc_python_out={into}",
|
||||
str(PROTO_FILE),
|
||||
]
|
||||
if protoc.main(args) != 0:
|
||||
sys.exit("protoc failed")
|
||||
|
||||
# protoc emits `import shard_runtime_pb2` — a bare top-level import that only
|
||||
# resolves if the generated directory happens to be on sys.path. Rewrite it
|
||||
# to a relative import so the package works as an installed package.
|
||||
grpc_module = into / "shard_runtime_pb2_grpc.py"
|
||||
text = grpc_module.read_text()
|
||||
text = text.replace(
|
||||
"import shard_runtime_pb2 as shard__runtime__pb2",
|
||||
"from . import shard_runtime_pb2 as shard__runtime__pb2",
|
||||
)
|
||||
grpc_module.write_text(text)
|
||||
|
||||
(into / "__init__.py").write_text(
|
||||
_HEADER + '"""Generated protobuf/gRPC stubs for the native Shard protocol."""\n'
|
||||
)
|
||||
|
||||
|
||||
def _tracked_files(directory: pathlib.Path) -> dict[str, bytes]:
|
||||
return {
|
||||
path.name: path.read_bytes()
|
||||
for path in sorted(directory.iterdir())
|
||||
if path.is_file() and path.suffix in {".py", ".pyi"}
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="verify the committed stubs match the .proto instead of rewriting them",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.check:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
fresh = pathlib.Path(tmp) / "generated"
|
||||
_generate(fresh)
|
||||
if not OUT_DIR.is_dir():
|
||||
print(f"generated stubs are missing: {OUT_DIR}", file=sys.stderr)
|
||||
return 1
|
||||
if _tracked_files(fresh) != _tracked_files(OUT_DIR):
|
||||
print(
|
||||
"generated stubs are out of date with shard_runtime.proto.\n"
|
||||
"Run: python scripts/generate_native_protocol.py",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
print("generated stubs are up to date")
|
||||
return 0
|
||||
|
||||
if OUT_DIR.exists():
|
||||
shutil.rmtree(OUT_DIR)
|
||||
_generate(OUT_DIR)
|
||||
print(f"wrote {OUT_DIR.relative_to(REPO_ROOT)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
71
scripts/generate_protocol_goldens.py
Normal file
71
scripts/generate_protocol_goldens.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write the committed cross-language conformance vectors.
|
||||
|
||||
The bytes under `packages/node/native/testdata/` are the reference both the
|
||||
Python and the C++ conformance tests assert against. They are committed so the
|
||||
C++ test can run without a Python step, and `--check` exists so they can never
|
||||
drift from the schema unnoticed: if a schema edit changes the canonical
|
||||
message's encoding, `--check` fails and the change has to be acknowledged.
|
||||
|
||||
Usage::
|
||||
|
||||
python scripts/generate_protocol_goldens.py # rewrite vectors
|
||||
python scripts/generate_protocol_goldens.py --check # fail if stale
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO_ROOT / "packages/node"))
|
||||
|
||||
from meshnet_node.native_protocol import conformance # noqa: E402
|
||||
|
||||
|
||||
def _vectors() -> dict[str, bytes]:
|
||||
return {
|
||||
conformance.GOLDEN_SESSION_REQUEST: conformance.serialize(
|
||||
conformance.canonical_session_request()
|
||||
),
|
||||
conformance.GOLDEN_CAPABILITY_REPORT: conformance.serialize(
|
||||
conformance.canonical_capability_report()
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
out_dir = conformance.TESTDATA_DIR
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
stale = []
|
||||
|
||||
for name, payload in _vectors().items():
|
||||
path = out_dir / name
|
||||
if args.check:
|
||||
if not path.is_file() or path.read_bytes() != payload:
|
||||
stale.append(name)
|
||||
continue
|
||||
path.write_bytes(payload)
|
||||
print(f"wrote {path.relative_to(REPO_ROOT)} ({len(payload)} bytes)")
|
||||
|
||||
if stale:
|
||||
print(
|
||||
"conformance vectors are stale: " + ", ".join(stale) + "\n"
|
||||
"The canonical message no longer encodes to the committed bytes. If "
|
||||
"that is intended, run: python scripts/generate_protocol_goldens.py",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
if args.check:
|
||||
print("conformance vectors are up to date")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
577
tests/test_native_shard_protocol.py
Normal file
577
tests/test_native_shard_protocol.py
Normal file
@@ -0,0 +1,577 @@
|
||||
"""Conformance tests for the native Shard protocol (ADR-0020, DGR-002).
|
||||
|
||||
Three layers are tested, and they are not the same thing:
|
||||
|
||||
1. The *schema* — asserted against the descriptor, not against the Python
|
||||
helpers. If a field the protocol promises to carry were dropped from the
|
||||
`.proto`, a test that only exercised the codec would still pass.
|
||||
2. The *codec* — that a payload which is corrupt, short, holed, or byte-swapped
|
||||
is rejected rather than fed to a forward pass.
|
||||
3. *Compatibility* — that an old build preserves fields a newer peer added, and
|
||||
that the committed cross-language vectors still encode as promised.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("google.protobuf", reason="protobuf runtime is required")
|
||||
|
||||
from google.protobuf import descriptor_pb2
|
||||
|
||||
from meshnet_node.activation_compression import CompressionPolicy
|
||||
from meshnet_node.native_protocol import (
|
||||
DEFAULT_MAX_CHUNK_BYTES,
|
||||
DEFAULT_MAX_FRAGMENTS_PER_TENSOR,
|
||||
DEFAULT_MAX_PREFILL_CHUNK_TOKENS,
|
||||
DEFAULT_MAX_TENSORS_PER_BUNDLE,
|
||||
HIDDEN_STATES,
|
||||
PayloadCorrupt,
|
||||
ProtocolError,
|
||||
checksum_of,
|
||||
decode_bundle,
|
||||
decode_tensor,
|
||||
default_flow_control,
|
||||
encode_bundle,
|
||||
encode_tensor,
|
||||
negotiate_flow_control,
|
||||
pb,
|
||||
plan_prefill_chunks,
|
||||
validate_session_message_size,
|
||||
)
|
||||
from meshnet_node.native_protocol import conformance
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
CPP_ROUNDTRIP = REPO_ROOT / "build/native" / conformance.CPP_ROUNDTRIP
|
||||
|
||||
|
||||
# --- The schema itself ------------------------------------------------------
|
||||
|
||||
|
||||
def test_service_exposes_capability_health_session_release_and_cancel():
|
||||
service = pb.DESCRIPTOR.services_by_name["ShardRuntime"]
|
||||
assert set(service.methods_by_name) == {
|
||||
"GetCapability",
|
||||
"Health",
|
||||
"Session",
|
||||
"Release",
|
||||
"Cancel",
|
||||
}
|
||||
|
||||
|
||||
def test_session_is_one_long_lived_bidirectional_stream():
|
||||
session = pb.DESCRIPTOR.services_by_name["ShardRuntime"].methods_by_name["Session"]
|
||||
assert session.client_streaming, "the seam must stream requests"
|
||||
assert session.server_streaming, "the seam must stream responses"
|
||||
|
||||
# Cancellation must not have to travel down a stream that flow control has
|
||||
# wedged, so it also exists as its own unary call.
|
||||
cancel = pb.DESCRIPTOR.services_by_name["ShardRuntime"].methods_by_name["Cancel"]
|
||||
assert not cancel.client_streaming and not cancel.server_streaming
|
||||
|
||||
|
||||
def test_envelope_carries_every_field_the_protocol_promises():
|
||||
# Asserted against the descriptor: this is the acceptance criterion, and it
|
||||
# must fail if the .proto drops a field, not merely if the codec stops
|
||||
# setting one.
|
||||
fields = set(pb.Envelope.DESCRIPTOR.fields_by_name)
|
||||
assert {
|
||||
"schema_version",
|
||||
"work_id",
|
||||
"route_session_id",
|
||||
"route_epoch",
|
||||
"fingerprint",
|
||||
"shard_range",
|
||||
"phase",
|
||||
"position",
|
||||
"idempotency_step",
|
||||
"cache_expectation",
|
||||
"deadline_unix_nanos",
|
||||
"chunk",
|
||||
} <= fields
|
||||
|
||||
assert {"model_artifact_digest", "runtime_recipe_digest"} <= set(
|
||||
pb.Fingerprint.DESCRIPTOR.fields_by_name
|
||||
)
|
||||
# Overlap-safe start (ADR-0012) is a distinct field from the registered one.
|
||||
assert {"start_layer", "end_layer", "effective_start_layer"} <= set(
|
||||
pb.ShardRange.DESCRIPTOR.fields_by_name
|
||||
)
|
||||
assert {"mode", "expected_past_len"} <= set(
|
||||
pb.CacheExpectation.DESCRIPTOR.fields_by_name
|
||||
)
|
||||
|
||||
|
||||
def test_named_tensor_bundle_is_versioned_and_fully_described():
|
||||
assert "bundle_version" in pb.TensorBundle.DESCRIPTOR.fields_by_name
|
||||
assert {"name", "shape", "dtype", "byte_order", "compression", "checksum", "fragments"} <= set(
|
||||
pb.NamedTensor.DESCRIPTOR.fields_by_name
|
||||
)
|
||||
|
||||
|
||||
def test_phases_and_error_codes_cover_the_lifecycle():
|
||||
assert {"PHASE_PREFILL", "PHASE_DECODE", "PHASE_RELEASE", "PHASE_CANCEL"} <= set(
|
||||
pb.Phase.keys()
|
||||
)
|
||||
# A cache miss is a first-class, recoverable outcome (ADR-0022), not a crash.
|
||||
assert {
|
||||
"ERROR_CODE_CACHE_MISS",
|
||||
"ERROR_CODE_FINGERPRINT_MISMATCH",
|
||||
"ERROR_CODE_EPOCH_STALE",
|
||||
"ERROR_CODE_SCHEMA_UNSUPPORTED",
|
||||
"ERROR_CODE_DEADLINE_EXCEEDED",
|
||||
"ERROR_CODE_FLOW_CONTROL_VIOLATION",
|
||||
} <= set(pb.ErrorCode.keys())
|
||||
|
||||
|
||||
# --- Tensor bundle round-trip ----------------------------------------------
|
||||
|
||||
|
||||
def test_tensor_round_trips_through_fragments():
|
||||
payload = bytes(range(256)) * 4
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES, payload, [1, 64, 8], pb.DTYPE_BFLOAT16, max_fragment_bytes=100
|
||||
)
|
||||
|
||||
assert len(tensor.fragments) > 1, "the bound must actually split the payload"
|
||||
assert tensor.total_bytes == len(payload)
|
||||
assert decode_tensor(tensor) == payload
|
||||
|
||||
|
||||
def test_bundle_round_trips_multiple_named_tensors():
|
||||
# An architecture boundary may need more than one tensor; that is why the
|
||||
# payload is a named bundle rather than a bare buffer.
|
||||
hidden = encode_tensor(HIDDEN_STATES, b"\x01\x02" * 8, [1, 8, 1], pb.DTYPE_BFLOAT16)
|
||||
positions = encode_tensor(
|
||||
"position_ids", (7).to_bytes(4, "little") * 8, [8], pb.DTYPE_INT32
|
||||
)
|
||||
bundle = encode_bundle([hidden, positions])
|
||||
|
||||
restored = decode_bundle(pb.TensorBundle.FromString(bundle.SerializeToString()))
|
||||
assert restored == {
|
||||
HIDDEN_STATES: b"\x01\x02" * 8,
|
||||
"position_ids": (7).to_bytes(4, "little") * 8,
|
||||
}
|
||||
|
||||
|
||||
def test_compressed_tensor_round_trips_and_keeps_its_uncompressed_checksum():
|
||||
pytest.importorskip("zstandard")
|
||||
# Highly compressible, and over the policy's minimum input size.
|
||||
payload = b"\x00" * 65536
|
||||
always = CompressionPolicy(min_input_bytes=0, min_savings_bytes=0, min_savings_ratio=0.0)
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES, payload, [1, 4096, 8], pb.DTYPE_BFLOAT16, policy=always
|
||||
)
|
||||
|
||||
assert tensor.compression == pb.COMPRESSION_ZSTD
|
||||
assert sum(len(f.payload) for f in tensor.fragments) < len(payload)
|
||||
# The checksum covers the uncompressed bytes, so it stays valid whether or
|
||||
# not a hop chose to compress.
|
||||
assert tensor.checksum == checksum_of(payload)
|
||||
assert decode_tensor(tensor) == payload
|
||||
|
||||
|
||||
# --- The codec refuses what it cannot account for --------------------------
|
||||
|
||||
|
||||
def test_corrupt_payload_is_rejected_by_checksum():
|
||||
tensor = encode_tensor(HIDDEN_STATES, b"\xaa" * 32, [1, 16, 1], pb.DTYPE_BFLOAT16)
|
||||
# Flip one byte, as a lossy relay or a bad NIC would.
|
||||
tensor.fragments[0].payload = b"\xab" + tensor.fragments[0].payload[1:]
|
||||
|
||||
with pytest.raises(PayloadCorrupt, match="CRC32C"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_missing_fragment_is_rejected_rather_than_silently_truncated():
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES, b"\xaa" * 64, [1, 32, 1], pb.DTYPE_BFLOAT16, max_fragment_bytes=16
|
||||
)
|
||||
del tensor.fragments[1]
|
||||
|
||||
with pytest.raises(PayloadCorrupt):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_fragment_hole_is_rejected():
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES, b"\xaa" * 64, [1, 32, 1], pb.DTYPE_BFLOAT16, max_fragment_bytes=16
|
||||
)
|
||||
# A gap in coverage: offsets no longer tile the body.
|
||||
tensor.fragments[2].byte_offset += 4
|
||||
|
||||
with pytest.raises(PayloadCorrupt, match="expected"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_shape_that_disagrees_with_payload_is_rejected_at_encode():
|
||||
with pytest.raises(ProtocolError, match="carries"):
|
||||
encode_tensor(HIDDEN_STATES, b"\x01\x02", [1, 8, 8], pb.DTYPE_BFLOAT16)
|
||||
|
||||
|
||||
def test_shape_that_disagrees_with_declared_bytes_is_rejected_at_decode():
|
||||
tensor = encode_tensor(HIDDEN_STATES, b"\xaa" * 32, [1, 16, 1], pb.DTYPE_BFLOAT16)
|
||||
# A peer claiming a larger tensor than its bytes describe.
|
||||
tensor.shape[1] = 32
|
||||
|
||||
with pytest.raises(PayloadCorrupt, match="implies"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_big_endian_tensor_is_rejected_loudly():
|
||||
tensor = encode_tensor(HIDDEN_STATES, b"\xaa" * 32, [1, 16, 1], pb.DTYPE_BFLOAT16)
|
||||
tensor.byte_order = pb.BYTE_ORDER_BIG_ENDIAN
|
||||
|
||||
# Byte-swapped activations would be plausible-looking garbage, so this is an
|
||||
# error rather than a best-effort read.
|
||||
with pytest.raises(ProtocolError, match="big-endian"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_bundle_from_a_newer_layout_is_refused():
|
||||
bundle = encode_bundle([])
|
||||
bundle.bundle_version = 99
|
||||
|
||||
with pytest.raises(ProtocolError, match="not supported"):
|
||||
decode_bundle(bundle)
|
||||
|
||||
|
||||
def test_bundle_with_unspecified_version_is_refused():
|
||||
with pytest.raises(ProtocolError, match="version 0"):
|
||||
decode_bundle(pb.TensorBundle())
|
||||
|
||||
|
||||
def test_tensor_with_unspecified_compression_is_refused():
|
||||
tensor = encode_tensor(HIDDEN_STATES, b"\xaa" * 32, [1, 16, 1], pb.DTYPE_BFLOAT16)
|
||||
tensor.compression = pb.COMPRESSION_UNSPECIFIED
|
||||
|
||||
with pytest.raises(ProtocolError, match="unspecified"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_tensor_with_unspecified_checksum_is_refused():
|
||||
tensor = encode_tensor(HIDDEN_STATES, b"\xaa" * 32, [1, 16, 1], pb.DTYPE_BFLOAT16)
|
||||
tensor.checksum.algorithm = pb.CHECKSUM_ALGORITHM_UNSPECIFIED
|
||||
|
||||
with pytest.raises(ProtocolError, match="unspecified"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_declared_tensor_size_cannot_exceed_negotiated_chunk_bound():
|
||||
tensor = pb.NamedTensor(
|
||||
name=HIDDEN_STATES,
|
||||
shape=[1],
|
||||
dtype=pb.DTYPE_UINT8,
|
||||
byte_order=pb.BYTE_ORDER_LITTLE_ENDIAN,
|
||||
total_bytes=DEFAULT_MAX_CHUNK_BYTES + 1,
|
||||
compression=pb.COMPRESSION_NONE,
|
||||
)
|
||||
|
||||
with pytest.raises(ProtocolError, match="negotiated chunk bound"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_stricter_session_negotiation_overrides_global_defaults():
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES,
|
||||
b"\xaa" * 32,
|
||||
[32],
|
||||
pb.DTYPE_UINT8,
|
||||
max_fragment_bytes=32,
|
||||
)
|
||||
|
||||
with pytest.raises(ProtocolError, match="16-byte negotiated"):
|
||||
decode_tensor(tensor, max_chunk_bytes=16, max_fragment_bytes=16)
|
||||
with pytest.raises(ProtocolError, match="16-byte"):
|
||||
decode_bundle(
|
||||
encode_bundle([tensor]),
|
||||
max_chunk_bytes=16,
|
||||
max_fragment_bytes=16,
|
||||
)
|
||||
|
||||
|
||||
def test_fragment_cannot_exceed_protocol_fragment_bound():
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES,
|
||||
b"\xaa" * (1024 * 1024 + 1),
|
||||
[1024 * 1024 + 1],
|
||||
pb.DTYPE_UINT8,
|
||||
max_fragment_bytes=1024 * 1024 + 1,
|
||||
)
|
||||
|
||||
with pytest.raises(PayloadCorrupt, match="fragment larger"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_fragment_count_is_bounded_before_sorting_or_allocation():
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES,
|
||||
b"\xaa" * (DEFAULT_MAX_FRAGMENTS_PER_TENSOR + 1),
|
||||
[DEFAULT_MAX_FRAGMENTS_PER_TENSOR + 1],
|
||||
pb.DTYPE_UINT8,
|
||||
max_fragment_bytes=1,
|
||||
max_fragments=DEFAULT_MAX_FRAGMENTS_PER_TENSOR + 1,
|
||||
)
|
||||
|
||||
with pytest.raises(PayloadCorrupt, match="fragments.*exceeding"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_tensor_count_is_bounded_before_bundle_decoding():
|
||||
tensors = [
|
||||
pb.NamedTensor(name=f"t-{index}")
|
||||
for index in range(DEFAULT_MAX_TENSORS_PER_BUNDLE + 1)
|
||||
]
|
||||
|
||||
with pytest.raises(ProtocolError, match="tensors.*exceeding"):
|
||||
encode_bundle(tensors)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("shape", "match"),
|
||||
[
|
||||
([1] * 9, "rank"),
|
||||
([-1], "dimension outside"),
|
||||
([1 << 31], "dimension outside"),
|
||||
],
|
||||
)
|
||||
def test_shape_rank_and_dimensions_are_bounded_before_payload_work(shape, match):
|
||||
with pytest.raises(ProtocolError, match=match):
|
||||
encode_tensor(HIDDEN_STATES, b"\x00", shape, pb.DTYPE_UINT8)
|
||||
|
||||
|
||||
def test_complete_session_message_includes_envelope_in_byte_bound():
|
||||
small = pb.SessionRequest(chunk=pb.ActivationChunk(bundle=encode_bundle([])))
|
||||
assert validate_session_message_size(small, max_chunk_bytes=64) == small.ByteSize()
|
||||
|
||||
oversized = pb.SessionRequest(
|
||||
chunk=pb.ActivationChunk(
|
||||
envelope=pb.Envelope(work_id="w" * 128),
|
||||
bundle=encode_bundle([]),
|
||||
)
|
||||
)
|
||||
with pytest.raises(ProtocolError, match="serialized session message"):
|
||||
validate_session_message_size(oversized, max_chunk_bytes=64)
|
||||
|
||||
|
||||
def test_bundle_serialized_size_is_bounded():
|
||||
payload = b"\xaa" * (DEFAULT_MAX_CHUNK_BYTES // 2)
|
||||
first = encode_tensor("first", payload, [len(payload)], pb.DTYPE_UINT8)
|
||||
second = encode_tensor("second", payload, [len(payload)], pb.DTYPE_UINT8)
|
||||
|
||||
with pytest.raises(ProtocolError, match="serialized tensor bundle"):
|
||||
encode_bundle([first, second])
|
||||
|
||||
|
||||
def test_compressed_tensor_cannot_expand_past_declared_size():
|
||||
pytest.importorskip("zstandard")
|
||||
payload = b"\x00" * 65536
|
||||
always = CompressionPolicy(min_input_bytes=0, min_savings_bytes=0, min_savings_ratio=0.0)
|
||||
tensor = encode_tensor(
|
||||
HIDDEN_STATES, payload, [len(payload)], pb.DTYPE_UINT8, policy=always
|
||||
)
|
||||
assert tensor.compression == pb.COMPRESSION_ZSTD
|
||||
# Simulate a hostile frame that advertises a small output while expanding to
|
||||
# the original 64 KiB. Shape and total_bytes agree, so only the bounded
|
||||
# decompressor protects the receiver.
|
||||
tensor.shape[:] = [1024]
|
||||
tensor.total_bytes = 1024
|
||||
|
||||
with pytest.raises(PayloadCorrupt, match="bounded zstd"):
|
||||
decode_tensor(tensor)
|
||||
|
||||
|
||||
def test_generated_runtime_floors_match_committed_stubs():
|
||||
metadata = (REPO_ROOT / "packages/node/pyproject.toml").read_text()
|
||||
assert '"grpcio>=1.82.1"' in metadata
|
||||
assert '"protobuf>=7.35.0"' in metadata
|
||||
|
||||
|
||||
# --- Bounded prefill chunking and the decode fast path ----------------------
|
||||
|
||||
|
||||
def test_prefill_is_split_into_bounded_token_aligned_chunks():
|
||||
chunks = plan_prefill_chunks(2048)
|
||||
|
||||
assert all(c.token_count <= DEFAULT_MAX_PREFILL_CHUNK_TOKENS for c in chunks)
|
||||
assert sum(c.token_count for c in chunks) == 2048
|
||||
# Contiguous, token-aligned, and no split falls mid-token.
|
||||
assert [c.first_position for c in chunks] == [
|
||||
i * DEFAULT_MAX_PREFILL_CHUNK_TOKENS for i in range(len(chunks))
|
||||
]
|
||||
assert [c.final_chunk for c in chunks] == [False] * (len(chunks) - 1) + [True]
|
||||
|
||||
|
||||
def test_final_prefill_chunk_carries_the_remainder():
|
||||
chunks = plan_prefill_chunks(300, max_tokens=128)
|
||||
|
||||
assert [c.token_count for c in chunks] == [128, 128, 44]
|
||||
assert chunks[-1].chunk_info().final_chunk
|
||||
assert chunks[0].position().first_position == 0
|
||||
assert chunks[-1].position().first_position == 256
|
||||
|
||||
|
||||
def test_empty_prefill_is_refused():
|
||||
with pytest.raises(ProtocolError):
|
||||
plan_prefill_chunks(0)
|
||||
|
||||
|
||||
def test_decode_fast_path_is_much_smaller_than_a_full_envelope_chunk():
|
||||
hidden = b"\x01\x02" * 8 # one token, hidden=8, bfloat16
|
||||
tensor = encode_tensor(HIDDEN_STATES, hidden, [1, 1, 8], pb.DTYPE_BFLOAT16)
|
||||
|
||||
fast = pb.SessionRequest(
|
||||
decode=pb.DecodeStep(
|
||||
idempotency_step=9,
|
||||
position=1024,
|
||||
expected_past_len=1024,
|
||||
tensor=tensor,
|
||||
work_id="work-7f3a",
|
||||
)
|
||||
)
|
||||
# The same single token carried the long way, repeating identity that the
|
||||
# handshake already fixed for the life of the stream.
|
||||
full = pb.SessionRequest(
|
||||
chunk=pb.ActivationChunk(
|
||||
envelope=conformance.canonical_session_request().chunk.envelope,
|
||||
bundle=encode_bundle([tensor]),
|
||||
)
|
||||
)
|
||||
|
||||
assert len(fast.SerializeToString()) * 2 < len(full.SerializeToString())
|
||||
assert decode_tensor(fast.decode.tensor) == hidden
|
||||
|
||||
|
||||
def test_flow_control_defaults_bound_the_queue_and_the_message():
|
||||
limits = default_flow_control()
|
||||
|
||||
assert limits.max_prefill_chunk_tokens == DEFAULT_MAX_PREFILL_CHUNK_TOKENS
|
||||
assert limits.max_chunk_bytes == DEFAULT_MAX_CHUNK_BYTES
|
||||
assert limits.max_inflight_chunks > 0
|
||||
|
||||
|
||||
def test_flow_control_negotiation_takes_the_strictest_bound():
|
||||
proposed = pb.FlowControl(
|
||||
credits_granted=64, max_inflight_chunks=64, max_chunk_bytes=64 << 20,
|
||||
max_prefill_chunk_tokens=1024,
|
||||
)
|
||||
settled = negotiate_flow_control(proposed, default_flow_control())
|
||||
|
||||
# A sender cannot talk a worker into unbounded queues by proposing a large
|
||||
# window: neither peer can raise the other's ceiling.
|
||||
assert settled.max_inflight_chunks == default_flow_control().max_inflight_chunks
|
||||
assert settled.credits_granted <= settled.max_inflight_chunks
|
||||
assert settled.max_chunk_bytes == DEFAULT_MAX_CHUNK_BYTES
|
||||
assert settled.max_prefill_chunk_tokens == DEFAULT_MAX_PREFILL_CHUNK_TOKENS
|
||||
|
||||
|
||||
def test_initial_credits_never_exceed_negotiated_inflight_limit():
|
||||
proposed = pb.FlowControl(credits_granted=64, max_inflight_chunks=64)
|
||||
limits = pb.FlowControl(credits_granted=64, max_inflight_chunks=8)
|
||||
|
||||
settled = negotiate_flow_control(proposed, limits)
|
||||
|
||||
assert settled.credits_granted == 8
|
||||
assert settled.max_inflight_chunks == 8
|
||||
|
||||
|
||||
# --- Compatibility ----------------------------------------------------------
|
||||
|
||||
|
||||
def test_committed_vectors_still_encode_as_promised():
|
||||
# The C++ test asserts against these exact bytes. If a schema change alters
|
||||
# the canonical encoding, it must be acknowledged by regenerating them.
|
||||
golden = (conformance.TESTDATA_DIR / conformance.GOLDEN_SESSION_REQUEST).read_bytes()
|
||||
assert conformance.serialize(conformance.canonical_session_request()) == golden
|
||||
|
||||
report = (conformance.TESTDATA_DIR / conformance.GOLDEN_CAPABILITY_REPORT).read_bytes()
|
||||
assert conformance.serialize(conformance.canonical_capability_report()) == report
|
||||
|
||||
|
||||
def test_golden_session_request_round_trips_with_every_field_intact():
|
||||
golden = (conformance.TESTDATA_DIR / conformance.GOLDEN_SESSION_REQUEST).read_bytes()
|
||||
request = pb.SessionRequest.FromString(golden)
|
||||
envelope = request.chunk.envelope
|
||||
|
||||
assert envelope.work_id == conformance.WORK_ID
|
||||
assert envelope.route_session_id == conformance.ROUTE_SESSION_ID
|
||||
assert envelope.route_epoch == conformance.ROUTE_EPOCH
|
||||
assert envelope.idempotency_step == conformance.IDEMPOTENCY_STEP
|
||||
assert envelope.shard_range.effective_start_layer == conformance.EFFECTIVE_START_LAYER
|
||||
assert envelope.phase == pb.PHASE_PREFILL
|
||||
assert envelope.cache_expectation.mode == pb.CACHE_MODE_PREFILL
|
||||
assert envelope.deadline_unix_nanos == conformance.DEADLINE_UNIX_NANOS
|
||||
assert decode_bundle(request.chunk.bundle) == {
|
||||
HIDDEN_STATES: conformance.canonical_payload()
|
||||
}
|
||||
assert request.SerializeToString(deterministic=True) == golden
|
||||
|
||||
|
||||
def test_unknown_fields_from_a_newer_peer_survive_a_forwarding_hop():
|
||||
# A Shard forwards activations onward. If it silently dropped fields a newer
|
||||
# peer added, it would corrupt a route it is merely a waypoint on.
|
||||
golden = (conformance.TESTDATA_DIR / conformance.GOLDEN_SESSION_REQUEST).read_bytes()
|
||||
future_field = b"\xb8\xe0\x04\xb9\x60" # field 9999, varint 12345
|
||||
request = pb.SessionRequest.FromString(golden + future_field)
|
||||
|
||||
assert request.chunk.envelope.work_id == conformance.WORK_ID
|
||||
assert request.SerializeToString() == golden + future_field
|
||||
|
||||
|
||||
def test_a_message_missing_newer_field_groups_still_parses():
|
||||
sparse = pb.SessionRequest(chunk=pb.ActivationChunk(envelope=pb.Envelope(work_id="w")))
|
||||
parsed = pb.SessionRequest.FromString(sparse.SerializeToString())
|
||||
|
||||
assert parsed.chunk.envelope.work_id == "w"
|
||||
assert parsed.chunk.envelope.route_epoch == 0
|
||||
assert parsed.chunk.envelope.phase == pb.PHASE_UNSPECIFIED
|
||||
|
||||
|
||||
def test_retired_fragment_field_stays_reserved():
|
||||
# `uncompressed_size` (field 5) was removed because NamedTensor.total_bytes
|
||||
# is the single source of truth. The number stays reserved so it can never
|
||||
# be recycled for a different meaning — a recycled number is the one schema
|
||||
# change that old and new peers cannot detect, because the bytes still parse.
|
||||
descriptor = descriptor_pb2.DescriptorProto()
|
||||
pb.TensorFragment.DESCRIPTOR.CopyToProto(descriptor)
|
||||
|
||||
assert 5 not in {field.number for field in descriptor.field}
|
||||
assert any(r.start <= 5 < r.end for r in descriptor.reserved_range)
|
||||
assert "uncompressed_size" in descriptor.reserved_name
|
||||
|
||||
|
||||
def test_a_peer_still_sending_the_retired_field_does_not_corrupt_the_tensor():
|
||||
# An older peer that still sets field 5 must be parsed, not rejected: the
|
||||
# value lands in unknown fields and the payload is unaffected.
|
||||
tensor = encode_tensor(HIDDEN_STATES, b"\xaa" * 32, [1, 16, 1], pb.DTYPE_BFLOAT16)
|
||||
wire = tensor.SerializeToString()
|
||||
fragment = pb.TensorFragment.FromString(tensor.fragments[0].SerializeToString() + b"\x28\x20")
|
||||
|
||||
assert fragment.payload == tensor.fragments[0].payload
|
||||
assert decode_tensor(pb.NamedTensor.FromString(wire)) == b"\xaa" * 32
|
||||
|
||||
|
||||
def test_generated_python_stubs_match_the_proto():
|
||||
pytest.importorskip("grpc_tools", reason="protoc toolchain is required to verify")
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(REPO_ROOT / "scripts/generate_native_protocol.py"), "--check"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not CPP_ROUNDTRIP.is_file(),
|
||||
reason="build the C++ conformance test to check cross-language agreement",
|
||||
)
|
||||
def test_cpp_and_python_agree_byte_for_byte():
|
||||
# Written by the C++ conformance test: it parsed the golden bytes into its
|
||||
# own object model and serialized them back. Byte equality means both
|
||||
# languages encode every field of this schema identically.
|
||||
golden = (conformance.TESTDATA_DIR / conformance.GOLDEN_SESSION_REQUEST).read_bytes()
|
||||
assert CPP_ROUNDTRIP.read_bytes() == golden
|
||||
Reference in New Issue
Block a user