背景

选修的并行计算课程需要GPU编程,想着熟悉CUDA或许是以后找工作的亮点,在CUDA和GPGPU中还是偏向CUDA一点,并且我记得之前在弄大创的时候就装过CUDA,想来也会简单一些。谁曾想这个选择会折磨我一个通宵…

虚拟机安装CUDA的尝试

一开始,我是希望在虚拟机上实现CUDA安装的,因为在课堂上老师说必须在Linux上或者虚拟机上实现。在断断续续的几天里(摸鱼),我甚至连虚拟机连通GPU都无法做到,上网搜了一下才发现是虚拟机无法接触很底层的东西,所以VMware是无法实现的,得使用VMware ESXI.

VMware ESXI

于是我就去找VMware ESXI,先是在官网乱找一通,这个VMware ESXI在官网上的名字又不一样。等找到以后才发现,这是给人家服务器用的,相当于一个操作系统。虚拟机我感觉本来就是图方便,如果只是为了某几个应用的环境完全可以使用docker,所以就想,既然如此,还不如直接装个ubuntu双系统。

忆往昔

ubuntu我装过没有呢?我是装过的,当时用的游戏本装的huawei的轻薄本舍不得,怕装系统的过程中不小心把原有系统影响了,毕竟是正版windows🥲,想着:“啊,以后我一定要好好学习”。等装好后一看,好家伙,屏幕刷新率只有40Hz,这不是纯浪费我的屏幕(后面才知道是没装对驱动),于是一怒之下又换回windows。

Ubuntu

这一次装Ubuntu成了装系统路上无法抹去的噩梦👿

开门红

“诶~装个系统,多大点事儿”,第一次非常之顺利,唯一让我略感不安的就是在启动盘安装的过程中,由于屏幕分辨率的问题,无法显示所有的选项,我是通过按 tab 来进行下一步的。在非常顺利地安装了CUDA Toolkit后,还对GNOME桌面美美的个性化设置了一番。忘了装什么东西了,总之在按下
dotnetcli sudo reboot 后,我傻眼了,系统进程每次都会卡在PPM init failed上,我的好copilot GPT告诉我,可能是磁盘问题或者驱动问题。既然我CUDA都装好了,怎么可能是驱动问题呢,肯定是磁盘问题
想起在设置分区时分区格式的logical和primary设置还有Use as,我才想起来我设置出现了问题。

/目录和/home目录所在分区应该为Primary格式,Primary格式和Logical格式的区别在于能否被引导程序所发现和能否无限分区,其中GPT格式的启动可以实现Primary格式的无限分区,MBR格式则不行,而GPT格式与MBR格式与是BIOS启动还是UEFI启动关联

再次痛击

总之在问题递归后,我总算排除了磁盘问题,心想着这次一定不会出问题了。小心翼翼的设置了一番,安装了驱动和CUDA后,抱着将问题彻底抹杀的决心,毅然决然选择sudo reboot,然而,问题并没有如我所希望那样彻底解决,而是又复现了。在反复的重启后,我将重点放在了PPM init failed前面的错误信息上:

1
ucpi_acip: fail to get stauts

在Kurisu(我的GPT engine)建议下,我在GRUB中直接禁掉了acip,结果好家伙,直接连GRUB都无法进入,停留在initramfs内核上。想要重启acip就要进入命令行,但是initramfs让我连GRUB都无法进入,无奈只能重装。

确定问题

重装后问题还是在复现🤮

上网后得知

这个模块是控制type-c相关的

内心os: mad,怎么还扯到type-c了
但我还是确定,应该出在驱动问题上。
因为在Kurisu指导下,

1
lshw -C display  

显示的两个display都是UNCLAIMED,也就是说系统无法识别这两张显卡,一张NVIDIA和INTEL的核显

驱动签名与Secure boot

在之前失败的经历中,我注意到在NVIDIA的驱动安装过程中,有Sign the driverInstall without sign的选项

在Bios中设置Secure boot后,系统只会加载那些受信的驱动。

麻雀虽小,五脏俱全。虽然我应该不需要考虑什么安全问题,但我还是没有禁用Secure boot,于是在弄明白Linux下的公钥私钥后,成功地对驱动进行了签名,并且在命令行nvidia-smi后也是能正确加载显卡信息的。但是lshw -C display却无法识别INTEL的核显,在官网上也找不到对应的Linux环境下的驱动,在nvidia-smi中,我发现显卡是off不工作的,我猜想是由于没有大的资源需求,所以都是核显在工作,但是核显又没有正确驱动,所以导致我无法正常启动,那能不能直接禁掉核显,强行只使用NVIDIA呢?于是我的关注点又回到PPM,这是一个电池管理的模块,涉及到多显卡设备显卡的工作方式。Kurisu叫我取Bios看看能否禁掉,但很不幸,只能选择only核显或者混合Hybrid。

