Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python memory fix #1214

Merged
merged 26 commits into from Feb 5, 2018
Merged

Python memory fix #1214

merged 26 commits into from Feb 5, 2018

Conversation

rcurtin
Copy link
Member

@rcurtin rcurtin commented Jan 24, 2018

This is a set of bugfixes for the Python bindings that improve several issues including fixing #1201. @nvasil: you can apply the patches from here or use this branch and it will fix your memory issue---I tested with the script from #1201.

A more clear summary of changes:

  • Avoid copies of matrices and other large types by using std::move() when calling CLI::SetParam<>() from the generated Cython bindings.

  • Clarify and correct ownership of matrix memory: now, in all cases, matrices passed down from Python will retain their ownership of the memory, and internally in mlpack we will use non-strict aliases so that we never delete the user's memory.

  • As a result of the code above, any binding that uses std::move() on the input matrix (most of them do) will now result in the Python user's matrix not being empty.

  • Also we print default values for optional string/double/int parameters now, which is nice.

There is more to do before I merge this, though:

  • Handle the model ownership issue: right now an input model gets wiped out and the user has to use copy.copy(). Instead we should have CLI hold pointers to models instead, which allows us to make output_model point to the same model as input_model.

  • Force copies by default but provide a clear warning that extra copies are being done. We can add an option make_copies and if make_copies=False, then we use aliases everywhere but the user's input matrices or models may be modified. I'm not sure if I like defaulting make_copies to True or False; there are advantages and disadvantages to each. I'm slightly inclined to set make_copies=False by default, then make clear in the documentation that if "weird things" are happening, try make_copies=True.

In any case, as it stands this fixes #1201.

This fixes mlpack#1201 but does collateral damage: now every matrix that is passed in
will be modified.  This will be fixed next...
First: don't transfer the memory state to Armadillo.  Instead, make a non-strict
alias; this means that Armadillo will only allocate new memory on a size change,
and we won't end up making the Python user's numpy array be nothing when we're
done because we std::move()d the memory.

Second: Only transfer the ownership of the matrix to numpy if the Armadillo
matrix actually allocated its own memory.  This can help with situations where
the user passes a numpy matrix that they have a reference to that will also be a
part of the output.
@ShikharJ
Copy link
Member

@rcurtin Why is the #1200 referenced in the last line? I'm assuming you meant to refer to #1201?

@rcurtin
Copy link
Member Author

rcurtin commented Jan 25, 2018

Because I remembered the correct number wrong when I originally wrote the description, then I went back and changed it but missed the third reference. :)

Specifically, whereas before any serializable type would actually be held as a
std::tuple<ModelType, std::string>, but now we will hold this as a
std::tuple<ModelType*, std::string>.  This also requires some changes for memory
handling.  Our assumption is that the CLI object will *own* (and delete) any
pointers it is holding, so we must also do some extra handling in the
end_program.hpp code.
If we make a copy during the conversion, then Armadillo should own the memory
(otherwise Python will delete the temporary).
This refactors the Python bindings to deal with holding pointers to serializable
types.

Some minor extra work is necessary to prevent Python from accidentally deleting
models multiple times.
This is hand-tested as working, but I need to write actual tests still.
(This is probably not completely correct but I am moving systems right now.)
@rcurtin
Copy link
Member Author

rcurtin commented Jan 29, 2018

Once I have this passing all the tests (we will see if it does) I think this is ready. The key change to the Python bindings is that now I can do the following:

>>> from mlpack import knn
>>> import numpy as np
>>> x = np.random.rand(10, 10)
>>> out = knn(reference=x, k=3, verbose=True)

That will (or may) modify x, but we can force a copy:

>>> out = knn(reference=x, k=3, verbose=True, copy_all_inputs=True)

and now x will be copied before the binding is run. This option is documented and should obviate the need for the clunky copy.copy() I was suggesting before. By default inputs are not copied for the sake of speed (including models).

@rcurtin
Copy link
Member Author

rcurtin commented Jan 29, 2018

@mlpack-jenkins build this

@zoq
Copy link
Member

zoq commented Jan 29, 2018

@mlpack-jenkins test this please

const void* /* input */,
void* output)
{
*((void**) output) =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such a pretty line 😃

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish I knew a better way to do it, but the only way I could seem to was by casting around void pointers in all kinds of strange ways...

@@ -31,6 +31,11 @@ void PrintInputProcessing(
const typename boost::disable_if<std::is_same<T,
std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0)
{
// The copy_all_inputs parameter must be handled first, and so is outside the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and so is outside the

Is this correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess it could also be written and therefore is outside the; I went ahead and changed it for clarity.

* MoveFromPtr[Model](CLI.GetParam[Model]('param_name'),
* (<ModelType> param_name).modelptr)
* SetParamPtr[Model]('param_name', (<ModelType> param_name).modelptr,
* CLI.HasParam('copy_all_inputs')) TODO
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this done?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wrote that down when I was getting up to get dinner, and when I came back and wrote the code apparently I forgot to remove the TODO... :)

@@ -0,0 +1,56 @@
/**
* @file delete_allocated_memory.hpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any difference between this file and the file in src/mlpack/bindings/cli/delete_allocated_memory.hpp? Maybe I missed something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case yes, the CLI bindings have to delete the first element of a tuple<T*, std::string>, but the test bindings only hold the T* directly so the delete command is a little different.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the clarification.

Copy link
Member

@zoq zoq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. No more comments.

@rcurtin
Copy link
Member Author

rcurtin commented Feb 5, 2018

Ok, I will go ahead and merge this then.

@rcurtin rcurtin merged commit fcd5ccf into mlpack:master Feb 5, 2018
@rcurtin rcurtin deleted the python-memfix branch February 5, 2018 15:55
nikhilgoel1997 added a commit to nikhilgoel1997/mlpack that referenced this pull request Feb 5, 2018
rcurtin added a commit that referenced this pull request Feb 5, 2018
rcurtin pushed a commit to rcurtin/mlpack that referenced this pull request Feb 15, 2018
rcurtin added a commit to rcurtin/mlpack that referenced this pull request Feb 15, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Memory leaks in the python wrappers
3 participants