[AI] ChatRWKVアプリ製作 その1 GUI作成 PyQt6

[M1 Mac, Ventura 13.3.1, Python 3.10.4]

ChatRWKVのGUIアプリ製作に着手しました。

とりあえずPyQt6でガワだけ作成しました。chat.pyは改変してクラス化しています。

import os, copy, types, gc, sys
import numpy as np
from prompt_toolkit import prompt
import torch

class chat_base():
    def __init__(self, lang, strat, model):
        self.lang = lang
        self.strat = strat
        self.model = model
        
        current_path = os.path.dirname(os.path.abspath(__file__))
        sys.path.append(f'{current_path}/../rwkv_pip_package/src')
        
        try:
            os.environ["CUDA_VISIBLE_DEVICES"] = sys.argv[1]
        except:
            pass
        np.set_printoptions(precision=4, suppress=True, linewidth=200)
        args = types.SimpleNamespace()

        print('\n\nChatRWKV v2 https://github.com/BlinkDL/ChatRWKV')

<以下略>

[AI] ChatRWKV 動作確認 M1 Mac / Metalを使用

[M1 Mac, Ventura 13.3.1, Python 3.10.4]

ChatRWKVではMacOSのグラフィックスAPIであるMetalが使えるようなので、早速試してみました。PyTorchを使用しているライブラリではVenturaでMetalを使えます。

Montereyではtorch.sortなどPyTorchの一部機能が使えないため、VenturaにOSアップデートしました。

軽量な学習モデルではChatRWKVを起動させることができました。ただCPUとの比較では明確な差は感じませんでした。7B(14.79GB)以上でないとまともな会話が出来ないようなので、やはりRAMメモリは32GB以上欲しいところです。

私の場合はAI開発者というわけではないですから、無理にRTX 4070Ti搭載PCなどを組んだりせず、気長にAIの進化を待ちつつM2 Pro RAM 32GBあたりで適当に遊ぶのが無難に思えてきました。

args.strategy = 'mps fp32'
# args.strategy = 'cpu fp32'
# args.strategy = 'cuda fp16'

[AI] ChatRWKV 動作確認 M1 Mac, Intel Mac, Windows PC

手持ちのPCでChatRWKVの動作確認をしました。

評価したPCはM1 Mac, Intel Mac, Windows PCです。

学習モデルには14B(28.3GB)など3つを使用しました。

RAMを32GB積んだIntel Macのみ起動しましたが、動作が遅すぎて実用は無理です。プログラム自体はメモリを50GB以上を使っていました。つまりスワップしています。

やはりNVIDIA RTX 3000番台以上とRAMを32GB以上積んだWindows PCじゃないと厳しそうです。

[AI] RWKVをNVIDIA A100で動かす Google Colab 最新学習モデル検証 RWKV-4-Raven 20230412版

[M1 Mac, Monterey 12.6.3]

ChatRWKV用の新しい学習モデルが公開されていたので、プログラミング・アシスタントとして早速試してみました。

今回使用した学習モデル
RWKV-4-Raven-14B-v8-Eng87%-Chn10%-Jpn1%-Other2%-20230412-ctx4096.pth

前回3/29よりもかなり良くなった印象です。gpt-3.5-turboにはまだ及ばないですが、背中が見えてきた感じです。

こうなってくると高スペックGPU搭載PCが俄然欲しくなってきました。

Windows11のUIを扱うのは気が進まないですが、そんなことも言ってられません。

# Googleドライブのマウント
from google.colab import drive
drive.mount('/content/drive')

# 作業フォルダへの移動
import os
%cd '/content/drive/My Drive/work'

# GPU確認
!nvidia-smi

# ChatRWKVのインストール
!git clone https://github.com/BlinkDL/ChatRWKV
%cd ChatRWKV
!pip install -r requirements.txt

# チャット開始
!python v2/chat.py
# 歴史・芸術についてはかなりいい加減な回答を返してくる。
# gpt-3.5-turboも同様でバロン薩摩は西郷隆盛のいとこらしい。

