From 89678106fc8445fb787316575a0793f385cc5ee5 Mon Sep 17 00:00:00 2001 From: king-of-xdu <1227418358@qq.com> Date: Tue, 29 Jul 2025 16:38:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9D=A8=E9=B9=8F=E7=BF=94-OpenCloudOS=5F9?= =?UTF-8?q?=E7=A1=AC=E4=BB=B6=E8=87=AA=E9=80=82=E5=BA=94LLM=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bin/ocl-llm" | 9 + .../config.yaml" | 3 + .../ocl_llm/__init__.py" | 6 + .../ocl_llm/config.py" | 22 ++ .../ocl_llm/hardware/__init__.py" | 1 + .../ocl_llm/hardware/scanner.py" | 202 ++++++++++++++++++ .../ocl_llm/main.py" | 74 +++++++ .../ocl_llm/models.py" | 89 ++++++++ .../ocl_llm/optimizer.py" | 129 +++++++++++ .../ocl_llm/runner.py" | 139 ++++++++++++ .../ocl_llm/utils/__init__.py" | 1 + .../ocl_llm/utils/display.py" | 10 + .../readme.md" | 14 ++ .../requirements.txt" | 3 + 14 files changed, 702 insertions(+) create mode 100755 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/bin/ocl-llm" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/config.yaml" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/__init__.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/config.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/__init__.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/scanner.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/main.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/models.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/optimizer.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/runner.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/__init__.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/display.py" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/readme.md" create mode 100644 "\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/requirements.txt" diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/bin/ocl-llm" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/bin/ocl-llm" new file mode 100755 index 0000000..e9b72a0 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/bin/ocl-llm" @@ -0,0 +1,9 @@ +#!/bin/bash +# 设置项目根目录 +PROJECT_DIR=$(dirname $(dirname $(realpath $0))) + +# 添加项目目录到Python路径 +export PYTHONPATH="$PROJECT_DIR:$PYTHONPATH" + +# 执行主程序 +python -m ocl_llm.main \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/config.yaml" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/config.yaml" new file mode 100644 index 0000000..c691c4c --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/config.yaml" @@ -0,0 +1,3 @@ +# OpenCloudOS LLM 配置文件 +#default_model: "deepseek-r1:1.5b" # 设置默认模型覆盖自动选择 +system_reserved_cores: 1 # 为系统保留的核心数 \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/__init__.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/__init__.py" new file mode 100644 index 0000000..ccf2726 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/__init__.py" @@ -0,0 +1,6 @@ +from . import main +from . import config +from . import optimizer +from . import models +from . import runner +from . import hardware \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/config.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/config.py" new file mode 100644 index 0000000..3faaac4 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/config.py" @@ -0,0 +1,22 @@ +import yaml +import os +from .utils.display import display + +class ConfigLoader: + def load(self): + config_path = "config.yaml" + default_config = { + 'default_model': None, # 如果不设置则自动选择 + 'system_reserved_cores': 1 + } + + if os.path.exists(config_path): + try: + with open(config_path, 'r') as f: + config = yaml.safe_load(f) + # 合并默认配置 + return {**default_config, **config} + except Exception as e: + display(f"⚠️ 配置文件加载错误: {e}, 使用默认配置") + + return default_config \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/__init__.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/__init__.py" new file mode 100644 index 0000000..a7b3f59 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/__init__.py" @@ -0,0 +1 @@ +from .scanner import HardwareScanner \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/scanner.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/scanner.py" new file mode 100644 index 0000000..5e74a32 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/hardware/scanner.py" @@ -0,0 +1,202 @@ +import psutil +import os +import re +import platform + +class HardwareScanner: + def __init__(self): + self.cpu_info = {} + self.gpu_info = {} + self.memory = 0 + self.disk_info = {} + + def scan(self): + self.scan_cpu() + self.scan_memory() + self.scan_gpu() + self.scan_disk() + return { + 'cpu': self.cpu_info, + 'memory': self.memory, + 'gpu': self.gpu_info, + 'disk': self.disk_info + } + + def scan_cpu(self): + """获取CPU信息,包括评分计算""" + try: + # 获取CPU核心数和线程数 + cores = psutil.cpu_count(logical=False) or 1 + threads = psutil.cpu_count(logical=True) or 2 + + # 获取CPU频率信息 + try: + freq_info = psutil.cpu_freq() + base_freq = freq_info.max / 1000 # 转换为GHz + except: + base_freq = 3.0 # 默认值 + + # 计算CPU评分:更合理的算法 + # 基础分 = 核心数 * 基础频率 + # 多线程加成 = (线程数/核心数) * 20% + base_score = cores * base_freq + multithread_boost = 1 + (threads / cores - 1) * 0.2 if cores > 0 else 1 + score = base_score * multithread_boost + + # 获取CPU品牌信息 + try: + with open('/proc/cpuinfo') as f: + cpuinfo = f.read() + brand_match = re.search(r'model name\s*:\s*(.+)$', cpuinfo, re.MULTILINE) + brand = brand_match.group(1).strip() if brand_match else "未知CPU" + except: + brand = "未知CPU" + + self.cpu_info = { + 'brand': brand, + 'cores': cores, + 'threads': threads, + 'base_freq': base_freq, + 'score': round(score, 1) # 保留一位小数 + } + + except Exception as e: + # 错误处理 + display(f"⚠️ CPU扫描失败: {e}") + self.cpu_info = { + 'brand': "未知CPU", + 'cores': 1, + 'threads': 2, + 'base_freq': 2.0, + 'score': 2.0 + } + + def scan_memory(self): + self.memory = round(psutil.virtual_memory().total / (1024 ** 3)) + + def scan_gpu(self): + # 先尝试检测NVIDIA GPU + if self.try_scan_nvidia_gpu(): + return + + # 尝试检测AMD GPU或其他类型 + # self.try_scan_other_gpu() + # 当前仅处理NVIDIA + + # 没有检测到任何GPU + self.gpu_info = None + + def try_scan_nvidia_gpu(self): + try: + from pynvml import nvmlInit, nvmlDeviceGetCount, nvmlDeviceGetHandleByIndex, nvmlDeviceGetMemoryInfo, nvmlDeviceGetName + + nvmlInit() + device_count = nvmlDeviceGetCount() + if device_count > 0: + handle = nvmlDeviceGetHandleByIndex(0) + memory_info = nvmlDeviceGetMemoryInfo(handle) + device_name = nvmlDeviceGetName(handle) + + # 简化设备名称 + device_name = str(device_name, 'utf-8') if isinstance(device_name, bytes) else device_name + device_name = re.sub(r'GeForce\s*|RTX\s*|NVIDIA\s*', '', device_name) + + self.gpu_info = { + 'name': device_name.strip(), + 'vram': round(memory_info.total / (1024 ** 3)), # 转换为GB + 'cuda_cores': self.get_cuda_cores(device_name.strip()) + } + return True + except ImportError: + print("⚠️ 警告: pynvml模块未安装,无法检测NVIDIA GPU信息") + except Exception as e: + print(f"⚠️ 警告: 获取NVIDIA GPU信息失败: {e}") + + return False + def scan_disk(self): + """扫描磁盘空间信息""" + try: + # 获取所有分区信息 + partitions = psutil.disk_partitions(all=False) + disk_info = { + 'total': 0, + 'free': 0, + 'partitions': [] + } + + for partition in partitions: + try: + usage = psutil.disk_usage(partition.mountpoint) + disk_info['total'] += usage.total + disk_info['free'] += usage.free + disk_info['partitions'].append({ + 'mountpoint': partition.mountpoint, + 'device': partition.device, + 'total': usage.total, + 'free': usage.free + }) + except Exception: + continue + + # 转换为GB + disk_info['total_gb'] = round(disk_info['total'] / (1024 ** 3)) + disk_info['free_gb'] = round(disk_info['free'] / (1024 ** 3)) + + self.disk_info = disk_info + except Exception as e: + print(f"⚠️ 警告: 磁盘扫描失败: {e}") + self.disk_info = { + 'total_gb': 0, + 'free_gb': 0, + 'partitions': [] + } + def get_cuda_cores(self, gpu_name): + """根据GPU名称估计CUDA核心数 - 改进版本""" + # 清理和标准化 GPU 名称 + clean_name = re.sub(r'[^a-zA-Z0-9\s]', '', gpu_name).strip().upper() + clean_name = re.sub(r'\s+', ' ', clean_name) # 合并多余空格 + + # 创建更全面的映射表 + cuda_core_map = { + "5090 D": 16384, "5090": 16384, + "4090": 16384, "4080": 9728, "3090": 10496, "3080": 8704, + "3070": 5888, "3060": 3584, "3050": 2560, + "A100": 6912, "A6000": 10752, "V100": 5120, + "TITAN V": 5120, "TITAN RTX": 4608, + "1080 TI": 3584, "1080": 2560, "1070": 1920, "1060": 1280, + "1650": 896, "MX150": 384, + # 添加更多常见型号 + "A5000": 8192, "A4000": 6144, "A2000": 3328, + "QUADRO RTX 6000": 4608, "QUADRO RTX 8000": 4608, + "TESLA P100": 3584, "TESLA T4": 2560 + } + + # 优先尝试完整匹配 + if clean_name in cuda_core_map: + return cuda_core_map[clean_name] + + # 尝试部分匹配(更灵活的匹配) + for pattern, cores in cuda_core_map.items(): + # 允许数字部分匹配(如 "5090" 匹配 "5090 D") + if re.search(rf'\b{pattern}\b', clean_name): + return cores + + # 提取主要数字型号(如从 "5090 D" 提取 "5090") + model_match = re.search(r'(\d{4})', clean_name) + if model_match: + model_num = model_match.group(1) + # 根据数字型号估计 + if model_num.startswith('50') or model_num.startswith('40'): + return 16384 # 50/40系列高端卡 + elif model_num.startswith('30'): + return 7000 # 30系列中高端 + + # 最后根据架构估算 + if "RTX 40" in clean_name or "40" in clean_name: + return 16384 # 40系列高端卡 + elif "RTX 30" in clean_name or "30" in clean_name: + return 10000 # 30系列高端卡 + elif "RTX 20" in clean_name or "20" in clean_name: + return 5000 # 20系列中端卡 + + return 0 # 无法确定 \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/main.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/main.py" new file mode 100644 index 0000000..351de60 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/main.py" @@ -0,0 +1,74 @@ +# ocl_llm/main.py +#!/usr/bin/env python3 +import sys +from .utils.display import display +from .hardware.scanner import HardwareScanner +from .optimizer import Optimizer +from .models import ModelManager +from .runner import Runner +from .config import ConfigLoader + +def main(): + display("🚀🚀 启动 OpenCloudOS LLM 部署工具...") + + try: + # 加载配置 + config = ConfigLoader().load() + + # 硬件扫描 + display("🔍🔍 正在检测硬件配置...") + scanner = HardwareScanner() + hardware = scanner.scan() + display(f"🖥🖥🖥️ 硬件检测结果:\n{format_hardware_info(hardware)}") + + # 优化配置 + optimizer = Optimizer(config) + params = optimizer.optimize_params(hardware) + display(f"⚙⚙️ 优化配置:\n{format_params(params)}") + + # 模型管理 + model_manager = ModelManager(config) + model_tag = params['model'] + model_manager.ensure_model(model_tag) + + # 运行服务 + runner = Runner(config) + runner.run(model_tag, params) + + except Exception as e: + display(f"❌❌ 发生错误: {e}") + sys.exit(1) + +def format_hardware_info(hardware): + # CPU信息 + cpu_info = (f"• CPU: {hardware['cpu'].get('brand', '未知')}\n" + f" 核心数: {hardware['cpu'].get('cores', 1)}核 {hardware['cpu'].get('threads', 2)}线程\n" + f" 基准频率: {hardware['cpu'].get('base_freq', 0)}GHz\n" + f" 综合评分: {hardware['cpu'].get('score', 1)}") + + # GPU信息 + gpu_info = "" + if hardware['gpu']: + gpu_info = (f"• GPU: {hardware['gpu']['name']}\n" + f" VRAM: {hardware['gpu']['vram']}GB\n" + f" CUDA核心: {hardware['gpu'].get('cuda_cores', 0)}") + else: + gpu_info = "• GPU: 未检测到" + + # 磁盘信息 + disk_info = (f"• 磁盘: 总空间 {hardware['disk']['total_gb']}GB, " + f"可用空间 {hardware['disk']['free_gb']}GB") + + # 内存信息 + memory_info = f"• 内存: {hardware['memory']}GB" + + return f"{cpu_info}\n{memory_info}\n{gpu_info}\n{disk_info}" + +def format_params(params): + return (f"• 推荐模型: {params['model']}\n" + f"• 上下文窗口: {params['ctx_size']} tokens\n" + f"• CPU线程: {params['threads']}\n" + f"• GPU加速层: {params.get('gpu_layers', 0)}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/models.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/models.py" new file mode 100644 index 0000000..97834ac --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/models.py" @@ -0,0 +1,89 @@ +import subprocess +import re +import psutil +from .utils.display import display +from .runner import OllamaService # 导入服务管理器 + +class ModelManager: + def __init__(self, config): + self.config = config + self.service = OllamaService() # 创建服务实例 + # 添加模型大小信息(与optimizer中一致) + self.model_sizes = { + "deepseek-r1:671b": 404, + "deepseek-r1:70b": 43, + "deepseek-r1:32b": 20, + "deepseek-r1:14b": 9, + "deepseek-r1:8b": 5.2, + "deepseek-r1:7b": 4.7, + "deepseek-r1:1.5b": 1.1, + "deepseek-r1:1.5b-qwen-distill-q4_K_M": 1.1 + } + + def model_exists(self, model_tag): + """检查模型是否已存在""" + cmd = ["ollama", "list"] + try: + result = subprocess.run(cmd, capture_output=True, text=True) + return model_tag in result.stdout + except: + return False + + def ensure_model(self, model_tag): + """确保模型存在,如果不存在则下载""" + # 确保 Ollama 服务已启动 + self.service.start() + + display(f"检查模型 {model_tag} 是否已存在...") + if self.model_exists(model_tag): + display(f"✅ 模型 {model_tag} 已存在") + return + + # 检查磁盘空间是否足够 + if not self.check_disk_space(model_tag): + required = self.model_sizes.get(model_tag, 5) * 1.2 + display(f"🛑🛑🛑 磁盘空间不足,无法下载 {model_tag} 模型") + raise Exception(f"磁盘空间不足,至少需要 {required}GB 空间") + + display(f"🚀 下载模型: {model_tag} (可能需要几分钟)...") + try: + process = subprocess.Popen( + ["ollama", "pull", model_tag], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True + ) + + # 实时输出下载进度 + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + if output: + # 过滤掉pull命令的多余输出 + if not output.startswith("pulling manifest") and not output.startswith("verifying sha256 digest"): + display(output.strip()) + + if process.returncode == 0: + display(f"✅ 模型 {model_tag} 下载完成") + else: + display(f"🛑 模型下载失败,退出码: {process.returncode}") + raise Exception(f"模型 {model_tag} 下载失败") + except Exception as e: + display(f"❌ 模型下载出错: {e}") + raise + + def check_disk_space(self, model_tag): + """检查磁盘是否有足够空间下载模型""" + try: + # 获取模型所需空间 + required_space = self.model_sizes.get(model_tag, 5) # GB + + # 获取磁盘信息 + disk = psutil.disk_usage('/') + free_gb = disk.free / (1024 ** 3) + + return free_gb >= required_space * 1.2 # 多留20%空间 + except Exception as e: + display(f"⚠️ 磁盘空间检查失败: {e}") + return True # 失败时默认继续 \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/optimizer.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/optimizer.py" new file mode 100644 index 0000000..febda00 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/optimizer.py" @@ -0,0 +1,129 @@ +class Optimizer: + def __init__(self, config): + self.config = config + # 添加CPU模型评分阈值 + self.model_score_thresholds = { + "deepseek-r1:671b": 350, # 高性能服务器级 + "deepseek-r1:70b": 180, + "deepseek-r1:32b": 100, + "deepseek-r1:14b": 50, + "deepseek-r1:8b": 30, + "deepseek-r1:7b": 20, + "deepseek-r1:1.5b": 8, # 最低要求 + "deepseek-r1:1.5b-qwen-distill-q4_K_M": 5 # 量化模型要求更低 + } + # 添加模型大小信息 + self.model_sizes = { + "deepseek-r1:671b": 404, + "deepseek-r1:70b": 43, + "deepseek-r1:32b": 20, + "deepseek-r1:14b": 9, + "deepseek-r1:8b": 5.2, + "deepseek-r1:7b": 4.7, + "deepseek-r1:1.5b": 1.1, + "deepseek-r1:1.5b-qwen-distill-q4_K_M": 1.1 + } + + def optimize_params(self, hardware): + params = { + 'model': self.select_model(hardware), + 'threads': self.calculate_threads(hardware), + 'gpu_layers': self.calculate_gpu_layers(hardware), + 'ctx_size': self.calculate_ctx_size(hardware) + } + return params + + def select_model(self, hardware): + # 优先选择配置中的默认模型 + default_model = self.config.get('default_model') + if default_model: + return default_model + + + cpu = hardware.get('cpu', {}) + gpu = hardware.get('gpu') + disk = hardware.get('disk', {}) + + gpu_vram = gpu.get('vram') if gpu else 0 + gpu_cuda_cores = gpu.get('cuda_cores', 0) if gpu else 0 + disk_free_gb = hardware['disk']['free_gb'] if 'disk' in hardware else 0 + + + if not gpu: + return self.select_cpu_model(cpu, disk) + + # 简化模型选择逻辑 + if gpu_vram >= 32: # 32GB VRAM + return "deepseek-r1:32b" if disk_free_gb >= 140 else "deepseek-r1:14b" + elif gpu_vram >= 16: + return "deepseek-r1:14b" if disk_free_gb >= 28 else "deepseek-r1:7b" + elif gpu_vram >= 8: + return "deepseek-r1:7b" if disk_free_gb >= 14 else "deepseek-r1:1.5b" + else: + return "deepseek-r1:1.5b" + + def fallback_model(self, hardware, disk_free_gb): + """磁盘空间不足时的模型选择策略""" + # 尝试选择较小模型 + if disk_free_gb >= 5: + return "deepseek-r1:1.5b" + elif disk_free_gb >= 2: + return "qwen:0.5b" + else: + return self.minimal_model() + + def minimal_model(self): + """返回最小可用模型""" + return "qwen:0.5b" # 假设存在一个最小模型 + + def calculate_threads(self, hardware): + # 留出一个核心给系统 + return max(1, hardware['cpu']['threads'] - 1) + + def calculate_gpu_layers(self, hardware): + if not hardware['gpu']: + return 0 + # 每1GB显存大约可加载一层 + return min(99, int(hardware['gpu']['vram'])) + + def calculate_ctx_size(self, hardware): + """修复:移除对 hardware['gpu_layers'] 的引用""" + gpu = hardware.get('gpu') + gpu_vram = gpu.get('vram') if gpu else 0 + + # 直接使用 GPU VRAM 计算上下文大小 + if gpu_vram > 0: + return min(8192, int(gpu_vram * 512)) + + # 纯 CPU 模式使用内存计算 + return min(4096, int(hardware['memory'] * 256)) + + def select_cpu_model(self, cpu, disk): + """纯CPU模式下的模型选择""" + # 获取CPU评分和磁盘信息 + cpu_score = cpu.get('score', 1) + disk_free = disk.get('free_gb', 0) + + # 根据CPU评分和磁盘空间选择模型 + # 从大到小尝试选择模型 + for model, min_score in sorted(self.model_score_thresholds.items(), + key=lambda x: x[1], reverse=True): + model_size = self.model_sizes.get(model, 0) + + # 检查CPU性能是否足够 + if cpu_score >= min_score: + # 检查磁盘空间是否足够(留出20%安全空间) + if disk_free >= model_size * 1.2: + return model + else: + display(f"⚠️ CPU足够但磁盘空间不足({disk_free}GB < {model_size*1.2}GB),尝试更小模型") + + # 如果都不满足,选择最小模型 + min_model = "qwen:0.5b" + min_size = self.model_sizes.get(min_model, 1) + + if disk_free < min_size * 1.2: + display(f"🛑🛑🛑 磁盘空间不足({disk_free}GB),无法运行最小模型({min_model})") + raise Exception("磁盘空间不足,无法运行任何模型") + + return min_model \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/runner.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/runner.py" new file mode 100644 index 0000000..294aa97 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/runner.py" @@ -0,0 +1,139 @@ +import os +import subprocess +import time +import atexit +from .utils.display import display + +class OllamaService: + def __init__(self): + self.service_process = None + + def start(self): + """启动 Ollama 服务""" + # 检查服务是否已经在运行 + if self.is_running(): + display("🔧 Ollama 服务已在运行") + return + + display("🛠️ 正在启动 Ollama 服务...") + try: + # 启动服务并捕获输出 + self.service_process = subprocess.Popen( + ["ollama", "serve"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True + ) + + # 等待服务启动完成 + display("⏳ 等待服务初始化...") + time.sleep(2) + + # 检查服务是否成功启动 + if self.is_running(): + display("✅ Ollama 服务已启动") + + # 注册退出时的清理函数 + atexit.register(self.stop) + else: + display("🛑 Ollama 服务启动失败") + self.log_output() + raise Exception("无法启动 Ollama 服务") + + except Exception as e: + display(f"❌ Ollama 服务启动失败: {e}") + raise + + def stop(self): + """停止 Ollama 服务""" + if self.service_process: + display("🛑 正在停止 Ollama 服务...") + try: + # 发送 SIGTERM 信号 + self.service_process.terminate() + + # 等待进程退出 + display("⏳ 等待服务停止...") + self.service_process.wait(timeout=10) + display("✅ Ollama 服务已停止") + + except Exception as e: + display(f"⚠️ 停止服务时出错: {e}") + finally: + self.service_process = None + + def is_running(self): + """检查 Ollama 服务是否正在运行""" + try: + result = subprocess.run( + ["curl", "-s", "http://localhost:11434/"], + capture_output=True, + text=True, + timeout=2 + ) + return "Ollama is running" in result.stdout + except: + return False + + def log_output(self): + """记录服务输出到日志""" + if not self.service_process: + return + + display("📄 服务输出日志:") + while True: + line = self.service_process.stdout.readline() + if not line and self.service_process.poll() is not None: + break + if line: + display(f" | {line.strip()}") + + display("--------------------") + +class Runner: + def __init__(self, config): + self.config = config + self.service = OllamaService() + + def run(self, model_tag, params): + """启动 Ollama 服务和 LLM 模型""" + try: + # 确保 Ollama 服务已启动 + self.service.start() + + # 启动模型服务 + display("✅ 启动LLM服务... (按Ctrl+C退出)") + + # 准备环境变量 + env = os.environ.copy() + env["OLLAMA_NUM_THREADS"] = str(params['threads']) + env["OLLAMA_NUM_CTX"] = str(params['ctx_size']) + + if params.get('gpu_layers', 0) > 0: + env["OLLAMA_GPU_LAYERS"] = str(params['gpu_layers']) + else: + # 禁用GPU加速 + env["OLLAMA_GPU_LAYERS"] = "0" + display("💡 纯CPU模式运行,禁用GPU加速") + + # 构建命令 + cmd = ["ollama", "run", model_tag] + + # 显示执行命令 + display(f"执行命令: {' '.join(cmd)}") + display(f"环境配置: [线程数: {params['threads']}, 上下文窗口: {params['ctx_size']}" + + (f", GPU加速层: {params['gpu_layers']}" if params.get('gpu_layers') else "")) + + # 启动模型服务 + subprocess.run(cmd, env=env) + + except KeyboardInterrupt: + display("\n🛑 服务已停止") + self.service.stop() + except Exception as e: + display(f"❌ 服务启动失败: {e}") + self.service.log_output() + self.service.stop() + raise + finally: + self.service.stop() \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/__init__.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/__init__.py" new file mode 100644 index 0000000..724c321 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/__init__.py" @@ -0,0 +1 @@ +from .display import display \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/display.py" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/display.py" new file mode 100644 index 0000000..ff664dc --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/ocl_llm/utils/display.py" @@ -0,0 +1,10 @@ +import sys + +def display(message): + """统一的显示函数,支持Emoji表情""" + print(message, flush=True) + + # 如果是错误信息,也记录到日志 + if "❌" in message or "🛑" in message or "Error" in message: + with open("ocl-llm.log", "a") as log_file: + log_file.write(message + "\n") \ No newline at end of file diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/readme.md" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/readme.md" new file mode 100644 index 0000000..33b95c2 --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/readme.md" @@ -0,0 +1,14 @@ +使用方法: + 1. 需要首先安装ollama(速度太慢考虑离线下载): + curl -fsSL https://ollama.com/install.sh | sh + 2. 安装pip: + sudo dnf install python3-pip + 3. 安装依赖: + pip3 install -r requirements.txt + 4. 给脚本可执行的权限: + chmod +x ./bin/ocl-llm + 5. 运行脚本: + ./bin/ocl_llm +核心功能: + 一键式部署​​:自动完成环境检测与模型部署 + ​​智能硬件评估​​:CPU/GPU/内存/磁盘综合检测与评分 diff --git "a/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/requirements.txt" "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/requirements.txt" new file mode 100644 index 0000000..d243aaf --- /dev/null +++ "b/\344\270\212\346\211\213issue_\344\275\234\345\223\201\346\226\207\344\273\266\345\244\271/\346\235\250\351\271\217\347\277\224-OpenCloudOS_9\347\241\254\344\273\266\350\207\252\351\200\202\345\272\224LLM\351\203\250\347\275\262\345\267\245\345\205\267/requirements.txt" @@ -0,0 +1,3 @@ +psutil +pynvml +pyyaml \ No newline at end of file -- Gitee