Skip to content
log0u7Public

About

P2P distribution of LLM model artifacts: BitTorrent swarms for Hugging Face models, Hub fallback, sha256-verified, Ollama import

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

llmp2p

llmp2p: a clay 3D ant colony carrying a glowing model brain between tiny servers and the cloud

CI Release image pipeline Go License: MIT

P2P distribution of LLM model artifacts: pull Hugging Face model repositories through a BitTorrent swarm when peers exist, fall back to HTTPS from the Hub when they do not, and keep your models seeding for everyone else.

Why

GGUF models are 1-100+ GB files served over plain HTTP: every download hits the Hub again. llmp2p treats models as what they are, large immutable blobs that a swarm serves better than a single origin. The more popular a model, the faster it distributes.

Good fit: sharing GGUF repos, homelabs with several machines, sparing the Hub. Bad fit: first pull of a model nobody seeds.

Development, issues and releases happen on GitHub (log0u7/llmp2p). The GitLab project (6admin.io/docker/llmp2p) builds, signs and publishes the container image (see Container image).

Highlights:

  • End-to-end verification: every byte is checked against torrent piece hashes, the manifest's per-file sha256, and Hub-published LFS oids.
  • Signed manifests: publishers sign with ed25519 (llmp2p keygen); fetchers enforce an optional signer allowlist.
  • Decentralized discovery (pull --dht): BEP 44 mutable records signed by the publisher's key replace the bootstrap index; small manifests travel inside the record itself.
  • Private swarm mode (pull --swarm-key): DHT-only discovery under a shared key, no bootstrap origins, no Hub fallback.

Quickstart

Install the CLI and the daemon, from the release page (linux/macos/windows binaries; on macOS, clear the Gatekeeper flag on first run: xattr -d com.apple.quarantine llmp2p), or with Go 1.26+ (the go.mod pins the toolchain, GOTOOLCHAIN=auto handles it):

go install github.com/log0u7/llmp2p/cmd/llmp2p@latest
go install github.com/log0u7/llmp2p/cmd/llmp2pd@latest

Then pull, import, share:

llmp2p pull hf:Qwen/Qwen2.5-0.5B-Instruct-GGUF
llmp2p import hf:Qwen/Qwen2.5-0.5B-Instruct-GGUF --name qwen2.5-0.5b
ollama run qwen2.5-0.5b

llmp2pd
curl -s localhost:8347/api/v1/status | jq

Expected result after pull: a manifest <sha256> / infohash <40hex> line pair, and llmp2p list showing the model with its pinned revision.

Alternative: build from source

Instead of go install, build from a clone. mise pins the whole dev environment (go, golangci-lint, gitleaks) in mise.toml:

git clone https://github.com/log0u7/llmp2p.git && cd llmp2p
mise install      # first task run may ask for `mise trust`
make build        # bin/llmp2p + bin/llmp2pd

Container image

Multi-arch (linux/amd64 + linux/arm64) FROM scratch image of llmp2pd, built from the pinned release source and keyless cosign-signed by the GitLab image project, published to registry.gitlab.com/6admin.io/docker/llmp2p/llmp2p:

Tag Content Signed
vX.Y.Z release, matching the packaged upstream version yes (keyless cosign)
latest latest build from main no
<commit-sha> every pipeline run no

The registry is public: pull anonymously, no login required.

docker run -d --name llmp2pd -p 127.0.0.1:8347:8347 \
  -v llmp2p-data:/data \
  registry.gitlab.com/6admin.io/docker/llmp2p/llmp2p:v0.4.2

Full runbook (podman/quadlet, systemd service units, CLI usage, signature verification): https://gitlab.com/6admin.io/docker/llmp2p#readme.

Release signatures are keyless (Sigstore): the certificate is issued to the GitLab CI OIDC token of the image repo tag pipeline. The registry is public, so cosign needs no registry credentials. Verify with cosign:

cosign verify --experimental-oci11 \
  --certificate-oidc-issuer https://gitlab.com \
  --certificate-identity-regexp "project_id:87047483:ref_type:tag:ref:v0.4.2$" \
  -a tag=v0.4.2 \
  registry.gitlab.com/6admin.io/docker/llmp2p/llmp2p:v0.4.2

