Skip to content

fix(nemotron_h): prefill memory grows roughly quadratically with prompt length and aborts with Metal out-of-memory at ~6K tokens #2281

Closed

Description

@YEONFEEL96

Prefilling a Nemotron-H model on Metal needs transient memory that grows roughly with the square of the prompt length. A prompt of about 6,000 tokens aborts the server with a Metal out-of-memory error on a 48 GB Mac, and about 3,000 tokens is enough to exhaust a 16-24 GB Mac.

After each request the footprint returns to about 11 GB, so this is a transient peak, not a leak. Backend.AI GO users hit it as "load the model, send 안녕, and memory overflows": the app's chat request carries a system prompt and tool schemas that already run to several thousand tokens.

Environment

  • mlxcel-server 0.7.0 (Backend.AI GO bundle), Apple M4 Pro 48 GB, macOS. The startup log reports Wired memory limit: 37.4 GB.

  • Model: hancheolp/mlx_test.

    • Nemotron 3.5 Lightning 30B-A3B, MLX affine 2-bit (group 64), 10.2 GB on disk.
    • model_type: nemotron_h.
    • 52 layers: Mamba2, MoE (128 experts, top 6), and 6 attention layers.
    • mamba_ssm_cache_dtype: float32, chunk_size: 128, max_position_embeddings: 262144.
  • Launch (as Backend.AI GO launches it):

    mlxcel-server --model <dir> --ctx-size 8192 --jinja --host 127.0.0.1 --port 39913 --alias repro --parallel 1
    

Measurements

Each request was one non-streaming /v1/chat/completions call:

  • a system message of N tokens plus the user message "안녕"
  • max_tokens: 16
  • chat_template_kwargs.enable_thinking: false

The peak is Physical footprint (peak) from vmmap --summary <pid>, read after each request. The footprint is 10.1 GB right after loading.

prompt tokens peak footprint over loaded
18 11.0 GB +0.9 GB
756 11.8 GB +1.7 GB
1,494 15.1 GB +5.0 GB
2,242 20.1 GB +10.0 GB
2,980 26.7 GB +16.6 GB
4,466 37.2 GB +27.1 GB
~6,000 aborted see below

Doubling the prompt from 1,494 to 2,980 tokens multiplies the extra memory by about 3.3. The abort:

libc++abi: terminating due to uncaught exception of type std::runtime_error: [METAL] Command buffer execution failed: Insufficient Memory (00000008:kIOGPUCommandBufferCallbackErrorOutOfMemory).

What does not change it

  • Prefill chunk size. --prefill-chunk-size 128 gives the same peaks: 15.0 GB at 1,494 tokens and 26.5 GB at 2,980, with an abort at ~6,000. With --parallel 1 (hybrid models are clamped to one slot) the chunk setting does not seem to bound this path.
  • Prompt cache. The prompt cache is on by default. The peak is reached during prefill of a request with no cache hit, so the cache is not the cause.

Comparison

choppedgarlic/Qwen3.8-27B-AEON-ULTIMATE-UNCENSORED-4bit-MLX (qwen3_5, gated delta hybrid) under the same launch peaks at only about 2.2 GB over loaded for a 4,484-token prompt. The growth looks specific to the Nemotron-H path. The likely suspect is the Mamba2 SSD prefill materializing an L×L (per head) intermediate over the whole prompt instead of per chunk_size block, or keeping all layers' intermediates alive in one lazy graph.

Expected

  • Prefill memory for nemotron_h grows about linearly with prompt length: chunked SSD over chunk_size blocks, and per-layer evaluation so intermediates are freed.
  • A prompt that does not fit returns an error instead of aborting the process.

Repro script

A self-contained Python script (start the server, send N-token prompts, read vmmap peaks) is available on request; the table above is its output.

Activity

  1. inureyes commented on Oct 13, 2026

    @inureyes
    Member

    PR #2290 is merged as c9e062a. The bounded SSD fix reduced the measured 6,019-token peak from 72.2 to 18.7 GiB on M1 Ultra, with three runs per cell, matching greedy outputs, and passing focused Metal tests.

    With maintainer approval, the remaining request-level memory admission requirement is split into #2291 and will be implemented next. This closure records the quadratic-prefill fix and the explicit scope transfer; it does not claim arbitrary Metal OOM recovery.

  2. added
    type:bugBug fixes, error corrections, or issue resolutions
    and removed on Oct 13, 2026
  3. self-assigned this
    on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

priority:highHigh prioritystatus:doneCompletedtype:bugBug fixes, error corrections, or issue resolutions

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions