[C++] 387 ChatAIアプリの製作 その56 API使用頻度の推移 (2023/3 – 2025/2)

[Mac M2 Pro 12CPU RAM 16GB, Sonoma 14.7.1, clang++ 16.0.0]

2023年3月にChatAIアプリ初版を製作して2年が経とうとしています。

APIを使用して作成されたJSONファイル数の推移をまとめてみました。

ChatGPT、Perplexity、つい最近ではDeepSeekとますます充実してきました。

gpt-4oとDeepSeekにPythonでのグラフ作成スクリプトを考えさせました。gpt-4oは一発で動きましたが、DeepSeek考案スクリプトはこちらで一回修正してあげてもダメでした。これにはガッカリです。

蒸留、量子化しなければまともに働くかどうかは不明です。

今月2502は2/8まで

 [C++] 386 ChatAIアプリの製作 その55 DeepSeek日本語量子化GGUFファイルの検証 llama.cpp

[Mac M2 Pro 12CPU RAM 16GB, Sonoma 14.7.1, clang++ 16.0.0]
GGUF: cyberagent-DeepSeek-R1-Distill-Qwen-14B-Japanese-Q5_K_M.gguf 10.5GB

DeepSeek日本語版をサイバーエージェントが公開したので、量子化ファイルを動作検証しました。

前回記事のGGUFと同様に時間はかかりますが、コード提案能力はなかなかのものです。これまで量子化するとかなり能力が落ちるという印象でしたが、DeepSeekではそのようなことはありませんでした。蒸留が効いているのでしょうか。

プログラミング用途ではgpt-4oやPerplexityからこちらに移行します。

ローカルLLMをまともに動かすにはユニファイドメモリ 64GB以上が必要、という認識が見事に覆りました。最新GGUFはRAM 16GBで使用に耐えるレベルにて動作します。

VRAM 12GBのWindowsPCではどうなるか検証してみたいです。

 [C++] 385 ChatAIアプリの製作 その54 DeepSeek量子化GGUFファイルの検証 llama.cpp

[Mac M2 Pro 12CPU RAM 16GB, Sonoma 14.7.1, clang++ 16.0.0]
GGUF: DeepSeek-R1-Distill-Llama-8B-Q4_K_M.gguf 4.9GB

話題のDeepSeekを量子化ファイルで試してみました。

プログラミング用途ではこれまで扱ってきたGGUFと比べてかなり優秀です。gpt-4oと比べると回答内容はまずまず、レスポンス時間はかなり長いです。

DeepSeekチャットサーバ起動コマンド

cd /AI/llama.cpp && ./bin/llama-server -m models/DeepSeek-R1-Distill-Llama-8B-Q4_K_M.gguf -ngl 1 -c 4096

 [C++] 384 ChatAIアプリの製作 その53 推論モデルo3-miniの導入

[Mac M2 Pro 12CPU, Sonoma 14.7.1, clang++ 16.0.0]
[C++] 383は非公開

普段はgpt-4oで十分足りていますが、評判がいいようなのでo3-miniも使えるようにしました。temperatureを削除して、reasoning_effortを追加しています。

