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

Model deployment support for model observability #492

Merged
merged 30 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
836a1d0
WIP
tiopramayudi Nov 2, 2023
b3a099c
Add PyFuncV3Model and update pyfuncserver to support publishing data …
tiopramayudi Nov 8, 2023
e870774
Move initialization of kafka producer after forking
tiopramayudi Nov 10, 2023
50faa38
Add row_ids information in prediction log
tiopramayudi Nov 10, 2023
ebc8c93
Add docstring
tiopramayudi Nov 10, 2023
5073d77
Update test
tiopramayudi Nov 10, 2023
5c9dab8
Update test
tiopramayudi Nov 14, 2023
8252d0f
Set request timestamp in the prediction log
tiopramayudi Nov 17, 2023
a9649c9
Remove unnecessary dockerfile
tiopramayudi Nov 17, 2023
1f74129
Add missing dependency
tiopramayudi Nov 17, 2023
0e6844b
Remove unncessary trailing space, unused import and wrong method invo…
tiopramayudi Nov 20, 2023
fce3c3f
Rename ml_predict to infer for PyFuncV3Model
tiopramayudi Nov 20, 2023
0b1e2dc
Remove unused abstract method in PyFuncV2Model
tiopramayudi Nov 21, 2023
8490629
Update pyfunc deployment that support model observability
tiopramayudi Nov 17, 2023
87b9e2f
Update model python client
tiopramayudi Nov 17, 2023
cbf6a4d
Fix deployment for pyfunc_v3
tiopramayudi Nov 21, 2023
6b681d6
Merge branch 'main' into model_obs_deployment
tiopramayudi Nov 22, 2023
a018c43
Change ml_predict into infer for PyFuncV3Model integration test
tiopramayudi Nov 22, 2023
15cc2fd
Rename migration file due to conflict
tiopramayudi Nov 22, 2023
1afb129
Merge branch 'main' into model_obs_deployment
tiopramayudi Nov 22, 2023
fa31b27
Add default value for column migration
tiopramayudi Nov 22, 2023
c31b2aa
Add validation
tiopramayudi Nov 28, 2023
ae0ab16
Add validation for deployment using model observability
tiopramayudi Nov 28, 2023
e97a045
Update helm chart version
tiopramayudi Nov 30, 2023
3e350e9
Merge branch 'main' into model_obs_deployment
tiopramayudi Nov 30, 2023
f2b4e0f
Update test for sdk
tiopramayudi Dec 1, 2023
ae42059
Fix calling deploy in client.py
tiopramayudi Dec 1, 2023
2f3af00
Fix lint error
tiopramayudi Dec 1, 2023
d23f59a
Add validation when publish pyfunc_v3 model from sdk
tiopramayudi Dec 4, 2023
85645f1
Update e2e test
tiopramayudi Dec 4, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/merlin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ jobs:
LOCAL_REGISTRY_PORT: 12345
LOCAL_REGISTRY: "dev.localhost"
INGRESS_HOST: "127.0.0.1.nip.io"
MERLIN_CHART_VERSION: 0.11.4
MERLIN_CHART_VERSION: 0.13.4
E2E_PYTHON_VERSION: "3.10.6"
K3S_VERSION: v1.26.7-k3s1
steps:
Expand Down
156 changes: 156 additions & 0 deletions api/api/validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package api

import (
"context"
"fmt"

"github.com/caraml-dev/merlin/config"
"github.com/caraml-dev/merlin/models"
"github.com/caraml-dev/merlin/pkg/protocol"
"github.com/caraml-dev/merlin/service"
"github.com/feast-dev/feast/sdk/go/protos/feast/core"
)

type requestValidator interface {
validate() error
}

type funcValidate struct {
f func() error
}

func newFuncValidate(f func() error) *funcValidate {
return &funcValidate{
f: f,
}
}

func (fv *funcValidate) validate() error {
return fv.f()
}

var supportedUPIModelTypes = map[string]bool{
models.ModelTypePyFunc: true,
models.ModelTypeCustom: true,
models.ModelTypePyFuncV3: true,
}

func isModelSupportUPI(model *models.Model) bool {
_, isSupported := supportedUPIModelTypes[model.Type]

return isSupported
}

