sgl-project/sglang · error · TypeError

mlx_call_multi outputs must be MLX arrays

Error message

mlx_call_multi outputs must be MLX arrays

What it means

After the container check, mlx_call_multi verifies every element is an mx.array; mixed Python scalars, torch tensors, or NumPy arrays in the returned sequence raise this TypeError before the shared evaluation boundary.

Source

Thrown at python/sglang/srt/utils/tensor_bridge.py:371

            else _torch_to_mlx(tensor.detach(), copy=False, synchronize=False)
        )
        for tensor in tensors
    )

    if target_device.type == "cpu" and any(
        array.dtype == mx.float64 for array in borrowed
    ):
        with mx.stream(mx.cpu):
            result = operation(*borrowed)
    else:
        result = operation(*borrowed)
    if not isinstance(result, (tuple, list)) or not result:
        raise TypeError(
            "mlx_call_multi operation must return a non-empty tuple or list of MLX arrays"
        )
    arrays = tuple(result)
    if any(not isinstance(array, mx.array) for array in arrays):
        raise TypeError("mlx_call_multi outputs must be MLX arrays")

    # Prepare all outputs before crossing the one shared MLX evaluation
    # boundary. This is the key difference from calling mlx_to_torch in a
    # loop, which would fence/evaluate every result separately.
    arrays = tuple(_prepare_mlx_export(array, target_device, mx) for array in arrays)
    mx.eval(*arrays)

    # DLPack cannot represent negative strides. Materialize all such outputs
    # together so even this safety path has one additional evaluation boundary
    # rather than one boundary per result.
    negative = tuple(_has_negative_stride(array) for array in arrays)
    if any(negative):
        materialized = []
        for array, needs_materialization in zip(arrays, negative):
            if needs_materialization:
                stream = mx.cpu if target_device.type == "cpu" else mx.gpu
                array = mx.contiguous(array, stream=stream)
            materialized.append(array)

View on GitHub (pinned to 0132848349)

Solutions

  1. Wrap scalars: mx.array(loss_value)
  2. Convert torch outputs back through torch_to_mlx or restructure to return only MLX arrays

Example fix

# before
def op(a):
    return (a * 2, 0.5)  # 0.5 is a float
# after
def op(a):
    return (a * 2, mx.array(0.5))
Defensive patterns

Strategy: type-guard

Validate before calling

assert all(hasattr(a, "dtype") and type(a).__module__.startswith("mlx") for a in result)

Type guard

def all_mlx_arrays(seq) -> bool:
    import mlx.core as mx
    return all(isinstance(a, mx.array) for a in seq)

Prevention

When it happens

Trigger: A callback returning (mx.array, float) or (mx.array, torch.Tensor) — any non-mx.array element.

Common situations: Returning loss values or metadata alongside tensors from the op; forgetting mx.array(...) conversion for scalars.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/74ccff3ce2a881fb. Report an issue: GitHub.