ai.onnx.InstanceNormalization / build /webgpu /instance-normalization-apply.wgsl.jinja
Xenova's picture
Xenova HF Staff
sync 2e7068faf55e
0af9165 verified
Raw
History Blame
1.84 kB
// Apply per-plane statistics: y = (x - mean) * inverseStddev * scale + bias.
// The vectorized route packs four adjacent spatial values per invocation; each
// packed load and store remains within one plane.
{% set vectorized = vectorized if vectorized is defined else false %}
{% if usesF16 %}
enable f16;
{% endif %}
{% set LOAD_OPEN = "vec4<f32>(" if usesF16 else "" %}
{% set LOAD_CLOSE = ")" if usesF16 else "" %}
{% set STORE_OPEN = "vec4<f16>(" if usesF16 else "" %}
{% set STORE_CLOSE = ")" if usesF16 else "" %}
{% set CHAN_OPEN = "f32(" if usesF16 else "" %}
{% set CHAN_CLOSE = ")" if usesF16 else "" %}
{{ env.wgsl.resourceDeclarations }}
const WG: u32 = {{ applyWorkgroupSize }}u;
@compute @workgroup_size(WG, 1, 1)
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
{% if not vectorized %}
// 2D-folded flat index: gid.y carries the high bits after dispatch folding.
{% endif %}
let index = gid.x + gid.y * nwg.x * WG;
if (index >= params.count) {
return;
}
{% if vectorized %}
// The vectorized path requires each plane to contain a multiple of four
// values, so a packed load/store never crosses an instance boundary.
let plane = (index * 4u) / params.spatial;
{% else %}
let plane = index / params.spatial;
{% endif %}
let channel = plane % params.channels;
let mean = stats[plane * 2u];
let inv_std = stats[plane * 2u + 1u];
{% if vectorized %}
let value = {{ LOAD_OPEN }}input[index]{{ LOAD_CLOSE }};
output[index] = {{ STORE_OPEN }}(value - vec4<f32>(mean)) * vec4<f32>(inv_std * {{ CHAN_OPEN }}scale[channel]{{ CHAN_CLOSE }}) + vec4<f32>({{ CHAN_OPEN }}bias[channel]{{ CHAN_CLOSE }}){{ STORE_CLOSE }};
{% else %}
output[index] = {{ scalar }}((f32(input[index]) - mean) * inv_std * f32(scale[channel]) + f32(bias[channel]));
{% endif %}
}