# Quickstart

> Extract oss-phoenix-artifacts, run run_pipeline.py, and recognize the ranked table as the success signal.

- Repository: xai-org/x-algorithm
- GitHub: https://github.com/xai-org/x-algorithm
- Human docs: https://grok-wiki.com/public/docs/xai-org-x-algorithm-23c09c39074c
- Complete Markdown: https://grok-wiki.com/public/docs/xai-org-x-algorithm-23c09c39074c/llms-full.txt

## Source Files

- `phoenix/README.md`
- `phoenix/run_pipeline.py`
- `phoenix/pyproject.toml`
- `.gitattributes`
- `phoenix/runners.py`

---

---
title: "Quickstart"
description: "Extract oss-phoenix-artifacts, run run_pipeline.py, and recognize the ranked table as the success signal."
---

`phoenix/run_pipeline.py` is the local end-to-end entry point in this checkout: it loads the published retrieval and ranker checkpoints, encodes `example_sequence.json`, retrieves from `sports_corpus.npz`, scores the top-K posts, and prints a ranked engagement table. That table is the success signal. Home Mixer, Thunder, and Grox are not part of this run.

<Note>
This page assumes a Python 3.11+ environment on Darwin or Linux. The `phoenix/uv.lock` environments list only `sys_platform == 'darwin'` and `sys_platform == 'linux'`. For LFS, `uv`/`pip`, and the extract layout in more detail, see [Installation](/installation).
</Note>

## What this run executes

`run_pipeline.py` composes the two Phoenix stages that production Home Mixer also uses, against a frozen sports corpus rather than live Thunder or cluster retrieval:

```text
example_sequence.json          retrieval/                     sports_corpus.npz
  user_id + history      +     config.json                    post_ids
                               model_params.npz               author_ids
                               embedding_tables.npz           candidate_representations
                                         │                              │
                                         ▼                              │
                              user representation  ── dot product ──────┘
                                         │
                                         ▼
                              top-K post / author IDs
                                         │
                                         ▼
                              ranker/  ──► sigmoid(logits) ──► demo weighted Score
                                         │
                                         ▼
                              PIPELINE RESULTS table
```

`run_ranker.py` and `run_retrieval.py` are leftover dummy-data scripts. They initialize random batches and do not load `oss-phoenix-artifacts`. Do not treat their output as a successful artifact run.

## Prerequisites

- Git LFS installed, and `phoenix/artifacts/oss-phoenix-artifacts.zip` materialized (not a 135-byte pointer).
- Working directory `phoenix/`.
- Python `>=3.11` with either `uv` or `pip`.
- Declared runtime deps from `phoenix/pyproject.toml`: `jax==0.8.1`, `dm-haiku>=0.0.13`, `numpy>=1.26.4`.

Confirm the archive is the real object before unzipping. The Git LFS pointer records size `2903518802` bytes (~2.70 GiB):

```bash
wc -c artifacts/oss-phoenix-artifacts.zip
# expected: 2903518802
```

If the file is ~135 bytes and starts with `version https://git-lfs.github.com/spec/v1`, run `git lfs pull` from the repository root. `.gitattributes` routes `*.zip` and `*.npz` through LFS.

## Extract the artifacts

<Steps>
<Step title="Change into phoenix/">

```bash
cd phoenix
```

</Step>
<Step title="Unzip into artifacts/">

```bash
unzip artifacts/oss-phoenix-artifacts.zip -d artifacts/
```

The zip’s top-level directory is `oss-phoenix-artifacts/`, so this creates `artifacts/oss-phoenix-artifacts/`. That nested directory is the `--artifacts_dir` you pass to the script.

</Step>
<Step title="Confirm the files the script opens">

`run_pipeline.py` opens these paths under `--artifacts_dir` (defaults shown in the next section). Missing any of them raises `FileNotFoundError` or `np.load` failure immediately.

</Step>
</Steps>

