Skip to content

Commit 1bb706b

Browse files
SS-JIApytorchbot
authored andcommitted
[ET-VK][conv2d] Fuse residual and activation epilogues into conv2d shaders
Pull Request resolved: #24807 ## Ulterior Motive Closing the DSR FP16 gap to LiteRT. DSR has 83 skip connections of the form `conv(...) + x * scale`. ET-VK runs each as a separate `mul` and `add` (168 dispatches, 347 ms per inference on Galaxy S25 against LiteRT's 148 ms). This commit adds runtime support for fusing them into the preceding convolution; the next commit fuses them during lowering. ## Rationale **What**: Every conv2d shader (direct, pointwise, depthwise, im2col GEMM and transposed) gains residual and scale bindings and applies an epilogue before storing: ``` out = activation(residual_op(conv + bias, residual * scale, alpha)) ``` Specialization constants select each stage. A new `et_vk.conv2d.with_epilogue` op exposes it. **Why**: One dispatch replaces conv, `mul` and `add`, and the conv output and scaled residual are never written to memory. Because every route applies the epilogue itself, the op never falls back to separate element-wise dispatches. ## Details - **Epilogue** (`conv2d_epilogue.glslh`): calls `apply_binary_op()` for the residual op and `apply_unary_op()` for the activation. - The C++ side accepts add, sub and mul residual ops and clamp, relu and leaky_relu activations. - Inlining the `apply_unary_op()` switch changes Adreno's code generation for the 2x2 tiled direct shaders, even with the activation specialized away: AOC instruction counts rise 6–25% (`conv2d_2x2x8_border_float`: 532 → 660) with unchanged register counts. - Against a switch limited to the three supported activations, large FP16 3x3 and 5x5 direct convolutions are 2.1–2.9% slower on Pixel 8 Pro, and small 3x3 direct convolutions 4.4–5.7% slower on Galaxy S23. Calling `apply_unary_op()` avoids a second activation dispatcher. - **Absent operands**: The residual slot binds the convolution input and the scale slot binds the packed bias, which have the slots' storage types. `has_residual` / `has_scale` gate the reads. - **Scale**: A constant `[1, C, 1, 1]` or `[C, 1, 1]` residual scale. Each route prepacks it like the bias (texture2d, using the transposed weight layout for transposed convolutions) with `prepack_conv2d_residual_scale()`, next to the bias prepack, before building the bindings (`Conv2dEpilogueArgs`). - **Validation**: `conv2d_epilogue_from_args()` throws at graph build when: - the residual is not texture3d or does not match the output's sizes, dtype and layout; - the scale is not a constant, is given without a residual, does not have the weight's dtype, or is not `[1, C, 1, 1]` or `[C, 1, 1]`. Other shapes with C values, such as `[C]`, broadcast along another dim in PyTorch; - the residual op or activation is unsupported. conv2d nodes pass the residual as their last resize argument, and their resize functions check that it still matches the resized output's sizes. - **Clamp** - **Before**: Each shader family selected its clamp differently: a `clamp_out` spec constant and an `out_min` / `out_max` UBO (direct); an `activation_type` 0/1/2 spec constant (pointwise and im2col GEMM); separately generated `_clamp` shader variants (depthwise and transposed). - **After**: All use `has_activation` + `activation_op` with `activation_a` / `activation_b`. The `_clamp` variants are gone. `conv_with_clamp` builds a clamp epilogue. - **Bindings and constants** - `conv2d_tiled`, `conv2d_dw` and `conv_transpose2d`: residual at 4, scale at 5, UBOs move from 4–8 to 6–10. UBO 10 holds `activation_a`, `activation_b` and `residual_alpha`. - `conv2d_pw_tiled`, `conv2d_gemm` and the depthwise output-tile shaders: residual and scale follow the bias; the clamp push constants become `activation_a`, `activation_b` and `residual_alpha`. - The epilogue specialization constants `has_activation`, `activation_op`, `has_residual`, `has_scale` and `residual_op` are contiguous in every shader, and `append_conv2d_epilogue_spec_vars()` appends all five. In `conv2d_tiled`, `has_bias` moves from ID 7 to 6, the group channel counts from 8–9 to 7–8, and `has_activation` (formerly `clamp_out`, 6) takes 9, ahead of the other epilogue constants at 10–13. - The unused `ngroups` (`conv2d_dw`) and `packed_dim` (`conv_transpose2d`) spec constants are removed. The transposed convolution no longer needs the `has_activation` resize argument that picked its `_clamp` variant. - **Entry point**: `conv2d_with_epilogue_impl()` replaces `conv2d_impl()` and implements `aten.convolution`, `conv_with_clamp` and `conv2d.with_epilogue` for 4D inputs. - **Depthwise tile override**: `conv2d_dw_impl` takes an optional `b1x1_override`. The benchmark's depthwise `b1x1` / `b4x2` routes call it instead of carrying a copy of the depthwise dispatch. The `b1x1` route now also selects the 1x1 tile for 5x5 kernels, as the Mali default already does. - **Benchmark** - Adds a `conv2d_with_epilogue` reference and a transposed convolution reference; the conv2d reference previously rejected transposed convolutions. The epilogue reference rejects the scale shapes and the scale without a residual that the runtime rejects. - `et_vk.conv2d.with_epilogue` accepts the `direct`, `im2col`, `b1x1` and `b4x2` routes and reports throughput. - Adds 204 correctness cases. 200 cover 5 epilogues × FP32/FP16 on 20 shape and route combinations: - one shape per route: direct, pointwise, im2col, the three depthwise shaders, transposed; - partial output texels or tiles on the direct, pointwise, im2col, depthwise tile and transposed routes; - a strided and dilated direct conv and a grouped direct conv. - The other 4 run a pointwise, direct, im2col and depthwise epilogue through a full → shrunk → full resize. - Adds the `operator_dynamic_resize` case check. It builds an operator case, which must expand to one case, once, then for each step resizes the staged inputs, checks the output sizes and validates the output. The q8ta streaming-shrink check now uses it instead of its own copy. - The depthwise `b1x1` / `b4x2` routes report batch > 1 as unsupported (SKIP), since the depthwise shaders support batch 1 only. - The route inspector now recognizes the tiled direct shader names. - `conv2d_gemm_sustained.json` binds the new GEMM slots. Usage: ```cpp // Parses and validates the epilogue arguments of a conv2d op const Conv2dEpilogue epilogue = conv2d_epilogue_from_args(graph, args); conv2d_direct_impl(graph, in, weight, bias, stride, padding, dilation, transposed, output_padding, groups, out, epilogue); // Inside a route const ValueRef packed_scale = prepack_conv2d_residual_scale(graph, epilogue, weight, transposed); const Conv2dEpilogueArgs epilogue_args(epilogue, in, packed_bias, packed_scale); // Bind epilogue_args.residual and epilogue_args.scale, append the epilogue's // specialization constants, and pass epilogue.residual as the last resize arg. append_conv2d_epilogue_spec_vars(spec_vars, epilogue); ``` ghstack-source-id: 469036183 @exported-using-ghexport Differential Revision: [D130216287](https://our.internmc.facebook.com/intern/diff/D130216287/)
1 parent 84c494c commit 1bb706b

33 files changed

Lines changed: 2730 additions & 653 deletions

‎backends/vulkan/runtime/graph/ops/glsl/convolution/conv2d/conv2d_dw.glsl‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212

1313
#define VEC4_T ${texel_type(DTYPE)}
1414

15-
#define op(X, A, B) ${OPERATOR}
16-
1715
#include "indexing_utils.h"
1816

1917
layout(std430) buffer;
@@ -22,17 +20,25 @@ ${layout_declare_tensor(0, "w", "t_out", DTYPE, "texture3d")}
2220
${layout_declare_tensor(1, "r", "t_in", DTYPE, "texture3d")}
2321
${layout_declare_tensor(2, "r", "t_kernel", DTYPE, "texture2d")}
2422
${layout_declare_tensor(3, "r", "t_bias", DTYPE, "texture2d")}
25-
${layout_declare_ubo(4, "ivec3", "out_limits")}
26-
${layout_declare_ubo(5, "ivec4", "in_sizes")}
27-
${layout_declare_ubo(6, "ivec2", "kernel_size", "ivec2", "stride", "ivec2", "padding", "ivec2", "dilation")}
28-
${layout_declare_ubo(7, "ivec2", "overlay_region", "int", "in_group_size")}
29-
${layout_declare_ubo(8, "float", "out_min", "float", "out_max")}
23+
${layout_declare_tensor(4, "r", "t_residual", DTYPE, "texture3d")}
24+
${layout_declare_tensor(5, "r", "t_scale", DTYPE, "texture2d")}
25+
${layout_declare_ubo(6, "ivec3", "out_limits")}
26+
${layout_declare_ubo(7, "ivec4", "in_sizes")}
27+
${layout_declare_ubo(8, "ivec2", "kernel_size", "ivec2", "stride", "ivec2", "padding", "ivec2", "dilation")}
28+
${layout_declare_ubo(9, "ivec2", "overlay_region", "int", "in_group_size")}
29+
${layout_declare_ubo(10, "float", "activation_a", "float", "activation_b", "float", "residual_alpha")}
3030

