#!/usr/bin/env bash

set_n_least_used_CUDA_VISIBLE_DEVICES() {
    local n=${1:-"9999"}
    echo "GPU Memory Usage:"
    local FIRST_N_GPU_IDS=$(nvidia-smi --query-gpu=memory.used --format=csv |
        tail -n +2 |
        nl -v 0 |
        tee /dev/tty |
        sort -g -k 2 |
        awk '{print $1}' |
        head -n $n)
    export CUDA_VISIBLE_DEVICES=$(echo $FIRST_N_GPU_IDS | sed 's/ /,/g')
    echo "Now CUDA_VISIBLE_DEVICES is set to:"
    echo "CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES"
}

set_n_least_used_CUDA_VISIBLE_DEVICES 4

set -xu

if [ -z "$SFT_DATASET" ]; then
    echo "Please set \$SFT_DATASET to the path to sft dataset."
    exit 1
fi

if [ -z "$PROMPT_DATASET" ]; then
    echo "Please set \$PROMPT_DATASET to the path to prompts csv."
    exit 1
fi

if [ -z "$PRETRAIN_DATASET" ]; then
    echo "Please set \$PRETRAIN_DATASET to the path to alpaca data."
    exit 1
fi

NUM_RETRY=3
BASE_DIR=$(dirname $(dirname $(realpath $BASH_SOURCE)))
EXAMPLES_DIR=$BASE_DIR/examples
MODELS_DIR=$BASE_DIR/examples/models_config
MODELS=('gpt2' 'bloom' 'opt' 'llama')
STRATEGIES=('ddp' 'colossalai_gemini' 'colossalai_zero2')

export OMP_NUM_THREADS=8

# install requirements
pip install -r $EXAMPLES_DIR/requirements.txt

python $EXAMPLES_DIR/download_model.py --model-dir $MODELS_DIR --config-only

get_pretrain() {
    local model=$1
    if [[ $model == "gpt2" ]]; then
        echo "gpt2"
    elif [[ $model == "bloom" ]]; then
        echo "bigscience/bloom-560m"
    elif [[ $model == "opt" ]]; then
        echo "facebook/opt-350m"
    else
        echo "Unknown model $model"
        exit 1
    fi
}

random_choice() {
    local arr=("$@")
    local len=${#arr[@]}
    local idx=$((RANDOM % len))
    echo ${arr[$idx]}
}

echo "[Test]: testing sft ..."

# FIXME: This is a hack to skip tests that are not working
#  - gpt2-ddp: RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
#  - llama-*: These tests can be passed locally, skipped for long execution time
#  - *-gemini: Gemini plugin does not support `from_pretrained` yet
SKIPPED_TESTS=(
    "gpt2-ddp"
    "llama-ddp"
    "llama-colossalai_gemini"
    "llama-colossalai_zero2"
    "gpt2-colossalai_gemini"
    "opt-colossalai_gemini"
    "bloom-colossalai_gemini"
)

GRAD_CKPTS=('' '--grad_checkpoint')
for lora_rank in '0' '4'; do
    for model in ${MODELS[@]}; do
        strategies=($(shuf -e "${STRATEGIES[@]}"))
        for strategy in ${strategies[@]}; do
            if [[ " ${SKIPPED_TESTS[*]} " =~ " $model-$strategy-$lora_rank " ]]; then
                echo "[Test]: Skipped $model-$strategy-$lora_rank"
                continue
            elif [[ " ${SKIPPED_TESTS[*]} " =~ " $model-$strategy " ]]; then
                echo "[Test]: Skipped $model-$strategy"
                continue
            fi
            pretrain=$(get_pretrain $model)
            pretrain_model=""
            if [[ $lora_rank -gt 0 ]]; then
                pretrain_model="--pretrain $pretrain"
            fi
            grad_ckpt=$(random_choice "${GRAD_CKPTS[@]}")
            for i in $(seq $NUM_RETRY); do
                echo "[Test]: $model-$strategy-$lora_rank, attempt $i"
                torchrun --standalone --nproc_per_node=4 $EXAMPLES_DIR/train_sft.py \
                    $pretrain_model --tokenizer $MODELS_DIR/$model \
                    --model $model --strategy $strategy --lora_rank $lora_rank $grad_ckpt \
                    --dataset $SFT_DATASET --max_datasets_size 8 \
                    --max_epochs 1 --batch_size 1 --accumulation_steps 1 --lr 1e-8 \
                    --save_path $EXAMPLES_DIR/rlhf_models/sft_ckpt_${model}_${lora_rank}
                passed=$?
                if [ $passed -eq 0 ]; then
                    break
                fi
            done
            if [ $passed -ne 0 ]; then
                echo "[Test]: Failed $model-$strategy-$lora_rank"
                exit 1
            fi
        done
    done
done

echo "[Test]: testing reward model ..."

# FIXME: This is a hack to skip tests that are not working
#  - gpt2-ddp: RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
#  - llama-*: These tests can be passed locally, skipped for long execution time
#  - *-gemini: Gemini plugin does not support `from_pretrained` yet
SKIPPED_TESTS=(
    "gpt2-ddp"
    "llama-ddp"
    "llama-colossalai_gemini"
    "llama-colossalai_zero2"
    "gpt2-colossalai_gemini"
    "opt-colossalai_gemini"
    "bloom-colossalai_gemini"
)

LOSS_FNS=('log_sig' 'log_exp')
DATASETS=('Anthropic/hh-rlhf' 'Dahoas/rm-static')
for lora_rank in '0' '4'; do
    for model in ${MODELS[@]}; do
        strategies=($(shuf -e "${STRATEGIES[@]}"))
        for strategy in ${strategies[@]}; do
            if [[ " ${SKIPPED_TESTS[*]} " =~ " $model-$strategy-$lora_rank " ]]; then
                echo "[Test]: Skipped $model-$strategy-$lora_rank"
                continue
            elif [[ " ${SKIPPED_TESTS[*]} " =~ " $model-$strategy " ]]; then
                echo "[Test]: Skipped $model-$strategy"
                continue
            fi
            pretrain=$(get_pretrain $model)
            pretrain_model=""
            if [[ $lora_rank -gt 0 ]]; then
                pretrain_model="--pretrain $pretrain"
            fi
            loss_fn=$(random_choice "${LOSS_FNS[@]}")
            dataset=$(random_choice "${DATASETS[@]}")
            subset=$(if [[ $dataset == "Dahoas/rm-static" ]]; then echo "None"; else echo "harmless-base"; fi)
            for i in $(seq $NUM_RETRY); do
                echo "[Test]: $model-$strategy-$lora_rank, attempt $i"
                torchrun --standalone --nproc_per_node=4 $EXAMPLES_DIR/train_reward_model.py \
                    $pretrain_model --tokenizer $MODELS_DIR/$model \
                    --dataset $dataset --subset $subset --max_datasets_size 8 \
                    --model $model --strategy $strategy --lora_rank $lora_rank \
                    --loss_fn $loss_fn --batch_size 1 --lr 1e-8 \
                    --save_path $EXAMPLES_DIR/rlhf_models/rm_ckpt_${model}_${lora_rank}.pt
                passed=$?
                if [ $passed -eq 0 ]; then
                    break
                fi
            done
            if [ $passed -ne 0 ]; then
                echo "[Test]: Failed to train reward model $model-$strategy-$lora_rank"
                exit 1
            fi
        done
    done
done

echo "[Test]: testing RLHF ..."

# FIXME: This is a hack to skip tests that are not working
#  - gpt2-ddp: RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
#  - llama-*: These tests can be passed locally, skipped for long execution time
#  - *-gemini: Gemini plugin does not support `from_pretrained` yet
SKIPPED_TESTS=(
    "gpt2-ddp"
    "llama-ddp"
    "llama-colossalai_gemini"
    "llama-colossalai_zero2"
    "gpt2-colossalai_gemini"
    "opt-colossalai_gemini"
    "bloom-colossalai_gemini"
)

for model in ${MODELS[@]}; do
    for lora_rank in '0' '4'; do
        strategies=($(shuf -e "${STRATEGIES[@]}"))
        for strategy in ${strategies[@]}; do
            if [[ " ${SKIPPED_TESTS[*]} " =~ " $model-$strategy-$lora_rank " ]]; then
                echo "[Test]: Skipped $model-$strategy-$lora_rank"
                continue
            elif [[ " ${SKIPPED_TESTS[*]} " =~ " $model-$strategy " ]]; then
                echo "[Test]: Skipped $model-$strategy"
                continue
            fi
            rm_pretrain=$(get_pretrain $model)
            rm_pretrain_model=""
            if [[ $lora_rank -gt 0 ]]; then
                rm_pretrain_model="--rm_pretrain $rm_pretrain"
            fi
            for i in $(seq $NUM_RETRY); do
                echo "[Test]: $model-$strategy-$lora_rank, attempt $i"
                torchrun --standalone --nproc_per_node=4 $EXAMPLES_DIR/train_prompts.py \
                    --prompt_dataset $PROMPT_DATASET --pretrain_dataset $PRETRAIN_DATASET --max_datasets_size 32 \
                    --strategy $strategy --model $model --tokenizer $MODELS_DIR/$model \
                    --num_episodes 1 --num_collect_steps 1 --num_update_steps 1 --lr 1e-8 \
                    --experience_batch_size 2 --train_batch_size 1 --lora_rank $lora_rank \
                    --pretrain $EXAMPLES_DIR/rlhf_models/sft_ckpt_${model}_${lora_rank} \
                    $rm_pretrain_model --rm_path $EXAMPLES_DIR/rlhf_models/rm_ckpt_${model}_${lora_rank}.pt \
                    --save_path $EXAMPLES_DIR/rlhf_models/actor_checkpoint_prompts.pt
                passed=$?
                if [ $passed -eq 0 ]; then
                    break
                fi
            done
            if [ $passed -ne 0 ]; then
                echo "[Test]: Failed to train RLHF $model-$strategy-$lora_rank"
                exit 1
            fi
        done
        rm -rf $EXAMPLES_DIR/rlhf_models/sft_ckpt_${model}_${lora_rank}
        rm $EXAMPLES_DIR/rlhf_models/rm_ckpt_${model}_${lora_rank}.pt
    done
done
rm $EXAMPLES_DIR/rlhf_models/actor_checkpoint_prompts.pt
