Ubuntu日和
【第62回】LM StudioでPythonのGUIコードを生成させたら殊の外優秀だった
2024年10月26日 06:17
筆者はプログラマーというわけではないが、たまにプログラムを書く。主に使用する言語はPythonだ。
とはいえ、ライブラリをいい感じに組み合わせて動かしているだけで、それをプログラムと呼んでいいのか自分でも葛藤があるくらいだ。自信はまったくないし、各ライブラリの作者にいくら感謝してもし足りない。
GUIを表示するアプリケーションはあまり書く機会がないものの、ゼロではない。そういった際には、PythonデフォルトのGUIライブラリであるtkinterを使用している。正直なところ見た目はあまりよくないが、情報が豊富でライセンスに関してあまり難しく考えなくてもいいところが気に入っている。
もともとプログラム自体が得意というわけではないので、何であってもGUIをイチから書くというのはなかなかにしんどく、本を買って勉強したりしたものの、苦手感を払拭するには至っていない。まぁそれでも書くのだが。
ある日、本誌の担当編集である劉デスクからLM StudioというローカルLLMを動作させるアプリケーションを紹介された。いろいろと遊んでいるうちに、こいつでGUIを生成させてみるとどのくらいの精度のものができるか気になった。そして、今回の記事がその成果となる。
今回使用するハードウェアとソフトウェア
今回使用するハードウェアは次の通りだ。
分類 | メーカー | 型番 |
---|---|---|
CPU | Intel | Core i5-13500 |
メモリー | Crucial | CT32G4DFD832A |
マザーボード | ASRock | B660M Pro RS |
グラフィックボード | MSI | GeForce RTX 3060 AERO ITX 12G OC |
SSD | WDC | EDS500G2B0C |
ケース | Silver Stone | SST-SG11B |
電源 | 玄人志向 | KRPW-SXP600W/90+ |
なんのことはない筆者の検証用PCだが、ポイントはGeForce RTX 3060を使用しているところだ。今回はNVIDIAのVRAMを12GB以上搭載したdGPUをターゲットとする。VRAMが8GB以下では、実用的なLLMを動作させるにはやや心許ない。
執筆時点でのSystem Requirementsによると、Ubuntuのバージョンは20.04 LTSと22.04 LTSで、それより新しいバージョンはあまりテストしていないということだ。したがって今回は22.04 LTSを使用する。
事前にプロプライエタリなドライバをインストールしておこう。「追加のドライバー」で確認できる。
ダウンロードと実行
LM Studioはインストール不要なAppImageという形式で提供されている。AppImageに関しては第4回で紹介しているので、ここでは繰り返さない。
LM StudioのサイトからLinux版をダウンロードしよう。
ただし実行するには libfuse2 というパッケージが必要なので、起動前にインストールしておこう。
実行するにはダウンロードしたファイルを右クリックして「アクセス権」にある「プログラムとして実行」にチェックを入れ、閉じたあとダブルクリックするだけだ。
設定の確認とモデルのダウンロード
起動したら、右上の「Skip onboarding」をクリックしてウィザードを飛ばしてしまおう。紹介の都合上その方がいいからで、そのまま進んでしまって何か問題があるわけではない。
通常の画面に遷移したら、何はなくとも右下の歯車をクリックする。
「App Settings」が開くので、まずは「言語」を「日本語 (Beta)」に変更しよう。
続いて「Runtimes」をクリックし、「CUDA llama.cpp」が選択されていることを確認しよう。このようになっていない場合、NVIDIAのプロプライエタリなドライバが正しくインストールされていないことが考えられる。
「Model Search」をクリックし、モデル(いうまでもなくLLMのMだ)をダウンロードする。今回は「Qwen2.5-14B-Instruct」をダウンロードする。9GBくらいあるので、それなりに時間がかかるだろう。サブウィンドウが表示され、進捗が表示されるのは分かりやすい。
14B、14ビリオン、すなわち140億パラメータから構成されている。この規模のLLMを動かそうとすると、VRAMが12GB以上必要だ。よって最低でもGeForce RTX 3060程度は必要となる。少ないパラメータ数でコードに特化したQwen2.5-Coder-7B-Instructもあるが、やはりパラメータ数が少ないのはいかんともしがたく、あまりおすすめできないと判断した。
ダウンロードが完了し、モデルを読み込むと使用可能となる。その際に「GPUオフロード」を最大値にするを忘れないでおこう。
tkinterのウィンドウを生成する
では実際にtkinterを用いたPythonのコードを生成していく。
ここでは、ボタンをクリックするとクリックしたボタンのメッセージが下部に表示されるウィンドウを作成することにする。具体的には次のようなものだ。
これをゴールに、最初に入力したのは「tkinterでボタンが2つあるウィンドウのソースコードを教えてください。なおボタンはgridで横に並べてください。ウィンドウサイズを変更したら、ボタンの位置も変更するようにしてください。」だ。
生成したコードをgeditなどで保存する。ここではファイル名を「sampledialog.py」とする。動作確認のため、端末を起動しtkinterをインストールする。具体的には sudo apt install python3-tk を実行しよう。
完了後、 python3 sampledialog.py を実行すると、次のようなウィンドウが表示された。
なんと、ウィンドウが2つ表示されてしまっている。実はありがちなミスなので、「ウィンドウが2つ表示されています。1つは不要です。」と入力してやり直してもらう。
無事に修正された。ボタンの距離が近すぎるので、「ボタンの左右に等間隔のスペースがほしいです」と入力した。
ボタンの左側にはスペースができたが、右側にもほしい。「ボタンの右側にも等間隔のスペースを入れてください」と入力した。
これだと何をしてほしいウィンドウかわからないので、「ボタンの上に、「どちらかのボタンをクリックしてください」というメッセージを追加してください」と入力した。
「ボタンをクリックしたら、ボタンの下にテキストを表示するようにしてください。テキストの内容はボタンに書かれているものと同一にしてください」と入力し、完成形に近づけた。
ボタンをクリックすると次のようになった。
下に余白がほしいので、「ボタンの下のテキストの下にもう少し余白がほしいです」と入力した。
ウィンドウとしてはこれで完成だ。
この段階でのソースコードを見てみよう。
import tkinter as tk
def create_button_panel(root):
# ボタンパネルを作成するフレーム
button_frame = tk.Frame(root)
# 5つの列を定義: 左のマージン, ボタン1, 中央の余白, ボタン2, 右のマージン
for col in range(5):
button_frame.grid_columnconfigure(col, weight=1) # 各列に均等な重みを設定
# メッセージラベルを作成
message_label = tk.Label(button_frame, text="どちらかのボタンをクリックしてください", pady=10)
message_label.grid(row=0, columnspan=5)
# 余白フレーム作成
left_margin = tk.Frame(button_frame, width=50) # 左端の余白
right_margin = tk.Frame(button_frame, width=50) # 右端の余白
central_margin = tk.Frame(button_frame, width=50) # 中央の余白
# ボタンを作成
def button_action(text):
label_text.set(text)
label_text = tk.StringVar()
button1_label = "Button 1"
button2_label = "Button 2"
button1 = tk.Button(button_frame, text=button1_label, command=lambda: button_action(button1_label))
button2 = tk.Button(button_frame, text=button2_label, command=lambda: button_action(button2_label))
# テキストラベルを作成
label = tk.Label(button_frame, textvariable=label_text)
# 左端の余白フレーム配置
left_margin.grid(row=1, column=0, sticky='nsew')
# ボタン配置
button1.grid(row=1, column=1, sticky=tk.W+tk.E)
central_margin.grid(row=1, column=2, sticky='nsew') # 中央の余白フレーム配置
button2.grid(row=1, column=3, sticky=tk.W+tk.E)
label.grid(row=2, columnspan=5) # テキストラベルを下部に配置
# 下部の余白フレームを作成と配置
bottom_margin = tk.Frame(button_frame, height=20)
bottom_margin.grid(row=3, columnspan=5, sticky='ew') # 下部の余白
# 右端の余白フレーム配置
right_margin.grid(row=1, column=4, sticky='nsew')
return button_frame
# Tkinter のルートウィンドウを作成
root = tk.Tk()
root.title("Button Dialog")
# ボタンパネルをメインウィンドウに配置
button_panel = create_button_panel(root)
button_panel.pack(fill=tk.X) # X 方向(幅)に張り出すように表示
# Tkinter イベントループを開始
root.mainloop()
関数の中に関数があるという、Pythonのソースコードとしてはかなり違和感があるものであった。よって「関数の中に関数があるのは不自然ではないでしょうか」とお願いしたら、何回かは動かない(エラーを出力する)ソースコードを出力し続け、根気強くエラーを提示して修正をお願いすると、最終的に次のようなソースコードを出力した。
import tkinter as tk
def handle_button_click(label_var, button_text):
def inner():
label_var.set(button_text)
return inner
def create_button_panel(root):
# ボタンパネルを作成するフレーム
button_frame = tk.Frame(root)
# 5つの列を定義: 左のマージン, ボタン1, 中央の余白, ボタン2, 右のマージン
for col in range(5):
button_frame.grid_columnconfigure(col, weight=1) # 各列に均等な重みを設定
# メッセージラベルを作成
message_label = tk.Label(button_frame, text="どちらかのボタンをクリックしてください", pady=10)
message_label.grid(row=0, columnspan=5)
label_text = tk.StringVar() # StringVar を使用してテキスト変更を管理
# テキストラベルを作成
label = tk.Label(button_frame, textvariable=label_text) # 初期設定時は空白文字列を設定
label.config(pady=5) # 余白を追加
label.grid(row=2, columnspan=5) # テキストラベルを下部に配置
# 余白フレーム作成
left_margin = tk.Frame(button_frame, width=50) # 左端の余白
central_margin = tk.Frame(button_frame, width=50) # 中央の余白
right_margin = tk.Frame(button_frame, width=50) # 右端の余白
# ボタンを作成
button1_label = "Button 1"
button2_label = "Button 2"
button1 = tk.Button(button_frame, text=button1_label, command=handle_button_click(label_text, button1_label))
button2 = tk.Button(button_frame, text=button2_label, command=handle_button_click(label_text, button2_label))
# 左端の余白フレーム配置
left_margin.grid(row=1, column=0, sticky='nsew')
# ボタン配置
button1.grid(row=1, column=1, sticky=tk.W+tk.E)
central_margin.grid(row=1, column=2, sticky='nsew') # 中央の余白フレーム配置
button2.grid(row=1, column=3, sticky=tk.W+tk.E)
# 右端の余白フレーム配置
right_margin.grid(row=1, column=4, sticky='nsew')
return button_frame
# Tkinter のルートウィンドウを作成
root = tk.Tk()
root.title("Button Dialog")
# ボタンパネルをメインウィンドウに配置
button_panel = create_button_panel(root)
button_panel.pack(fill=tk.X) # X 方向(幅)に張り出すように表示
# Tkinter イベントループを開始
root.mainloop()
マージンをforで確保するなどギョッとする部分もあるが、Pythonのソースコードを1箇所も手修正することなくこの精度のものができたのは驚愕しかない。
なお、同じ文章を入力しても生成されるソースコードには違いが出るようで、気に入るソースコードが出力されるまで何回も試すという手も取れそうだ。
メンテナンスのことを考えるとこのソースコードをそのまま使うのは難しいかもしれないが、ひとまずこんなものを作りたいというプロトタイピングにはぴったりだ。
第58回のような記事を書いておいて何だが、筆者はAI PCには懐疑的な立場であった。しかし自分で実際に動かしてみると、これはもう可能性しか感じない。高価なGPUを用意せずともいいようにNPUの性能が向上し、それがUbuntuでも手軽に使えるわりと近いであろう将来に期待したい。