update dags
This commit is contained in:
@@ -2,219 +2,95 @@ from airflow import DAG
|
|||||||
from airflow.operators.python import PythonOperator
|
from airflow.operators.python import PythonOperator
|
||||||
import pendulum
|
import pendulum
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
# --- CONFIGURATION ---
|
|
||||||
WORLD_SIZE = 2
|
WORLD_SIZE = 2
|
||||||
MASTER_ADDR = "airflow-worker-gpu-0.airflow-worker-gpu"
|
MASTER_ADDR = "127.0.0.1"
|
||||||
MASTER_PORT = "29500"
|
MASTER_PORT = "29500"
|
||||||
NCCL_TIMEOUT = 1800
|
|
||||||
|
|
||||||
BARRIER_FILE = f"/tmp/ddp_barrier_{WORLD_SIZE}"
|
|
||||||
|
|
||||||
default_args = {
|
default_args = {
|
||||||
'owner': 'airflow',
|
"owner": "airflow",
|
||||||
'retries': 1,
|
"retries": 1,
|
||||||
'retry_delay': timedelta(minutes=2),
|
"retry_delay": timedelta(minutes=2),
|
||||||
'execution_timeout': timedelta(hours=2),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def prepare_training_func():
|
# =========================
|
||||||
# reset barrier
|
# SINGLE SAFE DDP LAUNCHER
|
||||||
try:
|
# =========================
|
||||||
if os.path.exists(BARRIER_FILE):
|
def run_ddp_job():
|
||||||
os.remove(BARRIER_FILE)
|
import os
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
print("Barrier initialized")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def run_training_node_func(rank, world_size):
|
|
||||||
import socket
|
|
||||||
import torch
|
import torch
|
||||||
import torch.distributed as dist
|
import torch.distributed as dist
|
||||||
|
import torch.multiprocessing as mp
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
import torch.optim as optim
|
import torch.optim as optim
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
print("=" * 60)
|
def worker(rank, world_size):
|
||||||
print(f"Rank {rank}/{world_size} START {datetime.now()}")
|
os.environ["MASTER_ADDR"] = MASTER_ADDR
|
||||||
print(f"Hostname: {socket.gethostname()}")
|
os.environ["MASTER_PORT"] = MASTER_PORT
|
||||||
print("=" * 60)
|
os.environ["WORLD_SIZE"] = str(world_size)
|
||||||
|
os.environ["RANK"] = str(rank)
|
||||||
|
|
||||||
# =========================
|
# optional debug
|
||||||
# STEP 1: SAFE BARRIER
|
os.environ["NCCL_DEBUG"] = "INFO"
|
||||||
# =========================
|
os.environ["NCCL_ASYNC_ERROR_HANDLING"] = "1"
|
||||||
print(f"[{rank}] entering barrier sync...")
|
|
||||||
|
|
||||||
with open(BARRIER_FILE, "a+") as f:
|
torch.cuda.set_device(0)
|
||||||
f.write(f"{rank}\n")
|
|
||||||
|
|
||||||
# wait for all ranks
|
dist.init_process_group(
|
||||||
while True:
|
backend="nccl",
|
||||||
try:
|
init_method="env://",
|
||||||
with open(BARRIER_FILE, "r") as f:
|
rank=rank,
|
||||||
ready = set(f.read().strip().splitlines())
|
world_size=world_size,
|
||||||
|
)
|
||||||
|
|
||||||
print(f"[{rank}] ready workers: {ready}")
|
model = nn.Linear(10, 10).cuda()
|
||||||
|
|
||||||
if len(ready) == world_size:
|
ddp_model = torch.nn.parallel.DistributedDataParallel(
|
||||||
print(f"[{rank}] ALL WORKERS READY")
|
model,
|
||||||
break
|
device_ids=[0]
|
||||||
|
)
|
||||||
|
|
||||||
except FileNotFoundError:
|
loss_fn = nn.MSELoss()
|
||||||
pass
|
opt = optim.SGD(ddp_model.parameters(), lr=0.01)
|
||||||
|
|
||||||
time.sleep(1)
|
for epoch in range(5):
|
||||||
|
x = torch.randn(32, 10).cuda()
|
||||||
|
y = torch.randn(32, 10).cuda()
|
||||||
|
|
||||||
time.sleep(2) # small stabilization delay
|
opt.zero_grad()
|
||||||
|
|
||||||
# =========================
|
|
||||||
# STEP 2: NCCL ENV
|
|
||||||
# =========================
|
|
||||||
os.environ['MASTER_ADDR'] = MASTER_ADDR
|
|
||||||
os.environ['MASTER_PORT'] = MASTER_PORT
|
|
||||||
os.environ['WORLD_SIZE'] = str(world_size)
|
|
||||||
os.environ['RANK'] = str(rank)
|
|
||||||
|
|
||||||
# IMPORTANT FIX (IB stability toggle)
|
|
||||||
os.environ["NCCL_IB_DISABLE"] = "1" # <- FIX #2 (can turn OFF later)
|
|
||||||
os.environ["NCCL_SOCKET_IFNAME"] = "eth0"
|
|
||||||
|
|
||||||
os.environ['NCCL_DEBUG'] = 'INFO'
|
|
||||||
os.environ['NCCL_TIMEOUT'] = str(NCCL_TIMEOUT)
|
|
||||||
os.environ['NCCL_BLOCKING_WAIT'] = '1'
|
|
||||||
|
|
||||||
print(f"[{rank}] env ready")
|
|
||||||
|
|
||||||
# =========================
|
|
||||||
# STEP 3: INIT PROCESS GROUP
|
|
||||||
# =========================
|
|
||||||
dist.init_process_group(
|
|
||||||
backend="nccl",
|
|
||||||
init_method="env://",
|
|
||||||
timeout=timedelta(minutes=10),
|
|
||||||
rank=rank,
|
|
||||||
world_size=world_size
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"[{rank}] process group OK")
|
|
||||||
|
|
||||||
# =========================
|
|
||||||
# STEP 4: GPU
|
|
||||||
# =========================
|
|
||||||
device = torch.device("cuda:0")
|
|
||||||
torch.cuda.set_device(device)
|
|
||||||
|
|
||||||
model = nn.Sequential(
|
|
||||||
nn.Linear(10, 128),
|
|
||||||
nn.ReLU(),
|
|
||||||
nn.Linear(128, 10)
|
|
||||||
).to(device)
|
|
||||||
|
|
||||||
ddp_model = nn.parallel.DistributedDataParallel(
|
|
||||||
model,
|
|
||||||
device_ids=[0],
|
|
||||||
output_device=0
|
|
||||||
)
|
|
||||||
|
|
||||||
criterion = nn.MSELoss()
|
|
||||||
optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)
|
|
||||||
|
|
||||||
# =========================
|
|
||||||
# STEP 5: TRAIN LOOP
|
|
||||||
# =========================
|
|
||||||
for epoch in range(5):
|
|
||||||
ddp_model.train()
|
|
||||||
loss_sum = 0
|
|
||||||
|
|
||||||
for _ in range(5):
|
|
||||||
x = torch.randn(32, 10).to(device)
|
|
||||||
y = torch.randn(32, 10).to(device)
|
|
||||||
|
|
||||||
optimizer.zero_grad()
|
|
||||||
out = ddp_model(x)
|
out = ddp_model(x)
|
||||||
loss = criterion(out, y)
|
loss = loss_fn(out, y)
|
||||||
loss.backward()
|
loss.backward()
|
||||||
optimizer.step()
|
opt.step()
|
||||||
|
|
||||||
loss_sum += loss.item()
|
print(f"rank {rank} epoch {epoch} loss {loss.item()}")
|
||||||
|
|
||||||
print(f"[{rank}] epoch {epoch} loss {loss_sum/5:.4f}")
|
dist.destroy_process_group()
|
||||||
|
|
||||||
dist.destroy_process_group()
|
# IMPORTANT: THIS FIXES NCCL HANG
|
||||||
|
mp.spawn(worker, args=(WORLD_SIZE,), nprocs=WORLD_SIZE, join=True)
|
||||||
|
|
||||||
print(f"[{rank}] DONE")
|
return {"status": "success"}
|
||||||
|
|
||||||
return {"rank": rank, "status": "success"}
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup_sync_state_func():
|
|
||||||
try:
|
|
||||||
if os.path.exists(BARRIER_FILE):
|
|
||||||
os.remove(BARRIER_FILE)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
print("Barrier cleaned")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def training_summary_func(**context):
|
|
||||||
ti = context['ti']
|
|
||||||
|
|
||||||
print("\n=== SUMMARY ===")
|
|
||||||
|
|
||||||
for rank in range(WORLD_SIZE):
|
|
||||||
res = ti.xcom_pull(task_ids=f"train_rank_{rank}")
|
|
||||||
print(f"Rank {rank}: {res}")
|
|
||||||
|
|
||||||
return {"status": "done"}
|
|
||||||
|
|
||||||
|
|
||||||
# =========================
|
# =========================
|
||||||
# DAG
|
# AIRFLOW DAG
|
||||||
# =========================
|
# =========================
|
||||||
with DAG(
|
with DAG(
|
||||||
dag_id='pytorch_ddp_airflow_fixed',
|
dag_id="pytorch_ddp_airflow_fixed_stable",
|
||||||
default_args=default_args,
|
default_args=default_args,
|
||||||
schedule=None,
|
schedule=None,
|
||||||
start_date=pendulum.today('UTC').add(days=-1),
|
start_date=pendulum.today("UTC").add(days=-1),
|
||||||
catchup=False,
|
catchup=False,
|
||||||
max_active_runs=1,
|
max_active_runs=1,
|
||||||
tags=['gpu', 'ddp', 'fixed'],
|
tags=["ddp", "gpu", "stable"],
|
||||||
) as dag:
|
) as dag:
|
||||||
|
|
||||||
prep = PythonOperator(
|
train = PythonOperator(
|
||||||
task_id='prepare_training',
|
task_id="train_ddp",
|
||||||
python_callable=prepare_training_func,
|
python_callable=run_ddp_job,
|
||||||
queue='gpu',
|
queue="gpu",
|
||||||
)
|
)
|
||||||
|
|
||||||
training_tasks = []
|
|
||||||
for i in range(WORLD_SIZE):
|
|
||||||
t = PythonOperator(
|
|
||||||
task_id=f'train_rank_{i}',
|
|
||||||
python_callable=run_training_node_func,
|
|
||||||
op_kwargs={'rank': i, 'world_size': WORLD_SIZE},
|
|
||||||
queue='gpu',
|
|
||||||
)
|
|
||||||
training_tasks.append(t)
|
|
||||||
|
|
||||||
cleanup = PythonOperator(
|
|
||||||
task_id='cleanup',
|
|
||||||
python_callable=cleanup_sync_state_func,
|
|
||||||
queue='gpu',
|
|
||||||
trigger_rule='all_done',
|
|
||||||
)
|
|
||||||
|
|
||||||
summary = PythonOperator(
|
|
||||||
task_id='summary',
|
|
||||||
python_callable=training_summary_func,
|
|
||||||
trigger_rule='all_done',
|
|
||||||
)
|
|
||||||
|
|
||||||
prep >> training_tasks >> cleanup >> summary
|
|
||||||
|
|||||||
Reference in New Issue
Block a user