:::files
phoenix/artifacts/oss-phoenix-artifacts/
  retrieval/
    config.json
    model_params.npz
    embedding_tables.npz
  ranker/
    config.json
    model_params.npz
    embedding_tables.npz
  sports_corpus.npz
  example_sequence.json
:::

<Warning>
The script default `--artifacts_dir` is `./artifacts`, not `./artifacts/oss-phoenix-artifacts`. After the documented unzip, you must pass `--artifacts_dir artifacts/oss-phoenix-artifacts`. Pointing at `./artifacts` looks for `artifacts/retrieval/config.json`, which does not exist.
</Warning>

The Phoenix README documents the packaged sample as a frozen mini checkpoint plus a Sports-topic corpus and a three-post user history (NFL, NBA, NHL). `run_pipeline.py` does not hardcode those sizes; it reads `emb_size`, `num_actions`, `history_seq_len`, `candidate_seq_len`, hash parameters, and transformer fields from each `config.json`.

## Install and run

<Tabs>
<Tab title="uv">

```bash
cd phoenix
uv sync
uv run run_pipeline.py --artifacts_dir artifacts/oss-phoenix-artifacts
```

</Tab>
<Tab title="pip">

```bash
cd phoenix
pip install "jax==0.8.1" "dm-haiku>=0.0.13" "numpy>=1.26.4"
python run_pipeline.py --artifacts_dir artifacts/oss-phoenix-artifacts
```

</Tab>
</Tabs>

JAX is pinned to `0.8.1` in `pyproject.toml`. The first run compiles Haiku transforms for retrieval and ranking; later runs in the same process reuse those transforms.

### CLI flags

<ParamField body="--artifacts_dir" type="string" default="./artifacts">
Directory that contains `retrieval/`, `ranker/`, and (unless overridden) `example_sequence.json` and `sports_corpus.npz`. After the documented unzip, pass `artifacts/oss-phoenix-artifacts`.
</ParamField>

<ParamField body="--sequence_file" type="string">
User action history JSON. Default: `<artifacts_dir>/example_sequence.json`.
</ParamField>

<ParamField body="--corpus_file" type="string">
Corpus NPZ. Default: `<artifacts_dir>/sports_corpus.npz`.
</ParamField>

<ParamField body="--top_k_retrieval" type="int" default="200">
How many corpus posts to keep after the user-representation dot product. Capped at corpus length.
</ParamField>

<ParamField body="--top_k_display" type="int" default="30">
How many ranked rows to print. Capped at the retrieval K.
</ParamField>

<RequestExample>
```bash
uv run run_pipeline.py \
  --artifacts_dir artifacts/oss-phoenix-artifacts \
  --sequence_file artifacts/oss-phoenix-artifacts/example_sequence.json \
  --corpus_file artifacts/oss-phoenix-artifacts/sports_corpus.npz \
  --top_k_retrieval 200 \
  --top_k_display 30
```
</RequestExample>

## Success signal

A successful run logs load/retrieve/rank progress, then prints a 120-column table whose header is `PIPELINE RESULTS — User {user_id}`. That header, the per-row `https://x.com/a/status/{post_id}` URLs, and the closing `Weighted score range:` line are the verification signal.

Progress logs (INFO) include:

| Log | Meaning |
| --- | --- |
| `Loading retrieval model...` | `retrieval/model_params.npz` + unified hash table |
| `Loading ranker model...` | `ranker/model_params.npz` + unified hash table |
| `Loading corpus...` then `N posts, repr shape ...` | `sports_corpus.npz` loaded |
| `Loading user sequence from ...` then `User {id}, {n} history items` | JSON parsed |
| `Running retrieval...` then `User repr norm=...` | user tower applied |
| `Retrieved {K} (score range: low - high)` | corpus dot-product top-K |
| `Ranking {K} candidates...` | ranker batches of `candidate_seq_len` |

