You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Saving and loading models in Java with a functional API #101
Hi @Shajan , no worries at all for the meeting, I've created a new issue in GitHub to continue our discussion so it won't get lost in a PR that already has been merged.
So the idea is that while we will continue to only support session-centric saved models (these models with a single graph and one or more signatures), we will present it as a function to the user so the API will remain the same as much as possible when we start supporting them for real.
For the unit test, quick question, how do you build the Python model? Is it using one or more tf.function in it?
These are great developments, looking forward to a unit test whenever it's ready.
I merged some older code from @karllessard to save/load models, seems to work with preliminary testing: #100 (comment) Comments welcome! All my diffs, source, and outputs are posted.
Happy to test new methods for model save/load, if that's helpful. My work's motivated by deadlines over here, need model save/load. Thanks for everything!
@Shajan , can you please take a look at this new implementation? It turns out that the actual model was working great for loading function definition but not for exporting them, so I've shuffle the classes a little bit.
Also, like we discussed, even if in TF Java we continue to only support session-centric models, we expose each signatures as if they were real tf.function.
Right now the FunctionGraph (new revocable name for TfFunction) carries a session for executing directly a graph, providing a list of tensor inputs, but does not manage its lifetime (i.e. the function is invalid as soon as the session is released). I guess eventually, each function will have its own graph and session and therefore should become AutoCloseable as well to prevent leaks. But for now, a minimalist implementation seems to be enough to cover both of our cases, i.e. inference with function graphs and saved model export.
Please let me know what you think of it and see if we should still go in that direction or not, thank you!
I woke up this morning and rethought about this, I think resource management concerns shared by @yzhuang should be addressed properly. I'll push a new version of this code once I'm done with my coffee...
I still think that we should have a unit test loading a model saved from Python using tf.function, like you were previously suggesting. Because even if we can now save models in Java, we won't be able to test the inference on graphs created out of real functions yet. Let me know if you intend to do it or if I should, thanks!
I woke up this morning and rethought about this, I think resource management concerns shared by @yzhuang should be addressed properly. I'll push a new version of this code once I'm done with my coffee...
Hum, time to start doing some reference counting? :)
@saudet, I certainly thought of it! I think we should attack reference counting as a whole rather that just by pieces though (i.e. using it for managing all native resources, not only a few of them). It is probably better to wait after the first alpha release and see this as a future improvement.
My latest draft handles function resource management properly, at the extent of what we are able to do with try-with-resource blocks.
I still think that we should have a unit test loading a model saved from Python using tf.function, like you were previously suggesting. Because even if we can now save models in Java, we won't be able to test the inference on graphs created out of real functions yet. Let me know if you intend to do it or if I should, thanks!
Agree.
For the unit test, here is my suggestion:
Checkin as resource, saved models created in Python, using Tensorflow 1.X and 2.X, then java unit test using these models. The python code will be checked in with instructions, but will not create the saved model as part of the test run.
Ok thanks @Shajan , I've merged my changes, please let me know when you are done with your unit tests so we can make an official PR for the master branch
@saudet, I certainly thought of it! I think we should attack reference counting as a whole rather that just by pieces though (i.e. using it for managing all native resources, not only a few of them). It is probably better to wait after the first alpha release and see this as a future improvement.
My latest draft handles function resource management properly, at the extent of what we are able to do with try-with-resource blocks.
Can't we simply add a numActiveOwners besides numActiveRuns (and have ConcreteFunction increment it and Session.close() decrement it)? We get ref counting almost for free this way, because the ref counting concurrency handling logic is already implemented due to needing to handle numActiveRuns.
The proposal is done wrt the actual memory management paradigm where only resources that are explicitly allocated by the user must be protected by try-with-resource. For example, if you are loading a saved model bundle, you don't need to protect the retrieved ConcreteFunction instances, the same way as you don't need to protect the Session, since they are owned by the bundle itself.
// Actual sessionstry (SavedModelBundles = SavedModelBundler.load(...);
Tensor<TFloat32> in = TFloat32.tensorOf(...);
Tensor<TFloat32> out = s.session().feed("input1", in).fetch(out).run().get(0).expect(TFloat32.DTYPE)) {
System.out.println("Result is " + out.data().getFloat());
}
// New functionstry (SavedModelBundles = SavedModelBundler.load(...);
Tensor<TFloat32> in = TFloat32.tensorOf(...);
Tensor<TFloat32> out = s.function("func1").call(in).expect(TFloat32.DTYPE)) {
System.out.println("Result is " + out.data().getFloat());
}
On the other hand, if the user explicitly creates the function, he should then release it, the same way again we do with graphs and sessions.
// Actual sessionstry (Graphg = newGraph()) {
// ... fill graphtry (Sessions = newSession(g);
Tensor<TFloat32> in = TFloat32.tensorOf(...);
Tensor<TFloat32> out = s.session().feed("input1", in).fetch(out).run().get(0).expect(TFloat32.DTYPE)) {
System.out.println("Result is " + out.data().getFloat());
}
}
// New functionstry (ConcreteFunctionf = ConcreteFunction.create(...)) {
try (Tensor<TFloat32> in = TFloat32.tensorOf(...);
Tensor<TFloat32> out = f.call(in).expect(TFloat32.DTYPE)) {
System.out.println("Result is " + out.data().getFloat());
}
}
The only exceptional case is when a function is created from an existing session, in which case protecting it is not required and will have no effect, e.g.
try (Graphg = newGraph()) {
// ... fill the graphtry (Sessions = newSession(g)) {
try (ConcreteFunctionf = ConcreteFunction.create(Signature.builder()..., s)) {
// this block was not required but won't break anything neither
}
ConcreteFunctionf = ConcreteFunction.create(Signature.builder()..., s); // this is legit in this case
}
}
We are already planning to improve in the future the user experience with memory management as a whole by using reference counting on the native resources themselves but this is not in the scope of this feature. I think following the actual rules is acceptable for now.
Did you identified a case where the experience would be worsened by the introduction or the use of functions? And can you please provide an example where keeping track of the number of session owners would enhance it, providing that the Graph, Session and SavedModelBundle instances still need to be protected by try-with-resource?
@karllessard Hi Karl, the above usage patterns look good to me, thanks for the clarifications!
(Sorry for the late reply--have been traveling and my Internet access have been spotty.)
Just to clarify, are these try-with-resource semantics required in Scala, e.g. #100 (comment)
It seems the Scala code compiles without all the try-blocks, but is not using the try-blocks a resource-usage-related mistake?
So, for my Scala 2.11.8, I wrote a manager that maintains a list of AutoCloseable objects, and closes them properly. This may be worth doing properly if there is ever a Tensorflow Scala.
Also, I've tested this new Functional API in Scala, and saving works for me, thanks for your hard work @karllessard@Shajan! With the older Session-based model saving code, I had some issues with loading a model that used padding operations -- haven't tested yet with this new API.
Activity
karllessard commentedon Aug 14, 2020
Hi @Shajan , no worries at all for the meeting, I've created a new issue in GitHub to continue our discussion so it won't get lost in a PR that already has been merged.
So the idea is that while we will continue to only support session-centric saved models (these models with a single graph and one or more signatures), we will present it as a function to the user so the API will remain the same as much as possible when we start supporting them for real.
For the unit test, quick question, how do you build the Python model? Is it using one or more
tf.functionin it?Shajan commentedon Aug 15, 2020
Yes, a model with a few tf.functions.
Here is a colab notebook with example https://colab.research.google.com/drive/1NrFbhe6do2Hq0VF3hgEk4KsGhY9kkNfU?usp=sharing
Copied the main parts here below.
aday00 commentedon Aug 17, 2020
These are great developments, looking forward to a unit test whenever it's ready.
I merged some older code from @karllessard to save/load models, seems to work with preliminary testing: #100 (comment) Comments welcome! All my diffs, source, and outputs are posted.
Happy to test new methods for model save/load, if that's helpful. My work's motivated by deadlines over here, need model save/load. Thanks for everything!
Session-based saving of Tensorflow models, based on Karl Lessard's code
karllessard commentedon Aug 24, 2020
@Shajan , can you please take a look at this new implementation? It turns out that the actual model was working great for loading function definition but not for exporting them, so I've shuffle the classes a little bit.
Also, like we discussed, even if in TF Java we continue to only support session-centric models, we expose each signatures as if they were real
tf.function.Right now the
FunctionGraph(new revocable name forTfFunction) carries a session for executing directly a graph, providing a list of tensor inputs, but does not manage its lifetime (i.e. the function is invalid as soon as the session is released). I guess eventually, each function will have its own graph and session and therefore should become AutoCloseable as well to prevent leaks. But for now, a minimalist implementation seems to be enough to cover both of our cases, i.e. inference with function graphs and saved model export.Please let me know what you think of it and see if we should still go in that direction or not, thank you!
CC\ @aday00 , @yzhuang
karllessard commentedon Aug 24, 2020
I woke up this morning and rethought about this, I think resource management concerns shared by @yzhuang should be addressed properly. I'll push a new version of this code once I'm done with my coffee...
karllessard commentedon Aug 24, 2020
New PR draft available at #103
karllessard commentedon Aug 25, 2020
Hi @Shajan,
I still think that we should have a unit test loading a model saved from Python using
tf.function, like you were previously suggesting. Because even if we can now save models in Java, we won't be able to test the inference on graphs created out of real functions yet. Let me know if you intend to do it or if I should, thanks!saudet commentedon Aug 25, 2020
Hum, time to start doing some reference counting? :)
karllessard commentedon Aug 25, 2020
@saudet, I certainly thought of it! I think we should attack reference counting as a whole rather that just by pieces though (i.e. using it for managing all native resources, not only a few of them). It is probably better to wait after the first alpha release and see this as a future improvement.
My latest draft handles function resource management properly, at the extent of what we are able to do with
try-with-resourceblocks.Shajan commentedon Aug 27, 2020
Agree.
For the unit test, here is my suggestion:
Checkin as resource, saved models created in Python, using Tensorflow 1.X and 2.X, then java unit test using these models. The python code will be checked in with instructions, but will not create the saved model as part of the test run.
What do you think?
karllessard commentedon Aug 27, 2020
@Shajan : sounds good! Do you think you can take care of this part? I’ll merge my changes to the common branch probably today
Shajan commentedon Aug 27, 2020
@karllessard yes, i will add the test.
karllessard commentedon Aug 28, 2020
Ok thanks @Shajan , I've merged my changes, please let me know when you are done with your unit tests so we can make an official PR for the master branch
yzhuang commentedon Aug 28, 2020
Hi @karllessard , try-with-resource is potentially a worse user experience.
Sessionalready does reference counting to track active runs (so that the session is not closed while a run is in flight), see: https://github.com/tensorflow/java/blob/master/tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/Session.java#L361Can't we simply add a
numActiveOwnersbesidesnumActiveRuns(and have ConcreteFunction increment it and Session.close() decrement it)? We get ref counting almost for free this way, because the ref counting concurrency handling logic is already implemented due to needing to handlenumActiveRuns.karllessard commentedon Aug 29, 2020
Hi @yzhuang , thanks for you valuable feedbacks,
The proposal is done wrt the actual memory management paradigm where only resources that are explicitly allocated by the user must be protected by try-with-resource. For example, if you are loading a saved model bundle, you don't need to protect the retrieved
ConcreteFunctioninstances, the same way as you don't need to protect theSession, since they are owned by the bundle itself.On the other hand, if the user explicitly creates the function, he should then release it, the same way again we do with graphs and sessions.
The only exceptional case is when a function is created from an existing session, in which case protecting it is not required and will have no effect, e.g.
We are already planning to improve in the future the user experience with memory management as a whole by using reference counting on the native resources themselves but this is not in the scope of this feature. I think following the actual rules is acceptable for now.
Did you identified a case where the experience would be worsened by the introduction or the use of functions? And can you please provide an example where keeping track of the number of session owners would enhance it, providing that the
Graph,SessionandSavedModelBundleinstances still need to be protected by try-with-resource?yzhuang commentedon Sep 1, 2020
@karllessard Hi Karl, the above usage patterns look good to me, thanks for the clarifications!
(Sorry for the late reply--have been traveling and my Internet access have been spotty.)
aday00 commentedon Sep 9, 2020
Just to clarify, are these try-with-resource semantics required in Scala, e.g. #100 (comment)
It seems the Scala code compiles without all the try-blocks, but is not using the try-blocks a resource-usage-related mistake?
Shajan commentedon Sep 9, 2020
Unsure if i need additional permissions. Push to origin failed while trying to push the unit test.
java [shared-saved-model] $ git push origin
Username for 'https://github.com': sdasan@gmail.com
Password for 'https://sdasan@gmail.com@github.com':
...
remote: Resolving deltas: 100% (6/6), completed with 6 local objects.
remote: error: GH006: Protected branch update failed for refs/heads/shared-saved-model.
remote: error: You're not authorized to push to this branch. Visit https://docs.github.com/articles/about-protected-branches/ for more information.
To https://github.com/tensorflow/java
! [remote rejected] shared-saved-model -> shared-saved-model (protected branch hook declined)
error: failed to push some refs to 'https://github.com/tensorflow/java'
karllessard commentedon Sep 10, 2020
@Shajan , can you please try again?
Shajan commentedon Sep 10, 2020
Thanks, merged.
karllessard commentedon Sep 10, 2020
Great! I think I can now rebase that branch and make a PR out of it
aday00 commentedon Oct 15, 2020
Yes, required, and mistake otherwise. Tensors and other AutoCloseable native resources must be
close()ed to prevent resource leaks, but Scala 2.11.8 does not support try-with-resource semantics.Scala 2.13 introduced
Usingfor try-with-resource https://stackoverflow.com/questions/39866000/java-try-with-resource-not-working-with-scalaSo, for my Scala 2.11.8, I wrote a manager that maintains a list of AutoCloseable objects, and closes them properly. This may be worth doing properly if there is ever a Tensorflow Scala.
Also, I've tested this new Functional API in Scala, and saving works for me, thanks for your hard work @karllessard @Shajan! With the older Session-based model saving code, I had some issues with loading a model that used padding operations -- haven't tested yet with this new API.