Linuxでllama.cpp (llama-cli、llama-server) ビルドしたときのメモ

Debian13の環境でビルドした。

github.com

公式のビルド手順は以下に記載されている。

https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md

CPUビルド

cmakeとclangのインストールが必要。

git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build
cmake --build build --config Release -j8

build/bin/に全ツールが作成される。

注意点として、gccがデフォルトの環境ではエラーになった(おそらく多くのLinux環境で該当する)。その場合、以下で解決することができた。

rm -rf build/
CC=clang cmake -B build
CC=clang cmake --build build --config Release

上のように、ccのデフォルトをclangにして再度実行することで解決できる。

GPUバックエンド(vulkan)

自宅PCのGPUがAMDなので、CUDAではなくVulkanを使用してビルドした。

まずは以下でvulkanが有効か確認する。(vulkaninfoでエラーではなくGPUの情報が表示されればOK)

sudo apt install vulkan-tools
vulkaninfo

必要なツールをインストールする。

sudo apt install libvulkan-dev glslc

ビルドする。

cmake -B build -DGGML_VULKAN=1
cmake --build build --config Release -j8

研究・Bittorrentプロトコル

Bittorrentの複雑な仕様について調査してみた。まだ理解できていない点もあるけれど、一旦まとめてみる。

Bittorrentにはv1とv2の2つのバージョンがあるが、ここではv1について解説する。(現在の主流もv1である)

1.bencode

bencodeはBitTorrent特有のエンコードで、トレントファイルや通信の中では、この記法が使われている。

仕様はhttps://wiki.theory.org/BitTorrentSpecification#Bencodingを参照

2.トレントファイルの中身

仕様では"metainfo file"と呼ばれている。拡張子が.torrentの、いわゆるトレントファイルのこと。ファイル全体が1.のbencodeで書かれている。

詳細な仕様はhttps://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structureを参照

2.1 info

トレントファイルの最重要部分がinfo。主に以下のような情報が含まれている。

2.1.1 ファイルの情報

各ファイルのファイル名、ファイルサイズ、ファイルのパスの情報が書かれている。

2.1.2 ピースの情報

以下の情報が含まれている。

  • piece length : 一つのピースのサイズ
  • pieces : 各ピースの SHA1 ダイジェスト(20バイト)を、ピース数ぶん連結したバイト列

ピースについては下記に後述

2.2 その他

info以外に、トレントファイルには以下のような情報が含まれる。

  • announce : トラッカーのURL
  • creation date : 作成日時
  • created by : トレントファイル作成に使ったソフトウェア

3. ピース

ピースとは、共有対象とするファイルの中身を分割したデータのこと。共有するファイルが複数ある場合は、 トレントファイルのinfo内に書かれている順番にファイルを結合したデータ を分割する。

一つのピースのサイズは自由だが、だいたい256KBか512KBくらいになる。すべてのピースは同一サイズである必要がある(最終ピース以外)。

4. トラッカー

トラッカーは、各トレントのピアのIPアドレスやポートを取得するためのサーバ。P2P通信を行う前に、トラッカーにアクセスして、どこに接続すれば良いのかを確認する。

トラッカーは基本的に、普通のHTTP(S)サーバとして動く。拡張としてUDPトラッカーの仕様もあるが、ここでは説明を割愛する。

詳細な仕様はhttps://wiki.theory.org/BitTorrentSpecification#Tracker_HTTP.2FHTTPS_Protocolを参照

4.1. リクエスト

クライアントから、HTTP GETでサーバ(トラッカー)に通信する。URLの後ろに?を続ける形式でクエリを送信する。そのため、送信時はデータに対してURLエンコーディングが行われる。

以下のようなデータを送信する。

4.1.1. info_hash

info_hash は、トレントファイルのinfoの値をSHA1ハッシュ化した、160ビット(20バイト)のバイト列。info_hashは各トレントを識別するための重要な値で、他の場所でも使われる。

4.1.2. peer_id

peer_idは各ピアを識別するためのid。BitTorrentソフトウェアが内部的に自動生成している。慣習的な命名規則があり、使用しているソフトウェア名などがidを見ると分かるようになっている。

4.1.3. その他

info_hash、peer_id以外には、ポート番号、今までダウンロード・アップロードしたデータサイズの合計、ダウンロード完了までの残りサイズ、などの情報を送信している。

4.2. レスポンス

