2026/06/08

Claude CodeをローカルLLMで動かす

4ヶ月前のOpenHands実験から時間も経ったので、コーティングエージェントで使えるローカルLLMでそれなりに使えるものがないかなと探してみたところ、 今日の時点ではQwen3.6-27Bが良いらしいということわかったので、試してみました。

利用するのは

  • llama.cpp
  • Claude Code
の2つです。

llama.cppの準備


https://github.com/ggml-org/llama.cpp からコードを取得します。今回は rev. f71af352a52b8efe824c7a698d0632afa4794c01 を使いました。cloneしたときの最新版です。

今回は https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md を読んで、GPU対応のllama.cppをビルドしました。

ビルド環境にCUDA関連のツールが必要なので、NVidiaの「CUDA Toolkit 13.3 Downloads」ページから「deb (network)」を選んで、記載されている方法でインストールしました。

OpenBLASもUbuntu24.04の標準パッケージをインストールした上で、以下のコマンドでビルドしました。

cmake -B build -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS -DGGML_CUDA=ON  -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc -DCMAKE_CUDA_ARCHITECTURES="120" -DCUDAToolkit_ROOT=usr/local/cuda
cmake --build build --config Release -j 8
その後、実行すればLLMの準備は完了です。
export LLAMA_CACHE="model-cache/unsloth/Qwen3.6-27B-MTP-GGUF"
build/bin/llama-server \
    -hf unsloth/Qwen3.6-27B-MTP-GGUF:UD-Q2_K_XL \
    -ngl 99 --fit on -c 65536 -fa on -np 1 \
    --spec-type draft-mtp --spec-draft-n-max 2 \
    --host 0.0.0.0 \
    --cache-type-k q4_0 \
    --cache-type-v q4_0
GPUのメモリは16GBあるのですが、Q2にして、かつ、cache-typeをq4_0にしないと、コンテキスト長を65536まで伸ばせませんでした。131072だとメモリ不足で落ちます。 98304でもギリギリ動くようです。

Claude Codeの準備


Claude CodeはDockerコンテナ内で動かします。
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
    curl \
    ca-certificates \
    gnupg \
    nodejs \
    npm \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
RUN npm install -g @anthropic-ai/claude-code
ビルドは
docker build -f Dockerfile -t claude-code .
で行い、起動は
touch .bash_history # 不要かも?
docker run -it --rm \
  --name claude-code \
  -v "$(pwd)/workspace:/workspace" \
  -v "$(pwd)/.claude-home:/root/.claude" \
  -v "$(pwd)/.bash_history:/root/.bash_history" \
  -e ANTHROPIC_API_KEY=llamacpp \
  -e ANTHROPIC_AUTH_TOKEN=llamacpp \
  -e ANTHROPIC_BASE_URL=http://xxx.xxx.xxx.xxx:8080 \
  -e ANTHROPIC_MODEL=qwen3.6-27b \
  -w /workspace \
  --gpus all \
  claude-code
で行います。ANTHROPIC_BASE_URLの部分は実行環境のホストのIPアドレスを指定します。たぶん 127.0.0.1では動きません。

Dockerコンテナ内に入るので、そこで、claudeとタイプすればあとは普通に使えます。

ただし、Claude CodeがWebサーチできないので、なにか設定が抜けているかもしれません。

実験したときのClaude Codeはv2.1.168でした。

使ってみると?


思ってたより普通に動きます。難しいことはできないかもしれませんが、
  • マンデルブロ集合を計算してASCIIで描画する。C++で書いて、cmakeでビルド環境まで作成
  • Pytorchを使ってMNISTのモデルを作って学習し、評価する。Pytorchの環境だけは通常のpip installでは適切なバージョンをインストールできなかったので、手動で作成
くらいは問題なく動きました。gitの操作も簡単なものであればできました。コードは長いので省略します。

このレベルならコーディングのお手伝いくらいならできそうです。 有料の強力なモデルとローカルLLMを適宜切り替えて使うのがコスト的には良いのかもしれません。

2026/02/14

OpenHandsをローカルLLMで動かす(その2)

1年くらい前にOpenHandsをローカルLLMで動かしてみましたが、ローカルLLMの能力が低くて今ひとつでした。

ローカルLLMの能力も少し上がってきたようですので、再度試してみることにしました。

ollmaの準備


前回と基本的には同じです。

今回はモデルにdevstral-small-2:latestを使ってみることにしました。

気をつける点は、コンテキスト長をデフォルトから変更するというところです。

まずollamaのdockerコンテナ内で /root/.ollama/models/DevstralSmall2Ctx16k というファイルを作ります。中身は
FROM devstral-small-2:latest
PARAMETER num_ctx 16384
です。作成後、
$ ollama create devstral-small-2-16k -f /root/.ollama/models/DevstralSmall2Ctx16k.Modelfile
にて、ollamaにモデルを認識させます。すると
$ ollama list
NAME                                           ID              SIZE      MODIFIED      
devstral-small-2-16k:latest                    3360cd1e87b5    15 GB     5 hours ago
のような行が追加されるはずです。

