Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

AWS SageMaker

livebooks/aws/sagemaker.livemd

AWS SageMaker

Mix.install([
  {:aws, "~> 1.0"},
  {:ex_aws, "~> 2.5"},
  {:ex_aws_sts, "~> 2.3"},
  {:ex_aws_s3, "~> 2.5"},
  {:req, "~> 0.5"},
  {:nx, "~> 0.9"},
  {:stb_image, "~> 0.6"},
  {:hackney, "~> 1.20"},
  {:sweet_xml, "~> 0.7"},
  {:kino, "~> 0.14"}
])

認証

access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
region_input = Kino.Input.text("REGION")

[
  access_key_id_input,
  secret_access_key_input,
  region_input
]
|> Kino.Layout.grid(columns: 3)
client =
  AWS.Client.create(
    Kino.Input.read(access_key_id_input),
    Kino.Input.read(secret_access_key_input),
    Kino.Input.read(region_input)
  )
auth_config = [
  access_key_id: Kino.Input.read(access_key_id_input),
  secret_access_key: Kino.Input.read(secret_access_key_input),
  region: Kino.Input.read(region_input)
]

Kino.nothing()

アカウントID取得

account_id =
  ExAws.STS.get_caller_identity()
  |> ExAws.request!(auth_config)
  |> then(& &1.body.account)

ECRリポジトリー作成

region = Kino.Input.read(region_input)
image = "sagemaker-phoenix"
fullname = "#{account_id}.dkr.ecr.#{region}.amazonaws.com/#{image}:latest"
client
|> AWS.ECR.create_repository(%{
  "repositoryName" => image
})
client
|> AWS.ECR.describe_repositories(%{})
client
|> AWS.ECR.set_repository_policy(%{
  "repositoryName" => image,
  "policyText" =>
    Jason.encode!(%{
      "Statement" => [
        %{
          "Sid" => "ECR202201051440",
          "Effect" => "Allow",
          "Principal" => %{
            "AWS" => ["*"]
          },
          "Action" => "ecr:*"
        }
      ]
    })
})

ECR リポジトリーへのイメージプッシュ

token =
  client
  |> AWS.ECR.get_authorization_token(%{})
  |> elem(1)
  |> Map.get("authorizationData")
  |> Enum.at(0)
  |> Map.get("authorizationToken")
[username, password] =
  token
  |> Base.decode64!()
  |> String.split(":")
System.cmd(
  "docker",
  [
    "login",
    "--username",
    username,
    "--password",
    password,
    fullname
  ]
)
System.cmd(
  "docker",
  [
    "build",
    "-t",
    image,
    "/sagemaker/serve"
  ]
)
System.cmd(
  "docker",
  [
    "tag",
    image,
    fullname
  ]
)
System.cmd(
  "docker",
  [
    "push",
    fullname
  ]
)
client
|> AWS.ECR.list_images(%{
  "repositoryName" => image
})
|> elem(1)
|> Map.get("imageIds")
|> Kino.DataTable.new()

S3へのモデルファイルアップロード

アップロード先の指定

ExAws.S3.list_buckets()
|> ExAws.request!(auth_config)
|> then(& &1.body.buckets)
|> Kino.DataTable.new()
bucket_name_input = Kino.Input.text("BUCKET_NAME")
bucket_name = Kino.Input.read(bucket_name_input)
model_prefix = "models/"

モデルファイルの圧縮

models_path = "/sagemaker/serve/models"

filenames =
  models_path
  |> File.ls!()
  |> Enum.map(fn filename ->
    {
      to_charlist(filename),
      to_charlist(Path.join(models_path, filename))
    }
  end)
tar_filename = "models.tar.gz"

:erl_tar.create(tar_filename, filenames, [:compressed])

アップロードの実行

tar_filename
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(bucket_name, model_prefix <> tar_filename)
|> ExAws.request!(auth_config)
bucket_name
|> ExAws.S3.list_objects(prefix: model_prefix)
|> ExAws.request!(auth_config)
|> then(&amp; &amp;1.body.contents)
|> Kino.DataTable.new()

IAM ロールの定義

client
|> AWS.IAM.create_role(%{
  "RoleName" => "sagemaker-phoenix-role",
  "AssumeRolePolicyDocument" =>
    Jason.encode!(%{
      "Statement" => [
        %{
          "Sid" => "STS202201051440",
          "Effect" => "Allow",
          "Principal" => %{
            "Service" => ["sagemaker.amazonaws.com"]
          },
          "Action" => "sts:AssumeRole"
        }
      ]
    })
})
client
|> AWS.IAM.create_policy(%{
  "PolicyName" => "sagemaker-phoenix-role-policy",
  "PolicyDocument" =>
    Jason.encode!(%{
      "Version" => "2012-10-17",
      "Statement" => [
        %{
          "Effect" => "Allow",
          "Action" => [
            "cloudwatch:PutMetricData",
            "logs:CreateLogStream",
            "logs:PutLogEvents",
            "logs:CreateLogGroup",
            "logs:DescribeLogStreams",
            "ecr:GetAuthorizationToken"
          ],
          "Resource" => ["*"]
        },
        %{
          "Effect" => "Allow",
          "Action" => [
            "s3:GetObject"
          ],
          "Resource" => [
            "arn:aws:s3:::#{bucket_name}/*"
          ]
        },
        %{
          "Effect" => "Allow",
          "Action" => [
            "ecr:BatchCheckLayerAvailability",
            "ecr:GetDownloadUrlForLayer",
            "ecr:BatchGetImage"
          ],
          "Resource" => [
            "arn:aws:ecr:::repository/#{image}"
          ]
        }
      ]
    })
})
client
|> AWS.IAM.get_policy_version(%{
  "PolicyArn" => "arn:aws:iam::#{account_id}:policy/sagemaker-phoenix-role-policy",
  "VersionId" => "v1"
})
|> elem(1)
|> then(&amp; &amp;1["GetPolicyVersionResponse"]["GetPolicyVersionResult"]["PolicyVersion"]["Document"])
|> URI.decode()
|> Jason.decode!()
client
|> AWS.IAM.attach_role_policy(%{
  "RoleName" => "sagemaker-phoenix-role",
  "PolicyArn" => "arn:aws:iam::#{account_id}:policy/sagemaker-phoenix-role-policy"
})