トラッカーからのレスポンスは、上で述べたbencodeエンコードされた文字列で返される。

トラッカーのレスポンスには、以下のような情報が含まれる。

  • complete : 共有するピースが100%揃っているピアの数
  • incomplete : ピースが揃っていないピアの数
  • interval : 次にトラッカーにリクエストするまでの待機時間
  • peers : ピアのリスト。各ピアのid、IPアドレス、ポート番号の情報が含まれる。

5. ピア通信

BitTorrentでのピア同士の通信(いわゆるP2P)通信は、TCPを用いて行われる。

UDPを用いた拡張(uTP)もあり、uTPでは細かい輻輳制御を行って通信が効率化されている。これについては割愛する。

バイナリデータのやり取りになるので、ここでは細かい仕様を省略して、どのような情報が送信されるのかを大まかにまとめておく。 詳細な仕様はhttps://wiki.theory.org/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29を参照

5.0. ハンドシェイク

前述のトラッカーへのリクエストでも送信した、info_hashpeer_idを含むデータを片方が送り、もう片方が同様のデータを送信したら接続が確立したとみなす。

5.1. メッセージ

ハンドシェイクが完了したあとに送信するメッセージを以下に挙げる。

5.1.0. keep-alive

接続が切断されないよう、通信がないときに定期的に送信する。

5.1.0-3. choke、unchoke、interested、not interested

BitTorrentでは、相手ピアとの関係について、以下の二つの状態を用いる。

  • choke : 相手ピアからのデータ送信リクエストを受け付けないことを、「チョーク(choke)している」と表現する。
  • interested : 相手ピアからデータを送ってもらいたい状態であるときに、「興味ある(interested)」と表現する。

上の二つをまとめて言うと、「自分が相手ピアからchokeされていない(=unchoke)、かつ、自分が相手ピアに対してinterested」の状態のときだけ、相手ピアからデータを送信してもらうためのリクエストを送ることができる。

逆から見ると、「自分が相手ピアをchokeしていない(=unchoke)かつ、相手ピアが自分に対してinterested」の状態のとき、相手ピアが自分にリクエストを送れる。

choke、unchoke、interested、not interestedのメッセージは、chokeとinterestedの状態を変更するメッセージになる。(接続開始時はchoked、not interestedの状態になる)

5.1.4. have

ダウンロード完了したピースの情報を伝える。

5.1.5. bitfield

ハンドシェイク完了直後に送ることができる(送らなくてもよい)。自分がダウンロードが完了しているピースと、完了していないピースをビットで表して送信し、ダウンロード状態を伝える。

5.1.6. request

相手ピアにデータの送信を依頼する。 ピースと、そのピースのどの部分のデータが欲しいかを伝える。(BitTorrentでは基本的に、一度にピースをすべて送るのではなく、ピースの一部分(ブロック)ごとに送信する)

5.1.7. piece

requestに応じて、データをピアに送信する。

5.1.8. cancel

requestをキャンセルする。(他のピアからダウンロードできたときなど)

5.2. 拡張メッセージ

上記のメッセージ以外にも、使用できる拡張(extension)のメッセージがいくつかある。Fast ExtensionExtension Protocolは標準化されている。また、標準化されていない拡張も実際の通信では使われることがある。

メタデータ送信の拡張(これも標準化されている)は重要で、トレントファイルに含まれるinfoの情報を相手ピアから取得できる。この拡張によって、トレントファイルをダウンロードしなくても、マグネットリンクを使ってピア通信することが可能になっている。

5.3. メッセージ暗号化の拡張(PE、MSE)

BitTorrentには、ピアとの通信を暗号化する拡張がある(PEもしくはMSEとも呼ばれる)。これは標準化されていないが、BitTorrentソフトウェアの多くはデフォルトで使用するよう。

この拡張の仕様については、下記リンクが参考になる。

https://jwodder.github.io/kbits/posts/bt-encrypt/

Wiresharkでパケットキャプチャして通信を見る場合は、この暗号化を無効にしないと通信内容が見えなかったので、勉強時は注意すること。

6. DHT(分散ハッシュテーブル)

DHTはトラッカーを使わずにピアを発見するための仕組み。P2P通信だけでピアを発見することで、単一障害点のないPure P2Pが実現できる。(トラッカーを使う場合、トラッカーとの通信はクライアントサーバモデルになるので、純粋なP2Pではない)

仕様は以下で標準化されている。

https://bittorrent.org/beps/bep_0005.html

ただ、この仕様は曖昧なところが残っていたりして分かりにくいので、以下の実装(webtorrentの一部)を主に参考にした。

https://github.com/webtorrent/bittorrent-dht

6.1. ピアとノード

DHTの項では、「ピア」と「ノード」を(公式仕様にのっとって)以下のように使い分ける。

  • ピア(peer) : で述べた、共有ファイルをやりとりする通信をしているクライアント。
  • ノード(node) : DHT通信をするクライアント。

ピアの通信とノードの通信は異なるものである。ネットワークは別々に分かれており、混ざることはない。

6.2. ノードが保持する情報

DHTの各ノードは、各々が以下の情報を保持する。

  • ノードID : 160ビット(20バイト)のランダムなID。各ノードがそれぞれ固有のIDを持つ。
  • ピアの情報 : トレントinfo_hashと、そのトレントの通信をしているピアの情報(IPアドレス、ポート)を保持する。
  • ルーティングテーブル : 他のノードの情報(IPアドレス、ポート、ノードID)を保持する。ルーティングテーブルはバケット(bucket)と呼ばれる単位で分けて管理される。

6.3. DHTの通信プロトコル(KRPC)

DHTではKRPCと呼ばれるプロトコルでノード同士の通信を行う。通信にはUDPを用いる。 KRPCには以下の3種類の通信がある。

  • クエリ(query)
  • レスポンス(response)
  • エラー(error)

あるノードが相手ノードに対してクエリを送信したら、相手ノードがレスポンスもしくはエラーを返す、というだけの通信となっている。

クエリは、さらにping、find_node、get_peers、announce_peerの4種類に分かれるので、以下で解説する。

6.3.1. ping

相手ノードが返答できる状態か確認する。

6.3.2. find_node

ノードの情報の送信を相手に依頼する。

パラメータとして160ビット(20バイト)のIDを渡す。相手は、そのIDに近いIDのノードをレスポンスとして返す。受け取ったノードの情報はルーティングテーブルに追加し、管理する。

(「近いID」の意味については後述)

6.3.3. get_peers

トレントのピアの情報の送信を、相手に依頼する。

トレントinfo_hashをパラメータとして渡す。もし相手がそのinfo_hashのピアの情報を持っていたら、その情報がレスポンスとして返される。

info_hashの情報を持っていなかった場合、 そのinfo_hashに近いIDのノードがレスポンスとして返される。この場合、レスポンスとして返されたノードそれぞれに対して、再度get_peersクエリを送信する。新しいノードが見つからなくなるまで、これを繰り返す。 この動作はDHTの仕組みの核となる。詳細は後述。ピアやノードの情報を受け取ったら、上記のように、それらの情報は保持して管理する。

また、get_peersのレスポンスではtokenも一緒に返される。これはannounce_peerで使用する。

6.3.4. announce_peer

自分のピアが参加しているトレントのinfo_hashを送信し、自分がそのトレントに参加していることを宣伝する。送信時に、同じ相手ノードから前に受け取ったtoken(get_peersのレスポンスに含まれる)が必要(これは送信元IPアドレス偽装の攻撃を防ぐためのもの)。トレントのinfo_hashに近いノードに送信する。

announce_peerを受け取った側は、この情報を上記のように保持する。

6.4. 距離の算出(distance metric)

ノードIDトレントinfo_hashはどちらも160ビット(20バイト)のデータである。DHTでは、これらのデータに対して 距離(distance metric) を定義する。

160ビットのデータを二つ与えたとき、この二つの間でビット演算のXOR(排他的論理和)を行う。その結果を符号なし整数(unsigned integer)として解釈した結果が「距離」となる。

二つのデータの「距離」が小さいとき、「近い」と表現する。

上記のfind_nodeget_peersannounce_peerではノードIDやinfo_hashの「近さ」の評価に基づいた操作を行っている。

6.4.1. 「距離」をなぜ使うか?

この「距離」はBitTorrentのDHTプロトコルの根幹となるアイディアである。 Kademliaという分散ハッシュテーブルの仕組みがベースとなっており、二分木探索によって、広大な空間から目的のデータに高速にたどり着くための仕組みとなっている。Kademliaについては以下が参考になる。

https://hazm.at/mox/distributed-system/algorithm/distributed-hash-table/kademlia/index.html

二分木で、自分のIDと近いノードの情報をより多く保持するような仕組みになっている(このあたり理解不足)。

トレントのピアの情報は、announce_peerによって、info_hashに近いIDを持ったノードに送信される。そのため、ピア情報を取得するときは、info_hashに近いノードに問い合わせることで発見できる。

6.5. bootstrap

DHTに参加するとき、最初に通信するノードの設定が事前に必要となる。この初期通信のことはbootstrapと呼ばれる。(ただし公式の仕様には記載がない)

bootstrapにおいては、find_nodeのクエリを使って、自分のノードIDと近いノードの情報を集める。このbootstrap通信の相手ノードとなる、初期ノードの情報は、BitTorrentクライアントのソフトウェアがデフォルトで設定していることが多い。

dht.transmissionbt.comやdht.libtorrent.orgなど、有名BitTorrentクライアント作成者の提供するノードが使われていたりする。

6.6. プロトコル拡張

DHTにもいくつか拡張が存在する。任意のkey-valueをDHTで送信する拡張は自由度が高いかもしれない。

7. PEX(Peer Exchange)

トラッカーDHTの他に、ピアを発見する方法としてはPEXがある。

PEXは、ピア通信の拡張メッセージの一つであり、ピア同士の通信で新しいピアの情報をやり取りする。トラッカーやDHTとは違い、すでにピア通信をしていることが前提となるので、まだ一つもピアを発見していない場合は使うことができない。

8. LSD(Local Service Discovery)

LSDは、マルチキャストを使用してピアを発見する方法。

仕様は以下

https://bittorrent.org/beps/bep_0014.html

9. WebSeed

WebSeedは、トレントの共有ファイルが置いてあるHTTPなどのサーバを、BitTorrentのシード(全ピースを持っているピア)かのように扱う仕組み。

サーバ側は通常のサーバから設定を変える必要はない。クライアント側で工夫して実装する。

WebSeedの仕様は以下

https://bittorrent.org/beps/bep_0019.html

参考リンク

開発者による非公式の仕様解説。公式の仕様より具体的でわかりやすい。ただし、基本的な仕様の解説のみ

https://wiki.theory.org/BitTorrentSpecification

公式仕様。DHT、uTPなどの拡張仕様もここに載っている。

https://bittorrent.org/beps/bep_0000.html

libtorrent。開発者のArvid Norberg氏は、BitTorrent Inc.の元開発者のようで、公式仕様の策定などにも関わっている。

https://libtorrent.org/

Arvid Norberg氏によるlibtorrentの解説動画。輻輳制御など細かい通信性能の話が多い。

https://www.youtube.com/watch?v=Z6Os8HD80A4

webtorrentのDHT実装。コードがシンプルなので参考になった。

https://github.com/webtorrent/bittorrent-dht

Kademlia(BitTorrentのDHT実装の下敷きになっている)の論文。

https://cs.nyu.edu/~anirudh/CSCI-GA.2620-001/papers/kademlia.pdf

分散ハッシュテーブル(DHT)、Kademliaに関する日本語の解説。

https://hazm.at/mox/distributed-system/algorithm/distributed-hash-table/index.html

https://hazm.at/mox/distributed-system/algorithm/distributed-hash-table/kademlia/index.html

首藤一幸(現・京都大学教授)氏によるKademliaの解説。

http://www.shudo.net/article/20040727-Kademlia/shudo-Kademlia.pdf

その他、Kademliaの解説

https://codethechange.stanford.edu/guides/guide_kademlia.html

https://kelseyc18.github.io/kademlia_vis/basics/1/

https://pub.tik.ee.ethz.ch/students/2006-So/SA-2006-19.pdf

ollamaでローカルLLMを動かす方法のメモ

ollamaをpodmanを使って動かしてみた。(Dockerでもほぼ同じコマンドで動くはず)

起動

podman run -d --name ollama -p 11434:11434 -v ~/.ollama:/root/.ollama docker.io/ollama/ollama

公開するポートは11434になる。ダウンロードするモデルは/root/.ollamaに保存されるので、コンテナ停止時に消したくないならホスト側にバインドしておくとよい。

モデルのダウンロード

podman exec -it ollama ollama pull llama3.2:3b

ダウンロードできるモデルはhttps://ollama.com/searchで検索できる。

LLMとチャットする

podman exec -it ollama ollama run llama3.2:3b

