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

如何基于MPI集体通信实现矩阵与向量乘法?

如何用MPI集体通信实现矩阵-向量乘法?

要实现M×N矩阵与N×1向量相乘得到M×1向量,目标是利用MPI_Reduce这类集体通信API优化实现,而非常规的M个进程各计算一个结果元素的方案。你提出的“让N个进程分别计算每个xᵢ*vᵢ,再通过MPI_Reduce求和得到一行结果”的思路是可行的,但可以从数据划分和通信效率上做优化,避免多次通信带来的开销。

优化方案:列分块全局Reduce(推荐)

当进程数P ≤ N时,这种方案能大幅降低通信次数,提升整体效率:

步骤分解

  1. 数据分发
    • 主进程持有完整矩阵A和向量v,先通过MPI_Bcast将向量v广播给所有进程。
    • 将矩阵A按列分块:每个进程分配连续的N/P列(若N无法被P整除,最后一个进程多处理剩余列),主进程通过MPI_Scatterv将对应列块发送给各进程。
  2. 局部计算
    每个进程针对自己负责的列块,计算每行元素与v对应列元素的乘积,再对每行的乘积结果求和,得到一个M×1的局部部分和向量。
  3. 集体求和
    所有进程通过MPI_Reduce(指定MPI_SUM操作)将各自的局部部分和向量汇聚到主进程,主进程直接得到最终的M×1结果向量。

代码示例(C语言)

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    int rank, size;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    // 定义矩阵和向量维度
    const int M = 1000;
    const int N = 2000;

    double *A = NULL, *v = NULL, *local_A = NULL;
    double *local_sum = NULL, *result = NULL;
    int *sendcounts = NULL, *displs = NULL;

    // 主进程初始化数据
    if (rank == 0) {
        A = (double*)malloc(M * N * sizeof(double));
        v = (double*)malloc(N * sizeof(double));
        result = (double*)malloc(M * sizeof(double));
        // 填充测试数据:矩阵元素全为1,向量元素全为1
        for (int i = 0; i < M*N; i++) A[i] = 1.0;
        for (int i = 0; i < N; i++) v[i] = 1.0;
    }

    // 广播向量v到所有进程
    v = (double*)malloc(N * sizeof(double));
    MPI_Bcast(v, N, MPI_DOUBLE, 0, MPI_COMM_WORLD);

    // 计算每个进程负责的列数及元素总数
    int cols_per_proc = N / size;
    int remainder = N % size;
    int local_cols = cols_per_proc + (rank == size-1 ? remainder : 0);
    int local_elements = M * local_cols;

    // 分配局部矩阵内存
    local_A = (double*)malloc(local_elements * sizeof(double));

    // 主进程准备分发参数,分块发送矩阵列
    if (rank == 0) {
        sendcounts = (int*)malloc(size * sizeof(int));
        displs = (int*)malloc(size * sizeof(int));
        int offset = 0;
        for (int i = 0; i < size; i++) {
            sendcounts[i] = M * (cols_per_proc + (i == size-1 ? remainder : 0));
            displs[i] = offset * M;
            offset += cols_per_proc + (i == size-1 ? remainder : 0);
        }
    }
    MPI_Scatterv(A, sendcounts, displs, MPI_DOUBLE, local_A, local_elements, MPI_DOUBLE, 0, MPI_COMM_WORLD);

    // 计算局部每行的部分和
    local_sum = (double*)malloc(M * sizeof(double));
    int v_start = rank * cols_per_proc;
    for (int i = 0; i < M; i++) {
        local_sum[i] = 0.0;
        for (int j = 0; j < local_cols; j++) {
            local_sum[i] += local_A[i * local_cols + j] * v[v_start + j];
        }
    }

    // 全局Reduce求和得到最终结果
    MPI_Reduce(local_sum, result, M, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

    // 主进程验证结果(示例:所有元素应为N)
    if (rank == 0) {
        for (int i = 0; i < M; i++) {
            if (result[i] != N) {
                printf("Result mismatch at row %d: %f vs expected %d\n", i, result[i], N);
                break;
            }
        }
    }

    // 释放内存
    if (rank == 0) {
        free(A);
        free(result);
        free(sendcounts);
        free(displs);
    }
    free(v);
    free(local_A);
    free(local_sum);

    MPI_Finalize();
    return 0;
}

方案优势对比

你的原始思路需要对每行单独执行一次MPI_Reduce,当M很大时会产生大量重复通信,开销极高。而优化方案仅需一次MPI_Bcast和一次MPI_Reduce,通信次数与M无关,同时数据划分更均衡,计算/通信比更高,在大规模矩阵场景下效率提升明显。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 09:10:37