Summary
skippy-topology's split planner weights per-node layer allocation by vram_bytes, which is (intentionally, per #219) a blended weight-holding capacity, not a measure of compute throughput. Because capacity and throughput are different quantities, a node with large system RAM and weak compute is allocated a larger share of the pipeline than a node with a fast GPU.
This is not a restatement of #219 — that issue established the number is intentional and fixed the display labels (PR #222). This is about a consumer of that number: using hold-capacity as an allocation weight.
The number is intentional but the label is wrong. vram_bytes is computed as gpu_vram + 0.75 * (system_ram - gpu_vram). llama.cpp can offload layers to system RAM, so the value represents how large a model the node can actually serve — not how much GPU memory you have.
Agreed, and that's the correct semantic for a fit question. model_target_reconciliation_local_fit (crates/mesh-llm-host-runtime/src/runtime/mod.rs:4018-4026) asks "can this node hold the model?" and compares against vram_bytes — correct usage.
The problem
crates/skippy-topology/src/lib.rs:937 asks a different question with the same number:
(((request.layers.len() as u128) * (node.vram_bytes as u128)) / (total_weight as u128))
Layers allocated ∝ hold-capacity. But layers allocated should be ∝ how fast the node can execute them. NodeSpec (lib.rs:31-37) carries only node_id, cached_slice_bytes, vram_bytes — there is no throughput term. NodePlacementSignal adds rtt_ms/availability_score/missing_artifact_bytes, but those are tie-break adjustments in node_package_score (lib.rs:1158-1174), never multipliers on the span.
Pipeline parallelism is serial per request, so an over-allocated slow node isn't a partial contributor — it's a stall on every token.
Worked example (CPU-only node)
crates/mesh-llm-system/src/hardware/mod.rs:191-195:
fn apply_cpu_only_runtime_budget(survey: &mut HardwareSurvey, metrics: &[Metric], system_ram: u64) {
if metrics.contains(&Metric::VramBytes) && system_ram > 0 {
survey.vram_bytes = (system_ram as f64 * 0.75) as u64;
}
}
and the not(feature = "skippy-devices") path at mod.rs:537 uses * 0.90.
So a 128 GB CPU-only box advertises ~115 GB capacity and outranks a 24 GB RTX 4090 by ~4.8x, receiving ~4.8x the layers — to execute on CPU. There is no zero-capacity guard: validate_request (lib.rs:1500-1528) checks only EmptyLayers/EmptyNodes and contiguity, and span = span.max(1) (lib.rs:941) floors every node at >=1 layer.
The same directionality applies (more mildly) to any GPU node with large RAM offload, since mod.rs:330 does vram_bytes = total + (ram_offload as f64 * 0.90).
The data already exists on the wire
crates/mesh-llm-protocol/src/proto/node.rs:92-97 already carries mem_bandwidth_gbps, compute_tflops_fp32, compute_tflops_fp16; node.rs:44 carries advertised_model_throughput. As far as I can trace, their only consumers are /status and gpu-bench display (api/status.rs:258-344, runtime/mod.rs:5864-6012) — none reach plan_weighted_contiguous.
Notably, request routing already reasons about throughput (network/metrics.rs:479-489 tps_for_model, consumed at network/openai/transport.rs:3468 and ingress.rs:247), while the split planner does not — mesh-llm-routing has no skippy dependency.
One caveat on any fix
crates/mesh-llm-gpu-bench/src/runner.rs:31-34 returns None when gpu_count == 0, so bandwidth/TFLOPs are never measured on precisely the CPU-only nodes this affects (base detection hardcodes None at hardware/mod.rs:1125-1127). A throughput term would be inert for those nodes until some CPU-side measurement (STREAM-style, or a conservative device-class default) exists. Measurement likely has to land before the weight.
A min(vram_bytes, required_bytes) clamp would separately address a node with far more capacity than the model needs still absorbing proportionally more layers.
Possibly related
ROADMAP.md:60-63 notes MoE sharding "most results show this doesn't perform as well as one would hope, more research is needed." Speculative, but capacity-weighted placement across heterogeneous nodes seems like it could contribute.
Question
Is capacity-as-allocation-weight intended (e.g. deliberately co-locating layers where the weights fit, accepting the throughput cost), or is a throughput term a gap worth filing properly? Happy to work up a PR if the latter, but this looks like a design call that should be yours first.
Verified against 2c2e808 (2026-07-15). skippy-topology is a pure crate (serde-only, 33 unit tests); note weighted_contiguous_plan_uses_node_vram_for_layer_spans (tests.rs:231-255) pins the current behavior deliberately, which is partly why I'm asking rather than assuming.
Summary
skippy-topology's split planner weights per-node layer allocation byvram_bytes, which is (intentionally, per #219) a blended weight-holding capacity, not a measure of compute throughput. Because capacity and throughput are different quantities, a node with large system RAM and weak compute is allocated a larger share of the pipeline than a node with a fast GPU.This is not a restatement of #219 — that issue established the number is intentional and fixed the display labels (PR #222). This is about a consumer of that number: using hold-capacity as an allocation weight.
The premise (from #219, quoting @Bortlesboat)
Agreed, and that's the correct semantic for a fit question.
model_target_reconciliation_local_fit(crates/mesh-llm-host-runtime/src/runtime/mod.rs:4018-4026) asks "can this node hold the model?" and compares againstvram_bytes— correct usage.The problem
crates/skippy-topology/src/lib.rs:937asks a different question with the same number:Layers allocated ∝ hold-capacity. But layers allocated should be ∝ how fast the node can execute them.
NodeSpec(lib.rs:31-37) carries onlynode_id,cached_slice_bytes,vram_bytes— there is no throughput term.NodePlacementSignaladdsrtt_ms/availability_score/missing_artifact_bytes, but those are tie-break adjustments innode_package_score(lib.rs:1158-1174), never multipliers on the span.Pipeline parallelism is serial per request, so an over-allocated slow node isn't a partial contributor — it's a stall on every token.
Worked example (CPU-only node)
crates/mesh-llm-system/src/hardware/mod.rs:191-195:and the
not(feature = "skippy-devices")path atmod.rs:537uses* 0.90.So a 128 GB CPU-only box advertises ~115 GB capacity and outranks a 24 GB RTX 4090 by ~4.8x, receiving ~4.8x the layers — to execute on CPU. There is no zero-capacity guard:
validate_request(lib.rs:1500-1528) checks onlyEmptyLayers/EmptyNodesand contiguity, andspan = span.max(1)(lib.rs:941) floors every node at >=1 layer.The same directionality applies (more mildly) to any GPU node with large RAM offload, since
mod.rs:330doesvram_bytes = total + (ram_offload as f64 * 0.90).The data already exists on the wire
crates/mesh-llm-protocol/src/proto/node.rs:92-97already carriesmem_bandwidth_gbps,compute_tflops_fp32,compute_tflops_fp16;node.rs:44carriesadvertised_model_throughput. As far as I can trace, their only consumers are/statusand gpu-bench display (api/status.rs:258-344,runtime/mod.rs:5864-6012) — none reachplan_weighted_contiguous.Notably, request routing already reasons about throughput (
network/metrics.rs:479-489tps_for_model, consumed atnetwork/openai/transport.rs:3468andingress.rs:247), while the split planner does not —mesh-llm-routinghas noskippydependency.One caveat on any fix
crates/mesh-llm-gpu-bench/src/runner.rs:31-34returnsNonewhengpu_count == 0, so bandwidth/TFLOPs are never measured on precisely the CPU-only nodes this affects (base detection hardcodesNoneathardware/mod.rs:1125-1127). A throughput term would be inert for those nodes until some CPU-side measurement (STREAM-style, or a conservative device-class default) exists. Measurement likely has to land before the weight.A
min(vram_bytes, required_bytes)clamp would separately address a node with far more capacity than the model needs still absorbing proportionally more layers.Possibly related
ROADMAP.md:60-63notes MoE sharding "most results show this doesn't perform as well as one would hope, more research is needed." Speculative, but capacity-weighted placement across heterogeneous nodes seems like it could contribute.Question
Is capacity-as-allocation-weight intended (e.g. deliberately co-locating layers where the weights fit, accepting the throughput cost), or is a throughput term a gap worth filing properly? Happy to work up a PR if the latter, but this looks like a design call that should be yours first.
Verified against
2c2e808(2026-07-15).skippy-topologyis a pure crate (serde-only, 33 unit tests); noteweighted_contiguous_plan_uses_node_vram_for_layer_spans(tests.rs:231-255) pins the current behavior deliberately, which is partly why I'm asking rather than assuming.