Tag: PyTorch

  • Troubleshooting bitsandbytes: Resolving ‘RuntimeError: CUDA Setup failed despite GPU being available’

    Troubleshooting bitsandbytes: Resolving ‘RuntimeError: CUDA Setup failed despite GPU being available’

    TL;DR

    • The CUDA Setup failed error occurs when bitsandbytes cannot dynamically locate runtime libraries, encounters ABI mismatches, or lacks sufficient GPU compute capability, all of which can be inspected using python -m bitsandbytes.
    • Linux initialization failures caused by dynamic linking issues or missing libstdc++ symbols can be resolved by configuring dynamic library paths or compiling bitsandbytes from source against the local CUDA environment using CMake.
    • Native Windows environments frequently fail due to the library searching for Linux-specific paths and shared objects; running inside WSL2 or installing compatible Windows wheels provides a practical workaround.

    Understanding Why bitsandbytes Fails to Initialize CUDA

    The RuntimeError: CUDA Setup failed despite GPU being available error occurs when the Python environment successfully detects an NVIDIA GPU through PyTorch, but the underlying bitsandbytes C++/CUDA extension fails to load, locate, or execute its compiled GPU kernels. Because bitsandbytes operates as a Python wrapper over custom CUDA and C++ kernels designed for memory-efficient deep learning operations, initialization fails if the low-level execution environment does not satisfy its dynamic linking or hardware requirements.

    CUDA initialization failures are driven by three primary root causes:

    • Dynamic CUDA Library Discovery: At runtime, bitsandbytes searches system search paths for CUDA dynamic runtime shared libraries, such as libcudart.so on Linux or nvcuda.dll on Windows. If these libraries are absent from the linker search paths or are incompatible with the active PyTorch installation, the C++ extension cannot be loaded.
    • CUDA Version and Symbol Mismatches: When the CUDA version used to compile the installed PyTorch build does not match the system CUDA Toolkit or the pre-compiled binaries distributed within the bitsandbytes package, the runtime environment cannot properly bind the required symbols.
    • Compute Capability and Architecture Requirements: bitsandbytes requires specific minimum NVIDIA compute capability levels to execute its kernel backends. Attempting to initialize the library on an unsupported architecture or on a build missing compiled kernels for that specific architecture triggers a setup failure:
      • Compute capability 6.0+ (Pascal or newer): Required to run basic 8-bit optimizers and 4-bit/8-bit quantization routines.
      • Compute capability 7.5+ (Turing, Ampere, Ada Lovelace, Hopper, or newer): Required for accelerated LLM.int8() mixed-precision matrix multiplication.

    Sources: mistralai/Mixtral-8x7B-Instruct-v0.1 · [AUTOMATED] Model Memory Requirements, CUDA Setup failed despite GPU being available. Inspect the CUDA SETUP outputs above to fix your environment! I, bitsandbytes · PyPI, RuntimeError: CUDA Setup failed despite correct environment paths on Windows · Issue #976 · bitsandbytes-found, stackoverflow.com/questions/79913258/cuda-setup-error-missing-libnvji…

    Diagnosing CUDA and Runtime Mismatches Using 'python -m bitsandbytes'

    To pinpoint which of these underlying root causes is responsible for the error in a specific environment, running the diagnostic command provided in the error message outputs detailed information about the environment, detected NVIDIA GPU hardware, and library search stages:

    python -m bitsandbytes
    

    Executing this command triggers bitsandbytes’ internal CUDA setup routine to inspect system paths, compute capability levels, and dynamic library dependencies.

    The resulting diagnostic log reveals the exact sequence of checks bitsandbytes performs. In one developer’s environment report, the diagnostic output captured the following path inspections and library loading stages:

    CUDA_SETUP: WARNING! libcudart.so not found in any environmental path. Searching in backup paths...
    CUDA SETUP: CUDA runtime path found: /usr/local/cuda/lib64/libcudart.so
    CUDA SETUP: Highest compute capability among GPUs detected: 7.5
    CUDA SETUP: Detected CUDA version 121
    CUDA SETUP: Loading binary /home/yed_dev/.local/lib/python3.10/site-packages/bitsandbytes/libbitsandbytes_cuda121.so...
    /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by /home/yed_dev/.local/lib/python3.10/site-packages/bitsandbytes/libbitsandbytes_cuda121.so)
    CUDA SETUP: Something unexpected happened. Please compile from source:
    git clone [email protected]:TimDettmers/bitsandbytes.git
    cd bitsandbytes
    CUDA_VERSION=121
    python setup.py install
    

    As demonstrated in this log, running python -m bitsandbytes allows you to inspect several diagnostic points:

    • Directory and runtime discovery: It identifies missing or non-existent directories listed in environment paths (such as /usr/local/cuda/lib missing expected shared libraries like libcudart.so, libcudart.so.11.0, or libcudart.so.12.0) and notes when fallback searches find candidates like /usr/local/cuda/lib64/libcudart.so.
    • Hardware and version detection: It prints the detected CUDA version (e.g., CUDA 121) and the highest detected NVIDIA GPU compute capability (e.g., 7.5).
    • Binary loading and ABI mismatches: It shows which specific pre-compiled binary is being loaded (such as libbitsandbytes_cuda121.so) and captures underlying system loader failures, such as a missing C++ ABI symbol in the host’s libstdc++.so (e.g., version 'CXXABI_1.3.9' not found).

    If the setup encounters an unrecoverable failure during binary loading, diagnostic tools often suggest compiling bitsandbytes directly from source for the detected CUDA version.

    Sources: help!!!RuntimeError: CUDA Setup failed despite GPU being available. Please run the following command to get more information:

    Fixing Linux Setup Errors: Configuring CUDA Paths and Missing libstdc++ Symbols

    Once the diagnostic output reveals the exact nature of the failure, addressing it requires targeting the specific platform constraints. In Linux environments, bitsandbytes can detect the local CUDA runtime and hardware specifications successfully but still fail to initialize due to missing system library symbols. In one reported setup log, the initialization routine correctly located the CUDA runtime path at /usr/local/cuda/lib64/libcudart.so, detected CUDA version 117, and identified an NVIDIA GPU compute capability of 7.5, yet failed when attempting to load the pre-compiled binary:

    CUDA SETUP: CUDA runtime path found: /usr/local/cuda/lib64/libcudart.so
    CUDA SETUP: Highest compute capability among GPUs detected: 7.5
    CUDA SETUP: Detected CUDA version 117
    CUDA SETUP: Loading binary /usr/local/lib/python3.9/site-packages/bitsandbytes/libbitsandbytes_cuda117.so...
    /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by /usr/local/lib/python3.9/site-packages/bitsandbytes/libbitsandbytes_cuda117.so)
    

    This log demonstrates an incompatibility where the system’s libstdc++.so lacks the required CXXABI_1.3.9 symbol needed by libbitsandbytes_cuda117.so. When this dynamic linking error occurs during execution—such as importing dependent libraries like peft that call import bitsandbytes as bnb—compiling the package directly from source against the local environment resolves the ABI mismatch.

    While legacy runtime diagnostic outputs in older package versions recommended running make and python setup.py install from the original repository, current official bitsandbytes documentation maintains source builds through bitsandbytes-foundation/bitsandbytes using CMake:

    git clone https://github.com/bitsandbytes-foundation/bitsandbytes.git
    cd bitsandbytes
    cmake -DCOMPUTE_BACKEND=cuda -S .
    make
    pip install -e .
    

    This compilation workflow builds the CUDA backend kernels directly with your host compiler and active CUDA SDK, ensuring complete symbol compatibility with your system’s libstdc++ runtime.

    Sources: CUDA Setup failed despite GPU being available. Inspect the CUDA SETUP outputs above to fix your environment!

    Resolving Initialization Failures on Windows via Pre-compiled Wheels

    While Linux environments primarily encounter ABI and dynamic path issues, running bitsandbytes in native Windows environments presents a different set of challenges because the initialization routines often look for Linux-specific shared libraries and paths. In user reports running setups such as CUDA 11.8 with PyTorch 2.0.1 or CUDA 12.2.2, the initialization routine scans for .so binaries rather than Windows-compatible dynamic libraries, producing path warnings and failing to register the NVIDIA GPU:

    bin C:\...\bitsandbytes\libbitsandbytes_cpu.so
    UserWarning: C:\... did not contain ['libcudart.so', 'libcudart.so.11.0', 'libcudart.so.12.0'] as expected! Searching further paths...
    CUDA_SETUP: WARNING! libcudart.so not found in any environmental path. Searching in backup paths...
    UserWarning: WARNING: The following directories listed in your path were found to be non-existent: {WindowsPath('/usr/local/cuda/lib64')}
    CUDA SETUP: WARNING! libcuda.so not found! Do you have a CUDA driver installed?
    UserWarning: WARNING: No GPU detected! Check your CUDA paths. Proceeding to load CPU-only library...
    CUDA SETUP: Loading binary ...\libbitsandbytes_cpu.so...
    argument of type 'WindowsPath' is not iterable
    CUDA SETUP: Problem: The main issue seems to be that the main CUDA library was not detected.
    CUDA SETUP: Solution 1): Your paths are probably not up-to-date. You can update them via: sudo ldconfig.
    

    This log excerpt illustrates bitsandbytes attempting to search Linux directories like /usr/local/cuda/lib64 and falling back to a CPU-only .so binary on Windows, ultimately raising a TypeError (argument of type 'WindowsPath' is not iterable) and presenting Linux-specific solutions like sudo ldconfig.

    In cases reported across Windows installations using tools like threestudio or stable-diffusion-webui (with dependencies like xformers 0.0.20), the setup routine fails when it cannot resolve Windows-native runtime bindings. For threestudio, project collaborators recommended running the workflow inside WSL2 to bypass native Windows library resolution failures. In native Windows environments, users typically require dedicated Windows-compatible wheel builds to properly load the CUDA dynamic libraries.

    Sources: CUDA Setup failed despite GPU being available, CUDA Setup failed despite GPU being available. Please run the following command to get more information

    Closing thoughts

    Ultimately, these platform-specific failures illustrate that PyTorch detecting a GPU is only half the battle; bitsandbytes relies on strict low-level dynamic linking that fails whenever host runtime libraries, ABI symbols, or operating system paths diverge from its pre-built binaries.

    Running the python -m bitsandbytes diagnostic tool is essential because it immediately clarifies whether the issue is an unlocated libcudart.so, an incompatible host libstdc++ version, or a Windows environment trying to load Linux-specific paths. When these mismatches occur, targeted source compilation via CMake on Linux or operating within WSL2 / utilizing Windows-compatible builds provides the most reliable way to align the library’s custom kernels with your active CUDA environment.

    Frequently Asked Questions

    Why does bitsandbytes throw a CUDA Setup failed error even when PyTorch detects a GPU?

    This error occurs because the underlying C++/CUDA extension fails to load, locate, or execute its GPU kernels. Common root causes include dynamic CUDA library discovery issues, CUDA version and C++ ABI symbol mismatches, or an unsupported GPU compute capability.

    How can I diagnose CUDA and runtime issues with bitsandbytes?

    You can run the command ‘python -m bitsandbytes’ in your terminal. This diagnostic tool inspects environment paths, checks for required shared libraries like libcudart.so, reports detected CUDA and compute capability versions, and identifies binary loading failures.

    What compute capability is required to run bitsandbytes?

    Bitsandbytes requires a minimum compute capability of 6.0+ (Pascal or newer) for basic 8-bit optimizers and 4-bit/8-bit quantization routines. For accelerated LLM.int8() mixed-precision matrix multiplication, a compute capability of 7.5+ (Turing or newer) is required.

    Why does bitsandbytes encounter initialization errors on Windows?

    On native Windows environments, bitsandbytes often defaults to searching for Linux-specific file paths and .so shared libraries rather than Windows-compatible dynamic libraries. Running inside WSL2 or installing compatible Windows wheels provides a practical workaround to avoid these path and dynamic linking issues.


    Keep reading: LLM Inference & Serving

    All guides in this series: LLM Inference & Serving Guides: vLLM, Ollama & Hugging Face

  • Fixing Cannot Copy Out of Meta Tensor Errors in Hugging Face Transformers

    Fixing Cannot Copy Out of Meta Tensor Errors in Hugging Face Transformers

    TL;DR

    • The ‘Cannot copy out of meta tensor’ error happens when attempting manual operations like .to(device) on meta tensors, which serve as memory-free model placeholders.
    • Avoid calling .to() manually when loading models with device_map=’auto’, as Hugging Face Accelerate automatically handles layer placement and device transfers via hooks.
    • To resolve meta tensor errors in custom pipelines, use load_checkpoint_and_dispatch() when dispatching an Accelerate-managed model, or torch.nn.Module.to_empty() when manually allocating a model before loading a state dict — they solve different scenarios, not interchangeable alternatives.

    Understanding Meta Tensors in PyTorch and Hugging Face

    The error NotImplementedError: Cannot copy out of meta tensor; no data! occurs when PyTorch, Hugging Face Transformers, or Accelerate attempts to copy data, perform operations, or execute standard .to(device) calls on a tensor residing on the meta device. In PyTorch, a tensor on the meta device acts as a lightweight placeholder that stores metadata—such as shape, strides, and data type—without allocating physical memory or actual storage bytes. Both PyTorch and Hugging Face Transformers use this mechanism to construct large model architectures instantly without overwhelming CPU RAM or GPU VRAM.

    When loading large models in Hugging Face Transformers using device_map="auto" or low_cpu_mem_usage=True, Accelerate initially instantiates the model skeleton on the meta device. Setting low_cpu_mem_usage=True instructs from_pretrained to load the model via meta-device initialization, which prevents full model weights from being loaded into system memory twice. Accelerate then calculates optimal parameter placement across available hardware (splitting across GPUs, CPU RAM, or disk) before populating real weight data. Data-dependent operations that need real bytes—such as .to("cuda") or .item()—fail on a meta tensor because no physical memory has been allocated (though the exact exception differs: .to() raises this article’s Cannot copy out of meta tensor; no data! error, while .item() raises a separate Tensor.item() cannot be called on meta tensors error). Metadata-only operations like .clone(), by contrast, succeed on a meta tensor and simply return another meta tensor. If custom logic or user code tries to interact with or move these placeholder parameters before they are fully materialized into real tensors, PyTorch throws the error.

    To avoid and fix meta tensor errors, follow these standard practices and API guidelines:

    • Avoid Manual .to() Calls on device_map="auto" Models: Do not call model.to("cuda") or model.to(device) after instantiating a model with device_map="auto". Accelerate automatically manages layer movement and execution across devices using PyTorch forward hooks. Calling manual device transfer methods forces PyTorch to attempt copying empty meta tensors, triggering the exception.
    • Use Native Checkpoint Dispatching for Custom Models: When constructing custom model pipelines using init_empty_weights() to route layer creation onto the meta device, load and place real weights using load_checkpoint_and_dispatch() — this function loads a checkpoint from disk and replaces the empty meta tensors with real weight tensors before attaching execution hooks. dispatch_model() is a narrower tool: it only attaches the execution hooks that route each layer’s forward pass to its assigned device, and it expects the model to already hold real (non-meta) weights — calling it alone on a model still on the meta device will not populate those weights.
    • Use torch.nn.Module.to_empty() for Uninitialized Allocation: If manual model allocation is required before loading state dicts into memory, replace standard .to(device) calls with torch.nn.Module.to_empty(). This native PyTorch method transfers a model off the meta device onto a target device (such as CPU or CUDA) and allocates uninitialized memory without trying to read non-existent meta tensor values.
    • Verify Distributed & Offloading Settings: In distributed training or inference environments (such as DeepSpeed ZeRO-3 or FSDP), ensure training arguments do not conflict with model loading options. Passing explicit offloading configurations or disabling incompatible CPU RAM efficient loading flags ensures model weights are fully materialized before training loops begin.

    Sources: docs.pytorch.org/docs/stable/meta.html, How 🤗 Accelerate runs very large models thanks to PyTorch, Loading big models into memory · Hugging Face, DiffusionGemma weights remain on ‘meta’ device with device_map=”auto” · Issue #46566 · huggingface/transformer

    Closing thoughts

    Looking at the underlying cause, the “cannot copy out of meta tensor” error most often traces back to a friction point where legacy PyTorch habits—specifically manual .to(device) transfers—conflict with modern, automated memory management. That said, it is not always user error: Accelerate’s automatic dispatch logic can itself leave some submodules on the meta device for certain model architectures, and a later data-dependent operation (such as the .item() call inside model.generate()) then fails with a related meta-tensor exception — evidence that dispatch-logic bugs, not just manual .to() misuse, are a real source of these errors. Meta tensors are essential lightweight blueprints that enable the loading of massive architectures, but they require developers to trust frameworks like Hugging Face Accelerate to handle parameter placement behind the scenes. Using load_checkpoint_and_dispatch() for Accelerate-managed models, or to_empty() when manually allocating a model before loading a state dict, resolves most instantiation hurdles—though architecture-specific dispatch failures may still require filing an upstream issue. Ultimately, respecting the boundary between placeholder metadata and materialized weights ensures clean, exception-free pipelines without causing system memory exhaustion.

    Frequently Asked Questions

    What causes the “Cannot copy out of meta tensor” error in PyTorch and Hugging Face?

    The error occurs when PyTorch, Hugging Face Transformers, or Accelerate attempts to copy data, perform operations, or execute manual .to(device) calls on a tensor residing on the meta device. Because meta tensors are lightweight placeholders with no physical memory allocated, standard data-copying operations fail.

    How does device_map=”auto” utilize meta tensors when loading models?

    When using device_map="auto" or low_cpu_mem_usage=True, Accelerate initially instantiates the model skeleton on the meta device to prevent loading full model weights into system memory twice. It calculates optimal parameter placement across hardware before populating real weight data.

    Why should manual .to() calls be avoided on models instantiated with device_map=”auto”?

    Accelerate automatically manages layer movement and execution across devices using forward hooks. Calling .to("cuda") or .to(device) manually forces PyTorch to attempt copying empty meta tensors, triggering the exception.

    How can you safely allocate or dispatch custom models created on the meta device?

    For custom models, developers should load and place real weights using load_checkpoint_and_dispatch(), which reads a checkpoint from disk and replaces the empty meta tensors with real weight tensors. dispatch_model() is not a substitute for this — it only attaches execution hooks to a model whose weights are already real, so calling it alone on a model still on the meta device will not load any weights. If manual allocation is required before loading state dicts, torch.nn.Module.to_empty() should be used instead of standard .to(device) calls.


    Keep reading: LLM Inference & Serving

    All guides in this series: LLM Inference & Serving Guides: vLLM, Ollama & Hugging Face

  • Fixing Silent PyTorch DataLoader Crashes in Docker Containers

    Fixing Silent PyTorch DataLoader Crashes in Docker Containers

    TL;DR

    • PyTorch DataLoader uses POSIX shared memory at /dev/shm to efficiently pass CPU tensor data between parallel worker processes.
    • Docker containers allocate a default limit of only 64 MB to /dev/shm, causing workers to crash with a SIGBUS signal when mapped pages are accessed without backing space — a failure mode distinguishable in logs from a SIGKILL caused by host or container RAM exhaustion.
    • Preventing these DataLoader crashes requires configuring container-level shared memory to match PyTorch’s multi-process requirements.

    Understanding the Cause and Symptoms of DataLoader Shared Memory Crashes

    PyTorch supports multi-process data loading through torch.utils.data.DataLoader when the num_workers parameter is set to a value greater than zero. Under this configuration, parallel worker processes fetch and collate data simultaneously. To efficiently pass CPU tensor data between worker processes and the main process without expensive data copying, PyTorch relies on Inter-Process Communication (IPC) backed by POSIX shared memory, which is mounted at /dev/shm.

    By default, Docker allocates a restricted size of only 64 MB to /dev/shm for Docker containers. When deep learning workloads involve large batch sizes, heavy tensor payloads, or multiple worker processes, the active data buffers can quickly exceed this 64 MB shared memory allocation. In tmpfs-backed shared memory, mapping operations such as mmap or ftruncate may initially succeed, but when worker processes touch and access mapped pages without sufficient backing space, the operating system raises a SIGBUS (Bus Error) signal. This terminates worker processes with error notices such as DataLoader worker (pid X) is killed by signal: Bus error. It is possible that dataloader's workers are out of shared memory… or DataLoader worker exited unexpectedly. This failure is distinct from a SIGKILL signal (killed by signal: Killed), which indicates that an out-of-memory killer terminated a process because actual container RAM — a separate resource from /dev/shm — was exhausted.

    Resolving Shared Memory Exhaustion

    To prevent these crashes, you can configure the container’s shared memory capacity or IPC settings at launch:

    1. Increase Container Shared Memory Size (Recommended)
      Explicitly set /dev/shm when starting the container using --shm-size:
    docker run --shm-size=8g my_pytorch_image
    

    Note that 8g is only an illustrative example; you should size this value according to your number of workers, batch size, and prefetch settings. Prefer this method in most production workflows because it preserves container isolation while providing dedicated shared memory.

    In docker-compose.yml, specify shm_size for the corresponding service:

    services:
      training:
        image: my_pytorch_image
        shm_size: '8gb'
    
    1. Use the Host IPC Namespace
      Alternatively, share the host machine’s IPC namespace directly with the container:
    docker run --ipc=host my_pytorch_image
    

    Prefer --ipc=host in trusted environments where you want to grant the container access to host IPC resources without manually sizing /dev/shm, keeping in mind that sharing the host IPC namespace weakens container isolation.

    Sources: docs.pytorch.org/docs/stable/data.html, docs.pytorch.org/docs/stable/notes/multiprocessing.html, Shared Memory in Docker with the shm-size Flag – datawookie, Docker –shm-size: Set /dev/shm and Fix Crashes, PyTorch: increasing –shm-size to allow multiprocessing data loaders · Issue #937 · aws/sagemaker-python-sdk ·

    Closing thoughts

    Ultimately, these crashes highlight a mismatch between PyTorch’s multi-process IPC design and Docker’s restrictive default container settings. The default 64 MB shared memory cap can be problematic when using multiple workers, as signals like SIGBUS or unexpected worker exit messages can lead developers to investigate their dataset code rather than the container configuration. By adjusting container shared memory via --shm-size or using --ipc=host in trusted setups, teams can ensure reliable multi-process data loading without running out of shared memory.

    Frequently Asked Questions

    Why does PyTorch DataLoader crash in default Docker containers?

    PyTorch uses POSIX shared memory at /dev/shm to pass tensor data between worker processes and the main process when num_workers is greater than zero. Docker restricts /dev/shm to 64 MB by default, which is easily exhausted during heavy data loading workloads, leading to worker crashes.

    What default shared memory allocation does Docker provide to containers?

    By default, Docker allocates a restricted size of only 64 MB to /dev/shm.

    What error signals or messages occur when PyTorch shared memory is exhausted?

    Worker processes terminate with a SIGBUS (Bus Error) signal, often accompanied by messages stating that the worker was killed by a bus error and may be out of shared memory, or generic messages such as “DataLoader worker exited unexpectedly”. A SIGKILL signal is a distinct failure mode caused by out-of-memory termination due to actual RAM exhaustion, not /dev/shm size.

    How can I fix /dev/shm exhaustion in Docker?

    You can override the default shared memory limit by passing --shm-size to docker run (sized according to your number of workers, batch size, and prefetch), setting shm_size in docker-compose.yml, or using --ipc=host to share the host IPC namespace in trusted environments.

    Why does PyTorch DataLoader rely on POSIX shared memory?

    PyTorch relies on Inter-Process Communication (IPC) backed by POSIX shared memory to pass CPU tensor data between worker processes and the main process. This avoids expensive data copying when running multi-process data loading.


    Keep reading: Training & Architectures

    All guides in this series: LLM Training & Architecture Guides: Fine-Tuning, RL & New Models

  • vLLM CUDA Out of Memory: Fixing Startup, Runtime, and Persistent OOM Loops

    vLLM CUDA Out of Memory: Fixing Startup, Runtime, and Persistent OOM Loops

    TL;DR

    • “vLLM CUDA OOM” is three different problems: a startup failure (ValueError: To serve at least one request with the model's max seq len ...), a runtime failure under load (torch.OutOfMemoryError: CUDA out of memory. Tried to allocate ...), and a persistent loop where every request 500s after one bad one.
    • Almost every memory knob in vLLM — gpu_memory_utilization, max_model_len, max_num_seqs, kv_cache_dtype, enforce_eager, tensor_parallel_size — is an engine-init argument. You cannot change it on a running server, so most real fixes require a restart.
    • The only truly live lever is client-side: send fewer concurrent requests, smaller max_tokens, shorter prompts. PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True needs one restart to take effect but then greatly reduces the fragmentation form of the loop for good. LoRA adapters can be hot-swapped via /v1/load_lora_adapter.
    • The persistent loop means the engine’s error-handling has given up. On current vLLM (V1 engine) a dead GPU subprocess raises EngineDeadError; on an old pinned vLLM (before ~v0.10, when the V0 engine still existed) it was AsyncEngineDeadError: Background loop has errored already. Neither recovers in place — you restart. The practical goal is “restart without downtime” (load balancer, rolling deploy), not “never restart”.

    Three different “vLLM CUDA OOM” problems

    The phrase “vLLM won’t stop throwing CUDA OOM” covers three failures with different causes and different fixes. Identify which one you have before changing anything.

    1. Startup: not enough memory for the KV cache

    The server refuses to start and prints a ValueError like:

    ValueError: To serve at least one request with the model's max seq len (40960), (5.62 GiB KV cache is needed, which is larger than the available KV cache memory (4.89 GiB). Based on the available memory, the estimated maximum model length is 35600. Try increasing `gpu_memory_utilization` or decreasing `max_model_len` when initializing the engine.
    

    (The mismatched parenthesis after the sequence length is in vLLM’s own message.) It means: after loading the weights and reserving overhead, the memory left for the KV cache cannot hold even one request at max_model_len tokens. This is a sizing problem, fixed with init arguments — covered below.

    2. Runtime: an allocation failure under load

    The server is up and serving, then a request or a burst of them fails. There are two distinct sub-cases, and only one is graceful:

    • KV-cache block pressure. Too many concurrent sequences need more KV cache blocks than the pool has. On the V1 engine the scheduler preempts the newest sequences and later recomputes them (the default RECOMPUTE mode), so this shows up as higher latency and lower throughput, not errors. Nothing crashes.
    • Allocator OOM outside the pool. A prefill spike, activation buffers, CUDA-graph capture, or a second process on the GPU pushes transient usage past the free margin, and PyTorch raises:
    torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.00 GiB. GPU 0 has a total capacity of 23.99 GiB of which 1.10 GiB is free. ... If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation.
    

    Recompute preemption does not protect against this one — it only rations KV cache blocks, not activation or graph memory. An allocator OOM here can take down the EngineCore subprocess.

    3. The persistent loop: every request fails after one bad one

    One large request triggers an OOM, and from then on every request — even trivial ones — returns a 500. Three sub-causes, and they need different responses:

    • A dead engine (most common). The OOM exception escaped into the engine’s core loop and killed it. On current vLLM the GPU subprocess is gone and every request raises EngineDeadError (with EngineCore ... died messages in the logs). On an old pinned vLLM (before the V0 engine was removed around v0.10) the async background task died instead, raising AsyncEngineDeadError: Background loop has errored already. Neither is recoverable in place — the maintainers’ guidance is to restart the process. No allocator setting brings a dead engine back.
    • Allocator fragmentation. The engine is alive but PyTorch is holding reserved-but-unusable memory in fragmented pools; nvidia-smi shows free memory in aggregate while no single block is large enough. This is the case expandable_segments:True addresses.
    • Under-provisioning. The spike that OOMs keeps recurring because the server is sized past its GPU. The fix is the startup sizing covered further down, not recovery.

    Sources: vLLM issue #38516 (KV cache ValueError), vLLM issue #16118 (estimate max-model-len)

    Can you fix it without restarting? Mostly no — here is what is live

    Be clear-eyed about what is actually adjustable while the server runs.

    Init-only — changing these means restarting vllm serve: gpu_memory_utilization, max_model_len, max_num_seqs, max_num_batched_tokens, kv_cache_dtype, enforce_eager, tensor_parallel_size, pipeline_parallel_size, quantization, swap_space. These are all EngineArgs, baked in when the engine starts.

    Live, right now, no restart:

    • Client-side pressure. The only real serving-side lever on a running engine is sending less: lower client concurrency, cap max_tokens per request, shorten prompts. This immediately reduces peak KV cache and activation memory and will pull a server back from the edge.
    • LoRA adapters. If the pressure comes from loading many fine-tunes, start with VLLM_ALLOW_RUNTIME_LORA_UPDATING=True and add or drop adapters through POST /v1/load_lora_adapter and POST /v1/unload_lora_adapter, with no interruption to in-flight traffic. vLLM’s docs warn against enabling this outside a trusted, isolated environment, since it can load arbitrary adapters.

    One restart, then durable:

    • PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True — an environment variable read by PyTorch’s CUDA allocator, not by vLLM, so it is picked up on the next process start. It switches the allocator to growable segments that resist fragmentation, and the PyTorch OOM message itself recommends it. After that one restart it greatly reduces the fragmentation form of the loop without needing further restarts.

    Automatic, nothing to set:

    • KV-cache preemption. The V1 scheduler’s default RECOMPUTE preemption lets the server ration KV cache blocks under load instead of erroring. It does not cover allocator OOMs from activation or graph memory. This is automatic on current vLLM, which ships only the V1 engine.

    Sources: vLLM optimization docs, vLLM conserving memory docs

    Fixing the startup KV-cache OOM

    For the startup ValueError, the goal is to make the KV cache fit. In rough order of preference:

    • Lower --max-model-len to what your workload actually needs. vLLM prints an estimate (“estimated maximum model length is 35600”) — use that as a ceiling. The startup check requires enough KV cache memory to serve one request at the full max_model_len; a context window you never use just raises that bar.
    • Raise --gpu-memory-utilization if the GPU is dedicated to this server. The default is 0.9; 0.92–0.95 is safe on a card doing nothing else. Do not go to 1.0 — PyTorch’s allocator and prefill spikes need headroom.
    • Compress the KV cache with --kv-cache-dtype fp8 (or fp8_e4m3 / fp8_e5m2). This halves KV cache bytes versus fp16 for a small, usually acceptable quality cost, and often single-handedly resolves the error.
    • Cap concurrency with --max-num-seqs (and --max-num-batched-tokens). Fewer simultaneous sequences means a smaller KV cache reservation.
    • Shard the model with --tensor-parallel-size N (splits weights across N GPUs, freeing room for KV cache on each) or --pipeline-parallel-size N (splits layers).
    • Quantize the weights (AWQ, GPTQ, FP8) so the model itself occupies less, leaving more for the cache.
    vllm serve meta-llama/Llama-3.1-8B-Instruct \
      --max-model-len 16384 \
      --gpu-memory-utilization 0.92 \
      --kv-cache-dtype fp8 \
      --max-num-seqs 64
    

    Sources: vLLM optimization docs, vLLM issue #16118

    Fixing runtime OOM under load

    For torch.OutOfMemoryError while serving, the KV cache pool is sized fine but peak usage overflows the margin around it.

    • Lower --gpu-memory-utilization to 0.85–0.90. Counter-intuitively, giving vLLM less of the card leaves more raw headroom for the transient allocations — prefill, activations, CUDA graphs — that live outside the reserved pool. On a shared host, 0.80.
    • Add --enforce-eager. CUDA-graph capture holds a few hundred MB of static memory and can spike while capturing. Disabling it (at a modest decode-throughput cost) reclaims that room. If you want graphs but less memory, shrink compilation_config.cudagraph_capture_sizes instead.
    • Cap --max-num-seqs / --max-num-batched-tokens. These bound how many sequences and how much prefill can be in flight at once, the main driver of runtime spikes.
    • Set PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True before launch so a spike that is recovered does not strand fragmented memory behind it.
    • Check for other processes on the GPU (nvidia-smi) — a second CUDA process that grabs memory after vLLM’s pre-allocation is a classic cause of “it worked yesterday” OOMs.

    Watch the distinction drawn earlier: if you are only seeing latency climb and throughput sag with no errors, that is KV-cache preemption doing its job under load, and the answer is to reduce traffic or resize the deployment rather than to keep tuning the allocator margin.

    Sources: vLLM optimization docs, vLLM OOM root-cause diagnosis

    When every request fails after one bad one

    When every request fails after one bad one, work through this in order:

    1. Check whether the engine is dead. Grep the logs for EngineDeadError and EngineCore ... died, or AsyncEngineDeadError: Background loop has errored already on an old pinned vLLM. If you see either, the engine loop is gone and no in-place setting revives it. Restart the server — and make that restart cheap: put vLLM behind a load balancer or a Kubernetes Deployment with a readiness probe so a rolling restart drains and replaces the instance without dropping traffic. “Without restarting” is the wrong goal here; “restart without downtime” is the achievable one.
    2. If the engine is alive but every request still OOMs, suspect fragmentation. Set PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True and restart once. With expandable segments PyTorch grows and shrinks one pool instead of stranding memory in fragmented blocks, which greatly reduces this form of the loop. After that restart you should not need another for fragmentation.
    3. Make sure the V0 engine is not pinned. Current vLLM only has the V1 engine, whose GPU-subprocess isolation keeps a crash from propagating straight into the request handler. If an old deployment still sets VLLM_USE_V1=0, upgrading off V0 removes the AsyncEngineDeadError failure mode entirely.
    4. If it keeps recurring, you are under-provisioned. Apply the startup-sizing fixes from the earlier section (lower max_model_len, fp8 KV cache, lower max_num_seqs) so the spike never happens, rather than recovering from it again and again.

    Sources: vLLM GPU OOM CrashLoopBackOff runbook

    Preventing it: a starting configuration

    A starting point that avoids most OOM incidents on a single dedicated GPU:

    export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
    
    vllm serve <model> \
      --max-model-len <your real p99 context, not the model max> \
      --max-num-seqs <your real concurrency ceiling> \
      --gpu-memory-utilization 0.90 \
      --kv-cache-dtype fp8
    

    The principle: size max_model_len and max_num_seqs to the workload you actually have, not the model’s maximums; keep gpu_memory_utilization around 0.90 so there is headroom outside the pool; turn on fp8 KV cache unless you have measured a quality regression; and set the allocator env var so a recovered spike leaves less fragmentation behind. Load-test at your real concurrency before production — the KV cache math depends on numbers you only know at runtime.

    Sources: vLLM optimization docs

    Closing thoughts

    The honest version of “solve the vLLM OOM loop without restarting the server” is: prevent the OOM with correct init sizing, set expandable_segments:True once so fragmentation is far less likely to make it persistent, and lean on the V1 engine’s subprocess isolation and recompute preemption so KV-cache pressure costs you latency instead of a wedged server.

    What you genuinely do live is narrow — client-side load reduction now, LoRA hot-swap, and allocator config that applies on the next start. Everything else is an engine-init argument, so the practical target is not “never restart” but “restart without downtime”: a load balancer or a rolling Kubernetes deployment turns a required restart into a non-event. Chasing in-place recovery of a dead engine — EngineDeadError, or AsyncEngineDeadError on an old pinned V0 — is effort better spent on sizing the server so it does not die.

    Frequently Asked Questions

    How do I fix “CUDA out of memory” in vLLM?

    First identify which OOM it is. A startup ValueError: To serve at least one request ... is a KV-cache sizing problem: lower --max-model-len, raise --gpu-memory-utilization, or add --kv-cache-dtype fp8. A torch.OutOfMemoryError while serving is a headroom problem: lower --gpu-memory-utilization to 0.85, add --enforce-eager, cap --max-num-seqs, and set PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True. All of those except the env var require restarting the server.

    What does “To serve at least one request with the model’s max seq len” mean?

    After loading weights and reserving overhead, the memory left for the KV cache cannot hold a single request at max_model_len tokens. vLLM prints an estimated maximum model length; set --max-model-len at or below it, raise --gpu-memory-utilization, or use --kv-cache-dtype fp8 to make each token’s KV entry smaller.

    Can I change gpu_memory_utilization without restarting vLLM?

    No. It is an engine-init argument, fixed when vllm serve starts. The same is true of max_model_len, max_num_seqs, kv_cache_dtype, enforce_eager, and tensor_parallel_size. Changing any of them means a restart.

    What is a good gpu_memory_utilization value?

    0.90 on a dedicated GPU, 0.92–0.95 if the card is doing nothing else, 0.80–0.85 on a shared host or if you get runtime OOMs. Never 1.0 — PyTorch’s allocator and prefill spikes need memory outside vLLM’s reserved pool. The default is 0.9.

    Does --swap-space help with OOM on the vLLM V1 engine?

    Effectively no. V1’s default preemption mode is RECOMPUTE, and V1 dropped host-memory swap for the KV cache, so --swap-space does not offload blocks to CPU the way it did on V0. Rely on recompute preemption and correct sizing instead.

    How do I stop vLLM OOM errors from persisting across every request?

    First check the logs: EngineDeadError (or AsyncEngineDeadError: Background loop has errored already on an old pinned vLLM) means the engine loop is dead and only a restart fixes it. If the engine is alive but every request still OOMs, set PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True and restart once — that greatly reduces the allocator-fragmentation form of the loop. Current vLLM’s recompute preemption also keeps sustained KV-cache pressure from escalating into a crash.

    Does vLLM restart itself after a CUDA OOM crash?

    No. A dead engine (EngineDeadError, or AsyncEngineDeadError on old pinned vLLM) stays dead until the process is restarted externally. Run vLLM behind a load balancer or a Kubernetes Deployment with a readiness probe so that restart drains and replaces the instance without dropping traffic.


    Keep reading: LLM Inference & Serving

    All guides in this series: LLM Inference & Serving Guides: vLLM, Ollama & Hugging Face