Repository: Snapchat/GiGL
Pull request #45: Update config populator and validation for GLT pipelines
Author: login: mkolodner-sc
Created: 2025-05-13T20:10:39Z | Merged: 2025-05-19T18:29:08Z

**Scope of work done**
<!-- Description of PR goes here -->
Generally, this PR aims to clean up the unused fields in the template and populated frozen configs for GiGL V2 (GLT) pipelines. Specifically, we:

- Remove `flattened_graph_metadata` (v1 split_gen outputs) and `dataset_metadata` (v1 sgs outputs) fields created in `config_populator` for GLT Pipelines
- Remove validation check requirement that SGS fields be provided in template config
- Rename `gbml_config_pb_wrapper` field `should_run_experimental_glt_backend` -> `should_run_glt_backend`
- Allow `gbml_config_pb_wrapper.flattened_graph_metadata_pb_wrapper` and `gbml_config_pb_wrapper.dataset_metadata` to be `None` provided the above change for GLT pipelines, error if pb wrapper is accessed when this is not set
- Remove unused fields in example config for GLT 

**Did you add automated tests or write a test plan?**
Frozen config for CORA <> GLT E2E test after changes from [e2e job
](https://console.cloud.google.com/vertex-ai/pipelines/locations/us-central1/runs/cora-glt-udl-test-on--74047?project=external-snap-ci-github-gigl&inv=1&invt=AbxUYQ)
```
datasetConfig:
  dataPreprocessorConfig:
    dataPreprocessorArgs:
      mocked_dataset_name: cora_homogeneous_node_anchor_edge_features_user_defined_labels
    dataPreprocessorConfigClsPath: gigl.src.mocking.mocking_assets.passthrough_preprocessor_config_for_mocked_assets.PassthroughPreprocessorConfigForMockedAssets
featureFlags:
  data_preprocessor_num_shards: '2'
  should_run_glt_backend: 'True'
graphMetadata:
  condensedEdgeTypeMap:
    '0':
      dstNodeType: paper
      relation: cites
      srcNodeType: paper
  condensedNodeTypeMap:
    '0': paper
  edgeTypes:
  - dstNodeType: paper
    relation: cites
    srcNodeType: paper
  nodeTypes:
  - paper
inferencerConfig:
  command: python -m examples.distributed.homogeneous_inference
  inferenceBatchSize: 512
  inferencerArgs:
    log_every_n_batch: '50'
sharedConfig:
  inferenceMetadata:
    nodeTypeToInferencerOutputInfoMap:
      paper:
        embeddingsPath: external-snap-ci-github-gigl.gigl_embeddings.embeddings_paper_cora_glt_udl_test_on__74047
  postprocessedMetadata:
    postProcessorLogMetricsUri: gs://gigl-cicd-perm/cora_glt_udl_test_on__74047/post_processor/post_processor_metrics.json
  preprocessedMetadataUri: gs://gigl-cicd-perm/cora_glt_udl_test_on__74047/data_preprocess/preprocessed_metadata.yaml
  shouldSkipModelEvaluation: true
  shouldSkipTraining: true
  trainedModelMetadata:
    evalMetricsUri: gs://gigl-cicd-perm/cora_glt_udl_test_on__74047/trainer/models/trainer_eval_metrics.json
    scriptedModelUri: gs://gigl-cicd-perm/cora_glt_udl_test_on__74047/trainer/models/scripted_model.pt
    tensorboardLogsUri: gs://gigl-cicd-perm/cora_glt_udl_test_on__74047/trainer/tensorboard_logs/
    trainedModelUri: gs://public-gigl/mocked_assets/2024-07-15--21-30-07-UTC/cora_homogeneous_node_anchor_edge_features_user_defined_labels/trainer/models/model.pt
taskMetadata:
  nodeAnchorBasedLinkPredictionTaskMetadata:
    supervisionEdgeTypes:
    - dstNodeType: paper
      relation: cites
      srcNodeType: paper
```

***Updated Changelog.md?*** NO

***Ready for code review?:*** NO



Diff:
diff --git a/examples/distributed/configs/e2e_cora_udl_glt_task_config.yaml b/examples/distributed/configs/e2e_cora_udl_glt_task_config.yaml
index 3c2ec2295..c1bae367d 100644
--- a/examples/distributed/configs/e2e_cora_udl_glt_task_config.yaml
+++ b/examples/distributed/configs/e2e_cora_udl_glt_task_config.yaml
@@ -18,21 +18,6 @@ datasetConfig:
     dataPreprocessorArgs:
       # Supported keys in python/tests/test_assets/dataset_mocking/lib/mocked_dataset_artifact_metadata.json
       mocked_dataset_name: 'cora_homogeneous_node_anchor_edge_features_user_defined_labels'
-  # Below not used for GLT Inference
-  subgraphSamplerConfig:
-    numHops: 2
-    numNeighborsToSample: 10
-    numUserDefinedPositiveSamples: 1
-    numUserDefinedNegativeSamples: 1
-  splitGeneratorConfig:
-    assignerArgs:
-      seed: '42'
-      test_split: '0.2'
-      train_split: '0.7'
-      val_split: '0.1'
-    assignerClsPath: splitgenerator.lib.assigners.UserDefinedLabelsEdgeToLinkSplitHashingAssigner
-    splitStrategyClsPath: splitgenerator.lib.split_strategies.UserDefinedLabelsNodeAnchorBasedLinkPredictionSplitStrategy
-  # Above not used for GLT Inference
 inferencerConfig:
   inferencerArgs:
     # Example argument to inferencer
diff --git a/python/gigl/orchestration/kubeflow/utils/glt_backend.py b/python/gigl/orchestration/kubeflow/utils/glt_backend.py
index 889fb0d62..862021864 100644
--- a/python/gigl/orchestration/kubeflow/utils/glt_backend.py
+++ b/python/gigl/orchestration/kubeflow/utils/glt_backend.py
@@ -34,4 +34,4 @@ def _check_glt_backend_eligibility_component(
     config = GbmlConfigPbWrapper.get_gbml_config_pb_wrapper_from_uri(
         gbml_config_uri=UriFactory.create_uri(task_config_uri)
     )
-    return config.should_use_experimental_glt_backend
+    return config.should_use_glt_backend
diff --git a/python/gigl/src/common/types/pb_wrappers/gbml_config.py b/python/gigl/src/common/types/pb_wrappers/gbml_config.py
index 688510c33..c4763aa4d 100644
--- a/python/gigl/src/common/types/pb_wrappers/gbml_config.py
+++ b/python/gigl/src/common/types/pb_wrappers/gbml_config.py
@@ -46,19 +46,21 @@ class GbmlConfigPbWrapper:
 
     gbml_config_pb: gbml_config_pb2.GbmlConfig
 
-    _dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = field(init=False)
+    _dataset_metadata_pb_wrapper: Optional[DatasetMetadataPbWrapper] = field(
+        default=None, init=False
+    )
     _graph_metadata_pb_wrapper: GraphMetadataPbWrapper = field(init=False)
     _preprocessed_metadata_pb_wrapper: PreprocessedMetadataPbWrapper = field(init=False)
-    _flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = field(
-        init=False
-    )
+    _flattened_graph_metadata_pb_wrapper: Optional[
+        FlattenedGraphMetadataPbWrapper
+    ] = field(default=None, init=False)
     _task_metadata_pb_wrapper: TaskMetadataPbWrapper = field(init=False)
     _trained_model_metadata_pb_wrapper: TrainedModelMetadataPbWrapper = field(
         init=False
     )
-    _subgraph_sampling_strategy_pb_wrapper: SubgraphSamplingStrategyPbWrapper = field(
-        init=False
-    )
+    _subgraph_sampling_strategy_pb_wrapper: Optional[
+        SubgraphSamplingStrategyPbWrapper
+    ] = field(default=None, init=False)
 
     def __post_init__(self):
         # Populate the _preprocessed_metadata_pb_wrapper field
@@ -66,17 +68,29 @@ def __post_init__(self):
             uri=self.gbml_config_pb.shared_config.preprocessed_metadata_uri
         )
         # Populate the _dataset_metadata_pb_wrapper field
-        self.__load_dataset_metadata_pb_wrapper(
-            dataset_metadata_pb=self.gbml_config_pb.shared_config.dataset_metadata
-        )
+        if self.gbml_config_pb.shared_config.HasField("dataset_metadata"):
+            dataset_metadata_pb = self.gbml_config_pb.shared_config.dataset_metadata
+            self.__load_dataset_metadata_pb_wrapper(dataset_metadata_pb)
+        else:
+            logger.info(
+                "Skipping populating dataset_metadata_pb_wrapper as the message is missing from the input config"
+            )
+
         # Populate the _graph_metadata_pb_wrapper field
         self.__load_graph_metadata_pb_wrapper(
             graph_metadata_pb=self.gbml_config_pb.graph_metadata
         )
         # Populate the _flattened_graph_metadata_pb_wrapper field
-        self.__load_flattened_graph_metadata_pb_wrapper(
-            flattened_graph_metadata_pb=self.gbml_config_pb.shared_config.flattened_graph_metadata
-        )
+        if self.gbml_config_pb.shared_config.HasField("flattened_graph_metadata"):
+            flattened_graph_metadata_pb = (
+                self.gbml_config_pb.shared_config.flattened_graph_metadata
+            )
+            self.__load_flattened_graph_metadata_pb_wrapper(flattened_graph_metadata_pb)
+        else:
+            logger.info(
+                "Skipping populating flattened_graph_metadata_pb_wrapper as the message is missing from the input config"
+            )
+
         # Populate the _task_metadata_pb_wrapper field
         self.__load_task_metadata_pb_wrapper(
             task_metadata_pb=self.gbml_config_pb.task_metadata
@@ -85,11 +99,18 @@ def __post_init__(self):
         self.__load_trained_model_metadata_pb_wrapper(
             trained_model_metadata_pb=self.gbml_config_pb.shared_config.trained_model_metadata
         )
-
         # Populate the _subgraph_sampling_strategy_pb_wrapper field
-        self.__load_subgraph_sampling_strategy_pb_wrapper(
-            subgraph_sampling_strategy_pb=self.gbml_config_pb.dataset_config.subgraph_sampler_config.subgraph_sampling_strategy
+        subgraph_sampling_strategy_pb = (
+            self.gbml_config_pb.dataset_config.subgraph_sampler_config.subgraph_sampling_strategy
         )
+        if subgraph_sampling_strategy_pb.WhichOneof("strategy") is not None:
+            self.__load_subgraph_sampling_strategy_pb_wrapper(
+                subgraph_sampling_strategy_pb=self.gbml_config_pb.dataset_config.subgraph_sampler_config.subgraph_sampling_strategy
+            )
+        else:
+            logger.info(
+                "Skipping populating subgraph_sampling_strategy_pb_wrapper as the message is missing from the input config"
+            )
 
     def __load_preprocessed_metadata_pb_wrapper(self, uri: str) -> None:
         """
@@ -140,8 +161,8 @@ def __load_dataset_metadata_pb_wrapper(
         Args:
             dataset_metadata_pb (dataset_metadata_pb2.DatasetMetadata): The dataset metadata proto
         """
-        dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = (
-            DatasetMetadataPbWrapper(dataset_metadata_pb=dataset_metadata_pb)
+        dataset_metadata_pb_wrapper = DatasetMetadataPbWrapper(
+            dataset_metadata_pb=dataset_metadata_pb
         )
         object.__setattr__(
             self, "_dataset_metadata_pb_wrapper", dataset_metadata_pb_wrapper
@@ -178,10 +199,8 @@ def __load_flattened_graph_metadata_pb_wrapper(
         Args:
             flattened_graph_metadata_pb (flattened_graph_metadata_pb2.FlattenedGraphMetadata): The flattened graph metadata proto
         """
-        flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = (
-            FlattenedGraphMetadataPbWrapper(
-                flattened_graph_metadata_pb=flattened_graph_metadata_pb
-            )
+        flattened_graph_metadata_pb_wrapper = FlattenedGraphMetadataPbWrapper(
+            flattened_graph_metadata_pb=flattened_graph_metadata_pb
         )
         object.__setattr__(
             self,
@@ -233,23 +252,16 @@ def __load_subgraph_sampling_strategy_pb_wrapper(
         Args:
             subgraph_sampling_strategy_pb (subgraph_sampling_strategy_pb2.SubgraphSamplingStrategy): The subgraph sampling strategy proto
         """
-        if subgraph_sampling_strategy_pb.WhichOneof("strategy") is not None:
-            subgraph_sampling_strategy_pb_wrapper: SubgraphSamplingStrategyPbWrapper = (
-                SubgraphSamplingStrategyPbWrapper(
-                    subgraph_sampling_strategy_pb=subgraph_sampling_strategy_pb
-                )
-            )
-            object.__setattr__(
-                self,
-                "_subgraph_sampling_strategy_pb_wrapper",
-                subgraph_sampling_strategy_pb_wrapper,
-            )
-        else:
-            object.__setattr__(
-                self,
-                "_subgraph_sampling_strategy_pb_wrapper",
-                None,
+        subgraph_sampling_strategy_pb_wrapper: SubgraphSamplingStrategyPbWrapper = (
+            SubgraphSamplingStrategyPbWrapper(
+                subgraph_sampling_strategy_pb=subgraph_sampling_strategy_pb
             )
+        )
+        object.__setattr__(
+            self,
+            "_subgraph_sampling_strategy_pb_wrapper",
+            subgraph_sampling_strategy_pb_wrapper,
+        )
 
     @classmethod
     def get_gbml_config_pb_wrapper_from_uri(
@@ -273,11 +285,16 @@ def get_gbml_config_pb_wrapper_from_uri(
     @property
     def dataset_metadata_pb_wrapper(self) -> DatasetMetadataPbWrapper:
         """
-        Allows access to a dataset_metadata_pb_wrapper
+        Allows access to a dataset_metadata_pb_wrapper. If the underlying proto is empty, calling this property will instead throw an error.
 
         Returns:
-            DatasetMetadataPbWrapper: The dataset metadata pb wrapper
+            DatasetMetadataPbWrapper: The dataset metadata proto wrapper
         """
+        if self._dataset_metadata_pb_wrapper is None:
+            raise ValueError(
+                "Attempted to access the dataset metadata proto wrapper when it is not set. If using GLT backend, this field is expected to not be set, and "
+                "should not be referenced. If using SGS backend, this field is expected to be set. Please ensure this property is only accessed when the underlying proto is set."
+            )
         return self._dataset_metadata_pb_wrapper
 
     @property
@@ -286,20 +303,24 @@ def graph_metadata_pb_wrapper(self) -> GraphMetadataPbWrapper:
         Allows access to a graph_metadata_pb_wrapper
 
         Returns:
-            GraphMetadataPbWrapper: The graph metadata pb wrapper
+            GraphMetadataPbWrapper: The graph metadata proto wrapper
         """
         return self._graph_metadata_pb_wrapper
 
     @property
     def subgraph_sampling_strategy_pb_wrapper(
         self,
-    ) -> Optional[SubgraphSamplingStrategyPbWrapper]:
+    ) -> SubgraphSamplingStrategyPbWrapper:
         """
-        Allows access to a subgraph_sampling_strategy_pb_wrapper
+        Allows access to a subgraph_sampling_strategy_pb_wrapper. If the underlying proto is empty, calling this property will instead throw an error.
 
         Returns:
-            Optional[SubgraphSamplingStrategyPbWrapper]: The subgraph sampling strategy pb wrapper or none if it does not exist
+            SubgraphSamplingStrategyPbWrapper: The subgraph sampling strategy proto wrapper
         """
+        if self._subgraph_sampling_strategy_pb_wrapper is None:
+            raise ValueError(
+                "Attempted to access the subgraph sampling strategy proto wrapper when it is not set. Please ensure this property is only accessed when the underlying proto is set"
+            )
         return self._subgraph_sampling_strategy_pb_wrapper
 
     @property
@@ -313,13 +334,20 @@ def task_metadata_pb_wrapper(self) -> TaskMetadataPbWrapper:
         return self._task_metadata_pb_wrapper
 
     @property
-    def flattened_graph_metadata_pb_wrapper(self) -> FlattenedGraphMetadataPbWrapper:
+    def flattened_graph_metadata_pb_wrapper(
+        self,
+    ) -> FlattenedGraphMetadataPbWrapper:
         """
-        Allows access to a flattened_graph_metadata_pb_wrapper
+        Allows access to a flattened_graph_metadata_pb_wrapper.  If the underlying proto is empty, calling this property will instead throw an error.
 
         Returns:
-            FlattenedGraphMetadataPbWrapper: The flattened graph metadata pb wrapper
+            FlattenedGraphMetadataPbWrapper: The flattened graph metadata proto wrapper or None if it doesn't exist
         """
+        if self._flattened_graph_metadata_pb_wrapper is None:
+            raise ValueError(
+                "Attempted to access the flattened graph metadata proto wrapper when it is not set. If using GLT backend, this field is expected to not be set, and "
+                "should not be referenced. If using SGS backend, this field is expected to be set. Please ensure this property is only accessed when the underlying proto is set."
+            )
         return self._flattened_graph_metadata_pb_wrapper
 
     @property
@@ -328,7 +356,7 @@ def preprocessed_metadata_pb_wrapper(self) -> PreprocessedMetadataPbWrapper:
         Allows access to a preprocessed_metadata_pb_wrapper
 
         Returns:
-            PreprocessedMetadataPbWrapper: The preprocessed metadata pb wrapper
+            PreprocessedMetadataPbWrapper: The preprocessed metadata proto wrapper
         """
         return self._preprocessed_metadata_pb_wrapper
 
@@ -338,7 +366,7 @@ def trained_model_metadata_pb_wrapper(self) -> TrainedModelMetadataPbWrapper:
         Allows access to a trained_model_metadata_pb_wrapper
 
         Returns:
-            TrainedModelMetadataPbWrapper: The trained model metadata pb wrapper
+            TrainedModelMetadataPbWrapper: The trained model metadata proto wrapper
         """
         return self._trained_model_metadata_pb_wrapper
 
@@ -413,7 +441,7 @@ def trainer_config(self) -> gbml_config_pb2.GbmlConfig.TrainerConfig:
         return self.gbml_config_pb.trainer_config
 
     @property
-    def should_use_experimental_glt_backend(self) -> bool:
+    def should_use_glt_backend(self) -> bool:
         """
         Allows access to should_use_glt_backend under GbmlConfig
 
diff --git a/python/gigl/src/config_populator/config_populator.py b/python/gigl/src/config_populator/config_populator.py
index 962b76821..8e784cb18 100644
--- a/python/gigl/src/config_populator/config_populator.py
+++ b/python/gigl/src/config_populator/config_populator.py
@@ -145,7 +145,7 @@ def __populate_flattened_graph_metadata_pb(
             )
         else:
             raise TypeError(
-                f"Found un-supported training task type: {self.task_metadata_type}; it has to be one of {[option.value for option in TaskMetadataType]}"
+                f"Found un-supported training task type: {self.task_metadata_pb_wrapper.task_metadata_type}; it has to be one of {[option.value for option in TaskMetadataType]}"
             )
 
         flattened_graph_metadata_pb = (
@@ -518,6 +518,12 @@ def _populate_frozen_gbml_config_pb(
         self.__applied_task_identifier = applied_task_identifier
         self.__template_gbml_config = template_gbml_config_pb
 
+        template_gbml_config_pb_wrapper = GbmlConfigPbWrapper(
+            gbml_config_pb=template_gbml_config_pb
+        )
+
+        should_use_glt_backend = template_gbml_config_pb_wrapper.should_use_glt_backend
+
         output_gbml_config_pb = gbml_config_pb2.GbmlConfig()
         output_gbml_config_pb.CopyFrom(self.template_gbml_config)
 
@@ -535,9 +541,13 @@ def _populate_frozen_gbml_config_pb(
                 applied_task_identifier=self.applied_task_identifier
             )
         )
+        if not should_use_glt_backend:
+            flattened_graph_metadata_pb = self.__populate_flattened_graph_metadata_pb()
+            dataset_metadata_pb = self.__populate_dataset_metadata_pb()
+        else:
+            flattened_graph_metadata_pb = None
+            dataset_metadata_pb = None
 
-        flattened_graph_metadata_pb = self.__populate_flattened_graph_metadata_pb()
-        dataset_metadata_pb = self.__populate_dataset_metadata_pb()
         trained_model_metadata_pb = self.__populate_trained_model_metadata_pb(
             template_trained_model_metadata_pb=template_gbml_config_pb.shared_config.trained_model_metadata
         )
diff --git a/python/gigl/src/inference/inferencer.py b/python/gigl/src/inference/inferencer.py
index 0590e1f4b..732d4405d 100644
--- a/python/gigl/src/inference/inferencer.py
+++ b/python/gigl/src/inference/inferencer.py
@@ -46,7 +46,7 @@ def run(
             project=resource_config_wrapper.project,
         )
 
-        if gbml_config_wrapper.should_use_experimental_glt_backend:
+        if gbml_config_wrapper.should_use_glt_backend:
             inferencer_glt = GLTInferencer()
             inferencer_glt.run(
                 applied_task_identifier=applied_task_identifier,
diff --git a/python/gigl/src/post_process/post_processor.py b/python/gigl/src/post_process/post_processor.py
index 3b60ffc44..e9f9f8828 100644
--- a/python/gigl/src/post_process/post_processor.py
+++ b/python/gigl/src/post_process/post_processor.py
@@ -112,7 +112,7 @@ def __should_run_unenumeration(
         """
         When using the experimental GLT backend, we should run unenumeration in the post processor.
         """
-        return gbml_config_wrapper.should_use_experimental_glt_backend
+        return gbml_config_wrapper.should_use_glt_backend
 
     def __run(
         self,
diff --git a/python/gigl/src/training/trainer.py b/python/gigl/src/training/trainer.py
index 835fc0485..d27ec3b62 100644
--- a/python/gigl/src/training/trainer.py
+++ b/python/gigl/src/training/trainer.py
@@ -65,7 +65,7 @@ def run(
                 applied_task_identifier=applied_task_identifier,
             )
 
-        if gbml_config_wrapper.should_use_experimental_glt_backend:
+        if gbml_config_wrapper.should_use_glt_backend:
             trainer_v2 = GLTTrainer()
             trainer_v2.run(
                 applied_task_identifier=applied_task_identifier,
diff --git a/python/gigl/src/validation_check/libs/frozen_config_path_checks.py b/python/gigl/src/validation_check/libs/frozen_config_path_checks.py
index 0a4674276..8769dc4c9 100644
--- a/python/gigl/src/validation_check/libs/frozen_config_path_checks.py
+++ b/python/gigl/src/validation_check/libs/frozen_config_path_checks.py
@@ -49,7 +49,7 @@ def assert_trained_model_exists(
     Check if trained model file exists.
     """
     gbml_config_pb_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
-    if gbml_config_pb_wrapper.should_use_experimental_glt_backend:
+    if gbml_config_pb_wrapper.should_use_glt_backend:
         logger.warning(
             "Skipping trained model check since GLT Backend is being used."
             + "Currently it is not expected that model be piped in through gigl specific configs. "
@@ -72,7 +72,7 @@ def assert_split_generator_output_exists(
     Check if split generator output files exist.
     """
     gbml_config_pb_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
-    if gbml_config_pb_wrapper.should_use_experimental_glt_backend:
+    if gbml_config_pb_wrapper.should_use_glt_backend:
         logger.warning(
             "Skipping split generator output check since GLT Backend is being used."
         )
@@ -195,7 +195,7 @@ def assert_subgraph_sampler_output_exists(
     Check if subgraph sampler output files exist.
     """
     gbml_config_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
-    if gbml_config_wrapper.should_use_experimental_glt_backend:
+    if gbml_config_wrapper.should_use_glt_backend:
         logger.warning(
             "Skipping subgraph sampler output check since GLT Backend is being used."
         )
diff --git a/python/gigl/src/validation_check/libs/template_config_checks.py b/python/gigl/src/validation_check/libs/template_config_checks.py
index 9d186ae68..a77e2ad4c 100644
--- a/python/gigl/src/validation_check/libs/template_config_checks.py
+++ b/python/gigl/src/validation_check/libs/template_config_checks.py
@@ -52,7 +52,7 @@ def check_pipeline_has_valid_start_and_stop_flags(
     )
     components = [start_at] if stop_after is None else [start_at, stop_after]
     for component in components:
-        if gbml_config_wrapper.should_use_experimental_glt_backend:
+        if gbml_config_wrapper.should_use_glt_backend:
             if component in GLT_BACKEND_UNSUPPORTED_COMPONENTS:
                 raise ValueError(
                     f"Invalid component {component} for GLT Backend"
@@ -270,7 +270,7 @@ def check_if_trainer_cls_valid(
     """
     logger.info("Config validation check: if trainerClsPath and its args are valid.")
     gbml_config_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
-    if gbml_config_wrapper.should_use_experimental_glt_backend:
+    if gbml_config_wrapper.should_use_glt_backend:
         logger.warning(
             "Skipping trainer class validation as GLT Backend is not implemented yet. "
             + "Trainer class may actually be a path to a script so, the paradigm is different."
@@ -307,7 +307,7 @@ def check_if_inferencer_cls_valid(
     check_if_runtime_args_all_str(args_name="inferencerArgs", runtime_args=runtime_args)
 
     gbml_config_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
-    if gbml_config_wrapper.should_use_experimental_glt_backend:
+    if gbml_config_wrapper.should_use_glt_backend:
         logger.warning(
             "Skipping inferencer class validation as GLT Backend is enabled. "
             + "Inferencer class may actually be a path to a script so, the paradigm is different."
@@ -335,7 +335,7 @@ def check_if_split_generator_config_valid(
     """
     logger.info("Config validation check: if splitGeneratorConfig is valid.")
     gbml_config_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
-    if gbml_config_wrapper.should_use_experimental_glt_backend:
+    if gbml_config_wrapper.should_use_glt_backend:
         logger.warning(
             "Skipping splitGeneratorConfig validation as GLT Backend is enabled."
         )
@@ -366,6 +366,12 @@ def check_if_subgraph_sampler_config_valid(
     Check if subgraphSamplerConfig is valid.
     """
     logger.info("Config validation check: if subgraphSamplerConfig is valid.")
+    gbml_config_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
+    if gbml_config_wrapper.should_use_glt_backend:
+        logger.warning(
+            "Skipping subgraph sampler (SGS) validation check since GLT Backend is being used."
+        )
+        return
     subgraph_sampler_config = gbml_config_pb.dataset_config.subgraph_sampler_config
 
     if subgraph_sampler_config.HasField("subgraph_sampling_strategy"):
diff --git a/python/tests/unit/src/config_populator/config_populator_functionality_test.py b/python/tests/unit/src/config_populator/config_populator_functionality_test.py
index 98b36b030..34c5f4217 100644
--- a/python/tests/unit/src/config_populator/config_populator_functionality_test.py
+++ b/python/tests/unit/src/config_populator/config_populator_functionality_test.py
@@ -17,18 +17,33 @@
 
 logger = Logger()
 
+_TEMPLATE_CONFIG_FOR_SGS = gbml_config_pb2.GbmlConfig(
+    task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata(
+        node_based_task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata.NodeBasedTaskMetadata()
+    ),
+)
+
+_TEMPLATE_CONFIG_FOR_GLT = gbml_config_pb2.GbmlConfig(
+    task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata(
+        node_based_task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata.NodeBasedTaskMetadata()
+    ),
+    feature_flags={"should_run_glt_backend": "True"},
+)
+
 
 class ConfigPopulatorUnitTest(unittest.TestCase):
     """
     This test checks the completion of the ConfigPopulator step.
     """
 
-    def test_config_population_is_accurate(self):
+    def test_sgs_config_population_is_accurate(
+        self,
+    ):
         config_populator = ConfigPopulator()
 
         frozen_gbml_config_pb = config_populator._populate_frozen_gbml_config_pb(
             applied_task_identifier=self.applied_task_identifier,
-            template_gbml_config_pb=self.template_gbml_config_pb,
+            template_gbml_config_pb=_TEMPLATE_CONFIG_FOR_SGS,
         )
         gbml_config_pb_wrapper = GbmlConfigPbWrapper(
             gbml_config_pb=frozen_gbml_config_pb
@@ -40,8 +55,8 @@ def test_config_population_is_accurate(self):
         )
 
         # Assert the right flattened graph metadata was set.
-        self.assertIsNotNone(
-            gbml_config_pb_wrapper.shared_config.flattened_graph_metadata
+        self.assertTrue(
+            gbml_config_pb_wrapper.shared_config.HasField("flattened_graph_metadata")
         )
         flattened_output_pb = (
             gbml_config_pb_wrapper.flattened_graph_metadata_pb_wrapper.output_metadata
@@ -52,9 +67,10 @@ def test_config_population_is_accurate(self):
         ):
             self.assertNotEqual(flattened_output_pb.labeled_tfrecord_uri_prefix, "")
             self.assertNotEqual(flattened_output_pb.unlabeled_tfrecord_uri_prefix, "")
-
         # Assert the right dataset metadata was set
-        self.assertIsNotNone(gbml_config_pb_wrapper.shared_config.dataset_metadata)
+        self.assertTrue(
+            gbml_config_pb_wrapper.shared_config.HasField("dataset_metadata")
+        )
         dataset_metadata_pb = DatasetMetadataPbWrapper(
             dataset_metadata_pb=gbml_config_pb_wrapper.shared_config.dataset_metadata
         ).output_metadata
@@ -66,6 +82,81 @@ def test_config_population_is_accurate(self):
             self.assertNotEqual(dataset_metadata_pb.val_data_uri, "")
             self.assertNotEqual(dataset_metadata_pb.test_data_uri, "")
 
+        # GBML Config Pb Wrapper Checks
+        self.assertFalse(gbml_config_pb_wrapper.should_use_glt_backend)
+        # We should expect both of the below calls to happen successfully without error when they are set
+        gbml_config_pb_wrapper.flattened_graph_metadata_pb_wrapper
+        gbml_config_pb_wrapper.dataset_metadata_pb_wrapper
+
+        # Assert trainer metadata assets were set
+        trained_model_metadata_pb: trained_model_metadata_pb2.TrainedModelMetadata = (
+            gbml_config_pb_wrapper.shared_config.trained_model_metadata
+        )
+        self.assertTrue(
+            isinstance(
+                trained_model_metadata_pb,
+                trained_model_metadata_pb2.TrainedModelMetadata,
+            )
+        )
+        self.assertNotEqual(trained_model_metadata_pb.trained_model_uri, "")
+        self.assertNotEqual(trained_model_metadata_pb.scripted_model_uri, "")
+
+        # Assert inference metadata assets were set
+        inference_metadata_pb: inference_metadata_pb2.InferenceMetadata = (
+            gbml_config_pb_wrapper.shared_config.inference_metadata
+        )
+        self.assertTrue(
+            isinstance(inference_metadata_pb, inference_metadata_pb2.InferenceMetadata)
+        )
+        for node_type in inference_metadata_pb.node_type_to_inferencer_output_info_map:
+            self.assertNotEqual(
+                inference_metadata_pb.node_type_to_inferencer_output_info_map[
+                    node_type
+                ].embeddings_path,
+                "",
+            )
+            if (
+                gbml_config_pb_wrapper.task_metadata_pb_wrapper.task_metadata_type
+                == TaskMetadataType.NODE_BASED_TASK
+            ):
+                self.assertNotEqual(
+                    inference_metadata_pb.node_type_to_inferencer_output_info_map[
+                        node_type
+                    ].predictions_path,
+                    "",
+                )
+
+    def test_glt_config_population_is_accurate(self):
+        config_populator = ConfigPopulator()
+        frozen_gbml_config_pb = config_populator._populate_frozen_gbml_config_pb(
+            applied_task_identifier=self.applied_task_identifier,
+            template_gbml_config_pb=_TEMPLATE_CONFIG_FOR_GLT,
+        )
+        gbml_config_pb_wrapper = GbmlConfigPbWrapper(
+            gbml_config_pb=frozen_gbml_config_pb
+        )
+
+        # Check that preprocessed metadata uri is set.
+        self.assertNotEqual(
+            gbml_config_pb_wrapper.shared_config.preprocessed_metadata_uri, ""
+        )
+
+        self.assertFalse(
+            gbml_config_pb_wrapper.shared_config.HasField("flattened_graph_metadata")
+        )
+        self.assertFalse(
+            gbml_config_pb_wrapper.shared_config.HasField("dataset_metadata")
+        )
+
+        # GBML Config Pb Wrapper Checks
+        self.assertTrue(gbml_config_pb_wrapper.should_use_glt_backend)
+        with self.assertRaises(ValueError):
+            # We should expect to throw an error when accessing the flattened graph metadata pb wrapper when it is not set
+            gbml_config_pb_wrapper.flattened_graph_metadata_pb_wrapper
+        with self.assertRaises(ValueError):
+            # We should expect to throw an error when accessing the dataset metadata pb wrapper when it is not set
+            gbml_config_pb_wrapper.dataset_metadata_pb_wrapper
+
         # Assert trainer metadata assets were set
         trained_model_metadata_pb: trained_model_metadata_pb2.TrainedModelMetadata = (
             gbml_config_pb_wrapper.shared_config.trained_model_metadata
@@ -109,12 +200,6 @@ def setUp(self) -> None:
             f"test_config_populator_functionality_{current_formatted_datetime()}"
         )
 
-        self.template_gbml_config_pb = gbml_config_pb2.GbmlConfig(
-            task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata(
-                node_based_task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata.NodeBasedTaskMetadata()
-            ),
-        )
-
     def tearDown(self) -> None:
         logger.info("Config Populator pipeline completion test tear down.")
         return super().tearDown()


Reviews:
user: login: mkolodner-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T23:15:38Z
user: login: kmontemayor2-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T23:56:38Z
user: login: mkolodner-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T00:10:34Z
user: login: kmontemayor2-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T15:06:09Z
user: login: mkolodner-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T19:55:39Z
user: login: kmontemayor2-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T20:23:37Z
user: login: mkolodner-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T20:36:27Z
user: login: kmontemayor2-sc
body: Thanks Matt!
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T21:03:16Z
user: login: mkolodner-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T21:13:45Z
user: login: kmontemayor2-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T21:35:14Z
user: login: kmontemayor2-sc
body: I'd prefer if we can clean up the `init=False`/etc bits here, please try them out but if they don't work this LGTM
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-14T21:38:04Z
user: login: mkolodner-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T21:43:11Z
user: login: mkolodner-sc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T21:44:15Z

Review comments:
diff_hunk: @@ -46,37 +46,43 @@ class GbmlConfigPbWrapper:
 
     gbml_config_pb: gbml_config_pb2.GbmlConfig
 
-    _dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = field(init=False)
+    _dataset_metadata_pb_wrapper: Optional[DatasetMetadataPbWrapper] = field(init=False)
     _graph_metadata_pb_wrapper: GraphMetadataPbWrapper = field(init=False)
     _preprocessed_metadata_pb_wrapper: PreprocessedMetadataPbWrapper = field(init=False)
-    _flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = field(
-        init=False
-    )
+    _flattened_graph_metadata_pb_wrapper: Optional[
+        FlattenedGraphMetadataPbWrapper
+    ] = field(init=False)
     _task_metadata_pb_wrapper: TaskMetadataPbWrapper = field(init=False)
     _trained_model_metadata_pb_wrapper: TrainedModelMetadataPbWrapper = field(
         init=False
     )
-    _subgraph_sampling_strategy_pb_wrapper: SubgraphSamplingStrategyPbWrapper = field(
-        init=False
-    )
+    _subgraph_sampling_strategy_pb_wrapper: Optional[
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: mkolodner-sc
body: This should have always been `Optional`, seems like `mypy` doesn't catch type errors when we use `object.__setattr__`, but this has been adjusted now
created_at: 2025-05-13T23:15:38Z
author_association: COLLABORATOR
diff_hunk: @@ -148,6 +148,7 @@ def _get_uri_prefix_map(
         gbml_config_pb_wrapper: GbmlConfigPbWrapper,
         data_loader_types: List[DataloaderTypes],
     ) -> Dict[DataloaderTypes, str]:
+        assert gbml_config_pb_wrapper.dataset_metadata_pb_wrapper is not None
path: python/gigl/src/common/types/pb_wrappers/dataset_metadata_utils.py
user: login: kmontemayor2-sc
body: These asserts are a bit messy, can we have `the dataset_metadata_pb_wrapper` be some default wrapper (e.g. for an empty proto) if not provided? We can add an addition `has_dataset_metadata_pb_wrapper` to represent if it is intentionally empty or was just unset.

This goes along with how py protos handle nested messages https://protobuf.dev/reference/python/python-generated/#embedded_message
created_at: 2025-05-13T23:48:54Z
author_association: COLLABORATOR
diff_hunk: @@ -17,18 +19,50 @@
 
 logger = Logger()
 
+_TEMPLATE_V1_GBML_CONFIG_PB = gbml_config_pb2.GbmlConfig(
+    task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata(
+        node_based_task_metadata=gbml_config_pb2.GbmlConfig.TaskMetadata.NodeBasedTaskMetadata()
+    ),
+)
+
+_TEMPLATE_V2_GBML_CONFIG_PB = gbml_config_pb2.GbmlConfig(
path: python/tests/unit/src/config_populator/config_populator_functionality_test.py
user: login: kmontemayor2-sc
body: nit. Let's name this `"TEMPLATE_CONFIG_FOR_SGS` and `TEMPLATE_CONFIG_FOR_GLT` or equivalent? "V1" and "V2" aren't too descript imo.
created_at: 2025-05-13T23:52:19Z
author_association: COLLABORATOR
diff_hunk: @@ -39,32 +73,64 @@ def test_config_population_is_accurate(self):
             gbml_config_pb_wrapper.shared_config.preprocessed_metadata_uri, ""
         )
 
-        # Assert the right flattened graph metadata was set.
-        self.assertIsNotNone(
-            gbml_config_pb_wrapper.shared_config.flattened_graph_metadata
-        )
-        flattened_output_pb = (
-            gbml_config_pb_wrapper.flattened_graph_metadata_pb_wrapper.output_metadata
-        )
-        if isinstance(
-            flattened_output_pb,
-            flattened_graph_metadata_pb2.SupervisedNodeClassificationOutput,
-        ):
-            self.assertNotEqual(flattened_output_pb.labeled_tfrecord_uri_prefix, "")
-            self.assertNotEqual(flattened_output_pb.unlabeled_tfrecord_uri_prefix, "")
-
-        # Assert the right dataset metadata was set
-        self.assertIsNotNone(gbml_config_pb_wrapper.shared_config.dataset_metadata)
-        dataset_metadata_pb = DatasetMetadataPbWrapper(
-            dataset_metadata_pb=gbml_config_pb_wrapper.shared_config.dataset_metadata
-        ).output_metadata
-        if isinstance(
-            dataset_metadata_pb,
-            dataset_metadata_pb2.SupervisedNodeClassificationDataset,
-        ):
-            self.assertNotEqual(dataset_metadata_pb.train_data_uri, "")
-            self.assertNotEqual(dataset_metadata_pb.val_data_uri, "")
-            self.assertNotEqual(dataset_metadata_pb.test_data_uri, "")
+        if should_use_glt_backend:
+            self.assertFalse(
+                gbml_config_pb_wrapper.shared_config.HasField(
+                    "flattened_graph_metadata"
+                )
+            )
+            self.assertFalse(
+                gbml_config_pb_wrapper.shared_config.HasField("dataset_metadata")
+            )
+
+            # GBML Config Pb Wrapper Checks
+            self.assertTrue(gbml_config_pb_wrapper.should_use_glt_backend)
+            self.assertIsNone(
+                gbml_config_pb_wrapper.flattened_graph_metadata_pb_wrapper
+            )
+            self.assertIsNone(gbml_config_pb_wrapper.dataset_metadata_pb_wrapper)
+        else:
path: python/tests/unit/src/config_populator/config_populator_functionality_test.py
user: login: kmontemayor2-sc
body: I think we should avoid branches in tests as much as possible, in this case I'd have a (minor preference) for having two test cases with some duplicate logic. But this is a very small point, I just wanted to bring up the branching thing generally.
created_at: 2025-05-13T23:54:25Z
author_association: COLLABORATOR
diff_hunk: @@ -148,6 +148,7 @@ def _get_uri_prefix_map(
         gbml_config_pb_wrapper: GbmlConfigPbWrapper,
         data_loader_types: List[DataloaderTypes],
     ) -> Dict[DataloaderTypes, str]:
+        assert gbml_config_pb_wrapper.dataset_metadata_pb_wrapper is not None
path: python/gigl/src/common/types/pb_wrappers/dataset_metadata_utils.py
user: login: mkolodner-sc
body: Hmm, I had initially thought about approaching it this way, but my concern here was that a `pb_wrapper` which is provided an empty pb, and thus becomes an empty pb wrapper, feels a bit weird to me, in the sense that the pb wrapper itself becomes only partially functional, where some of its properties/functions may either be empty or just error out [[1](https://github.com/Snapchat/GiGL/blob/main/python/gigl/src/common/types/pb_wrappers/flattened_graph_metadata.py#L72-L80)]. This leads me to believe that generally, we shouldn't be populating a `pb_wrapper` if its internal `pb` is empty, and even if we do, we should probably still have some sort of `assert` in place in these locations whenever its used to prevent unforeseen behavior with using an empty pb.

Provided we'd need these `asserts` anyway, my reasoning was that it would be easier just to set the `pb_wrapper` to None and remove this state of limbo altogether. WDYT?
created_at: 2025-05-14T00:10:34Z
author_association: COLLABORATOR
diff_hunk: @@ -148,6 +148,7 @@ def _get_uri_prefix_map(
         gbml_config_pb_wrapper: GbmlConfigPbWrapper,
         data_loader_types: List[DataloaderTypes],
     ) -> Dict[DataloaderTypes, str]:
+        assert gbml_config_pb_wrapper.dataset_metadata_pb_wrapper is not None
path: python/gigl/src/common/types/pb_wrappers/dataset_metadata_utils.py
user: login: kmontemayor2-sc
body: > Hmm, I had initially thought about approaching it this way, but my concern here was that a pb_wrapper which is provided an empty pb, and thus becomes an empty pb wrapper, feels a bit weird to me, in the sense that the pb wrapper itself becomes only partially functional, where some of its properties/functions may either be empty

What's wrong with this? We don't require all fields to be set anyways so they be empty anyways - it should be the consuming code's responsibility to check, right?  I suppose the asserts are checking but they don't really provide any actionable advice to the user when we hit them, right?

I'd prefer if we could adopt more of things like:

```
if blah:
   raise ValueError("SGS expect TaskMetadata to be set, we did not receive that field. Was this config inteded to be run for the GLT backend?")
```

in our codebase which I feel like is much more "use friendly"

> or just error out [[1](https://github.com/Snapchat/GiGL/blob/main/python/gigl/src/common/types/pb_wrappers/flattened_graph_metadata.py#L72-L80)]

That's fine? I mean if the code breaks it breaks?

> Provided we'd need these asserts anyway, my reasoning was that it would be easier just to set the pb_wrapper to None and remove this state of limbo altogether. WDYT?

What state of limbo are we worried about here?

FWIW having empty messages as default values is nice as you can do stuff like `top_level.nested_message.some_field` without needing to do:

```
maybe_nested = top_level.nested_message
if nested is not None:
   foo = maybe_nested.some_field
```

Again I think that if we're trying to "conform to known" then we should align our pb wrapper with the pb behavior.

created_at: 2025-05-14T15:06:09Z
author_association: COLLABORATOR
diff_hunk: @@ -148,6 +148,7 @@ def _get_uri_prefix_map(
         gbml_config_pb_wrapper: GbmlConfigPbWrapper,
         data_loader_types: List[DataloaderTypes],
     ) -> Dict[DataloaderTypes, str]:
+        assert gbml_config_pb_wrapper.dataset_metadata_pb_wrapper is not None
path: python/gigl/src/common/types/pb_wrappers/dataset_metadata_utils.py
user: login: mkolodner-sc
body: Discussed offline, similar to above, we'll opt to throw an error when trying to access a `pb_wrapper` with an empty underlying proto so that can reduce the amount of asserts here while ensuring that an incomplete pb wrapper isn't used mistakenly. 
created_at: 2025-05-14T19:55:38Z
author_association: COLLABORATOR
diff_hunk: @@ -46,37 +46,43 @@ class GbmlConfigPbWrapper:
 
     gbml_config_pb: gbml_config_pb2.GbmlConfig
 
-    _dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = field(init=False)
+    _dataset_metadata_pb_wrapper: Optional[DatasetMetadataPbWrapper] = field(init=False)
     _graph_metadata_pb_wrapper: GraphMetadataPbWrapper = field(init=False)
     _preprocessed_metadata_pb_wrapper: PreprocessedMetadataPbWrapper = field(init=False)
-    _flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = field(
-        init=False
-    )
+    _flattened_graph_metadata_pb_wrapper: Optional[
+        FlattenedGraphMetadataPbWrapper
+    ] = field(init=False)
     _task_metadata_pb_wrapper: TaskMetadataPbWrapper = field(init=False)
     _trained_model_metadata_pb_wrapper: TrainedModelMetadataPbWrapper = field(
         init=False
     )
-    _subgraph_sampling_strategy_pb_wrapper: SubgraphSamplingStrategyPbWrapper = field(
-        init=False
-    )
+    _subgraph_sampling_strategy_pb_wrapper: Optional[
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: should we have this throw when not set too, instead of being optional?
created_at: 2025-05-14T20:12:54Z
author_association: COLLABORATOR
diff_hunk: @@ -273,11 +287,15 @@ def get_gbml_config_pb_wrapper_from_uri(
     @property
     def dataset_metadata_pb_wrapper(self) -> DatasetMetadataPbWrapper:
         """
-        Allows access to a dataset_metadata_pb_wrapper
+        Allows access to a dataset_metadata_pb_wrapper. If the underlying pb is empty, calling this property will instead throw an error.
 
         Returns:
             DatasetMetadataPbWrapper: The dataset metadata pb wrapper
         """
+        if self._dataset_metadata_pb_wrapper is None:
+            raise ValueError(
+                "Attempted to access the dataset metadata pb wrapper when it is not set. Please ensure this property is only accessed when the underlying proto is set"
+            )
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: Let's add a note that this may not be set if using the GLT backend, and is probably being read if using the SGS backend?
created_at: 2025-05-14T20:14:08Z
author_association: COLLABORATOR
diff_hunk: @@ -313,13 +331,19 @@ def task_metadata_pb_wrapper(self) -> TaskMetadataPbWrapper:
         return self._task_metadata_pb_wrapper
 
     @property
-    def flattened_graph_metadata_pb_wrapper(self) -> FlattenedGraphMetadataPbWrapper:
+    def flattened_graph_metadata_pb_wrapper(
+        self,
+    ) -> FlattenedGraphMetadataPbWrapper:
         """
-        Allows access to a flattened_graph_metadata_pb_wrapper
+        Allows access to a flattened_graph_metadata_pb_wrapper.  If the underlying pb is empty, calling this property will instead throw an error.
 
         Returns:
-            FlattenedGraphMetadataPbWrapper: The flattened graph metadata pb wrapper
+            FlattenedGraphMetadataPbWrapper: The flattened graph metadata pb wrapper or None if it doesn't exist
         """
+        if self._flattened_graph_metadata_pb_wrapper is None:
+            raise ValueError(
+                "Attempted to access the flattened graph metadata pb wrapper when it is not set. Please ensure this property is only accessed when the underlying proto is set"
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: ditto on why this may happen
created_at: 2025-05-14T20:14:27Z
author_association: COLLABORATOR
diff_hunk: @@ -366,6 +366,12 @@ def check_if_subgraph_sampler_config_valid(
     Check if subgraphSamplerConfig is valid.
     """
     logger.info("Config validation check: if subgraphSamplerConfig is valid.")
+    gbml_config_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
+    if gbml_config_wrapper.should_use_glt_backend:
+        logger.warning(
+            "Skipping subgraph sampler validation check since GLT Backend is being used."
path: python/gigl/src/validation_check/libs/template_config_checks.py
user: login: kmontemayor2-sc
body: skipping SGS and split gen validation checks?
created_at: 2025-05-14T20:17:35Z
author_association: COLLABORATOR
diff_hunk: @@ -46,37 +46,43 @@ class GbmlConfigPbWrapper:
 
     gbml_config_pb: gbml_config_pb2.GbmlConfig
 
-    _dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = field(init=False)
+    _dataset_metadata_pb_wrapper: Optional[DatasetMetadataPbWrapper] = field(init=False)
     _graph_metadata_pb_wrapper: GraphMetadataPbWrapper = field(init=False)
     _preprocessed_metadata_pb_wrapper: PreprocessedMetadataPbWrapper = field(init=False)
-    _flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = field(
-        init=False
-    )
+    _flattened_graph_metadata_pb_wrapper: Optional[
+        FlattenedGraphMetadataPbWrapper
+    ] = field(init=False)
     _task_metadata_pb_wrapper: TaskMetadataPbWrapper = field(init=False)
     _trained_model_metadata_pb_wrapper: TrainedModelMetadataPbWrapper = field(
         init=False
     )
-    _subgraph_sampling_strategy_pb_wrapper: SubgraphSamplingStrategyPbWrapper = field(
-        init=False
-    )
+    _subgraph_sampling_strategy_pb_wrapper: Optional[
+        SubgraphSamplingStrategyPbWrapper
+    ] = field(init=False)
 
     def __post_init__(self):
         # Populate the _preprocessed_metadata_pb_wrapper field
         self.__load_preprocessed_metadata_pb_wrapper(
             uri=self.gbml_config_pb.shared_config.preprocessed_metadata_uri
         )
         # Populate the _dataset_metadata_pb_wrapper field
-        self.__load_dataset_metadata_pb_wrapper(
-            dataset_metadata_pb=self.gbml_config_pb.shared_config.dataset_metadata
-        )
+        if self.gbml_config_pb.shared_config.HasField("dataset_metadata"):
+            dataset_metadata_pb = self.gbml_config_pb.shared_config.dataset_metadata
+        else:
+            dataset_metadata_pb = None
+        self.__load_dataset_metadata_pb_wrapper(dataset_metadata_pb)
         # Populate the _graph_metadata_pb_wrapper field
         self.__load_graph_metadata_pb_wrapper(
             graph_metadata_pb=self.gbml_config_pb.graph_metadata
         )
         # Populate the _flattened_graph_metadata_pb_wrapper field
-        self.__load_flattened_graph_metadata_pb_wrapper(
-            flattened_graph_metadata_pb=self.gbml_config_pb.shared_config.flattened_graph_metadata
-        )
+        if self.gbml_config_pb.shared_config.HasField("flattened_graph_metadata"):
+            flattened_graph_metadata_pb = (
+                self.gbml_config_pb.shared_config.flattened_graph_metadata
+            )
+        else:
+            flattened_graph_metadata_pb = None
+        self.__load_flattened_graph_metadata_pb_wrapper(flattened_graph_metadata_pb)
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: ```suggestion
        if self.gbml_config_pb.shared_config.HasField("flattened_graph_metadata"):             self.__load_flattened_graph_metadata_pb_wrapper(self.gbml_config_pb.shared_config.flattened_graph_metadata)

        else:
            logger.info("Skipping populating flattened_graph_metadata_pb_wrapper as the message is missing from the input config")
```

WDYT about?
created_at: 2025-05-14T20:20:52Z
author_association: COLLABORATOR
diff_hunk: @@ -46,37 +46,43 @@ class GbmlConfigPbWrapper:
 
     gbml_config_pb: gbml_config_pb2.GbmlConfig
 
-    _dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = field(init=False)
+    _dataset_metadata_pb_wrapper: Optional[DatasetMetadataPbWrapper] = field(init=False)
     _graph_metadata_pb_wrapper: GraphMetadataPbWrapper = field(init=False)
     _preprocessed_metadata_pb_wrapper: PreprocessedMetadataPbWrapper = field(init=False)
-    _flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = field(
-        init=False
-    )
+    _flattened_graph_metadata_pb_wrapper: Optional[
+        FlattenedGraphMetadataPbWrapper
+    ] = field(init=False)
     _task_metadata_pb_wrapper: TaskMetadataPbWrapper = field(init=False)
     _trained_model_metadata_pb_wrapper: TrainedModelMetadataPbWrapper = field(
         init=False
     )
-    _subgraph_sampling_strategy_pb_wrapper: SubgraphSamplingStrategyPbWrapper = field(
-        init=False
-    )
+    _subgraph_sampling_strategy_pb_wrapper: Optional[
+        SubgraphSamplingStrategyPbWrapper
+    ] = field(init=False)
 
     def __post_init__(self):
         # Populate the _preprocessed_metadata_pb_wrapper field
         self.__load_preprocessed_metadata_pb_wrapper(
             uri=self.gbml_config_pb.shared_config.preprocessed_metadata_uri
         )
         # Populate the _dataset_metadata_pb_wrapper field
-        self.__load_dataset_metadata_pb_wrapper(
-            dataset_metadata_pb=self.gbml_config_pb.shared_config.dataset_metadata
-        )
+        if self.gbml_config_pb.shared_config.HasField("dataset_metadata"):
+            dataset_metadata_pb = self.gbml_config_pb.shared_config.dataset_metadata
+        else:
+            dataset_metadata_pb = None
+        self.__load_dataset_metadata_pb_wrapper(dataset_metadata_pb)
         # Populate the _graph_metadata_pb_wrapper field
         self.__load_graph_metadata_pb_wrapper(
             graph_metadata_pb=self.gbml_config_pb.graph_metadata
         )
         # Populate the _flattened_graph_metadata_pb_wrapper field
-        self.__load_flattened_graph_metadata_pb_wrapper(
-            flattened_graph_metadata_pb=self.gbml_config_pb.shared_config.flattened_graph_metadata
-        )
+        if self.gbml_config_pb.shared_config.HasField("flattened_graph_metadata"):
+            flattened_graph_metadata_pb = (
+                self.gbml_config_pb.shared_config.flattened_graph_metadata
+            )
+        else:
+            flattened_graph_metadata_pb = None
+        self.__load_flattened_graph_metadata_pb_wrapper(flattened_graph_metadata_pb)
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: IF we do it this way we don't need to make the argument to `__load_flattened_graph_metadata_pb_wrapper` optional.

Ditto for the other fields like this.
created_at: 2025-05-14T20:21:43Z
author_association: COLLABORATOR
diff_hunk: @@ -366,6 +366,12 @@ def check_if_subgraph_sampler_config_valid(
     Check if subgraphSamplerConfig is valid.
     """
     logger.info("Config validation check: if subgraphSamplerConfig is valid.")
+    gbml_config_wrapper = GbmlConfigPbWrapper(gbml_config_pb=gbml_config_pb)
+    if gbml_config_wrapper.should_use_glt_backend:
+        logger.warning(
+            "Skipping subgraph sampler validation check since GLT Backend is being used."
path: python/gigl/src/validation_check/libs/template_config_checks.py
user: login: mkolodner-sc
body: Split_gen validation check and skipping is handled separately in the `check_if_split_generator_config_valid` function
created_at: 2025-05-14T20:36:26Z
author_association: COLLABORATOR
diff_hunk: @@ -178,10 +197,8 @@ def __load_flattened_graph_metadata_pb_wrapper(
         Args:
             flattened_graph_metadata_pb (flattened_graph_metadata_pb2.FlattenedGraphMetadata): The flattened graph metadata proto
         """
-        flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = (
-            FlattenedGraphMetadataPbWrapper(
-                flattened_graph_metadata_pb=flattened_graph_metadata_pb
-            )
+        flattened_graph_metadata_pb_wrapper = FlattenedGraphMetadataPbWrapper(
+            flattened_graph_metadata_pb=flattened_graph_metadata_pb
         )
         object.__setattr__(
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: can we do `self._flattened_graph_metadata_pb_wrapper = flattened_graph_metadata_pb_wrapper` here? (ditto generally)
created_at: 2025-05-14T21:01:31Z
author_association: COLLABORATOR
diff_hunk: @@ -313,13 +332,20 @@ def task_metadata_pb_wrapper(self) -> TaskMetadataPbWrapper:
         return self._task_metadata_pb_wrapper
 
     @property
-    def flattened_graph_metadata_pb_wrapper(self) -> FlattenedGraphMetadataPbWrapper:
+    def flattened_graph_metadata_pb_wrapper(
+        self,
+    ) -> FlattenedGraphMetadataPbWrapper:
         """
-        Allows access to a flattened_graph_metadata_pb_wrapper
+        Allows access to a flattened_graph_metadata_pb_wrapper.  If the underlying pb is empty, calling this property will instead throw an error.
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: mega nit: I think it may get confusing we use `pb` to refer to `partition book` some times and `protobuf` other times. WDYT about using `proto` here generally? 
created_at: 2025-05-14T21:02:47Z
author_association: COLLABORATOR
diff_hunk: @@ -178,10 +197,8 @@ def __load_flattened_graph_metadata_pb_wrapper(
         Args:
             flattened_graph_metadata_pb (flattened_graph_metadata_pb2.FlattenedGraphMetadata): The flattened graph metadata proto
         """
-        flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = (
-            FlattenedGraphMetadataPbWrapper(
-                flattened_graph_metadata_pb=flattened_graph_metadata_pb
-            )
+        flattened_graph_metadata_pb_wrapper = FlattenedGraphMetadataPbWrapper(
+            flattened_graph_metadata_pb=flattened_graph_metadata_pb
         )
         object.__setattr__(
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: mkolodner-sc
body: Ah it seems we can't since the gbml_config_pb_wrapper is frozen and thus is read-only, setting it this way gives 
```
error: Property "_preprocessed_metadata_pb_wrapper" defined in "GbmlConfigPbWrapper" is read-only
```
it seems object.__setattr__ is used to get around this in the scope of the `__post_init__()` function call specifically
created_at: 2025-05-14T21:13:45Z
author_association: COLLABORATOR
diff_hunk: @@ -178,10 +197,8 @@ def __load_flattened_graph_metadata_pb_wrapper(
         Args:
             flattened_graph_metadata_pb (flattened_graph_metadata_pb2.FlattenedGraphMetadata): The flattened graph metadata proto
         """
-        flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = (
-            FlattenedGraphMetadataPbWrapper(
-                flattened_graph_metadata_pb=flattened_graph_metadata_pb
-            )
+        flattened_graph_metadata_pb_wrapper = FlattenedGraphMetadataPbWrapper(
+            flattened_graph_metadata_pb=flattened_graph_metadata_pb
         )
         object.__setattr__(
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: > it seems object.setattr is used to get around this in the scope of the __post_init__() function call specifically

I see, thanks for checking. For my own knowledge can we use `setattr(self, ..., ...)` instead of the `object.__setattr__`?
created_at: 2025-05-14T21:35:14Z
author_association: COLLABORATOR
diff_hunk: @@ -46,37 +46,51 @@ class GbmlConfigPbWrapper:
 
     gbml_config_pb: gbml_config_pb2.GbmlConfig
 
-    _dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = field(init=False)
+    _dataset_metadata_pb_wrapper: Optional[DatasetMetadataPbWrapper] = field(
+        default=None, init=False
+    )
     _graph_metadata_pb_wrapper: GraphMetadataPbWrapper = field(init=False)
     _preprocessed_metadata_pb_wrapper: PreprocessedMetadataPbWrapper = field(init=False)
-    _flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = field(
-        init=False
-    )
+    _flattened_graph_metadata_pb_wrapper: Optional[
+        FlattenedGraphMetadataPbWrapper
+    ] = field(default=None, init=False)
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: kmontemayor2-sc
body: wait do we need to do `init = False` here still? not just `...: Optiona[X] = None`?


created_at: 2025-05-14T21:37:00Z
author_association: COLLABORATOR
diff_hunk: @@ -46,37 +46,51 @@ class GbmlConfigPbWrapper:
 
     gbml_config_pb: gbml_config_pb2.GbmlConfig
 
-    _dataset_metadata_pb_wrapper: DatasetMetadataPbWrapper = field(init=False)
+    _dataset_metadata_pb_wrapper: Optional[DatasetMetadataPbWrapper] = field(
+        default=None, init=False
+    )
     _graph_metadata_pb_wrapper: GraphMetadataPbWrapper = field(init=False)
     _preprocessed_metadata_pb_wrapper: PreprocessedMetadataPbWrapper = field(init=False)
-    _flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = field(
-        init=False
-    )
+    _flattened_graph_metadata_pb_wrapper: Optional[
+        FlattenedGraphMetadataPbWrapper
+    ] = field(default=None, init=False)
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: mkolodner-sc
body: Yeah we do, otherwise the dataclass will expect to receive these fields explicitly when constructing the dataclass with `__init__` (currently we only provide the `gbml_config_pb` when constructing this). We don't want to have `_flattened_graph_metadata_pb` passed explicitly here for example, and thus need it to be with `field(init=False)`.
created_at: 2025-05-14T21:43:11Z
author_association: COLLABORATOR
diff_hunk: @@ -178,10 +197,8 @@ def __load_flattened_graph_metadata_pb_wrapper(
         Args:
             flattened_graph_metadata_pb (flattened_graph_metadata_pb2.FlattenedGraphMetadata): The flattened graph metadata proto
         """
-        flattened_graph_metadata_pb_wrapper: FlattenedGraphMetadataPbWrapper = (
-            FlattenedGraphMetadataPbWrapper(
-                flattened_graph_metadata_pb=flattened_graph_metadata_pb
-            )
+        flattened_graph_metadata_pb_wrapper = FlattenedGraphMetadataPbWrapper(
+            flattened_graph_metadata_pb=flattened_graph_metadata_pb
         )
         object.__setattr__(
path: python/gigl/src/common/types/pb_wrappers/gbml_config.py
user: login: mkolodner-sc
body: This seems to pass type check this time, but still gives error in unit test: `dataclasses.FrozenInstanceError: cannot assign to field '_task_metadata_pb_wrapper'`
created_at: 2025-05-14T21:44:15Z
author_association: COLLABORATOR

PR comments:
user: login: mkolodner-sc
created_at: 2025-05-13T21:52:10Z
body: /e2e_test
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-13T21:52:25Z
body: # GiGL Automation

@ _21:52:25UTC_ : 🔄 `E2E Test` [started](https://github.com/Snapchat/GiGL/actions/runs/15007464186).

@ _23:11:21UTC_ : ✅ Workflow completed successfully.

author_association: CONTRIBUTOR
user: login: mkolodner-sc
created_at: 2025-05-14T20:50:14Z
body: /unit_test
author_association: COLLABORATOR
user: login: mkolodner-sc
created_at: 2025-05-14T20:50:25Z
body: /e2e_test
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-14T20:50:30Z
body: # GiGL Automation

@ _20:50:29UTC_ : 🔄 `Unit Test` [started](https://github.com/Snapchat/GiGL/actions/runs/15030806130).

@ _21:10:03UTC_ : ✅ Workflow completed successfully.

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-14T20:50:37Z
body: # GiGL Automation

@ _20:50:37UTC_ : 🔄 `E2E Test` [started](https://github.com/Snapchat/GiGL/actions/runs/15030809477).

@ _21:11:36UTC_ : ❌ Workflow failed.
Please check the logs for more details.

author_association: CONTRIBUTOR
user: login: mkolodner-sc
created_at: 2025-05-14T21:32:27Z
body: /e2e_test
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-14T21:32:40Z
body: # GiGL Automation

@ _21:32:39UTC_ : 🔄 `E2E Test` [started](https://github.com/Snapchat/GiGL/actions/runs/15031545725).

@ _22:53:51UTC_ : ✅ Workflow completed successfully.

author_association: CONTRIBUTOR