Skip to content

fix: correctness, resilience, and transport-safety bugs in the serve path#24

Open
ruminoob wants to merge 45 commits into
leyten:masterfrom
ruminoob:fix/review-critical
Open

fix: correctness, resilience, and transport-safety bugs in the serve path#24
ruminoob wants to merge 45 commits into
leyten:masterfrom
ruminoob:fix/review-critical

Conversation

@ruminoob

@ruminoob ruminoob commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Three focused fixes in the serve path, each traced to a concrete failure it prevents. All are surgical and independent; the normal (non-failure) serve path is unchanged.

1. Preserve recovered tokens on tree-path FT resume (correctness)

coordinate_pipe_tree seeded its output accumulator with out = [cur], dropping every token committed before a mid-request node death. The chain coordinator already does out = resume_ids + [cur]; the tree path had lost it, so under M25_TREE=1 a heal-and-resume returned only the post-failure tokens and continuation_preserved failed (silent truncation of the user's output).

Mirror the chain path exactly: seed out with resume_ids, and rebuild the draft prefix from prompt_ids + out instead of gen_ids + out so resume_ids is not double-counted. All three changed lines are no-ops when resume_ids is empty (gen_ids == prompt_ids), so only the resume path is affected.

2. Stage serve loops survive compute faults instead of crashing (resilience)

The tail and head/middle serve loops caught only EDGE_ERRORS (OSError/EOFError). Any other exception raised during a verify — a RuntimeError from an over-context request (RoPE table / static-KV overflow), a CUDA OutOfMemoryError, a malformed frame — propagated out of serve() and terminated the stage process, dropping its warm weights and taking the whole ring down until a manual relaunch. A request that is merely oversized, reachable through the gateway, was enough.

Restore specpipe's proven "bad msg → reset" guard: a parallel except Exception on each loop runs the same teardown as an edge close (reset layers, drop the channels, let the ring re-handshake on warm weights). EDGE_ERRORS is still matched first, so genuine edge deaths keep their existing predecessor/return-channel semantics.

3. Bound transport frame size and reject empty-blob amplification (safety)

shard/transport.py is the production receive path (SHARD_TRANSPORT=libp2p). The libp2p link authenticates the peer but the engine authorizes nothing, so any ring neighbour can stream a frame. Two unbounded allocations:

  • recv_msg read a 64-bit length off the socket and buffered up to 2^64 bytes with no cap.
  • _unpack returned torch.empty(shape) for an empty blob, so a few header bytes declaring shape [65536, 65536] allocated 16 GB of host RAM — a memory-amplification bomb a frame-size cap alone would not catch.

Add SHARD_MAX_FRAME (default 2 GiB): a longer declared length is refused before allocating, as a ConnectionError (an OSError → a dead edge the serve loop resets, not a crash). And require an empty blob to correspond to a zero-element tensor; a non-empty shape with no bytes is rejected as a malformed frame. Legitimate zero-element tensors still decode.

Testing

  • Full test suite: 64 passed (Python 3.13, CPU torch).
  • The transport guards were exercised directly over a socketpair: normal round-trip, a zero-element tensor, an oversized declared length, and the real empty-blob/huge-shape bomb — the bomb is rejected via the numel guard as a ValueError → ConnectionError, and the legit empty tensor still decodes.

leyten added 30 commits June 30, 2026 13:34
fix(ring): break tail bring-up handshake deadlock
The measured per-hop bottleneck is moving the bf16 activation over WAN (a 120KB
round-trip is 6-8x the connect-ping; transfer dominates, not relay/compute).
Transporting the inter-stage hidden state as fp8 (e4m3, per-tensor scale to keep
the residual-stream outlier channels in range) halves bytes/hop -> ~2x tok/s on a
transfer-bound ring. Opt-in: the bf16 path is byte-for-byte unchanged when off.

Lossy but deterministic + verifiable: receipts hash the fp8 engine's activations,
so trustless verification is intact (it just attests an fp8 engine, not bf16-exact).
Quality is a measured question (residual-stream outliers can bite) -> validate with
a bf16-vs-fp8 A/B on a real ring before claiming "usable M2.5 at 2x".

- transport: register float8_e4m3fn/e5m2 in the codec dtype table
- m25_pipe: _pack_h/_unpack_h + _hsend/_hrecv wrap the 5 activation wire sites
- scatter launcher passes M25_FP8_WIRE through to stages
Offline-validated: codec round-trips fp8 bit-exact, bytes exactly 0.50x, 2.26%
per-hop quant error on an outlier-heavy activation (per-channel scaling is the
tightening lever if the A/B shows quality loss).
…placement

Consolidate the 2026-07-01 state onto master: everything (handshake, select_ring,
EAGLE-chain, fp8) is now on master, branches deleted. Records: the measured fp8 A/B
(+9% on high-bw vast, ~2x is the residential bytes-bound regime; quality preserved
but not bit-exact); the 3-agent residential-bandwidth research (the bind is the
sender's UPLOAD; decode survives, long-context prefill is the wall on cable/DSL but
fine on fiber; you can't conjure upload, the fix is selection + use the download
direction + fewer bytes/hops); and the admission-vs-placement decision (coarse proven
floor in c0mpute + per-role capability matching in select_ring, not a join threshold).
Next: make select_ring upload-aware, then the validation + residential-throttle runs.
RESUME HERE was current but the lower sections lagged: ROADMAP leyten#1 still said
'vLLM tree GO/NO-GO NEXT' (superseded), PROVEN lacked select_ring/fp8/residential,
IN-FLIGHT still said 'reconfirm EAGLE accept'. Fixed: ROADMAP redirects to the
RESUME HERE next-actions; PROVEN adds select_ring, fp8, residential-bottleneck rows
and marks EAGLE merged; IN-FLIGHT marks EAGLE confirmed+merged.
select_ring minimized decode step-time only, blind to the residential wall:
prefill ships the [S,H] activation (~100MB/hop @16k) over home uplinks and the
bind is the SENDER'S UPLOAD, so a slow-cable node on a load-bearing hop costs
minutes of TTFT while decode's tiny activation survives.

The objective is now total request time (prefill + D*decode) with per-node upload
a first-class, sender-bound cost. Prefill transport is the engine's chunked+
pipelined makespan (sum_fwd(u)+(C-1)*max_fwd(u))/C, interpolating serial-blob SUM
and bottleneck MAX. The selector tails the lowest-upload node (the tail forwards
nothing), drops nodes whose uplink would dominate prefill, and relegates them to
off-critical roles (weight-seeder / aggregator / hot-standby / decode-only-replica
/ spot-check-verifier) derived from the drop reason, not a hard Mbps gate -- the
placement half of admission-vs-placement. Stays pure; byte-identical to the old
decode-only path when up_mbps is omitted (golden-snapshot tested).

Validated offline (scratchpad/sim_network.py, volunteer/residential pool): aware/
oracle ~0.98 across context while upload-blind collapses 0.98->0.80 as context
grows; TTFT speedup 2.5-5x (p95 19x); request-time 1.0->1.32x (2k->64k); the fat-
uplink rental pool shows a smaller gap. tests/test_topology.py: 10 tests, all pass.

Adversarial review also found a pre-existing false-"infeasible": the >TRIM latency
funnel's must-set was subnet-blind, trimming feasibility-critical distinct-subnet
cards when the fattest cards are co-located. Fixed (must-set keeps a distinct-
subnet cover) + regression-tested.
…a-blocked

Rented 8 real EU boxes for the residential upload A/B; measured real hop
bandwidth heterogeneity (8-127 Mbps, i.e. residential-tier slow hops exist
and would wall prefill), confirming the premise. The fully-automated per-node
upload aware-vs-blind comparison was blocked by vast-container limits (no
NET_ADMIN for tc, NAT hairpin, ssh-proxy rate-limiting under repeated runs).
Boxes torn down (0 live, ~$4). Clean number needs one gentle non-rate-limited
run; natural uplinks already span the residential range so no throttle needed.
M25_FP8_WIRE ships float8_e4m3fn tensors, but only shard/transport.py
(the libp2p codec) had the fp8 entries in its dtype table — the raw-TCP
PSK path rejected every fp8 frame as 'unknown tensor dtype' and reset
the edge in a loop. Mirror the two entries; the tables must stay in
lockstep (regression-tested in tests/test_eagle_draft.py).
… GQA broadcast

The committed-context cache was a Python list of per-token [1,NKV,HD]
tensors: every propose() re-cat'ed the WHOLE context + repeat_interleave
GQA-expanded it, and extend() ran ~8 tiny kernels per position in a
Python loop. With EAGLE at depth=1 all of that sits serially between
recv and the next send, so it is pure per-traversal latency.

Now: preallocated [CAP,NKV,HD] buffers (amortized doubling), one-shot
batched extend, draft-chain k/v written into the scratch tail past
ctx_len (next extend overwrites it — the committed cache is never
mutated), and attention broadcasts the GRP query groups against NKV-head
K/V instead of copying the context per step.

CPU bench at toy dims, ctx 3000: prefill-extend 327ms -> 2.1ms (156x),
decode round (extend K+1 + propose K) 16.4ms -> 4.35ms (3.8x); the GPU
win is larger (the old path was launch-overhead-bound). Proposals are
regression-locked to the original implementation (kept verbatim as the
test reference) across rounds, dtypes, buffer growth and the legacy
single-aux path: tests/test_eagle_draft.py, 6 tests. Also adds
prefill_pair_tokens — the pure per-chunk EAGLE pairing the coordinator
uses to feed the whole prompt (next commit).
… serial-path profile

Three coordinator/stage-side costs on the EAGLE path (the ~7 tok/s
reasoning product path), found by tracing one decode traversal:

* aux rode every hop UN-quantized: 3 layers x [K+1,H] bf16 ~ 166KB/hop
  — 3x the activation payload itself — while h got fp8. _merge_aux now
  packs each stage's aux as [fp8,scale] (M25_FP8_AUX, defaults to the
  M25_FP8_WIRE setting); _unpack dequantizes once coordinator-side.
  Aux only feeds the DRAFTER, so losslessness is untouched by
  construction — only accept could move, and only when the flag is on.

