Skip to content

Qwen3.8-2.4T on --device cuda: the model loads, then the first forward dies on cudaMalloc: out of memory #1123

Description

@localai-bot

What happened

Qwen3.8-2.4T-A95B UD-Q1_0 (370 GiB, 10 shards) loads to a serving state on --device cuda on a DGX Spark (GB10, 119 GB unified memory) and then dies on the first request:

=== READY after ~1560s ===          (26 min, so the load itself succeeded)
=== measuring (4 tokens) ===
TOTAL 0 tokens in 402.8s
TEXT: ''
== CUDA ==  CUDA Version 13.0.1
engine-fatal: EngineCore busy loop threw: vt cuda: cudaMalloc: out of memory
async-llm: output handler saw engine death: EngineCore encountered an issue [vt cuda: cudaMalloc: out of memory]
sse: stream aborted mid-flight: ...

The failure is in the EngineCore busy loop, not the loader — so weight residency is fine and it is the forward that cannot allocate.

Why this matters

The same checkpoint serves correctly on --device cpu on the same box, same binary, same prompt: TTFT 667.0 s, steady decode 44.2 s/token, coherent output. So the capability exists on one device path and not the other, and the GPU path is where the compute headroom is.

The plausible mechanism, not yet confirmed: the disk-borrow residency that makes this fit is a CPU-path capability. HostExpertSlotStore is host memory by construction, and the expert-streaming lane is CPU-gated. On the CUDA path the forward presumably needs expert slices (and activations) in device allocations, and cudaMalloc draws from the same unified 119 GB pool that already holds the dense remainder plus page cache. 2790 slices/token at 2.38 MB is ~6.5 GB per token of expert bytes that would have to be staged. Someone should confirm which allocation actually fails and its size rather than trust that reasoning — the log line does not name it.

Related: keep-quant device slice needs ResidentWeight is a known constraint in this area.

Reproduction

Under an rc lease on dgx:gpu0 (rc hold dgx:gpu0 --ttl 3h, then ssh — the checkpoint is on the host/NAS and rc run gives the container only):

docker run -d --name q1cuda --gpus all \
  -v /home/mudler/iq1bench:/src -v /home/mudler/ckpt:/ckpt:ro -w /src \
  -e VT_GGUF_PREFAULT=0 \
  vllmcpp-build:gb10 ./build-srv/examples/vllm-server \
  --model /ckpt/qwen3.8-q1_0/Qwen3.8-2.4T-A95B-UD-Q1_0-00001-of-00010.gguf \
  --device cuda --max-num-seqs 1 --max-model-len 512 --port 8899

Checkpoint: unsloth/Qwen3.8-2.4T-A95B-GGUF at revision 567d3e6ac26c5474b18311e619c04350fb9a5556, also staged at /usr/local/nas_share/checkpoints/qwen3.8-2.4t-a95b-ud-q1_0.

Readiness must be probed with python3, not curl — that image has no curl, and a suppressed curl exit 127 reads as "not ready" forever.

What would close this

Either the forward allocates within the pool on a larger-than-memory GGUF, or the engine refuses --device cuda for this configuration by name at load time with a message saying why. Loading for 26 minutes and then dying mid-stream is the worst of the three outcomes, and per policy an unimplemented arm should refuse with a message naming the missing part rather than fail late.

Not a blocker for the CPU result

The measured CPU numbers stand and are unaffected. Recording this so the --device cuda gap is visible debt rather than something rediscovered by the next person who tries the obvious thing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions