はじめに
自宅PCにベクトルデータベース(faiss)を構築しました。諸事情により、Ubuntu Server を使用します。Ubuntu の GUI は使用せず、他のPCの Windows から接続して操作します。
コマンド実行に PuTTY を使用します。今回は使用していませんが、 Tunnels を設定すると、Windows から VNC を使って接続することもできます。
PuTTY 上で文字をマウスで選択すると、自動的にコピーされ、他のアプリケーションに貼り付けることができます。また、PuTTY 上でマウスの右クリックを行うと、その位置にクリップボードの内容が貼り付けられます。
https://www.putty.org/



ファイルのダウンロードが必要な場合は、Windows のブラウザでファイルをダウンロードし、WinSCP を使用して Ubuntu Server に転送します。
https://winscp.net/eng/download.php

NVIDIA CUDA のインストールはこちらを参考にしました。
ローカル LLM の環境構築はこちらです。
開発環境
デスクトップPC(ASRock X300 マザーボード)
AMD Ryzen 5 5600G 6コア 12スレッド 3.9GHz
メモリ DDR4-3200 16GB x 2
SSD 1TB
GeForce RTX 3050
インストールするツールのバージョンを合わせないと、faiss-gpu が動作しないようです。以下の環境を目指します。
ツール | 付属 | バージョン |
---|---|---|
Ubuntu Server | 22.04.3 | |
Kernel | 6.2.0-26 | |
Default GCC | 11.4.0 | |
GLIBC | 2.35 | |
NVIDIA Driver | 550.54.14 | |
CUDA Toolkit | 12.4 | |
faiss-gpu | 1.9.0 |
Ubuntu Server インストール
ubuntu-22.04.3-live-server-amd64.iso
Rufus を使って起動 USB メモリを作成
PCの電源を入れて [F2] または DEL キーを押して USB から立ち上げる
Try or Install Ubuntu Server を選択して、PCへのインストールを開始
Language: English
Keyboard: Japanese
Your name: webmaster
Your servers name: jellyfish01
Pick a username: webmaster
Chose a password: ******
Confirm your password: ******
ネットワークは有線で接続
SSH は後でインストールする
インストールが終わったら、USB メモリを外して起動
OS アップデート バージョンを維持するために実施しない
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
再起動
SSH インストール
sudo apt update
sudo apt install openssh-server
外部から SSH で接続 ※ ここから putty で操作できます
ssh -l webmaster 192.168.1.24
password: ******
固定IP
ip addr show でネットワークカードの名前を確認(enp2s0)
sudo nano /etc/netplan/80-fixed-ip-address.yaml
network:
ethernets:
enp2s0:
dhcp4: false
addresses:
- 192.168.1.13/24
nameservers:
addresses: [210.123.12.34,789.12.654.32]
routes:
- to: default
via: 192.168.1.1
dhcp6: false
version: 2
sudo chmod 600 /etc/netplan/80-fixed-ip-address.yaml
sudo netplan apply
※ putty で操作していると、変更直後にネットワークが切断されるので、新しい IP アドレスで再接続します
よく使うコマンド
pwd
ls -alF
ip addr show
nano .env (矢印キーでカーソル移動、保存Ctrl+S、終了Ctrl+X)
Ctrl+Alt+[F6] でGUIを抜けてコンソール画面に行く
sudo shutdown -h now
sudo reboot
CUDA Toolkit
https://developer.nvidia.com/cuda-toolkit
旧バージョンは、右下にある「Archive of Previous CUDA Releases」のリンクから入手できます
CUDA Toolkit 12.4
Linux、x86_64、Ubuntu、22.04、deb (local)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-ubuntu2204.pin
sudo mv cuda-ubuntu2204.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda-repo-ubuntu2204-12-4-local_12.4.0-550.54.14-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2204-12-4-local_12.4.0-550.54.14-1_amd64.deb
sudo cp /var/cuda-repo-ubuntu2204-12-4-local/cuda-*-keyring.gpg /usr/share/keyrings/sudo apt-get update
sudo apt-get -y install cuda-toolkit-12-4
ここで、5行目は、4行目を実行して「To install the key, run this command:」で表示されるコマンドに置き換えて実行します
sudo apt-get install -y cuda-drivers
再起動
nano ~/.bashrc
export PATH="/usr/local/cuda/bin:$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH"
source ~/.bashrc
nano ~/.profile
#!/bin/bash
if [[ -f ~/.bashrc ]] ; then
. ~/.bashrc
fi
nvidia-smi
Driver Version: 550.54.14
nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2024 NVIDIA Corporation
Built on Tue_Feb_27_16:19:38_PST_2024
Cuda compilation tools, release 12.4, V12.4.99
Build cuda_12.4.r12.4/compiler.33961263_0
以下のウェブページを参考に、すべての nvidia のサービスが enable になっていることを確認します
sudo systemctl list-unit-files --type=service
必要ならば以下を実行して enable にします
sudo systemctl enable nvidia-hibernate.service nvidia-persistenced.service nvidia-resume.service nvidia-suspend.service
Anaconda
https://www.anaconda.com/download
ホームページでメールを登録すると、ダウンロードリンクが届きます
Linux、Python 3.12
Anaconda3-2024.10-1-Linux-x86_64.sh
をダウンロード。home ディレクトリにファイルを移動
bash ~/Anaconda3-2024.10-1-Linux-x86_64.sh
./anaconda3/bin/conda init
次のターミナルの起動から、conda 環境(base)になります

