From 891acc1fe85af710bf14f7e523c59b83bd576344 Mon Sep 17 00:00:00 2001
From: salch-cred <141555468+salch-cred@users.noreply.github.com>
Date: Sat, 5 Sep 2026 16:14:26 +0530
Subject: [PATCH] docs(book): add DisTrO explainer chapter
---
psyche-book/src/SUMMARY.md | 1 +
psyche-book/src/explain/distro.md | 64 +++++++++++++++++++++++++++++++
2 files changed, 65 insertions(+)
create mode 100644 psyche-book/src/explain/distro.md
diff --git a/psyche-book/src/SUMMARY.md b/psyche-book/src/SUMMARY.md
index 1f3fa22ad..57c0685a8 100644
--- a/psyche-book/src/SUMMARY.md
+++ b/psyche-book/src/SUMMARY.md
@@ -16,6 +16,7 @@
- [Model sharing](./explain/model-sharing.md)
- [GCS Checkpoints](./explain/gcs-checkpoints.md)
- [Rewards](./explain/rewards.md)
+ - [DisTrO](./explain/distro.md)
- [Glossary](./explain/glossary.md)
- [Development](./development/index.md)
diff --git a/psyche-book/src/explain/distro.md b/psyche-book/src/explain/distro.md
new file mode 100644
index 000000000..1171ba974
--- /dev/null
+++ b/psyche-book/src/explain/distro.md
@@ -0,0 +1,64 @@
+# DisTrO
+
+**DisTrO** (Distributed Training Over the Internet) is the family of training optimizers Psyche uses to make decentralized training practical: it lets a group of untrusted machines across the internet jointly train a large model while exchanging only a small fraction of the data that a conventional data-parallel setup would require.
+
+The name covers a set of optimizer variants; the one used in this codebase lives in `shared/modeling/src/distro.rs` and this chapter explains how it works and how its knobs map to the [run configuration](../enduser/run-config.md).
+
+## The problem: synchronization bandwidth
+
+In synchronous data-parallel training, after every step each trainer must share its gradient update with every other trainer. For a large model this means moving hundreds of megabytes to gigabytes of gradient data per step per trainer — tolerable inside a data center with 400 Gbit fabrics, impossible between arbitrary internet nodes.
+
+DisTrO's insight is that this synchronization traffic is enormously redundant. Two changes make it practical to strip almost all of it out:
+
+1. **Compression via transform**: apply a per-parameter-block transform (here a DCT — Discrete Cosine Transform) that concentrates the meaningful signal of the update into a few large coefficients, then keep only the top-k of those.
+2. **Residual accumulation**: instead of communicating the full gradient each step, each node maintains a local *momentum residual* — the portion of past updates it has not yet managed to send. Small components that were dropped by top-k filtering are not lost: they accumulate in the residual and get picked up by later rounds' top-k when they grow large enough.
+
+## How a step works
+
+Here is the local, per-step flow inside the `Distro` optimizer:
+
+```mermaid
+flowchart TD
+ A["Receive & decode peers' compressed updates (previous round)"] --> B["Apply aggregated update to local weights"]
+ B --> C["Fetch assigned data batch & compute gradient g_t"]
+ C --> D["Update local momentum residual:
m_t = decay * m_t-1 + lr * g_t
(minus what was already sent)"]
+ D --> E["Encode residual with DCT"]
+ E --> F["Keep only top-k coefficients per block"]
+ F --> G{1-bit sign quantization enabled?}
+ G -->|yes| H["Send sparse indices + quantized signs"]
+ G -->|no| I["Send sparse indices + amplitudes"]
+ H --> J["Broadcast compressed update to peers (P2P)"]
+ I --> J
+```
+
+Step by step:
+
+1. **Decode peers' updates.** Each node receives its peers' compressed updates (`DistroResult`), batch-decompresses them, applies the inverse DCT, and aggregates them. Because the aggregation is a simple sum of sparse contributions, it does not require a central reducer.
+2. **Apply the aggregated update** to local weights. Since every node applies the same aggregate of the previous round, the weight vectors stay synchronized.
+3. **Compute the local gradient** on the data batch the [Coordinator](./general-workflow.md) assigned to this node for this round.
+4. **Update the local momentum residual.** The residual is decayed (`compression_decay`), the new gradient is folded in, and — crucially — the components that were actually *sent* last round are subtracted back out, so nothing gets double-counted.
+5. **Compress.** The residual is encoded with the DCT transform, and only the top-k coefficients per chunk are kept (`compression_topk`, chunks sized by `compression_chunk`). The result is a sparse representation: indices plus values (or, with 1-bit quantization, just the *signs* of the values — one bit per coefficient).
+6. **Broadcast.** The compressed update goes out to all peers over the P2P network ([Iroh](./glossary.md)). Everything above happens off-chain; the Solana coordinator only tracks the state machine, data assignments, and witness proofs — never the weight data itself.
+
+The effect: bandwidth per step drops by orders of magnitude compared to shipping raw gradients, which is what allows training to run over the open internet rather than a data-center fabric.
+
+## Where it fits in the run
+
+DisTrO is the "DisTrO Optimizer" box in the [decentralized training flow](./general-workflow.md#decentralized-backend): the Coordinator assigns data batches and witnesses per round, and the optimizer handles all weight synchronization client-to-client. The two systems meet at the round boundary — a round is complete once a [witness quorum](./glossary.md) confirms that the training results for the round's batches have been shared, and the loop advances with the next batch assignments.
+
+## Configuration
+
+DisTrO is selected in the run config through the optimizer definition. The knobs that matter:
+
+| Parameter | Meaning | Effect of increasing it |
+| --------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
+| `compression_decay` | Momentum decay for the local residual (the β of the momentum) | More long-range accumulation; updates persist longer |
+| `compression_chunk` | Target size (in elements) of the chunks the DCT is applied over | Larger transform blocks; more global coefficient structure |
+| `compression_topk` | Number of DCT coefficients kept per chunk (the k of top-k) | More faithful updates, more bandwidth per step |
+| `quantize_1bit` | Replace transmitted amplitudes with just their signs | ~16x less payload per coefficient, slightly noisier updates |
+
+These interact directly with `max_round_train_time` in the [run config](../enduser/run-config.md): the amount of data each node must transfer per round bounds how fast rounds can complete on slow uplinks, so aggressive compression (low `compression_topk`, `quantize_1bit` on) favors geographically distributed fleets.
+
+## Implementations elsewhere
+
+The DisTrO technique was introduced by Nous Research and is also used beyond this Rust codebase — for example in the Python training stack — but the variant this book documents is the Rust implementation in [`shared/modeling/src/distro.rs`](https://github.com/PsycheFoundation/nousnet/blob/main/shared/modeling/src/distro.rs) (see `Distro`, `TransformDCT`, and `CompressDCT`), with the wire format for its compressed results in `shared/network/src/serialized_distro.rs` and a CLI tool to expand/inspect them in `tools/rust-tools/expand-distro`.