Skip to content

Finish train + infernet: p2p dataset/adapter pieces, multi-turn chats, native p2p chat - #165

Merged
ralyodio merged 2 commits into
masterfrom
feat/train-infernet-finish
Nov 9, 2026
Merged

ralyodio merged 2 commits into
masterfrom
feat/train-infernet-finish

Conversation

@ralyodio

@ralyodio ralyodio commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

train (c0mpute train lora, infernet.text.train) → available

(a) Peer to peer instead of buyer-hosted HTTP

  • Buyer stores the corpus as 1 MiB content-addressed pieces (pieces::put_bytes_pieces) and names them in the spec (corpus_hash / corpus_size / corpus_pieces). The worker pulls them over libp2p (new InputRef::fetch_to_file, streamed) and checks the offer's sha256.
  • Training events go out as gossip on the job topic (run_id; step events throttled to 4/s).
  • Worker stores the adapter files as pieces behind an AdapterManifest chunk; the receipt's output_hash is the manifest hash. Buyer fetches manifest + pieces and keeps the adapter only if the weights match the manifest's sha256. Worker job dir is removed afterwards; worker adapter pieces expire after 6 h.
  • c0mpute infernet text chat (infernet.text.infer, the "use it" step) is p2p the same way: adapter out as pieces (only on a worker cache miss), answer back as a chunk.
  • Both standalone text nodes now serve chunks from a store (spawn_node_with_store).
  • The worker side lives in c0mpute-core; c0mpute does all transfer and hands Infernet's trainer local file paths, so no Infernet change was needed for transport.

Compatibility. HTTP fields became optional. Workers before this release require dataset_url / adapter_url, so they can't parse a pieces-only spec and never bid on one. --legacy-http makes the buyer also run the old transfer server so those workers can bid; a sha256: receipt means the old HTTP path, checked exactly as before. Old buyers' HTTP specs still parse and run on new workers. A new worker prefers pieces when both are present.

(b) Multi-turn chats. messages records stay whole (every turn, in order) and prompt/completion becomes a one-exchange conversation (the prompt verbatim, not "Brief me on: …"). Training them needs InfernetProtocol/infernet-protocol#161: the peft runner renders each conversation with the model's chat template, puts the loss on assistant turns only, and reports formats: ["text","messages"]. Workers advertise infernet:text:format:messages only when their runner reports it, and a buyer whose corpus has conversations requires that tag, so an older runner (which would drop them) never wins the job. {title, text} records are unchanged.

infernet (infernet.inference) → available

Before this, c0mpute chat only went through c0mpute.com's HTTP API (with sign-in), and c0mpute infernet chat handed off to the separate infernet CLI.

  • New built-in c0mpute-core/src/inference.rs: workers answer from their own OpenAI-compatible runtime (Ollama at 127.0.0.1:11434 by default, C0MPUTE_INFERENCE_URL for llama-server etc., off disables). New role inference, on by default, advertised only while /v1/models lists chat models (refreshed every minute): infernet:inference plus infernet:inference:model:<id>. The runtime URL comes only from operator config, never from a spec. Answers are stored as chunks and checked against the receipt.
  • c0mpute chat --p2p "…" (the default when not signed in) runs one auction per turn over libp2p, REPL included. The c0mpute jobs serve bridge accepts infernet.inference too.
  • What still needs the infernet binary: only the Infernet runtimes on workers: temporal (infernet.temporal.*) and text train/infer (peft). Buyers never need it, and neither side needs it for chat. Answers come back in one piece; tokens are not streamed across the network yet.

Tests (docker, --network none --offline)

  • cargo test --workspace --locked: all green (core lib 299 after merging master, cli 19).
  • text_train_e2e (new, 2 tests): a 3 MiB mixed corpus (text + prompt/completion + 5-turn chats) goes out as pieces on loopback to a worker with a fake peft runner (tests/fixtures/fake-infernet-text). It writes a deterministic 2.5 MiB adapter derived from the corpus it received. The test checks: progress over gossip, the adapter coming back as pieces and matching byte for byte, every conversation turn reaching the trainer, worker cleanup, a tampered manifest being refused, then adapter chat p2p with a worker-side cache.
  • inference_e2e (new): a multi-turn chat goes p2p through the bridge to a stand-in runtime (every turn forwarded in order), a named model works, an unserved model gets no bids, an invalid spec is refused. Also run against the live Ollama on dev2 (C0MPUTE_INFERENCE_LIVE_URL, host network): passed, and the model answered "Blue."
  • Unit tests: p2p spec validation, old-worker parse failure (compat), conversion keeping every turn, record cap across turns, format tags, inference spec/result validation, a runtime-down answer, the CLI p2p spec.
  • Not run: a real peft training job (no peft runtime on the test box). The multi-turn masking was checked in the Infernet PR against the real Qwen2.5 and SmolLM2 tokenizers.

