Skip to content

Conversation

@nfelt
Copy link
Contributor

@nfelt nfelt commented Mar 5, 2021

This implements support in Rustboard for converting legacy TF 1.x histogram summaries that use HistogramProto into the k-by-3 tensor format that TensorBoard expects. We adopt the same logic as data_compat.py for 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.

@nfelt nfelt added the core:rustboard //tensorboard/data/server/... label Mar 5, 2021
@google-cla google-cla bot added the cla: yes label Mar 5, 2021
@nfelt nfelt marked this pull request as ready for review March 5, 2021 23:14
@nfelt nfelt requested a review from wchargin March 5, 2021 23:14
Copy link
Contributor

@wchargin wchargin left a 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.

Comment on lines +137 to +148
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()
}),
Copy link
Contributor

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.

Copy link
Contributor Author

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.)

Copy link
Contributor

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
Copy link
Contributor

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”.

Copy link
Contributor Author

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...

Comment on lines 121 to 122
let num_buckets = hp.bucket.len();
let bucket_edges = || hp.bucket_limit.iter().take(num_buckets - 1).copied();
Copy link
Contributor

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>.

Copy link
Contributor Author

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.

Copy link
Contributor

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,
Copy link
Contributor

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,).

Copy link
Contributor Author

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()))));
Copy link
Contributor

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).

Copy link
Contributor Author

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.

Base automatically changed from nfelt-rust-tensor-content-bytes-macro to master March 6, 2021 01:41
@wchargin wchargin mentioned this pull request Mar 6, 2021
34 tasks
Copy link
Contributor Author

@nfelt nfelt left a 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()))));
Copy link
Contributor Author

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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +137 to +148
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()
}),
Copy link
Contributor Author

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
Copy link
Contributor Author

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...

Comment on lines 121 to 122
let num_buckets = hp.bucket.len();
let bucket_edges = || hp.bucket_limit.iter().take(num_buckets - 1).copied();
Copy link
Contributor Author

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.

@nfelt nfelt requested a review from wchargin March 6, 2021 03:29
Comment on lines 121 to 122
let num_buckets = hp.bucket.len();
let bucket_edges = || hp.bucket_limit.iter().take(num_buckets - 1).copied();
Copy link
Contributor

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.

Comment on lines +137 to +148
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()
}),
Copy link
Contributor

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)];
Copy link
Contributor

@wchargin wchargin Mar 6, 2021

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.

Copy link
Contributor Author

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.

@nfelt nfelt merged commit 02664a7 into master Mar 9, 2021
@nfelt nfelt deleted the nfelt-rust-tensors-legacy-histograms branch March 9, 2021 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes core:rustboard //tensorboard/data/server/...

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants