Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions prepareModelPresentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env python3

"""
[발표 전 실행]
- atio 모델 스냅샷 시연을 위한 '무거운' 준비 스크립트
- 약 1GB의 더미 모델 파일 2개(v1, v2)를 생성합니다.
- atio.write_model_snapshot을 미리 실행하여 'ATIO_MODEL_SNAPSHOT' 폴더를 생성합니다.
- 실행에 1~2분 정도 소요될 수 있습니다.
"""

import os
import shutil
import numpy as np
import torch
import atio
import sys

# --- 설정 ---
DEMO_DIR = "ATIO_MODEL_DEMO_PREP"
MODEL_V1_PATH = os.path.join(DEMO_DIR, "model_v1_base.pth")
MODEL_V2_PATH = os.path.join(DEMO_DIR, "model_v2_finetuned.pth")
SNAPSHOT_PATH = "ATIO_MODEL_SNAPSHOT" # 메인 데모 스크립트가 참조할 경로

# --- 헬퍼 ---
def print_header(title):
print("\n" + "=" * 70)
print(f"🔧 {title}")
print("=" * 70)

# (create_dummy_model_file 함수는 이제 사용되지 않으므로 삭제하거나 주석 처리)
# def create_dummy_model_file(path, size_mb, change_ratio=0.0):
# ...

def setup():
# 이전 데모 파일/폴더 정리
shutil.rmtree(DEMO_DIR, ignore_errors=True)
shutil.rmtree(SNAPSHOT_PATH, ignore_errors=True)
os.makedirs(DEMO_DIR, exist_ok=True)

print_header("1. 더미 모델 파일 생성 (v1, v2)")

# --- v1 생성 ---
print(f" > v1: '{os.path.basename(MODEL_V1_PATH)}' 생성 중 (약 500MB)...")
size_mb = 500
num_elements = (size_mb * 1024 * 1024) // 4

# 100% 랜덤 데이터로 v1 텐서 생성
v1_tensor = torch.randn(num_elements)
v1_state_dict = {'layer1.weight': v1_tensor}
torch.save(v1_state_dict, MODEL_V1_PATH)
print(f" ✅ v1 생성 완료 (크기: {os.path.getsize(MODEL_V1_PATH) / 1024 / 1024:.0f}MB)")

# --- v2 생성 (v1 기반으로 1% 수정) ---
print(f" > v2: '{os.path.basename(MODEL_V2_PATH)}' 생성 중 (v1에서 1% 수정)...")

# v1 텐서를 그대로 복제 (99% 동일)
v2_tensor = v1_tensor.clone()

# 1%만 새로운 데이터로 덮어쓰기
change_ratio = 0.01
change_elements = int(num_elements * change_ratio)
v2_tensor[:change_elements] = torch.randn(change_elements) # 이 부분만 v1과 다름

v2_state_dict = {'layer1.weight': v2_tensor}
torch.save(v2_state_dict, MODEL_V2_PATH)
print(f" ✅ v2 생성 완료 (크기: {os.path.getsize(MODEL_V2_PATH) / 1024 / 1024:.0f}MB)")


print_header("2. atio 모델 스냅샷 생성 (v1, v2)")
print(" > v1 스냅샷 저장 중 (500MB)...")
atio.write_model_snapshot(MODEL_V1_PATH, SNAPSHOT_PATH, show_progress=True)
print(" ✅ v1 스냅샷 저장 완료.")

print("\n > v2 스냅샷 저장 중 (v1과 비교하여 변경분 1%만 저장)...")
atio.write_model_snapshot(MODEL_V2_PATH, SNAPSHOT_PATH, show_progress=True)
print(" ✅ v2 스냅샷 저장 완료.")

print_header("3. 결과 확인")
size_v1 = os.path.getsize(MODEL_V1_PATH)
size_v2 = os.path.getsize(MODEL_V2_PATH)

# 스냅샷 'data' 폴더의 실제 크기 계산
snapshot_data_dir = os.path.join(SNAPSHOT_PATH, "data")
snapshot_size = sum(
os.path.getsize(os.path.join(snapshot_data_dir, f))
for f in os.listdir(snapshot_data_dir)
if os.path.isfile(os.path.join(snapshot_data_dir, f))
)

print(f" > 원본 v1 크기: {size_v1 / 1024 / 1024:,.0f} MB")
print(f" > 원본 v2 크기: {size_v2 / 1024 / 1024:,.0f} MB")
print(f" > 원본 총합: {(size_v1 + size_v2) / 1024 / 1024:,.0f} MB")
print("-" * 30)
# (수정) f-string 포맷팅 수정
print(f" > atio 스냅샷 크기: {snapshot_size / 1024 / 1024:,.0f} MB (v1 + v2 변경분)")
print("\n🎉 준비 완료! 'MAIN_DEMO_SCRIPT.py'를 실행하여 시연을 시작하세요.")

if __name__ == "__main__":
try:
import torch
except ImportError:
print("❌ 'torch'가 필요합니다. 'pip install torch'로 설치해주세요.")
sys.exit(1)

setup()
286 changes: 286 additions & 0 deletions presentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
#!/usr/bin/env python3

"""
[발표 시연용 메인 스크립트]

- Act 1: atio.write (원자적 쓰기) - Pandas CSV 손상 및 보호 시연
- Act 2: atio.write_snapshot (데이터 스냅샷) - Tag, Delete, Revert 시연
- Act 3: atio.write_model_snapshot (모델 스냅샷) - 저장 공간 절약 및 복원 시연
"""

import os
import shutil
import multiprocessing
import time
import pandas as pd
import numpy as np
import polars as pl

# --- 모듈 경로 설정을 위한 코드 ---
try:
import atio
except ImportError:
print("Error: 'atio' 라이브러리를 찾을 수 없습니다.")
import sys
project_root = os.path.dirname(os.path.abspath(__file__))
src_path = os.path.join(project_root, 'src')
if src_path not in sys.path:
sys.path.insert(0, src_path)
try:
import atio
except ImportError:
sys.exit("atio 모듈 로드에 실패했습니다.")
# ------------------------------------

# Act 2 헬퍼를 위한 Rich 임포트
try:
from rich.console import Console
from rich.table import Table
RICH_AVAILABLE = True
console = Console()
except ImportError:
RICH_AVAILABLE = False
print("Info: 'rich' 라이브러리가 없으면 Act 2의 스냅샷 목록이 간단하게 출력됩니다.")

# --- 공통 헬퍼 ---
def print_header(title):
print("\n" + "=" * 70)
print(f"🧪 {title}")
print("=" * 70)

def pause_for_user(message):
print("\n" + "-" * 50)
print(f"🔵 [발표자 확인] {message}")
print(" (확인 후 Enter 키를 누르면 다음 단계로 진행합니다...)")
input()
print("-" * 50)

def cleanup_all(dirs):
print("\n" + "=" * 70)
print("🧹 모든 시연 디렉토리 정리 중...")
for d in dirs:
shutil.rmtree(d, ignore_errors=True)
print("✅ 정리 완료.")

# ========== Act 1: atio.write (원자적 쓰기) ==========

DEMO_DIR_ACT1 = "ATIO_DEMO_ACT1_ATOMIC"
PANDAS_UNSAFE_FILE = os.path.join(DEMO_DIR_ACT1, "1_pandas_UNSAFE.csv")
PANDAS_SAFE_FILE = os.path.join(DEMO_DIR_ACT1, "2_pandas_SAFE_atio.csv")

# 5x5 원본 데이터
data_5x5 = np.arange(1, 26).reshape(5, 5)
df_pd_original = pd.DataFrame(data_5x5, columns=['A', 'B', 'C', 'D', 'E'])

# 대용량 수정본 데이터
try:
large_pd_df = pd.DataFrame(np.random.randn(5_000_000, 10))
except MemoryError:
large_pd_df = pd.DataFrame(np.random.randn(1_000_000, 5))

def setup_act1():
shutil.rmtree(DEMO_DIR_ACT1, ignore_errors=True)
os.makedirs(DEMO_DIR_ACT1, exist_ok=True)

df_pd_original.to_csv(PANDAS_UNSAFE_FILE, index=False)
df_pd_original.to_csv(PANDAS_SAFE_FILE, index=False)
print(f"✅ Act 1 준비 완료. '{os.path.abspath(DEMO_DIR_ACT1)}' 폴더 생성됨.")

def task_unsafe_pandas(path, data):
print(f" [PID: {os.getpid()}] (Unsafe) Pandas 쓰기 시작...")
data.to_csv(path, index=False)

def task_safe_atio(path, data, format, **kwargs):
print(f" [PID: {os.getpid()}] (Safe) atio.write 시작...")
atio.write(data, path, format=format, verbose=True, **kwargs)

def demo_act_1_atomic_write():
print_header("Act 1: atio.write (원자적 쓰기) 시연")
setup_act1()

# --- 1-1. Unsafe (Pandas) ---
pause_for_user(f"'{os.path.basename(PANDAS_UNSAFE_FILE)}' 파일을 뷰어로 열어 '정상 (5x5)'임을 확인하세요.")
print(f" [Main] 'Unsafe Pandas' 쓰기 프로세스를 50ms 후 강제 종료합니다...")
p_unsafe_pd = multiprocessing.Process(target=task_unsafe_pandas, args=(PANDAS_UNSAFE_FILE, large_pd_df))
p_unsafe_pd.start()
time.sleep(0.05)
p_unsafe_pd.terminate()
p_unsafe_pd.join()
print(f" [Main] 💥 'Unsafe Pandas' 프로세스 강제 종료 완료.")
pause_for_user(f"'{os.path.basename(PANDAS_UNSAFE_FILE)}' 파일을 뷰어로 다시 열어 '손상'되었는지(0바이트 등) 확인하세요.")

# --- 1-2. Safe (atio) ---
pause_for_user(f"'{os.path.basename(PANDAS_SAFE_FILE)}' 파일을 뷰어로 열어 '정상 (5x5)'임을 확인하세요.")
print(f" [Main] 'Safe atio (csv)' 쓰기 프로세스를 50ms 후 강제 종료합니다...")
p_safe_pd = multiprocessing.Process(target=task_safe_atio, args=(PANDAS_SAFE_FILE, large_pd_df, "csv"), kwargs={"index": False})
p_safe_pd.start()
time.sleep(0.05)
p_safe_pd.terminate()
p_safe_pd.join()
print(f" [Main] 💥 'Safe atio' 프로세스 강제 종료 완료.")
pause_for_user(f"'{os.path.basename(PANDAS_SAFE_FILE)}' 파일을 뷰어로 다시 열어 '완벽하게 보존'되었는지 확인하세요.")

# ========== Act 2: atio.write_snapshot (Tag, Delete, Revert) ==========

DEMO_DIR_ACT2 = "ATIO_DEMO_ACT2_SNAPSHOT"

def list_snapshots_helper(table_path):
"""(신규) 스냅샷 목록을 cli.py처럼 예쁘게 출력하는 헬퍼 함수"""
print("\n--- [스냅샷 목록] ---")
snapshots = atio.list_snapshots(table_path)

if not RICH_AVAILABLE:
for s in snapshots:
tags = f" (Tags: {', '.join(s['tags'])})" if s['tags'] else ""
latest = " (LATEST)" if s['is_latest'] else ""
print(f" v{s['version_id']}: {s['message']}{tags}{latest}")
return

table = Table(title=f"History for '{os.path.basename(table_path)}'")
table.add_column("Ver", style="cyan")
table.add_column("Latest", style="magenta")
table.add_column("Tags", style="green")
table.add_column("Message")

for s in snapshots:
table.add_row(
str(s['version_id']),
"✅" if s['is_latest'] else "",
", ".join(s['tags']),
s['message']
)
console.print(table)


def setup_act2():
shutil.rmtree(DEMO_DIR_ACT2, ignore_errors=True)

# v1: 5x5 원본
v1_data = df_pd_original

# v2: 13번 값을 999로 수정
v2_data = v1_data.copy()
v2_data.at[2, 'C'] = 999

# v3: 'NEW_COL' 추가
v3_data = v1_data.copy()
v3_data['NEW_COL'] = ['a', 'b', 'c', 'd', 'e']

print(" > v1, v2, v3 스냅샷 순차 생성 중...")
atio.write_snapshot(v1_data, DEMO_DIR_ACT2, message="v1: Base 5x5 data")
pause_for_user("스냅샷 v1이 생성되었습니다.")
atio.write_snapshot(v2_data, DEMO_DIR_ACT2, message="v2: Added 999")
pause_for_user("스냅샷 v2이 생성되었습니다.")
# atio.write_snapshot(v3_data, DEMO_DIR_ACT2, message="v3: Added NEW_COL")
# pause_for_user("스냅샷 v3이 생성되었습니다.")
print("✅ Act 2 준비 완료.")

def demo_act_2_data_snapshot():
print_header("Act 2: atio.write_snapshot (Tag, Delete, Revert) 시연")
setup_act2()

pause_for_user("v1, v2(값 수정) 스냅샷을 생성했습니다. (v2가 최신)")
list_snapshots_helper(DEMO_DIR_ACT2)

# --- 1. Tag ---
pause_for_user("v2에 태그(final_release)를 지정합니다. [atio.tag_version]")
atio.tag_version(DEMO_DIR_ACT2, version_id=2, tag_name="final_release")
list_snapshots_helper(DEMO_DIR_ACT2)

# --- 2. Delete ---
pause_for_user("v1는 삭제합니다. [atio.delete_version]")
atio.delete_version(DEMO_DIR_ACT2, version_id=1)
print("\n ✅ v1 삭제 완료!")
list_snapshots_helper(DEMO_DIR_ACT2)

# # --- 3. Revert ---
# pause_for_user("v1을 'revert'하여 v4를 생성합니다. [atio.revert]")
# atio.revert(DEMO_DIR_ACT2, version_id_to_revert=1, message="Revert to v1 state")
# print("\n ✅ v1의 상태로 v4 생성 완료!")
# list_snapshots_helper(DEMO_DIR_ACT2)

# pause_for_user("Revert 후 '최신' 버전을 다시 읽습니다.")
# latest_v4 = atio.read_table(DEMO_DIR_ACT2)
# print("--- [Revert 후 최신 v4] --- (v1과 동일) ---")
# print(latest_v4)
# # print("\n (v4가 최신이 되었고, 내용은 v1과 동일한 5x5 데이터인 것을 확인)")

# ========== Act 3: atio.write_model_snapshot (공간 절약) ==========

MODEL_SNAPSHOT_PATH = "ATIO_MODEL_SNAPSHOT" # [준비] 스크립트가 생성한 폴더
DEMO_DIR_ACT3_RESTORE = "ATIO_DEMO_ACT3_RESTORE"

def setup_act3():
shutil.rmtree(DEMO_DIR_ACT3_RESTORE, ignore_errors=True)
os.makedirs(DEMO_DIR_ACT3_RESTORE, exist_ok=True)

if not os.path.exists(MODEL_SNAPSHOT_PATH):
print("="*70)
print("❌ [치명적 오류] Act 3 시연 준비가 안 됐습니다!")
print(f" > '{MODEL_SNAPSHOT_PATH}' 폴더를 찾을 수 없습니다.")
print(" > 'prepare_model_demo.py' 스크립트를 먼저 실행해주세요.")
print("="*70)
return False

print(f"✅ Act 3 준비 완료. '{MODEL_SNAPSHOT_PATH}' 폴더 확인됨.")
return True

def demo_act_3_model_snapshot():
print_header("Act 3: atio.write_model_snapshot (공간 절약) 시연")
if not setup_act3():
return # 준비 안 됐으면 중단

# 1. 공간 절약 확인 (시각적)
pause_for_user(f"'{MODEL_SNAPSHOT_PATH}' 폴더를 확인.")

print(" [설명] 준비 스크립트가 500MB(v1) + 505MB(v2) = 총 1,005MB의 모델을 생성했습니다.")
print(" 하지만 atio 스냅샷 폴더의 'data' 폴더 용량을 확인해보세요.")
print(" > (예상) 약 505MB~510MB일 것입니다. (v1 전체 + v2 변경분 1%)")
print(" > `atio`가 중복을 제거하고 변경된 부분만 저장하여 500MB를 절약했습니다.")

# 2. 복원 시연
pause_for_user("이제 이 작은 스냅샷에서 v1과 v2를 모두 복원합니다. [atio.read_model_snapshot]")

restore_v1_path = os.path.join(DEMO_DIR_ACT3_RESTORE, "RESTORED_v1.pth")
restore_v2_path = os.path.join(DEMO_DIR_ACT3_RESTORE, "RESTORED_v2.pth")

print(" > v1 복원 중...")
atio.read_model_snapshot(MODEL_SNAPSHOT_PATH, version=1, mode='restore',
destination_path=restore_v1_path, show_progress=True)

print(" > v2 복원 중...")
atio.read_model_snapshot(MODEL_SNAPSHOT_PATH, version=2, mode='restore',
destination_path=restore_v2_path, show_progress=True)

print(" ✅ v1, v2 복원 완료!")
pause_for_user(f"'{DEMO_DIR_ACT3_RESTORE}' 폴더를 확인하세요. 500MB짜리 v1과 v2가 모두 복원되었습니다.")


# ========== 메인 실행 ==========

def main():
all_demo_dirs = [DEMO_DIR_ACT1, DEMO_DIR_ACT2, DEMO_DIR_ACT3_RESTORE,
"ATIO_MODEL_DEMO_PREP", "ATIO_MODEL_SNAPSHOT"]

try:
# Act 1: 원자적 쓰기 (파일 손상 방지)
demo_act_1_atomic_write()

# Act 2: 데이터 스냅샷 (Tag, Delete, Revert)
demo_act_2_data_snapshot()

# Act 3: 모델 스냅샷 (공간 절약)
demo_act_3_model_snapshot()

except Exception as e:
print(f"\n" + "!" * 70)

if __name__ == "__main__":
main()
# cleanup_all([
# DEMO_DIR_ACT1,
# DEMO_DIR_ACT2,
# DEMO_DIR_ACT3_RESTORE,
# "ATIO_MODEL_DEMO_PREP",
# "ATIO_MODEL_SNAPSHOT"
# ])