🤖 Generated with Claude Code

ralyodio and others added 2 commits November 9, 2026 04:30
…native p2p chat

train (infernet.text.train, `c0mpute train lora`):
- The corpus goes out as content-addressed 1 MiB pieces from the buyer's
  chunk store; the worker pulls them over libp2p (InputRef::fetch_to_file,
  streamed) and checks the sha256 the offer names. Training events are
  published on the job topic (run_id, step events throttled to 4/s). The
  worker stores the adapter files as pieces behind an AdapterManifest and
  its receipt carries the manifest's content hash; the buyer fetches and
  keeps the adapter only if the weights match the manifest's sha256.
  Nothing needs a reachable HTTP address any more.
- `c0mpute infernet text chat` (infernet.text.infer) is p2p the same way:
  adapter out as pieces (fetched on a worker cache miss), answer back as a
  chunk named by the receipt.
- Compatibility: specs keep the HTTP fields optional. Workers before this
  release require dataset_url/adapter_url, so they never bid on a
  pieces-only job; `--legacy-http` makes the buyer also run the transfer
  server for them (receipt `sha256:` = the old HTTP path, checked as before).
  Old buyers' HTTP specs still parse and run on new workers.
- Multi-turn chats: messages records are kept whole (every turn, in order)
  and prompt/completion becomes a one-exchange conversation, instead of
  collapsing to a "Brief me on: <last user turn>" record. Infernet's runner
  trains them with the loss on assistant turns
  (InfernetProtocol/infernet-protocol#161) and reports formats:["messages"];
  workers then advertise infernet:text:format:messages and buyers whose
  corpus has conversations require it. {title, text} records are unchanged.
- module.toml: available.

infernet (infernet.inference):
- New built-in workload c0mpute-core/src/inference.rs: workers answer chat
  turns from their own OpenAI-compatible runtime (Ollama at 127.0.0.1:11434
  by default, C0MPUTE_INFERENCE_URL for llama-server etc., `off` disables).
  Role `inference`, on by default, advertised only while /v1/models lists
  chat models (refreshed every minute): infernet:inference plus a tag per
  model. Answers are stored as chunks and checked against the receipt.
- `c0mpute chat --p2p` (the default when not signed in) runs one auction
  per turn over libp2p, REPL included; the jobs bridge accepts it too.
  No c0mpute.com and no infernet binary on either side.
- module.toml: available; temporal and text workers still need the infernet
  CLI for Infernet's own runtimes.

Tests: text_train_e2e (3 MiB mixed corpus as pieces on loopback with a fake
peft runner, gossip progress, adapter pieces verified, then adapter chat p2p),
inference_e2e (multi-turn chat p2p via the bridge; also passed against the
live Ollama on dev2), unit tests for specs, compat, conversion and tags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
print("usage: infernet model generate --adapter <dir> | --input <request.json>", file=sys.stderr)
sys.exit(2)
req = json.load(open(flag("--input")))
weights = open(os.path.join(req["adapter_dir"], "adapter_model.safetensors"), "rb").read()
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

13 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 8 | LOW: 4

Severity Rule Location
HIGH js-ssrf-outbound-request apps/web/public/sw.js:24
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:53
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:80
MEDIUM sh-remote-script-execution scripts/dev-setup.sh:25
MEDIUM sh-eval-expansion scripts/dev-setup.sh:35
MEDIUM sh-remote-script-execution scripts/install.sh:173
MEDIUM sh-remote-script-execution scripts/install.sh:198
MEDIUM sh-remote-script-execution scripts/install.sh:316
MEDIUM sh-remote-script-execution scripts/install.sh:354
LOW py-ssrf-outbound-request examples/business-strategist-agent/chat.py:37
LOW py-ssrf-outbound-request examples/business-strategist-agent/train.py:54
LOW secret-generic-credential node/crates/c0mpute-core/src/webhook.rs:1165
LOW py-path-traversal node/crates/c0mpute-core/tests/fixtures/fake-infernet-text:29

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 0affb25 into master Nov 9, 2026
12 checks passed
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