google-cloud-mldiagnostics 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 (42) hide show
  1. google_cloud_mldiagnostics/__init__.py +13 -0
  2. google_cloud_mldiagnostics/api/__init__.py +7 -0
  3. google_cloud_mldiagnostics/api/metrics.py +38 -0
  4. google_cloud_mldiagnostics/api/mlrun.py +71 -0
  5. google_cloud_mldiagnostics/api/test_metrics.py +32 -0
  6. google_cloud_mldiagnostics/api/test_mlrun.py +97 -0
  7. google_cloud_mldiagnostics/clients/__init__.py +0 -0
  8. google_cloud_mldiagnostics/clients/control_plane_client.py +127 -0
  9. google_cloud_mldiagnostics/clients/logging_client.py +142 -0
  10. google_cloud_mldiagnostics/clients/test_control_plane_client.py +255 -0
  11. google_cloud_mldiagnostics/clients/test_logging_client.py +220 -0
  12. google_cloud_mldiagnostics/core/__init__.py +3 -0
  13. google_cloud_mldiagnostics/core/create_mlrun.py +131 -0
  14. google_cloud_mldiagnostics/core/global_manager.py +246 -0
  15. google_cloud_mldiagnostics/core/metrics.py +231 -0
  16. google_cloud_mldiagnostics/core/test_create_mlrun.py +235 -0
  17. google_cloud_mldiagnostics/core/test_global_manager.py +653 -0
  18. google_cloud_mldiagnostics/core/test_metrics.py +386 -0
  19. google_cloud_mldiagnostics/core/test_xprof.py +151 -0
  20. google_cloud_mldiagnostics/core/xprof.py +150 -0
  21. google_cloud_mldiagnostics/custom_types/__init__.py +0 -0
  22. google_cloud_mldiagnostics/custom_types/exceptions.py +29 -0
  23. google_cloud_mldiagnostics/custom_types/metric_types.py +32 -0
  24. google_cloud_mldiagnostics/custom_types/mlrun_types.py +79 -0
  25. google_cloud_mldiagnostics/utils/__init__.py +0 -0
  26. google_cloud_mldiagnostics/utils/config_utils.py +79 -0
  27. google_cloud_mldiagnostics/utils/gcp.py +72 -0
  28. google_cloud_mldiagnostics/utils/host_utils.py +161 -0
  29. google_cloud_mldiagnostics/utils/jax_utils/jax_config.py +43 -0
  30. google_cloud_mldiagnostics/utils/jax_utils/test_jax_config.py +72 -0
  31. google_cloud_mldiagnostics/utils/libtpu_utils/libtpu_metric.py +81 -0
  32. google_cloud_mldiagnostics/utils/libtpu_utils/libtpu_metric_test.py +131 -0
  33. google_cloud_mldiagnostics/utils/metric_utils.py +41 -0
  34. google_cloud_mldiagnostics/utils/metric_utils_test.py +71 -0
  35. google_cloud_mldiagnostics/utils/test_config_utils.py +170 -0
  36. google_cloud_mldiagnostics/utils/test_gcp.py +224 -0
  37. google_cloud_mldiagnostics/utils/test_host_utils.py +174 -0
  38. google_cloud_mldiagnostics-0.1.0.dist-info/METADATA +18 -0
  39. google_cloud_mldiagnostics-0.1.0.dist-info/RECORD +42 -0
  40. google_cloud_mldiagnostics-0.1.0.dist-info/WHEEL +5 -0
  41. google_cloud_mldiagnostics-0.1.0.dist-info/licenses/LICENSE +201 -0
  42. google_cloud_mldiagnostics-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,13 @@
