> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinbase.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Weights pipeline

> The epoch weight flow: how the master collects raw challenge weights, normalizes emissions, maps hotkeys to Bittensor UIDs, and publishes the vector the submitter posts on-chain.

Every epoch the master turns each challenge's raw hotkey scores into a single,
chain-ready weight vector. This page traces that flow end to end, from the per-epoch
trigger to the on-chain `set_weights` call.

*Source: `README.md:127-135`; `src/platform_network/master/service.py:85-139`.*

<Card title="Source repository" icon="github" href="https://github.com/BaseIntelligence/base">
  The weight aggregator, master weight service, and the submit-only on-chain
  submitter.
</Card>

## The flow at a glance

epoch → master collects raw challenge weights → normalize each challenge → normalize
emissions → combine into per-hotkey scores → map miner hotkeys to Bittensor UIDs →
normalize the UID vector → publish at `/v1/weights/latest` → the on-chain submitter
posts it to the chain.

*Source: `src/platform_network/master/service.py:85-139`; `src/platform_network/master/aggregator.py:37-73`.*

```mermaid theme={"dark"}
flowchart TB
    epoch(["Epoch boundary"]) --> collect["1 · Collect raw challenge weights"]
    collect --> normch["2 · Normalize each challenge's weights"]
    normch --> normem["3 · Normalize emissions"]
    normem --> combine["4 · Combine into per-hotkey scores"]
    combine --> mapuid["5 · Map miner hotkeys to Bittensor UIDs<br/>(drop no-UID; exclude UID 0)"]
    mapuid --> normuid["6 · Normalize the UID vector"]
    normuid --> publish["7 · Publish at /v1/weights/latest"]
    publish --> submit["8 · On-chain submitter posts set_weights"]
    submit --> chain(["Bittensor chain"])
```

## 1. Collect raw challenge weights

The master resolves the active challenges and their per-challenge tokens, then asks
each challenge for its weights over the internal contract. A challenge that fails to
return weights raises and is surfaced as a failure for that epoch.

*Source: `src/platform_network/master/service.py:30-39`, `src/platform_network/master/service.py:63-83`.*

Each challenge implements the standard contract — an async `get_weights()` returning
a `dict[str, float]` of hotkey → raw score. Raw scores are acceptable as long as they
are finite and non-negative; the master normalizes them.

```python theme={"dark"}
async def get_weights() -> dict[str, float]:
    return {"5F...hotkey": 1.0}
```

*Source: `docs/challenge-integration.md:9-14`; `src/platform_network/master/challenge_client.py:31-43`.*

## 2. Normalize each challenge's weights

For each challenge, raw scores are cleaned and normalized: non-finite and
non-positive values are dropped, and the remainder is scaled to sum to `1.0`. A
challenge whose cleaned weights sum to zero contributes an empty map.

```python theme={"dark"}
def normalize_weights(raw: dict[str, float]) -> dict[str, float]:
    cleaned = _clean_weights(raw)            # finite and > 0 only
    total = sum(cleaned.values())
    if total <= 0:
        return {}
    return {hotkey: value / total for hotkey, value in cleaned.items()}
```

*Source: `src/platform_network/master/aggregator.py:9-26`.*

## 3. Normalize emissions

The configured emission share of each successful challenge is normalized across all
active challenges so the shares sum to `1.0`. If the total is zero, every active
challenge gets `0.0`.

*Source: `src/platform_network/master/aggregator.py:29-34`.*

## 4. Combine into per-hotkey scores

Each challenge's normalized per-hotkey weight is multiplied by that challenge's
normalized emission share, and the products are summed per hotkey. Challenges that
did not return weights successfully are skipped.

```python theme={"dark"}
for result in challenge_results:
    if not result.ok:
        continue
    emission = emissions.get(result.slug, 0.0)
    for hotkey, weight in normalize_weights(result.weights).items():
        hotkey_scores[hotkey] += emission * weight
```

*Source: `src/platform_network/master/aggregator.py:44-49`.*

## 5. Map miner hotkeys to Bittensor UIDs

The per-hotkey scores are mapped to on-chain UIDs using the metagraph cache. A
hotkey with no UID is dropped, and **UID `0` (the master UID) is excluded** from the
miner vector.

```python theme={"dark"}
for hotkey, weight in hotkey_scores.items():
    uid = hotkey_to_uid.get(hotkey)
    if uid is None:
        continue
    if uid == 0:
        continue
    uid_scores[uid] += weight
```

*Source: `src/platform_network/master/aggregator.py:51-60`; `src/platform_network/master/service.py:88`.*

The master UID default is `0`.

*Source: `src/platform_network/config/settings.py:17`.*

## 6. Normalize the UID vector

The UID scores are normalized to sum to `1.0`. If the total is zero (no eligible
miners), the vector falls back to `{0: 1.0}` — all weight on the master UID. The
result is returned as a `FinalWeights`, sorted by UID.

```python theme={"dark"}
total = sum(uid_scores.values())
if total > 0:
    normalized = {uid: value / total for uid, value in uid_scores.items()}
else:
    normalized = {0: 1.0}
```

*Source: `src/platform_network/master/aggregator.py:62-73`.*

## 7. Publish at `/v1/weights/latest`

`compute_latest_response` wraps the final vector in a `MasterWeightsResponse` — the
UIDs and weights, the per-hotkey weights, the source-challenge contributions, the
configured `netuid` and chain endpoint, and timestamps. `expires_at` is
`computed_at` plus the freshness window.

*Source: `src/platform_network/master/service.py:92-116`.*

The freshness window is `MASTER_WEIGHTS_FRESHNESS_SECONDS = 720` seconds, and
`expires_at` is validated to be in the future when the response is built.

*Source: `src/platform_network/schemas/weights.py:8`.*

The public read is served by the proxy at `GET /v1/weights/latest`; the full
response schema is on the [Weights API](/reference/api-weights) page.

*Source: `src/platform_network/master/app_admin.py:136-157`.*

## 8. Submit on-chain

The on-chain submitter fetches the published vector and submits it to Bittensor with
the validator hotkey. In the master's own epoch loop, `run_epoch` calls the weight
setter directly when configured to submit.

*Source: `src/platform_network/master/service.py:118-139`; `README.md:135`.*

The submit path is owned by the [validator network](/architecture/validator-network):
the submitter is a separate, submit-only process that reads `/v1/weights/latest` and
calls `set_weights`.

*Source: `deploy/swarm/submitter/run_submitter.py:89-144`.*

## Failure behavior

If a challenge fails its health check or `get_weights`, its contribution is zero for
that epoch; the master does not auto-disable it.

*Source: `docs/security.md:55`.*

## Related

<CardGroup cols={2}>
  <Card title="Validator network" icon="shield-halved" href="/architecture/validator-network">
    The submit-only process that posts the vector on-chain.
  </Card>

  <Card title="Weights API" icon="scale-balanced" href="/reference/api-weights">
    The response schema served at `/v1/weights/latest`.
  </Card>
</CardGroup>

## Sources

Citations reference the `base` repository pinned at SHA
`e33109bfa4f5054928c3b4d429be9cf35d36b166` (see `SOURCES.md`). Paths prefixed with
`src/platform_network/` are the internal Python package.
