From 1f7412642ec43cf4699a531be5c7b96781aa59d5 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Tue, 12 Dec 2023 07:51:39 -0700 Subject: [PATCH 01/17] Add distributed-ml-training blueprint --- .../distributed-ml-training/README.md | 29 + .../distributed-ml-training/main.tf | 501 ++++++++++++++++++ .../distributed-ml-training/outputs.tf | 42 ++ .../distributed-ml-training/variables.tf | 0 .../distributed-ml-training/versions.tf | 10 + 5 files changed, 582 insertions(+) create mode 100644 terraform/ec2-examples/distributed-ml-training/README.md create mode 100644 terraform/ec2-examples/distributed-ml-training/main.tf create mode 100644 terraform/ec2-examples/distributed-ml-training/outputs.tf create mode 100644 terraform/ec2-examples/distributed-ml-training/variables.tf create mode 100644 terraform/ec2-examples/distributed-ml-training/versions.tf diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md new file mode 100644 index 00000000..a98de945 --- /dev/null +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -0,0 +1,29 @@ +# ECS machine learning distributed training + +This solution blueprint creates the infrastructure to run distributed training jobs using a Ray cluster and Pytorch. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. + +## Cost warning! + +By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU and multi-node distributed training, but **can increase costs considerably over time**. You can modify this blueprint to use g5.xlarge (with one GPU) instead from the local variable **instance_type_workers** - if you change the instance type, you need to also modify the worker task definition to use 1 GPU instead of 4 (see **resource_requirements** and container command parameter **--num-gpus**) + +## Deployment + +```shell +terraform init +terraform plan +terraform apply +``` + +## Components + +* Service discovery using AWS Cloud Map: The head node is registerer to a private DNS using loca zones via cloud map. This allow workers to discover the head service and join the cluster +* 2 autoscaling groups: One for the head instance and other for the worker instances +* ECS service definition: + * Task security group, task role and task execution role and + * Service discovery ARN is used in the service definition. ECS will automatically manage the registration and deregistration of tasks to this service discovery registry. + * Tasks for this service will be deployed in private subnet + * Task definitions with GPU resource requirements + +## Support + +Please open an issue for questions or unexpected behaviour \ No newline at end of file diff --git a/terraform/ec2-examples/distributed-ml-training/main.tf b/terraform/ec2-examples/distributed-ml-training/main.tf new file mode 100644 index 00000000..7fcfee34 --- /dev/null +++ b/terraform/ec2-examples/distributed-ml-training/main.tf @@ -0,0 +1,501 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} +data "aws_caller_identity" "current" {} + +locals { + name = "ecs-demo-distributed-ml-training" + region = "us-east-1" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 1) + instance_type_workers = "g5.12xlarge" + instance_type_head = "m5.xlarge" + ray_head_container_image = "docker.io/rayproject/ray-ml:2.7.1.artur.c9f4c6-py38" + ray_worker_container_image = "docker.io/rayproject/ray-ml:2.7.1.artur.c9f4c6-py38-gpu" + + user_data_head = <<-EOT + #!/bin/bash + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + EOF + EOT + + user_data_workers = <<-EOT + #!/bin/bash + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + EOF + echo "ip_resolve=4" >> /etc/yum.conf + EOT + + tags = { + Blueprint = local.name + GithubRepo = "github.com/aws-ia/ecs-blueprints" + } +} + +################################################################################ +# ECS Blueprint +################################################################################ + +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "~> 5.0" + + cluster_name = local.name + # Capacity provider - autoscaling groups + default_capacity_provider_use_fargate = false + autoscaling_capacity_providers = { + distributed_ml_training_head = { + auto_scaling_group_arn = module.autoscaling_head.autoscaling_group_arn + + managed_scaling = { + maximum_scaling_step_size = 1 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 1 + base = 1 + } + }, + distributed_ml_training_workers = { + auto_scaling_group_arn = module.autoscaling_workers.autoscaling_group_arn + managed_scaling = { + maximum_scaling_step_size = 1 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + }, + } + + # Shared task execution role + create_task_exec_iam_role = false + tags = local.tags +} + +resource "aws_service_discovery_private_dns_namespace" "this" { + name = "default.${local.name}.local" + description = "Service discovery namespace.clustername.local" + vpc = module.vpc.vpc_id + tags = local.tags +} + +resource "aws_service_discovery_service" "this" { + name = "head" + dns_config { + namespace_id = aws_service_discovery_private_dns_namespace.this.id + dns_records { + ttl = 300 + type = "A" + } + routing_policy = "MULTIVALUE" + } +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.2.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + map_public_ip_on_launch = false + + # Manage so we can name + manage_default_network_acl = true + default_network_acl_tags = { Name = "${local.name}-default" } + manage_default_route_table = true + default_route_table_tags = { Name = "${local.name}-default" } + manage_default_security_group = true + default_security_group_tags = { Name = "${local.name}-default" } + + tags = local.tags +} + +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +data "aws_ssm_parameter" "ecs_gpu_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended" +} + +module "autoscaling_head" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + name = "${local.name}-head" + + image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] + instance_type = local.instance_type_head + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(local.user_data_head) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonSSMManagedEC2InstanceDefaultPolicy = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" + AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + } + + vpc_zone_identifier = module.vpc.private_subnets + health_check_type = "EC2" + min_size = 1 + max_size = 1 + desired_capacity = 1 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + + tags = local.tags +} + +module "autoscaling_workers" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + name = "${local.name}-workers" + + #image_id = data.aws_ssm_parameter.ecs_bottlerocket_gpu_optimized_ami.value + image_id = jsondecode(data.aws_ssm_parameter.ecs_gpu_optimized_ami.value)["image_id"] + instance_type = local.instance_type_workers + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(local.user_data_workers) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role", + AmazonSSMManagedEC2InstanceDefaultPolicy = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" + } + + vpc_zone_identifier = module.vpc.private_subnets + health_check_type = "EC2" + min_size = 2 + max_size = 2 + desired_capacity = 2 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + block_device_mappings = [ + { + # Root volume + device_name = "/dev/xvda" + no_device = 0 + ebs = { + delete_on_termination = true + encrypted = false + volume_size = 50 + volume_type = "gp2" + } + } + ] + + tags = local.tags +} + +module "autoscaling_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = local.name + description = "Autoscaling group security group" + vpc_id = module.vpc.vpc_id + ingress_cidr_blocks = [module.vpc.vpc_cidr_block] + + ingress_rules = ["all-all"] + + egress_rules = ["all-all"] + + tags = local.tags +} + + +module "ecs_service_head" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "~> 5.0" + + name = "distributed_ml_training_head_service" + desired_count = 1 + cluster_arn = module.ecs_cluster.arn + enable_autoscaling = false + memory = 10240 + cpu = 3072 + # Task Definition + + requires_compatibilities = ["EC2"] + capacity_provider_strategy = { + default = { + capacity_provider = "distributed_ml_training_head" # needs to match name of capacity provider + weight = 1 + base = 1 + } + } + + task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn + tasks_iam_role_name = "dt-role-tasks" + tasks_iam_role_description = "Tasks IAM role for ${local.name}" + tasks_iam_role_policies = { + AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + } + create_task_exec_iam_role = false + enable_execute_command = false + deployment_minimum_healthy_percent = 0 + container_definitions = { + + ray_head = { + image = local.ray_head_container_image + user = 1000 + readonly_root_filesystem = false + cpu = 3072 + memory = 10240 + memory_reservation = 10240 + command = ["/bin/bash","-lc","--","ulimit -n 65536; ray start --head --dashboard-host=0.0.0.0 --metrics-export-port=8080 --num-cpus=0 --memory=10737418240 --block"] + linux_parameters = { + sharedMemorySize = 20480 + } + mount_points = [{ + sourceVolume = "ray_results" + containerPath = "/home/ray/ray_results" + readOnly = false + }] + } + } + volume = { + "ray_results" ={ + efs_volume_configuration = { + file_system_id = module.efs.id, + root_directory = "/" + transit_encryption= "ENABLED", + authorization_config = { + access_point_id = module.efs.access_points.ray_results.id + iam="ENABLED" + } + } + } + } + + service_registries = { + registry_arn = aws_service_discovery_service.this.arn + } + + network_mode = "awsvpc" + subnet_ids = module.vpc.private_subnets + security_group_rules = { + ingress_private_ips = { + type = "ingress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["10.0.0.0/8"] + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + + tags = local.tags +} + + +module "ecs_service_workers" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "~> 5.0" + deployment_minimum_healthy_percent = 0 + name = "distributed_ml_training_worker_service" + desired_count = 2 + cluster_arn = module.ecs_cluster.arn + enable_autoscaling = false + memory = 189440 + cpu = 10240 + # Task Definition + + requires_compatibilities = ["EC2"] + capacity_provider_strategy = { + default = { + capacity_provider = "distributed_ml_training_workers" # needs to match name of capacity provider + weight = 1 + base = 1 + } + } + + task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn + tasks_iam_role_name = "dt-role-tasks" + tasks_iam_role_description = "Tasks IAM role for ${local.name}" + tasks_iam_role_policies = { + AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + } + create_task_exec_iam_role = false + enable_execute_command = false + + container_definitions = { + ray_work = { + image = local.ray_worker_container_image + user = 1000 + readonly_root_filesystem = false + cpu = 10240 + memory = 189440 + memory_reservation = 189440 + command = ["/bin/bash","-lc","--","ray start --block --num-cpus=10 --num-gpus=4 --address=head.default.ecs-demo-distributed-ml-training.local:6379 --metrics-export-port=8080 --memory=198642237440"] + linux_parameters = { + sharedMemorySize = 20480 + } + resource_requirements = [{ + type="GPU" + value=4 + }] + mount_points = [{ + sourceVolume = "ray_results" + containerPath = "/home/ray/ray_results" + readOnly = false + }] + } + } + # We are using network=host because there will be a single container in each host with GPUs. There is less overhead when using a single container with + # access to all 4 GPUs available in g5.12xlarge than 4 containers with 1 GPU each. + network_mode = "host" + + volume = { + "ray_results" ={ + efs_volume_configuration = { + file_system_id = module.efs.id, + root_directory = "/" + transit_encryption= "ENABLED", + authorization_config = { + access_point_id = module.efs.access_points.ray_results.id + iam="ENABLED" + } + } + } + } + tags = local.tags +} + + +################################################################################ +# Shared storage - EFS +################################################################################ + + +module "efs" { + source = "terraform-aws-modules/efs/aws" + + # File system + name = "distributed-storage-shared" + creation_token = "distributed-storage-shared" + encrypted = true + attach_policy = false + + lifecycle_policy = { + transition_to_ia = "AFTER_30_DAYS" + } + + # Mount targets / security group + mount_targets = { + (local.azs[0]) = { + subnet_id = module.vpc.private_subnets[0] + } + } + + # Access point + access_points = { + ray_results = { + name = "ray_results" + posix_user = { + uid = 1000 + gid = 100 + } + root_directory = { + path = "/ray_results" + + creation_info ={ + owner_uid = 1000 + owner_gid = 100 + permissions = "755" + } + } + + tags = local.tags + } + } + security_group_description = "EFS distributed training security group" + security_group_vpc_id = module.vpc.vpc_id + security_group_rules = { + vpc = { + # relying on the defaults provdied for EFS/NFS (2049/TCP + ingress) + description = "NFS ingress from VPC private subnets" + cidr_blocks = ["10.0.0.0/8"] + } + } + + tags = local.tags +} + + +resource "aws_iam_role" "task_execution_role" { + name = "distributed_training_task_execution_role" + + # Terraform's "jsonencode" function converts a + # Terraform expression result to valid JSON syntax. + assume_role_policy = jsonencode({ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ECSTasksAssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "aws:SourceAccount": data.aws_caller_identity.current.account_id + }, + "ArnLike": { + "aws:SourceArn": "arn:aws:ecs:us-east-1:${data.aws_caller_identity.current.account_id}:*" + } + } + } + ] +}) + managed_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + ] + + tags = local.tags +} diff --git a/terraform/ec2-examples/distributed-ml-training/outputs.tf b/terraform/ec2-examples/distributed-ml-training/outputs.tf new file mode 100644 index 00000000..2c221ceb --- /dev/null +++ b/terraform/ec2-examples/distributed-ml-training/outputs.tf @@ -0,0 +1,42 @@ +################################################################################ +# VPC +################################################################################ + +output "vpc_id" { + description = "The ID of the VPC" + value = module.vpc.vpc_id +} + +output "private_subnets" { + description = "A list of private subnets for the client app" + value = module.vpc.private_subnets +} + +output "private_subnets_cidr_blocks" { + description = "A list of private subnets CIDRs" + value = module.vpc.private_subnets_cidr_blocks +} + +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs_cluster.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs_cluster.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs_cluster.name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs_cluster.cluster_capacity_providers +} diff --git a/terraform/ec2-examples/distributed-ml-training/variables.tf b/terraform/ec2-examples/distributed-ml-training/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/terraform/ec2-examples/distributed-ml-training/versions.tf b/terraform/ec2-examples/distributed-ml-training/versions.tf new file mode 100644 index 00000000..35402be4 --- /dev/null +++ b/terraform/ec2-examples/distributed-ml-training/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.6" + } + } +} From 3ebc805cbeba90197c0506b42e0c4afd8c1d7796 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Tue, 12 Dec 2023 08:40:47 -0700 Subject: [PATCH 02/17] Fix docs --- .../distributed-ml-training/README.md | 128 ++++++++++++++++-- 1 file changed, 119 insertions(+), 9 deletions(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index a98de945..35f7d291 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -4,7 +4,17 @@ This solution blueprint creates the infrastructure to run distributed training j ## Cost warning! -By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU and multi-node distributed training, but **can increase costs considerably over time**. You can modify this blueprint to use g5.xlarge (with one GPU) instead from the local variable **instance_type_workers** - if you change the instance type, you need to also modify the worker task definition to use 1 GPU instead of 4 (see **resource_requirements** and container command parameter **--num-gpus**) +By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU and multi-node distributed training, but **can increase costs considerably over time**. You can modify this blueprint to use g5.xlarge (with one GPU) instead from the local variable **instance_type_workers** - if you change the instance type, you need to also modify the worker task definition to use 1 GPU instead of 4 (see **resource_requirements** and container command parameter **--num-gpus**) and the example training script outline below (see **num_workers** parameter) + +## Components + +* Service discovery using AWS Cloud Map: The head node is registerer to a private DNS using loca zones via cloud map. This allow workers to discover the head service and join the cluster +* 2 autoscaling groups: One for the head instance and other for the worker instances +* ECS service definition: + * Task security group, task role and task execution role and + * Service discovery ARN is used in the service definition. ECS will automatically manage the registration and deregistration of tasks to this service discovery registry. + * Tasks for this service will be deployed in single private subnet to avoid AZ data transfer costs + * Task definitions with GPU resource requirements ## Deployment @@ -14,15 +24,115 @@ terraform plan terraform apply ``` -## Components +## Example training script: training the resnet18 model with the FashionMNIST dataset + +Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - sagemaker notebooks provide a better user experience to run training jobs in python than using the bash shell + +```bash +HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ + --filters 'Name=tag:Name,Values=ecs-demo-distributed-ml-training-head' \ + --query 'Reservations[*].Instances[*].InstanceId' --output text) + +aws ssm start-session --target $HEAD_INSTANCE_ID +CONTAINER_ID=$(sudo docker ps -qf "name=.*rayhead.*") +sudo docker exec -it $CONTAINER_ID bash +``` + +```python +export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training +python + +# import required torch and ray libraries +import tempfile +import torch +from torchvision.models import resnet18 +from torchvision.datasets import FashionMNIST +from torchvision.transforms import ToTensor, Normalize, Compose +from torch.utils.data import DataLoader +from torch.optim import Adam +from torch.nn import CrossEntropyLoss +from ray.train.torch import TorchTrainer +from ray.train import ScalingConfig, Checkpoint +import ray +from pprint import pprint +import time +from pprint import pprint + +# Connect to the Ray cluster +ray.init() + +# Download the data in the shared storage +transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) +train_data = FashionMNIST(root='/home/ray/ray_results/data', + train=True, download=True, + transform=transform) + +# Define the training function that the distributed processes will run +def train_func(config): + import os + # The NVIDIA Collective Communications Library (NCCL) implements multi-GPU + # and multi-node communication primitives optimized for NVIDIA GPUs. + # Since containers can have multiple interfaces, we explicitly set which one + # NCCL should use. + os.environ['NCCL_SOCKET_IFNAME']='eth0' + #os.environ['NCCL_DEBUG']='INFO' Uncomment this line if you want to debug NCCL + # Set up the model + model = resnet18(num_classes=10) + model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), + stride=(2, 2), + padding=(3, 3), + bias=False) + # Prepare model for distributed training + model = ray.train.torch.prepare_model(model) + # Setup loss and optimizer + criterion = CrossEntropyLoss() + optimizer = Adam(model.parameters(), lr=0.001) + # Retrieve the data from the shared storage. + transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) + train_data = FashionMNIST(root='/home/ray/ray_results/data', train=True, download=False, transform=transform) + train_loader = DataLoader(train_data, batch_size=128, shuffle=True) + # Prepare dataloader for distributed training + train_loader = ray.train.torch.prepare_data_loader(train_loader) + # Define training loop + for epoch in range(10): + start = time.time() + for images, labels in train_loader: + outputs = model(images) + loss = criterion(outputs, labels) + optimizer.zero_grad() + loss.backward() + optimizer.step() + print(f"[GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()}] | [Epoch {epoch} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") + # Only save checkpoint after the last epoch + if epoch == 9: + checkpoint_dir = tempfile.gettempdir() + checkpoint_path = checkpoint_dir + "/model.checkpoint" + torch.save(model.state_dict(), checkpoint_path) + # Report metrics and checkpoint to Ray. + ray.train.report({"loss": loss.item()},checkpoint=Checkpoint.from_directory(checkpoint_dir)) + +# The scaling config defines how many workers +# In this case is equal to the total GPU count +scaling_config = ScalingConfig(num_workers=8, use_gpu=True) + +# Create the trainer instance +trainer = TorchTrainer(train_func, + scaling_config=scaling_config) + +# Run the training +result = trainer.fit() + +# Print the results of the training +print(result) + +``` + +## Clean up + +```shell +terraform destroy +``` -* Service discovery using AWS Cloud Map: The head node is registerer to a private DNS using loca zones via cloud map. This allow workers to discover the head service and join the cluster -* 2 autoscaling groups: One for the head instance and other for the worker instances -* ECS service definition: - * Task security group, task role and task execution role and - * Service discovery ARN is used in the service definition. ECS will automatically manage the registration and deregistration of tasks to this service discovery registry. - * Tasks for this service will be deployed in private subnet - * Task definitions with GPU resource requirements ## Support From d15e67450efd4f677cb2c56f3e741cd3d730d388 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Tue, 12 Dec 2023 08:45:07 -0700 Subject: [PATCH 03/17] Fix docs --- terraform/ec2-examples/distributed-ml-training/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 35f7d291..7ca6bc37 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -1,6 +1,6 @@ # ECS machine learning distributed training -This solution blueprint creates the infrastructure to run distributed training jobs using a Ray cluster and Pytorch. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. +This solution blueprint creates the infrastructure to run distributed training jobs using a Ray cluster and PyTorch. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. ## Cost warning! @@ -24,9 +24,9 @@ terraform plan terraform apply ``` -## Example training script: training the resnet18 model with the FashionMNIST dataset +## Example: training the resnet18 model with the FashionMNIST dataset -Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - sagemaker notebooks provide a better user experience to run training jobs in python than using the bash shell +Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - [https://aws.amazon.com/sagemaker/notebooks/](SageMaker notebooks) provide a better user experience to run training jobs in python than using the bash shell ```bash HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ From 20f758785d016a1e78dc9018d74436b3aac6b294 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Tue, 12 Dec 2023 09:19:37 -0700 Subject: [PATCH 04/17] Fix format --- .../distributed-ml-training/README.md | 34 +-- .../distributed-ml-training/main.tf | 195 +++++++++--------- 2 files changed, 115 insertions(+), 114 deletions(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 7ca6bc37..16502f15 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -1,6 +1,6 @@ # ECS machine learning distributed training -This solution blueprint creates the infrastructure to run distributed training jobs using a Ray cluster and PyTorch. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. +This solution blueprint creates the infrastructure to run distributed training jobs using a Ray cluster and PyTorch. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. ## Cost warning! @@ -11,7 +11,7 @@ By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU * Service discovery using AWS Cloud Map: The head node is registerer to a private DNS using loca zones via cloud map. This allow workers to discover the head service and join the cluster * 2 autoscaling groups: One for the head instance and other for the worker instances * ECS service definition: - * Task security group, task role and task execution role and + * Task security group, task role and task execution role and * Service discovery ARN is used in the service definition. ECS will automatically manage the registration and deregistration of tasks to this service discovery registry. * Tasks for this service will be deployed in single private subnet to avoid AZ data transfer costs * Task definitions with GPU resource requirements @@ -21,7 +21,7 @@ By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU ```shell terraform init terraform plan -terraform apply +terraform apply ``` ## Example: training the resnet18 model with the FashionMNIST dataset @@ -63,24 +63,24 @@ ray.init() # Download the data in the shared storage transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) -train_data = FashionMNIST(root='/home/ray/ray_results/data', - train=True, download=True, +train_data = FashionMNIST(root='/home/ray/ray_results/data', + train=True, download=True, transform=transform) # Define the training function that the distributed processes will run def train_func(config): import os - # The NVIDIA Collective Communications Library (NCCL) implements multi-GPU - # and multi-node communication primitives optimized for NVIDIA GPUs. + # The NVIDIA Collective Communications Library (NCCL) implements multi-GPU + # and multi-node communication primitives optimized for NVIDIA GPUs. # Since containers can have multiple interfaces, we explicitly set which one # NCCL should use. - os.environ['NCCL_SOCKET_IFNAME']='eth0' + os.environ['NCCL_SOCKET_IFNAME']='eth0' #os.environ['NCCL_DEBUG']='INFO' Uncomment this line if you want to debug NCCL # Set up the model model = resnet18(num_classes=10) - model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), - stride=(2, 2), - padding=(3, 3), + model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), + stride=(2, 2), + padding=(3, 3), bias=False) # Prepare model for distributed training model = ray.train.torch.prepare_model(model) @@ -105,20 +105,20 @@ def train_func(config): print(f"[GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()}] | [Epoch {epoch} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") # Only save checkpoint after the last epoch if epoch == 9: - checkpoint_dir = tempfile.gettempdir() + checkpoint_dir = tempfile.gettempdir() checkpoint_path = checkpoint_dir + "/model.checkpoint" torch.save(model.state_dict(), checkpoint_path) # Report metrics and checkpoint to Ray. ray.train.report({"loss": loss.item()},checkpoint=Checkpoint.from_directory(checkpoint_dir)) -# The scaling config defines how many workers -# In this case is equal to the total GPU count +# The scaling config defines how many workers +# In this case is equal to the total GPU count scaling_config = ScalingConfig(num_workers=8, use_gpu=True) # Create the trainer instance -trainer = TorchTrainer(train_func, +trainer = TorchTrainer(train_func, scaling_config=scaling_config) - + # Run the training result = trainer.fit() @@ -136,4 +136,4 @@ terraform destroy ## Support -Please open an issue for questions or unexpected behaviour \ No newline at end of file +Please open an issue for questions or unexpected behaviour diff --git a/terraform/ec2-examples/distributed-ml-training/main.tf b/terraform/ec2-examples/distributed-ml-training/main.tf index 7fcfee34..3a32454c 100644 --- a/terraform/ec2-examples/distributed-ml-training/main.tf +++ b/terraform/ec2-examples/distributed-ml-training/main.tf @@ -9,11 +9,11 @@ locals { name = "ecs-demo-distributed-ml-training" region = "us-east-1" - vpc_cidr = "10.0.0.0/16" - azs = slice(data.aws_availability_zones.available.names, 0, 1) - instance_type_workers = "g5.12xlarge" - instance_type_head = "m5.xlarge" - ray_head_container_image = "docker.io/rayproject/ray-ml:2.7.1.artur.c9f4c6-py38" + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 1) + instance_type_workers = "g5.12xlarge" + instance_type_head = "m5.xlarge" + ray_head_container_image = "docker.io/rayproject/ray-ml:2.7.1.artur.c9f4c6-py38" ray_worker_container_image = "docker.io/rayproject/ray-ml:2.7.1.artur.c9f4c6-py38-gpu" user_data_head = <<-EOT @@ -28,7 +28,7 @@ locals { cat <<'EOF' >> /etc/ecs/ecs.config ECS_CLUSTER=${local.name} EOF - echo "ip_resolve=4" >> /etc/yum.conf + echo "ip_resolve=4" >> /etc/yum.conf EOT tags = { @@ -50,7 +50,7 @@ module "ecs_cluster" { default_capacity_provider_use_fargate = false autoscaling_capacity_providers = { distributed_ml_training_head = { - auto_scaling_group_arn = module.autoscaling_head.autoscaling_group_arn + auto_scaling_group_arn = module.autoscaling_head.autoscaling_group_arn managed_scaling = { maximum_scaling_step_size = 1 @@ -65,10 +65,11 @@ module "ecs_cluster" { } }, distributed_ml_training_workers = { - auto_scaling_group_arn = module.autoscaling_workers.autoscaling_group_arn + auto_scaling_group_arn = module.autoscaling_workers.autoscaling_group_arn managed_scaling = { maximum_scaling_step_size = 1 minimum_scaling_step_size = 1 + _scaling_step_size = 1 status = "ENABLED" target_capacity = 60 } @@ -77,18 +78,18 @@ module "ecs_cluster" { # Shared task execution role create_task_exec_iam_role = false - tags = local.tags + tags = local.tags } resource "aws_service_discovery_private_dns_namespace" "this" { name = "default.${local.name}.local" description = "Service discovery namespace.clustername.local" vpc = module.vpc.vpc_id - tags = local.tags + tags = local.tags } resource "aws_service_discovery_service" "this" { - name = "head" + name = "head" dns_config { namespace_id = aws_service_discovery_private_dns_namespace.this.id dns_records { @@ -156,9 +157,9 @@ module "autoscaling_head" { iam_role_name = local.name iam_role_description = "ECS role for ${local.name}" iam_role_policies = { - AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" AmazonSSMManagedEC2InstanceDefaultPolicy = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" - AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" } vpc_zone_identifier = module.vpc.private_subnets @@ -193,7 +194,7 @@ module "autoscaling_workers" { iam_role_name = local.name iam_role_description = "ECS role for ${local.name}" iam_role_policies = { - AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role", + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role", AmazonSSMManagedEC2InstanceDefaultPolicy = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" } @@ -249,10 +250,10 @@ module "ecs_service_head" { desired_count = 1 cluster_arn = module.ecs_cluster.arn enable_autoscaling = false - memory = 10240 - cpu = 3072 + memory = 10240 + cpu = 3072 # Task Definition - + requires_compatibilities = ["EC2"] capacity_provider_strategy = { default = { @@ -261,45 +262,45 @@ module "ecs_service_head" { base = 1 } } - - task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn + + task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn tasks_iam_role_name = "dt-role-tasks" tasks_iam_role_description = "Tasks IAM role for ${local.name}" tasks_iam_role_policies = { AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" } - create_task_exec_iam_role = false - enable_execute_command = false + create_task_exec_iam_role = false + enable_execute_command = false deployment_minimum_healthy_percent = 0 container_definitions = { - + ray_head = { image = local.ray_head_container_image - user = 1000 + user = 1000 readonly_root_filesystem = false - cpu = 3072 - memory = 10240 - memory_reservation = 10240 - command = ["/bin/bash","-lc","--","ulimit -n 65536; ray start --head --dashboard-host=0.0.0.0 --metrics-export-port=8080 --num-cpus=0 --memory=10737418240 --block"] + cpu = 3072 + memory = 10240 + memory_reservation = 10240 + command = ["/bin/bash", "-lc", "--", "ulimit -n 65536; ray start --head --dashboard-host=0.0.0.0 --metrics-export-port=8080 --num-cpus=0 --memory=10737418240 --block"] linux_parameters = { sharedMemorySize = 20480 } mount_points = [{ - sourceVolume = "ray_results" - containerPath = "/home/ray/ray_results" - readOnly = false + sourceVolume = "ray_results" + containerPath = "/home/ray/ray_results" + readOnly = false }] } } volume = { - "ray_results" ={ + "ray_results" = { efs_volume_configuration = { - file_system_id = module.efs.id, - root_directory = "/" - transit_encryption= "ENABLED", + file_system_id = module.efs.id, + root_directory = "/" + transit_encryption = "ENABLED", authorization_config = { - access_point_id = module.efs.access_points.ray_results.id - iam="ENABLED" + access_point_id = module.efs.access_points.ray_results.id + iam = "ENABLED" } } } @@ -310,10 +311,10 @@ module "ecs_service_head" { } network_mode = "awsvpc" - subnet_ids = module.vpc.private_subnets + subnet_ids = module.vpc.private_subnets security_group_rules = { ingress_private_ips = { - type = "ingress" + type = "ingress" from_port = 0 to_port = 0 protocol = "-1" @@ -333,17 +334,17 @@ module "ecs_service_head" { module "ecs_service_workers" { - source = "terraform-aws-modules/ecs/aws//modules/service" - version = "~> 5.0" + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "~> 5.0" deployment_minimum_healthy_percent = 0 - name = "distributed_ml_training_worker_service" - desired_count = 2 - cluster_arn = module.ecs_cluster.arn - enable_autoscaling = false - memory = 189440 - cpu = 10240 + name = "distributed_ml_training_worker_service" + desired_count = 2 + cluster_arn = module.ecs_cluster.arn + enable_autoscaling = false + memory = 189440 + cpu = 10240 # Task Definition - + requires_compatibilities = ["EC2"] capacity_provider_strategy = { default = { @@ -352,52 +353,52 @@ module "ecs_service_workers" { base = 1 } } - - task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn + + task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn tasks_iam_role_name = "dt-role-tasks" tasks_iam_role_description = "Tasks IAM role for ${local.name}" tasks_iam_role_policies = { AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" } create_task_exec_iam_role = false - enable_execute_command = false + enable_execute_command = false container_definitions = { ray_work = { image = local.ray_worker_container_image - user = 1000 + user = 1000 readonly_root_filesystem = false - cpu = 10240 - memory = 189440 - memory_reservation = 189440 - command = ["/bin/bash","-lc","--","ray start --block --num-cpus=10 --num-gpus=4 --address=head.default.ecs-demo-distributed-ml-training.local:6379 --metrics-export-port=8080 --memory=198642237440"] + cpu = 10240 + memory = 189440 + memory_reservation = 189440 + command = ["/bin/bash", "-lc", "--", "ray start --block --num-cpus=10 --num-gpus=4 --address=head.default.ecs-demo-distributed-ml-training.local:6379 --metrics-export-port=8080 --memory=198642237440"] linux_parameters = { sharedMemorySize = 20480 } resource_requirements = [{ - type="GPU" - value=4 + type = "GPU" + value = 4 }] mount_points = [{ - sourceVolume = "ray_results" - containerPath = "/home/ray/ray_results" - readOnly = false + sourceVolume = "ray_results" + containerPath = "/home/ray/ray_results" + readOnly = false }] } } - # We are using network=host because there will be a single container in each host with GPUs. There is less overhead when using a single container with - # access to all 4 GPUs available in g5.12xlarge than 4 containers with 1 GPU each. + # We are using network=host because there will be a single container in each host with GPUs. There is less overhead when using a single container with + # access to all 4 GPUs available in g5.12xlarge than 4 containers with 1 GPU each. network_mode = "host" volume = { - "ray_results" ={ + "ray_results" = { efs_volume_configuration = { - file_system_id = module.efs.id, - root_directory = "/" - transit_encryption= "ENABLED", + file_system_id = module.efs.id, + root_directory = "/" + transit_encryption = "ENABLED", authorization_config = { - access_point_id = module.efs.access_points.ray_results.id - iam="ENABLED" + access_point_id = module.efs.access_points.ray_results.id + iam = "ENABLED" } } } @@ -418,7 +419,7 @@ module "efs" { name = "distributed-storage-shared" creation_token = "distributed-storage-shared" encrypted = true - attach_policy = false + attach_policy = false lifecycle_policy = { transition_to_ia = "AFTER_30_DAYS" @@ -436,18 +437,18 @@ module "efs" { ray_results = { name = "ray_results" posix_user = { - uid = 1000 - gid = 100 + uid = 1000 + gid = 100 } root_directory = { - path = "/ray_results" + path = "/ray_results" - creation_info ={ - owner_uid = 1000 - owner_gid = 100 - permissions = "755" - } + creation_info = { + owner_uid = 1000 + owner_gid = 100 + permissions = "755" } + } tags = local.tags } @@ -472,29 +473,29 @@ resource "aws_iam_role" "task_execution_role" { # Terraform's "jsonencode" function converts a # Terraform expression result to valid JSON syntax. assume_role_policy = jsonencode({ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "ECSTasksAssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - }, - "Action": "sts:AssumeRole", - "Condition": { - "StringEquals": { - "aws:SourceAccount": data.aws_caller_identity.current.account_id - }, - "ArnLike": { - "aws:SourceArn": "arn:aws:ecs:us-east-1:${data.aws_caller_identity.current.account_id}:*" - } - } + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "ECSTasksAssumeRole", + "Effect" : "Allow", + "Principal" : { + "Service" : "ecs-tasks.amazonaws.com" + }, + "Action" : "sts:AssumeRole", + "Condition" : { + "StringEquals" : { + "aws:SourceAccount" : data.aws_caller_identity.current.account_id + }, + "ArnLike" : { + "aws:SourceArn" : "arn:aws:ecs:us-east-1:${data.aws_caller_identity.current.account_id}:*" + } } + } ] -}) + }) managed_policy_arns = [ - "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", - "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" ] tags = local.tags From 31484192c7a5931a00afc89ff818d6537b240819 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Tue, 12 Dec 2023 10:42:13 -0700 Subject: [PATCH 05/17] Add solution diagram and reference links --- .../distributed-ml-training/README.md | 7 +++++-- .../docs/architecture.jpg | Bin 0 -> 92284 bytes 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 terraform/ec2-examples/distributed-ml-training/docs/architecture.jpg diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 16502f15..252d95e3 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -1,6 +1,8 @@ # ECS machine learning distributed training -This solution blueprint creates the infrastructure to run distributed training jobs using a Ray cluster and PyTorch. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. +This solution blueprint creates the infrastructure to run distributed training jobs using a [Ray cluster](https://docs.ray.io/en/latest/cluster/getting-started.html) and (https://pytorch.org/)[PyTorch]. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. + +![Solution architecture](docs/architecture.jpg) ## Cost warning! @@ -15,6 +17,7 @@ By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU * Service discovery ARN is used in the service definition. ECS will automatically manage the registration and deregistration of tasks to this service discovery registry. * Tasks for this service will be deployed in single private subnet to avoid AZ data transfer costs * Task definitions with GPU resource requirements +* EFS file system for shared storage between the ECS cluster tasks ## Deployment @@ -26,7 +29,7 @@ terraform apply ## Example: training the resnet18 model with the FashionMNIST dataset -Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - [https://aws.amazon.com/sagemaker/notebooks/](SageMaker notebooks) provide a better user experience to run training jobs in python than using the bash shell +Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - [SageMaker notebooks](https://aws.amazon.com/sagemaker/notebooks/) provide a better user experience to run training jobs in python than using the bash shell ```bash HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ diff --git a/terraform/ec2-examples/distributed-ml-training/docs/architecture.jpg b/terraform/ec2-examples/distributed-ml-training/docs/architecture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..493c3d4f250f4812f0c7cbf62e6e1eb109c02ce7 GIT binary patch literal 92284 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 Z%U|=o(lgb}MFxK4E-7A2mePIve*s2Upo#zh literal 0 HcmV?d00001 From 2d7bdbe4d5d42eae5f17ccecae3225d5388411b9 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Tue, 12 Dec 2023 10:43:18 -0700 Subject: [PATCH 06/17] Add solution diagram and reference links --- terraform/ec2-examples/distributed-ml-training/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 252d95e3..5bbcdbd8 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -1,6 +1,6 @@ # ECS machine learning distributed training -This solution blueprint creates the infrastructure to run distributed training jobs using a [Ray cluster](https://docs.ray.io/en/latest/cluster/getting-started.html) and (https://pytorch.org/)[PyTorch]. The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. +This solution blueprint creates the infrastructure to run distributed training jobs using a [Ray cluster](https://docs.ray.io/en/latest/cluster/getting-started.html) and [PyTorch](https://pytorch.org/). The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. ![Solution architecture](docs/architecture.jpg) From 29a20e6dece86d02eff877ed33c7c222a6e01c1a Mon Sep 17 00:00:00 2001 From: sfloresk Date: Wed, 13 Dec 2023 08:35:21 -0700 Subject: [PATCH 07/17] Fix typo in docs --- terraform/ec2-examples/distributed-ml-training/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 5bbcdbd8..5c3521cd 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -10,7 +10,7 @@ By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU ## Components -* Service discovery using AWS Cloud Map: The head node is registerer to a private DNS using loca zones via cloud map. This allow workers to discover the head service and join the cluster +* Service discovery using AWS Cloud Map: The head node is registered to a private DNS using loca zones via cloud map. This allow workers to discover the head service and join the cluster * 2 autoscaling groups: One for the head instance and other for the worker instances * ECS service definition: * Task security group, task role and task execution role and From e372d83625a5ff1a8db9ace6e4c55a06145a8a7b Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 14 Dec 2023 08:36:15 -0700 Subject: [PATCH 08/17] Add training example file and simplify docs --- .../distributed-ml-training/README.md | 102 +++--------------- .../training_example.py | 83 ++++++++++++++ 2 files changed, 97 insertions(+), 88 deletions(-) create mode 100644 terraform/ec2-examples/distributed-ml-training/training_example.py diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 5c3521cd..15cce9c6 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -27,9 +27,11 @@ terraform plan terraform apply ``` +Due to the size of the container images, it might take several minutes until the cluster is ready + ## Example: training the resnet18 model with the FashionMNIST dataset -Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - [SageMaker notebooks](https://aws.amazon.com/sagemaker/notebooks/) provide a better user experience to run training jobs in python than using the bash shell +Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - Using [SageMaker](https://aws.amazon.com/sagemaker/) or [Cloud 9](https://aws.amazon.com/cloud9/) provide a better user experience to run training jobs in python than using the bash shell ```bash HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ @@ -37,97 +39,21 @@ HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ --query 'Reservations[*].Instances[*].InstanceId' --output text) aws ssm start-session --target $HEAD_INSTANCE_ID -CONTAINER_ID=$(sudo docker ps -qf "name=.*rayhead.*") +CONTAINER_ID=$(sudo docker ps -qf "name=.*-rayhead-.*") sudo docker exec -it $CONTAINER_ID bash ``` -```python -export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training -python - -# import required torch and ray libraries -import tempfile -import torch -from torchvision.models import resnet18 -from torchvision.datasets import FashionMNIST -from torchvision.transforms import ToTensor, Normalize, Compose -from torch.utils.data import DataLoader -from torch.optim import Adam -from torch.nn import CrossEntropyLoss -from ray.train.torch import TorchTrainer -from ray.train import ScalingConfig, Checkpoint -import ray -from pprint import pprint -import time -from pprint import pprint - -# Connect to the Ray cluster -ray.init() - -# Download the data in the shared storage -transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) -train_data = FashionMNIST(root='/home/ray/ray_results/data', - train=True, download=True, - transform=transform) - -# Define the training function that the distributed processes will run -def train_func(config): - import os - # The NVIDIA Collective Communications Library (NCCL) implements multi-GPU - # and multi-node communication primitives optimized for NVIDIA GPUs. - # Since containers can have multiple interfaces, we explicitly set which one - # NCCL should use. - os.environ['NCCL_SOCKET_IFNAME']='eth0' - #os.environ['NCCL_DEBUG']='INFO' Uncomment this line if you want to debug NCCL - # Set up the model - model = resnet18(num_classes=10) - model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), - stride=(2, 2), - padding=(3, 3), - bias=False) - # Prepare model for distributed training - model = ray.train.torch.prepare_model(model) - # Setup loss and optimizer - criterion = CrossEntropyLoss() - optimizer = Adam(model.parameters(), lr=0.001) - # Retrieve the data from the shared storage. - transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) - train_data = FashionMNIST(root='/home/ray/ray_results/data', train=True, download=False, transform=transform) - train_loader = DataLoader(train_data, batch_size=128, shuffle=True) - # Prepare dataloader for distributed training - train_loader = ray.train.torch.prepare_data_loader(train_loader) - # Define training loop - for epoch in range(10): - start = time.time() - for images, labels in train_loader: - outputs = model(images) - loss = criterion(outputs, labels) - optimizer.zero_grad() - loss.backward() - optimizer.step() - print(f"[GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()}] | [Epoch {epoch} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") - # Only save checkpoint after the last epoch - if epoch == 9: - checkpoint_dir = tempfile.gettempdir() - checkpoint_path = checkpoint_dir + "/model.checkpoint" - torch.save(model.state_dict(), checkpoint_path) - # Report metrics and checkpoint to Ray. - ray.train.report({"loss": loss.item()},checkpoint=Checkpoint.from_directory(checkpoint_dir)) - -# The scaling config defines how many workers -# In this case is equal to the total GPU count -scaling_config = ScalingConfig(num_workers=8, use_gpu=True) - -# Create the trainer instance -trainer = TorchTrainer(train_func, - scaling_config=scaling_config) - -# Run the training -result = trainer.fit() - -# Print the results of the training -print(result) +Inside the container shell, check the cluster status +```bash +ray status +``` +Run the [training script example](./training_example.py) - you can look at the comments inside the python script to learn more. + +```bash +export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training +wget https://raw.githubusercontent.com/aws-ia/ecs-blueprints/main/terraform/ec2-examples/distributed-ml-training/training_example.py +python training_example.py ``` ## Clean up diff --git a/terraform/ec2-examples/distributed-ml-training/training_example.py b/terraform/ec2-examples/distributed-ml-training/training_example.py new file mode 100644 index 00000000..fd3b8abe --- /dev/null +++ b/terraform/ec2-examples/distributed-ml-training/training_example.py @@ -0,0 +1,83 @@ + +# import required torch and ray libraries +import tempfile +import torch +from torchvision.models import resnet18 +from torchvision.datasets import FashionMNIST +from torchvision.transforms import ToTensor, Normalize, Compose +from torch.utils.data import DataLoader +from torch.optim import Adam +from torch.nn import CrossEntropyLoss +from ray.train.torch import TorchTrainer +from ray.train import ScalingConfig, Checkpoint +import ray +from pprint import pprint +import time +from pprint import pprint + +# Connect to the Ray cluster +ray.init() + +# Download the data in the shared storage +transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) +train_data = FashionMNIST(root='/home/ray/ray_results/data', + train=True, download=True, + transform=transform) + +# Define the training function that the distributed processes will run +def train_func(config): + import os + # The NVIDIA Collective Communications Library (NCCL) implements multi-GPU + # and multi-node communication primitives optimized for NVIDIA GPUs. + # Since containers can have multiple interfaces, we explicitly set which one + # NCCL should use. + os.environ['NCCL_SOCKET_IFNAME']='eth0' + #os.environ['NCCL_DEBUG']='INFO' Uncomment this line if you want to debug NCCL + # Set up the model + model = resnet18(num_classes=10) + model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), + stride=(2, 2), + padding=(3, 3), + bias=False) + # Prepare model for distributed training + model = ray.train.torch.prepare_model(model) + # Setup loss and optimizer + criterion = CrossEntropyLoss() + optimizer = Adam(model.parameters(), lr=0.001) + # Retrieve the data from the shared storage. + transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) + train_data = FashionMNIST(root='/home/ray/ray_results/data', train=True, download=False, transform=transform) + train_loader = DataLoader(train_data, batch_size=128, shuffle=True) + # Prepare dataloader for distributed training + train_loader = ray.train.torch.prepare_data_loader(train_loader) + # Define training loop + for epoch in range(10): + start = time.time() + for images, labels in train_loader: + outputs = model(images) + loss = criterion(outputs, labels) + optimizer.zero_grad() + loss.backward() + optimizer.step() + print(f"[GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()}] | [Epoch {epoch} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") + # Only save checkpoint after the last epoch + if epoch == 9: + checkpoint_dir = tempfile.gettempdir() + checkpoint_path = checkpoint_dir + "/model.checkpoint" + torch.save(model.state_dict(), checkpoint_path) + # Report metrics and checkpoint to Ray. + ray.train.report({"loss": loss.item()},checkpoint=Checkpoint.from_directory(checkpoint_dir)) + +# The scaling config defines how many workers +# In this case is equal to the total GPU count +scaling_config = ScalingConfig(num_workers=8, use_gpu=True) + +# Create the trainer instance +trainer = TorchTrainer(train_func, + scaling_config=scaling_config) + +# Run the training +result = trainer.fit() + +# Print the results of the training +print(result) \ No newline at end of file From 6ccd034d818779f538df7929539df53b90cddc81 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 14 Dec 2023 08:52:58 -0700 Subject: [PATCH 09/17] Add training example script file --- .../distributed-ml-training/README.md | 102 +++--------------- .../training_example.py | 83 ++++++++++++++ 2 files changed, 97 insertions(+), 88 deletions(-) create mode 100644 terraform/ec2-examples/distributed-ml-training/training_example.py diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 5c3521cd..b50c4c15 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -27,9 +27,11 @@ terraform plan terraform apply ``` +Due to the size of the container images, it might take several minutes until the cluster is ready + ## Example: training the resnet18 model with the FashionMNIST dataset -Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - [SageMaker notebooks](https://aws.amazon.com/sagemaker/notebooks/) provide a better user experience to run training jobs in python than using the bash shell +Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - Using notebooks with [SageMaker](https://aws.amazon.com/sagemaker/) or [Cloud 9](https://aws.amazon.com/cloud9/) provide a better user experience to run training jobs in python than using the bash shell ```bash HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ @@ -37,97 +39,21 @@ HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ --query 'Reservations[*].Instances[*].InstanceId' --output text) aws ssm start-session --target $HEAD_INSTANCE_ID -CONTAINER_ID=$(sudo docker ps -qf "name=.*rayhead.*") +CONTAINER_ID=$(sudo docker ps -qf "name=.*-rayhead-.*") sudo docker exec -it $CONTAINER_ID bash ``` -```python -export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training -python - -# import required torch and ray libraries -import tempfile -import torch -from torchvision.models import resnet18 -from torchvision.datasets import FashionMNIST -from torchvision.transforms import ToTensor, Normalize, Compose -from torch.utils.data import DataLoader -from torch.optim import Adam -from torch.nn import CrossEntropyLoss -from ray.train.torch import TorchTrainer -from ray.train import ScalingConfig, Checkpoint -import ray -from pprint import pprint -import time -from pprint import pprint - -# Connect to the Ray cluster -ray.init() - -# Download the data in the shared storage -transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) -train_data = FashionMNIST(root='/home/ray/ray_results/data', - train=True, download=True, - transform=transform) - -# Define the training function that the distributed processes will run -def train_func(config): - import os - # The NVIDIA Collective Communications Library (NCCL) implements multi-GPU - # and multi-node communication primitives optimized for NVIDIA GPUs. - # Since containers can have multiple interfaces, we explicitly set which one - # NCCL should use. - os.environ['NCCL_SOCKET_IFNAME']='eth0' - #os.environ['NCCL_DEBUG']='INFO' Uncomment this line if you want to debug NCCL - # Set up the model - model = resnet18(num_classes=10) - model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), - stride=(2, 2), - padding=(3, 3), - bias=False) - # Prepare model for distributed training - model = ray.train.torch.prepare_model(model) - # Setup loss and optimizer - criterion = CrossEntropyLoss() - optimizer = Adam(model.parameters(), lr=0.001) - # Retrieve the data from the shared storage. - transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) - train_data = FashionMNIST(root='/home/ray/ray_results/data', train=True, download=False, transform=transform) - train_loader = DataLoader(train_data, batch_size=128, shuffle=True) - # Prepare dataloader for distributed training - train_loader = ray.train.torch.prepare_data_loader(train_loader) - # Define training loop - for epoch in range(10): - start = time.time() - for images, labels in train_loader: - outputs = model(images) - loss = criterion(outputs, labels) - optimizer.zero_grad() - loss.backward() - optimizer.step() - print(f"[GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()}] | [Epoch {epoch} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") - # Only save checkpoint after the last epoch - if epoch == 9: - checkpoint_dir = tempfile.gettempdir() - checkpoint_path = checkpoint_dir + "/model.checkpoint" - torch.save(model.state_dict(), checkpoint_path) - # Report metrics and checkpoint to Ray. - ray.train.report({"loss": loss.item()},checkpoint=Checkpoint.from_directory(checkpoint_dir)) - -# The scaling config defines how many workers -# In this case is equal to the total GPU count -scaling_config = ScalingConfig(num_workers=8, use_gpu=True) - -# Create the trainer instance -trainer = TorchTrainer(train_func, - scaling_config=scaling_config) - -# Run the training -result = trainer.fit() - -# Print the results of the training -print(result) +Inside the container shell, check the cluster status +```bash +ray status +``` +Run the [training script example](./training_example.py) - you can look at the comments inside the python script to learn more. + +```bash +export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training +wget https://raw.githubusercontent.com/aws-ia/ecs-blueprints/main/terraform/ec2-examples/distributed-ml-training/training_example.py +python training_example.py ``` ## Clean up diff --git a/terraform/ec2-examples/distributed-ml-training/training_example.py b/terraform/ec2-examples/distributed-ml-training/training_example.py new file mode 100644 index 00000000..fd3b8abe --- /dev/null +++ b/terraform/ec2-examples/distributed-ml-training/training_example.py @@ -0,0 +1,83 @@ + +# import required torch and ray libraries +import tempfile +import torch +from torchvision.models import resnet18 +from torchvision.datasets import FashionMNIST +from torchvision.transforms import ToTensor, Normalize, Compose +from torch.utils.data import DataLoader +from torch.optim import Adam +from torch.nn import CrossEntropyLoss +from ray.train.torch import TorchTrainer +from ray.train import ScalingConfig, Checkpoint +import ray +from pprint import pprint +import time +from pprint import pprint + +# Connect to the Ray cluster +ray.init() + +# Download the data in the shared storage +transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) +train_data = FashionMNIST(root='/home/ray/ray_results/data', + train=True, download=True, + transform=transform) + +# Define the training function that the distributed processes will run +def train_func(config): + import os + # The NVIDIA Collective Communications Library (NCCL) implements multi-GPU + # and multi-node communication primitives optimized for NVIDIA GPUs. + # Since containers can have multiple interfaces, we explicitly set which one + # NCCL should use. + os.environ['NCCL_SOCKET_IFNAME']='eth0' + #os.environ['NCCL_DEBUG']='INFO' Uncomment this line if you want to debug NCCL + # Set up the model + model = resnet18(num_classes=10) + model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), + stride=(2, 2), + padding=(3, 3), + bias=False) + # Prepare model for distributed training + model = ray.train.torch.prepare_model(model) + # Setup loss and optimizer + criterion = CrossEntropyLoss() + optimizer = Adam(model.parameters(), lr=0.001) + # Retrieve the data from the shared storage. + transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) + train_data = FashionMNIST(root='/home/ray/ray_results/data', train=True, download=False, transform=transform) + train_loader = DataLoader(train_data, batch_size=128, shuffle=True) + # Prepare dataloader for distributed training + train_loader = ray.train.torch.prepare_data_loader(train_loader) + # Define training loop + for epoch in range(10): + start = time.time() + for images, labels in train_loader: + outputs = model(images) + loss = criterion(outputs, labels) + optimizer.zero_grad() + loss.backward() + optimizer.step() + print(f"[GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()}] | [Epoch {epoch} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") + # Only save checkpoint after the last epoch + if epoch == 9: + checkpoint_dir = tempfile.gettempdir() + checkpoint_path = checkpoint_dir + "/model.checkpoint" + torch.save(model.state_dict(), checkpoint_path) + # Report metrics and checkpoint to Ray. + ray.train.report({"loss": loss.item()},checkpoint=Checkpoint.from_directory(checkpoint_dir)) + +# The scaling config defines how many workers +# In this case is equal to the total GPU count +scaling_config = ScalingConfig(num_workers=8, use_gpu=True) + +# Create the trainer instance +trainer = TorchTrainer(train_func, + scaling_config=scaling_config) + +# Run the training +result = trainer.fit() + +# Print the results of the training +print(result) \ No newline at end of file From 35cf69665d627ae76b8a6c8a3bde0a60dd8e54ad Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 14 Dec 2023 12:10:17 -0700 Subject: [PATCH 10/17] Update task to use read only root fs --- .../distributed-ml-training/README.md | 10 +++++-- .../distributed-ml-training/main.tf | 26 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index b50c4c15..9a00e7f3 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -29,7 +29,7 @@ terraform apply Due to the size of the container images, it might take several minutes until the cluster is ready -## Example: training the resnet18 model with the FashionMNIST dataset +## Example: training the resnet18 model with the [FashionMNIST](https://github.com/zalandoresearch/fashion-mnist) dataset Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - Using notebooks with [SageMaker](https://aws.amazon.com/sagemaker/) or [Cloud 9](https://aws.amazon.com/cloud9/) provide a better user experience to run training jobs in python than using the bash shell @@ -39,11 +39,16 @@ HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ --query 'Reservations[*].Instances[*].InstanceId' --output text) aws ssm start-session --target $HEAD_INSTANCE_ID +``` + +Connect to the bash shell of the conntainer + +```bash CONTAINER_ID=$(sudo docker ps -qf "name=.*-rayhead-.*") sudo docker exec -it $CONTAINER_ID bash ``` -Inside the container shell, check the cluster status +Inside the container shell, check the cluster status - you should see three healthy nodes and 8 GPUs available ```bash ray status ``` @@ -52,6 +57,7 @@ Run the [training script example](./training_example.py) - you can look at the c ```bash export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training +cd /tmp wget https://raw.githubusercontent.com/aws-ia/ecs-blueprints/main/terraform/ec2-examples/distributed-ml-training/training_example.py python training_example.py ``` diff --git a/terraform/ec2-examples/distributed-ml-training/main.tf b/terraform/ec2-examples/distributed-ml-training/main.tf index 3a32454c..3fcda75b 100644 --- a/terraform/ec2-examples/distributed-ml-training/main.tf +++ b/terraform/ec2-examples/distributed-ml-training/main.tf @@ -275,9 +275,9 @@ module "ecs_service_head" { container_definitions = { ray_head = { + readonly_root_filesystem = true image = local.ray_head_container_image user = 1000 - readonly_root_filesystem = false cpu = 3072 memory = 10240 memory_reservation = 10240 @@ -289,10 +289,21 @@ module "ecs_service_head" { sourceVolume = "ray_results" containerPath = "/home/ray/ray_results" readOnly = false + }, + { + sourceVolume = "tmp" + containerPath = "/tmp" + readOnly = false }] } } volume = { + "tmp" = { + docker_volume_configuration = { + scope = "task" + driver = "local" + } + } "ray_results" = { efs_volume_configuration = { file_system_id = module.efs.id, @@ -365,9 +376,9 @@ module "ecs_service_workers" { container_definitions = { ray_work = { + readonly_root_filesystem = true image = local.ray_worker_container_image user = 1000 - readonly_root_filesystem = false cpu = 10240 memory = 189440 memory_reservation = 189440 @@ -383,6 +394,11 @@ module "ecs_service_workers" { sourceVolume = "ray_results" containerPath = "/home/ray/ray_results" readOnly = false + }, + { + sourceVolume = "tmp" + containerPath = "/tmp" + readOnly = false }] } } @@ -391,6 +407,12 @@ module "ecs_service_workers" { network_mode = "host" volume = { + "tmp" = { + docker_volume_configuration = { + scope = "task" + driver = "local" + } + } "ray_results" = { efs_volume_configuration = { file_system_id = module.efs.id, From ecc44f327c8688d3316d65b3272e6c8d0e53caeb Mon Sep 17 00:00:00 2001 From: sfloresk Date: Tue, 9 Jan 2024 13:43:16 -0700 Subject: [PATCH 11/17] Remove EFS - Add S3 --- .../distributed-ml-training/README.md | 20 ++- .../docs/architecture.jpg | Bin 92284 -> 0 bytes .../docs/architecture.png | Bin 0 -> 76095 bytes .../distributed-ml-training/main.tf | 163 ++++++++---------- .../distributed-ml-training/outputs.tf | 11 +- .../training_example.py | 60 +++++-- .../distributed-ml-training/versions.tf | 4 + 7 files changed, 139 insertions(+), 119 deletions(-) delete mode 100644 terraform/ec2-examples/distributed-ml-training/docs/architecture.jpg create mode 100644 terraform/ec2-examples/distributed-ml-training/docs/architecture.png diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 15cce9c6..99b669ef 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -2,7 +2,7 @@ This solution blueprint creates the infrastructure to run distributed training jobs using a [Ray cluster](https://docs.ray.io/en/latest/cluster/getting-started.html) and [PyTorch](https://pytorch.org/). The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. -![Solution architecture](docs/architecture.jpg) +![Solution architecture](docs/architecture.png) ## Cost warning! @@ -17,7 +17,7 @@ By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU * Service discovery ARN is used in the service definition. ECS will automatically manage the registration and deregistration of tasks to this service discovery registry. * Tasks for this service will be deployed in single private subnet to avoid AZ data transfer costs * Task definitions with GPU resource requirements -* EFS file system for shared storage between the ECS cluster tasks +* S3 bucket to store the results ## Deployment @@ -27,33 +27,39 @@ terraform plan terraform apply ``` -Due to the size of the container images, it might take several minutes until the cluster is ready +Due to the size of the container images, it might take several minutes until the containers reach a running state ## Example: training the resnet18 model with the FashionMNIST dataset -Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - Using [SageMaker](https://aws.amazon.com/sagemaker/) or [Cloud 9](https://aws.amazon.com/cloud9/) provide a better user experience to run training jobs in python than using the bash shell +Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - Using notebooks with [SageMaker](https://aws.amazon.com/sagemaker/) or [Cloud 9](https://aws.amazon.com/cloud9/) provide a better user experience to run training jobs in python than using the bash shell +1. Connect to the instance ```bash HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ --filters 'Name=tag:Name,Values=ecs-demo-distributed-ml-training-head' \ --query 'Reservations[*].Instances[*].InstanceId' --output text) aws ssm start-session --target $HEAD_INSTANCE_ID +``` + +2. Connect to the container +``` CONTAINER_ID=$(sudo docker ps -qf "name=.*-rayhead-.*") sudo docker exec -it $CONTAINER_ID bash ``` -Inside the container shell, check the cluster status +3. Inside the container shell, check the cluster status ```bash ray status ``` -Run the [training script example](./training_example.py) - you can look at the comments inside the python script to learn more. +4. Run the [training script example](./training_example.py) - you can look at the comments inside the python script to learn more about each step. +A bucket was created as part of the terraform plan, make sure to add the name of that bucket (starts with "dt-results-") as argument of the training_example.py script ```bash export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training wget https://raw.githubusercontent.com/aws-ia/ecs-blueprints/main/terraform/ec2-examples/distributed-ml-training/training_example.py -python training_example.py +python training_example.py YOUR_BUCKET_NAME ``` ## Clean up diff --git a/terraform/ec2-examples/distributed-ml-training/docs/architecture.jpg b/terraform/ec2-examples/distributed-ml-training/docs/architecture.jpg deleted file mode 100644 index 493c3d4f250f4812f0c7cbf62e6e1eb109c02ce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92284 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 Z%U|=o(lgb}MFxK4E-7A2mePIve*s2Upo#zh diff --git a/terraform/ec2-examples/distributed-ml-training/docs/architecture.png b/terraform/ec2-examples/distributed-ml-training/docs/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..925227428623f5a242e0b5a52272d681c311aab4 GIT binary patch literal 76095 zcmeFZbySpJ+crFdpo9pBfb<~UN=gsXN_QjOB||ruw16PpEhr6xgrb6Ur<6#S(#^XE zE(`{%~R%s4Z9U;8@una6Q%o+&HJ;9`?uLm&`bIax_n2m}KLfn1Qr zya?V|vx3<{AeTj~B_x#PBqU(U4;(G5?aUz%*=H}bE@`WMCQj8;rKy0Rsorr6dKwE+ zy<-KiVR>feduYYG zt*NyM+(q2A!eIH8w3D+zo`WD^Dv0d057x)Rh8!O*L7R#^Oyoo%lBABJyhd!zZ=m*A z^7vmLaV2LoX>cXyHhIs9I^VK;st1J?J*bVo3>n~%NL6WS4I(JWo6VM`5snu9J!tOnMiCrocc#{Urg!d`sR+(vYtf(nA_b5TI z=vC`5Hh-L_rs$dX{U3>HMsZ+;s2s-6&*OHHjK?y6lVtjFdfZWiFXj72lS(U9EE&g5 z2WIjSZy9ql`u(Ma_ax}(Eb|erIFdg~@222=^*Ou#Q9GHX>zV4|tVK}reNxAZrS}ZQ zUp-(;jlv_zzC*1;L-q+YntF`QfXM5rMeYqn??)@1DrV}Q`Yq4BmUu8-U zJhKy15?7)^UJZ#2$vn3mQ?4JC5D{y!JX7`+6@GHE`i%XZF(aON+E4hK`a1*27Y{RN4l`WNnz`|^v zTl;$`?7owva0|g@OGZl)h}jg$$CVX@isQP##VX&0`7>z1-jBe*z~-sio>|DM%acg| ze)q1&SYnhckYwAha`YJ3SK=u#zq4mtN#pUof4L(NH#w5q>4ISs9(s^6KGs(_*GtUu z?uRS-Xt_ZN_o-GWcjp%hTYcce}P`WJ`a8Ps?on{awAO$K&j z21VhM-I3WQApCH(R3@2#;cGDZjTza?(P7y)NS|Upzrp?dK$cWB;*|s+EpMx6HkQD% zWEsJz@m7iKOP=9AcW$W@4N8YAvm)r)Bz3H=lRZ&{bN4V?(ddS8h!3X^E^<^*i99ou ze4UlDh*Lp*;U#u%XoAU=>x8DJ&$Y4RBIMqTYBOsFYM8oebHDA@Dph<%a`cqYS+J{# zavJ-}yIE%{QH->3&QHTLLf^gK5;ca1eK`8=h)(t{>b}x=p20(R$r~#1RtktG-6mYOLT?AxysSyBdEyZ?BJLx<%J^LNkj^`rpE>#i{anml zSzEa}*%mo1S%YjmGomuA(F6)fMVio7sc+buikr7Ki8i@6EkiDTjQ%S3Ec-iiKl43i zJ?75Xd$AmCmW2$1iOUM%Sr#e_D#OYv`FRC<`Ht%9>K*FB>X{l3SzI)R@>ewk@-~&{ zvI^r0Q&{v|-xjL7K3jGhBrL4fnCh|OVNIlFe-x*rcc&;LB~v8bS2?RlpwM|>-^OKt zWpH`0B)?ugFJG^)e1Nf#q);oLTDdWMvPiu5P2YW6CteF%bsHY*F9VCi*|rgVVFMk5 z`mbu$w%%G)6U(*VWmOJ+t?{Z-Y9)5aR8X2fuY5`P(C1L|(Aq1WPi0&;DJvz%Ah%wx z&Y(_7=3a{wqqt?FVosMzilTx1@ZI>Vq^vr1_r`VH38m(+$EA<49EDPuCXFmOrdLJkou;-6QA6tywBU_UJ!Uzdonsa* zQ(g>x-mKkhWxkz2miYMPT?PK6sw6+Ya=uW0R<3bVx2a^CE*>R*BkM_%NlRmsTSF$f zQpF!B?}gC!bM-%{oYQIF(;U)#rit^4>=m+j=as`@tM&<1 zqg5;RoZ}g8C2k3BFWll+RlNDVGfupYA_yK5P|-@#7Rr1N`iovCIrec+htPpA5NUt@!g$|bzL<3(UpPt>OpqRNbcs1wf0^(| z^04-~7v%MR|6$_8y=Ty8bI(%bXk#qs4dnS`i5d8Fqg1b|R^@C*;!srE=ns1g|Dd0B zlz)?Y)BgRzvx|f-3{)xEqR)hu;4Cz}w_GikNgU{^6UWm&$rasQnJx7!^5oq2JAQFY z6MVagYF(~3|FWAo zwV#*YV?Dxiz;krVkMjWw3#XTmn6sZtsmhNpUt1c{&s6m?HE%H2);8I``}Ao4&dmE= zsVtIWgYDv2Bd?5(f>)WV?Ix37aT+5IT{~Z2xYrk7!Re}pG$wUUuo?}YYiM_mwT&Z* z6EnQ!CUtW6Q(NB)M=b?i*M|2wCbKrnHcQK0yfe0B!@92ws&?Cv9j?QyC#-wwq?+u7 zUX7`Zr)?J-ab3y7`Qq%4E97dHhUTX`cLp^FiS(Z8VOI%HB)-{Mb?lh3sUo#w9p81W zv+`bucu85zyUxoigzL^xEM=QhDN^@o1Nm0yzPo9P#KgP^(z|UpsJ^ehdoO3bu2js< zza2UshzyRqRCJ{m@5K=9PXEdg85g-Ac?`Kw;+-Vj#QH=$N;l!RCr_82w`B>+#8YKa zv5N9Jz5VWShV|pwWDF@Ib+x=rtvAckK;_Wl&~CqRUUv$XpvyAb%IXi7$%TmIJ>e-t zpl8NR=J>}$zTql~#=29#{E6cF)99m|PwiCkNkX3x6m?&{=e`XF6j_!yml(XIa;~41 zJ`UI8Z<$(s`>}D*S7>3kNC?ASbSlL=(&_bf!>5ywOONpi&)%GvD&^@d^ge;&jZ=LT zMIOD}+y5BzmeB|qlGc~1Da`Gu*}wM1H=8kkVROs5yUy^bk1W0*GV?9vX;;NWj)Zq`mf90v zR~aQWKJu;Oj}p2Go4;GLX#~B@$0I@+Fgy-Tr@sJya>TuS`{SvS!&8%P7MW==GrKs;MUj_aAs6Avm*6syR5rohyax@c7Lql1BiD9#+4*sg z$<`ildO&l1VT)Y`i07=(tQW>4_m8iBeWId z1x+38IgHI5P0Tqw?43~aK!iO6!ApB{gfYy+-p;{A&_m?L`8Nc?Yt-AEH(=*qLD-7i z&{j}}NjN?*hw*XTV*0?^31RK%07K1dY~tvO5V>&! zb)i2$Ki6sQVg1jQ99(|g7Pvu9)F+&`IBs(OnHvlhM!hSjZ0%ugrz2@?4`c?eA$ptp zw($A)|Lv21uK3qT?SDqz;=aZ8_t3vS`maMZT+AOxINF12B1He$uU~`z{_xj8VNTSo z|0Rl_az1|-2wD_dnDfu3iDEmOlH3LRNMS9hqz;~emZ5&2S>S=`=QHZ{3!(c9!)p)- z93m$vuI>R{ow)R6{KL$rAHiJj8N$kB?zB^3+zC{*2q4BJzZ1aKjExU7e@xlNR)_!I z(EmNQcjUveJr3`O^%;RxaHdDcCtuo0b)MTVRaW^O`aEUVLkjsS#hr*IXUj1RFf>1e zpkcxS{{4Xtb`uL~hE{m(*}s2=`o3lWB$^TmMaPFj{_Wu^AuK>XEG~%PzxfOw4hH)E z`ux>7B1D_At-8|g z?mW{!*L2!+IxQ0LMhx}O17n2xEg1#%2>ACFZ57Wj{00?uV&!O^fv^lptgVV*BHAMh z~SbhrP79VQxPtbPEb>>FjrJk5WTwZWotAW*Zo!-4R> zSMoc-gMY@rgx#BbXGit-Pzk6{7-e+u~jVPAnDQ_Z5~>^cb<f~pX-`bh7FkfcsN+TWtfAqM85 z>ds021cF9?6=fdW2bpvM9ah6dc9rN+FV?*HD1L3mT{f@}^y~V3xL_aXQ7drYl#+ua z^p*|I+AKZIq>askI{mmU0*6E!MwzKGO?eHU-FXZSrPbSm>aUlp{OTtk*kg_s?>F>gzQPV0`%^T0w4v@VInB zoC+-TD2!#c#E^fn6__#$P$^Yc%Tm6_kTUHPjqRbUaN_z))?)u^cW@P8kD+5O^j7>( zGc3VUcoH0@J@>tENZ?f*V5853;uRzyfvQ+28%^<1 zxeB+aSkb7`MQd$y6CscIB~f~mnp}0~NP7HJ4juVI|Ceeq>M)#Cxc6Xwyi}<|ucLw5 zd{h}D&WQ%C^{N|7((m&31X@WF2hDe@FN>oUbY+B-^{#S<{l?#R?jtvaL;E>Uey=)!`?P4_9 zaKC{=aY7oiENrM*)+LvJxgfZHHp)KYcy{FJ;1Fy1a)L0!SL+v}N-hVra$up!R?B>U z`d=4`1dCkqQcIFaZ-biEsYFsSc%QUb@wondNRtA_C zrKJrPe!dBWl->hjmJ$(wm|HAuqoD(2ywbG5ze)pwwgk31*K$I+pDsq;WPwrsWzTo7 z7Z!!Y@G7ARUGdp?)2`G-X&x0cS$3h*Vn&5XL*GP0a@8YkqF`CHRu)Qwk^ez2HLzg3 z%PM(ys}m6+mG31KPWub9E=H{**4zT~mJ3z!>As{l&p%9GR{|Bm+PRCdCmIRV3N8fQn`rxyopNmhW#Xf#>|n)cX{`ML zA({XE#f<^YJiVj%!MBaV#TyQ6vqqwAWv8yZQyfp(`mMlzrHiER*sfpl?NROY`^K;L zWUlVokEI9q{qOrbwY;!63i3{q`Q>GRLwJCR50A^D#;GNe!h?-Ja#snK8Gfd6=Oa@) za`Pl|txBRjp5`fC!1kWcktazU0e(fb2^Kp3Q--D=^ob<*{y1qVuo-M#c*Hd)Qn;{L z;R(U~qs)gPDsi}l?MJ$!K#OD@9iMnB!tuMF<@^`mN*AOGRl742=olfy@W)tGbwbO% z?G*ItVP&tXBQ(2#B1lo<%0Wg8hl8hR11`JGX#83Id6WvY0(;i`R$EXy@CHWI+MU|l zK{7Jr=i^z$I!x=Ad77 zZuW)7&UEcZ2Lb%ktSs~IN#LmNYY)mO$o z{}LW+o9%<8-l9mA+v9d^F2IaNUk!Uu{?3$`N?8OTILVU4&ue| zD$b4UBDcB*XAqFWBFbVT*Gd}4s3>HN6sLol>3c)wP@+h|`7V_2B5@b2g994DxQc~R zzFX%Jgt38XixB=;U_!7H&VD!ly7_30`;MDW5V$KRGzz2l0;Si`Fe^x=J^Ch}(xYT940L`zgM?-5 zG241jN;R-nr0DrArX#=l6&v6e5ai5GdG&>ni`-Z*9FLQMjK*1IV(g2H5nyow0m*|& zO@gwo|5n3v7}!IMrFHE`GY#)i_r9R$E!gaRt?QZ4ssQ#h>>W z07H`6{v7o?v%{2UgF^&Lv0i7>MIj(M5BrNx2E;QAVhN&HI6IZQr%uxk((N}n2BQPW9(3aBr}JU1_%GQSfMJw!c}rKj9|da;b*)7)!EkWlHhTmUs3o z;A`ZO^&rGv9Qh~RuU+nb>2 z>xnf9l{D79aSU(I@m-5G`hYMWCS#00JJN@$?UvRpXF*j1KX>WGMzKp6QF4!UCq{jB zL5OIx$er*n|QAKb_Q?nIF#k#0FMJ=IP2_$Xa2;VEgCS_gRRd2({i- zU~Haq#!IgoPsiS#ogP&&<8aiZix3IntrgxOzulmo5kD%8-bXO)5gox@iPGd05NzF8 zS`R0PWmh;xbkJ>2lZy7Ams!P(P2(LOul4yP+HX}qfpF!^lzE3!Z}+aoV8dD>sU3q$ znT8n$3MS4Dy8Sy|m3P@NB-xcIlW)#Oij8^cH@ZK-u4+5npN;GsP*bwENOu0Dykkp) z5j58I#(qc;2>h(0#(B1bt-K>zJ7E7{A#J>>Q}k#x<2B*IYyXq>MIP_m`_Sq%1yD42 zKn?#XyZuR5U;@caJxDf(lD`tD5(U-Uj5S`rM;OL0rA{r(=OGcV0g*WbHW ziiEdrwJemB$0{npB!qeSbsb~8fm=fD@n(BxFrKKDk*~czp9kfgeB+YH zi{C;y8?3Gfe8bODOPa{Gm363@c|4yTqlkU^+oa!-y~~L4lLYUL`r!!a0R_i_#|nI- zAM6@Wc1x}w?E@(#P`ZEVy%@VWd@}a>Fqq9RT z?<^Qb^4O$hs^_OF_dYpBUj7M0OoBzC_3{eSi17SN(+(g|BWn5r{=urONKvF;efJ@D zCF9t|!HhB@G~4k(OQDUo zi=^VyQ!OK3bHBa1sRWA^EAg{V2cFH<9tk|&7AkMMdWsFgN0!)J&65wMT{){pOeSxT zrS7dyj0>Oc&*HwDF0P)msrvHZ9{CV2+i`zbP(OFKaR1{BFFEKene8j%DUEbNlMmhe zy@GpRT(!MhckD{?9lN7MIE?uD*S+zI(%NM5)056)eKI!AAuLDf@>AaQ_m-^Q5 zV9J5pnpT1w#`9BTOvSRV{xDP7`gILgO0-rhkqum!`?x)S`Ep@V6&w6r5`*K-{3!X1 zAwnf#<0+wqWEc5HzY{ySJ8+6gCwo&<4JEx%4j(cNdsUK=?c5KR2iOx&)D%80jh_RaAB(gh_B^ttI2Ptr4Q6AVFHuNrD z>d%W55z#d+kXE%(mCf%B0JTXKnz#Zs;}bk;hd2W_ovrn{jj^5|Uqd;oHYlAx5X`sI zMpks!L$eK)4X}4APIHqSY=9Oq)FtCwU+Yjh^Eg^av@I!Z_r`te{%(j#k)&h2b|KYb z*ucHgq0k>JuIhw_JA8itakN&QsZek$)49bb%H(K7V>*defx?BJXQ)8xSB(m1tLc=uuyHl_JGC01d&GEE+XV|xAph!Wj=W*WX^qoY|ekxc|qNak=*DUx9nKs1a? zlHSr%iXAS}3T3+d_(;P4x_&niHU;5xe1IVw1g| zf@|6|IcQ=ehK5e>Fm{n44xW28%~yo3hu^tO4rjqc%k-kW@PiQ4XU<>InyB$6Skgt9;`BUReh>q0AQNzMDq*KanlJ5al}ZL<`;GD%w* zi^I0yUGj<`qh#Ky-yLgD_B&kK-AM;2oR)Y-E|_)vRqcG@ZtaoGc+HgG(WuVB3WIn% zTX|SY<5rhY=a}v5HR58iWn@ZT@`I3+c!{3M+bcL*07v~@b_HHV)sq7SsM#Q z{KTUWzUAI=w~Z~$@%*{3*w{dT8YS*}UtAR5xm^`zpr6Kt1;4-LU+?fCoj+iJxyQ66 zqB861$xNc&R0`QEl}Cb7<3hd%bMf=Ym6Gz|Aaw!9h69Te20q;`jHpRv)jWa~gvl9V zNXL>iC4`bMU|{ZjCm^FHqw5(0v zqGC$fIxcL=V;0xDt;Ss>JUJQ6y=o=#D4wi7|8w1BjFPCe($3I|H~>B#W=R`*$ynG> zp7GcWFp=L_aZS0hPWgN(ZjgBgo13>D$j4&RbHUZ|$2kgyEqOQaFzeTz(!zJ+l^{z*^Pauvg19i=yGrhfvc!H3KLkVX&?k^Z-Om@0c zFY4%-6yPGD!SbxF~8S%R=>9v*X??^t0=TKfKRtf7APC}<^|;6`5uRp!p%8O0+mEtp)hp&01tBT zy7d`I6QwENk(k$_wW8`m84QaGA$mi5<;A;@gxf+y*W}atA${JuWhJDGid*)d(tD$k zTa}8?p-#Ooly9{3?0LP{WI;kX>^#ugoGRiyW}*)%P%9#;u=WCRx#NpVw|$!TS+7Bo zYxB!Dw6F=JO{8K%!$PWen4;W}E5k&#+6!JZ75*0IJa$&ulUeGsMPe+U0bVx$og$-0 z*f3tM&Edw=O7?~2qSsY<&fQ$?Y<$-SJH2)%y*Iol!QSo(MvuiY=Ovdl`1HhZcinDI z9V*_tCXIZaBHq4S33Aa9d7-t6&dHLFmE$Xg@>=B?m~4WHhIck|!Z>XUR@!b5(INWUXA|q;8Xo-Or$z7Fe8X@p$H>U2w zZcep-RGNLi&fPTsxUe=z~)=ttlVG#JG^MY+vWj#T@GPPjvv6(~*7+kVKet0MbstXZu)1@|3m-yYQpfo#zBm z;&*!uX#>LMHR5s(#HouJXE9+KS$hmD*iDEhi@-^LVmf{hQ&L>BJ7znsQ=j)d8o5%* zfbp0taKGsS-iT*PS8-BlO>Y>oyb`UkTlA=dtzrMGbQaBoSEAPUTyu?Ty#?UgOWP|B zEf;$-=0WLH9h$G0f_#Oqo@ZD@p-|{P^2T_#)wTRwC0DLABR`pr(k2SIgEG&CzP zW{B2G;VPY!3$RQm%hEVHdavM+kf7qbc{<~)>=Wil zqej1V`lI}nJB6YoUuyYzzbf&^^yUkPN2q&xb*6;8TH)T^n3^)=>@Ch}fnK_MWo+O@-K7%CZDc?O_#uP-KWax5*RSH$HTo%KGD>LuwX|=qXBLyP5W!)s~d-XAQ zWK(v1K}PFkRAro_j~I3iKQ>#wI=%ls^$*sBLLAFs}6+$-!ZL(T4`_BF`C zqwI%Sdeq+JL*)7I_qtv@+Fnv?^nB9jinI=uo?EiGS?{&Ob&sb8X}s#@-qm0ptoyVpo*y;Oe?+8D;!u>XGX@CDh& z0`iqbX}+(^}}_J3;hUqF^43WwUk%;O`I00e5+a5x}P>jFQq zoKJE7@C-%Hef^-067h!{a0n@m>Gqv{K+T3V44YM7gqpeS4ut(7k$Ut2;hH%9{Tv2j z1fD(2E12^xcyg7q!pUHSV1)cg_{=Yk zRu6ErZlU2X&PA1o32Njnb1mYlcLz69a!39@oa8L(V!L7LZ z>OJ1!Rm6>?0JtK??S^j^rdGsg7}usfRGSpDJ_D9t4z)I9%D=3w>iz2v&V}C%S}($) z^q>)(wQFQ#kKr=_=e<_aZgmsj*+2?_g;BL7xv74J@Lgdqg3dd zw19#T^A*4MdvgPMfGzeb=SIN6_26;(4YH7gb_cn)D(L9X2-ToyFNmi1RYyxKn|~2M z>6u_dh}QOn?f9Voas{-YUm(*zJA-M4qTj=XDu=)|Ld(hV;lz2bzuGZF&5{#iAN|=9 z)W)f6frTS|(+=VY;TDq^8RTFe6GJe5vrT^#tcej0S-N1%CjwTx{Yt3#8Kg{2eCV%| z`y2>OM{$ItJ0f{}53t>&>kO+4=>t}Y$yxtlEa^}bJ=O&^VW5@xF+jQHqf|oAcXe4b zW^NNbN@rP(|BuGPxdGW_!_d3}?hW1mZiagq?-y5$)Ua2EpqSRqYSXkuO-~1U|5_HM zIfh$1qU#5AAvyA>3L?+}r5fZ`uK?#22p~Q*9S;JOYNVj@OM~|$^xz4#%AHG(N}`-i zrC*|muhG`X(+5~ETEa3kun=WgfDsavja%`c)dDSvZg81OPzV|IRJ>V7f!+#oE(E45{t_imk*qszm26Evz`7pa| zl-Xluvx0Qdz|jVkJ0k%pIcgv=X_r+j*xq{Y;=B69Na1<WD?_7F*>IzV<0T0jx^4r?ZUj>=~l83nN z412;0t@Vz(QU9kS38}N7Ng(HTKwVe9{5N&pKUZg(-%gOERChw6>PM|2hbrD0~ z&dWwLq<~vPy7ie1?m{AQ>?XN!kc@v!rh{!A0hq|o7)9gF}K?&e-J`_A&wsAoh zY%xkvDlJDj(E9+n72P1fi=w6{z#gtf=w4p=E~T>y2y3+B_dM0|0|8yd@!O^&(<&M1 zCC&w(y_>K5A7wgNMi~rn9ot*+P94=O=+$=u?#G!TPoMPkE9eoJ_W#^f?n8njYH(#0 zZ*(2cpptRx^~$)#kr!8`DhufHQ2`kusrL)}JCHywfGNb4YT=YANY&%G;X5k7oHye5 z?9^$bRdm4g4%J}5hYg#E{osb=ilI2k-vzQZynJ@O1!6}+lH@Y{KZO|SQx zrZrAI$P)5Kq_|mKld?S?Ay9*XENRu<#REv2OM`=Mtkw<4h9q0ike$@eB-lKu2H z`AA43e__L=Hzy!^Ttj6OULl-q+n^)o_-OHMAn6X`$Y%Tzf<;_B#rK%`X@~cBEApd$ zF=NGc&9nTp=wwV-%s`GEuwqyupwKcsOL@>Rc~E@tm&fj?m?nJQ2u1Frl1l;Pa$K@9zY>E!dQ z<+&<8yy-e1;DLw#i-3>8feq%Yp>eXuNH6AiA%z}|Uet0ktfucM_-b950&JUohFU=J zQMHNO-yIon7qFsEdk>U$x1th)RIe-(d%0zfk%(J%0r8rU`7xrM(m}Uxm21X9Ej$}- z>@H}n`QB8t*qNcF7t!VjWDoHUv)eh9l-|()(Io*?IO&ai^d%(wg6xw%mL`{Wrq=#} zRfm3u+E-L3{14=W-s03vcwVM57{`L8Nbbv^1%20Kdj=mxk3KzSH1Hce3|{-=%0qy+ z)7hqCb2{qBhEb}J2FX^ld-^1sF5o=yb)i6OUG!G?Uzu2x8)S)(#|}SX7%&Gyo15C# zlu(1^^PrOdvwcQsfWb9P?-FbsC#kl4kgicgP_wAJWv>bD)2-ZF!lYvchAC&zCjHB- z(F;Mwrc*E4-AQao2^Yi+l4T@VFSP20`8e2$rt{cT3 z;J_+bnI2X|`qojc$v251ZQV3V2z0hVZvgfGc8bfeiFG*sD7{LqN(OqA!v@F3>=SA~ zE9nRdYknty)bQhQzIAF3rxf+6Ihap!?6?uiW^%Z*GW=kaZPMrTV4*W#C94C`uV8uQ z_H+PSB7Q+v-8!;e81PLND8wGcI%K=*)Odk7S!kG6{t=Uo9>hszoi}uwib5 zHE0JB3P4bjG6eLOnUfQcTGTaC9z8q&Rvf{y7l|HKyO9||zW==$FX@G*?&mlcKvld1 zrEdkB%i&TUKMFBZI6FP*)X*{*L@iAH-zE_(q;IpG~=O1}1q*Zi_Gj2f&E% z0i4iu-1V#eeLz*-T1atE1vH#}2SCioEPG=$?tnD(#T&IzI&`k9AbkF6E+zmbNE`IA z^0razM@*Q3{}Zfl*Y4P`Y!-}~3rvjdB<($i^c4}Td-g`CY2d+(e0K&9^SfoJeFxYY zk2hxo1#@!%oD}p*_Lh`TgL<~!h&(zr$QQDsCZw~#js~H2)Kw774WgmLRlcWex|lli z)4e+&w8vncX;;i%gM$8?{rV*Oa$=^jAN01I%QC1YAqu6xD?Q;I>5pU4)phq}W;pNR z>pTJzu>P8WdRq=7YPKbkzwG6MHfD+0rGfRc;{`Db0KW4A3_~Y>$M5)uj9#Qmq!+du zXbe#q&#Ur1{?TFqfISB<8-7}tgsY1&@q?oo(ByyB+NE!fvCj(opJH~g3V4+2CGAqR zz#EsM9L7T_B#CR+y2RROp=Q^LOUy%w@jsBlm%T!oALDWZsPjp0=Y5`~!)X8gX6lZH zqjlTh8jhT_vq4ufC80GQ7l5!PDv6y`oPNp-<*>Qdg-gcMDu3(awVmOKn0Ai34t>9a zxp_dJRB#qojoDbthV%0QRH(K-)#Rlu+@b6|7c;k_$luglA_$=U*kS0Ifl)bY)^xMko zMh8?w#8%%uP7FTdM?{)8`M)>?C)ihwE^qkn3~J50Hy#=EyH=#1S@4tTxGiQpA$#N^ z9up*rkzkD0ib1U1RP()8Z-_Qh*koXGy+Uccal|ClGt}0jBx(zAi2Q)^E%&Y0fp~r( zka?LEA=t)IKB%deaF3&wZacr{F?NES25ha{v5;KG{I}nRpcQ}&Jy%ijkz%?$G`mVJ zk9%)%qGfwksMX$N+EmlMqofb?Tz*H75@#tkTHy&;M4Sv2C+!JosRxqf{l z{MxQ|F3zos{PME8DTbSp@Q;VS8_6zRs5X{z_x1+hFFH?;HXMzHtz`L4R5-1^%>oD) zkndJWh(5Vaz!loc-~%MTez(4Q(yP5g?Cf~AR+@-qaUlCAiK`N|4^YvUzerqp$U&FT z#!CR5zN^xhTjH1oSW!jsy}StbJm0Z(J;(aNi|M}!-h-*LQ_E(pi6z~BfI{24O?p}$ z2{Q!j%`>I3w5j9Jo;$yNGbB(E1U8{w(}@W5He$Ho zWL*)eb+d>dhitH5>1d;oA7GLxDZ}B` zJhjTI00?hzX=BbC8RjK(-Tywbygl5F z_89!HE-q7=o=JkAB0%^y1uZiv9H z@(0^rzFIKi92S5ue`22&C${>gxuaKxdQG!Zd=b_2HKgyN&|mF1<%PdO>;%nDa*ss5 ziy+rnHk?JlB_M&ULGL@QC(x>6Mx|-_iKuE;5_6W3fYjcE==jx=Sn35qBhAe;2n`a07)d$yG-_MNm>sHdGO&WfyLg0dv&|bw{P;? zojko>$n~!v>JhhNw&t&?nIc5w4H|zuBvj0=o*HlMf>C}Vl(?;&$!(@jwM3NZjlC5TL7eXzO zBTML`$7lL@9?%0}bdM}NZXo3~sy_7_q@w-oZ8hnK6y$P;u*G z6o@tD{O?BZ2ji}0t|OxyM5qX+-SLk=M)d)egmB2Up;jLak`Vc!YU?E*6;1vCgpd@4 zINiIfeml^WJvy1ccn?7aw>bg`_@|rL=s+b6PSNo+e*Y~1@>d52h-zzt3(l#Y^QBpb z*Sd<|ZnnAIn)vLM=DTaHEyZ7?a=qX)pu1hABdP3_tR1uq_+}lx_E|%gidpTBcuckc zN%C9K4{lE59_{`j=ZgQ&0m%#idFL=r=#@H%K@%1l}t zszrCR4*cKsvWyy~j3ldv#V#NyL1^usA^_OL|l-+qGo@xU#N>Gay;g@YHQZM$o$D%aelx zb9Et^_$C#zr|wWaIc(%_uVk-@KVl6TP1&6|=kWx1mmUlJ=J9mJP&{6)2uHwngxkPk zm*+yta7pf3&qTfJngRoQh?`GF3n;xBK@PJy&R=mE==*no7I^Wh`g8%jFx%f9hg@=Z zaWHQU*Fsd#G5x@8mGG2>MMMeo${&TgC2u}9nW)DK-&5>E>Q$EfxZQl2TU)q3kHD9# zH-Ci;WHT+&?g0%TfOi6(&mq2dQ3fMI@P(fHvj)(=wleHBfQA$gm&UXq6ZUu`u+_o6 zwuR!4S3$VgJ*b}Yv%R2zk(~JcViZ}az?9F{sH^%qmG`P~8VEK>op2$0{h7iNd(g6i zr#A_9?Y@kqjSw$0+Y1S0NWmT|h2b`9#TTg@&$lS`O66BW^IJvv8OCFuGX`bh7T5kx7O}q7*O*FCAqvB*- zj(yJ#2T|ejfUUb4rTvqVz5D_lNI}xk;(%K?(_psRdo5jS`lDq3P1dOG1+k^gE!KPI zH+k;TY{0H+mQT5q4uY2UI&pEcm(L?g(8Jx1A9Rd{c_Cm>kl#a@>ozi)(%@tY(h2kv6~^xX ze(WWYruI8To-elMIf1rw$KUG#XhS4DSy-pw;0%xb;&-&xDMey1@TR8P5n)Od&S2hF z{VOS~<|zILr1?_|f{K14#4XR9P`HX8aSvpO&%Q^dTtrVPp>D|e%vJzVe3x?PV?l0W z+oV;FQkrNWf36D7Dd_hcBOh6uX9rYRnS0<+!Dk0DeurdF(8Ce_lMdTMrRiIsn`g96 zZl%IFYTgO5Dz|U*VB4vxXoVOpfUUUe0TfL*MeHe^$7RQbIJ$FqOb8byXdvcuPv>gB=C-=P7&toUCwBFIwxeP=$~7av>E61(4k|qfKn};Ql=b~Y7fp! zH31YELAvJk0(IuD%%J-*pv11Au%Bkd3^~ke>Hq;17m5HU@!)gkIP!zPSF>HRaGPi% zj2C#nWN?WgIrg(tKk5>=58>@QQsYeqZ&=5EklF+G3gO|`Bg1csi{5tQr(L%9;n`jKjd%ZZ zrXUzn7LcMj%|Hm3-VjC$TC{i-2#A880FBOJ;M(~vpdQ*l-l!9o$~Nr=@clRa*k8;5 zu97lr4ewbx?A7Z)tu7vtHdRA+p1%AX1_w8@dMN>grAZro4gqkWyZ8SJhyghTsV_Y% zLx6naIKUwJ^Q6(gUIiK$Pj`Mu<)=2ug1^sl0^V7DX%jDx0yTjgJR))?sHu$WN6w5FWk934i1h>o#UGeD5CZE8Q3Lvk^fQa zxP$U^QGO3-TC0A*lz4(W6c-Lol~XAFbzc0xo|F#kKq-$WNe=`aoN@k<{nzQ`^K-jD zC&grMa%SNn!U0My3CK}`ciP}UZX&jmu=U+$av%?Q$+@y!X<+)l_`b^^B@Z}ohu<0r z5nJ5qC#gUWtibrW_hW_ws5XzE^hl2`pqnc3Hc(Z>3nCaS-)^{k2J>aM*qL0s63IWc z#W%qJa~Yk8+X5K`e!)bL;R8(D0cM4XMs#g@OYfbcWz28VGY}m(0>TeDRw$!vMN7Ge zpHgCECNvfS?*%U7U(V-7ae^)O*|}7V%GmDG!UI%LNAy{knii^qQo0!a@KsnK*f2c3 z*Nc+I3k=Mgg

