> ## 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.

# Capacity Planning

> Determine the correct -maxSessions value for your orchestrator using livepeer_bench, bandwidth calculations, and NVENC hardware session limits. Covers GPU and CPU transcoding capacity, AI VRAM budgeting, and session limit configuration.

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>
  Set `-maxSessions` to the minimum of your hardware limit and your bandwidth limit. Too low wastes capacity and limits earnings. Too high pushes transcoding behind live segment cadence – segments pile up, Gateways log errors, and your reputation score suffers.
</Tip>

***

`-maxSessions` is the ceiling on concurrent transcoding streams your Orchestrator accepts. Go-livepeer defaults to 10 sessions. For most hardware, this is either too conservative (leaving GPU capacity unused) or too aggressive (exceeding available bandwidth). The correct value requires measurement.

Two constraints determine the session limit independently:

* **Hardware limit** – the number of concurrent sessions the GPU transcodes within live segment timing, measured with `livepeer_bench`
* **Bandwidth limit** – the number of concurrent sessions your connection carries within available upload bandwidth

The session limit is `min(hardware_limit, bandwidth_limit)`.

Video transcoding capacity and AI inference capacity use separate limits and separate mechanisms. Video transcoding sessions. AI capacity is configured per pipeline via the `capacity` field in `aiModels.json`.

<CustomDivider />

## Measuring hardware capacity

`livepeer_bench` simulates network workloads with segments arriving at live pace across multiple concurrent sessions. It reports a duration ratio: total transcoding time divided by total source duration. A ratio below 1.0 means the GPU is keeping up. A ratio above 1.0 means it is falling behind.

### Installing livepeer\_bench

`livepeer_bench` ships with go-livepeer. Verify it is on PATH:

```bash icon="terminal" title="Check livepeer_bench availability" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
livepeer_bench -help
```

A missing command usually means go-livepeer is installed outside PATH. Add the binary directory to PATH or call the binary directly. The binary sits alongside `livepeer` and `livepeer_cli`.

### Setting up the benchmark

<StyledSteps>
  <StyledStep title="Download the test stream">
    ```bash icon="terminal" title="Download the benchmark stream" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    wget -c https://storage.googleapis.com/lp_testharness_assets/bbb_1080p_30fps_1min_2sec_hls.tar.gz
    tar -xvf bbb_1080p_30fps_1min_2sec_hls.tar.gz
    ls bbb/
    # source.m3u8 plus a set of .ts segment files
    ```
  </StyledStep>

  <StyledStep title="Download the rendition config">
    ```bash icon="terminal" title="Download the rendition config" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    wget -O transcodingOptions.json \
      https://raw.githubusercontent.com/livepeer/go-livepeer/master/cmd/livepeer_bench/transcodingOptions.json
    ```

    This config defines the four standard output renditions (240p, 360p, 480p, 720p). It is the same rendition set most Gateways use on the network.
  </StyledStep>

  <StyledStep title="Run at increasing concurrency">
    Run the benchmark at increasing `-concurrentSessions` values until the ratio exceeds 0.8:

    ```bash icon="code" title="session-scaling.sh" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    #!/bin/bash
    for i in {1..20}
    do
        livepeer_bench \
            -in bbb/source.m3u8 \
            -transcodingOptions transcodingOptions.json \
            -nvidia 0 \
            -concurrentSessions $i |& grep "Duration Ratio" >> bench.log
    done
    ```

    For multiple GPUs, pass both IDs:

    ```bash icon="terminal" title="Run livepeer_bench on multiple GPUs" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    -nvidia 0,1
    ```

    For CPU-only transcoding, omit the `-nvidia` flag entirely.
  </StyledStep>
</StyledSteps>

### Reading the output

The summary table appears after each run:

```text icon="terminal" title="Sample benchmark output" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
*------------------------------*---------------------*
| Concurrent Sessions          | 4                   |
| Real-Time Segs Transcoded    | 120                 |
| * Real-Time Segs Ratio *     | 1                   |
| Total Source Duration        | 240s                |
| Total Transcoding Duration   | 18.42s              |
| * Real-Time Duration Ratio * | 0.0768              |
*------------------------------*---------------------*
```

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Ratio</TableCell>
      <TableCell header>Meaning</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>**\< 1.0**</TableCell>
      <TableCell>GPU has headroom and handles this session count within live segment pace</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>**= 1.0**</TableCell>
      <TableCell>Exactly on live pace – no headroom; any spike causes lag</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>**> 1.0**</TableCell>
      <TableCell>GPU overloaded at this session count – reduce</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

**Production threshold:** the last concurrent session count where the ratio stays at or below 0.8 is the hardware limit. The 20% headroom absorbs upload/download overhead and transient load spikes.

Example output from the scaling script:

```text icon="terminal" title="Benchmark session scaling example" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
| * Real-Time Duration Ratio * | 0.058  |    # 1 session
| * Real-Time Duration Ratio * | 0.114  |    # 2 sessions
| * Real-Time Duration Ratio * | 0.421  |    # 3 sessions
| * Real-Time Duration Ratio * | 0.783  |    # 4 sessions — last below 0.8
| * Real-Time Duration Ratio * | 1.102  |    # 5 sessions — exceeds threshold
```

Hardware limit in this example: **4 sessions**.

### NVENC hardware session caps

Consumer NVIDIA GPUs enforce a hard limit of 3 to 8 concurrent NVENC sessions in the driver, regardless of VRAM or compute capacity. This limit is imposed by NVIDIA in consumer-grade drivers to differentiate from professional-grade Quadro and datacenter cards.

The benchmark reflects this cap with a sharp ratio jump at the NVENC ceiling, even when VRAM and compute remain available. Professional GPUs such as the A100 and H100 are outside this consumer driver restriction.

### CPU transcoding

For CPU-only setups, omit `-nvidia` from the benchmark command. Start at `-concurrentSessions 1` and increase. CPU transcoding produces significantly higher ratios per session than GPU. Use the benchmark result directly. Treat older rule-of-thumb figures as historical context only, because modern CPUs (Ryzen 9 7950X, Threadripper PRO) handle more sessions than older guidance suggests.

<CustomDivider />

## Calculating bandwidth capacity

Every transcoding session consumes upload and download bandwidth. The current standard rendition set totals approximately **5.65 Mbps upload per stream** (sum of all output renditions). Source resolution determines download volume, so budget **\~6 Mbps symmetric** per stream to cover both directions with margin.

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Available bandwidth</TableCell>
      <TableCell header>Theoretical max (streams)</TableCell>
      <TableCell header>With 20% margin</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>100 Mbps symmetric</TableCell>
      <TableCell>\~16 streams</TableCell>
      <TableCell>\~13 streams</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>500 Mbps symmetric</TableCell>
      <TableCell>\~83 streams</TableCell>
      <TableCell>\~66 streams</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>1 Gbps symmetric</TableCell>
      <TableCell>\~166 streams</TableCell>
      <TableCell>\~133 streams</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

Use the upload rate as the primary constraint. Residential connections with 100 Mbps download commonly have 20 to 30 Mbps upload, so the upload cap usually dominates.

<CustomDivider />

## Setting maxSessions

The session limit is `min(hardware_limit, bandwidth_limit)`. Example calculation:

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Input</TableCell>
      <TableCell header>Value</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>Hardware limit (from benchmark)</TableCell>
      <TableCell>12 sessions</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Available bandwidth</TableCell>
      <TableCell>100 Mbps symmetric</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Bandwidth limit (100 Mbps ÷ 6 Mbps, 20% margin)</TableCell>
      <TableCell>\~13 sessions</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>**Session limit**</TableCell>
      <TableCell>**min(12, 13) = 12**</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

Apply the limit in your startup command:

```bash icon="terminal" title="Set maxSessions at startup" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
livepeer \
    -orchestrator \
    -transcoder \
    -maxSessions 12 \
    -network arbitrum-one-mainnet \
    ...
```

For a split Orchestrator/transcoder setup, set `-maxSessions` on both nodes – the Orchestrator uses it to track total capacity; the transcoder uses it to control how many concurrent jobs it accepts.

<CustomDivider />

## AI inference and VRAM capacity

AI inference capacity is separate from video transcoding capacity. `-maxSessions` has no effect on AI pipeline concurrency. The `capacity` field in each `aiModels.json` entry controls how many concurrent inference requests that pipeline accepts.