解除は conda deactivate
ベクトルデータベース(faiss)のインストール
事前準備
sudo apt install unzip
faiss-gpu インストール
conda install -c pytorch -c nvidia faiss-gpu=1.9.0
動作確認のために、faiss のページにある DOWNLOAD ZIP からプログラムコード一式をダウンロードします

unzip faiss-main.zip
cd faiss-main/tutorial/python
(base) webmaster@jellyfish01:~/faiss-main/tutorial/python$ ls -1
1-Flat.py
2-IVFFlat.py
3-IVFPQ.py
4-GPU.py
5-Multiple-GPUs.py
7-PQFastScan.py
8-PQFastScanRefine.py
9-RefineComparison.py
python 1-Flat.py
True
100000
[[ 0 393 363 78]
[ 1 555 277 364]
[ 2 304 101 13]
[ 3 173 18 182]
[ 4 288 370 531]]
[[0. 7.1751738 7.20763 7.2511625]
[0. 6.3235645 6.684581 6.799946 ]
[0. 5.7964087 6.391736 7.2815123]
[0. 7.2779055 7.5279875 7.662846 ]
[0. 6.7638035 7.2951202 7.3688145]]
[[ 381 207 210 477]
[ 526 911 142 72]
[ 838 527 1290 425]
[ 196 184 164 359]
[ 526 377 120 425]]
[[ 9900 10500 9309 9831]
[11055 10895 10812 11321]
[11353 11103 10164 9787]
[10571 10664 10632 9638]
[ 9628 9554 10036 9582]]
python 2-IVFFlat.py
[[ 9900 9309 9810 10048]
[11055 10895 10812 11321]
[11353 10164 9787 10719]
[10571 10664 10632 10203]
[ 9628 9554 9582 10304]]
[[ 9900 10500 9309 9831]
[11055 10895 10812 11321]
[11353 11103 10164 9787]
[10571 10664 10632 9638]
[ 9628 9554 10036 9582]]
python 3-IVFPQ.py
[[ 0 78 608 159]
[ 1 555 1063 5]
[ 2 304 179 134]
[ 3 265 182 527]
[ 4 288 531 827]]
[[1.5882578 6.3516216 6.516344 6.597596 ]
[1.2973676 5.735963 5.951533 6.3694015]
[1.7135715 5.651118 6.1645284 6.3573594]
[1.8189969 6.7453256 7.0173464 7.044258 ]
[1.4755101 5.778677 6.308148 6.4126477]]
[[10242 9900 8746 10914]
[10913 10765 10473 11373]
[10719 11291 9380 10600]
[10630 10664 10122 10005]
[ 9229 9878 10304 9459]]
python 4-GPU.py
100000
[[ 381 207 210 477]
[ 526 911 142 72]
[ 838 527 1290 425]
[ 196 184 164 359]
[ 526 377 120 425]]
[[ 9900 10500 9309 9831]
[11055 10895 10812 11321]
[11353 11103 10164 9787]
[10571 10664 10632 9638]
[ 9628 9554 10036 9582]]
100000
[[ 381 207 210 477]
[ 526 911 142 72]
[ 838 527 1290 425]
[ 196 184 164 359]
[ 526 377 120 425]]
[[ 9900 9309 9810 10048]
[11055 10895 10812 11321]
[11353 10164 9787 10719]
[10571 10664 10632 10203]
[ 9628 9554 9582 10304]]
python 5-Multiple-GPUs.py
number of GPUs: 1
100000
[[ 381 207 210 477]
[ 526 911 142 72]
[ 838 527 1290 425]
[ 196 184 164 359]
[ 526 377 120 425]]
[[ 9900 10500 9309 9831]
[11055 10895 10812 11321]
[11353 11103 10164 9787]
[10571 10664 10632 9638]
[ 9628 9554 10036 9582]]
python 7-PQFastScan.py
[[0 1 2 3]
[0 1 2 3]
[0 1 2 3]
[0 1 2 3]
[0 1 2 3]]
[[16.33721 16.33721 16.33721 16.33721 ]
[15.116527 15.116527 15.116527 15.116527]
[17.16953 17.16953 17.16953 17.16953 ]
[17.143526 17.143526 17.143526 17.143526]
[16.08081 16.08081 16.08081 16.08081 ]]
[[6742 6788 6802 6803]
[6742 6788 6802 6803]
[6742 6788 6802 6803]
[6742 6788 6802 6803]
[6742 6788 6802 6803]]
python 8-PQFastScanRefine.py
[[13 18 24 9 17 2 11 26 19 7]
[19 14 22 18 21 29 27 4 13 25]
[18 13 26 23 2 9 11 19 0 4]
[10 11 13 26 12 20 4 18 0 15]
[ 4 13 23 3 0 19 14 29 18 22]]
[[ 9.544294 9.606979 9.746547 9.758554 10.249306 10.555075
10.558601 10.6802635 10.904479 11.284525 ]
[ 8.090923 8.267822 8.660107 9.150578 9.263431 9.5033
9.573061 10.001304 10.151635 10.272773 ]
[ 8.490352 8.556078 8.782871 9.400972 9.495619 9.554632
9.803928 9.993049 10.504381 10.699816 ]
[ 8.330563 8.555349 8.671155 9.483734 9.542344 9.554526
9.724651 9.920914 9.926396 10.169458 ]
[ 8.472397 8.755564 8.837374 8.9055195 9.545532 9.992572
10.073465 10.271679 10.275333 10.508036 ]]
[[0 1 2 3]
[0 1 2 3]
[0 1 2 3]
[0 1 2 3]
[0 1 2 3]]
python 9-RefineComparison.py
[[37127 58750 80258 23369 15585 76780 715 47582 36997 75695 79478 2731
37226 37030 1461 14625 25695 72008 51978 3029 52360 34597 49870 99230
82166 61145 77554 41486 52938 45992 31675 83963 82884 51824 14253 26829
98367 47779 16280 5701 38769 37444 40950 3154 75185 81630 11084 34055
17043 57268 37109 85484 47040 37074 36626 13266 59875 87351 51478 42139
11844 37427 8318 21919 65728 15532 72959 17458 31988 43550 87598 95532
87899 48962 52356 37218 3786 39929 72032 30351 2316 37318 21947 85248
81821 65540 35176 67053 60871 83799 85489 45245 50618 79618 57931 21242
119 9385 71709 18979]
[ 736 25457 73476 44125 40255 49243 1787 32306 68735 80902 86966 77102
41056 69383 41204 49796 1248 61255 7709 91908 83172 60422 10562 15626
...
PostgreSQL インストール
sudo apt update
sudo apt install postgresql postgresql-contrib
psql --version
sudo -i -u postgres
psql
postgres=#
CREATE USER admin WITH PASSWORD 'mypassword' SUPERUSER;
CREATE USER webmaster WITH PASSWORD 'mypassword';
CREATE DATABASE webmaster OWNER webmaster;
\q
で抜ける
exit
で postgres ユーザーを抜ける
psql -U webmaster -d webmaster -h localhost
CREATE TABLE knowledge_documents (
id UUID PRIMARY KEY,
knowledge_id TEXT NOT NULL,
content TEXT NOT NULL,
title TEXT NOT NULL,
metadata JSONB,
created_at TIMESTAMP DEFAULT now()
);
\q
で抜ける
\d knowledge_documents (テーブル一覧)
\l (データベース一覧)
\c (現在接続しているデータベース)
SELECT * FROM knowledge_documents;
faiss_service の作成
次のように作成します。
項目 | ツール |
---|---|
文書データベース | PostgreSQL |
ベクトルデータベース・検索 | faiss-gpu |
埋め込み(Embeddings) | OpenAI API(text-embedding-3-small) |
ウェブサーバー | Python Flask |
pip install flask psycopg2 openai
mkdir faiss_service
cd faiss_service
nano faiss_service.py
from flask import Flask, request, jsonify
from openai import OpenAI
import psycopg2
import faiss
import numpy as np
import os
import uuid
import json
app = Flask(__name__)
client = OpenAI()
# DB接続設定
db_conn = psycopg2.connect(
dbname="webmaster",
user="webmaster",
password=os.environ.get("PSQL_DB_PASSWORD"), # 環境変数から取得
host="localhost",
port="5432"
)
# FAISSインデックスの初期化
dimension = 1536
index_path = "faiss.index" # knowledge が複数ある場合は、複数 faiss_service_n.py を作成して、ポートで分ける(faiss.index.n)
res = faiss.StandardGpuResources() # use a single GPU
doc_id_map = []
if os.path.exists(index_path):
index = faiss.read_index(index_path)
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)
with open("doc_id_map.json", "r") as f:
doc_id_map = json.load(f)
print("FAISSインデックスをロードしました")
else:
index = faiss.IndexFlatL2(dimension)
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)
print("FAISS新規インデックスを作成しました")
def save_faiss():
cpu_index = faiss.index_gpu_to_cpu(gpu_index)
faiss.write_index(cpu_index, index_path)
with open("doc_id_map.json", "w") as f:
json.dump(doc_id_map, f)
print("FAISSインデックスを保存しました")
@app.route('/retrieval', methods=['POST'])
def retrieval():
"""クエリベクトルで検索"""
try:
data = request.get_json()
knowledge_id = data.get("knowledge_id") # knowledge が複数ある場合は、複数 faiss_service_n.py を作成して、ポートで分ける
query = data.get("query") # ユーザーが送信した質問文
retrieval_setting = data.get("retrieval_setting", {})
top_k = retrieval_setting.get("top_k", 5)
score_threshold = retrieval_setting.get("score_threshold", 0.5)
if not knowledge_id:
return jsonify({"error_code": 1001, "error_msg": "knowledge_idは必須です"}), 400
if not query:
return jsonify({"error_code": 1002, "error_msg": "queryは必須です"}), 400
# OpenAI API でクエリをベクトル化
response = client.embeddings.create(
input=query,
model="text-embedding-3-small"
)
query_vector = np.array(response.data[0].embedding, dtype=np.float32)
# FAISS検索
D, I = gpu_index.search(np.array([query_vector]), top_k)
cur = db_conn.cursor()
records = []
for dist, idx in zip(D[0], I[0]):
if idx == -1:
continue
score = 1 / (1 + dist)
if score < score_threshold:
continue
# UUIDを復元
doc_uuid = doc_id_map[idx]
# PostgreSQLから取得
cur.execute("""
SELECT content, title, metadata
FROM knowledge_documents
WHERE id = %s AND knowledge_id = %s
""", (doc_uuid, knowledge_id))
row = cur.fetchone()
if row:
records.append({
"content": row[0],
"title": row[1],
"metadata": row[2],
"score": score
})
return jsonify({"records": records})
except Exception as e:
return jsonify({"error_code": 5000, "error_msg": str(e)}), 500
@app.route('/add', methods=['POST'])
def add():
"""新しいドキュメント追加"""
try:
data = request.get_json()
knowledge_id = data['knowledge_id'] # knowledge が複数ある場合は、複数 faiss_service_n.py を作成して、ポートで分ける
content = data['content']
title = data['title']
metadata = data.get('metadata', {})
# OpenAI APIで埋め込みベクトルを取得
response = client.embeddings.create(
input = content,
model = "text-embedding-3-small"
)
vector = np.array(response.data[0].embedding, dtype=np.float32)
# UUID生成
new_id = str(uuid.uuid4())
# PostgreSQLに保存
cur = db_conn.cursor()
cur.execute("""
INSERT INTO knowledge_documents (id, knowledge_id, content, title, metadata)
VALUES (%s, %s, %s, %s, %s)
""", (new_id, knowledge_id, content, title, json.dumps(metadata)))
db_conn.commit()
# FAISSに保存
gpu_index.add(np.array([vector]))
doc_id_map.append(new_id)
save_faiss()
return jsonify({"status": "success", "id": new_id})
except Exception as e:
return jsonify({"error_code": 5000, "error_msg": str(e)}), 500
@app.route('/delete', methods=['POST'])
def delete():
"""ドキュメントを削除"""
try:
# すべての文書を再度ベクトル化するため、コストがかかります
data = request.get_json()
doc_id = data['id'] # 削除対象のUUID文字列
# knowledge が複数ある場合は、複数 faiss_service_n.py を作成して、ポートで分ける
# PostgreSQLから削除
cur = db_conn.cursor()
cur.execute("DELETE FROM knowledge_documents WHERE id = %s", (doc_id,))
db_conn.commit()
# 関数内で値を変更しする際は、global宣言が必要
global gpu_index
global doc_id_map
# FAISSを一旦すべて削除
dimension = 1536
index = faiss.IndexFlatL2(dimension)
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)
doc_id_map = []
# PostgreSQLからデータ読み出してFAISS再構築
cur.execute("SELECT id, content FROM knowledge_documents")
rows = cur.fetchall()
for row in rows:
doc_uuid, content = row
# OpenAI APIで再度ベクトル化
response = client.embeddings.create(
input=content,
model="text-embedding-3-small"
)
vector = np.array(response.data[0].embedding, dtype=np.float32)
# FAISSに追加
gpu_index.add(np.array([vector]))
doc_id_map.append(doc_uuid)
# 保存
save_faiss()
return jsonify({"status": "success", "id": doc_id})
except Exception as e:
return jsonify({"error_code": 5000, "error_msg": str(e)}), 500
# ウェブサーバー起動
@app.route("/")
def index():
return "こんにちは、これは Python の Web サーバーです!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
export PSQL_DB_PASSWORD="..."
export OPENAI_API_KEY="..."
python faiss_service.py
Dify に接続
Dify で外部ナレッジベースの設定を行って、faiss_service に接続します。
ナレッジ
「外部ナレッジベースと連携 →」

外部ナレッジベース連携APIを追加
API Key は未使用なので ABCdef など

外部ナレッジベース名: ローカルRAG
外部ナレッジベースID: AAA-BBB-CCC
「連携 →」ボタン押下

参考ページ
【Python】faiss-gpu をビルドしてインストールする
FAISS Getting started