5050
5151
5252class TopkAccuracy (pl .Callback ):
53- """Validation top-1 accuracy on the trained head's logits (``batch["logits"]``)."""
53+ """Validation top-1 / top-5 accuracy on the head's logits (``batch["logits"]``).
54+
55+ Uses ``average="micro"`` (correct / total) — the standard ImageNet top-1
56+ every paper reports. ``torchmetrics``'s
57+ :class:`~torchmetrics.classification.MulticlassAccuracy` defaults to
58+ ``average="macro"`` (mean of per-class recall); on the full balanced
59+ ImageNet val set macro and micro nearly coincide, but micro is the
60+ unambiguous convention, so we pin it and add top-5 alongside.
61+ """
5462
5563 def __init__ (self ):
5664 super ().__init__ ()
57- self .acc = torchmetrics .classification .MulticlassAccuracy (NUM_CLASSES )
65+ MCA = torchmetrics .classification .MulticlassAccuracy
66+ self .top1 = MCA (NUM_CLASSES , top_k = 1 , average = "micro" )
67+ self .top5 = MCA (NUM_CLASSES , top_k = 5 , average = "micro" )
5868
5969 def on_validation_batch_end (self , trainer , pl_module , outputs , batch , batch_idx ):
60- self .acc .to (pl_module .device )
61- self .acc .update (batch ["logits" ], batch ["label" ].long ())
70+ self .top1 .to (pl_module .device )
71+ self .top5 .to (pl_module .device )
72+ self .top1 .update (batch ["logits" ], batch ["label" ].long ())
73+ self .top5 .update (batch ["logits" ], batch ["label" ].long ())
6274
6375 def on_validation_epoch_end (self , trainer , pl_module ):
64- pl_module .log ("val/top1" , self .acc .compute (), prog_bar = True , sync_dist = True )
65- self .acc .reset ()
76+ pl_module .log ("val/top1" , self .top1 .compute (), prog_bar = True , sync_dist = True )
77+ pl_module .log ("val/top5" , self .top5 .compute (), prog_bar = True , sync_dist = True )
78+ self .top1 .reset ()
79+ self .top5 .reset ()
6680
6781
68- def build_loaders (batch_size , num_workers ):
82+ def build_loaders (batch_size , num_workers , use_randaug = True , cpu_norm = False ):
6983 # CPU does the bare minimum (decode + square resize) so the GPU aug pipeline
7084 # is the bottleneck-free fast path. Train images stay un-normalized [0,1]
7185 # floats for the kornia GPU ops; val is fully prepared on CPU.
72- train_cpu = transforms .Compose (
73- transforms .RGB (),
74- transforms .Resize ((256 , 256 )),
75- transforms .ToImage (), # -> float tensor in [0, 1], no normalization
76- )
86+ if cpu_norm :
87+ # debug path: full CPU pipeline producing normalized 224 crops, no gpu_transform
88+ train_cpu = transforms .Compose (
89+ transforms .RGB (),
90+ transforms .RandomResizedCrop ((224 , 224 ), scale = (0.08 , 1.0 )),
91+ transforms .RandomHorizontalFlip (p = 0.5 ),
92+ transforms .ToImage (mean = _MEAN , std = _STD ),
93+ )
94+ else :
95+ train_cpu = transforms .Compose (
96+ transforms .RGB (),
97+ transforms .Resize ((256 , 256 )),
98+ transforms .ToImage (), # [0,1], GPU pipeline normalizes
99+ )
77100 val_cpu = transforms .Compose (
78101 transforms .RGB (),
79102 transforms .Resize ((256 , 256 )),
@@ -84,14 +107,18 @@ def build_loaders(batch_size, num_workers):
84107 path = "ILSVRC/imagenet-1k" , split = "train" , transform = train_cpu
85108 )
86109 # Heavy augmentation, batched on GPU (DeiT/AugReg policy).
87- train_ds . gpu_transform = GPUCompose (
110+ aug = [
88111 ToDevice (),
89112 GPURandomResizedCrop (224 , scale = (0.08 , 1.0 )),
90113 GPURandomHorizontalFlip (p = 0.5 ),
91- GPURandAugment (n = 2 , m = 9 ),
92- GPUNormalize (mean = _MEAN , std = _STD ),
93- GPURandomErasing (p = 0.25 ),
94- )
114+ ]
115+ if use_randaug :
116+ aug .append (GPURandAugment (n = 2 , m = 9 ))
117+ aug .append (GPUNormalize (mean = _MEAN , std = _STD ))
118+ if use_randaug :
119+ aug .append (GPURandomErasing (p = 0.25 ))
120+ if not cpu_norm :
121+ train_ds .gpu_transform = GPUCompose (aug )
95122 val_ds = spt .data .HFDataset (
96123 path = "ILSVRC/imagenet-1k" , split = "validation" , transform = val_cpu
97124 )
@@ -113,20 +140,43 @@ def build_loaders(batch_size, num_workers):
113140def main ():
114141 ap = argparse .ArgumentParser ()
115142 ap .add_argument ("--backbone" , default = "vit_large_patch16_224" )
143+ ap .add_argument ("--strategy" , default = "fsdp2" , help = "fsdp2 | ddp | auto (debug)" )
144+ ap .add_argument ("--devices" , default = "auto" , help = "'auto' or an int (debug)" )
116145 ap .add_argument ("--epochs" , type = int , default = 300 )
146+ ap .add_argument (
147+ "--max-steps" , type = int , default = 0 , help = ">0 caps steps (smoke test)"
148+ )
117149 ap .add_argument ("--batch-size" , type = int , default = 64 , help = "per-GPU batch" )
118150 ap .add_argument ("--lr" , type = float , default = 1e-3 )
119151 ap .add_argument ("--weight-decay" , type = float , default = 0.05 )
120152 ap .add_argument ("--drop-path" , type = float , default = 0.4 ) # DeiT-III ViT-L
121153 ap .add_argument ("--label-smoothing" , type = float , default = 0.1 )
122154 ap .add_argument ("--num-workers" , type = int , default = 12 )
155+ ap .add_argument ("--no-randaug" , action = "store_true" , help = "debug: drop RandAugment" )
156+ ap .add_argument ("--no-mixup" , action = "store_true" , help = "debug: drop Mixup/CutMix" )
157+ ap .add_argument (
158+ "--cpu-norm" ,
159+ action = "store_true" ,
160+ help = "debug: normalize on CPU, no gpu_transform" ,
161+ )
162+ ap .add_argument (
163+ "--overfit-batches" ,
164+ type = int ,
165+ default = 0 ,
166+ help = "debug: Lightning overfit_batches" ,
167+ )
123168 args = ap .parse_args ()
124169
125170 # Speed knobs (TF32 + autotuned cuDNN); bf16 comes from the Trainer.
126171 torch .set_float32_matmul_precision ("high" )
127172 torch .backends .cudnn .benchmark = True
128173
129- train_loader , val_loader = build_loaders (args .batch_size , args .num_workers )
174+ train_loader , val_loader = build_loaders (
175+ args .batch_size ,
176+ args .num_workers ,
177+ use_randaug = not args .no_randaug ,
178+ cpu_norm = args .cpu_norm ,
179+ )
130180 data = spt .data .DataModule (train = train_loader , val = val_loader )
131181
132182 backbone = getattr (spt .backbone , args .backbone )(
@@ -141,9 +191,18 @@ def main():
141191
142192 def forward (self , batch , stage ):
143193 if self .training :
144- images , soft = mixup (batch ["image" ], batch ["label" ])
194+ if args .no_mixup :
195+ images , target = batch ["image" ], batch ["label" ]
196+ else :
197+ images , target = mixup (batch ["image" ], batch ["label" ])
145198 logits = self .backbone (images )
146- batch ["loss" ] = F .cross_entropy (logits , soft )
199+ loss = F .cross_entropy (logits , target )
200+ batch ["loss" ] = loss
201+ # Log train loss so we can actually see whether the backbone learns
202+ # (CE starts near ln(1000)=6.9; flat there = frozen, dropping = learning).
203+ self .log (
204+ "train/loss" , loss .detach (), prog_bar = True , on_step = True , sync_dist = True
205+ )
147206 else :
148207 batch ["logits" ] = self .backbone (batch ["image" ])
149208 return batch
@@ -162,17 +221,22 @@ def forward(self, batch, stage):
162221 },
163222 )
164223
165- trainer = dict (
166- strategy = "fsdp2" ,
224+ # Pass a real pl.Trainer (not a config dict): the Manager wraps a dict
225+ # trainer in OmegaConf, which can't hold callback *instances*
226+ # (UnsupportedValueType). Callback instances are fine on a built Trainer.
227+ devices = args .devices if args .devices == "auto" else int (args .devices )
228+ trainer = pl .Trainer (
229+ strategy = args .strategy ,
167230 precision = "bf16-mixed" ,
168231 accelerator = "gpu" ,
169- devices = "auto" ,
232+ devices = devices ,
170233 max_epochs = args .epochs ,
234+ max_steps = args .max_steps if args .max_steps > 0 else - 1 ,
235+ overfit_batches = args .overfit_batches if args .overfit_batches > 0 else 0.0 ,
171236 callbacks = [
172237 pl .pytorch .callbacks .LearningRateMonitor (logging_interval = "step" ),
173238 TopkAccuracy (),
174239 ],
175- gradient_clip_val = 1.0 ,
176240 num_sanity_val_steps = 0 ,
177241 enable_checkpointing = True ,
178242 )
0 commit comments