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