Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ML Engine: Versions on Ansible #2068

Merged
merged 6 commits into from
Jul 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions products/mlengine/ansible.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,30 @@ datasources: !ruby/object:Overrides::ResourceOverrides
Model: !ruby/object:Overrides::Ansible::ResourceOverride
facts: !ruby/object:Provider::Ansible::FactsOverride
has_filters: false
Version: !ruby/object:Overrides::Ansible::ResourceOverride
facts: !ruby/object:Provider::Ansible::FactsOverride
has_filters: false
overrides: !ruby/object:Overrides::ResourceOverrides
Model: !ruby/object:Overrides::Ansible::ResourceOverride
transport: !ruby/object:Overrides::Ansible::Transport
decoder: decode_response
provider_helpers:
- products/mlengine/helpers/ansible/model_decoder.py
Version: !ruby/object:Overrides::Ansible::ResourceOverride
update: |
if module.params.get('is_default') is True:
set_default(module)
post_create: |
if module.params.get('is_default') is True:
set_default(module)
transport: !ruby/object:Overrides::Ansible::Transport
decoder: decode_response
properties:
isDefault: !ruby/object:Overrides::Ansible::PropertyOverride
aliases: ['default']
provider_helpers:
- products/mlengine/helpers/ansible/model_decoder.py
- products/mlengine/helpers/ansible/version_set_default.py
files: !ruby/object:Provider::Config::Files
resource:
<%= lines(indent(compile('provider/ansible/resource~compile.yaml'), 4)) -%>
36 changes: 36 additions & 0 deletions products/mlengine/ansible_version_added.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
:facts:
:Model:
:version_added: '2.9'
:Version:
:version_added: '2.9'
:regular:
:Model:
:version_added: '2.9'
Expand All @@ -21,3 +23,37 @@
:version_added: '2.9'
:labels:
:version_added: '2.9'
:Version:
:version_added: '2.9'
:name:
:version_added: '2.9'
:description:
:version_added: '2.9'
:deploymentUri:
:version_added: '2.9'
:runtimeVersion:
:version_added: '2.9'
:machineType:
:version_added: '2.9'
:labels:
:version_added: '2.9'
:framework:
:version_added: '2.9'
:pythonVersion:
:version_added: '2.9'
:serviceAccount:
:version_added: '2.9'
:autoScaling:
:version_added: '2.9'
:minNodes:
:version_added: '2.9'
:manualScaling:
:version_added: '2.9'
:nodes:
:version_added: '2.9'
:predictionClass:
:version_added: '2.9'
:model:
:version_added: '2.9'
:isDefault:
:version_added: '2.9'
169 changes: 169 additions & 0 deletions products/mlengine/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ objects:
self_link: projects/{{project}}/models/{{name}}
# This resources is not updatable (outside of versions, which is a version-level method)
input: true
async: !ruby/object:Api::Async
actions: ['delete']
operation: !ruby/object:Api::Async::Operation
path: 'name'
base_url: '{{op_id}}'
wait_ms: 1000
result: !ruby/object:Api::Async::Result
path: 'response'
resource_inside_response: true
status: !ruby/object:Api::Async::Status
path: 'done'
complete: True
allowed:
- True
- False
error: !ruby/object:Api::Async::Error
path: 'error'
message: 'message'
description: |
Represents a machine learning solution.

Expand Down Expand Up @@ -73,3 +91,154 @@ objects:
- !ruby/object:Api::Type::KeyValuePairs
name: 'labels'
description: One or more labels that you can add, to organize your models.
- !ruby/object:Api::Resource
name: 'Version'
base_url: projects/{{project}}/models/{{model}}/versions
self_link: projects/{{project}}/models/{{model}}/versions/{{name}}
input: true
description: |
Each version is a trained model deployed in the cloud, ready to handle
prediction requests. A model can have multiple versions
async: !ruby/object:Api::Async
operation: !ruby/object:Api::Async::Operation
path: 'name'
base_url: '{{op_id}}'
wait_ms: 1000
result: !ruby/object:Api::Async::Result
path: 'response'
resource_inside_response: true
status: !ruby/object:Api::Async::Status
path: 'done'
complete: True
allowed:
- True
- False
error: !ruby/object:Api::Async::Error
path: 'error'
message: 'message'
parameters:
- !ruby/object:Api::Type::ResourceRef
name: 'model'
description: The model that this version belongs to
resource: Model
imports: name
required: true
- !ruby/object:Api::Type::Boolean
name: 'isDefault'
description: If true, this version will be used to handle prediction requests that do not specify a version
properties:
- !ruby/object:Api::Type::String
name: 'name'
description: |
The name specified for the version when it was created.

