deploy.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import urllib
  2. from abc import ABC
  3. from http import HTTPStatus
  4. from typing import Optional
  5. import json
  6. import requests
  7. from attrs import asdict, define, field, validators
  8. from modelscope.hub.api import ModelScopeConfig
  9. from modelscope.hub.constants import (API_RESPONSE_FIELD_DATA,
  10. API_RESPONSE_FIELD_MESSAGE)
  11. from modelscope.hub.errors import (NotLoginException, NotSupportError,
  12. RequestError, handle_http_response, is_ok,
  13. raise_for_http_status)
  14. from modelscope.hub.utils.utils import get_endpoint
  15. from modelscope.utils.logger import get_logger
  16. # yapf: enable
  17. logger = get_logger()
  18. class Accelerator(object):
  19. CPU = 'cpu'
  20. GPU = 'gpu'
  21. class Vendor(object):
  22. EAS = 'eas'
  23. class EASRegion(object):
  24. beijing = 'cn-beijing'
  25. hangzhou = 'cn-hangzhou'
  26. class EASCpuInstanceType(object):
  27. """EAS Cpu Instance Type, ref(https://help.aliyun.com/document_detail/144261.html)
  28. """
  29. tiny = 'ecs.c6.2xlarge'
  30. small = 'ecs.c6.4xlarge'
  31. medium = 'ecs.c6.6xlarge'
  32. large = 'ecs.c6.8xlarge'
  33. class EASGpuInstanceType(object):
  34. """EAS Gpu Instance Type, ref(https://help.aliyun.com/document_detail/144261.html)
  35. """
  36. tiny = 'ecs.gn5-c28g1.7xlarge'
  37. small = 'ecs.gn5-c8g1.4xlarge'
  38. medium = 'ecs.gn6i-c24g1.12xlarge'
  39. large = 'ecs.gn6e-c12g1.3xlarge'
  40. def min_smaller_than_max(instance, attribute, value):
  41. if value > instance.max_replica:
  42. raise ValueError(
  43. "'min_replica' value: %s has to be smaller than 'max_replica' value: %s!"
  44. % (value, instance.max_replica))
  45. @define
  46. class ServiceScalingConfig(object):
  47. """Resource scaling config
  48. Currently we ignore max_replica
  49. Args:
  50. max_replica: maximum replica
  51. min_replica: minimum replica
  52. """
  53. max_replica: int = field(default=1, validator=validators.ge(1))
  54. min_replica: int = field(
  55. default=1, validator=[validators.ge(1), min_smaller_than_max])
  56. @define
  57. class ServiceResourceConfig(object):
  58. """Eas Resource request.
  59. Args:
  60. accelerator: the accelerator(cpu|gpu)
  61. instance_type: the instance type.
  62. scaling: The instance scaling config.
  63. """
  64. instance_type: str
  65. scaling: ServiceScalingConfig
  66. accelerator: str = field(
  67. default=Accelerator.CPU,
  68. validator=validators.in_([Accelerator.CPU, Accelerator.GPU]))
  69. @define
  70. class ServiceProviderParameters(ABC):
  71. pass
  72. @define
  73. class EASDeployParameters(ServiceProviderParameters):
  74. """Parameters for EAS Deployment.
  75. Args:
  76. resource_group: the resource group to deploy, current default.
  77. region: The eas instance region(eg: cn-hangzhou).
  78. access_key_id: The eas account access key id.
  79. access_key_secret: The eas account access key secret.
  80. vendor: must be 'eas'
  81. """
  82. region: str
  83. access_key_id: str
  84. access_key_secret: str
  85. resource_group: Optional[str] = None
  86. vendor: str = field(
  87. default=Vendor.EAS, validator=validators.in_([Vendor.EAS]))
  88. @define
  89. class EASListParameters(ServiceProviderParameters):
  90. """EAS instance list parameters.
  91. Args:
  92. resource_group: the resource group to deploy, current default.
  93. region: The eas instance region(eg: cn-hangzhou).
  94. access_key_id: The eas account access key id.
  95. access_key_secret: The eas account access key secret.
  96. vendor: must be 'eas'
  97. """
  98. access_key_id: str
  99. access_key_secret: str
  100. region: str = None
  101. resource_group: str = None
  102. vendor: str = field(
  103. default=Vendor.EAS, validator=validators.in_([Vendor.EAS]))
  104. @define
  105. class DeployServiceParameters(object):
  106. """Deploy service parameters
  107. Args:
  108. instance_name: the name of the service.
  109. model_id: the modelscope model_id
  110. revision: the modelscope model revision
  111. resource: the resource requirement.
  112. provider: the cloud service provider.
  113. """
  114. instance_name: str
  115. model_id: str
  116. revision: str
  117. resource: ServiceResourceConfig
  118. provider: ServiceProviderParameters
  119. class AttrsToQueryString(ABC):
  120. """Convert the attrs class to json string.
  121. Args:
  122. """
  123. def to_query_str(self):
  124. self_dict = asdict(
  125. self.provider, filter=lambda attr, value: value is not None)
  126. json_str = json.dumps(self_dict)
  127. print(json_str)
  128. safe_str = urllib.parse.quote_plus(json_str)
  129. print(safe_str)
  130. query_param = 'provider=%s' % safe_str
  131. return query_param
  132. @define
  133. class ListServiceParameters(AttrsToQueryString):
  134. provider: ServiceProviderParameters
  135. skip: int = 0
  136. limit: int = 100
  137. @define
  138. class GetServiceParameters(AttrsToQueryString):
  139. provider: ServiceProviderParameters
  140. @define
  141. class DeleteServiceParameters(AttrsToQueryString):
  142. provider: ServiceProviderParameters
  143. class ServiceDeployer(object):
  144. """Facilitate model deployment on to supported service provider(s).
  145. """
  146. def __init__(self, endpoint=None):
  147. self.endpoint = endpoint if endpoint is not None else get_endpoint()
  148. self.headers = {'user-agent': ModelScopeConfig.get_user_agent()}
  149. self.cookies = ModelScopeConfig.get_cookies()
  150. if self.cookies is None:
  151. raise NotLoginException(
  152. 'Token does not exist, please login with HubApi first.')
  153. # deploy_model
  154. def create(self, model_id: str, revision: str, instance_name: str,
  155. resource: ServiceResourceConfig,
  156. provider: ServiceProviderParameters):
  157. """Deploy model to cloud, current we only support PAI EAS, this is an async API ,
  158. and the deployment could take a while to finish remotely. Please check deploy instance
  159. status separately via checking the status.
  160. Args:
  161. model_id (str): The deployed model id
  162. revision (str): The model revision
  163. instance_name (str): The deployed model instance name.
  164. resource (ServiceResourceConfig): The service resource information.
  165. provider (ServiceProviderParameters): The service provider parameter
  166. Raises:
  167. NotSupportError: Not supported platform.
  168. RequestError: The server return error.
  169. Returns:
  170. ServiceInstanceInfo: The information of the deployed service instance.
  171. """
  172. if provider.vendor != Vendor.EAS:
  173. raise NotSupportError(
  174. 'Not support vendor: %s ,only support EAS current.' %
  175. (provider.vendor))
  176. create_params = DeployServiceParameters(
  177. instance_name=instance_name,
  178. model_id=model_id,
  179. revision=revision,
  180. resource=resource,
  181. provider=provider)
  182. path = f'{self.endpoint}/api/v1/deployer/endpoint'
  183. body = asdict(create_params)
  184. r = requests.post(
  185. path, json=body, cookies=self.cookies, headers=self.headers)
  186. handle_http_response(r, logger, self.cookies, 'create_service')
  187. if r.status_code >= HTTPStatus.OK and r.status_code < HTTPStatus.MULTIPLE_CHOICES:
  188. if is_ok(r.json()):
  189. data = r.json()[API_RESPONSE_FIELD_DATA]
  190. return data
  191. else:
  192. raise RequestError(r.json()[API_RESPONSE_FIELD_MESSAGE])
  193. else:
  194. raise_for_http_status(r)
  195. return None
  196. def get(self, instance_name: str, provider: ServiceProviderParameters):
  197. """Query the specified instance information.
  198. Args:
  199. instance_name (str): The deployed instance name.
  200. provider (ServiceProviderParameters): The cloud provider information, for eas
  201. need region(eg: ch-hangzhou), access_key_id and access_key_secret.
  202. Raises:
  203. RequestError: The request is failed from server.
  204. Returns:
  205. Dict: The information of the requested service instance.
  206. """
  207. params = GetServiceParameters(provider=provider)
  208. path = '%s/api/v1/deployer/endpoint/%s?%s' % (
  209. self.endpoint, instance_name, params.to_query_str())
  210. r = requests.get(path, cookies=self.cookies, headers=self.headers)
  211. handle_http_response(r, logger, self.cookies, 'get_service')
  212. if r.status_code == HTTPStatus.OK:
  213. if is_ok(r.json()):
  214. data = r.json()[API_RESPONSE_FIELD_DATA]
  215. return data
  216. else:
  217. raise RequestError(r.json()[API_RESPONSE_FIELD_MESSAGE])
  218. else:
  219. raise_for_http_status(r)
  220. return None
  221. def delete(self, instance_name: str, provider: ServiceProviderParameters):
  222. """Delete deployed model, this api send delete command and return, it will take
  223. some to delete, please check through the cloud console.
  224. Args:
  225. instance_name (str): The instance name you want to delete.
  226. provider (ServiceProviderParameters): The cloud provider information, for eas
  227. need region(eg: ch-hangzhou), access_key_id and access_key_secret.
  228. Raises:
  229. RequestError: The request is failed.
  230. Returns:
  231. Dict: The deleted instance information.
  232. """
  233. params = DeleteServiceParameters(provider=provider)
  234. path = '%s/api/v1/deployer/endpoint/%s?%s' % (
  235. self.endpoint, instance_name, params.to_query_str())
  236. r = requests.delete(path, cookies=self.cookies, headers=self.headers)
  237. handle_http_response(r, logger, self.cookies, 'delete_service')
  238. if r.status_code == HTTPStatus.OK:
  239. if is_ok(r.json()):
  240. data = r.json()[API_RESPONSE_FIELD_DATA]
  241. return data
  242. else:
  243. raise RequestError(r.json()[API_RESPONSE_FIELD_MESSAGE])
  244. else:
  245. raise_for_http_status(r)
  246. return None
  247. def list(self,
  248. provider: ServiceProviderParameters,
  249. skip: Optional[int] = 0,
  250. limit: Optional[int] = 100):
  251. """List deployed model instances.
  252. Args:
  253. provider (ServiceProviderParameters): The cloud service provider parameter,
  254. for eas, need access_key_id and access_key_secret.
  255. skip (int, optional): start of the list, current not support.
  256. limit (int, optional): maximum number of instances return, current not support
  257. Raises:
  258. RequestError: The request is failed from server.
  259. Returns:
  260. List: List of instance information
  261. """
  262. params = ListServiceParameters(
  263. provider=provider, skip=skip, limit=limit)
  264. path = '%s/api/v1/deployer/endpoint?%s' % (self.endpoint,
  265. params.to_query_str())
  266. r = requests.get(path, cookies=self.cookies, headers=self.headers)
  267. handle_http_response(r, logger, self.cookies, 'list_service_instances')
  268. if r.status_code == HTTPStatus.OK:
  269. if is_ok(r.json()):
  270. data = r.json()[API_RESPONSE_FIELD_DATA]
  271. return data
  272. else:
  273. raise RequestError(r.json()[API_RESPONSE_FIELD_MESSAGE])
  274. else:
  275. raise_for_http_status(r)
  276. return None