oneiros / model /loader.py
Adinda Panca Mochamad
Fix Space: Python 3.10, lazy llama import, startup aman
552d89f
Raw
History Blame Contribute Delete
5.82 kB
"""Singleton loader llama-cpp untuk Space dan lokal."""
from __future__ import annotations
import os
import threading
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from llama_cpp import Llama
NAMA_FILE_MODEL = "qwen2.5-7b-instruct-q4_k_m.gguf"
NAMA_SHARD_Q4KM_PERTAMA = "qwen2.5-7b-instruct-q4_k_m-00001-of-00002.gguf"
NAMA_SHARD_Q4KM_KEDUA = "qwen2.5-7b-instruct-q4_k_m-00002-of-00002.gguf"
NAMA_FILE_ALTERNATIF = (
NAMA_FILE_MODEL,
NAMA_SHARD_Q4KM_PERTAMA,
"qwen2.5-7b-instruct-q2_k.gguf",
)
_kunci_inferensi = threading.Lock()
_model: object | None = None
_path_model_tercache: str | None = None
def kunci_inferensi():
"""Context manager untuk serialisasi panggilan model."""
return _kunci_inferensi
def _jumlah_layer_gpu() -> int:
nilai_env = os.getenv("N_GPU_LAYERS", "")
if nilai_env != "":
return int(nilai_env)
if os.getenv("SPACE_ID"):
return 0
return -1
def _folder_punya_pasangan_shard(folder: Path) -> str | None:
"""llama.cpp butuh shard 00001 + 00002 di folder yang sama."""
pertama = folder / NAMA_SHARD_Q4KM_PERTAMA
kedua = folder / NAMA_SHARD_Q4KM_KEDUA
if pertama.is_file() and kedua.is_file():
return str(pertama.resolve())
return None
def _cari_gguf_di_direktori(akar: Path, kedalaman_maks: int = 8) -> str | None:
if not akar.is_dir():
return None
akar_resolved = akar.resolve()
# Prioritas: pasangan shard Q4_K_M lengkap
for root, dirs, berkas in os.walk(akar_resolved):
kedalaman = len(Path(root).relative_to(akar_resolved).parts)
if kedalaman > kedalaman_maks:
dirs.clear()
continue
pasangan = _folder_punya_pasangan_shard(Path(root))
if pasangan:
return pasangan
for root, dirs, berkas in os.walk(akar_resolved):
kedalaman = len(Path(root).relative_to(akar_resolved).parts)
if kedalaman > kedalaman_maks:
dirs.clear()
continue
for nama_target in NAMA_FILE_ALTERNATIF:
if nama_target in berkas:
return str((Path(root) / nama_target).resolve())
return None
def resolve_model_path() -> str:
"""Resolve path GGUF; hormati MODEL_PATH dari env Space."""
env_path = os.getenv("MODEL_PATH")
if env_path:
path_env = Path(env_path)
if path_env.is_file():
return str(path_env.resolve())
if path_env.is_dir():
pasangan = _folder_punya_pasangan_shard(path_env)
if pasangan:
return pasangan
ditemukan = _cari_gguf_di_direktori(path_env)
if ditemukan:
return ditemukan
kandidat = [Path("./models") / n for n in NAMA_FILE_ALTERNATIF]
kandidat += [Path(f"/data/{n}") for n in NAMA_FILE_ALTERNATIF]
kandidat.append(Path("/data/models") / NAMA_FILE_MODEL)
for path in kandidat:
if path.is_file():
return str(path.resolve())
for folder in (Path("/data"), Path("/data/models"), Path("./models")):
if folder.is_dir():
pasangan = _folder_punya_pasangan_shard(folder)
if pasangan:
return pasangan
ditemukan = _cari_gguf_di_direktori(folder)
if ditemukan:
return ditemukan
cache_hub = Path.home() / ".cache" / "huggingface" / "hub"
if cache_hub.is_dir():
for repo_dir in cache_hub.glob("models--Qwen--Qwen2.5-7B-Instruct-GGUF*"):
ditemukan = _cari_gguf_di_direktori(repo_dir)
if ditemukan:
return ditemukan
return str((Path("./models") / NAMA_SHARD_Q4KM_PERTAMA).resolve())
def diagnosis_lingkungan_model() -> dict:
"""Info debug untuk log Space / skrip verifikasi."""
path = resolve_model_path()
folder = Path(path).parent if path else Path(".")
ada_shard_1 = (folder / NAMA_SHARD_Q4KM_PERTAMA).is_file()
ada_shard_2 = (folder / NAMA_SHARD_Q4KM_KEDUA).is_file()
isi_data: list[str] = []
if Path("/data").is_dir():
try:
isi_data = sorted(os.listdir("/data"))[:30]
except OSError:
isi_data = ["<tidak bisa baca /data>"]
return {
"space_id": os.getenv("SPACE_ID"),
"model_path": path,
"model_file_exists": Path(path).is_file() if path else False,
"shard_00001": ada_shard_1,
"shard_00002": ada_shard_2,
"shard_pair_ok": ada_shard_1 and ada_shard_2,
"n_gpu_layers": _jumlah_layer_gpu(),
"n_ctx": os.getenv("N_CTX", "4096"),
"skip_warmup": os.getenv("ONEIROS_SKIP_WARMUP"),
"isi_data_root": isi_data,
}
def get_model_path() -> str:
global _path_model_tercache
if _path_model_tercache is None:
_path_model_tercache = resolve_model_path()
return _path_model_tercache
def reset_model_path_cache() -> None:
global _path_model_tercache, _model
_path_model_tercache = None
_model = None
def get_model() -> "Llama":
global _model, _path_model_tercache
from llama_cpp import Llama
path = resolve_model_path()
_path_model_tercache = path
if _model is None:
if not Path(path).is_file():
diag = diagnosis_lingkungan_model()
raise FileNotFoundError(
f"Model GGUF tidak ditemukan di {path}. "
f"Diagnosis: shard_pair_ok={diag['shard_pair_ok']}, "
f"isi /data={diag['isi_data_root']}. "
"Set MODEL_PATH ke shard 00001 atau tunggu preload."
)
_model = Llama(
model_path=path,
n_ctx=int(os.getenv("N_CTX", "4096")),
n_gpu_layers=_jumlah_layer_gpu(),
verbose=False,
)
return _model
MODEL_PATH = resolve_model_path()