@@ -126,7 +126,12 @@ def _metadata(autoconfig=None):
126126 return md
127127
128128 @staticmethod
129- def _estimated_vram_gib (config : SafeSynthesizerParameters , autoconfig : PretrainedConfig ) -> float :
129+ def _estimated_vram_gib (
130+ config : SafeSynthesizerParameters ,
131+ autoconfig : PretrainedConfig ,
132+ * ,
133+ batch_size : int | None = None ,
134+ ) -> float :
130135 from nemo_safe_synthesizer .preflight .checks .environment import (
131136 estimate_base_model_params ,
132137 estimate_training_vram_components ,
@@ -138,7 +143,7 @@ def _estimated_vram_gib(config: SafeSynthesizerParameters, autoconfig: Pretraine
138143 comp = estimate_training_vram_components (
139144 n_params = n_params ,
140145 training_cfg = config .training ,
141- batch_size = config .training .batch_size ,
146+ batch_size = batch_size if batch_size is not None else config .training .batch_size ,
142147 seq_len = DEFAULT_MAX_SEQ_LENGTH ,
143148 hidden_size = getattr (autoconfig , "hidden_size" , None ),
144149 num_hidden_layers = getattr (autoconfig , "num_hidden_layers" , None ),
@@ -175,6 +180,49 @@ def test_ample_vram_is_silent(self, default_config):
175180 assert not any (i .code == "low_vram" for i in issues )
176181 assert not any (i .code == "vram_exceeds_capacity" for i in issues )
177182
183+ def test_automatic_accumulation_uses_physical_batch_for_vram_estimate (self , default_config ):
184+ """A physical microbatch cap prevents a false VRAM issue for the logical batch."""
185+ config = default_config .model_copy (deep = True )
186+ config .training .batch_size = 8
187+ config .training .gradient_accumulation_steps = "auto"
188+ config .training .max_physical_batch_size = 4
189+ autoconfig = self ._autoconfig ()
190+ metadata = self ._metadata (autoconfig = autoconfig )
191+ batch_4_gib = self ._estimated_vram_gib (config , autoconfig , batch_size = 4 )
192+ batch_8_gib = self ._estimated_vram_gib (config , autoconfig , batch_size = 8 )
193+ available_gib = (batch_4_gib + batch_8_gib ) / 2
194+ fake_props = MagicMock (total_memory = 100 * 1024 ** 3 )
195+
196+ with (
197+ patch ("torch.cuda.is_available" , return_value = True ),
198+ patch ("nemo_safe_synthesizer.llm.utils.get_max_vram" , return_value = {0 : available_gib / 100 }),
199+ patch ("torch.cuda.get_device_properties" , return_value = fake_props ),
200+ ):
201+ issues = VRAMHeadroomCheck ().run (make_ctx (config = config , metadata = metadata ))
202+
203+ assert not any (issue .code in {"low_vram" , "vram_exceeds_capacity" } for issue in issues )
204+
205+ def test_automatic_accumulation_reports_physical_batch_when_vram_is_low (self , default_config ):
206+ """The VRAM diagnostic reports the capped physical per-device batch."""
207+ config = default_config .model_copy (deep = True )
208+ config .training .batch_size = 8
209+ config .training .gradient_accumulation_steps = "auto"
210+ config .training .max_physical_batch_size = 4
211+ autoconfig = self ._autoconfig ()
212+ metadata = self ._metadata (autoconfig = autoconfig )
213+ batch_4_gib = self ._estimated_vram_gib (config , autoconfig , batch_size = 4 )
214+ fake_props = MagicMock (total_memory = 100 * 1024 ** 3 )
215+
216+ with (
217+ patch ("torch.cuda.is_available" , return_value = True ),
218+ patch ("nemo_safe_synthesizer.llm.utils.get_max_vram" , return_value = {0 : batch_4_gib / 200 }),
219+ patch ("torch.cuda.get_device_properties" , return_value = fake_props ),
220+ ):
221+ issues = VRAMHeadroomCheck ().run (make_ctx (config = config , metadata = metadata ))
222+
223+ issue = next (issue for issue in issues if issue .code in {"low_vram" , "vram_exceeds_capacity" })
224+ assert "Per-device batch_size=4" in issue .message
225+
178226 def test_absurd_batch_errors (self , default_config ):
179227 """Per-device batch_size far too large must fail preflight."""
180228 default_config .training .batch_size = 100_000
0 commit comments