- 训练脚本: train_v2.py + llm_chat_improved.py - API生成: api_correction.py - 高温多样化: regenerate_all_high_temp.py - 每日数据生成: daily_data_gen.py - 数据: xiaoli_train_v2.json(40776条) + vocab_v2.json - 蒸馏问题: exam_distill_questions等 - Web服务: web_server.py + chat.html - 新增 .gitignore 排除旧版文件
128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""小狸v1.2 Web聊天服务器 - 当前训练中的模型"""
|
|
import sys, os, json, time
|
|
sys.path.insert(0, '/workspace/xiaoli-llm-chat')
|
|
import torch
|
|
from flask import Flask, request, jsonify, send_from_directory
|
|
from llm_chat_improved import LLMModel, LLMChat, build_vocabulary
|
|
|
|
print("=" * 50)
|
|
print(" 小狸 v1.2 Web 服务加载中...")
|
|
print("=" * 50)
|
|
|
|
device = 'cpu'
|
|
model_dir = '/workspace/xiaoli-llm-chat'
|
|
|
|
# 构建当前vocab
|
|
with open(f'{model_dir}/xiaoli_merged_data_v2.json', 'r', encoding='utf-8') as f:
|
|
all_data = json.load(f)
|
|
pairs = []
|
|
for d in all_data:
|
|
q = d.get('question','') or d.get('input','') or d.get('prompt','') or ''
|
|
a = d.get('answer','') or d.get('output','') or d.get('response','') or ''
|
|
if q and a:
|
|
pairs.append((q, a))
|
|
vocab = build_vocabulary(pairs)
|
|
print(f"词汇表: {len(vocab)} | 数据: {len(all_data)}条")
|
|
|
|
# 加载模型 (epoch 50 = 干净epoch 41 + vocab扩展)
|
|
ckpt_path = f'{model_dir}/llm_model_v2_resume_epoch_50.pth'
|
|
print(f"加载: {ckpt_path}")
|
|
ckpt = torch.load(ckpt_path, map_location=device)
|
|
old_vocab_size = ckpt.get('model_config', {}).get('vocab_size', 0)
|
|
cfg = ckpt['model_config']
|
|
print(f"断点vocab: {old_vocab_size}")
|
|
|
|
model = LLMModel(
|
|
vocab_size=len(vocab),
|
|
d_model=cfg['d_model'], n_heads=cfg['n_heads'],
|
|
n_layers=cfg['n_layers'], d_ff=cfg['d_ff'],
|
|
max_len=cfg['max_len'], dropout=cfg.get('dropout', 0.1),
|
|
use_memory=False, use_thinking=False, use_moe=False,
|
|
num_experts=1, top_k=1,
|
|
)
|
|
|
|
# 迁移权重
|
|
old_sd = ckpt['model_state_dict']
|
|
new_sd = model.state_dict()
|
|
if old_vocab_size != len(vocab):
|
|
for key in new_sd:
|
|
if key in old_sd:
|
|
w = old_sd[key]
|
|
if w.shape == new_sd[key].shape:
|
|
new_sd[key] = w.clone()
|
|
elif len(w.shape) == 2 and w.shape[0] == old_vocab_size:
|
|
new_sd[key][:old_vocab_size] = w
|
|
elif 'freq' in key:
|
|
pass
|
|
else:
|
|
try: new_sd[key] = w.clone()
|
|
except: pass
|
|
model.load_state_dict(new_sd)
|
|
else:
|
|
model.load_state_dict(old_sd)
|
|
|
|
model.to(device)
|
|
model.eval()
|
|
chat = LLMChat(model, vocab, device, max_length=cfg['max_len'])
|
|
print(f"✅ 模型加载完成! | voc={len(vocab)} | max_len={cfg['max_len']}")
|
|
|
|
app = Flask(__name__)
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return send_from_directory(model_dir, 'chat.html')
|
|
|
|
@app.route('/api/chat', methods=['POST'])
|
|
def api_chat():
|
|
data = request.get_json()
|
|
question = data.get('question', '').strip()
|
|
temp = float(data.get('temperature', 0.2))
|
|
max_len = int(data.get('max_length', 100))
|
|
history = data.get('history', None)
|
|
|
|
if not question:
|
|
return jsonify({'error': '问题不能为空'})
|
|
|
|
# 如果有history,转换为对话格式
|
|
history_pairs = None
|
|
if history and isinstance(history, list) and len(history) > 0:
|
|
history_pairs = [(h['q'], h['a']) for h in history]
|
|
|
|
try:
|
|
t0 = time.time()
|
|
answer = chat.generate_response(question, max_length=max_len, temperature=temp, history=history_pairs)
|
|
elapsed = time.time() - t0
|
|
return jsonify({
|
|
'question': question,
|
|
'answer': answer,
|
|
'time': f'{elapsed:.1f}s',
|
|
'temperature': temp,
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)})
|
|
|
|
@app.route('/api/info')
|
|
def model_info():
|
|
return jsonify({
|
|
'name': '小狸 v1.2',
|
|
'version': 'epoch 52 (架构扩展: 7.9M→21.3M, max_len=256)',
|
|
'vocab_size': len(vocab),
|
|
'max_length': cfg['max_len'],
|
|
'd_model': cfg['d_model'],
|
|
'layers': cfg['n_layers'],
|
|
'heads': cfg['n_heads'],
|
|
'd_ff': cfg['d_ff'],
|
|
'data_size': f'{len(all_data):,}条',
|
|
'training': '新架构训练中 epoch 1→200',
|
|
'features': ['多轮对话(1,694条)', '长文本(758条)', '我不知道(1,140条)'],
|
|
})
|
|
|
|
if __name__ == '__main__':
|
|
print("\n" + "=" * 50)
|
|
print(" 小狸 v1.2 聊天服务已启动!")
|
|
print(" 访问地址: http://0.0.0.0:5000")
|
|
print(" 建议温度: 0.1 ~ 0.2")
|
|
print("=" * 50)
|
|
app.run(host='0.0.0.0', port=5000, debug=False, threaded=True) |