アプリケーション開発ポータルサイト
ServerNote.NET
カテゴリー【C/C++Debian
CUDA13環境下でGPU使用版のllama.cppを導入しC++ライブラリを使う
POSTED BY
2025-12-03

当方の環境は以下の通りである。

Debian GNU/Linux 12 (bookworm)
Linux 6.1.0-41-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.158-1 (2025-11-09) x86_64 GNU/Linux
NVIDIA-SMI 580.105.08 Driver Version: 580.105.08 CUDA Version: 13.0
VGA compatible controller: NVIDIA Corporation AD106 [GeForce RTX 4060 Ti] (rev a1)
Python 3.11.2

GPU版のllama.cppコマンドおよびライブラリは、以下のようにして導入する。

git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
cmake -B build -DLLAMA_CURL=OFF -DGGML_CUDA=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j

このようにすると、build/bin以下に、llama-cli, llama-server, libllama.so, libggml.so, libggml-base.so, libggml-cuda.soが出来上がる。なおincludeはinclude, ggml/include内にある。

単体テストコマンド

llama.cpp/build/bin/llama-cli -m Qwen3VL-8B-Instruct-Q8_0.gguf -p "###INPUT:以降の文章から出発時間と思われる表現と、到着時間と思われる表現を抜き出しYYYYMMDDHHMM形式で推測しJSONで出力せよ。また、出発地と思われる表現と、目的地と思われる表現を抜き出してJSONで出力せよ。JSON以外は出力しないこと。なお本日は20251203である。###INPUT:「いやー、明日午後に新宿を出て、神奈川へ向かいます。あさって着けばいいかな。」"

ggml_cuda_init: found 1 CUDA devices:
  Device 0: NVIDIA GeForce RTX 4060 Ti, compute capability 8.9, VMM: yes
print_info: n_layer          = 36
load_tensors: offloading 36 repeating layers to GPU
load_tensors: offloading output layer to GPU
load_tensors: offloaded 37/37 layers to GPU
load_tensors:   CPU_Mapped model buffer size =   630.59 MiB
load_tensors:        CUDA0 model buffer size =  7669.77 MiB
.......................................................................................
llama_context: constructing llama_context
llama_context: n_seq_max     = 1
llama_context: n_ctx         = 4096
llama_context: n_ctx_seq     = 4096
llama_context: n_batch       = 2048
llama_context: n_ubatch      = 512
llama_context: causal_attn   = 1
llama_context: flash_attn    = auto
llama_context: kv_unified    = false
llama_context: freq_base     = 5000000.0
llama_context: freq_scale    = 1
llama_context:  CUDA_Host  output buffer size =     0.58 MiB
llama_kv_cache:      CUDA0 KV buffer size =   576.00 MiB
llama_kv_cache: size =  576.00 MiB (  4096 cells,  36 layers,  1/1 seqs), K (f16):  288.00 MiB, V (f16):  288.00 MiB
llama_context: Flash Attention was auto, set to enabled
llama_context:      CUDA0 compute buffer size =   304.75 MiB
llama_context:  CUDA_Host compute buffer size =    16.02 MiB
assistant
{
  "出発時間": "202512041300",
  "到着時間": "202512050000",
  "出発地": "新宿",
  "目的地": "神奈川"
}

GPUもちゃんと使っているし、解析もバッチリできている。

C/C++言語ライブラリ

次は、一緒にビルドされたシェアードライブラリとインクルードファイルを使って、C言語に組み込み、コードから呼んでみる。以下のようなコードを書く。

C/C++llama_cpp_sample.cppGitHub Source
#include "llama.h"

#include <iostream>
#include <vector>
#include <string>