これでollamaの準備は完了です。

OpenHandsの準備


https://docs.openhands.dev/openhands/usage/run-openhands/local-setup に従って進めるだけです。 具体的には
$ uv tool install openhands --python 3.12
$ openhands serve
だけで完了です。事前にuvの準備は必要です。

次に、 http://localhost:3000/settings にアクセスして、AdvancedをONにしたうえで、以下のように設定します。 https://docs.openhands.dev/openhands/usage/llms/local-llmsを参考にしています。

設定名設定値
Custom Modelopenai/devstral-small-2-16k:latest
Base URLhttp://192.168.xxx.yyy:11434/v1
API Keyollamaで動いているので何でも良い
以上が設定できると、会話画面にて
hello
と入力すると、
Hello! How can I assist you today?
のように出力されます。

注意するところは、Custom Modelの設定値の最初をollama/ではなくopenai/とする点と、Base URLの最後を:11434ではなく:11434/v1にする点です。 もし間違うと、中途半端に動きます。指示を出す画面で例えば

hello
と入力すると、
{"name": "finish", "message": "Hello! How can I assist you today?"}
のようにjsonの文字列がそのまま画面上に出力されてしまいます。

以上でとりあえずは動くようになりました。しかし、なかなか言うことを聞いてくれません。コマンドを特定できているのに実行しなかった場合に「実行して!」と頼んでも

I cannot execute commands without a specific task or context. Please provide me with a clear instruction or task so I can assist you effectively. 
とか言われるので、コンテキスト長が16k程度では全然足りていないのかもしれません。

2026/02/11

RTX 5060 TiをUbuntu24.04で動かす

GeForce RTX 5060 Ti を Ubuntu24.04 のPCで動かそうとしたら、とても手間がかかったので、メモとして残しておきます。

ブート


CSM (Compatibility Support Module) ブートでは動かないです。UEFIによるブートにする必要があります。

確かデフォルト設定でUbuntu24.04をインストールしていたのですが、UEFIによるブートになっていませんでした。

そのため、どうにかしてUEFI用として550MiBのパーティションを作る必要があります。今回は運良く空き領域が残っていましたので、gpartedでパーティションを作りました。 フォーマットはfat32で、ESPフラグを付けます。

その後、マウントしてgrubのインストールをします。

$ sudo mkdir -p /boot/efi
$ sudo mount /dev/your-partition-name /boot/efi 
$ sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu
そして
$ ls -la /boot/efi/EFI/ubuntu/
にてgrubx64.efiがあればうまくインストールできていることが確認できます。

$ sudo update-grub
にて更新して、/etc/fstabに以下のような内容を追記します。
UUID=XXXX-XXXX /boot/efi vfat umask=0077 0 1 
UUIDは8桁のもので良いようです。

カーネルとドライバ


これでブートはするものの、5060Tiを5060Tiとして認識してくれません。動くドライバと動かないドライバがあるようです。 ただ、このあたりの情報が2025年6月あたりの情報ばかりで、最近の情報があまりネット上では見つかりませんでした。

そこで、試したパターンのうち記録に残していたものについて、ここに残しておきます。筆者の環境ではこうなるというだけで他の環境でもこうなるとは限りませんのでご注意ください。

nvidia-driver-linux-image-結果
画面マウスカーソルネットワークnvidia-smi
5906.17.0-1004-nvidia真っ黒で左上の
キャレットも表示されない
---
5906.17.0-14-generic真っ黒で左上の
キャレットも表示されない
---
5906.14.0-1015-nvidiaGUI付きで起動する動く記録なし失敗
5906.14.0-37-genericGUI付きで起動する動かない記録なし失敗
5906.11.0-1016-nvidiaGUI付きで起動する動かない動かない失敗
5906.11.0-29-genericGUI付きで起動する動く動く失敗
5906.8.0-1045-nvidiaGUI付きで起動する動く動く失敗
5906.8.0-100-genericGUI付きで起動した動かない動く失敗
590-open6.17.0-1004-nvidiaGUI付きで起動する動く動く失敗
590-open6.17.0-14-genericGUI付きで起動する動く動く失敗
590-open6.14.0-1015-nvidiaGUI付きで起動する動く動く失敗
590-open6.14.0-37-genericGUI付きで起動する動かない動かない失敗
590-open6.11.0-1016-nvidiaGUI付きで起動する動かない動かない失敗
590-open6.11.0-29-genericGUI付きで起動する動く動く失敗
590-open6.8.0-1045-nvidiaGUI付きで起動する動く動く失敗
590-open6.8.0-100-genericGUI付きで起動する動かない動かない失敗
575-open6.17.0-1004-nvidiaGUI付きで起動する動く動く正常に動作
575-open6.17.0-14-genericGUI付きで起動する動く動く正常に動作
575-open6.14.0-37-genericGUI付きで起動する動かない動かない失敗
575-open6.11.0-29-genericGUI付きで起動する動く動く失敗

以上より、筆者の環境では nvidia-driver-575-openと6.17.0 の組み合わせで正常に動作することが分かりました。

なお、nvidia-driver-575-openをインストールすると何故かnvidia-driver-580-openもインストールされます。 そして、nvidia-smiを実行すると

Driver Version: 580.126.09
と表示されるので、結局、nvidia-driver-580-openが使われているようです。

2026/01/24

マイクのプラグインパワー

マイクのプラグインパワーが実際のところマイクにどういう電圧をかけているのか調べてみました。

プラグの端子の名称はここでは下図のように呼んでいます(Nano Banana Proに描いてもらいました)。

なお、テスターは古ーーいアナログテスターなので測定誤差が大きいです。電圧の絶対値は間違っている可能性が高いですし、個体差もあるでしょうから、正しい値が必要な方はこんな個人メモを参考にせず、必ずご自身で測定してください。

測定結果


測定結果です。T=Tip、R1=Ring1、R2=Ring2、S=Sleeveです。

ELECOM USB-AADC02BK のマイク用3極ジャック

テスターの-極テスターの+極電圧
ST2.6V
SR12.6V
SR20.0V
R2R12.6V
R2T2.6V
R1T0.0V
SleeveとR2は両方ともジャック内のグランドの接点に接触しているようです。

ELECOM USB-AADC02BK のマイク付きイヤホン用4極ジャック

テスターの-極テスターの+極電圧
ST-2.6V
SR1-2.6V
SR2-2.6V
R2R10.0V
R2T0.0V
R1T0.0V
CTIA規格と思われます。CTIAの場合、R2がグランドでSがマイクになるので(参考)、 Sに電圧がかかっている状態になります。音声出力側(TまたはR1)は電圧がかからないし、R2はグランドなので、それらとSとの間で電位差が発生します。

Arvel製のHAMU02BKのマイク用ジャック

テスターの-極テスターの+極電圧
ST0.0V
SR10.0V
SR20.0V
R2R14.2V (稀に8.8Vのときもある)
R2T一瞬プラス側に振れるがすぐに0Vになる
R1T一瞬マイナス側に振れるがすぐに0Vになる
接点の接触状況に応じて内部回路で出力を制御しているっぽくみえます。 手持ちのコンデンサマイクでは動作しましたが、Tipに電圧がかかってないので2極プラグのマイクだと動かなさそうです。 テスターの-極をR1とR2に当ててショートさせている状態で+極をTに当ててみましたが、0.0Vでした。

audio-technica AT-MA2

テスターの-極テスターの+極電圧
ST2.4V
SR12.4V
SR20.0V
R2R10.0V
R2T0.0V
R1T0.0V
MONOとSTEREO設定でどちらも同じ結果でした。R2はジャック内でどの接点とも接触していないようです。

まとめ


デバイスごとに供給される電圧が違っていることは分かりました。規格がないのでそのとおりの結果ですね。

4極プラグで試したので、プラグのどの位置がジャック内で接触しているのかもばらばらであることも分かりました。 ばらばらということは3極対応のジャックをつなげるために4極プラグのコードを使うと、接続できないこともありそうです。

2025/11/24

画像認識モデルを人間の判断に近づける方法

THINGS datasetというのを使って人間の判断に類似するように画像認識モデルをファインチューンできるらしい。

https://arxiv.org/abs/2409.06509
https://doi.org/10.1038/s41586-025-09631-6 (nature)

2025/11/23

DockerでGUI付きのコンテナ立てたい!

というわけで、以下のようなDockerfileを書くと、VNCでアクセスできるようになります。RDPを最初に試しましたが、Rootless dockerでは動かせませんでした。

apt-getでインストールしているパッケージは、人によっては不要なものも混じっているので、必要に応じて編集します。

FROM ubuntu:24.04

RUN apt-get update && apt-get install -y \
    xfce4 xfce4-goodies \
    xfce4-terminal \
    dbus-x11 \
    wget \
    net-tools \
    locales \
    tzdata \
    ibus \
    ibus-mozc \
    language-pack-ja-base \
    language-pack-ja \
    fonts-noto-cjk \
    fonts-noto-color-emoji \
    nano \
    firefox \
    tigervnc-standalone-server tigervnc-common \
    x11-xserver-utils \
    && rm -rf /var/lib/apt/lists/*

RUN locale-gen ja_JP.UTF-8
ENV LANG=ja_JP.UTF-8
ENV LANGUAGE=ja_JP:ja
ENV LC_ALL=ja_JP.UTF-8
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
    && echo 'Asia/Tokyo' > /etc/timezone

# VNC
EXPOSE 5901

# Set up VNC
USER ubuntu
RUN mkdir -p /home/ubuntu/.vnc && \
    echo "[ここにVNCの接続パスワードを記入]" | vncpasswd -f > /home/ubuntu/.vnc/passwd && \
    chmod 600 /home/ubuntu/.vnc/passwd && \
    bash -c "( \
    echo '#!/bin/bash' && \
    echo unset SESSION_MANAGER && \
    echo unset DBUS_SESSION_BUS_ADDRESS && \
    echo xrdb /home/ubuntu/.Xresources && \
    echo export GTK_IM_MODULE=ibus && \
    echo export QT_IM_MODULE=ibus && \
    echo export XMODIFIERS=@im=ibus && \
    echo dbus-launch --exit-with-session ibus-daemon --xim --daemonize && \
    echo exec startxfce4 ) > /home/ubuntu/.vnc/xstartup && \
    chmod +x /home/ubuntu/.vnc/xstartup"

CMD ["vncserver", "-localhost", "no", "-fg", "-geometry", "1600x900", "-depth", "24", ":1"]
次に
docker build -t ubuntu24-gui .
のようにしてビルドします。

できあがったら、

docker run -p 5901:5901 ubuntu24-gui
で実行します。

これで接続できるようになっているので、VNCのクライアントを使うと接続できます。

Remminaを使う場合は、ユーザー名をubuntuに、ユーザーパスワードをDockerfileに書いたVNCのパスワードにすると接続できます。

ローカルホストからのみアクセスできるようにするのであれば

docker run -p 127.0.0.1:5901:5901 ubuntu24-gui
で実行します。

また、ローカルホストのみに限定した上で、sshトンネルでアクセスするにはRemminaのSSHトンネルの設定をします。

「ループバックアドレス経由のトンネル」にチェックを入れて、SSHの認証情報を設定すれば接続できます。

aptパッケージのキャッシュ

Dockerfileを作っているとき、多くの場合は試行錯誤するので、何度も何度もaptとかapt-getとかでパッケージのダウンロードを行うことになります。

通信回線が高速で苦にならないのならそのままでも良いのですが、パッケージの数が多い場合など、待ち時間が長くなってしまいます。

そこで、apt-cacher-ngを使ってみます。

使い方は以下の通り。

  1. Dockerのホスト側で
    sudo apt install apt-cacher-ng
    などとして、apt-cacher-ngをインストールします。
  2. 設定ファイルは /etc/apt-cacher-ng/acng.conf にあるので、必要があれば書き換えます。ここではデフォルトのまま使います。
  3. サーバが起動しているかどうかやキャッシュの状況などは http://localhost:3142/acng-report.html にブラウザでアクセスすることで確認できます。
  4. 以下のようなコードをDockerfileに追記します。
    FROM ubuntu:24.04
    
    ARG USE_CACHE=false
    ARG APT_CACHE_SERVER=127.0.0.1
    
    # キャッシュを使う場合のみ設定を追加
    RUN if [ "$USE_CACHE" = "true" ]; then \
          echo "Acquire::http { Proxy \"http://${APT_CACHE_SERVER}:3142\"; };" > /etc/apt/apt.conf.d/01proxy; \
        fi
    
  5. 次のようにオプションを指定してビルドします。
    docker build --build-arg USE_CACHE=true --build-arg APT_CACHE_SERVER=[ホストのIPアドレス] .
    

2025/11/04

Win11 23H2から25H2へ

Windows11 23H2のサポート期限が2025/11/11が近づいています。

https://learn.microsoft.com/ja-jp/lifecycle/announcements/windows-11-23h2-end-of-updates-home-pro から引用すると

Windows 11 Home and Pro, version 23H2 will reach the end of updates on November 11, 2025. This version was released in October of 2023.

This edition will no longer receive security updates after November 11, 2025. Customers who contact Microsoft Support after this date will be directed to update their device to the latest version of Windows 11 to remain supported.

です。さて、Windows Updateにこの更新が降ってきていれば良いのですが、降ってきていない場合は自分でアップデートする必要があります。

調べるといくつか方法があるようですが、素直に「Windows 11 インストール アシスタント」というのをダウンロードして実行し、画面に沿って進めると、25H2に一足飛びにアップグレードできるようです(少なくとも筆者の場合はそうなりました)。

2025/10/19

リチウムとアルツハイマー病

アミロイドがリチウムを集めてしまって周囲の細胞がリチウム不足になって細胞が正常に機能しなくなるらしい。 そして、アミロイドが集めにくいリチウム塩をアルツハイマー病のマウスに投与すると、認知障害を予防できたらしい。

ほんとかな。マウスでならそうなのかも。

https://doi.org/10.1038/s41586-025-09335-x
https://doi.org/10.1038/d41586-025-02255-w

2025/10/12

生成AIの定義

生成AIの分かりやすい定義をようやく見つけた。

https://www.stateof.ai/で公開されている スライドにこう書かれていた。

Generative AI: A family of AI systems that are capable of generating new content (e.g. text, images, audio, or 3D assets) based on 'prompts'.
つまり、プロンプト(指示文)が入力に必要ということ。そのため、単に入力データを変換するもの、例えば、
  • 画像を入力して何が写っているのかのラベルを出力する
  • 映像を入力して、写っている物体の動作に対してラベルを出力する
  • 映像を入力して、高解像度の映像を出力する
  • テキストを入れたらその内容をしゃべる
  • 音声を入れたらテキストを出力する
  • 日本語を入れたら英語を出力する
のようなものは、たとえTransformerベースのモデルを使っていたとしても、拡散モデルを使っていたとしても、この定義だと生成AIには該当しないことになる。

とても分かりやすい。

人間がLLMを使ってタスクをこなすときの倫理

人間が実行する場合は倫理的な行動をするけれど、同じタスクをLLMに指示して代わりに実行させる場合、直接実行する場合に比べて倫理的な行動が減るらしい。

https://doi.org/10.1038/d41586-025-02819-w
https://doi.org/10.1038/s41586-025-09505-x

2025/08/17

ECoGの信号から音声を合成

https://doi.org/10.1038/s41586-025-09127-3 によると、脳表面に設置した電極(ECoG)から取得したデータを使ってリアルタイムに音声を合成できるようになったらしい。Supplementary Videoを見る限り、速度はまだまだではあるものの、イントネーションも変えられるし、強調もできるような発声ができている。

ここまで出来るようになっているので、普通に発話できるようになるのも時間の問題だと思われる。

一方、ECoGではこのレベルで動くようになっているものの、EEGで同じことをするのはまだまだ難しいのだろう。

2025/04/05

OpenHandsをローカルLLMで動かす

OpenHandsをローカルLLMで動かしてみます。

OpenHandsの準備


https://github.com/All-Hands-AI/OpenHands/のQuick Startに従って、dockerを動かすだけです。 Rootless dockerでも動作します。

/var/run/docker.sockを使ってOpenHandsがdockerを制御するようでしたので、今回は、KVM上でUbuntu24.04を動作させ、その中でAllHandsを動作させました。

ollamaの準備


ローカルLLMについてはollamaで動作させます。

ollamaもdockerイメージがhttps://hub.docker.com/r/ollama/ollamaで配布されているのでそれをそのまま利用します。 こちらもrootless dockerでも動作します。

$ docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
を実行するとollamaが立ち上がります。
$ docker exec -it ollama bash
でdocker内に入って、ollamaを実行すると
root@xxxxxxxx:/# ollama
Usage:
  ollama [flags]
  ollama [command]

Available Commands:
  serve       Start ollama
  create      Create a model from a Modelfile
  show        Show information for a model
  run         Run a model
  stop        Stop a running model
  pull        Pull a model from a registry
  push        Push a model to a registry
  list        List models
  ps          List running models
  cp          Copy a model
  rm          Remove a model
  help        Help about any command

Flags:
  -h, --help      help for ollama
  -v, --version   Show version information

Use "ollama [command] --help" for more information about a command.
となり、ollamaが実行できることが確認できます。

ollamaのモデルの準備


試している環境ではGPUメモリが8GBなので、小さめのLLMを探してきます。

以下のものを試してみましたが、hhao/qwen2.5-coder-tools:7bがかろうじて動く程度で、ほかはOpenHandsで使うには適切ではないLLMでした。

hhao/qwen2.5-coder-tools:7b    1fbf62f22cd4    4.7 GB
codegemma:7b                   0c96700aaada    5.0 GB
llama3.2:3b                    a80c4f17acd5    2.0 GB
codellama:7b                   8fdf8f752f6e    3.8 GB
qwen2.5-coder:3b               e7149271c296    1.9 GB
opencoder:1.5b                 8573dfc23c16    1.4 GB
opencoder:8b                   cd882db52297    4.7 GB
もっと良いLLMがあるかもしれませんが、ここではhhao/qwen2.5-coder-tools:7bを使って進めます。runすると
/# ollama run hhao/qwen2.5-coder-tools:7b
>>> Send a message (/? for help)
と表示され、OpenHandsからアクセスできる状態になりました。試しにメッセージを入れてみると、
>>> Pythonでhelloと出力するスクリプトを作成してください
```python
print("hello")
```
とか
>>> rustでhelloと出力するコードを書いて、ビルドして実行する方法を書いてください
```json
{
  "name": "create_file",
  "arguments": {
    "filename": "main.rs",
    "content": "fn main() {\n    println!(\"Hello\");\n}"
  }
}
```
のように出力されました。日本語でも動きそうです。OpenHandsから呼び出すときにollama runが実行されている必要はありません。

OpenHandsの設定


KVM内からollamaにアクセスできることを確認します。
curl http://host.ip.address:11434/api/generate -d '{
  "model": "hhao/qwen2.5-coder-tools:7b",
  "prompt":"Write a bash script that prints hello"
}'
json形式の応答が返ってくれば、動作しています。

docker run -it --rm -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.29-nikolaik \
       -e LOG_ALL_EVENTS=true \
       -e LOG_JSON=false \
       -e LOG_LEVEL=DEBUG \
       -e DEBUG=true \
       -e OLLAMA_API_BASE=http://host.ip.address:11434 \
       -v /var/run/docker.sock:/var/run/docker.sock \
       -v ~/.openhands-state:/.openhands-state \
       -p 3000:3000 \
       --add-host host.docker.internal:host-gateway  \
       --name openhands-app \
       docker.all-hands.dev/all-hands-ai/openhands:0.29
host.ip.addressは適切な値に書き換えてください。

KVM内のブラウザでlocalhost:3000にアクセスします。 左下の⚙アイコンをクリックして、

Custom model: ollama/hhao/qwen2.5-coder-tools:7b
Base URL: http://host.ip.address:11434
API Key: なんでもOK
Agent: CodeActAgent
と設定します。

これで動くようになります。指示として例えば

Could you write a bash script that just prints "こんにちは" ?
を入力すると動き始め、下図のようになりました。
ただ、ファイルはつくれたもののその後はうまく動作しなくなってしまいました。

OpenHandsを活用するには、もっと賢いLLMが必要なようです。

2025/02/22

FLUX.1で画像生成

FLUX.1のSchnellモデルを使った画像生成をしてみます。

FLUXのオリジナルの実装


オリジナルの実装はこちらです。

モデル: https://huggingface.co/black-forest-labs/FLUX.1-schnell
コード: https://github.com/black-forest-labs/flux

指示にしたがって環境を設定し、実行すると、44.5GBのダウンロードがはじまります。

これは src/flux/util.py を読むと google/t5-v1_1-xxl をロードするようになっており、 https://huggingface.co/google/t5-v1_1-xxl/tree/main を見るとちょうどモデルサイズが44.5GBなので、おそらくこれをダウンロードしようとしているのだと思われます。

https://huggingface.co/black-forest-labs/FLUX.1-schnell/tree/main に配置されているテキストエンコーダが使われない理由は謎です。

ダウンロードサイズは大きいですが https://github.com/black-forest-labs/flux/issues/7 によると、16GBくらいVRAMがあればよさそうです。sequential offloadをすれば2GBくらいらしいのですが、cli.pyのオプションを見てもそれらしきものを見つけることができませんでした。

量子化版


とはいえもう少し小さいモデルでまずは動かしてみたいので、量子化済みのモデルで試してみます。いくつかHuggingFaceにアップロードされていますが、今回はhttps://huggingface.co/aifoundry-org/FLUX.1-schnell-Quantizedを使ってみます。

現時点では[WIP]と書かれているものの動かし方がいちおうは記載されているので、そのとおりに実行してみます。

最初に table-diffusion.cpp をダウンロードしてビルドします。

次に、 Schnell-Q2_KAutoencoderCLIP_LT5XXL をダウンロードして、models/以下に配置します。

これで準備は完了です。先ほどダウンロードした一番小さいモデル(Q2_K)で試してみます。

./build/bin/sd --diffusion-model models/flux1-schnell-Q2_K.gguf --vae models/ae.safetensors --clip_l models/clip_l.safetensors --t5xxl models/t5xxl_fp16.safetensors -p "a frog holding a sign saying 'hi' " -o ../frog.png -v --cfg-scale 1.0 --sampling-method euler -v --seed 42 --steps 4
を実行します。

以下の様な蛙の画像が出力されます。

他にも

./build/bin/sd --diffusion-model models/flux1-schnell-Q2_K.gguf --vae models/ae.safetensors --clip_l models/clip_l.safetensors --t5xxl models/t5xxl_fp16.safetensors -p "Photo-realistic Rabbit running in meadow" -o ../frog.png -v --cfg-scale 1.0 --sampling-method euler -v --seed 10 --steps 20
を実行すると

となります。

T5XXLはモデルサイズは大きいもののGPUメモリはあまり必要ないようで、デフォルトの512x512のサイズの画像生成時に観測できた範囲ではGPUメモリの使用量は4.7GBくらいでした。

./build/bin/sd --diffusion-model models/flux1-schnell-Q4_K.gguf --vae models/ae.safetensors --clip_l models/clip_l.safetensors --t5xxl models/t5xxl_fp16.safetensors -p "Photo-realistic Rabbit running in meadow" -o ../frog.png -v --cfg-scale 1.0 --sampling-method euler -v --seed 13 --steps 20
のようにQ2_KではなくQ4_Kを使ってみると
となります。

Q2_KではなくQ4_Kのモデルの方を使った場合、デフォルトの512x512のサイズの画像生成時に観測できた範囲ではGPUメモリの使用量は7.4GBくらいでした。

試した範囲では実行結果に再現性があり、コマンドの引数が同じであれば同じ結果が出力されるようです。

2025/02/10

Landlockによる制限をかけるときにGPUを使うには

Landlock (を使ったsandboxer) による制限をかけるときにnvidia-smiを実行するには、次のパスを許可すれば良いようです。

LL_FS_RW=$(ls /dev/nvidia* | tr '\n' ':' | sed 's/:$//') LL_FS_RO="/usr/lib:/usr/bin" ./sandboxer nvidia-smi
RO側はもう少し制限をかけることができるかもしれません。

nvidia-smiだけでなく、NVidiaのGPUを計算に利用するには、

LL_FS_RW="/proc:"$(ls /dev/nvidia* | tr '\n' ':' | sed 's/:$//') LL_FS_RO="/usr/lib:/usr/bin" ./sandboxer /path/to/app
のように/procを読み書き権限で追加する必要があるようです。 アプリケーション依存かもしれませんので、必ずしもこれだけで使えるようになるとは限りません。

2025/02/02

uvのインストール

最近のUbuntuやDebianのシステムに入っているPythonでpip installしようとすると、

error: externally-managed-environment
と言われ、venv環境を作れと言ってきます。

全体で使いたいパッケージをいちいちvenv環境に入ってインストールして、というのは手間なので、 エラーメッセージに書かれているとおり、Debian/Ubuntuのパッケージにある pipx をインストールします。

そのうえで、

$ pipx install uv
とすると、uvをインストールできて、全体でuvが使えるようになります。

編集距離の計算

編集距離(Edit Distance, Levenshtein Distance)の計算のライブラリは今となってはたくさんありそうですが、 いい感じに表示できる jiwer (https://jitsi.github.io/jiwer/) というPythonのライブラリがあったので、メモ。

こんな感じで書けます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import jiwer
import jaconv

def main():
    reference = "hello world"
    hypothesis = "hello duck"

    error = jiwer.wer(reference, hypothesis)
    print(error)
    error = jiwer.cer(reference, hypothesis)
    print(error)
    print("--------------")
    output = jiwer.process_characters(reference, hypothesis)
    print(jiwer.visualize_alignment(output))

    print("--------------")
    output = jiwer.process_characters("あいうえお", "あううお")
    prev_hyp = False
    for line in jiwer.visualize_alignment(output).split("\n"):
        zen = False
        if prev_hyp:
            prev_hyp = False
            zen = True
        if line[:4] == "REF:":
            zen = True
        if line[:4] == "HYP:":
            zen = True
            prev_hyp = True
        if zen:
            line = jaconv.h2z(line, kana=True, digit=True, ascii=True)
        print(line)

if __name__ == "__main__":
    main()
結果は
0.5
0.45454545454545453
--------------
sentence 1
REF: hello world
HYP: hello duck*
           SSSSD

number of sentences: 1
substitutions=4 deletions=1 insertions=0 hits=6

cer=45.45%

--------------
sentence 1
REF: あいうえお
HYP: あうう*お
      S D 

number of sentences: 1
substitutions=1 deletions=1 insertions=0 hits=3

cer=40.00%

のようになります。

2024/07/14

話者ベクトル

話者ベクトルをhttps://github.com/speechbrain/speechbrain.gitで取得してみます。

話者ベクトルの計算にはEncoderClassifier.encode_batchを使います。サンプリング周波数16kHzの音声データをこの関数に入力すると話者ベクトルが得られます。

それ以外の部分は音声の読み込みとt-SNEによる2Dベクトル化、散布図描画のための色の選択と、散布図の作成をしているだけです。

35行目でEncoderClassifierを作成して、52行目で話者ベクトルを取得します。

audio-16k.listは1行ごとに.wavファイルへのパスが書かれていることを想定しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import sys
from collections import defaultdict
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
import torchaudio

sys.path.append("speechbrain")
from speechbrain.inference import EncoderClassifier

def make_colormap(label_list, min_count):
    color_map = defaultdict(int)
    for label in label_list:
        color_map[label] += 1

    color_map_sorted = []
    for k, v in color_map.items():
        color_map_sorted.append((v, k))
    color_map_sorted = reversed(sorted(color_map_sorted))

    colors = ["b", "g", "r", "c", "m", "y", "k", "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"]
    cindex = 0
    for v, k in color_map_sorted:
        if v < min_count:
            color_map[k] = None
            continue
        color_map[k] = colors[cindex] if cindex < len(colors) else "grey"
        cindex += 1
    color_list = []
    for i in range(len(label_list)):
        color_list.append(color_map[label_list[i]])
    return color_list

def main(wav_list):
    ec = EncoderClassifier.from_hparams(
            source="speechbrain/spkrec-ecapa-voxceleb",
            savedir="cache",
            run_opts={"device": "cuda"},
            use_auth_token=False
         )
    ec.hparams.label_encoder.ignore_len()
    embed_list = []
    label_list = []
    with open(wav_list) as wavs:
        i = 0
        for line in wavs:
            i += 1
            line = line.strip()
            signal, fs = torchaudio.load(line)
            assert fs == 16000
            # Get a speaker vector
            embed_list.append(ec.encode_batch(signal, wav_lens=None).squeeze(dim=1).cpu().numpy())
            label_list.append(line.split("/")[1].split("_")[0]) # Get speaker name
            if i % 100 == 0:
                print(i)
    label_list = make_colormap(label_list, min_count=100)

    # Filter speaker vectors
    embed_list = [embed_list[i] for i in range(len(label_list)) if label_list[i] is not None]
    label_list = [label_list[i] for i in range(len(label_list)) if label_list[i] is not None]
    embed_list = np.concatenate(embed_list)

    tsne = TSNE(n_components=2, random_state=123)
    vec_2d = tsne.fit_transform(embed_list)

    plt.figure(figsize=(10, 8))
    plt.scatter(vec_2d[:,0], vec_2d[:,1], alpha=0.7, c=label_list)
    plt.title("t-SNE visualization of speaker vectors")
    plt.xlabel("t-SNE feature 1")
    plt.ylabel("t-SNE feature 2")
    plt.grid(True, linestyle="--", alpha=0.7)
    plt.savefig("spk_distribution.png", dpi=300, bbox_inches="tight")
    plt.close()

if __name__ == "__main__":
    main("audio-16k.list")
実行すると、以下のような散布図が得られます。少し混じっているところはあるものの、色ごとに、つまり話者ごとにクラスタが異なっていることが分かります。

2024/07/06

moreの挙動が変化した

Debianを11から12にアップグレードしたところ、moreコマンドのデフォルトの挙動が変化しました。

具体的には、表示するテキストの行が画面の行数よりも短いときは、単にcatで出力される状態であったのに、画面をクリアして画面の上部に表示されるようになりました。

lessでいうと、less -Fの挙動をしていたものが、less -cに似た挙動に変更されてしまいました。しかも、元の画面を削除して復旧しないという挙動です。

less -Fが手間なのでmoreを使っていたのに、これは困ったことになってしまいました。

Perplexityによると

Linuxのmoreコマンドの動作変更は、util-linux パッケージのバージョン2.38で導入されました。この変更により、表示する内容が端末の画面高さよりも少ない行数の場合でも、画面の上部から表示されるようになりました。 この変更は、より一貫性のある動作を提供し、ユーザーエクスペリエンスを向上させることを目的としています。以前のバージョンでは、内容が短い場合に画面の下部に表示されることがあり、これが一部のユーザーにとって混乱を招く原因となっていました。 util-linux 2.38は2022年4月にリリースされており、この版で上記の動作変更が実装されました。したがって、util-linux 2.37.2と2.38.1の間で発生したこの変更は、正確には2.38で導入されたものです。
とのこと。

moreコマンドが含まれているlinux-utilのバージョンは、Debian 11では2.36.1、Debian 12では2.38.1です。また、Ubuntu 22.04のmoreはDebian 11と挙動が同じで、そのバージョンは2.37.2です。

そこで、linux-utilのログを調べてみると、

2021-06-01	build-sys: release++ (v2.37)v2.37
2022-01-31	build-sys: release++ (v2.38-rc1)v2.38-rc1
2022-03-28	build-sys: release++ (v2.38)v2.38
でした。これらの間のどこかに変更が含まれているはずです。

調べてみると

commit df6b29d3b8e9a55d2bfa69c593e17da4d11a3fac
AuthorDate: Wed Sep 29 14:50:14 2021 +0200
CommitDate: Wed Sep 29 14:50:14 2021 +0200

    more: POSIX compliance patch preventing exit on EOF without -e
で-eオプションが導入されて、デフォルトの挙動が変わっていました。とても煩わしいです。

さらにその後のコミットをたどっていくと、なんと、

commit 28b391ce7e58f8327c092b3911c05f526d0ad586
AuthorDate: Wed Jun 15 10:03:44 2022 +0200
CommitDate: Wed Jun 15 10:03:44 2022 +0200

    more: restore exit-on-eof if POSIXLY_CORRECT is not set
    
    In version 2.38, exit-on-eof has been disabled by default. This change
    is annoying for users and forces many users to use 'alias more="more
    -e"'. It seems better to force POSIX lovers to use POSIXLY_CORRECT
    env. variable and stay backwardly compatible by default.
    
    Addresses: https://github.com/util-linux/util-linux/issues/1703
    Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=2088493
 
がコミットされていて、POSIXでないとやだ!っていう人は環境変数POSIXLY_CORRECTを使いなさいという形に戻っていました。

Debian 12のパッケージ更新タイミングの運のなさよ。とりあえず、alias more="more -e"で回避するしか無さそうです。

2024/06/09

Wasserstein distance の発音

2つの確率分布間の距離を表す Wasserstein distance の発音は https://en.wikipedia.org/wiki/Wasserstein_metric によると

Most English-language publications use the German spelling "Wasserstein" (attributed to the name "Vaseršteĭn" (Russian: Васерштейн) being of Yiddish origin)
と書かれていて、Wassersteinはドイツ語スペルなので、そのまま読むと「ヴァッサーシュタイン」になるはず。

キリル文字の「Васерштейн」を変換表で変換すると「Vasershteyn」または「Vasershtein」になるので、これを英語読みすると「ヴァッサーシュタイン」。

AIのゴッドファーザーと呼ばれているなかの一人であるLeCunの投稿でも https://x.com/ylecun/status/991375003626233858

Hint: "Wasserstein", as in "Wasserstein distance" is pronounced "vassershtaeen" not "wassersteen".
とあるので、やはり「ヴァッサーシュタイン」。

一方、なぜか日本語文献を読むと Wikipediaでも 転移学習でも「ワッサースタイン」と書かれている。

どうして日本ではドイツ語表記したスペルの英語読みが広がっているんだろう?

Einsteinは「アインシュタイン」と呼んで、「アインスタイン」とは書かないのに、不思議である。