2023-12-21 14:24:50 -07:00
|
|
|
import time
|
|
|
|
|
2023-09-26 22:09:11 -06:00
|
|
|
try:
|
|
|
|
import gevent.monkey
|
|
|
|
|
|
|
|
gevent.monkey.patch_all()
|
|
|
|
except ImportError:
|
|
|
|
pass
|
|
|
|
|
2023-12-21 14:24:50 -07:00
|
|
|
import logging
|
2023-08-21 21:28:52 -06:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
from pathlib import Path
|
2023-09-23 21:17:13 -06:00
|
|
|
|
2023-09-20 20:30:31 -06:00
|
|
|
import simplejson as json
|
2023-10-27 19:19:22 -06:00
|
|
|
from flask import Flask, jsonify, render_template, request, Response
|
2023-08-21 21:28:52 -06:00
|
|
|
|
2023-10-27 19:19:22 -06:00
|
|
|
import config
|
|
|
|
from llm_server import opts
|
|
|
|
from llm_server.cluster.backend import get_model_choices
|
|
|
|
from llm_server.cluster.cluster_config import cluster_config
|
|
|
|
from llm_server.config.config import mode_ui_names
|
|
|
|
from llm_server.config.load import load_config
|
|
|
|
from llm_server.custom_redis import flask_cache, redis
|
2024-05-07 09:48:51 -06:00
|
|
|
from llm_server.database.conn import database, Database
|
2023-09-20 20:30:31 -06:00
|
|
|
from llm_server.database.create import create_db
|
2023-10-27 19:19:22 -06:00
|
|
|
from llm_server.helpers import auto_set_base_client_api
|
|
|
|
from llm_server.llm.vllm.info import vllm_info
|
2024-05-07 09:48:51 -06:00
|
|
|
from llm_server.logging import init_logging, create_logger
|
2023-10-27 19:19:22 -06:00
|
|
|
from llm_server.routes.openai import openai_bp, openai_model_bp
|
2023-09-12 10:30:45 -06:00
|
|
|
from llm_server.routes.server_error import handle_server_error
|
2023-09-26 13:32:33 -06:00
|
|
|
from llm_server.routes.v1 import bp
|
2023-10-27 19:19:22 -06:00
|
|
|
from llm_server.routes.v1.generate_stats import generate_stats
|
|
|
|
from llm_server.sock import init_wssocket
|
2023-09-12 10:30:45 -06:00
|
|
|
|
2023-10-27 19:19:22 -06:00
|
|
|
# TODO: seperate queue item timeout for websockets (make longer, like 5 minutes)
|
|
|
|
# TODO: return an `error: True`, error code, and error message rather than just a formatted message
|
|
|
|
# TODO: what happens when all backends are offline? What about the "online" key in the stats page?
|
|
|
|
# TODO: redis SCAN vs KEYS??
|
|
|
|
# TODO: is frequency penalty the same as ooba repetition penalty???
|
|
|
|
# TODO: make sure openai_moderation_enabled works on websockets, completions, and chat completions
|
2023-09-25 22:32:48 -06:00
|
|
|
|
2023-09-27 18:36:51 -06:00
|
|
|
# Lower priority
|
2023-10-27 19:19:22 -06:00
|
|
|
# TODO: if a backend is at its limit of concurrent requests, choose a different one
|
|
|
|
# TODO: make error messages consitient
|
|
|
|
# TODO: support logit_bias on OpenAI and Ooba endpoints.
|
|
|
|
# TODO: add a way to cancel VLLM gens. Maybe use websockets?
|
|
|
|
# TODO: validate openai_silent_trim works as expected and only when enabled
|
|
|
|
# TODO: rewrite config storage. Store in redis so we can reload it.
|
|
|
|
# TODO: set VLLM to stream ALL data using socket.io. If the socket disconnects, cancel generation.
|
2023-09-28 01:34:15 -06:00
|
|
|
# TODO: estiamted wait time needs to account for full concurrent_gens but the queue is less than concurrent_gens
|
|
|
|
# TODO: the estiamted wait time lags behind the stats
|
2023-09-27 14:36:49 -06:00
|
|
|
# TODO: simulate OpenAI error messages regardless of endpoint
|
2023-09-27 16:12:36 -06:00
|
|
|
# TODO: send extra headers when ratelimited?
|
2023-09-25 18:18:29 -06:00
|
|
|
# TODO: make sure log_prompt() is used everywhere, including errors and invalid requests
|
2023-09-25 22:32:48 -06:00
|
|
|
# TODO: unify logging thread in a function and use async/await instead
|
2023-09-27 18:36:51 -06:00
|
|
|
# TODO: move the netdata stats to a seperate part of the stats and have it set to the currently selected backend
|
|
|
|
# TODO: have VLLM reply with stats (TPS, generated token count, processing time)
|
|
|
|
# TODO: add config reloading via stored redis variables
|
2023-09-25 22:32:48 -06:00
|
|
|
|
2023-09-27 16:12:36 -06:00
|
|
|
# Done, but need to verify
|
|
|
|
# TODO: add more excluding to SYSTEM__ tokens
|
|
|
|
# TODO: return 200 when returning formatted sillytavern error
|
2023-09-25 00:55:20 -06:00
|
|
|
|
2023-09-27 23:36:44 -06:00
|
|
|
script_path = os.path.dirname(os.path.realpath(__file__))
|
2023-08-21 21:28:52 -06:00
|
|
|
config_path_environ = os.getenv("CONFIG_PATH")
|
|
|
|
if config_path_environ:
|
|
|
|
config_path = config_path_environ
|
|
|
|
else:
|
2023-08-21 23:07:12 -06:00
|
|
|
config_path = Path(script_path, 'config', 'config.yml')
|
2023-08-21 21:28:52 -06:00
|
|
|
|
2023-10-27 19:19:22 -06:00
|
|
|
success, config, msg = load_config(config_path)
|
2023-08-21 21:28:52 -06:00
|
|
|
if not success:
|
2024-05-07 09:48:51 -06:00
|
|
|
logger = logging.getLogger('llm_server')
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
|
logger.error(f'Failed to load config: {msg}')
|
2023-08-21 21:28:52 -06:00
|
|
|
sys.exit(1)
|
|
|
|
|
2023-12-21 14:24:50 -07:00
|
|
|
init_logging(Path(config['webserver_log_directory']) / 'server.log')
|
2024-05-07 09:48:51 -06:00
|
|
|
logger = create_logger('Server')
|
|
|
|
logger.debug('Debug logging enabled.')
|
|
|
|
|
|
|
|
try:
|
|
|
|
import vllm
|
|
|
|
except ModuleNotFoundError as e:
|
|
|
|
logger.error(f'Could not import vllm-gptq: {e}')
|
|
|
|
sys.exit(1)
|
2023-12-21 14:24:50 -07:00
|
|
|
|
|
|
|
while not redis.get('daemon_started', dtype=bool):
|
|
|
|
logger.warning('Could not find the key daemon_started in Redis. Did you forget to start the daemon process?')
|
|
|
|
time.sleep(10)
|
|
|
|
|
|
|
|
logger.info('Started HTTP worker!')
|
|
|
|
|
2024-05-07 09:48:51 -06:00
|
|
|
Database.initialise(maxconn=config['mysql']['maxconn'], host=config['mysql']['host'], user=config['mysql']['username'], password=config['mysql']['password'], database=config['mysql']['database'])
|
2023-09-20 20:30:31 -06:00
|
|
|
create_db()
|
2023-08-21 21:28:52 -06:00
|
|
|
|
2023-12-21 14:24:50 -07:00
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
# Fixes ConcurrentObjectUseError
|
|
|
|
# https://github.com/miguelgrinberg/simple-websocket/issues/24
|
|
|
|
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 25}
|
|
|
|
|
|
|
|
app.register_blueprint(bp, url_prefix='/api/')
|
|
|
|
app.register_blueprint(openai_bp, url_prefix='/api/openai/v1/')
|
|
|
|
app.register_blueprint(openai_model_bp, url_prefix='/api/openai/')
|
|
|
|
init_wssocket(app)
|
|
|
|
flask_cache.init_app(app)
|
|
|
|
flask_cache.clear()
|
|
|
|
|
2023-08-21 21:28:52 -06:00
|
|
|
|
|
|
|
@app.route('/')
|
2023-08-23 23:11:12 -06:00
|
|
|
@app.route('/api')
|
2023-09-14 14:05:50 -06:00
|
|
|
@app.route('/api/openai')
|
2023-09-26 22:09:11 -06:00
|
|
|
@flask_cache.cached(timeout=10)
|
2023-08-23 23:11:12 -06:00
|
|
|
def home():
|
2023-10-27 19:19:22 -06:00
|
|
|
base_client_api = redis.get('base_client_api', dtype=str)
|
2023-08-25 13:53:23 -06:00
|
|
|
stats = generate_stats()
|
2023-10-27 19:19:22 -06:00
|
|
|
model_choices, default_model = get_model_choices()
|
2023-08-23 23:11:12 -06:00
|
|
|
|
2023-10-27 19:19:22 -06:00
|
|
|
if default_model:
|
|
|
|
if not model_choices.get(default_model):
|
|
|
|
return 'The server is still starting up. Please wait...'
|
2023-08-25 13:53:23 -06:00
|
|
|
|
2023-10-27 19:19:22 -06:00
|
|
|
default_model_info = model_choices[default_model]
|
|
|
|
|
|
|
|
if default_model_info['queued'] == 0 and default_model_info['queued'] >= default_model_info['concurrent_gens']:
|
2023-08-25 13:53:23 -06:00
|
|
|
# There will be a wait if the queue is empty but prompts are processing, but we don't
|
|
|
|
# know how long.
|
2023-10-27 19:19:22 -06:00
|
|
|
default_estimated_wait_sec = f"less than {int(default_model_info['estimated_wait'])} seconds"
|
2023-08-25 13:53:23 -06:00
|
|
|
else:
|
2023-10-27 19:19:22 -06:00
|
|
|
default_estimated_wait_sec = f"{int(default_model_info['estimated_wait'])} seconds"
|
|
|
|
else:
|
|
|
|
default_model_info = {
|
|
|
|
'model': 'OFFLINE',
|
|
|
|
'processing': '-',
|
|
|
|
'queued': '-',
|
|
|
|
'context_size': '-',
|
|
|
|
}
|
|
|
|
default_estimated_wait_sec = 'OFFLINE'
|
2023-08-23 23:11:12 -06:00
|
|
|
|
2023-10-30 14:42:50 -06:00
|
|
|
if default_model_info['context_size'] is None:
|
|
|
|
# Sometimes a model doesn't provide the correct config, so the context size is set
|
|
|
|
# to None by the daemon.
|
|
|
|
default_model_info['context_size'] = '-'
|
|
|
|
|
2023-08-23 23:27:33 -06:00
|
|
|
if len(config['analytics_tracking_code']):
|
|
|
|
analytics_tracking_code = f"<script>\n{config['analytics_tracking_code']}\n</script>"
|
|
|
|
else:
|
|
|
|
analytics_tracking_code = ''
|
|
|
|
|
2023-08-24 17:55:55 -06:00
|
|
|
if config['info_html']:
|
2023-09-13 20:40:55 -06:00
|
|
|
info_html = config['info_html']
|
2023-08-24 17:55:55 -06:00
|
|
|
else:
|
|
|
|
info_html = ''
|
|
|
|
|
2023-09-12 01:04:11 -06:00
|
|
|
mode_info = ''
|
2023-10-27 19:19:22 -06:00
|
|
|
for k, v in cluster_config.all().items():
|
|
|
|
if v['mode'] == 'vllm':
|
|
|
|
mode_info = vllm_info
|
|
|
|
break
|
2023-09-17 18:55:36 -06:00
|
|
|
|
2023-08-23 23:11:12 -06:00
|
|
|
return render_template('home.html',
|
2023-09-12 16:40:09 -06:00
|
|
|
llm_middleware_name=opts.llm_middleware_name,
|
2023-08-23 23:27:33 -06:00
|
|
|
analytics_tracking_code=analytics_tracking_code,
|
2023-08-24 17:55:55 -06:00
|
|
|
info_html=info_html,
|
2023-10-27 19:19:22 -06:00
|
|
|
default_model=default_model_info['model'],
|
|
|
|
default_active_gen_workers=default_model_info['processing'],
|
|
|
|
default_proompters_in_queue=default_model_info['queued'],
|
|
|
|
current_model=opts.manual_model_name if opts.manual_model_name else None, # else running_model,
|
2023-09-24 21:45:30 -06:00
|
|
|
client_api=f'https://{base_client_api}',
|
2023-10-27 19:19:22 -06:00
|
|
|
ws_client_api=f'wss://{base_client_api}/v1/stream' if opts.enable_streaming else 'disabled',
|
|
|
|
default_estimated_wait=default_estimated_wait_sec,
|
|
|
|
mode_name=mode_ui_names[opts.frontend_api_mode][0],
|
|
|
|
api_input_textbox=mode_ui_names[opts.frontend_api_mode][1],
|
|
|
|
streaming_input_textbox=mode_ui_names[opts.frontend_api_mode][2],
|
|
|
|
default_context_size=default_model_info['context_size'],
|
2023-08-29 14:00:35 -06:00
|
|
|
stats_json=json.dumps(stats, indent=4, ensure_ascii=False),
|
2023-09-12 01:04:11 -06:00
|
|
|
extra_info=mode_info,
|
2023-09-17 18:55:36 -06:00
|
|
|
openai_client_api=f'https://{base_client_api}/openai/v1' if opts.enable_openi_compatible_backend else 'disabled',
|
2023-09-14 14:05:50 -06:00
|
|
|
expose_openai_system_prompt=opts.expose_openai_system_prompt,
|
|
|
|
enable_streaming=opts.enable_streaming,
|
2023-10-27 19:19:22 -06:00
|
|
|
model_choices=model_choices,
|
|
|
|
proompters_5_min=stats['stats']['proompters']['5_min'],
|
|
|
|
proompters_24_hrs=stats['stats']['proompters']['24_hrs'],
|
2023-08-23 23:11:12 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-10-27 19:19:22 -06:00
|
|
|
@app.route('/robots.txt')
|
|
|
|
def robots():
|
|
|
|
# TODO: have config value to deny all
|
|
|
|
# TODO: https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt
|
|
|
|
t = """User-agent: *
|
|
|
|
Allow: /"""
|
|
|
|
r = Response(t)
|
|
|
|
r.headers['Content-Type'] = 'text/plain'
|
|
|
|
return r
|
|
|
|
|
2023-09-24 15:54:35 -06:00
|
|
|
|
2023-08-21 21:28:52 -06:00
|
|
|
@app.route('/<first>')
|
|
|
|
@app.route('/<first>/<path:rest>')
|
|
|
|
def fallback(first=None, rest=None):
|
|
|
|
return jsonify({
|
2023-08-30 18:53:26 -06:00
|
|
|
'code': 404,
|
2023-08-21 21:28:52 -06:00
|
|
|
'msg': 'not found'
|
|
|
|
}), 404
|
|
|
|
|
|
|
|
|
2023-09-12 01:04:11 -06:00
|
|
|
@app.errorhandler(500)
|
|
|
|
def server_error(e):
|
2023-09-12 10:30:45 -06:00
|
|
|
return handle_server_error(e)
|
2023-09-12 01:04:11 -06:00
|
|
|
|
|
|
|
|
2023-09-17 18:55:36 -06:00
|
|
|
@app.before_request
|
|
|
|
def before_app_request():
|
2023-09-23 23:24:08 -06:00
|
|
|
auto_set_base_client_api(request)
|
2023-09-17 18:55:36 -06:00
|
|
|
|
|
|
|
|
2023-08-21 21:28:52 -06:00
|
|
|
if __name__ == "__main__":
|
2023-12-21 14:24:50 -07:00
|
|
|
# server_startup(None)
|
2023-09-26 22:09:11 -06:00
|
|
|
print('FLASK MODE - Startup complete!')
|
2023-09-14 17:38:20 -06:00
|
|
|
app.run(host='0.0.0.0', threaded=False, processes=15)
|