[LLM] Experimenting with AutoTrain vs SFTTrainer

대규모 언어 모델을 Fine tuning할 때 HuggingFace에서는 AutoTrainSFTTrainer 두 가지를 사용 할 수 있다. AutoTrain은 CLI 기반으로 손쉽게 학습을 시작할 수 있는 도구이고, SFTTrainer는 파이썬 코드 기반으로 세밀한 제어가 가능한 도구이다.

AutoTrain은 학습 환경을 자동으로 구성해주기 때문에 초보자나 빠르게 결과를 확인하고 싶은 경우에 적합하다. 몇 가지 옵션만 주면 모델과 데이터 로딩, 학습 파라미터 세팅, 저장 경로까지 모두 알아서 설정해주는 것이 장점이다. 하지만 CLI 환경이라서 옵션의 유연성이 제한되고, 체크포인트 재개 기능이 옵티마이저 상태까지 복원되지 않는 제약이 있는 것이 단점이다.

SFTTrainer는 HuggingFace의 trl 라이브러리에서 제공하는 학습기반 클래스이다.
Trainer를 확장하여 Supervised Fine-Tuning에 맞게 최적화되어 있으며, resume_from_checkpoint 옵션을 통해 학습을 완전히 재개할 수 있는 것이 장점이다.
Data preprocessing, Training loop, Logging, Callback 등을 세밀하게 커스터마이징할 수 있기 때문에 연구 목적이나 장기 학습에서 강력한 도구이다. 다만 AutoTrain에 비해 설정을 직접 챙겨야 하므로 알아야 할 것이 많다.

AutoTrain의 CLI 실행 예시는 다음과 같다.

autotrain llm \
--train \
--model {base model name} \
--project-name {fine tuning model name} \
--data-path data/ \
--text-column text \
--lr 2e-4 \
--batch-size 1 \
--epochs 1 \
--block-size 512 \
--warmup-ratio 0.1 \
--lora-r 16 \
--lora-alpha 32 \
--lora-dropout 0.05 \
--weight-decay 0.01 \
--gradient-accumulation 8 \
--mixed-precision fp16 \
--peft \
--quantization int4 \
--trainer sft

위 명령어는 SFTTrainer 코드로 다음과 같이 변환할 수 있다.

from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments
from trl import SFTTrainer
from peft import LoraConfig, get_peft_model
from datasets import load_dataset

base_model = f"{base_model_name}"
finetuned_model = f"{fine_tuning_model_name}"

dataset = load_dataset("csv", data_files="data/train.csv")
train_dataset = dataset["train"]

tokenizer = AutoTokenizer.from_pretrained(base_model, use_fast=True)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(
    base_model,
    load_in_4bit=True,
    device_map="auto"
)

training_args = TrainingArguments(
    output_dir=finetuned_model,
    num_train_epochs=1,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    weight_decay=0.01,
    warmup_ratio=0.1,
    logging_dir=f"{finetuned_model}/logs",
    logging_steps=50,
    save_strategy="steps",
    save_steps=200,
    save_total_limit=2,
    fp16=True,
    report_to="none",
)

peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=["q_proj", "k_proj", "v_proj"]
)

model = get_peft_model(model, peft_config)

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=train_dataset,
    max_seq_length=512,
    packing=False,
    args=training_args,
)

trainer.train()

trainer.save_model(finetuned_model)
tokenizer.save_pretrained(finetuned_model)

이와 같이 AutoTrain CLI는 빠른 실험에 적합하고, SFTTrainer는 완전한 재개와 세밀한 제어가 필요한 상황에 적합하다. 상황에 따라 두 가지 도구를 병행하여 사용하는 것이 가장 효율적이다.

[LLM] Fixing AutoTrain’s use_flash_attention_2 TypeError

LLM 학습 중 아래처럼 TypeError가 터지면서 학습이 바로 종료되는 경우가 있다.

