Tech Notes

ConoHaがGPUを提供し始めたので早速Vulkanで叩いてみた

この記事はConoHa Advent Calender 2023 14日目の記事です。

あのConoHaがGPUを提供し始めたらしいともっぱらの噂。 GPUをC++で直接殴る話を書いている人間としては聞き捨てならないので早速申し込んで使ってみました。

近頃の若者はGPUと言えばPythonだ、NumPyだ、CUDAだ、などと言うばかりで嘆かわしい限り。ここはひとつ、OSやGPUベンダに依存しないC/C++向け低レイヤ開発APIたるVulkanで触ってみなければなりません。

申し込み作業

GPUサーバーは他の種類のサーバーと同じようにConoHaアカウントを作れば即利用できる訳ではなく、申し込んで審査を受ける必要があるので申し込みます。

https://www.conoha.jp/vps/gpu/

いくつかの個人情報と希望する利用情報を入力。

ConoHaが提供しているGPUは2023年12月現在「NVIDIA L4」と「NVIDIA H100」の2機種があるのでどちらか選びます。(H100の方が強い。)

とりあえずお試し出来ればいいなという用途なのでL4を1台ということで申し込みます。

返事のメールは割とすぐに返ってきました。このメールが来た上で本人確認書類をメールで送信して手続き完了となります。あとは審査結果を待つのみ。

審査結果も1日半くらいで返ってきました。もうちょっとかかるかと思っていたので思ったよりスムーズ!

L4 × 1台を頼んだらH100 × 2台とL4 × 4台を許可されました。 かっきり申し込んだ分だけの台数しか利用を許されない、という訳ではないようです。H100も試せそう。

ホームページで「事業者向け」と銘打っているので実は審査段階で落とされるんじゃないかと少し危ぶんでいたのですが、興味を持っただけの素人個人でも全然問題ありませんでした。

無事コントロールパネルから選べるようになりました。

L4インスタンス1時間169円、H100インスタンス1時間1398円也。

正直に言うと競合サービスに比べて割高な感じはしますが、クレジットカードを持ってない人間としてはConoHaチャージで使えるConoHaのサーバはお手軽で安心感があります。アプリケーションテンプレートといった独自の強みもありますし、サービスはまだ始まったばかりですし、今後良い感じになっていって有力な選択肢になっていくと良いな~と思います。

サーバー構築

OSイメージから構築する場合はUbuntu 22.04固定、またアプリケーションイメージとして現在以下の3つが提供されています。

  • AUTOMATIC 1111
    • WebUI付きのStable Diffusion
  • InvokeAI
    • これもUI付きのStable Diffusion
  • NVIDIA Container Toolkit
    • Dockerコンテナなどの中からGPUを使いやすくするためのツール

が、今回は特にアプリケーションイメージのお世話にはなることはなく、素のUbuntuをインストールします。

とりあえずL4のインスタンスを構築。

デフォルトのネームタグが「GPU-xxx」になる

構築完了まで何分か要します。

GPUサーバだからといってコントロールパネルは特に変わらない様子。GPU使用率が見れたりとかはしないようです。

Vulkanの開発環境をインストール。

sudo apt update
sudo apt install g++ vulkan-tools libvulkan-dev -y

あとNVIDIAのGPUドライバも入れます。いくらvulkanがベンダ非依存のAPIと言ってもNVIDIAのドライバを入れなければNVIDIAのGPUは認識できません。

sudo apt install nvidia-driver-535 -y

よく分からないので最新のドライバをインストールしました。

これで環境構築はオーケー。

スペックを確認してみる

とりあえずシステムに刺さっているGPUの情報を出してみます。

#include <iostream>
#include <vulkan/vulkan.hpp>
using namespace std;

int main() {
    vk::InstanceCreateInfo instCreateInfo;
    vk::UniqueInstance instance = vk::createInstanceUnique(instCreateInfo);

    std::vector<vk::PhysicalDevice> physDevices = instance->enumeratePhysicalDevices();
    cout << "GPU x " << physDevices.size() << endl;

    for(const auto& physDevice : physDevices) {
        const auto props = physDevice.getProperties();
        cout << props.deviceName << " (ID=" << props.deviceID << ")" << endl;

        cout << "  queue families" << endl;
        const auto queueProps = physDevice.getQueueFamilyProperties();
        for (size_t i = 0; i < queueProps.size(); i++) {
            cout << "    queue family " << i << ": "
                << (queueProps[i].queueFlags & vk::QueueFlagBits::eGraphics ? 'G' : '_')
                << (queueProps[i].queueFlags & vk::QueueFlagBits::eCompute ? 'C' : '_')
                << (queueProps[i].queueFlags & vk::QueueFlagBits::eTransfer ? 'T' : '_')
                << (queueProps[i].queueFlags & vk::QueueFlagBits::eSparseBinding ? 'S' : '_')
                << (queueProps[i].queueFlags & vk::QueueFlagBits::eProtected ? 'P' : '_')
                << " x " << queueProps[i].queueCount << endl;
        }

        cout << "  memory types" << endl;
        const auto memProps = physDevice.getMemoryProperties();
        for (size_t i = 0; i < memProps.memoryTypeCount; i++) {
            std::cout << "    memoryType " << i << ": "
                << "heapIndex: " << memProps.memoryTypes[i].heapIndex << ", "
                << (memProps.memoryTypes[i].propertyFlags & vk::MemoryPropertyFlagBits::eDeviceLocal ? "DeviceLocal " : "")
                << (memProps.memoryTypes[i].propertyFlags & vk::MemoryPropertyFlagBits::eHostVisible ? "HostVisible " : "")
                << (memProps.memoryTypes[i].propertyFlags & vk::MemoryPropertyFlagBits::eHostCoherent ? "HostCoherent " : "")
                << (memProps.memoryTypes[i].propertyFlags & vk::MemoryPropertyFlagBits::eHostCached ? "HostCached " : "")
                << std::endl;
        }
        
        cout << "  memory heaps" << endl;
        for (size_t i = 0; i < memProps.memoryHeapCount; i++) {
            std::cout << "    memoryHeap " << i << ": "
                << "size: " << memProps.memoryHeaps[i].size << " bytes"
                << std::endl;
        }
    }
    return 0;
}

-lvulkanオプションを付けてビルド。

g++ main.cpp -lvulkan
./a.out

こんな感じで出ました。ちゃんとNVIDIA L4が認識されています。

GPU x 2
NVIDIA L4 (ID=10168)
  queue families
    queue family 0: GCTS_ x 16
    queue family 1: __TS_ x 2
    queue family 2: _CTS_ x 8
    queue family 3: __TS_ x 4
    queue family 4: __TS_ x 1
  memory types
    memoryType 0: heapIndex: 1,
    memoryType 1: heapIndex: 0, DeviceLocal
    memoryType 2: heapIndex: 1, HostVisible HostCoherent
    memoryType 3: heapIndex: 1, HostVisible HostCoherent HostCached
    memoryType 4: heapIndex: 0, DeviceLocal HostVisible HostCoherent
  memory heaps
    memoryHeap 0: size: 24152899584 bytes
    memoryHeap 1: size: 101299150848 bytes
llvmpipe (LLVM 15.0.7, 256 bits) (ID=0)
  queue families
    queue family 0: GCT__ x 1
  memory types
    memoryType 0: heapIndex: 0, DeviceLocal HostVisible HostCoherent HostCached
  memory heaps
    memoryHeap 0: size: 2147483648 bytes

24GBのVRAMもしっかり確認できました。

L4と別に認識されているllvmpipeというのはどうやら本物のGPUではなくデフォルトで入っているソフトウェア的に作られたデバイスらしい?(詳しくないためよく分からない...)

実はこの辺の情報は別にプログラムを書かなくてもvulkaninfoで出せば手っ取り早いのですが、自分で書いたプログラムから認識されてることの確認としてやってみました。

いろいろ描画

実は機械学習には疎いので、とりあえず適当に何か描かせてみます。今はすっかりAIブームですが、GPUの本来のお仕事はコンピュータグラフィックスです。

VPSにディスプレイは付いてないので、STBライブラリで描画した画像を書き出してみます。

三角形の描画

とりあえずVulkanでHello World的なやつと言ったらコレ。

ソースコード

このはちゃんの画像テクスチャを貼ってみる

清楚かわいいこのはちゃんを表示してみます。

ソースコード

いけるいける。

当たり前ですが、GPUなのでAIを始めとしたGPGPU用途だけでなくグラフィックス用途にも使えます。

例えば超ハイポリでレンダリングが大変なモデルデータとかをConoHaのGPUサーバーに投げて動画生成をやらせるとかももしかしたらあり…かも……

コンピュートシェーダによる計算

さすがにCGだけやって終わりという訳には行かないので、何かしらGPGPUらしくコンピュートシェーダを使ったものを。

本当はMNISTの学習とかやらせてみたかったのですが、勉強も実装も間に合わなかったので行列積の計算をして逃げます。

こんな感じでいいんですかね?ちゃんと実装すれば良い感じに行列サイズを一般化できそうですが……コンピュートシェーダナニモワカラナイ

ソースコード

最近はsubgroup命令とかいうのを使うのがイケてる仕草らしいです。

#version 450
#extension GL_KHR_shader_subgroup_arithmetic : enable

// 中略

void main() {
    float elem_prod = matrixInA.elem[gl_WorkGroupID.y * param.matAWBH + gl_LocalInvocationID.x] * matrixInB.elem[gl_LocalInvocationID.x * param.matBW + gl_WorkGroupID.x];
    elem_prod = subgroupAdd(elem_prod);
    matrixOut.elem[gl_WorkGroupID.y * param.matBW + gl_WorkGroupID.x] = elem_prod;
}

出力

matrixA:
-0.80173 0.00029254 0.453746 -0.0318885 -0.821589 -0.194135 0.216023 0.445183
-0.520808 -0.158906 -0.634355 0.542592 0.519817 0.593988 0.30926 -0.784064
-0.220685 -0.137691 0.249739 0.931318 -0.433101 -0.758591 0.531733 -0.607703
0.901922 0.317358 0.955323 -0.654286 0.733593 -0.0154729 -0.191013 0.138698
0.333731 -0.255685 -0.609868 -0.620606 -0.64577 0.81769 0.868495 -0.93405
0.258448 0.151325 -0.141476 0.0768751 -0.887067 -0.619971 -0.720184 0.463082
0.297728 -0.993098 0.269211 0.631356 0.367053 -0.0963407 -0.322198 0.466915
-0.0669645 -0.989954 -0.234588 -0.548091 0.592761 0.631975 0.0251471 -0.516385
matrixB:
-0.591677 0.659061 0.520934 0.0184022 0.0708649 -0.0852441 -0.493247 0.638159
-0.810586 -0.42038 -0.293909 0.215648 0.664259 0.379186 -0.770521 -0.568888
0.145581 -0.437547 0.639787 0.926192 -0.553992 0.290814 0.561636 -0.969181
0.550133 0.027796 0.354018 -0.763865 0.328853 -0.07094 0.203889 0.371007
0.449393 -0.569881 -0.650561 -0.966446 0.143556 0.822252 0.0866715 0.705947
0.0926449 -0.258632 -0.000558615 0.912107 -0.210434 -0.439232 -0.500085 0.37763
0.891808 -0.760247 0.664539 -0.970337 0.845215 0.21383 0.148277 0.245876
-0.109974 -0.893967 -0.517717 -0.197138 0.22687 -0.425362 0.500859 0.952095
matrixA x matrixB =
0.279132 -0.771726 0.308956 0.749496 -0.111986 -0.530783 0.924445 -1.13973
1.29376 0.0321536 -0.165434 -1.15197 0.420537 0.327332 -0.465161 0.494961
1.06701 0.411081 1.36515 -1.18336 0.46983 0.322435 0.661388 -0.999081
-0.869122 -0.368007 0.0801617 0.904547 -0.491096 0.877844 -0.173795 -0.176473
0.242396 0.908299 1.11949 0.571534 0.244966 -0.565854 -1.24059 -0.103604
-1.40316 0.970163 -0.114039 0.746986 -0.278041 -0.819303 0.0504453 -0.35204
0.832692 0.156741 0.148147 -0.663626 -0.673525 -0.291824 1.16435 1.31635
0.910538 0.400693 -0.189975 0.0676355 -0.856405 0.0358355 0.0327349 0.716098

おわりに

ConoHaのGPUもVulkanから使えることが確認できました。なんかもうちょっと強いGPUの威力を分かりやすく見せられるアプリケーションが見せられればと思っていたのですが、時間がなかった&いいネタが思いつかなかったので次回作にご期待ください。


コメント