Files
SIA/sia/llm_engine/qwq_llm_engine.py
2025-04-07 13:35:20 +02:00

140 lines
4.4 KiB
Python

from pathlib import Path
from threading import Thread
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, TextIteratorStreamer, BitsAndBytesConfig
from typing import Callable, Iterator, Optional, List
import json
import torch
from . import LlmEngine
from .. import util
from .xml_logits_processor import XmlLogitsProcessor
class QwQLlmEngine(LlmEngine):
def __init__(
self,
model_path: Path,
temperature: float,
token_limit: int = None,
xml_schema_text: Optional[str] = None,
):
"""
Initialize the QwQ LLM Engine.
Args:
model_path: Local path to the model
temperature: Sampling temperature
token_limit: Maximum tokens to generate
xml_schema_text: Optional XML schema to validate against
"""
self._temperature = temperature
self._token_limit = token_limit
if xml_schema_text:
self._logits_processor = XmlLogitsProcessor(self._tokenizer, xml_schema_text)
with open('/root/sia/qwq_tokenizer_config.json', 'r') as f:
tokenizer_config = json.load(f)
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True
)
model = AutoModelForCausalLM.from_pretrained(
model_path,
return_dict=True,
device_map="auto",
use_cache=True,
quantization_config=quantization_config,
)
self._tokenizer = AutoTokenizer.from_pretrained(
model_path,
tokenizer_config=tokenizer_config,
)
self._pipline = pipeline(
"text-generation",
model=model,
tokenizer=self._tokenizer,
return_full_text=False,
)
def infer(self, system_prompt: str, main_context: str, should_stop: Callable[[], bool] = lambda: False) -> Iterator[str]:
"""
Run inference using the system prompt and main context.
Args:
system_prompt: The system prompt string
main_context: The main context string after templating
should_stop: Callback that returns True when inference should stop
Returns:
Iterator[str]: An iterator that yields the generated text.
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": main_context}
]
text = self._tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
streamer = TextIteratorStreamer(
self._tokenizer,
skip_prompt=True,
)
generation_kwargs = {
"text_inputs": text,
"do_sample": True,
"temperature": self._temperature,
"max_new_tokens": self._token_limit,
"streamer": streamer,
}
if self._logits_processor:
generation_kwargs["logits_processor"] = self.logits_processor
generation_thread = Thread(
target=self._pipline,
kwargs=generation_kwargs
)
generation_thread.start()
for text in util.stop_before_value(streamer, self._tokenizer.eos_token):
yield text
if should_stop():
break
generation_thread.join()
def token_count(self, system_prompt: str, main_context: str) -> int:
"""
Count tokens for the given system prompt and main context.
Args:
system_prompt: The system prompt string
main_context: The main context string
Returns:
int: Total number of tokens
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": main_context}
]
prompt = self._tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
return len(self._tokenizer.encode(prompt))
def token_limit(self) -> int:
return self._token_limit