oidn-web 0.4.0 → 0.5.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 +45 -0
- package/README.md +83 -19
- package/dist/oidn.js +3239 -2642
- package/dist/oidn.umd.cjs +512 -296
- package/lib/UNet.d.ts +54 -20
- package/lib/UNet.js +194 -118
- package/lib/UNet.js.map +1 -1
- package/lib/backend.d.ts +1 -8
- package/lib/backend.js +1 -9
- package/lib/backend.js.map +1 -1
- package/lib/finalRgbShader.d.ts +13 -0
- package/lib/finalRgbShader.js +160 -0
- package/lib/finalRgbShader.js.map +1 -0
- package/lib/graphOptimizer.js +1 -2
- package/lib/graphOptimizer.js.map +1 -1
- package/lib/hdrTransfer.d.ts +14 -0
- package/lib/hdrTransfer.js +61 -0
- package/lib/hdrTransfer.js.map +1 -0
- package/lib/main.d.ts +16 -7
- package/lib/main.js +4 -5
- package/lib/main.js.map +1 -1
- package/lib/nativeUNet.d.ts +39 -3
- package/lib/nativeUNet.js +449 -120
- package/lib/nativeUNet.js.map +1 -1
- package/lib/process.d.ts +5 -11
- package/lib/process.js +35 -49
- package/lib/process.js.map +1 -1
- package/lib/tileScheduler.d.ts +32 -4
- package/lib/tileScheduler.js +133 -20
- package/lib/tileScheduler.js.map +1 -1
- package/package.json +9 -2
- package/src/UNet.ts +287 -158
- package/src/backend.ts +1 -14
- package/src/finalRgbShader.ts +186 -0
- package/src/graphOptimizer.ts +1 -2
- package/src/hdrTransfer.ts +88 -0
- package/src/main.ts +28 -13
- package/src/nativeUNet.ts +515 -116
- package/src/process.ts +43 -70
- package/src/tileScheduler.ts +216 -24
- package/benchmarks/compare.mjs +0 -651
- package/benchmarks/leak.mjs +0 -255
- package/benchmarks/results/before-spatial.json +0 -391
- package/benchmarks/results/before-spatial.md +0 -47
- package/benchmarks/results/int8-scan.json +0 -2007
- package/benchmarks/results/int8-scan.md +0 -160
- package/benchmarks/results/int8-w8a8-scan.json +0 -2007
- package/benchmarks/results/int8-w8a8-scan.md +0 -160
- package/benchmarks/results/int8-weight-channel.json +0 -1413
- package/benchmarks/results/int8-weight-channel.md +0 -118
- package/benchmarks/results/int8-weight-only.json +0 -1437
- package/benchmarks/results/int8-weight-only.md +0 -118
- package/benchmarks/results/kernel-webnn-final.json +0 -1115
- package/benchmarks/results/kernel-webnn-final.md +0 -104
- package/benchmarks/results/latest-optimized.json +0 -375
- package/benchmarks/results/latest-optimized.md +0 -47
- package/benchmarks/results/latest.json +0 -391
- package/benchmarks/results/latest.md +0 -47
- package/benchmarks/results/profile-baseline.json +0 -331
- package/benchmarks/results/profile-baseline.md +0 -12
- package/benchmarks/results/profile-conv2x.json +0 -331
- package/benchmarks/results/profile-conv2x.md +0 -12
- package/benchmarks/results/profile-fast-init.json +0 -385
- package/benchmarks/results/profile-fast-init.md +0 -47
- package/benchmarks/results/profile-fp16-fma.json +0 -369
- package/benchmarks/results/profile-fp16-fma.md +0 -47
- package/benchmarks/results/profile-fp16-tiled-decoder.json +0 -369
- package/benchmarks/results/profile-fp16-tiled-decoder.md +0 -47
- package/benchmarks/results/profile-fp16-tiled-encoder.json +0 -369
- package/benchmarks/results/profile-fp16-tiled-encoder.md +0 -47
- package/benchmarks/results/profile-fp16-unfused-pool.json +0 -385
- package/benchmarks/results/profile-fp16-unfused-pool.md +0 -47
- package/benchmarks/results/profile-input-major.json +0 -347
- package/benchmarks/results/profile-input-major.md +0 -12
- package/benchmarks/results/profile-k16.json +0 -347
- package/benchmarks/results/profile-k16.md +0 -12
- package/benchmarks/results/profile-k4.json +0 -347
- package/benchmarks/results/profile-k4.md +0 -12
- package/benchmarks/results/profile-pool-reuse.json +0 -331
- package/benchmarks/results/profile-pool-reuse.md +0 -12
- package/benchmarks/results/profile-precompiled.json +0 -385
- package/benchmarks/results/profile-precompiled.md +0 -47
- package/benchmarks/results/profile-static-channels.json +0 -385
- package/benchmarks/results/profile-static-channels.md +0 -47
- package/benchmarks/results/profile-static-io.json +0 -385
- package/benchmarks/results/profile-static-io.md +0 -47
- package/benchmarks/results/profile-tiled-conv.json +0 -331
- package/benchmarks/results/profile-tiled-conv.md +0 -12
- package/benchmarks/results/profile-tiled-decoder.json +0 -347
- package/benchmarks/results/profile-tiled-decoder.md +0 -12
- package/benchmarks/results/profile-tiled-matmul.json +0 -331
- package/benchmarks/results/profile-tiled-matmul.md +0 -12
- package/benchmarks/results/profile-unfused-decoder.json +0 -379
- package/benchmarks/results/profile-unfused-decoder.md +0 -12
- package/benchmarks/results/profile-unfused-pool.json +0 -347
- package/benchmarks/results/profile-unfused-pool.md +0 -12
- package/benchmarks/results/spatial-auto.json +0 -575
- package/benchmarks/results/spatial-auto.md +0 -61
- package/benchmarks/results/subgroup-smoke.json +0 -1094
- package/benchmarks/results/subgroup-smoke.md +0 -104
- package/benchmarks/results/webnn-smoke.json +0 -739
- package/benchmarks/results/webnn-smoke.md +0 -76
- package/scripts/inspect-model.mjs +0 -64
- package/tests/modelSpec.test.mjs +0 -128
- package/tests/resourceLifecycle.test.mjs +0 -383
- package/tests/tileScheduler.test.mjs +0 -90
package/src/process.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { WGPUComputePass } from './WGPUComputePass';
|
|
2
|
+
import {
|
|
3
|
+
hdrTransferFuncCPU,
|
|
4
|
+
hdrTransferFuncInverseCPU,
|
|
5
|
+
type HDRTransfer
|
|
6
|
+
} from './hdrTransfer';
|
|
7
|
+
export { hdrTransferFuncCPU, hdrTransferFuncInverseCPU } from './hdrTransfer';
|
|
8
|
+
export type { HDRTransfer } from './hdrTransfer';
|
|
2
9
|
|
|
3
10
|
const a = 1.41283765e3;
|
|
4
11
|
const b = 1.64593172;
|
|
@@ -37,6 +44,8 @@ const yMax = 65504;
|
|
|
37
44
|
const xMax = PUForward(yMax);
|
|
38
45
|
const normScale = 1 / xMax;
|
|
39
46
|
const rcpNormScale = xMax;
|
|
47
|
+
const logXMax = Math.log(yMax + 1);
|
|
48
|
+
const logNormScale = 1 / logXMax;
|
|
40
49
|
|
|
41
50
|
export class Tile {
|
|
42
51
|
constructor(
|
|
@@ -75,53 +84,6 @@ export function avgLogLum({
|
|
|
75
84
|
return key / Math.pow(2, averageLuminance);
|
|
76
85
|
}
|
|
77
86
|
|
|
78
|
-
export function hdrTransferFuncCPU({
|
|
79
|
-
data,
|
|
80
|
-
channels,
|
|
81
|
-
inputScale
|
|
82
|
-
}: {
|
|
83
|
-
data: Float32Array;
|
|
84
|
-
channels: number;
|
|
85
|
-
inputScale: number;
|
|
86
|
-
}) {
|
|
87
|
-
const newData = new Float32Array(data.length);
|
|
88
|
-
newData.set(data);
|
|
89
|
-
// https://github.com/RenderKit/oidn/blob/713ec7838ba650f99e0a896549c0dca5eeb3652d/core/color.h#L71
|
|
90
|
-
for (let i = 0; i < newData.length; i += channels) {
|
|
91
|
-
// First three are color
|
|
92
|
-
for (let c = 0; c < 3; c++) {
|
|
93
|
-
let y = newData[i + c] * inputScale;
|
|
94
|
-
// https://github.com/RenderKit/oidn/blob/713ec7838ba650f99e0a896549c0dca5eeb3652d/devices/cpu/color.ispc#L135
|
|
95
|
-
newData[i + c] = PUForward(y) * normScale;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return newData;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function hdrTransferFuncInverseCPU({
|
|
103
|
-
data,
|
|
104
|
-
channels,
|
|
105
|
-
inputScale
|
|
106
|
-
}: {
|
|
107
|
-
data: Float32Array;
|
|
108
|
-
channels: number;
|
|
109
|
-
inputScale: number;
|
|
110
|
-
}) {
|
|
111
|
-
const newData = new Float32Array(data.length);
|
|
112
|
-
newData.set(data);
|
|
113
|
-
|
|
114
|
-
const outputScale = 1 / inputScale;
|
|
115
|
-
for (let i = 0; i < newData.length; i += channels) {
|
|
116
|
-
for (let c = 0; c < 3; c++) {
|
|
117
|
-
let x = newData[i + c] * rcpNormScale;
|
|
118
|
-
newData[i + c] = PUInverse(x) * outputScale;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return newData;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
87
|
const constsCode = `
|
|
126
88
|
const a = ${a};
|
|
127
89
|
const b = ${b};
|
|
@@ -147,7 +109,11 @@ export class GPUDataProcess {
|
|
|
147
109
|
|
|
148
110
|
private _isInputTexture?: boolean;
|
|
149
111
|
|
|
150
|
-
constructor(
|
|
112
|
+
constructor(
|
|
113
|
+
private _device: GPUDevice,
|
|
114
|
+
private _isHDR: boolean,
|
|
115
|
+
private _hdrTransfer: HDRTransfer = 'pu'
|
|
116
|
+
) {
|
|
151
117
|
const commonUniforms = [
|
|
152
118
|
{
|
|
153
119
|
label: 'inputScale',
|
|
@@ -261,26 +227,40 @@ out_color[outIdx] = textureLoad(in_color, globalId.xy, 0);
|
|
|
261
227
|
|
|
262
228
|
this._isInputTexture = isInputTexture;
|
|
263
229
|
const isHDR = this._isHDR;
|
|
230
|
+
const hdrForwardCode = this._hdrTransfer === 'log'
|
|
231
|
+
? `fn HDRForward(y: f32) -> f32 { return log(y + 1.0) * logNormScale; }`
|
|
232
|
+
: `fn HDRForward(y: f32) -> f32 {
|
|
233
|
+
if (y <= y0) { return a * y * normScale; }
|
|
234
|
+
else if (y <= y1) { return (b * pow(y, c) + d) * normScale; }
|
|
235
|
+
else { return (e * log(y + f) + g) * normScale; }
|
|
236
|
+
}`;
|
|
237
|
+
const hdrInverseCode = this._hdrTransfer === 'log'
|
|
238
|
+
? `fn HDRInverse(x: f32) -> f32 { return exp(x * logXMax) - 1.0; }`
|
|
239
|
+
: `fn HDRInverse(x: f32) -> f32 {
|
|
240
|
+
let y = x * rcpNormScale;
|
|
241
|
+
if (y <= x0) { return y / a; }
|
|
242
|
+
else if (y <= x1) { return pow((y - d) / b, 1 / c); }
|
|
243
|
+
else { return exp((y - g) / e) - f; }
|
|
244
|
+
}`;
|
|
264
245
|
const commonCSDefine = /* wgsl */ `
|
|
265
246
|
${constsCode}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
} else if (y <= y1) {
|
|
270
|
-
return b * pow(y, c) + d;
|
|
271
|
-
} else {
|
|
272
|
-
return e * log(y + f) + g;
|
|
273
|
-
}
|
|
274
|
-
}`;
|
|
247
|
+
const logXMax = ${logXMax};
|
|
248
|
+
const logNormScale = ${logNormScale};
|
|
249
|
+
${hdrForwardCode}`;
|
|
275
250
|
function readInputCode(inputName: string) {
|
|
276
251
|
return isInputTexture
|
|
277
|
-
? `textureLoad(in_${inputName},
|
|
252
|
+
? `textureLoad(in_${inputName}, vec2u(inputPosition), 0)`
|
|
278
253
|
: `in_${inputName}[inIdx]`;
|
|
279
254
|
}
|
|
280
255
|
const commonCSMain = /* wgsl */ `
|
|
281
256
|
let x = i32(globalId.x);
|
|
282
257
|
let y = i32(globalId.y);
|
|
283
|
-
let
|
|
258
|
+
let inputPosition = clamp(
|
|
259
|
+
vec2i(x, y) + inputOffset,
|
|
260
|
+
vec2i(0),
|
|
261
|
+
inputSize - vec2i(1)
|
|
262
|
+
);
|
|
263
|
+
let inIdx = inputPosition.y * inputSize.x + inputPosition.x;
|
|
284
264
|
let col = ${readInputCode('color')};
|
|
285
265
|
|
|
286
266
|
let outIdx = y * outputSize.x + x;
|
|
@@ -290,7 +270,7 @@ if (${denoiseAlpha}) {
|
|
|
290
270
|
out_color[outIdx] = vec3f(1.0 - col.a);
|
|
291
271
|
}
|
|
292
272
|
else if (${isHDR}) {
|
|
293
|
-
out_color[outIdx] = vec3f(
|
|
273
|
+
out_color[outIdx] = vec3f(HDRForward(col.r * inputScale), HDRForward(col.g * inputScale), HDRForward(col.b * inputScale));
|
|
294
274
|
}
|
|
295
275
|
else {
|
|
296
276
|
out_color[outIdx] = col.rgb;
|
|
@@ -316,15 +296,8 @@ ${commonCSMain}
|
|
|
316
296
|
this._outputPass.setCSCode({
|
|
317
297
|
csDefine: /* wgsl */ `
|
|
318
298
|
${constsCode}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return y / a;
|
|
322
|
-
} else if (y <= x1) {
|
|
323
|
-
return pow((y - d) / b, 1 / c);
|
|
324
|
-
} else {
|
|
325
|
-
return exp((y - g) / e) - f;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
299
|
+
const logXMax = ${logXMax};
|
|
300
|
+
${hdrInverseCode}
|
|
328
301
|
`,
|
|
329
302
|
csMain: /* wgsl */ `
|
|
330
303
|
let x = i32(globalId.x);
|
|
@@ -346,7 +319,7 @@ if (${denoiseAlpha}) {
|
|
|
346
319
|
}
|
|
347
320
|
else if (${isHDR}) {
|
|
348
321
|
out_color[outIdx] = vec4f(
|
|
349
|
-
vec3f(
|
|
322
|
+
vec3f(HDRInverse(col.r), HDRInverse(col.g), HDRInverse(col.b)) / inputScale,
|
|
350
323
|
// Pick the alpha
|
|
351
324
|
raw.a
|
|
352
325
|
);
|
package/src/tileScheduler.ts
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
export interface DynamicTileOptions {
|
|
2
2
|
/** Smallest output tile edge. Defaults to 256. */
|
|
3
3
|
minTileSize?: number;
|
|
4
|
-
/** Initial output tile edge. Defaults to
|
|
4
|
+
/** Initial maximum output tile edge. Defaults to 432. */
|
|
5
5
|
initialTileSize?: number;
|
|
6
6
|
/** Desired upper bound for one tile's GPU latency. Defaults to 16 ms. */
|
|
7
7
|
targetTileTimeMs?: number;
|
|
8
|
-
/** Amount added or removed after each completed execution. Defaults to
|
|
8
|
+
/** Amount added or removed after each completed execution. Defaults to 16. */
|
|
9
9
|
adjustmentStep?: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export type DynamicTileSetting = boolean | DynamicTileOptions;
|
|
13
13
|
|
|
14
14
|
const defaultMinTileSize = 256;
|
|
15
|
-
const defaultInitialTileSize =
|
|
15
|
+
const defaultInitialTileSize = 432;
|
|
16
16
|
const defaultTargetTileTimeMs = 16;
|
|
17
|
-
const defaultAdjustmentStep =
|
|
17
|
+
const defaultAdjustmentStep = 16;
|
|
18
18
|
export const OIDN_TILE_ALIGNMENT = 16;
|
|
19
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
|
+
|
|
20
45
|
function alignUp(value: number, alignment: number) {
|
|
21
46
|
return Math.ceil(value / alignment) * alignment;
|
|
22
47
|
}
|
|
@@ -25,16 +50,180 @@ function alignDown(value: number, alignment: number) {
|
|
|
25
50
|
return Math.floor(value / alignment) * alignment;
|
|
26
51
|
}
|
|
27
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
|
+
|
|
28
220
|
function clamp(value: number, min: number, max: number) {
|
|
29
221
|
return Math.min(Math.max(value, min), max);
|
|
30
222
|
}
|
|
31
223
|
|
|
32
|
-
function
|
|
224
|
+
function percentile(values: number[], fraction: number) {
|
|
33
225
|
const sorted = [...values].sort((a, b) => a - b);
|
|
34
|
-
|
|
35
|
-
return sorted.length % 2
|
|
36
|
-
? sorted[middle]
|
|
37
|
-
: (sorted[middle - 1] + sorted[middle]) / 2;
|
|
226
|
+
return sorted[Math.ceil(sorted.length * fraction) - 1];
|
|
38
227
|
}
|
|
39
228
|
|
|
40
229
|
/** Fits one output dimension without exceeding the configured tile cap. */
|
|
@@ -57,6 +246,8 @@ export class DynamicTileController {
|
|
|
57
246
|
|
|
58
247
|
private _tileSize: number;
|
|
59
248
|
private _adjustmentStep: number;
|
|
249
|
+
private _smoothedTileTimeMs?: number;
|
|
250
|
+
private _completeExecutionsSinceChange = 0;
|
|
60
251
|
|
|
61
252
|
constructor(maxTileSize: number, setting: DynamicTileSetting = true) {
|
|
62
253
|
const options = typeof setting === 'object' ? setting : {};
|
|
@@ -106,11 +297,22 @@ export class DynamicTileController {
|
|
|
106
297
|
);
|
|
107
298
|
if (validTimes.length === 0) return false;
|
|
108
299
|
|
|
109
|
-
|
|
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
|
+
|
|
110
312
|
let nextTileSize = this._tileSize;
|
|
111
|
-
if (
|
|
313
|
+
if (this._smoothedTileTimeMs > this.targetTileTimeMs * 1.25) {
|
|
112
314
|
nextTileSize -= this._adjustmentStep;
|
|
113
|
-
} else if (
|
|
315
|
+
} else if (this._smoothedTileTimeMs < this.targetTileTimeMs * 0.65) {
|
|
114
316
|
nextTileSize += this._adjustmentStep;
|
|
115
317
|
}
|
|
116
318
|
|
|
@@ -121,18 +323,8 @@ export class DynamicTileController {
|
|
|
121
323
|
);
|
|
122
324
|
if (nextTileSize === this._tileSize) return false;
|
|
123
325
|
this._tileSize = nextTileSize;
|
|
326
|
+
this._smoothedTileTimeMs = undefined;
|
|
327
|
+
this._completeExecutionsSinceChange = 0;
|
|
124
328
|
return true;
|
|
125
329
|
}
|
|
126
330
|
}
|
|
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
|
-
}
|