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

# AI Earning Quickstart

> Start earning from AI inference in under 2 hours - one GPU, one warm model, no active set membership required.

export const StyledStep = ({title, icon, titleSize = 'h3', iconColor = null, titleColor = null, children, className = '', style = {}, ...rest}) => {
  const styledTitle = titleColor ? <span style={{
    color: titleColor
  }}>{title}</span> : title;
  return <Step title={styledTitle} icon={icon} iconColor={iconColor || undefined} titleSize={titleSize} className={className} style={style} {...rest}>
      {children}
    </Step>;
};

export const StyledSteps = ({children, iconColor, titleColor, lineColor, iconSize = '24px', className = '', style = {}, ...rest}) => {
  const resolvedIconColor = iconColor || 'var(--accent-dark, #18794E)';
  const resolvedTitleColor = titleColor || 'var(--lp-color-accent)';
  const resolvedLineColor = lineColor || 'var(--lp-color-accent)';
  return <div className={['docs-styled-steps', className].filter(Boolean).join(' ')} style={style} {...rest}>
      <style>{`
        .docs-styled-steps .steps > div > div.absolute > div {
          background-color: ${resolvedIconColor};
        }
        .docs-styled-steps .steps > div > div.w-full > p {
          color: ${resolvedTitleColor};
        }
        .docs-styled-steps .steps > div > div.absolute.w-px {
          background-color: ${resolvedLineColor};
        }
        .docs-styled-steps .steps > div:last-child > div.absolute.w-px::after {
          content: '';
          position: absolute;
          bottom: 0;
          left: 50%;
          transform: translateX(-50%);
          width: 6px;
          height: 6px;
          background-color: ${resolvedLineColor};
          transform: translateX(-50%) rotate(45deg);
        }
      `}</style>
      <div>
        <Steps>{children}</Steps>
      </div>
    </div>;
};

export const TableCell = ({children, align = "left", header = false, style = {}, className = "", ...rest}) => {
  const Component = header ? "th" : "td";
  return <Component className={className} style={{
    padding: "0.75rem 1rem",
    textAlign: align,
    border: header ? "none" : "1px solid var(--lp-color-border-default)",
    ...style
  }} {...rest}>
      {children}
    </Component>;
};

export const TableRow = ({children, header = false, hover = false, style = {}, className = "", ...rest}) => {
  const rowId = `table-row-${Math.random().toString(36).substr(2, 9)}`;
  return <>
      {hover && <style>{`
          #${rowId}:hover {
            background-color: var(--lp-color-bg-card);
          }
        `}</style>}
      <tr id={rowId} className={className} style={{
    ...header && ({
      backgroundColor: "var(--lp-color-accent-strong)",
      color: "var(--lp-color-on-accent)",
      fontWeight: "bold"
    }),
    ...style
  }} {...rest}>
        {children}
      </tr>
    </>;
};

export const StyledTable = ({children, variant = "default", style = {}, className = "", ...rest}) => {
  const wrapperVariants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)",
      overflow: "hidden"
    },
    bordered: {
      border: "2px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-page)",
      overflow: "hidden"
    },
    minimal: {
      border: "none",
      backgroundColor: "transparent",
      overflow: "visible"
    }
  };
  return <div data-docs-styled-table-shell className={className} style={{
    width: "100%",
    padding: 0,
    margin: 0,
    ...wrapperVariants[variant],
    ...style
  }} {...rest}>
      <table data-docs-styled-table style={{
    width: "100%",
    borderCollapse: "collapse",
    borderSpacing: 0,
    margin: 0,
    backgroundColor: "transparent"
  }}>
        {children}
      </table>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

<Tip>
  AI inference routing follows capability and price. A node with a 24 GB GPU and a warm model enters the routing pool as soon as registration propagates, without waiting for active-set entry or a large LPT bond.
</Tip>

***

This tutorial gets an Orchestrator earning from AI inference in under two hours. One GPU, one warm diffusion model, no Active Set membership needed. Estimated time: **1.5 to 2.5 hours** (most of this is model download time - approximately 6 GB).

**You will verify:**

