diff --git a/app.py b/app.py index 7fe71d2..f1ce2cc 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,7 @@ import json import re import math import functools +import urllib.request from flask import Flask, request, jsonify, send_from_directory, session, redirect from flask_cors import CORS from db import get_db, init_db, DB_PATH @@ -600,16 +601,86 @@ def get_models(): db.close() return jsonify(result) +@app.route('/api/models/grouped') +def get_models_grouped(): + db = get_db() + models = db.execute('SELECT * FROM models ORDER BY base_model, sort_order, name').fetchall() + db.close() + grouped = {} + for m in models: + d = dict(m) + base = d['base_model'] + if base not in grouped: + grouped[base] = [] + grouped[base].append(d) + # Get default quant from settings + db2 = get_db() + dq = db2.execute("SELECT value FROM settings WHERE key = 'default_quant'").fetchone() + db2.close() + default_quant = dq['value'] if dq else 'Q4_K_M' + return jsonify({'models': grouped, 'default_quant': default_quant}) + # ----- Parse Natural Language ----- @app.route('/api/parse-nl', methods=['POST']) def parse_nl(): data = request.json text = data.get('text', '') + # Try LLM API first if enabled + db = get_db() + llm_enabled = db.execute("SELECT value FROM settings WHERE key = 'llm_enabled'").fetchone() + if llm_enabled and llm_enabled['value'] == 'true': + llm_url = db.execute("SELECT value FROM settings WHERE key = 'llm_api_url'").fetchone() + llm_key = db.execute("SELECT value FROM settings WHERE key = 'llm_api_key'").fetchone() + llm_model = db.execute("SELECT value FROM settings WHERE key = 'llm_api_model'").fetchone() + llm_prompt = db.execute("SELECT value FROM settings WHERE key = 'llm_system_prompt'").fetchone() + db.close() + url = llm_url['value'] if llm_url else '' + key = llm_key['value'] if llm_key else '' + model = llm_model['value'] if llm_model else '' + system_prompt = llm_prompt['value'] if llm_prompt else '' + if url: + try: + result = call_llm_for_parsing(url, key, model, system_prompt, text) + if result: + return jsonify(result) + except Exception as e: + print(f'LLM parse failed: {e}', file=sys.stderr) + else: + db.close() + # Fallback to regex parsing result = parse_natural_language(text) return jsonify(result) +def call_llm_for_parsing(url, key, model, system_prompt, user_text): + """Call LLM API to parse natural language into params.""" + headers = {'Content-Type': 'application/json'} + if key: + headers['Authorization'] = f'Bearer {key}' + body = { + 'model': model, + 'messages': [ + {'role': 'system', 'content': system_prompt}, + {'role': 'user', 'content': user_text} + ], + 'temperature': 0.1, + 'max_tokens': 2000, + } + req = urllib.request.Request(url, data=json.dumps(body).encode('utf-8'), headers=headers, method='POST') + with urllib.request.urlopen(req, timeout=30) as resp: + data = json.loads(resp.read().decode('utf-8')) + # OpenAI-compatible response + content = data['choices'][0]['message']['content'] + # Try to extract JSON from the response + content = content.strip() + if content.startswith('```'): + content = re.sub(r'^```\w*\n?', '', content) + content = re.sub(r'\n?```$', '', content) + result = json.loads(content) + return result + + # ==================== Admin API ==================== # All admin routes below require login @@ -800,9 +871,9 @@ def admin_add_model(): data = request.json db = get_db() db.execute( - '''INSERT INTO models (name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', - (data['name'], data['size_gb'], data['layers'], data['embd'], + '''INSERT INTO models (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', + (data['base_model'], data['name'], data['size_gb'], data['layers'], data['embd'], data['kv_heads'], data['head_dim'], data['attention_heads'], data.get('quant', ''), data.get('description', ''), data.get('sort_order', 0)) ) @@ -818,9 +889,9 @@ def admin_model_edit(mid): if request.method == 'PUT': data = request.json db.execute( - '''UPDATE models SET name=?, size_gb=?, layers=?, embd=?, kv_heads=?, + '''UPDATE models SET base_model=?, name=?, size_gb=?, layers=?, embd=?, kv_heads=?, head_dim=?, attention_heads=?, quant=?, description=?, sort_order=? WHERE id=?''', - (data['name'], data['size_gb'], data['layers'], data['embd'], + (data['base_model'], data['name'], data['size_gb'], data['layers'], data['embd'], data['kv_heads'], data['head_dim'], data['attention_heads'], data.get('quant', ''), data.get('description', ''), data.get('sort_order', 0), mid) ) diff --git a/db.py b/db.py index 2122509..2a7a0b4 100644 --- a/db.py +++ b/db.py @@ -80,6 +80,7 @@ def init_db(): c.execute(''' CREATE TABLE IF NOT EXISTS models ( id INTEGER PRIMARY KEY AUTOINCREMENT, + base_model TEXT NOT NULL, name TEXT NOT NULL, size_gb REAL NOT NULL, layers INTEGER NOT NULL, @@ -312,44 +313,79 @@ def insert_default_data(conn): (v2_id,) + p) # ===== Default models ===== + # (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order) default_models = [ - ("Llama-3-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, "Q4_K_M", "Meta Llama 3 8B Instruct, Q4_K_M 量化", 1), - ("Llama-3-8B-Instruct (Q8_0)", 8.5, 32, 4096, 8, 128, 32, "Q8_0", "Meta Llama 3 8B Instruct, Q8_0 量化", 2), - ("Llama-3-8B-Instruct (FP16)", 15.5, 32, 4096, 8, 128, 32, "FP16", "Meta Llama 3 8B Instruct, FP16", 3), - ("Llama-3-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, "Q4_K_M", "Meta Llama 3 70B Instruct, Q4_K_M 量化", 4), - ("Llama-3-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, "Q8_0", "Meta Llama 3 70B Instruct, Q8_0 量化", 5), - ("Llama-3-70B-Instruct (FP16)", 138.0, 80, 8192, 8, 128, 64, "FP16", "Meta Llama 3 70B Instruct, FP16", 6), - ("Llama-3.1-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, "Q4_K_M", "Meta Llama 3.1 8B Instruct, Q4_K_M 量化", 7), - ("Llama-3.1-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, "Q4_K_M", "Meta Llama 3.1 70B Instruct, Q4_K_M 量化", 8), - ("Llama-3.1-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, "Q8_0", "Meta Llama 3.1 70B Instruct, Q8_0 量化", 9), - ("Llama-3.1-405B-Instruct (Q4_K_M)", 226.0, 126, 16384, 8, 128, 128, "Q4_K_M", "Meta Llama 3.1 405B Instruct, Q4_K_M 量化", 10), - ("Qwen2.5-7B-Instruct (Q4_K_M)", 4.7, 28, 3584, 4, 128, 28, "Q4_K_M", "Qwen2.5 7B Instruct, Q4_K_M 量化", 11), - ("Qwen2.5-14B-Instruct (Q4_K_M)", 8.7, 40, 5120, 8, 128, 40, "Q4_K_M", "Qwen2.5 14B Instruct, Q4_K_M 量化", 12), - ("Qwen2.5-32B-Instruct (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, "Q4_K_M", "Qwen2.5 32B Instruct, Q4_K_M 量化", 13), - ("Qwen2.5-72B-Instruct (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, "Q4_K_M", "Qwen2.5 72B Instruct, Q4_K_M 量化", 14), - ("Qwen2.5-72B-Instruct (Q8_0)", 75.0, 80, 8192, 8, 128, 64, "Q8_0", "Qwen2.5 72B Instruct, Q8_0 量化", 15), - ("DeepSeek-V2-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, "Q4_K_M", "DeepSeek V2 Chat, Q4_K_M 量化", 16), - ("DeepSeek-V2.5-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, "Q4_K_M", "DeepSeek V2.5 Chat, Q4_K_M 量化", 17), - ("DeepSeek-R1-Distill-Qwen-32B (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, "Q4_K_M", "DeepSeek R1 Distill Qwen 32B, Q4_K_M 量化", 18), - ("DeepSeek-R1-Distill-Llama-70B (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, "Q4_K_M", "DeepSeek R1 Distill Llama 70B, Q4_K_M 量化", 19), - ("Mistral-7B-Instruct-v0.3 (Q4_K_M)", 4.4, 32, 4096, 8, 128, 32, "Q4_K_M", "Mistral 7B Instruct v0.3, Q4_K_M 量化", 20), - ("Mixtral-8x7B-Instruct (Q4_K_M)", 26.0, 32, 4096, 8, 128, 32, "Q4_K_M", "Mixtral 8x7B Instruct, Q4_K_M 量化", 21), - ("Gemma-2-9B-It (Q4_K_M)", 5.4, 42, 3584, 4, 256, 14, "Q4_K_M", "Google Gemma 2 9B It, Q4_K_M 量化", 22), - ("Gemma-2-27B-It (Q4_K_M)", 16.5, 46, 4608, 4, 128, 36, "Q4_K_M", "Google Gemma 2 27B It, Q4_K_M 量化", 23), - ("Phi-3-Mini-4K-Instruct (Q4_K_M)", 2.5, 32, 3072, 32, 96, 32, "Q4_K_M", "Microsoft Phi-3 Mini 4K Instruct, Q4_K_M 量化", 24), - ("Phi-3-Medium-14B-Instruct (Q4_K_M)", 8.4, 40, 5120, 10, 128, 40, "Q4_K_M", "Microsoft Phi-3 Medium 14B Instruct, Q4_K_M 量化", 25), - ("GLM-4-9B-Chat (Q4_K_M)", 5.5, 40, 4096, 4, 128, 40, "Q4_K_M", "Zhipu GLM-4 9B Chat, Q4_K_M 量化", 26), + # Llama-3-8B-Instruct + ("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, "Q4_K_M", "Meta Llama 3 8B Instruct, Q4_K_M 量化", 1), + ("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (Q8_0)", 8.5, 32, 4096, 8, 128, 32, "Q8_0", "Meta Llama 3 8B Instruct, Q8_0 量化", 2), + ("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (FP16)", 15.5, 32, 4096, 8, 128, 32, "FP16", "Meta Llama 3 8B Instruct, FP16", 3), + # Llama-3-70B-Instruct + ("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, "Q4_K_M", "Meta Llama 3 70B Instruct, Q4_K_M 量化", 4), + ("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, "Q8_0", "Meta Llama 3 70B Instruct, Q8_0 量化", 5), + ("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (FP16)", 138.0, 80, 8192, 8, 128, 64, "FP16", "Meta Llama 3 70B Instruct, FP16", 6), + # Llama-3.1-8B-Instruct + ("Llama-3.1-8B-Instruct", "Llama-3.1-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, "Q4_K_M", "Meta Llama 3.1 8B Instruct, Q4_K_M 量化", 7), + ("Llama-3.1-8B-Instruct", "Llama-3.1-8B-Instruct (Q8_0)", 8.5, 32, 4096, 8, 128, 32, "Q8_0", "Meta Llama 3.1 8B Instruct, Q8_0 量化", 8), + # Llama-3.1-70B-Instruct + ("Llama-3.1-70B-Instruct", "Llama-3.1-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, "Q4_K_M", "Meta Llama 3.1 70B Instruct, Q4_K_M 量化", 9), + ("Llama-3.1-70B-Instruct", "Llama-3.1-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, "Q8_0", "Meta Llama 3.1 70B Instruct, Q8_0 量化", 10), + # Llama-3.1-405B-Instruct + ("Llama-3.1-405B-Instruct", "Llama-3.1-405B-Instruct (Q4_K_M)", 226.0, 126, 16384, 8, 128, 128, "Q4_K_M", "Meta Llama 3.1 405B Instruct, Q4_K_M 量化", 11), + # Qwen2.5-7B-Instruct + ("Qwen2.5-7B-Instruct", "Qwen2.5-7B-Instruct (Q4_K_M)", 4.7, 28, 3584, 4, 128, 28, "Q4_K_M", "Qwen2.5 7B Instruct, Q4_K_M 量化", 12), + ("Qwen2.5-7B-Instruct", "Qwen2.5-7B-Instruct (Q8_0)", 7.6, 28, 3584, 4, 128, 28, "Q8_0", "Qwen2.5 7B Instruct, Q8_0 量化", 13), + # Qwen2.5-14B-Instruct + ("Qwen2.5-14B-Instruct", "Qwen2.5-14B-Instruct (Q4_K_M)", 8.7, 40, 5120, 8, 128, 40, "Q4_K_M", "Qwen2.5 14B Instruct, Q4_K_M 量化", 14), + # Qwen2.5-32B-Instruct + ("Qwen2.5-32B-Instruct", "Qwen2.5-32B-Instruct (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, "Q4_K_M", "Qwen2.5 32B Instruct, Q4_K_M 量化", 15), + ("Qwen2.5-32B-Instruct", "Qwen2.5-32B-Instruct (Q8_0)", 32.0, 64, 5120, 8, 128, 64, "Q8_0", "Qwen2.5 32B Instruct, Q8_0 量化", 16), + # Qwen2.5-72B-Instruct + ("Qwen2.5-72B-Instruct", "Qwen2.5-72B-Instruct (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, "Q4_K_M", "Qwen2.5 72B Instruct, Q4_K_M 量化", 17), + ("Qwen2.5-72B-Instruct", "Qwen2.5-72B-Instruct (Q8_0)", 75.0, 80, 8192, 8, 128, 64, "Q8_0", "Qwen2.5 72B Instruct, Q8_0 量化", 18), + # DeepSeek-V2-Chat + ("DeepSeek-V2-Chat", "DeepSeek-V2-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, "Q4_K_M", "DeepSeek V2 Chat, Q4_K_M 量化", 19), + # DeepSeek-V2.5-Chat + ("DeepSeek-V2.5-Chat", "DeepSeek-V2.5-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, "Q4_K_M", "DeepSeek V2.5 Chat, Q4_K_M 量化", 20), + # DeepSeek-R1-Distill-Qwen-32B + ("DeepSeek-R1-Distill-Qwen-32B", "DeepSeek-R1-Distill-Qwen-32B (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, "Q4_K_M", "DeepSeek R1 Distill Qwen 32B, Q4_K_M 量化", 21), + ("DeepSeek-R1-Distill-Qwen-32B", "DeepSeek-R1-Distill-Qwen-32B (Q8_0)", 32.0, 64, 5120, 8, 128, 64, "Q8_0", "DeepSeek R1 Distill Qwen 32B, Q8_0 量化", 22), + # DeepSeek-R1-Distill-Llama-70B + ("DeepSeek-R1-Distill-Llama-70B", "DeepSeek-R1-Distill-Llama-70B (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, "Q4_K_M", "DeepSeek R1 Distill Llama 70B, Q4_K_M 量化", 23), + ("DeepSeek-R1-Distill-Llama-70B", "DeepSeek-R1-Distill-Llama-70B (Q8_0)", 75.0, 80, 8192, 8, 128, 64, "Q8_0", "DeepSeek R1 Distill Llama 70B, Q8_0 量化", 24), + # Mistral-7B-Instruct-v0.3 + ("Mistral-7B-Instruct-v0.3", "Mistral-7B-Instruct-v0.3 (Q4_K_M)", 4.4, 32, 4096, 8, 128, 32, "Q4_K_M", "Mistral 7B Instruct v0.3, Q4_K_M 量化", 25), + ("Mistral-7B-Instruct-v0.3", "Mistral-7B-Instruct-v0.3 (Q8_0)", 7.5, 32, 4096, 8, 128, 32, "Q8_0", "Mistral 7B Instruct v0.3, Q8_0 量化", 26), + # Mixtral-8x7B-Instruct + ("Mixtral-8x7B-Instruct", "Mixtral-8x7B-Instruct (Q4_K_M)", 26.0, 32, 4096, 8, 128, 32, "Q4_K_M", "Mixtral 8x7B Instruct, Q4_K_M 量化", 27), + # Gemma-2-9B-It + ("Gemma-2-9B-It", "Gemma-2-9B-It (Q4_K_M)", 5.4, 42, 3584, 4, 256, 14, "Q4_K_M", "Google Gemma 2 9B It, Q4_K_M 量化", 28), + # Gemma-2-27B-It + ("Gemma-2-27B-It", "Gemma-2-27B-It (Q4_K_M)", 16.5, 46, 4608, 4, 128, 36, "Q4_K_M", "Google Gemma 2 27B It, Q4_K_M 量化", 29), + # Phi-3-Mini-4K-Instruct + ("Phi-3-Mini-4K-Instruct", "Phi-3-Mini-4K-Instruct (Q4_K_M)", 2.5, 32, 3072, 32, 96, 32, "Q4_K_M", "Microsoft Phi-3 Mini 4K Instruct, Q4_K_M 量化", 30), + # Phi-3-Medium-14B-Instruct + ("Phi-3-Medium-14B-Instruct", "Phi-3-Medium-14B-Instruct (Q4_K_M)", 8.4, 40, 5120, 10, 128, 40, "Q4_K_M", "Microsoft Phi-3 Medium 14B Instruct, Q4_K_M 量化", 31), + # GLM-4-9B-Chat + ("GLM-4-9B-Chat", "GLM-4-9B-Chat (Q4_K_M)", 5.5, 40, 4096, 4, 128, 40, "Q4_K_M", "Zhipu GLM-4 9B Chat, Q4_K_M 量化", 32), + ("GLM-4-9B-Chat", "GLM-4-9B-Chat (Q8_0)", 9.0, 40, 4096, 4, 128, 40, "Q8_0", "Zhipu GLM-4 9B Chat, Q8_0 量化", 33), ] for m in default_models: - c.execute('''INSERT INTO models (name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', m) + c.execute('''INSERT INTO models (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', m) # ===== Default settings ===== c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("admin_password", "admin123")) c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("default_gpu", "RTX 3090")) c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("default_version", "b10068")) c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("default_mode", "gpu")) + c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("default_quant", "Q4_K_M")) + # LLM API settings for natural language parsing + c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_enabled", "false")) + c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_api_url", "")) + c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_api_key", "")) + c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_api_model", "")) + c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_system_prompt", "你是一个llama.cpp命令行参数解析助手。用户会用自然语言描述他想运行的模型和参数配置,你需要将其解析为JSON格式的参数。\n\n可用的参数键包括: model, ctx_size, n_gpu_layers, threads, batch_size, temperature, top_k, top_p, flash_attn, port, host, parallel, split_mode, mlock, numa, repeat_penalty, presence_penalty, frequency_penalty, seed, min_p, typical, mirostat, mirostat_lr, mirostat_ent。\n\nGPU型号会通过 _gpu_name 字段返回,GPU数量通过 _gpu_count 返回。\n\n只返回JSON,不要其他文本。")) conn.commit() diff --git a/static/admin.html b/static/admin.html index 00c51d8..486b870 100644 --- a/static/admin.html +++ b/static/admin.html @@ -123,7 +123,8 @@
| ID | 名称 | 大小(GB) | 层数 | EMBD | KV | HD | Heads | 量化 | 排序 | 操作 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| ID | 基模型 | 名称 | 大小(GB) | 层数 | EMBD | KV | HD | Heads | 量化 | 排序 | 操作 |