Delete app.py
Browse files
app.py
DELETED
|
@@ -1,1448 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import gc
|
| 3 |
-
import json
|
| 4 |
-
import random
|
| 5 |
-
import torch
|
| 6 |
-
import asyncio
|
| 7 |
-
import logging
|
| 8 |
-
import time
|
| 9 |
-
from typing import List, Dict, Any, Optional, Union, AsyncGenerator, Tuple
|
| 10 |
-
from fastapi import FastAPI, HTTPException, Query, Request, Depends, status
|
| 11 |
-
from fastapi.responses import StreamingResponse, PlainTextResponse, HTMLResponse, JSONResponse
|
| 12 |
-
from fastapi.security import APIKeyHeader
|
| 13 |
-
from pydantic import BaseModel, Field, ValidationError, validator
|
| 14 |
-
from transformers import (
|
| 15 |
-
AutoConfig, AutoModelForCausalLM, AutoTokenizer,
|
| 16 |
-
GenerationConfig, LogitsProcessorList,
|
| 17 |
-
MinLengthLogitsProcessor, MaxLengthCriteria,
|
| 18 |
-
StoppingCriteriaList, StoppingCriteria
|
| 19 |
-
)
|
| 20 |
-
import uvicorn
|
| 21 |
-
from concurrent.futures import ThreadPoolExecutor
|
| 22 |
-
import math
|
| 23 |
-
import torch.nn.functional as F
|
| 24 |
-
import copy
|
| 25 |
-
|
| 26 |
-
app = FastAPI(title="Chatbot Profesional API", version="1.0.0")
|
| 27 |
-
|
| 28 |
-
class StopSequenceCriteria(StoppingCriteria):
|
| 29 |
-
def __init__(self, stop_sequences: List[str], tokenizer: AutoTokenizer):
|
| 30 |
-
self.tokenizer = tokenizer
|
| 31 |
-
self.stop_sequences_text = []
|
| 32 |
-
self.stop_sequence_ids = []
|
| 33 |
-
for seq in stop_sequences:
|
| 34 |
-
if seq:
|
| 35 |
-
encoded_ids = tokenizer.encode(seq, add_special_tokens=False)
|
| 36 |
-
decoded_text = tokenizer.decode(encoded_ids, skip_special_tokens=True)
|
| 37 |
-
if decoded_text:
|
| 38 |
-
self.stop_sequences_text.append(decoded_text)
|
| 39 |
-
self.stop_sequence_ids.append(encoded_ids)
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
|
| 43 |
-
if not self.stop_sequence_ids:
|
| 44 |
-
return False
|
| 45 |
-
|
| 46 |
-
input_ids_list = input_ids[0].tolist()
|
| 47 |
-
|
| 48 |
-
for stop_seq_ids in self.stop_sequence_ids:
|
| 49 |
-
stop_len = len(stop_seq_ids)
|
| 50 |
-
if len(input_ids_list) >= stop_len:
|
| 51 |
-
if input_ids_list[-stop_len:] == stop_seq_ids:
|
| 52 |
-
return True
|
| 53 |
-
|
| 54 |
-
check_tail_len = 50
|
| 55 |
-
if self.stop_sequence_ids:
|
| 56 |
-
max_stop_seq_token_len = max((len(seq) for seq in self.stop_sequence_ids), default=0)
|
| 57 |
-
check_tail_len = max(check_tail_len, max_stop_seq_token_len + 10)
|
| 58 |
-
|
| 59 |
-
tail_ids = input_ids_list[-min(check_tail_len, len(input_ids_list)):]
|
| 60 |
-
tail_text = self.tokenizer.decode(tail_ids, skip_special_tokens=True)
|
| 61 |
-
|
| 62 |
-
for stop_seq_text in self.stop_sequences_text:
|
| 63 |
-
if stop_seq_text and stop_seq_text in tail_text:
|
| 64 |
-
return True
|
| 65 |
-
|
| 66 |
-
return False
|
| 67 |
-
|
| 68 |
-
logging.getLogger("uvicorn").handlers.clear()
|
| 69 |
-
logging.getLogger("uvicorn.error").handlers.clear()
|
| 70 |
-
logging.getLogger("uvicorn.access").handlers.clear()
|
| 71 |
-
logging.getLogger("uvicorn").propagate = False
|
| 72 |
-
logging.getLogger("uvicorn.error").propagate = False
|
| 73 |
-
logging.getLogger("uvicorn.access").propagate = False
|
| 74 |
-
logging.getLogger("uvicorn").setLevel(logging.CRITICAL)
|
| 75 |
-
logging.getLogger("uvicorn.error").setLevel(logging.CRITICAL)
|
| 76 |
-
logging.getLogger("uvicorn.access").setLevel(logging.CRITICAL)
|
| 77 |
-
logging.getLogger("fastapi").setLevel(logging.CRITICAL)
|
| 78 |
-
logging.getLogger("transformers").setLevel(logging.CRITICAL)
|
| 79 |
-
logging.getLogger().handlers.clear()
|
| 80 |
-
logging.getLogger().addHandler(logging.NullHandler())
|
| 81 |
-
|
| 82 |
-
DEFAULT_MODEL_NAME = "microsoft/bitnet-b1.58-2B-4T"
|
| 83 |
-
MODEL_NAME = os.environ.get("MODEL_NAME", DEFAULT_MODEL_NAME)
|
| 84 |
-
SYSTEM_PROMPT = os.environ.get("SYSTEM_PROMPT", "Eres un asistente profesional y servicial.")
|
| 85 |
-
|
| 86 |
-
try:
|
| 87 |
-
MAX_CONTEXT_TOKENS = int(os.environ.get("MAX_CONTEXT_TOKENS", 1024))
|
| 88 |
-
if MAX_CONTEXT_TOKENS <= 0:
|
| 89 |
-
raise ValueError("MAX_CONTEXT_TOKENS must be positive.")
|
| 90 |
-
except (ValueError, TypeError) as e:
|
| 91 |
-
logging.error(f"Invalid MAX_CONTEXT_TOKENS environment variable: {os.environ.get('MAX_CONTEXT_TOKENS')}. Using default 1024. Error: {e}")
|
| 92 |
-
MAX_CONTEXT_TOKENS = 1024
|
| 93 |
-
|
| 94 |
-
try:
|
| 95 |
-
MAX_GENERATION_TOKENS = int(os.environ.get("MAX_GENERATION_TOKENS", 512))
|
| 96 |
-
if MAX_GENERATION_TOKENS <= 0:
|
| 97 |
-
raise ValueError("MAX_GENERATION_TOKENS must be positive.")
|
| 98 |
-
except (ValueError, TypeError) as e:
|
| 99 |
-
logging.error(f"Invalid MAX_GENERATION_TOKENS environment variable: {os.environ.get('MAX_GENERATION_TOKENS')}. Using default 512. Error: {e}")
|
| 100 |
-
MAX_GENERATION_TOKENS = 512
|
| 101 |
-
|
| 102 |
-
try:
|
| 103 |
-
MAX_CONCURRENT_GENERATIONS = int(os.environ.get("MAX_CONCURRENT_GENERATIONS", 4))
|
| 104 |
-
if MAX_CONCURRENT_GENERATIONS <= 0:
|
| 105 |
-
raise ValueError("MAX_CONCURRENT_GENERATIONS must be positive.")
|
| 106 |
-
except (ValueError, TypeError) as e:
|
| 107 |
-
logging.error(f"Invalid MAX_CONCURRENT_GENERATIONS environment variable: {os.environ.get('MAX_CONCURRENT_GENERATIONS')}. Using default 4. Error: {e}")
|
| 108 |
-
MAX_CONCURRENT_GENERATIONS = 4
|
| 109 |
-
|
| 110 |
-
TRUST_REMOTE_CODE_ENV = os.environ.get("TRUST_REMOTE_CODE", "false").lower() == "true"
|
| 111 |
-
TRUST_REMOTE_CODE = TRUST_REMOTE_CODE_ENV or (MODEL_NAME == DEFAULT_MODEL_NAME)
|
| 112 |
-
ENABLE_FLASH_ATTENTION_2 = os.environ.get("ENABLE_FLASH_ATTENTION_2", "false").lower() == "true"
|
| 113 |
-
TORCH_DTYPE_STR = os.environ.get("TORCH_DTYPE", "float32")
|
| 114 |
-
TORCH_DTYPE = getattr(torch, TORCH_DTYPE_STR.lower(), torch.float32)
|
| 115 |
-
if TORCH_DTYPE != torch.float32:
|
| 116 |
-
logging.warning(f"Requested dtype {TORCH_DTYPE_STR} might not be fully performant on CPU. Using float32.")
|
| 117 |
-
TORCH_DTYPE = torch.float32
|
| 118 |
-
|
| 119 |
-
API_KEY = os.environ.get("API_KEY")
|
| 120 |
-
|
| 121 |
-
global_model = None
|
| 122 |
-
global_tokenizer = None
|
| 123 |
-
global_tokens: Dict[str, Optional[int]] = {}
|
| 124 |
-
executor = ThreadPoolExecutor(max_workers=MAX_CONCURRENT_GENERATIONS)
|
| 125 |
-
generation_semaphore = asyncio.Semaphore(MAX_CONCURRENT_GENERATIONS)
|
| 126 |
-
|
| 127 |
-
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
|
| 128 |
-
|
| 129 |
-
async def get_api_key(api_key: str = Depends(api_key_header)):
|
| 130 |
-
if API_KEY is None:
|
| 131 |
-
return
|
| 132 |
-
if api_key is None or api_key != API_KEY:
|
| 133 |
-
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or missing API Key")
|
| 134 |
-
return api_key
|
| 135 |
-
|
| 136 |
-
class GenerateRequest(BaseModel):
|
| 137 |
-
input_text: str = Field(..., description="The input text from the user.", examples=["Hola, ¿cómo estás?"])
|
| 138 |
-
history: Optional[List[Dict[str, str]]] = Field(None, description="Conversation history.", examples=[[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the capital of France?"}, {"role": "assistant", "content": "The capital of France is Paris."}]])
|
| 139 |
-
stream: bool = Field(True, description="Whether to stream the response.")
|
| 140 |
-
temperature: float = Field(1.0, ge=0.0, le=2.0, description="Controls the randomness.")
|
| 141 |
-
top_k: int = Field(50, ge=0, description="Top-k filtering.")
|
| 142 |
-
top_p: float = Field(1.0, ge=0.0, le=1.0, description="Top-p (nucleus) filtering.")
|
| 143 |
-
repetition_penalty: float = Field(1.0, ge=0.0, description="Repetition penalty.")
|
| 144 |
-
frequency_penalty: float = Field(0.0, ge=0.0, description="Frequency penalty.")
|
| 145 |
-
presence_penalty: float = Field(0.0, ge=0.0, description="Presence penalty.")
|
| 146 |
-
num_beams: int = Field(1, ge=1, description="Number of beams for beam search.")
|
| 147 |
-
length_penalty: float = Field(1.0, ge=0.0, description="Length penalty.")
|
| 148 |
-
no_repeat_ngram_size: int = Field(0, ge=0, description="No repeat ngram size.")
|
| 149 |
-
early_stopping: bool = Field(False, description="Early stopping for beam search.")
|
| 150 |
-
do_sample: bool = Field(True, description="Whether to use sampling.")
|
| 151 |
-
use_mirostat: bool = Field(False, description="Whether to use Mirostat sampling.")
|
| 152 |
-
mirostat_tau: float = Field(5.0, ge=0.0, description="Mirostat tau.")
|
| 153 |
-
mirostat_eta: float = Field(0.1, ge=0.0, description="Mirostat eta.")
|
| 154 |
-
max_new_tokens: int = Field(MAX_GENERATION_TOKENS, ge=1, description="Max new tokens.")
|
| 155 |
-
system_prompt: Optional[str] = Field(None, description="Override the default system prompt.")
|
| 156 |
-
seed: Optional[int] = Field(None, description="Random seed.")
|
| 157 |
-
stop_sequences: Optional[List[str]] = Field(None, description="List of stop strings.", examples=[[".", "\nUsuario:"]])
|
| 158 |
-
tokenize_only: bool = Field(False, description="If true, only tokenize input.")
|
| 159 |
-
strip_trailing_whitespace: bool = Field(False, description="Strip trailing whitespace.")
|
| 160 |
-
remove_incomplete_sentences: bool = Field(False, description="Remove incomplete last sentence.")
|
| 161 |
-
num_return_sequences: int = Field(1, ge=1, le=5, description="Number of sequences to return (non-streaming).")
|
| 162 |
-
bad_words_ids: Optional[List[List[int]]] = Field(None, description="List of bad word token ids.", examples=[[[32000], [32001]]])
|
| 163 |
-
forced_bos_token_id: Optional[int] = Field(None, description="Forced BOS token id.")
|
| 164 |
-
forced_eos_token_id: Optional[int] = Field(None, description="Forced EOS token id.")
|
| 165 |
-
renormalize_logits: Optional[bool] = Field(None, description="Renormalize logits.")
|
| 166 |
-
suppress_tokens: Optional[List[int]] = Field(None, description="Tokens to suppress.")
|
| 167 |
-
begin_suppress_tokens: Optional[List[int]] = Field(None, description="Tokens to suppress at beginning.")
|
| 168 |
-
end_suppress_tokens: Optional[List[int]] = Field(None, description="Tokens to suppress at end.")
|
| 169 |
-
encoder_no_repeat_ngram_size: int = Field(0, ge=0, description="Encoder no repeat ngram size.")
|
| 170 |
-
min_length: int = Field(0, ge=0, description="Minimum total length.")
|
| 171 |
-
max_length: Optional[int] = Field(None, description="Maximum total length.")
|
| 172 |
-
exponential_decay_length_penalty: Optional[Tuple[float, int, float]] = Field(None, description="Exponential decay length penalty.")
|
| 173 |
-
use_cache: bool = Field(True, description="Use cache.")
|
| 174 |
-
typical_p: float = Field(1.0, ge=0.0, le=1.0, description="Typical P sampling.")
|
| 175 |
-
epsilon_cutoff: float = Field(0.0, ge=0.0, description="Epsilon cutoff for LTS.")
|
| 176 |
-
eta_cutoff: float = Field(0.0, ge=0.0, description="Eta cutoff for LTS.")
|
| 177 |
-
temperature_cutoff: Optional[float] = Field(None, ge=0.0, description="Temperature cutoff.")
|
| 178 |
-
encoder_repetition_penalty: float = Field(1.0, ge=0.0, description="Encoder repetition penalty.")
|
| 179 |
-
max_time: Optional[float] = Field(None, ge=0.0, description="Maximum time in seconds.")
|
| 180 |
-
output_watermark: bool = Field(False, description="Output watermark.")
|
| 181 |
-
remove_input_from_output: bool = Field(False, description="Remove input from output.")
|
| 182 |
-
eos_token_id_override: Optional[int] = Field(None, description="Override EOS token id.")
|
| 183 |
-
pad_token_id_override: Optional[int] = Field(None, description="Override PAD token id.")
|
| 184 |
-
bos_token_id_override: Optional[int] = Field(None, description="Override BOS token id.")
|
| 185 |
-
repetition_penalty_range: Optional[int] = Field(None, ge=0, description="Repetition penalty range.")
|
| 186 |
-
diversity_penalty: float = Field(0.0, ge=0.0, description="Diversity penalty for diverse beam search.")
|
| 187 |
-
num_beam_groups: int = Field(1, ge=1, description="Number of beam groups for diverse beam search.")
|
| 188 |
-
return_dict_in_generate: bool = Field(False, description="Return dictionary from generate.")
|
| 189 |
-
output_attentions: bool = Field(False, description="Output attentions.")
|
| 190 |
-
output_hidden_states: bool = Field(False, description="Output hidden states.")
|
| 191 |
-
output_scores: bool = Field(False, description="Output scores.")
|
| 192 |
-
return_token_logprobs: bool = Field(False, description="Return token logprobs in stream.")
|
| 193 |
-
return_text_from_sequence: bool = Field(True, description="Decode generated sequence to text.")
|
| 194 |
-
length_normalization_factor: Optional[float] = Field(None, description="Length normalization factor for beam search.")
|
| 195 |
-
min_new_tokens: int = Field(0, ge=0, description="Minimum number of new tokens.")
|
| 196 |
-
do_normalize_logits: bool = Field(False, description="Normalize logits.")
|
| 197 |
-
return_generation_inputs: bool = Field(False, description="Return generation inputs.")
|
| 198 |
-
return_unused_generate_parameters: bool = Field(False, description="Return unused generate parameters.")
|
| 199 |
-
use_fast_tokenizer: bool = Field(True, description="Use fast tokenizer if available.")
|
| 200 |
-
model_kwargs: Optional[Dict[str, Any]] = Field(None, description="Additional model kwargs for generate.")
|
| 201 |
-
tokenizer_kwargs: Optional[Dict[str, Any]] = Field(None, description="Additional tokenizer kwargs for encode.")
|
| 202 |
-
return_only_text: bool = Field(False, description="If true, only return the generated text.")
|
| 203 |
-
|
| 204 |
-
@validator('stop_sequences')
|
| 205 |
-
def validate_stop_sequences(cls, v):
|
| 206 |
-
if v is not None:
|
| 207 |
-
if not all(isinstance(seq, str) for seq in v):
|
| 208 |
-
raise ValueError('Each stop sequence must be a string')
|
| 209 |
-
return v
|
| 210 |
-
|
| 211 |
-
@validator('bad_words_ids')
|
| 212 |
-
def validate_bad_words_ids(cls, v):
|
| 213 |
-
if v is not None:
|
| 214 |
-
if not all(isinstance(word_id_list, list) and all(isinstance(token_id, int) for token_id in word_id_list) for word_id_list in v):
|
| 215 |
-
raise ValueError('bad_words_ids must be a list of lists of integers')
|
| 216 |
-
return v
|
| 217 |
-
|
| 218 |
-
@validator('exponential_decay_length_penalty')
|
| 219 |
-
def validate_exponential_decay_length_penalty(cls, v):
|
| 220 |
-
if v is not None:
|
| 221 |
-
if not (isinstance(v, (list, tuple)) and len(v) == 3 and
|
| 222 |
-
isinstance(v[0], (int, float)) and v[0] > 0 and
|
| 223 |
-
isinstance(v[1], int) and v[1] >= 0 and
|
| 224 |
-
isinstance(v[2], (int, float))):
|
| 225 |
-
raise ValueError('exponential_decay_length_penalty must be a tuple/list of 3 numbers (decay_factor, start_index, threshold)')
|
| 226 |
-
return v
|
| 227 |
-
|
| 228 |
-
class TokenizeRequest(BaseModel):
|
| 229 |
-
text: Union[str, List[str]] = Field(..., description="Text or list of texts to tokenize.")
|
| 230 |
-
add_special_tokens: bool = Field(True, description="Whether to add special tokens.")
|
| 231 |
-
is_split_into_words: bool = Field(False, description="Whether the input text is pre-tokenized.")
|
| 232 |
-
return_token_type_ids: bool = Field(False, description="Whether to return token type IDs.")
|
| 233 |
-
padding: Union[bool, str] = Field(False, description="Enable padding.")
|
| 234 |
-
truncation: Union[bool, str] = Field(False, description="Enable truncation.")
|
| 235 |
-
max_length: Optional[int] = Field(None, ge=1, description="Maximum length for padding and truncation.")
|
| 236 |
-
return_tensors: Optional[str] = Field(None, description="The type of tensors to return.")
|
| 237 |
-
return_attention_mask: Optional[bool] = Field(None, description="Whether to return the attention mask.")
|
| 238 |
-
return_offsets_mapping: Optional[bool] = Field(None, description="Whether to return offsets mapping.")
|
| 239 |
-
return_length: Optional[bool] = Field(None, description="Whether to return the length.")
|
| 240 |
-
verbose: bool = Field(False, description="Verbose tokenizer output.")
|
| 241 |
-
tokenizer_kwargs: Optional[Dict[str, Any]] = Field(None, description="Additional tokenizer kwargs.")
|
| 242 |
-
|
| 243 |
-
class DecodeRequest(BaseModel):
|
| 244 |
-
token_ids: List[int] = Field(..., description="List of token IDs to decode.", examples=[[1, 2, 3]])
|
| 245 |
-
skip_special_tokens: bool = Field(True, description="Skip special tokens.")
|
| 246 |
-
clean_up_tokenization_spaces: bool = Field(True, description="Clean up spaces.")
|
| 247 |
-
decode_kwargs: Optional[Dict[str, Any]] = Field(None, description="Additional decode kwargs.")
|
| 248 |
-
|
| 249 |
-
class SystemPromptUpdateRequest(BaseModel):
|
| 250 |
-
system_prompt: str = Field(..., description="The new global system prompt.")
|
| 251 |
-
|
| 252 |
-
class ModelReloadRequest(BaseModel):
|
| 253 |
-
model_name: Optional[str] = Field(None, description="New model name.")
|
| 254 |
-
trust_remote_code: Optional[bool] = Field(None, description="Override trust_remote_code.")
|
| 255 |
-
enable_flash_attention_2: Optional[bool] = Field(None, description="Override enable_flash_attention_2.")
|
| 256 |
-
torch_dtype: Optional[str] = Field(None, description="Override torch_dtype.")
|
| 257 |
-
model_kwargs: Optional[Dict[str, Any]] = Field(None, description="Additional model kwargs for from_pretrained.")
|
| 258 |
-
tokenizer_kwargs: Optional[Dict[str, Any]] = Field(None, description="Additional tokenizer kwargs for from_pretrained.")
|
| 259 |
-
|
| 260 |
-
def format_conversation(input_text: str, history: Optional[List[Dict[str, str]]], system_prompt: Optional[str]) -> str:
|
| 261 |
-
full_history: List[Dict[str, str]] = []
|
| 262 |
-
used_system_prompt = system_prompt if system_prompt is not None else SYSTEM_PROMPT
|
| 263 |
-
if not history or history[0].get("role") != "system" or history[0].get("content") != used_system_prompt:
|
| 264 |
-
full_history.append({"role": "system", "content": used_system_prompt})
|
| 265 |
-
if history:
|
| 266 |
-
full_history.extend(history)
|
| 267 |
-
if not full_history or full_history[-1].get("role") != "user" or full_history[-1].get("content") != input_text:
|
| 268 |
-
full_history.append({"role": "user", "content": input_text})
|
| 269 |
-
|
| 270 |
-
if global_tokenizer and hasattr(global_tokenizer, 'apply_chat_template') and global_tokenizer.chat_template:
|
| 271 |
-
try:
|
| 272 |
-
return global_tokenizer.apply_chat_template(full_history, tokenize=False, add_generation_prompt=True)
|
| 273 |
-
except Exception as e:
|
| 274 |
-
logging.error(f"Failed to apply chat template: {e}. Falling back to manual formatting.")
|
| 275 |
-
pass
|
| 276 |
-
formatted_text = ""
|
| 277 |
-
for i, message in enumerate(full_history):
|
| 278 |
-
if i == 0 and message["role"] == "system" and len(full_history) > 1 and full_history[1].get("role") == "system":
|
| 279 |
-
continue
|
| 280 |
-
if message["role"] == "system":
|
| 281 |
-
formatted_text += f"{message['content'].strip()}\n\n"
|
| 282 |
-
elif message["role"] == "user":
|
| 283 |
-
formatted_text += f"Usuario: {message['content'].strip()}\n"
|
| 284 |
-
elif message["role"] == "assistant":
|
| 285 |
-
formatted_text += f"Bot: {message['content'].strip()}\n"
|
| 286 |
-
if not formatted_text.endswith("Bot:"):
|
| 287 |
-
formatted_text += "Bot:"
|
| 288 |
-
return formatted_text.strip()
|
| 289 |
-
|
| 290 |
-
def truncate_encoded_ids(input_ids: torch.Tensor, max_length: int) -> torch.Tensor:
|
| 291 |
-
if input_ids.shape[-1] > max_length:
|
| 292 |
-
return input_ids[:, -max_length:]
|
| 293 |
-
return input_ids
|
| 294 |
-
|
| 295 |
-
def apply_seed(seed: Optional[int]):
|
| 296 |
-
if seed is not None:
|
| 297 |
-
torch.manual_seed(seed)
|
| 298 |
-
random.seed(seed)
|
| 299 |
-
if torch.cuda.is_available():
|
| 300 |
-
torch.cuda.manual_seed_all(seed)
|
| 301 |
-
|
| 302 |
-
def get_stopping_criteria(req: GenerateRequest, initial_ids: torch.Tensor, tokenizer: AutoTokenizer) -> StoppingCriteriaList:
|
| 303 |
-
criteria = StoppingCriteriaList()
|
| 304 |
-
max_len_from_req = None
|
| 305 |
-
if req.max_length is not None and req.max_length > 0:
|
| 306 |
-
max_len_from_req = req.max_length
|
| 307 |
-
elif req.max_new_tokens is not None and req.max_new_tokens > 0:
|
| 308 |
-
max_len_from_req = initial_ids.shape[-1] + req.max_new_tokens
|
| 309 |
-
else:
|
| 310 |
-
max_len_from_req = initial_ids.shape[-1] + MAX_GENERATION_TOKENS
|
| 311 |
-
if max_len_from_req is not None and max_len_from_req > 0:
|
| 312 |
-
criteria.append(MaxLengthCriteria(max_len_from_req))
|
| 313 |
-
if req.min_length is not None and req.min_length > 0:
|
| 314 |
-
eos_token_id = req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id", -1)
|
| 315 |
-
criteria.append(MinLengthLogitsProcessor(initial_ids.shape[-1] + req.min_length, eos_token_id))
|
| 316 |
-
if req.stop_sequences:
|
| 317 |
-
criteria.append(StopSequenceCriteria(req.stop_sequences, tokenizer))
|
| 318 |
-
return criteria
|
| 319 |
-
|
| 320 |
-
def generate_next_token_sync(
|
| 321 |
-
input_ids,
|
| 322 |
-
past_key_values,
|
| 323 |
-
gen_cfg: GenerationConfig,
|
| 324 |
-
device: str
|
| 325 |
-
) -> Tuple[torch.Tensor, Any, Optional[float], Optional[torch.Tensor], Any, Any]:
|
| 326 |
-
with torch.no_grad():
|
| 327 |
-
outputs = global_model(
|
| 328 |
-
input_ids, past_key_values=past_key_values,
|
| 329 |
-
use_cache=gen_cfg.use_cache, return_dict=True,
|
| 330 |
-
output_attentions=gen_cfg.output_attentions,
|
| 331 |
-
output_hidden_states=gen_cfg.output_hidden_states,
|
| 332 |
-
output_scores=gen_cfg.output_scores,
|
| 333 |
-
)
|
| 334 |
-
logits = outputs.logits[:, -1, :]
|
| 335 |
-
past = outputs.past_key_values
|
| 336 |
-
scores = outputs.scores if gen_cfg.output_scores else None
|
| 337 |
-
attentions = outputs.attentions if gen_cfg.output_attentions else None
|
| 338 |
-
hidden_states = outputs.hidden_states if gen_cfg.output_hidden_states else None
|
| 339 |
-
step_logits_for_criteria = logits.clone()
|
| 340 |
-
if gen_cfg.do_normalize_logits:
|
| 341 |
-
logits = F.log_softmax(logits, dim=-1)
|
| 342 |
-
if gen_cfg.do_sample:
|
| 343 |
-
if gen_cfg.use_mirostat_mode == 1 and hasattr(global_model, 'mirostat_sample_logits'):
|
| 344 |
-
token = global_model.mirostat_sample_logits(
|
| 345 |
-
logits=logits,
|
| 346 |
-
temperature=gen_cfg.temperature,
|
| 347 |
-
mirostat_tau=gen_cfg.mirostat_tau,
|
| 348 |
-
mirostat_eta=gen_cfg.mirostat_eta
|
| 349 |
-
).unsqueeze(0).to(device)
|
| 350 |
-
else:
|
| 351 |
-
logits = logits / gen_cfg.temperature
|
| 352 |
-
if gen_cfg.temperature_cutoff is not None and gen_cfg.temperature_cutoff > 0:
|
| 353 |
-
logits = torch.where(logits < gen_cfg.temperature_cutoff, torch.tensor(-float('Inf')).to(logits.device), logits)
|
| 354 |
-
if gen_cfg.top_k:
|
| 355 |
-
topk_values, topk_indices = torch.topk(logits, gen_cfg.top_k)
|
| 356 |
-
logits[logits < topk_values[:, -1]] = -float('Inf')
|
| 357 |
-
if gen_cfg.top_p < 1.0:
|
| 358 |
-
sorted_logits, sorted_indices = torch.sort(logits, dim=-1, descending=True)
|
| 359 |
-
cumulative_probs = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1)
|
| 360 |
-
sorted_indices_to_remove = cumulative_probs > gen_cfg.top_p
|
| 361 |
-
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
| 362 |
-
sorted_indices_to_remove[..., 0] = False
|
| 363 |
-
indices_to_remove = sorted_indices[sorted_indices_to_remove]
|
| 364 |
-
logits[:, indices_to_remove] = -float('Inf')
|
| 365 |
-
if gen_cfg.typical_p < 1.0:
|
| 366 |
-
probs = torch.softmax(logits, dim=-1)
|
| 367 |
-
entropy = torch.distributions.Categorical(probs).entropy()
|
| 368 |
-
probs_sorted, indices_sorted = torch.sort(probs, dim=-1, descending=True)
|
| 369 |
-
cumsum_probs_sorted = torch.cumsum(probs_sorted, dim=-1)
|
| 370 |
-
mask = cumsum_probs_sorted < gen_cfg.typical_p * entropy.exp()
|
| 371 |
-
indices_to_remove = indices_sorted[~mask]
|
| 372 |
-
logits[:, indices_to_remove] = -float('Inf')
|
| 373 |
-
if gen_cfg.epsilon_cutoff is not None and gen_cfg.epsilon_cutoff > 0:
|
| 374 |
-
probs = torch.softmax(logits, dim=-1)
|
| 375 |
-
mask = probs < gen_cfg.epsilon_cutoff
|
| 376 |
-
logits[:, mask] = -float('Inf')
|
| 377 |
-
if gen_cfg.eta_cutoff is not None and gen_cfg.eta_cutoff > 0:
|
| 378 |
-
probs = torch.softmax(logits, dim=-1)
|
| 379 |
-
mask = probs > gen_cfg.eta_cutoff
|
| 380 |
-
logits[:, ~mask] = -float('Inf')
|
| 381 |
-
probs = torch.softmax(logits, dim=-1)
|
| 382 |
-
token = torch.multinomial(probs, 1)
|
| 383 |
-
else:
|
| 384 |
-
token = torch.argmax(logits, dim=-1, keepdim=True)
|
| 385 |
-
token_logprob = None
|
| 386 |
-
if gen_cfg.output_scores:
|
| 387 |
-
log_probs = F.log_softmax(step_logits_for_criteria, dim=-1)
|
| 388 |
-
if 0 <= token.squeeze().item() < log_probs.shape[-1]:
|
| 389 |
-
token_logprob = float(log_probs[:, token.squeeze()].item())
|
| 390 |
-
else:
|
| 391 |
-
token_logprob = None
|
| 392 |
-
return token, past, token_logprob, step_logits_for_criteria, attentions, hidden_states
|
| 393 |
-
|
| 394 |
-
def post_process_text(text: str, strip_trailing_whitespace: bool, remove_incomplete_sentences: bool) -> str:
|
| 395 |
-
if strip_trailing_whitespace:
|
| 396 |
-
text = text.rstrip()
|
| 397 |
-
if remove_incomplete_sentences:
|
| 398 |
-
for terminator in ['.', '!', '?', '\n']:
|
| 399 |
-
last_terminator = text.rfind(terminator)
|
| 400 |
-
if last_terminator != -1:
|
| 401 |
-
text = text[:last_terminator + 1]
|
| 402 |
-
break
|
| 403 |
-
return text
|
| 404 |
-
|
| 405 |
-
async def stream_generation_logic(req: GenerateRequest, initial_ids: torch.Tensor, gen_cfg: GenerationConfig, device: str) -> AsyncGenerator[Union[str, Tuple[Dict[str, Any], str]], None]:
|
| 406 |
-
past = None
|
| 407 |
-
generated_tokens_count = 0
|
| 408 |
-
eos_token_id = req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id")
|
| 409 |
-
pad_token_id = req.pad_token_id_override if req.pad_token_id_override is not None else global_tokens.get("pad_token_id", eos_token_id)
|
| 410 |
-
stop_token_ids = {eos_token_id} if eos_token_id is not None else set()
|
| 411 |
-
if pad_token_id is not None and pad_token_id != eos_token_id:
|
| 412 |
-
stop_token_ids.add(pad_token_id)
|
| 413 |
-
|
| 414 |
-
current_ids = initial_ids
|
| 415 |
-
start_time = time.time()
|
| 416 |
-
total_ids_list = initial_ids.tolist()[0]
|
| 417 |
-
finish_reason = "unknown"
|
| 418 |
-
|
| 419 |
-
stopping_criteria = get_stopping_criteria(req, initial_ids, global_tokenizer)
|
| 420 |
-
|
| 421 |
-
last_step_logits = None
|
| 422 |
-
accumulated_text_for_processing = ""
|
| 423 |
-
|
| 424 |
-
try:
|
| 425 |
-
while True:
|
| 426 |
-
if generated_tokens_count >= req.max_new_tokens:
|
| 427 |
-
finish_reason = "max_new_tokens"
|
| 428 |
-
break
|
| 429 |
-
if req.max_time is not None and (time.time() - start_time) > req.max_time:
|
| 430 |
-
finish_reason = "time"
|
| 431 |
-
break
|
| 432 |
-
|
| 433 |
-
input_ids_sync = current_ids if past is None else token
|
| 434 |
-
|
| 435 |
-
token, past, token_logprob, step_logits, attentions, hidden_states = await asyncio.to_thread(
|
| 436 |
-
generate_next_token_sync,
|
| 437 |
-
input_ids_sync,
|
| 438 |
-
past,
|
| 439 |
-
gen_cfg,
|
| 440 |
-
device
|
| 441 |
-
)
|
| 442 |
-
last_step_logits = step_logits
|
| 443 |
-
|
| 444 |
-
generated_token_id = token[0].item()
|
| 445 |
-
total_ids_list.append(generated_token_id)
|
| 446 |
-
|
| 447 |
-
text = global_tokenizer.decode([generated_token_id], skip_special_tokens=True)
|
| 448 |
-
accumulated_text_for_processing += text
|
| 449 |
-
|
| 450 |
-
if req.return_only_text:
|
| 451 |
-
yield text
|
| 452 |
-
else:
|
| 453 |
-
chunk_payload: Dict[str, Any] = {
|
| 454 |
-
"type": "token",
|
| 455 |
-
"text": text,
|
| 456 |
-
"token_id": generated_token_id,
|
| 457 |
-
"generated_tokens_count": generated_tokens_count + 1,
|
| 458 |
-
}
|
| 459 |
-
if req.return_token_logprobs and token_logprob is not None:
|
| 460 |
-
chunk_payload["logprob"] = token_logprob
|
| 461 |
-
|
| 462 |
-
yield json.dumps(chunk_payload) + "\n"
|
| 463 |
-
|
| 464 |
-
if generated_token_id in stop_token_ids:
|
| 465 |
-
finish_reason = "eos_token"
|
| 466 |
-
break
|
| 467 |
-
|
| 468 |
-
current_full_ids_tensor = torch.tensor([total_ids_list], device=device)
|
| 469 |
-
if stopping_criteria(current_full_ids_tensor, step_logits):
|
| 470 |
-
finish_reason = "stopping_criteria"
|
| 471 |
-
current_len = len(total_ids_list)
|
| 472 |
-
initial_len = initial_ids.shape[-1]
|
| 473 |
-
|
| 474 |
-
max_len_crit_met = any(isinstance(c, MaxLengthCriteria) for c in stopping_criteria) and \
|
| 475 |
-
( (req.max_new_tokens is not None and current_len >= (initial_len + req.max_new_tokens)) or
|
| 476 |
-
(req.max_length is not None and current_len >= req.max_length) )
|
| 477 |
-
stop_seq_crit_met = any(isinstance(c, StopSequenceCriteria) for c in stopping_criteria) and req.stop_sequences and \
|
| 478 |
-
any(seq in global_tokenizer.decode(total_ids_list[initial_len:], skip_special_tokens=True) for seq in req.stop_sequences)
|
| 479 |
-
|
| 480 |
-
if max_len_crit_met:
|
| 481 |
-
if req.max_new_tokens is not None and current_len >= (initial_len + req.max_new_tokens):
|
| 482 |
-
finish_reason = "max_new_tokens"
|
| 483 |
-
elif req.max_length is not None and current_len >= req.max_length:
|
| 484 |
-
finish_reason = "max_length"
|
| 485 |
-
|
| 486 |
-
if stop_seq_crit_met:
|
| 487 |
-
finish_reason = "stop_sequence"
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
break
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
current_ids = token
|
| 494 |
-
generated_tokens_count += 1
|
| 495 |
-
|
| 496 |
-
final_text_raw = global_tokenizer.decode(total_ids_list[initial_ids.shape[-1]:], skip_special_tokens=True)
|
| 497 |
-
if req.stop_sequences and finish_reason == "stop_sequence":
|
| 498 |
-
for stop_seq in req.stop_sequences:
|
| 499 |
-
if stop_seq and stop_seq in final_text_raw:
|
| 500 |
-
final_text_raw = final_text_raw.split(stop_seq, 1)[0]
|
| 501 |
-
break
|
| 502 |
-
|
| 503 |
-
final_text_processed = post_process_text(final_text_raw, req.strip_trailing_whitespace, req.remove_incomplete_sentences)
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
if not req.return_only_text:
|
| 507 |
-
final_payload: Dict[str, Any] = {
|
| 508 |
-
"type": "done",
|
| 509 |
-
"total_prompt_tokens": initial_ids.shape[-1],
|
| 510 |
-
"total_generated_tokens": generated_tokens_count,
|
| 511 |
-
"total_sequence_tokens": len(total_ids_list),
|
| 512 |
-
"final_text": final_text_processed,
|
| 513 |
-
"finish_reason": finish_reason
|
| 514 |
-
}
|
| 515 |
-
yield json.dumps(final_payload) + "\n"
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
except Exception as e:
|
| 519 |
-
logging.exception("Streaming generation error:")
|
| 520 |
-
if req.return_only_text:
|
| 521 |
-
yield f"Error: {e}\n"
|
| 522 |
-
else:
|
| 523 |
-
error_payload = {"type": "error", "message": str(e)}
|
| 524 |
-
yield json.dumps(error_payload) + "\n"
|
| 525 |
-
|
| 526 |
-
finally:
|
| 527 |
-
await cleanup(device)
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
async def non_stream_generation_logic(req: GenerateRequest, initial_ids: torch.Tensor, gen_cfg: GenerationConfig, device: str) -> Dict[str, Any]:
|
| 531 |
-
try:
|
| 532 |
-
logits_processor_list = LogitsProcessorList()
|
| 533 |
-
|
| 534 |
-
stopping_criteria_list = get_stopping_criteria(req, initial_ids, global_tokenizer)
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
with torch.no_grad():
|
| 538 |
-
out = global_model.generate(
|
| 539 |
-
input_ids=initial_ids,
|
| 540 |
-
generation_config=gen_cfg,
|
| 541 |
-
return_dict_in_generate=True,
|
| 542 |
-
output_scores=req.output_scores,
|
| 543 |
-
output_attentions=req.output_attentions,
|
| 544 |
-
output_hidden_states=req.output_hidden_states,
|
| 545 |
-
num_return_sequences=req.num_return_sequences,
|
| 546 |
-
bad_words_ids=req.bad_words_ids,
|
| 547 |
-
suppress_tokens=req.suppress_tokens,
|
| 548 |
-
begin_suppress_tokens=req.begin_suppress_tokens,
|
| 549 |
-
end_suppress_tokens=req.end_suppress_tokens,
|
| 550 |
-
logits_processor=logits_processor_list if logits_processor_list else None,
|
| 551 |
-
stopping_criteria=stopping_criteria_list if stopping_criteria_list else None,
|
| 552 |
-
)
|
| 553 |
-
|
| 554 |
-
generated_data = []
|
| 555 |
-
for i in range(req.num_return_sequences):
|
| 556 |
-
if i >= len(out.sequences):
|
| 557 |
-
break
|
| 558 |
-
|
| 559 |
-
sequence = out.sequences[i]
|
| 560 |
-
start_index = initial_ids.shape[-1]
|
| 561 |
-
generated_ids_tensor = sequence[start_index:]
|
| 562 |
-
full_sequence_ids = sequence.tolist()
|
| 563 |
-
|
| 564 |
-
text = global_tokenizer.decode(generated_ids_tensor, skip_special_tokens=True)
|
| 565 |
-
|
| 566 |
-
if req.stop_sequences:
|
| 567 |
-
for stop_seq in req.stop_sequences:
|
| 568 |
-
if stop_seq and stop_seq in text:
|
| 569 |
-
text = text.split(stop_seq, 1)[0]
|
| 570 |
-
break
|
| 571 |
-
|
| 572 |
-
text = post_process_text(text, req.strip_trailing_whitespace, req.remove_incomplete_sentences)
|
| 573 |
-
|
| 574 |
-
finish_reason = "length"
|
| 575 |
-
eos_token_id = req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id")
|
| 576 |
-
if len(generated_ids_tensor) > 0 and eos_token_id is not None and generated_ids_tensor[-1] == eos_token_id:
|
| 577 |
-
finish_reason = "eos_token"
|
| 578 |
-
elif len(generated_ids_tensor) >= gen_cfg.max_new_tokens:
|
| 579 |
-
finish_reason = "max_new_tokens"
|
| 580 |
-
elif req.max_length is not None and len(full_sequence_ids) >= req.max_length:
|
| 581 |
-
finish_reason = "max_length"
|
| 582 |
-
elif hasattr(out, 'max_time_exceeded') and out.max_time_exceeded:
|
| 583 |
-
finish_reason = "time"
|
| 584 |
-
|
| 585 |
-
if req.stop_sequences and finish_reason == "length":
|
| 586 |
-
decoded_full_output = global_tokenizer.decode(full_sequence_ids, skip_special_tokens=True)
|
| 587 |
-
if any(seq in decoded_full_output for seq in req.stop_sequences):
|
| 588 |
-
finish_reason = "stop_sequence"
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
item_data: Dict[str, Any] = {
|
| 592 |
-
"text": text if req.return_text_from_sequence else None,
|
| 593 |
-
"token_ids": generated_ids_tensor.tolist(),
|
| 594 |
-
"generated_tokens_count": len(generated_ids_tensor),
|
| 595 |
-
"finish_reason": finish_reason
|
| 596 |
-
}
|
| 597 |
-
if not req.remove_input_from_output:
|
| 598 |
-
item_data["full_sequence_token_ids"] = full_sequence_ids
|
| 599 |
-
|
| 600 |
-
if req.output_scores and hasattr(out, 'scores') and out.scores is not None:
|
| 601 |
-
item_data["scores"] = "Scores output needs custom handling (complex structure)."
|
| 602 |
-
|
| 603 |
-
if req.return_token_logprobs:
|
| 604 |
-
item_data["token_logprobs"] = "Token logprobs require parsing scores output which is complex for batched/beamed generation."
|
| 605 |
-
|
| 606 |
-
if req.output_attentions and hasattr(out, 'attentions') and out.attentions is not None:
|
| 607 |
-
item_data["attentions"] = "Attentions output needs custom handling (too large)."
|
| 608 |
-
if req.output_hidden_states and hasattr(out, 'hidden_states') and out.hidden_states is not None:
|
| 609 |
-
item_data["hidden_states"] = "Hidden states output needs custom handling (too large)."
|
| 610 |
-
if req.output_watermark and hasattr(out, 'watermark') and out.watermark is not None:
|
| 611 |
-
item_data["watermark"] = out.watermark[i] if isinstance(out.watermark, list) and len(out.watermark) > i else out.watermark
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
generated_data.append(item_data)
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
response_payload: Dict[str, Any] = {
|
| 618 |
-
"prompt_tokens": initial_ids.shape[-1],
|
| 619 |
-
"generated_sequences": generated_data,
|
| 620 |
-
}
|
| 621 |
-
if req.num_return_sequences == 1 and generated_data:
|
| 622 |
-
response_payload["total_tokens"] = response_payload["prompt_tokens"] + generated_data[0]["generated_tokens_count"]
|
| 623 |
-
|
| 624 |
-
if req.return_dict_in_generate:
|
| 625 |
-
raw_out_dict = {}
|
| 626 |
-
for key in out.keys():
|
| 627 |
-
if key not in ['sequences', 'scores', 'attentions', 'hidden_states', 'past_key_values', 'watermark', 'sequences_scores']:
|
| 628 |
-
value = out[key]
|
| 629 |
-
if isinstance(value, torch.Tensor):
|
| 630 |
-
raw_out_dict[key] = value.tolist()
|
| 631 |
-
else:
|
| 632 |
-
raw_out_dict[key] = value
|
| 633 |
-
|
| 634 |
-
response_payload["raw_generate_output"] = raw_out_dict
|
| 635 |
-
|
| 636 |
-
return response_payload
|
| 637 |
-
|
| 638 |
-
except Exception as e:
|
| 639 |
-
logging.exception("Non-streaming generation error:")
|
| 640 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Generation error: {e}")
|
| 641 |
-
|
| 642 |
-
async def cleanup(device: str):
|
| 643 |
-
if device == "cuda" and torch.cuda.is_available():
|
| 644 |
-
torch.cuda.empty_cache()
|
| 645 |
-
gc.collect()
|
| 646 |
-
|
| 647 |
-
@app.get("/resource_status", summary="Get resource usage status (e.g., GPU memory)", dependencies=[Depends(get_api_key)])
|
| 648 |
-
async def resource_status():
|
| 649 |
-
status_data = {}
|
| 650 |
-
status_data["cuda_available"] = torch.cuda.is_available()
|
| 651 |
-
if torch.cuda.is_available():
|
| 652 |
-
device_count = torch.cuda.device_count()
|
| 653 |
-
status_data["device_count"] = device_count
|
| 654 |
-
status_data["devices"] = []
|
| 655 |
-
for i in range(device_count):
|
| 656 |
-
try:
|
| 657 |
-
device_status = {
|
| 658 |
-
"id": i,
|
| 659 |
-
"name": torch.cuda.get_device_name(i),
|
| 660 |
-
"total_memory_mib": round(torch.cuda.get_device_properties(i).total_memory / (1024 * 1024), 2),
|
| 661 |
-
"allocated_memory_mib": round(torch.cuda.memory_allocated(i) / (1024 * 1024), 2),
|
| 662 |
-
"cached_memory_mib": round(torch.cuda.memory_reserved(i) / (1024 * 1024), 2),
|
| 663 |
-
}
|
| 664 |
-
status_data["devices"].append(device_status)
|
| 665 |
-
except Exception as e:
|
| 666 |
-
logging.error(f"Error getting GPU memory info for device {i}: {e}")
|
| 667 |
-
status_data["devices"].append({"id": i, "error": str(e)})
|
| 668 |
-
else:
|
| 669 |
-
status_data["message"] = "CUDA not available. GPU resource info is not applicable."
|
| 670 |
-
|
| 671 |
-
status_data["cpu_cores"] = os.cpu_count()
|
| 672 |
-
status_data["thread_settings"] = {
|
| 673 |
-
"OMP_NUM_THREADS": os.environ.get("OMP_NUM_THREADS"),
|
| 674 |
-
"MKL_NUM_THREADS": os.environ.get("MKL_NUM_THREADS"),
|
| 675 |
-
"PYTORCH_BEFORE_FORK_THREAD_CONTROLS": os.environ.get("PYTORCH_BEFORE_FORK_THREAD_CONTROLS"),
|
| 676 |
-
"torch.get_num_threads()": torch.get_num_threads(),
|
| 677 |
-
"torch.get_num_interop_threads()": torch.get_num_interop_threads(),
|
| 678 |
-
}
|
| 679 |
-
|
| 680 |
-
status_data["max_concurrent_generations"] = MAX_CONCURRENT_GENERATIONS
|
| 681 |
-
status_data["currently_running_generations"] = MAX_CONCURRENT_GENERATIONS - generation_semaphore._value
|
| 682 |
-
status_data["available_slots"] = generation_semaphore._value
|
| 683 |
-
|
| 684 |
-
return JSONResponse(status_data)
|
| 685 |
-
|
| 686 |
-
@app.on_event("startup")
|
| 687 |
-
async def load_model():
|
| 688 |
-
global global_model, global_tokenizer, global_tokens, MODEL_NAME, TRUST_REMOTE_CODE, ENABLE_FLASH_ATTENTION_2, TORCH_DTYPE, TORCH_DTYPE_STR, TRUST_REMOTE_CODE_ENV
|
| 689 |
-
|
| 690 |
-
torch.set_num_threads(max(1, os.cpu_count() // 2))
|
| 691 |
-
torch.set_num_interop_threads(max(1, os.cpu_count() // 4))
|
| 692 |
-
|
| 693 |
-
torch.backends.cuda.preferred_linalg_backend = "fused" if torch.backends.cuda.is_built() else None
|
| 694 |
-
torch.backends.cudnn.benchmark = True if torch.cuda.is_available() else False
|
| 695 |
-
|
| 696 |
-
try:
|
| 697 |
-
TORCH_DTYPE = getattr(torch, TORCH_DTYPE_STR.lower(), torch.float32)
|
| 698 |
-
if TORCH_DTYPE != torch.float32:
|
| 699 |
-
logging.warning(f"Requested dtype {TORCH_DTYPE_STR} might not be fully performant on CPU. Using float32.")
|
| 700 |
-
TORCH_DTYPE = torch.float32
|
| 701 |
-
except AttributeError:
|
| 702 |
-
logging.warning(f"Invalid TORCH_DTYPE specified: {TORCH_DTYPE_STR}. Falling back to float32.")
|
| 703 |
-
TORCH_DTYPE = torch.float32
|
| 704 |
-
|
| 705 |
-
current_model_name = MODEL_NAME
|
| 706 |
-
current_trust_remote_code = TRUST_REMOTE_CODE_ENV or (current_model_name == DEFAULT_MODEL_NAME)
|
| 707 |
-
device = "cpu"
|
| 708 |
-
|
| 709 |
-
try:
|
| 710 |
-
logging.info(f"Loading config for model: {current_model_name}")
|
| 711 |
-
config = AutoConfig.from_pretrained(current_model_name, trust_remote_code=current_trust_remote_code)
|
| 712 |
-
logging.info(f"Original config: {config}")
|
| 713 |
-
|
| 714 |
-
logging.info(f"Modifying config to load only 1 hidden layer.")
|
| 715 |
-
if hasattr(config, 'num_hidden_layers'):
|
| 716 |
-
original_num_layers = config.num_hidden_layers
|
| 717 |
-
config.num_hidden_layers = 1
|
| 718 |
-
logging.info(f"Changed num_hidden_layers from {original_num_layers} to 1.")
|
| 719 |
-
elif hasattr(config, 'num_layers'):
|
| 720 |
-
original_num_layers = config.num_layers
|
| 721 |
-
config.num_layers = 1
|
| 722 |
-
logging.info(f"Changed num_layers from {original_num_layers} to 1.")
|
| 723 |
-
else:
|
| 724 |
-
logging.warning(f"Could not find a standard layer count parameter (num_hidden_layers, num_layers) in config for {current_model_name}. Model will be loaded with default layer count.")
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
logging.info(f"Loading tokenizer for model: {current_model_name}")
|
| 728 |
-
tokenizer_kwargs = {"config": config, "trust_remote_code": current_trust_remote_code}
|
| 729 |
-
global_tokenizer = AutoTokenizer.from_pretrained(current_model_name, **tokenizer_kwargs)
|
| 730 |
-
logging.info("Tokenizer loaded.")
|
| 731 |
-
|
| 732 |
-
logging.info(f"Loading model: {current_model_name} with dtype {TORCH_DTYPE} onto {device}")
|
| 733 |
-
|
| 734 |
-
model_kwargs = {"config": config, "torch_dtype": TORCH_DTYPE, "trust_remote_code": current_trust_remote_code}
|
| 735 |
-
|
| 736 |
-
global_model = AutoModelForCausalLM.from_pretrained(current_model_name, **model_kwargs)
|
| 737 |
-
global_model.to(device)
|
| 738 |
-
|
| 739 |
-
try:
|
| 740 |
-
global_model = torch.compile(global_model, mode="max-autotune")
|
| 741 |
-
logging.info("Model compiled with torch.compile (max-autotune mode).")
|
| 742 |
-
except Exception as e:
|
| 743 |
-
logging.warning(f"Failed to compile model with torch.compile: {e}")
|
| 744 |
-
pass
|
| 745 |
-
|
| 746 |
-
global_model.eval()
|
| 747 |
-
logging.info("Model loaded successfully.")
|
| 748 |
-
|
| 749 |
-
global_tokens["eos_token_id"] = global_tokenizer.eos_token_id
|
| 750 |
-
global_tokens["pad_token_id"] = global_tokenizer.pad_token_id
|
| 751 |
-
if global_tokens["pad_token_id"] is None and global_tokens["eos_token_id"] is not None:
|
| 752 |
-
global_tokens["pad_token_id"] = global_tokens["eos_token_id"]
|
| 753 |
-
if global_model.config.pad_token_id is None:
|
| 754 |
-
global_model.config.pad_token_id = global_tokens["pad_token_id"]
|
| 755 |
-
elif global_tokens["pad_token_id"] is None and global_tokens["eos_token_id"] is None:
|
| 756 |
-
logging.warning("Neither EOS nor PAD token is defined for this tokenizer/model.")
|
| 757 |
-
if global_model.config.pad_token_id is None and global_tokens.get("pad_token_id") is not None:
|
| 758 |
-
global_model.config.pad_token_id = global_tokens["pad_token_id"]
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
except Exception as e:
|
| 762 |
-
logging.exception("Failed to load model or tokenizer:")
|
| 763 |
-
global_model = None
|
| 764 |
-
global_tokenizer = None
|
| 765 |
-
global_tokens = {}
|
| 766 |
-
|
| 767 |
-
html_code = """
|
| 768 |
-
<!DOCTYPE html>
|
| 769 |
-
<html lang="es">
|
| 770 |
-
<head>
|
| 771 |
-
<meta charset="UTF-8" />
|
| 772 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 773 |
-
<title>Chatbot Profesional</title>
|
| 774 |
-
<style>
|
| 775 |
-
body { font-family: Arial, sans-serif; margin: 20px; }
|
| 776 |
-
#chatbox { width: 100%; height: 400px; border: 1px solid #ccc; padding: 10px; overflow-y: scroll; margin-bottom: 10px; }
|
| 777 |
-
#user-input { width: calc(100% - 100px); padding: 8px; box-sizing: border-box;}
|
| 778 |
-
#send-btn { width: 90px; padding: 8px 0;}
|
| 779 |
-
#input-area { display: flex;}
|
| 780 |
-
</style>
|
| 781 |
-
</head>
|
| 782 |
-
<body>
|
| 783 |
-
<h1>Chatbot Profesional (POST API)</h1>
|
| 784 |
-
<div id="chatbox"></div>
|
| 785 |
-
<div id="input-area">
|
| 786 |
-
<input type="text" id="user-input" placeholder="Escribe tu mensaje aquí..." autocomplete="off"/>
|
| 787 |
-
<button id="send-btn">Enviar</button>
|
| 788 |
-
</div>
|
| 789 |
-
<script>
|
| 790 |
-
const chatbox = document.getElementById('chatbox');
|
| 791 |
-
const userInput = document.getElementById('user-input');
|
| 792 |
-
const sendBtn = document.getElementById('send-btn');
|
| 793 |
-
|
| 794 |
-
let conversationHistory = [];
|
| 795 |
-
const DEFAULT_SYSTEM_PROMPT = "Eres un asistente profesional y servicial.";
|
| 796 |
-
let currentSystemPrompt = DEFAULT_SYSTEM_PROMPT;
|
| 797 |
-
let botMessageElement = null;
|
| 798 |
-
|
| 799 |
-
function appendMessage(sender, text, isStreaming = false) {
|
| 800 |
-
let msg;
|
| 801 |
-
if (isStreaming && botMessageElement) {
|
| 802 |
-
botMessageElement.textContent += text;
|
| 803 |
-
} else {
|
| 804 |
-
msg = document.createElement('p');
|
| 805 |
-
msg.innerHTML = `<strong>${sender}:</strong> `;
|
| 806 |
-
const textNode = document.createTextNode(text);
|
| 807 |
-
msg.appendChild(textNode);
|
| 808 |
-
chatbox.appendChild(msg);
|
| 809 |
-
if (sender === 'Bot' && isStreaming) {
|
| 810 |
-
botMessageElement = textNode;
|
| 811 |
-
} else {
|
| 812 |
-
botMessageElement = null;
|
| 813 |
-
}
|
| 814 |
-
}
|
| 815 |
-
chatbox.scrollTop = chatbox.scrollHeight;
|
| 816 |
-
}
|
| 817 |
-
|
| 818 |
-
function updateHistory(role, content) {
|
| 819 |
-
conversationHistory.push({ "role": role, "content": content });
|
| 820 |
-
const maxHistorySize = 10;
|
| 821 |
-
if (conversationHistory.length > maxHistorySize * 2) {
|
| 822 |
-
conversationHistory = conversationHistory.slice(-(maxHistorySize * 2));
|
| 823 |
-
}
|
| 824 |
-
}
|
| 825 |
-
|
| 826 |
-
async function sendMessage() {
|
| 827 |
-
const text = userInput.value;
|
| 828 |
-
if (!text) {
|
| 829 |
-
return;
|
| 830 |
-
}
|
| 831 |
-
appendMessage('Usuario', text);
|
| 832 |
-
updateHistory("user", text);
|
| 833 |
-
userInput.value = '';
|
| 834 |
-
sendBtn.disabled = true;
|
| 835 |
-
|
| 836 |
-
botMessageElement = null;
|
| 837 |
-
|
| 838 |
-
const messagePayload = {
|
| 839 |
-
input_text: text,
|
| 840 |
-
history: conversationHistory,
|
| 841 |
-
system_prompt: currentSystemPrompt,
|
| 842 |
-
stream: true,
|
| 843 |
-
temperature: 1.0,
|
| 844 |
-
top_k: 50,
|
| 845 |
-
top_p: 1.0,
|
| 846 |
-
repetition_penalty: 1.0,
|
| 847 |
-
frequency_penalty: 0.0,
|
| 848 |
-
presence_penalty: 0.0,
|
| 849 |
-
num_beams: 1,
|
| 850 |
-
length_penalty: 1.0,
|
| 851 |
-
no_repeat_ngram_size: 0,
|
| 852 |
-
early_stopping: false,
|
| 853 |
-
do_sample: true,
|
| 854 |
-
use_mirostat: false,
|
| 855 |
-
mirostat_tau: 5.0,
|
| 856 |
-
mirostat_eta: 0.1,
|
| 857 |
-
max_new_tokens: 512,
|
| 858 |
-
num_return_sequences: 1,
|
| 859 |
-
return_token_logprobs: true
|
| 860 |
-
};
|
| 861 |
-
|
| 862 |
-
try {
|
| 863 |
-
const response = await fetch('/generate', {
|
| 864 |
-
method: 'POST',
|
| 865 |
-
headers: {
|
| 866 |
-
'Content-Type': 'application/json',
|
| 867 |
-
// Add API Key header if needed
|
| 868 |
-
// 'X-API-Key': 'YOUR_API_KEY_HERE'
|
| 869 |
-
},
|
| 870 |
-
body: JSON.stringify(messagePayload),
|
| 871 |
-
});
|
| 872 |
-
|
| 873 |
-
if (!response.ok) {
|
| 874 |
-
const errorData = await response.json();
|
| 875 |
-
throw new Error(`API Error: ${response.status} ${response.statusText} - ${errorData.detail || errorData.error}`);
|
| 876 |
-
}
|
| 877 |
-
|
| 878 |
-
const reader = response.body.getReader();
|
| 879 |
-
const decoder = new TextDecoder();
|
| 880 |
-
let buffer = '';
|
| 881 |
-
let currentBotResponse = "";
|
| 882 |
-
|
| 883 |
-
while (true) {
|
| 884 |
-
const { value, done } = await reader.read();
|
| 885 |
-
if (done) break;
|
| 886 |
-
|
| 887 |
-
buffer += decoder.decode(value, { stream: true });
|
| 888 |
-
|
| 889 |
-
const lines = buffer.split('\n');
|
| 890 |
-
buffer = lines.pop();
|
| 891 |
-
|
| 892 |
-
for (const line of lines) {
|
| 893 |
-
if (line.trim() === '') continue;
|
| 894 |
-
try {
|
| 895 |
-
const data = JSON.parse(line);
|
| 896 |
-
if (data.type === 'token') {
|
| 897 |
-
currentBotResponse += data.text;
|
| 898 |
-
appendMessage('Bot', data.text, true);
|
| 899 |
-
console.log('Token:', data.token_id, 'Text:', data.text, 'Logprob:', data.logprob);
|
| 900 |
-
} else if (data.type === 'done') {
|
| 901 |
-
console.log('Generation done', data);
|
| 902 |
-
if (data.total_tokens !== undefined) {
|
| 903 |
-
appendMessage('System', `Generated ${data.total_tokens} tokens. Finish reason: ${data.finish_reason}`);
|
| 904 |
-
}
|
| 905 |
-
if (data.final_text !== undefined) {
|
| 906 |
-
updateHistory("assistant", data.final_text);
|
| 907 |
-
} else if (currentBotResponse) {
|
| 908 |
-
updateHistory("assistant", currentBotResponse);
|
| 909 |
-
}
|
| 910 |
-
|
| 911 |
-
} else if (data.type === 'error') {
|
| 912 |
-
appendMessage('Error', data.message);
|
| 913 |
-
currentBotResponse = "";
|
| 914 |
-
}
|
| 915 |
-
} catch (e) {
|
| 916 |
-
console.error('Failed to parse stream chunk:', e, line);
|
| 917 |
-
appendMessage('Error', 'Failed to process stream.');
|
| 918 |
-
currentBotResponse = "";
|
| 919 |
-
reader.cancel();
|
| 920 |
-
return;
|
| 921 |
-
}
|
| 922 |
-
}
|
| 923 |
-
}
|
| 924 |
-
|
| 925 |
-
if (buffer.trim() !== '') {
|
| 926 |
-
try {
|
| 927 |
-
const data = JSON.parse(buffer);
|
| 928 |
-
if (data.type === 'token') {
|
| 929 |
-
currentBotResponse += data.text;
|
| 930 |
-
appendMessage('Bot', data.text, true);
|
| 931 |
-
console.log('Token:', data.token_id, 'Text:', data.text, 'Logprob:', data.logprob);
|
| 932 |
-
} else if (data.type === 'done') {
|
| 933 |
-
console.log('Generation done', data);
|
| 934 |
-
if (data.total_tokens !== undefined) {
|
| 935 |
-
appendMessage('System', `Generated ${data.total_tokens} tokens. Finish reason: ${data.finish_reason}`);
|
| 936 |
-
}
|
| 937 |
-
if (data.final_text !== undefined) {
|
| 938 |
-
updateHistory("assistant", data.final_text);
|
| 939 |
-
} else if (currentBotResponse) {
|
| 940 |
-
updateHistory("assistant", currentBotResponse);
|
| 941 |
-
}
|
| 942 |
-
} else if (data.type === 'error') {
|
| 943 |
-
appendMessage('Error', data.message);
|
| 944 |
-
currentBotResponse = "";
|
| 945 |
-
}
|
| 946 |
-
} catch (e) {
|
| 947 |
-
console.error('Failed to parse remaining buffer:', e, buffer);
|
| 948 |
-
appendMessage('Error', 'Failed to process remaining stream data.');
|
| 949 |
-
currentBotResponse = "";
|
| 950 |
-
}
|
| 951 |
-
}
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
if (currentBotResponse && !botMessageElement) {
|
| 955 |
-
updateHistory("assistant", currentBotResponse);
|
| 956 |
-
}
|
| 957 |
-
botMessageElement = null;
|
| 958 |
-
currentBotResponse = "";
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
} catch (error) {
|
| 962 |
-
console.error('Send message error:', error);
|
| 963 |
-
appendMessage('Error', error.message || 'An unknown error occurred.');
|
| 964 |
-
botMessageElement = null;
|
| 965 |
-
currentBotResponse = "";
|
| 966 |
-
} finally {
|
| 967 |
-
sendBtn.disabled = false;
|
| 968 |
-
}
|
| 969 |
-
}
|
| 970 |
-
|
| 971 |
-
sendBtn.onclick = sendMessage;
|
| 972 |
-
|
| 973 |
-
userInput.addEventListener('keypress', function(event) {
|
| 974 |
-
if (event.key === 'Enter') {
|
| 975 |
-
event.preventDefault();
|
| 976 |
-
sendMessage();
|
| 977 |
-
}
|
| 978 |
-
});
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
</script>
|
| 982 |
-
</body>
|
| 983 |
-
</html>
|
| 984 |
-
"""
|
| 985 |
-
|
| 986 |
-
@app.get("/", response_class=HTMLResponse, summary="Interactive HTML interface")
|
| 987 |
-
async def root():
|
| 988 |
-
return HTMLResponse(content=html_code)
|
| 989 |
-
|
| 990 |
-
@app.get("/health", summary="Check API health and model status")
|
| 991 |
-
async def health():
|
| 992 |
-
status_data = {}
|
| 993 |
-
model_loaded = global_model is not None
|
| 994 |
-
tokenizer_loaded = global_tokenizer is not None
|
| 995 |
-
|
| 996 |
-
status_data["model_loaded"] = model_loaded
|
| 997 |
-
status_data["tokenizer_loaded"] = tokenizer_loaded
|
| 998 |
-
if model_loaded and tokenizer_loaded:
|
| 999 |
-
status_data["status"] = "ok"
|
| 1000 |
-
elif not model_loaded and not tokenizer_loaded:
|
| 1001 |
-
status_data["status"] = "model and tokenizer not loaded"
|
| 1002 |
-
elif not model_loaded:
|
| 1003 |
-
status_data["status"] = "model not loaded"
|
| 1004 |
-
else:
|
| 1005 |
-
status_data["status"] = "tokenizer not loaded"
|
| 1006 |
-
|
| 1007 |
-
status_data["cuda_available"] = torch.cuda.is_available()
|
| 1008 |
-
if torch.cuda.is_available():
|
| 1009 |
-
device_count = torch.cuda.device_count()
|
| 1010 |
-
status_data["device_count"] = device_count
|
| 1011 |
-
status_data["devices"] = []
|
| 1012 |
-
for i in range(device_count):
|
| 1013 |
-
try:
|
| 1014 |
-
device_status = {
|
| 1015 |
-
"id": i,
|
| 1016 |
-
"name": torch.cuda.get_device_name(i),
|
| 1017 |
-
"total_memory_mib": round(torch.cuda.get_device_properties(i).total_memory / (1024 * 1024), 2),
|
| 1018 |
-
"allocated_memory_mib": round(torch.cuda.memory_allocated(i) / (1024 * 1024), 2),
|
| 1019 |
-
"cached_memory_mib": round(torch.cuda.memory_reserved(i) / (1024 * 1024), 2),
|
| 1020 |
-
}
|
| 1021 |
-
status_data["devices"].append(device_status)
|
| 1022 |
-
except Exception as e:
|
| 1023 |
-
logging.error(f"Error getting GPU memory info for device {i}: {e}")
|
| 1024 |
-
status_data["devices"].append({"id": i, "error": str(e)})
|
| 1025 |
-
else:
|
| 1026 |
-
status_data["message"] = "CUDA not available. GPU resource info is not applicable."
|
| 1027 |
-
|
| 1028 |
-
status_data["cpu_cores"] = os.cpu_count()
|
| 1029 |
-
status_data["thread_settings"] = {
|
| 1030 |
-
"OMP_NUM_THREADS": os.environ.get("OMP_NUM_THREADS"),
|
| 1031 |
-
"MKL_NUM_THREADS": os.environ.get("MKL_NUM_THREADS"),
|
| 1032 |
-
"PYTORCH_BEFORE_FORK_THREAD_CONTROLS": os.environ.get("PYTORCH_BEFORE_FORK_THREAD_CONTROLS"),
|
| 1033 |
-
"torch.get_num_threads()": torch.get_num_threads(),
|
| 1034 |
-
"torch.get_num_interop_threads()": torch.get_num_interop_threads(),
|
| 1035 |
-
}
|
| 1036 |
-
|
| 1037 |
-
status_data["max_concurrent_generations"] = MAX_CONCURRENT_GENERATIONS
|
| 1038 |
-
status_data["currently_running_generations"] = MAX_CONCURRENT_GENERATIONS - generation_semaphore._value
|
| 1039 |
-
status_data["available_slots"] = generation_semaphore._value
|
| 1040 |
-
|
| 1041 |
-
return JSONResponse(status_code=status.HTTP_200_OK if model_loaded and tokenizer_loaded else status.HTTP_503_SERVICE_UNAVAILABLE, content=status_data)
|
| 1042 |
-
|
| 1043 |
-
@app.get("/config", summary="Get API configuration")
|
| 1044 |
-
async def get_config():
|
| 1045 |
-
torch_dtype_str_out = str(TORCH_DTYPE).split('.')[-1] if isinstance(TORCH_DTYPE, torch.dtype) else str(TORCH_DTYPE)
|
| 1046 |
-
|
| 1047 |
-
return JSONResponse({
|
| 1048 |
-
"model_name": MODEL_NAME,
|
| 1049 |
-
"system_prompt_default": SYSTEM_PROMPT,
|
| 1050 |
-
"max_context_tokens": MAX_CONTEXT_TOKENS,
|
| 1051 |
-
"max_generation_tokens": MAX_GENERATION_TOKENS,
|
| 1052 |
-
"cuda_available": torch.cuda.is_available(),
|
| 1053 |
-
"model_loaded": global_model is not None,
|
| 1054 |
-
"tokenizer_loaded": global_tokenizer is not None,
|
| 1055 |
-
"max_concurrent_generations": MAX_CONCURRENT_GENERATIONS,
|
| 1056 |
-
"trust_remote_code_startup_env": TRUST_REMOTE_CODE_ENV,
|
| 1057 |
-
"trust_remote_code_effective": TRUST_REMOTE_CODE,
|
| 1058 |
-
"enable_flash_attention_2": ENABLE_FLASH_ATTENTION_2,
|
| 1059 |
-
"torch_dtype": torch_dtype_str_out,
|
| 1060 |
-
"eos_token_id": global_tokens.get("eos_token_id"),
|
| 1061 |
-
"pad_token_id": global_tokens.get("pad_token_id"),
|
| 1062 |
-
"bos_token_id": global_tokenizer.bos_token_id if global_tokenizer else None,
|
| 1063 |
-
"api_key_required": API_KEY is not None
|
| 1064 |
-
})
|
| 1065 |
-
|
| 1066 |
-
@app.get("/model/info", summary="Get detailed model information", dependencies=[Depends(get_api_key)])
|
| 1067 |
-
async def get_model_info():
|
| 1068 |
-
if global_model is None:
|
| 1069 |
-
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Model is not loaded.")
|
| 1070 |
-
try:
|
| 1071 |
-
config_dict = global_model.config.to_dict()
|
| 1072 |
-
keys_to_remove = ['torch_dtype', '_attn_implementation', 'architectures', 'id2label', 'label2id', 'torch_dtype']
|
| 1073 |
-
for key in keys_to_remove:
|
| 1074 |
-
config_dict.pop(key, None)
|
| 1075 |
-
return JSONResponse({
|
| 1076 |
-
"model_name": MODEL_NAME,
|
| 1077 |
-
"is_loaded": True,
|
| 1078 |
-
"device": str(global_model.device),
|
| 1079 |
-
"torch_dtype": str(global_model.dtype),
|
| 1080 |
-
"config": config_dict
|
| 1081 |
-
})
|
| 1082 |
-
except Exception as e:
|
| 1083 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting model info: {e}")
|
| 1084 |
-
|
| 1085 |
-
@app.post("/tokenize", summary="Tokenize text", dependencies=[Depends(get_api_key)])
|
| 1086 |
-
async def tokenize_endpoint(req: TokenizeRequest):
|
| 1087 |
-
if global_tokenizer is None:
|
| 1088 |
-
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Tokenizer is not loaded.")
|
| 1089 |
-
try:
|
| 1090 |
-
tokenizer_kwargs_final = req.tokenizer_kwargs or {}
|
| 1091 |
-
return_tensors = req.return_tensors if req.return_tensors is not None else None
|
| 1092 |
-
if return_tensors is None and (req.return_attention_mask or req.return_offsets_mapping or req.return_length):
|
| 1093 |
-
return_tensors = "pt"
|
| 1094 |
-
encoded = global_tokenizer(
|
| 1095 |
-
req.text,
|
| 1096 |
-
add_special_tokens=req.add_special_tokens,
|
| 1097 |
-
return_token_type_ids=req.return_token_type_ids,
|
| 1098 |
-
padding=req.padding,
|
| 1099 |
-
truncation=req.truncation,
|
| 1100 |
-
max_length=req.max_length,
|
| 1101 |
-
is_split_into_words=req.is_split_into_words,
|
| 1102 |
-
return_tensors=return_tensors,
|
| 1103 |
-
return_attention_mask=req.return_attention_mask,
|
| 1104 |
-
return_offsets_mapping=req.return_offsets_mapping,
|
| 1105 |
-
return_length=req.return_length,
|
| 1106 |
-
verbose=req.verbose,
|
| 1107 |
-
**tokenizer_kwargs_final
|
| 1108 |
-
)
|
| 1109 |
-
if return_tensors:
|
| 1110 |
-
return JSONResponse({k: v.tolist() if isinstance(v, torch.Tensor) else v for k, v in encoded.items()})
|
| 1111 |
-
else:
|
| 1112 |
-
if isinstance(req.text, str):
|
| 1113 |
-
response_data = {
|
| 1114 |
-
"tokens": encoded["input_ids"],
|
| 1115 |
-
"token_count": len(encoded["input_ids"]),
|
| 1116 |
-
"input_text": req.text
|
| 1117 |
-
}
|
| 1118 |
-
if "token_type_ids" in encoded: response_data["token_type_ids"] = encoded["token_type_ids"]
|
| 1119 |
-
if "attention_mask" in encoded: response_data["attention_mask"] = encoded["attention_mask"]
|
| 1120 |
-
if "offset_mapping" in encoded: response_data["offset_mapping"] = encoded["offset_mapping"]
|
| 1121 |
-
if "length" in encoded: response_data["length"] = encoded["length"]
|
| 1122 |
-
return JSONResponse(response_data)
|
| 1123 |
-
elif isinstance(req.text, list):
|
| 1124 |
-
results = []
|
| 1125 |
-
for i, item_text in enumerate(req.text):
|
| 1126 |
-
item_result: Dict[str, Any] = {"text": item_text}
|
| 1127 |
-
if i < len(encoded["input_ids"]):
|
| 1128 |
-
item_result["tokens"] = encoded["input_ids"][i]
|
| 1129 |
-
item_result["token_count"] = len(encoded["input_ids"][i])
|
| 1130 |
-
if "token_type_ids" in encoded and i < len(encoded["token_type_ids"]):
|
| 1131 |
-
item_result["token_type_ids"] = encoded["token_type_ids"][i]
|
| 1132 |
-
if "attention_mask" in encoded and i < len(encoded["attention_mask"]):
|
| 1133 |
-
item_result["attention_mask"] = encoded["attention_mask"][i]
|
| 1134 |
-
if "offset_mapping" in encoded and i < len(encoded["offset_mapping"]):
|
| 1135 |
-
item_result["offset_mapping"] = encoded["offset_mapping"][i]
|
| 1136 |
-
if "length" in encoded and i < len(encoded["length"]):
|
| 1137 |
-
item_result["length"] = encoded["length"][i]
|
| 1138 |
-
else:
|
| 1139 |
-
item_result["tokens"] = []
|
| 1140 |
-
item_result["token_count"] = 0
|
| 1141 |
-
results.append(item_result)
|
| 1142 |
-
return JSONResponse(results)
|
| 1143 |
-
except Exception as e:
|
| 1144 |
-
logging.exception("Tokenization error:")
|
| 1145 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Tokenization error: {e}")
|
| 1146 |
-
|
| 1147 |
-
@app.post("/decode", summary="Decode token IDs to text", dependencies=[Depends(get_api_key)])
|
| 1148 |
-
async def decode_endpoint(req: DecodeRequest):
|
| 1149 |
-
if global_tokenizer is None:
|
| 1150 |
-
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Tokenizer is not loaded.")
|
| 1151 |
-
try:
|
| 1152 |
-
decode_kwargs = req.decode_kwargs or {}
|
| 1153 |
-
text = global_tokenizer.decode(
|
| 1154 |
-
req.token_ids,
|
| 1155 |
-
skip_special_tokens=req.skip_special_tokens,
|
| 1156 |
-
clean_up_tokenization_spaces=req.clean_up_tokenization_spaces,
|
| 1157 |
-
**decode_kwargs
|
| 1158 |
-
)
|
| 1159 |
-
return JSONResponse({"text": text})
|
| 1160 |
-
except Exception as e:
|
| 1161 |
-
logging.exception("Decoding error:")
|
| 1162 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Decoding error: {e}")
|
| 1163 |
-
|
| 1164 |
-
@app.post("/system_prompt", summary="Update global system prompt", dependencies=[Depends(get_api_key)])
|
| 1165 |
-
async def update_system_prompt(req: SystemPromptUpdateRequest):
|
| 1166 |
-
global SYSTEM_PROMPT
|
| 1167 |
-
if req.system_prompt is not None:
|
| 1168 |
-
SYSTEM_PROMPT = req.system_prompt.strip()
|
| 1169 |
-
return JSONResponse({"status": "success", "message": "Global system prompt updated"})
|
| 1170 |
-
else:
|
| 1171 |
-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="System prompt cannot be null")
|
| 1172 |
-
|
| 1173 |
-
@app.get("/system_prompt", summary="Get global system prompt", dependencies=[Depends(get_api_key)])
|
| 1174 |
-
async def get_system_prompt():
|
| 1175 |
-
return JSONResponse({"system_prompt": SYSTEM_PROMPT})
|
| 1176 |
-
|
| 1177 |
-
@app.post("/model/reload", summary="Reload the model", dependencies=[Depends(get_api_key)])
|
| 1178 |
-
async def reload_model(req: ModelReloadRequest):
|
| 1179 |
-
global global_model, global_tokenizer, global_tokens, MODEL_NAME, TRUST_REMOTE_CODE, ENABLE_FLASH_ATTENTION_2, TORCH_DTYPE, TORCH_DTYPE_STR, TRUST_REMOTE_CODE_ENV
|
| 1180 |
-
new_model_name = req.model_name if req.model_name else MODEL_NAME
|
| 1181 |
-
new_trust_remote_code = req.trust_remote_code if req.trust_remote_code is not None else (TRUST_REMOTE_CODE_ENV or (new_model_name == DEFAULT_MODEL_NAME))
|
| 1182 |
-
new_enable_flash_attention_2 = req.enable_flash_attention_2 if req.enable_flash_attention_2 is not None else ENABLE_FLASH_ATTENTION_2
|
| 1183 |
-
new_torch_dtype_str_req = req.torch_dtype if req.torch_dtype else TORCH_DTYPE_STR
|
| 1184 |
-
try:
|
| 1185 |
-
new_torch_dtype = getattr(torch, new_torch_dtype_str_req.lower())
|
| 1186 |
-
if new_torch_dtype != torch.float32:
|
| 1187 |
-
logging.warning(f"Requested dtype {new_torch_dtype_str_req} might not be fully performant on CPU. Using float32.")
|
| 1188 |
-
new_torch_dtype = torch.float32
|
| 1189 |
-
elif not isinstance(new_torch_dtype, torch.dtype):
|
| 1190 |
-
raise AttributeError
|
| 1191 |
-
new_torch_dtype_str = str(new_torch_dtype).split('.')[-1]
|
| 1192 |
-
except AttributeError:
|
| 1193 |
-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unsupported torch_dtype: {new_torch_dtype_str_req}")
|
| 1194 |
-
device = "cpu"
|
| 1195 |
-
async def _reload_model():
|
| 1196 |
-
global global_model, global_tokenizer, global_tokens, MODEL_NAME, TRUST_REMOTE_CODE, ENABLE_FLASH_ATTENTION_2, TORCH_DTYPE, TORCH_DTYPE_STR
|
| 1197 |
-
logging.info(f"Attempting to load model: {new_model_name}")
|
| 1198 |
-
try:
|
| 1199 |
-
logging.info("Unloading current model...")
|
| 1200 |
-
await cleanup(device)
|
| 1201 |
-
if global_model is not None:
|
| 1202 |
-
del global_model
|
| 1203 |
-
global_model = None
|
| 1204 |
-
if global_tokenizer is not None:
|
| 1205 |
-
del global_tokenizer
|
| 1206 |
-
global_tokenizer = None
|
| 1207 |
-
global_tokens = {}
|
| 1208 |
-
torch.cuda.empty_cache() if torch.cuda.is_available() else None
|
| 1209 |
-
gc.collect()
|
| 1210 |
-
logging.info("Current model unloaded.")
|
| 1211 |
-
logging.info(f"Loading config for model: {new_model_name}")
|
| 1212 |
-
config = AutoConfig.from_pretrained(new_model_name, trust_remote_code=new_trust_remote_code)
|
| 1213 |
-
logging.info(f"Original config: {config}")
|
| 1214 |
-
|
| 1215 |
-
logging.info(f"Modifying config to load only 1 hidden layer.")
|
| 1216 |
-
if hasattr(config, 'num_hidden_layers'):
|
| 1217 |
-
original_num_layers = config.num_hidden_layers
|
| 1218 |
-
config.num_hidden_layers = 1
|
| 1219 |
-
logging.info(f"Changed num_hidden_layers from {original_num_layers} to 1.")
|
| 1220 |
-
elif hasattr(config, 'num_layers'):
|
| 1221 |
-
original_num_layers = config.num_layers
|
| 1222 |
-
config.num_layers = 1
|
| 1223 |
-
logging.info(f"Changed num_layers from {original_num_layers} to 1.")
|
| 1224 |
-
else:
|
| 1225 |
-
logging.warning(f"Could not find a standard layer count parameter (num_hidden_layers, num_layers) in config for {new_model_name}. Model will be loaded with default layer count.")
|
| 1226 |
-
|
| 1227 |
-
logging.info(f"Loading tokenizer for model: {new_model_name}")
|
| 1228 |
-
tokenizer_kwargs = {"config": config, "trust_remote_code": new_trust_remote_code}
|
| 1229 |
-
if req.tokenizer_kwargs:
|
| 1230 |
-
tokenizer_kwargs.update(req.tokenizer_kwargs)
|
| 1231 |
-
tokenizer = AutoTokenizer.from_pretrained(new_model_name, **tokenizer_kwargs)
|
| 1232 |
-
logging.info("Tokenizer loaded.")
|
| 1233 |
-
logging.info(f"Loading model: {new_model_name} with dtype {new_torch_dtype_str} onto {device}")
|
| 1234 |
-
model_kwargs = {"config": config, "torch_dtype": new_torch_dtype, "trust_remote_code": new_trust_remote_code}
|
| 1235 |
-
model = AutoModelForCausalLM.from_pretrained(new_model_name, **model_kwargs)
|
| 1236 |
-
model.to(device)
|
| 1237 |
-
try:
|
| 1238 |
-
model = torch.compile(model, mode="max-autotune")
|
| 1239 |
-
logging.info("New model compiled with torch.compile (max-autotune mode).")
|
| 1240 |
-
except Exception as e:
|
| 1241 |
-
logging.warning(f"Failed to compile new model with torch.compile: {e}")
|
| 1242 |
-
pass
|
| 1243 |
-
model.eval()
|
| 1244 |
-
logging.info("New model loaded successfully.")
|
| 1245 |
-
global_model = model
|
| 1246 |
-
global_tokenizer = tokenizer
|
| 1247 |
-
global_tokens["eos_token_id"] = global_tokenizer.eos_token_id
|
| 1248 |
-
global_tokens["pad_token_id"] = global_tokenizer.pad_token_id
|
| 1249 |
-
if global_tokens["pad_token_id"] is None and global_tokens["eos_token_id"] is not None:
|
| 1250 |
-
global_tokens["pad_token_id"] = global_tokens["eos_token_id"]
|
| 1251 |
-
if global_model.config.pad_token_id is None:
|
| 1252 |
-
global_model.config.pad_token_id = global_tokens["pad_token_id"]
|
| 1253 |
-
elif global_tokens["pad_token_id"] is None and global_tokens["eos_token_id"] is None:
|
| 1254 |
-
logging.warning("Neither EOS nor PAD token defined for new model.")
|
| 1255 |
-
if global_model.config.pad_token_id is None and global_tokens.get("pad_token_id") is not None:
|
| 1256 |
-
global_model.config.pad_token_id = global_tokens["pad_token_id"]
|
| 1257 |
-
MODEL_NAME = new_model_name
|
| 1258 |
-
TRUST_REMOTE_CODE = new_trust_remote_code
|
| 1259 |
-
ENABLE_FLASH_ATTENTION_2 = new_enable_flash_attention_2
|
| 1260 |
-
TORCH_DTYPE = new_torch_dtype
|
| 1261 |
-
TORCH_DTYPE_STR = new_torch_dtype_str
|
| 1262 |
-
if hasattr(global_tokenizer, 'use_fast'):
|
| 1263 |
-
pass
|
| 1264 |
-
logging.info(f"Model successfully reloaded to: {MODEL_NAME}")
|
| 1265 |
-
logging.info({"status": "success", "message": f"Model {new_model_name} loaded successfully."})
|
| 1266 |
-
except Exception as e:
|
| 1267 |
-
logging.exception(f"Failed to load model {new_model_name}:")
|
| 1268 |
-
global_model = None
|
| 1269 |
-
global_tokenizer = None
|
| 1270 |
-
global_tokens = {}
|
| 1271 |
-
logging.error({"status": "error", "message": f"Failed to load model {new_model_name}: {e}. Model is now unloaded."})
|
| 1272 |
-
asyncio.create_task(_reload_model())
|
| 1273 |
-
return JSONResponse({"status": "info", "message": f"Attempting to load model {new_model_name} in background. Check logs for status."})
|
| 1274 |
-
|
| 1275 |
-
@app.post("/model/unload", summary="Unload the model", dependencies=[Depends(get_api_key)])
|
| 1276 |
-
async def unload_model():
|
| 1277 |
-
global global_model, global_tokenizer, global_tokens
|
| 1278 |
-
device = "cpu"
|
| 1279 |
-
logging.info("Attempting to unload model.")
|
| 1280 |
-
try:
|
| 1281 |
-
await cleanup(device)
|
| 1282 |
-
if global_model is not None:
|
| 1283 |
-
del global_model
|
| 1284 |
-
global_model = None
|
| 1285 |
-
if global_tokenizer is not None:
|
| 1286 |
-
del global_tokenizer
|
| 1287 |
-
global_tokenizer = None
|
| 1288 |
-
global_tokens = {}
|
| 1289 |
-
torch.cuda.empty_cache() if torch.cuda.is_available() else None
|
| 1290 |
-
gc.collect()
|
| 1291 |
-
logging.info("Model unloaded successfully.")
|
| 1292 |
-
return JSONResponse({"status": "success", "message": "Model unloaded successfully."})
|
| 1293 |
-
except Exception as e:
|
| 1294 |
-
logging.exception("Failed to unload model:")
|
| 1295 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to unload model: {e}")
|
| 1296 |
-
|
| 1297 |
-
@app.get("/queue/status", summary="Get current generation queue status", dependencies=[Depends(get_api_key)])
|
| 1298 |
-
async def queue_status():
|
| 1299 |
-
try:
|
| 1300 |
-
current_running = MAX_CONCURRENT_GENERATIONS - generation_semaphore._value
|
| 1301 |
-
is_waiting = generation_semaphore.locked()
|
| 1302 |
-
return JSONResponse({
|
| 1303 |
-
"max_concurrent": MAX_CONCURRENT_GENERATIONS,
|
| 1304 |
-
"currently_running": current_running,
|
| 1305 |
-
"available_slots": generation_semaphore._value
|
| 1306 |
-
})
|
| 1307 |
-
except Exception as e:
|
| 1308 |
-
logging.exception("Error getting queue status:")
|
| 1309 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting queue status: {e}")
|
| 1310 |
-
|
| 1311 |
-
@app.get("/tokens/special", summary="Get special token IDs", dependencies=[Depends(get_api_key)])
|
| 1312 |
-
async def get_special_tokens():
|
| 1313 |
-
if global_tokenizer is None:
|
| 1314 |
-
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Tokenizer is not loaded.")
|
| 1315 |
-
try:
|
| 1316 |
-
return JSONResponse({
|
| 1317 |
-
"eos_token": global_tokenizer.eos_token,
|
| 1318 |
-
"eos_token_id": global_tokenizer.eos_token_id,
|
| 1319 |
-
"pad_token": global_tokenizer.pad_token,
|
| 1320 |
-
"pad_token_id": global_tokenizer.pad_token_id,
|
| 1321 |
-
"bos_token": global_tokenizer.bos_token,
|
| 1322 |
-
"bos_token_id": global_tokenizer.bos_token_id,
|
| 1323 |
-
"unk_token": global_tokenizer.unk_token,
|
| 1324 |
-
"unk_token_id": global_tokenizer.unk_token_id,
|
| 1325 |
-
})
|
| 1326 |
-
except Exception as e:
|
| 1327 |
-
logging.exception("Error getting special tokens:")
|
| 1328 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting special tokens: {e}")
|
| 1329 |
-
|
| 1330 |
-
@app.post("/generate", summary="Generate text", dependencies=[Depends(get_api_key)])
|
| 1331 |
-
async def generate_endpoint(req: GenerateRequest):
|
| 1332 |
-
if global_model is None or global_tokenizer is None:
|
| 1333 |
-
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Model is not loaded. It may still be loading or failed to load.")
|
| 1334 |
-
device = "cpu"
|
| 1335 |
-
apply_seed(req.seed)
|
| 1336 |
-
try:
|
| 1337 |
-
initial_prompt_text = format_conversation(req.input_text, req.history, req.system_prompt)
|
| 1338 |
-
except Exception as e:
|
| 1339 |
-
logging.exception("Error formatting conversation:")
|
| 1340 |
-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Error formatting conversation: {e}")
|
| 1341 |
-
try:
|
| 1342 |
-
tokenizer_encoding_kwargs = req.tokenizer_kwargs or {}
|
| 1343 |
-
if 'use_fast' not in tokenizer_encoding_kwargs:
|
| 1344 |
-
tokenizer_encoding_kwargs['use_fast'] = req.use_fast_tokenizer
|
| 1345 |
-
encoded = global_tokenizer(initial_prompt_text, return_tensors="pt", add_special_tokens=False, **tokenizer_encoding_kwargs).to(device)
|
| 1346 |
-
initial_ids_before_trunc = encoded.input_ids
|
| 1347 |
-
initial_prompt_tokens_count_before_trunc = initial_ids_before_trunc.shape[-1]
|
| 1348 |
-
ids = truncate_encoded_ids(initial_ids_before_trunc, MAX_CONTEXT_TOKENS)
|
| 1349 |
-
current_prompt_tokens_count = ids.shape[-1]
|
| 1350 |
-
except Exception as e:
|
| 1351 |
-
logging.exception("Tokenizer error during encoding:")
|
| 1352 |
-
await cleanup(device)
|
| 1353 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Tokenizer encoding error: {e}")
|
| 1354 |
-
if req.tokenize_only:
|
| 1355 |
-
await cleanup(device)
|
| 1356 |
-
return JSONResponse({
|
| 1357 |
-
"prompt_tokens_count": initial_prompt_tokens_count_before_trunc,
|
| 1358 |
-
"max_context_tokens": MAX_CONTEXT_TOKENS,
|
| 1359 |
-
"truncated": initial_prompt_tokens_count_before_trunc > MAX_CONTEXT_TOKENS,
|
| 1360 |
-
"input_text_processed": initial_prompt_text,
|
| 1361 |
-
"input_ids_truncated": ids.tolist()[0]
|
| 1362 |
-
})
|
| 1363 |
-
total_capacity = MAX_CONTEXT_TOKENS + MAX_GENERATION_TOKENS
|
| 1364 |
-
total_requested_seq_len = current_prompt_tokens_count + req.max_new_tokens
|
| 1365 |
-
if not req.stream and total_requested_seq_len > total_capacity:
|
| 1366 |
-
await cleanup(device)
|
| 1367 |
-
raise HTTPException(
|
| 1368 |
-
status_code=status.HTTP_400_BAD_REQUEST,
|
| 1369 |
-
detail=f"Requested sequence length ({total_requested_seq_len} tokens = {current_prompt_tokens_count} prompt + {req.max_new_tokens} new) exceeds model capacity ({total_capacity} tokens) and non-streaming is requested. Consider enabling streaming or reducing max_new_tokens."
|
| 1370 |
-
)
|
| 1371 |
-
async with generation_semaphore:
|
| 1372 |
-
try:
|
| 1373 |
-
gen_cfg = GenerationConfig(
|
| 1374 |
-
temperature=req.temperature,
|
| 1375 |
-
top_k=req.top_k,
|
| 1376 |
-
top_p=req.top_p,
|
| 1377 |
-
repetition_penalty=req.repetition_penalty,
|
| 1378 |
-
frequency_penalty=req.frequency_penalty,
|
| 1379 |
-
presence_penalty=req.presence_penalty,
|
| 1380 |
-
num_beams=req.num_beams if not req.stream else 1,
|
| 1381 |
-
length_penalty=req.length_penalty,
|
| 1382 |
-
no_repeat_ngram_size=req.no_repeat_ngram_size,
|
| 1383 |
-
early_stopping=req.early_stopping,
|
| 1384 |
-
do_sample=req.do_sample,
|
| 1385 |
-
use_mirostat_mode=1 if req.use_mirostat else 0,
|
| 1386 |
-
mirostat_tau=req.mirostat_tau,
|
| 1387 |
-
mirostat_eta=req.mirostat_eta,
|
| 1388 |
-
max_new_tokens=req.max_new_tokens,
|
| 1389 |
-
eos_token_id=req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id"),
|
| 1390 |
-
pad_token_id=req.pad_token_id_override if req.pad_token_id_override is not None else global_tokens.get("pad_token_id"),
|
| 1391 |
-
bos_token_id=req.bos_token_id_override if req.bos_token_id_override is not None else global_tokenizer.bos_token_id,
|
| 1392 |
-
num_return_sequences=req.num_return_sequences if not req.stream else 1,
|
| 1393 |
-
bad_words_ids=req.bad_words_ids,
|
| 1394 |
-
forced_bos_token_id=req.forced_bos_token_id,
|
| 1395 |
-
forced_eos_token_id=req.forced_eos_token_id,
|
| 1396 |
-
renormalize_logits=req.renormalize_logits,
|
| 1397 |
-
suppress_tokens=req.suppress_tokens,
|
| 1398 |
-
begin_suppress_tokens=req.begin_suppress_tokens,
|
| 1399 |
-
end_suppress_tokens=req.end_suppress_tokens,
|
| 1400 |
-
encoder_no_repeat_ngram_size=req.encoder_no_repeat_ngram_size,
|
| 1401 |
-
min_length=req.min_length,
|
| 1402 |
-
max_length=req.max_length,
|
| 1403 |
-
exponential_decay_length_penalty=req.exponential_decay_length_penalty,
|
| 1404 |
-
use_cache=req.use_cache,
|
| 1405 |
-
typical_p=req.typical_p,
|
| 1406 |
-
epsilon_cutoff=req.epsilon_cutoff,
|
| 1407 |
-
eta_cutoff=req.eta_cutoff,
|
| 1408 |
-
temperature_cutoff=req.temperature_cutoff,
|
| 1409 |
-
encoder_repetition_penalty=req.encoder_repetition_penalty,
|
| 1410 |
-
max_time=req.max_time,
|
| 1411 |
-
output_watermark=req.output_watermark,
|
| 1412 |
-
diversity_penalty=req.diversity_penalty,
|
| 1413 |
-
num_beam_groups=req.num_beam_groups if not req.stream else 1,
|
| 1414 |
-
length_normalization_factor=req.length_normalization_factor,
|
| 1415 |
-
min_new_tokens=req.min_new_tokens,
|
| 1416 |
-
do_normalize_logits=req.do_normalize_logits,
|
| 1417 |
-
output_scores=req.output_scores,
|
| 1418 |
-
output_attentions=req.output_attentions,
|
| 1419 |
-
output_hidden_states=req.output_hidden_states,
|
| 1420 |
-
)
|
| 1421 |
-
if req.stream:
|
| 1422 |
-
gen_cfg.use_cache = True
|
| 1423 |
-
gen_cfg.num_beams = 1
|
| 1424 |
-
gen_cfg.num_return_sequences = 1
|
| 1425 |
-
gen_cfg.num_beam_groups = 1
|
| 1426 |
-
return StreamingResponse(stream_generation_logic(req, ids, gen_cfg, device), media_type="text/plain" if req.return_only_text else "application/json")
|
| 1427 |
-
else:
|
| 1428 |
-
response_payload = await non_stream_generation_logic(req, ids, gen_cfg, device)
|
| 1429 |
-
if req.return_only_text:
|
| 1430 |
-
texts = [seq["text"] for seq in response_payload.get("generated_sequences", []) if seq.get("text") is not None]
|
| 1431 |
-
if req.num_return_sequences == 1 and texts:
|
| 1432 |
-
return PlainTextResponse(texts[0])
|
| 1433 |
-
else:
|
| 1434 |
-
return JSONResponse(texts)
|
| 1435 |
-
else:
|
| 1436 |
-
return JSONResponse(response_payload)
|
| 1437 |
-
except Exception as e:
|
| 1438 |
-
logging.exception("Generation error:")
|
| 1439 |
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Generation error: {e}")
|
| 1440 |
-
finally:
|
| 1441 |
-
await cleanup(device)
|
| 1442 |
-
|
| 1443 |
-
if __name__ == "__main__":
|
| 1444 |
-
uvicorn.run(
|
| 1445 |
-
app, host="0.0.0.0", port=7860,
|
| 1446 |
-
log_level="critical",
|
| 1447 |
-
access_log=False
|
| 1448 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|