int main(int argc, char **argv) {
    if (argc < 3) {
        std::cerr << "usage: " << argv[0] << " model.gguf \"prompt text\"\n";
        return 1;
    }

    std::string model_path = argv[1];
    std::string prompt = argv[2];

    // 生成最大長
    int n_predict = 512;

    // **各パラメータ**
    int n_ctx     = 4096; // 総コンテキスト長
    int n_batch   = 2048; // 1回のdecodeで処理できるトークン数
    int n_ubatch  = 512;  // 内部マイクロバッチ(VRAM節約)
    int n_layer   = 36;   // GPUに載せる層数(-1 = すべての層)

    // GPUでoffloadする層数
    llama_model_params mparams = llama_model_default_params();
    mparams.n_gpu_layers = n_layer;

    // **モデルロード**
    llama_backend_init();
    ggml_backend_load_all();

    llama_model *model = llama_model_load_from_file(model_path.c_str(), mparams);
    if (!model) {
        std::cerr << "ERROR: failed to load model\n";
        return 1;
    }
    const llama_vocab *vocab = llama_model_get_vocab(model);

    // **コンテキスト作成**
    llama_context_params cparams = llama_context_default_params();
    cparams.n_ctx   = n_ctx;
    cparams.n_batch = n_batch;
    cparams.n_ubatch = n_ubatch;

    llama_context *ctx = llama_init_from_model(model, cparams);
    if (!ctx) {
        std::cerr << "ERROR: failed to init context\n";
        llama_model_free(model);
        return 1;
    }

    // プロンプトをトークン化
    int32_t tcount = llama_tokenize(vocab, prompt.c_str(), prompt.size(),
                                    nullptr, 0, false, true);
    if (tcount < 0) tcount = -tcount;
    std::vector<llama_token> prompt_tokens(tcount);
    llama_tokenize(vocab, prompt.c_str(), prompt.size(),
                   prompt_tokens.data(), prompt_tokens.size(),
                   false, true);

    // **プロンプトを評価**
    llama_batch batch = llama_batch_get_one(prompt_tokens.data(), prompt_tokens.size());
    if (llama_decode(ctx, batch) != 0) {
        std::cerr << "ERROR: prompt eval failed\n";
        llama_free(ctx);
        llama_model_free(model);
        return 1;
    }

    std::string result;
    int n_generated = 0;

    while (n_generated < n_predict) {
        const float *logits = llama_get_logits(ctx);
        int32_t n_vocab = llama_vocab_n_tokens(vocab);

        // greedy
        int best = 0;
        float best_log = logits[0];
        for (int i = 1; i < n_vocab; ++i) {
            if (logits[i] > best_log) {
                best_log = logits[i];
                best = i;
            }
        }

        // EOS判定
        if (llama_vocab_is_eog(vocab, best))
            break;

        // トークン → 文字列
        char buf[256];
        int32_t cc = llama_token_to_piece(vocab, best, buf, sizeof(buf), 0, true);
        if (cc > 0) result.append(buf, cc);

        // 次のステップ:1トークンだけ評価
        batch = llama_batch_get_one(&best, 1);
        if (llama_decode(ctx, batch) != 0)
            break;

        ++n_generated;
    }

    // **まとめてドン!出力**
    std::cout << result << std::endl;

    llama_free(ctx);
    llama_model_free(model);
    llama_backend_free();
    return 0;
}

ビルド

g++ -std=c++17 -O3 llama_cpp_sample.cpp -I llama.cpp/include -I llama.cpp/ggml/include -L llama.cpp/build/bin -lllama -ldl -pthread -lggml -lggml-base -lggml-cuda

実行

env LD_LIBRARY_PATH=llama.cpp/build/bin ./a.out Qwen3VL-8B-Instruct-Q8_0.gguf "###INPUT:以降の文章から出発時間と思われる表現と、到着時間と思われる表現を抜き出しYYYYMMDDHHMM形式で推測しJSONで出力せよ。また、出発地と思われる表現と、目的地と思われる表現を抜き出してJSONで出力せよ。JSON以外は出力しないこと。なお本日は20251203である。###INPUT:「いやー、明日午後に新宿を出て、神奈川へ向かいます。あさって着けばいいかな。」"

ggml_cuda_init: found 1 CUDA devices:
  Device 0: NVIDIA GeForce RTX 4060 Ti, compute capability 8.9, VMM: yes