if(countQ == 0 && load == false){
    cout << "question1回目\n" << question << endl;
    cout << "model: \n" << model.c_str() << endl;

    if (model.find("gpt") != string::npos){
        // gpt-4
        requestData = "{\"model\":\"" + model + "\", \"messages\":[{\"role\":\"system\",\"content\":\"" + systemStr + "\"},{\"role\":\"user\",\"content\":\"" + question + "\"}], \"temperature\":0.0}";
    } else if (model.find("mini") != string::npos){
        // o3-mini
        requestData = "{\"model\":\"" + model + "\", \"messages\":[{\"role\":\"system\",\"content\":\"" + systemStr + "\"},{\"role\":\"user\",\"content\":\"" + question + "\"}], \"reasoning_effort\":\"medium\"}";
    } else if (model.find("llama.cpp") != string::npos){
        // llama.cpp
        requestData = "{\"model\":\"" + model + "\", \"messages\":[{\"role\":\"system\",\"content\":\"" + systemStr + "\"},{\"role\":\"user\",\"content\":\"" + question + "\"}], \"temperature\":0.0}";
    } else if (model.find("sonar") != string::npos){
        // perplexity
        requestData = "{\"model\":\"" + model + "\", \"messages\":[{\"role\":\"system\",\"content\":\"" + systemStr + "\"},{\"role\":\"user\",\"content\":\"" + question + "\\n日本語で回答してください。" + "\"}], \"temperature\":0.0}";
    }

Makefileで並列処理 / jオプション その1

CPUのコアをフルに使ってビルドする場合はjオプションを追加します。諸説ありますが、基本的にはコア数を付けてビルドします。

# M2 Pro Mac miniは12コア
make all -j12

Makefileはそのまま使えます。今のところ最初のmakeコマンドでは失敗しますが、2回目で正常にビルドできます。

オブジェクトファイルの作成を並列処理できるので、その工程は大体1/cppファイル数(12以下)の処理時間に短縮できます。今回は8ファイルなので約1/8です。かなりの高速化になります。ただしMakefileを複数用いる場合は何らかの工夫が必要になるようです。

比較的簡単なプログラムであればものの数秒でビルドできてしまいます。ここまで高速化できると、C++を扱うホビープログラマにとってはM1で十分です。

ただXcodeでSwiftのプロジェクトをビルドするとそれなりに時間がかかるため、M2 Proでもまだ足りないですね。

今は1回目に必ずビルド失敗する状況ですから、最初からmake allコマンドでシングルコア・ビルドしていれば良いわけでちょっと微妙な改良ではあります。ビルドに数分かかるような大きめのプログラムでしたら導入の価値ありです。

[C++] 382 SwitchBot管理アプリの製作 その9 UserDefaultsでデータ永続化 Objective-C++, wxWidgets

[Mac M2 Pro 12CPU, Sonoma 14.5, wxWidgets 3.2.5]

設定データを保存する方法としてCSVを考えていましたが、macOSアプリですからOS固有の方法を活用してみたくなりました。

Objective-C++で書いた関数を使って設定データをUserDefaultsで管理するようにしました。

以前開発していたBBSブラウザと同様、cppファイルとmmファイルがプロジェクトに混在する形となります。

#include <iostream>
#import <Foundation/Foundation.h>

void saveToUserDefaults(const std::string& key, const std::string& value) {
    NSString *nsKey = [NSString stringWithUTF8String:key.c_str()];
    NSString *nsValue = [NSString stringWithUTF8String:value.c_str()];
    [[NSUserDefaults standardUserDefaults] setObject:nsValue forKey:nsKey];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

std::string getFromUserDefaults(const std::string& key) {
    NSString *nsKey = [NSString stringWithUTF8String:key.c_str()];
    NSString *nsValue = [[NSUserDefaults standardUserDefaults] stringForKey:nsKey];
    if (nsValue) {
        return std::string([nsValue UTF8String]);
    } else {
        return "";
    }
}

[C++] 381 SwitchBot管理アプリの製作 その8 エアコン自動運転 wxWidgets

[Mac M2 Pro 12CPU, Sonoma 14.5, wxWidgets 3.2.5]

エアコン自動運転機能を実装しました。

エアコン動作温度を0.1度単位で設定し、温度幅を純正アプリの0.5度から最小0.1度にすることができます。

これでエアコン操作をこのアプリに任せて、なおかつ浴室の状態を常にモニタリングできるようになりました。グラフは過去の数値を最大4時間分プロットしています。

次はiPhone版やiPad版をC++(Objective-C++)ベースで作れるかどうか検討します。

[C++] 380 SwitchBot管理アプリの製作 その7 グラフ埋め込み wxWidgets

[Mac M2 Pro 12CPU, Sonoma 14.5, wxWidgets 3.2.5]

C++にはmatplotlibライブラリに匹敵するグラフ作成ライブラリが見つからず、ラッパーであるmatplotlib-cppは古いためかアプリがクラッシュします。

Py_Initialize()などを使ってPythonスクリプトをモジュール化しましたが、wxWidgetsのonTimer関数ではうまく動作せず落ちてしまうので、スクリプトのままターミナルで実行しました。

これで絶対湿度など数値とグラフをチェックし、進捗を確認できるようになりました。

#include "CallPythonScript.h"
#include <cstdio>
#include <string>
#include <iostream>
#include <fstream>

void CallPythonScript(const std::string& csvPath, const std::string& pngPath) {
    std::string scriptPath = "GenerateGraph.py";
    std::string command = "/usr/local/bin/python " + scriptPath + " " + csvPath + " " + pngPath + " > /dev/null 2>&1";

    FILE* pipe = popen(command.c_str(), "r");
    if (!pipe) {
        std::cerr << "Failed to execute Python script" << std::endl;
        return;
    }

    int result = pclose(pipe);
    if (result != 0) {
        std::cerr << "Failed to execute Python script" << std::endl;
    } else {
        std::ifstream file(pngPath);
        if (!file) {
            std::cerr << "PNG file not found or cannot be opened" << std::endl;
        } else {
            std::cout << "PNG file created successfully" << std::endl;
        }
    }
}

[C++] 379 SwitchBot管理アプリの製作 その6 浴室乾燥チェック実装 wxWidgets

[Mac M2 Pro 12CPU, Sonoma 14.5, wxWidgets 3.2.5]

洗濯物が乾燥しているであろう絶対湿度まで下がる過程で段階的にアプリの表示が変わるようにしました。

絶対湿度は5分間の移動平均値(右側の数値)で判定しています。

あとは実際の浴室乾燥でテストしながらアプリを調整していきます。

これでガス代を少しは節約できそうです。また浴室の相対湿度を70%以下に近づけるよう意識することでカビの発生を低減できます。

[C++] 378 SwitchBot管理アプリの製作 その5 APIリクエスト wxWidgets

[Mac M2 Pro 12CPU, Sonoma 14.5, wxWidgets 3.2.5]

エアコンを操作するAPIリクエストのところでつまづきました。

Pythonでは100%成功していましたが、C++では成功率50%以下になりました。色々調べた結果、HMAC_CTXによる署名生成に問題があることが判明し、これを使わずに署名生成して解決しました。

Pythonではシンプルに正常なコードになる場合でも、C++に移植するのは容易ではないことがたまにあります。

#include "signGenerate.h"
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <openssl/buffer.h>
#include <uuid/uuid.h>

std::string generate_uuid() {
    uuid_t uuid;
    uuid_generate_random(uuid);
    char uuid_str[37];
    uuid_unparse(uuid, uuid_str);
    return std::string(uuid_str);
}

std::string base64_encode(const unsigned char* input, int length) {
    BIO *bmem, *b64;
    BUF_MEM *bptr;

    b64 = BIO_new(BIO_f_base64());
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);

    BIO_write(b64, input, length);
    BIO_flush(b64);
    BIO_get_mem_ptr(b64, &bptr);

    std::string output(bptr->data, bptr->length - 1);
    BIO_free_all(b64);

    return output;
}

std::string hmac_sha256(const std::string& key, const std::string& data) {
    unsigned char* digest;
    digest = HMAC(EVP_sha256(), key.c_str(), key.length(), (unsigned char*)data.c_str(), data.length(), NULL, NULL);

    return std::string((char*)digest, SHA256_DIGEST_LENGTH);
}