<ResponseExample>
```text
========================================================================================================================
PIPELINE RESULTS — User <user_id>
History: <n> items | Corpus: <n> posts
Retrieved top <K> → Ranked by engagement model
========================================================================================================================
Rank  Score    Ret     Fav     Reply   RT      Dwell   VQV     Topics                         Post URL
------------------------------------------------------------------------------------------------------------------------
1     <w>      <ret>   <p1>    <p4>    <p6>    <p11>   <p13>   <topic>                        https://x.com/a/status/<id>
...
<DISPLAY rows>

Weighted score range: [<worst>, <best>]
========================================================================================================================
```
</ResponseExample>

<Check>
The run succeeded when stdout contains `PIPELINE RESULTS — User`, `DISPLAY` ranked rows (`min(top_k_display, K)`), post URLs of the form `https://x.com/a/status/{id}`, and a `Weighted score range:` line. `uv run pytest` in `phoenix/` does **not** replace this signal: those tests use synthetic tensors and never open the artifact zip.
</Check>

### Printed columns

| Column | Source |
| --- | --- |
| `Rank` | `1 .. DISPLAY` after `argsort(-weighted)` |
| `Score` | demo weighted sum (below) |
| `Ret` | retrieval dot-product score for that corpus row |
| `Fav` | `sigmoid(logits)[:, 1]` (`IDX_FAV`, `SERVER_TWEET_FAV`) |
| `Reply` | index `4` (`SERVER_TWEET_REPLY`) |
| `RT` | index `6` (`SERVER_TWEET_RETWEET`) |
| `Dwell` | index `11` (`CLIENT_TWEET_RECAP_DWELLED`) |
| `VQV` | index `13` (`CLIENT_TWEET_VIDEO_QUALITY_VIEW`) |
| `Topics` | `sports_corpus.npz` `topics` (empty string if the key is absent), truncated to 28 characters |
| `Post URL` | `https://x.com/a/status/{post_id}` |

`IDX_QUOTE = 5` is defined in `run_pipeline.py` but is not printed and is not part of the demo weighted sum.

## Inputs the script actually reads

### `example_sequence.json`

Required shape consumed by `run_pipeline.py`:

```json
{
  "user_id": 123,
  "history": [
    {
      "post_id": 1,
      "author_id": 2,
      "actions": { "1": 1.0, "11": 1.0 }
    }
  ]
}
```

<ResponseField name="user_id" type="int">
Hashed with the retrieval and ranker `hash_params` independently.
</ResponseField>

<ResponseField name="history[].post_id" type="uint64">
Copied into a zero-padded vector of length `history_seq_len`. Extra items beyond that length are dropped (`history[:hist_len]`).
</ResponseField>

<ResponseField name="history[].author_id" type="uint64">
Same padding and truncation as `post_id`.
</ResponseField>

<ResponseField name="history[].actions" type="object">
String keys are `int`-parsed ActionName indices. Values are written into `history_actions[i, idx]` when `idx < num_actions`. Unknown or out-of-range indices are ignored.
</ResponseField>

Action indices used by the demo (`1`, `4`, `5`, `6`, `11`, `13`) follow the proto `ActionName` enum, not the 0-based order of `runners.ACTIONS`. Changing the sample history is covered on [Customize a user sequence](/customize-user-sequence).

### `sports_corpus.npz`

| Array | Role |
| --- | --- |
| `post_ids` | Candidate tweet IDs |
| `author_ids` | Author IDs aligned with `post_ids` |
| `candidate_representations` | Precomputed retrieval vectors; `scores = corpus_repr @ user_repr[0]` |
| `topics` | Optional; defaults to `""` per row if missing |

### Checkpoints

`runners.load_model_params` reads each `model_params.npz` as nested Haiku params (`key.split("/")` → module path + leaf). `load_embedding_table` expects `user_embeddings`, `item_embeddings`, and `author_embeddings`. `build_unified_emb_table` concatenates those tables behind a pad offset of `65`, then user, item, and author vocab blocks.

