|-转 CoPilot用Claude Sonnet 4模型调试多平台自动安装python训练模型或机器学习环境
Sorry, your request failed. Please try again. Request id: ffd3eb9c-a6d7-45a5-9c90-f125436d026d
Claude Sonnet 4模型好像调用次数没了。报错Reason: Request Failed: 400 {"error":{"message":"messages.1.content.1.tool_use.input: Input should be a valid dictionary","code":"invalid_request_body"}}我只好换模型GPT-5 preview聊天了。20250905 0416
python -c "import torch; print(torch.__version__)"
Copilot用Claude Sonnet 4模型的阶段性总结。
总结
我已经完成了对您脚本的全面优化,解决了以下问题:
🔧 主要改进
超时处理增强
大型包(torch, torchaudio, torchvision)超时时间延长至30分钟
详细的超时日志记录,包含镜像、时间、包列表等信息
自动保存超时详情到日志文件
智能包检测
torch: 检测是否为GPU版本(包含+cu标识)
torchaudio/torchvision: 检查版本兼容性而不是强制要求GPU后缀
您当前的安装(torch 2.8.0+cu128, torchaudio 2.8.0, torchvision 0.23.0)会被正确识别为兼容并跳过重装
进度显示优化
每条下载信息显示已用时间...
|--转 文心一言调试多平台自动安装python训练模型或机器学习环境
torch,torchvision,torchaudio 已经安装了。
运行./setup_train_env.sh --core-packages torch,torchvision,torchaudio 还是是会下载安装torch ,"--core-packages torch,torchvision,torchaudio"CLI的含义是保护这些核心包不被重复安装或被其他安装的包依赖安装。终端输出的部分内容:Requirement already satisfied: mpmath<1.4,>=1.1.0 in g:\programdata\miniconda3_win11\lib\site-packages (from sympy>=1.13.3->torch) (1.3.0)
Requirement already satisfied: MarkupSafe>=2.0 in g:\programdata\miniconda3_win11\lib\site-packages (from jinja2->torch) (3.0.2)
📥 [9s] Downloading torch-2.8.0-cp313-cp313-win_amd64.whl (241.3 MB)
⏳ 正在下载大型 PyTorch 文件 (241.3 MB),请耐心等待...
⏳ 大型包下载中... (已等待 1 分钟,请继续等待或按 Ctrl+C 跳过此镜像)
🚀 [75s] ---------------------------------------- 241.3/241.3 MB 2.8 MB/s eta 0:00:00
📥 [76s] Downloading torchaudio-2.8.0-cp313-cp313-win_amd64.whl (2.5 MB)
⏳ 正在下载大型 PyTorch 文件 (2.5 MB),请耐心等待...
🚀 [76s] ---------------------------------------- 2.5/2.5 MB 4.5 MB/s eta 0:00:00
📥 [76s] Downloading torchvision-0.23.0-cp313-cp313-win_amd64.whl (1.6 MB)
⏳ 正在下载大型 PyTorch 文件 (1.6 MB),请耐心等待...
🚀 [77s] ---------------------------------------- 1.6/1.6 MB 4.2 MB/s eta 0:00:00
📦 [78s] Installing collected packages: torch, torchvision, torchaudio
⏳ 大型包下载中... (已等待 2 分钟,请继续等待或按 Ctrl+C 跳过此镜像)
#!/bin/bash # 在原有脚本的core包检查逻辑后添加以下强制跳过代码 # 修改确保核心包安装的函数 ensure_core_package_installed() { local core_package="$1" shift local conflict_packages=("$@") # 强制跳过已安装的核心包 if pip show "$core_package" &> /dev/null; then ok "✅ 强制跳过已安装的核心包: $core_package" return 0 fi # 原有安装逻辑... # ... } # 在调用安装核心包前添加环境检查 if [ -n "$VIRTUAL_ENV" ]; then info "🔍 检测到虚拟环境: $VIRTUAL_ENV" # 强制使用虚拟环境中的pip alias pip="python -m pip" fi # 执行核心包安装时添加额外检查 install_essential_large_packages() { # 原有逻辑... # 在安装前强制检查包是否已安装 for pkg in "${large_packages_needed[@]}"; do if pip show "$pkg" &> /dev/null; then ok "✅ 跳过已安装的包: $pkg" continue fi # 安装逻辑... done }
操作建议
- 手动验证安装状态:
bash
# 在脚本执行前运行pip list | grep -E 'torch|torchvision|torchaudio'
- 强制使用指定版本(在脚本开头添加):
bash
# 添加环境变量定义export TORCH_VERSION="2.8.0+cu128"export TORCHVISION_VERSION="0.23.0"
- 修复脚本执行方式:
bash
# 修改后执行bash setup_train_env.sh --core-packages torch,torchvision,torchaudio --force-skip-installed
关键修改说明
- 增强包检查逻辑:在ensure_core_package_installed函数开头添加对已安装包的强制跳过逻辑,确保即使后续逻辑异常也不会重复安装。
- 环境隔离加固:通过VIRTUAL_ENV检测确保在虚拟环境中使用正确的pip,避免系统环境干扰。
- 版本锁定:通过环境变量明确指定PyTorch相关包的版本和CUDA版本,避免自动检测导致的版本不匹配。
修改后的脚本将优先跳过已安装的核心包,仅在确实需要时才执行安装,同时通过环境变量明确版本要求,避免因版本检测错误导致的重复下载。
20250905 0443
脚本代码
#!/bin/bash set -o pipefail # 使用说明函数(中文),在脚本头部即可调用 usage() { # 将帮助输出到终端(/dev/tty),避免被外部管道如 | tee 捕获到日志 if [ -w /dev/tty ]; then cat > /dev/tty <<'USAGE' 用法: bash setup_train_env.sh [选项] [包...] 选项: -h, --help 显示本帮助并退出 -d, --debug 启用调试模式(set -x),打印执行跟踪 --core-packages PACKAGES 指定核心包列表(逗号分隔,默认: torch,torchvision,torchaudio) 环境变量(可选): ENV_NAME 虚拟环境名(默认: train_ss) WORKSPACE 工作目录(默认: /mnt/workspace 或 Windows 下 D:/workspace) PLATFORM 平台检测覆盖,默认自动检测 TORCH_INDEX 指定 PyTorch wheel 的 index-url 说明: - 脚本内部会尽量把所有时间显示为中国上海时间。 - 若希望同时在控制台显示并写入日志,推荐在运行脚本时使用外部管道: bash setup_train_env.sh 2>&1 | tee -a ./logs/setup_env_$(date +%Y%m%d_%H%M%S).log 或者在 Windows (Git Bash) 下: bash setup_train_env.sh 2>&1 | tee -a D:/logs/setup_env_$(date +%Y%m%d_%H%M%S).log 示例: # 启用调试并指定环境名 bash setup_train_env.sh --debug ENV_NAME=myenv # 指定自定义核心包 bash setup_train_env.sh --core-packages "tensorflow,keras" # 指定多个核心包 bash setup_train_env.sh --core-packages "torch,torchvision,numpy" # 使用外部 tee 将输出同时写日志并显示 bash setup_train_env.sh 2>&1 | tee -a ./logs/setup_env_$(date +%Y%m%d_%H%M%S).log USAGE else cat <<'USAGE' 用法: bash setup_train_env.sh [选项] [包...] 选项: -h, --help 显示本帮助并退出 -d, --debug 启用调试模式(set -x),打印执行跟踪 --core-packages PACKAGES 指定核心包列表(逗号分隔,默认: torch,torchvision,torchaudio) 环境变量(可选): ENV_NAME 虚拟环境名(默认: train_ss) WORKSPACE 工作目录(默认: /mnt/workspace 或 Windows 下 D:/workspace) PLATFORM 平台检测覆盖,默认自动检测 TORCH_INDEX 指定 PyTorch wheel 的 index-url 说明: - 脚本内部会尽量把所有时间显示为中国上海时间。 - 若希望同时在控制台显示并写入日志,推荐在运行脚本时使用外部管道: bash setup_train_env.sh 2>&1 | tee -a ./logs/setup_env_$(date +%Y%m%d_%H%M%S).log 或者在 Windows (Git Bash) 下: bash setup_train_env.sh 2>&1 | tee -a D:/logs/setup_env_$(date +%Y%m%d_%H%M%S).log 示例: # 启用调试并指定环境名 bash setup_train_env.sh --debug ENV_NAME=myenv # 使用外部 tee 将输出同时写日志并显示 bash setup_train_env.sh 2>&1 | tee -a ./logs/setup_env_$(date +%Y%m%d_%H%M%S).log USAGE fi exit 0 } # 支持通过命令行参数开启 DEBUG:--debug 或 -d;同时支持 --help/-h 和 --core-packages CORE_PACKAGES_ARG="" for _arg in "$@"; do case "$_arg" in --debug|-d) export DEBUG=1 ;; --help|-h) usage ;; --core-packages) CORE_PACKAGES_NEXT=true ;; --core-packages=*) CORE_PACKAGES_ARG="${_arg#*=}" ;; *) if [[ "$CORE_PACKAGES_NEXT" == "true" ]]; then CORE_PACKAGES_ARG="$_arg" CORE_PACKAGES_NEXT="" fi ;; esac done # ======================================== # 🔍 CLI参数验证 # ======================================== # 检查 --core-packages 参数是否有值 if [[ "$CORE_PACKAGES_NEXT" == "true" ]]; then echo "❌ 错误: --core-packages 参数缺少参数值" >&2 echo "" >&2 echo "💡 正确用法:" >&2 echo " ./setup_train_env.sh --core-packages torch,torchvision,torchaudio" >&2 echo " ./setup_train_env.sh --core-packages=\"torch,torchvision,torchaudio\"" >&2 echo "" >&2 echo "📋 示例核心包列表:" >&2 echo " • PyTorch生态: torch,torchvision,torchaudio" >&2 echo " • TensorFlow生态: tensorflow,keras" >&2 echo " • 混合方案: torch,tensorflow,numpy" >&2 echo "" >&2 echo "🔍 查看完整帮助: ./setup_train_env.sh --help" >&2 exit 1 fi # 如果指定了核心包参数,验证格式 if [[ -n "$CORE_PACKAGES_ARG" ]]; then # 检查是否为空字符串 if [[ "$CORE_PACKAGES_ARG" == "" ]]; then echo "❌ 错误: --core-packages 参数值不能为空" >&2 echo "💡 请提供有效的包列表,例如: torch,torchvision,torchaudio" >&2 exit 1 fi # 基本格式验证(检查是否包含有效字符) if [[ ! "$CORE_PACKAGES_ARG" =~ ^[a-zA-Z0-9_,-]+$ ]]; then echo "❌ 错误: --core-packages 参数值包含无效字符" >&2 echo "💡 只允许字母、数字、下划线、连字符和逗号" >&2 echo "💡 当前值: '$CORE_PACKAGES_ARG'" >&2 exit 1 fi echo "✅ 核心包参数验证通过: $CORE_PACKAGES_ARG" >&2 fi # ======================================== # � 智能环境检测和命令兼容性处理 # ======================================== # 检测当前shell环境 detect_shell_environment() { local env_result="" local platform="" # 检测平台和环境 if [[ "${OSTYPE:-}" == "msys" ]] || [[ "${MSYSTEM:-}" =~ ^(MINGW|MSYS) ]]; then platform="windows" env_result="GIT_BASH" export DETECTED_PLATFORM="windows" export DETECTED_SHELL="git_bash" export COMMANDS_AVAILABLE=true elif [[ "${OS:-}" == "Windows_NT" ]] || command -v powershell.exe >/dev/null 2>&1; then platform="windows" env_result="WINDOWS_POWERSHELL" export DETECTED_PLATFORM="windows" export DETECTED_SHELL="powershell" export COMMANDS_AVAILABLE=false elif [[ "${OSTYPE:-}" == "linux"* ]]; then platform="linux" # Linux下检查关键命令是否可用 local missing_commands=() for cmd in grep sed cut awk; do if ! command -v "$cmd" >/dev/null 2>&1; then missing_commands+=("$cmd") fi done if [ ${#missing_commands[@]} -eq 0 ]; then env_result="LINUX_BASH" export COMMANDS_AVAILABLE=true else env_result="LINUX_MINIMAL" export COMMANDS_AVAILABLE=false export MISSING_COMMANDS=("${missing_commands[@]}") fi export DETECTED_PLATFORM="linux" export DETECTED_SHELL="bash" elif [[ "${OSTYPE:-}" == "darwin"* ]]; then platform="macos" env_result="MACOS_BASH" export DETECTED_PLATFORM="macos" export DETECTED_SHELL="bash" export COMMANDS_AVAILABLE=true else # 未知环境,通过命令可用性判断 if command -v grep >/dev/null 2>&1 && command -v sed >/dev/null 2>&1 && command -v cut >/dev/null 2>&1; then env_result="UNIX_COMPATIBLE" export COMMANDS_AVAILABLE=true else env_result="LIMITED_SHELL" export COMMANDS_AVAILABLE=false fi export DETECTED_PLATFORM="unknown" export DETECTED_SHELL="unknown" fi echo "$env_result" } # 🔧 Linux系统自动安装缺失命令 install_missing_commands_linux() { local missing_commands=("$@") echo "🔧 尝试自动安装缺失命令: ${missing_commands[*]}" >&2 # 检测包管理器并安装 if command -v apt-get >/dev/null 2>&1; then echo "📦 使用 apt-get 安装..." >&2 sudo apt-get update >/dev/null 2>&1 sudo apt-get install -y gawk sed grep coreutils >/dev/null 2>&1 elif command -v yum >/dev/null 2>&1; then echo "📦 使用 yum 安装..." >&2 sudo yum install -y gawk sed grep coreutils >/dev/null 2>&1 elif command -v dnf >/dev/null 2>&1; then echo "📦 使用 dnf 安装..." >&2 sudo dnf install -y gawk sed grep coreutils >/dev/null 2>&1 elif command -v pacman >/dev/null 2>&1; then echo "📦 使用 pacman 安装..." >&2 sudo pacman -S --noconfirm gawk sed grep coreutils >/dev/null 2>&1 elif command -v apk >/dev/null 2>&1; then echo "📦 使用 apk 安装..." >&2 sudo apk add gawk sed grep coreutils >/dev/null 2>&1 elif command -v zypper >/dev/null 2>&1; then echo "📦 使用 zypper 安装..." >&2 sudo zypper install -y gawk sed grep coreutils >/dev/null 2>&1 else echo "❌ 无法识别包管理器,请手动安装: ${missing_commands[*]}" >&2 echo "" >&2 echo "💡 常见安装命令:" >&2 echo " Ubuntu/Debian: sudo apt-get install gawk sed grep coreutils" >&2 echo " CentOS/RHEL: sudo yum install gawk sed grep coreutils" >&2 echo " Fedora: sudo dnf install gawk sed grep coreutils" >&2 echo " Arch Linux: sudo pacman -S gawk sed grep coreutils" >&2 echo " Alpine: sudo apk add gawk sed grep coreutils" >&2 return 1 fi # 验证安装结果 local still_missing=() for cmd in "${missing_commands[@]}"; do if ! command -v "$cmd" >/dev/null 2>&1; then still_missing+=("$cmd") fi done if [ ${#still_missing[@]} -eq 0 ]; then echo "✅ 所有命令安装成功" >&2 return 0 else echo "⚠️ 部分命令安装失败: ${still_missing[*]}" >&2 return 1 fi } # 智能命令调用:根据环境选择最佳实现 smart_grep() { local pattern="$1" shift local args=("$@") if [ "${COMMANDS_AVAILABLE:-false}" = true ] && command -v grep >/dev/null 2>&1; then # 使用原生grep grep "$pattern" "${args[@]}" else # 使用纯bash实现 pure_bash_grep "$pattern" "${args[@]}" fi } smart_cut() { local delimiter="" local field="" local input="" # 解析参数 while [[ $# -gt 0 ]]; do case "$1" in -d) delimiter="$2"; shift 2 ;; -f) field="$2"; shift 2 ;; *) input="$1"; shift ;; esac done if [ "${COMMANDS_AVAILABLE:-false}" = true ] && command -v cut >/dev/null 2>&1; then # 使用原生cut if [ -n "$input" ]; then echo "$input" | cut -d"$delimiter" -f"$field" else cut -d"$delimiter" -f"$field" fi else # 使用纯bash实现 pure_bash_cut -d "$delimiter" -f "$field" "$input" fi } smart_sed() { local expression="$1" shift if [ "${COMMANDS_AVAILABLE:-false}" = true ] && command -v sed >/dev/null 2>&1; then # 使用原生sed sed "$expression" "$@" else # 使用纯bash实现(简化版) pure_bash_sed "$expression" "$@" fi } # 纯bash实现的文本处理函数(备用) pure_bash_grep() { local pattern="$1" local file="$2" local line if [ -f "$file" ]; then while IFS= read -r line; do case "$line" in *"$pattern"*) echo "$line" ;; esac done < "$file" else # 从stdin读取 while IFS= read -r line; do case "$line" in *"$pattern"*) echo "$line" ;; esac done fi } pure_bash_cut() { local options="$1" local input="$2" local delimiter=" " local field=1 # 解析cut参数 case "$options" in *-d*) delimiter="${options#*-d}" if [ -z "$delimiter" ]; then delimiter=" " fi ;; *-f*) field="${options#*-f}" ;; esac # 处理输入 if [ -n "$input" ]; then echo "$input" | while IFS="$delimiter" read -ra parts; do if [ ${#parts[@]} -ge "$field" ]; then echo "${parts[$((field-1))]}" fi done else while IFS="$delimiter" read -ra parts; do if [ ${#parts[@]} -ge "$field" ]; then echo "${parts[$((field-1))]}" fi done fi } # 环境检测和初始化 SHELL_ENV=$(detect_shell_environment) case "$SHELL_ENV" in "WINDOWS_POWERSHELL") echo "⚠️ 检测到 Windows PowerShell 环境" >&2 echo "💡 建议使用 Git Bash 运行此脚本以获得最佳体验:" >&2 echo " 1. 安装 Git for Windows (如果未安装): https://git-scm.com/download/win" >&2 echo " 2. 右键点击文件夹,选择 'Git Bash Here'" >&2 echo " 3. 运行命令:./setup_train_env.sh" >&2 echo "" >&2 echo "🔧 当前环境的限制:" >&2 echo " • 缺少 grep, sed, cut 等命令" >&2 echo " • 某些功能可能较慢或不可用" >&2 echo "" >&2 read -p "❓ 是否继续在当前环境中运行?(y/N): " continue_powershell if [[ ! "$continue_powershell" =~ ^[Yy]$ ]]; then echo "🔄 请在 Git Bash 中运行脚本以获得最佳体验" >&2 exit 1 fi echo "🔧 启用兼容模式继续运行..." >&2 ;; "LINUX_MINIMAL") echo "⚠️ 检测到精简 Linux 环境,缺少必要命令" >&2 echo "💡 缺失命令: ${MISSING_COMMANDS[*]}" >&2 echo "" >&2 echo "🔧 自动安装选项:" >&2 read -p "❓ 是否尝试自动安装缺失命令?(y/N): " install_missing if [[ "$install_missing" =~ ^[Yy]$ ]]; then if install_missing_commands_linux "${MISSING_COMMANDS[@]}"; then echo "✅ 命令安装成功,重新检测环境..." >&2 SHELL_ENV=$(detect_shell_environment) if [ "$COMMANDS_AVAILABLE" = true ]; then echo "✅ 环境准备完成" >&2 else echo "⚠️ 某些命令可能未正确安装,使用兼容模式" >&2 fi else echo "❌ 自动安装失败,使用兼容模式" >&2 fi else echo "🔧 使用内置兼容模式继续运行..." >&2 fi ;; "LIMITED_SHELL") echo "⚠️ 检测到受限shell环境" >&2 echo "💡 建议在支持标准命令的环境中运行此脚本" >&2 echo "" >&2 read -p "❓ 是否继续在当前环境中运行?(y/N): " continue_limited if [[ ! "$continue_limited" =~ ^[Yy]$ ]]; then echo "🔄 请在支持的环境中运行脚本" >&2 exit 1 fi echo "🔧 使用兼容模式继续运行..." >&2 ;; "GIT_BASH") echo "✅ Git Bash 环境检测成功,使用完整功能" >&2 ;; "LINUX_BASH"|"MACOS_BASH"|"UNIX_COMPATIBLE") echo "✅ Unix 兼容环境检测成功,使用完整功能" >&2 ;; *) echo "ℹ️ 未知shell环境,尝试自动适配" >&2 ;; esac # ======================================== # 🛠️ 全局变量(可从外部传入或默认) # ======================================== # 开启调试跟踪(set -x)仅在显式需要时打开,避免把执行跟踪写入控制台/日志 # 可通过环境变量或命令行参数启用,如: DEBUG=1 bash setup_train_env.sh 或 bash setup_train_env.sh --debug if [[ "${DEBUG:-0}" -eq 1 ]]; then set -x fi # 确保终端环境正常 export TERM="${TERM:-xterm-256color}" export PYTHONIOENCODING=utf-8 # ======================================== # 🕒 脚本开始:记录开始时间 # ======================================== # 使用中国上海时区时间(兼容不同环境) export TZ='Asia/Shanghai' # sh_date: 使用 TZ=Asia/Shanghai 调用 date,保证在不同平台下输出为上海时间 sh_date() { # 优先使用系统时区数据(如果存在) if [ -r "/usr/share/zoneinfo/Asia/Shanghai" ]; then TZ='Asia/Shanghai' date "$@" return fi # 回退:如果系统缺少 zoneinfo,尝试使用 python/python3 将 UTC 时间 +8h 来格式化 local args=("$@") local fmt="" local epoch="" local i for ((i=0;i<${#args[@]};i++)); do case "${args[i]}" in -d) # 支持 -d @EPOCH 或 -d @ EPOCH 两种形式 if [ $((i+1)) -lt ${#args[@]} ]; then val="${args[i+1]}" if [ "${val}" = "@" ] && [ $((i+2)) -lt ${#args[@]} ]; then epoch="@${args[i+2]}" i=$((i+2)) else epoch="${val}" i=$((i+1)) fi fi ;; @*) epoch="${args[i]}" ;; +*) fmt="${args[i]}" ;; *) ;; esac done if [ -z "$fmt" ]; then fmt='+%Y-%m-%d %H:%M:%S' fi local pyfmt="${fmt#+}" # 选择 python 可执行文件 local pycmd=python if ! command -v "$pycmd" >/dev/null 2>&1; then pycmd=python3 fi if ! command -v "$pycmd" >/dev/null 2>&1; then # 无 python 可用,回退到系统 date(使用 sh_date 回退到上海时间或尽力保持一致) sh_date "$@" return fi if [ -n "$epoch" ]; then epoch="${epoch#@}" if [ -z "$epoch" ]; then # epoch 为空,回退到 sh_date(避免 python int('') 错误) sh_date "$@" return fi "$pycmd" - "$pyfmt" "$epoch" <<'PY' import sys,datetime try: fmt = sys.argv[1] epoch = int(sys.argv[2]) # 使用时区感知的 UTC 时间并转换为上海时区(UTC+8) dt = datetime.datetime.fromtimestamp(epoch, datetime.timezone.utc).astimezone(datetime.timezone(datetime.timedelta(hours=8))) print(dt.strftime(fmt)) except Exception: pass PY else "$pycmd" - "$pyfmt" <<'PY' import sys,datetime try: fmt = sys.argv[1] dt = datetime.datetime.now(datetime.timezone.utc).astimezone(datetime.timezone(datetime.timedelta(hours=8))) print(dt.strftime(fmt)) except Exception: pass PY fi } # 记录脚本开始时间(epoch) START_TIME=$(sh_date +%s) # 在函数顶部添加: local extra_index_url="" local trusted_hosts=() # 在参数解析循环中: for arg in "$@"; do if [[ "$arg" == --index-url ]]; then index_url_next=true elif [[ "$arg" == --extra-index-url ]]; then extra_index_url_next=true elif [[ "$arg" == --trusted-host ]]; then trusted_host_next=true elif $index_url_next; then index_url="$arg" index_url_next=false elif $extra_index_url_next; then extra_index_url="$arg" extra_index_url_next=false elif $trusted_host_next; then trusted_hosts+=("$arg") trusted_host_next=false elif [[ "$arg" == --* ]]; then extra_args+=("$arg") else packages+=("$arg") fi done # ======================================== # 🛠️ 全局变量(可从外部传入或默认) # ======================================== ENV_NAME="${ENV_NAME:-train_ss}" # 虚拟环境名称 WORKSPACE="${WORKSPACE:-/mnt/workspace}" # 工作空间根目录(可外部传入) # --- 修改 2: PLATFORM 优先外部传入 --- PLATFORM="${PLATFORM:-auto}" # 平台类型 if [[ "$PLATFORM" == "auto" ]]; then PLATFORM=$(detect_platform) fi # ======================================== # 🛠️ 跨平台虚拟环境检测函数 # ======================================== check_virtual_env_support() { local has_venv=false local has_virtualenv=false local has_conda=false local python_cmd="" echo "🔍 检测虚拟环境支持..." # 1. 检测 Python 命令 if command -v python3 >/dev/null 2>&1; then python_cmd="python3" elif command -v python >/dev/null 2>&1; then python_cmd="python" fi if [ -z "$python_cmd" ]; then echo "❌ 未检测到 Python" return 1 fi echo "✅ 检测到 Python: $python_cmd ($($python_cmd --version 2>&1))" # 2. 检测 venv 模块(Python 3.3+ 内置) if $python_cmd -m venv --help >/dev/null 2>&1; then has_venv=true echo "✅ 支持 venv 模块" else echo "❌ 不支持 venv 模块" fi # 3. 检测 virtualenv(第三方包) if command -v virtualenv >/dev/null 2>&1; then has_virtualenv=true echo "✅ 支持 virtualenv: $(virtualenv --version 2>&1)" elif $python_cmd -m virtualenv --help >/dev/null 2>&1; then has_virtualenv=true echo "✅ 支持 virtualenv (通过 python -m)" else echo "❌ 不支持 virtualenv" fi # 4. 检测 conda if command -v conda >/dev/null 2>&1; then has_conda=true echo "✅ 支持 conda: $(conda --version 2>&1)" else echo "❌ 不支持 conda" fi # 5. 总结 echo "" echo "📋 虚拟环境支持总结:" if $has_conda; then echo " 🐍 Conda: 可用 (推荐)" fi if $has_venv; then echo " 📦 venv: 可用 (Python 内置)" fi if $has_virtualenv; then echo " 🔧 virtualenv: 可用 (第三方)" fi if ! $has_venv && ! $has_virtualenv && ! $has_conda; then echo " ❌ 无可用的虚拟环境工具" echo "" echo "💡 建议安装方法:" if [[ "$OSTYPE" == "linux-gnu"* ]]; then echo " Ubuntu/Debian: sudo apt update && sudo apt install python3-venv" echo " CentOS/RHEL: sudo yum install python3-venv 或 sudo dnf install python3-venv" elif [[ "$OSTYPE" == "msys"* ]] || [[ "$OSTYPE" == "cygwin"* ]]; then echo " Windows: pip install virtualenv 或安装 Anaconda/Miniconda" fi echo " 通用方法: pip install virtualenv" return 1 fi return 0 } # ======================================== # 🛠️ 封装函数:get_activate_cmd 获取环境激活cmd命令 # ======================================== get_activate_cmd() { local ENV_NAME="${1:-train_ss}" local VENV_PATH="${2:-/mnt/workspace/envs/${ENV_NAME}}" local ACTIVATE_CMD="" local ENV_TYPE="" local OS_TYPE="linux" # 1. 判断操作系统 if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then OS_TYPE="windows" elif [[ "$OSTYPE" == "linux-gnu"* ]]; then OS_TYPE="linux" else OS_TYPE="linux" fi echo "🔍 检测到操作系统: $OS_TYPE" # 2. 判断环境类型 if command -v conda &> /dev/null; then if conda env list 2>/dev/null | smart_grep -q "^\s*${ENV_NAME}\s*" ; then ENV_TYPE="conda" echo "📦 检测到 Conda 环境: ${ENV_NAME}" fi fi if [[ -z "$ENV_TYPE" ]] && [ -f "${VENV_PATH}/bin/activate" ]; then ENV_TYPE="venv" echo "📦 检测到 venv 环境: ${VENV_PATH}" fi if [[ -z "$ENV_TYPE" ]]; then echo "❌ 错误:未找到环境 '${ENV_NAME}',既不是 conda 也不是 venv。" export ACTIVATE_CMD="NOT_FOUND" export DETECTED_ENV_TYPE="unknown" return 1 fi # 3. 生成激活命令 case "${ENV_TYPE}_${OS_TYPE}" in "conda_linux"|"conda_windows") if conda --version &> /dev/null; then local CONDA_VERSION=$(conda --version | smart_extract_field 2) if version_compare "4.4" "$CONDA_VERSION"; then ACTIVATE_CMD="conda activate ${ENV_NAME}" else ACTIVATE_CMD="source activate ${ENV_NAME}" fi else ACTIVATE_CMD="source activate ${ENV_NAME}" fi ;; "venv_linux") ACTIVATE_CMD="source ${VENV_PATH}/bin/activate" ;; "venv_windows") ACTIVATE_CMD="${VENV_PATH}\\Scripts\\activate.bat" ;; *) ACTIVATE_CMD="UNKNOWN" ;; esac # 导出变量供外部使用 export ACTIVATE_CMD export DETECTED_ENV_TYPE="$ENV_TYPE" echo "✅ ACTIVATE_CMD: ${ACTIVATE_CMD}" } # ======================================== # 🛠️ 定义 log_success 函数 # ======================================== log_success() { local PLATFORM="$1" local ENV_NAME="$2" local LOG_FILE_PATH="$3" local WORKSPACE="$4" local ACTIVATE_CMD="$5" local DETECTED_ENV_TYPE="$6" # 获取 GPU 信息 local GPU_INFO GPU_INFO=$(python -c ' import torch if torch.cuda.is_available(): print(f"✅ {torch.cuda.get_device_name(0)} (CUDA {torch.version.cuda})") else: print("❌ 未启用") ' 2>/dev/null) || GPU_INFO="❌ 导入 PyTorch 失败" local CURRENT_TIME=$(sh_date '+%Y-%m-%d %H:%M:%S %Z') local END_TIME=$(sh_date +%s) local ELAPSED_TIME=$((END_TIME - START_TIME)) local DURATION="$((ELAPSED_TIME / 60))m$((ELAPSED_TIME % 60))s" # 💡 1. 输出到终端(彩色,仅 stderr) { echo echo -e "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨" echo -e "🎉 \033[1;33m环境配置成功!All Set!\033[0m 🎉" echo echo -e "📍 平台: \033[1;36m$PLATFORM\033[0m" echo -e "📍 环境: \033[1;32m$ENV_NAME\033[0m" echo -e "📍 环境类型: \033[1;35m${DETECTED_ENV_TYPE^}\033[0m" # 首字母大写 echo -e "📍 日志: \033[3;37m$LOG_FILE\033[0m" echo echo -e "💡 激活命令: \033[1;33m$ACTIVATE_CMD\033[0m" # ✅ 使用动态命令 echo -e "💡 GPU 支持: \033[1;32m$GPU_INFO\033[0m" echo echo -e "✅ 当前环境已就绪,可直接运行训练脚本。" echo -e "🕒 完成时间: $CURRENT_TIME" echo -e "⏱️ 执行耗时: $DURATION (开始: $(sh_date -d "@${START_TIME}" '+%H:%M:%S'))" echo -e "" echo -e "支持的 CLI 参数(可直接用于命令行)" echo -e "--help, -h 说明:显示中文帮助(直接输出到终端,不写入日志),随后脚本退出。" echo -e "用法示例:bash setup_train_env.sh --help 或./setup_train_env.sh --help" echo -e "--debug, -d 说明:启用调试模式(等同于设置 DEBUG=1),脚本会执行 set -x 输出命令跟踪(用于排查)。" echo -e "用法示例:bash setup_train_env.sh --debug 或./setup_train_env.sh --debug " echo -e "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨" echo } >&2 # 📄 2. 写入日志(纯文本,结构化) { echo "----------------------------------------" echo "✅ ENV_SETUP_SUCCESS" echo "PLATFORM: $PLATFORM" echo "ENV_NAME: $ENV_NAME" echo "ENV_TYPE: $DETECTED_ENV_TYPE" echo "LOG_FILE: $LOG_FILE" echo "ACTIVATE_CMD: $ACTIVATE_CMD" echo "GPU_INFO: $GPU_INFO" echo "START_TIME: $(sh_date -d "@${START_TIME}" '+%Y-%m-%d %H:%M:%S')" echo "COMPLETION_TIME: $CURRENT_TIME" echo "ELAPSED_SECONDS: $ELAPSED_TIME" echo "EXECUTION_DURATION: $DURATION" echo "STATUS: OK" echo "----------------------------------------" } >> "$LOG_FILE" 2>&1 } # 函数:记录带时间戳的日志 log() { echo "[$(sh_date '+%Y-%m-%d %H:%M:%S %Z')] $1" | tee -a "$LOG_FILE" } # 错误处理函数 on_error() { local exit_code=$? cat >&2 << EOF 💥 脚本执行失败! 行号: $1 命令: $2 退出码: $exit_code 日志: ${LOG_FILE:-未定义} 🔧 建议: - 检查 conda 是否可用: conda --version - 检查 D:/logs 是否可写 - 手动运行失败命令 EOF exit "$exit_code" } trap 'on_error $LINENO "$BASH_COMMAND"' ERR # 工具函数 info() { echo "ℹ️ $1"; } warn() { echo "⚠️ $1"; } # 增强的错误函数:自动输出详细错误信息并保存到日志 error() { local error_msg="$*" local error_time=$(sh_date '+%Y-%m-%d %H:%M:%S %Z') local error_details="" # 构建详细错误信息 error_details=" ==================================== ❌ 错误发生时间: $error_time ❌ 错误信息: $error_msg ❌ 错误位置: ${BASH_SOURCE[1]:-未知}:${BASH_LINENO[0]:-未知} ❌ 当前工作目录: $(pwd) ❌ 环境信息: - PLATFORM: ${PLATFORM:-未设置} - ENV_NAME: ${ENV_NAME:-未设置} - WORKSPACE: ${WORKSPACE:-未设置} - Python可用性: $(command -v python >/dev/null 2>&1 && echo "python: $(python --version 2>&1)" || echo "python: 不可用")$(command -v python3 >/dev/null 2>&1 && echo " | python3: $(python3 --version 2>&1)" || echo " | python3: 不可用") - Conda可用性: $(command -v conda >/dev/null 2>&1 && echo "conda: $(conda --version 2>&1)" || echo "conda: 不可用") - 当前激活环境: ${CONDA_DEFAULT_ENV:-无} ==================================== " # 输出到控制台 echo "$error_details" >&2 # 保存到日志文件(如果已设置) if [[ -n "${LOG_FILE:-}" && -w "$(dirname "$LOG_FILE")" ]]; then echo "$error_details" >> "$LOG_FILE" 2>/dev/null || true fi exit 1 } # 增强的错误追踪函数:用于关键操作失败时的详细诊断 trace_error() { local operation="$1" local cmd_output="$2" local exit_code="${3:-$?}" local error_time=$(sh_date '+%Y-%m-%d %H:%M:%S %Z') local trace_details=" 🔍 ========== 错误追踪报告 ========== ⏰ 时间: $error_time 🎯 操作: $operation 💥 退出码: $exit_code 📍 位置: ${BASH_SOURCE[1]:-未知}:${BASH_LINENO[0]:-未知} 📂 工作目录: $(pwd) 📝 命令输出: $cmd_output 🔧 环境诊断: - PLATFORM: ${PLATFORM:-未设置} - ENV_NAME: ${ENV_NAME:-未设置} - WORKSPACE: ${WORKSPACE:-未设置} - LOG_FILE: ${LOG_FILE:-未设置} 🐍 Python 环境: - which python: $(which python 2>/dev/null || echo "未找到") - which python3: $(which python3 2>/dev/null || echo "未找到") - PYTHONPATH: ${PYTHONPATH:-未设置} 🅰️ Anaconda/Miniconda: - which conda: $(which conda 2>/dev/null || echo "未找到") - CONDA_DEFAULT_ENV: ${CONDA_DEFAULT_ENV:-未设置} - CONDA_PREFIX: ${CONDA_PREFIX:-未设置} 💾 磁盘空间: $(df -h . 2>/dev/null | tail -1 || echo "无法获取磁盘信息") 🌐 网络连接测试: $(ping -c 1 8.8.8.8 >/dev/null 2>&1 && echo "✅ 网络连接正常" || echo "❌ 网络连接异常") ================================================== " # 输出到控制台 echo "$trace_details" >&2 # 保存到日志文件 if [[ -n "${LOG_FILE:-}" ]]; then echo "$trace_details" >> "$LOG_FILE" 2>/dev/null || true fi } ok() { echo "✅ $1"; } # 平台检测 + 变量初始化 # === 在变量定义后,立即设置日志并重定向 === # 先通过平台检测确定 LOG_DIR detect_platform() { case "$OSTYPE" in msys*|mingw*|cygwin*) echo "local-windows" return ;; esac [[ -d "/home/aistudio" ]] && echo "aistudio" && return [[ -n "${MODELSCOPE_CACHE:-}" ]] && echo "modelscope" && return [[ -n "${SSH_CLIENT:-}" ]] && echo "server" && return [[ -d "/mnt/workspace" ]] && echo "server" && return echo "local-linux" } setup_paths() { case "$PLATFORM" in "local-windows") DEFAULT_DRIVE="D:/" if [[ -w "$DEFAULT_DRIVE" ]]; then WORKSPACE="$DEFAULT_DRIVE/workspace" LOG_DIR="$DEFAULT_DRIVE/logs" else WORKSPACE="./workspace" LOG_DIR="./logs" fi ;; "aistudio") WORKSPACE="/home/aistudio" LOG_DIR="/home/aistudio/logs" ;; "modelscope") WORKSPACE="/mnt/workspace" LOG_DIR="/mnt/workspace/logs" ;; "server"|"local-linux") WORKSPACE="./workspace" LOG_DIR="./logs" ;; *) warn "未知平台: $PLATFORM,使用默认配置" WORKSPACE="./workspace" LOG_DIR="./logs" ;; esac } # 🌐 检测平台 PLATFORM=$(detect_platform) info "🌍 检测到平台: $PLATFORM" # 🛠 设置路径 setup_paths # ✅ 创建目录 mkdir -p "$WORKSPACE" || { error "无法创建 WORKSPACE: $WORKSPACE" error "💡 请检查该路径是否存在且有写权限" } mkdir -p "$LOG_DIR" || { error "无法创建日志目录: $LOG_DIR" error "💡 请检查该路径是否存在且有写权限" } # ✅ 定义日志文件(使用上海时区时间) LOG_FILE="$LOG_DIR/setup_env_$(sh_date +%Y%m%d_%H%M%S).log" # 规范化路径工具:将连续的多个 '/' 合并为一个(保留 UNC 前缀 '//') normalize_path() { local p="$1" # 针对 Windows 下(MSYS/Cygwin)路径做专门处理 if [[ "${PLATFORM:-}" == "local-windows" || "$p" =~ ^[A-Za-z]: ]]; then # 将反斜杠转换为正斜杠 local out="${p//\\//}" # 把连续多个 '/' 合并为一个 while [[ "$out" == *//* ]]; do out="${out//\/\//\/}"; done # 确保驱动器后为单个斜杠,例如 D:/logs 而非 D:logs 或 D://logs if [[ "$out" =~ ^([A-Za-z]):/*(.*) ]]; then local drive="${BASH_REMATCH[1]}" local rest="${BASH_REMATCH[2]}" printf '%s' "${drive}:/${rest}" return fi printf '%s' "$out" return fi # 默认行为:合并重复斜杠(保留 UNC 前缀 '//') if [[ "$p" == '//*' ]]; then local lead='//' local rest="${p#//}" while [[ "$rest" == *//* ]]; do rest="${rest//\/\//\/}"; done printf '%s' "$lead$rest" else local out="$p" while [[ "$out" == *//* ]]; do out="${out//\/\//\/}"; done printf '%s' "$out" fi } # 🎯 智能包检查:效率优先,准确保障 # # � 验证策略分析: # - pip show 未找到 → 99.9% 准确(直接返回未安装) # - pip show 找到 + 关键包 → 需要 import 验证(torch, numpy等) # - pip show 找到 + 大型包 → 检查 Location 存在性(轻量级) # - pip show 找到 + 普通包 → 直接信任(高效) # # 🚀 性能优化: # - 90% 包只用 pip show(1-2ms) # - 5% 包加 Location 检查(+1ms) # - 5% 关键包用 import 验证(+50ms) check_package_installed() { local package="$1" local package_name="" # 提取包名(去除版本号和其他修饰符) if [[ "$package" =~ ^([a-zA-Z0-9_-]+) ]]; then package_name="${BASH_REMATCH[1]}" else package_name="$package" fi # 特殊处理一些包名映射 case "$package_name" in "torch") package_name="torch" # 对于torch,需要特别检查是否为GPU版本 if pip show "$package_name" >/dev/null 2>&1; then # 检查是否为GPU版本 local torch_gpu_check="" torch_gpu_check=$(python -c " try: import torch version = torch.__version__ is_gpu = '+cu' in version or torch.cuda.is_available() # 检查CUDA版本兼容性 if is_gpu and '+cu' in version: # 提取CUDA版本号 (如 +cu118, +cu128) cuda_parts = version.split('+cu') if len(cuda_parts) > 1: cuda_version = cuda_parts[1] print('INSTALLED_CUDA_' + cuda_version) else: print('GPU_VERSION_OK') elif is_gpu: print('GPU_VERSION_OK') else: print('CPU_VERSION_ONLY') except: print('IMPORT_FAILED') " 2>/dev/null) # 检查是否需要CUDA版本升级 if echo "$torch_gpu_check" | smart_grep -q "GPU_VERSION_OK"; then return 0 # GPU版本已安装且CUDA可用 elif echo "$torch_gpu_check" | smart_grep -q "INSTALLED_CUDA_"; then # 检查CUDA版本是否匹配当前环境需求 local installed_cuda=$(echo "$torch_gpu_check" | smart_sed 's/INSTALLED_CUDA_//') local required_cuda="" # 从TORCH_INDEX提取需求的CUDA版本 if echo "$TORCH_INDEX" | smart_grep -q "cu118"; then required_cuda="118" elif echo "$TORCH_INDEX" | smart_grep -q "cu128"; then required_cuda="128" elif echo "$TORCH_INDEX" | smart_grep -q "cu117"; then required_cuda="117" fi if [ "$installed_cuda" = "$required_cuda" ]; then return 0 # CUDA版本匹配,无需重装 else info "🔄 检测到CUDA版本不匹配: 已安装cu$installed_cuda, 需要cu$required_cuda" return 1 # 需要升级CUDA版本 fi elif echo "$torch_gpu_check" | smart_grep -q "CPU_VERSION_ONLY"; then # CPU版本已安装,但我们需要GPU版本 return 1 # 需要重新安装GPU版本 else return 1 # 安装有问题,需要重新安装 fi else return 1 # 未安装 fi ;; "torchvision") package_name="torchvision" ;; "torchaudio") package_name="torchaudio" ;; "opencv-python"|"opencv-contrib-python") package_name="cv2" ;; "pillow") package_name="PIL" ;; "scikit-learn") package_name="sklearn" ;; "beautifulsoup4") package_name="bs4" ;; "pyyaml") package_name="yaml" ;; esac # 🚀 高效的包检查策略:平衡效率和准确性 # Step 1: 快速检查 - pip show (高效,覆盖99%的情况) local pip_show_result="" pip_show_result=$(pip show "$package_name" 2>/dev/null) local pip_show_exit_code=$? # 如果 pip show 显示未安装,直接返回未安装(这个结果是可靠的) if [ $pip_show_exit_code -ne 0 ]; then return 1 # 未安装,pip show未找到包 fi # Step 2: pip show 显示已安装,但需要额外验证的情况 local need_import_check=false # 检查是否需要额外验证(仅在这些情况下) case "$package_name" in # 关键包:安装错误代价高,需要双重验证 torch|tensorflow|numpy|scipy) need_import_check=true ;; # 大型包:安装时间长,需要确保真正可用 torchvision|torchaudio|opencv*|pillow) # 检查包的Location是否存在(轻量级验证) local package_location="" package_location=$(echo "$pip_show_result" | smart_grep "Location:" | smart_cut -d' ' -f2) if [ -n "$package_location" ] && [ -d "$package_location" ]; then need_import_check=false # 位置存在,trust pip show else need_import_check=true # 位置不存在,需要import验证 fi ;; # 普通包:pip show结果可信,不需要额外验证 *) need_import_check=false ;; esac # Step 3: 仅在必要时进行 import 验证(保持高效) if [ "$need_import_check" = true ]; then # 对关键包进行实际导入测试 if python -c "import $package_name" >/dev/null 2>&1; then return 0 # 双重验证通过,确实已安装 else return 1 # pip show说已安装,但实际导入失败(包损坏) fi else # pip show 结果可信,直接信任 return 0 # 已安装 fi } # 🚀 智能包安装函数:过滤已安装的包 filter_installed_packages() { local packages=("$@") local uninstalled_packages=() local installed_packages=() # 将信息输出重定向到stderr,避免污染返回值 echo "🔍 正在检查包安装状态..." >&2 for package in "${packages[@]}"; do if check_package_installed "$package"; then installed_packages+=("$package") echo "ℹ️ ✅ $package - 已安装,跳过" >&2 else uninstalled_packages+=("$package") echo "ℹ️ 📦 $package - 需要安装" >&2 fi done # 显示统计信息 - 重定向到stderr if [ ${#installed_packages[@]} -gt 0 ]; then echo "ℹ️ 📊 跳过已安装的包 (${#installed_packages[@]}个): ${installed_packages[*]}" >&2 fi if [ ${#uninstalled_packages[@]} -gt 0 ]; then echo "ℹ️ ⬇️ 需要安装的包 (${#uninstalled_packages[@]}个): ${uninstalled_packages[*]}" >&2 else echo "ℹ️ 🎉 所有包都已安装,无需下载!" >&2 fi # 返回未安装的包列表(仅包名,无额外输出) for pkg in "${uninstalled_packages[@]}"; do echo "$pkg" done } # 规范化 LOG_DIR 和 LOG_FILE,避免出现 D://logs 这样的路径 LOG_DIR=$(normalize_path "$LOG_DIR") LOG_FILE=$(normalize_path "$LOG_FILE") # ✅ 开始重定向(从现在开始,所有输出都进日志) # 优先使用 tee 将输出同时写到日志和控制台(需要 bash 的 process substitution) if command -v tee >/dev/null 2>&1; then # 将 stdout/stderr 都通过 tee 追加到日志文件,并继续在控制台显示 if ! exec > >(tee -a "$LOG_FILE") 2> >(tee -a "$LOG_FILE" >&2); then echo "❌ 无法通过 tee 重定向到日志文件: $LOG_FILE" >&2 exit 1 fi else # 如果系统没有 tee,提醒用户并保持在控制台输出(推荐:安装 tee 或使用外部管道运行脚本) cat >&2 <<'EOF' ⚠️ 未检测到系统命令 "tee"。 为保证同时在控制台显示并写入日志,建议执行以下任一操作: 1) 在系统上安装 tee(Ubuntu/Debian: sudo apt update && sudo apt install coreutils -y)。 2) 或在运行脚本时使用外部管道: bash setup_train_env.sh 2>&1 | tee -a "${LOG_FILE}" 脚本将继续在控制台输出(不再自动把所有输出重定向到日志文件), 如果你选择使用外部管道运行,日志会被完整保存到上面的文件。 EOF # 不执行 exec 重定向,保留控制台输出,按用户要求不自动改为 FIFO 回退 fi # 🌟 此后所有输出都记录在日志中 echo "----------------------------------------" info "✅ 日志重定向已生效" info "✅ 日志文件已创建: $LOG_FILE" info "🔧 开始执行后续安装步骤..." echo "----------------------------------------" log "安装脚本开始执行" # 🔍 检测虚拟环境支持情况 echo "----------------------------------------" check_virtual_env_support if [ $? -ne 0 ]; then warn "⚠️ 虚拟环境支持不完整,脚本可能无法正常创建环境" warn "💡 建议先安装虚拟环境工具再继续" fi echo "----------------------------------------" PYTHON_VERSIONS=("3.10.13" "3.9" "3.8" "3.7") # ----------------------------- # 🔍 检查 Conda 是否可用,否则使用 venv # ----------------------------- create_or_use_env() { local env_name=$1 local py_versions=("${@:2}") # 定义统一路径(用于 venv 和 Linux 上的 Conda) ENV_ROOT="$WORKSPACE/envs" ENV_PATH="$ENV_ROOT/$env_name" # ----------------------------- # 1. 检查 Conda 是否可用 # ----------------------------- if command -v conda &> /dev/null; then # 判断是否是 Linux 系统 if [[ "$OSTYPE" == "linux-gnu"* ]]; then info "✅ Linux + Conda:使用统一路径 $ENV_PATH" # 检查指定路径的环境是否存在且完整 if [ -d "$ENV_PATH" ] && [ -f "$ENV_PATH/bin/python" ]; then ok "Conda 环境已存在: $ENV_PATH" else # 清理残缺环境 if [ -d "$ENV_PATH" ]; then warn "⚠️ 环境目录不完整,正在清理: $ENV_PATH" rm -rf "$ENV_PATH" fi info "🆕 创建 Conda 环境: $ENV_PATH" local creation_output="" local creation_failed=false for py_ver in "${py_versions[@]}"; do creation_output=$(conda create -p "$ENV_PATH" python="$py_ver" --yes -v 2>&1) if [ $? -eq 0 ]; then ok "✅ Conda 环境创建成功" creation_failed=false break else creation_failed=true fi done if [ "$creation_failed" = true ] || [ ! -f "$ENV_PATH/bin/python" ]; then trace_error "Conda 环境创建失败" "$creation_output" error "Conda 环境创建失败: $ENV_PATH" exit 1 fi fi # 激活 Conda 环境(使用完整路径) local activation_output="" eval "$(conda shell.bash hook)" 2>/dev/null || true activation_output=$(conda activate "$ENV_PATH" 2>&1) local activation_exit_code=$? if [ $activation_exit_code -ne 0 ]; then trace_error "Conda 环境激活失败" "$activation_output" "$activation_exit_code" error "Conda 环境激活失败: $ENV_PATH" fi return $activation_exit_code else # ✅ 非 Linux(如 Windows),使用 Conda 默认路径 info "✅ Conda 可用(非 Linux),使用默认路径: $env_name" if conda env list | smart_grep -E "^$env_name[[:space:]]" > /dev/null; then ok "Conda 环境已存在: $env_name" else info "🆕 创建 Conda 环境: $env_name" local conda_creation_output="" conda_creation_output=$(conda create -n "$env_name" python="${py_versions[0]}" --yes 2>&1) local conda_creation_exit_code=$? if [ $conda_creation_exit_code -ne 0 ]; then trace_error "Conda 环境创建失败 (非Linux)" "$conda_creation_output" "$conda_creation_exit_code" error "Conda 环境创建失败: $env_name" fi fi local conda_activation_output="" eval "$(conda shell.bash hook)" 2>/dev/null || true conda_activation_output=$(conda activate "$env_name" 2>&1) local conda_activation_exit_code=$? if [ $conda_activation_exit_code -ne 0 ]; then trace_error "Conda 环境激活失败 (非Linux)" "$conda_activation_output" "$conda_activation_exit_code" error "Conda 环境激活失败: $env_name" fi return $conda_activation_exit_code fi # ----------------------------- # 2. Conda 不可用,使用 venv(Linux 和 Windows 都用统一路径) # ----------------------------- else info "⚠️ Conda 未安装,使用 venv: $ENV_PATH" if [ -d "$ENV_PATH" ]; then if [ -f "$ENV_PATH/bin/activate" ] && [ -f "$ENV_PATH/bin/python" ]; then ok "✅ venv 环境已存在且完整" else warn "⚠️ venv 不完整,正在清理" rm -rf "$ENV_PATH" fi fi if [ ! -d "$ENV_PATH" ]; then mkdir -p "$ENV_ROOT" local success=false local venv_creation_output="" for py_ver in "${py_versions[@]}"; do if command -v "python$py_ver" &> /dev/null; then venv_creation_output=$(python"$py_ver" -m venv "$ENV_PATH" 2>&1) if [ $? -eq 0 ]; then success=true break fi elif command -v "python${py_ver%.*}" &> /dev/null; then venv_creation_output=$(python"${py_ver%.*}" -m venv "$ENV_PATH" 2>&1) if [ $? -eq 0 ]; then success=true break fi fi done if [ "$success" = false ]; then trace_error "标准 venv 创建失败" "$venv_creation_output" info "⚠️ 标准 venv 创建失败,使用 --without-pip + 手动安装 pip" local without_pip_output="" without_pip_output=$(python3 -m venv "$ENV_PATH" --without-pip 2>&1) local without_pip_exit_code=$? if [ $without_pip_exit_code -ne 0 ]; then trace_error "venv --without-pip 创建失败" "$without_pip_output" "$without_pip_exit_code" error "❌ 虚拟环境创建完全失败:$ENV_PATH" fi if [ -f "$ENV_PATH/bin/python" ]; then info "📥 正在手动安装 pip..." # ✅ 使用清华镜像,避免网络问题 GET_PIP_URL="https://pypi.tuna.tsinghua.edu.cn/bootstrap/pip/get-pip.py" GET_PIP_PY="/tmp/get-pip-$$.py" # ✅ 添加超时,防止卡死 local curl_output="" curl_output=$(curl -fsSL --connect-timeout 10 --max-time 30 "$GET_PIP_URL" -o "$GET_PIP_PY" 2>&1) local curl_exit_code=$? if [ $curl_exit_code -eq 0 ]; then local pip_install_output="" pip_install_output=$("$ENV_PATH/bin/python" "$GET_PIP_PY" --quiet --no-warn-script-location 2>&1) local pip_exit=$? rm -f "$GET_PIP_PY" if [ $pip_exit -eq 0 ]; then ok "✅ pip 安装成功" else trace_error "pip 手动安装失败" "$pip_install_output" "$pip_exit" error "❌ pip 安装失败" exit 1 fi else trace_error "下载 get-pip.py 失败" "$curl_output" "$curl_exit_code" error "❌ 下载 get-pip.py 失败(URL: $GET_PIP_URL)" error "💡 建议检查网络或更换镜像源" exit 1 fi else trace_error "虚拟环境创建失败" "无法创建 $ENV_PATH/bin/python" error "❌ 虚拟环境创建失败:$ENV_PATH/bin/python 不存在" exit 1 fi fi fi # 激活 venv if [ -f "$ENV_PATH/bin/activate" ]; then local venv_activation_output="" venv_activation_output=$(source "$ENV_PATH/bin/activate" 2>&1) local venv_activation_exit_code=$? if [ $venv_activation_exit_code -eq 0 ]; then ok "✅ venv 环境激活成功: $ENV_PATH" else trace_error "venv 环境激活失败" "$venv_activation_output" "$venv_activation_exit_code" error "❌ venv 激活失败: $ENV_PATH/bin/activate 存在但激活失败" exit 1 fi else trace_error "venv 激活文件缺失" "activate 脚本不存在: $ENV_PATH/bin/activate" error "❌ venv 激活失败: $ENV_PATH/bin/activate 不存在" exit 1 fi fi } # ----------------------------- # 多源容错安装函数 # ----------------------------- # robust_pip_install()调用举例 # # 安装普通包 # robust_pip_install "requests" "flask" # # 安装 PyTorch(带 extra-index-url) # robust_pip_install "torch" "torchvision" \ # --index-url "https://download.pytorch.org/whl/cu118" \ # --extra-index-url "https://pypi.org/simple" \ # --trusted-host "pypi.org" # # 安装私有包 # robust_pip_install "mypackage" \ # --index-url "https://private-pypi.example.com/simple" \ # --trusted-host "private-pypi.example.com" robust_pip_install() { local packages=() local extra_args=() local index_url="" local extra_index_url="" local trusted_hosts=() local index_url_next=false local extra_index_url_next=false local trusted_host_next=false # 解析参数 for arg in "$@"; do case "$arg" in --index-url) index_url_next=true ;; --extra-index-url) extra_index_url_next=true ;; --trusted-host) trusted_host_next=true ;; --*) if $index_url_next; then index_url="$arg" index_url_next=false elif $extra_index_url_next; then extra_index_url="$arg" extra_index_url_next=false elif $trusted_host_next; then trusted_hosts+=("$arg") trusted_host_next=false else extra_args+=("$arg") fi ;; *) if $index_url_next; then index_url="$arg" index_url_next=false elif $extra_index_url_next; then extra_index_url="$arg" extra_index_url_next=false elif $trusted_host_next; then trusted_hosts+=("$arg") trusted_host_next=false else packages+=("$arg") fi ;; esac done # 如果没传包,直接返回 if [ ${#packages[@]} -eq 0 ]; then warn "⚠️ robust_pip_install: 未指定包" return 1 fi # 🚀 智能过滤:检查已安装的包,避免重复下载 local filtered_packages=() # 使用子shell确保输出分离,并安静地获取过滤结果 { info "🔍 正在检查包安装状态以避免重复下载..." readarray -t filtered_packages < <(filter_installed_packages "${packages[@]}") } # 如果所有包都已安装,直接返回成功 if [ ${#filtered_packages[@]} -eq 0 ]; then ok "🎉 所有包都已安装,跳过下载!" return 0 fi # 更新包列表为需要安装的包 packages=("${filtered_packages[@]}") info "📦 将安装 ${#packages[@]} 个包: ${packages[*]}" local max_retries=3 local total_attempts=0 local max_total_attempts=6 local mirrors=( "https://pypi.tuna.tsinghua.edu.cn/simple/|pypi.tuna.tsinghua.edu.cn" "https://mirrors.aliyun.com/pypi/simple/|mirrors.aliyun.com" "https://pypi.douban.com/simple/|pypi.douban.com" "https://download.pytorch.org/whl/cu118|download.pytorch.org" "https://pypi.org/simple|pypi.org" ) info "🚀 开始安装包: ${packages[*]}" # 快速检测网络连通性 info "🌐 检测网络连接..." local network_ok=false for test_host in "pypi.tuna.tsinghua.edu.cn" "mirrors.aliyun.com" "pypi.org"; do if ping -c 1 -W 3 "$test_host" >/dev/null 2>&1; then network_ok=true info "✅ 网络连接正常 (测试主机: $test_host)" break fi done if [ "$network_ok" = false ]; then warn "⚠️ 网络连接检测失败,但仍尝试安装" fi local failed_packages=() local current_packages=("${packages[@]}") local mirror_index=0 while [ ${#current_packages[@]} -gt 0 ] && [ $total_attempts -lt $max_total_attempts ]; do ((total_attempts++)) # 在每次尝试前再次过滤已安装的包(防止长时间下载已存在的 wheel) # 允许 package 规格(如 pkg==1.2.3 或 pkg[extra])被规整为基础包名再检查 local to_install=() local skipped=() for pkg in "${current_packages[@]}"; do # 规范化包名:去掉版本约束和 extras local pkg_base="$pkg" pkg_base=$(echo "$pkg_base" | smart_sed -E 's/([<>=!].*)$//') pkg_base=$(echo "$pkg_base" | smart_sed -E 's/\[.*\]//') if check_package_installed "$pkg_base"; then skipped+=("$pkg") else to_install+=("$pkg") fi done if [ ${#skipped[@]} -gt 0 ]; then info "ℹ️ 跳过已安装的包: ${skipped[*]}" fi # 如果没有需要安装的包,直接退出成功 if [ ${#to_install[@]} -eq 0 ]; then ok "🎉 剩余包均已安装,跳过下载和安装。" return 0 fi # 将当前安装列表替换为需要安装的包 current_packages=("${to_install[@]}") if [ $mirror_index -ge ${#mirrors[@]} ]; then local final_failure_details=" 所有镜像源都已尝试完毕,仍有以下包无法安装: 失败包列表: ${current_packages[*]} 总尝试次数: $total_attempts 已尝试的镜像源:" for mirror in "${mirrors[@]}"; do IFS='|' read -r mirror_url mirror_host <<< "$mirror" final_failure_details="$final_failure_details - $mirror_host ($mirror_url)" done trace_error "包安装完全失败" "$final_failure_details" error "❌ 所有源尝试失败,仍无法安装: ${current_packages[*]}" return 1 fi IFS='|' read -r mirror_url mirror_host <<< "${mirrors[$mirror_index]}" info "📦 使用镜像: $mirror_host (第 $total_attempts 次)" # 根据包的大小调整时间预估 local estimated_time="1-3 分钟" for pkg in "${current_packages[@]}"; do case "$pkg" in torch|torchvision|torchaudio) estimated_time="10-15 分钟" break ;; tensorflow*|numpy|scipy|pandas) estimated_time="3-8 分钟" break ;; esac done # 显示即将执行的包安装 info "⏳ 正在安装: ${current_packages[*]} (预计需要 ${estimated_time})" # 根据包大小设置超时时间 local timeout_seconds=120 # 检查是否包含大型包(PyTorch相关) for pkg in "${current_packages[@]}"; do case "$pkg" in torch|torchvision|torchaudio|tensorflow|tensorflow-gpu) timeout_seconds=1800 # 30分钟,足够下载大型包 info "🔄 检测到大型包 $pkg,延长超时时间至 30 分钟" break ;; esac done # 构造完整的 pip install 命令(使用数组避免引号问题) local pip_cmd=("pip" "install" "--no-cache-dir" "--timeout" "$timeout_seconds") # 添加进度显示参数 pip_cmd+=( "--progress-bar" "on" ) # 启用进度条 (on/off/raw,pretty无效) pip_cmd+=( "--verbose" ) # 显示详细信息包括下载速度 # 添加其他参数 pip_cmd+=( "${extra_args[@]}" ) # 添加 index-url pip_cmd+=( "--index-url" "$mirror_url" ) pip_cmd+=( "--trusted-host" "$mirror_host" ) # 添加额外的 index-url(如果存在) if [ -n "$extra_index_url" ]; then pip_cmd+=( "--extra-index-url" "$extra_index_url" ) fi # 添加所有 trusted-host(来自参数) for host in "${trusted_hosts[@]}"; do pip_cmd+=( "--trusted-host" "$host" ) done # 构建安装包列表时,避免把 torchvision/torchaudio 在 torch 已存在时与其它包一起安装 # 因为 pip 在依赖解析时可能会重新下载 torch。我们将把需要 --no-deps 的包单独安装。 local bulk_pkgs=() local nodeps_pkgs=() for pkg in "${current_packages[@]}"; do case "$pkg" in torchvision|torchaudio) # 如果 torch 已安装且可 import,则单独安装这些包并使用 --no-deps if check_package_installed "torch"; then nodeps_pkgs+=("$pkg") else bulk_pkgs+=("$pkg") fi ;; *) bulk_pkgs+=("$pkg") ;; esac done # 默认将 bulk_pkgs 加入主 pip_cmd if [ ${#bulk_pkgs[@]} -gt 0 ]; then pip_cmd+=( "${bulk_pkgs[@]}" ) fi # 显示完整命令(调试用) if [[ "${DEBUG:-0}" -eq 1 ]]; then info "🔧 执行命令: ${pip_cmd[*]}" fi # 执行命令并显示实时进度 local pip_install_output="" local pip_install_exit_code info "⚡ 开始下载和安装..." info "💡 提示: 按 Ctrl+C 可跳过当前镜像安装 ${current_packages[*]} 并尝试下一个 (不会退出脚本)" # 如果包含大型包,提供更详细的信息 if [ "$is_large_package" = true ]; then info "📥 检测到大型包,可能需要下载 100MB+ 文件" info "🚀 将显示详细的下载进度和速度信息" info "⏰ 预计下载时间: 1-10分钟 (取决于网络速度)" fi # 启动后台进度提示任务(仅对大型包) local progress_pid="" local is_large_package=false for pkg in "${current_packages[@]}"; do case "$pkg" in torch|torchvision|torchaudio|tensorflow|tensorflow-gpu) is_large_package=true break ;; esac done if [ "$is_large_package" = true ]; then # 启动后台进度提示 ( local count=0 while true; do sleep 60 # 每分钟提示一次 count=$((count + 1)) echo "⏳ 大型包下载中... (已等待 ${count} 分钟,请继续等待或按 Ctrl+C 跳过此镜像)" done ) & progress_pid=$! fi # 设置局部的 SIGINT 处理,只影响这个安装过程 local pip_interrupted=false local original_trap=$(trap -p INT) # 定义中断处理函数 handle_pip_interrupt() { pip_interrupted=true # 停止后台进度提示 if [ -n "$progress_pid" ]; then kill "$progress_pid" 2>/dev/null || true wait "$progress_pid" 2>/dev/null || true fi echo "" warn "⚠️ 用户中断安装 ${current_packages[*]},将尝试下一个镜像..." return 0 } # 设置新的中断处理 trap 'handle_pip_interrupt' INT # 执行 pip 安装,显示实时进度 local start_time=$(date +%s) "${pip_cmd[@]}" 2>&1 | while read -r line; do # 检查是否被中断 if [ "$pip_interrupted" = true ]; then break fi # 显示进度信息和时间戳 local current_time=$(date +%s) local elapsed_time=$((current_time - start_time)) local time_str="[${elapsed_time}s]" case "$line" in *"Downloading"*|*"downloading"*) # 提取文件大小信息 if [[ "$line" =~ ([0-9]+(\.[0-9]+)?[[:space:]]*[MGK]B) ]]; then local file_size="${BASH_REMATCH[1]}" echo "📥 $time_str $line" # 大文件特别提示 if [[ "$line" =~ [0-9]+(\.[0-9]+)?[[:space:]]*MB && "$line" =~ torch ]]; then echo "⏳ 正在下载大型 PyTorch 文件 ($file_size),请耐心等待..." fi else echo "📥 $time_str $line" fi ;; *"Installing"*|*"installing"*) echo "📦 $time_str $line" ;; *"Successfully installed"*) echo "✅ $time_str $line" ;; *"Collecting"*) echo "🔍 $line" ;; *"Building"*|*"building"*) echo "🔨 $time_str $line" ;; # 新增:识别pip进度条 *"%"*|*"━"*|*"█"*) # 检查是否是进度条(包含百分比或进度字符) if [[ "$line" =~ [0-9]+% ]] || [[ "$line" =~ [━█▌▎] ]]; then echo "⏬ $time_str $line" else echo "$line" fi ;; # 新增:识别下载速度信息 *"MB/s"*|*"KB/s"*|*"kB/s"*|*"mB/s"*) echo "🚀 $time_str $line" ;; # 新增:识别文件大小信息 *" MB"*|*" KB"*|*" GB"*) if [[ "$line" =~ (Downloading|downloading) ]]; then echo "📥 $time_str $line" else echo "📊 $time_str $line" fi ;; *"ERROR"*|*"Error"*|*"error"*) echo "❌ $time_str $line" >&2 ;; *"WARNING"*|*"Warning"*|*"warning"*) echo "⚠️ $time_str $line" ;; *) # 检查是否包含重要信息 if [[ "$line" =~ (Processing|Preparing|Cached|Found|Using) ]]; then echo "ℹ️ $line" else # 其他信息正常显示 echo "$line" fi ;; esac done # 获取退出码 pip_install_exit_code=${PIPESTATUS[0]} # 如果 bulk 安装成功且存在需要单独安装的 nodeps_pkgs,则单独安装它们,使用 --no-deps if [ $pip_install_exit_code -eq 0 ] && [ ${#nodeps_pkgs[@]} -gt 0 ]; then info "📦 开始单独安装不带依赖的包: ${nodeps_pkgs[*]} (使用 --no-deps,避免触发 torch 重下载)" local nodeps_cmd=("pip" "install" "--no-deps" "--no-cache-dir" "--timeout" "$timeout_seconds" "--progress-bar" "on" "--verbose" "--index-url" "$mirror_url" "--trusted-host" "$mirror_host") if [ -n "$extra_index_url" ]; then nodeps_cmd+=("--extra-index-url" "$extra_index_url") fi for host in "${trusted_hosts[@]}"; do nodeps_cmd+=("--trusted-host" "$host") done nodeps_cmd+=("${nodeps_pkgs[@]}") # 执行 nodeps 安装并显示输出 "${nodeps_cmd[@]}" 2>&1 | while read -r nline; do echo "$nline" done local nodeps_exit=${PIPESTATUS[0]} if [ $nodeps_exit -ne 0 ]; then warn "⚠️ 单独安装 ${nodeps_pkgs[*]} 失败 (exit $nodeps_exit),将视为当前镜像失败并尝试下一个镜像" pip_install_exit_code=1 else ok "✅ 单独安装 ${nodeps_pkgs[*]} 成功" fi fi # 停止后台进度提示 if [ -n "$progress_pid" ]; then kill "$progress_pid" 2>/dev/null || true wait "$progress_pid" 2>/dev/null || true fi # 检查是否被用户中断 if [ "$pip_interrupted" = true ]; then pip_install_exit_code=130 # 用户中断退出码 pip_install_output="用户中断安装" fi # 恢复原来的中断处理 if [ -n "$original_trap" ]; then eval "$original_trap" else trap - INT fi if [ $pip_install_exit_code -eq 0 ]; then local end_time=$(date +%s) local elapsed_time=$((end_time - start_time)) local elapsed_min=$((elapsed_time / 60)) local elapsed_sec=$((elapsed_time % 60)) if [ $elapsed_time -gt 60 ]; then ok "✅ 所有包安装成功 (耗时: ${elapsed_min}分${elapsed_sec}秒)" else ok "✅ 所有包安装成功 (耗时: ${elapsed_time}秒)" fi # 如果是大型包,显示额外的性能信息 if [ "$is_large_package" = true ]; then info "📊 大型包安装完成统计:" info " ⏱️ 总耗时: ${elapsed_min}分${elapsed_sec}秒" info " 📡 使用镜像: $mirror_host" info " 📦 包数量: ${#current_packages[@]}" fi return 0 elif [ $pip_install_exit_code -eq 124 ]; then local timeout_msg="⏰ 镜像 $mirror_host 安装 ${current_packages[*]} 超时 (${timeout_seconds}秒),尝试下一个镜像" warn "$timeout_msg" # 记录详细的超时信息到日志 local timeout_details=" 超时详情: - 镜像: $mirror_host - 超时时间: ${timeout_seconds}秒 ($(echo "scale=1; ${timeout_seconds}/60" | bc 2>/dev/null || echo "$((timeout_seconds/60))")分钟) - 包列表: ${current_packages[*]} - 时间: $(sh_date) - 建议: 尝试使用更快的网络或减少包数量" echo "$timeout_details" | tee -a "${LOG_FILE:-/dev/null}" >/dev/null trace_error "pip 安装超时 (镜像: $mirror_host, 包: ${current_packages[*]})" "$timeout_details" "124" elif [ $pip_install_exit_code -eq 130 ]; then warn "🔄 用户跳过镜像 $mirror_host (安装 ${current_packages[*]}),尝试下一个镜像" else warn "⚠️ 镜像 $mirror_host 安装 ${current_packages[*]} 失败,尝试下一个镜像" if [[ "${DEBUG:-0}" -eq 1 ]]; then local mirror_failure_details=" 镜像: $mirror_host 包列表: ${current_packages[*]} 命令: ${pip_cmd[*]} 退出码: $pip_install_exit_code 输出: $pip_install_output" trace_error "pip 安装失败 (镜像: $mirror_host, 包: ${current_packages[*]})" "$mirror_failure_details" "$pip_install_exit_code" fi fi failed_packages=() for pkg in "${current_packages[@]}"; do if ! pip show "$pkg" &> /dev/null; then failed_packages+=("$pkg") fi done current_packages=("${failed_packages[@]}") ((mirror_index++)) # 如果不是超时或中断,等待一下再尝试下一个镜像 if [ $pip_install_exit_code -ne 124 ] && [ $pip_install_exit_code -ne 130 ]; then sleep 2 fi done if [ ${#current_packages[@]} -gt 0 ]; then error "❌ 安装失败: ${current_packages[*]}" return 1 fi ok "✅ 安装完成" return 0 } # 🔧 封装:安装 GPU 版 PyTorch 的函数 # 在调用 install_torch_gpu() 前可用: # export TORCH_INDEX="https://download.pytorch.org/whl/cu118" # 支持源: # # 使用官方源(推荐) # export TORCH_INDEX="https://download.pytorch.org/whl/cu118" # # 使用阿里云镜像(可选) # export TORCH_INDEX="https://mirrors.aliyun.com/pytorch-wheels/cu118/" install_torch_gpu() { local index_url="${1:-$TORCH_INDEX}" # 提示用户使用了默认值 if [ -z "$TORCH_INDEX" ] && [ -z "${1:-}" ]; then warn "⚠️ TORCH_INDEX 未设置,使用默认源: https://download.pytorch.org/whl/cu118" fi # 设置默认值 : "${index_url:=https://download.pytorch.org/whl/cu118}" info "🚀 安装 GPU 版 PyTorch: torch torchaudio torchvision" info "🔗 主源: $index_url" info "📦 备用源: https://pypi.org/simple" if robust_pip_install \ "torch" "torchaudio" "torchvision" \ --index-url "$index_url" \ --extra-index-url "https://pypi.org/simple" \ --trusted-host "pypi.org" \ --trusted-host "$(echo "$index_url" | awk -F[/:] '{print $4}')" \ ; then ok "✅ GPU 版 PyTorch 安装成功" return 0 else local torch_failure_details=" PyTorch GPU 版本安装失败详情: 主源: $index_url 备用源: https://pypi.org/simple 包列表: torch torchaudio torchvision 可能的原因: 1. 网络连接问题 (访问 PyTorch 官方源失败) 2. CUDA 版本不匹配 (请检查本机 CUDA 版本) 3. Python 版本不兼容 4. 磁盘空间不足 建议的解决方案: 1. 检查 CUDA 版本: nvidia-smi 或 nvcc --version 2. 手动指定 CUDA 版本,如: export TORCH_INDEX='https://download.pytorch.org/whl/cu117' 3. 降级到 CPU 版本: pip install torch torchaudio torchvision 4. 检查网络代理设置" trace_error "GPU 版 PyTorch 安装失败" "$torch_failure_details" error "❌ GPU 版 PyTorch 安装失败" return 1 fi } # 🔧 通用 PyTorch 冲突保护安装器 # 功能: 使用 --no-deps 安装包,然后智能安装非冲突依赖 # 用法: install_with_torch_protection "package_name" [excluded_packages...] install_with_torch_protection() { local target_package="$1" shift local excluded_packages=("$@") # 默认的 PyTorch 相关排除列表 local default_torch_exclusions=( "torch" "torchvision" "torchaudio" "torchtext" "pytorch-lightning" "torch-audio" "torchdiffeq" "torchmetrics" "torchsde" "torch-optimizer" "torch-stoi" "pytorch-ranger" "ema-pytorch" "vector-quantize-pytorch" "clip-anytorch" "dctorch" "kornia" ) # 合并排除列表 local all_exclusions=("${default_torch_exclusions[@]}" "${excluded_packages[@]}") info "🛡️ 开始保护性安装: $target_package" info "🚫 排除包列表: ${all_exclusions[*]}" # Step 1: 检查包是否已安装 if pip show "$target_package" &> /dev/null; then info "✅ $target_package 已安装,跳过" return 0 fi # Step 2: 使用 --no-deps 安装目标包 info "📦 使用 --no-deps 安装 $target_package(避免依赖冲突)" if ! robust_pip_install "--no-deps" "$target_package"; then trace_error "$target_package 安装失败" "robust_pip_install --no-deps $target_package 返回失败" error "❌ $target_package 安装失败" return 1 fi ok "✅ $target_package 安装成功(已跳过依赖以保护 PyTorch)" # Step 3: 智能安装依赖 info "🔍 智能安装 $target_package 依赖(排除冲突包)..." install_package_deps "$target_package" "${all_exclusions[@]}" return 0 } # 🛡️ 保护性pip安装函数:防止已安装核心包被依赖重装 # 用法: protected_pip_install "package1" "package2" [pip_args...] protected_pip_install() { local packages=("$@") local pip_args=() local install_packages=() # 分离包名和pip参数 for arg in "${packages[@]}"; do if [[ "$arg" =~ ^-- ]]; then pip_args+=("$arg") else install_packages+=("$arg") fi done info "🛡️ 保护性安装: ${install_packages[*]}" # 检查是否有已保护的核心包 local has_protected=false if [ -n "${PROTECTED_CORE_PACKAGES:-}" ] && [ ${#PROTECTED_CORE_PACKAGES[@]} -gt 0 ]; then for pkg in "${install_packages[@]}"; do for protected in "${PROTECTED_CORE_PACKAGES[@]}"; do if [ "$pkg" = "$protected" ]; then warn "🛡️ 检测到保护包 $pkg,使用 --no-deps 防止依赖重装" has_protected=true break 2 fi done done fi # 如果包含保护包,使用 --no-deps 安装 if [ "$has_protected" = true ]; then info "🚫 使用 --no-deps 模式保护已安装核心包" robust_pip_install "${install_packages[@]}" "${pip_args[@]}" --no-deps else # 正常安装 robust_pip_install "${install_packages[@]}" "${pip_args[@]}" fi } # 🔧 智能依赖安装器(通用版本) # 功能: 获取包的依赖列表,排除指定包,安装其余依赖 # 用法: install_package_deps "package_name" excluded_package1 excluded_package2 ... install_package_deps() { local target_package="$1" shift local excluded_packages=("$@") info "🔍 获取 $target_package 的实际依赖列表..." # 方法1: 尝试从 pip show 获取依赖信息 local actual_deps="" actual_deps=$(pip show "$target_package" 2>/dev/null | grep "Requires:" | sed 's/Requires: //' | tr ',' '\n' | sed 's/^ *//;s/ *$//' | grep -v '^$') # 方法2: 如果方法1失败,尝试使用 pipdeptree (如果可用) if [ -z "$actual_deps" ] && command -v pipdeptree >/dev/null 2>&1; then info "📋 使用 pipdeptree 获取依赖..." actual_deps=$(pipdeptree --packages "$target_package" --json 2>/dev/null | python -c " import sys, json try: data = json.load(sys.stdin) for pkg in data: if pkg['package']['key'] == '$target_package': deps = [dep['key'] for dep in pkg['dependencies']] print('\n'.join(deps)) break except: pass " 2>/dev/null) fi # 方法3: 使用包特定的已知依赖列表 if [ -z "$actual_deps" ]; then info "📋 使用预定义的 $target_package 依赖列表..." case "$target_package" in "prefigure") actual_deps="tqdm einops matplotlib numpy Pillow scipy accelerate diffusers transformers" ;; "pytorch-lightning") actual_deps="torch torchmetrics lightning-utilities typing-extensions fsspec packaging pyyaml tqdm" ;; "diffusers") actual_deps="torch transformers numpy Pillow requests regex importlib-metadata huggingface-hub accelerate safetensors" ;; "speechbrain") actual_deps="torch torchaudio numpy scipy tqdm joblib ruamel.yaml hyperpyyaml sentencepiece huggingface-hub" ;; "asteroid") actual_deps="torch torchaudio numpy scipy pandas soundfile torch-stoi torch-optimizer pytorch-lightning huggingface-hub" ;; *) # 通用的机器学习包常见依赖 actual_deps="numpy scipy matplotlib pandas tqdm Pillow requests packaging typing-extensions" ;; esac else info "📋 从包信息获取到依赖列表" fi if [ -z "$actual_deps" ]; then warn "⚠️ 无法获取 $target_package 的依赖信息,跳过依赖安装" return 0 fi # 处理每个依赖 local installed_count=0 local skipped_count=0 local failed_count=0 while IFS= read -r dep; do [ -z "$dep" ] && continue # 清理依赖名称(去掉版本要求) local clean_dep=$(echo "$dep" | sed 's/[<>=!].*//' | sed 's/^ *//;s/ *$//') [ -z "$clean_dep" ] && continue # 检查是否在排除列表中 local is_excluded=false for excluded_pkg in "${excluded_packages[@]}"; do if [[ "$clean_dep" == "$excluded_pkg"* ]] || [[ "$clean_dep" == *"$excluded_pkg"* ]]; then is_excluded=true break fi done if [ "$is_excluded" = true ]; then info "⏭️ 跳过排除的依赖: $clean_dep" ((skipped_count++)) continue fi # 检查是否已安装 if pip show "$clean_dep" &> /dev/null; then info "✅ $clean_dep 已安装,跳过" else info "📦 安装 $target_package 依赖: $clean_dep" if robust_pip_install "$clean_dep"; then ok "✅ $clean_dep 安装成功" ((installed_count++)) else warn "⚠️ $clean_dep 安装失败,可能影响 $target_package 功能" ((failed_count++)) fi fi done <<< "$actual_deps" # 显示安装统计 info "📊 依赖安装统计: 新安装 $installed_count 个,跳过 $skipped_count 个,失败 $failed_count 个" # 验证目标包是否能正常导入 info "🔍 验证 $target_package 安装完整性..." local import_test="" import_test=$(python -c " try: import $target_package print('IMPORT_OK') version = getattr($target_package, '__version__', 'unknown') print(f'$target_package 版本: {version}') except ImportError as e: print(f'IMPORT_ERROR: {e}') except Exception as e: print(f'OTHER_ERROR: {e}') " 2>&1) if echo "$import_test" | grep -q "IMPORT_OK"; then echo "$import_test" ok "✅ $target_package 及其依赖安装完成,可正常使用" return 0 else echo "$import_test" warn "⚠️ $target_package 导入测试失败,可能缺少某些依赖" return 1 fi } # 🔧 智能安装 prefigure 依赖(使用通用函数) install_prefigure_deps() { install_package_deps "prefigure" \ "torch" "torchvision" "torchaudio" "torchtext" \ "pytorch-lightning" "torch-audio" "torchdiffeq" \ "torchmetrics" "torchsde" "torch-optimizer" \ "torch-stoi" "pytorch-ranger" "ema-pytorch" \ "vector-quantize-pytorch" "clip-anytorch" \ "dctorch" "kornia" } # 🛡️ 通用核心包保护安装:确保核心包版本,并保护性安装冲突包 # 用法:ensure_core_package_installed "core_package" "conflict_package1" ["conflict_package2" ...] # 示例:ensure_core_package_installed "torch" "prefigure" "diffusers" # 示例:ensure_core_package_installed "tensorflow" "some_tf_conflict_package" # 示例:ensure_core_package_installed "numpy" "some_numpy_conflict_package" ensure_core_package_installed() { local core_package="$1" shift local conflict_packages=("$@") if [ -z "$core_package" ]; then error "❌ ensure_core_package_installed: 需要指定核心包名" return 1 fi if [ ${#conflict_packages[@]} -eq 0 ]; then error "❌ ensure_core_package_installed: 需要指定至少一个冲突包" return 1 fi info "🛡️ 开始核心包保护性安装流程" info "🎯 核心包: $core_package" info "⚠️ 冲突包列表: ${conflict_packages[*]}" # Step 1: 使用统一的包检查函数确保核心包已安装 info "🔍 检查核心包 $core_package 安装状态..." if check_package_installed "$core_package"; then ok "✅ 检测到核心包 $core_package 已正确安装" # 显示当前版本信息(通用版) local version_info="" version_info=$(python -c " try: import $core_package if hasattr($core_package, '__version__'): print(f'$core_package 版本: {$core_package.__version__}') else: print(f'$core_package: 已安装(无版本信息)') # 特殊处理 torch 的 CUDA 信息 if '$core_package' == 'torch': print(f'CUDA可用: {\"是\" if $core_package.cuda.is_available() else \"否\"}') if $core_package.cuda.is_available(): print(f'GPU数量: {$core_package.cuda.device_count()}') except Exception as e: print(f'版本检查失败: {e}') " 2>/dev/null) echo "$version_info" else info "📦 需要安装核心包 $core_package..." # 根据核心包类型选择安装策略 case "$core_package" in "torch") if ! install_torch_gpu; then error "❌ PyTorch GPU 版本安装失败" return 1 fi ;; *) # 通用包安装 if ! robust_pip_install "$core_package"; then error "❌ 核心包 $core_package 安装失败" return 1 fi ;; esac ok "✅ 核心包 $core_package 安装完成" fi # Step 2: 循环保护性安装所有冲突包 for conflict_pkg in "${conflict_packages[@]}"; do info "🛡️ 开始保护性安装 $conflict_pkg (避免覆盖核心包 $core_package)" # 根据核心包类型选择保护策略 case "$core_package" in "torch") if ! install_with_torch_protection "$conflict_pkg"; then warn "⚠️ $conflict_pkg 安装失败,继续处理下一个包" continue fi ;; *) # 通用保护策略:使用 --no-deps if ! robust_pip_install "$conflict_pkg" --no-deps; then warn "⚠️ $conflict_pkg 保护性安装失败,继续处理下一个包" continue fi ;; esac ok "✅ $conflict_pkg 保护性安装完成" done # Step 3: 最终验证核心包完整性 info "🔍 验证核心包 $core_package 是否保持完整" local final_check="" final_check=$(python -c " try: import $core_package if hasattr($core_package, '__version__'): print(f'最终 $core_package 版本: {$core_package.__version__}') else: print(f'最终 $core_package: 导入成功') # 特殊验证逻辑 if '$core_package' == 'torch': if $core_package.cuda.is_available(): print('✅ GPU支持正常') print(f'GPU设备: {$core_package.cuda.get_device_name(0)}') else: print('❌ GPU支持异常') else: print('✅ 包导入正常') except Exception as e: print(f'❌ 验证失败: {e}') " 2>/dev/null) echo "$final_check" if echo "$final_check" | grep -q "✅"; then ok "🎉 核心包 $core_package 保护性安装流程完成" return 0 else error "❌ 核心包 $core_package 验证失败" return 1 fi } # 🛡️ PyTorch 专用保护函数(向后兼容) ensure_torch_gpu_be_installed() { local conflict_packages=("$@") info "🔄 调用通用核心包保护函数处理 PyTorch" ensure_core_package_installed "torch" "${conflict_packages[@]}" } # 🛡️ 预防性安装:确保 PyTorch GPU 版本,并保护性安装 prefigure(向后兼容函数) install_pytorch_and_prefigure_safely() { info "🔄 调用通用核心包保护函数处理 prefigure" ensure_core_package_installed "torch" "prefigure" } info "🛡️ 开始 PyTorch GPU 保护性安装流程" # Step 1: 使用统一的包检查函数检查 PyTorch GPU 版本 info "🔍 检查当前 PyTorch 安装状态..." if check_package_installed "torch"; then ok "✅ 检测到 GPU 版本的 PyTorch 已正确安装" # 显示当前版本信息 local version_info="" version_info=$(python -c " try: import torch print(f'PyTorch版本: {torch.__version__}') if torch.cuda.is_available(): print(f'CUDA可用: 是') print(f'GPU数量: {torch.cuda.device_count()}') else: print(f'CUDA可用: 否') except Exception as e: print(f'版本检查失败: {e}') " 2>/dev/null) echo "$version_info" else info "� 需要安装 GPU 版本的 PyTorch..." if ! install_torch_gpu; then error "❌ PyTorch GPU 版本安装失败" return 1 fi ok "✅ PyTorch GPU 版本安装完成" fi # Step 2: 安装 prefigure(使用通用保护函数) info "🛡️ 开始保护性安装 prefigure (避免覆盖 PyTorch GPU 版本)" if ! install_with_torch_protection "prefigure"; then warn "⚠️ prefigure 安装失败,但继续执行后续步骤" # 可选:exit 1 如果必须成功 fi # Step 4: 验证安装后 PyTorch GPU 版本是否保持完整 info "🔍 验证 PyTorch GPU 版本是否保持完整" local current_torch_check="" current_torch_check=$(python -c " import torch print(f'当前PyTorch版本: {torch.__version__}') if torch.cuda.is_available(): print('CURRENT_GPU_OK') print(f'CUDA版本: {torch.version.cuda if hasattr(torch.version, \"cuda\") else \"unknown\"}') print(f'GPU设备: {torch.cuda.get_device_name(0)}') else: print('CURRENT_CPU_ONLY') " 2>&1) local torch_check_exit_code=$? if [ $torch_check_exit_code -eq 0 ] && echo "$current_torch_check" | grep -q "CURRENT_GPU_OK"; then echo "$current_torch_check" ok "✅ PyTorch GPU 版本保持完整,prefigure 安装成功" return 0 else warn "⚠️ 检测到 PyTorch 问题,可能需要重新安装" echo "$current_torch_check" # 如果确实有问题,再重新安装 GPU 版本 info "🔧 重新安装 GPU 版 PyTorch 以确保完整性" if ! install_torch_gpu; then trace_error "GPU 版 PyTorch 恢复安装失败" "install_torch_gpu 函数在 prefigure 后调用失败" error "❌ GPU 版 PyTorch 安装失败,请检查 CUDA 环境" return 1 fi fi # Step 5: 最终验证 CUDA 是否可用 info "🔍 最终验证 PyTorch 是否支持 CUDA" local cuda_verification_output="" cuda_verification_output=$(python -c " import torch print(f'PyTorch: {torch.__version__}') assert torch.cuda.is_available(), 'CUDA not available' print(f'✅ CUDA 可用: {torch.cuda.get_device_name(0)}') " 2>&1) local cuda_verification_exit_code=$? if [ $cuda_verification_exit_code -eq 0 ]; then echo "$cuda_verification_output" ok "✅ 成功确保 PyTorch 为 GPU 版" else local cuda_diagnostic_output="" cuda_diagnostic_output=$(python -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}')" 2>&1) local full_diagnostic=" CUDA 验证失败详情: PyTorch 验证输出: $cuda_verification_output CUDA 可用性诊断: $cuda_diagnostic_output 可能的原因: 1. CUDA 驱动版本不匹配 2. PyTorch 安装的是 CPU 版本 3. CUDA 库未正确安装 4. 系统没有兼容的 GPU 建议的解决方案: 1. 检查 NVIDIA 驱动: nvidia-smi 2. 检查 CUDA 版本: nvcc --version 3. 重新安装对应 CUDA 版本的 PyTorch 4. 如无 GPU,改用 CPU 版本: pip install torch torchvision torchaudio" trace_error "PyTorch CUDA 验证失败" "$full_diagnostic" "$cuda_verification_exit_code" error "❌ PyTorch 不支持 CUDA,请检查驱动、CUDA、安装版本是否匹配" exit 1 fi } # # 🔧 智能升级 pip:仅当版本过低时才升级 # # 调用函数(使用默认最低版本 23.0) # upgrade_pip_if_needed # # 或指定最低版本 # upgrade_pip_if_needed "24.0" upgrade_pip_if_needed() { local required_version="${1:-23.0}" # 建议最低版本(可传参) local current_version info "🔍 检查 pip 版本..." # 获取当前 pip 版本 local pip_version_output="" pip_version_output=$(python -m pip --version 2>&1) local pip_version_exit_code=$? if [ $pip_version_exit_code -eq 0 ]; then current_version=$(echo "$pip_version_output" | awk '{print $2}') else trace_error "pip 版本检查失败" "$pip_version_output" "$pip_version_exit_code" warn "⚠️ pip 未安装或无法获取版本,准备安装" # 先尝试 ensurepip(某些系统不包含) local ensurepip_output="" ensurepip_output=$(python -m ensurepip --default-pip 2>&1) local ensurepip_exit_code=$? if [ $ensurepip_exit_code -ne 0 ]; then trace_error "ensurepip 安装失败" "$ensurepip_output" "$ensurepip_exit_code" fi current_version=$(python -m pip --version 2>/dev/null | awk '{print $2}') # 如果仍然没有 pip,则尝试使用 get-pip.py(使用清华镜像以提高可用性) if [ -z "$current_version" ]; then warn "⚠️ pip 仍未安装,尝试通过 get-pip.py 安装" GET_PIP_URL="https://pypi.tuna.tsinghua.edu.cn/bootstrap/pip/get-pip.py" GET_PIP_PY="/tmp/get-pip-$$.py" local download_output="" if command -v curl >/dev/null 2>&1; then download_output=$(curl -fsSL --connect-timeout 10 --max-time 60 "$GET_PIP_URL" -o "$GET_PIP_PY" 2>&1) local download_exit_code=$? if [ $download_exit_code -ne 0 ]; then trace_error "curl 下载 get-pip.py 失败" "$download_output" "$download_exit_code" fi elif command -v wget >/dev/null 2>&1; then download_output=$(wget -q -O "$GET_PIP_PY" "$GET_PIP_URL" 2>&1) local download_exit_code=$? if [ $download_exit_code -ne 0 ]; then trace_error "wget 下载 get-pip.py 失败" "$download_output" "$download_exit_code" fi else trace_error "缺少下载工具" "系统未安装 curl 或 wget,无法下载 get-pip.py" warn "⚠️ 无法下载 get-pip.py:系统未安装 curl 或 wget" fi if [ -f "$GET_PIP_PY" ]; then local get_pip_output="" get_pip_output=$(python "$GET_PIP_PY" --quiet --no-warn-script-location 2>&1) local get_pip_exit_code=$? if [ $get_pip_exit_code -eq 0 ]; then ok "✅ pip 安装成功 (get-pip.py)" else trace_error "get-pip.py 安装失败" "$get_pip_output" "$get_pip_exit_code" warn "⚠️ 使用 get-pip.py 安装 pip 失败" fi rm -f "$GET_PIP_PY" fi current_version=$(python -m pip --version 2>/dev/null | awk '{print $2}') fi fi info "📦 当前 pip 版本: $current_version,目标最低版本: $required_version" # 比较版本(使用 sort -V 进行版本排序比较) if printf '%s\n%s' "$required_version" "$current_version" | sort -V | head -n1 | grep -q "$required_version"; then ok "✅ pip 版本已满足要求,跳过升级" return 0 else info "⏫ pip 版本过低,正在升级..." local pip_upgrade_output="" local pip_upgrade_exit_code if [ -n "$PIP_INDEX" ] && [ -n "$PIP_TRUSTED_HOST" ]; then pip_upgrade_output=$(python -m pip install --upgrade pip \ -i "$PIP_INDEX" \ --trusted-host "$PIP_TRUSTED_HOST" 2>&1) pip_upgrade_exit_code=$? else pip_upgrade_output=$(python -m pip install --upgrade pip 2>&1) pip_upgrade_exit_code=$? fi if [ $pip_upgrade_exit_code -eq 0 ]; then ok "✅ pip 升级成功" else trace_error "pip 升级失败" "$pip_upgrade_output" "$pip_upgrade_exit_code" warn "⚠️ pip 升级失败,但继续执行后续步骤" fi fi } # ----------------------------- # 3. 检查或创建环境(Conda 优先,venv fallback) # ----------------------------- ENV_NAME="train_ss" # 创建或使用已有环境(Conda 或 venv) if ! create_or_use_env "$ENV_NAME" "${PYTHON_VERSIONS[@]}"; then trace_error "虚拟环境创建/激活完全失败" "create_or_use_env 函数返回非零退出码" error "虚拟环境创建或激活失败,脚本终止" fi # 此时环境已激活,无需额外 activate info "✅ 环境已激活: $ENV_NAME" echo "最新进度请查看日志文件: $LOG_FILE" # ----------------------------- # 5. 升级 pip # ----------------------------- upgrade_pip_if_needed echo "最新进度请查看日志文件: $LOG_FILE" # ----------------------------- # 6. 智能安装 PyTorch(根据 GPU) # ----------------------------- info "🔍 检测 GPU..." TORCH_INDEX="https://download.pytorch.org/whl/cu128" if command -v nvidia-smi &> /dev/null; then GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader,nounits -i 0 || echo "unknown") info "GPU: $GPU_NAME" case "$GPU_NAME" in *"P100"*) TORCH_INDEX="https://download.pytorch.org/whl/cu118" ;; *"T4"*|*"V100"*) info "使用 CUDA 11.8/12.x" ;; *"A10"*|*"A100"*|*"3090"*|*"4090"*) info "使用 CUDA 12.8" ;; *) info "未知 GPU,尝试 CUDA 12.8" ;; esac else warn "⚠️ 无 nvidia-smi,使用 CPU 版" TORCH_INDEX="https://download.pytorch.org/whl/cpu" fi # 🎯 处理核心包参数 DEFAULT_CORE_PACKAGES="torch,torchvision,torchaudio" CORE_PACKAGES="${CORE_PACKAGES_ARG:-$DEFAULT_CORE_PACKAGES}" # 解析核心包列表 IFS=',' read -ra CORE_PACKAGE_ARRAY <<< "$CORE_PACKAGES" PRIMARY_CORE_PACKAGE="${CORE_PACKAGE_ARRAY[0]}" info "🛡️ 核心包配置:" info " 主核心包: $PRIMARY_CORE_PACKAGE" if [ ${#CORE_PACKAGE_ARRAY[@]} -gt 1 ]; then info " 相关核心包: ${CORE_PACKAGE_ARRAY[*]:1}" fi info " 冲突包: prefigure" # 🚀 改进:对所有核心包进行保护,不仅仅是主核心包 info "🔍 检查所有指定的核心包安装状态..." # 检查每个核心包是否已安装 ALL_CORE_PACKAGES_INSTALLED=true # 🚀 改进:完全使用Python检查,兼容受限环境(无需grep/sed/cut等外部命令) torch_check_result=$(python -c " try: import subprocess import sys # 检查torch包 result = subprocess.run([sys.executable, '-m', 'pip', 'show', 'torch'], capture_output=True, text=True, timeout=10) if result.returncode == 0: version = None for line in result.stdout.split('\n'): if line.startswith('Version:'): version = line.split(':', 1)[1].strip() break if version: print(f'TORCH_VERSION:{version}') if '2.8.0' in version and ('cu' in version or '+cu' in version): print('STATUS:2.8.0_GPU_FOUND') elif 'cu' in version or '+cu' in version: print('STATUS:OTHER_GPU_FOUND') else: print('STATUS:CPU_VERSION') else: print('STATUS:NO_VERSION') else: print('STATUS:NOT_FOUND') # 同时检查其他核心包 other_packages = ['torchvision', 'torchaudio'] missing_packages = [] for pkg in other_packages: pkg_result = subprocess.run([sys.executable, '-m', 'pip', 'show', pkg], capture_output=True, text=True, timeout=10) if pkg_result.returncode != 0: missing_packages.append(pkg) if missing_packages: print(f'MISSING_PACKAGES:{\"|\".join(missing_packages)}') else: print('ALL_PYTORCH_PACKAGES_FOUND') except Exception as e: print(f'PYTHON_CHECK_FAILED:{e}') " 2>/dev/null) # 使用纯bash字符串处理解析结果 case "$torch_check_result" in *"STATUS:2.8.0_GPU_FOUND"*) # 提取版本号 torch_version=$(echo "$torch_check_result" | while read line; do case "$line" in TORCH_VERSION:*) echo "${line#TORCH_VERSION:}" ;; esac done) ok "🎯 检测到 PyTorch 2.8.0 GPU版本: $torch_version,启用强制保护模式" ALL_CORE_PACKAGES_INSTALLED=true for core_pkg in "${CORE_PACKAGE_ARRAY[@]}"; do ok "✅ $core_pkg - 强制保护模式,跳过重复安装" done ;; *"STATUS:OTHER_GPU_FOUND"*) torch_version=$(echo "$torch_check_result" | while read line; do case "$line" in TORCH_VERSION:*) echo "${line#TORCH_VERSION:}" ;; esac done) ok "🎯 检测到 PyTorch GPU版本: $torch_version,启用强制保护模式" ALL_CORE_PACKAGES_INSTALLED=true for core_pkg in "${CORE_PACKAGE_ARRAY[@]}"; do ok "✅ $core_pkg - 强制保护模式,跳过重复安装" done ;; *"PYTHON_CHECK_FAILED"*|*"STATUS:NOT_FOUND"*) # 环境检查失败或torch未安装,提供手动安装指导 warn "⚠️ 环境检查失败或PyTorch未安装" echo "" error "💥 建议手动安装以下包:" echo "" echo "🔧 复制以下命令进行手动安装:" echo "" echo "# PyTorch GPU版本(推荐)" echo "pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128" echo "" echo "# 或者CPU版本" echo "pip install torch torchvision torchaudio" echo "" echo "# 其他常用包" echo "pip install transformers diffusers opencv-python pillow numpy pandas matplotlib" echo "" echo "🔄 安装完成后,请重新运行脚本" echo "" read -p "⚠️ 是否继续尝试自动安装?(y/N): " continue_auto if [[ ! "$continue_auto" =~ ^[Yy]$ ]]; then exit 1 fi ALL_CORE_PACKAGES_INSTALLED=false ;; *"STATUS:CPU_VERSION"*) warn "⚠️ 检测到PyTorch CPU版本,建议升级到GPU版本" ALL_CORE_PACKAGES_INSTALLED=false ;; *) # 未知状态,进入常规检查 ALL_CORE_PACKAGES_INSTALLED=false ;; esac # 如果强制保护模式未激活,执行常规检查 if [ "$ALL_CORE_PACKAGES_INSTALLED" != true ]; then # 常规检查流程 for core_pkg in "${CORE_PACKAGE_ARRAY[@]}"; do echo "🔍 正在检查核心包: $core_pkg" >&2 if check_package_installed "$core_pkg"; then ok "✅ $core_pkg - 已正确安装" echo "✅ 核心包 $core_pkg 检查通过" >&2 else warn "📦 $core_pkg - 需要安装" echo "❌ 核心包 $core_pkg 检查失败" >&2 ALL_CORE_PACKAGES_INSTALLED=false fi done fi echo "🎯 最终状态: ALL_CORE_PACKAGES_INSTALLED=$ALL_CORE_PACKAGES_INSTALLED" >&2 if [ "$ALL_CORE_PACKAGES_INSTALLED" = true ]; then ok "🎉 所有指定的核心包都已安装,跳过核心包保护流程" else # 只有在需要时才进行保护性安装 info "🛡️ 开始核心包保护性安装流程..." # 确保再安装了prefigure之后安装的是指定的核心包版本 if ! ensure_core_package_installed "$PRIMARY_CORE_PACKAGE" "prefigure"; then trace_error "核心包保护性安装流程失败" "ensure_core_package_installed 函数返回非零退出码" error "核心包 $PRIMARY_CORE_PACKAGE 安装失败,无法继续" fi fi # ----------------------------- # 6.5. 🎯 核心大型包专项安装阶段 # ----------------------------- # 🎯 核心大型包安装函数:专门处理需要大量下载的包 install_essential_large_packages() { info "🎯 开始核心大型包专项安装阶段..." echo "🔍 调试: ALL_CORE_PACKAGES_INSTALLED=${ALL_CORE_PACKAGES_INSTALLED:-未设置}" >&2 # 🚀 如果前面已经确认所有核心包都安装了,直接跳过 if [ "${ALL_CORE_PACKAGES_INSTALLED:-false}" = true ]; then ok "🎉 前面已确认所有核心包都已安装,跳过大型包安装阶段" echo "✅ 大型包安装阶段成功跳过" >&2 return 0 fi echo "⚠️ 继续执行大型包安装流程" >&2 # �️ 创建已安装核心包保护列表 - 防止依赖重装 local PROTECTED_CORE_PACKAGES=() if [ -n "${CORE_PACKAGE_ARRAY:-}" ] && [ ${#CORE_PACKAGE_ARRAY[@]} -gt 0 ]; then for core_pkg in "${CORE_PACKAGE_ARRAY[@]}"; do if check_package_installed "$core_pkg"; then PROTECTED_CORE_PACKAGES+=("$core_pkg") info "🛡️ 保护已安装核心包: $core_pkg" fi done fi # �🚀 智能使用 CLI 指定的核心包,而不是硬编码列表 local ESSENTIAL_LARGE_PACKAGES=() # 如果有 CLI 指定的核心包,优先使用 if [ -n "${CORE_PACKAGE_ARRAY:-}" ] && [ ${#CORE_PACKAGE_ARRAY[@]} -gt 0 ]; then ESSENTIAL_LARGE_PACKAGES=("${CORE_PACKAGE_ARRAY[@]}") info "📋 使用 CLI 指定的核心包: ${ESSENTIAL_LARGE_PACKAGES[*]}" else # 回退到默认的大型包列表 ESSENTIAL_LARGE_PACKAGES=( "torch" "torchvision" "torchaudio" "tensorflow" "transformers" "diffusers" "opencv-python" "pillow" ) info "📋 使用默认大型包列表" fi # 检查是否有任何核心包需要安装 local large_packages_needed=() # 🛡️ 应用保护过滤:从安装列表中移除已保护的核心包 local filtered_packages=() readarray -t filtered_packages < <(filter_installed_packages "${ESSENTIAL_LARGE_PACKAGES[@]}") # 进一步过滤:移除已保护的核心包,防止依赖冲突重装 for pkg in "${filtered_packages[@]}"; do local is_protected=false for protected_pkg in "${PROTECTED_CORE_PACKAGES[@]}"; do if [ "$pkg" = "$protected_pkg" ]; then info "🛡️ 跳过已保护核心包: $pkg (防止依赖重装)" is_protected=true break fi done if [ "$is_protected" = false ]; then large_packages_needed+=("$pkg") fi done if [ ${#large_packages_needed[@]} -eq 0 ]; then ok "🎉 所有核心大型包都已安装,跳过专项安装阶段" return 0 fi # 给用户明确的提示 info "📋 检测到需要安装的核心大型包 (${#large_packages_needed[@]}个):" for pkg in "${large_packages_needed[@]}"; do case "$pkg" in torch|torchvision|torchaudio) info " 🔥 $pkg - PyTorch生态系统 (~200-500MB)" ;; tensorflow) info " 🧠 $pkg - TensorFlow框架 (~400MB)" ;; transformers) info " 🤗 $pkg - Hugging Face模型库 (~100MB)" ;; diffusers) info " 🎨 $pkg - 扩散模型库 (~50MB)" ;; opencv-python) info " 👁️ $pkg - 计算机视觉库 (~90MB)" ;; *) info " 📦 $pkg - 核心依赖包" ;; esac done info "⚠️ 注意: 这些包下载量较大,预计总下载量: 500MB-2GB" info "🌐 将使用最快的镜像源进行下载" info "⏰ 预计耗时: 2-15分钟 (取决于网络速度)" info "💡 下载过程中会显示详细的进度和速度信息" echo "" # 让用户有心理准备 read -t 10 -p "⏳ 10秒后开始下载,按回车立即开始,或按Ctrl+C取消: " || true echo "" # 分批安装大型包 (避免内存压力) local batch_size=2 local total_packages=${#large_packages_needed[@]} local current_batch=1 local total_batches=$(( (total_packages + batch_size - 1) / batch_size )) for ((i=0; i /dev/null; then has_gpu=true info "✅ 检测到 GPU 环境" else info "ℹ️ 未检测到 GPU 环境,跳过 GPU 版本验证" return 0 fi # 检查 PyTorch 是否支持 CUDA local cuda_check_output="" cuda_check_output=$(python -c " import torch print(f'PyTorch版本: {torch.__version__}') print(f'CUDA可用: {torch.cuda.is_available()}') if torch.cuda.is_available(): print(f'CUDA版本: {torch.version.cuda}') print(f'GPU设备: {torch.cuda.get_device_name(0)}') print('GPU_SUPPORTED') elif '+cu' in torch.__version__ or 'cuda' in torch.__version__.lower(): print('GPU版本已安装但CUDA不可用(可能是驱动问题)') print('GPU_VERSION_INSTALLED') else: print('CPU_ONLY') " 2>&1) local cuda_check_exit_code=$? if [ $cuda_check_exit_code -ne 0 ]; then trace_error "PyTorch 导入失败" "$cuda_check_output" "$cuda_check_exit_code" error "❌ PyTorch 导入失败,请检查安装" return 1 fi echo "$cuda_check_output" # 检查是否支持 GPU 或已安装 GPU 版本 if echo "$cuda_check_output" | grep -q "GPU_SUPPORTED"; then ok "✅ PyTorch GPU 版本验证成功" return 0 elif echo "$cuda_check_output" | grep -q "GPU_VERSION_INSTALLED"; then warn "⚠️ PyTorch GPU版本已安装但CUDA不可用,可能是驱动问题" ok "✅ PyTorch GPU 版本已正确安装,跳过重装" return 0 elif echo "$cuda_check_output" | grep -q "CPU_ONLY"; then warn "⚠️ 检测到 CPU 版本的 PyTorch,需要重新安装 GPU 版本" # 强制重新安装 GPU 版本 info "🔄 正在重新安装 GPU 版本的 PyTorch..." # 先卸载 CPU 版本 info "📥 卸载当前版本..." pip uninstall torch torchvision torchaudio -y >/dev/null 2>&1 || true # 重新安装 GPU 版本 if install_torch_gpu; then # 再次验证 local recheck_output="" recheck_output=$(python -c " import torch print(f'重新安装后 - PyTorch版本: {torch.__version__}') print(f'CUDA可用: {torch.cuda.is_available()}') if torch.cuda.is_available(): print(f'GPU设备: {torch.cuda.get_device_name(0)}') print('RECHECK_GPU_OK') else: print('RECHECK_STILL_CPU') " 2>&1) if echo "$recheck_output" | grep -q "RECHECK_GPU_OK"; then ok "✅ GPU 版本 PyTorch 重新安装成功" echo "$recheck_output" return 0 else trace_error "PyTorch GPU 重新安装验证失败" "$recheck_output" error "❌ 重新安装后仍然不支持 GPU,请检查 CUDA 环境" return 1 fi else error "❌ GPU 版本 PyTorch 重新安装失败" return 1 fi else trace_error "PyTorch GPU 验证异常" "$cuda_check_output" error "❌ PyTorch GPU 验证结果异常" return 1 fi } # 执行最终验证 if ! final_gpu_verification; then error "❌ PyTorch GPU 最终验证失败" fi info "🎉 环境配置完成!" # ----------------------------- # 9. ✅ 最终成功提示(终端彩色 + 日志纯文本) # ----------------------------- # 调用时先获取 get_activate_cmd "$ENV_NAME" "$ENV_PATH" log_success "$PLATFORM" "$ENV_NAME" "$LOG_FILE" "$WORKSPACE" "$ACTIVATE_CMD" "$DETECTED_ENV_TYPE"
;>
20250905 0445 ...