ERROR | 2025-08-27 13:47:10 | autotrain.trainers.common:wrapper:215 - train has failed due to an exception:
Traceback (most recent call last):
  ...
  File "/usr/local/lib/python3.12/dist-packages/transformers/modeling_utils.py", line 4999, in from_pretrained
    model = cls(config, *model_args, **model_kwargs)
TypeError: Exaone4ForCausalLM.__init__() got an unexpected keyword argument 'use_flash_attention_2'

오류가 발생하는 원인은 autotrain이 동작할때 내부적으로 AutoModelForCausalLM.from_pretrained() 호출 시 항상 use_flash_attention_2=config.use_flash_attention_2를 전달하는데 실제 원본 모델 클래스는 이 인자를 받지 않아 TypeError 발생하는 현상이다.

이 문제를 간단히 해결하는 방법은 autotrain 패키지의 실제 파일 위치(utils.py)에서 해당 인자 전달 한 줄을 주석 처리(또는 삭제)하는 것이다.

사용자의 환경에 따라 위치가 조금 다를 수 있지만 대략 적으로 autotrain utils.py 위치는 다음과 같다.

/usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py

위치를 확인했으면 다음과 같이 먼저 백업을 해두고 해당라인을 주석 처리 한다.

cp /usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py \
   /usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py.bak

sed -i 's/use_flash_attention_2=config.use_flash_attention_2/# use_flash_attention_2=config.use_flash_attention_2/' \
/usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py

grep -n "use_flash_attention_2" /usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py

# 848:            - use_flash_attention_2 (bool): Flag to use flash attention 2.
# 949:            # use_flash_attention_2=config.use_flash_attention_2,
# 957:            # use_flash_attention_2=config.use_flash_attention_2,

변경 사항이 제대로 반영되었는지 확인한 뒤 다시 실행하면, 오류가 해결된 것을 확인할 수 있다.

[Azure Function]Ensuring Azure Function App is Running Before Deployment

프로덕션 환경에서 Azure Function App을 배포할 때, 앱의 상태를 확인하는 것은 매우 중요하다. 만약 Azure Function App의 상태가 ‘Runing’이 아니라면 배포가 되지 않는다.

Azure DevOps와 Azure CLI를 사용해 Function App의 상태를 확인하고, 앱이 실행 중이지 않다면 시작해야한다. 이 간단한 스크립트를 통해 배포 오류를 방지하고 원활한 운영을 보장할 수 있습니다.

Azure DevOps 작업 구성

- task: AzureCLI@2
  inputs:
    azureSubscription: ' Your Azure subscription details' 
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      state=$(az functionapp show --resource-group $(resourceGroupName) --name $(functionAppName) --slot second --query "state" -o tsv)
      if [ "$state" != "Running" ]; then
        echo "Function App is not running. Starting the Function App..."
        az functionapp start --resource-group $(resourceGroupName) --name $(functionAppName) --slot second
      else
        echo "Function App is already running."
      fi

[Ubuntu]Getting an SSL/TLS Certification with Let’s Encrypt for free

네트워크를 사용하는 서비스를 할 때, SSL(Secure Sockets Layer)/TLS(Transport Layer Security) 인증서를 등록해서 https 통신을 할 수 있도록 해야 안전한 서비스를 할 수 있다. 그런데 일반적으로는 SSL 인증서를 발급 받으려면, SSL 발급해주는 사이트에서 일정 비용을 내고 인증서를 구입을 해야 한다.

인증서 비용이 부담이 없다면 그냥 구입해서 사용하는 것이 편하긴 하지만, 그냥 간단히 Study를 목적으로 잠깐 사용할 것이라면 비용을 지불하기 조금 아까 울 수 있다.

Let’s Encrypt라는 인증 기관에서는 무료로 사용할 수 있는 SSL 인증서를 제공한다. 유효기간이 90일 정도라 주기적으로 갱신해야 겠지만 Study가 목적이라면 충분한 기간이다.

