콘텐츠로 이동

RLflow — 랩 RL MLOps 플랫폼 개발 명령서

문서 버전: 1.1 (2026-09-12 기준 버전 고정) 기준 자료: rlops_pipeline.svg (구조도). 본 문서의 모든 항목은 구조도의 요소에 1:1로 대응하며, 구조도에 없는 기능은 범위 밖이다. 대상 독자: 플랫폼 구현자(초기: 랩대표), 이후 유지보수 승계자, 플랫폼 위에 프로젝트를 만드는 연구자.


0. 원칙 (변경 불가)

  1. 플랫폼은 얇게, 규약은 두껍게. 직접 작성하는 코드는 접착 코드와 RLflow 콘솔뿐이다. 학습·추적·스케줄링·관측은 전부 검증된 오픈소스를 그대로 쓴다.
  2. 플랫폼은 특정 연구에 종속되지 않는다. 시뮬레이터, 알고리즘, 로봇은 프로젝트가 결정한다. 플랫폼은 train / evaluate / export 세 진입점과 config 스키마만 요구한다.
  3. 정책은 아티팩트다. 모든 정책은 git hash + config hash + 에셋 hash + 이미지 digest 네 값으로 계보가 고정되며, 이 네 값이 없는 런은 클러스터에서 실행되지 않는다.
  4. 승격은 게이트를 통과해야만 일어난다. candidate → validated → deployed 순서를 건너뛰는 경로는 존재하지 않는다.
  5. 승계 기준: 플랫폼 작성자가 부재한 상태에서 신규 연구자가 문서만 보고 2시간 안에 프로젝트를 만들고 첫 런을 대시보드에서 확인할 수 있어야 한다.

1. 구조도 요소와 구현 매핑

구조도 요소 구현 대상 소유
RLflow 콘솔 띠 rlflow 웹 클라이언트 + API 서버 플랫폼
연구자 → Git 저장소 프로젝트 저장소 (템플릿 생성) 프로젝트
① CI (GitHub Actions) .github/workflows/ci.yaml (템플릿 포함) 플랫폼 템플릿
② 학습 파이프라인 (Argo + Ray Tune) Argo WorkflowTemplate train 플랫폼
③ 평가·승격 (Argo) Argo WorkflowTemplate evaluate-promote 플랫폼
입력: Hydra config / DVC 에셋 / 학습 이미지 프로젝트 configs/, dvc.yaml, 플랫폼 베이스 이미지 혼합
기록: MLflow / SeaweedFS Helm 배포 서비스 플랫폼
④ 시뮬 평가 스테이지 evaluate 진입점 + eval_suite/ 프로젝트
⑤ 실기 스테이지 deploy/ ROS 2 노드 + ONNX Runtime 프로젝트
⑥ 관측 (Prometheus / Grafana / Slack) Helm 배포 + 알림 규칙 플랫폼
⑦ real2sim 피드백 텔레메트리 인덱서 + 보정 스크립트 + PR 생성 플랫폼(도구) / 프로젝트(보정 내용)
실행 인프라 k3s 2노드, GPU Operator, Helm 플랫폼

2. 저장소

2.1 lab-platform (플랫폼 저장소, 1개)

lab-platform/
  infra/
    k3s/                # 설치 스크립트, 노드 조인 토큰 관리, 재설치 절차
    helm/               # 각 서비스의 values.yaml (SeaweedFS, PostgreSQL, MLflow, Argo, kube-prometheus-stack, GPU Operator, oauth2-proxy)
    argo/               # WorkflowTemplate: train, evaluate-promote, sweep
    alerts/             # Prometheus 알림 규칙, Grafana 대시보드 JSON
  images/
    base/               # 베이스 Dockerfile (CUDA, torch, uv), 태그 규칙
    isaaclab/           # Isaac Lab 이미지
    mujoco/             # MuJoCo / MJX 이미지
  rlflow/
    api/                # FastAPI
    web/                # 웹 클라이언트
  templates/
    isaac-lab-locomotion/
    mujoco-manipulation/
    bare-gym/
  cli/                  # `lab` CLI (uv 스크립트)
  docs/                 # MkDocs: 온보딩, 런북, ADR

2.2 프로젝트 저장소 (템플릿으로 생성, 프로젝트당 1개)

<project>/
  configs/
    env/        reward/     algo/      randomization/   eval/
    config.yaml            # Hydra 루트, defaults 목록
  envs/                    # 환경 정의
  eval_suite/              # 평가 시나리오 정의 (YAML) + 지표 계산
  deploy/                  # 로봇별 어댑터 (ROS 2 패키지)
  train.py  evaluate.py  export.py
  dvc.yaml  .dvc/
  pyproject.toml           # uv 관리
  .github/workflows/ci.yaml
  docs/

프로젝트 = 저장소 1개 = MLflow experiment 1개 = DVC 원격 경로 1개 = Kubernetes 네임스페이스 1개 = RLflow 프로젝트 1개. 다섯 개는 같은 슬러그를 공유한다.


3. 인터페이스 계약 (프로젝트가 플랫폼에 제공하는 것)

3.1 진입점

세 파일은 반드시 존재하고, 아래 시그니처로 CLI 실행이 가능해야 한다.

uv run train.py    --config-name <name> [hydra overrides]   --run-id <mlflow_run_id>
uv run evaluate.py --checkpoint <path> --suite eval_suite/<suite>.yaml --out <dir>
uv run export.py   --checkpoint <path> --out <dir>/policy.onnx
  • train.py: 체크포인트를 $CKPT_DIR에 주기적으로 저장하고, 재시작 시 최신 체크포인트에서 재개해야 한다. 이 두 조건이 없으면 ② 파이프라인은 실패로 처리한다.
  • evaluate.py: <out>/report.json을 생성한다. 스키마는 3.4.
  • export.py: ONNX 파일과 <out>/io_spec.json(입력·출력 텐서 이름, 차원, 정규화 상수)을 생성한다.

3.2 config 스키마

Hydra 루트 config는 다음 최상위 키를 반드시 가진다. 플랫폼 로깅 래퍼는 이 구조를 해석해 MLflow에 기록한다.

project: <slug>
env: {...}
reward:
  terms:
    <term_name>: {weight: float, ...}
algo: {name: str, ...}
randomization: {...}
curriculum: {...}
eval:
  default_suite: eval_suite/<name>.yaml
export:
  obs_keys: [...]
  action_dim: int

검증은 프로젝트의 pydantic 스키마(configs/schema.py)로 하고, CI ①에서 실행한다.

3.3 로깅 규약

프로젝트는 플랫폼이 제공하는 lab_logging 패키지의 콜백을 사용한다. 콜백이 자동으로 기록하는 것:

  • run 시작 시: git commit, 더러운 워킹트리 여부, 해석된 config 전체, config hash, DVC 에셋 hash, 컨테이너 이미지 digest, 노드 이름, GPU 번호
  • 학습 중: reward/<term_name> 항별 평균, 에피소드 길이, KL, 손실, 처리량(steps/s)
  • run 종료 시: 최종 체크포인트를 SeaweedFS로 업로드하고 로컬 삭제

reward.terms의 항 이름과 로깅 키가 일치해야 한다. 불일치 시 CI 실패. 지표 이름, 단위, 태그의 상세 규격은 16.1을 따른다.

3.4 평가 리포트 스키마 (report.json)

{
  "policy_ref": {"git": "...", "config_hash": "...", "asset_hash": "...", "image": "..."},
  "suite": "eval_suite/terrain_v1.yaml",
  "seeds": [0,1,2,3,4],
  "metrics": {
    "<metric_name>": {"mean": 0.0, "std": 0.0, "per_scenario": {"<scenario>": 0.0}}
  },
  "gate": {"required": {"<metric_name>": {"min": 0.0}}, "regression_vs": "<prev_policy_ref>"}
}

3.5 배포 어댑터