By#M>z!VZI}cYhx2&;CD@y>(dB-}f&%jG%-8Qqlt`9f~vpqmm-s zElQ_!x1fTQAYIapbPfopASpRCNY?<;FvQ)1et-AebAIQZd;j2h{5;QR_IvNOSFiP2 zo9U}P7oR2D-{3yI9HSpDj|Wq_kI@gpAO3?yS0SDijHBcur3d9o*AFbl^A;d!4ErVV zqV)}){Pb@V%t`(x_L|ZCJ>+UqH2;OHk4*4G)5K_B$-4unEhFRCymdPuTMM)vG-C9d z{)&&=F3up{Eup2-xnZyifD%wpyfnLb;nw z4ct~qcmFV_B?v9Y_na0Sp62uo$R*12>$RJoX+)o#ZF+7B*dcaP-Pciv7`%kBN1s5c zw&i=t9#g7Pvz#>BagD+nuhIro!+!XKrA$RJzEJM+NbTP>U6nCTC?y1(bF}*4JqEut}+xc``gs8=jh>PCf+TGrVnj4Gc#a|VCWnAlh`Wx#9r^}wk zZS-0e5l6$f=2Ii`vvx?^mTSDcJITQ2%c0Z)(fip>fTx-OPyIoEndhv%_}t#D;JGoQ z)SKzs{Ih$=@dvoyRlToN@LsWrA{&BaubrPg@wz3}TALp#xOqf-U!vk$vrL+CpjIo0zOb@k2E!T=X?_13Ba zlAE*0tPK|?dWYiCJwNO4-sPHq<)p{pNP%jXCFvt=KLFzn?Gzi75l%T+O9`eaFCBB( zy@BBV^XMuTIlQFm2b<{;tF$!NpH;I9qpQ}m}+QS90xlvJ&%9U+q{79 zIA@`c{4}__ht&5;wHFVe^`4`sK=pb~IJ=A)S?w;jh$MmhVddu!%`+EY#+*w7H>`HM zMVBS$&D&a269mVlA*)!NN2)b_7HP)#WZTywg*mm5%*M~8zWUaSU>Q4_i8cQ#%;4Uh zt)<9nwF2tvYacM2;<{=(W15C%T^u!*NY-l48{_>S8rM8TpwBbsN@(0|&7&{QwtSb+ z#q<%vn7#^$2|$T3wV>+SI{OISci-i9D9l-Bc5wLgy56 z^Ad0$u{Uhs#OYZm2<~&BPlMXqd8fk9ZV8P~x~=QG)aBAPB5W?c4>x5&Wl~&9eA1j_ z6Rz~^y#y)c@mrO*B&MW?Z+eqqKu|d-mM*GOLc5u0kY?%~=fm?HdLol^gyYnfcB_wS z6ewQfE7EP((`K&TG{u;bYH+a=x66PoQUe25crs~V+=fb;29D7}rz_D#0Sfzwjn4IM z$vAzR<*mlwJcCx}?##Exm;D&KlkA$Us;3+tR=`W$!#aOiG~?eyYB}GlFlw&q5ww5pO zf{S3OT*ij{i|WAcn)&vIv9IkvE$M&r#ia}q60VNF5tbZH$tWTnE}^LzYrUQqc61tK zc6WAOQPk?(p>@^t2Pj&Qr#XfQp#!?QX)?Ep-+JI%IH$DUKmGC(`ixG%O0s#8MrYssb16hwV0@Wd+}o~=e}dfi zM0%gbb0@)N)2=Ei_AxrZ_gHY8oCKcgJe|FC&~RZJtL0?mxDj>Dqp+&)ZJOtMRMH}v zW9s#kOpv!(+J*1Z+PYo7A-AgkZh2MG9(DOl>b{Nea%9M8tXtqF$(&ETGP!W#$cm zoq@+r>WNH6be|wHS(Sh&N!~kFdq9tOj^yEDjOwVQ)C@X(%sVX*wL7d~(9hC%Jy9Va z$N$XI|3!R$G-M~^qM?)8b?3Eim{cws1l8;Gv%{+U0rPoJN&SSvL_ES};>$mG|)L^qqnh zThnYlf$B^`=ja$uJ3798b4hZz<@W@$H4EuvPVEM~P6F;`%Y*5QcUQB$uWh{?(XzR> zyXi?cKIo%=XMx7u^-qWL(kOA?m-Vy7FK68+>qRC^2~M|ncM9#|Rr>_{!ubKXv>9dT znMo>&%zGBbv8z~r@LB5ILdD#zcP6|BDYh3*t7j1;wlsToEor){KgF&Y-Bh=?W3|Qz zJuC_9oqQF?MQjJj-yu1oQE6b`wQf%S{^-w;*8F2#7v_i$XDm(Qed>P%J?(Wz>ld1n%v@bBF_GTkL=4TDhL8DD zlxO|;K4)nNW>6fe?M8=-b{FY1MD-4-?%(e_n>TTs{oS5;IDPRewY5Zb>_TW$F0+fs zT7`gv@1yIruy<2V-Var6E@F3qfK?pSjds|6k?N*>;Pg)0t3RmwEuHsE=^KWwgQx>U zOVxeN3k(S9AZT11pMm^jA6k{}vqfvIgT(7qpo{Af{Ev_^cjcw+xk-Q+&dO4l|-OzEYW|oYpu3*u`*`aulWWQc5G% zLf(bQ1V`1kjQtu?_?nuw{IxUpTNU%wxk#vbM28mmho3zf9}kWl5thJFJ_OG@Teh5u=wtD)H;u`k4Z zKbx`JE>^FoiB|D4&)|Uu2xE^CBTI)F&dDsc4`g6A1f2b!FRh``h>3@cS%D};`HUDU z)r(DaTG?4j3!f<3X(-j$qpmU~R&O?!3C+!xtlt!7BZsfWEOv2k)Jvs%Dk3$9d=qKw zmZhjVY7eK~x2{x-oapCPq>7(@JDEyyraY`2U#W1nL{27I_5M&cqFk*VOuaVBK%!u& z_Uczw)CbE7sZ!RvDz61*9yyLlik}`4SB%pM)bvG&dHD|dM(;DT&6PYjOcFg59cmKW z33%A;(8Xl}>KL9q4H*F%0&}X208djgH3WvPG#IXP4HhtOyzA0H^~<}JApY=Uye~dI z;=zXSk{0(?!<#pq>s|Fwq3nK-w!ggU*%z2G5?GFJyLBIcEi^>U)yxHbPF)WW`MIeD zO+Nt;=UmXhd{rIABW!Zk=een8I4w%yxb@WZm)KKO5djKdCpTG2MY_msI!^@Ec}q7d z1)f2KZ1-^I5up{E@uJeG9KN0$785WCK&Y#xDygzIUw!QHV}rt^WhWh%LaOgaU7KUB zk*js4$csvci8pq8rq1Tpif5j$0rq{ybQQOIJ3KjH0dE$H3f(|=rQq*xXBsL z;*A3*ofS5APQ$poMb7>El19^+hm&f}Tp45zSPM%aI0zTY_!A79UWc4WuOcp5q)o&2 z>!l>F2ED&HIrQz;f3*p)YhGJt5#0@2zQ10F{Y-ZrqAji&OLGCG(X9bT#fOw-D@;?* zmlUrYmRGn=In^cjPS;K8?n~PV7T63_yI<+Jwpm~8vbJBezqtR+6VYwD$X}$CWDg2X zS2VvMZ4&_oXiea~&;~biLSYHwXUWdm!^0rjgwc|lh>PLobA2lM6HCrnua?uZ^S4w` z*|2wwzdLuC$9;V3fFUV??6gRy-WR!fBD6Qy)Y!QdTZ0ZhaP=K6M&aSZ#LqSychOb+ zWl4oTx*0BYSQlA?HB({UlYEa6SPO<$-iZDa-@VdU^l?#yYTdPv&;H6!o643MymelI=uOrCo$Kr5-4u9Jyth{ry`AzyWUhxZSHzOL>IrhH=y1wPvtm75R{%S* zPt+kUp}^$~zJ=jx5!kxaU1cV-LT6f9$IJwirBqH$WCtSp!leYETdVN&V7jMnF{Da> zv}5h&?B>m-^TE}?k%&U?DLmMe#cAPC$?l5nlT3a#39-$rhp2tpGaCaCqoS&)5xJ7r za3U$43;I)xn8T_ky`p_?ApIWgE@Yxo%G+Y2;ueqSQ@j)j;rx`+G})kRv_ihDciAxz zF&I#eabE93sgbQLW!C}aKu}U1fDqs8D(&7WbWRB_>bDU!OQu(9nff$N7YmABt{o#O z<%np4&!G4K>P?28Yn6po$6|65R8FTJ{svM=`{exP4>^6E>*H(y?leWJ<7|m9tglx( zHGrcrZPp_nSC5s1 zP1|gXBYWIPmMk1x9evhOt~~}-+uDQvPhA9>4ef={k5tOdE!Mq7T$ z`@guFeNeW`?4D8t7i!Pj0!xze2QLteFgzI5yZArA&-1u%sR;fldFykWx1oMx}A@p=vVdit~q5b+GqdE(jT<# zgNMEM5&9gxkez(9Q~}-1U!)&bdxs|MrksnVkhD0_1&Yz)0B%a~me#(}TQOJ9&}~F^ zUqlMyJ-1VF@lQ-gY3kbr)3_Gsw{b?-Kz0Hn#P|zK2WP64e{Z0W7lvnh)>{Yl%ar@8 zo4sDB4o8$THD99B>Zf`IoSTmNt?K7r^82e8klI$xjwkQ+=}XvWF78O9)l3V)EC#aD z=DE}00tC|B2_VV`^@sTO6Ma*Iw~s(UnXj!6@h!Z580{;15_z)nD;=^kb8$S*fkr!5 z<{?xYH}7nvHC>#0?w%ysO~iQ~CL#^2Kx5<(H*xy;3w)4`w|1iUv)=1)2p7+)7|OK; zaeW4)y@%m|(nbp6&kM3`gG0Tt`nHv$8x=j4n+Wfi1kj0bMR)1O(YeE@k7=m9z?ZJ? z6!2-K2H6g*s-z(E`s20G?k5CXFVY%(xf=J_98X!>P(=cMd#PlFN#!gr9I^6sk)mrS z!Cl4WsVO(=ZL_Ce=Vn<+B$pftqId@vY_YS(-N(0gd$gDJ3vx~4XsY?2UfrU;^Tfk0 zB;(g+VpnheOiP1ZGuAJ^-=Dq4Mr(0RjF_s6bMlQ>u8Ugbmav-um>5EEo1a7$A1 zZ&p<@3KK z-DCaw`AXY28M3*G>%42GgK%u#^+=@LJa2hRm*kUVXV}c2WpJ@y>M_A{deJR}wl)o6WvW49 zHLjEwq9)Gaom7<`@*krPGbti7z5E%iN!S_j7A#f&Dd`wv;)2hG|J{U6|j~*VhI@LYwiq2Uj~ZP=&y{XaV`3cx17n>^evGqL-F;#joam8pItI3TvL?D z;RQlTwun$fLiiIDfu*ayCd&%IpV!T7{8@gA+PJrzY^kB=qm7=#3G>x*R9~NqJv5M3 zO9Q2}08f2p$E_<7l{Q+hhCg+W<+|$Ifx2(j#QCP!&eKK&HK0Gy|A#xk&v9xamv^0h zt&Q3na@VbX_Bo=JPTqCKD+p1opA0%h&Ijx&Q0oGC%CH<&Q|GfM@|4;6sxL_IuOfDqlM4Rj8=6;(7ujW?VW1@gp zo9{^29dCUpR_&~rGD-7T;qS*6N2K?AFpns3A56nLIM=1T>fJ@z@>Km+TICO?``>Qa zo!AXH_b46e6t*i&fbhFuW<<}o>xAEGg12tAzp9-I?ySoTIN787HS zdv2R9;P@6sezCZ2e5>a{)jBFHKWs%L$Hk+tl?hSpR*VuJ7UCuUcoQ7ZLG9BIPxBc?NKFJ+@NOv^)*Nn_r4okJ~q^k^2=uaCbL$xf9PPZk9W8 zCGP{V*UKa~AveKiLl=&(3=LZ4-ZgYtboQ6DxI3-uG5C3}As z0!fkm#i{7x6FXxkO}8x;xv;zX)^$xTL>DOwQywP1-D`rHcQv6MKc*RgPJVF{Wc}gk z2)Kcd-9k1E>N^GtMA#t(Qdv~4w!O2Cnp&9+cba#nubHOSE!_|0I9)%nXxC9g+I#I5 zyLLA^_L-O_)aBZtwkzr>e!QjgeQ8h{lnR{d6rs^4o+L9{BNn18L()y8RMvdU-0 zX|2-x@xJfgpIjG15Di=OKoLt;xM6{@NL(G9(5;aJPKq9(c5Bmf_hnn*lgotnE2~rt zdN!f3dc!zcenhE=OMd$q>Z-sN;^On_nB&!o?SSTx?**Sfe(c_U+UP9}T7oEv0C(QM zEG+#pfN?hqfGWkoOODGb#a}obD2Hrb&b(ca1YFj|w;c~_+b&p4Z>F#UgFoYQ_j z2C;+-KsprDY$NxM142GUw}OSRKv4uQ{UqeBpEWkdj!I#Kso>sL z*Ho!MeS^>d+*_l1k=zarDLZozNToxe)Va6e`=nqNAMO+20V*#Eg>(PE?3Xau`#;&@{dRkgGjWdu{J{-Xk^@^D>%l~XP#kH(5YHFv*d3+nMxDG8~rz;IfsG((5 z_B;w;Q#LDS#Z)H+fHy|<`yXp>rT@=t7T3TJ?Ql4>o30Lr@Yso}WE^bcsAu#Vs}i#0 zL)!BfL_p)gCC_AbJ5n2VPBvImL03Uu4^t=1Dm2_22f&P}X7>qUXh-9|%=_^+a<%uu z(ejVevjC%h+|q(}NWB7o5j=hN|Aln?mqjY^sk37A<8ZxLvH^I|-4VvhOGFfm56ee} zSrKzd&o3|OZ;10hIO(0pE$qm}^}$DWz>KGQzOBCO6uHX=cwEBW<~N|zkG~9b@9bEy z{*(ii2X454^{aic*O>xDyGg8Pmy8I$ROf1rkug7b#y|Fx5a41C0^H<+{ok^!&N@Hn zwgIC;-2h4F_p`wGXmE#6Jg1YV^@IBa62qt*pmqi@YUh7t#VXr}SjJX#(_xx`9Sszp zUAFS%MMA{CO}K&`cK%ijU1QwCXCD90+K$^5Xkh&_k(rII%8iv{GsBUl zi-UGWKK;#PTg2+GK)I zM)PlRno9cwzaq3Vn{J=+F9HEev?uGyAU1 z96`ZdnxCdHrI$dt|8(U7R03ThnbHZ%03})HI>wJZK-$su=Pox&MzeXc>Jz=6lTn6% zu~X?H;*dn5{&hKi3qW2>AIgkhTCB);hK_SDm?x)xNF-NFM4~ zz2BQ2=C4oZ3HKWa3+H%p2Q++5`$)-yor78P#ItYR&^NhT&?&;u3+z`$wjamN{RU;`!58uP4TU=Zc>~as%7^&>@SwBISU&G+ zW=L_t4AbvbKwLYDzl=Kq%_;= zhZ8(erT+Q%dF(R5VBqL+RTktn&Uf%K1=NHbSc#^$n8B!*i|T!h0n;XN7v%o=FJAi! zWxCO(5DGrGz3|sX&t^w$?Hv3U*yK<{w*4n)cYLtk`c!LlEzVIuh;Gada5n5PQhNx- z3wvhP#sV0W+(!otTX=z9k3%Vt%P*Z~b+H=2g=Jz#s8cVFPKmNu-br{1`v`WA=<%=t z3t7s2yHt~_n7DrZC1zeg+t9bI^!v{^z~+pw|9VNS@nz2i{Q*2L$k<3~y>2!tD*X3J zPqW(SeU+yA$BG)cro`|Q&+y|>Y_PSkj{%rXwBjFK=}7gyM(U!wGrf($Q8&?`C7wR? zq&xI#d-QM>50oA-^}6(pe{PY<))sFKcRm=^ohSXI-Pl9;`AX6-X6Ur>NwSI+)a!8> z6=2e7(5P~6<|&xyeN36(0`N1d!!n0puA&nn{|12E|7&uoD;=D!z6+HJikLVzk{Fby zce8#*yiSD*=-_w)R@7>UZ*2rCy7Re#k@W5)vx1+zN)qD2w4WgB`dG{~!QA%^>PI$YPjW~Jub~))Uc9fui+-LbUluE4bSXF4 zge!Y*opRUXpiyA%K{|jJ5{%pP0*lH+JX;G|=U$S6xsz9MbS=V*t65ajnaUvhLsOXuP zg{!rK23D@LA1oc|XIa*bAN)9UBg#cF$5g;RqEC$uPp80jdJ2{h_@xD)zHFdft-@=H zPE%&(R_p+7MJJMK)i!EVtpqk8Z?Tu)D{k)`w;Ba0e~$QBjZp+bw}B!E7Y@vU5h{a4 zB<_;|j9VuG@EB*fBGn*2SCPjvnx{rEEaOMsQV@!WR|_hDWuO1R2VU=etZ+x1_4|o` zAAEsh{j+{7FUXgwe}WU7_dAWx@|Lj1>y9Q1aI6SNVn_aQq6902N6GyrVB?`+2d8Mk z7VIT0YBT4;PzC~qmHaJ5XZq>KPcSi4z$YSVWdj}?<*N<-Oy$7zJjF1q2X_dmLZBL7Lg#Qus6sMY zf=NZB`tu&A-Y8Oz@_X4rMu@}FH!8AYic0d17dhOs@LcjrqBC3SwzcxL>P@Y?nBl^W zTwEa}4}sjuQ#$Inj)e<{KneZ8VD)X<7^7t)VQgx~*=N3UAvEE2%!5l3_k=$YSCbGv zF+Qo0WsMruAsa@#=wf$~@Z*pQvWg;{#UjO%!uN+@zq|_@k;&648$bbF;Da=YNFcrxVc+)=dowh}ii^gwm7-#@^-Ar^VutN!^Mf0*g^R z!w&$9F@VL0wL*d**l*5*lE4Pvh4`_by$9d@{>dRD$wfB|V&Tpxp>z8WZ6OhDF^=pp zHrBxHW!%ft+>rJoUk!E?x)BWbhfg+N)OK<6z9Gh(6OSm}P!88AU|50=sv=*auBXy3 z*Orx^*%K9EjyuHgXq=J=#`%6*n*TFiU*v5V1PgcJBZPYZ$Y@vqTpfGv{H$HyM?LH= z5jG6skBj%KR1$YEDkFmgc58aRlWfq-S!b*0R*nq1$5Ydq^QEz*#69A?*}TsKyjHq? z9>W;1kIqcLDaYes;R-(W^BJVjlrWWzu86)@sGj%WtW33_QNuh1Qv=a-{3Z!pD3*pFp*zmXEc<{9Mb2>l?~ut-m6v(T~dkTV_nQ~~Uat?752J>a<7YlBMDhHQatb3{O7f!vhV1b4-?-Zh)33R5TZF)PA_3{%ELOmz z4A?6I#4X9@_muG?*^AvaTY7WZBuqmBTD65IM{H!49pNoE4)w zDZ)4+@BJVN*o#+-1FxwC3a6SrP4~zB)yEfEVZjCn1-=Hm2Q6u|t9>c`%(la%%7P%$ zCy!0aGSAgGtGQ^_T`9ft51Sep0l#;dpl5ONi@es1t0@kfw-$Gf2Gu=nQG1GIB{2dl z@bM~o_Qg;Jz|Hs}G0HJOz%sOI0ZT5tgOi=qy`@cM+3HDP@8YgWcA9YAD&I5oo#%94 z8zX<7Xsdq^yBKF!VtvBPK$mb7^W1mQc?^KAjUyC9a*YC_oqH3{njW2w5|_U_+)AfK z|GX`C+AS;c03S#$1ly8%wi6x9?lPnhNu7-=!3wvedF-EWVBZxIRaHWvU6A5lw@FT2 zC4b<_i3;tMt{So!yt-bE+;uW0PK;^%d4=uXwi*GT!dL0p?M#J_%xUfhBYN|iJ1bXQ zk6!AeP5U6d2Y5GL691W<68Pz{UNR`G+^N}1Ev)xr+OgT`c%Ihbw~$$uwS4Vbn!9be z7aHZdlVn)RX;nPNuV%#7O*`zH&N?5&OuL7@Z1VJz^wKuA+b#S{5%4$ts~}puiD>-u za5`pJgh@Lx;-?E4uc7i<1*K%9qg4P%Ay71ni6LS8l>~KUs^;G*UgaY4?Bf3#aUlsB_Rk zjUTY-g}woIDsx{27aK;%=r6UBO9*?_=>LR_CL3}LU^tzPx5RkKil=Pl%MM*+h=0-s z`0cj@;1E2Cd7^>QJ}N7NgPV#g$Q@UKADnQuvb^=Y9|S)3DuWYMclJF7TDb3?2>etQ zIklG^{j_v*bX*L(<8I?*y7RW5rBeH#HLmeHIOCqGJzgZ6+p2J2C}*{nI&Ip;pyJMZ zf$>uLB>wV^zPNn02&MtAP|kXDdhb!z(Tn4~hsU5sWc=xqMuBC@x6+kjk^FJA!cD#5xV3dnOK2yG;hju^LH76DP{B*A7(N+gIbq98>Cz7 zbUMBDh}-pp(x`FZLnCSF6Y<4=1+n`#M<7VjCdaw@im*Qr#GTEB8s z?h@NvsCYI~`aXPPSXDu|N%3>!F4sxnoPlTa{n#67{7sd`FOKuJ7u;IF`~WSBzDsi40sUb~UB^tQ9o> z(rEsMFMExFYaIs|wjQ*ib5@+mD{wqj!gPU!c8!|&F|Er&fc24eG$_d*cn7dSY|7sV z*1?+vZEn~d!s#Z0a!Ozl>=nxI!H|UCN!JE3Dh3Xt_W!~?V6^+TqN#pT!%q!sBl$6N zTo3PRzHcYu*7N%orwuPa-kiDoRW8?3{btUT(+T8ZqFwLgt%X>TO;%i!_5;+t6T?7- zv8k4>%z)JJ`;Tf?)fCfppDuZJ@NtMH zh0b?QTgdktOHz>~mcutg=`MBTwDk_nD5B8m!qH57UbC=E+%TSj`R1dE(cA{5i}}d7-U2bPuD^fP=dzxl4$SdLZ z-94PvIokI{x<4p+p;NRSa%-*}_0-JR%mv>PQ5D`oGzgX6g(pf(zs1k>+#FF>i&y7Y zbQ43~pKGjGi12jbS>8XI;D#qYX}m?sfHbtzw`d-8890b5oapMMuGmI9UnL91_^I2# zby?ctf2ZT8_T+ICt3?#MuR%zOqy?Hu%h!q1G54-Zi%UB>sHgyI#tJnu%xYUwga(ts ztctt1;rurmv}~K##U3ttv?6Lc3JQz#ST>pM>UaK3^UmN!Jm*pMNb@+Nt>9WHENy1h zucxyenDSod%NUG1`+WEy_s$==Qme+RQ$#vQ$)PV1%)$Cjo9V4|A|CSJ`&E0Xb8^-3 zB8744&uGyJdkq_nnR@m=@Bb-n&l@avepVNpOIL@aazBdRsMOWm^}Z-UcLl)@lFrh77DJ`T{zGsil+t-yshuM(w~#+*F7 zoaP1xN-tKI251Drc^GM{isBGaUZETeI}OD(=Ja<9-ns1InRN_aBu_qxY3S13{T2Q} z?XzP_6>>-ORgQq`q?K<)_$xluFLZY<-X*_2QBwU8rWp}QQ$3L*>CWb6aDNWpvAFN| zwN9&4bIZX9I@gM#289N))%`9Zas#GUjm{K&jXSNGrmrZQAwsCJ3(lfu^V{xpPZ3pw z&RKaSGe;Y*4#&Fg;dIpPip6MG%)~9mp1BwD@yvMomS|fk+Z=`lf5HY6?p361uC{Ha zcqZ~OT}+zT3At|Po1VCeo*xiI3R^mMP%P$7Uqi{vdy@GyTfF|PfH7v-fIDLuqNmpF zaU)wSns6g+C+WC~`rboUe<1uw|2oZ&F%uDMpjiqjK70Nmohl>B;ld~Kfuu0;)0O$w zystDhGgACh#%&rVD^4$a3R}cKX*RE`&6uTm_7^=pI!~_K+1wD~Ijf>D#ZLQymcDJk zyJPuKUpr{PSM)4Wss_JaY~koFU8yB@mM`F{?!AafYxOU#Mnil8KfyS8t9v9TgKPC4 za#NgB>x?B;*x|7+i;{ZvyvBx$J30BF10%Uq4bS&-WE2mj6xZ^)#!`<2_p>#vo#ere z-_kzXe)Gh@bD(ewPYSm>z!rfa26?KeQvJrRcs0IRg!a*yFu|=hz%0Rc;mrx+HH`43 zzT9k`n1h?6mgG=l)~(l~yjZkKpNUieOU(-UbL=@9;mGLkenEeh)A^#K#urXeqiq>`)S*e zbBo{SZlbpYW$z44fL8Meszk{|En( zB>8CoWNcQzD7j}DiOHe&S)-?C>L>{mI;LtR5l?x?`)VL2ChLyU6`;%0> zn+2-sDX|=WjgkKZqlxt^e(o*P*_6d*)ZIDyVnr8A47aO2R;Zu|Gxhd5)vBlqp~1>e zLamVOy~tDd+nXqE(BJB}FdH*&+n21%vk~>6u^EXelJpyLW_9-Ne1VG}xqm_h!tdBE z40~oU{*5Fgq+>tM5yJH2*Qh?3RdHICHaUN;1;HChic0EtLK4_uJY@uUR!4-Hk$=~( z^IhhBVkjYS~O8FS^Ph!m5eyR3$x z4e2p@l&YJn_uLD(84S0?_RI;eR9p*3QWE%{dz%JeZ1y?FlGh9^V(}AlI`5fZqfd8A zP)Y^LT+-LSUbg4gF4u#&tJI!v9_@GU;@JOLT&9h>7pK>nHknN4IRLBp9q!|f`rh6X zECPbXPKS9LS3YM{wVY?&M#g7T0UO5_^e=he>I44pe&uW+uNX26c$oi=I!q7Z5DbK1 zw-UY17PtQ%{+jnEirEjcn%lqgh-GwaG)us?Y(H4E{tG`d)L4uAWAk0W(D(WYu`u4j z11Ml;J~h{m8f6N(Jc>R$bez}^!kYh#G%QU1j$6+kts+C61hZv?QQC*Uub>nD?)Fwe z|HHebv;fphx0Gx8NJ`$B(Rf#B;tj1m9X>|b*^!ukWNH$5UAr)AM+UA4)p;pAF=MZOW!*!G_wQcXUc=p>&7Tjt^i zyYMH?3-0=rjNo13X(rb8G~v;A4wr_JuiQP5Oa!VzJeJ?-Q36d0_wgE=(2@-Ft*^lM zLOZRS@E0kU?ba)|^;Fu(AD&OT1C8_EwB7W~APh8jf(n1GvxPg`owbv2N~ec$XSX)Z zdYv6ps7fg5cM<(Tz;JUtoOYYYYF=HFGp@L>t}e{ZkhWg zy~*xU97CaO+oHUirrI{c>dz^g-({0mk6AuEo^6ZhvF$zaKAS1AdLu^VdLB7w!fA(+ zDV|TCZeKSccDnKHlcr9~^W_l&oe2Eb!?=-&)8dvY%~tNhRXUPVH*}^Eu6kx8#Y0=% zZVZNfZAp@%XC(}?+_8oVIUbqZ#oM`7Mh0Hf+LSg=5YPpRr(BT@Kllg|b$&%YREVLS z5)PfZo?GWcL}QcBP*7|*ao=&ons_R|O~K)N_VTkBsIlm|lwakvj+^zHa}y&gAs|q5 zcmL)U?aOT|%ja(c?Xsy`%3VkbtKh{S_~a}^1Y1Y2|GWmo2m(!D{)NjuAe^lw%^Eu~ z`dwH>z`Fm267Pq@Z{M7e7G{|eI5}OV^#)E)KfMrqalw_*z)@P?*%`z_n!V#UUXrX zyD4U{@rD<$Zr4a{xbFxnVdgjpEq!~~Q|T7w`)ZANXEZA}d+SfnBPZcwhpzKU<0-QX z11W3KYCG=!i-tlK<~*)_9NpWV7?3}Nr&D8{ zR5>`6Q@jO($?YEhsdNrmV%NS63w>W;f80dEjR=Qmhu^gS>ylVO8)cB2(~9C~J%hAA zu|z%KSt|Fj*_!4k5XY7qKXzR9W-0A)8zjEj949X@o~}NQoN<#Q`*VI~I#tyZ%vB8| z{mQ%9Q*!}QKK&!_qQCQJm#r2G`5g7P|2Vi8tJR8IZsS|!@zYwE=5%J7rA=VkOSA%QwHS`TYzX(QrD7rHqAfRUzQ?kVtB;Fu6AllG_FC zlEe6s)f>DyJQMYWqV;pPusbY8rG)?HA87stbjvqcZUn%CaktSukrnqzp@gpkAZIzm zwvq3NnV-GANyG4fi3_WIG-jrDEd={bY2?dlbcS4NJ%_|qoQ_HDFYDK;ga;xnAjB) ziebm0{4SKnL?`Y?VN0d9EQ?IZzQDo6u1{$$6Z(H7e}y}Ams7q4sk!Cd20mFd`-s^mH*iUiQS~H@IDtT1 za1-9ii|q!t2OVKhcV@VFoa?PYvCFITgP&mQxzD`*9=<0!7mU-Q9O+Y7K*~S_I~!_E zK}*YNu*phG^d=u>P{?Yt+Ke8(gjyu~aGP;MjfI521u=ZR!bT#-11R}d3XGtQ*Gxv; z<8NZM|5fB~Uv0_`C%?>uon2MG%`i`r5x@wDAh^C;BhuqYddgcl)95PsQ0?6i;j(bl zcWjyBhhY}KaU-9l5)uH9pzKAq(%)S|uzBEqhw0Y^@rdaoiT-8Dh-&V=G_3*Pv_rAMKb0R;~dz+0`_VivPk@3#jUSANKboAxx6Ux zHCzNpoBvf3CL7SJoocqdznrGlrRbIuVxaZ*C3L9 zgVEbBQMhB&huGf1yjP+`=3~(BsJJiR?TG^+FY&VGU`-{ zIs1oV6<9X{Qf|6h0y77jeT0Q(K)h&yF57&E15Zf@YJYJ)&n2!y8FR^qJXjN@>I$;> zNIa*NUys$^csY#ZmnBk?Bxeu~z2p=8+zOaksYf^}k*hneBmI_|xte{g+PgvN$GXqP zK&ugMr1k-AHRdwkMfrJ0N%?&s;*os%Rr&8%W|WKKaV`xD9`PM8=IJ^GPga3W@phi{ z4SR)_k)6PR=n%tKOnZYYt-g3eJfgZ4LkxtmR6#l=(>iN;-=kX4pJN-fWkVqeq>4t& z7!C6V?+qz#>ylz^of;t45l|cZEwdz-ti&VtRjjX*561FnywaW^vSt4oC@LL~v^@h1+^R-0yl)^KZYw7r25FKxaJd{YrAlKzX6? z?EZ-CH`2NZh9An28IKs?BhnSg_qB>7ND26`1wOKBC_G5Jc4oZ(P6xB9@!wTJUEr>t zpEBe9k59kSLr;BZ9IobIQYe0Kee8`2j`+Hd+vbDh#F^H5B<+?sUs%>UUr}@_KCQBn zwA5+7L$P3Ji*0$oI5wNU(-Rx3Jb^XsQcNKJiFl)NO&-!pBYN}{fDj$v=wa9I;rvY= z;$w$|`4nlmRW3=fa*gqvwL4f5o($R>B*0nyzc};;v3BA`CKhPX)SKomg)+e4Qjeo} zLT=A~Z(i(;*+CsigF(V;3fuZ^M@M=+(MKYn&66Q#$@4c` zAN|uDH*vR{qOLGYO=63jlo^19iEAMF``D7XEYd?`F1%|GKHSOgE`nJcSLxcYI~Fi% z+Q9uP?-q!Z6Sz1hGbnx!=33yP;i(nd@eQ9r+XMd8`#w{RMfXR4~v6vuaR z)_Acy@&x$t2G1WY-e8xCeMywv9PrX9U%0}H28-w3)}Qp}c#np``SWjo@izSRP9?KN zcIG+0SPykA-2j3vpn2l{Ofk3I)Pzwd6E`;OmlpPlB4yW%Y&Aa~7EdoLLbkk9MW0P- z&0Ok2=7$F=Vu$uIh-L{E3T6DRnf%S)s4x*qi<@*^=P!=IeSBi7vTd>S^(=DpjPP_S z_}JE4du{rrD!)4@4*p<(`wnQGE0k9$hsb4bNcnL{GEJ|uJsAPxd#j%18Ym|63zy$8 zuhheqk&An^fbFIv6&?cxKYWMxOHuNA%pBkeDAq|F6kh@SD-!JV3G}X~EL0XZ^81@F zch@+_R<6Fsb*QltTFdLG(wrGr!Yw(@v*4BBe3bYEaSN;(z<6o%c&V`rR;YRxdGA2q z6Q%pgUgsnI%VQ~E0y$KDdq6qzJW>L2enR>wU&e!WS~q~AfVn_% zvC9~9p#M?fz;a3B!QjGc@wnaiA>O!=MDXGaT7WltoxsTt}#W%Kvw5G-Z($)g8!Jk%4=Cp zFgQ&Fd-!_y1;ZstE}0FJi@Q{1gzr8=pvE!0Q>=yYkc61hE@ikMhxFT8CG7a|JNR@7 zmxumm-M}w!eKqZgze&PamDvQLDihIaQ}$(!b$ZwxdyQ=*8!@B1|6#(va(?-VI?Rg- zb@thgH)Nk-JE;>o_R={>z$b0&BWB zTv15?{YjiIE}QC}ITR00x0b`=zVE2C;%Icu+JvT(J>rxCE8(N2yHd`-8Xo3|GAWR) z$N3GAE3il^=pOUs_aFN?_1=rRLqc*w2xDx~QK!d^R5uC{Bf8bOlAr>pL&%ABlK-tYP`z!6uy!7S(_DA5rKs6=H27pgIzhgc)D;g=^x>yX@yN|hAi#De0bM^~x z?F`U8RU-eaJHRs{1x&0VQwu0JBYW{AB2t>H};CMZfcVhg>=S7<%$3!ep@qhQpM3w!Xoygy2QWo(R2A z2rb2p5xUet!1?=+w_$Ha2 z46&FGaOT$f0(1N6#q9l%?1U;F(EATm99Kc0>erSH~#Ov{ueVV5XTGJX8_Yrf14_r1U5XIF1XaW*&X%ODV-20 zopAMR3sT-85BK{(ZQ+bF-j9_n)u*P3pZ_0G_$zR@yj+-vJM?DAt?KY2LD6HjC9tRL z|6=d0qpIAt{&7H5KoK@zP|_eGpmc)>n{EVYY3c4(KtMV~8U;2D(j5{?Bi-F0At@5S z^=yylo^yZqz4yKE-{0YQhG%&86Kk#6^E2n14lBJZ9t&R?>inAF-a0&HFR>`>Uc&u&>h{?adY`r{eYvS4fyp4gVVA1SXR8>In|;YDTI6f7pF{Y%)2iB7IJuiU~X zk26~|wpMByjJ^kha2L318{%_PjzTLb4`!j`NgD#r>Qs4*3=tByo! zi3qnbX-J4r6faRy1mOkwioDH}XmYsLE-QSf^@g2VWOzkzX3y?(*J-lHavUpb?@#B0 z1a9M=lk=X$xRr$m*Oo{!t^c-1e-I7;&Ctpv=Z%+|C>67N{5%}8=@Gne*O|O#HQljN zSY{j&qmfNDfqit7^(10pD7ZKCN7Ih7EIGD$FaP?F4&t}P1lK5p5M6r8xBs>QNK){L zL>KWUpeQD}O+dMDi##LrG0e0hmotk_yQ)5M9X}JphxB4d=G(`$NA-6k@SR?jA3f!u zx{KZxd?(WApCq-nH$NKG4z;d{o|^>?9-wKE|C*|^WHQ(QJw14sRTeH7B=3Jsj64O! zWkoTL;NP7mO#1i!5A-$!Q z_GAN;>sLhk?Uj0U+|b)jHBIlD@;reH4U@26{`*c6(uAkaJeA0if_G~aRc)`eS%VdOoW^b!wooyq||sNJa9&hvV8rN;CRcH!N zynj1~dMLxvDnO9f(wt&i(od|p=PgQ{Kep2KG&Yhw%5j(Z5rc_ybh* zww8EAPS6-0`2dSv;r*;zc$fX^(hIT)^D-|hvolA+!r8^7QUB?ksu7x#Y;Nq z1SC;8zNzQ={4m#k;7L^Y2#qB~C9UV59}#{JyfTnQY_(&QAO|bZxc{DS+dLu$mdizKd5WNYbjt%aT2_;)i-2~*@i z7j|xe&>ZEbSI`Wej*6rE*yBAUyZ&i>#kT5s_#u7+>NR&FE=bw2;m&&Q*rs|xnsWya zT>YtDFWdzQ^yZg7yA)%-+kR{wXU-d=!8?JLK3Fm;EULo^vwZo_UnBnn>sthnp2$Z~Nero2w zaYgD~OzJhKr;+Q5VMoG)7P8eSYZg|6dg+|o>B+>qtTF?6Z;Buk>}#|47(2FDeqn!S zXB2ehU!rc3Xbv-7!h6451x^v4-PH=Gb{$Bcn`GdU^av5ec3{o~yuBn(c>{7C!~Cw_ ziJ<)}Z#Gh*dMZ8h)p_+{;J(lmg*%TGo0?{w9ucv3NSDDSYze*7%UY;VB%uUp|u+h`T^6$3x!F+ccJG-kG#3G)Y3Kib4wg?kvdZUrfAgRs|u z=hd(0)fA0igs0xc`y=BTf6zlJE&8#H0)E~wi>P<0e?8MRxObjZ$-t^BHTW^VdJ@NP zrvU}^C;IV?GOJfvrrwNclySlCxa3i~uU)e*qZ!mai+3F6M-{lWetrw(AB6T%*n3-^ zVbo0VUU?p-idh_`kopsy*vnKx!x>|sBPVmFGnW$9KgbdVJraatFVE#gQF{XE&y|yY z3&@hmt2V%U3yyM3ifQf4?zt{&_g4)X{u-s>+IOLB@_Q1ek;<`HxOiotUb7ADIYt2{ z_&feNjS@v;XCP=M|JPjy^jYafj=aNKS%MKAm%IvR9snr_>AvwWyopsxB1~NV>n4%o zpnBe3^ig~)A9)P)<4tmEQ&jM2mm6_4!8rSH_D_-sVeeS{BV>4c5&xZlN44u>gqXeP zM44HpdbxRAML(5Ts4{3!9#TIHnvoBsOGLqU>(5Wj9KcMJ^&M=^N%?P}rFqD_2n>ul z;moSCTgd^<+O;gej1&a}wIdg3+Op~TV74h}xve5bVk0uOhuml%YS=HmL?gi=X{Los zK44m9KMZolNsdyY`7YcUP14GEz)i73lN0!nKCb(kl+ACt&Mo)xt1Il3lzKC^os28~ za*13DWfl{|2MJL>w`%0TTw~91w@;UEXMvvmBbq_`m0&D#?pTSjSy72pEK|g?R$*L{ z>w30nTR4Tfw~5@_;@2_}oX`ww=ZyO{2^-L)&#N1CFxQaN$|DUH`Ls&N$J=9|-c)4Nf{> zOvOXJ-w%U6R!uoh#7RZmg^^RWX0tx=4o;>=2GiOgE;Q)OfmEGwzXrga+Fw%;#1Ihy z92+1MiH*)8{v1q;kp~IykCtOcGhT)992VCm3$K7-gfn5u z>H$lP@RC80w-6r2PEj~IgZxzt>ZnEtJ8W=L#-(=68my*&WjPFXYM}LnbH-iiYHuR9 zl0>DA;OWjQb+8F#s>7a%4|QKwE%JiVIkD@YegD($1Wr{j-Ra}D`lDWOGPz)S^rO1N z^z5)aPPK0Yn_$Ca89{56)*y5HZvb?t3_6LIm=5QurJ4Dmfrs2a0P|CF!8|4j#-bKH zzYpwKg$U1vv%>^!VmmP9#(G58GTJ76_;LsB?(pwH@DdgC#^qgp(ODFTGm6;5{e;P7=(ynu@8d>g6 z$TS!~th8B(#t8rH0D{ed$5lN%8}GQ z>aorhuYP!wW1Q>)FRXAGGmKp;b_7F+7)8z($S&l;I3tJ7qhu26M%4AXqiH3UZ`$VE z?#I>+U~adGMrYMX`?7FmqHR!liZ!e3rq{H~N_N?>dMEl?8b%BiJXju#*vWMT)3O}i zSx*?oYS~YWJk!V4apyydukgEQrwH+|{ElpTkS2)^zy}3MZCT=xIOuq?fY^PB1J)k{ zokW;Ho6r?dIau%EMkAbNLpgu2Ii}=hxSx`DnSOqp;x);K&%uQHAF{#hp_ZFWqV^N! zCEfdF;LTFszzpYgFuUA-;fkEW9?>>YR55*M9tC~PBS=zM?(3nY2g^odTX%&{D{kY< za($9<18x3qP7#PX4sVT@~^sP-)6plMiSg_w7W7O2-28P8C(7NA#4=dSzbF?Vp z;WLvngY>W}sFjW3b^;SZH9Far=Bs4x@`Qa| z7?G6J9$;>RC0|Ksx^HK}7z@8Dys5oSm=()%f3=TCknoHy?eK=c@pHy42_o1%!}X6` zR6?dL1!+Rf@^L|ztr92do`>{77_F&s#s)p;ze2r z8v}PN?A^S<{hCFZg7_1c%i`@%;C4aY)nH7!nN6Q`TC9wZh~_?zNki~D!4a6SlJ!(Y zjiwNV{%`_}kr@Q?JaZxFTk9>B7)`iLGMF91qX`%55LdB@EL>!kZ4yO)>p%mRp~kN_ z5S$wAmKmA7$@AnKm^|4ujZQC2Wk#~*M~h*R%2&yS5aaIIx$eN`_{>WD>#2g{UYxAv3BI#K z2yASGI>*_;-A)R4g`f2-j$|v*9jP~Xo=x)M@a;4vvYrgoscg0oK3YZC!6Q#ToP$l} z79$@VNy^?jQ7=2NYf-5nX&+EC?q}>&OY37a28ey@=I%h&Af-%);dQph++q&~bs06v z?VA(VCM*ei_ANOl6Yp#_sZIhynf)vXdLqlP9j%sCRHaP5Nd#LI2O5FDv|g6M1J*tb z$x}sSx+@t4>)F0^XGcV=lG?ifbl}>N!_X76`wN_@_0lV$$#dBFHtk%-rAap$z`zrS zaE!NzUMB=g!V7L=Mln*RUD5Qem0+%WdT(vu-qB`{KgrME`Sg|0VFjNdDZ7{U+R^IG zy2Q0H%gUn`LWMft29gCSW=OSwW9%%JyOxx=+nf$_x095E(w)mzH<^O*HEh&Mw^dW+dNWR5CN{*aPFg zG7CFD(;6uejzz;cR381PB6OBDxNW9ZD38$X?CG8FA~09$lt-LgNgDH|y|cwQp&4be z#8Ri3D6IfZ=&)p}5{rAuZ!<6QE#;BEziHnGKi0VpPQfJgymT8aMw_>M(t-G?8;CeU2;~gCF1Qht1;ZR&8{RXY_LUQ z7%Iht3iKnum~-f6A6b1d9THr-wpF=sgMUhUIMm)(Ynui{T79nLL%z@g?QdTQvRwzg zg&eABvYYU%rL)DjeY;=pBZWII4yxGBz*%|lGXi78ddDhPp&iGazTNZ`Cr9z&wAn>X zIIU|E;F17^>E|<5C2xuoVhO*!fD0vq-eC2WpY`EF@khbjJPE~%1}8-$TJF28mgltE zMQMaZ;79RmtKR z;vzAs$>s`1WqqX|A4|?79k)F{ZnPKg%`sksV7YkX{v|?l^#?MNNJ_tTOaMhJQqj=B zIIU?s4w4I}t}6~5kQ*z{k7}F*=T}#-4G+;bxcb7TSfXRAX1kzwDa@Qj-;v)Dv3s8o zMxVBpNMB;u>;cb37l&reT#2eo_}X?kJHcFF zAx|M|A%uim(aKQ|51W)nSwdjmAae_vEUDSy7TxU#nRua~JI20FeV0MnO=F^($i5&T z8NB29-O7n&IIJVEO>)3lqo)HDEa@0Mm6V}xHW$5yId#HXfZJL|?4;f93fKmCe7V_( z?AcMnMa&D)q)h|Qi?gE*E|!<&@1hh^i?eZli9E^Q(IGZ&J;*@}h!eW3N25SRJ^M~A z%s1enR58;3_94QvMv&D|jn<}(c^=QbhG%qS@vN=X?Iy03)jJor2e#OtKhUY%Z9_a@ z!RuMnZ|ZKVQ=}p%-Ib-YL0GPIz2I-}9UB`nh=~e$-u1H$);eju9T+?xU3RB?nb$Ud z92TxvzDQ&C_{tPRVeS0zKrl;wx#v=__bC|jHdW56lxEix?fNY?BL8u$6?%G6%8tgz z=Z_ys$PN$MJGS<4f5^YvM^D9zZMGf%e%iPAZpMy{uZh*e6(o7e`+Bcwyb%ffrU<-x zjWl`;AnS$V&_{g{(()pm9KiW2w$M?(>XU`>j=mbvdyo2V2U8DiC^t|tnf0cL4#R4e zB*sa?Gwi#yKv``$lrWU~#xe+XFpYi=A?EDqmO_8DAhODG5H*#zS9ODwXO(MuZ5SYf zdWhceFvISMKF8)UCO~MARcTwCb-+L@5SS^Nv2-ts;;NYvTG6!xQ2TlLrwL&jQj|(l z-To{_nHFQ{Z69xaVqG=ZBc&oZ+8kqbCj280uHt$+|$BXEwQF+hGKX&sYb?9+OE)dJ5 zaB(0GyS1bpxl773e8V8E7Ryd|^#;l6u!7?dM}azOe;nS{GAFPDqml$VdcGPhW|4&C zB0QI{!dWEyMjJ`$93MKg(FsZNMSYH|PO?K-C#+Th^R#M2MENEiuNyI{MyOT8Svj!| zT)#s-l2)+F>sQ1*TmTJ0Ca2S#{RsHcKI8a8-g*4`lH#b~I24~#=U{+<-W^`YJO(_8 z28uL-J?=4)R_T)Ds;sg9^G1^dlXH?m0Y5S2YN5BUQ+ zTA~juX6oyBiRAjl~)x?UC z*I|9}a}scyjv}`qQg>R2s}~QYlxz?9l*+1?6rlGb5!MI~AsSl!4u(@r7R&POiX2r3 zxKFcV%kZb)=()mC^bqPf2|Y7Fq=K-Dn%^0pKt-UE_B3jFWZ&+j;lk2F#-?)^tH16Y>uf+b%9C-=d{N0uaD1Lg^7uFLtbj+fyqsADdq7 znfBwRR67Gzgzt@E4JKH?5WRw|MHI)t4 z_klpJM8(q*t)EUaKjmdymR+XW*fsJH5bJX_gs)v8U})z1N)uUE`?sY*SlcW$o~X@mAo@V4+qFbdf<2P5lv zsAl-j-44LqNubnACG0M6O1U<82}iC!U&Az;cHOTXC+p&8!$s!{vu_+`TNQhQCdN8h z$?nU3A8xLBO%tv+o*kMJto$Tf&V^A~Jyp_4Ee5{%SDV`eq=;Y@zJnG^Q!wE8(D3v)+IY~0 zE-9lKDvUT-_2r%1$h=sgbhvnRF>w9_XHu-ptR^kl^9(f4&zjh(mg`QrNUP`A^?76( zGNkG-V59gB@PRIeW;PX?`-8!VqThw>zmCUL$a{u>-8;VM!I9wRvx9MBtK4CYa>dRw z3$=r<7k5V(-EBHvnT{F0WvVG-VRv3bNG*j3Zr5yg_H2M%dOFjSkonf8qhM&n4$o>$-0D z(+-O=EjLCMNA6Sdp_S5PS`Cl=poCXU0;=EYz7*7NIDZFx)79(um4^z%g#Q*B_3_1> z*UaFuo<6BHO1?Z*jerp+_XZ=@vD-zs6C1E4|do5W-xyz*TAAZ zseDE~$?GsXq@_m3>+In`^M5+E2XYrEA`gy zMDE_=yRkYVW>Nadb?YEZAUXSl*F?v5SdK02az-$}(~RiBdf$*qgO1QQ2e{6GPZVOM zC~1g@QRS(sCch%T;DKS1O20+PP{UA_@qwb3h9hA`upXVqPaGBft$H0X7qfALGV$m8 zX%as{JIw4iS8)cWa}y)7Ky7`~Se6mOv|@XXXEQXRul=Dit*GKQ0sH=GKVesCJ&y?>h)qg<0i`;4?ed zIxIdl?ew~kx+>>-Dxs>0{TaWZ)VNDVl>w-mO8!G$#kz~RFQ&QOoC8g1m`8U-XKO7M z<%iPSBd2F;S2$-J!`8jVo}Q>5>4(bGIc$HprDfk&YxBydcx$1oXPI@rpmP5dp7x{; z&G4e$IOkGQQmIV_5V&uRl%wv{CYrvCk^Wnt|2|Sc7yK5`4uj|hEt8HlTbTy-A29q` z&=e;@@8bC&pD=#sOm&N)<@yhxdInAI+n7wDDA&kX7aVcJP4r7BsiUfxBaaZ{vyVdU z`wJ3LDgEuAr#UtMbh5r5zcF%v~kxk#`F95Sn6H*7o>Te7=(9 zacGV!mf3Lr_5=)Iv9CPpEmhT49SJeTRThiJAHIPCaJ}ZSg}v=DU$kCXfIZ+T^Ftf;d!CY8R$3U}sHS4Y47+TS*Y46d^8lFa%k#DF z@RJy;^3PDQ7Z={wPO{DpEem_MZACuYF;z;xvFQ~&_vU3$WCEc+InxO(Uemdq$}}9M zIyoNM%=RO(Vf)FI*#dR>n2m+4JJ}qXURtCMjZ3Pz#9}kqy>#5frKK3=Uu`Fnj$6`Wt>_!*5e;YSwW@Dp@z8c0Jd{H4vKR2SLjXb@s}zCdVn4t5MQq71J(cjJQEr^z(?yUV#h6?so@) z>PBmbg6CPxTwX?A-{W|80=9T$9z|@^8T&5HY032`tcj27zGCh-Q;2tWd{NPme1G~A zVFuSIVg4O^9S?q0i;_+Oo=Vj){XxvB(7_eplE8v0Can!0#*JH~G?${>0}++@xFsGn z=Dq2kWc>qehL=*^EkRMQ@+D?I4LP-5C$0YWE5eUQE^1i^QG52y_BO8;E*t@|zrPpI zZEPuz-ZmPpg!@+_!V`m1b<&Z)Q+1({jTJqE3ye;aQpRKJItg3}?e^ad1^34lw(LO& zw5EsdB_XQqQbS=Dg0KG}|2XwwklwV(@NzW_EezNYk!xMrM-x^J+3h`9T_dErG6? zXX7-PdS2fj6jH3Vv)8-+&1qV7hI?(s0sf?8nZxL$CYT@wmR8vh7)pvYN8-A+ER^s2 zD}{Iqk^Yf|%LujfY2kd@a(^tMiezMI=aq8GfNQlv2ns!~_RNy`^#upONuS)TR5s4_ z8Mh5%S3;H=$p_VcOQX=aF_{V82vAdShs$ zPO1CV5=np6YbvyQM7c#>6_JBQGn*}yP)C3`At9l?0;p`}%c#G7qqbj5hlQ(~|IW;@ zXj^pO2NoI)j?YzAUD(TbUns#s-ZW+}(P+z-rAtl!eIfj~f52Ssny0+waOB&+bax|NVz4DD=c_iC;I&lT<`1Cd9Cnq?n3)D;r3kdsl$}CFiwyT{D zo+M+RPr+Z4mFiq|=yS>3u%i+>1d4K+MC_{TxiTe)77WpG4@|cvxpZJ|#U~_hPAb6+ zIj5?1n@M3$3oA=gk_-4(atnuYa@m;Q4KsWe;9nCco5$*`aj}g|0Y;c7lkYGgWS(}- zvi%KI2E(7_6m1b=4WFvn_Q*Qq`B-8pQIuU>oUotDAhzl}9ij&MNEz0(hm^l~cb9QP zRGWPjt*ou-T1~X!>lH6v;<_$i^I>6yZ`c0xS+|zIO{$8x-x(knck8Zw6t$No<& z-!u?jSCcrInWOGq7B^`&n%F9M^LDEK=UJxHM5`i#j!_BtA)0MV3j!!YBJYA(ZcAD_3aiKBqC=+YxiD1RYxZ9Uk;@Ejc@MDZE}c zS!QWcNHixZ?GIu&R%};)_hp|;i~mR0HP^xQPPvN_CsH|2U>N+6UEwTTxuycGdHlQt~q$G z^xAfxBR2TfcIk#NGf%~IxWA53sKd#afI~ihu{^Pz-CUjgE>>a?ZIOTBuCj58pnNO? z<>J@Jw{yJ8Ccoyt3C8fS|2~j2L-Sc(}}d)Ml!H?k+D`+Oqd!)2$jta{%WjHxrE z8#r{+3Tv7w7Mupr(xV4D)vkVjf5BhS$>w!lH7z*UD|j(qKH%G_9$ieEVY8xX9an9( zdFgYmm_3I{ar(rSD)QQ`20MeavWHV5iO7af*0b5FkW-YqWWxEUdif{?a3eLRX%9#> z;1aB7E=e}BUhI}lRMiyBvM%zBHC_Fp#O!8=nXcm>MaFe&hS&XdV@)3``nJ0#e62lS z<~0RIoP9FRjMJNwFY0E;-#Isk?mF%3N)D?N96`Qg2?3Ki{E1Koi=WsqNd_<476gXA0 z>Q(c4Z(bISW5F=jPK zPwI`?LLY8gf>Cp?8;-aO_dRzIOMFCVpRf4UV}GFc)0`*wW?V>>CRD%;@3s**cJ@~{ zZKruEWo$n`^H%F~w~B~3j5C2-Dd!dCd*bY_sH&MBrS1BQ_wjRiHdWsx8FCy()HLJ_ zIeI1I#(YC*2YQXW-sKzhoQrgy@7oQPO5h(j8RG+k!Cb*wOIB&McDr5`C}3$Z^0%Jq z!{wSKt)`NQ#D)7*01-e~*ru0oH54bLEj%)U25njU#x0%IXSgpw9LnK}8nXsQ zMDs3{1E{Sk3Wq)Ezy4S8{0xXDtY6q*anNA2P5E7*BBmKum@%f4+?dVrKMFVJdVBP)<3F~3?VOXk%c zTQi0!y?=&0c!GIRF6dVpV1X*{bx~iQ%vpfn#~5TqJqd9w z7q9@-NFHv1R=a@A4}pqRAS4Jw$+B660c2(UI%dUsWds)50Vr-wOU{>@fcVuQ0cE?= zd?I!j95v{@FXX=Z>|iycsd;=Cy?zp zs(Y36YmJi?`-`us4NT`QwEssy9jT~MiBY#=VeR)Ib>}%hGuozGxyJoJ2y%Hx`X76H zQn~W>S&8&U0}<$RLIu?RNeMS2z}t0z__vw>gPzelz%qxyHK2aCY%x-59jnxINV0Cf zUn=Cj9Y|N^dK^uSU<)n;vko@;T+(Xb=KfYXFbJw|%2d&VzLih(0Epi^dmkA%w53lg zxMMp-#}92;qyWv6`;HL&vDdKEBK$5&tcO$xq!Nwa}_4B&}| z-Z0l^)lter?F5z;)4!x&GsnP;h)?Lk1G1b-z4@z^NN3a}xjZIn{S$`wF`Jd6EMtf7 z9RZ zFU-n_)mC>i{PqPCpZ1+p{Q`)eW_z9;s1DV-*zt>wITZ!003Y-zfP%aOjg$t(JU^=P zUjA9LU9Z&nphP+Xk67nq_Y2t&Fmv+a+0BNJKhUM)QOxBQ*7wtcN4Yqu+R3Se$a?)V z=q{u{N{GPyyOH$sboBo7yB}umZ9AsU+zZ1m>Yxfg^h~Q&`Al}LVD8;7UIvJ1hzAC} zy;=Ej0J273GVZp?dxIcfq3;;dNjy&lalT9MZ_muAegbH~iMm68s;gQ_gNJ-?weyua z8V{#<;yyyM`_O5#=*u!mUsni~A%M|Q98J4z|CkTT)TprJHfBwn1RXIG0b8uthVEn! z9IOvXebKTg8!DZFow%DMk)2e@Ch@G5Mo!0F*{Wf3FrTUS=z&Q(xMugl#7H6%SPX-h zlbs7{ws?}{dXxF>Qry1>7UT}9tt+;5$FYiK?v^X97q#BfIF95Zc3!+8cxp{{t=$bA zd=K}=i<5bxu#ZlNwx9i6R0?*n+O8&B0Qbe&w_l_#P%phRnCLyKd7d%UXZ6)8{a{+g zu5Mu3iaLg0vssPyH)0oA1nP>mcD4lLfAh?4afyJz5-S(xleML9Eb@V&BT#6?q1hkX zPL$brKjqd=`s9-v+%3e6P5}B*vx|^PSk|_KOD_*=yICB?XO|q^A($$k@pL2AX?bi_ ztA_^lqO|K_nzlRzCp(`1Dp3>ghSFbP@%(fUOr)&D=La#_zs0$I3w^j zUwju$C`{;;O|aMT&KzyEKgV;52!IEyKmLK$|%Ve*_6&bJ+iEZj@sTDn9o!FMLJeRqAdH~Sc)K3qG;PXX91hD80Ca9cFD^j*EC4}K_GQQdi_m}khZGvOHt19LhpKb@G zZztUA$e58o;pTWy*IPdt>)9MWcjqRm#u;4sK+aUa8rwMTcj)(D<>oNyaF;yX0erDq z?`?qQyn$p1eRF^-rG4B(DvTgplcriKsxM(=+x|1I8~GtGI>aTSWLwx4YSGe5a?3Ft z!?b<6$Jppr^3>Lr@e_%d(2}K}zO_}au>2O3M#~Blk?~~lnBXc5C#QUa;kQ&BK_ymw zu#12_9xmn7sWIRws}iu9=1WpWm?b=fDb~rN%du7u@;XeHiXpOM=NG!e_M9>A)O^-3 zEk&a~^Y8BzxI^4;bq9~cOtev@K_T}r+=>H(8mUu@b+yz%t11IfZJJJF_8lqTsA#~_ zPKPzDs)S<$u;%Z2>??3?zQPf(-mo0ziZ%}PU11x2+V#CEB{}|~{010Nop4j0TFG}B zhC3?a-qGlCAVa=Y^yO}6cM|VNtaeKa1mw+sjgdBQu%E8W5ZofGO-ET25G;7MA4RN* zNyZ7IE5d*17*aEPuQOxj#j`I_o>W^km7bk%zW>PA2`c3?MU$vzj#7@z;*KH%)|Jmk z$bQ75_MnCG9!Z30i?nHb!5{CLF$eK(y*e{$ck+O+(>%6o?{V6Yawzqu%akK z98>*3pp1un!r~KPmqJ#J98(0qOmg>|(K7ndV<2%=HZ7rkO6l&B1Y;B3WLN|+SUyf> z*6}`!aJnRW#c2aSbdE7mb#z$ao@J>1y5{B!_#GltMYyiYI;cx#$r-d}RKWpw&NP}= z#NV9)kY$ zQ512Zvsxeuz=rgBBLpbVU8VrqYDv2WSRA&+Fxet6mLxSzMql*plQ=~kH4Q~XO{WoC zF>iTkYiTosQ2b%QC<7HwnBe&_oQl@Z$3`2qn1C$Gp9(#PMyTPDI`b>?(^bx#x;Nrb zk128@v}NA=@sn*(^|eqrFaeD^lS>hjz{F6-WSTc%3HdtO#LKAW7n$&4> z?u}7PE3jaGG=+!_^Rw~lN(p?9VGsgPB4xTC0CuJcQ~r+ssF!Q|#waD5l{?_Vb9|BR8PXw#SNQa=P-AIAQkfWg8#7mud zFZJ?N*2k zR^aR6jp9K4&=k*;>_Q47Y>6remin_Ga0TG5?e=!I<~0jY=xg|hIV~r-I17Q0jh~%- zP3F1k-x<*CUhw4g4W3X{wugczb-F+Lm#;CfU7p|#QEyQ#jLKesf4r#;_pY6NdCh)G zkDtx3za#qa3hb$vTE{xL5!MUj;0o$q-7k z1apL50r4jpN@Xe`NggU{$z-P)j|y}XR6+&0FG!&%K!iVYfqG7$rZ6bKP_R@F$=;i!|O2${7V>h-b z#<9G+^^$Q;jMPE7XY--uoer?zn|S2cpap!EY8+*nRy0#0I)h(QJqa0~)LA^a?epM( z*sHALoKUlQ?9ZB{KP((jSjx)C0DMc-IulDI0)y_ zbR0AM6ZGVP1U(gbiy?dxSQcL-nvXBsw3Hz!iE=pFnwqj#)wJ=GMtm?D3A58$k{~Ti4S<30^|L@F?#Cx2q+gX`z!P*f`vXNu0+>A z9+XwtqT@rx`cg2Ha(6sCLrh-IMh}}Qgk1_1&r;E}5#_`&?kKb6c&iY4FDDTe(gEZ4 zAQ3oOr8)prIHqtZ#J%S^ z_K~}R_HGA?D0~F*-Tp{SXKpn#xuOWXS%FXjy12*WDrn`&IvPu#v0|IZ!xhl@8VK3V zI}&2jf3g7RP?cJ?0C1-tL;g>2$A9VQt86a&qo4?(Ky6TeJN;d$3MPt6P>?+_F5UaT zi2BGUQOv23aw{oBY%fH<)}6;FZzv2D;2&IUxx zKY}`MF?JG~K%4>;v@1Rap`V*ZA0o?WK?orqGwK&g6VMCvdk}V-A^2BOD)@B?);}XZ zLLE@)2g|FQ*-YgR&|XUcwJ`_l z=z28IH-3~owal}ozm7&o)3~QW+}y-YGV<@u-bRl^>q`p!7z7l*?uHR)0^D2&HMpHN z3oGqYnecO;Rb*fb{ox$cJm7f}WLx6@3ZYWbGx%zygs1chb<=*NY$_o5SG)`4y$1f^nekJI|o zI{j?l^)6 zOlf)67C>Qg1&ZMPKJW*ySx&!S=bHW!1gq|MiO`rqfgE*wTCd+ff_@LeP7U<`DoXXY zrV&2TFo1b7yuzTUWv+&PBNh;SH57 z@PN?&3w(R8{J+5WKdAga1-|uYKh8Ga9-0&@45?BM($LHAep*ug<!`3$2)`uSG+Tqp`R z-?AdTEXISX!&^Bn6%md1KtDx!)Q6ZDh?$SZ=qDlbn`@jwd)bHgHFIf3!kPXlhVwTI zrwP-VESBTzYY+9ep(4TYuxCFb*VsFGzY$JAsgeX*w>~S-qxlT3d(PM3H|AS~Hu7*k zvMd^}{JOCeRBtr~ID!0VHy#v1r$ID9gjM(3H={>Fe7c{2|CE9QS7X`0|qmg?Q#O zxCvaL6-04^sDFS{6!vx^`Bkaj1n$08GABt2gYMRE{*QP2(mX%;{P9P)03o_ z^`aKTKE-KJBzyH)BtEcbNP%=4E23eQl|jd6l(WE9UF~5+swRlMcVoRhIjfoTbWu!d}ps& zl?({+Q%A(i&R%0IKLm;IMU+CdGAQUo;A_phsNdoWu<3o0rhx1}iceN(zUYED2?Enk zIM!}6CwMmZz6Mt_BFr<5V}Fh)l;N~#mkJs2ssxJTvnY@J4dxCM+*;hWd#JwX4Z7gn zeC8+4IAiHLwdN<-f@I(Vj|0xd*ViDIxpNKV^pI2yQlNyl*Mzuh?_CnwK}3S&kVy#z z^)Vs1Tt@fDAOy_ousROxvx5F@_jK4_K(pD#PlwAh9V+j~y$uYyO7W{5bE1SO zFF9jCdP^z+o8nV12Dq9Ly-+q)Ga-+G5iCP4_}4N(QuW_0Bb&O>LvtL2{XOB}y365# zB>iy%Et9c*VoyDAfq&y`;8;^~q(GaD^=re0>@H-mg>mfF?F^vSkrq@x7ubkkT;ygT z4Vkj`>i*(2&eIlf8qfxLTJ&db3ZqvtUyGoC6sk}$*iPEcyMv$cPz5w|F5y)4PlDT# zk67;$YfuqMec#+@PP7w0k1H!zv6;K1+-YFj_9y zK0;{wW)xc-NGKn+4@ z^z{pB@biY7&W4;L;wfmpO26+fz=YbdO>m=OZnA$)>Jp~>7yaG-5Wy)0n zj72J;hPh1PhPf3VW;bB}b|=f2K$G%7-yI+|#{iu>hUDk^)A1Cp+D zw?Xm|GND>Pu9k(V-^bQj;fnXH3=JgcjeZ~J$F&kDX*xo?Aqg!5AH?dxG8TU1|GQ;a z-A5`gVzA>daJ`mEL8g5<)Kt|u&iqsEfa|pkS#P1tNwih~%8?-j3o>aej&vgqXGKl^ zKnPT_$%2ib`n4Gvy@mO<7R3dn5PLuR#ooy}3H$UlxTss5!Uj-LCVBV+u$}G2SIa=x z8=TnnMx-qN^@uRn{XVPcavtz<7`Ol_AcZG%6CB)RQcC0ynusD8koyGdzvqEa5_-aO z32<~?4CFwN%)Sy50Pd3K{r{P7Fv26KXqth*rard146V=|w@u6&2n;_UFfKvC;a}2S zoC;7en|cV37;uw8KiB`6YdY_O{9uW(lKL;b5CV9V!$?@16mPb<@J7g8r6?d8QXx6{ zcg7imPz8tPl5dc2Onn4i_gPvGbM6V%tu9c~x&bi{Eg)&=*!c~jJa-{+;{HpVta6$@ zAuE`xY+?q81t{+y04ACmXr^CU+WBxD<!~c?stSnZW*-2E7{T9TK zNEI94RR+JYPbJZgMkRMvk^ojl>DX_PW`6RorJ~utTSkH;fXZZa`-jgO`vO^|J75f1 zPz5r>F>hrW=IG!+mgo+A*)xk0FuYCK=%E&H9qHaiLKuiherE;sKjIopA^876NsQ2X zbTEl{|18EOL;`lGq3tZ*d~5-Hx8@9<9czZ}-+CquBtVEW@?{(5j71<}hJ)0t&>5yj zfSZW&;YpxpHqQKdIMN#W?|6VpOw!*AI=UGwNF5!Mhlim6p27N``3B8kQM`Re4h;kI zTmu!X5>W7WSnAA<5+wH;f6YsPM07vs^n`4f76Gh);D5(8#bO}EzBv|s3q+5Ap3(vSD$*c6vBv&#(-XMy$keAMP%jIVr1xImHblXgRtDiQ zCJPR*?0+W}XZ|-v&r?e3=$?=}tr*Jp6$v_X?-n3!p9I>3eybK^#*>haXM3kApWlq` z;?C9wzh=#Vq$P$FIK2m-?hovJ$ipLP#}kE^iyIeD^8ah^JAk5UmVW_3Py|^)5Rf1! zU;tEd5LS>NNKTSP2};g6EGR*;f})Zo=Oj5QNkBl7D{0A+b7l!|4k&WJ>-}!ktM`BP zUe(7|QKxp#nVHl5o9>?Oo*p*eE60U|^e&n}&XWxP!2a)GF|e3)#9{|B`e&J|qd~7B z`AG61<#&`qLXd3^7x(eiH36!UsX5Fwjy*2L;uZeyrur`# zkKsNH2_h-(pfu1AOsc4qi#U@#Glkwhve%p1_NwE6=~r-Y9@hr&0R3P{7A zoJ->*K&ae%kt46%6%+i`IBZ1Wo5zZx-A6HnE*YANiL`~DuzTy9i*Q;3U?vRbodL_0 z^vfVg2>>3rMYe!pp*paZvqzCHR@pAbSdXM((_hlrwX{=&Y{wo6;Sy77-ilkU*yn8 zSL(DLU!kn+?l2=6SC~~QQkKlTK=mIj?(6QwAzoKi4Nj46!(Ke@GzwR+s%>W{`A3cQQLRJLbMgaRc~kiK0LR?xe~(tgVjhN+pb_eJBOf&CM1c)+3;#dvY89oh=J!x@ir zq=jFX7ej!KxH&77@ep8>oXy<#x0z1#6$l-@ZcTz%lo1~*C})`_^VK>dr$V0KA7e?>-f z%a_0PHj{3jJhEI-%f)4t0C|=FJ%#oEChYd}R!P_O8VSUqphC$7$OqF|#>DKNwC{rv z^9}hzOLX-BbdNkGb)cnd)PO$)=UDS_#l2!oD54?D1%NYM*UA3j99WL|wq156F!k&< z2TqR9G9VWWY;4|ldO{izGFUAy8SlP90pE zm97SRf$hhMj>e50J*911(ZX`+iBEPM57QFxOugj9&201YlFf3k0jiyRL?JYcVne7_iO?s{I@PrHXShS1)S|k67TX4&g z+lZNpQNexY#u>Iqqzpc3vO%=l-9n3HnDgm`bbgu1?kl>gSa6~fV9;^YluirXTIv6p1dXMKnYy(UrR z7bsh73{Q^Wn&-=}4Ro5^$S5>U5Y@Kx6bRY^#VuFw2wI9J74)u~M}F)t)JTKQN1S4_ zlBa`veF~eZS)okf)E0RK`o<%B@w@M9P~X4{au@2`8h>+oy>W%B^R^gu)-k8^PNBS; z&oV3Uw4HgxOe%YKE3%>7`_kFmr>w?}3au%R9HU~ZrRTdeZKnGg>~39wMsYEY^o;A) z6ZfrcKUN#hZyJwm^rCjAjKmv1fTMWC{SrPQK~4(4u%%BJ!hvkeve+I!k^NvL1owx= z@=Qvu;Zl{iwxvl(mOfM;ug82jay6k@JzueKfcp#C>@MzJlJs7y2=Bc+i9rTm%yaao zjI`a@RPxj;Jjc2|>D6$NOQ_1%3mKUy(_Q@h&LNj`0@|lSs;Uc9xYH$;vjf8N@-O<` z-Uw=QXd0&YHj?zVpy!-ig5Fjnzdz-pK?o$KWL^9K-kFcugqR)c^zhfh ze%uE$Oc~6u*VpR3y?}=$oE)oPW-d*dFa@?V23dh_Bew zsZdM3OKj5|ysHKe5gPOUOttQhGY+4`jrE#dGr$T#p z99^fHO0MO!a<+)x9*^p3((K5qPZ>`x9B|QYez%h%&AJ`Vt}0JZvB95@;x(Gg&Z>w? zGp(3qv1u*xcD5ms#+|P$oU;BZ5Qu?Bin4WRxo8rQJC>_G9`fw;xr#dx;2%#VX3MAA zYqk;W*JWf@kzq=1l~utNPXx>9&F`06owD$6oh!j=p?Olh$`v5TTF&2Z(zMoVoT6KO z0rt6kZk205&ZUJWhSUbTozIMV?t0Pfe)HvXRAb%sbIA1+E?4H(>BU=jK=AuQE6I-N z>MhAzYm{9bR|GFtB5;_@PnE0X~{S_zC%Bngx_*#eM<{dH%FX+`eyrB6S1e3sy!d7i^LyEhOdYBazY z(9=}mQd+3Eno5TnqJd;7`s6vV;ry>fO&=n zpH)ANOxe&Uj&Rm>yaA^@KN^B}T;tf7sw5(G#g-9~(GZHEXS zC`+q~6EZ4De|dzeez;uoQ1yOVGO_k~LIBoX)8JFPbDB>%I;iwf{MLAhQK`phu|ZUN zAom9ylg}#JP6|P4p+4i$E~%tXM>T9U1g44b^yjO%L=R9sb`o=gF94GzNBiiSAE@le@Wro?y zX(`(;EGSJvL2(=2qBylrFn(~xIKeFsX;Bs5<+)K~Z1Ir0y^#e9>LzWC-9>uGRKBw# z%f!f^IAkl3snW}64CKkky|e|%xGL+s^!T`h#iHJlw{&&mY6L$>%IJyeX*ssJDTY%O zv?*k1+lm^3awMB-7_V(_tqn#x8s_K{^_`pe5E7&?YiANC=B6bu<9Nx)(zkT`<#MOi zZ0{9SzUA_D!FHjfXQ#=(s+bh*mKb&^y_<$*arN64Qs-Povhc67cg5~xI_i$7&zEJ1 z@ld>2Ui6-`Y-jf;WxfQL(7H*HXBMrXDCyn%MKwB=EdrK$lR&#~bPtuGthzbB)mQ43 z+;=r8vQLvk+u_nG3YTW`MXdfxI$o_hUmRa8D9X4fsfx>urRS%lB8TSwvfH)tg1tmU+6iA$!zyj7p5rc*UDZ@Nu*xz!o5z7&Ji4joOWh`BmSn`uqnd+mH4 zF9^dn(A4cp-i7n_cjl6C@cT9_>5wwKv;16Z?Y^C`uD)0qrtQ)nX1bOXU5pfb zcjM~%kQke#yx{YzCiWv4b}ozHpof|Ox9+pZt<2|Q!;)&7cH`wE{nOsIlS5tu+*^cP zWt|Os)GnW-?5d?6QHThZP2b(x%5<`8M^Z;_hf)r_okB5H6uP#Mn5>sv=U8=J^L%1j z6v1soTY@Zm^6b^!5xn>c_PWe|$EYEEvY_u(@lL}H2L015E0$b+Z!I&cE8n6sC>ho^ zK*0rQ{F72zK{5`a+UQlJw$&%$Pm!09~r)w3=Q zSK(21ZM0_AE^}vT83j0N83ihKTF?Q*eDuuyj?R14r@_8}DlJ^wZx*~XcD_#ic)@i3 zI5;=BGN>M>MtBT z@7T3^frRvo#B1?JWH2#xR4A9GhWmc-g(7o>vUeOp=$B>L$+op+^(QXRwP`@;WV*WL z11KgfE{K`dMWmt~9fH|B^{np({p3(oms zrCyP_dRsGuvlNM11MI_=`Kzz@lrsv9vXV~N&19ES*bjB{HsiCFzdvtrFM7{xd)Ba- zG;Pvqp5`rYxskW9O|a@fE7*r2&zR*B$zLv=cqaVvca(W|-{?mt(kHXt+Z z&OwsbrXW2cIM8eyR%?uosTmD>C9%g|rOz^Dc9Iy+yF6;=%W_<8ZMN-1Np3|ZtPL2P zUR|iE?`lX|@6ySw8v)x@4BnU4BemQl3&pU$behbQ%-lWgFUO2Uxmxu65qHjhpa=TNi+*Rj?p20Vv@TC>+)6+9^PXSHEdf-&l$x4BO_pgSU&^udUP-VSoUwnRX)8Kzt=o#rg~j77 zp24t|!w*qSSoEvocy)1n8@=WEgbp$Zw=^T(R z*`NCp8En@E=C-_tZTMq2=q4TO@PeNAb+UkxoTJy=zGf~}p+@qO^G3oDYZ5v(DuNl@69&LVOT34A4 zj+!vW{?PwT-9E3~Nb5L`cz3)w=hZ97o%IU!t{$WOg$&HiFLCQ>XJ4Bso07hLE(RzD zlJ`&}_bTP686W=g6$BFklXfzjI6wUcX0M=+QD50Ry=ZLHo4InL+|*@zD8n`p(l&Zr zJ+?3>vt>Wwrcvs2Z{U(xF5k>M8#>}sH!o&^@mtM=x_Hg>Q zq$wlVPZbX6OKNh3*u}mD7xJFikC`j!t)Dm==_v$O_-xQa6nV({gBbYPeOo8kiF>Y>z~Q za?m|(DLo$FZTkc5G>KT>jST9q$?mXrKbrsr6bAnX{**pWvHVF#}^NDChHaNcM8mu_a6@|34Tj*L* zffK_CPLzsm1ypW<;D65s3{P!tE?;(E|huo6nCGc8`@1#_=ns~H}f%+1nJ4M>_ zw%fmt?bC}HaVl#qxrNLeV&^cumGHaMRC=nQ+#^=v%tezW_Be8~Ya6OTEurS*376wA z0<1VS8pvUanN7)3BdKT4=w6IUsjl$0Zg$ye)(FvS;TvA=c~LmE0BYQ{2e7`6x4Rha zQzV)l%3o`y!GRTTXJDBEssojCH)ja%NoF-RBuiR6#D|$kIgL&j%RYK3!ign+lYhC- zuip}~Bh+b!KQ&$ybs?wR6cu0E1Yhj6@Y!AOOPk1siBv&$*`cH>JJK;Mhzj4z=5QcG3BGvLd z9oNfGuc(EbDQf94O1;6QnE`o-sOn4cZBwKkdz2dSDHL=)Pl`-ie!+3htZ3_IxhRUQ zRYoZ?2yK)!0m?pzgF5@ej{V|!#S-RhVTNEje9}Xjbi4Ex1#sS%X_#vVnaNLUSWTc> z6#3fUIznwY0(#4oB*#v@R_p*X2|ES9^_Emt^Ajxp$SbPH7gB9|lR`?n z1Nz9)(sOeiN1E+c5(UnP74=!}yfTCtxX4?eG_$8|h$#r@Fx|fFFe$|jyve-MUnWu1 zW@b%v%GXu+(Vz?-$YSwAh{+$gI|-nbN#gLnyR-SCyX0~04AFkeB-7rXwV$F3BWYaO z6=qS3<}Om$`RKa7V6G^iAOqe~9T|tIVo{_>^2EUUJ?3X`{lK9BX+4kqG(W7ryRf6w z%h?+)xi#T`PLXfU6(JD4QFSiAZYpgnGdzIoqWMG&e!rAngbiI+N{c=RP3xO6KUR;P z8)8(cyfhZUD9s)X{^2(vUCRpE%C(%{H!MJn>IkcE+SWK6#N!B;b;YC=e$kAgPfHBq zG>rWGcag1R6ZUs5mMyir8hV=)E@?tVTUJ|2X_ARc?b`O7$*5$I#L@~ajv+m+55-2x zC@aLjYsPtBh3P^yxw^p8e@>L0!4+EN0B?%|Puv-$y(X`zHSzBpDjMlf!NRVcQLnNj zfz4p3wi77WIp0YNyp1726>}%M9GjFGuDV$7uE*UheSka93cIk^VHSfjuMZbLx58hL zH)Xw0A;Ed_=A37kmYlbp$@kc@ucBfMrWrx2(Du>CMb%&@2lrRAP~=Fb6HsyqjgLO& zTk4}o7M9xw_p@6pfn}hUOR&E@Ck099L~WBDt(CI#Jaoti9_?wlU{HWaD&P1)jqclS zC93APYUu*o_K9p*%fhSEW|chjXk~e*JRftx_dTUZgPdoTdyC%hc&rK(eq8HH@6O#f z(5Mpd6D+=cQ=E5O$g$*fk&}9@A)is$(qkAvnpQ%;dom%7T|<=3b#d%`zG4W2qjlV* zu*kLz=ZGAezRP=M?fl}N2Rk+{9s^J@2^wd1;r#n1O8K9bny)Yy%xttMS}^-w1&G6E1d0b1we}Z5eMX;8-r){;^-dwgJ&U6CG@Ni2-BRx42ti*P~|OYQ}CyX4L^exs}i6W zEoPZg7wnDOGu3hhxTc^na4zQpBcP)x=-|MwzFT>VPfecE+XI7QcQ3A7T~?8&@%l&l z_fGu+tm^JLFRaxbl!jqoVj+oT;aaR#S9gV~jxCZgq3GZU@GUkT5$(?lXE+>l6Y~y@ zg)1h`=qB3-tfN1IyJC_MSh)r)`uX?an8oM%%nu*6!(5N$1P9__<7wQ6V>)<-W-Ml5 znQre&jAHz1!rhH_P+)~7W=kCU<}qUo;+k(wfGDg?=LBeZkY$ipm!62$T(N4tPA+FX zaC|GOBQ0*D{NaAwCnM&~9%nOZzjkq0!jOu_aBsq6 zqGct{_jF|(tQ+HtM7oWv_0(zO5pAdeS|M3bmk!w&d@TlXPa4^SB3*!Qb9R~GTA~B0 z@v*Q`L<^;h$6pHn=JYc_&}yvwi0Bjcw7YM^^M3#G6AW$uE4xyk2Ust}AhtEW$TTey z7n)EGlvLVoKlN;_{^*+Ae>$iQxu-IhU%WhCx%Z8x|1{caC;TU%79;?YB$ZJZ}_#Pa4~}k!4J^7d*Udo=s!8UcFJSxvKWFfy{#j&<0pC%PQ+wf@nnp7=%lA zEv%EDwH)3^y+BY_(%Dy^`3Y7Q767MV%Bm#*A9EPQ@UN!qzJmmkhnS9eff)BFzP{1C z$pZ~54G=yp-rGWh373c!!rZ})8nRVZA2lYne{4rcf!>rrZ%Xae+$^ETe8LX#jm^R| zSp0y++SSUng#nJJx&r3Ml}VlRWQ~|LlD`m+iA6@TpmCjyw~|NDfS4Bk;1vSP^3}@q zK>RU`;Y$!~e|1WD(ms2QOc~bUmC75bEcY zJJN!?J^0$WhIAtkbVzln{;yAC2jVl)28Hv`%fd0q&!t}4XAF2DX=qAgUwBIgz1%%; zPtpk&xCh|VGm;GVpGYtl0J34c>5KpVG%o@j6m^TWn8z)`L}`Q$$1vE56@~v}c!XMs*H~&xY9WViKev0-@H5k6^#TZ1oM`iyUy;-j^`>{hPLPQ?y1HiEwH&_EA5b(eu zGCgC35|fK%U~q%@DC?nn9|*xQdECc4&jYGUzgGTDg#&V|fR|rutTy(AB*lW)@9BL& zhhZF&;6<4FARp+^F7&+cAq{_CaI?f9_)UNK!$Ip0l3)Kq3b#7~{gwEct}leJ>&!8T z2!<8R6F^9xk^gY~CpN1#vHQHESlQSB#xdMSKnElGG(6P-bz;;o<&9s^ zKtyyNph`BF>ctX{=juHzF&1AQDwHZ59DcGdo-Kb8uy+r~Jm>-OU1F^>Z7ARmHro>-N{UyWz zsAxsN%zqhg^KWw(C|igYC&z3{`^8WN8IOYw+wdeV?}r;Su4H$-=#G zjUE0#?JHnryxOXRzVW#L9_2KKSCP(>S_Rx{eO98f2`!x*<-(?-I0fr zNo3*5Qwk*^H~Akl(I278Pa4Qx z1_HE`3zFSOhzYS|A6fQQuf_hI4KeZnGAU+|TKFsX4k%z&*y}QC((9}L;QrBjBGjv_ zx{8e!3d;T6&q&PD|s@P^n+jxl0uTp#G$pvh@XF$36Vf~MD>+dKa=)bdF zX;<{{e|6+C=!NI|cJ#+XgGQfcGz#Kj9Wq9`w zWB=#1ehVe{f4KEqQ2oCTLRcwr69xu$A5;tAAI|;%#JPXC^$)jxFP;C%+@c3W4^mf4j literal 0 HcmV?d00001 diff --git a/terraform/ec2-examples/distributed-ml-training/main.tf b/terraform/ec2-examples/distributed-ml-training/main.tf index 3a32454c..cd090f56 100644 --- a/terraform/ec2-examples/distributed-ml-training/main.tf +++ b/terraform/ec2-examples/distributed-ml-training/main.tf @@ -140,6 +140,11 @@ data "aws_ssm_parameter" "ecs_gpu_optimized_ami" { name = "/aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended" } +resource "aws_placement_group" "workers" { + name = "ml-training" + strategy = "cluster" +} + module "autoscaling_head" { source = "terraform-aws-modules/autoscaling/aws" version = "~> 6.5" @@ -159,7 +164,6 @@ module "autoscaling_head" { iam_role_policies = { AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" AmazonSSMManagedEC2InstanceDefaultPolicy = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" - AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" } vpc_zone_identifier = module.vpc.private_subnets @@ -182,9 +186,9 @@ module "autoscaling_workers" { name = "${local.name}-workers" - #image_id = data.aws_ssm_parameter.ecs_bottlerocket_gpu_optimized_ami.value - image_id = jsondecode(data.aws_ssm_parameter.ecs_gpu_optimized_ami.value)["image_id"] - instance_type = local.instance_type_workers + placement_group = aws_placement_group.workers.name + image_id = jsondecode(data.aws_ssm_parameter.ecs_gpu_optimized_ami.value)["image_id"] + instance_type = local.instance_type_workers security_groups = [module.autoscaling_sg.security_group_id] user_data = base64encode(local.user_data_workers) @@ -264,11 +268,20 @@ module "ecs_service_head" { } task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn - tasks_iam_role_name = "dt-role-tasks" - tasks_iam_role_description = "Tasks IAM role for ${local.name}" + tasks_iam_role_name = "taskRole" + tasks_iam_role_description = "Task role for ${local.name}" tasks_iam_role_policies = { - AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + ReadOnlyAccess = "arn:aws:iam::aws:policy/ReadOnlyAccess" } + tasks_iam_role_statements = [ + { + actions = ["s3:*"] + resources = [ + "arn:aws:s3:::${aws_s3_bucket.results.bucket}", + "arn:aws:s3:::${aws_s3_bucket.results.bucket}/*" + ] + } + ] create_task_exec_iam_role = false enable_execute_command = false deployment_minimum_healthy_percent = 0 @@ -286,22 +299,18 @@ module "ecs_service_head" { sharedMemorySize = 20480 } mount_points = [{ - sourceVolume = "ray_results" - containerPath = "/home/ray/ray_results" + sourceVolume = "tmp" + containerPath = "/tmp" readOnly = false }] } } volume = { - "ray_results" = { - efs_volume_configuration = { - file_system_id = module.efs.id, - root_directory = "/" - transit_encryption = "ENABLED", - authorization_config = { - access_point_id = module.efs.access_points.ray_results.id - iam = "ENABLED" - } + "tmp" = { + dockerVolumeConfiguration = { + scope = "shared", + driver = "local", + autoprovision = true } } } @@ -355,11 +364,21 @@ module "ecs_service_workers" { } task_exec_iam_role_arn = aws_iam_role.task_execution_role.arn - tasks_iam_role_name = "dt-role-tasks" - tasks_iam_role_description = "Tasks IAM role for ${local.name}" + tasks_iam_role_name = "taskRole" + tasks_iam_role_description = "Task role for ${local.name}" tasks_iam_role_policies = { - AmazonElasticFileSystemClientFullAccess = "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" + ReadOnlyAccess = "arn:aws:iam::aws:policy/ReadOnlyAccess" } + tasks_iam_role_statements = [ + { + actions = ["s3:*"] + resources = [ + "arn:aws:s3:::${aws_s3_bucket.results.bucket}", + "arn:aws:s3:::${aws_s3_bucket.results.bucket}/*" + ] + } + ] + create_task_exec_iam_role = false enable_execute_command = false @@ -380,92 +399,52 @@ module "ecs_service_workers" { value = 4 }] mount_points = [{ - sourceVolume = "ray_results" - containerPath = "/home/ray/ray_results" + sourceVolume = "tmp" + containerPath = "/tmp" readOnly = false }] } } - # We are using network=host because there will be a single container in each host with GPUs. There is less overhead when using a single container with - # access to all 4 GPUs available in g5.12xlarge than 4 containers with 1 GPU each. - network_mode = "host" - volume = { - "ray_results" = { - efs_volume_configuration = { - file_system_id = module.efs.id, - root_directory = "/" - transit_encryption = "ENABLED", - authorization_config = { - access_point_id = module.efs.access_points.ray_results.id - iam = "ENABLED" - } + "tmp" = { + dockerVolumeConfiguration = { + scope = "shared", + driver = "local", + autoprovision = true } } } - tags = local.tags -} - - -################################################################################ -# Shared storage - EFS -################################################################################ - - -module "efs" { - source = "terraform-aws-modules/efs/aws" - - # File system - name = "distributed-storage-shared" - creation_token = "distributed-storage-shared" - encrypted = true - attach_policy = false - - lifecycle_policy = { - transition_to_ia = "AFTER_30_DAYS" - } - - # Mount targets / security group - mount_targets = { - (local.azs[0]) = { - subnet_id = module.vpc.private_subnets[0] - } - } - # Access point - access_points = { - ray_results = { - name = "ray_results" - posix_user = { - uid = 1000 - gid = 100 - } - root_directory = { - path = "/ray_results" - - creation_info = { - owner_uid = 1000 - owner_gid = 100 - permissions = "755" - } - } - - tags = local.tags - } - } - security_group_description = "EFS distributed training security group" - security_group_vpc_id = module.vpc.vpc_id + network_mode = "awsvpc" + subnet_ids = module.vpc.private_subnets security_group_rules = { - vpc = { - # relying on the defaults provdied for EFS/NFS (2049/TCP + ingress) - description = "NFS ingress from VPC private subnets" + ingress_private_ips = { + type = "ingress" + from_port = 0 + to_port = 0 + protocol = "-1" cidr_blocks = ["10.0.0.0/8"] } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } } - tags = local.tags } +resource "random_id" "bucket_name" { + byte_length = 8 +} + +resource "aws_s3_bucket" "results" { + bucket = "dt-results-${random_id.bucket_name.hex}" + tags = local.tags + force_destroy = true +} resource "aws_iam_role" "task_execution_role" { name = "distributed_training_task_execution_role" @@ -495,8 +474,6 @@ resource "aws_iam_role" "task_execution_role" { }) managed_policy_arns = [ "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", - "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess" ] - tags = local.tags } diff --git a/terraform/ec2-examples/distributed-ml-training/outputs.tf b/terraform/ec2-examples/distributed-ml-training/outputs.tf index 2c221ceb..b40e18ef 100644 --- a/terraform/ec2-examples/distributed-ml-training/outputs.tf +++ b/terraform/ec2-examples/distributed-ml-training/outputs.tf @@ -36,7 +36,12 @@ output "cluster_name" { value = module.ecs_cluster.name } -output "cluster_capacity_providers" { - description = "Map of cluster capacity providers attributes" - value = module.ecs_cluster.cluster_capacity_providers + +################################################################################ +# S3 +################################################################################ + +output "s3_bucket" { + description = "ARN that identifies the bucket for results" + value = aws_s3_bucket.results.arn } diff --git a/terraform/ec2-examples/distributed-ml-training/training_example.py b/terraform/ec2-examples/distributed-ml-training/training_example.py index fd3b8abe..b49ffa53 100644 --- a/terraform/ec2-examples/distributed-ml-training/training_example.py +++ b/terraform/ec2-examples/distributed-ml-training/training_example.py @@ -1,6 +1,5 @@ # import required torch and ray libraries -import tempfile import torch from torchvision.models import resnet18 from torchvision.datasets import FashionMNIST @@ -9,18 +8,25 @@ from torch.optim import Adam from torch.nn import CrossEntropyLoss from ray.train.torch import TorchTrainer -from ray.train import ScalingConfig, Checkpoint +from ray.train import ScalingConfig, RunConfig +from filelock import FileLock import ray -from pprint import pprint import time -from pprint import pprint +import argparse + +# Get arguments + +parser = argparse.ArgumentParser() +parser.add_argument("bucket_name", help="Bucket to publish results.", type=str) +args = parser.parse_args() + # Connect to the Ray cluster ray.init() # Download the data in the shared storage transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) -train_data = FashionMNIST(root='/home/ray/ray_results/data', +train_data = FashionMNIST(root='./data', train=True, download=True, transform=transform) @@ -46,38 +52,60 @@ def train_func(config): optimizer = Adam(model.parameters(), lr=0.001) # Retrieve the data from the shared storage. transform = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) - train_data = FashionMNIST(root='/home/ray/ray_results/data', train=True, download=False, transform=transform) - train_loader = DataLoader(train_data, batch_size=128, shuffle=True) + with FileLock(os.path.expanduser("./data.lock")): + train_data = FashionMNIST(root='./data', train=True, download=True, transform=transform) + # Download test data from open datasets + test_data = FashionMNIST(root="./data",train=False,download=True,transform=transform) + batch_size=128 + train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True) + test_loader = DataLoader(test_data, batch_size=batch_size) # Prepare dataloader for distributed training train_loader = ray.train.torch.prepare_data_loader(train_loader) + test_loader = ray.train.torch.prepare_data_loader(test_loader) # Define training loop for epoch in range(10): start = time.time() + model.train() for images, labels in train_loader: outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() - print(f"[GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()}] | [Epoch {epoch} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") - # Only save checkpoint after the last epoch + print(f"[Epoch {epoch} | GPU{torch.cuda.current_device()}: Process rank {torch.distributed.get_rank()} | Batchsize: {128} | Steps: {len(train_loader)} | Total epoch time: {time.time()-start}]") + model.eval() + test_loss, num_correct, num_total = 0, 0, 0 + + # Calculate loss and accuaricy in the 10th epoch + # you might want to do this in each epoch to detect overfitting as early as possible if epoch == 9: - checkpoint_dir = tempfile.gettempdir() - checkpoint_path = checkpoint_dir + "/model.checkpoint" - torch.save(model.state_dict(), checkpoint_path) + with torch.no_grad(): + for images, labels in test_loader: + prediction = model(images) + loss = criterion(prediction, labels) + test_loss += loss.item() + num_total += labels.shape[0] + num_correct += (prediction.argmax(1) == labels).sum().item() + + test_loss /= len(test_loader) + accuracy = num_correct / num_total # Report metrics and checkpoint to Ray. - ray.train.report({"loss": loss.item()},checkpoint=Checkpoint.from_directory(checkpoint_dir)) + ray.train.report(metrics={"loss": test_loss, "accuracy": accuracy}) # The scaling config defines how many workers -# In this case is equal to the total GPU count +# In this case is equal to the total GPU count scaling_config = ScalingConfig(num_workers=8, use_gpu=True) # Create the trainer instance trainer = TorchTrainer(train_func, - scaling_config=scaling_config) + scaling_config=scaling_config, + run_config=RunConfig( + storage_path=f"s3://{args.bucket_name}/", + name="ecs_dt_results") + ) # Run the training result = trainer.fit() # Print the results of the training -print(result) \ No newline at end of file +print(result) diff --git a/terraform/ec2-examples/distributed-ml-training/versions.tf b/terraform/ec2-examples/distributed-ml-training/versions.tf index 35402be4..c32bfca9 100644 --- a/terraform/ec2-examples/distributed-ml-training/versions.tf +++ b/terraform/ec2-examples/distributed-ml-training/versions.tf @@ -6,5 +6,9 @@ terraform { source = "hashicorp/aws" version = ">= 4.6" } + random = { + source = "hashicorp/random" + version = ">= 3.6.0" + } } } From a9b4436671699a87bffe89eb391d6d8c3c50fd1c Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 11 Jan 2024 08:41:12 -0700 Subject: [PATCH 12/17] Fix bugs and remove deployment of supporting resources --- .../distributed-ml-training-architecture.png | Bin .../distributed-ml-training/README.md | 83 +++++++--- .../distributed-ml-training/main.tf | 147 ++++++++---------- .../distributed-ml-training/outputs.tf | 30 ---- .../training_example.py | 5 +- .../distributed-ml-training/variables.tf | 0 6 files changed, 130 insertions(+), 135 deletions(-) rename terraform/ec2-examples/distributed-ml-training/docs/architecture.png => docs/distributed-ml-training-architecture.png (100%) delete mode 100644 terraform/ec2-examples/distributed-ml-training/variables.tf diff --git a/terraform/ec2-examples/distributed-ml-training/docs/architecture.png b/docs/distributed-ml-training-architecture.png similarity index 100% rename from terraform/ec2-examples/distributed-ml-training/docs/architecture.png rename to docs/distributed-ml-training-architecture.png diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 99b669ef..42318a3d 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -1,35 +1,40 @@ # ECS machine learning distributed training -This solution blueprint creates the infrastructure to run distributed training jobs using a [Ray cluster](https://docs.ray.io/en/latest/cluster/getting-started.html) and [PyTorch](https://pytorch.org/). The Ray head node runs on a m5.xlarge instance, while the 2 workers run on g5.12xlarge instances. +This solution blueprint creates the infrastructure to run distributed training jobs using a [Ray cluster](https://docs.ray.io/en/latest/cluster/getting-started.html) and [PyTorch](https://pytorch.org/). -![Solution architecture](docs/architecture.png) +![Distributed ML architecture](../../../docs/distributed-ml-training-architecture.png) -## Cost warning! - -By default, this blueprint uses g5.12xlarge (with 4 GPUs) to showcase multi-GPU and multi-node distributed training, but **can increase costs considerably over time**. You can modify this blueprint to use g5.xlarge (with one GPU) instead from the local variable **instance_type_workers** - if you change the instance type, you need to also modify the worker task definition to use 1 GPU instead of 4 (see **resource_requirements** and container command parameter **--num-gpus**) and the example training script outline below (see **num_workers** parameter) +By default, this blueprint uses g5.xlarge (with 1 GPU) instances to showcase a multi-node, data parallel distributed training. You can modify this blueprint to use larger instances from the local variable **instance_type_workers** if you need more GPUs. - if you change the instance type, you need to also modify the worker task and service definition memory, CPU and GPUs and the container command parameters. The [training script example](./training_example.py) assumes 2 machines with a single GPU each, but can be changed via the **num_workers** variable. ## Components -* Service discovery using AWS Cloud Map: The head node is registered to a private DNS using loca zones via cloud map. This allow workers to discover the head service and join the cluster -* 2 autoscaling groups: One for the head instance and other for the worker instances +* Service discovery: The head node is registered to a private DNS using local zones via cloud map. This allows worker tasks to discover the head task and join the cluster on start up. +* 2 autoscaling groups: One for the head instance and another one for the worker instances * ECS service definition: - * Task security group, task role and task execution role and - * Service discovery ARN is used in the service definition. ECS will automatically manage the registration and deregistration of tasks to this service discovery registry. - * Tasks for this service will be deployed in single private subnet to avoid AZ data transfer costs - * Task definitions with GPU resource requirements + * Head service: runs singleton processes responsible for cluster management + * Worker service: runs training jobs * S3 bucket to store the results ## Deployment +1. Deploy core-infra resources + ```shell +cd ./terraform/ec2-examples/core-infra terraform init -terraform plan -terraform apply +terraform apply -target=module.vpc -target=aws_service_discovery_private_dns_namespace.this + ``` -Due to the size of the container images, it might take several minutes until the containers reach a running state +2. Deploy this blueprint -## Example: training the resnet18 model with the FashionMNIST dataset +```shell +cd ../distributed-ml-training +terraform init +terraform apply +``` + +## Example: training the resnet model with the FashionMNIST dataset Once the cluster is deployed, you can connect to the EC2 instance running the head container using SSM, and open a bash shell in the container from there. This is only for demonstration purposes - Using notebooks with [SageMaker](https://aws.amazon.com/sagemaker/) or [Cloud 9](https://aws.amazon.com/cloud9/) provide a better user experience to run training jobs in python than using the bash shell @@ -43,18 +48,51 @@ aws ssm start-session --target $HEAD_INSTANCE_ID ``` 2. Connect to the container + +Due to the size of the container images, it might take several minutes until the containers reach a running state. The following command will fail if the contains is not running. + ``` CONTAINER_ID=$(sudo docker ps -qf "name=.*-rayhead-.*") sudo docker exec -it $CONTAINER_ID bash ``` -3. Inside the container shell, check the cluster status +3. Inside the container shell, check the cluster status. 3 nodes should be listed as healthy with 2.0 GPUs available ```bash ray status ``` +Example output: + +``` +======== Autoscaler status: 2024-01-11 07:19:06.991162 ======== +Node status +--------------------------------------------------------------- +Healthy: + 1 node_a3d74b6d5089c52f9848c1529349ba5c4966edaa633374b0566c7d69 + 1 node_a5a1aa596068c73e17e029ca221bfad7a7b0085a0273da3c7ad86096 + 1 node_3ae0c0cabb682158fef418bbabdf2ea63820e8b68e4ae2f4b24c8e66 +Pending: + (no pending nodes) +Recent failures: + (no failures) + +(...) + +Resources +--------------------------------------------------------------- +Usage: + 0.0/6.0 CPU + 0.0/2.0 GPU + 0B/38.00GiB memory + 0B/11.87GiB object_store_memory + +Demands: + (no resource demands) + +``` + 4. Run the [training script example](./training_example.py) - you can look at the comments inside the python script to learn more about each step. -A bucket was created as part of the terraform plan, make sure to add the name of that bucket (starts with "dt-results-") as argument of the training_example.py script +A bucket is created as part of the terraform plan (Bucket ARN is printed as output). Make sure to add the name of that bucket (starts with "dt-results-") as argument of the training_example.py script ```bash export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training @@ -64,11 +102,20 @@ python training_example.py YOUR_BUCKET_NAME ## Clean up +1. Destroy this blueprint + ```shell terraform destroy ``` +1. Destroy core-infra resources + +```shell +cd ../core-infra +terraform destroy + +``` ## Support -Please open an issue for questions or unexpected behaviour +Please open an issue for questions or unexpected behavior diff --git a/terraform/ec2-examples/distributed-ml-training/main.tf b/terraform/ec2-examples/distributed-ml-training/main.tf index 05cda63d..db34935f 100644 --- a/terraform/ec2-examples/distributed-ml-training/main.tf +++ b/terraform/ec2-examples/distributed-ml-training/main.tf @@ -2,16 +2,12 @@ provider "aws" { region = local.region } -data "aws_availability_zones" "available" {} data "aws_caller_identity" "current" {} locals { - name = "ecs-demo-distributed-ml-training" - region = "us-east-1" - - vpc_cidr = "10.0.0.0/16" - azs = slice(data.aws_availability_zones.available.names, 0, 1) - instance_type_workers = "g5.12xlarge" + name = "ecs-demo-distributed-ml-training" + region = "us-west-2" + instance_type_workers = "g5.xlarge" instance_type_head = "m5.xlarge" ray_head_container_image = "docker.io/rayproject/ray-ml:2.7.1.artur.c9f4c6-py38" ray_worker_container_image = "docker.io/rayproject/ray-ml:2.7.1.artur.c9f4c6-py38-gpu" @@ -81,17 +77,10 @@ module "ecs_cluster" { tags = local.tags } -resource "aws_service_discovery_private_dns_namespace" "this" { - name = "default.${local.name}.local" - description = "Service discovery namespace.clustername.local" - vpc = module.vpc.vpc_id - tags = local.tags -} - resource "aws_service_discovery_service" "this" { name = "head" dns_config { - namespace_id = aws_service_discovery_private_dns_namespace.this.id + namespace_id = data.aws_service_discovery_dns_namespace.core_infra.id dns_records { ttl = 300 type = "A" @@ -100,45 +89,6 @@ resource "aws_service_discovery_service" "this" { } } -################################################################################ -# Supporting Resources -################################################################################ - -module "vpc" { - source = "terraform-aws-modules/vpc/aws" - version = "~> 5.2.0" - - name = local.name - cidr = local.vpc_cidr - - azs = local.azs - private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] - public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] - - enable_nat_gateway = true - single_nat_gateway = true - enable_dns_hostnames = true - map_public_ip_on_launch = false - - # Manage so we can name - manage_default_network_acl = true - default_network_acl_tags = { Name = "${local.name}-default" } - manage_default_route_table = true - default_route_table_tags = { Name = "${local.name}-default" } - manage_default_security_group = true - default_security_group_tags = { Name = "${local.name}-default" } - - tags = local.tags -} - -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux -data "aws_ssm_parameter" "ecs_optimized_ami" { - name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" -} - -data "aws_ssm_parameter" "ecs_gpu_optimized_ami" { - name = "/aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended" -} resource "aws_placement_group" "workers" { name = "ml-training" @@ -166,7 +116,7 @@ module "autoscaling_head" { AmazonSSMManagedEC2InstanceDefaultPolicy = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" } - vpc_zone_identifier = module.vpc.private_subnets + vpc_zone_identifier = data.aws_subnets.private.ids health_check_type = "EC2" min_size = 1 max_size = 1 @@ -202,7 +152,7 @@ module "autoscaling_workers" { AmazonSSMManagedEC2InstanceDefaultPolicy = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" } - vpc_zone_identifier = module.vpc.private_subnets + vpc_zone_identifier = data.aws_subnets.private.ids health_check_type = "EC2" min_size = 2 max_size = 2 @@ -233,12 +183,19 @@ module "autoscaling_sg" { source = "terraform-aws-modules/security-group/aws" version = "~> 4.0" - name = local.name - description = "Autoscaling group security group" - vpc_id = module.vpc.vpc_id - ingress_cidr_blocks = [module.vpc.vpc_cidr_block] + name = local.name + description = "Autoscaling group security group" + vpc_id = data.aws_vpc.core_infra.id - ingress_rules = ["all-all"] + ingress_with_cidr_blocks = [ + { + from_port = -1 + to_port = -1 + protocol = -1 + description = "Allow all from VPC CIDR block" + cidr_blocks = data.aws_vpc.core_infra.cidr_block + }, + ] egress_rules = ["all-all"] @@ -288,7 +245,7 @@ module "ecs_service_head" { container_definitions = { ray_head = { - readonly_root_filesystem = true + readonly_root_filesystem = false image = local.ray_head_container_image user = 1000 cpu = 3072 @@ -302,11 +259,6 @@ module "ecs_service_head" { sourceVolume = "tmp" containerPath = "/tmp" readOnly = false - }, - { - sourceVolume = "tmp" - containerPath = "/tmp" - readOnly = false }] } } @@ -325,7 +277,7 @@ module "ecs_service_head" { } network_mode = "awsvpc" - subnet_ids = module.vpc.private_subnets + subnet_ids = data.aws_subnets.private.ids security_group_rules = { ingress_private_ips = { type = "ingress" @@ -355,8 +307,8 @@ module "ecs_service_workers" { desired_count = 2 cluster_arn = module.ecs_cluster.arn enable_autoscaling = false - memory = 189440 - cpu = 10240 + memory = 15360 + cpu = 3072 # Task Definition requires_compatibilities = ["EC2"] @@ -389,29 +341,24 @@ module "ecs_service_workers" { container_definitions = { ray_work = { - readonly_root_filesystem = true + readonly_root_filesystem = false image = local.ray_worker_container_image user = 1000 - cpu = 10240 - memory = 189440 - memory_reservation = 189440 - command = ["/bin/bash", "-lc", "--", "ray start --block --num-cpus=10 --num-gpus=4 --address=head.default.ecs-demo-distributed-ml-training.local:6379 --metrics-export-port=8080 --memory=198642237440"] + cpu = 3072 + memory = 15360 + memory_reservation = 15360 + command = ["/bin/bash", "-lc", "--", "ulimit -n 65536; ray start --block --num-cpus=3 --num-gpus=1 --address=head.default.core-infra.local:6379 --metrics-export-port=8080 --memory=15032385536"] linux_parameters = { - sharedMemorySize = 20480 + sharedMemorySize = 10240 } resource_requirements = [{ type = "GPU" - value = 4 + value = 1 }] mount_points = [{ sourceVolume = "tmp" containerPath = "/tmp" readOnly = false - }, - { - sourceVolume = "tmp" - containerPath = "/tmp" - readOnly = false }] } } @@ -426,7 +373,7 @@ module "ecs_service_workers" { } network_mode = "awsvpc" - subnet_ids = module.vpc.private_subnets + subnet_ids = data.aws_subnets.private.ids security_group_rules = { ingress_private_ips = { type = "ingress" @@ -476,7 +423,7 @@ resource "aws_iam_role" "task_execution_role" { "aws:SourceAccount" : data.aws_caller_identity.current.account_id }, "ArnLike" : { - "aws:SourceArn" : "arn:aws:ecs:us-east-1:${data.aws_caller_identity.current.account_id}:*" + "aws:SourceArn" : "arn:aws:ecs:${local.region}:${data.aws_caller_identity.current.account_id}:*" } } } @@ -487,3 +434,35 @@ resource "aws_iam_role" "task_execution_role" { ] tags = local.tags } + +################################################################################ +# Supporting Resources +################################################################################ + +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +data "aws_ssm_parameter" "ecs_gpu_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended" +} + +data "aws_subnets" "private" { + filter { + name = "tag:Name" + values = ["core-infra-private-${local.region}a"] + } +} + +data "aws_vpc" "core_infra" { + filter { + name = "tag:Name" + values = ["core-infra"] + } +} + +data "aws_service_discovery_dns_namespace" "core_infra" { + name = "default.core-infra.local" + type = "DNS_PRIVATE" +} diff --git a/terraform/ec2-examples/distributed-ml-training/outputs.tf b/terraform/ec2-examples/distributed-ml-training/outputs.tf index b40e18ef..a686db64 100644 --- a/terraform/ec2-examples/distributed-ml-training/outputs.tf +++ b/terraform/ec2-examples/distributed-ml-training/outputs.tf @@ -1,22 +1,3 @@ -################################################################################ -# VPC -################################################################################ - -output "vpc_id" { - description = "The ID of the VPC" - value = module.vpc.vpc_id -} - -output "private_subnets" { - description = "A list of private subnets for the client app" - value = module.vpc.private_subnets -} - -output "private_subnets_cidr_blocks" { - description = "A list of private subnets CIDRs" - value = module.vpc.private_subnets_cidr_blocks -} - ################################################################################ # Cluster ################################################################################ @@ -26,17 +7,6 @@ output "cluster_arn" { value = module.ecs_cluster.arn } -output "cluster_id" { - description = "ID that identifies the cluster" - value = module.ecs_cluster.id -} - -output "cluster_name" { - description = "Name that identifies the cluster" - value = module.ecs_cluster.name -} - - ################################################################################ # S3 ################################################################################ diff --git a/terraform/ec2-examples/distributed-ml-training/training_example.py b/terraform/ec2-examples/distributed-ml-training/training_example.py index b49ffa53..c9f435bd 100644 --- a/terraform/ec2-examples/distributed-ml-training/training_example.py +++ b/terraform/ec2-examples/distributed-ml-training/training_example.py @@ -92,9 +92,8 @@ def train_func(config): # Report metrics and checkpoint to Ray. ray.train.report(metrics={"loss": test_loss, "accuracy": accuracy}) -# The scaling config defines how many workers -# In this case is equal to the total GPU count -scaling_config = ScalingConfig(num_workers=8, use_gpu=True) +# The scaling config defines how many worker processes to use for the training. Usually equals to the number of GPUs +scaling_config = ScalingConfig(num_workers=2, use_gpu=True) # Create the trainer instance trainer = TorchTrainer(train_func, diff --git a/terraform/ec2-examples/distributed-ml-training/variables.tf b/terraform/ec2-examples/distributed-ml-training/variables.tf deleted file mode 100644 index e69de29b..00000000 From 40013059fd885440002730de4fd89ca323a045fa Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 11 Jan 2024 08:43:26 -0700 Subject: [PATCH 13/17] Change aws provider version to >= 5.0 --- terraform/ec2-examples/distributed-ml-training/README.md | 2 +- terraform/ec2-examples/distributed-ml-training/versions.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 42318a3d..a50b62ba 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -64,7 +64,7 @@ ray status Example output: ``` -======== Autoscaler status: 2024-01-11 07:19:06.991162 ======== +(...) Node status --------------------------------------------------------------- Healthy: diff --git a/terraform/ec2-examples/distributed-ml-training/versions.tf b/terraform/ec2-examples/distributed-ml-training/versions.tf index c32bfca9..c60e8f58 100644 --- a/terraform/ec2-examples/distributed-ml-training/versions.tf +++ b/terraform/ec2-examples/distributed-ml-training/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.6" + version = ">= 5.0" } random = { source = "hashicorp/random" From 49f5c07c7c31ed7b762dfeeac024708d696cff0f Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 11 Jan 2024 08:47:48 -0700 Subject: [PATCH 14/17] Improve docs format --- terraform/ec2-examples/distributed-ml-training/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index a50b62ba..5ac350f0 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -23,7 +23,6 @@ By default, this blueprint uses g5.xlarge (with 1 GPU) instances to showcase a m cd ./terraform/ec2-examples/core-infra terraform init terraform apply -target=module.vpc -target=aws_service_discovery_private_dns_namespace.this - ``` 2. Deploy this blueprint From 486dd47ba5c12c6aeb108e497666b686b1a6de51 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 11 Jan 2024 08:59:39 -0700 Subject: [PATCH 15/17] Include region in test commands, change output bucket arn to id --- .../ec2-examples/distributed-ml-training/README.md | 10 ++++++---- .../ec2-examples/distributed-ml-training/outputs.tf | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 5ac350f0..220c3718 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -40,10 +40,11 @@ Once the cluster is deployed, you can connect to the EC2 instance running the he 1. Connect to the instance ```bash HEAD_INSTANCE_ID=$(aws ec2 describe-instances \ - --filters 'Name=tag:Name,Values=ecs-demo-distributed-ml-training-head' \ - --query 'Reservations[*].Instances[*].InstanceId' --output text) + --filters 'Name=tag:Name,Values=ecs-demo-distributed-ml-training-head' 'Name=instance-state-name,Values=running' \ + --query 'Reservations[*].Instances[0].InstanceId' --output text --region us-west-2 +) -aws ssm start-session --target $HEAD_INSTANCE_ID +aws ssm start-session --target $HEAD_INSTANCE_ID --region us-west-2 ``` 2. Connect to the container @@ -55,7 +56,8 @@ CONTAINER_ID=$(sudo docker ps -qf "name=.*-rayhead-.*") sudo docker exec -it $CONTAINER_ID bash ``` -3. Inside the container shell, check the cluster status. 3 nodes should be listed as healthy with 2.0 GPUs available +3. Inside the container shell, check the cluster status. 3 nodes should be listed as healthy with 2.0 GPUs available - If you do not see 2.0 GPUs, the workers have not started yet. + ```bash ray status ``` diff --git a/terraform/ec2-examples/distributed-ml-training/outputs.tf b/terraform/ec2-examples/distributed-ml-training/outputs.tf index a686db64..b0889634 100644 --- a/terraform/ec2-examples/distributed-ml-training/outputs.tf +++ b/terraform/ec2-examples/distributed-ml-training/outputs.tf @@ -12,6 +12,6 @@ output "cluster_arn" { ################################################################################ output "s3_bucket" { - description = "ARN that identifies the bucket for results" - value = aws_s3_bucket.results.arn + description = "Bucket name for results" + value = aws_s3_bucket.results.id } From a32c91dadff3caff4e866adf4f12bdb266ff4d3a Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 11 Jan 2024 09:55:06 -0700 Subject: [PATCH 16/17] Change bucket ARN for name in the docs --- terraform/ec2-examples/distributed-ml-training/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 220c3718..99233af7 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -93,7 +93,7 @@ Demands: ``` 4. Run the [training script example](./training_example.py) - you can look at the comments inside the python script to learn more about each step. -A bucket is created as part of the terraform plan (Bucket ARN is printed as output). Make sure to add the name of that bucket (starts with "dt-results-") as argument of the training_example.py script +A bucket is created as part of the terraform plan (Bucket name is printed as output). Make sure to add the name of that bucket (starts with "dt-results-") as argument of the training_example.py script ```bash export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training From da408a81689d9f10a964de899d8ed9f43b91ce08 Mon Sep 17 00:00:00 2001 From: sfloresk Date: Thu, 11 Jan 2024 12:43:53 -0700 Subject: [PATCH 17/17] Add result of training script --- .../distributed-ml-training/README.md | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/terraform/ec2-examples/distributed-ml-training/README.md b/terraform/ec2-examples/distributed-ml-training/README.md index 99233af7..e84fc22d 100644 --- a/terraform/ec2-examples/distributed-ml-training/README.md +++ b/terraform/ec2-examples/distributed-ml-training/README.md @@ -92,8 +92,7 @@ Demands: ``` -4. Run the [training script example](./training_example.py) - you can look at the comments inside the python script to learn more about each step. -A bucket is created as part of the terraform plan (Bucket name is printed as output). Make sure to add the name of that bucket (starts with "dt-results-") as argument of the training_example.py script +4. Run the [training script example](./training_example.py) - It uses distributed data parallel to split the data between GPUs. You can look at the comments inside the python script to learn more about each step. A bucket is created as part of the terraform plan (Bucket name is printed as output). Make sure to add the name of that bucket (starts with "dt-results-") as argument of the training_example.py script ```bash export RAY_DEDUP_LOGS=0 # Makes the logs verbose per each process in the training @@ -101,6 +100,56 @@ wget https://raw.githubusercontent.com/aws-ia/ecs-blueprints/main/terraform/ec2- python training_example.py YOUR_BUCKET_NAME ``` +Example output: + +``` +(...) +Wrapping provided model in DistributedDataParallel. +(...) + +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 0 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.660568237304688] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 0 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.65453052520752] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 1 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.172431230545044] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 1 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.17476797103882] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 2 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 16.807305574417114] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 2 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 16.807661056518555] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 3 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.16184115409851] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 3 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.164414882659912] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 4 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.43423628807068] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 4 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.430140495300293] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 5 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.319995880126953] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 5 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.331279277801514] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 6 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.402108669281006] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 6 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.385886192321777] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 7 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 16.865890741348267] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 7 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 16.86034846305847] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 8 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.0880389213562] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 8 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.094018697738647] +(RayTrainWorker pid=234, ip=10.0.15.42) [Epoch 9 | GPU0: Process rank 1 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.191094160079956] +(RayTrainWorker pid=227, ip=10.0.2.255) [Epoch 9 | GPU0: Process rank 0 | Batchsize: 128 | Steps: 235 | Total epoch time: 17.189364910125732] + +(..) +╭───────────────────────────────╮ +│ Training result │ +├───────────────────────────────┤ +│ checkpoint_dir_name │ +│ time_this_iter_s 182.976 │ +│ time_total_s 182.976 │ +│ training_iteration 1 │ +│ accuracy 0.8852 │ +│ loss 0.41928 │ +╰───────────────────────────────╯ + +(...) Total running time: 3min 7s + +Result( + metrics={'loss': 0.4192830347106792, 'accuracy': 0.8852}, + path='dt-results-EXAMPLE/ecs_dt_results/TorchTrainer_d1824_00000_0_(...)', + filesystem='s3', + checkpoint=None +) +``` + ## Clean up 1. Destroy this blueprint