The version name must be unique within the model it is created in.
required: true
- !ruby/object:Api::Type::String
name: 'description'
description: The description specified for the version when it was created.
# isDefault is output-only, but all creation-based providers will override this
# to implement `setDefaultVersion` API.
- !ruby/object:Api::Type::Boolean
name: 'isDefault'
description: If true, this version will be used to handle prediction requests that do not specify a version
output: true
- !ruby/object:Api::Type::String
name: 'deploymentUri'
description: The Cloud Storage location of the trained model used to create the version
required: true
- !ruby/object:Api::Type::Time
name: 'createTime'
description: The time the version was created.
output: true
- !ruby/object:Api::Type::Time
name: 'lastUseTime'
description: The time the version was last used for prediction.
output: true
- !ruby/object:Api::Type::String
name: 'runtimeVersion'
description: The AI Platform runtime version to use for this deployment
- !ruby/object:Api::Type::Enum
name: 'machineType'
description: The type of machine on which to serve the model. Currently only applies to online prediction service.
values:
- mls1-c1-m2
- mls1-c4-m2
- !ruby/object:Api::Type::Enum
name: 'state'
description: The state of a version
values:
- UNKNOWN
- READY
- CREATING
- FAILED
- DELETING
- UPDATING
output: true
- !ruby/object:Api::Type::String
name: 'errorMessage'
description: The details of a failure or cancellation
output: true
- !ruby/object:Api::Type::Array
name: 'packageUris'
description: Cloud Storage paths (gs://…) of packages for custom prediction routines or scikit-learn pipelines with custom code.
output: true
item_type: Api::Type::String
- !ruby/object:Api::Type::KeyValuePairs
name: 'labels'
description: One or more labels that you can add, to organize your model versions
# no etag since it's input: true
- !ruby/object:Api::Type::Enum
name: 'framework'
description: The machine learning framework AI Platform uses to train this version of the model
values:
- FRAMEWORK_UNSPECIFIED
- TENSORFLOW
- SCIKIT_LEARN
- XGBOOST
- !ruby/object:Api::Type::Enum
name: 'pythonVersion'
rambleraptor marked this conversation as resolved.
Show resolved Hide resolved
description: |
The version of Python used in prediction. If not set, the default
version is '2.7'. Python '3.5' is available when runtimeVersion is
set to '1.4' and above. Python '2.7' works with all supported runtime
versions.
values:
- '2.7'
- '3.5'
- !ruby/object:Api::Type::String
name: 'serviceAccount'
description: Specifies the service account for resource access control.
- !ruby/object:Api::Type::NestedObject
name: 'autoScaling'
description: |
Automatically scale the number of nodes used to serve the model in
response to increases and decreases in traffic. Care should be taken
to ramp up traffic according to the model's ability to scale or you
will start seeing increases in latency and 429 response codes.
conflicts: ['manualScaling']
properties:
- !ruby/object:Api::Type::Integer
name: 'minNodes'
description: The minimum number of nodes to allocate for this mode
- !ruby/object:Api::Type::NestedObject
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to flatten the scaling-related nested objects' single member fields into top-level params?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ansible doesn't have this feature currently (I believe TF does).

name: 'manualScaling'
description: |
Manually select the number of nodes to use for serving the model. You
should generally use autoScaling with an appropriate minNodes
instead, but this option is available if you want more predictable
billing. Beware that latency and error rates will increase if the
traffic exceeds that capability of the system to serve it based on
the selected number of nodes.
properties:
- !ruby/object:Api::Type::Integer
name: 'nodes'
description: The number of nodes to allocate for this model. These nodes are always up, starting from the time the model is deployed
- !ruby/object:Api::Type::String
name: 'predictionClass'
description: |
The fully qualified name (module_name.class_name) of a class that
implements the Predictor interface described in this reference
field. The module containing this class should be included in a
package provided to the packageUris field.

42 changes: 42 additions & 0 deletions products/mlengine/examples/ansible/version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2017 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
--- !ruby/object:Provider::Ansible::Example
task: !ruby/object:Provider::Ansible::Task
name: gcp_mlengine_version
code:
# ML Engine doesn't like dashes, which ansible's test name
# generator uses.
name: "{{ resource_name | replace('-', '_') }}"
model: "{{ model }}"
runtime_version: 1.13
python_version: 3.5
is_default: true
deployment_uri: gs://ansible-cloudml-bucket/
project: <%= ctx[:project] %>
auth_kind: <%= ctx[:auth_kind] %>
service_account_file: <%= ctx[:service_account_file] %>

dependencies:
- !ruby/object:Provider::Ansible::Task
name: gcp_mlengine_model
code:
name: <%= dependency_name('model', 'version').gsub('-', '_') %>
description: 'My model'
regions:
- 'us-central1'
online_prediction_logging: true
online_prediction_console_logging: true
project: <%= ctx[:project] %>
auth_kind: <%= ctx[:auth_kind] %>
service_account_file: <%= ctx[:service_account_file] %>
register: model
3 changes: 2 additions & 1 deletion products/mlengine/helpers/ansible/model_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
# Short names are given (and expected) by the API
# but are returned as full names.
def decode_response(response, module):
response['name'] = response['name'].split('/')[-1]
if 'name' in response and 'metadata' not in response:
response['name'] = response['name'].split('/')[-1]
return response
20 changes: 20 additions & 0 deletions products/mlengine/helpers/ansible/version_set_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2019 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Sets this version as default.
def set_default(module):
res = {'project': module.params['project'], 'model': replace_resource_dict(module.params['model'], 'name'), 'name': module.params['name']}
link = "https://ml.googleapis.com/v1/projects/{project}/models/{model}/versions/{name}:setDefault".format(**res)

auth = GcpSession(module, 'mlengine')
return_if_object(module, auth.post(link))