* go-livepeer starts with the AI worker enabled
* The warm model registers at `tools.livepeer.cloud/ai/network-capabilities`
* A local test inference returns a result
* The node is live on the Livepeer AI network

<CustomDivider />

## Prerequisites

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Requirement</TableCell>
      <TableCell header>Minimum</TableCell>
      <TableCell header>Verify with</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>Linux server</TableCell>
      <TableCell>Ubuntu 22.04+</TableCell>
      <TableCell>`lsb_release -a`</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>NVIDIA GPU</TableCell>
      <TableCell>24 GB VRAM for diffusion models (8 GB for LLM or audio pipelines)</TableCell>
      <TableCell>`nvidia-smi` - shows GPU and VRAM</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>NVIDIA drivers</TableCell>
      <TableCell>CUDA 12.0 compatible (`525.60.13`+)</TableCell>
      <TableCell>`nvidia-smi` shows driver version</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Docker + NVIDIA Container Toolkit</TableCell>
      <TableCell>Docker Engine 24+</TableCell>
      <TableCell>`docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi`</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Arbitrum One RPC endpoint</TableCell>
      <TableCell>Free tier (Alchemy / Infura)</TableCell>
      <TableCell>Must return `"result":"0xa4b1"` for `eth_chainId`</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>ETH on Arbitrum One</TableCell>
      <TableCell>\~0.005 ETH for AI registration gas</TableCell>
      <TableCell>Check your wallet on [Arbiscan.io](https://arbiscan.io)</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Disk space</TableCell>
      <TableCell>20 GB free on the model directory partition</TableCell>
      <TableCell>`df -h ~/.lpData/`</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

<CustomDivider />

## Step 1: Install go-livepeer and pull the AI Runner

<StyledSteps>
  <StyledStep title="Pull the go-livepeer Docker image">
    ```bash icon="terminal" filename="pull-livepeer" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker pull livepeer/go-livepeer:latest
    ```

    Verify:

    ```bash icon="terminal" filename="verify-livepeer" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker run --rm livepeer/go-livepeer:latest livepeer --version
    ```
  </StyledStep>

  <StyledStep title="Verify GPU access for the AI runner">
    go-livepeer uses Docker-out-of-Docker to manage AI Runner containers. Verify the container runtime can see the GPU:

    ```bash icon="terminal" filename="verify-gpu" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi
    ```

    Expected: the `nvidia-smi` table showing your GPU, driver version, and VRAM. Re-install the NVIDIA Container Toolkit until this command succeeds.
  </StyledStep>

  <StyledStep title="Create directories and volume">
    ```bash icon="terminal" filename="setup-dirs" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    mkdir -p ~/.lpData/models
    docker volume create livepeer-ai-data
    ```
  </StyledStep>
</StyledSteps>

<CustomDivider />

## Step 2: Write aiModels.json

`aiModels.json` declares which AI pipelines and models the node serves, and at what price.

<StyledSteps>
  <StyledStep title="Choose your model">
    For a 24 GB GPU, `ByteDance/SDXL-Lightning` is the recommended starting model for `text-to-image`. It is fast (4 inference steps), in high demand, and approximately 6 GB on disk.

    Check current network demand at [tools.Livepeer.cloud/ai/network-capabilities](https://tools.livepeer.cloud/ai/network-capabilities) before deciding. The models with the most Gateway registrations receive the most routing traffic.
  </StyledStep>

  <StyledStep title="Create aiModels.json">
    ```bash icon="terminal" filename="create-aimodels" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    cat > ~/.lpData/aiModels.json << 'EOF'
    [
      {
        "pipeline": "text-to-image",
        "model_id": "ByteDance/SDXL-Lightning",
        "price_per_unit": 4768371,
        "warm": true
      }
    ]
    EOF
    ```

    Field reference:

    * `pipeline` - the pipeline type (must match exactly)
    * `model_id` - the HuggingFace model ID (case-sensitive, including the org prefix)
    * `price_per_unit` - price in wei per output pixel (`4768371` ≈ \$0.0005 per megapixel at late-2025 ETH rates)
    * `warm: true` - load the model into VRAM at startup for instant first-response

    Check current competitive pricing at [tools.Livepeer.cloud/ai/network-capabilities](https://tools.livepeer.cloud/ai/network-capabilities) before finalising `price_per_unit`. Gateways skip Orchestrators priced above their cap.
  </StyledStep>
</StyledSteps>

<CustomDivider />

## Step 3: Pre-download the model

Download model weights before starting the node. The warm model must be available at startup or the AI worker will attempt to download it on-demand, which delays the first inference.

```bash icon="terminal" filename="download-model" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
docker run --rm \
  -v ~/.lpData/models:/models \
  --gpus all \
  livepeer/ai-runner:latest \
  bash -c "PIPELINE=text-to-image MODEL_ID=ByteDance/SDXL-Lightning bash /app/dl_checkpoints.sh"
```

This downloads approximately 6 GB. On a 100 Mbps connection, expect 8 to 12 minutes. Watch for the download progress output.

Verify the model files are present:

```bash icon="terminal" filename="verify-model" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
ls -lh ~/.lpData/models/
```

A non-empty directory confirms the download completed.

<CustomDivider />

## Step 4: Start go-livepeer with AI worker

<StyledSteps>
  <StyledStep title="Start the node">
    ```bash icon="terminal" filename="start-ai-node" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker run -d \
      --name livepeer-ai-orchestrator \
      -v ~/.lpData/:/root/.lpData/ \
      -v /var/run/docker.sock:/var/run/docker.sock \
      --network host \
      --gpus all \
      livepeer/go-livepeer:latest \
      -network arbitrum-one-mainnet \
      -ethUrl https://arb-mainnet.g.alchemy.com/v2/YOUR_API_KEY \
      -orchestrator \
      -transcoder \
      -nvidia 0 \
      -pricePerUnit 1000 \
      -serviceAddr YOUR_PUBLIC_IP:8935 \
      -aiWorker \
      -aiModels /root/.lpData/aiModels.json \
      -aiModelsDir /root/.lpData/models
    ```

    Replace `YOUR_API_KEY` and `YOUR_PUBLIC_IP`. The three AI-specific flags:

    * `-aiWorker` - enables the AI inference worker
    * `-aiModels` - path to `aiModels.json` inside the container
    * `-aiModelsDir` - path to model weights on the **host machine** (Docker mounts this into AI Runner containers)

    <Warning>
      `-aiModelsDir` must be a host path, not a path inside the container. Docker uses this path to mount model files into AI Runner containers via Docker-out-of-Docker. A container-local path will not resolve correctly.
    </Warning>
  </StyledStep>

  <StyledStep title="Watch for AI worker startup">
    ```bash icon="terminal" filename="watch-startup" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker logs -f livepeer-ai-orchestrator 2>&1 | grep -i "ai-runner\|pipeline\|warm\|model\|container"
    ```

    Expected lines (may take 2 to 5 minutes while the model loads into VRAM):

    ```text icon="terminal" title="Expected startup lines" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    Starting AI worker
    Pulling livepeer/ai-runner container
    Pipeline text-to-image started
    Warm model loaded: ByteDance/SDXL-Lightning
    ```

    A missing `Warm model loaded` line after 10 minutes points to a startup problem. Check for errors:

    ```bash icon="terminal" filename="check-errors" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker logs livepeer-ai-orchestrator 2>&1 | grep -i "error\|fail\|oom" | tail -20
    ```
  </StyledStep>
</StyledSteps>

<CustomDivider />

## Step 5: Register AI capabilities

<StyledSteps>
  <StyledStep title="Activate on-chain for a new node">
    Existing video Orchestrators can keep their current on-chain registration. New AI-only nodes should run `livepeer_cli` to register:

    ```bash icon="terminal" filename="activate" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker exec -it livepeer-ai-orchestrator livepeer_cli
    ```

    Select **"Invoke multi-step become an Orchestrator"**. Minimal LPT self-delegation is sufficient for AI-only operation - the Active Set threshold applies to video transcoding routing, not AI routing.
  </StyledStep>

  <StyledStep title="Verify local capability registration">
    ```bash icon="terminal" filename="check-capabilities" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    curl http://localhost:7935/getNetworkCapabilities | python3 -m json.tool
    ```

    Expected: a JSON response with a `pipelines` array that includes `text-to-image` with your model listed.
  </StyledStep>

  <StyledStep title="Verify external network registration">
    Open [tools.Livepeer.cloud/ai/network-capabilities](https://tools.livepeer.cloud/ai/network-capabilities).

    Search by your Ethereum address or service URI. Your node should appear under `text-to-image` with status **Warm** within 2 to 5 minutes of the AI worker reporting the model loaded.

    A missing entry after 10 minutes points to a registration or container problem. Inspect container status:

    ```bash icon="terminal" filename="check-containers" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker ps --filter name=livepeer
    ```

    All containers (`livepeer-ai-orchestrator` and the AI Runner container) should show `Up`. A restarting AI Runner container indicates a model load failure.
  </StyledStep>
</StyledSteps>

<CustomDivider />

## Step 6: Test a local inference

Send a test inference request directly to the Orchestrator to confirm the pipeline is serving:

```bash icon="terminal" filename="test-inference" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
curl -X POST http://localhost:8935/text-to-image \
  -H "Content-Type: application/json" \
  -d '{
    "model_id": "ByteDance/SDXL-Lightning",
    "prompt": "a blue mountain lake at sunrise",
    "width": 512,
    "height": 512,
    "num_inference_steps": 4
  }' \
  -o test-output.png
```

Verify the response:

```bash icon="terminal" filename="check-output" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
file test-output.png
ls -lh test-output.png
```

Expected: `test-output.png: PNG image data` with a non-zero file size. The PNG is the generated image.

<Note>
  The first inference after startup is slower than later requests because the CUDA kernels warm up. That is normal. From the second request onward, SDXL-Lightning at 512×512 should complete in under 5 seconds.
</Note>

<CustomDivider />

## What happened

The node completed the AI inference path:

1. **go-livepeer started with `-aiWorker`** - it read `aiModels.json`, pulled the `livepeer/ai-runner` container, mounted the model weights from the host, and loaded `ByteDance/SDXL-Lightning` into GPU VRAM as a warm model.
2. **Capability advertisement** - go-livepeer registered the `text-to-image` pipeline and warm model status on-chain (via the AI Service Registry) and announced it to the network. Gateways that query for `text-to-image` capability now see this node in their routing pool.
3. **The test inference** travelled from the local HTTP call to the AI worker, to the AI Runner container, through SDXL-Lightning's inference pipeline, and back as a PNG. In production, this same path is triggered by a Gateway routing an inference request.
4. **Earning begins** when a Gateway routes a job to this node. Each completed job sends a PM ticket worth approximately `price_per_unit × pixels_in_output` wei. See <LinkArrow href="/v2/orchestrators/guides/payments-and-pricing/payment-receipts" label="Payment Receipts" newline={false} /> for how winning tickets translate to ETH.

**AI routing ignores active-set rank.** It checks capability advertisement and price instead. A new node with a warm model and a competitive price starts receiving AI jobs on the same day it registers.

<CustomDivider />

## Related pages

<CardGroup cols={2}>
  <Card title="Add AI to a Video Node" icon="layer-group" href="/v2/orchestrators/guides/tutorials/add-ai-to-video-node" arrow horizontal>
    Add AI to an existing video Orchestrator without touching the video configuration.
  </Card>

  <Card title="Model Demand Reference" icon="chart-bar" href="/v2/orchestrators/guides/ai-and-job-workloads/model-demand-reference" arrow horizontal>
    Which models are receiving the most Gateway requests and at what price tiers.
  </Card>

  <Card title="Pricing Strategy" icon="tag" href="/v2/orchestrators/guides/config-and-optimisation/pricing-strategy" arrow horizontal>
    Set AI pricing in aiModels.json: wei vs USD notation and competitive positioning.
  </Card>

  <Card title="AI Model Management" icon="sliders" href="/v2/orchestrators/guides/config-and-optimisation/ai-model-management" arrow horizontal>
    Warm vs cold strategy, VRAM allocation, and optimisation flags.
  </Card>
</CardGroup>