Let’s Encrypt 인증서를 발급 받으려면, 먼저 인증서를 설치할 server와 public domain이 준비되어 있어야 한다.

준비가 다 되었다면, 먼저 letsencrypt을 설치한다.

sudo apt-get update
sudo apt-get install letsencrypt

설치가 완료되었으면 아래 명령을 실행하면, DNS에 등록할 TXT 레코드를 확인 할 수 있다.

sudo certbot certonly --manual -d [yourdomain.com] --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory

TXT 정보를 DNS에 등록하고 발급을 계속 진행하면 /etc/letsencrypt/achive 에 인증서가 발급 된 것을 확인 할 수 있다.

/etc/letsencrypt/achive/<yourdomain.com>
├── cert.pem  // your domain 인증서 public key
├── chain.pem // Intermediate 인증서
├── fullchain.pem // cert.pem + chain.pem
└── privkey.pem // cert.pem 비밀 키

총 4개의 파일이 생성되어 있다. 이 인증서 파일을 사용하여 서버에 SSL 인증서를 설정하여 사용하면 된다.

Port Foward to Windows from WSL(Windows Subsystem for Linux) Ubuntu

WSL에서 만든 서비스가 public한 외부 network에 공개되기 위해서는 Windows network interface에서 WSL network interface로 port forwarding을 설정해야 한다.

port forwarding을 설정을 하기 위해서는 먼저 windows에서 설정하려는 port number를 이미 사용 중인지 확인해야 한다.

netstat -ano | findstr $port

TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       1480
TCP    0.0.0.0:19812          0.0.0.0:0              LISTENING       11280
...... 생략 ......

만약, 사용하려는 port가 이미 사용 중이라면 다른 port를 사용하도록 변경하던가, 어떤 Porcess에서 사용 중인지 확인해서 사용 해제를 해야 한다.

사용 가능한 port 라면, windows에서 서비스에 사용할 ip와 port를 wsl ip와 port에 연결해준다.

netsh interface portproxy add v4tov4 listenport={$listen port} listenaddress={$windows ip adress} connectport={$connect port} connectaddress={$wsl ip address}

연결이 되었다면, 다음과 같이 확인 할 수 있다.

netsh interface portproxy show v4tov4

Listen on ipv4:             Connect to ipv4:
Address         Port        Address         Port
--------------- ----------  --------------- ----------
0.0.0.0         80          172.30.106.233  80

[Databricks] Get configuration profile

Databricks CLI를 사용할 때, 인증을 갱신을 해야 할 때가 있다.

databricks configure --host "<databricks host https://>" --token

PAT(Personal Access Token)을 입력하라고 나오는데 Databricks workspace에서 발급한 user token을 입력해 주면 된다.

Input Personal aceess token

인증이 갱신 되었다면, Databricks CLI가 정상 동작 할 것이다.

databricks secrets list-scopes

[Databricks] Using Azure Key Vault

Databricks에서 작업을 할 때 외부 데이터 소스를 가져와야 할 때가 있다. 이럴 때 외부 데이터 에 접근하기 위해서 연결 정보( ID, Password 등)가 필요하게 되는데, 이 정보는 작업자 외에 유출되면 안되는 경우가 있다.

이런 유출에 민감한 정보를 다뤄야 할 때, Secret manager 서비스를 사용하면 좋고, 이번 포스트에서는 Azure에서 제공하는 Key Vault 서비스를 사용해서 Databricks에서 민감정보를 다루는 방법을 알아보겠다.

먼저, Azure Key Vault 리소스를 생성하고 Secret에 연결 정보(SQL Connection string)를 등록한다.

이제 Databricks에서 Key Vault에 등록된 정보를 사용하기 위해서 secret scope를 만든다.

databricks secrets create-scope --scope databricks-secrets01 --scope-backend-type AZURE_KEYVAULT --resource-id /subscriptions/<subscription Id>/resourceGroups/<resource group name>/providers/Microsoft.KeyVault/vaults/databricks-secrets01 --dns-name https://databricks-secrets01.vault.azure.net/

