fusion-llm-demo / inference /ollama_deploy.py
zhan1206
fix(v12): resolve 6 defects from v11 audit (F-NEW-6, S-NEW-5, M-NEW-5/6, MI-NEW-4/5/6)
656dacb
Raw
History Blame
11.7 kB
"""
Fusion 推理部署:Ollama 一键部署工具
功能:
1. 自动转换模型为 GGUF 格式
2. 生成 Modelfile
3. 创建 Ollama 模型
4. 支持 Thinking Dial 控制
使用方法:
python inference/ollama_deploy.py --model_path ./output/fusion-8b --model_name fusion-8b
前置要求:
- 安装 llama.cpp(用于转换)
- 安装 Ollama(https://ollama.com)
作者:zhan1206
项目:Fusion - 六边形开源大模型
许可证:Apache 2.0
"""
import argparse
import subprocess
import os
import json
from pathlib import Path
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def check_dependencies():
"""
检查依赖项
"""
logger.info("[SEARCH] 检查依赖项...")
# 检查 llama.cpp 转换脚本
llama_cpp_dir = os.environ.get("LLAMA_CPP_DIR", "")
convert_script = os.path.join(llama_cpp_dir, "convert-hf-to-gguf.py")
if not os.path.exists(convert_script):
logger.warning(f"[WARN] 未找到 llama.cpp 转换脚本:{convert_script}")
logger.warning(" 请设置环境变量 LLAMA_CPP_DIR 或手动下载 llama.cpp")
logger.warning(" https://github.com/ggerganov/llama.cpp")
return False
# 检查 Ollama
try:
result = subprocess.run(
["ollama", "--version"],
capture_output=True,
text=True,
shell=True,
)
if result.returncode == 0:
logger.info(f"[OK] Ollama 已安装:{result.stdout.strip()}")
else:
logger.warning("[WARN] Ollama 未安装或无法运行")
logger.warning(" 请访问 https://ollama.com 安装")
return False
except FileNotFoundError:
logger.warning("[WARN] Ollama 未安装")
logger.warning(" 请访问 https://ollama.com 安装")
return False
logger.info("[OK] 依赖项检查通过")
return True
def convert_to_gguf(
model_path: str,
output_path: str,
quantize: str = "q4_k_m",
):
"""
将 HuggingFace 模型转换为 GGUF 格式
参数:
model_path: HuggingFace 模型路径
output_path: 输出路径
quantize: 量化级别(q4_k_m, q5_k_m, q8_0 等)
"""
logger.info("[SYNC] 转换为 GGUF 格式...")
llama_cpp_dir = os.environ.get("LLAMA_CPP_DIR", "")
convert_script = os.path.join(llama_cpp_dir, "convert-hf-to-gguf.py")
# 转换命令
cmd = [
"python", convert_script,
model_path,
"--outtype", "f16", # 先转为 f16
"--outfile", output_path,
]
logger.info(f" 运行命令:{' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"[FAIL] 转换失败:{result.stderr}")
raise RuntimeError("GGUF 转换失败")
logger.info(f"[OK] GGUF 转换完成:{output_path}")
# 量化(可选)
if quantize:
logger.info(f"[TOOL] 量化模型({quantize})...")
quantize_cmd = [
os.path.join(llama_cpp_dir, "llama-quantize"),
output_path,
output_path.replace(".gguf", f"_{quantize}.gguf"),
quantize,
]
result = subprocess.run(quantize_cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.warning(f"[WARN] 量化失败:{result.stderr}")
logger.warning(" 继续使用未量化模型")
else:
output_path = output_path.replace(".gguf", f"_{quantize}.gguf")
logger.info(f"[OK] 量化完成:{output_path}")
return output_path
def create_modelfile(
model_path: str,
modelfile_path: str,
model_name: str,
context_size: int = 32768,
thinking_dial: bool = True,
):
"""
创建 Ollama Modelfile
参数:
model_path: GGUF 模型路径
modelfile_path: Modelfile 输出路径
model_name: 模型名称
context_size: 上下文窗口大小
thinking_dial: 是否启用 Thinking Dial
"""
logger.info("[NOTE] 创建 Modelfile...")
# Modelfile 内容
content = f"""# Fusion 模型:{model_name}
# 由 Fusion 项目自动生成
# 项目地址:<ADDRESS_REMOVED>
FROM {model_path}
# 模型参数
PARAMETER num_ctx {context_size}
PARAMETER temperature 0.8
PARAMETER top_p 0.95
PARAMETER repeat_penalty 1.1
# 系统提示词
SYSTEM \"\"\"你是一个强大的 AI 助手。你支持动态推理强度控制:
- 简单问题:直接回答
- 复杂问题:启用思维链推理
使用 <|think| depth=N|> 控制推理深度(N=0-3)。
\"\"\"
# 模板(支持 Thinking Dial)
TEMPLATE \"\"\"{{ if .System }}<|im_start|>system
{{ .System }}<|im_end|>
{{ end }}{{ if .Prompt }}<|im_start|>user
{{ .Prompt }}<|im_end|>
{{ end }}<|im_start|>assistant
\"\"\"
"""
# 如果启用 Thinking Dial,添加特殊 token 处理
if thinking_dial:
content += f"""
# Thinking Dial 示例(训练时注入)
# <|think_depth_0|> 简单问题,直接回答
# <|think_depth_3|> 复杂问题,详细推理
"""
# 写入文件
with open(modelfile_path, 'w', encoding='utf-8') as f:
f.write(content)
logger.info(f"[OK] Modelfile 创建完成:{modelfile_path}")
def create_ollama_model(modelfile_path: str, model_name: str):
"""
使用 Modelfile 创建 Ollama 模型
参数:
modelfile_path: Modelfile 路径
model_name: 模型名称
"""
logger.info(f"[GO] 创建 Ollama 模型:{model_name}...")
# 删除已存在的模型
subprocess.run(
["ollama", "rm", model_name],
capture_output=True,
)
# 创建模型
cmd = ["ollama", "create", model_name, "-f", modelfile_path]
logger.info(f" 运行命令:{' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"[FAIL] 创建失败:{result.stderr}")
raise RuntimeError("Ollama 模型创建失败")
logger.info(f"[OK] Ollama 模型创建成功:{model_name}")
logger.info(f" 运行 `ollama run {model_name}` 开始使用")
def deploy(
model_path: str,
model_name: str,
output_dir: str = "./ollama_output",
quantize: str = "q4_k_m",
context_size: int = 32768,
thinking_dial: bool = True,
):
"""
完整部署流程
参数:
model_path: HuggingFace 模型路径
model_name: 模型名称
output_dir: 输出目录
quantize: 量化级别
context_size: 上下文窗口
thinking_dial: 是否启用 Thinking Dial
"""
logger.info("[GO] 开始 Ollama 部署流程...")
logger.info(f" 模型路径:{model_path}")
logger.info(f" 模型名称:{model_name}")
# 1. 检查依赖
if not check_dependencies():
logger.error("[FAIL] 依赖项检查失败,请先安装所需工具")
return False
# 2. 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 3. 转换为 GGUF
gguf_path = os.path.join(output_dir, f"{model_name}.gguf")
try:
gguf_path = convert_to_gguf(
model_path=model_path,
output_path=gguf_path,
quantize=quantize,
)
except RuntimeError as e:
logger.error(f"[FAIL] GGUF 转换失败:{e}")
return False
# 4. 创建 Modelfile
modelfile_path = os.path.join(output_dir, "Modelfile")
create_modelfile(
model_path=gguf_path,
modelfile_path=modelfile_path,
model_name=model_name,
context_size=context_size,
thinking_dial=thinking_dial,
)
# 5. 创建 Ollama 模型
try:
create_ollama_model(
modelfile_path=modelfile_path,
model_name=model_name,
)
except RuntimeError as e:
logger.error(f"[FAIL] Ollama 模型创建失败:{e}")
return False
# 6. 生成使用示例
example_path = os.path.join(output_dir, "USAGE.md")
generate_usage_example(model_name, example_path)
logger.info("[OK] 部署完成!")
logger.info(f" 运行:`ollama run {model_name}`")
logger.info(f" 示例:见 {example_path}")
return True
def generate_usage_example(model_name: str, output_path: str):
"""
生成使用示例文档
"""
content = f"""# Fusion 模型使用示例
## 1. 基础使用
```bash
# 启动模型
ollama run {model_name}
# 在交互界面中输入问题
> 解释量子纠缠
```
## 2. Thinking Dial 控制
Fusion 支持动态推理强度控制。在问题前添加控制 token:
```bash
# depth=0:直接回答(闲聊、翻译)
> <|think_depth_0|> 今天天气怎么样?
# depth=1:简单推理
> <|think_depth_1|> 计算 123 * 456
# depth=2:中等推理
> <|think_depth_2|> 证明勾股定理
# depth=3:深度推理(思维链)
> <|think_depth_3|> 解决这个算法问题:...
```
## 3. REST API
Ollama 提供 OpenAI 兼容的 API:
```bash
# 启动 Ollama 服务
ollama serve
# 调用 API
curl <a href="http://localhost:11434/api/generate">http://localhost:11434/api/generate</a> -d {{
"model": "{model_name}",
"prompt": "解释机器学习",
"stream": false
}}
```
## 4. Python 调用
```python
import ollama
# 基础调用
response = ollama.generate(
model="{model_name}",
prompt="解释量子纠缠",
)
print(response["response"])
# 带 Thinking Dial
response = ollama.generate(
model="{model_name}",
prompt="<|think_depth_2|> 证明勾股定理",
)
print(response["response"])
```
## 5. 参数调整
在 Ollama 中调整生成参数:
```bash
# 温度(创造性)
ollama run {model_name} --temperature 0.9
# 上下文窗口
ollama run {model_name} --num_ctx 16384
# Top-p 采样
ollama run {model_name} --top_p 0.95
```
---
**提示**:更多用法见 Ollama 文档 https://ollama.com/docs
"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(content)
logger.info(f"[INFO] 使用示例已生成:{output_path}")
def main():
parser = argparse.ArgumentParser(description="Fusion Ollama 一键部署")
parser.add_argument("--model_path", type=str, required=True,
help="HuggingFace 模型路径")
parser.add_argument("--model_name", type=str, required=True,
help="Ollama 模型名称(如 fusion-8b)")
parser.add_argument("--output_dir", type=str, default="./ollama_output",
help="输出目录")
parser.add_argument("--quantize", type=str, default="q4_k_m",
choices=["q4_k_m", "q5_k_m", "q8_0", "f16"],
help="量化级别")
parser.add_argument("--context_size", type=int, default=32768,
help="上下文窗口大小)")
parser.add_argument("--no_thinking_dial", action="store_false",
dest="thinking_dial",
help="禁用 Thinking Dial")
args = parser.parse_args()
# 执行部署
success = deploy(
model_path=args.model_path,
model_name=args.model_name,
output_dir=args.output_dir,
quantize=args.quantize,
context_size=args.context_size,
thinking_dial=args.thinking_dial,
)
if success:
logger.info("[DONE] 部署成功!")
else:
logger.error("[FAIL] 部署失败")
if __name__ == "__main__":
main()