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.
- package/CHANGELOG.md +49 -0
- package/README.md +140 -4
- package/benchmarks/compare.mjs +651 -0
- package/benchmarks/leak.mjs +255 -0
- package/benchmarks/results/before-spatial.json +391 -0
- package/benchmarks/results/before-spatial.md +47 -0
- package/benchmarks/results/int8-scan.json +2007 -0
- package/benchmarks/results/int8-scan.md +160 -0
- package/benchmarks/results/int8-w8a8-scan.json +2007 -0
- package/benchmarks/results/int8-w8a8-scan.md +160 -0
- package/benchmarks/results/int8-weight-channel.json +1413 -0
- package/benchmarks/results/int8-weight-channel.md +118 -0
- package/benchmarks/results/int8-weight-only.json +1437 -0
- package/benchmarks/results/int8-weight-only.md +118 -0
- package/benchmarks/results/kernel-webnn-final.json +1115 -0
- package/benchmarks/results/kernel-webnn-final.md +104 -0
- package/benchmarks/results/latest-optimized.json +375 -0
- package/benchmarks/results/latest-optimized.md +47 -0
- package/benchmarks/results/latest.json +391 -0
- package/benchmarks/results/latest.md +47 -0
- package/benchmarks/results/profile-baseline.json +331 -0
- package/benchmarks/results/profile-baseline.md +12 -0
- package/benchmarks/results/profile-conv2x.json +331 -0
- package/benchmarks/results/profile-conv2x.md +12 -0
- package/benchmarks/results/profile-fast-init.json +385 -0
- package/benchmarks/results/profile-fast-init.md +47 -0
- package/benchmarks/results/profile-fp16-fma.json +369 -0
- package/benchmarks/results/profile-fp16-fma.md +47 -0
- package/benchmarks/results/profile-fp16-tiled-decoder.json +369 -0
- package/benchmarks/results/profile-fp16-tiled-decoder.md +47 -0
- package/benchmarks/results/profile-fp16-tiled-encoder.json +369 -0
- package/benchmarks/results/profile-fp16-tiled-encoder.md +47 -0
- package/benchmarks/results/profile-fp16-unfused-pool.json +385 -0
- package/benchmarks/results/profile-fp16-unfused-pool.md +47 -0
- package/benchmarks/results/profile-input-major.json +347 -0
- package/benchmarks/results/profile-input-major.md +12 -0
- package/benchmarks/results/profile-k16.json +347 -0
- package/benchmarks/results/profile-k16.md +12 -0
- package/benchmarks/results/profile-k4.json +347 -0
- package/benchmarks/results/profile-k4.md +12 -0
- package/benchmarks/results/profile-pool-reuse.json +331 -0
- package/benchmarks/results/profile-pool-reuse.md +12 -0
- package/benchmarks/results/profile-precompiled.json +385 -0
- package/benchmarks/results/profile-precompiled.md +47 -0
- package/benchmarks/results/profile-static-channels.json +385 -0
- package/benchmarks/results/profile-static-channels.md +47 -0
- package/benchmarks/results/profile-static-io.json +385 -0
- package/benchmarks/results/profile-static-io.md +47 -0
- package/benchmarks/results/profile-tiled-conv.json +331 -0
- package/benchmarks/results/profile-tiled-conv.md +12 -0
- package/benchmarks/results/profile-tiled-decoder.json +347 -0
- package/benchmarks/results/profile-tiled-decoder.md +12 -0
- package/benchmarks/results/profile-tiled-matmul.json +331 -0
- package/benchmarks/results/profile-tiled-matmul.md +12 -0
- package/benchmarks/results/profile-unfused-decoder.json +379 -0
- package/benchmarks/results/profile-unfused-decoder.md +12 -0
- package/benchmarks/results/profile-unfused-pool.json +347 -0
- package/benchmarks/results/profile-unfused-pool.md +12 -0
- package/benchmarks/results/spatial-auto.json +575 -0
- package/benchmarks/results/spatial-auto.md +61 -0
- package/benchmarks/results/subgroup-smoke.json +1094 -0
- package/benchmarks/results/subgroup-smoke.md +104 -0
- package/benchmarks/results/webnn-smoke.json +739 -0
- package/benchmarks/results/webnn-smoke.md +76 -0
- package/dist/oidn.js +4166 -22580
- package/dist/oidn.umd.cjs +776 -5799
- package/lib/UNet.d.ts +66 -15
- package/lib/UNet.js +162 -257
- package/lib/UNet.js.map +1 -1
- package/lib/WGPUComputePass.d.ts +1 -1
- package/lib/WGPUComputePass.js +6 -4
- package/lib/WGPUComputePass.js.map +1 -1
- package/lib/backend.d.ts +8 -4
- package/lib/backend.js +36 -44
- package/lib/backend.js.map +1 -1
- package/lib/graphOptimizer.d.ts +54 -0
- package/lib/graphOptimizer.js +216 -0
- package/lib/graphOptimizer.js.map +1 -0
- package/lib/main.d.ts +33 -10
- package/lib/main.js +5 -0
- package/lib/main.js.map +1 -1
- package/lib/modelSpec.d.ts +80 -0
- package/lib/modelSpec.js +270 -0
- package/lib/modelSpec.js.map +1 -0
- package/lib/nativeUNet.d.ts +67 -0
- package/lib/nativeUNet.js +1735 -0
- package/lib/nativeUNet.js.map +1 -0
- package/lib/process.js +3 -0
- package/lib/process.js.map +1 -1
- package/lib/resourceTracker.d.ts +26 -0
- package/lib/resourceTracker.js +65 -0
- package/lib/resourceTracker.js.map +1 -0
- package/lib/tileScheduler.d.ts +33 -0
- package/lib/tileScheduler.js +86 -0
- package/lib/tileScheduler.js.map +1 -0
- package/lib/webnnUNet.d.ts +52 -0
- package/lib/webnnUNet.js +535 -0
- package/lib/webnnUNet.js.map +1 -0
- package/package.json +9 -5
- package/scripts/inspect-model.mjs +64 -0
- package/src/UNet.ts +236 -339
- package/src/WGPUComputePass.ts +6 -4
- package/src/backend.ts +42 -55
- package/src/graphOptimizer.ts +301 -0
- package/src/main.ts +71 -11
- package/src/modelSpec.ts +414 -0
- package/src/nativeUNet.ts +2256 -0
- package/src/process.ts +3 -1
- package/src/resourceTracker.ts +94 -0
- package/src/tileScheduler.ts +138 -0
- package/src/webnnUNet.ts +812 -0
- package/tests/modelSpec.test.mjs +128 -0
- package/tests/resourceLifecycle.test.mjs +383 -0
- package/tests/tileScheduler.test.mjs +90 -0
- package/lib/helper.d.ts +0 -4
- package/lib/helper.js +0 -33
- package/lib/helper.js.map +0 -1
- package/lib/kernels.d.ts +0 -1
- package/lib/kernels.js +0 -26
- package/lib/kernels.js.map +0 -1
- package/src/helper.ts +0 -43
- 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
|
+
}
|