잘 만들어 졌는지 아래 scope list 명령어로 확인해 본다.

databricks secrets list-scopes
Scope                 Backend         KeyVault URL
--------------------  --------------  ---------------------------------------------
databricks-secrets01  AZURE_KEYVAULT  https://databricks-secrets01.vault.azure.net/

이제 Databricks에서 Key Vault에 등록된 Secret 값을 사용하기 위한 준비 작업은 끝났다.
Workspace에서 notebook을 하나 만들고 아래 script 처럼 secret 값을 불러와 사용해보자.

jdbcUrl = dbutils.secrets.get(scope="databricks-secrets01", key="databricks-jdbc-url")
connectionProperties = {
  "user": dbutils.secrets.get(scope="databricks-secrets01", key="databricks-user"),
  "password": dbutils.secrets.get(scope="databricks-secrets01", key="databricks-password")
}

위 방식대로 불러온 값들은 변수에 저장되지만 databricks 내에서는 확인 할 수 없다.

값을 확인하기 위해서 print를 해보면 “REDACTED”라고 출력되는 것을 확인 할 수 있다.
하지만 해당 변수를 이용해서 실행시켜보면 정상 동작한다.

df = spark.read.jdbc(url=jdbcUrl, table='sys.objects', properties=connectionProperties)
df.show(2)

+----------+---------+------------+---------+----------------+----+------------+--------------------+--------------------+-------------+------------+-------------------+
|      name|object_id|principal_id|schema_id|parent_object_id|type|   type_desc|         create_date|         modify_date|is_ms_shipped|is_published|is_schema_published|
+----------+---------+------------+---------+----------------+----+------------+--------------------+--------------------+-------------+------------+-------------------+
| sysrscols|        3|        null|        4|               0|  S |SYSTEM_TABLE|2023-03-30 17:00:...|2023-03-30 17:00:...|         true|       false|              false|
|sysrowsets|        5|        null|        4|               0|  S |SYSTEM_TABLE|2009-04-13 12:59:...|2023-03-30 17:00:...|         true|       false|              false|
+----------+---------+------------+---------+----------------+----+------------+--------------------+--------------------+-------------+------------+-------------------+

이렇게 Azure Key Vault를 이용하면 Databricks에서 민감정보를 다뤄야 할 때, 실제 값을 보여주지 않으면서 script는 정상 동작 시킬 수 있다.

마지막으로, 사용하는 key vault에 있는 민감정보 값들이 더 이상 필요 없어진다면 해당 key vault에 접근 할 수 없도록 하는 것이 바람직하다. delete scope로 더 이상 사용 할 수 없도록 scope를 제거 할 수 있다.

 databricks secrets delete-scope --scope databricks-secrets01

WSL(Windows Subsystem for Linux) Ubuntu CRON Job 사용하기

CRON(Command Run On)이란, Unix System에서 제공하는 Job scheduler를 말한다. CRON은 Shell 명령어들이 주어진 일정에 주기적으로 실행하기 위해서 crontab(CRON Table)이란 것을 참조하고, crontab에는 실행시킬 Job 목록과 일정이 기록되어 있다.

CRON Table

사실, Windows에서도 Task scheduler라는 기능을 사용하면 Scheduling을 얼마든지 할 수 있긴 하다. 하지만 간혹 이미 Linux shell script로 제공되는 경우 등 Linux 환경에서의 작업이 더 유리한 상황인 경우에는 해당 script를 Windows 용으로 변경해야 하는 번거로움이 있다.

이럴때 WSL을 이용하면 좋다. WSL은 Ubuntu환경을 거의 대부분 제공하기 때문에 별도의 Linux 환경을 구성하지 않아도 주어진 Shell Script를 그대로 이용하면 Scheduling이 가능하다.

예를 들어, Linux System에 대한 Health check를 주기적으로 매시간마다 기록해야하는 상황이라고 가정해보자. Linux System이기 때문에 Bash shell script로 작업하는 것이 유리 할 것이다.

먼저, CRON Scehduler가 Backgroud에서 동작하기 위해서 daemon service를 설치 및 실행해야한다.

# Install CRON package
$ sudo apt-get udpate
$ sudo apt-get install cron

# CRON Service start
$ sudo service cron start

CRON이 정상적으로 실행되었다면, 다음으로 Health check를 위한 Script를 준비해야한다. Script는 직접 작성하여도 좋지만, 미리 잘 만들어져서 공개된 Open Source Script를 사용하면 쉽게 작업 할 수 있다.

WSL(Ubuntu-18.04)를 실행시켜 미리 잘 준비된 Script를 가져와 보자.
(Linux health check script는 여기를 참조하였다. Script code는 Apache 2.0 License로 배포되었다.)

$ wget https://tecmint.com/wp-content/scripts/tecmint_monitor.sh

Shell Script가 준비가 되었다면, WSL에서 CRON에 등록하여 사용하기만 하면 된다.
다음 명령어를 실행하면 CRON Table을 편집할 수 있는 편집창이 나온다.

$ crontab -e

아래 Command Code를 그림과 같이 편집창에 실행될 Schedule과 함께 넣어준다.

# monitor script 실행 결과를 result text 파일에 출력한다.
0 * * * * /home/newth/CRON/tecmint_monitor.sh >> /home/newth/CRON/result.txt
CRON Table 편집창

저장하고 나면 CRON Table에 Job Schedule이 등록된 것이다. 등록된 Schedule로 인해서 CRON은 매 시간 정각마다 Command에 입력된 절대경로 shell script를 실행시킬 것이다.

정상적으로 실행 되는지 확인해 보고 싶다면 다음과 같이 syslog를 확인해 보면된다.

$ tail -f /var/log/syslog

CRON Table에 등록한 대로 동작한 결과를 확인 해 볼 수 있다.

$ /var/log$ tail -f syslog

Feb 14 22:45:28 DESKTOP-LM4ALRO crontab[331]: (newth) BEGIN EDIT (newth) --CRON Table 편집 
Feb 14 22:45:33 DESKTOP-LM4ALRO crontab[331]: (newth) END EDIT (newth) --CRON Table 편집 종료
Feb 14 22:45:35 DESKTOP-LM4ALRO crontab[342]: (newth) LIST (newth) --CRON Table 리스트 확인
... 생 략 ...
Feb 14 23:00:00 DESKTOP-LM4ALRO CRON[433]: (newth) CMD (/home/newth/CRON/tecmint_monitor.sh | cat > /home/newth/CRON/result.txt) --CRON Table 실행 Log


/bin/sh^M: bad interpreter 해결 방법

Linux에서 Shell Script를 실행 할때, “/bin/sh^M: bad interpreter”라는 오류가 발생할 때가 있다. 그리고 오류 내용은 입력하지도 않은 ‘^M’이란 문자때문에 오류가 발생한 것으로 나온다.

작성한 Shell Script를 열어 내용을 아무리 살펴보아도 ‘^M’이 입력된 부분이 있다거나 잘 못 작성된 부분이 있는 것을 찾기 힘들어 엄청 열 받을 것이다.

먼저, 이 오류에 대부분의 원인에 대해서 말하자면 Windows에서 작성한 Script를 Linux에서 바로 실행 하려고 하면 나타나는 현상인데, Windows와 Linux의 개행문자를 표현 하는 방식이 다르기 때문이다.

개행문자란 무엇인가?

개행문자(혹은 Enter)는 대부분의 편집 툴에서 편집 작업시 눈에 보이지는 않지만 줄바꿈이 있을 때마다 자동으로 입력되는 문자이다. 그리고 개행문자는 앞서 언급했듯이 운영체제 별로 표현법이 다르다.

Windows에서는 CRLF(\r\n)으로 표현하는데 그 의미는 다음과 같다.

CR(Carriage Return, \r)은 새로운 행을 추가하고 LF(Line Feed, \n)는 시작위치로 돌아간다는 의미이다.

반면에, Linux에서는 LF(\n)으로만 표현하도록 되어 있다.

때문에 Windows에서 작성한 문서를 Linux에서 보면 CR이 개행문자로 인식되지 못하고 ‘^M’으로 인식 되어 버린다. 그리고 이런한 이유로 오류가 발생하게 된다.

그럼 어떻게 보이지도 않는 “^M” 문자를 처리해야 하는가?

해결법은 간단하다. VI 편집기를 사용할때 옵션 [-b]를 다음과 같이 사용하여 실행하면 바이너리 모드로 문서를 볼 수 있는데 확인해 보면 모든 줄 끝에 ‘^M’문자가 들어간 것을 확인 할 수 있다.

$ vi -b [수정 할 문서 이름]
Windows에서 작성한 문서를 Linux에서 바이너리 모드로 오픈한 화면

모든 줄마다 붙어있는 ‘^M’ 문자를 모두 삭제해 하고 다시 실행해보면 “/bin/sh^M: bad interpreter” 오류는 해결 될 것이다.

WSL(Windows Subsystem for Linux) Ubuntu GUI 환경 구성

WSL에는 기본적으로 GUI 환경을 제공하지 않는다. 대부분의 경우 터미널로 출력되는 결과물만으로도 작업하는데 별다른 문제가 없지만 간혹 GUI가 꼭 필할 때도 있다.

리눅스의 GUI환경(X-window)은 처음부터 원격실행을 고려하여 Server-Client 구조로 분리하여 설계되었다. 이러한 이유로 GUI를 제공하지 않는 WSL에서도 X-window Server-Client통신에 사용되는 X11이라는 프로토콜을 이용하면 원격으로 GUI환경을 사용할 수 있다.

X-Window 기본개념

원격지가 X-Client가 되고 원격지의 화면을 받는 곳이 X-Server가 된다. 원격지의 실행되는 GUI 프로그램이 X-Client를 통해서 X-Server에게 화면정보를 요청보내면 X-Server는 요청온 정보를 받아 화면을 그려준다.

WSL GUI 환경을 구성하기 위해서는 첫번째로 Windows 10에 X-Window Server로 동작할 Xming 서버를 설치해야 한다.

설치를 완료하고 Xming을 실행하면 시스템 트레이에서 다음과 같이 아이콘을 확인 할 수 있다.

Windows 10 System Tray

다음으로 WSL에 X-Client를 구성해야 한다. 먼저, Machine ID를 생성한다.

$ sudo systemd-machine-id-setup
$ sudo dbus-uuidgen --ensure

ID가 잘 만들어 졌는지 확인해보자.

$ cat /etc/machine-id

잘 만들어 졌다면, 이제 X-Window 패키지와 구성 요소들을 설치한다.

$ sudo apt-get install x11-apps xfonts-base xfonts-100dpi xfonts-75dpi xfonts-cyrillic

마지막으로 GUI 프로그램이 실행될때 그래픽을 출력할 수 있도록 디스플레이 환경변수를 다음과 같이 “.bashrc”에 설정 한다.

$ export DISPLAY=:0
$ source ~/.bashrc

이제 모든 준비가 되었다. WSL GUI 환경이 잘 구성 되었는지 확인하기 위해서 다음과 같이 WSL Shell에 다음과 같이 명령을 실행해 본다.

$ xeyes

잘 구성 되었다면, 다음과 같이 웃기게 생긴 눈달린 창이 나와서 마우스를 따라서 시선을 움직일 것이다.

WSL Ubuntu GUI 환경 구성

이제, WSL에서도 GUI 프로그램을 사용해 보자.