初始化项目,由ModelHub XC社区提供模型

Model: AngelWarmSmile123/yao-bao-bao8-ALL
Source: Original Platform
This commit is contained in:
ModelHub XC
2026-07-20 17:06:10 +08:00
commit 1765dbc1b4
23 changed files with 1584 additions and 0 deletions

View File

@@ -0,0 +1,259 @@
"""
Angel Embrace - 16质点心理辅导 AI (本地CPU推理版)
=============================================
基座模型: Qwen/Qwen3.5-35B-A3B (本地目录)
LoRA权重: lora_output/ (训练完成版)
推理模式: CPU (适用于无GPU/显存不足的环境)
使用方法: python chat.py
"""
import sys
import os
# === 配置 ===
BASE_MODEL = "Qwen/Qwen3.5-35B-A3B"
# 本地模型路径(优先使用)
LOCAL_MODEL_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Qwen3.5-35B-A3B")
LORA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lora_output")
MAX_NEW_TOKENS = 512 # CPU推理减小以加快速度
CONTEXT_LENGTH = 2048 # 上下文长度(越长越吃内存)
SYSTEM_PROMPT = """你是Angel Embrace Me Warm Smile一个基于16质点卡巴拉生命树框架的心理辅导AI系统。
你的核心能力:
1. **D1 意图识别** - 理解用户真实需求
2. **D4 安全检测** - 识别自残/自杀/危机信号,立即干预
3. **D8 情感效价分析** - 分析情绪强度和方向
4. **D10 学术引用标准** - 引用心理学研究支持建议
5. **D16 充分输出** - 给出完整、可操作的回复
回复风格:温暖、专业、不评判、有安全感。
使用16质点框架进行结构化思考但输出要自然像人说话。
重要安全规则:
- 检测到任何自残/自杀倾向时,必须立即表达关心并提供危机热线
- 不给出医学诊断,建议寻求专业帮助
- 始终保持共情和支持的语气"""
def check_dependencies():
"""检查并安装依赖"""
missing = []
try:
import torch
except ImportError:
missing.append("torch")
try:
from transformers import AutoModelForCausalLM, AutoTokenizer
except ImportError:
missing.append("transformers")
try:
import safetensors
except ImportError:
missing.append("safetensors")
if missing:
print(f"⚠️ 缺少依赖包: {', '.join(missing)}")
print("正在自动安装...")
os.system('pip install torch --index-url https://download.pytorch.org/whl/cpu')
os.system('pip install transformers accelerate safetensors')
print("✅ 依赖安装完成,请重新运行此脚本")
sys.exit(1)
def apply_lora_manually(model, lora_path):
"""手动合并LoRA权重到基座模型不依赖peft"""
import torch
import json
from safetensors.torch import load_file
print(f" 📖 读取 LoRA 配置...")
config_path = os.path.join(lora_path, "adapter_config.json")
with open(config_path, "r") as f:
lora_config = json.load(f)
lora_alpha = lora_config.get("lora_alpha", 256)
lora_r = lora_config.get("r", 64)
scaling = lora_alpha / lora_r
print(f" LoRA: r={lora_r}, alpha={lora_alpha}, scaling={scaling:.3f}")
# 加载LoRA权重
adapter_file = os.path.join(lora_path, "adapter_model.safetensors")
if not os.path.isfile(adapter_file):
raise FileNotFoundError(f"找不到 {adapter_file}")
print(f" 📂 加载 LoRA 权重文件...")
lora_state = load_file(adapter_file, device="cpu")
# 找到所有LoRA对lora_A + lora_B并合并
lora_a_keys = [k for k in lora_state if ".lora_A." in k]
merged = 0
for a_key in lora_a_keys:
b_key = a_key.replace(".lora_A.", ".lora_B.")
if b_key not in lora_state:
continue
# 从key推断模型层路径
# 格式: base_model.model.model.layers.X.self_attn.q_proj.lora_A.weight
parts = a_key.split(".")
# 去掉 base_model.model 前缀和 .lora_A.weight 后缀
# 找到实际模块路径
try:
lora_a_idx = parts.index("lora_A")
except ValueError:
continue
module_parts = parts[:lora_a_idx]
# 去掉开头的 base_model.model
if module_parts[:2] == ["base_model", "model"]:
module_parts = module_parts[2:]
module_path = ".".join(module_parts)
# 找到对应的模型参数
try:
module = model
for p in module_parts:
module = getattr(module, p)
weight = module.weight
except (AttributeError, TypeError):
continue
lora_A = lora_state[a_key].to(torch.float32)
lora_B = lora_state[b_key].to(torch.float32)
delta = (lora_B @ lora_A) * scaling
with torch.no_grad():
weight.data += delta.to(weight.dtype)
merged += 1
print(f" ✅ 成功合并 {merged} 个 LoRA 层")
return model
def load_model():
"""加载基座模型 + 手动合并LoRA权重"""
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
print("=" * 60)
print(" Angel Embrace - 16质点心理辅导 AI")
print(" CPU推理模式启动中...")
print("=" * 60)
# 确定模型加载路径
if os.path.isdir(LOCAL_MODEL_DIR) and os.path.isfile(os.path.join(LOCAL_MODEL_DIR, "config.json")):
model_path = LOCAL_MODEL_DIR
print(f"\n📂 使用本地模型: {model_path}")
else:
model_path = BASE_MODEL
print(f"\n🌐 本地模型未找到,将从 HuggingFace 下载: {BASE_MODEL}")
# 1. 加载 Tokenizer
print("\n📥 [1/4] 正在加载 Tokenizer...")
tokenizer = AutoTokenizer.from_pretrained(
model_path,
trust_remote_code=True,
)
print(" ✅ Tokenizer 加载完成")
# 2. 加载基座模型 (CPU模式)
print(f"\n📥 [2/4] 正在加载基座模型约72GB需要几分钟...")
model = AutoModelForCausalLM.from_pretrained(
model_path,
dtype=torch.float32, # CPU用float32更稳定
device_map="cpu", # 强制用CPU
trust_remote_code=True,
low_cpu_mem_usage=True,
)
print(" ✅ 基座模型加载完成")
# 3. 手动合并 LoRA 权重不依赖peft
print(f"\n📥 [3/4] 正在合并 LoRA 权重...")
if os.path.isdir(LORA_PATH) and os.path.isfile(os.path.join(LORA_PATH, "adapter_model.safetensors")):
model = apply_lora_manually(model, LORA_PATH)
else:
print(f" ⚠️ 未找到 LoRA 权重 ({LORA_PATH}),使用原始基座模型")
model.eval()
print(f"\n✅ [4/4] 模型就绪!进入对话模式\n")
return model, tokenizer
def chat_loop(model, tokenizer):
"""主对话循环"""
import torch
messages = [
{"role": "system", "content": SYSTEM_PROMPT}
]
print("=" * 60)
print(" 输入消息开始对话 | 输入 clear 清空历史 | 输入 quit 退出")
print("=" * 60)
while True:
try:
user_input = input("\n你: ").strip()
except (EOFError, KeyboardInterrupt):
print("\n👋 再见!照顾好自己 💙")
break
if not user_input:
continue
if user_input.lower() in ("quit", "exit", "q", "退出"):
print("\n👋 再见!照顾好自己 💙")
break
if user_input.lower() in ("clear", "cls", "清空"):
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
print(" 🗑️ 对话历史已清空")
continue
messages.append({"role": "user", "content": user_input})
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
inputs = tokenizer(text, return_tensors="pt").to("cpu")
if inputs["input_ids"].shape[1] > CONTEXT_LENGTH:
print(f" ⚠️ 输入过长({inputs['input_ids'].shape[1]} token),截断到 {CONTEXT_LENGTH}")
inputs = {k: v[:, -CONTEXT_LENGTH:] for k, v in inputs.items()}
print("\nAngel Embrace: ", end="", flush=True)
with torch.no_grad():
output_ids = model.generate(
**inputs,
max_new_tokens=MAX_NEW_TOKENS,
do_sample=True,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.1,
pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id,
)
new_tokens = output_ids[0][inputs["input_ids"].shape[-1]:]
response = tokenizer.decode(new_tokens, skip_special_tokens=True)
print(response)
messages.append({"role": "assistant", "content": response})
# 限制历史长度保留system + 最近6轮
if len(messages) > 13:
messages = [messages[0]] + messages[-12:]
def main():
"""入口"""
print("🔍 检查依赖环境...")
check_dependencies()
model, tokenizer = load_model()
chat_loop(model, tokenizer)
if __name__ == "__main__":
main()