3131
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3232

3333
#include "dispatch.glslh"
3434

35-
${layout_declare_spec_const(C, "int", "ngroups", "1")}
35+
${layout_declare_spec_const(C, "bool", "has_activation", "false")}
36+
${layout_declare_spec_const(C, "int", "activation_op", "0")}
37+
${layout_declare_spec_const(C, "bool", "has_residual", "false")}
38+
${layout_declare_spec_const(C, "bool", "has_scale", "false")}
39+
${layout_declare_spec_const(C, "int", "residual_op", "0")}
40+
41+
#include "conv2d_epilogue.glslh"
3642

3743
/*
3844
* Computes a depthwise convolution. Each shader invocation calculates the
@@ -79,5 +85,7 @@ void main() {
7985
}
8086
}
8187

82-
imageStore(t_out, pos, op(sum, out_min, out_max));
88+
const vec4 scale =
89+
has_scale ? texelFetch(t_scale, ivec2(pos.z, 0), 0) : vec4(1);
90+
imageStore(t_out, pos, apply_epilogue(vec4(sum), pos, scale));
8391
}

‎backends/vulkan/runtime/graph/ops/glsl/convolution/conv2d/conv2d_dw.yaml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
conv2d_dw:
88
parameter_names_with_default_values:
9-
OPERATOR: X
109
NDIM: 3
1110
DTYPE: float
1211
generate_variant_forall:
@@ -15,5 +14,3 @@ conv2d_dw:
1514
- VALUE: float
1615
shader_variants:
1716
- NAME: conv2d_dw
18-
- NAME: conv2d_dw_clamp
19-
OPERATOR: clamp(X, A, B)

‎backends/vulkan/runtime/graph/ops/glsl/convolution/conv2d/conv2d_dw_output_tile.glsl‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020

2121
#define LOCAL_WG_SIZE 64
2222

23-
#define op(X, A, B) ${OPERATOR}
24-
2523
#include "indexing_utils.h"
2624

2725
layout(std430) buffer;
@@ -30,6 +28,8 @@ ${layout_declare_tensor(0, "w", "t_out", DTYPE, "texture3d")}
3028
${layout_declare_tensor(1, "r", "t_in", DTYPE, "texture3d")}
3129
${layout_declare_tensor(2, "r", "t_kernel", DTYPE, "texture2d")}
3230
${layout_declare_tensor(3, "r", "t_bias", DTYPE, "texture2d")}
31+
${layout_declare_tensor(4, "r", "t_residual", DTYPE, "texture3d")}
32+
${layout_declare_tensor(5, "r", "t_scale", DTYPE, "texture2d")}
3333

3434
layout(push_constant) uniform restrict Block {
3535
ivec4 out_limits;
@@ -41,12 +41,21 @@ layout(push_constant) uniform restrict Block {
4141
ivec2 overlay_region;
4242
int in_group_size;
4343
int dummy_padding;
44-
float out_min;
45-
float out_max;
44+
float activation_a;
45+
float activation_b;
46+
float residual_alpha;
4647
};
4748

4849
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
4950

51+
${layout_declare_spec_const(C, "bool", "has_activation", "false")}
52+
${layout_declare_spec_const(C, "int", "activation_op", "0")}
53+
${layout_declare_spec_const(C, "bool", "has_residual", "false")}
54+
${layout_declare_spec_const(C, "bool", "has_scale", "false")}
55+
${layout_declare_spec_const(C, "int", "residual_op", "0")}
56+
57+
#include "conv2d_epilogue.glslh"
58+
5059
/*
5160
* Computes a depthwise convolution. Each shader invocation calculates the
5261
* output at a single output location.
@@ -125,11 +134,16 @@ void main() {
125134
}
126135

127136
const VEC4_T bias = texelFetch(t_bias, ivec2(pos.z, 0), 0);
137+
const vec4 scale =
138+
has_scale ? texelFetch(t_scale, ivec2(pos.z, 0), 0) : vec4(1);
128139
for (int y = 0; y < BATCH_SIZE_Y; y++) {
129140
for (int x = 0; x < BATCH_SIZE_X; x++) {
130141
const ivec3 out_pos = ivec3(pos.x + x, pos.y + y, pos.z);
131142
if (all(lessThan(out_pos.xy, out_limits.xy))) {
132-
imageStore(t_out, out_pos, op(sum[y * BATCH_SIZE_X + x] + bias, out_min, out_max));
143+
imageStore(
144+
t_out,
145+
out_pos,
146+
apply_epilogue(vec4(sum[y * BATCH_SIZE_X + x] + bias), out_pos, scale));
133147
}
134148
}
135149
}

‎backends/vulkan/runtime/graph/ops/glsl/convolution/conv2d/conv2d_dw_output_tile.yaml‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
conv2d_dw_output_tile:
88
parameter_names_with_default_values:
9-
OPERATOR: X
109
NDIM: 3
1110
DTYPE: float
1211
TILE_SIZE: 3
@@ -18,26 +17,12 @@ conv2d_dw_output_tile:
1817
- VALUE: float
1918
shader_variants:
2019
- NAME: conv2d_dw_output_tile_3x3
21-
- NAME: conv2d_dw_output_tile_3x3_clamp
22-
OPERATOR: clamp(X, A, B)
2320
- NAME: conv2d_dw_output_tile_5x5
2421
TILE_SIZE: 5
25-
- NAME: conv2d_dw_output_tile_5x5_clamp
26-
OPERATOR: clamp(X, A, B)
27-
TILE_SIZE: 5
2822
- NAME: conv2d_dw_output_tile_3x3_b1x1
2923
BATCH_SIZE_X: 1
3024
BATCH_SIZE_Y: 1
31-
- NAME: conv2d_dw_output_tile_3x3_b1x1_clamp
32-
OPERATOR: clamp(X, A, B)
33-
BATCH_SIZE_X: 1
34-
BATCH_SIZE_Y: 1
3525
- NAME: conv2d_dw_output_tile_5x5_b1x1
3626
TILE_SIZE: 5
3727
BATCH_SIZE_X: 1
3828
BATCH_SIZE_Y: 1
39-
- NAME: conv2d_dw_output_tile_5x5_b1x1_clamp
40-
OPERATOR: clamp(X, A, B)
41-
TILE_SIZE: 5
42-
BATCH_SIZE_X: 1
43-
BATCH_SIZE_Y: 1

‎backends/vulkan/runtime/graph/ops/glsl/convolution/conv2d/conv2d_dw_sned_output_tile.glsl‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
#define TILE_SIZE ${TILE_SIZE}
1616

17-
#define op(X, A, B) ${OPERATOR}
18-
1917
#include "indexing_utils.h"
2018

2119
layout(std430) buffer;
@@ -24,6 +22,8 @@ ${layout_declare_tensor(0, "w", "t_out", DTYPE, "texture3d")}
2422
${layout_declare_tensor(1, "r", "t_in", DTYPE, "texture3d")}
2523
${layout_declare_tensor(2, "r", "t_kernel", DTYPE, "texture2d")}
2624
${layout_declare_tensor(3, "r", "t_bias", DTYPE, "texture2d")}
25+
${layout_declare_tensor(4, "r", "t_residual", DTYPE, "texture3d")}
26+
${layout_declare_tensor(5, "r", "t_scale", DTYPE, "texture2d")}
2727

2828
layout(push_constant) uniform restrict Block {
2929
ivec4 out_limits;
@@ -35,12 +35,21 @@ layout(push_constant) uniform restrict Block {
3535
ivec2 overlay_region;
3636
int in_group_size;
3737
int dummy_padding;
38-
float out_min;
39-
float out_max;
38+
float activation_a;
39+
float activation_b;
40+
float residual_alpha;
4041
};
4142

4243
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
4344

45+
${layout_declare_spec_const(C, "bool", "has_activation", "false")}
46+
${layout_declare_spec_const(C, "int", "activation_op", "0")}
47+
${layout_declare_spec_const(C, "bool", "has_residual", "false")}
48+
${layout_declare_spec_const(C, "bool", "has_scale", "false")}
49+
${layout_declare_spec_const(C, "int", "residual_op", "0")}
50+
51+
#include "conv2d_epilogue.glslh"
52+
4453
/*
4554
* Computes a depthwise convolution. Each shader invocation calculates the
4655
* output at a single output location.
@@ -79,5 +88,7 @@ void main() {
7988
}
8089
}
8190

82-
imageStore(t_out, pos, op(sum, out_min, out_max));
91+
const vec4 scale =
92+
has_scale ? texelFetch(t_scale, ivec2(pos.z, 0), 0) : vec4(1);
93+
imageStore(t_out, pos, apply_epilogue(vec4(sum), pos, scale));
8394
}

‎backends/vulkan/runtime/graph/ops/glsl/convolution/conv2d/conv2d_dw_sned_output_tile.yaml‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
conv2d_dw_sned_output_tile:
88
parameter_names_with_default_values:
9-
OPERATOR: X
109
NDIM: 3
1110
DTYPE: float
1211
TILE_SIZE: 3
@@ -16,10 +15,5 @@ conv2d_dw_sned_output_tile:
1615
- VALUE: float
1716
shader_variants:
1817
- NAME: conv2d_dw_sned_output_tile_3x3
19-
- NAME: conv2d_dw_sned_output_tile_3x3_clamp
20-
OPERATOR: clamp(X, A, B)
2118
- NAME: conv2d_dw_sned_output_tile_5x5
2219
TILE_SIZE: 5
23-
- NAME: conv2d_dw_sned_output_tile_5x5_clamp
24-
OPERATOR: clamp(X, A, B)
25-
TILE_SIZE: 5
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/*
10+
* Element-wise ops applied to a convolution output texel before it is stored:
11+
*
12+
* out = activation(residual_op(conv, residual * scale, alpha))
13+
*
14+
* residual_op is add, sub or mul, and activation is clamp, relu or leaky_relu.
15+
*
16+
* The including shader binds t_residual, a texture3d with the output's sizes,
17+
* and declares the specialization constants has_residual, has_scale,
18+
* residual_op, has_activation and activation_op, and the uniforms
19+
* residual_alpha, activation_a and activation_b. Operands that are absent are
20+
* bound to other tensors of the dispatch and never read.
21+
*/
22+
23+
#ifndef CONV2D_EPILOGUE_GLSLH
24+
#define CONV2D_EPILOGUE_GLSLH
25+
26+
#define IN_T vec4
27+
#define OUT_T vec4
28+
#define ALPHA_T float
29+
#define PARAM_T float
30+
#define BINARY_OP_VEC4
31+
#define BINARY_OP_IS_FLOAT
32+
#define UNARY_OP_IS_FLOAT
33+
34+
#include "binary/binary_op.glslh"
35+
#include "unary/unary_op.glslh"
36+
37+
#undef IN_T
38+
#undef OUT_T
39+
#undef ALPHA_T
40+
#undef PARAM_T
41+
#undef BINARY_OP_VEC4
42+
#undef BINARY_OP_IS_FLOAT
43+
#undef UNARY_OP_IS_FLOAT
44+
45+
// apply_binary_op() and apply_unary_op() are not reused for binary size: the
46+
// op is a specialization constant, so every case of their switches stays in the
47+
// SPIR-V, and the epilogue is inlined at each output store of every conv2d
48+
// shader.
49+
vec4 apply_residual_op(const vec4 v, const vec4 residual) {
50+
switch (residual_op) {
51+
case BINARY_OP_SUB:
52+
return v - residual_alpha * residual;
53+
case BINARY_OP_MUL:
54+
return v * residual;
55+
default: // BINARY_OP_ADD
56+
return v + residual_alpha * residual;
57+
}
58+
}
59+
60+
vec4 apply_activation(const vec4 v) {
61+
switch (activation_op) {
62+
case UNARY_OP_RELU:
63+
return max(v, vec4(0));
64+
case UNARY_OP_LEAKY_RELU:
65+
return leaky_relu(v, activation_a);
66+
default: // UNARY_OP_CLAMP
67+
return clamp(v, activation_a, activation_b);
68+
}
69+
}
70+
71+
// `scale` is the residual scale of the output channels in `pos.z`.
72+
vec4 apply_epilogue(vec4 v, const ivec3 pos, const vec4 scale) {
73+
if (has_residual) {
74+
vec4 residual = texelFetch(t_residual, pos, 0);
75+
if (has_scale) {
76+
residual *= scale;
77+
}
78+
v = apply_residual_op(v, residual);
79+
}
80+
if (has_activation) {
81+
v = apply_activation(v);
82+
}
83+
return v;
84+
}
85+
86+
#endif // CONV2D_EPILOGUE_GLSLH

