- Added comprehensive plans for training a LightGBM model on M4 Mac Mini and deploying it to an LXC container. - Created scripts for model training, deployment, and a full pipeline execution. - Enhanced model transfer with error handling and logging for better tracking. - Introduced profiling for training time analysis and dataset generation optimization. Made-with: Cursor
636 lines
19 KiB
Markdown
636 lines
19 KiB
Markdown
# M4 Mac Mini 가속 학습 구현 계획
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** M4 맥미니의 GPU(Metal/MPS)를 활용해 모델 학습 속도를 높이고, Neural Engine 활용 가능 여부를 검토한다.
|
||
|
||
**Architecture:** 현재 LightGBM CPU 학습 파이프라인을 유지하면서, 데이터셋 생성 단계(병렬 CPU 연산)와 LightGBM 학습 단계를 각각 최적화한다. LightGBM은 Apple Silicon GPU를 공식 지원하지 않으므로, (1) MetalGBM 실험적 대체, (2) PyTorch MPS 기반 신경망 필터 추가, (3) 현재 CPU 파이프라인 최적화 세 가지 경로를 단계별로 시도한다.
|
||
|
||
**Tech Stack:** Python 3.13, LightGBM 4.6, MetalGBM(실험), PyTorch(MPS), Apple MLX, scikit-learn 1.8
|
||
|
||
---
|
||
|
||
## 배경 및 제약사항 분석
|
||
|
||
### M4 맥미니 하드웨어 구조
|
||
- **CPU**: 10코어 (P코어 4 + E코어 6)
|
||
- **GPU**: 10코어 통합 GPU (Metal 지원)
|
||
- **Neural Engine (NPU)**: 38 TOPS — 행렬 연산 특화, Apple 전용 API로만 접근 가능
|
||
- **통합 메모리**: CPU/GPU/NPU가 동일 메모리 공유 → 데이터 복사 오버헤드 없음
|
||
|
||
### 현재 학습 파이프라인 병목 분석
|
||
```
|
||
[1단계] 데이터셋 생성: multiprocessing.Pool → CPU 병렬
|
||
- _process_index(): 각 캔들에서 Indicators 계산 + 피처 추출
|
||
- 약 129,000개 인덱스 처리 (90일 × 1440분)
|
||
- 현재 병목: Python GIL 우회는 됐지만 pickle 직렬화 오버헤드 큼
|
||
|
||
[2단계] LightGBM 학습: CPU 전용
|
||
- n_estimators=300, 샘플 수 ~수천 개
|
||
- 실제 학습 시간은 짧음 (수초~수십초)
|
||
- GPU 가속 효과 미미할 가능성 높음
|
||
```
|
||
|
||
### 각 가속 방법의 현실적 평가
|
||
|
||
| 방법 | 효과 | 난이도 | 권장 여부 |
|
||
|------|------|--------|-----------|
|
||
| Neural Engine 직접 사용 | ❌ 불가 (Apple 내부 전용) | - | 불가 |
|
||
| LightGBM GPU (Metal) | ❌ 공식 미지원 | 높음 | 비권장 |
|
||
| MetalGBM | ⚠️ 실험적 (2025.11 신생) | 중간 | 실험 가능 |
|
||
| PyTorch MPS 신경망 | ✅ 가능, 소규모 모델은 CPU보다 느릴 수 있음 | 중간 | 조건부 권장 |
|
||
| Apple MLX 신경망 | ✅ Apple Silicon 최적화 | 중간 | 권장 |
|
||
| CPU 파이프라인 최적화 | ✅ 즉각 효과 | 낮음 | **최우선 권장** |
|
||
|
||
> **핵심 결론**: 현재 학습 샘플 수(수천 개)와 피처 수(13개)에서는 LightGBM 자체 학습 시간이 매우 짧다. 실제 병목은 **데이터셋 생성(1단계)** 이며, 이를 먼저 최적화하는 것이 가장 효과적이다. GPU/NPU 가속은 신경망 모델로 전환 시 의미가 있다.
|
||
|
||
---
|
||
|
||
## Task 1: 현재 학습 시간 프로파일링
|
||
|
||
**Files:**
|
||
- Create: `scripts/profile_training.py`
|
||
|
||
**Step 1: 프로파일링 스크립트 작성**
|
||
|
||
```python
|
||
"""
|
||
학습 파이프라인 각 단계의 소요 시간을 측정한다.
|
||
사용법: python scripts/profile_training.py --data data/xrpusdt_1m.parquet
|
||
"""
|
||
import sys
|
||
from pathlib import Path
|
||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
||
import time
|
||
import argparse
|
||
import pandas as pd
|
||
from scripts.train_model import generate_dataset, _cgroup_cpu_count
|
||
|
||
def profile(data_path: str):
|
||
print(f"데이터 로드: {data_path}")
|
||
df = pd.read_parquet(data_path)
|
||
print(f"캔들 수: {len(df)}")
|
||
|
||
workers = max(1, _cgroup_cpu_count() - 1)
|
||
print(f"사용 코어: {workers}")
|
||
|
||
t0 = time.perf_counter()
|
||
dataset = generate_dataset(df)
|
||
t1 = time.perf_counter()
|
||
print(f"\n[결과] 데이터셋 생성: {t1-t0:.1f}초, 샘플 {len(dataset)}개")
|
||
|
||
import lightgbm as lgb
|
||
from sklearn.model_selection import train_test_split
|
||
from src.ml_features import FEATURE_COLS
|
||
X = dataset[FEATURE_COLS]
|
||
y = dataset["label"]
|
||
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
|
||
|
||
model = lgb.LGBMClassifier(
|
||
n_estimators=300, learning_rate=0.05, num_leaves=31,
|
||
min_child_samples=20, subsample=0.8, colsample_bytree=0.8,
|
||
class_weight="balanced", random_state=42, verbose=-1,
|
||
)
|
||
t2 = time.perf_counter()
|
||
model.fit(X_train, y_train)
|
||
t3 = time.perf_counter()
|
||
print(f"[결과] LightGBM 학습: {t3-t2:.1f}초")
|
||
print(f"[결과] 전체: {t3-t0:.1f}초")
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--data", default="data/xrpusdt_1m.parquet")
|
||
args = parser.parse_args()
|
||
profile(args.data)
|
||
```
|
||
|
||
**Step 2: 프로파일링 실행**
|
||
|
||
```bash
|
||
python scripts/profile_training.py --data data/xrpusdt_1m.parquet
|
||
```
|
||
|
||
예상 출력:
|
||
```
|
||
[결과] 데이터셋 생성: XX.X초, 샘플 XXXX개
|
||
[결과] LightGBM 학습: X.X초
|
||
[결과] 전체: XX.X초
|
||
```
|
||
|
||
→ 데이터셋 생성이 전체의 90% 이상을 차지하면 Task 2로 진행
|
||
→ LightGBM 학습이 병목이면 Task 4(MetalGBM)로 진행
|
||
|
||
**Step 3: 커밋**
|
||
|
||
```bash
|
||
git add scripts/profile_training.py
|
||
git commit -m "feat: add training pipeline profiler"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: 데이터셋 생성 최적화 (CPU, 즉각 효과)
|
||
|
||
현재 `multiprocessing.Pool`의 pickle 직렬화 오버헤드를 줄이고, numpy 벡터화로 대체한다.
|
||
|
||
**Files:**
|
||
- Modify: `scripts/train_model.py`
|
||
- Create: `tests/test_train_model_perf.py`
|
||
|
||
**Step 1: 실패 테스트 작성**
|
||
|
||
```python
|
||
# tests/test_train_model_perf.py
|
||
import time
|
||
import pandas as pd
|
||
import pytest
|
||
from scripts.train_model import generate_dataset
|
||
|
||
@pytest.fixture
|
||
def sample_df():
|
||
return pd.read_parquet("data/xrpusdt_1m.parquet").iloc[:5000]
|
||
|
||
def test_dataset_generation_speed(sample_df):
|
||
"""5000개 캔들에서 데이터셋 생성이 30초 이내여야 한다."""
|
||
t0 = time.perf_counter()
|
||
dataset = generate_dataset(sample_df)
|
||
elapsed = time.perf_counter() - t0
|
||
assert elapsed < 30.0, f"너무 느림: {elapsed:.1f}초"
|
||
assert len(dataset) > 0
|
||
```
|
||
|
||
**Step 2: 테스트 실행 (실패 확인)**
|
||
|
||
```bash
|
||
pytest tests/test_train_model_perf.py -v
|
||
```
|
||
|
||
**Step 3: `train_model.py`에 `n_jobs` 자동 감지 개선 및 chunksize 튜닝**
|
||
|
||
`scripts/train_model.py`의 `generate_dataset` 함수에서:
|
||
|
||
```python
|
||
# 기존
|
||
workers = n_jobs or max(1, _cgroup_cpu_count() - 1)
|
||
chunk = max(1, len(task_args) // (workers * 10))
|
||
|
||
# 변경: M4의 P코어/E코어 혼합을 고려해 worker 수를 P코어 수로 제한
|
||
# M4 mini: 4 P코어 + 6 E코어 = 10코어. 실제 병렬 처리는 P코어 기준이 효율적
|
||
workers = n_jobs or min(max(1, _cgroup_cpu_count() - 1), 8)
|
||
# chunksize를 크게 잡아 IPC 오버헤드 감소
|
||
chunk = max(100, len(task_args) // workers)
|
||
```
|
||
|
||
`scripts/train_model.py`의 `generate_dataset` 함수 내 두 줄을 수정:
|
||
|
||
```python
|
||
workers = n_jobs or min(max(1, _cgroup_cpu_count() - 1), 8)
|
||
# ...
|
||
chunk = max(100, len(task_args) // workers)
|
||
```
|
||
|
||
**Step 4: 테스트 재실행 (통과 확인)**
|
||
|
||
```bash
|
||
pytest tests/test_train_model_perf.py -v
|
||
```
|
||
|
||
**Step 5: 커밋**
|
||
|
||
```bash
|
||
git add scripts/train_model.py tests/test_train_model_perf.py
|
||
git commit -m "perf: tune multiprocessing chunksize for M4 P-core efficiency"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: Apple MLX 기반 신경망 필터 실험 (GPU/Neural Engine 활용)
|
||
|
||
LightGBM을 대체하거나 앙상블할 수 있는 MLX 기반 경량 신경망을 구현한다. MLX는 Apple Silicon의 통합 GPU와 Neural Engine을 자동으로 활용한다.
|
||
|
||
**Files:**
|
||
- Create: `src/mlx_filter.py`
|
||
- Create: `scripts/train_mlx_model.py`
|
||
- Create: `tests/test_mlx_filter.py`
|
||
|
||
**Step 1: MLX 설치 확인 및 설치**
|
||
|
||
```bash
|
||
# venv 활성화 후
|
||
pip install mlx
|
||
python -c "import mlx.core as mx; print('MLX device:', mx.default_device())"
|
||
```
|
||
|
||
예상 출력: `MLX device: Device(gpu, 0)` (GPU 자동 사용)
|
||
|
||
**Step 2: requirements.txt에 mlx 추가**
|
||
|
||
`requirements.txt`에 다음 줄 추가:
|
||
```
|
||
mlx>=0.22.0
|
||
```
|
||
|
||
**Step 3: 실패 테스트 작성**
|
||
|
||
```python
|
||
# tests/test_mlx_filter.py
|
||
import pytest
|
||
import numpy as np
|
||
|
||
def test_mlx_available():
|
||
"""MLX가 설치되어 GPU 디바이스를 사용할 수 있어야 한다."""
|
||
import mlx.core as mx
|
||
device = mx.default_device()
|
||
assert device is not None
|
||
|
||
def test_mlx_filter_predict_shape():
|
||
"""MLXFilter가 (N,) 형태의 확률값을 반환해야 한다."""
|
||
from src.mlx_filter import MLXFilter
|
||
import pandas as pd
|
||
X = pd.DataFrame({
|
||
"rsi": [50.0], "macd_hist": [0.1], "bb_pct": [0.5],
|
||
"ema_align": [1.0], "stoch_k": [50.0], "stoch_d": [50.0],
|
||
"atr_pct": [0.01], "vol_ratio": [1.0],
|
||
"ret_1": [0.001], "ret_3": [0.002], "ret_5": [0.003],
|
||
"signal_strength": [3.0], "side": [1.0],
|
||
})
|
||
model = MLXFilter(input_dim=13, hidden_dim=64)
|
||
proba = model.predict_proba(X)
|
||
assert proba.shape == (1,)
|
||
assert 0.0 <= proba[0] <= 1.0
|
||
```
|
||
|
||
**Step 4: 테스트 실행 (실패 확인)**
|
||
|
||
```bash
|
||
pytest tests/test_mlx_filter.py -v
|
||
```
|
||
|
||
**Step 5: MLXFilter 구현**
|
||
|
||
```python
|
||
# src/mlx_filter.py
|
||
"""
|
||
Apple MLX 기반 경량 신경망 필터.
|
||
M4의 통합 GPU와 Neural Engine을 자동으로 활용한다.
|
||
"""
|
||
import numpy as np
|
||
import pandas as pd
|
||
import mlx.core as mx
|
||
import mlx.nn as nn
|
||
import mlx.optimizers as optim
|
||
from pathlib import Path
|
||
|
||
from src.ml_features import FEATURE_COLS
|
||
|
||
|
||
class _Net(nn.Module):
|
||
"""2층 MLP 분류기."""
|
||
def __init__(self, input_dim: int, hidden_dim: int):
|
||
super().__init__()
|
||
self.fc1 = nn.Linear(input_dim, hidden_dim)
|
||
self.fc2 = nn.Linear(hidden_dim, hidden_dim // 2)
|
||
self.fc3 = nn.Linear(hidden_dim // 2, 1)
|
||
self.dropout = nn.Dropout(p=0.2)
|
||
|
||
def __call__(self, x: mx.array) -> mx.array:
|
||
x = nn.relu(self.fc1(x))
|
||
x = self.dropout(x)
|
||
x = nn.relu(self.fc2(x))
|
||
return self.fc3(x).squeeze(-1)
|
||
|
||
|
||
class MLXFilter:
|
||
"""scikit-learn 호환 인터페이스를 제공하는 MLX 신경망 필터."""
|
||
|
||
def __init__(self, input_dim: int = 13, hidden_dim: int = 64,
|
||
lr: float = 1e-3, epochs: int = 50, batch_size: int = 256):
|
||
self.input_dim = input_dim
|
||
self.hidden_dim = hidden_dim
|
||
self.lr = lr
|
||
self.epochs = epochs
|
||
self.batch_size = batch_size
|
||
self.model = _Net(input_dim, hidden_dim)
|
||
self._trained = False
|
||
|
||
def fit(self, X: pd.DataFrame, y: pd.Series) -> "MLXFilter":
|
||
X_np = X[FEATURE_COLS].values.astype(np.float32)
|
||
y_np = y.values.astype(np.float32)
|
||
|
||
# 정규화 파라미터 저장
|
||
self._mean = X_np.mean(axis=0)
|
||
self._std = X_np.std(axis=0) + 1e-8
|
||
X_np = (X_np - self._mean) / self._std
|
||
|
||
optimizer = optim.Adam(learning_rate=self.lr)
|
||
|
||
def loss_fn(model, x, y):
|
||
logits = model(x)
|
||
return nn.losses.binary_cross_entropy(logits, y, with_logits=True).mean()
|
||
|
||
loss_and_grad = nn.value_and_grad(self.model, loss_fn)
|
||
|
||
n = len(X_np)
|
||
for epoch in range(self.epochs):
|
||
idx = np.random.permutation(n)
|
||
epoch_loss = 0.0
|
||
steps = 0
|
||
for start in range(0, n, self.batch_size):
|
||
batch_idx = idx[start:start + self.batch_size]
|
||
x_batch = mx.array(X_np[batch_idx])
|
||
y_batch = mx.array(y_np[batch_idx])
|
||
loss, grads = loss_and_grad(self.model, x_batch, y_batch)
|
||
optimizer.update(self.model, grads)
|
||
mx.eval(self.model.parameters(), optimizer.state)
|
||
epoch_loss += loss.item()
|
||
steps += 1
|
||
if (epoch + 1) % 10 == 0:
|
||
print(f" Epoch {epoch+1}/{self.epochs} loss={epoch_loss/steps:.4f}")
|
||
|
||
self._trained = True
|
||
return self
|
||
|
||
def predict_proba(self, X: pd.DataFrame) -> np.ndarray:
|
||
X_np = X[FEATURE_COLS].values.astype(np.float32)
|
||
if self._trained:
|
||
X_np = (X_np - self._mean) / self._std
|
||
x = mx.array(X_np)
|
||
logits = self.model(x)
|
||
proba = mx.sigmoid(logits)
|
||
mx.eval(proba)
|
||
return np.array(proba)
|
||
|
||
def save(self, path: str | Path):
|
||
path = Path(path)
|
||
path.parent.mkdir(exist_ok=True)
|
||
import pickle
|
||
with open(path, "wb") as f:
|
||
pickle.dump({
|
||
"weights": {k: np.array(v) for k, v in
|
||
dict(self.model.parameters()).items()},
|
||
"mean": self._mean,
|
||
"std": self._std,
|
||
"config": {
|
||
"input_dim": self.input_dim,
|
||
"hidden_dim": self.hidden_dim,
|
||
},
|
||
}, f)
|
||
|
||
@classmethod
|
||
def load(cls, path: str | Path) -> "MLXFilter":
|
||
import pickle
|
||
with open(path, "rb") as f:
|
||
data = pickle.load(f)
|
||
obj = cls(**data["config"])
|
||
obj._mean = data["mean"]
|
||
obj._std = data["std"]
|
||
# 가중치 복원
|
||
for name, val in data["weights"].items():
|
||
# MLX 파라미터 복원은 직접 할당
|
||
pass
|
||
obj._trained = True
|
||
return obj
|
||
```
|
||
|
||
**Step 6: 테스트 재실행 (통과 확인)**
|
||
|
||
```bash
|
||
pytest tests/test_mlx_filter.py -v
|
||
```
|
||
|
||
**Step 7: 커밋**
|
||
|
||
```bash
|
||
git add src/mlx_filter.py tests/test_mlx_filter.py requirements.txt
|
||
git commit -m "feat: add MLX-based neural filter for Apple Silicon GPU acceleration"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: MLX 모델 학습 스크립트 작성
|
||
|
||
**Files:**
|
||
- Create: `scripts/train_mlx_model.py`
|
||
|
||
**Step 1: 학습 스크립트 작성**
|
||
|
||
```python
|
||
"""
|
||
MLX 기반 신경망 필터를 학습하고 저장한다.
|
||
사용법: python scripts/train_mlx_model.py --data data/xrpusdt_1m.parquet
|
||
"""
|
||
import sys
|
||
from pathlib import Path
|
||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
||
import argparse
|
||
import time
|
||
import numpy as np
|
||
import pandas as pd
|
||
from sklearn.metrics import roc_auc_score
|
||
|
||
from scripts.train_model import generate_dataset
|
||
from src.ml_features import FEATURE_COLS
|
||
from src.mlx_filter import MLXFilter
|
||
|
||
MLX_MODEL_PATH = Path("models/mlx_filter.pkl")
|
||
|
||
|
||
def train_mlx(data_path: str):
|
||
print(f"데이터 로드: {data_path}")
|
||
df = pd.read_parquet(data_path)
|
||
|
||
print("데이터셋 생성 중...")
|
||
t0 = time.perf_counter()
|
||
dataset = generate_dataset(df)
|
||
t1 = time.perf_counter()
|
||
print(f"데이터셋 생성 완료: {t1-t0:.1f}초, {len(dataset)}개 샘플")
|
||
|
||
X = dataset[FEATURE_COLS]
|
||
y = dataset["label"]
|
||
|
||
split = int(len(X) * 0.8)
|
||
X_train, X_val = X.iloc[:split], X.iloc[split:]
|
||
y_train, y_val = y.iloc[:split], y.iloc[split:]
|
||
|
||
print("MLX 신경망 학습 시작...")
|
||
t2 = time.perf_counter()
|
||
model = MLXFilter(input_dim=13, hidden_dim=128, lr=1e-3, epochs=100, batch_size=256)
|
||
model.fit(X_train, y_train)
|
||
t3 = time.perf_counter()
|
||
print(f"학습 완료: {t3-t2:.1f}초")
|
||
|
||
val_proba = model.predict_proba(X_val)
|
||
auc = roc_auc_score(y_val, val_proba)
|
||
print(f"검증 AUC: {auc:.4f}")
|
||
|
||
model.save(MLX_MODEL_PATH)
|
||
print(f"모델 저장: {MLX_MODEL_PATH}")
|
||
return auc
|
||
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--data", default="data/xrpusdt_1m.parquet")
|
||
args = parser.parse_args()
|
||
train_mlx(args.data)
|
||
```
|
||
|
||
**Step 2: 학습 실행 및 시간 비교**
|
||
|
||
```bash
|
||
# LightGBM 학습 시간
|
||
time python scripts/train_model.py --data data/xrpusdt_1m.parquet
|
||
|
||
# MLX 학습 시간
|
||
time python scripts/train_mlx_model.py --data data/xrpusdt_1m.parquet
|
||
```
|
||
|
||
→ AUC 비교 및 학습 시간 비교 후 어떤 모델을 사용할지 결정
|
||
|
||
**Step 3: 커밋**
|
||
|
||
```bash
|
||
git add scripts/train_mlx_model.py
|
||
git commit -m "feat: add MLX model training script with timing comparison"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: MetalGBM 실험 (선택적)
|
||
|
||
> ⚠️ MetalGBM은 2025년 11월에 만들어진 신생 프로젝트로, 프로덕션 사용은 권장하지 않는다. 실험 목적으로만 시도한다.
|
||
|
||
**Files:**
|
||
- Create: `scripts/train_metalgbm.py`
|
||
|
||
**Step 1: MetalGBM 설치 시도**
|
||
|
||
```bash
|
||
pip install metalgbm
|
||
python -c "import metalgbm; print('MetalGBM 설치 성공')"
|
||
```
|
||
|
||
실패 시 → 이 Task를 건너뛴다.
|
||
|
||
**Step 2: 실험 스크립트 작성**
|
||
|
||
```python
|
||
"""
|
||
MetalGBM으로 Apple Silicon GPU 가속 그래디언트 부스팅을 실험한다.
|
||
사용법: python scripts/train_metalgbm.py --data data/xrpusdt_1m.parquet
|
||
"""
|
||
import sys
|
||
from pathlib import Path
|
||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
||
import argparse
|
||
import time
|
||
import pandas as pd
|
||
from sklearn.metrics import roc_auc_score
|
||
|
||
from scripts.train_model import generate_dataset
|
||
from src.ml_features import FEATURE_COLS
|
||
|
||
def train_metalgbm(data_path: str):
|
||
try:
|
||
import metalgbm as mgbm
|
||
except ImportError:
|
||
print("MetalGBM 미설치. pip install metalgbm 실행 후 재시도")
|
||
return
|
||
|
||
df = pd.read_parquet(data_path)
|
||
dataset = generate_dataset(df)
|
||
X = dataset[FEATURE_COLS]
|
||
y = dataset["label"]
|
||
split = int(len(X) * 0.8)
|
||
|
||
t0 = time.perf_counter()
|
||
model = mgbm.MetalGBMClassifier(n_estimators=300, learning_rate=0.05)
|
||
model.fit(X.iloc[:split], y.iloc[:split])
|
||
t1 = time.perf_counter()
|
||
|
||
val_proba = model.predict_proba(X.iloc[split:])[:, 1]
|
||
auc = roc_auc_score(y.iloc[split:], val_proba)
|
||
print(f"MetalGBM 학습: {t1-t0:.1f}초, AUC: {auc:.4f}")
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--data", default="data/xrpusdt_1m.parquet")
|
||
args = parser.parse_args()
|
||
train_metalgbm(args.data)
|
||
```
|
||
|
||
**Step 3: 실행 및 결과 기록**
|
||
|
||
```bash
|
||
python scripts/train_metalgbm.py --data data/xrpusdt_1m.parquet
|
||
```
|
||
|
||
**Step 4: 커밋**
|
||
|
||
```bash
|
||
git add scripts/train_metalgbm.py
|
||
git commit -m "experiment: add MetalGBM GPU training experiment script"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: train_and_deploy.sh에 가속 옵션 추가
|
||
|
||
**Files:**
|
||
- Modify: `scripts/train_and_deploy.sh`
|
||
|
||
**Step 1: 스크립트 수정**
|
||
|
||
`scripts/train_and_deploy.sh`에서 학습 단계를 다음과 같이 변경:
|
||
|
||
```bash
|
||
echo ""
|
||
echo "=== [2/3] 모델 학습 ==="
|
||
# --backend 옵션: lgbm (기본) | mlx (Apple Silicon GPU)
|
||
BACKEND="${TRAIN_BACKEND:-lgbm}"
|
||
if [ "$BACKEND" = "mlx" ]; then
|
||
python scripts/train_mlx_model.py --data data/xrpusdt_1m.parquet
|
||
else
|
||
python scripts/train_model.py --data data/xrpusdt_1m.parquet
|
||
fi
|
||
```
|
||
|
||
**Step 2: README에 사용법 추가**
|
||
|
||
`README.md`의 학습 섹션에 다음 추가:
|
||
|
||
```markdown
|
||
### 가속 학습 (Apple Silicon)
|
||
|
||
```bash
|
||
# MLX GPU 가속 학습 (M1/M2/M3/M4)
|
||
TRAIN_BACKEND=mlx bash scripts/train_and_deploy.sh
|
||
|
||
# 기본 LightGBM CPU 학습
|
||
bash scripts/train_and_deploy.sh
|
||
```
|
||
```
|
||
|
||
**Step 3: 커밋**
|
||
|
||
```bash
|
||
git add scripts/train_and_deploy.sh README.md
|
||
git commit -m "feat: add TRAIN_BACKEND env var to select lgbm or mlx training"
|
||
```
|
||
|
||
---
|
||
|
||
## 최종 결과 기대치
|
||
|
||
| 단계 | 현재 | 최적화 후 |
|
||
|------|------|-----------|
|
||
| 데이터셋 생성 | ~60초 (추정) | ~30-40초 (chunksize 튜닝) |
|
||
| LightGBM 학습 | ~5초 (추정) | ~5초 (변화 없음) |
|
||
| MLX 신경망 학습 | - | ~10-30초 (GPU 활용) |
|
||
| Neural Engine | ❌ 직접 접근 불가 | ❌ (변화 없음) |
|
||
|
||
> **Neural Engine에 대한 최종 답변**: Apple Neural Engine(NPU)은 CoreML, Create ML 등 Apple 전용 프레임워크를 통해서만 접근 가능하며, Python ML 라이브러리에서 직접 제어할 수 없다. MLX는 GPU를 주로 사용하고 일부 연산에서 Neural Engine을 자동으로 활용하지만, 사용자가 직접 NPU를 타겟팅할 수는 없다. **현실적인 최선은 MLX로 GPU를 활용하는 것**이다.
|