WatNeru commited on
Commit
adb0f98
·
1 Parent(s): e3cf4cd

Switch from llama-cpp-python to transformers for PyTorch model support

Browse files
Files changed (3) hide show
  1. app.py +19 -35
  2. package/ai.py +56 -72
  3. requirements.txt +3 -1
app.py CHANGED
@@ -40,10 +40,7 @@ adapter = None
40
  status_message = "モデル初期化中..."
41
  status_lock = threading.Lock()
42
 
43
- HF_MODEL_REPO = os.getenv("HF_MODEL_REPO", "WatNeru/LLMView-model")
44
- HF_MODEL_FILENAME = os.getenv(
45
- "HF_MODEL_FILENAME", "llama-3.2-3b-instruct-q4_k_m.gguf"
46
- )
47
  HF_LOCAL_DIR = Path(
48
  os.getenv(
49
  "HF_MODEL_LOCAL_DIR",
@@ -72,25 +69,27 @@ def _set_status(message: str) -> None:
72
 
73
 
74
  def ensure_model_available() -> str:
75
- """モデルファイルをローカルに用意(なければHFから取得)"""
76
  print(f"[MODEL] ensure_model_available() 開始")
77
  current_path = Path(path_manager.get_model_path())
78
  print(f"[MODEL] 現在のモデルパス: {current_path}")
79
 
80
- if current_path.exists():
81
- print(f"[MODEL] 既存のモデルファイルを使用: {current_path}")
82
- return str(current_path)
83
-
84
- print(f"[MODEL] モデルファイルが見つからないため、ダウンロード開始")
 
 
 
85
  HF_LOCAL_DIR.mkdir(parents=True, exist_ok=True)
86
  print(f"[MODEL] ダウンロード先ディレクトリ: {HF_LOCAL_DIR}")
87
  _set_status("Hugging Face からモデルをダウンロード中...")
88
 
89
- # snapshot_downloadの戻り値(ダウンロードィレクトリを使用
90
  try:
91
  downloaded_dir = snapshot_download(
92
  repo_id=HF_MODEL_REPO,
93
- allow_patterns=HF_MODEL_FILENAME,
94
  local_dir=str(HF_LOCAL_DIR),
95
  local_dir_use_symlinks=False,
96
  token=HF_TOKEN,
@@ -102,32 +101,17 @@ def ensure_model_available() -> str:
102
  traceback.print_exc()
103
  raise
104
 
105
- # ダウンロードされたディレクトリからファイル検索
106
  downloaded_dir_path = Path(downloaded_dir)
107
  print(f"[MODEL] ダウンロード先パス: {downloaded_dir_path}")
108
 
109
- # まず直接パスを試す
110
- downloaded = downloaded_dir_path / HF_MODEL_FILENAME
111
- print(f"[MODEL] 直接パスを確認: {downloaded}")
112
-
113
- if not downloaded.exists():
114
- # リポジトリ構造を保持している可能性があるので、再帰的に検索
115
- print(f"[MODEL] 直接パスに存在しないため、再帰的に検索中...")
116
- found_files = list(downloaded_dir_path.rglob(HF_MODEL_FILENAME))
117
- print(f"[MODEL] 見つかったファイル数: {len(found_files)}")
118
- if found_files:
119
- downloaded = found_files[0]
120
- print(f"[MODEL] ファイルを発見: {downloaded}")
121
- else:
122
- # ディレクトリ内の全ファイルをリストアップしてデバッグ情報を出力
123
- all_files = list(downloaded_dir_path.rglob("*"))
124
- print(f"[MODEL] ディレクトリ内の全ファイル: {[str(f) for f in all_files[:20]]}")
125
- raise FileNotFoundError(
126
- f"モデル {HF_MODEL_FILENAME} が {downloaded_dir} に見つかりません。"
127
- f"見つかったファイル: {[str(f) for f in all_files[:10]]}"
128
- )
129
-
130
- model_path_str = str(downloaded.resolve())
131
  print(f"[MODEL] モデルパスを設定: {model_path_str}")
132
  os.environ["LLM_MODEL_PATH"] = model_path_str
133
  path_manager.model_path = model_path_str
 
40
  status_message = "モデル初期化中..."
41
  status_lock = threading.Lock()
42
 
43
+ HF_MODEL_REPO = os.getenv("HF_MODEL_REPO", "meta-llama/Llama-3.2-3B-Instruct")
 
 
 
44
  HF_LOCAL_DIR = Path(
45
  os.getenv(
46
  "HF_MODEL_LOCAL_DIR",
 
69
 
70
 
71
  def ensure_model_available() -> str:
72
+ """モデルディレクトリをローカルに用意(なければHFから取得)"""
73
  print(f"[MODEL] ensure_model_available() 開始")
74
  current_path = Path(path_manager.get_model_path())
75
  print(f"[MODEL] 現在のモデルパス: {current_path}")
76
 
77
+ # PyTorch モデルの場合、ディレクトリ全体をチェック
78
+ if current_path.exists() and current_path.is_dir():
79
+ # config.json があるか確認(モデルディレクトリの確認)
80
+ if (current_path / "config.json").exists():
81
+ print(f"[MODEL] 既存のモデルディレクトリ使用: {current_path}")
82
+ return str(current_path)
83
+
84
+ print(f"[MODEL] モデルディレクトリが見つからないため、ダウンロードを開始")
85
  HF_LOCAL_DIR.mkdir(parents=True, exist_ok=True)
86
  print(f"[MODEL] ダウンロード先ディレクトリ: {HF_LOCAL_DIR}")
87
  _set_status("Hugging Face からモデルをダウンロード中...")
88
 
89
+ # snapshot_downloadでモデル全体をダウンロード(PyTorch モルは複数ファイル
90
  try:
91
  downloaded_dir = snapshot_download(
92
  repo_id=HF_MODEL_REPO,
 
93
  local_dir=str(HF_LOCAL_DIR),
94
  local_dir_use_symlinks=False,
95
  token=HF_TOKEN,
 
101
  traceback.print_exc()
102
  raise
103
 
104
+ # ダウンロードされたディレクトリを確認
105
  downloaded_dir_path = Path(downloaded_dir)
106
  print(f"[MODEL] ダウンロード先パス: {downloaded_dir_path}")
107
 
108
+ # config.json があるか確認
109
+ if not (downloaded_dir_path / "config.json").exists():
110
+ raise FileNotFoundError(
111
+ f"モデルディレクトリ {downloaded_dir} に config.json が見つかりません。"
112
+ )
113
+
114
+ model_path_str = str(downloaded_dir_path.resolve())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  print(f"[MODEL] モデルパスを設定: {model_path_str}")
116
  os.environ["LLM_MODEL_PATH"] = model_path_str
117
  path_manager.model_path = model_path_str
package/ai.py CHANGED
@@ -47,64 +47,47 @@ class AI:
47
  cls._instances.clear()
48
 
49
  def _load_model(self, model_path: str) -> Optional[Any]:
50
- """モデルをロード"""
51
  try:
52
  if not model_path or not os.path.exists(model_path):
53
  return None
54
 
55
- # llama-cpp-pythonを使用してモデルをロード
56
  try:
57
- from llama_cpp import Llama
 
58
 
59
  # GPUが利用可能かチェック
60
- use_gpu = False
61
- try:
62
- # CUDAが利可能かチェック
63
- result = subprocess.run(
64
- ["nvidia-smi"],
65
- capture_output=True,
66
- text=True,
67
- timeout=2
68
- )
69
- if result.returncode == 0:
70
- # GPUが利用可能
71
- use_gpu = True
72
- print("[AI] GPU検出: CUDAを使用します")
73
- else:
74
- print("[AI] GPU未検出: CPUモードで実行します")
75
- except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
76
- # nvidia-smiが使えない場合はCPUモード
77
  print("[AI] GPU未検出: CPUモードで実行します")
78
 
79
- # GPUモードで試行
80
- if use_gpu:
81
- try:
82
- llm = Llama(
83
- model_path=model_path,
84
- n_ctx=2048,
85
- logits_all=True,
86
- n_gpu_layers=-1, # すべてのレイヤーをGPUに配置
87
- verbose=True,
88
- )
89
- print("[AI] モデルロード成功 (GPUモード)")
90
- return llm
91
- except Exception as gpu_error:
92
- print(f"[AI] GPUモードでのロードに失敗、CPUモードにフォールバック: {gpu_error}")
93
- use_gpu = False # CPUモードにフォールバック
94
 
95
- # CPUモドでロード
96
- llm = Llama(
97
- model_path=model_path,
98
- n_ctx=2048,
99
- logits_all=True,
100
- n_gpu_layers=0,
101
- verbose=True,
102
  )
103
- print("[AI] モデルロード成功 (CPUモード)")
104
- return llm
 
 
 
 
 
 
 
 
 
 
 
105
  except Exception as e:
106
  import traceback
107
- print(f"[AI] llama-cpp-pythonでのロードに失敗: {e}")
108
  traceback.print_exc()
109
  return None
110
 
@@ -129,35 +112,34 @@ class AI:
129
  return []
130
 
131
  try:
132
- # llama-cpp-pythoncreate_completionを使用
133
- if hasattr(self.model, "create_completion"):
134
- resp = self.model.create_completion(
135
- prompt=text,
136
- max_tokens=1,
137
- logprobs=k,
138
- temperature=0.0,
139
- echo=False,
140
- )
141
 
142
- # ポンスからトークンと確率を抽出
143
- items: List[Tuple[str, float]] = []
144
- choice = resp.get("choices", [{}])[0]
145
- lp = choice.get("logprobs", {})
146
- top = lp.get("top_logprobs", [])
147
 
148
- if top and isinstance(top[0], dict):
149
- cand_dict = top[0]
150
- tokens = list(cand_dict.keys())
151
- logprobs = [cand_dict[t] for t in tokens]
152
-
153
- # logprobsを確率に変換
154
- probs = self._softmax_from_logprobs(logprobs)
155
-
156
- for token, prob in zip(tokens, probs):
157
- items.append((token, float(prob)))
158
 
159
- # 確率順でソートして上位k個を返す
160
- items = sorted(items, key=lambda x: x[1], reverse=True)[:k]
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  # 確率を正規化
163
  if items:
@@ -171,11 +153,13 @@ class AI:
171
 
172
  return items
173
  else:
174
- print("モデルがcreate_completionメソッドをサポートていません")
175
  return []
176
 
177
  except Exception as e:
178
  print(f"トークン確率取得エラー: {e}")
 
 
179
  return []
180
 
181
  def _softmax_from_logprobs(self, logprobs: List[float]) -> List[float]:
 
47
  cls._instances.clear()
48
 
49
  def _load_model(self, model_path: str) -> Optional[Any]:
50
+ """モデルをロード(Transformers使用)"""
51
  try:
52
  if not model_path or not os.path.exists(model_path):
53
  return None
54
 
55
+ # transformersを使用してモデルをロード
56
  try:
57
+ from transformers import AutoModelForCausalLM, AutoTokenizer
58
+ import torch
59
 
60
  # GPUが利用可能かチェック
61
+ device = "cuda" if torch.cuda.is_available() else "cpu"
62
+ if device == "cuda":
63
+ print("[AI] GPU検出: CUDAを使します")
64
+ else:
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  print("[AI] GPU未検出: CPUモードで実行します")
66
 
67
+ print(f"[AI]デルをロード中: {model_path}")
68
+ print(f"[AI] デバイス: {device}")
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ # クナイザーとモデルをロード
71
+ tokenizer = AutoTokenizer.from_pretrained(
72
+ model_path,
73
+ token=os.getenv("HF_TOKEN"),
 
 
 
74
  )
75
+ model = AutoModelForCausalLM.from_pretrained(
76
+ model_path,
77
+ torch_dtype=torch.float16 if device == "cuda" else torch.float32,
78
+ device_map="auto" if device == "cuda" else None,
79
+ token=os.getenv("HF_TOKEN"),
80
+ )
81
+
82
+ if device == "cpu":
83
+ model = model.to(device)
84
+
85
+ # モデルとトークナイザーをタプルで返す
86
+ print(f"[AI] モデルロード成功 ({device}モード)")
87
+ return (model, tokenizer)
88
  except Exception as e:
89
  import traceback
90
+ print(f"[AI] transformersでのロードに失敗: {e}")
91
  traceback.print_exc()
92
  return None
93
 
 
112
  return []
113
 
114
  try:
115
+ # transformers モデル場合
116
+ if isinstance(self.model, tuple) and len(self.model) == 2:
117
+ model, tokenizer = self.model
118
+ import torch
 
 
 
 
 
119
 
120
+ # テキストをトークン
121
+ inputs = tokenizer(text, return_tensors="pt")
122
+ device = next(model.parameters()).device
123
+ inputs = {k: v.to(device) for k, v in inputs.items()}
 
124
 
125
+ # モデルで推論(勾配計算なし)
126
+ with torch.no_grad():
127
+ outputs = model(**inputs)
128
+ logits = outputs.logits[0, -1, :] # 最後のトークンのlogits
 
 
 
 
 
 
129
 
130
+ # logitsを確率に変換(softmax)
131
+ probs = torch.softmax(logits, dim=-1)
132
+
133
+ # 上位k個のトークンを取得
134
+ top_probs, top_indices = torch.topk(probs, k)
135
+
136
+ # トークンIDを文字列に変換
137
+ items: List[Tuple[str, float]] = []
138
+ for idx, prob in zip(top_indices, top_probs):
139
+ token_id = idx.item()
140
+ token = tokenizer.decode([token_id])
141
+ prob_value = prob.item()
142
+ items.append((token, float(prob_value)))
143
 
144
  # 確率を正規化
145
  if items:
 
153
 
154
  return items
155
  else:
156
+ print("モデルがサポートされていません")
157
  return []
158
 
159
  except Exception as e:
160
  print(f"トークン確率取得エラー: {e}")
161
+ import traceback
162
+ traceback.print_exc()
163
  return []
164
 
165
  def _softmax_from_logprobs(self, logprobs: List[float]) -> List[float]:
requirements.txt CHANGED
@@ -10,7 +10,9 @@ sudachipy>=0.6.7
10
  sudachidict-core>=20240125
11
 
12
  # AI/LLM
13
- llama-cpp-python==0.2.79
 
 
14
 
15
  # UI
16
  gradio>=4.38.0
 
10
  sudachidict-core>=20240125
11
 
12
  # AI/LLM
13
+ transformers>=4.40.0
14
+ torch>=2.0.0
15
+ accelerate>=0.30.0
16
 
17
  # UI
18
  gradio>=4.38.0