Skip to content
This repository has been archived by the owner on Sep 17, 2022. It is now read-only.

Start integration with TensorBoard #197

Merged
merged 32 commits into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2235585
Working on TensorBoard op support
caisq Jan 22, 2019
af891d7
Calling SummaryWriter op and getting DT_RESOURCE output as a string is
caisq Jan 22, 2019
0b56dd0
WIP
caisq Jan 23, 2019
7623739
WIP: createSummaryFileWriter() seems to be creating dirs now
caisq Jan 23, 2019
d3ae88e
At a stage where WriteScalarSummary op says int32 != int64 for step
caisq Jan 24, 2019
c787a36
Seems to be writing scalar summaries now, though step value seems wrong.
caisq Jan 24, 2019
f259290
Checkpoint: TB summary scalar writing basically works
caisq Jan 29, 2019
aa0c345
Simplify CopyTFE_TensorHandleDataToResourceArray
caisq Jan 29, 2019
ff82823
Gradually removing the int64 hack
caisq Jan 29, 2019
9c986ff
Remove some debugging prints
caisq Jan 29, 2019
f725d04
Make int64 full range work; Clean up C++ code; Delete debug code
caisq Jan 30, 2019
4a708df
Fix some comments
caisq Jan 30, 2019
d8d10b0
Some clean up
caisq Jan 30, 2019
baed1e2
Revise API
caisq Jan 30, 2019
25bf64e
Merge branch 'master' into tensorboard
caisq Jan 30, 2019
093681a
Fix linter errors
caisq Jan 30, 2019
47354ba
Fix linter errors; add comments
caisq Jan 30, 2019
da37fd7
Add doc strings
caisq Jan 30, 2019
5bd42cf
Cleanup in summaryWriter()
caisq Jan 30, 2019
d785813
Adjust API exports
caisq Jan 30, 2019
3558b26
Change async summaryFileWriter() to sync
caisq Jan 30, 2019
73ad84b
Doc string fix
caisq Jan 30, 2019
2b2ca41
Add code snippet in doc string of `summaryFileWriter()`
caisq Jan 30, 2019
65985a4
Remove obsolete TODO items
caisq Jan 30, 2019
c9a00ad
Fix typo; remove unwanted file
caisq Jan 30, 2019
e1ae2d9
Fix typo in code snippet
caisq Jan 30, 2019
92ea73c
Address review comments; add guard for int32 value bounds
caisq Feb 1, 2019
bf6800c
Add unit tests for int64_tensors.ts
caisq Feb 1, 2019
1045d82
Add unit tests for tensorboard summary methods
caisq Feb 1, 2019
3101217
Respond to review comments
caisq Feb 5, 2019
91e3621
Address review comments
caisq Feb 5, 2019
2e1e058
Merge branch 'master' into tensorboard
caisq Feb 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 116 additions & 19 deletions binding/tfjs_backend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ TFE_TensorHandle *CreateTFE_TensorHandleFromTypedArray(napi_env env,
width = sizeof(float);
break;
case napi_int32_array:
if (dtype != TF_INT32) {
if (dtype != TF_INT32 && dtype != TF_INT64) {
// Currently, both int32- and int64-type Tensors are represented
// as Int32Arrays in JavaScript. See int64_tensors.ts for details
// about the latter.
NAPI_THROW_ERROR(env, "Tensor type does not match Int32Array");
return nullptr;
}
Expand All @@ -78,12 +81,26 @@ TFE_TensorHandle *CreateTFE_TensorHandleFromTypedArray(napi_env env,
}

// Double check that width matches TF data type size:
if (width != TF_DataTypeSize(dtype)) {
NAPI_THROW_ERROR(env,
"Byte size of elements differs between JavaScript VM "
"(%zu) and TensorFlow (%zu)",
width, TF_DataTypeSize(dtype));
return nullptr;
if (dtype == TF_INT64) {
// Currently, int64-type Tensors are represented as Int32Arrays. So the
// logic for comparing the byte size of the typed-array representation and
// the byte size of the tensor dtype needs to be special-cased for int64.
if (width * 2 != TF_DataTypeSize(dtype)) {
NAPI_THROW_ERROR(
env,
"Byte size of elements differs between JavaScript VM "
"(%zu * 2 = %zu) and TensorFlow (%zu) for int64-type tensor",
width, width * 2, TF_DataTypeSize(dtype));
return nullptr;
}
} else {
if (width != TF_DataTypeSize(dtype)) {
NAPI_THROW_ERROR(env,
"Byte size of elements differs between JavaScript VM "
"(%zu) and TensorFlow (%zu)",
width, TF_DataTypeSize(dtype));
return nullptr;
}
}

// Determine the size of the buffer based on the dimensions.
Expand All @@ -93,16 +110,35 @@ TFE_TensorHandle *CreateTFE_TensorHandleFromTypedArray(napi_env env,
}

// Ensure the shape matches the length of the passed in typed-array.
if (num_elements != array_length) {
NAPI_THROW_ERROR(env,
"Shape does not match typed-array in bindData() "
"(num_elements=%zu, array_length=%zu)",
num_elements, array_length);
return nullptr;
if (dtype == TF_INT64) {
// Currently, int64-type Tensors are represented as Int32Arrays.
// To represent a int64-type Tensor of `n` elements, an Int32Array of
// length `2 * n` is requried. This is why the length-match checking
// logic is special-cased for int64.
if (array_length != num_elements * 2) {
NAPI_THROW_ERROR(
env,
"Shape does not match two times typed-array in bindData() "
"(num_elements * 2 = %zu, array_length=%zu) for int64 data type",
num_elements * 2, array_length);
return nullptr;
}
} else {
if (num_elements != array_length) {
NAPI_THROW_ERROR(env,
"Shape does not match typed-array in bindData() "
"(num_elements=%zu, array_length=%zu)",
num_elements, array_length);
return nullptr;
}
}

// Allocate and memcpy JS data to Tensor.
const size_t byte_size = num_elements * width;
// Currently, int64-type Tensors are represented as Int32Arrays.
// So the logic for comparing the byte size of the typed-array representation
// and the byte size of the tensor dtype needs to be special-cased for int64.
const size_t byte_size =
dtype == TF_INT64 ? num_elements * width * 2 : num_elements * width;
TF_AutoTensor tensor(
TF_AllocateTensor(dtype, shape, shape_length, byte_size));
memcpy(TF_TensorData(tensor.tensor), array_data, byte_size);
Expand Down Expand Up @@ -252,8 +288,8 @@ void CopyTFE_TensorHandleDataToTypedArray(napi_env env,
&array_buffer_value);
ENSURE_NAPI_OK(env, nstatus);

// TFE_TensorHandleResolve can use a shared data pointer, memcpy() the current
// value to the newly allocated NAPI buffer.
// TFE_TensorHandleResolve can use a shared data pointer, memcpy() the
// current value to the newly allocated NAPI buffer.
memcpy(array_buffer_data, TF_TensorData(tensor.tensor), byte_length);

nstatus = napi_create_typedarray(env, array_type, num_elements,
Expand Down Expand Up @@ -324,6 +360,56 @@ void CopyTFE_TensorHandleDataToStringArray(napi_env env,
}
}

void CopyTFE_TensorHandleDataToResourceArray(
napi_env env, TFE_Context *tfe_context, TFE_TensorHandle *tfe_tensor_handle,
napi_value *result) {
TF_AutoStatus tf_status;

TF_AutoTensor tensor(
TFE_TensorHandleResolve(tfe_tensor_handle, tf_status.status));
ENSURE_TF_OK(env, tf_status);

if (TF_TensorType(tensor.tensor) != TF_RESOURCE) {
NAPI_THROW_ERROR(env, "Tensor is not of type TF_RESOURCE");
return;
}

void *tensor_data = TF_TensorData(tensor.tensor);
ENSURE_VALUE_IS_NOT_NULL(env, tensor_data);

size_t num_elements = GetTensorNumElements(tensor.tensor);
if (num_elements != 1) {
NAPI_THROW_ERROR(env,
"For DT_RESOURCE tensors, Node.js binding currently "
"supports only exactly 1 element, but encountered "
"DT_RESOURCE tensor with %zu elements.",
num_elements);
}

TF_AutoStatus status;

// Create a JS string to stash the resouce handle into.
napi_status nstatus;
size_t byte_length = TF_TensorByteSize(tensor.tensor);
nstatus = napi_create_array_with_length(env, byte_length, result);
ENSURE_NAPI_OK(env, nstatus);

napi_value array_buffer_value;
void *array_buffer_data = nullptr;
nstatus = napi_create_arraybuffer(env, byte_length, &array_buffer_data,
&array_buffer_value);
ENSURE_NAPI_OK(env, nstatus);

// TFE_TensorHandleResolve can use a shared data pointer, memcpy() the
// current value to the newly allocated NAPI buffer.
memcpy(array_buffer_data, tensor_data, byte_length);

// This method will only return uint8 arrays.
nstatus = napi_create_typedarray(env, napi_uint8_array, byte_length,
array_buffer_value, 0, result);
ENSURE_NAPI_OK(env, nstatus);
}

// Handles converting the stored TF_Tensor data into the correct JS value.
void CopyTFE_TensorHandleDataToJSData(napi_env env, TFE_Context *tfe_context,
TFE_TensorHandle *tfe_tensor_handle,
Expand All @@ -340,6 +426,7 @@ void CopyTFE_TensorHandleDataToJSData(napi_env env, TFE_Context *tfe_context,
// Determine the type of the array
napi_typedarray_type typed_array_type;
bool is_string = false;
bool is_resource = false;
TF_DataType tensor_data_type = TFE_TensorHandleDataType(tfe_tensor_handle);
switch (tensor_data_type) {
case TF_COMPLEX64:
Expand All @@ -355,6 +442,11 @@ void CopyTFE_TensorHandleDataToJSData(napi_env env, TFE_Context *tfe_context,
case TF_STRING:
is_string = true;
break;
case TF_RESOURCE:
// We currently represent a resource handle as an `Uint8Array`.
typed_array_type = napi_uint8_array;
is_resource = true;
break;
default:
REPORT_UNKNOWN_TF_DATA_TYPE(env,
TFE_TensorHandleDataType(tfe_tensor_handle));
Expand All @@ -364,6 +456,9 @@ void CopyTFE_TensorHandleDataToJSData(napi_env env, TFE_Context *tfe_context,
if (is_string) {
CopyTFE_TensorHandleDataToStringArray(env, tfe_context, tfe_tensor_handle,
result);
} else if (is_resource) {
CopyTFE_TensorHandleDataToResourceArray(env, tfe_context, tfe_tensor_handle,
result);
} else {
CopyTFE_TensorHandleDataToTypedArray(env, tfe_context, tfe_tensor_handle,
tensor_data_type, typed_array_type,
Expand Down Expand Up @@ -426,8 +521,9 @@ void AssignOpAttr(napi_env env, TFE_Op *tfe_op, napi_value attr_value) {
nstatus = GetStringParam(env, attr_name_value, attr_name_string);
ENSURE_NAPI_OK(env, nstatus);

// OpAttr will be used beyond the scope of this function call. Stash ops in a
// set for re-use instead of dynamically reallocating strings for operations.
// OpAttr will be used beyond the scope of this function call. Stash ops in
// a set for re-use instead of dynamically reallocating strings for
// operations.
const char *attr_name =
ATTR_NAME_SET.insert(attr_name_string.c_str()).first->c_str();

Expand Down Expand Up @@ -761,7 +857,8 @@ napi_value TFJSBackend::ExecuteOp(napi_env env, napi_value op_name_value,
nstatus = napi_get_value_int32(env, num_output_values, &num_outputs);
ENSURE_NAPI_OK_RETVAL(env, nstatus, nullptr);

// Push `nullptr` to get a valid pointer in the call to `TFE_Execute()` below.
// Push `nullptr` to get a valid pointer in the call to `TFE_Execute()`
// below.
std::vector<TFE_TensorHandle *> result_handles(num_outputs, nullptr);

int size = result_handles.size();
Expand Down
2 changes: 2 additions & 0 deletions binding/tfjs_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,11 @@ static napi_value InitTFNodeJSBinding(napi_env env, napi_value exports) {
// Types
EXPORT_INT_PROPERTY(TF_FLOAT);
EXPORT_INT_PROPERTY(TF_INT32);
EXPORT_INT_PROPERTY(TF_INT64);
EXPORT_INT_PROPERTY(TF_BOOL);
EXPORT_INT_PROPERTY(TF_COMPLEX64);
EXPORT_INT_PROPERTY(TF_STRING);
EXPORT_INT_PROPERTY(TF_RESOURCE);

// Op AttrType
EXPORT_INT_PROPERTY(TF_ATTR_STRING);
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const io = {
...tf.io,
...nodeIo
};

// Export all union package symbols
export * from '@tensorflow/tfjs';

Expand All @@ -61,3 +62,5 @@ tf.io.registerLoadRouter(nodeHTTPRequestRouter);
import {ProgbarLogger} from './callbacks';
// Register the ProgbarLogger for Model.fit() at verbosity level 1.
tf.registerCallbackConstructor(1, ProgbarLogger);

export * from './node';
76 changes: 76 additions & 0 deletions src/int64_tensors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @license
* Copyright 2018 Google Inc. 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 {Shape, util} from '@tensorflow/tfjs';
import {endianness} from 'os';

const INT32_MAX = 2147483648;

/**
* Node.js-specific tensor type: int64-type scalar.
*
* This class is created for a specific purpose: to support
* writing `step`s to TensorBoard via op-kernel bindings.
* `step` is required to have an int64 dtype, but TensorFlow.js
* (tfjs-core) doesn't have a built-in int64 dtype. This is
* related to a lack of `Int64Array` or `Uint64Array` typed
* array in basic JavaScript.
*
* This class is introduced as a workaround.
*/
export class Int64Scalar {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little confused with this class still.

It represents an int64 but JS can't handle those types today. It tries to parse the high/low parts of the number and validate endianness? This is mostly confusing because I think we just wanted to check that the numbers are within INT32_MIN and INT32_MAX. Why do we need all the various other stuff - seems somewhat expensive for not doing anything.

readonly dtype: string = 'int64';
readonly rank: number = 1;
private valueArray_: Int32Array;

private static endiannessOkay_: boolean;

constructor(readonly value: number) {
if (Int64Scalar.endiannessOkay_ == null) {
if (endianness() !== 'LE') {
throw new Error(
`Int64Scalar does not support endianness of this machine: ` +
`${endianness()}`);
}
Int64Scalar.endiannessOkay_ = true;
}

util.assert(
value > -INT32_MAX && value < INT32_MAX - 1,
`Got a value outside of the bound of values supported for int64 ` +
`dtype ([-${INT32_MAX}, ${INT32_MAX - 1}]): ${value}`);
util.assert(
Number.isInteger(value),
`Expected value to be an integer, but got ${value}`);

// We use two int32 elements to represent a int64 value. This assumes
// little endian, which is checked above.
const highPart = Math.floor(value / INT32_MAX);
console.log(`highPart = ${highPart}`); // DEBUG
const lowPart = value % INT32_MAX;
this.valueArray_ = new Int32Array([lowPart, highPart]);
}

get shape(): Shape {
return [];
}

/** Get the Int32Array that represents the int64 value. */
get valueArray(): Int32Array {
return this.valueArray_;
}
}
24 changes: 24 additions & 0 deletions src/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license
* Copyright 2018 Google Inc. 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.
* =============================================================================
*/

/**
* Public API symbols under the tf.node.* namespace.
*/

import {summaryFileWriter} from './tensorboard';

export const node = {summaryFileWriter};
Loading