* the drafter saw only the LAST prefill chunk (default 512 tokens) of
  context: coordinate_pipe kept just the final chunk's aux. Every chunk
  now extends the EAGLE context as its response arrives (pairing =
  prefill_pair_tokens; the extend hides in the next chunk's WAN wait),
  so accept on long prompts stops being capped by a 512-token window.
  _eagle_aux_range also drops its per-position Python stack loop.

* the decode wall was unattributed: t_draft/t_recv were measured but
  never returned. The result now carries decode_s/draft_s/ring_wait_s
  and coord prints the breakdown — the profile that ranks the next
  serial-path fix (measure first).
The 2026-06-27 warm sweep found K=8 ~2x over K=6 on the scattered EU
ring, and every EAGLE measurement since ran K=8 — but both entry points
still defaulted to 6, so any launch that forgot --K served below the
known optimum. Defaults now match the receipts.
GraphRunner replays a captured graph and never re-runs run_block's aux
capture, so with both flags set the EAGLE drafter silently feeds on
stale prefill aux — accept craters and any measurement taken that way
lies. The combination now refuses to start instead of quietly
corrupting a run. (Found by the 2026-07-02 review fleet.)
…ram routing

Two ways the hybrid wasted its drafting budget, both review-fleet finds:

* every divergence (and the end-of-job drain) called d_fetch() on a
  request known to be stale — computing a full proposal (on the EAGLE
  path a K-step serial chain) just to discard it, then re-drafting.
  Drafters now expose cancel(); the coordinator uses it (falls back to
  fetch() for drafters without one).

* NgramDrafter set matched=True on ANY ng-token anchor recurrence, even
  with zero preceding-context agreement (best_len=0). On novel/
  reasoning text those are coincidences, so garbage n-gram drafts
  displaced the EAGLE drafts the hybrid exists for — accept 0 where
  EAGLE gets 2-4 tokens. matched now requires best_len >= min_match
  (default 1; M25_NGRAM_MINMATCH=0 restores any-anchor routing). The
  n-gram proposal itself is unchanged — only the routing signal moves;
  standalone n-gram deployments are unaffected. Needs the warm A/B to
  quantify the accept lift; the routing logic is regression-tested.
… worktree-safe REPO

Three measurement-integrity holes in m25_scatter_pipe, all review-fleet
finds, all of which would have poisoned the upcoming branch-vs-master
warm A/B:

* M25_* flags were forwarded to the STAGES only — the coordinator and
  gateway processes got none, so e.g. M25_EAGLE=1 warmed aux-capturing
  stages while the coordinator silently drafted n-gram-only. One
  eng_env() prefix now feeds all three launch sites (only flags the
  operator actually set; unset ones fall through to the identical
  per-process defaults). Also brings the launcher's --K default to the
  measured 8.

* push_code ignored scp failures — one flaky vast SSH silently
  launched a stale or MIXED-VERSION ring. Now retries once, then fails
  the launch loudly.

* REPO was hardcoded to the main checkout — running the launcher from
  a worktree (the A/B: branch arm vs master arm) would push the WRONG
  tree's files. Derived from the launcher's own location now
  (SHARD_REPO overrides).
…rag-quote accept 13->44%

Six arms on a scattered 6x5090 EU ring (rental-lottery order, same ring
all arms). Serial-path branch vs master, EAGLE reasoning-hybrid, honest
bench. Both warm orderings agree branch>master (fwd 4.0->5.9, rev
4.6->5.5) = +33% decode-weighted, jitter-robust. Accept identical on
every cell EXCEPT rag-quote, where the whole-prompt drafter-context fix
lifts accept 13->44% (g 2.0->4.5, ~4->10.5 tok/s) reproducibly across
both branch passes. fp8-aux quality-safe (accept unchanged). min-match
unproven on one pass.
…ail channel lifecycles

The warm-ring wedge (re-warm before every coordinator, the session tax): the tail
closed its predecessor whenever the coordinator-return died, the EOF cascaded
upstream, and no stage ever re-dialed nxt_sock — one coordinator exit bricked the
whole warm ring. Ports specpipe's proven recovery: head/middle deliberately drop
the forward link on any edge error and rebuild it (dial-forever, 0.5s) before
re-accepting a predecessor; the tail gives pred and ret independent lifecycles
(ret death keeps pred+KV and accepts a new hello_return mid-session via select;
pred death tears down both so a reset-ok can never reach a dead coordinator) and
drops a dead job's in-flight replies until the next reset. Warm smoke-test:
coord -> kill -> new coord on the same ring, no re-warm.
…not the receipts'

CRITICAL trust hole (2026-07-02 fleet, 3 reviewers): verify_coverage's target
layer_count was max(layer_end) over the receipts under test, so a ring that
omitted layers still "tiled fully" and passed — skip-compute-and-still-get-paid.
The m25 coordinator now passes S.cfg.num_hidden_layers (62) and fails CLOSED on
an empty receipt list when SHARD_RECEIPTS is on; specpipe gains --n-layers and
warns loudly when the target is self-derived (legacy path, coordinator never
loads the model). tests/test_receipt_coverage.py pins the fixed semantics
(truncated/gap/overlap/out-of-range/pinning/tamper all raise).
…live, guarded acks

Two adversarial review passes on the churn-recovery port traced four real failure
modes; all four are closed here:
- A conn that speaks a JOB frame is adopted as the NEW predecessor with its frame
  queued (specpipe fill()'s semantic) instead of closed as junk — closing it
  kill-looped a stage replacement after a silent pred death, both at bring-up and
  mid-session.
- TCP keepalive (60/20/3) on every ring socket bounds silent-peer death (no FIN:
  power loss, NAT expiry) to ~2min; the select()-based tail can't otherwise see a
  half-open predecessor now that the old 600s recv-timeout teardown (the idle
  wedge itself) is gone.
- ret_ok sends are guarded at both loci: a coordinator dying between hello and
  ack was misclassified as predecessor death and cost a full ring cascade.
- Pred-death keeps a JUST-reconnected coordinator's return channel (stale set, no
  reset consumed): the old-session EOF racing the gateway's retry used to kill
  the exact retry churn recovery exists for. Reset is only honored once a live
  ret exists; mid-session ret drops its greeting timeout (parity with bring-up);
  the pending pool is capped at 8.
Also ports wire.py's malformed-frame guard to shard/transport.py recv_msg (the
production path): the resilient tail now recv's stranger conns all session, so
one unparseable frame crashing the stage process went from a bring-up window to
a standing exposure. ConnectionError -> EDGE_ERRORS -> edge reset, not process
death.
…al GQA kernel, receipts

