"""
GLAMWalk -- Volume 2: The Gauntlet
09_vram_guarded_model.py

The load/use/unload discipline for running AI models on a VRAM-limited GPU, using a context manager to guarantee cleanup even if something inside fails.

Source: Volume Two, Section 4.2 -- The Hardware Constraint That Shapes Everything
Targets Python 3.11.4 -- see Volume One, Chapter 1.5/1.6.

NOTE: This is a teaching example. Some names (e.g. a placeholder
value or object) stand in for something you'd supply yourself when
adapting the pattern to a real script.
"""

class VRAMGuardedModel:
    """
    The general pattern every model wrapper in this pipeline follows.
    Load only when needed; release explicitly the moment the job is done,
    so the NEXT model has room. Never assume you can hold two large
    models in memory at once on an 8 GB card.
    """
    def __init__(self, model_path):
        self.model_path = model_path
        self._model = None

    def load(self):
        if self._model is None:
            print(f"Loading model from {self.model_path}...")
            self._model = _actually_load_the_model(self.model_path)  # illustrative
        return self._model

    def unload(self):
        if self._model is not None:
            print("Releasing model from VRAM...")
            del self._model
            self._model = None
            import torch
            torch.cuda.empty_cache()   # actively hand the freed memory back

    def __enter__(self):
        self.load()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.unload()
