web: hand the upscaler's activations to the chip that is running them

The WebGPU execution provider has no PReLU kernel. The model is 34 convolutions
with a PReLU after every one of them, so an export that took the GPU path was
split 33 times: each activation came off the chip to be activated on the
processor and went straight back, a 64-channel map in both directions, per tile.
A machine with a good graphics chip was not exporting any faster for having it.

PReLU(x) is exactly Relu(x) - slope * Relu(-x), and Relu, Neg, Mul and Sub the
provider does implement, so scripts/realesr-gpu.py writes the 33 activations out
as those four and drops the slopes nobody reads any more. The model file is the
output of that script, not the file as published.

One 256x256 tile through the model before and after, on a WebGPU session: the
runtime no longer reports nodes left off the preferred provider (it did, once,
before) and the processor path answers bit for bit what it answered before. The
warning itself cannot be switched off from here - env.logLevel is read when the
runtime module initialises, before any of this runs - so the graph was fixed
rather than the lines hidden.
This commit is contained in:
2026-09-23 09:02:53 +07:00
parent a2c12a2638
commit a0965101e1
3 changed files with 496 additions and 117 deletions
+9
View File
@@ -9,6 +9,11 @@ import type * as Ort from 'onnxruntime-web';
// Both the runtime (served next to canvaskit.wasm, see scripts/copy-wasm.mjs)
// and the model (public/models) are static files; nothing here changes what the
// server can do.
//
// The model is not the file as published: its 33 PReLU activations are written
// out as Relu/Neg/Mul/Sub, because the WebGPU provider has no PReLU kernel and
// would otherwise hand every activation to the processor and take it back, 33
// times per tile. scripts/realesr-gpu.py is that rewrite and explains it.
const MODEL_URL = '/models/realesr-general-x4v3.onnx';
// The wasm dir, not the file: the runtime picks its own name inside it.
@@ -54,6 +59,10 @@ function load(): Promise<Loaded> {
// A laptop with two GPUs would otherwise hand this to the one built into
// the processor. The model is the export, so ask for the fast one.
ort.env.webgpu.powerPreference = 'high-performance';
// env.logLevel looks like a switch for the runtime's own warnings and is not
// one — it is read when the module initialises, before this runs, so a level
// set here silences nothing. Left alone, they are worth reading: the last one
// to appear said the graph was being split between the chip and the processor.
const session = await ort.InferenceSession.create(MODEL_URL, { executionProviders: ['webgpu'] }).catch(() =>
ort.InferenceSession.create(MODEL_URL, { executionProviders: ['wasm'] })
);