Port of eagle/tree-verify onto the merged serial-path base, with the 2026-07-02
fleet's payload verdict applied (the measured tok/s loss was ~6-7x self-inflicted
wire bytes, not physics). What changed vs the measured v1:
- propose_tree is best-first top-M by cumulative draft log-prob (M25_TREE_M/
  _TOPB/_DEPTH; default 12 nodes) on the O(1) drafter internals — kills the
  fixed-2^d shape (62 nodes bought +0.7 g) that made the 1.23MB aux return.
- Tree traffic rides _hsend/_hrecv: fp8 activations + fp8 aux per hop.
- attn_tree always runs the manual broadcast-GQA kernel (tree_spec._gqa_masked_
  attend): a dense mask knocks SDPA off flash on sm_120, and there is no
  repeat_interleave copy of the context; mask built once per stage-call on
  device in h's dtype.
- Tree blocks are attested (signer.observe both branches + receipt sweep +
  coverage verify in coordinate_pipe_tree) — verification no longer silently
  off under M25_TREE, and the tree A/B carries the same receipt cost as chain.
- Whole-prompt EAGLE prefill context (per-chunk extend), draft_s/ring_wait_s
  breakdown, loud no-aux guard, launcher forwards M25_TREE* + pushes tree_spec.
- tests/test_tree_spec.py (8): mask ancestor property vs brute force, walk vs
  exhaustive, chain degeneracy, kernel-vs-reference, RoPE gather, and
  propose_tree topb=1 == propose() exactly (the offline losslessness gate).