The certificate identity uses the immutable GitLab project id (87047483), not the project path.

Run as a service (bare metal)

Binaries are static: install llmp2pd (and llmp2p to manage models) from the release page and run the daemon as a user systemd service:

cp deploy/systemd/llmp2pd.service ~/.config/systemd/user/
# adjust ExecStart if the binary is not in ~/bin
systemctl --user daemon-reload
systemctl --user enable --now llmp2pd
journalctl --user -u llmp2pd -f

# keep it running without an active login session:
loginctl enable-linger "$USER"

macOS: deploy/macos/llmp2pd.plist (launchd). Windows: deploy/windows/nssm-llmp2pd.ps1 (NSSM). For a containerized service (docker/podman, quadlet): see the GitLab image runbook.

How a pull works

flowchart TD
    A["llmp2p pull hf:owner/repo"] --> B["Resolve on the Hub<br>pin revision, list files, LFS sha256"]
    B --> C{"Local files already match<br>the pinned revision?"}
    C -->|yes| Z["cache hit"]
    C -->|no| D{"Discovery<br>BEP 44 record (with --dht + allowlist)<br>or bootstrap index entry?"}
    D -->|"record or entry,<br>manifest known or embedded"| E["Join the swarm<br>BitTorrent DHT, BEP 9 metadata"]
    D -->|"nothing discoverable"| F
    E -->|"no data within grace"| F
    D2["--swarm-key: DHT only,<br>no origins, no Hub fallback"] -.-> D
    F["HTTPS download from the Hub<br>resume, sha256"]
    E --> G["Final per-file<br>sha256 verification"]
    F --> G
    G --> H[("Store")]
    H --> I["llmp2pd keeps it seeding"]
Loading

With --dht, the swarm entry (and for small models the manifest itself) is a record signed by the publisher's key: no bootstrap index needed. Every byte is verified twice: against torrent piece hashes during transfer, then against the manifest's per-file sha256 (Hub LFS oid).

Commands

Command Purpose
llmp2p pull <ref> pull a repo: swarm, then HTTP fallback (--dht, --swarm-key for decentralized/private discovery)
llmp2p seed <ref|.torrent> seed until interrupted
llmp2p import <ref|.gguf> register the GGUF in Ollama
llmp2p list show stored models
llmp2p verify <ref> re-verify a stored model's integrity
llmp2p remove <ref> remove a model from the store
llmp2p keygen generate the ed25519 publisher key used to sign manifests
llmp2pd background seeder + status API on 127.0.0.1:8347

Full grammar and flags: docs/reference/cli.md · daemon API: docs/reference/daemon-api.md.

Make a model discoverable

Every pull publishes to your local index. Two ways to share beyond your machine:

  • Open a PR adding the entry (and its manifest) to this repository's index.json: the shared trust root, discoverable by strangers with no key exchange. docs/how-to/contribute-index-entry.md
  • Publish into the DHT: run llmp2p keygen, then pull with --dht — a signed record makes the swarm discoverable to anyone trusting your public key (--allowed-signers, or --swarm-key for a private swarm). See docs/how-to/private-swarm.md.

Documentation

Organized as Diátaxis: start at docs/README.md.

I want to... Read
learn hands-on, start to finish tutorials/get-started.md
solve a specific task docs/how-to/
look up a command, flag, or schema docs/reference/
understand how and why it works docs/explanation/ (architecture, trust model, protocol)
know why the code looks this way docs/adr/ (8 MADR decision records)
review security audit findings docs/audits/

Status

v0.4.2, experimental: whole-repo pulls work end to end (swarm, DHT discovery, private swarms), the protocol may still change before 1.0. See ROADMAP.md.

Contributing

CONTRIBUTING.md: Conventional Commits, atomic changes, gitleaks pre-commit hook, tests required. Security issues: see docs/explanation/trust-model.md for what is protected before opening an issue.

License

MIT

About

P2P distribution of LLM model artifacts: BitTorrent swarms for Hugging Face models, Hub fallback, sha256-verified, Ollama import

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages