-
Notifications
You must be signed in to change notification settings - Fork 2k
Split NonMaxSuppressionV3 and NonMaxSuppresionV5 to two APIs #2557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
pyu10055
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for fixing this issue.
Reviewable status: 0 of 1 approvals obtained (waiting on @dsmilkov, @lina128, and @pyu10055)
tfjs-core/src/backends/backend.ts, line 600 at r1 (raw file):
boxes: Tensor2D, scores: Tensor1D, maxOutputSize: number, iouThreshold: number, scoreThreshold?: number, softNmsSigma?: number): [Tensor1D, Tensor1D, Scalar] {
It might be better to define a interface for the output, otherwise user might not know the meaning of each tensor.
tfjs-core/src/backends/non_max_suppression_impl.ts, line 34 at r1 (raw file):
} export function nonMaxSuppressionV3(
the method names might be good to be consistent with the external APIs.
dsmilkov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice work!! One blocking question about the user-facing https://www.tensorflow.org/api_docs/python/tf/image/non_max_suppression_with_scores having 2 outputs instead of 3.
Reviewed 6 of 6 files at r1.
Reviewable status: 0 of 1 approvals obtained (waiting on @dsmilkov, @lina128, and @pyu10055)
tfjs-core/src/backends/backend.ts, line 597 at r1 (raw file):
} nonMaxSuppressionWithScore(
Going forward we decided to add any new kernels in a modular way in order to avoid the ever growing library size. This means that instead of adding a new method to the Backend interface, we add the new kernels in standalone files and call registerKernel(). See src/backends/webgl/square.ts and src/backends/cpu/square.ts for examples of modularized Square. You don't have to modularize in this PR, but I think we should modularize before we release new tfjs-core. This way, after we release new tfjs-core, tfjs-node just needs to call registerKernel with its own implementation.
tfjs-core/src/backends/backend.ts, line 600 at r1 (raw file):
Previously, pyu10055 (Ping Yu) wrote…
It might be better to define a interface for the output, otherwise user might not know the meaning of each tensor.
Array of tensors is ok here since this is a kernel (internal api) and the inference and backprop infra expects tensor|tensor[] as a result of a kernel, analogous to TF C++ kernels which return array of tensors.
tfjs-core/src/ops/image_ops.ts, line 239 at r1 (raw file):
* - A 1D tensor with the corresponding scores for each selected box. * - A number representing the number of valid elements in the selected * box indices.
The user-facing https://www.tensorflow.org/api_docs/python/tf/image/non_max_suppression_with_scores has 2 outputs instead of 3. Let's match that API at the user-facing level.
nsthorat
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status: 0 of 1 approvals obtained (waiting on @dsmilkov, @lina128, and @pyu10055)
tfjs-core/src/backends/backend.ts, line 597 at r1 (raw file):
Previously, dsmilkov (Daniel Smilkov) wrote…
Going forward we decided to add any new kernels in a modular way in order to avoid the ever growing library size. This means that instead of adding a new method to the Backend interface, we add the new kernels in standalone files and call
registerKernel(). Seesrc/backends/webgl/square.tsandsrc/backends/cpu/square.tsfor examples of modularizedSquare. You don't have to modularize in this PR, but I think we should modularize before we release new tfjs-core. This way, after we release new tfjs-core, tfjs-node just needs to call registerKernel with its own implementation.
I'll lightly push towards doing it in this PR, of course still optional! :) Na, if the way this gets done is confusing let us know and we can sync with you on a GVC!
tfjs-core/src/ops/image_ops.ts, line 239 at r1 (raw file):
Previously, dsmilkov (Daniel Smilkov) wrote…
The user-facing https://www.tensorflow.org/api_docs/python/tf/image/non_max_suppression_with_scores has 2 outputs instead of 3. Let's match that API at the user-facing level.
also I think we should return an object with named return values (an object). It's fine to keep the kernel an array.
tfjs-core/src/ops/image_ops_test.ts, line 251 at r1 (raw file):
const scoreThreshold = 0; const softNmsSigma = 0.5; const indices = await tf.image.nonMaxSuppressionWithScoreAsync(
can you also add a test for memory here? Since we don't wrap async methods in "op" we have to be careful with tensor leaking manually (e.g. make sure tf.memory().numTensors before and after the call only increase tensors by 1, the output)
Hi Ping, thanks for the review! Changed to use NamedTensorMap for the output. As for method names, I keep it in sync with wasm method name: https://github.com/tensorflow/tfjs/blob/master/tfjs-backend-wasm/src/kernels/NonMaxSuppressionV3.ts and TF C++ version: https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/non-max-suppression-v5 |
Ah, really nice catch! Thank you Daniel! Changed output to 2. The numValidOutputs is only for V4, and for V5, the flag to pad the result to the maxOutputSize is always false, therefore, no need for this output. I made sure the unused tensor is disposed. Also changed to write the new Op using the modular pattern, that design is really smart, it makes it more flexible to name function than the interface method. Also explicitly registering Ops makes it more readable and more flexible to manage Ops. Thank you for letting me know this new pattern! |
Hi Nikhil, thank you for your review! Changed to the modular pattern. Also changed the output to use NamedTensorMap. And thanks for asking to test memory leak, I added the test and it did keep unnecessary Tensor because the generic implementation generates three Tensors, whereas V5 only needs two. I disposed the unused Tensor and it passed the memory test. |
pyu10055
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status:
complete! 1 of 1 approvals obtained (waiting on @dsmilkov, @lina128, @nsthorat, and @pyu10055)
tfjs-core/src/backends/non_max_suppression_impl.ts, line 34 at r1 (raw file):
Previously, lina128 (Na Li) wrote…
Hi Ping, thanks for the review! Changed to use NamedTensorMap for the output. As for method names, I keep it in sync with wasm method name: https://github.com/tensorflow/tfjs/blob/master/tfjs-backend-wasm/src/kernels/NonMaxSuppressionV3.ts and TF C++ version: https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/non-max-suppression-v5 I don't have a strong preference on whether the internal kernel name should be consistent with external APIs, as long as we have some consistency within our library. Maybe it makes sense for now, to keep our kernel names to be in sync with C version's kernel names, and keep our external API names to be in sync with Python version's API names? This helps us identify amount of feature parity at each level and help tracing reference.
this name sounds good, since we are going to register the kernel for these ops, it would be better to use the TF kernel name.
tfjs-core/src/backends/webgl/non_max_suppression_with_score.ts, line 38 at r3 (raw file):
registerKernel({ kernelName: 'NonMaxSuppressionWithScore',
It would be better to use the TF op name.
Agreed. Done. |
dsmilkov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice work! I left one small API suggestion. Otherwise LGTM!
Reviewed 5 of 10 files at r2, 5 of 5 files at r4.
Reviewable status:complete! 2 of 1 approvals obtained (waiting on @lina128, @nsthorat, and @pyu10055)
tfjs-core/src/ops/image_ops.ts, line 239 at r1 (raw file):
Previously, lina128 (Na Li) wrote…
Ah, really nice catch! Thank you Daniel! Changed output to 2. The numValidOutputs is only for V4, and for V5, the flag to pad the result to the maxOutputSize is always false, therefore, no need for this output. I made sure the unused tensor is disposed. Also changed to write the new Op using the modular pattern, that design is really smart, it makes it more flexible to name function than the interface method. Also explicitly registering Ops makes it more readable and more flexible to manage Ops. Thank you for letting me know this new pattern!
Awesome!
tfjs-core/src/ops/image_ops.ts, line 264 at r4 (raw file):
attrs) as Tensor[]; return {selectedIndices: result[0], selectedScores: result[1]};
tiny API suggestion: indices and scores to make it shorter since "selected" is implicit given what this method does.
Agreed. I used this naming mainly to keep in sync with Python API's naming: https://www.tensorflow.org/api_docs/python/tf/image/non_max_suppression_with_scores?version=stable |
…rn to create new op; Fix output to two instead of three
Because in the C implementation, the V3 has 1 output but the V5 has 3 outputs, we have to define different APIs for them. Otherwise, models that uses NonMaxSuppressionV5 may break because subsequent Op may need to access the second or the third output.
Changes made:
Note:
This change is