@@ -811,6 +811,7 @@ struct vk_device_struct {
811811 vk_pipeline pipeline_set_rows_i32[GGML_TYPE_COUNT];
812812 vk_pipeline pipeline_set_rows_i64[GGML_TYPE_COUNT];
813813 vk_pipeline pipeline_norm_f32;
814+ vk_pipeline pipeline_norm_mul_add_f32;
814815 vk_pipeline pipeline_group_norm_f32;
815816 vk_pipeline pipeline_rms_norm_f32;
816817 vk_pipeline pipeline_rms_norm_mul_f32;
@@ -3640,6 +3641,14 @@ static void ggml_vk_load_shaders(vk_device& device) {
36403641 // Xe2/Xe3 with coopmat enabled - warptile performance tuning
36413642 l_warptile = { 512, 128, 128, 16, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
36423643 l_warptile_mmq = { 512, 128, 128, 32, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
3644+ } else if (device->vendor_id == VK_VENDOR_ID_ARM) {
3645+ // QVAC-21257 iter2: Mali/Valhall (16-wide subgroup, no coopmat). The q8_0 MMQ matmuls
3646+ // dominate the CLIP vision-encode (~65 %, ~90 GFLOPS/s, run #80). The generic large MMQ
3647+ // tile uses only 8 warps/workgroup (block_size 128); widen to a 32-warp shape (block_size
3648+ // 512, wm=16/wn=32 — the valid 16-wide layout the Intel Xe2 branch uses) to raise GPU
3649+ // occupancy on the dominant matmul path. Falls back automatically (shmem check below) if
3650+ // it doesn't fit. Float MMQ path only (q8_0 goes through mul_mat_q_f16).
3651+ l_warptile_mmq = { 512, 128, 128, 32, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
36433652 }
36443653
36453654 l_wg_denoms = { l_warptile[1], l_warptile[2], 1 };
@@ -5024,7 +5033,11 @@ static void ggml_vk_load_shaders(vk_device& device) {
50245033 }
50255034 ggml_vk_create_pipeline(device, device->pipeline_mul_mat_vec_nc_f16_f32, "mul_mat_vec_nc_f16_f32", mul_mat_vec_nc_f16_f32_len, mul_mat_vec_nc_f16_f32_data, "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_nc_push_constants), {1, 1, 1}, {}, 1);
50265035
5027- ggml_vk_create_pipeline(device, device->pipeline_norm_f32, "norm_f32", norm_f32_len, norm_f32_data, "main", 2, sizeof(vk_op_push_constants), {1, 1, 1}, {}, 1);
5036+ // norm.comp now uses the binary head (4 bindings: input, weight, bias, output) and
5037+ // spec constants {norepeat, do_multiply, do_add}. Plain norm sets both to 0; the fused
5038+ // NORM+MUL+ADD path sets both to 1. Same bytecode for both, mirroring rms_norm.
5039+ ggml_vk_create_pipeline(device, device->pipeline_norm_f32, "norm_f32", norm_f32_len, norm_f32_data, "main", 4, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {0, 0, 0}, 1, true);
5040+ ggml_vk_create_pipeline(device, device->pipeline_norm_mul_add_f32, "norm_mul_add_f32", norm_f32_len, norm_f32_data, "main", 4, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {0, 1, 1}, 1, true);
50285041 ggml_vk_create_pipeline(device, device->pipeline_group_norm_f32, "group_norm_f32", group_norm_f32_len, group_norm_f32_data, "main", 2, sizeof(vk_op_push_constants), {1, 1, 1}, {}, 1);
50295042
50305043 ggml_vk_create_pipeline(device, device->pipeline_rms_norm_f32, "rms_norm_f32", rms_norm_f32_len, rms_norm_f32_data, "main", 4, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {0, 0}, 1, true);
@@ -10924,7 +10937,8 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const
1092410937 return nullptr;
1092510938 case GGML_OP_NORM:
1092610939 if (src0->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
10927- return ctx->device->pipeline_norm_f32;
10940+ // num_additional_fused_ops == 2 means fused NORM+MUL+ADD (layernorm scale+bias).
10941+ return ctx->num_additional_fused_ops == 2 ? ctx->device->pipeline_norm_mul_add_f32 : ctx->device->pipeline_norm_f32;
1092810942 }
1092910943 return nullptr;
1093010944 case GGML_OP_GROUP_NORM:
@@ -11491,6 +11505,10 @@ static void ggml_vk_op_f32(ggml_backend_vk_context * ctx, vk_context& subctx, co
1149111505
1149211506 switch (op) {
1149311507 case GGML_OP_NORM:
11508+ // One workgroup per row/channel/sample, matching norm.comp's stride-based
11509+ // indexing (gl_WorkGroupID.{x,y,z} = {row, channel, sample}).
11510+ elements = { (uint32_t)ne01, (uint32_t)ne02, (uint32_t)ne03 };
11511+ break;
1149411512 case GGML_OP_RMS_NORM_BACK:
1149511513 case GGML_OP_L2_NORM:
1149611514 case GGML_OP_SOFT_MAX:
@@ -12550,10 +12568,51 @@ static void ggml_vk_geglu_back(ggml_backend_vk_context * ctx, vk_context& subctx
1255012568 ggml_vk_op_f32<vk_op_push_constants>(ctx, subctx, src0, src1, nullptr, nullptr, dst, GGML_OP_GEGLU_BACK, { (uint32_t)ggml_nelements(dst), (uint32_t)dst->ne[0], 0.0f, 0.0f, 0.0f, 0.0f });
1255112569}
1255212570
12553- static void ggml_vk_norm(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) {
12554- float * op_params = (float *)dst->op_params;
12571+ static void ggml_vk_norm(ggml_backend_vk_context * ctx, vk_context& subctx, const struct ggml_cgraph * cgraph, int node_idx) {
12572+ ggml_tensor * norm = cgraph->nodes[node_idx];
12573+ float * op_params = (float *)norm->op_params;
12574+
12575+ ggml_tensor * dst;
12576+ const ggml_tensor * src0; // norm input (A)
12577+ const ggml_tensor * src1; // weight (B)
12578+ const ggml_tensor * src2; // bias (C)
12579+
12580+ if (ctx->num_additional_fused_ops == 2) {
12581+ // fused NORM + MUL + ADD (layernorm scale + bias)
12582+ ggml_tensor * mul = cgraph->nodes[node_idx + 1];
12583+ ggml_tensor * add = cgraph->nodes[node_idx + 2];
12584+ ggml_tensor * weight = mul->src[0] == norm ? mul->src[1] : mul->src[0];
12585+ ggml_tensor * bias = add->src[0] == mul ? add->src[1] : add->src[0];
12586+ // shader uses plain col indexing (no stride), requires zero misalignment
12587+ GGML_ASSERT(get_misalign_bytes(ctx, weight) == 0);
12588+ GGML_ASSERT(get_misalign_bytes(ctx, bias) == 0);
12589+ dst = add;
12590+ src0 = norm->src[0];
12591+ src1 = weight;
12592+ src2 = bias;
12593+ } else {
12594+ dst = norm;
12595+ // plain norm: bind weight/bias to the input so all 4 descriptors are valid.
12596+ // do_multiply/do_add spec constants are false, so they are never read.
12597+ src0 = norm->src[0];
12598+ src1 = norm->src[0];
12599+ src2 = norm->src[0];
12600+ }
12601+
12602+ const uint32_t src0_type_size = ggml_type_size(src0->type);
12603+ const uint32_t src1_type_size = ggml_type_size(src1->type);
12604+ const uint32_t src2_type_size = ggml_type_size(src2->type);
1255512605
12556- ggml_vk_op_f32<vk_op_push_constants>(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_NORM, { (uint32_t)src0->ne[0], (uint32_t)src0->ne[1], op_params[0], 0.0f, 0.0f, 0.0f });
12606+ vk_op_binary_push_constants bin {
12607+ (uint32_t)ggml_nelements(src0),
12608+ (uint32_t)src0->ne[0], (uint32_t)src0->ne[1], (uint32_t)src0->ne[2], (uint32_t)src0->ne[3], (uint32_t)src0->nb[0] / src0_type_size, (uint32_t)src0->nb[1] / src0_type_size, (uint32_t)src0->nb[2] / src0_type_size, (uint32_t)src0->nb[3] / src0_type_size,
12609+ (uint32_t)src1->ne[0], (uint32_t)src1->ne[1], (uint32_t)src1->ne[2], (uint32_t)src1->ne[3], (uint32_t)src1->nb[0] / src1_type_size, (uint32_t)src1->nb[1] / src1_type_size, (uint32_t)src1->nb[2] / src1_type_size, (uint32_t)src1->nb[3] / src1_type_size,
12610+ (uint32_t)src2->ne[0], (uint32_t)src2->ne[1], (uint32_t)src2->ne[2], (uint32_t)src2->ne[3], (uint32_t)src2->nb[0] / src2_type_size, (uint32_t)src2->nb[1] / src2_type_size, (uint32_t)src2->nb[2] / src2_type_size, (uint32_t)src2->nb[3] / src2_type_size,
12611+ 0,
12612+ op_params[0], 0.0f, 0,
12613+ };
12614+
12615+ ggml_vk_op_f32<vk_op_binary_push_constants>(ctx, subctx, src0, src1, src2, nullptr, dst, GGML_OP_NORM, std::move(bin));
1255712616}
1255812617
1255912618static void ggml_vk_group_norm(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) {
@@ -14859,7 +14918,7 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr
1485914918
1486014919 break;
1486114920 case GGML_OP_NORM:
14862- ggml_vk_norm(ctx, compute_ctx, src0, node );
14921+ ggml_vk_norm(ctx, compute_ctx, cgraph, node_idx );
1486314922
1486414923 break;
1486514924 case GGML_OP_GROUP_NORM:
@@ -15768,6 +15827,50 @@ static bool ggml_vk_can_fuse(const ggml_backend_vk_context * ctx, const struct g
1576815827 return false;
1576915828 }
1577015829 }
15830+ if (ops.size() == 3 && ops.begin()[0] == GGML_OP_NORM && ops.begin()[1] == GGML_OP_MUL && ops.begin()[2] == GGML_OP_ADD) {
15831+ // fused layernorm (NORM) + MUL (scale) + ADD (bias)
15832+ const ggml_tensor *norm = cgraph->nodes[node_idx];
15833+ const ggml_tensor *mul = cgraph->nodes[node_idx + 1];
15834+ const ggml_tensor *add = cgraph->nodes[node_idx + 2];
15835+
15836+ // f32-only
15837+ if (norm->src[0]->type != GGML_TYPE_F32 || norm->type != GGML_TYPE_F32 ||
15838+ mul->type != GGML_TYPE_F32 ||
15839+ add->type != GGML_TYPE_F32) {
15840+ return false;
15841+ }
15842+ // MUL must consume the NORM result as src[0], ADD must consume the MUL result as src[0].
15843+ if (mul->src[0] != norm || add->src[0] != mul) {
15844+ return false;
15845+ }
15846+ const ggml_tensor *weight = mul->src[1];
15847+ const ggml_tensor *bias = add->src[1];
15848+ if (weight->type != GGML_TYPE_F32 || bias->type != GGML_TYPE_F32) {
15849+ return false;
15850+ }
15851+ // weight/bias must be 1-D row vectors broadcast over rows, matching norm->ne[0].
15852+ if (weight->ne[0] != norm->ne[0] || bias->ne[0] != norm->ne[0]) {
15853+ return false;
15854+ }
15855+ if (ggml_nrows(weight) != 1 || ggml_nrows(bias) != 1) {
15856+ return false;
15857+ }
15858+ // mul/add outputs must match norm input shape (no broadcast batch dims)
15859+ if (!ggml_are_same_shape(norm->src[0], add)) {
15860+ return false;
15861+ }
15862+ // contiguous and aligned (shader assumes contiguous rows and zero misalignment)
15863+ if (!ggml_is_contiguous(weight) || !ggml_is_contiguous(bias)) {
15864+ return false;
15865+ }
15866+ if (!ggml_is_contiguous_rows(norm->src[0])) {
15867+ return false;
15868+ }
15869+ if (get_misalign_bytes(ctx, weight) != 0 || get_misalign_bytes(ctx, bias) != 0) {
15870+ return false;
15871+ }
15872+ }
15873+
1577115874 auto const &mm_add_ok = [&](const ggml_tensor *mul, const ggml_tensor *add) {
1577215875 const ggml_tensor *bias = add->src[0] == mul ? add->src[1] : add->src[0];
1577315876
@@ -16324,6 +16427,14 @@ static ggml_status ggml_backend_vk_graph_compute(ggml_backend_t backend, ggml_cg
1632416427 // they are overwritten, and one workgroup per row. So close enough.
1632516428 op_srcs_fused_elementwise[0] = true;
1632616429 op_srcs_fused_elementwise[1] = true;
16430+ } else if (ggml_vk_can_fuse(ctx, cgraph, i, { GGML_OP_NORM, GGML_OP_MUL, GGML_OP_ADD })) {
16431+ ctx->num_additional_fused_ops = 2;
16432+ fusion_string = "NORM_MUL_ADD";
16433+ // norm is not elementwise, but whole rows are consumed by one workgroup per
16434+ // row and the mean/variance are computed before output is written. So close enough.
16435+ op_srcs_fused_elementwise[0] = true;
16436+ op_srcs_fused_elementwise[1] = true;
16437+ op_srcs_fused_elementwise[2] = true;
1632716438 } else if (ggml_can_fuse_subgraph(cgraph, i, { GGML_OP_ROPE, GGML_OP_VIEW, GGML_OP_SET_ROWS }, { i + 2 }) &&
1632816439 ggml_check_edges(cgraph, i, rope_view_set_rows_edges) &&
1632916440 ggml_vk_can_fuse_rope_set_rows(ctx, cgraph, i)) {
@@ -16652,6 +16763,10 @@ static void ggml_vk_graph_optimize(ggml_backend_t backend, struct ggml_cgraph *
1665216763 if (!used[c] &&
1665316764 is_src_of(graph->nodes[j], graph->nodes[c]) &&
1665416765 !(j == c+1 && c == current_set.back() && graph->nodes[c]->op == GGML_OP_RMS_NORM && graph->nodes[j]->op == GGML_OP_MUL) &&
16766+ !(j == c+1 && c == current_set.back() && graph->nodes[c]->op == GGML_OP_NORM && graph->nodes[j]->op == GGML_OP_MUL) &&
16767+ // Keep NORM->MUL->ADD consecutive: allow MUL->ADD only when MUL follows a NORM.
16768+ !(j == c+1 && c == current_set.back() && graph->nodes[c]->op == GGML_OP_MUL && graph->nodes[j]->op == GGML_OP_ADD &&
16769+ current_set.size() >= 2 && graph->nodes[current_set[current_set.size() - 2]]->op == GGML_OP_NORM) &&
1665516770 !(j == c+1 && c == current_set.back() && graph->nodes[c]->op == GGML_OP_MUL_MAT && graph->nodes[j]->op == GGML_OP_ADD) &&
1665616771 !(j == c+1 && c == current_set.back() && graph->nodes[c]->op == GGML_OP_MUL_MAT_ID && graph->nodes[j]->op == GGML_OP_ADD_ID) &&
1665716772 !(j == c+1 && c == current_set.back() && graph->nodes[c]->op == GGML_OP_MUL_MAT_ID && graph->nodes[j]->op == GGML_OP_MUL) &&
@@ -17936,11 +18051,24 @@ static ggml_backend_dev_t ggml_backend_vk_reg_get_device(ggml_backend_reg_t reg,
1793618051 return devices[device];
1793718052}
1793818053
18054+ static bool ggml_backend_vk_supports_efficient_fa(ggml_backend_t backend) {
18055+ ggml_backend_vk_context * ctx = (ggml_backend_vk_context *)backend->context;
18056+ return ctx->device->coopmat2 || ctx->device->coopmat1_fa_support;
18057+ }
18058+
18059+ static void * ggml_backend_vk_reg_get_proc_address(ggml_backend_reg_t reg, const char * name) {
18060+ GGML_UNUSED(reg);
18061+ if (strcmp(name, "ggml_backend_supports_efficient_fa") == 0) {
18062+ return (void *)ggml_backend_vk_supports_efficient_fa;
18063+ }
18064+ return NULL;
18065+ }
18066+
1793918067static const struct ggml_backend_reg_i ggml_backend_vk_reg_i = {
1794018068 /* .get_name = */ ggml_backend_vk_reg_get_name,
1794118069 /* .get_device_count = */ ggml_backend_vk_reg_get_device_count,
1794218070 /* .get_device = */ ggml_backend_vk_reg_get_device,
17943- /* .get_proc_address = */ NULL ,
18071+ /* .get_proc_address = */ ggml_backend_vk_reg_get_proc_address ,
1794418072};
1794518073
1794618074ggml_backend_reg_t ggml_backend_vk_reg() {
0 commit comments