-
Notifications
You must be signed in to change notification settings - Fork 214
Functional graph definition API #181
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
Comments
A couple of considerations I ran into when working with my Kotlin version of this:
@TFFunction("MyFunction", "addTwo")
interface MyFunctionDef{
default Operand<TFloat32> define(Ops tf, Operand<TFloat32> x){
return tf.math.add(x, tf.constant(2f));
}
} generates final class MyFunction extends BaseConcreteFunction implements MyFunctionDef {
@Override
public Signature signature(Ops tf){
Placeholder<TFloat32> x = tf.placeholder(TFloat32.class);
Operand<TFloat32> output = MyFunctionDef.super.define(x);
return Signature.builder("addTwo").input("x", input).output("output", output).build();
}
// ConcreteFunction stuff based on signature()
} but it's still a bit clunky. What I did in my Kotlin experiments was to have |
I totally agree. Python does this mapping automatically by analyzing the name of the arguments in the function invocation but we cannot do that at runtime in Java. There is already a convenient shortcut for functions that just accept and return a single tensor. Maybe we can change that method to accept a list of positional tensor arguments where the order must follow the same as in the function signature? This approach cannot be totally error prone but could be acceptable, we could at least validate that the type and shape of the tensors match those of the signature at runtime and fail gracefully if they don't. As for the returned tensors, we probably need to use the same utility as was proposed in #167 but we should decouple it from the graph/session execution, as functions could also be ran eagerly (or eventually will). So maybe something like Again we could still pass a Kotlin makes it easier to build up maps, with |
I think a lot of my preferences for this depends on where you see I see what you mean about the result class, but there will still be some differences from session, in particular the "get from Output/Operand" methods. A |
It is definitely the first choice for running inference on a loaded saved model, as it takes care itself of the tensor/op name mangling that is even more apparent since TF2.0. A lot of our users will use TF Java specifically for running inference on a model pre-trained in Python, so won't have to deal with layers. Still, they will need to keep track of the lifetime of the input/output tensors so we need to make that easy for them. With inference, I don't see why a user would prefer to run its graph using a session instead of a function.
Functions are mainly used for running a model after it has been built (or compiled), as a whole, but we should probably see how they could be transformed as a custom layer when building a model as well.
That is something we can look at, yes. Or we could simply add this methods to the |
Ok, that clears up most of my concerns about the interface. While the
Atm there's no way to find Outputs from strings without storing the Graph, although you could use a |
For tensor lifetimes, is there any reason a finalizer wasn't used? It seems like the perfect solution here, which makes me think I'm missing something. |
We cannot rely on the garbage collector to free native resources like tensors, as it won't keep track of their actual size in native memory, thus won't trigger in time even if the JVM is close to be OOM. In eager mode, we do activate the GC to collect unused resources allocated during a session (it used to be done directly in TF but now is handled by JavaCPP) but this is just used as a "best-effort" and we ask our users to free their resources explicitly. Another solution that we've discussed many time is to rely on reference counter (like C++ smart pointers) instead of using |
Right, I didn't realize finalize had so many caveats, that's unfortunate. Usability wise, I don't see any benefit to reference counting over close and try with resources, you'd still have to call it manually. Has any thought been given to using scopes, like how |
Although it's implicit, that still uses reference counting though. It's similar to how Swift does ARC (which might be adopted in the Java language too at some point in the far far future), so comes with all the caveats about circular references and what not. |
Using finalizers causes the JVM to do a bunch of extra bookkeeping and tend to put those objects on the slow path for everything. Plus they are tricky to reason about and not necessarily called. It's best to avoid them unless absolutely necessary. |
I should be there. @saudet I don't think we'd run into any circular references or anything similar since all of our references are unidirectional, so to speak: multiple java objects can reference one tensor. There's no tensors referencing each other, or anything like that. I'm not sure how it interacts with eager mode and operands but it doesn't seem like it would be too bad. One issue with any lifecycle solution that we should talk about either now or Friday: NDArrays. Since TTypes are now NDArrays, it's possible to do something like: TFloat32 x = someTensor();
FloatNdArray y = x.index(...); // or return x as a NDArray, etc
The easiest solutions are to add a close method to NDArray that closes any underlying tensor, or add a way to get an underlying tensor if it exists, but it's still rather inconvenient. |
So, I was bored and decided to play around with a It was surprisingly easy, the only Java API issue I ran into (other than |
That's very interesting, @JimClarke5 raised another case where converting an It shouldn't be hard to do it properly, without the use of a fake class. We probably just need a revert mapping table to find the right |
That won't work for my use case, I don't think. I don't have access to the For the compiler plugin specifically, the optimal solution for me would be a "fake"/mock |
Could you elaborate a little more on what your use case is and what the hurdles are? You can inspect the graph to get the operations back out of it and might be able to unpick it a little more from there. |
Basically, I have something like this being generated: val testFuncProp = FunctionRunner("testFunc") {
val a = it["a"] as Int? ?: error("a is not present")
val b = it["b"] as Operand<TInt32>? ?: error("b is not present")
val c: Operand<TInt32> = math.add(b, constant(2))
val outputs = mapOf("c" to c)
val result: (Map<String, Operand<*>>) -> Pair<Int, Operand<TInt32>> = {
val c = it[""] as Operand<TInt32>? ?: error("c is not present")
(a + 2) to c
}
FunctionResult(outputs, result)
} The only things that were there originally is the The goal is to substitute the In other words, I have some code that looks like |
The text was updated successfully, but these errors were encountered: