diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD index 15cb1ad18bd..0a21c073660 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD @@ -78,6 +78,7 @@ tf_ng_web_test_suite( "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects:debugger_effects_test_lib", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:debugger_store_test_lib", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts:alerts_container_test_lib", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value:debug_tensor_value_component_test_lib", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions:graph_executions_container_test_lib", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/source_code:source_code_container_test_lib", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/source_code:source_code_test_lib", diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container_test.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container_test.ts index 51949f4e509..5ab71389d34 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container_test.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container_test.ts @@ -34,8 +34,8 @@ import {DebuggerContainer} from './debugger_container'; import { DataLoadState, State, - TensorDebugMode, AlertType, + TensorDebugMode, } from './store/debugger_types'; import { createAlertsState, @@ -435,58 +435,6 @@ describe('Debugger Container', () => { } describe('Execution Data module', () => { - it('CURT_HEALTH TensorDebugMode, One Output', () => { - const fixture = TestBed.createComponent(ExecutionDataContainer); - fixture.detectChanges(); - - store.setState( - createState( - createDebuggerState({ - executions: { - numExecutionsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 111, - }, - executionDigestsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 222, - pageLoadedSizes: {0: 100}, - numExecutions: 1000, - }, - executionDigests: {}, - pageSize: 100, - displayCount: 50, - scrollBeginIndex: 90, - focusIndex: 98, - executionData: { - 98: createTestExecutionData({ - op_type: 'Inverse', - tensor_debug_mode: TensorDebugMode.NO_TENSOR, - debug_tensor_values: null, - }), - }, - }, - }) - ) - ); - fixture.detectChanges(); - - const opTypeElement = fixture.debugElement.query(By.css('.op-type')); - expect(opTypeElement.nativeElement.innerText).toEqual('Inverse'); - const inputTensorsElement = fixture.debugElement.query( - By.css('.input-tensors') - ); - expect(inputTensorsElement.nativeElement.innerText).toEqual('1'); - const outputTensorsElement = fixture.debugElement.query( - By.css('.output-tensors') - ); - expect(outputTensorsElement.nativeElement.innerText).toEqual('1'); - const debugTensorValuesContainers = fixture.debugElement.queryAll( - By.css('.debug-tensor-values-container') - ); - expect(debugTensorValuesContainers.length).toEqual(0); - }); - it('CURT_HEALTH TensorDebugMode, One Output', () => { const fixture = TestBed.createComponent(ExecutionDataContainer); fixture.detectChanges(); @@ -514,7 +462,7 @@ describe('Debugger Container', () => { 98: createTestExecutionData({ op_type: 'Inverse', tensor_debug_mode: TensorDebugMode.CURT_HEALTH, - debug_tensor_values: [[-1, 1]], + debug_tensor_values: [[0, 1]], }), }, }, @@ -534,98 +482,19 @@ describe('Debugger Container', () => { ); expect(outputTensorsElement.nativeElement.innerText).toEqual('1'); const outputSlotElements = fixture.debugElement.queryAll( - By.css('.output-slot') + By.css('.output-slot-number') ); - expect(outputSlotElements.length).toEqual(1); - expect(outputSlotElements[0].nativeElement.innerText).toEqual('0'); - const anyInfNanElements = fixture.debugElement.queryAll( - By.css('.curt-health-contains-inf-nan') + expect(outputSlotElements.length).toBe(1); + expect(outputSlotElements[0].nativeElement.innerText).toBe( + 'Output slot 0:' ); - expect(anyInfNanElements.length).toEqual(1); - expect(anyInfNanElements[0].nativeElement.innerText).toEqual('Yes'); - }); - - it('CONCISE_HEALTH TensorDebugMode, Two Outputs', () => { - const fixture = TestBed.createComponent(ExecutionDataContainer); - fixture.detectChanges(); - - store.setState( - createState( - createDebuggerState({ - executions: { - numExecutionsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 111, - }, - executionDigestsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 222, - pageLoadedSizes: {0: 100}, - numExecutions: 1000, - }, - executionDigests: {}, - pageSize: 100, - displayCount: 50, - scrollBeginIndex: 90, - focusIndex: 98, - executionData: { - 98: createTestExecutionData({ - op_type: 'FooOp', - output_tensor_device_ids: ['d0', 'd0'], - output_tensor_ids: [123, 124], - tensor_debug_mode: TensorDebugMode.CONCISE_HEALTH, - debug_tensor_values: [[-1, 100, 0, 0, 0], [-1, 10, 1, 2, 3]], - }), - }, - }, - }) - ) + const debugTensorValueElements = fixture.debugElement.queryAll( + By.css('debug-tensor-value') ); - fixture.detectChanges(); - - const opTypeElement = fixture.debugElement.query(By.css('.op-type')); - expect(opTypeElement.nativeElement.innerText).toEqual('FooOp'); - const inputTensorsElement = fixture.debugElement.query( - By.css('.input-tensors') - ); - expect(inputTensorsElement.nativeElement.innerText).toEqual('1'); - const outputTensorsElement = fixture.debugElement.query( - By.css('.output-tensors') - ); - expect(outputTensorsElement.nativeElement.innerText).toEqual('2'); - const outputSlotElements = fixture.debugElement.queryAll( - By.css('.output-slot') - ); - expect(outputSlotElements.length).toEqual(2); - expect(outputSlotElements[0].nativeElement.innerText).toEqual('0'); - expect(outputSlotElements[1].nativeElement.innerText).toEqual('1'); - const sizeElements = fixture.debugElement.queryAll( - By.css('.concise-health-size') - ); - expect(sizeElements.length).toEqual(2); - expect(sizeElements[0].nativeElement.innerText).toEqual('100'); - expect(sizeElements[1].nativeElement.innerText).toEqual('10'); - const negInfsElements = fixture.debugElement.queryAll( - By.css('.concise-health-neg-infs') - ); - expect(negInfsElements.length).toEqual(2); - expect(negInfsElements[0].nativeElement.innerText).toEqual('0'); - expect(negInfsElements[1].nativeElement.innerText).toEqual('1'); - const posInfsElements = fixture.debugElement.queryAll( - By.css('.concise-health-pos-infs') - ); - expect(posInfsElements.length).toEqual(2); - expect(posInfsElements[0].nativeElement.innerText).toEqual('0'); - expect(posInfsElements[1].nativeElement.innerText).toEqual('2'); - const nanElements = fixture.debugElement.queryAll( - By.css('.concise-health-nans') - ); - expect(nanElements.length).toEqual(2); - expect(nanElements[0].nativeElement.innerText).toEqual('0'); - expect(nanElements[1].nativeElement.innerText).toEqual('3'); + expect(debugTensorValueElements.length).toBe(1); }); - it('CONCISE_HEALTH TensorDebugMode, Two Outputs, Only One With Data', () => { + it('CURT_HEALTH TensorDebugMode, Two Outputs', () => { const fixture = TestBed.createComponent(ExecutionDataContainer); fixture.detectChanges(); @@ -650,13 +519,10 @@ describe('Debugger Container', () => { focusIndex: 98, executionData: { 98: createTestExecutionData({ - op_type: 'BarOp', - output_tensor_device_ids: ['d0', 'd0'], - output_tensor_ids: [123, 124], - tensor_debug_mode: TensorDebugMode.CONCISE_HEALTH, - // First output slot has no data (e.g., due to non-floating - // dtype). - debug_tensor_values: [null, [-1, 10, 1, 2, 3]], + op_type: 'Inverse', + output_tensor_ids: [10, 11], + tensor_debug_mode: TensorDebugMode.CURT_HEALTH, + debug_tensor_values: [[0, 0], [0, 1]], }), }, }, @@ -666,7 +532,7 @@ describe('Debugger Container', () => { fixture.detectChanges(); const opTypeElement = fixture.debugElement.query(By.css('.op-type')); - expect(opTypeElement.nativeElement.innerText).toEqual('BarOp'); + expect(opTypeElement.nativeElement.innerText).toEqual('Inverse'); const inputTensorsElement = fixture.debugElement.query( By.css('.input-tensors') ); @@ -676,216 +542,19 @@ describe('Debugger Container', () => { ); expect(outputTensorsElement.nativeElement.innerText).toEqual('2'); const outputSlotElements = fixture.debugElement.queryAll( - By.css('.output-slot') - ); - expect(outputSlotElements.length).toEqual(2); - expect(outputSlotElements[0].nativeElement.innerText).toEqual('0'); - expect(outputSlotElements[1].nativeElement.innerText).toEqual('1'); - const sizeElements = fixture.debugElement.queryAll( - By.css('.concise-health-size') - ); - expect(sizeElements.length).toEqual(1); - expect(sizeElements[0].nativeElement.innerText).toEqual('10'); - const negInfsElements = fixture.debugElement.queryAll( - By.css('.concise-health-neg-infs') - ); - expect(negInfsElements.length).toEqual(1); - expect(negInfsElements[0].nativeElement.innerText).toEqual('1'); - const posInfsElements = fixture.debugElement.queryAll( - By.css('.concise-health-pos-infs') - ); - expect(posInfsElements.length).toEqual(1); - expect(posInfsElements[0].nativeElement.innerText).toEqual('2'); - const nanElements = fixture.debugElement.queryAll( - By.css('.concise-health-nans') - ); - expect(nanElements.length).toEqual(1); - expect(nanElements[0].nativeElement.innerText).toEqual('3'); - }); - - it('FULL_HEALTH TensorDebugMode, One outputs', () => { - const fixture = TestBed.createComponent(ExecutionDataContainer); - fixture.detectChanges(); - - store.setState( - createState( - createDebuggerState({ - executions: { - numExecutionsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 111, - }, - executionDigestsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 222, - pageLoadedSizes: {0: 100}, - numExecutions: 1000, - }, - executionDigests: {}, - pageSize: 100, - displayCount: 50, - scrollBeginIndex: 90, - focusIndex: 98, - executionData: { - 98: createTestExecutionData({ - op_type: 'FooOp', - output_tensor_device_ids: ['d0'], - output_tensor_ids: [123], - tensor_debug_mode: TensorDebugMode.FULL_HEALTH, - debug_tensor_values: [ - // [tensor_id, device_id, dtype, rank, element_count, - // neg_inf_count, pos_inf_count, nan_count, - // neg_finite_count, zero_count, pos_finite_count]. - [-1, -1, 1, 2, 6, 0, 0, 1, 2, 3, 0], - ], - }), - }, - }, - }) - ) - ); - fixture.detectChanges(); - - const opTypeElement = fixture.debugElement.query(By.css('.op-type')); - expect(opTypeElement.nativeElement.innerText).toEqual('FooOp'); - const inputTensorsElement = fixture.debugElement.query( - By.css('.input-tensors') - ); - expect(inputTensorsElement.nativeElement.innerText).toEqual('1'); - const outputTensorsElement = fixture.debugElement.query( - By.css('.output-tensors') - ); - expect(outputTensorsElement.nativeElement.innerText).toEqual('1'); - const outputSlotElements = fixture.debugElement.queryAll( - By.css('.output-slot') - ); - expect(outputSlotElements.length).toEqual(1); - expect(outputSlotElements[0].nativeElement.innerText).toEqual('0'); - const dtypeElements = fixture.debugElement.queryAll( - By.css('.full-health-dtype') - ); - expect(dtypeElements.length).toEqual(1); - expect(dtypeElements[0].nativeElement.innerText).toEqual('float32'); - const rankElements = fixture.debugElement.queryAll( - By.css('.full-health-rank') - ); - expect(rankElements.length).toEqual(1); - expect(rankElements[0].nativeElement.innerText).toEqual('2'); - const sizeElements = fixture.debugElement.queryAll( - By.css('.full-health-size') + By.css('.output-slot-number') ); - expect(sizeElements.length).toEqual(1); - expect(sizeElements[0].nativeElement.innerText).toEqual('6'); - const negInfElements = fixture.debugElement.queryAll( - By.css('.full-health-neg-inf') - ); - expect(negInfElements.length).toEqual(1); - expect(negInfElements[0].nativeElement.innerText).toEqual('0'); - const posInfElements = fixture.debugElement.queryAll( - By.css('.full-health-pos-inf') - ); - expect(posInfElements.length).toEqual(1); - expect(posInfElements[0].nativeElement.innerText).toEqual('0'); - const nanElements = fixture.debugElement.queryAll( - By.css('.full-health-nan') - ); - expect(nanElements.length).toEqual(1); - expect(nanElements[0].nativeElement.innerText).toEqual('1'); - const negFiniteElements = fixture.debugElement.queryAll( - By.css('.full-health-neg-finite') - ); - expect(negFiniteElements.length).toEqual(1); - expect(negFiniteElements[0].nativeElement.innerText).toEqual('2'); - const zeroElements = fixture.debugElement.queryAll( - By.css('.full-health-zero') - ); - expect(zeroElements.length).toEqual(1); - expect(zeroElements[0].nativeElement.innerText).toEqual('3'); - const posFiniteElements = fixture.debugElement.queryAll( - By.css('.full-health-pos-finite') - ); - expect(posFiniteElements.length).toEqual(1); - expect(posFiniteElements[0].nativeElement.innerText).toEqual('0'); - }); - - it('SHAPE TensorDebugMode, Two Outputs', () => { - const fixture = TestBed.createComponent(ExecutionDataContainer); - fixture.detectChanges(); - - store.setState( - createState( - createDebuggerState({ - executions: { - numExecutionsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 111, - }, - executionDigestsLoaded: { - state: DataLoadState.LOADED, - lastLoadedTimeInMs: 222, - pageLoadedSizes: {0: 100}, - numExecutions: 1000, - }, - executionDigests: {}, - pageSize: 100, - displayCount: 50, - scrollBeginIndex: 90, - focusIndex: 98, - executionData: { - 98: createTestExecutionData({ - op_type: 'FooOp', - output_tensor_device_ids: ['d0', 'd0'], - output_tensor_ids: [123, 124], - tensor_debug_mode: TensorDebugMode.SHAPE, - debug_tensor_values: [ - [-1, 1, 0, 1, 0, 0, 0, 0, 0, 0], - // Use -1337 dtype enum value to test the unknown-dtype logic. - [-1, -1337, 2, 20, 4, 5, 0, 0, 0, 0], - ], - }), - }, - }, - }) - ) - ); - fixture.detectChanges(); - - const opTypeElement = fixture.debugElement.query(By.css('.op-type')); - expect(opTypeElement.nativeElement.innerText).toEqual('FooOp'); - const inputTensorsElement = fixture.debugElement.query( - By.css('.input-tensors') - ); - expect(inputTensorsElement.nativeElement.innerText).toEqual('1'); - const outputTensorsElement = fixture.debugElement.query( - By.css('.output-tensors') - ); - expect(outputTensorsElement.nativeElement.innerText).toEqual('2'); - const outputSlotElements = fixture.debugElement.queryAll( - By.css('.output-slot') + expect(outputSlotElements.length).toBe(2); + expect(outputSlotElements[0].nativeElement.innerText).toBe( + 'Output slot 0:' ); - expect(outputSlotElements.length).toEqual(2); - expect(outputSlotElements[0].nativeElement.innerText).toEqual('0'); - expect(outputSlotElements[1].nativeElement.innerText).toEqual('1'); - const dtypeElements = fixture.debugElement.queryAll( - By.css('.shape-dtype') + expect(outputSlotElements[1].nativeElement.innerText).toBe( + 'Output slot 1:' ); - expect(dtypeElements.length).toEqual(2); - expect(dtypeElements[0].nativeElement.innerText).toEqual('float32'); - expect(dtypeElements[1].nativeElement.innerText).toEqual('Unknown dtype'); - const rankElements = fixture.debugElement.queryAll(By.css('.shape-rank')); - expect(rankElements.length).toEqual(2); - expect(rankElements[0].nativeElement.innerText).toEqual('0'); - expect(rankElements[1].nativeElement.innerText).toEqual('2'); - const sizeElements = fixture.debugElement.queryAll(By.css('.shape-size')); - expect(sizeElements.length).toEqual(2); - expect(sizeElements[0].nativeElement.innerText).toEqual('1'); - expect(sizeElements[1].nativeElement.innerText).toEqual('20'); - const shapeElements = fixture.debugElement.queryAll( - By.css('.shape-shape') + const debugTensorValueElements = fixture.debugElement.queryAll( + By.css('debug-tensor-value') ); - expect(shapeElements.length).toEqual(2); - expect(shapeElements[0].nativeElement.innerText).toEqual('()'); - expect(shapeElements[1].nativeElement.innerText).toEqual('(4,5)'); + expect(debugTensorValueElements.length).toBe(2); }); }); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/BUILD index 123282e662e..864934b7d40 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/BUILD +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/BUILD @@ -31,15 +31,28 @@ ng_module( ], ) +ng_module( + name = "debug_tensor_value", + srcs = [ + "debug_tensor_value.ts", + ], + deps = [ + ":types", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin:tf_types", + ], +) + tf_ts_library( name = "debugger_store_test_lib", testonly = True, srcs = [ + "debug_tensor_value_test.ts", "debugger_reducers_test.ts", "debugger_selectors_test.ts", ], tsconfig = "//:tsconfig-test", deps = [ + ":debug_tensor_value", ":store", ":types", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debug_tensor_value.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debug_tensor_value.ts new file mode 100644 index 00000000000..b21bda64290 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debug_tensor_value.ts @@ -0,0 +1,213 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +import {DTYPE_ENUM_TO_NAME} from '../tf_dtypes'; +import {DebugTensorValue, TensorDebugMode} from './debugger_types'; + +export interface RawDebugTensorValue { + tensorDebugMode: TensorDebugMode; + array: null | number[]; +} + +export interface RawDebugTensorValueNoTensor extends RawDebugTensorValue { + tensorDebugMode: TensorDebugMode.NO_TENSOR; + array: null; +} + +export interface RawDebugTensorValueCurtHealth extends RawDebugTensorValue { + tensorDebugMode: TensorDebugMode.CURT_HEALTH; + array: [ + number, // Tensor ID. + number // 0-1 indicator for the presence of inf/nan. + ]; +} + +export interface RawDebugTensorValueConciseHealth extends RawDebugTensorValue { + tensorDebugMode: TensorDebugMode.CURT_HEALTH; + array: [ + number, // Tensor ID. + number, // Element count (size). + number, // -inf count. + number, // +inf count. + number // nan count. + ]; +} + +export interface RawDebugTensorValueShape extends RawDebugTensorValue { + tensorDebugMode: TensorDebugMode.SHAPE; + array: [ + number, // Tensor ID. + number, // DType enum value. + number, // Rank. + number, // Size. + number, // Shape truncated at head to a maximum length of 6. + number, + number, + number, + number, + number + ]; +} + +export interface RawDebugTensorValueFullHealth extends RawDebugTensorValue { + tensorDebugMode: TensorDebugMode.FULL_HEALTH; + array: [ + number, // Tensor ID. + number, // Device ID. + number, // DType enum value. + number, // Rank. + number, // Size. + number, // -inf count. + number, // +inf count. + number, // nan count. + number, // -finite count. + number, // zero count. + number // +finite count. + ]; +} + +export interface RawDebugTensorValueFullTensor extends RawDebugTensorValue { + tensorDebugMode: TensorDebugMode.FULL_HEALTH; + array: null; +} + +/** + * Parse a number array that represents debugging summary of an instrumented + * tensor value. + * + * @param tensorDebugMode and the array of number that represents various + * aspect of the instrumented tensor. The semantics of the numbers are + * determined by `tensorDebugMode`. + * @returns A DebugTensorValue object with the same information as + * carried by `array`, but represented in a more explicit fashion. + * For numbers that represent breakdown of numeric values by type + * (e.g., counts of -inf, +inf and nan), the corresponding fields + * in the returned object will be defined only if the count is non-zero. + */ +export function parseDebugTensorValue( + raw: RawDebugTensorValue +): DebugTensorValue { + const {tensorDebugMode, array} = raw; + switch (tensorDebugMode) { + case TensorDebugMode.NO_TENSOR: { + if (array !== null) { + throw new Error( + 'Unexpectedly received non-null debug-tensor-value array ' + + 'under NO_TENSOR mode' + ); + } + return {}; + } + case TensorDebugMode.CURT_HEALTH: { + if (array === null || array.length !== 2) { + throw new Error( + `Under CURT_HEALTH mode, expected debug-tensor-value array ` + + `to have length 2, but got ${JSON.stringify(array)}` + ); + } + return { + hasInfOrNaN: Boolean(array[1]), + }; + } + case TensorDebugMode.CONCISE_HEALTH: { + if (array === null || array.length !== 5) { + throw new Error( + `Under CONCISE_HEALTH mode, expected debug-tensor-value array ` + + `to have length 5, but got ${JSON.stringify(array)}` + ); + } + const value: DebugTensorValue = { + size: array[1], + }; + if (array[2] > 0) { + value.numNegativeInfs = array[2]; + } + if (array[3] > 0) { + value.numPositiveInfs = array[3]; + } + if (array[4] > 0) { + value.numNaNs = array[4]; + } + return value; + } + case TensorDebugMode.SHAPE: { + if (array === null || array.length !== 10) { + throw new Error( + `Under SHAPE mode, expected debug-tensor-value array ` + + `to have length 10, but got ${JSON.stringify(array)}` + ); + } + const rank = array[2]; + let shape: number[] = array.slice(4, Math.min(4 + rank, array.length)); + if (shape.length < rank) { + // The SHAPE mode truncates the shape at head. + shape = new Array(rank - shape.length).concat(shape); + } + return { + dtype: DTYPE_ENUM_TO_NAME[array[1]], + rank, + size: array[3], + shape, + }; + } + case TensorDebugMode.FULL_HEALTH: { + if (array === null || array.length !== 11) { + throw new Error( + `Under FULL_HEALTH mode, expected debug-tensor-value array ` + + `to have length 11, but got ${JSON.stringify(array)}` + ); + } + const rank = array[3]; + const value: DebugTensorValue = { + dtype: DTYPE_ENUM_TO_NAME[array[2]], + rank, + size: array[4], + }; + if (array[5] > 0) { + value.numNegativeInfs = array[5]; + } + if (array[6] > 0) { + value.numPositiveInfs = array[6]; + } + if (array[7] > 0) { + value.numNaNs = array[7]; + } + if (array[8] > 0) { + value.numNegativeFinites = array[8]; + } + if (array[9] > 0) { + value.numZeros = array[9]; + } + if (array[10] > 0) { + value.numPositiveFinites = array[10]; + } + return value; + } + case TensorDebugMode.FULL_TENSOR: { + // Under FULL_TENSOR mode, the full tensor value is supplied via + // separate means. No summary values are provided for the tensor value. + if (array !== null) { + throw new Error( + 'Unexpectedly received non-null debug-tensor-value array ' + + 'under FULL_TENSOR mode' + ); + } + return {}; + } + default: { + throw new Error(`Unrecognized tensorDebugMode: ${tensorDebugMode}`); + } + } +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debug_tensor_value_test.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debug_tensor_value_test.ts new file mode 100644 index 00000000000..830fc7cb6a4 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debug_tensor_value_test.ts @@ -0,0 +1,480 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +import {TensorDebugMode} from './debugger_types'; +import {parseDebugTensorValue} from './debug_tensor_value'; + +describe('parseDebugTensorValue', () => { + describe('CURT_HEALTH', () => { + it('returns correct value if tensor has no inf or nan', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CURT_HEALTH, + array: [ + 123, // tensor ID + 0, // has inf or nan? + ], + }); + expect(debugValue).toEqual({ + hasInfOrNaN: false, + }); + }); + + it('returns correct value if tensor has inf or nan', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CURT_HEALTH, + array: [ + 123, // tensor ID + 1, // has inf or nan? + ], + }); + expect(debugValue).toEqual({ + hasInfOrNaN: true, + }); + }); + + for (const array of [null, [0], [0, 1, 1]]) { + it(`throws error for null or wrong array arg: ${JSON.stringify( + array + )}`, () => { + expect(() => + parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CURT_HEALTH, + array, + }) + ).toThrowError(/CURT_HEALTH.*expect.*length 2/); + }); + } + }); + + describe('CONCISE_HEALTH', () => { + it('returns correct value if tensor is all healthy', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CONCISE_HEALTH, + array: [ + 123, + 1000, // size + 0, + 0, + 0, + ], + }); + expect(debugValue).toEqual({ + size: 1000, + }); + }); + + it('returns correct value if tensor has nan', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CONCISE_HEALTH, + array: [ + 123, + 1000, // size + 0, + 0, + 1, + ], + }); + expect(debugValue).toEqual({ + size: 1000, + numNaNs: 1, + }); + }); + + it('returns correct value if tensor has neg inf', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CONCISE_HEALTH, + array: [123, 1000, 2, 0, 0], + }); + expect(debugValue).toEqual({ + size: 1000, + numNegativeInfs: 2, + }); + }); + + it('returns correct value if tensor has pos inf', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CONCISE_HEALTH, + array: [ + 123, + 1000, // size + 0, + 22, + 0, + ], + }); + expect(debugValue).toEqual({ + size: 1000, + numPositiveInfs: 22, + }); + }); + + it('returns correct value if tensor has nan, -inf and inf', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CONCISE_HEALTH, + array: [ + 123, + 1000, // size + 10, + 20, + 30, + ], + }); + expect(debugValue).toEqual({ + size: 1000, + numNegativeInfs: 10, + numPositiveInfs: 20, + numNaNs: 30, + }); + }); + + for (const array of [null, [0, 10, 0, 0], [0, 10, 0, 0, 0, 0]]) { + it(`throws error for null or wrong array arg: ${JSON.stringify( + array + )}`, () => { + expect(() => + parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CONCISE_HEALTH, + array, + }) + ).toThrowError(/CONCISE_HEALTH.*expect.*length 5/); + }); + } + }); + + describe('SHAPE', () => { + it('returns correct value for 0D bool', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.SHAPE, + array: [ + 123, + 10, // bool + 0, // rank + 1, // size + 0, + 0, + 0, + 0, + 0, + 0, + ], + }); + expect(debugValue).toEqual({ + dtype: 'bool', + rank: 0, + size: 1, + shape: [], + }); + }); + + it('returns correct value for 1D int32', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.SHAPE, + array: [ + 123, + 3, // int32 + 1, // rank + 46, // size + 46, + 0, + 0, + 0, + 0, + 0, + ], + }); + expect(debugValue).toEqual({ + dtype: 'int32', + rank: 1, + size: 46, + shape: [46], + }); + }); + + it('returns correct value for 2D float32', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.SHAPE, + array: [ + 123, + 1, // float32 + 2, // rank + 1200, + 30, + 40, + 0, + 0, + 0, + 0, + ], + }); + expect(debugValue).toEqual({ + dtype: 'float32', + rank: 2, + size: 1200, + shape: [30, 40], + }); + }); + + it('returns correct value for 6D float64', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.SHAPE, + array: [ + 123, + 2, // float64 + 6, // rank + 1200, + 1, + 2, + 3, + 4, + 5, + 10, + ], + }); + expect(debugValue).toEqual({ + dtype: 'float64', + rank: 6, + size: 1200, + shape: [1, 2, 3, 4, 5, 10], + }); + }); + + it('returns correct value for truncated shape: 7D', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.SHAPE, + array: [ + 123, + 5, // int16 + 7, // rank 7 + 1200, + 3, + 4, + 1, + 2, + 1, + 5, + ], + }); + expect(debugValue).toEqual({ + dtype: 'int16', + rank: 7, + size: 1200, + // Truncated dimensions are filled with `undefined`s. + shape: [undefined, 3, 4, 1, 2, 1, 5], + }); + }); + + it('returns correct value for truncated shape: 8D', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.SHAPE, + array: [ + 123, + 1, // float32 + 8, // rank 8 + 1200, + 3, + 4, + 1, + 2, + 1, + 5, + ], + }); + expect(debugValue).toEqual({ + dtype: 'float32', + rank: 8, + size: 1200, + // Truncated dimensions are filled with `undefined`s. + shape: [undefined, undefined, 3, 4, 1, 2, 1, 5], + }); + }); + + for (const array of [ + null, + [123, 1, 8, 1200, 3, 4, 1, 2, 1], + [123, 1, 8, 1200, 3, 4, 1, 2, 1, 5, 6], + ]) { + it(`throws error for null or wrong array arg: ${JSON.stringify( + array + )}`, () => { + expect(() => + parseDebugTensorValue({tensorDebugMode: TensorDebugMode.SHAPE, array}) + ).toThrowError(/SHAPE.*expect.*length 10/); + }); + } + }); + + describe('FULL_HEALTH', () => { + it('returns correct value for float32 2D with no inf or nan', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.FULL_HEALTH, + array: [ + 123, + 0, + 1, // float32 + 2, // rank + 600, // size + 0, + 0, + 0, + 100, + 200, + 300, + ], + }); + expect(debugValue).toEqual({ + dtype: 'float32', + rank: 2, + size: 600, + numNegativeFinites: 100, + numZeros: 200, + numPositiveFinites: 300, + }); + }); + + it('returns correct value for float64 scalar nan', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.FULL_HEALTH, + array: [ + 123, + 0, + 2, // float64 + 0, // rank + 1, // size + 0, + 0, + 1, + 0, + 0, + 0, + ], + }); + expect(debugValue).toEqual({ + dtype: 'float64', + rank: 0, + size: 1, + numNaNs: 1, + }); + }); + + it('returns correct value for bfloat16 1D with -inf and +inf', () => { + const debugValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.FULL_HEALTH, + array: [ + 123, + 0, + 14, // bfloat16 + 1, // rank + 10, // size + 3, + 7, + 0, + 0, + 0, + 0, + ], + }); + expect(debugValue).toEqual({ + dtype: 'bfloat16', + rank: 1, + size: 10, + numNegativeInfs: 3, + numPositiveInfs: 7, + }); + }); + + for (const array of [ + null, + [123, 0, 14, 1, 10, 3, 7, 0, 0, 0], + [123, 0, 14, 1, 10, 3, 7, 0, 0, 0, 0, 0], + ]) { + it(`throws error for null or wrong array arg: ${JSON.stringify( + array + )}`, () => { + expect(() => + parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.FULL_HEALTH, + array, + }) + ).toThrowError(/FULL_HEALTH.*expect.*length 11/); + }); + } + }); + + describe('NO_TENSOR', () => { + it('returns empty object', () => { + expect( + parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.NO_TENSOR, + array: null, + }) + ).toEqual({}); + }); + + for (const array of [[], [0]]) { + it(`throws error for non-null array arg: ${JSON.stringify( + array + )}`, () => { + expect(() => + parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.NO_TENSOR, + array, + }) + ).toThrowError(/non-null.*NO_TENSOR/); + }); + } + }); + + describe('FULL_TENSOR', () => { + it('returns empty object', () => { + expect( + parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.FULL_TENSOR, + array: null, + }) + ).toEqual({}); + }); + + for (const array of [[], [0]]) { + it(`throws error for non-null array arg: ${JSON.stringify( + array + )}`, () => { + expect(() => + parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.FULL_TENSOR, + array, + }) + ).toThrowError(/non-null.*FULL_TENSOR/); + }); + } + }); + + describe('Invalid TensorDebugMode', () => { + for (const debugMode of [ + null, + undefined, + NaN, + TensorDebugMode.UNSPECIFIED, + ]) { + it('throws error', () => { + expect(() => + parseDebugTensorValue({ + tensorDebugMode: debugMode as TensorDebugMode, + array: null, + }) + ).toThrowError(); + }); + } + }); +}); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_types.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_types.ts index 09ca45e2f89..dac2076c4fa 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_types.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_types.ts @@ -155,6 +155,59 @@ export interface InfNanAlert extends Alert { graph_execution_trace_index: number | null; } +/** + * Values that summarize a tensor watched by the debugger. + */ +export interface DebugTensorValue { + // Data type of the tensor. + dtype?: string; + + // Rank of the tensor (e.g., 0 for scalar, 1 for 1D tensor, etc.) + rank?: number; + + // Shape of the tensor. + // In the case where the shape is truncated beyond the highest + // rank a debug mode can represent, the truncated part is filled with + // `undefined`s. + shape?: Array; + + // Size (total element count) of the tensor. + size?: number; + + // Number of NaN elements. + numNaNs?: number; + + // Number of -Infinity elements. + numNegativeInfs?: number; + + // Number of +Infinity elements. + numPositiveInfs?: number; + + // Number of finite negative elements. + numNegativeFinites?: number; + + // Numbe of zeros elements. + numZeros?: number; + + // Number of finite positive elements. + numPositiveFinites?: number; + + // Whether the tensor contains any NaN or Infinity elements. + hasInfOrNaN?: boolean; + + // Minimum value. + min?: number; + + // Maximum value. + max?: number; + + // Arithmetic mean. + mean?: number; + + // Variance. + variance?: number; +} + export interface ExecutionDigestLoadState extends LoadState { // A map from page number to whether the page has been loaded // - in full, in which case the value is pageSize. diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/BUILD new file mode 100644 index 00000000000..f8df14e00f8 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/BUILD @@ -0,0 +1,41 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("@npm_angular_bazel//:index.bzl", "ng_module") +load("//tensorboard/defs:defs.bzl", "tf_ts_library") + +licenses(["notice"]) # Apache 2.0 + +ng_module( + name = "debug_tensor_value", + srcs = [ + "debug_tensor_value_component.ts", + "debug_tensor_value_module.ts", + ], + deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:debug_tensor_value", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "@npm//@angular/common", + "@npm//@angular/core", + ], +) + +tf_ts_library( + name = "debug_tensor_value_component_test_lib", + testonly = True, + srcs = [ + "debug_tensor_value_component_test.ts", + ], + tsconfig = "//:tsconfig-test", + deps = [ + ":debug_tensor_value", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:debug_tensor_value", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "//tensorboard/webapp/angular:expect_angular_core_testing", + "//tensorboard/webapp/angular:expect_ngrx_store_testing", + "@npm//@angular/common", + "@npm//@angular/compiler", + "@npm//@angular/core", + "@npm//@angular/platform-browser", + "@npm//@types/jasmine", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_component.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_component.ts new file mode 100644 index 00000000000..a55fe017e38 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_component.ts @@ -0,0 +1,304 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +import {Component, Input} from '@angular/core'; + +import {DebugTensorValue} from '../../store/debugger_types'; + +const basicDebugInfoStyle = ` + :host { + background-color: #e3e5e8; + border: 1px solid #c0c0c0; + border-radius: 4px; + font-family: 'Roboto Mono', monospace; + height: 14px; + line-height: 14px; + margin: 0 2px; + padding: 1px 3px; + width: max-content; + } +`; + +@Component({ + selector: 'debug-tensor-dtype', + template: ` + {{ dtype }} + `, + styles: [basicDebugInfoStyle], +}) +export class DebugTensorDTypeComponent { + @Input() + dtype!: string; +} + +@Component({ + selector: 'debug-tensor-rank', + template: ` + {{ rank }}D + `, + styles: [basicDebugInfoStyle], +}) +export class DebugTensorRankComponent { + @Input() + rank!: number; +} + +@Component({ + selector: 'debug-tensor-shape', + template: ` + shape:{{ shapeString }} + `, + styles: [basicDebugInfoStyle], +}) +export class DebugTensorShapeComponent { + @Input() + shape!: Array; + + get shapeString(): string { + return ( + '[' + + this.shape + .map((dim) => { + return dim === undefined ? '?' : String(dim); + }) + .join(',') + + ']' + ); + } +} + +@Component({ + selector: 'debug-tensor-numeric-breakdown', + template: ` +
+ size: + {{ size }} +
+
+
+
+ NaN + ×{{ numNaNs }} +
+
+ -∞ + ×{{ numNegativeInfs }} +
+
+ +∞ + ×{{ numPositiveInfs }} +
+
+ - + ×{{ numNegativeFinites }} +
+
+ 0 + ×{{ numZeros }} +
+
+ + + ×{{ numPositiveFinites }} +
+
+ `, + styles: [ + ` + :host { + background-color: #e3e5e8; + border: 1px solid #c0c0c0; + border-radius: 4px; + font-family: 'Roboto Mono', monospace; + font-size: 10px; + margin: 0 2px; + padding: 1px; + } + .break { + flex-basis: 100%; + width: 0; + } + .size { + display: block; + height: 11px; + line-height: 11px; + margin: 0 3px; + vertical-align: middle; + } + .breakdown { + border-top: 1px solid rgba(0, 0, 0, 0.12); + display: flex; + height: 11px; + line-height: 11px; + padding: 2px; + vertical-align: middle; + } + .category { + margin-bottom: 2px; + margin-left: 4px; + margin-top: 2px; + heigth: 100%; + width: max-content; + } + .category-tag { + border-radius: 2px; + padding: 0 2px; + } + .finite { + background-color: #aaa; + color: #fefefe; + } + .infinite { + background-color: #e52592; + color: #fff; + } + `, + ], +}) +export class DebugTensorNumericBreakdownComponent { + @Input() + size!: number; + + @Input() + numNaNs!: number | undefined; + + @Input() + numNegativeInfs: number | undefined; + + @Input() + numPositiveInfs: number | undefined; + + @Input() + numNegativeFinites: number | undefined; + + @Input() + numZeros: number | undefined; + + @Input() + numPositiveFinites: number | undefined; + + get breakdownExists(): boolean { + return ( + this.numNaNs !== undefined || + this.numNegativeInfs !== undefined || + this.numPositiveInfs !== undefined || + this.numNegativeFinites !== undefined || + this.numZeros !== undefined || + this.numPositiveFinites !== undefined + ); + } +} + +@Component({ + selector: 'debug-tensor-has-inf-or-nan', + template: ` +
+ {{ infoString }} +
+ `, + styles: [ + ` + .container { + background-color: #e3e5e8; + border: 1px solid #c0c0c0; + border-radius: 4px; + color: #666666; + font-family: 'Roboto Mono', monospace; + height: 14px; + line-height: 14px; + margin: 0 2px; + padding: 1px 3px; + width: max-content; + } + .has-inf-or-nan { + background-color: #e52592; + color: #fff; + } + `, + ], +}) +export class DebugTensorHasInfOrNaNComponent { + @Input() + hasInfOrNaN!: boolean; + + get infoString(): string { + return this.hasInfOrNaN ? 'Has ∞/NaN' : 'No ∞/NaN'; + } +} + +@Component({ + selector: 'debug-tensor-value', + template: ` + + + + + + + + + + + `, + styles: [ + ` + :host { + align-items: flex-start; + display: flex; + flex-wrap: nowrap; + overflow: hidden; + vertical-align: top; + } + debug-tensor-numeric-breakdown { + display: inline-block; + } + `, + ], +}) +export class DebugTensorValueComponent { + @Input() + debugTensorValue!: DebugTensorValue; +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_component_test.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_component_test.ts new file mode 100644 index 00000000000..1ad97f8f934 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_component_test.ts @@ -0,0 +1,278 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +/** + * Unit tests for the the DebugTensorValue Angular component. + */ + +import {TestBed} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; + +import {parseDebugTensorValue} from '../../store/debug_tensor_value'; +import {TensorDebugMode} from '../../store/debugger_types'; +import { + DebugTensorDTypeComponent, + DebugTensorHasInfOrNaNComponent, + DebugTensorNumericBreakdownComponent, + DebugTensorRankComponent, + DebugTensorShapeComponent, + DebugTensorValueComponent, +} from './debug_tensor_value_component'; + +describe('debug-tensor-value components', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + DebugTensorDTypeComponent, + DebugTensorHasInfOrNaNComponent, + DebugTensorNumericBreakdownComponent, + DebugTensorRankComponent, + DebugTensorShapeComponent, + DebugTensorValueComponent, + ], + }).compileComponents(); + }); + + describe('DebugTensorDTypeComponent', () => { + it('displays correct dtype', () => { + const fixture = TestBed.createComponent(DebugTensorDTypeComponent); + const component = fixture.componentInstance; + component.dtype = 'bfloat16'; + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('bfloat16'); + }); + }); + + describe('DebugTensorRankComponent', () => { + for (const [rank, expectedDimText] of [ + [0, '0D'], + [1, '1D'], + [2, '2D'], + [6, '6D'], + ] as Array<[number, string]>) { + it(`displays correct rank=${rank}`, () => { + const fixture = TestBed.createComponent(DebugTensorRankComponent); + const component = fixture.componentInstance; + component.rank = rank; + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe(expectedDimText); + }); + } + }); + + describe('DebugTensorShapeComponent', () => { + for (const [shape, expectedShapeText] of [ + [[], 'shape:[]'], + [[30], 'shape:[30]'], + [[4, 5], 'shape:[4,5]'], + [[1, 4, 5], 'shape:[1,4,5]'], + [[1, 3, 3, 7], 'shape:[1,3,3,7]'], + [[1, 2, 3, 4, 5], 'shape:[1,2,3,4,5]'], + [[0, 1, 2, 3, 4, 5], 'shape:[0,1,2,3,4,5]'], + [[undefined, 1, 3, 3, 7, 10, 20], 'shape:[?,1,3,3,7,10,20]'], + [[undefined, undefined, 1, 3, 3, 7, 10, 20], 'shape:[?,?,1,3,3,7,10,20]'], + ] as Array<[Array, string]>) { + it(`displays correct shape: ${JSON.stringify(expectedShapeText)}`, () => { + const fixture = TestBed.createComponent(DebugTensorShapeComponent); + const component = fixture.componentInstance; + component.shape = shape; + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe(expectedShapeText); + }); + } + }); + + describe('DebugTensorNumericBreakdownComponent', () => { + it('No breakdown available: displays size only', () => { + const fixture = TestBed.createComponent( + DebugTensorNumericBreakdownComponent + ); + const component = fixture.componentInstance; + component.size = 345; + fixture.detectChanges(); + const sizeElement = fixture.debugElement.query(By.css('.size-value')); + expect(sizeElement.nativeElement.innerText).toBe('345'); + const breakdownElement = fixture.debugElement.query(By.css('.breakdown')); + expect(breakdownElement).toBeNull(); + }); + + it('displays size and number of finite elements', () => { + const fixture = TestBed.createComponent( + DebugTensorNumericBreakdownComponent + ); + const component = fixture.componentInstance; + component.size = 345; + component.numNegativeFinites = 300; + component.numZeros = 40; + component.numPositiveFinites = 5; + fixture.detectChanges(); + const sizeElement = fixture.debugElement.query(By.css('.size-value')); + expect(sizeElement.nativeElement.innerText).toBe('345'); + const breakdownElement = fixture.debugElement.query(By.css('.breakdown')); + expect(breakdownElement).not.toBeNull(); + const tagElements = fixture.debugElement.queryAll( + By.css('.category-tag') + ); + const countElements = fixture.debugElement.queryAll( + By.css('.category-count') + ); + expect(tagElements.length).toBe(3); + expect(countElements.length).toBe(3); + expect(tagElements[0].nativeElement.innerText).toBe('-'); + expect(countElements[0].nativeElement.innerText).toBe('×300'); + expect(tagElements[1].nativeElement.innerText).toBe('0'); + expect(countElements[1].nativeElement.innerText).toBe('×40'); + expect(tagElements[2].nativeElement.innerText).toBe('+'); + expect(countElements[2].nativeElement.innerText).toBe('×5'); + }); + + it('displays size and number of infinite elements', () => { + const fixture = TestBed.createComponent( + DebugTensorNumericBreakdownComponent + ); + const component = fixture.componentInstance; + component.size = 345; + component.numNaNs = 300; + component.numNegativeInfs = 40; + component.numPositiveInfs = 5; + fixture.detectChanges(); + const sizeElement = fixture.debugElement.query(By.css('.size-value')); + expect(sizeElement.nativeElement.innerText).toBe('345'); + const breakdownElement = fixture.debugElement.query(By.css('.breakdown')); + expect(breakdownElement).not.toBeNull(); + const tagElements = fixture.debugElement.queryAll( + By.css('.category-tag') + ); + const countElements = fixture.debugElement.queryAll( + By.css('.category-count') + ); + expect(tagElements.length).toBe(3); + expect(countElements.length).toBe(3); + expect(tagElements[0].nativeElement.innerText).toBe('NaN'); + expect(countElements[0].nativeElement.innerText).toBe('×300'); + expect(tagElements[1].nativeElement.innerText).toBe('-∞'); + expect(countElements[1].nativeElement.innerText).toBe('×40'); + expect(tagElements[2].nativeElement.innerText).toBe('+∞'); + expect(countElements[2].nativeElement.innerText).toBe('×5'); + }); + }); + + describe('DebugTensorHasInfOrNaNComponent', () => { + it('displays no inf or nan', () => { + const fixture = TestBed.createComponent(DebugTensorHasInfOrNaNComponent); + const component = fixture.componentInstance; + component.hasInfOrNaN = false; + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('No ∞/NaN'); + }); + + it('displays has inf or nan', () => { + const fixture = TestBed.createComponent(DebugTensorHasInfOrNaNComponent); + const component = fixture.componentInstance; + component.hasInfOrNaN = true; + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('Has ∞/NaN'); + }); + }); + + describe('DebugTensorValueComponent', () => { + it('displays CURT_HEALTH data', () => { + const fixture = TestBed.createComponent(DebugTensorValueComponent); + const component = fixture.componentInstance; + component.debugTensorValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CURT_HEALTH, + array: [123, 0], + }); + fixture.detectChanges(); + expect( + fixture.debugElement.query(By.css('debug-tensor-has-inf-or-nan')) + ).not.toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-dtype')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-shape')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-numeric-breakdown')) + ).toBeNull(); + }); + + it('displays CONCISE_HEALTH data', () => { + const fixture = TestBed.createComponent(DebugTensorValueComponent); + const component = fixture.componentInstance; + component.debugTensorValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.CONCISE_HEALTH, + array: [123, 345, 300, 40, 5], + }); + fixture.detectChanges(); + expect( + fixture.debugElement.query(By.css('debug-tensor-has-inf-or-nan')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-dtype')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-shape')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-numeric-breakdown')) + ).not.toBeNull(); + }); + + it('displays SHAPE data', () => { + const fixture = TestBed.createComponent(DebugTensorValueComponent); + const component = fixture.componentInstance; + component.debugTensorValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.SHAPE, + array: [123, 5, 7, 1200, 3, 4, 1, 2, 1, 5], + }); + fixture.detectChanges(); + expect( + fixture.debugElement.query(By.css('debug-tensor-has-inf-or-nan')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-dtype')) + ).not.toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-shape')) + ).not.toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-numeric-breakdown')) + ).not.toBeNull(); + }); + + it('displays FULL_HEALTH data', () => { + const fixture = TestBed.createComponent(DebugTensorValueComponent); + const component = fixture.componentInstance; + component.debugTensorValue = parseDebugTensorValue({ + tensorDebugMode: TensorDebugMode.FULL_HEALTH, + array: [123, 0, 1, 2, 600, 0, 0, 0, 100, 200, 300], + }); + fixture.detectChanges(); + expect( + fixture.debugElement.query(By.css('debug-tensor-has-inf-or-nan')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-dtype')) + ).not.toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-shape')) + ).toBeNull(); + expect( + fixture.debugElement.query(By.css('debug-tensor-numeric-breakdown')) + ).not.toBeNull(); + }); + }); +}); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_module.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_module.ts new file mode 100644 index 00000000000..d467e0d5bd7 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value/debug_tensor_value_module.ts @@ -0,0 +1,40 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; + +import { + DebugTensorDTypeComponent, + DebugTensorHasInfOrNaNComponent, + DebugTensorNumericBreakdownComponent, + DebugTensorRankComponent, + DebugTensorShapeComponent, + DebugTensorValueComponent, +} from './debug_tensor_value_component'; + +@NgModule({ + declarations: [ + DebugTensorDTypeComponent, + DebugTensorHasInfOrNaNComponent, + DebugTensorNumericBreakdownComponent, + DebugTensorRankComponent, + DebugTensorShapeComponent, + DebugTensorValueComponent, + ], + imports: [CommonModule], + exports: [DebugTensorValueComponent], +}) +export class DebugTensorValueModule {} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/BUILD index e65580b3c84..a15f09e2a70 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/BUILD +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/BUILD @@ -19,7 +19,9 @@ ng_module( "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin:tf_types", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:debug_tensor_value", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value", "@npm//@angular/common", "@npm//@angular/core", "@npm//@ngrx/store", diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.css b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.css index 0957ac2e5c0..1e27c0c4b98 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.css +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.css @@ -13,12 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -.debug-tensor-values-container { - height: 60px; - overflow-x: auto; - overflow-y: auto; -} - .debug-tensor-values-table { width: 100%; } @@ -57,6 +51,29 @@ limitations under the License. width: 360px; } +.output-slots { + height: 60px; + overflow-x: auto; + overflow-y: auto; +} + +.output-slot-container { + border-top: 1px solid rgba(0, 0, 0, 0.12); + margin-top: 5px; + padding: 2px 0; + vertical-align: top; +} + +.output-slot-number { + display: block; + font-family: 'Roboto Mono', monospace; +} + +.output-slot-debug-tensor-value { + display: block; + margin: 3px 0 3px 30px; +} + .output-tensors { margin-top: 5px; } diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ng.html b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ng.html index 4cc5066cf34..06f69702b2f 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ng.html +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ng.html @@ -18,7 +18,7 @@
- Execution #{{ focusedExecutionIndex }} + Eager Execution #{{ focusedExecutionIndex }}
@@ -58,210 +58,25 @@
-
-
-
- - - - - - - - - -
SlotContains inf/nan?
- {{ i }} - - {{ singleDebugTensorValues[1] ? "Yes" : "No" }} -
+
+
+ + +
+ Output slot {{ i }}:
- -
- - - - - - - - - - - - - - - -
SlotSize#(-inf)#(inf)#(nan)
- {{ i }} - - {{ singleDebugTensorValues[1] }} - - {{ singleDebugTensorValues[2] }} - - {{ singleDebugTensorValues[3] }} - - {{ singleDebugTensorValues[4] }} -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - -
SlotDTypeRankSize#(-inf)#(inf)#(nan)#(-)#(+)#(0)
- {{ i }} - - - {{ debugTensorDtypes[i] }} - - - {{ singleDebugTensorValues[3] }} - - {{ singleDebugTensorValues[4] }} - - {{ singleDebugTensorValues[5] }} - - {{ singleDebugTensorValues[6] }} - - {{ singleDebugTensorValues[7] }} - - {{ singleDebugTensorValues[8] }} - - {{ singleDebugTensorValues[9] }} - - {{ singleDebugTensorValues[10] }} -
+
+ +
- -
- - - - - - - - - - - - - - - -
SlotDTypeRankSizeShape
- {{ i }} - - - {{ debugTensorDtypes[i] }} - - - {{ singleDebugTensorValues[2] }} - - {{ singleDebugTensorValues[3] }} - - ({{ singleDebugTensorValues.slice(4, 4 + - singleDebugTensorValues[2]) }}) -
-
- - -
diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ts index a945b7f144e..ad3ac9824bf 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_component.ts @@ -15,6 +15,7 @@ limitations under the License. import {Component, Input} from '@angular/core'; import {Execution, TensorDebugMode} from '../../store/debugger_types'; +import {parseDebugTensorValue} from '../../store/debug_tensor_value'; @Component({ selector: 'execution-data-component', @@ -51,4 +52,5 @@ export class ExecutionDataComponent { // So that the enum can be used in the template html. public TensorDebugMode = TensorDebugMode; + parseDebugTensorValue = parseDebugTensorValue; } diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_module.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_module.ts index f524e3b505e..e8d69218042 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_module.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data/execution_data_module.ts @@ -16,12 +16,13 @@ limitations under the License. import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; +import {DebugTensorValueModule} from '../debug_tensor_value/debug_tensor_value_module'; import {ExecutionDataComponent} from './execution_data_component'; import {ExecutionDataContainer} from './execution_data_container'; @NgModule({ declarations: [ExecutionDataComponent, ExecutionDataContainer], - imports: [CommonModule], + imports: [CommonModule, DebugTensorValueModule], exports: [ExecutionDataContainer], }) export class ExecutionDataModule {} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/BUILD index aa77f493bc8..618ef9b5991 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/BUILD +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/BUILD @@ -17,10 +17,11 @@ ng_module( "graph_executions_component.ng.html", ], deps = [ - "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin:tf_types", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:debug_tensor_value", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value", "//tensorboard/webapp/angular:expect_angular_cdk_scrolling", "@npm//@angular/common", "@npm//@angular/core", @@ -44,6 +45,7 @@ tf_ts_library( "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/debug_tensor_value", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/execution_data", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/inactive", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/source_files", diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.css b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.css index b297ad3e6d3..fa1f54c6793 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.css +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.css @@ -65,7 +65,7 @@ limitations under the License. height: 36px; line-height: 36px; text-align: left; - vertical-align: top; + vertical-align: middle; white-space: nowrap; width: 100%; } @@ -90,3 +90,8 @@ limitations under the License. text-align: right; width: 240px; } + +debug-tensor-value { + display: inline-block; + margin: 2px 0; +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ng.html b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ng.html index 68db3b26c30..827041487e4 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ng.html +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ng.html @@ -44,6 +44,15 @@ {{ graphExecutionData[i].op_type }}
+ + +
diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ts index 145960e6936..8237aacb6b1 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_component.ts @@ -21,6 +21,7 @@ import { Output, } from '@angular/core'; +import {parseDebugTensorValue} from '../../store/debug_tensor_value'; import {GraphExecution} from '../../store/debugger_types'; @Component({ @@ -41,4 +42,6 @@ export class GraphExecutionsComponent { @Output() onScrolledIndexChange = new EventEmitter(); + + parseDebugTensorValue = parseDebugTensorValue; } diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_container_test.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_container_test.ts index 6e6749f057d..2eab240d676 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_container_test.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_container_test.ts @@ -24,7 +24,11 @@ import {MockStore, provideMockStore} from '@ngrx/store/testing'; import {DebuggerComponent} from '../../debugger_component'; import {DebuggerContainer} from '../../debugger_container'; -import {State, GraphExecution} from '../../store/debugger_types'; +import { + State, + GraphExecution, + TensorDebugMode, +} from '../../store/debugger_types'; import {getNumGraphExecutions, getGraphExecutionData} from '../../store'; import { createDebuggerState, @@ -92,6 +96,8 @@ describe('Graph Executions Container', () => { graphExecutionData[i] = createTestGraphExecution({ op_name: `TestOp_${i}`, op_type: `OpType_${i}`, + tensor_debug_mode: TensorDebugMode.CONCISE_HEALTH, + debug_tensor_value: [i, 100, 0, 0, 0], }); } store.overrideSelector(getGraphExecutionData, graphExecutionData); @@ -123,6 +129,10 @@ describe('Graph Executions Container', () => { expect(tensorNames[i].nativeElement.innerText).toBe(`TestOp_${i}:0`); expect(opTypes[i].nativeElement.innerText).toBe(`OpType_${i}`); } + const debugTensorValueElements = fixture.debugElement.queryAll( + By.css('debug-tensor-value') + ); + expect(debugTensorValueElements.length).toBe(tensorContainers.length); })); it('renders # execs and execs viewport if # execs > 0; not loaded', fakeAsync(() => { diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_module.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_module.ts index 5e43f6e91cb..f90afb617f1 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_module.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/graph_executions/graph_executions_module.ts @@ -17,12 +17,13 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; +import {DebugTensorValueModule} from '../debug_tensor_value/debug_tensor_value_module'; import {GraphExecutionsComponent} from './graph_executions_component'; import {GraphExecutionsContainer} from './graph_executions_container'; @NgModule({ declarations: [GraphExecutionsComponent, GraphExecutionsContainer], - imports: [CommonModule, ScrollingModule], + imports: [CommonModule, DebugTensorValueModule, ScrollingModule], exports: [GraphExecutionsContainer], }) export class GraphExecutionsModule {}