-
Notifications
You must be signed in to change notification settings - Fork 14
Performance: Optimize tensor disposal loop allocations #158
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -102,6 +102,7 @@ export class ParakeetModel { | |||||||||||||||||||||||||||||||||
| this._targetLenTensor = new ort.Tensor('int32', this._targetLenArray, [1]); | ||||||||||||||||||||||||||||||||||
| this._encoderFrameBuffer = null; // Will be allocated when we know the dimension D | ||||||||||||||||||||||||||||||||||
| this._encoderFrameTensor = null; // Will be allocated when we know D | ||||||||||||||||||||||||||||||||||
| this._seenOutputs = []; // Reusable array for tensor disposal tracking | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Incremental decode cache: stores decoder state at the end of the prefix | ||||||||||||||||||||||||||||||||||
| // keyed by a caller-provided cacheKey. This lets us skip decoding the | ||||||||||||||||||||||||||||||||||
|
|
@@ -323,10 +324,23 @@ export class ParakeetModel { | |||||||||||||||||||||||||||||||||
| const logits = out['outputs']; | ||||||||||||||||||||||||||||||||||
| const outputState1 = out['output_states_1']; | ||||||||||||||||||||||||||||||||||
| const outputState2 = out['output_states_2']; | ||||||||||||||||||||||||||||||||||
| const seenOutputs = new Set(); | ||||||||||||||||||||||||||||||||||
| for (const value of Object.values(out)) { | ||||||||||||||||||||||||||||||||||
| if (!value || typeof value.dispose !== 'function' || seenOutputs.has(value)) continue; | ||||||||||||||||||||||||||||||||||
| seenOutputs.add(value); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Performance: Avoid Object.values and per-frame Set allocations in this hot loop. | ||||||||||||||||||||||||||||||||||
| let seenCount = 0; | ||||||||||||||||||||||||||||||||||
| for (const key in out) { | ||||||||||||||||||||||||||||||||||
| const value = out[key]; | ||||||||||||||||||||||||||||||||||
| if (!value || typeof value.dispose !== 'function') continue; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let alreadySeen = false; | ||||||||||||||||||||||||||||||||||
| for (let j = 0; j < seenCount; j++) { | ||||||||||||||||||||||||||||||||||
| if (this._seenOutputs[j] === value) { | ||||||||||||||||||||||||||||||||||
| alreadySeen = true; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (alreadySeen) continue; | ||||||||||||||||||||||||||||||||||
| this._seenOutputs[seenCount++] = value; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (value === logits || value === outputState1 || value === outputState2) continue; | ||||||||||||||||||||||||||||||||||
| value.dispose(); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+329
to
346
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a persistent array Consider nulling out the used slots in |
||||||||||||||||||||||||||||||||||
|
|
@@ -339,12 +353,20 @@ export class ParakeetModel { | |||||||||||||||||||||||||||||||||
| const failDecoderStep = (message) => { | ||||||||||||||||||||||||||||||||||
| logits?.dispose?.(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const disposed = new Set(); | ||||||||||||||||||||||||||||||||||
| let disposedCount = 0; | ||||||||||||||||||||||||||||||||||
| const disposeUniqueState = (state) => { | ||||||||||||||||||||||||||||||||||
| if (!state) return; | ||||||||||||||||||||||||||||||||||
| for (const tensor of [state.state1, state.state2]) { | ||||||||||||||||||||||||||||||||||
| if (!tensor || tensor === this._combState1 || tensor === this._combState2 || disposed.has(tensor)) continue; | ||||||||||||||||||||||||||||||||||
| disposed.add(tensor); | ||||||||||||||||||||||||||||||||||
| if (!tensor || tensor === this._combState1 || tensor === this._combState2) continue; | ||||||||||||||||||||||||||||||||||
| let alreadyDisposed = false; | ||||||||||||||||||||||||||||||||||
| for (let i = 0; i < disposedCount; i++) { | ||||||||||||||||||||||||||||||||||
| if (this._seenOutputs[i] === tensor) { | ||||||||||||||||||||||||||||||||||
| alreadyDisposed = true; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (alreadyDisposed) continue; | ||||||||||||||||||||||||||||||||||
| this._seenOutputs[disposedCount++] = tensor; | ||||||||||||||||||||||||||||||||||
| tensor.dispose?.(); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
@@ -683,10 +705,22 @@ export class ParakeetModel { | |||||||||||||||||||||||||||||||||
| const s = performance.now(); | ||||||||||||||||||||||||||||||||||
| const encOut = await this.encoderSession.run({ audio_signal: input, length: lenTensor }); | ||||||||||||||||||||||||||||||||||
| tEncode = performance.now() - s; | ||||||||||||||||||||||||||||||||||
| enc = encOut['outputs'] ?? Object.values(encOut)[0]; | ||||||||||||||||||||||||||||||||||
| enc = encOut['outputs']; | ||||||||||||||||||||||||||||||||||
| if (enc === undefined) { | ||||||||||||||||||||||||||||||||||
| for (const key in encOut) { | ||||||||||||||||||||||||||||||||||
| enc = encOut[key]; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
706
to
+713
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Using If
Suggested change
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| const encOut = await this.encoderSession.run({ audio_signal: input, length: lenTensor }); | ||||||||||||||||||||||||||||||||||
| enc = encOut['outputs'] ?? Object.values(encOut)[0]; | ||||||||||||||||||||||||||||||||||
| enc = encOut['outputs']; | ||||||||||||||||||||||||||||||||||
| if (enc === undefined) { | ||||||||||||||||||||||||||||||||||
| for (const key in encOut) { | ||||||||||||||||||||||||||||||||||
| enc = encOut[key]; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||
| // Dispose per-call input tensors even when encoder execution fails. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
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.
🧩 Analysis chain
🏁 Script executed:
# First, let's check if the file exists and view the specific lines mentioned wc -l src/parakeet.jsRepository: ysdede/parakeet.js
Length of output: 82
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 567
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 526
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 1631
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 1056
🏁 Script executed:
# Check git history to see if this was changed from Object.values git log --oneline --all -20 -- src/parakeet.jsRepository: ysdede/parakeet.js
Length of output: 1309
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 91
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 7726
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 149
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 44
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 44
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 963
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 2301
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 80
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 1806
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 1239
🏁 Script executed:
Repository: ysdede/parakeet.js
Length of output: 44
Add
Object.hasOwn()guards to match previous iteration semantics.The
for...inloop at lines 330–345 and the encoder fallback loops at lines 708–714 and 717–723 walk the entire prototype chain, whereas the originalObject.values(...)only iterated own properties. While ONNX Runtime session results are plain objects with only own properties, adding an own-property guard preserves the original contract and defensive semantics without sacrificing the performance optimization.Suggested fix
for (const key in out) { + if (!Object.hasOwn(out, key)) continue; const value = out[key]; if (!value || typeof value.dispose !== 'function') continue; let alreadySeen = false; for (let j = 0; j < seenCount; j++) { @@ enc = encOut['outputs']; if (enc === undefined) { for (const key in encOut) { + if (!Object.hasOwn(encOut, key)) continue; enc = encOut[key]; break; } } @@ enc = encOut['outputs']; if (enc === undefined) { for (const key in encOut) { + if (!Object.hasOwn(encOut, key)) continue; enc = encOut[key]; break; } }Also applies to: 708–714, 717–723
🤖 Prompt for AI Agents