From b53e1a1903e324c44c7bff578455bcae33bca320 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 15 Jan 2021 17:06:55 -0800 Subject: [PATCH 1/6] rust: support images and generic blob sequences Summary: We now support TF 1.x images, TF 2.x images, and any tensors that have an explicit blob sequence data class. Test Plan: Unit tests included. As an end-to-end test, images now appear in TensorBoard with `--load_fast`, in both the images and time series dashboards. wchargin-branch: rust-images-generic-blobs wchargin-source: 614b7b48a326e3303af05b5790da402ba5f72af3 --- tensorboard/data/server/data_compat.rs | 178 ++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 5 deletions(-) diff --git a/tensorboard/data/server/data_compat.rs b/tensorboard/data/server/data_compat.rs index 0c951c39ce..e5d1759102 100644 --- a/tensorboard/data/server/data_compat.rs +++ b/tensorboard/data/server/data_compat.rs @@ -23,6 +23,7 @@ use crate::proto::tensorboard as pb; use pb::summary_metadata::PluginData; pub(crate) const SCALARS_PLUGIN_NAME: &str = "scalars"; +pub(crate) const IMAGES_PLUGIN_NAME: &str = "images"; pub(crate) const GRAPHS_PLUGIN_NAME: &str = "graphs"; /// The inner contents of a single value from an event. @@ -70,11 +71,29 @@ impl EventValue { /// Consumes this event value and enriches it into a blob sequence. /// - /// For now, this succeeds only for graphs. + /// For now, this supports `GraphDef`s, summaries with `image`, or summaries with `tensor` set + /// to a rank-1 tensor of type `DT_STRING`. pub fn into_blob_sequence(self) -> Result { match self { - EventValue::Summary(_) => Err(DataLoss), EventValue::GraphDef(GraphDefValue(blob)) => Ok(BlobSequenceValue(vec![blob])), + EventValue::Summary(SummaryValue(value_box)) => match *value_box { + pb::summary::value::Value::Image(im) => { + let w = format!("{}", im.width).into_bytes(); + let h = format!("{}", im.height).into_bytes(); + let buf = im.encoded_image_string; + Ok(BlobSequenceValue(vec![w, h, buf])) + } + pb::summary::value::Value::Tensor(tp) => { + if tp.dtype == i32::from(pb::DataType::DtString) + && tp.tensor_shape.map_or(false, |shape| shape.dim.len() == 1) + { + Ok(BlobSequenceValue(tp.string_val)) + } else { + Err(DataLoss) + } + } + _ => Err(DataLoss), + }, } } } @@ -166,13 +185,16 @@ impl SummaryValue { // form. (Some(md), _) if md.data_class != i32::from(pb::DataClass::Unknown) => Box::new(md), (_, Value::SimpleValue(_)) => blank(SCALARS_PLUGIN_NAME, pb::DataClass::Scalar), + (_, Value::Image(_)) => blank(IMAGES_PLUGIN_NAME, pb::DataClass::BlobSequence), (Some(mut md), _) => { // Use given metadata, but first set data class based on plugin name, if known. - #[allow(clippy::single_match)] // will have more patterns later match md.plugin_data.as_ref().map(|pd| pd.plugin_name.as_str()) { Some(SCALARS_PLUGIN_NAME) => { md.data_class = pb::DataClass::Scalar.into(); } + Some(IMAGES_PLUGIN_NAME) => { + md.data_class = pb::DataClass::BlobSequence.into(); + } _ => {} }; Box::new(md) @@ -461,16 +483,72 @@ mod tests { } } - mod graphs { + mod blob_sequences { use super::*; #[test] - fn test_metadata() { + fn test_metadata_graph() { let md = GraphDefValue::initial_metadata(); assert_eq!(&md.plugin_data.unwrap().plugin_name, GRAPHS_PLUGIN_NAME); assert_eq!(md.data_class, i32::from(pb::DataClass::BlobSequence)); } + #[test] + fn test_metadata_tf1x_image() { + let v = SummaryValue(Box::new(Value::Image(pb::summary::Image { + height: 480, + width: 640, + colorspace: 3, + encoded_image_string: b"\x89PNGabc".to_vec(), + ..Default::default() + }))); + let result = v.initial_metadata(None); + + assert_eq!( + *result, + pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: IMAGES_PLUGIN_NAME.to_string(), + ..Default::default() + }), + data_class: pb::DataClass::BlobSequence.into(), + ..Default::default() + } + ); + } + + #[test] + fn test_metadata_tf2x_image_without_dataclass() { + let md = pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: IMAGES_PLUGIN_NAME.to_string(), + content: b"preserved!".to_vec(), + ..Default::default() + }), + ..Default::default() + }; + let v = SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[2])), + string_val: vec![b"\x89PNGabc".to_vec(), b"\x89PNGdef".to_vec()], + ..Default::default() + }))); + let result = v.initial_metadata(Some(md)); + + assert_eq!( + *result, + pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: IMAGES_PLUGIN_NAME.to_string(), + content: b"preserved!".to_vec(), + ..Default::default() + }), + data_class: pb::DataClass::BlobSequence.into(), + ..Default::default() + } + ); + } + #[test] fn test_enrich_graph_def() { let v = EventValue::GraphDef(GraphDefValue(vec![1, 2, 3, 4])); @@ -479,6 +557,96 @@ mod tests { Ok(BlobSequenceValue(vec![vec![1, 2, 3, 4]])) ); } + + #[test] + fn test_enrich_tf1x_image() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Image(pb::summary::Image { + height: 480, + width: 640, + colorspace: 3, + encoded_image_string: b"\x89PNGabc".to_vec(), + ..Default::default() + })))); + let expected = BlobSequenceValue(vec![ + b"640".to_vec(), + b"480".to_vec(), + b"\x89PNGabc".to_vec(), + ]); + assert_eq!(v.into_blob_sequence(), Ok(expected)); + } + + #[test] + fn test_enrich_valid_tensor() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[2])), + string_val: vec![b"abc".to_vec(), b"defghi".to_vec()], + ..Default::default() + })))); + let expected = BlobSequenceValue(vec![b"abc".to_vec(), b"defghi".to_vec()]); + assert_eq!(v.into_blob_sequence(), Ok(expected)); + } + + #[test] + fn test_enrich_valid_empty_tensor() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[0])), + string_val: vec![], + ..Default::default() + })))); + let expected = BlobSequenceValue(vec![]); + assert_eq!(v.into_blob_sequence(), Ok(expected)); + } + + #[test] + fn test_enrich_invalid_empty_tensor() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[0, 3])), // bad rank + string_val: vec![], + ..Default::default() + })))); + assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + } + + #[test] + fn test_enrich_scalar_tensor() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[])), + string_val: vec![b"no scalars for you".to_vec()], + ..Default::default() + })))); + assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + } + + #[test] + fn test_enrich_higher_rank_tensor() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[2, 2])), + string_val: vec![ + b"ab".to_vec(), + b"cd".to_vec(), + b"ef".to_vec(), + b"gh".to_vec(), + ], + ..Default::default() + })))); + assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + } + + #[test] + fn test_enrich_non_string_tensor() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtFloat.into(), + tensor_shape: Some(tensor_shape(&[2])), + float_val: vec![1.0, 2.0], + ..Default::default() + })))); + assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + } } mod unknown { From d2ceae484c8a687e9de2d7a324264af16250df56 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 15 Jan 2021 17:10:12 -0800 Subject: [PATCH 2/6] rust: pass summary metadata to `into_blob_sequence` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: For some data types, data-compat conversions of _values_ depend on the time series _metadata_. For instance, we need to adjust the shapes of audio tensors to remove labels, and we need to update graph sub-plugins (Keras models, etc.) to convert scalars to tensors. This patch updates the `EventValue::into_blob_sequence` interface to require a reference to the initial summary metadata so that we can add support for such data types. It’s not yet used. Test Plan: Existing unit tests suffice; for an end-to-end test the graphs and images plugins still work. wchargin-branch: rust-blob-sequence-metadata wchargin-source: 318204dbc3eaedf339f13b4d166b9b823248a6af --- tensorboard/data/server/data_compat.rs | 47 ++++++++++++++++++++------ tensorboard/data/server/run.rs | 7 ++-- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/tensorboard/data/server/data_compat.rs b/tensorboard/data/server/data_compat.rs index e5d1759102..1b9ccf0eda 100644 --- a/tensorboard/data/server/data_compat.rs +++ b/tensorboard/data/server/data_compat.rs @@ -73,7 +73,10 @@ impl EventValue { /// /// For now, this supports `GraphDef`s, summaries with `image`, or summaries with `tensor` set /// to a rank-1 tensor of type `DT_STRING`. - pub fn into_blob_sequence(self) -> Result { + pub fn into_blob_sequence( + self, + _metadata: &pb::SummaryMetadata, + ) -> Result { match self { EventValue::GraphDef(GraphDefValue(blob)) => Ok(BlobSequenceValue(vec![blob])), EventValue::Summary(SummaryValue(value_box)) => match *value_box { @@ -553,26 +556,30 @@ mod tests { fn test_enrich_graph_def() { let v = EventValue::GraphDef(GraphDefValue(vec![1, 2, 3, 4])); assert_eq!( - v.into_blob_sequence(), + v.into_blob_sequence(GraphDefValue::initial_metadata().as_ref()), Ok(BlobSequenceValue(vec![vec![1, 2, 3, 4]])) ); } #[test] fn test_enrich_tf1x_image() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Image(pb::summary::Image { + let v = SummaryValue(Box::new(Value::Image(pb::summary::Image { height: 480, width: 640, colorspace: 3, encoded_image_string: b"\x89PNGabc".to_vec(), ..Default::default() - })))); + }))); + let md = v.initial_metadata(None); let expected = BlobSequenceValue(vec![ b"640".to_vec(), b"480".to_vec(), b"\x89PNGabc".to_vec(), ]); - assert_eq!(v.into_blob_sequence(), Ok(expected)); + assert_eq!( + EventValue::Summary(v).into_blob_sequence(md.as_ref()), + Ok(expected) + ); } #[test] @@ -584,7 +591,10 @@ mod tests { ..Default::default() })))); let expected = BlobSequenceValue(vec![b"abc".to_vec(), b"defghi".to_vec()]); - assert_eq!(v.into_blob_sequence(), Ok(expected)); + assert_eq!( + v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), + Ok(expected) + ); } #[test] @@ -596,7 +606,10 @@ mod tests { ..Default::default() })))); let expected = BlobSequenceValue(vec![]); - assert_eq!(v.into_blob_sequence(), Ok(expected)); + assert_eq!( + v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), + Ok(expected) + ); } #[test] @@ -607,7 +620,10 @@ mod tests { string_val: vec![], ..Default::default() })))); - assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + assert_eq!( + v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), + Err(DataLoss) + ); } #[test] @@ -618,7 +634,10 @@ mod tests { string_val: vec![b"no scalars for you".to_vec()], ..Default::default() })))); - assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + assert_eq!( + v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), + Err(DataLoss) + ); } #[test] @@ -634,7 +653,10 @@ mod tests { ], ..Default::default() })))); - assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + assert_eq!( + v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), + Err(DataLoss) + ); } #[test] @@ -645,7 +667,10 @@ mod tests { float_val: vec![1.0, 2.0], ..Default::default() })))); - assert_eq!(v.into_blob_sequence(), Err(DataLoss)); + assert_eq!( + v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), + Err(DataLoss) + ); } } diff --git a/tensorboard/data/server/run.rs b/tensorboard/data/server/run.rs index 9491c95950..16f2dd07d8 100644 --- a/tensorboard/data/server/run.rs +++ b/tensorboard/data/server/run.rs @@ -104,7 +104,7 @@ impl StageTimeSeries { fn commit(&mut self, tag: &Tag, run: &mut commit::RunData) { use pb::DataClass; match self.data_class { - DataClass::Scalar => self.commit_to(tag, &mut run.scalars, EventValue::into_scalar), + DataClass::Scalar => self.commit_to(tag, &mut run.scalars, |ev, _| ev.into_scalar()), DataClass::Tensor => { warn!( "Tensor time series not yet supported (tag: {:?}, plugin: {:?})", @@ -125,7 +125,7 @@ impl StageTimeSeries { /// Helper for `commit`: writes staged data for this time series into storage for a statically /// known data class. - fn commit_to Result>( + fn commit_to Result>( &mut self, tag: &Tag, store: &mut commit::TagStore, @@ -134,9 +134,10 @@ impl StageTimeSeries { let commit_ts = store .entry(tag.clone()) .or_insert_with(|| commit::TimeSeries::new(self.metadata.clone())); + let metadata = self.metadata.as_ref(); self.rsv .commit_map(&mut commit_ts.basin, |StageValue { wall_time, payload }| { - (wall_time, enrich(payload)) + (wall_time, enrich(payload, metadata)) }); } } From a1c43621e7e67569cfe81e98915f8bd2f456cc46 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 15 Jan 2021 17:33:25 -0800 Subject: [PATCH 3/6] rust: support audio Summary: We now support TF 1.x and TF 2.x audio summaries. This includes TF 2.x audio summaries with labels; the labels will be stripped. This means that the audio dashboard now works under `--load_fast`. Test Plan: Unit tests should suffice. As an end-to-end test, checked the audio dashboard against TF 2.x audio summaries both with and without labels (i.e., of shape `[k, 2]` and of shape `[k]`), and against [TF 1.x data]. [TF 1.x data]: https://gist.github.com/wchargin/d9c415fb609119824919de8391d0b8c4 wchargin-branch: rust-audio wchargin-source: 16ec21cd82fb0145b816d2d2b3e01859d4a54511 --- tensorboard/data/server/data_compat.rs | 156 +++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 8 deletions(-) diff --git a/tensorboard/data/server/data_compat.rs b/tensorboard/data/server/data_compat.rs index 1b9ccf0eda..cc7c3f69d6 100644 --- a/tensorboard/data/server/data_compat.rs +++ b/tensorboard/data/server/data_compat.rs @@ -24,6 +24,7 @@ use pb::summary_metadata::PluginData; pub(crate) const SCALARS_PLUGIN_NAME: &str = "scalars"; pub(crate) const IMAGES_PLUGIN_NAME: &str = "images"; +pub(crate) const AUDIO_PLUGIN_NAME: &str = "audio"; pub(crate) const GRAPHS_PLUGIN_NAME: &str = "graphs"; /// The inner contents of a single value from an event. @@ -71,11 +72,13 @@ impl EventValue { /// Consumes this event value and enriches it into a blob sequence. /// - /// For now, this supports `GraphDef`s, summaries with `image`, or summaries with `tensor` set - /// to a rank-1 tensor of type `DT_STRING`. + /// For now, this supports `GraphDef`s, summaries with `image` or `audio`, or summaries with + /// `tensor` set to a rank-1 tensor of type `DT_STRING`. If the summary metadata indicates that + /// this is audio data, `tensor` may also be a string tensor of shape `[k, 2]`, in which case + /// the second axis is assumed to represent string labels and is dropped entirely. pub fn into_blob_sequence( self, - _metadata: &pb::SummaryMetadata, + metadata: &pb::SummaryMetadata, ) -> Result { match self { EventValue::GraphDef(GraphDefValue(blob)) => Ok(BlobSequenceValue(vec![blob])), @@ -86,11 +89,29 @@ impl EventValue { let buf = im.encoded_image_string; Ok(BlobSequenceValue(vec![w, h, buf])) } - pb::summary::value::Value::Tensor(tp) => { - if tp.dtype == i32::from(pb::DataType::DtString) - && tp.tensor_shape.map_or(false, |shape| shape.dim.len() == 1) - { + pb::summary::value::Value::Audio(au) => { + Ok(BlobSequenceValue(vec![au.encoded_audio_string])) + } + pb::summary::value::Value::Tensor(mut tp) + if tp.dtype == i32::from(pb::DataType::DtString) => + { + let shape = tp.tensor_shape.unwrap_or_default(); + if shape.dim.len() == 1 { Ok(BlobSequenceValue(tp.string_val)) + } else if shape.dim.len() == 2 + && shape.dim[1].size == 2 + && (metadata + .plugin_data + .as_ref() + .map_or(false, |pd| pd.plugin_name == AUDIO_PLUGIN_NAME)) + { + // Extract just the actual audio clips along the first axis. + let audio: Vec> = tp + .string_val + .chunks_exact_mut(2) + .map(|chunk| std::mem::take(&mut chunk[0])) + .collect(); + Ok(BlobSequenceValue(audio)) } else { Err(DataLoss) } @@ -189,13 +210,14 @@ impl SummaryValue { (Some(md), _) if md.data_class != i32::from(pb::DataClass::Unknown) => Box::new(md), (_, Value::SimpleValue(_)) => blank(SCALARS_PLUGIN_NAME, pb::DataClass::Scalar), (_, Value::Image(_)) => blank(IMAGES_PLUGIN_NAME, pb::DataClass::BlobSequence), + (_, Value::Audio(_)) => blank(AUDIO_PLUGIN_NAME, pb::DataClass::BlobSequence), (Some(mut md), _) => { // Use given metadata, but first set data class based on plugin name, if known. match md.plugin_data.as_ref().map(|pd| pd.plugin_name.as_str()) { Some(SCALARS_PLUGIN_NAME) => { md.data_class = pb::DataClass::Scalar.into(); } - Some(IMAGES_PLUGIN_NAME) => { + Some(IMAGES_PLUGIN_NAME) | Some(AUDIO_PLUGIN_NAME) => { md.data_class = pb::DataClass::BlobSequence.into(); } _ => {} @@ -552,6 +574,60 @@ mod tests { ); } + #[test] + fn test_metadata_tf1x_audio() { + let v = SummaryValue(Box::new(Value::Audio(pb::summary::Audio { + sample_rate: 44100.0, + encoded_audio_string: b"RIFFabcd".to_vec(), + ..Default::default() + }))); + let result = v.initial_metadata(None); + + assert_eq!( + *result, + pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: AUDIO_PLUGIN_NAME.to_string(), + ..Default::default() + }), + data_class: pb::DataClass::BlobSequence.into(), + ..Default::default() + } + ); + } + + #[test] + fn test_metadata_tf2x_audio_without_dataclass() { + let md = pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: AUDIO_PLUGIN_NAME.to_string(), + content: b"preserved!".to_vec(), + ..Default::default() + }), + ..Default::default() + }; + let v = SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[1, 2])), + string_val: vec![b"\x89PNGabc".to_vec(), b"label".to_vec()], + ..Default::default() + }))); + let result = v.initial_metadata(Some(md)); + + assert_eq!( + *result, + pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: AUDIO_PLUGIN_NAME.to_string(), + content: b"preserved!".to_vec(), + ..Default::default() + }), + data_class: pb::DataClass::BlobSequence.into(), + ..Default::default() + } + ); + } + #[test] fn test_enrich_graph_def() { let v = EventValue::GraphDef(GraphDefValue(vec![1, 2, 3, 4])); @@ -672,6 +748,70 @@ mod tests { Err(DataLoss) ); } + + #[test] + fn test_enrich_tf1x_audio() { + let v = SummaryValue(Box::new(Value::Audio(pb::summary::Audio { + sample_rate: 44100.0, + encoded_audio_string: b"RIFFabcd".to_vec(), + ..Default::default() + }))); + let md = v.initial_metadata(None); + let expected = BlobSequenceValue(vec![b"RIFFabcd".to_vec()]); + assert_eq!( + EventValue::Summary(v).into_blob_sequence(md.as_ref()), + Ok(expected) + ); + } + + #[test] + fn test_enrich_audio_without_labels() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[3])), + string_val: vec![ + b"RIFFwav0".to_vec(), + b"RIFFwav1".to_vec(), + b"RIFFwav2".to_vec(), + ], + ..Default::default() + })))); + let expected = BlobSequenceValue(vec![ + b"RIFFwav0".to_vec(), + b"RIFFwav1".to_vec(), + b"RIFFwav2".to_vec(), + ]); + assert_eq!( + v.into_blob_sequence(&blank(AUDIO_PLUGIN_NAME, pb::DataClass::BlobSequence)), + Ok(expected) + ); + } + + #[test] + fn test_enrich_audio_with_labels() { + let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[3, 2])), + string_val: vec![ + b"RIFFwav0".to_vec(), + b"label 0".to_vec(), + b"RIFFwav1".to_vec(), + b"label 1".to_vec(), + b"RIFFwav2".to_vec(), + b"label 2".to_vec(), + ], + ..Default::default() + })))); + let expected = BlobSequenceValue(vec![ + b"RIFFwav0".to_vec(), + b"RIFFwav1".to_vec(), + b"RIFFwav2".to_vec(), + ]); + assert_eq!( + v.into_blob_sequence(&blank(AUDIO_PLUGIN_NAME, pb::DataClass::BlobSequence)), + Ok(expected) + ); + } } mod unknown { From 61a5e01f7ac1ecc4589c20c6cd21449c1d85076b Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 15 Jan 2021 18:25:02 -0800 Subject: [PATCH 4/6] rust: support tagged run metadata graphs Summary: This patch adds support for TF 1.x `tagged_run_metadata` events. Because this is a new top-level event type, the change extends into the `run` module as well as `data_compat`, but the changed surface area is still rather small. Test Plan: The graphs demo added in #4469 includes tagged run metadata graphs. They now appear in the graphs dashboard with `--load_fast`, and include compute time information. wchargin-branch: rust-tagged-run-metadata wchargin-source: e8ed2e7af25aba1206bccf77218edaf231b1c858 --- tensorboard/data/server/data_compat.rs | 68 +++++++++++++++++++++++--- tensorboard/data/server/run.rs | 52 ++++++++++++++++++-- tensorboard/data/server/writer.rs | 49 +++++++++++++++++++ 3 files changed, 156 insertions(+), 13 deletions(-) diff --git a/tensorboard/data/server/data_compat.rs b/tensorboard/data/server/data_compat.rs index cc7c3f69d6..5f529798b8 100644 --- a/tensorboard/data/server/data_compat.rs +++ b/tensorboard/data/server/data_compat.rs @@ -26,13 +26,15 @@ pub(crate) const SCALARS_PLUGIN_NAME: &str = "scalars"; pub(crate) const IMAGES_PLUGIN_NAME: &str = "images"; pub(crate) const AUDIO_PLUGIN_NAME: &str = "audio"; pub(crate) const GRAPHS_PLUGIN_NAME: &str = "graphs"; +pub(crate) const GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME: &str = "graph_tagged_run_metadata"; /// The inner contents of a single value from an event. /// /// This does not include associated step, wall time, tag, or summary metadata information. Step /// and wall time are available on every event and just not tracked here. Tag and summary metadata -/// information are materialized on `Event`s whose `oneof what` is `summary`, but implicit for -/// graph defs. See [`GraphDefValue::initial_metadata`] and [`SummaryValue::initial_metadata`] for +/// information are materialized on `Event`s whose `oneof what` is `tagged_run_metadata` or +/// `summary`, but implicit for graph defs. See [`GraphDefValue::initial_metadata`], +/// [`TaggedRunMetadataValue::initial_metadata`], and [`SummaryValue::initial_metadata`] for /// type-specific helpers to determine summary metadata given appropriate information. /// /// This is kept as close as possible to the on-disk event representation, since every record in @@ -46,6 +48,7 @@ pub(crate) const GRAPHS_PLUGIN_NAME: &str = "graphs"; #[derive(Debug)] pub enum EventValue { GraphDef(GraphDefValue), + TaggedRunMetadata(TaggedRunMetadataValue), Summary(SummaryValue), } @@ -53,11 +56,12 @@ impl EventValue { /// Consumes this event value and enriches it into a scalar. /// /// This supports `simple_value` (TF 1.x) summaries as well as rank-0 tensors of type - /// `DT_FLOAT`. Returns `DataLoss` if the value is a `GraphDef`, is an unsupported summary, or - /// is a tensor of the wrong rank. + /// `DT_FLOAT`. Returns `DataLoss` if the value is a `GraphDef`, a tagged run metadata proto, + /// an unsupported summary, or a tensor of the wrong rank. pub fn into_scalar(self) -> Result { let value_box = match self { EventValue::GraphDef(_) => return Err(DataLoss), + EventValue::TaggedRunMetadata(_) => return Err(DataLoss), EventValue::Summary(SummaryValue(v)) => v, }; match *value_box { @@ -72,16 +76,20 @@ impl EventValue { /// Consumes this event value and enriches it into a blob sequence. /// - /// For now, this supports `GraphDef`s, summaries with `image` or `audio`, or summaries with - /// `tensor` set to a rank-1 tensor of type `DT_STRING`. If the summary metadata indicates that - /// this is audio data, `tensor` may also be a string tensor of shape `[k, 2]`, in which case - /// the second axis is assumed to represent string labels and is dropped entirely. + /// For now, this supports `GraphDef`s, tagged run metadata protos, summaries with `image` or + /// `audio`, or summaries with `tensor` set to a rank-1 tensor of type `DT_STRING`. If the + /// summary metadata indicates that this is audio data, `tensor` may also be a string tensor of + /// shape `[k, 2]`, in which case the second axis is assumed to represent string labels and is + /// dropped entirely. pub fn into_blob_sequence( self, metadata: &pb::SummaryMetadata, ) -> Result { match self { EventValue::GraphDef(GraphDefValue(blob)) => Ok(BlobSequenceValue(vec![blob])), + EventValue::TaggedRunMetadata(TaggedRunMetadataValue(run_metadata)) => { + Ok(BlobSequenceValue(vec![run_metadata])) + } EventValue::Summary(SummaryValue(value_box)) => match *value_box { pb::summary::value::Value::Image(im) => { let w = format!("{}", im.width).into_bytes(); @@ -155,6 +163,12 @@ fn tensor_proto_to_scalar(tp: &pb::TensorProto) -> Option { /// plugin metadata, but these are not materialized. pub struct GraphDefValue(pub Vec); +/// A value from an `Event` whose `tagged_run_metadata` field is set. +/// +/// This contains only the `run_metadata` from the event (not the tag). This itself represents the +/// encoding of a `RunMetadata` proto, but that is deserialized at the plugin level. +pub struct TaggedRunMetadataValue(pub Vec); + /// A value from an `Event` whose `summary` field is set. /// /// This contains a [`summary::value::Value`], which represents the underlying `oneof value` field @@ -183,6 +197,17 @@ impl GraphDefValue { } } +impl TaggedRunMetadataValue { + /// Determines the metadata for a time series whose first event is a + /// [`TaggedRunMetadata`][`EventValue::TaggedRunMetadata`]. + pub fn initial_metadata() -> Box { + blank( + GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME, + pb::DataClass::BlobSequence, + ) + } +} + impl SummaryValue { /// Determines the metadata for a time series given its first event. /// @@ -237,6 +262,14 @@ impl Debug for GraphDefValue { } } +impl Debug for TaggedRunMetadataValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("TaggedRunMetadataValue") + .field(&format_args!("<{} bytes>", self.0.len())) + .finish() + } +} + /// Creates a summary metadata value with plugin name and data class, but no other contents. fn blank(plugin_name: &str, data_class: pb::DataClass) -> Box { Box::new(pb::SummaryMetadata { @@ -518,6 +551,16 @@ mod tests { assert_eq!(md.data_class, i32::from(pb::DataClass::BlobSequence)); } + #[test] + fn test_metadata_tagged_run_metadata() { + let md = TaggedRunMetadataValue::initial_metadata(); + assert_eq!( + &md.plugin_data.unwrap().plugin_name, + GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME + ); + assert_eq!(md.data_class, i32::from(pb::DataClass::BlobSequence)); + } + #[test] fn test_metadata_tf1x_image() { let v = SummaryValue(Box::new(Value::Image(pb::summary::Image { @@ -637,6 +680,15 @@ mod tests { ); } + #[test] + fn test_enrich_tagged_run_metadata() { + let v = EventValue::TaggedRunMetadata(TaggedRunMetadataValue(vec![1, 2, 3, 4])); + assert_eq!( + v.into_blob_sequence(GraphDefValue::initial_metadata().as_ref()), + Ok(BlobSequenceValue(vec![vec![1, 2, 3, 4]])) + ); + } + #[test] fn test_enrich_tf1x_image() { let v = SummaryValue(Box::new(Value::Image(pb::summary::Image { diff --git a/tensorboard/data/server/run.rs b/tensorboard/data/server/run.rs index 16f2dd07d8..e28a179f9d 100644 --- a/tensorboard/data/server/run.rs +++ b/tensorboard/data/server/run.rs @@ -23,7 +23,7 @@ use std::path::PathBuf; use std::sync::RwLock; use crate::commit; -use crate::data_compat::{EventValue, GraphDefValue, SummaryValue}; +use crate::data_compat::{EventValue, GraphDefValue, SummaryValue, TaggedRunMetadataValue}; use crate::event_file::EventFileReader; use crate::proto::tensorboard as pb; use crate::reservoir::StageReservoir; @@ -277,6 +277,21 @@ fn read_event( }; ts.rsv.offer(step, sv); } + Some(pb::event::What::TaggedRunMetadata(trm_proto)) => { + let sv = StageValue { + wall_time, + payload: EventValue::GraphDef(GraphDefValue(trm_proto.run_metadata)), + }; + use std::collections::hash_map::Entry; + let ts = match time_series.entry(Tag(trm_proto.tag)) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(v) => { + let metadata = TaggedRunMetadataValue::initial_metadata(); + v.insert(StageTimeSeries::new(metadata)) + } + }; + ts.rsv.offer(step, sv); + } Some(pb::event::What::Summary(sum)) => { for mut summary_pb_value in sum.value { let summary_value = match summary_pb_value.value { @@ -346,6 +361,12 @@ mod test { WallTime::new(1235.0).unwrap(), b"".to_vec(), )?; + f1.write_tagged_run_metadata( + &Tag("step0000".to_string()), + Step(0), + WallTime::new(1235.0).unwrap(), + b"".to_vec(), + )?; f1.write_scalar(&tag, Step(0), WallTime::new(1235.0).unwrap(), 0.25)?; f1.write_scalar(&tag, Step(1), WallTime::new(1236.0).unwrap(), 0.50)?; f1.write_scalar(&tag, Step(2), WallTime::new(1237.0).unwrap(), 0.75)?; @@ -404,11 +425,9 @@ mod test { ] ); + assert_eq!(run_data.blob_sequences.len(), 2); + let run_graph_tag = Tag(GraphDefValue::TAG_NAME.to_string()); - assert_eq!( - run_data.blob_sequences.keys().collect::>(), - vec![&run_graph_tag] - ); let graph_ts = run_data.blob_sequences.get(&run_graph_tag).unwrap(); assert_eq!( *graph_ts.metadata, @@ -430,6 +449,29 @@ mod test { )] ); + let run_metadata_tag = Tag("step0000".to_string()); + let run_metadata_ts = run_data.blob_sequences.get(&run_metadata_tag).unwrap(); + assert_eq!( + *run_metadata_ts.metadata, + pb::SummaryMetadata { + plugin_data: Some(pb::summary_metadata::PluginData { + plugin_name: crate::data_compat::GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME + .to_string(), + ..Default::default() + }), + data_class: pb::DataClass::BlobSequence.into(), + ..Default::default() + } + ); + assert_eq!( + run_metadata_ts.valid_values().collect::>(), + vec![( + Step(0), + WallTime::new(1235.0).unwrap(), + &commit::BlobSequenceValue(vec![b"".to_vec()]) + )] + ); + Ok(()) } } diff --git a/tensorboard/data/server/writer.rs b/tensorboard/data/server/writer.rs index 3f2c5a3e89..019b659a9a 100644 --- a/tensorboard/data/server/writer.rs +++ b/tensorboard/data/server/writer.rs @@ -65,6 +65,27 @@ pub trait SummaryWriteExt: Write { }; self.write_event(&event) } + + /// Writes a TFRecord containing a TF 1.x `tagged_run_metadata` event. + fn write_tagged_run_metadata( + &mut self, + tag: &Tag, + step: Step, + wt: WallTime, + run_metadata: Vec, + ) -> std::io::Result<()> { + let event = pb::Event { + step: step.0, + wall_time: wt.into(), + what: Some(pb::event::What::TaggedRunMetadata(pb::TaggedRunMetadata { + tag: tag.0.clone(), + run_metadata, + ..Default::default() + })), + ..Default::default() + }; + self.write_event(&event) + } } impl SummaryWriteExt for W {} @@ -158,4 +179,32 @@ mod tests { }; assert_eq!(event, &expected); } + + #[test] + fn test_tagged_run_metadata_roundtrip() { + let mut cursor = Cursor::new(Vec::::new()); + cursor + .write_tagged_run_metadata( + &Tag("step0000".to_string()), + Step(777), + WallTime::new(1234.5).unwrap(), + b"my run metadata".to_vec(), + ) + .unwrap(); + cursor.set_position(0); + let events = read_all_events(cursor).unwrap(); + assert_eq!(events.len(), 1); + + let event = &events[0]; + let expected = pb::Event { + step: 777, + wall_time: 1234.5, + what: Some(pb::event::What::TaggedRunMetadata(pb::TaggedRunMetadata { + tag: "step0000".to_string(), + run_metadata: b"my run metadata".to_vec(), + })), + ..Default::default() + }; + assert_eq!(event, &expected); + } } From ede7691c8a543c1c9383b31c642b0aa2f16044b6 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 15 Jan 2021 19:02:05 -0800 Subject: [PATCH 5/6] rust: support graph sub-plugins Summary: This patch adds support for data read by the graphs dashboard but written under the following plugin names: - `graph_run_metadata` - `graph_run_metadata_graph` - `graph_keras_model` (source of truth in `plugins/graph/metadata.py`). Test Plan: Unit tests included. As an end-to-end test, all the data written by the graphs plugin demo now works with `--load_fast`. wchargin-branch: rust-graph-subplugins wchargin-source: 6364e461c0617338db342b2d35ef5cee245c6ccb --- tensorboard/data/server/data_compat.rs | 87 +++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/tensorboard/data/server/data_compat.rs b/tensorboard/data/server/data_compat.rs index 5f529798b8..83d088f068 100644 --- a/tensorboard/data/server/data_compat.rs +++ b/tensorboard/data/server/data_compat.rs @@ -27,6 +27,9 @@ pub(crate) const IMAGES_PLUGIN_NAME: &str = "images"; pub(crate) const AUDIO_PLUGIN_NAME: &str = "audio"; pub(crate) const GRAPHS_PLUGIN_NAME: &str = "graphs"; pub(crate) const GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME: &str = "graph_tagged_run_metadata"; +pub(crate) const GRAPH_RUN_METADATA_PLUGIN_NAME: &str = "graph_run_metadata"; +pub(crate) const GRAPH_RUN_METADATA_WITH_GRAPH_PLUGIN_NAME: &str = "graph_run_metadata_graph"; +pub(crate) const GRAPH_KERAS_MODEL_PLUGIN_NAME: &str = "graph_keras_model"; /// The inner contents of a single value from an event. /// @@ -76,11 +79,17 @@ impl EventValue { /// Consumes this event value and enriches it into a blob sequence. /// - /// For now, this supports `GraphDef`s, tagged run metadata protos, summaries with `image` or - /// `audio`, or summaries with `tensor` set to a rank-1 tensor of type `DT_STRING`. If the - /// summary metadata indicates that this is audio data, `tensor` may also be a string tensor of - /// shape `[k, 2]`, in which case the second axis is assumed to represent string labels and is - /// dropped entirely. + /// This supports: + /// + /// - `GraphDef`s; + /// - tagged run metadata protos; + /// - summaries with TensorFlow 1.x `image` or `audio`; + /// - summaries with `tensor` set to a rank-1 tensor of type `DT_STRING`; + /// - for audio metadata, summaries with `tensor` set to a shape-`[k, 2]` tensor of type + /// `DT_STRING`, in which case the second axis is assumed to represent string labels and is + /// dropped entirely; + /// - for graph sub-plugin metadata, summaries with `tensor` set to a rank-0 tensor of type + /// `DT_STRING`, which is converted to a shape-`[1]` tensor. pub fn into_blob_sequence( self, metadata: &pb::SummaryMetadata, @@ -108,10 +117,7 @@ impl EventValue { Ok(BlobSequenceValue(tp.string_val)) } else if shape.dim.len() == 2 && shape.dim[1].size == 2 - && (metadata - .plugin_data - .as_ref() - .map_or(false, |pd| pd.plugin_name == AUDIO_PLUGIN_NAME)) + && is_plugin(&metadata, AUDIO_PLUGIN_NAME) { // Extract just the actual audio clips along the first axis. let audio: Vec> = tp @@ -120,6 +126,14 @@ impl EventValue { .map(|chunk| std::mem::take(&mut chunk[0])) .collect(); Ok(BlobSequenceValue(audio)) + } else if shape.dim.is_empty() + && tp.string_val.len() == 1 + && (is_plugin(&metadata, GRAPH_RUN_METADATA_PLUGIN_NAME) + || is_plugin(&metadata, GRAPH_RUN_METADATA_WITH_GRAPH_PLUGIN_NAME) + || is_plugin(&metadata, GRAPH_KERAS_MODEL_PLUGIN_NAME)) + { + let data = tp.string_val.into_iter().next().unwrap(); + Ok(BlobSequenceValue(vec![data])) } else { Err(DataLoss) } @@ -157,6 +171,13 @@ fn tensor_proto_to_scalar(tp: &pb::TensorProto) -> Option { } } +/// Tests whether `md` has plugin name `plugin_name`. +fn is_plugin(md: &pb::SummaryMetadata, plugin_name: &str) -> bool { + md.plugin_data + .as_ref() + .map_or(false, |pd| pd.plugin_name == plugin_name) +} + /// A value from an `Event` whose `graph_def` field is set. /// /// This contains the raw bytes of a serialized `GraphDef` proto. It implies a fixed tag name and @@ -242,7 +263,11 @@ impl SummaryValue { Some(SCALARS_PLUGIN_NAME) => { md.data_class = pb::DataClass::Scalar.into(); } - Some(IMAGES_PLUGIN_NAME) | Some(AUDIO_PLUGIN_NAME) => { + Some(IMAGES_PLUGIN_NAME) + | Some(AUDIO_PLUGIN_NAME) + | Some(GRAPH_RUN_METADATA_PLUGIN_NAME) + | Some(GRAPH_RUN_METADATA_WITH_GRAPH_PLUGIN_NAME) + | Some(GRAPH_KERAS_MODEL_PLUGIN_NAME) => { md.data_class = pb::DataClass::BlobSequence.into(); } _ => {} @@ -671,6 +696,48 @@ mod tests { ); } + #[test] + fn test_graph_subplugins() { + for &plugin_name in &[ + GRAPH_RUN_METADATA_PLUGIN_NAME, + GRAPH_RUN_METADATA_WITH_GRAPH_PLUGIN_NAME, + GRAPH_KERAS_MODEL_PLUGIN_NAME, + ] { + let md = pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: plugin_name.to_string(), + content: b"1".to_vec(), + ..Default::default() + }), + ..Default::default() + }; + let v = SummaryValue(Box::new(Value::Tensor(pb::TensorProto { + dtype: pb::DataType::DtString.into(), + tensor_shape: Some(tensor_shape(&[])), + string_val: vec![b"some-graph-proto".to_vec()], + ..Default::default() + }))); + + // Test both metadata and enrichment here, for convenience. + let initial_metadata = v.initial_metadata(Some(md)); + assert_eq!( + *initial_metadata, + pb::SummaryMetadata { + plugin_data: Some(PluginData { + plugin_name: plugin_name.to_string(), + content: b"1".to_vec(), + ..Default::default() + }), + data_class: pb::DataClass::BlobSequence.into(), + ..Default::default() + }, + ); + let expected_enriched = BlobSequenceValue(vec![b"some-graph-proto".to_vec()]); + let actual_enriched = EventValue::Summary(v).into_blob_sequence(&initial_metadata); + assert_eq!(actual_enriched, Ok(expected_enriched)); + } + } + #[test] fn test_enrich_graph_def() { let v = EventValue::GraphDef(GraphDefValue(vec![1, 2, 3, 4])); From a4fe0c2ad1511f81f5704c22c26c95fbbf094f06 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Tue, 19 Jan 2021 13:31:05 -0800 Subject: [PATCH 6/6] [rust-graph-subplugins: update patch] wchargin-branch: rust-graph-subplugins wchargin-source: 18ab56494a673282f35985487df79a72a1ab7b41 --- tensorboard/data/server/data_compat.rs | 389 ------------------------- 1 file changed, 389 deletions(-) diff --git a/tensorboard/data/server/data_compat.rs b/tensorboard/data/server/data_compat.rs index 4276a63f74..83d088f068 100644 --- a/tensorboard/data/server/data_compat.rs +++ b/tensorboard/data/server/data_compat.rs @@ -26,15 +26,10 @@ pub(crate) const SCALARS_PLUGIN_NAME: &str = "scalars"; pub(crate) const IMAGES_PLUGIN_NAME: &str = "images"; pub(crate) const AUDIO_PLUGIN_NAME: &str = "audio"; pub(crate) const GRAPHS_PLUGIN_NAME: &str = "graphs"; -<<<<<<< HEAD pub(crate) const GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME: &str = "graph_tagged_run_metadata"; pub(crate) const GRAPH_RUN_METADATA_PLUGIN_NAME: &str = "graph_run_metadata"; pub(crate) const GRAPH_RUN_METADATA_WITH_GRAPH_PLUGIN_NAME: &str = "graph_run_metadata_graph"; pub(crate) const GRAPH_KERAS_MODEL_PLUGIN_NAME: &str = "graph_keras_model"; -||||||| 5acd97e48 -======= -pub(crate) const GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME: &str = "graph_tagged_run_metadata"; ->>>>>>> 040f6775cb54e244be209c5015ec411a6d337e40 /// The inner contents of a single value from an event. /// @@ -84,7 +79,6 @@ impl EventValue { /// Consumes this event value and enriches it into a blob sequence. /// -<<<<<<< HEAD /// This supports: /// /// - `GraphDef`s; @@ -100,23 +94,8 @@ impl EventValue { self, metadata: &pb::SummaryMetadata, ) -> Result { -||||||| 5acd97e48 - /// For now, this succeeds only for graphs. - pub fn into_blob_sequence(self) -> Result { -======= - /// For now, this supports `GraphDef`s, tagged run metadata protos, summaries with `image` or - /// `audio`, or summaries with `tensor` set to a rank-1 tensor of type `DT_STRING`. If the - /// summary metadata indicates that this is audio data, `tensor` may also be a string tensor of - /// shape `[k, 2]`, in which case the second axis is assumed to represent string labels and is - /// dropped entirely. - pub fn into_blob_sequence( - self, - metadata: &pb::SummaryMetadata, - ) -> Result { ->>>>>>> 040f6775cb54e244be209c5015ec411a6d337e40 match self { EventValue::GraphDef(GraphDefValue(blob)) => Ok(BlobSequenceValue(vec![blob])), -<<<<<<< HEAD EventValue::TaggedRunMetadata(TaggedRunMetadataValue(run_metadata)) => { Ok(BlobSequenceValue(vec![run_metadata])) } @@ -161,48 +140,6 @@ impl EventValue { } _ => Err(DataLoss), }, -||||||| 5acd97e48 -======= - EventValue::TaggedRunMetadata(TaggedRunMetadataValue(run_metadata)) => { - Ok(BlobSequenceValue(vec![run_metadata])) - } - EventValue::Summary(SummaryValue(value_box)) => match *value_box { - pb::summary::value::Value::Image(im) => { - let w = format!("{}", im.width).into_bytes(); - let h = format!("{}", im.height).into_bytes(); - let buf = im.encoded_image_string; - Ok(BlobSequenceValue(vec![w, h, buf])) - } - pb::summary::value::Value::Audio(au) => { - Ok(BlobSequenceValue(vec![au.encoded_audio_string])) - } - pb::summary::value::Value::Tensor(mut tp) - if tp.dtype == i32::from(pb::DataType::DtString) => - { - let shape = tp.tensor_shape.unwrap_or_default(); - if shape.dim.len() == 1 { - Ok(BlobSequenceValue(tp.string_val)) - } else if shape.dim.len() == 2 - && shape.dim[1].size == 2 - && (metadata - .plugin_data - .as_ref() - .map_or(false, |pd| pd.plugin_name == AUDIO_PLUGIN_NAME)) - { - // Extract just the actual audio clips along the first axis. - let audio: Vec> = tp - .string_val - .chunks_exact_mut(2) - .map(|chunk| std::mem::take(&mut chunk[0])) - .collect(); - Ok(BlobSequenceValue(audio)) - } else { - Err(DataLoss) - } - } - _ => Err(DataLoss), - }, ->>>>>>> 040f6775cb54e244be209c5015ec411a6d337e40 } } } @@ -326,7 +263,6 @@ impl SummaryValue { Some(SCALARS_PLUGIN_NAME) => { md.data_class = pb::DataClass::Scalar.into(); } -<<<<<<< HEAD Some(IMAGES_PLUGIN_NAME) | Some(AUDIO_PLUGIN_NAME) | Some(GRAPH_RUN_METADATA_PLUGIN_NAME) @@ -334,12 +270,6 @@ impl SummaryValue { | Some(GRAPH_KERAS_MODEL_PLUGIN_NAME) => { md.data_class = pb::DataClass::BlobSequence.into(); } -||||||| 5acd97e48 -======= - Some(IMAGES_PLUGIN_NAME) | Some(AUDIO_PLUGIN_NAME) => { - md.data_class = pb::DataClass::BlobSequence.into(); - } ->>>>>>> 040f6775cb54e244be209c5015ec411a6d337e40 _ => {} }; Box::new(md) @@ -647,7 +577,6 @@ mod tests { } #[test] -<<<<<<< HEAD fn test_metadata_tagged_run_metadata() { let md = TaggedRunMetadataValue::initial_metadata(); assert_eq!( @@ -810,331 +739,14 @@ mod tests { } #[test] -||||||| 5acd97e48 -======= - fn test_metadata_tagged_run_metadata() { - let md = TaggedRunMetadataValue::initial_metadata(); - assert_eq!( - &md.plugin_data.unwrap().plugin_name, - GRAPH_TAGGED_RUN_METADATA_PLUGIN_NAME - ); - assert_eq!(md.data_class, i32::from(pb::DataClass::BlobSequence)); - } - - #[test] - fn test_metadata_tf1x_image() { - let v = SummaryValue(Box::new(Value::Image(pb::summary::Image { - height: 480, - width: 640, - colorspace: 3, - encoded_image_string: b"\x89PNGabc".to_vec(), - ..Default::default() - }))); - let result = v.initial_metadata(None); - - assert_eq!( - *result, - pb::SummaryMetadata { - plugin_data: Some(PluginData { - plugin_name: IMAGES_PLUGIN_NAME.to_string(), - ..Default::default() - }), - data_class: pb::DataClass::BlobSequence.into(), - ..Default::default() - } - ); - } - - #[test] - fn test_metadata_tf2x_image_without_dataclass() { - let md = pb::SummaryMetadata { - plugin_data: Some(PluginData { - plugin_name: IMAGES_PLUGIN_NAME.to_string(), - content: b"preserved!".to_vec(), - ..Default::default() - }), - ..Default::default() - }; - let v = SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[2])), - string_val: vec![b"\x89PNGabc".to_vec(), b"\x89PNGdef".to_vec()], - ..Default::default() - }))); - let result = v.initial_metadata(Some(md)); - - assert_eq!( - *result, - pb::SummaryMetadata { - plugin_data: Some(PluginData { - plugin_name: IMAGES_PLUGIN_NAME.to_string(), - content: b"preserved!".to_vec(), - ..Default::default() - }), - data_class: pb::DataClass::BlobSequence.into(), - ..Default::default() - } - ); - } - - #[test] - fn test_metadata_tf1x_audio() { - let v = SummaryValue(Box::new(Value::Audio(pb::summary::Audio { - sample_rate: 44100.0, - encoded_audio_string: b"RIFFabcd".to_vec(), - ..Default::default() - }))); - let result = v.initial_metadata(None); - - assert_eq!( - *result, - pb::SummaryMetadata { - plugin_data: Some(PluginData { - plugin_name: AUDIO_PLUGIN_NAME.to_string(), - ..Default::default() - }), - data_class: pb::DataClass::BlobSequence.into(), - ..Default::default() - } - ); - } - - #[test] - fn test_metadata_tf2x_audio_without_dataclass() { - let md = pb::SummaryMetadata { - plugin_data: Some(PluginData { - plugin_name: AUDIO_PLUGIN_NAME.to_string(), - content: b"preserved!".to_vec(), - ..Default::default() - }), - ..Default::default() - }; - let v = SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[1, 2])), - string_val: vec![b"\x89PNGabc".to_vec(), b"label".to_vec()], - ..Default::default() - }))); - let result = v.initial_metadata(Some(md)); - - assert_eq!( - *result, - pb::SummaryMetadata { - plugin_data: Some(PluginData { - plugin_name: AUDIO_PLUGIN_NAME.to_string(), - content: b"preserved!".to_vec(), - ..Default::default() - }), - data_class: pb::DataClass::BlobSequence.into(), - ..Default::default() - } - ); - } - - #[test] ->>>>>>> 040f6775cb54e244be209c5015ec411a6d337e40 fn test_enrich_graph_def() { let v = EventValue::GraphDef(GraphDefValue(vec![1, 2, 3, 4])); assert_eq!( -<<<<<<< HEAD v.into_blob_sequence(GraphDefValue::initial_metadata().as_ref()), Ok(BlobSequenceValue(vec![vec![1, 2, 3, 4]])) ); } - #[test] - fn test_enrich_tagged_run_metadata() { - let v = EventValue::TaggedRunMetadata(TaggedRunMetadataValue(vec![1, 2, 3, 4])); - assert_eq!( - v.into_blob_sequence(GraphDefValue::initial_metadata().as_ref()), -||||||| 5acd97e48 - v.into_blob_sequence(), -======= - v.into_blob_sequence(GraphDefValue::initial_metadata().as_ref()), ->>>>>>> 040f6775cb54e244be209c5015ec411a6d337e40 - Ok(BlobSequenceValue(vec![vec![1, 2, 3, 4]])) - ); - } -<<<<<<< HEAD - - #[test] - fn test_enrich_tf1x_image() { - let v = SummaryValue(Box::new(Value::Image(pb::summary::Image { - height: 480, - width: 640, - colorspace: 3, - encoded_image_string: b"\x89PNGabc".to_vec(), - ..Default::default() - }))); - let md = v.initial_metadata(None); - let expected = BlobSequenceValue(vec![ - b"640".to_vec(), - b"480".to_vec(), - b"\x89PNGabc".to_vec(), - ]); - assert_eq!( - EventValue::Summary(v).into_blob_sequence(md.as_ref()), - Ok(expected) - ); - } - - #[test] - fn test_enrich_valid_tensor() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[2])), - string_val: vec![b"abc".to_vec(), b"defghi".to_vec()], - ..Default::default() - })))); - let expected = BlobSequenceValue(vec![b"abc".to_vec(), b"defghi".to_vec()]); - assert_eq!( - v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), - Ok(expected) - ); - } - - #[test] - fn test_enrich_valid_empty_tensor() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[0])), - string_val: vec![], - ..Default::default() - })))); - let expected = BlobSequenceValue(vec![]); - assert_eq!( - v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), - Ok(expected) - ); - } - - #[test] - fn test_enrich_invalid_empty_tensor() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[0, 3])), // bad rank - string_val: vec![], - ..Default::default() - })))); - assert_eq!( - v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), - Err(DataLoss) - ); - } - - #[test] - fn test_enrich_scalar_tensor() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[])), - string_val: vec![b"no scalars for you".to_vec()], - ..Default::default() - })))); - assert_eq!( - v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), - Err(DataLoss) - ); - } - - #[test] - fn test_enrich_higher_rank_tensor() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[2, 2])), - string_val: vec![ - b"ab".to_vec(), - b"cd".to_vec(), - b"ef".to_vec(), - b"gh".to_vec(), - ], - ..Default::default() - })))); - assert_eq!( - v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), - Err(DataLoss) - ); - } - - #[test] - fn test_enrich_non_string_tensor() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtFloat.into(), - tensor_shape: Some(tensor_shape(&[2])), - float_val: vec![1.0, 2.0], - ..Default::default() - })))); - assert_eq!( - v.into_blob_sequence(&blank("myblobs", pb::DataClass::BlobSequence)), - Err(DataLoss) - ); - } - - #[test] - fn test_enrich_tf1x_audio() { - let v = SummaryValue(Box::new(Value::Audio(pb::summary::Audio { - sample_rate: 44100.0, - encoded_audio_string: b"RIFFabcd".to_vec(), - ..Default::default() - }))); - let md = v.initial_metadata(None); - let expected = BlobSequenceValue(vec![b"RIFFabcd".to_vec()]); - assert_eq!( - EventValue::Summary(v).into_blob_sequence(md.as_ref()), - Ok(expected) - ); - } - - #[test] - fn test_enrich_audio_without_labels() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[3])), - string_val: vec![ - b"RIFFwav0".to_vec(), - b"RIFFwav1".to_vec(), - b"RIFFwav2".to_vec(), - ], - ..Default::default() - })))); - let expected = BlobSequenceValue(vec![ - b"RIFFwav0".to_vec(), - b"RIFFwav1".to_vec(), - b"RIFFwav2".to_vec(), - ]); - assert_eq!( - v.into_blob_sequence(&blank(AUDIO_PLUGIN_NAME, pb::DataClass::BlobSequence)), - Ok(expected) - ); - } - - #[test] - fn test_enrich_audio_with_labels() { - let v = EventValue::Summary(SummaryValue(Box::new(Value::Tensor(pb::TensorProto { - dtype: pb::DataType::DtString.into(), - tensor_shape: Some(tensor_shape(&[3, 2])), - string_val: vec![ - b"RIFFwav0".to_vec(), - b"label 0".to_vec(), - b"RIFFwav1".to_vec(), - b"label 1".to_vec(), - b"RIFFwav2".to_vec(), - b"label 2".to_vec(), - ], - ..Default::default() - })))); - let expected = BlobSequenceValue(vec![ - b"RIFFwav0".to_vec(), - b"RIFFwav1".to_vec(), - b"RIFFwav2".to_vec(), - ]); - assert_eq!( - v.into_blob_sequence(&blank(AUDIO_PLUGIN_NAME, pb::DataClass::BlobSequence)), - Ok(expected) - ); - } -||||||| 5acd97e48 -======= - #[test] fn test_enrich_tagged_run_metadata() { let v = EventValue::TaggedRunMetadata(TaggedRunMetadataValue(vec![1, 2, 3, 4])); @@ -1319,7 +931,6 @@ mod tests { Ok(expected) ); } ->>>>>>> 040f6775cb54e244be209c5015ec411a6d337e40 } mod unknown {