update dags

This commit is contained in:
2026-04-15 00:10:17 +03:00
parent 1a79410ad7
commit 481b978b4c

View File

@@ -5,102 +5,93 @@ from datetime import timedelta
WORLD_SIZE = 2 WORLD_SIZE = 2
# Адрес мастера — headless DNS пода воркера
MASTER_ADDR = "airflow-worker-gpu-0.airflow-worker-gpu" MASTER_ADDR = "airflow-worker-gpu-0.airflow-worker-gpu"
MASTER_PORT = "29500" MASTER_PORT = "29500"
default_args = { default_args = {
"owner": "airflow", "owner": "airflow",
"retries": 0, "retries": 0,
"execution_timeout": timedelta(hours=2), "execution_timeout": timedelta(hours=1),
} }
# ------------------------- def run_training_node_func(rank: int, world_size: int):
# SIMPLE PREP (NO VARIABLES)
# -------------------------
def prepare_training_func():
print("Starting distributed training (no sync state needed)")
return True
# -------------------------
# CORE TRAINING
# -------------------------
def run_training_node_func(rank, world_size):
import os import os
import time import time
import socket
import torch import torch
import torch.distributed as dist import torch.distributed as dist
import torch.nn as nn import torch.nn as nn
import torch.optim as optim import torch.optim as optim
from datetime import datetime from datetime import datetime
import socket
print("=" * 80) print("=" * 80)
print(f"RANK {rank}/{world_size} START {datetime.now()}") print(f"RANK {rank}/{world_size} HOST={socket.gethostname()} {datetime.now()}")
print(f"HOST: {socket.gethostname()}")
print("=" * 80) print("=" * 80)
# ------------------------- # ── статический барьер: ждём пока оба воркера подтянутся ──────────────────
# FIXED BARRIER (important)
# -------------------------
print(f"[{rank}] sync barrier (static sleep)")
time.sleep(10) time.sleep(10)
# ------------------------- # ── переменные окружения для NCCL + InfiniBand ────────────────────────────
# STATIC CONFIG (NO AIRFLOW VARIABLES) os.environ.update(
# ------------------------- {
os.environ["MASTER_ADDR"] = MASTER_ADDR "MASTER_ADDR": MASTER_ADDR,
os.environ["MASTER_PORT"] = MASTER_PORT "MASTER_PORT": MASTER_PORT,
os.environ["WORLD_SIZE"] = str(world_size) "WORLD_SIZE": str(world_size),
os.environ["RANK"] = str(rank) "RANK": str(rank),
# InfiniBand: указываем IPoIB-интерфейс вместо eth0
# Разрешаем NCCL использовать RDMA (IB verbs)
"NCCL_IB_DISABLE": "0",
# GPUDirect RDMA — если драйвер поддерживает
"NCCL_P2P_DISABLE": "0",
# Явно форсируем IB transport (опционально, NCCL сам выберет,
# но полезно для отладки)
"NCCL_NET": "IB",
# Отладка: INFO покажет какой транспорт выбран,
# поменяй на TRACE для полного вывода
"NCCL_DEBUG": "INFO",
"NCCL_DEBUG_SUBSYS": "NET,INIT",
"TORCH_NCCL_BLOCKING_WAIT": "1",
}
)
os.environ["NCCL_SOCKET_IFNAME"] = "eth0" print(f"[{rank}] MASTER={MASTER_ADDR}:{MASTER_PORT} IB_IF=ib0")
os.environ["NCCL_DEBUG"] = "INFO"
os.environ["TORCH_NCCL_BLOCKING_WAIT"] = "1"
print(f"[{rank}] MASTER = {MASTER_ADDR}:{MASTER_PORT}") # ── init process group ────────────────────────────────────────────────────
# -------------------------
# INIT PROCESS GROUP
# -------------------------
dist.init_process_group( dist.init_process_group(
backend="nccl", backend="nccl",
init_method="env://", init_method="env://",
rank=rank, rank=rank,
world_size=world_size, world_size=world_size,
timeout=timedelta(minutes=5),
) )
print(f"[{rank}] dist.init_process_group OK")
print(f"[{rank}] DDP INIT OK") # ── GPU binding ───────────────────────────────────────────────────────────
# -------------------------
# GPU BINDING (CRITICAL FIX)
# -------------------------
torch.cuda.set_device(0) torch.cuda.set_device(0)
device = torch.device("cuda:0") device = torch.device("cuda:0")
print(f"[{rank}] GPU={torch.cuda.get_device_name(0)}") print(f"[{rank}] GPU={torch.cuda.get_device_name(0)}")
# ------------------------- # ── минимальная модель ────────────────────────────────────────────────────
# MODEL
# -------------------------
model = nn.Sequential( model = nn.Sequential(
nn.Linear(10, 128), nn.Linear(10, 128),
nn.ReLU(), nn.ReLU(),
nn.Linear(128, 10), nn.Linear(128, 10),
).to(device) ).to(device)
ddp_model = nn.parallel.DistributedDataParallel( ddp_model = nn.parallel.DistributedDataParallel(model, device_ids=[0])
model,
device_ids=[0],
)
optimizer = optim.SGD(ddp_model.parameters(), lr=0.001) optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)
loss_fn = nn.MSELoss() loss_fn = nn.MSELoss()
# ------------------------- # ── allreduce smoke-test перед обучением ──────────────────────────────────
# TRAIN LOOP probe = torch.ones(1).to(device) * rank
# ------------------------- dist.all_reduce(probe, op=dist.ReduceOp.SUM)
expected = sum(range(world_size))
assert probe.item() == expected, f"allreduce mismatch: got {probe.item()}"
print(f"[{rank}] allreduce smoke-test PASSED (sum={probe.item()})")
# ── train loop ────────────────────────────────────────────────────────────
for epoch in range(5): for epoch in range(5):
x = torch.randn(32, 10).to(device) x = torch.randn(32, 10).to(device)
y = torch.randn(32, 10).to(device) y = torch.randn(32, 10).to(device)
@@ -114,42 +105,21 @@ def run_training_node_func(rank, world_size):
print(f"[{rank}] epoch={epoch} loss={loss.item():.4f}") print(f"[{rank}] epoch={epoch} loss={loss.item():.4f}")
dist.destroy_process_group() dist.destroy_process_group()
print(f"[{rank}] DONE")
return {"rank": rank, "status": "ok"} return {"rank": rank, "status": "ok"}
# ------------------------- # ── DAG ───────────────────────────────────────────────────────────────────────
# CLEANUP
# -------------------------
def cleanup_func():
print("cleanup done")
return True
def summary_func(**context):
print("training done")
return True
# -------------------------
# DAG
# -------------------------
with DAG( with DAG(
dag_id="ddp_airflow_stable", dag_id="ddp_ib_test",
start_date=pendulum.today("UTC").add(days=-1), start_date=pendulum.today("UTC").add(days=-1),
schedule=None, schedule=None,
catchup=False, catchup=False,
max_active_runs=1, max_active_runs=1,
max_active_tasks=2, max_active_tasks=WORLD_SIZE,
default_args=default_args, default_args=default_args,
) as dag: ) as dag:
prep = PythonOperator(
task_id="prep",
python_callable=prepare_training_func,
queue="gpu",
)
tasks = [] tasks = []
for r in range(WORLD_SIZE): for r in range(WORLD_SIZE):
t = PythonOperator( t = PythonOperator(
@@ -160,18 +130,5 @@ with DAG(
) )
tasks.append(t) tasks.append(t)
cleanup = PythonOperator( # запускаем параллельно — без зависимостей между рангами
task_id="cleanup", # (prep/cleanup убраны, это минимальный тест)
python_callable=cleanup_func,
trigger_rule="all_done",
queue="gpu",
)
summary = PythonOperator(
task_id="summary",
python_callable=summary_func,
trigger_rule="all_done",
queue="gpu",
)
prep >> tasks >> cleanup >> summary