diff --git a/ggml/src/ggml-cpu/arch/x86/quants.c b/ggml/src/ggml-cpu/arch/x86/quants.c index ea54cfe44ce..92f4cc3af23 100644 --- a/ggml/src/ggml-cpu/arch/x86/quants.c +++ b/ggml/src/ggml-cpu/arch/x86/quants.c @@ -716,29 +716,42 @@ void ggml_vec_dot_q4_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi float sumf = 0; #if defined(__AVX2__) - // Initialize accumulator with zeros - __m256 acc = _mm256_setzero_ps(); - - // Main loop - for (; ib < nb; ++ib) { - /* Compute combined scale for the block */ - const __m256 d = _mm256_set1_ps( GGML_CPU_FP16_TO_FP32(x[ib].d) * GGML_CPU_FP16_TO_FP32(y[ib].d) ); - - __m256i qx = bytes_from_nibbles_32(x[ib].qs); + // Two independent FMA accumulator chains to hide the latency of the + // `_mm256_fmadd_ps` reduction: the original loop accumulated every block + // into a single `acc`, forming a serial dependency chain one FMA long per + // block. Processing two blocks per iteration into `acc0`/`acc1` keeps two + // FMAs in flight and halves the loop-control overhead. The two partial + // sums are combined before the final horizontal reduce. + const __m256i off = _mm256_set1_epi8( 8 ); + + __m256 acc0 = _mm256_setzero_ps(); + __m256 acc1 = _mm256_setzero_ps(); + + // Main loop, two blocks per iteration. + for (; ib + 1 < nb; ib += 2) { + const __m256 d0 = _mm256_set1_ps( GGML_CPU_FP16_TO_FP32(x[ib+0].d) * GGML_CPU_FP16_TO_FP32(y[ib+0].d) ); + const __m256 d1 = _mm256_set1_ps( GGML_CPU_FP16_TO_FP32(x[ib+1].d) * GGML_CPU_FP16_TO_FP32(y[ib+1].d) ); // Now we have a vector with bytes in [ 0 .. 15 ] interval. Offset them into [ -8 .. +7 ] interval. - const __m256i off = _mm256_set1_epi8( 8 ); - qx = _mm256_sub_epi8( qx, off ); + const __m256i qx0 = _mm256_sub_epi8( bytes_from_nibbles_32(x[ib+0].qs), off ); + const __m256i qx1 = _mm256_sub_epi8( bytes_from_nibbles_32(x[ib+1].qs), off ); - __m256i qy = _mm256_loadu_si256((const __m256i *)y[ib].qs); + const __m256i qy0 = _mm256_loadu_si256((const __m256i *)y[ib+0].qs); + const __m256i qy1 = _mm256_loadu_si256((const __m256i *)y[ib+1].qs); - const __m256 q = mul_sum_i8_pairs_float(qx, qy); + acc0 = _mm256_fmadd_ps( d0, mul_sum_i8_pairs_float(qx0, qy0), acc0 ); + acc1 = _mm256_fmadd_ps( d1, mul_sum_i8_pairs_float(qx1, qy1), acc1 ); + } - /* Multiply q with scale and accumulate */ - acc = _mm256_fmadd_ps( d, q, acc ); + // Trailing odd block. + for (; ib < nb; ++ib) { + const __m256 d = _mm256_set1_ps( GGML_CPU_FP16_TO_FP32(x[ib].d) * GGML_CPU_FP16_TO_FP32(y[ib].d) ); + const __m256i qx = _mm256_sub_epi8( bytes_from_nibbles_32(x[ib].qs), off ); + const __m256i qy = _mm256_loadu_si256((const __m256i *)y[ib].qs); + acc0 = _mm256_fmadd_ps( d, mul_sum_i8_pairs_float(qx, qy), acc0 ); } - sumf = hsum_float_8(acc); + sumf = hsum_float_8(_mm256_add_ps(acc0, acc1)); #elif defined(__AVX__) __m256 accum = _mm256_setzero_ps(); for (; ib + 1 < nb; ib += 2) {