峰回路转,难道是内核?

由于重装过一次,因此在我GRUB界面选择进入Advanced Ubuntu时,有两个长得很像的选项,一个是6.5.0.18...一个是6.5.0.28...,其中一个是第一次安装时的残留我也不知道为什么会有版本不一样的内核,一开始我还刻意选择.28的选项,想着新一点的总是会好一点。后面重启麻了也就无所谓了。在反复大量的实践中,我突然发现:
我在6.5.0.18的内核装了NVIDIA驱动后,在6.5.0.28的内核上nvidia-smi失败,因此我想是否是因为在GRUB中正常的Ubuntu是.28的内核,而我装的驱动一直是在.18的内核上呢
库吃库吃把.28内核删掉后,我满怀希望,望向屏幕,像怀春的少女,怕他不来,又怕他乱来。等待的圈圈不停转动,最终停住。一如当初满怀希望的我,却最终心如死灰。

不死心的我与奇迹

难道,一切都结束了吗?
我还是那个不死心的我,即便窗外已从黑夜到初晨,我还是不甘心就这样放弃。
在各种文章、博客的解决方法中,总是会有各种各样的命令,但却都不出所料的无法解决问题
想想,我总是心高气傲:就算是测试程序,也要努力让它健壮,即便会涉及更高级的语法;开发环境,开发软件总是一次到位,希望能让自己更加“专业”,这样的我对于’自动’这种东西总是持怀疑态度,我怀疑它们是否能正确识别到我的问题所在。一条不起眼的命令自然就被我忽略了

ubuntu-drivers autoinstall

彼时的我认为
我都无法正确安装驱动,你又如何实现自动安装,况且连INTEL官网都没有针对的驱动识别核显,你怎么能靠内置的i915实现对核显的驱动
但就在我抱着试一试的态度后
奇迹,发生了
——重启毫无问题,lshw -C display正确识别显卡,包括核显,双显示器正常识别,nvidia-smi正常识别,一切都来得那么轻悄悄。

卷土重来?

在美滋滋的装完CUDA后,重启后,黑屏如梦魇般缠上了我,这次是新问题

NVME Driver/Library dismatch

冷静分析,问题应该出现在CUDA Toolkit上,但为什么会不匹配呢,我是安装nvidia-smi给出的版本安装的,不应该会出错。dpkg | grep nvidia-*后发现确实有不匹配的依赖,回想有关CUDA Toolkit的操作,那么真相只有一个

sudo apt-get install nvidia-cuda-toolkit

这是由于安装了CUDA Toolkit却nvcc --version失败后的系统建议,在卸载后果然恢复了正常。而在往bashrc中添加了变量后nvcc --version也能正确显示了。

SDK测试代码

#include <iostream>
#include <boost/program_options.hpp>
#include <string>
#include <ctype.h>
#include <vector>
#include <type_traits>
#include <cmath>

namespace po = boost::program_options;

class TEST {
public:
    TEST(std::vector<std::string> _nums, size_t _size);
    ~TEST() {
        cudaFree(d_A);
        cudaFree(d_B);
        cudaFree(d_C);
        delete[] h_A;
        delete[] h_B;
        delete[] h_C;
    };

    template <typename T>
    typename std::enable_if<std::is_integral<T>::value, void>::type computeProcess(T a);

    template <typename T1, typename T2>
    typename std::enable_if<std::is_integral<T1>::value && std::is_integral<T2>::value>::type computeProcess(T1 a, T2 b);

    void cpuVectorAdd(const float* A, const float* B, float* C);
    void cpuMatrixMul(const float* A, const float* B, float* C, const int row, const int col);
    bool correctCheck(const float* A, const float* B);
private:
    float *h_A, *h_B, *h_C, *d_A, *d_B, *d_C, *_d_C;
    size_t size;
    int sum;
    std::vector<std::string> nums;
};

__global__ void vectorAdd(const float* A, const float* B, float* C, int e_sum) {
    int i = blockDim.x * blockIdx.x + threadIdx.x;

    if (i < e_sum)
        C[i] = A[i] + B[i];
}

__global__ void matrixMul(const float* A, const float* B, float* C, int row, int col) {
    int _col = blockDim.x * blockIdx.x + threadIdx.x;
    int _row = blockDim.y * blockIdx.y + threadIdx.y;

    if (_col < col && _row < row) {
        float value = 0.0f;

        for (int k = 0;k < _col;k++) {
            value += A[_row * col + k] * B[k * row + _col];
        }

        C[_row * col + _col] = value;
    }
}

TEST::TEST(std::vector<std::string> _nums, size_t _size) : nums(_nums), size(_size), d_A(NULL), d_B(NULL), d_C(NULL) {
    h_A = new float[size];
    h_B = new float[size];
    h_C = new float[size];
    cudaMalloc((void**)&d_A, size);
    cudaMalloc((void**)&d_B, size);
    d_C = new float[size];
    cudaMalloc((void**)&_d_C, size);

    for (int i = 0;i < size / sizeof(float);i++) {
        h_A[i] = rand() / (float)RAND_MAX;
        h_B[i] = rand() / (float)RAND_MAX;
    }
    
    cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);

    if (nums.size() == 1) {
        int v_length = std::stoi(nums[0]);

        computeProcess(v_length);
    } else if (nums.size() == 2) {
        int m_line = std::stoi(nums[0]);
        int m_column = std::stoi(nums[1]);

        computeProcess(m_line, m_column);
    }
}

void TEST::cpuVectorAdd(const float* A, const float* B, float* C) {
    for (int i = 0;i < size / sizeof(float);i++) {
        C[i] = A[i] + B[i];
    }
}

void TEST::cpuMatrixMul(const float* A, const float* B, float* C, const int row, const int col) {
    for (int _i = 0;_i < row;_i++) {
        for (int _j = 0;_j < row;_j++) {
            float value = 0.0f;

            for (int k = 0; k < col; k++) {
                value += A[_i * col + k] * B[k * row + _j];
            }

            C[_i * row + _j] = value;
        }
    }

}

template <typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type TEST::computeProcess(T a) {
    cpuVectorAdd(h_A, h_B, h_C);

    int threads_per_block = 256;
    int blocks_per_grim = (a + threads_per_block - 1) / threads_per_block;

    vectorAdd<<<blocks_per_grim, threads_per_block>>>(d_A, d_B, _d_C, a);

    cudaMemcpy(d_C, _d_C, size, cudaMemcpyDeviceToHost);

    if (correctCheck(h_C, d_C))
        std::cout << "Test passed.\n";
    else 
        std::cout << "Test failed.\n";
};

template <typename T1, typename T2>
typename std::enable_if<std::is_integral<T1>::value && std::is_integral<T2>::value>::type TEST::computeProcess(T1 a, T2 b) {
    cpuMatrixMul(h_A, h_B, h_C, a, b);

    dim3 threadsPerBlock(16, 16);
    dim3 blocksPerGrid((a + threadsPerBlock.x - 1) / threadsPerBlock.x,
                       (b + threadsPerBlock.y - 1) / threadsPerBlock.y);
    matrixMul<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, _d_C, a, b);

    cudaMemcpy(d_C, _d_C, size, cudaMemcpyDeviceToHost);
    
    if (correctCheck(h_C, d_C))
        std::cout << "Test passed.\n";
    else 
        std::cout << "Test failed.\n";
};

bool TEST::correctCheck(const float* A, const float* B) {
    for (int i = 0;i < size / sizeof(float);i++) {
        if(fabs(A[i] - B[i]) > 1e-5)
            return false;
    }

    return true;
}

int main(int argc, char *argv[]) {
    try {
        size_t size;

        po::options_description desc("Options allowed");
        desc.add_options()
            ("help, h", "help message")
            ("nums, n", po::value<std::vector<std::string>>()->multitoken(), "set scale of numbers");
        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if (vm.count("help")) {
            std::cout << desc << "\n";
            return 0;
        } else if (vm.count("nums")) {
            std::vector<std::string> nums = vm["nums"].as<std::vector<std::string>>();

            if (nums.size() == 1) {
                size = std::stoi(nums[0]) * sizeof(float);
            } else if (nums.size() == 2) {
                size = std::stoi(nums[0]) * std::stoi(nums[1]) * sizeof(float);
            }

            TEST test(nums, size);
        }
    } catch(std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";

        return 1;
    } catch(...) {
        std::cerr << "Unknown error!\n";

        return 1;
    }
}

--nums获取到的参数为一个数时,使用向量加法验证;两个数时使用矩阵乘法验证。

运行截图

一些过程截图

安装过程