You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何_mm256_mul_epi32指令输出向量中隔位元素为零?

问题原因分析与解决方法

为什么会出现隔位零?

你遇到的问题核心是对_mm256_mul_epi32这个intrinsic的行为理解有误:

  • 该函数对应AVX指令VPMULDQ,它的作用是将256位向量视为4个64位元素(每个64位包含两个32位整数),仅对每个64位元素的低32位整数进行乘法,输出64位结果。
  • 具体来说,它只会处理输入向量中索引为0、2、4、6的32位元素,每个乘积结果占64位(即输出向量中两个连续的32位位置)。因为你的乘积都未超过2^32,所以每个64位结果的高32位为0,这就是你看到隔位输出0的原因。
  • 输入中索引为1、3、5、7的元素完全没有参与运算,自然不会出现在输出里。

正确实现向量点积的方法

如果要计算两个8元素32位整数向量的点积,需要根据是否需要处理溢出选择不同的实现方式:

方式1:忽略溢出(使用32位乘积)

用_mm256_mullo_epi32计算逐元素32位乘积,再对乘积向量做水平求和:

#include <iostream>
#include <immintrin.h>
#define ALIGN 64
using namespace std;

int main()
{
    int* a = (int*)aligned_alloc(ALIGN, sizeof(int)*8);
    int* b = (int*)aligned_alloc(ALIGN, sizeof(int)*8);
    
    a[0]=103; a[1]=198; a[2]=105; a[3]=115; a[4]=81; a[5]=255; a[6]=74; a[7]=236;
    b[0]=8; b[1]=172; b[2]=163; b[3]=32; b[4]=62; b[5]=247; b[6]=73; b[7]=132;

    __m256i vec_a = _mm256_load_si256((__m256i*)a);
    __m256i vec_b = _mm256_load_si256((__m256i*)b);
    
    // 计算逐元素32位乘积(溢出会被截断)
    __m256i mul_result = _mm256_mullo_epi32(vec_a, vec_b);
    
    // 拆分256位向量为两个128位向量,分别求和
    __m128i low_half = _mm256_extracti128_si256(mul_result, 0);
    __m128i high_half = _mm256_extracti128_si256(mul_result, 1);
    
    // 128位向量内水平求和
    low_half = _mm_add_epi32(low_half, _mm_shuffle_epi32(low_half, _MM_SHUFFLE(1,0,3,2)));
    low_half = _mm_add_epi32(low_half, _mm_shuffle_epi32(low_half, _MM_SHUFFLE(2,3,0,1)));
    int sum_low = _mm_cvtsi128_si32(low_half);
    
    high_half = _mm_add_epi32(high_half, _mm_shuffle_epi32(high_half, _MM_SHUFFLE(1,0,3,2)));
    high_half = _mm_add_epi32(high_half, _mm_shuffle_epi32(high_half, _MM_SHUFFLE(2,3,0,1)));
    int sum_high = _mm_cvtsi128_si32(high_half);
    
    int dot_product = sum_low + sum_high;
    
    cout << "点积结果:" << dot_product << endl;
    
    // 手动验证
    int manual = 103*8 + 198*172 + 105*163 + 115*32 + 81*62 +255*247 +74*73 +236*132;
    cout << "手动计算结果:" << manual << endl;
    
    free(a);
    free(b);
    return 0;
}

方式2:避免溢出(使用64位乘积)

需要分别处理奇偶索引元素的乘积,再合并求和:

#include <iostream>
#include <immintrin.h>
#define ALIGN 64
using namespace std;

int main()
{
    int* a = (int*)aligned_alloc(ALIGN, sizeof(int)*8);
    int* b = (int*)aligned_alloc(ALIGN, sizeof(int)*8);
    
    a[0]=103; a[1]=198; a[2]=105; a[3]=115; a[4]=81; a[5]=255; a[6]=74; a[7]=236;
    b[0]=8; b[1]=172; b[2]=163; b[3]=32; b[4]=62; b[5]=247; b[6]=73; b[7]=132;

    __m256i vec_a = _mm256_load_si256((__m256i*)a);
    __m256i vec_b = _mm256_load_si256((__m256i*)b);
    
    // 计算偶数索引元素的64位乘积
    __m256i mul_even = _mm256_mul_epi32(vec_a, vec_b);
    
    // 将奇数索引元素移到偶数位置,以便用_mm256_mul_epi32计算
    __m256i vec_a_odd = _mm256_permute4x64_epi64(vec_a, 0x55); // 交换每个64位lane内的两个32位元素
    __m256i vec_b_odd = _mm256_permute4x64_epi64(vec_b, 0x55);
    
    // 计算奇数索引元素的64位乘积
    __m256i mul_odd = _mm256_mul_epi32(vec_a_odd, vec_b_odd);
    
    // 合并两个乘积向量的64位元素
    __m256i total_mul = _mm256_add_epi64(mul_even, mul_odd);
    
    // 水平求和64位元素
    __m128i low_half = _mm256_extracti128_si256(total_mul, 0);
    __m128i high_half = _mm256_extracti128_si256(total_mul, 1);
    
    low_half = _mm_add_epi64(low_half, _mm_shuffle_epi32(low_half, _MM_SHUFFLE(1,0,3,2)));
    high_half = _mm_add_epi64(high_half, _mm_shuffle_epi32(high_half, _MM_SHUFFLE(1,0,3,2)));
    
    __m128i sum_total = _mm_add_epi64(low_half, high_half);
    long long dot_product = _mm_cvtsi128_si64(sum_total);
    
    cout << "64位点积结果:" << dot_product << endl;
    
    // 手动验证
    long long manual = (long long)103*8 + (long long)198*172 + (long long)105*163 + (long long)115*32 + 
                      (long long)81*62 + (long long)255*247 + (long long)74*73 + (long long)236*132;
    cout << "手动计算结果:" << manual << endl;
    
    free(a);
    free(b);
    return 0;
}

内容的提问来源于stack exchange,提问作者Abhishek Ghosh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 14:10:30