已合并
update: ads model #38187
bigprestigee1创建于 6月10日
update: ads model #38187
已合并
bigprestigee1创建于 6月10日
共 8 个文件变更+1510-0
@@ -0,0 +1,71 @@
1+# SimpleV1SparseDrive (torch-npu / Ascend)
2+ 
3+---
4+ 
5+## 1. 项目概述
6+ 
7+本项目实现了名为 **SimpleV1SparseDrive** 的模型结构,整体由以下模块组成:
8+ 
9+| 模块 | 说明 |
10+|------|------|
11+| `data_preprocessor` | `BaseDataPreprocessor()` |
12+| `img_backbone` | ResNet(包含大量 `BatchNorm2d`) |
13+| `img_neck` | FPN |
14+| `head` | `V1SparseDriveHead` |
15+| ├─ `det_head` | Sparse4DHeadLike(含 DeformableFeatureAggregation / FlashAttention / Refinement) |
16+| ├─ `map_head` | Sparse4DHeadLike |
17+| └─ `motion_plan_head` | Motion & Planning(含多层 FlashAttention / FFN / refinement) |
18+| `depth_branch` | DenseDepthNetLike(3 个 depth layer) |
19+| `grid_mask` | GridMask |
20+ 
21+---
22+ 
23+## 2. 环境与依赖
24+ 
25+### 2.1 硬件/系统要求
26+ 
27+- Ascend NPU 环境已正确安装(驱动 / CANN 等)
28+ 
29+### 2.2 Pip 环境包(已验证可复现)
30+ 
31+- 基础依赖包
32+ 
33+```bash
34+pip install -r requirement.txt
35+```
36+ 
37+- 源码安装mmcv
38+ 
39+```bash
40+git clone -b 1.x https://github.com/open-mmlab/mmcv.git
41+cd mmcv
42+MMCV_WITH_OPS=1 FORCE_NPU=1 python setup.py install
43+cd ..
44+```
45+ 
46+> 建议固定上述版本以保证结果可复现。
47+ 
48+---
49+ 
50+## 3. 运行训练和验证
51+ 
52+本工程提供两种运行模式:
53+ 
54+- **Eager(默认)**:不启用 `torch.compile`,直接动态图执行。
55+- **Graph/Compile**:通过 `--compile` 启用 `torch.compile`(Inductor 图模式),首步通常包含编译开销。
56+ 
57+> 默认运行 **200 step**(如需修改步数,请按工程内 `config.py` 的参数/配置为准)。
58+ 
59+### 3.1 训练(生成日志)
60+ 
61+#### 3.1.1 Eager 模式(默认)
62+ 
63+```bash
64+python train.py 2>&1 | tee eager.log
65+```
66+ 
67+#### 3.1.2 Graph / torch.compile 模式
68+ 
69+```bash
70+python train.py --npu-backend dvm --compile 2>&1 | tee compile.log
71+```
@@ -0,0 +1,25 @@
1+CONFIG = dict(
2+ seed=3407,
3+ batch_size=2,
4+ num_workers=0,
5+ max_epochs=1,
6+ train_steps=200,
7+ lr=1e-4,
8+ weight_decay=1e-4,
9+ 
10+ model=dict(
11+ num_det_classes=10,
12+ num_map_classes=3,
13+ backbone_pretrained=None,
14+ use_grid_mask=True,
15+ ),
16+ 
17+ dummy_dataset=dict(
18+ length=400,
19+ image_size=(256, 704),
20+ num_det_boxes=20,
21+ num_map_instances=10,
22+ num_det_classes=10,
23+ num_map_classes=3,
24+ )
25+)
@@ -0,0 +1,3 @@
1+from .dummy_dataset import DummySparseDriveDataset, simple_collate
2+ 
3+__all__ = ["DummySparseDriveDataset", "simple_collate"]
@@ -0,0 +1,77 @@
1+import torch
2+from torch.utils.data import Dataset
3+ 
4+ 
5+class DummySparseDriveDataset(Dataset):
6+ """
7+ 模拟后续真实 dataloader 输出格式:
8+ {
9+ "inputs": Tensor[3, H, W],
10+ "data_samples": {
11+ "sample_idx": int,
12+ "img_shape": (H, W),
13+ "gt_det_boxes": Tensor[num_det_boxes, 11],
14+ "gt_det_labels": Tensor[num_det_boxes],
15+ "gt_map_points": Tensor[num_map_instances, 40],
16+ "gt_map_labels": Tensor[num_map_instances],
17+ "gt_motion": Tensor[24],
18+ "gt_plan": Tensor[12],
19+ }
20+ }
21+ """
22+ def __init__(
23+ self,
24+ length=20,
25+ image_size=(256, 704),
26+ num_det_boxes=20,
27+ num_map_instances=10,
28+ num_det_classes=10,
29+ num_map_classes=3,
30+ ):
31+ self.length = length
32+ self.image_size = image_size
33+ self.num_det_boxes = num_det_boxes
34+ self.num_map_instances = num_map_instances
35+ self.num_det_classes = num_det_classes
36+ self.num_map_classes = num_map_classes
37+ 
38+ def __len__(self):
39+ return self.length
40+ 
41+ def __getitem__(self, idx):
42+ h, w = self.image_size
43+ 
44+ image = torch.randn(3, h, w)
45+ 
46+ gt_det_boxes = torch.randn(self.num_det_boxes, 11)
47+ gt_det_labels = torch.randint(0, self.num_det_classes, (self.num_det_boxes,))
48+ 
49+ gt_map_points = torch.randn(self.num_map_instances, 40)
50+ gt_map_labels = torch.randint(0, self.num_map_classes, (self.num_map_instances,))
51+ 
52+ gt_motion = torch.randn(24)
53+ gt_plan = torch.randn(12)
54+ 
55+ sample = {
56+ "inputs": image,
57+ "data_samples": {
58+ "sample_idx": idx,
59+ "img_shape": (h, w),
60+ "gt_det_boxes": gt_det_boxes,
61+ "gt_det_labels": gt_det_labels,
62+ "gt_map_points": gt_map_points,
63+ "gt_map_labels": gt_map_labels,
64+ "gt_motion": gt_motion,
65+ "gt_plan": gt_plan,
66+ }
67+ }
68+ return sample
69+ 
70+ 
71+def simple_collate(batch):
72+ inputs = torch.stack([item["inputs"] for item in batch], dim=0)
73+ data_samples = [item["data_samples"] for item in batch]
74+ return {
75+ "inputs": inputs,
76+ "data_samples": data_samples,
77+ }
@@ -0,0 +1,3 @@
1+from .simple_v1sparsedrive import SimpleV1SparseDrive
2+ 
3+__all__ = ["SimpleV1SparseDrive"]
@@ -0,0 +1,6 @@
1+mmdet==2.28.2
2+mmengine==0.10.7
3+scipy==1.13.1
4+opencv-python==4.7.0.68
5+pandas==2.3.3
6+numpy==1.23.4
@@ -0,0 +1,280 @@
1+import os
2+import argparse
3+import random
4+import time
5+import numpy as np
6+import torch
7+from torch.utils.data import DataLoader
8+ 
9+import torch_npu
10+ 
11+from config import CONFIG
12+from datasets.dummy_dataset import DummySparseDriveDataset, simple_collate
13+from models import SimpleV1SparseDrive
14+ 
15+def patch_torch_npu_dvm_decomp_excludes(verbose: bool = True):
16+ """
17+ Runtime patch for:
18+ .../site-packages/torch_npu/_inductor/dvm/decomp.py
19+ 
20+ It appends:
21+ aten._native_batch_norm_legit_functional
22+ aten.native_batch_norm_backward
23+ 
24+ into decomps_to_exclude_npu (idempotent), then triggers patch_decomp().
25+ """
26+ import importlib
27+ import torch
28+ 
29+ mod = importlib.import_module("torch_npu._inductor.dvm.decomp")
30+ 
31+ aten = torch.ops.aten
32+ to_add = [
33+ aten._native_batch_norm_legit_functional,
34+ aten.native_batch_norm_backward,
35+ ]
36+ 
37+ if not hasattr(mod, "decomps_to_exclude_npu"):
38+ raise RuntimeError("torch_npu._inductor.dvm.decomp has no decomps_to_exclude_npu")
39+ 
40+ lst = mod.decomps_to_exclude_npu
41+ added = 0
42+ for op in to_add:
43+ if op not in lst:
44+ lst.append(op)
45+ added += 1
46+ 
47+ if verbose:
48+ print(
49+ f"[PATCH] torch_npu dvm decomp: added {added}/{len(to_add)} excludes. "
50+ f"Now len={len(lst)}"
51+ )
52+ if hasattr(mod, "patch_decomp"):
53+ mod.patch_decomp()
54+ if verbose:
55+ print("[PATCH] torch_npu dvm decomp: patch_decomp() called.")
56+ else:
57+ if verbose:
58+ print("[PATCH] torch_npu dvm decomp: no patch_decomp() found; only list updated.")
59+ 
60+ return mod
61+ 
62+def parse_args():
63+ parser = argparse.ArgumentParser()
64+ parser.add_argument(
65+ "--compile",
66+ action="store_true",
67+ help="Enable torch.compile (default: eager).",
68+ )
69+ parser.add_argument(
70+ "--profile",
71+ action="store_true",
72+ help="Enable torch_npu profiler (default: off).",
73+ )
74+ parser.add_argument(
75+ "--prof-dir",
76+ type=str,
77+ default=os.path.join(".", "prof", "example.prof"),
78+ help="Profiler output dir (only used when --profile).",
79+ )
80+ parser.add_argument(
81+ "--npu-backend",
82+ type=str,
83+ default="dvm")
84+ return parser.parse_args()
85+ 
86+ 
87+def set_seed(seed=3407):
88+ random.seed(seed)
89+ np.random.seed(seed)
90+ torch.manual_seed(seed)
91+ if torch.cuda.is_available():
92+ torch.cuda.manual_seed_all(seed)
93+ if hasattr(torch, "npu") and torch.npu.is_available():
94+ torch.npu.manual_seed_all(seed)
95+ 
96+ 
97+def get_profiler_config():
98+ return torch_npu.profiler._ExperimentalConfig(
99+ export_type=[
100+ torch_npu.profiler.ExportType.Text,
101+ torch_npu.profiler.ExportType.Db,
102+ ],
103+ profiler_level=torch_npu.profiler.ProfilerLevel.Level2,
104+ msprof_tx=False,
105+ aic_metrics=torch_npu.profiler.AiCMetrics.AiCoreNone,
106+ l2_cache=False,
107+ op_attr=False,
108+ data_simplification=False,
109+ record_op_args=False,
110+ gc_detect_threshold=None,
111+ )
112+ 
113+ 
114+class NullProfiler:
115+ """兼容 profile 的空实现:start/step/stop 都是 no-op。"""
116+ def start(self): pass
117+ def step(self): pass
118+ def stop(self): pass
119+ 
120+ 
121+def maybe_enable_compile(model, enabled: bool):
122+ patch_torch_npu_dvm_decomp_excludes(verbose=True)
123+ if not enabled:
124+ print("[INFO] torch.compile disabled (eager mode).")
125+ return model
126+ try:
127+ model.img_backbone = torch.compile(
128+ model.img_backbone, dynamic=False, mode="reduce-overhead"
129+ )
130+ print("[INFO] torch.compile enabled successfully.")
131+ except Exception:
132+ print("[WARN] torch.compile failed, fallback to eager mode.")
133+ 
134+ return model
135+ 
136+ 
137+def build_profiler(enabled: bool, prof_output_dir: str, warmup_step_num=5, exec_step_num=10):
138+ if not enabled:
139+ print("[INFO] profiler disabled.")
140+ return NullProfiler()
141+ 
142+ os.makedirs(os.path.dirname(prof_output_dir), exist_ok=True)
143+ g_prof_config = get_profiler_config()
144+ 
145+ prof = torch_npu.profiler.profile(
146+ activities=[
147+ torch_npu.profiler.ProfilerActivity.CPU,
148+ torch_npu.profiler.ProfilerActivity.NPU,
149+ ],
150+ schedule=torch_npu.profiler.schedule(
151+ wait=0, warmup=warmup_step_num, active=exec_step_num, repeat=1
152+ ),
153+ on_trace_ready=torch_npu.profiler.tensorboard_trace_handler(prof_output_dir),
154+ record_shapes=False,
155+ profile_memory=False,
156+ with_stack=False,
157+ with_modules=False,
158+ with_flops=False,
159+ experimental_config=g_prof_config,
160+ )
161+ print(f"[INFO] profiler enabled. output_dir = {prof_output_dir}")
162+ return prof
163+ 
164+ 
165+def main():
166+ args = parse_args()
167+ os.environ['TORCHINDUCTOR_NPU_BACKEND']=args.npu_backend
168+ torch.use_deterministic_algorithms(True)
169+ cfg = CONFIG
170+ set_seed(cfg["seed"])
171+ 
172+ device = (
173+ "npu" if hasattr(torch, "npu") and torch.npu.is_available()
174+ else "cuda" if torch.cuda.is_available()
175+ else "cpu"
176+ )
177+ print(f"[INFO] device = {device}")
178+ print(f"[INFO] flags: compile={args.compile}, profile={args.profile}")
179+ 
180+ dataset = DummySparseDriveDataset(**cfg["dummy_dataset"])
181+ dataloader = DataLoader(
182+ dataset,
183+ batch_size=cfg["batch_size"],
184+ shuffle=True,
185+ num_workers=cfg["num_workers"],
186+ collate_fn=simple_collate,
187+ drop_last=True,
188+ )
189+ 
190+ model = SimpleV1SparseDrive(**cfg["model"]).to(device)
191+ print(model)
192+ 
193+ # optional compile
194+ model = maybe_enable_compile(model, enabled=args.compile)
195+ 
196+ # optional profiler
197+ warmup_step_num = 5
198+ exec_step_num = 10
199+ prof = build_profiler(
200+ enabled=args.profile,
201+ prof_output_dir=args.prof_dir,
202+ warmup_step_num=warmup_step_num,
203+ exec_step_num=exec_step_num,
204+ )
205+ 
206+ model.train()
207+ optimizer = torch.optim.AdamW(
208+ model.parameters(),
209+ lr=cfg["lr"],
210+ weight_decay=cfg["weight_decay"],
211+ )
212+ 
213+ prof.start()
214+ print("[INFO] start training debug...")
215+ 
216+ for step, batch in enumerate(dataloader):
217+ if step >= cfg["train_steps"]:
218+ break
219+ 
220+ step_start_time = time.time()
221+ 
222+ inputs = batch["inputs"].to(device)
223+ data_samples = batch["data_samples"]
224+ 
225+ optimizer.zero_grad()
226+ loss_dict = model(inputs, data_samples=data_samples, mode="loss")
227+ total_loss = sum(v for v in loss_dict.values())
228+ total_loss.backward()
229+ optimizer.step()
230+ 
231+ prof.step()
232+ 
233+ step_time = time.time() - step_start_time
234+ log_str = f"[step {step}] total_loss={total_loss.item():.6f}, time={step_time:.4f}s"
235+ for k, v in loss_dict.items():
236+ log_str += f", {k}={v.item():.6f}"
237+ print(log_str)
238+ 
239+ print("[INFO] train debug finished.")
240+ prof.stop()
241+ 
242+ # quick sanity forward
243+ model.eval()
244+ with torch.no_grad():
245+ batch = next(iter(dataloader))
246+ inputs = batch["inputs"].to(device)
247+ data_samples = batch["data_samples"]
248+ outputs = model(inputs, data_samples=data_samples, mode="predict")
249+ 
250+ print("=" * 120)
251+ print("[det]")
252+ print("det query:", outputs["head"]["det"]["query"].shape)
253+ print("det cls layers:", len(outputs["head"]["det"]["all_cls_scores"]))
254+ print("det reg layers:", len(outputs["head"]["det"]["all_reg_preds"]))
255+ print("det last cls:", outputs["head"]["det"]["all_cls_scores"][-1].shape)
256+ print("det last reg:", outputs["head"]["det"]["all_reg_preds"][-1].shape)
257+ 
258+ print("\n[map]")
259+ print("map query:", outputs["head"]["map"]["query"].shape)
260+ print("map cls layers:", len(outputs["head"]["map"]["all_cls_scores"]))
261+ print("map reg layers:", len(outputs["head"]["map"]["all_reg_preds"]))
262+ print("map last cls:", outputs["head"]["map"]["all_cls_scores"][-1].shape)
263+ print("map last reg:", outputs["head"]["map"]["all_reg_preds"][-1].shape)
264+ 
265+ print("\n[motion_plan]")
266+ mp = outputs["head"]["motion_plan"]
267+ for k, v in mp.items():
268+ print(k, v.shape if v is not None else None)
269+ 
270+ print("\n[depth_branch]")
271+ for i, d in enumerate(outputs["depth_branch"]):
272+ print(f"depth[{i}] shape: {d.shape}")
273+ 
274+ print("\n[fpn_feats]")
275+ for i, f in enumerate(outputs["fpn_feats"]):
276+ print(f"fpn_feats[{i}] shape: {f.shape}")
277+ 
278+ 
279+if __name__ == "__main__":
280+ main()