You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Bash脚本让8卡服务器GPU始终满负荷运行训练任务?

保持8-GPU服务器满负载的动态任务调度方案

你的原始脚本确实会因为任务时长不一致导致GPU空闲浪费,要实现GPU一空闲就立刻启动新任务,我们可以用Bash的进程管理特性来做动态调度,核心是用wait -n监听任意后台进程的结束,然后及时给空闲GPU分配新任务。

下面是完整的改进脚本,我会一步步解释逻辑:

完整脚本

#!/bin/bash

# 第一步:生成所有待执行的任务列表(把所有超参数组合存起来)
tasks=()
for l1 in {1e-4,2e-4,5e-4,1e-3}; do
    for attr in {0..7}; do
        tasks+=("$l1 $attr")
    done
done

# 第二步:初始化每个GPU的第一个任务,记录进程ID
declare -A gpu_pids  # 用关联数组存储GPU编号对应的进程ID
for gpu in {0..7}; do
    if [ ${#tasks[@]} -gt 0 ]; then
        # 取出列表第一个任务
        task="${tasks[0]}"
        tasks=("${tasks[@]:1}")  # 从列表中移除已分配的任务
        
        # 解析任务参数
        read lr attr <<< "$task"
        
        # 启动任务,可选:把输出重定向到单独日志文件
        python train.py --lr "$lr" --attr "$attr" --device "$gpu" > "train_gpu${gpu}_lr${lr}_attr${attr}.log" 2>&1 &
        
        # 记录该GPU对应的进程ID
        gpu_pids[$gpu]=$!
        echo "GPU $gpu 启动任务:lr=$lr, attr=$attr (PID: ${gpu_pids[$gpu]})"
    fi
done

# 第三步:动态补充任务,直到所有任务分配完毕
while [ ${#tasks[@]} -gt 0 ]; do
    # 等待任意一个后台进程结束,获取结束的进程PID
    finished_pid=$(wait -n)
    
    # 找出这个PID对应的GPU(哪个GPU空闲了)
    for gpu in "${!gpu_pids[@]}"; do
        if [ "${gpu_pids[$gpu]}" -eq "$finished_pid" ]; then
            echo "GPU $gpu 任务完成(PID: $finished_pid),准备分配新任务..."
            
            # 取出下一个待执行任务
            task="${tasks[0]}"
            tasks=("${tasks[@]:1}")
            read lr attr <<< "$task"
            
            # 在空闲GPU上启动新任务
            python train.py --lr "$lr" --attr "$attr" --device "$gpu" > "train_gpu${gpu}_lr${lr}_attr${attr}.log" 2>&1 &
            gpu_pids[$gpu]=$!
            echo "GPU $gpu 启动新任务:lr=$lr, attr=$attr (PID: ${gpu_pids[$gpu]})"
            
            break  # 找到对应的GPU后退出循环,处理下一个结束的进程
        fi
    done
done

# 第四步:等待最后一批任务全部完成
wait
echo "所有训练任务已完成!"

关键逻辑解释

  1. 任务预生成:先把所有需要运行的(lr, attr)组合存入数组,避免循环嵌套导致的调度混乱,方便后续按需取用。
  2. GPU进程跟踪:用关联数组gpu_pids记录每个GPU当前运行的进程ID,这样能精准定位哪个GPU空闲了。
  3. 动态等待与补位:wait -n是Bash 4.3+的特性,它会等待任意一个后台进程结束并返回其PID。我们通过PID找到对应的空闲GPU,立刻给它分配新任务,确保8块GPU始终处于满负载状态。
  4. 日志分离:把每个任务的输出重定向到单独的日志文件,避免多个任务的输出混在一起,方便后续排查问题。

兼容性注意

如果你的服务器Bash版本低于4.3(可以用bash --version查看),wait -n无法使用,这时可以用循环检查每个进程的存活状态来替代:

# 替代wait -n的逻辑,适合低版本Bash
while [ ${#tasks[@]} -gt 0 ]; do
    # 遍历所有GPU,检查进程是否存活
    for gpu in "${!gpu_pids[@]}"; do
        if ! kill -0 "${gpu_pids[$gpu]}" 2>/dev/null; then
            # 进程已结束,分配新任务
            echo "GPU $gpu 任务完成,准备分配新任务..."
            task="${tasks[0]}"
            tasks=("${tasks[@]:1}")
            read lr attr <<< "$task"
            python train.py --lr "$lr" --attr "$attr" --device "$gpu" > "train_gpu${gpu}_lr${lr}_attr${attr}.log" 2>&1 &
            gpu_pids[$gpu]=$!
            echo "GPU $gpu 启动新任务:lr=$lr, attr=$attr (PID: ${gpu_pids[$gpu]})"
            
            # 处理完一个GPU后,重新开始循环(避免漏查其他可能空闲的GPU)
            continue 2
        fi
    done
    sleep 1  # 每秒检查一次,避免占用过多CPU
done

这种方式效率稍低,但能兼容旧版本Bash。

内容的提问来源于stack exchange,提问作者Hammer. Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:26:42