Files
frankenstallm/source/scripts/launch_3b_pretrain.sh
ModelHub XC d4abdb70fa 初始化项目,由ModelHub XC社区提供模型
Model: pathcosmos/frankenstallm
Source: Original Platform
2026-07-14 04:21:16 +08:00

259 lines
10 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# launch_3b_pretrain.sh — 8-GPU FP8 pretraining launcher for Korean 3B LLM
#
# Features:
# - SIGHUP 방어: SSH 끊김 시 자동으로 nohup+setsid로 세션 보호
# - Graceful shutdown: SIGTERM 시 Python 시그널 핸들러가 비상 체크포인트 저장
# - 자동 resume: 최신 체크포인트에서 자동 재개
# - PID 파일: 프로세스 모니터링 및 제어용
# - grep 파이프라인 exit code 보호 (|| true)
#
# Usage:
# bash scripts/launch_3b_pretrain.sh # full run (60B tokens)
# bash scripts/launch_3b_pretrain.sh --max_steps 500 # quick test
# bash scripts/launch_3b_pretrain.sh --resume checkpoints/korean_3b_fp8_run1/checkpoint-0010000
# MAX_STEPS=95000 bash scripts/launch_3b_pretrain.sh # 100B tokens
#
# 모니터링:
# tail -f checkpoints/korean_3b_fp8_run1/train.log
# cat checkpoints/korean_3b_fp8_run1/train.pid
#
# 중지 (비상 체크포인트 자동 저장):
# kill $(cat checkpoints/korean_3b_fp8_run1/train.pid)
#
# 강제 종료 (체크포인트 저장 없음):
# kill -9 $(cat checkpoints/korean_3b_fp8_run1/train.pid)
# =============================================================================
# -u: 미정의 변수 에러
# NOTE: -e, -o pipefail 의도적 제거
# 이전 문제: grep 파이프라인에서 모든 라인이 필터링되면 exit code 1 반환
# → pipefail이 이를 스크립트 실패로 전파 → 학습 중단
# 해결: set -e/pipefail 제거 + grep 체인에 || true 추가
set -u
# ---- Configurable defaults --------------------------------------------------
RUN_NAME="${RUN_NAME:-korean_3b_fp8_run1}"
CONFIG="${CONFIG:-configs/korean_3b_fp8.yaml}"
TRAIN_DATA="${TRAIN_DATA:-data/3b_train.bin}"
VAL_DATA="${VAL_DATA:-data/3b_val.bin}"
CKPT_DIR="checkpoints/${RUN_NAME}"
LOG_FILE="${CKPT_DIR}/train.log"
NPROC=8
MASTER_PORT="${MASTER_PORT:-29501}"
MAX_STEPS="${MAX_STEPS:-57000}"
BATCH_SIZE=5
GRAD_ACCUM=8
WARMUP_STEPS=2000
SEED=42
# ---- B200 / NVSwitch single-node NCCL tuning (3B optimized, v2) ----------
export NCCL_IB_DISABLE=1
export NCCL_ALGO=NVLS,Ring # NVSwitch hardware reduction first (was Ring,Tree)
export NCCL_PROTO=Simple
export NCCL_NVLS_ENABLE=1 # NVLink SHARP — hardware-accelerated all-reduce
export NCCL_MIN_NCHANNELS=32 # raise minimum for NVSwitch headroom (was 16)
export NCCL_MAX_NCHANNELS=32
export NCCL_BUFFSIZE=268435456 # 256MB (was 128MB) — reduces bucket pipeline stalls
export NCCL_P2P_LEVEL=NVL
export NCCL_NET_GDR_LEVEL=0
export OMP_NUM_THREADS=4
export MKL_NUM_THREADS=4
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
# Triton/Inductor cache on executable filesystem (not /tmp which is noexec)
export TRITON_CUDACRT_PATH=/usr/local/cuda/include
export TRITON_PTXAS_PATH=/usr/local/cuda/bin/ptxas
cd "$(dirname "$0")/.."
mkdir -p "${CKPT_DIR}"
# ---- Session protection (SIGHUP 방어) ---------------------------------------
# tmux/screen 없이 실행 시, 자동으로 nohup + setsid로 래핑하여
# SSH 끊김(SIGHUP)으로부터 학습 프로세스를 보호합니다.
#
# 작동 원리:
# 1. tmux/screen/이미 보호됨 여부 확인
# 2. 미보호 상태이면 _LAUNCH_PROTECTED=1 설정 후 nohup setsid로 자기 자신을 재실행
# 3. 재실행된 프로세스는 새로운 세션 리더가 되어 터미널과 분리됨
# 4. 원래 셸은 PID와 모니터링 명령을 출력하고 즉시 종료
PID_FILE="${CKPT_DIR}/train.pid"
if [[ -z "${_LAUNCH_PROTECTED:-}" ]] && [[ -z "${TMUX:-}" ]] && [[ -z "${STY:-}" ]]; then
export _LAUNCH_PROTECTED=1
NOHUP_LOG="${CKPT_DIR}/launch_$(date +%Y%m%d_%H%M%S).log"
echo "=================================================================="
echo " SIGHUP PROTECTION ACTIVATED"
echo " tmux/screen 미감지 → 세션 보호 모드 자동 활성화 (nohup + setsid)"
echo " SSH 끊어져도 학습이 계속됩니다."
echo "=================================================================="
echo ""
# 자기 자신을 세션 보호 모드로 재실행
nohup setsid bash "$0" "$@" > "${NOHUP_LOG}" 2>&1 &
BG_PID=$!
echo "${BG_PID}" > "${PID_FILE}"
echo " PID : ${BG_PID}"
echo " PID 파일 : ${PID_FILE}"
echo " Launch 로그 : ${NOHUP_LOG}"
echo " 학습 로그 : ${LOG_FILE}"
echo ""
echo " 모니터링:"
echo " tail -f ${LOG_FILE}"
echo ""
echo " 중지 (비상 체크포인트 자동 저장):"
echo " kill \$(cat ${PID_FILE})"
echo ""
echo " 강제 종료:"
echo " kill -9 \$(cat ${PID_FILE})"
echo "=================================================================="
exit 0
fi
# ---- Cleanup on exit --------------------------------------------------------
PREWARM_PID=""
cleanup() {
rm -f "${PID_FILE}" 2>/dev/null || true
if [[ -n "${PREWARM_PID:-}" ]]; then
kill "${PREWARM_PID}" 2>/dev/null || true
fi
}
trap cleanup EXIT
# PID 파일 기록 (tmux/screen 내에서 실행 시에도 PID 추적 가능)
echo "$$" > "${PID_FILE}"
# ---- Pre-flight checks ------------------------------------------------------
if [[ ! -f "${CONFIG}" ]]; then
echo "[ERROR] Config not found: ${CONFIG}"
exit 1
fi
if [[ ! -f "${TRAIN_DATA}" ]]; then
echo "[ERROR] Training data not found: ${TRAIN_DATA}"
exit 1
fi
# GPU 메모리 체크 (3B는 최소 80GB/GPU 권장, B200=192GB → OK)
GPU_MEM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1 || echo "0")
if [[ "$GPU_MEM" -gt 0 && "$GPU_MEM" -lt 80000 ]]; then
echo "[WARN] GPU memory ${GPU_MEM}MB < 80GB. 3B 학습에 부족할 수 있음."
fi
# 중복 프로세스 방지
EXISTING_PID=$(pgrep -f "pretrain.py.*korean_3b" 2>/dev/null | head -1 || true)
if [[ -n "$EXISTING_PID" ]]; then
echo "[ERROR] 이미 3B pretrain 프로세스 실행 중 (PID: ${EXISTING_PID})"
echo " kill ${EXISTING_PID} 로 먼저 종료하세요."
exit 1
fi
# 디스크 여유 확인 (최소 1TB 필요)
AVAIL_KB=$(df /PROJECT 2>/dev/null | awk 'NR==2{print $4}')
if [[ -n "${AVAIL_KB:-}" ]] && [[ "$AVAIL_KB" -lt 1073741824 ]]; then
AVAIL_TB=$(echo "scale=1; $AVAIL_KB / 1073741824" | bc 2>/dev/null || echo "?")
echo "[WARN] /PROJECT 여유 ${AVAIL_TB}TB < 1TB. 체크포인트 저장 공간 부족 가능."
fi
# ---- Resume detection -------------------------------------------------------
RESUME_ARG=""
EXTRA_ARGS="${*:-}"
if [[ ! "${EXTRA_ARGS}" =~ "--resume" ]]; then
# 가장 최근 체크포인트 자동 감지
LATEST_CKPT=$(ls -d "${CKPT_DIR}"/checkpoint-* 2>/dev/null | sort -V | tail -1 || true)
if [[ -n "$LATEST_CKPT" ]]; then
echo "[INFO] 자동 resume 감지: ${LATEST_CKPT}"
RESUME_ARG="--resume ${LATEST_CKPT}"
fi
fi
# ---- Banner ------------------------------------------------------------------
SESSION_TYPE="direct"
[[ -n "${TMUX:-}" ]] && SESSION_TYPE="tmux"
[[ -n "${STY:-}" ]] && SESSION_TYPE="screen"
[[ -n "${_LAUNCH_PROTECTED:-}" ]] && SESSION_TYPE="protected (nohup+setsid)"
echo "=================================================================="
echo " Korean 3B LLM Pre-Training (FP8)"
echo " Run name : ${RUN_NAME}"
echo " Config : ${CONFIG}"
echo " CKPT dir : ${CKPT_DIR}"
echo " Log file : ${LOG_FILE}"
echo " Max steps : ${MAX_STEPS}"
echo " Batch : ${BATCH_SIZE} (local) x ${NPROC} GPU x ${GRAD_ACCUM} accum"
echo " Eff tokens : $((BATCH_SIZE * NPROC * GRAD_ACCUM * 4096)) tokens/step (~1M)"
echo " Total tokens: ~$((MAX_STEPS * BATCH_SIZE * NPROC * GRAD_ACCUM * 4096 / 1000000000))B"
echo " Resume : ${RESUME_ARG:-none (fresh start)}"
echo " Session : ${SESSION_TYPE}"
echo " PID : $$ (file: ${PID_FILE})"
echo " Started : $(date)"
echo "=================================================================="
export PYTHONWARNINGS="ignore::UserWarning:torch.library"
# ---- Pre-warm OS page cache (NUMA-interleaved, non-blocking) ---------------
if [[ -f "${TRAIN_DATA}" ]]; then
echo "[INFO] Pre-warming page cache for ${TRAIN_DATA} (NUMA interleaved)..."
numactl --interleave=all dd if="${TRAIN_DATA}" of=/dev/null bs=16M 2>/dev/null &
PREWARM_PID=$!
fi
# ---- Launch training ---------------------------------------------------------
# grep 파이프라인 보호:
# 문제: grep -v 가 매칭 라인이 없으면 exit code 1 반환
# 해결: { ... || true; } 래핑으로 파이프라인 exit code를 항상 0으로 보장
# torchrun의 실제 exit code는 PIPESTATUS[0]으로 별도 캡처
numactl --interleave=all \
torchrun \
--nproc_per_node=${NPROC} \
--master_port=${MASTER_PORT} \
train/pretrain.py \
--config "${CONFIG}" \
--train_data "${TRAIN_DATA}" \
--val_data "${VAL_DATA}" \
--checkpoint_dir "${CKPT_DIR}" \
--log_file "${LOG_FILE}" \
--max_steps ${MAX_STEPS} \
--batch_size ${BATCH_SIZE} \
--grad_accum ${GRAD_ACCUM} \
--warmup_steps ${WARMUP_STEPS} \
--seed ${SEED} \
${RESUME_ARG} \
${EXTRA_ARGS} \
2>&1 | { grep -v "UserWarning" \
| grep -v "Warning only once" \
| grep -v "Overriding a previously" \
| grep -v "dispatch key:" \
| grep -v "previous kernel:" \
| grep -v "new kernel:" \
| grep -v "operator: flash_attn" \
| grep -v "registered at /usr/local" \
| grep -v "self.m.impl" \
|| true; }
EXIT_CODE=${PIPESTATUS[0]}
# ---- Exit summary ------------------------------------------------------------
echo ""
echo "=================================================================="
echo " Finished : $(date)"
echo " Exit code : ${EXIT_CODE}"
if [[ ${EXIT_CODE} -eq 0 ]]; then
echo " Status : SUCCESS (학습 완료 또는 graceful shutdown)"
elif [[ ${EXIT_CODE} -eq 143 ]]; then
echo " Status : TERMINATED (SIGTERM — 비상 체크포인트 저장됨)"
elif [[ ${EXIT_CODE} -eq 137 ]]; then
echo " Status : KILLED (SIGKILL — 강제 종료, 체크포인트 미저장)"
elif [[ ${EXIT_CODE} -eq 1 ]]; then
echo " Status : ERROR (${LOG_FILE} 확인 필요)"
else
echo " Status : FAILED (exit code ${EXIT_CODE}, ${LOG_FILE} 확인)"
fi
echo "=================================================================="
exit ${EXIT_CODE}