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
Python memory fix #1214
Conversation
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.
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.)
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:
That will (or may) modify
and now |
@mlpack-jenkins build this |
@mlpack-jenkins test this please |
const void* /* input */, | ||
void* output) | ||
{ | ||
*((void**) output) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such a pretty line 😃
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this done?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
Ok, I will go ahead and merge this then. |
…in sparse_coding_main.cpp.
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 callingCLI::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 haveCLI
hold pointers to models instead, which allows us to makeoutput_model
point to the same model asinput_model
.Force copies by default but provide a clear warning that extra copies are being done. We can add an option
make_copies
and ifmake_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 defaultingmake_copies
toTrue
orFalse
; there are advantages and disadvantages to each. I'm slightly inclined to setmake_copies=False
by default, then make clear in the documentation that if "weird things" are happening, trymake_copies=True
.In any case, as it stands this fixes #1201.