{"record":{"id":"f978622eed176d93","repo":"sgl-project/sglang","slug":"borrow-torch-tensors-requires-mps-tensors-got-de","errorCode":null,"errorMessage":"borrow_torch_tensors requires MPS tensors, got {devices}","messagePattern":"borrow_torch_tensors requires MPS tensors, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/utils/tensor_bridge.py","lineNumber":166,"sourceCode":"\n\n@_serialized_bridge\ndef borrow_torch_tensors(\n    *tensors: torch.Tensor, synchronize: bool = True\n) -> tuple[MlxTensorView, ...]:\n    \"\"\"Borrow one or more Torch MPS tensors, optionally synchronizing once.\n\n    The returned views own the Torch tensor references for their entire\n    lifetime.  No data copy is made.  Set ``synchronize=False`` only when a\n    surrounding operation (such as :func:`mlx_call`) performs the producer\n    barrier immediately before consuming the views.  This helper is\n    intentionally separate from :func:`torch_to_mlx`, whose contract is an\n    independent MLX copy.\n    \"\"\"\n    detached = tuple(tensor.detach() for tensor in tensors)\n    if any(tensor.device.type != \"mps\" for tensor in detached):\n        devices = \", \".join(str(tensor.device) for tensor in detached)\n        raise ValueError(f\"borrow_torch_tensors requires MPS tensors, got {devices}\")\n    if synchronize and detached:\n        torch.mps.synchronize()\n    return tuple(MlxTensorView._from_synchronized(tensor) for tensor in detached)\n\n\n@_serialized_bridge\ndef torch_to_mlx(tensor: torch.Tensor) -> mx.array:\n    \"\"\"Convert a PyTorch tensor to an independent MLX array.\n\n    MPS inputs are copied inside the unified Metal device.  Use ``mlx_call``\n    when a complete operation needs zero-copy MPS input imports; it owns the\n    borrowed MLX arrays for the complete lazy operation.\n\n    Args:\n        tensor: PyTorch CPU or MPS tensor.\n\n    Returns:\n        MLX array with the same data","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/utils/tensor_bridge.py#L148-L184","documentation":"borrow_torch_tensors borrows multiple tensors as zero-copy MLX views and validates up-front that every detached tensor is on MPS, listing all offending devices in the message. Unlike torch_to_mlx it never copies, hence the strict requirement.","triggerScenarios":"Calling borrow_torch_tensors([...]) where at least one tensor is on cpu/cuda — the check runs before any synchronization so no side effects occur.","commonSituations":"Mixed-device batches (some tensors never moved to MPS), or a default-device code path creating tensors on CPU in an otherwise MPS pipeline.","solutions":["Move all tensors to MPS before borrowing: [t.to('mps') for t in ts]","Use torch_to_mlx per-tensor if CPU provenance is acceptable for a copy"],"exampleFix":"# before\nviews = borrow_torch_tensors([a_cpu, b_mps])\n# after\nviews = borrow_torch_tensors([a_cpu.to('mps'), b_mps])","handlingStrategy":"validation","validationCode":"if any(t.device.type != \"mps\" for t in tensors):\n    tensors = [t.to(\"mps\") for t in tensors]\nviews = borrow_torch_tensors(tensors)","typeGuard":"def all_mps(ts) -> bool:\n    return all(t.device.type == \"mps\" for t in ts)","tryCatchPattern":null,"preventionTips":["Coerce the whole batch to MPS before borrowing","Write tests that assert the non-MPS batch fails before synchronization"],"tags":["sglang","mlx","mps","batch","device"],"backgroundTag":"unsupported-device","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}