SageMaker モデル作成

client
|> AWS.SageMaker.create_model(%{
  "ModelName" => "sagemaker-phoenix-model",
  "ExecutionRoleArn" => "arn:aws:iam::#{account_id}:role/sagemaker-phoenix-role",
  "PrimaryContainer" => %{
    "Image" => fullname,
    "ModelDataUrl" => "s3://#{bucket_name}/#{model_prefix}#{tar_filename}"
  }
})

SageMaker エンドポイント設定作成

client
|> AWS.SageMaker.create_endpoint_config(%{
  "EndpointConfigName" => "sagemaker-phoenix-endpoint-config",
  "ProductionVariants" => [
    %{
      "VariantName" => "variant-1",
      "ModelName" => "sagemaker-phoenix-model",
      "InstanceType" => "ml.t2.medium",
      "InitialInstanceCount" => 1,
      "InitialVariantWeight" => 1
    }
  ]
})

SageMaker エンドポイント作成

client
|> AWS.SageMaker.create_endpoint(%{
  "EndpointName" => "sagemaker-phoenix-endpoint",
  "EndpointConfigName" => "sagemaker-phoenix-endpoint-config"
})
client
|> AWS.SageMaker.describe_endpoint(%{
  "EndpointName" => "sagemaker-phoenix-endpoint"
})
|> elem(1)
|> Map.get("EndpointStatus")

SageMaker エンドポイント呼出

binary =
  "https://raw.githubusercontent.com/pjreddie/darknet/master/data/dog.jpg"
  |> Req.get!()
  |> Map.get(:body)
binary
|> StbImage.read_binary!()
|> StbImage.to_nx()
|> Kino.Image.new()
client
|> AWS.SageMakerRuntime.invoke_endpoint(
  "sagemaker-phoenix-endpoint",
  %{
    "Body" => binary,
    "ContentType" => "image/jpeg"
  }
)
|> elem(1)
|> Map.get("Body")
|> Jason.decode!()
|> Map.get("predictions")
|> Kino.DataTable.new()
image_input = Kino.Input.image("INPUT_IMAGE", format: :jpeg)
binary =
  image_input
  |> Kino.Input.read()
  |> Map.get(:file_ref)
  |> Kino.Input.file_path()
  |> File.read!()

client
|> AWS.SageMakerRuntime.invoke_endpoint(
  "sagemaker-phoenix-endpoint",
  %{
    "Body" => binary,
    "ContentType" => "image/jpeg"
  }
)
|> elem(1)
|> Map.get("Body")
|> Jason.decode!()
|> Map.get("predictions")
|> Kino.DataTable.new()

SageMaker エンドポイント削除

client
|> AWS.SageMaker.delete_endpoint(%{
  "EndpointName" => "sagemaker-phoenix-endpoint"
})
client
|> AWS.SageMaker.delete_endpoint_config(%{
  "EndpointConfigName" => "sagemaker-phoenix-endpoint-config"
})
client
|> AWS.SageMaker.delete_model(%{
  "ModelName" => "sagemaker-phoenix-model"
})

IAM ロール削除

client
|> AWS.IAM.detach_role_policy(%{
  "RoleName" => "sagemaker-phoenix-role",
  "PolicyArn" => "arn:aws:iam::#{account_id}:policy/sagemaker-phoenix-role-policy"
})
client
|> AWS.IAM.delete_policy(%{
  "PolicyArn" => "arn:aws:iam::#{account_id}:policy/sagemaker-phoenix-role-policy"
})
client
|> AWS.IAM.delete_role(%{
  "RoleName" => "sagemaker-phoenix-role"
})

ECR リポジトリー削除

image_digest_list =
  client
  |> AWS.ECR.list_images(%{
    "repositoryName" => image
  })
  |> elem(1)
  |> Map.get("imageIds")
  |> Enum.map(&amp;Map.take(&amp;1,["imageDigest"]))
client
|> AWS.ECR.batch_delete_image(%{
  "repositoryName" => image,
  "imageIds" => image_digest_list
})
client
|> AWS.ECR.list_images(%{
  "repositoryName" => image
})
|> elem(1)
|> Map.get("imageIds")
|> Kino.DataTable.new()
client
|> AWS.ECR.delete_repository(%{
  "repositoryName" => image
})

モデルファイル削除

ExAws.S3.delete_object(bucket_name, model_prefix <> tar_filename)
|> ExAws.request!(auth_config)