Skip to content

Commit 2a4c873

Browse files
authored
[tfjs-node] fixed summary writer memory leak (#7490)
BUG * fixed summary writer memory leak due to the step Int64Scalar internal tensor not deleted after use. * also fixed a similar issue related to saved model execution
1 parent e1fd1f1 commit 2a4c873

File tree

2 files changed

+47
-14
lines changed

2 files changed

+47
-14
lines changed

tfjs-node/src/nodejs_kernel_backend.ts

+28-14
Original file line numberDiff line numberDiff line change
@@ -459,32 +459,41 @@ export class NodeJSKernelBackend extends KernelBackend {
459459
private getMappedInputTensorIds(
460460
inputs: Tensor[], inputTensorInfos: ModelTensorInfo[]) {
461461
const tensorIds = this.getInputTensorIds(inputs);
462+
const newTensors = [];
462463
for (let i = 0; i < inputs.length; i++) {
463464
if (inputTensorInfos[i] != null) {
464465
if (inputTensorInfos[i].tfDtype === 'DT_UINT8') {
465466
const data = Uint8Array.from(inputs[i].dataSync());
466467
const inputTensorId = this.binding.createTensor(
467468
inputs[i].shape, this.binding.TF_UINT8, data);
468469
tensorIds[i] = inputTensorId;
470+
newTensors.push(i);
469471
} else if (inputTensorInfos[i].tfDtype === 'DT_INT64') {
470472
const data =
471473
encodeInt32ArrayAsInt64(inputs[i].dataSync() as Int32Array);
472474
const inputTensorId = this.binding.createTensor(
473475
inputs[i].shape, this.binding.TF_INT64, data);
474476
tensorIds[i] = inputTensorId;
477+
newTensors.push(i);
475478
}
476479
}
477480
}
478-
return tensorIds;
481+
return {tensorIds, newTensors};
479482
}
480483

481484
runSavedModel(
482485
id: number, inputs: Tensor[], inputTensorInfos: ModelTensorInfo[],
483486
outputOpNames: string[]): Tensor[] {
487+
const {tensorIds, newTensors} =
488+
this.getMappedInputTensorIds(inputs, inputTensorInfos);
484489
const outputMetadata = this.binding.runSavedModel(
485-
id, this.getMappedInputTensorIds(inputs, inputTensorInfos),
486-
inputTensorInfos.map(info => info.name).join(','),
490+
id, tensorIds, inputTensorInfos.map(info => info.name).join(','),
487491
outputOpNames.join(','));
492+
for (let i = 0; i < tensorIds.length; i++) {
493+
if (newTensors.includes(i)) {
494+
this.binding.deleteTensor(tensorIds[i]);
495+
}
496+
}
488497
return outputMetadata.map(m => this.createOutputTensor(m));
489498
}
490499

@@ -542,9 +551,10 @@ export class NodeJSKernelBackend extends KernelBackend {
542551
}
543552
const opAttrs: TFEOpAttr[] =
544553
[{name: 'T', type: this.binding.TF_ATTR_TYPE, value: typeAttr}];
545-
546-
this.binding.executeOp(
547-
'WriteScalarSummary', opAttrs, this.getInputTensorIds(inputArgs), 0);
554+
const ids = this.getInputTensorIds(inputArgs);
555+
this.binding.executeOp('WriteScalarSummary', opAttrs, ids, 0);
556+
// release the tensorflow tensor for Int64Scalar value of step
557+
this.binding.deleteTensor(ids[1]);
548558
});
549559
}
550560

@@ -561,9 +571,10 @@ export class NodeJSKernelBackend extends KernelBackend {
561571
// and places the values in 30 buckets, while WriteSummary expects a
562572
// tensor which already describes the bucket widths and counts.
563573
//
564-
// If we were to use WriteHistogramSummary, we wouldn't have to implement
565-
// the "bucketization" of the input tensor, but we also wouldn't have
566-
// control over the number of buckets, or the description of the graph.
574+
// If we were to use WriteHistogramSummary, we wouldn't have to
575+
// implement the "bucketization" of the input tensor, but we also
576+
// wouldn't have control over the number of buckets, or the description
577+
// of the graph.
567578
//
568579
// Therefore, we instead use WriteSummary, which makes it possible to
569580
// support these features. However, the trade-off is that we have to
@@ -594,8 +605,10 @@ export class NodeJSKernelBackend extends KernelBackend {
594605
const typeAttr = this.typeAttributeFromTensor(buckets);
595606
const opAttrs: TFEOpAttr[] =
596607
[{name: 'T', type: this.binding.TF_ATTR_TYPE, value: typeAttr}];
597-
this.binding.executeOp(
598-
'WriteSummary', opAttrs, this.getInputTensorIds(inputArgs), 0);
608+
const ids = this.getInputTensorIds(inputArgs);
609+
this.binding.executeOp('WriteSummary', opAttrs, ids, 0);
610+
// release the tensorflow tensor for Int64Scalar value of step
611+
this.binding.deleteTensor(ids[1]);
599612
});
600613
}
601614

@@ -609,9 +622,10 @@ export class NodeJSKernelBackend extends KernelBackend {
609622
*
610623
* @param data A `Tensor` of any shape. Must be castable to `float32`
611624
* @param bucketCount Optional positive `number`
612-
* @returns A `Tensor` of shape `[k, 3]` and type `float32`. The `i`th row is
613-
* a triple `[leftEdge, rightEdge, count]` for a single bucket. The value of
614-
* `k` is either `bucketCount`, `1` or `0`.
625+
* @returns A `Tensor` of shape `[k, 3]` and type `float32`. The `i`th row
626+
* is
627+
* a triple `[leftEdge, rightEdge, count]` for a single bucket. The value
628+
* of `k` is either `bucketCount`, `1` or `0`.
615629
*/
616630
private buckets(data: Tensor, bucketCount?: number): Tensor<tf.Rank> {
617631
if (data.size === 0) {

tfjs-node/src/tensorboard_test.ts

+19
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ describe('tensorboard', () => {
9090
expect(fileNames.length).toEqual(1);
9191
});
9292

93+
it('Writing tf.Scalar no memory leak', () => {
94+
const writer = tfn.node.summaryFileWriter(tmpLogDir);
95+
const beforeNumTFTensors = writer.backend.getNumOfTFTensors();
96+
const value = scalar(42);
97+
writer.scalar('foo', value, 0);
98+
writer.flush();
99+
value.dispose();
100+
expect(writer.backend.getNumOfTFTensors()).toBe(beforeNumTFTensors);
101+
});
102+
93103
it('No crosstalk between two summary writers', () => {
94104
const logDir1 = path.join(tmpLogDir, '1');
95105
const writer1 = tfn.node.summaryFileWriter(logDir1);
@@ -180,6 +190,15 @@ describe('tensorboard', () => {
180190
expect(fileSize2 - fileSize1).toEqual(2 * incrementPerScalar);
181191
});
182192

193+
it('summaryFileWriter no memory leak', () => {
194+
const writer = tfn.node.summaryFileWriter(tmpLogDir);
195+
const beforeNumTFTensors = writer.backend.getNumOfTFTensors();
196+
const value = tensor1d([1, 2, 3, 4, 5], 'int32');
197+
writer.histogram('foo', value, 0, 5);
198+
writer.flush();
199+
value.dispose();
200+
expect(writer.backend.getNumOfTFTensors()).toBe(beforeNumTFTensors);
201+
});
183202
it('Can create multiple normal distribution', () => {
184203
const writer = tfn.node.summaryFileWriter(tmpLogDir);
185204
tf.tidy(() => {

0 commit comments

Comments
 (0)