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
- Wrap scalars: mx.array(loss_value)
- 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
- Wrap scalars with mx.array(...) inside ops
- Never mix torch tensors into MLX op returns
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
- mlx_call_multi operation must return a non-empty tuple or li
- Serve backend {name!r} factory returned {type(backend).__nam
- resolved target '{qualified_name}' is not callable: {type(ta
- SGLANG_USE_MLX requires stable Torch 2.13.x and MLX >= 0.32.
- SGLANG_USE_MLX requires stable Torch 2.13.x and MLX >= 0.32.
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/74ccff3ce2a881fb.
Report an issue: GitHub.