…edge fixed)

6xEU 5090 ring: coord kill -9 mid-decode (depth=4 in flight) -> new coordinator
completes on the same warm ring (6.70 -> 6.60 tok/s, weights never reloaded).
Stage logs show the ported recovery choreography (drop+rebuild forward links,
tail re-accepts channels). The re-warm-per-coordinator tax is gone.
…chain-tree

The tree coordinator drove the EAGLE drafter directly, silently dropping the
HybridDrafter's n-gram half; verbatim-reuse cells paid full EAGLE accept
(2026-07-02 warm A/B: rag-quote 5.6 -> 3.0 tok/s while every novel cell won).
Route per round: n-gram first, a real match verifies as a 1-wide tree (and now
banks the +1 correction/bonus token the chain path drops); miss -> EAGLE tree.
…+18%

Same-warm 6xEU ring, honest bench: chain 3.9 -> tree-hybrid 4.6 decode-weighted
(reason-math 4.8->6.0, reason-logic 3.0->4.7, code-edit 4.5->6.1). Flips the v1
"tree loses tok/s on tight rings" verdict — the loss was wire payload, as the
2026-07-02 fleet concluded. Losslessness gate: 76 identical tokens then one
near-tie kernel flip (documented class). rag-quote stays chain's win (pipelined
n-gram vs sync tree round) — depth-aware hybrid is the measured next lever.
Fold the 2026-07-02 review fleet (79 confirmed) into a clear tiered
roadmap: TIER 0 done (merged serial-path), TIER 1 perf (wedge, tree-
verify, topology, cheap levers), TIER 2 trust/moat (the CRITICAL self-
referential coverage check + 4 high), TIER 3 robustness (gateway/wire/
contained bugs), TIER 4 cleanup. Records the key signal: after the merge
only 1 remaining HIGH is perf — risk moved off the single-stream hot
path onto trust + gateway/wire. Adds the honest tok/s projection (~5.7-7
today -> ~10-12 good tight ring post-tree-verify+topology, ~5-6 global
scatter; most findings are NOT tok/s). Full per-finding detail in
.claude/plans/fleet-findings-20260702.md.
…erify wins EU +18%