deploy/ 는 ROS 2 패키지이며 다음을 포함한다.

  • 추론 노드: ONNX Runtime으로 policy.onnx를 로드하고 io_spec.json으로 관측·행동을 매핑
  • 안전 래퍼 노드: 정책 노드와 별도 프로세스. 힘·토크 한계, 작업공간 한계, 관절 속도 한계, 워치독(정책 출력 지연 시 정지). 정책 코드가 바뀌어도 이 노드는 바뀌지 않는다.
  • 텔레메트리 노드: 관절 상태, 명령, F/T, 정책 버전, 결과 태그(성공/실패 유형)를 rosbag으로 저장하고 시도 종료 시 SeaweedFS telemetry/<project>/<policy_ref>/에 업로드. 토픽·메시지·좌표계·메타데이터 규격은 16.2를 따른다.

4. 실행 인프라 (구조도 하단 띠)

4.1 노드

항목 노드 A 노드 B
역할 k3s server + 워커 + 플랫폼 서비스 k3s agent
GPU RTX 4090 × 4 RTX 4090 × 4
스토리지 NVMe (local-path-provisioner), SeaweedFS·MLflow DB 볼륨 NVMe (local-path-provisioner), A의 백업 사본
네트워크 고정 IP, 노드 간 10GbE 직결, 외부 접근은 Tailscale 동일

4.2 설치 (infra/k3s/)

  1. Ubuntu 22.04/24.04, NVIDIA 드라이버, nvidia-container-toolkit
  2. GPU 전력 상한 320W 설정을 systemd 서비스로 고정
  3. 노드 A: k3s server, 노드 B: k3s agent 조인
  4. containerd nvidia 런타임 인식 확인
  5. Helm으로 순서대로 설치: GPU Operator(드라이버 비활성) → SeaweedFS → PostgreSQL(MLflow 백엔드) → MLflow → Argo Workflows → kube-prometheus-stack(Prometheus + Grafana) → RLflow
  6. nvidia-smi 파드로 8장 인식 확인
  7. 전 과정은 infra/k3s/install.sh 하나로 재현 가능해야 하며, 승계자가 깨끗한 노드에서 이 스크립트로 재설치하는 리허설을 분기 1회 수행한다.

4.3 스케줄링 규칙

  • 프로젝트당 네임스페이스, ResourceQuota로 GPU 기본 2장. 4장은 RLflow에서 기간 지정 신청.
  • 우선순위 클래스 두 단계: deadline(선점 가능), explore.
  • GPU 1장(노드 A의 0번)은 interactive taint로 예약. 디버깅·평가 게이트 전용.
  • 런당 GPU 1장. 멀티노드 학습은 지원하지 않는다.
  • 24시간 초과 런은 체크포인트 재개 필수(3.1), 72시간 초과 런은 explore 클래스에서 선점 대상.

4.4 스토리지 정책

  • 체크포인트: 학습 중 로컬, 종료 시 SeaweedFS 업로드 후 로컬 삭제
  • SeaweedFS 보존: validated 이상 정책은 영구, 그 외 체크포인트 90일, 텔레메트리 180일
  • 백업: SeaweedFS와 PostgreSQL 볼륨을 매일 노드 B로, 주 1회 외부(NAS 또는 클라우드)로. 복구 리허설 분기 1회.

5. 입력 계층 (구조도 상단: Hydra config / DVC 에셋 / 학습 이미지)

5.1 Hydra config

  • 보상 항, 랜덤화 범위, 커리큘럼은 코드가 아니라 config다. 코드에 하드코딩된 보상 가중치는 CI에서 거부한다(정적 검사: reward/ 모듈 내 숫자 리터럴 금지 규칙).
  • config hash = 해석된(resolved) config의 정규화 YAML SHA-256.

5.2 DVC 에셋

  • URDF/MJCF/USD, 지형 생성기 시드, 액추에이터 모델 파라미터, 시스템 식별 결과는 DVC로 추적하고 원격은 SeaweedFS dvc/<project>/.
  • 에셋 hash = dvc.lock의 SHA-256.

5.3 학습 이미지

  • 베이스 이미지는 플랫폼이 태그로 관리한다(lab/base:<cuda>-<torch>-<date>). 프로젝트 Dockerfile은 베이스 위에 uv sync --frozen만 수행한다.
  • Python 의존성은 uv와 uv.lock으로만 관리한다. pip, poetry, pipenv 사용 금지.
  • 이미지 digest는 런 계보의 네 번째 값이다.

6. ① CI (GitHub Actions)

프로젝트 템플릿에 포함되는 ci.yaml. PR마다 실행.

  1. ruff + pre-commit
  2. config 스키마 검증 (configs/schema.py로 모든 configs/**/*.yaml 로드)
  3. 보상 항 이름과 로깅 키 일치 검사
  4. 환경 단위 테스트 (pytest envs/)
  5. 1분 smoke 학습: train.py+smoke=true로 60초 실행, NaN 없음과 체크포인트 저장을 확인
  6. PR 리뷰 1인 승인 필수 (브랜치 보호)

결과는 GitHub 상태 체크와 함께 RLflow API(POST /ci-events)로 전송된다. 구조도의 "CI 결과 알림" 점선이 이것이다.


7. ② 학습 파이프라인 (Argo Workflows + Ray Tune)

infra/argo/train.yaml WorkflowTemplate. 파라미터: project, git_ref, config_name, overrides, sweep(선택), priority.

단계:

  1. resolve: git_ref를 commit hash로 고정, config 해석·hash, dvc.lock hash, 이미지 digest 조회. 네 값을 워크플로 출력으로 기록. MLflow run 생성.
  2. fetch: 컨테이너 이미지 pull, dvc pull, config 렌더링
  3. train-teacher:
  4. sweep이 없으면 단일 파드로 train.py 실행
  5. sweep이 있으면 Ray Tune 헤드 파드 1 + 워커 파드 N(각 GPU 1장). 탐색 공간은 configs/sweep/<name>.yaml. 최적 trial의 체크포인트를 다음 단계 입력으로 선택.
  6. distill-student: teacher 체크포인트로 학생 정책 학습 (프로젝트가 train.py --distill-from을 지원할 때만, 아니면 건너뜀)
  7. finalize: 체크포인트 SeaweedFS 업로드, MLflow run 종료, registry에 candidate로 등록, RLflow에 완료 이벤트

체크포인트 재개: 파드가 선점·장애로 죽으면 Argo retryStrategy로 재시작하고, train.py$CKPT_DIR의 최신 체크포인트에서 재개한다.


8. ③ 평가·승격 (Argo Workflows)

infra/argo/evaluate-promote.yaml. 입력: candidate 정책 ref.

  1. evaluate: evaluate.pyeval.default_suite로 실행. 시드 5개. 결과 report.json SeaweedFS 업로드, MLflow에 지표 로깅.
  2. regression-check: 같은 프로젝트의 현재 validated 정책 리포트와 비교. gate.required 최소치 미달 또는 어느 지표든 통계적으로 유의한 하락(시드 5개 기준 Welch t-test, p<0.05, 효과 크기 임계값은 suite에 명시)이 있으면 실패.
  3. export: export.py 실행, ONNX와 io_spec.json 업로드. ONNX Runtime으로 로드해 1000 스텝 추론 지연 측정, 프로젝트 config의 제어 주기 안에 들어오는지 확인.
  4. promote: registry 단계를 validated로 변경. promotion.manual_approval=true인 프로젝트는 RLflow에서 승인 버튼을 눌러야 이 단계가 진행된다(Argo suspend).
  5. RLflow에 리포트 링크와 결과 이벤트 전송. 구조도의 "평가 리포트 · 승격 결과" 점선.

registry 단계 정의:

단계 의미 진입 조건
candidate 학습 완료 ② 종료
validated 시뮬 게이트 통과 ③ 2·3단계 통과 (+ 수동 승인)
deployed 실기 배포 중 ⑤ 롤아웃 완료, 동시에 프로젝트당 1개

9. ④ 시뮬 평가 스테이지

  • 평가는 학습과 같은 클러스터, 같은 이미지에서 실행한다(구조도 인프라 띠의 점선).
  • eval_suite/<name>.yaml은 시나리오 목록(지형 종류, 마찰 범위, 외란 크기·시점, 페이로드, 초기 자세 분포)과 지표, 게이트 최소치, 회귀 비교 대상 지표를 정의한다.
  • 시뮬레이터는 프로젝트가 선택한다. 템플릿 기본값: 로코모션은 Isaac Lab, 매니퓰레이션은 MuJoCo/MJX. 정책 추론은 PyTorch.
  • validated가 아닌 정책은 ⑤로 넘어갈 수 없다.

