oidn-webgpu 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/LICENSE +21 -0
  3. package/README.md +89 -2
  4. package/dist/oidn.js +6140 -0
  5. package/dist/oidn.umd.cjs +1513 -0
  6. package/lib/UNet.d.ts +161 -0
  7. package/lib/UNet.js +536 -0
  8. package/lib/UNet.js.map +1 -0
  9. package/lib/WGPUComputePass.d.ts +62 -0
  10. package/lib/WGPUComputePass.js +278 -0
  11. package/lib/WGPUComputePass.js.map +1 -0
  12. package/lib/WGPUFullQuadPass.d.ts +50 -0
  13. package/lib/WGPUFullQuadPass.js +261 -0
  14. package/lib/WGPUFullQuadPass.js.map +1 -0
  15. package/lib/backend.d.ts +1 -0
  16. package/lib/backend.js +39 -0
  17. package/lib/backend.js.map +1 -0
  18. package/lib/finalRgbShader.d.ts +13 -0
  19. package/lib/finalRgbShader.js +160 -0
  20. package/lib/finalRgbShader.js.map +1 -0
  21. package/lib/gpuTypes.d.ts +158 -0
  22. package/lib/gpuTypes.js +27 -0
  23. package/lib/gpuTypes.js.map +1 -0
  24. package/lib/graphOptimizer.d.ts +54 -0
  25. package/lib/graphOptimizer.js +216 -0
  26. package/lib/graphOptimizer.js.map +1 -0
  27. package/lib/hdr.d.ts +31 -0
  28. package/lib/hdr.js +340 -0
  29. package/lib/hdr.js.map +1 -0
  30. package/lib/hdrTransfer.d.ts +22 -0
  31. package/lib/hdrTransfer.js +128 -0
  32. package/lib/hdrTransfer.js.map +1 -0
  33. package/lib/main.d.ts +57 -0
  34. package/lib/main.js +24 -0
  35. package/lib/main.js.map +1 -0
  36. package/lib/modelSpec.d.ts +80 -0
  37. package/lib/modelSpec.js +270 -0
  38. package/lib/modelSpec.js.map +1 -0
  39. package/lib/native/denoise.d.ts +25 -0
  40. package/lib/native/denoise.js +223 -0
  41. package/lib/native/denoise.js.map +1 -0
  42. package/lib/native/program.d.ts +53 -0
  43. package/lib/native/program.js +310 -0
  44. package/lib/native/program.js.map +1 -0
  45. package/lib/native/recordingDevice.d.ts +125 -0
  46. package/lib/native/recordingDevice.js +247 -0
  47. package/lib/native/recordingDevice.js.map +1 -0
  48. package/lib/nativeUNet.d.ts +25 -0
  49. package/lib/nativeUNet.js +184 -0
  50. package/lib/nativeUNet.js.map +1 -0
  51. package/lib/nativeUNetCore.d.ts +202 -0
  52. package/lib/nativeUNetCore.js +2945 -0
  53. package/lib/nativeUNetCore.js.map +1 -0
  54. package/lib/process.d.ts +37 -0
  55. package/lib/process.js +328 -0
  56. package/lib/process.js.map +1 -0
  57. package/lib/resourceTracker.d.ts +26 -0
  58. package/lib/resourceTracker.js +65 -0
  59. package/lib/resourceTracker.js.map +1 -0
  60. package/lib/sanitize.d.ts +15 -0
  61. package/lib/sanitize.js +28 -0
  62. package/lib/sanitize.js.map +1 -0
  63. package/lib/tileScheduler.d.ts +61 -0
  64. package/lib/tileScheduler.js +199 -0
  65. package/lib/tileScheduler.js.map +1 -0
  66. package/lib/tza.d.ts +15 -0
  67. package/lib/tza.js +114 -0
  68. package/lib/tza.js.map +1 -0
  69. package/lib/webnnUNet.d.ts +52 -0
  70. package/lib/webnnUNet.js +535 -0
  71. package/lib/webnnUNet.js.map +1 -0
  72. package/package.json +43 -4
  73. package/src/UNet.ts +861 -0
  74. package/src/WGPUComputePass.ts +382 -0
  75. package/src/WGPUFullQuadPass.ts +348 -0
  76. package/src/backend.ts +42 -0
  77. package/src/finalRgbShader.ts +186 -0
  78. package/src/gpuTypes.ts +179 -0
  79. package/src/graphOptimizer.ts +301 -0
  80. package/src/hdr.ts +398 -0
  81. package/src/hdrTransfer.ts +169 -0
  82. package/src/main.ts +126 -0
  83. package/src/modelSpec.ts +414 -0
  84. package/src/native/core-profile.json +40 -0
  85. package/src/native/denoise.ts +268 -0
  86. package/src/native/program.ts +326 -0
  87. package/src/native/recordingDevice.ts +341 -0
  88. package/src/nativeUNet.ts +269 -0
  89. package/src/nativeUNetCore.ts +3735 -0
  90. package/src/process.ts +368 -0
  91. package/src/resourceTracker.ts +94 -0
  92. package/src/sanitize.ts +29 -0
  93. package/src/tileScheduler.ts +330 -0
  94. package/src/tza.ts +136 -0
  95. package/src/webnnUNet.ts +812 -0
@@ -0,0 +1,330 @@
1
+ export interface DynamicTileOptions {
2
+ /** Smallest output tile edge. Defaults to 256. */
3
+ minTileSize?: number;
4
+ /** Initial maximum output tile edge. Defaults to 432. */
5
+ initialTileSize?: number;
6
+ /** Desired upper bound for one tile's GPU latency. Defaults to 16 ms. */
7
+ targetTileTimeMs?: number;
8
+ /** Amount added or removed after each completed execution. Defaults to 16. */
9
+ adjustmentStep?: number;
10
+ }
11
+
12
+ export type DynamicTileSetting = boolean | DynamicTileOptions;
13
+
14
+ const defaultMinTileSize = 256;
15
+ const defaultInitialTileSize = 432;
16
+ const defaultTargetTileTimeMs = 16;
17
+ const defaultAdjustmentStep = 16;
18
+ export const OIDN_TILE_ALIGNMENT = 16;
19
+
20
+ export interface TileRect {
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ }
26
+
27
+ export interface PlannedTile {
28
+ column: number;
29
+ row: number;
30
+ input: TileRect;
31
+ output: TileRect;
32
+ }
33
+
34
+ export interface TilePlan {
35
+ columns: number;
36
+ rows: number;
37
+ overlap: number;
38
+ maxOutputWidth: number;
39
+ maxOutputHeight: number;
40
+ inputPixelCount: number;
41
+ inputShapeCount: number;
42
+ tiles: PlannedTile[];
43
+ }
44
+
45
+ function alignUp(value: number, alignment: number) {
46
+ return Math.ceil(value / alignment) * alignment;
47
+ }
48
+
49
+ function alignDown(value: number, alignment: number) {
50
+ return Math.floor(value / alignment) * alignment;
51
+ }
52
+
53
+ function partitionRange(length: number, parts: number, index: number) {
54
+ const start = Math.round(index * length / parts);
55
+ const end = Math.round((index + 1) * length / parts);
56
+ return { start, end };
57
+ }
58
+
59
+ function alignRangeWithinImage(
60
+ start: number,
61
+ end: number,
62
+ imageSize: number,
63
+ minimumSize = 0
64
+ ) {
65
+ const currentSize = end - start;
66
+ const alignedSize = Math.max(
67
+ minimumSize,
68
+ alignUp(currentSize, OIDN_TILE_ALIGNMENT)
69
+ );
70
+ let alignedStart = start - Math.floor((alignedSize - currentSize) / 2);
71
+ // At an image edge, keep the padded portion on the right/bottom. Input
72
+ // readers replicate the nearest image edge into that portion, while output
73
+ // tiles remain strictly inside the original image.
74
+ alignedStart = clamp(alignedStart, 0, Math.max(0, imageSize - alignedSize));
75
+ return { start: alignedStart, end: alignedStart + alignedSize };
76
+ }
77
+
78
+ /**
79
+ * Splits an image into balanced rectangular output regions. Only sides that
80
+ * touch another tile receive a halo; image edges rely on the model's normal
81
+ * boundary behavior instead of doing redundant off-edge work.
82
+ */
83
+ export function planTileGrid(
84
+ width: number,
85
+ height: number,
86
+ maxTileSize: number,
87
+ overlap: number
88
+ ): TilePlan {
89
+ if (!Number.isInteger(width) || width <= 0 ||
90
+ !Number.isInteger(height) || height <= 0) {
91
+ throw new Error('Tile grid dimensions must be positive integers');
92
+ }
93
+ if (!Number.isFinite(maxTileSize) || maxTileSize <= 0) {
94
+ throw new Error('Maximum tile size must be positive');
95
+ }
96
+ if (!Number.isFinite(overlap) || overlap < 0) {
97
+ throw new Error('Tile overlap must be non-negative');
98
+ }
99
+
100
+ const alignedMaximum = Math.max(
101
+ OIDN_TILE_ALIGNMENT,
102
+ alignDown(maxTileSize, OIDN_TILE_ALIGNMENT)
103
+ );
104
+ const alignedOverlap = alignUp(overlap, OIDN_TILE_ALIGNMENT);
105
+ const columns = Math.max(1, Math.ceil(width / alignedMaximum));
106
+ const rows = Math.max(1, Math.ceil(height / alignedMaximum));
107
+ const tiles: PlannedTile[] = [];
108
+ let maxOutputWidth = 0;
109
+ let maxOutputHeight = 0;
110
+
111
+ for (let row = 0; row < rows; row++) {
112
+ const outputY = partitionRange(height, rows, row);
113
+ for (let column = 0; column < columns; column++) {
114
+ const outputX = partitionRange(width, columns, column);
115
+ const desiredInputX = {
116
+ start: column === 0
117
+ ? outputX.start
118
+ : Math.max(0, outputX.start - alignedOverlap),
119
+ end: column === columns - 1
120
+ ? outputX.end
121
+ : Math.min(width, outputX.end + alignedOverlap)
122
+ };
123
+ const desiredInputY = {
124
+ start: row === 0
125
+ ? outputY.start
126
+ : Math.max(0, outputY.start - alignedOverlap),
127
+ end: row === rows - 1
128
+ ? outputY.end
129
+ : Math.min(height, outputY.end + alignedOverlap)
130
+ };
131
+ const inputX = alignRangeWithinImage(
132
+ desiredInputX.start,
133
+ desiredInputX.end,
134
+ width
135
+ );
136
+ const inputY = alignRangeWithinImage(
137
+ desiredInputY.start,
138
+ desiredInputY.end,
139
+ height
140
+ );
141
+ const output: TileRect = {
142
+ x: outputX.start,
143
+ y: outputY.start,
144
+ width: outputX.end - outputX.start,
145
+ height: outputY.end - outputY.start
146
+ };
147
+ const input: TileRect = {
148
+ x: inputX.start,
149
+ y: inputY.start,
150
+ width: inputX.end - inputX.start,
151
+ height: inputY.end - inputY.start
152
+ };
153
+ maxOutputWidth = Math.max(maxOutputWidth, output.width);
154
+ maxOutputHeight = Math.max(maxOutputHeight, output.height);
155
+ tiles.push({ column, row, input, output });
156
+ }
157
+ }
158
+
159
+ const inputShapes = () => new Set(
160
+ tiles.map(({ input }) => `${input.width}x${input.height}`)
161
+ );
162
+ if (inputShapes().size > 2) {
163
+ const maximumInputWidth = Math.max(
164
+ ...tiles.map(({ input }) => input.width)
165
+ );
166
+ const maximumInputHeight = Math.max(
167
+ ...tiles.map(({ input }) => input.height)
168
+ );
169
+ const uniformWidthPixelCount = tiles.reduce(
170
+ (sum, { input }) => sum + maximumInputWidth * input.height,
171
+ 0
172
+ );
173
+ const uniformHeightPixelCount = tiles.reduce(
174
+ (sum, { input }) => sum + input.width * maximumInputHeight,
175
+ 0
176
+ );
177
+ if (uniformWidthPixelCount <= uniformHeightPixelCount) {
178
+ for (const tile of tiles) {
179
+ const range = alignRangeWithinImage(
180
+ tile.input.x,
181
+ tile.input.x + tile.input.width,
182
+ width,
183
+ maximumInputWidth
184
+ );
185
+ tile.input.x = range.start;
186
+ tile.input.width = range.end - range.start;
187
+ }
188
+ } else {
189
+ for (const tile of tiles) {
190
+ const range = alignRangeWithinImage(
191
+ tile.input.y,
192
+ tile.input.y + tile.input.height,
193
+ height,
194
+ maximumInputHeight
195
+ );
196
+ tile.input.y = range.start;
197
+ tile.input.height = range.end - range.start;
198
+ }
199
+ }
200
+ }
201
+
202
+ const finalInputShapes = inputShapes();
203
+ const inputPixelCount = tiles.reduce(
204
+ (sum, { input }) => sum + input.width * input.height,
205
+ 0
206
+ );
207
+
208
+ return {
209
+ columns,
210
+ rows,
211
+ overlap: alignedOverlap,
212
+ maxOutputWidth,
213
+ maxOutputHeight,
214
+ inputPixelCount,
215
+ inputShapeCount: finalInputShapes.size,
216
+ tiles
217
+ };
218
+ }
219
+
220
+ function clamp(value: number, min: number, max: number) {
221
+ return Math.min(Math.max(value, min), max);
222
+ }
223
+
224
+ function percentile(values: number[], fraction: number) {
225
+ const sorted = [...values].sort((a, b) => a - b);
226
+ return sorted[Math.ceil(sorted.length * fraction) - 1];
227
+ }
228
+
229
+ /** Fits one output dimension without exceeding the configured tile cap. */
230
+ export function fitTileDimension(imageSize: number, maxTileSize: number) {
231
+ return imageSize <= maxTileSize
232
+ ? Math.min(alignUp(imageSize, OIDN_TILE_ALIGNMENT), maxTileSize)
233
+ : maxTileSize;
234
+ }
235
+
236
+ /**
237
+ * Chooses an output tile size from completed GPU timings. A size change is
238
+ * applied to the next execution so one in-flight tiled pass keeps a stable
239
+ * overlap, allocation plan, and pipeline set.
240
+ */
241
+ export class DynamicTileController {
242
+ readonly enabled: boolean;
243
+ readonly maxTileSize: number;
244
+ readonly minTileSize: number;
245
+ readonly targetTileTimeMs: number;
246
+
247
+ private _tileSize: number;
248
+ private _adjustmentStep: number;
249
+ private _smoothedTileTimeMs?: number;
250
+ private _completeExecutionsSinceChange = 0;
251
+
252
+ constructor(maxTileSize: number, setting: DynamicTileSetting = true) {
253
+ const options = typeof setting === 'object' ? setting : {};
254
+ this.enabled = setting !== false;
255
+ this.maxTileSize = Math.max(
256
+ OIDN_TILE_ALIGNMENT,
257
+ alignDown(maxTileSize, OIDN_TILE_ALIGNMENT)
258
+ );
259
+ this.minTileSize = clamp(
260
+ alignUp(options.minTileSize ?? defaultMinTileSize, OIDN_TILE_ALIGNMENT),
261
+ OIDN_TILE_ALIGNMENT,
262
+ this.maxTileSize
263
+ );
264
+ this.targetTileTimeMs = Math.max(
265
+ 1,
266
+ options.targetTileTimeMs ?? defaultTargetTileTimeMs
267
+ );
268
+ this._adjustmentStep = Math.max(
269
+ OIDN_TILE_ALIGNMENT,
270
+ alignUp(
271
+ options.adjustmentStep ?? defaultAdjustmentStep,
272
+ OIDN_TILE_ALIGNMENT
273
+ )
274
+ );
275
+ this._tileSize = this.enabled
276
+ ? clamp(
277
+ alignUp(
278
+ options.initialTileSize ?? defaultInitialTileSize,
279
+ OIDN_TILE_ALIGNMENT
280
+ ),
281
+ this.minTileSize,
282
+ this.maxTileSize
283
+ )
284
+ : this.maxTileSize;
285
+ }
286
+
287
+ get tileSize() {
288
+ return this._tileSize;
289
+ }
290
+
291
+ /** Returns true when the next execution should use a different tile size. */
292
+ observe(tileTimesMs: number[]) {
293
+ if (!this.enabled || tileTimesMs.length === 0) return false;
294
+
295
+ const validTimes = tileTimesMs.filter(
296
+ (duration) => Number.isFinite(duration) && duration >= 0
297
+ );
298
+ if (validTimes.length === 0) return false;
299
+
300
+ // The first tile may still pay one-time GPU residency costs even after
301
+ // shape preparation. It should not make a stable layout look too slow.
302
+ const representativeTimes = validTimes.length >= 3
303
+ ? validTimes.slice(1)
304
+ : validTimes;
305
+ const observedTime = percentile(representativeTimes, 0.75);
306
+ this._smoothedTileTimeMs = this._smoothedTileTimeMs === undefined
307
+ ? observedTime
308
+ : this._smoothedTileTimeMs * 0.65 + observedTime * 0.35;
309
+ this._completeExecutionsSinceChange++;
310
+ if (this._completeExecutionsSinceChange < 2) return false;
311
+
312
+ let nextTileSize = this._tileSize;
313
+ if (this._smoothedTileTimeMs > this.targetTileTimeMs * 1.25) {
314
+ nextTileSize -= this._adjustmentStep;
315
+ } else if (this._smoothedTileTimeMs < this.targetTileTimeMs * 0.65) {
316
+ nextTileSize += this._adjustmentStep;
317
+ }
318
+
319
+ nextTileSize = clamp(
320
+ alignUp(nextTileSize, OIDN_TILE_ALIGNMENT),
321
+ this.minTileSize,
322
+ this.maxTileSize
323
+ );
324
+ if (nextTileSize === this._tileSize) return false;
325
+ this._tileSize = nextTileSize;
326
+ this._smoothedTileTimeMs = undefined;
327
+ this._completeExecutionsSinceChange = 0;
328
+ return true;
329
+ }
330
+ }
package/src/tza.ts ADDED
@@ -0,0 +1,136 @@
1
+ // Copyright 2018 Intel Corporation
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Helper types and functions
5
+ export type TensorLayout = 'x' | 'oihw';
6
+
7
+ export type DataType = 'Float32' | 'Float16';
8
+
9
+ export class TensorDesc {
10
+ dims: number[] = [];
11
+ paddedDims: number[] = [];
12
+ layout: TensorLayout = 'x';
13
+ dataType: DataType = 'Float32';
14
+
15
+ getByteSize(): number {
16
+ let size = 1;
17
+ for (const dim of this.paddedDims) {
18
+ size *= dim;
19
+ }
20
+ if (this.dataType === 'Float32') {
21
+ size *= 4;
22
+ } else if (this.dataType === 'Float16') {
23
+ size *= 2;
24
+ }
25
+ return size;
26
+ }
27
+ }
28
+
29
+ export class HostTensor {
30
+ constructor(public desc: TensorDesc, public data: Uint8Array) {}
31
+ }
32
+
33
+ class DataViewReader {
34
+ offset: number = 0;
35
+
36
+ constructor(private _view: DataView) {}
37
+
38
+ read(size: number): number {
39
+ const view = this._view;
40
+ const offset = this.offset;
41
+ this.offset += size;
42
+ switch (size) {
43
+ case 1:
44
+ return view.getUint8(offset);
45
+ case 2:
46
+ return view.getUint16(offset, true);
47
+ case 4:
48
+ return view.getUint32(offset, true);
49
+ case 8:
50
+ // TODO
51
+ return Number(view.getBigUint64(offset, true));
52
+ default:
53
+ throw new Error('unsupported read size');
54
+ }
55
+ }
56
+ }
57
+
58
+ export function parseTZA(buffer: ArrayBuffer): Map<string, HostTensor> {
59
+ const input = new Uint8Array(buffer);
60
+ const viewReader = new DataViewReader(new DataView(buffer));
61
+
62
+ // Parse the magic value
63
+ const magic = viewReader.read(2);
64
+ if (magic !== 0x41d7) {
65
+ throw new Error('invalid or corrupted weights blob');
66
+ }
67
+
68
+ // Parse the version
69
+ const majorVersion = viewReader.read(1);
70
+ const minorVersion = viewReader.read(1);
71
+ if (majorVersion !== 2) {
72
+ throw new Error('unsupported weights blob version');
73
+ }
74
+
75
+ // Parse the table offset and jump to the table
76
+ const tableOffset = viewReader.read(8);
77
+ viewReader.offset = tableOffset;
78
+
79
+ // Parse the number of tensors
80
+ const numTensors = viewReader.read(4);
81
+
82
+ // Parse the tensors
83
+ const tensorMap = new Map<string, HostTensor>();
84
+ for (let i = 0; i < numTensors; ++i) {
85
+ const tensorDesc = new TensorDesc();
86
+
87
+ // Parse the name
88
+ const nameLen = viewReader.read(2);
89
+ const name = new TextDecoder().decode(
90
+ input.subarray(viewReader.offset, viewReader.offset + nameLen)
91
+ );
92
+ viewReader.offset += nameLen;
93
+
94
+ // Parse the number of dimensions
95
+ const ndims = viewReader.read(1);
96
+
97
+ // Parse the shape of the tensor
98
+ for (let j = 0; j < ndims; ++j) {
99
+ tensorDesc.dims.push(viewReader.read(4));
100
+ }
101
+
102
+ tensorDesc.paddedDims = [...tensorDesc.dims];
103
+
104
+ // Parse the layout of the tensor
105
+ const layoutStr = new TextDecoder().decode(
106
+ input.subarray(viewReader.offset, viewReader.offset + ndims)
107
+ );
108
+ if (layoutStr === 'oihw') {
109
+ tensorDesc.layout = 'oihw';
110
+ }
111
+
112
+ viewReader.offset += ndims;
113
+
114
+ // Parse the data type of the tensor
115
+ const dataType = String.fromCharCode(viewReader.read(1));
116
+ if (dataType === 'f') {
117
+ tensorDesc.dataType = 'Float32';
118
+ } else if (dataType === 'h') {
119
+ tensorDesc.dataType = 'Float16';
120
+ } else {
121
+ throw new Error('invalid tensor data type');
122
+ }
123
+
124
+ // Parse the offset to the tensor data
125
+ const tensorOffset = viewReader.read(8);
126
+ const tensorData = input.slice(
127
+ tensorOffset,
128
+ tensorOffset + tensorDesc.getByteSize()
129
+ );
130
+
131
+ // Add the tensor to the map
132
+ tensorMap.set(name, new HostTensor(tensorDesc, tensorData));
133
+ }
134
+
135
+ return tensorMap;
136
+ }