HayrettinIscan commited on
Commit
ca3f598
·
verified ·
1 Parent(s): 1b93e89

Faz2 dep: code/meshai_train/engine.py

Browse files
Files changed (1) hide show
  1. code/meshai_train/engine.py +188 -0
code/meshai_train/engine.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import numpy as np
8
+ import torch
9
+ import torch.nn as nn
10
+ from torch.utils.data import DataLoader, Subset
11
+
12
+ from meshai_train.dataset import PreprocessedMeshDataset, collate_preprocessed
13
+ from meshai_train.models import MeshAIHybridTrainBundle
14
+
15
+ TRAIN_VERSION = "v4.0-real-preprocessed"
16
+
17
+
18
+ def hybrid_loss(out: dict[str, torch.Tensor], batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, dict[str, float]]:
19
+ voxel_loss = nn.functional.mse_loss(out["voxel_pred"], batch["voxel_tgt"])
20
+ bridge_loss = nn.functional.mse_loss(out["bridge_out"], out["tex_latent"].detach())
21
+ tex_reg = out["tex_latent"].pow(2).mean() * 1e-4
22
+ total = voxel_loss + bridge_loss + tex_reg
23
+ return total, {
24
+ "voxel": float(voxel_loss.item()),
25
+ "bridge": float(bridge_loss.item()),
26
+ "tex_reg": float(tex_reg.item()),
27
+ }
28
+
29
+
30
+ def save_real_checkpoint(
31
+ path: Path,
32
+ *,
33
+ epoch: int,
34
+ global_step: int,
35
+ model: MeshAIHybridTrainBundle,
36
+ extra: dict[str, Any] | None = None,
37
+ ) -> None:
38
+ payload = {
39
+ "version": TRAIN_VERSION,
40
+ "epoch": epoch,
41
+ "global_step": global_step,
42
+ "geometry": model.geometry.state_dict(),
43
+ "texture": model.texture.state_dict(),
44
+ "bridge": model.bridge.state_dict(),
45
+ "extra": extra or {},
46
+ }
47
+ torch.save(payload, path)
48
+
49
+
50
+ def load_real_checkpoint(path: Path, model: MeshAIHybridTrainBundle, device: str) -> int:
51
+ state = torch.load(path, map_location=device, weights_only=False)
52
+ if state.get("version") != TRAIN_VERSION:
53
+ return 0
54
+ model.geometry.load_state_dict(state["geometry"])
55
+ model.texture.load_state_dict(state["texture"])
56
+ model.bridge.load_state_dict(state["bridge"])
57
+ return int(state.get("global_step", 0))
58
+
59
+
60
+ def start_real_training(
61
+ *,
62
+ monitor: Any,
63
+ checkpoint_dir: Path,
64
+ output_dir: Path,
65
+ token: str | None,
66
+ epochs: int,
67
+ resume_from: Path | None,
68
+ validation_every: int,
69
+ val_ratio: float,
70
+ limit: int | None,
71
+ hf_repo: str,
72
+ data_root: Path | None,
73
+ log_fn: Any,
74
+ log_vram_fn: Any,
75
+ clear_gpu_fn: Any,
76
+ load_val_uids_fn: Any,
77
+ checkpoint_every: int = 100,
78
+ ) -> None:
79
+ device = "cuda" if torch.cuda.is_available() else "cpu"
80
+ dtype = torch.float32
81
+
82
+ log_fn(f"Pipeline surumu: {TRAIN_VERSION} (gercek preprocessed latent + render)")
83
+ log_fn(f"Veri: {hf_repo}" + (f" | limit={limit}" if limit else ""))
84
+ if torch.cuda.is_available():
85
+ log_fn(f"GPU: {torch.cuda.get_device_name(0)}")
86
+ log_vram_fn("startup")
87
+
88
+ dataset = PreprocessedMeshDataset(
89
+ token=token,
90
+ data_root=data_root,
91
+ hf_repo=hf_repo,
92
+ limit=limit,
93
+ )
94
+ val_uids = load_val_uids_fn()
95
+ val_idx = [i for i, o in enumerate(dataset.objects) if str(o.get("uid")) in val_uids]
96
+ if not val_idx:
97
+ val_count = max(1, int(len(dataset) * val_ratio))
98
+ val_idx = list(range(val_count))
99
+ train_idx = [i for i in range(len(dataset)) if i not in set(val_idx)]
100
+ if not train_idx:
101
+ train_idx = list(range(len(dataset)))
102
+ val_idx = train_idx[:1]
103
+
104
+ train_loader = DataLoader(
105
+ Subset(dataset, train_idx),
106
+ batch_size=2 if device == "cuda" else 1,
107
+ shuffle=True,
108
+ pin_memory=device == "cuda",
109
+ collate_fn=collate_preprocessed,
110
+ )
111
+ val_loader = DataLoader(
112
+ Subset(dataset, val_idx),
113
+ batch_size=2 if device == "cuda" else 1,
114
+ shuffle=False,
115
+ pin_memory=device == "cuda",
116
+ collate_fn=collate_preprocessed,
117
+ )
118
+ log_fn(f"Veri: {len(train_idx)} train + {len(val_idx)} val (gercek latent/render).")
119
+
120
+ model = MeshAIHybridTrainBundle().to(device=device, dtype=dtype)
121
+ opt = torch.optim.AdamW(model.parameters(), lr=1e-4, fused=False)
122
+ latest = checkpoint_dir / "latest_model.pt"
123
+ global_step = 0
124
+ if resume_from and resume_from.exists():
125
+ global_step = load_real_checkpoint(resume_from, model, device)
126
+ log_fn(f"Resume: {resume_from} step={global_step}")
127
+
128
+ def _eval() -> tuple[float, float]:
129
+ model.eval()
130
+ losses: list[float] = []
131
+ with torch.no_grad():
132
+ for batch in val_loader:
133
+ batch = {k: v.to(device) if isinstance(v, torch.Tensor) else v for k, v in batch.items()}
134
+ out = model(batch["geom_in"], batch["views"])
135
+ loss, _ = hybrid_loss(out, batch)
136
+ losses.append(float(loss.item()))
137
+ model.train()
138
+ mean = float(np.mean(losses)) if losses else float("nan")
139
+ return mean, mean
140
+
141
+ for epoch in range(1, epochs + 1):
142
+ log_fn(f"--- Epoch {epoch}/{epochs} ---")
143
+ model.train()
144
+ epoch_losses: list[float] = []
145
+ for batch in train_loader:
146
+ batch = {k: v.to(device) if isinstance(v, torch.Tensor) else v for k, v in batch.items()}
147
+ opt.zero_grad(set_to_none=True)
148
+ out = model(batch["geom_in"], batch["views"])
149
+ loss, parts = hybrid_loss(out, batch)
150
+ if not torch.isfinite(loss):
151
+ monitor.note_nan_skip("hybrid")
152
+ continue
153
+ loss.backward()
154
+ torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
155
+ opt.step()
156
+ global_step += 1
157
+ epoch_losses.append(float(loss.item()))
158
+ monitor.note_step(global_step, "geometry", parts["voxel"])
159
+ monitor.last["train_loss_texture"] = round(parts["bridge"], 6)
160
+ if global_step <= 3 or global_step % 25 == 0:
161
+ log_fn(
162
+ f"step={global_step} loss={float(loss.item()):.6f} "
163
+ f"voxel={parts['voxel']:.6f} bridge={parts['bridge']:.6f}"
164
+ )
165
+
166
+ if checkpoint_every > 0 and global_step % checkpoint_every == 0:
167
+ save_real_checkpoint(latest, epoch=epoch, global_step=global_step, model=model)
168
+ log_fn(
169
+ f"CHECKPOINT_SAVED step={global_step} -> {latest} "
170
+ f"({latest.stat().st_size // 1024} KB)"
171
+ )
172
+
173
+ if global_step % validation_every == 0:
174
+ val_geom, val_tex = _eval()
175
+ monitor.note_validation(global_step, val_geom, val_tex)
176
+ log_vram_fn(f"step_{global_step}")
177
+
178
+ val_geom, val_tex = _eval()
179
+ geom_mean = float(np.mean(epoch_losses)) if epoch_losses else float("nan")
180
+ monitor.note_epoch_end(epoch, epochs, geom_mean, val_tex, val_geom, val_tex)
181
+ save_real_checkpoint(latest, epoch=epoch, global_step=global_step, model=model)
182
+ clear_gpu_fn()
183
+ log_fn(f"Epoch {epoch} kaydedildi -> {latest} ({latest.stat().st_size // 1024} KB)")
184
+ log_fn(f"CHECKPOINT_SAVED step={global_step} epoch={epoch} -> {latest}")
185
+
186
+ monitor.finish(ok=True)
187
+ log_fn("Gercek preprocessed egitim tamamlandi.")
188
+ log_fn("Not: Tam TRELLIS/Hunyuan agirlik fine-tune sonraki adim; bu asama MeshAI hibrit latent koprusu.")