-
Notifications
You must be signed in to change notification settings - Fork 214
how to implement DataType families? #115
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
DataType
s?
I think most Java developers would expect to use Although surely we want to align closely with the data type families defined in the core library, and that does motivate exposing them through the core library's C API, I see drawbacks to that approach:
The set of data types evolves slowly, so I see little drawback to maintaining the type families as hand-coded interface inheritance. Also, I can imagine times when a bit of poetic license would be beneficial to fit better into Java's conceptual structure. |
The requirement is that some methods allow only a floating data type, some allow a number (floating + integer), some allow a boolean and number, while others allow any type. As long as we can make that distinction, we could use |
I think a set of interfaces is fine. Either way when we move to a version of Java with sealed classes & type patterns in switches (https://openjdk.java.net/jeps/360 and https://github.com/openjdk/amber-docs/blob/master/site/design-notes/type-patterns-in-switch.md) then we can get benefits from having a closed hierarchy and hopefully cut out a bunch of unnecessary code. |
Hi Dean & all, thanks for starting a thread on this very important topic,
Manipulating properly data types in TensorFlow Java is indeed a non-trivial
task. Besides, you will find in this PR draft
<#92> multiple attempts to reduce
the complexity of the solution actually in place. Let me deep dive a little
bit more in the subject, with some history lessons (sorry for the long
message but I think it is important to understand every intrinsic details
to take the right decisions).
The first version of the API was only supporting runtime data type checks
via the Tensor.datatype()
<https://github.com/tensorflow/java/blob/1d35c17dcc85286a91f59a6ff0b94c48f1b8d4b1/tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/Tensor.java#L221>
and Output.datatype()
<https://github.com/tensorflow/java/blob/1d35c17dcc85286a91f59a6ff0b94c48f1b8d4b1/tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/Output.java#L46>,
where a `DataType` was simply represented by an enum
<https://github.com/tensorflow/tensorflow/blob/46b6537110a00470dfc3093911f1a8c9eebdbe25/tensorflow/java/src/main/java/org/tensorflow/DataType.java#L24>.
Then, support was added for carrying the data type of each graph node and
tensor as a generic parameter on their respective classes so that some type
validation can be accomplished at compile time. While some collaborators
already questioned the practicality of doing this, I personally think it is
a good example of leveraging idiomatic features of Java compared to
type-agnostic languages like Python.
It is important to note that at that time, it was decided to map common
TensorFlow data types to a standard Java types, familiar to all developers,
and fallbacking on a custom type class only if such correspondence cannot
be done. You can still consult this original mapping
<https://github.com/tensorflow/tensorflow/blob/46b6537110a00470dfc3093911f1a8c9eebdbe25/tensorflow/java/src/main/java/org/tensorflow/DataType.java#L107>
in the TF Java 1.x implementation. While this solution was elegant, it had
some drawbacks that forced me to switch to a different design when adding
support for NdArray:
- Java standard types (Float, Integer, Double...) were simply used as
markers for compile-time safety checks and could never be used to access
safely the data as we would expect (explicit conversion of the data by the
user was still required, as you can see in TF Java 1.x here
<https://github.com/tensorflow/tensorflow/blob/46b6537110a00470dfc3093911f1a8c9eebdbe25/tensorflow/java/src/main/java/org/tensorflow/Tensor.java#L362>).
Furthermore, custom types like `UInt8` cannot even be instantiated to hold
any value.
- Java standard types do carry enough information to let us map the
tensor data in a n-dimensional space (NdArray) while still benifitting from
primitive type support for faster I/O (e.g when accessing a tensor of
32-bit floats, we want `float` endpoints and not only `Float` ones
requiring auto-boxing/unboxing).
That sort of explain why I've ended up adding this hierarchy of type
classes and interfaces
<https://github.com/tensorflow/java/tree/master/tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/types>
to replace the former Java standard types as the parameter of our generic
classes like `Tensor`, `Output`, `Operand`, etc. This way, when calling `
Tensor<T>.data()
<https://github.com/tensorflow/java/blob/1d35c17dcc85286a91f59a6ff0b94c48f1b8d4b1/tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/Tensor.java#L275>`,
which returns an instance of `T`, we have everything in hand to access
efficiently the tensor memory (e.g. `TFloat32` extends directly from
`FloatNdArray` with all its primitive endpoints). This detail is very
important to understand as it justifies most of the decisions that were
taken in the actual design of data types in TF Java.
In an attempt to centralize everything related to a given data type in a
single file, I've also replaced the original enum in `DataType` by static
instances
<https://github.com/tensorflow/java/blob/1d35c17dcc85286a91f59a6ff0b94c48f1b8d4b1/tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/types/TFloat32.java#L41>
of this classes instead, instantiated in each type classes. This also
allows us to pass more complex parameters as this instantiation of the data
type object, such as the tensor data mapper. Ideally, the factory for
`DataType` would have been package-private, as only a finite set of
instances should exist (sealed interfaces would enforce this as well). But
since type classes and `DataType` are not under the same package (and doing
so will break a few things as well), I had to keep it public. But we can
intuitively think that instead of comparing datatypes by name at runtime as
Jim did temporarily, we could compare them by their identity:
`if (dtype == TInt32.DTYPE || dtype == TFloat32.DTYPE || ...`
or even better, we can pass the characteristics of a given data type to this
factory
<https://github.com/tensorflow/java/blob/1d35c17dcc85286a91f59a6ff0b94c48f1b8d4b1/tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/DataType.java#L55>
as a series of boolean, a `BitSet` or maybe even its type family so that
methods like `DataType.isNumber()` won't need to switch on a value at
runtime.
I do agree though that mixing both runtime and compile time data type
checks increase the cognitive load for our users, as Dean explained. If we
could do everything at compile time (which is again a powerful idiomatic
feature of Java that we should leverage), that would be perfect but is
nearly impossible. Replacing raw switches by `instanceof` sounds right at
first but you need to be careful since it is not the instances of a
`DataType` that extends from the `TType` families but the type classes
themselves. Instances of these classes are only accessible once you have
mapped the memory of a tensor. So you won't be able to extract valuable
information simply from `Output.datatype()`, which is mostly useful in
graph building since we don't always have access to a tensor.
- Karl
…On Sat, Sep 19, 2020 at 3:50 PM Adam Pocock ***@***.***> wrote:
I think a set of interfaces is fine. Either way when we move to a version
of Java with sealed classes & type patterns in switches (
https://openjdk.java.net/jeps/360 and
https://github.com/openjdk/amber-docs/blob/master/site/design-notes/type-patterns-in-switch.md)
then we can get benefits from having a closed hierarchy and hopefully cut
out a bunch of unnecessary code.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#115 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACNEEXW5BGR4QQRIRVDN2IDSGUDQHANCNFSM4RTCUKCA>
.
|
My leaning from an initial study of this situation is that
I love the overall direction of PR draft <#92>. I think the PR's definition of
I would find this much easier to understand if we could express it this way, where
Anyway, I'm good so far, but when we add
Looking back at the current roles of
|
The reason why I preferred to carry both the recursive and the Java type in
This is not really required, when you think about it, even in the actual implementation. It is only used to safely typed the returned value of
With PR draft #92 , that will work yes and I agree it would be a great improvement. The reason for having a
TF1.x was doing something similar, I'm ok to go back to this schema if that can improve the whole user experience. I don't think we need a I would leave the responsibility of initializing this metadata to the So we should do now something like:
Right now we build protobuf DataType from the numeric value of a given type, like in here. That will still be possible to do then from
I agree, again with PR #92 that should work well |
Ah, ok, I do see benefit in the Putting that aside, as an alternative to this:
we could do this:
(I'm not sure yet how I'd advocate using generics in the above. I like the simplicity of the bare The
So instead of code like this:
We'd end up with code like this:
I see this as a benefit because of how it relates to compile-time typing:
|
Ideally we could do this, the problem though is that we won't be able to do reverse lookups from a native type code to a concrete Java class until the user initializes explicitly that class. For example, for a fictional function that returns true or false if the tensor in input has a zero value,
Under the hood, to instantiate the tensor This is why we now have this static helper class,
I like @deansher , it looks like reviving #92 is a good next step, what do you think about starting your experimentations based on that branch? |
Yikes -- good point!
I agree wrt the principles, but I don't have enough context to judge wrt version/code management. @karllessard , is your intuition that
|
That is what I had in mind, yes. Despite that this PR draft is getting "old", it shouldn't be very hard to rebase it on master when we will be ready to merge it, i.e. once we are satisfied with the changes. I'm aware that the PR might get pretty large at that time but this kind of changes require us to work on a complete solution to ensure that we are on the good track. There might still be a possibility that we could split the PR in smaller parts later if needed. |
I'll certainly go with your intuition on that! I'll try some small PRs against #92 's branch. (Or else coordinate further if I can't PR in the main repo against a fork's PR branch.) |
So I was playing around data types yesterday to get break from the release stuff, and I ended up with a series of new observations concerning the removal of the generic parameter to the In general, the transition went smoothly, only at the For example, In other words, From this observation, I was thinking if there is a way to add additional information to a What is interesting with annotations too is that with our annotation processor, we can scan all classes annotated with I'm not done brainstorming if the usage of annotations in this context makes total sense or not but I wanted to share my thoughts now so I can increase the number of neurons in my network :) |
Your framing of this, Karl, aligns exactly with my mental model of it:
That said, I don't have experience providing a heavily-used package that defines its own annotations. So if there are gotchas, I don't know them. |
Resolved by #174 |
Currently in tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/DataType.java, we use an uncomfortable pattern:
DataType
is omniscient aboutTType
and dispatches on a stringTType.NAME
. For example:The author of this pattern, @JimClarke5, mentioned in our Google Group that he regarded it as temporary:
@karllessard suggested a direction, although with some open questions:
Let's decide on a direction! This is moderately pervasive in our code, but also a pretty simple change, so I'd advocate we choose a direction soon and I'm tempted to volunteer to make the change.
The text was updated successfully, but these errors were encountered: