sgl-project/sglang · error · TypeError

mlx_call_multi operation must return a non-empty tuple or li

Error message

mlx_call_multi operation must return a non-empty tuple or list of MLX arrays

What it means

The operation callback passed to mlx_call_multi must return a non-empty tuple or list of results (single outputs belong to mlx_call). After invoking the callback, the wrapper checks the container type and emptiness before touching elements.

Source

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

    borrowed: tuple[Any, ...] = tuple(
        (
            tensor.array
            if isinstance(tensor, MlxTensorView)
            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 = []

View on GitHub (pinned to 0132848349)

Solutions

  1. Return a tuple: `return (result,)` from the callback
  2. Use mlx_call for single-output operations
  3. Ensure every code path in the callback returns the container

Example fix

# before
def op(a, b):
    return a + b  # single array
outs = mlx_call_multi(op, [a, b])
# after
def op(a, b):
    return (a + b, a - b)
outs = mlx_call_multi(op, [a, b])
Defensive patterns

Strategy: validation

Validate before calling

result = op(*inputs)
assert isinstance(result, (tuple, list)) and len(result) > 0

Type guard

def is_valid_multi_result(r) -> bool:
    return isinstance(r, (tuple, list)) and len(r) > 0

Prevention

When it happens

Trigger: A callback returning None, a single mx.array, an empty list [], or any non-tuple/list object.

Common situations: Reusing a single-output op in mlx_call_multi without wrapping its return, or an op with a conditional early `return` path returning None.

Related errors


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