return tensor && *tensor && tensor->GetDeviceType() == device_type;
}
-inline Tensor* BlobSetTensor(Blob* blob, const Tensor& tensor) {
- return blob->Reset<Tensor>(new Tensor(tensor));
+inline Tensor* BlobSetTensor(Blob* blob, Tensor&& tensor) {
+ return blob->Reset<Tensor>(new Tensor(std::move(tensor)));
}
inline Tensor GetSizedTensorWithOptions(
- const Tensor& t,
+ Tensor&& previous_tensor,
at::IntList dims,
at::TensorOptions options) {
- Tensor tensor = t;
+ Tensor tensor = std::move(previous_tensor);
if (tensor.GetDevice() == options.device() ||
(!tensor.GetDevice().has_index() &&
tensor.GetDeviceType() == options.device().type())) {
inline Tensor
XBlobGetMutableTensor(Blob* blob, at::IntList dims, at::TensorOptions options) {
- return *BlobGetMutableTensor(blob, dims, options);
+ return BlobGetMutableTensor(blob, dims, options)->UnsafeSharedInstance();
}
inline Tensor* BlobGetMutableTensor(Blob* blob, DeviceType device_type) {
CAFFE_ENFORCE(
ival->isTensor(),
"Output(int, DeviceType) is only available for IValues that store Tensors");
- Tensor tensor = caffe2::Tensor(ival->toTensor());
- tensor = GetSizedTensorWithOptions(tensor, dims, options);
+ Tensor tensor = GetSizedTensorWithOptions(
+ caffe2::Tensor(ival->toTensor()), dims, options);
+ // assign it back in case it changed
auto at_tensor = at::Tensor(std::move(tensor.getIntrusivePtr()));
*ival = IValue(at_tensor);
* NB: See TensorImpl for documentation on these methods.
*/
class CAFFE2_API Tensor final {
+ private:
+ enum Unsafe { IDoWantAliasing };
+ Tensor(const Tensor& other, Unsafe _) : impl_(other.getIntrusivePtr()) {}
+
protected:
using TensorImplPtr = c10::intrusive_ptr<TensorImpl, UndefinedTensorImpl>;
TensorImplPtr impl_;
}
}
+ // caffe2::Tensor is explicitly marked as moveable-only because before
+ // the refactoring the class used to be a value type and a lot of user code
+ // is written this way. With PyTorch unification, caffe2::Tensor actually
+ // has semantics of a shared_ptr now (via intrusive_ptr). However, to prevent
+ // accidental mistakes when changing legacy code we keep caffe2::Tensor
+ // to have movable semantics.
+ //
+ // If you need to get a pointer to the same Tensor instance (not to be
+ // confused with shared storage), `UnsafeSharedInstance` can be used. It has
+ // the same behavior as `at::Tensor a = b`.
+ Tensor(const Tensor&) = delete;
+ Tensor& operator=(const Tensor&) = delete;
+ Tensor(Tensor&&) = default;
+ Tensor& operator=(Tensor&&) = default;
+
operator bool() const {
return impl_.defined();
}
return impl_.get();
}
+ Tensor UnsafeSharedInstance() const {
+ return Tensor(*this, IDoWantAliasing);
+ }
+
/**
* @brief Creates a tensor of the given device type.
*
}
}
- auto data = Input(0);
+ const auto& data = Input(0);
auto output = Output(0);
return SliceImpl<SIndex, Context>(
inputs.size() <=
static_cast<unsigned>(config_.predict_net->external_input_size()));
for (size_t i = 0; i < inputs.size(); ++i) {
+ // This is evil and shares the same underlying tensor
BlobSetTensor(
getBlob(config_.ws.get(), config_.predict_net->external_input(i)),
- inputs[i]);
+ inputs[i].UnsafeSharedInstance());
}
if (!config_.ws->RunNet(config_.predict_net->name())) {
}
outputs->clear();
for (size_t i = 0; i < config_.predict_net->external_output_size(); ++i) {
- outputs->push_back(
- getTensor(config_.ws.get(), config_.predict_net->external_output(i)));
+ outputs->emplace_back(
+ getTensor(config_.ws.get(), config_.predict_net->external_output(i))
+ .UnsafeSharedInstance());
}
return true;
}
"Input can't be found: ",
input.first);
}
- BlobSetTensor(getBlob(config_.ws.get(), input.first), input.second);
+ // This is evil and shares the same underlying tensor
+ BlobSetTensor(
+ getBlob(config_.ws.get(), input.first),
+ input.second.UnsafeSharedInstance());
}
return config_.ws->RunNet(config_.predict_net->name());
outputs->clear();
for (size_t i = 0; i < config_.predict_net->external_output_size(); ++i) {
outputs->push_back(
- getTensor(config_.ws.get(), config_.predict_net->external_output(i)));
+ getTensor(config_.ws.get(), config_.predict_net->external_output(i))
+ .UnsafeSharedInstance());
}
return true;
}
}
for (const std::string& outputName : output_names()) {
- outputs->emplace(outputName, getTensor(config_.ws.get(), outputName));
+ outputs->emplace(
+ outputName,
+ getTensor(config_.ws.get(), outputName).UnsafeSharedInstance());
}
return true;
}
template <class Context>
class TensorFeeder : public BlobFeederBase {
public:
- Tensor FeedTensor(
+ Tensor FeedTensor(const DeviceOption& option, PyArrayObject* original_array) {
+ Tensor out;
+ FeedTensor(option, original_array, &out, false);
+ return out;
+ }
+
+ void FeedTensor(
const DeviceOption& option,
PyArrayObject* original_array,
- Tensor* out = nullptr) {
+ Tensor* out,
+ bool in_place) {
#ifdef USE_NUMPY
PyArrayObject* array = PyArray_GETCONTIGUOUS(original_array);
auto g = MakeGuard([&]() { Py_XDECREF(array); });
dims.push_back(npy_dims[i]);
}
- Tensor tensor;
- bool in_place = out != nullptr;
+ Tensor& tensor = *out;
if (in_place) {
- out->Resize(dims);
- tensor = *out;
+ tensor.Resize(dims);
}
// Now, copy the data to the tensor.
switch (npy_type) {
tensor.raw_mutable_data());
}
context.FinishDeviceComputation();
- return tensor;
#else
CAFFE_THROW("Caffe2 compiled without NumPy support.");
- return caffe2::Tensor(); // will not reach here
#endif // USE_NUMPY
}
FeedTensor(
option,
original_array,
- BlobGetMutableTensor(blob, OptionToDevice(option).type()));
+ BlobGetMutableTensor(blob, OptionToDevice(option).type()),
+ true);
} else {
blob->Reset<Tensor>(new Tensor(FeedTensor(option, original_array)));
}
cpu_tensor_feeder.FeedTensor(
option,
original_array,
- BlobGetMutableTensor(blob, OptionToDevice(option).type()));
+ BlobGetMutableTensor(blob, OptionToDevice(option).type()),
+ true);
} else {
blob->Reset<Tensor>(new Tensor(
cpu_tensor_feeder.FeedTensor(cpu_option, original_array)));