gamedevelop's picture
Update chatcode/runtime.py
18aeae0 verified
Raw History Blame Contribute Delete
4.74 kB
"""Pinned text-only inference. Model weights are loaded only on a cloud GPU."""
from __future__ import annotations
import json
import os
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CONFIG = json.loads(Path(__file__).with_name("config.json").read_text(encoding="utf-8"))
os.environ.setdefault("HF_HOME", str(ROOT / ".cache/huggingface"))
os.environ.setdefault("HF_HUB_DISABLE_XET", "1")
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "false")
def conversation(prompt: str, history: list[dict] | None = None) -> list[dict]:
if not prompt.strip():
raise ValueError("Enter a message first.")
if len(prompt) > 24000:
raise ValueError("Please split this message into smaller parts.")
messages = [{"role": "system", "content": CONFIG["system_prompt"]}]
previous = history or []
if len(previous) % 2:
raise ValueError("Conversation history is incomplete. Start a new chat.")
for index, item in enumerate(previous):
if item.get("role") != ("user" if index % 2 == 0 else "assistant"):
raise ValueError("Conversation history is invalid. Start a new chat.")
if not isinstance(item.get("content"), str):
raise ValueError("This chat accepts text messages only.")
messages.append({"role": item["role"], "content": item["content"]})
messages.append({"role": "user", "content": prompt.strip()})
return messages
def gpu_available() -> bool:
import torch
return bool(torch.cuda.is_available())
def load_engine(adapter: str | None = None):
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
if not gpu_available():
raise RuntimeError("The cloud model is not connected. This computer shows the interface preview only.")
tokenizer = AutoTokenizer.from_pretrained(
CONFIG["base_model"], revision=CONFIG["base_revision"], trust_remote_code=False)
model = AutoModelForCausalLM.from_pretrained(
CONFIG["base_model"], revision=CONFIG["base_revision"], trust_remote_code=False,
dtype=torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16,
device_map={"": "cuda:0"}, attn_implementation="sdpa")
if adapter:
from peft import PeftConfig, PeftModel
adapter_config = PeftConfig.from_pretrained(adapter)
if (adapter_config.base_model_name_or_path != CONFIG["base_model"]
or adapter_config.revision != CONFIG["base_revision"]):
raise ValueError("Adapter does not match the pinned chat-and-code model.")
model = PeftModel.from_pretrained(model, adapter)
return tokenizer, model.eval()
def answer(tokenizer, model, prompt: str, history: list[dict] | None = None,
max_new_tokens: int = CONFIG["max_new_tokens"]) -> str:
if not 1 <= max_new_tokens <= 2048:
raise ValueError("Response length must be between 1 and 2,048 tokens.")
messages = conversation(prompt, history)
while True:
inputs = tokenizer.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True,
return_dict=True, return_tensors="pt")
if inputs["input_ids"].shape[1] + max_new_tokens <= 4096:
break
if len(messages) <= 2:
raise ValueError("This message is too long. Please send a smaller code example.")
del messages[1:3]
return generate_messages(tokenizer, model, messages, max_new_tokens=max_new_tokens)
def generate_messages(tokenizer, model, messages, max_new_tokens=768,
context_limit=4096, do_sample=True):
import torch
if not 1 <= max_new_tokens <= 2048 or not 256 <= context_limit <= 8192:
raise ValueError("Invalid response or context length.")
inputs = tokenizer.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True,
return_dict=True, return_tensors="pt")
if inputs["input_ids"].shape[1] + max_new_tokens > context_limit:
raise ValueError("Source packet is too long. Shorten the excerpts; no sources were silently removed.")
inputs = inputs.to(model.device)
sampling = {"temperature": 0.7, "top_p": 0.8, "top_k": 20} if do_sample else {}
with torch.inference_mode():
output = model.generate(
**inputs, max_new_tokens=max_new_tokens, do_sample=do_sample, **sampling,
pad_token_id=tokenizer.pad_token_id, eos_token_id=tokenizer.eos_token_id)
reply = tokenizer.decode(output[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True).strip()
if not reply:
raise RuntimeError("The model returned an empty response. Please try again.")
return reply