From 49d81c7b43b9184846cdc0ec6c6e679efa1718ce Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Wed, 10 Jul 2024 14:54:32 +0800 Subject: [PATCH 01/22] docs: add getting started for README.md --- ai-ml/jark-stack/terraform/README.md | 39 +++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/ai-ml/jark-stack/terraform/README.md b/ai-ml/jark-stack/terraform/README.md index 1b5b022ee..4ac6fe7a9 100644 --- a/ai-ml/jark-stack/terraform/README.md +++ b/ai-ml/jark-stack/terraform/README.md @@ -1,6 +1,43 @@ # JupyterHub, Argo, Ray, Kubernetes -Docs coming soon... +# Prerequisites + +Before we begin, ensure you have all the prerequisites in place to make the deployment process smooth and hassle-free. Ensure that you have installed the following tools on your machine. + +- aws cli +- kubectl +- terraform + +# Deploy + +Clone the repository + +``` +git clone https://github.com/awslabs/data-on-eks.git +``` + +Navigate into one of the example directories and run install.sh script + +Important Note: Ensure that you update the region in the variables.tf file before deploying the blueprint. Additionally, confirm that your local region setting matches the specified region to prevent any discrepancies. For example, set your `export AWS_DEFAULT_REGION=""` to the desired region: + +``` +cd data-on-eks/ai-ml/jark-stack/ && chmod +x install.sh +./install.sh +``` + +# Verify the resources +Verify the Amazon EKS Cluster + +``` +aws eks --region us-west-2 describe-cluster --name jark-stack + +# Creates k8s config file to authenticate with EKS +aws eks --region us-west-2 update-kubeconfig --name jark-stack + +# Output shows the EKS Managed Node group nodes +kubectl get nodes + +``` ## Requirements From e64d10e8ca89501c3fb6e175b749da1dcb8fe1a9 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 16 Jul 2024 15:27:25 +0800 Subject: [PATCH 02/22] feat: add guide of creating snapshots for preloading container images into data volume --- .../preload-container-image-ami/README.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 ai-ml/jark-stack/preload-container-image-ami/README.md diff --git a/ai-ml/jark-stack/preload-container-image-ami/README.md b/ai-ml/jark-stack/preload-container-image-ami/README.md new file mode 100644 index 000000000..9a480f64e --- /dev/null +++ b/ai-ml/jark-stack/preload-container-image-ami/README.md @@ -0,0 +1,64 @@ +# Preload Container Images + +To preload the container images in EBS snapshots and launching them in an EKS cluster, using sample will use Bottlerocket AMIs for Amazon EKS. +You can use the generated EBS Snapshot to launch worker nodes in Amazon EKS Node Groups and Worker Nodes created by Karpenter. + +For details, refer to the GitHub sample: +https://github.com/aws-samples/bottlerocket-images-cache/tree/main + +Usage Example: + +``` +# Using nohup in terminals to avoid disconnections +❯ nohup ./snapshot.sh --snapshot-size 150 -r us-west-2 docker.io/rayproject/ray-ml:2.10.0-py310-gpu,public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest & +❯ tail -f nohup.out + +2024-07-15 17:18:53 I - [1/8] Deploying EC2 CFN stack ... +2024-07-15 17:22:07 I - [2/8] Launching SSM . +2024-07-15 17:22:08 I - SSM launched in instance i-07d10182abc8a86e1. +2024-07-15 17:22:08 I - [3/8] Stopping kubelet.service .. +2024-07-15 17:22:10 I - Kubelet service stopped. +2024-07-15 17:22:10 I - [4/8] Cleanup existing images .. +2024-07-15 17:22:12 I - Existing images cleaned +2024-07-15 17:22:12 I - [5/8] Pulling images: +2024-07-15 17:22:12 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 ... +2024-07-15 17:27:50 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 pulled. +2024-07-15 17:27:50 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 ... +2024-07-15 17:27:58 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 pulled. +2024-07-15 17:27:58 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 ... +2024-07-15 17:31:34 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 pulled. +2024-07-15 17:31:34 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 ... +2024-07-15 17:31:36 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 pulled. +2024-07-15 17:31:36 I - [6/8] Stopping instance ... +2024-07-15 17:32:25 I - Instance i-07d10182abc8a86e1 stopped +2024-07-15 17:32:25 I - [7/8] Creating snapshot ... +2024-07-15 17:38:36 I - Snapshot snap-0c6d965cf431785ed generated. +2024-07-15 17:38:36 I - [8/8] Cleanup. +2024-07-15 17:38:37 I - Stack deleted. +2024-07-15 17:38:37 I - -------------------------------------------------- +2024-07-15 17:38:37 I - All done! Created snapshot in us-west-2: snap-0c6d965cf431785ed +``` + +You can copy the snapshot ID `snap-0c6d965cf431785ed` and move forward. +An end-to-end deployment example can be found in [JARK Stack](../terraform/README.md) + +# With Karpenter + +You can specify snapshot ID in a Karpenter node template. You should also specify AMI used when provisioning node is BottleRocket. Add the content on EC2NodeClass (or AWSNodeTemplate on older release of Karpenter): + +v1beta1 API: +``` +apiVersion: karpenter.k8s.aws/v1beta1 +kind: EC2NodeClass +metadata: + name: default +spec: + amiFamily: Bottlerocket # Ensure OS is BottleRocket + blockDeviceMappings: + - deviceName: /dev/xvdb + ebs: + volumeSize: 150Gi + volumeType: gp3 + kmsKeyID: "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab" # Specify KMS ID if you use custom KMS key + snapshotID: snap-0123456789 # Specify your snapshot ID here +``` From 7f4a1ee5a39553198fa1f81efbe84d65f7a97362 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 16 Jul 2024 15:29:41 +0800 Subject: [PATCH 03/22] feat: add karpenter manifest to support --- ai-ml/jark-stack/terraform/karpenter.tf | 160 ++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 ai-ml/jark-stack/terraform/karpenter.tf diff --git a/ai-ml/jark-stack/terraform/karpenter.tf b/ai-ml/jark-stack/terraform/karpenter.tf new file mode 100644 index 000000000..7b2d78af0 --- /dev/null +++ b/ai-ml/jark-stack/terraform/karpenter.tf @@ -0,0 +1,160 @@ + +resource "kubectl_manifest" "karpenter_gpu_node_class" { + yaml_body = <<-YAML + apiVersion: karpenter.k8s.aws/v1beta1 + kind: EC2NodeClass + metadata: + name: default + spec: + amiFamily: Bottlerocket + role: ${module.eks_blueprints_addons.karpenter.node_iam_role_name} + securityGroupSelectorTerms: + - tags: + Name: ${module.eks.cluster_name}-node + subnetSelectorTerms: + - tags: + karpenter.sh/discovery: ${module.eks.cluster_name} + tags: + karpenter.sh/discovery: ${module.eks.cluster_name} + blockDeviceMappings: + # Root device + - deviceName: /dev/xvda + ebs: + volumeSize: 50Gi + volumeType: gp3 + encrypted: true + # Data device: Container resources such as images and logs + - deviceName: /dev/xvdb + ebs: + volumeSize: 300Gi + volumeType: gp3 + encrypted: true + ${var.bottlerocket_data_disk_snpashot_id != null ? "snapshotID: ${var.bottlerocket_data_disk_snpashot_id}" : ""} + YAML + depends_on = [module.eks_blueprints_addons] +} + +resource "kubectl_manifest" "karpenter_gpu_node_pool" { + yaml_body = <<-YAML + apiVersion: karpenter.sh/v1beta1 + kind: NodePool + metadata: + name: gpu + labels: + NodeGroupType: g5-gpu-karpenter + type: karpenter + spec: + disruption: + consolidateAfter: 600s + consolidationPolicy: WhenEmpty + expireAfter: 720h + limits: + cpu: 1k + memory: 1000Gi + nvidia.com/gpu: 50 + template: + spec: + nodeClassRef: + name: default + requirements: + - key: kubernetes.io/arch + operator: In + values: ["amd64"] + - key: karpenter.k8s.aws/instance-category + operator: In + values: ["g"] + - key: karpenter.k8s.aws/instance-generation + operator: Gt + values: ["4"] + - key: "karpenter.k8s.aws/instance-cpu" + operator: In + values: ["4", "8", "16"] + - key: kubernetes.io/os + operator: In + values: ["linux"] + - key: "karpenter.k8s.aws/instance-hypervisor" + operator: In + values: ["nitro"] + - key: "topology.kubernetes.io/zone" + operator: In + values: ${jsonencode(local.azs)} + - key: karpenter.sh/capacity-type + operator: In + values: ["on-demand"] + taints: + - key: nvidia.com/gpu + value: "Exists" + effect: "NoSchedule" + + YAML + depends_on = [module.eks_blueprints_addons] +} + +resource "kubectl_manifest" "karpenter_node_pool" { + yaml_body = <<-YAML + apiVersion: karpenter.sh/v1beta1 + kind: NodePool + metadata: + name: default + labels: + NodeGroupType: x86-cpu-karpenter + type: karpenter + spec: + disruption: + consolidateAfter: 600s + consolidationPolicy: WhenEmpty + expireAfter: 720h + limits: + cpu: 1k + template: + spec: + kubelet: + maxPods: 110 + nodeClassRef: + name: default + requirements: + - key: "karpenter.k8s.aws/instance-category" + operator: In + values: ["c", "m", "r"] + - key: "karpenter.k8s.aws/instance-cpu" + operator: In + values: ["4", "8", "16"] + - key: "karpenter.k8s.aws/instance-hypervisor" + operator: In + values: ["nitro"] + - key: "topology.kubernetes.io/zone" + operator: In + values: ${jsonencode(local.azs)} + - key: "kubernetes.io/arch" + operator: In + values: ["amd64"] + - key: "karpenter.sh/capacity-type" + operator: In + values: ["on-demand"] + YAML + depends_on = [module.eks_blueprints_addons] +} + + +resource "aws_iam_policy" "karpenter_controlloer_policy" { + description = "Additional IAM policy for Karpenter controller" + policy = data.aws_iam_policy_document.karpenter_controller_policy.json +} + +data "aws_iam_policy_document" "karpenter_controller_policy" { + statement { + actions = [ + "ec2:RunInstances", + "ec2:CreateLaunchTemplate", + ] + resources = ["*"] + effect = "Allow" + sid = "Karpenter" + } +} + +resource "aws_iam_role_policy_attachment" "karpenter_controller_policy_attachment" { + # name = "karpenter-controller-policy-attachment" + role = module.eks_blueprints_addons.karpenter.iam_role_name + policy_arn = aws_iam_policy.karpenter_controlloer_policy.arn +} \ No newline at end of file From ad213f1e4f523bc8b11eb74f0cc7c78112689a33 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 16 Jul 2024 15:32:13 +0800 Subject: [PATCH 04/22] fix: remove nodeSelector & correct import_path --- .../ray-serve-stablediffusion.yaml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml b/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml index d7ec79f9c..754c54621 100644 --- a/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml +++ b/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml @@ -21,7 +21,7 @@ spec: serveConfigV2: | applications: - name: stable-diffusion-deployment - import_path: "ray_serve_stablediffusion:entrypoint" + import_path: "ray_serve_sd:entrypoint" route_prefix: "/" runtime_env: env_vars: @@ -61,6 +61,7 @@ spec: # For faster inference scaling, consider building a custom image with only your workload's essential dependencies. # Smaller images lead to faster scaling, especially across multiple nodes. # Notice that we are using the same image for both the head and worker nodes. You might hit ModuleNotFoundError if you use a different image for head and worker nodes. + # Preload Container Image into data volumes for faster new ray worker nodes - name: head image: public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest imagePullPolicy: IfNotPresent # Ensure the image is always pulled when updated @@ -87,9 +88,9 @@ spec: requests: cpu: 2 memory: 16Gi - nodeSelector: - NodeGroupType: x86-cpu-karpenter - type: karpenter + # nodeSelector: + # NodeGroupType: x86-cpu-karpenter + # type: karpenter volumes: - name: ray-logs emptyDir: {} @@ -123,9 +124,9 @@ spec: cpu: "3" memory: "14Gi" nvidia.com/gpu: 1 - nodeSelector: - NodeGroupType: g5-gpu-karpenter - type: karpenter + # nodeSelector: + # NodeGroupType: g5-gpu-karpenter + # type: karpenter tolerations: - key: "nvidia.com/gpu" operator: "Exists" From 0a8ba73ccf78dee7dc5a3f83b2c6347315fb7225 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 16 Jul 2024 15:39:36 +0800 Subject: [PATCH 05/22] chore: bump nvidia_device_plugin version to v0.15.1 --- ai-ml/jark-stack/terraform/addons.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai-ml/jark-stack/terraform/addons.tf b/ai-ml/jark-stack/terraform/addons.tf index 89c9d6fcc..6b310df96 100644 --- a/ai-ml/jark-stack/terraform/addons.tf +++ b/ai-ml/jark-stack/terraform/addons.tf @@ -182,7 +182,7 @@ module "data_addons" { #--------------------------------------------------------------- enable_nvidia_device_plugin = true nvidia_device_plugin_helm_config = { - version = "v0.14.5" + version = "v0.15.1" name = "nvidia-device-plugin" values = [ <<-EOT From 95a62398d5c534a425dca72e53e5e32bdf730699 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 16 Jul 2024 15:50:33 +0800 Subject: [PATCH 06/22] feat: add Karpenter ec2nodeclass with custom EBS snapshots with preloaded container images --- ai-ml/jark-stack/terraform/README.md | 35 +++++++++++++++++++++++++ ai-ml/jark-stack/terraform/addons.tf | 2 +- ai-ml/jark-stack/terraform/variables.tf | 8 ++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/ai-ml/jark-stack/terraform/README.md b/ai-ml/jark-stack/terraform/README.md index 4ac6fe7a9..f2f5c96cd 100644 --- a/ai-ml/jark-stack/terraform/README.md +++ b/ai-ml/jark-stack/terraform/README.md @@ -8,6 +8,40 @@ Before we begin, ensure you have all the prerequisites in place to make the depl - kubectl - terraform +## Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS + +Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins for downloading and extracting container images from Amazon ECR. + +To build snapshots with preloaded container images, refer to [this page](../preload-container-image-ami/README.md) for details. + +``` +# Get the snapshot ID with ./snapshot.sh + +export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed + +./install.sh +... +``` +You can find the container image is preloaded into the disk disk. + +``` +kubectl describe pod + +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Warning FailedScheduling 41m default-scheduler 0/8 nodes are available: 1 Insufficient cpu, 3 Insufficient memory, 8 Insufficient nvidia.com/gpu. preemption: 0/8 nodes are available: 8 No preemption victims found for incoming pod. + Normal Nominated 41m karpenter Pod should schedule on: nodeclaim/gpu-ljvhl + Normal Scheduled 40m default-scheduler Successfully assigned stablediffusion/stablediffusion-raycluster-ms6pl-worker-gpu-85d22 to ip-100-64-136-72.us-west-2.compute.internal + Normal Pulled 40m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine + Normal Created 40m kubelet Created container wait-gcs-ready + Normal Started 40m kubelet Started container wait-gcs-ready + Normal Pulled 39m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine + Normal Created 39m kubelet Created container worker + Normal Started 38m kubelet Started container worker + ``` + # Deploy Clone the repository @@ -93,6 +127,7 @@ kubectl get nodes | [region](#input\_region) | region | `string` | `"us-west-2"` | no | | [secondary\_cidr\_blocks](#input\_secondary\_cidr\_blocks) | Secondary CIDR blocks to be attached to VPC | `list(string)` |
[
"100.64.0.0/16"
]
| no | | [vpc\_cidr](#input\_vpc\_cidr) | VPC CIDR. This should be a valid private (RFC 1918) CIDR range | `string` | `"10.1.0.0/21"` | no | +| [preload-image-bottlerocket-snapshot-id](#input\_preload-image-bottlerocket-snapshot-id) | The snapshot ID of bottlerocket that preloads the container image | `string` | | no ## Outputs diff --git a/ai-ml/jark-stack/terraform/addons.tf b/ai-ml/jark-stack/terraform/addons.tf index 6b310df96..4a079b748 100644 --- a/ai-ml/jark-stack/terraform/addons.tf +++ b/ai-ml/jark-stack/terraform/addons.tf @@ -223,7 +223,7 @@ module "data_addons" { #--------------------------------------------------------------- # Karpenter Resources Add-on #--------------------------------------------------------------- - enable_karpenter_resources = true + enable_karpenter_resources = false karpenter_resources_helm_config = { g5-gpu-karpenter = { values = [ diff --git a/ai-ml/jark-stack/terraform/variables.tf b/ai-ml/jark-stack/terraform/variables.tf index 20723a956..efd9697db 100644 --- a/ai-ml/jark-stack/terraform/variables.tf +++ b/ai-ml/jark-stack/terraform/variables.tf @@ -50,3 +50,11 @@ variable "enable_kubecost" { type = bool default = false } + + +variable "bottlerocket_data_disk_snpashot_id" { + description = "Bottlerocket Data Disk Snapshot ID" + type = string + default = "" + +} \ No newline at end of file From d732759d305602f2401bf519cee94b0be057701c Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Wed, 17 Jul 2024 10:22:41 +0800 Subject: [PATCH 07/22] fix: add node labeling in Karpenter NodePool --- ai-ml/jark-stack/terraform/karpenter.tf | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ai-ml/jark-stack/terraform/karpenter.tf b/ai-ml/jark-stack/terraform/karpenter.tf index 7b2d78af0..f63d9d5a9 100644 --- a/ai-ml/jark-stack/terraform/karpenter.tf +++ b/ai-ml/jark-stack/terraform/karpenter.tf @@ -40,9 +40,6 @@ resource "kubectl_manifest" "karpenter_gpu_node_pool" { kind: NodePool metadata: name: gpu - labels: - NodeGroupType: g5-gpu-karpenter - type: karpenter spec: disruption: consolidateAfter: 600s @@ -53,6 +50,10 @@ resource "kubectl_manifest" "karpenter_gpu_node_pool" { memory: 1000Gi nvidia.com/gpu: 50 template: + metadata: + labels: + NodeGroupType: g5-gpu-karpenter + type: karpenter spec: nodeClassRef: name: default @@ -96,9 +97,7 @@ resource "kubectl_manifest" "karpenter_node_pool" { kind: NodePool metadata: name: default - labels: - NodeGroupType: x86-cpu-karpenter - type: karpenter + spec: disruption: consolidateAfter: 600s @@ -107,6 +106,10 @@ resource "kubectl_manifest" "karpenter_node_pool" { limits: cpu: 1k template: + metadata: + labels: + NodeGroupType: x86-cpu-karpenter + type: karpenter spec: kubelet: maxPods: 110 From 63104c189f493340cf0b904200cb4eb9ef2591a7 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Wed, 17 Jul 2024 10:23:01 +0800 Subject: [PATCH 08/22] fix: Uncomment nodeSelector in RayService --- .../ray-serve-stablediffusion.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml b/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml index 754c54621..bca28103e 100644 --- a/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml +++ b/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml @@ -88,9 +88,9 @@ spec: requests: cpu: 2 memory: 16Gi - # nodeSelector: - # NodeGroupType: x86-cpu-karpenter - # type: karpenter + nodeSelector: + NodeGroupType: x86-cpu-karpenter + type: karpenter volumes: - name: ray-logs emptyDir: {} @@ -124,9 +124,9 @@ spec: cpu: "3" memory: "14Gi" nvidia.com/gpu: 1 - # nodeSelector: - # NodeGroupType: g5-gpu-karpenter - # type: karpenter + nodeSelector: + NodeGroupType: g5-gpu-karpenter + type: karpenter tolerations: - key: "nvidia.com/gpu" operator: "Exists" From 035bc294f462d5dfffcd59e43e0f0c640eccc582 Mon Sep 17 00:00:00 2001 From: Darren Lin <76021640+lindarr915@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:14:40 +0800 Subject: [PATCH 09/22] Bottlerocket cache container image (#1) * feat: run GPU node with BR and EBS snapshot with container image cache * refactor: remove kubectl_manifest of karpenter custom resources * feat: locust file fo load testing * feat: End-to-end deployment of Bottlerocket nodes with container image cache --- ai-ml/jark-stack/terraform/README.md | 22 ++- ai-ml/jark-stack/terraform/addons.tf | 40 +++- ai-ml/jark-stack/terraform/install.sh | 4 + ai-ml/jark-stack/terraform/karpenter.tf | 141 -------------- .../locust/locustfile.py | 30 +++ .../img/bottlerocket-image-cache.png | Bin 0 -> 111517 bytes .../bestpractices/preload-container-images.md | 173 ++++++++++++++++++ 7 files changed, 255 insertions(+), 155 deletions(-) create mode 100644 gen-ai/inference/stable-diffusion-rayserve-gpu/locust/locustfile.py create mode 100644 website/docs/bestpractices/img/bottlerocket-image-cache.png create mode 100644 website/docs/bestpractices/preload-container-images.md diff --git a/ai-ml/jark-stack/terraform/README.md b/ai-ml/jark-stack/terraform/README.md index f2f5c96cd..40c8b892b 100644 --- a/ai-ml/jark-stack/terraform/README.md +++ b/ai-ml/jark-stack/terraform/README.md @@ -8,16 +8,6 @@ Before we begin, ensure you have all the prerequisites in place to make the depl - kubectl - terraform -## Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS - -Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins for downloading and extracting container images from Amazon ECR. - -To build snapshots with preloaded container images, refer to [this page](../preload-container-image-ami/README.md) for details. - -``` -# Get the snapshot ID with ./snapshot.sh - -export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed ./install.sh ... @@ -44,6 +34,8 @@ Events: # Deploy + + Clone the repository ``` @@ -54,6 +46,16 @@ Navigate into one of the example directories and run install.sh script Important Note: Ensure that you update the region in the variables.tf file before deploying the blueprint. Additionally, confirm that your local region setting matches the specified region to prevent any discrepancies. For example, set your `export AWS_DEFAULT_REGION=""` to the desired region: +## Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS (o) + +Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins for downloading and extracting container images from Amazon ECR. + +To build snapshots with preloaded container images, refer to [this page](../preload-container-image-ami/README.md) for details. + +# Get the snapshot ID with ./snapshot.sh + +export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed + ``` cd data-on-eks/ai-ml/jark-stack/ && chmod +x install.sh ./install.sh diff --git a/ai-ml/jark-stack/terraform/addons.tf b/ai-ml/jark-stack/terraform/addons.tf index 4a079b748..f56eb700d 100644 --- a/ai-ml/jark-stack/terraform/addons.tf +++ b/ai-ml/jark-stack/terraform/addons.tf @@ -145,9 +145,10 @@ module "eks_blueprints_addons" { #--------------------------------------------------------------- # Data on EKS Kubernetes Addons #--------------------------------------------------------------- + module "data_addons" { source = "aws-ia/eks-data-addons/aws" - version = "~> 1.31.4" # ensure to update this to the latest/desired version + version = "~> 1.40" # source = "aws-ia/eks-data-addons/aws" oidc_provider_arn = module.eks.oidc_provider_arn @@ -182,7 +183,7 @@ module "data_addons" { #--------------------------------------------------------------- enable_nvidia_device_plugin = true nvidia_device_plugin_helm_config = { - version = "v0.15.1" + version = "v0.16.1" name = "nvidia-device-plugin" values = [ <<-EOT @@ -223,14 +224,16 @@ module "data_addons" { #--------------------------------------------------------------- # Karpenter Resources Add-on #--------------------------------------------------------------- - enable_karpenter_resources = false + enable_karpenter_resources = true karpenter_resources_helm_config = { + g5-gpu-karpenter = { values = [ <<-EOT name: g5-gpu-karpenter clusterName: ${module.eks.cluster_name} ec2NodeClass: + amiFamily: Bottlerocket karpenterRole: ${split("/", module.eks_blueprints_addons.karpenter.node_iam_role_arn)[1]} subnetSelectorTerms: id: ${module.vpc.private_subnets[2]} @@ -238,6 +241,20 @@ module "data_addons" { tags: Name: ${module.eks.cluster_name}-node instanceStorePolicy: RAID0 + blockDeviceMappings: + # Root device + - deviceName: /dev/xvda + ebs: + volumeSize: 50Gi + volumeType: gp3 + encrypted: true + # Data device: Container resources such as images and logs + - deviceName: /dev/xvdb + ebs: + volumeSize: 300Gi + volumeType: gp3 + encrypted: true + ${var.bottlerocket_data_disk_snpashot_id != null ? "snapshotID: ${var.bottlerocket_data_disk_snpashot_id}" : ""} nodePool: labels: @@ -276,13 +293,28 @@ module "data_addons" { name: x86-cpu-karpenter clusterName: ${module.eks.cluster_name} ec2NodeClass: + amiFamily: Bottlerocket karpenterRole: ${split("/", module.eks_blueprints_addons.karpenter.node_iam_role_arn)[1]} subnetSelectorTerms: id: ${module.vpc.private_subnets[3]} securityGroupSelectorTerms: tags: Name: ${module.eks.cluster_name}-node - instanceStorePolicy: RAID0 + # instanceStorePolicy: RAID0 + blockDeviceMappings: + # Root device + - deviceName: /dev/xvda + ebs: + volumeSize: 100Gi + volumeType: gp3 + encrypted: true + # Data device: Container resources such as images and logs + - deviceName: /dev/xvdb + ebs: + volumeSize: 300Gi + volumeType: gp3 + encrypted: true + ${var.bottlerocket_data_disk_snpashot_id != null ? "snapshotID: ${var.bottlerocket_data_disk_snpashot_id}" : ""} nodePool: labels: diff --git a/ai-ml/jark-stack/terraform/install.sh b/ai-ml/jark-stack/terraform/install.sh index 1814a9044..c1b95e6c0 100755 --- a/ai-ml/jark-stack/terraform/install.sh +++ b/ai-ml/jark-stack/terraform/install.sh @@ -6,6 +6,10 @@ targets=( "module.eks" ) +# Get the snapshot ID with ./snapshot.sh +# Uncomment the command line below and replace the value with the snapshot ID +# export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0ffe58efdd845d938 + # Initialize Terraform terraform init -upgrade diff --git a/ai-ml/jark-stack/terraform/karpenter.tf b/ai-ml/jark-stack/terraform/karpenter.tf index f63d9d5a9..87bcded99 100644 --- a/ai-ml/jark-stack/terraform/karpenter.tf +++ b/ai-ml/jark-stack/terraform/karpenter.tf @@ -1,144 +1,3 @@ - -resource "kubectl_manifest" "karpenter_gpu_node_class" { - yaml_body = <<-YAML - apiVersion: karpenter.k8s.aws/v1beta1 - kind: EC2NodeClass - metadata: - name: default - spec: - amiFamily: Bottlerocket - role: ${module.eks_blueprints_addons.karpenter.node_iam_role_name} - securityGroupSelectorTerms: - - tags: - Name: ${module.eks.cluster_name}-node - subnetSelectorTerms: - - tags: - karpenter.sh/discovery: ${module.eks.cluster_name} - tags: - karpenter.sh/discovery: ${module.eks.cluster_name} - blockDeviceMappings: - # Root device - - deviceName: /dev/xvda - ebs: - volumeSize: 50Gi - volumeType: gp3 - encrypted: true - # Data device: Container resources such as images and logs - - deviceName: /dev/xvdb - ebs: - volumeSize: 300Gi - volumeType: gp3 - encrypted: true - ${var.bottlerocket_data_disk_snpashot_id != null ? "snapshotID: ${var.bottlerocket_data_disk_snpashot_id}" : ""} - YAML - depends_on = [module.eks_blueprints_addons] -} - -resource "kubectl_manifest" "karpenter_gpu_node_pool" { - yaml_body = <<-YAML - apiVersion: karpenter.sh/v1beta1 - kind: NodePool - metadata: - name: gpu - spec: - disruption: - consolidateAfter: 600s - consolidationPolicy: WhenEmpty - expireAfter: 720h - limits: - cpu: 1k - memory: 1000Gi - nvidia.com/gpu: 50 - template: - metadata: - labels: - NodeGroupType: g5-gpu-karpenter - type: karpenter - spec: - nodeClassRef: - name: default - requirements: - - key: kubernetes.io/arch - operator: In - values: ["amd64"] - - key: karpenter.k8s.aws/instance-category - operator: In - values: ["g"] - - key: karpenter.k8s.aws/instance-generation - operator: Gt - values: ["4"] - - key: "karpenter.k8s.aws/instance-cpu" - operator: In - values: ["4", "8", "16"] - - key: kubernetes.io/os - operator: In - values: ["linux"] - - key: "karpenter.k8s.aws/instance-hypervisor" - operator: In - values: ["nitro"] - - key: "topology.kubernetes.io/zone" - operator: In - values: ${jsonencode(local.azs)} - - key: karpenter.sh/capacity-type - operator: In - values: ["on-demand"] - taints: - - key: nvidia.com/gpu - value: "Exists" - effect: "NoSchedule" - - YAML - depends_on = [module.eks_blueprints_addons] -} - -resource "kubectl_manifest" "karpenter_node_pool" { - yaml_body = <<-YAML - apiVersion: karpenter.sh/v1beta1 - kind: NodePool - metadata: - name: default - - spec: - disruption: - consolidateAfter: 600s - consolidationPolicy: WhenEmpty - expireAfter: 720h - limits: - cpu: 1k - template: - metadata: - labels: - NodeGroupType: x86-cpu-karpenter - type: karpenter - spec: - kubelet: - maxPods: 110 - nodeClassRef: - name: default - requirements: - - key: "karpenter.k8s.aws/instance-category" - operator: In - values: ["c", "m", "r"] - - key: "karpenter.k8s.aws/instance-cpu" - operator: In - values: ["4", "8", "16"] - - key: "karpenter.k8s.aws/instance-hypervisor" - operator: In - values: ["nitro"] - - key: "topology.kubernetes.io/zone" - operator: In - values: ${jsonencode(local.azs)} - - key: "kubernetes.io/arch" - operator: In - values: ["amd64"] - - key: "karpenter.sh/capacity-type" - operator: In - values: ["on-demand"] - YAML - depends_on = [module.eks_blueprints_addons] -} - - resource "aws_iam_policy" "karpenter_controlloer_policy" { description = "Additional IAM policy for Karpenter controller" policy = data.aws_iam_policy_document.karpenter_controller_policy.json diff --git a/gen-ai/inference/stable-diffusion-rayserve-gpu/locust/locustfile.py b/gen-ai/inference/stable-diffusion-rayserve-gpu/locust/locustfile.py new file mode 100644 index 000000000..e441c1420 --- /dev/null +++ b/gen-ai/inference/stable-diffusion-rayserve-gpu/locust/locustfile.py @@ -0,0 +1,30 @@ +import json +from locust import HttpUser, task, between + +class StableDiffusionUser(HttpUser): + wait_time = between(1, 2) # Seconds between requests + + @task + def generate_image(self): + prompt = "A beautiful sunset over the ocean" + payload = { + "prompt": prompt + } + + headers = { + "Content-Type": "application/json" + } + + response = self.client.get( + "/imagine", + params=payload, + data=json.dumps(payload), + headers=headers + ) + + if response.status_code == 200: + print(f"Generated image for prompt: {prompt}") + else: + print(f"Error generating image: {response.text}") + + # You can add more tasks here if needed diff --git a/website/docs/bestpractices/img/bottlerocket-image-cache.png b/website/docs/bestpractices/img/bottlerocket-image-cache.png new file mode 100644 index 0000000000000000000000000000000000000000..2b1179023e6b0a564d1c7c86ac37e10f9f727afc GIT binary patch literal 111517 zcmagF1yodP*aixMQUcP7fDHFDU#jg9oI>F5~x=;-L< zn2!N1KQ(_80XKSoYeOBn!Y-j7baZS!0s622Zweacg`&Fz(*5`8lAH{d=pS$iq<2Y9 zPTvcK4DcfofJdO+o9KZ<<4_*|zLS%YlarJIOM+x=WfU)gw80?YA_JC^SCn=5w?6`d zBK*%pU?~}3g3D$IB#sb3^v7NT=>UHl69Q0Pz#V7?t`;`H1qS@e%DBpcT@|kYkJ>&y zUMMKa9fAwEBxe8yDN2D9fac2(BU>}mOLDrvvlq@21>6i#NKfM75Dz~@pcIY>beC0< zl9d7hO}bb=qBqdVKvqUZN=8avNm)TkR{rqKUjh$%%l*^a27yNS;r_GSA?>ID1m<75 ziFjjZ0LUKZV5AF?M1kQbSyN#5hkeK>KYtv+tbbVdzl$Hf2%!3){%!R@k#R_1CvulS z`T)y4a0rYa!W-y<`yZ~_;C;N2II66?Cm0U5rJ9(SBT4_xPCx)8{4=#Bz*tGyeK)`ue`+AXq!Nzz)w3mmwl^(%e4n{LDaqxm! zQZ0bh>^*>sgSEXq!NSBu*}%+R24rid5GW6fv2yTnrz&|LuB~991GDf29w;`Jcr*g3 zh(sBhnK@VjV-Y&?2unpdYv9q`(o-4e0|f;-&lh?BY`xx0+${HKV z>FE%C?A>Wz0T5ugtThH0Z(wAHQZRuKOw25m4SkfLhB{_Yij^(Y&raEv>aPoxv7#y| z)AaC46l;jCt|H7#$&{i*g&=f+DRf8}`v8o$GQ`YA9*!YVeM~9x0Z@#UnG(iN0qzbl zhwCD#h7j36l8u2U!4_d|VC(NLYZpkd^uh%a4)+kKNb@HfVy$6TB&;4xNk#^TGnX@$ z^&`03T4Oy-iGEnL{NX})BM-Ea*&$LarGdT;*~~&)-v;E1^wdYo215LN%^h?J26j|1Lg!G@R;EUt{x%p(BTuY6#lzab z=&+4ug!CrbCEk+Zi#D)8!O%To-f@}4MnU_L!61Z)Vl@Ccxh^^xwt3I;YFdITdwPi301 zrHQ8rK^A8XA>eG$7@9uL5`k4#^tSLck#(ThD(G9#?7bXN_E2hojVam#Z*J!9qpu`m zViE{dqF{As3YHXeOMPk}#>m^ zR5T9s#gJ?R@w&cx2C{&?5$zR0U_CtrKWhi=02yOth@U;oM8_QL<8R_E=V?ilF|(yw zdMThm5U>H36zC^wt*GIbQ=y2TLr<-VUKh(6<6O zYKieDI^fJ?9Kd$=x&a0_ZJ4gV4ar#XP_J=#!$S?jDMOL=csv>@kHt~U3@}g|gg%4@ z(E*YCw3RUCeirf+e{T{EXJ)Ufi-iKqV}U_tz}+5B@^H7&hk5zSYa63zihy%4z@cn( zO-+pyY-!d|gFu9Xy&T@b)E11fMjFUq17IkG9g(Ks4}s|0SR(>0P&%eCIRseWjvQ!g zhlkTV4a@=|6r`yf#sF<>?TIn8lgHa)4ns(QyS6Pv*UHZpSl1-bG{8@e0CP8`n857a zVO~ZS_O>(!YdZuIW9bEVQ1U?-+2{g3_)yv8WZ;J^gc{4E{B`Xq=0rtPKM#Of@9?yX$%cxRZRS#t^)<7t#b}0?~(= zLXk=!sJ8-K5r;td1u83>_kE##TK^XZNdw76oScs0NwU4#kVIajoOpHKiYXf(J2TTWNB-Nf42PJbeSlst z1Of>H?3xH6d3&M#WWjzu0cZywBVD9C+0Vz=(9}oA*jib^$XmlzC!(z>JJ(Yp* zsN-owpukXoeFO8_ljO`CJZyn{LkDVPWl16!Sz`4_o@kJR0#qJsZD!$bPoU}ec*$T1 z`dB?%ABw+^xuqf=NkIWparbfXvVxKvz*JK`1sVwM>n?{dl|^}gkq&Y&T`L%oqK|Mu zQmGc^M%Lyg7%y`(ADEJbF3yC4^3n10clU!MJt#<=9FWEsfRu>#zOt6Ova)vO7IH>* zN@y#nvOlmb0*XL2q2a+O|3h~qPeJK>KmY|lj3*v4WCvv!0YNhMBLU$}$CIXGXDbhv zwO1mLY5uahCbo9QrdA|R9Uo$#qOzecR9Vq7&|JX?q+lw8mob$;WIB*>7${?H$waIN z6?g>W4RsX!(Nu)7p|YuishqJT8LUJ?2I}LztaJ^L6tbco$Q!N$$N3tgQGl3r(ZC=} z9G+-mC~Kx@Z7Pd2w364eQjqa7gUY~ciGjdtJ2NY?i4v9u2FNk6l65c*@J9Ftc<5Tm z!DT=MlA(g4gC){~L?D?$?39oYG!&y`3-UwbXkSZ3 zUz8^b;-iE_;}t!Wtw0b%`2e!1tZ3}5XG4Kg;7TSYXs8F+*B7f#!^1rSdwxUZ2%N2j zJ06J3O7e21+CV~}W2BE%w8TI#o@RQO!v%femNGOf+QQlhMOCx~%*l`7PtuVwryOPv zKz{`)S| zmg2II#ze8T8)`qOG$sCcz^!tPS6?shGK@|es_*RV1l0@*1bpdH@1*7p1HDWku62Xg42_ugPeROoK@ z_~mRArw6$<@pAlvN(g@DiaG!8-9dF|=j)tT3|GXb?_bt>cEr@`0@3vt>#>_r$i`#;R`}xaD2`X0>TA5ny>{ zI(%&T)UQvP_G#ONS2C#)T2W)fE3WC-t&h*F=4G&gNB`y4AvNEa%fN_E)zK?x5yP4r zOThD;`Q2LpIOuim+$}ScqGB4KfBGI@u`q)tSNUD< zKJ{BQj|ggvIV%5JPw79N&UIvYI^4SVBkHk%0;qYullkrR@@v?T0QgO2Op!<1;mKy%;L#fKK{O7J(yty z49fDufYw=Eqq{((pAsI^c{yPJ{R(k`4Brb9j|mCsc^{GSHWgNgGr5aDaa(0T{rkoI zgin66}wtVV~)Y)?;_e) ztk3ahr@UDkEG-CqH7}ZU1RwdY-UD=FvlxwFm7i~BA$k+SrSknX9Ivjo$(^-*3!l>A z?%O~P9f!n3ftM+^W3gXvNFhshw`JmJ9Ca@*e7U6-*;jY0WSGJ%9Bs(BdE5w+5ji(H zmb>?sw3QLkk=H{)NZ!&xoRa76>-;G50`kAcc0;S-C_ z2Q;!!GMUpr!IPE-ws2&eHp%NR;pC4YfW5@VN?KJK=w|k7g zo8bPec0@hm*fmo(=mL}F!RE^ZOl8oo1;@~Hh>&XwamkndSh<5NV1@-0KODB*B0O?q zwbWDlQuRGC8M}PII{W>*ydrP|7i2i<)W6-`XKub^p~tvBv3Nr`x=H@`gaaeFb>lWQ zpLhu|;-mFK^gp80W4==?YIF7o_{3Sni0FrF5=nxauu`vaJ9yKD!RCama^G=w8O|`q zRAmmoQEtZ2&?6?J?^SwiJmx$Ywz)V}Lf~mLyMFPHAd(L)E&ID!LHUUz7xNw8>h%_-IDO(Jf3?3z zg?~W_6|7$_QcgUYtzFqF_l>Jw4?ADEvbSQ9`~B)FES9YbA_U zs+CoEI*|~bT3ffoCp44h-gg4ltv1{I&_67CYj^jl(125p;JVty{CUP(gun6PMw2Y} zSwu)|)9hx2c6cNykNtJ7_JD4Z7)(o|G|RXfhv*(y^NHO*bWFDz zUkLU`frpXK$m_CCdl!CP9j11-ehIr5>gm=yzakkb3h=!0<9-MdOX1@K%>8Xxo5^dW#w_4`*yo{y34CfW9`O^%*$`L)QbSusB-!bcPY zWRnuT&O*=^=)1~WSCK!m5Mp!C*h1bf9seK|>G!B>er;5Vut-J3Pe}+7~yVdH~ z=H1?#q5j}vzD04T)%qVBWFK9e^V@_=;2XvdN> zLkI^Kz(M)QQi_;*m7%80%e9yDTwmo}mth-M$9jW?PiCBc72UM@sPbHK$jEo<%7@0H zJWY~!SVHZ=f~unBWlADU0Ic&zqsy)U6wE7+02h|xBz`f#ZHr|i4&C-u>L8a>*E^dL z=I>Ja+d)WA%{w(*&VAQjstXA;9@Xvh!O|=KmTOqYUc8AMp z>I2fOr3Mjq1ApOY2KD6 z9$3wHRq{^43znMdZ7#rye@|y}(K6XQMZgOs@xIDAYFzw%sO5O|fv7eg<93ca>VBsD z16{P^edj9YG8p?pjxRsUd2)aGCfAqN@ssQFuYWc$FYcT|Ejynm%zC*0JqYj+*MzMe z{NXws*T=BX@~rvCQKX>L3E)R<5sO(I;)0`N+)dSsakC4&0ffqF+ix3pnYLhOWO-8= zV9!7YCC0lI@|vCKd_el0w(@90yg;99LEq*4)1^?l4>`a2;!7q%M%E(Ya#I5}b!s2l zmy5qHj|o}mkx7$x2}_{9QZD6*N>E7{0CD%lsrlNf|Mg*Lz`#ERu$6n%d{D zD^U(F3WQADF{??w1ZY90pMzkH&DVyQ^CF)75iPH_`&Se}G1z%U| zhzn}R<4X{n@;n`;mb?9-PQ6kX-)5Cxq63EN`K8SJtCHd~qntCBh^+&-ldO-63sFJ~ zduqgk!7Z;$^*(lQBil27wxo6FRqqy~)8`J3S4<40qwh0;fBRQ64XS2MzS3f2`gL0S zz9ZvWwxL*L$m90?M@AgUyW1*Vy}4p#@>K#OHJ0`<&8&w zt@0eOCuqv$hq3uD=f|7BP#=h^3~aBY>(fPrvacF&MEhJ+`q?eav>eC;W>#|pMY0v| zZc*%D$A$Wwg<_7|3xsEEMf>boh%9jCwog@~_jV_5DLHK=GOe%cu%prML^+7D#kwEE zZ-?c371I*z??R?zo~F?&N1a<(1ux*6n;5zFCK-e8i#1E1aRh89LBYS^o4-^BHNX4< z+k9D0OH0jE7`t@-(NeaDPZe7=e%h-qq?Ft2Z^v17Y}>OxKg+cR6FzTHkeu3uqiY z!!si5_=2a1lWm@XQy31L+1}}Hc%;@>&_cnT_@k*>>*oRgFch4chG^Xsbk6jX6wY-tPu2@-FRY0i^(yB|EGEeDDBWfE-R69qiWIiXvuYNkrit+N^_+({ zzYOU*_GE_jWmd3vMv6r>Yy2lpog2$f#4bv7H&_Yu^)6e4cyUrnAzN-i{QFG!C*O)U zpS92qbSE%S?>)rR@hw8OOrc-*7Cv(l`& z6y)_Y(garE^^@fxlC$aCSHR}ZrWTY4I>Y=O*x}7cgOt^>)1SMtE~8s&=%sWGT&tv% zW+TW4zj*5)W4YziHDwtAt!l-4gS#S1ervDP|8ZjU=(~CU98gaDlLR+CJ=DC?8(g?! zAA~|=I<`V2)F{E@+6f!&!wgCiJvYqbi?tevNle}B<wo>ma7nZh7P=KE~ z-3$2Aom}+CHsbi2SdQs;rGw{&-z)_Xj?nn&(aAe9KX~@YV?#+kVL_UB zY)Pl!6ty;mU@6&jv%AyrR)dUvIX$GT)8 zJodrSbHSvBdAG|-Og|F@d~gCh<`Dkoq4y+-UnN^tF^t;ijyjhs__4RSRou@f zM*g=;p__NL5P@gn`Fp8(Y>Rzm?1QZL=eWZ|$mC>g*T=B;J9OdHeAU6D!KGq1C>Ih4 z93rCUoVFga9z88_KUqMI4v3n%p$y61&Q7CK_g|>tx%%#YT>60d$}E?#G2E-xC;M~^ z9A6yWoi?ME8xtnbpmzLe{pmbEMWGkL&O;V8z9Ob+F^J_oS&@0cp|M4E(&iozHwv_^ z+yD3Qdm`c?1juMSmZlR(?%0~A#^`%{qa(1Zll|_e!t5`@9L`Q|Sy#^mdx~bu;wnUEr-{o%CuAT}5n` zqIqM2h<4@0xEiA|m%_6nqEG76+S{y8Deg}-|19MgL59EucnFK$1{r!>^S3=>cMEj{ zsx8vd&AsZ9GHXqJa!2hMVhFwy4eAYTF>uvOYO+nYRCnOxiQtehT4>g_Z{g8Pt4BiL zCa!D=C3LBX=j-MjW8hp^f1$!Oom6>4EmZo^s;|p0>?@B8F}pO!;%wWtRQ1p2c0WlT zi5ad*u{`y8r7wQv$Wfj?Kq2MxI;AJyPjBiHcF-}3nF8qYK=!B>2t;_=&% zg@Bawi^di%4KnA*Qm8e50hUQjb~>me$^CQvCn9le@+L{cdBuDV-NL5p zb(s}gwbO2rFZp1j!EBDu#&bP?VA*E%(el@-!Wg(fpBLA}-3KmpJ3${tS0B6edr(jvh>$Ycx>`}lLmna^W+s3(Gj0W8hJ0~Q zo5ZG!YRMZZ49X%5&RzXUYjE5`L!E{0s1HT>51%F>(j1?T=QO^acrf;x z5>T*w=i@Bbk7L2|?;}2k-|}xLXbQR@tOV)_7sWiAvu(%M;Ip}wh7 z`L)VaHTkCKd4A}(4!j}pQVKO#=5u@m?tEMDeA@>rjw(~3&)JXq1_=3pYWVim0;Q6+ zPJd{WN$G{w?6_j2so{k+2|L@Z%3*e`XG!gBjoXL`rLSMwV_$d%Q*hTC?F+3C>Z+PI z9`wc!&!J~l`>*m`%ZtW@atb{vMhE35wWm-QIjfb%^EeEC-V|7EqKzT;0IX-$HEQ}J z-J>Ard)s(Tzsr;ufv?Q_)x1YdVg2jwEkmRwhPS(z5#8qb&!OTxea_Ls<)c7&t-r3; zw~=ZfZvN*uaw4J?Knmi|{zy4{cBVz?MqFZ}DKTnMu>p;^)bMgTDK71S2Ph`NQ1v6o zTrVbpL!cRR@%CfQ(I`)TyYs!TCz76S3Pyo>rq85**IH(zp9z_7Yk$6gUC!N$or57l z`k>#dvJ&*S#-+dVwAg9ZZ0vn7g*7UFjb?jg3bQ+L&8c-r%aSD~zSIMyxK%QK*R_s< z^j|c*A$&zbiKh?hk<`SUrCC4ddMUsE!TVn|57qlho{lHJik+R{Uj0-^S5ftn`r~C} zc{o$w8L8jKu*#b`-({KSQI~-4~S~VqSDu0?0!8xxD{@R{seD?S_EO zM4n!sHb3*D3BD+Z$tcgn|5~PLx(uAi1z$}jMWGlD#*#Rk?x0>XNYv4ec~?{>IWKyv zHJq7=eAhIQtS{mv{ix)}Ey{;6$%!SD3-3`*@sSi-((0G9r$fK;ydLwftI64JF*k6k zXo}0&jLPELsV4aU0x zR(YE$-42u=-t_$)r+RR%sw;@DcG$k7+rmFQxj{4x}{*xX9 zh?$Ct*j0Kclqda^R}aWQaf;76;-GMA;z4JGZhT{2K^zzEe1p)W9!{(EQP{Cn$lX|5 zwgg|{xRZAgP3?{hRhS2y`*)%bI7%jx)6rME9?2+LTyy;FL%T&UK$1KE1WP-q1hH+4 zAK3A^&w!%m0}kjLu~VkD3Egt%M_*5#xh=uj!P-?fdT<(H*m#jk`e3@f=k#y4r?al< zGha@H>|;)7E_41kK<_qK+~ScTxE$TwEjQrmixpVc$(Z6U$-WrL_eSo$uHX=y5^Kcu zY2?!R{^zzVo)xZpZH1zbk8(h6>%biI!X{sWllUjtb%s>AEIfNOE-u%H+=+9fSMMT@( z={y5krsPyj+kU-k`UTWgG5M9F?bzv?(wP$zb}n@tUAcu+M^cvat(AEHa(**@5cW6TQ!;tAL0!`9q?L>TGFaGV?)eX=|@ ztyGw2{xB|RNK|ulko&bEX84o3b|i4#$kUKL*ZC?cFrMdQ2>L~XVqH#Rd#bNgCX7;V zhStmP{2ruR^^2);2@&QO&R6*pJUL;fQFkhZt8NIJaEUjS59Pi+)Fqk;Z=J!+o9l9% zNa}I!xihamTY}z-QST8|Vn@U;u$D5~^TrgPZ3{Xx3 z25t&lhD9?J^=tB~E!D+v1@7;?EqGq65O^x0Y`8%kzl*Evim9Ie_`I*SJ#cOz<}Gq0 z)%)I#!5PBy|n{Xj`lo#?XS5SqSn4ZE=(yR5V%u*I-XEoMfLH>oiKP_?m z=nyJ2ap}{(=<>&fuNcfpA9K~~p$6b#&jL253o$p-;${=i&P|Ls31WviT%1qe7`UAlrmoJxQt(xp(n=+6X7dgIBl4I{5PAA`AMzNOSkK<9oIf?Ta4*2Z*H^1x z?Q6x+3-~zXMZ}08W}{fvSCs&ySS*GbgqNa-kh*|pEh^&S$oUD$cfcn226p1F&jmjB z+6U#nrIwqU*$MQO^9^ouQ9j$@n1|`=n%0bMZBRO5mKvBI;vzQ6!}&J6=>Pp zS`b*1bVnl|wp>$Ywv~E_A6qbQW)toYlOgYIH3V6^$md@l%hhuUr1r9FuEQGTv)j5~ zsyG8McsGv}b5Q<4-3ZI;ugfRpS&Sd4Wd*&cKRxsG*E-WVf#p2^pi_ZeFZ*}}#S_xtY*zu;j# z67IHdFm-^z#$}wj3iUr+yiNVuuad;onSvLzpgPG@xc)F^NF}1?QO(M8#|h_ykW7Y8 zJ9!7{Wnua0WKOB;FPt-s>SMx0gwL?d^JBBVp#D@Gp6DM|0M2LMR2lpN$Vm~r%JW&I z@_Y7kG3kyvPmQZ1(K=G^bem&`$H&tBzowa;+>+sL8%+?dj8^aH0*RhOeV$(-JdU~c zqf&Q6K-gv^o4j{7;D=3(42HB2wtMTs!?8(C(k8O@3h(d7XL~yT1R*-E+<&AJu2)&6 z4v;?lIu)o4#TddXq~nBz1v+>5){_;7xfJINUZ;_GD7Sg`y&+b=-#lkt*sgEeZN)V8 zI=Oy3dZ5=b@NU4@GjG)8=f>!Pc{p)6Po=-zana~x(3x*t4aOO{Yj23pr-m_4kE-1T z&Eg4~nm*ht^9qO|d85BPcyWl8;JKq}?F9q*@BJCc2vq-eSB zx2oN7wF?V;la1y+s)cGQ*1VNpBJsW-eR7A%++h3Q9o8~ zl>Cc$l3TT@G33-OwE8u-{bdq9jCd!K6cQKx{%@^N?};eDfp=0jwAcX=RI-$Qi~=*M z2~Oq~a_3qh9QTS!oTW3JHi@tr_K3M6t-+Q`QbNhMbz5P7JI4?bS6_K~M`XxQCe-bR zhV58_^V*{SWmoyd+`YJh1fZHw?D?O{kIxZ6;WX$!zIZo%SQ%K)cz$g5+}18TR|2@> zm`LX{=k~_UhMDu<*K*v9u$;Znh!!~KQ}^B(!+bH^Z_T@uW)H7=WxGb{9a{Hop7G%t z@zkSr(VIN&2Rz0CrhPF^CEsj#QJTl!%)i*!ADSEFQNwM1Q7>z+q4$dUKj6?IESac% zf_@EFX?I%ba{j#b+0ZZSMuK4(4F@V!m_(`K!O$Pch2n^i3mvQ?7xG=Jm>azHCH4~k z_ger74hi=~4Q5<_U3N@JjLGJB6H{fML!fgsAV8`6vm$?l8_q1G3YdR;SFA}H{Slx* z!jO1;wk5fzB`AO1pG86STXzGO@nowDYOD`6aL_Ew{2+46)lgbAe^A|#0PJjVzE0r0 z$o$*5=+|VU>fbC%I|;q54Un*4wm1GRxfTK&ueQ0V`Sa{|#s33-{UgXk_=yO?L;L1I zH+$D8pz_Y@MUvzhRaJgYQoM}_F_|n|&0wX}@qG7fvCAJqFA7z3>NHB%LAMC=Eh^I_ zk#*QXi4Nyt{F#j~BN5G0b-hQsceXG8TDV)Rr5V3Jdu~E@5+O3*zGZcp^8a7n`w|6i z7j|5GHFWRxn+D>%?8$dLqOC(nW?t8$CSk#j2hzSj&1zQmCl6ZS%{HQ6Up9{JebwL5 zvM!=nLXBj z&N83d6nC0dEmaK12If*YbuLkhH&40+q+Vt$wUK5Fay zYU=3?MvPbY&xgY`KNbhg1!MEyxz6jZ5}e~hmPa^VC}{?JK^ZE}6iB;j;5HL^Sx4!z zb?r|4IgIO#A7&))&flP;<`3twM@E%99894|^p|0aH%^l;)j2*o51`|D=ey{UKf`MK zyNV#-jhdK9QvHe*;y?QK1vt}#Pca?VWbD1ehkeZ6+j5v)$WLML;Ah?UpuZ&T$>FaZ z@>g3N1OWXk@YKM`v+ITHG@oYnSTBrr3hvo|Vx@IJ%v#<+KfZ&NJS3Ac+z`qfU7FDMfXa zT_$0pk?kEahkS}PVl__fWWJG?yH@(igrzl6dll3!B(N`-yGT=e@p~Cw_5< z7d+}Vo`37Z&EzNLU=hhKx0GkCsV%r`PsHh?I8Xk+z6Ci7j8A7t&gL`-s+qcP;bDHYUp~tSd_!M&A${%es}Pp?Hbo_T z#2l%HY&0TP5HcMT0xd@`5 zh}-9u_zkDCjH8|OS$VRSsR(PJ_~Muo(UbcZNzK0Su`lxCX=%={Qt=|*V5n{G!`H9x zD4!VD)4oHo<5O<>67cgy7TV}6bNVB2x`2E<==}qOgsJ23-)ymkbkkbri(EjduO^r8 zN#)Cmw4J*oQv2px0>g*GClRGDSTSX}elL0YLMz-ZV(GX5i`aAuh! z-exevHZ^@ZlgOJqe7aUvx3F?{vq3LWaQtnx*%YASZY7UzegYw>guM6akvcWFDkmeE z%$~Xlt>k!#7z^a68ja{m7SPG_-#;1AX^pQ>2I|bSn zA2B!Alhuw9O)nlR^?bG1hZvS;{j-rLH(AP(&&0LAmHbwn&b{4X@~F&?zx_sG!PoSY zTqk$s-O5;<`1yn{syRGjS!v=G;Yo|Wt4~P0NR4%}*1E52?_xJ)zhydp)QX2)!%jk> z!{E8|dt>skyNew3Hn35;%`?vb$$rirY1e+zyXT{J?AWK0O4PCT@RyyVf_$GZ<~ruE z=+*VvQ25_mTfNz$=l@isnfv&V<49?<$J#78RoKW%Hb;-1`@xM|Q6g!+j3~(WH=WdD zbQs}mr}^AQX`jy%Is4)=7Z$;I{#)_}ji2fF=&|(M^<_r9$4<-Noa09qe{udx`ZgDq zLGx?^Ykkd(ioI?B#bS0k7<%@(^TjU-1~)Xsj&$DE($(agUpuZer2eVcQ9MzSv@yaZbL6bhZa^k zneGUFm|F$o93N}Sm&RAk-1phOa+FD;@a~(t4U-q$9M*Y?Bij15?@v5`GTxq?cU$o_ z$*Gpy)R3+orhLV1E%>dcU@2tJT7Ic~A3 zmwv0MOeyPB3~H?#_WpI$dQTb{-Y!RT)RG$1#7xB*|F{anw)VNt}OD<+47)Uk7!7s7dU_~GWn^6_tVER6d!v_|BZw}IE;#BYPf<(F$X?0h zqjz-CdUK&1Rp=`#o0@c&gsZ;e+};ms%@MWe*Z1X$c4wLvw<38?OBx-uFV;vswRwCi zn6B!ZqTA3|M97E9w0Gv>4Cs;LfK}HX2}jY59$B%Vdqj6#g0mo}{d$vY|2)g{6>-P= zRoqSdXHo1;SDHGzkJz{z9SUP|`YF8fJ*sKckzU41?zGY}ha3IN6n)l(qbj3xwk|h# zeI;1g+8IuP!b2EZzZ*(w9cxQl0R}dryk_%0{Jqn6YE+6&o*amXTtkM> zs*?1^f8Ju6%|!Uo(p)NHBy`Kwal#<(nxm`qC%Qm*)Mda zE;RVd{uu3%nQV=rzvTJtwFKd9SLh@Y9VRm;$F&IO@GXLYDZT6cdqGg}uPg}q17%-~ z?z>jIK==rTp-P}tx~r)#OYvFpov5AzI*)p~SdXm|M6Q+;XYe?aB}--qH#)8v zLjz6U&3Ve#=!AxymuA*)Y4|(>Bys~6K7Cqme3M=f$SA*^8t;3dOEF2DlF@yYq88Q1 zigJ`9mXCez`l@mI?VZ-{i>j*UA=$5y8%bbs$e*(=(pzP?GfhsssA z-(+UGIQsjI?x}#m^JyHyUAyw)m^phvN%#o*Ij&MQTQ&bz@iNW(6`l+LzuKcCe|)#Mw|Ya*J<$>Btw33x+N6QL zSNcrMJHph1w)oSMk_9WDTVz076H#oB64%!-bWr8sUv`SpaJfCl3az`32Lj(MH-@cx z;_)*~Ai1tzuP3kE>My&atX|)p0eKkwI5B;6JFAN!rqgkA-LSMtb(39~JmLH~^mYwx zql{>6V}o`mxYD>hRKBpAXs$xkGBD^LxyAj>&o6tA3m2wzmUDKfyuDMMmRgVM$*#WM z^LdAG<2Gb6Zb*EBE*G z@6~OUC3Fi-_+~FD_}}v46g={8qI)E}SW;rDoDgTA|DVtN9e5)u#Pi{oe^mqf^ncka zKBV`F|J9(%L%-Dg11;yeUwhKl*0j2?A>0LxqI}!TOOX9qr3=s7LFSl8V8@CN!yhqI z>|$A`KA6b7y?t8Y1I>`vxr5Hk^8Wk6oOG2ROHpiNsSh6ZfgO9~OP;v3lNj0#keQcL z9tR5Mo%etC=~i1L+p=NAV`ay$A88uO3skVQb#AGoJ~uqwy3F>DXh_P6J^*n+mw z2UO3UD>Wq_{lzEISS#Q+JJpbr8aN#NX4(6P-w?d@-aV$Jhi}d{lDj%;cYi8-I{B7p zA`kY*&Il{7)mU9SyLwu&u2qG4 zAbUACtaQ0&y=ot5el(^`997N8$tpj%>OYMJD zt8wMZ;uua7`!mL?A(GY3ulicV_B#Ao4HPa)QF)lFuRfZbU+R>zuWJZOD1U#uQM-r2 zo*K$uaaS?_$GsuX_kGtkdQhXq1-j^j_NhA$Fk|pdXNh~@2O)uvi>1bPI~KJs`Gjq_ zI5Mu5#%5~l=$vhT;RjE8lX0JSr86VrPOYD)CL}eF5zNzbE+R=a)7#_nte~l5dCY zk(#^rcH%TN)%T_fp5941*yul1#B$BwW@`y!?%MFXAZ~BhHNf7_FQz^(Plj`)Pa~ww z)z--=y&>#?$jN>tbZ`6nQ%zNZ)AmMPSy_+h)&jLH_1-<dqJNc-VIJUNRjJ+mUrn|Q8+S&E2 ztfM26{4@JszjpH2m1p`lhC;u#^Iw#YgNh61P38XFr8Dk&xBVpFFrkO3?G5{AVn*YR z({#1w-p`x5u?8^~cAZYt?IprP-kJS!<#i2X;~&3W8bez(S)&-eC)=tMUgRk*eX)_Q z37lw2&@eVz+@Z#BgoNJz?w-^72{G1{HqSIQLu>$PjE07nrzNg87TMSY)%gP2*x*`8 zm}bg;9ZOZ0GYsvTmJ`EFx4(FCYVa9Wr8sbTZEH~J_^}ktijWtt6K8$O#um4h0~beX zQMQPQGYOkN{3oiWrj{J*MWs&WL^-n%GW<#0Eo-$O|?{mr`Z(R_`BPt&Ep3etgp_6hT7 z2Cy;zQsz0%zX#N501u64_EcD3FnpN}e?Xc?H?*jI6w6Sft!*yOw+Sf~um1S?GgNU{ z6H#B zP%>m7H4EbE{P3RN``gYn$9M4aeNl<@jU_!FAxjkx{m(s@y?81v7`y*FL4*F+JsS(* zbxP>|4jUWW`jYzYf<*M*&U__ttJlXDB8xn z`j=45^H!IN%f%k=*irzGx%}*#d)Uza-h752d33B>YIF}%BMNlmrOdL+F|jB}@hAy3=tgT@tG0w05~#$bo78{mnDR;0BYT*1Hoa{@+>%7Ow;E z>)cLQ3_EJqQe)#ccJX@i_{z2$Vhr+J_AJ{Gfg#7TN%6*SAuabuNm|jV4pPh_1)ncN z-qcp8GvzkN{}@>wa=aHr)Jp6&!;WFxQ!NxA_u%&#GfHyEyIx9itL znS*n6jz^LYYRCf(Aul!Ap|cK5SYI42%@x~UxV{7SY;cozg)y1|(FEdR|7iKUtaVTx&WTDi3=U&Ww7`{_U)7sC@fGhZ&mPotvQDgVM zji}nUEeV6^5_HQDVYy6tAuVJgA<;r(`N-`zRu#03ty1{6;&YRQrw(>sNxMi$k9Ek= zF36V5|Gamvh3(s9BGfk(?Ar7DN4-VH%G=KmGU_tW?|$V6ZB3M(irJXgM6}Q*-iamb zV%MkNOtP`|73Yud^p;HGigLDuHb*xlZx`8(q~|9x?R`Hdn$P}#IXd-RMswO!hwpx# z+oo4wclvd6e{(f=Qb$v> z!X*{YX}?|UU%nRusOW|<{?SkA8cga&Z?;*+|3lSRu*DTD+Y;Q}9fG?{(4ZlBaCdhI z?!g^`JAvSn!QI_0z~Js~fx&r``@VZ_&L7xc@9yf7RjXFFYil-M2zTrB4uUEdx@8S? zN?#h?_q3YQ(;VAGdEF1!g1aY~$M@@ETU~HnNwc1at20s0q&ixPY2ARA24DFz!So;wxVl`dP8;fcuKlfn#K8C zIgQS~S6=$ji@M4LABSn0gh_8u63ykZ=ft|B5PbiK3S5K0@ao- zU%570fsUfGsL<4^Vglc2@rlD zd5Qfxk<*9NvMfGTZHT9Kh0AzQLhX=NL7#WHlCsi*Br*I)9DE@pUoat>0x_nI<%^iF1RPN zDx3jmjb18-uU5nb#M|4gXGntT!Ty6+-%4=3&)`FfzTm=1&*H{UAefo*gr{I97gTou z?9pFdKU-N?n50qlsG9Q8X}acA)VPFxbBt;73T$1=Yth4X_{giYM~FcOSaTdm&ztnN znqtF%nx(eN=3m$Px-*{vz2r?G0Cs96n}0dI+&NEyw-QU`?f|jhZzd0~7lb9a5&!yf z=$o6Qc~%1~5A92H?GzF8FIq|7U{^T}Q2ZE~OI2mWSG1RhUS1l|{n}x7vF!VzGAYQo zlM-cLlOSRzpfr6Q9XP@n^So`oz?i_j;(Slj?@HtNhkkTn^6NbgY$G_qGCzrOl}Pku(z(jn(?7XBCQ zUiM_hpD9r$B{u?+^M6um z{=F`0S6Wg|%1)>*Nn2G7hXm@nl#oa~5YRBTg5^dpBvc`~(H>M#Pj`{Yq*XU_K;Zdk zzGHkdyllB+Tqw*^Ac>5#ZIXw5!+CA5gkiukEdqjc1@Us`4|Z0jas^a5aq)^HPY-F+ z(VqAA+l*>tLOw`LJFfh{PF2#*Hs3z9m^pcRrwt-Yuk zeqfQ9-sSXib9II3M!=?>ubd^FQT*4bs6)rynRTf88oZ5q8%GPcnEseKa3eMs96pj| zUcv}XBx8pmB2Hwky|I>;_l`Nnj=f5hlCG&UbLH7rv@|eA8bC*02pSb0WXa}XZ^pm^ zos!>$WLm&#DIyP@tH$sO(n=%NI$siTN8>5{aV;9#)(KRIj^VHM)oC=I*;LD<)WD-3 z3UID#MTXTebFZFStoZ^TQbHW(IB#BCZj}se_`h6$o`Q6%gM;YuXiNt+f@!)ic~quv zEPD54O+u%tO-u?3>DJ${e@$6dCh8af^eu`hYm0(2D2?;{<+u+0jlVUn9;e4;8JJKD zANxc4i0olg0hU@BKSpW1qzG#}Ypz;w=P?af78@P*GK0lOh=ixKEY1IDpB^rHmsW&& z#qaRqKQbZ0GcMy-YHSNb-vrgcA@C9S)CXf44oAA0ebZHy8Zf;&@i#fEb@}8`)Q!)! zxXQUj9i63Xip=CZrDIQbM(S%QeH z;HF34a8guS!AsD@wp&Ku26rWP<5~f=X2$J>`kyx32BW)GxyelMgKN>_QPAFw1snqpW&0 z>z1%_E9c2ZCoS`OjPGBgLxbxqwQW~3P3XH7Qd?v&vK!BsG&ooY`HFK}| zfK^3ET5GJX!5473#c~GEXnX^E&&6im5dFAX9Z+2(br8bJBsKNhGH0!1&f^Mx^;bpQYl>of#|7%x-3|WzqvZ@!O%6E&IDOK$9-j; z5l3Ruv{Di~&K|4VSfJT?c3QdC=E}(m0juk{=Nq%3$yqfVoPU+y;``Bfq9Xf4*$5yz zNHJrRyXFjos?F9EMb-W)nyh?47^GX1$%<_=-nrOIAY_%t>Qz%wgkrJf`;Az>i4u{P zIFYKZZY%QzK7d@O*>2peRK(-;n8 z!(+x8XzFWkpg@2hz7rpTW|SnIabNFTX}X1&k0M<{8l)|0vJRkH(X6{C&L2>YzB#n8 zgym81I-aP2eyOCu*Fp%6JbQCPp;gMZnX@EUyd&M7r4ra;uDR zvI{T|jm9EilEIuf)surR#3`k7)#fj({byPibGIG~Neg%8$H{3%6sXpTlL)fLI#n!~>hYt-`@AwJGP_ko)_B<31;(Nrq zCu)3qeZ$O_NG(XQC?hyzlD?g;f9_XX!`+6YYa8C1pf0aiK9rU4?tV5DJFC3UjhNU5 zi#Cz;FnZ43l{;^p=*U;h6s(c3gcswG(I{{=YSuyQF$dQH7xst zYs20sP??vUYouVb+4@ds0iLW`onBGk$n0rQXV+c7yrRSD0) zB;P*fb7FF!j8{%dw8Y7@?K+ERaU;wNyVamj10ETQtkoX9DiUbJ+pQM zt^Dk~bvacOLq9(j8n#?gS@O(4<-HWQ0`%5VOpaw?$qc=nDXAEoveSt*18W*}o5r}G zez2#a_i(9t^sFqoa8z5`cX{_i;@{ZI{Fe!sn(4T{6N`wwe`dCns&gpV_I1j@-pkvL z0869y1B9K*fQVO-lG6WVHPs0J-q*Hf%hbFlmcV<`o6gI#rJ}ff8C(=&h~{QSea?~K z|0_m}I&I zeiQBEE=O_Vb_r8nZ-2_dEuE#K7#9eft1E*v@lD9>AQY>$c5A=?`n@=5$nTjY75hYW z*Wi@JRBQ4ZMx}TE2&Aytes~|_lihAu^-!){1tN$g%=n3hhYIJ^fLe2#khBo zVUrTHJ2t4B#c^kY?I~3&mkg%eF1T!%46cW}jArqrXGnhP{`&!(BIfTr^ppmTwFMFhMqx&-b09ZSx5={P^#AH7anyi za&43Twz6@;Z__1>r^X!SHBTpM|90e>?sCHYegE-!vR(ed`NKqTD6CGak$*sE=&nW6 z+2fP^DJjGr9r_lnQ~t{y(f&?0_0{~$iWK)pBBcYc*POcL+C5)$7eFKD^Ys;Y=O=0G z40)clz3_cxz86G252A~FbMw0@40g-UE=6xYy}Evvogr>{U`+2CjfqMOD~)vh zrljNQ0%&g3ZP~nuV)w_9xUS89Yrm$QrzMmwk}h@woCu|==lyHL1(1mYleIL4CDh1J&u8s76dA+WxyC#CVUm*MaAW8odV zr1Yr`ic`-k95|Q&tsM&`WJ0+4pU&mc!+40bg9vbO9Rc%=mt3XIPlEd`ogLSOLL2q| zDXcEQJx`y(u7227hHUk=-COp0x+hKqY#|{M@lzLriQM^W_`C4#2y9sTk=cA6>q>;hk+7!2mpW9KYXBQrL5=B9s zK+b?p*pSB=Qrcoa&?u!0P)?0z2ug#ZCu#?_wQy_9GGQ|E95UZ2ElvO;b^cM(;w8Rw@W9r= zHFN=h05ki%GSpSb-_0>g=vZ5M)|=04GEB zwjp}oS@4r-qN7~(b0Svih8!=S#uV5+^zZrUfJ^>{tS|Gj$Wwu-FDu3zpG1v{7q*5k zCm{8_+Js1h9tr4g_-dMCa#HvG_fv@)Se0!RwH+@ zpUdT|j+L2(MK>wl0y*!^tuNE0zdQtzDVo8&e6mnZXSVYq*WnenH>Fplev(KSx*Ex$CYvQS#dq6 z;IeAblfhMAc1~ivQggL&Qg-X`&#*7}lHJ?v7}eE|kZg_-nH?XWsU*E^{EY->A%J%; zyN!un*dC4H`w-28LLQHk()B)Nt#3pSrbPhlOR;x=sQbj)-huKg zPikGnebCB83uGt~D&_~zS#9kzF}V1PX6Tg7d^XE_`=^Z zG18T+8alA5_&P94$>~k&7W;34{N99S(7~tPR-zQ5-M-+-oQJ&3r*o%C7O4U=NXYYy z#W9eE_4(FKAKI{iA-WQ87ro6%LOiY=(8=6r8pe;ru@0||2t|t;&2Lv%LDxJk-T)4m z(n~LB9<h5YMr3cCK9>W0XtI}94=i1?LO%c<`urkSfapKU)EopaR| zlj$`a8@EgUS&DMzm(I=Z=am6ou(PzmmWB;U9VR)QiCnyjcjs#FO~{Me2=J1%+rZS7 zgudi=cp?EfDZz0hcly_A`55}mS41*HHd>d#bM<9x{}m$E@Q7T$p$}sU6hlqc!sj9|e08pjmg0=4pvbkbqvh{)^qyf@V~iGJg1%!cJ<`_`b?9HhW+*kA%u1pE!=;9J zR*}C}im~vT!rU0#043ej_bmU`7BqmH&P**YZ-y_P1n0~kv7CQAXXId!J((@=<(`?X z$-W$Upi1stjti!~3=90YrEJE>phDfxBQ*E8<<)d1j`BT}w%n6adSH2|yAsZms0|qz zn%bW~K%8q39{!mkqICUl^-;Ie9qz&0U<~ZVwfKBwO6dRCi}(U$B4e#V_{q$15gr5; z+ibPo&i*WmsG&#~VtP0oxn#Ae*S$jWim3}Sb|$=1z7Y)MgW&P45@beGjK(&IN6{fdb!jorcb^<1bL z{EGatE^%${MV~~!Gd8o4@Qd{Ef+QAW-J~@c(st#fw;zu{U_IOK2U)R2W&5k&XBcSk zXt~$F!r#qW*1a)C`_NpHy^+7fQZK{Zd}Q^|y8bPd7nB;rEv4(Iv~Wdw^!dLG-te!% zYh8h(dTY?yQ6(cp`8=P)s`&Ysn2_xfhAeo_XkM&8Yu_T7&2}Rm*dJ}1&THt6>4*O~ z3O91f<3jCTiPCJ^r_L%`U=?$^SWjCx#_9vP&ssMuEnyA#5#JXxS1z&u*l)+P(|@>~ ztA$1`vvJq$Z45!Y@M5>E8O+yaW?LPzgK0*xM` zoi2FEZQU*l9o=qz+f=WzVpPol2=AyP(*)MgnBN;3gL?Kl=RFr+zxD#=6>(I{yN2FA zKhTQySvwDzrfuI=Gy)NDm~9B|g3r7w$o>-nIE<0LK8-yF(egVpg*$9kLHLzZ3OUWy zv7zqy&h~Z9Y&%fl&;!XtG`(vZT5H*~DUUQkjxa2e9~BE*eY0R3nXCv1jIx94u&J>$ zZl740h4kp;<$syqI6m{p7x*r!0smvZP*{if&}(nC>#UY)B?h0MR=RIE%!l*$l^6mt zL){6!pXqBJrtgbQz3WaRKdhX5*t2%G4Tt_!d7gu@()I*tl2z2M&RDD5J`e*|^?5(` zw)zJB_tzbvMJ{XC*E9;Ah2$V#uT#-(wmt6;V~6Z4%>=Nkga45M=@V0Tq*% zPmxb5|GGGfR8}J?-SV;fZ1A_TUU2@Y|E~|RLL@z+?U*9`9R8K$ojr_l-)2wMRPOVh zSaE2>n1)$OY6~`0!gt8(9%t01yaw^FO;wEVCOsE`@4Cy^Q1*t8>XhJS4aWq9yZm34 zW3oyE%xr#@npVE2pW^ZVLevy>G$Nt3{qyDI^;Q_lOI9gwDWm7l4!^yZg)a)X*t$K0 zwQ?gPlY(sQx;KedmG7CswG7xBw&A|eZ25A(wPFXfcX5&@5t5^4b)q-PO>->${C_iy zFqx2Pm*CU2_<6LIZX6El=~lN=qepvpcl{xQp6t+uq@QlFLhx74T5)-NHyDj*MvYJb zHC8)i*;&Ae#J*Dd$6)F+Cr-MzTC=|TDkvU4@Q|`Sc{l-No_sYeW3lXBrcLL1XoXLS zJYpdsr}3>m_hR(zU2H7xo*w19&tJ0RM>y4{nkUmv4H=~em5iJb+cVW>zZxV#5jquh zJKLb-3+oKrUA5-xRCOn#acNqMmMX|p-y}V2p!wQsW|?w2kyEG zhI+svI1a}0rVsQ72fKGoi>!;qQE;B_{r)mQ%I&^WrC1&CH1o)BNFwNM`8#b{@IxI% z-N#ttoGOOl;itjZ;hrw^sqt37;Y7}-w1p>H0HEfbgvV!n0!6`{zz?%|^*zmFrR8AM z2_bK9p5rw4DE>}TrT*{=I$Vy)aSVP%JucVf$CZrG%9bEvs9S;+zJR-f_a&)o0}d7+ z%&6&za;O~6)^a7`joz_aXf?1LIB&PxF`$>}f6~Ta@cd%&k3B+a@?~)~5--&2o2I{8 zpd82oNHy2dfs+OZZAdJ-%ue2iumWCk7-Lp#FLj;nGT!UpT#|Uy!YHe>+mu8|O7YCGMiX)L2Qb z6VdE&^UdSGNHKO^D)&JfpZ!DDMBv&)ji+TB2)Dws&}&h$+u4+I9fD{out+ z2XF(|Jjc0fXopiP;LGqVmMWPYI}Jc+FbepOIH!0Ql{nT(z)poDH||RJdx&M{{k3dZ zq!YG-RTetmp$R%#J4h4t<@Fv zJl#I&0;#-D7ErI~0GLx)#BTi@GNwy(fa}8_ds7j3#E>xHgCx>4D3O zS~zUAx%U$_8;EXx$1w}7{prBa6Yv0Wg@p92(f>HUmvG2M|KIJ9Ow2d(K95R&`5x+? z?&cBNKs$Gh+H}!iP5nGx(2i89MY?)j3w%sRrsa%7^0hB>K4yIy(0s5% z&Z^q>)@8AJ(JBV}UXv`=9*78wJQXYby(r8ePk5wUfL^c z2Z6^;jg^LP+XW)qds(|BA`Au0?`KrM@B}!i^whIwj5L>4+;#BZ`1c)f?r)SbeBo*B znLCDwliTY^rmh)At{QWH7ev6)vzkNOX-*tv)ABIOR#yIY$t6uJE$yw=X`>_8`O9_I z&2isHP)uNpUwh~J@yxBZmg{aowqojwI6Bifsi!a3$b9sDgchd|WIODh-JysydkN&M zw{i#@Qj2WqA;1PQfx*8*zny;v$v42No_diEn+S>NR^!BhXMN#VGd0}_VYh;GidSbT zDCkHh`u>kFxBEgE%e~G06MwqkQHIuZ^sct%6|&4~CDj~%{aSm5`lRwLc&C#9?Lvsz za{f*H%2I#l4(-ji{w6}X6o z%x{MQ=rS5Lr`*kZM8i`56O{WWv`<6L?mNP3#txgVc9rIo`uD*|swEu9!R50mpSRkw zdVF&WT*C`Ixs9IT9)fUle19Od;j1*Bb^y@n_ZgJFh)UZnQ^_D5I+|juCSp~b>WPBM zpyo*rEC4oEcgK67UjBY78x`q9$(^w^^PhX-p8&Zhod?NzjfgAVYl+!omWs{iRM;Sc z`24>ikVx`yFRarBV~$=lC%O7Ktun7?;lKjjyQAXqrIJXy$kml@O?Rf+H=L}}kCYWt zf2{xsk;8K^Ap#*r^P87sHk0EpSiUjL4K1tzb;y#e(9`gMDz(@GiAcy&P zqk%*uyo2)D(*;6vV2#x<61ycKTB}L3e&fpTF|gBpTZQi{HBm-%z3i@)LTf6g{Ghoz zsHgSXi1~LGRIIGKVqhzSRd0x%9pBm^44<{iX8(#n0;4apSUYjb?!Ad14scID)m@?K zS%&8|nx7vsz1`;kfl(q+*wQ3=ID`tqbY3qQAA5Z#o58=*lP1GNuye2CyYtGXC0hww=3`oJII*8dzjoCXJ z*10yi6&8$+2@%qlPjl`C>aAk`%LOPY|J0s-mqrbf&96@MHd(lnwZW?saty3|)=T0M%NR%}=q80R|AhGIz`u2b zSm!aDJW+Rn-aB;kJjic2{xwsgCqjsXFRS+EOwmjkIl3=zEDgzc@$L$f=Ak7qusnl zL`~bQqa@Lwz&_+_(XevuwH^(CN6=3d1U8 z#zeg`Gi>iRM?Am_bO4$_#VPt%35*uZfsg$530!ddM}BU!pbwm0`*gvAExiwY+6gS7 zXnId~rpP|BW=Wm9p&FyCUm)&k77~PsMr@CuZYl=-{s>JuXD^Eu@<3&wYT3`Y!nlAn zMRdJmH%cr14#xR4ZK5!%aGuOJ?>|{V@Fdo!j-7^Epl5xTS#DkE|L4vAdXrBH9fU-Z)h{y5HLPe2}Gp%!AI zTS|c*1TDsqDBmCMpnQSG7wfU(6fA6U zC;w%XCh9Vgh@;f8NShMj0WkUkrILodIxRukH}tJNhu1%k3FpktGs{y}P$j{R_^>)A zrfQ<>zv@F(U0d3BX9I>W@WX)O7Fob@z-N9;>kmJh?iuit z8O5{1cUAB5!678#OgTVyXB*onV10KBh=iv&oA2JH152jlT%t=z!hIX-`JVW>+HXxK z@UeM`%%?`qzdxoVFW?b)OLPH+8rKw!?5W<~7$0ON<|#_Te>PV|Ea7WE6wY0zCslh@ zxz7ML5t177Q=5v9mpMx)DRr04lT{1wG=fN)wIQj!N>RQbDvXSeKkHCpX1MO9YFUow ziI5B?P+$a$L-lTP*e}(>jgF2QLq?;MoEO(qlGVJ%;;#;X5vja<;=?66vPTQXWrGmI z{VD=EY=f!#uDwXrkuj)ID+n@kJ&{s9Ir?QnW4#?VC482}v}2gx^%@vi;?;T8(>C-5 zY+eMJ)&yzlw2T=gl|2|XZu^2x$m)wHU!9|f>e!Q`r|o-QtL@D8$)M=_W%DnmjJ~mg z^q|=`kz?y%eFMOQjQj~1kCV9ymn+S?Pl}r{o){FZCVzmr8w5<*>_i(Sh`0PJeB*xD z#u0Y?NE978Cf;3X#-JAXWH{ow=+Mw5%KH@|u5Vi1B=qXg?Y{^V(Kd>any1Zcdp;jy zoUiyix*ZmHUYdw0H1pO*?gj&qu_50l; zqz=cn4Q-44`)REs+Y(@lB_fBWj;bCEk&y2ap&c=#o^C?&LxT#qx$MK}L*rj{mXfFN z-oWbu&+Z2yB8)*Z|bZ9 zpR%;a2W`v}fxSMbi$uw&25X0S!B;zjXd5wV?hY%JL;(g^jRVbvqt^3}Rs9pYx#Vl^ zO@+4Eg-OFWX`=y6cEf;+ghlWamt_o5)+l?E!0snW;Z5+-%`?l6vBuAH_N?r;yr&2e zBH;3kgS1I94EdA+X`v)>;aydXtHe+TlQ7zQJFo^G{7uOz+Jax0{F9Jlf=|_$@kK=O zL!#b7THLtUP6P=U@Y2cli@m(WC)=6Xa5i3y$`3eoF-i*FAAUI@A>4`kb`v4y<2h}n zVjrevS2m<%bb~khJt2?7QlclY$)QsXYg;q>h60Yall^-=xd~|n50C{fTv*;U!+SI( zneNr<1YyUaE1 zq=z#&*q}eFkjF&|DiQryF%gA3eQs?p7)x`0ZwYVsoUmqaTPgMK5CuG4Zr$V|!F#s@ z0Mv3Msvd+?2A}?-;rF zrvwudAC53g7IT(x^~DBzkO~MDE$@%yYI$3Q$y1>=SHMZ)^de*1NmJLR%T?gMw^A2{ z=j!X`W_T^CqCwsd<$w~V$2+mip_}B@_9S)?`J&M50rglijIEg}reX3omUE8I zs@Dc1;xyC)-{|u)xpM)`%fkf=AIn#A2k{jLAzNQzxc!KyH}52iKII6@T*0rNYs*4G zBbg!L;{p^@B|zSd<)6VPM+J7hAyNI4jB&LA?wwA(m}3>>j@EK-iGWylig|hz)1=l7 zpXTd?Z>OAM%W4qCFu!2HShz)kw-B*NS)e*3(=!6E|6DXx3&~|rgXt^9FB>|O5o*+W zzJQ5?Jo7|+l^H!+dT`Wf=jul9xTe+-5KbUL!p(hSnxhAjXXhn`dESc;`m79k|&o=>F{VSAe4rR&!M$dY;2Gt z;^rNVkT#$>sJJH>H^|MNpwWY6nhcBw`rk_n;>39H_&K)?g_?v&&Zcpd42x*IF~X!> zbx%)Uy?vtbh!Y_cyzfmMm)l8hr_;tMzfa>hMIqY3V$p(`s`As(2?SQIHG$}d;%Z~|#w{Aa?DG%#d3tDrkCfcpZVqyCBS*hV1G7tzJlEU_p3j^D zLw-m`^uiJ7L!}#d5S3MM%REPg&h}okU2Me_l^QlPlw{rt!Y2R>4G3TahU~3$@3%aN zFQRQ#$)yU~kf!7B*AxZfZXodK;G`4qLu2sGyUpKAbp-6u1JD%8aLSS13!~dV_c`srCzH z`Ch{3;UexA5TV}bTOD9K8s5yr1ukrOo0ZH_@ZBtw zUe1ThX~2%`Y4myX8nd@aUf?yE*V^l{r=cm!(~oEInaUCKhm7O?&Mwqz|{5|J|IEobf@-1$&iEOT2g-ITj>j08wm9p12QtJ(oA%-sM@)uk$(Kvz&P-67F`T$ zlqeu;Y`RFIIdgzSb|v5}NH-mk@$>q5bFElLWT;qry_x5(PxR#N1H3<`dyRPshgl`e z)3%DPJ?ivr01e=%1tLfwZiSS?XTMmT?s2i6JM*E4;`K7(vg5)Z!*VhQ2@MUc+GR)P z{wDto^&me%>^5JiFFY>VS7OI$f`Z56SC5xN_z3aXsIW^AHZyNCV=<{UOiu2eG8oJa z9lLhczQN}NiasM!-YD=`n|fYcn1DP2wIDM0a%V!4fdQp8k=Jo=iVJFVsLdfws9_Qb zqz2*G6tVCt+jynrRO8skIK{tc z(Mx=mV&GS|J!1}zJ4d9ekG#BX0Y7|Ux{xj7pXK+0n>Hq=$oY&K5YE(bF{2BK!jmG~ z^lP-4OyhbV#lH@nMb9Bi<{>G)B9N@>%%@cY;{xxi6aAb%?uWMRTy@}7U~5cSS9A)e zh-{_fAuM#AeyMLTbU#h_?z*C*jZu~5-NyeOJb&Ek@CLfwObA(eA84d!#tx%lJ?HKlOt32-#MLO`R z8;0=9A0s2u4?ds{YRiTG9o@CXnp&3!*FY{_vtP^*$v@(-Pc+aE4-fvM{DfNzJinBe z<|yK@B%oex>@1~}=Y|>`i~(|>dW)e0u|PM5Ow{Pr)z6jn>YUl0eM38}a{A!Vkl#O1 zAtlxJVV^qAO1Cgnv&r4*07~*mq9Y+^UgpTeybni`qgqdPd_mR)Th{6&4EpsTE6`5B3i9}1))$gfmr0l$#%x!KzD}&5=d4^_C$V&FP!998c;BN1vYH`L zj(hL5|1F6l@5J!DKE~S5tbGp2a3|=&qO8Ksn1_r;URK;J4umkvfIF8PI}&j27}3<& zsMsqTEg-7m(l+xf$nA@r+Zt9W-!p4iyXYI-g z(>8q>F5+7$1T@>oH{YQ&l(j~`KW30;_ayU1JUK1LU~WkaQxdPbFIRt5Nfl`fX$_(S z2zT>2%IP}6+$E9ExMN%27PzGK>|*W*s1~Z(S6bDJYN9I@Rx)h3-DB7w3hLYec{>yx zu1;T{2+V8mWEsPWd_ZP-)5G+3BzHYEX^87o0Ih#}g0U|7P@L8o+6}fwJ!+dl2BfZ!Ik}%}#FL#rbKg{;5eD+%N zJ}^+{blHYWhrT41Hkr>V0afYZ#TBRR3O}9JZI2nfUY9y{f!BSvgf<=~sXivRLY?(; z4aJclxVyVM-_C3G$+LIB#>K_OPqQ;JLbL{46uLasq^03diFpv?=_ud8U2khGR@b{D z9hSnA;@XY&MD5SlW4W~^H~SOSb_;9*kmV#W<=;#SVK{c&Y6_gy&!Vq^7rI{Y0-jbj zKGeOB=iAyGOr(+|)e|P4npr>B54@S+hfIjP@7Z(hX>~s#6=j{xv|1NlK1Y*0)pq{-dj@5=?-**Zgx zRl)Z{#CzUcjg6#O@zA;*x6rD<{bh~;$D=XNQ~Wb~ywA5nnikB)*(!TIf=NkzXT4^7 z{LjmuB|~!0Tx&84tqW^ZVX58&ya72ME;)Hkwf0&q%F;Ng@TBn?O%Vt2MowD#hq(_{ z?JtR%&k4EA$mcnZSLrYkOtWl1(W(!?$&_kP@vMM|TYfk$XT=mUL(kdG&IRvWb{WdMBY!ch)D7DSbjmk$FN zW0mg@3fK%r*c>3p#UaEfj*UgV^y!GN9%MW)8;TFh$|AN}_EuHJdfvha45D~Bl)W4z zaq4yoxFI>{%zrtx3QF?8RqMc8Q_$Gd4!tc-H$1k-j|hS5NOi7;c931ql~WdbspsCj z8WMZ0>08>|9CjHZI~b3&KI)o8w6ibmx*uTA`B-44D2olU%5|ST$||8zn2naA_JK(F z@qE$EO8->q9~z&^y0+i~A+8Bc+7=i_IT+(ZrHp4cnTsOXtxi`-1#buO@wYGFfVRo? zW#cA?5P5)2gMpCl8AE|;P~oNhl7RDj4(X_2;&js!E*m{OqWHsoK?O8Uy&3>b6AK1G zuW2*-O5@sUV{nmi>hC_n82MP0q72LN?-fjNz+`mh@ThI_o8FzSZypMD4&@LSR+~uT zBi1^Nk|GIQ12CH}+K06_nZgM%4Hqh$%T6%lRdTgG>x6BecYuDRG)B76_9FI8s-_={ADX-*xCX^I zCn^}Z9F_$xgDi>(H^qZWY-9uggSt|HBr-^^{PE8Z9|)tvnFaRpfh8W-V&HJ&dT(rN zQs^R-J>V)PsJFBuwD&%CYVFw~xyGk_QRDkb>H{$k4~BDLbtAd|A62iH+l7^J=!=b3 zcV<$Ez{ezb<9VN-Y%Aj1nVr#77Eajn9(&-CpA}*FjZ%*NO}HuI@K&Fveipw{qa)tj7ao2(5ftx7gc* za)yv6P%Qz0!f%@BvO83mW1BQ#Xsd1G1=x3uAa?(*AU_ESopdvQ?$5Eh$lIS%F+o!D zz-N~s_#FQrBxzCfw1T%+NKPlvx!U4JpW`+uI600~kB9S!Fbna#{zGpJku|`>KF2>b-y%q`Z*T1l-0aQ)CRj`9G9n`9j z)B1OB8UUG-YY=urbMu!WH$Za0Iah|f+yjwkm1aZ6rh5gh4n8+_>-S17`d&Q?x!`Tw zf%+1;;31b5ebIwpvKXCc6KCK_Y!Qb{PnI259`*r4Julk82V}Q&@fy8ixg;iK%{JBq zddJENHqI)k-d{iaeYnaQ87@^r7n&7TChW%`XucxfeH<^5ardYzP63oO+feH~h)YZ0 zbl%zOCZP(A;?bqXLCbA-Z9w~fV0|ZILSxdmf{rLD9?Tl%NiIOYIoz(J(f($-yHoYY zZBg4;d65bosNQW-Sp*Iq;0G8u>-Qj(DR@~AfyJN=-s7|_W!tEC0lO&DfTst^i8?XUa!kk4L1ip`a!RI&t#l|~1W zRo5}rNpYCyz$w!Dy4%%kBC8KxQ6Vdj?@+wmM{s8Z!MqoM3JIYSxDSRw6!4n%X?X{tAlljY zVu)4URmu^->U^B9`p{T_QH{<3Qf+?ksjn3{BTXQ9%JDl6X$2Yi>uyX4s=y8vevIH4 zLZXmkFF4N0ua79P+fJ>KUB7uc(?1xz)x|j)2d3gC#T7S+8?Q$D;pBqUmk{`D)_gJa z)~-EkkwT=x8VdN@Qs8lWZ2Z$ZSri-8@vK6^FpykL&X6a3=VMHg@eWEXliz7%333k- zQ8xwAt2GU(yNl?POF)ZdWzFRzdGvAHptDKLnKHdcKP3D(5K}mob0{yeO+NdI5}jRG zNz2a-CzPg@6DfNVVgddMfj+m=EBym=u2Bb}eh#Bq^VKuH+}Ft23AIAfF-fX85=Sp# zUg}gMYAmy)$X-D(WBD~F#w6NMX9}93Q;Bx)?3~uOFm_oe6k2g4gvDZf0^j-8R@8x7 zVFd_D)bPYr8eKX)S+2`W*)F>V$a*%=1Bbh&^IOG1&w7MLt5`h)NY4^0UZgFvm1kSX zNaE7nQ;Qn@uyqY%0FD+H*IimltMO~uuy1*PR>OZG>P?SyB6@_+6^;0Z1X>sq(O$2^ z=XSUeq;1ASAWbGiqN;8-hbv8qJm2_bmiQ!01BZ^~IZfqE#!@0R1<-roHV@F<8|>id zubYbJb!t1~Yu-UWz>JbadLPQLtom~s*9jL$m?~}3FX&k~l2K7vnLEt*N>LP5B%2Tp z@!)~^k>Rfp-{%gCBSWZ)T9i0ov(EtH=&GCiFcww2-9h+k$W;Y*+lp{Pfd)IsyWmig zDq$NRN9H>xQ!Mq<>(fPqT~!Atis!vhJ<0%q3|Mtl1QKp03%cDG3=R+_$1!Fhal)xP z7!KF-Oc732$fe+27^%#>s=CIjNh6|JsKqN8beN{Bv^gMBblNLanbUY<$rS)8`|B1hIKGrR#Z1?*OY}C z9AbvGM63TTzqG3lg1uG#J}~t86Sc0#9(osBKoH*V>IurfH8ERUp7n^R3+Vm#VuU^b z^^u%oD=3=(aSMW?C1y==^a}&m+pj9`{wwxp%vI!f!hY_JpdeAo2?qwK6hoj5Fv7x? zJ{+vo7{r;>JjCcmG(JbSpT zAt=DQCJ)B7995+ZDo(olKpQDf;?`;XcNpmym1w=LP$A^7lfuMu<=Vg$6(RTcSK@P5 zHLe~Uk=yG&Td;y4NP%LF{3P}doi30imH^lk`(!Fruug_8isA@jdON z<}xy8qV!lAN2(Sqn?fB($|PRGEK`=pnNqt;g7QGURGpM745?9{Lr7t1(R#5QY*Xy1 z8Gt_xBXc==Fra8eMHHNkG8k|o!O{DhZY&gW>;8)cz*W@#($LTl3zGS4HHKMaX!IT9 z@lz3c-ck=GJ?zRTedk()Zqr)Zmk@H%F~)>wa0b>ltbPXAYm(0=KQpkt_KUN^ZY1^K zkZfZ?4G1Epv&9AxQ5nQcug+Y%J$eW*yMa%NbQzi~TwK9R_d(gg(tvZOel5P{c#u(Gv@bvO7@BYlhfjqx{wK~D}C z8rmk-S22Tj*E7wcaec`r!xP!b0yZOL@-ymwXBB&b3g10+g0BWLh^v*PzzL+WxZiA( z1^H9_J9eU^;NQ|Kqgw-u{#1+F8Ge{!U;2f+a>#eL6z*C;JP7?k1H{G)2ySj;Dnj-^HO<90&IS`ZUGl^ zKqg5SSmXp2*PQMjv&qajOsXbozl)9bi5ej+k%3}8FN>vWg^1I@L;{4TP-{-@J{*z+ z%4oyCk|j!432?cDIel5s4D{zH0E3`WDqX{9#8s?{2!Fp_Hpj>hQbysp7ldwdpI z3PK+D>cqupY-T>P@~fq9>7lQH$>O&1a)@Nx2Qnhf9axLxBoJu!guahCs1~BgVY#>> z9HOR5vmr_+^GZE9P>is)(at-YSXYBbW0Y(P{<0Zd^Hg;vmYkre%<6#_{lB;$TU%hF zufYF)lCbAulcnbeG6DEn@j_ZzyPEqXpRJ-F<lRPvfU_ssIIx=nsOb`0 z^}12J!`q`!T*ont)s|^P>Lng9f5!>2+?hHe*oDiZ^{r(m1-TPA>cc=bgdeJ;07k}V zISC}Av-sSa^ue#~XZ5DzMf_m@Ai{fmWg-_j-g~&IRXPE=grvI2-MM7j6E8J8JfLO$a~6`78Lt2JvAtsb5pmaxy()Ag0B$m0+iw1n_Z z05D1@s`6juk`?UWHc}x^)yH%&*2S(q_SF>)6BZ?!K$hEM`XNZ;58-FjowGC8Zw1b> z`G?XyGBXU|F+26`!GSnXUtvpP)6Jrhb<%gD4e>%S&aY#3u%R$z>juJ27_fMh@mBnC z0}YgK?G;|<;3>x#xnj93n%W3Rb|?=WTAruL(Y&w>`eim5uA{%>6nXC6nxKuk|HOb* zm!{lVeQqN$*+H|3hR!?VD8T)MB!AerW)FWOsKt;&)DH%i-Vyg9yKJ9dYp)7Eq1iRQ zp-En@WqIuDhF=zHNcc%>O4S;I4po+O@IEx&R@!W$9U$|^r%P}UXU z`Oln!e?9@*p4=!FKC#%N!WEbITfrqQ_sR=VOcB=KLNZ^0Fn-5Sd-wZr6rpXr3HTQx zpEt5GSyhctmL808PGYfmKQo6wixC6q zuLe9G6DL{uo9`$!ts$!^xblc0S zieEA%zE(2RxgYpuOu7fI~in(xRD zS$}~>%zA$r(>HH|G+k{>P_{+}V=u#GY$YdHJRXSs6*CTTGX7%pv)Ji??J-eeXg_^V z0^dw?*pPmAy$*aQ-(e{ahLt$MXH%V#S~ zB;oAUf21_mK4lQHI2`x{xoMr*Aa6|S*i^w#B}cPM;kx@)x_G{&r$|46_HE=Kq2|+* z&`I0I*E9%^-i;9igbi{|%r&IOevJezT5@M@+CQpx47zCswF9+Ga(LO0iz-F2=FTGu z^TtU?l9Z-Vv@%AztA?e@r0r6LB2Firzl_-uBdO_Te==6}oNzQ*(T=Jt=4Q-tF1#(a z`;0hxV%<}@-KH{nfT}&Z0}Ducw>{NZaFiheeoH8IdyNw+Uv>c?CQtpBWJ@tf&q#q; zO)GbWG;a1$tqQo##W^PQA7K4xzELy;RKd1E@;Zv!iiy!F(&Q<0Xj>S&Yw{uZO{@ep z0U<`I8d*k{L&x(jjQ&p2{vjoKh#80O4h{ugi{G*~FW3W93!i0XT~||;^NA02YKwSe z_wZ1`Or1)8k1eo}8KkO`qD{I_Q!|ApvAwp zFu-R6B^8KPL|_06rHp@=%$qM%pMWEh3d50$840nncBv~@VazA@{gGAX3YY6av(Ubk zwj|)2G8L3jv=l@0FXfaHOP(BDV$)Pt()0azYx2A@2%%kh95$_pn0-{jq~ba=uXT9% z9#q(rbK$^}NJIOt%Ft8Jq^M-XQx7nt0ZvjYjo$#;==_{{g#M4g(GkPX{$f{eY^?rs z57HD-&-g%A=WW7d-OwHcx~EsKt_R@KO_v4I5du;`QnZ8@2_O&tg0z?90K&OX;kZ5=t`=;c*CpOR;E(YH6(yW?b+qI*dkV7kq>zS=4y;x9j+R% zRaBC2lXdH-H_hAC4m6Yygo$)uSoqcCwHGO)F#gP~XsT3I#-0!tA8K@|0LfCBx)cj) z#YOa1gy4Q$YZ8h$y@>5LAqb%9ogJ$>&5pxQ0FeZp2AHQgePZ(<{{#`$1>hNT z8uYtoK(-ja#hcMl7P7AGM>olzGjP=L{ViuJAmz1phap-Yb455MU0f8m*NW6t6AYZO z^IQSi&UfLnbiCwtZ;u9e`8m8CSK@hmiRQ*ulErCO;cP?{^>DTZ0*+$|ZY7h4LU>+{ z&3fu6UGS$|4EcsUx{o25YtP+o`se3ey8bp%T*Krfk*FGL?bh8O#M1D zjky*eS{=J`E#O2oAND^uanKg4f4><7nz%oJa5NY#<^;nb0u(QYu-^J!Wtb;e#&;hhy%%))xRdRe zj?A{J)oRXgWQ--?oo0JN1$$PuBW=`dR-!HHYKM5!DNq=nGmf_vd$3pgrpA?{YKD#deKmwWW5* z=1*Q*u4%OKQ^vCw0pwuBb8*i}twX6S#Qr``3+4TPmN0F&7bk{5MtdWUiT8{+>M{NR zr|bHlW?_Fy<9D4Y`X2{R>QRZ}-`Jv+S7A@;%!My4SzRqI{9N|eZ3RL?V*@VE+A2D^ zqSR~%(O92&j<^b%U%K6ZcC_2*8c8>={oOP=54ctfr;_zt+>NMYx1(BV!7dIpZmRQ; zfbn!Gdy0v?!P_a#Z(O7IkraJk2$;JNOkz>yfrg92E;VWq>HNfd3PT+WQx81xO+C~i z=FG)=mn{xZVQlo{)@+@N%{_j7%4TB=tfW+lv`L%e&hClsKuy=|H@HX6TLrhO@_WI#=t}>mig^g5T{kcb7bp zI(lTqNl!|M18LzcFS58Ac*!ONp1FvD4OVLI34le%u}ma?BgnTJ&Gvj?155X4a6QZW zMOki9sA=bw=QT>PJ=gC2qI5n!Xhypa6iEDhk=un6b7`s?AfmQ=ljDusPT6y ziHTH|gn1ShRkd>1-uox-h=0jx$?8;T>LPKyxaY#Wkm>VmI&plIg%TMwM#Zx$W3m!D zw|d@bmm{gI+0IWqVI{emnzvp*Iw}G!H!xwmYwcz%hAT`6LqgkS6N640LRp&71dQWiK6Cz zIuR5$rWdvcm#zzf-+E06$~$8t9~ywuy_l|L0G27Msyla9z;g+nS03+k5rMz72vUG8 z@f1%l$eO1IEYF)#l+RcFU>ji;t422C*OH8>e4}u-6pXi|x{}s8O65-pPo(V4pxaC4 z`PvU;j_;fG3FF*B0F(J`vMHJKee!~Q@UHJd+C0*Y)FQvT2KD=nGS8-t(aH&1sF zj;U_|A_2O@NEl4L+?EySGA)eI7t6MJJCwxH^KZP^7EmV*eto6dONA6HvV8y0#qvSiQ{C-1 zwayaooLc9*2*fwfJb<8lHbji|Q&F-zQpjTg=-7YOh7F(sRlkq3ZwBWf&r?Y$XixrT zgdmqm_3QAuJZ!21W8$wR@KY5HsSEk&8~9KfaO&Mzuj>t|9i;y&!c`u z0zB#|_J49o^F4Ab1LVS~IPwFA1$k&}Oj}D)(b3auwo?yi=-<2(NwYOAM%aJooVH}I z`RMb&+vadP2Z!0uQpY#A6+d5gVhB3;4T58|l<7&uSrhmwKu-YCpQs$SK`h`?+%%;0 zM|kV@JEMp?+n5qm%haD@r}o-=6Mb~RvS2ed4g)42kW87Sv%H?m^NMqCNi9S50S zTSrA7&pT7ob?4~+Z+P%89tM#$D>bWVd)tVr{Mw5Z4JrlFT6_DY5i^RZu(I&=ddS(B zalU)sm>LmL09E^x@tqtQOtUba&jxV$o*z>!SAqZ-N!R^+nOYpz+R8Cdszd^y8Ve+_ zR@zoR8y=V{(&_Hq1)w%j1@(r)lDjfh5ulXHs7*pi(IwKU%toiD4*biOpm!M|u1kmlN;vi>3} zAk8~zs=?cTr=|T>rs0hLpEL>eTt*au)6yfxjvB!B`SSGiG;7m&Rz|I%fyVtfHjKa% z#`e!1hWq=|!FC|rH*-v~bJ+L4iS(6JILy0LU&}=c%~euuRP8J0STZaCJy_ZHC;F!c zJu%7=x_}Wa#@tq#7T#j`j~?9o*HTb|A;>!qOy%5<@Os@pa%){z8|0xZLs9VJUBfSb zM?RUD$$xoa0OWAkJMne)`JYvO>ya@w#`%v?ko~Vw__s_hbkL}7>&tmT(VBfP{Gj$D zNgNOO!+MkTOY^YPIjgn!49s091H&p&;6@Dawiw*j9Edv5d_YPlu*XN$e%dbnip zWadv5zyXRLHml{lnd_L45&c@zJQRHW1GH5aF> zK8XV8K<1uI0Xz?|D0HS=i6}%6@Y(~m2T2wJgYS@$r=f(mia#^>A8?Fq?u>j~=v9XI zyYgNBgE#rG?BIyprE?x&2ZhS{6;|ut;;tEnRum(14L%q92(sT2{(EFUZ;_;XnSYO5 zs5e9nygg=_6(AY&pW9>bv%Npr%AbShwv&Z+M-P*|mQO(b*5Gic-ah@^XE%nu=P=@c zM@60C9Uiob0=|xvtF?4}Z~#JdF&} zGFyrHG!Y?OTBxRc`WehWvWJ7;&pcz1+Zbqzx1_b)-MvbB5Qy8;$RXtz)gNpdMz(ki zKVZcyy`ZkT9|7@GeY~MoLj57quVku{3^>F(qlu1E{D9gP!mB!QoUlR@hHNqL+7;Q_Y{z0Gp9&1X;F63 z4gAT0<+iorF@%5{{GThNu&!*95ex$s)Ws(aTp`;6|52UpDCMw?f2zZNq_ixY?E^l? zKAZ0XC_bu~6028?KQkHL|5cz5CV9G#6JLGKx~SN#BmK{8VNseSyZ$p<8KWgVZ^3|= zeRql{QYCs?>L8DNiS3QSImpr>P8H22C)`+BVB}O)*HGhHc?uU@TNAa-q$y_#7?SD> zey+(j2j}$v3?593UYz&X`G4+uT7Q&iJ7)(QSRJ43bm*;B&JA;^YEAlbNDO!^tuUk}iH-mER&i3YLa>WE(GAGyvcMf<;l)VC{ax+y$7ZK%_jJkEUCNMw7o7`is@|CSDUO zM^tcKQQ0Ws-&WlI|7?XBQqc#2WC@@jz7rBQS0sY4fNE1C+H8dgD^Tp-Rb|SD1CF{F zra(R|KtsOEsdwd9=|Duy?pO}XnEh1i@w zc_z*I63QyX-3I9jXd(nAk^b4DZRvt9(tsz)Z?sUd~ z6dD0Jq^{zJ?^ZxQETDy}A09~!j7L>9DMP)6*cLQapPIM9Yaq?_(;>e*)>(f_#|1d? zI>y{t^zDSsnFfe|1%U7lloAOHbrtY`-e(jzv1+RS;@E(B3Et@9pxdf<-+&X(3zRy% zQ@fT-epLhCtz>`R@AGP_4Dx%VQ*+7z&xXuODPg_fwH*b00|vwsI-LisGI_qfW#_ES z4pRQ${2z+wj|s?uZ`^X9D9zt_on`ke+{n%2U`~S1e706!COQe(O(R9uXLd`FQ}^S) zspSv))zAJhkYE2q5FjqRP2YZN-aAaO$uKkmp+;Fk#rkblmF8;%cO)Eg!<#R+3(KNE z=&}q|(r)&y^upH=mp!^@dp7@WvjoR&=Or}tOY?1pU2wv1_Rx4my`&U+ore%7k5qc5 zfzDzn;4|Wnej7gOa$`1Tn0q|({dFv-Gu4?+n|dRK#fj*>*C29RX&=2IHMkjMBrMW| z96$hEG<9MQZWaa~$#>qn_%4{H&Fi{K?!JY}_9n;YSKLrDmK~3!RN?{)c2k%j!a4XR ztp@Z0Q-QmhnYxd*xjdE46(w7QMboZ+J0GL^Rl15)9J*=eLU&rQ%VLrElW%uG3jbUG z^3j<3lN*yTQESaCwhK#8Xx~znPKzr-JB>4%dfCa$)x#E+!nBQWw%9+PSz%2_bBTW7 zwc&W=fqq1&{rJXYRo8Bbmd?fxF^>RwfptH;=zPg%r3zhyHP@4PJGONvrqt%pwwDdT z9a@>CLaJRN1v!8O$-Y&l(e+X@=#Gtwv*RX=H^(kGXtviP#tJ_$lb0)xHMr zH%*>8^Xw4y-rJM@ByW1$4vb4)?@+mp_#nKeXJy}*25kFbzbX!$*`L7{n(^Yoc9hhS zqLNZKC?GPlhVvF($)1)IbypGD*y@R)9^ZwU_nsgl0sb?uAMRJ4_E|SrSaxd=x!b)6 zn|k>C+a)ybtWg{2vD02hk6!lC#fNOfQ+faNhw>cA)IHU2;x(7QN(q6!>#Jb7>SiWr z|EI0plJyDxWq41^2Ca5t$ywH~XYQ|oX{?#{7|y9ZI!F8PsEV5nYKf734t})DUdN6? zn2c5OxFO-8BrWvN4H8O=A=$+)$&Fwmv@tOGtCYnytxGp%RqDqJmUdvvj**toc;_FP z^t7K^4S5eKd{A@cG9j6;`A_e<_$KHTlfm@X-S;qMlm^;ht9Dayy&3zuJYGKAUF)czgH1ko%_5$ z;9I*Ymq5ep=U~`X){F&hXRin>@BChiU<4vp{hO!f>t_wxo?nAExM8SO6lneRp`g%? zhywjYz-R`bBZ(sGFe{_*)$0q!_*oAZB z25{VV`)6Yp?NsX>Ue=2;#+CBD80A*Gg75~!%P` z5X@YI4>v&^7~RqR6P#sa8n(Gp8TS{cmhJH^U1sdV=EiLNTPlvu4tV2l)k0Hvs{Uc` znGD(mNf|?MA_=6J<}C8H20_Du;V#(~Nb+3Y%OL`(j3bI^rORb7zag$B7e3HlT6>|l zy{HAgZ#Qhcdg?!IULW|i!EQj%&{NXRE}Rz)zMplZkD*&G47k|v6k|)0VhR%7!XT}F z;oa}1M4se481OT|^>eo_M>9~9Nbtuh8^I-{Z&6Z&MC>ANv%E^*lKh2y5_fZ+wxXc* zGuZeCG89U9E+ouwaYC=>UqybFQ}QVayU`!>=-R z3$^lGs<0r(%96d(ZgY9!AR>DM$m3r+0g0!7p7#I36wMARPKU=w8Vgn}I%&mhg|PZR zfa#Ikt7vWp{zWt^F+UF(nV>Fn7sU>WxIJ^~PPqn&r>{QbD!O>XD8s+_GrL@Sm}RKA z4y5!#GdO-ciisd|`lCcD=l&REIL?K~g{0{E-mxR~>vjc)L?GNHCa3IIwDP`lUNK_X z>nRg;CEcN|A6zgq(8lcG&GCharxpWm6gu!4bCs&!QqbuQwTtBxqB4eAU_`F$w!B+f ztY}{8y(0(532F~tz3$l#yM|A-z#v~&%8sT~yJy*BxK(IO`l7mC6{X6t@}tyAl!=m} zP)voMOQlM#Xm+T7E|IOUZW{TB@yRQO-x40>LbO||ys0*GzhM$g`1-7+z zu%wq%F?&4t(f%Ij6mk|MNQh5pRaWkrYo1DaH7AR1wQ+FS$9D2;68FVhWTe|+6W*+N zhu%cuwfZihM2-M2%aJ{%HUZgu<=4oz?6u<#%^4>DqfUzU=j@m-49dDGU!(_5i`|Tc z1-Rs$O2KSmA|3D|Ez$^C zGCI<}y*frY@jGkJtg4!@vdf2CwZgVq_J{g$W>H7YS$5*aVK~m<9Dw~d5HhJIzfiNe zmcg@MFrOC14T19d9$P=0Ce+jw`?{4jqz(3mjy2J>pRU4t|F!A@7dk!OXHht4r2$SC zZFclI2#mm{1n|<*)`XAm>N+q@BP-R0SFGKOC6LvrF_MHI^EE9_6{O#x@#M%H|B*8 zZB2g6^g^iW%*IQ`*Z&)!Xb6v{*Jr=N^S#`vKn=-Iho*W#o$}XJy}}&M#7at`i{I47 zEpfA-U%O)Kv}+dRaZ*s%{hmPA{Q+++^ja$)=VV7|LZZdR&*#346E%Gbo=h4vtKc5t ze8!ha^h+M16%?z)lA&o?gm>f;b4g+15K`>D;^RGX&)Sm^E7^|>I2+9eojFRMkLHZt zxt|^-loB^9->~m{ntBniTCot(cP_+IFr0QlhQ0mK*eut4j*6!%<{?co)3qid#QvI) z>ZJG?Sa+GUvqm0Md<=F9o8x&DrWE}{VVn6~+>iBTHm&NtS8Sfct+n&HU?zPyVzs;| z0{kV>+IsfUyiAO=QW$r`70J@tp2&s-L+*i_@jE}hCdPz>fS)+0{;o^xva;nVb{Hhb z{tW(Aw2R&CYa_Pd9DO^7UMqeI63xy@dhwcmfqzlt| zrc%wfp3xj;iy# zj8_&uJUq6XV4ueG@w~52hkwHYBW6e_f8pV6H5&21%gX=>IbdvxY=lvW(h1kj)z1^( z_)GHiV&c`y#(^)CMP_JYDO5KvYTGL(&gpsHKofnnVZt*1y6{Z_>P%Rr3qR9WZ&d$h)lL?0|Az-{yPL2V-|0Wiw}H-^dxv|? zJ0Hq@2ScQXG*Q`>?O49NEPT)Su%GcY>1x+>KidXKV}KX)cEvlyOBus5R$!vXeXdU@ zNzADl3u?JDTsq7;cr-VsD>a-owiDc4&=Y|6O=7Ejw@<; z8T{5DNTp0Y{+^z47dn$VbAkwX#lu}-o}s@kdp0-<1g%oYR@t(`q@NT8vA<2u@EVyA7Ahdhv!6p$(%B^; zlBYDvmVB)GC8=k*^qgD@26Ui4Hcf&^$QKCWayF&*KizX=$TswLGbH4TqV4@UU|HQP z&*Zatv8a?9gtW0mRGoNJ2fbNM9>9B~9ju#+?qsrko;%8rE!h^TbF^C@a&Rrp8nM9p z-MqW!EcOy|u`g!EAPw0mQMTt7u|CX}`$3b`{V07R($0^^!=bu&by@0b;Hva-skgz_ znwZ2;t{<5_;mv!r^!Im2MMrw9JJsu-!0Fk=aw-hOIIR~vqO^{?o!k@-CYjOu6r#Fu zq-)z@dJBGHa-K8z;NMrEt*w&7nzOpFcW)xw<=rnj9Pq>CstNsjG*5zZIv*hlwoKcj zMofKeaoP5vM17kZwnvvNWToer+Z|nh+pi_`S)U;!B$0?<=J3FnI_B|OM^h48j16Pt z-oEYc#G1feiCyNH%$JPw#}d$!mB6k9m%y&p%`Jzs^0m=&9<=L4QU}YAqx` zVL0mDFbhL{qbtcZ_q(_`Z!?XZcF1~-LLw@od4I>{!H;{xk6-Qvg?0QA6q_8wUG{bD zN%g}1^C*SfXD1lBCZn{VMy7EiggdIFL}b$WWeSG=1O+h~cO@_(z~*In~`+t_wF)T z<@2~r>TeVt&g#~@6*sMg3wI0b4j%(WY>96aOJd}~0L-WyYRZ-g>f0|}>cjE^o-$#L zR)%G!%O|vNs)4Ni!~#DL<*H{z1@S6k$5k#lnS8euHInWsl6fxWSAq#Qas;e5J*P?D zXnvkKxzBkoul_LytlAy+P*=SskINSUS&f&>0~ij9+4w63J@BCx)hK?COY1E0q*;Ox z)q3*#Y@Kkc6&s6(v3f?o=fB8bG+>!k^7FJ$& ze%JY&^LW2;rd@o94ffnI;kV*`WF_Z`HtKl`!G3Yb?IgK(W$<^wd|w|St8eRGCq`Ag zy}m#!={ zPBgvFQ03tzsXXgB)a%h1k>=~zJU!~F#}X8Psa%)!SFixwoNZoLMr7k12gcy+VhqO+@h3I8=cl8E!ct>!@d~%uju(T{X6*e|SrAl2NP< z>G2wt=7~ZE-;djo+@Lh zQpm$g@A4iH>D?M#W1C8|B-LF&V25qFo)uEkOO8Y9#;dSS2`Rad%h7Y-How{)@wvEt zb+{`=I(q5SxcIYN4yeK)SOO!|yy8Puopq7!7G)TZQKbe62{yKRldKLHx1xQiZ@R-+ zaL_+YU(Mj+Npa!uWU~4$Y!n!H$(c$G(MLS>ev))LiaYS96wl(4#_RYl%{vGR*{I5q zB(@6+Pdh=9UZ_R&OOdAN+@rf3)a4ocf~t~DAa%HsuzO&KPH9awkZDoo>f}6F>7*uJ z2v!eU@D{g#3~)js+FhMr{`xILo#swe7OV;Z!Ef2KeSyyU#RFBBux8@yK0^{ZEeHdS zOn)RP@MI0PrgG%vRj?J`C&qc;+Av2UE}5ANf5x1cs;Dm5_s?*SEF-1Q2;u%}(Otv} zM{3|;z-*3VOD!jmdkU2bD-4Cy8y|!+` zO%s-6o8y41(uId|p7EVLRMJ25o(Hibi^PHdU?qymYHxjfKa!Ktb^r}qOJO^BKpEx{ zh^E@+{*r4C6`0k4p8N{dW7eR@;wiLpXyQZZay9x>_#zoOX}d<;aqf^UO3YS<;MW9C zz6LX*vF8_%Sz^IqoEAOy6W7Ai)KXQEqI9?O4-I_($1~WAY>Qz%78JC&+lb*GlrRRX zWX30p`$`-h#>C{o2_^GI`(Erk;MPJquVC?P8)BcIXl%51TYKt@l+b;m9ueFI7zXbn zyT0XiInsm5kmC(pThrs=MiH76Vc77n2|F~PsomJvO}zyJiX-a9OEvKM`6fcikL;Qke6hBuUBnF?Aj> z3vtoWt6q$a^tQ;2cD(_=RTJg7cIp}xMlYFmIUmFFvyBX_F5~-#n5GX`h2T*~^Jx>< zlDI&opNQEkvP~yJx-NQb*dV<&?Qphb8ox_V=T%v&Madfd8s*NPZ&^H#yE!#_{FPf} z!eF81t_XQ1H^k*dq*4G~RECOo*;l5f*>A3jZtghY4MVIQ13K}9T(q9{KF+f(vw7E- z_gBL$=F8Tj%AY%4LY9wn4IIe<@CwQ{C>?)0*EiZd5Bc_roi#=-?R3HK*Q*bifaot-*x)|i`af@Y+P*7bk}qJD zg)^RXJ*`?_7G{xwy;Ab6a{@(k35VbH+r#!aFU()*e}84XV4FAyUv$TE&dVfBteDfF$UYC>BrmOY^A$#wa(`dFzTCkIRwMi`{Sq( zrf5^pRZ>=0!B?6OYtQ_*b5q)ue8)>W^Qd%^c$V#K-|v|?d~zi3E7m;O9Vf>fjuWMd zO!#MR$NT1;EdIvgBr_i?3{ZRUTRe@oiYI-+;IdF9O<$Xgns&Nx<@Ui5!W>}eI?Cc+ z*c-yc=ifX)!)bd{UpS-bG;j8fi7w8K@OX&*8Ku6Rw{SvXQJhP$IYBsMEBZQO9f$PN zQ_sxqLmkPJYw;`kfL%fY!FQP{=)_KLA^#^~3*5&2K4+1!8kpUPbjQ zFtI4Yt2@p45>Q8KS;qDhFU|;oOb~_WaN4fzlhBdkWn|w<&sl1Vc+{ui(zeMe^4I&{ znG?Q_r`_@Jm->Nn#=UM45;!$HJFlXr-E3_#-%=9S$?%Jw%9SKx!)r~Q2dY2Km84v(C(vK`R+Tvc`n~2&K9() z6)3SQ1(|dx$u>uQ&!nXL>Am~CaBur6?9_2Y0q$<-6-GqEPRrc4dDuun z>r-)l?uGGkU78unK}ml(XYH43+R@&OL=wLg(b3pscPdX7a61;E<$bLXj0|UIhfnC# z?~)03RdC|1dhjArWD>2`rtgPl3)L1(`So}B2foHVVy2&aOlj4fM-wbiRsUS_>^~^s z_q4KeYzU1vE7hIGoGh|uPS7CZu4U);C5irg`z2(1+vnFfkmi}zh?ihu+&Cgpb$qm@ zrOd={?y{j1q2)nLiEECEiS%}6b+i<+?G>)fB3HPy{{x|e0&PP7~v3<`}esqz87=0|UG~j&j zK1#fXdVEp;)VvprY4KN|RX^(_-iY7_R$4JSFaI_hvcN}dROE(b!tBRS<8euL8TxsN zeNGtT*_Zt=ZrRwCOEi*Yv!`A2#59)$p#c^RQRy7C7X|RdKm9b-S^UZT-utJIDnYEy z!zbvnHIZkz=1x)FtOh%$8R@3#d8l)8hFD=_U({PRwKdy6aV(jw7K(ep@nEfoU30cD?7@11R$W)Ld$){&VFtv?4VVr?~=QbL8yo=jj zYuf?q;KrZ+qOYfYU)y}K0297mE?QZ1-AoR#k8#ISZ@b=h%bUm5!AbI#G7(=^kdhdEby_9LhPtt^O_ zea?=Hk((V6Oo{~p1bvpT3k_304%xn>WY}Jayt}ki(0l|fEOMqXL zYeOAj;_>dsXaS#wjqrNBkGfw~qy4Z4!N_ly>_6f0JXW29&_L^aaOTX*OYeISS!bB= zDz7dYgb-?EsT5ucHz>jXZITevx3w2M?XInj&yW>JSp;a z@qN+VqI7+0uiF$gAF<-EtNBFi-n2=XXOC|o`G*v;Yy2Cf57&BqZw!RPwU#5p0Y)2E z&o5CpE3f?8auS6Icy4YfSGuyRyM$~n$8u#w!-1H!Ew9zp!>Pb{%$`XLV(h+U5up_x z!bC@w-gVclz@2Xv>Yt=fOL!UKI|BQPD!(__zdx)9F4nHO-8yleSls#i_N-#ju5epEEo*~6Ka1}jEaZ??Ug|jO z@X_jJiVPdyJs7^@+5m(1%cgZZVLR(-Q?#{QS=#j;mK3JzYKPCni6{OY`Esae`AXJ- z1?N*kIYQdYNqS)`%EFn1Tf@c2K+64#GuPjUm;&CV6CdN(KzOF&)?tB+1hQ=8g37Q@ z&pbK9aI0{uZ}$SVR9rPWInQaxe;dY-|zZ)p%uejML zZ?fbY%J$j$yH#?N_V&yp#HlsT$7Vk^N~cQDcqKvqQFS>n8SGv4Xi{XCEQM0bMdT~O zkx|2wWw&@UAH5)k$)drVC3-7^iimhFIZN=g_Z+rDb{K0Taj>#J75;YcNsyr;%h7V| zOM9kJ0mt#|mmw~guVT2c5fxCw)eg$<*;JOxB;O>|--A6GXX{>38+c13?Be|C6jLLAUkZ zE@2k`ToDKmKfQO=frwtWE_>G@38|rvLiRUauoQP~uF&qETO%79K;JhXMHXbf;A?ItpxK{5%wks{B3S3)Gqv zcr6?^n?9pW3eK~rOc^eFFQNOahjGJM#LS82?YgIkPJXmPz~9i`m?B@2sCpQ|QOKW* z(xiJC?H=^Ivo^hWdc?P^A$mAFCcY4&TR@LY@SG10^m;Ek5a?yVvCv%Fgg*|$e9z zfvceR;nnB*=5tf4MIa*nlNCpznz5WaH8 zL(2tYDt8q%XQ=PxW!HS23;Kf)+s^x^o#j4NFS*4vB51rnHD#jQaNt~;e!I$W7~3?N z`-&#+{5flMi&4vXB@p9b=Cv7|7(R3dAjHii5Co)Wt_>%OdVhWkzK*-|qtat96s_3#brAe+SeMbde>FOC znVs1QZsU~jBFCLKBmWAD`Jyid8klqnt8b-w)}J(=-+mA?iM1q;08hnjZIuf=e_TwI z<5@Y)E7T)sh=R{dOYn-a)2Qxpr)+a})7@G8_)03Ke5=UsYbgyEH9=<{se-q0ai`h~ zk68M*O|9K9$W3L4M?jRwDc68M3K$Y%xHAG@IYA{PT@n`S#kCQC0o!yN`RKz=ZY)ML z#z<~Be*R(Ie*QAGcBt%wT>O_D5Pr|J?^3IW&b#BuDW2|qp=w(5pDVZp3H`Aw=fCe1 z^OhTjGeqM(kNA^^hv}^doRw)a6ORu~xUhC-Z;70IaV93)75OezGqn}FdiQAq2Hx+r zcZaQ%trp!cbPJO?f-|yX?Jq}&C~UH73P~8;=|Ex1YUx%=LGw z!7enbGx2sE_Or7GGt(#Dh|bOM@|LiKg~B}#tuHZZ_*%NigIEL#PKmz0QZd#Yv&Zf! zTnu-Q;W;eh5`-*IzG^0qvRr(*^DpCy2Zo=zl#blq<UDG( zk1OtCGxZSI3eGVCCRB0>;BwjGm`iUmd5({qXOE*7p5oXyUK29Extn`#RG+)man|tm zH2DaBEde{n{bu)SHo3R7&#AX2@R|<*1PCXp5REq7|QR-i6SO z#IN2{Y4VRppm0xTsPKK}Exu;x`ZH)S{Anad)A_JxCmVg;CoGPS_x-@*YrL-CdBN(7 zhH2Ef!+j)>L~403$8*Yg#n(~lVe6_mHTUVXI(Io1nm9F$Z@|aEW=%V1qmTM~nxd|W z#oNt}Gj{B^66QE)(Q*$?SI9u#`Tc6K zj{8ki0d4Z84bHCQDf>xHj&X<)uwb&AJI8lYaA#M+`#`!1$sMlHgwOt@ETVJWHXkK3ui;vZOclKrE)Uyn66?HPp4*c=Hwiz^4Sbyz{XQThI>^G zX#V!NBRcBmFd{Mfk?gcPgShLLR#1T7^mcH_RvK=m?L*==4iT}F-~=t_k@tSB^D@3p z=()LY8{P*)>`H9!`FE=2c0wji8?WoTcP6xJc0#xdD;Fn_ScXCV=Zhgv{V(?3vAxnK zco$uJ1b(bLH z;anJdm+d(H5W(brU(5ozQC8uYHDMWzWnNLlDs57QXH3&?Wzr>8kDL;!Op7gvr>!T7 zU;QW3IhRea;|nL$pG}^4V#lQcX%SPA7Qgi8k`b9zrfT#^-uL9|UpqUs3xp_3w0_4% z6jN)dm8t0c**JS`T|DKmK+dpr-DE4yF;spP6mQ|#8vmI@6MVBUH-G^Z{dWzaMKW$M zfZjn4{qQoM&r!Seb%8!(WxKyyI{nMpdg9RLkJRh^e7U=>_}0uuOFmp|MDHKKxv1ZB8g=TXD2A#tJc^&Z zS_wxbzc$X2|3<#1quIgKdoIUNaCBcL(G-`JljC}onQLo(6gbp%Ol~{iUDz&122;t% zvO&pjQygca{}D|TG%M@qrIoUJDk*r%8Xg#lS1exidi-PC@n@!tsrU8M-}k7%W{=e8 z6-}P@2klSyVrB}32HdLWx7MouP!A7+uR)Ost0LXFxcIk?SN>utfdl!G?l+nyUGx1# z&^N8V?hmwuki&PCg{x+-0*(*Q1&57(r;Dn`^7F*j+P^X5?vmYhEk%1r-UYrJ(c^FV z-)O%23lVA*i5p@}H@QAO3ah3|sjctqBJ8|8ohQm|+BUyWSG#@R2w*;z%D@W+7!?j- zZYgy7?yPj65Zqi{4S#YggGIY4%Wd-G`}%UBG?5 z7LydW#VoTB;8(hGH1|XpEyMV=Zlui6UIx7ze55J{4$twI=8e;( z-{1|)cB;hjr{1z>&Z%R#V42T8Ml$4h%7GUEV&tN<%d{kf37ZZNlT_N0cY8HW$P_3ebzYD6=$0E6YYN=R zmW9SLyTCe&J)z*+=ONN}6w_0Q;;OySm*np!HI(PAi-mGum`o5v1RfolP#^jgD4+z% zG+?U4W3L7B*% zDm(`lu}R1}aK#7Rx~|#zyqkOd`cAp@M$bp6V`1Z{qBahCHrKjg+Z&g9>{wKoNF;Np zTyuNYJhEfz@8%sJPmJsgKJtv|EFnyK4ZMElo7s~ssS@b9CTpl4clxA?Sn$MhjNb*+$YFkF!T(GHj;5Y=TZpWnqWc_)6SqI4PI-wO?cK(BnLV>+3?%4KhM|GB5Xsp?;FwcF?A z`NW#t)fH#9Wo=w9z9liMcQSbWW&5*|uMr&W%q`>nF-R@R_p19c+xTImz`6h!km(h7 zx|1u)WGW-8U$sTc{eMZ%PVhR7e>^J1Om}#Z=0oU>Tk5*t^yzkc=r=jYKWvz)j` z6KwQk6BSVsxhDI=3LjI)meB?ez3Q*Kx%{^`q=;v4T4^3zzU^b)`dE(ER}ou%Kk{%d z?!7KaJIX5GusodyD)V&?wJ=c51c0 zKKHWL8NP|5GbI;3o+1#B9Lr&WyIuVFy%tT8d1;#{FDmO}x@LK!`S6U6+Twd#Tc4>; z>M@+1lPmN0!;n8Syehpfx6Z;x(_*jtr}~?x$oLs8-6TksfEb?@8pR?bKKDMCD%3M& z`JpZ$vX-lMhJykf+8ViYaFnNs;QXR~V$TW3T3dIt{9teRC4IBN&sUV~^*e^t%U_6& zde(4tF*SjGz^}}JUsZY%^8WRjP6+WucK34_mKfj$7sgI=4+894p$lfE8lmH;o-{eF zZ1yqN{dj?Lm;3qTJv@+VvpOyw{n5Sl^*m$ld#E}c$l-Ox^};^s`Erh6iO}=?0t{28 za(v&M>klxS?e?LZ%?I zg61b!WZsoIqjyu3`;cyRprJt-)~ zW}GFe7R*;j#8xJu}f1MiMo#DOtT^2)9s?NG4*2rld*QL$Ik$X3fU_3aO+-Xl$bj%mGA}q;Y^V6RDBTiNE zSiSh_P$)RD*q$Fn4y4OM8hd9?jkc7mD&9INyC+2JS4^skzQAQFzgVBz&%Dj=;Dgp# zqBYFhdDe62u;JQqTz~cuns>OVfzo;f9yHI=B$vihM5$E{jeEIiysbL~TE6{)Ec~v4 z6=R}PA{On#d;ECdnNjmIck^v*<$i}xkO77%gs8PE@5e<$SqYJqLpL*1p4-9|ohA9O z;a^+k*N_sqjhj9&hvZuAJ|9g%k~*&B68AEG=HaY7>?-xW+x9HEA^)@5+#4o4)=<2U z*E*CcIChEd++GeZ9i#V}`FXpnjnN)U$^qZ6weI9iBzk9Z{5SfWx174JFHYOmBkul7 zRbL)2#MhDd#LavWq3LG*Hlk%WIoBia=R_4MxNQ5`ZJ(0OdLqRWmgl(`_wp-Ffdm!=^=wo9yzw%;S*s^?f${-Rdtr&wE1?vNC|jk6h%lZ@7gX2tKcg zf>U_4h%BHa^M8HTwLPjH;h8ap)CU~I%(}6T*tMP@vwLN@Jg23>cHwrf5VmS(8JFHH z3##_0o9rE(N8UDcRBffDcx@zG)VZJvw$+URL zAGv*jqMcdu8AXM$Llg~YrNz?ul3o4+S*L3DKN=uH<}l^c@J$2DKKC|gw}ef-OodrO zu=9U-Lh@S@3pklF&wE}A>nhD?*0lTfbvxv7TdUIx_w5p#WJwUev>a8GlJI{6nW!l7 zL~+F{$3ttny$#>yzn_7<+VYzEE7Fn)-EV5kR>Vnr}KL^l~e)gBnkPva@ATK|kBL>Ne(H#z=}P z8=CLFT2dk4z+$`{$`-XeF9Et4{QHP~b~bbTN=x)0NrH4~@pzw{MvzzpI`)P^L;$Mo z-}_;>P;Zomx@;u~=1xWSu|UVPu}m7w!^4v27Z+ZZ&ZK1(J?c$1X%C@shd$0#!vW_2 zKmnd`V!=vI;Za>&BvhxIQV$!H>bQe9L9NE^j4RsAfty!jwsEXt_L`J%hP&L|o2mci zP}H%|w2rSi6mt>%k#Vo6&rRGUq-v^MJzzX5F)`aY6p7=@FlKx8o2xfi^8ANcbhDuc zq9!^@g!{QBtdxk*zE`xC-l0yAz^cuWECaxkrDFz#d!epRYrcI5}a~#5R-n~Q?Xh(DpvC>k5vA+qtz$d)2Eib|*G_X`ACk8+Bbt!XIIs4#U?zDs=0}d0!*WZ2f@i{X+VM`TUT^pu?widI{gF z>xX>%POx~7^StpkcP-|MS!Yj16VXKX1Z1;E+1FB&uZu@Ey<5t{k-Q$8Q51*LY#^ln zs_pcwVZAk~E-e5PTP!);RLew#aWtdi0Y&xQP*Y5$!X|!MS`omu$6o4v<@gH`w#yKF zn&4aAQ1?#%s6Ct3UMw#VxgJt;Ixt*%e`TklU4^U0rf79A#vpUZY-ql~7*IjuDpVBb^#13HPmZ}>YcgW&LHBtbgBM9rf_rNGt}znHXwbM> z>wD<)_VkEEZ^X2yW&>pilt$370k9$BYK@#ika*!=XfA|8m2_+ayA|rM(pDx_6k^0e z#i2*z$!Ft5%1mUmC~}c+x$?&OpQXw^bYxx!o{4@`Yox#t=oKTM^#CM|bRnn{up_RMa) z6Fp-p^DL2mhgC&A-b!(y#Ao+{ktY8hMYl_|kb%8%A%ta4B8MDxJOPxtCo`YW5Kz0ax)#nQz-X3^|}NT zWfR`g$QEp~v}&A`Jxg%eU{6q~wam@~LeHX7dft$>-R&PwXv-;hL$xglYFypDgRtcx zuVJ>hvnC4`m-gxiMMdM647TriYm|-aPor2gXf86G4!FKC9YXI88Qb%lb@Sj?X~Q-}$9aujqWBJ34qpNiqHqDn8N541?$0&# z`0_Mpd)n>J=~x&Y*8`$l2K1x>D3fMnHZ4w-Ci#o*puzHa!-`1J1=x`ZLbuE~k4>Gw z;~hNM7^L0f)x*KEbqq65Ogm)O%+Pe7!xKZH|AdA$O%BzGk?GZIc>adX)_56bssdx6 zn?;Juumja7emk5bA_UR^3~SSB=$w@V5M}7J54ZoqJ;7>^#=Zu%A9nM58&K;Z@5%5+ z&U{nL!CGB%>%dzGyZAMrwRUc1knse5*UmA8aKw1sKVI8HKmjvLF1brdhzff+fkU+(T&3|Vo7nEvYMMV$ z;lYO|leFqUkx{E~L8Y91J{5@>CTEt3j@&w+uSnpU`dz!1TKv#R?2givt&Cm}yL^^! z|2cOfl^iLi2n~1VE-mSjXqO2tVg9Y{(Wv{1>p(JIZaB1+zKDbkm+s((lct6vGT+jF z$e7;L%%lcOy!hs0o>ys%Zc#J-n*aR|DcI40he+Me6T5tkQ~a4TGEqs#@Yz}1*{e@0 zV7!c>DW#mOxCDNnWN6Gt zqDLe%NdHzHRU4ZXf}cE6O9g>mR>mPInW?-~xU?UM+`<-0jUy*KE=^x`->mKV#?XLL zg-e9HffKhZJeqk4Gs@7imq%w&oRrL=g9=X$=6<-|!Ca9({10+`>M}7ILF0 zio-cOmWd2D20VDL@Xp!vbv@_#$?&GvIwmJ-?uZ&G=$P9&a75e7oVSgYWG~j4zSL_I zs4UjFZA)^nL=En+K@ zAv`nJkQ`><^2y_aIHTGr6FRGHi?}W~$lWugMk}NHZtz(paD>v0ngrj_I#J-p*^wC{ z_O&yaX&4#fo>tfJ>qy~>x<&bIsLOs2R*&~jC<$A*f#~;<Tbrco;_Z9uU;j}6D? zM5E8(hkUyA%m&OGO}u_R(XV8*T;t7w$FZj(&cF z!euEf0@XSb3Z$|SfH?aN8v8x_(H*{v8LH;AkK8kDkhnkHxr1AJXpd4rE)7gW_=WKn zdtrp5nh*6-K#lGPg?nj+qiz+x5Js-G;9b1avU z;543Xjr6}Hp#jM?$G=VY8Z|hI`aWMBbt)T&kirDr`Mp}f48{GF5T_!BBO6?9B>*3V zu*)>4c>m7@bj1w17tu(umug8Z?-LU(VR4ypVavp%{H`UL`@Pz@@iyHmw-*68MKaU zFqHpJITAjM!>`OceG1-U7yEdka+o&cNwaCntFZ?%cM+X$pN4P#ekCI6$C)`h-i95 zlR>l+(A|xA@zxj4kD}V5trON)8YNQucO$k&j@b{qGQ)SlAQ*28N;wP7Vzpq|QsG1N z%)(Gq&0$@iLP}WDffgzULbB&y6Y|iX{aomm7_SfPCC6Ky@n4TTwJsLm8S@7nYFy+H#9fPI%Zq_HL9c*LoJMuN?_Y{t$#(j|b`6 zNmj|L_Cg1-+L$MIv(R*Vr3KTXYV@h2K87Cb-$kp4kvLq2S18aax9B;Po!i_77f{45 zFu;H&xa9yytjvOo_U>}R0DCof`=iiBU+t1RfSVG+-Vc?g2;jnKtW^I#)fflzi{o2% z0~W3U1t{l!3e-`J`Xd3FeI~zV7$}_wU1G>Qo#psAO}`;UX3|wnM!vf0t6ESUOXn|B z8t$F$PZzT9=nC@WVgIvh7U-?L3Uq6Whb8;#)pV-$2Bz5Uxu`}6reL~7n@0_LS~B50 zx#BDMQa9gHydvw|+5?{2-f^P)0<`GwE_XjP-Tex`S#oQ4i}{R?MW9gf2DUasqtW_K z*UP8WBRx7B$FTTRyZ^Z%8)Ut>6Uy0R9+n>;!(Rwhy4} z?O>NktpsBZCjBN7e|;}StR$d<9&Tdfs4tx5zHXC33J`xl38%rEIu?lV+T$WX2;(XT za^fkG!X-VR>9`dkWkqoUTw^5w?g9H5>q!s7rtyc$`HjBGCkRqmzb>NIaC}_@uD)pI zb==l(R8%Bk3Wn&o6~aP>pR(TwtemEj!wgpLO%weQWkbDx=WkO)<~nX9-*R})%o`lD zA+u}|z~|5JcvV}wf*or;2IRBf0r=erHV~`P{&+J8oFT-rF<{_(Ex%@~CzkxmNt7d9 zBC+@fGX9ru`#ACxHpMV5qRbGG*)1R~PFq5DbGE>jXE#(=W6FjVLR(!>HQcHJcg3Qr z#pCyla^4iIxuH!;2);rWUth2yulTo`{y}ucr(7e?XzE{3Qk!r|swlf-b7~8D`CUni zNMDCJ)%Cms!R52Pl1c#r(Er*gg=zye7BHkL`E1lE%w0aJVU2W~eI;?$%Wr=eqXzJY zr~J)1rv{hsug?!@UGG1;@)!!cabzhSDA0Z6VLSuA8=**PhPSLeX=`$6)HrpF`({}(@ZS3n`Cf5-zVK!xHhy|0ih{nMg?`m6NLogWb=({T zG5dAt;&dH};?y#l+T?|*++PnX&en89n}#`jRGji)oCS5Kh;!0Dri8b?it^F+=aqkI zToNlj!o7}HVH40~_|bf9@s}w*nRNMnl~MkxMckYYaf5i9LYG+d0K|_=`~t{2xRBqV zSd*(0)~SguLWMOX#-G;92b%lbW(k~sji$Qkzo^Sp0|!IJ_-f(TkqvN@X9WU79@MGl z_cJ9rhRjjN#L8hllsuHG3K;eCCB!xhQc}rm(>Y?Dus+oA=7YiKMFOpj%xLUE)%QBY zRu&g>lDa(ZxtYRrbpp_V?=|g|cQn6D(X81+F`*bRrFziGYa=PxRgWMg)Z`ilFiI!h z@I$Fu>e<38%XvGJJgVr(l>FU`naHJ2q>|!*{eJ$(zIkly7uBkU6(beLS>36KSPANB z+5RFppkb&hA#T0@7Yne>=ATR?k)bEE^gEvr<4#RY4+2sV( z#84Sjx*ijlYdJ@3Pv*<}_COGd^A6t{eF->H#J@l~4;1eoW|17={?^Dyg8>9pq5cw_ zmLVX-^K$K;5gn+=KNH?P{=Ln>4VKKGB0RC3)`Mj(WO%yPgJlX)sMB2Ha$3Q)7_I?D z-hxO$`duXiH$&y2T*z z5Y(ouda<|Rfi5Aij(r{{f@M9>o)W6IMTvq0eFIt@wce_Y{P;zPS6uMnz8{5bp57>L_rBQ_z=+F2 zK9ja;;6AkIM6Z<43lN}20pixFXA$)@*~yl^cvwlL;s>Y|O^i&^!nbiGL&(eDr}V(% z;k)ocnd_3vBlkyHvI9UB>LUfjB!cIzSSZ41ad~+azQ^r`(?Eu2q-vbin?iP|Eb z@KT482MBBkp4JS9R54a~hCSXT{iI`KC2$15aI!YS9S{YrA+n#6?{#eX1zzG{O5M z&o<*7v7wY7~$2wVq9#T!P6mA<3Fwo@r%bN&mKDadM^y6k_Q z_Zt?7^Ae7!o8$HQBg)W* z*#wmA_oSaQoQ+!hfB4lI>MH5c%T2;=XV}Bl-qd6Aancmq$j2hx13`Gt#;;5$$9t@c z@R0LdVE#Gx5Y@oEa4>o;7a?urZT&2ZKp|}5H^o>@&0?Cb#qYhx(9-D)Z?5YnAtB}9 zW4~ifiY@bE^N)8C@MNN`uNNK+1k{vqfIhQ~UZQYtCre$vN{jiE=gwmwmUrGv)_JNDXEmF~4{ti8eVmHolI3z@DaDaStSL2bgi zO7>H`B_IO%ELThEpVL6pUE zSd7|2{D7XMZ~@(4n#zDtba)u$4IPVl_fACi@I!3iqh35Uc$CT>WC|PUE2ZH}n&hw; zTZttT#W?q@vHhP>E|exCM3*|IW!P9j)YA-wg-11JU)>}dV@DLa(nUzSw7?uHx^+-n zMI&JlVNq06aG`KO-DGSZL1Q1VXAsD_KtdV3qx2sg$pJ0BP;lUqmSiZL4v+|3{!jpo!o4en%u3$kst4QzXY(p;jSQMge^@7)??X8Jhi{Z$`P> zk5yn8$x=r3TH=%bCRV_MURX?V!D$(A1|5z?#y(NU^#ocaoK+@_re!(wlp#seFVM z!2_-@S7Z(uTqp*(z8Pb}NYn~GL{uXJ_?0mDfSvIe@Yc1}Radox3 z_%2`5CUN3bWIR}KiumCFNu6@x5Q}_f&?9Gp49-Xdx^JrcXGQ(3NbAq3&itIDeph{Z z%KZmj1s~{a@QMO4bsQVG{|uSHPuBZXsDc5jm_VQwz+C_F(YzoNGo7stcC9ikuBslT zC(FIwCM|XpUg>LIpoD%O2nH1lDm4_7GUt?A=0*xf!(V?Zt5&j3`PTlGrG09S|U)BgQw9XIkk@0rd zx|&DnCfCKb;-ZAEU#9F#CvWO%Uf}r%b$J2`q?XX2dH)EEj+v3f!WIdkfcR<1a}h|v z?OT$6ri<`y(l2kSm||zhx<8<+@Q~cDSMsMM8fI;h)sVi4y;l3dN{tSWu`k z5QHaA5V`dibZm;th0*_S;{WeWOzZ~$e4&NFr~RY{zI2w%4;=sBbZFEG7zG84n0_(F zB&)lX0liOeW3jVxH6d)+^!Has-+z9vXSA9a0|jJ*fo9FvpdYSd8+SAk2}+ihUGx}n zw`YbPbqWxuA{O}Bpk{y&HuGOZgcRyZkbwK=lG(!o(m^Uk0K&gNKT6Uo)ezo}aw0xY zXN`q85=Q&0faM=h_-&A2BO?X~-N1H|T43z7gGg5`#3P?+`8N#-Np96JJCjU4vjI8~ z0n;GlzJS7!#g6UTpX|}*-Bmfda#vl&O!Hr=OhA!y9 zP*eBqSN|7iucvm-N`dVmJyrm09kFC}kRERUN*Ul#unXE%WMD}94?Gbe4vm+Rk_92L z6u1PYAn zu-FG7!Q7>?st)$kIkjt|A1So-I)Nlv9iD4)vmO71th#SO0`3AI_;o!g58#-ttoUY;E?gkv}W$#(J@=p8Q`aN)|#Lf(z-!4o~3S+2f&^fMiMC zcP$PV85~s14>Z&pBzJo;oD27R$K{^qq@C7;d#rfHLn{H|QrdqoK4?NeQBEt_d~}fh zb7_eY(TD5y#DyRYeOS-lY3Wy=YvwA1F!UaR9RQvqKwwYZuf=?N)a}$6@{jVQgO=Qp z;~xkp93-m(m{6Gjfuespc;yiT#y@IZ*DGGuLhdDr00=oNVAQ=WXR78^=W4BZgJM2$ z#2)o%5e_liypmGVbgfbHjFG1`82Rkgx2nG!*P#DT-bn&Ym=uIxY~t%&(-j3#q3*)E77A`G%+T&g<7x zas#NdLmlbETT)ZOqbT=Z^cZ7G2gt+4&_Y42;<3Cbtwv0Rc$pQk{Bu()bD?|DlBfe5 zN9+DH@H_#lBM&}{tPMPW&Kc)fy{6Re_FDe1y7)W*%LZHei%%a2&ZBH9p$bN?Xt>t& zut|{C)+Dr{E^^_dBcgIf<1`7xOI%@9Yb-DuSKnt{EqkF2;+$Vkh!9uXT@JDr9PEt` zC$4-{9ouAq1xxNi#ne~qoj(xUpW1_VJ0olFZ?7G3!7~8OV+`K(rM&;$wls>>!W5MubIAm#v$C-1g z_ll!bjr(dHTN!`Z-pBM}RGlyXlVrJ{ihk+C_gMrf2)zEEAn1ny1x9zqZcEEoUEcd6 z%E>W!>K~xWrC>N3;?VDrC^?qxxE#r)C>jvF#ZA}n*;&6>m0=tW_06&lwx@KsNuUMX z;WrItYKo2Py*eqvzB9Y?(B*`npvo^x0OegCiA$MF+dR^irWL!4x?L!X!r*aTU?lRI zqmX7_EU=(d-eZwc0R2>gvWgUo0BvgO!tN1X4|I16Sx42Fa5EA8%#QWzF_V{Q`&#;^ zLd>&jqH{KhKWLS0&D@M=_?)d~&?Q)9K@6em&WAh^^C zzPt{6Q3hP{^T(II)xGI zsr1!-NB^*xod!^{_|dW4EFU~LJ2Z$ofXRS@W|XDTyg178W=Z!va#M%8V3jUZ77c%L z05n7Wqn%Q8zT{oBA$(TFEZ`B{53Uc6(cM8ODO>5*mbzuM>Ze;t>BLZCmRwCW{PDEIv zte931gQ0j7{iN7ilk7ofFIN*xUu7@j*M@o>+iEQc${vw;&kFhu5w}10+O){8bAf%A z>E%Ed*HyOpVkjz+bkS1E)JfQmWg5#k>fkXFSpkkCHZDBmLaE{4a6xU)$yl${iF-3- z?z>J9qFOll{2e*l{W^!2@)^KxQQW^XYfaYyqk+Sww6Ar}dOJfA*JMr6QwM|X!=e!C zq5%{W;(v=7(mLJt7UUoj-uc@P<492^#>CW5NWr&{;zqW>4IRVs=HU#9N@+N7uyc(N z2LzDE#7BNyT{ZMrxZ1S%R5Rxpp~8`19UQR2|5<(#Ql!-Y{U9DU76s=pRxf;ei3+YJt0yf!AifBkqzug`FyuBnJ6Di51-;Bd+eP&&ykZhlA$CoxGalri_AQ`SWvi3}kdvw#jD zBgZQ3xPy=r_aF<8n8YqzsKuz%KZyki53@}>AO<|9)SO5AqSiK#+=f1N1v@!Z8u zPTLTGNa`nc8p;8$YN;?Vaf#K>5sOPxOH*qpzGYJr7@)C+Vn=&XY1+>uK5_G8rZhPt z*l`v~w>?insn~OoE3=a!KH+CHK~2TYr7};Ud2Hazm0tT|C^@ICa|1cd5Hv~EB>)YR zn9SXWllOCguaq&)^GwK7NS`y$cI{s0UhX)8c*&JyhOn%7;RDs{B7jJ;r-QqfF$!A= zURuQ{@f!|GBn2!ol!%tz`?s)SXAQ73bg@&(?4D3<-=>soW(*op5T>k-1N8u%HG)pL z`L#_2XUmI^Ki?J_!7;}j$BZ@ty?c{Nw3}wztN1kNdMnANu#Fe)aZ-Y;yAfU_H!@1*5hc+90ba# z#oS}gjqHl*tsw8^V@hIaoa!j&FpXV1mpTngbDZByicbwH(D8k?jPVFc^}s16m_G@? z3%M#W^HyCC3d**M5lmAPtf<%>KzkA~@KfL=2EH+qOBPc`yY<3}bBrVs&lkp*45%09 zbR0kgIlHyPMPhgdY-MxvXZO5qIN<7a>m5)zyTj}6%$y(Z(K~+~vz$_khHGGLK+uEx zh38WFSf#u}?4uoO+elTuU}6*T&XYadeqO-Hc;+G3_@2z90dTLD&j6&6p*bm`m&qu zQ&pjlizfd@-s)3+pK{kxgJ)VKK$Ii}KjTcdKS5zHAJAd^xsXx0Oy7&a(l<-3I&#jy zAvuO(mE<9?pAINWz;Z>_W21&aLE3Ygm%q zMdld`(n-p!OomAhWWojzhYtBp4H3gH>l`3U5Nk;a4(?)#uj@mX;f0z@Y%D|Nf1a~Z zo+5T)^*RFD&;>m(9|sDSqna1R$hR9bl7P;Oe0}+{Co=u)%R5-K(p0nD8k92B-HCGP-$;-8MW~Fg%&R3|eAp zgNRdB)1$mh_=KgcuhB8lLzOb&H`$QxR9ZoYt<_)_lQ@-QX-uKhw|@sYO=lYKi8ekW z_!E!(;iCxp)eTz7hq;>=kl3&Kj2iX1VY)8>-qfckdBBHoaBgVpXZ#|ybI7H+ssjl5 zKZc=*_^I*d1hbWm>lU_WPJ7)4RDck&j|u4ZuHtt4t0`cJ_;vq90MU1$>fw$N=v62K z?ErK)fHO&$Y}qm6S(GxzHZ$6lreQ$2{!rm$8I)#sn<=2xjsyem;Y9U zSt5zg0<%9Fh@8qsJ4a&po&*5#n_pgMt>Ok4MALG6?jk=dZgS+whGVj%in{819iM$2f=UH z_=o^n{;HD)PyQ6smxCd(D`mj)Pbosh>=7mA!?#9Jm!Uc4Ce&Qb55q!P?`x-M1oKTg z$-*#w-@h99VnHpt1<^X{C!dORv_0S>!wgHm?lvbWtqZw(?M*bNHw?r0&6v}a%r)2QsP$zXpAn&Jcoh#d6+l&pZhe#y0U!Qzhi(qt8jbd zTQ2x|Z1Q<~p=XF1plZew8?vC^OKrDAt9d;Q8yt|6x!AQ~Nw_!-BlnQP-~CWwghq4= zmfv)Z+xxd#(!^lP=t`YnV{5jKe#UzHI@2TgQ_YsgR0G+kPMOrwpWK|)Q9oZe#MrI*OYe@W|%en7X= zWPJ;yZagF56yu3fkLL{sB7sz@77TsV85RFKe7}4s4mAzUv@_(=ni>^d7cCIpe33gdY0jGL!1w7foZ$%Y~{eY9jOP6mU83m_uN zRUPq`+|aPfW8P8*Ipim@H65s70N&z=KT8Bs97G$_W+Sj%2<7?{1mrG4m1~3uc63qn zUCU-0kgJhIfn66h$ht1xXQ)aqx=uup!(sX%1Kqv|btxHSL4+P`ydkfwF>{xQa%F9b z$Y(-JCqgF~4|8xO3`+zLv~cfD+)|sx_%=gTxsx3Y-al^};d@*(aWlI8SH@dfBDN5J z+ZK3xy;!39PwG4@|H}XmiXt12*_DzJYH_lxKA9dhS}eq%e(x7SHK)>QlycPv=sp3!;La4LUQ4i&^6J`o@Fdb~vCTT0V4a@O z%KL7&34fI1>$84>?{ZYCh{dO;6KQ<5Oe6=0{Qj1@{HZpux&xhznxA|UzOPD=&n(4! z#nzW91d0|UlOA1_-So{-A~pI*j;n4NP_oubE)t5!jA8G`ydJ8skVCo@e2vT)>VjDA z+DI>HG|6$baeuv6c?0=V z^78wcu_QB411?D$-aic<5Q@z+DFF*uYYDV~OmM(e*Ub=~L5NC;V9)47qvgE8?S5N| zweV24YOsb$mYWE?8sy6&YV>f|w;By9!zn%91D!OBbR*Bq;Oe%rF!o>y2o1-sx(;7$ zhf=Q{$tHjDOvB;oF#_I&l*?X2KL3DZq)07&`0YHly35~?kNs9@-2WE~@Ivb}`|)F| zjL?d>TtBgW_%8y+Zam5M$0O$FA#A|7Jk%-wiX}s)4%XH^4Dljd@0Ki><8eQu9Y*)C z$DZ~swR;Mj8v4p2u%>`Muo^A*_ur-EQlNu1c>PZ$qmgp-GCCTCvBo+#Ee^VO#L zk26%a9RvQm34gkfKCGHPa6bg5GCV8>Ew;{!8@nOwb+;|=e}j<-AQ5oE)%CsEf4z+B zLzc{Y1m)it?>ue3^)%locd_u7V}A;ABR)07y0fN4`5?3YQhua9=}aW&JfyOi-#6D~ z421}EbY7{oQ0d_0Jq0%Ad=w;e+M}}BZT?xsLa~s$p7O;hEJq`GpGeB}?f70{uhnXW zithV{U~mHfCrk&h5suU9H(y{4cy3d>@n4nwmi0mJsPKL^oP-g@Sz-I3myvWO)Mkb1 zuDo*3&jv#Lzdzri)AKwisQ+(I;6Z+xFg3<9Ttd~N%TYX0_t+!*!rx*!_>w|qlZwaG z=zLXx-)45uxup)IzArL(7`!a>d>n}*TfY3;P*Peo#`}6)V87wAENE&bR_n5$p`@V?=fB*;_BoImkk5NcyI3!5^-ccl_F1 zM1hiWmn1!PR2@*O;x-6HO?*N2a)A-CzG_$` zav4+Ie(AI8Zn8fb z!V+qK@{)n1S(t;rI%!F5ysca+C+Bri5TRnc)#k8M#ssMkz0II3{Gc()V}ht)YNlMg zy}jK6PLbYz<+5}j&SECuXnZK z%2^IlzmA5QO}>1>(ml>|bsay{<+%G}$fm{QLV9^j&ONj~QVfSna>E$Hr*!Zl(1! z?tZC}`AqA0)fr-qj0zKJJsgNc58tNIs7N9<!VBKFM z1aH(T$yGdYi6h5%;ho58A3;(i%4Sif@%s_~mBWxR@Ma2NvEaOEB)sd`%RDP z@Rm0>PFprT;ZVcCGhLQ6Yn_hdu=#y>Gabihdid?GcHFAEp9krXa_sw3YCyKLD-{Ft zM?M!T$Yy`obF^aAeJ{HsTVT5s3{DMXre}W&d6@gK0w_rFe-KI-p3x$aZ9dJM9r-X# zNi3LBgGhn-uZHiD0xOA|P<+)yF7+pQ;LXn6A*&1e<$_RGyy5Eh8GJj#|BJ70{Esv0 z+Kn5tjqNnHZL4Y2*tQzmwr$%sCTX0;PBXEcoN1pI=X^Q;Kr+9*_PuanZE@KeZ%7SN z*!-g&B*#VLK8ix&uQ+fji?voxThJuog7jAsa*kf2;tPH|RECxSl@hliA!la@ZShm2RO)J{y) z{XDMzfm$R#5(w@3htg)L2T}qFlb+L0VDH5QMyPu;z}EvAP!u7*j#*r^=NTpPn17Sp zJ{s-cyF%}^zfN2%(+6GNaz>mGzvbvoO?`^JZPU z$C7y8487cJMVV8>Ur0Whc4>07-{EeqeqFTvPHERmvYh_|nUop8(_N@4DmkXlLh5Dv zO_EcMBCDsrVN?(rl{gM!lxc{Mk1-Kl4Vwa{l@=2Cdzczo`?%~UHrg2~_Vl2s9RHuk zu3c61+aR8dz~aVbLBAs6hhMg6R!vV?yzXQ4qNMUA5kOjbpk5~j8l=W_Y~Vx8(REcx zimt4&D0JWiu2M$1s5vZ`yzINWJ^)RHx(zyzHYX zsCd@S15IXFw;lx>9$+r^H=Xvb%-hP$&7Bv&{_^y|*diMGdRxqNH#}W5VZ+59Vvl~veT~S#B{8l@UdD1weV;?O7dW#(wANj4W2E`Hxxy)LHC7t>M-EGvcC`v^o zK2Evbk|h{9g6r3c_ozy>0n7J-z4*`k)tz8dLU0E2cO0k>qio-9OM1>8InfF*rA-mZ zutZsuh1e18E273q5aE1qNA2rE5Dn=?;9CTRT%CJ6L)v;%*19}q=7~+aullu^r9>$M zpMG0CVb{bkdC{mP`i%q%y}evBsH2{|S5!dOY+!OQ195A&^S%VK7_gDvsZ+dMeOV(h+Fc3eBjBVHm1^L(D6%p#vW?0kxn@SU<63E3>P&3G?ua{nMlHgLZ z&paiZM*?CSCl+0&6edT^C*uZ+3-(eOpGz4^Sj0IfSWAvc`i7&wtiaxbPx{(L0L@+2 zX5R{v*?;v&erg-WjW`E_AaemYAm5W!UbYu|(DEkyNI!~=U0X6qVjrkG<374L6$9p6 zCS_<3In4W)9KF@|f{w}=sa|1EoA=3ToA1dIwx)eC0>cjLAxp}Wu}AReHG8pD9fWlA zH~*_l{%te5ES4<+MZSluxNppiv$90-di)Rlc~oe$@WTZIhS?j`@ZK~U-u0~IyA2Hu zp}ik(7rw3hwcDeFo^+=x^?R_vEbiFj-hS6Y{(ns{(=a~*nwn@YSG`vORs>~%pILU%Lwlt4hu%Cm_&)sl7l9`7- zvtSpAsddO9E@ZoknU)e7W4_U&G589vx=wh)u$tg7#beMFoT$`wtz%%8I`AZy5Nx!v zg7o$_H;4S?Th$l1`_HXr_h;JzN3Q12gTr|jKGm8rzf+E|`3Yot+bNG;kKxxU6;V(8 z^Om4zN58L}e)YOO!9906ud6dTRmh<`Mr5?N7ltA;J5 zID$T7pWY0jC%QCA2cj;@4IEaUTg4dpQb9{>t}4$WeZ9!+HTB@l6b(>ePGgb)-7rL^ zpNC;+#*3TXp4BgdPkdyOF_EcA{BQn_50_`&07ISb(>5z?GC^@63AZK5L`$McIi4`Y z2P$&DKm4qRF}pJoULob-?RuXgOs2f~iwM%06)JFSALh6PccW1a5BK@0STx;8*x6Dn z{1Z`QVnwCg+?J3ferTs(w0SO~g!T|6HLnmykyWY_MS%aXzHPv0^ZHiQd9ynEc5W-{ zOG|XtOJ-4jJ`v(Mtpl3_S^_k(rDsjR-~7TgA{W|VS3S<{Yj$@QE?54v^D;o&w!w^c z?D+%YPai=YHWIP*d8Axs)5nbl2P=PXvFq zBL7jlZ_!E7&W_?Q`qFple72za-$f^zATsic0a{AQjWTz@-$P6KcW5W>&JI`C>R60D zcT<^m(;NJQfhYp751XA{M^{uiy>e7>e`wvRDzSv&9HKlFAiix7l2WUpkPhBqicN6v z2;kk+5IT;+CXkGLd^8vOIY&KQQaxZ>0jKM>`x=^@#wm4dXG4(h@9oesdawqex zoHF7iZyg%(7dklda2LsyCKY@WcJrf`?<@O^-K$lUfR*)k(1X<5VHes4joGi&Bh#iY z_u_wPpND^>Kr5pS+s(N{WBrh>w68pna)Z2=(+7(yO@vuV^Aj0zlK#}-1bOzs?PzE( zd7PL8<}EkkVbYWU zlMsnxuk(i|r_zuPCYVNBIQO6&T@v;Dka({g;|qk~rD z3uufh=snRkTh|cbt6$WRg>~G>mjGSl!ty(z7s4~Ww64+L$pnlUiR4Q?TLjWo*`;1c zT}~Ix_}R1WU;&kwBBy7g+G;bL7YAFq^S19^YwfApRe~Vu*z17$+N~2*a)Z_Fd1Xxh zPA@#NhBixzKZtu-4X~V8jDcJ=)To0zi)1YI1LMRAO*OZzq0*l%8Z9>`h?f+1-4s0& zGN@(zp2JSfv5(2Hf!bcq=aPvt6Lw{Z}iS}OxT-qtw_XB97ekBd{; zc=t0Bfj2WB1-k6Oh0#zXf)5^!cm;Cqb*x>DnIAkm*>WlXZ1{f z6D$Dbf#KF@Rx&1Hupha72}MRCY}TKZ;qVfME)+Wv$svhBqw2&~AmU$F%%P4rcx_Ffoj6xbZ$GtKUf61o#IqhYmd_{nohV z)UE=XEX&}~*<$N`;0pw{yzl;n_pD=rZ}zqj zFzNiRsD8Yb8i-Q$APdRKkd!dNLV$3%d>HZgw@P)%0x%SuS14yif5Yd7*+rJY4@)0# z$!3&;5u@`kZ!Xtf_%V4oCAa}}-|?|=wy}_M^3S3P#{3M*1mg0LM#kp2tlpn7=q2Pu zjCbv!$}2j`=_={aXX!}kCqJbXlvV3~!wo@5FPZP~tgZmJ&fuAIiF)$&V2a)&qF?C!d6WhUt=^a?yQyF8a|w!|1;8V*4dAET^G45WYXd_LutW`)t&~_EbnO*;Hxncu zn?p&ud~o74z{u6>O@7}O^+fjEak23`5tJE~4(46d`1eI3kaS(`VAk$(x0L>t<$_ot z$Wd`x^99Xcg49P~c7?%!@4Ee+LLX0)^f9d=Ta<4^?uzs-B8js7W*nGvhM#Z^YyOoAZeD*VV@U8RS5UCZ=N9d2#Hb)oW6%5Ba74hU?-B3E zS=Rp|+#u=D7!z}fbFkD^9lCrQI$b_>1fgRZ zkJYcK0#ZZtU1A2T!r|ehnlO~&I!8Q6YD2ICoSP$mAl5o}!cbwTx?armIhgr^WN#1+XvMOFTIghvO*sglNo_Vj1C19&M z2%;d@GAPESov+qCg>f>vkglF=HcdfgOplYUw@B^TD^y=`KPhAF-Dy=7nb=1&1(*Tq zI=#fdUc1FYb%19&<)7k>TVX+DZm^nnYgGIr9Px%Sx_nP16Qa;z^JbF>}~<1KEUoY+fZDNeB&A4p^^AQTD~-Y*_QK_r{lfeVqBQY-9u$E;<>hZt(Pf2vtPV#9AIr8*^|84Av29iMTeWM zh@THJFk|Sg-|u%_yIwn!5Rl1@*vi93v zK~s!iMtbIq%oLk8b0E|yT zg}cS-gfb?7JG-Cy7*?B}bYU)pak8ca)I&dsfstp@?4p^E@%D&y+BvLdb-1ufW)>Dx zIFaQFgW_!T{sgef;pONea0A$b_~(6gw1K;%cq27Cl-=Rdyksti4=NS&^Ok@<{=-<>EujEU12c4IC$ zm_bs_F44*ljk`{o0o&4*JT@Yg9;8@&f2;LEKUk;>8=qi+LeN}2N*;M{(xM0A5Y>+Z zj`h$pxm=fLFPDEm1lD*!fd1F20Ee56RQK(~km+@CYB1n)4{hztkm+;ra&g>BGx$V} z*XQ+8y$t~l)u%?A-ab>^4y(T>uZ3zQTy-;$MZ5&<Up<-CvlTkn0`9J|3rFMm`4YdLgIrF`yHdWq= zI#wQV4DFZ5)B?UF_&$#+VHfLQh3}l^nkAxmgfJf+CQ2@%j)grBpEix35o}vy@*PiB zbF{ni2v{*-^gaxod^U6Xm#H{k{aqeJwu_4aLF=}j4_AfT1BNezp^jR(0`*0sP(zKKn(0h@<{Db&itC36Y^2MShic z(wkHcE{UF(ROj|0gIzN7wMVUU;uvONqRAN(iI$NKeXkLHOhD=W!k3OMHPPQ&6Vm8b zpF3%79-D7i<^7@|iTeEYvKH|%EB6C#5Ba65bP|t5jz`@%2*u9Mn<@$@wMHrmA4X;_mzWCKM_-eAwMv{>RV8 z4xxTpcxG{7lIE@Nll>noGxoiFF38MRwy;J~Yab+;-a0zSOazkq_pef^lX@9fuU)hJoC{iX#7?_426TrKT^aK{ z1E+b%(?J4+1xl1)1g6JQux0+aYm?{4dsg$qIU;^P;b+|&x8_tGg|I4;yr}5QR2el- z1(WG*a>~$A!z64kHh<^Q2g+H2k%cSD=%lW~bNm;=@RSh`d@C-Wi*lKRH9Za0dGxDvWXBB`qVEx>|(NM=_ zFw*`Hr!+*iPup&WZTRMc`dOBHFjc0!tp2jlCtH>fCTa-Q8X2kN`6oaT@&9K&34&_ns< z7M#Ih(O$rN{jE23ArhzEC zL||{@JgBd-ix;^tQZGjm82_0NF%3U3?)*HWzd(*(N{Gh~$s{z;9R(P_o4vYOq4RT$ zfJ`c(Ezm^<&UD4U&53Jyq53A=^J6(9{BHE<2TEv{3#G6@L^KS%;|8ky{qjN^{BWkX zdf4y#kOJDT^l>a5rZ?{=vLrpUi5ef+G7N2Jumb=S%G4PDgh;*{_%IJ903SjhfOWTvQ zVE?TJ7-MpQ@)_*M{rZ>jXyMzGC9LXn-}Hv@zRkW!Zzb#)Sz92t0At4Kvq(uva0hWR z%Glofq?wiARXm7bIZ0qf>k&F??B_j+iaqY_bg7xM^SM1yV{kq{_@3|uEJ$9>7Dm@I zxs{12rK$U~>y$qY(C=d!gg8|09Zb*mL}(}v>Ib1hhNSIQwPr;#zq=tUDsJ}1@S>!b z+z^i;Uh!^BY+h%ZmQfF+S)!SSD~rLol`Ei8&87#&mP^+UQ73*5n6Byur|t0o4BI^x z9MG?mqZ(l2rd2GXs9D$_OiVz`t}*!gpCO5-E@)u~=Vf!MF^qU#@F!S9wOQTiY_Svb&!%i!6?aA_rN% z=y9Fz6xR;kR>I8NGTUL9C&?SXO;#IrFZYhTC@Ea9RDO_bEVaRJV>ywj;m=gbUj~^> ze#ObfF(_B)%Ym~_ph8BiqM!d@2UE@_>5ED<|A)F!K%#+}j^K|8C0K4Zs(%Oz&(70B zC`P(YeDEAe$fO&WIR?r66!i~MoKg!SH~}e*sl+A;yN18ms|Z0EvF9yy;|i;OnG|%>rG-O z=MBS}Sb_M7Je&{pA7>8K0D6E~3r!NlIo@|grdRw5C$6IL1Ro_RLM zA+w~p1+(#s1dqAn1+tz!%q?Tw2x|7LPeiXbNML1BChTqfZB5Rn83v=-JzjAX(qo$wy^}Y<*#67nAck6x2%~78nOII`oKI9<7&vr^w-u(v9HCsovY* zwopOQssp{au5;1h3!v8UKw^BCWoL-9Hi3gLS*fY_97zl>)V{n_MRb!NZZtyzzkg+I zar;mrwjYa|Yk3l}Rgc%vnQ8hQ4-1COMcM{-M=C(fFFSr_*U@uo*GyTa`wCSj_)`WD zD~JK|&L|Tp%W%r%v2)n$vPSm@T3DA{^FCR`v2i?=$4+G{V!D6rrVa+=<|K6u#7}H7 z95zxjHv0Hvi0QMMMgOx-Z1hwO&u$L5NF=j%@Qici-HC5FKTl8(Oi62wF~tdL8HR9Q zIdf4b7m4*!E(IkOZV`NhcGMDdS$HK8%&y^ zY^cb77^C*0W6V7+W|YA*-30iik_U&>Db$PSnaGf5%LoU{pnvcx;5eaa#wh20Mf)U+ zdSj2a^RT^f&N86q<<1whl=~X1)UhGP=o?{^_5LZKZpZ~QUknoLQP+b49o#(%<76}J z%jSk?mh4jt=jZlsDtR6moENTL{-qC-HCKyF`z5$skG`qGw~4E!x(wX3`nPYL#Mke* zTurZ)fLyZ^ini;M-r$0A_5M=MpV}S|3d^J6E_)diEt3a#kpNGBOUE6T$pOk(qilLx zmU)Z4N`pW0?DpFq41teS%>XWuX?X0!fy12xJ^l);x0V|barn$m+uN1_y^fw++1ek6 zi8COYK{E0m%>XLIbW2Y{4C!ut7*-5W1>5!zR1o5nL838+I>=(wt-6Pdo13)4^N6>d zd%0Zp^XGO^?1-mK@}%Pp(%-&NKsc^%lOr2#0LhB0FCOo8E6L!NJTle>2ZS z|xkTXTYd4q+o9V?Fzp6X!AJ0lL60QImiLHR>M^6d+ zgTI}N?a=CK*998pNxjME)|FITjMX#o}m+_KkahCwvXUctnkGe7?>G|JL1a+Em7(>so zAYnl-{Zdew`QAuI=Sa5CfNEiPAs&;fCx+C+gz0q;yCASSomvhy=rSwyJ$84@1GC;# zM&o0-SWlUW88ISL4iQOuW)(oVGe5S=+@(4?kCI2ea~O*P9`vh;;$lig`47?3BwXJM z^RJ!nPktgXfu-Ki0=Tc~MB@(hB_kBl>}}#*!F*WEe%A|=nUIM3)R1XoU^V7G`K(UA z+bpM6XpcvjwutlZM=(VdR5K^t-A>e^T6P*Pu?9B!{AH zfYrdaNR1nSr}Zc%t?%&br#{a^wYbMuNTtwhuzrA|$l%rm6fPW5v)A~U|25-=(E%sk zgZ~eXfeF1^tf*&9XGtxCc?N;(z0!d?X!gq| z$W-CeMEaCGm>FNN2$njW)mQQbK~Jh*8Y|B|tgl9;pT71u#wH6!%jbO9!)G2p&#mxj zd7OFs3W2Eh;FiCnD5#?afkZ+Is3(HRL%d5Bis8KYgx!K0WJjZoPuXtojnxyM0vV*2 z2a#)+ouTWriX@OYRnrHu{lvfEx&dRPG?)v|k3YKL_P0^ypYD9UpCHb?*TTQ_7R3D= zxh-k7!l>uSUs_#Ced5fh-`}@{gI=N<=Yc03(h9iJN^w}uG&R12e5Nh!Vlh|Z>sWFc z^Gi;A#{})02$H7sTfYmbffkdxbPx@9CT6 z+E-@8k9UMTWW|J=L0?U<<7Zs;EtH?Ya=VUii{Tn#0*VGF${lr|#r>&~P z*p{1U5Gt{1{dWZG@b*CYQ`qf%(^P8WFuID2{;8|SK4DjEESC978YId*tB4w$(4T<& z+)92wN@bz>_J%m|g&EkI=YP zp|B>FIrWj)Er!li zxk#xqzu;U+wD%oZ@1FNX>9+;%oeA50z=?P0P4%W3vBNTDHNyR+N*a4n7t$5 z!-;e&1N4`4b5yK1&&Fko&e`Fd(KwDoe^ay;>nc&`xi{ZddUO&up+!_W+jh9h7SJghSx`l|%5LT>}FE@(T@L|_*yzp~Y6n@NBX?&=OVx_~wk zzg$_Awy(dNF)N+qu;<6{z?~$Yjfb6j;{ zyd$xuC?7u2E~yz?;fE=QqQ17j+ssn-aRtAGY^gx@V6j#Mz>a4gJ{aBnQurEH@5`S- zFGTXzUqC8JW`POV*84Eq)KB+x^BrssU9m?753lC@5(93=`H5F4*R06xL`y*W=?tTK z?a^>030G(vum_^YyA^K z1!zPTZhH_jB&(sGkSTDnZ8aW{m!{HpaHBA_YTR%qtS{as7d-z|jTDP8Id|wN5QzcV za>^m;WQYjPYrXde>Yw4_$d_*p+l|kS<&?9kLfWO~zUGpTA8ounV~egflhG>^I!>-Q zF6~uVh_zubjTrWYB2s)ZHM;x7O&}c-aPJ@JPUK2Z~3e_7`@_!{*n zj0Cmm4v#G0>QuTJd~m&^vqc^PL#7_@7l7cA=A zgZ7%4GrpSXV2WMfy>8g8=nj2>Y=Um$S`I~L?HK98u+#GRB{H7In(*i8p@PE!H4*Y= zV@=S?Xss~$K~UTt>zjxD{CwWay<0?U#b*wb_a_Nh+n3u|8?IG46{-8 z6Ffj<{}7KaJ@5^EPGOiOJJKYFO9*UcJN;q(33=quc=Lxu*2QtHu6t(Hc3Ze%^!l^5 ziVC_$GkhC+?pFOwh=!9@JDVE=*Xw(bwz8bv50;3}1X=6;xWWlRa+KU797y@1g74@~ zY}ui4=eCMJ)}L@pRxLjjv)5X8H-v<4ROoj2dxl#+7)&*auDJr$W+ByHBw{%VKvEY( z;hIcw6A%mZ@^#%zD{j53alI?R35+K;8)i0kONxQX#xLpJ^Vd3&@y1Sw8ogxK8ZBey z^IURss&6jHNwO43f_`5DrDf2_>g7^&ac)#9Odbq~xlUc{E3sQ-)TU3exk{Gzu09uR zPG`JFus$rb`=|H+cOFDzaE5QA5N@t!o+<#n>}P8@ukO+L)R?DKM01c!QFB(I@ctEV zE~YOHSVMGf4Zq?HmC}+jd2_hPjZBb`1rT-&$g{we=0#X0KzZth{wBFxte@~BGyL0Foi#z7iK@4+tPv=fTRbb#XJ_#MUc26t zHVY?<1+0po~^e=MBj4$XiT1aV`R4M&S)@B(witv=PVr z+3-KXafW-$@*JTn!QM!>ExtXjdJ3PBK`!@$aoCA|H|EOjNkb)t^JvRCz;3``p@3>Q-o8_K-5As9Z} zJ|AsJNxKpvxPi@I~K z0LuGvZqvIq3dFAv#gK~1l)jvoUf6g7yQZF`cuDW$>$M!61GlQbqr^J9#QrG_oW@W` zvihZMALOWH=L!^<&AxHjvn71ytCR=lVlL*NqYf_lgiyzS?i@@p3(&LIe3y#`-wo<9 zF;C8aRrY95=JcHII~ilTb8m1FI!4;ywaSyw1=WAMaF&U#RJV%94|AaejI|xTAwp=x zf;)$M(#1-2%ZQ3jDeE#=)u)wH81Fk{Ymh03nsiiQ8=X*ch5Gy?RL+>5NHK0CVC^*` zq+1I|yFZ5IME*{!q-x~RqUh;;5orYS5;lMksB@UR!fS1e6I*NdpEsMzUoh*Z+fY_@ z>^XG=d836fGmBYYH^aEw>GAtZflKHl>r>+NdsLe>j zdD*-Te#KRszJSH~x+4u{BuzM!>mR}b9;1*HdmM$>9hE0$hd}0FW$Adcu3H4UP&!Gg zGyWu=f+mA?y{k_cy74p!;KW2CmXQBQ39%b@?q8*!m?uX-%N|se&d5dAYNUTc&vb{K zI?6DhB+2#SlBYg|A(G=leOM`ig@-iK(#3vzrGDfO9VR&86Qgdpj{=F%pzB)-E&h`~ zlyS|)#*yO8pNV$v(FS@8LekpLE<;ZTl=Z?Zq>jEF5UOjg=0|pwq>mx!8fyqg| zn(ezrVM7NY4hO*+{Ti!2k1*t@2K84Re-4Dy?|Mro>p_t@hPmH)2H_URm+Hs4#~iJ1 zUA?{_K7(jUVzx;RPGjYidi^`e?v2yc94!&l%3t%>KN`u4ICYBl-z4-mVf2$OHKtTu zk_;tKvJ^hv4UIURAJ$ia=x$sW%fY3uZm8eo<(&5j4i>ymmqv$9PH0VZP zk9seqwW_GuUM-9!j~UCtq#g3)Ago!!(Zwht8no!CltGE(lrDB zJHE9KR8!>s>mF*w-wK1sL~O56zaNG~+6|)5qY06@4B>@Tu$i;&f3N9QM6=G}HyKv& z_`<2c!!&){bK{CoPxzpHb2iRMHb4$$?Z{G1o4`OeIHWPR7bO-+jU-MQ)7R#Js#!mf zh`pUJ1!^}P35Fo1sKT1LY_bx?KCS3a*a6^{K+O<6@A3hF^*^RDMFKde>o?t$X7 z8V?dAGxo`DO=707UeLWaQBXqqH?()7lCkckxz9!L+l;3;ef#_@sg)68cTInNU(d@d zN-bQ(CD<`N&X!VMBN3lpL5+quF)JKsUsWqm1ZKSi>^UxM+y2coxM&vTxM=%-5esa;!c+(!flQTk z8V?NRz!+xvR0~g)v+0rB_=Z^6_F*GUBTE&k@2?3(oN1;_7H>b#U#&oD+l~49JbK#u z)8a%drm$BQ`o^K7Qg78YfmhU#D3u8jI4mT0IAlrExVWM+aHa)(W>V`ajU8r{6rex8 zWn`rMAD?qSkU+P`gNS&>$9osyfrC6Y;6E!APJ*$lA;ne;GCid7f;EnmrFB^obq zI>QVer8}aVT+iu(^;2Ww^U3D*5S!vrG3jWH!jqL0^$&^`Wc|b~MEI5H7k7(Q-Zh%S z*i9HTB{NJ>Qz!K^*76cY;y^pNf>0-6X;OWIyEP$wM zo_}Pd>|FI0HhTYp?Vv3oMxaDNR;@4xU+}F>tUU5tks33|K&k%MK)I2x?_D(ox~9iS zRIgy0g7=W(oFcIKKh=Uqbnh#oP5(3b;G_~j-+`@h9a+xXn}eiU$DP{;+wc^z-iqX4 z>h`4Bl=G0;-w+~QeDWHzUICfvr36y?j7!bJh_jG`V9~1TR;Vx~X$c2N z`#LNy`h89<(g&dg&~B!gXW`?E4iirl>c7eBFP=E4`%eL|T=1?6QSq+e!X34#oonmJ zv!7y#iv}hMY2>44Q%wlt9u>L{JTBY@Nu)*+Kt_KN)+Ztur*3Gx=?}D68$fsJ{C^tq zWY2+()9$X1C}2E&GblC9WS{M}XlJ^mA;dI#8N|-x(1QPpz<_sD4Zv5h#Kf2C*_+K@l;j3aTa$nXcc){^tg)`GXcE`Ij()gf*{=hIBA0!Hmow_K6qNSGD`9BfducG4IpIW1s?AZ1P_9RIwmo^ht>>x6Mc7Wqe& zf4v6f(@fL%hbQnq`7rRH9YDWm?CvyDQ7XV@agGW+~T?3HXF& z5T*}@SgY3ifra z>E8+e2Fj2WiT>X$cRr|AK*G9?IVHD@qY^#N28kh|4s(WpGX5k`bivUzh{4C#zao{*ezHGOMS>iCb36%0=ztTd10BHm|!OwzSW-O zl&orM$G5chqI9N1V2t=wNih4rOzreX`2h$ zXD7_U`|9zapXamq#rS*yCg&k~RDDa=KB(hOxsl`S6%Fqp;M+;SpQJw1$j0da>|eIP zp#Q6}j{fhoQ;Qx$yfjzZct|Bc%e{a_H}E&xaIr36TDN(HDAdU+9Mhmcv;D^mM~P4C zQs(}*+{p%eHZ?;ADG`y(=iFESDh&Mkl@=D%_hJq)xoT7WPvh(I?*3PibMo3;=9}zS z%EtyJZlkKS^-7YtM+^Eb-S*4F>X4ci8ZSG>_xsK4@hjL$kGL zAl&W}T)s1{g8r?h>(}mh`H#~uuVTTO^+%pkg>0=BlgX5swQ?)scrF~UN9j1Z-_9l< ztdj>W&D~TRnmXwoYj}G#)@CVRsS-?R!2JPq(2LS6{s7QYi&U9^zPJBCr=lAFoi)@T zc#LLoN;~k8wC4d5JIb--Ejjmm4;|6<%8FsLE3zc5TA`~2HJr0nSnpw>+`GDy;St`Z z1U4%wnz-osEj(&GAQ~-32G7ay&MGXfam_~&jK9ll%>iLV{%OWX$928y0&r0S)dr!! z=Wx%AW^D@=v9;NJw#e9@L=!k#SWq0)r`ClSbxD;p=KJh>-vh}ry}Rl=vD|9qrdDmJ znb5f(&=39twq#UV7d>&GqUe)5Nl=h&=qocHaXu7m5VF5MF}NxrR}gXuMnoi?n|6wg z%bDd%t6paMM|$!StdUV3aLaboNW9n~c?D+6fr78JL{D=q8DveqVKo z#rXw}vHMGfIwLNGq2NR7^e7uZnsg(l+v7$XAnN{XH#=-XE_{*`O}p7)w@~!^;|uye zd*%1;I+xpp_F9_04x_h;R>Ie!9$@K|Pn^>IR_7_W7!B0#20*2Z4^rRIO5fIlSB=v` z5%eI2l+j~*R* zuoH;7_lIV4H9Bqu-}W)HoYcH&p^7L+ zRd%%$^aSsHpH#KcOL&-sp!~k=J3+jWNly&AQVYOou)^y;`1f@WnHdl%qZ&c51+r;Y z2EC|T>&{zt6m2#2YCf$5!Ob$4eD=jry1ZX9eLw*#V?Cj&?n*ZJvqt(j15MNhsMiAu z3{P`Cm`7Yk&r(S8w_0oBK?{+BtxMlK);y@%JXEE#9HGR48>|717$EYT>X4B4-N1)- z#+=;DZd7N7c=6}is7Tg!vs_LrwZW)E!JJvZ4rfXFgZXq`GBsiLMrX1*Hc>qL)~IgT z*t)w&X(QCnynL7TnpO2Y!~-sCkZ2DD*o^C(ISK2Y%NyTA@KaXWN>1ItPz`{yheKj4 zPXVN1k)?`Rt{douSpmh;47nR80ULz0LCZnFcUR5Q>DONOvz?j3U-iquV7m|{t zM*wZ?+m6tdS61hvZy)}nBH0nJq2?NoSfbMUk7{E?;+9aU8xfgCtRwVAW)JxH3MUXXS-rpd#(F)bLQBplxux%1; z<5nk^d6?E1i-PhMZQn|B*8LlO^*m_|x99k?EhHfYtB_2{g&ZAnn6rOPc5qqY+X6Gh= z;3pV-blC9)Hh-{}}kCK=euB{}9v$~n$dPeQb523xgNlvaGjVgHG zy+kGGvBKyem!Q~cb02;Mv&;gHq}nEctx17BXOQQ;a8^AIqvr%4sTTEEEFB|596$s% zf3Dke#PQI(q}sk45dhQjMpA9rlEQw(HOxa1I?e!8#&(3NXsfwFmqPjtLt=#jGSpajf^*hk7F7Ey%-j~( z0PPB-F=nchnPy;MNwcakKHPpm7n1mnghs24*e0f=FpLt2F6IoEf;1R(q{v!<;CP`- zI7dnb$M+dHSb?Q%POwriE~;jN`>4P8+n*;RRcMGczqc-A>V9-UUdO9`r9%_t25(tX(ZWYG;L=rQ%me<$chI3sA6A-S%R=-3K`2v+*X2ZH ztS>z*fEsMzEHm?HU5o!9xzf?mwnk^gUZZH%b(hmOA&ZvM)khp{suvOJe5kUuCF*&v zZ@mhq$%*VJynbMvZVE84{yLQGvT~viu0gaaMB_%gyIyJO@tOO1in3t<1OzMcf;wMfnXSFn6Ysw+6v{IvMFf_>b2Inb=AX=b7~*v);}A zsLMv8Mo$+2+xhlp#uL9zZeG9l^6|r_YFy~e3L@a>bmAL|kAGs&vT+Hh>q1R@1- z5?K)STX1R1eL2r3nFoWj1#Uay5gydI+)rS#QNoDWfjbEk%VDTtVjY{h$4tky5KTT| zWod}*xTdOKZ&-)?RNyEZh<=<9zr3Kr$99rGR3@ywfIEe{USB{L{^zPm?~G30=wRFlm0Fa!I^%L2SSzrd~!ZAPNP4FK#U` zR4M3v85L?}Ja8$~?ji(kbIGJ@R0XBpYHu)3Mm!!l&fE$*Q>lAtaz>4!VN5rJuTVq$ zq?*0#UW+jM@Nr&~RTPCigiqQC48It;I~mlz&a=8*!1DbB*98T;YvPD$TW5SiDUEqi zMw|2Ya(7ny4z0ukuulq((qhnbeg~TWspK6P?fTaHmW(;Df+?GDj#2(5EA=N8qQEL8 zMnBl1B?=vJrYJrjKwOpNz_lR zOvud)nkDC{21Oek9W{=hz!1ft%v9XX!M2t*(WwBa_TnyiwJ%|L%(gHFF_MgzNHE}l_ z!Wh?&YpD2){vW2^F+37(Y1{5-!U-leC$??dHYc`iPCT)lOeUOkY}@SEw)y71_ukL< z{qG-rbgx=h)m7)J`XOG6|BTs4?lb<=hv#{wmO>PYZ#VGVEWzg37?6jj9L*pTc(^DV z%!(!zo;`0}sZS<+^P69(V-6)EYed`KL-#uJ_uI}!)tTn60OVpyVGcXoy^hu@x+e@M z54ub1#VMC(PT6HG-Hs#42aR&6s=sBg5OS;#SzqQgxRn&{KW+=1Qtap-VmexQnr-8p z+*;orjuVy|07T7wd(C9XSdDy1N}8OjC$xG|wg9^cfi3qgVBfhj(60sjQw^|?ss0I- z)loE@e|5YUazT?(Hpp=9{1@rYK<(V-9)}42fP%$3f39}C02uQ_MYJ=bESRRh74a#$ zCg$y4S9ZhG+|1JhH6p0&-Fxk4*~*X4o*EjBYdR(qGx@nZ7X%|z*&}(-h;*-#f$z%< zs{WHh^lsIG$Mt;JUI4+%%WxrVG>3B7fiT&4=$PdS>Ot9=Z+tt;3H|UlRd^v#ko;=G z)>LWNm&TG<%8rlH2tLJ@T<3M-!aGP9y%;7)z?f|V?c&Hpz3|rERW=R3AzBFKH+9lu zRwFFlLTc7?F@15guPb&(SbdFtJiSB1PxUJ#DI2$;1FtPGzY~&oHKoJS_x38TCXOE# zuz)p^S46qC=@uj-C93E@kuDB5UTY+javaL0el={zr8JSpChpC|&{9mLoTGzKkS9>h zRBg2fj@Y^f9D|5zzh?i&G}(*^O_QsSZ>wxGUHK?P*}u*FXh9;)ucl_*NsTS+NqeuJ zbs_bZ7gOpTJDZk^gGW27gqh?Y-D7szVU-gf9f`OZ$Z*F1hupWpRdIWjI#PD@9ef^z zl%#G*WWp?JAEs!cSY$FR;9~!2*e0?BWZmHX^p;{i7Bng0!FfVr>xW3uE|kJB7bozzLQ_O(7HW+63;Ui4A2>FvsnJU^|&n=gleL%Zsp!HxgPDd zwQ3&+7{6ZFIFH^7NCvOHn@+POCyyUY zPd@^6upWX&e>tp}WZFZAn2&(}JaDJbNfA+NC&sGqB426VQx+`euK4l1J+Yf%K(slB z-b#_%SorZaZnx|ji$E?=K>6iJy5V)%NwZG$&y(KV>dKy4asBIYWw_VfLo7?oE-C-2 zqKfXR99Jw9h=k5ow}ai#!V1o-N|W!|ia$kie>|6j)<*hxfw%9}G1$q^O4W1i!`X|M zjKYHURvpSBi%)f;xMSF0CEy+z3sA00|<<>!|ls9kT*j<=Db;aP#5n5F%DVdwcwuu_cNimkRWBdr*|j)*e%)>+gAZ$iP? z9u`fUDpZMnLP}Y&`HnazE!KsdO*0PobOIwhWzeJ0zygEgQJ&wABEj$;0;0=3|{xtks(@0*PTqf(3WnlOlcV)w5!xL+~zlE;@~ zoaO03t_Z-y$Pu`H|yem_((Xdk#v>e&=dNbkpZ7vY_uDQ z|Gcimih{1!8q_d~OGkZ7CM=^XH@5H8@kqTZfTmtL@+?_nalUFQ`^2a$)Tm3H^-oOn z2EjN7M)R(Il&TE~0YrIyVac2|94mFmpydzj`1(tya`zEztkK(~-PILP&ygy1G@^lQ zL?l`$*L2yB8%|*a426n=uc4qT?Ed&=}mYGX8I5g zOo_oH5|JSg|0w01+wQ`l%Gpq(G%=7Jsjc&HKdDb7>O_H)>xd_79r0p5n?$qjXK{2~ zmXG@`3AB~bi=@*~vcmY~Rhm;}ITSl+v~a-k6W16cnQ-5kqNG+}$cY1;wv<`m2sJ@xqOShA`4g)M!Tw7w(HR)<}iU2#M|2 zdb6(Z@{Xw4+krW2OYPU-U|ZKr{XaXUqO{o7JKi)9GF5uC&!F%lfBu$P`{V%UnNF}t zYiQoP#{;D4kfp}MP<5C~OsycG@7~lU%t)qzyhSn3)%r8O=|gi!K^@cX$YhG>58g3N z=&H$e7!#mMY8*eS6O{5be9(G3KKN@9wUj-09w3}tp$^YUwz=^cO9{cv_Qc>OPr`xj ztJg8X$)Uj$68V!ogUTRpCrj%ySYsL8*GHTj^O&CX95r|2GF}6HRx; z*t=`vxS>L=%Mv=ThcQU?ycRg_TIbvN2w`F|Xo711KeD&s(k%W5RV?$bgwA{4pvE?# zKg9*o&^bu@S1p4}>7wosKy5_2M0Y+_^TYXvR)gI~NdI&P`UQ#NYqYWZFenQVW_C|i z2o3u^d+q3#wY2Upik7wfg&!q55gz1DFO(a7*f7KQicjK+e3(B3{i=9*&#QIWK`%gj zOc)RD?ct@!_+?iy>CJ(2mj{8StlsZx)h?&HS9vjWd?U>~vrb@!s#TY4`Z-A7mVtj- z*T`}@+RLz`V0QNjklc<>oa-T9RYKI?RkyU#k^wP@l^LCF0)iwz*~O@Wg*k1O_c5+=;x zu3tt=?SW|wl@AW4TpWgOvGj*`=WkBY<%qId{_#f*xC?pjq+wapn8;TQhw}gv@yNbB zyE6Gi#qau%b;BsTkLMhO{J}w=6^e6LhT>o5h_$%Ux)PKiNEqUJGCYa*0$Y*5hH@z@ zZs1X}EcvxJCG;c%%I}<$@haVkvdfCnrY&QX76jsXzI=u?*Hl0V1x*yL-IuN%tg64n zL zUeyPJtt<^&vVB=C1=j=7*xwmv+K(1}PQ22+UmMoS@8Ba%2n^72E`N%)-Gz@a#WCEg z=GsM2W=fqm;R*ggN0)2koLTe<&!hZ6h}uatMxdM=$vn4NP*`1V!2f6w3@9^BwjcA< zny)5nUC0^_bTykUG5|^}u6;p?ZQX48ZN_zcEPM7UA&KZ~pd`%>q*&;nAYJ_GSA2X` zHh&L4IPn?-aT0>cmB2X>*=@MTrKwo=mH-q3Fp;8V_@K)_Q1a-* zFZ0F`?z_f5QOakMAxh4QF;25nVFlh-wm}H*KGu9p*3nwSk~*hZmdX!;uVc{XT%UC0 zsQLE_ei9VJbXBM@K9K8W^L&l{pqzq;nvOfQbSfRg? zeabmvbcNgO<86H6mu57v_}Z`5sgwvD3I2}WQQ_I{bq0shR0Se?ZuY!<0_;EIFo$xo>V{hq1q}uQ6p*9SmjIj%k?PnsyK+9_g<#{27Q`x%2Sx1em_h(fCu@Ept>Q15iEyUt;J+I#c1A|E7K_a!n4t$CkjjV}rk^T4`Mr|w% z^zi}Hg2BQDVP}GHYZQd}7l3NOqfHmzy|Ne>JMp)13yY`pjfcicUofUA9n{wudzU(A zVTF%aGrRI#j_-jh9Lhrl!yYG%F*Y{D0hAC}uFaw!JvH{LGZTlOd2+(Si_8Gs_FwT9 zj_h84r>FLOT58OQJ z`T``AVy)An$c9+Zqr)^2pCnLWZe~hCX~-FiS_G)RGjY$kupCIa|J@_tKu-c5H77yh;qVumN^My|d_Blvn+rr7%i0~?cTPY77}ZC5xqtXG@$ zBAA>2x%G8i&$IaW>+MOx#1U-!%O@v2m?qA>ZB1X3J%-&TU}%&Sw64RXOem^)+MCEn zUL@Bv#OO63Ua!~4A}Q^0^DQN{6=H{>2b|1zjb>vK!ny;sfT7{Nm3-R2vDJs=FUuoq}fq^5x37Q5pg)XLZ<@Wo#>!dSRD_!W((W_; z7Dj#p9vL|n0%@|374R*DF*F|WWIKB@<8kZnBvw!ivm{YZU9Tu5%h=!nC8b;~dbbqX zpK_GM;8e%m4H$fR%y8l5pbosYXQ#Zb{%&rhFRuQzHHJdfbBhO*8T*dKv!eB?c~e@e2BGRhKRsGOPQs3e3sw8$yEAYpJ%me!BWW2x;Z>L59Kco{;+&1jXOT} zQmu$f^I8RbKym=GITk4oag&YT6{>*7SfeMZu-KpyvT%A9frH_2qYBFcl8JnqFsZD1 z@{~HjCm`2$Psi65w>}0^j)BS5Z#{K!r=^TCgD0sQM=3%SWkU}PP!Iqf$?e9VDpp&M zWwIe3!^aIB`8vi1kjzUKlqUqtZGA@D2CR{>diOW-Z%*;uio!+M$TS*|d4jcN*)L#N z@X3bJa8w=rtK@jB=B7Vd{>Zv*D$~q@-2^{E(&QgQr9qwHJE90-(`CU9L)&aHYd7*x`l z-oNTil*EQOzu4z)py%~B*jQSU@FWJfzH=)4bJ>61_~(wp_@AEBkmkjjs}LZIF2^ZK zrrvfC2|&XPxQc|7UKJ9JP|D_ULjI|#m`%uSAjm3COo(YvMhjz`FoPy6U+F;L*y#)U zJjq$l%r=EB8#vCFXn2r|r6!D>x>=dv%H&B(Jx0`-CB!-f6=lhhDgB~(f`QOiM72zV z^azSW@yX6v;VXr2wZ9{{SB;sX0R1t@HX`7}e_a!}G=oagz{w|#rH?9)+#qh!EtD7H zEjr=yyf&WQ^IQa=D;0R?=o%Z+GX_a5TlgJ3v7!GkQE~;99qe{9P{0cZdDrwlvS>GV z!ji**8~|vfW1IjSK+CyafJ~xI4@;>J&7%`iO~AI9^Gjmg+0P%w%5#q@(lJ}z00g5t zS^SQl*u>$>z#z^aqY!PpI%Kd(Sb4EUnp~F_jI@u;i9_AkrNjoY$WV?`LyfUJMUtK= z8I9wXQOcrt zUMez2ZRp2m;?8TQ-EQP{@(|ttCWjZEkKw&;|8ptw*9+buLlB7@FPaEJmK0+Kk}-^M z3S5wEAp&ZZif%5a!VUAp4TAztX&|)iweQUZ*M}!zt3uq`9<5YZ?^FND_2?4WKDkrN zkN8E zG&9oMX&hRfmX58um$kN5g=Y@1rO4wo=0PEa%hgni*ul908a> z|BKFZe9EyeK3h(k0qL{6 z00&}aG+b_e%x#lB{~afAL08uCp{rPb*;xtboVyu(#?CK$5Y~U^5`)NxpQP~wZyX)g zuW30Zr*5wH_EHxES6?TGJp(%1bUO(389w|8e3;}=ZmzCt#mzDu@M~IBNA4ul>LE=M zv@nEOyF}^z=?$uAo@BjaQ-ipWYnYf&$@Dp{5{SNZ&hr_>LgR#uq=V9Za@%C}7ZgbQ zAx9tEPns#9IuF6x{?~KbWpn(u=X8;sAB=gEMVICBE&k;t;TR3VQvv1V?LnpE@%z(4 z{cAl6ed_4HZKSblG+>~A-Q#B(>+o+Hi|L=@UxJ1Rw#19O5QafJ6{0!Tjn(Qv9SVh* z_=vI8<~-JTbB%zqaV-@Q>S3Pn3=MR@aH`mexVp)H`jZnflx$J@n4+3P<>W=%voYY> z90gO1O#&liv0TRv7a$5~*l&6L9}AEs0mk`@XY9#8mkI*0U;wG;{2<4CC!ns=Al{fs zg3Pmq9i{-iUxWoa6jqpGmQ~>HiSp;*8iFuBMH0@wy|6Q2WaIlj(PG^#9_2Ps7DW52 z_Xq&;Chvk2;D#B1W;*bqG*8)Ji|W)q=L(HVa^Mmh%q$;md9NZLPFtE@@FT{;1JI;- zFw74B7-;NO7!47=rZ;RSJ2xld;7<8_4BM1i}?@;b+_?87jxLh*7kRE^;@=aX`8d{{V1f4s4A{GCh zo_2=SUVUizF0Wj}NQo5#Oph-mziFs;F-be}2t*N8?I@o@qj?|$1*3LJrkbHVMMd>H zVYabi-Z5^?AVrTKGz|XnjT^Uu+g6FX_TANJ!vHSA08>Z|m%`a(t3G^v(1b7|38&$< zVIa@)>M;JJrAP-KI}%>gY;C6<;pK3 z7M&w~A;2`LF{IN6^enML1Dy^6Pd+!5GT$=hI1I%J3(sr zZe=e-nq^Ox&n!7vRf`3p(mXaMJl>HA;_=B2;EI1qK(hC*IeTJ@h-3P=3uguEs_$uy z%gxQ*Hw&L(QnaAVn;eIjKy9$&&K9%Pwb6}92%w&=r&va_dr3M(p>-RXBEWe4#&)! zgpTBfW~ZBdo{&Teb{qVrWp4BXaU^wR+ z(lF2e+y|g7ti-^r2_wHNkb8fERuF?^Gm-(`;|IJBML5M!PzM4h8uX~eCxl^${RoC5 zK`@2zG#tqOv3o}K@gdPIy=;^?71cLZADQQO$dmr8*91~Du7jcg8Yp_aiM3FHG>HIRN6}sn!o0;sAB{?PT za5e|>1NPQIo1Z;4pS$@bYpMNBAukehk9X7A!hLpY(qcV*+ea_jBDv&$b5|76`I`~rDNWjX(B_vJ|w z_l4%b2SQ|zH*ul6XBAJA1J0QR6J`Un%1w5#{~wJOFt9X8vi;D;)+&aZI4Vum5HjHB zyXg4A7H+HB`2*rHXu7_YrIX^oFG6IpeB&-?G6!!y-yc^h_7qc=)|>30e&&sDd2r3F z@#W%WCR0JX`o74-8^@tzwZp~A{_L!r|H6XPtGUt9@0-B={aWDW@cONbfys*AfHzGi zN6y*B>k{T<_;u2Uzk$nzLg!1tli+QwfQt`?8Jx58g)ZIL_?G2rH#5h7x;&R?#fmLm zf#A@HbchI`G6L<>Ct2}Uj!ou7{DsL7rCi5CsI`w6Y~3Fn|9l7zkJ4ciemwBg-Uw%} z$zdw|*7l%J*x750Wc)_L?dToVbWY>-9J;U+9MPl@72~ho@|RVTg#@n{D#zC<5r>pa83H9LQb1Y?0 zX9LC-6f`zupA^jB7Z&)*QE4psEp9iLvZqm_D5i|GhtN^V!b>TspR* z1hzVN13kp9MND$eUf7`;z$z`jGlCUk*Q9+~;ZG3Fa{jjfmQ!q5&6@wl2=8Zhe?cxG z9vMdE?02r{b)aaK-nAajd|uCPm(_fpWV0vX;yn`i`7FZEW6-e;a(&a%Ez2?gds-(kRh$?|uPRAig5@H&3}4TZWIeEI)=O6U}b&ubWo z%TSNU_k6mGycz;wzoClO_jd>XjHFsknjlWjJg3P-xPruP?!e=a7$+g9Ky%q+%m-=; zmz|?iTi`P0%ZJZyPrL$+@t6JaoB8_;M=6IlkKU*XXlQ!*6VnD24*EgF)izJL-bsPB z@??YU`jh?XteXl>mzfsVLEG2Mpg2B$p8C#%Nq$Q_=ppR)zm?Cg$Mb9x%R?|ab@M0f zjbPEl)6%qzv@b5Zl9Q8J+;=hGn?Xht*QHWn>u4FIO)Zw^4FM>&-Zv)}NUK?|ku6C( zj%R+wAYL&RR9mI$iP?etpZPjQQ?0dz|Hi3#Q7g{kxWc>jFeF z(MQO!Nk@O~OvIq~2=M;ajzpHzthLk6%-3u5E;+H9=nDxl?4q4E+ld`u!T7u_s^oX1 z$C!y20=R3x^Ly9EOr^aIS1@Bq!)IW~z5V5KSBh?6hKv;hs2j#q0p%2bGCskgZz3Ym zJzpKq%l!fP5Y5X<=~F9T(&5%MnMnKFY&hJKwyycKT%5iSrAxlZP*qxI)q{a=Eu_n- zTugLM;?yq^m}DGvVp{Lb26s<`fUfP{v1d!A%n^{`wf;ah4n0*S#~mLr#*f64hzJdP ze8Q#Bw zt?9G9XoTE%fDNP~U#G3wd}hOJBYoc-PN(Ny8sVbCif3cNmc(M4&O5C?Vu{fR&BIbx zvp=YUV*`)RY{E%4I~=)Bz!6y%1=@fZZUPD0WKa;sV7YGQ!x>Ow)}6Y}P(Dg-Zx}GX zgtS*X$)3#*b7d&W-iSajhBbJr$(xbm1_%P3+ee&mQ!HC++}h+3M~dV0ctU z^;qP;5PdZ^om~dX`;9T8!-o1>Ti<_F85cMT>o?*XZcs5@;d0@^UMBq#V-r}J?qpN1 z3@ydPT(n(Zf4P6SrR9ZZJal68bO4RC&^)2~&;MjtBLf2_0B+f&1Rv}*l^QCIM(%N8 zAq;kah=NDQ|I&UpHThd-k-*V5D5V7$l$TQR*Hm_n7t)5GAd)}hTTV53tZMB>y=o-p z-*wbO{orCHUrxrx?mXA_*jM(h2L;SWJ$Gbej>0dH4)i5Cllj9d&k3c4>0=uHCo_iC z{|k?j5))kIv6Tqo6%y+0!uYa_(esh{;tZUuc0MnW7-^A#PYo#Rji8XCF_yH5yng;)T%TddjJe zQ2Y>Jz+qXrNS#TL1;y2+cv|XN>fJ5m{BakIDuG*aO&4+f#dw04O6|i}rrtS{y;y_8 zTS<}0EhkE(sx$LQ6M|=ZWd-D~B@0-Y;zpQu$q+u>BqlXO2uE0@> zZ@%be1!)O^+l2Kv4MS4e%~zYP4Q4e(+r;+pmR(ekc=jufX3J0TF3a{L3o|4(hfgnB zu&41S9oK8MW0v~YFerQOV!JrIb)uaqB(=EN9LWJfjM@V{t$~EU1pZy$iVU87JKXCN zme>%8ia%MazPir`wo(GbaNAsUW%s4^!{6IhC+f5md>{EQ0!$2MZ?9XCBdS@G+4Yw< z2$P=w*kz};o3wiOxM@pEGV|LvPvGQ(iOQkGQdg(Ku+0MBM84X&In1RycE&dY2B zC=b-Umu0y1(@>&bd@x-AVoGfl&IYJ!soxT(QZhh-Yvs-#(bYv==;$!NR7~44@O>pt zqfvB2BJaSp{q72z=b){gj0tAmY?`@xurQ^O>Fa7Ad$jx)4CPsYy5$^NQ~=Ycxq6Px zTuH*%x3sa%*rl@mK=>0Yjm)V-wesobS#W3XY->Gl>Dgi;!w{ge9~~Vn{o{y->Xi93 z#zGYx>zi8JgD4ZL4 z@xLG0BTuWev#PUUM9eo4FW*w^E{C1WE-9w!NGqZUc+em#g+ z(JAQ^xYHdkuN4EK~Cs&yl8U`k~)J4dBeOs&(Ywx=WBv!6VIZT{4^A$@j zK5K4@FY~>f*lZg|*JU%%qwYEznL13jSz6mlKyt`Kx6G}FZ$$c)C+!bq$eoJYguZ9M z_wiJvsCi}AV-lTpuGom!N!yThLhUNvC5NvEj{n6M3Hq>DEN8ipXEq=k%4eCcfrYmo zr%dAp{DFt+NtR;oB~KIzis;|zs4$M!JIqPJt8T>ltJuF<*d(|tY56XAPrjJm(L=HG zZaTU*~a4dDU{iJ2^G&b zy69#*jrrJCvN~r_WZ$s9HhRvx8H{V&pK4E&W)2@+)i}Kv#@Hs2*@;q=&oR|cJpbkS z{{Ji?&EC=N0I+(aO8PiRsXLN^Cl;zvVqpyoW;~+eUZI1Bj53bu=BcRC=#cGfxQftQ z^G*)C$&#(>JH1Dg`Zwcr%iX7qH4iAl<2^1v0-1m0PPmgr0v!88+e&1^86O4}HM zf{`Adr%ogoQ<^g~CiKFd=<3;2;aPV4?(n2aY4A@)@=tI%I5-(Dooo`rR4QCMEl*}@ zz}9E%7WhsNG%erI%+@NvVXFBKhZ0#u+xW96vRbJZR9~c$ha6>e8NtIY>rWTEZ@`xG zK`=Rn(?+^$gbgw*FVsJ2Zkv7>xL$1yo=R4|!7=Ulujns&5t z=;VFl1mIg#{W92Lh-JIeGQ0LsMs0Lt}AD~nHdjJ*2}NbnC2WDC9wJEoo5 zs4yQ+68;CE);Ot82&>^*s#B53akbuxQz1^GCaTLho!}6skt=VS=7^RFGCCFnvHnSZ&t20|INGvt0o>}^zX=jBo zvK!4|X1=~ZxHRUMaJk}sA+SV8r44uA?GB$%Gf%A?iv{kyIPR+kPOmr&#}FR3k8f~f zohs0P&&qGV8Pd)!9Ci|@=iMTp=0&Bc2z<7H?b+`u_4a?wcS?T0i@OGTgVft&R;zwT zYYyM=rQ4nn7vD+!q}S) zqtp7w{3Ot~4k|`HL&-V9{Zt{z1q{s%msRWlv&nMG3`Q9x)&Iyca=Vh{VPf9Gsn>5rY^LNuEJVxXoNQh^p2;| zfB2D5xRrvBM@wv9E1S%FXwgv9c36!-<6v@Q3wG&})C0;BAI)Y!;bxV>F@&*-7I z`dP3vgeUfWFAS0!G`1lgU`?D?4$Gn+r!^oL2Y0CFbI|A^zTr}J{fI|#Fv|PCQZskx zzsggU%lranh}v3ZwZe78>L!&~FmM{1;n8+hoTqy&*V*YazQ3N*X{qSBZkTQm$}1*7 z9$4W&JRR$Z1r2h|qMbo~8_X2_&?|3!rtwX;`Cj-IO)c_s(6w*Hoa^sLRe|Bzbkxt; zWa=rQa<6TS)*0WkkDtV}^ll0*A<;F3{{1Y-7;m+GIyZHeZtS8l_Y@=TDkU&M@lu{; zds1e1BhvP6!{27h`C42tB*_ipYY*T+OnzM~0$~ZtpQ+3Q>2{hi0xpbi#f9p z8-|VPjd$)+Jy+q{h=wg5t=?DZ^o7USoH1`2MuZ%t+SI>BwAv|V zre<1;20lvch)f4Jq+KfrE7Gtio3S}+AAdj#YDTWoW}WTt%$eG=;5o+z(0w!aRgcgd zEV%Y`h_Y=BH0|%;g6X`}jCY6oai=sY|o7w_pyJt(7Hd}lC9qL9MjwigC>LQ@*o zNy#}`kpX%dr#Vhrz@@0cwxBk$L+p<-vSm}5hhudi=iI78bcT}dfrSylejAw4mQNT~ z0nGgQRC(jJT3H};dSYJyTw=W^{_hO=bgM|9lElc|FJe()E2?JSp`SDC~vO~oo-u;(sjq;ki7?p z?&(Iy99YJk1`~716p24)Qq?UIK`R0yL4}0F=Cj%sPB#^wn5>T${bc)US1|AY2NrU# za;88S39m%IB)(C6S=%8vk*w6jSC{F?aWNK{1;bXs&bVJ&&-ugB^m89fblBkt*&GUf z>FcU^sc|iN#UR5z7$AZ%X=Y9;T9W~z_;yyo;#LU6J`=}|Ai;;Ewnm5;2WJNVX|Mh3 z4j}3ye!gtu-3@TP!&rfimR-g>HF+41cZI9f)r=NVs3`7yCq-r2RSH5 zqYO+tO@?R{z(`rx?EFD1_}Ra5sAHpZd3!v55t4kZ@{)iGF1{P}ZSn7N$=t<79I%6! z_`+3e>lm7Jct}Yecrq!beAk%B>eLn?)I`Z?$<&Q;25L6P4hTGvX)iPwfjCiq*e$Ps zb5%n7gA^#W0G{_XF8!Om30jDlRI6ih^*OsR+FZ6x2NVg~gXeKox4QI+w7mgFqR58( zC*gR|m0=N@sQw`fn)b(|$G&5sFlsI^KQLiWIcNCFq4(`b@2N>}=@L1m}=Y$I1q)wSI7dhG6TCl!o~C*Rjph1d)+-hY9n4j*-kA_MIP0do@QN?Vv*S_}~dO%ewl^W%uhKL%C9$58aFeo{Slei6#G z^(2h%QL-f#!hNUOB*sU9+uJ2Nt=@}oI3o4Dm2zB-4DHYABU2N{mrbKd!xON+;v1hE zLg=WdvAut%Vc^L@_+(nI>`*N}eRq)yuYOk=F>G`u`6OM@)OuOJ*7DRp<3qJpeJwK~ zJR3dboH0tkt%xp>y^yu#L}evi#uGG0^LE%Npj~5EXf)9<8ov>!(0-QYZt?7; z^dGNClyB)Dm_Q(k1$+bn-1?*^K>3wV%UoMwgy5^uz65(iDOzf5c6NlTp9IBn z(&(GnfeFjhe1ctv4#c&y(MR74cM>kWXjd6RYf1K^iEXCL_~A!N3aejwT&6`OS50gN zpFuv**|@B1-v&a26x6xX2oS0-`?1}h(6P@0DfVQch}W6C7-_1$0f`dh>99s<%5`j| zaQm7B;?_d6J(jfd&=5KocupSqkipSmC`y*{`{U-QVD9UzneCEBWZI@zZp+|3-*q9B z_xNObFrZ<&+d5Faz>8d*U7uyML8t4EIz(@w{;=)v*j!#57<80=ZV%IKngB#$n*+T`956VEVbp3P)|O~4A6(k^RCX; zzbQ#7)s1bqd?rJnVFa=2(MCF{b%B@QyNCM+TTT(dN=~c{A4>vFmd)l8Zrvwk@>vd` z#)VhK*es_M+IJPDwBA5hC6sag8w5r$n7U)C(du1HQMJd4eIcxK7hW_AlQR|6^h4VK zC5>2eEiLVR+aa^QCoXZsN;;0(#P)0?MoDR+UOu$?Y?3Zq>fmsL7PjkiQadG2&Rv4e zm>EeXLT5*ZS6R$&CVI7JAKAs_DptWs9jWqz0}{6rJRZ1hwy>k4Yk^moR0Tia$XQhg zB#^Wy`2dv)z|W4VeJU?mfUECLB?frcV_O?l2n`!8?-2^c2E7!r*o@9WI&1pq5OLIV zK2e)c#MN4!?p-#gOB5GTzoajW(3x50z~|CKr@fj-?)>EsL@KsYxUDX)`Keo*1*te} z2xgg-CR@heT$-7OCn1Ti9zX3*g+-D?#bIMy?Vf;A%Bj1z$x5j*w+sj`19WFGRZmdg zEZaLxR|`6ZVo9agy`V3mN&adV4Z)*N-Wk?^e*b6v#PmgAVI?$p1>glPekbqb^8<_XMIRS z)dxJyT=}#F_;sJMjq505g=Ioh%{KMLXX$SVXN@YE?7J?o+ib;RzXTQyEsNUyM^o`} zno&vbx|J1%_4LZ|t*Hr3#IfgRRu0FS+Lprm6i&fbW8)< zNsIv3&*!6M!)iZD|Ic6(bue7w-^_=pL;*yHVzaO(bIP}L&sDVk<~1GwqKCxC+UoRu zk$v(iJlUYSiQy*5zeXG+H4LGXu8bMmzEX~naUWv-i!agDXHB}GKwK=jy=+%S9wx*p z?OlO4kFG}MPJO^?9LPOnC@f9Bs64PCeh;A}zdl%FpcfyOnLd`Q<2EXm=CBi@5 z(+Oc>cRGOdT;QpW=aFUm9}BPm;eW%K^#y0YH#jjC8^6@#9(4H$AL zMKQ54#G|KvTz z*>DJaKm=l=knV;o@ktaDU|M_g=i>3%2eO)=@rLMcELyh zR7QtseQ53;`Pk9jHk77^&|ncbRZ;-}+JR4?DFw75D)(?w!9W_B^-#cx+=d1C3lRuc zWq`N3LJ;NYmhLZ*r@=ma5=Z%M2!h1t01|q!Eh`xXUyN9sywKdr%Enia0sy!f?QbrZ zmz(9K|J*>(#qAygkbZS@i(foxL)?W{39GMC^g+&S;BudV>)T za?Oqs8B7vLr5>GMLVk+;|ENAij8{)=wq&+Rd%kJ;Z%KpTb4 zjDMJeI5DxtK)F77@snT#F~gw3{5ubCydtD*R9-_$sW2Agx#5|z7-yieq$y=*7OkKj zcqbX7{mJ#&*D*Fb0jPl!*dW#WlKpKFULc)ZfGrn<}vzszEy3K$;y!p=o zrWE2S9?lh5fkL5qc*VItjz0SFe_O^-h-h&QQt?sORe@ie}HcdDHd zaT81KK6i~sLJXczv`b*0)~{)r7;;K{QK}5UAY~+!q|k)@P6vuIlS~sR?aQ6oJ~KJp zeC6bv!oW+1E9ihpS}@|p9kx8!`6ecQ!;11P1W@F;pQ)9Cd3FDU$|};0 z1FcUrT>JQEGjW$C1>dVN2+8~|MY{CR1noVxVKPDNeC);soB5O8%7%meP7%)g(IoHC zJ`4ds#Jg_k4QBUbWCZkqZ3d<#Cj;An8NL%b=};TlI5Bm$*F52G}-ksqrYqiG< zt7?3zNb|R%ar}bFLVMq<<;?l|kMoJ@27N`#MQF>Kn%;?@fxn)bMQ+35`#fn2owKB zNLiSGi=BR|r4Ww*XXj=B);@B$LkHmjGHN;k6KD0V5{yrrc)j|& zWY#ku5QeJwXnedJze#ejrvYWIz9?h zY$0VVX^5B+;D9v^4uRYVT~X=)VdD26Mak0iF=XiObWDCJ=r6vZuDz}5TbxU-c%jVZ zEkqu|`P4>OHOz^wy+&KvCRj!l6*W*VyQM#Azd>Dqg_+cFQ`@elrSNd~Fh2s^f`k_9 z6J)<|5$h#hpw1OrqEIAF^R`xG7&20Z?o6>?mOFG&hmdroTtGkWdYfo*S!oHuUC`Wv zY_*cRpAsT!3YB{CuswnVv#2(z@AqZkUW%&~%F5AF1MK~r$OtjJ*aZ@doFdrt8rz34 z9?|5@yDo_IZ$7SlcT=&$>rdo>@r5(bcPOE?@fNd=%;4K_&k8W)-;=OmPrGYtnZTHe zPN=?+U>}dZPzkV$0DxOsIZ zu9i)vVWT@d_DZ>bSd>ED;}#$=GLF9y!mJXwoP2;d4r^k##XP2M!+?K_6|XlQP2rD@ zH}?)}mqv~s+g-|~U!;7FgpB{^Kiwl$y!LeM6 zVd+9`e)<#t6Vu@@nvqhW+^;vEz^-ZPT6IRv~SqSzR{y~dZ?GqVbS64D4F1_Qz*dj8(N z{ttRxW*FfJ*fZ3+cT|`(7)$e)AhO}AYfy^<3kn2VoTi*$_T zgM3pIV;M!X>o+Ezc&#*nJX zS65o*(TFAXyY!?9S8N#knZG{O8N9901Rt~uDciW5qrnwvJ-E@^C(zpC3QEgL-@9ff zX3o3i3>R{XM|9jO@ZbFO0laJ@1rBViO}Vl!u&#^&wtFo~`|q#2@a6*^XKE_!&Cq3c z24nf%)7@~kUwJv$zePG2B2LbKQFD|JcNTr zi=>Tgcs1*{!4uoD=@$!2&qORK~6s$jYeGk^VE zI|b3u*Zz%`7fl0T7M_^8dE=~RgFEmnGuJ6H)i=~`!ehsf+xA|t#j+vWQ+|%`d^}39 z>*bL<|6@-`t{r<4$|+ug~Br`EaM%}h(O56RR612z1#CJ#&v_+?^$ z;vMBv8n)EGc4sR0b=0|f04QUZbQ)F5A_D8L@fSC#=d&<0F{G|@?T)n=>IYL#{aNwe znreO-z&M5fH?Pv9o4@~x%Yew=b)m|A|LA^s5WNKlWpaBnMesdnv))!nUp{(Mu#uI1 zO{h_HNmd?jD$LZ>?UjF24L2FWmyH6QSV?9b&;F;H&6!wI`ODg)o5Zv7^}B_x)O9HY zEZTKjtfytY_QUUTfTtSz*XC=HD>d1ycidE&l4~Yv)v=jLFUx%|x6|L+eOPWV6Z8Aw z7|&C+03(+i-R_}jG}|6;kXo_7Cgbd&)Zb#!yBrhDFl7<0xBYcqq^M!A84Y;gTqUxA z?~kX)bLiUW(Qm%&LSyiqJbVdc5HVxDH0$j8%2bs6XT7Mlb9X7hz^XHyD0&Phnt-ANLkH()FB z1dt-!ihG{<1di=7^aYxJ;!k1-oFCu7{SZ@aCoe^FzevLCDpB2I=d-0W&C{5{HYF@Ii)v_{)(5xOf-7fauuEYhN9*Z?RBcTIp9LLA8reOg z=^?S$x2GV)Ch|qUA%7OF%O7M5xDX7{AGLYw?A+h{+F;X#+bd z#!BoRjP?AMYGY%gt7kgxvRU45Bc{Un$oB!ZXVzQ2fg1$2|14{!)c4H(Xgp^|rR4h{ zC;zExFk6iBuO)Fomdj=)z2Ebl7Nb|rX>4}5ar0zd`Q~G+x!63*GBoapuGw$QyC)yau0iYj0c^}1k`rjg1 zdWSG+@w$+3R>ZX|s0WMZ3bfXp)G3Udiwb@9Z^L)H@|a(~tMbhPItt^PbYiCAevcWU;=RPgyP6bejCvLe#iK(o#}mSQRH-0?m>^YobW z=E_DVB@wDO+~kI$zPgBzaJmZPh+8Nq^`Xor^ZSvU+*wjy?7_{8{g5b^u0av$Gr$$2 zyrB3{#x%rR;$*~-EoT?%L{`VLocLhS&bkAzG&nEQe4Stjm$^=ILqpew<2^T&rMKKe zQu>w7zh2lm1C|M*84MN7V}P{KJCEWHyRX@VSJjsBU&pu!e9L`TrkB+|{Ge`FZBUx{ zM7y8n)^U>_toaMC{urrdk!YCW|XV7se z3+Hk3Iz?-(r1QQc-w39Fa5-6zuR-~GZaYt{x1laH>lne@x2lWiJ|Oi4KG*XiUvlO_ zgM|ZS6;r^b7rm8+mF4#QwAEQH)FdGmSvY%8LHo|xPQWo`_Hs7ymq z^=T(O^7_<=-4{)O>(&RF6KBVx4D=eq&=E9+S5F^3n;{Y&J;m@KZyozV@OV0^j6h32 z&Eb$2rJ?UgNm(bwhzQIX)u?@A&O<3CEOlcjGHSW8e*WUX0hG5|{SKz{5ff@&=NpI0 z@NjuEd~GPg;j%+P7KTcUV0~EsGnN+A2<-71+Y8wwE+g=l3D6_}axr)DBA8gY$ujjMDp>j+tjF#@P3j6^_W*X^kEdY`Gr7#S&3)A+tg}5bPF(86 zZ7PjgO1Lj(Td;mpfJl;2#;W;~sZD!&%*G!taZ*>rO!_^XO11pdF$*gxaQ7sR$Y!X^`Up0k+W&H zxPa|1qd=&|`NBaRqh^Ij&P+SnO%Z?N8pVDjb`W&ck_h_226HEqH!Q~aitD5V8)n{| zHA(YWAW#P80g`%QGA2ht4H)RkOS@b|Wbs-#{7)1vv5c|sCVmv`oU=^e=`THFq{QKE6{&gCDL zcMGovLpDLqtXCE>=p;z`_w8c~t;ap>3Pe`qv9&W4n7>C|6Ze>kD;_AI7BDwI=_Y3i z+sF!itaFD-0ej4QC>D3A(6%gBmmb*MZ8}l+`Prc-Pbo@A#m}Z&fXyY@;yv)?^F_@~ zNatiGzf+#1b#pS5Swv*!0fxorb-2|sO;8c$ZgxrMF0?aqBF6g$Yp!Kf+|(20>#@bp z!v0L{IFnEi)gAPEIi+rFFprH^j{MiwkK#Wo$Dg0wCiHUTZtHm$YGgjVQSzFo9M+lw zx6FC`e1{xEG4EB*qID;Q_=%YQSGO+N)YWo`&`U1nhq>-p+s;)d2UYd;eS#I=ql^xH zf7;^n%Hw@?is76Oo!!_&V4-E2UuiMjH{LX_=bU{$EO!(}h64F>7cI3V4iwuvz$`Pz zfgO6wsyNRUNi!SPwjE?^A11*@P9{V9QJ?0$-EI-jCbCMSc)G!R++z)>sOG*9rcblA zw1A!)y>?M=#y&qH7J9CKHG}kL1_at=)OT70{rDJE@C->DEjdeVr0W-+v8|e}PJ8^$ z@1+C()P_;kpC}p=M70-p`J3t1rU{t=7MLU28V6+>0(3B}p~2$#i|) z2ajU~Z8Z(?qek0HWG#1hB0FzbdlKg|tu#a>N~}p%^%NB$X8!u%!2nJ3vCiX}3H?%= zG>;2$HMJ$-Cb#PcrSt=WXS6ViOnd|H8YWJ$k^^#*xqH}d)?U_`hNyJ4CnZ0!Pag0vmFM?_lA$rJ}ovekc`}^nm3ejhVylUm%ovObk zyDPIVadmCoGit|hg5sw8-W!$*e2Kfe?pML~<{Qx(PMUMJcF+trh#xw-LjfchGKi!+n?1O_;udfG6-zsqo zELMK$rKl&nE7isaKICz>Tu$}X)E5qO=Z4J~M{B2=oA(+gcpkP`k%hPtRWkhFiC-B- zk|jsXs}|u@V+w>j_voW@Gb(B6Cf^q*R$gnW(izW7>u1%~x}#2a*-|ySIQPt5C$E3g zSE%`VX~xk$f)af=#-W*0UZ#8QptQV{$2ZeRA z$_;Oo-e8cYP9>6ICT3MyOeIL0Ud&pc_LoMv`S5)OF9+~&OKMi7ZU)#9wk{;i{uYc0 z>mbF@JI=o!{ViwfzEXBts!_Ekd+@;LeDGF126%LgRJ(T!F!Zz-=XX+vMEVD!Qv2fr zxZ2Xmh3fs?%uiv2WqSe%c7{r0ee{Fqd(y6VszHbctU#+XV2V1k64n5i71s+Iq(0nc zopMu(_*6fAHsG5y+U&lp?Xq~pnMr|T_W`kW^)F@ENr&39DGu6w0VBteBX*ktzH`-D zTQ_X!{P!2l%fM=Kp0y@E7D4u+*3Hurwab&IrVh!v?%PNHR%P{7VmfE+^W-_AX6Jsa zlp#kLj<(PM_G2?umkZP`19vOvva%FY9E9lx9Jth8#Vx6OPye-s1*i6FTXl}F!koNb zvSS?yvGx1qff%jJh@UxwLCKc8Rp6{Pm$WbtGC*ACmTAGTsFcQmgIub#dAVngYB#n>w& zvR2_CU=uF;!#-qERI6hAZpzpS8c4axbc~nZZK$nyYn{K@e4FUtZAd>oaDjYg z#FJJxg+Y)wx;17TrPopCC6?D2#kl)mUD>=c<)xgUFE#0*3436d*yRka#dOS8mae>Y zvF%T6tnAj$HL9Ib(eB(BzYknbi(FXY{lXGfLsr|Hrxqoo44m++5o~a7VtoU44togD z`K>fwq3zhPE?qBJbDFEqLXkyg>$#(~98pFla2Bw97>?u3pxnMP&yix&Km5y*Ho3x> zX)0r;lQAjUUkba3sL=%+tQ_-AG%keoDr>Km1F#o*{82QyKnt~;FsQon*=VbP@#e04 z!fo0Maz{oP*6W2eFQ{ht{s!s1W#D=#(?dDkj8kS|iN@ecC91;wy)o!&JpWcJ^ zYvoS=YX7}5KVgtEif;FwcnryYfDj(V&Q+{oXm6Dl<3vo8C?-*KlfbHG~}^%4M387T#aLDg8r}w z>FIwT;N^dNYaLE5`3zbI{p%7T#Ferja>%j1Q@o zDKhkvt^vj-H=2`!@GbNyG-zI{qHancn`;Z|=W8d>sN2eol<@1jt9riEwCDksLaXR^ zy8>QfhU{14Q!&;n$p|)9l8AwD&XYB=ChMigaie1u(2Nt;0Y??+lvd6^9B4qhsDvZU zlvb~{ zfMJiJe8hqFi_CvTZa*%ACuVq0t4FgI4jN@(hSXB50U!uX3`|2sg)wQ~+y-vD~ zXWAq@T%1$#A7;zTBwy*(*aijip^_`}UcCz5olc7fr6zylJH|Ui7o^5m{gz7WOa;Jw z7Zuobcror!fh!LtGt= zYVhERC}+}NoToETO|bo#E7Z%@FYfj(2imBkB%qg7j;>S|n328y;t9f%APv`_O6CXX zVA~`nI~#5OynIIPhq4_+Iga57!V8H?PUOkgpuF^PQH-spWd^0T1h>(2m!3~zW5hG6 zU)W$0X;8-Ff{K4ev~aX(lGyZo@tb$0prvvH)SV#T~U?|+K|=Ob`$MwM%j{+KuIuX&5#RQHrN)H|B$9PZ&-G4|6^ zF%7ruf22hIO&7d|W-+diGiId+wN(n<3Mt>a>X{~}qBQCbU1!Ll4CQzv<45fgmys^? zKs@ZASXoLPA`DL+GJ5JB<5fbGascP+j8SPLtk}R0)AiwqN7kXs;#AqYUXg*)p*JTY z!!IpNYCeo$8{%eUq1X7o%#2cp=gWU7FTRk+!kAF(4~7FH{-)4KxJa*q zm{$w9hjnFO#UCK&0^8cQadLCSLdLRx>27kw?>kXy*zW)1yUXQO`3^C(uPFR-=J=mH zK^2ve5L4^3$u!vB(DWB8wmU&43-{8hK@L2qSit##34+iTBqTzF7pkygH*87AU~B$7 zTy-(Xt{+7+!+H;goQ&-ACf-TvaJmF_WLfD@oX-E0yg{~%nhh4xp%gkzdP2erIqtd0 z-BszFBOJT(VdA)kP=KXqvPQQLKW&xUR8r!j3u$iK+^FnU&Aa3OoSe zltzI{LMUKYT#w#e@j&~+g-}u3&(lx6W=XK z-cEB^YeH!*Rq+1TCG^7`)ZysM*G!0mUlxi#vj|xf*lF=!iXaaPeOFL~@Fs|wj&vR6 z`{gR~^=Mq7&2jCPE%W7m=R3%)u5{ezRq=2jnN=Qx&IjR?e-cN|`D`~b7uF=k@drjOb|#4V|*D2A=4W;wzRDP918hJ z?nl0-#C|T{mrtl@f%&oNoe^9BnE!yD6Zt3rNPjkaEW6T136-}2;gT) zQqZMz&FCX=im4&KiN2G0M$HxaPi%;OMCVj_*mr$6V8tte<}m_?zALkER(daCEq(y*BWvfjT5Tn~CZCAR~3L~wFYmo714hm4l>Cl-^M zfDUuHNlLQ}$_)W*$kkujWAqKe#N-{X-*89!cJFr&p`u@{1MjpZy4cb)6#SB^2Yz>t zVj|ga|DCA!I1Ij+J7{|uXc)RNf6&7a+ck*g_bPRyU%kY@f#32|vxz<_FB5kKnn#b9 zPScZYsehdCF%McY2TDQk0z*@ytd$ZL)(n1XPx?l!N8g%WL*DmtmGoWsf9FhS-BUK^VUGM{duI8UCZChdeiOXt?pg zz_P)DpZ67qVKnTEatZ(?Nkf-J1iwWsR?Bbh#c@t}D(udn{lu!LdR#xcf$EM{sTNO2T?g2 z_i9!j4HZwXyg*p3{6^`F69?}7f0jK}&7yZ|rV^tx2z(;xGVL+8|JrMi&-!G46|7(RG9PnHG!%v(i?N|4dV{{sP#`4<2H literal 0 HcmV?d00001 diff --git a/website/docs/bestpractices/preload-container-images.md b/website/docs/bestpractices/preload-container-images.md new file mode 100644 index 000000000..e94d3662b --- /dev/null +++ b/website/docs/bestpractices/preload-container-images.md @@ -0,0 +1,173 @@ +--- +title: Preload container images into Bottlerocket data volumes with Karpenter +sidebar_position: 2 +--- +import CollapsibleContent from '../../../src/components/CollapsibleContent'; + +# Preload container images into Bottlerocket data volumes with Karpenter with EBS Snapshots + +The purpose of this pattern is to reduce the cold start time of containers with large images by caching the images in the data volume of Bottlerocket OS. + +Data analytics and machine learning workloads often require large container images (usually measured by Gigabytes), which can take several minutes to pull and extract from Amazon ECR or other image registry. Reduce image pulling time is the key of improving speed of launching these containers. + +Bottlerocket OS is a Linux-based open-source operating system built by AWS specifically for running containers. It has two volumes, an OS volume and a data volume, with the latter used for storing artifacts and container images. This sample will leverage the data volume to pull images and take snapshots for later usage. + +To demonstrate the process of caching images in EBS snapshots and launching them in an EKS cluster, this sample will use Amazon EKS optimized Bottlerocket AMIs. + +For details, refer to the GitHub sample and blog post: +https://github.com/aws-samples/bottlerocket-images-cache/tree/main +https://aws.amazon.com/blogs/containers/reduce-container-startup-time-on-amazon-eks-with-bottlerocket-data-volume/ + + +## Overview of this script + +![](img/bottlerocket-image-cache.png) + +1. Launch an EC2 instance with Bottlerocket for EKS AMI, +2. Access to instance via Amazon System Manager +3. Pull images to be cached in this EC2 using Amazon System Manager Run Command. +4. Shut down the instance, build the EBS snapshot for the data volume. +5. Terminate the instance. + +## Usage Example + +``` +git clone https://github.com/aws-samples/bottlerocket-images-cache/ +cd bottlerocket-images-cache/ + +# Using nohup in terminals to avoid disconnections +❯ nohup ./snapshot.sh --snapshot-size 150 -r us-west-2 \ + docker.io/rayproject/ray-ml:2.10.0-py310-gpu,public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest & + +❯ tail -f nohup.out + +2024-07-15 17:18:53 I - [1/8] Deploying EC2 CFN stack ... +2024-07-15 17:22:07 I - [2/8] Launching SSM . +2024-07-15 17:22:08 I - SSM launched in instance i-07d10182abc8a86e1. +2024-07-15 17:22:08 I - [3/8] Stopping kubelet.service .. +2024-07-15 17:22:10 I - Kubelet service stopped. +2024-07-15 17:22:10 I - [4/8] Cleanup existing images .. +2024-07-15 17:22:12 I - Existing images cleaned +2024-07-15 17:22:12 I - [5/8] Pulling images: +2024-07-15 17:22:12 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 ... +2024-07-15 17:27:50 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 pulled. +2024-07-15 17:27:50 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 ... +2024-07-15 17:27:58 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 pulled. +2024-07-15 17:27:58 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 ... +2024-07-15 17:31:34 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 pulled. +2024-07-15 17:31:34 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 ... +2024-07-15 17:31:36 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 pulled. +2024-07-15 17:31:36 I - [6/8] Stopping instance ... +2024-07-15 17:32:25 I - Instance i-07d10182abc8a86e1 stopped +2024-07-15 17:32:25 I - [7/8] Creating snapshot ... +2024-07-15 17:38:36 I - Snapshot snap-0c6d965cf431785ed generated. +2024-07-15 17:38:36 I - [8/8] Cleanup. +2024-07-15 17:38:37 I - Stack deleted. +2024-07-15 17:38:37 I - -------------------------------------------------- +2024-07-15 17:38:37 I - All done! Created snapshot in us-west-2: snap-0c6d965cf431785ed +``` + +You can copy the snapshot ID `snap-0c6d965cf431785ed` and configure it as a snapshot for worker nodes. + +# Using Snapshot with Amazon EKS and Karpenter + +You can specify `snapshotID` in a Karpenter node class. Add the content on EC2NodeClass: + +``` +apiVersion: karpenter.k8s.aws/v1beta1 +kind: EC2NodeClass +metadata: + name: default +spec: + amiFamily: Bottlerocket # Ensure OS is BottleRocket + blockDeviceMappings: + - deviceName: /dev/xvdb + ebs: + volumeSize: 150Gi + volumeType: gp3 + kmsKeyID: "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab" # Specify KMS ID if you use custom KMS key + snapshotID: snap-0123456789 # Specify your snapshot ID here +``` + +# End-to-End deployment example + +An end-to-end deployment example can be found in [Stable Diffusion on GPU](../terraform/README.md) + +Clone the repository + +``` +git clone https://github.com/awslabs/data-on-eks.git +``` + +Navigate into one of the example directories and run install.sh script + +Important Note: Ensure that you update the region in the variables.tf file before deploying the blueprint. Additionally, confirm that your local region setting matches the specified region to prevent any discrepancies. For example, set your `export AWS_DEFAULT_REGION=""` to the desired region: + +## Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS + +Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins for downloading and extracting container images from Amazon ECR. + +``` +export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed + +cd data-on-eks/ai-ml/jark-stack/ && chmod +x install.sh +./install.sh +``` + +# Verify the resources +Verify the Amazon EKS Cluster + +``` +aws eks --region us-west-2 describe-cluster --name jark-stack + +# Creates k8s config file to authenticate with EKS +aws eks --region us-west-2 update-kubeconfig --name jark-stack + +# Output shows the EKS Managed Node group nodes +kubectl get nodes + +## Deploying the Ray Cluster with Stable Diffusion Model +Ensure the cluster is configured locally + +```bash +aws eks --region us-west-2 update-kubeconfig --name jark-stack +``` + +**Deploy RayServe Cluster** + +```bash +cd ./../gen-ai/inference/stable-diffusion-rayserve-gpu +kubectl apply -f ray-service-stablediffusion.yaml +``` + +This deployment establishes a Ray head pod running on an x86 instance and a worker pod on a GPU G5 instance as shown below. + +```bash +kubectl get pods -n stablediffusion + +NAME READY STATUS +rservice-raycluster-hb4l4-worker-gpu-worker-group-z8gdw 1/1 Running +stablediffusion-service-raycluster-hb4l4-head-4kfzz 2/2 Running +``` +You can find the container image is preloaded into the disk disk. + + +``` +kubectl describe pod + +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Warning FailedScheduling 41m default-scheduler 0/8 nodes are available: 1 Insufficient cpu, 3 Insufficient memory, 8 Insufficient nvidia.com/gpu. preemption: 0/8 nodes are available: 8 No preemption victims found for incoming pod. + Normal Nominated 41m karpenter Pod should schedule on: nodeclaim/gpu-ljvhl + Normal Scheduled 40m default-scheduler Successfully assigned stablediffusion/stablediffusion-raycluster-ms6pl-worker-gpu-85d22 to ip-100-64-136-72.us-west-2.compute.internal + Normal Pulled 40m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine + Normal Created 40m kubelet Created container wait-gcs-ready + Normal Started 40m kubelet Started container wait-gcs-ready + Normal Pulled 39m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine + Normal Created 39m kubelet Created container worker + Normal Started 38m kubelet Started container worker + ``` + +TODO: add benchmark results and the time save for downloading container images \ No newline at end of file From b92887fe9323370bfa7575e882c7390b924a2ba1 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 13 Aug 2024 11:43:25 +0800 Subject: [PATCH 10/22] fix: add __pycache__ path in .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7a4c74629..7ed60474a 100755 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ site # node modules node_modules +gen-ai/inference/stable-diffusion-rayserve-gpu/locust/__pycache__/* \ No newline at end of file From bd63f2857a7ffa090a6a6dd2e1ee1e59c885a9fd Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 13 Aug 2024 15:59:05 +0800 Subject: [PATCH 11/22] fix: correcting the import path --- .../bestpractices/preload-container-images.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/website/docs/bestpractices/preload-container-images.md b/website/docs/bestpractices/preload-container-images.md index e94d3662b..213b3e2d3 100644 --- a/website/docs/bestpractices/preload-container-images.md +++ b/website/docs/bestpractices/preload-container-images.md @@ -2,7 +2,7 @@ title: Preload container images into Bottlerocket data volumes with Karpenter sidebar_position: 2 --- -import CollapsibleContent from '../../../src/components/CollapsibleContent'; +import CollapsibleContent from '../../src/components/CollapsibleContent'; # Preload container images into Bottlerocket data volumes with Karpenter with EBS Snapshots @@ -18,12 +18,11 @@ For details, refer to the GitHub sample and blog post: https://github.com/aws-samples/bottlerocket-images-cache/tree/main https://aws.amazon.com/blogs/containers/reduce-container-startup-time-on-amazon-eks-with-bottlerocket-data-volume/ - ## Overview of this script ![](img/bottlerocket-image-cache.png) -1. Launch an EC2 instance with Bottlerocket for EKS AMI, +1. Launch an EC2 instance with Bottlerocket for EKS AMI. 2. Access to instance via Amazon System Manager 3. Pull images to be cached in this EC2 using Amazon System Manager Run Command. 4. Shut down the instance, build the EBS snapshot for the data volume. @@ -91,21 +90,19 @@ spec: # End-to-End deployment example -An end-to-end deployment example can be found in [Stable Diffusion on GPU](../terraform/README.md) +An end-to-end deployment example can be found in [Stable Diffusion on GPU](https://awslabs.github.io/data-on-eks/docs/gen-ai/inference/stablediffusion-gpus). -Clone the repository +### Clone the repository ``` git clone https://github.com/awslabs/data-on-eks.git ``` -Navigate into one of the example directories and run install.sh script - Important Note: Ensure that you update the region in the variables.tf file before deploying the blueprint. Additionally, confirm that your local region setting matches the specified region to prevent any discrepancies. For example, set your `export AWS_DEFAULT_REGION=""` to the desired region: -## Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS +### Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS -Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins for downloading and extracting container images from Amazon ECR. +Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins (depending on the image size) for downloading and extracting container images from Amazon ECR. ``` export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed From de70d0a148a71077579ae4af962cf6ae98315553 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 13 Aug 2024 16:08:32 +0800 Subject: [PATCH 12/22] feat: add websites docs for preload container images on bottlerocket nodes --- website/docs/bestpractices/preload-container-images.md | 2 +- website/docs/gen-ai/inference/stablediffusion-gpus.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/website/docs/bestpractices/preload-container-images.md b/website/docs/bestpractices/preload-container-images.md index 213b3e2d3..e8842886e 100644 --- a/website/docs/bestpractices/preload-container-images.md +++ b/website/docs/bestpractices/preload-container-images.md @@ -90,7 +90,7 @@ spec: # End-to-End deployment example -An end-to-end deployment example can be found in [Stable Diffusion on GPU](https://awslabs.github.io/data-on-eks/docs/gen-ai/inference/stablediffusion-gpus). +An end-to-end deployment example can be found in [Stable Diffusion on GPU](../gen-ai/inference/stablediffusion-gpus). ### Clone the repository diff --git a/website/docs/gen-ai/inference/stablediffusion-gpus.md b/website/docs/gen-ai/inference/stablediffusion-gpus.md index 45b80288b..c0a4124eb 100644 --- a/website/docs/gen-ai/inference/stablediffusion-gpus.md +++ b/website/docs/gen-ai/inference/stablediffusion-gpus.md @@ -6,10 +6,11 @@ import CollapsibleContent from '../../../src/components/CollapsibleContent'; :::info -We are actively enhancing this blueprint to incorporate improvements in observability and logging. - +- We are actively enhancing this blueprint to incorporate improvements in observability and logging. +- To accelerate the deployment of image retrieval on Ray workerss, refer to [Preload container images into Bottlerocket data volumes with Karpenter with EBS Snapshots](../../bestpractices/preload-container-images) ::: + # Deploying Stable Diffusion v2 with GPUs, Ray Serve and Gradio This pattern demonstrates how to deploy the [Stable Diffusion V2](https://huggingface.co/stabilityai/stable-diffusion-2-1) model on Amazon EKS, using [GPUs](https://aws.amazon.com/ec2/instance-types/g4/) for accelerated image generation. [Ray Serve](https://docs.ray.io/en/latest/serve/index.html) provides efficient scaling across multiple GPU nodes, while [Karpenter](https://karpenter.sh/) dynamically manages node provisioning. From 40144ebbcfc3f7e1277710a760f0f02172b3676d Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Tue, 13 Aug 2024 17:01:35 +0800 Subject: [PATCH 13/22] fix: update .gitignore --- .gitignore | 4 +- website/package-lock.json | 266 ++++++++++++++++++++++++++++++++++++++ website/package.json | 1 + 3 files changed, 270 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7ed60474a..21493b035 100755 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,6 @@ site # node modules node_modules -gen-ai/inference/stable-diffusion-rayserve-gpu/locust/__pycache__/* \ No newline at end of file +gen-ai/inference/stable-diffusion-rayserve-gpu/locust/__pycache__/* +website/package-lock.json +website/package.json \ No newline at end of file diff --git a/website/package-lock.json b/website/package-lock.json index aad2c9f86..fc757686a 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -19,6 +19,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-slick": "^0.30.2", + "serve": "^14.2.3", "slick-carousel": "^1.8.1" }, "devDependencies": { @@ -4421,6 +4422,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4705,6 +4712,26 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "license": "ISC" }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -5506,6 +5533,91 @@ "node": ">=4" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk-template/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/chalk-template/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk-template/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chalk/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -5733,6 +5845,23 @@ "node": ">=8" } }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -11045,6 +11174,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-reference": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", @@ -17324,6 +17465,31 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.3.tgz", + "integrity": "sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==", + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.5", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/serve-handler": { "version": "6.1.5", "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", @@ -17439,6 +17605,74 @@ "node": ">= 0.8.0" } }, + "node_modules/serve/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/serve/node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/serve/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -19216,6 +19450,38 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/update-check/node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/update-check/node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/update-notifier": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", diff --git a/website/package.json b/website/package.json index 43241d093..0dd47a908 100644 --- a/website/package.json +++ b/website/package.json @@ -30,6 +30,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-slick": "^0.30.2", + "serve": "^14.2.3", "slick-carousel": "^1.8.1" }, "devDependencies": { From f111dcc02f719bc7be79b7fb65aa13f91391eefa Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Mon, 19 Aug 2024 15:11:10 +0800 Subject: [PATCH 14/22] fix: remove unsued README.md docs --- .../preload-container-image-ami/README.md | 64 -------- ai-ml/jark-stack/terraform/README.md | 139 ------------------ 2 files changed, 203 deletions(-) delete mode 100644 ai-ml/jark-stack/preload-container-image-ami/README.md delete mode 100644 ai-ml/jark-stack/terraform/README.md diff --git a/ai-ml/jark-stack/preload-container-image-ami/README.md b/ai-ml/jark-stack/preload-container-image-ami/README.md deleted file mode 100644 index 9a480f64e..000000000 --- a/ai-ml/jark-stack/preload-container-image-ami/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Preload Container Images - -To preload the container images in EBS snapshots and launching them in an EKS cluster, using sample will use Bottlerocket AMIs for Amazon EKS. -You can use the generated EBS Snapshot to launch worker nodes in Amazon EKS Node Groups and Worker Nodes created by Karpenter. - -For details, refer to the GitHub sample: -https://github.com/aws-samples/bottlerocket-images-cache/tree/main - -Usage Example: - -``` -# Using nohup in terminals to avoid disconnections -❯ nohup ./snapshot.sh --snapshot-size 150 -r us-west-2 docker.io/rayproject/ray-ml:2.10.0-py310-gpu,public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest & -❯ tail -f nohup.out - -2024-07-15 17:18:53 I - [1/8] Deploying EC2 CFN stack ... -2024-07-15 17:22:07 I - [2/8] Launching SSM . -2024-07-15 17:22:08 I - SSM launched in instance i-07d10182abc8a86e1. -2024-07-15 17:22:08 I - [3/8] Stopping kubelet.service .. -2024-07-15 17:22:10 I - Kubelet service stopped. -2024-07-15 17:22:10 I - [4/8] Cleanup existing images .. -2024-07-15 17:22:12 I - Existing images cleaned -2024-07-15 17:22:12 I - [5/8] Pulling images: -2024-07-15 17:22:12 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 ... -2024-07-15 17:27:50 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 pulled. -2024-07-15 17:27:50 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 ... -2024-07-15 17:27:58 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 pulled. -2024-07-15 17:27:58 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 ... -2024-07-15 17:31:34 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 pulled. -2024-07-15 17:31:34 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 ... -2024-07-15 17:31:36 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 pulled. -2024-07-15 17:31:36 I - [6/8] Stopping instance ... -2024-07-15 17:32:25 I - Instance i-07d10182abc8a86e1 stopped -2024-07-15 17:32:25 I - [7/8] Creating snapshot ... -2024-07-15 17:38:36 I - Snapshot snap-0c6d965cf431785ed generated. -2024-07-15 17:38:36 I - [8/8] Cleanup. -2024-07-15 17:38:37 I - Stack deleted. -2024-07-15 17:38:37 I - -------------------------------------------------- -2024-07-15 17:38:37 I - All done! Created snapshot in us-west-2: snap-0c6d965cf431785ed -``` - -You can copy the snapshot ID `snap-0c6d965cf431785ed` and move forward. -An end-to-end deployment example can be found in [JARK Stack](../terraform/README.md) - -# With Karpenter - -You can specify snapshot ID in a Karpenter node template. You should also specify AMI used when provisioning node is BottleRocket. Add the content on EC2NodeClass (or AWSNodeTemplate on older release of Karpenter): - -v1beta1 API: -``` -apiVersion: karpenter.k8s.aws/v1beta1 -kind: EC2NodeClass -metadata: - name: default -spec: - amiFamily: Bottlerocket # Ensure OS is BottleRocket - blockDeviceMappings: - - deviceName: /dev/xvdb - ebs: - volumeSize: 150Gi - volumeType: gp3 - kmsKeyID: "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab" # Specify KMS ID if you use custom KMS key - snapshotID: snap-0123456789 # Specify your snapshot ID here -``` diff --git a/ai-ml/jark-stack/terraform/README.md b/ai-ml/jark-stack/terraform/README.md deleted file mode 100644 index 40c8b892b..000000000 --- a/ai-ml/jark-stack/terraform/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# JupyterHub, Argo, Ray, Kubernetes - -# Prerequisites - -Before we begin, ensure you have all the prerequisites in place to make the deployment process smooth and hassle-free. Ensure that you have installed the following tools on your machine. - -- aws cli -- kubectl -- terraform - - -./install.sh -... -``` -You can find the container image is preloaded into the disk disk. - -``` -kubectl describe pod - -... -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Warning FailedScheduling 41m default-scheduler 0/8 nodes are available: 1 Insufficient cpu, 3 Insufficient memory, 8 Insufficient nvidia.com/gpu. preemption: 0/8 nodes are available: 8 No preemption victims found for incoming pod. - Normal Nominated 41m karpenter Pod should schedule on: nodeclaim/gpu-ljvhl - Normal Scheduled 40m default-scheduler Successfully assigned stablediffusion/stablediffusion-raycluster-ms6pl-worker-gpu-85d22 to ip-100-64-136-72.us-west-2.compute.internal - Normal Pulled 40m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine - Normal Created 40m kubelet Created container wait-gcs-ready - Normal Started 40m kubelet Started container wait-gcs-ready - Normal Pulled 39m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine - Normal Created 39m kubelet Created container worker - Normal Started 38m kubelet Started container worker - ``` - -# Deploy - - - -Clone the repository - -``` -git clone https://github.com/awslabs/data-on-eks.git -``` - -Navigate into one of the example directories and run install.sh script - -Important Note: Ensure that you update the region in the variables.tf file before deploying the blueprint. Additionally, confirm that your local region setting matches the specified region to prevent any discrepancies. For example, set your `export AWS_DEFAULT_REGION=""` to the desired region: - -## Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS (o) - -Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins for downloading and extracting container images from Amazon ECR. - -To build snapshots with preloaded container images, refer to [this page](../preload-container-image-ami/README.md) for details. - -# Get the snapshot ID with ./snapshot.sh - -export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed - -``` -cd data-on-eks/ai-ml/jark-stack/ && chmod +x install.sh -./install.sh -``` - -# Verify the resources -Verify the Amazon EKS Cluster - -``` -aws eks --region us-west-2 describe-cluster --name jark-stack - -# Creates k8s config file to authenticate with EKS -aws eks --region us-west-2 update-kubeconfig --name jark-stack - -# Output shows the EKS Managed Node group nodes -kubectl get nodes - -``` - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.0.0 | -| [aws](#requirement\_aws) | >= 3.72 | -| [helm](#requirement\_helm) | >= 2.4.1 | -| [kubectl](#requirement\_kubectl) | >= 1.14 | -| [kubernetes](#requirement\_kubernetes) | >= 2.10 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 3.72 | -| [aws.ecr](#provider\_aws.ecr) | >= 3.72 | -| [kubernetes](#provider\_kubernetes) | >= 2.10 | - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [data\_addons](#module\_data\_addons) | aws-ia/eks-data-addons/aws | ~> 1.31.4 | -| [ebs\_csi\_driver\_irsa](#module\_ebs\_csi\_driver\_irsa) | terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks | ~> 5.20 | -| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 19.15 | -| [eks\_blueprints\_addons](#module\_eks\_blueprints\_addons) | aws-ia/eks-blueprints-addons/aws | ~> 1.2 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | - -## Resources - -| Name | Type | -|------|------| -| [kubernetes_annotations.disable_gp2](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/annotations) | resource | -| [kubernetes_config_map_v1.notebook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/config_map_v1) | resource | -| [kubernetes_namespace_v1.jupyterhub](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace_v1) | resource | -| [kubernetes_secret_v1.huggingface_token](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource | -| [kubernetes_storage_class.default_gp3](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/storage_class) | resource | -| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | -| [aws_ecrpublic_authorization_token.token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecrpublic_authorization_token) | data source | -| [aws_eks_cluster_auth.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster_auth) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [eks\_cluster\_version](#input\_eks\_cluster\_version) | EKS Cluster version | `string` | `"1.30"` | no | -| [enable\_aws\_efa\_k8s\_device\_plugin](#input\_enable\_aws\_efa\_k8s\_device\_plugin) | Enable AWS EFA K8s Device Plugin | `bool` | `false` | no | -| [enable\_kubecost](#input\_enable\_kubecost) | Enable Kubecost addon | `bool` | `false` | no | -| [huggingface\_token](#input\_huggingface\_token) | Hugging Face Secret Token | `string` | `"DUMMY_TOKEN_REPLACE_ME"` | no | -| [name](#input\_name) | Name of the VPC and EKS Cluster | `string` | `"jark-stack"` | no | -| [region](#input\_region) | region | `string` | `"us-west-2"` | no | -| [secondary\_cidr\_blocks](#input\_secondary\_cidr\_blocks) | Secondary CIDR blocks to be attached to VPC | `list(string)` |
[
"100.64.0.0/16"
]
| no | -| [vpc\_cidr](#input\_vpc\_cidr) | VPC CIDR. This should be a valid private (RFC 1918) CIDR range | `string` | `"10.1.0.0/21"` | no | -| [preload-image-bottlerocket-snapshot-id](#input\_preload-image-bottlerocket-snapshot-id) | The snapshot ID of bottlerocket that preloads the container image | `string` | | no - -## Outputs - -| Name | Description | -|------|-------------| -| [configure\_kubectl](#output\_configure\_kubectl) | Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig | - From 1d214c40330779a61abcbd9aa6c3e39b70f87bc2 Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Mon, 19 Aug 2024 15:11:30 +0800 Subject: [PATCH 15/22] fix: move additional IAM policy to addon.tf --- ai-ml/jark-stack/terraform/karpenter.tf | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/ai-ml/jark-stack/terraform/karpenter.tf b/ai-ml/jark-stack/terraform/karpenter.tf index 87bcded99..e69de29bb 100644 --- a/ai-ml/jark-stack/terraform/karpenter.tf +++ b/ai-ml/jark-stack/terraform/karpenter.tf @@ -1,22 +0,0 @@ -resource "aws_iam_policy" "karpenter_controlloer_policy" { - description = "Additional IAM policy for Karpenter controller" - policy = data.aws_iam_policy_document.karpenter_controller_policy.json -} - -data "aws_iam_policy_document" "karpenter_controller_policy" { - statement { - actions = [ - "ec2:RunInstances", - "ec2:CreateLaunchTemplate", - ] - resources = ["*"] - effect = "Allow" - sid = "Karpenter" - } -} - -resource "aws_iam_role_policy_attachment" "karpenter_controller_policy_attachment" { - # name = "karpenter-controller-policy-attachment" - role = module.eks_blueprints_addons.karpenter.iam_role_name - policy_arn = aws_iam_policy.karpenter_controlloer_policy.arn -} \ No newline at end of file From 5658061a981c1b324ec3c93d22d872ad6ef48d3b Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Mon, 19 Aug 2024 15:12:55 +0800 Subject: [PATCH 16/22] fix: update page title and move end-to-end example to stable diffusion page --- .../bestpractices/preload-container-images.md | 87 ++----------------- 1 file changed, 5 insertions(+), 82 deletions(-) diff --git a/website/docs/bestpractices/preload-container-images.md b/website/docs/bestpractices/preload-container-images.md index e8842886e..1142e8dec 100644 --- a/website/docs/bestpractices/preload-container-images.md +++ b/website/docs/bestpractices/preload-container-images.md @@ -1,10 +1,10 @@ --- -title: Preload container images into Bottlerocket data volumes with Karpenter +title: Preload container images into data volumes sidebar_position: 2 --- import CollapsibleContent from '../../src/components/CollapsibleContent'; -# Preload container images into Bottlerocket data volumes with Karpenter with EBS Snapshots +# Preload container images into data volumes with EBS Snapshots The purpose of this pattern is to reduce the cold start time of containers with large images by caching the images in the data volume of Bottlerocket OS. @@ -15,8 +15,8 @@ Bottlerocket OS is a Linux-based open-source operating system built by AWS speci To demonstrate the process of caching images in EBS snapshots and launching them in an EKS cluster, this sample will use Amazon EKS optimized Bottlerocket AMIs. For details, refer to the GitHub sample and blog post: -https://github.com/aws-samples/bottlerocket-images-cache/tree/main -https://aws.amazon.com/blogs/containers/reduce-container-startup-time-on-amazon-eks-with-bottlerocket-data-volume/ +- [GitHub - Caching Container Images for AWS Bottlerocket Instances](https://github.com/aws-samples/bottlerocket-images-cache/tree/main) +- [Blog Post - Reduce container startup time on Amazon EKS with Bottlerocket data volume](https://aws.amazon.com/blogs/containers/reduce-container-startup-time-on-amazon-eks-with-bottlerocket-data-volume/) ## Overview of this script @@ -90,81 +90,4 @@ spec: # End-to-End deployment example -An end-to-end deployment example can be found in [Stable Diffusion on GPU](../gen-ai/inference/stablediffusion-gpus). - -### Clone the repository - -``` -git clone https://github.com/awslabs/data-on-eks.git -``` - -Important Note: Ensure that you update the region in the variables.tf file before deploying the blueprint. Additionally, confirm that your local region setting matches the specified region to prevent any discrepancies. For example, set your `export AWS_DEFAULT_REGION=""` to the desired region: - -### Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS - -Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins (depending on the image size) for downloading and extracting container images from Amazon ECR. - -``` -export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed - -cd data-on-eks/ai-ml/jark-stack/ && chmod +x install.sh -./install.sh -``` - -# Verify the resources -Verify the Amazon EKS Cluster - -``` -aws eks --region us-west-2 describe-cluster --name jark-stack - -# Creates k8s config file to authenticate with EKS -aws eks --region us-west-2 update-kubeconfig --name jark-stack - -# Output shows the EKS Managed Node group nodes -kubectl get nodes - -## Deploying the Ray Cluster with Stable Diffusion Model -Ensure the cluster is configured locally - -```bash -aws eks --region us-west-2 update-kubeconfig --name jark-stack -``` - -**Deploy RayServe Cluster** - -```bash -cd ./../gen-ai/inference/stable-diffusion-rayserve-gpu -kubectl apply -f ray-service-stablediffusion.yaml -``` - -This deployment establishes a Ray head pod running on an x86 instance and a worker pod on a GPU G5 instance as shown below. - -```bash -kubectl get pods -n stablediffusion - -NAME READY STATUS -rservice-raycluster-hb4l4-worker-gpu-worker-group-z8gdw 1/1 Running -stablediffusion-service-raycluster-hb4l4-head-4kfzz 2/2 Running -``` -You can find the container image is preloaded into the disk disk. - - -``` -kubectl describe pod - -... -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Warning FailedScheduling 41m default-scheduler 0/8 nodes are available: 1 Insufficient cpu, 3 Insufficient memory, 8 Insufficient nvidia.com/gpu. preemption: 0/8 nodes are available: 8 No preemption victims found for incoming pod. - Normal Nominated 41m karpenter Pod should schedule on: nodeclaim/gpu-ljvhl - Normal Scheduled 40m default-scheduler Successfully assigned stablediffusion/stablediffusion-raycluster-ms6pl-worker-gpu-85d22 to ip-100-64-136-72.us-west-2.compute.internal - Normal Pulled 40m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine - Normal Created 40m kubelet Created container wait-gcs-ready - Normal Started 40m kubelet Started container wait-gcs-ready - Normal Pulled 39m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine - Normal Created 39m kubelet Created container worker - Normal Started 38m kubelet Started container worker - ``` - -TODO: add benchmark results and the time save for downloading container images \ No newline at end of file +An end-to-end deployment example can be found in [Stable Diffusion on GPU](../gen-ai/inference/stablediffusion-gpus). \ No newline at end of file From c265ee6375dd30f15e957f7687ddf3c8dec6734d Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Mon, 19 Aug 2024 15:13:26 +0800 Subject: [PATCH 17/22] fix: remove comments --- ai-ml/jark-stack/terraform/install.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ai-ml/jark-stack/terraform/install.sh b/ai-ml/jark-stack/terraform/install.sh index c1b95e6c0..1814a9044 100755 --- a/ai-ml/jark-stack/terraform/install.sh +++ b/ai-ml/jark-stack/terraform/install.sh @@ -6,10 +6,6 @@ targets=( "module.eks" ) -# Get the snapshot ID with ./snapshot.sh -# Uncomment the command line below and replace the value with the snapshot ID -# export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0ffe58efdd845d938 - # Initialize Terraform terraform init -upgrade From c521992825976a4d58739413c73db085f1dc651e Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Mon, 19 Aug 2024 15:14:27 +0800 Subject: [PATCH 18/22] docs: add preload container image --- .../gen-ai/inference/stablediffusion-gpus.md | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/website/docs/gen-ai/inference/stablediffusion-gpus.md b/website/docs/gen-ai/inference/stablediffusion-gpus.md index c0a4124eb..1202af61f 100644 --- a/website/docs/gen-ai/inference/stablediffusion-gpus.md +++ b/website/docs/gen-ai/inference/stablediffusion-gpus.md @@ -7,7 +7,7 @@ import CollapsibleContent from '../../../src/components/CollapsibleContent'; :::info - We are actively enhancing this blueprint to incorporate improvements in observability and logging. -- To accelerate the deployment of image retrieval on Ray workerss, refer to [Preload container images into Bottlerocket data volumes with Karpenter with EBS Snapshots](../../bestpractices/preload-container-images) + ::: @@ -39,6 +39,15 @@ Ensure that you have installed the following tools on your machine. 2. [kubectl](https://Kubernetes.io/docs/tasks/tools/) 3. [terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) +### (Optional) Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS + +To accelerate the deployment of image retrieval on Ray workers, refer to [Preload container images into Bottlerocket data volumes with Karpenter with EBS Snapshots](../../bestpractices/preload-container-images) + +Define the `TF_VAR_bottlerocket_data_disk_snpashot_id` to enable Karpenter to provision Bottlerocket worker nodes with EBS Snapshots, to reduce cold start for container startup. This will likely to save 10 mins (depending on the image size) for downloading and extracting container images from Amazon ECR. + +``` +export TF_VAR_bottlerocket_data_disk_snpashot_id=snap-0c6d965cf431785ed +``` ### Deploy Clone the repository @@ -47,6 +56,12 @@ Clone the repository git clone https://github.com/awslabs/data-on-eks.git ``` + +``` +cd data-on-eks/ai-ml/jark-stack/ && chmod +x install.sh +./install.sh +``` + Navigate into one of the example directories and run `install.sh` script **Important Note:** Ensure that you update the region in the `variables.tf` file before deploying the blueprint. @@ -110,7 +125,7 @@ Verify the deployment by running the following commands :::info -The deployment process may take up to 10 to 12 minutes. The Head Pod is expected to be ready within 2 to 3 minutes, while the Ray Serve worker pod may take up to 10 minutes for image retrieval and Model deployment from Huggingface. +If you did not preload container images into the data volume, the deployment process may take up to 10 to 12 minutes. The Head Pod is expected to be ready within 2 to 3 minutes, while the Ray Serve worker pod may take up to 10 minutes for image retrieval and Model deployment from Huggingface. ::: @@ -124,6 +139,27 @@ rservice-raycluster-hb4l4-worker-gpu-worker-group-z8gdw 1/1 Running stablediffusion-service-raycluster-hb4l4-head-4kfzz 2/2 Running ``` +If you have preload container images into the data volume, you can find the message showing `Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine` in the output of `kubectl describe pod -n stablediffusion`. + + +``` +kubectl describe pod -n stablediffusion + +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Warning FailedScheduling 41m default-scheduler 0/8 nodes are available: 1 Insufficient cpu, 3 Insufficient memory, 8 Insufficient nvidia.com/gpu. preemption: 0/8 nodes are available: 8 No preemption victims found for incoming pod. + Normal Nominated 41m karpenter Pod should schedule on: nodeclaim/gpu-ljvhl + Normal Scheduled 40m default-scheduler Successfully assigned stablediffusion/stablediffusion-raycluster-ms6pl-worker-gpu-85d22 to ip-100-64-136-72.us-west-2.compute.internal + Normal Pulled 40m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine + Normal Created 40m kubelet Created container wait-gcs-ready + Normal Started 40m kubelet Started container wait-gcs-ready + Normal Pulled 39m kubelet Container image "public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest" already present on machine + Normal Created 39m kubelet Created container worker + Normal Started 38m kubelet Started container worker + ``` + This deployment also sets up a stablediffusion service with multiple ports configured; port `8265` is designated for the Ray dashboard and port `8000` for the Stable Diffusion model endpoint. ```bash From ddc156185eb0747d73f8b8e1020fd93bce53f9eb Mon Sep 17 00:00:00 2001 From: Darren Lin Date: Mon, 19 Aug 2024 15:18:25 +0800 Subject: [PATCH 19/22] fix: add additional IAM policy statements for karpenter to launch from snapshot --- ai-ml/jark-stack/terraform/addons.tf | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ai-ml/jark-stack/terraform/addons.tf b/ai-ml/jark-stack/terraform/addons.tf index f56eb700d..74ac3200f 100644 --- a/ai-ml/jark-stack/terraform/addons.tf +++ b/ai-ml/jark-stack/terraform/addons.tf @@ -119,6 +119,9 @@ module "eks_blueprints_addons" { chart_version = "0.37.0" repository_username = data.aws_ecrpublic_authorization_token.token.user_name repository_password = data.aws_ecrpublic_authorization_token.token.password + source_policy_documents = [ + data.aws_iam_policy_document.karpenter_controller_policy.json + ] } #--------------------------------------- @@ -148,7 +151,7 @@ module "eks_blueprints_addons" { module "data_addons" { source = "aws-ia/eks-data-addons/aws" - version = "~> 1.40" # source = "aws-ia/eks-data-addons/aws" + version = "~> 1.40" oidc_provider_arn = module.eks.oidc_provider_arn @@ -384,3 +387,15 @@ resource "kubernetes_config_map_v1" "notebook" { "dogbooth.ipynb" = file("${path.module}/src/notebook/dogbooth.ipynb") } } + +data "aws_iam_policy_document" "karpenter_controller_policy" { + statement { + actions = [ + "ec2:RunInstances", + "ec2:CreateLaunchTemplate", + ] + resources = ["*"] + effect = "Allow" + sid = "KarpenterControllerAdditionalPolicy" + } +} From 4a517a1348eb186a5934cfac9fd9930aa757508f Mon Sep 17 00:00:00 2001 From: Darren Lin <76021640+lindarr915@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:48:47 +0800 Subject: [PATCH 20/22] fix: bump data on eks addons to 1.33 to support karpenter helm resources with bottlerocket --- ai-ml/jark-stack/terraform/addons.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai-ml/jark-stack/terraform/addons.tf b/ai-ml/jark-stack/terraform/addons.tf index 74ac3200f..ec32adc3e 100644 --- a/ai-ml/jark-stack/terraform/addons.tf +++ b/ai-ml/jark-stack/terraform/addons.tf @@ -151,7 +151,7 @@ module "eks_blueprints_addons" { module "data_addons" { source = "aws-ia/eks-data-addons/aws" - version = "~> 1.40" + version = "~> 1.33" oidc_provider_arn = module.eks.oidc_provider_arn From 4ba1b2a8f5c2647e5529b5baf28cdc0a85e776a8 Mon Sep 17 00:00:00 2001 From: Apoorva Kulkarni Date: Tue, 20 Aug 2024 16:24:40 -0700 Subject: [PATCH 21/22] fixes for pre-commit --- .gitignore | 2 +- ai-ml/jark-stack/terraform/variables.tf | 4 +-- .../ray-serve-stablediffusion.yaml | 4 +-- .../bestpractices/preload-container-images.md | 30 +++++++++---------- .../gen-ai/inference/stablediffusion-gpus.md | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 21493b035..f0e17ef55 100755 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,4 @@ site node_modules gen-ai/inference/stable-diffusion-rayserve-gpu/locust/__pycache__/* website/package-lock.json -website/package.json \ No newline at end of file +website/package.json diff --git a/ai-ml/jark-stack/terraform/variables.tf b/ai-ml/jark-stack/terraform/variables.tf index efd9697db..cfc27f17c 100644 --- a/ai-ml/jark-stack/terraform/variables.tf +++ b/ai-ml/jark-stack/terraform/variables.tf @@ -56,5 +56,5 @@ variable "bottlerocket_data_disk_snpashot_id" { description = "Bottlerocket Data Disk Snapshot ID" type = string default = "" - -} \ No newline at end of file + +} diff --git a/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml b/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml index bca28103e..7bb996f72 100644 --- a/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml +++ b/gen-ai/inference/stable-diffusion-rayserve-gpu/ray-serve-stablediffusion.yaml @@ -21,7 +21,7 @@ spec: serveConfigV2: | applications: - name: stable-diffusion-deployment - import_path: "ray_serve_sd:entrypoint" + import_path: "ray_serve_sd:entrypoint" route_prefix: "/" runtime_env: env_vars: @@ -61,7 +61,7 @@ spec: # For faster inference scaling, consider building a custom image with only your workload's essential dependencies. # Smaller images lead to faster scaling, especially across multiple nodes. # Notice that we are using the same image for both the head and worker nodes. You might hit ModuleNotFoundError if you use a different image for head and worker nodes. - # Preload Container Image into data volumes for faster new ray worker nodes + # Preload Container Image into data volumes for faster new ray worker nodes - name: head image: public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest imagePullPolicy: IfNotPresent # Ensure the image is always pulled when updated diff --git a/website/docs/bestpractices/preload-container-images.md b/website/docs/bestpractices/preload-container-images.md index 1142e8dec..cf25a2fd8 100644 --- a/website/docs/bestpractices/preload-container-images.md +++ b/website/docs/bestpractices/preload-container-images.md @@ -14,7 +14,7 @@ Bottlerocket OS is a Linux-based open-source operating system built by AWS speci To demonstrate the process of caching images in EBS snapshots and launching them in an EKS cluster, this sample will use Amazon EKS optimized Bottlerocket AMIs. -For details, refer to the GitHub sample and blog post: +For details, refer to the GitHub sample and blog post: - [GitHub - Caching Container Images for AWS Bottlerocket Instances](https://github.com/aws-samples/bottlerocket-images-cache/tree/main) - [Blog Post - Reduce container startup time on Amazon EKS with Bottlerocket data volume](https://aws.amazon.com/blogs/containers/reduce-container-startup-time-on-amazon-eks-with-bottlerocket-data-volume/) @@ -34,11 +34,11 @@ For details, refer to the GitHub sample and blog post: git clone https://github.com/aws-samples/bottlerocket-images-cache/ cd bottlerocket-images-cache/ -# Using nohup in terminals to avoid disconnections +# Using nohup in terminals to avoid disconnections ❯ nohup ./snapshot.sh --snapshot-size 150 -r us-west-2 \ - docker.io/rayproject/ray-ml:2.10.0-py310-gpu,public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest & + docker.io/rayproject/ray-ml:2.10.0-py310-gpu,public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest & -❯ tail -f nohup.out +❯ tail -f nohup.out 2024-07-15 17:18:53 I - [1/8] Deploying EC2 CFN stack ... 2024-07-15 17:22:07 I - [2/8] Launching SSM . @@ -48,17 +48,17 @@ cd bottlerocket-images-cache/ 2024-07-15 17:22:10 I - [4/8] Cleanup existing images .. 2024-07-15 17:22:12 I - Existing images cleaned 2024-07-15 17:22:12 I - [5/8] Pulling images: -2024-07-15 17:22:12 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 ... -2024-07-15 17:27:50 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 pulled. -2024-07-15 17:27:50 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 ... -2024-07-15 17:27:58 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 pulled. -2024-07-15 17:27:58 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 ... -2024-07-15 17:31:34 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 pulled. -2024-07-15 17:31:34 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 ... -2024-07-15 17:31:36 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 pulled. -2024-07-15 17:31:36 I - [6/8] Stopping instance ... +2024-07-15 17:22:12 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 ... +2024-07-15 17:27:50 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - amd64 pulled. +2024-07-15 17:27:50 I - Pulling docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 ... +2024-07-15 17:27:58 I - docker.io/rayproject/ray-ml:2.10.0-py310-gpu - arm64 pulled. +2024-07-15 17:27:58 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 ... +2024-07-15 17:31:34 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - amd64 pulled. +2024-07-15 17:31:34 I - Pulling public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 ... +2024-07-15 17:31:36 I - public.ecr.aws/data-on-eks/ray2.11.0-py310-gpu-stablediffusion:latest - arm64 pulled. +2024-07-15 17:31:36 I - [6/8] Stopping instance ... 2024-07-15 17:32:25 I - Instance i-07d10182abc8a86e1 stopped -2024-07-15 17:32:25 I - [7/8] Creating snapshot ... +2024-07-15 17:32:25 I - [7/8] Creating snapshot ... 2024-07-15 17:38:36 I - Snapshot snap-0c6d965cf431785ed generated. 2024-07-15 17:38:36 I - [8/8] Cleanup. 2024-07-15 17:38:37 I - Stack deleted. @@ -90,4 +90,4 @@ spec: # End-to-End deployment example -An end-to-end deployment example can be found in [Stable Diffusion on GPU](../gen-ai/inference/stablediffusion-gpus). \ No newline at end of file +An end-to-end deployment example can be found in [Stable Diffusion on GPU](../gen-ai/inference/stablediffusion-gpus). diff --git a/website/docs/gen-ai/inference/stablediffusion-gpus.md b/website/docs/gen-ai/inference/stablediffusion-gpus.md index 1202af61f..020514287 100644 --- a/website/docs/gen-ai/inference/stablediffusion-gpus.md +++ b/website/docs/gen-ai/inference/stablediffusion-gpus.md @@ -39,7 +39,7 @@ Ensure that you have installed the following tools on your machine. 2. [kubectl](https://Kubernetes.io/docs/tasks/tools/) 3. [terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) -### (Optional) Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS +### (Optional) Reduce Cold Start Time by Preloading Container Images in Bottlerocket OS To accelerate the deployment of image retrieval on Ray workers, refer to [Preload container images into Bottlerocket data volumes with Karpenter with EBS Snapshots](../../bestpractices/preload-container-images) From 86ac090fad9657c90d06132bd311ff28b505f81a Mon Sep 17 00:00:00 2001 From: Apoorva Kulkarni Date: Tue, 20 Aug 2024 16:37:12 -0700 Subject: [PATCH 22/22] fix pre-commit on the merged main --- .../vllm-rayserve-inf2/patches/vllm_v0.5.0_neuron.patch | 1 - 1 file changed, 1 deletion(-) diff --git a/gen-ai/inference/vllm-rayserve-inf2/patches/vllm_v0.5.0_neuron.patch b/gen-ai/inference/vllm-rayserve-inf2/patches/vllm_v0.5.0_neuron.patch index b9b0ccf8a..b185c1592 100644 --- a/gen-ai/inference/vllm-rayserve-inf2/patches/vllm_v0.5.0_neuron.patch +++ b/gen-ai/inference/vllm-rayserve-inf2/patches/vllm_v0.5.0_neuron.patch @@ -28,4 +28,3 @@ index cd29db7..6814348 100644 - choices=[8, 16, 32], help='Token block size for contiguous chunks of ' 'tokens.') -