oriented-det 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. export/__init__.py +9 -0
  2. export/ort_runtime.py +67 -0
  3. export/postprocess.py +151 -0
  4. export/scripts/__init__.py +6 -0
  5. export/scripts/build_faster_rcnn_savedmodel.py +104 -0
  6. export/scripts/export_onnx.py +210 -0
  7. export/scripts/onnx_to_savedmodel.py +35 -0
  8. export/scripts/predict_savedmodel.py +94 -0
  9. export/scripts/save_predictions_tf.py +447 -0
  10. export/scripts/to_tflite.py +32 -0
  11. export/tests/test_export_onnx_optional.py +82 -0
  12. export/tests/test_export_wrappers.py +105 -0
  13. export/tests/test_faster_rcnn_export_parity.py +201 -0
  14. export/tests/test_ort_runtime.py +41 -0
  15. export/tf_serving_model.py +96 -0
  16. export/val_dataset.py +116 -0
  17. export/wrappers.py +161 -0
  18. oriented_det/__init__.py +77 -0
  19. oriented_det/cli/__init__.py +92 -0
  20. oriented_det/cli/train.py +18 -0
  21. oriented_det/configs/_base_/augmentation.json +21 -0
  22. oriented_det/configs/_base_/datasets/dota_le90.json +21 -0
  23. oriented_det/configs/_base_/fp16.json +5 -0
  24. oriented_det/configs/_base_/models/oriented_rcnn_r50.json +26 -0
  25. oriented_det/configs/_base_/models/rotated_faster_rcnn_r50.json +53 -0
  26. oriented_det/configs/_base_/models/rotated_retinanet_r50.json +30 -0
  27. oriented_det/configs/_base_/preprocessing.json +8 -0
  28. oriented_det/configs/_base_/schedules/1x.json +33 -0
  29. oriented_det/configs/config.schema.json +404 -0
  30. oriented_det/configs/oriented_rcnn/dota_le90_1x.json +121 -0
  31. oriented_det/configs/oriented_rcnn/dota_le90_3x.json +11 -0
  32. oriented_det/configs/rotated_faster_rcnn/dota_le90_1x.json +119 -0
  33. oriented_det/configs/rotated_faster_rcnn/dota_le90_3x.json +9 -0
  34. oriented_det/configs/rotated_retinanet/dota_le90_1x.json +107 -0
  35. oriented_det/configs/rotated_retinanet/dota_le90_3x.json +30 -0
  36. oriented_det/data/__init__.py +89 -0
  37. oriented_det/data/airbus_playground.py +611 -0
  38. oriented_det/data/dota.py +741 -0
  39. oriented_det/data/dota_classes.py +26 -0
  40. oriented_det/data/evaluation.py +648 -0
  41. oriented_det/data/flips.py +115 -0
  42. oriented_det/data/preprocessing.py +335 -0
  43. oriented_det/data/tiling.py +399 -0
  44. oriented_det/data/transforms.py +377 -0
  45. oriented_det/geometry/__init__.py +8 -0
  46. oriented_det/geometry/poly.py +127 -0
  47. oriented_det/geometry/qbox.py +63 -0
  48. oriented_det/geometry/rbox.py +250 -0
  49. oriented_det/geometry/transforms.py +266 -0
  50. oriented_det/models/__init__.py +36 -0
  51. oriented_det/models/backbones/__init__.py +11 -0
  52. oriented_det/models/backbones/resnet_fpn.py +79 -0
  53. oriented_det/models/backbones/utils.py +81 -0
  54. oriented_det/models/bbox_coder.py +355 -0
  55. oriented_det/models/faster_rcnn_inference.py +494 -0
  56. oriented_det/models/horizontal_roi_coder.py +155 -0
  57. oriented_det/models/oriented_rcnn.py +1256 -0
  58. oriented_det/models/oriented_roi.py +1664 -0
  59. oriented_det/models/oriented_rpn.py +2104 -0
  60. oriented_det/models/rotated_retinanet.py +1030 -0
  61. oriented_det/models/utils.py +590 -0
  62. oriented_det/ops/__init__.py +58 -0
  63. oriented_det/ops/gpu_ops.py +1109 -0
  64. oriented_det/ops/iou.py +172 -0
  65. oriented_det/ops/kfiou.py +275 -0
  66. oriented_det/ops/nms.py +202 -0
  67. oriented_det/ops/probiou.py +165 -0
  68. oriented_det/ops/rotated_ops.py +122 -0
  69. oriented_det/ops/utils.py +257 -0
  70. oriented_det/pretrained/__init__.py +23 -0
  71. oriented_det/pretrained/hub.py +249 -0
  72. oriented_det/pretrained/manifest.json +46 -0
  73. oriented_det/runtime/__init__.py +29 -0
  74. oriented_det/runtime/checkpoint.py +274 -0
  75. oriented_det/runtime/collate.py +348 -0
  76. oriented_det/runtime/inference.py +1286 -0
  77. oriented_det/train/__init__.py +102 -0
  78. oriented_det/train/config.py +872 -0
  79. oriented_det/train/engine.py +2933 -0
  80. oriented_det/train/grouped_ce.py +139 -0
  81. oriented_det/train/piecewise_schedule.py +33 -0
  82. oriented_det/train/profiler.py +287 -0
  83. oriented_det/train/utils.py +999 -0
  84. oriented_det/utils/__init__.py +31 -0
  85. oriented_det/utils/config.py +376 -0
  86. oriented_det/utils/device.py +35 -0
  87. oriented_det/utils/logging.py +163 -0
  88. oriented_det/utils/progress.py +62 -0
  89. oriented_det/utils/viz.py +181 -0
  90. oriented_det-0.1.0.dist-info/METADATA +313 -0
  91. oriented_det-0.1.0.dist-info/RECORD +115 -0
  92. oriented_det-0.1.0.dist-info/WHEEL +5 -0
  93. oriented_det-0.1.0.dist-info/entry_points.txt +2 -0
  94. oriented_det-0.1.0.dist-info/licenses/LICENSE +202 -0
  95. oriented_det-0.1.0.dist-info/top_level.txt +3 -0
  96. tools/__init__.py +1 -0
  97. tools/app.py +1284 -0
  98. tools/dataset_stats.py +389 -0
  99. tools/dota_labels_to_comma.py +132 -0
  100. tools/free_gpu.py +142 -0
  101. tools/generate_airbus_playground_csv.py +154 -0
  102. tools/image_demo.py +200 -0
  103. tools/lr_finder.py +771 -0
  104. tools/measure_sampled_riou_error.py +483 -0
  105. tools/playground_to_dota.py +290 -0
  106. tools/pretrained_download.py +49 -0
  107. tools/preview_augmentation.py +510 -0
  108. tools/publish_checkpoint.py +96 -0
  109. tools/save_predictions.py +2350 -0
  110. tools/sync_vendored_configs.py +94 -0
  111. tools/tile_dota.py +447 -0
  112. tools/train.py +2324 -0
  113. tools/train_example.py +244 -0
  114. tools/train_multi_gpu.py +367 -0
  115. tools/visualize_boxes.py +183 -0
tools/train_example.py ADDED
@@ -0,0 +1,244 @@
1
+ """Example: Training oriented detection models on DOTA dataset.
2
+
3
+ This script demonstrates how to:
4
+ 1. Load DOTA dataset
5
+ 2. Set up model and optimizer
6
+ 3. Train with the training engine
7
+ 4. Evaluate on validation set
8
+ 5. Save checkpoints
9
+ """
10
+
11
+ import os
12
+ # Set CUDA memory allocation configuration before importing PyTorch
13
+ # This helps with CUDA out of memory errors by using expandable segments
14
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
15
+
16
+ import argparse
17
+ from pathlib import Path
18
+ import sys
19
+
20
+ try:
21
+ import torch
22
+ import torch.optim as optim
23
+ from torch.utils.data import DataLoader
24
+ except ImportError as e:
25
+ print(f"Required dependencies not installed: {e}")
26
+ print("Please install: pip install torch")
27
+ sys.exit(1)
28
+
29
+ from oriented_det import OrientedRCNN, RotatedRetinaNet
30
+ from oriented_det.data import build_dota_loader, DOTADataset
31
+ from oriented_det.train import train, CheckpointManager, MetricTracker
32
+ from oriented_det.train.utils import collate_dota_samples
33
+
34
+ try:
35
+ from torch.utils.tensorboard import SummaryWriter
36
+ TENSORBOARD_AVAILABLE = True
37
+ except ImportError:
38
+ TENSORBOARD_AVAILABLE = False
39
+ SummaryWriter = None # type: ignore
40
+
41
+
42
+ def create_model(model_type: str, num_classes: int, backbone: str = "resnet50", pretrained: bool = False):
43
+ """Create a detection model.
44
+
45
+ Args:
46
+ model_type: "oriented_rcnn" or "rotated_retinanet"
47
+ num_classes: Number of detection classes
48
+ backbone: Backbone name
49
+ pretrained: Whether to use pretrained backbone
50
+ """
51
+ if model_type == "oriented_rcnn":
52
+ return OrientedRCNN(
53
+ num_classes=num_classes,
54
+ backbone_name=backbone,
55
+ pretrained_backbone=pretrained,
56
+ )
57
+ elif model_type == "rotated_retinanet":
58
+ return RotatedRetinaNet(
59
+ num_classes=num_classes,
60
+ backbone_name=backbone,
61
+ pretrained_backbone=pretrained,
62
+ )
63
+ else:
64
+ raise ValueError(f"Unknown model type: {model_type}")
65
+
66
+
67
+ def create_data_loaders(
68
+ data_root: Path,
69
+ batch_size: int = 4,
70
+ num_workers: int = 4,
71
+ allowed_classes: list = None,
72
+ difficult_strategy: str = "drop",
73
+ ):
74
+ """Create training and validation data loaders.
75
+
76
+ Args:
77
+ data_root: Root directory of DOTA dataset
78
+ batch_size: Batch size
79
+ num_workers: Number of data loading workers
80
+ allowed_classes: Optional list of allowed class names
81
+ difficult_strategy: drop | ignore | keep (DOTA difficult flag handling)
82
+ """
83
+ # Note: This is a simplified example. In practice, you'd need to:
84
+ # 1. Load images properly (DOTADataset returns paths, not tensors)
85
+ # 2. Apply transforms/augmentation
86
+ # 3. Create a proper collate function that loads images
87
+
88
+ train_loader = build_dota_loader(
89
+ root_dir=data_root,
90
+ split="train",
91
+ batch_size=batch_size,
92
+ shuffle=True,
93
+ num_workers=num_workers,
94
+ allowed_classes=allowed_classes,
95
+ difficult_strategy=difficult_strategy,
96
+ )
97
+
98
+ val_loader = build_dota_loader(
99
+ root_dir=data_root,
100
+ split="val",
101
+ batch_size=batch_size,
102
+ shuffle=False,
103
+ num_workers=num_workers,
104
+ allowed_classes=allowed_classes,
105
+ difficult_strategy=difficult_strategy,
106
+ )
107
+
108
+ return train_loader, val_loader
109
+
110
+
111
+ def main():
112
+ parser = argparse.ArgumentParser(description="Train oriented detection model on DOTA")
113
+ parser.add_argument("data_root", type=Path, help="Root directory of DOTA dataset")
114
+ parser.add_argument("--model-type", choices=["oriented_rcnn", "rotated_retinanet"], default="oriented_rcnn",
115
+ help="Model type to train")
116
+ parser.add_argument("--backbone", default="resnet50", help="Backbone architecture")
117
+ parser.add_argument("--num-classes", type=int, default=15, help="Number of classes")
118
+ parser.add_argument("--batch-size", type=int, default=4, help="Batch size")
119
+ parser.add_argument("--num-epochs", type=int, default=12, help="Number of training epochs")
120
+ parser.add_argument("--lr", type=float, default=0.001, help="Learning rate")
121
+ parser.add_argument("--device", default=None,
122
+ help="Device to train on")
123
+ parser.add_argument("--checkpoint-dir", type=Path, default=Path("checkpoints"),
124
+ help="Directory to save checkpoints")
125
+ parser.add_argument("--resume", type=Path, help="Path to checkpoint to resume from")
126
+ parser.add_argument("--use-amp", action="store_true", help="Use automatic mixed precision")
127
+ parser.add_argument("--gradient-accumulation", type=int, default=1,
128
+ help="Gradient accumulation steps")
129
+ parser.add_argument("--num-workers", type=int, default=4, help="Number of data loading workers")
130
+ parser.add_argument("--log-dir", type=Path, help="Directory for TensorBoard logs (enables TensorBoard if provided)")
131
+
132
+ args = parser.parse_args()
133
+
134
+ if args.device is None:
135
+ from oriented_det.utils import get_device
136
+ args.device = str(get_device())
137
+
138
+ # Create model
139
+ print(f"Creating {args.model_type} model with {args.num_classes} classes...")
140
+ model = create_model(args.model_type, args.num_classes, args.backbone, pretrained=True)
141
+ model.to(args.device)
142
+
143
+ # Create data loaders
144
+ print(f"Loading DOTA dataset from {args.data_root}...")
145
+ try:
146
+ train_loader, val_loader = create_data_loaders(
147
+ args.data_root,
148
+ batch_size=args.batch_size,
149
+ num_workers=args.num_workers,
150
+ )
151
+ print(f"Train samples: {len(train_loader.dataset)}")
152
+ print(f"Val samples: {len(val_loader.dataset)}")
153
+ except Exception as e:
154
+ print(f"Error loading dataset: {e}")
155
+ print("\nNote: This example requires a properly formatted DOTA dataset.")
156
+ print("The dataset should have the following structure:")
157
+ print(" data_root/")
158
+ print(" labelTxt/ (annotation files)")
159
+ print(" images/ (image files)")
160
+ sys.exit(1)
161
+
162
+ # Create optimizer
163
+ optimizer = optim.SGD(
164
+ model.parameters(),
165
+ lr=args.lr,
166
+ momentum=0.9,
167
+ weight_decay=0.0001,
168
+ )
169
+
170
+ # Learning rate scheduler
171
+ lr_scheduler = optim.lr_scheduler.StepLR(
172
+ optimizer,
173
+ step_size=8,
174
+ gamma=0.1,
175
+ )
176
+
177
+ # Checkpoint manager
178
+ checkpoint_manager = CheckpointManager(
179
+ args.checkpoint_dir,
180
+ best_metric="total_loss", # In practice, use mAP
181
+ higher_is_better=False,
182
+ )
183
+
184
+ # Setup TensorBoard writer if log_dir is provided
185
+ writer = None
186
+ if args.log_dir:
187
+ if not TENSORBOARD_AVAILABLE:
188
+ print("Warning: TensorBoard is not available. Install with: pip install tensorboard")
189
+ else:
190
+ args.log_dir.mkdir(parents=True, exist_ok=True)
191
+ writer = SummaryWriter(log_dir=str(args.log_dir))
192
+ print(f"TensorBoard logging enabled. Logs saved to: {args.log_dir}")
193
+ print(f"View logs with: tensorboard --logdir {args.log_dir}")
194
+
195
+ # Resume from checkpoint if provided
196
+ start_epoch = 0
197
+ if args.resume and args.resume.exists():
198
+ print(f"Resuming from checkpoint: {args.resume}")
199
+ checkpoint_manager.load(args.resume, model, optimizer)
200
+ start_epoch = checkpoint_manager.load(args.resume, model, optimizer).get("epoch", 0) + 1
201
+
202
+ # Training configuration
203
+ print("\nTraining configuration:")
204
+ print(f" Model: {args.model_type}")
205
+ print(f" Backbone: {args.backbone}")
206
+ print(f" Batch size: {args.batch_size}")
207
+ print(f" Learning rate: {args.lr}")
208
+ print(f" Epochs: {args.num_epochs}")
209
+ print(f" Device: {args.device}")
210
+ print(f" Mixed precision: {args.use_amp}")
211
+ print(f" Gradient accumulation: {args.gradient_accumulation}")
212
+ print()
213
+
214
+ # Train
215
+ try:
216
+ history = train(
217
+ model=model,
218
+ train_loader=train_loader,
219
+ optimizer=optimizer,
220
+ device=torch.device(args.device),
221
+ num_epochs=args.num_epochs,
222
+ val_loader=val_loader,
223
+ lr_scheduler=lr_scheduler,
224
+ checkpoint_manager=checkpoint_manager,
225
+ use_amp=args.use_amp,
226
+ gradient_accumulation_steps=args.gradient_accumulation,
227
+ max_grad_norm=1.0,
228
+ start_epoch=start_epoch,
229
+ writer=writer,
230
+ )
231
+
232
+ print("\nTraining completed!")
233
+ print(f"Checkpoints saved to: {args.checkpoint_dir}")
234
+
235
+ except KeyboardInterrupt:
236
+ print("\nTraining interrupted by user")
237
+ print(f"Checkpoints saved to: {args.checkpoint_dir}")
238
+ except Exception as e:
239
+ print(f"\nTraining error: {e}")
240
+ raise
241
+
242
+
243
+ if __name__ == "__main__":
244
+ main()
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env python3
2
+ """Multi-GPU training wrapper for train.py.
3
+
4
+ This script launches train.py with torchrun for distributed training across multiple GPUs.
5
+ Configuration is read from JSON config files; pass --config <path> and other train.py arguments.
6
+
7
+ Usage:
8
+ python train_multi_gpu.py [--nproc-per-node N] --config <config.json> [train.py arguments...]
9
+
10
+ Examples:
11
+ # Use all available GPUs with default config
12
+ python train_multi_gpu.py --config configs/rotated_faster_rcnn/dota_le90_3x.json
13
+
14
+ # Use 4 GPUs
15
+ python train_multi_gpu.py --nproc-per-node 4 --config configs/rotated_faster_rcnn/dota_le90_3x.json
16
+
17
+ # Use 2 GPUs with custom arguments
18
+ python train_multi_gpu.py --nproc-per-node 2 --config configs/.../config.json --use-amp
19
+
20
+ Checkpoint loading is configured only in the JSON ``checkpoint`` section (same as ``train.py``).
21
+
22
+ The script automatically:
23
+ - Detects the number of available GPUs
24
+ - Launches train.py with torchrun
25
+ - Forwards all arguments (including --config) to train.py
26
+ - Sets up proper environment variables for distributed training
27
+ """
28
+
29
+ import subprocess
30
+ import sys
31
+ import os
32
+ from pathlib import Path
33
+
34
+ try:
35
+ import torch
36
+ except ImportError:
37
+ print("Error: PyTorch is not installed. Please install PyTorch first.")
38
+ sys.exit(1)
39
+
40
+
41
+ def get_num_gpus():
42
+ """Get the number of available GPUs."""
43
+ if not torch.cuda.is_available():
44
+ print("Warning: CUDA is not available. Falling back to CPU training.")
45
+ return 0
46
+ return torch.cuda.device_count()
47
+
48
+
49
+ def find_torchrun():
50
+ """Find torchrun executable."""
51
+ # Try torchrun first (PyTorch 1.9+)
52
+ try:
53
+ result = subprocess.run(
54
+ ["torchrun", "--help"],
55
+ capture_output=True,
56
+ text=True,
57
+ timeout=5,
58
+ )
59
+ if result.returncode == 0:
60
+ return "torchrun"
61
+ except (FileNotFoundError, subprocess.TimeoutExpired):
62
+ pass
63
+
64
+ # Fallback to python -m torch.distributed.launch
65
+ try:
66
+ result = subprocess.run(
67
+ [sys.executable, "-m", "torch.distributed.launch", "--help"],
68
+ capture_output=True,
69
+ text=True,
70
+ timeout=5,
71
+ )
72
+ if result.returncode == 0:
73
+ return "python -m torch.distributed.launch"
74
+ except (FileNotFoundError, subprocess.TimeoutExpired):
75
+ pass
76
+
77
+ return None
78
+
79
+
80
+ def main():
81
+ """Main entry point."""
82
+ import argparse
83
+
84
+ parser = argparse.ArgumentParser(
85
+ description="Multi-GPU training wrapper for train.py (config-based training)",
86
+ formatter_class=argparse.RawDescriptionHelpFormatter,
87
+ epilog="""
88
+ Examples:
89
+ # Use all available GPUs (pass --config and any train.py options)
90
+ python train_multi_gpu.py --config configs/rotated_faster_rcnn/dota_le90_3x.json
91
+
92
+ # Use 4 GPUs
93
+ python train_multi_gpu.py --nproc-per-node 4 --config configs/.../config.json
94
+
95
+ # Use 2 GPUs with custom arguments
96
+ python train_multi_gpu.py --nproc-per-node 2 --config configs/.../config.json --use-amp --debug
97
+ """,
98
+ )
99
+
100
+ parser.add_argument(
101
+ "--nproc-per-node",
102
+ type=int,
103
+ default=None,
104
+ help="Number of processes per node (default: number of available GPUs)",
105
+ )
106
+
107
+ parser.add_argument(
108
+ "--master-port",
109
+ type=int,
110
+ default=29500,
111
+ help="Master port for distributed training (default: 29500)",
112
+ )
113
+
114
+ parser.add_argument(
115
+ "--backend",
116
+ choices=("gloo", "nccl"),
117
+ default=None,
118
+ help="Distributed backend (default: nccl for single-node, faster than Gloo with P2P/SHM enabled)",
119
+ )
120
+
121
+ # Parse known args to separate wrapper args from train.py args
122
+ args, unknown_args = parser.parse_known_args()
123
+
124
+ # Get number of GPUs
125
+ num_gpus = get_num_gpus()
126
+
127
+ if num_gpus == 0:
128
+ print("Error: No GPUs available. Cannot run multi-GPU training.")
129
+ print("Please use train.py directly for CPU or single-GPU training.")
130
+ sys.exit(1)
131
+
132
+ # Determine number of processes
133
+ nproc_per_node = args.nproc_per_node or num_gpus
134
+
135
+ if nproc_per_node > num_gpus:
136
+ print(f"Warning: Requested {nproc_per_node} GPUs but only {num_gpus} available.")
137
+ print(f"Using {num_gpus} GPUs instead.")
138
+ nproc_per_node = num_gpus
139
+
140
+ if nproc_per_node < 1:
141
+ print("Error: nproc-per-node must be at least 1.")
142
+ sys.exit(1)
143
+
144
+ # Find torchrun
145
+ torchrun_cmd = find_torchrun()
146
+ if torchrun_cmd is None:
147
+ print("Error: Could not find torchrun or torch.distributed.launch.")
148
+ print("Please ensure PyTorch is properly installed.")
149
+ sys.exit(1)
150
+
151
+ # Train via installable module (odet train / oriented_det.cli.train)
152
+ train_module = "oriented_det.cli.train"
153
+ extra_args = unknown_args
154
+ if torchrun_cmd == "torchrun":
155
+ cmd = [
156
+ "torchrun",
157
+ "--nproc-per-node", str(nproc_per_node),
158
+ "--master-port", str(args.master_port),
159
+ "-m", train_module,
160
+ ] + extra_args
161
+ else:
162
+ cmd = [
163
+ sys.executable,
164
+ "-m", "torch.distributed.launch",
165
+ "--nproc-per-node", str(nproc_per_node),
166
+ "--master-port", str(args.master_port),
167
+ train_module,
168
+ ] + extra_args
169
+
170
+ # Set up environment for single-node multi-GPU training
171
+ env = os.environ.copy()
172
+
173
+ # Short TMPDIR to avoid Gloo "File name too long" (store paths can embed hostname)
174
+ if "TMPDIR" not in env:
175
+ for d in ("/dev/shm", "/tmp"):
176
+ if os.path.isdir(d):
177
+ env["TMPDIR"] = d
178
+ break
179
+
180
+ # Backend: default NCCL for all (faster with P2P/SHM enabled on single-node A100)
181
+ # With PyTorch 2.3+ and proper configuration, NCCL works reliably on single-node
182
+ if args.backend:
183
+ env["DIST_BACKEND"] = args.backend
184
+ else:
185
+ env.setdefault("DIST_BACKEND", "nccl") # NCCL is faster than Gloo on single-node multi-GPU
186
+
187
+ # Ensure MASTER_ADDR and MASTER_PORT are set for distributed init
188
+ env.setdefault("MASTER_ADDR", "127.0.0.1")
189
+ env.setdefault("MASTER_PORT", str(args.master_port))
190
+
191
+ socket_ifname = None
192
+ use_gib = False
193
+
194
+ # Configure NCCL for GCP. gIB is only available on A3 Ultra, A4, A4X instances.
195
+ # A2 instances (A100) do NOT support gIB - use socket-only NCCL (NVLink intra-node, sockets/gVNIC inter-node).
196
+ if env.get("DIST_BACKEND", "nccl") == "nccl":
197
+ # Check instance type to determine if gIB is available
198
+ try:
199
+ instance_type = subprocess.run(
200
+ ["curl", "-s", "-H", "Metadata-Flavor: Google",
201
+ "http://metadata.google.internal/computeMetadata/v1/instance/machine-type"],
202
+ capture_output=True, text=True, timeout=2
203
+ )
204
+ if instance_type.returncode == 0:
205
+ instance_type = instance_type.stdout.strip().split("/")[-1]
206
+ # gIB is only available on A3 Ultra, A4, A4X (not A2)
207
+ if instance_type.startswith(("a3-ultra", "a4", "a4x")):
208
+ gib_script = "/usr/local/gib/scripts/set_nccl_env.sh"
209
+ if os.path.exists(gib_script):
210
+ use_gib = True
211
+ env["NCCL_NET"] = "gIB"
212
+ env["NCCL_CROSS_NIC"] = "0"
213
+ env["NCCL_NET_GDR_LEVEL"] = "PIX"
214
+ env["NCCL_P2P_NET_CHUNKSIZE"] = "131072"
215
+ env["NCCL_NVLS_CHUNKSIZE"] = "524288"
216
+ env["NCCL_IB_ADAPTIVE_ROUTING"] = "1"
217
+ env["NCCL_IB_QPS_PER_CONNECTION"] = "4"
218
+ env["NCCL_IB_TC"] = "52"
219
+ env["NCCL_IB_FIFO_TC"] = "84"
220
+ env["NCCL_TUNER_CONFIG_PATH"] = "/usr/local/gib/configs/tuner_config_a3u.txtpb"
221
+ print(f"Using GCP gIB backend (instance: {instance_type})")
222
+ else:
223
+ print(f"Warning: gIB script not found on {instance_type}, using socket-only NCCL")
224
+ else:
225
+ print(f"Instance {instance_type} does not support gIB (A2/A100), using socket-only NCCL")
226
+ else:
227
+ print("Could not detect instance type, using socket-only NCCL")
228
+ except Exception:
229
+ print("Could not detect instance type, using socket-only NCCL")
230
+
231
+ # A2 / socket-only: recommended NCCL settings (GDR off, socket tuning, NCCL 2.12.7+)
232
+ if not use_gib:
233
+ env.pop("NCCL_NET", None) # Ensure no inherited gIB on A2
234
+ env["NCCL_NET_GDR_LEVEL"] = "0" # Disable GPUDirect RDMA (unsupported on A2)
235
+ env["NCCL_SOCKET_NTHREADS"] = "4"
236
+ env["NCCL_NSOCKETS_PER_PEER"] = "4"
237
+ env.setdefault("NCCL_DEBUG", "WARN") # WARN: minimal output; use INFO for debugging
238
+
239
+ # Remove GCP gIB shim from LD_LIBRARY_PATH on A2 (shim enforces PIX, incompatible with A2)
240
+ # The shim (libnccl-net_internal.so) checks guest_config.txtpb and enforces gIB settings
241
+ ld_path = env.get("LD_LIBRARY_PATH", "")
242
+ if ld_path:
243
+ # Remove /usr/local/gib/lib64 to prevent shim from loading
244
+ paths = [p for p in ld_path.split(":") if p and not p.rstrip("/").endswith("/usr/local/gib/lib64")]
245
+ if paths:
246
+ env["LD_LIBRARY_PATH"] = ":".join(paths)
247
+ else:
248
+ env.pop("LD_LIBRARY_PATH", None)
249
+ print("Removed GCP gIB shim from LD_LIBRARY_PATH (A2 uses socket-only NCCL)")
250
+
251
+ # Detect and set network interface for single-node communication (required for socket-only NCCL)
252
+ socket_ifname = env.get("NCCL_SOCKET_IFNAME")
253
+ if not socket_ifname:
254
+ try:
255
+ result = subprocess.run(["ip", "route", "get", "8.8.8.8"], capture_output=True, text=True, timeout=2)
256
+ if result.returncode == 0:
257
+ for line in result.stdout.split('\n'):
258
+ if 'dev' in line:
259
+ parts = line.split()
260
+ if 'dev' in parts:
261
+ idx = parts.index('dev')
262
+ if idx + 1 < len(parts):
263
+ socket_ifname = parts[idx + 1]
264
+ break
265
+ except (FileNotFoundError, subprocess.TimeoutExpired, Exception):
266
+ pass
267
+ if not socket_ifname:
268
+ for ifname in ["ens8", "ens4", "eth0", "eno1"]:
269
+ try:
270
+ with open(f"/sys/class/net/{ifname}/operstate", "r") as f:
271
+ if "up" in f.read().lower():
272
+ socket_ifname = ifname
273
+ break
274
+ except (FileNotFoundError, IOError):
275
+ continue
276
+ if socket_ifname:
277
+ env["NCCL_SOCKET_IFNAME"] = socket_ifname
278
+
279
+ # For Gloo backend: set GLOO_SOCKET_IFNAME (reuse socket_ifname if already detected for NCCL)
280
+ if env.get("DIST_BACKEND", "nccl") == "gloo":
281
+ if not socket_ifname:
282
+ try:
283
+ result = subprocess.run(["ip", "route", "get", "8.8.8.8"], capture_output=True, text=True, timeout=2)
284
+ if result.returncode == 0:
285
+ for line in result.stdout.split('\n'):
286
+ if 'dev' in line:
287
+ parts = line.split()
288
+ if 'dev' in parts:
289
+ idx = parts.index('dev')
290
+ if idx + 1 < len(parts):
291
+ socket_ifname = parts[idx + 1]
292
+ break
293
+ except (FileNotFoundError, subprocess.TimeoutExpired, Exception):
294
+ pass
295
+ if not socket_ifname:
296
+ for ifname in ["ens8", "ens4", "eth0", "eno1"]:
297
+ try:
298
+ with open(f"/sys/class/net/{ifname}/operstate", "r") as f:
299
+ if "up" in f.read().lower():
300
+ socket_ifname = ifname
301
+ break
302
+ except (FileNotFoundError, IOError):
303
+ continue
304
+
305
+ # Optimize Gloo backend settings for better performance
306
+ if env.get("DIST_BACKEND", "nccl") == "gloo":
307
+ # Use proper network interface for Gloo
308
+ if socket_ifname:
309
+ env.setdefault("GLOO_SOCKET_IFNAME", socket_ifname)
310
+ # Increase Gloo timeout (default 30s, increase for large models)
311
+ env.setdefault("GLOO_TIMEOUT_SECONDS", "1800") # 30 minutes
312
+ # Use async operations where possible (if supported)
313
+ # Note: GLOO_ASYNC may not be available in all PyTorch versions
314
+
315
+ # Print info
316
+ print("=" * 80)
317
+ print("Multi-GPU Training Launcher")
318
+ print("=" * 80)
319
+ print(f"GPUs available: {num_gpus}")
320
+ print(f"GPUs to use: {nproc_per_node}")
321
+ print(f"Module: {train_module}")
322
+ proj = env.get("ORIENTED_DET_PROJECT_ROOT")
323
+ if proj:
324
+ print(f"ORIENTED_DET_PROJECT_ROOT: {proj}")
325
+ from oriented_det.pretrained import get_pretrained_dir
326
+
327
+ pre = env.get("ORIENTED_DET_PRETRAINED_DIR")
328
+ print(f"Pretrained cache: {pre or get_pretrained_dir()}")
329
+ print(f"Command: {' '.join(cmd)}")
330
+ print("=" * 80)
331
+ print("Distributed Training Configuration:")
332
+ backend = env.get('DIST_BACKEND', 'nccl')
333
+ print(f" Backend: {backend} (default: NCCL, use --backend gloo for Gloo)")
334
+ print(f" MASTER_ADDR: {env.get('MASTER_ADDR', '127.0.0.1')}")
335
+ print(f" MASTER_PORT: {env.get('MASTER_PORT', '29500')}")
336
+ if backend == "nccl":
337
+ net = env.get("NCCL_NET", "socket (default)")
338
+ ifname = env.get("NCCL_SOCKET_IFNAME", "not set")
339
+ if net == "gIB":
340
+ gdr_level = env.get("NCCL_NET_GDR_LEVEL", "not set")
341
+ print(f" NCCL_NET: {net} (gIB backend)")
342
+ print(f" NCCL_NET_GDR_LEVEL: {gdr_level}")
343
+ else:
344
+ print(f" NCCL_NET: {net} (socket-only, A2)")
345
+ print(f" NCCL_NET_GDR_LEVEL: {env.get('NCCL_NET_GDR_LEVEL', 'not set')}")
346
+ print(f" NCCL_SOCKET_NTHREADS: {env.get('NCCL_SOCKET_NTHREADS', 'not set')}")
347
+ print(f" NCCL_NSOCKETS_PER_PEER: {env.get('NCCL_NSOCKETS_PER_PEER', 'not set')}")
348
+ print(f" NCCL_DEBUG: {env.get('NCCL_DEBUG', 'not set')}")
349
+ print(f" NCCL_SOCKET_IFNAME: {ifname}")
350
+ print("=" * 80)
351
+ print()
352
+
353
+ # Launch training
354
+ try:
355
+ # Run the command with environment variables and forward stdout/stderr
356
+ result = subprocess.run(cmd, env=env, check=False)
357
+ sys.exit(result.returncode)
358
+ except KeyboardInterrupt:
359
+ print("\nTraining interrupted by user.")
360
+ sys.exit(130)
361
+ except Exception as e:
362
+ print(f"Error launching training: {e}")
363
+ sys.exit(1)
364
+
365
+
366
+ if __name__ == "__main__":
367
+ main()