Adapt MLX pipeline concepts for Skippy - #812
Closed
i386 wants to merge 1 commit into
Closed
Conversation
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Skippy can now borrow the useful parts of MLX pipeline parallelism without becoming an MLX backend and without adding tensor-parallel collectives.
The important translation is this:
No public OpenAI API behavior changes. No stage wire-format bytes change. The new behavior is planner/protocol-helper/telemetry groundwork for better split serving.
Big Picture
Before, Skippy’s pipeline mostly answered: “which nodes can hold these layer ranges?”
After this PR, the pipeline can also answer: “which ordered chain is cheapest to run, what artifacts will it need, what role does each stage play, what cache epoch is this message in, and how large is the activation transfer we are timing?”
1. Transport-Aware Stage Ordering
Before
The package-aware contiguous planner could prefer nodes based on node-local signals such as VRAM, cached artifact bytes, missing artifact bytes, and RTT-ish placement hints.
That was useful but incomplete for a pipeline. In a layer pipeline, adjacent stages exchange activations on every prefill/decode step. The cost that matters is not only “is node B good?” but also “is the edge from node A to node B good?”
So two plans could contain the same nodes and the same layer ranges, but have very different runtime behavior:
flowchart LR subgraph SameNodesBadOrder["Before: same selected nodes, unlucky order"] A["stage 0\nnode A"] -->|"18 ms"| B["stage 1\nnode B"] B -->|"24 ms"| C["stage 2\nnode C"] C -->|"3 ms"| D["stage 3\nnode D"] endThe old planner had no directed edge model to say that
A -> C -> B -> Dis a better chain thanA -> B -> C -> Dwhen both are otherwise valid.How It Works Now
This PR adds
StageEdgeSignal:source_node_idtarget_node_idrtt_mslarge_frame_bytes_per_secdirect_prediction_return_supportedThe new
plan_package_aware_contiguous_with_transport(...)entry point keeps the same package-aware placement constraints, then scores possible stage orderings by adjacent directed edge cost.For small stage counts, it exhaustively evaluates permutations. For larger stage counts, it uses a greedy fallback so the planner does not explode combinatorially.
flowchart LR subgraph BetterOrder["After: ordered by adjacent edge cost"] A["stage 0\nnode A"] -->|"2 ms"| C["stage 1\nnode C"] C -->|"3 ms"| B["stage 2\nnode B"] B -->|"4 ms"| D["stage 3\nnode D"] endDiagnostics now record measured chosen edges, for example:
Why This Is Good
Skippy split serving is latency-sensitive because decode is a serialized chain. A single bad adjacent hop can dominate the whole pipeline even if every individual node has enough memory.
This gives the planner the same practical discipline MLX gets from pipeline ranks over Ring/TCP: make the model topology match the transport shape. It is especially useful on real meshes where Ethernet, Wi-Fi, Thunderbolt, and remote nodes can all appear in the same candidate set.
Reviewer Notes
2. Artifact Cold-Start Visibility
Before
Skippy already has layer packages, and package locality can affect placement. But once the planner accepted a topology, it did not summarize the artifact cold-start cost of that topology.
Operators could infer some things from node selection, but they could not easily see:
That made slow startup harder to explain.
How It Works Now
This PR adds
artifact_diagnostics.After a package-aware plan is selected, the planner aggregates selected-stage artifact data:
cached_slice_bytesmissing_artifact_bytesRepresentative diagnostic:
The diagnostic stays quiet for plain weighted plans that did not provide artifact signals, so existing non-package planning does not get noisy placeholder output.
Why This Is Good
This is the Skippy version of MLX’s metadata-first load trick.
MLX pipeline load first discovers metadata and rank-owned weights, then each rank downloads only the files it needs. Skippy cannot copy that mechanism directly because it serves GGUF layer packages rather than safetensors, but the idea transfers cleanly:
This makes package planning less of a black box and gives us the data needed for future “prefer cached topology” and peer-transfer scheduling work.
3. Stage Role Metadata
Before
stage_indexcarried too much meaning implicitly.For example:
Those roles were real, but they were not explicit in the topology data. A reviewer or UI had to infer behavior from index and layer range.
How It Works Now
This PR adds
StageRole:DriverEmbeddingIntermediateReadoutEach
StagePlannow carries aroles: Vec<StageRole>field with serde defaulting, so older serialized plans that do not include roles still deserialize.Role assignment is deterministic:
Driver,EmbeddingIntermediateReadoutDriver,Embedding,ReadoutWhy This Is Good
MLX’s rank ordering made final/readout ownership obvious by convention. Skippy should not reverse public stage numbering just to copy MLX, because stage 0 owns driver/session responsibilities.
Explicit roles give us the useful part without destabilizing Skippy’s mental model:
This also reduces accidental coupling between “stage index” and “stage responsibility.”
4. Request / Cache Epoch Coordination
Before
The binary stage protocol already carried the necessary identity fields:
request_idsession_idcheckpoint_generationprompt_token_countdecode_stepBut code treated them as individual fields. There was no single value that meant “this stage message belongs to this request/cache generation.”
That makes future overlapping prefill, restore, trim, decode, and cache-reuse logic harder to audit.
How It Works Now
This PR adds
StageRequestEpoch, derived from existing wire/header fields:StageWireMessage::request_epoch()centralizes extraction.The epoch helper can compare whether two messages belong to the same request/session flow and whether one epoch is stale relative to another epoch.
Binary transport telemetry now includes checkpoint generation with the other epoch values:
Why This Is Good
MLX rank 0 keeps ranks synchronized by broadcasting request state through collectives. Skippy does not need that exact broadcast pattern because Skippy has typed stage messages, but it needs the same discipline: every stage should agree about which request/session/cache generation a frame belongs to.
This PR does not start rejecting stale frames yet. It creates the safe vocabulary for that next step:
Compatibility
This is not a wire-format change.
The protocol crate changed, but the on-the-wire bytes did not.
5. Activation-Frame Transfer Accounting
Before
Skippy had two separate approximations of stage-message size:
llama_stage.message_wire_bytesThose estimates could drift. The conditioned-wire path also undercounted parts of the message such as the full fixed header, position sidebands, sampling metadata, chat metadata, and raw import bytes.
For large prefill activation frames, that undercount makes simulated network pressure less trustworthy.
How It Works Now
This PR adds
StageWireMessage::estimated_wire_bytes().The estimate includes:
Both telemetry and wire conditioning now use the same helper.
Representative telemetry:
Why This Is Good
MLX can stripe large payloads over multiple neighbor sockets. Skippy is not adding striping in this PR because that needs capability negotiation, chunk headers, reassembly buffers, backpressure, and mixed-version fallback.
But before adding striping, Skippy needs accurate accounting:
This is the low-risk foundation for later large prefill-frame striping.
CLI / Operator Output
No stable CLI command output changes in this PR.
The new user-visible text is planner and telemetry diagnostic material. Existing doctor/status/reporting surfaces can display it once they consume the new topology diagnostics.
Simulated representative output:
What This PR Does Not Do
Validation
cargo fmt --all -- --checkcargo test -p skippy-protocol --libcargo test -p skippy-topology --libcargo test -p skippy-server --libcargo clippy -p skippy-protocol --all-targets -- -D warningscargo clippy -p skippy-topology --all-targets -- -D warningscargo clippy -p skippy-server --all-targets -- -D warningscargo check -p mesh-llmcargo clippy -p mesh-llm --all-targets -- -D warnings