Skip to content

Commit

Permalink
Merge pull request #219 from EthicalML/tensor_python_numpy_ownership
Browse files Browse the repository at this point in the history
[PYTHON] Ensure numpy array increments refcount of tensor to keep valid
  • Loading branch information
axsaucedo authored May 15, 2021
2 parents 5303904 + 12f6ad5 commit a3d8b78
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 6 deletions.
33 changes: 33 additions & 0 deletions docs/overview/python-package.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,39 @@ More specifically, it can be through the following functions:
* mgr.eval_async_<opname>_def - Runs operation asynchronously under a new anonymous sequence
* seq.record_<opname> - Records operation in sequence (requires sequence to be in recording mode)

Tensor Component
------------------

The `kp.Tensor` component provides utilities to load and manage data into GPU memory.

The primary interface to the GPU image leverage `np.array` containers which wrap the GPU memory.

One of the key things to take into consideration is the GPU memory and resource management that is provided by Kompute - namely the `kp.Tensor` allows for the memory to be managed until the python object refcount goes down to zero or is explicitly destroyed with the `destroy()` function.

Another thing to bare in mind is that when the `.data()` function is called, the numpy array would add an extra refcount, and the underlying resources won't be destroyed until that object is destroyed. This is shown more intuitively in the example below:

.. code-block:: python
:linenos:
m = kp.Manager()
t = m.tensor([1,2,3])
td = t.data()
del t
td
# this is OK
assert td.base.is_init() == True # OK
m.destroy() # Frees all memory inside tensors
assert td.base.is_init() == False # Consistent to expected setup
del td # Now this calls tensor destructor as refcount reaches 0
Log Level Configuration
^^^^^^
Expand Down
11 changes: 5 additions & 6 deletions python/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,17 @@ PYBIND11_MODULE(kp, m) {
py::class_<kp::Tensor, std::shared_ptr<kp::Tensor>>(m, "Tensor", DOC(kp, Tensor))
.def("data", [](kp::Tensor& self) {
// Non-owning container exposing the underlying pointer
py::str dummyDataOwner; // Explicitly request data to not be owned by np
switch (self.dataType()) {
case kp::Tensor::TensorDataTypes::eFloat:
return py::array(self.size(), self.data<float>(), dummyDataOwner);
return py::array(self.size(), self.data<float>(), py::cast(&self));
case kp::Tensor::TensorDataTypes::eUnsignedInt:
return py::array(self.size(), self.data<uint32_t>(), dummyDataOwner);
return py::array(self.size(), self.data<uint32_t>(), py::cast(&self));
case kp::Tensor::TensorDataTypes::eInt:
return py::array(self.size(), self.data<int32_t>(), dummyDataOwner);
return py::array(self.size(), self.data<int32_t>(), py::cast(&self));
case kp::Tensor::TensorDataTypes::eDouble:
return py::array(self.size(), self.data<double>(), dummyDataOwner);
return py::array(self.size(), self.data<double>(), py::cast(&self));
case kp::Tensor::TensorDataTypes::eBool:
return py::array(self.size(), self.data<bool>(), dummyDataOwner);
return py::array(self.size(), self.data<bool>(), py::cast(&self));
default:
throw std::runtime_error("Kompute Python data type not supported");
}
Expand Down
23 changes: 23 additions & 0 deletions python/test/test_tensor_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,26 @@ def test_type_unsigned_int():

assert np.all(tensor_out.data() == arr_in_a * arr_in_b)

def test_tensor_numpy_ownership():

arr_in = np.array([1, 2, 3])

m = kp.Manager()

t = m.tensor(arr_in)

# This should increment refcount for tensor sharedptr
td = t.data()

assert td.base.is_init() == True
assert np.all(td == arr_in)

del t

assert td.base.is_init() == True
assert np.all(td == arr_in)

m.destroy()

assert td.base.is_init() == False

0 comments on commit a3d8b78

Please sign in to comment.