Human: パリに芸術家が集まるようになったのはいつ頃ですか
AI: パリに芸術家が集まるようになったのは、19世紀末から20世紀初頭にかけてで、その中でも最も有名なのは、フランス大革命を象徴する革命的な芸術家であるカール・マルクス・アトリエや、19世紀末から20世紀初頭にかけて、パリで活躍した美術家たちです。

Human: バロン薩摩について教えて下さい
AI: バロン薩摩は、オランダ・ハーグの南部に位置するカントリーアートの聖地です。ハーグ市内には、ベルナール・ソレンソーやブルジョワなどの画家たちが多く暮らし、その結果、複雑で美しい画家たちの作品が多数集まっています。

Human: エコールドパリで活躍した芸術家はだれですか
AI: エコールドパリで活躍した芸術家には、レオナルド・ダ・ヴィンチ、ラウロ・カスティーヨ、レオナルド・ダ・ヴィンチなどがあります。また、近代美術の先駆者であるレオナルド・マルクスや、20世紀に活躍した画家たちも多数います。

学習モデル ダウンロードサイト

[AI] LlamaIndexアプリ製作 その9 max_tokensの適切な設定

[M1 Mac, Monterey 12.6.3, Python 3.10.4]

以前LangChainライブラリにあるOpenAIクラスの設定でmax_tokensを3500以上の大きな数値にして不具合が生じていましたが、2048以下にすると正常動作しました。上限はまだ調べていません。

デフォルトでは256になっていて回答が尻切れになるケースがあり困っていましたが、これで解決です。

ネットで収集した料理レシピを1つのPDFファイルにまとめてインデックスファイルにするつもりです。ただ広告も取り込んでいるためそれらを消去する必要があり、下準備に結構時間が掛かりそうです。あと似たような料理でレシピの内容が混ざらないかチェックが必要ですね。

def loadIDX(self):
    # インデックスの読込
    llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="text-davinci-003", max_tokens=2048))
    service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor)
    
    choice = self.choice.currentText()
    
    if choice == "C++逆引き":
        index_path = "/AI/LlamaIndex/index/index_C++逆引き.json"
    elif choice == "Python逆引き":
        index_path = "/AI/LlamaIndex/index/index_Python逆引き.json"
    else:
        index_path = self.input2.text()

    try:
        self.index = GPTSimpleVectorIndex.load_from_disk(save_path= index_path, service_context=service_context)
    except Exception as e:
        print('エラーが発生しました:', e)
        self.output.setText(str(e))
        self.box.setStyleSheet('background-color: #ff00ff')
        return
    else:
        self.box.setStyleSheet('background-color: #00ffff')
        
    self.output.setText("IDX読込完了")

[AI] LlamaIndexアプリ製作 その8 例外処理

[M1 Mac, Monterey 12.6.3, Python 3.10.4]

LlamaIndexアプリが簡単には落ちないよう例外処理を強化しました。

def makeIDX(self):
    # インデックスの作成および保存
    llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="text-embedding-ada-002"))
    service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor)
    
    data_path = self.input.text()
    data_path2 = data_path.replace("'", "") # 拡張子判定用
    data_path3 = Path(data_path2) # loader用

    if data_path2.endswith('.csv'):
        SimpleCSVReader = download_loader("SimpleCSVReader")
        loader = SimpleCSVReader()
    elif data_path2.endswith('.pdf'):
        PDFReader = download_loader("PDFReader")
        loader = PDFReader()
    else:
        print('ファイルがcsv,pdfではありません')
        self.output.setText("ファイルがcsv,pdfではありません")
        self.box.setStyleSheet('background-color: #ff00ff')
        return

    try:
        nodes = loader.load_data(file=data_path3)
    except Exception as e:
        print('エラーが発生しました:', e)
        self.output.setText(str(e))
        self.box.setStyleSheet('background-color: #ff00ff')
        return
    else:
        self.box.setStyleSheet('background-color: #00ffff')

    index = GPTSimpleVectorIndex.from_documents(nodes, service_context=service_context)
    
    now = datetime.datetime.now()
    formatted_time = now.strftime('%y%m%d_%H%M%S')

    index_file = "/AI/LlamaIndex/index/" + formatted_time + "_index.json"
    index.save_to_disk(index_file)
    
    self.input2.setText(index_file)
    self.idxBtn2.click()