VRAM is the binding constraint for AI capacity. A 24 GB GPU holds one large diffusion model warm, or multiple smaller pipelines simultaneously:

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Pipeline</TableCell>
      <TableCell header>Warm model VRAM</TableCell>
      <TableCell header>Notes</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>`text-to-image` (SDXL-Lightning)</TableCell>
      <TableCell>\~12–18 GB</TableCell>
      <TableCell>Lightning models lean toward 12 GB end</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>`image-to-image` (SDXL)</TableCell>
      <TableCell>\~12–18 GB</TableCell>
      <TableCell>Same architecture as text-to-image</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>`audio-to-text` (Whisper large-v3)</TableCell>
      <TableCell>\~3 GB</TableCell>
      <TableCell>VRAM-efficient; pairs well with other pipelines</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>`image-to-text` (BLIP)</TableCell>
      <TableCell>\~1–2 GB</TableCell>
      <TableCell>Entry-level pipeline; runs on 4 GB GPUs</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>`llm` (Llama 3.1 8B quantised)</TableCell>
      <TableCell>\~8 GB</TableCell>
      <TableCell>Via Ollama q4 quantisation; accessible on 8 GB GPUs</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>`upscale` (SD x4 upscaler)</TableCell>
      <TableCell>\~8 GB</TableCell>
      <TableCell>Smaller than generation models</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>`segment-anything-2` (SAM2 large)</TableCell>
      <TableCell>\~12–24 GB</TableCell>
      <TableCell>Depends on model variant</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

**Beta constraint:** Only one warm model per GPU is supported during the Beta phase. Additional entries with `"warm": true` beyond the number of GPUs will cause a conflict at startup. Keep additional pipelines cold or assign them to separate GPUs.

**Video vs AI VRAM:** NVENC and NVDEC use dedicated hardware blocks and consume minimal VRAM for video transcoding. Running video sessions alongside warm AI models on the same GPU is supported, and AI model footprint remains the main VRAM constraint.

<CustomDivider />

## Tuning after going live

The benchmark estimate is a starting point. Live network conditions add variables the benchmark omits:

* Actual segment sizes and bitrates from Gateways vary from the test stream
* Upload latency and jitter add overhead beyond raw bandwidth measurements
* Reward calls and ticket redemptions consume CPU and network intermittently

Monitor these Prometheus metrics after activation:

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Metric</TableCell>
      <TableCell header>Watch for</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>`livepeer_transcode_duration_seconds`</TableCell>
      <TableCell>Should stay below 2 seconds per session (the segment duration)</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>`livepeer_real_time_seg_transcoded`</TableCell>
      <TableCell>Drops below 1 when segments fall behind live segment cadence</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>CPU/GPU utilisation</TableCell>
      <TableCell>Sustained above 95% signals the limit should be reduced</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

After 24 hours of clean operation, increment `-maxSessions` by 1 to 2 and observe. A sudden drop in Gateway traffic should send you to the logs first; look for `OrchestratorCapped` errors that show the session ceiling is blocking new jobs.

<CustomDivider />

## Related pages

<CardGroup cols={2}>
  <Card title="AI Inference Operations" icon="microchip" href="/v2/orchestrators/guides/ai-and-job-workloads/ai-inference-operations" arrow horizontal>
    Full aiModels.json reference including the `capacity` field for AI pipeline concurrency.
  </Card>

  <Card title="Model Management" icon="memory" href="/v2/orchestrators/guides/config-and-optimisation/ai-model-management" arrow horizontal>
    Warm vs cold strategy and VRAM allocation across multiple AI pipelines.
  </Card>

  <Card title="Metrics and Alerting" icon="chart-mixed" href="/v2/orchestrators/guides/monitoring-and-tooling/metrics-and-alerting" arrow horizontal>
    Prometheus metrics for transcoding throughput, alerting, and session health.
  </Card>

  <Card title="GPU Support Reference" icon="server" href="/v2/orchestrators/resources/gpu-support" arrow horizontal>
    NVENC session caps by GPU tier and supported hardware matrix.
  </Card>
</CardGroup>
