Repository: ggml-org/llama.cpp
Pull request: ggml-cpu: optimise s390x multiply extend instructions

This PR optimizes the multiply extend vector instructions for Q4_0, Q4_K, Q5_K, and Q6_K quantizations by using the fused multiply-add instruction instead of separating them into multiple instruction calls. We notice a performance improvement of about 28.77% and 16.35% for Prompt Processing and Token Generation respectively.

Old Instruction Set
```assembly
_Z13vec_muloe_oldDv16_aS_:
        vmob    %v0,%v24,%v26
        vmeb    %v24,%v24,%v26
        vah     %v24,%v0,%v24
        br      %r14
```

New Instruction Set
```assembly
_Z13vec_muloe_newDv16_aS_:
        vmob    %v0,%v24,%v26
        vmaeb   %v24,%v24,%v26,%v0
        br      %r14
```

### Performance

IBM Granite 3.3 2B Instruct model was used for this benchmark.

| model                    | size     | params | backend | threads | test  | t/s upstream/master | t/s PR       | speedup |
|--------------------------|----------|--------|---------|---------|-------|---------------------|--------------|---------|
| granite 3B Q4_0          | 1.35 GiB | 2.53 B | BLAS    | 1       | pp512 | 11.24 ± 0.36        | 11.41 ± 0.84 | 1.02    |
| granite 3B Q4_0          | 1.35 GiB | 2.53 B | BLAS    | 1       | tg128 | 1.38 ± 0.10         | 1.56 ± 0.01  | 1.13    |
| granite 3B Q4_K - Medium | 1.44 GiB | 2.53 B | BLAS    | 1       | pp512 | 10.87 ± 0.29        | 10.94 ± 1.41 | 1.01    |
| granite 3B Q4_K - Medium | 1.44 GiB | 2.53 B | BLAS    | 1       | tg128 | 1.91 ± 0.25         | 2.25 ± 0.01  | 1.18    |
| granite 3B Q5_K - Medium | 1.68 GiB | 2.53 B | BLAS    | 1       | pp512 | 9.97 ± 1.13         | 11.36 ± 1.18 | 1.14    |
| granite 3B Q5_K - Medium | 1.68 GiB | 2.53 B | BLAS    | 1       | tg128 | 1.91 ± 0.19         | 2.11 ± 0.01  | 1.10    |
| granite 3B Q6_K          | 1.94 GiB | 2.53 B | BLAS    | 1       | pp512 | 8.63 ± 1.94         | 11.53 ± 0.06 | 1.34    |
| granite 3B Q6_K          | 1.94 GiB | 2.53 B | BLAS    | 1       | tg128 | 1.83 ± 0.07         | 1.97 ± 0.08  | 1.08    |

### Verification

```console
$ build/bin/test-quantize-fns
Testing f32
Testing f16
Testing q4_0
Testing q4_1
Testing q5_0
Testing q5_1
Testing q8_0
Testing q8_1
Testing q2_K
Testing q3_K
Testing q4_K
Testing q5_K
Testing q6_K
Testing q8_K
Testing iq2_xxs
Testing iq2_xs
Testing iq3_xxs
Testing iq1_s
Testing iq4_nl
Testing iq3_s
Testing iq2_s
Testing iq4_xs
Testing i8
Testing i16
Testing i32
Testing i64
Testing f64
Testing iq1_m
Testing bf16
Testing tq1_0
Testing tq2_0
Testing mxfp4
```

Changed files:
filename: ggml/src/ggml-cpu/arch/s390/quants.c
patch: @@ -181,11 +181,11 @@ void ggml_vec_dot_q4_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
         const int8x16_t v_yh = vec_xl(QK8_0/2, y[ib].qs);
 
         const int16x8_t v_xylso = vec_mulo(v_xls, v_yl);
-        const int16x8_t v_xylse = vec_mule(v_xls, v_yl);
+        const int16x8_t v_xyl = vec_meadd(v_xls, v_yl, v_xylso);
         const int16x8_t v_xyhso = vec_mulo(v_xhs, v_yh);
-        const int16x8_t v_xyhse = vec_mule(v_xhs, v_yh);
+        const int16x8_t v_xyh = vec_meadd(v_xhs, v_yh, v_xyhso);
 
-        int16x8_t v_xy_ = v_xylso + v_xylse + v_xyhso + v_xyhse; v_xy_ += vec_reve(v_xy_);
+        int16x8_t v_xy_ = v_xyl + v_xyh; v_xy_ += vec_reve(v_xy_);
 
         const float32x4_t v_xy = vec_float(vec_unpackh(v_xy_));
         const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x[ib].d) * GGML_CPU_FP16_TO_FP32(y[ib].d));
@@ -890,8 +890,7 @@ void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
         const int16x8_t v_minsh = (int16x8_t)vec_unpackh((uint8x16_t)v_mins8);
 
         const int32x4_t v_minso = vec_mulo(v_ysums, v_minsh);
-        const int32x4_t v_minse = vec_mule(v_ysums, v_minsh);
-        const int32x4_t v_mins = v_minso + v_minse;
+        const int32x4_t v_mins = vec_meadd(v_ysums, v_minsh, v_minso);
         sumf -= dmin * (v_mins[0] + v_mins[1] + v_mins[2] + v_mins[3]);
 
         const uint8_t * scales = (const uint8_t *)utmp;
@@ -1004,8 +1003,7 @@ void ggml_vec_dot_q5_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
         const int16x8_t v_minsh = (int16x8_t)vec_unpackh(v_mins8);
 
         const int32x4_t v_minsho = vec_mulo(v_ysums, v_minsh);
-        const int32x4_t v_minshe = vec_mule(v_ysums, v_minsh);
-        const int32x4_t v_mins = vec_add(v_minsho, v_minshe);
+        const int32x4_t v_mins = vec_meadd(v_ysums, v_minsh, v_minsho);
         const int32_t mins = vec_hsum_i32x4(v_mins);
 
         const uint8_t * scales = (const uint8_t *)utmp;
@@ -1110,10 +1108,10 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
         const int16x8_t v_scaleh = vec_unpackl(v_scale);
 
         const int32x4_t v_minslo = vec_mulo(v_ysumsl, v_scalel);
-        const int32x4_t v_minsle = vec_mule(v_ysumsl, v_scalel);
+        const int32x4_t v_minsl = vec_meadd(v_ysumsl, v_scalel, v_minslo);
         const int32x4_t v_minsho = vec_mulo(v_ysumsh, v_scaleh);
-        const int32x4_t v_minshe = vec_mule(v_ysumsh, v_scaleh);
-        const int32x4_t v_mins = v_minslo + v_minsle + v_minsho + v_minshe;
+        const int32x4_t v_minsh = vec_meadd(v_ysumsh, v_scaleh, v_minsho);
+        const int32x4_t v_mins = vec_add(v_minsl, v_minsh);
 
         const int32_t mins = vec_hsum_i32x4(v_mins);