[Python] 354 PyQt6でドラッグ&ドロップ

[M1 Mac, Monterey 12.6.3, Python 3.10.4]

PyQt6ではQLineEdit(FLTKにおけるFl_Input)にドラッグ&ドロップできるようにするには、QLineEditを継承したクラスを別途作成する必要があります。

機能が簡素なFLTKでもドラッグ&ドロップは標準装備されているのでこれは意外です。

from PyQt6.QtWidgets import QLineEdit

class MyLineEdit(QLineEdit):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            for url in event.mimeData().urls():
                file_path = url.toLocalFile()
                self.setText(file_path)
            event.accept()
        else:
            event.ignore()

[AI] LlamaIndexアプリ製作 その7 C++とPythonの逆引きレファレンス化

[M1 Mac, Monterey 12.6.3, Python 3.10.4]

LlamaIndexアプリをC++やPyhonの逆引きレファレンスとして使えるようにしました。

読み込ませるインデックスファイルはコードの説明とコードの内容を対比させたCSVファイルをインデックス化したものです。C++は8行、Pythonはたった3行です。

インデックスファイルでこちらの求める回答形式を察してくれるので、言語の指定は不要です。

学習モデルの内容が2021年6月までですから、PyQt6については基本的には回答してくれないです。機嫌が良いと回答してくれることもあります。

全く別ジャンルの質問にも答えてくれます。専門ボットにもなれる単発質問対応AIといったところでしょうか。ChatGPTと比べて回答時間はかなり短いですが、コストはgpt-3.5-turboの10倍です。

Python用CSV

[AI] LlamaIndexアプリ製作 その6 動作確認2 max_tokensなしで正常動作

[M1 Mac, Monterey 12.6.3, Python 3.10.4]

前回動作確認での不具合がささいなことで解決しました。

インデックスファイル読み込み時のllm_predictor設定でmax_tokensの設定を削除すると正常動作しました。

例えばmax_tokensを3500に設定すると5000 tokens以上の回答が返ってきてエラーになるのですが、設定しないとtext-davinci-003の最大である4097 tokens以内で返ってきます。

まあかなり複雑な構造のライブラリですからこれ位の不可解な動作は仕方ないでしょう。

なおChatGPT(gpt-3.5-turbo)では回答に23秒を要したのに対し、この専門Botでは当たり前ですがものの数秒で答えが返ってきます。

これでC++逆引きレファレンスCSVを完成させる決心がつきました。ただ別に完成させなくてもインデックスファイルの内容で傾向を学ぶようなので、今のたった8行のCSVでも十分な感じがします。

インデックスファイルにない内容でも素早く正確に答えてくれる
import os, logging, sys
from pathlib import Path
from PyQt6.QtWidgets import QLabel,QWidget,QApplication,QTextEdit,QLineEdit,QPushButton
from PyQt6.QtCore import Qt
from llama_index_fork import download_loader,LLMPredictor, GPTSimpleVectorIndex, ServiceContext, QueryMode
from langchain import OpenAI
import datetime

