unslothai/unsloth · error · ValueError

Invalid gpu_ids {requested_ids}: duplicate GPU IDs are not a

Error message

Invalid gpu_ids {requested_ids}: duplicate GPU IDs are not allowed. Parent-visible GPUs: {parent_visible_ids}

What it means

Raised on the CUDA/ROCm/XPU path (after the env-var mask was confirmed numeric) when requested gpu_ids contain duplicates. The message also lists the parent-visible GPU ids so the user can see the valid id space. Distinct from the Vulkan duplicate error: this one can cross-check against the parent-visible set.

Source

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

            raise ValueError(
                f"Invalid gpu_ids {requested_ids}: GPU IDs must be non-negative. "
                f"Rejected IDs: {negative_ids}."
            )
        return requested_ids

    if not parent_visible_spec["supports_explicit_gpu_ids"]:
        env_var_name = (
            "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:

View on GitHub (pinned to 203007d190)

Solutions

  1. Deduplicate before calling: sorted(set(gpu_ids)) (order preserved with dict.fromkeys if order matters).
  2. Fix the config merge that introduced the duplicate.
  3. Use each physical GPU id exactly once.

Example fix

# before
resolve_requested_gpu_ids([0, 0, 1])  # ValueError

# after
resolve_requested_gpu_ids(list(dict.fromkeys([0, 0, 1])))  # [0, 1]
Defensive patterns

Strategy: validation

Validate before calling

def dedup_ids(ids):
    return list(dict.fromkeys(int(i) for i in ids))

# resolve_requested_gpu_ids(dedup_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: Calling resolve_requested_gpu_ids([0, 0]) or any list with repeats while CUDA_VISIBLE_DEVICES (or ZE_AFFINITY_MASK) is numeric or unset.

Common situations: Merging a default gpu list with a user-provided one ([0] + [0,1]); multi-select UI bugs re-submitting an id; hand-edited JSON/YAML configs; scripts appending ids in a loop without dedup.

Related errors


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