히스토리

  • [2026-02-12 Thu 17:30] 업데이트

HomeAgent ML 파이프라인 도구세트 운영전략

NixOS GPU 클러스터에서 ML 파인튜닝 파이프라인을 운영하기 위한 도구세트 결정 과정. 알고리즘이 아닌 운영 인프라 관점 — 도구 선택, 에이전트 협업, 상태 모니터링에 집중한다.

배경: 왜 이 파이프라인이 필요한가

HomeAgent는 엣지 NPU(Hailo-8/10H)에서 동작하는 Constitutional AI 에이전트다. 클라우드 API 호출 없이 오프라인에서 자율 판단해야 하므로, 로컬 GPU 클러스터에서 파인튜닝 → 양자화 → 엣지 배포하는 파이프라인이 필요하다.

파인튜닝 파이프라인 (gpu-03, 6단계 ~1시간):
 
데이터 준비 → QLoRA 학습 → LoRA 병합 → 평가 → ONNX 변환 → HEF 양자화
 (CPU ~5분)  (GPU ~30min)  (GPU ~5분) (GPU ~10분)  (CPU/GPU)  (CPU ~10분)

                                                        ↓ SCP / NFS
                                                  RPi5 + Hailo-8 추론

Phase 1: Qwen3-0.6B × 2개 (Intent + Summary), Hailo-8 Phase 2: Qwen3-1.5B 통합 모델, Hailo-10H

현재 인프라 상태 (2026-02-09)

구성요소상태위치비고
파인튜닝 Python 환경tuning.nix 완료 (17패키지)gpu-03NixOS 25.11 네이티브
QLoRA 검증test-qlora.py 성공gpu-03Qwen3-0.6B, ~4GB VRAM
wandb설치됨 (미연결)gpu-03 tuning.nixreport_to=“none” 상태
Temporal Server 1.29.3가동중 (Docker)storage-01:7233helloworld 검증 완료
Temporal UI 2.45.0가동중 (Docker)storage-01:8233웹 모니터링
Doltgres 0.54.10가동중 (Docker)storage-01:5434Git-like 버전관리 PostgreSQL
NFS storage/models마운트됨 (10G)storage-01 → gpu-03모델 아티팩트
이슈 트래킹br (JSONL + git)로컬에이전트 협업용

핵심: 도구는 이미 가동중인데 배선이 안 된 상태. Temporal과 Doltgres는 원래 Tuya IoT 알림톡 브릿지용으로 배포됐지만, ML 파이프라인에도 재활용할 수 있다.

풀어야 할 3가지 질문

  1. 학습 실험 추적 — wandb (SaaS) vs MLflow (self-host)
  2. 파이프라인 오케스트레이션 — Temporal vs 수동 스크립트
  3. 데이터셋/모델 버전 관리 — Doltgres vs DVC vs git-lfs

Q1 확정: 실험 트래킹 → wandb SaaS

문제

파인튜닝을 반복하면 하이퍼파라미터, loss 곡선, GPU 사용률을 비교해야 한다. “v3가 왜 v2보다 나은가?”를 나중에 재현할 수 있어야 한다.

비교

기준wandb SaaSMLflow (NixOS 셀프호스팅)
설정 비용report_to”wandb”= 1줄서비스 구성, 포트, 스토리지
HF transformers 통합네이티브report_to”mlflow”=
대시보드wandb.ai 즉시 사용http://gpu-03:5000 구축 필요
데이터 프라이버시클라우드 의존완전 자체 호스팅
NixOS 패키지tuning.nix에 이미 있음mlflow-server 있으나 버그 보고 (GH#330746)
비용무료 (퍼블릭)무료

결정: wandb SaaS로 시작

근거:

  • tuning.nix에 이미 설치되어 있고, 코드 1줄 변경으로 즉시 활성화
  • Phase 1 데이터(홈 Intent JSON)는 민감하지 않음
  • 노트북에서 wandb.ai 접속만으로 실시간 대시보드

전환 계획:

  • Phase 2 Constitutional 데이터가 민감해지면 → MLflow 셀프호스팅 또는 wandb 셀프호스팅
  • MLflow는 nixpkgs에 패키지가 있으므로 전환 부담 낮음

적용 방법:

# test-qlora.py → train-intent.py 변경점
training_args = TrainingArguments(
    report_to="wandb",           # "none" → "wandb"
    run_name="intent-v1-qlora-r16",
)
# 최초 1회
ssh gpu-03 "wandb login"

Q2 확정: 파이프라인 오케스트레이션 → Temporal Python Worker

문제

6단계 선형 파이프라인을 어떻게 실행하고, 실패를 관리하고, 진행상황을 모니터링하는가. 특히 AI 에이전트(Claude)가 학습을 트리거하고 결과를 확인할 수 있어야 한다.

비교

기준수동 스크립트Temporal
설정 비용0 (ssh + python)Worker 배포 필요
장애 복구처음부터 재실행 (~1시간)중단점 복구 (~10분)
모니터링stdout + wandbTemporal UI + wandb
에이전트 협업ssh 명령 직접 실행API로 워크플로우 트리거/조회
확장성단일 스크립트 한계멀티노드, 분기, 스케줄링
서버 인프라불필요이미 storage-01에 가동중

조사 결과: Temporal Python SDK

  • v1.22.0 (2026-02-03), GA, 프로덕션 검증 완료
  • OpenAI, Descript 등에서 ML 파이프라인에 실사용 중
  • Python 3.10-3.14 지원, async/threaded/multiprocess Activity 모두 지원
  • GPU Task Queue: gpu-03에 Python Worker를 띄우고, GPU Activity를 전용 큐로 라우팅
  • Heartbeat: 장시간 학습(30min+) 중 진행상황 보고 → Worker 죽으면 감지 후 재시도

결정: Temporal Python Worker

근거:

  • Temporal Server가 이미 storage-01에서 가동중 — Worker만 추가하면 됨
  • “순수 스크립트 + 실패 시 처음부터”보다 “Activity 단위 재시도”가 실용적
  • Temporal UI(8233)에서 파이프라인 진행상황을 노트북에서 바로 확인 가능
  • Claude가 Temporal API로 워크플로우 트리거/상태조회 가능 → 에이전트 협업의 핵심
  • Python SDK가 GA이므로 tuning.nix 환경과 자연스럽게 통합

아키텍처:

┌─ storage-01 ─────────────────────────────────────┐
│  Temporal Server (Docker)                         │
│    └─ gRPC: 192.168.165.141:7233                  │
│    └─ UI: http://192.168.165.141:8233             │
│  Doltgres (Docker)                                │
│    └─ PostgreSQL: 192.168.165.141:5434            │
└──────────┬────────────────────────────────────────┘
           │ gRPC (Worker가 Server를 폴링)

┌─ gpu-03 ─────────────────────────────────────────┐
│  Temporal Python Worker                           │
│    └─ TaskQueue: "gpu-training"                   │
│    └─ Activity: prepare_dataset (CPU)             │
│    └─ Activity: train_qlora (GPU, heartbeat)      │
│    └─ Activity: merge_lora (GPU)                  │
│    └─ Activity: evaluate (GPU)                    │
│    └─ Activity: export_onnx (CPU/GPU)             │
│    └─ Activity: quantize_hef (CPU)                │
└──────────────────────────────────────────────────┘

Workflow 예시 (Python):

@workflow.defn
class QLoRAPipeline:
    @workflow.run
    async def run(self, config: PipelineConfig) -> PipelineResult:
        # 1. Doltgres에서 데이터셋 로드
        dataset = await workflow.execute_activity(
            prepare_dataset, config,
            start_to_close_timeout=timedelta(minutes=10),
        )
        # 2. QLoRA 학습 (장시간, heartbeat 필수)
        model = await workflow.execute_activity(
            train_qlora, config,
            task_queue="gpu-training",
            start_to_close_timeout=timedelta(hours=1),
            heartbeat_timeout=timedelta(minutes=5),
            retry_policy=RetryPolicy(maximum_attempts=3),
        )
        # 3~6. 후속 단계...
        return PipelineResult(metrics=metrics, artifact_path=path)

Heartbeat 패턴 (30분 학습):

@activity.defn
async def train_qlora(config: QLoRAConfig) -> TrainResult:
    trainer = setup_trainer(config)
    for epoch in range(config.num_epochs):
        for step, batch in enumerate(trainer.dataloader):
            loss = trainer.train_step(batch)
            if step % 100 == 0:
                activity.heartbeat(f"epoch={epoch}, step={step}, loss={loss:.4f}")
    return TrainResult(model_path=trainer.save())

Q3 확정: 데이터셋 버전 관리 → Doltgres + NFS

문제

학습 데이터셋의 변경 이력을 추적하고, 실험 간 “어떤 샘플이 바뀌었는지”를 알아야 한다. 모델 바이너리(1-2GB)는 별도 관리가 필요하다.

아티팩트별 관리 전략

아티팩트크기관리 도구근거
Intent 데이터셋< 5MB (500+ 행)DoltgresSQL 쿼리 + row-level diff
Summary 데이터셋< 5MB (300+ 행)Doltgres동일
LoRA 어댑터~50MBNFS재생성 가능
병합 모델 (FP16)~1.2GBNFS재생성 가능
ONNX 모델~1.5GBNFS재생성 가능
HEF 파일~수 MBhomeagent-config git최종 산출물

비교: Doltgres vs DVC vs git-lfs

기준DoltgresDVCgit-lfs
데이터 모델SQL 테이블 (관계형)파일 (모든 형식)파일 (모든 형식)
Diff 단위행 단위 (row-level)파일 단위파일 단위
브랜칭SQL로 브랜치/머지Git 브랜치 활용Git 브랜치 활용
쿼리완전한 SQL불가불가
비정형 데이터부적합적합적합
생태계독자적 (성장중)lakeFS에 인수됨 (2025-11)GitHub 네이티브
현재 상태이미 가동중별도 설치 필요별도 설치 필요

조사 결과: Doltgres

  • v0.54.10, Beta (1.0 목표 2026 Q2), PostgreSQL 호환
  • Prolly Tree 스토리지: Git의 Merkle Tree와 유사한 구조로 효율적 diff/merge
  • Python 통합: psycopg2, SQLAlchemy 등 표준 PostgreSQL 드라이버 그대로 사용
  • Flock Safety 사례: Dolt를 ML Feature Store로 프로덕션 사용 (버전 관리 + 브랜치 격리)
  • DVC 대비 핵심 장점: 행 단위 diff, SQL 쿼리, 시간 여행(time-travel) 쿼리

주의사항:

  • CLI 없음 — 모든 버전 관리 조작이 SQL 함수를 통해야 함
  • 파일로 저장하지 않음 — 학습 프레임워크가 JSONL을 기대하면 변환 필요
  • 500-1000행 규모에서는 변환 오버헤드 무시 가능

결정: Doltgres (데이터셋) + NFS (아티팩트)

근거:

  • storage-01에 이미 가동중 — 추가 인프라 불필요
  • 홈 Intent/Summary 데이터셋은 구조화된 JSON → SQL 테이블로 자연스럽게 매핑
  • dolt_diff_training_samples 로 “v1에서 v2로 어떤 샘플이 바뀌었나” 추적 가능
  • DVC는 lakeFS에 인수되어 생태계 불확실 → 신규 도입에 리스크

Doltgres 테이블 설계:

CREATE TABLE intent_samples (
    id SERIAL PRIMARY KEY,
    instruction TEXT NOT NULL,
    input TEXT,
    output TEXT NOT NULL,
    category VARCHAR(50),
    quality_score FLOAT,
    meta JSONB,              -- 실험용 유연 필드 (스키마 변경 없이 확장)
    created_at TIMESTAMP DEFAULT NOW()
);

meta JSONB 컬럼의 역할:

  • 핵심 필드(instruction, output)는 고정 컬럼 → row-level diff의 대상
  • 실험적 필드(context, difficulty, source 등)는 meta 에 → 스키마 변경 없이 빠른 실험
  • 예: INSERT INTO intent_samples (..., meta) VALUES (..., '{"source": "manual", "difficulty": "hard"}')

브랜치/디프/커밋 워크플로우:

-- 실험 브랜치 생성
SELECT dolt_branch('experiment/intent-v2');
SELECT dolt_checkout('experiment/intent-v2');
 
-- 샘플 추가/수정
INSERT INTO intent_samples (instruction, output) VALUES (...);
 
-- 커밋
SELECT dolt_add('-A');
SELECT dolt_commit('-m', 'Add 50 new intent samples for edge cases');
 
-- v1 vs v2 diff (행 단위!)
SELECT diff_type, from_instruction, to_instruction, from_output, to_output
FROM dolt_commit_diff_intent_samples
WHERE from_commit = HASHOF('experiment/intent-v1')
  AND to_commit = HASHOF('experiment/intent-v2');
 
-- 학습 스크립트에서 데이터 로드
-- Python: engine = create_engine("postgresql+psycopg2://...:{port}/ml_datasets/experiment/intent-v2")

학습 스크립트에서 Doltgres → JSONL 변환:

import pandas as pd
from sqlalchemy import create_engine
import json
 
engine = create_engine(
    "postgresql+psycopg2://postgres@192.168.165.141:5434/ml_datasets"
)
df = pd.read_sql(
    "SELECT instruction, input, output FROM intent_samples WHERE quality_score > 0.8",
    engine,
)
# HuggingFace datasets 형식으로 변환
records = df.to_dict(orient='records')

확정: 미래 아키텍처

┌─ 노트북 (ThinkPad P16s) ─────────────────────────────────────┐
│                                                                │
│  Temporal UI (8233) → 파이프라인 진행상황 모니터링              │
│  wandb.ai           → 학습 메트릭 (loss, GPU, 하이퍼파라미터)  │
│  br ready           → 이슈 확인                                │
│  Claude Code        → br + Temporal API 트리거 + 결과 해석     │
│                                                                │
└────────────────────────────────────────────────────────────────┘
         │ ssh / https / gRPC

┌─ storage-01 (Docker) ─────────────────────────────────────────┐
│                                                                │
│  Temporal Server 1.29.3  → 파이프라인 오케스트레이션            │
│    └─ gRPC: 192.168.165.141:7233                               │
│    └─ UI: http://192.168.165.141:8233                          │
│                                                                │
│  Doltgres 0.54.10        → 데이터셋 버전 관리                  │
│    └─ PostgreSQL: 192.168.165.141:5434                         │
│    └─ branch: main, experiment/intent-v1, v2...                │
│    └─ row-level diff: "어떤 샘플이 바뀌었나"                   │
│                                                                │
│  /storage/models/tuning/ → 모델 아티팩트 (NFS)                │
│                                                                │
└───────────────────────────────┬────────────────────────────────┘
                                │ gRPC (Worker 폴링) + NFS (10G)

┌─ gpu-03 (파인튜닝 전용, RTX 5080 16GB) ──────────────────────┐
│                                                                │
│  tuning.nix: Python 17패키지 (NixOS 25.11 네이티브)           │
│                                                                │
│  Temporal Python Worker                                        │
│    └─ TaskQueue: "gpu-training"                                │
│    └─ Doltgres SQL → JSONL 변환 (Activity 1)                  │
│    └─ QLoRA 학습 + wandb 기록 (Activity 2, heartbeat)         │
│    └─ LoRA 병합 (Activity 3)                                   │
│    └─ 평가 → Doltgres에 결과 기록 (Activity 4)                │
│    └─ ONNX 변환 (Activity 5)                                   │
│    └─ HEF 양자화 (Activity 6)                                  │
│                                                                │
│  wandb: report_to="wandb" (학습 메트릭 자동 기록)             │
│                                                                │
└────────────────────────────────────────────────────────────────┘
                                │ SCP / NFS

                    ┌───────────────────────┐
                    │  RPi5 + Hailo-8       │
                    │  HEF 추론 검증        │
                    └───────────────────────┘

재현성 공식

재현 = flake.lock (환경)
     + Doltgres commit hash (데이터셋)
     + git commit (코드)
     + wandb run ID (하이퍼파라미터 + 메트릭)

이 4가지를 기록하면 “정확히 같은 환경에서, 같은 데이터로, 같은 코드와 설정으로” 학습을 재현할 수 있다. Docker 없이, NixOS 네이티브 + 기존 인프라만으로.

리스크 대응 (Gemini 검토 반영, 2026-02-09)

Gemini의 피어 리뷰에서 지적된 3가지 리스크와 대응 전략. 총평: “이력서 주도 개발이 아니라, 자산 최적화 주도 개발(Asset-Optimization Driven Development)“

리스크 1: 스키마 경직성

문제: JSON 파일은 필드 추가가 자유롭지만, RDBMS는 ALTER TABLE이 필요. 초기 실험에서 데이터셋 구조가 자주 바뀔 수 있다.

대응: 하이브리드 스키마 — 핵심 필드는 고정 컬럼(SQL diff 대상), 실험 필드는 meta JSONB 컬럼. 테이블 설계에 이미 반영 완료.

리스크 2: Beta 소프트웨어 의존

문제: Doltgres 0.54.10은 Beta. 데이터 깨짐, 커넥션 끊김, 락 등의 가능성.

대응: JSONL 탈출구 — Temporal 워크플로우의 마지막 단계 또는 별도 Cron으로 Doltgres 데이터를 NFS에 JSONL 백업.

# 백업 Activity 예시
@activity.defn
async def backup_to_jsonl():
    engine = create_engine("postgresql+psycopg2://postgres@192.168.165.141:5434/ml_datasets")
    df = pd.read_sql("SELECT * FROM intent_samples", engine)
    backup_path = f"/storage/backup/doltgres/intent_samples_{datetime.now():%Y%m%d}.jsonl"
    df.to_json(backup_path, orient='records', lines=True, force_ascii=False)

핵심: Doltgres가 깨져도 JSONL + git으로 즉시 폴백 가능. 이 폴백 경로가 있기 때문에 Beta 소프트웨어를 시도할 수 있다.

리스크 3: 디버깅 복잡도

문제: NixOS + Python Worker + 원격 DB + 비동기 워크플로우 조합은 디버깅이 어렵고 노트북에서 재현하기 까다롭다.

대응: Local Mode 개발 가이드

[개발 시] 노트북 → storage-01 Temporal Server + Doltgres에 직접 연결
  - Worker를 노트북에서 실행 (GPU 없이 CPU 모드로 테스트)
  - Temporal Server: 192.168.165.141:7233 (사내망)
  - Doltgres: 192.168.165.141:5434 (사내망)
  - 소규모 데이터(10행)로 파이프라인 전체 흐름 검증
 
[배포 시] gpu-03에 Worker 배포 → GPU TaskQueue 폴링
  - 실제 학습은 gpu-03에서만 실행

Worker가 어디서 실행되든 Temporal Server에 붙기만 하면 되는 구조이므로, 노트북에서 개발 → gpu-03에서 실행의 전환이 단순하다.

미개척 영역에 대한 메모

Temporal + Doltgres 조합은 *2026-02 현재 아무도 공개적으로 시도하지 않은 패턴*이다.

유사 사례:

  • Dolt + Metaflow (Netflix 워크플로우 엔진) — 공식 통합
  • Dolt + Flyte (Kubernetes ML 오케스트레이터) — 데이터 리니지 통합
  • Flock Safety + Hosted Dolt — ML Feature Store로 프로덕션 사용
  • Temporal + OpenAI Agents SDK — AI 에이전트 오케스트레이션

우리 조합이 독특한 점:

  • NixOS 네이티브 환경에서 재현성 보장
  • 소규모 (단일 GPU, 500행 데이터셋) 에서 시작하는 현실적 접근
  • 에이전트(Claude) 협업이 설계에 포함됨
  • 이미 가동중인 인프라를 재활용

안 되더라도 시도하는 것에 가치가 있다:

  • 실패하면 순수 Python 스크립트 + git JSONL로 폴백 가능 (~1일 전환)
  • 성공하면 row-level 데이터 리니지 + durable execution을 확보

Phase 전환 가이드

조건전환
Phase 2 민감 데이터wandb SaaS → MLflow 셀프호스팅
데이터셋 10만+ 행Doltgres 단독 → Doltgres + DVC (비정형)
멀티노드 학습 (gpu-01+02+03)단일 Worker → 멀티 Worker (TaskQueue 분리)
모델 배포 자동화수동 SCP → Temporal Workflow (HEF → RPi5 OTA)
Doltgres 1.0 출시 (2026 Q2 목표)Beta 관찰 → 안정화 확인

다음 행동 (Action Items)

즉시 (Phase 1 시작):

  • wandb login 실행 (gpu-03)
  • test-qlora.py → train-intent.py 전환 (report_to=“wandb”)
  • Doltgres에 ml_datasets DB + intent_samples 테이블 생성 (meta JSONB 포함)
  • 샘플 데이터 10개를 Doltgres에 INSERT

다음 단계 (Temporal Worker):

  • temporalio Python 패키지를 tuning.nix에 추가
  • 노트북에서 Local Mode로 Worker 개발/테스트 (CPU, 소규모 데이터)
  • gpu-03에 Temporal Python Worker 배포 (6 Activity)
  • 파이프라인 1회 실행 → Temporal UI + wandb 동시 확인

안전장치:

  • Doltgres → JSONL 백업 Activity 구현 (NFS에 저장)
  • 백업 Cron 또는 파이프라인 마지막 단계에 통합

검증:

  • 학습 완료 후 Doltgres에 평가 결과 기록
  • 데이터셋 v2 브랜치 → diff 확인 → 재학습
  • 전체 재현성 검증 (flake.lock + Doltgres commit + wandb run + git commit)
  • JSONL 백업에서 복원 테스트 (폴백 경로 검증)

참고 자료

프로젝트 문서

  • hej-nixos-cluster/MODEL_TUNING.md — 전체 전략서
  • hej-nixos-cluster/docs/ml-파이프라인-도구세트-가이드 — 도구 비교 (초안)
  • hej-nixos-cluster/modules/gpu/tuning.nix — NixOS 환경 정의 (17패키지)
  • hej-nixos-cluster/scripts/test-qlora.py — QLoRA 검증 스크립트
  • hej-kip/temporal_bridge/ — Temporal + Doltgres Docker 구성

조사 출처

Temporal:

Doltgres:

비교: