unslothai/unsloth · error · ValueError

Invalid gpu_ids {requested_ids}: GPU IDs must be non-negativ

Error message

Invalid gpu_ids {requested_ids}: GPU IDs must be non-negative. Rejected IDs: {negative_ids}. Parent-visible GPUs: {parent_visible_ids}

What it means

Raised on the CUDA/ROCm/XPU path when any requested gpu_id is negative; rejected ids and the parent-visible set are both included in the message. It runs after the duplicate check, so [0, -1] first fails as a duplicate-free but negative-containing list here.

Source

Thrown at studio/backend/utils/hardware/hardware.py:2791

            "ZE_AFFINITY_MASK" if get_device() == DeviceType.XPU else "CUDA_VISIBLE_DEVICES"
        )
        raise ValueError(
            f"Invalid gpu_ids {requested_ids}: explicit physical GPU IDs are "
            f"unsupported when {env_var_name} uses non-numeric or subdevice "
            f"entries ({parent_visible_spec['raw']!r}). Omit gpu_ids to use "
            "the parent-visible devices."
        )

    if len(set(requested_ids)) != len(requested_ids):
        raise ValueError(
            f"Invalid gpu_ids {requested_ids}: duplicate GPU IDs are not allowed. "
            f"Parent-visible GPUs: {parent_visible_ids}"
        )

    # Reject negative IDs.
    negative_ids = [gpu_id for gpu_id in requested_ids if gpu_id < 0]
    if negative_ids:
        raise ValueError(
            f"Invalid gpu_ids {requested_ids}: GPU IDs must be non-negative. "
            f"Rejected IDs: {negative_ids}. Parent-visible GPUs: {parent_visible_ids}"
        )

    # Only enforce the physical upper bound when the count is reliable (nvidia-smi).
    # A torch count reflects only visible devices, so it could falsely reject valid
    # physical indices. The parent-visible check below is always authoritative.
    if physical_gpu_count > 0 and parent_visible_ids:
        max_parent_id = max(parent_visible_ids)
        if physical_gpu_count > max_parent_id:
            # Count is plausibly physical, so enforce it.
            out_of_range = [gpu_id for gpu_id in requested_ids if gpu_id >= physical_gpu_count]
            if out_of_range:
                raise ValueError(
                    f"Invalid gpu_ids {requested_ids}: IDs must be physical GPU IDs "
                    f"between 0 and {physical_gpu_count - 1}. "
                    f"Rejected IDs: {out_of_range}. Parent-visible GPUs: {parent_visible_ids}"
                )

View on GitHub (pinned to 203007d190)

Solutions

  1. Use None or [] for auto-selection instead of -1.
  2. Clamp/validate ids to 0..N-1 before passing them.
  3. Fix the 'find free GPU' helper to return None when no GPU is free and handle that case.

Example fix

# before
gpu_ids = free_gpu_ids or -1  # -> ValueError

# after
gpu_ids = free_gpu_ids or []  # auto-select
Defensive patterns

Strategy: validation

Validate before calling

gpu_ids = [i for i in gpu_ids if isinstance(i, int) and i >= 0]
gpu_ids = list(dict.fromkeys(gpu_ids))
# then resolve_requested_gpu_ids(gpu_ids)

Type guard

def is_valid_gpu_id_list(ids) -> bool:
    return (
        isinstance(ids, (list, tuple))
        and all(isinstance(i, int) and i >= 0 for i in ids)
        and len(set(ids)) == len(ids)
    )

Prevention

When it happens

Trigger: Passing gpu_ids containing a negative value, commonly -1 imported from CUDA's 'all GPUs' convention or produced by index arithmetic underflow.

Common situations: Scripts that set gpu_ids=-1 to mean 'use everything'; default sentinel values of -1 leaking from config parsing; -1 returned from a 'find free gpu' helper when none found.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/c6f99fa0478b6824. Report an issue: GitHub.