10. ⑤ 실기 스테이지

롤아웃 순서는 고정이며 RLflow에서 단계별 체크리스트로 관리한다.

  1. 벤치: 로봇 고정, 관절 명령 한계 50%, 안전 래퍼 동작 확인
  2. 테더/보호구: 정상 한계, 사람 감독
  3. 제한 공간: 무감독 반복 시도, 텔레메트리 수집
  4. deployed 승격, 이전 deployed 정책은 retired로 이동하되 원클릭 롤백 대상으로 유지

RB5-850 어댑터 요구사항(프로젝트 deploy/에서 구현):

  • 손목 F/T 센서 드라이버 통합, 관측에 힘·토크 포함
  • 제어 주기와 저수준 명령 인터페이스(위치/속도 스트리밍 주기, 토크 모드 가능 여부)를 시스템 식별 문서로 남기고 시뮬 액추에이터 모델의 근거로 사용
  • 안전 래퍼: 힘 한계 초과 시 정지, 작업공간 벗어남 시 정지, 정책 출력 지연 2주기 초과 시 정지

사족보행 어댑터는 같은 계약으로 별도 구현한다. 두 어댑터가 같은 파이프라인 위에서 동작하는 것이 플랫폼의 범용성 검증 기준이다.


11. ⑥ 관측 (Prometheus / Grafana / Slack)

11.1 수집

  • 노드: GPU 사용률·온도·전력, CPU, 메모리, 디스크 잔량 (DCGM exporter, node exporter)
  • 런: lab_logging이 Pushgateway로 보내는 steps/s, 손실, KL, 항별 보상, NaN 플래그
  • 파이프라인: Argo 워크플로 상태·소요 시간
  • 실기: 텔레메트리 노드가 보내는 시도 수, 성공률, 안전 래퍼 개입 횟수

11.2 알림 규칙 (infra/alerts/rules.yaml)

Slack으로 가는 것은 사람 판단이 필요한 것만이다.

규칙 조건 동작
학습 이상 감지 손실 또는 그래디언트가 NaN/Inf, 또는 KL이 기준의 10배 런 자동 중단 후 알림
처리량 저하 steps/s가 런 초기 평균의 40% 미만 10분 지속 알림
GPU 온도 85도 초과 5분 전력 상한 하향 후 알림
디스크 노드 잔량 15% 미만 알림
게이트 실패 ③ regression-check 실패 프로젝트 채널 알림
안전 래퍼 개입 실기 시도 중 개입 발생 즉시 알림
자원 대기 deadline 런이 30분 이상 큐 대기 랩대표 알림

11.3 대시보드 (infra/alerts/dashboards/)

  • 클러스터: 8 GPU 슬롯 점유 현황, 네임스페이스별 사용량, 큐 대기 목록
  • 런: 항별 보상 곡선, 처리량, 체크포인트 이력
  • 프로젝트: registry 단계별 정책 수, 최근 게이트 통과율, 실기 성공률 추이

Grafana 대시보드는 RLflow 콘솔에 iframe으로 임베드된다.


12. ⑦ real2sim 피드백

목적: 실기 텔레메트리로 시뮬 파라미터를 보정하고, 그 결과를 config·에셋 변경 PR로 만들어 연구자에게 돌려보낸다. 사람이 리뷰하는 PR이며 자동 머지는 없다.

  1. 텔레메트리 인덱서(플랫폼): SeaweedFS telemetry/에 새 rosbag이 오면 16.2 규격을 검증하고(불합격은 거부·알림), Parquet으로 변환해 telemetry-parquet/에 저장한 뒤 시도 단위 테이블(정책 ref, 시나리오, 결과, 힘 프로파일 요약)을 PostgreSQL에 적재
  2. 시뮬-실기 차이 계량(플랫폼 도구 + 프로젝트 정의): 프로젝트의 real2sim/compare.py가 같은 시나리오의 시뮬 롤아웃과 실기 rosbag을 비교해 파라미터별 잔차를 계산
  3. 보정 제안(플랫폼 도구): 잔차를 줄이는 방향으로 randomization 범위와 DVC 에셋(액추에이터 모델 계수)의 변경안을 생성. 사용한 시도 목록은 16.3의 데이터셋으로 고정해 에셋 hash에 포함
  4. PR 생성: 변경안을 프로젝트 저장소에 브랜치로 푸시하고 PR 생성. PR 본문에 비교 리포트 링크 첨부. 이 PR은 ①부터 다시 흐른다.

프로젝트가 real2sim/compare.py를 제공하지 않으면 1단계 인덱싱까지만 수행한다.


13. RLflow 콘솔

13.1 위치와 역할

RLflow는 파이프라인을 대체하지 않는다. Argo, MLflow, Prometheus, GitHub의 API를 프로젝트·계정 단위로 묶어 보여주고, 사람의 개입(승인, 롤백, 쿼터 신청)을 받는 파사드다. 내부 도구를 교체해도 RLflow 화면이 유지되도록, 각 외부 시스템은 api/integrations/<name>.py 어댑터 하나로만 접근한다.

13.2 기술 스택

  • API: FastAPI, PostgreSQL(MLflow와 인스턴스 공유, DB 분리), uv 관리
  • 웹: React + TypeScript + Vite, shadcn/ui + Tailwind CSS. 디자인 규격은 13.7
  • 인증: OIDC(GitHub 조직 로그인). 역할: admin(랩대표), researcher, viewer

13.3 데이터 모델

User(id, github_login, role, created_at)
Project(slug, name, owner_id, template, git_url, namespace, mlflow_experiment_id, dvc_remote, gpu_quota, manual_approval)
ProjectMember(project_slug, user_id, role)
Run(id, project_slug, argo_workflow, mlflow_run_id, git_hash, config_hash, asset_hash, image_digest, status, priority, submitted_by)
Policy(ref, project_slug, run_id, stage, report_url, onnx_url, promoted_by, promoted_at)
Rollout(id, policy_ref, step, checklist_json, started_by)
QuotaRequest(id, project_slug, gpus, from, to, status)
Event(id, project_slug, kind, payload_json, at)

13.4 기능 (구조도 띠의 6칸)

  1. 연구자 계정·권한: 사용자 목록, 역할 변경, 프로젝트 멤버 관리, GPU 쿼터 신청·승인(승인 시 ResourceQuota 갱신)
  2. 프로젝트 생성: 템플릿 선택 → 저장소 생성(GitHub API) → MLflow experiment, DVC 원격, 네임스페이스, 쿼터, 문서 페이지 생성. 실패 시 전체 롤백.
  3. Git 연결: 저장소·브랜치·열린 PR과 CI 상태 표시(GitHub webhook 수신)
  4. 런 조회·비교: 런 제출 폼(config, overrides, sweep, priority), 실행 중 런의 Argo 상태와 MLflow 지표, 런 두 개 이상의 항별 보상 곡선 비교, 계보 네 값 표시
  5. 게이트 승인·롤백: validated 대기 정책의 리포트 열람과 승인/거부(Argo suspend 해제), deployed 정책의 원클릭 롤백(이전 retired 정책을 deployed로), 롤아웃 체크리스트
  6. 감시·알림: Grafana 임베드, Slack 채널 매핑 설정, 이벤트 타임라인

13.5 API (요약)

POST /projects                      프로젝트 생성
GET  /projects/{slug}
POST /projects/{slug}/runs          런 제출 → Argo 워크플로 생성
GET  /projects/{slug}/runs/{id}
GET  /projects/{slug}/policies
POST /policies/{ref}/approve        수동 승인
POST /policies/{ref}/rollback
POST /projects/{slug}/rollouts
POST /quota-requests, POST /quota-requests/{id}/approve
POST /ci-events                     GitHub Actions → RLflow
POST /pipeline-events               Argo → RLflow

13.6 lab CLI

콘솔과 같은 API를 쓰는 CLI. lab new-project, lab run submit, lab run status, lab policy list, lab policy approve. CLI는 콘솔 없이도 플랫폼을 운영할 수 있게 하는 승계 안전장치다.

13.7 웹 클라이언트 디자인 규격

목표: 모던하고 밀도 있는 업무 도구. 기준 이미지는 Linear, Vercel 대시보드, GitHub의 설정 화면이다. 장식이 아니라 정보가 화면을 채운다.

13.7.1 기반

  • 컴포넌트: shadcn/ui를 그대로 쓴다. 컴포넌트를 새로 그리지 않고, 없는 것은 shadcn 프리미티브(Radix)를 조합한다. 스타일 오버라이드는 토큰 수준(globals.css의 CSS 변수)에서만 하고 컴포넌트 파일을 직접 수정하지 않는다.
  • 폰트: Noto Sans KR(본문·UI), JetBrains Mono(해시, 경로, 로그, 수치 열). 폰트 두 종 외에는 쓰지 않는다. 자체 호스팅(/fonts), font-display: swap.
  • 아이콘: lucide-react만. 크기 16px(인라인)·20px(내비게이션). 컬러 아이콘, 일러스트, 이모지 금지.
  • 로고: 상단 좌측에 RLflow 로고(제공 파일, 배경 제거본) 높이 24px. 로고 색을 변형하지 않는다.

13.7.2 색 토큰

로고의 청록(#187C8C)과 워드마크 잉크(#202428)에서 파생한다. shadcn 토큰에 아래 값을 매핑한다.

토큰 라이트 다크 용도
--primary #187C8C #3AA3B3 주 버튼, 활성 탭, 링크, 포커스 링
--primary-foreground #FFFFFF #0B1D21 primary 위 글자
--background #FFFFFF #0F1214 페이지
--foreground #202428 #E6E8EA 본문
--card #FFFFFF #151A1D 카드, 패널
--muted #F3F5F6 #1B2125 표 헤더, 비활성 배경
--muted-foreground #6B7379 #9AA3A9 보조 텍스트, 라벨
--border #E3E7E9 #262D32 구분선, 입력 테두리
--accent #E6F2F4 #12333A 호버, 선택 행
--destructive #B42318 #F04438 삭제, 롤백 확인
--ring #187C8C #3AA3B3 포커스

상태 색은 파이프라인 단계 표시에만 쓰며 배지·점·텍스트 세 형태로만 노출한다.

상태
running --primary
succeeded / validated / deployed #1F7A3E (다크 #3DBB6A)
pending / candidate / queued --muted-foreground
failed / rejected --destructive
warning (자원 대기, 처리량 저하) #B54708 (다크 #F79009)

청록 외의 색상은 위 상태 색 네 가지뿐이다. 차트 계열색은 청록 명도 단계(#187C8C, #5FA8B4, #A5CFD6)와 회색 단계로만 구성하고, 비교 대상이 4개를 넘으면 사용자가 선택한 런만 강조하고 나머지는 회색으로 내린다.

13.7.3 금지 목록

다음은 리뷰에서 거부한다.

  • 그라디언트 배경, 글로우, 블러, 네온, 유리 효과
  • 보라·핑크 계열 강조색, 무지개 차트 팔레트
  • 히어로 섹션, 마케팅 문구, 대형 장식 제목, 빈 여백을 채우기 위한 일러스트
  • 카드 안의 카드, 과도한 라운딩(--radius는 6px 고정), 그림자는 팝오버·다이얼로그에만 shadow-md
  • 이모지, 컬러 아이콘, 애니메이션 배경. 트랜지션은 150ms 이하의 opacity·transform만
  • 텍스트 굵기 세 종류 초과 (400, 500, 600만 사용)
  • 상태를 색으로만 표시하는 것 (항상 텍스트 라벨 동반)

13.7.4 레이아웃

  • 좌측 고정 사이드바 240px(접으면 56px): 프로젝트 전환기, 내비게이션(개요, 런, 정책, 롤아웃, Git, 감시, 설정). 상단 바 48px: 브레드크럼, 검색(⌘K), 알림, 사용자 메뉴.
  • 본문 최대 폭 제한 없음. 표와 차트는 가로 폭을 다 쓴다. 폼은 최대 640px.
  • 기본 밀도는 촘촘하게: 표 행 높이 36px, 본문 14px, 보조 12px, 제목 16/20px. 페이지 제목은 20px 600 하나뿐이다.
  • 페이지 구조는 동일하게: 제목 행(제목, 주 동작 버튼 1개) → 필터 행 → 본문. 대시보드성 페이지도 카드 격자가 아니라 표와 차트의 세로 나열을 우선한다.

13.7.5 핵심 화면과 사용할 컴포넌트

화면 구성 shadcn 컴포넌트
프로젝트 개요 상단 지표 4개(활성 런, GPU 사용, 최근 게이트 통과율, deployed 정책), 최근 이벤트 타임라인, 열린 PR Card(지표만), Table, Badge
런 목록 상태·우선순위·제출자 필터, 정렬, 행 클릭으로 상세. 계보 네 값은 mono 폰트 축약(7자)과 복사 버튼 DataTable, Select, Tooltip
런 상세 좌: Argo 단계 스텝퍼, 우: 지표 차트(항별 보상 토글), 하단: 로그 스트림, 체크포인트 표 Tabs, ScrollArea, Toggle, Recharts
런 비교 2~4개 런 선택, 같은 지표를 한 차트에 겹침, 차이 표 Command(런 선택), Recharts
런 제출 config 선택, override 입력(mono textarea, 문법 검증), sweep 선택, priority, GPU 수 Form, Combobox, RadioGroup, Sheet
정책 레지스트리 candidate / validated / deployed / retired 탭, 리포트 링크, 승인·거부·롤백 Tabs, Table, AlertDialog(승인·롤백 확인)
롤아웃 정책별 4단계 체크리스트, 단계별 담당·시각·비고, 안전 개입 기록 Checkbox, Accordion, Textarea
Git 저장소·브랜치, 열린 PR과 CI 상태, CI 실패 로그 링크 Table, Badge, 외부 링크 아이콘
감시 Grafana iframe(프로젝트별 대시보드 URL), 알림 규칙 목록, Slack 채널 매핑 Tabs, Switch, Input
설정·계정 멤버·역할, 쿼터 신청과 승인, 템플릿 관리(admin) Table, Dialog, Select

13.7.6 상호작용 규칙

  • 파괴적 동작(롤백, 거부, 멤버 제거)은 AlertDialog로 대상 이름을 표시하고 확인한다. 승격 승인도 리포트 요약을 다이얼로그에 띄운 뒤 확인한다.
  • 로딩은 Skeleton으로 레이아웃을 유지한다. 스피너는 버튼 내부에서만.
  • 빈 상태는 한 줄 설명과 주 동작 버튼 하나. 일러스트 없음.
  • 오류는 Toast(일시)와 인라인 Alert(폼) 두 가지만. 전체 화면 오류 페이지는 401/404/500 세 종.
  • 표는 URL 쿼리에 필터·정렬 상태를 저장한다. 링크를 공유하면 같은 화면이 열려야 한다.
  • 키보드: ⌘K 전역 검색(프로젝트, 런 id, 정책 ref, 사용자), 표에서 j/k 이동과 Enter 열기.
  • 다크 모드는 시스템 설정을 따르고 사용자 메뉴에서 고정 가능. 두 모드 모두 위 토큰만으로 구성되어야 한다.

13.7.7 수용 기준

  • 디자인 토큰 파일(globals.css)과 shadcn 설정(components.json) 외에 색상 하드코딩이 없다(CI에서 #[0-9a-f]{6} 검색으로 검사, 예외는 토큰 파일과 차트 팔레트 상수 1곳).
  • 모든 화면이 라이트·다크에서 WCAG AA 대비를 만족한다.
  • 1280px 폭에서 가로 스크롤 없이 모든 화면이 동작한다. 모바일은 조회만 지원한다.
  • 13.7.3 금지 목록 위반 0건을 리뷰어가 확인한다.

13.8 기록·표시 책임 경계

원칙: MLflow·Argo·Prometheus는 "런과 정책과 실행에 대한 사실"의 기록 주체이고, RLflow는 "사람과 조직의 결정"의 기록 주체다. RLflow는 사실 데이터를 복제하지 않고 API로 읽어 요약만 보여주며, 상세는 원 시스템으로 리다이렉트한다. RLflow가 MLflow나 Argo의 UI를 대체하려는 기능 요청은 기본적으로 거부하고 링크로 답한다.

13.8.1 기록 책임

데이터 기록 주체 비고
지표 시계열, 파라미터, 해석된 config, 계보 태그 MLflow 학습 프로세스(lab_logging)가 직접 기록
체크포인트, ONNX, 평가 리포트, io_spec SeaweedFS MLflow 아티팩트로 색인
정책 버전과 단계 (candidate / validated / deployed / retired) MLflow Model Registry alias 정책 상태의 유일한 진실
워크플로 실행 상태, 단계별 파드 로그 Argo
노드·GPU·처리량 메트릭, 알림 발화 Prometheus / Alertmanager
실기 텔레메트리 원본과 Parquet SeaweedFS 16.2
텔레메트리 시도 인덱스 PostgreSQL (인덱서) 16.2.5
사용자, 역할, 프로젝트 멤버, GPU 쿼터 RLflow DB 다른 시스템에 없는 개념
프로젝트 정의 (슬러그 ↔ 저장소 ↔ experiment ↔ DVC 원격 ↔ 네임스페이스) RLflow DB 다섯 시스템을 묶는 색인
런 제출 요청 (제출자, 시각, 우선순위, 요청 GPU 수) RLflow DB 제출은 결정, 실행은 사실
승격 승인·거부, 롤백 결정 (결정자, 시각, 사유) RLflow DB 감사 기록
롤아웃 체크리스트, 단계별 확인 서명 RLflow DB 사람의 절차
이벤트 타임라인 (CI 결과, 게이트 결과, 알림 발송, alias 변경 감지) RLflow DB 여러 시스템의 사건을 시간순으로 통합

규칙:

  1. RLflow DB는 MLflow가 가진 값을 저장하지 않는다. Run 테이블은 mlflow_run_id, argo_workflow 같은 외래 키와 제출 정보만 가진다. 목록 화면용으로 최종 상태와 최종 지표 값 최대 6개를 캐시할 수 있으며, 캐시는 lab cache rebuild로 MLflow에서 언제든 재구성 가능해야 한다.
  2. 정책 단계 변경은 두 단계 쓰기다. RLflow DB에 결정을 기록한 뒤 MLflow alias를 변경한다. 둘 중 하나가 실패하면 결정을 롤백하고 오류를 표시한다.
  3. MLflow UI에서 alias를 직접 바꾸는 역방향 변경은 RLflow가 5분 주기로 감지해 이벤트로 기록하고 admin에게 알린다. 되돌리지는 않는다.
  4. RLflow는 각 외부 시스템을 integrations/<name>.py 어댑터로만 접근하며, 어댑터 인터페이스는 "런 id로 지표 시계열", "정책 ref로 alias", "워크플로 id로 단계 상태" 수준의 추상 메서드만 가진다. MLflow 내부 스키마나 Argo CRD 구조가 어댑터 밖으로 새지 않는다.

13.8.2 표시 책임

화면 요소 RLflow가 직접 표시 리다이렉트 대상
런 목록: 상태, 제출자, 우선순위, 소요 시간, 계보 축약
런 상세: 지표 차트 16.1.2 필수 지표와 항별 보상, 최근 20,000포인트까지 다운샘플링 MLflow: 전체 지표, 임의 축, 히스토그램
런 상세: 해석된 config 읽기 전용 뷰어, diff (다른 런과) MLflow: 파라미터 검색
런 상세: 아티팩트 체크포인트·리포트·ONNX 목록과 다운로드 링크 MLflow: 아티팩트 브라우저
런 비교 2~4개 런, 같은 지표 겹침, 최종값 차이 표 MLflow: 5개 이상, 평행 좌표, 파라미터 차이
워크플로 진행 단계 스텝퍼, 단계별 소요 시간, 실패 단계 표시 Argo: DAG 전체, 재시도 이력
로그 실패 단계의 마지막 200줄 Argo: 파드별 전체 로그
정책 목록: 단계, 리포트 요약 (지표 평균·표준편차, 게이트 통과 여부, 실패 유형 카운트) MLflow: 모델 버전 상세
인프라·GPU·처리량 Grafana iframe Grafana: 편집, 임의 쿼리
알림 이력 이벤트 타임라인 Alertmanager
텔레메트리 시도 목록, 결과·안전 개입 집계 Parquet 다운로드 링크 (분석은 노트북에서)
승인·롤백·롤아웃·쿼터·멤버·프로젝트 생성 RLflow만 없음

리다이렉트 규칙:

  • 런·정책·워크플로 화면 상단 우측에 "MLflow에서 열기", "Argo에서 열기" 버튼을 고정 배치한다. 링크는 해당 run id, 모델 버전, 워크플로 이름으로 깊이 연결(deep link)되어야 한다.
  • 외부 시스템 링크는 새 탭으로 열고 외부 링크 아이콘을 붙인다. iframe 임베드는 Grafana 대시보드에만 허용한다.
  • 외부 시스템 인증은 같은 OIDC로 통일해 리다이렉트 시 재로그인이 없어야 한다. 불가능한 시스템(Argo 기본 인증 등)은 SSO 프록시(oauth2-proxy)를 앞에 둔다.
  • RLflow에 없는 정보를 사용자가 요청하면 화면에 "상세는 MLflow에서" 링크를 두는 것으로 답하고, 기능 추가는 13.8.2의 왼쪽 열 범위를 넘지 않는다.

14. 마일스톤과 수용 기준

M1 — 인프라와 세로 한 줄 (0~3개월)

  • 4.2 설치 스크립트로 2노드 클러스터 구성, 8 GPU 인식
  • SeaweedFS, MLflow, Argo, Prometheus, Grafana Helm 배포, values 커밋
  • lab_logging 패키지, 베이스 이미지 1종
  • 템플릿 1종(bare-gym)으로 프로젝트 1개 생성, ② 단일 런 → ③ 평가 → ONNX export까지 수동 개입 없이 통과
  • 수용 기준: 위 흐름이 스크립트 한 번으로 재현되고, MLflow에 계보 네 값이 기록된다

M2 — 파이프라인 완성 (3~6개월)

  • ① CI 템플릿, Ray Tune 스윕, 회귀 게이트, registry 단계, 수동 승인
  • 알림 규칙 11.2 전부, 대시보드 11.3
  • 템플릿 3종 완성
  • 수용 기준: 두 번째 프로젝트(다른 시뮬레이터)를 템플릿으로 생성해 플랫폼 코드 수정 없이 M1 흐름이 통과된다

M3 — RLflow 콘솔 (6~9개월)

  • 13.4의 6개 기능, lab CLI
  • 13.7 디자인 규격 준수 (수용 기준 13.7.7)
  • 13.8 책임 경계 준수: RLflow DB에 지표·파라미터 컬럼이 없고, 모든 런·정책 화면에 MLflow·Argo 딥링크가 있다
  • 수용 기준: 신규 연구자가 문서만 보고 2시간 안에 프로젝트 생성과 첫 런 확인. 실제 신입에게 시켜 시간을 측정한다

M4 — 실기와 real2sim (9~15개월)

  • RB5-850 어댑터, 안전 래퍼, 텔레메트리 노드, 롤아웃 체크리스트
  • 텔레메트리 인덱서, real2sim PR 생성
  • 사족보행 어댑터를 두 번째 클라이언트로 연결
  • 수용 기준: 두 로봇이 같은 파이프라인에서 deployed까지 도달하고, real2sim PR이 ①부터 다시 흘러 validated에 도달한 사례 1건 이상

M5 — 승계 (15개월~)

  • 승계자 1인이 플랫폼 저장소에 PR을 머지한 이력
  • 재설치 리허설, 백업 복구 리허설 각 1회 이상 기록
  • ADR, 온보딩, 런북 완비. 플랫폼 인프라를 워크숍 논문 또는 기술 보고서로 정리

15. 문서 (docs/, 코드와 같은 저장소)

  • 온보딩: 계정 → 프로젝트 생성 → 첫 런 → 대시보드, 2시간 목표
  • 프로젝트 계약: 3장의 내용을 연구자 관점으로 재서술
  • 운영 런북: 노드 재부팅, 디스크 부족, SeaweedFS/PostgreSQL 복구, GPU 노드 추가, k3s 업그레이드, 인증서 갱신
  • ADR: 각 도구 선택 근거(왜 k3s, 왜 MLflow, 왜 Argo, 왜 Ray Tune, 왜 uv) 한 페이지씩
  • 변경 정책: 플랫폼 저장소의 모든 변경은 PR과 리뷰어 1인, 인터페이스 계약(3장) 변경은 모든 활성 프로젝트 소유자에게 공지 후 2주 유예

16. 데이터 규격

원칙: 봉투는 규격화하고 내용물은 자유로 둔다. 어떤 지표를 재고 어떤 센서를 다는지는 프로젝트가 정하지만, 값의 이름·단위·시간축·메타데이터는 플랫폼이 정한다. 그래야 두 프로젝트를 나란히 비교할 수 있고, ⑦ real2sim이 재현된다. 규격 강제 지점은 두 곳뿐이다: CI ①(16.1 검사)과 텔레메트리 인덱서(16.2 검사).

모든 규격에는 schema_version(정수)이 붙는다. 규격을 바꿀 때는 버전을 올리고 옛 버전 판독기를 남긴다. 옛 런과 옛 bag은 변환하지 않는다.

16.1 학습 지표와 런 메타데이터

16.1.1 지표 이름

지표 이름은 <group>/<name> 형식이며 group은 아래 다섯 개만 허용한다. 그 외 group은 CI에서 거부한다.

group 용도
reward/ 보상 항별 에피소드 평균. 이름은 config.reward.terms의 키와 정확히 일치 reward/tracking_lin_vel, reward/torque_penalty
train/ 알고리즘 내부 값 train/loss_value, train/loss_policy, train/kl, train/entropy, train/lr, train/grad_norm
env/ 환경 수준 결과 env/ep_len, env/ep_return, env/success_rate, env/curriculum_level
sys/ 실행 상태 sys/steps_per_s, sys/gpu_mem_gb, sys/nan_flag, sys/ckpt_step
custom/ 프로젝트 고유 지표 custom/foot_air_time

이름 규칙: 소문자, 숫자, 밑줄만. 정규화된 값은 _norm 접미사. 각도는 rad, 각속도는 rad/s. 백분율은 0~1 비율로 기록하고 이름에 _rate 접미사.

16.1.2 필수 지표

lab_logging 콜백이 모든 런에 반드시 기록하는 키. 프로젝트가 값을 제공하지 못하면 콜백이 NaN이 아니라 기록 자체를 생략하고, CI smoke 학습에서 실패 처리한다.

reward/<모든 term>, train/loss_value, train/loss_policy, train/kl, env/ep_len, env/ep_return, sys/steps_per_s, sys/nan_flag, sys/ckpt_step

16.1.3 기록 축

  • 모든 지표의 x축은 global_step(환경 스텝 누적 수, 정수)이다. 반복(iteration) 수나 에피소드 수를 x축으로 쓰지 않는다.
  • 벽시계 시간은 MLflow가 자동 기록하는 timestamp를 쓰고 별도 지표로 만들지 않는다.
  • 기록 주기: sys/는 30초마다, 나머지는 반복마다. 반복이 5초보다 짧으면 5초 단위로 집계(평균)해 기록한다.

16.1.4 단위

SI 고정. 길이 m, 각도 rad, 시간 s, 힘 N, 토크 N·m, 질량 kg, 속도 m/s, 각속도 rad/s. 단위는 이름에 넣지 않는다(예외: sys/gpu_mem_gb처럼 SI가 아닌 관례 단위는 접미사로 명시).

16.1.5 MLflow 태그 (필수)

태그
schema_version 정수
project 프로젝트 슬러그
template 생성 템플릿 이름
git_hash / git_dirty commit SHA / true|false
config_hash 해석된 config SHA-256
asset_hash dvc.lock SHA-256
image_digest 컨테이너 이미지 digest
robot sim:<시뮬레이터>:<모델> 또는 real:<모델>
algo config.algo.name
suite 평가 시 사용한 suite 파일 경로
parent_run 증류·재개·스윕 자식일 때 부모 run id
sweep_id Ray Tune 스윕 소속일 때
priority deadline 또는 explore

16.1.6 아티팩트 경로 (MLflow run 내부)

config/resolved.yaml
config/schema_version
checkpoints/step_<n>.pt        (SeaweedFS 경로를 가리키는 참조 파일)
eval/<suite_name>/report.json
export/policy.onnx
export/io_spec.json

16.1.7 io_spec.json

{
  "schema_version": 1,
  "obs": [{"name": "joint_pos", "dim": 12, "unit": "rad", "offset": [...], "scale": [...]}],
  "action": {"name": "joint_target", "dim": 12, "unit": "rad", "offset": [...], "scale": [...]},
  "control_hz": 50,
  "history_len": 1
}

obs 순서는 ONNX 입력 텐서의 연결 순서와 같다. 실기 어댑터는 이 파일만 읽고 관측을 조립한다.

16.2 실기·센서 데이터 계약

16.2.1 표준 토픽

로봇별 원시 토픽은 그대로 두고, 프로젝트의 어댑터가 아래 표준 토픽으로 재발행한다. 인덱서는 표준 토픽만 읽는다.

토픽 메시지 내용
/lab/joint_state sensor_msgs/JointState 위치, 속도, 실측 토크(또는 전류 환산). name 배열 순서는 16.2.2의 관절 순서
/lab/joint_cmd sensor_msgs/JointState 정책이 낸 명령 (위치 또는 토크, io_spec.action.unit)
/lab/ft geometry_msgs/WrenchStamped 손목 F/T. frame_id는 16.2.2의 센서 프레임
/lab/imu sensor_msgs/Imu 베이스 IMU (사족보행 등 해당 로봇만)
/lab/policy_obs std_msgs/Float32MultiArray 정책 입력 벡터 (정규화 후)
/lab/policy_action std_msgs/Float32MultiArray 정책 출력 벡터 (정규화 후)
/lab/safety_event lab_msgs/SafetyEvent 안전 래퍼 개입: 종류, 임계값, 측정값, 시각
/lab/trial lab_msgs/TrialEvent 시도 시작·종료, 시나리오 id, 결과 태그

lab_msgs는 플랫폼 저장소의 ROS 2 패키지다.

16.2.2 좌표계·순서 규약 (deploy/frames.yaml)

schema_version: 1
robot: RB5-850
base_frame: base_link
joint_order: [j1, j2, j3, j4, j5, j6]          # 시뮬 에셋의 관절 순서와 동일
ft_frame: ft_sensor_link                       # 축 방향은 REP-103 (x 전방, y 좌, z 상)
ft_sign: [1, 1, 1, 1, 1, 1]                    # 제조사 축이 다르면 여기서 보정
tool_frame: tcp
units: {angle: rad, force: N, torque: Nm}

시뮬 에셋의 관절 순서와 joint_order가 다르면 CI ①에서 실패한다.

16.2.3 시간과 샘플링

  • 모든 표준 토픽의 타임스탬프는 ROS time 단일 클록. 센서 자체 클록은 쓰지 않는다.
  • /lab/joint_state, /lab/joint_cmd, /lab/ft는 정책 제어 주기의 정수배로 기록한다. 기본은 제어 주기와 동일(io_spec.control_hz).
  • 표준 토픽 사이의 시각 정렬은 인덱서가 joint_cmd 기준으로 최근접 보간한다.

16.2.4 bag 메타데이터 (metadata.json, bag과 같은 디렉터리, 필수)

{
  "schema_version": 1,
  "trial_id": "uuid",
  "project": "<slug>",
  "policy_ref": {"git": "...", "config_hash": "...", "asset_hash": "...", "image": "..."},
  "policy_stage": "validated",
  "robot": "real:RB5-850",
  "frames_version": "<deploy/frames.yaml의 SHA-256>",
  "calibration_version": "<캘리브레이션 파일 SHA-256>",
  "scenario_id": "peg_round_offset_2mm",
  "rollout_step": "tethered",
  "result": "success | fail:<type> | aborted:<reason>",
  "safety_intervention": false,
  "operator": "<github_login>",
  "started_at": "ISO-8601", "ended_at": "ISO-8601",
  "notes": ""
}

result의 실패 유형은 프로젝트가 eval_suite/failure_types.yaml에 정의한 목록에서만 고른다.

16.2.5 저장 경로와 변환

SeaweedFS
  telemetry/<project>/<policy_ref_short>/<trial_id>/   ← bag + metadata.json (원본, 180일)
  telemetry-parquet/<project>/<trial_id>.parquet       ← 표준 토픽만, 시각 정렬 완료 (영구)

Parquet 컬럼: t(s, 시도 시작 기준), joint_pos[*], joint_vel[*], joint_tau[*], cmd[*], ft[6], obs[*], action[*], safety_flag. 연구자와 real2sim 도구는 bag이 아니라 Parquet을 읽는다.

16.2.6 인덱서 검증 규칙

다음 중 하나라도 위반하면 시도를 rejected로 표시하고 프로젝트 Slack 채널에 알린다. 거부된 시도는 통계·보정에 쓰이지 않는다.

  • metadata.json 누락 또는 필수 키 누락
  • 표준 토픽 중 /lab/joint_state, /lab/joint_cmd, /lab/trial 누락
  • joint_order 길이와 JointState.name 길이 불일치
  • policy_ref가 registry에 없음
  • 타임스탬프 역행 또는 1초 이상 공백

16.3 데이터셋 버전 관리

  • 텔레메트리 시도 묶음, 시스템 식별 결과, 센서 캘리브레이션, 지형·물체 스캔은 프로젝트의 datasets/<name>/에 DVC로 추적한다.
  • 각 데이터셋 디렉터리는 manifest.yaml을 가진다.
schema_version: 1
name: rb5_peg_telemetry_v3
kind: telemetry | sysid | calibration | scan
source_trials: [uuid, uuid, ...]       # kind=telemetry일 때 필수
created_by: <github_login>
created_at: ISO-8601
parent: rb5_peg_telemetry_v2           # 파생 관계
description: ""
  • 데이터셋은 dvc.lock에 포함되므로 에셋 hash에 자동으로 들어간다. 따라서 어떤 정책이 어떤 실기 데이터로 보정된 시뮬에서 학습됐는지가 계보로 남는다.
  • 데이터셋은 수정하지 않고 새 버전을 만든다. source_trials는 추가만 가능하다.

16.4 평가 리포트 확장

3.4의 report.json에 다음을 추가한다.

"schema_version": 1,
"per_seed": {"<metric>": [v0, v1, v2, v3, v4]},
"scenario_defs_hash": "<eval_suite 파일 SHA-256>",
"failure_counts": {"<failure_type>": n}

per_seed는 8장 회귀 검사의 t-test 입력이며, failure_counts의 키는 16.2.4와 같은 failure_types.yaml을 쓴다. 시뮬 실패 유형과 실기 실패 유형이 같은 어휘를 쓰는 것이 ⑦ 비교의 전제다.

16.5 규격 변경 절차

  1. 플랫폼 저장소에 docs/adr/data-schema-<n>.md로 변경 사유와 판독기 호환 계획 기록
  2. schema_version 증가, 옛 버전 판독기 유지
  3. 활성 프로젝트 소유자 공지 후 2주 유예 (15장 변경 정책과 동일)
  4. 유예 종료 후 CI와 인덱서가 새 버전을 요구

17. 기술 스택 버전 고정과 compose

17.1 버전 선정 원칙

  • 각 계층에 도구 하나. 그 도구의 "최신 마이너의 직전 안정 마이너"를 기본으로 잡는다. 최신 마이너는 첫 패치 두 개가 나온 뒤에만 올린다.
  • 버전은 이 문서가 아니라 infra/versions.yaml 하나에 적고, Helm values·Dockerfile·compose가 전부 그 파일을 참조한다. 이 문서의 표는 2026-09-12 기준 확인값이며 설치 시 versions.yaml을 다시 확인한다.
  • 업그레이드는 분기 1회 창구에서 한 번에, 재설치 리허설(4.2)과 함께 수행한다.

17.2 확정 스택 (2026-09-12 확인)

계층 도구 고정 버전 비고
OS Ubuntu 24.04 LTS ROS 2 Jazzy와 Isaac Sim 5.x 지원
GPU 드라이버 NVIDIA 580 계열 호스트 설치, GPU Operator는 driver 비활성
클러스터 k3s v1.35.x (stable 채널) 최신은 v1.36.4+k3s1(2026-08); 1.36은 12월 창구에서
GPU 스케줄링 NVIDIA GPU Operator v26.3.x 26.7.0 존재, 1.36 지원 확인 후 상향. MIG 미사용, DRA 비활성
패키지 관리 Helm 3.x 최신
워크플로 Argo Workflows v4.1.x (차트 argo-workflows 2.0.x) 3.7은 2026-08 이후 EOL 예정
탐색 Ray (Tune) 2.58.x 학습 이미지에 ray[tune]
실험 추적 MLflow 3.15.x 3.x의 LoggedModel·alias 기반 registry 사용
추적 DB PostgreSQL 16.x MLflow와 RLflow가 인스턴스 공유, DB 분리
객체 저장소 SeaweedFS 3.x 최신 안정 태그 (설치 시 고정) 17.3 참조
관측 kube-prometheus-stack 최신 안정 차트 (Prometheus 3.x, Grafana 12.x, Alertmanager 포함) 설치 시 차트 버전 고정
GPU 메트릭 DCGM Exporter GPU Operator 번들
SSO 프록시 oauth2-proxy 7.x Argo·Grafana·MLflow 앞단
컨테이너 런타임 containerd k3s 번들 (2.x)
Python CPython 3.11 Isaac Lab 2.x 요구 버전과 일치
Python 패키지 uv 최신 uv.lock 커밋 필수
config Hydra / OmegaConf 1.3.x / 2.3.x
데이터 버전 DVC 3.x 원격은 SeaweedFS S3
학습 프레임워크 PyTorch 2.x (Isaac Lab 요구 버전 고정)
시뮬 Isaac Lab / MuJoCo(MJX) 2.x / 3.x 프로젝트 템플릿별 고정
추론 ONNX Runtime 1.2x 실기 노드
로봇 미들웨어 ROS 2 Jazzy (LTS, 2029까지)
RLflow API FastAPI 0.1xx 최신 Python 3.12 가능
RLflow 웹 React / Vite / shadcn / Tailwind React 19, Tailwind 4

17.3 객체 저장소 결정: MinIO 제외

MinIO 커뮤니티 에디션은 2025년 5월 관리 콘솔 제거, 2025년 10월 바이너리·이미지 배포 중단, 2025년 12월 유지보수 모드 선언을 거쳐 2026년 4월 25일 저장소가 아카이브되었다. 보안 패치가 보장되지 않으므로 신규 인프라의 저장소로 쓰지 않는다.

대체는 SeaweedFS로 결정한다. 근거: Apache 2.0, 활발한 릴리스, S3 게이트웨이와 공식 Helm 차트, 대용량 파일(체크포인트·rosbag) 처리에 적합, 필러 메타데이터를 PostgreSQL에 둘 수 있어 백업 경로가 기존 DB 백업과 합쳐진다. 후보였던 Garage는 소규모 단일 노드에 더 단순하지만 대용량·다중 노드 확장이 약하고, RustFS는 MinIO 호환성이 가장 높지만 아직 이력이 짧다. 세 후보의 비교는 docs/adr/object-storage.md에 남긴다.

SeaweedFS 구성: master 1, volume 2(노드 A·B 각 1, 복제 001), filer 1(메타데이터 PostgreSQL), S3 게이트웨이 1. 버킷: mlflow-artifacts, dvc, telemetry, telemetry-parquet, backup.

17.4 배포 형태 구분

  • 운영(랩 클러스터): 전부 Helm으로 k3s에 배포한다. compose는 쓰지 않는다. Argo Workflows와 GPU Operator는 Kubernetes 전용이라 compose로 실행할 수 없다.
  • 개발(개발자 노트북, RLflow 작업용): compose/dev.yaml로 PostgreSQL, SeaweedFS, MLflow, RLflow API·웹을 띄운다. Argo·Prometheus는 어댑터의 페이크 구현(integrations/fake_argo.py, integrations/fake_prom.py)으로 대체하거나, 필요 시 k3d로 로컬 k3s를 띄워 실제 어댑터를 붙인다.
  • CI(통합 테스트): 같은 compose/dev.yaml을 GitHub Actions 서비스로 사용한다.

17.5 compose/dev.yaml

이미지 태그는 infra/versions.yaml에서 .env로 생성해 주입한다. 아래는 구조를 보여주는 초안이며 태그는 자리표시자다.

name: rlflow-dev

services:
  postgres:
    image: postgres:${POSTGRES_TAG}
    environment:
      POSTGRES_USER: lab
      POSTGRES_PASSWORD: lab
      POSTGRES_DB: rlflow
    volumes:
      - pg:/var/lib/postgresql/data
      - ./init-db.sql:/docker-entrypoint-initdb.d/init.sql:ro   # mlflow, seaweedfs_filer DB 추가 생성
    ports: ["5432:5432"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U lab"]
      interval: 5s
      timeout: 3s
      retries: 20

  seaweedfs-master:
    image: chrislusf/seaweedfs:${SEAWEEDFS_TAG}
    command: master -ip=seaweedfs-master -port=9333 -mdir=/data
    volumes: ["sw-master:/data"]

  seaweedfs-volume:
    image: chrislusf/seaweedfs:${SEAWEEDFS_TAG}
    command: volume -mserver=seaweedfs-master:9333 -ip=seaweedfs-volume -port=8080 -dir=/data -max=50
    volumes: ["sw-volume:/data"]
    depends_on: [seaweedfs-master]

  seaweedfs-filer:
    image: chrislusf/seaweedfs:${SEAWEEDFS_TAG}
    command: filer -master=seaweedfs-master:9333 -ip=seaweedfs-filer -port=8888
    volumes: ["./seaweedfs/filer.toml:/etc/seaweedfs/filer.toml:ro"]   # postgres2 필러 스토어
    depends_on:
      postgres: { condition: service_healthy }
      seaweedfs-volume: { condition: service_started }

  seaweedfs-s3:
    image: chrislusf/seaweedfs:${SEAWEEDFS_TAG}
    command: s3 -filer=seaweedfs-filer:8888 -port=8333 -config=/etc/seaweedfs/s3.json
    volumes: ["./seaweedfs/s3.json:/etc/seaweedfs/s3.json:ro"]      # 액세스 키와 버킷 정책
    ports: ["8333:8333"]
    depends_on: [seaweedfs-filer]

  s3-init:
    image: amazon/aws-cli:${AWSCLI_TAG}
    entrypoint: ["/bin/sh", "-c"]
    command: >
      "for b in mlflow-artifacts dvc telemetry telemetry-parquet; do
         aws --endpoint-url http://seaweedfs-s3:8333 s3 mb s3://$$b || true; done"
    environment:
      AWS_ACCESS_KEY_ID: lab
      AWS_SECRET_ACCESS_KEY: labsecret
      AWS_DEFAULT_REGION: us-east-1
    depends_on: [seaweedfs-s3]

  mlflow:
    image: ghcr.io/mlflow/mlflow:${MLFLOW_TAG}
    command: >
      mlflow server --host 0.0.0.0 --port 5000
      --backend-store-uri postgresql://lab:lab@postgres:5432/mlflow
      --artifacts-destination s3://mlflow-artifacts
      --serve-artifacts
    environment:
      MLFLOW_S3_ENDPOINT_URL: http://seaweedfs-s3:8333
      AWS_ACCESS_KEY_ID: lab
      AWS_SECRET_ACCESS_KEY: labsecret
    ports: ["5000:5000"]
    depends_on:
      postgres: { condition: service_healthy }
      s3-init: { condition: service_completed_successfully }

  rlflow-api:
    build: { context: ../rlflow/api }
    environment:
      DATABASE_URL: postgresql+psycopg://lab:lab@postgres:5432/rlflow
      MLFLOW_TRACKING_URI: http://mlflow:5000
      S3_ENDPOINT_URL: http://seaweedfs-s3:8333
      ARGO_ADAPTER: fake            # 운영은 k8s
      PROM_ADAPTER: fake
      GITHUB_ADAPTER: fake          # 로컬은 웹훅 수신 불가
      OIDC_ISSUER: http://dex:5556  # 로컬 OIDC
    ports: ["8000:8000"]
    depends_on:
      postgres: { condition: service_healthy }
      mlflow: { condition: service_started }

  rlflow-web:
    build: { context: ../rlflow/web }
    environment:
      VITE_API_URL: http://localhost:8000
    ports: ["5173:5173"]
    depends_on: [rlflow-api]

  dex:
    image: ghcr.io/dexidp/dex:${DEX_TAG}
    command: dex serve /etc/dex/config.yaml
    volumes: ["./dex/config.yaml:/etc/dex/config.yaml:ro"]   # 정적 사용자 2명(admin, researcher)
    ports: ["5556:5556"]

volumes:
  pg:
  sw-master:
  sw-volume:

로컬에서 실제 Argo 어댑터를 시험할 때는 k3d cluster create rlflow-dev로 k3s를 띄우고 infra/helm/argo-workflows를 같은 values로 설치한 뒤 ARGO_ADAPTER=k8s로 바꾼다. GPU가 있는 노트북이 아니어도 학습 파드 대신 sleep 파드로 워크플로 구조를 검증할 수 있게 infra/argo/train.yamldry_run 파라미터를 둔다.

17.6 Helm values 고정 (infra/helm/)

각 디렉터리에 Chart.lock에 준하는 chart-version 파일과 values.yaml을 둔다. 필수 고정 항목:

차트 고정 항목
gpu-operator driver.enabled=false, toolkit.enabled=true, dcgmExporter.enabled=true, mig.strategy=none, devicePlugin.config로 time-slicing 비활성
argo-workflows controller.workflowDefaultsttlStrategy, podGC, retryStrategy; artifact repository를 SeaweedFS S3로; server.authModes=[sso], oauth2-proxy 연동
seaweedfs volume 복제 001, filer 스토어 postgres2, S3 활성, 노드 A·B nodeSelector
postgresql 단일 인스턴스, local-path PVC, 매일 pg_dump CronJob
mlflow 커뮤니티 차트 또는 자체 Deployment. backend-store PostgreSQL, artifacts SeaweedFS, --serve-artifacts
kube-prometheus-stack 보존 30일, additionalScrapeConfigs에 Pushgateway, grafana.grafana.iniallow_embedding=true와 OIDC
oauth2-proxy GitHub 조직 제한, Argo·Grafana·MLflow 인그레스 앞단
rlflow 자체 차트. API·웹 Deployment, PostgreSQL 연결, OIDC

17.7 이미지 태그 규칙 (images/)

lab/base:cu128-torch2.x-py311-<YYYYMMDD>
lab/isaaclab:2.x-<base 태그>
lab/mujoco:3.x-<base 태그>
lab/rlflow-api:<git short sha>
lab/rlflow-web:<git short sha>

베이스 이미지는 월 1회 재빌드하되 프로젝트는 태그를 명시적으로 올릴 때만 바뀐다. latest 태그는 어디에도 쓰지 않는다.


18. 범위 밖 (명시적으로 하지 않는 것)

  • 멀티노드 분산 학습
  • 실기 A/B 테스트나 섀도 배포
  • 자체 스케줄러, 자체 실험 추적기, 자체 시각화 도구 구현
  • LLM 기반 자동 보상 설계 (향후 별도 프로젝트로 검토)
  • 클라우드 GPU 버스팅 (노드가 4대를 넘을 때 재검토)