1
+ """Init module for ml diagnostics."""
2
+
3
+ # Import the upfront API for users
4
+ from . import api
5
+ from . import core
6
+
7
+
8
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
9
+
10
+
11
+ machinelearning_run = api.machinelearning_run
12
+ metrics = api.metrics
13
+ xprof = core.xprof.Xprof
@@ -0,0 +1,7 @@
1
+ """API for interacting with the diagnostics library."""
2
+
3
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.api import metrics
4
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.api import mlrun
5
+
6
+
7
+ machinelearning_run = mlrun.machinelearning_run
@@ -0,0 +1,38 @@
1
+ """Module for recording metrics."""
2
+
3
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.core import metrics
4
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.custom_types import metric_types
5
+
6
+
7
+ _metrics_recorder = metrics.metrics_recorder
8
+
9
+
10
+ def record(
11
+ metric_name: metric_types.MetricType or str,
12
+ value: int | float,
13
+ step: int | None = None,
14
+ labels: dict[str, str] | None = None,
15
+ record_on_all_hosts: bool = False,
16
+ ) -> None:
17
+ """Record a single metric value using the active run.
18
+
19
+ Args:
20
+ metric_name: Name of metric to record.
21
+ value: Metric value.
22
+ step: Optional step number (auto-incremented if not provided).
23
+ labels: Optional additional labels.
24
+ record_on_all_hosts: Whether to record metrics on all hosts.
25
+
26
+ Raises:
27
+ RecordingError: If no active run or recording fails.
28
+
29
+ Example:
30
+ metrics.record(MetricType.TF_FLOPS, per_device_tf_flops)
31
+ metrics.record(MetricType.LEARNING_RATE, learning_rate)
32
+ metrics.record(MetricType.LEARNING_RATE, learning_rate, step=1)
33
+ """
34
+ is_enum = isinstance(metric_name, metric_types.MetricType)
35
+ metric_name = metric_name.value if is_enum else metric_name
36
+ _metrics_recorder.record(
37
+ metric_name, value, step, labels, record_on_all_hosts
38
+ )
@@ -0,0 +1,71 @@
1
+ """Module for creating and managing ML runs."""
2
+ from typing import Any
3
+
4
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.core import create_mlrun
5
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.custom_types import exceptions
6
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.custom_types import mlrun_types
7
+
8
+
9
+ # Main SDK function - this is the primary interface users will import
10
+ def machinelearning_run(
11
+ run_group: str,
12
+ name: str | None = None,
13
+ configs: dict[str, Any] | None = None,
14
+ gcs_path: str | None = None,
15
+ gcp_project: str | None = None,
16
+ gcp_region: str | None = None,
17
+ metrics_record_interval_sec: float = 10.0,
18
+ ) -> mlrun_types.MLRun:
19
+ """Create a new machine learning run.
20
+
21
+ This is the main entry point for the SDK that users will call to create ML
22
+ runs.
23
+
24
+ Args:
25
+ run_group: The run set this run belongs to
26
+ name: The name of the run (optional), system will generate a unique ID if
27
+ not provided by users.
28
+ configs: dict of configuration parameters
29
+ gcs_path: GCS path for storing run artifacts
30
+ gcp_project: The GCP project ID
31
+ gcp_region: The GCP region
32
+ metrics_record_interval_sec: The interval in seconds
33
+ for recording system metrics backend (tpu duty cycle,
34
+ tpu tensorcore utilization, hbm utilization,
35
+ host cpu utilization, host memory utilization).
36
+
37
+ Returns:
38
+ MLRun: A new ML run instance
39
+
40
+ Example:
41
+ from google_cloud_mldiagnostics import machinelearning_run
42
+
43
+ my_run = machinelearning_run(
44
+ run_group="training_set_v1",
45
+ name="experiment_1",
46
+ configs={"epochs": 100, "batch_size": 32},
47
+ gcs_path="gs://my-bucket/experiments"
48
+ )
49
+
50
+ # Update configs using dict methods
51
+ my_run.configs.update({"epochs": 300, "optimizer": "adam"})
52
+
53
+ # Update configs using attribute notation
54
+ my_run.configs.batch_size = 64
55
+ """
56
+ if not run_group:
57
+ raise exceptions.MLRunConfigurationError(
58
+ "run_group is required and must be provided. The run_group parameter"
59
+ " helps identify and organize your ML workloads. Please provide a"
60
+ " meaningful run_group name (e.g., 'training_v1',"
61
+ " 'experiment_batch_1')."
62
+ )
63
+ return create_mlrun.initialize_mlrun(
64
+ run_group=run_group,
65
+ name=name,
66
+ configs=configs,
67
+ gcs_path=gcs_path,
68
+ gcp_project=gcp_project,
69
+ gcp_region=gcp_region,
70
+ metrics_record_interval_sec=metrics_record_interval_sec,
71
+ )
@@ -0,0 +1,32 @@
1
+ """Unit tests for the API metrics module."""
2
+
3
+ import unittest
4
+ from unittest import mock
5
+
6
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.api import metrics
7
+
8
+
9
+ class TestMetricsAPI(unittest.TestCase):
10
+ """Test cases for the metrics API module."""
11
+
12
+ @mock.patch(
13
+ "google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.api."
14
+ "metrics._metrics_recorder"
15
+ )
16
+ def test_record_function_minimal_parameters(self, mock_recorder):
17
+ """Test record function with minimal parameters."""
18
+ # Setup
19
+ mock_metric_type = mock.Mock()
20
+ mock_metric_type.value = "test_metric"
21
+
22
+ # Call the API function
23
+ metrics.record(metric_name=mock_metric_type, value=42.5)
24
+
25
+ # Verify delegation to the underlying recorder
26
+ mock_recorder.record.assert_called_once_with(
27
+ mock_metric_type, 42.5, None, None, False
28
+ )
29
+
30
+
31
+ if __name__ == "__main__":
32
+ unittest.main()
@@ -0,0 +1,97 @@
1
+ """Unit tests for the machinelearning_run module."""
2
+
3
+ import json
4
+ import unittest
5
+ from unittest import mock
6
+
7
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.api import mlrun as api_mlrun
8
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.custom_types import exceptions
9
+
10
+
11
+ class TestMachineLearningRun(unittest.TestCase):
12
+ """Test cases for the machinelearning_run function."""
13
+
14
+ def setUp(self):
15
+ """Set up test fixtures before each test method."""
16
+ super().setUp()
17
+ self.valid_run_group = "test_run_group"
18
+ self.valid_name = "test_run_name"
19
+ self.valid_configs = {"epochs": 100, "batch_size": 32}
20
+ self.valid_gcs_path = "gs://test-bucket/path"
21
+ self.mock_create_mlrun = mock.patch(
22
+ "google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.core.create_mlrun.initialize_mlrun"
23
+ ).start()
24
+
25
+ def tearDown(self):
26
+ """Clean up after each test method."""
27
+ mock.patch.stopall()
28
+ super().tearDown()
29
+
30
+ def test_machinelearning_run_with_all_parameters(self):
31
+ """Test creating ML run with all parameters provided."""
32
+ api_mlrun.machinelearning_run(
33
+ run_group=self.valid_run_group,
34
+ name=self.valid_name,
35
+ configs=self.valid_configs,
36
+ gcs_path=self.valid_gcs_path
37
+ )
38
+
39
+ self.mock_create_mlrun.assert_called_once_with(
40
+ run_group=self.valid_run_group,
41
+ name=self.valid_name,
42
+ configs=self.valid_configs,
43
+ gcs_path=self.valid_gcs_path,
44
+ gcp_project=None,
45
+ gcp_region=None,
46
+ metrics_record_interval_sec=10.0,
47
+ )
48
+
49
+ def test_machinelearning_run_none_run_group_raises_error(self):
50
+ """Test that None run_group raises MLRunConfigurationError."""
51
+ with self.assertRaises(exceptions.MLRunConfigurationError) as context:
52
+ api_mlrun.machinelearning_run(run_group=None)
53
+
54
+ self.assertIn(
55
+ "run_group is required and must be provided. The run_group parameter"
56
+ " helps identify and organize your ML workloads. Please provide a"
57
+ " meaningful run_group name (e.g., 'training_v1',"
58
+ " 'experiment_batch_1').",
59
+ str(context.exception),
60
+ )
61
+ self.assertIn("identify and organize", str(context.exception))
62
+
63
+ def test_yaml_json_workflow_configs(self):
64
+ """Test ML run creation with configs loaded from a JSON representation of YAML."""
65
+ self.valid_run_group = "test_run_group"
66
+ self.valid_name = "test_run_name"
67
+ self.valid_configs = {"epochs": 100, "batch_size": 32}
68
+ self.valid_gcs_path = "gs://test-bucket/path"
69
+
70
+ # Simulate YAML data that was loaded and converted to JSON
71
+ yaml_data = {
72
+ "epochs": 200,
73
+ "batch_size": 64,
74
+ "learning_rate": 0.001,
75
+ "model_type": "transformer"
76
+ }
77
+ json_data = json.dumps(yaml_data)
78
+
79
+ api_mlrun.machinelearning_run(
80
+ run_group=self.valid_run_group,
81
+ name=self.valid_name,
82
+ configs=json.loads(json_data), # Parse JSON back to dict for this test
83
+ gcs_path=self.valid_gcs_path
84
+ )
85
+
86
+ self.mock_create_mlrun.assert_called_once_with(
87
+ run_group=self.valid_run_group,
88
+ name=self.valid_name,
89
+ configs=yaml_data, # Verify that the YAML data was correctly converted
90
+ gcs_path=self.valid_gcs_path,
91
+ gcp_project=None,
92
+ gcp_region=None,
93
+ metrics_record_interval_sec=10.0,
94
+ )
95
+
96
+ if __name__ == "__main__":
97
+ unittest.main()
File without changes
@@ -0,0 +1,127 @@
1
+ """Client for sending requests to Diagon Control Plane."""
2
+
3
+ import re
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import google.auth
7
+ from google.auth.transport import requests as google_auth_requests
8
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.utils import host_utils
9
+ import requests
10
+
11
+
12
+ class ControlPlaneClient:
13
+ """Client for communicating with Google Cloud Hypercompute Cluster ML Run service."""
14
+
15
+ def __init__(
16
+ self,
17
+ project_id: str = "supercomputer-testing",
18
+ location: str = "us-central1",
19
+ base_url: str = "https://autopush-hypercomputecluster.sandbox.googleapis.com/v1alpha",
20
+ ):
21
+ """Initializes a new ControlPlaneClient.
22
+
23
+ Args:
24
+ project_id: Google Cloud project ID
25
+ location: Google Cloud location/region
26
+ base_url: Base URL for the API endpoint
27
+ """
28
+ self.project_id = project_id
29
+ self.location = location
30
+ self.base_url = base_url
31
+ self.ml_runs_url = f"{base_url}/projects/{project_id}/locations/{location}/machineLearningRuns"
32
+
33
+ # Initialize Google Cloud credentials
34
+ self.credentials, _ = google.auth.default()
35
+
36
+ def _get_access_token(self) -> str:
37
+ """Get Google Cloud access token for authentication."""
38
+ if not self.credentials.valid:
39
+ self.credentials.refresh(google_auth_requests.Request())
40
+
41
+ return self.credentials.token
42
+
43
+ def _get_headers(self) -> Dict[str, str]:
44
+ """Get HTTP headers with authentication."""
45
+ return {
46
+ "Content-Type": "application/json",
47
+ "Authorization": f"Bearer {self._get_access_token()}",
48
+ }
49
+
50
+ def create_ml_run(
51
+ self,
52
+ name: str,
53
+ display_name: str,
54
+ run_phase: str,
55
+ configs: Optional[Dict[str, Any]] = None,
56
+ tools: Optional[List[Dict[str, Any]]] = None,
57
+ metrics: Optional[Dict[str, str]] = None,
58
+ artifacts: Optional[Dict[str, str]] = None,
59
+ run_group: Optional[str] = None,
60
+ labels: Optional[Dict[str, str]] = None,
61
+ ) -> Dict[str, Any]:
62
+ """Create a new ML run using the Google Cloud API.
63
+
64
+ Args:
65
+ name: Name of the run
66
+ display_name: Display name for the run
67
+ run_phase: Phase of the run (ACTIVE, COMPLETE, FAILED)
68
+ configs: Configuration settings (userConfigs, softwareConfigs,
69
+ hardwareConfigs)
70
+ tools: List of tools to enable (e.g., XProf, NSys)
71
+ metrics: Metrics for the run (e.g., avgStep, avgLatency)
72
+ artifacts: Artifacts configuration (e.g., gcsPath)
73
+ run_group: Run group grouping identifier
74
+ labels: Custom labels for the run
75
+
76
+ Returns:
77
+ Response from the API as a dictionary
78
+
79
+ Raises:
80
+ requests.exceptions.RequestException: If the HTTP request fails
81
+ """
82
+ payload = {"displayName": display_name, "name": name}
83
+
84
+ if configs:
85
+ payload["configs"] = configs
86
+
87
+ if metrics:
88
+ payload["metrics"] = metrics
89
+
90
+ if artifacts:
91
+ payload["artifacts"] = artifacts
92
+
93
+ if run_group:
94
+ payload["runSet"] = run_group
95
+
96
+ if labels:
97
+ payload["labels"] = labels
98
+
99
+ if run_phase:
100
+ payload["runPhase"] = run_phase
101
+
102
+ if tools:
103
+ payload["tools"] = tools
104
+
105
+ # Sanitize the name for machineLearningRunId
106
+ sanitized_name = host_utils.sanitize_identifier(name)
107
+
108
+ params = {"machine_learning_run_id": sanitized_name}
109
+
110
+ response = requests.post(
111
+ self.ml_runs_url,
112
+ headers=self._get_headers(),
113
+ params=params,
114
+ json=payload,
115
+ )
116
+
117
+ # Raise an exception for HTTP error status codes
118
+ response.raise_for_status()
119
+
120
+ return response.json()
121
+
122
+ def update_ml_run(
123
+ self
124
+ ) -> None:
125
+ """Update an existing ML run using the Google Cloud API."""
126
+ del self
127
+ pass
@@ -0,0 +1,142 @@
1
+ """Client for writing metrics directly to Google Cloud Logging."""
2
+
3
+ import datetime
4
+ import logging
5
+ import os
6
+ from typing import List, Optional
7
+
8
+ from google.auth import credentials
9
+ from google.cloud import logging as cloud_logging
10
+ from google.cloud.logging_v2 import resource
11
+ from google_cloud_mldiagnostics.src.google_cloud_mldiagnostics.custom_types import exceptions
12
+
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class LoggingClient:
18
+ """Client for writing metrics directly to Google Cloud Logging."""
19
+
20
+ def __init__(
21
+ self,
22
+ project_id: str,
23
+ log_name: str = "ml_diagnostics_metric",
24
+ credentials_path: Optional[str] = None,
25
+ user_credentials: Optional[credentials.Credentials] = None,
26
+ ):
27
+ """Initialize the logging client.
28
+
29
+ Args:
30
+ project_id: GCP project ID
31
+ log_name: Name of the log to write to
32
+ credentials_path: Optional path to service account credentials
33
+ user_credentials: Optional explicit credentials object.
34
+ """
35
+ self.project_id = project_id
36
+ self.log_name = log_name
37
+
38
+ try:
39
+ if user_credentials:
40
+ # Use explicit credentials if provided
41
+ self.client = cloud_logging.Client(
42
+ project=project_id, credentials=user_credentials
43
+ )
44
+ elif credentials_path:
45
+ # Fall back to credentials file
46
+ os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = credentials_path
47
+ self.client = cloud_logging.Client(project=project_id)
48
+ else:
49
+ # Use default credentials
50
+ self.client = cloud_logging.Client(project=project_id)
51
+
52
+ # Get the logger for this log name
53
+ self.logger = self.client.logger(log_name)
54
+
55
+ except Exception as e:
56
+ raise exceptions.MLDiagnosticError(
57
+ f"Failed to initialize logging client: {e}"
58
+ ) from e
59
+
60
+ def write_metric(
61
+ self,
62
+ metric_name: str,
63
+ value: float | int | List[float | int],
64
+ run_id: str,
65
+ location: str,
66
+ step: Optional[int] = None,
67
+ labels: Optional[dict[str, str]] = None,
68
+ ):
69
+ """Write a single metric point to Cloud Logging.
70
+
71
+ Args:
72
+ metric_name: Name of the metric
73
+ value: Metric value. Can be a single int or float, or a list of ints
74
+ and floats.
75
+ run_id: ML run identifier
76
+ location: ML run region
77
+ step: Optional step number
78
+ labels: Optional additional labels
79
+
80
+ Returns:
81
+ True if the metric was written successfully, False otherwise.
82
+ """
83
+ try:
84
+ # Create timestamp
85
+ current_time = datetime.datetime.now(datetime.timezone.utc)
86
+
87
+ # Define the resource for the log entry
88
+ metric_resource = resource.Resource(
89
+ type="generic_node",
90
+ labels={
91
+ "project_id": self.project_id,
92
+ "location": location,
93
+ "namespace": metric_name,
94
+ "node_id": run_id,
95
+ },
96
+ )
97
+
98
+ # Build the log payload structure
99
+ if isinstance(value, (int, float)):
100
+ value = [value]
101
+ payload = {"values": value}
102
+
103
+ # Add optional fields
104
+ if step is not None:
105
+ payload["step_index"] = step
106
+ if labels:
107
+ payload.update(labels)
108
+
109
+ # Write to Cloud Logging
110
+ self.logger.log_struct(
111
+ payload,
112
+ severity="INFO",
113
+ timestamp=current_time,
114
+ resource=metric_resource,
115
+ )
116
+
117
+ logger.info("Successfully written metric to log: %s", metric_name)
118
+
119
+ except Exception as e:
120
+ raise exceptions.MLDiagnosticError(
121
+ f"Failed to write to Cloud Logging: {e}"
122
+ ) from e
123
+
124
+
125
+ class NoOpLoggingClient(LoggingClient):
126
+ """A logging client that performs no operations."""
127
+
128
+ def __init__(self): # pylint: disable=super-init-not-called
129
+ """Initializes the NoOp client. Does nothing."""
130
+ pass
131
+
132
+ def write_metric(
133
+ self,
134
+ metric_name: str,
135
+ value: float | int | List[float | int],
136
+ run_id: str,
137
+ location: str,
138
+ step: Optional[int] = None,
139
+ labels: Optional[dict[str, str]] = None,
140
+ ):
141
+ """This is a no-op and does not write any metrics."""
142
+ pass