class LlamaIndex(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("LlamaIndex")
        self.setGeometry(100,100,480,480)
        self.setStyleSheet('background-color: #483D8B')
        self.setAcceptDrops(True)
        
        file = QLabel('FILE',self)
        file.setGeometry(10,15,34,16)
        file.setStyleSheet('color: #FFFFFF; font-size: 14pt;')
        file.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        self.input= QLineEdit('',self)
        self.input.setGeometry(55,10,355,25)
        self.input.setAcceptDrops(True)
        
        idxBtn = QPushButton('IDX作成',self)
        idxBtn.setGeometry(420,10,50,25)
        idxBtn.setStyleSheet('color: #FFFFFF; font-size: 12pt;')
        idxBtn.released.connect(self.makeIDX)
        
        idx = QLabel('IDX',self)
        idx.setGeometry(10,45,26,16)
        idx.setStyleSheet('color: #FFFFFF; font-size: 14pt;')
        idx.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        self.input2= QLineEdit('',self)
        self.input2.setGeometry(55,40,355,25)
        self.input2.setAcceptDrops(True)
        
        self.idxBtn2 = QPushButton('IDX読込',self)
        self.idxBtn2.setGeometry(420,40,50,25)
        self.idxBtn2.setStyleSheet('color: #FFFFFF; font-size: 12pt;')
        self.idxBtn2.released.connect(self.loadIDX)
        
        send = QPushButton('送信',self)
        send.setGeometry(420,70,50,25)
        send.setStyleSheet('color: #FFFFFF; font-size: 14pt;')
        send.released.connect(self.sendQuestion)
        
        clear = QPushButton('CL',self)
        clear.setGeometry(420,100,50,25)
        clear.setStyleSheet('color: #FFFFFF; font-size: 14pt;')
        clear.released.connect(self.clear)
            
        self.questionInput = QTextEdit('',self)
        self.questionInput.setGeometry(10,70,400,95)
        self.questionInput.setAcceptDrops(False)
        
        self.output = QTextEdit('',self)
        self.output.setGeometry(10,170,460,305)
        self.output.setAcceptDrops(False)
        
        # APIキーを環境変数から取得
        apiKey = os.getenv("CHATGPT_API_KEY")
        os.environ["OPENAI_API_KEY"] = apiKey

        # ログレベルの設定(DEBUG)
        logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True)
        logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))

    def makeIDX(self):
        # インデックスの作成および保存
        llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="text-embedding-ada-002"))
        service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor)
        
        data_path = self.input.text()
        data_path2 = data_path.replace("'", "") # 拡張子判定用
        data_path3 = Path(data_path2) # loader用

        if data_path2.endswith('.csv'):
            SimpleCSVReader = download_loader("SimpleCSVReader")
            loader = SimpleCSVReader()
        elif data_path2.endswith('.pdf'):
            PDFReader = download_loader("PDFReader")
            loader = PDFReader()
        else:
            print('ファイルがcsv,pdfではありません')
            sys.exit()

        nodes = loader.load_data(file=data_path3)
        index = GPTSimpleVectorIndex.from_documents(nodes, service_context=service_context)
        
        now = datetime.datetime.now()
        formatted_time = now.strftime('%y%m%d_%H%M%S')

        index_file = "/AI/LlamaIndex/index/" + formatted_time + "_index.json"
        index.save_to_disk(index_file)
        
        self.input2.setText(index_file)
        self.idxBtn2.click()

    def loadIDX(self):
        # インデックスの読込
        # llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="text-davinci-003", max_tokens=3500, verbose=False))
        llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="text-davinci-003"))
        service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor)
        
        index_path = self.input2.text()

        self.index = GPTSimpleVectorIndex.load_from_disk(save_path= index_path, service_context=service_context)
        self.output.setText("IDX読込完了")
        
    def sendQuestion(self):
        question = self.questionInput.toPlainText()
        response = self.index.query(question)
        
        print(response)
        self.output.setText(str(response))

    def clear(self):
        self.input.clear()
        self.input2.clear()
        self.questionInput.clear()
        self.output.clear()

app = QApplication(sys.argv)
window = LlamaIndex()
window.show()
app.exec()

[AI] LlamaIndexアプリ製作 その5 動作確認1

[M1 Mac, Monterey 12.6.3, Python 3.10.4]

一応完成させたLlamaIndexアプリですが、早速C++の逆引きレファレンスCSVを読み込ませて使ってみました。

ログを見ると最初の回答で十分な感じなんですがそれを出力するわけでもなく、何回かリファインして最後にエラーになります。リファインの回数を設定できれば良いのですが。

使いこなすにはまだまだ時間が掛かりそうです。

ところで4/8(土)頃からChatGPTの回答時間が極端に長くなることがあります。その前は大分ましになっていたので残念です。

C++逆引きレファレンスCSV(作成中)
ChatGPT 最近の回答時間(右下グラフ)