llama_model_load_from_file_impl: using device CUDA0 (NVIDIA GeForce RTX 4060 Ti) (0000:65:00.0) - 14550 MiB free
llama_model_loader: loaded meta data with 30 key-value pairs and 399 tensors from Qwen3VL-8B-Instruct-Q8_0.gguf (version GGUF V3 (latest))
print_info: n_layer          = 36
load_tensors: offloading 36 repeating layers to GPU
load_tensors: offloaded 36/37 layers to GPU
load_tensors:   CPU_Mapped model buffer size =  8300.36 MiB
load_tensors:        CUDA0 model buffer size =  7039.16 MiB
.......................................................................................
llama_context: constructing llama_context
llama_context: n_seq_max     = 1
llama_context: n_ctx         = 4096
llama_context: n_ctx_seq     = 4096
llama_context: n_batch       = 2048
llama_context: n_ubatch      = 512
llama_context: causal_attn   = 1
llama_context: flash_attn    = auto
llama_context: kv_unified    = false
llama_context: freq_base     = 5000000.0
llama_context: freq_scale    = 1
set_abort_callback: call
llama_context:      CUDA0 compute buffer size =   935.34 MiB
llama_context:  CUDA_Host compute buffer size =    16.02 MiB
llama_context: graph nodes  = 1267
llama_context: graph splits = 4 (with bs=512), 3 (with bs=1)
###OUTPUT:{"departure_time": "202512041300", "arrival_time": "202512050000", "departure_place": "新宿", "arrival_place": "神奈川"}

いい感じ。しっかりGPUも使っているっぽい。

システム常駐サーバー

最初にモデルをロードして初期化した後はシステムに常駐し、外部からのプロンプトを受け付け、結果をソケット経由で返すものが望ましい。llama-serverがまさにそれ。

llama.cpp/build/bin/llama-server --model Qwen3VL-8B-Instruct-Q8_0.gguf --port 8668 --host 0.0.0.0 --n-gpu-layers -1

ggml_cuda_init: found 1 CUDA devices:
  Device 0: NVIDIA GeForce RTX 4060 Ti, compute capability 8.9, VMM: yes
init: using 19 threads for HTTP server
start: binding port with default address family
main: loading model
srv    load_model: loading model 'Qwen3VL-8B-Instruct-Q8_0.gguf'
llama_model_load_from_file_impl: using device CUDA0 (NVIDIA GeForce RTX 4060 Ti) (0000:65:00.0) - 14550 MiB free
llama_model_loader: loaded meta data with 30 key-value pairs and 399 tensors from Qwen3VL-8B-Instruct-Q8_0.gguf (version GGUF V3 (latest))
print_info: n_layer          = 36
llama_context: n_ctx         = 4096
llama_context: n_ctx_seq     = 4096
llama_context: n_batch       = 2048
llama_context: n_ubatch      = 512
main: model loaded
main: server is listening on http://0.0.0.0:8668
main: starting the main loop...
srv  update_slots: all slots are idle

立ち上がったらnvidia-smiすれば、GPUを占有しているのがわかるはず。テストは以下のように打つ。

curl http://localhost:8668/completion \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "###INPUT:以降の文章から出発時間と思われる表現と、到着時間と思われる表現を抜き出しYYYYMMDDHHMM形式で推測しJSONで出力せよ。また、出発地と思われる表現と、目的地と思われる表現を抜き出してJSONで出力せよ。JSON以外は出力しないこと。なお本日は20251203である。###INPUT:「いやー、明日午後に新宿を出て、神奈川へ向かいます。あさって着けばいいかな。」",
    "n_predict": 128,
    "temperature": 0.2
  }'

出力

{"index":0,"content":"###OUTPUT:{\"departure_time\": \"202512041300\", \"arrival_time\": \"202512050000\", \"departure_place\": \"新宿\", \"arrival_place\": \"神奈川\"}"

なかなかやるなあ。llama-cpp-pythonと違い、本家本体のllama.cppソースビルドであれば、最新のQwenモデルも問題なく使える。

※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

【キーワード検索】