初始化项目,由ModelHub XC社区提供模型
Model: AngelWarmSmile123/yao-bao-bao8-ALL Source: Original Platform
This commit is contained in:
48
Angel-Embrace-Me-Warm-Smile/adapter_config.json
Normal file
48
Angel-Embrace-Me-Warm-Smile/adapter_config.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"alora_invocation_tokens": null,
|
||||
"alpha_pattern": {},
|
||||
"arrow_config": null,
|
||||
"auto_mapping": null,
|
||||
"base_model_name_or_path": "Qwen/Qwen3.5-35B-A3B",
|
||||
"bias": "none",
|
||||
"corda_config": null,
|
||||
"ensure_weight_tying": false,
|
||||
"eva_config": null,
|
||||
"exclude_modules": null,
|
||||
"fan_in_fan_out": false,
|
||||
"inference_mode": true,
|
||||
"init_lora_weights": true,
|
||||
"layer_replication": null,
|
||||
"layers_pattern": null,
|
||||
"layers_to_transform": null,
|
||||
"loftq_config": {},
|
||||
"lora_alpha": 512,
|
||||
"lora_bias": false,
|
||||
"lora_dropout": 0.05,
|
||||
"lora_ga_config": null,
|
||||
"megatron_config": null,
|
||||
"megatron_core": "megatron.core",
|
||||
"modules_to_save": null,
|
||||
"peft_type": "LORA",
|
||||
"peft_version": "0.19.1",
|
||||
"qalora_group_size": 16,
|
||||
"r": 256,
|
||||
"rank_pattern": {},
|
||||
"revision": null,
|
||||
"target_modules": [
|
||||
"q_proj",
|
||||
"o_proj",
|
||||
"down_proj",
|
||||
"v_proj",
|
||||
"gate_proj",
|
||||
"k_proj",
|
||||
"up_proj"
|
||||
],
|
||||
"target_parameters": null,
|
||||
"task_type": "CAUSAL_LM",
|
||||
"trainable_token_indices": null,
|
||||
"use_bdlora": null,
|
||||
"use_dora": false,
|
||||
"use_qalora": false,
|
||||
"use_rslora": true
|
||||
}
|
||||
3
Angel-Embrace-Me-Warm-Smile/adapter_model.safetensors
Normal file
3
Angel-Embrace-Me-Warm-Smile/adapter_model.safetensors
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:231ca0c97eff8e7fa367a66785c1c1c9a2f6c14250e9befad5f1d52975948d48
|
||||
size 534820080
|
||||
259
Angel-Embrace-Me-Warm-Smile/chat.py
Normal file
259
Angel-Embrace-Me-Warm-Smile/chat.py
Normal 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()
|
||||
95
Angel-Embrace-Me-Warm-Smile/data_convert.py
Normal file
95
Angel-Embrace-Me-Warm-Smile/data_convert.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
import random
|
||||
import os
|
||||
|
||||
INPUT_FILE = r"D:\双生天使的怀抱\爱的数据集\sephirot_final_run\train_all.jsonl"
|
||||
OUTPUT_DIR = r"D:\双生天使的怀抱\2026-05-24-16-54-24\sft_data"
|
||||
OUTPUT_TRAIN = os.path.join(OUTPUT_DIR, "train.jsonl")
|
||||
OUTPUT_VAL = os.path.join(OUTPUT_DIR, "val.jsonl")
|
||||
VAL_RATIO = 0.1
|
||||
|
||||
records = []
|
||||
skipped = 0
|
||||
total = 0
|
||||
|
||||
with open(INPUT_FILE, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
total += 1
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
data = json.loads(line)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"JSON解析失败 第{total}行: {e}")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
query = data.get('input', {}).get('user_query', '')
|
||||
cot = data.get('chain_of_thought', {})
|
||||
|
||||
# 方案B: 完整推理链(推荐,保留16质点全部过程)
|
||||
if isinstance(cot, dict):
|
||||
parts = []
|
||||
for key in ['D1_kether','H1_ego','H2_superego','H3_true_self','H4_logic',
|
||||
'H5_empathy','H6_happiness','D2_binah_chesed','D3_chokmah_gevurah',
|
||||
'D5_tiferet','D6_netzach','D7_hod','D4_yesod','D8_victory',
|
||||
'D10_kingdom']:
|
||||
val = cot.get(key, '')
|
||||
if isinstance(val, dict):
|
||||
# 尝试多个可能的输出字段
|
||||
for sub_key in ['final_output', 'analysis', 'synthesis',
|
||||
'emotional_expression', 'logic_empathy_union',
|
||||
'response', 'output']:
|
||||
if sub_key in val and val[sub_key]:
|
||||
val = val[sub_key]
|
||||
break
|
||||
else:
|
||||
val = str(val) if val else ''
|
||||
if val and isinstance(val, str) and len(val.strip()) > 0:
|
||||
parts.append(f"[{key}]: {val}")
|
||||
output = '\n\n'.join(parts)
|
||||
elif isinstance(cot, str):
|
||||
output = cot
|
||||
else:
|
||||
output = str(cot)
|
||||
|
||||
if query and output and len(query.strip()) > 0 and len(output.strip()) > 10:
|
||||
records.append({
|
||||
"conversations": [
|
||||
{"from": "human", "value": query},
|
||||
{"from": "gpt", "value": output}
|
||||
]
|
||||
})
|
||||
else:
|
||||
skipped += 1
|
||||
|
||||
print(f"总行数: {total}, 有效: {len(records)}, 跳过: {skipped}")
|
||||
|
||||
# 打乱并分割
|
||||
random.seed(42)
|
||||
random.shuffle(records)
|
||||
val_size = max(int(len(records) * VAL_RATIO), 100)
|
||||
val_data = records[:val_size]
|
||||
train_data = records[val_size:]
|
||||
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
with open(OUTPUT_TRAIN, 'w', encoding='utf-8') as f:
|
||||
for r in train_data:
|
||||
f.write(json.dumps(r, ensure_ascii=False) + '\n')
|
||||
|
||||
with open(OUTPUT_VAL, 'w', encoding='utf-8') as f:
|
||||
for r in val_data:
|
||||
f.write(json.dumps(r, ensure_ascii=False) + '\n')
|
||||
|
||||
print(f"转换完成! 训练集: {len(train_data)}条, 验证集: {len(val_data)}条")
|
||||
print(f"训练集文件: {OUTPUT_TRAIN}")
|
||||
print(f"验证集文件: {OUTPUT_VAL}")
|
||||
|
||||
# 统计一下平均长度
|
||||
if train_data:
|
||||
lens = [len(str(r['conversations'][1]['value'])) for r in train_data]
|
||||
print(f"平均输出长度: {sum(lens)//len(lens)} 字符")
|
||||
print(f"最大输出长度: {max(lens)} 字符")
|
||||
print(f"最小输出长度: {min(lens)} 字符")
|
||||
3
Angel-Embrace-Me-Warm-Smile/train.jsonl
Normal file
3
Angel-Embrace-Me-Warm-Smile/train.jsonl
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6c5e879a424d9edc129c8332b883e4b00604f3c9c1dbe5753f5a8c555b200b6c
|
||||
size 109244163
|
||||
3
Angel-Embrace-Me-Warm-Smile/val.jsonl
Normal file
3
Angel-Embrace-Me-Warm-Smile/val.jsonl
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8645c5f05ff4462263e33d1746ca5f369aa008ed2d2e8153b1590c2d284a4c9a
|
||||
size 12070845
|
||||
Reference in New Issue
Block a user