-
Notifications
You must be signed in to change notification settings - Fork 6.8k
[Clojure] Add resource scope to clojure package #13993
Conversation
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.
The implementation looks good to me. The tests are technically sound and illustrate the desired behavior. My only comment is just a nit and question: is there a use case (perhaps one someone encountered) that would make sense as a more natural example for the tests but could still be easily tested? (.e.g with the isDisposed
method or otherwise).
contrib/clojure-package/test/org/apache/clojure_mxnet/resource_scope_test.clj
Outdated
Show resolved
Hide resolved
(is (true? (.isDisposed (:temp-x @native-resources)))) | ||
(is (false? (.isDisposed x))))) | ||
|
||
(deftest test-resource-scope-with-sym |
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.
can you also add a unit test for nested with sym?
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.
one more unit test idea (similar in spirit to scala api): many ndarray/ones
within a vector
and only the first one is returned after a transformation (e.g. (+ (first v) 1)
)
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.
interesting the result here - I put test cases in, but if a whole vector has been assigned to a let binding and you return the first, the whole vector is not disposed. You have to call copy on the first or not assign the whole vector to a let to have it disposed
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.
There are some edge cases still I think there - but on the whole, it's an improvement for mem management of native resources.
|
||
(defn -main [& args] | ||
(let [[dev dev-num] args | ||
devs (if (= dev ":gpu") | ||
(mapv #(context/gpu %) (range (Integer/parseInt (or dev-num "1")))) | ||
(mapv #(context/cpu %) (range (Integer/parseInt (or dev-num "1")))))] | ||
(start devs))) | ||
(resource-scope/using (start devs)))) |
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.
having the whole thing encapsulated in one resource-scope
seems weird to me. is it possible to wrap using
around the individual pieces (e.g. mnist-iter
, fit
, fit-params
) so that most of the code remains the same?
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.
that's a good idea - thanks
contrib/clojure-package/src/org/apache/clojure_mxnet/resource_scope.clj
Outdated
Show resolved
Hide resolved
contrib/clojure-package/test/org/apache/clojure_mxnet/resource_scope_test.clj
Show resolved
Hide resolved
Thanks for your review @kedarbellare - I'll work on implementing the feedback 😸 |
(defmacro using | ||
"Uses a Resource Scope for all forms. This is a way to manage all Native Resources like NDArray and Symbol - it will deallocate all Native Resources by calling close on them automatically. It will not call close on Native Resources returned from the form" | ||
[& forms] | ||
`(ResourceScope/using (new ResourceScope) (util/forms->scala-fn ~@forms))) |
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.
this may be a stupid question but is there any value in reusing a previously defined ResourceScope
object? why does the scala API allow a scope
to be passed in?
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.
That's a good question. From looking at the Scala api I would say that it would enable finer grained control - you could manually add or remove native resources from a scope and then manually call close on it. But I don't think that we need to expose that on the Clojure side, unless you have a different view on it.
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 don't see a big need for it -- at least for now -- but i was wondering whether the motivation was something like tf.variable_scope (e.g. reusing symbols).
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 don't know - maybe @nswamy knows more about the context behind the design? ^
I had some stuff come up - but plan to finish this up sometime this week :) |
- move from defs to atoms to make the tests a bit better
more tests
186a8aa
to
a5db1f2
Compare
I think all the feedback is addressed. Feel free to take another look. If there are no more changes needed, I will merge 😸 |
(swap! native-resources conj x) | ||
x)))))] | ||
(is (false? (ndarray/is-disposed return-val))) | ||
(is (every? true? (mapv ndarray/is-disposed (:temp-ndarrays @native-resources)))))) |
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.
@native-resources
is a vector. just check (mapv ndarray/is-disposed @native-resources)
? (also i didn't know that (:keyword vector)
didn't raise an error)
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.
If fixed some other problems with laziness in the tests with repeatedly
. Evidently the weird behavior in the dispose of the first of the collection was just my fault on the tests. Now everything works as expected 🎆
:eval-data test-data | ||
(resource-scope/with-let [_mod (m/module (get-symbol) {:contexts devs})] | ||
(-> _mod | ||
(m/fit {:train-data (mx-io/mnist-iter {:image (str data-dir "train-images-idx3-ubyte") |
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.
just for my understanding, does resource-scope
not work when the train-data
and eval-data
are defined outside the with-let
block?
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.
It will work if they are defs
outside, but you will still get the undisposed ndarray warning. I refactored to move it out to functions which does work and looks better too :)
now they work as expected!
(is (false? (sym/is-disposed x))))) | ||
|
||
;;; Note that if first is returned the rest of the collection ndarrays will | ||
;;; NOT be disposed |
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.
update comment?
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.
oh yes - the curse of the leftover misleading comments :)
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.
neat!! looks great! 🎉
can't wait to use this 😃 |
* Add resource scope to clojure package * add rat * fix integration test * feedback from @benkamphaus - move from defs to atoms to make the tests a bit better * adding alias with-do and with-let more tests * another test * Add examples in docstring * refactor example and test to use resource-scope/with-let * fix tests and problem with laziness now they work as expected! * refactor to be a bit more modular * remove comments
* Add resource scope to clojure package * add rat * fix integration test * feedback from @benkamphaus - move from defs to atoms to make the tests a bit better * adding alias with-do and with-let more tests * another test * Add examples in docstring * refactor example and test to use resource-scope/with-let * fix tests and problem with laziness now they work as expected! * refactor to be a bit more modular * remove comments
* Add resource scope to clojure package * add rat * fix integration test * feedback from @benkamphaus - move from defs to atoms to make the tests a bit better * adding alias with-do and with-let more tests * another test * Add examples in docstring * refactor example and test to use resource-scope/with-let * fix tests and problem with laziness now they work as expected! * refactor to be a bit more modular * remove comments
Description
Ports ResourceScope from the Scala package and introduces
using
macro to automatically close any resource in the enclosing forms.#13442
and see https://cwiki.apache.org/confluence/display/MXNET/JVM+Memory+Management for more context
Checklist
Essentials
Please feel free to remove inapplicable items for your PR.
Changes
Also added the resource scope to the imclassification example.
New runs to the example do not show any warnings of memory leaks of this nature:
WARN org.apache.mxnet.WarnIfNotDisposed: LEAK: [one-time warning] ...