oidn-web 0.3.5 → 0.4.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 (122) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +140 -4
  3. package/benchmarks/compare.mjs +651 -0
  4. package/benchmarks/leak.mjs +255 -0
  5. package/benchmarks/results/before-spatial.json +391 -0
  6. package/benchmarks/results/before-spatial.md +47 -0
  7. package/benchmarks/results/int8-scan.json +2007 -0
  8. package/benchmarks/results/int8-scan.md +160 -0
  9. package/benchmarks/results/int8-w8a8-scan.json +2007 -0
  10. package/benchmarks/results/int8-w8a8-scan.md +160 -0
  11. package/benchmarks/results/int8-weight-channel.json +1413 -0
  12. package/benchmarks/results/int8-weight-channel.md +118 -0
  13. package/benchmarks/results/int8-weight-only.json +1437 -0
  14. package/benchmarks/results/int8-weight-only.md +118 -0
  15. package/benchmarks/results/kernel-webnn-final.json +1115 -0
  16. package/benchmarks/results/kernel-webnn-final.md +104 -0
  17. package/benchmarks/results/latest-optimized.json +375 -0
  18. package/benchmarks/results/latest-optimized.md +47 -0
  19. package/benchmarks/results/latest.json +391 -0
  20. package/benchmarks/results/latest.md +47 -0
  21. package/benchmarks/results/profile-baseline.json +331 -0
  22. package/benchmarks/results/profile-baseline.md +12 -0
  23. package/benchmarks/results/profile-conv2x.json +331 -0
  24. package/benchmarks/results/profile-conv2x.md +12 -0
  25. package/benchmarks/results/profile-fast-init.json +385 -0
  26. package/benchmarks/results/profile-fast-init.md +47 -0
  27. package/benchmarks/results/profile-fp16-fma.json +369 -0
  28. package/benchmarks/results/profile-fp16-fma.md +47 -0
  29. package/benchmarks/results/profile-fp16-tiled-decoder.json +369 -0
  30. package/benchmarks/results/profile-fp16-tiled-decoder.md +47 -0
  31. package/benchmarks/results/profile-fp16-tiled-encoder.json +369 -0
  32. package/benchmarks/results/profile-fp16-tiled-encoder.md +47 -0
  33. package/benchmarks/results/profile-fp16-unfused-pool.json +385 -0
  34. package/benchmarks/results/profile-fp16-unfused-pool.md +47 -0
  35. package/benchmarks/results/profile-input-major.json +347 -0
  36. package/benchmarks/results/profile-input-major.md +12 -0
  37. package/benchmarks/results/profile-k16.json +347 -0
  38. package/benchmarks/results/profile-k16.md +12 -0
  39. package/benchmarks/results/profile-k4.json +347 -0
  40. package/benchmarks/results/profile-k4.md +12 -0
  41. package/benchmarks/results/profile-pool-reuse.json +331 -0
  42. package/benchmarks/results/profile-pool-reuse.md +12 -0
  43. package/benchmarks/results/profile-precompiled.json +385 -0
  44. package/benchmarks/results/profile-precompiled.md +47 -0
  45. package/benchmarks/results/profile-static-channels.json +385 -0
  46. package/benchmarks/results/profile-static-channels.md +47 -0
  47. package/benchmarks/results/profile-static-io.json +385 -0
  48. package/benchmarks/results/profile-static-io.md +47 -0
  49. package/benchmarks/results/profile-tiled-conv.json +331 -0
  50. package/benchmarks/results/profile-tiled-conv.md +12 -0
  51. package/benchmarks/results/profile-tiled-decoder.json +347 -0
  52. package/benchmarks/results/profile-tiled-decoder.md +12 -0
  53. package/benchmarks/results/profile-tiled-matmul.json +331 -0
  54. package/benchmarks/results/profile-tiled-matmul.md +12 -0
  55. package/benchmarks/results/profile-unfused-decoder.json +379 -0
  56. package/benchmarks/results/profile-unfused-decoder.md +12 -0
  57. package/benchmarks/results/profile-unfused-pool.json +347 -0
  58. package/benchmarks/results/profile-unfused-pool.md +12 -0
  59. package/benchmarks/results/spatial-auto.json +575 -0
  60. package/benchmarks/results/spatial-auto.md +61 -0
  61. package/benchmarks/results/subgroup-smoke.json +1094 -0
  62. package/benchmarks/results/subgroup-smoke.md +104 -0
  63. package/benchmarks/results/webnn-smoke.json +739 -0
  64. package/benchmarks/results/webnn-smoke.md +76 -0
  65. package/dist/oidn.js +4166 -22580
  66. package/dist/oidn.umd.cjs +776 -5799
  67. package/lib/UNet.d.ts +66 -15
  68. package/lib/UNet.js +162 -257
  69. package/lib/UNet.js.map +1 -1
  70. package/lib/WGPUComputePass.d.ts +1 -1
  71. package/lib/WGPUComputePass.js +6 -4
  72. package/lib/WGPUComputePass.js.map +1 -1
  73. package/lib/backend.d.ts +8 -4
  74. package/lib/backend.js +36 -44
  75. package/lib/backend.js.map +1 -1
  76. package/lib/graphOptimizer.d.ts +54 -0
  77. package/lib/graphOptimizer.js +216 -0
  78. package/lib/graphOptimizer.js.map +1 -0
  79. package/lib/main.d.ts +33 -10
  80. package/lib/main.js +5 -0
  81. package/lib/main.js.map +1 -1
  82. package/lib/modelSpec.d.ts +80 -0
  83. package/lib/modelSpec.js +270 -0
  84. package/lib/modelSpec.js.map +1 -0
  85. package/lib/nativeUNet.d.ts +67 -0
  86. package/lib/nativeUNet.js +1735 -0
  87. package/lib/nativeUNet.js.map +1 -0
  88. package/lib/process.js +3 -0
  89. package/lib/process.js.map +1 -1
  90. package/lib/resourceTracker.d.ts +26 -0
  91. package/lib/resourceTracker.js +65 -0
  92. package/lib/resourceTracker.js.map +1 -0
  93. package/lib/tileScheduler.d.ts +33 -0
  94. package/lib/tileScheduler.js +86 -0
  95. package/lib/tileScheduler.js.map +1 -0
  96. package/lib/webnnUNet.d.ts +52 -0
  97. package/lib/webnnUNet.js +535 -0
  98. package/lib/webnnUNet.js.map +1 -0
  99. package/package.json +9 -5
  100. package/scripts/inspect-model.mjs +64 -0
  101. package/src/UNet.ts +236 -339
  102. package/src/WGPUComputePass.ts +6 -4
  103. package/src/backend.ts +42 -55
  104. package/src/graphOptimizer.ts +301 -0
  105. package/src/main.ts +71 -11
  106. package/src/modelSpec.ts +414 -0
  107. package/src/nativeUNet.ts +2256 -0
  108. package/src/process.ts +3 -1
  109. package/src/resourceTracker.ts +94 -0
  110. package/src/tileScheduler.ts +138 -0
  111. package/src/webnnUNet.ts +812 -0
  112. package/tests/modelSpec.test.mjs +128 -0
  113. package/tests/resourceLifecycle.test.mjs +383 -0
  114. package/tests/tileScheduler.test.mjs +90 -0
  115. package/lib/helper.d.ts +0 -4
  116. package/lib/helper.js +0 -33
  117. package/lib/helper.js.map +0 -1
  118. package/lib/kernels.d.ts +0 -1
  119. package/lib/kernels.js +0 -26
  120. package/lib/kernels.js.map +0 -1
  121. package/src/helper.ts +0 -43
  122. package/src/kernels.ts +0 -31
package/src/process.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { buffer, data } from '@tensorflow/tfjs';
2
1
  import { WGPUComputePass } from './WGPUComputePass';
3
2
 
4
3
  const a = 1.41283765e3;
@@ -490,5 +489,8 @@ else {
490
489
  dispose() {
491
490
  this._outputPass.dispose();
492
491
  this._inputPassAux.dispose();
492
+ this._inputPassColor.dispose();
493
+ // copyPass borrows outputPass's target and must not destroy it again.
494
+ this._copyPass.dispose(false);
493
495
  }
494
496
  }
@@ -0,0 +1,94 @@
1
+ export type OIDNResourceKind =
2
+ | 'gpu-buffer'
3
+ | 'gpu-query-set'
4
+ | 'ml-context'
5
+ | 'ml-graph'
6
+ | 'ml-tensor';
7
+
8
+ export interface OIDNResourceStats {
9
+ created: number;
10
+ destroyed: number;
11
+ live: number;
12
+ peakLive: number;
13
+ }
14
+
15
+ export interface OIDNResourceSnapshot {
16
+ live: number;
17
+ created: number;
18
+ destroyed: number;
19
+ peakLive: number;
20
+ pending: number;
21
+ byKind: Partial<Record<OIDNResourceKind, OIDNResourceStats>>;
22
+ }
23
+
24
+ interface MutableResourceStats extends OIDNResourceStats {
25
+ resources: Set<object>;
26
+ }
27
+
28
+ /**
29
+ * Counts explicitly owned browser resources. Keeping the ownership registry in
30
+ * production makes cache eviction and failure cleanup testable without relying
31
+ * on JS heap measurements, which do not include WebGPU allocations.
32
+ */
33
+ export class OIDNResourceTracker {
34
+ private _stats = new Map<OIDNResourceKind, MutableResourceStats>();
35
+
36
+ track<T extends object>(kind: OIDNResourceKind, resource: T): T {
37
+ let stats = this._stats.get(kind);
38
+ if (!stats) {
39
+ stats = {
40
+ created: 0,
41
+ destroyed: 0,
42
+ live: 0,
43
+ peakLive: 0,
44
+ resources: new Set()
45
+ };
46
+ this._stats.set(kind, stats);
47
+ }
48
+ if (stats.resources.has(resource)) return resource;
49
+ stats.resources.add(resource);
50
+ stats.created++;
51
+ stats.live++;
52
+ stats.peakLive = Math.max(stats.peakLive, stats.live);
53
+ return resource;
54
+ }
55
+
56
+ release(
57
+ kind: OIDNResourceKind,
58
+ resource: object | undefined,
59
+ destroy: () => void
60
+ ) {
61
+ if (!resource) return false;
62
+ const stats = this._stats.get(kind);
63
+ if (!stats?.resources.delete(resource)) return false;
64
+ try {
65
+ destroy();
66
+ } finally {
67
+ stats.destroyed++;
68
+ stats.live--;
69
+ }
70
+ return true;
71
+ }
72
+
73
+ snapshot(pending = 0): OIDNResourceSnapshot {
74
+ let live = 0;
75
+ let created = 0;
76
+ let destroyed = 0;
77
+ let peakLive = 0;
78
+ const byKind: OIDNResourceSnapshot['byKind'] = {};
79
+ for (const [kind, stats] of this._stats) {
80
+ const entry = {
81
+ created: stats.created,
82
+ destroyed: stats.destroyed,
83
+ live: stats.live,
84
+ peakLive: stats.peakLive
85
+ };
86
+ byKind[kind] = entry;
87
+ live += entry.live;
88
+ created += entry.created;
89
+ destroyed += entry.destroyed;
90
+ peakLive += entry.peakLive;
91
+ }
92
+ return { live, created, destroyed, peakLive, pending, byKind };
93
+ }
94
+ }
@@ -0,0 +1,138 @@
1
+ export interface DynamicTileOptions {
2
+ /** Smallest output tile edge. Defaults to 256. */
3
+ minTileSize?: number;
4
+ /** Initial output tile edge. Defaults to 384. */
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 128. */
9
+ adjustmentStep?: number;
10
+ }
11
+
12
+ export type DynamicTileSetting = boolean | DynamicTileOptions;
13
+
14
+ const defaultMinTileSize = 256;
15
+ const defaultInitialTileSize = 384;
16
+ const defaultTargetTileTimeMs = 16;
17
+ const defaultAdjustmentStep = 128;
18
+ export const OIDN_TILE_ALIGNMENT = 16;
19
+
20
+ function alignUp(value: number, alignment: number) {
21
+ return Math.ceil(value / alignment) * alignment;
22
+ }
23
+
24
+ function alignDown(value: number, alignment: number) {
25
+ return Math.floor(value / alignment) * alignment;
26
+ }
27
+
28
+ function clamp(value: number, min: number, max: number) {
29
+ return Math.min(Math.max(value, min), max);
30
+ }
31
+
32
+ function median(values: number[]) {
33
+ const sorted = [...values].sort((a, b) => a - b);
34
+ const middle = Math.floor(sorted.length / 2);
35
+ return sorted.length % 2
36
+ ? sorted[middle]
37
+ : (sorted[middle - 1] + sorted[middle]) / 2;
38
+ }
39
+
40
+ /** Fits one output dimension without exceeding the configured tile cap. */
41
+ export function fitTileDimension(imageSize: number, maxTileSize: number) {
42
+ return imageSize <= maxTileSize
43
+ ? Math.min(alignUp(imageSize, OIDN_TILE_ALIGNMENT), maxTileSize)
44
+ : maxTileSize;
45
+ }
46
+
47
+ /**
48
+ * Chooses an output tile size from completed GPU timings. A size change is
49
+ * applied to the next execution so one in-flight tiled pass keeps a stable
50
+ * overlap, allocation plan, and pipeline set.
51
+ */
52
+ export class DynamicTileController {
53
+ readonly enabled: boolean;
54
+ readonly maxTileSize: number;
55
+ readonly minTileSize: number;
56
+ readonly targetTileTimeMs: number;
57
+
58
+ private _tileSize: number;
59
+ private _adjustmentStep: number;
60
+
61
+ constructor(maxTileSize: number, setting: DynamicTileSetting = true) {
62
+ const options = typeof setting === 'object' ? setting : {};
63
+ this.enabled = setting !== false;
64
+ this.maxTileSize = Math.max(
65
+ OIDN_TILE_ALIGNMENT,
66
+ alignDown(maxTileSize, OIDN_TILE_ALIGNMENT)
67
+ );
68
+ this.minTileSize = clamp(
69
+ alignUp(options.minTileSize ?? defaultMinTileSize, OIDN_TILE_ALIGNMENT),
70
+ OIDN_TILE_ALIGNMENT,
71
+ this.maxTileSize
72
+ );
73
+ this.targetTileTimeMs = Math.max(
74
+ 1,
75
+ options.targetTileTimeMs ?? defaultTargetTileTimeMs
76
+ );
77
+ this._adjustmentStep = Math.max(
78
+ OIDN_TILE_ALIGNMENT,
79
+ alignUp(
80
+ options.adjustmentStep ?? defaultAdjustmentStep,
81
+ OIDN_TILE_ALIGNMENT
82
+ )
83
+ );
84
+ this._tileSize = this.enabled
85
+ ? clamp(
86
+ alignUp(
87
+ options.initialTileSize ?? defaultInitialTileSize,
88
+ OIDN_TILE_ALIGNMENT
89
+ ),
90
+ this.minTileSize,
91
+ this.maxTileSize
92
+ )
93
+ : this.maxTileSize;
94
+ }
95
+
96
+ get tileSize() {
97
+ return this._tileSize;
98
+ }
99
+
100
+ /** Returns true when the next execution should use a different tile size. */
101
+ observe(tileTimesMs: number[]) {
102
+ if (!this.enabled || tileTimesMs.length === 0) return false;
103
+
104
+ const validTimes = tileTimesMs.filter(
105
+ (duration) => Number.isFinite(duration) && duration >= 0
106
+ );
107
+ if (validTimes.length === 0) return false;
108
+
109
+ const observedTime = median(validTimes);
110
+ let nextTileSize = this._tileSize;
111
+ if (observedTime > this.targetTileTimeMs * 1.25) {
112
+ nextTileSize -= this._adjustmentStep;
113
+ } else if (observedTime < this.targetTileTimeMs * 0.65) {
114
+ nextTileSize += this._adjustmentStep;
115
+ }
116
+
117
+ nextTileSize = clamp(
118
+ alignUp(nextTileSize, OIDN_TILE_ALIGNMENT),
119
+ this.minTileSize,
120
+ this.maxTileSize
121
+ );
122
+ if (nextTileSize === this._tileSize) return false;
123
+ this._tileSize = nextTileSize;
124
+ return true;
125
+ }
126
+ }
127
+
128
+ /** Waits for all work submitted before this call and tolerates device loss. */
129
+ export async function waitForSubmittedGPUWork(
130
+ queue: Pick<GPUQueue, 'onSubmittedWorkDone'>
131
+ ) {
132
+ try {
133
+ await queue.onSubmittedWorkDone();
134
+ } catch {
135
+ // tileExecute has no error callback. Preserve its completion/cancellation
136
+ // behavior and let the owning GPUDevice report device loss separately.
137
+ }
138
+ }