GitHub Actions
Quality improvements: Unicode chars, Token class, imports, type hints, formatting
3f78ea8
Raw
History Blame Contribute Delete
4.03 kB
"""llama-cpp-python in-process backend."""
from __future__ import annotations
from hearthnet.services.llm.backends.base import BackendModel, ChatResult, Token
from hearthnet.services.llm.tokenizers import model_family
def _family(model_name: str) -> str:
return model_family(model_name)
class LlamaCppBackend:
name = "llama_cpp"
def __init__(self, model_path: str, n_ctx: int = 4096, n_gpu_layers: int = -1) -> None:
self._model_path = model_path
self._n_ctx = n_ctx
self._n_gpu_layers = n_gpu_layers
self._llm = None
model_name = model_path.split("/")[-1].split(".")[0]
self.models = [
BackendModel(
name=model_name,
family=_family(model_name),
context_length=n_ctx,
requires_internet=False,
)
]
def is_available(self) -> bool:
try:
from importlib.util import find_spec
from pathlib import Path
return Path(self._model_path).exists() and find_spec("llama_cpp") is not None
except ImportError:
return False
async def warm(self) -> None:
if not self.is_available():
return
import asyncio
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, self._load_model)
def _load_model(self) -> None:
from llama_cpp import Llama
self._llm = Llama(
model_path=self._model_path,
n_ctx=self._n_ctx,
n_gpu_layers=self._n_gpu_layers,
verbose=False,
)
async def chat(
self,
messages: list[dict],
*,
model: str = "",
stream: bool = False,
temperature: float = 0.7,
max_tokens: int = 1024,
**kwargs,
):
import asyncio
import time
if self._llm is None:
await self.warm()
if self._llm is None:
raise RuntimeError("llama.cpp model not loaded")
t0 = time.monotonic()
loop = asyncio.get_running_loop()
if not stream:
result = await loop.run_in_executor(
None,
lambda: self._llm.create_chat_completion(
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
),
)
text = result["choices"][0]["message"]["content"]
ms = int((time.monotonic() - t0) * 1000)
return ChatResult(
text=text,
tokens_in=result["usage"]["prompt_tokens"],
tokens_out=result["usage"]["completion_tokens"],
model=self.models[0].name,
ms=ms,
)
return self._stream_chat(messages, temperature, max_tokens)
async def _stream_chat(self, messages, temperature, max_tokens):
import asyncio
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(
None,
lambda: self._llm.create_chat_completion(
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
stream=True,
),
)
for chunk in result:
delta = chunk["choices"][0].get("delta", {})
text = delta.get("content", "")
done = chunk["choices"][0]["finish_reason"] is not None
if text or done:
yield Token(text=text, stop=done)
async def complete(self, prompt: str, *, model: str = "", stream: bool = False, **kwargs):
messages = [{"role": "user", "content": prompt}]
return await self.chat(messages, model=model, stream=stream, **kwargs)
async def close(self) -> None:
self._llm = None
def health(self) -> dict:
return {
"backend": "llama_cpp",
"model_path": self._model_path,
"loaded": self._llm is not None,
}