func validateRequest(validators ...requestValidator) error {
for _, validator := range validators {
if err := validator.validate(); err != nil {
return err
}
}
return nil
}

func customModelValidation(model *models.Model, version *models.Version) requestValidator {
return newFuncValidate(func() error {
if model.Type == models.ModelTypeCustom {
if err := validateCustomPredictor(version); err != nil {
return err
}
}
return nil
})
}

func upiModelValidation(model *models.Model, endpointProtocol protocol.Protocol) requestValidator {
return newFuncValidate(func() error {
if !isModelSupportUPI(model) && endpointProtocol == protocol.UpiV1 {
return fmt.Errorf("%s model is not supported by UPI", model.Type)
}
return nil
})
}

func newVersionEndpointValidation(version *models.Version, envName string) requestValidator {
return newFuncValidate(func() error {
endpoint, ok := version.GetEndpointByEnvironmentName(envName)
if ok && (endpoint.IsRunning() || endpoint.IsServing()) {
return fmt.Errorf("there is `%s` deployment for the model version", endpoint.Status)
}
return nil
})
}

func deploymentQuotaValidation(ctx context.Context, model *models.Model, env *models.Environment, endpointSvc service.EndpointsService) requestValidator {
return newFuncValidate(func() error {
deployedModelVersionCount, err := endpointSvc.CountEndpoints(ctx, env, model)
if err != nil {
return fmt.Errorf("unable to count number of endpoints in env %s: %w", env.Name, err)
}

if deployedModelVersionCount >= config.MaxDeployedVersion {
return fmt.Errorf("max deployed endpoint reached. Max: %d Current: %d, undeploy existing endpoint before continuing", config.MaxDeployedVersion, deployedModelVersionCount)
}
return nil
})
}

func transformerValidation(
ctx context.Context,
endpoint *models.VersionEndpoint,
stdTransformerCfg config.StandardTransformerConfig,
feastCore core.CoreServiceClient) requestValidator {
return newFuncValidate(func() error {
if endpoint.Transformer != nil && endpoint.Transformer.Enabled {
err := validateTransformer(ctx, endpoint, stdTransformerCfg, feastCore)
if err != nil {
return fmt.Errorf("Error validating transformer: %w", err)
}
}
return nil
})
}

func updateRequestValidation(prev *models.VersionEndpoint, new *models.VersionEndpoint) requestValidator {
return newFuncValidate(func() error {
if prev.EnvironmentName != new.EnvironmentName {
return fmt.Errorf("updating environment is not allowed, previous: %s, new: %s", prev.EnvironmentName, new.EnvironmentName)
}

if prev.Status == models.EndpointPending {
return fmt.Errorf("updating endpoint status to %s is not allowed when the endpoint is currently in the pending state", new.Status)
}

if new.Status != prev.Status {
if prev.Status == models.EndpointServing {
return fmt.Errorf("updating endpoint status to %s is not allowed when the endpoint is currently in the serving state", new.Status)
}

if new.Status != models.EndpointRunning && new.Status != models.EndpointTerminated {
return fmt.Errorf("updating endpoint status to %s is not allowed", new.Status)
}
}
return nil
})
}

func deploymentModeValidation(prev *models.VersionEndpoint, new *models.VersionEndpoint) requestValidator {
return newFuncValidate(func() error {
// Should not allow changing the deployment mode of a pending/running/serving model for 2 reasons:
// * For "serving" models it's risky as, we can't guarantee graceful re-deployment
// * Kserve uses slightly different deployment resource naming under the hood and doesn't clean up the older deployment
if (prev.IsRunning() || prev.IsServing()) && new.DeploymentMode != "" &&
new.DeploymentMode != prev.DeploymentMode {
return fmt.Errorf("changing deployment type of a %s model is not allowed, please terminate it first", prev.Status)
}
return nil
})
}

func modelObservabilityValidation(endpoint *models.VersionEndpoint, model *models.Model) requestValidator {
return newFuncValidate(func() error {
if endpoint.EnableModelObservability && model.Type != models.ModelTypePyFuncV3 {
return fmt.Errorf("model type should be pyfunc_v3 if want to enable model observablity")
}
return nil
})
}
Loading
Loading