RESUME HERE now points at the two validated branches (wedge+trust, tree-v2),
PROVEN gains the tree A/B + churn-survival rows, the trust row drops its
self-referential warning, and TIER-1.1/1.2 + TIER-2.1 are ticked. Next: land
both PRs, then the high-RTT global-scatter cell and the depth-aware hybrid.
leyten call (2026-07-02): steady-state rings are regional by construction
(select_ring picks close subsets; a global ring is a placement failure). The
global measure was go/no-go only while tree lost on tight rings; v2 wins the
tight EU ring, so nothing hangs on a global number. NEXT re-ranked: depth-aware
hybrid, then the RTT-ordered warm run for the absolute 10-12 check.
leyten and others added 15 commits July 2, 2026 22:56
…ith a pinned node

select_ring's >_TRIM latency funnel built its feasibility cover require-blind:
the pinned node's subnet slot went to the pool's fattest same-subnet card
(which can never co-ring with require), and k_min stopped the cover walk
before the other-subnet fat nodes a require-compatible ring needs — so the
widened fallback searched a starved pool and returned None while a feasible
ring existed. Fix: a second cover walk seeded with require (its subnet spent,
its capacity counted), purely additive to the trimmed pool. Regression test
fails pre-fix, passes post-fix; the legacy golden snapshot is byte-identical
(the funnel never fires there). Unblocks the RTT-ordered launch (TIER 1.3).
fix(topology): require-aware trim funnel — no more false-infeasible w…
…ke-ring harness (leyten#14)

Squash of perf/tree-depth-hybrid (11 commits): usability-report + CPU fake-ring harnesses, honest g + full-accept bonus, M25_STAGE_TIMING transport/compute split, select_ring head-first deployable orientation, depth-aware hybrid (pipelined n-gram bursts + sync tree rounds) with adversarial-review fixes, good-ring receipts (3 arms). 64 CPU tests green.
…he public internet

The c0mpute technical report (author: leyten), final copy: page-one hero
chart, the accept-gated pipelining law with a both-ends-calibrated
Monte-Carlo, the depth-aware hybrid router, the transport/compute split and
the CPU-launch-bound discovery, verification measured at 0.4% of stage
compute, and a live coordinator-kill recovery timeline. Ships with the
interleaved multi-arm bench harness (research/m25_paper_bench.py), four
receipt sets from the test evening (docs/receipts/m25-paper-*), and every
figure generated from those receipts (docs/paper/figures.py). 64 CPU tests
green.
Paper published on Zenodo (CC-BY-4.0, v1.0). DOI added to the paper title
block and the README, with the report linked from the top of the README.
Centered labels in the two segments collided where the segments were
narrower than the text. Transport labels now sit outside the bar end,
stage-compute labels stay inside their segment at a size that fits.
Reviewer feedback: one plain byline (leyten · c0mpute.ai · July 3, 2026)
replaces the author and links lines; no links on the front page. Abstract
now states the stages are untrusted, the defensible form of the trustless
claim (Limitations still names the freshness/replay gaps). Title unchanged.
The concept DOI resolves to the latest report version; version DOIs stay
valid for citations of a specific revision.
Reviewer feedback round two. 'Decentralized inference' placed in the intro
and related work, 'decentralized AI' once in the close; three placements
total, no stuffing. Conclusion gains a roadmap paragraph in which every
future item traces to a measured finding (drafter training toward the 0.8
crossover, freshness/spot-recompute verification, CPU-aware selection and
graph capture, right-sized models on the same fabric) and ends on the idle
consumer-GPU supply plus a commitment to keep publishing receipts.
coordinate_pipe_tree started its output accumulator at `out = [cur]`,
dropping every token committed before a mid-request node death. The
chain coordinator already does `out = resume_ids + [cur]`; the tree
path lost it, so under M25_TREE=1 a heal-and-resume returned only the
post-failure tokens and continuation_preserved failed.

Mirror the chain path exactly: seed `out` with resume_ids, and rebuild
the draft prefix from prompt_ids + out (not gen_ids + out) so resume_ids
is not double-counted. All three lines are no-ops when resume_ids is
empty (gen_ids == prompt_ids), so the normal serve path is unchanged.
The tail and head/middle serve loops caught only EDGE_ERRORS
(OSError/EOFError). Any other exception raised during a verify -- a
RuntimeError from an over-context request (RoPE table / static-KV
overflow), a CUDA OutOfMemoryError, a malformed frame -- propagated out
of serve() and terminated the stage process, dropping its warm weights
and taking the whole ring down until a manual relaunch. A request that
is merely oversized, reachable through the unvalidated gateway, was
enough to do it.

Restore specpipe's proven "bad msg -> reset" guard: add a parallel
except Exception to each loop that runs the same teardown as an edge
close (reset layers, drop the channels, let the ring re-handshake on
warm weights). EDGE_ERRORS is still matched first, so genuine edge
deaths keep their existing predecessor/return-channel semantics.
shard/transport.py is the production receive path (SHARD_TRANSPORT=libp2p).
The libp2p link authenticates the peer but the engine authorizes nothing,
so any ring neighbour can stream a frame. Two unbounded allocations:

  - recv_msg read a 64-bit length straight off the socket and buffered up
    to 2**64 bytes with no cap.
  - _unpack returned torch.empty(shape) for an empty blob, so a few header
    bytes declaring shape [65536,65536] allocated 16 GB of host RAM -- a
    memory-amplification bomb a frame-size cap alone would not catch.

Add SHARD_MAX_FRAME (default 2 GiB): a longer declared length is refused
before allocating, as a ConnectionError (an OSError -> a dead edge the
serve loop resets, not a crash). And require an empty blob to correspond
to a zero-element tensor; a non-empty shape with no bytes is rejected as
a malformed frame. Legitimate zero-element tensors still decode.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants