Spaces:
Running
fix: v3 comprehensive defect repair
Browse filesFixes from AI audit report (20 defects, 14 fixed):
F1: Unified GQA config - num_key_value_heads=8 in config.json (was 32)
F3: Added missing sbla_mode, hidden_act, attention_dropout to config.json
S1: Removed redundant fusion-config-8b.json (3 configs -> 1)
S3: Moved debug/script files to scripts/, out of tests/ and root/
S5: README 256K -> 32K (expandable to 256K with RoPE scaling)
M1: Fixed mini_data.json think_rank distribution (was all 0, now 0:89,1:3,2:8,3:8)
M3: Added train_tokenizer.py script for SentencePiece .model generation
M5: Renamed test_sbbla -> test_sbla, moved fix_* from tests/ to scripts/
L1: Added bitsandbytes, ollama, scipy, scikit-learn to requirements.txt
L2: Removed Push-ToGitHub.ps1 and push_to_github.py (duplicate tooling)
L5: Fixed mini config block_size==window_size (32 vs 128), sbla_mode=mixed
New modules:
- models/tokenizer.py: Unified tokenizer management (GPT2 placeholder + Fusion SP)
- scripts/train_tokenizer.py: SentencePiece tokenizer training
- scripts/fix_mini_data.py: Fix think_rank distribution
- scripts/add_depth3_samples.py: Add depth=3 samples
Also:
- Deleted ollama_deploy_fixed.py, fusion_model_fixed.py (intermediate artifacts)
- Added M4 TODO in FusionAttention (unify with sbla_attention.py)
- Updated train/full_finetune.py to use models.tokenizer module
- Push-ToGitHub.ps1 +0 -149
- README.md +2 -2
- config.json +14 -11
- configs/fusion-mini-config.json +9 -9
- data/mini_data.json +51 -11
- data_pipeline/t_kd_distillation_train.py +549 -0
- fusion-config-8b.json +0 -42
- inference/ollama_deploy_v2.py +522 -0
- models/fusion_model.py +50 -12
- models/tokenizer.py +146 -0
- push_to_github.py +0 -228
- requirements.txt +7 -2
- scripts/add_depth3_samples.py +42 -0
- {tests → scripts}/create_mini_data.py +11 -1
- debug_attn.py → scripts/debug_attn.py +0 -0
- debug_layer.py → scripts/debug_layer.py +0 -0
- debug_lm.py → scripts/debug_lm.py +0 -0
- debug_loss.py → scripts/debug_loss.py +0 -0
- debug_mask.py → scripts/debug_mask.py +0 -0
- scripts/fix_mini_data.py +61 -0
- {tests → scripts}/fix_thinking_dial.py +0 -0
- {tests → scripts}/fix_thinking_dial2.py +0 -0
- scripts/train_tokenizer.py +155 -0
- test_train_loop.py +0 -38
- test_training.py +0 -38
- tests/debug_attn.py +0 -74
- tests/debug_layer.py +0 -63
- tests/debug_lm.py +0 -54
- tests/debug_loss.py +0 -63
- tests/debug_mask.py +0 -68
- test_sblla_integration.py → tests/test_sbla_integration.py +0 -0
- tests/test_sblla_integration.py +0 -59
- train/full_finetune.py +9 -7
|
@@ -1,149 +0,0 @@
|
|
| 1 |
-
# Fusion 项目 GitHub 推送脚本(本地执行)
|
| 2 |
-
# 作者:朱子瞻
|
| 3 |
-
# 项目:Fusion - 六边形开源大模型
|
| 4 |
-
|
| 5 |
-
Write-Host "=" * 60 -ForegroundColor Cyan
|
| 6 |
-
Write-Host "Fusion 项目 GitHub 推送脚本" -ForegroundColor Cyan
|
| 7 |
-
Write-Host "=" * 60
|
| 8 |
-
|
| 9 |
-
# 1. 检查 Git
|
| 10 |
-
Write-Host "`n🔍 检查 Git..." -ForegroundColor Yellow
|
| 11 |
-
try {
|
| 12 |
-
$gitVersion = git --version
|
| 13 |
-
Write-Host "✅ Git 已安装:$gitVersion" -ForegroundColor Green
|
| 14 |
-
} catch {
|
| 15 |
-
Write-Host "❌ Git 未安装,请先安装 Git" -ForegroundColor Red
|
| 16 |
-
exit 1
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
# 2. 进入项目目录
|
| 20 |
-
$projectDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
| 21 |
-
Set-Location $projectDir
|
| 22 |
-
Write-Host "`n📂 项目目录:$projectDir" -ForegroundColor Yellow
|
| 23 |
-
|
| 24 |
-
# 3. 检查 Git 状态
|
| 25 |
-
Write-Host "`n🔍 检查 Git 状态..." -ForegroundColor Yellow
|
| 26 |
-
$status = git status --porcelain
|
| 27 |
-
if ($status) {
|
| 28 |
-
Write-Host "⚠️ 有未提交的更改" -ForegroundColor Yellow
|
| 29 |
-
git status
|
| 30 |
-
|
| 31 |
-
$commit = Read-Host "`n是否提交更改?(Y/N)"
|
| 32 |
-
if ($commit -eq 'Y' -or $commit -eq 'y') {
|
| 33 |
-
$msg = Read-Host "输入提交信息(默认:Update)"
|
| 34 |
-
if (-not $msg) { $msg = "Update" }
|
| 35 |
-
|
| 36 |
-
git add .
|
| 37 |
-
git commit -m $msg
|
| 38 |
-
Write-Host "✅ 已提交" -ForegroundColor Green
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
# 4. 创建 GitHub 仓库
|
| 43 |
-
Write-Host "`n📦 创建 GitHub 仓库..." -ForegroundColor Yellow
|
| 44 |
-
Write-Host " 仓库名:fusion-llm"
|
| 45 |
-
Write-Host " 描述:Fusion - 六边形开源大模型"
|
| 46 |
-
Write-Host "`n🔐 请输入 GitHub Personal Access Token"
|
| 47 |
-
Write-Host " 创建地址:<ADDRESS_REMOVED>
|
| 48 |
-
Write-Host " 需要权限:repo(全选)`n"
|
| 49 |
-
|
| 50 |
-
$token = Read-Host "Token" -AsSecureString
|
| 51 |
-
$tokenPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
| 52 |
-
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($token)
|
| 53 |
-
)
|
| 54 |
-
|
| 55 |
-
if (-not $tokenPlain) {
|
| 56 |
-
Write-Host "❌ Token 不能为空" -ForegroundColor Red
|
| 57 |
-
exit 1
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
# 调用 GitHub API 创建仓库
|
| 61 |
-
$headers = @{
|
| 62 |
-
"Authorization" = "token $tokenPlain"
|
| 63 |
-
"Accept" = "application/vnd.github.v3+json"
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
$body = @{
|
| 67 |
-
"name" = "fusion-llm"
|
| 68 |
-
"description" = "Fusion - 六边形开源大模型 | 集百家之长,铸最强开源模型"
|
| 69 |
-
"private" = $false
|
| 70 |
-
"has_issues" = $true
|
| 71 |
-
"has_projects" = $true
|
| 72 |
-
"has_wiki" = $true
|
| 73 |
-
"auto_init" = $false
|
| 74 |
-
} | ConvertTo-Json
|
| 75 |
-
|
| 76 |
-
try {
|
| 77 |
-
$response = Invoke-RestMethod -Uri "https://api.github.com/user/repos" `
|
| 78 |
-
-Method Post `
|
| 79 |
-
-Headers $headers `
|
| 80 |
-
-Body $body `
|
| 81 |
-
-ContentType "application/json"
|
| 82 |
-
|
| 83 |
-
$repoUrl = $response.html_url
|
| 84 |
-
$cloneUrl = $response.clone_url
|
| 85 |
-
|
| 86 |
-
Write-Host "`n✅ 仓库创建成功!" -ForegroundColor Green
|
| 87 |
-
Write-Host " URL: $repoUrl" -ForegroundColor Cyan
|
| 88 |
-
Write-Host " Clone URL: $cloneUrl" -ForegroundColor Cyan
|
| 89 |
-
|
| 90 |
-
} catch {
|
| 91 |
-
if ($_.Exception.Response.StatusCode -eq 422) {
|
| 92 |
-
Write-Host "`n⚠️ 仓库 fusion-llm 已存在" -ForegroundColor Yellow
|
| 93 |
-
$cloneUrl = "https://github.com/zhan1206/fusion-llm.git"
|
| 94 |
-
} else {
|
| 95 |
-
Write-Host "`n❌ 创建失败:$($_.Exception.Message)" -ForegroundColor Red
|
| 96 |
-
Write-Host " 请检查 Token 权限" -ForegroundColor Yellow
|
| 97 |
-
exit 1
|
| 98 |
-
}
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
# 5. 推送代码
|
| 102 |
-
Write-Host "`n🚀 推送代码到 GitHub..." -ForegroundColor Yellow
|
| 103 |
-
|
| 104 |
-
# 移除已存在的 remote
|
| 105 |
-
git remote remove origin 2>$null
|
| 106 |
-
|
| 107 |
-
# 添加 remote(使用 HTTPS + Token)
|
| 108 |
-
$tokenWithAuth = $tokenPlain
|
| 109 |
-
$remoteUrl = $cloneUrl -replace "https://", "https://${tokenWithAuth}@"
|
| 110 |
-
git remote add origin $remoteUrl
|
| 111 |
-
|
| 112 |
-
# 推送
|
| 113 |
-
Write-Host " 推送分支:master" -ForegroundColor Yellow
|
| 114 |
-
try {
|
| 115 |
-
$pushResult = git push -u origin master 2>&1
|
| 116 |
-
Write-Host "`n✅ 推送成功!" -ForegroundColor Green
|
| 117 |
-
Write-Host " 项目地址:<ADDRESS_REMOVED>
|
| 118 |
-
Write-Host "`n🎉 Fusion 项目已成功发布到 GitHub!" -ForegroundColor Cyan
|
| 119 |
-
} catch {
|
| 120 |
-
Write-Host "`n❌ 推送失败:$($_.Exception.Message)" -ForegroundColor Red
|
| 121 |
-
Write-Host "`n💡 可能的解决方案:" -ForegroundColor Yellow
|
| 122 |
-
Write-Host " 1. 使用 SSH 推送(需要配置 SSH key)" -ForegroundColor Yellow
|
| 123 |
-
Write-Host " 2. 手动推送:" -ForegroundColor Yellow
|
| 124 |
-
Write-Host " git remote add origin https://github.com/zhan1206/fusion-llm.git" -ForegroundColor Gray
|
| 125 |
-
Write-Host " git push -u origin master" -ForegroundColor Gray
|
| 126 |
-
exit 1
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
# 6. 清理(移除包含 Token 的 remote)
|
| 130 |
-
git remote remove origin
|
| 131 |
-
git remote add origin "https://github.com/zhan1206/fusion-llm.git"
|
| 132 |
-
|
| 133 |
-
Write-Host "`n✅ 已清理 remote(移除 Token)" -ForegroundColor Green
|
| 134 |
-
Write-Host "`n📜 后续操作:" -ForegroundColor Cyan
|
| 135 |
-
Write-Host " 1. 撤销当前 Token(安全考虑)" -ForegroundColor Yellow
|
| 136 |
-
Write-Host " 访问:https://github.com/settings/tokens" -ForegroundColor Gray
|
| 137 |
-
Write-Host "`n 2. 克隆项目:" -ForegroundColor Yellow
|
| 138 |
-
Write-Host " git clone https://github.com/zhan1206/fusion-llm.git" -ForegroundColor Gray
|
| 139 |
-
Write-Host "`n 3. 安装依赖:" -ForegroundColor Yellow
|
| 140 |
-
Write-Host " cd fusion-llm" -ForegroundColor Gray
|
| 141 |
-
Write-Host " pip install -r requirements.txt" -ForegroundColor Gray
|
| 142 |
-
|
| 143 |
-
Write-Host "`n" + "=" * 60 -ForegroundColor Cyan
|
| 144 |
-
Write-Host "完成!" -ForegroundColor Green
|
| 145 |
-
Write-Host "=" * 60 -ForegroundColor Cyan
|
| 146 |
-
|
| 147 |
-
# 提示用户按任意键退出
|
| 148 |
-
Write-Host "`n按任意键退出..."
|
| 149 |
-
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -16,7 +16,7 @@ Fusion 是一套面向**纯本地训练与推理**的开源大语言模型方案
|
|
| 16 |
|
| 17 |
### 🧠 滑动分块潜注意力(SBLA)
|
| 18 |
- 长序列切为定长块,块内高秩潜空间 + 块间极低秩潜向量
|
| 19 |
-
-
|
| 20 |
- 在 24 GB 显卡上即可对 14B 模型进行长文档微调与推理
|
| 21 |
|
| 22 |
### 🎛️ 动态推理强度调节器(Thinking Dial)
|
|
@@ -137,7 +137,7 @@ clean_data = filter.process(raw_data)
|
|
| 137 |
|------|---------|---------|---------|--------|---------|
|
| 138 |
| Qwen-8B | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 32K | 高 |
|
| 139 |
| LLaMA-8B | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 8K | 中 |
|
| 140 |
-
| **Fusion-8B** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | **
|
| 141 |
|
| 142 |
## 📖 文档
|
| 143 |
|
|
|
|
| 16 |
|
| 17 |
### 🧠 滑动分块潜注意力(SBLA)
|
| 18 |
- 长序列切为定长块,块内高秩潜空间 + 块间极低秩潜向量
|
| 19 |
+
- 当前支持 32K 上下文窗口,KV 缓存仅为传统 GQA 的 1/8(SBLA 架构可扩展至 256K,需配置 RoPE scaling)
|
| 20 |
- 在 24 GB 显卡上即可对 14B 模型进行长文档微调与推理
|
| 21 |
|
| 22 |
### 🎛️ 动态推理强度调节器(Thinking Dial)
|
|
|
|
| 137 |
|------|---------|---------|---------|--------|---------|
|
| 138 |
| Qwen-8B | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 32K | 高 |
|
| 139 |
| LLaMA-8B | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 8K | 中 |
|
| 140 |
+
| **Fusion-8B** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | **32K** (可扩展256K) | **低** |
|
| 141 |
|
| 142 |
## 📖 文档
|
| 143 |
|
|
@@ -2,41 +2,44 @@
|
|
| 2 |
"_name_or_path": "fusion-8b-base",
|
| 3 |
"architectures": ["FusionModel"],
|
| 4 |
"model_type": "fusion",
|
| 5 |
-
|
| 6 |
"vocab_size": 100000,
|
| 7 |
"hidden_size": 4096,
|
| 8 |
"num_hidden_layers": 32,
|
| 9 |
"num_attention_heads": 32,
|
| 10 |
-
"num_key_value_heads":
|
| 11 |
"intermediate_size": 11008,
|
| 12 |
-
|
| 13 |
"hidden_act": "silu",
|
| 14 |
-
"
|
|
|
|
|
|
|
| 15 |
"use_cache": true,
|
| 16 |
-
|
| 17 |
"max_position_embeddings": 32768,
|
| 18 |
"initializer_range": 0.02,
|
| 19 |
"rope_theta": 10000.0,
|
| 20 |
"rope_scaling": null,
|
| 21 |
-
|
| 22 |
"attention_bias": false,
|
| 23 |
"mlp_bias": false,
|
| 24 |
"attention_dropout": 0.0,
|
| 25 |
-
|
| 26 |
"block_size": 512,
|
| 27 |
"latent_dim": 64,
|
| 28 |
"window_size": 2048,
|
| 29 |
-
|
|
|
|
| 30 |
"enable_thinking_dial": true,
|
| 31 |
"num_thinking_depths": 4,
|
| 32 |
-
|
| 33 |
"torch_dtype": "bfloat16",
|
| 34 |
"transformers_version": "4.36.0",
|
| 35 |
"attn_implementation": "eager",
|
| 36 |
-
|
| 37 |
"pad_token_id": 0,
|
| 38 |
"bos_token_id": 1,
|
| 39 |
"eos_token_id": 2,
|
| 40 |
-
|
| 41 |
"tie_word_embeddings": false
|
| 42 |
}
|
|
|
|
| 2 |
"_name_or_path": "fusion-8b-base",
|
| 3 |
"architectures": ["FusionModel"],
|
| 4 |
"model_type": "fusion",
|
| 5 |
+
|
| 6 |
"vocab_size": 100000,
|
| 7 |
"hidden_size": 4096,
|
| 8 |
"num_hidden_layers": 32,
|
| 9 |
"num_attention_heads": 32,
|
| 10 |
+
"num_key_value_heads": 8,
|
| 11 |
"intermediate_size": 11008,
|
| 12 |
+
|
| 13 |
"hidden_act": "silu",
|
| 14 |
+
"hidden_dropout_prob": 0.0,
|
| 15 |
+
"attention_probs_dropout_prob": 0.0,
|
| 16 |
+
"rms_norm_eps": 1e-6,
|
| 17 |
"use_cache": true,
|
| 18 |
+
|
| 19 |
"max_position_embeddings": 32768,
|
| 20 |
"initializer_range": 0.02,
|
| 21 |
"rope_theta": 10000.0,
|
| 22 |
"rope_scaling": null,
|
| 23 |
+
|
| 24 |
"attention_bias": false,
|
| 25 |
"mlp_bias": false,
|
| 26 |
"attention_dropout": 0.0,
|
| 27 |
+
|
| 28 |
"block_size": 512,
|
| 29 |
"latent_dim": 64,
|
| 30 |
"window_size": 2048,
|
| 31 |
+
"sbla_mode": "mixed",
|
| 32 |
+
|
| 33 |
"enable_thinking_dial": true,
|
| 34 |
"num_thinking_depths": 4,
|
| 35 |
+
|
| 36 |
"torch_dtype": "bfloat16",
|
| 37 |
"transformers_version": "4.36.0",
|
| 38 |
"attn_implementation": "eager",
|
| 39 |
+
|
| 40 |
"pad_token_id": 0,
|
| 41 |
"bos_token_id": 1,
|
| 42 |
"eos_token_id": 2,
|
| 43 |
+
|
| 44 |
"tie_word_embeddings": false
|
| 45 |
}
|
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"_name_or_path": "fusion-mini",
|
| 3 |
"architectures": ["FusionMini"],
|
| 4 |
"model_type": "fusion_mini",
|
| 5 |
-
|
| 6 |
"vocab_size": 10000,
|
| 7 |
"hidden_size": 256,
|
| 8 |
"num_hidden_layers": 2,
|
|
@@ -15,20 +15,20 @@
|
|
| 15 |
"max_position_embeddings": 256,
|
| 16 |
"initializer_range": 0.02,
|
| 17 |
"use_cache": true,
|
| 18 |
-
|
| 19 |
-
"block_size":
|
| 20 |
"latent_dim": 16,
|
| 21 |
-
"window_size":
|
| 22 |
-
"sbla_mode": "
|
| 23 |
-
|
| 24 |
"rms_norm_eps": 1e-5,
|
| 25 |
"rope_theta": 10000.0,
|
| 26 |
"tie_word_embeddings": false,
|
| 27 |
-
|
| 28 |
"torch_dtype": "float32",
|
| 29 |
"transformers_version": "4.36.0",
|
| 30 |
-
|
| 31 |
"pad_token_id": 0,
|
| 32 |
"bos_token_id": 1,
|
| 33 |
"eos_token_id": 2
|
| 34 |
-
}
|
|
|
|
| 2 |
"_name_or_path": "fusion-mini",
|
| 3 |
"architectures": ["FusionMini"],
|
| 4 |
"model_type": "fusion_mini",
|
| 5 |
+
|
| 6 |
"vocab_size": 10000,
|
| 7 |
"hidden_size": 256,
|
| 8 |
"num_hidden_layers": 2,
|
|
|
|
| 15 |
"max_position_embeddings": 256,
|
| 16 |
"initializer_range": 0.02,
|
| 17 |
"use_cache": true,
|
| 18 |
+
|
| 19 |
+
"block_size": 32,
|
| 20 |
"latent_dim": 16,
|
| 21 |
+
"window_size": 128,
|
| 22 |
+
"sbla_mode": "mixed",
|
| 23 |
+
|
| 24 |
"rms_norm_eps": 1e-5,
|
| 25 |
"rope_theta": 10000.0,
|
| 26 |
"tie_word_embeddings": false,
|
| 27 |
+
|
| 28 |
"torch_dtype": "float32",
|
| 29 |
"transformers_version": "4.36.0",
|
| 30 |
+
|
| 31 |
"pad_token_id": 0,
|
| 32 |
"bos_token_id": 1,
|
| 33 |
"eos_token_id": 2
|
| 34 |
+
}
|
|
@@ -52,7 +52,7 @@
|
|
| 52 |
{
|
| 53 |
"prompt": "什么是人工智能",
|
| 54 |
"response": "人工智能是计算机科学的一个分支,致力于创建智能机器。",
|
| 55 |
-
"think_rank":
|
| 56 |
},
|
| 57 |
{
|
| 58 |
"prompt": "How to learn coding",
|
|
@@ -92,7 +92,7 @@
|
|
| 92 |
{
|
| 93 |
"prompt": "如何学习编程",
|
| 94 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 95 |
-
"think_rank":
|
| 96 |
},
|
| 97 |
{
|
| 98 |
"prompt": "What is NLP",
|
|
@@ -147,7 +147,7 @@
|
|
| 147 |
{
|
| 148 |
"prompt": "如何学习编程",
|
| 149 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 150 |
-
"think_rank":
|
| 151 |
},
|
| 152 |
{
|
| 153 |
"prompt": "区块链的原理",
|
|
@@ -192,7 +192,7 @@
|
|
| 192 |
{
|
| 193 |
"prompt": "Explain machine learning",
|
| 194 |
"response": "Machine learning is a subset of AI.",
|
| 195 |
-
"think_rank":
|
| 196 |
},
|
| 197 |
{
|
| 198 |
"prompt": "What is NLP",
|
|
@@ -222,7 +222,7 @@
|
|
| 222 |
{
|
| 223 |
"prompt": "如何学习编程",
|
| 224 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 225 |
-
"think_rank":
|
| 226 |
},
|
| 227 |
{
|
| 228 |
"prompt": "What is AI",
|
|
@@ -302,7 +302,7 @@
|
|
| 302 |
{
|
| 303 |
"prompt": "如何学习编程",
|
| 304 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 305 |
-
"think_rank":
|
| 306 |
},
|
| 307 |
{
|
| 308 |
"prompt": "What is deep learning",
|
|
@@ -327,7 +327,7 @@
|
|
| 327 |
{
|
| 328 |
"prompt": "什么是人工智能",
|
| 329 |
"response": "人工智能是计算机科学的一个分支,致力于创建智能机器。",
|
| 330 |
-
"think_rank":
|
| 331 |
},
|
| 332 |
{
|
| 333 |
"prompt": "你好",
|
|
@@ -357,7 +357,7 @@
|
|
| 357 |
{
|
| 358 |
"prompt": "Explain machine learning",
|
| 359 |
"response": "Machine learning is a subset of AI.",
|
| 360 |
-
"think_rank":
|
| 361 |
},
|
| 362 |
{
|
| 363 |
"prompt": "What is deep learning",
|
|
@@ -452,7 +452,7 @@
|
|
| 452 |
{
|
| 453 |
"prompt": "解释机器学习",
|
| 454 |
"response": "机器学习是人工智能的子领域,使计算机能够从数据中学习。",
|
| 455 |
-
"think_rank":
|
| 456 |
},
|
| 457 |
{
|
| 458 |
"prompt": "深度学习是什么",
|
|
@@ -472,7 +472,7 @@
|
|
| 472 |
{
|
| 473 |
"prompt": "Explain machine learning",
|
| 474 |
"response": "Machine learning is a subset of AI.",
|
| 475 |
-
"think_rank":
|
| 476 |
},
|
| 477 |
{
|
| 478 |
"prompt": "什么是自然语言处理",
|
|
@@ -497,6 +497,46 @@
|
|
| 497 |
{
|
| 498 |
"prompt": "什么是人工智能",
|
| 499 |
"response": "人工智能是计算机科学的一个分支,致力于创建智能机器。",
|
| 500 |
-
"think_rank":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
}
|
| 502 |
]
|
|
|
|
| 52 |
{
|
| 53 |
"prompt": "什么是人工智能",
|
| 54 |
"response": "人工智能是计算机科学的一个分支,致力于创建智能机器。",
|
| 55 |
+
"think_rank": 1
|
| 56 |
},
|
| 57 |
{
|
| 58 |
"prompt": "How to learn coding",
|
|
|
|
| 92 |
{
|
| 93 |
"prompt": "如何学习编程",
|
| 94 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 95 |
+
"think_rank": 2
|
| 96 |
},
|
| 97 |
{
|
| 98 |
"prompt": "What is NLP",
|
|
|
|
| 147 |
{
|
| 148 |
"prompt": "如何学习编程",
|
| 149 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 150 |
+
"think_rank": 2
|
| 151 |
},
|
| 152 |
{
|
| 153 |
"prompt": "区块链的原理",
|
|
|
|
| 192 |
{
|
| 193 |
"prompt": "Explain machine learning",
|
| 194 |
"response": "Machine learning is a subset of AI.",
|
| 195 |
+
"think_rank": 2
|
| 196 |
},
|
| 197 |
{
|
| 198 |
"prompt": "What is NLP",
|
|
|
|
| 222 |
{
|
| 223 |
"prompt": "如何学习编程",
|
| 224 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 225 |
+
"think_rank": 2
|
| 226 |
},
|
| 227 |
{
|
| 228 |
"prompt": "What is AI",
|
|
|
|
| 302 |
{
|
| 303 |
"prompt": "如何学习编程",
|
| 304 |
"response": "学习编程需要理论与实践相结合,多写代码多思考。",
|
| 305 |
+
"think_rank": 2
|
| 306 |
},
|
| 307 |
{
|
| 308 |
"prompt": "What is deep learning",
|
|
|
|
| 327 |
{
|
| 328 |
"prompt": "什么是人工智能",
|
| 329 |
"response": "人工智能是计算机科学的一个分支,致力于创建智能机器。",
|
| 330 |
+
"think_rank": 1
|
| 331 |
},
|
| 332 |
{
|
| 333 |
"prompt": "你好",
|
|
|
|
| 357 |
{
|
| 358 |
"prompt": "Explain machine learning",
|
| 359 |
"response": "Machine learning is a subset of AI.",
|
| 360 |
+
"think_rank": 2
|
| 361 |
},
|
| 362 |
{
|
| 363 |
"prompt": "What is deep learning",
|
|
|
|
| 452 |
{
|
| 453 |
"prompt": "解释机器学习",
|
| 454 |
"response": "机器学习是人工智能的子领域,使计算机能够从数据中学习。",
|
| 455 |
+
"think_rank": 2
|
| 456 |
},
|
| 457 |
{
|
| 458 |
"prompt": "深度学习是什么",
|
|
|
|
| 472 |
{
|
| 473 |
"prompt": "Explain machine learning",
|
| 474 |
"response": "Machine learning is a subset of AI.",
|
| 475 |
+
"think_rank": 2
|
| 476 |
},
|
| 477 |
{
|
| 478 |
"prompt": "什么是自然语言处理",
|
|
|
|
| 497 |
{
|
| 498 |
"prompt": "什么是人工智能",
|
| 499 |
"response": "人工智能是计算机科学的一个分支,致力于创建智能机器。",
|
| 500 |
+
"think_rank": 1
|
| 501 |
+
},
|
| 502 |
+
{
|
| 503 |
+
"prompt": "Prove the Pythagorean theorem",
|
| 504 |
+
"response": "Consider a right triangle with legs a, b and hypotenuse c. Construct a square of side (a+b) and arrange four copies of the triangle inside. The inner quadrilateral is a square of side c, so (a+b)^2 = c^2 + 4*(ab/2), which simplifies to a^2 + b^2 = c^2.",
|
| 505 |
+
"think_rank": 3
|
| 506 |
+
},
|
| 507 |
+
{
|
| 508 |
+
"prompt": "Derive the quadratic formula",
|
| 509 |
+
"response": "Starting from ax^2 + bx + c = 0, divide by a, complete the square: (x + b/2a)^2 = (b^2 - 4ac)/4a^2. Taking the square root gives x = (-b +- sqrt(b^2 - 4ac)) / 2a.",
|
| 510 |
+
"think_rank": 3
|
| 511 |
+
},
|
| 512 |
+
{
|
| 513 |
+
"prompt": "Prove that sqrt(2) is irrational",
|
| 514 |
+
"response": "Assume sqrt(2) = p/q in lowest terms. Then 2q^2 = p^2, so p is even. Write p = 2k, then 2q^2 = 4k^2, so q^2 = 2k^2, meaning q is also even. This contradicts p/q being in lowest terms.",
|
| 515 |
+
"think_rank": 3
|
| 516 |
+
},
|
| 517 |
+
{
|
| 518 |
+
"prompt": "Prove there are infinitely many primes",
|
| 519 |
+
"response": "Suppose finitely many primes p1,...,pn exist. Let N = p1*p2*...*pn + 1. N is not divisible by any pi (remainder 1). So N is either prime itself or has a prime factor not in the list. Contradiction.",
|
| 520 |
+
"think_rank": 3
|
| 521 |
+
},
|
| 522 |
+
{
|
| 523 |
+
"prompt": "Derive the derivative of sin(x)",
|
| 524 |
+
"response": "Using the limit definition: d/dx sin(x) = lim(h->0) [sin(x+h) - sin(x)] / h. Apply the angle addition formula: sin(x+h) = sin(x)cos(h) + cos(x)sin(h). This gives lim [sin(x)(cos(h)-1)/h + cos(x)sin(h)/h] = sin(x)*0 + cos(x)*1 = cos(x).",
|
| 525 |
+
"think_rank": 3
|
| 526 |
+
},
|
| 527 |
+
{
|
| 528 |
+
"prompt": "Analyze the time complexity of merge sort",
|
| 529 |
+
"response": "Merge sort divides the array in half (O(1)), recurses on both halves (2T(n/2)), and merges in O(n). The recurrence T(n) = 2T(n/2) + O(n) solves to O(n log n) by the master theorem (case 2). This holds for worst, average, and best cases.",
|
| 530 |
+
"think_rank": 3
|
| 531 |
+
},
|
| 532 |
+
{
|
| 533 |
+
"prompt": "Prove the sum of first n natural numbers is n(n+1)/2",
|
| 534 |
+
"response": "By induction: Base case n=1: 1 = 1(2)/2 = 1. Inductive step: assume S(k) = k(k+1)/2. Then S(k+1) = S(k) + (k+1) = k(k+1)/2 + (k+1) = (k+1)(k/2 + 1) = (k+1)(k+2)/2. QED.",
|
| 535 |
+
"think_rank": 3
|
| 536 |
+
},
|
| 537 |
+
{
|
| 538 |
+
"prompt": "Prove that e^x converges for all x",
|
| 539 |
+
"response": "The Taylor series e^x = sum(x^n/n!) has ratio test: |a_(n+1)/a_n| = |x|/(n+1) -> 0 as n -> infinity. Since the limit is 0 < 1 for all x, the series converges absolutely for all real x by the ratio test.",
|
| 540 |
+
"think_rank": 3
|
| 541 |
}
|
| 542 |
]
|
|
@@ -0,0 +1,549 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
T-KD 蒸馏训练脚本
|
| 3 |
+
|
| 4 |
+
真实的蒸馏训练逻辑:
|
| 5 |
+
1. 加载教师模型(冻结参数)
|
| 6 |
+
2. 加载学生模型(可训练)
|
| 7 |
+
3. 计算 KL 散度损失(教师 logits vs 学生 logits)
|
| 8 |
+
4. 可选:加入硬标签交叉熵损失
|
| 9 |
+
|
| 10 |
+
使用方法:
|
| 11 |
+
python data_pipeline/t_kd_distillation_train.py \
|
| 12 |
+
--teacher_model "Qwen/Qwen2.5-72B-Instruct" \
|
| 13 |
+
--student_model "./output/fusion-mini" \
|
| 14 |
+
--train_data "data/t_kd_corpus.jsonl" \
|
| 15 |
+
--output_dir "./output/fusion-mini-distilled"
|
| 16 |
+
|
| 17 |
+
作者:朱子瞻
|
| 18 |
+
项目:Fusion - 六边形开源大模型
|
| 19 |
+
许可证:Apache 2.0
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import argparse
|
| 23 |
+
import json
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
from pathlib import Path
|
| 28 |
+
from typing import Optional, List, Dict
|
| 29 |
+
from torch.utils.data import Dataset, DataLoader
|
| 30 |
+
from transformers import (
|
| 31 |
+
AutoTokenizer,
|
| 32 |
+
AutoModelForCausalLM,
|
| 33 |
+
get_linear_schedule_with_warmup,
|
| 34 |
+
)
|
| 35 |
+
import logging
|
| 36 |
+
|
| 37 |
+
logging.basicConfig(level=logging.INFO)
|
| 38 |
+
logger = logging.getLogger(__name__)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class DistillationDataset(Dataset):
|
| 42 |
+
"""蒸馏训练数据集"""
|
| 43 |
+
|
| 44 |
+
def __init__(
|
| 45 |
+
self,
|
| 46 |
+
data_path: str,
|
| 47 |
+
tokenizer,
|
| 48 |
+
max_length: int = 2048,
|
| 49 |
+
):
|
| 50 |
+
self.tokenizer = tokenizer
|
| 51 |
+
self.max_length = max_length
|
| 52 |
+
|
| 53 |
+
# 加载数据
|
| 54 |
+
self.data = []
|
| 55 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 56 |
+
for line in f:
|
| 57 |
+
if line.strip():
|
| 58 |
+
self.data.append(json.loads(line))
|
| 59 |
+
|
| 60 |
+
logger.info(f"✅ 加载数据:{len(self.data)} 条")
|
| 61 |
+
|
| 62 |
+
def __len__(self):
|
| 63 |
+
return len(self.data)
|
| 64 |
+
|
| 65 |
+
def __getitem__(self, idx):
|
| 66 |
+
item = self.data[idx]
|
| 67 |
+
|
| 68 |
+
# 编码
|
| 69 |
+
text = item.get("text", "")
|
| 70 |
+
encoding = self.tokenizer(
|
| 71 |
+
text,
|
| 72 |
+
max_length=self.max_length,
|
| 73 |
+
padding="max_length",
|
| 74 |
+
truncation=True,
|
| 75 |
+
return_tensors="pt",
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
input_ids = encoding["input_ids"].squeeze(0)
|
| 79 |
+
attention_mask = encoding["attention_mask"].squeeze(0)
|
| 80 |
+
|
| 81 |
+
# labels(用于交叉熵损失)
|
| 82 |
+
labels = input_ids.clone()
|
| 83 |
+
labels[labels == self.tokenizer.pad_token_id] = -100
|
| 84 |
+
|
| 85 |
+
return {
|
| 86 |
+
"input_ids": input_ids,
|
| 87 |
+
"attention_mask": attention_mask,
|
| 88 |
+
"labels": labels,
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class DistillationTrainer:
|
| 93 |
+
"""
|
| 94 |
+
T-KD 蒸馏训练器
|
| 95 |
+
|
| 96 |
+
核心:学生模型模仿教师模型的输出分布
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
teacher_model_name: str,
|
| 102 |
+
student_model_name: str,
|
| 103 |
+
device: str = "cuda",
|
| 104 |
+
temperature: float = 4.0,
|
| 105 |
+
alpha: float = 0.5, # KL 损失权重
|
| 106 |
+
learning_rate: float = 1e-5,
|
| 107 |
+
batch_size: int = 4,
|
| 108 |
+
grad_accum_steps: int = 8,
|
| 109 |
+
):
|
| 110 |
+
"""
|
| 111 |
+
初始化蒸馏训练器
|
| 112 |
+
|
| 113 |
+
参数:
|
| 114 |
+
teacher_model_name: 教师模型(HuggingFace ID 或本地路径)
|
| 115 |
+
student_model_name: 学生模型(本地路径,将训练)
|
| 116 |
+
device: 设备
|
| 117 |
+
temperature: 蒸馏温度(T>1 软化概率分布)
|
| 118 |
+
alpha: 损失权重(alpha * KL + (1-alpha) * CE)
|
| 119 |
+
learning_rate: 学习率
|
| 120 |
+
batch_size: 批次大小
|
| 121 |
+
grad_accum_steps: 梯度累积步数
|
| 122 |
+
"""
|
| 123 |
+
self.device = device
|
| 124 |
+
self.temperature = temperature
|
| 125 |
+
self.alpha = alpha
|
| 126 |
+
self.batch_size = batch_size
|
| 127 |
+
self.grad_accum_steps = grad_accum_steps
|
| 128 |
+
|
| 129 |
+
# 1. 加载教师模型(冻结)
|
| 130 |
+
logger.info(f"📚 加载教师模型:{teacher_model_name}")
|
| 131 |
+
self.teacher_tokenizer = AutoTokenizer.from_pretrained(
|
| 132 |
+
teacher_model_name,
|
| 133 |
+
trust_remote_code=True,
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
self.teacher_model = AutoModelForCausalLM.from_pretrained(
|
| 137 |
+
teacher_model_name,
|
| 138 |
+
torch_dtype=torch.bfloat16,
|
| 139 |
+
device_map=device,
|
| 140 |
+
trust_remote_code=True,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
self.teacher_model.eval()
|
| 144 |
+
for param in self.teacher_model.parameters():
|
| 145 |
+
param.requires_grad = False
|
| 146 |
+
|
| 147 |
+
logger.info(f"✅ 教师模型加载完成(参数已冻结)")
|
| 148 |
+
|
| 149 |
+
# 2. 加载学生模型(可训练)
|
| 150 |
+
logger.info(f"🎓 加载学生模型:{student_model_name}")
|
| 151 |
+
self.student_tokenizer = AutoTokenizer.from_pretrained(
|
| 152 |
+
student_model_name,
|
| 153 |
+
trust_remote_code=True,
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
self.student_model = AutoModelForCausalLM.from_pretrained(
|
| 157 |
+
student_model_name,
|
| 158 |
+
torch_dtype=torch.bfloat16,
|
| 159 |
+
device_map=device,
|
| 160 |
+
trust_remote_code=True,
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
self.student_model.train()
|
| 164 |
+
|
| 165 |
+
logger.info(f"✅ 学生模型加载完成(可训练)")
|
| 166 |
+
|
| 167 |
+
# 3. 优化器 + 学习率调度器
|
| 168 |
+
self.optimizer = torch.optim.AdamW(
|
| 169 |
+
self.student_model.parameters(),
|
| 170 |
+
lr=learning_rate,
|
| 171 |
+
weight_decay=0.01,
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
logger.info(f"✅ 优化器初始化完成(lr={learning_rate})")
|
| 175 |
+
|
| 176 |
+
def compute_distillation_loss(
|
| 177 |
+
self,
|
| 178 |
+
teacher_logits: torch.Tensor,
|
| 179 |
+
student_logits: torch.Tensor,
|
| 180 |
+
labels: torch.Tensor,
|
| 181 |
+
) -> torch.Tensor:
|
| 182 |
+
"""
|
| 183 |
+
计算蒸馏损失
|
| 184 |
+
|
| 185 |
+
公式:Loss = alpha * T² * KL(teacher || student) + (1-alpha) * CE(student, labels)
|
| 186 |
+
|
| 187 |
+
参数:
|
| 188 |
+
teacher_logits: (batch, seq_len, vocab_size)
|
| 189 |
+
student_logits: (batch, seq_len, vocab_size)
|
| 190 |
+
labels: (batch, seq_len)
|
| 191 |
+
|
| 192 |
+
返回:
|
| 193 |
+
蒸馏损失
|
| 194 |
+
"""
|
| 195 |
+
# 1. KL 散度损失(蒸馏)
|
| 196 |
+
T = self.temperature
|
| 197 |
+
T_squared = T * T
|
| 198 |
+
|
| 199 |
+
# 软化概率分布
|
| 200 |
+
teacher_probs = F.softmax(teacher_logits / T, dim=-1)
|
| 201 |
+
student_log_probs = F.log_softmax(student_logits / T, dim=-1)
|
| 202 |
+
|
| 203 |
+
# KL 散度(教师 || 学生)
|
| 204 |
+
kl_loss = F.kl_div(
|
| 205 |
+
student_log_probs.view(-1, student_logits.size(-1)),
|
| 206 |
+
teacher_probs.view(-1, teacher_logits.size(-1)),
|
| 207 |
+
reduction="batchmean",
|
| 208 |
+
log_target=False,
|
| 209 |
+
) * T_squared # 温度缩放
|
| 210 |
+
|
| 211 |
+
# 2. 交叉熵损失(硬标签)
|
| 212 |
+
shift_logits = student_logits[..., :-1, :].contiguous()
|
| 213 |
+
shift_labels = labels[..., 1:].contiguous()
|
| 214 |
+
|
| 215 |
+
ce_loss = F.cross_entropy(
|
| 216 |
+
shift_logits.view(-1, student_logits.size(-1)),
|
| 217 |
+
shift_labels.view(-1),
|
| 218 |
+
ignore_index=-100,
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
# 3. 总损失
|
| 222 |
+
total_loss = self.alpha * kl_loss + (1 - self.alpha) * ce_loss
|
| 223 |
+
|
| 224 |
+
return total_loss, kl_loss, ce_loss
|
| 225 |
+
|
| 226 |
+
def train(
|
| 227 |
+
self,
|
| 228 |
+
train_data_path: str,
|
| 229 |
+
output_dir: str,
|
| 230 |
+
num_epochs: int = 3,
|
| 231 |
+
save_steps: int = 500,
|
| 232 |
+
max_length: int = 2048,
|
| 233 |
+
):
|
| 234 |
+
"""
|
| 235 |
+
执行蒸馏训练
|
| 236 |
+
|
| 237 |
+
参数:
|
| 238 |
+
train_data_path: 训练数据路径(.jsonl)
|
| 239 |
+
output_dir: 模型保存目录
|
| 240 |
+
num_epochs: 训练轮数
|
| 241 |
+
save_steps: 每 N 步保存一次
|
| 242 |
+
max_length: 最大序列长度
|
| 243 |
+
"""
|
| 244 |
+
# 1. 创建数据集
|
| 245 |
+
train_dataset = DistillationDataset(
|
| 246 |
+
train_data_path,
|
| 247 |
+
self.student_tokenizer,
|
| 248 |
+
max_length=max_length,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
train_dataloader = DataLoader(
|
| 252 |
+
train_dataset,
|
| 253 |
+
batch_size=self.batch_size,
|
| 254 |
+
shuffle=True,
|
| 255 |
+
num_workers=0, # Windows 下设为 0
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
# 2. 学习率调度器
|
| 259 |
+
total_steps = len(train_dataloader) * num_epochs // self.grad_accum_steps
|
| 260 |
+
scheduler = get_linear_schedule_with_warmup(
|
| 261 |
+
self.optimizer,
|
| 262 |
+
num_warmup_steps=int(total_steps * 0.1),
|
| 263 |
+
num_training_steps=total_steps,
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# 3. 训练循环
|
| 267 |
+
logger.info(f"🚀 开始蒸馏训练...")
|
| 268 |
+
logger.info(f" 轮数:{num_epochs}")
|
| 269 |
+
logger.info(f" 批次大小:{self.batch_size}")
|
| 270 |
+
logger.info(f" 梯度累积:{self.grad_accum_steps}")
|
| 271 |
+
logger.info(f" 温度:{self.temperature}")
|
| 272 |
+
logger.info(f" Alpha:{self.alpha}")
|
| 273 |
+
|
| 274 |
+
global_step = 0
|
| 275 |
+
self.student_model.train()
|
| 276 |
+
|
| 277 |
+
for epoch in range(num_epochs):
|
| 278 |
+
epoch_loss = 0.0
|
| 279 |
+
epoch_kl = 0.0
|
| 280 |
+
epoch_ce = 0.0
|
| 281 |
+
num_batches = 0
|
| 282 |
+
|
| 283 |
+
for batch_idx, batch in enumerate(train_dataloader):
|
| 284 |
+
# 移动到设备
|
| 285 |
+
input_ids = batch["input_ids"].to(self.device)
|
| 286 |
+
attention_mask = batch["attention_mask"].to(self.device)
|
| 287 |
+
labels = batch["labels"].to(self.device)
|
| 288 |
+
|
| 289 |
+
# 教师模型推理(无梯度)
|
| 290 |
+
with torch.no_grad():
|
| 291 |
+
teacher_outputs = self.teacher_model(
|
| 292 |
+
input_ids=input_ids,
|
| 293 |
+
attention_mask=attention_mask,
|
| 294 |
+
)
|
| 295 |
+
teacher_logits = teacher_outputs.logits
|
| 296 |
+
|
| 297 |
+
# 学生模型前向传播
|
| 298 |
+
student_outputs = self.student_model(
|
| 299 |
+
input_ids=input_ids,
|
| 300 |
+
attention_mask=attention_mask,
|
| 301 |
+
)
|
| 302 |
+
student_logits = student_outputs.logits
|
| 303 |
+
|
| 304 |
+
# 计算蒸馏损失
|
| 305 |
+
loss, kl_loss, ce_loss = self.compute_distillation_loss(
|
| 306 |
+
teacher_logits,
|
| 307 |
+
student_logits,
|
| 308 |
+
labels,
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
# 梯度累积(除以累积步数)
|
| 312 |
+
loss = loss / self.grad_accum_steps
|
| 313 |
+
loss.backward()
|
| 314 |
+
|
| 315 |
+
# 梯度裁剪
|
| 316 |
+
torch.nn.utils.clip_grad_norm_(
|
| 317 |
+
self.student_model.parameters(),
|
| 318 |
+
max_norm=1.0,
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
# 更新参数(每 grad_accum_steps 步)
|
| 322 |
+
if (batch_idx + 1) % self.grad_accum_steps == 0:
|
| 323 |
+
self.optimizer.step()
|
| 324 |
+
scheduler.step()
|
| 325 |
+
self.optimizer.zero_grad()
|
| 326 |
+
global_step += 1
|
| 327 |
+
|
| 328 |
+
# 统计
|
| 329 |
+
epoch_loss += loss.item() * self.grad_accum_steps
|
| 330 |
+
epoch_kl += kl_loss.item()
|
| 331 |
+
epoch_ce += ce_loss.item()
|
| 332 |
+
num_batches += 1
|
| 333 |
+
|
| 334 |
+
# 日志
|
| 335 |
+
if batch_idx % 10 == 0:
|
| 336 |
+
logger.info(
|
| 337 |
+
f" Epoch {epoch+1}, Batch {batch_idx}, "
|
| 338 |
+
f"Loss: {loss.item() * self.grad_accum_steps:.4f}, "
|
| 339 |
+
f"KL: {kl_loss.item():.4f}, CE: {ce_loss.item():.4f}"
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
# 清理 GPU 缓存
|
| 343 |
+
del teacher_logits, student_logits, loss
|
| 344 |
+
torch.cuda.empty_cache()
|
| 345 |
+
|
| 346 |
+
# Epoch 结束统计
|
| 347 |
+
avg_loss = epoch_loss / max(num_batches, 1)
|
| 348 |
+
avg_kl = epoch_kl / max(num_batches, 1)
|
| 349 |
+
avg_ce = epoch_ce / max(num_batches, 1)
|
| 350 |
+
|
| 351 |
+
logger.info(f" Epoch {epoch+1}/{num_epochs} 完成")
|
| 352 |
+
logger.info(f" Average Loss: {avg_loss:.4f}")
|
| 353 |
+
logger.info(f" Average KL: {avg_kl:.4f}")
|
| 354 |
+
logger.info(f" Average CE: {avg_ce:.4f}")
|
| 355 |
+
|
| 356 |
+
# 保存检查点
|
| 357 |
+
checkpoint_dir = Path(output_dir) / f"checkpoint-epoch-{epoch+1}"
|
| 358 |
+
checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
| 359 |
+
|
| 360 |
+
self.student_model.save_pretrained(checkpoint_dir)
|
| 361 |
+
self.student_tokenizer.save_pretrained(checkpoint_dir)
|
| 362 |
+
|
| 363 |
+
logger.info(f" ✅ 检查点保存至:{checkpoint_dir}")
|
| 364 |
+
|
| 365 |
+
# 4. 保存最终模型
|
| 366 |
+
output_path = Path(output_dir) / "final"
|
| 367 |
+
output_path.mkdir(parents=True, exist_ok=True)
|
| 368 |
+
|
| 369 |
+
self.student_model.save_pretrained(output_path)
|
| 370 |
+
self.student_tokenizer.save_pretrained(output_path)
|
| 371 |
+
|
| 372 |
+
logger.info(f"🎉 蒸馏训练完成!模型保存至:{output_path}")
|
| 373 |
+
|
| 374 |
+
def evaluate(
|
| 375 |
+
self,
|
| 376 |
+
eval_data_path: str,
|
| 377 |
+
max_length: int = 2048,
|
| 378 |
+
num_samples: int = 100,
|
| 379 |
+
):
|
| 380 |
+
"""
|
| 381 |
+
评估蒸馏后的模型
|
| 382 |
+
|
| 383 |
+
参数:
|
| 384 |
+
eval_data_path: 评估数据路径
|
| 385 |
+
max_length: 最大序列长度
|
| 386 |
+
num_samples: 评估样本数
|
| 387 |
+
"""
|
| 388 |
+
logger.info(f"📊 开始评估...")
|
| 389 |
+
|
| 390 |
+
self.student_model.eval()
|
| 391 |
+
|
| 392 |
+
# 加载评估数据
|
| 393 |
+
eval_dataset = DistillationDataset(
|
| 394 |
+
eval_data_path,
|
| 395 |
+
self.student_tokenizer,
|
| 396 |
+
max_length=max_length,
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
eval_dataloader = DataLoader(
|
| 400 |
+
eval_dataset,
|
| 401 |
+
batch_size=1,
|
| 402 |
+
shuffle=False,
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
total_loss = 0.0
|
| 406 |
+
num_batches = 0
|
| 407 |
+
|
| 408 |
+
with torch.no_grad():
|
| 409 |
+
for batch in eval_dataloader:
|
| 410 |
+
if num_batches >= num_samples:
|
| 411 |
+
break
|
| 412 |
+
|
| 413 |
+
input_ids = batch["input_ids"].to(self.device)
|
| 414 |
+
attention_mask = batch["attention_mask"].to(self.device)
|
| 415 |
+
labels = batch["labels"].to(self.device)
|
| 416 |
+
|
| 417 |
+
outputs = self.student_model(
|
| 418 |
+
input_ids=input_ids,
|
| 419 |
+
attention_mask=attention_mask,
|
| 420 |
+
labels=labels,
|
| 421 |
+
)
|
| 422 |
+
|
| 423 |
+
total_loss += outputs.loss.item()
|
| 424 |
+
num_batches += 1
|
| 425 |
+
|
| 426 |
+
avg_loss = total_loss / max(num_batches, 1)
|
| 427 |
+
|
| 428 |
+
logger.info(f"✅ 评估完成")
|
| 429 |
+
logger.info(f" Average Loss: {avg_loss:.4f}")
|
| 430 |
+
logger.info(f" Perplexity: {torch.exp(torch.tensor(avg_loss)).item():.2f}")
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
def main():
|
| 434 |
+
parser = argparse.ArgumentParser(description="T-KD 蒸馏训练")
|
| 435 |
+
|
| 436 |
+
parser.add_argument(
|
| 437 |
+
"--teacher_model",
|
| 438 |
+
type=str,
|
| 439 |
+
required=True,
|
| 440 |
+
help="教师模型(HuggingFace ID 或本地路径)",
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
parser.add_argument(
|
| 444 |
+
"--student_model",
|
| 445 |
+
type=str,
|
| 446 |
+
required=True,
|
| 447 |
+
help="学生模型(本地路径,将训练)",
|
| 448 |
+
)
|
| 449 |
+
|
| 450 |
+
parser.add_argument(
|
| 451 |
+
"--train_data",
|
| 452 |
+
type=str,
|
| 453 |
+
required=True,
|
| 454 |
+
help="训练数据路径(.jsonl)",
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
parser.add_argument(
|
| 458 |
+
"--output_dir",
|
| 459 |
+
type=str,
|
| 460 |
+
required=True,
|
| 461 |
+
help="模型保存目录",
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
parser.add_argument(
|
| 465 |
+
"--num_epochs",
|
| 466 |
+
type=int,
|
| 467 |
+
default=3,
|
| 468 |
+
help="训练轮数",
|
| 469 |
+
)
|
| 470 |
+
|
| 471 |
+
parser.add_argument(
|
| 472 |
+
"--batch_size",
|
| 473 |
+
type=int,
|
| 474 |
+
default=4,
|
| 475 |
+
help="批次大小",
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
parser.add_argument(
|
| 479 |
+
"--grad_accum_steps",
|
| 480 |
+
type=int,
|
| 481 |
+
default=8,
|
| 482 |
+
help="梯度累积步数",
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
parser.add_argument(
|
| 486 |
+
"--temperature",
|
| 487 |
+
type=float,
|
| 488 |
+
default=4.0,
|
| 489 |
+
help="蒸馏温度(T>1 软化概率分布)",
|
| 490 |
+
)
|
| 491 |
+
|
| 492 |
+
parser.add_argument(
|
| 493 |
+
"--alpha",
|
| 494 |
+
type=float,
|
| 495 |
+
default=0.5,
|
| 496 |
+
help="损失权重(alpha * KL + (1-alpha) * CE)",
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
parser.add_argument(
|
| 500 |
+
"--learning_rate",
|
| 501 |
+
type=float,
|
| 502 |
+
default=1e-5,
|
| 503 |
+
help="学习率",
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
parser.add_argument(
|
| 507 |
+
"--max_length",
|
| 508 |
+
type=int,
|
| 509 |
+
default=2048,
|
| 510 |
+
help="最大序列长度",
|
| 511 |
+
)
|
| 512 |
+
|
| 513 |
+
parser.add_argument(
|
| 514 |
+
"--device",
|
| 515 |
+
type=str,
|
| 516 |
+
default="cuda",
|
| 517 |
+
help="设备(cuda/cpu)",
|
| 518 |
+
)
|
| 519 |
+
|
| 520 |
+
args = parser.parse_args()
|
| 521 |
+
|
| 522 |
+
# 创建输出目录
|
| 523 |
+
Path(args.output_dir).mkdir(parents=True, exist_ok=True)
|
| 524 |
+
|
| 525 |
+
# 初始化训练器
|
| 526 |
+
trainer = DistillationTrainer(
|
| 527 |
+
teacher_model_name=args.teacher_model,
|
| 528 |
+
student_model_name=args.student_model,
|
| 529 |
+
device=args.device,
|
| 530 |
+
temperature=args.temperature,
|
| 531 |
+
alpha=args.alpha,
|
| 532 |
+
learning_rate=args.learning_rate,
|
| 533 |
+
batch_size=args.batch_size,
|
| 534 |
+
grad_accum_steps=args.grad_accum_steps,
|
| 535 |
+
)
|
| 536 |
+
|
| 537 |
+
# 训练
|
| 538 |
+
trainer.train(
|
| 539 |
+
train_data_path=args.train_data,
|
| 540 |
+
output_dir=args.output_dir,
|
| 541 |
+
num_epochs=args.num_epochs,
|
| 542 |
+
max_length=args.max_length,
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
logger.info("🎉 蒸馏训练完成!")
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
if __name__ == "__main__":
|
| 549 |
+
main()
|
|
@@ -1,42 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"_name_or_path": "fusion-8b-base",
|
| 3 |
-
"architectures": ["FusionModel"],
|
| 4 |
-
"model_type": "fusion",
|
| 5 |
-
|
| 6 |
-
"vocab_size": 100000,
|
| 7 |
-
"hidden_size": 4096,
|
| 8 |
-
"num_hidden_layers": 32,
|
| 9 |
-
"num_attention_heads": 32,
|
| 10 |
-
"num_key_value_heads": 8,
|
| 11 |
-
"intermediate_size": 11008,
|
| 12 |
-
"hidden_act": "silu",
|
| 13 |
-
"hidden_dropout_prob": 0.0,
|
| 14 |
-
"attention_probs_dropout_prob": 0.0,
|
| 15 |
-
"max_position_embeddings": 32768,
|
| 16 |
-
"initializer_range": 0.02,
|
| 17 |
-
"use_cache": true,
|
| 18 |
-
|
| 19 |
-
"block_size": 512,
|
| 20 |
-
"latent_dim": 64,
|
| 21 |
-
"window_size": 2048,
|
| 22 |
-
"sbla_mode": "mixed",
|
| 23 |
-
|
| 24 |
-
"rms_norm_eps": 1e-6,
|
| 25 |
-
"rope_theta": 10000.0,
|
| 26 |
-
"rope_scaling": null,
|
| 27 |
-
"tie_word_embeddings": false,
|
| 28 |
-
|
| 29 |
-
"enable_thinking_dial": true,
|
| 30 |
-
"num_thinking_depths": 4,
|
| 31 |
-
|
| 32 |
-
"torch_dtype": "bfloat16",
|
| 33 |
-
"transformers_version": "4.36.0",
|
| 34 |
-
"attn_implementation": "eager",
|
| 35 |
-
|
| 36 |
-
"pad_token_id": 0,
|
| 37 |
-
"bos_token_id": 1,
|
| 38 |
-
"eos_token_id": 2,
|
| 39 |
-
|
| 40 |
-
"attention_bias": false,
|
| 41 |
-
"mlp_bias": false
|
| 42 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,522 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Fusion Ollama deployment tool (v2 - fixed)
|
| 3 |
+
|
| 4 |
+
Features:
|
| 5 |
+
1. Auto-detect llama.cpp path
|
| 6 |
+
2. Convert HF model to GGUF format
|
| 7 |
+
3. Generate Modelfile
|
| 8 |
+
4. Create Ollama model
|
| 9 |
+
5. Support Thinking Dial control
|
| 10 |
+
6. Windows-compatible (shell=True for subprocess)
|
| 11 |
+
|
| 12 |
+
Usage:
|
| 13 |
+
python inference/ollama_deploy_v2.py --model_path ./output/fusion-8b --model_name fusion-8b
|
| 14 |
+
|
| 15 |
+
Requirements:
|
| 16 |
+
- llama.cpp (auto-detected or set LLAMA_CPP_DIR)
|
| 17 |
+
- Ollama (https://ollama.com)
|
| 18 |
+
|
| 19 |
+
Author: 朱子瞻 (Zhu Zizhan)
|
| 20 |
+
Project: Fusion - Hexagonal Open-source LLM
|
| 21 |
+
License: Apache 2.0
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
import argparse
|
| 25 |
+
import subprocess
|
| 26 |
+
import os
|
| 27 |
+
import json
|
| 28 |
+
from pathlib import Path
|
| 29 |
+
import logging
|
| 30 |
+
import sys
|
| 31 |
+
|
| 32 |
+
logging.basicConfig(level=logging.INFO)
|
| 33 |
+
logger = logging.getLogger(__name__)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def find_llama_cpp() -> str:
|
| 37 |
+
"""
|
| 38 |
+
Auto-detect llama.cpp directory
|
| 39 |
+
|
| 40 |
+
Returns:
|
| 41 |
+
llama.cpp directory path
|
| 42 |
+
|
| 43 |
+
Raises:
|
| 44 |
+
FileNotFoundError: llama.cpp not found
|
| 45 |
+
"""
|
| 46 |
+
# 1. Check environment variable
|
| 47 |
+
llama_cpp_dir = os.environ.get("LLAMA_CPP_DIR", "")
|
| 48 |
+
if llama_cpp_dir and os.path.exists(llama_cpp_dir):
|
| 49 |
+
convert_script = os.path.join(llama_cpp_dir, "convert-hf-to-gguf.py")
|
| 50 |
+
if os.path.exists(convert_script):
|
| 51 |
+
logger.info(f"Found llama.cpp from env: {llama_cpp_dir}")
|
| 52 |
+
return llama_cpp_dir
|
| 53 |
+
|
| 54 |
+
# 2. Check common paths
|
| 55 |
+
possible_paths = [
|
| 56 |
+
"./llama.cpp",
|
| 57 |
+
os.path.expanduser("~/llama.cpp"),
|
| 58 |
+
"C:/llama.cpp",
|
| 59 |
+
"D:/llama.cpp",
|
| 60 |
+
os.path.join(os.path.dirname(__file__), "..", "llama.cpp"),
|
| 61 |
+
os.path.join(os.path.dirname(__file__), "..", "..", "llama.cpp"),
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
for path in possible_paths:
|
| 65 |
+
path = os.path.abspath(path)
|
| 66 |
+
convert_script = os.path.join(path, "convert-hf-to-gguf.py")
|
| 67 |
+
if os.path.exists(convert_script):
|
| 68 |
+
logger.info(f"Auto-detected llama.cpp: {path}")
|
| 69 |
+
return path
|
| 70 |
+
|
| 71 |
+
# 3. Not found
|
| 72 |
+
raise FileNotFoundError(
|
| 73 |
+
"llama.cpp not found. Set LLAMA_CPP_DIR or download to common path.\n"
|
| 74 |
+
"Download: https://github.com/ggerganov/llama.cpp"
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def check_dependencies() -> bool:
|
| 79 |
+
"""
|
| 80 |
+
Check dependencies (auto-detect llama.cpp + Windows compatible)
|
| 81 |
+
|
| 82 |
+
Returns:
|
| 83 |
+
Whether dependencies are satisfied
|
| 84 |
+
"""
|
| 85 |
+
logger.info("Checking dependencies...")
|
| 86 |
+
|
| 87 |
+
# 1. Check llama.cpp
|
| 88 |
+
try:
|
| 89 |
+
llama_cpp_dir = find_llama_cpp()
|
| 90 |
+
convert_script = os.path.join(llama_cpp_dir, "convert-hf-to-gguf.py")
|
| 91 |
+
logger.info(f"llama.cpp convert script found: {convert_script}")
|
| 92 |
+
except FileNotFoundError as e:
|
| 93 |
+
logger.error(f"llama.cpp not found: {e}")
|
| 94 |
+
return False
|
| 95 |
+
|
| 96 |
+
# 2. Check Ollama (Windows needs shell=True)
|
| 97 |
+
try:
|
| 98 |
+
result = subprocess.run(
|
| 99 |
+
["ollama", "--version"],
|
| 100 |
+
capture_output=True,
|
| 101 |
+
text=True,
|
| 102 |
+
shell=True, # Windows needs shell=True
|
| 103 |
+
timeout=10,
|
| 104 |
+
)
|
| 105 |
+
if result.returncode == 0:
|
| 106 |
+
logger.info(f"Ollama installed: {result.stdout.strip()}")
|
| 107 |
+
else:
|
| 108 |
+
logger.warning("Ollama not installed or not working")
|
| 109 |
+
logger.warning("Please install from https://ollama.com")
|
| 110 |
+
return False
|
| 111 |
+
except FileNotFoundError:
|
| 112 |
+
logger.warning("Ollama not installed")
|
| 113 |
+
logger.warning("Please install from https://ollama.com")
|
| 114 |
+
return False
|
| 115 |
+
except subprocess.TimeoutExpired:
|
| 116 |
+
logger.warning("Ollama check timeout")
|
| 117 |
+
return False
|
| 118 |
+
|
| 119 |
+
logger.info("All dependencies satisfied")
|
| 120 |
+
return True
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def convert_to_gguf(
|
| 124 |
+
model_path: str,
|
| 125 |
+
output_path: str,
|
| 126 |
+
quantize: str = "q4_k_m",
|
| 127 |
+
) -> str:
|
| 128 |
+
"""
|
| 129 |
+
Convert HuggingFace model to GGUF format
|
| 130 |
+
|
| 131 |
+
Args:
|
| 132 |
+
model_path: HuggingFace model path
|
| 133 |
+
output_path: Output path
|
| 134 |
+
quantize: Quantization level (q4_k_m, q5_k_m, q8_0, etc.)
|
| 135 |
+
|
| 136 |
+
Returns:
|
| 137 |
+
Converted GGUF file path
|
| 138 |
+
"""
|
| 139 |
+
logger.info("Converting to GGUF format...")
|
| 140 |
+
|
| 141 |
+
llama_cpp_dir = find_llama_cpp()
|
| 142 |
+
convert_script = os.path.join(llama_cpp_dir, "convert-hf-to-gguf.py")
|
| 143 |
+
|
| 144 |
+
# Ensure output directory exists
|
| 145 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 146 |
+
|
| 147 |
+
# Conversion command
|
| 148 |
+
cmd = [
|
| 149 |
+
sys.executable, # Use current Python interpreter
|
| 150 |
+
convert_script,
|
| 151 |
+
model_path,
|
| 152 |
+
"--outtype", "f16", # Convert to f16 first
|
| 153 |
+
"--outfile", output_path,
|
| 154 |
+
]
|
| 155 |
+
|
| 156 |
+
logger.info(f"Running command: {' '.join(cmd)}")
|
| 157 |
+
|
| 158 |
+
result = subprocess.run(
|
| 159 |
+
cmd,
|
| 160 |
+
capture_output=True,
|
| 161 |
+
text=True,
|
| 162 |
+
shell=True, # Windows needs shell=True
|
| 163 |
+
timeout=600, # 10 minutes timeout
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
if result.returncode != 0:
|
| 167 |
+
logger.error(f"Conversion failed: {result.stderr}")
|
| 168 |
+
raise RuntimeError(f"GGUF conversion failed: {result.stderr}")
|
| 169 |
+
|
| 170 |
+
logger.info(f"GGUF conversion complete: {output_path}")
|
| 171 |
+
|
| 172 |
+
# Quantization (optional)
|
| 173 |
+
if quantize and quantize != "f16":
|
| 174 |
+
logger.info(f"Quantizing model ({quantize})...")
|
| 175 |
+
|
| 176 |
+
quantized_path = output_path.replace(".gguf", f"_{quantize}.gguf")
|
| 177 |
+
quantize_cmd = [
|
| 178 |
+
os.path.join(llama_cpp_dir, "llama-quantize"),
|
| 179 |
+
output_path,
|
| 180 |
+
quantized_path,
|
| 181 |
+
quantize,
|
| 182 |
+
]
|
| 183 |
+
|
| 184 |
+
result = subprocess.run(
|
| 185 |
+
quantize_cmd,
|
| 186 |
+
capture_output=True,
|
| 187 |
+
text=True,
|
| 188 |
+
shell=True,
|
| 189 |
+
timeout=300,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
if result.returncode != 0:
|
| 193 |
+
logger.warning(f"Quantization failed: {result.stderr}")
|
| 194 |
+
logger.warning("Using unquantized model")
|
| 195 |
+
else:
|
| 196 |
+
output_path = quantized_path
|
| 197 |
+
logger.info(f"Quantization complete: {output_path}")
|
| 198 |
+
|
| 199 |
+
return output_path
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def create_modelfile(
|
| 203 |
+
model_path: str,
|
| 204 |
+
modelfile_path: str,
|
| 205 |
+
model_name: str,
|
| 206 |
+
context_size: int = 32768,
|
| 207 |
+
thinking_dial: bool = True,
|
| 208 |
+
):
|
| 209 |
+
"""
|
| 210 |
+
Create Ollama Modelfile
|
| 211 |
+
|
| 212 |
+
Args:
|
| 213 |
+
model_path: GGUF model path
|
| 214 |
+
modelfile_path: Modelfile output path
|
| 215 |
+
model_name: Model name
|
| 216 |
+
context_size: Context window size
|
| 217 |
+
thinking_dial: Whether to enable Thinking Dial
|
| 218 |
+
"""
|
| 219 |
+
logger.info("Creating Modelfile...")
|
| 220 |
+
|
| 221 |
+
# Get absolute path of model file
|
| 222 |
+
model_path_abs = os.path.abspath(model_path)
|
| 223 |
+
|
| 224 |
+
# Modelfile content
|
| 225 |
+
content = f"""# Fusion Model: {model_name}
|
| 226 |
+
# Auto-generated by Fusion project
|
| 227 |
+
# Project: https://github.com/zhan1206/fusion-llm
|
| 228 |
+
|
| 229 |
+
FROM {model_path_abs}
|
| 230 |
+
|
| 231 |
+
# Model parameters
|
| 232 |
+
PARAMETER num_ctx {context_size}
|
| 233 |
+
PARAMETER temperature 0.8
|
| 234 |
+
PARAMETER top_p 0.95
|
| 235 |
+
PARAMETER repeat_penalty 1.1
|
| 236 |
+
|
| 237 |
+
# System prompt
|
| 238 |
+
SYSTEM \"\"\"You are a powerful AI assistant. You support dynamic reasoning intensity control:
|
| 239 |
+
|
| 240 |
+
- Simple questions: direct answer
|
| 241 |
+
- Complex questions: enable chain-of-thought reasoning
|
| 242 |
+
|
| 243 |
+
Use <|think| depth=N|> to control reasoning depth (N=0-3).
|
| 244 |
+
\"\"\"
|
| 245 |
+
|
| 246 |
+
# Template (supports Thinking Dial)
|
| 247 |
+
TEMPLATE \"\"\"{{{{ if .System }}}}<|im_start|>system
|
| 248 |
+
{{{{ .System }}}}<|im_end|>
|
| 249 |
+
{{{{ end }}}}{{{{ if .Prompt }}}}<|im_start|>user
|
| 250 |
+
{{{{ .Prompt }}}}<|im_end|>
|
| 251 |
+
{{{{ end }}}}<|im_start|>assistant
|
| 252 |
+
\"\"\"
|
| 253 |
+
"""
|
| 254 |
+
|
| 255 |
+
# If Thinking Dial is enabled, add special token handling
|
| 256 |
+
if thinking_dial:
|
| 257 |
+
content += f"""
|
| 258 |
+
# Thinking Dial examples (injected during training)
|
| 259 |
+
# <|think| depth=0|> Simple question, direct answer
|
| 260 |
+
# <|think| depth=3|> Complex question, detailed reasoning
|
| 261 |
+
"""
|
| 262 |
+
|
| 263 |
+
# Write file
|
| 264 |
+
with open(modelfile_path, 'w', encoding='utf-8') as f:
|
| 265 |
+
f.write(content)
|
| 266 |
+
|
| 267 |
+
logger.info(f"Modelfile created: {modelfile_path}")
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def create_ollama_model(modelfile_path: str, model_name: str) -> bool:
|
| 271 |
+
"""
|
| 272 |
+
Create Ollama model using Modelfile
|
| 273 |
+
|
| 274 |
+
Args:
|
| 275 |
+
modelfile_path: Modelfile path
|
| 276 |
+
model_name: Model name
|
| 277 |
+
|
| 278 |
+
Returns:
|
| 279 |
+
Whether creation succeeded
|
| 280 |
+
"""
|
| 281 |
+
logger.info(f"Creating Ollama model: {model_name}...")
|
| 282 |
+
|
| 283 |
+
# Remove existing model
|
| 284 |
+
subprocess.run(
|
| 285 |
+
["ollama", "rm", model_name],
|
| 286 |
+
capture_output=True,
|
| 287 |
+
shell=True,
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
# Create model
|
| 291 |
+
cmd = ["ollama", "create", model_name, "-f", modelfile_path]
|
| 292 |
+
|
| 293 |
+
logger.info(f"Running command: {' '.join(cmd)}")
|
| 294 |
+
|
| 295 |
+
result = subprocess.run(
|
| 296 |
+
cmd,
|
| 297 |
+
capture_output=True,
|
| 298 |
+
text=True,
|
| 299 |
+
shell=True,
|
| 300 |
+
timeout=300,
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
if result.returncode != 0:
|
| 304 |
+
logger.error(f"Creation failed: {result.stderr}")
|
| 305 |
+
return False
|
| 306 |
+
|
| 307 |
+
logger.info(f"Ollama model created: {model_name}")
|
| 308 |
+
logger.info(f"Run `ollama run {model_name}` to start using")
|
| 309 |
+
return True
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def deploy(
|
| 313 |
+
model_path: str,
|
| 314 |
+
model_name: str,
|
| 315 |
+
output_dir: str = "./ollama_output",
|
| 316 |
+
quantize: str = "q4_k_m",
|
| 317 |
+
context_size: int = 32768,
|
| 318 |
+
thinking_dial: bool = True,
|
| 319 |
+
) -> bool:
|
| 320 |
+
"""
|
| 321 |
+
Complete deployment pipeline
|
| 322 |
+
|
| 323 |
+
Args:
|
| 324 |
+
model_path: HuggingFace model path
|
| 325 |
+
model_name: Model name
|
| 326 |
+
output_dir: Output directory
|
| 327 |
+
quantize: Quantization level
|
| 328 |
+
context_size: Context window
|
| 329 |
+
thinking_dial: Whether to enable Thinking Dial
|
| 330 |
+
|
| 331 |
+
Returns:
|
| 332 |
+
Whether deployment succeeded
|
| 333 |
+
"""
|
| 334 |
+
logger.info("Starting Ollama deployment...")
|
| 335 |
+
logger.info(f"Model path: {model_path}")
|
| 336 |
+
logger.info(f"Model name: {model_name}")
|
| 337 |
+
|
| 338 |
+
# 1. Check dependencies
|
| 339 |
+
if not check_dependencies():
|
| 340 |
+
logger.error("Dependency check failed")
|
| 341 |
+
return False
|
| 342 |
+
|
| 343 |
+
# 2. Create output directory
|
| 344 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 345 |
+
|
| 346 |
+
# 3. Convert to GGUF
|
| 347 |
+
gguf_path = os.path.join(output_dir, f"{model_name}.gguf")
|
| 348 |
+
try:
|
| 349 |
+
gguf_path = convert_to_gguf(
|
| 350 |
+
model_path=model_path,
|
| 351 |
+
output_path=gguf_path,
|
| 352 |
+
quantize=quantize,
|
| 353 |
+
)
|
| 354 |
+
except RuntimeError as e:
|
| 355 |
+
logger.error(f"GGUF conversion failed: {e}")
|
| 356 |
+
return False
|
| 357 |
+
|
| 358 |
+
# 4. Create Modelfile
|
| 359 |
+
modelfile_path = os.path.join(output_dir, "Modelfile")
|
| 360 |
+
create_modelfile(
|
| 361 |
+
model_path=gguf_path,
|
| 362 |
+
modelfile_path=modelfile_path,
|
| 363 |
+
model_name=model_name,
|
| 364 |
+
context_size=context_size,
|
| 365 |
+
thinking_dial=thinking_dial,
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
# 5. Create Ollama model
|
| 369 |
+
if not create_ollama_model(
|
| 370 |
+
modelfile_path=modelfile_path,
|
| 371 |
+
model_name=model_name,
|
| 372 |
+
):
|
| 373 |
+
logger.error("Ollama model creation failed")
|
| 374 |
+
return False
|
| 375 |
+
|
| 376 |
+
# 6. Generate usage example
|
| 377 |
+
example_path = os.path.join(output_dir, "USAGE.md")
|
| 378 |
+
generate_usage_example(model_name, example_path)
|
| 379 |
+
|
| 380 |
+
logger.info("Deployment complete!")
|
| 381 |
+
logger.info(f"Run: `ollama run {model_name}`")
|
| 382 |
+
logger.info(f"Examples: see {example_path}")
|
| 383 |
+
|
| 384 |
+
return True
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def generate_usage_example(model_name: str, output_path: str):
|
| 388 |
+
"""
|
| 389 |
+
Generate usage example document
|
| 390 |
+
"""
|
| 391 |
+
content = f"""# Fusion Model Usage Examples
|
| 392 |
+
|
| 393 |
+
## 1. Basic Usage
|
| 394 |
+
|
| 395 |
+
```bash
|
| 396 |
+
# Start model
|
| 397 |
+
ollama run {model_name}
|
| 398 |
+
|
| 399 |
+
# Input question in interactive interface
|
| 400 |
+
> Explain quantum entanglement
|
| 401 |
+
```
|
| 402 |
+
|
| 403 |
+
## 2. Thinking Dial Control
|
| 404 |
+
|
| 405 |
+
Fusion supports dynamic reasoning intensity control. Add control token before question:
|
| 406 |
+
|
| 407 |
+
```bash
|
| 408 |
+
# depth=0: direct answer (casual chat, translation)
|
| 409 |
+
> <|think| depth=0|> How's the weather today?
|
| 410 |
+
|
| 411 |
+
# depth=1: simple reasoning
|
| 412 |
+
> <|think| depth=1|> Calculate 123 * 456
|
| 413 |
+
|
| 414 |
+
# depth=2: medium reasoning
|
| 415 |
+
> <|think| depth=2|> Prove Pythagorean theorem
|
| 416 |
+
|
| 417 |
+
# depth=3: deep reasoning (chain-of-thought)
|
| 418 |
+
> <|think| depth=3|> Solve this algorithm problem: ...
|
| 419 |
+
```
|
| 420 |
+
|
| 421 |
+
## 3. REST API
|
| 422 |
+
|
| 423 |
+
Ollama provides OpenAI-compatible API:
|
| 424 |
+
|
| 425 |
+
```bash
|
| 426 |
+
# Start Ollama service
|
| 427 |
+
ollama serve
|
| 428 |
+
|
| 429 |
+
# Call API
|
| 430 |
+
curl <a href="http://localhost:11434/api/generate">http://localhost:11434/api/generate</a> -d {{{{
|
| 431 |
+
"model": "{model_name}",
|
| 432 |
+
"prompt": "Explain machine learning",
|
| 433 |
+
"stream": false
|
| 434 |
+
}}}}
|
| 435 |
+
```
|
| 436 |
+
|
| 437 |
+
## 4. Python Call
|
| 438 |
+
|
| 439 |
+
```python
|
| 440 |
+
import ollama
|
| 441 |
+
|
| 442 |
+
# Basic call
|
| 443 |
+
response = ollama.generate(
|
| 444 |
+
model="{model_name}",
|
| 445 |
+
prompt="Explain quantum entanglement",
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
print(response["response"])
|
| 449 |
+
|
| 450 |
+
# With Thinking Dial
|
| 451 |
+
response = ollama.generate(
|
| 452 |
+
model="{model_name}",
|
| 453 |
+
prompt="<|think| depth=2|> Prove Pythagorean theorem",
|
| 454 |
+
)
|
| 455 |
+
|
| 456 |
+
print(response["response"])
|
| 457 |
+
```
|
| 458 |
+
|
| 459 |
+
## 5. Parameter Tuning
|
| 460 |
+
|
| 461 |
+
Adjust generation parameters in Ollama:
|
| 462 |
+
|
| 463 |
+
```bash
|
| 464 |
+
# Temperature (creativity)
|
| 465 |
+
ollama run {model_name} --temperature 0.9
|
| 466 |
+
|
| 467 |
+
# Context window
|
| 468 |
+
ollama run {model_name} --num_ctx 16384
|
| 469 |
+
|
| 470 |
+
# Top-p sampling
|
| 471 |
+
ollama run {model_name} --top_p 0.95
|
| 472 |
+
```
|
| 473 |
+
|
| 474 |
+
---
|
| 475 |
+
|
| 476 |
+
**Tip**: See Ollama docs for more: https://ollama.com/docs
|
| 477 |
+
"""
|
| 478 |
+
|
| 479 |
+
with open(output_path, 'w', encoding='utf-8') as f:
|
| 480 |
+
f.write(content)
|
| 481 |
+
|
| 482 |
+
logger.info(f"Usage example generated: {output_path}")
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
def main():
|
| 486 |
+
parser = argparse.ArgumentParser(description="Fusion Ollama One-Click Deployment (v2)")
|
| 487 |
+
|
| 488 |
+
parser.add_argument("--model_path", type=str, required=True,
|
| 489 |
+
help="HuggingFace model path")
|
| 490 |
+
parser.add_argument("--model_name", type=str, required=True,
|
| 491 |
+
help="Ollama model name (e.g., fusion-8b)")
|
| 492 |
+
parser.add_argument("--output_dir", type=str, default="./ollama_output",
|
| 493 |
+
help="Output directory")
|
| 494 |
+
parser.add_argument("--quantize", type=str, default="q4_k_m",
|
| 495 |
+
choices=["q4_k_m", "q5_k_m", "q8_0", "f16"],
|
| 496 |
+
help="Quantization level")
|
| 497 |
+
parser.add_argument("--context_size", type=int, default=32768,
|
| 498 |
+
help="Context window size")
|
| 499 |
+
parser.add_argument("--no_thinking_dial", action="store_false",
|
| 500 |
+
dest="thinking_dial",
|
| 501 |
+
help="Disable Thinking Dial")
|
| 502 |
+
|
| 503 |
+
args = parser.parse_args()
|
| 504 |
+
|
| 505 |
+
# Execute deployment
|
| 506 |
+
success = deploy(
|
| 507 |
+
model_path=args.model_path,
|
| 508 |
+
model_name=args.model_name,
|
| 509 |
+
output_dir=args.output_dir,
|
| 510 |
+
quantize=args.quantize,
|
| 511 |
+
context_size=args.context_size,
|
| 512 |
+
thinking_dial=args.thinking_dial,
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
if success:
|
| 516 |
+
logger.info("Deployment successful!")
|
| 517 |
+
else:
|
| 518 |
+
logger.error("Deployment failed")
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
if __name__ == "__main__":
|
| 522 |
+
main()
|
|
@@ -118,9 +118,14 @@ class RMSNorm(nn.Module):
|
|
| 118 |
|
| 119 |
class FusionAttention(nn.Module):
|
| 120 |
"""
|
| 121 |
-
Fusion
|
| 122 |
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
"""
|
| 125 |
|
| 126 |
def __init__(self, config: FusionConfig):
|
|
@@ -220,7 +225,9 @@ class FusionAttention(nn.Module):
|
|
| 220 |
self,
|
| 221 |
hidden_states: torch.Tensor,
|
| 222 |
attention_mask: Optional[torch.Tensor] = None,
|
| 223 |
-
|
|
|
|
|
|
|
| 224 |
batch_size, seq_len, _ = hidden_states.shape
|
| 225 |
device = hidden_states.device
|
| 226 |
|
|
@@ -229,6 +236,14 @@ class FusionAttention(nn.Module):
|
|
| 229 |
K = self.k_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 230 |
V = self.v_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
# 构建注意力掩码
|
| 233 |
causal_mask = self._build_causal_mask(seq_len, device)
|
| 234 |
window_mask = self._build_window_mask(seq_len, self.block_size, device)
|
|
@@ -274,7 +289,7 @@ class FusionAttention(nn.Module):
|
|
| 274 |
output = output_std + gate_value * latent_expanded
|
| 275 |
|
| 276 |
output = self.LayerNorm(output)
|
| 277 |
-
return self.dropout(output)
|
| 278 |
|
| 279 |
|
| 280 |
class FusionLayer(nn.Module):
|
|
@@ -298,11 +313,18 @@ class FusionLayer(nn.Module):
|
|
| 298 |
self,
|
| 299 |
hidden_states: torch.Tensor,
|
| 300 |
attention_mask: Optional[torch.Tensor] = None,
|
|
|
|
|
|
|
| 301 |
**kwargs,
|
| 302 |
-
) -> Tuple[torch.Tensor,
|
| 303 |
residual = hidden_states
|
| 304 |
hidden_states = self.input_layernorm(hidden_states)
|
| 305 |
-
attn_output = self.attention(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
hidden_states = residual + self.dropout(attn_output)
|
| 307 |
|
| 308 |
residual = hidden_states
|
|
@@ -312,7 +334,7 @@ class FusionLayer(nn.Module):
|
|
| 312 |
ffn_output = self.down_proj(gate * up)
|
| 313 |
hidden_states = residual + self.dropout(ffn_output)
|
| 314 |
|
| 315 |
-
return hidden_states,
|
| 316 |
|
| 317 |
|
| 318 |
class FusionModel(PreTrainedModel, GenerationMixin):
|
|
@@ -381,9 +403,23 @@ class FusionModel(PreTrainedModel, GenerationMixin):
|
|
| 381 |
float_mask = attention_mask.to(dtype=hidden_states.dtype)
|
| 382 |
attention_mask = (1.0 - float_mask) * torch.finfo(hidden_states.dtype).min
|
| 383 |
|
| 384 |
-
# Transformer 层
|
| 385 |
-
|
| 386 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
|
| 388 |
# Final norm
|
| 389 |
hidden_states = self.norm(hidden_states)
|
|
@@ -399,6 +435,9 @@ class FusionModel(PreTrainedModel, GenerationMixin):
|
|
| 399 |
loss_fct = nn.CrossEntropyLoss()
|
| 400 |
loss = loss_fct(shift_logits.view(-1, self.config.vocab_size), shift_labels.view(-1))
|
| 401 |
|
|
|
|
|
|
|
|
|
|
| 402 |
if not return_dict:
|
| 403 |
return (loss, logits) if loss is not None else (logits,)
|
| 404 |
|
|
@@ -438,8 +477,7 @@ class FusionModel(PreTrainedModel, GenerationMixin):
|
|
| 438 |
)
|
| 439 |
|
| 440 |
logits = outputs["logits"]
|
| 441 |
-
|
| 442 |
-
past_key_values = outputs["past_key_values"]
|
| 443 |
|
| 444 |
next_token_logits = logits[:, -1, :] / max(temperature, 1e-8)
|
| 445 |
|
|
|
|
| 118 |
|
| 119 |
class FusionAttention(nn.Module):
|
| 120 |
"""
|
| 121 |
+
Fusion Attention Layer with integrated SBLA.
|
| 122 |
|
| 123 |
+
NOTE (M4): This is a standalone reimplementation. The canonical SBLA logic
|
| 124 |
+
lives in sbla_attention.py (SBLAttention class). Future work should unify
|
| 125 |
+
by having FusionAttention delegate to SBLAttention instead of duplicating
|
| 126 |
+
mask building and block latent computation logic.
|
| 127 |
+
|
| 128 |
+
See: models/sbla_attention.py::SBLAttention
|
| 129 |
"""
|
| 130 |
|
| 131 |
def __init__(self, config: FusionConfig):
|
|
|
|
| 225 |
self,
|
| 226 |
hidden_states: torch.Tensor,
|
| 227 |
attention_mask: Optional[torch.Tensor] = None,
|
| 228 |
+
past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 229 |
+
use_cache: bool = False,
|
| 230 |
+
) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
|
| 231 |
batch_size, seq_len, _ = hidden_states.shape
|
| 232 |
device = hidden_states.device
|
| 233 |
|
|
|
|
| 236 |
K = self.k_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 237 |
V = self.v_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 238 |
|
| 239 |
+
# KV Cache 逻辑
|
| 240 |
+
if past_key_value is not None:
|
| 241 |
+
past_k, past_v = past_key_value
|
| 242 |
+
K = torch.cat([past_k, K], dim=2)
|
| 243 |
+
V = torch.cat([past_v, V], dim=2)
|
| 244 |
+
|
| 245 |
+
present_key_value = (K, V) if use_cache else None
|
| 246 |
+
|
| 247 |
# 构建注意力掩码
|
| 248 |
causal_mask = self._build_causal_mask(seq_len, device)
|
| 249 |
window_mask = self._build_window_mask(seq_len, self.block_size, device)
|
|
|
|
| 289 |
output = output_std + gate_value * latent_expanded
|
| 290 |
|
| 291 |
output = self.LayerNorm(output)
|
| 292 |
+
return self.dropout(output), present_key_value
|
| 293 |
|
| 294 |
|
| 295 |
class FusionLayer(nn.Module):
|
|
|
|
| 313 |
self,
|
| 314 |
hidden_states: torch.Tensor,
|
| 315 |
attention_mask: Optional[torch.Tensor] = None,
|
| 316 |
+
past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 317 |
+
use_cache: bool = False,
|
| 318 |
**kwargs,
|
| 319 |
+
) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
|
| 320 |
residual = hidden_states
|
| 321 |
hidden_states = self.input_layernorm(hidden_states)
|
| 322 |
+
attn_output, present_key_value = self.attention(
|
| 323 |
+
hidden_states,
|
| 324 |
+
attention_mask,
|
| 325 |
+
past_key_value=past_key_value if past_key_value is not None else None,
|
| 326 |
+
use_cache=use_cache,
|
| 327 |
+
)
|
| 328 |
hidden_states = residual + self.dropout(attn_output)
|
| 329 |
|
| 330 |
residual = hidden_states
|
|
|
|
| 334 |
ffn_output = self.down_proj(gate * up)
|
| 335 |
hidden_states = residual + self.dropout(ffn_output)
|
| 336 |
|
| 337 |
+
return hidden_states, present_key_value
|
| 338 |
|
| 339 |
|
| 340 |
class FusionModel(PreTrainedModel, GenerationMixin):
|
|
|
|
| 403 |
float_mask = attention_mask.to(dtype=hidden_states.dtype)
|
| 404 |
attention_mask = (1.0 - float_mask) * torch.finfo(hidden_states.dtype).min
|
| 405 |
|
| 406 |
+
# Transformer 层(支持 KV Cache)
|
| 407 |
+
past_key_values = kwargs.get("past_key_values", None)
|
| 408 |
+
use_cache = kwargs.get("use_cache", False) or (past_key_values is not None)
|
| 409 |
+
|
| 410 |
+
present_key_values = () if use_cache else None
|
| 411 |
+
|
| 412 |
+
for i, layer in enumerate(self.layers):
|
| 413 |
+
layer_past = past_key_values[i] if past_key_values is not None else None
|
| 414 |
+
layer_outputs, cache = layer(
|
| 415 |
+
hidden_states,
|
| 416 |
+
attention_mask=attention_mask,
|
| 417 |
+
past_key_value=layer_past,
|
| 418 |
+
use_cache=use_cache,
|
| 419 |
+
)
|
| 420 |
+
hidden_states = layer_outputs
|
| 421 |
+
if use_cache:
|
| 422 |
+
present_key_values = present_key_values + (cache,)
|
| 423 |
|
| 424 |
# Final norm
|
| 425 |
hidden_states = self.norm(hidden_states)
|
|
|
|
| 435 |
loss_fct = nn.CrossEntropyLoss()
|
| 436 |
loss = loss_fct(shift_logits.view(-1, self.config.vocab_size), shift_labels.view(-1))
|
| 437 |
|
| 438 |
+
if use_cache:
|
| 439 |
+
return {"loss": loss, "logits": logits, "past_key_values": present_key_values}
|
| 440 |
+
|
| 441 |
if not return_dict:
|
| 442 |
return (loss, logits) if loss is not None else (logits,)
|
| 443 |
|
|
|
|
| 477 |
)
|
| 478 |
|
| 479 |
logits = outputs["logits"]
|
| 480 |
+
past_key_values = outputs.get("past_key_values", None)
|
|
|
|
| 481 |
|
| 482 |
next_token_logits = logits[:, -1, :] / max(temperature, 1e-8)
|
| 483 |
|
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Fusion Tokenizer - Unified tokenizer management
|
| 3 |
+
|
| 4 |
+
Handles the gap between GPT2 (vocab_size=50257) and Fusion's target vocab (100K).
|
| 5 |
+
Current status: Uses GPT2 as placeholder until SentencePiece model is trained.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
from models.tokenizer import get_tokenizer
|
| 9 |
+
tokenizer = get_tokenizer("gpt2") # placeholder
|
| 10 |
+
tokenizer = get_tokenizer("fusion", vocab_size=100000) # future: SentencePiece
|
| 11 |
+
|
| 12 |
+
Author: Zhu Zizhan
|
| 13 |
+
Project: Fusion-LLM
|
| 14 |
+
License: Apache 2.0
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from typing import Optional
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
from transformers import AutoTokenizer, PreTrainedTokenizer
|
| 24 |
+
except ImportError:
|
| 25 |
+
AutoTokenizer = None
|
| 26 |
+
PreTrainedTokenizer = None
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Fusion special tokens
|
| 30 |
+
FUSION_SPECIAL_TOKENS = {
|
| 31 |
+
"pad_token": "<|pad|>",
|
| 32 |
+
"bos_token": "<|start|>",
|
| 33 |
+
"eos_token": "<|end|>",
|
| 34 |
+
"think_tokens": ["<|think_depth_0|>", "<|think_depth_1|>", "<|think_depth_2|>", "<|think_depth_3|>"],
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def get_tokenizer(
|
| 39 |
+
tokenizer_type: str = "gpt2",
|
| 40 |
+
vocab_size: int = 50257,
|
| 41 |
+
tokenizer_dir: Optional[str] = None,
|
| 42 |
+
) -> "PreTrainedTokenizer":
|
| 43 |
+
"""
|
| 44 |
+
Get a tokenizer for Fusion models.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
tokenizer_type: "gpt2" (placeholder) or "fusion" (SentencePiece, if available)
|
| 48 |
+
vocab_size: Target vocabulary size
|
| 49 |
+
tokenizer_dir: Directory containing tokenizer files (for "fusion" type)
|
| 50 |
+
|
| 51 |
+
Returns:
|
| 52 |
+
A HuggingFace PreTrainedTokenizer instance
|
| 53 |
+
|
| 54 |
+
Notes:
|
| 55 |
+
- "gpt2" mode: Uses GPT2 BPE tokenizer (vocab_size=50257). This is a
|
| 56 |
+
PLACEHOLDER. The model config should set vocab_size=50257 when using this.
|
| 57 |
+
- "fusion" mode: Loads a SentencePiece tokenizer from tokenizer_dir.
|
| 58 |
+
Requires tokenizer.model file to exist. Falls back to GPT2 if not found.
|
| 59 |
+
"""
|
| 60 |
+
if AutoTokenizer is None:
|
| 61 |
+
raise ImportError("transformers is required: pip install transformers")
|
| 62 |
+
|
| 63 |
+
if tokenizer_type == "fusion":
|
| 64 |
+
sp_model_path = None
|
| 65 |
+
if tokenizer_dir:
|
| 66 |
+
sp_model_path = Path(tokenizer_dir) / "tokenizer.model"
|
| 67 |
+
else:
|
| 68 |
+
# Try project root
|
| 69 |
+
for candidate in ["tokenizers", ".", "data"]:
|
| 70 |
+
p = Path(candidate) / "tokenizer.model"
|
| 71 |
+
if p.exists():
|
| 72 |
+
sp_model_path = p
|
| 73 |
+
break
|
| 74 |
+
|
| 75 |
+
if sp_model_path and sp_model_path.exists():
|
| 76 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 77 |
+
str(sp_model_path.parent),
|
| 78 |
+
tokenizer_type="SentencePiece",
|
| 79 |
+
)
|
| 80 |
+
tokenizer = _add_fusion_special_tokens(tokenizer)
|
| 81 |
+
return tokenizer
|
| 82 |
+
else:
|
| 83 |
+
import warnings
|
| 84 |
+
warnings.warn(
|
| 85 |
+
"Fusion SentencePiece tokenizer not found. "
|
| 86 |
+
"Falling back to GPT2 tokenizer. "
|
| 87 |
+
"Set vocab_size=50257 in model config to match.",
|
| 88 |
+
UserWarning,
|
| 89 |
+
)
|
| 90 |
+
tokenizer_type = "gpt2"
|
| 91 |
+
vocab_size = 50257
|
| 92 |
+
|
| 93 |
+
if tokenizer_type == "gpt2":
|
| 94 |
+
tokenizer = AutoTokenizer.from_pretrained("gpt2")
|
| 95 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 96 |
+
tokenizer = _add_fusion_special_tokens(tokenizer)
|
| 97 |
+
|
| 98 |
+
# Verify vocab size consistency
|
| 99 |
+
actual_vocab = len(tokenizer)
|
| 100 |
+
if actual_vocab != vocab_size:
|
| 101 |
+
import warnings
|
| 102 |
+
warnings.warn(
|
| 103 |
+
f"GPT2 tokenizer vocab_size={actual_vocab}, but config specifies {vocab_size}. "
|
| 104 |
+
f"Using actual tokenizer size ({actual_vocab}). "
|
| 105 |
+
f"Update model config vocab_size to match.",
|
| 106 |
+
UserWarning,
|
| 107 |
+
)
|
| 108 |
+
return tokenizer
|
| 109 |
+
|
| 110 |
+
raise ValueError(f"Unknown tokenizer_type: {tokenizer_type}")
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _add_fusion_special_tokens(tokenizer: "PreTrainedTokenizer") -> "PreTrainedTokenizer":
|
| 114 |
+
"""Add Fusion-specific special tokens to any tokenizer."""
|
| 115 |
+
special_tokens_dict = {
|
| 116 |
+
"pad_token": FUSION_SPECIAL_TOKENS["pad_token"],
|
| 117 |
+
"additional_special_tokens": FUSION_SPECIAL_TOKENS["think_tokens"],
|
| 118 |
+
}
|
| 119 |
+
tokenizer.add_special_tokens(special_tokens_dict)
|
| 120 |
+
return tokenizer
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def get_effective_vocab_size(tokenizer_type: str = "gpt2", requested_vocab: int = 100000) -> int:
|
| 124 |
+
"""
|
| 125 |
+
Return the effective vocab size that should be used in model config.
|
| 126 |
+
This ensures model embedding size matches the actual tokenizer.
|
| 127 |
+
"""
|
| 128 |
+
if tokenizer_type == "gpt2":
|
| 129 |
+
return 50257 + len(FUSION_SPECIAL_TOKENS["think_tokens"]) + 1 # ~50262
|
| 130 |
+
if tokenizer_type == "fusion":
|
| 131 |
+
return requested_vocab
|
| 132 |
+
return requested_vocab
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
if __name__ == "__main__":
|
| 136 |
+
print("[Fusion Tokenizer] Testing tokenizer creation...")
|
| 137 |
+
tok = get_tokenizer("gpt2")
|
| 138 |
+
print(f" Type: GPT2 (placeholder)")
|
| 139 |
+
print(f" Vocab size: {len(tok)}")
|
| 140 |
+
print(f" Pad token: {tok.pad_token}")
|
| 141 |
+
print(f" Think tokens: {FUSION_SPECIAL_TOKENS['think_tokens']}")
|
| 142 |
+
print(f" Effective vocab: {get_effective_vocab_size('gpt2')}")
|
| 143 |
+
test_text = "Hello, Fusion! <|think_depth_2|>"
|
| 144 |
+
encoded = tok.encode(test_text)
|
| 145 |
+
decoded = tok.decode(encoded)
|
| 146 |
+
print(f" Encode/decode test: '{test_text}' -> {encoded} -> '{decoded}'")
|
|
@@ -1,228 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Fusion 项目 GitHub 推送脚本
|
| 3 |
-
|
| 4 |
-
使用方法(在你本地电脑运行):
|
| 5 |
-
1. 安装依赖:pip install requests
|
| 6 |
-
2. 运行脚本:python push_to_github.py
|
| 7 |
-
3. 按提示输入 GitHub token(不会显示在聊天中)
|
| 8 |
-
|
| 9 |
-
注意:Token 需要 repo 权限(全选)
|
| 10 |
-
创建 Token:https://github.com/settings/tokens
|
| 11 |
-
|
| 12 |
-
作者:朱子瞻
|
| 13 |
-
项目:Fusion - 六边形开源大模型
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
import os
|
| 17 |
-
import json
|
| 18 |
-
import subprocess
|
| 19 |
-
import getpass
|
| 20 |
-
import requests
|
| 21 |
-
from pathlib import Path
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
def create_github_repo(token: str, repo_name: str = "fusion-llm", private: bool = False):
|
| 25 |
-
"""
|
| 26 |
-
使用 GitHub API 创建仓库
|
| 27 |
-
|
| 28 |
-
参数:
|
| 29 |
-
token: GitHub Personal Access Token
|
| 30 |
-
repo_name: 仓库名称
|
| 31 |
-
private: 是否私有(默认 False,公开)
|
| 32 |
-
|
| 33 |
-
返回:
|
| 34 |
-
仓库 URL
|
| 35 |
-
"""
|
| 36 |
-
print(f"\n📦 创建 GitHub 仓库:{repo_name}...")
|
| 37 |
-
|
| 38 |
-
url = "https://api.github.com/user/repos"
|
| 39 |
-
|
| 40 |
-
headers = {
|
| 41 |
-
"Authorization": f"token {token}",
|
| 42 |
-
"Accept": "application/vnd.github.v3+json",
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
data = {
|
| 46 |
-
"name": repo_name,
|
| 47 |
-
"description": "Fusion - 六边形开源大模型 | 集百家之长,铸最强开源模型",
|
| 48 |
-
"private": private,
|
| 49 |
-
"has_issues": True,
|
| 50 |
-
"has_projects": True,
|
| 51 |
-
"has_wiki": True,
|
| 52 |
-
"auto_init": False, # 不要自动创建 README
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
response = requests.post(url, headers=headers, json=data)
|
| 56 |
-
|
| 57 |
-
if response.status_code == 201:
|
| 58 |
-
repo_data = response.json()
|
| 59 |
-
repo_url = repo_data["html_url"]
|
| 60 |
-
clone_url = repo_data["clone_url"]
|
| 61 |
-
|
| 62 |
-
print(f"✅ 仓库创建成功!")
|
| 63 |
-
print(f" URL: {repo_url}")
|
| 64 |
-
print(f" 克隆 URL (HTTPS): {clone_url}")
|
| 65 |
-
|
| 66 |
-
return clone_url
|
| 67 |
-
|
| 68 |
-
elif response.status_code == 422:
|
| 69 |
-
# 仓库已存在
|
| 70 |
-
print(f"⚠️ 仓库 {repo_name} 已存在")
|
| 71 |
-
return f"https://github.com/zhan1206/{repo_name}.git"
|
| 72 |
-
|
| 73 |
-
else:
|
| 74 |
-
print(f"❌ 创建失败:{response.status_code}")
|
| 75 |
-
print(f" 错误信息:{response.text}")
|
| 76 |
-
return None
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
def push_to_github(repo_url: str, project_dir: str, use_ssh: bool = False):
|
| 80 |
-
"""
|
| 81 |
-
推送代码到 GitHub
|
| 82 |
-
|
| 83 |
-
参数:
|
| 84 |
-
repo_url: 仓库 URL
|
| 85 |
-
project_dir: 项目目录
|
| 86 |
-
use_ssh: 是否使用 SSH(默认 False,使用 HTTPS)
|
| 87 |
-
"""
|
| 88 |
-
print(f"\n🚀 推送代码到 GitHub...")
|
| 89 |
-
|
| 90 |
-
# 切换到项目目录
|
| 91 |
-
os.chdir(project_dir)
|
| 92 |
-
|
| 93 |
-
# 如果已经设置了 remote,先删除
|
| 94 |
-
subprocess.run(
|
| 95 |
-
["git", "remote", "remove", "origin"],
|
| 96 |
-
capture_output=True,
|
| 97 |
-
)
|
| 98 |
-
|
| 99 |
-
# 添加 remote
|
| 100 |
-
if use_ssh:
|
| 101 |
-
# SSH 格式
|
| 102 |
-
ssh_url = repo_url.replace(
|
| 103 |
-
"https://github.com/",
|
| 104 |
-
"git@github.com:",
|
| 105 |
-
)
|
| 106 |
-
remote_url = ssh_url
|
| 107 |
-
else:
|
| 108 |
-
# HTTPS 格式
|
| 109 |
-
remote_url = repo_url
|
| 110 |
-
|
| 111 |
-
print(f" Remote URL: {remote_url}")
|
| 112 |
-
|
| 113 |
-
result = subprocess.run(
|
| 114 |
-
["git", "remote", "add", "origin", remote_url],
|
| 115 |
-
capture_output=True,
|
| 116 |
-
text=True,
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
if result.returncode != 0:
|
| 120 |
-
print(f"❌ 添加 remote 失败:{result.stderr}")
|
| 121 |
-
return False
|
| 122 |
-
|
| 123 |
-
# 推送代码
|
| 124 |
-
print(f" 推送分支:master")
|
| 125 |
-
|
| 126 |
-
result = subprocess.run(
|
| 127 |
-
["git", "push", "-u", "origin", "master"],
|
| 128 |
-
capture_output=True,
|
| 129 |
-
text=True,
|
| 130 |
-
)
|
| 131 |
-
|
| 132 |
-
if result.returncode == 0:
|
| 133 |
-
print(f"✅ 推送成功!")
|
| 134 |
-
print(f"\n🎉 项目已发布:{repo_url.replace('.git', '')}")
|
| 135 |
-
return True
|
| 136 |
-
else:
|
| 137 |
-
print(f"❌ 推送失败:{result.stderr}")
|
| 138 |
-
|
| 139 |
-
# 如果是 HTTPS 且失败,提示使用 SSH
|
| 140 |
-
if not use_ssh:
|
| 141 |
-
print(f"\n💡 提示:如果 HTTPS 推送失败,可以尝试使用 SSH:")
|
| 142 |
-
print(f" 1. 生成 SSH key:ssh-keygen -t ed25519 -C \"your_email@example.com\"")
|
| 143 |
-
print(f" 2. 添加 SSH key 到 GitHub:https://github.com/settings/keys")
|
| 144 |
-
print(f" 3. 重新运行脚本,输入 'y' 使用 SSH")
|
| 145 |
-
|
| 146 |
-
return False
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
def main():
|
| 150 |
-
print("=" * 60)
|
| 151 |
-
print("Fusion 项目 GitHub 推送脚本")
|
| 152 |
-
print("=" * 60)
|
| 153 |
-
|
| 154 |
-
# 1. 获取项目目录
|
| 155 |
-
project_dir = os.path.dirname(os.path.abspath(__file__))
|
| 156 |
-
print(f"\n📂 项目目录:{project_dir}")
|
| 157 |
-
|
| 158 |
-
# 2. 检查 Git 状态
|
| 159 |
-
os.chdir(project_dir)
|
| 160 |
-
|
| 161 |
-
result = subprocess.run(
|
| 162 |
-
["git", "status", "--porcelain"],
|
| 163 |
-
capture_output=True,
|
| 164 |
-
text=True,
|
| 165 |
-
)
|
| 166 |
-
|
| 167 |
-
if result.stdout:
|
| 168 |
-
print(f"\n⚠️ 有未提交的更改:")
|
| 169 |
-
print(result.stdout)
|
| 170 |
-
|
| 171 |
-
commit = input("\n是否提交这些更改?(y/N): ").strip().lower()
|
| 172 |
-
|
| 173 |
-
if commit == 'y':
|
| 174 |
-
subprocess.run(["git", "add", "."])
|
| 175 |
-
commit_msg = input("输入提交信息(默认:Update):") or "Update"
|
| 176 |
-
subprocess.run(["git", "commit", "-m", commit_msg])
|
| 177 |
-
print(f"✅ 已提交")
|
| 178 |
-
else:
|
| 179 |
-
print(f"⚠️ 取消推送")
|
| 180 |
-
return
|
| 181 |
-
|
| 182 |
-
# 3. 获取 GitHub Token(安全输入,不显示)
|
| 183 |
-
print(f"\n🔐 输入 GitHub Personal Access Token")
|
| 184 |
-
print(f" 创建 Token:https://github.com/settings/tokens")
|
| 185 |
-
print(f" 需要权限:repo(全选)")
|
| 186 |
-
print(f" (输入时不会显示,这是正常的)")
|
| 187 |
-
|
| 188 |
-
token = getpass.getpass("Token: ")
|
| 189 |
-
|
| 190 |
-
if not token:
|
| 191 |
-
print(f"❌ Token 不能为空")
|
| 192 |
-
return
|
| 193 |
-
|
| 194 |
-
# 4. 创建 GitHub 仓库
|
| 195 |
-
repo_name = "fusion-llm"
|
| 196 |
-
repo_url = create_github_repo(token, repo_name, private=False)
|
| 197 |
-
|
| 198 |
-
if not repo_url:
|
| 199 |
-
print(f"❌ 创建仓库失败")
|
| 200 |
-
return
|
| 201 |
-
|
| 202 |
-
# 5. 推送代码
|
| 203 |
-
use_ssh = input("\n使用 SSH 推送?(y/N): ").strip().lower() == 'y'
|
| 204 |
-
|
| 205 |
-
success = push_to_github(repo_url, project_dir, use_ssh=use_ssh)
|
| 206 |
-
|
| 207 |
-
if not success and not use_ssh:
|
| 208 |
-
# 如果 HTTPS 失败,询问是否尝试 SSH
|
| 209 |
-
retry_ssh = input("\n是否尝试使用 SSH 推送?(y/N): ").strip().lower()
|
| 210 |
-
|
| 211 |
-
if retry_ssh == 'y':
|
| 212 |
-
# 修改 URL 为 SSH 格式
|
| 213 |
-
ssh_url = repo_url.replace(
|
| 214 |
-
"https://github.com/",
|
| 215 |
-
"git@github.com:",
|
| 216 |
-
)
|
| 217 |
-
success = push_to_github(ssh_url, project_dir, use_ssh=True)
|
| 218 |
-
|
| 219 |
-
if success:
|
| 220 |
-
print(f"\n🎉 完成!项目已成功推送到 GitHub")
|
| 221 |
-
print(f" 仓库地址:<ADDRESS_REMOVED>
|
| 222 |
-
print(f" 克隆命令:git clone {repo_url.replace('.git', '')}.git")
|
| 223 |
-
else:
|
| 224 |
-
print(f"\n❌ 推送失败,请检查错误信息")
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
if __name__ == "__main__":
|
| 228 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -8,6 +8,7 @@ datasets>=2.16.0
|
|
| 8 |
|
| 9 |
# PEFT (LoRA/QLoRA)
|
| 10 |
peft>=0.7.0
|
|
|
|
| 11 |
|
| 12 |
# 分布式训练
|
| 13 |
deepspeed>=0.12.0
|
|
@@ -25,8 +26,12 @@ tokenizers>=0.15.0
|
|
| 25 |
langid>=1.1.6
|
| 26 |
|
| 27 |
# 推理部署
|
| 28 |
-
|
| 29 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
# 评估(可选)
|
| 32 |
evaluate>=0.4.0
|
|
|
|
| 8 |
|
| 9 |
# PEFT (LoRA/QLoRA)
|
| 10 |
peft>=0.7.0
|
| 11 |
+
bitsandbytes>=0.41.0
|
| 12 |
|
| 13 |
# 分布式训练
|
| 14 |
deepspeed>=0.12.0
|
|
|
|
| 26 |
langid>=1.1.6
|
| 27 |
|
| 28 |
# 推理部署
|
| 29 |
+
ollama>=0.1.0
|
| 30 |
+
# 注意:Ollama 也需要从 https://ollama.com/ 下载二进制客户端
|
| 31 |
+
|
| 32 |
+
# 量化工具依赖
|
| 33 |
+
scipy>=1.11.0
|
| 34 |
+
scikit-learn>=1.3.0
|
| 35 |
|
| 36 |
# 评估(可选)
|
| 37 |
evaluate>=0.4.0
|
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Add depth=3 samples to mini_data.json for Thinking Dial coverage."""
|
| 3 |
+
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
DEPTH_3_SAMPLES = [
|
| 7 |
+
{"prompt": "Prove the Pythagorean theorem", "response": "Consider a right triangle with legs a, b and hypotenuse c. Construct a square of side (a+b) and arrange four copies of the triangle inside. The inner quadrilateral is a square of side c, so (a+b)^2 = c^2 + 4*(ab/2), which simplifies to a^2 + b^2 = c^2.", "think_rank": 3},
|
| 8 |
+
{"prompt": "Derive the quadratic formula", "response": "Starting from ax^2 + bx + c = 0, divide by a, complete the square: (x + b/2a)^2 = (b^2 - 4ac)/4a^2. Taking the square root gives x = (-b +- sqrt(b^2 - 4ac)) / 2a.", "think_rank": 3},
|
| 9 |
+
{"prompt": "Prove that sqrt(2) is irrational", "response": "Assume sqrt(2) = p/q in lowest terms. Then 2q^2 = p^2, so p is even. Write p = 2k, then 2q^2 = 4k^2, so q^2 = 2k^2, meaning q is also even. This contradicts p/q being in lowest terms.", "think_rank": 3},
|
| 10 |
+
{"prompt": "Prove there are infinitely many primes", "response": "Suppose finitely many primes p1,...,pn exist. Let N = p1*p2*...*pn + 1. N is not divisible by any pi (remainder 1). So N is either prime itself or has a prime factor not in the list. Contradiction.", "think_rank": 3},
|
| 11 |
+
{"prompt": "Derive the derivative of sin(x)", "response": "Using the limit definition: d/dx sin(x) = lim(h->0) [sin(x+h) - sin(x)] / h. Apply the angle addition formula: sin(x+h) = sin(x)cos(h) + cos(x)sin(h). This gives lim [sin(x)(cos(h)-1)/h + cos(x)sin(h)/h] = sin(x)*0 + cos(x)*1 = cos(x).", "think_rank": 3},
|
| 12 |
+
{"prompt": "Analyze the time complexity of merge sort", "response": "Merge sort divides the array in half (O(1)), recurses on both halves (2T(n/2)), and merges in O(n). The recurrence T(n) = 2T(n/2) + O(n) solves to O(n log n) by the master theorem (case 2). This holds for worst, average, and best cases.", "think_rank": 3},
|
| 13 |
+
{"prompt": "Prove the sum of first n natural numbers is n(n+1)/2", "response": "By induction: Base case n=1: 1 = 1(2)/2 = 1. Inductive step: assume S(k) = k(k+1)/2. Then S(k+1) = S(k) + (k+1) = k(k+1)/2 + (k+1) = (k+1)(k/2 + 1) = (k+1)(k+2)/2. QED.", "think_rank": 3},
|
| 14 |
+
{"prompt": "Prove that e^x converges for all x", "response": "The Taylor series e^x = sum(x^n/n!) has ratio test: |a_(n+1)/a_n| = |x|/(n+1) -> 0 as n -> infinity. Since the limit is 0 < 1 for all x, the series converges absolutely for all real x by the ratio test.", "think_rank": 3},
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
def main():
|
| 18 |
+
with open('data/mini_data.json', 'r', encoding='utf-8') as f:
|
| 19 |
+
data = json.load(f)
|
| 20 |
+
|
| 21 |
+
old_dist = {}
|
| 22 |
+
for item in data:
|
| 23 |
+
r = item.get('think_rank', 0)
|
| 24 |
+
old_dist[r] = old_dist.get(r, 0) + 1
|
| 25 |
+
print(f"Before: {old_dist}")
|
| 26 |
+
|
| 27 |
+
data.extend(DEPTH_3_SAMPLES)
|
| 28 |
+
|
| 29 |
+
new_dist = {}
|
| 30 |
+
for item in data:
|
| 31 |
+
r = item.get('think_rank', 0)
|
| 32 |
+
new_dist[r] = new_dist.get(r, 0) + 1
|
| 33 |
+
print(f"After: {new_dist}")
|
| 34 |
+
|
| 35 |
+
with open('data/mini_data.json', 'w', encoding='utf-8') as f:
|
| 36 |
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 37 |
+
|
| 38 |
+
print(f"Total: {len(data)} items")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
if __name__ == '__main__':
|
| 42 |
+
main()
|
|
@@ -67,10 +67,20 @@ def create_mini_dataset(output_path: str, num_samples: int = 100):
|
|
| 67 |
else:
|
| 68 |
prompt, response = random.choice(english_samples)
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
data.append({
|
| 71 |
"prompt": prompt,
|
| 72 |
"response": response,
|
| 73 |
-
"think_rank":
|
| 74 |
})
|
| 75 |
|
| 76 |
# 保存为 JSON
|
|
|
|
| 67 |
else:
|
| 68 |
prompt, response = random.choice(english_samples)
|
| 69 |
|
| 70 |
+
# Assign think_rank based on content depth
|
| 71 |
+
if any(kw in prompt for kw in ["Prove", "Derive", "Analyze", "\u8bc1\u660e", "\u63a8\u5bfc", "\u5206\u6790"]):
|
| 72 |
+
think_rank = 3
|
| 73 |
+
elif any(kw in prompt for kw in ["Explain", "How", "Why", "\u89e3\u91ca", "\u5982\u4f55", "\u4e3a\u4ec0\u4e48"]):
|
| 74 |
+
think_rank = 2
|
| 75 |
+
elif any(kw in prompt for kw in ["Write", "Implement", "\u5199", "\u5b9e\u73b0"]):
|
| 76 |
+
think_rank = 1
|
| 77 |
+
else:
|
| 78 |
+
think_rank = 0
|
| 79 |
+
|
| 80 |
data.append({
|
| 81 |
"prompt": prompt,
|
| 82 |
"response": response,
|
| 83 |
+
"think_rank": think_rank,
|
| 84 |
})
|
| 85 |
|
| 86 |
# 保存为 JSON
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Fix mini_data.json: distribute think_rank 0-3 based on prompt content."""
|
| 3 |
+
|
| 4 |
+
import json
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
# Keywords suggesting different thinking depths
|
| 8 |
+
DEPTH_3_KEYWORDS = ['prove', 'theorem', 'proof', 'derive', 'mathematical', 'complex',
|
| 9 |
+
'prove', 'derive', 'calculate', 'analyze deeply',
|
| 10 |
+
'\u8bc1\u660e', '\u63a8\u5bfc', '\u5b9a\u7406', '\u590d\u6742', '\u6df1\u5165\u5206\u6790']
|
| 11 |
+
DEPTH_2_KEYWORDS = ['explain', 'why', 'how does', 'compare', 'difference',
|
| 12 |
+
'algorithm', 'design', 'optimize',
|
| 13 |
+
'\u89e3\u91ca', '\u4e3a\u4ec0\u4e48', '\u5982\u4f55', '\u6bd4\u8f83', '\u7b97\u6cd5', '\u8bbe\u8ba1', '\u4f18\u5316']
|
| 14 |
+
DEPTH_1_KEYWORDS = ['write', 'implement', 'code', 'function', 'create',
|
| 15 |
+
'\u5199', '\u5b9e\u73b0', '\u7f16\u5199', '\u4ee3\u7801', '\u521b\u5efa']
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def assign_depth(item):
|
| 19 |
+
text = (item.get('prompt', '') + ' ' + item.get('response', '')).lower()
|
| 20 |
+
for kw in DEPTH_3_KEYWORDS:
|
| 21 |
+
if kw.lower() in text:
|
| 22 |
+
return 3
|
| 23 |
+
for kw in DEPTH_2_KEYWORDS:
|
| 24 |
+
if kw.lower() in text:
|
| 25 |
+
return 2
|
| 26 |
+
for kw in DEPTH_1_KEYWORDS:
|
| 27 |
+
if kw.lower() in text:
|
| 28 |
+
return 1
|
| 29 |
+
return 0
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def main():
|
| 33 |
+
with open('data/mini_data.json', 'r', encoding='utf-8') as f:
|
| 34 |
+
data = json.load(f)
|
| 35 |
+
|
| 36 |
+
# Count current distribution
|
| 37 |
+
old_dist = {}
|
| 38 |
+
for item in data:
|
| 39 |
+
r = item.get('think_rank', 0)
|
| 40 |
+
old_dist[r] = old_dist.get(r, 0) + 1
|
| 41 |
+
print(f"Before fix: {old_dist}")
|
| 42 |
+
|
| 43 |
+
# Fix
|
| 44 |
+
for item in data:
|
| 45 |
+
item['think_rank'] = assign_depth(item)
|
| 46 |
+
|
| 47 |
+
# Count new distribution
|
| 48 |
+
new_dist = {}
|
| 49 |
+
for item in data:
|
| 50 |
+
r = item.get('think_rank', 0)
|
| 51 |
+
new_dist[r] = new_dist.get(r, 0) + 1
|
| 52 |
+
print(f"After fix: {new_dist}")
|
| 53 |
+
|
| 54 |
+
with open('data/mini_data.json', 'w', encoding='utf-8') as f:
|
| 55 |
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 56 |
+
|
| 57 |
+
print(f"Fixed {len(data)} items")
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
if __name__ == '__main__':
|
| 61 |
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Train a SentencePiece tokenizer for Fusion models.
|
| 4 |
+
|
| 5 |
+
This creates the tokenizer.model file needed by Fusion's 100K vocabulary.
|
| 6 |
+
Training data should be a plain text file with one sentence per line.
|
| 7 |
+
|
| 8 |
+
Usage:
|
| 9 |
+
python scripts/train_tokenizer.py --input data/tokenizer_train.txt --vocab_size 100000 --output tokenizers/
|
| 10 |
+
|
| 11 |
+
Requirements:
|
| 12 |
+
pip install sentencepiece
|
| 13 |
+
|
| 14 |
+
Author: Zhu Zizhan
|
| 15 |
+
Project: Fusion-LLM
|
| 16 |
+
License: Apache 2.0
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import argparse
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def train_tokenizer(input_path: str, vocab_size: int, output_dir: str, model_type: str = "unigram"):
|
| 25 |
+
"""Train a SentencePiece tokenizer."""
|
| 26 |
+
try:
|
| 27 |
+
import sentencepiece as spm
|
| 28 |
+
except ImportError:
|
| 29 |
+
print("[ERROR] sentencepiece not installed. Run: pip install sentencepiece")
|
| 30 |
+
sys.exit(1)
|
| 31 |
+
|
| 32 |
+
if not os.path.exists(input_path):
|
| 33 |
+
print(f"[ERROR] Training data not found: {input_path}")
|
| 34 |
+
print("Create a plain text file with one sentence per line.")
|
| 35 |
+
print("For bilingual (zh+en) tokenizer, mix Chinese and English text.")
|
| 36 |
+
sys.exit(1)
|
| 37 |
+
|
| 38 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 39 |
+
model_prefix = os.path.join(output_dir, "tokenizer")
|
| 40 |
+
|
| 41 |
+
print(f"[Tokenizer] Training SentencePiece model...")
|
| 42 |
+
print(f" Input: {input_path}")
|
| 43 |
+
print(f" Vocab size: {vocab_size}")
|
| 44 |
+
print(f" Model type: {model_type}")
|
| 45 |
+
print(f" Output: {output_dir}/")
|
| 46 |
+
|
| 47 |
+
# Special tokens for Fusion
|
| 48 |
+
control_symbols = [
|
| 49 |
+
"<|pad|>", "<|start|>", "<|end|>",
|
| 50 |
+
"<|think_depth_0|>", "<|think_depth_1|>",
|
| 51 |
+
"<|think_depth_2|>", "<|think_depth_3|>",
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
spm.SentencePieceTrainer.train(
|
| 55 |
+
input=input_path,
|
| 56 |
+
model_prefix=model_prefix,
|
| 57 |
+
vocab_size=vocab_size,
|
| 58 |
+
model_type=model_type,
|
| 59 |
+
character_coverage=0.9995, # High coverage for CJK
|
| 60 |
+
input_sentence_size=10000000,
|
| 61 |
+
shuffle_input_sentence=True,
|
| 62 |
+
control_symbols=control_symbols,
|
| 63 |
+
unk_id=0,
|
| 64 |
+
bos_id=1,
|
| 65 |
+
eos_id=2,
|
| 66 |
+
pad_id=3,
|
| 67 |
+
byte_fallback=True, # Important for multilingual
|
| 68 |
+
split_by_unicode_script=True,
|
| 69 |
+
allow_whitespace_only_pieces=True,
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
model_path = os.path.join(output_dir, "tokenizer.model")
|
| 73 |
+
vocab_path = os.path.join(output_dir, "tokenizer.vocab")
|
| 74 |
+
|
| 75 |
+
print(f"\n[Done] Tokenizer trained successfully!")
|
| 76 |
+
print(f" Model: {model_path}")
|
| 77 |
+
print(f" Vocab: {vocab_path}")
|
| 78 |
+
|
| 79 |
+
# Verify
|
| 80 |
+
sp = spm.SentencePieceProcessor()
|
| 81 |
+
sp.load(model_path)
|
| 82 |
+
print(f" Actual vocab size: {sp.get_piece_size()}")
|
| 83 |
+
|
| 84 |
+
# Test
|
| 85 |
+
test_zh = "Fusion是一个开源大语言模型"
|
| 86 |
+
test_en = "Fusion is an open-source language model"
|
| 87 |
+
print(f"\n Test encode (zh): {test_zh}")
|
| 88 |
+
print(f" -> {sp.encode(test_zh)}")
|
| 89 |
+
print(f" Test encode (en): {test_en}")
|
| 90 |
+
print(f" -> {sp.encode(test_en)}")
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def create_sample_training_data(output_path: str, num_lines: int = 100000):
|
| 94 |
+
"""Create sample training data for tokenizer (for testing only)."""
|
| 95 |
+
import random
|
| 96 |
+
|
| 97 |
+
print(f"[Sample Data] Creating sample training data: {output_path}")
|
| 98 |
+
samples_zh = [
|
| 99 |
+
"人工智能是计算机科学的一个重要分支",
|
| 100 |
+
"深度学习使用多层神经网络来模拟人脑",
|
| 101 |
+
"自然语言处理帮助计算机理解人类语言",
|
| 102 |
+
"机器学习使计算机能够从数据中学习",
|
| 103 |
+
"大语言模型在文本生成任务中表现出色",
|
| 104 |
+
"Transformer架构彻底改变了自然语言处理领域",
|
| 105 |
+
"注意力机制是现代深度学习的核心组件",
|
| 106 |
+
"预训练语言模型通过大规模语料学习语言知识",
|
| 107 |
+
"微调技术使模型适应特定下游任务",
|
| 108 |
+
"开源模型推动了人工智能技术的普及",
|
| 109 |
+
]
|
| 110 |
+
samples_en = [
|
| 111 |
+
"Artificial intelligence is a branch of computer science",
|
| 112 |
+
"Deep learning uses multi-layer neural networks",
|
| 113 |
+
"Natural language processing helps computers understand language",
|
| 114 |
+
"Machine learning enables computers to learn from data",
|
| 115 |
+
"Large language models excel at text generation tasks",
|
| 116 |
+
"The Transformer architecture revolutionized NLP",
|
| 117 |
+
"Attention mechanisms are core components of modern deep learning",
|
| 118 |
+
"Pre-trained models learn language knowledge from large corpora",
|
| 119 |
+
"Fine-tuning adapts models to specific downstream tasks",
|
| 120 |
+
"Open-source models promote the democratization of AI",
|
| 121 |
+
]
|
| 122 |
+
|
| 123 |
+
with open(output_path, 'w', encoding='utf-8') as f:
|
| 124 |
+
for _ in range(num_lines):
|
| 125 |
+
if random.random() > 0.5:
|
| 126 |
+
f.write(random.choice(samples_zh) + "\n")
|
| 127 |
+
else:
|
| 128 |
+
f.write(random.choice(samples_en) + "\n")
|
| 129 |
+
|
| 130 |
+
print(f" Generated {num_lines} lines")
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def main():
|
| 134 |
+
parser = argparse.ArgumentParser(description="Train Fusion SentencePiece tokenizer")
|
| 135 |
+
parser.add_argument("--input", type=str, default="data/tokenizer_train.txt",
|
| 136 |
+
help="Path to training data (one sentence per line)")
|
| 137 |
+
parser.add_argument("--vocab_size", type=int, default=100000,
|
| 138 |
+
help="Vocabulary size (default: 100000)")
|
| 139 |
+
parser.add_argument("--output", type=str, default="tokenizers/",
|
| 140 |
+
help="Output directory")
|
| 141 |
+
parser.add_argument("--model_type", type=str, default="unigram",
|
| 142 |
+
choices=["unigram", "bpe"], help="Model type")
|
| 143 |
+
parser.add_argument("--create_sample_data", action="store_true",
|
| 144 |
+
help="Create sample training data for testing")
|
| 145 |
+
args = parser.parse_args()
|
| 146 |
+
|
| 147 |
+
if args.create_sample_data:
|
| 148 |
+
create_sample_training_data(args.input)
|
| 149 |
+
return
|
| 150 |
+
|
| 151 |
+
train_tokenizer(args.input, args.vocab_size, args.output, args.model_type)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
if __name__ == "__main__":
|
| 155 |
+
main()
|
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
"""Small training loop to verify loss decreases"""
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.insert(0, ".")
|
| 4 |
-
import torch
|
| 5 |
-
from models.fusion_model import FusionModel, FusionConfig
|
| 6 |
-
|
| 7 |
-
config = FusionConfig(
|
| 8 |
-
vocab_size=10000,
|
| 9 |
-
hidden_size=256,
|
| 10 |
-
num_hidden_layers=2,
|
| 11 |
-
num_attention_heads=4,
|
| 12 |
-
intermediate_size=512,
|
| 13 |
-
block_size=64,
|
| 14 |
-
latent_dim=16,
|
| 15 |
-
sbla_mode="pure_sbla",
|
| 16 |
-
max_position_embeddings=256,
|
| 17 |
-
)
|
| 18 |
-
|
| 19 |
-
model = FusionModel(config)
|
| 20 |
-
model.train()
|
| 21 |
-
|
| 22 |
-
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
|
| 23 |
-
batch_size, seq_len = 4, 32
|
| 24 |
-
|
| 25 |
-
print("[DEBUG] Small training loop (5 steps)...")
|
| 26 |
-
for step in range(5):
|
| 27 |
-
input_ids = torch.randint(0, 10000, (batch_size, seq_len))
|
| 28 |
-
attention_mask = torch.ones(batch_size, seq_len)
|
| 29 |
-
|
| 30 |
-
optimizer.zero_grad()
|
| 31 |
-
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids)
|
| 32 |
-
loss = outputs["loss"]
|
| 33 |
-
loss.backward()
|
| 34 |
-
optimizer.step()
|
| 35 |
-
|
| 36 |
-
print(f"Step {step}: loss = {loss.item():.4f}")
|
| 37 |
-
|
| 38 |
-
print("\nTraining loop successful - loss is decreasing!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
"""Test training"""
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.insert(0, ".")
|
| 4 |
-
import torch
|
| 5 |
-
from models.fusion_model import FusionModel, FusionConfig
|
| 6 |
-
|
| 7 |
-
config = FusionConfig(
|
| 8 |
-
vocab_size=10000,
|
| 9 |
-
hidden_size=256,
|
| 10 |
-
num_hidden_layers=2,
|
| 11 |
-
num_attention_heads=4,
|
| 12 |
-
intermediate_size=512,
|
| 13 |
-
block_size=64,
|
| 14 |
-
latent_dim=16,
|
| 15 |
-
sbla_mode="pure_sbla",
|
| 16 |
-
max_position_embeddings=256,
|
| 17 |
-
)
|
| 18 |
-
|
| 19 |
-
model = FusionModel(config)
|
| 20 |
-
model.train()
|
| 21 |
-
|
| 22 |
-
batch_size, seq_len = 2, 32
|
| 23 |
-
input_ids = torch.randint(0, 10000, (batch_size, seq_len))
|
| 24 |
-
attention_mask = torch.ones(batch_size, seq_len)
|
| 25 |
-
|
| 26 |
-
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids)
|
| 27 |
-
print(f"Loss: {outputs['loss'].item():.4f}")
|
| 28 |
-
|
| 29 |
-
loss = outputs["loss"]
|
| 30 |
-
loss.backward()
|
| 31 |
-
print(f"Gradients exist: {model.embeddings.weight.grad is not None}")
|
| 32 |
-
|
| 33 |
-
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
|
| 34 |
-
optimizer.zero_grad()
|
| 35 |
-
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids)
|
| 36 |
-
outputs["loss"].backward()
|
| 37 |
-
optimizer.step()
|
| 38 |
-
print("Optimizer step successful!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
"""Debug attention step by step"""
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.insert(0, ".")
|
| 4 |
-
import torch
|
| 5 |
-
import torch.nn.functional as F
|
| 6 |
-
import math
|
| 7 |
-
|
| 8 |
-
print("[DEBUG] Step-by-step attention debugging...")
|
| 9 |
-
|
| 10 |
-
from models.fusion_model import FusionConfig
|
| 11 |
-
|
| 12 |
-
config = FusionConfig(
|
| 13 |
-
vocab_size=10000,
|
| 14 |
-
hidden_size=256,
|
| 15 |
-
num_hidden_layers=2,
|
| 16 |
-
num_attention_heads=4,
|
| 17 |
-
intermediate_size=512,
|
| 18 |
-
block_size=64,
|
| 19 |
-
latent_dim=16,
|
| 20 |
-
sbla_mode="pure_sbla",
|
| 21 |
-
max_position_embeddings=256,
|
| 22 |
-
)
|
| 23 |
-
|
| 24 |
-
# Manual attention computation
|
| 25 |
-
batch_size, seq_len = 2, 32
|
| 26 |
-
hidden_states = torch.randn(batch_size, seq_len, 256)
|
| 27 |
-
device = hidden_states.device
|
| 28 |
-
|
| 29 |
-
# Q/K/V
|
| 30 |
-
q_proj = torch.nn.Linear(256, 256, bias=False)
|
| 31 |
-
k_proj = torch.nn.Linear(256, 256, bias=False)
|
| 32 |
-
v_proj = torch.nn.Linear(256, 256, bias=False)
|
| 33 |
-
|
| 34 |
-
Q = q_proj(hidden_states).view(batch_size, seq_len, 4, 64).transpose(1, 2)
|
| 35 |
-
K = k_proj(hidden_states).view(batch_size, seq_len, 4, 64).transpose(1, 2)
|
| 36 |
-
V = v_proj(hidden_states).view(batch_size, seq_len, 4, 64).transpose(1, 2)
|
| 37 |
-
|
| 38 |
-
print(f"Q shape: {Q.shape}, has_nan: {torch.isnan(Q).any()}")
|
| 39 |
-
print(f"K shape: {K.shape}, has_nan: {torch.isnan(K).any()}")
|
| 40 |
-
print(f"V shape: {V.shape}, has_nan: {torch.isnan(V).any()}")
|
| 41 |
-
|
| 42 |
-
# Compute attention scores
|
| 43 |
-
attn_scores = torch.matmul(Q, K.transpose(-1, -2)) / math.sqrt(64)
|
| 44 |
-
print(f"Attn scores: min={attn_scores.min():.4f}, max={attn_scores.max():.4f}, has_nan: {torch.isnan(attn_scores).any()}")
|
| 45 |
-
|
| 46 |
-
# Check for -inf in scores
|
| 47 |
-
print(f"Scores has -inf: {torch.isinf(attn_scores).any()}")
|
| 48 |
-
print(f"Scores has inf: {torch.isinf(attn_scores).any()}")
|
| 49 |
-
|
| 50 |
-
# Check causal mask
|
| 51 |
-
causal_mask = torch.triu(torch.ones(seq_len, seq_len, device=device, dtype=torch.bool), diagonal=1)
|
| 52 |
-
causal_mask = causal_mask.float().masked_fill(causal_mask, float('-inf'))
|
| 53 |
-
print(f"Causal mask: min={causal_mask.min():.4f}, max={causal_mask.max():.4f}")
|
| 54 |
-
|
| 55 |
-
# Add causal mask
|
| 56 |
-
attn_scores_masked = attn_scores + causal_mask
|
| 57 |
-
print(f"Scores after mask: min={attn_scores_masked.min():.4f}, has_nan: {torch.isnan(attn_scores_masked).any()}")
|
| 58 |
-
|
| 59 |
-
# Softmax
|
| 60 |
-
attn_probs = F.softmax(attn_scores_masked, dim=-1)
|
| 61 |
-
print(f"Attn probs: min={attn_probs.min():.4f}, max={attn_probs.max():.4f}, has_nan: {torch.isnan(attn_probs).any()}")
|
| 62 |
-
|
| 63 |
-
# Context
|
| 64 |
-
context = torch.matmul(attn_probs, V)
|
| 65 |
-
print(f"Context: min={context.min():.4f}, max={context.max():.4f}, has_nan: {torch.isnan(context).any()}")
|
| 66 |
-
|
| 67 |
-
# Reshape
|
| 68 |
-
context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, 256)
|
| 69 |
-
print(f"Context reshaped: min={context.min():.4f}, max={context.max():.4f}, has_nan: {torch.isnan(context).any()}")
|
| 70 |
-
|
| 71 |
-
# Output projection
|
| 72 |
-
out_proj = torch.nn.Linear(256, 256, bias=False)
|
| 73 |
-
output = out_proj(context)
|
| 74 |
-
print(f"Output: min={output.min():.4f}, max={output.max():.4f}, has_nan: {torch.isnan(output).any()}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
"""Debug layer by layer"""
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.insert(0, ".")
|
| 4 |
-
import torch
|
| 5 |
-
|
| 6 |
-
print("[DEBUG] Testing layer by layer...")
|
| 7 |
-
|
| 8 |
-
from models.fusion_model import FusionModel, FusionConfig, FusionAttention
|
| 9 |
-
|
| 10 |
-
config = FusionConfig(
|
| 11 |
-
vocab_size=10000,
|
| 12 |
-
hidden_size=256,
|
| 13 |
-
num_hidden_layers=2,
|
| 14 |
-
num_attention_heads=4,
|
| 15 |
-
intermediate_size=512,
|
| 16 |
-
block_size=64,
|
| 17 |
-
latent_dim=16,
|
| 18 |
-
sbla_mode="pure_sbla",
|
| 19 |
-
max_position_embeddings=256,
|
| 20 |
-
)
|
| 21 |
-
|
| 22 |
-
model = FusionModel(config)
|
| 23 |
-
model.eval()
|
| 24 |
-
|
| 25 |
-
# Get the attention layer
|
| 26 |
-
attn = model.layers[0].attention
|
| 27 |
-
|
| 28 |
-
batch_size, seq_len = 2, 32
|
| 29 |
-
hidden_states = torch.randn(batch_size, seq_len, 256)
|
| 30 |
-
|
| 31 |
-
# Test attention
|
| 32 |
-
attn_out = attn.forward(hidden_states)
|
| 33 |
-
print(f"Attention output: min={attn_out.min():.4f}, max={attn_out.max():.4f}, has_nan: {torch.isnan(attn_out).any()}")
|
| 34 |
-
|
| 35 |
-
# Test RMSNorm
|
| 36 |
-
norm = model.layers[0].input_layernorm
|
| 37 |
-
norm_out = norm.forward(hidden_states)
|
| 38 |
-
print(f"RMSNorm output: min={norm_out.min():.4f}, max={norm_out.max():.4f}, has_nan: {torch.isnan(norm_out).any()}")
|
| 39 |
-
|
| 40 |
-
# Test FFN
|
| 41 |
-
ffn = model.layers[0]
|
| 42 |
-
residual = hidden_states
|
| 43 |
-
norm1_out = ffn.input_layernorm(hidden_states)
|
| 44 |
-
attn_out = ffn.attention(norm1_out)
|
| 45 |
-
after_attn = residual + attn_out
|
| 46 |
-
print(f"After attention residual: min={after_attn.min():.4f}, max={after_attn.max():.4f}, has_nan: {torch.isnan(after_attn).any()}")
|
| 47 |
-
|
| 48 |
-
norm2_out = ffn.post_attention_layernorm(after_attn)
|
| 49 |
-
print(f"Post-attention norm: min={norm2_out.min():.4f}, max={norm2_out.max():.4f}, has_nan: {torch.isnan(norm2_out).any()}")
|
| 50 |
-
|
| 51 |
-
gate = torch.nn.functional.silu(ffn.gate_proj(norm2_out))
|
| 52 |
-
up = ffn.up_proj(norm2_out)
|
| 53 |
-
print(f"Gate: min={gate.min():.4f}, max={gate.max():.4f}, has_nan: {torch.isnan(gate).any()}")
|
| 54 |
-
print(f"Up: min={up.min():.4f}, max={up.max():.4f}, has_nan: {torch.isnan(up).any()}")
|
| 55 |
-
|
| 56 |
-
gate_up = gate * up
|
| 57 |
-
print(f"Gate*Up: min={gate_up.min():.4f}, max={gate_up.max():.4f}, has_nan: {torch.isnan(gate_up).any()}")
|
| 58 |
-
|
| 59 |
-
ffn_out = ffn.down_proj(gate_up)
|
| 60 |
-
print(f"FFN output: min={ffn_out.min():.4f}, max={ffn_out.max():.4f}, has_nan: {torch.isnan(ffn_out).any()}")
|
| 61 |
-
|
| 62 |
-
final = after_attn + ffn_out
|
| 63 |
-
print(f"Final layer output: min={final.min():.4f}, max={final.max():.4f}, has_nan: {torch.isnan(final).any()}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,54 +0,0 @@
|
|
| 1 |
-
"""Debug norm and lm_head"""
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.insert(0, ".")
|
| 4 |
-
import torch
|
| 5 |
-
|
| 6 |
-
print("[DEBUG] Testing norm and lm_head...")
|
| 7 |
-
|
| 8 |
-
from models.fusion_model import FusionModel, FusionConfig
|
| 9 |
-
|
| 10 |
-
config = FusionConfig(
|
| 11 |
-
vocab_size=10000,
|
| 12 |
-
hidden_size=256,
|
| 13 |
-
num_hidden_layers=2,
|
| 14 |
-
num_attention_heads=4,
|
| 15 |
-
intermediate_size=512,
|
| 16 |
-
block_size=64,
|
| 17 |
-
latent_dim=16,
|
| 18 |
-
sbla_mode="pure_sbla",
|
| 19 |
-
max_position_embeddings=256,
|
| 20 |
-
)
|
| 21 |
-
|
| 22 |
-
model = FusionModel(config)
|
| 23 |
-
model.eval()
|
| 24 |
-
|
| 25 |
-
# Simulate hidden states after all layers
|
| 26 |
-
batch_size, seq_len = 2, 32
|
| 27 |
-
hidden_states = torch.randn(batch_size, seq_len, 256)
|
| 28 |
-
|
| 29 |
-
# Final norm
|
| 30 |
-
norm_out = model.norm(hidden_states)
|
| 31 |
-
print(f"Final norm output: min={norm_out.min():.4f}, max={norm_out.max():.4f}, has_nan: {torch.isnan(norm_out).any()}")
|
| 32 |
-
|
| 33 |
-
# LM head
|
| 34 |
-
logits = model.lm_head(norm_out)
|
| 35 |
-
print(f"Logits: min={logits.min():.4f}, max={logits.max():.4f}, has_nan: {torch.isnan(logits).any()}")
|
| 36 |
-
|
| 37 |
-
# Now test with actual layers
|
| 38 |
-
input_ids = torch.randint(0, 10000, (batch_size, seq_len))
|
| 39 |
-
embed_out = model.embeddings(input_ids)
|
| 40 |
-
print(f"Embeddings: min={embed_out.min():.4f}, max={embed_out.max():.4f}, has_nan: {torch.isnan(embed_out).any()}")
|
| 41 |
-
|
| 42 |
-
# Forward through layers
|
| 43 |
-
hs = model.dropout(embed_out)
|
| 44 |
-
print(f"After dropout: min={hs.min():.4f}, max={hs.max():.4f}, has_nan: {torch.isnan(hs).any()}")
|
| 45 |
-
|
| 46 |
-
for i, layer in enumerate(model.layers):
|
| 47 |
-
hs, _ = layer(hs)
|
| 48 |
-
print(f"Layer {i} output: min={hs.min():.4f}, max={hs.max():.4f}, has_nan: {torch.isnan(hs).any()}")
|
| 49 |
-
|
| 50 |
-
hs = model.norm(hs)
|
| 51 |
-
print(f"After final norm: min={hs.min():.4f}, max={hs.max():.4f}, has_nan: {torch.isnan(hs).any()}")
|
| 52 |
-
|
| 53 |
-
logits = model.lm_head(hs)
|
| 54 |
-
print(f"Final logits: min={logits.min():.4f}, max={logits.max():.4f}, has_nan: {torch.isnan(logits).any()}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
"""Debug script for NaN loss"""
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.insert(0, ".")
|
| 4 |
-
import torch
|
| 5 |
-
|
| 6 |
-
print("[DEBUG] Testing Fusion Model loss calculation...")
|
| 7 |
-
|
| 8 |
-
# Import directly
|
| 9 |
-
from models.fusion_model import FusionModel, FusionConfig
|
| 10 |
-
|
| 11 |
-
config = FusionConfig(
|
| 12 |
-
vocab_size=10000,
|
| 13 |
-
hidden_size=256,
|
| 14 |
-
num_hidden_layers=2,
|
| 15 |
-
num_attention_heads=4,
|
| 16 |
-
intermediate_size=512,
|
| 17 |
-
block_size=64,
|
| 18 |
-
latent_dim=16,
|
| 19 |
-
sbla_mode="pure_sbla",
|
| 20 |
-
max_position_embeddings=256,
|
| 21 |
-
)
|
| 22 |
-
|
| 23 |
-
model = FusionModel(config)
|
| 24 |
-
model.eval()
|
| 25 |
-
|
| 26 |
-
# Small test
|
| 27 |
-
batch_size, seq_len = 2, 32
|
| 28 |
-
input_ids = torch.randint(0, config.vocab_size, (batch_size, seq_len))
|
| 29 |
-
attention_mask = torch.ones(batch_size, seq_len)
|
| 30 |
-
|
| 31 |
-
with torch.no_grad():
|
| 32 |
-
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids, return_dict=True)
|
| 33 |
-
|
| 34 |
-
logits = outputs["logits"]
|
| 35 |
-
print(f"Logits stats: min={logits.min():.4f}, max={logits.max():.4f}, mean={logits.mean():.4f}")
|
| 36 |
-
print(f"Logits has inf: {torch.isinf(logits).any()}")
|
| 37 |
-
print(f"Logits has nan: {torch.isnan(logits).any()}")
|
| 38 |
-
|
| 39 |
-
# Check logits at last position
|
| 40 |
-
last_logits = logits[:, -1, :]
|
| 41 |
-
print(f"Last token logits: min={last_logits.min():.4f}, max={last_logits.max():.4f}")
|
| 42 |
-
|
| 43 |
-
# Try computing loss manually
|
| 44 |
-
shift_logits = logits[..., :-1, :].contiguous()
|
| 45 |
-
shift_labels = input_ids[..., 1:].contiguous()
|
| 46 |
-
print(f"Shift logits stats: min={shift_logits.min():.4f}, max={shift_logits.max():.4f}")
|
| 47 |
-
print(f"Shift logits has inf: {torch.isinf(shift_logits).any()}")
|
| 48 |
-
print(f"Shift labels range: {shift_labels.min():.0f} - {shift_labels.max():.0f}")
|
| 49 |
-
|
| 50 |
-
loss_fct = torch.nn.CrossEntropyLoss()
|
| 51 |
-
try:
|
| 52 |
-
loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
|
| 53 |
-
print(f"Manual loss: {loss.item():.4f}")
|
| 54 |
-
except Exception as e:
|
| 55 |
-
print(f"Loss computation error: {e}")
|
| 56 |
-
|
| 57 |
-
# Check for any NaN in embeddings
|
| 58 |
-
embeds = model.embeddings(input_ids)
|
| 59 |
-
print(f"Embeddings: min={embeds.min():.4f}, max={embeds.max():.4f}, has_nan={torch.isnan(embeds).any()}")
|
| 60 |
-
|
| 61 |
-
# Test a simple forward without labels
|
| 62 |
-
outputs_no_labels = model(input_ids=input_ids, attention_mask=attention_mask, return_dict=True)
|
| 63 |
-
print(f"Forward no labels logits: min={outputs_no_labels['logits'].min():.4f}, max={outputs_no_labels['logits'].max():.4f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,68 +0,0 @@
|
|
| 1 |
-
"""Debug attention_mask handling"""
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.insert(0, ".")
|
| 4 |
-
import torch
|
| 5 |
-
|
| 6 |
-
print("[DEBUG] Testing attention_mask handling...")
|
| 7 |
-
|
| 8 |
-
from models.fusion_model import FusionModel, FusionConfig
|
| 9 |
-
|
| 10 |
-
config = FusionConfig(
|
| 11 |
-
vocab_size=10000,
|
| 12 |
-
hidden_size=256,
|
| 13 |
-
num_hidden_layers=2,
|
| 14 |
-
num_attention_heads=4,
|
| 15 |
-
intermediate_size=512,
|
| 16 |
-
block_size=64,
|
| 17 |
-
latent_dim=16,
|
| 18 |
-
sbla_mode="pure_sbla",
|
| 19 |
-
max_position_embeddings=256,
|
| 20 |
-
)
|
| 21 |
-
|
| 22 |
-
model = FusionModel(config)
|
| 23 |
-
model.eval()
|
| 24 |
-
|
| 25 |
-
batch_size, seq_len = 2, 32
|
| 26 |
-
input_ids = torch.randint(0, 10000, (batch_size, seq_len))
|
| 27 |
-
|
| 28 |
-
# Case 1: No attention_mask
|
| 29 |
-
hs = model.embeddings(input_ids)
|
| 30 |
-
hs = model.dropout(hs)
|
| 31 |
-
for i, layer in enumerate(model.layers):
|
| 32 |
-
hs, _ = layer(hs)
|
| 33 |
-
hs = model.norm(hs)
|
| 34 |
-
logits1 = model.lm_head(hs)
|
| 35 |
-
print(f"No mask logits: min={logits1.min():.4f}, max={logits1.max():.4f}, has_nan: {torch.isnan(logits1).any()}")
|
| 36 |
-
|
| 37 |
-
# Case 2: attention_mask as 2D
|
| 38 |
-
mask_2d = torch.ones(batch_size, seq_len)
|
| 39 |
-
hs = model.embeddings(input_ids)
|
| 40 |
-
hs = model.dropout(hs)
|
| 41 |
-
for i, layer in enumerate(model.layers):
|
| 42 |
-
hs, _ = layer(hs, attention_mask=mask_2d)
|
| 43 |
-
hs = model.norm(hs)
|
| 44 |
-
logits2 = model.lm_head(hs)
|
| 45 |
-
print(f"2D mask logits: min={logits2.min():.4f}, max={logits2.max():.4f}, has_nan: {torch.isnan(logits2).any()}")
|
| 46 |
-
|
| 47 |
-
# Case 3: What does _build_causal_mask return for seq_len=32?
|
| 48 |
-
device = hs.device
|
| 49 |
-
causal_mask = torch.triu(torch.ones(seq_len, seq_len, device=device, dtype=torch.bool), diagonal=1)
|
| 50 |
-
causal_mask = causal_mask.float().masked_fill(causal_mask, float('-inf'))
|
| 51 |
-
print(f"Causal mask shape: {causal_mask.shape}, has -inf: {(causal_mask == float('-inf')).any()}")
|
| 52 |
-
print(f"Causal mask diag: {torch.diag(causal_mask)[:5]}")
|
| 53 |
-
|
| 54 |
-
# Case 4: What is combined_mask after adding window_mask?
|
| 55 |
-
window_mask = torch.zeros(seq_len, seq_len, device=device)
|
| 56 |
-
combined_mask = (causal_mask + window_mask).unsqueeze(0).unsqueeze(0)
|
| 57 |
-
print(f"Combined mask: shape={combined_mask.shape}, min={combined_mask.min()}, max={combined_mask.max()}")
|
| 58 |
-
|
| 59 |
-
# Check if softmax produces NaN when there are rows full of -inf
|
| 60 |
-
attn_scores_test = torch.randn(2, 4, 32, 32)
|
| 61 |
-
attn_scores_test = attn_scores_test + combined_mask
|
| 62 |
-
print(f"Attn scores after mask: has -inf rows: {(attn_scores_test == float('-inf')).any(dim=-1).all()}")
|
| 63 |
-
attn_probs = torch.softmax(attn_scores_test, dim=-1)
|
| 64 |
-
print(f"Attn probs: has nan: {torch.isnan(attn_probs).any()}")
|
| 65 |
-
|
| 66 |
-
# Case 5: Forward pass with full model
|
| 67 |
-
outputs = model(input_ids=input_ids, attention_mask=mask_2d, return_dict=True)
|
| 68 |
-
print(f"Full model logits: min={outputs['logits'].min():.4f}, max={outputs['logits'].max():.4f}, has_nan: {torch.isnan(outputs['logits']).any()}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
File without changes
|
|
@@ -1,59 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
测试 SBLA 注意力集成(无 emoji 版本)
|
| 3 |
-
"""
|
| 4 |
-
import sys
|
| 5 |
-
sys.path.insert(0, '.')
|
| 6 |
-
|
| 7 |
-
from models.fusion_mini import FusionMini, FusionMiniConfig
|
| 8 |
-
import torch
|
| 9 |
-
|
| 10 |
-
print("测试 SBLA 注意力集成...")
|
| 11 |
-
print()
|
| 12 |
-
|
| 13 |
-
# 1. 创建配置
|
| 14 |
-
print("[1] 创建配置...")
|
| 15 |
-
config = FusionMiniConfig(
|
| 16 |
-
vocab_size=1000,
|
| 17 |
-
hidden_size=128,
|
| 18 |
-
num_hidden_layers=2,
|
| 19 |
-
num_attention_heads=4,
|
| 20 |
-
)
|
| 21 |
-
print(" 配置创建成功")
|
| 22 |
-
print(f" 隐层大小:{config.hidden_size}")
|
| 23 |
-
print(f" 层数:{config.num_hidden_layers}")
|
| 24 |
-
print()
|
| 25 |
-
|
| 26 |
-
# 2. 创建模型
|
| 27 |
-
print("[2] 创建模型(包含 SBLA 注意力)...")
|
| 28 |
-
model = FusionMini(config)
|
| 29 |
-
print(" 模型创建成功")
|
| 30 |
-
param_count = sum(p.numel() for p in model.parameters()) / 1e3
|
| 31 |
-
print(f" 参数量:{param_count:.1f}K")
|
| 32 |
-
print()
|
| 33 |
-
|
| 34 |
-
# 3. 测试前向传播
|
| 35 |
-
print("[3] 测试前向传播...")
|
| 36 |
-
input_ids = torch.randint(0, 1000, (2, 64))
|
| 37 |
-
print(f" 输入形状:{input_ids.shape}")
|
| 38 |
-
|
| 39 |
-
outputs = model.forward(input_ids=input_ids, labels=input_ids)
|
| 40 |
-
loss_value = outputs["loss"].item()
|
| 41 |
-
print(f" 前向传播成功")
|
| 42 |
-
print(f" Loss:{loss_value:.4f}")
|
| 43 |
-
print()
|
| 44 |
-
|
| 45 |
-
# 4. 验证 SBLA 是否使用
|
| 46 |
-
print("[4] 验证 SBLA 注意力...")
|
| 47 |
-
has_sblla = any("SBLAttention" in str(module) for module in model.modules())
|
| 48 |
-
if has_sblla:
|
| 49 |
-
print(" SBLA 注意力已集成到模型中")
|
| 50 |
-
else:
|
| 51 |
-
print(" 未检测到 SBLA 注意力(可能使用了标准注意力)")
|
| 52 |
-
print()
|
| 53 |
-
|
| 54 |
-
print("所有测试通过!")
|
| 55 |
-
print()
|
| 56 |
-
print("下一步:")
|
| 57 |
-
print(" 1. 重新训练模型(使用 SBLA 注意力)")
|
| 58 |
-
print(" 2. 对比标准注意力和 SBLA 的性能")
|
| 59 |
-
print(" 3. 推送代码到 GitHub")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -25,9 +25,9 @@ import torch
|
|
| 25 |
import torch.nn as nn
|
| 26 |
import deepspeed
|
| 27 |
from transformers import (
|
| 28 |
-
AutoTokenizer,
|
| 29 |
get_linear_schedule_with_warmup,
|
| 30 |
)
|
|
|
|
| 31 |
from torch.utils.data import Dataset, DataLoader
|
| 32 |
import json
|
| 33 |
import os
|
|
@@ -158,14 +158,16 @@ def create_local_model(
|
|
| 158 |
return model, config
|
| 159 |
|
| 160 |
|
| 161 |
-
def create_tokenizer(vocab_size: int = 32000):
|
| 162 |
"""
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
| 164 |
"""
|
| 165 |
-
|
| 166 |
-
tokenizer =
|
| 167 |
-
tokenizer
|
| 168 |
-
tokenizer.add_special_tokens({'pad_token': '[PAD]'})
|
| 169 |
return tokenizer
|
| 170 |
|
| 171 |
|
|
|
|
| 25 |
import torch.nn as nn
|
| 26 |
import deepspeed
|
| 27 |
from transformers import (
|
|
|
|
| 28 |
get_linear_schedule_with_warmup,
|
| 29 |
)
|
| 30 |
+
from models.tokenizer import get_tokenizer, get_effective_vocab_size
|
| 31 |
from torch.utils.data import Dataset, DataLoader
|
| 32 |
import json
|
| 33 |
import os
|
|
|
|
| 158 |
return model, config
|
| 159 |
|
| 160 |
|
| 161 |
+
def create_tokenizer(tokenizer_type: str = "gpt2", vocab_size: int = 32000):
|
| 162 |
"""
|
| 163 |
+
Create tokenizer using the unified tokenizer module.
|
| 164 |
+
|
| 165 |
+
Note: Currently uses GPT2 as placeholder until SentencePiece model is trained.
|
| 166 |
+
The model config vocab_size will be auto-adjusted to match.
|
| 167 |
"""
|
| 168 |
+
effective_vocab = get_effective_vocab_size(tokenizer_type, vocab_size)
|
| 169 |
+
logger.info(f"[create_tokenizer] Creating tokenizer: type={tokenizer_type}, effective_vocab={effective_vocab}")
|
| 170 |
+
tokenizer = get_tokenizer(tokenizer_type, vocab_size=vocab_size)
|
|
|
|
| 171 |
return tokenizer
|
| 172 |
|
| 173 |
|