‎backends/vulkan/runtime/graph/ops/glsl/convolution/conv2d/conv2d_pw_tiled.glsl‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ $if WEIGHT_STORAGE == "buffer":
4040
$if PIXEL_TILES_ON_X:
4141
#define PIXEL_TILES_ON_X
4242
${layout_declare_tensor(B, "r", "t_bias", DTYPE, "texture2d")}
43+
${layout_declare_tensor(B, "r", "t_residual", DTYPE, "texture3d")}
44+
${layout_declare_tensor(B, "r", "t_scale", DTYPE, "texture2d")}
4345

4446
${layout_declare_ubo(B, "ivec4", "in_sizes")}
4547
${layout_declare_ubo(B, "ivec4", "out_sizes")}
@@ -49,14 +51,21 @@ layout(push_constant) uniform restrict Block {
4951
int stride_w;
5052
int padding_h;
5153
int padding_w;
52-
float out_min;
53-
float out_max;
54+
float activation_a;
55+
float activation_b;
56+
float residual_alpha;
5457
};
5558

5659
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
5760

5861
${layout_declare_spec_const(C, "int", "stride_1_padding_0", "0")}
59-
${layout_declare_spec_const(C, "int", "activation_type", "0")}
62+
${layout_declare_spec_const(C, "bool", "has_activation", "false")}
63+
${layout_declare_spec_const(C, "int", "activation_op", "0")}
64+
${layout_declare_spec_const(C, "bool", "has_residual", "false")}
65+
${layout_declare_spec_const(C, "bool", "has_scale", "false")}
66+
${layout_declare_spec_const(C, "int", "residual_op", "0")}
67+
68+
#include "conv2d_epilogue.glslh"
6069

6170
#include "gemm/tile_utils/linear_fp_input_tile.glslh"
6271
#include "gemm/tile_utils/linear_fp_packed_weight_tile_load.glslh"
@@ -140,18 +149,22 @@ void store_output_tile_with_checks(
140149
const InCoords coords,
141150
const int n4_start,
142151
const int N4) {
152+
vec4 scale[TILE_N4];
153+
[[unroll]] for (int n4 = 0; n4 < TILE_N4; ++n4) {
154+
scale[n4] = has_scale && n4_start + n4 < N4
155+
? texelFetch(t_scale, ivec2(n4_start + n4, 0), 0)
156+
: vec4(1);
157+
}
143158
// Reuse the coordinates computed for this invocation rather than dividing by
144159
// W_out again per output texel.
145160
[[unroll]] for (int m = 0; m < TILE_M; ++m) {
146161
[[unroll]] for (int n4 = 0; n4 < TILE_N4; ++n4) {
147162
if (coords.in_bounds[m] && n4_start + n4 < N4) {
148-
VEC4_T texel = out_tile.data[m][n4];
149-
if (activation_type == 1) {
150-
texel = max(texel, VEC4_T(0.0));
151-
} else if (activation_type == 2) {
152-
texel = clamp(texel, VEC4_T(out_min), VEC4_T(out_max));
153-
}
154-
imageStore(t_out, ivec3(coords.out_pos[m], n4_start + n4), vec4(texel));
163+
const ivec3 pos = ivec3(coords.out_pos[m], n4_start + n4);
164+
imageStore(
165+
t_out,
166+
pos,
167+
apply_epilogue(vec4(out_tile.data[m][n4]), pos, scale[n4]));
155168
}
156169
}
157170
}

0 commit comments

Comments
 (0)