22# Copyright (c) 2024, 2025 Oracle and/or its affiliates.
33# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
44
5+ import re
56from typing import Any , Dict , Optional
67
78from oci .data_science .models import Model
8- from pydantic import BaseModel , Field
9+ from pydantic import BaseModel , Field , model_validator
10+
11+ from ads .aqua import logger
12+ from ads .aqua .config .utils .serializer import Serializable
913
1014
1115class ContainerSpec :
@@ -25,7 +29,6 @@ class ContainerSpec:
2529class ModelConfigResult (BaseModel ):
2630 """
2731 Represents the result of getting the AQUA model configuration.
28-
2932 Attributes:
3033 model_details (Dict[str, Any]): A dictionary containing model details extracted from OCI.
3134 config (Dict[str, Any]): A dictionary of the loaded configuration.
@@ -42,3 +45,222 @@ class Config:
4245 extra = "ignore"
4346 arbitrary_types_allowed = True
4447 protected_namespaces = ()
48+
49+
50+ class GPUSpecs (Serializable ):
51+ """
52+ Represents the GPU specifications for a compute instance.
53+ """
54+
55+ gpu_memory_in_gbs : Optional [int ] = Field (
56+ default = None , description = "The amount of GPU memory available (in GB)."
57+ )
58+ gpu_count : Optional [int ] = Field (
59+ default = None , description = "The number of GPUs available."
60+ )
61+ gpu_type : Optional [str ] = Field (
62+ default = None , description = "The type of GPU (e.g., 'V100, A100, H100')."
63+ )
64+
65+
66+ class GPUShapesIndex (Serializable ):
67+ """
68+ Represents the index of GPU shapes.
69+
70+ Attributes
71+ ----------
72+ shapes (Dict[str, GPUSpecs]): A mapping of compute shape names to their GPU specifications.
73+ """
74+
75+ shapes : Dict [str , GPUSpecs ] = Field (
76+ default_factory = dict ,
77+ description = "Mapping of shape names to GPU specifications." ,
78+ )
79+
80+
81+ class ComputeShapeSummary (Serializable ):
82+ """
83+ Represents the specifications of a compute instance's shape.
84+ """
85+
86+ core_count : Optional [int ] = Field (
87+ default = None , description = "The number of CPU cores available."
88+ )
89+ memory_in_gbs : Optional [int ] = Field (
90+ default = None , description = "The amount of memory (in GB) available."
91+ )
92+ name : Optional [str ] = Field (
93+ default = None , description = "The name identifier of the compute shape."
94+ )
95+ shape_series : Optional [str ] = Field (
96+ default = None , description = "The series or category of the compute shape."
97+ )
98+ gpu_specs : Optional [GPUSpecs ] = Field (
99+ default = None ,
100+ description = "The GPU specifications associated with the compute shape." ,
101+ )
102+
103+ @model_validator (mode = "after" )
104+ @classmethod
105+ def set_gpu_specs (cls , model : "ComputeShapeSummary" ) -> "ComputeShapeSummary" :
106+ """
107+ Validates and populates GPU specifications if the shape_series indicates a GPU-based shape.
108+
109+ - If the shape_series contains "GPU", the validator first checks if the shape name exists
110+ in the GPU_SPECS dictionary. If found, it creates a GPUSpecs instance with the corresponding data.
111+ - If the shape is not found in the GPU_SPECS, it attempts to extract the GPU count from the shape name
112+ using a regex pattern (looking for a number following a dot at the end of the name).
113+
114+ The information about shapes is taken from: https://docs.oracle.com/en-us/iaas/data-science/using/supported-shapes.htm
115+
116+ Returns:
117+ ComputeShapeSummary: The updated instance with gpu_specs populated if applicable.
118+ """
119+ try :
120+ if (
121+ model .shape_series
122+ and "GPU" in model .shape_series .upper ()
123+ and model .name
124+ and not model .gpu_specs
125+ ):
126+ # Try to extract gpu_count from the shape name using a regex (e.g., "VM.GPU3.2" -> gpu_count=2)
127+ match = re .search (r"\.(\d+)$" , model .name )
128+ if match :
129+ gpu_count = int (match .group (1 ))
130+ model .gpu_specs = GPUSpecs (gpu_count = gpu_count )
131+ except Exception as err :
132+ logger .debug (
133+ f"Error occurred in attempt to extract GPU specification for the f{ model .name } . "
134+ f"Details: { err } "
135+ )
136+ return model
137+
138+
139+ class AquaMultiModelRef (Serializable ):
140+ """
141+ Lightweight model descriptor used for multi-model deployment.
142+
143+ This class only contains essential details
144+ required to fetch complete model metadata and deploy models.
145+
146+ Attributes
147+ ----------
148+ model_id : str
149+ The unique identifier of the model.
150+ model_name : Optional[str]
151+ The name of the model.
152+ gpu_count : Optional[int]
153+ Number of GPUs required for deployment.
154+ env_var : Optional[Dict[str, Any]]
155+ Optional environment variables to override during deployment.
156+ artifact_location : Optional[str]
157+ Artifact path of model in the multimodel group.
158+ """
159+
160+ model_id : str = Field (..., description = "The model OCID to deploy." )
161+ model_name : Optional [str ] = Field (None , description = "The name of model." )
162+ gpu_count : Optional [int ] = Field (
163+ None , description = "The gpu count allocation for the model."
164+ )
165+ env_var : Optional [dict ] = Field (
166+ default_factory = dict , description = "The environment variables of the model."
167+ )
168+ artifact_location : Optional [str ] = Field (
169+ None , description = "Artifact path of model in the multimodel group."
170+ )
171+
172+ class Config :
173+ extra = "ignore"
174+ protected_namespaces = ()
175+
176+
177+ class ContainerPath (Serializable ):
178+ """
179+ Represents a parsed container path, extracting the path, name, and version.
180+
181+ This model is designed to parse a container path string of the format
182+ '<image_path>:<version>'. It extracts the following components:
183+ - `path`: The full path up to the version.
184+ - `name`: The last segment of the path, representing the image name.
185+ - `version`: The version number following the final colon.
186+
187+ Example Usage:
188+ --------------
189+ >>> container = ContainerPath(full_path="iad.ocir.io/ociodscdev/odsc-llm-evaluate:0.1.2.9")
190+ >>> container.path
191+ 'iad.ocir.io/ociodscdev/odsc-llm-evaluate'
192+ >>> container.name
193+ 'odsc-llm-evaluate'
194+ >>> container.version
195+ '0.1.2.9'
196+
197+ >>> container = ContainerPath(full_path="custom-scheme://path/to/versioned-model:2.5.1")
198+ >>> container.path
199+ 'custom-scheme://path/to/versioned-model'
200+ >>> container.name
201+ 'versioned-model'
202+ >>> container.version
203+ '2.5.1'
204+
205+ Attributes
206+ ----------
207+ full_path : str
208+ The complete container path string to be parsed.
209+ path : Optional[str]
210+ The full path up to the version (e.g., 'iad.ocir.io/ociodscdev/odsc-llm-evaluate').
211+ name : Optional[str]
212+ The image name, which is the last segment of `path` (e.g., 'odsc-llm-evaluate').
213+ version : Optional[str]
214+ The version number following the final colon in the path (e.g., '0.1.2.9').
215+
216+ Methods
217+ -------
218+ validate(values: Any) -> Any
219+ Validates and parses the `full_path`, extracting `path`, `name`, and `version`.
220+ """
221+
222+ full_path : str
223+ path : Optional [str ] = None
224+ name : Optional [str ] = None
225+ version : Optional [str ] = None
226+
227+ @model_validator (mode = "before" )
228+ @classmethod
229+ def validate (cls , values : Any ) -> Any :
230+ """
231+ Validates and parses the full container path, extracting the image path, image name, and version.
232+
233+ Parameters
234+ ----------
235+ values : dict
236+ The dictionary of values being validated, containing 'full_path'.
237+
238+ Returns
239+ -------
240+ dict
241+ Updated values dictionary with extracted 'path', 'name', and 'version'.
242+ """
243+ full_path = values .get ("full_path" , "" ).strip ()
244+
245+ # Regex to parse <image_path>:<version>
246+ match = re .match (
247+ r"^(?P<image_path>.+?)(?::(?P<image_version>[\w\.]+))?$" , full_path
248+ )
249+
250+ if not match :
251+ raise ValueError (
252+ "Invalid container path format. Expected format: '<image_path>:<version>'"
253+ )
254+
255+ # Extract image_path and version
256+ values ["path" ] = match .group ("image_path" )
257+ values ["version" ] = match .group ("image_version" )
258+
259+ # Extract image_name as the last segment of image_path
260+ values ["name" ] = values ["path" ].split ("/" )[- 1 ]
261+
262+ return values
263+
264+ class Config :
265+ extra = "ignore"
266+ protected_namespaces = ()
0 commit comments