通过pybind11从 C++绑定返回torch::Tensor时,Python会出现段错误
我有来自一个设备的数据,这些数据在Python中以数组的形式捕获,然后发送到一个C++绑定,以加速一些后处理,这些后处理必须先对数据进行正确格式化,并通过大量迭代任务将其转换为一个 torch.Tensor,再用 libtorch 进行最终转换成张量。我正在使用 pybind11 将C++函数绑定到Python。
我已经在C++实现中测试了这个函数(processing),它确实能够正确处理我的数据并创建具有正确形状和数据的张量,但在从绑定返回时,在C++ -> Python边界上出现了一些奇怪的内存问题。
基本情况是,我从C++获取最终张量并将其返回给Python。我把返回的张量赋给Python的一个变量,这样的做法可以工作。我能够访问张量的形状、数据类型等信息,但当我尝试访问任何类似数组的数据(通过下标访问、打印张量等)时,有时会发生段错误,具体取决于张量的形状。
据我所知,这与C++和 Python在调用析构函数时的内存管理问题有关。看起来Python基本上是在创建张量的浅拷贝,只能让我访问像形状这样的信息,但底层的实际数据却被C++销毁了,因此我本质上是在访问一个空指针(根据我的观察大致如此)。我尝试使用 py::return_value_policy::copy 来阻止这种情况,但没有效果(我尝试了所有返回值策略,但没有一个起作用)。
然而,这种行为非常奇怪,因为它只在某些张量尺寸下发生,就像一些非常特定的尺寸(见下文)。
这是我的C++函数签名和pybind模块:
// processing.cpp
#include <torch/torch.h>
#include <pybind11/pybind11.h>
namespace pybind11 as py;
torch::Tensor processing(py::array np_data, py::array dim){
// Lots of working code here, converts numpy to uint8_t C array
// to do necessary C stuff
// ...
// data_array is a C-style array of uint16_t
auto options = torch::TensorOptions().dtype(torch::kUInt8);
torch::Tensor output = torch::from_blob(data_array, options);
finalOutput = torch::reshape(output, /*proper output shape*/);
return finalOutput;
}
// Create python bindings
PYBIND11_MODULE(processing_ext, m, py::mod_gil_not_used()){
m.def("processing", &processing,
py::arg("batchOutputs"),
py::arg("dim"),
py::return_value_policy::copy
);
}
我使用ninja按以下 setup.py 进行编译,因为cmake在 python3 setup.py build 上一直让人头疼:
# setup.py
from setuptools import setup
from torch.utils.cpp_extension import CppExtension, BuildExtension
import os
setup(
name="processing_ext",
ext_modules=[
CppExtension(
name="processing_ext",
sources=["processing.cpp"],
extra_compile_args=[],
),
],
cmdclass={
'build_ext': BuildExtension
}
)
然后我在Python中调用我的绑定。
# test.py
import torch
import processing_ext # My c++ binding
# Dimensions
batch_size: int = 5
num_neurons: int = 32
data_len: int = 28
data_wid: int = 28
data: np.ndarray = ... # My data
output_shape: np.ndarray = np.ndarray([batch_size, num_neurons, data_len, data_wid])
# Call my C++ binding
output: torch.Tensor = processing_ext.processing(data, output_shape)
上述代码在上文列出的 test.py 指定的维度下可以工作,但并非在所有尺寸下都能工作。具体来说,保持其他维度不变时,当 num_neurons 处于1-5,或18-35之间时能工作,但在6-17或 36直到无穷之间则不行(这些区间均包含端点)。
有什么想法解释为什么在某些尺寸下会发生段错误,而在其他尺寸下不会?
我在Ubuntu 24.04.4上使用Python 3.12.3,搭配torch 2.7.1和 pybind11 2.11.2。
解决方案
/// Exposes the given `data` as a `Tensor` without taking ownership of the
/// original data. `sizes` should specify the shape of the tensor, `strides` the
/// stride in each dimension. The `deleter` function (a
/// `std::function<void(void*)>`) will be called on the `data` when the Tensor
/// data would normally be deallocated. The `TensorOptions` specify additional
/// configuration options for the returned tensor, such as what type to
/// interpret the `data` as.
很可能在执行 from_blob 之后需要对张量进行克隆,以确保创建了一个张量对象实例:
// Source - https://stackoverflow.com/q/79934444
// Posted by inventi, modified by community. See post 'Timeline' for change history
// Retrieved 2026-05-01, License - CC BY-SA 4.0
// processing.cpp
#include <torch/torch.h>
#include <pybind11/pybind11.h>
namespace pybind11 as py;
torch::Tensor processing(py::array np_data, py::array dim){
// Lots of working code here, converts numpy to uint8_t C array
// to do necessary C stuff
// ...
// data_array is a C-style array of uint16_t
auto options = torch::TensorOptions().dtype(torch::kUInt8);
torch::Tensor output = torch::from_blob(data_array, options).clone();
finalOutput = torch::reshape(output, /*proper output shape*/);
return finalOutput;
}
// Create python bindings
PYBIND11_MODULE(processing_ext, m, py::mod_gil_not_used()){
m.def("processing", &processing,
py::arg("batchOutputs"),
py::arg("dim"),
py::return_value_policy::copy
);
}