http経由での使用

モデル一覧の確認

curl http://localhost:11434/api/tags

チャット

curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2:3b",
  "messages": [{
    "role": "user",
    "content": "こんにちは"
  }],
  "stream": false
}'

この場合、チャットの返答はJSON形式で返ってくるので、必要に応じてjqコマンドなどを使う。

ループデバイス操作のメモ(Linux)

ループデバイスは、ファイルをデバイスのように使える仕組み。読み込み、書き込みできる。
ddでコピーした既存のデバイスをマウントしたいときなどに使える。

開く

-fを使うと自動でデバイス名割り当てになる。
ループデバイス名はloopX(Xは数字)のような名前。

sudo losetup -f [ファイル名]
sudo losetup [ループデバイス名] [ファイル名]

マウント

/dev/mapper/[ループデバイス名]がデバイスになるので、mount /dev/mapper/loopX /mntのようにマウントする

閉じる

sudo losetup -d [ループデバイス名]

(メモ)systemdサービスの通信量を取得

設定

systemdサービスで、IPAccounting=yesに変更する。
(一時的)

sudo systemctl set-property [対象サービス名].service IPAccounting=yes

(恒久的)
以下で設定ファイルにIPAccounting=yesを追記する。

sudo systemctl edit [対象サービス名].service

取得

systemctl show -p IPIngressBytes --value [対象サービス名].service
systemctl show -p IPEgressBytes --value [対象サービス名].service

計測開始からの累計通信量が表示される。
一秒ごとや一分ごとの通信量が見たいなら引き算すればよい

東京近郊・各駅のアクセス性を評価(引っ越しエリア検討のため)

引っ越しエリアを決めるために、東京周辺の代表的な駅のアクセスの良さを表にしてみた。 以下3駅への所要時間の平均を表にしている。(調査には乗り換え検索サイトを利用)

  • 東京
  • 新宿
  • 永田町

なぜ永田町を入れているのかというと、東京駅と新宿駅だけだと一部の駅(たとえば上野東京ライン埼京線が止まる駅)のアクセス性が過大評価されるように思えたからである。

①山手線の内側、②JR路線なし、③東京駅・新宿駅のどちらからも距離がある、④地下鉄路線が複数、の条件を満たす駅として指標に入れた。

路線 駅名 所要時間(平均) 東京 新宿 永田町
JR中央線 中野 14.3 20 5 18
JR京浜東北線 赤羽 21 17 15 31
京王線 明大前 21.3 28 8 28
小田急小田原線 経堂 21.3 30 15 19
メトロ半蔵門線 押上 24 18 29 25
メトロ有楽町線 小竹向原 24.3 30 19 24
JR横須賀線 武蔵小杉 25 22 22 31
JR中央線 吉祥寺 25.7 31 17 29
JR総武線 新小岩 26.3 14 31 34
JR京浜東北線 川崎 28.3 18 37 30
JR京浜東北線 川口 28.3 24 24 37
都営三田線 板橋本町 29.3 33 22 33
西武新宿線 沼袋 30.3 36 21 34
都営新宿線 船堀 30.7 26 33 33
メトロ東西線 西葛西 31 25 38 30
JR埼京線 戸田公園 33 34 24 41
JR京葉線 新浦安 33 20 47 32
JR京浜東北線 浦和 33.3 27 28 45
メトロ千代田線 綾瀬 33.3 29 36 35
JR京浜東北線 横浜 34.7 31 34 39
東急田園都市線 溝の口 35 41 35 29
京成押上線 青砥 35.3 30 36 40
埼玉高速鉄道 川口元郷 36 38 36 34
JR総武線 本八幡 36.3 26 40 43
メトロ有楽町線 和光市 37.7 43 28 42
JR常磐線 松戸 39.3 28 44 46
JR京浜東北線 大宮 40 34 34 52
JR総武線 西船橋 40 32 45 43
つくばエクスプレス 八潮 41.7 33 45 47
東武伊勢崎線 草加 44.7 41 46 47
京王線 調布 46 53 34 51
JR中央線 国立 47.3 53 38 51

「路線」の欄は、通っている路線の中から適当に一つ選んだだけ(この路線で所要時間を計算しているわけではない)。

正確な調査ではないが、大雑把な目安として。

なお、学校/職場が決まっているなら、現実的には実際の通勤・通学時間のほうが重要である。