CLI(Command Line Interface)는 텍스트 명령어로 컴퓨터를 제어하는 인터페이스입니다. GUI에서 마우스 클릭으로 하는 작업을 CLI에서는 명령어 한 줄로 처리합니다. 차이는 자동화 가능성입니다. GUI 작업은 반복할 때마다 사람이 직접 클릭해야 하지만, CLI 명령어는 스크립트로 저장하여 무한히 반복할 수 있습니다. 100개의 모델을 학습시키고, 결과를 정리하고, 서버에 배포하는 과정 — 이 모든 것을 CLI로 자동화할 수 있습니다.
AI/ML 개발에서 CLI 숙련도는 생산성을 결정짓는 핵심 역량입니다. 데이터 전처리 파이프라인을 설계하고, 원격 GPU 서버에서 학습을 실행하고, 결과 로그를 분석하고, 모델을 배포하는 전 과정이 CLI에서 이루어집니다.특히 GPU 서버는 대부분 SSH로만 접근 가능합니다. GUI 없이 서버를 다뤄야 하는 상황에서, CLI 능력이 곧 작업 능력입니다. 또한 ML 실험은 본질적으로 반복적입니다 — 하이퍼파라미터를 바꿔가며 수십, 수백 번 학습을 돌리는 과정을 스크립트로 자동화하면 엄청난 시간을 절약할 수 있습니다.
# GPU 메모리를 가장 많이 사용하는 프로세스 정렬nvidia-smi --query-compute-apps=pid,used_gpu_memory --format=csv,noheader | sort -t',' -k2 -rn# 학습 로그에서 에폭별 최저 loss 추출grep "loss:" training.log | awk '{print $2, $4}' | sort -k2 -n | head -5# CSV 파일의 특정 컬럼 값 분포 확인cut -d',' -f3 data.csv | sort | uniq -c | sort -rn | head -10# Python 파일 줄 수 통계find . -name "*.py" | xargs wc -l | sort -n | tail -10# 여러 서버의 GPU 상태 한번에 확인for host in gpu01 gpu02 gpu03; do echo "=== $host ===" && ssh $host nvidia-smi --query-gpu=index,memory.used --format=csvdone# 디렉터리별 파일 수 카운트find /data/images -type d | while read dir; do echo "$(find "$dir" -maxdepth 1 -type f | wc -l) $dir"done | sort -rn | head -10
SSH 연결이 끊어져도 프로세스가 계속 실행됩니다. 원격 서버에서 학습을 실행할 때 필수입니다.
Copy
# 새 세션 생성tmux new -s training# 학습 실행 후 세션에서 분리 (학습은 계속 실행)# Ctrl+B, D# 세션 목록 확인tmux ls# 세션 다시 연결tmux attach -t training# 세션 종료tmux kill-session -t training
tmux 단축키
동작
Ctrl+B, D
세션 분리 (detach)
Ctrl+B, C
새 창(window) 생성
Ctrl+B, N/P
다음/이전 창 이동
Ctrl+B, %
수평 분할 (pane)
Ctrl+B, "
수직 분할 (pane)
Ctrl+B, 방향키
pane 간 이동
Ctrl+B, [
스크롤 모드 (q로 종료)
SSH 세션에서 직접 python train.py를 실행하면, SSH 연결이 끊어질 때 학습도 함께 종료됩니다 (SIGHUP 시그널). 반드시 tmux 또는 nohup을 사용하세요.
# ~/.bashrc 또는 ~/.zshrc에 추가# GPU 특정 번호로 학습 실행train() { local gpu=${1:-0} local script=${2:-train.py} shift 2 CUDA_VISIBLE_DEVICES=$gpu python $script "$@"}# 사용: train 0 train.py --lr 0.001# 프로젝트 디렉터리로 이동 + 가상환경 활성화proj() { cd ~/projects/$1 && source .venv/bin/activate}# 사용: proj my-experiment# 포트 사용 중인 프로세스 확인port() { lsof -i :$1}# 사용: port 8888# SSH 터널 한번에 열기tunnel() { local port=${1:-8888} local host=${2:-gpu-server} ssh -fNL $port:localhost:$port $host echo "터널 열림: localhost:$port → $host:$port"}# 사용: tunnel 8888 gpu-server
#!/bin/bash# for 문: 하이퍼파라미터 그리드 서치for lr in 0.1 0.01 0.001 0.0001; do for batch_size in 16 32 64; do echo "학습: lr=$lr, batch_size=$batch_size" python train.py --lr $lr --batch-size $batch_size \ --name "exp_lr${lr}_bs${batch_size}" donedone# while 문: GPU 메모리가 비워질 때까지 대기while true; do FREE_MEM=$(nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits -i 0) if [ "$FREE_MEM" -gt 20000 ]; then echo "GPU 메모리 확보됨 (${FREE_MEM}MB) — 학습 시작" break fi echo "GPU 메모리 부족 (${FREE_MEM}MB) — 30초 후 재확인" sleep 30donepython train.py
#!/bin/bashset -euo pipefail# -e: 에러 시 즉시 종료# -u: 미정의 변수 사용 시 에러# -o pipefail: 파이프라인 중 하나라도 실패하면 에러# 종료 코드 확인 ($? = 직전 명령의 종료 코드)python train.pyif [ $? -eq 0 ]; then echo "학습 성공!" python evaluate.pyelse echo "학습 실패! 종료 코드: $?" exit 1fi# trap: 스크립트 종료 시 정리 작업cleanup() { echo "정리 작업 실행..." rm -rf /tmp/training_cache_$$ echo "완료"}trap cleanup EXIT
#!/bin/bash# run_experiments.sh — 하이퍼파라미터 스윕 자동화set -euo pipefailEXPERIMENT_NAME="resnet50_sweep"TIMESTAMP=$(date +%Y%m%d_%H%M%S)LOG_DIR="logs/${EXPERIMENT_NAME}_${TIMESTAMP}"mkdir -p "$LOG_DIR"LEARNING_RATES=(0.01 0.001 0.0001)BATCH_SIZES=(32 64 128)for lr in "${LEARNING_RATES[@]}"; do for bs in "${BATCH_SIZES[@]}"; do RUN_NAME="${EXPERIMENT_NAME}_lr${lr}_bs${bs}" LOG_FILE="${LOG_DIR}/${RUN_NAME}.log" echo "[$(date)] 시작: $RUN_NAME" CUDA_VISIBLE_DEVICES=0 python train.py \ --name "$RUN_NAME" \ --lr "$lr" \ --batch-size "$bs" \ --epochs 50 \ > "$LOG_FILE" 2>&1 if [ $? -eq 0 ]; then echo "[$(date)] 성공: $RUN_NAME" else echo "[$(date)] 실패: $RUN_NAME (로그: $LOG_FILE)" fi donedoneecho "모든 실험 완료. 로그: $LOG_DIR"
SSH 접속이 자꾸 끊겨요
~/.ssh/config에 ServerAliveInterval 60과 ServerAliveCountMax 3을 추가하세요. 60초마다 keepalive 패킷을 보내 연결을 유지합니다. 서버 측에서도 /etc/ssh/sshd_config의 ClientAliveInterval과 ClientAliveCountMax를 확인하세요. 근본적으로는 tmux를 사용하면 SSH 연결이 끊어져도 작업이 계속 유지됩니다.
파이프라인에서 어디서 에러가 나는지 모르겠어요
set -o pipefail을 사용하면 파이프라인 중 하나라도 실패 시 전체가 실패합니다. 디버깅 시에는 파이프를 끊고 단계별로 실행하거나, tee로 중간 결과를 파일에 저장하세요. 예: cmd1 | tee /tmp/step1.txt | cmd2 | tee /tmp/step2.txt | cmd3. $PIPESTATUS 배열로 각 단계의 종료 코드를 확인할 수도 있습니다.
nohup과 tmux 중 어떤 걸 써야 하나요?
tmux를 권장합니다. nohup은 단순히 SIGHUP을 무시할 뿐이지만, tmux는 세션을 완전히 분리하여 나중에 다시 연결할 수 있습니다. 학습 진행 상황을 확인하고, 로그를 스크롤하고, 여러 창을 관리할 수 있습니다. nohup은 일회성 간단한 작업에만 사용하세요.
셸 스크립트에서 set -euo pipefail은 왜 쓰나요?
-e는 에러 발생 시 즉시 종료하여 잘못된 상태로 계속 진행하는 것을 방지합니다. -u는 정의되지 않은 변수 사용을 에러로 처리하여 오타를 잡아냅니다. -o pipefail은 파이프라인의 중간 실패를 감지합니다. 세 옵션을 함께 사용하면 스크립트의 안정성이 크게 향상됩니다.
cron 작업이 실행되지 않아요
가장 흔한 원인: (1) 환경변수 미로드 — cron은 minimal 환경에서 실행되므로 PATH, CUDA_HOME 등이 없습니다. 스크립트 안에서 source ~/.bashrc를 추가하세요. (2) 권한 문제 — 실행 파일에 chmod +x가 되어있는지 확인하세요. (3) 절대 경로 미사용 — python 대신 /usr/bin/python3 또는 /home/user/.venv/bin/python을 사용하세요. cron 로그는 /var/log/cron 또는 journalctl -u cron으로 확인합니다.
원격 서버의 Jupyter를 로컬에서 열고 싶어요
SSH 터널링을 사용합니다. ssh -L 8888:localhost:8888 user@gpu-server로 접속한 후, 서버에서 jupyter lab --no-browser --port 8888을 실행하세요. 로컬 브라우저에서 http://localhost:8888로 접속하면 원격 Jupyter에 연결됩니다. ~/.ssh/config에 LocalForward 8888 localhost:8888을 추가하면 매번 -L 옵션을 쓰지 않아도 됩니다.