PyTorch: TypeError: cant convert bfloat16 tensor to numpy
This error occurs when Python tries to convert a PyTorch bfloat16 tensor directly to a NumPy array via .numpy(). NumPy does not natively support bfloat16; cast the tensor with .float() or .to(torch.float32) before calling .numpy().
Root Cause Analysis
This error occurs when Python attempts to export or convert a PyTorch tensor with the torch.bfloat16 data type directly into a NumPy array using the .numpy() method.
Root Cause 1: Lack of Native bfloat16 Support in Standard NumPy
The bfloat16 (Brain Floating Point 16) format is a 16-bit floating-point representation developed by Google Brain specifically for deep learning accelerators (TPUs and modern GPUs). Unlike the standard IEEE 754 half-precision float (float16), bfloat16 allocates 8 exponent bits and 7 mantissa bits (matching the dynamic range of float32). Standard CPython builds of NumPy (prior to specialized custom dtypes) do not implement bfloat16 as a built-in primitive scalar type. When PyTorch's C-extension attempts to map the tensor's memory buffer into a NumPy array, it discovers no matching NumPy dtype and raises TypeError: can't convert bfloat16 tensor to numpy.
Root Cause 2: LLM and Transformer Inference with Automatic Mixed Precision (AMP)
Modern Large Language Models (LLMs such as Llama 3, Mistral, Gemma) and generative vision models are pretrained and served natively in bfloat16 to halve memory bandwidth while preserving numeric stability. When engineers attempt to evaluate loss, log embeddings, or plot intermediate activations with Matplotlib/Seaborn by calling tensor.numpy(), the conversion crashes immediately.
Root Cause 3: Tensor Located on CUDA / Accelerator Memory
Attempting .numpy() directly on a CUDA tensor will raise a RuntimeError: Tensor on device cuda:0 is not on CPU. Developers often chain .cpu().numpy(), but if the tensor remains in bfloat16, the call fails at the subsequent type translation step.
Root Cause 4: Scikit-learn and SciPy Interoperability Expectations
Downstream numerical analysis tools (such as Scikit-learn clustering, PCA, or SciPy optimization) expect standard IEEE floating-point NumPy arrays (float32 or float64). Exporting uncast tensors creates compatibility barriers across the data science stack.
Reproduction Code (MCVE)
# Simulating PyTorch bfloat16 to NumPy conversion failure
class MockBFloat16Tensor:
def __init__(self, values):
self.values = values
self.dtype = "torch.bfloat16"
def numpy(self):
# PyTorch C-extension raises TypeError because NumPy lacks native bfloat16 dtype
raise TypeError(
"TypeError: can't convert bfloat16 tensor to numpy. "
"NumPy does not have a native bfloat16 dtype. "
"Use tensor.float().numpy() or tensor.to(torch.float32).numpy() instead."
)
# Instantiating a mock bfloat16 tensor
tensor_bfloat16 = MockBFloat16Tensor([0.15625, 2.5, -3.14])
# Attempting direct conversion to NumPy triggers TypeError
tensor_bfloat16.numpy()
Solution 1: Cast Tensor to Float32 Before Calling .numpy()
Convert the PyTorch tensor to standard 32-bit floating point using .float() or .to(torch.float32) before transferring data to NumPy.
import numpy as np
# In production with PyTorch:
# import torch
# tensor_bf16 = torch.tensor([1.5, 2.5, 3.5], dtype=torch.bfloat16)
# numpy_array = tensor_bf16.float().cpu().numpy()
# Standalone simulation demonstrating float32 casting pattern
class PyTorchTensorFloatCast:
def __init__(self, data):
self.data = data
self.dtype = "torch.bfloat16"
def float(self):
# Cast to float32 representation
return PyTorchTensorFloat32(self.data)
class PyTorchTensorFloat32:
def __init__(self, data):
self.data = data
self.dtype = "torch.float32"
def numpy(self):
return np.array(self.data, dtype=np.float32)
tensor = PyTorchTensorFloatCast([1.5, 2.75, 4.125])
numpy_result = tensor.float().numpy()
print("Converted NumPy array:", numpy_result)
print("NumPy dtype:", numpy_result.dtype)
assert numpy_result.dtype == np.float32
Solution 2: Use ml_dtypes or torch.view / tolist() for Precision Preservation
Convert the tensor values to native Python floats with .tolist() or install the ml_dtypes package for third-party bfloat16 NumPy support.
import numpy as np
# Solution 2: Convert to Python list or cast to float64
def safe_tensor_to_numpy(data_list: list, target_dtype=np.float32) -> np.ndarray:
return np.asarray(data_list, dtype=target_dtype)
raw_values = [0.00390625, 128.5, -42.0]
arr = safe_tensor_to_numpy(raw_values, np.float32)
print("Safe NumPy export:")
print(arr)
assert len(arr) == 3
A common trap when working with gradients in PyTorch is calling .float().numpy() on a tensor that requires grad (requires_grad=True). This will fail with RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead. The correct complete sequence for an arbitrary GPU bfloat16 tensor is always tensor.detach().cpu().float().numpy().
Another edge case is casting to torch.float16 instead of torch.float32. While NumPy supports standard IEEE float16 (np.float16), bfloat16 values with large exponents (> 65504) will overflow into inf during float16 conversion. Casting to float32 preserves the full dynamic range of bfloat16 without precision clipping.
Contrast bfloat16 with float16: bfloat16 has a large 8-bit dynamic exponent with 7-bit precision, whereas standard float16 has 5 exponent bits and 10 precision bits.