Retrieval sets `PhoenixRetrievalModelConfig.enable_linear_proj = True`. Ranking uses `PhoenixModelConfig` with `num_actions` and `post_age_granularity_mins` from `ranker/config.json` (granularity default `60` if omitted). Transformer `widening_factor` is hardcoded to `2.0` and `attn_output_multiplier` to `0.125`.

## Demo score versus production

The printed `Score` is a four-term demo combination, not Home Mixer’s `WeightedScorer`:

```python
weighted = (
    all_probs[:, IDX_FAV] * 1.0
    + all_probs[:, IDX_REPLY] * 0.5
    + all_probs[:, IDX_RT] * 0.3
    + all_probs[:, IDX_DWELL] * 0.2
)
```

Production `WeightedScorer` sums many more Phoenix heads (including quote, click, VQV-when-eligible, share, follow, and negative-feedback terms) and then offsets/normalizes. VQV is displayed in the quickstart table but is **not** in this demo sum. See [Multi-action scoring](/multi-action-scoring) and [Scorers and weights](/scorers-and-weights).

Ranking runs in chunks of `candidate_seq_len` (from `ranker/config.json`), pads a short final chunk with zeros, then slices logits back to the real candidate count before concatenating.

## Common failures

<AccordionGroup>
<Accordion title="zip is a Git LFS pointer">
`artifacts/oss-phoenix-artifacts.zip` is 135 bytes and `unzip` fails or extracts nothing useful. `.gitattributes` marks `*.zip` and `*.npz` as LFS. Install Git LFS, then `git lfs pull`. Expected size is `2903518802` bytes.
</Accordion>
<Accordion title="FileNotFoundError for retrieval/config.json">
`--artifacts_dir` points at `phoenix/artifacts` (the default) instead of `phoenix/artifacts/oss-phoenix-artifacts`, or the unzip landed in a different directory. Confirm `retrieval/config.json`, `ranker/config.json`, `sports_corpus.npz`, and `example_sequence.json` all sit directly under the directory you pass.
</Accordion>
<Accordion title="Missing sports_corpus.npz or example_sequence.json">
`--corpus_file` and `--sequence_file` default to those names inside `--artifacts_dir`. If you flattened the zip or renamed files, pass the flags explicitly.
</Accordion>
<Accordion title="KeyError on embedding tables or config keys">
`build_unified_emb_table` requires `user_embeddings`, `item_embeddings`, and `author_embeddings`. Hashing requires `hash_params` plus `user_vocab_size`, `item_vocab_size`, and `author_vocab_size`. Use the published archive; do not substitute the dummy tensors from `run_ranker.py` / `run_retrieval.py`.
</Accordion>
</AccordionGroup>

More LFS, `viewer_id`, Thunder, and Grox failures live on [Troubleshooting](/troubleshooting). This checkout cannot stand in for production Home Mixer; see [Runtime boundaries](/runtime-boundaries).

## Next

<CardGroup>
<Card title="Installation" href="/installation">
Python 3.11+, uv or pip, Git LFS, and the extract layout required before this command.
</Card>
<Card title="Run the inference pipeline" href="/run-inference-pipeline">
How checkpoints, `example_sequence.json`, and `sports_corpus.npz` are encoded, retrieved, and ranked.
</Card>
<Card title="Customize a user sequence" href="/customize-user-sequence">
`example_sequence.json` fields, ActionName indices, history padding, and the `--top_k_*` knobs.
</Card>
<Card title="Test Phoenix" href="/test-phoenix">
`uv run pytest` targets for attention-mask and retrieval assertions (not the artifact table).
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
LFS pointer files, wrong `--artifacts_dir`, and other checkout failures.
</Card>
<Card title="Runtime boundaries" href="/runtime-boundaries">
What this checkout can execute locally versus unpublished Home Mixer, Thunder, and Grox surfaces.
</Card>
</CardGroup>
