-
Notifications
You must be signed in to change notification settings - Fork 1.7k
rust: support legacy TF 1.x histogram summaries #4740
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
Conversation
wchargin
left a 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.
I manually generated an organic TF 1.x histogram summary
You can use gs://tensorboard-bench-logs/mnist here, too. Can confirm
that it shows histograms at this commit but not before it, even with
your core tensor support.
| dim: vec![ | ||
| pb::tensor_shape_proto::Dim { | ||
| size: num_buckets as i64, | ||
| ..Default::default() | ||
| }, | ||
| pb::tensor_shape_proto::Dim { | ||
| size: 3, | ||
| ..Default::default() | ||
| }, | ||
| ], | ||
| ..Default::default() | ||
| }), |
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.
Optional: this is just tensor_shape(&[num_buckets as i64, 3]) if you
promote fn tensor_shape from a test helper to a real helper.
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.
True, but if you don't mind, I think I'll leave it as just a test helper for now since it's not all that bad written out, and I'm feeling a little burned from sinking a fair amount of time earlier generalizing this into a generic TensorProto construction utility (as we'd discussed, with the extension trait etc) before emerging from a pile of yak hair and deciding that there really wasn't enough boilerplate and I was ending up with net more complexity and lines of code from writing this utility and its tests than I was saving in the first place.
(Not that I necessarily object to such a utility at some point especially if we end up with more of these manipulations, to be clear; just feeling meh about it at this stage.)
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.
Sure, sounds good. Appreciate the awareness of the yak density and happy
that you feel comfortable calling that out.
| // bucket right edges; the first bucket's left edge is assumed to be -DBL_MAX and | ||
| // subsequent left edges are defined as the right edge of the preceeding bucket. | ||
| // | ||
| // Our conversion logic in data_compat.py however disobeys this and instead sets 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.
Sigh—my bad, probably. Put it on the list of “ways in which the
histogram transformation pipeline makes zero sense”.
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.
No worries, was not trying to cast aspersions :) I too have known about this quirk for a long time and not fixed it and then keep forgetting exactly what the quirk was, so I figured I'd just write it down here for posterity. Maybe 2021 will be the year we actually revisit histogram binning...
| let num_buckets = hp.bucket.len(); | ||
| let bucket_edges = || hp.bucket_limit.iter().take(num_buckets - 1).copied(); |
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.
Shall we check that bucket.len() == bucket_limit.len() and signal data
loss otherwise? or at least take num_buckets to be the shorter of the
two lengths? As written, it looks like bucket_lefts could be much
shorter than bucket_counts, which would make the output shape not
correct.
Also, if hp.bucket.len() == 0, the num_buckets - 1 is an error
(panic in debug mode), so let’s do something nicer in that case?
Maybe worth a test (up to you).
In case you’re interested, my sketch of this routine took quite a
different approach (imperative instead of streamy). I think that I like
your idea to use tensor_content, since at least cloning a Bytes is
cheap compared to cloning a Vec<f64>.
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.
Fair enough; updated to require the lengths match and to support the empty case (thanks for pointing that out), and added tests for each.
Re: streaminess what's the fun of having zero 𝜖-cost abstractions if we aren't gonna use 'em? 🦀 But I do actually find it easier to read personally than the offset-arithmetic version.
I went with tensor_content mostly to match what tf.make_tensor_proto will produce, FWIW.
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.
Lovely; thanks! Yeah, I’m perfectly happy with the streams here. I’m
part of the “use streams judiciously” crowd, and this looks like a
pretty easy sell. It’s definitely more readable than mine.
| ], | ||
| ..Default::default() | ||
| }), | ||
| tensor_content: tensor_content, |
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.
nit: Clippy says to pun this (tensor_content,).
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.
Done.
| bucket: vec![0.0, 10.0, 20.0, 20.0, 10.0, 0.0], | ||
| ..Default::default() | ||
| }; | ||
| let v = EventValue::Summary(SummaryValue(Box::new(Value::Histo(hp.clone())))); |
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.
nit: Clippy says these clones are useless (hp.clone() → hp, and below).
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.
Done, must have been vestigial.
nfelt
left a 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.
PTAL
| bucket: vec![0.0, 10.0, 20.0, 20.0, 10.0, 0.0], | ||
| ..Default::default() | ||
| }; | ||
| let v = EventValue::Summary(SummaryValue(Box::new(Value::Histo(hp.clone())))); |
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.
Done, must have been vestigial.
| ], | ||
| ..Default::default() | ||
| }), | ||
| tensor_content: tensor_content, |
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.
Done.
| dim: vec![ | ||
| pb::tensor_shape_proto::Dim { | ||
| size: num_buckets as i64, | ||
| ..Default::default() | ||
| }, | ||
| pb::tensor_shape_proto::Dim { | ||
| size: 3, | ||
| ..Default::default() | ||
| }, | ||
| ], | ||
| ..Default::default() | ||
| }), |
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.
True, but if you don't mind, I think I'll leave it as just a test helper for now since it's not all that bad written out, and I'm feeling a little burned from sinking a fair amount of time earlier generalizing this into a generic TensorProto construction utility (as we'd discussed, with the extension trait etc) before emerging from a pile of yak hair and deciding that there really wasn't enough boilerplate and I was ending up with net more complexity and lines of code from writing this utility and its tests than I was saving in the first place.
(Not that I necessarily object to such a utility at some point especially if we end up with more of these manipulations, to be clear; just feeling meh about it at this stage.)
| // bucket right edges; the first bucket's left edge is assumed to be -DBL_MAX and | ||
| // subsequent left edges are defined as the right edge of the preceeding bucket. | ||
| // | ||
| // Our conversion logic in data_compat.py however disobeys this and instead sets 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.
No worries, was not trying to cast aspersions :) I too have known about this quirk for a long time and not fixed it and then keep forgetting exactly what the quirk was, so I figured I'd just write it down here for posterity. Maybe 2021 will be the year we actually revisit histogram binning...
| let num_buckets = hp.bucket.len(); | ||
| let bucket_edges = || hp.bucket_limit.iter().take(num_buckets - 1).copied(); |
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.
Fair enough; updated to require the lengths match and to support the empty case (thanks for pointing that out), and added tests for each.
Re: streaminess what's the fun of having zero 𝜖-cost abstractions if we aren't gonna use 'em? 🦀 But I do actually find it easier to read personally than the offset-arithmetic version.
I went with tensor_content mostly to match what tf.make_tensor_proto will produce, FWIW.
| let num_buckets = hp.bucket.len(); | ||
| let bucket_edges = || hp.bucket_limit.iter().take(num_buckets - 1).copied(); |
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.
Lovely; thanks! Yeah, I’m perfectly happy with the streams here. I’m
part of the “use streams judiciously” crowd, and this looks like a
pretty easy sell. It’s definitely more readable than mine.
| dim: vec![ | ||
| pb::tensor_shape_proto::Dim { | ||
| size: num_buckets as i64, | ||
| ..Default::default() | ||
| }, | ||
| pb::tensor_shape_proto::Dim { | ||
| size: 3, | ||
| ..Default::default() | ||
| }, | ||
| ], | ||
| ..Default::default() | ||
| }), |
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.
Sure, sounds good. Appreciate the awareness of the yak density and happy
that you feel comfortable calling that out.
| let bucket_rights = bucket_edges().chain(iter::once(hp.max)); | ||
| // Skip the last `bucket_limit`; it gets replaced by `hp.max`. It's okay to ignore | ||
| // the edge case at 0 since `.zip()` will stop immediately in that case anyway. | ||
| let bucket_edges = &hp.bucket_limit[..usize::saturating_sub(num_buckets, 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.
For general reference, this can be num_buckets.saturating_sub(1) if
you want, but I don’t mind it like this. No action required.
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.
Ack, thanks for the observation. I guess I didn't really think about whether num_buckets is already usize. Will leave as-is just to avoid re-pushing only for this.
This implements support in Rustboard for converting legacy TF 1.x histogram summaries that use
HistogramProtointo the k-by-3 tensor format that TensorBoard expects. We adopt the same logic asdata_compat.pyfor consistency, even though the semantics are a bit dubious.Test plan: unit tests, plus I manually generated an organic TF 1.x histogram summary and confirmed that it now A) shows up in Rustboard-backed TB and B) looks the same as in slow-loading TB.
Part of #4422 tensor support sub-task.