From fa9413e0086912949cd5436c6a901435582aa202 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Fri, 31 Jan 2025 14:15:00 +0100 Subject: [PATCH 001/122] Clean up index.md files - restore original structure - remove test_status: passed - add back fedora support --- content/install-guides/acfl.md | 3 -- content/install-guides/anaconda.md | 2 - content/install-guides/azure-cli.md | 2 - content/install-guides/dotnet.md | 16 +++--- content/install-guides/eksctl.md | 22 ++++---- content/install-guides/forge.md | 10 ++-- content/install-guides/gcc/arm-gnu.md | 31 +++++------ content/install-guides/gcc/cross.md | 11 ++-- content/install-guides/gcc/native.md | 5 +- content/install-guides/gfortran.md | 6 +-- content/install-guides/kubectl.md | 8 ++- content/install-guides/oci-cli.md | 26 +++++----- content/install-guides/pytorch.md | 2 - content/install-guides/terraform.md | 2 - .../dynamic-memory-allocator/_index.md | 52 +++++++++++-------- .../cross-platform/intrinsics/_index.md | 46 +++++++++------- .../tfm/_index.md | 43 +++++++++------ .../zephyr/_index.md | 41 +++++++++------ .../clickhouse/_index.md | 6 +-- .../codec/_index.md | 42 +++++++++------ .../geekbench/_index.md | 8 +-- .../memcached/_index.md | 41 +++++++++------ .../memcached_cache/_index.md | 42 +++++++++------ .../ml-perf/_index.md | 42 +++++++++------ .../mysql_benchmark/_index.md | 4 +- .../mysql_tune/_index.md | 6 +-- .../nginx/_index.md | 4 +- .../postgresql_tune/_index.md | 2 - .../servers-and-cloud-computing/ran/_index.md | 42 +++++++++------ .../snappy/_index.md | 42 +++++++++------ .../vectorscan/_index.md | 6 +-- .../zlib/_index.md | 44 ++++++++++------ tools/check.py | 21 +++++--- 33 files changed, 378 insertions(+), 302 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index cf070251b5..292f677212 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -16,9 +16,6 @@ test_images: - fedora:latest test_link: null test_maintenance: true -test_status: -- passed -- passed title: Arm Compiler for Linux tool_install: true weight: 1 diff --git a/content/install-guides/anaconda.md b/content/install-guides/anaconda.md index aaf905a029..22434d507b 100644 --- a/content/install-guides/anaconda.md +++ b/content/install-guides/anaconda.md @@ -15,8 +15,6 @@ test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed title: Anaconda tool_install: true weight: 1 diff --git a/content/install-guides/azure-cli.md b/content/install-guides/azure-cli.md index 24bf7057c0..414949764a 100644 --- a/content/install-guides/azure-cli.md +++ b/content/install-guides/azure-cli.md @@ -11,8 +11,6 @@ official_docs: https://learn.microsoft.com/en-us/cli/azure test_images: - ubuntu:latest test_maintenance: true -test_status: -- passed title: Azure CLI tool_install: true weight: 1 diff --git a/content/install-guides/dotnet.md b/content/install-guides/dotnet.md index 7014b73ce5..72764eb060 100644 --- a/content/install-guides/dotnet.md +++ b/content/install-guides/dotnet.md @@ -11,8 +11,6 @@ test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed title: .NET SDK tool_install: true weight: 1 @@ -20,7 +18,7 @@ weight: 1 The [.NET SDK](https://dotnet.microsoft.com/en-us/) is a free, open-source, and cross-platform development environment that provides a broad set of tools and libraries for building applications. You can use it to create a variety of applications including web apps, mobile apps, desktop apps, and cloud services. -The .NET SDK is available for Linux distributions on Arm-based systems. +The .NET SDK is available for Linux distributions on Arm-based systems. ## What should I do before installing the .NET SDK on Arm Linux? @@ -44,7 +42,7 @@ There are two ways to install the .NET SDK on your computer: - Using the Linux package manager. - Using the install script. -Select the one that works best for you. +Select the one that works best for you. ### How can I install .NET SDK using the Linux package manager? @@ -68,11 +66,11 @@ To install the .NET SDK using a script, follow the instructions below: 1. Download the install script: -```bash +```bash wget https://dot.net/v1/dotnet-install.sh ``` -2. Run the script (it will install .NET SDK 8 under the folder .dotnet): +2. Run the script (it will install .NET SDK 8 under the folder .dotnet): You have some options to specify the version you want to install. @@ -104,9 +102,9 @@ You can also add the search path to your `$HOME/.bashrc` so it is set for all ne ## How do I verify the .NET SDK installation? -To check that the installation was successful, type: +To check that the installation was successful, type: -```bash +```bash dotnet --list-sdks ``` @@ -190,7 +188,7 @@ The expected output in the console is: Hello World! ``` -You are ready to use the .NET SDK on Arm Linux. +You are ready to use the .NET SDK on Arm Linux. You can find more information about .NET on Arm in the [AWS Graviton Technical Guide](https://github.com/aws/aws-graviton-getting-started/blob/main/dotnet.md). diff --git a/content/install-guides/eksctl.md b/content/install-guides/eksctl.md index 8888bfe346..dbc195f5a6 100644 --- a/content/install-guides/eksctl.md +++ b/content/install-guides/eksctl.md @@ -14,8 +14,6 @@ test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed title: AWS EKS CLI (eksctl) tool_install: true weight: 1 @@ -27,7 +25,7 @@ The EKS CLI is available for a variety of operating systems and Linux distributi ## What should I do before installing eksctl on Arm Linux and Windows on Arm? -This install guide provides a quick solution to install `eksctl` on Arm Linux and Windows on Arm. +This install guide provides a quick solution to install `eksctl` on Arm Linux and Windows on Arm. 1. Confirm you have an Arm machine @@ -45,11 +43,11 @@ aarch64 If you see a different result, you are not using an Arm computer running 64-bit Linux. -For Windows, confirm the Arm architecture by typing "Settings" in the Windows search box. +For Windows, confirm the Arm architecture by typing "Settings" in the Windows search box. When the settings appear, click System on the left side and then About at the bottom. -In the Device specifications section look for "ARM-based processor" in the System type area. +In the Device specifications section look for "ARM-based processor" in the System type area. 2. Install `kubectl` @@ -57,13 +55,13 @@ Install the Kubernetes command-line tool, `kubectl`, using the [Kubectl install ## How do I download and install eksctl on Arm Linux? -1. Download the `eksctl` package using `curl`: +1. Download the `eksctl` package using `curl`: ```bash { target="ubuntu:latest" } curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_Linux_arm64.tar.gz" ``` -2. Install `eksctl` with: +2. Install `eksctl` with: ```bash { target="ubuntu:latest" } tar -xzf eksctl_Linux_arm64.tar.gz -C /tmp && rm eksctl_Linux_arm64.tar.gz @@ -104,11 +102,11 @@ The output will be similar to: `eksctl` relies on the AWS CLI being installed and configured. Use the [AWS CLI install guide](/install-guides/aws-cli/) to install the AWS CLI. The CLI provides the `aws` command. -You will also need to configure the AWS CLI using the `aws configure` or the `aws configure sso` command. There are multiple ways to configure the CLI, including environment variables, command-line options, and credentials files. Refer to the [Configure the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) for more details. +You will also need to configure the AWS CLI using the `aws configure` or the `aws configure sso` command. There are multiple ways to configure the CLI, including environment variables, command-line options, and credentials files. Refer to the [Configure the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) for more details. ## How do I use eksctl to create a simple EKS cluster? -With your AWS account configured, run `eksctl` to create a cluster with 2 nodes with AWS Graviton processors: +With your AWS account configured, run `eksctl` to create a cluster with 2 nodes with AWS Graviton processors: ```console eksctl create cluster \ @@ -116,12 +114,12 @@ eksctl create cluster \ --region us-east-1 \ --node-type t4g.small \ --nodes 2 \ ---nodegroup-name node-group-1 +--nodegroup-name node-group-1 ``` -Use the AWS console to look at the resources associated with the cluster and monitor progress of cluster creation. +Use the AWS console to look at the resources associated with the cluster and monitor progress of cluster creation. -When the cluster is created, use `kubectl` to get the status of the nodes in the cluster. +When the cluster is created, use `kubectl` to get the status of the nodes in the cluster. ```console kubectl get nodes -o wide diff --git a/content/install-guides/forge.md b/content/install-guides/forge.md index 51c7b3e6a3..704f28a305 100644 --- a/content/install-guides/forge.md +++ b/content/install-guides/forge.md @@ -15,8 +15,6 @@ test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed title: Linaro Forge tool_install: true weight: 1 @@ -35,7 +33,7 @@ Linaro Forge runs on Linux hosts and multiple architectures. See the Linaro Forg This install guide assumes an Arm AArch64 platform running Ubuntu Linux. -## Download +## Download Download and extract the appropriate installation package from [Linaro Forge Downloads](https://www.linaroforge.com/downloadForge/). @@ -73,9 +71,9 @@ Run the Windows file executable to install the Linaro Forge Remote Client. ### Graphical installer -Optionally, you can run the installer executable with a graphical interface. +Optionally, you can run the installer executable with a graphical interface. -```bash +```bash cd linaro-forge-24.0.3-linux-aarch64/ ./installer ``` @@ -171,4 +169,4 @@ perf-report -n 128 myapp ``` Two files `myapp_128p_.html` and `myapp_128p_.txt` will be created in the current directory. -Linaro Forge is now installed and ready to use. +Linaro Forge is now installed and ready to use. diff --git a/content/install-guides/gcc/arm-gnu.md b/content/install-guides/gcc/arm-gnu.md index bf2d7b4814..9cee28a9df 100644 --- a/content/install-guides/gcc/arm-gnu.md +++ b/content/install-guides/gcc/arm-gnu.md @@ -12,9 +12,6 @@ test_images: - fedora:latest test_link: null test_maintenance: true -test_status: -- passed -- passed title: Arm GNU Toolchain tool_install: false weight: 4 @@ -30,19 +27,19 @@ However there are reasons you may wish to use earlier compiler versions, so olde ## How do I download the Arm GNU Toolchain? {#download} Arm GNU Toolchain releases consist of cross toolchains for the following host operating systems: - -Linux + +Linux * Available for x86_64 and AArch64 host architectures - * Available for bare-metal and Linux targets - -Windows + * Available for bare-metal and Linux targets + +Windows * Available for x86 host architecture only (compatible with x86_64) * Available for bare-metal and Linux targets - -macOS + +macOS * Available for x86_64 and Apple silicon (beta) host architectures * Available for bare-metal targets only - + Download the correct toolchain variant for your development needs from the [Arm Developer website](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/downloads). ## How do I install the Arm GNU Toolchain on Linux? @@ -80,7 +77,7 @@ source ~/.bashrc ## How do I install the Arm GNU Toolchain on macOS? -Downloads for `macOS` are available as tar files (`.tar.xz`) and package files (`.pkg`). +Downloads for `macOS` are available as tar files (`.tar.xz`) and package files (`.pkg`). ### tar files For `.tar.xz` files, unpack the downloaded file to the install directory. @@ -89,7 +86,7 @@ tar xJf arm-gnu-toolchain---.tar.xz -C /path/to/inst ``` ### pkg files -For `.pkg` files use the installer. +For `.pkg` files use the installer. ```console sudo installer -pkg arm-gnu-toolchain---.pkg -target / ``` @@ -114,7 +111,7 @@ The `/etc/paths` file is a list of paths to search. ### Apple Silicon -Here is a specific example for macOS with Apple Silicon and the AArch32 bare-metal target. +Here is a specific example for macOS with Apple Silicon and the AArch32 bare-metal target. ```console wget https://developer.arm.com/-/media/Files/downloads/gnu/14.2.rel1/binrel/arm-gnu-toolchain-14.2.rel1-darwin-arm64-arm-none-eabi.pkg @@ -136,15 +133,15 @@ For example, to install the tools silently, amend users `PATH` and add registry ```console gcc-arm---mingw-w64-i686-arm-none-eabi.exe /S /P /R ``` -The zip package is a backup to Windows installer for those who cannot run the installer. You can unzip the package and then invoke the tools directly. +The zip package is a backup to Windows installer for those who cannot run the installer. You can unzip the package and then invoke the tools directly. -## Setting up product license +## Setting up product license Arm GNU Toolchain is open sourced and freely available for use. No licenses need to be set up for use. To use the Arm GNU Toolchain in conjunction with [Arm Development Studio](https://developer.arm.com/Tools%20and%20Software/Arm%20Development%20Studio) you must [register the toolchain](https://developer.arm.com/documentation/101469/2022-0/Installing-and-configuring-Arm-Development-Studio/Register-a-compiler-toolchain). -## Get started +## Get started To verify the installation is correct enter: ```console diff --git a/content/install-guides/gcc/cross.md b/content/install-guides/gcc/cross.md index 8e31795b90..e076f2b51a 100644 --- a/content/install-guides/gcc/cross.md +++ b/content/install-guides/gcc/cross.md @@ -12,14 +12,11 @@ test_images: - fedora:latest test_link: null test_maintenance: true -test_status: -- passed -- passed title: Cross-compiler tool_install: false weight: 3 --- -GCC is available on all Linux distributions and can be installed using the package manager. +GCC is available on all Linux distributions and can be installed using the package manager. This covers `gcc` and `g++` for compiling C and C++ as a cross-compiler targeting the Arm architecture. @@ -27,9 +24,9 @@ This covers `gcc` and `g++` for compiling C and C++ as a cross-compiler targetin GCC is often used to cross-compile software for Arm microcontrollers and embedded devices which have firmware and other low-level software. The executables are `arm-none-eabi-gcc` and `arm-none-eabi-g++`. -GCC is also used to cross compile Linux applications. Applications can be compiled for 32-bit or 64-bit Linux systems. +GCC is also used to cross compile Linux applications. Applications can be compiled for 32-bit or 64-bit Linux systems. -The executables for 32-bit are `arm-linux-gnueabihf-gcc` and `arm-linux-gnueabihf-g++`. +The executables for 32-bit are `arm-linux-gnueabihf-gcc` and `arm-linux-gnueabihf-g++`. The executables for 64-bit are `aarch64-linux-gnu-gcc` and `aarch64-linux-gnu-g++`. @@ -90,7 +87,7 @@ brew install arm-none-eabi-gcc ## Setting up product license {#license} -GCC is open source and freely available for use. +GCC is open source and freely available for use. ## Get started {#start} diff --git a/content/install-guides/gcc/native.md b/content/install-guides/gcc/native.md index 74a07e0ea5..a2478d91a8 100644 --- a/content/install-guides/gcc/native.md +++ b/content/install-guides/gcc/native.md @@ -12,9 +12,6 @@ test_images: - fedora:latest test_link: null test_maintenance: true -test_status: -- passed -- passed title: Native compiler tool_install: false weight: 2 @@ -77,7 +74,7 @@ dnf groupinstall 'Development Tools' -y ``` ## Setting up product license -GCC is open source and freely available for use. +GCC is open source and freely available for use. ## Get started {#start} diff --git a/content/install-guides/gfortran.md b/content/install-guides/gfortran.md index 5f664e3c4f..0f5462d205 100644 --- a/content/install-guides/gfortran.md +++ b/content/install-guides/gfortran.md @@ -18,8 +18,6 @@ test_images: - fedora:latest test_link: null test_maintenance: true -test_status: -- passed title: GFortran tool_install: true weight: 1 @@ -47,7 +45,7 @@ aarch64 If you see a different result, you are not using an Arm computer running 64-bit Linux. -## Download +## Download The Linux package manager downloads the required files so there are no special instructions. @@ -64,7 +62,7 @@ sudo apt install gfortran -y ### Installing on Red Hat / Fedora / Amazon Linux -These Linux distributions use `yum` as the package manager. +These Linux distributions use `yum` as the package manager. To install the most common development tools use the commands below. If the machine has `sudo` you can use it. diff --git a/content/install-guides/kubectl.md b/content/install-guides/kubectl.md index 6fb122e592..c2ff049df9 100644 --- a/content/install-guides/kubectl.md +++ b/content/install-guides/kubectl.md @@ -17,8 +17,6 @@ test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed title: Kubectl tool_install: true weight: 1 @@ -30,7 +28,7 @@ The Kubernetes command-line tool, [kubectl](https://kubernetes.io/docs/reference ## What should I consider before installing kubectl for Ubuntu on Arm? -[General installation information](https://kubernetes.io/docs/tasks/tools/) is available which covers all supported operating systems, but it doesn't talk about Arm. +[General installation information](https://kubernetes.io/docs/tasks/tools/) is available which covers all supported operating systems, but it doesn't talk about Arm. This article provides a quick solution to install `kubectl` for Ubuntu on Arm. @@ -48,7 +46,7 @@ If you see a different result, you are not using an Arm computer running 64-bit ## How do I download and Install kubectl for Ubuntu on Arm? -The easiest way to install `kubectl` for Ubuntu on Arm is to use curl and copy the executable to a common location. +The easiest way to install `kubectl` for Ubuntu on Arm is to use curl and copy the executable to a common location. To install curl, for example on ubuntu: @@ -62,7 +60,7 @@ Download and install the latest version of `kubctl`. There is just 1 executable curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl" ``` -If you have sudo or root access, install the executable in a common location for all users. +If you have sudo or root access, install the executable in a common location for all users. ```bash { target="ubuntu:latest" } sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 984c64407b..99bb7751a1 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -2,7 +2,7 @@ additional_search_terms: - cloud - OCI -- Oracle +- Oracle - infrastructure layout: installtoolsall @@ -14,21 +14,19 @@ official_docs: https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall test_images: - ubuntu:latest test_maintenance: true -test_status: -- passed title: Oracle Cloud Infrastructure (OCI) CLI tool_install: true weight: 1 --- -The Oracle Cloud Infrastructure Command Line Interface (OCI CLI) is a cross-platform command-line tool that can be installed locally on development computers. OCI CLI is used to connect to OCI and execute administrative commands on OCI resources. +The Oracle Cloud Infrastructure Command Line Interface (OCI CLI) is a cross-platform command-line tool that can be installed locally on development computers. OCI CLI is used to connect to OCI and execute administrative commands on OCI resources. -It is available for a variety of operating systems and Linux distributions and has multiple ways to install it. +It is available for a variety of operating systems and Linux distributions and has multiple ways to install it. ## Before you begin -[General installation information](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm#InstallingCLI__linux_and_unix) is available which covers all supported Linux distributions. - +[General installation information](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm#InstallingCLI__linux_and_unix) is available which covers all supported Linux distributions. + This install guide provides a quick solution to install OCI CLI for Ubuntu on Arm. Confirm you are using an Arm machine by running: @@ -47,24 +45,24 @@ If you see a different result, you are not using an Arm computer running 64-bit ## Download and Install -The easiest way to install OCI CLI for Ubuntu on Arm is to use the install script. +The easiest way to install OCI CLI for Ubuntu on Arm is to use the install script. You can run an automated install with default values or an interactive install to change the default values. -To run an an automated install with default values run: +To run an an automated install with default values run: ```bash { target="ubuntu:latest" } curl -o install.sh https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh bash ./install.sh --accept-all-defaults ``` -To run an interactive install that allows you to change default values run: +To run an interactive install that allows you to change default values run: ```console bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" ``` -Run the command below to add the `oci` command to your search path: +Run the command below to add the `oci` command to your search path: ```bash { target="ubuntu:latest" } source $HOME/.bashrc @@ -76,17 +74,17 @@ Verify OCI CLI is installed using the `--version` option: oci --version ``` -Configure OCI CLI using the `setup config` command: +Configure OCI CLI using the `setup config` command: ```console oci setup config ``` -To complete the setup you will need your OCID and API key. +To complete the setup you will need your OCID and API key. Log in to the OCI web console to access your OCID and API key. -Open the Profile menu and click Tenancy: to locate your OCID. +Open the Profile menu and click Tenancy: to locate your OCID. ![oci1 #center](https://github.com/ArmDeveloperEcosystem/arm-learning-paths/assets/40816837/7d5faf0a-2b62-41a8-ac1c-66e11aa01e5d) diff --git a/content/install-guides/pytorch.md b/content/install-guides/pytorch.md index 5d4e1ed115..74b9c0100b 100644 --- a/content/install-guides/pytorch.md +++ b/content/install-guides/pytorch.md @@ -14,8 +14,6 @@ test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed title: PyTorch tool_install: true weight: 1 diff --git a/content/install-guides/terraform.md b/content/install-guides/terraform.md index fe14076589..f85e4993db 100644 --- a/content/install-guides/terraform.md +++ b/content/install-guides/terraform.md @@ -16,8 +16,6 @@ test_images: - ubuntu:latest test_link: false test_maintenance: true -test_status: -- passed title: Terraform tool_install: true weight: 1 diff --git a/content/learning-paths/cross-platform/dynamic-memory-allocator/_index.md b/content/learning-paths/cross-platform/dynamic-memory-allocator/_index.md index c7e28fa70b..71f021457f 100644 --- a/content/learning-paths/cross-platform/dynamic-memory-allocator/_index.md +++ b/content/learning-paths/cross-platform/dynamic-memory-allocator/_index.md @@ -1,38 +1,48 @@ --- -armips: null -author_primary: David Spickett -layout: learningpathall +title: Write a Dynamic Memory Allocator + +minutes_to_complete: 120 + +who_is_this_for: This is an introductory topic for software developers learning about dynamic memory allocation for the first time, and who may have used malloc and free in C programming. It also provides a starting point to explore more advanced memory allocation topics. + learning_objectives: - Explain how dynamic memory allocation and the C heap works - Write a simple dynamic memory allocator - Explain some of the risks of heap allocation in general -learning_path_main_page: 'yes' -minutes_to_complete: 120 -operatingsystems: -- Linux + prerequisites: - Familiarity with C programming, with a good understanding of pointers. - A Linux machine to run the example code. -skilllevels: Introductory -subjects: Performance and Architecture -armips: - - Cortex-A - - Neoverse + +author_primary: David Spickett + test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed -title: Write a Dynamic Memory Allocator + +### Tags +skilllevels: Introductory +subjects: Performance and Architecture +armips: + - Cortex-A + - Neoverse +operatingsystems: + - Linux tools_software_languages: -- C -- Coding -weight: 1 -who_is_this_for: This is an introductory topic for software developers learning about dynamic memory allocation for the first time, - and who may have used malloc and free in C programming. It also provides a starting point to explore more advanced memory allocation topics. + - C + - Coding + +### Cross-platform metadata only shared_path: true shared_between: - laptops-and-desktops - embedded-and-microcontrollers ---- + + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- \ No newline at end of file diff --git a/content/learning-paths/cross-platform/intrinsics/_index.md b/content/learning-paths/cross-platform/intrinsics/_index.md index 0545380d72..4f7a322497 100644 --- a/content/learning-paths/cross-platform/intrinsics/_index.md +++ b/content/learning-paths/cross-platform/intrinsics/_index.md @@ -1,46 +1,54 @@ --- -armips: -- Neoverse -- Cortex-A -author_primary: Jason Andrews +title: Porting architecture specific intrinsics + description: Learn how to port architecture-specific intrinsics to Arm processors. -layout: learningpathall + +minutes_to_complete: 30 + +who_is_this_for: This is an advanced topic for software developers interested in porting + architecture specific intrinsics to Arm processors. + learning_objectives: - Describe what intrinsics are and how to find them in code. - Evaluate options and use header-only libraries to port architecture-specific intrinsics to Arm. -learning_path_main_page: 'yes' -minutes_to_complete: 30 -operatingsystems: -- Linux + prerequisites: - Some understanding of SIMD concepts. - An Arm based machine or [cloud instance](/learning-paths/servers-and-cloud-computing/csp/) running Ubuntu Linux. - Optionally, an `x86_64` machine also running Ubuntu. -skilllevels: Advanced -subjects: Performance and Architecture + +author_primary: Jason Andrews + test_images: - amd64/ubuntu:latest - arm64v8/ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed -- passed -title: Porting architecture specific intrinsics + +### Tags +skilllevels: Advanced +subjects: Performance and Architecture +armips: +- Neoverse +- Cortex-A +operatingsystems: +- Linux tools_software_languages: - Neon - SVE - Coding - Intrinsics -weight: 1 -who_is_this_for: This is an advanced topic for software developers interested in porting - architecture specific intrinsics to Arm processors. - ### Cross-platform metadata only shared_path: true shared_between: - servers-and-cloud-computing - laptops-and-desktops + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. --- diff --git a/content/learning-paths/embedded-and-microcontrollers/tfm/_index.md b/content/learning-paths/embedded-and-microcontrollers/tfm/_index.md index 34b0461b62..36f17b08cb 100644 --- a/content/learning-paths/embedded-and-microcontrollers/tfm/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/tfm/_index.md @@ -1,27 +1,33 @@ --- -armips: -- Cortex-M -- Corstone -author_primary: Pareena Verma -layout: learningpathall +title: Get started with Trusted Firmware-M + +minutes_to_complete: 15 + +who_is_this_for: This is an introductory topic for software developers new to Trusted + Firmware-M. + learning_objectives: - Build and run the reference TF-M tests and example application. -learning_path_main_page: 'yes' -minutes_to_complete: 15 -operatingsystems: -- Baremetal + prerequisites: - Some familiarity with embedded C programming - A machine running Ubuntu Linux -skilllevels: Introductory -subjects: Security + +author_primary: Pareena Verma + test_images: - armswdev/arm-tools:bare-metal-compilers test_link: null test_maintenance: true -test_status: -- passed -title: Get started with Trusted Firmware-M + +### Tags +skilllevels: Introductory +subjects: Security +armips: +- Cortex-M +- Corstone +operatingsystems: +- Baremetal tools_software_languages: - Arm Virtual Hardware - Fixed Virtual Platform @@ -29,7 +35,10 @@ tools_software_languages: - Trusted Firmware - Coding -weight: 1 -who_is_this_for: This is an introductory topic for software developers new to Trusted - Firmware-M. +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/content/learning-paths/embedded-and-microcontrollers/zephyr/_index.md b/content/learning-paths/embedded-and-microcontrollers/zephyr/_index.md index 09a945cc2f..787dc239c4 100644 --- a/content/learning-paths/embedded-and-microcontrollers/zephyr/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/zephyr/_index.md @@ -1,33 +1,42 @@ --- -armips: -- Cortex-M -author_primary: Pareena Verma -layout: learningpathall +title: Run the Zephyr RTOS on Arm Corstone-300 + +minutes_to_complete: 30 + +who_is_this_for: This is an introductory topic for software developers getting started + with the Zephyr RTOS. + learning_objectives: - Build and run Zephyr applications on the Corstone-300 -learning_path_main_page: 'yes' -minutes_to_complete: 30 -operatingsystems: -- RTOS + prerequisites: - Some familiarity with embedded C programming - A Linux machine running Ubuntu, or an AWS account to use [Arm Virtual Hardware](https://www.arm.com/products/development-tools/simulation/virtual-hardware) -skilllevels: Introductory -subjects: RTOS Fundamentals + +author_primary: Pareena Verma + test_images: - amd64/ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed -title: Run the Zephyr RTOS on Arm Corstone-300 + +### Tags +skilllevels: Introductory +subjects: RTOS Fundamentals +armips: +- Cortex-M +operatingsystems: +- RTOS tools_software_languages: - Zephyr - Arm Virtual Hardware - Fixed Virtual Platform - Coding -weight: 1 -who_is_this_for: This is an introductory topic for software developers getting started - with the Zephyr RTOS. +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/content/learning-paths/servers-and-cloud-computing/clickhouse/_index.md b/content/learning-paths/servers-and-cloud-computing/clickhouse/_index.md index f29777b4ff..163e8cf7de 100644 --- a/content/learning-paths/servers-and-cloud-computing/clickhouse/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/clickhouse/_index.md @@ -20,8 +20,8 @@ subjects: Databases armips: - Neoverse tools_software_languages: - - ClickHouse - - ClickBench + - ClickHouse + - ClickBench operatingsystems: - Linux @@ -29,8 +29,6 @@ test_images: - ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed ### FIXED, DO NOT MODIFY # ================================================================================ diff --git a/content/learning-paths/servers-and-cloud-computing/codec/_index.md b/content/learning-paths/servers-and-cloud-computing/codec/_index.md index 0a993d00d3..64627386cb 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/_index.md @@ -1,31 +1,41 @@ --- -armips: -- Neoverse -author_primary: Pareena Verma -layout: learningpathall +title: Run x265 (H.265 codec) on Arm servers + +minutes_to_complete: 10 + +who_is_this_for: This is an introductory topic for software developers who want to + build and run an x265 codec on Arm servers and measure performance. + learning_objectives: - Build x265 codec on Arm server - Run x265 codec on Arm server with the same video of various resolutions and encoding presets to measure the performance impact -learning_path_main_page: 'yes' -minutes_to_complete: 10 -operatingsystems: -- Linux + prerequisites: - An [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from an appropriate cloud service provider. This Learning Path has been verified on AWS EC2 and Oracle cloud services, running `Ubuntu Linux 20.04.` -skilllevels: Introductory -subjects: Libraries + +author_primary: Pareena Verma + test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed -title: Run x265 (H.265 codec) on Arm servers + +### Tags +skilllevels: Introductory +subjects: Libraries +armips: +- Neoverse tools_software_languages: - x265 -weight: 1 -who_is_this_for: This is an introductory topic for software developers who want to - build and run an x265 codec on Arm servers and measure performance. +operatingsystems: +- Linux + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/content/learning-paths/servers-and-cloud-computing/geekbench/_index.md b/content/learning-paths/servers-and-cloud-computing/geekbench/_index.md index 7f34868462..b794937354 100644 --- a/content/learning-paths/servers-and-cloud-computing/geekbench/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/geekbench/_index.md @@ -4,9 +4,9 @@ title: Get started with Geekbench on Arm minutes_to_complete: 15 who_is_this_for: This is an introductory topic for software developers interested in comparing the performance of Arm Linux computers using Geekbench. - + learning_objectives: -- Learn how to install and run Geekbench +- Learn how to install and run Geekbench - Use Geekbench to help determine the appropriate hardware configuration for your workload prerequisites: @@ -25,11 +25,11 @@ operatingsystems: test_maintenance: true test_images: - ubuntu:latest -test_status: - - passed tools_software_languages: - Geekbench +### FIXED, DO NOT MODIFY +# ================================================================================ weight: 1 layout: learningpathall learning_path_main_page: 'yes' diff --git a/content/learning-paths/servers-and-cloud-computing/memcached/_index.md b/content/learning-paths/servers-and-cloud-computing/memcached/_index.md index 71efce0ee8..c89fcda856 100644 --- a/content/learning-paths/servers-and-cloud-computing/memcached/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/memcached/_index.md @@ -1,29 +1,40 @@ --- -armips: -- Neoverse -author_primary: Pareena Verma -layout: learningpathall +title: Run memcached on Arm servers and measure its performance + +minutes_to_complete: 10 + +who_is_this_for: This is an introductory topic for developers who want to use memcached as their in-memory key-value store. + learning_objectives: - Install and run memcached on your Arm-based cloud server - Use an open-source benchmark to test memcached performance -learning_path_main_page: 'yes' -minutes_to_complete: 10 -operatingsystems: -- Linux + prerequisites: - An Arm based instance from an appropriate cloud service provider. -skilllevels: Introductory -subjects: Web + +author_primary: Pareena Verma + test_images: - ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed -title: Run memcached on Arm servers and measure its performance + +### Tags +skilllevels: Introductory +subjects: Web +armips: +- Neoverse +operatingsystems: +- Linux tools_software_languages: - Memcached -weight: 1 -who_is_this_for: This is an introductory topic for developers who want to use memcached as their in-memory key-value store. + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall + --- diff --git a/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md b/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md index 3df343a7ad..1f23cdae22 100644 --- a/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md @@ -1,37 +1,47 @@ --- -armips: -- Neoverse -author_primary: Pareena Verma +title: Deploy Memcached as a cache for MySQL and PostgreSQL on Arm based servers + description: Deploy Memcached as a cache for MySQL and PostgreSQL on Arm servers -layout: learningpathall + +minutes_to_complete: 60 + +who_is_this_for: This is an advanced topic for developers who want to use memcached as their in-memory key-value store. + learning_objectives: - Deploy memcached as a cache for MySQL on AWS, Azure and GCP Arm based Instance - Deploy memcached as a cache for PostgreSQL on AWS, Azure and GCP Arm based Instance -learning_path_main_page: 'yes' -minutes_to_complete: 60 -operatingsystems: -- Linux + prerequisites: - An Amazon Web Services (AWS) [account](https://aws.amazon.com/) - An Azure portal [account](https://azure.microsoft.com/en-in/get-started/azure-portal) - A Google Cloud [account](https://console.cloud.google.com/) - A machine with [Terraform](/install-guides/terraform/), [AWS CLI](/install-guides/aws-cli), [Google Cloud CLI](/install-guides/gcloud), [Azure CLI](/install-guides/azure-cli), [AWS IAM authenticator](https://docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html), and [Ansible](/install-guides/ansible/) installed -skilllevels: Advanced -subjects: Web + +author_primary: Pareena Verma + + test_images: - ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed -title: Deploy Memcached as a cache for MySQL and PostgreSQL on Arm based servers + +### Tags +skilllevels: Advanced +subjects: Web +armips: +- Neoverse tools_software_languages: - Memcached - SQL - MySQL - PostgreSQL +operatingsystems: +- Linux -weight: 1 -who_is_this_for: This is an advanced topic for developers who want to use memcached as their in-memory key-value store. - +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/content/learning-paths/servers-and-cloud-computing/ml-perf/_index.md b/content/learning-paths/servers-and-cloud-computing/ml-perf/_index.md index 337299f949..68e8730cc7 100644 --- a/content/learning-paths/servers-and-cloud-computing/ml-perf/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/ml-perf/_index.md @@ -1,31 +1,41 @@ --- -armips: -- Neoverse -author_primary: Pareena Verma -layout: learningpathall +title: Measure Machine Learning Inference Performance on Arm servers + +minutes_to_complete: 20 + +who_is_this_for: This is an introductory topic for software developers interested + in benchmarking machine learning workloads on Arm servers. + learning_objectives: - Install and run TensorFlow on your Arm-based cloud server. - Use MLPerf Inference benchmark suite, an open-sourced benchmark from MLCommons to test ML performance on your Arm server. -learning_path_main_page: 'yes' -minutes_to_complete: 20 -operatingsystems: -- Linux + prerequisites: - An [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from an appropriate cloud service provider or an on-premise Arm server. -skilllevels: Introductory -subjects: ML + +author_primary: Pareena Verma + test_images: - ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed -title: Measure Machine Learning Inference Performance on Arm servers + +### Tags +skilllevels: Introductory +subjects: ML +armips: +- Neoverse +operatingsystems: +- Linux tools_software_languages: - TensorFlow -weight: 1 -who_is_this_for: This is an introductory topic for software developers interested - in benchmarking machine learning workloads on Arm servers. + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/content/learning-paths/servers-and-cloud-computing/mysql_benchmark/_index.md b/content/learning-paths/servers-and-cloud-computing/mysql_benchmark/_index.md index de0621b0be..611ea0a84e 100644 --- a/content/learning-paths/servers-and-cloud-computing/mysql_benchmark/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/mysql_benchmark/_index.md @@ -29,9 +29,9 @@ test_images: - ubuntu:22.04 test_link: null test_maintenance: false -test_status: - - passed +### FIXED, DO NOT MODIFY +# ================================================================================ weight: 1 layout: learningpathall learning_path_main_page: 'yes' diff --git a/content/learning-paths/servers-and-cloud-computing/mysql_tune/_index.md b/content/learning-paths/servers-and-cloud-computing/mysql_tune/_index.md index 8847472e14..7b18790948 100644 --- a/content/learning-paths/servers-and-cloud-computing/mysql_tune/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/mysql_tune/_index.md @@ -10,7 +10,7 @@ learning_objectives: prerequisites: - Bare-metal or cloud [installation of MySQL](/learning-paths//servers-and-cloud-computing/mysql) - + author_primary: Julio Suarez skilllevels: Advanced @@ -29,9 +29,9 @@ test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: - - passed +### FIXED, DO NOT MODIFY +# ================================================================================ weight: 1 layout: learningpathall learning_path_main_page: 'yes' diff --git a/content/learning-paths/servers-and-cloud-computing/nginx/_index.md b/content/learning-paths/servers-and-cloud-computing/nginx/_index.md index 19aa9efde5..015642b2b1 100644 --- a/content/learning-paths/servers-and-cloud-computing/nginx/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/nginx/_index.md @@ -23,7 +23,7 @@ subjects: Web armips: - Neoverse tools_software_languages: - - Nginx + - Nginx operatingsystems: - Linux @@ -31,8 +31,6 @@ test_images: - ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed ### FIXED, DO NOT MODIFY # ================================================================================ diff --git a/content/learning-paths/servers-and-cloud-computing/postgresql_tune/_index.md b/content/learning-paths/servers-and-cloud-computing/postgresql_tune/_index.md index c2cb101778..cebe432772 100644 --- a/content/learning-paths/servers-and-cloud-computing/postgresql_tune/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/postgresql_tune/_index.md @@ -16,8 +16,6 @@ author_primary: Julio Suarez test_images: - ubuntu:latest test_maintenance: true -test_status: - - passed ### Tags skilllevels: Advanced diff --git a/content/learning-paths/servers-and-cloud-computing/ran/_index.md b/content/learning-paths/servers-and-cloud-computing/ran/_index.md index f4b87a7be2..978ed36759 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/_index.md @@ -1,32 +1,42 @@ --- -armips: -- Neoverse -author_primary: Ronan Synnott -layout: learningpathall +title: Get started with the Arm 5G RAN Acceleration Library (ArmRAL) + +minutes_to_complete: 15 + +who_is_this_for: This is an introductory topic for software developers new to the + Arm RAN Acceleration Library (ArmRAL). + learning_objectives: - Build and install the Arm RAN Acceleration Library - Test the capabilities of your platform -learning_path_main_page: 'yes' -minutes_to_complete: 15 -operatingsystems: -- Linux + prerequisites: - An Arm computer running Linux. Cloud instances can be used, refer to the list of [Arm cloud service providers](/learning-paths/servers-and-cloud-computing/csp/). -skilllevels: Introductory -subjects: Performance and Architecture + +author_primary: Ronan Synnott + test_images: - ubuntu:latest test_link: null test_maintenance: true -test_status: -- passed -title: Get started with the Arm 5G RAN Acceleration Library (ArmRAL) + +### Tags +skilllevels: Introductory +subjects: Performance and Architecture +armips: +- Neoverse +operatingsystems: +- Linux tools_software_languages: - ArmRAL - 5G - GCC -weight: 1 -who_is_this_for: This is an introductory topic for software developers new to the - Arm RAN Acceleration Library (ArmRAL). + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/content/learning-paths/servers-and-cloud-computing/snappy/_index.md b/content/learning-paths/servers-and-cloud-computing/snappy/_index.md index ae1d666326..cb30f2197d 100644 --- a/content/learning-paths/servers-and-cloud-computing/snappy/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/snappy/_index.md @@ -1,30 +1,40 @@ --- -armips: -- Neoverse -author_primary: Pareena Verma -layout: learningpathall +title: Measure performance of compression libraries on Arm servers + +minutes_to_complete: 10 + +who_is_this_for: This is an introductory topic for software developers using compression + libraries on Arm servers. + learning_objectives: - Install and run lzbench with snappy and zstd - Measure compression library performance running on 64-bit Arm AWS EC2 instance -learning_path_main_page: 'yes' -minutes_to_complete: 10 -operatingsystems: -- Linux + prerequisites: - An [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from an appropriate cloud service provider. -skilllevels: Introductory -subjects: Libraries + +author_primary: Pareena Verma + test_images: - ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed -title: Measure performance of compression libraries on Arm servers + +### Tags +skilllevels: Introductory +subjects: Libraries +armips: +- Neoverse +operatingsystems: +- Linux tools_software_languages: - snappy -weight: 1 -who_is_this_for: This is an introductory topic for software developers using compression - libraries on Arm servers. + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/content/learning-paths/servers-and-cloud-computing/vectorscan/_index.md b/content/learning-paths/servers-and-cloud-computing/vectorscan/_index.md index 11f93284e0..4e22800f75 100644 --- a/content/learning-paths/servers-and-cloud-computing/vectorscan/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/vectorscan/_index.md @@ -4,7 +4,7 @@ title: Install Vectorscan (Hyperscan on Arm) and use it with Snort 3 minutes_to_complete: 15 who_is_this_for: This is an introductory topic for software developers using Hyperscan who want to migrate to Arm. - + learning_objectives: - Install and run Vectorscan on an Arm-based instance @@ -22,7 +22,7 @@ subjects: Libraries armips: - Neoverse tools_software_languages: - - Vectorscan + - Vectorscan operatingsystems: - Linux @@ -30,8 +30,6 @@ test_images: - ubuntu:latest test_link: https://github.com/armflorentlebeau/arm-learning-paths/actions/runs/4312122327 test_maintenance: true -test_status: -- passed ### FIXED, DO NOT MODIFY # ================================================================================ diff --git a/content/learning-paths/servers-and-cloud-computing/zlib/_index.md b/content/learning-paths/servers-and-cloud-computing/zlib/_index.md index 5dc567ae24..fe9e1bef1a 100644 --- a/content/learning-paths/servers-and-cloud-computing/zlib/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/zlib/_index.md @@ -1,30 +1,40 @@ --- -armips: -- Neoverse -author_primary: Jason Andrews -layout: learningpathall +title: Learn how to build and use Cloudflare zlib on Arm servers + +minutes_to_complete: 30 + +who_is_this_for: This is an introductory topic for software developers to learn how + to to build and use Cloudflare zlib on Arm servers. + learning_objectives: - Build zlib-cloudflare on an Arm server - Run example applications using zlib - Measure and analyze performance to see improvements -learning_path_main_page: 'yes' -minutes_to_complete: 30 -operatingsystems: -- Linux + prerequisites: - An Arm Linux computer or an [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider running Ubuntu 20.04 or Ubuntu 22.04. -skilllevels: Introductory -subjects: Libraries + +author_primary: Jason Andrews + test_images: - ubuntu:latest -test_link: +test_link: test_maintenance: true -test_status: -- passed -title: Learn how to build and use Cloudflare zlib on Arm servers + +### Tags +armips: +- Neoverse +skilllevels: Introductory +subjects: Libraries +operatingsystems: +- Linux tools_software_languages: - zlib -weight: 1 -who_is_this_for: This is an introductory topic for software developers to learn how - to to build and use Cloudflare zlib on Arm servers. + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +layout: learningpathall --- diff --git a/tools/check.py b/tools/check.py index ff1e940da8..91772899eb 100644 --- a/tools/check.py +++ b/tools/check.py @@ -184,12 +184,21 @@ def check(json_file, start, stop, md_article): test_type = test["type"] # Check type if test_type == "bash": - # chmod cmd file - run_command = [f"chmod +x {test_cmd_filename}"] - subprocess.run(run_command, shell=True, capture_output=True) - logging.debug(run_command) - # execute file as is with bash - run_command = [f"bash ./{test_cmd_filename}"] + if "ubuntu" in test_image: + # chmod cmd file + run_command = [f"chmod +x {test_cmd_filename}"] + subprocess.run(run_command, shell=True, capture_output=True) + logging.debug(run_command) + # execute file as is with bash + run_command = [f"bash ./{test_cmd_filename}"] + elif "fedora" in test_image: + container_name = init_container(i_img=n_image, img=test_image) + logging.info(f"{container_name} initialized") + # copy files to docker + docker_cmd = [f"docker cp {test_cmd_filename} test_{n_image}:/home/{username}/"] + subprocess.run(docker_cmd, shell=True, capture_output=True) + logging.debug(docker_cmd) + run_command = [f"docker exec -u {username} -w /home/{username} test_{n_image} bash {test_cmd_filename}"] elif test_type == "fvp": # Start instance for image if start: From 0e05710ba828ed76ed07b8f4bf178a7b0cdf4125 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Fri, 31 Jan 2025 15:51:17 +0100 Subject: [PATCH 002/122] Skip tests where target and image don't match --- content/install-guides/acfl.md | 11 ++++++----- content/install-guides/forge.md | 2 +- tools/check.py | 7 ++++++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 292f677212..9a8ef026fa 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -76,7 +76,7 @@ You are now ready to install ACfL [manually](#manual) or with [Spack](#spack). Use an Arm recommended script to select, download, and install your preferred ACfL package. -```console +```bash bash <(curl -L https://developer.arm.com/-/cdn-downloads/permalink/Arm-Compiler-for-Linux/Package/install.sh) ``` @@ -145,14 +145,15 @@ Arm Compiler for Linux is available to install with the Ubuntu system package ma ### Setup the ACfL package repository: -Add the ACfL `apt` package repository to your Ubuntu 20.04 or 22.04 system: +Add the ACfL `apt` package repository to your system. These instructions apply for Ubuntu 22.04. You can check the [available versions](https://developer.arm.com/packages/) for other releases. ```bash { target="ubuntu:latest" } +export sudo apt update sudo apt install -y curl source /etc/os-release -curl "https://developer.arm.com/packages/ACfL%3A${NAME}-${VERSION_ID/%.*/}/${VERSION_CODENAME}/Release.key" | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc -echo "deb https://developer.arm.com/packages/ACfL%3A${NAME}-${VERSION_ID/%.*/}/${VERSION_CODENAME}/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list +curl "https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key" | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update ``` @@ -162,7 +163,7 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: -```bash { target="ubuntu:latest" } +```bash sudo apt install acfl ``` diff --git a/content/install-guides/forge.md b/content/install-guides/forge.md index 704f28a305..1be1342d0e 100644 --- a/content/install-guides/forge.md +++ b/content/install-guides/forge.md @@ -73,7 +73,7 @@ Run the Windows file executable to install the Linaro Forge Remote Client. Optionally, you can run the installer executable with a graphical interface. -```bash +```console cd linaro-forge-24.0.3-linux-aarch64/ ./installer ``` diff --git a/tools/check.py b/tools/check.py index 91772899eb..6566e4ce12 100644 --- a/tools/check.py +++ b/tools/check.py @@ -163,7 +163,8 @@ def check(json_file, start, stop, md_article): test_target = test.get("target") if test_target and test_target != test_image: - pass + bar(skipped=True) + continue elif not test_target: pass elif test_target: @@ -199,6 +200,10 @@ def check(json_file, start, stop, md_article): subprocess.run(docker_cmd, shell=True, capture_output=True) logging.debug(docker_cmd) run_command = [f"docker exec -u {username} -w /home/{username} test_{n_image} bash {test_cmd_filename}"] + else: + logging.debug(f"Image {test_image} not supported for testing. Contact the maintainers if you think this is a mistake.") + bar(skipped=True) + continue elif test_type == "fvp": # Start instance for image if start: From 86240401dde615e1e84f70c348deda8fc50a309b Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Thu, 6 Feb 2025 15:56:07 +0100 Subject: [PATCH 003/122] Change how fedora container is initialized --- content/install-guides/acfl.md | 10 +++++----- tools/check.py | 10 +++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index b6e7af0130..1651566a39 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -177,7 +177,7 @@ Arm Compiler for Linux is available to install with either the `dnf` or `yum` sy Install ACfL and prerequisites from the Amazon Linux 2023 `rpm` package repository with `dnf`: -```bash +```bash { target="fedora:latest" } sudo dnf update sudo dnf install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo @@ -186,7 +186,7 @@ sudo dnf install acfl Or using the equivalent `yum` commands: -```bash +```bash { target="fedora:latest" } sudo yum update sudo yum install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo @@ -195,7 +195,7 @@ sudo yum install acfl The ACfL tools are now ready to use. -### Red Hat Enterprise Linux (RHEL) 9 +### Red Hat Enterprise Linux (RHEL) 9 Arm Compiler for Linux is available to install with either the `dnf` or `yum` system package manager. @@ -203,7 +203,7 @@ Arm Compiler for Linux is available to install with either the `dnf` or `yum` sy Install ACfL and prerequisites from the RHEL 9 `rpm` package repository with `dnf`: -```bash +```bash { target="fedora:latest" } sudo dnf update sudo dnf install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo @@ -212,7 +212,7 @@ sudo dnf install acfl Or using the equivalent `yum` commands: -```bash +```bash { target="fedora:latest" } sudo yum update sudo yum install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo diff --git a/tools/check.py b/tools/check.py index 6566e4ce12..b53e13c348 100644 --- a/tools/check.py +++ b/tools/check.py @@ -153,6 +153,11 @@ def check(json_file, start, stop, md_article): test_images = data["test_images"] for n_image, test_image in zip(range(0, len(test_images)), test_images): logging.info(f"--- Testing on {test_image} ---") + + if test_image != "ubuntu": + container_name = init_container(i_img=n_image, img=test_image) + logging.info(f"{container_name} initialized") + with alive_progress.alive_bar(data["ntests"], title=test_image, stats=False) as bar: for n_test in range(0, data["ntests"]): if dictionary_lookup(data, f"{n_test}"): @@ -192,9 +197,7 @@ def check(json_file, start, stop, md_article): logging.debug(run_command) # execute file as is with bash run_command = [f"bash ./{test_cmd_filename}"] - elif "fedora" in test_image: - container_name = init_container(i_img=n_image, img=test_image) - logging.info(f"{container_name} initialized") + elif "fedora" in test_target: # copy files to docker docker_cmd = [f"docker cp {test_cmd_filename} test_{n_image}:/home/{username}/"] subprocess.run(docker_cmd, shell=True, capture_output=True) @@ -298,6 +301,7 @@ def check(json_file, start, stop, md_article): logging.info(f"Removing files that were created during testing from repository") for path in paths_to_remove: if os.path.isfile(path) or os.path.islink(path): + os.chmod(path, 0o777) os.remove(path) elif os.path.isdir(path): From 203da374ab196b847bd159c79dbf297bfa3be644 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Thu, 6 Feb 2025 16:14:27 +0100 Subject: [PATCH 004/122] Assign test target default value, catch permissions exception --- tools/check.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/check.py b/tools/check.py index b53e13c348..348420891c 100644 --- a/tools/check.py +++ b/tools/check.py @@ -166,7 +166,7 @@ def check(json_file, start, stop, md_article): logging.info(f"Error getting test from JSON file, skipping") continue - test_target = test.get("target") + test_target = test.get("target") or "" if test_target and test_target != test_image: bar(skipped=True) continue @@ -301,8 +301,11 @@ def check(json_file, start, stop, md_article): logging.info(f"Removing files that were created during testing from repository") for path in paths_to_remove: if os.path.isfile(path) or os.path.islink(path): - os.chmod(path, 0o777) - os.remove(path) + try: + os.chmod(path, 0o777) + os.remove(path) + except PermissionError as e: + logging.debug(f"Failed to remove {path} with error: {e}") elif os.path.isdir(path): shutil.rmtree(path) From 3ed9451e52fd727d8972989e8adc3828e44a8886 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Thu, 6 Feb 2025 16:18:35 +0100 Subject: [PATCH 005/122] Update condition --- tools/check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check.py b/tools/check.py index 348420891c..7a8d79cd1a 100644 --- a/tools/check.py +++ b/tools/check.py @@ -154,7 +154,7 @@ def check(json_file, start, stop, md_article): for n_image, test_image in zip(range(0, len(test_images)), test_images): logging.info(f"--- Testing on {test_image} ---") - if test_image != "ubuntu": + if "ubuntu" not in test_image: container_name = init_container(i_img=n_image, img=test_image) logging.info(f"{container_name} initialized") From 67171eaf2912b465de19f47e676687edd15c7b82 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Thu, 6 Feb 2025 17:25:06 +0100 Subject: [PATCH 006/122] Catch exception for directories --- tools/check.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tools/check.py b/tools/check.py index 7a8d79cd1a..6168424c52 100644 --- a/tools/check.py +++ b/tools/check.py @@ -300,16 +300,18 @@ def check(json_file, start, stop, md_article): if paths_to_remove: logging.info(f"Removing files that were created during testing from repository") for path in paths_to_remove: - if os.path.isfile(path) or os.path.islink(path): - try: - os.chmod(path, 0o777) - os.remove(path) - except PermissionError as e: - logging.debug(f"Failed to remove {path} with error: {e}") + try: + + if os.path.isfile(path) or os.path.islink(path): + os.chmod(path, 0o777) + os.remove(path) - elif os.path.isdir(path): - shutil.rmtree(path) - logging.debug(f"Removed {path}") + + elif os.path.isdir(path): + shutil.rmtree(path) + logging.debug(f"Removed {path}") + except PermissionError as e: + logging.debug(f"Failed to remove {path} with error: {e}") # Stop instance if stop: From ac0821755b7c762f5c6aa063f8feefd8399cd405 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 10 Feb 2025 10:58:21 +0100 Subject: [PATCH 007/122] Update acfl.md, run on Ubuntu 22.04 --- .github/workflows/test-lp.yml | 2 +- content/install-guides/acfl.md | 20 ++++++++++---------- content/install-guides/oci-cli.md | 2 +- tools/patch.py | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index 4371ef45ac..af227944bc 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -2,7 +2,7 @@ name: Test Learning Path on: pull_request jobs: Test-Pull-Request: - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-22.04-arm steps: - name: Check out repository code uses: actions/checkout@v4 diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 1651566a39..561100fce7 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -76,7 +76,7 @@ You are now ready to install ACfL [manually](#manual) or with [Spack](#spack). Use an Arm recommended script to select, download, and install your preferred ACfL package. -```bash +```console bash <(curl -L https://developer.arm.com/-/cdn-downloads/permalink/Arm-Compiler-for-Linux/Package/install.sh) ``` @@ -181,7 +181,7 @@ Install ACfL and prerequisites from the Amazon Linux 2023 `rpm` package reposito sudo dnf update sudo dnf install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo -sudo dnf install acfl +sudo dnf -y install acfl ``` Or using the equivalent `yum` commands: @@ -190,7 +190,7 @@ Or using the equivalent `yum` commands: sudo yum update sudo yum install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo -sudo yum install acfl +sudo yum -y install acfl ``` The ACfL tools are now ready to use. @@ -207,7 +207,7 @@ Install ACfL and prerequisites from the RHEL 9 `rpm` package repository with `dn sudo dnf update sudo dnf install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo -sudo dnf install acfl +sudo dnf -y install acfl ``` Or using the equivalent `yum` commands: @@ -216,7 +216,7 @@ Or using the equivalent `yum` commands: sudo yum update sudo yum install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo -sudo yum install acfl +sudo yum -y install acfl ``` The ACfL tools are now ready to use. @@ -316,7 +316,7 @@ ACfL is now [ready to use](#armclang). To get started with the Arm C/C++ Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } armclang --version ``` @@ -340,7 +340,7 @@ armclang hello.c -o hello Run the application with: -```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } ./hello ``` @@ -354,7 +354,7 @@ Hello, C World! To get started with the Arm Fortran Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } armflang --version ``` @@ -368,12 +368,12 @@ end program hello ``` Build the application with: -```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } armflang hello.f90 -o hello ``` Run the application with: -```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } ./hello ``` diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 99bb7751a1..e1a4bdd6a7 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -70,7 +70,7 @@ source $HOME/.bashrc Verify OCI CLI is installed using the `--version` option: -```bash { target="ubuntu:latest", env_source="~/.bashrc" } +```bash { target="ubuntu:latest", env_source="$HOME/.bashrc" } oci --version ``` diff --git a/tools/patch.py b/tools/patch.py index 36513cf7c1..a8342c160e 100644 --- a/tools/patch.py +++ b/tools/patch.py @@ -19,7 +19,7 @@ def patch(article_path: str, results: dict, link: str): content_type, sw_category, content_title = article_path_parts article_path = PurePath(article_path, "_index.md") elif "install-guides" in article_path_parts: - content_type, content_title = article_path_parts + content_type, content_title, _ = article_path_parts content_title = content_title.strip(".md") sw_category = content_type else: From 9bfa7309efd3843bba5607b0df9ff4aa73eb3d5e Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 10 Feb 2025 13:10:15 +0100 Subject: [PATCH 008/122] Update intrinsics, codec LPs --- content/learning-paths/cross-platform/intrinsics/intro.md | 6 +++--- .../servers-and-cloud-computing/codec/x265.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/learning-paths/cross-platform/intrinsics/intro.md b/content/learning-paths/cross-platform/intrinsics/intro.md index b281b64da2..68c715617e 100644 --- a/content/learning-paths/cross-platform/intrinsics/intro.md +++ b/content/learning-paths/cross-platform/intrinsics/intro.md @@ -8,11 +8,11 @@ weight: 2 Migrating C/C++ applications from x64 to Arm requires recompiling the source code for the Arm architecture. A simple recompile works much of the time, but not always. -SIMD extensions are one of the common barriers encountered when porting C/C++ applications from x64 to Arm. This article is a short background on intrinsics and how to identify them in code. This Learning Path presents options for how to get the code compiled and running on an Arm-based platform. +SIMD extensions are one of the common barriers encountered when porting C/C++ applications from x64 to Arm. This article is a short background on intrinsics and how to identify them in code. This Learning Path presents options for how to get the code compiled and running on an Arm-based platform. ## Intrinsics -Intrinsics are functions which are built into the compiler and not part of a library. They look like function calls, but don’t require an actual function call. When the compiler encounters intrinsics it directly substitutes a sequence of instructions. Intrinsics are often used to access special instructions that don’t have a direct mapping from C/C++ or when performance optimization is needed. +Intrinsics are functions which are built into the compiler and not part of a library. They look like function calls, but don’t require an actual function call. When the compiler encounters intrinsics it directly substitutes a sequence of instructions. Intrinsics are often used to access special instructions that don’t have a direct mapping from C/C++ or when performance optimization is needed. One use of intrinsics is to access SIMD (single-instruction, multiple-data) instructions directly from C/C++ for improved application performance. Intrinsics are easier to work with compared to assembly language, but they often pose a challenge when porting source code to a new architecture. @@ -21,7 +21,7 @@ Intel Streaming SIMD Extensions (SSE) and [Arm NEON](https://developer.arm.com/d There are generally 3 ways to program SIMD hardware: - The C/C++ compiler recognizes opportunities to use SIMD instructions and inserts them automatically (with or without some guidance) - Intrinsics to access SIMD instructions directly from C/C++ source code -- Assembly programming +- Assembly programming ## Source code example diff --git a/content/learning-paths/servers-and-cloud-computing/codec/x265.md b/content/learning-paths/servers-and-cloud-computing/codec/x265.md index 602d6713e8..2d33303084 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/x265.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/x265.md @@ -8,11 +8,11 @@ weight: 2 `x265` is an open-source H.265/HVEC encoder that offers very high compression efficiency and performance. There have been significant efforts to optimize the open-source libx265 implementation of the H.265 encoder on Arm Neoverse platforms which supports Neon instructions. The optimized code is available on [Bitbucket](https://bitbucket.org/multicoreware/x265_git/) -Install GCC for your Arm Linux distribution. Refer to the [install guide](/install-guides/gcc/native/) for additional information. +Install GCC for your Arm Linux distribution. Refer to the [install guide](/install-guides/gcc/native/) for additional information. Install `Cmake` and other dependencies: ```bash -sudo apt install git wget cmake cmake-curses-gui -y +sudo apt install wget git-all cmake cmake-curses-gui -y ``` Install `Build-essential` and `yasm` ```bash @@ -23,7 +23,7 @@ sudo apt install build-essential yasm -y ```bash git clone https://bitbucket.org/multicoreware/x265_git.git -cd x265_git/build/linux +cd x265_git/build/aarch64-linux ./make-Makefiles.bash make ``` From f459513dbc1ff26363d778fa075ca22ed0461bb6 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 10 Feb 2025 14:19:02 +0100 Subject: [PATCH 009/122] Address test errors --- content/install-guides/acfl.md | 8 ++++---- content/install-guides/gcc/native.md | 4 ++-- tools/check.py | 2 +- tools/patch.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 32ecf539f5..1fdf42b960 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -179,7 +179,7 @@ Install ACfL and prerequisites from the Amazon Linux 2023 `rpm` package reposito ```bash { target="fedora:latest" } sudo dnf update -sudo dnf install 'dnf-command(config-manager)' procps psmisc make environment-modules +sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo sudo dnf -y install acfl ``` @@ -188,7 +188,7 @@ Or using the equivalent `yum` commands: ```bash { target="fedora:latest" } sudo yum update -sudo yum install 'dnf-command(config-manager)' procps psmisc make environment-modules +sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo sudo yum -y install acfl ``` @@ -205,7 +205,7 @@ Install ACfL and prerequisites from the RHEL 9 `rpm` package repository with `dn ```bash { target="fedora:latest" } sudo dnf update -sudo dnf install 'dnf-command(config-manager)' procps psmisc make environment-modules +sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo sudo dnf -y install acfl ``` @@ -214,7 +214,7 @@ Or using the equivalent `yum` commands: ```bash { target="fedora:latest" } sudo yum update -sudo yum install 'dnf-command(config-manager)' procps psmisc make environment-modules +sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo sudo yum -y install acfl ``` diff --git a/content/install-guides/gcc/native.md b/content/install-guides/gcc/native.md index 536791b72a..c9c3c3a0e3 100644 --- a/content/install-guides/gcc/native.md +++ b/content/install-guides/gcc/native.md @@ -65,12 +65,12 @@ To install the most common development tools use the commands below. If the mach ```bash { target="fedora:latest" } sudo dnf update -y -sudo dnf groupinstall 'Development Tools' -y +sudo dnf group install 'Development Tools' -y ``` If `sudo` is not available become _root_ and omit the `sudo`. ```console dnf update -y -dnf groupinstall 'Development Tools' -y +dnf group install 'Development Tools' -y ``` ## Setting up product license diff --git a/tools/check.py b/tools/check.py index 6168424c52..3d8ad1e63b 100644 --- a/tools/check.py +++ b/tools/check.py @@ -154,7 +154,7 @@ def check(json_file, start, stop, md_article): for n_image, test_image in zip(range(0, len(test_images)), test_images): logging.info(f"--- Testing on {test_image} ---") - if "ubuntu" not in test_image: + if test_image != "ubuntu:latest": container_name = init_container(i_img=n_image, img=test_image) logging.info(f"{container_name} initialized") diff --git a/tools/patch.py b/tools/patch.py index a8342c160e..5c6044f94b 100644 --- a/tools/patch.py +++ b/tools/patch.py @@ -19,7 +19,7 @@ def patch(article_path: str, results: dict, link: str): content_type, sw_category, content_title = article_path_parts article_path = PurePath(article_path, "_index.md") elif "install-guides" in article_path_parts: - content_type, content_title, _ = article_path_parts + content_type, content_title, *others = article_path_parts content_title = content_title.strip(".md") sw_category = content_type else: From 2f094456ca66c857e7bc1aa01ddd2c3b6e6cd062 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 10 Feb 2025 16:20:33 +0100 Subject: [PATCH 010/122] Update acfl.md --- content/install-guides/acfl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 1fdf42b960..19808bdd0e 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -154,7 +154,7 @@ export sudo apt update sudo apt install -y curl environment-modules python3 libc6-dev . /etc/os-release -curl "https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key" | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +curl -qO "https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key" | apt-key add - echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update ``` From 6d3d9c3166bd1ec347364c8dedb76af27e67521e Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 10 Feb 2025 16:32:26 +0100 Subject: [PATCH 011/122] Update acfl.md --- content/install-guides/acfl.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 19808bdd0e..b7382f0864 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -152,10 +152,10 @@ Add the ACfL `apt` package repository to your system. These instructions apply f ```bash { target="ubuntu:latest" } export sudo apt update -sudo apt install -y curl environment-modules python3 libc6-dev +sudo apt install -y wget gpg environment-modules python3 libc6-dev . /etc/os-release -curl -qO "https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key" | apt-key add - -echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list +wget -qO - https://developer.arm.com/packages/ACfL:Ubuntu-20/focal/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" >> /etc/apt/sources.list.d/developer-arm-com.list sudo apt update ``` From ec39d60bb14c9c701a9120ce1ba2df747c32b747 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 10 Feb 2025 16:41:39 +0100 Subject: [PATCH 012/122] Update acfl.md --- content/install-guides/acfl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index b7382f0864..5c7f0b04d2 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -154,7 +154,7 @@ export sudo apt update sudo apt install -y wget gpg environment-modules python3 libc6-dev . /etc/os-release -wget -qO - https://developer.arm.com/packages/ACfL:Ubuntu-20/focal/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" >> /etc/apt/sources.list.d/developer-arm-com.list sudo apt update ``` From e3178d3f374387ba3872ac828fd3ddbc9f40ef68 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 11 Feb 2025 13:45:19 +0100 Subject: [PATCH 013/122] Update acfl.md --- content/install-guides/acfl.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 5c7f0b04d2..aa3937c0fe 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -150,13 +150,14 @@ Arm Compiler for Linux is available to install with the Ubuntu system package ma Add the ACfL `apt` package repository to your system. These instructions apply for Ubuntu 22.04. You can check the [available versions](https://developer.arm.com/packages/) for other releases. ```bash { target="ubuntu:latest" } -export sudo apt update -sudo apt install -y wget gpg environment-modules python3 libc6-dev +sudo apt install -y wge gnupg gpg environment-modules python3 libc6-dev . /etc/os-release wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc -echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" >> /etc/apt/sources.list.d/developer-arm-com.list +echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" > /etc/apt/sources.list.d/developer-arm-com.list sudo apt update +sudo apt install acfl + ``` The ACfL Ubuntu package repository is now ready to use. From 1b408b54fac4429be7526c3acd7a9a1df3a8da93 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 11 Feb 2025 13:59:08 +0100 Subject: [PATCH 014/122] Update acfl.md --- content/install-guides/acfl.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index aa3937c0fe..ba42d14a8c 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -152,12 +152,9 @@ Add the ACfL `apt` package repository to your system. These instructions apply f ```bash { target="ubuntu:latest" } sudo apt update sudo apt install -y wge gnupg gpg environment-modules python3 libc6-dev -. /etc/os-release wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc -echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" > /etc/apt/sources.list.d/developer-arm-com.list +echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee etc/apt/sources.list.d/developer-arm-com.list sudo apt update -sudo apt install acfl - ``` The ACfL Ubuntu package repository is now ready to use. From 4c9048ec707c6c1d06abb70443783dd596227c75 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 11 Feb 2025 16:27:00 +0100 Subject: [PATCH 015/122] Update acfl.md --- content/install-guides/acfl.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index ba42d14a8c..f673f068d7 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -151,9 +151,9 @@ Add the ACfL `apt` package repository to your system. These instructions apply f ```bash { target="ubuntu:latest" } sudo apt update -sudo apt install -y wge gnupg gpg environment-modules python3 libc6-dev -wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc -echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee etc/apt/sources.list.d/developer-arm-com.list +sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev +wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update ``` From 87e176aa140b8a912fd3db44496de0d518863d9d Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 11 Feb 2025 16:45:45 +0100 Subject: [PATCH 016/122] Update acfl.md --- content/install-guides/acfl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index f673f068d7..ef35862fc5 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -154,7 +154,7 @@ sudo apt update sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list -sudo apt update +sudo apt update && sudo apt upgrade -y && sudo apt --fix-broken install -y ``` The ACfL Ubuntu package repository is now ready to use. From c515a286d0260393de0ca440d2e7292fc42d7b79 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 12 Feb 2025 09:19:02 +0100 Subject: [PATCH 017/122] Update acfl.md --- content/install-guides/acfl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index ef35862fc5..20d6a32d6f 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -164,7 +164,7 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: ```bash -sudo apt install acfl +sudo apt NEEDRESTART_MODE=a install acfl ``` ### Amazon Linux 2023 From 027470c86f924f9c870621b0050a5231fee60c57 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 24 Feb 2025 13:58:56 +0100 Subject: [PATCH 018/122] Update LPs for test failures --- content/install-guides/acfl.md | 4 +-- .../clickhouse/clickhouse.md | 20 +++++++------- .../servers-and-cloud-computing/codec/x265.md | 10 +++---- .../geekbench/geekbench.md | 18 ++++++------- .../ml-perf/ml-perf.md | 13 +++++----- .../servers-and-cloud-computing/ran/armral.md | 13 +++++----- .../vectorscan/install.md | 22 ++++++++-------- .../vectorscan/snort.md | 26 +++++++++---------- .../servers-and-cloud-computing/zlib/perf.md | 10 +++---- .../zlib/py-zlib.md | 12 ++++----- 10 files changed, 75 insertions(+), 73 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 20d6a32d6f..4711945f57 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -163,8 +163,8 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: -```bash -sudo apt NEEDRESTART_MODE=a install acfl +```console +sudo apt install acfl ``` ### Amazon Linux 2023 diff --git a/content/learning-paths/servers-and-cloud-computing/clickhouse/clickhouse.md b/content/learning-paths/servers-and-cloud-computing/clickhouse/clickhouse.md index 62b3a3460c..5d5459d1eb 100644 --- a/content/learning-paths/servers-and-cloud-computing/clickhouse/clickhouse.md +++ b/content/learning-paths/servers-and-cloud-computing/clickhouse/clickhouse.md @@ -12,7 +12,7 @@ ClickBench is open-source software used to evaluate the performance of various d ## Before you begin -You will need an Arm server or an [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider running a recent version of Ubuntu for Arm. +You will need an Arm server or an [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider running a recent version of Ubuntu for Arm. You will also need sufficient storage on the instance for the web-analytics dataset used for measuring ClickHouse performance, 500 GB is recommended. @@ -28,7 +28,7 @@ curl https://clickhouse.com/ | sh sudo DEBIAN_FRONTEND=noninteractive ./clickhouse install ``` -2. Set the compression method for `clickhouse-server` to use `zstd` by running the commands: +2. Set the compression method for `clickhouse-server` to use `zstd` by running the commands: ```bash echo " @@ -44,7 +44,7 @@ compression: sudo clickhouse start ``` -## Run ClickBench +## Run ClickBench 1. Clone the ClickBench repository: @@ -60,9 +60,9 @@ cd ClickBench/clickhouse clickhouse-client < create.sql ``` -3. Load the benchmark data +3. Load the benchmark data -The data file is very large and takes more than 10 minutes to download and uncompress. +The data file is very large and takes more than 10 minutes to download and uncompress. ```console wget --continue 'https://datasets.clickhouse.com/hits_compatible/hits.tsv.gz' @@ -71,7 +71,7 @@ gzip -d hits.tsv.gz 4. Import the data using `clickhouse-client`: -Importing the data takes more than 5 minutes. +Importing the data takes more than 5 minutes. ```console clickhouse-client --time --query "INSERT INTO hits FORMAT TSV" < hits.tsv @@ -79,17 +79,17 @@ clickhouse-client --time --query "INSERT INTO hits FORMAT TSV" < hits.tsv The data used is the [Anonymized Web Analytics dataset](https://clickhouse.com/docs/en/getting-started/example-datasets/metrica/). -5. Execute the benchmark +5. Execute the benchmark The script loops through each query three times. A total of 43 queries are run. -```bash +```console ./run.sh ``` ### ClickBench results -When you execute the `run.sh` script, the query processing time for each individual query is displayed on the console. +When you execute the `run.sh` script, the query processing time for each individual query is displayed on the console. The three comma separated values represent the query latency time for each of the three times the query is run. @@ -101,6 +101,6 @@ The three comma separated values represent the query latency time for each of th The summarized results are also saved to the `results.csv` file in the current directory. The `results.csv` file has 129 lines (43 queries each run 3 times). -You can try different types of hardware and compare results. For example, if you use AWS try c6g.2xlarge and compare to c7g.2xlarge to see the difference between AWS Graviton2 and Graviton3 processors. +You can try different types of hardware and compare results. For example, if you use AWS try c6g.2xlarge and compare to c7g.2xlarge to see the difference between AWS Graviton2 and Graviton3 processors. diff --git a/content/learning-paths/servers-and-cloud-computing/codec/x265.md b/content/learning-paths/servers-and-cloud-computing/codec/x265.md index 2d33303084..e51f13ebde 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/x265.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/x265.md @@ -12,18 +12,18 @@ Install GCC for your Arm Linux distribution. Refer to the [install guide](/insta Install `Cmake` and other dependencies: ```bash -sudo apt install wget git-all cmake cmake-curses-gui -y +sudo apt install wget git-all cmake cmake-curses-gui build-essential -y ``` -Install `Build-essential` and `yasm` -```bash -sudo apt install build-essential yasm -y +If you are running on x86, also install `yasm` +```console +sudo apt install yasm -y ``` ## Download and build x265 source ```bash git clone https://bitbucket.org/multicoreware/x265_git.git -cd x265_git/build/aarch64-linux +cd x265_git/build/linux ./make-Makefiles.bash make ``` diff --git a/content/learning-paths/servers-and-cloud-computing/geekbench/geekbench.md b/content/learning-paths/servers-and-cloud-computing/geekbench/geekbench.md index d8d9a39759..2470664c94 100644 --- a/content/learning-paths/servers-and-cloud-computing/geekbench/geekbench.md +++ b/content/learning-paths/servers-and-cloud-computing/geekbench/geekbench.md @@ -4,11 +4,11 @@ title: Download and run Geekbench weight: 2 --- -When selecting Arm-based hardware, you may need a way to compare different systems and select a hardware configuration for your workload. Running benchmarks on different systems with different configurations is a good way to get more information about system performance. +When selecting Arm-based hardware, you may need a way to compare different systems and select a hardware configuration for your workload. Running benchmarks on different systems with different configurations is a good way to get more information about system performance. [Geekbench](https://www.geekbench.com/index.html) is a cross-platform benchmark that makes it easy to measure system performance. [Preview Versions](https://www.geekbench.com/preview/) are available for Linux on Arm. You can also check the Geekbench [downloads](https://www.geekbench.com/download/) area for additional operating system options. -Geekbench provides a single-core score and a multi-core score, as well as individual performance scores for specific tests. You can use the scores to compare different systems and different configurations. A higher score is better. +Geekbench provides a single-core score and a multi-core score, as well as individual performance scores for specific tests. You can use the scores to compare different systems and different configurations. A higher score is better. Additional features are available with a purchased [license](https://www.primatelabs.com/store/). @@ -16,7 +16,7 @@ Additional features are available with a purchased [license](https://www.primate You will need a local Arm platform or an [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider. Geekbench 5 requires `Ubuntu 16.04 LTS` or later and Geekbench 6 requires `Ubuntu 18.04 LTS` or later. -## Download +## Download Both Geekbench 5 and Geekbench 6 binaries are available to download. The instructions below are for the Geekbench 6 preview version. @@ -26,21 +26,21 @@ Both Geekbench 5 and Geekbench 6 binaries are available to download. The instruc sudo apt install -y wget ``` -2. Download, extract the archive, and navigate to the directory: +2. Download, extract the archive: ```bash wget https://cdn.geekbench.com/Geekbench-6.2.2-LinuxARMPreview.tar.gz tar -xf Geekbench-6.2.2-LinuxARMPreview.tar.gz -cd Geekbench-6.2.2-LinuxARMPreview ``` -## Run +## Run -There is no need for a browser or Linux desktop to run the benchmark. The Linux command line is all you need. +There is no need for a browser or Linux desktop to run the benchmark. The Linux command line is all you need. -Run the `geekbench6` benchmark. +Navigate to the extracted directory and run the `geekbench6` benchmark. ```bash +cd Geekbench-6.2.2-LinuxARMPreview ./geekbench6 ``` @@ -59,7 +59,7 @@ Upload succeeded. Visit the following link and view your results online: You can create an account on [Geekbench browser](https://browser.geekbench.com) and save the results from your runs. This makes it easy to run Geekbench on a variety of systems and see your results together and compare them. You can also add notes to the results to help remember information about each run. -The `` and `` will be unique numbers for your run. +The `` and `` will be unique numbers for your run. ```output Visit the following link and add this result to your profile: diff --git a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md index 2b9b7064c7..a637df4493 100644 --- a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md +++ b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md @@ -5,7 +5,7 @@ title: "Measure ML Inference Performance on Arm servers" weight: 2 layout: "learningpathall" - + --- @@ -34,11 +34,12 @@ sudo pip install pybind11 ``` ## Clone the MLPerf Inference Benchmarks Repo for Image Classification and Object Detection -You will use the MLPerf Inference benchmark suite from MLCommons to benchmark models for a widely-used ML use-case such as Image Classification and Object Detection. +You will use the MLPerf Inference benchmark suite from MLCommons to benchmark models for a widely-used ML use-case such as Image Classification and Object Detection. Start by cloning the repository below: ```bash +cd $HOME git clone --recurse-submodules https://github.com/mlcommons/inference.git mlperf_inference ``` @@ -72,7 +73,7 @@ export ONEDNN_DEFAULT_FPMATH_MODE=BF16 ``` AWS Graviton3 instances are the first instances with BF16 support. -## Download the ML Model +## Download the ML Model Next, download the ML model you want to run the benchmark with. In this example, download the `resnet50-v1.5` model. @@ -80,7 +81,7 @@ Next, download the ML model you want to run the benchmark with. In this example, wget -q https://zenodo.org/record/2535873/files/resnet50_v1.pb ``` -## Download the dataset +## Download the dataset You need to download a dataset for the ML model you want to benchmark. The imagenet2012 validation dataset is best used with this ML model. You can [download the dataset](http://image-net.org/challenges/LSVRC/2012/) after you register. @@ -101,7 +102,7 @@ export DATA_DIR=`pwd`/fake_imagenet ## Now run the benchmark on your Arm machine -You can now launch the benchmark on your Arm machine, using the command below: +You can now launch the benchmark on your Arm machine, using the command below: ```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=~/mlperf_inference/vision/classification_and_detection/;DATA_DIR=~/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="~/mlperf_inference/vision/classification_and_detection/" } ./run_local.sh tf resnet50 cpu @@ -121,7 +122,7 @@ device is one of [cpu|gpu] For all other options, run help as shown below: -```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=~/mlperf_inference/vision/classification_and_detection/;DATA_DIR=~/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="~/mlperf_inference/vision/classification_and_detection/" } +```console ./run_local.sh --help ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index c2c2c11446..d42b7c2ced 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -38,12 +38,13 @@ The source code is available from the [GitLab repository](https://gitlab.arm.com Use `git` to download the code: ```bash +cd $HOME git clone https://git.gitlab.arm.com/networking/ral.git ``` ## Create a build folder -Navigate to the source directory and create an empty build directory: +Navigate to the source directory and create an empty build directory: ```bash cd ral @@ -73,7 +74,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash { cwd="ral/build" } +```bash { cwd="$HOME/ran/build" } cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -101,15 +102,15 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash { cwd="ran/build" } +```bash { cwd="$HOME/ran/build" } make ``` ## Install the library -Install the library: +Install the library: -```bash { cwd="ran/build" } +```bash { cwd="$HOME/ran/build" } sudo make install ``` @@ -117,7 +118,7 @@ sudo make install Build and run the supplied benchmark example by running: -```bash { cwd="ral/build",ret_code="0" } +```bash { cwd="$HOME/ran/build",ret_code="0" } make check ``` diff --git a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md index 06d9b45225..d3cb7a79fd 100644 --- a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md +++ b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md @@ -16,13 +16,13 @@ This Learning Path explains how to use Vectorscan on Arm and provides an example ## Before you begin -You should have an Arm server available with Ubuntu 20.04 or Ubuntu 22.04 installed. +You should have an Arm server available with Ubuntu 20.04 or Ubuntu 22.04 installed. The instructions provided have been tested on an Ubuntu 22.04 AWS Arm EC2 instance (c6g.xlarge) and Ubuntu 20.04 Oracle Ampere A1 instance. ### Software dependencies -Before building Vectorscan, install the following software. +Before building Vectorscan, install the following software. Update the sources list for the package manager. @@ -33,12 +33,12 @@ sudo apt update GCC for your Arm Linux distribution. If needed, refer to the [installation guide](/install-guides/gcc/native/). ```bash -sudo apt install -y build-essential +sudo apt install -y build-essential ``` [CMake build system](https://cmake.org/): -```bash +```bash sudo apt install -y cmake ``` @@ -83,9 +83,9 @@ cd vectorscan ## Determine if your processor has SVE -[Scalable Vector Extensions (SVE)](https://developer.arm.com/Architectures/Scalable%20Vector%20Extensions) is a SIMD extension of the Arm architecture which is available on some Arm processors. For example, the Neoverse-N1 does not include SVE and the Neoverse-V1 does include SVE. +[Scalable Vector Extensions (SVE)](https://developer.arm.com/Architectures/Scalable%20Vector%20Extensions) is a SIMD extension of the Arm architecture which is available on some Arm processors. For example, the Neoverse-N1 does not include SVE and the Neoverse-V1 does include SVE. -Vectorscan will run faster if you have an processor with SVE and you enable it when building the software. +Vectorscan will run faster if you have an processor with SVE and you enable it when building the software. To determine if SVE is available on your processor run: @@ -93,15 +93,15 @@ To determine if SVE is available on your processor run: lscpu | grep sve ``` -If SVE is available the Flags will be printed: +If SVE is available the Flags will be printed: ```output Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm ssbs paca pacg dcpodp svei8mm svebf16 i8mm bf16 dgh rng ``` -If no SVE is present, there will be no output. +If no SVE is present, there will be no output. -### Build Vectorscan +### Build Vectorscan Create a build directory and build with cmake: @@ -117,7 +117,7 @@ cmake -DBUILD_SVE=1 ../ Use `make` to build the vectorscan library: -```bash { cwd="./vectorscan/vectorscan-build" } +```bash { cwd="./vectorscan/build" } make -j$(nproc) ``` @@ -127,7 +127,7 @@ The executables from the build are created in the `bin` directory. Run a check to validate that `Vectorscan` is built and running correctly: -```bash { cwd="./vectorscan/vectorscan-build" } +```bash { cwd="./vectorscan/build" } ./bin/unit-hyperscan ``` diff --git a/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md b/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md index 38f4612614..1396318d38 100644 --- a/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md +++ b/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md @@ -15,12 +15,12 @@ You can install Snort 3 on an Ubuntu Linux Arm-based server, and run it with Vec ## Before you begin -You should already have an Arm server running Ubuntu Linux from the previous topic. +You should already have an Arm server running Ubuntu Linux from the previous topic. Install the Snort 3 dependencies: ```bash -sudo apt update +sudo apt update sudo apt-get install -y build-essential autotools-dev libdumbnet-dev libluajit-5.1-dev libpcap-dev \ zlib1g-dev pkg-config libhwloc-dev cmake liblzma-dev openssl libssl-dev cpputest libsqlite3-dev \ libtool uuid-dev git autoconf bison flex libcmocka-dev libnetfilter-queue-dev libunwind-dev \ @@ -75,26 +75,26 @@ Download (but do not build) [Boost C++ Libraries](https://www.boost.org/): ```bash cd ~/snort_src -wget https://boostorg.jfrog.io/artifactory/main/release/1.85.0/source/boost_1_85_0.tar.gz -tar -xvzf boost_1_85_0.tar.gz +wget https://archives.boost.io/release/1.87.0/source/boost_1_87_0.tar.gz +tar -xvzf boost_1_87_0.tar.gz ``` Download Vectorscan: ```bash cd ~/snort_src -git clone https://github.com/VectorCamp/vectorscan -cd vectorscan -cd .. -mkdir hyperscan-build -cd hyperscan-build +git clone https://github.com/VectorCamp/vectorscan +cd vectorscan +cd .. +mkdir hyperscan-build +cd hyperscan-build ``` Configure and build Vectorscan: ```bash { cwd="snort_src/hyperscan-build" } cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DBOOST_ROOT=~/snort_src/boost_1_85_0/ ~/snort_src/vectorscan/ -make -j$(nproc) && sudo make install +make -j$(nproc) && sudo make install ``` Install [FlatBuffers](https://google.github.io/flatbuffers/): @@ -138,7 +138,7 @@ cd ~/snort_src wget https://github.com/snort3/snort3/archive/refs/tags/3.2.2.0.tar.gz -O snort3-3.2.2.0.tar.gz tar -xzvf snort3-3.2.2.0.tar.gz cd snort3-3.2.2.0 -./configure_cmake.sh --prefix=/usr/local --enable-tcmalloc +./configure_cmake.sh --prefix=/usr/local --enable-tcmalloc cd build make -j$(nproc) sudo make install @@ -146,7 +146,7 @@ sudo make install ## Confirm Snort 3 is installed and running properly -Snort 3 should be installed in `/usr/local/bin`. +Snort 3 should be installed in `/usr/local/bin`. Verify it is installed and running correctly by printing the version: @@ -178,7 +178,7 @@ You should see output similar to the following: You can test the performance of Snort 3 with Vectorscan on your Arm instance. -Download a capture file to using for testing: +Download a capture file to using for testing: ```bash mkdir ~/snort3_test diff --git a/content/learning-paths/servers-and-cloud-computing/zlib/perf.md b/content/learning-paths/servers-and-cloud-computing/zlib/perf.md index 48941244ef..09966ed858 100644 --- a/content/learning-paths/servers-and-cloud-computing/zlib/perf.md +++ b/content/learning-paths/servers-and-cloud-computing/zlib/perf.md @@ -29,7 +29,7 @@ The previous section explained how to run a Python program to compress large fil Continue with the same `zip.py` program as the previous section. Make sure to start with `zip.py` and `largefile` available. Confirm the application is working and `largefile.gz` is created when it is run. ```bash -python ./zip.py +python zip.py ``` ## Run the example with perf using the default zlib @@ -40,7 +40,7 @@ Run with the default `zlib` and time the execution. perf stat python ./zip.py ``` -The `perf stat` command will display counts of a few selected PMU events. +The `perf stat` command will display counts of a few selected PMU events. ## Use perf record and generate the flame graph @@ -50,7 +50,7 @@ You can also record the application activity with `perf record`. `-F` specifies perf record -F 99 -g python ./zip.py ``` -To visualize the results, you can generate an image with `FlameGraph`. Install it with: +To visualize the results, you can generate an image with `FlameGraph`. Install it with: ```bash sudo apt install git -y @@ -77,9 +77,9 @@ Note that the `zlib` and the `deflate` function are taking significant time. ## Run the example again with perf stat and zlib-cloudflare -This time use `LD_PRELOAD` to change to `zlib-cloudflare` instead and check the performance difference. +This time use `LD_PRELOAD` to change to `zlib-cloudflare` instead and check the performance difference. -Adjust the path to the Cloudflare `libz.so` as needed. +Adjust the path to the Cloudflare `libz.so` as needed. ```console LD_PRELOAD=/usr/local/lib/libz.so perf stat python ./zip.py diff --git a/content/learning-paths/servers-and-cloud-computing/zlib/py-zlib.md b/content/learning-paths/servers-and-cloud-computing/zlib/py-zlib.md index 157c030cb8..1dcf263b61 100644 --- a/content/learning-paths/servers-and-cloud-computing/zlib/py-zlib.md +++ b/content/learning-paths/servers-and-cloud-computing/zlib/py-zlib.md @@ -6,7 +6,7 @@ weight: 3 ## Install necessary software packages -* Make sure `python3` is available when `python` is run. +* Make sure `python3` is available when `python` is run. ```bash sudo apt install python-is-python3 -y @@ -14,7 +14,7 @@ sudo apt install python-is-python3 -y ## Detailed Steps -The previous section explained how to build the Cloudflare `zlib` which includes the use of `crc32` instructions to improve performance on data compression. +The previous section explained how to build the Cloudflare `zlib` which includes the use of `crc32` instructions to improve performance on data compression. Use a Python example and measure the performance difference with `zlib-cloudflare`. @@ -48,16 +48,16 @@ dd if=/dev/zero of=largefile count=1M bs=1024 Run with the default `zlib` and time the execution. ```bash -time python ./zip.py +time python zip.py ``` -Make a note of how many seconds the program took. +Make a note of how many seconds the program took. ## Run the example again with zlib-cloudflare -This time, use `LD_PRELOAD` to change to `zlib-cloudflare` instead and check the performance difference. +This time, use `LD_PRELOAD` to change to `zlib-cloudflare` instead and check the performance difference. -Adjust the path to `libz.so` as needed. +Adjust the path to `libz.so` as needed. ```bash time LD_PRELOAD=/usr/local/lib/libz.so python ./zip.py From 14b8bd314ee261a7a8777501a85431e487a9a42d Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 24 Feb 2025 15:33:58 +0100 Subject: [PATCH 019/122] Update LPs - Disable some test until upstream issue is fixed: https://github.com/mlcommons/inference/pull/2131 --- content/install-guides/gcc/native.md | 4 ++-- content/install-guides/oci-cli.md | 1 + .../servers-and-cloud-computing/ml-perf/ml-perf.md | 12 +++++++----- .../servers-and-cloud-computing/ran/armral.md | 8 ++++---- .../servers-and-cloud-computing/zlib/perf.md | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/content/install-guides/gcc/native.md b/content/install-guides/gcc/native.md index c9c3c3a0e3..3ba51a6cf5 100644 --- a/content/install-guides/gcc/native.md +++ b/content/install-guides/gcc/native.md @@ -65,12 +65,12 @@ To install the most common development tools use the commands below. If the mach ```bash { target="fedora:latest" } sudo dnf update -y -sudo dnf group install 'Development Tools' -y +sudo dnf group install "development-tools" -y ``` If `sudo` is not available become _root_ and omit the `sudo`. ```console dnf update -y -dnf group install 'Development Tools' -y +sudo dnf group install "development-tools" -y ``` ## Setting up product license diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 468c17827c..d7a190e6d0 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -65,6 +65,7 @@ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scrip Run the command below to add the `oci` command to your search path: ```bash { target="ubuntu:latest" } +echo 'export PATH=$HOME/bin:$PATH' >> $HOME/.bashrc source $HOME/.bashrc ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md index a637df4493..caa74c35af 100644 --- a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md +++ b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md @@ -48,8 +48,10 @@ git clone --recurse-submodules https://github.com/mlcommons/inference.git mlperf Next, build and install the MLPerf Inference Benchmark for the Image Classification and Object Detection use case using the steps below: ```bash -cd mlperf_inference/loadgen/ +cd $HOME/mlperf_inference/loadgen/ CFLAGS="-std=c++14" sudo python3 setup.py develop --user +``` +```console cd ../vision/classification_and_detection/ sudo python3 setup.py develop ``` @@ -77,7 +79,7 @@ AWS Graviton3 instances are the first instances with BF16 support. Next, download the ML model you want to run the benchmark with. In this example, download the `resnet50-v1.5` model. -```bash { cwd="~/mlperf_inference/vision/classification_and_detection/" } +```bash { cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } wget -q https://zenodo.org/record/2535873/files/resnet50_v1.pb ``` @@ -87,7 +89,7 @@ You need to download a dataset for the ML model you want to benchmark. The image For this example, you generate a fake image dataset using the tooling included in the repo. Use the command below: -```bash { cwd="~/mlperf_inference/vision/classification_and_detection/" } +```bash { cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } tools/make_fake_imagenet.sh ``` @@ -104,7 +106,7 @@ export DATA_DIR=`pwd`/fake_imagenet You can now launch the benchmark on your Arm machine, using the command below: -```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=~/mlperf_inference/vision/classification_and_detection/;DATA_DIR=~/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="~/mlperf_inference/vision/classification_and_detection/" } +```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/;DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } ./run_local.sh tf resnet50 cpu ``` @@ -122,7 +124,7 @@ device is one of [cpu|gpu] For all other options, run help as shown below: -```console +```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/;DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } ./run_local.sh --help ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index d42b7c2ced..0972644c63 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -74,7 +74,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash { cwd="$HOME/ran/build" } +```bash { cwd="$HOME/ral/build" } cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -102,7 +102,7 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash { cwd="$HOME/ran/build" } +```bash { cwd="$HOME/ral/build" } make ``` @@ -110,7 +110,7 @@ make Install the library: -```bash { cwd="$HOME/ran/build" } +```bash { cwd="$HOME/ral/build" } sudo make install ``` @@ -118,7 +118,7 @@ sudo make install Build and run the supplied benchmark example by running: -```bash { cwd="$HOME/ran/build",ret_code="0" } +```bash { cwd="$HOME/ral/build",ret_code="0" } make check ``` diff --git a/content/learning-paths/servers-and-cloud-computing/zlib/perf.md b/content/learning-paths/servers-and-cloud-computing/zlib/perf.md index 09966ed858..ab2c568021 100644 --- a/content/learning-paths/servers-and-cloud-computing/zlib/perf.md +++ b/content/learning-paths/servers-and-cloud-computing/zlib/perf.md @@ -28,7 +28,7 @@ The previous section explained how to run a Python program to compress large fil Continue with the same `zip.py` program as the previous section. Make sure to start with `zip.py` and `largefile` available. Confirm the application is working and `largefile.gz` is created when it is run. -```bash +```console python zip.py ``` From a1a2b393cc62a91a86cc7c968caa95b6e1bc3e37 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 26 Feb 2025 08:49:25 +0100 Subject: [PATCH 020/122] Update env_source call to use '.' instead of 'source' --- content/install-guides/oci-cli.md | 2 +- .../servers-and-cloud-computing/ml-perf/ml-perf.md | 2 -- tools/check.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index d7a190e6d0..52f471a1ee 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -54,6 +54,7 @@ To run an an automated install with default values run: ```bash { target="ubuntu:latest" } curl -o install.sh https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh bash ./install.sh --accept-all-defaults +echo 'export PATH=$HOME/bin:$PATH' >> $HOME/.bashrc ``` To run an interactive install that allows you to change default values run: @@ -65,7 +66,6 @@ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scrip Run the command below to add the `oci` command to your search path: ```bash { target="ubuntu:latest" } -echo 'export PATH=$HOME/bin:$PATH' >> $HOME/.bashrc source $HOME/.bashrc ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md index caa74c35af..ee37798920 100644 --- a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md +++ b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md @@ -50,8 +50,6 @@ Next, build and install the MLPerf Inference Benchmark for the Image Classificat ```bash cd $HOME/mlperf_inference/loadgen/ CFLAGS="-std=c++14" sudo python3 setup.py develop --user -``` -```console cd ../vision/classification_and_detection/ sudo python3 setup.py develop ``` diff --git a/tools/check.py b/tools/check.py index 3d8ad1e63b..7eaabb8c28 100644 --- a/tools/check.py +++ b/tools/check.py @@ -85,7 +85,7 @@ def write_commands_to_file(test_cmd_filename, test): # - Working directory is specified # - An environment variable is specified cmd_args = { - "env_source":"source", + "env_source":".", "cwd":"cd ", "env":"export" } From 66f9b6d15137c4f56d06c308a2dbd19d7df854dd Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 26 Feb 2025 09:34:27 +0100 Subject: [PATCH 021/122] Update LPs - acfl.md disable some tests (verified offline) - Add corner case to parse.py --- content/install-guides/acfl.md | 18 +++++++++--------- tools/patch.py | 7 +++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 4711945f57..a222921d38 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -202,7 +202,7 @@ Arm Compiler for Linux is available to install with either the `dnf` or `yum` sy Install ACfL and prerequisites from the RHEL 9 `rpm` package repository with `dnf`: ```bash { target="fedora:latest" } -sudo dnf update +sudo dnf update -y sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo sudo dnf -y install acfl @@ -211,7 +211,7 @@ sudo dnf -y install acfl Or using the equivalent `yum` commands: ```bash { target="fedora:latest" } -sudo yum update +sudo yum update -y sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo sudo yum -y install acfl @@ -243,19 +243,19 @@ echo "module use /opt/arm/modulefiles" >> ~/.bashrc To list available modules: -```bash { env_source="~/.bashrc" } +```console module avail ``` To configure Arm Compiler for Linux: -```bash { env_source="~/.bashrc" } +```console module load acfl/24.10.1 ``` To configure GCC: -```bash { env_source="~/.bashrc" } +```console module load gnu/14.2.0 ``` @@ -314,7 +314,7 @@ ACfL is now [ready to use](#armclang). To get started with the Arm C/C++ Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console armclang --version ``` @@ -352,7 +352,7 @@ Hello, C World! To get started with the Arm Fortran Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console armflang --version ``` @@ -366,12 +366,12 @@ end program hello ``` Build the application with: -```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console armflang hello.f90 -o hello ``` Run the application with: -```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console ./hello ``` diff --git a/tools/patch.py b/tools/patch.py index 5c6044f94b..dbbad8a192 100644 --- a/tools/patch.py +++ b/tools/patch.py @@ -19,7 +19,11 @@ def patch(article_path: str, results: dict, link: str): content_type, sw_category, content_title = article_path_parts article_path = PurePath(article_path, "_index.md") elif "install-guides" in article_path_parts: - content_type, content_title, *others = article_path_parts + # In case the install guide is in a subdirectory + if len(article_path_parts) > 3: + content_type, subdirectory, content_title, *others = article_path_parts + else: + content_type, content_title, *others = article_path_parts content_title = content_title.strip(".md") sw_category = content_type else: @@ -32,7 +36,6 @@ def patch(article_path: str, results: dict, link: str): for image, i in zip(test_images, range(len(test_images))): if content_title not in data["sw_categories"][sw_category]: raise SystemExit(f"{content_title} does not exist in {stats_file}. Add it to update the stats report.") - data["sw_categories"][sw_category][content_title]["tests_and_status"][i][image] = results_values[results[image]] if link: From 37d0c60fed1892e5b5cdc8bc90f99e90a2e7d0f7 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 26 Feb 2025 14:19:47 +0100 Subject: [PATCH 022/122] Update LPs --- content/install-guides/acfl.md | 8 ++++---- content/install-guides/oci-cli.md | 2 +- .../servers-and-cloud-computing/codec/x265.md | 19 ++++++++++++++++++- .../servers-and-cloud-computing/ran/armral.md | 8 ++++---- tools/check.py | 2 +- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index a222921d38..cadbd21e5a 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -176,7 +176,7 @@ Arm Compiler for Linux is available to install with either the `dnf` or `yum` sy Install ACfL and prerequisites from the Amazon Linux 2023 `rpm` package repository with `dnf`: ```bash { target="fedora:latest" } -sudo dnf update +sudo dnf update -y sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo sudo dnf -y install acfl @@ -185,7 +185,7 @@ sudo dnf -y install acfl Or using the equivalent `yum` commands: ```bash { target="fedora:latest" } -sudo yum update +sudo yum update -y sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo sudo yum -y install acfl @@ -332,13 +332,13 @@ int main() Build the application with: -```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console armclang hello.c -o hello ``` Run the application with: -```console { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```console ./hello ``` diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 52f471a1ee..5d0be3217c 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -66,7 +66,7 @@ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scrip Run the command below to add the `oci` command to your search path: ```bash { target="ubuntu:latest" } -source $HOME/.bashrc +. $HOME/.bashrc ``` Verify OCI CLI is installed using the `--version` option: diff --git a/content/learning-paths/servers-and-cloud-computing/codec/x265.md b/content/learning-paths/servers-and-cloud-computing/codec/x265.md index e51f13ebde..759630c3c0 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/x265.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/x265.md @@ -12,6 +12,7 @@ Install GCC for your Arm Linux distribution. Refer to the [install guide](/insta Install `Cmake` and other dependencies: ```bash +sudo apt update sudo apt install wget git-all cmake cmake-curses-gui build-essential -y ``` If you are running on x86, also install `yasm` @@ -21,12 +22,28 @@ sudo apt install yasm -y ## Download and build x265 source +Clone the repository and navigate to the `linux` directory. + ```bash git clone https://bitbucket.org/multicoreware/x265_git.git cd x265_git/build/linux +``` + +Run the interactive bash script to change the default flags for the build. Then, run `make`. + +```console ./make-Makefiles.bash make ``` + +{{ % notice Note % }} +If you encounter an error regarding an unknown value, you can change the `ENABLE_NEON_I8MM` flag to `OFF` using the interactive bash script in the previous step. + +```output +error: unknown value ‘armv9-a+i8mm+sve2’ for ‘-march’ +``` +{{ % /notice % }} + For detailed instructions refer to this [README](https://bitbucket.org/multicoreware/x265_git/src/master/build/README.txt). ## Download video streams to run x265 on and measure performance @@ -53,7 +70,7 @@ To benchmark the performance of `x265`, using the `1080P` video file, run the co You can vary the preset settings on the different resolution images and measure the impact on performance. For full usage instructions, refer to the [command line documentation](https://x265.readthedocs.io/en/master/cli.html), or see the help: -```bash { cwd="x265_git/build/linux",ret_code="1" } +```console ./x265 --help ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index 0972644c63..74f075240b 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -74,7 +74,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash { cwd="$HOME/ral/build" } +```bash cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -102,7 +102,7 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash { cwd="$HOME/ral/build" } +```bash make ``` @@ -110,7 +110,7 @@ make Install the library: -```bash { cwd="$HOME/ral/build" } +```bash sudo make install ``` @@ -118,7 +118,7 @@ sudo make install Build and run the supplied benchmark example by running: -```bash { cwd="$HOME/ral/build",ret_code="0" } +```bash { ret_code="0" } make check ``` diff --git a/tools/check.py b/tools/check.py index 7eaabb8c28..0fb93b39d2 100644 --- a/tools/check.py +++ b/tools/check.py @@ -87,7 +87,7 @@ def write_commands_to_file(test_cmd_filename, test): cmd_args = { "env_source":".", "cwd":"cd ", - "env":"export" + "env=":"export" } for cmd_arg in cmd_args.keys(): if cmd_arg in test: From 0bbf040cfc577c189d517520590eeaf418aafb9d Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 24 Mar 2025 17:17:21 +0100 Subject: [PATCH 023/122] Debug errors --- content/install-guides/oci-cli.md | 1 + .../learning-paths/servers-and-cloud-computing/codec/x265.md | 3 ++- .../servers-and-cloud-computing/ml-perf/ml-perf.md | 1 + .../learning-paths/servers-and-cloud-computing/ran/armral.md | 2 ++ .../servers-and-cloud-computing/vectorscan/install.md | 4 ++-- .../servers-and-cloud-computing/vectorscan/snort.md | 2 +- 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 5d0be3217c..dd624f53e9 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -67,6 +67,7 @@ Run the command below to add the `oci` command to your search path: ```bash { target="ubuntu:latest" } . $HOME/.bashrc +cat $HOME/.bashrc ``` Verify OCI CLI is installed using the `--version` option: diff --git a/content/learning-paths/servers-and-cloud-computing/codec/x265.md b/content/learning-paths/servers-and-cloud-computing/codec/x265.md index 759630c3c0..fab9353ae1 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/x265.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/x265.md @@ -12,7 +12,8 @@ Install GCC for your Arm Linux distribution. Refer to the [install guide](/insta Install `Cmake` and other dependencies: ```bash -sudo apt update +sudo apt clean && sudo apt update +dpkg --get-selections | grep hold sudo apt install wget git-all cmake cmake-curses-gui build-essential -y ``` If you are running on x86, also install `yasm` diff --git a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md index ee37798920..7b321721bc 100644 --- a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md +++ b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md @@ -105,6 +105,7 @@ export DATA_DIR=`pwd`/fake_imagenet You can now launch the benchmark on your Arm machine, using the command below: ```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/;DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } +export DATA_DIR=`pwd`/fake_imagenet ./run_local.sh tf resnet50 cpu ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index 74f075240b..30c82c5081 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -75,6 +75,8 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: ```bash +pwd +ls .. cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` diff --git a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md index d3cb7a79fd..eea44c97ff 100644 --- a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md +++ b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md @@ -127,8 +127,8 @@ The executables from the build are created in the `bin` directory. Run a check to validate that `Vectorscan` is built and running correctly: -```bash { cwd="./vectorscan/build" } -./bin/unit-hyperscan +```bash +$HOME/vectorscan/build/bin/unit-hyperscan ``` All the unit tests should run successfully. At the end of execution you will see output similar to: diff --git a/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md b/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md index 1396318d38..d7baf29a04 100644 --- a/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md +++ b/content/learning-paths/servers-and-cloud-computing/vectorscan/snort.md @@ -24,7 +24,7 @@ sudo apt update sudo apt-get install -y build-essential autotools-dev libdumbnet-dev libluajit-5.1-dev libpcap-dev \ zlib1g-dev pkg-config libhwloc-dev cmake liblzma-dev openssl libssl-dev cpputest libsqlite3-dev \ libtool uuid-dev git autoconf bison flex libcmocka-dev libnetfilter-queue-dev libunwind-dev \ -libmnl-dev ethtool libjemalloc-dev ragel +libmnl-dev ethtool libjemalloc-dev ragel libfl-dev ``` ## Download and install other required software From d84afb3474bd1840a0b971b39f9b9f0605a95c93 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 25 Mar 2025 08:57:56 +0100 Subject: [PATCH 024/122] Fix errors --- .../servers-and-cloud-computing/ml-perf/ml-perf.md | 5 ++--- .../servers-and-cloud-computing/ran/armral.md | 8 +++----- .../servers-and-cloud-computing/vectorscan/install.md | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md index 7b321721bc..1a1b7f5da0 100644 --- a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md +++ b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md @@ -104,8 +104,7 @@ export DATA_DIR=`pwd`/fake_imagenet You can now launch the benchmark on your Arm machine, using the command below: -```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/;DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } -export DATA_DIR=`pwd`/fake_imagenet +```bash { env="TF_ENABLE_ONEDNN_OPTS=1 ONEDNN_DEFAULT_FPMATH_MODE=BF16 MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/ DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } ./run_local.sh tf resnet50 cpu ``` @@ -123,7 +122,7 @@ device is one of [cpu|gpu] For all other options, run help as shown below: -```bash { env="TF_ENABLE_ONEDNN_OPTS=1;ONEDNN_DEFAULT_FPMATH_MODE=BF16;MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/;DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } +```bash { env="TF_ENABLE_ONEDNN_OPTS=1 ONEDNN_DEFAULT_FPMATH_MODE=BF16 MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/ DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } ./run_local.sh --help ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index 30c82c5081..502e878a81 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -74,9 +74,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash -pwd -ls .. +```bash { cwd="$HOME/ral/build"} cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -104,7 +102,7 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash +```bash { cwd="$HOME/ral/build"} make ``` @@ -112,7 +110,7 @@ make Install the library: -```bash +```bash { cwd="$HOME/ral/build"} sudo make install ``` diff --git a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md index eea44c97ff..96b7ffc7fc 100644 --- a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md +++ b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md @@ -127,8 +127,8 @@ The executables from the build are created in the `bin` directory. Run a check to validate that `Vectorscan` is built and running correctly: -```bash -$HOME/vectorscan/build/bin/unit-hyperscan +```bash { cwd="./vectorscan/build" } +ls bin && ./bin/unit-vectorscan ``` All the unit tests should run successfully. At the end of execution you will see output similar to: From bda7bdee1bb6395821a4982d357af468325c6274 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 25 Mar 2025 09:34:54 +0100 Subject: [PATCH 025/122] Fix errors --- content/install-guides/acfl.md | 8 ++++---- content/install-guides/oci-cli.md | 6 ++---- .../servers-and-cloud-computing/codec/x265.md | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index cadbd21e5a..03c788ba6a 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -178,7 +178,7 @@ Install ACfL and prerequisites from the Amazon Linux 2023 `rpm` package reposito ```bash { target="fedora:latest" } sudo dnf update -y sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules -sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo +sudo dnf config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo sudo dnf -y install acfl ``` @@ -187,7 +187,7 @@ Or using the equivalent `yum` commands: ```bash { target="fedora:latest" } sudo yum update -y sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules -sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo +sudo yum config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo sudo yum -y install acfl ``` @@ -204,7 +204,7 @@ Install ACfL and prerequisites from the RHEL 9 `rpm` package repository with `dn ```bash { target="fedora:latest" } sudo dnf update -y sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules -sudo dnf config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo +sudo dnf config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo sudo dnf -y install acfl ``` @@ -213,7 +213,7 @@ Or using the equivalent `yum` commands: ```bash { target="fedora:latest" } sudo yum update -y sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules -sudo yum config-manager --add-repo https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo +sudo yum config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo sudo yum -y install acfl ``` diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index dd624f53e9..3853a82f51 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -54,7 +54,6 @@ To run an an automated install with default values run: ```bash { target="ubuntu:latest" } curl -o install.sh https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh bash ./install.sh --accept-all-defaults -echo 'export PATH=$HOME/bin:$PATH' >> $HOME/.bashrc ``` To run an interactive install that allows you to change default values run: @@ -63,11 +62,10 @@ To run an interactive install that allows you to change default values run: bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" ``` -Run the command below to add the `oci` command to your search path: +Run the command below to restart your shell. This will enable the tab completion for the command. ```bash { target="ubuntu:latest" } -. $HOME/.bashrc -cat $HOME/.bashrc +exec -l $SHELL ``` Verify OCI CLI is installed using the `--version` option: diff --git a/content/learning-paths/servers-and-cloud-computing/codec/x265.md b/content/learning-paths/servers-and-cloud-computing/codec/x265.md index fab9353ae1..cee3c26f5d 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/x265.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/x265.md @@ -12,8 +12,7 @@ Install GCC for your Arm Linux distribution. Refer to the [install guide](/insta Install `Cmake` and other dependencies: ```bash -sudo apt clean && sudo apt update -dpkg --get-selections | grep hold +sudo apt clean && sudo apt update && sudo apt --fix-broken install sudo apt install wget git-all cmake cmake-curses-gui build-essential -y ``` If you are running on x86, also install `yasm` From a11129cd1f112522e3740facbdbfdde08a8c8c16 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 25 Mar 2025 10:48:33 +0100 Subject: [PATCH 026/122] Re-enable some tests --- content/install-guides/acfl.md | 12 ++++++------ .../servers-and-cloud-computing/ran/armral.md | 6 +++--- .../vectorscan/install.md | 2 +- data/stats_current_test_info.yml | 1 + 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 03c788ba6a..408e213845 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -86,7 +86,7 @@ bash <(curl -L https://developer.arm.com/-/cdn-downloads/permalink/Arm-Compiler- Download releases from the command line using `wget`. Install `wget` if not present. -```console +```bash sudo apt install wget ``` @@ -126,7 +126,7 @@ sudo ./arm-compiler-for-linux_24.10.1_Ubuntu-22.04.sh --accept ```bash { target="fedora:latest" } sudo yum -y install environment-modules python3 glibc-devel tar -xvf arm-compiler-for-linux_24.10.1_RHEL-9_aarch64.tar -cd arm-compiler-for-linux_24.10.1_RHEL-9 +cd ./arm-compiler-for-linux_24.10.1_RHEL-9 sudo ./arm-compiler-for-linux_24.10.1_RHEL-9.sh --accept ``` @@ -163,7 +163,7 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: -```console +```bash { target="ubuntu:latest" } sudo apt install acfl ``` @@ -243,7 +243,7 @@ echo "module use /opt/arm/modulefiles" >> ~/.bashrc To list available modules: -```console +```bash module avail ``` @@ -314,7 +314,7 @@ ACfL is now [ready to use](#armclang). To get started with the Arm C/C++ Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```console +```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } armclang --version ``` @@ -352,7 +352,7 @@ Hello, C World! To get started with the Arm Fortran Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```console +```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } armflang --version ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index 502e878a81..7f48244fda 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -74,7 +74,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash { cwd="$HOME/ral/build"} +```bash { pre_cmd="cd $HOME/ral && mkdir build && cd build"} cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -102,7 +102,7 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash { cwd="$HOME/ral/build"} +```bash { pre_cmd="cd $HOME/ral && mkdir build && cd build"} make ``` @@ -110,7 +110,7 @@ make Install the library: -```bash { cwd="$HOME/ral/build"} +```bash { pre_cmd="cd $HOME/ral && mkdir build && cd build"} sudo make install ``` diff --git a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md index 96b7ffc7fc..b51267c4c7 100644 --- a/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md +++ b/content/learning-paths/servers-and-cloud-computing/vectorscan/install.md @@ -128,7 +128,7 @@ The executables from the build are created in the `bin` directory. Run a check to validate that `Vectorscan` is built and running correctly: ```bash { cwd="./vectorscan/build" } -ls bin && ./bin/unit-vectorscan +ls bin && ./bin/unit-hyperscan ``` All the unit tests should run successfully. At the end of execution you will see output similar to: diff --git a/data/stats_current_test_info.yml b/data/stats_current_test_info.yml index 6e4e88ffbc..d5f68e7ef3 100644 --- a/data/stats_current_test_info.yml +++ b/data/stats_current_test_info.yml @@ -102,6 +102,7 @@ sw_categories: readable_title: GFortran tests_and_status: - ubuntu:latest: passed + - fedora:latest: passed go: readable_title: Go tests_and_status: [] From 6ad542da258678ac9b19a35183d538b2094652b7 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 25 Mar 2025 11:22:21 +0100 Subject: [PATCH 027/122] Disable duplicated install, debug oci --- content/install-guides/acfl.md | 6 +++--- content/install-guides/oci-cli.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 408e213845..89356b96bd 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -164,7 +164,7 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: ```bash { target="ubuntu:latest" } -sudo apt install acfl +sudo apt install acfl -y ``` ### Amazon Linux 2023 @@ -175,7 +175,7 @@ Arm Compiler for Linux is available to install with either the `dnf` or `yum` sy Install ACfL and prerequisites from the Amazon Linux 2023 `rpm` package repository with `dnf`: -```bash { target="fedora:latest" } +```console sudo dnf update -y sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo @@ -184,7 +184,7 @@ sudo dnf -y install acfl Or using the equivalent `yum` commands: -```bash { target="fedora:latest" } +```console sudo yum update -y sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3AAmazonLinux-2023/latest/ACfL%3AAmazonLinux-2023.repo diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 3853a82f51..663a85b656 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -71,7 +71,7 @@ exec -l $SHELL Verify OCI CLI is installed using the `--version` option: ```bash { target="ubuntu:latest", env_source="$HOME/.bashrc" } -oci --version +cat $HOME/.bashrc && oci --version ``` Configure OCI CLI using the `setup config` command: From 7c0bfb617c3edce6a902b205d22afb11a19c93e5 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 25 Mar 2025 12:51:16 +0100 Subject: [PATCH 028/122] Disable ACFL tests, fix module avail failure --- content/install-guides/acfl.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 89356b96bd..be4f217b51 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -163,7 +163,7 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: -```bash { target="ubuntu:latest" } +```console sudo apt install acfl -y ``` @@ -201,7 +201,7 @@ Arm Compiler for Linux is available to install with either the `dnf` or `yum` sy Install ACfL and prerequisites from the RHEL 9 `rpm` package repository with `dnf`: -```bash { target="fedora:latest" } +```console sudo dnf update -y sudo dnf -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo dnf config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo @@ -210,7 +210,7 @@ sudo dnf -y install acfl Or using the equivalent `yum` commands: -```bash { target="fedora:latest" } +```console sudo yum update -y sudo yum -y install 'dnf-command(config-manager)' procps psmisc make environment-modules sudo yum config-manager addrepo --from-repofile=https://developer.arm.com/packages/ACfL%3ARHEL-9/standard/ACfL%3ARHEL-9.repo @@ -223,7 +223,7 @@ The ACfL tools are now ready to use. Arm Compiler for Linux uses environment modules to dynamically modify your user environment. Refer to the [Environment Modules documentation](https://lmod.readthedocs.io/en/latest/#id) for more information. -Set up the environment, for example, in your `.bashrc` and add module files. +Set up the environment, for example, in your `.bashrc` and add module files. Then, list the available modules: #### Ubuntu Linux: @@ -231,6 +231,7 @@ Set up the environment, for example, in your `.bashrc` and add module files. echo ". /usr/share/modules/init/bash" >> ~/.bashrc echo "module use /opt/arm/modulefiles" >> ~/.bashrc . ~/.bashrc +module avail ``` #### Red Hat or Amazon Linux: @@ -239,11 +240,6 @@ echo "module use /opt/arm/modulefiles" >> ~/.bashrc echo ". /usr/share/Modules/init/bash" >> ~/.bashrc echo "module use /opt/arm/modulefiles" >> ~/.bashrc . ~/.bashrc -``` - -To list available modules: - -```bash module avail ``` From a25929e646fba69ac34f11b5ba8c21f60b5c59f7 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 25 Mar 2025 14:42:26 +0100 Subject: [PATCH 029/122] Update LPs --- content/install-guides/acfl.md | 4 ++-- .../servers-and-cloud-computing/ml-perf/ml-perf.md | 2 +- .../servers-and-cloud-computing/ran/armral.md | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index be4f217b51..f9b12a2807 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -114,7 +114,7 @@ Each command sequence includes accepting the license agreement to automate the i #### Ubuntu Linux: -```bash { target="ubuntu:latest", env="DEBIAN_FRONTEND=noninteractive" } +```bash { target="ubuntu:latest" } sudo -E apt-get -y install environment-modules python3 libc6-dev tar -xvf arm-compiler-for-linux_24.10.1_Ubuntu-22.04_aarch64.tar cd ./arm-compiler-for-linux_24.10.1_Ubuntu-22.04 @@ -163,7 +163,7 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: -```console +```bash { target="ubuntu:latest" } sudo apt install acfl -y ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md index 1a1b7f5da0..fe843137c7 100644 --- a/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md +++ b/content/learning-paths/servers-and-cloud-computing/ml-perf/ml-perf.md @@ -122,7 +122,7 @@ device is one of [cpu|gpu] For all other options, run help as shown below: -```bash { env="TF_ENABLE_ONEDNN_OPTS=1 ONEDNN_DEFAULT_FPMATH_MODE=BF16 MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection/ DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection/" } +```bash { env="TF_ENABLE_ONEDNN_OPTS=1 ONEDNN_DEFAULT_FPMATH_MODE=BF16 MODEL_DIR=$HOME/mlperf_inference/vision/classification_and_detection DATA_DIR=$HOME/mlperf_inference/vision/classification_and_detection/fake_imagenet", cwd="$HOME/mlperf_inference/vision/classification_and_detection" } ./run_local.sh --help ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index 7f48244fda..c6266a5d9d 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -74,7 +74,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash { pre_cmd="cd $HOME/ral && mkdir build && cd build"} +```bash { cwd="$HOME/ral/build" } cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -102,7 +102,7 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash { pre_cmd="cd $HOME/ral && mkdir build && cd build"} +```bash { cwd="$HOME/ral/build" } make ``` @@ -110,7 +110,7 @@ make Install the library: -```bash { pre_cmd="cd $HOME/ral && mkdir build && cd build"} +```bash { cwd="$HOME/ral/build" } sudo make install ``` From 64b28e63898018d200a91c89f6e1d50f234f2830 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Fri, 28 Mar 2025 15:42:27 +0100 Subject: [PATCH 030/122] Add tmate session --- .github/workflows/test-lp.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index af227944bc..c94e140392 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -17,12 +17,14 @@ jobs: - name: Install dependencies if: steps.changed-markdown-files.outputs.any_changed == 'true' run: pip install -r tools/requirements.txt + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 - name: Run test suite for all changed .md files id: run-suite if: steps.changed-markdown-files.outputs.any_changed == 'true' # Run the test suite run: | - set -o pipefail; ./tools/test_lp.sh ${{ steps.changed-markdown-files.outputs.all_changed_files }} 2>&1 | tee test-lp-output.txt + set -o pipefail; ./tools/test_lp.sh ${{ steps.changed-markdown-files.outputs.all_changed_files }} 2>&1 | tee test-lp-output.txt - name: Parse test suite errors id: test-suite-state if: success() From 224462da6c7af9efa6c452cecc2bc9cfd289aa56 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 31 Mar 2025 11:11:40 +0200 Subject: [PATCH 031/122] Switch to stable apt-get --- content/install-guides/acfl.md | 2 +- .../learning-paths/servers-and-cloud-computing/codec/x265.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index f9b12a2807..a03a4d319d 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -164,7 +164,7 @@ The ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: ```bash { target="ubuntu:latest" } -sudo apt install acfl -y +sudo apt-get install acfl -y ``` ### Amazon Linux 2023 diff --git a/content/learning-paths/servers-and-cloud-computing/codec/x265.md b/content/learning-paths/servers-and-cloud-computing/codec/x265.md index cee3c26f5d..76560c5f15 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/x265.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/x265.md @@ -12,8 +12,8 @@ Install GCC for your Arm Linux distribution. Refer to the [install guide](/insta Install `Cmake` and other dependencies: ```bash -sudo apt clean && sudo apt update && sudo apt --fix-broken install -sudo apt install wget git-all cmake cmake-curses-gui build-essential -y +sudo apt-get update +sudo apt-get install wget git-all cmake cmake-curses-gui build-essential -y ``` If you are running on x86, also install `yasm` ```console From 088739484094ccc41876636a79fdeeb6b9f266cd Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 31 Mar 2025 11:24:25 +0200 Subject: [PATCH 032/122] Fix git dependency for x265.md and merge instruction blocks for acfl.md --- content/install-guides/acfl.md | 16 +++++----------- .../servers-and-cloud-computing/codec/x265.md | 4 ++-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index a03a4d319d..ade7fa8e54 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -149,22 +149,16 @@ Arm Compiler for Linux is available to install with the Ubuntu system package ma Add the ACfL `apt` package repository to your system. These instructions apply for Ubuntu 22.04. You can check the [available versions](https://developer.arm.com/packages/) for other releases. +After that, the ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: + + ```bash { target="ubuntu:latest" } sudo apt update sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list -sudo apt update && sudo apt upgrade -y && sudo apt --fix-broken install -y -``` - -The ACfL Ubuntu package repository is now ready to use. - -#### Install ACfL - -Download and install Arm Compiler for Linux with: - -```bash { target="ubuntu:latest" } -sudo apt-get install acfl -y +sudo apt update +sudo apt install acfl -y ``` ### Amazon Linux 2023 diff --git a/content/learning-paths/servers-and-cloud-computing/codec/x265.md b/content/learning-paths/servers-and-cloud-computing/codec/x265.md index 76560c5f15..fa53cf3ab5 100644 --- a/content/learning-paths/servers-and-cloud-computing/codec/x265.md +++ b/content/learning-paths/servers-and-cloud-computing/codec/x265.md @@ -12,8 +12,8 @@ Install GCC for your Arm Linux distribution. Refer to the [install guide](/insta Install `Cmake` and other dependencies: ```bash -sudo apt-get update -sudo apt-get install wget git-all cmake cmake-curses-gui build-essential -y +sudo apt update +sudo apt install wget git cmake cmake-curses-gui build-essential -y ``` If you are running on x86, also install `yasm` ```console From 33a13cc6f04e20934171cbc439f9a462b27fd099 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Mon, 31 Mar 2025 11:40:48 +0200 Subject: [PATCH 033/122] Remove colon encoding --- content/install-guides/acfl.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index ade7fa8e54..dd83ea355a 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -155,8 +155,8 @@ After that, the ACfL Ubuntu package repository is now ready to use. Download and ```bash { target="ubuntu:latest" } sudo apt update sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev -wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc -echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list +wget -qO - https://developer.arm.com/packages/ACfL:Ubuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +echo "deb https://developer.arm.com/packages/ACfL:Ubuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update sudo apt install acfl -y ``` From ce3bbcba693a91d18c6d4ff02e4843c53a295152 Mon Sep 17 00:00:00 2001 From: Maddy Underwood <167196745+madeline-underwood@users.noreply.github.com> Date: Tue, 1 Apr 2025 20:58:08 +0000 Subject: [PATCH 034/122] Starting content review. --- .../servers-and-cloud-computing/deepseek-cpu/_index.md | 4 ++-- .../deepseek-cpu/deepseek-chatbot.md | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md index 89924f5aef..cc2ee4c67e 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md @@ -10,12 +10,12 @@ minutes_to_complete: 30 who_is_this_for: This is an introductory topic for developers interested in running DeepSeek-R1 on Arm-based servers. learning_objectives: - - Download and build llama.cpp on your Arm server. + - Download and build llama.cpp on your Arm-based server. - Download a pre-quantized DeepSeek-R1 model from Hugging Face. - Run the pre-quantized model on your Arm CPU and measure the performance. prerequisites: - - An [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider or an on-premise Arm server. This Learning Path was tested on an AWS Graviton4 r8g.24xlarge instance. + - An [Arm-based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider or an on-premise Arm server. This Learning Path was tested on an AWS Graviton4 r8g.24xlarge instance. author: - Tianyu Li diff --git a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md index 7ba5604cc8..a8f80e6e03 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md +++ b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md @@ -10,9 +10,15 @@ layout: learningpathall The instructions in this Learning Path are for any Arm server running Ubuntu 24.04 LTS. You need an Arm server instance with at least 64 cores and 512GB of RAM to run this example. Configure disk storage up to at least 400 GB. The instructions have been tested on an AWS Graviton4 r8g.24xlarge instance. -## Overview +## Background and what you'll build + +Arm CPUs are widely used in ML and AI use cases. In this Learning Path, you will learn how to run a generative AI inference-based use case of a LLM chatbot on Arm-based CPUs by deploying the [DeepSeek-R1 671B LLM](https://huggingface.co/bartowski/DeepSeek-R1-GGUF) on your Arm-based CPU using `llama.cpp`, optimized for Arm hardware. You'll: + +- Build and run `llama.cpp` with Arm-specific performance improvements. +- Download a quantized GGUF model from Hugging Face. +- Run and measure performance on a large Arm instance (e.g., AWS Graviton4). + -Arm CPUs are widely used in traditional ML and AI use cases. In this Learning Path, you learn how to run generative AI inference-based use cases like a LLM chatbot on Arm-based CPUs. You do this by deploying the [DeepSeek-R1 GGUF models](https://huggingface.co/bartowski/DeepSeek-R1-GGUF) on your Arm-based CPU using `llama.cpp`. [llama.cpp](https://github.com/ggerganov/llama.cpp) is an open source C/C++ project developed by Georgi Gerganov that enables efficient LLM inference on a variety of hardware - both locally, and in the cloud. From b1108be782f0a132879f9b4e8bc46440b9271da8 Mon Sep 17 00:00:00 2001 From: Barbara Corriero Date: Fri, 4 Apr 2025 12:24:31 -0700 Subject: [PATCH 035/122] Add new WoA Build ONNX Runtime Learning Path --- .../1-dev-env-setup.md | 53 +++++++++++ .../2-build-onnxruntime.md | 60 +++++++++++++ .../3-build-onnxruntime-generate-api.md | 52 +++++++++++ .../4-run-benchmark-on-WoA.md | 90 +++++++++++++++++++ .../win_on_arm_build_onnxruntime/_index.md | 51 +++++++++++ .../_next-steps.md | 8 ++ 6 files changed, 314 insertions(+) create mode 100644 content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md create mode 100644 content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md create mode 100644 content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md create mode 100644 content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md create mode 100644 content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md create mode 100644 content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_next-steps.md diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md new file mode 100644 index 0000000000..e14387bccb --- /dev/null +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md @@ -0,0 +1,53 @@ +--- +title: Create a development environment +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Set up your development environment + +In this learning path, you will learn how to build and deploy a simple LLM-based tutorial on a Windows-on-ARM (WoA) laptop using ONNX Runtime for inference. + +You will first learn how to build the ONNX Runtime and ONNX Runtime generate() API library and then how to download the Phi-3 tand run the utorial. This tutorial runs the short context (4k) mini (3.3B) model variant of Phi 3 model. The short context version accepts a shorter (4K) prompts and produces shorter output text compared to the long (128K) context version. The short version will consume less memory. + +Your first task is to prepare a development environment with the required software: + +- Visual Studio 2022 IDE (latest version recommended) +- Python 3.10+ (tested with version ???BRB) +- CMake 3.28 or higher (tested with version 3.30.5) + +The following instructions were tested on an WoA 64-bit Windows machine with at least 16GB of RAM. + +## Install Visual Studio 2022 IDE + +Follow these steps to install and configure Android Studio: + +1. Download and install the latest version of [Visual Studio IDE](https://visualstudio.microsoft.com/downloads/). + +2. Select the **Community Version**. An installer called *VisualStudioSetup.exe* will be downloaded. + +3. From your Downloads folder, double-click the installer to start the installation. + +4. Follow the prompts and acknowledge **License Terms** and **Privacy Statement**. + +5. Once Downloaded and Installed complete select your workloads. As a minimum you should select **Desktop Development with C++**. This will install the **Microsoft Visual Studio Compiler** or **MSVC**. + +## Install Python 3.10+ (Tested with version 3.11.9) + +Download and install [Python 3.110+](https://www.python.org/downloads/) + +Tested version [Python 3.11.9](https://www.python.org/downloads/release/python-3119/) + +## Install CMake + +CMake is an open-source tool that automates the build process for software projects, helping to generate platform-specific build configurations. + +[Download and install CMake](https://cmake.org/download/) + +{{% notice Note %}} +The instructions were tested with version 3.30.5 +{{% /notice %}} + +You now have the required development tools installed to follow this learning path. diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md new file mode 100644 index 0000000000..3699fd00bd --- /dev/null +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md @@ -0,0 +1,60 @@ +--- +title: Build ONNX Runtime +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Compile ONNX Runtime for Windows ARM64 CPU +Now that you have your environment set up correctly, you can build the ONNX Runtime inference engine. + +ONNX Runtime is an open-source inference engine designed to accelerate the deployment of machine learning models, particularly those in the Open Neural Network Exchange (ONNX) format. ONNX Runtime is optimized for high performance and low latency, making it popular for production deployment of AI models. You can learn more by reading the [ONNX Runtime Overview](https://onnxruntime.ai/). + +### Clone ONNX Runtime Repo + +Open a Developer Command Prompt for Visual Studio to properly setup the environment including path to compiler, linker, utilities and header files. Create your workspace and check out the source tree: + +```bash +cd C:\Users\%USERNAME% +mkdir repos\lp +cd repos\lp +git clone --recursive https://github.com/Microsoft/onnxruntime.git +cd onnxruntime +git checkout 4eeefd7260b7fa42a71dd1a08b423d5e7c722050 +``` + +{{% notice Note %}} +You might be able to use a later commit. These steps have been tested with the commit `4eeefd7260b7fa42a71dd1a08b423d5e7c722050`. +{{% /notice %}} + +### Build for Windows CPU + +You can build "Release" for a build type that aims to provide an +a build optimized for performance but without debug information. + + +```bash +.\build.bat --config Release --build_shared_lib --parallel --compile_no_warning_as_error --skip_submodule_sync --skip_tests +``` + + +As an alternative, you can build "RelWithDebInfo" for a build type that aims to provide a release-optimized build with debug information. + +```bash +.\build.bat --config RelWithDebInfo --build_shared_lib --parallel --compile_no_warning_as_error --skip_submodule_sync --skip_tests +``` + + +### Resulting Dynamic Link Library +When the build is complete, onnxruntime.dll dynamic linked library can be found in: + +``` +dir .\build\Windows\Release\Release\onnxruntime.dll +``` + +or if you build with debug information it can be found in: + +``` +dir .\build\Windows\RelWithDebInfo\RelWithDebInfo\onnxruntime.dll +``` diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md new file mode 100644 index 0000000000..6569f8d0e2 --- /dev/null +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md @@ -0,0 +1,52 @@ +--- +title: Build ONNX Runtime Generate() API +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Cross-compile the ONNX Runtime Generate() API for Windows ARM64 CPU + +The Generate() API in ONNX Runtime is designed for text generation tasks using models like Phi-3. It implements the generative AI loop for ONNX models, including: +- pre- and post-processing +- inference with ONNX Runtime +- logits processing +- search and sampling +- KV cache management. +You can learn more by reading the [ONNX Runtime generate() API page](https://onnxruntime.ai/docs/genai/). + +In this page you will learn how to build the Generate API() from source (C/C++ build). + + +### Clone onnxruntime-genai Repo +Within your Windows Developer Command Prompt for Visual Studio, checkout the source repo: + +```bash +cd C:\Users\%USERNAME% +cd repos\lp +git clone https://github.com/microsoft/onnxruntime-genai +cd onnxruntime-genai +git checkout b2e8176c99473afb726d364454dc827d2181cbb2 +``` + +{{% notice Note %}} +You might be able to use later commits. These steps have been tested with the commit `b2e8176c99473afb726d364454dc827d2181cbb2`. +{{% /notice %}} + +### Build for Windows ARM64 CPU +The build command below has a --config argument, which takes the following options: +- ```Release``` builds release build +- ```Debug``` builds binaries with debug symbols +- ```RelWithDebInfo``` builds release binaries with debug info + + +```bash +python build.py --config Release --skip_tests +``` + +When the build is complete, confirm the ONNX Runtime Generate() API Dynamic Link Library has been created: + +```output +dir build\Windows\Release\Release\onnxruntime-genai.dll +``` diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md new file mode 100644 index 0000000000..71ae302472 --- /dev/null +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md @@ -0,0 +1,90 @@ +--- +title: Run Phi3 model on an ARM Windows Device +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Run a Phi-3 model on your an ARM Windows Device + +In this section you will learn how to obtain and run on your ARM Windows device (or virtual device) the Phi3-mini model. To do so you will be using a simple model runner program which provides performance metrics. + +The Phi-3-mini (3.3B) model has a short (4k) context version and a long (128k) context version. The long context version can accept much longer prompts and produces longer output text, but it does consume more memory. +In this learning path, you will use the short context version, which is quantized to 4-bits. + +The Phi-3-mini model used here is in an ONNX format. + +### Setup + +Phi-3 ONNX models are hosted on HuggingFace. +Hugging Face uses Git for version control and to download ONNX model files, which can be quite large. +You will first need to get and install the Git Large File Storage (LFS) extension. + +``` bash +winget install -e --id GitHub.GitLFS +git lfs install +``` +If you don’t have winget, download and run the exe from the [official source](https://docs.github.com/en/repositories/working-with-files/managing-large-files/installing-git-large-file-storage?platform=windows). +If the extension is already installed for you when you run the above ``git`` command it will say ``Git LFS initialized``. + +You then need to install the ``HuggingFace CLI``. + +``` bash +pip install huggingface-hub[cli] +``` + +### Download the Phi-3-mini (4k) model for CPU and Mobile + +``` bash +cd C:\Users\%USERNAME% +cd repos\lp +huggingface-cli download microsoft/Phi-3-mini-4k-instruct-onnx --include cpu_and_mobile/cpu-int4-rtn-block-32-acc-level-4/* --local-dir . +``` +This command downloads the model into a folder called `cpu_and_mobile`. + +### Build model runner (ONNX Runtime GenAI C Example) +In the previous section you built ONNX RUntime Geerate() API from source. +The headers and dynamic linked libraries that are built need to be copied pver to appropriate folders (``lib`` and ``inclue``). +Building from source is a better practice because the examples usually are updated to run with the latest changes. +First, cross-compile the model runner to run on Android using the commands below: + +``` bash +copy onnxruntime\build\Windows\Release\Release\onnxruntime.* onnxruntime-genai\examples\c\lib +cd onnxruntime-genai +copy build\Windows\Release\Release\onnxruntime-genai.* examples\c\lib +copy src\ort_genai.h examples\c\include\ +copy src\ort_genai_c.h examples\c\include\ +``` + +you can now build the model runner executable in the ''onnxruntime-genai'' folder using the commands below: + +``` bash +cd examples/c +cmake -A arm64 -S . -B build -DPHI3=ON +cd build +cmake --build . --config Release +``` + +After successful build, a binary program called `phi3` will be created in the ''onnxruntime-genai'' folder. +```output +dir examples\c\build\Release\phi3.exe +``` + +#### Run the model + +Use the runner you just built to execute the model with the following command: + +``` bash +cd C:\Users\%USERNAME% +cd repos\lp +.\onnxruntime-genai\examples\c\build\Release\phi3.exe .\cpu_and_mobile\cpu-int4-rtn-block-32-acc-level-4\ cpu +``` + +This will allow the runner program to load the model. It will then prompt you to input the text prompt to the model. After you enter your input prompt, the text output by the model will be displayed. On completion, performance metrics similar to those shown below should be displayed: + +``` +Prompt length: 64, New tokens: 931, Time to first: 1.79s, Prompt tokens per second: 35.74 tps, New tokens per second: 6.34 tps +``` + +You have successfully run the Phi-3 model on your Windows device powered by ARM. diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md new file mode 100644 index 0000000000..d1f3c381ca --- /dev/null +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md @@ -0,0 +1,51 @@ +--- +title: Powering Phi-3 with ONNX Runtime & KleidiAI on Windows + +minutes_to_complete: 60 + +who_is_this_for: A deep-dive for advanced developers looking to build ONNX Runtime on Windows ARM (WoA) and leverage the Generate() API to run Phi-3 inference with KleidiAI acceleration. + +learning_objectives: + - Build ONNX Runtime and ONNX Runtime generate() API for Windows on ARM. + - Run a Phi-3 model using ONNX Runtime on an Arm-based Windows laptop. + +prerequisites: + - A Windows on Arm computer such as the Lenovo Thinkpad X13 running Windows 11 or a Windows on Arm virtual machine + +author: Barbara Corriero + +### Tags +skilllevels: Advanced +subjects: ML +armips: + - Cortex-A + - Cortex-X +tools_software_languages: + - Visual Studio IDE - 2022+ Community Version + - C++ + - Python 3.10+ + - Git + - CMake-3.28 or higher +operatingsystems: + - Windows + +further_reading: + - resource: + title: ONNX Runtime + link: https://onnxruntime.ai/docs/ + type: documentation + - resource: + title: ONNX Runtime generate() API + link: https://onnxruntime.ai/docs/genai/ + type: documentation + - resource: + title: Accelerating AI Developer Innovation Everywhere with New Arm Kleidi + link: https://newsroom.arm.com/blog/arm-kleidi + type: blog + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_next-steps.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- From 12ba4b0517aea0352acf974451f571a2fae49b7c Mon Sep 17 00:00:00 2001 From: Barbara Corriero Date: Mon, 7 Apr 2025 10:25:09 -0700 Subject: [PATCH 036/122] Changes from last reading --- .../1-dev-env-setup.md | 8 ++++---- .../3-build-onnxruntime-generate-api.md | 14 +++++++------- .../4-run-benchmark-on-WoA.md | 11 +++++------ .../win_on_arm_build_onnxruntime/_index.md | 2 +- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md index e14387bccb..c19eb442fa 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md @@ -10,19 +10,19 @@ layout: learningpathall In this learning path, you will learn how to build and deploy a simple LLM-based tutorial on a Windows-on-ARM (WoA) laptop using ONNX Runtime for inference. -You will first learn how to build the ONNX Runtime and ONNX Runtime generate() API library and then how to download the Phi-3 tand run the utorial. This tutorial runs the short context (4k) mini (3.3B) model variant of Phi 3 model. The short context version accepts a shorter (4K) prompts and produces shorter output text compared to the long (128K) context version. The short version will consume less memory. +You will first learn how to build the ONNX Runtime and ONNX Runtime Generate() API library and then how to download the Phi-3 model and run the tutorial. This tutorial runs the short context (4k) mini (3.3B) variant of Phi 3 model. The short context version accepts a shorter (4K) prompts and produces shorter output text compared to the long (128K) context version. The short version will consume less memory. Your first task is to prepare a development environment with the required software: - Visual Studio 2022 IDE (latest version recommended) -- Python 3.10+ (tested with version ???BRB) +- Python 3.10+ (tested with version 3.11.9) - CMake 3.28 or higher (tested with version 3.30.5) The following instructions were tested on an WoA 64-bit Windows machine with at least 16GB of RAM. ## Install Visual Studio 2022 IDE -Follow these steps to install and configure Android Studio: +Follow these steps to install and configure Visual Studio 2022 IDE: 1. Download and install the latest version of [Visual Studio IDE](https://visualstudio.microsoft.com/downloads/). @@ -32,7 +32,7 @@ Follow these steps to install and configure Android Studio: 4. Follow the prompts and acknowledge **License Terms** and **Privacy Statement**. -5. Once Downloaded and Installed complete select your workloads. As a minimum you should select **Desktop Development with C++**. This will install the **Microsoft Visual Studio Compiler** or **MSVC**. +5. Once "Downloaded" and "Installed" complete select your workloads. As a minimum you should select **Desktop Development with C++**. This will install the **Microsoft Visual Studio Compiler** or **MSVC**. ## Install Python 3.10+ (Tested with version 3.11.9) diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md index 6569f8d0e2..36af64cb93 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md @@ -6,15 +6,15 @@ weight: 4 layout: learningpathall --- -## Cross-compile the ONNX Runtime Generate() API for Windows ARM64 CPU +## Compile the ONNX Runtime Generate() API for Windows ARM64 CPU The Generate() API in ONNX Runtime is designed for text generation tasks using models like Phi-3. It implements the generative AI loop for ONNX models, including: - pre- and post-processing -- inference with ONNX Runtime -- logits processing +- inference with ONNX Runtime- logits processing - search and sampling -- KV cache management. -You can learn more by reading the [ONNX Runtime generate() API page](https://onnxruntime.ai/docs/genai/). +- KV cache management. + +You can learn more by reading the [ONNX Runtime Generate() API page](https://onnxruntime.ai/docs/genai/). In this page you will learn how to build the Generate API() from source (C/C++ build). @@ -35,12 +35,12 @@ You might be able to use later commits. These steps have been tested with the co {{% /notice %}} ### Build for Windows ARM64 CPU -The build command below has a --config argument, which takes the following options: +The build command below has a ---config argument, which takes the following options: - ```Release``` builds release build - ```Debug``` builds binaries with debug symbols - ```RelWithDebInfo``` builds release binaries with debug info - +Below are the instruction to build ```Release```: ```bash python build.py --config Release --skip_tests ``` diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md index 71ae302472..f925cf5ca7 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md @@ -6,7 +6,7 @@ weight: 5 layout: learningpathall --- -## Run a Phi-3 model on your an ARM Windows Device +## Run a Phi-3 model on your ARM Windows Device In this section you will learn how to obtain and run on your ARM Windows device (or virtual device) the Phi3-mini model. To do so you will be using a simple model runner program which provides performance metrics. @@ -44,10 +44,9 @@ huggingface-cli download microsoft/Phi-3-mini-4k-instruct-onnx --include cpu_and This command downloads the model into a folder called `cpu_and_mobile`. ### Build model runner (ONNX Runtime GenAI C Example) -In the previous section you built ONNX RUntime Geerate() API from source. -The headers and dynamic linked libraries that are built need to be copied pver to appropriate folders (``lib`` and ``inclue``). +In the previous section you built ONNX RUntime Generate() API from source. +The headers and dynamic linked libraries that are built need to be copied over to appropriate folders (``lib`` and ``inclue``). Building from source is a better practice because the examples usually are updated to run with the latest changes. -First, cross-compile the model runner to run on Android using the commands below: ``` bash copy onnxruntime\build\Windows\Release\Release\onnxruntime.* onnxruntime-genai\examples\c\lib @@ -66,14 +65,14 @@ cd build cmake --build . --config Release ``` -After successful build, a binary program called `phi3` will be created in the ''onnxruntime-genai'' folder. +After a successful build, a binary program called `phi3` will be created in the ''onnxruntime-genai'' folder. ```output dir examples\c\build\Release\phi3.exe ``` #### Run the model -Use the runner you just built to execute the model with the following command: +Use the runner you just built to execute the model with the following commands: ``` bash cd C:\Users\%USERNAME% diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md index d1f3c381ca..374b6efc68 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md @@ -10,7 +10,7 @@ learning_objectives: - Run a Phi-3 model using ONNX Runtime on an Arm-based Windows laptop. prerequisites: - - A Windows on Arm computer such as the Lenovo Thinkpad X13 running Windows 11 or a Windows on Arm virtual machine + - A Windows on Arm computer such as the Lenovo Thinkpad X13 running Windows 11 or a Windows on Arm [virtual machine](https://learn.arm.com/learning-paths/cross-platform/woa_azure/) author: Barbara Corriero From 2031fa791726798857c9db1670df004fdd4081bc Mon Sep 17 00:00:00 2001 From: Rani Chowdary Mandepudi Date: Thu, 10 Apr 2025 14:15:05 -0500 Subject: [PATCH 037/122] quantize & run LLM on Arm --- .../vLLM-quant/1-overview.md | 112 ++++++++++ .../vLLM-quant/2-quantize-model.md | 178 ++++++++++++++++ .../vLLM-quant/3-run-benchmark.md | 195 ++++++++++++++++++ .../vLLM-quant/_index.md | 59 ++++++ .../vLLM-quant/_next-steps.md | 8 + 5 files changed, 552 insertions(+) create mode 100644 content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md create mode 100644 content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md create mode 100644 content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md create mode 100644 content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md create mode 100644 content/learning-paths/servers-and-cloud-computing/vLLM-quant/_next-steps.md diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md new file mode 100644 index 0000000000..fd0766b7b6 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md @@ -0,0 +1,112 @@ +--- +title: Overview and Environment Setup +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Overview + +[vLLM](https://github.com/vllm-project/vllm) is an open-source, high-throughput inference engine designed to efficiently serve large language models (LLMs). It offers an OpenAI-compatible API, supports dynamic batching, and is optimized for low-latency performance — making it suitable for both real-time and batch inference workloads. + +This learning path walks through how to combine vLLM with INT8 quantization techniques to reduce memory usage and improve inference speed, enabling large models like Llama 3.1 to run effectively on Arm-based CPUs. + +The model featured in this guide — [Llama 3.1 8B Instruct](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct) — is sourced from Hugging Face, quantized using the `llmcompressor`, and deployed using vLLM. + +Testing for this learning path was performed on AWS Graviton instance (c8g.16xlarge). The instructions are intended for Arm-based servers running Ubuntu 24.04 LTS. + + +## Learning Path Setup + +This learning path uses a Python virtual environment (`venv`) to manage dependencies in an isolated workspace. This approach ensures a clean environment, avoids version conflicts, and makes it easy to reproduce results — especially when using custom-built packages like `vLLM` and `PyTorch`. + +### Set up the Python environment + +To get started, create a virtual environment and activate it as shown below: + +```bash +sudo apt update +sudo apt install -y python3 python3-venv +python3 -m venv vllm_env +source vllm_env/bin/activate +pip install --upgrade pip +``` +This will create a local Python environment named (`vllm_env`) and upgrade pip to the latest version. + +### Install system dependencies + +These packages are needed to build libraries like OpenBLAS and manage system-level performance: + +```bash +sudo apt-get update -y +sudo apt-get install -y gcc-12 g++-12 libnuma-dev python3-pip +sudo apt install python-is-python3 +``` +Set the system default compilers to version 12: + +```bash +sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 10 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-12 +``` +Next, install the [`tcmalloc memory allocator`](https://docs.vllm.ai/en/latest/getting_started/installation/cpu.html?device=arm), which helps improve performance during inference: + +```bash +sudo apt-get install libtcmalloc-minimal4 +``` +This library will be preloaded during model serving to reduce latency and improve memory efficiency. + +### Install OpenBLAS + +OpenBLAS is an optimized linear algebra library that improves performance for matrix-heavy operations, which are common in LLM inference. To get the best performance on Arm CPUs, it's recommended to build OpenBLAS from source. + +Run these commands to clone and build OpenBLAS: +```bash +git clone https://github.com/OpenMathLib/OpenBLAS.git +cd OpenBLAS +git checkout ef9e3f715 +``` +{{% notice Note %}} +This commit is known to work reliably with Arm CPU optimizations (BF16, OpenMP) and has been tested in this learning path. Using it ensures consistent behavior. You can try `main`, but newer commits may introduce changes that haven't been validated here. +{{% /notice %}} + +```bash +make -j$(nproc) BUILD_BFLOAT16=1 USE_OPENMP=1 NO_SHARED=0 DYNAMIC_ARCH=1 TARGET=ARMV8 CFLAGS=-O3 +make -j$(nproc) BUILD_BFLOAT16=1 USE_OPENMP=1 NO_SHARED=0 DYNAMIC_ARCH=1 TARGET=ARMV8 CFLAGS=-O3 PREFIX=/home/ubuntu/OpenBLAS/dist install +``` +This will build and install OpenBLAS into `/home/ubuntu/OpenBLAS/dist` with optimizations for Arm CPUs. + +### Install Python dependencies + +Once the system libraries are in place, install the Python packages required for model quantization and serving. You’ll use prebuilt CPU wheels for vLLM and PyTorch, and install additional tools like `llmcompressor` and `torchvision`. + +Before proceeding, make sure the following files are downloaded to your home directory: +```bash + +``` +These are required to complete the installation and model quantization steps. +Now, navigate to your home directory: +```bash +cd /home/ubuntu/ +``` + +Install the vLLM wheel. This wheel contains the CPU-optimized version of `vLLM`, built specifically for Arm architecture. Installing it from a local `.whl` file ensures compatibility with the rest of your environment and avoids potential conflicts from nightly or default pip installations. + +```bash +pip install vllm-0.7.3.dev151+gfaee222b.cpu-cp312-cp312-linux_aarch64.whl --force-reinstall +``` +Install `llmcompressor`, which is used to quantize the model: +```bash +pip install llmcompressor +``` +Install torchvision (nightly version for CPU): +```bash +pip install --force-reinstall torchvision==0.22.0.dev20250213 --extra-index-url https://download.pytorch.org/whl/nightly/cpu +``` +Install the custom PyTorch CPU wheel:
+This custom PyTorch wheel is prebuilt for Arm CPU architectures and includes the necessary optimizations for running inference. Installing it locally ensures compatibility with your environment and avoids conflicts with default pip packages. +```bash +pip install torch-2.7.0.dev20250306-cp312-cp312-manylinux_2_28_aarch64.whl --force-reinstall --no-deps +``` + +You’re now ready to quantize the model and start serving it with `vLLM` on an Arm-based system. \ No newline at end of file diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md new file mode 100644 index 0000000000..b1c6390807 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md @@ -0,0 +1,178 @@ +--- +title: Quantize and Launch the vLLM server +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Access the Model from Hugging Face + +Before quantizing, authenticate with Hugging Face using a personal access token. You can generate one from your [Hugging Face Hub](https://huggingface.co/) account under Access Tokens: + +```bash +huggingface-cli login --token $hf_token +``` +## Quantization Script Template + +Create the `vllm_quantize_model.py` script shown below to quantize the model : +```bash +import argparse +import os +from transformers import AutoModelForCausalLM, AutoTokenizer + +from llmcompressor.modifiers.quantization import QuantizationModifier +from compressed_tensors.quantization import QuantizationScheme +from compressed_tensors.quantization.quant_args import ( + QuantizationArgs, + QuantizationStrategy, + QuantizationType, +) +from llmcompressor.transformers import oneshot + + +def main(): + parser = argparse.ArgumentParser( + description="Quantize a model using LLM Compressor with customizable mode, scheme, and group size." + ) + parser.add_argument( + "model_id", + type=str, + help="Model identifier or path (e.g., 'meta-llama/Llama-2-13b-chat-hf' or '/path/to/model')", + ) + parser.add_argument( + "--mode", + type=str, + choices=["int4", "int8"], + required=True, + help="Quantization mode: int4 or int8", + ) + parser.add_argument( + "--scheme", + type=str, + choices=["channelwise", "groupwise"], + required=True, + help="Quantization scheme for weights (groupwise is only supported for int4)", + ) + parser.add_argument( + "--groupsize", + type=int, + default=32, + help="Group size for groupwise quantization (only used when scheme is groupwise). Defaults to 32." + ) + args = parser.parse_args() + + # Validate unsupported configuration + if args.mode == "int8" and args.scheme == "groupwise": + raise ValueError("Groupwise int8 is unsupported. Please use channelwise for int8.") + + # Extract a base model name from the model id or path for the output directory + if "/" in args.model_id: + base_model_name = args.model_id.split("/")[-1] + else: + base_model_name = os.path.basename(args.model_id) + + # Determine output directory based on mode and scheme + if args.mode == "int4": + output_dir = f"{base_model_name}-w4a8-{args.scheme}" + else: # int8 + output_dir = f"{base_model_name}-w8a8-{args.scheme}" + + print(f"Loading model '{args.model_id}'...") + model = AutoModelForCausalLM.from_pretrained( + args.model_id, device_map="auto", torch_dtype="auto", trust_remote_code=True + ) + tokenizer = AutoTokenizer.from_pretrained(args.model_id) + + # Define quantization arguments based on mode and chosen scheme. + if args.mode == "int8": + # Only channelwise is supported for int8. + weights_args = QuantizationArgs( + num_bits=8, + type=QuantizationType.INT, + strategy=QuantizationStrategy.CHANNEL, + symmetric=True, + dynamic=False, + ) + else: # int4 mode + if args.scheme == "channelwise": + strategy = QuantizationStrategy.CHANNEL + weights_args = QuantizationArgs( + num_bits=4, + type=QuantizationType.INT, + strategy=strategy, + symmetric=True, + dynamic=False, + ) + else: # groupwise + strategy = QuantizationStrategy.GROUP + weights_args = QuantizationArgs( + num_bits=4, + type=QuantizationType.INT, + strategy=strategy, + group_size=args.groupsize, + symmetric=True, + dynamic=False + ) + + # Activation quantization remains the same for both modes. + activations_args = QuantizationArgs( + num_bits=8, + type=QuantizationType.INT, + strategy=QuantizationStrategy.TOKEN, + symmetric=False, + dynamic=True, + observer=None, + ) + + # Create a quantization scheme for Linear layers. + scheme = QuantizationScheme( + targets=["Linear"], + weights=weights_args, + input_activations=activations_args, + ) + + # Create a quantization modifier. We ignore the "lm_head" layer. + modifier = QuantizationModifier(config_groups={"group_0": scheme}, ignore=["lm_head"]) + + # Apply quantization and save the quantized model. + oneshot( + model=model, + recipe=modifier, + tokenizer=tokenizer, + output_dir=output_dir, + ) + print(f"Quantized model saved to: {output_dir}") + + +if __name__ == "__main__": + main() + + +``` +Then run the quantization script using `vllm_quantize_model.py`. This generates an INT8 quantized version of the model using channelwise precision, which reduces memory usage while maintaining model accuracy: + +```bash +cd /home/ubuntu/ +python vllm_quantize_model.py meta-llama/Llama-3.1-8B-Instruct --mode int8 --scheme channelwise +``` +The output model will be saved locally at: +`/home/ubuntu/Llama-3.1-8B-Instruct-w8a8-channelwise`. + +## Launch the vLLM server + +The vLLM server supports the OpenAI-compatible `/v1/chat/completions` API. This is used in this learning path for single-prompt testing with `curl` and for batch testing using a custom Python script that simulates multiple concurrent requests. + +Once the model is quantized, launch the vLLM server to enable CPU-based inference. This configuration uses `tcmalloc` and the optimized `OpenBLAS` build to improve performance and reduce latency: + +```bash +LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libtcmalloc_minimal.so.4:/home/ubuntu/OpenBLAS/libopenblas.so \ +ONEDNN_DEFAULT_FPMATH_MODE=BF16 \ +VLLM_TARGET_DEVICE=cpu \ +VLLM_CPU_KVCACHE_SPACE=32 \ +VLLM_CPU_OMP_THREADS_BIND="0-$(($(nproc) - 1))" \ +vllm serve /home/ubuntu/Llama-3.1-8B-Instruct-w8a8-channelwise \ +--dtype float32 --swap-space 16 +``` +This command starts the vLLM server using the quantized model. It preloads `tcmalloc` for efficient memory allocation and uses OpenBLAS for accelerated matrix operations. Thread binding is dynamically set based on the number of available cores to maximize parallelism on Arm CPUs. + diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md new file mode 100644 index 0000000000..f6eae52a1a --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md @@ -0,0 +1,195 @@ +--- +title: vLLM Inference Test +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Run Single Inference + +Once the server is running, start by verifying it with a basic single-prompt request using `curl`. This confirms the server is running correctly and that the OpenAI-compatible /v1/chat/completions API is responding as expected: + +```bash +curl http://localhost:8000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "/home/ubuntu/Llama-3.1-8B-Instruct-w8a8-channelwise", + "temperature": "0.0", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "tell me a funny story"} + ] + }' +``` +If the setup is working correctly, you'll receive a streaming response from the vLLM server. + +The server logs will show that the request was processed successfully. You'll also see prompt and generation throughput metrics, which provide a lightweight benchmark of the model’s performance in your environment. + +The following log output was generated from a single-prompt test run using the steps in this learning path: + +```output +INFO: Started server process [201749] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO 04-10 18:13:14 chat_utils.py:332] Detected the chat template content format to be 'string'. You can set `--chat-template-content-format` to override this. +INFO 04-10 18:13:14 logger.py:39] Received request chatcmpl-a71fae48603c4d90a5d9aa6efd740fec: prompt: '<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\nCutting Knowledge Date: December 2023\nToday Date: 26 Jul 2024\n\nYou are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\ntell me a funny story<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n', params: SamplingParams(n=1, presence_penalty=0.0, frequency_penalty=0.0, repetition_penalty=1.0, temperature=0.0, top_p=1.0, top_k=-1, min_p=0.0, seed=None, stop=[], stop_token_ids=[], bad_words=[], include_stop_str_in_output=False, ignore_eos=False, max_tokens=131026, min_tokens=0, logprobs=None, prompt_logprobs=None, skip_special_tokens=True, spaces_between_special_tokens=True, truncate_prompt_tokens=None, guided_decoding=None), prompt_token_ids: None, lora_request: None, prompt_adapter_request: None. +INFO 04-10 18:13:14 engine.py:275] Added request chatcmpl-a71fae48603c4d90a5d9aa6efd740fec. +WARNING 04-10 18:13:15 cpu.py:143] Pin memory is not supported on CPU. +INFO 04-10 18:13:17 metrics.py:455] Avg prompt throughput: 9.2 tokens/s, Avg generation throughput: 11.6 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.1%, CPU KV cache usage: 0.0%. +INFO 04-10 18:13:22 metrics.py:455] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 27.0 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.2%, CPU KV cache usage: 0.0%. +INFO 04-10 18:13:27 metrics.py:455] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 26.5 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.3%, CPU KV cache usage: 0.0%. +INFO: 127.0.0.1:45986 - "POST /v1/chat/completions HTTP/1.1" 200 OK + +``` + +These results confirm that the model is running efficiently on CPU, with stable prompt and generation throughput — a solid baseline before scaling to batch inference. + +## Run Batch Inference + +After confirming single-prompt inference, run batch testing to simulate concurrent load and measure server performance at scale. + +Use the following Python script to simulate concurrent user interactions. + +Save it as `batch_test.py`: +```bash +import requests +import json +import os +import time +import multiprocessing +import argparse + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +# prompts (duplicate questions) +# https://github.com/ggml-org/llama.cpp/blob/b4753/examples/parallel/parallel.cpp#L42-L52 +prompts = [ + #"Tell me a joke about AI.", + "What is the meaning of life?", + "Tell me an interesting fact about llamas.", + "What is the best way to cook a steak?", + "Are you familiar with the Special Theory of Relativity and can you explain it to me?", + "Recommend some interesting books to read.", + "What is the best way to learn a new language?", + "How to get a job at Google?", + "If you could have any superpower, what would it be?", + "I want to learn how to play the piano.", + "What is the meaning of life?", + "Tell me an interesting fact about llamas.", + "What is the best way to cook a steak?", + "Are you familiar with the Special Theory of Relativity and can you explain it to me?", + "Recommend some interesting books to read.", + "What is the best way to learn a new language?", + "How to get a job at Google?", +] + +def get_stream(url, prompt, index): + s = requests.Session() + print(bcolors.OKGREEN, "Sending request #{}".format(index), bcolors.ENDC) + with s.post(url, headers=None, json=prompt, stream=True) as resp: + print(bcolors.WARNING, "Waiting for the reply #{} to the prompt '".format(index) + prompt["messages"][0]["content"] + "'", bcolors.ENDC) + for line in resp.iter_lines(): + if line: + print(line) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + # this is a mandatory parameter + parser.add_argument("server", help="llama server IP ir DNS address", type=str) + parser.add_argument("port", help="llama server port", type=int) + parser.add_argument("-s", "--stream", help="stream the reply", action="store_true") + parser.add_argument("-b", "--batch", help="concurrent request batch size", type=int, default=1) + parser.add_argument("--max_tokens", help="maximum output tokens", type=int, default=128) + parser.add_argument("--schema", help="enndpoint schema (http/https)", type=str, default="http", choices=["http", "https"]) + parser.add_argument("-m", "--model", help="model name", type=str) + args = parser.parse_args() + + # by default, OpenAI-compatible API is used for the tests, which is supported by both llama.cpp and vllm + openAPI_endpoint = "/v1/chat/completions" + server = args.schema + "://" + args.server + ":" + str(args.port) + openAPI_endpoint + + print(server) + start = time.time() + + proc = [] + for i in range(args.batch): + prompt = { + "messages": [ + {"role": "user", "content": prompts[i]} + ], + "model": args.model, + "temperature": 0, + "max_tokens": args.max_tokens, # for vllm, it ignores n_predict + "n_predict": args.max_tokens, # for llama.cpp (will be ignored by vllm) + "stream": False # streaming + } + + proc.append(multiprocessing.Process(target=get_stream, args=(server, prompt, i))) + + # start the processes + for p in proc: + p.start() + + # wait for all the processes to finish + for p in proc: + p.join() + + end = time.time() + print("done!") + print(end - start) +``` +Then, run it using: + +```bash +python batch_test.py localhost 8000 --schema http --batch 16 -m /home/ubuntu/Llama-3.1-8B-Instruct-w8a8-channelwise +``` +This simulates multiple users interacting with the model in parallel and helps validate server-side performance under load. +You can modify the number of requests using the --batch flag or review/edit batch_test.py to customize prompt content and concurrency logic. + +When the test completes, server logs will display a summary including average prompt throughput and generation throughput. This helps benchmark how well the model performs under concurrent load on your Arm-based system. + +### Sample Output +Your logs should display successful responses and performance stats, confirming the model handles concurrent requests as expected. + +The following log output was generated from a batch inference run using the steps in this learning path: + +```output +INFO 04-10 18:20:55 metrics.py:455] Avg prompt throughput: 144.4 tokens/s, Avg generation throughput: 153.4 tokens/s, Running: 16 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 1.2%, CPU KV cache usage: 0.0%. +INFO 04-10 18:21:00 metrics.py:455] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 239.9 tokens/s, Running: 16 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 2.1%, CPU KV cache usage: 0.0%. +INFO: 127.0.0.1:57558 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57574 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57586 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57600 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57604 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57620 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57634 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57638 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57644 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57654 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57660 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57676 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57684 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57696 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57712 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO: 127.0.0.1:57718 - "POST /v1/chat/completions HTTP/1.1" 200 OK +INFO 04-10 18:21:10 metrics.py:455] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 7.7 tokens/s, Running: 0 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0 +``` + +This output confirms the server is handling concurrent requests effectively, with consistent generation throughput across 16 requests — a strong indication of stable multi-request performance on CPU. + +### Go Beyond: Power Up Your vLLM Workflow +Now that you’ve successfully quantized and served a model using vLLM on Arm, here are some further ways to explore: + +* **Try different models:** Apply the same steps to other [Hugging Face models](https://huggingface.co/models) like Qwen or Gemma. + +* **Connect a chat client:** Link your server with OpenAI-compatible UIs like [Open WebUI](https://github.com/open-webui/open-webui) or explore [OpenAI-compatible clients](https://github.com/topics/openai-api-client). diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md new file mode 100644 index 0000000000..7cebfcee55 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md @@ -0,0 +1,59 @@ +--- +title: Quantize and Run a Large Language Model using vLLM on Arm Servers + + +minutes_to_complete: 45 + +who_is_this_for: This learning path is intended for software developers and AI engineers interested in optimizing and deploying large language models using vLLM on Arm-based servers. It’s ideal for those looking to explore CPU-based inference and model quantization techniques. + +learning_objectives: + - Build and configure OpenBLAS to optimize LLM performance. + - Set up vLLM and PyTorch using builds optimized for Arm CPUs. + - Download and quantize a large language model using INT8 techniques. + - Launch a vLLM server to serve the quantized model. + - Run single-prompt and batch inference using the vLLM OpenAI-compatible API. + + +prerequisites: + - An Arm-based server or cloud instance running with at least 32 CPU cores, 64 GB RAM and 80 GB of available disk space. + - Familiarity with Python and machine learning concepts. + - An active Hugging Face account with access to the target model. + +author: Rani Chowdary Mandepudi + +### Tags +skilllevels: Introductory +subjects: ML +armips: + - Neoverse +operatingsystems: + - Linux +tools_software_languages: + - vLLM + - LLM + - GenAI + - Python + + +further_reading: + - resource: + title: vLLM Documentation + link: https://docs.vllm.ai/ + type: documentation + - resource: + title: vLLM GitHub Repository + link: https://github.com/vllm-project/vllm + type: github + - resource: + title: Hugging Face Model Hub + link: https://huggingface.co/models + type: website + + + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_next-steps.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- From 19c60e5c66d3aa19a28576327918815001a40429 Mon Sep 17 00:00:00 2001 From: GitHub Actions Stats Bot <> Date: Mon, 14 Apr 2025 02:39:47 +0000 Subject: [PATCH 038/122] automatic update of stats files --- data/stats_current_test_info.yml | 10 +-- data/stats_weekly_data.yml | 102 +++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/data/stats_current_test_info.yml b/data/stats_current_test_info.yml index 1b80959299..d083eb22de 100644 --- a/data/stats_current_test_info.yml +++ b/data/stats_current_test_info.yml @@ -1,5 +1,5 @@ summary: - content_total: 347 + content_total: 348 content_with_all_tests_passing: 31 content_with_tests_enabled: 59 sw_categories: @@ -78,10 +78,6 @@ sw_categories: readable_title: Azure CLI tests_and_status: - ubuntu:latest: passed - bedrust: - readable_title: Bedrust - tests_and_status: - - ubuntu:latest: passed bolt: readable_title: BOLT tests_and_status: [] @@ -144,10 +140,6 @@ sw_categories: readable_title: PyTorch tests_and_status: - ubuntu:latest: passed - ros2: - readable_title: ROS2 - tests_and_status: - - ubuntu:latest: passed rust: readable_title: Rust for Linux Applications tests_and_status: [] diff --git a/data/stats_weekly_data.yml b/data/stats_weekly_data.yml index 761864c8da..5d20a2b1bf 100644 --- a/data/stats_weekly_data.yml +++ b/data/stats_weekly_data.yml @@ -5592,3 +5592,105 @@ avg_close_time_hrs: 0 num_issues: 10 percent_closed_vs_total: 0.0 +- a_date: '2025-04-14' + content: + automotive: 1 + cross-platform: 30 + embedded-and-microcontrollers: 41 + install-guides: 96 + iot: 5 + laptops-and-desktops: 35 + mobile-graphics-and-gaming: 29 + servers-and-cloud-computing: 111 + total: 348 + contributions: + external: 91 + internal: 458 + github_engagement: + num_forks: 30 + num_prs: 11 + individual_authors: + adnan-alsinan: 1 + alaaeddine-chakroun: 2 + albin-bernhardsson: 1 + alex-su: 1 + alexandros-lamprineas: 1 + andrew-choi: 1 + annie-tallund: 3 + arm: 3 + arnaud-de-grandmaison: 3 + avin-zarlez: 1 + basma-el-gaabouri: 1 + ben-clark: 1 + bolt-liu: 2 + brenda-strech: 1 + chaodong-gong: 1 + chen-zhang: 1 + christopher-seidl: 7 + cyril-rohr: 1 + daniel-gubay: 1 + daniel-nguyen: 2 + david-spickett: 2 + dawid-borycki: 32 + diego-russo: 2 + dominica-abena-o.-amanfo: 1 + elham-harirpoush: 2 + florent-lebeau: 5 + "fr\xE9d\xE9ric--lefred--descamps": 2 + gabriel-peterson: 5 + gayathri-narayana-yegna-narayanan: 1 + georgios-mermigkis: 1 + geremy-cohen: 1 + graham-woodward: 1 + han-yin: 1 + iago-calvo-lista: 1 + james-whitaker: 1 + jason-andrews: 98 + joe-stech: 4 + johanna-skinnider: 2 + jonathan-davies: 2 + jose-emilio-munoz-lopez: 1 + julie-gaskin: 5 + julio-suarez: 6 + kasper-mecklenburg: 1 + kieran-hejmadi: 6 + koki-mitsunami: 2 + konstantinos-margaritis: 9 + kristof-beyls: 1 + leandro-nunes: 1 + liliya-wu: 1 + masoud-koleini: 1 + mathias-brossard: 1 + michael-hall: 5 + na-li: 1 + nader-zouaoui: 2 + nikhil-gupta: 1 + nobel-chowdary-mandepudi: 4 + odin-shen: 4 + owen-wu: 2 + pareena-verma: 42 + paul-howard: 2 + pranay-bakre: 5 + preema-merlin-dsouza: 1 + przemyslaw-wirkus: 2 + rani-chowdary-mandepudi: 1 + ravi-malhotra: 1 + rin-dobrescu: 1 + roberto-lopez-mendez: 2 + ronan-synnott: 46 + shuheng-deng: 1 + thirdai: 1 + tianyu-li: 1 + tom-pilar: 1 + uma-ramalingam: 1 + varun-chari: 2 + visualsilicon: 1 + willen-yang: 1 + ying-yu: 2 + yiyang-fan: 1 + zach-lasiuk: 2 + zhengjun-xing: 2 + issues: + avg_close_time_hrs: 0 + num_issues: 16 + percent_closed_vs_total: 0.0 From 38a547f4d2fe92bb3e2670e4b15c116ff0b7f7ad Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 15 Apr 2025 09:51:56 +0200 Subject: [PATCH 039/122] Update tj-actions/changed-files to v46 --- .github/workflows/test-lp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index c94e140392..125bed9aec 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -10,7 +10,7 @@ jobs: ref: ${{ steps.vars.outputs.branch-name }} - name: Get all changed markdown files id: changed-markdown-files - uses: tj-actions/changed-files@v44 + uses: tj-actions/changed-files@v46 with: files: | **.md From 6a64e7da6e962405d2d590ac375805e3cf5afdce Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 15 Apr 2025 17:39:09 +0100 Subject: [PATCH 040/122] initial commit for LP --- .../disk-io-benchmark/_index.md | 40 ++++++ .../disk-io-benchmark/_next-steps.md | 8 ++ .../characterising-workload.md | 131 +++++++++++++++++ .../disk-io-benchmark/diskio.jpeg | Bin 0 -> 31998 bytes .../disk-io-benchmark/introduction.md | 50 +++++++ .../disk-io-benchmark/using-fio.md | 134 ++++++++++++++++++ 6 files changed, 363 insertions(+) create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_next-steps.md create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/diskio.jpeg create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md new file mode 100644 index 0000000000..5a19949afa --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md @@ -0,0 +1,40 @@ +--- +title: Benchmark Storage I/O Performance with Fio + +minutes_to_complete: 30 + +who_is_this_for: A cloud developer who wants to optimise storage cost or performance of their application. Developers who want to uncover potential storage-bound bottlenecks or changes when migrating an application to a different platform. + +learning_objectives: + - Understand basic data flow for storage devices + - Understand how to run the fio microbenchmark + +prerequisites: + - Access to an Arm-based server + - Basic understanding of Linux + +author: Kieran Hejmadi + +### Tags +skilllevels: Introductory +subjects: Runbook, Performance and Architecture +armips: + - Neoverse +tools_software_languages: + - bash +operatingsystems: + - Linux + + +further_reading: + - resource: + title: Fio documentation + link: https://fio.readthedocs.io/en/latest/fio_doc.html#running-fio + type: documentation + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_next-steps.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md new file mode 100644 index 0000000000..a0ad33e8cb --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md @@ -0,0 +1,131 @@ +--- +title: Characterising a Workload +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Characterising your Workload + +The basic attributes of a given workload are the following. + +- IOPS +- I/O Size +- Throughput +- Read to Write Ratio +- Random vs Sequential access + +The characteristics of many real-world workloads will vary over time, for example an application that periodically flushes writes to disk. Further, the system-wide and process-specific characteristics may be significantly differently. + +## Example Workload + +Connect to an Arm-based cloud instance. As an example workload, we will be using the media manipulation tool, FFMPEG on an AWS `t4g.medium` instance, using the default block storage options provided by AWS. + + +```bash +sudo apt update +sudo apt install ffmpeg iotop -y +``` + +Download the popular example video, `BigBuckBunny.mp4` to demonstrate a transcoding workload. + +```bash +wget http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 +``` + +Run the following command to begin transcoding the video and audio using the `H.264` and `aac` transcoders respectively. We use the `-flush_packets` flag to write each chunk of video back to storage. + +```bash +ffmpeg -i BigBuckBunny.mp4 -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k -flush_packets 1 output_video.mp4 +``` +Whilst the transcoding is running, we can use the `pidstat` command to see the disk statistics. + +```bash +pidstat -d -p $(pgrep ffmpeg) 1 +``` +From the table below, we can observe this process is predominantly writing to disk at ~`300 kB/s`. + +```output +Linux 6.8.0-1024-aws (ip-10-248-213-118) 04/15/25 _aarch64_ (2 CPU) + +10:01:24 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command +10:01:25 1000 24250 0.00 276.00 0.00 0 ffmpeg +10:01:26 1000 24250 0.00 256.00 0.00 0 ffmpeg +10:01:27 1000 24250 0.00 216.00 0.00 0 ffmpeg +10:01:28 1000 24250 0.00 184.00 0.00 0 ffmpeg +10:01:29 1000 24250 0.00 424.00 0.00 0 ffmpeg +10:01:30 1000 24250 0.00 312.00 0.00 0 ffmpeg +10:01:31 1000 24250 0.00 372.00 0.00 0 ffmpeg +10:01:32 1000 24250 0.00 344.00 0.00 0 ffmpeg +``` + +Since this example `151MB` video fits within memory, we observe no `kB_rd/s` for the storage device. However, since we are flushing to storage we observe ~275 `kB_wr/s` for this specific process. + +We can use iotop to confirm that our `ffmpeg` process has the greatest disk utilisation. + +```bash +sudo iotop +``` + +```output +Total DISK READ: 0.00 B/s | Total DISK WRITE: 332.11 K/s +Current DISK READ: 0.00 B/s | Current DISK WRITE: 0.00 B/s + TID PRIO USER DISK READ DISK WRITE> COMMAND + 24891 be/4 ubuntu 0.00 B/s 332.11 K/s ffmpeg -i BigBuckBunny.mp4 -c:v ~ts 1 output_video.mp4 [mux0:mp4] + 1 be/4 root 0.00 B/s 0.00 B/s systemd --system --deserialize=74 + 2 be/4 root 0.00 B/s 0.00 B/s [kthreadd] +``` + +Using the input, output statistics command (`iostat`) we can observe the system-wide metrics from the `nvme0n1` drive. Please Note that we are using a snapshot of this workload, more accurate characteristics can be obtained by measuring the distribution of a workload. + +```bash +watch -n 0.1 iostat -z nvme0n1 +``` +You should see output similar to that below. + +```output +Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd +nvme0n1 3.81 31.63 217.08 0.00 831846 5709210 0 +``` + +The following characteristics are calculated as follows. + +1. IOPS + +**Value:** 3.81 +_This is taken directly from the `tps` (transfers per second) field._ + +2. Throughput + +**Read:** 31.63 kB/s +**Write:** 217.08 kB/s +**Total:** 248.71 kB/s +_Sum of read and write throughput values._ + +3. Average I/O Size + +**Value:** ≈ 65.3 KB +_Calculated as total throughput divided by IOPS: 248.71 / 3.81._ + + +4. Read/Write Ratio + +**Read:** ~13% +**Write:** ~87% +_Computed as: (read or write throughput) ÷ total throughput._ + +Finally, to the access pattern of our workload (i.e. random vs. sequential) is slightly more complicated. We can infer the locality of our accesses through our understanding of the program, the cache hit rate (for reads), and merge rate on the dispatch side. Metrics that show high degree of merging, cache hits and low wait times suggest that our accesses are sequential in nature. + +Running the following command. + +```bash +iostat -xz nvme0n1 +``` + +```output +Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util +nvme0n1 0.66 29.64 0.24 26.27 0.73 44.80 2.92 203.88 3.17 52.01 2.16 69.70 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.01 0.15 +``` + +The `wrqm/s` column is the number of write requests merged per second before being issued. Calculated the percentage or writes merged compared to writes is one indicator of how sequental the data accesses are. diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/diskio.jpeg b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/diskio.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..cd21b646545b67714ab40a69c37953c639ee2423 GIT binary patch literal 31998 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 tvP|ZGM}Fy# Date: Wed, 16 Apr 2025 07:24:00 +0200 Subject: [PATCH 041/122] Update acfl.md --- content/install-guides/acfl.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index dd83ea355a..0ae58aad96 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -149,7 +149,7 @@ Arm Compiler for Linux is available to install with the Ubuntu system package ma Add the ACfL `apt` package repository to your system. These instructions apply for Ubuntu 22.04. You can check the [available versions](https://developer.arm.com/packages/) for other releases. -After that, the ACfL Ubuntu package repository is now ready to use. Download and install Arm Compiler for Linux with: +After that, the ACfL Ubuntu package repository is now ready to use. Run the commands below to install the dependencies needed. ```bash { target="ubuntu:latest" } @@ -158,6 +158,11 @@ sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev wget -qO - https://developer.arm.com/packages/ACfL:Ubuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc echo "deb https://developer.arm.com/packages/ACfL:Ubuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update +``` + +Install Arm Compiler for Linux with: + +```console sudo apt install acfl -y ``` From 3bcf2053a67428bb6c93aafc13cd1fa15277feae Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 16 Apr 2025 07:28:25 +0200 Subject: [PATCH 042/122] Update Ubuntu image to 24.04 --- .github/workflows/test-lp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index 47c950768a..068fcf1a01 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -2,7 +2,7 @@ name: Test Learning Path on: pull_request jobs: Test-Pull-Request: - runs-on: ubuntu-22.04-arm + runs-on: ubuntu-24.04-arm steps: - name: Check out repository code uses: actions/checkout@v4 From 3c0b5a9b64bfdae90dc06573b050cf652c15dc8c Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 16 Apr 2025 08:04:59 +0200 Subject: [PATCH 043/122] Update acfl.md --- content/install-guides/acfl.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 0ae58aad96..67724dcb46 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -155,17 +155,14 @@ After that, the ACfL Ubuntu package repository is now ready to use. Run the comm ```bash { target="ubuntu:latest" } sudo apt update sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev -wget -qO - https://developer.arm.com/packages/ACfL:Ubuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc -echo "deb https://developer.arm.com/packages/ACfL:Ubuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list +wget -qO - https://developer.arm.com/packages/arm-toolchains%3Aubuntu-24/linux/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +echo "deb https://developer.arm.com/packages/arm-toolchains%3Aubuntu-24/linux/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update +sudo apt install acfl -y ``` Install Arm Compiler for Linux with: -```console -sudo apt install acfl -y -``` - ### Amazon Linux 2023 Arm Compiler for Linux is available to install with either the `dnf` or `yum` system package manager. From 07153cbdc2c5d3e10f13eb579b0224130aea7caf Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 16 Apr 2025 09:45:59 +0200 Subject: [PATCH 044/122] Update acfl.md --- content/install-guides/acfl.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 67724dcb46..e11b513a1c 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -152,11 +152,11 @@ Add the ACfL `apt` package repository to your system. These instructions apply f After that, the ACfL Ubuntu package repository is now ready to use. Run the commands below to install the dependencies needed. -```bash { target="ubuntu:latest" } +```bash sudo apt update sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev -wget -qO - https://developer.arm.com/packages/arm-toolchains%3Aubuntu-24/linux/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc -echo "deb https://developer.arm.com/packages/arm-toolchains%3Aubuntu-24/linux/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list +wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc +echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update sudo apt install acfl -y ``` From e2abdec7c1dae64c47f796a414d035641bdb32fd Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 16 Apr 2025 11:12:16 +0200 Subject: [PATCH 045/122] Update acfl.md --- content/install-guides/acfl.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index e11b513a1c..2d8a5a12af 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -158,11 +158,14 @@ sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc echo "deb https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/ ./" | sudo tee /etc/apt/sources.list.d/developer-arm-com.list sudo apt update -sudo apt install acfl -y ``` Install Arm Compiler for Linux with: +```consoles +sudo apt install acfl -y +``` + ### Amazon Linux 2023 Arm Compiler for Linux is available to install with either the `dnf` or `yum` system package manager. From 7e77dd53003e3b019d6b6e420f02379dd749a2cd Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 16 Apr 2025 12:01:17 +0200 Subject: [PATCH 046/122] Update acfl.md, oci-cli.md, armral.md --- content/install-guides/acfl.md | 2 +- content/install-guides/oci-cli.md | 4 ++-- .../servers-and-cloud-computing/ran/armral.md | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 2d8a5a12af..8ae6b64ec8 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -152,7 +152,7 @@ Add the ACfL `apt` package repository to your system. These instructions apply f After that, the ACfL Ubuntu package repository is now ready to use. Run the commands below to install the dependencies needed. -```bash +```console sudo apt update sudo apt install -y wget gnupg gpg environment-modules python3 libc6-dev wget -qO - https://developer.arm.com/packages/ACfL%3AUbuntu-22/jammy/Release.key | sudo tee /etc/apt/trusted.gpg.d/developer-arm-com.asc diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 663a85b656..c6e45ee75f 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -70,8 +70,8 @@ exec -l $SHELL Verify OCI CLI is installed using the `--version` option: -```bash { target="ubuntu:latest", env_source="$HOME/.bashrc" } -cat $HOME/.bashrc && oci --version +```bash { target="ubuntu:latest", env_source=".bashrc" } +oci --version ``` Configure OCI CLI using the `setup config` command: diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index c6266a5d9d..27cb525fac 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -38,7 +38,6 @@ The source code is available from the [GitLab repository](https://gitlab.arm.com Use `git` to download the code: ```bash -cd $HOME git clone https://git.gitlab.arm.com/networking/ral.git ``` @@ -74,7 +73,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash { cwd="$HOME/ral/build" } +```bash cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -102,7 +101,7 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash { cwd="$HOME/ral/build" } +```bash make ``` @@ -110,7 +109,7 @@ make Install the library: -```bash { cwd="$HOME/ral/build" } +```bash sudo make install ``` From d4789ef648640cdfcfba60b1dfa3c8958be4d79b Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 16 Apr 2025 11:38:21 +0100 Subject: [PATCH 047/122] added workload characteristics --- .../disk-io-benchmark/EBS.png | Bin 0 -> 118494 bytes .../disk-io-benchmark/_index.md | 2 +- .../characterising-workload.md | 4 +- .../disk-io-benchmark/introduction.md | 3 +- .../disk-io-benchmark/using-fio.md | 131 +++++++++++------- 5 files changed, 83 insertions(+), 57 deletions(-) create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/EBS.png diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/EBS.png b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/EBS.png new file mode 100644 index 0000000000000000000000000000000000000000..13f8422e2efb743370f6a140411a4601c75ecfdc GIT binary patch literal 118494 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®m{o_aH$^aE1 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 literal 0 HcmV?d00001 diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md index 5a19949afa..42d40167ae 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md @@ -1,5 +1,5 @@ --- -title: Benchmark Storage I/O Performance with Fio +title: Microbenchmark Storage Performance with Fio minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md index a0ad33e8cb..6b2d6986fb 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md @@ -20,7 +20,9 @@ The characteristics of many real-world workloads will vary over time, for exampl ## Example Workload -Connect to an Arm-based cloud instance. As an example workload, we will be using the media manipulation tool, FFMPEG on an AWS `t4g.medium` instance, using the default block storage options provided by AWS. +Connect to an Arm-based cloud instance. As an example workload, we will be using the media manipulation tool, FFMPEG on an AWS `t4g.medium` instance, with 2 Elastic Block Storage (EBS) volumes as per the image below. + +![ebs](./EBS.png) ```bash diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md index 3530320db3..5944de1bc4 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md @@ -46,5 +46,4 @@ In benchmarking tools like fio, you might select I/O engines such as sync (synch ### Typical Storage Types for Cloud Instances -Instance storage = volatile, lost when instance is stopped -Block storage = e.g., El \ No newline at end of file +In a cloud environment, you are typically sharing a physical instance of a server with multiple tenants. As such the CPU cores and storage devices may be shared. As such, cloud service providers often provide an IOPS per GB. Where your system's storage performance is proportional to the block device size. See [this example](https://docs.oracle.com/en-us/iaas/Content/Block/Concepts/blockvolumeperformance.htm) for more information. \ No newline at end of file diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md index d1428238b9..64dca8dff7 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md @@ -6,7 +6,7 @@ weight: 4 layout: learningpathall --- -## Simulating Workload with FIO +## Install Fio Flexible I/O (fio) is a command-line tool to generate a synthetic workload with specific I/O characteristics. @@ -29,106 +29,131 @@ fio-3.36 gnuplot 6.0 patchlevel 0 ``` - -## Best Practices for Microbenchmarking - -Software which you did not write or has been less extensively tested will have bugs. Be aware of some of the known [bug tracker](https://github.com/axboe/fio/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug). - -List any assumptions you have, for example, there are no chron jobs running at the time, that the storage media has an on-disk cache, the storage device has the latest stable firmware, temperatures are nominal. - ## Locate Device -Use the disk free, `df` command to see the mounted storage devices connected to your server. We are excluding the temporary file system, `tmpfs` for clarity. +We have the option to directly microbenchmark the block device or microbenchmark a mounted filesystem. Use the disk free, `df` command to see if the mounted storage devices connected to your server. We are excluding the temporary file system, `tmpfs` for clarity. ```bash df -hTx tmpfs ``` -As we can see the storage we configured through the AWS console is at `/dev/root` with 30GB available. **Please Note** using the incorrect filesystem name can cause issues. +The output below shows a 30GB ```output Filesystem Type Size Used Avail Use% Mounted on -/dev/root ext4 30G 2.2G 28G 8% / -efivarfs efivarfs 128K 3.1K 125K 3% /sys/firmware/efi/efivars +Filesystem Type Size Used Avail Use% Mounted on +/dev/root ext4 6.8G 2.8G 4.0G 41% / +efivarfs efivarfs 128K 3.7K 125K 3% /sys/firmware/efi/efivars /dev/nvme0n1p16 ext4 891M 57M 772M 7% /boot /dev/nvme0n1p15 vfat 98M 6.4M 92M 7% /boot/efi ``` -Using the `lsblk` command to view the 2 elastic block storage (EBS) devices attached to the server (`nvme1n1` and `nvme2n1`). This may be useful if you want to target a specific partition of the disk. Here we can see how the `NVMe` storage device has been partitioned. Fio allows us to test a device through a filesystem or to directly write to block device without a file system. +Using the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. + +```bash +lsblk -e 7 +``` ```output -nvme2n1 259:0 0 16G 0 disk -nvme0n1 259:1 0 16G 0 disk -├─nvme0n1p1 259:3 0 15G 0 part / +NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS +nvme1n1 259:0 0 8G 0 disk +nvme0n1 259:1 0 8G 0 disk +├─nvme0n1p1 259:3 0 7G 0 part / ├─nvme0n1p15 259:4 0 99M 0 part /boot/efi └─nvme0n1p16 259:5 0 923M 0 part /boot -nvme1n1 259:2 0 16G 0 disk +nvme2n1 259:2 0 8G 0 disk ``` +We can use the `blkid` command to find the directory for `nvme1n1`. ## Generating a Synthetic Workload -The fio tool uses simple configuration `jobfiles` to describe the characterisics of your synthetic workload. A template `jobfile` is shown below. Parameters under the `[global]` option are shared among jobs. These can be overwritten if a new value is specified under the job. - -```output -; -- start job file -- -[global] -; Common param -[job1] -; job1's parameters -[job2] -; job2's parameters -``` +Let us say we want to simulate an loggin system with the following characteristics observed using the tools from the previous section. -Please refer to the [documentation](https://fio.readthedocs.io/en/latest/fio_doc.html#job-file-format) for full details on the configuration file structure. +*The logging workload has a light sequential write averaging 100 IOPS. The system write throughput is 5 MB/s with 83% writes. There are infrequent bursts of reads, operating at 1000 IOPS and 256MB/s. The workload can scale the infrequent reads and writes to use up to 16 threads each.* +The fio tool uses simple configuration `jobfiles` to describe the characterisics of your synthetic workload. A template `jobfile` is shown below. Parameters under the `[global]` option are shared among jobs. From the example above, we have created 2 jobs to represent the steady write and infrequent reads. -Let us say we want to simulate an I/O workload with the following characteristed observed using various tools. +Please refer to the [documentation](https://fio.readthedocs.io/en/latest/fio_doc.html#job-file-format) for full details on the configuration file structure. -*The system's disks have a light sequential write workload averaging 100 IOPS. The system throughput is 5 MB/s with 80% writes. There are infrequent bursts of reads, operating at 1000 IOPS and 256MB/s. The workload can scale the infrequent read to use all the available cores. However, the writes are limited to a single job* +Copy and paste the configuration file below into 2 files named `nvme.fio`. Replace the `` with the block devices we are comparing and just the `filename` parameter accordingly. -Copy and paste the configuration file below into a file named `example.fio`. Replace the `filename` with the location of where you'd like to read / write to. Please note: Do not write to a drive that contains critical information such as drives used for booting. We recommend writing to an unformatted block device or a mounted filesystem that you are OK to lose data from. +**Please note**: Do not write to a drive that contains critical information such as drives used for booting. We recommend writing to an unformatted block device or a mounted filesystem that you are OK to lose data from. ```ini ; -- start job file including.fio -- [global] ioengine=libaio -direct=1 +direct=1 ; write directly to the drive time_based runtime=30 -group_reporting +group_reporting=1 log_avg_msec=1000 -filename=/dev/nvme1n1 +filename=/dev/nvme1n1 ; or nvme1n1 [steady_write] name=steady_write rw=write -bs=64k -rate=5m -numjobs=1 -iodepth=1 -write_bw_log=steady_write -write_iops_log=steady_write -write_lat_log=steady_write +bs=64k ; Block size of 64KiB (default block size of 4 KiB) +rate=5m ; limit to 5 MiB/s +numjobs=${NUM_JOBS} ; set at the command line +iodepth=${IO_DEPTH} + [burst_read] name=burst_read rw=read -bs=256k -rate=256m +bs=256k ; adjust the block size to 64KiB writes (default is 4KiB) +rate=256m ; limit to 256 MiB/s numjobs=${NUM_JOBS} -iodepth=64 -startdelay=10 -runtime=5 -write_bw_log=burst_read -write_iops_log=burst_read -write_lat_log=burst_read - +iodepth=${IO_DEPTH} +startdelay=10 ; simulate infrequent reads (5 seconds out 30) +runtime=5s ; -- end job file including.fio -- ``` -Run the following command to run the workload. +Run the following commands to run each test back to back. ```bash -sudo NUM_JOBS=$(nproc) fio example.fio +sudo NUM_JOBS=16 IO_DEPTH=64 fio nvme1.fio ``` +Then + +```bash +sudo NUM_JOBS=16 IO_DEPTH=64 fio nvme2.fio +``` +The output from NVMe1 shows: + +```output +Run status group 0 (all jobs): + READ: bw=127MiB/s (133MB/s), 127MiB/s-127MiB/s (133MB/s-133MB/s), io=677MiB (710MB), run=5333-5333msec + WRITE: bw=80.0MiB/s (83.9MB/s), 80.0MiB/s-80.0MiB/s (83.9MB/s-83.9MB/s), io=2400MiB (2517MB), run=30003-30003msec + +Disk stats (read/write): + nvme1n1: ios=2805/38226, sectors=1391184/4892928, merge=0/0, ticks=188746/514998, in_queue=703745, util=88.53% +``` + +```output +Run status group 0 (all jobs): + READ: bw=87.9MiB/s (92.1MB/s), 87.9MiB/s-87.9MiB/s (92.1MB/s-92.1MB/s), io=462MiB (484MB), run=5256-5256msec + WRITE: bw=59.5MiB/s (62.4MB/s), 59.5MiB/s-59.5MiB/s (62.4MB/s-62.4MB/s), io=1794MiB (1881MB), run=30128-30128msec + +Disk stats (read/write): + nvme2n1: ios=1896/28700, sectors=947760/3673600, merge=0/0, ticks=169728/1054671, in_queue=1224400, util=80.22% +``` + +More interestingly, the latency for each request is different. + +```output + lat (msec) : 2=57.80%, 4=0.15%, 10=0.34%, 20=1.46%, 50=3.50% + lat (msec) : 100=1.85%, 250=6.17%, 500=11.80%, 750=5.73%, 1000=2.79% + lat (msec) : 2000=4.41%, >=2000=4.01% +``` + +With the `nvme2` device showing 20% of writes taking 2+ seconds to confirm a write, compared to the 4% with `nvme1`. These insights suggest that if this contrived logging system is latency sensitive, the `nvme2` can offer better performance. + +```output + lat (msec) : 2=30.37%, 4=0.07%, 10=0.37%, 20=0.14%, 50=0.07% + lat (msec) : 100=0.46%, 250=1.89%, 500=6.17%, 750=9.99%, 1000=9.27% + lat (msec) : 2000=20.61%, >=2000=9.02% +``` From 2f17a3d1b52f5f57dd2b60fe5cd87f987eef2715 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 16 Apr 2025 13:18:01 +0200 Subject: [PATCH 048/122] Update oci-cli.md, armral.md --- content/install-guides/oci-cli.md | 10 ++-------- .../servers-and-cloud-computing/ran/armral.md | 12 ++++++------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index c6e45ee75f..6888f8c0c9 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -53,7 +53,7 @@ To run an an automated install with default values run: ```bash { target="ubuntu:latest" } curl -o install.sh https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh -bash ./install.sh --accept-all-defaults +bash ./install.sh --accept-all-defaults --update-path-and-enable-tab-completion --rc-file-path="$HOME/.bashrc" ``` To run an interactive install that allows you to change default values run: @@ -62,15 +62,9 @@ To run an interactive install that allows you to change default values run: bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" ``` -Run the command below to restart your shell. This will enable the tab completion for the command. - -```bash { target="ubuntu:latest" } -exec -l $SHELL -``` - Verify OCI CLI is installed using the `--version` option: -```bash { target="ubuntu:latest", env_source=".bashrc" } +```bash { target="ubuntu:latest", env_source="$HOME/.bashrc" } oci --version ``` diff --git a/content/learning-paths/servers-and-cloud-computing/ran/armral.md b/content/learning-paths/servers-and-cloud-computing/ran/armral.md index 27cb525fac..d959ebef45 100644 --- a/content/learning-paths/servers-and-cloud-computing/ran/armral.md +++ b/content/learning-paths/servers-and-cloud-computing/ran/armral.md @@ -37,7 +37,7 @@ The source code is available from the [GitLab repository](https://gitlab.arm.com Use `git` to download the code: -```bash +```bash { cwd="$HOME" } git clone https://git.gitlab.arm.com/networking/ral.git ``` @@ -45,7 +45,7 @@ git clone https://git.gitlab.arm.com/networking/ral.git Navigate to the source directory and create an empty build directory: -```bash +```bash { cwd="$HOME" } cd ral mkdir build cd build @@ -73,7 +73,7 @@ Use the features available on your hardware when you run `cmake` in the next sec If your platform supports only Neon (`asimd`), set up the build with: -```bash +```bash { cwd="$HOME/ral/build" } cmake -DBUILD_TESTING=On -DARMRAL_ARCH=NEON .. ``` @@ -101,7 +101,7 @@ cmake -DBUILD_TESTING=On -DARMRAL_ARCH=SVE -DCMAKE_INSTALL_PREFIX=/home/ubuntu/a Build the library using `make`: -```bash +```bash { cwd="$HOME/ral/build" } make ``` @@ -109,7 +109,7 @@ make Install the library: -```bash +```bash { cwd="$HOME/ral/build" } sudo make install ``` @@ -117,7 +117,7 @@ sudo make install Build and run the supplied benchmark example by running: -```bash { ret_code="0" } +```bash { ret_code="0" cwd="$HOME/ral/build" } make check ``` From 1a4737265e81aa72d127ffdecb721c93bd453038 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Wed, 16 Apr 2025 14:27:36 +0200 Subject: [PATCH 049/122] Update oci-cli.md --- content/install-guides/acfl.md | 18 +++++++++--------- content/install-guides/oci-cli.md | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 8ae6b64ec8..8f10fd154a 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -123,7 +123,7 @@ sudo ./arm-compiler-for-linux_24.10.1_Ubuntu-22.04.sh --accept #### Red Hat Linux: -```bash { target="fedora:latest" } +```console sudo yum -y install environment-modules python3 glibc-devel tar -xvf arm-compiler-for-linux_24.10.1_RHEL-9_aarch64.tar cd ./arm-compiler-for-linux_24.10.1_RHEL-9 @@ -226,19 +226,19 @@ Set up the environment, for example, in your `.bashrc` and add module files. The #### Ubuntu Linux: -```bash { target="ubuntu:latest" } -echo ". /usr/share/modules/init/bash" >> ~/.bashrc -echo "module use /opt/arm/modulefiles" >> ~/.bashrc -. ~/.bashrc +```bash { target="ubuntu:latest" pre_cmd=". /usr/share/modules/init/bash; module use /opt/arm/modulefiles" } +echo ". /usr/share/modules/init/bash" >> $HOME/.bashrc +echo "module use /opt/arm/modulefiles" >> $HOME/.bashrc +source $HOME/.bashrc module avail ``` #### Red Hat or Amazon Linux: -```bash { target="fedora:latest" } -echo ". /usr/share/Modules/init/bash" >> ~/.bashrc -echo "module use /opt/arm/modulefiles" >> ~/.bashrc -. ~/.bashrc +```bash { target="fedora:latest" env_source="/usr/share/modules/init/bash" pre_cmd="module use /opt/arm/modulefiles" } +echo ". /usr/share/Modules/init/bash" >> $HOME/.bashrc +echo "module use /opt/arm/modulefiles" >> $HOME/.bashrc +source $HOME/.bashrc module avail ``` diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index 6888f8c0c9..d870f8d585 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -53,7 +53,7 @@ To run an an automated install with default values run: ```bash { target="ubuntu:latest" } curl -o install.sh https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh -bash ./install.sh --accept-all-defaults --update-path-and-enable-tab-completion --rc-file-path="$HOME/.bashrc" +bash ./install.sh --accept-all-defaults --update-path-and-enable-tab-completion --rc-file-path $HOME/.bashrc ``` To run an interactive install that allows you to change default values run: From 9e4b4186a41d04f45409c35a59a4eff6fb0ae70b Mon Sep 17 00:00:00 2001 From: Barbara Corriero Date: Wed, 16 Apr 2025 11:23:57 -0700 Subject: [PATCH 050/122] Updated LP title according to Product Marketing request --- .../win_on_arm_build_onnxruntime/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md index 374b6efc68..ba0fe78f23 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md @@ -1,12 +1,12 @@ --- -title: Powering Phi-3 with ONNX Runtime & KleidiAI on Windows +title: Powering Phi-3 on Arm PC with ONNX Runtime on Windows minutes_to_complete: 60 who_is_this_for: A deep-dive for advanced developers looking to build ONNX Runtime on Windows ARM (WoA) and leverage the Generate() API to run Phi-3 inference with KleidiAI acceleration. learning_objectives: - - Build ONNX Runtime and ONNX Runtime generate() API for Windows on ARM. + - Build ONNX Runtime and ONNX Runtime Generate() API for Windows on ARM. - Run a Phi-3 model using ONNX Runtime on an Arm-based Windows laptop. prerequisites: From 835baa8acad7ebdccc37c078a149527117d1eb88 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 16 Apr 2025 21:10:21 +0000 Subject: [PATCH 051/122] GitHub Windows on Arm LP --- .../gh-arm-runners-win/_images/capacity.png | Bin 0 -> 13088 bytes .../gh-arm-runners-win/_images/cpuinfo.png | Bin 0 -> 18031 bytes .../gh-arm-runners-win/_images/new-runner.png | Bin 0 -> 42476 bytes .../gh-arm-runners-win/_images/platform.png | Bin 0 -> 33346 bytes .../gh-arm-runners-win/_images/size.png | Bin 0 -> 23304 bytes .../gh-arm-runners-win/_images/win_image.png | Bin 0 -> 27071 bytes .../gh-arm-runners-win/_index.md | 45 +++++++++++ .../gh-arm-runners-win/_next-steps.md | 8 ++ .../gh-arm-runners-win/automate-win-app.md | 58 ++++++++++++++ .../gh-arm-runners-win/introduction.md | 75 ++++++++++++++++++ 10 files changed, 186 insertions(+) create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/capacity.png create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/cpuinfo.png create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/new-runner.png create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/platform.png create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/size.png create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/win_image.png create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_next-steps.md create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/capacity.png b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..3570850cb908df0bbe12aece257ecda26bb37de4 GIT binary patch literal 13088 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 literal 0 HcmV?d00001 diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/cpuinfo.png b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/cpuinfo.png new file mode 100644 index 0000000000000000000000000000000000000000..b3c03b9f5317dcb4b63262375c31dea0c0e5426e GIT binary patch literal 18031 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

GhLa8*5 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%0Dg{Tv!}MtDSPYM=mGf@xOIRnI25!>n7fzGR`7D;i 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 Iq+=8GA0KL&Gynhq literal 0 HcmV?d00001 diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/platform.png b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/platform.png new file mode 100644 index 0000000000000000000000000000000000000000..54fa77e281cb5effed163900866e275f9a5eb99f GIT binary patch literal 33346 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 Ioaw9o0XQ16UjP6A literal 0 HcmV?d00001 diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/win_image.png b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/win_image.png new file mode 100644 index 0000000000000000000000000000000000000000..16c3d85212016265108efe503c44218405920735 GIT binary patch literal 27071 zcmeFZWmr`0_b4pgp@4LkAR*n|(%mIp(jWsv3xdE9(y4R}(m5ao-5nB2=TO5)pYgfQ zIlo8zzwd|l%X?kt{68?T*?aDFuXV4E9iye8gojOm{oug^JQZbmod*w4A3k`1^a2wd z@t*=;=|aRWBySxh*$3besvX1ys)LNW%!3EDAe?J!G{iNQhqAHvg9ne9fBzt<=rA2U zc;KX`A}^!&%Hm)ND~I^o($Vo9b~s&u{3jH`pJ2L58Ex?oVXUEa4870em{>qMv9@CraoIU5_7-?C(5QQ9IJMMSCbiC(F1Jp9rdxA;^P}5N&>nu!WoAyWGloc0fcFpS2S})de}3d3 zThd5!Ks(ws6XvfxIgwB=(UCFfLLdD5<15Kn3jW!8gpFvUdoM1kDE7Z^{_`vo@mN@7 zxSVB>@$65dG@dcTdr%0a>1w6B#|xGAu(7c{WdGjABWxuF5XM|5up-^9rO^}IA;nB<>W{a!r`6_Ww=1;fL?5F(T>AovHfe}72R zA%$axWhh7f1sC<G($bdNFUEYT>Qz0qRaV4OaQ`ruUevs%gSbHVAj^YH?c@tHvb7Cxj=ZQvypmqg(1>GbI9+5WiHKyx>}t9{qjUD>CjII4Ds zm7oFJTtC~_`whA@B6Qs8n#sXiy`!v_lz9Ngdxw-*A&r9maQ{QclSx)^@`o7zK~5C0 z*nK0Gl-~F>=0_qM%l!LR#?amSVlUZ72E!TF2?92tzw zXJGA%AXR(uz$f~w6MyXtjvP-Y@5p9z7_3#2hMQiFL;KFO?a67y@I>Q2PbLh~?FF1i z;jK4Gh688(lHfzpL=^3u`v+h;slvfhy`rlwOcw%HCj|GalGBUDSa0l_|fbv{%z3~ z$bh_SPPa1pR{!J#XIDxhx^Ik+O8;OeF6C)5on)9S?B|yOz+3`sa3>`Hzm$g1tvg3K;(_^#91Zv`!ccHUra>Sp*j)nKWwo zOuA;4r-|W0inQ<#sy%Z&qH+q;rSz(k8=WH*ugc6JItIczz7jQ{hqgvn`z~H)y$KWj z7n92*!OF4n$ba#FVo6p^VrZwEn9Y&pP4tD%?G$@?j*tb*wRQd)YGdY4IG6Tau>Q;T zOHsC~N%nxUDm||JyDT3s`^vnzXN}DZGS)jxt=Cue8}zBOvCJgAYdtC_WB6G#e`$Li zlD>{iOj3=cX!ln61z-`)sjz3(J2z^MQ}^~c{TdCM!<1fo+%%2Gn&%e>DMkAmeCs1C z>i!2YbQ20aw*i?$>^qG4mAxvvPgei^wu_*x( zuW2ENK}u$Vf%DcIzJ^3%oO>G!+y7@@P9~kl>Cqc_zK3W)KyfBY>0g>tnjvgIC+2A1 ze4^%qfg&Ca!xoh#qYG_yq5^l>5}B9pme89gtBU zo>X<0bKNKW4mP2@vSGsMeFNObhp=4%Gpl-}_fQG*LBnJydfFRsf4A$I4`d?6T|6q@ z-bb(v0em8LP5l0DPt*`S{CPw53b6Exj2&d|vGPu39$yUvt>FrlxZAvxZG+kH7Eob_3Y3X3hB4YWu z)9zq|S5xidyrQAu7}!Cz*pW$0OuQbg7izNmYA8J#)OU-L->6((lY3U!xqrj97IA~)rFd+@{k8sIfbhQ}v_6=!n1J*hPo6UD_^-Jd*RE+juGjYYiz77) zx+5@IoJ>Wo23%~1$Y6YBDf`!^uhqzSWgbHeEsuulwxehJ6;^5zb78}=rw{LuNt*7x zG$Y!>Hs$`Qc3XqY%Zmfv`Ln%l1|^HcyUVprfZ(V_K=4LoU1?L^yoczYEQmmeiNvvp zF1(%U002*7fKBAV&ndd*PGw)K-JUBX*%3%Yt0kWp5*V>gD_s9t1U+mAD~Zo9(n zlqxbYTuRDkdsK@x^aV^WcbJJTePc>2kCze7#=-#ndR*r`JhwCx_MUnde(nq7eM@W5 zH|Qt*xvwkTOmfeft^OpVgQc7m>n}B69L9ph(06MxZUaj@SfKdm&QYD1MI$tLjRx;W z>1t6FE$;@-``bU?DAj=5@%n4mieC@d&)>?I`*3!u&=_g4lWsGaM-Ap)e&4w%)?I%` zYIh0Wd>d4$cF1wSc;GCgdV!Z9zEY7m&Tdq@J^666wb`7$4$u^|rwE^TvwMPJI~qA% z2LaR%(Pq@&gvQ?EI=V!?g7FB<=<`lGZ>ScE_s$a=@V*VP$EV!A_1Zcea^DRr@=Hkj zdRVP_+y*{(*AI>m($%%RI= ztJdJnFT7py#c$UGl-WZhOqYdkMFW1t+`Ze&on1Qn$g!~Aa+tWo8~BENfAudm$Va1K zd-KtF06kOFar*+Mb1__!=G8rf-FhmUa;r5g%Eq0|GHWhi#MO2r-ntt`0oz5IhnfHp z&d``xYKd1V_wM{)($-sNZZz2FLiC0xtnKCu^R!K%&fD@#Q~@~Rjmo0aD(Q5e|N1z? z6tNlRG#xSu((uKfXx`o^1XMMQz5D(?og^;3&S5$(vUbvL2AbAyLsYdQPypLf(%Y0^ z?XwB7WaSPBK5VN(;#QmaOX$jfGxdHcpPGRx#^vBVb>UllKd>3Qdl}MAS^h{;EpvpO zG?Yu{YPku0bDufg;saOhhVjK!Si(ek;Z>=5!z(;9{JML}PMR(d6+t6H+9JDlqBu)~ zw9&MIQiFcEW>6aWDB4m)L7~rdHiMpiUtGXoQP6VX?hdX@LV^zOIfWBzw%y^IAH1zW znN?%6c|s~+ohv?%`zkV2x=K0);M|TE?*9EN!9p@z2OUc64(cgbM64?Fg;zyoqvS|n)kpt@ z#wgIB?yrD2?cwh%6`I!BUP~rC2=>AI^T9APLdTO#%+^q4>c2z`17Ykg#s;Py-q+rQ z2s0lUs{Hu=d8|hulwgpf-R@tm1SZ1L%1Sfg{0STh^+X0?X(bzYedX_4T2usZdb(t^ z`?~0Y4q<6&O)^FATUx?!1aLyae&qYR4I+HIQLI8krIKKS0Q!zUmL~ak|uoL9n<>PVy*ND41^Yv zOzy8Wwu)Hmzk%H2s{c>qLi63lY-i7>zLb^x^(&O>`po;{=inD>2hHGnt7Fm;DiwUI z9WGYiTU#$8T6|ua#wqKIE%D~#R>Vyme9dIXLzYscbK28OElgP^?2;D$J_{2%Y9UEz zu9ju7AN4!r4|d*Q{4XCY(r;KR0#h_p>#E1zUP&@Rq$Kl_K9tbJCF!qRjS6Z) z#gos7+)`XB67+D9`yj!c#DtE%fiB$Q^O3>u)yedvXrCviX~iniBIj8BeHH7-9#*IX z8rX`)?_X?(_mi7z;M+OLIWhtv7>EU=AG|Mb!{KiuPKqp95E2_#c;ltG2W zyl5A!&I}_|tW5kX8luPCmJa{dvoo^Lh)?$&w+G0Xj0L^B))FE-h`nz*%~Tn0KGFV5 zK@np5R?#>UhBe9D6a5Ea-+xC7?QW{W_eB&_AE7SFa%`jS36=CAHt|$BW8ALWLYo`_ zzWrFy;ADfaXBMU5jqo6#sV;Z=NkY zoOeH}H3z_ERaCxzrUg%o=xo9<^_^$B*Yj^>&qBHcyvoF{S5ED2YhI@3Yi2uM8+MgM zVEK=lYt?KnH2%_PJ(QZFn}x|06j+qy#QlkBA}KO?eU8ki^cWHYF`s%+xbA$#nE*Eik9ygPB-Ox?#J8p zm)YY(^>P-8z{j8CBm9X*$ls1nv%Ru+M)jA#jI|(>^AO2EeG5=f0Q0iF_?9Y93L@|T zGqNAZabkFt#S!;nJV4^JaDz;~OIvSE)IgTchZq}eul2!I+`dD~V^q+I~R{CKli~)VhlK~m2T~CFs7G?+SC~1?G&fjcb*MPP8SpCeI2{b$CvKIK{K97WG zIOs2L&>HQ;ZYbSJSa3Dmk}$<3f_kNO&22S8YCvv2t5egeE@3ymMTY;BF_7Db#(qIzVYF`+7@xsQ>Fd?7*E-u?O$op0 zoBo6S!q6Y~m1kmRH+MMEG2_sYIce}l;O^yxl*?GH*#pL3HEMo=d6f%1mZPt#C3Ge%peAc$O%kQQe3mW<- zE`brsS0*YG^2i*15{G0AVvjY_R$9vRJGsvVYKUxA-uSvXKsIs3RPqcjEa0@7&(p;? zh}If0>>|t!Na$YGo>m(G8=Pt{Az!Aj>3np%G3!T8Hx|t8kC*}S>G^X`!9KgsGwt0k z%TA4cbz=XT3&=PayeGY@+=&k3UC=5Yc*Ex1QoZz57HIvOLh0s6h43eE$kT_dLS?%an;+6kl$HL4W_PwDC z@1e51l(4ak?9DTxPYJoiS#!b@EwjRSeCBfQ6%C;cnv%aYFM4a@-BsAb1B0kqwyGV3 z9QC>9``{S}ad@~sG6GHbb#5b*6dAFBlmvzJve5u}kF-Ji)TI`5qa1djX(32F z&7Qub-Q^d0hO~MM`=g)V@>p57OYO)sUEdu3KwKWilPJs&Ya5IN#j(l?3r6|mNx7|| zb%Ls@4E2S{|8R%<=zsgTk=pqm^F+!AKSW?&)`G@ad4ibhFv4$_+O{nDk#PW)kyypE zueF0t`mn;_cKy}xK5wAA`Y*EKoHF**KO;&VLQI#TJRZJ>Ili;?_&%Ylri?ZG!8zGa zD7xD9?!asgOhsclOm5;~S@9>|^HSF9-X@E~-XA~zn);`1&3V+f`?LbFwY!aMb zi0mwd0#6~HvcR}Jj^DDFMjhn#n#yqgGs>n3i!&RMeoBvswKr8VOCr!mBDXBK)sKY- zitLz?{s``IBG>w8Iin&y37L z^IwEzaTs!ZUYO2b9U}=){_r5%U?8{;ny{=il%{a6wvDDf+B5R8yyJA;Yaj92+>X;j zNn|YR2Hb%AQG^x&61DW0jgN%Etj+negt5F4D^mMWj4S6WoYa$&oXY8Mz45H}K%1XG9huNwm7ykz{{ASos zY=}DN?ft^Yf7dw?6#2iXL>p-fv{HI)W-+D8QlpZVxUy9gI(}PN9Hg{1`|@|*6u8^g z)8(OPS*tjpbe=po=<@R&?>v;R&8r)`**?pMJB!67qIl$uo}#5ee&LdmLEp&byD}9? zP<^wez@~$;jYXxT`5W(t(sTl0rO*&vla(&+#uVk;&QCr5MKFTq{%i9chwe;F{wV1; zGLgQ##&=2E7uH8hWW#*|zvXy{c+ObT4fz_X4QsS%8O5%RrVJB2haIOxA=dH zf7m5fB$@m(9A4-5mvY*7wPhiX@zV!9VUV}(=&V^BWaHI(dD_02QXhlIa+WfX=L5Gs z@}#yrMvkY~g4oq7gFb)lM$spXtS^#-o(iQefYmSGC;}^ro)z7tM14Rsofq%xwW%>j8iR=&N$Q(p_>qKx`=wBYmC5 z5+strkGMxk!+;vjIa&3asO?8wu14H0M+SK81;13rOxAL!$Bo zPLmpY4MZLduX^)kHqX|#d}MK4+NEUMqBKJMkqP#^ckk> z(J2pp?32FCpA*yNar?;m>55&e3(Ny;KtG_BIhR##=8n={jtt1Kf9WzE5_GuEe4v`Q zXJ&D05Q@%|(sG(`Zh2RJ7z1@&+!EfiJ%8%I0){$ht@CMo&nLZw)<*rhKJ7G2S%ScV z!6K8{T~EzFWTeG`0=Pw`D7QuyI0{RcjCTny@N2kyi%!VDPfk^e=djpXrG3jBt+N^%+AoG@|`224>PfTxNSL_X4ebu2dC;R#lHnCs6v#k z&$~Wer3X-JW&`%J8)}&E^cWQ$V@GQ#ec+knx?Nyu1uE`QM^+k1F#qEK((7AXa2V<}$JRu3# zu@@j6=OS_<0AAy04xsLuMd$oZ-oKywTGt%rm0k=!%zbUxzNhQ3wb#TW2 z_CrSe_<%V;7A?wO9hTuRm#x4r{4r^xSMKdnuLkxYep$fU+YR+|i>9hvVDFYhb_4gt z8D}A$xt|4byQT=R`eZJjG%EYFzVGmEc{I;)`P`QgO0c+mkxBa5A#onVm%7j{W+y$# zMQqo~IN<8t=?(4r>F5>Baq!F51{<(bSLyc^6dtJ9O7OlD=+%m}})obBWEJMa9l>mv5bxKY>t|%b=6?s8rCJ#35&U zfLlH^s8^(4%KPRP{H*4{?G)1~4tRF8EY1pnN}gVQp42>mzxW`^d?oqz)SPlsA$ex- zC98YL`33%Y02FY?QhUeZF(b0+JvO-#e#7|?DD~}HV)NPp1ShwG+@2@zwiz@ZR0J+^ zn-7EAB!vuThbwDCj7^20P1U~D!R@mEYxkx2q`Mjm-U;{WcFzcy%ihZJwc)kF#`_7F zv5pJ#+2)>Z2A)`)x7qB>-FX0Kmb-awKsB}RQwC#`S+A*iF3qCviYEF(4(3x&7294J z?84o-=YM>RRd=^!HNgNTocL3i+K-4%8T*3XY2JyIP%WQi!F)E>W_y|{C!dty?ljgL zzbu8YhXN&(F4R$!+H7yQG`eET^R_N|4yel83r8txM6QC8+e~l1?zqnM&Kp*Dt_Ib# zfY&Ddy5djs)J}poXXn}IevpWr4=!J$(BIN-c7~LF3yS$lz|eAc%SX76fse>@U$Ir~@S5G!~jEFM}$v9ZMd0iHFN*2gfyo$e=yVg<6SQckasPuk_+d>hl@` zU!WHnAc@yprS|sqifm7o_6%|_J0!2r3ECe)1^EVkLcjwnFaW~3ZWYW|seNA6G5_e7 zKr4S_F)Hy!V*&c}tTMy?=+uAy60If~&GLH}Xlvb>yRakADt7YObTG-PSR&IrGvTEd zdf%%S{e8R|JuSa!g+wW8 zmqqdm5>i#|WF|0MNY9k=-dwHva^er2WRKGxit=TO+z+(nDUA6@M5U@p+FQ1|i3d1h zrTtl|557nD&OO<(Z4>Q z!+%Odi}GuY1Mt$9urSmeo6k73(h>w)wnL&N+9;P=6NdDA;((y`#}x!$ z*_TFP4F^7wcT+4Ox>2Tsp~>o;2`iE)QFtIOYn%muK`Uc_H6Aex9nxbTu|oyM!!FDw z1?Rs{n=AHYI@Ha>#n3QtIT{|#p30vi9DeE~h8|FZHaErl18&vf;zP+{GbM>qbH#aq z?WOT)^gCEiu?*T+oTQX>Xx6G%p{mFiAp1B9r$dLx^@*a4)x|3izSq%Z=cIyM=#0Lk~O#XO!hRr9Slt7p6U)pkYbu#`-g zmNV-dt|XE3DA>L9o%=PQ_?`NjnC#Dd%E!ImeM%O7EMIxInZ4H##2BKDk)Oz6=-MY^ zn%!6So*^myH6^k9@@`YQ{#UAjj$Po=J6De^YJBzak(f(!n~K*4@PuBatJiBcX9HzT zioW0$}37~XLMZa-S!=OJ9yNU>0n+VAJ<)* zVVy>iB8q3sL5giRO38&EdzQSfo(XATpB3TQ&=o`=ANv&wLOXV)G;{$zcFnRPrc1*@ zcNsW-Ma%g8yP5X$FzI6F@+xIvEtMJcT3`H1%ieSSQ+$ItC9(~?chMl&h+Beb8q$@H zYeuF?Mg3|oaRamzzX7{?L-Hv$Fg@U6I$njGQ`9qP>Tu%xK1QZ$S#rJy$<)h|8`+&* zLqg%aJ3v}yw_fRdmwxu-b@(yZdV0lQ(Q4H@JZLkE&o*upnQ=AUHcj{QZqda}lAv`R zw7!84Kj=k=q-}75n7tOu)zCTQg-sxOc~d}i(0CNk^AT<0fZ>$6k-!*uo06%EMcLcg zTf_zGpwl|&ePE8UP7p}6(tjARz;jY#h>zNJ+I6f8%X4M}-TiDJb}-sc>AIfG0}`%W z@8CI?gnK`Za7hcTSOIgd)6zepoV_w}A>nY|_tThx%2wG=v1xDT{2&Hr}nzp>GNwaq8=?bk~^9<}B{P z-#veCk6Hwg^E-0sEsATMEE)J=Hez-CusUD*M~>{iN+GX630CC>HaIswb*Vtv{QjzfM%*%43Mc5 zY?kP)b1_6MEDHk_>sUZTEfb+2;p&vlxJ=uXMVTwvww<7^i0aX`J|b;op#4>90ZpC? zyDdk$>c#1&p{Wennv%12zY)XszH|c`tu9}UFG7>lybEQOz_NMt<3qx|un$3Ocd@7(dL~^PxC4`|S9g=} z5L;B*P8+lrV6uRYoDCJmT{aZMyHm1@4P}>1xs@4SMRm}fagnP*u9~`kx4XkI zqo?Cd5EkI25gYP7YC@xegM{yEZTcr2HsdmMhliCvB`qaRl-j3hig}sGyrSm3D(oms6@T5)J56b>#MGl>`YnQV zh17`DsWo**>Fhv<1lBbwGm~$p_i?q6@Y<<%yX+)bm1btpol)GQmD?@Fcu#gJi@hu_ zUAl>U7yjWxS{GPwjq<{l1hBu9mV^AU>%97S9L7dYKmvq&%Ih_3Ia9jllWo&G*c(2= z3TSqhj8w$bPzjsX{haCOIY3oF zZ6npJ*Ir!PrNq!U=aR9)9g|jzCc^s50gNxomBQONIx*A-H+Md8EyX{%`H}6-^z3Kw zy61!r)6KF*mYf99uOJKp6LuQ|v6aPc$@&}YlLw++=5dR!V2?@N)0JeJ#Ev-`ccyMPYX5MfJB-4+qQ6(rJPZ0Qc( z(^oL@=cf;2Hb;X+Mrry2m-H{cImbuWf|r!9%7c2{MZU}~408J`opxO2JD-1cAKswv z`Qg8`^7i?fnc}z%yWaBo8l~x-2dGbY`f(obJQ5b%=IM=S9Q&6yU&bX1!f62_VG>`~ zW3wk+9kxvFUV+-ro@yiqlPabYWGPLwT{X-)?i5|-oMHo_!b~dY4&n2o8ZNX>@DSmv z%<<&}$%hReiv~@?kRb%NGij2uIN;elO3QQgT96!gO{YtdbZit=2t9KIZ-fFuoPe_n zd7g~>F1zlP8D&@y@tadfDO8OzkX>LRxi6>)RPNmaw4Y?hG(vWsk#Rw#kwCgT7<}U$- zLc8w{O|o;aacdFp%|-R@ZUH`dY4?os^yhOjk(JZ9*a@r1QTfpKLi7AWRMYh$XKIPh z65^$#%wtm|RT$!)CMnququ?>*x7{rE2zd&lV0IUbG z0T@KrK9P|P>8NJ#+8e}k+5BXdYT!jQDpg63K_!rP@fPtJ=8rO$#?aUE^^u3<%PhTj zByjQ^>1*WEddqIuUK)bjiDCRv?}X;7EVYHj0ujyAw#ti{5ghP@Rj|MaxsC<`8X?je z{>&qX=QoaZLq=&t0X3=51VoihlhMPX9w}c!95~#xvOiFOB8K1DY2g)*H(c1CIQUHt zc);R_>E39K+VeP2b9iljpxsh_@IDWG;VUW=dF|}({8uCP0&+auC#vCXU(Eed0$m~& zBIC{bp5zJ29|avmUeWk>Cm0Sz48#3Su>bzbe^c!LPlowDFcQVSHRC3>9?!X0WDmcp z>pCy+%p_W9OahnYa(t7(DOR#>j%wq-xX-$w(tXHykK|t=8W8GEYC_T(l@gvqf6|;x zzsxPZ!8(Zh5Yo_Pu)JKWDyZ+4YCoKiM89&~mILtWO{`S#`%88Rn||lyC9YPJ22y!_ zR_!L>9X}&CmVTL}RvDeYae|3lnkAGFmnEGf zL5c#CE;HM}Y&bvL&KJrn#2KI3IsjE0?6WEs`Hf9if`l#O1p5mlWmEoL(v)7qLX|4LGuz58)*q~&$gAdl#2 zp^r*VB^eSK2`U*P;S%w<=v7-)^D@z9piSz~cTq_VI#$P$@?ySla_b=)m4mt>#eX%+ zg((tZa=!VfE77AZ^_qT3F29l%E%R{Pcb{0mXo+Q7U6_)7sc!2$;HJ1pzR0UJFy`w> zs%mjtOFD5suW?-u+eeMGG8tHzHT_6bpY&?(MoN*|S*Ru62@p=JU+>n#NyMnt&&4ms%Vv!R-_9tVX^1r!1I=UCidX`E|7 z&o#(}(&&UsLNxvG?c>?j6IM!F(7#(OuXaQQ0DG5F*hAbilT(eq*1wZW9du`V-To3Q z4L}CdQzf6O3f(o1zyp;<_|Ly#rCl?FEl?~Dgs>=Uk6Q5X;h(uie0Zp98%H7CRXfa+ z8ZxX?{_ckk@0hEa|B{-R+h#@nsJXlU&DxMwE%a>hV1KCBfdH+`?lHI+bNTwwSti(q zlr__O+;X!r6ZMocx?zQMCI*E?BmF}~aoy*Kwx{O;hIjBY-8*JB`%OQ|^mKRrh0+xc zfs`WWrn5;fPe}s&UGoyd=3#KUN4XNvTA23V(xCGQaWsh&`f{38=;=fbZ--hmd;IG8 zELpvq)nYjO=Q;sxZzz}+QJ5u;*p_xPE)O~uD=~T_(grf7&%Ll*p1+C>LpA2LNW-l^ z(YnZvIO{&SB;ImKp$ZgX;;JW?CgXM=G9oeW8TZb6Rr+Nw%&pw$igKIn00(DU2P7O8 z`Lg&V&R3|TZ(x?Jg#rl~-Z&JLBZKZ%edsYtRQ{}FWRxe3?iJ{Feu^Ap#FzolB*RV4 zSJZ_|J+4}j4vuxrY4OfBfoqvnP4r4N*nDlglp!xhuPRQEK#RK+TlDj-eOKOoIr4?< zSHdC-AER5<;S78X#q#N@6~`Bkk>s-9X}HxnSeA&(&;8F_CFxJ4^uc3S0TR6X22UKs z(o(lC;ijStZD%qNsbw-!H`7@f@*c1EvUhJQ!$P`@UkFR`HP!I!S>NE2@Ykj58zIt< z03|O=mSP3f;;$CT))03rrm4aM5t9Esj<+vOIaR9jn9T^& znEyod;83meGg6VMT(d3ouqE{NLjdL8VIZ;M>;gd!QR`m2UziZx`;tgaZQTLj43w3_ zy+2}HtRICndveCcVv*ym!Gix!qVxcV3-NNjh?0+%DXS*eevfjGnWdJd zQ_=p^Mwv$TJw6RxyP}#6^Q>pi&yyu^a07c`@$=}fb&8NB6+`L0qQ6s>k;aqdydpp? zeSel-?mR|;B~J!}GV_rlT31k*!OTePMumrIAd{B4hdvWm+}mMY)WXlwt>x`i$O46N z)3w2~XZxfsGhdt78{NZQi_@718+(iCPUHN`J67Vgc7a6K*j z2-pm*UH^PQW|~B|d~RHEU?3K*U2(vkbFld7xh?45tr;=D3F#wpU>gwF3vPhGTFvzl zlpKjCY2Ire2ABU5W!-9U^d9I0l1;WxtA))uEo?p*$XlQ_K3r{0$$c)8>y+YdLDn_2 z`6!eL#nt9nC3I6X-MM8iuVcR?Y^4(wC92$NV8$&$iR(Ps`{(zf1dGzQEiZ|1^j|3{ew&t*k7JkFc%7lgqtI9Nu=<;z7 zNl+$pwPJ3&HAV0d+H_7dYWaR-U#b!LcN&Hw}aPBI9PJc?Cl<+{E>AU9CZ% z1uE$rE$EBijtC#2KWS4=0+dDbw9{|C3|1!9xb-?~j+*o<0f*J})w=DF4*>)dvu&IUhr67{C|w|{I4 zl18*E(Y#(NGOo9=U2{oTyg{NqL)hY6CE;iRPK)YeO+fk-8OP3p2#KX-o-(}BXGq>y z2KHOGNcKIO1>#=B6*s8jl-p<=p^U~sC^nD7yvdcvh7JTSo(|g~o%h6C`q=nIn6UHC zi^$PCb_VYR?Yi$bTG1*RcWAyI>+;$6EVv`jWPWeiJXq!MbX&<_SM2Jxp8PE84Zt#V zuBk$^c-I>Yap<%49*u2*fgn+}NJY#3pZBJ1V#SGky21;VJu9}eT6JjANB-Ti9tObq z?F=xJpA6Rw#tMw~HywzfkgAnVZ*6<;`I5Q(IAHs=N!J1xG)>}&SK1Z%N$FDBlUY(d z@(iPKjarjUi@V~QRZI|CX=n`8)UdGkjt0V6!j}Q_D3;K*>qpbGHNxj!1hY#%jp&_v zP@!i$xxL&bs%UAuN@4&(jRrxUs<;MA37_g4S~HlTn_)#h8KKA!keI!<)giwsS9E#K z`-Ss*4CW?p+<@n5?GYjt-x%rm-ms%jv$bgEQvv``38_ufUmkHio7=CG* z^@10AGSTHoG#KR$r@v4iy-24;h0Tx{s>+IM`iu>7c-~-=$nvA_pRlC^MTI+R> zL&|4HkK>IurF4sG3aB4$2>@Ljr*KU@;`tgOGwP<0J*fEi^QP+YOjMTGR;5}v!rKok!2wRwsM zg2an%vD%=rCPq1byw9TipOJv+l;q-&|0i#H@=TZPj3pL8fclTis>~XiScQ9WcGbs7 zErIeah_?VNLT4p@AcIjs;Rj8kO8kKRFAtGFQ#u0m;r@c_Qj=UoQ(5k{e;4_x;zrtDi5O@Uaew9i*|?kdgn3H+879jTOj5mQ)#OSeVwhTWUQS zmA9uVYusa;UZb=FOseIMs@Sqm+aluNthhX!f4xS5P>*g(;$gVOJ0B`mbRira3^>8oBrTE|ujFc@+xBock|Jk(NZv<@WU&l#cw1 z-2cP`MZZU<@c1?EkBrdKBGMP@HkG;#_xmR&2ob>1DN6qxQv9}di68_yPim*j`Tngo ze?R4f`FoTk{uDX)WW7Yw_V_8r`s-%N^jumR!I7Pb&deal$J98(yvGU&(N?PXC?KEQiQsPW3XD-b-?%Xq33-uhw{t_7Dz!`{@!dJd~KU~ zw*M;X`WK~8zte%n$ofxo_Qk^m_{MVAjYZQwQ1uYvT#_^YKCs~QUvHF2%#$ud#GWWW zR_td{@#*FiX2K#JO8u=BWdUw|#t2lTbr5~3zu-#2972{Sz}6qv<0_L5rr}Z@bw-CF zKR_o^v&*V<^otarlm8hcOB-NCiu&twTOW-`lX+X48egn&os0t&dO){#mjYYwN5{7W zueA~HSP4{tm+w*B>cBd~^=Cfxu)7-x+9Uk;*(p;xDBqm+`;shB+6gr|Lz2^m6UXDi zWr*!b)}^_}fga3)#+Y7gFFk&AzI)jlfG!J0C*+UCkK4Z70eJm~gZOh0#uXJ3*WHfs znOn&}F?K?Col>E?$nF$*P3bP=ChIo$bZl!^Je+#ZwvNV(R6V5;OXGK|U~%~!?Opc( z@ym)vT?|!Y5QbeK%zj?7Iiz4{4PWOce)wl>DpYohNmgE}M{sht4Dkmq;8IH%xl-i( ztL}gU{^wS(UUYItRyK_U3}r+-48W4(8j<#BssiVOOTpa#8P}5jg8AD{YFc~H0Ghhj z2N!!e8*g*{6~l!+(OdJ1{*#SzCd5dznr+wq=*>o2<~>s%5z9(XzUNu&-4^Iaop=$^W~tqke4?#u=X0ulVzm*^dpew` z>TT72UGLZ4VSJ^~N3K*q37djV!#4lZL5^uj*FYybtfXXC-2O4}q*o9yxqp4+@mxSW zaDbScWD8s@LPt)&)#7z#Dz-lt_$e#v7nN9KTP)# z8n@WxzZ}nBqF-5z>l`HwtLonL-P#e`S(^^E9A+Wg;(S{l?$9UVOt_!tS>Qlb=QGPq4gVhdBPwygeD0s=m8FMXvUVP+2beaWr zX-#8zwa78f8-B|x)o;Fz2)x@52}`H2ATiVCdzsA3f=6eh&?N`=kT>lP(Kh5DPmOCy zYwnF0m>`bpnI@2WwlRXM_vja^t17UOj*n%gKbg5AAAg4dg=JSdj&+p;dgJ)cl2hAr z=F5pM`P5cImw$Q04OC#$0;{C9QLSfV$3m2dr+Ih6Jq}30LX~yW?dt-(^AbD}Rmz|uKrW2l0lTzv)D3eFyVe-7R zILm0(a5notZ?&Kc3L6W@{6Wg8oN!~eiyH%RDZlOGGUYY1Pw?f!I2zO>cu5sVu9E*Og&BcOO>O^{N?N^o+^Go^7i`Gg)GimVPh^(oM_*H)Q$dNvf>)8a z)WSwR#J^i>5AXgVu=P3Rx;R^sN=kgM{>zSXCbCkS0~YYJAhP8^wVbczLtE=t2| zj~Vb83n2$Gg@QgXjQU&~0tpX8nTSUa%^oW90tbn&J47)MYLQBEqNQ}uo3l08C4~Qp zF`W!NMkNUzsg0fsdZ>KlqzY_(z-Qr?Hp+M9B$V2jeONhIH00ntk^wK&&b8B_D34|b z2|R-~+r{b&oi*o?-q9NPhQ?s3=~8loyJzfA+RiW={_^jhTy1O)^9Ugk&vP8 zSDKuuJV~P%dyp4fL`M=^`%?OW%kL28Qmy)~*^xe})fOtnv7)C+-PwAk-Fn)2>pcI( z?ZsDPsq0EL$eour`PZfNL6TI;Vgtecp}?uG!Tdytq^Bk|H=~vtiMg}8OEc~S13lAH zsWp4vdXf&`uXHa?+b_M(k#-7iZ=jU>d`Ma}ARIlvWWJMtJBd1g;ZW_OHx(g)40D$Uk>(?INv;!c0u8kX3K$LGs{QSf zc`YV%y9B+;7l%(lH$0sfS2z!;MWDlm`H4g|!HRxMo~5{Yft26twD5j`%osu6>(p*V zTWUH^cR5dJM{`+(&b8}t{i@$hpQoX^gWpMoj4QtE*v_7q(2W%`ywg&w4ly4g{~zt0 zXH*kgyT@q)B25$#YeWzc0i_xc2t@*dh9XV62!tjgAkwRXN|P!G1nEdqAe7LKsI<_O z-a!%qNdO7WQ19d%Ju1BK=ezD&_gmIv*3QhcXJ?-MfByTom$^Jv;>Vl6AGvU8F{G;G z9U)7!;PEqgT_^F*fyJ7O{fohDhy+}icSS4x%FE|BANyck5lOS_cu$vKIYux=Q=Nsao>e%UQp|KuBiUgjTvDnd?53NPr2j2iNtZn6 zSAL<)vBUrVCfRgbrrMw8Zh^J-{5rCZPf#dsZ8(S>70=J4y{}jnF=*?F)0DU zQ%GU$9;JjqKecBbNif9u9x-KNmMBU@#-`@Vu<*^iaR2yJ@L?9XN07y8;{2Lr;A)9= zVQ_c4f{QbKP+;nmhD}`#(MscmT*m71rAYsUDp%pcPVzT7GU{@^d8Ki+MfGy+chkhl zmw$DU>~*ZP^(9L5n`A`fK#_i9My+-73c`dgg^rp>Z$|nIF;)8d*U>Idas#$h4C_86 zUwgZ_!ucTglP34W>bT)bJeQme?kaEkPD9JjvlSs{79TiYd770iS|m3?O= zTL|l(y`AXo;?eU`9fsvW3ysw5rEZ9YgIt)wM?TQZLyX_`cdqRz4@N+`d9j zxLnG2tW2XtR4V9vqbaK=L_oUIqVL0_c0dyyFOM|C191{4;s@Uv zF4Zc?xsz)}ZDZtRUKzB1nm*kZHmaIEbgRCf8rf{JL$v^%nn#&Nvmb)Dbg~?=OkKCQ zn0UVfqgH4oQ%-(X<7e(fiXnz`)^BhPdK0AcYDy}~T$0^9t7j}d^2c>Pv(~y=q;4sP zOp3ZCc+TbmxpH`zyl$;yeh+D1-1K5Q19n;NELL=(qJ3l7D&xA}45C6A!MvCQ?FI87 z^7};y&UwuW5=#?45@KHq3on@8k1@`cqm9>hidJ&Up9o~qFg|(c<)s4!gnUB#uhq)z zbrgjw-}AJ{R)=dR@Atv4KH9edU|Qk27}&M4Burx+2aa@Vu)&$ z8yl%PtH)m+mC^Z2#H{wAEK;CjI%Q}8%J;@?{e=~6gdlg2iEQmSN{oP-n!or0Y&k(} z_^5s$aOi0FY%j^56A;SpPAU78%Nii2lU}ono7KB6o;uF0mcO9A;pl5Cl=Id`dO!@^ zhtE3SSN3q~URl!UJH63CsRYqVHcKUCOEdrb@qs-}XC4#{85H*nM1F8UPx!QrP-z#( zxUZ>62wJ|sgEUrQE1_+xVVs#W(_d=?oJN;RZ!Z&>E}!ns9p~~Db5Hj$>R*-M|}33L_aahh=HgiepD>G)%YN zP2sZbdoBjEiV`m(neQ|mGu(#N#=foy+d*}P1;kXVo2{_)%-gS_mNdzu8Ty&YWEN!A zl%C<%`gOxP!Yk34wj*CEyv)F5OKyh78S!B66>VLb9i{)2524`o>v{~fz`Qa)7MO-a zdD&-fW!h1uNDIt#J`r*iav`UJ^*UbhKKHC#yQCVMQ{`*q_y9Muslh^H0@MC>*IY#R z{-}^7eT1EC8E1`wjKr9Er*Sa=o~!j39m=!&a1|-%W7hx`N`jd(mSNsM>Y0)>%sf3e z&|O=VdD+0M2n8Yw1@|0*sP|7xcFd-Ayp17cbWLX&Vo6YE-Xii^NAR@BjA z$A@$VJykkWSNlrkPM!~T2_Ibxzrm5=9^P(C9=oyCWBvRgLjB7)@ea$&>OuafiEjQt zPeNF)=m8sf$zkEav+}Zzd8_7Kb5pFO8AIN5$;ATA_iuQ~d;_kZANg5p`=1Y%``XsP zx~eNc1DOl*9VMf`8qluK0FImxA4RCCu7sTXR`h8sYb?=IXcQ zCDOaGJGe@x)|uird>np$kUrPCTf#bm)6$$K7i@gL=eCVK)NI!Xmj}`fq#6{tP-|9UsZL$*EQTbFZbkHOp&|${JykP z8*m~g`@*IEEd33~WO#v@eu(`O#Uzqv_T!;joxa4(as$z}(s${t($ZAr1cxLCcSjC* zM>Ko?_WnNmv4A-}!n;Q*uMz|aL zBWcCHrr872*>Tq6qQm3$FuJ8ap9zJ;s!!fwxmvyw6UD01j;8z2M~5@{`gkp2t_v6K&MK6}b% zOlOoqSM6!7netz2$)z9pJ}F@q`w{V@$b}I7gDbg>J6mgNr0}iC*7^jt1i3X+ksWB^ z{TaKaKsPGLjZn{}4|Adg3#zjiFYFP~Z^JvVRxe}>zrKlxS-pFu_f{txnlOh#x2UWi zd0_a0>F|kd#q~E?EGnn?*Wv_+iSe=wy&2~_R}sRm33+?|CjkIqL&s{byIcZ7t{m1^ zPd02QSmB_TrF}g{9sE`hKn!9k{Zl0@=wzd1=gydfG-oi4!g$oAWIFN@e$7yR02*MghwSZg6}9vus0h9JV|+)w(1RUusR1GhOZP1yxJ&7*UOVE ztH{v{Xck;#kz? zhc|9z8jRFjFJkEP^xvA1M{vku<$Gi~k~FpZmHMImp-Nzx_!5KIGV4gw15n79Nd zrB0ziT?=iX71>lPlEX1o)yx2XT{QBmoYt{3O4ipCrvS(VJ6Xc1_Eu8G<)&&rp>WP=N^~^eEf|`HrFfC zzZC-TSwJVXNs4~$6QQ`k6EK)Q-2%suczgFGD@2R5z7uQZfwtj}TYmQ2&*RPRKuhE?+FbeF`Ykme1=9udaPK}&26$X&#=WRk_JD8F0O8tSlXs5` zW}G4_d$FZ$?+z6m;BI4Qe|C5GaZEMPlJ^32&mUF*%937xAcQoEFPMK`{trnfz?%z@ zrx#@X*LSPw?3!s4r;W<{_i3o_2`_uIC57UEao)Tj4rFLAX*qM>knJ>>XJKwj^(MSK38H8&!LiG(h6#kgy?AE$n7w+K zgN<21GxYuM51K$t^i6DjX&gU#f^>~C1Cm2BH0bwmJTXoQm>{yFB2Y!Bj^DD6e+Crh zIVH6ZLU>yf1ENGiit>xZeo5Ou3K(P_%KRAp0UCDW@DjGmt=H{0ne?C01ps~&Pp;T( z=p5CyUG{!se?d6&_gXS=UCkyGpIa4Lc@Jm|;t!(n*&QcpIvRk8%=l56XLoM^MJCAU zUo!2_(G8)D`pbK4a@2I9XG8B}{A*V0$QD1F{Zb7o9ckGl|i(D zj0_m33Nt~eky1s;imU2R!cXyKtaLukX$hb;vt~T>Y45L7Krinpq(gA&ux*~rib7Uc z?WV9tUZ<e!7LleU|EdmFRl49sS1_NK)GqxFzxSQtYPsWBx{O zFaNT`_AMU+`>|yUV}+zjqat~)ii_(2XzV#IjGcDRwA8D2 z2F$OLh%32`sP;k-Gybq#{yoG1Qatm19s{hypjOARyukhW^YIGptF5G+jnJAbUnC(? zs^zWGbjvtKlv~CMwYm0nyk$Tv#f@-2sOYNxKF-u=8&j>>@GNFrE6X_E_FPFklo_=l zV7L{A!)-eqFFkr??|r|6bTb@$sMqS0I^cNE7k;`B7R_4ND=$Y@tYb%tPpTos6`r)h z=E-%;$2VEP1PpD7I+W`KG$g;SFrNQJ6xRMidls`#QItYT8(oSQ!ro!*`xlpDb##pM zbp~ALQ=!?jyIqLN zv%(j@KS65CIXx~gT?rj=nR4(@W8hAP5~v^anoDq7j4ohA>;yi5kg;b-mCbVF3C_5g zr%c_McSo)omf00DX>4lwIxmSEmWlausO`CP2=MUVAL109-{88AqkVOk{PjBW^W;(q zA@%@veGt~HB;~Cup#|mkSsN8C@H(&(+K50$F1(42kC~4?ey0yq7OSAoqU&{n7;n=4 zq;)2sgPT65Wo2ip{=tSWuJ!c|yO>Y2-M8fGWBPA}rsLF>_k#Nwc_^l3(EgaL!7{gy z~WsJpXn6D^nm!5MsUq^4rzBx0XLaKFws`<`X3%q;4DZC z1-Gs3E4&Ahy~+q=f2rl^nY-H(&jX6c!BMMl2kodduFf13y~LT5yRPIO#S(O-+(QiP zje*~@2ixTk-VoY;2}OUK6Z|1{l!_?a$5*8cVJ-PTj9^VWH`^?z|J%q&bKK2Nw-e@XC zon^hEr$3ATKjs3NG_M&Bx;jSWRRunners`. + +On the `Runners` page, select the `New runner` drop-down on the top right, and then select `New GitHub-hosted runner`. + +![new-runner #center](_images/new-runner.png) + + +Specify a name for the runner, this is the `runs-on` field in your workflows so make the name clear for others who use it. + +Choose Windows ARM64 for the platform and click `Save`. + +![platform #center](_images/platform.png) + +Specify the operating system image for the runner, select `Microsoft Windows 11 Desktop by Arm Limited`, and click `Save`. + +![image #center](_images/win_image.png) + +Select the size of the large runner you need and and click `Save`. + +![specifications #center](_images/size.png) + +The `Capacity` section includes the maximum concurrency, which is the number of jobs to run at the same time. Specify at least two for this Learning Path. + +You can also set the runner group for this runner. The runner group controls the settings for this runner. Pay attention to the runner group as you may need to return to the runner group settings if any configuration changes are needed. + +![capacity #center](_images/capacity.png) + +Finally, click `Create runner`. + +Your new Arm-hosted runner Windows large runner is ready to use. You will need to remember the runner name for use in your workflows with the `runs-on` label. + +### Check the server hardware used by the Arm-hosted Windows Runners +The Arm-hosted runners are powered by Microsoft Azure Cobalt 100 processors, based on the Arm Neoverse N2, with 4 vCPUs and Armv9-A features, including Scalable Vector Extension 2 (SVE2). + +The output from running the `wmic` command on the Arm-hosted runner is below. + +![Arm-hosted runner info #center](_images/cpuinfo.png) + + +### Check Installed Software on the Arm-hosted Windows Runners + +To find out more about the software installed on the Arm-hosted runners, visit the [GitHub Actions Partner Images repository](https://github.com/actions/partner-runner-images/). Check the [image documentation](https://github.com/actions/partner-runner-images/blob/main/images/arm-windows-11-image.md) for a list of software packages installed on the Windows 11 by Arm Limited image. You can also use this repository to report issues or request additional software. + From aca19100bae5ad79107ff983c6a0dcc3f0a58302 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 16 Apr 2025 21:34:04 +0000 Subject: [PATCH 052/122] GitHub Windows Runner LP --- .../gh-arm-runners-win/_images/capacity.png | Bin 13088 -> 50568 bytes .../gh-arm-runners-win/_images/platform.png | Bin 33346 -> 34163 bytes .../gh-arm-runners-win/_index.md | 6 +++- .../gh-arm-runners-win/automate-win-app.md | 26 +++++++++--------- .../gh-arm-runners-win/introduction.md | 2 +- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/capacity.png b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/capacity.png index 3570850cb908df0bbe12aece257ecda26bb37de4..dfb0ff4e343502c709a3a22ae7936ef412b1e861 100644 GIT binary patch literal 50568 zcmeFZbySpH+XspWD)InIh?J;wOUF=(ba#g|4Bd^0g_P0^-3;BG(%m&MAe{pe1Hv$H z#^-&{I*-iro&U~S=dAPnA+BK$*S+^u`}$q`x9<=YC273-Gu>xzN#i0=2l zTe51jKQS=6sAONi)bKKbL2=&_PoP{-x6>?_b{p z?w|T9X=;1Ikz69&(2iOU1}>=P=8<4tLEe@BFqr;UWeDCatOw#4fBtBDD1d(#{nr>l zWf<**KDRONJ$;Mu$B!RGPjy~--V}q9dM|GDE}D3ZpkJ6_tQl4}rJ#h0-g-yp_Aifq zYl7o{W0Jo=e&%TFCG@%T$6WrN+W#LXB>U0dq(cL~H!BNsOe_^4d(yv;W+)XM%xY>~ zmy@qG!eZ(c{O~DkLk01SPUN=Nwo~koP6pK{T-^8L&u^IbZCg3pD6L729Zmtiw)Nl^ zSg(F5S0(#VSFxMgi*gPEje-U#=ESmBrQxi@e4)Z(4oQy|kof_sP;HLO8U_2~rYd5B zKRpa(JT+W%k&5h#aOZ{-dLl-_(U%=paLMAOqliUBB#9pNrT56?RgZC`TACIZSGCTU zaul?lq^W6G$D6)}wyXzCUk%I5uaC*yXVf(LVc8+2tWz-*0I<6%kNr1@5vLT9JjHH4 zVFMwWr|?JxvA(0@)f4nDn5MKZX|D{(`nGBQT`fzbaBwmuiCtT|(WJm|DO+9{?=iqQ zDO1{NuVi%U6j@%Vc;{2qw&yBOdXMBjCzbC`1-0T7m(_*xPF2h1n)@ETgS^# zcEq6u+oE@a&yrWxN4!SW;KtN!w$LY2coLSj17lu&nl|gRw;GnqJtvRGUk-EnH^}%d z7LDe!e6-z{zA|IxJ*&w8sg&zZ8D0n>@F}LX?axZ478*JD+i}(RKf}IGEAYLH+NGXc zxGv(Nd9%PZXVy~41|j5lyh^zw*PMd%so#yM+R6r|EDb3W951%Lw8SR0S!ZD(<}gsJ zUc)ZtIXGj76;3DORPdu&WU^d*1GlNfM+Fvm*`ycAwHp%#i_tw?OfJUxbjS9{y-Vpu z`#hgwivj;2AidqS#%3e7T^nW{QJ8Bht$yqq&*=j@%hFf@SobVsH2C!MfoHPxuIDL& zUw)EfOKo}8CC08^tL{&hU?b5{>ZJk{ikP9keqi*u+V*9lkCFk4;R`vA%O%d8>E|!q zn<`>NvkWIV&S2)!2?=9#DKrzYH8vNT67b3W7SQ=(H2K0{+WsxJUp)i7Z-TuYT~B-y zo&RM?Yer93ztQnd-Az%e5@PF6r8`ZRm$s49NbhV}fQ`#1^VIGYg%vN4m4GhY%+ovf zHMow*6!+g;m7mg8_7-{Sl;4RAzaKMOJG_@8N;MQ%>oA4yIFkj0PZAR`+StB()Q3r@ zo9L;w{j^bQ;69s=h^<}U6^YNVdErY-4aL_F=nBIG?xDq>3+q?svTWN{icxESpSZ|+ zLA@+0;E+Zt8G-G0XvfrXAP6q9ud|a~Z|2*XOkP_+dZL-ey*dR^@T|`a_BJb?zGON= zf^N@Oxu|~VXNZ{idCPz5c%_BebDDY%Ow|~SXlZ_hJ1E2?SsQ2Ll=+aZx{duj0|%0@ znw7Vr(cGeuG-*KNa1yBkmXxCW^5=GfNG}9W|Q{#qAQ@su5O|c>! zbH?=%pg=_K%AQeyH$`?$4$^!&-$DW{^o5CkhyE~0Lrn^}4|49qK3tUe+P%ozKO`vaut`2wsM(2GDr<ArO-x@6jhR_Aq@bI@&jAWx?l1sCi^6HLQvP zOuq{sT1;l?nN1^Qtd6IJY^In!AOj%HLPCbahCuRANB&Cj-G1%C1IuhVN2`SHTd?wA z$uFU1)IE)X)p3HrxL-9!+WC>^)jJPU5m2 zaYG7Eyg14=X+pG}Kn3_apVNzS<{OaY&~F`%`QaS|=53U?&s=|=-FsD1Xj!ljar#1k zmm4Z65yz(cEYL5<=U7AW0-DiDBG~YXK#8m8*KKlX0-L$_haf9L=K4Z~X4vY7DLH%l zNBZ&B<$;}{x;^IYYh;$HQ~AYn;!y7Ilgn*>vCvu#P1Re`JDQO@U|LQ}j_PQ;#n@O# z^wS^Xh=!Mz#Nmlwj;nAI61pr(shlP3UgS*=$)M%Fr(aouP(_(VYy!?J)+CKwRa}pT zPo%usyIO3)vQt|>M2W;XZ@rJgVL5@w5OoPO=@FGQsN)Wr1%wU~ImiL3C*yr*Y!8!o z$qgW~p`^uea%p2F+wFOszxOO{#i@t@e%slN&Fb`S_DM(=7CHV7Qd`S9G`#NQap_Ee zel8({j_IS6;>QI8{X>(@#~Ym0)0;?cu2s=+{LkxlP`~IoOuc5VD6t zqow`5&#Rts&`W4Bkt=7SvFNRU(plh#do>r0-ZwEu$r=po@pSw&o$~Rk_7(_UCH$##dqVaUPnx zmd4=zurj9rd_Qm;>0gtb#vD`fwRfo{CiYwuz0b*@u3%jj^H6WQgLj+NduBvUzmW2v zIkCP<-yw}SrqbaVbD}Dp<0c#s>x-bMTtnVp-_CtTNEyo|ZS_xmn@>w`Pvy+rD%LeU zHLTJiKgquOh2Xq!c#%%n4yg*3x;l;Y*)|^9is8EpHB+dc)`%6@7W0uTkYXJFadZxv z9hr1)ES|Uz6I@hi44D-IS4}oKVoFPjQ7Wa=k}w_6+$gO^+c4)MS;4#S7$}ESXUH~1 zk!%wP+qS)6@3-mqyoOKq*jK?MUGQWPZ2UZ<2SOMK%w zK?}HR2biVxr7TQng(e0j6ZT`>sHo3WXsF?b4&hTB7uIS%cl0tS1`|tOjj$XYnNZ53 zMj~>ib4$=>{Z;L)iLBgn4>Z&gWG4I8q_2DuGD&h{=BT3e{sUH7*-m^kX-=#-4_F1` z^pYCT)+8>6QV&ze>@75D5p<}L9yOr4Hd`?l~{ZAET~w)7klaWm(_a3%Jvy? zL&{?~J^Y%{^osWhgI?Wq(YN9->^-lr(n!jwb_wJBonzbT3lH3C(P9f2QOPE8#r?Qh z@xF|Xz!}eBAt;t?MCs=9XexA{ZETy7j@aTrh-w~NGLRay(XR5!?@dwwGT)2Kws^|- z-*DTv;vG1%?Ie62CT92e;#6exyW0sHr9x7CIzC6evbF5LSu%(tac5_P!k^9Ryn1F& zc2|DSw8SHvkPl6<;!k7W{^MKfj;Jr%p_%ioIP)zE3C1dhElWz)@_WK?dQI&g{%FD& zCm+AkY9|Cypqci8brH(*$bkcD9B6;lD~IwtEj!Bhe+DWlGlXlRM$GJWcRrvE>7x9r z?@M?vT3n=;qkPh)h)up3ZRjZr%D;5WogbiCgGUQB5?*ZqCPf=Er$c!j3m^U~w2Gsf zS-dQ}yoMEyK2(ItG3H&}FVPe^s)Kzmj%PY9#|UlcYb-{4RM9M2*1v^CUWgj$hEz=A zqUp5)l?0+lDDFPHkw@Q(n~^-#8Q0N|^FbfdLYbn%boD8kB~V7{u`4rI?oz4dsF%r# zo8g(R`L~&V5bwAn@1ec56Qx9N{U*R6v9zF$L?GR}7VC_OEH749gKPhpqH+|dMr9z= zfKEmTZLOn^vB>#!R!A3AGQ9OyzdEFp)>F7BLp?hEV2~C)^^&K0QND(1uWzV4-3j}M zMrXV}T22GJX|(u=rhfZ;$wpcIIH2ddWG*@kRuRBFf5<9$AQis#OmisE2pDnkGooZ_ zMe>uzNe6`{gom(tBP)mbdQ9AX?{RYGgjpRutJshIs%vk&+EU|7YXOmi>I@Rdo1&P z31oF10C~11i0}??*vb5=nmOZs^3~3py-`5PSE@?+ekn+9RnS1B*^Rx$AnQ@_*h!&X z!u(JNrpodWFP*S`&}5mQnwS50B1k9&@*xxAN%2!OtbR0YLf!j z$_7Y8baQv$=X!HRrS!vIYL$7BY6q+QNl$y66l>MJhnrNn>CarolTM28bSACWPr&p} zDE;NdgK3$6u522dk3MD-bqupybgkE;22ZXa4kq7HL&4a^{XB?o6jRepHFhhE{AqvH zii|{zWq0t~Ze%Yr3oc^aO>I!GUC_vtMU;$AGUei5JIUxX_w!uEz78D*`3O&H$A_nu zva$JYr%Ee88KC)5SyQu?-kf z2w9t+dl~vFg%`w$vFTnD|gZiimEO8rnfUes(|cqp{R;rdYkv4m(qM4kCY2Koc4Mi3Jv z9;Y^*BS&rV?b^-ACcnjBV78gBZIogIyJjh?C7{Y45$0dMjq}$cPA?K7v==?Jhwoj^ z4RF>ic}KnxV-$AF>yD+?Si znA2`+<7wX!PSO1Z-~`2q-4BkI?~;J`!g==L`L7mcg3s}g#WNloUpeBi0?dy&*Q-t! zo#QViLXR$vi5z_4X+#d2DjSFZ(#JdU942apZt=3MyK8OwV{!Gn%h@-!6B8^Ka{2tb zW5Q&*rkHAqzBZlrj$y5}qd=!OGw+u!v>hkjky-L|D*8`ND9CPmUEgOY(A0#5Q@)^4 z%Z=pBV%M@Bw6CnrJAOR$#72rNd$wgM3uqnrh%E_ly+6SrT#;jcmp|M|6s-1FtMPbY zydG8{n@|@O%fv-PfMs*skLirc;{%ZGQWcr;k!GMzPvXUR-e8zQ$P=o{H`Uy7nQOU0 zJ1c>uGb9buxrfWcpKZA53_0${O2UNlL!muuKzP(fbbxz`NULLRN9X)y>49!-Khu2f zCw9-gM{{Yo@03LggdZHnWQQWRCB3LdbHhD|RgIy}z+?~6<2&JX!%fY8^>K4x0n7Wt znd@p}=`A}c=Gm$rrz|{gIGxnfuVU?l!MaBh+|}Q8x2B3<&(TrCAsp+bd`+csnl#qm$To`C0!o9P!kiHPzm)XE7<00`_>-$ zV9~9;>CAkGv>)om=kIs?I*~Kg6WTyzpcL}bvJR3PZ1!DvYS@$`H)ZvR*yd+($57+G zMlxt7KB-4#p2^6v;St5nomn)f>3$sJFIeEzB_}!D@zbP7XZT?&+_>>I?qEY9U9Iv| zr#@l_Vnn6k`m`>gK)XH*zLD|&ZgsVycIsFJ86*3wz3*vhjW*VqjCE?2)e z-TR9JBPp2mm26dWeqp7@z>1{28EFvAFT*G4%%iF4xl6|M5D=cm_kOfm9rW@plZ~it zmMbheq{d--KBG&GrFykv54f{l>oNrp0&B8VpI#?}98B_cNIh$5g5qr32M(n-@@K^H zxFvkGAk|Kf628fZ^z&r8p001m+%QEq(^unmLZbUlEyz+)lTteHNA~ukI9bc#<-Wo^?;W4nV#QG}EnXx1r6T9;do`E4&EJ%Z`(7r+8MuY0 z$Hhj*f-gN;cuM{1bn|&mANC6N_Ae|XNy@{-Kd-O4wZC-+HTF*W?+23yocY0Q+_M7o zru(rtEq8Wyo6owpcLIx4G{@FKe%g0v7^Bv+@Y~Zn?~8cNYYh2XwDdcYmmI;W^D}Vt z!kXK{l6755nMxI1Kg6*WjJ~Ia7J6Uh{gu<3IZw5k@&nS3;Fy>)W2yQTn*#!rD`ahW zzpNIh zGNb0W92F3$t}PAF6)l_Y>sD@?T;(t1Z7hn1>~p(Q`<8ZJy2RqAPW07pAMhR0HUIpq zTbXV90kL?ZS-=@lX&flkF+7+p(|5d$oUr04H!k|zri`Nks*MFEjmd|h6shw&#WfF7{ItmCXNs^{Dpz(&eFz{+yJL@|1)4v zDq?(rD&(=JO^8YV)EBCf+|fdT3Ft+X(9J}0k2nEUUtHo#0u4<~^$P~xZC1u$(-}>sP1A*biyl6@MUiKsF(R&{Z2dlekK@S;ap-Q?3 zZ*J31&+1j8;}+YXJ9u%kyu5otbaw76CgI-SRiNb2H|#0C`p|mRB*elFQf0F`HaGz? zNzTKQD8HfpBenGQU^;K`tBe~155#{YppcWxAfE_{^bP!h5#)tJPFtjZ1k>Lx^Hv#k zA`3XmCi*{b6i+Vf`XdaCGemgA&^+gXWqrX{he=W$Q|70N8 zmjt+0bjYmBBjM^fL4&r|-$&0MC|Jwk+Y7pMIC>uI1M)fJFI&FRTlRQRO-jz6Tq!l6 zcaffs_P(R@^#FbD(c&(qud)mYY>twH{Ze7x_EOR)9vSll$m@G(jZ^>2 zUQ&Nxtev{#wpwiLYmT3Ch5r)di8#b|WwZ?-|J)>O0|1BSxq(!;`84({dUN3tGn`(B zaIUC}Sx^~Lj?BuRzLGg`jBRjZ%q2x@7-3*KW0R|v(Q-Xd8!wMOSyqA_YWtok*^i0G z8p%5TiGcoIzzQyE9_H(_y>MBrh-URr|DdlmGzSwVl^Axq)_O`g8xg6@ijRrT-&6kk z$vfk$Nr*YA8!Y>@J*|SEXEzd%dye@m9=H)|p3*)Tn?jTj$6=(JvcB?)+Sbf2BLe_K&|2PsJsMh}hhUWbHdn6+c8X zLHbiv1A^GRQq>wQcjo_YkGRvxH~b;+w|}dAMfv^xiaACeG{>9#-L+_lqBBGj$0!qY zhak@U*NfD5uN}5tnJtX6iT}9|VPJlsMdgbqB4q(1V`@P?86#|6EudZT9NMg(R*MJU zk#CvWqeWIUW-bx(uPkEGOad92IUsvP-ef?A(&Wywcmvorac)0x;8mseY& z_X8&}@yJ;P>$hJ2?%^RVmh!L^)M_=meP_i(s;mF!2XsCvh1;-AHO=*1IGu@7{W=J~ z*x98qeK|gf7jI1fw8AUS;jONjPC%dEIFYrfL&@!J*v>wD@l&IFyElwWkha+W+UPh| zlzT$*%Hx;(rezwN@2$p!Lc^?1rRF!UWltk0L;l>FzET7=e4$&*bt?z2DVAlIFw3A@ zZL|#Y!y{IkpEd$`^9E}Xe+u}$gSfN=XIgW1vo0}s8DB6vkpQV{K|xW11^GqF+lUz1*yQlx zEqGjdHQsbJpgh66P|^jMSmZCp3I@RZohv?$Kw|oy?Ip#w(Tl{w1XDBv@Z3}k4Fk#Z zh0Om^ptO}UkCwGZz251%%>&bp*3e6qtb9$C1RHXy>9J5Pz787 z8t4mhXBI4sT6a%;imyNFn(^#+DZ4~j{hdm7IKRbgN@t2&i^P>1ReUM2vM(`&tC?`U z{Kp0u|eHxJC>HcomG5T6E=l#ue;f1M)X*uwIwVA+k_uVnX!bN7q zA68pIpuMwU3(VE6jw~H@JZ2z?X;pS|p@>pYOKn8(cN@j6na`EXyHJ1URLH_5C0OZCT z=;z`RfY*{Zq}6CRLN?;?6zX%@?i6svL&dK?Y!dO!y^5m&*$dO$ha3?q6-U32_HznpP3J}H zKEo%j^Q|1Z{8A&;hi5j|^~9HFRM3;@RP`Owtl@4{~Om|xmwHTpJT~AB7SlQ1Y-`lnC zPb&}RmYUCQ2f>v=OKA&_6}kfLo~oRGr)fD`@b76U6RSJ8YP)b>Y^A|{=x<;GwTsR> z(8=37yJK1G8tHl(XkwGjm7p+qn6XxPo15PMvA}r=G{~ZTCQigYAgzl3r?Jv2-`j@% z2XpaPz_Ez7K4ogBQ;#eEnNLB~_D%#I5xlLQh8nPzvNyFH_8g^DL1Y>4=1!WI2(SXy zaixJm2Ok0>l~|07mIG{#^>$9(qa$Vlq1NuOlVu7~u} z%rbP?NS--1)?WMFO9f;EkPFwo+EmiOQ;X#ubLLA-a0N&%s@5n0ehue2^(}BAkkfZZ z+TqIdwuSjk!J7_sY)~GFnGaP1QTeIW^&XPbY<^T>u9!#vWOZTFex1;HB{JD)lDmA; z(m~-rt{87%{auC>|6Z}p_-fSfcni{oqou*KwAN*hY-fzyMMHKRBbMxzQyTyMt|CRJ zN~WHDxyGw521wZ>w%hhMxQLAN-5+2eJ ze2_{^F8MV0{rRx3k>K5~2zuuwq3kZlKr<=uubJoNJ=A&F6yy3;Q^rtj{>g-goW;;F z-r;EjvpXc)!LY{WtAzJqK(QFP9Bw8UkW zkeb#^*lgYWZk|=Z@Z<3okK(0#-iNR`MTrY}$KsJ8asar$5Alz9 zdivlIV}eOY>)5b^D+p56X9?H#JqAo9x^P}Hn7RCNOOn$BZBqd^5_s$zh9gsUqBp|b z?S0mCdv-6idJ4_%*tiML+n*Zxw#MQ6Weg8b*e*~5ayEh^{U@vlbM>=4@hS5{hrcXj z_$Lu%BaJ`YrnHIO{VF=^xMFPIBrfywseY~bOIk!rWg$+HuuHCkuWze5PBt(juOOSmA_v20YfpvB=L5p*3uT0Ycxe=inr+?guX9y(qJ=<>4WsgU#Cis z?m;|htE56GUO4ZX%((W+w$WDQM$F{W4E9Fx)K-R0s4*YZ zT(IU*G?f7(U#E(-SId~%*|l{-XK?%=Z)NQv%+HYu15u41fo8${9{%y4fuF&7j&5rE zE;MxiER|*Gox-r?2l0Ls#lMe~BPv*!DP5N}3BT#6|S1ci(Te0_Yo*&dnAsDUdz5=5Z5$aTm zmf=^mD(KM?S*AEIJL`%ctYQV+-{v9t^fB3PTxjWe=Qe$swHUzV%|Ul~hG@$hEica1 z!SRxL7peFzr5t_k&BXT;)87$EU3&hxH7&zhNi|=X6)r6L6nNGbJwP*!$0w5)0=|$g z^T4PG<$i-dnE>MtdV@_u>NEaQbS+aSa`Bb;Q9`w%)aY&7d_Jwy@@C&P8xgGhETra( zFnhImQbx2}t2eW(SN}(FXy6y|9ol!k$t7RrLlqu87vSxVIdrbXm$9#TLn<%P4CWgN zuupATE#@{o0$Hbetgf%`RZft8l}QC6+NrAGpi*91p72dqrWIKLSxf`M)9u$O(TR9H zKb>`JfNS}gMw*Gx5@1gFVW01{YcnwQVzmTnDpkM7pC#J_e(ji6RE#6lbMkmx*(KLk zVLB6Q_sx4bSW(h4aKhd4B)C+rZ^9)nbfJkuV13}E)tR2ry+${yvqa)>p<|JU^4Jt# zI^ZP-Q{MdahL&?e=q&^n->p z8mj~Y6OZ=)BA`S~v_K6s)3lSu=%nw1Q)AHiK|8u_-S~EYwRaQVw;2%7ad8<7v4z7P zT@17+<0KHP$*XJq(>s5wZb!B4j-1_ITlWJy){L(;^p+X1MOwh(|AC$Z-v5_}x-gnZ z{DmUB{N2MW4~m-3s~&uV50BQEZYR`lYMt@R8M*S22?Si`v|NB}1mP3rYwvD2i}=<3 z*)9VZIi1IjVn#UQp$3qIwQngm!yMfi#62*{N_C?Q`6$szD))##qWt^2HG|v@_>PJc zI4!EPoSNQu2hH>DOQ0InZ>orLZt@>6f_zYP4>jdc^WYovMRih8PM`e(5~B8J>mD^< zaS;?%2Fzex@lQ1O_d|oeKR}6l413LVP>9UPDyTK^{f1xg&T6hKw(roA8JGoZo`j-|+lF7EN~u2WVDBOO|Hn2bj0)#n{O z1)4aZa3nj&B(ePct+tyR$w zPqGs4#@*cZg49-Zn7j~pQ=(8)onQAWGn$*PsXT2o=|eG-FvJIyBZJ6Aom=?)Q#%Nw zh|#|KR3-B7E{=d01#7(-crr6qua@yX)zx~CoaX#8ks`omg@wa*O^_^a5WQ#pRy_E= zIe!JM&RSIC2I`Ez=pM4EcAW}&XpAW68nb7n7E}k2SJ|5XOH0z6T?Mt1*?CCX`mXYb zKt`G>eZh~K+96R>VWk)O1zBNf@}jQXcsS#n1Bf&0+gQ{`EPVyGKQ zP7_&rHDU=?aLXYIfB~r+PD!zgW@3Ll_2-Hb)cFkzv0BBolLCUWXk29lEw477Oiqb` ziWNVtPHvB{pSmO*a#AFL#j8y*Qbvy99~0eA)vW%WYk5u4aK9 z67tnquyu02Jt@4#0v*k3-3(Qv!`MRP3RnZUo=w5C;|YNBN4|y~FR}3C1}1Ji+0P!Q zaMVb{RnmZOoRjmey)0g6)iWz0E0IkKoy?nDn<@<^+pd_#(sCGmuL(|*Rkz2a6e(%Y zcIBVkYD5km<~I2Y$paP*N@|Zi;gjmyxN~LsHhEaZRlF% z2;AHVCPUvE{@`7O!OMd@sD=p)c&(K8GVaFT-G})Eq(gr*np(;n1IfHr^F#)(;<5KH zB{bKr0BLw9Vk3Kit+_ViCQlC$in{)6=>Df#tdFPURKIFdjHR9s4bE=_4mcvWHIw|< zUHjq9MjNfQNcNw7-+pG?HKw8v7|HeBZM`ds)7-@H!s@GcamQ>MMaE3com@jQ*6a4( zqSght#}A}QoNlv`Q zpD(TQ)+L6V?WL9;Nm^#hwjP3n@-@vdwvxFNFEhvjD%?X7BZx>SAY~GUsa|Rw!#HC0 z45pDDSv==jfj&c1R-JgZ_8tYIDn)(QLI%Z2A0w^D9P2(-zi4&z($dAMJUnXIx=NJBo6fb6vy9qbJ0>SckyRoqM5cZ+4fccXEry9l2 z`MYJ15zL$ww$ZaZy^f9YCiiMC`_r@ca%8Ssdy&-o_$FqGDMJxmY zaaSQlL^(F7+4m=4hSc5+QR}MTE6A5*spe~sn5OczDbod*t-WV}kAL($`R>0wMyTn# z@bGcy0X}<`Eh!L=G~lyHI=+g4wn(bNa8rhQBWU;RN4}8fn4Z~_Q`*0|N-`0aBgdPQ z>qzyI>QE@N7}(>2H?hQ5i-Mu-7 zo*EwQdG6?#3iomdB-?c5MT*9_sQZ)QC)JE*Ai*-UbNFzjbglNN|EBBFi+E<9%1c#6 z$~kRArEs1Cz6sYoA{V)LxH&?&8Qtgg>^=>Om5-|Lc}-TN=oyqS6)uheTc6|r=M&TW zGQrG^JA{UYEn1czRjpZ=l1gFM=b5<+w(lemLzorUzWMBRD@xqswBA=GvPiqRxbgrw zY4)2jzm_Im{Elv0BQLu-g-b1qJtxxk3r;@mMg8U%0UF8izVwn)&MbAglE+zd12aIx zZsZPx8(5Wnd!AuI2z;LMWSa2fIC&rqElLkmz**$PY zJK>411qe#ae6&dOp`z59&tg!NKjLQ1onD5$Bdjxinpv_Bp>Ql_(*b9C@Y|lAEaS{P zaAvk7^=Nj?r9XRdz1S?rl4VJ9&a!kG#gfd#$3n`gs)-rPHV_Xw110r5Ny1Plsrjov zp^#Pf?v0%+=oL}^u}O$ejE$WYcg>pTDzByY-bpkFYB>~{r?vS_KZno$Gn*AW==Si@ zGddVFZ%L!MR@XQ4?ng?bh9=*|*kn+}7-YTjnTzZebWeQY zoc!^uQBpav2j7znAm{HF$re$ZqWk)E`^WQ&iMs_^92`KG_Q*}(NFsm1%5_|QXp?3A zR7sewW&fL-&OlrzE`lW=HY#Z0vpjSTGJt5Zi5V7l;ik@Da${SlE0R_&N`IJEZV--q z3;f6l}D4xFm8#bn}F`8W)|wbyum2x5 zlQrQBeW~?DRN-3+DxkUw{sJl-a9`!Eyp)P1wum7Ipjipkz@z($?2Ynh=CPX%A68B~ zHMSYZyz35UBY{L~Pi4J8)An0&OU${u@(pn|a_3pY%3ajVJQcI%my3jK^C`Nw{)Mdb zpqC+NFPQF1kH;tFZH$V%|3@U zEoMDVoJMOF?|it-glV-yZ2e%WRnJxl9u-fyUsH)lo|NVQDm@q3f5Xw#+A1jrYI0p{ zf&}t|t9(Duqv)4<0E?aleYd$SQjLkgCd&~Ywdm65I7PA&edc#Ti6*w8X$hqVqRmQ; z9!>e*U@I~1*T>9_SE}5vHfY^hd{rs~ed?L>!p7b7TNCWTxg1+X=Z=mOC1NpP`)ZuM z6Y3IKd1m*0C)d5AySx>y)zcaag@p`7_}C6y4}E<5*+3`TPZd+%jPFc+pH-N`qfgL? zm3_YV56;1Vvylz1IW;S1mN317-zbXIhKGcG4%H$*9dN;Oi1S#?paM2T%m7`xt(|NV zEk~$I36FN;vq7%VAM*IdufrxkXXI~3QheqGA<}zv;UT|Ej5?PS%$;$(c z$K~`L(OHbW_2Q=}+8(glr2Tlq3Y_?8Tt3=Zx0p$rZ=~QYik7ct{YbeHf5%iEESgOo zXiL?&voflLBA<=4c&ki(8Vckqn&#pj%GvO=*qkr?q{@GTMB*1uZptNX@-??|yQTt5x7Gpq zv8ei0YRbBJaS`OLM~U7|24hzc#Knl3en8D}WyCY5VEN6W#*+>5P2vbvpUf`)Nv9K)KM|p-F7YTlFHXylL4Z+M8Dj+c-JC z`j^qlXis{_1Y8dU**cqm`vZsr+??hK0hp#q(@msLs4J|j9!ao_+hiUXu3)|PC^E|1 z349ScRC9FAqrD=_KNjLkqorlDuD9e%T|Amuhq~h)$uZqUI`@#S@T7nDlb<8yIH=kK z8C$YrnTHP!AFj^Q_|2^M&r`&F(YZ}YUrWVp`>gbPg2kG@0^|6j(E`24H^gRIW#ZR+}ou*^;e zn5$nypb3xuE7lH(!Z8sVRPcw81VMw(QQjRVzaT9;lOjQDC~Nr@HI&xihf8Qk7< zX7pKe;6)hyDT`LMF0JRd4mWw;k^<@ubZc_(fYlD9dE;B3S)T1-iB^NhcfV%0zzTP5 zt|E>`v<*i5y?FH7xfil@WqL$Hi(qUj6d&^RVLRAkq#fbAfj?!TUSsn!*&gI=flo+_ zgSJh}$Ul{)kst3bH1RAjGEKcOd*sEo0|6Uh(qZFDw%@EDtg%p)KqiIrnIB&3woz;n zTyDW#`OeNegtkCvK}0k3`tg#~=!FJ4ZPyf4#k`K@;a2V(1AzGblPfoR)e8$WTu}Lp zBpsE-$=W=eeijv0zH4@(Z0ZffHWR5zBprv8=K(F(< z*-^z|dF=~0TJahbhSHFEXV)-VJuo`^`-1D~qucEEP9DW-Y9O_9@QVu zv(I40BRo7(M=JJhS}bc>%vaWzqEU~gXlap@0Rh?nO&5DCQoUH5f4o?vXfxx}QN-zT zAz@^nbP}^&Ym~CnY|S)`2aJQvPQO9%@uq#Zfr)PnXJ;fq)kT){p8zxCR#nfU&32rZ zdUs#4bFu$C?ZIV|4PX|kHk|+x+s!sUVPy(A(FeaI8iCek`Q1d zB9-K7>sEV0z?d)MYC=7s?ef*~CE2v&o2=5iiFXvtv@~nP+Y*N zz4$afzn6@+{$naQ>sMXWNfEBJ2bxCTiu>d8;3TjWD!{7VNp^uYsKX!l!LIg`PC5tD z>idiYp07~2BJkG#!-H#WP!}=1Z}N#oEVq9jS%p|%hXAF^7TAWo>eToVr=H^wK%49m z;tUDOV_0-2vYWWJa*=Mrnqc`4J#V-%Z+qht1Gq{cp`HzqT-k zuz}3KsJmOYEvCJGHI2YeiWKaY6;m>#fH%)zpv`+l;>bbC^idSL64!t!b|t-p{{K z0$>eL?mM=7AZBOcGETo3=lw#`)hI?+H_otc|DqS-H-#)jWP8n?AWb6OsFU$&mqhT6#IHMP64Hl!gCMcmjU1_UcJBaYP21LUUZnm4hWn1eBs zN9n_M*YCN6-u8Wam!89+KioUWI7uC2G&Kad8d_eb95f^5f%(SAX2xl%JHRC8}3K3rr#@Ma`E; zfid1TqN6JcO^#Bi?Ud58uw9xLyBk$y>D|(o?IYpu(=2erbL3&m%ugpo9kcQGqS=jYjkrm!S>=KMp*E`l42=;omB!L@nc$ zH9gKY^&a>kFM-38^TA1g*12y4DaJ^8x6ZQp5lu-?M4`w=I(x$gmdH=r@RMfsYWQC8 z&ZVBWm;WsREornDN9YAPp8?rECT02^->78tPf(QHxz7~T!RRo~lY#P%^aE!HDN` zOB%J~wp=80cTWidlN#2KcgXI|E;aLoP2j5{hjwpg)VPdC?%f{yn9;Oy5?^MeQBQR8 zvbL!P*5O{^|7D?Zt0BURHYg} zXZ^fIl$%y;OnNCC@+>4r_qg3yI<|<+l%Er zpNN1*4zo0bM;q&ppZkReHix;UgdB%Z``C2sxAjGz-nnA2OuHRK4TZn?Ax+W5zjpw@ zk?#8%Zll;YAkp^+j>*q(#HV+XKml zii^>rf4QOPpO+iCux2ac5Y}2mi}wEPYV*l=_(?p2tVoK`>uJ}yiIyd^oyU0#qFGxm z0_zc8V&hCTMpvgeD4yE9iML3t_~=xj=Mmo*R$p^n=-JBIvs%h;$d0B5nsKk~iB$tP zAD5;~6c%241gSF9CN4;CL$M!&L#V`LSnNh&c`~dJhOULK0?L0os*a}BS zo!Y6eS4?=3PUSry$A|pyAC2=Yb>hpGrM_O5YSx_6IhkwGqqcADbbBLDW6Rr_^RUFU?UT_T?}6|Eq8{rZ*EOaMWp_O z6x1~aJea!sT!RQ6|5ty?H^=X;G!@1CtOo4xR&L=PS*Qm2j&W{)r|;(1U8YihWFOY9 zQ)?g<@n-UDfqVN0U)cUt0GaEPgyXZ8tGo~Dysb+_%snW=Dv}504v1>Z(QO^Q+DL*i zoJtyTMCGRajJbCzv2@O6#@87GU78_GxmK!Z2L>VY<|9pOpJe%G{qXVN->ma|Qjefl z5X&`A@@Skl2qc{DX>wkE>x9S~qxM}|rl8w2@Dy_$NvgUCDt(f>UrvKG#oak238W5p z#XFjL_B>=V4SPAj;zD;sVE0Y2arWlgE`T2SWP{vRvC<*W9QN?6S*La~jSkG+cjd~= z410eS(*Jb@q87Sq(VezFCb{6HgFDC=^eK7lxd*_IATfiobOlD>gud9P#RZp@u7Y)pWYTVZ1p6O968_Z#%y~k zJI=?4U3)!STwk)i9}#EzcVXE}5VUYto^ZNQE!Gm$4-&k}+n%*y{KiiSA2B`R52S_p zpK~s${~zqVcQl+`zdwB6iAY35kPrm9qlH9-(K}It(Fvk6qK@8&h(w7XdIp0iGkWi3 zM08Q2jxOq8l;~v)zbnu4ob%i#&iSqNzU%$#UFRRmGGotN``UY7dw;&4;=k^6&?%+- z)dlv_21#-Uw|?U~8bch_Cjwf7mc~^fq>Van3bh+-_wR?R_{OECvrM z%j|?2Ot8V&IlQ!1? zW@09?-ZIhu)PJmq#8^s(o|F|}ARhllevre_>(T{xz5zy2?eW6$oY9IOj8 z-`C6YH>KI|A5+2kj5%QTQmNz9i^uFfa<9bI1b0c2uA6sw_@oT=Q;HdJ<=P}{#7T!M zI#1gk>&I05;STqZ=Z~5e4nKq2MAjepb1@vrAhyoRldc4;S0sN|MIuJ$8ud28iMh#z zwSAI9t-Pw=Sv>LwL?O1la?5nM*@

4|flQ^0tU8Ck$jH=myf(qS7ogAbSYhPgCV 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 diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/platform.png b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/platform.png index 54fa77e281cb5effed163900866e275f9a5eb99f..30ebb83bc9d96805025203824adc6eb485efd773 100644 GIT binary patch literal 34163 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 Zej}tbM~dd`7YKkK6(!Bv2nD0S{{rU literal 33346 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 Date: Wed, 16 Apr 2025 21:49:31 +0000 Subject: [PATCH 053/122] GitHub Windows Arm runner LP --- .../gh-arm-runners-win/_images/actions.png | Bin 0 -> 83997 bytes .../gh-arm-runners-win/automate-win-app.md | 39 ++++++++++++------ .../gh-arm-runners-win/introduction.md | 4 +- 3 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/actions.png diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/actions.png b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_images/actions.png new file mode 100644 index 0000000000000000000000000000000000000000..3767458c423269e90e548a6c92d2c3aaa624f1dc GIT binary patch literal 83997 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�HZKh?GCD7SM;#~OYDCVIx=@<9k+gRE0j1Ffs0XR(36zH*iiy|!*_;%v z!jO`al^wE;$ldXnSn96aZoqGDs^Np$u3MbHCUk_{KwJ$xWcBpD!HdJ|;;u*70qpl( zV7@BJ`-N88z|vyu7KlF+{}@XK!J{F98~S?}dm-QL)UH2X|4bWbtO)dNG%vShH~|ff7P@wu_CXd=%vR%F3Uq@q=Evb&6iIv5{3Bv9}?@qkZvwo-ym8Z$FXC$D^c@ zz?VOMHy4ca1r{y}|45DnhOM#9LTCHKYy;}=i*HAIY6p+^ZI|S|`C6=6N$ z2OETRfQ!4w>A%s~--^R6%B(L9|9CiUa`AR5TCo|DXqt)sIk^mJ8E6FC2?dCPTtj${ zWO2U7eAZ)9-L-uB4WM4#Z0Gh+22Wo}KlDt*2VQ(QY^+BFHoXXSE>}!4ui&gDzQ|Br z2$H=Uwe_-#bYzH6RLwlniAJd?y(3n-=%KU$W?p^Nz|GPfeon!|NQz57c7k18y(2xP z{95s>%;Z*QjHVkINDNI{oRWv}Xvy11F_`yW@hG&@%tdb}LB&*&JH*37N>-Vj!yu(! zLByxp3Prm^c%w4pLP!hf2zw88gbBLmcWPoh8Ruusa0GJK>*m2YiVj+io9Otkr?4oj zrOMjgFdXB7eLblC@{R2_MHGrHpKk85Y;e=CTrKFw!p{m?Px$+WLf|SBQEhe`w|K{( zo*dcF=qc&O$d1gJ$)`XN#w=??2?m zAEM#=;i4Oj9Ln`yOi=P6Y&lGq>hst@iZ#ww@QI9+i}amchym}na%Pj9UIfcvi;b_V zQZnmB!<2^SPtE`;qGi)7MVEJU#?{t-M0S=;dF$^u5WGat z);Bj@g;Q76D&3+ z2?V5*XH%Fq4qSC*aRhpxV1mA1)XUIEALz9B@1dFUs?ruPav^5@)s@(!@VXk0777%Sw20MVp5MEQ+Ng)yF!M0&1p_(V-TV0D$cYMyJau-}~L08GCcNKjf+KcT9 z4vD&u{R$6n-G$=)L*2Jq46gd4=_@Z|qsm|GA}z%kbg4Gzkm??WDPGgtHQZ^49-smn z-lN3x$QZy>2?Sj;aCgA{&t$9gbYS;4DHOPy>n5Hpe_MG2FN(I|JwxKMBaMHkoJsqm zvMOWW1NEWg!Q?wr3R6??=!p&_wZB_`voCD(KTNZGJVBe-EqA&nhgZtRwgVPNbdbKI zx>!}&-ze8IdpK1WBoCEb18B+I+izOWpK#wM0(9p(A$WG#1IF(ZTM1CaCH#qfIT9k2szPcWnRlIaQE5x32 zstHQO{NOR*N*C+h6?RdMdW)6?)8rCYASC;)9aJ@agfI}jXawFi@Ud{-UHk>PVl;5>~5I@Il850KfBi?19)q!{VHO+*rru=v)+ZfQhzqn@}dXN-zs~) zZ3UJY@f|F)>3|iS=@F)pQDdi#sqTiE6(NHQ{(G)CF+b#cb?t0IS6*p?x+OI&O$^MojgoOl?L5-^ z&y}#1&f54mWfRDU3l=>4eku9ERk*f*N1ip|%v%50T>`>^nLw?x^zHg|>JI?wDxhU$Qa6c@ z{afh;Ctr+E>;=&iRXOe8&y51VtPDstoYM<@CH&0T0vnI&^4-~ayRAD#rDiFXsZuj9<>R>Y2Zs>(dwdYvo#9Bz~=2 zpBBqs+v4i(@@rfCw99(^I&-a_Vg7f|T#E`}8^UnorvyYdfPd%z(m0cS+W7YW0ApRp AVE_OC literal 0 HcmV?d00001 diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md index a563d47f7f..881f67555b 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md @@ -37,22 +37,35 @@ This GitHub Actions workflow [`msbuild.yml`](https://github.com/odincodeshen/Spi Below is an explanation of the steps in the workflow: - **Trigger Events**: The workflow runs when there is a push or pull_request event on the main branch. - **Job Definition**: A single job named build is defined. It runs on the GitHub Arm-hosted Windows runner (`windows-11-arm`) as shown: + + **Trigger Events**: The workflow runs when there is a push or pull_request event on the main branch. + + **Job Definition**: A single job named build is defined. It runs on the GitHub Arm-hosted Windows runner (`windows-11-arm`) as shown: + ```console jobs: build: runs-on: windows-11-arm ``` - **Checkout Repository**: Uses the `actions/checkout@v4` action to fetch the code. - **Add MSBuild to PATH**: Adds MSBuild tools for the build process using `microsoft/setup-msbuild@v1.0.2`. - **Restore Dependencies**: Runs `nuget restore` to restore NuGet packages required by the solution. - **Create Download Directory**: Creates a directory to store downloaded files and verifies the Python version. - **Download ARM Performance Libraries**: Downloads the Windows installer for ARM Performance Libraries (APL) and verifies the downloaded files. - **Install ARM Performance Libraries**: Installs the downloaded ARM Performance Libraries using `msiexec.exe` with a quiet mode and logs the process. - **Check Installation Success**: Verifies the success of the APL installation by checking the exit code and logs. - **Build the Solution**: Runs MSBuild to build the solution with the specified configuration (Debug) and platform (ARM64). - **Upload Build Artifact**: Uploads the built executable as an artifact using `actions/upload-artifact@v4`. - -This workflow automates the process of dependency management, environment setup, building your Windows on Arm project, and storing the final artifact all using GitHub Arm-hosted runner. + **Checkout Repository**: Uses the `actions/checkout@v4` action to fetch the code. + + **Add MSBuild to PATH**: Adds MSBuild tools for the build process using `microsoft/setup-msbuild@v1.0.2`. + + **Restore Dependencies**: Runs `nuget restore` to restore NuGet packages required by the solution. + + **Create Download Directory**: Creates a directory to store downloaded files and verifies the Python version. + + **Download ARM Performance Libraries**: Downloads the Windows installer for ARM Performance Libraries (APL) and verifies the downloaded files. + + **Install ARM Performance Libraries**: Installs the downloaded ARM Performance Libraries using `msiexec.exe` with a quiet mode and logs the process. + + **Check Installation Success**: Verifies the success of the APL installation by checking the exit code and logs. + + **Build the Solution**: Runs MSBuild to build the solution with the specified configuration (Debug) and platform (ARM64). + + **Upload Build Artifact**: Uploads the built executable as an artifact using `actions/upload-artifact@v4`. + +This workflow automates the process of dependency management, environment setup, building your Windows on Arm project, and storing the final artifact all using GitHub Arm-hosted runner. You can view the `Actions` logs on the repository for each step. +![action #center](_images/actions.png) +You have successfully built your Windows application and uploaded the application as an artifact of your workflow using the GitHub Arm-hosted Windows runner. diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md index 15a1a27201..5a4c4cdcb8 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md @@ -14,7 +14,7 @@ Runners are the machines that execute jobs in a GitHub Actions workflow. An Arm- Arm-hosted runners are available for public repositories at no cost, subject to [standard usage limits](https://docs.github.com/en/actions/administering-github-actions/usage-limits-billing-and-administration#usage-limits). -### How to Use GitHub Arm-hosted Windows Runners +### How to use GitHub Arm-hosted Windows Runners? To leverage the GitHub Windows Arm64 hosted runners, you will need to use the following label in your public repository workflow runs: @@ -51,7 +51,7 @@ Select the size of the large runner you need and and click `Save`. ![specifications #center](_images/size.png) -The `Capacity` section includes the maximum concurrency, which is the number of jobs to run at the same time. Specify at least two for this Learning Path. +The `Capacity` section includes the maximum concurrency, which is the number of jobs to run at the same time. You can also set the runner group for this runner. The runner group controls the settings for this runner. Pay attention to the runner group as you may need to return to the runner group settings if any configuration changes are needed. From 4cee80d8008e0471a2f3161b2ce3e5a6cfa47992 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 17 Apr 2025 11:09:53 +0100 Subject: [PATCH 054/122] minor update of LP for clarity --- .../disk-io-benchmark/IOPS.png | Bin 0 -> 40437 bytes .../disk-io-benchmark/_index.md | 6 +- .../characterising-workload.md | 79 +++++++------- .../disk-io-benchmark/introduction.md | 25 +++-- .../disk-io-benchmark/using-fio.md | 100 ++++++++++-------- 5 files changed, 112 insertions(+), 98 deletions(-) create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/IOPS.png diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/IOPS.png b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/IOPS.png new file mode 100644 index 0000000000000000000000000000000000000000..37d9d49bdeb1eeb8d334301d63fcd6fe1012cf79 GIT binary patch literal 40437 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 AGynhq literal 0 HcmV?d00001 diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md index 42d40167ae..8ac495d0ad 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md @@ -7,7 +7,8 @@ who_is_this_for: A cloud developer who wants to optimise storage cost or perform learning_objectives: - Understand basic data flow for storage devices - - Understand how to run the fio microbenchmark + - Use basic observability utilities such as iostat, iotop and pidstat + - Understand how to run fio for microbenchmarking a block storage device prerequisites: - Access to an Arm-based server @@ -17,11 +18,12 @@ author: Kieran Hejmadi ### Tags skilllevels: Introductory -subjects: Runbook, Performance and Architecture +subjects: Performance and Architecture armips: - Neoverse tools_software_languages: - bash + - Runbook operatingsystems: - Linux diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md index 6b2d6986fb..bc3061a571 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md @@ -6,7 +6,7 @@ weight: 3 layout: learningpathall --- -## Characterising your Workload +## Basic Characteristics The basic attributes of a given workload are the following. @@ -18,21 +18,23 @@ The basic attributes of a given workload are the following. The characteristics of many real-world workloads will vary over time, for example an application that periodically flushes writes to disk. Further, the system-wide and process-specific characteristics may be significantly differently. -## Example Workload +## Running an Example Workload -Connect to an Arm-based cloud instance. As an example workload, we will be using the media manipulation tool, FFMPEG on an AWS `t4g.medium` instance, with 2 Elastic Block Storage (EBS) volumes as per the image below. - -![ebs](./EBS.png) +Connect to an Arm-based cloud instance. As an example workload, we will be using the media manipulation tool, FFMPEG on an AWS `t4g.medium` instance. +First install the prequistite tools. ```bash sudo apt update sudo apt install ffmpeg iotop -y ``` -Download the popular example video, `BigBuckBunny.mp4` to demonstrate a transcoding workload. +Download the popular reference video for transcoding, `BigBuckBunny.mp4` which is available under the [Creative Commons 3.0 License](https://creativecommons.org/licenses/by/3.0/). ```bash +cd ~ +mkdir src +cd src wget http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 ``` @@ -41,12 +43,15 @@ Run the following command to begin transcoding the video and audio using the `H. ```bash ffmpeg -i BigBuckBunny.mp4 -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k -flush_packets 1 output_video.mp4 ``` -Whilst the transcoding is running, we can use the `pidstat` command to see the disk statistics. + +### Observing Disk Usage + +Whilst the transcoding is running, we can use the `pidstat` command to see the disk statistics of that specific process. ```bash pidstat -d -p $(pgrep ffmpeg) 1 ``` -From the table below, we can observe this process is predominantly writing to disk at ~`300 kB/s`. +Since this example `151MB` video fits within memory, we observe no `kB_rd/s` for the storage device. However, since we are flushing to storage we observe ~275 `kB_wr/s` for this specific process. ```output Linux 6.8.0-1024-aws (ip-10-248-213-118) 04/15/25 _aarch64_ (2 CPU) @@ -62,9 +67,11 @@ Linux 6.8.0-1024-aws (ip-10-248-213-118) 04/15/25 _aarch64_ 10:01:32 1000 24250 0.00 344.00 0.00 0 ffmpeg ``` -Since this example `151MB` video fits within memory, we observe no `kB_rd/s` for the storage device. However, since we are flushing to storage we observe ~275 `kB_wr/s` for this specific process. +{{% notice Please Note%}} +In this simple example, since we are interacting with a file on the mounted filesystem, we are also observing the behaviour of the filesystem. +{{% /notice %}} -We can use iotop to confirm that our `ffmpeg` process has the greatest disk utilisation. +Of course, there may be other processes or background services that are writing to this disk. We can use `iotop` command for inspection. As per the output below, the `ffmpeg` process has the greatest disk utilisation. ```bash sudo iotop @@ -91,35 +98,7 @@ Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_w nvme0n1 3.81 31.63 217.08 0.00 831846 5709210 0 ``` -The following characteristics are calculated as follows. - -1. IOPS - -**Value:** 3.81 -_This is taken directly from the `tps` (transfers per second) field._ - -2. Throughput - -**Read:** 31.63 kB/s -**Write:** 217.08 kB/s -**Total:** 248.71 kB/s -_Sum of read and write throughput values._ - -3. Average I/O Size - -**Value:** ≈ 65.3 KB -_Calculated as total throughput divided by IOPS: 248.71 / 3.81._ - - -4. Read/Write Ratio - -**Read:** ~13% -**Write:** ~87% -_Computed as: (read or write throughput) ÷ total throughput._ - -Finally, to the access pattern of our workload (i.e. random vs. sequential) is slightly more complicated. We can infer the locality of our accesses through our understanding of the program, the cache hit rate (for reads), and merge rate on the dispatch side. Metrics that show high degree of merging, cache hits and low wait times suggest that our accesses are sequential in nature. - -Running the following command. +To observe the more detailed metrics we can run `iostat` with the `-x` option. ```bash iostat -xz nvme0n1 @@ -130,4 +109,24 @@ Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB nvme0n1 0.66 29.64 0.24 26.27 0.73 44.80 2.92 203.88 3.17 52.01 2.16 69.70 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.01 0.15 ``` -The `wrqm/s` column is the number of write requests merged per second before being issued. Calculated the percentage or writes merged compared to writes is one indicator of how sequental the data accesses are. +### Basic Characteristics of our Example Workload + +This is a simple transcoding workload with flushed writes, where most data is processed and stored in memory. Disk I/O is minimal, with an IOPS of just 3.81, low throughput (248.71 kB/s), and an average IO depth of 0.01 — all indicating very low disk utilization. The 52% write merge rate and low latencies further suggest sequential, infrequent disk access, reinforcing that the workload is primarily memory-bound. + + +| Metric | Calculation Explanation | Value | +|--------------------|-------------------------------------------------------------------------------------------------------------|---------------| +| IOPS | Taken directly from the `tps` (transfers per second) field | 3.81 | +| Throughput (Read) | From monitoring tool output | 31.63 kB/s | +| Throughput (Write) | From monitoring tool output | 217.08 kB/s | +| Throughput (Total) | Sum of read and write throughput | 248.71 kB/s | +| Avg I/O Size | Total throughput divided by IOPS: 248.71 / 3.81 | ≈ 65.3 KB | +| Read Ratio | Read throughput ÷ total throughput: 31.63 / 248.71 | ~13% | +| Write Ratio | Write throughput ÷ total throughput: 217.08 / 248.71 | ~87% | +| IO Depth | Taken directly from `aqu-sz` (average number of in-flight I/Os) | 0.01 | +| Access Pattern | Based on cache hits, merge rates, and low wait times. 52% of writes were merged (`wrqm/s` = 3.17, `w/s` = 2.92) → suggests mostly sequential access | Sequential-ish (52.01% merged) | + + +{{% notice Please Note%}} +If you have access to the workloads source code, the expected access patterns can more easily be observed. +{{% /notice %}} diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md index 5944de1bc4..90b1d2cd2a 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md @@ -12,7 +12,7 @@ The ideal storage activity of your system is 0. In this situation all of your ap ## High-Level Flow of Data -The diagram below is a high-level overview of how data can be written or read from a storage device. +The diagram below is a high-level overview of how data can be written or read from a storage device. This diagram illustrates a multi-disk I/O architecture where each disk (Disk 1 to Disk N) has an I/O queue and optional disk cache, communicating with a central CPU via a disk controller. Memory is not explicitly shown but typically resides between the CPU and storage, offering faster access times than disk but is volatile. File systems, though not depicted, operate at the OS/kernel level to handling file access metadata and offer a familiar way to interact with files and directories. In NVMe-based systems, the disk controller is located on the drive itself, reducing latency and improving parallelism. ![disk i/o](./diskio.jpeg) @@ -20,30 +20,33 @@ Many techniques are transparent to a developer. The queue at the operating syste ## Key Terms -**Sectors and Blocks** +#### Sectors and Blocks Sectors are the basic physical units on a storage device. For instance, traditional hard drives typically use a sector size of 512 bytes, while many modern disks use 4096 bytes (or 4K sectors) to improve error correction and efficiency. Blocks are the logical grouping of one or more sectors used by filesystems for data organization. A common filesystem block size is 4096 bytes, meaning that each block might consist of 8 of the 512-byte sectors, or simply map directly to a 4096-byte physical sector layout if the disk supports it. -**Input Output Operations per second (IOPS)** -IOPS is a measure of how much traffic your storage system can manage. It is worth noting that IOPS can vary by block size depending on the storage medium (e.g., flash drives). +#### Input Output Operations per second (IOPS) +IOPS is a measure of how much random read or write requests your storage system can manage. It is worth noting that IOPS can vary by block size depending on the storage medium (e.g., flash drives). Importantly, traditional hard disk drives (HDDs) often don't specify the IOPS. For example the IOPS value for HDD volume on AWS is not shown. -**Throughput / Bandwidth** -Throughput is the data transfer rate normally in MB/s. IOPS x block size is the bandwidth utilisation of your system. +![iops_hdd](./IOPS.png) -**Queue Depth** +#### Throughput / Bandwidth +Throughput is the data transfer rate normally in MB/s. IOPS x block size is the bandwidth utilisation of your system. Max throughput is usually reached for sequential access patterns. + +#### Queue Depth Queue depth refers to the number of simultaneous I/O operations that can be pending on a device. Consumer SSDs might typically have a queue depth in the range of 32 to 64, whereas enterprise-class NVMe drives can support hundreds or even thousands of concurrent requests per queue. This parameter affects how much the device can parallelize operations and therefore influences overall I/O performance. -**I/O Schedule Engine** +#### I/O Schedule Engine The I/O engine is the software component within Linux responsible for managing I/O requests between applications and the storage subsystem. For example, in Linux, the kernel’s block I/O scheduler acts as an I/O engine by queuing and dispatching requests to device drivers. Schedulers use multiple queues to reorder requests optimal disk access. In benchmarking tools like fio, you might select I/O engines such as sync (synchronous I/O), `libaio` (Linux native asynchronous I/O library), or `io_uring` (which leverages newer Linux kernel capabilities for asynchronous I/O). -**Merging** +#### I/O Wait +This is the perceived time spent waiting for I/O to return the value from the perspective of the CPU core. +### Considerations for Cloud Storage -### Typical Storage Types for Cloud Instances +In a cloud environment, you are typically sharing a physical instance of a server with multiple tenants. As such the CPU cores and storage devices may be shared resulting in cloud service providers often provide an IOPS per GB. Where your system's storage performance is proportional to the block device size. See [this example](https://docs.oracle.com/en-us/iaas/Content/Block/Concepts/blockvolumeperformance.htm) for more information. -In a cloud environment, you are typically sharing a physical instance of a server with multiple tenants. As such the CPU cores and storage devices may be shared. As such, cloud service providers often provide an IOPS per GB. Where your system's storage performance is proportional to the block device size. See [this example](https://docs.oracle.com/en-us/iaas/Content/Block/Concepts/blockvolumeperformance.htm) for more information. \ No newline at end of file diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md index 64dca8dff7..e542fb754b 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md @@ -6,37 +6,36 @@ weight: 4 layout: learningpathall --- -## Install Fio +## Setup and Install Fio -Flexible I/O (fio) is a command-line tool to generate a synthetic workload with specific I/O characteristics. +I will be using the same `t4g.medium` instance from the previous section with 2 different types of SSD-based elastic block storage devices as per the console screenshot below. Both block devices have the same, 8GiB capacity but the `io1` type offers greater IOPS. In this section we want to observe what the real-world performance for our workload is so that it can inform our selection. -Fio is available through most Linux distribution packages. Please refer to the [documentation](https://github.com/axboe/fio) for the binary package availability. +![EBS](./EBS.png) + +Flexible I/O (fio) is a command-line tool to generate a synthetic workload with specific I/O characteristics. Fio is available through most Linux distribution packages. Please refer to the [documentation](https://github.com/axboe/fio) for the binary package availability. ```bash sudo apt update -sudo apt install fio gnuplot -y +sudo apt install fio -y ``` Confirm installation with the following commands. ```bash fio --version -gnuplot --version ``` ```output fio-3.36 -gnuplot 6.0 patchlevel 0 ``` ## Locate Device -We have the option to directly microbenchmark the block device or microbenchmark a mounted filesystem. Use the disk free, `df` command to see if the mounted storage devices connected to your server. We are excluding the temporary file system, `tmpfs` for clarity. +`Fio` allows us to microbenchmark either the block device or a mounted filesystem. The disk free, `df` command to confirm our EBS volumes are not mounted. ```bash df -hTx tmpfs ``` -The output below shows a 30GB ```output Filesystem Type Size Used Avail Use% Mounted on @@ -47,7 +46,7 @@ efivarfs efivarfs 128K 3.7K 125K 3% /sys/firmware/efi/efivars /dev/nvme0n1p15 vfat 98M 6.4M 92M 7% /boot/efi ``` -Using the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. +Using the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. `nvme1n1` corresponds to the faster `io2` block device and `nvme2n1` corresponds to the slower `gp3` block device. ```bash lsblk -e 7 @@ -62,22 +61,27 @@ nvme0n1 259:1 0 8G 0 disk └─nvme0n1p16 259:5 0 923M 0 part /boot nvme2n1 259:2 0 8G 0 disk ``` + +{{% notice Please Note%}} +If you have more than 1 block volumes attached to an instance, the `sudo nvme list` command from the `nvme-cli` package and be used to differentiate between volumes +{{% /notice %}} + We can use the `blkid` command to find the directory for `nvme1n1`. ## Generating a Synthetic Workload -Let us say we want to simulate an loggin system with the following characteristics observed using the tools from the previous section. +Let us say we want to simulate a fictional logging application with the following characteristics observed using the tools from the previous section. -*The logging workload has a light sequential write averaging 100 IOPS. The system write throughput is 5 MB/s with 83% writes. There are infrequent bursts of reads, operating at 1000 IOPS and 256MB/s. The workload can scale the infrequent reads and writes to use up to 16 threads each.* +{{% notice Workload%}} +The logging workload has a light sequential read and write workload. The system write throughput per thread is 5 MB/s with 83% writes. There are infrequent bursts of reads for approximately 5 seconds, operating at 16MB/s per thread. The workload can scale the infrequent reads and writes to use up to 16 threads each. The block size for the writes and reads are 64KiB and 256KiB respectively (as opposed to the standard 4KiB Page size). -The fio tool uses simple configuration `jobfiles` to describe the characterisics of your synthetic workload. A template `jobfile` is shown below. Parameters under the `[global]` option are shared among jobs. From the example above, we have created 2 jobs to represent the steady write and infrequent reads. +Further, the application is sensitive to latency and since it holds critical information, needs to write directly to non-volatile storage (directIO). +{{% /notice %}} -Please refer to the [documentation](https://fio.readthedocs.io/en/latest/fio_doc.html#job-file-format) for full details on the configuration file structure. +The fio tool uses simple configuration `jobfiles` to describe the characterisics of your synthetic workload. Parameters under the `[global]` option are shared among jobs. From the example below, we have created 2 jobs to represent the steady write and infrequent reads. Please refer to the official [documentation](https://fio.readthedocs.io/en/latest/fio_doc.html#job-file-format) for more details. Copy and paste the configuration file below into 2 files named `nvme.fio`. Replace the `` with the block devices we are comparing and just the `filename` parameter accordingly. -**Please note**: Do not write to a drive that contains critical information such as drives used for booting. We recommend writing to an unformatted block device or a mounted filesystem that you are OK to lose data from. - ```ini ; -- start job file including.fio -- [global] @@ -87,27 +91,22 @@ time_based runtime=30 group_reporting=1 log_avg_msec=1000 -filename=/dev/nvme1n1 ; or nvme1n1 - +rate=16m,5m ; limit to 16 MB/s and 5MB/s for read and write per job +numjobs=${NUM_JOBS} ; set at the command line +iodepth=${IO_DEPTH} ; set at the command line +filename=/dev/nvme1n1 ; or nvme2n1 [steady_write] name=steady_write -rw=write +rw=write ; sequential write bs=64k ; Block size of 64KiB (default block size of 4 KiB) -rate=5m ; limit to 5 MiB/s -numjobs=${NUM_JOBS} ; set at the command line -iodepth=${IO_DEPTH} - [burst_read] name=burst_read rw=read bs=256k ; adjust the block size to 64KiB writes (default is 4KiB) -rate=256m ; limit to 256 MiB/s -numjobs=${NUM_JOBS} -iodepth=${IO_DEPTH} startdelay=10 ; simulate infrequent reads (5 seconds out 30) -runtime=5s +runtime=5 ; -- end job file including.fio -- ``` @@ -122,38 +121,49 @@ Then ```bash sudo NUM_JOBS=16 IO_DEPTH=64 fio nvme2.fio ``` -The output from NVMe1 shows: + +### Interpreting Results ```output + +nvme1: + Run status group 0 (all jobs): - READ: bw=127MiB/s (133MB/s), 127MiB/s-127MiB/s (133MB/s-133MB/s), io=677MiB (710MB), run=5333-5333msec - WRITE: bw=80.0MiB/s (83.9MB/s), 80.0MiB/s-80.0MiB/s (83.9MB/s-83.9MB/s), io=2400MiB (2517MB), run=30003-30003msec + READ: bw=118MiB/s (124MB/s), 118MiB/s-118MiB/s (124MB/s-124MB/s), io=629MiB (660MB), run=5324-5324msec + WRITE: bw=80.0MiB/s (83.9MB/s), 80.0MiB/s-80.0MiB/s (83.9MB/s-83.9MB/s), io=2400MiB (2517MB), run=30006-30006msec Disk stats (read/write): - nvme1n1: ios=2805/38226, sectors=1391184/4892928, merge=0/0, ticks=188746/514998, in_queue=703745, util=88.53% -``` + nvme1n1: ios=2663/38225, sectors=1294480/4892800, merge=0/0, ticks=148524/454840, in_queue=603364, util=62.19% + +nvme2: -```output Run status group 0 (all jobs): - READ: bw=87.9MiB/s (92.1MB/s), 87.9MiB/s-87.9MiB/s (92.1MB/s-92.1MB/s), io=462MiB (484MB), run=5256-5256msec - WRITE: bw=59.5MiB/s (62.4MB/s), 59.5MiB/s-59.5MiB/s (62.4MB/s-62.4MB/s), io=1794MiB (1881MB), run=30128-30128msec + READ: bw=85.6MiB/s (89.8MB/s), 85.6MiB/s-85.6MiB/s (89.8MB/s-89.8MB/s), io=456MiB (478MB), run=5322-5322msec + WRITE: bw=60.3MiB/s (63.2MB/s), 60.3MiB/s-60.3MiB/s (63.2MB/s-63.2MB/s), io=1816MiB (1904MB), run=30119-30119msec Disk stats (read/write): - nvme2n1: ios=1896/28700, sectors=947760/3673600, merge=0/0, ticks=169728/1054671, in_queue=1224400, util=80.22% + nvme2n1: ios=1872/28855, sectors=935472/3693440, merge=0/0, ticks=159753/1025104, in_queue=1184857, util=89.83% ``` -More interestingly, the latency for each request is different. +Here we can see that the faster `io2` block storage (`nvme1`) is able to meet the throughput requirement of 80MB/s for steady writes when all 16 write threads are running (5MB/s per thread). However `gp2` saturates at 60.3 MiB/s with over 89.8% SSD utilisation. + +We are told the fictional logging application is sensitive to operation latency. The output belows highlights that over ~35% operations have a latency above 1s on nvme2 compared to ~7% on nvme1. ```output - lat (msec) : 2=57.80%, 4=0.15%, 10=0.34%, 20=1.46%, 50=3.50% - lat (msec) : 100=1.85%, 250=6.17%, 500=11.80%, 750=5.73%, 1000=2.79% - lat (msec) : 2000=4.41%, >=2000=4.01% -``` -With the `nvme2` device showing 20% of writes taking 2+ seconds to confirm a write, compared to the 4% with `nvme1`. These insights suggest that if this contrived logging system is latency sensitive, the `nvme2` can offer better performance. + nvme2: -```output - lat (msec) : 2=30.37%, 4=0.07%, 10=0.37%, 20=0.14%, 50=0.07% - lat (msec) : 100=0.46%, 250=1.89%, 500=6.17%, 750=9.99%, 1000=9.27% - lat (msec) : 2000=20.61%, >=2000=9.02% + lat (usec) : 10=0.01%, 500=1.53%, 750=5.13%, 1000=7.55% + lat (msec) : 2=29.49%, 4=0.89%, 10=0.09%, 20=0.02%, 50=0.21% + lat (msec) : 100=0.56%, 250=1.84%, 500=6.39%, 750=9.76%, 1000=10.17% + lat (msec) : 2000=19.59%, >=2000=6.77% + + nvme1: + + lat (usec) : 750=0.44%, 1000=0.41% + lat (msec) : 2=62.63%, 4=1.12%, 10=0.34%, 20=1.61%, 50=3.91% + lat (msec) : 100=2.34%, 250=5.91%, 500=8.46%, 750=4.33%, 1000=2.50% + lat (msec) : 2000=3.62%, >=2000=2.38% ``` + +The insights gathered by microbenchmarking with fio above can lead to more informed decisions about which block storage to connect to your Arm-based instance. From f9157a3b2077dbe99b9da73aa5875038eb9de5af Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 17 Apr 2025 11:17:50 +0100 Subject: [PATCH 055/122] remove df-h command as not necessary for LP --- .../disk-io-benchmark/using-fio.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md index e542fb754b..c72bac07f8 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md @@ -8,7 +8,7 @@ layout: learningpathall ## Setup and Install Fio -I will be using the same `t4g.medium` instance from the previous section with 2 different types of SSD-based elastic block storage devices as per the console screenshot below. Both block devices have the same, 8GiB capacity but the `io1` type offers greater IOPS. In this section we want to observe what the real-world performance for our workload is so that it can inform our selection. +I will be using the same `t4g.medium` instance from the previous section with 2 different types of SSD-based block storage devices as per the console screenshot below. Both block devices have the same, 8GiB capacity but the `io1` is geared towards I/O as opposed to the general purpose SSD `gp2`. In this section we want to observe what the real-world performance for our workload is so that it can inform our selection. ![EBS](./EBS.png) @@ -33,19 +33,6 @@ fio-3.36 `Fio` allows us to microbenchmark either the block device or a mounted filesystem. The disk free, `df` command to confirm our EBS volumes are not mounted. -```bash -df -hTx tmpfs -``` - -```output -Filesystem Type Size Used Avail Use% Mounted on -Filesystem Type Size Used Avail Use% Mounted on -/dev/root ext4 6.8G 2.8G 4.0G 41% / -efivarfs efivarfs 128K 3.7K 125K 3% /sys/firmware/efi/efivars -/dev/nvme0n1p16 ext4 891M 57M 772M 7% /boot -/dev/nvme0n1p15 vfat 98M 6.4M 92M 7% /boot/efi -``` - Using the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. `nvme1n1` corresponds to the faster `io2` block device and `nvme2n1` corresponds to the slower `gp3` block device. ```bash @@ -66,8 +53,6 @@ nvme2n1 259:2 0 8G 0 disk If you have more than 1 block volumes attached to an instance, the `sudo nvme list` command from the `nvme-cli` package and be used to differentiate between volumes {{% /notice %}} -We can use the `blkid` command to find the directory for `nvme1n1`. - ## Generating a Synthetic Workload Let us say we want to simulate a fictional logging application with the following characteristics observed using the tools from the previous section. From 6e722fbdb49dfecd9bb8770ffbbaec2ce4db30d1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 17 Apr 2025 11:21:15 +0100 Subject: [PATCH 056/122] removed cloud considerations to simplify LP --- .../disk-io-benchmark/introduction.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md index 90b1d2cd2a..697690922a 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md @@ -45,8 +45,3 @@ In benchmarking tools like fio, you might select I/O engines such as sync (synch #### I/O Wait This is the perceived time spent waiting for I/O to return the value from the perspective of the CPU core. - -### Considerations for Cloud Storage - -In a cloud environment, you are typically sharing a physical instance of a server with multiple tenants. As such the CPU cores and storage devices may be shared resulting in cloud service providers often provide an IOPS per GB. Where your system's storage performance is proportional to the block device size. See [this example](https://docs.oracle.com/en-us/iaas/Content/Block/Concepts/blockvolumeperformance.htm) for more information. - From 228250b1d177b7c21581ba93702da81d8f8b638e Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 17 Apr 2025 11:41:17 +0100 Subject: [PATCH 057/122] added tip for visualisation --- .../disk-io-benchmark/using-fio.md | 16 ++++++++++++++-- .../disk-io-benchmark/visualisations.png | Bin 0 -> 15379 bytes 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/visualisations.png diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md index c72bac07f8..efad4d0867 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md @@ -12,7 +12,7 @@ I will be using the same `t4g.medium` instance from the previous section with 2 ![EBS](./EBS.png) -Flexible I/O (fio) is a command-line tool to generate a synthetic workload with specific I/O characteristics. Fio is available through most Linux distribution packages. Please refer to the [documentation](https://github.com/axboe/fio) for the binary package availability. +Flexible I/O (fio) is a command-line tool to generate a synthetic workload with specific I/O characteristics. This serves as a simpler alternative to full record and replay testing. Fio is available through most Linux distribution packages, please refer to the [documentation](https://github.com/axboe/fio) for the binary package availability. ```bash sudo apt update @@ -109,8 +109,9 @@ sudo NUM_JOBS=16 IO_DEPTH=64 fio nvme2.fio ### Interpreting Results -```output +The final terminal output from both runs are shown below. +```output nvme1: Run status group 0 (all jobs): @@ -134,6 +135,7 @@ Here we can see that the faster `io2` block storage (`nvme1`) is able to meet th We are told the fictional logging application is sensitive to operation latency. The output belows highlights that over ~35% operations have a latency above 1s on nvme2 compared to ~7% on nvme1. + ```output nvme2: @@ -151,4 +153,14 @@ We are told the fictional logging application is sensitive to operation latency. lat (msec) : 2000=3.62%, >=2000=2.38% ``` +This insights above suggest the SSD designed for throughput, `io2` is more suitable than the general purpose `gp2` storage to meet the requirements of our logging application. + +{{% notice Tip%}} +If the text output is hard to follow, you can use the `fio2gnuplot` package to plot the data graphically or use the visualisations available from the cloud service provider's dashboard. See image below for an example. + + ![plot](./visualisations.png) +{{% /notice %}} + The insights gathered by microbenchmarking with fio above can lead to more informed decisions about which block storage to connect to your Arm-based instance. + + diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/visualisations.png b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/visualisations.png new file mode 100644 index 0000000000000000000000000000000000000000..20f83e8d72db6806b83a0cd5a59e0512484fa1fe GIT binary patch literal 15379 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%0D0hXGRzvi(|qf7+#R)l8b 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 Date: Thu, 17 Apr 2025 12:04:09 +0100 Subject: [PATCH 058/122] final review --- .../disk-io-benchmark/_index.md | 2 +- .../disk-io-benchmark/characterising-workload.md | 8 ++++---- .../disk-io-benchmark/introduction.md | 7 +++---- .../disk-io-benchmark/using-fio.md | 10 +++++----- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md index 8ac495d0ad..28b4d145e2 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md @@ -6,7 +6,7 @@ minutes_to_complete: 30 who_is_this_for: A cloud developer who wants to optimise storage cost or performance of their application. Developers who want to uncover potential storage-bound bottlenecks or changes when migrating an application to a different platform. learning_objectives: - - Understand basic data flow for storage devices + - Understand the flow of data for storage devices - Use basic observability utilities such as iostat, iotop and pidstat - Understand how to run fio for microbenchmarking a block storage device diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md index bc3061a571..06116a2a22 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md @@ -16,7 +16,7 @@ The basic attributes of a given workload are the following. - Read to Write Ratio - Random vs Sequential access -The characteristics of many real-world workloads will vary over time, for example an application that periodically flushes writes to disk. Further, the system-wide and process-specific characteristics may be significantly differently. +There are many more characteristics to observe, just as latency but since this is an introductory topic we will mostly stick to the high-level metrics listed above. ## Running an Example Workload @@ -38,7 +38,7 @@ cd src wget http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 ``` -Run the following command to begin transcoding the video and audio using the `H.264` and `aac` transcoders respectively. We use the `-flush_packets` flag to write each chunk of video back to storage. +Run the following command to begin transcoding the video and audio using the `H.264` and `aac` transcoders respectively. We use the `-flush_packets` flag to write each chunk of video back to storage from memory. ```bash ffmpeg -i BigBuckBunny.mp4 -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k -flush_packets 1 output_video.mp4 @@ -51,7 +51,7 @@ Whilst the transcoding is running, we can use the `pidstat` command to see the d ```bash pidstat -d -p $(pgrep ffmpeg) 1 ``` -Since this example `151MB` video fits within memory, we observe no `kB_rd/s` for the storage device. However, since we are flushing to storage we observe ~275 `kB_wr/s` for this specific process. +Since this example `151MB` video fits within memory, we observe no `kB_rd/s` for the storage device after the initial read. However, since we are flushing to storage we observe period ~275 `kB_wr/s`. ```output Linux 6.8.0-1024-aws (ip-10-248-213-118) 04/15/25 _aarch64_ (2 CPU) @@ -111,7 +111,7 @@ nvme0n1 0.66 29.64 0.24 26.27 0.73 44.80 2.92 203. ### Basic Characteristics of our Example Workload -This is a simple transcoding workload with flushed writes, where most data is processed and stored in memory. Disk I/O is minimal, with an IOPS of just 3.81, low throughput (248.71 kB/s), and an average IO depth of 0.01 — all indicating very low disk utilization. The 52% write merge rate and low latencies further suggest sequential, infrequent disk access, reinforcing that the workload is primarily memory-bound. +This is a simple transcoding workload with flushed writes, where most data is processed and stored in memory. Disk I/O is minimal, with an IOPS of just 3.81, low throughput (248.71 kB/s), and an average IO depth of 0.01 — all summarised in very low disk utilization. The 52% write merge rate and low latencies further suggest sequential, infrequent disk access, reinforcing that the workload is primarily memory-bound. | Metric | Calculation Explanation | Value | diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md index 697690922a..08a9a873d5 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md @@ -8,15 +8,14 @@ layout: learningpathall ## Introduction -The ideal storage activity of your system is 0. In this situation all of your applications data and instructions are available in memory or caches and no reads or writes to a spinning hard-disk drive or solid-state SSD are required. However, due to physical capacity limitations, data volatility and need to store large amounts of data, many applications require frequent access to storage media. +The ideal storage activity of your system is 0. In this situation all of your application data and instructions are available in memory or caches with no reads or writes to a spinning hard-disk drive or solid-state SSD required. However, due to physical capacity limitations, data volatility and need to store large amounts of data, many applications require frequent access to storage media. ## High-Level Flow of Data -The diagram below is a high-level overview of how data can be written or read from a storage device. This diagram illustrates a multi-disk I/O architecture where each disk (Disk 1 to Disk N) has an I/O queue and optional disk cache, communicating with a central CPU via a disk controller. Memory is not explicitly shown but typically resides between the CPU and storage, offering faster access times than disk but is volatile. File systems, though not depicted, operate at the OS/kernel level to handling file access metadata and offer a familiar way to interact with files and directories. In NVMe-based systems, the disk controller is located on the drive itself, reducing latency and improving parallelism. +The diagram below is a high-level overview of how data can be written or read from a storage device. This diagram illustrates a multi-disk I/O architecture where each disk (Disk 1 to Disk N) has an I/O queue and optional disk cache, communicating with a central CPU via a disk controller. Memory is not explicitly shown but resides between the CPU and storage, offering fast access times with the tradeoff of volatile. File systems, though not depicted, operate at the OS/kernel level to handling file access metadata and offer a friendly way to interact through files and directories. ![disk i/o](./diskio.jpeg) -Many techniques are transparent to a developer. The queue at the operating system level and disk level may reorder I/O requests to improve efficiency (for example an atomic write that increments a value by 1 followed by a minus by 1). ## Key Terms @@ -32,7 +31,7 @@ IOPS is a measure of how much random read or write requests your storage system ![iops_hdd](./IOPS.png) #### Throughput / Bandwidth -Throughput is the data transfer rate normally in MB/s. IOPS x block size is the bandwidth utilisation of your system. Max throughput is usually reached for sequential access patterns. +Throughput is the data transfer rate normally in MB/s with bandwidth specifying the maximum amount that a connection can transfer. IOPS x block size can be used to calculate the storage throughput of your application. #### Queue Depth Queue depth refers to the number of simultaneous I/O operations that can be pending on a device. Consumer SSDs might typically have a queue depth in the range of 32 to 64, whereas enterprise-class NVMe drives can support hundreds or even thousands of concurrent requests per queue. This parameter affects how much the device can parallelize operations and therefore influences overall I/O performance. diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md index efad4d0867..bc50025ac4 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md @@ -8,7 +8,7 @@ layout: learningpathall ## Setup and Install Fio -I will be using the same `t4g.medium` instance from the previous section with 2 different types of SSD-based block storage devices as per the console screenshot below. Both block devices have the same, 8GiB capacity but the `io1` is geared towards I/O as opposed to the general purpose SSD `gp2`. In this section we want to observe what the real-world performance for our workload is so that it can inform our selection. +I will be using the same `t4g.medium` instance from the previous section with 2 different types of SSD-based block storage devices as per the console screenshot below. Both block devices have the same, 8GiB capacity but the `io1` is geared towards throughput as opposed to the general purpose SSD `gp2`. In this section we want to observe what the real-world performance for our workload is so that it can inform our selection. ![EBS](./EBS.png) @@ -31,9 +31,9 @@ fio-3.36 ## Locate Device -`Fio` allows us to microbenchmark either the block device or a mounted filesystem. The disk free, `df` command to confirm our EBS volumes are not mounted. +`Fio` allows us to microbenchmark either the block device or a mounted filesystem. The disk free, `df` command to confirm our EBS volumes are not mounted. Writing to drives that hold critical information may cause issues. Hence we are writing to blank, unmounted block storage device. -Using the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. `nvme1n1` corresponds to the faster `io2` block device and `nvme2n1` corresponds to the slower `gp3` block device. +Using the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. `nvme1n1` corresponds to the faster `io2` block device and `nvme2n1` corresponds to the slower `gp2` block device. ```bash lsblk -e 7 @@ -58,9 +58,9 @@ If you have more than 1 block volumes attached to an instance, the `sudo nvme li Let us say we want to simulate a fictional logging application with the following characteristics observed using the tools from the previous section. {{% notice Workload%}} -The logging workload has a light sequential read and write workload. The system write throughput per thread is 5 MB/s with 83% writes. There are infrequent bursts of reads for approximately 5 seconds, operating at 16MB/s per thread. The workload can scale the infrequent reads and writes to use up to 16 threads each. The block size for the writes and reads are 64KiB and 256KiB respectively (as opposed to the standard 4KiB Page size). +The logging workload has light sequential read and write characteristics. The system write throughput per thread is 5 MB/s with 83% writes. There are infrequent bursts of reads for approximately 5 seconds, operating at up to 16MB/s per thread. The workload can scale the infrequent reads and writes to use up to 16 threads each. The block size for the writes and reads are 64KiB and 256KiB respectively (as opposed to the standard 4KiB Page size). -Further, the application is sensitive to latency and since it holds critical information, needs to write directly to non-volatile storage (directIO). +Further, the application latency sensitive and given it holds critical information, needs to write directly to non-volatile storage through direct IO. {{% /notice %}} The fio tool uses simple configuration `jobfiles` to describe the characterisics of your synthetic workload. Parameters under the `[global]` option are shared among jobs. From the example below, we have created 2 jobs to represent the steady write and infrequent reads. Please refer to the official [documentation](https://fio.readthedocs.io/en/latest/fio_doc.html#job-file-format) for more details. From 6ae7779ecd48c8460fa519565563a5a4e69d80c5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Stats Bot <> Date: Mon, 21 Apr 2025 02:42:26 +0000 Subject: [PATCH 059/122] automatic update of stats files --- data/stats_weekly_data.yml | 102 +++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/data/stats_weekly_data.yml b/data/stats_weekly_data.yml index 5d20a2b1bf..ed68951890 100644 --- a/data/stats_weekly_data.yml +++ b/data/stats_weekly_data.yml @@ -5694,3 +5694,105 @@ avg_close_time_hrs: 0 num_issues: 16 percent_closed_vs_total: 0.0 +- a_date: '2025-04-21' + content: + automotive: 1 + cross-platform: 30 + embedded-and-microcontrollers: 41 + install-guides: 96 + iot: 5 + laptops-and-desktops: 35 + mobile-graphics-and-gaming: 29 + servers-and-cloud-computing: 111 + total: 348 + contributions: + external: 91 + internal: 458 + github_engagement: + num_forks: 30 + num_prs: 11 + individual_authors: + adnan-alsinan: 1 + alaaeddine-chakroun: 2 + albin-bernhardsson: 1 + alex-su: 1 + alexandros-lamprineas: 1 + andrew-choi: 1 + annie-tallund: 3 + arm: 3 + arnaud-de-grandmaison: 3 + avin-zarlez: 1 + basma-el-gaabouri: 1 + ben-clark: 1 + bolt-liu: 2 + brenda-strech: 1 + chaodong-gong: 1 + chen-zhang: 1 + christopher-seidl: 7 + cyril-rohr: 1 + daniel-gubay: 1 + daniel-nguyen: 2 + david-spickett: 2 + dawid-borycki: 32 + diego-russo: 2 + dominica-abena-o.-amanfo: 1 + elham-harirpoush: 2 + florent-lebeau: 5 + "fr\xE9d\xE9ric--lefred--descamps": 2 + gabriel-peterson: 5 + gayathri-narayana-yegna-narayanan: 1 + georgios-mermigkis: 1 + geremy-cohen: 1 + graham-woodward: 1 + han-yin: 1 + iago-calvo-lista: 1 + james-whitaker: 1 + jason-andrews: 98 + joe-stech: 4 + johanna-skinnider: 2 + jonathan-davies: 2 + jose-emilio-munoz-lopez: 1 + julie-gaskin: 5 + julio-suarez: 6 + kasper-mecklenburg: 1 + kieran-hejmadi: 6 + koki-mitsunami: 2 + konstantinos-margaritis: 9 + kristof-beyls: 1 + leandro-nunes: 1 + liliya-wu: 1 + masoud-koleini: 1 + mathias-brossard: 1 + michael-hall: 5 + na-li: 1 + nader-zouaoui: 2 + nikhil-gupta: 1 + nobel-chowdary-mandepudi: 4 + odin-shen: 4 + owen-wu: 2 + pareena-verma: 42 + paul-howard: 2 + pranay-bakre: 5 + preema-merlin-dsouza: 1 + przemyslaw-wirkus: 2 + rani-chowdary-mandepudi: 1 + ravi-malhotra: 1 + rin-dobrescu: 1 + roberto-lopez-mendez: 2 + ronan-synnott: 46 + shuheng-deng: 1 + thirdai: 1 + tianyu-li: 1 + tom-pilar: 1 + uma-ramalingam: 1 + varun-chari: 2 + visualsilicon: 1 + willen-yang: 1 + ying-yu: 2 + yiyang-fan: 1 + zach-lasiuk: 2 + zhengjun-xing: 2 + issues: + avg_close_time_hrs: 0 + num_issues: 16 + percent_closed_vs_total: 0.0 From 0302b0ad9b3be50d29e9c1411d5d24aa0bd5797b Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Mon, 21 Apr 2025 11:18:56 -0500 Subject: [PATCH 060/122] Tech review of GitHub Arm-hosted runners. --- .../gh-arm-runners-win/_index.md | 10 +++++-- .../gh-arm-runners-win/automate-win-app.md | 28 +++++++++++++------ .../gh-arm-runners-win/introduction.md | 25 ++++++++++------- .../windows_armpl/_index.md | 1 + 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md index 5b6bcc50ba..7cc1f85e89 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md @@ -1,6 +1,10 @@ --- title: Automate Windows on Arm Builds with GitHub Arm-hosted Runners +draft: true +cascade: + draft: true + minutes_to_complete: 20 who_is_this_for: This is an introductory tutorial for software developers interested in automating Windows application builds on Arm architecture using GitHub Actions. @@ -11,14 +15,14 @@ learning_objectives: - Automate the build process of Windows applications using GitHub Actions. prerequisites: - - A GitHub account - - Basic knowledge of GitHub Actions + - A GitHub account. + - Basic knowledge of GitHub Actions. author: - Pareena Verma ### Tags -skilllevels: Introducory +skilllevels: Introductory subjects: CI-CD armips: - Cortex-A diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md index 881f67555b..827ab84cc9 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md @@ -15,7 +15,7 @@ A basic overview of the application is provided here but for details on building The source code for the application that renders a rotating 3D cube to perform the calculations using different programming options is provided in this GitHub repository. ```console -https://github.com/odincodeshen/SpinTheCubeInGDI.git +https://github.com/arm/SpinTheCubeInGDI ``` The application implements a spinning cube and consists of four key components: @@ -26,21 +26,21 @@ The application implements a spinning cube and consists of four key components: The code has two options to calculate the rotation: -1. Multithreading: the application utilizes multithreading to improve performance by distributing the rotation calculations across multiple threads. -2. Arm Performance Libraries: the application utilizes optimized math library functions for the rotation calculations. +1. Multithreading: the application uses multithreading to improve performance by distributing the rotation calculations across multiple threads. +2. Arm Performance Libraries: the application uses optimized math library functions for the rotation calculations. -You will learn how to automate the build process for this application by using GitHub Actions that leverages the Arm-hosted Windows runners. +You will learn how to automate the build process for this application by using GitHub Actions with Arm-hosted Windows runners. ### Automate the Build Process -This GitHub Actions workflow [`msbuild.yml`](https://github.com/odincodeshen/SpinTheCubeInGDI/blob/main/.github/workflows/msbuild.yml) that automates the build process for this project using MSBuild for Windows on Arm is included in this repository. +The [GitHub Actions workflow `msbuild.yml`](https://github.com/arm/SpinTheCubeInGDI/blob/main/.github/workflows/msbuild.yml) that automates the build process using MSBuild for Windows on Arm is included in the repository. Below is an explanation of the steps in the workflow: - **Trigger Events**: The workflow runs when there is a push or pull_request event on the main branch. + **Trigger Events**: The workflow runs when there is a push or pull request event on the main branch. - **Job Definition**: A single job named build is defined. It runs on the GitHub Arm-hosted Windows runner (`windows-11-arm`) as shown: + **Job Definition**: A single job named `build` is defined. It runs on the GitHub Arm-hosted Windows runner (`windows-11-arm`) as shown: ```console jobs: @@ -65,7 +65,17 @@ jobs: **Upload Build Artifact**: Uploads the built executable as an artifact using `actions/upload-artifact@v4`. -This workflow automates the process of dependency management, environment setup, building your Windows on Arm project, and storing the final artifact all using GitHub Arm-hosted runner. You can view the `Actions` logs on the repository for each step. +This workflow automates the process of dependency management, environment setup, building the project, and storing the final artifact all using a GitHub Arm-hosted Windows runner. + +### Fork the repository and run the workflow + +To run the workflow, you can fork the repository and run the workflow in your GitHub account. + +To fork the repository, go to the repository page on GitHub and click the `Fork` button in the top right corner. This will create a copy of the repository under your own GitHub account. + +You can then run the workflow in your forked repository by navigating to the `Actions` tab and selecting the MSBuild workflow, then clicking `Run workflow`. + +You can view the `Actions` logs in the repository for each step. ![action #center](_images/actions.png) -You have successfully built your Windows application and uploaded the application as an artifact of your workflow using the GitHub Arm-hosted Windows runner. +You have learned how to build a Windows application and upload the result as an artifact of your workflow using the GitHub Arm-hosted Windows runner. diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md index 5a4c4cdcb8..61a22c08f3 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md @@ -6,7 +6,7 @@ weight: 2 ### FIXED, DO NOT MODIFY layout: learningpathall --- -GitHub Arm-hosted runners for Windows are now available in public preview, providing a powerful and efficient way to build, test and deploy your Windows applications on Arm without the need for virtualization. These runners come with a Windows 11 Desktop image, equipped with many tools to get started with your workflows. +GitHub Arm-hosted runners for Windows are now available in public preview, providing a powerful and efficient way to build, test, and deploy your Windows applications on Arm without the need for virtualization. These runners come with a Windows 11 Desktop image, equipped with many tools to get started with your workflows. ### What are GitHub Arm-hosted Runners? @@ -16,17 +16,22 @@ Arm-hosted runners are available for public repositories at no cost, subject to ### How to use GitHub Arm-hosted Windows Runners? -To leverage the GitHub Windows Arm64 hosted runners, you will need to use the following label in your public repository workflow runs: +To leverage the GitHub Windows Arm64 hosted runners, you need to use the following label in your public repository workflow runs: ```yaml runs-on: windows-11-arm ``` -Please note that this label will not work in private repositories, the workflow will fail if you add it. +{{% notice Note %}} +This label will not work in private repositories, the workflow will fail if you use it. +{{% /notice %}} -Two types of GitHub-hosted runners are available; standard runners, and larger runners. Larger runners are differentiated from standard runners because users can control the amount of RAM, the number of CPUs, and configure the allocated disk space. You can also use the Windows 11 Desktop Arm Image with larger runners. To use this image on large runners, you will need to create a new runner. -### How can I create an GitHub Arm-hosted Windows large runner? +### What types of GitHub-hosted runners are available? + +Two types of GitHub-hosted runners are available; standard runners and larger runners. Larger runners are differentiated from standard runners because you can control the amount of RAM, the number of CPUs, and configure the allocated disk space. You can also use the Windows 11 Desktop Arm Image with larger runners. To use this image on larger runners, you will need to create a new runner. + +### How can I create an GitHub Arm-hosted Windows larger runner? Arm-hosted runners are created at the organization level. @@ -36,7 +41,6 @@ On the `Runners` page, select the `New runner` drop-down on the top right, and t ![new-runner #center](_images/new-runner.png) - Specify a name for the runner, this is the `runs-on` field in your workflows so make the name clear for others who use it. Choose Windows ARM64 for the platform and click `Save`. @@ -47,7 +51,7 @@ Specify the operating system image for the runner, select `Microsoft Windows 11 ![image #center](_images/win_image.png) -Select the size of the large runner you need and and click `Save`. +Select the size of the larger runner you need and click `Save`. ![specifications #center](_images/size.png) @@ -59,9 +63,10 @@ You can also set the runner group for this runner. The runner group controls the Finally, click `Create runner`. -Your new Arm-hosted runner Windows large runner is ready to use. You will need to remember the runner name for use in your workflows with the `runs-on` label. +Your new Arm-hosted Windows larger runner is ready to use. Remember the runner name so you can use it in your workflows with the `runs-on` label. + +### How do I check the server hardware used by the Arm-hosted Windows Runners? -### Check the server hardware used by the Arm-hosted Windows Runners The Arm-hosted runners are powered by Microsoft Azure Cobalt 100 processors, based on the Arm Neoverse N2, with 4 vCPUs and Armv9-A features, including Scalable Vector Extension 2 (SVE2). The output from running the `wmic` command on the Arm-hosted runner is below. @@ -69,7 +74,7 @@ The output from running the `wmic` command on the Arm-hosted runner is below. ![Arm-hosted runner info #center](_images/cpuinfo.png) -### Check Installed Software on the Arm-hosted Windows Runners +### How can I find the software installed on the Arm-hosted Windows Runners? To find out more about the software installed on the Arm-hosted runners, visit the [GitHub Actions Partner Images repository](https://github.com/actions/partner-runner-images/). Check the [image documentation](https://github.com/actions/partner-runner-images/blob/main/images/arm-windows-11-image.md) for a list of software packages installed on the Windows 11 by Arm Limited image. You can also use this repository to report issues or request additional software. diff --git a/content/learning-paths/laptops-and-desktops/windows_armpl/_index.md b/content/learning-paths/laptops-and-desktops/windows_armpl/_index.md index 4d7fffb5e8..8c5f809893 100644 --- a/content/learning-paths/laptops-and-desktops/windows_armpl/_index.md +++ b/content/learning-paths/laptops-and-desktops/windows_armpl/_index.md @@ -23,6 +23,7 @@ tools_software_languages: - Visual Studio - C# - .NET + - Arm Performance Libraries operatingsystems: - Windows From 87be06c18ef41198bee60919ca57e403a20f88a3 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Mon, 21 Apr 2025 15:27:54 -0500 Subject: [PATCH 061/122] Use GitHub Copilot to write NEON intrinsics --- .../cross-platform/adler32/_index.md | 60 ++ .../cross-platform/adler32/_next-steps.md | 8 + .../cross-platform/adler32/about-2.md | 52 ++ .../cross-platform/adler32/build-6.md | 62 ++ .../cross-platform/adler32/makefile-5.md | 114 ++++ .../cross-platform/adler32/more-11.md | 18 + .../cross-platform/adler32/neon-7.md | 558 ++++++++++++++++++ .../cross-platform/adler32/neon-debug-9.md | 99 ++++ .../cross-platform/adler32/neon-run-8.md | 128 ++++ .../cross-platform/adler32/simple-c-3.md | 196 ++++++ .../cross-platform/adler32/summary-10.md | 98 +++ .../cross-platform/adler32/test-prog-4.md | 247 ++++++++ 12 files changed, 1640 insertions(+) create mode 100644 content/learning-paths/cross-platform/adler32/_index.md create mode 100644 content/learning-paths/cross-platform/adler32/_next-steps.md create mode 100644 content/learning-paths/cross-platform/adler32/about-2.md create mode 100644 content/learning-paths/cross-platform/adler32/build-6.md create mode 100644 content/learning-paths/cross-platform/adler32/makefile-5.md create mode 100644 content/learning-paths/cross-platform/adler32/more-11.md create mode 100644 content/learning-paths/cross-platform/adler32/neon-7.md create mode 100644 content/learning-paths/cross-platform/adler32/neon-debug-9.md create mode 100644 content/learning-paths/cross-platform/adler32/neon-run-8.md create mode 100644 content/learning-paths/cross-platform/adler32/simple-c-3.md create mode 100644 content/learning-paths/cross-platform/adler32/summary-10.md create mode 100644 content/learning-paths/cross-platform/adler32/test-prog-4.md diff --git a/content/learning-paths/cross-platform/adler32/_index.md b/content/learning-paths/cross-platform/adler32/_index.md new file mode 100644 index 0000000000..e5f83f07c4 --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/_index.md @@ -0,0 +1,60 @@ +--- +title: Write NEON intrinsics using GitHub Copilot to improve Adler32 performance + +draft: true +cascade: + draft: true + +minutes_to_complete: 45 + +who_is_this_for: This is an introductory topic for C/C++ developers who are interested in using GitHub Copilot to improve performance using NEON intrinsics. + +learning_objectives: + - Use GitHub Copilot to write NEON intrinsics to improve performance of the Adler32 checksum algorithm. + +prerequisites: + - An Arm computer running Linux with the GNU compiler (gcc) installed. + - VS Code with GitHub Copilot installed. + +author: Jason Andrews + +### Tags +skilllevels: Introductory +subjects: Performance and Architecture +armips: + - Neoverse + - Cortex-A +tools_software_languages: + - GCC + - Runbook + +operatingsystems: + - Linux +shared_path: true +shared_between: + - servers-and-cloud-computing + - laptops-and-desktops + - mobile-graphics-and-gaming + + +further_reading: + - resource: + title: Arm C Language Extensions + link: https://arm-software.github.io/acle/ + type: Documentation + - resource: + title: Adler-32 Checksum Algorithm + link: https://en.wikipedia.org/wiki/Adler-32 + type: Article + - resource: + title: NEON Programming Quick Reference + link: https://developer.arm.com/documentation/den0018/a + type: Documentation + + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/cross-platform/adler32/_next-steps.md b/content/learning-paths/cross-platform/adler32/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/cross-platform/adler32/about-2.md b/content/learning-paths/cross-platform/adler32/about-2.md new file mode 100644 index 0000000000..04fd4fcc0e --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/about-2.md @@ -0,0 +1,52 @@ +--- +title: About NEON and Adler32 +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Introduction + +In computing, optimizing performance is crucial for applications that process large amounts of data. This Learning Path focuses on implementing and optimizing the Adler32 checksum algorithm using Arm advanced SIMD (Single Instruction, Multiple Data) capabilities. You'll learn how to leverage GitHub Copilot to simplify the development process while achieving significant performance improvements. + +## Simplifying Arm NEON Development with GitHub Copilot + +Developers recognize that Arm NEON SIMD instructions can significantly boost performance for computationally intensive applications, particularly in areas like image processing, audio/video codecs, and machine learning. However, writing NEON intrinsics directly requires specialized knowledge of the instruction set, careful consideration of data alignment, and complex vector operations that can be error-prone and time-consuming. Many developers avoid implementing these optimizations due to the steep learning curve and development overhead. + +The good news is that AI developer tools such as GitHub Copilot make working with NEON intrinsics much more accessible. By providing intelligent code suggestions, automated vectorization hints, and contextual examples tailored to your specific use case, GitHub Copilot can help bridge the knowledge gap and accelerate the development of NEON-optimized code. This allows developers to harness the full performance potential of Arm processors without the traditional complexity and time-consuming effort. + +Writing NEON intrinsics with GitHub Copilot can be demonstrated by creating a complete project from scratch, and comparing the C implementation with the NEON implementation. + +While you may not create complete projects from scratch, and you shouldn't blindly trust the generated code, it's helpful to see what's possible using an example so you can apply the principles to your own projects. + +## Accelerating Adler32 Checksum with Arm NEON Instructions + +This project demonstrates how to significantly improve the performance of Adler32 checksum calculations using Arm NEON instructions. + +### What is Arm NEON? + +Arm NEON is an advanced SIMD architecture extension for Arm processors. It provides a set of instructions that can process multiple data elements in parallel using specialized vector registers. NEON technology enables developers to accelerate computationally intensive algorithms by performing the same operation on multiple data points simultaneously, rather than processing them one at a time. This parallelism is particularly valuable for multimedia processing, scientific calculations, and cryptographic operations where the same operation needs to be applied to large datasets. + +## What is Adler32? + +Adler32 is a checksum algorithm that was invented by Mark Adler in 1995. It's used in the zlib compression library and is faster than CRC32 but provides less reliable error detection. + +The algorithm works by calculating two 16-bit sums: + +- s1: A simple sum of all bytes +- s2: A sum of all s1 values after each byte +- The final checksum is (s2 << 16) | s1. + +## Project Overview + +This project explains how you can use GitHub Copilot to create everything listed below: + +- Standard C implementation of Adler32 +- Test program to confirm Adler32 works correctly for inputs of various sizes +- Makefile to build and run the program +- Performance measurement code to record how long the algorithm takes +- NEON version of Adler32 to increase performance +- Tables showing performance comparison between the standard C version and the NEON version + +Continue to the next section to start creating the project. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/build-6.md b/content/learning-paths/cross-platform/adler32/build-6.md new file mode 100644 index 0000000000..d0fac641c2 --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/build-6.md @@ -0,0 +1,62 @@ +--- +title: Build and run the test program +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I test the build and run? + +The required files are now complete to test the Adler32 algorithm. +- Adler32 C function +- Test program to call the Adler32 function to test for correctness and measure performance +- Makefile to build and run + +Copy the information below to your GitHub Copilot Agent session: + +```console +Use the Makefile to build the project and run to make sure the checksum results are correct for all data sizes. +``` + +The output is: + +```output + +GitHub Copilot: I'll help you build and run the Adler-32 checksum project to verify the results. Let me do this step-by-step. + +First, let's build the project using the provided Makefile:Great! The build completed successfully without any errors or warnings. The Makefile compiled both source files (`adler32-simple.c` and adler32-test.c) and linked them into the executable adler32-test. + +Now let's run the tests to verify that the Adler-32 checksum implementation works correctly for all data sizes:I ran the Adler-32 checksum project, and here are the results: + +The build completed successfully without any errors or warnings. The Makefile correctly compiled both source files (`adler32-simple.c` and adler32-test.c) and linked them into the executable adler32-test. + +When running the tests, I got the following results: + +### Verification Tests +All 6 verification tests passed successfully: +1. Empty string: PASS +2. "a": PASS +3. "abc": PASS +4. "message digest": PASS +5. "abcdefghijklmnopqrstuvwxyz": PASS +6. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789": PASS + +### Incremental API Test +The incremental API test also passed, showing that both direct and incremental calculation methods produce the same checksum (0x3d094d1a). + +### Performance Tests +The implementation was benchmarked with different data sizes: +- 1 KB: 195.31 MB/s +- 10 KB: 128.50 MB/s +- 100 KB: 261.11 MB/s +- 1 MB: 297.80 MB/s +- 10 MB: 298.17 MB/s + +The results confirm that your Adler-32 checksum implementation is correct for all data sizes and performs efficiently. The optimizations for the Neoverse N1 architecture specified in the Makefile appear to be working properly, with throughput reaching nearly 300 MB/s for larger data sizes. + +``` + +The results from GitHub Copilot explain that the Adler32 checksum calculations are correct and give some initial performance results. The results don't mean much yet as there is nothing to compare with. + +Continue to the next section to implement Adler32 using NEON and compare the performance. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/makefile-5.md b/content/learning-paths/cross-platform/adler32/makefile-5.md new file mode 100644 index 0000000000..3edf9ae2e3 --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/makefile-5.md @@ -0,0 +1,114 @@ +--- +title: Create a Makefile +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I create a Makefile to build and run the test program? + +To create a Makefile, copy and paste the information below to GitHub Copilot. The prompt explains that the Makefile should use `gcc` as the compiler and target the Neoverse N1 processor. + +```console +Read the .c files in my project and +create a Makefile to build and run the adler32-test program using gcc. +Select the best compiler flags for Neoverse N1 to optimize for performance. +``` + +The output is: + +```output + +GitHub Copilot: I'll create a Makefile for your Adler-32 project that's optimized for the Neoverse N1 architecture. First, let's look at the project structure and understand the code files better.I see that the file search didn't return results. Let's try again with a different approach.I can see the project contains three files: adler32-simple.c, adler32-simple.h, and adler32-test.c. Let's examine the code in adler32-simple.c to understand the implementation before creating the Makefile.I've examined the code files in your project: + +1. adler32-simple.c - Contains the implementation of the Adler-32 checksum algorithm with functions for initialization, update, and finalization +2. adler32-simple.h - Presumably contains the function declarations +3. adler32-test.c - Contains test code for verifying and benchmarking the Adler-32 implementation + +Now I'll create a Makefile optimized for performance on the Neoverse N1 architecture. The Neoverse N1 is an ARM architecture that benefits from specific compiler optimizations. + +# Makefile for Adler-32 checksum implementation +# Optimized for Neoverse N1 architecture + +# Compiler and flags +CC = gcc +# Neoverse N1 specific optimization flags: +# -march=armv8.2-a+crypto: Target ARMv8.2-A architecture with crypto extensions (Neoverse N1) +# -mcpu=neoverse-n1: Specifically optimize for Neoverse N1 +# -O3: Highest standard optimization level +# -flto: Link-time optimization +# -fomit-frame-pointer: Don't keep the frame pointer in a register for functions that don't need one +# -ftree-vectorize: Vectorize loops when possible +# -ffast-math: Enable fast math optimizations +# Additional useful flags: +# -Wall -Wextra: Enable warnings +# -g: Include debugging symbols +CFLAGS = -march=armv8.2-a+crypto -mcpu=neoverse-n1 -O3 -flto -fomit-frame-pointer -ftree-vectorize -ffast-math -Wall -Wextra -g + +# Source files +SRCS = adler32-simple.c adler32-test.c +OBJS = $(SRCS:.c=.o) +TARGET = adler32-test + +# Default target +all: $(TARGET) + +# Link the executable +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) -o $@ $^ + +# Compile source files +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +# Run the tests +run: $(TARGET) + ./$(TARGET) + +# Clean generated files +clean: + rm -f $(OBJS) $(TARGET) + +# Rebuild the project +rebuild: clean all + +.PHONY: all run clean rebuild + + +Made changes. + +I've created a Makefile for your Adler-32 project, optimized specifically for the Neoverse N1 architecture. Let me highlight some key features of this Makefile: + +### Optimization Flags for Neoverse N1 + +The Makefile includes several compiler flags tailored for maximum performance on the Neoverse N1: + +- `-march=armv8.2-a+crypto`: Targets ARMv8.2-A architecture with crypto extensions +- `-mcpu=neoverse-n1`: Specifically optimizes for the Neoverse N1 processor +- `-O3`: Enables the highest standard optimization level +- `-flto`: Enables link-time optimization for whole-program optimization +- `-fomit-frame-pointer`: Frees up a register by not keeping frame pointers when not needed +- `-ftree-vectorize`: Automatically vectorizes loops to leverage SIMD instructions +- `-ffast-math`: Enables optimizations that might affect floating-point precision but improve performance + +### Building and Testing + +To use the Makefile, you can run the following commands: + +1. To build the project: + make + +2. To run the tests after building: + make run + +3. To clean the build files: + make clean + +4. To rebuild everything from scratch: + make rebuild + +The Makefile is designed to be simple but effective, automatically handling dependencies and providing different targets for common development tasks. +``` + +You are now ready to use the Makefile to build and run the test application. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/more-11.md b/content/learning-paths/cross-platform/adler32/more-11.md new file mode 100644 index 0000000000..50a263a8ac --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/more-11.md @@ -0,0 +1,18 @@ +--- +title: Other ideas for GitHub Copilot +weight: 11 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## What else can I do with GitHub Copilot on this project? + +You can investigate more topics using GitHub Copilot. + +- Direct GitHub Copilot to try different compiler flags and use Agent mode to iterate through the options to find the best solution. +- Add support for the Clang compiler to the Makefile and compare the results to GCC. Depending on the application code, changing the compiler can result in improved performance. +- Use GitHub Copilot to generate different data sizes and random data patterns to further investigate correct functionality and performance. +- Try different algorithm implementations that use compiler autovectorization instead of NEON intrinsics or break down the Adler32 checksum into smaller blocks of data. It may be possible to get even better performance without NEON using the compiler and a better structure for the C code. + +While AI tools do not create performance code for every programming problem, they can be a big help to get you started in a new area of programming such as performance optimization using NEON intrinsics. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/neon-7.md b/content/learning-paths/cross-platform/adler32/neon-7.md new file mode 100644 index 0000000000..fc009548ed --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/neon-7.md @@ -0,0 +1,558 @@ +--- +title: Create a NEON version of Adler32 +weight: 7 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I create a NEON version of Adler32 to improve performance? + +Writing NEON intrinsics is an advanced programming topic which requires time and effort to learn and apply. GitHub Copilot is a good way to get started with NEON, both to write new code and to debug issues during development. + +You can instruct Github Copilot to create a NEON version of Adler32 using the prompt below. + +```console +Add a second implementation of adler32 that processes data in blocks and uses Arm NEON intrinsics to improve performance. +Create it in the file adler32-neon.c +For inputs less than 16 bytes use a standard implementation. +Update the Makefile and other files to add this new version and compare performance. +Print out the speedup from the original version. +Make sure the checksum values are the same for both versions. +``` + +The output is: + +```output + +GitHub Copilot: I'll help you create a NEON-optimized version of adler32 and update the project to compare performance with the original implementation. Let's start by examining the existing code structure to understand the current implementation.Great, I've found the header file for the simple Adler-32 implementation. Now let's look at the implementation file to understand how it works.I've examined the existing code for the Adler32 implementation. Now let's check the test file to understand how to properly integrate our new implementation:I've analyzed your Adler32 implementation and test code. Now I'll create a NEON-optimized version that processes data in blocks, update the Makefile, and compare the performance between both implementations. + +Let's first create the NEON implementation file: + +/* + * adler32-neon.h - Header file for NEON-optimized Adler-32 checksum implementation + */ + +#ifndef ADLER32_NEON_H +#define ADLER32_NEON_H + +#include +#include + +/** + * Calculate Adler-32 checksum for a data buffer using NEON optimization + * + * @param data Pointer to the data buffer + * @param length Length of the data in bytes + * @param initial Initial Adler-32 value (typically 1) + * + * @return The calculated Adler-32 checksum + */ +uint32_t adler32_neon(const unsigned char *data, size_t length, uint32_t initial); + +/** + * Initialize Adler-32 checksum calculation + * + * @return The initial Adler-32 value (1) + */ +uint32_t adler32_neon_init(void); + +/** + * Update Adler-32 checksum with new data using NEON optimization + * + * @param adler Current Adler-32 value + * @param data Pointer to the data buffer + * @param length Length of the data in bytes + * + * @return The updated Adler-32 value + */ +uint32_t adler32_neon_update(uint32_t adler, const unsigned char *data, size_t length); + +/** + * Finalize Adler-32 calculation + * + * @param adler Current Adler-32 value + * + * @return The final Adler-32 checksum + */ +uint32_t adler32_neon_final(uint32_t adler); + +#endif /* ADLER32_NEON_H */ + + +Made changes. + +Now I'll create the implementation file for the NEON-optimized Adler32: + +/* + * adler32-neon.c - NEON-optimized implementation of Adler-32 checksum algorithm + * + * This implementation uses ARM NEON SIMD instructions to process multiple bytes in parallel + * for improved performance on Neoverse N1 and other ARM platforms with NEON support. + */ + +#include +#include +#include +#include "adler32-neon.h" + +/* Adler-32 parameters */ +#define MOD_ADLER 65521 /* Largest prime number less than 2^16 */ +#define BLOCK_SIZE 16 /* Process 16 bytes at once with NEON */ + +/* + * Standard implementation of adler32 for small inputs + * Used for inputs less than BLOCK_SIZE + */ +static uint32_t adler32_standard(const unsigned char *data, size_t length, uint32_t initial) +{ + uint32_t a = initial & 0xffff; + uint32_t b = (initial >> 16) & 0xffff; + size_t i; + + /* Process each byte */ + for (i = 0; i < length; i++) { + a = (a + data[i]) % MOD_ADLER; + b = (b + a) % MOD_ADLER; + } + + /* Combine the two 16-bit values */ + return (b << 16) | a; +} + +/* + * adler32_neon - Calculate the Adler-32 checksum of a data buffer using NEON instructions + * @data: Data buffer + * @length: Length of data buffer in bytes + * @initial: Initial Adler-32 value (typically 1) + * + * Returns the calculated Adler-32 checksum + */ +uint32_t adler32_neon(const unsigned char *data, size_t length, uint32_t initial) +{ + uint32_t a = initial & 0xffff; + uint32_t b = (initial >> 16) & 0xffff; + + /* For small inputs, use the standard implementation */ + if (length < BLOCK_SIZE) { + return adler32_standard(data, length, initial); + } + + /* Process blocks of BLOCK_SIZE bytes */ + while (length >= BLOCK_SIZE) { + /* NEON registers to accumulate sums */ + uint16x8_t va = vdupq_n_u16(0); /* 8 parallel accumulators for a */ + uint16x8_t vb = vdupq_n_u16(0); /* 8 parallel accumulators for b */ + + /* + * We'll process 16 bytes at a time: + * - Load 16 bytes into a 16x8 vector + * - Compute partial sums in vectors + */ + uint8x16_t vdata = vld1q_u8(data); + + /* Convert to 16-bit values for accumulation */ + uint16x8_t vdata_low = vmovl_u8(vget_low_u8(vdata)); + uint16x8_t vdata_high = vmovl_u8(vget_high_u8(vdata)); + + /* Accumulate a values: a += data[i] */ + va = vaddq_u16(va, vdata_low); + va = vaddq_u16(va, vdata_high); + + /* + * Accumulate b values with proper weighting: + * b += a + data[i] * (BLOCK_SIZE - i) + * + * Since we compute 16 bytes at once, weights are 16,15,...,1 + */ + static const uint16_t weights[16] = {16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}; + uint16x8_t vweights_low = vld1q_u16(weights); + uint16x8_t vweights_high = vld1q_u16(weights + 8); + + uint16x8_t weighted_low = vmulq_u16(vdata_low, vweights_low); + uint16x8_t weighted_high = vmulq_u16(vdata_high, vweights_high); + + vb = vaddq_u16(vb, weighted_low); + vb = vaddq_u16(vb, weighted_high); + + /* Accumulate partial sums into scalar a and b values */ + uint16_t partial_a[8]; + uint16_t partial_b[8]; + vst1q_u16(partial_a, va); + vst1q_u16(partial_b, vb); + + for (size_t i = 0; i < 8; i++) { + a += partial_a[i]; + b += partial_b[i]; + } + + /* Apply modulo to avoid overflow */ + a %= MOD_ADLER; + b = (b + 16 * a) % MOD_ADLER; /* Add BLOCK_SIZE * a to account for sequential weights */ + + /* Move to next block */ + data += BLOCK_SIZE; + length -= BLOCK_SIZE; + } + + /* Process remaining bytes using standard implementation */ + if (length > 0) { + uint32_t adler = (b << 16) | a; + adler = adler32_standard(data, length, adler); + return adler; + } + + /* Combine the two 16-bit values */ + return (b << 16) | a; +} + +/* + * adler32_neon_init - Initialize an Adler-32 checksum + * + * Returns the initial Adler-32 value + */ +uint32_t adler32_neon_init(void) +{ + return 1; +} + +/* + * adler32_neon_update - Update Adler-32 checksum with new data using NEON optimization + * @adler: Current Adler-32 value + * @data: Data buffer + * @length: Length of data buffer in bytes + * + * Returns the updated Adler-32 value + */ +uint32_t adler32_neon_update(uint32_t adler, const unsigned char *data, size_t length) +{ + return adler32_neon(data, length, adler); +} + +/* + * adler32_neon_final - Finalize Adler-32 calculation + * @adler: Current Adler-32 value + * + * Returns the final Adler-32 checksum + */ +uint32_t adler32_neon_final(uint32_t adler) +{ + return adler; +} + + +Made changes. + +Now I'll update the test file to add performance comparisons between the original and NEON implementations: + +/* + * adler32-test.c - Test and benchmark for Adler-32 checksum implementation + */ + +#include +#include +#include +#include +#include +#include "adler32-simple.h" +#include "adler32-neon.h" + +/* Known test vectors for Adler-32 */ +struct test_vector { + const char *data; + uint32_t expected; +}; + +/* Test data sizes in bytes */ +#define KB (1024) +#define MB (1024 * 1024) + +static const struct test_vector test_vectors[] = { + { "", 0x00000001 }, + { "a", 0x00620062 }, + { "abc", 0x024d0127 }, + { "message digest", 0x29750586 }, + { "abcdefghijklmnopqrstuvwxyz", 0x90860b20 }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0x8adb150c }, +}; + +/* Structure to hold test data */ +struct test_data { + unsigned char *buffer; + size_t size; + const char *name; +}; + +/* Generate random test data of specified size */ +static unsigned char* generate_test_data(size_t size) { + unsigned char *data = malloc(size); + if (!data) { + fprintf(stderr, "Failed to allocate memory for test data\n"); + exit(EXIT_FAILURE); + } + + for (size_t i = 0; i < size; i++) { + data[i] = rand() & 0xFF; + } + + return data; +} + +/* Performance test result structure */ +struct perf_result { + double time; + double throughput; + uint32_t checksum; +}; + +/* Run performance test on a data buffer using the specified implementation */ +static struct perf_result test_performance_impl(const unsigned char *data, size_t size, + uint32_t (*adler_func)(const unsigned char *, size_t, uint32_t), + uint32_t (*init_func)(void), + const char *impl_name) { + clock_t start, end; + struct perf_result result; + + /* Warm-up run */ + adler_func(data, size, init_func()); + + /* Timed run */ + start = clock(); + result.checksum = adler_func(data, size, init_func()); + end = clock(); + + result.time = ((double) (end - start)) / CLOCKS_PER_SEC; + result.throughput = (double)size / (result.time * MB); + + printf(" %s:\n", impl_name); + printf(" Checksum: 0x%08x\n", result.checksum); + printf(" Time: %.6f seconds\n", result.time); + printf(" Throughput: %.2f MB/s\n", result.throughput); + + return result; +} + +/* Run performance test on a data buffer for both implementations */ +static void test_performance(const unsigned char *data, size_t size, const char *test_name) { + printf("Testing %s (%zu bytes):\n", test_name, size); + + /* Test the simple implementation */ + struct perf_result simple = test_performance_impl(data, size, adler32, adler32_init, "Simple implementation"); + + /* Test the NEON implementation */ + struct perf_result neon = test_performance_impl(data, size, adler32_neon, adler32_neon_init, "NEON implementation"); + + /* Check if checksums match */ + if (simple.checksum != neon.checksum) { + printf(" CHECKSUM MISMATCH: Simple 0x%08x, NEON 0x%08x\n", + simple.checksum, neon.checksum); + } + + /* Calculate speedup */ + double speedup = simple.time / neon.time; + printf(" Speedup: %.2fx\n\n", speedup); +} + +/* Run verification tests with known test vectors */ +static void run_verification_tests(void) { + printf("Running verification tests:\n"); + printf("==========================\n"); + + int passed_simple = 0; + int passed_neon = 0; + int total = sizeof(test_vectors) / sizeof(test_vectors[0]); + + for (size_t i = 0; i < total; i++) { + const struct test_vector *tv = &test_vectors[i]; + uint32_t result_simple = adler32((const unsigned char *)tv->data, strlen(tv->data), adler32_init()); + uint32_t result_neon = adler32_neon((const unsigned char *)tv->data, strlen(tv->data), adler32_neon_init()); + + printf("Test %zu: \"%s\"\n", i + 1, tv->data); + printf(" Expected: 0x%08x\n", tv->expected); + printf(" Simple: 0x%08x (%s)\n", result_simple, + (result_simple == tv->expected) ? "PASS" : "FAIL"); + printf(" NEON: 0x%08x (%s)\n", result_neon, + (result_neon == tv->expected) ? "PASS" : "FAIL"); + + if (result_simple == tv->expected) passed_simple++; + if (result_neon == tv->expected) passed_neon++; + + /* Check if both implementations match */ + if (result_simple != result_neon) { + printf(" WARNING: Simple and NEON implementations produce different results!\n"); + } + + printf("\n"); + } + + printf("Verification summary:\n"); + printf(" Simple: %d/%d tests passed\n", passed_simple, total); + printf(" NEON: %d/%d tests passed\n\n", passed_neon, total); +} + +/* Run performance tests with different data sizes */ +static void run_performance_tests(void) { + printf("Running performance tests:\n"); + printf("=========================\n"); + + /* Define test data sizes */ + struct test_data tests[] = { + { NULL, 1 * KB, "1 KB" }, + { NULL, 10 * KB, "10 KB" }, + { NULL, 100 * KB, "100 KB" }, + { NULL, 1 * MB, "1 MB" }, + { NULL, 10 * MB, "10 MB" } + }; + + /* Generate random test data for each size */ + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + tests[i].buffer = generate_test_data(tests[i].size); + test_performance(tests[i].buffer, tests[i].size, tests[i].name); + } + + /* Free allocated memory */ + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + free(tests[i].buffer); + } +} + +/* Test the incremental API for both implementations */ +static void test_incremental_api(void) { + printf("Testing incremental API:\n"); + printf("=======================\n"); + + /* Generate test data */ + size_t size = 1 * MB; + unsigned char *data = generate_test_data(size); + + printf("Simple implementation:\n"); + /* Calculate checksum in one go */ + uint32_t direct = adler32(data, size, adler32_init()); + + /* Calculate checksum incrementally */ + uint32_t incremental = adler32_init(); + + /* Process in chunks of 64KB */ + size_t chunk_size = 64 * KB; + for (size_t offset = 0; offset < size; offset += chunk_size) { + size_t remaining = size - offset; + size_t current_chunk = (remaining < chunk_size) ? remaining : chunk_size; + incremental = adler32_update(incremental, data + offset, current_chunk); + } + incremental = adler32_final(incremental); + + /* Compare results */ + printf(" Direct calculation: 0x%08x\n", direct); + printf(" Incremental calculation: 0x%08x\n", incremental); + printf(" Match: %s\n\n", (direct == incremental) ? "YES" : "NO"); + + printf("NEON implementation:\n"); + /* Calculate checksum in one go */ + uint32_t direct_neon = adler32_neon(data, size, adler32_neon_init()); + + /* Calculate checksum incrementally */ + uint32_t incremental_neon = adler32_neon_init(); + + /* Process in chunks of 64KB */ + for (size_t offset = 0; offset < size; offset += chunk_size) { + size_t remaining = size - offset; + size_t current_chunk = (remaining < chunk_size) ? remaining : chunk_size; + incremental_neon = adler32_neon_update(incremental_neon, data + offset, current_chunk); + } + incremental_neon = adler32_neon_final(incremental_neon); + + /* Compare results */ + printf(" Direct calculation: 0x%08x\n", direct_neon); + printf(" Incremental calculation: 0x%08x\n", incremental_neon); + printf(" Match: %s\n\n", (direct_neon == incremental_neon) ? "YES" : "NO"); + + /* Compare simple vs NEON */ + printf("Cross implementation comparison:\n"); + printf(" Simple checksum: 0x%08x\n", direct); + printf(" NEON checksum: 0x%08x\n", direct_neon); + printf(" Match: %s\n\n", (direct == direct_neon) ? "YES" : "NO"); + + free(data); +} + +int main(void) { + /* Seed random number generator */ + srand((unsigned int)time(NULL)); + + printf("Adler-32 Test and Benchmark\n"); + printf("===========================\n\n"); + + /* Run verification tests with known test vectors */ + run_verification_tests(); + + /* Test the incremental API */ + test_incremental_api(); + + /* Run performance tests with different data sizes */ + run_performance_tests(); + + return 0; +} + + +Made changes. + +Let's update the Makefile to include the NEON implementation in the build: + +# Makefile for Adler-32 checksum implementation +# Optimized for Neoverse N1 architecture + +# Compiler and flags +CC = gcc +# Neoverse N1 specific optimization flags: +# -march=armv8.2-a+crypto: Target ARMv8.2-A architecture with crypto extensions (Neoverse N1) +# -mcpu=neoverse-n1: Specifically optimize for Neoverse N1 +# -O3: Highest standard optimization level +# -flto: Link-time optimization +# -fomit-frame-pointer: Don't keep the frame pointer in a register for functions that don't need one +# -ftree-vectorize: Vectorize loops when possible +# -ffast-math: Enable fast math optimizations +# Additional useful flags: +# -Wall -Wextra: Enable warnings +# -g: Include debugging symbols +CFLAGS = -march=armv8.2-a+crypto -mcpu=neoverse-n1 -O3 -flto -fomit-frame-pointer -ftree-vectorize -ffast-math -Wall -Wextra -g + +# Source files +SRCS = adler32-simple.c adler32-neon.c adler32-test.c +OBJS = $(SRCS:.c=.o) +TARGET = adler32-test + +# Default target +all: $(TARGET) + +# Link the executable +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) -o $@ $^ + +# Compile source files +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +# Run the tests +run: $(TARGET) + ./$(TARGET) + +# Clean generated files +clean: + rm -f $(OBJS) $(TARGET) + +# Rebuild the project +rebuild: clean all + +.PHONY: all run clean rebuild + + +Made changes. + +Now that I've created the NEON implementation and updated the test code and Makefile, let's compile and run the code to see the performance comparison. + +``` + +GitHub Copilot generated a new implementation of Adler32 using NEON intrinsics. It also updated the test program and the Makefile to make it easy to compare the two versions. + +Continue to the next section to compile and run the NEON version and investigate performance. diff --git a/content/learning-paths/cross-platform/adler32/neon-debug-9.md b/content/learning-paths/cross-platform/adler32/neon-debug-9.md new file mode 100644 index 0000000000..dd147bcd7c --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/neon-debug-9.md @@ -0,0 +1,99 @@ +--- +title: Debug the NEON version to match the standard C version +weight: 9 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I debug the NEON version Adler32 and compare performance? + +During the last step, GitHub Copilot revealed that the functional results with NEON do not match the C version. + +Use the prompt below to instruct GitHub Copilot to try to debug and fix the issue. + +```console +Can you modify adler32-neon.c to produce the correct checksum results for the adler32 algorithm? The test program is adler32-test.c and the simple version is adler32-simple.c +The NEON version should produce the same checksum as adler32-simple.c but run faster. +``` + +If the LLM you are using doesn't resolve the NEON functional issues, you can try with another LLM such as Gemini 2.5 Pro. + +The output is: + +```output +GitHub Copilot: Okay, I can help with that. The issue seems to be in how the `a` and `b` accumulators are updated within the NEON processing loop. The update to `b` needs to correctly incorporate the value of `a` *before* it was updated in the current block, along with the weighted sum of the data bytes. + +I will modify the `adler32_neon` function in adler32-neon.c to correctly calculate the sums `s1` (sum of bytes) and `s2` (weighted sum of bytes) for each block using NEON intrinsics, and then update the scalar `a` and `b` values according to the Adler-32 definition: `a = (a_old + s1) % MOD_ADLER` and `b = (b_old + BLOCK_SIZE * a_old + s2) % MOD_ADLER`. + +uint32_t adler32_neon(const unsigned char *data, size_t length, uint32_t initial) +{ + uint32_t a = initial & 0xffff; + uint32_t b = (initial >> 16) & 0xffff; + + /* For small inputs, use the standard implementation */ + if (length < BLOCK_SIZE) { + return adler32_standard(data, length, initial); + } + + /* Preload weights */ + static const uint16_t weights[16] = {16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}; + uint16x8_t vweights_low = vld1q_u16(weights); + uint16x8_t vweights_high = vld1q_u16(weights + 8); + + /* Process blocks of BLOCK_SIZE bytes */ + while (length >= BLOCK_SIZE) { + /* Store old 'a' before processing the block */ + uint32_t a_old = a; + + /* Load 16 bytes */ + uint8x16_t vdata = vld1q_u8(data); + + /* Widen bytes to 16 bits */ + uint16x8_t vdata_low = vmovl_u8(vget_low_u8(vdata)); + uint16x8_t vdata_high = vmovl_u8(vget_high_u8(vdata)); + + /* Calculate S1 = sum(data[i]) for the block */ + uint16x8_t vs1_sum = vaddq_u16(vdata_low, vdata_high); + /* Horizontal sum for S1 */ + uint32x4_t vs1_pairs = vpaddlq_u16(vs1_sum); + uint64x2_t vs1_quads = vpaddlq_u32(vs1_pairs); + uint32_t block_s1 = (uint32_t)(vgetq_lane_u64(vs1_quads, 0) + vgetq_lane_u64(vs1_quads, 1)); + + /* Calculate S2 = sum((BLOCK_SIZE-i) * data[i]) for the block */ + uint16x8_t weighted_low = vmulq_u16(vdata_low, vweights_low); + uint16x8_t weighted_high = vmulq_u16(vdata_high, vweights_high); + uint16x8_t vs2_sum = vaddq_u16(weighted_low, weighted_high); + /* Horizontal sum for S2 */ + uint32x4_t vs2_pairs = vpaddlq_u16(vs2_sum); + uint64x2_t vs2_quads = vpaddlq_u32(vs2_pairs); + uint32_t block_s2 = (uint32_t)(vgetq_lane_u64(vs2_quads, 0) + vgetq_lane_u64(vs2_quads, 1)); + + /* Update scalar a and b */ + a = (a_old + block_s1) % MOD_ADLER; + b = (b + (BLOCK_SIZE * a_old) + block_s2) % MOD_ADLER; + + /* Move to next block */ + data += BLOCK_SIZE; + length -= BLOCK_SIZE; + } + + /* Process remaining bytes using standard implementation */ + if (length > 0) { +// ...existing code... + + +Made changes. + +I've modified the `adler32_neon` function in adler32-neon.c to correctly calculate the checksum using NEON intrinsics. + +Now, I'll check for any compilation errors and then rebuild and run the test program to verify the results.I have modified the `adler32_neon` function in adler32-neon.c to correctly calculate the block sums and update the `a` and `b` accumulators according to the Adler-32 algorithm definition, using NEON intrinsics for the block processing. + +I then checked for errors in the modified file and rebuilt the project using `make`. The build completed successfully, although there's a minor compiler warning in adler32-test.c about comparing signed and unsigned integers, which is unrelated to the changes made. + +You can now run the test program adler32-test to verify that the NEON implementation produces the same results as the simple implementation. +``` + +GitHub Copilot was able to modify the NEON version to get the correct functional results. + +Continue to the next section to summarize the project, including the performance improvements, and generate a full project description. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/neon-run-8.md b/content/learning-paths/cross-platform/adler32/neon-run-8.md new file mode 100644 index 0000000000..1f12830456 --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/neon-run-8.md @@ -0,0 +1,128 @@ +--- +title: Compare the NEON version to the standard C version of Adler32 +weight: 8 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I run the NEON version of Adler32 and compare performance? + +The first step to investigate if NEON can provide improved performance is to make sure the NEON version builds, runs, and matches the results of the C version. + +To build the NEON version and compare it to the C version use the prompt below in your GitHub Copilot Agent session: + +```console +Use the Makefile to compile and run the new test program and compare the checksum values and performance of each implementation. +``` + +The output is: + +```output + +GitHub Copilot: I'll help you compile and run the test program to compare the checksum implementations. Let's first examine the Makefile to understand how to build and run the tests.I've examined the Makefile and now I understand how to compile and run the test program. The Makefile contains targets for building the adler32 implementations and running the tests. + +Let's also look at the test program to understand what it's testing:I've examined the Makefile and the test program (adler32-test.c). Now I understand how we can compile and run the tests to compare the checksum implementations. The adler32-test.c file contains comprehensive tests that will: + +1. Verify both implementations with known test vectors +2. Test the incremental API for both implementations +3. Run performance benchmarks with different data sizes (1KB to 10MB) + +Let me compile and run the test program using the Makefile:I see that the `make clean && make` command completed, but it didn't show detailed output. Let's run the test program to see the comparison results:I've run the Adler-32 test program for you. The test results show that the current implementation has a few issues that need to be addressed. + +Here's what I found from running the tests: + +1. **Verification Tests**: The program only tested one implementation against known test vectors (all 6 tests passed). However, it should be testing both the simple and NEON implementations separately, as designed in the test code. + +2. **Incremental API Test**: Only one implementation was tested in the incremental mode, but the test program is designed to test both. + +3. **Performance Tests**: The benchmarks only showed results for a single implementation rather than comparing both the simple and NEON versions as intended. + +The issue appears to be that the test program is not fully comparing both implementations as it was designed to do. This suggests there might be a mismatch between what's implemented and what's being tested. + +Let's check the simple implementation to understand what functions are available:I've compiled and run the Adler-32 test program to compare the implementations. Let me share what I found. + +The test program ran, but it's not properly comparing both implementations as it was designed to do. Here's what I discovered: + +1. The test passed all 6 verification tests, but it only appears to be testing one implementation against the known test vectors, not both the simple and NEON implementations as intended. + +2. Only one implementation was tested in the incremental API tests, while the code is written to test both. + +3. The performance benchmarks only showed results for a single implementation rather than comparing both as intended. For reference, the single implementation achieved: + - 1KB: 195.31 MB/s + - 10KB: 279.02 MB/s + - 100KB: 298.64 MB/s + - 1MB: 298.24 MB/s + - 10MB: 298.03 MB/s + +Looking at the test code and the adler32-simple.h header file, I can see the issue is likely related to the test program not properly executing the comparison between both implementations. + +Let me fix this by updating the test file so it properly tests both implementations. Then we can run it again to get the comparative results.I've reviewed the test program (adler32-test.c) and found that it's correctly designed to compare both the simple and NEON implementations. + +There seems to be an issue with how the tests are being run or how the implementations are connected. The test code expects to compare both implementations, but when it ran, it only showed results for a single implementation. + +Let's run the tests again, but with verbose output to better understand what's happening:I've run the tests to compare the Adler-32 implementations, and now I can provide you with the detailed results. + +## Test Results + +### Verification Tests +- Both implementations were tested against known test vectors. +- The Simple implementation passed all 6 test cases (100%). +- The NEON implementation passed 4 out of 6 test cases (67%). +- There were mismatches in test case #5 (alphabet string) and test case #6 (alphanumeric string). + +### Incremental API Test +- Both implementations showed consistency between direct and incremental calculation methods. +- However, the checksums between the Simple and NEON implementations don't match. + +### Performance Tests +For each test size, here are the results: + +1. **1 KB Data**: + - Simple: 195.31 MB/s + - NEON: 976.56 MB/s + - Speedup: 5.00x + - Checksums don't match + +2. **10 KB Data**: + - Simple: 287.22 MB/s + - NEON: 1085.07 MB/s + - Speedup: 3.78x + - Checksums don't match + +3. **100 KB Data**: + - Simple: 295.93 MB/s + - NEON: 1302.08 MB/s + - Speedup: 4.40x + - Checksums don't match + +4. **1 MB Data**: + - Simple: 297.97 MB/s + - NEON: 1305.48 MB/s + - Speedup: 4.38x + - Checksums don't match + +5. **10 MB Data**: + - Simple: 297.95 MB/s + - NEON: 1310.44 MB/s + - Speedup: 4.40x + - Checksums don't match + +## Analysis + +The NEON implementation shows impressive performance gains, with speedups ranging from 3.78x to 5.00x compared to the simple implementation. This demonstrates the effectiveness of using SIMD (Single Instruction, Multiple Data) instructions for checksum calculations. + +However, there's a critical issue: **the checksum values from the NEON implementation don't match those from the simple implementation**. Since the simple implementation passes all verification tests, this indicates that the NEON implementation has a bug that needs to be fixed. + +The most likely issue is in the NEON vector processing logic. From the test results, it appears the NEON implementation is correctly handling shorter inputs (the first 4 test cases passed) but fails on longer inputs. This suggests the bug might be related to how it processes larger blocks of data. + +## Conclusion + +The NEON implementation offers significant performance improvements (up to 5x faster), which would be valuable for applications processing large amounts of data. However, it produces incorrect checksum values currently, making it unsuitable for production use until fixed. + +If you'd like, I can analyze the code to identify and fix the issue in the NEON implementation so you can maintain both correctness and the performance benefits. +``` + +GitHub Copilot has come to the conclusion that although the NEON version looks like it offers better performance, it does not produce the correct results for the checksums. Obviously, this is not a good result which needs to be fixed. + +Continue to the next section to fix the functional issues and continue with the performance comparison. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/simple-c-3.md b/content/learning-paths/cross-platform/adler32/simple-c-3.md new file mode 100644 index 0000000000..ffc8a3dd7d --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/simple-c-3.md @@ -0,0 +1,196 @@ +--- +title: Create a C version of Adler32 +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I generate a C function for Adler32? + +You can use GitHub Copilot to generate a simple C version of the Adler32 checksum algorithm. + +On your Arm Linux computer, create a new directory and enter the empty directory: + +```console +mkdir adler32 && cd adler32 +``` + +Open VS Code in the new directory to start the project: + +```console +code . & +``` + +Open GitHub Copilot, select the Large Language Model (LLM) you want to use, and select `Agent` mode. + +{{% notice Note %}} +Your results will be different based on the model you use. The model used for the output shown is Claude 3.7 Sonnet. +{{% /notice %}} + +Copy and paste the information below as a GitHub Copilot prompt and click the `Send` button. + +```console +Write a basic C implementation of the adler32 checksum function. +Write the function in a file named adler32-simple.c that can be called from another C file. +Don't create a C main function yet. +``` + +The output is: + +```output + +GitHub Copilot: I'll create a basic C implementation of the Adler-32 checksum function in a file named adler32-simple.c. Let me do that for you. + +First, let me check if the file exists already:The file doesn't exist yet, so I'll create it. Let me write the Adler-32 checksum implementation for you. + +The Adler-32 checksum algorithm works by calculating two 16-bit checksums A and B and combining them into a single 32-bit checksum. It's a simple algorithm that provides decent error detection with minimal computational overhead. + +/* + * adler32-simple.c - Simple implementation of Adler-32 checksum algorithm + * + * The Adler-32 checksum is a checksum algorithm which was invented by Mark Adler. + * It is used in the zlib compression library and specified in RFC 1950. + */ + +#include +#include + +/* Adler-32 parameters */ +#define MOD_ADLER 65521 /* Largest prime number less than 2^16 */ + +/* + * adler32 - Calculate the Adler-32 checksum of a data buffer + * @data: Data buffer + * @length: Length of data buffer in bytes + * @initial: Initial Adler-32 value (typically 1) + * + * Returns the calculated Adler-32 checksum + */ +uint32_t adler32(const unsigned char *data, size_t length, uint32_t initial) +{ + uint32_t a = initial & 0xffff; + uint32_t b = (initial >> 16) & 0xffff; + size_t i; + + /* Process each byte */ + for (i = 0; i < length; i++) { + a = (a + data[i]) % MOD_ADLER; + b = (b + a) % MOD_ADLER; + } + + /* Combine the two 16-bit values */ + return (b << 16) | a; +} + +/* + * adler32_init - Initialize an Adler-32 checksum + * + * Returns the initial Adler-32 value + */ +uint32_t adler32_init(void) +{ + return 1; +} + +/* + * adler32_update - Update Adler-32 checksum with new data + * @adler: Current Adler-32 value + * @data: Data buffer + * @length: Length of data buffer in bytes + * + * Returns the updated Adler-32 value + */ +uint32_t adler32_update(uint32_t adler, const unsigned char *data, size_t length) +{ + return adler32(data, length, adler); +} + +/* + * adler32_final - Finalize Adler-32 calculation + * @adler: Current Adler-32 value + * + * Returns the final Adler-32 checksum + */ +uint32_t adler32_final(uint32_t adler) +{ + return adler; +} + + +Made changes. + +Now let's create a header file to make the Adler-32 functions accessible from other C files: + +/* + * adler32-simple.h - Header file for Adler-32 checksum implementation + */ + +#ifndef ADLER32_SIMPLE_H +#define ADLER32_SIMPLE_H + +#include +#include + +/** + * Calculate Adler-32 checksum for a data buffer + * + * @param data Pointer to the data buffer + * @param length Length of the data in bytes + * @param initial Initial Adler-32 value (typically 1) + * + * @return The calculated Adler-32 checksum + */ +uint32_t adler32(const unsigned char *data, size_t length, uint32_t initial); + +/** + * Initialize Adler-32 checksum calculation + * + * @return The initial Adler-32 value (1) + */ +uint32_t adler32_init(void); + +/** + * Update Adler-32 checksum with new data + * + * @param adler Current Adler-32 value + * @param data Pointer to the data buffer + * @param length Length of the data in bytes + * + * @return The updated Adler-32 value + */ +uint32_t adler32_update(uint32_t adler, const unsigned char *data, size_t length); + +/** + * Finalize Adler-32 calculation + * + * @param adler Current Adler-32 value + * + * @return The final Adler-32 checksum + */ +uint32_t adler32_final(uint32_t adler); + +#endif /* ADLER32_SIMPLE_H */ + + +Made changes. + +I've created a basic C implementation of the Adler-32 checksum algorithm in adler32-simple.c as requested. I also added a corresponding header file adler32-simple.h to make the functions accessible from other C files. + +The implementation includes: + +1. `adler32()` - The core function that calculates the checksum for a data buffer +2. `adler32_init()` - Function to initialize an Adler-32 checksum (returns 1) +3. `adler32_update()` - Function to update a checksum with new data +4. `adler32_final()` - Function to finalize the checksum calculation + +These functions follow the standard pattern for checksum/hash functions, allowing you to calculate checksums for data in chunks rather than all at once if needed. + +The Adler-32 algorithm works by calculating two 16-bit sums (A and B) and combining them into a 32-bit checksum. It uses the modulus 65521, which is the largest prime number less than 2^16. + +As requested, I did not include a main function, so this code can be used as a library for other C files. +``` + +You now have the files `adler32-simple.c` and `adler32-simple.h` in your directory which compute the Adler32 checksum. These will serve as the reference implementations of the algorithm with baseline performance. + +Continue to the next section to create the test application. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/summary-10.md b/content/learning-paths/cross-platform/adler32/summary-10.md new file mode 100644 index 0000000000..7ff80a22cc --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/summary-10.md @@ -0,0 +1,98 @@ +--- +title: Summarize the project with a README.md file +weight: 10 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I summarize the project results? + +You can use GitHub Copilot to generate a project summary in a README file. + +Copy the prompt below to your GitHub Copilot Agent chat and review the created README file. + +```console +Review the files in my project. +Create a README.md file that describes the background and overview of the project. +Explain the two implementations of the adler32 checksum. +Add a note that the performance results recorded on the Neoverse N1 processor. +Use a table to compare the original version and the NEON version and show the performance improvement factor. +``` + +Below is the created README.md file. The formatting doesn't match the Learning Path template exactly, but you can copy the the README file to a new repository in GitHub for improved results. + +## Adler-32 Checksum Implementation Comparison + +### Background + +The Adler-32 checksum is an algorithm invented by Mark Adler, used in the zlib compression library and specified in RFC 1950. It provides a fast way to calculate a checksum for data integrity verification, balancing speed and reliability. It generates a 32-bit integer value based on the input data. + +### Overview + +This project provides and compares two implementations of the Adler-32 checksum algorithm: + +1. A simple, standard C implementation. +2. An optimized implementation using ARM NEON SIMD instructions. + +The goal is to demonstrate the performance benefits of using NEON intrinsics for this type of computation on compatible ARM architectures, such as the Neoverse N1. + +The project includes: +* Source code for both implementations (`adler32-simple.c`, `adler32-neon.c`). +* Header files (`adler32-simple.h`, `adler32-neon.h`). +* A test and benchmark program (`adler32-test.c`) to verify correctness and measure performance. +* A `Makefile` for easy compilation and testing. + +### Implementations + +#### 1. Simple Implementation (`adler32-simple.c`) + +This is a straightforward C implementation following the standard Adler-32 algorithm definition. It processes the input data byte by byte, updating two 16-bit accumulators (`a` and `b`) modulo 65521 (the largest prime smaller than 2^16). + +#### 2. NEON-Optimized Implementation (`adler32-neon.c`) + +This implementation leverages ARM NEON SIMD (Single Instruction, Multiple Data) instructions to accelerate the checksum calculation. Key aspects include: +* Processing data in blocks (16 bytes at a time). +* Using NEON intrinsics (`vld1q_u8`, `vmovl_u8`, `vaddq_u16`, `vpaddlq_u16`, `vmulq_u16`, etc.) to perform parallel operations on data vectors. +* Calculating the sums `S1` (sum of bytes) and `S2` (weighted sum) for each block using vector operations. +* Updating the scalar `a` and `b` accumulators based on the block results. +* Falling back to the standard implementation for data lengths smaller than the block size or for the remaining bytes after processing full blocks. + +### Performance + +The performance of both implementations was measured on an **AWS Graviton2 processor (based on Arm Neoverse N1 cores)**. The benchmark program (`adler32-test`) calculates the checksum for various data sizes and measures the time taken and throughput (in MB/s). + +The following table summarizes the throughput results and the performance improvement factor of the NEON version compared to the simple version: + +| Data Size | Simple Throughput (MB/s) | NEON Throughput (MB/s) | Speedup Factor | +| :-------- | :----------------------- | :--------------------- | :------------- | +| 1 KB | 244.14 | 976.56 | 4.00x | +| 10 KB | 295.93 | 3255.21 | 11.00x | +| 100 KB | 298.64 | 3150.20 | 10.55x | +| 1 MB | 298.33 | 3215.43 | 10.78x | +| 10 MB | 298.37 | 3194.89 | 10.71x | + +**Note:** Performance results can vary based on the specific hardware, compiler, and system load. The results above demonstrate a significant performance improvement (around **10-11x** for larger data sizes) when using NEON optimization on the Neoverse N1 architecture. + +### Building and Running + +Use the provided `Makefile`: + +```bash +# Compile the code +make + +# Run verification and performance tests +make run + +# Clean up generated files +make clean +``` + +The table summarizes the speedup obtained by the NEON version. + +Using Agent mode in GitHub Copilot is a significant benefit when you are actively building and running software. Agent mode can create files and modify them to make needed improvements. + +The entire project was done without modifying any of the generated files. While you may not need to do this on a real project, the concept of writing NEON intrinsics to improve performance was demonstrated. You can also use GitHub Copilot to fix issues in NEON code that are difficult to debug for developers who are not experts. + +Make sure to try different LLMs with Copilot as the results will vary greatly depending on the model. diff --git a/content/learning-paths/cross-platform/adler32/test-prog-4.md b/content/learning-paths/cross-platform/adler32/test-prog-4.md new file mode 100644 index 0000000000..e19359036b --- /dev/null +++ b/content/learning-paths/cross-platform/adler32/test-prog-4.md @@ -0,0 +1,247 @@ +--- +title: Create a test program +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How can I create a test program to verify Adler32 works correctly? + +You can continue your GitHub Copilot Agent session to create a test program using the prompt below. Copy and paste it into GitHub Copilot to generate a test program. + +```console +Create a C file named adler32-test.c which includes a main() function and calls the adler32 function in adler32-simple.c +Add code around the adler32 calls to measure the performance of the adler32 checksum function. +Generate a simple test to make sure the adler32 generates the expected checksum. +Add randomly generated test data of various sizes: 1 kb, 10 kb, 100 kb, 1 mb, and 10 mb. +Add code to run all test data with different sizes and measure performance. +Don't create the Makefile yet. +``` + +The output is below: + +```output + +GitHub Copilot: I'll create a test file for the Adler-32 implementation that includes performance testing with different data sizes. I'll first check the current implementation to understand how to use the functions properly.Now I understand how the Adler-32 implementation works. Let me create a test file that measures the performance of the adler32 function with different data sizes and verifies that it produces the expected checksums. + +/* + * adler32-test.c - Test and benchmark for Adler-32 checksum implementation + */ + +#include +#include +#include +#include +#include +#include "adler32-simple.h" + +/* Known test vectors for Adler-32 */ +struct test_vector { + const char *data; + uint32_t expected; +}; + +/* Test data sizes in bytes */ +#define KB (1024) +#define MB (1024 * 1024) + +static const struct test_vector test_vectors[] = { + { "", 0x00000001 }, + { "a", 0x00620062 }, + { "abc", 0x024d0127 }, + { "message digest", 0x29750586 }, + { "abcdefghijklmnopqrstuvwxyz", 0x90860b20 }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0x8adb150c }, +}; + +/* Structure to hold test data */ +struct test_data { + unsigned char *buffer; + size_t size; + const char *name; +}; + +/* Generate random test data of specified size */ +static unsigned char* generate_test_data(size_t size) { + unsigned char *data = malloc(size); + if (!data) { + fprintf(stderr, "Failed to allocate memory for test data\n"); + exit(EXIT_FAILURE); + } + + for (size_t i = 0; i < size; i++) { + data[i] = rand() & 0xFF; + } + + return data; +} + +/* Run performance test on a data buffer */ +static void test_performance(const unsigned char *data, size_t size, const char *test_name) { + clock_t start, end; + double cpu_time_used; + uint32_t checksum; + + printf("Testing %s (%zu bytes):\n", test_name, size); + + /* Warm-up run */ + adler32(data, size, adler32_init()); + + /* Timed run */ + start = clock(); + checksum = adler32(data, size, adler32_init()); + end = clock(); + + cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC; + double throughput = (double)size / (cpu_time_used * MB); + + printf(" Checksum: 0x%08x\n", checksum); + printf(" Time: %.6f seconds\n", cpu_time_used); + printf(" Throughput: %.2f MB/s\n\n", throughput); +} + +/* Run verification tests with known test vectors */ +static void run_verification_tests(void) { + printf("Running verification tests:\n"); + printf("==========================\n"); + + int passed = 0; + for (size_t i = 0; i < sizeof(test_vectors) / sizeof(test_vectors[0]); i++) { + const struct test_vector *tv = &test_vectors[i]; + uint32_t result = adler32((const unsigned char *)tv->data, strlen(tv->data), adler32_init()); + + printf("Test %zu: \"%s\"\n", i + 1, tv->data); + printf(" Expected: 0x%08x\n", tv->expected); + printf(" Result: 0x%08x\n", result); + + if (result == tv->expected) { + printf(" Status: PASS\n"); + passed++; + } else { + printf(" Status: FAIL\n"); + } + printf("\n"); + } + + printf("Verification summary: %d/%zu tests passed\n\n", + passed, sizeof(test_vectors) / sizeof(test_vectors[0])); +} + +/* Run performance tests with different data sizes */ +static void run_performance_tests(void) { + printf("Running performance tests:\n"); + printf("=========================\n"); + + /* Define test data sizes */ + struct test_data tests[] = { + { NULL, 1 * KB, "1 KB" }, + { NULL, 10 * KB, "10 KB" }, + { NULL, 100 * KB, "100 KB" }, + { NULL, 1 * MB, "1 MB" }, + { NULL, 10 * MB, "10 MB" } + }; + + /* Generate random test data for each size */ + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + tests[i].buffer = generate_test_data(tests[i].size); + test_performance(tests[i].buffer, tests[i].size, tests[i].name); + } + + /* Free allocated memory */ + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + free(tests[i].buffer); + } +} + +/* Test the incremental API */ +static void test_incremental_api(void) { + printf("Testing incremental API:\n"); + printf("=======================\n"); + + /* Generate test data */ + size_t size = 1 * MB; + unsigned char *data = generate_test_data(size); + + /* Calculate checksum in one go */ + uint32_t direct = adler32(data, size, adler32_init()); + + /* Calculate checksum incrementally */ + uint32_t incremental = adler32_init(); + + /* Process in chunks of 64KB */ + size_t chunk_size = 64 * KB; + for (size_t offset = 0; offset < size; offset += chunk_size) { + size_t remaining = size - offset; + size_t current_chunk = (remaining < chunk_size) ? remaining : chunk_size; + incremental = adler32_update(incremental, data + offset, current_chunk); + } + incremental = adler32_final(incremental); + + /* Compare results */ + printf(" Direct calculation: 0x%08x\n", direct); + printf(" Incremental calculation: 0x%08x\n", incremental); + printf(" Match: %s\n\n", (direct == incremental) ? "YES" : "NO"); + + free(data); +} + +int main(void) { + /* Seed random number generator */ + srand((unsigned int)time(NULL)); + + printf("Adler-32 Test and Benchmark\n"); + printf("===========================\n\n"); + + /* Run verification tests with known test vectors */ + run_verification_tests(); + + /* Test the incremental API */ + test_incremental_api(); + + /* Run performance tests with different data sizes */ + run_performance_tests(); + + return 0; +} + + +Made changes. + +I've created the adler32-test.c file with a comprehensive test suite for the Adler-32 checksum implementation. This test file includes: + +1. **Verification tests** with known test vectors to ensure the implementation produces the expected output for standard inputs. + +2. **Incremental API test** to verify that performing the checksum calculation incrementally (in chunks) produces the same result as calculating it in one pass. + +3. **Performance tests** with randomly generated data of various sizes: + - 1 KB + - 10 KB + - 100 KB + - 1 MB + - 10 MB + +The test program performs the following operations: +- Generates random test data for each size +- Calculates the checksum for each data buffer +- Measures the execution time +- Calculates and displays the throughput in MB/s + +The test output will show: +- Results from verification tests against known checksums +- Comparison between direct and incremental calculation +- Performance metrics for each data size + +To compile and run this test, you'll need to use a command like: + +gcc -Wall -O2 adler32-simple.c adler32-test.c -o adler32-test +./adler32-test + +If you'd like me to create the Makefile for easier compilation, just let me know. + +``` + +You now have a test program which confirms the Adler32 algorithm is working correctly for various input data and can measure the performance of the previously generated C function. + +Continue to the next section to run the test program and see the results. + From 215bad358a101faed3ca192fae31dea57fad8506 Mon Sep 17 00:00:00 2001 From: Kiel Friedt Date: Mon, 21 Apr 2025 18:25:20 -0700 Subject: [PATCH 062/122] added details on 3 nodes replica and more detail in general on the setup and testing of mongodb --- .../mongodb/benchmark_mongodb-8.0.md | 10 +- .../{replica.md => create_replica_set.md} | 159 +++++++++--------- .../mongodb/mongodb_configuration.md | 84 --------- .../mongodb/perf_mongodb.md | 6 +- .../mongodb/replica_set_testing.md | 49 ++++++ .../mongodb/run_mongodb.md | 2 +- 6 files changed, 142 insertions(+), 168 deletions(-) rename content/learning-paths/servers-and-cloud-computing/mongodb/{replica.md => create_replica_set.md} (50%) delete mode 100644 content/learning-paths/servers-and-cloud-computing/mongodb/mongodb_configuration.md create mode 100644 content/learning-paths/servers-and-cloud-computing/mongodb/replica_set_testing.md diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md b/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md index fd13c2030d..8723e40ea7 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md @@ -28,7 +28,7 @@ sudo yum install python2 {{< /tab >}} {{< /tabpane >}} -For Ubuntu 22.04 and 24.04, Python 2 is not available using the package manager. +For Ubuntu 22.04 and 24.04, Python 2 is not available using the package manager. You can install Python 2.7 using: @@ -61,16 +61,16 @@ cd ycsb-0.17.0 ./bin/ycsb ``` -## Load/Insert Test on MongoDB +## A simple Load/Insert Test on MongoDB -To load and test the performance of loading data(INSERT) into default database `ycsb` at `localhost:27017` where MongoDB is running using the synchronous driver run the following command: +To load and test the performance of loading data(INSERT) into default database `ycsb` at `(localhost/Primary Node):27017` where MongoDB is running using the synchronous driver run the following command: ```console ./bin/ycsb load mongodb -s -P workloads/workloada -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 -threads 10 ``` The "-P" parameter is used to load property files. In this example, you used it load the workloada parameter file which sets the recordcount to 1000 in addition to other parameters. The "-threads" parameter indicates the number of threads and is set to 1 by default. -## Update/Read/Read Modify Write Test on MongoDB +## A simple Update/Read/Read Modify Write Test on MongoDB To test the performance of executing a workload which includes running UPDATE, Read Modify Write(RMW) and/or READ operations on the data using 10 threads for example, use the following command: @@ -115,5 +115,5 @@ At the end of each test, statistics are printed to the console. Shown below is t ... ``` -Continue to the next section to run YCSB on a 3 node cluster. +[Continue to the next section to run YCSB on a 3 node cluster.](/learning-paths/servers-and-cloud-computing/mongodb/replica_set_testing) diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/replica.md b/content/learning-paths/servers-and-cloud-computing/mongodb/create_replica_set.md similarity index 50% rename from content/learning-paths/servers-and-cloud-computing/mongodb/replica.md rename to content/learning-paths/servers-and-cloud-computing/mongodb/create_replica_set.md index dd3cb518fd..ea02b48e0b 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/replica.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/create_replica_set.md @@ -1,81 +1,40 @@ --- # User change -title: "Run YCSB using a 3 node replica set" +title: "Creating MongoDB test scenarios" -draft: true - -weight: 5 # (intro is 1), 2 is first, 3 is second, etc. +weight: 3 # (intro is 1), 2 is first, 3 is second, etc. # Do not modify these elements layout: "learningpathall" --- - -The recommended MongoDB YCSB test setup is a relica set containing three nodes of equal size. The primary node is the node you send the YCSB traffic to and the others are secondary nodes. +## MongoDB test scenarios +To test Mongodb you need two parts. A instance running the testing software([YCSB](/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0)). one or more instances running MongoDB in some configuration. The recommended MongoDB test setup is a three node relica set. These three nodes are of equal size with one instance being desigated as the primary node( the target for test traffic ) and the others as secondary nodes. ## What is a replica set? -A replica set is a group of instances that maintain the same data set. A replica set contains many nodes, but 3 nodes are used for testing. +A replica set is a group of instances that maintain the same dataset. A replica set contains many nodes, but three nodes are the most common for testing. ## What node size should I use? -The most common size for testing MongoDB is an 8 vCPU instance. You can test with any sized machine, but if you are looking for ideal testing conditions 8 vCPUs is enough. Each node should have 32GB of RAM. - -You should keep the complete data set in memory. Additional details abut the recommended configuration are provided below. - -## Create a replica set - -Create a 3 node replica set by starting 3 Arm instances with the specifications above. - -Install MongoDB on each node using the previously provided instructions. - -Select 1 instance as the primary node and install YCSB on the instance. - -## Initialize the replica set - -1. Set variables with the IP addresses of each node: +The most common size for testing MongoDB is an 8 vCPU instance. You can test with any sized instance, but if you are looking for ideal testing conditions, 8 vCPUs is enough. Each node should have atleast 32GB of RAM. - ```bash - PRIMARY_NODE_IP="" - SECONDARY_NODE1_IP="" - SECONDARY_NODE2_IP="" - ``` +To achieve the best results, its recommended to keep the complete data set in memory. If you see disk access when running tests, increase the RAM size of your instances. Additional details about the recommended configuration are provided below. -2. Connect to the primary node using the MongoDB shell: - - ```bash - mongosh --host :27017 - ``` +## Creating replica sets -3. Initialize the replica set with the following command: +You can create replica sets of any size(two is the minimum). Three is recemmended but you can add as many as you like. - ```bash - PRIMARY_NODE_IP="" - SECONDARY_NODE1_IP="" - SECONDARY_NODE2_IP="" +## Three node replica sets - mongosh --host $PRIMARY_NODE_IP:27017 < Date: Tue, 22 Apr 2025 10:36:17 +0200 Subject: [PATCH 063/122] Add learning path "get started with CMSIS-DSP using Python" --- .../cmsisdsp-dev-with-python/_index.md | 71 +++ .../cmsisdsp-dev-with-python/_next-steps.md | 8 + .../cmsisdsp-dev-with-python/audiowidget.png | Bin 0 -> 8296 bytes .../cmsisdsp-dev-with-python/cleaned.png | Bin 0 -> 23575 bytes .../cmsisdsp-dev-with-python/hanning.png | Bin 0 -> 24657 bytes .../cmsisdsp-dev-with-python/how-to-1.md | 24 + .../cmsisdsp-dev-with-python/how-to-2.md | 76 ++++ .../cmsisdsp-dev-with-python/how-to-3.md | 87 ++++ .../cmsisdsp-dev-with-python/how-to-4.md | 126 ++++++ .../cmsisdsp-dev-with-python/how-to-5.md | 368 ++++++++++++++++ .../cmsisdsp-dev-with-python/how-to-6.md | 414 ++++++++++++++++++ .../cmsisdsp-dev-with-python/how-to-7.md | 112 +++++ .../cmsisdsp-dev-with-python/how-to-8.md | 27 ++ .../cmsisdsp-dev-with-python/signal.png | Bin 0 -> 28520 bytes .../cmsisdsp-dev-with-python/sumhanning.png | Bin 0 -> 35395 bytes .../cmsisdsp-dev-with-python/vad.png | Bin 0 -> 23204 bytes 16 files changed, 1313 insertions(+) create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_next-steps.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/audiowidget.png create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/cleaned.png create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/hanning.png create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-1.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-2.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-3.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-8.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/signal.png create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/sumhanning.png create mode 100644 content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/vad.png diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md new file mode 100644 index 0000000000..6058477e9d --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md @@ -0,0 +1,71 @@ +--- +title: Learn how to get started with CMSIS-DSP using Python + +minutes_to_complete: 10 + +who_is_this_for: Developers writing DSP/AI software + +learning_objectives: + - Understand how to use the CMSIS-DSP Python package + - Understand how the Python implementation maps to the C one + - Develop a complex application with CMSIS-DSP + +prerequisites: + - Some familiarity with DSP programming + - Some familiarity with Python programming + - Knowledge of C + - Some familiarity with CMSIS-DSP + - Python installed on your computer + +author: Christophe Favergeon + +### Tags +skilllevels: Advanced +subjects: Libraries +armips: + - Cortex-M + - Cortex-A +tools_software_languages: + - VS Code + - CMSIS-DSP + - Python + - C + - Jupyter Notebook +operatingsystems: + - Linux + - Windows + - macOS + + + + + +further_reading: + - resource: + title: Biquad filters with CMSIS-DSP Python package + link: https://developer.arm.com/documentation/102463/latest/ + type: documentation + - resource: + title: CMSIS-DSP library + link: https://github.com/ARM-software/CMSIS-DSP + type: Open-source project + - resource: + title: CMSIS-DSP python package + link: https://pypi.org/project/cmsisdsp/ + type: Open-source project + - resource: + title: CMSIS-DSP Python package examples and tests + link: https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples + type: Open-source project + - resource: + title: CMSIS-Stream + link: https://github.com/ARM-software/CMSIS-Stream + type: Open-source project + + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_next-steps.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/audiowidget.png b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/audiowidget.png new file mode 100644 index 0000000000000000000000000000000000000000..7d6630778b4f2fd5e8b50b7d5baa046c8ae57555 GIT binary patch literal 8296 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 literal 0 HcmV?d00001 diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/cleaned.png b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/cleaned.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb3e0fd70e962e309e3da182b79a1ac6f8c3125 GIT binary patch literal 23575 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 F{{#UgjCKG3 literal 0 HcmV?d00001 diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/hanning.png b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/hanning.png new file mode 100644 index 0000000000000000000000000000000000000000..e36e52d1a10ec7b5ab0073809f80f8e665ff80f4 GIT binary patch literal 24657 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 jZ}1: + data=data[:,0] +data = data.astype(np.float32) +data=data/np.max(np.abs(data)) +dataQ15 = fix.toQ15(data) +``` + +The code above does the following: +- Converts the audio into a NumPy array +- If the audio is stereo, only one channel is kept +- Normalizes the audio to ensure no value exceeds 1 +- Converts the audio to Q15 fixed-point representation to enable the use of CMSIS-DSP fixed-point functions + +Now, plot the audio waveform: + +```python +plt.plot(data) +plt.show() +``` + +You'll get the following output: + +![audio signal alt-text#center](signal.png "Figure 2. Audio signal") + +In the picture, you can see a sequence of words. Between the words, the signal is not zero: there is some noise. + +In a real application, you don't wait for the entire signal to be received. The signal is continuous. The samples are processed as they are received. Processing can either be sample-based or block-based. For this learning path, the processing will be block-based. + +Before you can move to the next step, this signal must be split into blocks. The processing will occur on small blocks of samples of a given duration. + + + +```python +winDuration=30e-3/6 +winOverlap=15e-3/6 + +winLength=int(np.floor(samplerate*winDuration)) +winOverlap=int(np.floor(samplerate*winOverlap)) +slices=sliding_window_view(data,winLength)[::winOverlap,:] +slices_q15=sliding_window_view(dataQ15,winLength)[::winOverlap,:] +``` + +Refer to the [NumPy documentation](https://numpy.org/doc/stable/reference/generated/numpy.lib.stride_tricks.sliding_window_view.html) for details about `sliding_window_view`. It's not the most efficient function, but it is sufficient for this tutorial. + +The signal is split into overlapping blocks: each block reuses half of the samples from the previous block as defined by the `winOverlap` variable. + +You are now ready to move on to the next step: you have an audio signal that has been split into overlapping blocks, and processing will occur on those blocks. + diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md new file mode 100644 index 0000000000..a31f4410e4 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md @@ -0,0 +1,126 @@ +--- +title: Write a simple VAD +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Write a simple voice activity detection + +To remove the noise between speech segments, you need to detect when voice is present. + +Voice activity detection can be complex, but for this learning path, you'll implement a very simple and naive approach based on energy. The idea is that if the environment isn't too noisy, speech should have more energy than the noise. + +The detection will rely on a comparison with a threshold that must be manually tuned. + +You'll first implement a version of the voice activity detection (VAD) with NumPy, which will serve as a reference. + +Then you'll implement the same version using CMSIS-DSP with the Q15 fixed-point format. + +### NumPy VAD + +First, you need to compute the energy of the signal within a block of samples. You'll ignore any constant component and focus only on the varying part of the signal: + +```python +# Energy of the window +def signal_energy(window): + w = window - np.mean(window) + return(10*np.log10(np.sum(window * window))) +``` +Then, compare the energy to a threshold to determine whether the block of audio is speech or noise: + +```python +def signal_vad(window): + if signal_energy(window)>-11: + return(1) + else: + return(0) +``` + +The threshold is hard-coded. It's not a very clean solution, but it's sufficient for a tutorial. + +When using such a detector, you'll quickly find that it is not sufficient. You'll need another pass to clean up the detection signal. + +```python +def clean_vad(v): + v = np.hstack([[0],v,[0]]) + # Remove isolated peak + vmin=[np.min(l) for l in sliding_window_view(v,3)] + vmin = np.hstack([[0,0],vmin,[0]]) + # Remove isolated hole + vmax=[np.max(l) for l in sliding_window_view(vmin,4)] + return(vmax) +``` + +Now you can apply this algorithm to the audio signal and plot the VAD detection over it to see if it's working: + +```python +_,ax=plt.subplots(1,1) +cleaned=clean_vad([signal_vad(w) for w in slices]) +vad = np.array([[w]*(winLength-winOverlap) for w in cleaned]).flatten() +ax.plot(data) +ax.plot(vad) +``` +The reference implementation works. You can now implement the same version using CMSIS-DSP. + +![vad alt-text#center](vad.png "Figure 3. VAD") + + +### CMSIS-DSP Q15 VAD + +First, you need to compute the signal energy from audio in Q15 format using CMSIS-DSP. + +If you look at the CMSIS-DSP documentation, you'll see that the power and log functions don't produce results in Q15 format. Tracking the fixed-point format throughout all lines of an algorithm can be challenging. + +For this tutorial, instead of trying to determine the exact fixed-point format of the output and applying the necessary shift to adjust the output's fixed-point format, we'll simply tune the threshold of the detection function. + +```python +def signal_energy_q15(window): + mean=dsp.arm_mean_q15(window) + # Subtracting the mean won't cause saturation + # So we use the CMSIS-DSP negate function on an array containing a single sample. + neg_mean=dsp.arm_negate_q15([mean])[0] + window=dsp.arm_offset_q15(window,neg_mean) + energy=dsp.arm_power_q15(window) + # Energy is not in Q15 format (refer to the CMSIS-DSP documentation). + energy=dsp.ssat(energy>>20,16) + dB=dsp.arm_vlog_q15([energy]) + # The output of the `vlog` is not in q15 + # The multiplication by 10 is missing compared to the NumPy + # reference implementation. + # The result of this function is not equivalent to the float implementation due to different + # formats used in intermediate computations. + # As a consequence, a different threshold must be used to compensate for these differences. + return(dB[0]) +``` + +The comparison function is very similar to the NumPy reference, but the threshold is different: + +```python +def signal_vad_q15(window): + # The threshold is not directly comparable to the float implementation + # due to the different intermediate formats used in the fixed-point implementation. + if signal_energy_q15(window)>fix.toQ15(-0.38): + return(1) + else: + return(0) +``` + +Note that in a C code, you would use the output of `fix.toQ15(-0.38)`. + +`fix.toQ15` is a utility of the Python package to convert float to fixed-point. It is not available in the CMSIS-DSP C implementation. +CMSIS-DSP C has functions like `arm_float_to_q15` which work on arrays and are meant to be used at runtime. If you need a precomputed constant, you can use a utility function like `fix.toQ15` and use the resulting value in the code. + +The clean VAD function is the same for both the NumPy and Q15 versions. + +Now you can check whether the Q15 version is working by plotting the signal and the output of the Q15 VAD algorithm. + +```python +_,ax=plt.subplots(1,1) +cleaned=clean_vad([signal_vad_q15(w) for w in slices_q15]) +vad_q15 = np.array([[w]*winOverlap for w in cleaned]).flatten() +ax.plot(data) +ax.plot(vad_q15) + +``` \ No newline at end of file diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md new file mode 100644 index 0000000000..fe078449d3 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md @@ -0,0 +1,368 @@ +--- +title: Write a noise suppression algorithm +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Write a noise suppression algorithm + +### Overlapping windows + +The blocks of audio samples you created in the previous steps will be multiplied by a Hanning window function, which looks like this: + +```python +window=dsp.arm_hanning_f32(winLength) +plt.plot(window) +plt.show() +``` + +![hanning alt-text#center](hanning.png "Figure 4. Hanning Window") + + +The slices we created are overlapping. By applying a Hanning window function and summing the slices, you can reconstruct the original signal. + +Indeed, summing two Hanning window shifted by half the width of the sample block: +![summed hanning alt-text#center](sumhanning.png "Figure 5. Summed Hanning Window") + +As result, if you multiply the overlapping blocks of samples by Hanning windows and sum the result, you can reconstruct the original signal: + + +```python +offsets = range(0, len(data),winOverlap) +offsets=offsets[0:len(slices)] +res=np.zeros(len(data)) +i=0 +for n in offsets: + res[n:n+winLength] += slices[i]*window + i=i+1 +plt.plot(res) +plt.show() +``` + +You can now listen to the recombined signal: +```python +audio2=Audio(data=res,rate=samplerate,autoplay=False) +audio2 +``` + + +This means you can process each slice independently and then recombine them at the end to produce the output signal. + +### Principle of the noise reduction + +The algorithm works in the spectral domain, so a FFT will be used. +When there is no speech (as detected with the VAD), the noise level in each frequency band is estimated. + +When speech is detected, the noise estimate is used. + +Noise filtering in each band uses a simplified Wiener filter. + +A gain is applied to the signal, defined as follow: + +``` +H(f) = S(f) / (S(f) + N(f)) + +``` +- `S(f)` is the speech spectrum. +- `N(f)` is the noise spectrum. + +``` +H(f) = 1 / (1 + N(f)/S(f)) + +``` + +For this tutorial, we assume a high SNR. The VAD relies on this assumption: the signal energy is sufficient to detect speech. +With a high signal-to-noise ratio, the transfer function can be approximated as: + +``` +H(f) ~ 1 - N(f)/S(f) + +``` + +You don't have access to `S(f)`, only to the measured `S(f) + N(f)` which will be used under the assumption that the noise is small, making the approximation acceptable: + +``` +H(f) ~ 1 - N(f) / (S(f) + N(f)) + +``` + +with `S(f) + N(f) = E(f)` + +It can be rewritten as: + +`H(f)` is approximately: + +``` +H(f) ~ (E(f) - N(f)) / E(f) +``` + +- `N(f)` is estimated when there is no speech. +- `E(f)` is the observed energy in a frequency band. + +In the Python code below, you’ll see this formula implemented as: + +```python +scaling = (energy - self._noise)/energy +``` + +(Don’t evaluate this Python code in your Jupyter notebook—it will be run later as part of the full implementation.) + +### NoiseSuppressionReference class + +The entire algorithm will be packaged as a Python class. +The class functions are explained below using Python code that should not be evaluated in the Jupyter notebook. + +You should only evaluate the full class definition in the Jupyter notebook—not the code snippets used for explanation. + + +#### NoiseSuppressionReference constructor + + + +The constructor: +- Uses the audio slices as input +- Computes the VAD signal for the full audio signal +- Applies the Hanning window to each slice +- Computes the FFT length that can be used for each slice +- Computes the padding needed for the FFT + +The FFT length must be a power of 2. The slice length is not necessarily a power of 2. The constructor computes the closest usable power of 2. The audio slices are padded with zeros on both sides to match the required FFT length. + + +#### subnoise +```python +def subnoise(self,v): + # This is a Wiener estimate. + energy = v * np.conj(v) + 1e-6 + + scaling = (energy - self._noise)/energy + scaling[scaling<0] = 0 + + return(v * scaling) +``` + +This function computes the approximate Wiener gain. +If the gain is negative, it is set to 0. +A small value is added to the energy to avoid division by zero. +This function is applied to all frequency bands of the FFT. The `v` argument is a vector. + +#### remove_noise +```python +def remove_noise(self,w): + # We pad the signal with zeros. This assumes the padding is divisible by 2. + # A more robust implementation would also handle the odd-length case. + # The FFT length is greater than the window length and must be a power of 2. + sig=self.window_and_pad(w) + + # FFT + fft=np.fft.fft(sig) + # Noise suppression + fft = self.subnoise(fft) + # IFFT + res=np.fft.ifft(fft) + # We assume the result should be real, so we ignore the imaginary part. + res=np.real(res) + # We remove the padding. + res=self.remove_padding(res) + return(res) +``` + +The function computes the FFT (with padding) and reduces noise in the frequency bands using the approximate Wiener gain. + +#### estimate_noise +```python + def estimate_noise(self,w): + # Compute the padded signal. + sig=self.window_and_pad(w) + fft=np.fft.fft(sig) + + # Estimate the noise energy. + self._noise = np.abs(fft)*np.abs(fft) + + # Remove the noise. + fft = self.subnoise(fft) + + # Perform the IFFT, assuming the result is real, so we ignore the imaginary part. + res=np.fft.ifft(fft) + res=np.real(res) + res=self.remove_padding(res) + return(res) +``` + +This function is very similar to the previous one. +It's used when no speech detected. +It updates the noise estimate before reducing the noise. + + +#### nr + +```python +def nr(self): + for (w,v) in zip(self._slices,self._vad): + result=None + if v==1: + # If voice is detected, we only remove the noise. + result=self.remove_noise(w) + else: + # If no voice is detected, we update the noise estimate. + result=self.estimate_noise(w) + self._signal.append(result) +``` + +The main function: it removes noise from each slice. +If a slice does not contain speech, the noise estimate is updated before reducing noise in each frequency band. + +#### overlap_and_add + +The filtered slices are recombined: + +```python +def overlap_and_add(self): + offsets = range(0, len(self._signal)*winOverlap,winOverlap) + offsets=offsets[0:len(self._signal)] + res=np.zeros(len(data)) + i=0 + for n in offsets: + res[n:n+winLength]+=self._signal[i] + i=i+1 + return(res) +``` + +### The final code for the Python class +```python +def fft_length(length): + result=2 + fft_shift=1 + while result < length: + result = 2*result + fft_shift = fft_shift + 1 + return(result,fft_shift) + +class NoiseSuppression(): + def __init__(self,slices): + self._windowLength=len(slices[0]) + self._fftLen,self._fftShift=fft_length(self._windowLength) + + self._padding_left=(self._fftLen - self._windowLength)//2 + self._padding_right=self._fftLen- self._windowLength-self._padding_left + + self._signal=[] + self._slices=slices + self._window=None + + def window_and_pad(self,w): + if w.dtype==np.int32: + w=dsp.arm_mult_q31(w,self._window) + elif w.dtype==np.int16: + w=dsp.arm_mult_q15(w,self._window) + else: + w = w*self._window + sig=np.hstack([np.zeros(self._padding_left,dtype=w.dtype),w,np.zeros(self._padding_right,dtype=w.dtype)]) + return(sig) + + def remove_padding(self,w): + return(w[self._padding_left:self._padding_left+self._windowLength]) + +class NoiseSuppressionReference(NoiseSuppression): + def __init__(self,slices): + # In a better version this could be computed from the signal length by taking the + # smaller power of two greater than the signal length. + NoiseSuppression.__init__(self,slices) + + # Compute the vad signal + self._vad=clean_vad([signal_vad(w) for w in slices]) + self._noise=np.zeros(self._fftLen) + # The Hann window + self._window=dsp.arm_hanning_f32(self._windowLength) + + # Subtract the noise + def subnoise(self,v): + # This is a Wiener estimate + energy = v * np.conj(v) + 1e-6 + + scaling = (energy - self._noise)/energy + scaling[scaling<0] = 0 + + return(v * scaling) + + def remove_noise(self,w): + # We pad the signal with zero. It assumes that the padding can be divided by 2. + # In a better implementation we would manage also the odd case. + # The padding is required because the FFT has a length which is greater than the length of + # the window + sig=self.window_and_pad(w) + + # FFT + fft=np.fft.fft(sig) + # Noise suppression + fft = self.subnoise(fft) + # IFFT + res=np.fft.ifft(fft) + # We assume the result should be real so we just ignore the imaginary part + res=np.real(res) + # We remove the padding + res=self.remove_padding(res) + return(res) + + + + def estimate_noise(self,w): + # Compute the padded signal + sig=self.window_and_pad(w) + fft=np.fft.fft(sig) + + # Estimate the noise energy + self._noise = np.abs(fft)*np.abs(fft) + + # Remove the noise + fft = self.subnoise(fft) + + # IFFT and we assume the result is real so we ignore imaginary part + res=np.fft.ifft(fft) + res=np.real(res) + res=self.remove_padding(res) + return(res) + + # Process all the windows using the VAD detection + def nr(self): + for (w,v) in zip(self._slices,self._vad): + result=None + if v==1: + # If voice detected, we only remove the noise + result=self.remove_noise(w) + else: + # If no voice detected, we update the noise estimate + result=self.estimate_noise(w) + self._signal.append(result) + + # Overlap and add to rebuild the signal + def overlap_and_add(self): + offsets = range(0, len(self._signal)*winOverlap,winOverlap) + offsets=offsets[0:len(self._signal)] + res=np.zeros(len(data)) + i=0 + for n in offsets: + res[n:n+winLength]+=self._signal[i] + i=i+1 + return(res) +``` +You can now test this algorithm on the original signal: + +```python +n=NoiseSuppressionReference(slices) +n.nr() +cleaned=n.overlap_and_add() +plt.plot(cleaned) +plt.show() +``` + +![cleaned alt-text#center](cleaned.png "Figure 6. Cleaned signal") + +You can now listen to the result: + +```python +audioRef=Audio(data=cleaned,rate=samplerate,autoplay=False) +audioRef +``` \ No newline at end of file diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md new file mode 100644 index 0000000000..45d54dbdd8 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md @@ -0,0 +1,414 @@ +--- +title: Write the CMSIS-DSP Q15 implementation +weight: 7 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Write the CMSIS-DSP Q15 implementation + +### Slicing + +The CMSIS-DSP implementation is very similar to the reference implementation you just tested. + +With the following code, you’ll check that recombining the windowed block samples works correctly. +Since the Q15 representation is less accurate than float and can saturate, it’s a good idea to verify the recombination step. + +The Hanning window is converted to Q15 format. Then, the slices are multiplied by the Q15 Hanning window and summed. +The final result is converted to float. + +```python +offsets = range(0, len(data),winOverlap) +offsets=offsets[0:len(slices_q15)] +res=np.zeros(len(data)) +window_q15=fix.toQ15(window) +i=0 +for n in offsets: + w = dsp.arm_mult_q15(slices_q15[i],window_q15) + res[n:n+winLength] = dsp.arm_add_q15(res[n:n+winLength],w) + i=i+1 +res_q15=fix.Q15toF32(res) +plt.plot(res_q15) +plt.show() +``` +You can now listen to the audio to check the result: +```python +audio4=Audio(data=res_q15,rate=samplerate,autoplay=False) +audio4 +``` +### Utilities + +CMSIS-DSP does not have a complex data type. Complex numbers are represented as a float array with alternating real and imaginary parts: real, imaginary, real, imaginary, and so on. + +You’ll need functions to convert to and from NumPy complex arrays. + +```python +def imToReal1D(a): + ar=np.zeros(np.array(a.shape) * 2) + ar[0::2]=a.real + ar[1::2]=a.imag + return(ar) + +def realToIm1D(ar): + return(ar[0::2] + 1j * ar[1::2]) +``` + +## The final Q15 implementation + +Try the final implementation first, and then we’ll analyze the differences from the reference implementation. + +```python +class NoiseSuppressionQ15(NoiseSuppression): + def __init__(self,slices): + NoiseSuppression.__init__(self,slices) + + # VAD signal. + self._vad= clean_vad(np.array([signal_vad_q15(w) for w in slices])) + self._noise=np.zeros(self._fftLen,dtype=np.int32) + # Q15 version of the Hanning window. + self._window=fix.toQ15(dsp.arm_hanning_f32(self._windowLength)) + # CFFT Q15 instance. + self._cfftQ15=dsp.arm_cfft_instance_q15() + status=dsp.arm_cfft_init_q15(self._cfftQ15,self._fftLen) + + self._noise_status = -1 + self._noise_max = 0x7FFF + + + # Subtract the noise. + def subnoise(self,v,status,the_max): + + # We cannot compute the energy in Q15, because many values would otherwise be 0 + # The noise signal is too small for its energy to be representable in Q15. + # So we convert to Q31 and perform noise subtraction in Q31. + vq31 = dsp.arm_q15_to_q31(v) + energy = dsp.arm_cmplx_mag_squared_q31(vq31) + + # The energy for the signal and noise were computed on a rescaled signal. + # So, we remove the scaling from the values before computing the ratio (energy - noise) / energy. + # `status == 0` means the signal has been rescaled. + if status==0: + the_max_q31=dsp.arm_q15_to_q31([the_max])[0] + energy=dsp.arm_scale_q31(energy,the_max_q31,0) + energy=dsp.arm_scale_q31(energy,the_max_q31,0) + + noise = self._noise + # `status == 0` means the noise has been rescaled. + if self._noise_status==0: + the_max_q31=dsp.arm_q15_to_q31([self._noise_max])[0] + noise=dsp.arm_scale_q31(noise,the_max_q31,0) + noise=dsp.arm_scale_q31(noise,the_max_q31,0) + + + temp = dsp.arm_sub_q31(energy , noise) + temp[temp<0]=0 + + scalingQ31 = np.zeros(len(temp),dtype=np.int32) + shift = np.zeros(len(temp),dtype=np.int32) + + # The scaling factor `(energy - noise) / energy` is computed. + k=0 + # We assume that `|energy - noise|<=energy` + # Otherwise, we set scaling to `1` + # If energy is `0`, we also set scaling to `1`. + # When `a == b`, `shiftVal` is equal to `1` because `1` (as the result of the division operator) + # is represented as `0x40000000` with a shift of `1` instead of `0x7FFFFFFF` for output of division + # We handle this case separately + for a,b in zip(temp,energy): + quotient=0x7FFFFFFF + shiftVal=0 + if b!=0 and a!=b: + # We compute the quotient. + status,quotient,shiftVal = dsp.arm_divide_q31(a,b) + if shiftVal > 0: + quotient=0x7FFFFFFF + shiftVal = 0 + + scalingQ31[k] = quotient + shift[k] = shiftVal + + k = k + 1 + + + res=dsp.arm_cmplx_mult_real_q31(vq31,scalingQ31) + resQ15 = dsp.arm_q31_to_q15(res) + + return(resQ15) + + # To achieve maximum accuracy with the Q15 FFT, the signal is rescaled before computing the FFT + # It is divided by its maximum value. + def rescale(self,w): + the_max,index=dsp.arm_absmax_q15(w) + + quotient=0x7FFF + the_shift=0 + status = -1 + if the_max != 0: + status,quotient,the_shift = dsp.arm_divide_q15(0x7FFF,the_max) + if status == 0: + w=dsp.arm_scale_q15(w,quotient,the_shift) + return(w,status,the_max) + + # The scaling is removed after the IFFT is computed. + def undo_scale(self,w,the_max): + w=dsp.arm_scale_q15(w,the_max,0) + return(w) + + + def remove_noise(self,w): + w,status,the_max = self.rescale(w) + sig=self.window_and_pad(w) + + # Convert to complex. + signalR=np.zeros(len(sig) * 2,dtype=np.int16) + signalR[0::2]=sig + + + if dsp.has_neon(): + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,tmp=self._tmp) + else: + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,1) + + resultR = self.subnoise(resultR,status,the_max) + + if dsp.has_neon(): + res = dsp.arm_cfft_q15(self._cfftQ15,resultR,1,tmp=self._tmp) + else: + res = dsp.arm_cfft_q15(self._cfftQ15,resultR,1,1) + res = dsp.arm_shift_q15(res,self._fftShift) + + res=res[0::2] + res=self.remove_padding(res) + + if status == 0: + res=self.undo_scale(res,the_max) + return(res) + + def estimate_noise(self,w): + w,status,the_max = self.rescale(w) + self._noise_status = status + self._noise_max = the_max + + sig=self.window_and_pad(w) + + signalR=np.zeros(len(sig) * 2) + signalR[0::2]=sig + + if dsp.has_neon(): + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,tmp=self._tmp) + else: + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,1) + + resultRQ31 = dsp.arm_q15_to_q31(resultR) + + + self._noise = dsp.arm_cmplx_mag_squared_q31(resultRQ31) + + + resultR = np.zeros(len(resultR),dtype=np.int16) + + if dsp.has_neon(): + res = dsp.arm_cfft_q15(self._cfftQ15,resultR,1,tmp=self._tmp) + else: + res = dsp.arm_cfft_q15(self._cfftQ15,resultR,1,1) + res = dsp.arm_shift_q15(res,self._fftShift) + + res=res[0::2] + res=self.remove_padding(res) + + if status == 0: + res=self.undo_scale(res,the_max) + + return(res) + + def do_nothing(self,w): + w,status,the_max = self.rescale(w) + sig=self.window_and_pad(w) + + + # Convert to complex. + signalR=np.zeros(len(sig) * 2,dtype=np.int16) + signalR[0::2]=sig + + + if dsp.has_neon(): + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,tmp=self._tmp) + res = dsp.arm_cfft_q15(self._cfftQ15,resultR,1,tmp=self._tmp) + else: + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,1) + res = dsp.arm_cfft_q15(self._cfftQ15,resultR,1,1) + res = dsp.arm_shift_q15(res,self._fftShift) + + res=res[0::2] + + res=self.remove_padding(res) + + if status == 0: + res=self.undo_scale(res,the_max) + + return(res) + + + def nr(self,nonr=False): + if dsp.has_neon(): + tmp_nb = dsp.arm_cfft_tmp_buffer_size(dt.Q15,self._fftLen,1) + self._tmp = np.zeros(tmp_nb,dtype=np.int16) + for (w,v) in zip(self._slices,self._vad): + result=None + if nonr: + result = self.do_nothing(w) + else: + if v==1: + result=self.remove_noise(w) + else: + result=self.estimate_noise(w) + self._signal.append(result) + + def overlap_and_add(self): + nbSamples = len(self._signal)*winOverlap + offsets = range(0, nbSamples,winOverlap) + offsets=offsets[0:len(self._signal)] + res=np.zeros(nbSamples,dtype=np.int16) + i=0 + for n in offsets: + res[n:n+winLength] = dsp.arm_add_q15(res[n:n+winLength],self._signal[i]) + i=i+1 + return(res) +``` + +Verify that the Q15 algorithm is working: + +```python +n=NoiseSuppressionQ15(slices_q15) +n.nr() +cleaned_q15=n.overlap_and_add() +plt.plot(fix.Q15toF32(cleaned_q15)) +plt.show() +``` + +You can now listen to the result: + +```python +audioQ15=Audio(data=fix.Q15toF32(cleaned_q15),rate=samplerate,autoplay=False) +audioQ15 +``` + +## Differences with the float implementation + +There are many differences from the original float implementation, which are explained below. + +### constructor + +The constructor is similar and uses Q15 instead of float. The Hanning window is converted to Q15, and Q15 versions of the CFFT objects are created. + +### subnoise + +The noise reduction function is more complex for several reasons: + +- Q15 is not accurate enough for the energy computation. Q31 is used instead. For instance: +```python +vq31 = dsp.arm_q15_to_q31(v) +energy = dsp.arm_cmplx_mag_squared_q31(vq31) +``` + +- For maximum accuracy, the signal is rescaled before calling this function. Since energy is not a linear function, the scaling factor must be compensated when computing the Wiener gain. The argument `status` is zero when the scaling has been applied. A similar scaling factor is applied to the noise: +```python +if status==0: + the_max_q31=dsp.arm_q15_to_q31([the_max])[0] + energy=dsp.arm_scale_q31(energy,the_max_q31,0) + energy=dsp.arm_scale_q31(energy,the_max_q31,0) +``` + +- CMSIS-DSP fixed-point division represents 1 exactly. So in Q31, instead of using `0x7FFFFFFF`, `1` is represented as `0x40000000` with a shift of `1`. This behavior is handled in the algorithm when converting the scaling factor to an approximate Q31 value: +```python +status,quotient,shiftVal = dsp.arm_divide_q31(a,b) +if shiftVal > 0: + quotient=0x7FFFFFFF + shiftVal = 0 +``` + +- The final scaling is performed using a Q31 multiplication, and the result is converted back to Q15: +```python +res = dsp.arm_cmplx_mult_real_q31(vq31,scalingQ31) +resQ15 = dsp.arm_q31_to_q15(res) +``` + +### rescaling + +To achieve maximum accuracy in Q15, the signal (and noise) is rescaled before computing the energy. +This rescaling function did not exist in the float implementation. The signal is divided by its maximum value to bring it to full scale:: + +```python +def rescale(self,w): + the_max,index=dsp.arm_absmax_q15(w) + + quotient=0x7FFF + the_shift=0 + status = -1 + if the_max != 0: + status,quotient,the_shift = dsp.arm_divide_q15(0x7FFF,the_max) + if status == 0: + w=dsp.arm_scale_q15(w,quotient,the_shift) + return(w,status,the_max) +``` + +The scaling must be reversed after the IFFT to allow recombining the slices and reconstructing the signal: + +```python +def undo_scale(self,w,the_max): + w=dsp.arm_scale_q15(w,the_max,0) + return(w) +``` + +### noise suppression + +The algorithm closely follows the float implementation. +However, there is a small difference because CMSIS-DSP can be built for Cortex-A and Cortex-M. On Cortex-A, there are small differences in the FFT API, as it uses a different implementation. + +If the Python package has been built with Neon acceleration, it will use the new API that requires an additional temporary buffer. + +If this temporary buffer is not provided, the Python package will allocate it automatically. While you can use the same API, this is less efficient. + +It is better to detect than the package have been compiled with Neon acceleration, allocate a temporary buffer and use it in the FFT calls. + +```python +if dsp.has_neon(): + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,tmp=self._tmp) +else: + resultR = dsp.arm_cfft_q15(self._cfftQ15,signalR,0,1) +``` + +In the Neon version, the FFT’s bit-reversal flag is no longer available. It’s not possible to disable bit reversal in the Neon version. + +A scaling factor must be applied to the IFFT output: + +```python +res = dsp.arm_shift_q15(res,self._fftShift) +``` + +This scaling is unrelated to the signal and noise scaling used for improved accuracy. + +The output of the Q15 IFFT is not in Q15 format and must be converted. This is typical of fixed-point FFTs, and the same applies to Q31 FFTs. + +Finally, the accuracy-related scaling factor is removed at the end of the function: + +```python +if status == 0: + res=self.undo_scale(res,the_max) +``` + +### noise estimation + +The noise estimation function performs both noise estimation and noise suppression. + +Noise energy is computed in Q31 for higher accuracy. +The FFT functions detect whether the package was built with Neon support. + +### donothing + +`donothing` is a debug function. You can disable noise reduction and test only slicing, overlap-add, and the FFT/IFFT in between. + +This function applies scaling and performs the FFT/IFFT. + +It’s a good way to check for saturation issues (which are common with fixed-point arithmetic) and to ensure proper scaling compensation. diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md new file mode 100644 index 0000000000..bd833cd900 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md @@ -0,0 +1,112 @@ +--- +title: Convert the CMSIS-DSP Python to C +weight: 8 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Convert the CMSIS-DSP Python to C + +Once the Python code is working, writing the C code should be straightforward, since the CMSIS-DSP Python wrapper’s API closely mirrors the C API. + +### Rescaling +For example, let’s look at rescaling +#### Python version + +```python +def rescale(self,w): + the_max,index=dsp.arm_absmax_q15(w) + + quotient=0x7FFF + the_shift=0 + status = -1 + if the_max != 0: + status,quotient,the_shift = dsp.arm_divide_q15(0x7FFF,the_max) + if status == 0: + w=dsp.arm_scale_q15(w,quotient,the_shift) + return(w,status,the_max) +``` +#### C Version + + +```C +arm_status rescale(q15_t *w, uint32_t nb,q15_t *the_max) +{ + uint32_t index; + q15_t quotient = 0x7FFF; + /* Default status value for signal is zero so can't be rescaled */ + arm_status status = ARM_MATH_SINGULAR; + int16_t the_shift = 0; + *the_max=0; + + arm_absmax_q15(w,nb,the_max,&index); + if (*the_max != 0) + { + status = arm_divide_q15(0x7FFF,*the_max,"ient,&the_shift); + if (status == ARM_MATH_SUCCESS) + { + arm_scale_q15(w,quotient,the_shift,w,nb); + } + } + + return(status); + +} + +``` + +### Signal energy + +#### Python version +```python +def signal_energy_q15(window): + mean=dsp.arm_mean_q15(window) + # If we subtract the mean, we won't get saturation. + # So we use the CMSIS-DSP negate function on an array containing a single sample. + neg_mean=dsp.arm_negate_q15([mean])[0] + window=dsp.arm_offset_q15(window,neg_mean) + energy=dsp.arm_power_q15(window) + # Energy is not in Q15 (refer to CMSIS-DSP documentation). + energy=dsp.ssat(energy>>20,16) + dB=dsp.arm_vlog_q15([energy]) + # The output of the `vlog` is not in Q15 + # The multiplication by `10` is missing compared to the NumPy + # reference implementation. + # The result of this function is not equivalent to the float implementation due to the different + # formats used in the intermediate computations. + # As a consequence, a different threshold will have to be used + # to compensate for these differences. + return(dB[0]) +``` + +#### C version +```C +int16_t signal_energy_q15(q15_t *window,unit32_t nb) +{ + q15_t mean,neg_mean; + arm_mean_q15(window,nb,&mean); + + arm_negate_q15(&mean,&mean,1); + + arm_offset_q15(window,neg_mean,window,nb); + + q63_t energy_q63; + q15_t energy; + arm_power_q15(window,nb,&energy_q63); + + energy=(q15_t)_SSAT((q31_t)(energy_q63>>20),16); + + // Fixed point format of result is on 16 bit + // but the specific format has not been identified + // to make this tutorial easier. + // We just know it is not q15 + int16_t dB; + + arm_vlog_q15(&energy,&dB,1); + + return(dB) +} +``` + +A DSP function written in Python using CMSIS-DSP can be easily converted into a similar C function. diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-8.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-8.md new file mode 100644 index 0000000000..5fc02c677a --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-8.md @@ -0,0 +1,27 @@ +--- +title: Study more examples +weight: 9 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Study more examples + +The [CMSIS-DSP python example folder](https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples) contains many tests, examples, and some Jupyter notebooks. + +You can study these examples to gain a better understanding of how to use the Python package. + +The [CMSIS-DSP python package](https://pypi.org/project/cmsisdsp/) describes the differences between the Python API and the C API. + + +## Remaining issues + +The CMSIS-DSP Python package helps to design and translate a DSP function working on a block of samples from Python to C. +But in a real application, you don’t receive blocks of samples, but rather a continuous stream. + +The stream of samples must be split into blocks before the DSP function can be used. The processed blocks may need to be recombined to reconstruct a signal. + +Part of the difficulty in this learning path comes from splitting and recombining the signal. Translating this part of the Python code to C adds further complexity. + +[CMSIS-Stream](https://github.com/ARM-software/CMSIS-Stream) may help for this. It is a platform-independent technology designed to simplify the use of block-processing functions with sample streams. It is a low-overhead solution. diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/signal.png b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/signal.png new file mode 100644 index 0000000000000000000000000000000000000000..83640fdbe54dbcb81a3bac150de09d38d84ebb03 GIT binary patch literal 28520 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

>GJ;+fF*l%nYN^S;0SybK;6&PN0RaN>a#D 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 literal 0 HcmV?d00001 diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/sumhanning.png b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/sumhanning.png new file mode 100644 index 0000000000000000000000000000000000000000..e9a8ca4da5a4d5ab8ebe79b98f0455847390a858 GIT binary patch literal 35395 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 literal 0 HcmV?d00001 diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/vad.png b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/vad.png new file mode 100644 index 0000000000000000000000000000000000000000..7b2d0195b7ea2254854e3b16568ab3aaf0881ffc GIT binary patch literal 23204 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 literal 0 HcmV?d00001 From 4ceefce483fe7825913960ea3cca78de1a85fcbc Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Tue, 22 Apr 2025 10:56:48 +0000 Subject: [PATCH 064/122] Reviewed index --- .../cca-veraison-aws/_index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md index 09f29aed1f..9c929962f7 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md @@ -1,5 +1,5 @@ --- -title: Build a CCA Attestation Service in AWS with Veraison +title: Build a CCA Attestation Service on AWS with Veraison draft: true cascade: @@ -7,17 +7,17 @@ cascade: minutes_to_complete: 90 -who_is_this_for: This Learning Path is for developers who understand the basics of CCA attestation and the Veraison project, and who wish to progress onto creating a more scalable deployment of a CCA attestation verifier service in the cloud. +who_is_this_for: This Learning Path is for developers familiar with CCA attestation and the Veraison project. You'll learn how to deploy a scalable CCA attestation verifier service on AWS. learning_objectives: - - Create an attestation service in the AWS cloud using components from the Veraison project. - - Prepare the Veraison service to act as a verifier for Arm CCA attestation tokens by provisioning CCA platform endorsements. + - Build an attestation service on AWS using the Veraison project's components. + - Set up Veraison as a verifier for Arm CCA attestation tokens by provisioning CCA platform endorsements. prerequisites: - - An [AWS account](/learning-paths/servers-and-cloud-computing/csp/aws/) for accessing AWS cloud services. - - An x86 computer running Ubuntu or Arch Linux, which is authorized to use the AWS account. Other build environments might be possible, but will require the configuration of toolchains for cross-compilation. + - An [AWS account](/learning-paths/servers-and-cloud-computing/csp/aws/) with access to AWS services. + - An x86 computer running Ubuntu or Arch Linux, authorized for AWS access. If you're using another build environment, you'll need to configure the toolchains for cross-compilation. author: Paul Howard From df078ee16fdf987408171ea3e28b2d2bf17250f6 Mon Sep 17 00:00:00 2001 From: Christophe Favergeon Date: Tue, 22 Apr 2025 13:26:12 +0200 Subject: [PATCH 065/122] Enable mathematical typesetting globally. and rework the learning path `CMSIS-DSP with Python` to use LaTeX for mathematical typesetting. It would be better to be able to enable math mode only for specific learning paths. --- config.toml | 8 +++++ .../cmsisdsp-dev-with-python/how-to-5.md | 35 ++++++------------- .../layouts/_default/baseof.html | 4 +++ .../layouts/partials/math.html | 12 +++++++ 4 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 themes/arm-design-system-hugo-theme/layouts/partials/math.html diff --git a/config.toml b/config.toml index ee086f99cc..a262d66618 100644 --- a/config.toml +++ b/config.toml @@ -27,6 +27,12 @@ cloudFrontDistributionID = "E2NEF61QWPFRIH" [markup.goldmark] [markup.goldmark.renderer] unsafe = true + [markup.goldmark.extensions] + [markup.goldmark.extensions.passthrough] + enable = true + [markup.goldmark.extensions.passthrough.delimiters] + block = [['\[', '\]'], ['$$', '$$']] + inline = [['\(', '\)']] [frontmatter] lastmod = ["lastmod", ":git", "date", "publishDate"] @@ -83,3 +89,5 @@ title = 'Arm Learning Paths' description = 'Tutorials with code examples, created by the Arm ecosystem to develop better code faster across all platforms: Servers, phones, laptops, embedded devices, and microcontrollers.' social_image = '/img/social-image.png' twitter_handle = '@ArmSoftwareDev' + +math = true diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md index fe078449d3..4b2c8fed07 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md @@ -61,45 +61,32 @@ Noise filtering in each band uses a simplified Wiener filter. A gain is applied to the signal, defined as follow: -``` -H(f) = S(f) / (S(f) + N(f)) - -``` -- `S(f)` is the speech spectrum. -- `N(f)` is the noise spectrum. +$$H(f) = \frac{S(f)}{S(f) + N(f)}$$ -``` -H(f) = 1 / (1 + N(f)/S(f)) +- \(S(f)\) is the speech spectrum. +- \(N(f)\) is the noise spectrum. -``` +$$H(f) = \frac{1}{1 + \frac{N(f)}{S(f)}}$$ For this tutorial, we assume a high SNR. The VAD relies on this assumption: the signal energy is sufficient to detect speech. With a high signal-to-noise ratio, the transfer function can be approximated as: -``` -H(f) ~ 1 - N(f)/S(f) +$$H(f) \approx 1 - \frac{N(f)}{S(f)}$$ -``` +You don't have access to \(S(f)\), only to the measured \(S(f) + N(f)\) which will be used under the assumption that the noise is small, making the approximation acceptable: -You don't have access to `S(f)`, only to the measured `S(f) + N(f)` which will be used under the assumption that the noise is small, making the approximation acceptable: +$$H(f) \approx 1 - \frac{N(f)}{S(f) + N(f)}$$ -``` -H(f) ~ 1 - N(f) / (S(f) + N(f)) -``` +with \(S(f) + N(f) = E(f)\) -with `S(f) + N(f) = E(f)` +- \(E(f)\) is the observed energy in a frequency band. It can be rewritten as: -`H(f)` is approximately: - -``` -H(f) ~ (E(f) - N(f)) / E(f) -``` +$$H(f) \approx \frac{E(f) - N(f)}{E(f)}$$ -- `N(f)` is estimated when there is no speech. -- `E(f)` is the observed energy in a frequency band. +- \(N(f)\) is estimated when there is no speech. In the Python code below, you’ll see this formula implemented as: diff --git a/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html b/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html index 3d2d0da93a..ca5cc446bf 100644 --- a/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html +++ b/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html @@ -9,6 +9,10 @@ {{partial "head/analytics.html" .}} {{partial "head/conditional-redirect.html" .}} {{partial "head/hotjar-surveys.html" .}} + + {{ if .Param "math" }} + {{ partialCached "math.html" . }} + {{ end }} {{partial "header/nav-masthead.html" .}} diff --git a/themes/arm-design-system-hugo-theme/layouts/partials/math.html b/themes/arm-design-system-hugo-theme/layouts/partials/math.html new file mode 100644 index 0000000000..1f74076371 --- /dev/null +++ b/themes/arm-design-system-hugo-theme/layouts/partials/math.html @@ -0,0 +1,12 @@ + + \ No newline at end of file From c16ad4436468b05d15064dbb27719b674ddfe8f3 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 22 Apr 2025 13:53:41 +0200 Subject: [PATCH 066/122] Update acfl.md, oci-cli.md, fix patch.py bug --- content/install-guides/acfl.md | 10 +++++----- content/install-guides/oci-cli.md | 2 +- tools/patch.py | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 8f10fd154a..671ec75ed2 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -162,7 +162,7 @@ sudo apt update Install Arm Compiler for Linux with: -```consoles +```console sudo apt install acfl -y ``` @@ -226,7 +226,7 @@ Set up the environment, for example, in your `.bashrc` and add module files. The #### Ubuntu Linux: -```bash { target="ubuntu:latest" pre_cmd=". /usr/share/modules/init/bash; module use /opt/arm/modulefiles" } +```bash { target="ubuntu:latest" pre_cmd=". /usr/share/modules/init/bash" pre_cmd="module use /opt/arm/modulefiles" } echo ". /usr/share/modules/init/bash" >> $HOME/.bashrc echo "module use /opt/arm/modulefiles" >> $HOME/.bashrc source $HOME/.bashrc @@ -235,7 +235,7 @@ module avail #### Red Hat or Amazon Linux: -```bash { target="fedora:latest" env_source="/usr/share/modules/init/bash" pre_cmd="module use /opt/arm/modulefiles" } +```bash { target="fedora:latest" env_source="/usr/share/Modules/init/bash" pre_cmd="module use /opt/arm/modulefiles" } echo ". /usr/share/Modules/init/bash" >> $HOME/.bashrc echo "module use /opt/arm/modulefiles" >> $HOME/.bashrc source $HOME/.bashrc @@ -309,7 +309,7 @@ ACfL is now [ready to use](#armclang). To get started with the Arm C/C++ Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```bash { env_source="$HOME/.bashrc", pre_cmd=". /usr/share/modules/init/bash; module use /opt/arm/modulefiles; module load acfl/24.10.1" } armclang --version ``` @@ -347,7 +347,7 @@ Hello, C World! To get started with the Arm Fortran Compiler and compile a simple application follow the steps below. Check that the correct compiler version is being used: -```bash { env_source="~/.bashrc", pre_cmd="module load acfl/24.10.1" } +```bash { env_source="$HOME/.bashrc", pre_cmd=". /usr/share/modules/init/bash; module use /opt/arm/modulefiles; module load acfl/24.10.1" } armflang --version ``` diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index d870f8d585..de47245467 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -64,7 +64,7 @@ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scrip Verify OCI CLI is installed using the `--version` option: -```bash { target="ubuntu:latest", env_source="$HOME/.bashrc" } +```bash { target="ubuntu:latest", pre_cmd=". $HOME/.bashrc" } oci --version ``` diff --git a/tools/patch.py b/tools/patch.py index dbbad8a192..338692c1ef 100644 --- a/tools/patch.py +++ b/tools/patch.py @@ -24,7 +24,8 @@ def patch(article_path: str, results: dict, link: str): content_type, subdirectory, content_title, *others = article_path_parts else: content_type, content_title, *others = article_path_parts - content_title = content_title.strip(".md") + # Remove ".md" from the content title if it exists + content_title = content_title[:-3] if content_title.endswith(".md") else content_title sw_category = content_type else: raise SystemExit("Unknown content path, pass learning paths or install guides only") From 9309946b7c36e0f843503bf468ec4c6a9698e137 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 22 Apr 2025 13:56:30 +0200 Subject: [PATCH 067/122] Update Arm ASR LP --- .../04-generic_library.md | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md b/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md index e907c3b66b..8fc60785e6 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md +++ b/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md @@ -8,7 +8,7 @@ layout: learningpathall ## Introduction -Follow these steps to implement **Arm Accuracy Super Resolution (Arm ASR)** in your custom engine. +Follow these steps to implement **Arm Accuracy Super Resolution (Arm ASR)** in your custom engine. Arm ASR is an optimized variant of [Fidelity Super Resolution 2](https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/main/docs/techniques/super-resolution-temporal.md) (FSR2) that includes extensive mobile-specific optimizations, ensuring high performance on mobile devices. @@ -45,7 +45,7 @@ See the following sections to learn how to configure Arm ASR: cd accuracy-super-resolution-generic-library ``` -2. Set a variable for the package location for easy reference. +2. Set a variable for the package location for easy reference. You will use this path to refer to files in the repository: @@ -161,36 +161,6 @@ FfxErrorCode ffxmFsr2GetRenderResolutionFromUpscalingRatio( FfxmFsr2UpscalingRatio upscalingRatio) ``` -## Performance -Depending on your target hardware and operating configuration, Arm ASR will operate at different performance levels. The table below compares the rendering performance of two Arm GPUs (Immortalis-G715 and Immortalis-G720) when using different upscaling settings at two target resolutions. - - -| Target resolution | Quality | Upscaling Ratio | Immortalis-G715 | Immortalis-G720 | -|-------------------|--------------------|-----------|-----------------|-----------------| -| 2800x1260 | Quality | 1.5x | 6.5 ms | 4.1 ms | -| | | 1.7x | 6.3 ms | 3.8 ms | -| | | 2x | 6.1 ms | 3.3 ms | -| | Balanced | 1.5x | 5.8 ms | 3.3 ms | -| | | 1.7x | 5.4 ms | 3.1 ms | -| | | 2x | 4.7 ms | 2.8 ms | -| | Performance | 1.5x | 5.4 ms | 3.2 ms | -| | | 1.7x | 5.3 ms | 2.9 ms | -| | | 2x | 4.6 ms | 2.5 ms | -| 2400x1080 | Quality | 1.5x | 5.3 ms | 2.9 ms | -| | | 1.7x | 4.8 ms | 2.7 ms | -| | | 2x | 4.3 ms | 2.5 ms | -| | Balanced | 1.5x | 4.2 ms | 2.5 ms | -| | | 1.7x | 4.0 ms | 2.3 ms | -| | | 2x | 3.6 ms | 2.2 ms | -| | Performance | 1.5x | 4.1 ms | 2.4 ms | -| | | 1.7x | 3.7 ms | 2.1 ms | -| | | 2x | 3.6 ms | 2 ms | - ## Shader Variants and Extensions **Unless you are using the prebuilt shaders with the standalone VK backend**, be aware of the following definitions when integrating Arm ASR shaders: From 57d5d196a7cc5b1447df7beb5583289cbe13ab91 Mon Sep 17 00:00:00 2001 From: Christophe Favergeon Date: Tue, 22 Apr 2025 14:12:27 +0200 Subject: [PATCH 068/122] Some cleaning to the text --- .../cmsisdsp-dev-with-python/_index.md | 10 ++-- .../cmsisdsp-dev-with-python/how-to-1.md | 4 +- .../cmsisdsp-dev-with-python/how-to-5.md | 49 ++++++++++++++++--- .../cmsisdsp-dev-with-python/how-to-6.md | 2 +- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md index 6058477e9d..b149fb3eb9 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/_index.md @@ -1,21 +1,21 @@ --- -title: Learn how to get started with CMSIS-DSP using Python +title: Getting Started with CMSIS-DSP Using Python -minutes_to_complete: 10 +minutes_to_complete: 30 who_is_this_for: Developers writing DSP/AI software learning_objectives: - Understand how to use the CMSIS-DSP Python package - - Understand how the Python implementation maps to the C one - - Develop a complex application with CMSIS-DSP + - Understand how the Python implementation maps to the C implementation + - Develop a complex application using CMSIS-DSP prerequisites: - Some familiarity with DSP programming - Some familiarity with Python programming - Knowledge of C - Some familiarity with CMSIS-DSP - - Python installed on your computer + - Python installed on your system author: Christophe Favergeon diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-1.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-1.md index 40630b196c..7efc9b88da 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-1.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-1.md @@ -18,7 +18,7 @@ The CMSIS-DSP Python package is a Python API for CMSIS-DSP. Its goal is to make For this reason, the Python API is as close as possible to the C one. -Fixed point arithmetic is not often provided by Python packages which generally focus on floating-point operations. The CMSIS-DSP Python package provides the same fixed point arithmetic functions as the C version : Q31, Q15 and Q7. The package is also providing float functions and in the future will also provide half-precision floats like the C API. +Fixed-point arithmetic is rarely provided by Python packages, which generally focus on floating-point operations. The CMSIS-DSP Python package provides the same fixed-point arithmetic functions as the C version: Q31, Q15 and Q7. The package also provides floating-point functions and will also support half-precision floats in the future, like the C API. -Finally, the CMSIS-DSP Python package is compatible with NumPy and can be used with all other scientific and AI Python packages such as SciPy or PyTorch. +Finally, the CMSIS-DSP Python package is compatible with NumPy and can be used with all other scientific and AI Python packages such as SciPy and PyTorch. diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md index 4b2c8fed07..ee6aaab78d 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md @@ -23,7 +23,7 @@ plt.show() The slices we created are overlapping. By applying a Hanning window function and summing the slices, you can reconstruct the original signal. -Indeed, summing two Hanning window shifted by half the width of the sample block: +Indeed, summing two Hanning windows shifted by half the width of the sample block gives: ![summed hanning alt-text#center](sumhanning.png "Figure 5. Summed Hanning Window") As result, if you multiply the overlapping blocks of samples by Hanning windows and sum the result, you can reconstruct the original signal: @@ -96,7 +96,7 @@ scaling = (energy - self._noise)/energy (Don’t evaluate this Python code in your Jupyter notebook—it will be run later as part of the full implementation.) -### NoiseSuppressionReference class +### NoiseSuppression and NoiseSuppressionReference classes The entire algorithm will be packaged as a Python class. The class functions are explained below using Python code that should not be evaluated in the Jupyter notebook. @@ -104,19 +104,53 @@ The class functions are explained below using Python code that should not be eva You should only evaluate the full class definition in the Jupyter notebook—not the code snippets used for explanation. -#### NoiseSuppressionReference constructor +#### NoiseSuppression constructor +`NoiseSuppression` is a shared class used by both the float reference implementation and the Q15 version. +```python +class NoiseSuppression(): + def __init__(self,slices): + self._windowLength=len(slices[0]) + self._fftLen,self._fftShift=fft_length(self._windowLength) + + self._padding_left=(self._fftLen - self._windowLength)//2 + self._padding_right=self._fftLen- self._windowLength-self._padding_left + + self._signal=[] + self._slices=slices + self._window=None +``` -The constructor: +The constructor for `NoiseSuppression`: - Uses the audio slices as input -- Computes the VAD signal for the full audio signal -- Applies the Hanning window to each slice - Computes the FFT length that can be used for each slice - Computes the padding needed for the FFT The FFT length must be a power of 2. The slice length is not necessarily a power of 2. The constructor computes the closest usable power of 2. The audio slices are padded with zeros on both sides to match the required FFT length. +#### NoiseSuppressionReference constructor + +```python +class NoiseSuppressionReference(NoiseSuppression): + def __init__(self,slices): + # In a better version this could be computed from the signal length by taking the + # smaller power of two greater than the signal length. + NoiseSuppression.__init__(self,slices) + + # Compute the vad signal + self._vad=clean_vad([signal_vad(w) for w in slices]) + self._noise=np.zeros(self._fftLen) + # The Hann window + self._window=dsp.arm_hanning_f32(self._windowLength) +``` + +The constructor for `NoiseSuppressionReference`: +- Uses the audio slices as input +- Call the constructor for `NoiseSuppression` +- Computes the VAD signal for the full audio signal +- Compute the Hanning window + #### subnoise ```python @@ -218,6 +252,9 @@ def overlap_and_add(self): ``` ### The final code for the Python class + +You can evaluate this code in your Jupyter notebook. + ```python def fft_length(length): result=2 diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md index 45d54dbdd8..10e9f97fcc 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-6.md @@ -370,7 +370,7 @@ If the Python package has been built with Neon acceleration, it will use the new If this temporary buffer is not provided, the Python package will allocate it automatically. While you can use the same API, this is less efficient. -It is better to detect than the package have been compiled with Neon acceleration, allocate a temporary buffer and use it in the FFT calls. +It is better to detect whether the package has been compiled with Neon acceleration, allocate a temporary buffer and use it in the FFT calls. This approach is closer to how the C API must be used. ```python if dsp.has_neon(): From 6640b32ab186a03b0ab160c3e3423ede2243aab5 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 22 Apr 2025 15:24:25 +0200 Subject: [PATCH 069/122] Update acfl.md, oci-cli.md --- content/install-guides/acfl.md | 2 +- content/install-guides/oci-cli.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/install-guides/acfl.md b/content/install-guides/acfl.md index 671ec75ed2..a245253464 100644 --- a/content/install-guides/acfl.md +++ b/content/install-guides/acfl.md @@ -235,7 +235,7 @@ module avail #### Red Hat or Amazon Linux: -```bash { target="fedora:latest" env_source="/usr/share/Modules/init/bash" pre_cmd="module use /opt/arm/modulefiles" } +```console echo ". /usr/share/Modules/init/bash" >> $HOME/.bashrc echo "module use /opt/arm/modulefiles" >> $HOME/.bashrc source $HOME/.bashrc diff --git a/content/install-guides/oci-cli.md b/content/install-guides/oci-cli.md index de47245467..ecf4ed435e 100644 --- a/content/install-guides/oci-cli.md +++ b/content/install-guides/oci-cli.md @@ -64,7 +64,7 @@ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scrip Verify OCI CLI is installed using the `--version` option: -```bash { target="ubuntu:latest", pre_cmd=". $HOME/.bashrc" } +```console oci --version ``` From 9b029538d4ca509062ab459a780bcb6a756bd39b Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Tue, 22 Apr 2025 08:52:12 -0500 Subject: [PATCH 070/122] New Nerdctl install guide --- content/install-guides/nerdctl.md | 181 ++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 content/install-guides/nerdctl.md diff --git a/content/install-guides/nerdctl.md b/content/install-guides/nerdctl.md new file mode 100644 index 0000000000..f8f16f3f0a --- /dev/null +++ b/content/install-guides/nerdctl.md @@ -0,0 +1,181 @@ +--- +title: Nerdctl +author: Jason Andrews + +draft: true + +minutes_to_complete: 10 + +official_docs: https://github.com/containerd/nerdctl + +additional_search_terms: +- container +- containerd +- docker +- Linux + +test_images: +- ubuntu:latest +test_maintenance: false + +tool_install: true +layout: installtoolsall +multi_install: false +multitool_install_part: false +weight: 1 +--- + +Nerdctl is an open-source command-line interface (CLI) designed to be compatible with the popular Docker CLI, but specifically for interacting with [containerd](https://containerd.io/). It provides a familiar user experience for developers operators who are familiar with Docker, while leveraging the capabilities of containerd as the underlying container runtime. + +Using containerd and nerdctl provides similar functionality to Docker but with a smaller memory and CPU footprint, making it ideal for IoT or edge solutions, especially on Arm devices which balance energy efficiency and performance. Nerdctl also supports running containers in a rootless mode, enhancing security by not requiring elevated privileges. + +This guide focuses on installing containerd and Nerdctl on Arm Linux. + +## Before you begin + +This guide assumes you are using Ubuntu 22.04 or later on an Arm-based system (like a Raspberry Pi or an Arm instance in the cloud). + +Confirm you are using an Arm machine by running: + +```bash +uname -m +``` + +The output should be: + +```output +aarch64 +``` + +Ensure `wget` and `tar` are installed: + +```bash +sudo apt-get update +sudo apt-get install -y wget tar +``` + +## Install containerd + +Install the containerd runtime: + +```bash +sudo apt-get update +sudo apt-get install -y containerd +``` + +Start and enable the containerd service: + +```bash +sudo systemctl start containerd +sudo systemctl enable containerd +``` + +## Install nerdctl and CNI plugins + +Install nerdctl and the necessary CNI (Container Network Interface) plugins. Replace version numbers if needed. + +```bash +NERDCTL_VERSION=$(curl -s https://api.github.com/repos/containerd/nerdctl/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//') +wget https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-arm64.tar.gz +sudo tar -xzvf nerdctl-${NERDCTL_VERSION}-linux-arm64.tar.gz -C /usr/local/bin +``` + +Install the CNI plugins: + +```bash +CNI_VERSION=$(curl -s https://api.github.com/repos/containernetworking/plugins/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//') +wget https://github.com/containernetworking/plugins/releases/download/v${CNI_VERSION}/cni-plugins-linux-arm64-v${CNI_VERSION}.tgz +sudo mkdir -p /opt/cni/bin +sudo tar -xzvf cni-plugins-linux-arm64-v${CNI_VERSION}.tgz -C /opt/cni/bin +``` + +Clean up the downloaded files: + +```bash +rm nerdctl-${NERDCTL_VERSION}-linux-arm64.tar.gz cni-plugins-linux-arm64-v${CNI_VERSION}.tgz +``` + + +{{% notice Note %}} +The commands above attempt to fetch the latest versions automatically. You can replace `${NERDCTL_VERSION}` and `${CNI_VERSION}` with specific versions if required.* +{{% /notice %} + +## Verify the installation + +Test your installation by running a simple NGINX container: + +```console +sudo nerdctl run --name uname armswdev/uname +``` + +Wait a few seconds for the container to run, and the Architecture is printed: + +```output +Architecture is aarch64 +``` + + +Clean up the test container: + +```console +sudo nerdctl rm uname +``` + +You can also check the nerdctl version: +```console +sudo nerdctl version +``` + +## Basic nerdctl commands + +Here are some common commands to get you started: + +List running containers: + +```console +sudo nerdctl ps +``` + +List all containers (including stopped): + +```console +sudo nerdctl ps -a +``` + +List images: + +```console +sudo nerdctl images +``` + +Pull an image: + +```console +sudo nerdctl pull : +``` + +Build an image from Dockerfile in current directory: + +```console +sudo nerdctl build -t : . +``` + +Remove an image: + +```console +sudo nerdctl rmi : +``` + +Stop a container: + +```console +sudo nerdctl stop +``` + +Remove a container: + +```console +sudo nerdctl rm +``` + +You are now ready to use nerdctl and containerd. From d2c9495c52935dc564915a89ade953780f8a5e98 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Tue, 22 Apr 2025 17:27:29 +0200 Subject: [PATCH 071/122] Remove tmate session, add check for capitalization and spaces --- .github/workflows/test-lp.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index 068fcf1a01..b029dd2e97 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -18,11 +18,21 @@ jobs: with: files: | **.md + - name: Check for capital letters or spaces in content directory + run: | + echo "Checking for capital letters or spaces in content directory paths..." + + git diff --name-only origin/${{ github.base_ref }}...HEAD | + grep '^content/' | + grep -E '[A-Z]|[[:space:]]' && { + echo "❌ Found invalid file or directory names with capital letters or spaces in 'content/'" + exit 1 + } + + echo "✅ No capital letters or spaces found in 'content/' paths." - name: Install dependencies if: steps.changed-markdown-files.outputs.any_changed == 'true' run: pip install -r tools/requirements.txt - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - name: Run test suite for all changed .md files id: run-suite if: steps.changed-markdown-files.outputs.any_changed == 'true' From e652252b16fe8727c2b9b3e001e9b5a2bde1932b Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Tue, 22 Apr 2025 17:55:09 +0000 Subject: [PATCH 072/122] Editorial review --- .../cca-veraison-aws/aws-account-prep.md | 21 ++++----- .../cca-veraison-aws/deployment.md | 16 +++---- .../cca-veraison-aws/domain-and-cert.md | 45 +++++++++++++------ .../cca-veraison-aws/endorsements.md | 8 ++-- .../cca-veraison-aws/overview.md | 20 ++++++--- .../cca-veraison-aws/use-verifier.md | 6 +-- 6 files changed, 70 insertions(+), 46 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/aws-account-prep.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/aws-account-prep.md index 15aa0786c8..5ae2079cbf 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/aws-account-prep.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/aws-account-prep.md @@ -6,20 +6,21 @@ weight: 3 layout: learningpathall --- -## Prepare Your AWS Account -For this learning path, you will need an active AWS account. If you do not have an AWS account, please refer to the [AWS documentation](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html). +## Prepare your AWS account +You’ll need an active AWS account for this Learning Path. If you don't have one yet, refer to the [AWS documentation](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html). -This learning path assumes that you have administrator level privileges for your AWS account. +{{% notice Note %}} +This Learning Path assumes that you have administrator-level privileges for your AWS account. {{% /notice %}} -## Install the AWS Command-Line Tools -For this section, you will need the AWS Command-Line (CLI) tools. Please refer to the [AWS documentation](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) for the steps needed to install the latest version of the AWS CLI. +## Install AWS command-line tools +You’ll need the AWS Command-Line Interface (CLI) installed for this section. Follow the [AWS documentation](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to install the latest version. -## Set Up Authentication -You will need to configure your local environment to authenticate with the AWS cloud in order to build the Veraison deployment. +## Set up authentication +You'll need to set up your local environment to authenticate with AWS before deploying Veraison. -The recommended way to do this is using Single Sign-On (SSO). The steps to do this are documented in Veraison's documentation [here](https://github.com/veraison/services/tree/main/deployments/aws#aws-account). +The recommended method is Single Sign-On (SSO). Follow the steps in Veraison's documentation [here](https://github.com/veraison/services/tree/main/deployments/aws#aws-account). -It is important to ensure that authentication is configured correctly. The best way to do this is to run a simple command-line operation such as the following: +To confirm authentication is configured correctly, run a simple command, such as: ```bash aws ec2 describe-availability-zones @@ -66,4 +67,4 @@ You should see output similar to the following (depending on which AWS region yo ] } ``` -If this operation fails, please do not attempt to proceed with the next steps of this learning path. Refer to [AWS documentation](https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-authentication.html) for help with troubleshooting this step. +If this operation fails, pause here and troubleshoot using the [AWS documentation](https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-authentication.html) before continuing. diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/deployment.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/deployment.md index b8fe73a878..7a6df84242 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/deployment.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/deployment.md @@ -9,7 +9,7 @@ layout: learningpathall ## Create the Veraison Deployment Now that your AWS account, internet domain and certificate are prepared, you are ready to deploy the Veraison services into AWS. -This process is highly automated, but it takes some time, because a number of resources need to be created in AWS. Be prepared for this step to take from 30 to 60 minutes, although there won't be too much for you to do during this time. You will just run a command to kick off the process. +This process is highly automated, but will take between 30 to 60 minutes, as several resources need to be created in AWS. The deployment process is documented in [Veraison's GitHub repository](https://github.com/veraison/services/blob/main/deployments/aws/README.md). @@ -25,7 +25,7 @@ make bootstrap ``` Once your build environment is bootstrapped, you will use the [Quickstart](https://github.com/veraison/services/tree/main/deployments/aws#quickstart) procedure to provide some AWS configuration and create the deployment. -You need to provide your AWS account-specific configuration that specifies the IDs of the VPC and subnets that will be used for the deployment as well as the CIDR that will be granted access to the deployment. In this deployment you will use `misc/arm.cfg` file for example. Make sure you update `VERAISON_AWS_REGION` to the same region where you created your AWS Certificate for your new domain.`VERAISON_AWS_DNS_NAME` will need to match the domain name you chose. +You'll provide AWS-specific settings, including the IDs for your VPC and subnets, and the CIDR block allowed access to the deployment. In this deployment, you will use `misc/arm.cfg` file for example. Make sure you update `VERAISON_AWS_REGION` to the same region where you created your AWS certificate for your new domain.`VERAISON_AWS_DNS_NAME` needs to match the domain name you chose. Once the account-specific config file is created, define `AWS_ACCOUNT_CFG` environment variable to point to it and then create the deployment. @@ -35,7 +35,7 @@ export AWS_ACCOUNT_CFG=misc/arm.cfg # replace with path to your config make deploy ``` -You do not need to use the end-to-end flow as described in the document. Later in this learning path, you will perform some additional steps to prepare and use the Veraison services. +You do not need to use the end-to-end flow as described in the document. Later in this Learning Path, you will perform some additional steps to prepare and use the Veraison services. The rest of the document provides additional information about how to manage the deployment, but you don't need this now. @@ -45,15 +45,15 @@ In the command shell where you ran the steps above, run the following command: ```bash veraison status ``` -This command will output a status report for the deployment. If successful, it will include information about:- +This command outputs a status report for the deployment. If successful, it includes information about: - The Amazon Machine Images (AMIs) that have been used for the servers. - The status of the VPC stack, support stack and services stack. All of these should read as `created`. - Information about RDS, ElastiCache and EC2 resources in the deployment. - The version of the Veraison software that is running. - The public part of the key that is used to sign attestation results (known as the EAR Verification Key). -- A list of media types that Veraison will accept as attestation evidence. -- A list of media types that Veraison will accept as endorsements. +- A list of media types that Veraison accepts as attestation evidence. +- A list of media types that Veraison accepts as endorsements. Use the following command to test the REST API endpoint of the verification service. Remember to substitute `example-veraison.com` with the domain name that you used in the initial step, but you will need to keep the `services` prefix as shown. @@ -69,6 +69,6 @@ Use the following command to test the REST API endpoint of the endorsement provi curl https://services.example-veraison.com:9443/.well-known/veraison/provisioning ``` -This command will produce JSON output containing the list of supported media types for endorsement. +This command produces JSON output containing the list of supported media types for endorsement. -Your Veraison services are now deployed and working, and you can proceed to the next step. +Your Veraison services are now successfully deployed - you're ready for the next step. \ No newline at end of file diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md index ed1258d5eb..a3c500fef1 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md @@ -7,51 +7,68 @@ layout: learningpathall --- ## Create Your Domain in Route53 -Veraison provides cloud services for attestation. These services are published on the internet and are accessible via HTTPS using RESTful APIs. Like all cloud services, they require a suitable internet domain that allows the consumers of those services to locate them. Domains are named using string labels separated by dots (full stops). You will be familiar with domain names such as `www.amazon.com` - they allow public internet resources to be located conveniently and routed using shared internet infrastructure such as DNS. +Veraison provides cloud services for attestation. These services are published on the internet and are accessible via HTTPS using RESTful APIs. Like all cloud services, they need a domain so users can easily find and access them. Domains are named using string labels separated by dots. You will be familiar with domain names such as `www.amazon.com` - they allow public internet resources to be located conveniently and routed using shared internet infrastructure such as DNS. +### What is Route53? -[Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html) is an AWS service that allows you to register and manage domains. In order to create your Veraison deployment in AWS, you will first need to choose a domain name and register it with Route53. +[Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html) is an AWS service that allows you to register and manage domains. In order to create your Veraison deployment in AWS, you first need to choose a domain name and register it with Route53. -Your domain name _must_ be unique and specific to your Veraison deployment. Remember that this domain name will be used to create public internet services, so it cannot clash with any other domains that exist on the public internet. In this learning path, we will use `example-veraison.com` as an illustrative example of a domain name. However, _do not_ use this name for your own deployment. Choose your own name, and use it in place of the example as you progress through the learning path. +### Choosing your domain name -The easiest way to create your domain is using the Route53 dashboard in the AWS Management Console. Using your web browser and AWS account credentials, sign into the console and search for the Route53 service. This will take you to the Route53 dashboard. Locate the domain registration option, which will look something like the image below. Remember to use your own domain name where highlighted, not the `example-veraison.com` domain. +Your domain name must be unique and specific to your Veraison deployment. Remember that this domain name is for creating public internet services, so it cannot clash with any other domains that exist on the public internet. + +In this Learning Path, you will use `example-veraison.com` as an illustrative example of a domain name. However, do not use this name for your own deployment. Choose your own name, and use it in place of the example as you progress through the Learning Path. + +### Registering your Domain with Route53 + +The easiest way to create your domain is using the Route53 dashboard in the AWS Management Console: + +* Using your web browser and AWS account credentials, sign into the console and search for the Route53 service. This takes you to the Route53 dashboard. +* Locate the domain registration option, which looks something like the image below. Remember to use your own domain name where highlighted, not the `example-veraison.com` domain. **Note:** If you have an existing domain and you wish to transfer it into Route53, instead of creating a new domain, you can use this option as well as shown below. Otherwise, the instructions here assume that you are creating a new domain. ![Register Domain](./create-domain.png) -Route53 will check the domain name for you to ensure that it doesn't clash with others on the internet. Provided that there isn't a clash, Route53 will give you the option of registering your chosen domain name, or some alternatives. For example, it might suggest you could use `example-veraison.net` or `example-veraison.org`. +Route53 checks your domain name to make sure it’s unique on the internet. Provided that there isn't a clash, Route53 gives you the option of registering your chosen domain name, or some alternatives. For example, it might suggest you could use `example-veraison.net` or `example-veraison.org`. -Route53 will charge an annual fee for the domain registration, and the size of this fee can differ depending on your name choice. Choose the name that you would like to use, and proceed to complete the registration process using the on-screen instructions in your browser. +Route53 charges an annual fee for domain registration, which varies depending on your chosen name. Choose the name that you would like to use, and proceed to complete the registration process using the on-screen instructions in your browser. ## Request Your Domain Certificate -Veraison publishes secure cloud services that can be reached on the internet using HTTPs. This means that you will need a certificate for your registered domain. A certificate will allow consumers of your Veraison services to trust those services and connect with them securely. -The [AWS Certificate Manager (ACM)](https://aws.amazon.com/certificate-manager/) can be used to issue a certificate for your domain. Navigate to this service within the AWS Management Console in your web browser. +### What is a Domain Certificate? +Veraison publishes secure cloud services that can be reached on the internet using HTTPs. You'll need a certificate for your domain to help users trust and securely connect to your Veraison services. -Select "Request Certificate" from the menu on the left as shown. +### Requesting a Certificate with AWS Certificate Manager +You can use the [AWS Certificate Manager (ACM)](https://aws.amazon.com/certificate-manager/) to issue a certificate for your domain: + +* Navigate to this service within the AWS Management Console in your web browser. + +* Select **Request Certificate** from the menu on the left as shown. ![Request Certificate](./request-certificate.png) -On the first page of the certificate wizard, select the option to request a **public certificate** as shown below and click **Next**. +On the first page of the certificate wizard, select **public certificate**, then click **Next**. ![Request Public Certificate](./request-public-certificate.png) -The next page of the wizard will be used to set other properties of the certificate, starting with the domain name. The primary domain name of the certificate must precisely match the domain name that you registered with Route53 in the previous step. However, the Veraison deployment will also create a number of named services nested within that primary domain. In order for the certificate to adequately cover all of those named services, you need to give the certificate an additional name, which uses an asterisk (*) wildcard as shown below. Remember, once again, substitute your chosen domain name to use in place of `example-veraison.com`. +The next page of the wizard is for setting other properties of the certificate, starting with the domain name. The primary domain name of the certificate must precisely match the domain name that you registered with Route53 in the previous step. However, the Veraison deployment also creates a number of named services nested within that primary domain. In order for the certificate to adequately cover all of those named services, you need to give the certificate an additional name, which uses an asterisk (*) wildcard as shown below. Remember, once again, substitute your chosen domain name to use in place of `example-veraison.com`. Use the **Add another name to this certificate** button to create the secondary name with the wildcard. ![Set Certificate Names](./set-cert-names.png) -For the validation method, you should use **DNS validation**, which will be the recommended default. You can also use the default **RSA 2048** for the certificate key algorithm as shown below. +For the validation method, you should use **DNS validation**, which is the recommended default. You can also use the default **RSA 2048** for the certificate key algorithm as shown below. ![Certificate Validation and Key Algorithm](./cert-validation-and-algorithm.png) Now click **Request** to request the certificate from the Certificate Manager. -Before AWS can issue the certificate, it will need to check that you own the domain. Since you have registered the domain in Route53 in the previous step, this will be straightforward. +### Validating Your Domain Ownership + +Before AWS can issue the certificate, it checks that you own the domain. Since you registered the domain in Route53 earlier, this is straightforward. Use the ACM dashboard to view the certificate. You will see that it has a status of "Pending Validation". You will also see the two associated domains: `example-veraison.com` and `*.example-veraison.com`, or whatever your chosen domain name is. -Click the button that says **Create records in Route 53**. This will allow AWS to prove that you own the domain, at which point it will issue the certificate, and the status will change from "Pending Validation" to "Issued". Be aware that this process can take up to about half an hour. +Click **Create records in Route 53** to confirm domain ownership. AWS then issues the certificate, and its status changes from **Pending Validation** to **Issued**. Be aware that this process can take up to about half an hour. Once your domain and certificate are prepared, you are ready to create your Veraison deployment. diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/endorsements.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/endorsements.md index 26e6cb3f46..29adb6c969 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/endorsements.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/endorsements.md @@ -17,7 +17,7 @@ cd $HOME git clone https://git.codelinaro.org/linaro/dcap/cca-demos/poc-endorser ``` ## Configure the Endorsement Tool for AWS -By default, the endorsement tool assumes that your Veraison services are deployed locally on your machine. This is not the case here, because your Veraison services have been deployed into AWS instead. Therefore, you will need to provide some configuration to the tool, in order to point it at the correct API endpoints with the required authentication. +By default, the endorsement tool assumes that your Veraison services are deployed locally on your machine. This is not the case here, because your Veraison services have been deployed into AWS instead. You'll need to configure the tool to point it at the correct API endpoints with the required authentication. In the command shell where you created the AWS deployment of Veraison, run the following command: @@ -69,7 +69,7 @@ Now run the following command to provision the endorsements: ```bash make endorse ``` -This command will run the Docker container and send the CCA endorsements to Veraison. You should see output similar to the following: +This command runs the Docker container and sends the CCA endorsements to your AWS-hosted Veraison deployment. You should see output similar to the following: ```output docker run --network=host "cca-demo/endorser" @@ -88,7 +88,7 @@ Next, return to the command shell where you created the Veraison AWS deployment, cd $HOME/services/deployments/aws veraison stores ``` -This command will query Veraison's database stores. If the CCA endorsements were provisioned successfully, the output should look something like the example below. (You don't need to be concerned with understanding all of the detail here.) +This command will query Veraison's database stores. If the CCA endorsements were provisioned successfully, the output should look something like the example below. (You don't need to understand all of the detail here.) ```output TRUST ANCHORS: @@ -304,4 +304,4 @@ ARM_CCA://0/f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAUFgAAAAAAAA= POLICIES: --------- ``` -Your Veraison deployment is now complete and ready to act as an attestation verification service for pre-silicon Arm CCA platforms. +Your Veraison deployment is now ready to act as an attestation verification service for pre-silicon Arm CCA platforms. \ No newline at end of file diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/overview.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/overview.md index 0f21604dee..4a60120e68 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/overview.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/overview.md @@ -1,18 +1,24 @@ --- -title: "Overview: Deploying Veraison in AWS" +title: "Overview" weight: 2 ### FIXED, DO NOT MODIFY layout: learningpathall --- -## Overview -[Veraison](https://github.com/veraison) is a community open-source project that is part of the [Confidential Computing Consortium (CCC)](https://confidentialcomputing.io). Veraison provides the components that are needed to build attestation verification services for confidential computing or other use cases. Veraison acts as the Verifier role in the [RATS architecture (RFC9334)](https://datatracker.ietf.org/doc/rfc9334/), which is a common model for attestation-based systems. Veraison makes use of community standardization efforts to ensure a high degree of interoperability. +## Deploying Veraison on AWS +[Veraison](https://github.com/veraison) is a community open-source project that is part of the [Confidential Computing Consortium (CCC)](https://confidentialcomputing.io). Veraison provides components for building attestation verification services for confidential computing and other use cases. -Attestation is essential for confidential computing, and Veraison can be used as the verifier service for Arm's Confidential Compute Architecture (CCA). If you have not already familiarized yourself with CCA attestation and Veraison, it is recommended that you first follow the learning paths [Get Started with CCA Attestation and Veraison](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-veraison/) and [Run an end-to-end Attestation Flow with Arm CCA](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-essentials/). +Veraison acts as the Verifier role in the [RATS architecture (RFC9334)](https://datatracker.ietf.org/doc/rfc9334/), which is a common model for attestation-based systems. Veraison makes use of community standardization efforts to ensure a high degree of interoperability. -The two learning paths linked above make use of a Veraison verification service that is published and maintained by [Linaro](https://www.linaro.org). +Attestation is essential for confidential computing, and Veraison acts as a verifier for Arm's Confidential Compute Architecture (CCA). -In this learning path, you will create and publish your own Veraison verification service in the AWS cloud. After you complete the learning path, you will be able to go back through the steps of the previous two learning paths, and use your own AWS-hosted Veraison service instead of the one hosted by Linaro. +{{% notice Learning Tip %}} +If you're new to CCA attestation and Veraison, you will benefit from first completing the Learning Paths [Get Started with CCA Attestation and Veraison](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-veraison/) and [Run an end-to-end Attestation Flow with Arm CCA](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-essentials/). These two Learning Paths above use a Veraison verification service hosted by [Linaro](https://www.linaro.org). +{{% /notice %}} -AWS is not the only way to deploy Veraison, but we will adopt it here as an example of using public cloud infrastructure. You can read about other types of deployment in the [Veraison project README](https://github.com/veraison/services?tab=readme-ov-file#services). +In this Learning Path, you'll create and deploy your own Veraison verification service on AWS. After completing this Learning Path, you'll be able to revisit the two Learning Paths mentioned above, using your own AWS-hosted Veraison service instead of the one hosted by Linaro. + +{{% notice Note%}} +AWS isn't the only deployment option for Veraison, but you'll use it here as an example of deploying on public cloud infrastructure. For other deployment methods, see the [Veraison project README](https://github.com/veraison/services?tab=readme-ov-file#services). +{{% /notice %}} \ No newline at end of file diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md index 9778fb5952..f643ba866c 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md @@ -9,10 +9,10 @@ layout: learningpathall ## Use Your AWS Deployment to Verify a CCA Attestation Token Now that your Veraison services are deployed into AWS and initialized with endorsements for the CCA reference platform, you are ready to make use of the verification service to verify a CCA attestation token. -To do this, you should follow the steps set out in the learning path [Get Started with CCA Attestation and Veraison](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-veraison/). However, you should follow this learning path in such a way that it uses your AWS deployment of Veraison, instead of the service provided by Linaro. +To do this, you should follow the steps set out in the Learning Path [Get Started with CCA Attestation and Veraison](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-veraison/). Follow the steps in that Learning Path exactly, except you'll use your AWS-hosted Veraison deployment instead of Linaro's service. The URL for the Veraison server provided by Linaro is `https://veraison.test.linaro.org:8443`. -Instead if using this URL, you should use the URL for your Veraison service, which will be of the form ` https://services.example-veraison.com:8443`, although you will need to replace `example-veraison.com` with your own registered AWS domain. +Instead of using this URL, you should use the URL for your Veraison service, which will be of the form `https://services.example-veraison.com:8443`, although you need to replace `example-veraison.com` with your own registered AWS domain. -Apart from this URL change, all other steps in the learning path remain the same. +Apart from this URL change, all other steps in the Learning Path remain the same. From 17e1938a556ca091c869b353784c535b02f62ce5 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Tue, 22 Apr 2025 18:18:33 +0000 Subject: [PATCH 073/122] Final tweaks --- .../cca-veraison-aws/domain-and-cert.md | 18 +++++++++++------- .../cca-veraison-aws/use-verifier.md | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md index a3c500fef1..37cf7ee988 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/domain-and-cert.md @@ -12,20 +12,22 @@ Veraison provides cloud services for attestation. These services are published o [Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html) is an AWS service that allows you to register and manage domains. In order to create your Veraison deployment in AWS, you first need to choose a domain name and register it with Route53. -### Choosing your domain name +### Choosing Your Domain Name Your domain name must be unique and specific to your Veraison deployment. Remember that this domain name is for creating public internet services, so it cannot clash with any other domains that exist on the public internet. In this Learning Path, you will use `example-veraison.com` as an illustrative example of a domain name. However, do not use this name for your own deployment. Choose your own name, and use it in place of the example as you progress through the Learning Path. -### Registering your Domain with Route53 +### Registering Your Domain with Route53 The easiest way to create your domain is using the Route53 dashboard in the AWS Management Console: * Using your web browser and AWS account credentials, sign into the console and search for the Route53 service. This takes you to the Route53 dashboard. * Locate the domain registration option, which looks something like the image below. Remember to use your own domain name where highlighted, not the `example-veraison.com` domain. -**Note:** If you have an existing domain and you wish to transfer it into Route53, instead of creating a new domain, you can use this option as well as shown below. Otherwise, the instructions here assume that you are creating a new domain. +{{% notice Note %}} +If you have an existing domain, you can transfer it into Route53 instead of registering a new one, as shown below. Otherwise, the instructions here assume that you are creating a new domain. +{{% /notice %}} ![Register Domain](./create-domain.png) @@ -35,8 +37,8 @@ Route53 charges an annual fee for domain registration, which varies depending on ## Request Your Domain Certificate -### What is a Domain Certificate? -Veraison publishes secure cloud services that can be reached on the internet using HTTPs. You'll need a certificate for your domain to help users trust and securely connect to your Veraison services. +### Why Do I Need a Domain Certificate? +Veraison publishes secure cloud services that can be reached on the internet using HTTPs. You need a domain certificate so users can securely and confidently connect to your Veraison services. ### Requesting a Certificate with AWS Certificate Manager You can use the [AWS Certificate Manager (ACM)](https://aws.amazon.com/certificate-manager/) to issue a certificate for your domain: @@ -47,11 +49,13 @@ You can use the [AWS Certificate Manager (ACM)](https://aws.amazon.com/certifica ![Request Certificate](./request-certificate.png) -On the first page of the certificate wizard, select **public certificate**, then click **Next**. +On the first page of the certificate wizard, select **Request a public certificate**, then click **Next**. ![Request Public Certificate](./request-public-certificate.png) -The next page of the wizard is for setting other properties of the certificate, starting with the domain name. The primary domain name of the certificate must precisely match the domain name that you registered with Route53 in the previous step. However, the Veraison deployment also creates a number of named services nested within that primary domain. In order for the certificate to adequately cover all of those named services, you need to give the certificate an additional name, which uses an asterisk (*) wildcard as shown below. Remember, once again, substitute your chosen domain name to use in place of `example-veraison.com`. +The next page of the wizard is for setting other properties of the certificate, starting with the domain name. The primary domain name of the certificate must precisely match the domain name that you registered with Route53 in the previous step. However, the Veraison deployment also creates a number of named services nested within that primary domain. + +In order for the certificate to adequately cover all of those named services, you need to give the certificate an additional name, which uses an asterisk (*) wildcard as shown below. Remember, once again, substitute your chosen domain name to use in place of `example-veraison.com`. Use the **Add another name to this certificate** button to create the secondary name with the wildcard. diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md index f643ba866c..212f44f0a5 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/use-verifier.md @@ -9,10 +9,10 @@ layout: learningpathall ## Use Your AWS Deployment to Verify a CCA Attestation Token Now that your Veraison services are deployed into AWS and initialized with endorsements for the CCA reference platform, you are ready to make use of the verification service to verify a CCA attestation token. -To do this, you should follow the steps set out in the Learning Path [Get Started with CCA Attestation and Veraison](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-veraison/). Follow the steps in that Learning Path exactly, except you'll use your AWS-hosted Veraison deployment instead of Linaro's service. +To do this, you should follow the steps set out in the Learning Path [Get Started with CCA Attestation and Veraison](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cca-veraison/). Follow the steps in this Learning Path exactly, except you'll use your AWS-hosted Veraison deployment instead of Linaro's service. The URL for the Veraison server provided by Linaro is `https://veraison.test.linaro.org:8443`. -Instead of using this URL, you should use the URL for your Veraison service, which will be of the form `https://services.example-veraison.com:8443`, although you need to replace `example-veraison.com` with your own registered AWS domain. +Instead of this URL, use the one for your own Veraison service, which will be of the form `https://services.example-veraison.com:8443`, although you need to replace `example-veraison.com` with your AWS domain. Apart from this URL change, all other steps in the Learning Path remain the same. From a34a45fa3e17b6971785cd32648e1104223e6691 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Tue, 22 Apr 2025 21:45:43 +0000 Subject: [PATCH 074/122] Editorial review. --- .../deepseek-cpu/_index.md | 8 ++-- .../deepseek-cpu/deepseek-chatbot.md | 40 ++++++++++--------- .../deepseek-cpu/deepseek-server.md | 15 +++++-- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md index cc2ee4c67e..714e5c72e1 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/_index.md @@ -7,15 +7,15 @@ cascade: minutes_to_complete: 30 -who_is_this_for: This is an introductory topic for developers interested in running DeepSeek-R1 on Arm-based servers. +who_is_this_for: This Learning Path is for developers who want to run DeepSeek-R1 on Arm-based servers. learning_objectives: - - Download and build llama.cpp on your Arm-based server. + - Clone and build llama.cpp on your Arm-based server. - Download a pre-quantized DeepSeek-R1 model from Hugging Face. - - Run the pre-quantized model on your Arm CPU and measure the performance. + - Run the model on your Arm CPU and benchmark its performance. prerequisites: - - An [Arm-based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider or an on-premise Arm server. This Learning Path was tested on an AWS Graviton4 r8g.24xlarge instance. + - An [Arm-based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud provider or an on-premise Arm server. This Learning Path was tested on an AWS Graviton4 r8g.24xlarge instance. author: - Tianyu Li diff --git a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md index a8f80e6e03..498b7fe824 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md +++ b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md @@ -6,12 +6,10 @@ weight: 3 layout: learningpathall --- -## Before you begin -The instructions in this Learning Path are for any Arm server running Ubuntu 24.04 LTS. You need an Arm server instance with at least 64 cores and 512GB of RAM to run this example. Configure disk storage up to at least 400 GB. The instructions have been tested on an AWS Graviton4 r8g.24xlarge instance. - - ## Background and what you'll build +The instructions in this Learning Path are for any Arm server running Ubuntu 24.04 LTS. You need an Arm server instance with at least 64 cores and 512GB of RAM to run this example. Configure disk storage up to at least 400 GB. The instructions have been tested on an AWS Graviton4 r8g.24xlarge instance. + Arm CPUs are widely used in ML and AI use cases. In this Learning Path, you will learn how to run a generative AI inference-based use case of a LLM chatbot on Arm-based CPUs by deploying the [DeepSeek-R1 671B LLM](https://huggingface.co/bartowski/DeepSeek-R1-GGUF) on your Arm-based CPU using `llama.cpp`, optimized for Arm hardware. You'll: - Build and run `llama.cpp` with Arm-specific performance improvements. @@ -22,7 +20,7 @@ Arm CPUs are widely used in ML and AI use cases. In this Learning Path, you will [llama.cpp](https://github.com/ggerganov/llama.cpp) is an open source C/C++ project developed by Georgi Gerganov that enables efficient LLM inference on a variety of hardware - both locally, and in the cloud. -## About the DeepSeek-R1 model and GGUF model format +## Understanding the DeepSeek-R1 model and GGUF format The [DeepSeek-R1 model](https://huggingface.co/deepseek-ai/DeepSeek-R1) from DeepSeek-AI available on Hugging Face, is released under the [MIT License](https://github.com/deepseek-ai/DeepSeek-R1/blob/main/LICENSE) and free to use for research and commercial purposes. @@ -30,9 +28,9 @@ The DeepSeek-R1 model has 671 billion parameters, based on Mixture of Experts(Mo Traditionally, the training and inference of LLMs has been done on GPUs using full-precision 32-bit (FP32) or half-precision 16-bit (FP16) data type formats for the model parameter and weights. Recently, a new binary model format called GGUF was introduced by the `llama.cpp` team. This new GGUF model format uses compression and quantization techniques that remove the dependency on using FP32 and FP16 data type formats. For example, GGUF supports quantization where model weights that are generally stored as FP16 data types are scaled down to 4-bit integers. This significantly reduces the need for computational resources and the amount of RAM required. These advancements made in the model format and the data types used make Arm CPUs a great fit for running LLM inferences. -## Install dependencies +## Install build dependencies on your Arm-based server -Install the following packages on your Arm based server instance: +Install the following packages: ```bash sudo apt update @@ -46,7 +44,7 @@ sudo apt install gcc g++ -y sudo apt install build-essential -y ``` -## Download and build llama.cpp +## Clone and build llama.cpp You are now ready to start building `llama.cpp`. @@ -107,7 +105,7 @@ general: ``` -## Install Hugging Face Hub +## Set up Hugging Face and download the model There are a few different ways you can download the DeepSeek-R1 model. In this Learning Path, you download the model from Hugging Face. @@ -143,19 +141,19 @@ huggingface-cli download bartowski/DeepSeek-R1-GGUF --include "*DeepSeek-R1-Q4_0 ``` Before you proceed and run this model, take a quick look at what `Q4_0` in the model name denotes. -## Quantization format +## Understanding the Quantization format `Q4_0` in the model name refers to the quantization method the model uses. The goal of quantization is to reduce the size of the model (to reduce the memory space required) and faster (to reduce memory bandwidth bottlenecks transferring large amounts of data from memory to a processor). The primary trade-off to keep in mind when reducing a model's size is maintaining quality of performance. Ideally, a model is quantized to meet size and speed requirements while not having a negative impact on performance. This model is `DeepSeek-R1-Q4_0-00001-of-00010.gguf`, so what does each component mean in relation to the quantization level? The main thing to note is the number of bits per parameter, which is denoted by 'Q4' in this case or 4-bit integer. As a result, by only using 4 bits per parameter for 671 billion parameters, the model drops to be 354 GB in size. -## Run the pre-quantized DeepSeek-R1 LLM model weights on your Arm-based server +## Run the DeepSeek-R1 Chatbot on your Arm server As of [llama.cpp commit 0f1a39f3](https://github.com/ggerganov/llama.cpp/commit/0f1a39f3), Arm has contributed code for performance optimization with three types of GEMV/GEMM kernels corresponding to three processor types: -* AWS Graviton2, where you only have NEON support (you will see less improvement for these GEMV/GEMM kernels), -* AWS Graviton3, where the GEMV/GEMM kernels exploit both SVE 256 and MATMUL INT8 support, and -* AWS Graviton4, where the GEMV/GEMM kernels exploit NEON/SVE 128 and MATMUL_INT8 support +* AWS Graviton2, where you only have NEON support (you will see less improvement for these GEMV/GEMM kernels). +* AWS Graviton3, where the GEMV/GEMM kernels exploit both SVE 256 and MATMUL INT8 support. +* AWS Graviton4, where the GEMV/GEMM kernels exploit NEON/SVE 128 and MATMUL_INT8 support. With the latest commits in `llama.cpp` you will see improvements for these Arm optimized kernels directly on your Arm-based server. You can run the pre-quantized Q4_0 model as is and do not need to re-quantize the model. @@ -167,7 +165,9 @@ Run the pre-quantized DeepSeek-R1 model exactly as the weights were downloaded f This command will use the downloaded model (`-m` flag), disable conversation mode explicitly (`-no-cnv` flag), adjust the randomness of the generated text (`--temp` flag), with the specified prompt (`-p` flag), and target a 512 token completion (`-n` flag), using 64 threads (`-t` flag). -You may notice there are many gguf files downloaded, llama.cpp can load all series of files by passing the first one with `-m` flag. +You might notice there are many gguf files downloaded. Llama.cpp can load all series of files by passing the first one with `-m` flag. + +## Analyze the output and performance statistics You will see lots of interesting statistics being printed from llama.cpp about the model and the system, followed by the prompt and completion. The tail of the output from running this model on an AWS Graviton4 r8g.24xlarge instance is shown below: @@ -380,10 +380,10 @@ llama_perf_context_print: total time = 42340.53 ms / 531 tokens The `system_info` printed from llama.cpp highlights important architectural features present on your hardware that improve the performance of the model execution. In the output shown above from running on an AWS Graviton4 instance, you will see: - * NEON = 1 This flag indicates support for Arm's Neon technology which is an implementation of the Advanced SIMD instructions - * ARM_FMA = 1 This flag indicates support for Arm Floating-point Multiply and Accumulate instructions - * MATMUL_INT8 = 1 This flag indicates support for Arm int8 matrix multiplication instructions - * SVE = 1 This flag indicates support for the Arm Scalable Vector Extension + * NEON = 1 This flag indicates support for Arm's Neon technology which is an implementation of the Advanced SIMD instructions. + * ARM_FMA = 1 This flag indicates support for Arm Floating-point Multiply and Accumulate instructions. + * MATMUL_INT8 = 1 This flag indicates support for Arm int8 matrix multiplication instructions. + * SVE = 1 This flag indicates support for the Arm Scalable Vector Extension. The end of the output shows several model timings: @@ -392,5 +392,7 @@ The end of the output shows several model timings: * prompt eval time refers to the time taken to process the prompt before generating the new text. * eval time refers to the time taken to generate the output. Generally anything above 10 tokens per second is faster than what humans can read. +## What's next? + You have successfully run a LLM chatbot with Arm KleidiAI optimizations, all running on your Arm AArch64 CPU on your server. You can continue experimenting and trying out the model with different prompts. diff --git a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-server.md b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-server.md index dfc1ca43b5..c1302ace6e 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-server.md +++ b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-server.md @@ -6,6 +6,8 @@ weight: 4 layout: learningpathall --- +## Start the LLM server with llama.cpp + You can use the `llama.cpp` server program and submit requests using an OpenAI-compatible API. This enables applications to be created which access the LLM multiple times without starting and stopping it. You can also access the server over the network to another machine hosting the LLM. @@ -45,7 +47,7 @@ curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/jso }' 2>/dev/null | jq -C ``` -The `model` value in the API is not used, you can enter any value. This is because there is only one model loaded in the server. +The `model` value is ignored by the server, so you can use any placeholder string. This is because there is only one model loaded in the server. Run the script: @@ -90,9 +92,11 @@ The `curl` command accesses the LLM and you see the output: } ``` +## Inspect the JSON output + In the returned JSON data you see the LLM output, including the content created from the prompt. -## Use Python +## Access the API using Python You can also use a Python program to access the OpenAI-compatible API. @@ -121,7 +125,7 @@ client = OpenAI( completion = client.chat.completions.create( model="not-used", messages=[ - {"role": "system", "content": "You are a coding assistant, skilled in programming.."}, + {"role": "system", "content": "You are a coding assistant, skilled in programming..."}, {"role": "user", "content": "Write a hello world program in C++."} ], stream=True, @@ -137,6 +141,9 @@ Run the Python file (make sure the server is still running): python ./python-test.py ``` +## Example Output + + You see the output generated by the LLM: ```output @@ -192,4 +199,6 @@ When compiled and run, this program will display: Hello World! ``` +## What's next? + You can continue to experiment with different large language models and write scripts to try them. From 0ad9cca28dab306ecf7a62a487f8616eec60de86 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Tue, 22 Apr 2025 23:24:04 -0500 Subject: [PATCH 075/122] New install guide for nerdctl and containerd --- content/install-guides/nerdctl.md | 164 ++++++++++++++++++++++++++---- 1 file changed, 147 insertions(+), 17 deletions(-) diff --git a/content/install-guides/nerdctl.md b/content/install-guides/nerdctl.md index f8f16f3f0a..b87c5a1181 100644 --- a/content/install-guides/nerdctl.md +++ b/content/install-guides/nerdctl.md @@ -6,7 +6,7 @@ draft: true minutes_to_complete: 10 -official_docs: https://github.com/containerd/nerdctl +official_docs: https://github.com/containerd/nerdctl/blob/main/docs/command-reference.md additional_search_terms: - container @@ -25,15 +25,17 @@ multitool_install_part: false weight: 1 --- -Nerdctl is an open-source command-line interface (CLI) designed to be compatible with the popular Docker CLI, but specifically for interacting with [containerd](https://containerd.io/). It provides a familiar user experience for developers operators who are familiar with Docker, while leveraging the capabilities of containerd as the underlying container runtime. +[Nerdctl](https://github.com/containerd/nerdctl) is an open-source command-line interface (CLI) designed to be compatible with the Docker CLI, but specifically for interacting with [containerd](https://containerd.io/). It provides a familiar user experience for developers who are familiar with Docker, while leveraging the capabilities of containerd as the underlying container runtime. -Using containerd and nerdctl provides similar functionality to Docker but with a smaller memory and CPU footprint, making it ideal for IoT or edge solutions, especially on Arm devices which balance energy efficiency and performance. Nerdctl also supports running containers in a rootless mode, enhancing security by not requiring elevated privileges. +Using containerd and nerdctl provides similar functionality to Docker but with a smaller memory footprint, making it ideal for IoT and edge solutions, especially on Arm devices that balance energy efficiency and performance. -This guide focuses on installing containerd and Nerdctl on Arm Linux. +Nerdctl also supports running containers in rootless mode, which helps enhance security by not requiring elevated privileges. Rootless mode is not covered below but you can refer to the [documentation](https://rootlesscontaine.rs/getting-started/containerd/) for information about how to run `containerd-rootless-setuptool.sh install`. + +This guide explains how to install and use containerd and nerdctl on Arm Linux and run with `sudo` ## Before you begin -This guide assumes you are using Ubuntu 22.04 or later on an Arm-based system (like a Raspberry Pi or an Arm instance in the cloud). +This guide assumes you are using a Debian-based Arm Linux distribution, including Ubuntu and Raspberry Pi OS. You can use a local Arm Linux computer or an Arm instance in the cloud. Confirm you are using an Arm machine by running: @@ -47,7 +49,7 @@ The output should be: aarch64 ``` -Ensure `wget` and `tar` are installed: +Ensure `wget` and `tar` are installed. Most distributions will include them, but if not run: ```bash sudo apt-get update @@ -59,8 +61,7 @@ sudo apt-get install -y wget tar Install the containerd runtime: ```bash -sudo apt-get update -sudo apt-get install -y containerd +sudo apt-get install containerd -y ``` Start and enable the containerd service: @@ -70,9 +71,30 @@ sudo systemctl start containerd sudo systemctl enable containerd ``` +Confirm the service is running: + +```console +systemctl status containerd.service +``` + +When containerd is running, the output is similar to: + +```output +● containerd.service - containerd container runtime + Loaded: loaded (/usr/lib/systemd/system/containerd.service; enabled; preset: enabled) + Active: active (running) since Tue 2025-04-22 20:12:03 UTC; 2min 20s ago + Docs: https://containerd.io + Main PID: 8428 (containerd) + Tasks: 9 + Memory: 13.0M (peak: 13.7M) + CPU: 401ms + CGroup: /system.slice/containerd.service + └─8428 /usr/bin/containerd +``` + ## Install nerdctl and CNI plugins -Install nerdctl and the necessary CNI (Container Network Interface) plugins. Replace version numbers if needed. +Install nerdctl and the necessary CNI (Container Network Interface) plugins. ```bash NERDCTL_VERSION=$(curl -s https://api.github.com/repos/containerd/nerdctl/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//') @@ -95,35 +117,131 @@ Clean up the downloaded files: rm nerdctl-${NERDCTL_VERSION}-linux-arm64.tar.gz cni-plugins-linux-arm64-v${CNI_VERSION}.tgz ``` - {{% notice Note %}} -The commands above attempt to fetch the latest versions automatically. You can replace `${NERDCTL_VERSION}` and `${CNI_VERSION}` with specific versions if required.* -{{% /notice %} +The commands above attempt to fetch the latest versions automatically. If required, you can replace `${NERDCTL_VERSION}` and `${CNI_VERSION}` with specific versions. +{{% /notice %}} + +## Install BuildKit + +If you want to build container images with nerdctl, you need to install [BuildKit](https://github.com/moby/buildkit). + +If you only need to run container images you can skip this step. + +```bash +BUILDKIT_VERSION=$(curl -s https://api.github.com/repos/moby/buildkit/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//') +wget https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-arm64.tar.gz +sudo tar -xzvf buildkit-v${BUILDKIT_VERSION}.linux-arm64.tar.gz -C /usr +rm buildkit-v${BUILDKIT_VERSION}.linux-arm64.tar.gz +``` + +Create a systemd service for BuildKit: + +```bash +sudo tee /etc/systemd/system/buildkit.service > /dev/null << EOF +[Unit] +Description=BuildKit +Documentation=https://github.com/moby/buildkit + +[Service] +ExecStart=/usr/bin/buildkitd --oci-worker=false --containerd-worker=true + +[Install] +WantedBy=multi-user.target +EOF +``` + +Start and enable the BuildKit service: + +```bash +sudo systemctl daemon-reload +sudo systemctl start buildkit +sudo systemctl enable buildkit +``` + +Verify BuildKit is running: + +```console +sudo systemctl status buildkit +``` + +When running, the output is similar to: + +```output +ubuntu@m1u:~$ sudo systemctl status buildkit +● buildkit.service - BuildKit + Loaded: loaded (/etc/systemd/system/buildkit.service; enabled; preset: enabled) + Active: active (running) since Tue 2025-04-22 22:55:39 CDT; 18min ago + Docs: https://github.com/moby/buildkit + Main PID: 22280 (buildkitd) + Tasks: 10 (limit: 4598) + Memory: 14.6M (peak: 42.0M) + CPU: 1.144s + CGroup: /system.slice/buildkit.service + └─22280 /usr/bin/buildkitd --oci-worker=false --containerd-worker=true +``` + +Check that buildctl can communicate with the daemon: + +```console +sudo buildctl debug workers +``` + +If BuildKit is properly installed, you should see output similar to: + +```output +ID PLATFORMS +jz1h9gb0xq39ob6868cr3ev6r linux/arm64 +``` ## Verify the installation -Test your installation by running a simple NGINX container: +You can check the nerdctl version: + +```console +sudo nerdctl version +``` + +Test your installation by running a simple container that prints the processor architecture: ```console sudo nerdctl run --name uname armswdev/uname ``` -Wait a few seconds for the container to run, and the Architecture is printed: +Wait a few seconds for the container to start. The architecture is printed: ```output Architecture is aarch64 ``` - Clean up the test container: ```console sudo nerdctl rm uname ``` -You can also check the nerdctl version: +To build a container image, use a text editor to copy the lines below to a new file named `Dockerfile`. + ```console -sudo nerdctl version +FROM ubuntu:latest +CMD echo -n "Architecture is " && uname -m +``` + +Build the container image: + +```console +sudo nerdctl build -t uname -f Dockerfile . +``` + +Run the new container image: + +```console +sudo nerdctl run uname +``` + +The output is the architecture: + +```output +Architecture is aarch64 ``` ## Basic nerdctl commands @@ -178,4 +296,16 @@ Remove a container: sudo nerdctl rm ``` +View container logs: + +```console +sudo nerdctl logs +``` + +Execute a command in a running container: + +```console +sudo nerdctl exec -it +``` + You are now ready to use nerdctl and containerd. From 58017b5b68eb799a987bdad386187d8fab0acfb0 Mon Sep 17 00:00:00 2001 From: Christophe Favergeon Date: Wed, 23 Apr 2025 07:40:53 +0200 Subject: [PATCH 076/122] Some cleaning : C code, some Python comment, picture placement. --- .../cmsisdsp-dev-with-python/how-to-4.md | 3 +-- .../cmsisdsp-dev-with-python/how-to-5.md | 2 -- .../cmsisdsp-dev-with-python/how-to-7.md | 18 ++++++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md index a31f4410e4..d85a8dc10a 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-4.md @@ -62,10 +62,9 @@ vad = np.array([[w]*(winLength-winOverlap) for w in cleaned]).flatten() ax.plot(data) ax.plot(vad) ``` -The reference implementation works. You can now implement the same version using CMSIS-DSP. - ![vad alt-text#center](vad.png "Figure 3. VAD") +The reference implementation works. You can now implement the same version using CMSIS-DSP. ### CMSIS-DSP Q15 VAD diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md index ee6aaab78d..e36593d3e0 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-5.md @@ -134,8 +134,6 @@ The FFT length must be a power of 2. The slice length is not necessarily a power ```python class NoiseSuppressionReference(NoiseSuppression): def __init__(self,slices): - # In a better version this could be computed from the signal length by taking the - # smaller power of two greater than the signal length. NoiseSuppression.__init__(self,slices) # Compute the vad signal diff --git a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md index bd833cd900..94f3f21b47 100644 --- a/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md +++ b/content/learning-paths/embedded-and-microcontrollers/cmsisdsp-dev-with-python/how-to-7.md @@ -29,8 +29,10 @@ def rescale(self,w): ``` #### C Version - ```C +#include "dsp/basic_math_functions.h" +#include "dsp/statistics_functions.h" + arm_status rescale(q15_t *w, uint32_t nb,q15_t *the_max) { uint32_t index; @@ -46,7 +48,7 @@ arm_status rescale(q15_t *w, uint32_t nb,q15_t *the_max) status = arm_divide_q15(0x7FFF,*the_max,"ient,&the_shift); if (status == ARM_MATH_SUCCESS) { - arm_scale_q15(w,quotient,the_shift,w,nb); + arm_scale_q15(w,quotient,(int8_t)the_shift,w,nb); } } @@ -82,12 +84,16 @@ def signal_energy_q15(window): #### C version ```C -int16_t signal_energy_q15(q15_t *window,unit32_t nb) +#include "dsp/basic_math_functions.h" +#include "dsp/fast_math_functions.h" +#include "dsp/statistics_functions.h" + +int16_t signal_energy_q15(q15_t *window,uint32_t nb) { q15_t mean,neg_mean; arm_mean_q15(window,nb,&mean); - arm_negate_q15(&mean,&mean,1); + arm_negate_q15(&mean,&neg_mean,1); arm_offset_q15(window,neg_mean,window,nb); @@ -95,7 +101,7 @@ int16_t signal_energy_q15(q15_t *window,unit32_t nb) q15_t energy; arm_power_q15(window,nb,&energy_q63); - energy=(q15_t)_SSAT((q31_t)(energy_q63>>20),16); + energy=(q15_t)__SSAT((q31_t)(energy_q63>>20),16); // Fixed point format of result is on 16 bit // but the specific format has not been identified @@ -105,7 +111,7 @@ int16_t signal_energy_q15(q15_t *window,unit32_t nb) arm_vlog_q15(&energy,&dB,1); - return(dB) + return(dB); } ``` From 64f40558f312b32504c926d500e561887b2c5dd6 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Wed, 23 Apr 2025 10:24:21 +0000 Subject: [PATCH 077/122] Minor change --- .../deepseek-cpu/deepseek-chatbot.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md index 498b7fe824..f365cae002 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md +++ b/content/learning-paths/servers-and-cloud-computing/deepseek-cpu/deepseek-chatbot.md @@ -165,7 +165,7 @@ Run the pre-quantized DeepSeek-R1 model exactly as the weights were downloaded f This command will use the downloaded model (`-m` flag), disable conversation mode explicitly (`-no-cnv` flag), adjust the randomness of the generated text (`--temp` flag), with the specified prompt (`-p` flag), and target a 512 token completion (`-n` flag), using 64 threads (`-t` flag). -You might notice there are many gguf files downloaded. Llama.cpp can load all series of files by passing the first one with `-m` flag. +You might notice there are many gguf files. Llama.cpp can load all series of files by passing the first one with `-m` flag. ## Analyze the output and performance statistics From aab9b7031b29fefe320523add0c095bed4134c7d Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Wed, 23 Apr 2025 08:52:08 -0500 Subject: [PATCH 078/122] Build CCA attestation with Veraison is ready to publish. --- .../cca-veraison-aws/_index.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md index 9c929962f7..61e81630f2 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-veraison-aws/_index.md @@ -1,31 +1,25 @@ --- title: Build a CCA Attestation Service on AWS with Veraison -draft: true -cascade: - draft: true - minutes_to_complete: 90 who_is_this_for: This Learning Path is for developers familiar with CCA attestation and the Veraison project. You'll learn how to deploy a scalable CCA attestation verifier service on AWS. - learning_objectives: - Build an attestation service on AWS using the Veraison project's components. - Set up Veraison as a verifier for Arm CCA attestation tokens by provisioning CCA platform endorsements. - prerequisites: - An [AWS account](/learning-paths/servers-and-cloud-computing/csp/aws/) with access to AWS services. - An x86 computer running Ubuntu or Arch Linux, authorized for AWS access. If you're using another build environment, you'll need to configure the toolchains for cross-compilation. - author: Paul Howard ### Tags skilllevels: Advanced subjects: Performance and Architecture armips: + - Neoverse - Cortex-A operatingsystems: - Linux @@ -33,9 +27,6 @@ tools_software_languages: - CCA - Runbook - - - further_reading: - resource: title: RATS architecture (RFC 9334) @@ -50,8 +41,6 @@ further_reading: link: https://datatracker.ietf.org/doc/draft-ietf-rats-ar4si/ type: documentation - - ### FIXED, DO NOT MODIFY # ================================================================================ weight: 1 # _index.md always has weight of 1 to order correctly From 80d8cf973b36dfbba3e12fc3df3451ddc0033f3c Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 23 Apr 2025 14:39:21 +0000 Subject: [PATCH 079/122] renamed content dirs & .md files that had capital letters. --- .../{5-SME2-matmul-asm.md => 5-sme2-matmul-asm.md} | 0 ...{6-SME2-matmul-intr.md => 6-sme2-matmul-intr.md} | 0 .../{3-env-setup-FVP.md => 3-env-setup-fvp.md} | 0 .../FA_Sphinx.png | Bin .../FA_render_graph_1.1.gif | Bin .../FA_step_drawcalls.gif | Bin .../Pillars.gif | Bin .../Render_graph_egypt.png | Bin .../Render_graph_egypt_redundant_attachments.png | Bin .../Render_graph_egypt_redundant_rps.png | Bin .../_index.md | 0 .../_next-steps.md | 0 .../analyze.md | 0 .../analyze_geometry.md | 0 .../analyze_render_graph.md | 0 .../capture_a_trace.md | 0 .../fa_analysis.png | Bin .../fa_capture.png | Bin .../fa_connect.png | Bin .../fa_content_metrics.png | Bin .../fa_detailed_snakes.png | Bin .../fa_detailed_sphinx.png | Bin .../fa_frame_hierarchy.png | Bin .../fa_launch_screen.png | Bin .../fa_show_descriptions.png | Bin .../fa_snakes.png | Bin .../fa_sort_vse.png | Bin .../feedback.md | 0 .../what_is_frame_advisor.md | 0 ...t-1.md => 5-render-a-simple-3d-object-part-1.md} | 0 ...t-2.md => 6-render-a-simple-3d-object-part-2.md} | 0 ...mline.md => 7-profiling-app-using-streamline.md} | 0 .../1-dev-env-setup.md | 0 .../2-executorch-setup.md | 0 .../3-understanding-llama-models.md} | 0 .../4-prepare-llama-models.md} | 0 .../5-run-benchmark-on-android.md} | 0 .../6-build-android-chat-app.md} | 0 .../_index.md | 0 .../_next-steps.md | 0 .../device-explorer-upload.png | Bin .../device-explorer.png | Bin .../example-prompt-1.png | Bin .../example-prompt-2.png | Bin .../{02-ML-in-games.md => 02-ml-in-games.md} | 0 .../{03-Install.md => 03-install.md} | 0 ...{04-The-Dr-Arm-Game.md => 04-the-dr-arm-game.md} | 0 ...-in-Unity.md => 05-machine-learning-in-unity.md} | 0 ...The-Unity-project.md => 06-the-unity-project.md} | 0 ...d-Components.md => 07-objects-and-components.md} | 0 ...ur-ML-agents.md => 08-training-our-ml-agents.md} | 0 .../{09-Optimizations.md => 09-optimizations.md} | 0 .../{10-Summary.md => 10-summary.md} | 0 .../1-devenv-and-model.md | 0 .../2-generate-apk.md | 0 .../3-benchmark.md | 0 .../Loading_page.png | Bin .../_index.md | 0 .../_next-steps.md | 0 .../background.md | 0 .../chat2.png | Bin .../chat_page.png | Bin .../example-picture.png | Bin .../example.png | Bin .../install_ndk.png | Bin .../open_project.png | Bin .../{funASR => funasr}/1_asr.md | 0 .../{funASR => funasr}/2_modelscope.md | 0 .../{funASR => funasr}/3_funasr.md | 0 .../{funASR => funasr}/_index.md | 0 .../_next-steps.md | 0 ...xample_application.md => example_application.md} | 0 .../{Tuning Parameters.md => tuning-parameters.md} | 0 .../_index.md | 0 .../_next-steps.md | 0 .../before-you-begin.md | 0 .../bundles.png | Bin .../instrumentation.md | 0 .../instrumentation2.md | 0 .../intro.md | 0 .../plot.md | 0 .../{vLLM => vllm}/_index.md | 0 .../{vLLM => vllm}/_next-steps.md | 0 .../{vLLM => vllm}/vllm-run.md | 0 .../{vLLM => vllm}/vllm-server.md | 0 .../{vLLM => vllm}/vllm-setup.md | 0 86 files changed, 0 insertions(+), 0 deletions(-) rename content/learning-paths/cross-platform/multiplying-matrices-with-sme2/{5-SME2-matmul-asm.md => 5-sme2-matmul-asm.md} (100%) rename content/learning-paths/cross-platform/multiplying-matrices-with-sme2/{6-SME2-matmul-intr.md => 6-sme2-matmul-intr.md} (100%) rename content/learning-paths/embedded-and-microcontrollers/introduction-to-tinyml-on-arm/{3-env-setup-FVP.md => 3-env-setup-fvp.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/FA_Sphinx.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/FA_render_graph_1.1.gif (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/FA_step_drawcalls.gif (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/Pillars.gif (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/Render_graph_egypt.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/Render_graph_egypt_redundant_attachments.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/Render_graph_egypt_redundant_rps.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/_index.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/_next-steps.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/analyze.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/analyze_geometry.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/analyze_render_graph.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/capture_a_trace.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_analysis.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_capture.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_connect.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_content_metrics.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_detailed_snakes.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_detailed_sphinx.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_frame_hierarchy.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_launch_screen.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_show_descriptions.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_snakes.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/fa_sort_vse.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/feedback.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Analyze_a_frame_with_Frame_Advisor => analyze_a_frame_with_frame_advisor}/what_is_frame_advisor.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/{5-render-a-simple-3D-object-part-1.md => 5-render-a-simple-3d-object-part-1.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/{6-render-a-simple-3D-object-part-2.md => 6-render-a-simple-3d-object-part-2.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/{7-Profiling-App-using-Streamline.md => 7-profiling-app-using-streamline.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/1-dev-env-setup.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/2-executorch-setup.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/3-Understanding-LLaMA-models.md => build-llama3-chat-android-app-using-executorch-and-xnnpack/3-understanding-llama-models.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/4-Prepare-LLaMA-models.md => build-llama3-chat-android-app-using-executorch-and-xnnpack/4-prepare-llama-models.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/5-Run-Benchmark-on-Android.md => build-llama3-chat-android-app-using-executorch-and-xnnpack/5-run-benchmark-on-android.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/6-Build-Android-Chat-App.md => build-llama3-chat-android-app-using-executorch-and-xnnpack/6-build-android-chat-app.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/_index.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/_next-steps.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/device-explorer-upload.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/device-explorer.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/example-prompt-1.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK => build-llama3-chat-android-app-using-executorch-and-xnnpack}/example-prompt-2.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{02-ML-in-games.md => 02-ml-in-games.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{03-Install.md => 03-install.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{04-The-Dr-Arm-Game.md => 04-the-dr-arm-game.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{05-Machine-learning-in-Unity.md => 05-machine-learning-in-unity.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{06-The-Unity-project.md => 06-the-unity-project.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{07-Objects-and-Components.md => 07-objects-and-components.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{08-Training-our-ML-agents.md => 08-training-our-ml-agents.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{09-Optimizations.md => 09-optimizations.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/{10-Summary.md => 10-summary.md} (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/1-devenv-and-model.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/2-generate-apk.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/3-benchmark.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/Loading_page.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/_index.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/_next-steps.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/background.md (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/chat2.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/chat_page.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/example-picture.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/example.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/install_ndk.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/{Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN => vision-llm-inference-on-android-with-kleidiai-and-mnn}/open_project.png (100%) rename content/learning-paths/servers-and-cloud-computing/{funASR => funasr}/1_asr.md (100%) rename content/learning-paths/servers-and-cloud-computing/{funASR => funasr}/2_modelscope.md (100%) rename content/learning-paths/servers-and-cloud-computing/{funASR => funasr}/3_funasr.md (100%) rename content/learning-paths/servers-and-cloud-computing/{funASR => funasr}/_index.md (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => funasr}/_next-steps.md (100%) rename content/learning-paths/servers-and-cloud-computing/java-gc-tuning/{Example_application.md => example_application.md} (100%) rename content/learning-paths/servers-and-cloud-computing/java-gc-tuning/{Tuning Parameters.md => tuning-parameters.md} (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => pmuv3_plugin_learning_path}/_index.md (100%) rename content/learning-paths/servers-and-cloud-computing/{funASR => pmuv3_plugin_learning_path}/_next-steps.md (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => pmuv3_plugin_learning_path}/before-you-begin.md (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => pmuv3_plugin_learning_path}/bundles.png (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => pmuv3_plugin_learning_path}/instrumentation.md (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => pmuv3_plugin_learning_path}/instrumentation2.md (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => pmuv3_plugin_learning_path}/intro.md (100%) rename content/learning-paths/servers-and-cloud-computing/{PMUv3_plugin_learning_path => pmuv3_plugin_learning_path}/plot.md (100%) rename content/learning-paths/servers-and-cloud-computing/{vLLM => vllm}/_index.md (100%) rename content/learning-paths/servers-and-cloud-computing/{vLLM => vllm}/_next-steps.md (100%) rename content/learning-paths/servers-and-cloud-computing/{vLLM => vllm}/vllm-run.md (100%) rename content/learning-paths/servers-and-cloud-computing/{vLLM => vllm}/vllm-server.md (100%) rename content/learning-paths/servers-and-cloud-computing/{vLLM => vllm}/vllm-setup.md (100%) diff --git a/content/learning-paths/cross-platform/multiplying-matrices-with-sme2/5-SME2-matmul-asm.md b/content/learning-paths/cross-platform/multiplying-matrices-with-sme2/5-sme2-matmul-asm.md similarity index 100% rename from content/learning-paths/cross-platform/multiplying-matrices-with-sme2/5-SME2-matmul-asm.md rename to content/learning-paths/cross-platform/multiplying-matrices-with-sme2/5-sme2-matmul-asm.md diff --git a/content/learning-paths/cross-platform/multiplying-matrices-with-sme2/6-SME2-matmul-intr.md b/content/learning-paths/cross-platform/multiplying-matrices-with-sme2/6-sme2-matmul-intr.md similarity index 100% rename from content/learning-paths/cross-platform/multiplying-matrices-with-sme2/6-SME2-matmul-intr.md rename to content/learning-paths/cross-platform/multiplying-matrices-with-sme2/6-sme2-matmul-intr.md diff --git a/content/learning-paths/embedded-and-microcontrollers/introduction-to-tinyml-on-arm/3-env-setup-FVP.md b/content/learning-paths/embedded-and-microcontrollers/introduction-to-tinyml-on-arm/3-env-setup-fvp.md similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/introduction-to-tinyml-on-arm/3-env-setup-FVP.md rename to content/learning-paths/embedded-and-microcontrollers/introduction-to-tinyml-on-arm/3-env-setup-fvp.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/FA_Sphinx.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/FA_Sphinx.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/FA_Sphinx.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/FA_Sphinx.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/FA_render_graph_1.1.gif b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/FA_render_graph_1.1.gif similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/FA_render_graph_1.1.gif rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/FA_render_graph_1.1.gif diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/FA_step_drawcalls.gif b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/FA_step_drawcalls.gif similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/FA_step_drawcalls.gif rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/FA_step_drawcalls.gif diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Pillars.gif b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Pillars.gif similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Pillars.gif rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Pillars.gif diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Render_graph_egypt.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Render_graph_egypt.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Render_graph_egypt.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Render_graph_egypt.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Render_graph_egypt_redundant_attachments.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Render_graph_egypt_redundant_attachments.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Render_graph_egypt_redundant_attachments.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Render_graph_egypt_redundant_attachments.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Render_graph_egypt_redundant_rps.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Render_graph_egypt_redundant_rps.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/Render_graph_egypt_redundant_rps.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/Render_graph_egypt_redundant_rps.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/_index.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/_index.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/_index.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/_index.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/_next-steps.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/_next-steps.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/_next-steps.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/_next-steps.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/analyze.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/analyze.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/analyze.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/analyze.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/analyze_geometry.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/analyze_geometry.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/analyze_geometry.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/analyze_geometry.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/analyze_render_graph.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/analyze_render_graph.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/analyze_render_graph.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/analyze_render_graph.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/capture_a_trace.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/capture_a_trace.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/capture_a_trace.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/capture_a_trace.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_analysis.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_analysis.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_analysis.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_analysis.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_capture.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_capture.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_capture.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_capture.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_connect.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_connect.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_connect.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_connect.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_content_metrics.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_content_metrics.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_content_metrics.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_content_metrics.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_detailed_snakes.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_detailed_snakes.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_detailed_snakes.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_detailed_snakes.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_detailed_sphinx.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_detailed_sphinx.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_detailed_sphinx.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_detailed_sphinx.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_frame_hierarchy.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_frame_hierarchy.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_frame_hierarchy.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_frame_hierarchy.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_launch_screen.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_launch_screen.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_launch_screen.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_launch_screen.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_show_descriptions.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_show_descriptions.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_show_descriptions.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_show_descriptions.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_snakes.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_snakes.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_snakes.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_snakes.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_sort_vse.png b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_sort_vse.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/fa_sort_vse.png rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/fa_sort_vse.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/feedback.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/feedback.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/feedback.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/feedback.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/what_is_frame_advisor.md b/content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/what_is_frame_advisor.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Analyze_a_frame_with_Frame_Advisor/what_is_frame_advisor.md rename to content/learning-paths/mobile-graphics-and-gaming/analyze_a_frame_with_frame_advisor/what_is_frame_advisor.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3D-object-part-1.md b/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3d-object-part-1.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3D-object-part-1.md rename to content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3d-object-part-1.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/6-render-a-simple-3D-object-part-2.md b/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/6-render-a-simple-3d-object-part-2.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/6-render-a-simple-3D-object-part-2.md rename to content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/6-render-a-simple-3d-object-part-2.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-Profiling-App-using-Streamline.md b/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-profiling-app-using-streamline.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-Profiling-App-using-Streamline.md rename to content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-profiling-app-using-streamline.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/1-dev-env-setup.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/1-dev-env-setup.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/1-dev-env-setup.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/1-dev-env-setup.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/2-executorch-setup.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/2-executorch-setup.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/2-executorch-setup.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/2-executorch-setup.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/3-Understanding-LLaMA-models.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/3-understanding-llama-models.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/3-Understanding-LLaMA-models.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/3-understanding-llama-models.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/4-Prepare-LLaMA-models.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/4-prepare-llama-models.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/4-Prepare-LLaMA-models.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/4-prepare-llama-models.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/5-Run-Benchmark-on-Android.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/5-run-benchmark-on-android.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/5-Run-Benchmark-on-Android.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/5-run-benchmark-on-android.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/6-Build-Android-Chat-App.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/6-build-android-chat-app.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/6-Build-Android-Chat-App.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/6-build-android-chat-app.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/_index.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_index.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/_index.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_index.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/_next-steps.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_next-steps.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/_next-steps.md rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_next-steps.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/device-explorer-upload.png b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/device-explorer-upload.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/device-explorer-upload.png rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/device-explorer-upload.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/device-explorer.png b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/device-explorer.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/device-explorer.png rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/device-explorer.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/example-prompt-1.png b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/example-prompt-1.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/example-prompt-1.png rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/example-prompt-1.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/example-prompt-2.png b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/example-prompt-2.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Build-Llama3-Chat-Android-App-Using-Executorch-And-XNNPACK/example-prompt-2.png rename to content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/example-prompt-2.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/02-ML-in-games.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/02-ml-in-games.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/02-ML-in-games.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/02-ml-in-games.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/03-Install.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/03-install.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/03-Install.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/03-install.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/04-The-Dr-Arm-Game.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/04-the-dr-arm-game.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/04-The-Dr-Arm-Game.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/04-the-dr-arm-game.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/05-Machine-learning-in-Unity.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/05-machine-learning-in-unity.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/05-Machine-learning-in-Unity.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/05-machine-learning-in-unity.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/06-The-Unity-project.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/06-the-unity-project.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/06-The-Unity-project.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/06-the-unity-project.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/07-Objects-and-Components.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/07-objects-and-components.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/07-Objects-and-Components.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/07-objects-and-components.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-Training-our-ML-agents.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-training-our-ml-agents.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-Training-our-ML-agents.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-training-our-ml-agents.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/09-Optimizations.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/09-optimizations.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/09-Optimizations.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/09-optimizations.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/10-Summary.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/10-summary.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/10-Summary.md rename to content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/10-summary.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/1-devenv-and-model.md b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/1-devenv-and-model.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/1-devenv-and-model.md rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/1-devenv-and-model.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/2-generate-apk.md b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/2-generate-apk.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/2-generate-apk.md rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/2-generate-apk.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/3-benchmark.md b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/3-benchmark.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/3-benchmark.md rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/3-benchmark.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/Loading_page.png b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/Loading_page.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/Loading_page.png rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/Loading_page.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/_index.md b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/_index.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/_index.md rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/_index.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/_next-steps.md b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/_next-steps.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/_next-steps.md rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/_next-steps.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/background.md b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/background.md similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/background.md rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/background.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/chat2.png b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/chat2.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/chat2.png rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/chat2.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/chat_page.png b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/chat_page.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/chat_page.png rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/chat_page.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/example-picture.png b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/example-picture.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/example-picture.png rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/example-picture.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/example.png b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/example.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/example.png rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/example.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/install_ndk.png b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/install_ndk.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/install_ndk.png rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/install_ndk.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/open_project.png b/content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/open_project.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/Vision-LLM-inference-on-Android-with-KleidiAI-and-MNN/open_project.png rename to content/learning-paths/mobile-graphics-and-gaming/vision-llm-inference-on-android-with-kleidiai-and-mnn/open_project.png diff --git a/content/learning-paths/servers-and-cloud-computing/funASR/1_asr.md b/content/learning-paths/servers-and-cloud-computing/funasr/1_asr.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/funASR/1_asr.md rename to content/learning-paths/servers-and-cloud-computing/funasr/1_asr.md diff --git a/content/learning-paths/servers-and-cloud-computing/funASR/2_modelscope.md b/content/learning-paths/servers-and-cloud-computing/funasr/2_modelscope.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/funASR/2_modelscope.md rename to content/learning-paths/servers-and-cloud-computing/funasr/2_modelscope.md diff --git a/content/learning-paths/servers-and-cloud-computing/funASR/3_funasr.md b/content/learning-paths/servers-and-cloud-computing/funasr/3_funasr.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/funASR/3_funasr.md rename to content/learning-paths/servers-and-cloud-computing/funasr/3_funasr.md diff --git a/content/learning-paths/servers-and-cloud-computing/funASR/_index.md b/content/learning-paths/servers-and-cloud-computing/funasr/_index.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/funASR/_index.md rename to content/learning-paths/servers-and-cloud-computing/funasr/_index.md diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/_next-steps.md b/content/learning-paths/servers-and-cloud-computing/funasr/_next-steps.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/_next-steps.md rename to content/learning-paths/servers-and-cloud-computing/funasr/_next-steps.md diff --git a/content/learning-paths/servers-and-cloud-computing/java-gc-tuning/Example_application.md b/content/learning-paths/servers-and-cloud-computing/java-gc-tuning/example_application.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/java-gc-tuning/Example_application.md rename to content/learning-paths/servers-and-cloud-computing/java-gc-tuning/example_application.md diff --git a/content/learning-paths/servers-and-cloud-computing/java-gc-tuning/Tuning Parameters.md b/content/learning-paths/servers-and-cloud-computing/java-gc-tuning/tuning-parameters.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/java-gc-tuning/Tuning Parameters.md rename to content/learning-paths/servers-and-cloud-computing/java-gc-tuning/tuning-parameters.md diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/_index.md b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/_index.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/_index.md rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/_index.md diff --git a/content/learning-paths/servers-and-cloud-computing/funASR/_next-steps.md b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/_next-steps.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/funASR/_next-steps.md rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/_next-steps.md diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/before-you-begin.md b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/before-you-begin.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/before-you-begin.md rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/before-you-begin.md diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/bundles.png b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/bundles.png similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/bundles.png rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/bundles.png diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/instrumentation.md b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/instrumentation.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/instrumentation.md rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/instrumentation.md diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/instrumentation2.md b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/instrumentation2.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/instrumentation2.md rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/instrumentation2.md diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/intro.md b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/intro.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/intro.md rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/intro.md diff --git a/content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/plot.md b/content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/plot.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/PMUv3_plugin_learning_path/plot.md rename to content/learning-paths/servers-and-cloud-computing/pmuv3_plugin_learning_path/plot.md diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM/_index.md b/content/learning-paths/servers-and-cloud-computing/vllm/_index.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/vLLM/_index.md rename to content/learning-paths/servers-and-cloud-computing/vllm/_index.md diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM/_next-steps.md b/content/learning-paths/servers-and-cloud-computing/vllm/_next-steps.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/vLLM/_next-steps.md rename to content/learning-paths/servers-and-cloud-computing/vllm/_next-steps.md diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM/vllm-run.md b/content/learning-paths/servers-and-cloud-computing/vllm/vllm-run.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/vLLM/vllm-run.md rename to content/learning-paths/servers-and-cloud-computing/vllm/vllm-run.md diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM/vllm-server.md b/content/learning-paths/servers-and-cloud-computing/vllm/vllm-server.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/vLLM/vllm-server.md rename to content/learning-paths/servers-and-cloud-computing/vllm/vllm-server.md diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM/vllm-setup.md b/content/learning-paths/servers-and-cloud-computing/vllm/vllm-setup.md similarity index 100% rename from content/learning-paths/servers-and-cloud-computing/vLLM/vllm-setup.md rename to content/learning-paths/servers-and-cloud-computing/vllm/vllm-setup.md From 04d605d45c078e53346b3ffd0daf416f54a4b37f Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Wed, 23 Apr 2025 16:31:15 +0000 Subject: [PATCH 080/122] Tech review of Azure IoT LP --- .../learning-paths/iot/azure-iot/_index.md | 13 +--- .../iot/azure-iot/aggregation.md | 19 +++-- .../iot/azure-iot/device_registration.md | 43 ++++++----- .../learning-paths/iot/azure-iot/iot-hub.md | 8 +-- .../iot/azure-iot/monitoring.md | 71 +++++++++---------- .../learning-paths/iot/azure-iot/portal.md | 39 +++++----- .../azure-iot/stream-analytics-dynamo-db.md | 27 ++++--- .../iot/azure-iot/stream-analytics.md | 33 ++++----- 8 files changed, 121 insertions(+), 132 deletions(-) diff --git a/content/learning-paths/iot/azure-iot/_index.md b/content/learning-paths/iot/azure-iot/_index.md index 6757d7f5f4..78495acc86 100644 --- a/content/learning-paths/iot/azure-iot/_index.md +++ b/content/learning-paths/iot/azure-iot/_index.md @@ -1,7 +1,7 @@ --- -title: Creating IoT Solutions in Azure for Arm64-Powered Devices +title: Create IoT Solutions in Azure for Arm Powered Devices -minutes_to_complete: 320 +minutes_to_complete: 180 who_is_this_for: This is an advanced topic for software developers interested in learning how to build a comprehensive IoT solution in Azure that streams, stores, monitors, aggregates, and visualizes data from Arm64-powered IoT devices. @@ -17,8 +17,6 @@ learning_objectives: prerequisites: - A machine that can run Python3, and Visual Studio Code. - Azure Account and Subscription. - - Azure CLI (Command Line Interface). - - Azure IoT SDK for Python. author: Dawid Borycki @@ -33,13 +31,8 @@ operatingsystems: - Linux - macOS tools_software_languages: - - Coding + - Azure - VS Code -shared_path: true -shared_between: - - servers-and-cloud-computing - - laptops-and-desktops - - mobile-graphics-and-gaming further_reading: - resource: diff --git a/content/learning-paths/iot/azure-iot/aggregation.md b/content/learning-paths/iot/azure-iot/aggregation.md index e89871f593..146184b1c1 100644 --- a/content/learning-paths/iot/azure-iot/aggregation.md +++ b/content/learning-paths/iot/azure-iot/aggregation.md @@ -7,8 +7,7 @@ weight: 8 layout: "learningpathall" --- -## Objective -In the previous section, you configured Azure Stream Analytics to securely store incoming IoT telemetry data in Azure Cosmos DB, making sensor data readily available for further processing. In this section, you’ll enhance your IoT solution by implementing real-time data aggregation capabilities using Azure Functions. Azure Functions is a powerful, event-driven, serverless compute service provided by Azure that allows you to execute custom code in response to scheduled events without managing infrastructure. You’ll create an Azure Function that periodically queries sensor data from Cosmos DB and computes aggregated metrics, such as average, minimum, and maximum values, enabling you to derive actionable insights and monitor sensor performance more effectively. +In the previous section, you configured Azure Stream Analytics to securely store incoming IoT telemetry data in Azure Cosmos DB, making sensor data readily available for further processing. In this section, you will enhance your IoT solution by implementing real-time data aggregation capabilities using Azure Functions. Azure Functions is a powerful, event-driven, serverless compute service provided by Azure that allows you to execute custom code in response to scheduled events without managing infrastructure. You will create an Azure Function that periodically queries sensor data from Cosmos DB and computes aggregated metrics, such as average, minimum, and maximum values, enabling you to derive actionable insights and monitor sensor performance more effectively. ## Data Aggregation As your IoT solution matures, the volume of sensor data continuously captured and securely stored in Azure Cosmos DB grows rapidly. However, raw telemetry data alone may not effectively communicate actionable insights, especially when quick decision-making and proactive management are required. Transforming this raw sensor data into meaningful, summarized information becomes essential for efficient monitoring, accurate analysis, and rapid response. @@ -18,9 +17,9 @@ Aggregating sensor readings into various metrics such as average, minimum, and m In this section, you will leverage Azure Functions to implement a data aggregation. This Azure Function will respond to the HTTP trigger, and return aggregated sensor data. ### Azure Function with HTTP Trigger -Building upon the sensor data aggregation strategy, this section demonstrates how to implement a serverless Azure Function using an HTTP trigger to calculate real-time insights from sensor data stored in Azure Cosmos DB. Specifically, you’ll create an HTTP-triggered function that queries temperature readings from the past minute, computes the average temperature, and returns this aggregated value as a JSON response. This HTTP-triggered approach provides an on-demand method to access up-to-date metrics. +Building upon the sensor data aggregation strategy, this section demonstrates how to implement a serverless Azure Function using an HTTP trigger to calculate real-time insights from sensor data stored in Azure Cosmos DB. Specifically, you will create an HTTP-triggered function that queries temperature readings from the past minute, computes the average temperature, and returns this aggregated value as a JSON response. This HTTP-triggered approach provides an on-demand method to access up-to-date metrics. -To implement this functionality open the function_app.py and modify it as follows: +To implement this functionality open the `function_app.py` and modify it as follows: 1. Add the following import statements: ```python from azure.cosmos import CosmosClient @@ -128,7 +127,7 @@ def get_average_temperature(req: func.HttpRequest) -> func.HttpResponse: ) ``` -The GetAverageTemperature function is triggered by an HTTP GET request sent to the route /averagetemperature. Upon invocation, it first logs that a request has been received for calculating the average temperature based on data from the last minute. +The `GetAverageTemperature` function is triggered by an HTTP GET request sent to the route /averagetemperature. Upon invocation, it first logs that a request has been received for calculating the average temperature based on data from the last minute. The function then retrieves the Cosmos DB connection string from an environment variable. If the connection string is not available, the function logs an error and returns a 500 Internal Server Error response, indicating that essential configuration details are missing. @@ -142,7 +141,7 @@ When data points are available, the function computes the average temperature fr Finally, if the average calculation succeeds, the function constructs a JSON response containing the calculated average temperature (rounded to two decimal places) along with a success message. It then sends this response back to the caller with a status code of 200 OK and the configured CORS header {"Access-Control-Allow-Origin": "*"}, which is required to ensure that the portal can successfully retrieve and display the data from the function. -Before running the function, dependencies need to be added and installed. Open the requirements.txt file and include the following lines: +Before running the function, dependencies need to be added and installed. Open the `requirements.txt` file and include the following lines: ```json azure-cosmos @@ -181,7 +180,7 @@ Now that your Azure Function is fully tested and ready, it's time to deploy it t ![img40 alt-text#center](Figures/40.png) ## Configure Function App Settings -We have just deployed the functions to Azure. Previously, when testing the functions locally, we used the local.settings.json file to store the Cosmos DB connection string. However, this local configuration file is not deployed to Azure. Therefore, we need to update the corresponding settings directly within the Azure portal. +You have just deployed the functions to Azure. Previously, when testing the functions locally, you used the `local.settings.json` file to store the Cosmos DB connection string. However, this local configuration file is not deployed to Azure. Therefore, you need to update the corresponding settings directly within the Azure portal. Azure Function App settings, which are also known as application settings or environment variables, are designed to securely store sensitive configuration information, such as database connection strings, API keys, and other confidential details. Storing the Cosmos DB connection string as an app setting in Azure ensures secure management of your database credentials, allowing your function to safely access Cosmos DB without exposing sensitive information within your source code. @@ -208,7 +207,7 @@ Once you've configured the connection string, test your deployed Azure Function This confirms your Azure Function successfully connects to Cosmos DB, retrieves real-time data, and calculates the average temperature as intended -## Summary and next steps -In this section, you created an HTTP-triggered Azure Function that retrieves and aggregates records from Cosmos DB. You then deployed your Azure Function to Azure, configured secure application settings to safely store the Cosmos DB connection string, and verified the functionality. You also learned that the local configuration file (local.settings.json) is not automatically deployed to Azure, making it necessary to manually set up these sensitive settings within the Azure portal. Securely managing these application settings in Azure ensures that your functions can reliably connect to Cosmos DB, facilitating the accurate retrieval and processing of IoT telemetry data. +## Next Steps +In this section, you created an HTTP-triggered Azure Function that retrieves and aggregates records from Cosmos DB. You then deployed your Azure Function to Azure, configured secure application settings to safely store the Cosmos DB connection string, and verified the functionality. -In the next step, you’ll create a static website that leverages this HTTP-triggered function to display the average temperature in a web-based portal, thus completing your IoT solution. \ No newline at end of file +In the next step, you will create a static website that leverages this HTTP-triggered function to display the average temperature in a web-based portal, thus completing your IoT solution. diff --git a/content/learning-paths/iot/azure-iot/device_registration.md b/content/learning-paths/iot/azure-iot/device_registration.md index 603a60e3aa..32c4ea1f48 100644 --- a/content/learning-paths/iot/azure-iot/device_registration.md +++ b/content/learning-paths/iot/azure-iot/device_registration.md @@ -7,19 +7,19 @@ weight: 4 layout: "learningpathall" --- -## Intro -In this section, you’ll learn how to build a comprehensive IoT simulation using Azure IoT Hub and Python. You’ll create a reusable SensorReading class designed to simulate realistic sensor readings, including temperature, pressure, humidity, and timestamps. Additionally, you’ll implement a telemetry simulator that periodically sends these sensor readings to Azure IoT Hub, enabling you to observe real-time data streaming and cloud integration. +## Overview +In this section, you will learn how to build a comprehensive IoT simulation using Azure IoT Hub and Python. You will create a reusable `SensorReading` class designed to simulate realistic sensor readings, including temperature, pressure, humidity, and timestamps. You will also implement a telemetry simulator that periodically sends these sensor readings to Azure IoT Hub, enabling you to observe real-time data streaming and cloud integration. -Finally, you’ll configure your Python application to connect securely to Azure IoT Hub, allowing you to monitor and validate the continuous data flow. By the end of this section, you’ll have hands-on experience simulating IoT telemetry, providing a solid foundation for developing more advanced data analytics and visualization solutions in Azure. +Finally, you will configure your Python application to connect securely to Azure IoT Hub, allowing you to monitor and validate the continuous data flow. By the end of this section, you will have hands-on experience simulating IoT telemetry, providing a solid foundation for developing more advanced data analytics and visualization solutions in Azure. ## Azure IoT device SDK Begin by installing the Azure IoT Device SDK for Python, which provides essential tools and libraries needed to develop IoT applications that communicate seamlessly with Azure IoT Hub. This SDK enables secure device connectivity, message transmission, and management functionalities directly from Python code. -You can install the SDK easily using Python’s package manager pip. Open a terminal or command prompt and run the following command: +You can install the SDK easily using Python’s package manager `pip`. Open a terminal or command prompt and run the following command: ``` pip install azure-iot-device ``` -The output should look similar to the following one: +The output should look like: ```output Collecting azure-iot-device Downloading azure_iot_device-2.14.0-py3-none-any.whl.metadata (15 kB) @@ -56,11 +56,11 @@ Successfully installed PySocks-1.7.1 azure-iot-device-2.14.0 deprecation-2.1.0 j ``` ## Creating a Python IoT Simulator Application -In this section, you’ll create a Python application that simulates realistic sensor data generated by an Arm64-powered IoT device and streams this data securely to Azure IoT Hub. You’ll define a reusable and structured SensorReading class, capable of generating randomized yet realistic sensor measurements, including temperature, pressure, humidity, and timestamps. +In this section, you will create a Python application that simulates realistic sensor data generated by an Arm64 powered IoT device and streams this data securely to Azure IoT Hub. You will define a reusable and structured `SensorReading` class, capable of generating randomized yet realistic sensor measurements, including temperature, pressure, humidity, and timestamps. -Following this, you’ll implement an asynchronous telemetry simulator method, which continuously generates sensor readings at predefined intervals and transmits them to Azure IoT Hub. +After this, you will implement an asynchronous telemetry simulator method, which continuously generates sensor readings at predefined intervals and transmits them to Azure IoT Hub. -To achieve the above, create the iot_simulator.py file and modify it as follows: +To achieve the above, create a file named `iot_simulator.py` and add the following content to it: ```python import asyncio @@ -146,23 +146,23 @@ if __name__ == "__main__": main() ``` -The SensorReading class is designed to encapsulate and manage data from an IoT sensor. It models a single sensor reading by holding key attributes such as the device’s unique identifier, the measured temperature, pressure, and humidity, along with a timestamp that records exactly when the reading was taken. This structured representation allows you to easily handle and process sensor data within your application. +The `SensorReading` class is designed to encapsulate and manage data from an IoT sensor. It models a single sensor reading by holding key attributes such as the device unique identifier, measured temperature, pressure, and humidity, along with a timestamp that records exactly when the reading was taken. This structured representation allows you to easily handle and process sensor data within your application. -In addition to storing these values, the class provides a to_json method, which converts the sensor reading into a JSON-formatted string. This makes it particularly useful for transmitting data over the network, as JSON is a widely accepted format in IoT communications and web services. +In addition to storing these values, the class provides a `to_json` method, which converts the sensor reading into a `JSON`formatted string. This makes it particularly useful for transmitting data over the network, as `JSON` is a widely accepted format in IoT communications and web services. -Furthermore, the class includes a static method called generate_random. This method is a utility that simulates sensor data by generating random, yet realistic, values for temperature, pressure, and humidity. It also automatically sets the current UTC time (with proper timezone awareness) as the timestamp for the reading. This feature is especially useful when you need to simulate sensor output for testing or demonstration purposes, allowing you to mimic the behavior of a real IoT device without requiring actual sensor hardware. +Furthermore, the class includes a static method called `generate_random`. This method is a utility that simulates sensor data by generating random, yet realistic, values for temperature, pressure, and humidity. It also automatically sets the current `UTC` time (with proper timezone awareness) as the timestamp for the reading. This feature is especially useful when you need to simulate sensor output for testing or demonstration purposes, allowing you to mimic the behavior of a real IoT device without requiring actual sensor hardware. -Then we have the send_telemetry method. It is an asynchronous function designed to connect an IoT device to Azure IoT Hub and continuously transmit telemetry data at specified intervals. When invoked, it begins by creating a device client instance using the provided connection string, which contains the necessary credentials to authenticate with the IoT Hub. Once the connection is established, the send_telemetry method logs a confirmation message indicating a successful connection. +Next you have the `send_telemetry` method. It is an asynchronous function designed to connect an IoT device to Azure IoT Hub and continuously transmit telemetry data at specified intervals. When invoked, it begins by creating a device client instance using the provided connection string, which contains the necessary credentials to authenticate with the IoT Hub. Once the connection is established, the `send_telemetry` method logs a confirmation message indicating a successful connection. -Inside an infinite loop, the function repeatedly generates a new sensor reading by calling the generate_random method of the SensorReading class, which simulates realistic sensor data for temperature, pressure, and humidity along with a current timestamp. This sensor data is then converted to a JSON string using the to_json method, making it suitable for transmission. The JSON message is sent to the Azure IoT Hub using the device client, and a log entry records each transmission for monitoring purposes. +Inside an infinite loop, the function repeatedly generates a new sensor reading by calling the `generate_random` method of the `SensorReading` class, which simulates realistic sensor data for temperature, pressure, and humidity along with a current timestamp. This sensor data is then converted to a `JSON` string using the `to_json` method, making it suitable for transmission. The `JSON` message is sent to the Azure IoT Hub using the device client, and a log entry records each transmission for monitoring purposes. The function includes error handling to gracefully manage interruptions. For instance, if the user stops the process (via a keyboard interrupt), it logs that the telemetry sending has been halted. Additionally, any unexpected errors are caught and logged. Finally, regardless of how the loop is exited, the function ensures that the device client is properly disconnected from the IoT Hub, logging this disconnection to maintain clear operational records. -The main function serves as the entry point of the application, where essential configuration values for connecting to Azure IoT Hub are defined. Here, it sets the CONNECTION_STRING—which you need to replace with your actual device connection string from Azure IoT Hub—along with a unique DEVICE_ID and an INTERVAL_SECONDS value that determines how frequently the telemetry data is sent (in this case, every second). +The main function serves as the entry point of the application, where essential configuration values for connecting to Azure IoT Hub are defined. Here, it sets the `CONNECTION_STRING`, which you need to replace with your actual device connection string from Azure IoT Hub, along with a unique `DEVICE_ID` and an `INTERVAL_SECONDS` value that determines how frequently the telemetry data is sent (in this case, every second). -Within the main function, the asynchronous send_telemetry function is executed using asyncio.run(), which manages the event loop and ensures that the asynchronous operations run correctly. This function call initiates the process of connecting to the IoT Hub, generating sensor readings, and transmitting telemetry data at regular intervals. The entire operation is wrapped in a try-except block to catch and log any errors that might occur during execution. +Within the main function, the asynchronous `send_telemetry` function is executed using `asyncio.run()`, which manages the event loop and ensures that the asynchronous operations run correctly. This function call initiates the process of connecting to the IoT Hub, generating sensor readings, and transmitting telemetry data at regular intervals. The entire operation is wrapped in a try-except block to catch and log any errors that might occur during execution. -Finally, the conditional check if __name__ == "__main__": ensures that the main function is called only when the script is executed directly, rather than when it is imported as a module in another script. This structure provides a clear and organized starting point for the application, making it easier to understand and maintain. +Finally, the conditional check `if __name__ == "__main__":` ensures that the main function is called only when the script is executed directly, rather than when it is imported as a module in another script. This structure provides a clear and organized starting point for the application, making it easier to understand and maintain. ## Connecting IoT Device to Azure IoT Hub To connect the Python application you developed earlier to Azure IoT Hub, follow these detailed steps. @@ -183,7 +183,7 @@ Ensure this connection string is stored securely, as it provides authentication ## Streaming Telemetry Data to Azure IoT Hub Now you’re ready to stream telemetry data from your Python application directly to Azure IoT Hub. Follow these steps to configure and run the application: -1. Open your Python IoT simulator script (iot_simulator.py). +1. Open your Python IoT simulator script `iot_simulator.py`. 2. Update the connection string. Replace the placeholder with your actual Azure IoT device primary connection string, which you retrieved from the Azure portal: ```python CONNECTION_STRING = "HostName=iot-hub-arm64.azure-devices.net" @@ -195,7 +195,7 @@ Ensure this connection string exactly matches the string copied from Azure IoT H python3 iot_simulator.py ``` -Upon successful execution, you should see output similar to the following, confirming a stable connection and periodic telemetry data transmission to Azure IoT Hub: +Upon successful execution, you should see output similar to what is shown below, confirming a stable connection and periodic telemetry data transmission to Azure IoT Hub: ```output 2025-03-16 19:39:12,944 - INFO - Creating client for connecting using MQTT over TCP @@ -240,11 +240,8 @@ Upon successful execution, you should see output similar to the following, confi Each telemetry message contains randomized sensor data (temperature, pressure, humidity), device ID, and a timestamp, providing realistic simulated data for IoT applications. -To stop streaming telemetry data, press Ctrl+C in the terminal. The application will gracefully disconnect from Azure IoT Hub. +To stop streaming telemetry data, press `Ctrl+C` in the terminal. The application will gracefully disconnect from Azure IoT Hub. This step completes the telemetry-streaming component of your Azure IoT application, laying the groundwork for subsequent steps like data processing, monitoring, and visualization -## Summary -In this part, you’ve successfully created and configured a Python IoT simulator application designed specifically for streaming realistic sensor data from Arm64-powered IoT devices. You’ve implemented a robust and reusable SensorReading class, generating randomized values for key sensor metrics—temperature, pressure, humidity—and timestamping these readings accurately. Additionally, you connected the Python application securely to your newly created Azure IoT Hub using the Azure IoT device SDK, establishing real-time telemetry data streaming capabilities. - -With this setup in place, your simulated IoT device continuously transmits data to Azure IoT Hub, providing a solid foundation to explore more advanced IoT scenarios, such as real-time data analytics, cloud storage, monitoring, alerts, and data visualization within the Azure ecosystem. \ No newline at end of file +With this setup in place, your simulated IoT device continuously transmits data to Azure IoT Hub, providing a solid foundation to explore more advanced IoT scenarios, such as real-time data analytics, cloud storage, monitoring, alerts, and data visualization within the Azure ecosystem. diff --git a/content/learning-paths/iot/azure-iot/iot-hub.md b/content/learning-paths/iot/azure-iot/iot-hub.md index f215e28989..3f06af94b2 100644 --- a/content/learning-paths/iot/azure-iot/iot-hub.md +++ b/content/learning-paths/iot/azure-iot/iot-hub.md @@ -1,6 +1,6 @@ --- # User change -title: "IoT Hub" +title: "Azure IoT Hub" weight: 3 @@ -19,8 +19,8 @@ In the following sections of this tutorial, you’ll learn how to create and con ## Create Azure IoT Hub Start by creating an Azure IoT Hub 1. Sign in to the Azure Portal: -* Open your web browser and go to portal.azure.com. -* Sign in using your Azure account credentials. + * Open your web browser and go to https://portal.azure.com + * Sign in using your Azure account credentials. 2. Create a new Azure IoT Hub resource * On the Azure Portal home page, select “Create a resource” at the top left as shown below @@ -59,4 +59,4 @@ Start by creating an Azure IoT Hub ![img5 alt-text#center](Figures/05.png) ## Next steps -Now that your Azure IoT Hub is ready, you can proceed to register and configure your IoT devices. In the next step, you’ll learn how to register an Arm64-based IoT device and start streaming data using Python and Azure IoT SDK. \ No newline at end of file +Now that your Azure IoT Hub is ready, you can proceed to register and configure your IoT devices. In the next step, you’ll learn how to register an Arm64-based IoT device and start streaming data using Python and Azure IoT SDK. diff --git a/content/learning-paths/iot/azure-iot/monitoring.md b/content/learning-paths/iot/azure-iot/monitoring.md index 13c71fa98e..b844c3677c 100644 --- a/content/learning-paths/iot/azure-iot/monitoring.md +++ b/content/learning-paths/iot/azure-iot/monitoring.md @@ -7,15 +7,14 @@ weight: 7 layout: "learningpathall" --- -## Objective -In the previous section, you successfully configured Azure Stream Analytics to store incoming IoT telemetry data securely in Azure Cosmos DB. The stored sensor data is now readily accessible for further analysis, monitoring, and action. In this section, you’ll enhance your IoT solution by implementing real-time data monitoring and alerting capabilities using Azure Functions. +In the previous section, you successfully configured Azure Stream Analytics to store incoming IoT telemetry data securely in Azure Cosmos DB. The stored sensor data is now readily accessible for further analysis, monitoring, and action. In this section, you will enhance your IoT solution by implementing real-time data monitoring and alerting capabilities using Azure Functions. -Azure Functions is a powerful, event-driven, serverless compute service provided by Azure, enabling you to execute custom code in response to specific events or triggers without the need to manage infrastructure. You’ll create an Azure Function that regularly queries temperature data from Cosmos DB, evaluates sensor readings against predefined thresholds, and sends notifications when critical values are exceeded—such as detecting overheating or environmental anomalies. By adding this functionality, you’ll build proactive monitoring into your IoT pipeline, ensuring timely responses to sensor data events and improving overall operational reliability. +Azure Functions is a powerful, event-driven, serverless compute service provided by Azure, enabling you to execute custom code in response to specific events or triggers without the need to manage infrastructure. You will create an Azure Function that regularly queries temperature data from Cosmos DB, evaluates sensor readings against predefined thresholds, and sends notifications when critical values are exceeded,such as detecting overheating or environmental anomalies. By adding this functionality, you will build proactive monitoring into your IoT pipeline, ensuring timely responses to sensor data events and improving overall operational reliability. ## Azure Functions -Azure Functions is a serverless computing platform provided by Microsoft Azure, designed to enable developers to run event-driven code without having to provision or manage infrastructure. With Azure Functions, you can easily create small, focused applications or services that automatically respond to events, such as database updates, HTTP requests, IoT sensor data events, or scheduled tasks. Because Azure Functions is serverless, it automatically scales based on workload, providing elasticity, rapid deployment, and simplified maintenance—developers only pay for resources actually consumed during execution. +Azure Functions is a serverless computing platform provided by Microsoft Azure, designed to enable developers to run event-driven code without having to provision or manage infrastructure. With Azure Functions, you can easily create small, focused applications or services that automatically respond to events, such as database updates, HTTP requests, IoT sensor data events, or scheduled tasks. Because Azure Functions is serverless, it automatically scales based on workload, providing elasticity, rapid deployment, and simplified maintenance, developers only pay for resources actually consumed during execution. -In IoT scenarios, Azure Functions are particularly valuable for responding to real-time data events, such as sensor readings exceeding specific thresholds. You can integrate Azure Functions seamlessly with services like Azure Cosmos DB, Azure IoT Hub, or Azure Notification Hubs, enabling functions to trigger automatically when new data is received or when certain conditions are met. This flexibility allows you to build responsive, cost-effective, and efficient IoT applications that require minimal setup yet offer highly scalable, real-time processing capabilities. +In IoT, Azure Functions are particularly valuable for responding to real-time data events, such as sensor readings exceeding specific thresholds. You can integrate Azure Functions seamlessly with services like Azure Cosmos DB, Azure IoT Hub, or Azure Notification Hubs, enabling functions to trigger automatically when new data is received or when certain conditions are met. This flexibility allows you to build responsive, cost-effective, and efficient IoT applications that require minimal setup yet offer highly scalable, real-time processing capabilities. ### Event-driven Architecture Azure Functions are inherently event-driven, meaning your code is automatically executed in direct response to specific events or triggers, without manual intervention. Each Azure Function remains dormant and consumes no resources until activated by a defined event, at which point the function is instantly executed. Common triggers include events like new data being written to Azure Cosmos DB, telemetry messages arriving in Azure IoT Hub, incoming HTTP requests, scheduled timers, or even queue-based messages. @@ -23,7 +22,7 @@ Azure Functions are inherently event-driven, meaning your code is automatically This event-driven design ensures real-time responsiveness, making Azure Functions especially well-suited for IoT scenarios, where timely reactions to incoming sensor data or critical events are crucial. For instance, an Azure Function can immediately activate upon detecting new sensor data in Cosmos DB, evaluate the data (such as checking whether temperature thresholds are exceeded), and promptly send alerts or trigger follow-up actions. ### Serverless and Scalability -Azure Functions is built on a serverless computing model, meaning you can execute custom code in response to specific events without having to provision or maintain any underlying server infrastructure. This approach enables developers to focus purely on application logic rather than spending time on managing servers, operating systems, or runtime environments. When an event—such as an HTTP request, database update, or new IoT sensor reading—occurs, Azure Functions automatically triggers your custom code, scales the necessary resources dynamically, and executes the function. +Azure Functions is built on a serverless computing model, meaning you can execute custom code in response to specific events without having to provision or maintain any underlying server infrastructure. This approach enables developers to focus purely on application logic rather than spending time on managing servers, operating systems, or runtime environments. When an event such as an HTTP request, database update, or new IoT sensor reading occurs, Azure Functions automatically triggers your custom code, scales the necessary resources dynamically, and executes the function. In the context of IoT solutions, the serverless model offered by Azure Functions is especially valuable because it can efficiently handle unpredictable workloads, scaling instantly as data volume fluctuates. Functions can scale out horizontally to accommodate spikes in IoT data without manual intervention, providing real-time responsiveness and reliability. This automatic scaling, coupled with a consumption-based billing model (paying only for resources actually consumed), makes Azure Functions an optimal choice for cost-effective, efficient, and responsive IoT monitoring, alerting, and analytics applications. @@ -36,12 +35,12 @@ Examples of triggers include: * Cosmos DB Trigger - runs whenever new data is added or updated in Cosmos DB. * IoT Hub/Event Hub Triggers - respond immediately to events like incoming IoT device messages. -In IoT scenarios, triggers tied to Cosmos DB are particularly powerful. For example, an Azure Function can automatically activate when new sensor readings are stored in Cosmos DB, allowing you to implement real-time monitoring, send immediate notifications, or perform analytics. Each function’s event-driven execution ensures your application remains highly responsive, efficient, and scalable—crucial for maintaining performance and cost-effectiveness in IoT solutions +In IoT scenarios, triggers tied to Cosmos DB are particularly powerful. For example, an Azure Function can automatically activate when new sensor readings are stored in Cosmos DB, allowing you to implement real-time monitoring, send immediate notifications, or perform analytics. Each function’s event-driven execution ensures your application remains highly responsive, efficient, and scalable, crucial for maintaining performance and cost-effectiveness in IoT solutions ### Azure Functions Bindings In addition to triggers, Azure Functions provide another powerful feature called bindings. Bindings allow you to effortlessly connect your functions to other Azure services or external resources, simplifying both input and output integration. Using bindings, you can directly access data from services like Azure Cosmos DB, Azure Blob Storage, Azure Queue Storage without the need to write custom integration code or manage connection logic manually. -Bindings greatly accelerate development, as you can easily read from or write to external services declaratively—just by defining simple configurations. For instance, when working with IoT solutions, you can configure a Cosmos DB input binding to automatically retrieve sensor data as documents, or set up an output binding to seamlessly persist aggregated data or alerts back to Cosmos DB or Azure Storage. This eliminates repetitive integration code, reduces complexity, and significantly enhances productivity. +Bindings greatly accelerate development, as you can easily read from or write to external services declaratively just by defining simple configurations. For instance, when working with IoT solutions, you can configure a Cosmos DB input binding to automatically retrieve sensor data as documents, or set up an output binding to seamlessly persist aggregated data or alerts back to Cosmos DB or Azure Storage. This eliminates repetitive integration code, reduces complexity, and significantly enhances productivity. Overall, Azure Function bindings simplify and speed up your development workflow, allowing you to focus entirely on your application logic rather than managing integration details @@ -50,14 +49,14 @@ When working with Azure Functions, there are two primary deployment approaches: Azure Portal provides a user-friendly interface to quickly create, configure, and manage Azure Functions directly from your web browser. It’s particularly suitable for simple scenarios or quick prototypes, as it requires minimal setup and no installation of additional tools. You can easily define triggers, bindings, environment variables, and monitor function execution directly through the portal interface. -However, there’s an important limitation for Python-based Azure Functions. If you choose Python with a Linux-based hosting plan (required for Python functions), the Azure Portal does not support in-portal code editing. In other words, while you can manage and monitor your Python functions in the portal, you can’t directly edit or modify the function’s Python source code there. +However, there’s an important limitation for Python based Azure Functions. If you choose Python with a Linux-based hosting plan (required for Python functions), the Azure Portal does not support in-portal code editing. In other words, while you can manage and monitor your Python functions in the portal, you can’t directly edit or modify the function’s Python source code there. To overcome this limitation, local development is highly recommended for Python-based Azure Functions. Local development involves developing and testing your Azure Functions on your own computer using the Azure Functions Core Tools and a IDE like Visual Studio Code. After development and local testing, you deploy your function to Azure using command-line tools (func CLI), IDE integrations, or continuous integration solutions such as GitHub Actions. -For Python functions on Linux-based plans, local development and deployment represent the best-practice approach, enabling you to efficiently create, debug, test, and manage more sophisticated IoT solutions. Therefore, in this section we will use local development. +For Python functions on Linux-based plans, local development and deployment represent the best-practice approach, enabling you to efficiently create, debug, test, and manage more sophisticated IoT solutions. Therefore, in this section you will use local development. ## Create an Azure Function App -We will start by creating an Azure Function App, in which we will create an Azure Function that regularly queries temperature data from Cosmos DB. In the next step, we will add the capability to send notifications, whenever the temperature reading exceeds a predefined threshold. Proceed as follows: +You will start by creating an Azure Function App, in which you will create an Azure Function that regularly queries temperature data from Cosmos DB. In the next step, upi will add the capability to send notifications, whenever the temperature reading exceeds a predefined threshold. Proceed as follows: 1. Sign in to the Azure Portal. 2. Click “Create a resource”, type “Function App”, and select it: ![img24 alt-text#center](Figures/24.png) @@ -70,7 +69,7 @@ We will start by creating an Azure Function App, in which we will create an Azur * Runtime Stack: Select Python. * Version: Select 3.11 * Region: Select the same region as your Cosmos DB and IoT Hub. -* Operating System: Linux as Windows is unavailable for Python. +* Operating System: Select Linux as Windows is unavailable for Python. 5. Click Review + Create, and then Create. ![img26 alt-text#center](Figures/26.png) @@ -87,7 +86,7 @@ Ensure Azure Functions Core Tools are properly installed by running: func --version ``` -The output would be like: +The output should look like: ```output func --version 4.0.6821 @@ -101,29 +100,29 @@ Follow these steps to create an Azure Function locally using Visual Studio Code: 2. Type "Create Function": ![img27 alt-text#center](Figures/27.png) 3. Select Azure Functions: Create Function... -4. Select folder for your new function. For example create a new folder Arm.AzureIoT.AzureFunctions +4. Select folder for your new function. For example create a new folder `Arm.AzureIoT.AzureFunctions` 5. Visual Studio Code will display the wizard, which enables you to configure your function: ![img28 alt-text#center](Figures/28.png) 6. Use this wizard to configure the function: -* Select a language: pick Python +* Select a language: Select Python * Select a Python programming model: Model V2 (Recommended) -* Select a Python interpreter to create a virtual environment: select python3 +* Select a Python interpreter to create a virtual environment: Select python3 * Select a template for your project's first function: CosmosDB trigger -* Name of the function you want to create: keep default (cosmosdb_trigger) -* Name of the container to be monitored: SensorReadings (or the one you created during Azure Cosmos DB provisioning) -* Name of the Cosmos DB database that includes the container to be monitored: IoTDatabaee (or the one you created during Azure Cosmos DB provisioning) -* Select the app setting with your Cosmos DB account connection string from "local.settings.json: Select + Create new local app setting +* Name of the function you want to create: Use the default (cosmosdb_trigger) +* Name of the container to be monitored: `SensorReadings` (or the one you created during Azure Cosmos DB provisioning) +* Name of the Cosmos DB database that includes the container to be monitored: `IoTDatabase` (or the one you created during Azure Cosmos DB provisioning) +* Select the app setting with your Cosmos DB account connection string from "local.settings.json": Select + Create new local app setting * Select your Azure subscription, and then select a database account (armiotcosmosdb or the one you used during Azure Cosmos DB provisioning) * Select how you would like to open your project: Open in current window Visual Studio Code will create the following files: -* function_app.py - this primary function code file. In the Azure Functions Python V2 programming model, bindings and triggers are defined using Python decorators directly within this file -* local.settings.json - this file is specifically used for local development, storing connection strings, app settings, and environment variables. It’s not deployed to Azure, so it’s safe to include sensitive data (like connection strings) locally for testing purpose. -* host.json - defines global configuration settings that affect the behavior of your entire Azure Functions application. Examples include function timeout limits, logging levels, and concurrency settings -* requirements.txt - this file lists all Python packages and dependencies required by your function app. Azure Functions uses this file to automatically install the necessary Python packages when deploying the function to Azure +* `function_app.py` - this primary function code file. In the Azure Functions Python V2 programming model, bindings and triggers are defined using Python decorators directly within this file +* `local.settings.json` - this file is specifically used for local development, storing connection strings, app settings, and environment variables. It’s not deployed to Azure, so it’s safe to include sensitive data (like connection strings) locally for testing purpose. +* `host.json` - defines global configuration settings that affect the behavior of your entire Azure Functions application. Examples include function timeout limits, logging levels, and concurrency settings +* `requirements.txt` - this file lists all Python packages and dependencies required by your function app. Azure Functions uses this file to automatically install the necessary Python packages when deploying the function to Azure ### Modify Function Code -You will now modify the function code to check whether the temperature of the new sensor readings is above a threshold. To do so, open the function_app.py and modify it as follows: +You will now modify the function code to check whether the temperature of the new sensor readings is above a threshold. To do so, open the `function_app.py` and modify it as follows: ```Python import azure.functions as func @@ -172,14 +171,14 @@ To run your function locally proceed as follows: func start ``` -3. Run the iot_simulator.py in a separate terminal window. +3. Run the `iot_simulator.py` in a separate terminal window. You should then see the following logs, depending on the generated temperature values: ![img29 alt-text#center](Figures/29.png) ## Monitoring and Notifications -In this section, you'll extend your existing Azure Function to send email notifications using SendGrid whenever the temperature exceeds the defined threshold +In this section, you will extend your existing Azure Function to send email notifications using `SendGrid` whenever the temperature exceeds the defined threshold ### Create a SendGrid Account in Azure Follow these steps to create a SendGrid account: @@ -205,7 +204,8 @@ Follow these steps to create a SendGrid account: 11. Copy the generated API key securely. You will not be able to retrieve it later. ### Configure SendGrid API Key in your Azure Function -Update your local.settings.json file to include the SendGrid API key: +Update your `local.settings.json` file to include the SendGrid API key as shown: +```console { "IsEncrypted": false, "Values": { @@ -215,11 +215,11 @@ Update your local.settings.json file to include the SendGrid API key: "SENDGRID_API_KEY": "" } } - -Replace with the actual key obtained earlier +``` +Replace `` with the actual key obtained earlier ### Install SendGrid Python Library -Update your project's requirements.txt to include the SendGrid Python SDK: +Update your project's `requirements.txt` to include the SendGrid Python SDK: ```console azure-functions sendgrid @@ -231,7 +231,7 @@ pip install -r requirements.txt ``` ### Extend Your Azure Function to Send Email Notifications -Modify your existing function_app.py file as follows: +Modify your existing `function_app.py` file as follows: ```python import azure.functions as func import logging @@ -286,14 +286,13 @@ def cosmosdb_trigger(azcosmosdb: func.DocumentList): ) ``` -The send_email_alert function is responsible for sending an email notification through SendGrid whenever a sensor reading exceeds the specified temperature threshold. It constructs an email message using details about the IoT device, including the device_id, current temperature, and the event timestamp. The function utilizes SendGrid’s Python SDK (SendGridAPIClient) to send the email message. If the email is successfully sent, it logs a confirmation with the status code. If the email fails, it captures and logs the error details, ensuring that any issues with email delivery can be easily identified and resolved. This function enables proactive monitoring by immediately alerting the user when potentially critical temperature conditions are detected, significantly enhancing the reliability and responsiveness of the IoT system +The `send_email_alert` function is responsible for sending an email notification through SendGrid whenever a sensor reading exceeds the specified temperature threshold. It constructs an email message using details about the IoT device, including the device_id, current temperature, and the event timestamp. The function utilizes SendGrid’s Python SDK (SendGridAPIClient) to send the email message. If the email is successfully sent, it logs a confirmation with the status code. If the email fails, it captures and logs the error details, ensuring that any issues with email delivery can be easily identified and resolved. This function enables proactive monitoring by immediately alerting the user when potentially critical temperature conditions are detected, significantly enhancing the reliability and responsiveness of the IoT system Now, start your function: ```console func start ``` -Then run the iot_simulator.py to send telemetry data, and wait for alert notifications. +Then run the `iot_simulator.py` to send telemetry data, and wait for alert notifications. -## Summary and Next Steps -You have now successfully configured real-time monitoring with email notifications. You can proceed to enhance your IoT solution by aggregating data and creating dashboards or visualizations. \ No newline at end of file +You have now successfully configured real-time monitoring with email notifications. You can proceed to enhance your IoT solution by aggregating data and creating dashboards or visualizations. diff --git a/content/learning-paths/iot/azure-iot/portal.md b/content/learning-paths/iot/azure-iot/portal.md index 9db8dbcc38..ca514e0fcd 100644 --- a/content/learning-paths/iot/azure-iot/portal.md +++ b/content/learning-paths/iot/azure-iot/portal.md @@ -8,13 +8,19 @@ layout: "learningpathall" --- ## Objective -We have successfully established the core backend components for our IoT solution. An IoT simulator continuously generates sensor data, streaming it securely to the cloud via Azure IoT Hub. These sensor readings are stored in Cosmos DB, ensuring data persistence and scalability. Additionally, we’ve implemented an Azure Function that can be triggered through HTTP requests to query Cosmos DB and calculate the average temperature from recent sensor data. With these underlying services fully operational, we’re now prepared to build a WWW portal that will visually present real-time temperature information to our end-users. +You have successfully established the core backend components for our IoT solution. An IoT simulator continuously generates sensor data, streaming it securely to the cloud via Azure IoT Hub. These sensor readings are stored in Cosmos DB, ensuring data persistence and scalability. Additionally, you have implemented an Azure Function that can be triggered through HTTP requests to query Cosmos DB and calculate the average temperature from recent sensor data. With these underlying services fully operational, you can now build a web portal that will visually present real-time temperature information to end users. ## Website -Start by creating a new folder named Arm.AzureIoT.Portal, inside which you’ll create three files: index.html, main.js, and styles.css. The first file, index.html will define the structure of the webpage, and contain the HTML markup and links to the JavaScript and CSS files. The second file, main.js will include the logic and interactivity of the webpage. In this project, it will handle fetching temperature data from your Azure Function and updating the displayed content dynamically. The last file, styles.css will contain all the styling information, controlling the visual appearance of your webpage. +Start by creating a new folder named `Arm.AzureIoT.Portal`, inside which you will create three files: `index.html`, `main.js`, and `styles.css`. + + *`index.html` will define the structure of the webpage, and contain the HTML markup and links to the JavaScript and CSS files. + + *`main.js` will include the logic and interactivity of the webpage. In this project, it will handle fetching temperature data from your Azure Function and updating the displayed content dynamically. + + *`styles.css` will contain all the styling information, controlling the visual appearance of your webpage. ### styles.css -Modify your styles.css file by adding the following CSS: +Modify your `styles.css` file by adding the following CSS: ```css body, html { margin: 0; @@ -57,15 +63,16 @@ body, html { } ``` -The provided CSS sets a modern, dark-themed appearance for your IoT portal webpage. Here’s a breakdown of its styling: -* body and html. The styles remove default margins and paddings, define a dark background color (#121212), set the text color to white for high contrast, and center content both horizontally and vertically using Flexbox. -* .container - this creates a central container element with padding for spacing, rounded corners (border-radius: 8px) for a softer look, a subtle shadow effect for depth, and a slightly lighter dark background (#1e1e1e) to distinguish the content area from the main page background. -* h1 - this defines the main title style with increased font size (2.5rem) and additional spacing below to clearly separate the title from other content. -* button - styles the interactive “Get Temperature” button, giving it a blue color (#1e88e5), white text for readability, rounded corners for a friendly appearance, and smooth color-transition effects when hovered to improve user experience. -* .result - formats the text area where the temperature reading will appear, adding sufficient margin for clear spacing and slightly larger text size to make the results easily readable. +The provided CSS sets a modern, dark-themed appearance for your IoT portal webpage. Here is a breakdown of its styling: + + * body and html. The styles remove default margins and paddings, define a dark background color (#121212), set the text color to white for high contrast, and center content both horizontally and vertically using Flexbox. + * `.container` - this creates a central container element with padding for spacing, rounded corners (border-radius: 8px) for a softer look, a subtle shadow effect for depth, and a slightly lighter dark background (#1e1e1e) to distinguish the content area from the main page background. + * `h1` - this defines the main title style with increased font size (2.5rem) and additional spacing below to clearly separate the title from other content. + * `button` - styles the interactive “Get Temperature” button, giving it a blue color (#1e88e5), white text for readability, rounded corners for a friendly appearance, and smooth color-transition effects when hovered to improve user experience. + * `.result` - formats the text area where the temperature reading will appear, adding sufficient margin for clear spacing and slightly larger text size to make the results easily readable. ### main.js -Now, open your main.js file and update it with the following JavaScript code: +Now, open your `main.js` file and update it with the following JavaScript code: ```JavaScript const functionUrl = ""; @@ -125,10 +132,10 @@ In the body section we have: * a button (id="getTempBtn") that users click to trigger the JavaScript logic retrieving temperature data from your Azure Function. * a placeholder label (div) with the id "result" initially showing "Temperature: -- °C". The JavaScript updates this label dynamically with the actual temperature retrieved from your backend. -Finally, the index.html includes the JavaScript file (main.js) placed at the end of the body to ensure the HTML elements are fully loaded before executing scripts. +Finally, the `index.html` includes the JavaScript file (main.js) placed at the end of the body to ensure the HTML elements are fully loaded before executing scripts. ## Testing the Implementation: -Make sure you’ve saved all three files (index.html, main.js, and styles.css). Next: +Make sure you have saved all three files (index.html, main.js, and styles.css). Next: 1. Start the IoT Simulator to begin streaming data to the Azure IoT Hub. 2. Open the index.html file locally in your web browser. 3. Click the "Get temperature" button. @@ -138,7 +145,7 @@ You should now see real-time temperature readings displayed: ![img44 alt-text#center](Figures/44.png) ## Deployment to Azure Blob Storage -You will now deploy the web portal you’ve created to Azure Blob Storage, making it accessible online. +You will now deploy the web portal you have created to Azure Blob Storage, making it accessible online. ### Create and Configure Azure Blob Storage 1. Sign in to the Azure Portal. @@ -158,7 +165,7 @@ You will now deploy the web portal you’ve created to Azure Blob Storage, makin * Navigate to your newly created storage account. * Under Data management, click “Static website”. * Select “Enabled”. -* Set index.html as the index document name. +* Set `index.html` as the index document name. ![img48 alt-text#center](Figures/48.png) * Click Save. @@ -166,7 +173,7 @@ After saving, Azure provides you with a URL like: https:// Make sure to save this URL, as it will serve as the public endpoint for your website. ### Upload Files to Azure Blob Storage -You can upload your website files directly using the Azure Portal or via Azure Storage Explorer. Here, we’ll use the Azure Portal: +You can upload your website files directly using the Azure Portal or via Azure Storage Explorer. Here, you will use the Azure Portal: 1. Navigate to your storage account. 2. Under Data storage, select “Containers”. 3. Open the container named ”$web” (created automatically when enabling static websites). @@ -180,4 +187,4 @@ After uploading your files, open a browser and navigate to https:// Date: Wed, 23 Apr 2025 16:48:12 +0000 Subject: [PATCH 081/122] Starting editorial --- .../laptops-and-desktops/gh-arm-runners-win/_index.md | 10 +++++----- .../gh-arm-runners-win/introduction.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md index 7cc1f85e89..ee39512734 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md @@ -7,16 +7,16 @@ cascade: minutes_to_complete: 20 -who_is_this_for: This is an introductory tutorial for software developers interested in automating Windows application builds on Arm architecture using GitHub Actions. +who_is_this_for: This introductory tutorial is for software developers who want to automate Windows application builds on Arm architecture using GitHub Actions. learning_objectives: - - Learn about GitHub Arm-hosted Windows runners. - - Understand how to configure workflows for Arm-hosted runners. - - Automate the build process of Windows applications using GitHub Actions. + - Describe GitHub Arm-hosted Windows runners. + - Configure workflows to run on Arm-hosted runners. + - Automate Windows application builds with GitHub Actions. prerequisites: - A GitHub account. - - Basic knowledge of GitHub Actions. + - Familiarity with GitHub Actions. author: - Pareena Verma diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md index 61a22c08f3..4ec0c5bbc6 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md @@ -14,7 +14,7 @@ Runners are the machines that execute jobs in a GitHub Actions workflow. An Arm- Arm-hosted runners are available for public repositories at no cost, subject to [standard usage limits](https://docs.github.com/en/actions/administering-github-actions/usage-limits-billing-and-administration#usage-limits). -### How to use GitHub Arm-hosted Windows Runners? +### How do I use GitHub Arm-hosted Windows Runners? To leverage the GitHub Windows Arm64 hosted runners, you need to use the following label in your public repository workflow runs: @@ -23,15 +23,15 @@ runs-on: windows-11-arm ``` {{% notice Note %}} -This label will not work in private repositories, the workflow will fail if you use it. +This label does not work in private repositories - the workflow will fail if you use it. {{% /notice %}} ### What types of GitHub-hosted runners are available? -Two types of GitHub-hosted runners are available; standard runners and larger runners. Larger runners are differentiated from standard runners because you can control the amount of RAM, the number of CPUs, and configure the allocated disk space. You can also use the Windows 11 Desktop Arm Image with larger runners. To use this image on larger runners, you will need to create a new runner. +GitHub offers two types of hosted runners: standard and larger runners. Larger runners give you more control—you can configure the amount of RAM, number of CPUs, and disk space. You can also use the Windows 11 Desktop Arm Image with larger runners. To use this image on larger runners, you will need to create a new runner. -### How can I create an GitHub Arm-hosted Windows larger runner? +### How can I create a GitHub Arm-hosted Windows larger runner? Arm-hosted runners are created at the organization level. @@ -41,7 +41,7 @@ On the `Runners` page, select the `New runner` drop-down on the top right, and t ![new-runner #center](_images/new-runner.png) -Specify a name for the runner, this is the `runs-on` field in your workflows so make the name clear for others who use it. +Specify a name for the runner. This name is used in the `runs-on` field in your workflows, so make it clear for others who use it. Choose Windows ARM64 for the platform and click `Save`. From 087b84f7d3a2fc3e3db59c28924081654bcfe89b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 23 Apr 2025 18:56:35 +0000 Subject: [PATCH 082/122] fixed all 'Figures' and 'Images' diretories to lowercase, and where they are referenced in .md files --- .../app.md | 4 +-- .../datasets-and-training.md | 4 +-- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.png | Bin .../{Figures => figures}/07.jpg | Bin .../{Figures => figures}/08.jpg | Bin .../{Figures => figures}/1.png | Bin .../{Figures => figures}/2.png | Bin .../{Figures => figures}/3.png | Bin .../{Figures => figures}/4.png | Bin .../inference.md | 2 +- .../intro.md | 4 +-- .../mobile-app.md | 4 +-- .../model.md | 4 +-- .../cross-platform/woa_azure/azure_windows.md | 24 ++++++------- .../woa_azure/{Images => images}/azure2.png | Bin .../{Images => images}/click_create.png | Bin .../{Images => images}/click_create_vm.png | Bin .../{Images => images}/config-vm-1.png | Bin .../woa_azure/{Images => images}/create.png | Bin .../{Images => images}/deploy_complete.png | Bin .../{Images => images}/deploying.png | Bin .../{Images => images}/image_type.png | Bin .../{Images => images}/public_ip.png | Bin .../woa_azure/{Images => images}/rdp.png | Bin .../{Images => images}/review_create.png | Bin .../search_and_click_vm.png | Bin .../{Images => images}/select_24h2.png | Bin .../{Images => images}/select_win_arm_64.png | Bin .../woa_azure/{Images => images}/system.png | Bin .../{Images => images}/vm_search.png | Bin .../advanced_soc/connecting_peripheral.md | 16 ++++----- .../advanced_soc/creating_peripheral.md | 16 ++++----- .../advanced_soc/generating_bitstream.md | 20 +++++------ .../{Images => images}/Picture10.png | Bin .../{Images => images}/Picture11.png | Bin .../{Images => images}/Picture12.png | Bin .../{Images => images}/Picture13.png | Bin .../{Images => images}/Picture14.png | Bin .../{Images => images}/Picture15.png | Bin .../{Images => images}/Picture16.png | Bin .../{Images => images}/Picture17.png | Bin .../{Images => images}/Picture18.png | Bin .../{Images => images}/Picture19.png | Bin .../{Images => images}/Picture2.png | Bin .../{Images => images}/Picture20.png | Bin .../{Images => images}/Picture21.png | Bin .../{Images => images}/Picture22.png | Bin .../{Images => images}/Picture23.png | Bin .../{Images => images}/Picture24.png | Bin .../{Images => images}/Picture25.png | Bin .../{Images => images}/Picture26.png | Bin .../{Images => images}/Picture27.png | Bin .../{Images => images}/Picture28.png | Bin .../{Images => images}/Picture29.png | Bin .../{Images => images}/Picture3.png | Bin .../{Images => images}/Picture30.png | Bin .../{Images => images}/Picture31.png | Bin .../{Images => images}/Picture32.png | Bin .../{Images => images}/Picture4.png | Bin .../{Images => images}/Picture5.png | Bin .../{Images => images}/Picture6.png | Bin .../{Images => images}/Picture7.png | Bin .../{Images => images}/Picture8.jpg | Bin .../{Images => images}/Picture9.png | Bin .../advanced_soc/setup.md | 10 +++--- .../asm/{Images => images}/AddSource.png | Bin .../asm/{Images => images}/AddSource2.png | Bin .../asm/{Images => images}/Build.png | Bin .../asm/{Images => images}/CallStack.png | Bin .../asm/{Images => images}/CallStack2.png | Bin .../asm/{Images => images}/CallStack3.png | Bin .../asm/{Images => images}/Debug.png | Bin .../asm/{Images => images}/Flash.png | Bin .../asm/{Images => images}/NewKeilProject.png | Bin .../{Images => images}/ProjectExplorer.png | Bin .../asm/{Images => images}/SelectDevice1.png | Bin .../asm/{Images => images}/SelectDevice2.png | Bin .../{Images => images}/SoftwareComponents.png | Bin .../asm/{Images => images}/TargetOptions.png | Bin .../asm/{Images => images}/TargetOptions2.png | Bin .../asm/{Images => images}/TargetOptions3.png | Bin .../img_nn_stcube/build_nn.md | 4 +-- .../img_nn_stcube/deploy_nn.md | 12 +++---- .../{Images => images}/lab4_1.PNG | Bin .../{Images => images}/lab4_10.PNG | Bin .../{Images => images}/lab4_11.PNG | Bin .../{Images => images}/lab4_12.PNG | Bin .../{Images => images}/lab4_2.PNG | Bin .../{Images => images}/lab4_3.PNG | Bin .../{Images => images}/lab4_4.PNG | Bin .../{Images => images}/lab4_5.PNG | Bin .../{Images => images}/lab4_6.PNG | Bin .../{Images => images}/lab4_7.PNG | Bin .../{Images => images}/lab4_8.PNG | Bin .../{Images => images}/lab4_9.PNG | Bin .../img_class_stcube.zip | Bin .../img_nn_stcube/run_nn.md | 8 ++--- .../tflow_nn_stcube/features.md | 2 +- .../{Images => images}/output1.PNG | Bin .../{Images => images}/output2.PNG | Bin .../{Images => images}/output3.PNG | Bin .../{Images => images}/output4.PNG | Bin .../{Images => images}/output5.PNG | Bin .../tf_stm32.zip | Bin .../tflow_nn_stcube/test.md | 6 ++-- .../tflow_nn_stcube/train_nn.md | 2 +- .../iot/azure-iot/aggregation.md | 16 ++++----- .../iot/azure-iot/device_registration.md | 6 ++-- .../iot/azure-iot/{Figures => figures}/01.png | Bin .../iot/azure-iot/{Figures => figures}/02.png | Bin .../iot/azure-iot/{Figures => figures}/03.png | Bin .../iot/azure-iot/{Figures => figures}/04.png | Bin .../iot/azure-iot/{Figures => figures}/05.png | Bin .../iot/azure-iot/{Figures => figures}/06.png | Bin .../iot/azure-iot/{Figures => figures}/07.png | Bin .../iot/azure-iot/{Figures => figures}/08.png | Bin .../iot/azure-iot/{Figures => figures}/09.png | Bin .../iot/azure-iot/{Figures => figures}/10.png | Bin .../iot/azure-iot/{Figures => figures}/11.png | Bin .../iot/azure-iot/{Figures => figures}/12.png | Bin .../iot/azure-iot/{Figures => figures}/13.png | Bin .../iot/azure-iot/{Figures => figures}/14.png | Bin .../iot/azure-iot/{Figures => figures}/15.png | Bin .../iot/azure-iot/{Figures => figures}/16.png | Bin .../iot/azure-iot/{Figures => figures}/17.png | Bin .../iot/azure-iot/{Figures => figures}/18.png | Bin .../iot/azure-iot/{Figures => figures}/19.png | Bin .../iot/azure-iot/{Figures => figures}/20.png | Bin .../iot/azure-iot/{Figures => figures}/21.png | Bin .../iot/azure-iot/{Figures => figures}/22.png | Bin .../iot/azure-iot/{Figures => figures}/23.png | Bin .../iot/azure-iot/{Figures => figures}/24.png | Bin .../iot/azure-iot/{Figures => figures}/25.png | Bin .../iot/azure-iot/{Figures => figures}/26.png | Bin .../iot/azure-iot/{Figures => figures}/27.png | Bin .../iot/azure-iot/{Figures => figures}/28.png | Bin .../iot/azure-iot/{Figures => figures}/29.png | Bin .../iot/azure-iot/{Figures => figures}/30.png | Bin .../iot/azure-iot/{Figures => figures}/31.png | Bin .../iot/azure-iot/{Figures => figures}/32.png | Bin .../iot/azure-iot/{Figures => figures}/33.png | Bin .../iot/azure-iot/{Figures => figures}/34.png | Bin .../iot/azure-iot/{Figures => figures}/35.png | Bin .../iot/azure-iot/{Figures => figures}/36.png | Bin .../iot/azure-iot/{Figures => figures}/37.png | Bin .../iot/azure-iot/{Figures => figures}/38.png | Bin .../iot/azure-iot/{Figures => figures}/39.png | Bin .../iot/azure-iot/{Figures => figures}/40.png | Bin .../iot/azure-iot/{Figures => figures}/41.png | Bin .../iot/azure-iot/{Figures => figures}/42.png | Bin .../iot/azure-iot/{Figures => figures}/43.png | Bin .../iot/azure-iot/{Figures => figures}/44.png | Bin .../iot/azure-iot/{Figures => figures}/45.png | Bin .../iot/azure-iot/{Figures => figures}/46.png | Bin .../iot/azure-iot/{Figures => figures}/47.png | Bin .../iot/azure-iot/{Figures => figures}/48.png | Bin .../iot/azure-iot/{Figures => figures}/49.png | Bin .../iot/azure-iot/{Figures => figures}/50.png | Bin .../learning-paths/iot/azure-iot/iot-hub.md | 10 +++--- .../iot/azure-iot/monitoring.md | 24 ++++++------- .../learning-paths/iot/azure-iot/portal.md | 14 ++++---- .../azure-iot/stream-analytics-dynamo-db.md | 20 +++++------ .../iot/azure-iot/stream-analytics.md | 10 +++--- content/learning-paths/iot/iot-sdk/aws.md | 4 +-- .../iot-sdk/{Images => images}/keyword.png | Bin .../iot/iot-sdk/{Images => images}/speech.png | Bin .../electron/{Figures => figures}/01.png | Bin .../electron/{Figures => figures}/02.png | Bin .../electron/{Figures => figures}/03.png | Bin .../electron/{Figures => figures}/04.png | Bin .../laptops-and-desktops/electron/how-to-1.md | 8 ++--- .../create-self-hosted-runner-github.md | 6 ++-- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.png | Bin .../{Figures => figures}/07.png | Bin .../{Figures => figures}/08.png | Bin .../{Figures => figures}/09.png | Bin .../{Figures => figures}/10.png | Bin .../{Figures => figures}/11.png | Bin .../{Figures => figures}/12.png | Bin .../{Figures => figures}/13.png | Bin .../{Figures => figures}/14.png | Bin .../self_hosted_cicd_github/how-to-2.md | 8 ++--- .../prepare-github-repo.md | 6 ++-- .../set-up-docker-repo.md | 8 ++--- .../win_aws_iot/{Figures => figures}/01.png | Bin .../win_aws_iot/{Figures => figures}/02.png | Bin .../win_aws_iot/{Figures => figures}/03.png | Bin .../win_aws_iot/{Figures => figures}/04.png | Bin .../win_aws_iot/{Figures => figures}/05.png | Bin .../win_aws_iot/{Figures => figures}/06.png | Bin .../win_aws_iot/{Figures => figures}/07.png | Bin .../win_aws_iot/{Figures => figures}/08.png | Bin .../win_aws_iot/{Figures => figures}/09.png | Bin .../win_aws_iot/how-to-2.md | 16 ++++----- .../win_aws_iot/how-to-3.md | 2 +- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.png | Bin .../{Figures => figures}/07.png | Bin .../{Figures => figures}/08.png | Bin .../win_aws_iot_dynamodb/how-to-1.md | 16 ++++----- .../win_aws_iot_lambda/create-rule.md | 16 ++++----- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.png | Bin .../{Figures => figures}/07.png | Bin .../{Figures => figures}/08.png | Bin .../{Figures => figures}/09.png | Bin .../{Figures => figures}/10.png | Bin .../{Figures => figures}/11.png | Bin .../{Figures => figures}/12.png | Bin .../{Figures => figures}/13.png | Bin .../{Figures => figures}/14.png | Bin .../{Figures => figures}/15.png | Bin .../{Figures => figures}/16.png | Bin .../{Figures => figures}/17.png | Bin .../{Figures => figures}/18.png | Bin .../{Figures => figures}/19.png | Bin .../{Figures => figures}/20.png | Bin .../{Figures => figures}/21.png | Bin .../{Figures => figures}/22.png | Bin .../{Figures => figures}/23.png | Bin .../{Figures => figures}/24.png | Bin .../win_aws_iot_lambda/implement-lambda.md | 32 +++++++++--------- .../create-lambda.md | 8 ++--- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.png | Bin .../win_aws_iot_lambda_dynamodb/testing.md | 4 +-- .../win_aws_iot_s3/add-lambda.md | 6 ++-- .../win_aws_iot_s3/deploy.md | 2 +- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../win_aws_iot_s3/static-website.md | 2 +- .../win_cef/{Figures => figures}/01.png | Bin .../win_cef/{Figures => figures}/02.png | Bin .../win_cef/{Figures => figures}/03.png | Bin .../win_cef/{Figures => figures}/04.png | Bin .../laptops-and-desktops/win_cef/how-to-1.md | 8 ++--- .../win_forms/{Figures => figures}/01.png | Bin .../win_forms/{Figures => figures}/02.png | Bin .../win_forms/{Figures => figures}/03.png | Bin .../win_forms/{Figures => figures}/04.png | Bin .../win_forms/{Figures => figures}/05.png | Bin .../win_forms/{Figures => figures}/06.png | Bin .../win_forms/{Figures => figures}/07.png | Bin .../win_forms/{Figures => figures}/08.png | Bin .../win_forms/{Figures => figures}/09.png | Bin .../win_forms/{Figures => figures}/10.png | Bin .../win_forms/{Figures => figures}/11.png | Bin .../win_forms/{Figures => figures}/12.png | Bin .../win_forms/{Figures => figures}/13.png | Bin .../win_forms/how-to-1.md | 14 ++++---- .../win_forms/how-to-2.md | 12 +++---- .../win_net8/{Figures => figures}/01.png | Bin .../win_net8/{Figures => figures}/02.png | Bin .../win_net8/{Figures => figures}/03.png | Bin .../win_net8/{Figures => figures}/04.png | Bin .../win_net8/{Figures => figures}/05.png | Bin .../laptops-and-desktops/win_net8/how-to-1.md | 4 +-- .../laptops-and-desktops/win_net8/how-to-2.md | 6 ++-- .../win_winui3/{Figures => figures}/01.png | Bin .../win_winui3/{Figures => figures}/02.png | Bin .../win_winui3/{Figures => figures}/03.png | Bin .../win_winui3/{Figures => figures}/04.png | Bin .../win_winui3/{Figures => figures}/05.png | Bin .../win_winui3/{Figures => figures}/06.png | Bin .../win_winui3/{Figures => figures}/07.png | Bin .../win_winui3/{Figures => figures}/08.png | Bin .../win_winui3/{Figures => figures}/09.png | Bin .../win_winui3/how-to-1.md | 12 +++---- .../win_winui3/how-to-2.md | 6 ++-- .../win_wpf/{Figures => figures}/01.png | Bin .../win_wpf/{Figures => figures}/02.png | Bin .../win_wpf/{Figures => figures}/03.png | Bin .../win_wpf/{Figures => figures}/04.png | Bin .../win_wpf/{Figures => figures}/05.png | Bin .../win_wpf/{Figures => figures}/06.png | Bin .../win_wpf/{Figures => figures}/07.png | Bin .../win_wpf/{Figures => figures}/08.png | Bin .../win_wpf/{Figures => figures}/09.png | Bin .../win_wpf/{Figures => figures}/10.png | Bin .../win_wpf/{Figures => figures}/11.png | Bin .../win_wpf/{Figures => figures}/12.png | Bin .../win_wpf/{Figures => figures}/13.png | Bin .../laptops-and-desktops/win_wpf/how-to-1.md | 14 ++++---- .../laptops-and-desktops/win_wpf/how-to-2.md | 12 +++---- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.png | Bin .../{Figures => figures}/07.png | Bin .../{Figures => figures}/08.png | Bin .../{Figures => figures}/09.png | Bin .../{Figures => figures}/10.png | Bin .../{Figures => figures}/11.png | Bin .../{Figures => figures}/12.png | Bin .../{Figures => figures}/13.png | Bin .../win_xamarin_forms/how-to-1.md | 16 ++++----- .../win_xamarin_forms/how-to-2.md | 10 +++--- .../{Images => images}/actions.png | Bin .../{Images => images}/ghrunner_1.png | Bin .../{Images => images}/ghrunner_2.png | Bin .../{Images => images}/ghrunner_3.png | Bin .../windows_cicd_github/runner-run.md | 4 +-- .../windows_cicd_github/runner-setup.md | 2 +- .../android_opencv_camera/add-camera.md | 8 ++--- .../android_opencv_camera/create-project.md | 14 ++++---- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.png | Bin .../{Figures => figures}/07.png | Bin .../{Figures => figures}/08.png | Bin .../{Figures => figures}/09.png | Bin .../{Figures => figures}/10.png | Bin .../{Figures => figures}/11.jpg | Bin .../{Figures => figures}/12.jpg | Bin .../android_opencv_camera/process-images.md | 2 +- .../create-project.md | 10 +++--- .../face-detection.md | 2 +- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.png | Bin .../{Figures => figures}/04.png | Bin .../{Figures => figures}/05.png | Bin .../{Figures => figures}/06.jpg | Bin .../android_opencv_kleidicv/create-project.md | 4 +-- .../{Figures => figures}/01.png | Bin .../{Figures => figures}/02.png | Bin .../{Figures => figures}/03.jpg | Bin .../{Figures => figures}/04.jpg | Bin .../{Figures => figures}/05.jpg | Bin .../{Figures => figures}/06.jpg | Bin .../{Figures => figures}/07.jpg | Bin .../{Figures => figures}/08.jpg | Bin .../{Figures => figures}/09.jpg | Bin .../{Figures => figures}/10.jpg | Bin .../android_opencv_kleidicv/process-images.md | 16 ++++----- .../android_sve2/{Figures => figures}/01.png | Bin .../android_sve2/{Figures => figures}/02.png | Bin .../android_sve2/{Figures => figures}/03.png | Bin .../android_sve2/{Figures => figures}/04.png | Bin .../android_sve2/{Figures => figures}/05.png | Bin .../android_sve2/{Figures => figures}/06.png | Bin .../android_sve2/{Figures => figures}/07.png | Bin .../android_sve2/{Figures => figures}/08.png | Bin .../android_sve2/{Figures => figures}/09.png | Bin .../android_sve2/part1.md | 12 +++---- .../android_sve2/part2.md | 6 ++-- 375 files changed, 290 insertions(+), 290 deletions(-) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/01.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/02.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/03.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/05.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/06.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/07.jpg (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/08.jpg (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/1.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/2.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/3.png (100%) rename content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/{Figures => figures}/4.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/azure2.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/click_create.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/click_create_vm.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/config-vm-1.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/create.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/deploy_complete.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/deploying.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/image_type.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/public_ip.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/rdp.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/review_create.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/search_and_click_vm.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/select_24h2.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/select_win_arm_64.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/system.png (100%) rename content/learning-paths/cross-platform/woa_azure/{Images => images}/vm_search.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture10.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture11.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture12.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture13.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture14.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture15.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture16.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture17.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture18.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture19.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture2.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture20.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture21.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture22.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture23.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture24.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture25.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture26.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture27.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture28.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture29.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture3.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture30.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture31.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture32.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture4.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture5.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture6.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture7.png (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture8.jpg (100%) rename content/learning-paths/embedded-and-microcontrollers/advanced_soc/{Images => images}/Picture9.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/AddSource.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/AddSource2.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/Build.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/CallStack.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/CallStack2.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/CallStack3.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/Debug.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/Flash.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/NewKeilProject.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/ProjectExplorer.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/SelectDevice1.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/SelectDevice2.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/SoftwareComponents.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/TargetOptions.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/TargetOptions2.png (100%) rename content/learning-paths/embedded-and-microcontrollers/asm/{Images => images}/TargetOptions3.png (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_1.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_10.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_11.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_12.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_2.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_3.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_4.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_5.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_6.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_7.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_8.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Images => images}/lab4_9.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/{Project_Files => project_files}/img_class_stcube.zip (100%) rename content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/{Images => images}/output1.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/{Images => images}/output2.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/{Images => images}/output3.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/{Images => images}/output4.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/{Images => images}/output5.PNG (100%) rename content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/{Project_Files => project_files}/tf_stm32.zip (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/01.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/02.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/03.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/04.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/05.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/06.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/07.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/08.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/09.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/10.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/11.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/12.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/13.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/14.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/15.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/16.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/17.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/18.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/19.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/20.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/21.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/22.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/23.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/24.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/25.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/26.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/27.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/28.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/29.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/30.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/31.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/32.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/33.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/34.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/35.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/36.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/37.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/38.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/39.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/40.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/41.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/42.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/43.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/44.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/45.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/46.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/47.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/48.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/49.png (100%) rename content/learning-paths/iot/azure-iot/{Figures => figures}/50.png (100%) rename content/learning-paths/iot/iot-sdk/{Images => images}/keyword.png (100%) rename content/learning-paths/iot/iot-sdk/{Images => images}/speech.png (100%) rename content/learning-paths/laptops-and-desktops/electron/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/electron/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/electron/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/electron/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/09.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/10.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/11.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/12.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/13.png (100%) rename content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/{Figures => figures}/14.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot/{Figures => figures}/09.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/09.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/10.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/11.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/12.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/13.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/14.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/15.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/16.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/17.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/18.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/19.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/20.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/21.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/22.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/23.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/{Figures => figures}/24.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_s3/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_s3/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_s3/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_s3/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_aws_iot_s3/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_cef/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_cef/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_cef/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_cef/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/09.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/10.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/11.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/12.png (100%) rename content/learning-paths/laptops-and-desktops/win_forms/{Figures => figures}/13.png (100%) rename content/learning-paths/laptops-and-desktops/win_net8/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_net8/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_net8/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_net8/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_net8/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/win_winui3/{Figures => figures}/09.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/09.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/10.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/11.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/12.png (100%) rename content/learning-paths/laptops-and-desktops/win_wpf/{Figures => figures}/13.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/01.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/02.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/03.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/04.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/05.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/06.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/07.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/08.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/09.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/10.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/11.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/12.png (100%) rename content/learning-paths/laptops-and-desktops/win_xamarin_forms/{Figures => figures}/13.png (100%) rename content/learning-paths/laptops-and-desktops/windows_cicd_github/{Images => images}/actions.png (100%) rename content/learning-paths/laptops-and-desktops/windows_cicd_github/{Images => images}/ghrunner_1.png (100%) rename content/learning-paths/laptops-and-desktops/windows_cicd_github/{Images => images}/ghrunner_2.png (100%) rename content/learning-paths/laptops-and-desktops/windows_cicd_github/{Images => images}/ghrunner_3.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/01.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/02.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/03.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/04.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/05.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/06.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/07.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/08.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/09.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/10.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/11.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/{Figures => figures}/12.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/{Figures => figures}/01.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/{Figures => figures}/02.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/{Figures => figures}/03.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/{Figures => figures}/04.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/{Figures => figures}/05.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/{Figures => figures}/06.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/01.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/02.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/03.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/04.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/05.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/06.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/07.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/08.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/09.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/{Figures => figures}/10.jpg (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/01.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/02.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/03.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/04.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/05.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/06.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/07.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/08.png (100%) rename content/learning-paths/mobile-graphics-and-gaming/android_sve2/{Figures => figures}/09.png (100%) diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/app.md b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/app.md index 5848fe0386..275d199bee 100644 --- a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/app.md +++ b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/app.md @@ -30,8 +30,8 @@ To run the app in Android Studio using an emulator, follow these steps: Once the application starts, click the **Load Image** button. It loads a randomly-selected image. Then, click **Run Inference** to recognize the digit. The application displays the predicted label and the inference time as shown below: -![img alt-text#center](Figures/05.png "Figure 7. Digit Recognition 1") +![img alt-text#center](figures/05.png "Figure 7. Digit Recognition 1") -![img alt-text#center](Figures/06.png "Figure 8. Digit Recognition 2") +![img alt-text#center](figures/06.png "Figure 8. Digit Recognition 2") In the next step of this Learning Path, you will learn how to further optimize the model. diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/datasets-and-training.md b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/datasets-and-training.md index 1c66e8fedf..5f052a280a 100644 --- a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/datasets-and-training.md +++ b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/datasets-and-training.md @@ -72,7 +72,7 @@ Make sure to replace 'x' with the version number of Python that you have install After running the code, you will see output similar to Figure 5: -![image alt-text#center](Figures/01.png "Figure 5. Output") +![image alt-text#center](figures/01.png "Figure 5. Output") ## Train the Model @@ -134,7 +134,7 @@ for t in range(epochs): After running the code, you see the following output showing the training progress, as displayed in Figure 2. -![image alt-text#center](Figures/02.png "Figure 2. Output 2") +![image alt-text#center](figures/02.png "Figure 2. Output 2") Once the training is complete, you see output similar to: diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/01.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/01.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/01.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/01.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/02.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/02.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/02.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/02.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/03.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/03.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/03.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/03.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/05.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/05.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/05.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/05.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/06.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/06.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/06.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/06.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/07.jpg b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/07.jpg similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/07.jpg rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/07.jpg diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/08.jpg b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/08.jpg similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/08.jpg rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/08.jpg diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/1.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/1.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/1.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/1.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/2.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/2.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/2.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/2.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/3.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/3.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/3.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/3.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/4.png b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/4.png similarity index 100% rename from content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/Figures/4.png rename to content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/figures/4.png diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/inference.md b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/inference.md index 343f3a822c..e5a0402c0d 100644 --- a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/inference.md +++ b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/inference.md @@ -108,7 +108,7 @@ This code demonstrates how to use a saved PyTorch model for inference and visual After running the code, you should see results similar to the following figure: -![image](Figures/03.png "Figure 6. Results Displayed") +![image](figures/03.png "Figure 6. Results Displayed") ### What have you learned? diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/intro.md b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/intro.md index 683b9eb999..12b2a01994 100644 --- a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/intro.md +++ b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/intro.md @@ -117,7 +117,7 @@ To ensure everything is set up correctly, follow these next steps: 4. Select the Python kernel you created earlier, `pytorch-env`. To do so, click **Kernels** in the top right-hand corner. Then, click **Jupyter Kernel...**, and you will see the Python kernel as shown below: -![img1 alt-text#center](Figures/1.png "Figure 1: Python kernel.") +![img1 alt-text#center](figures/1.png "Figure 1: Python kernel.") 5. In your Jupyter notebook, run the following code to verify PyTorch is working correctly: @@ -127,6 +127,6 @@ print(torch.__version__) ``` It will look as follows: -![img2 alt-text#center](Figures/2.png "Figure 2: Jupyter Notebook.") +![img2 alt-text#center](figures/2.png "Figure 2: Jupyter Notebook.") Now you have set up your development environment, you can move on to creating a PyTorch model. diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/mobile-app.md b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/mobile-app.md index cfbc922d4d..630f04422f 100644 --- a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/mobile-app.md +++ b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/mobile-app.md @@ -228,9 +228,9 @@ This optimization showcases the benefits of quantization and layer fusion for mo This would allow the model to take full advantage of the device's computational capabilities, potentially further reducing the inference time. -![fig alt-text#center](Figures/07.jpg "Figure 9.") +![fig alt-text#center](figures/07.jpg "Figure 9.") -![fig alt-text#center](Figures/08.jpg "Figure 10.") +![fig alt-text#center](figures/08.jpg "Figure 10.") ### What have you learned? diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/model.md b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/model.md index e189a5c708..2e2e1ae80b 100644 --- a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/model.md +++ b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/model.md @@ -9,7 +9,7 @@ layout: "learningpathall" You can create and train a feedforward neural network to classify handwritten digits from the MNIST dataset. This dataset contains 70,000 images, comprising 60,000 training images and 10,000 testing images of handwritten numerals (0-9), each with dimensions of 28x28 pixels. Some representative MNIST digits with their corresponding labels are shown in Figure 3: -![img3 alt-text#center](Figures/3.png "Figure 3: MNIST Digits and Labels.") +![img3 alt-text#center](figures/3.png "Figure 3: MNIST Digits and Labels.") The neural network begins with an input layer containing 28x28 = 784 input nodes, with each node accepting a single pixel from a MNIST image. @@ -94,7 +94,7 @@ summary(model, (1, 28, 28)) After running the notebook, you will see the output as shown in Figure 4: -![img4 alt-text#center](Figures/4.png "Figure 4: Notebook Output.") +![img4 alt-text#center](figures/4.png "Figure 4: Notebook Output.") You will see a detailed summary of the NeuralNetwork model’s architecture, including the following information: diff --git a/content/learning-paths/cross-platform/woa_azure/azure_windows.md b/content/learning-paths/cross-platform/woa_azure/azure_windows.md index 58769e274d..bd016e37e0 100644 --- a/content/learning-paths/cross-platform/woa_azure/azure_windows.md +++ b/content/learning-paths/cross-platform/woa_azure/azure_windows.md @@ -23,7 +23,7 @@ To begin: 1. Login to your Azure account. 2. From the Azure search bar, begin typing the word "virtual machines" until the [Virtual Machines](https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.Compute%2FVirtualMachines) service appears as a selection. -![Search and Select VMs](Images/search_and_click_vm.png) +![Search and Select VMs](images/search_and_click_vm.png) 3. Select `Virtual Machines`. @@ -31,7 +31,7 @@ The Virtual Machines page appears. 4. Click `Create` > `Azure virtual machine`. -![Create an Azure VM](Images/click_create_vm.png) +![Create an Azure VM](images/click_create_vm.png) The `Create a virtual machine` page appears. @@ -42,14 +42,14 @@ The `Create a virtual machine` page appears. 9. Select `Standard` for `Security Type`. 10. Select `See all images` - You can find this under the `Image` dropdown. -![Config the VM Part 1](Images/config-vm-1.png) +![Config the VM Part 1](images/config-vm-1.png) The `Marketplace` page appears. 11. Enter `windows 11` into the search bar and press enter. 12. Click the `Image Type` filter and select `Arm64`. -![Select Arm images](Images/image_type.png) +![Select Arm images](images/image_type.png) You are presented with all available Arm VM images. @@ -59,7 +59,7 @@ Note all the different Arm images (Windows and others) Azure provides; feel free 13. From the drop-down at the bottom of the `Microsoft Windows 11 Preview arm64` tile, select `Windows 11 Professional 24H2 - Arm64`. -![img2](Images/select_24h2.png) +![img2](images/select_24h2.png) Upon selection, you are returned to the `Create a virtual machine` screen with your architecture set to `Arm64` and your image set to `Windows 11 Professional, version 24H2 - ARM64 Gen2`. @@ -79,21 +79,21 @@ The VM size suggested for this learning path is enough to demonstrate Windows on 19. Click `Review + create`. -![Review and Create](Images/review_create.png) +![Review and Create](images/review_create.png) The validation and confirmation page appears. 20. Upon review and validation of your settings, select `Create` to launch your Windows on Arm virtual machine. -![Create](Images/create.png) +![Create](images/create.png) At this point, Azure will deploy your new Windows on Arm instance to the Azure cloud. This may take a few moments to complete. -![Deploy](Images/deploying.png) +![Deploy](images/deploying.png) Upon completion, your screen will look similar to: -![Deploy Complete](Images/deploy_complete.png) +![Deploy Complete](images/deploy_complete.png) 21. Click `Go to Resource`. @@ -101,7 +101,7 @@ Upon completion, your screen will look similar to: From this resource page, note the `Public IP address` of your virtual machine, as you will use in the next step to connect to your VM instance. -![Public IP](Images/public_ip.png) +![Public IP](images/public_ip.png) ## Connect to your Windows on Arm VM @@ -117,7 +117,7 @@ In our example, we use the `Remote Desktop Connection` app as our RDP client, bu 4. Click connect. -![RDP #center](Images/rdp.png) +![RDP #center](images/rdp.png) You can now interact with the VM in the same way as you would a local desktop. @@ -128,4 +128,4 @@ If you have issues connecting to your instance, see this Microsoft article: Open `Control Panel` > `System`, and verify that `Device` > `System Type` identifies as an Arm-based processor. -![System #center](Images/system.png) +![System #center](images/system.png) diff --git a/content/learning-paths/cross-platform/woa_azure/Images/azure2.png b/content/learning-paths/cross-platform/woa_azure/images/azure2.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/azure2.png rename to content/learning-paths/cross-platform/woa_azure/images/azure2.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/click_create.png b/content/learning-paths/cross-platform/woa_azure/images/click_create.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/click_create.png rename to content/learning-paths/cross-platform/woa_azure/images/click_create.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/click_create_vm.png b/content/learning-paths/cross-platform/woa_azure/images/click_create_vm.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/click_create_vm.png rename to content/learning-paths/cross-platform/woa_azure/images/click_create_vm.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/config-vm-1.png b/content/learning-paths/cross-platform/woa_azure/images/config-vm-1.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/config-vm-1.png rename to content/learning-paths/cross-platform/woa_azure/images/config-vm-1.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/create.png b/content/learning-paths/cross-platform/woa_azure/images/create.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/create.png rename to content/learning-paths/cross-platform/woa_azure/images/create.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/deploy_complete.png b/content/learning-paths/cross-platform/woa_azure/images/deploy_complete.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/deploy_complete.png rename to content/learning-paths/cross-platform/woa_azure/images/deploy_complete.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/deploying.png b/content/learning-paths/cross-platform/woa_azure/images/deploying.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/deploying.png rename to content/learning-paths/cross-platform/woa_azure/images/deploying.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/image_type.png b/content/learning-paths/cross-platform/woa_azure/images/image_type.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/image_type.png rename to content/learning-paths/cross-platform/woa_azure/images/image_type.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/public_ip.png b/content/learning-paths/cross-platform/woa_azure/images/public_ip.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/public_ip.png rename to content/learning-paths/cross-platform/woa_azure/images/public_ip.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/rdp.png b/content/learning-paths/cross-platform/woa_azure/images/rdp.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/rdp.png rename to content/learning-paths/cross-platform/woa_azure/images/rdp.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/review_create.png b/content/learning-paths/cross-platform/woa_azure/images/review_create.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/review_create.png rename to content/learning-paths/cross-platform/woa_azure/images/review_create.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/search_and_click_vm.png b/content/learning-paths/cross-platform/woa_azure/images/search_and_click_vm.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/search_and_click_vm.png rename to content/learning-paths/cross-platform/woa_azure/images/search_and_click_vm.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/select_24h2.png b/content/learning-paths/cross-platform/woa_azure/images/select_24h2.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/select_24h2.png rename to content/learning-paths/cross-platform/woa_azure/images/select_24h2.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/select_win_arm_64.png b/content/learning-paths/cross-platform/woa_azure/images/select_win_arm_64.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/select_win_arm_64.png rename to content/learning-paths/cross-platform/woa_azure/images/select_win_arm_64.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/system.png b/content/learning-paths/cross-platform/woa_azure/images/system.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/system.png rename to content/learning-paths/cross-platform/woa_azure/images/system.png diff --git a/content/learning-paths/cross-platform/woa_azure/Images/vm_search.png b/content/learning-paths/cross-platform/woa_azure/images/vm_search.png similarity index 100% rename from content/learning-paths/cross-platform/woa_azure/Images/vm_search.png rename to content/learning-paths/cross-platform/woa_azure/images/vm_search.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/connecting_peripheral.md b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/connecting_peripheral.md index 76bfd0c9a6..86fdf0c0a1 100644 --- a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/connecting_peripheral.md +++ b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/connecting_peripheral.md @@ -12,23 +12,23 @@ Follow the steps outline below to connect the AXI4 peripheral you created in the 1. Right-click on the empty space again in the diagram and choose “Create Port...” (Ctrl+K). Create 2 ports with the following settings and names: - ![Creating output port](Images/Picture15.png) + ![Creating output port](images/Picture15.png) *Figure 3.1. Creating output port* - ![Creating input port](Images/Picture16.png) + ![Creating input port](images/Picture16.png) *Figure 3.2. Creating input port* 2. Wire the “sw[3:0]” input to the “sw[3:0]” port of the “axi_gpio_asoc_0” block and the same for the “led[3:0]” output to the equivalent port of the block as shown in the diagram below. This connects them to external ports of the ZYNQ chip: - ![Vivado block diagram](Images/Picture17.png) + ![Vivado block diagram](images/Picture17.png) *Figure 3.3. Vivado block diagram* 3. Two IP blocks will be generated automatically. The “Processor System Reset” IP is used to generate reset signals for different peripherals. The “AXI Interconnect” IP here is used to interconnect AXI4-Lite Subordinate and AXI Manager. Select the “Address Editor” tab next to “Diagram” and change the “64K” to “4K”. Save all your progress. - ![Changing peripheral address settings](Images/Picture18.png) + ![Changing peripheral address settings](images/Picture18.png) *Figure 3.4. Changing peripheral address settings* @@ -40,26 +40,26 @@ Follow the steps outline below to connect the AXI4 peripheral you created in the 5. Right-click the “Generate Bitstream” option in the “Flow Navigator” on the left and select “Bitstream settings”. Click the three dots next to “tcl.pre”: - ![Changing bitstream settings](Images/Picture19.png) + ![Changing bitstream settings](images/Picture19.png) *Figure 3.5. Changing bitstream settings* 6. Select the “New Script” option, click the three dots next to the empty box, choose the “pins.tcl” file you created earlier and click “Ok” on all windows. Right-click the “Constraints” under the sources tab and select “Add sources”: - ![Adding sources](Images/Picture20.png) + ![Adding sources](images/Picture20.png) *Figure 3.6. Adding sources* 7. Select “Add or create constraints” and click “Next”. Select “Create File”, give any name to the file for example pin_constraints, and click “Finish”: - ![Creating a constraints file](Images/Picture21.png) + ![Creating a constraints file](images/Picture21.png) *Figure 3.7. Creating a constraints file* 8. Expand the “Constraints” folder within the “Sources” tab and double-click the file you just created to open it. Add the following constraints from [Digilent/Zybo-Z7-10-Pmod-VGA/blob/master/src/constraints/Zybo-Z7-Master.xdc](https://github.com/Digilent/Zybo-Z7-10-Pmod-VGA/blob/master/src/constraints/Zybo-Z7-Master.xdc), and save the file: - ![Editing constraints file](Images/Picture22.png) + ![Editing constraints file](images/Picture22.png) *Figure 3.8. Editing constraints file* diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/creating_peripheral.md b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/creating_peripheral.md index e15534e45d..1498a78246 100644 --- a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/creating_peripheral.md +++ b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/creating_peripheral.md @@ -12,19 +12,19 @@ The Xilinx Vivado tools provide a simplified way to create an AXI4 peripheral. Y 1. Start by clicking “Tools” -> “Create and Package New IP”. Click “Next” and choose the following option: - ![Creating AXI4 peripheral](Images/Picture7.png) + ![Creating AXI4 peripheral](images/Picture7.png) *Figure 2.1. Creating AXI4 peripheral* 2. Give the following name to the IP (you may keep the IP location path as provided by default): - ![Adding peripheral details](Images/Picture8.jpg) + ![Adding peripheral details](images/Picture8.jpg) *Figure 2.2. Adding peripheral details* 3. You need a Subordinate interface and four 32-bit registers for the switches and LEDs. - ![Peripheral settings for Subordinate interface](Images/Picture9.png) + ![Peripheral settings for Subordinate interface](images/Picture9.png) *Figure 2.3. Peripheral settings for Subordinate interface* @@ -36,7 +36,7 @@ These are the basic template files generated for an AXI-Lite peripheral. You can 5. Double-click to open the top-level Verilog file called “AUP_advanced_SoC_v1_0.v”: - ![Editing top-level Verilog file](Images/Picture10.png) + ![Editing top-level Verilog file](images/Picture10.png) *Figure 2.4. Editing top-level Verilog file* @@ -64,7 +64,7 @@ These are the basic template files generated for an AXI-Lite peripheral. You can ``` 8. Save the changes in the file (Ctrl+S). Next, expand and open the other Verilog file (AUP_advanced_SoC_v1_0_S00_AXI.v) shown below: - ![Edit the second Verilog file](Images/Picture11.png) + ![Edit the second Verilog file](images/Picture11.png) *Figure 2.5. Edit the second Verilog file* @@ -93,7 +93,7 @@ These are the basic template files generated for an AXI-Lite peripheral. You can 12. Save the changes in the file (Ctrl+S). Next, go to the “Package IP – AUP_advanced_SoC” tab, choose the “Customization Parameters” option on the left and click “Merge Changes from Customization Parameters Wizard” to update the IP package with the changes made in HDL files: - ![. Saving all the changes](Images/Picture12.png) + ![. Saving all the changes](images/Picture12.png) *Figure 2.6. Saving all the changes* @@ -127,7 +127,7 @@ These are the basic template files generated for an AXI-Lite peripheral. You can ``` 15. Then, click on “Settings” (under “Project Manager”) in the “Flow Navigator” menu on the left. Expand the “IP” section in the new window that appears and choose the “Repository” option. - ![Adding IP Repository](Images/Picture13.png) + ![Adding IP Repository](images/Picture13.png) *Figure 2.7. Adding IP Repository* @@ -135,7 +135,7 @@ These are the basic template files generated for an AXI-Lite peripheral. You can 17. Click “Run Connection Automation” and then click “OK” to connect the AXI-Lite Subordinate interface on GPIO peripheral to the AXI Manager interface on Arm processor. - ![Connect AXI-Lite Subordinate interface (Custom IP) to AXI Manager interface](Images/Picture14.png) + ![Connect AXI-Lite Subordinate interface (Custom IP) to AXI Manager interface](images/Picture14.png) *Figure 2.8. Connect AXI-Lite Subordinate interface (Custom IP) to AXI Manager interface* diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/generating_bitstream.md b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/generating_bitstream.md index 8aa3bd0f7c..cef827302b 100644 --- a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/generating_bitstream.md +++ b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/generating_bitstream.md @@ -12,56 +12,56 @@ In this section you will generate the bitstream for this project and write a C p 1. Under the “Sources” tab on the left, expand the “Design Sources” folder, right-click the design1.bd file, choose the “Create HDL Wrapper” and select all default options. - ![Creating HDL Wrapper](Images/Picture23.png) + ![Creating HDL Wrapper](images/Picture23.png) *Figure 3.1. Creating HDL Wrapper* 2. Save any other changes and click “Generate Bitstream” on the left and click “Ok” for all pop-ups. This process takes some time. Once the process is done, select “Open Hardware Manager”: - ![Generating bitstream and uploading to board](Images/Picture24.png) + ![Generating bitstream and uploading to board](images/Picture24.png) *Figure 3.2. Generating bitstream and uploading to board* 3. Connect the board and make sure the jumper (JP3) above the red LED on the Zybo board is in JTAG mode. Then, in Vivado, click “Auto Connect” in the Hardware Manager as shown below: - ![Connecting the board](Images/Picture25.png) + ![Connecting the board](images/Picture25.png) *Figure 3.3. Connecting the board* 4. Right-click on the board, select “Program Device” as shown below and click “Program” on the pop-up window. - ![Programming the board](Images/Picture26.png) + ![Programming the board](images/Picture26.png) *Figure 3.4. Programming the board* 5. Once the board is programmed, the green LED labeled “LD12” should light up on the board. Click “File” on the main menu bar and select “Export” -> “Export Hardware” and click “Next” on the pop-up window. Choose the following option on the next page: - ![Exporting hardware and bitstream file](Images/Picture27.png) + ![Exporting hardware and bitstream file](images/Picture27.png) *Figure 3.5. Exporting hardware and bitstream file* 6. Choose the “export to” location as the project folder and save the file. Then click “Finish”. Next, click “Tools” on the main menu bar and select “Launch Vitis IDE”. Choose the same project folder as your workspace. Click “File” -> “New” -> “Application Project”. - ![Creating a new application project](Images/Picture28.png) + ![Creating a new application project](images/Picture28.png) *Figure 3.6. Creating a new application project* 7. Select the “Create a new platform from hardware (XSA)” tab and click browse to select the XSA file you saved earlier: - ![Adding the XSA file](Images/Picture29.png) + ![Adding the XSA file](images/Picture29.png) *Figure 3.7. Adding the XSA file* 8. Click next and give a name (e.g. led_system) to the application project. Click “Next” until you reach the following page and choose “Empty Application(C)” and click “Finish”: - ![Creating an empty C Application](Images/Picture30.png) + ![Creating an empty C Application](images/Picture30.png) *Figure 3.8. Creating an empty C Application* 9. Then right-click the “src” folder within the application project you created and add a new file called “main.c”. - ![Adding a main.c file](Images/Picture31.png) + ![Adding a main.c file](images/Picture31.png) *Figure 3.9. Adding a main.c file* @@ -81,7 +81,7 @@ In this section you will generate the bitstream for this project and write a C p 12. Right-click the application project in the explorer tab, select “Build Project” and ensure that the build is successful. Then right click again and select “Run As” and then “1 Launch Hardware” to upload everything to the board. - ![Running the program on the board.](Images/Picture32.png) + ![Running the program on the board.](images/Picture32.png) *Figure 3.10. Running the program on the board.* diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture10.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture10.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture10.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture10.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture11.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture11.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture11.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture11.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture12.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture12.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture12.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture12.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture13.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture13.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture13.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture13.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture14.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture14.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture14.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture14.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture15.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture15.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture15.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture15.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture16.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture16.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture16.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture16.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture17.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture17.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture17.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture17.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture18.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture18.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture18.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture18.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture19.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture19.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture19.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture19.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture2.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture2.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture2.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture2.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture20.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture20.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture20.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture20.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture21.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture21.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture21.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture21.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture22.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture22.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture22.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture22.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture23.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture23.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture23.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture23.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture24.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture24.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture24.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture24.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture25.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture25.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture25.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture25.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture26.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture26.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture26.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture26.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture27.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture27.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture27.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture27.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture28.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture28.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture28.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture28.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture29.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture29.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture29.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture29.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture3.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture3.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture3.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture3.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture30.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture30.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture30.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture30.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture31.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture31.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture31.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture31.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture32.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture32.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture32.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture32.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture4.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture4.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture4.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture4.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture5.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture5.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture5.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture5.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture6.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture6.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture6.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture6.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture7.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture7.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture7.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture7.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture8.jpg b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture8.jpg similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture8.jpg rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture8.jpg diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture9.png b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture9.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/advanced_soc/Images/Picture9.png rename to content/learning-paths/embedded-and-microcontrollers/advanced_soc/images/Picture9.png diff --git a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/setup.md b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/setup.md index 84513f6fdb..8ba3dd68ef 100644 --- a/content/learning-paths/embedded-and-microcontrollers/advanced_soc/setup.md +++ b/content/learning-paths/embedded-and-microcontrollers/advanced_soc/setup.md @@ -16,31 +16,31 @@ After installation is complete, follow the steps outlined below to setup a works 2. Open Xilinx Vivado and create a new project in your workspace as shown below: - ![Creating a project](Images/Picture2.png) + ![Creating a project](images/Picture2.png) *Figure 1.1. Creating a project* 3. Give it a meaningful name and click "Next". Select RTL Project and click “Next”. You will not be adding any sources or constraints so click “Next” for the next two pages. In the next page, click the “Boards” tab and search for “Zybo” in the search bar and click the download button next to “Zybo Z7-10” if the option is available. Then select it and click “Next”. Do not click on the hyperlink but click on the empty area next to “Zybo Z7-10”. - ![Board Selection](Images/Picture3.png) + ![Board Selection](images/Picture3.png) *Figure 1.2. Board Selection* 4. Click “Finish”. In the “Flow Navigator” menu on the left, click “Create Block Design” under “IP Integrator”: - ![Creating a new block design](Images/Picture4.png) + ![Creating a new block design](images/Picture4.png) *Figure 1.3. Creating a new block design* 5. Choose a design name or use the default one and click “Ok”. Within the empty “Diagram” box on the right-hand side, right-click and select “Add IP”. Enter “Zynq” in the search box and choose “ZYNQ7 Processing System”. Click the “Run Block Automation” option that has now appeared. - ![Running Block Automation](Images/Picture5.png) + ![Running Block Automation](images/Picture5.png) *Figure 1.4. Running Block Automation* 6. Click “Ok” on the pop-up window. - ![Run Block Automation default settings](Images/Picture6.png) + ![Run Block Automation default settings](images/Picture6.png) Figure 1.5. Run Block Automation default settings* diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/AddSource.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/AddSource.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/AddSource.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/AddSource.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/AddSource2.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/AddSource2.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/AddSource2.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/AddSource2.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/Build.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/Build.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/Build.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/Build.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/CallStack.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/CallStack.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/CallStack.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/CallStack.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/CallStack2.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/CallStack2.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/CallStack2.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/CallStack2.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/CallStack3.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/CallStack3.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/CallStack3.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/CallStack3.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/Debug.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/Debug.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/Debug.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/Debug.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/Flash.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/Flash.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/Flash.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/Flash.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/NewKeilProject.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/NewKeilProject.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/NewKeilProject.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/NewKeilProject.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/ProjectExplorer.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/ProjectExplorer.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/ProjectExplorer.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/ProjectExplorer.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/SelectDevice1.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/SelectDevice1.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/SelectDevice1.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/SelectDevice1.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/SelectDevice2.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/SelectDevice2.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/SelectDevice2.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/SelectDevice2.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/SoftwareComponents.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/SoftwareComponents.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/SoftwareComponents.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/SoftwareComponents.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/TargetOptions.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/TargetOptions.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/TargetOptions.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/TargetOptions.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/TargetOptions2.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/TargetOptions2.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/TargetOptions2.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/TargetOptions2.png diff --git a/content/learning-paths/embedded-and-microcontrollers/asm/Images/TargetOptions3.png b/content/learning-paths/embedded-and-microcontrollers/asm/images/TargetOptions3.png similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/asm/Images/TargetOptions3.png rename to content/learning-paths/embedded-and-microcontrollers/asm/images/TargetOptions3.png diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md index c5d8402043..72390b8268 100644 --- a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md +++ b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md @@ -91,7 +91,7 @@ plt.show() The expected output is shown below -![output1](Images/lab4_1.PNG) +![output1](images/lab4_1.PNG) Next, normalize all the training and testing data to have values between 0 and 1. This normalization facilitates machine learning. Each RGB value ranges from 0 to 255, so divide the training and testing data by 255. @@ -124,7 +124,7 @@ You are going to create a small convolutional neural network for image classific Here is an image illustrating the network architecture. Note that only convolution and dense layers are illustrated in this image. -![output2](Images/lab4_2.PNG) +![output2](images/lab4_2.PNG) Execute the code blocks below to create a sequential model and add the layers diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/deploy_nn.md b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/deploy_nn.md index dc4619ee98..d740e10fa1 100644 --- a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/deploy_nn.md +++ b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/deploy_nn.md @@ -52,15 +52,15 @@ In this section, you will deploy the model directly on the STM32 board. 5. Set `Toolchain/IDE` as `STM32CubeIDE` -![output3](Images/lab4_3.PNG) +![output3](images/lab4_3.PNG) 6. Go to `Pinout & Configuration` and clear pinouts from the `Pinout` menu. -![output4](Images/lab4_4.PNG) +![output4](images/lab4_4.PNG) 7. In `Software Packs` menu, click `Select Components`. Enable `X-CUBE-AI`. For device application, choose `Validation`. Click `OK` to save. -![output5](Images/lab4_5.PNG) +![output5](images/lab4_5.PNG) 8. Navigate to `X-CUBE-AI` configuration. @@ -70,7 +70,7 @@ In this section, you will deploy the model directly on the STM32 board. 11. Generate the validation code for the model by clicking `Generate Code`. -![output6](Images/lab4_6.PNG) +![output6](images/lab4_6.PNG) 12. Open STM32CubeIDE. @@ -80,10 +80,10 @@ In this section, you will deploy the model directly on the STM32 board. 15. Ensure that the board is connected to your computer. If it is correctly connected, build and flash the code by clicking `Run As`. -![output7](Images/lab4_7.PNG) +![output7](images/lab4_7.PNG) 16. If you get an ‘undefined reference’ error, go to `Core/Src/main.c`. Remove `static` from the declaration of the `MX_USART1_UART_Init()` function and also from its definition. Try `Run As` again. -![output8](Images/lab4_8.PNG) +![output8](images/lab4_8.PNG) With the model now deployed on the STM32 board, you are ready to test it. diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_1.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_1.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_1.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_1.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_10.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_10.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_10.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_10.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_11.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_11.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_11.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_11.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_12.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_12.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_12.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_12.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_2.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_2.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_2.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_2.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_3.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_3.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_3.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_3.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_4.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_4.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_4.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_4.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_5.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_5.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_5.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_5.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_6.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_6.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_6.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_6.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_7.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_7.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_7.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_7.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_8.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_8.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_8.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_8.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_9.PNG b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_9.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Images/lab4_9.PNG rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/images/lab4_9.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Project_Files/img_class_stcube.zip b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/project_files/img_class_stcube.zip similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/Project_Files/img_class_stcube.zip rename to content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/project_files/img_class_stcube.zip diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/run_nn.md b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/run_nn.md index a367b8cefd..75d94ac8c8 100644 --- a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/run_nn.md +++ b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/run_nn.md @@ -37,20 +37,20 @@ If the board is not detected, click the black button on the board to reset, then Select the model network from the list of models deployed on the board. -![output9](Images/lab4_9.PNG) +![output9](images/lab4_9.PNG) Select the network and the label file (`Data/labels/cifar10_labels.txt`) -![output10](Images/lab4_10.PNG) +![output10](images/lab4_10.PNG) Open an image to test. The tool will automatically launch a new pane, and show the inference result. Observe that the model correctly predicted the label. In addition, note the time taken to finish the prediction. -![output11](Images/lab4_11.PNG) +![output11](images/lab4_11.PNG) You can also use your workstation camera to test image classification. Hold an appropriate picture up to your camera, then press `S`. The tool captures the image and sends it to the board. -![output12](Images/lab4_12.PNG) +![output12](images/lab4_12.PNG) You have now successfully ran the model on your STM32 board. diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/features.md b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/features.md index 24cca9f7ff..25023fc47d 100644 --- a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/features.md +++ b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/features.md @@ -54,7 +54,7 @@ for idx, file in enumerate(data_files): You can check the extracted features with this code block. These are the extracted features from one data sample. Expected output shown below: -![output5](Images/output5.PNG) +![output5](images/output5.PNG) ## Feature based model diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output1.PNG b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output1.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output1.PNG rename to content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output1.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output2.PNG b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output2.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output2.PNG rename to content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output2.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output3.PNG b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output3.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output3.PNG rename to content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output3.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output4.PNG b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output4.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output4.PNG rename to content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output4.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output5.PNG b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output5.PNG similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Images/output5.PNG rename to content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/images/output5.PNG diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Project_Files/tf_stm32.zip b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/project_files/tf_stm32.zip similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/Project_Files/tf_stm32.zip rename to content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/project_files/tf_stm32.zip diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/test.md b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/test.md index cf247622fc..dc7751d42b 100644 --- a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/test.md +++ b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/test.md @@ -77,7 +77,7 @@ plt.legend(loc='lower right') ``` In this example, see that the training and validation accuracy start to converge after around 150 epochs. This means that the 200 epochs are enough to train the model. If you train the model for too many epochs, then the validation accuracy may drop due to overfitting. If you experience this, re-run [training](#train) with an appropriate epoch value. -![output2](Images/output2.PNG) +![output2](images/output2.PNG) ## Investigate learning rate (optional) @@ -102,7 +102,7 @@ plt.legend(loc='lower right') ``` Expected output shown below: -![output3](Images/output3.PNG) +![output3](images/output3.PNG) Now try a lower learning rate, which is 0.0001. Execute the code block. The graph shows the training and validation loss values decrease much more slowly. So, it is important to use a proper learning rate in training. @@ -124,7 +124,7 @@ plt.legend(loc='lower right') Expected output shown below: -![output4](Images/output4.PNG) +![output4](images/output4.PNG) With the model trained, you are now ready to test it. diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/train_nn.md b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/train_nn.md index d69fd49196..17942e6cbc 100644 --- a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/train_nn.md +++ b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/train_nn.md @@ -162,4 +162,4 @@ plot_single_sample(data_sample=data[idx], label=labels[idx]) Example output is shown below: -![output1](Images/output1.PNG) +![output1](images/output1.PNG) diff --git a/content/learning-paths/iot/azure-iot/aggregation.md b/content/learning-paths/iot/azure-iot/aggregation.md index e89871f593..f21100bc2f 100644 --- a/content/learning-paths/iot/azure-iot/aggregation.md +++ b/content/learning-paths/iot/azure-iot/aggregation.md @@ -162,23 +162,23 @@ func start ``` Once running, observe the HTTP trigger endpoint, which should appear similar to the following: -![img36 alt-text#center](Figures/36.png) +![img36 alt-text#center](figures/36.png) Next, start the simulator to stream sensor data and open the HTTP trigger endpoint URL in your web browser. You will see the calculated average temperature displayed: -![img37 alt-text#center](Figures/37.png) +![img37 alt-text#center](figures/37.png) ## Deploy to Azure Function App Now that your Azure Function is fully tested and ready, it's time to deploy it to Azure, making it accessible online and available for integration with other services and applications. Visual Studio Code provides an easy and efficient way to deploy Azure Functions directly from your local development environment. Follow these steps to deploy your function 1. In Visual Studio Code, open the Command Palette (Ctrl+Shift+P on Windows/Linux, or Cmd+Shift+P on macOS) and search for "Azure Functions: Deploy to Function App": -![img38 alt-text#center](Figures/38.png) +![img38 alt-text#center](figures/38.png) 2. The deployment wizard will guide you through the following selections: * Subscription: choose the Azure subscription you wish to use, * Select a function app: Select the Function App that you previously created in Azure (in this example, "IoTTemperatureAlertFunc") * Confirm your deployment: -![img39 alt-text#center](Figures/39.png) +![img39 alt-text#center](figures/39.png) 3. Wait for the deployment to complete. This process typically takes a few moments. Once deployed, your Azure Function is hosted in Azure and ready for use. 4. Open the Azure Portal, and go to your function app (in this example, "IoTTemperatureAlertFunc"). You will see the deployed functions: -![img40 alt-text#center](Figures/40.png) +![img40 alt-text#center](figures/40.png) ## Configure Function App Settings We have just deployed the functions to Azure. Previously, when testing the functions locally, we used the local.settings.json file to store the Cosmos DB connection string. However, this local configuration file is not deployed to Azure. Therefore, we need to update the corresponding settings directly within the Azure portal. @@ -191,7 +191,7 @@ Follow these steps to configure the Cosmos DB connection string 3. Click the + Add button 4. Enter the name you used in your code (e.g., armiotcosmosdb_DOCUMENTDB). 5. Paste the Cosmos DB connection string into the Value field: -![img41 alt-text#center](Figures/41.png) +![img41 alt-text#center](figures/41.png) 6. Click Apply to add the setting. 7. Press Apply at the bottom to apply changes. Then, confirm to save changes @@ -200,11 +200,11 @@ Once you've configured the connection string, test your deployed Azure Function 1. Return to the Overview page of your Azure Function App. 2. Click on your HTTP-triggered function (GetAverageTemperature). 3. Click Get function URL and copy the displayed URL (under default): -![img42 alt-text#center](Figures/42.png) +![img42 alt-text#center](figures/42.png) 4. Open this URL in your web browser. 5. Start your IoT simulator to begin streaming telemetry data to Cosmos DB. 6. Refresh or access the function URL again, and you should see the calculated average temperature displayed: -![img43 alt-text#center](Figures/43.png) +![img43 alt-text#center](figures/43.png) This confirms your Azure Function successfully connects to Cosmos DB, retrieves real-time data, and calculates the average temperature as intended diff --git a/content/learning-paths/iot/azure-iot/device_registration.md b/content/learning-paths/iot/azure-iot/device_registration.md index 603a60e3aa..31a218464c 100644 --- a/content/learning-paths/iot/azure-iot/device_registration.md +++ b/content/learning-paths/iot/azure-iot/device_registration.md @@ -169,15 +169,15 @@ To connect the Python application you developed earlier to Azure IoT Hub, follow 1. Register a Device on Azure IoT Hub: * Open the Azure Portal, go to your IoT Hub, and click Devices under Device management: -![img6 alt-text#center](Figures/06.png) +![img6 alt-text#center](figures/06.png) * Click “Add Device”, enter a device ID (e.g., arm64Device01), and leave the authentication type as “Symmetric key.” -![img7 alt-text#center](Figures/07.png) +![img7 alt-text#center](figures/07.png) * Click “Save”. 2. Next, you’ll need to retrieve the connection string to integrate your Python application with Azure IoT Hub: * From the device list, select your newly created device (arm64Device01) * Copy the Primary Connection String from the device details page. You’ll need this connection string to authenticate your Python application when connecting and streaming telemetry data -![img8 alt-text#center](Figures/08.png) +![img8 alt-text#center](figures/08.png) Ensure this connection string is stored securely, as it provides authentication credentials for your device. In the next step, you’ll integrate this connection string into your Python simulator app, enabling secure communication and real-time data streaming to Azure IoT Hub diff --git a/content/learning-paths/iot/azure-iot/Figures/01.png b/content/learning-paths/iot/azure-iot/figures/01.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/01.png rename to content/learning-paths/iot/azure-iot/figures/01.png diff --git a/content/learning-paths/iot/azure-iot/Figures/02.png b/content/learning-paths/iot/azure-iot/figures/02.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/02.png rename to content/learning-paths/iot/azure-iot/figures/02.png diff --git a/content/learning-paths/iot/azure-iot/Figures/03.png b/content/learning-paths/iot/azure-iot/figures/03.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/03.png rename to content/learning-paths/iot/azure-iot/figures/03.png diff --git a/content/learning-paths/iot/azure-iot/Figures/04.png b/content/learning-paths/iot/azure-iot/figures/04.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/04.png rename to content/learning-paths/iot/azure-iot/figures/04.png diff --git a/content/learning-paths/iot/azure-iot/Figures/05.png b/content/learning-paths/iot/azure-iot/figures/05.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/05.png rename to content/learning-paths/iot/azure-iot/figures/05.png diff --git a/content/learning-paths/iot/azure-iot/Figures/06.png b/content/learning-paths/iot/azure-iot/figures/06.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/06.png rename to content/learning-paths/iot/azure-iot/figures/06.png diff --git a/content/learning-paths/iot/azure-iot/Figures/07.png b/content/learning-paths/iot/azure-iot/figures/07.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/07.png rename to content/learning-paths/iot/azure-iot/figures/07.png diff --git a/content/learning-paths/iot/azure-iot/Figures/08.png b/content/learning-paths/iot/azure-iot/figures/08.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/08.png rename to content/learning-paths/iot/azure-iot/figures/08.png diff --git a/content/learning-paths/iot/azure-iot/Figures/09.png b/content/learning-paths/iot/azure-iot/figures/09.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/09.png rename to content/learning-paths/iot/azure-iot/figures/09.png diff --git a/content/learning-paths/iot/azure-iot/Figures/10.png b/content/learning-paths/iot/azure-iot/figures/10.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/10.png rename to content/learning-paths/iot/azure-iot/figures/10.png diff --git a/content/learning-paths/iot/azure-iot/Figures/11.png b/content/learning-paths/iot/azure-iot/figures/11.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/11.png rename to content/learning-paths/iot/azure-iot/figures/11.png diff --git a/content/learning-paths/iot/azure-iot/Figures/12.png b/content/learning-paths/iot/azure-iot/figures/12.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/12.png rename to content/learning-paths/iot/azure-iot/figures/12.png diff --git a/content/learning-paths/iot/azure-iot/Figures/13.png b/content/learning-paths/iot/azure-iot/figures/13.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/13.png rename to content/learning-paths/iot/azure-iot/figures/13.png diff --git a/content/learning-paths/iot/azure-iot/Figures/14.png b/content/learning-paths/iot/azure-iot/figures/14.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/14.png rename to content/learning-paths/iot/azure-iot/figures/14.png diff --git a/content/learning-paths/iot/azure-iot/Figures/15.png b/content/learning-paths/iot/azure-iot/figures/15.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/15.png rename to content/learning-paths/iot/azure-iot/figures/15.png diff --git a/content/learning-paths/iot/azure-iot/Figures/16.png b/content/learning-paths/iot/azure-iot/figures/16.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/16.png rename to content/learning-paths/iot/azure-iot/figures/16.png diff --git a/content/learning-paths/iot/azure-iot/Figures/17.png b/content/learning-paths/iot/azure-iot/figures/17.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/17.png rename to content/learning-paths/iot/azure-iot/figures/17.png diff --git a/content/learning-paths/iot/azure-iot/Figures/18.png b/content/learning-paths/iot/azure-iot/figures/18.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/18.png rename to content/learning-paths/iot/azure-iot/figures/18.png diff --git a/content/learning-paths/iot/azure-iot/Figures/19.png b/content/learning-paths/iot/azure-iot/figures/19.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/19.png rename to content/learning-paths/iot/azure-iot/figures/19.png diff --git a/content/learning-paths/iot/azure-iot/Figures/20.png b/content/learning-paths/iot/azure-iot/figures/20.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/20.png rename to content/learning-paths/iot/azure-iot/figures/20.png diff --git a/content/learning-paths/iot/azure-iot/Figures/21.png b/content/learning-paths/iot/azure-iot/figures/21.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/21.png rename to content/learning-paths/iot/azure-iot/figures/21.png diff --git a/content/learning-paths/iot/azure-iot/Figures/22.png b/content/learning-paths/iot/azure-iot/figures/22.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/22.png rename to content/learning-paths/iot/azure-iot/figures/22.png diff --git a/content/learning-paths/iot/azure-iot/Figures/23.png b/content/learning-paths/iot/azure-iot/figures/23.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/23.png rename to content/learning-paths/iot/azure-iot/figures/23.png diff --git a/content/learning-paths/iot/azure-iot/Figures/24.png b/content/learning-paths/iot/azure-iot/figures/24.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/24.png rename to content/learning-paths/iot/azure-iot/figures/24.png diff --git a/content/learning-paths/iot/azure-iot/Figures/25.png b/content/learning-paths/iot/azure-iot/figures/25.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/25.png rename to content/learning-paths/iot/azure-iot/figures/25.png diff --git a/content/learning-paths/iot/azure-iot/Figures/26.png b/content/learning-paths/iot/azure-iot/figures/26.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/26.png rename to content/learning-paths/iot/azure-iot/figures/26.png diff --git a/content/learning-paths/iot/azure-iot/Figures/27.png b/content/learning-paths/iot/azure-iot/figures/27.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/27.png rename to content/learning-paths/iot/azure-iot/figures/27.png diff --git a/content/learning-paths/iot/azure-iot/Figures/28.png b/content/learning-paths/iot/azure-iot/figures/28.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/28.png rename to content/learning-paths/iot/azure-iot/figures/28.png diff --git a/content/learning-paths/iot/azure-iot/Figures/29.png b/content/learning-paths/iot/azure-iot/figures/29.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/29.png rename to content/learning-paths/iot/azure-iot/figures/29.png diff --git a/content/learning-paths/iot/azure-iot/Figures/30.png b/content/learning-paths/iot/azure-iot/figures/30.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/30.png rename to content/learning-paths/iot/azure-iot/figures/30.png diff --git a/content/learning-paths/iot/azure-iot/Figures/31.png b/content/learning-paths/iot/azure-iot/figures/31.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/31.png rename to content/learning-paths/iot/azure-iot/figures/31.png diff --git a/content/learning-paths/iot/azure-iot/Figures/32.png b/content/learning-paths/iot/azure-iot/figures/32.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/32.png rename to content/learning-paths/iot/azure-iot/figures/32.png diff --git a/content/learning-paths/iot/azure-iot/Figures/33.png b/content/learning-paths/iot/azure-iot/figures/33.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/33.png rename to content/learning-paths/iot/azure-iot/figures/33.png diff --git a/content/learning-paths/iot/azure-iot/Figures/34.png b/content/learning-paths/iot/azure-iot/figures/34.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/34.png rename to content/learning-paths/iot/azure-iot/figures/34.png diff --git a/content/learning-paths/iot/azure-iot/Figures/35.png b/content/learning-paths/iot/azure-iot/figures/35.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/35.png rename to content/learning-paths/iot/azure-iot/figures/35.png diff --git a/content/learning-paths/iot/azure-iot/Figures/36.png b/content/learning-paths/iot/azure-iot/figures/36.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/36.png rename to content/learning-paths/iot/azure-iot/figures/36.png diff --git a/content/learning-paths/iot/azure-iot/Figures/37.png b/content/learning-paths/iot/azure-iot/figures/37.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/37.png rename to content/learning-paths/iot/azure-iot/figures/37.png diff --git a/content/learning-paths/iot/azure-iot/Figures/38.png b/content/learning-paths/iot/azure-iot/figures/38.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/38.png rename to content/learning-paths/iot/azure-iot/figures/38.png diff --git a/content/learning-paths/iot/azure-iot/Figures/39.png b/content/learning-paths/iot/azure-iot/figures/39.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/39.png rename to content/learning-paths/iot/azure-iot/figures/39.png diff --git a/content/learning-paths/iot/azure-iot/Figures/40.png b/content/learning-paths/iot/azure-iot/figures/40.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/40.png rename to content/learning-paths/iot/azure-iot/figures/40.png diff --git a/content/learning-paths/iot/azure-iot/Figures/41.png b/content/learning-paths/iot/azure-iot/figures/41.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/41.png rename to content/learning-paths/iot/azure-iot/figures/41.png diff --git a/content/learning-paths/iot/azure-iot/Figures/42.png b/content/learning-paths/iot/azure-iot/figures/42.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/42.png rename to content/learning-paths/iot/azure-iot/figures/42.png diff --git a/content/learning-paths/iot/azure-iot/Figures/43.png b/content/learning-paths/iot/azure-iot/figures/43.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/43.png rename to content/learning-paths/iot/azure-iot/figures/43.png diff --git a/content/learning-paths/iot/azure-iot/Figures/44.png b/content/learning-paths/iot/azure-iot/figures/44.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/44.png rename to content/learning-paths/iot/azure-iot/figures/44.png diff --git a/content/learning-paths/iot/azure-iot/Figures/45.png b/content/learning-paths/iot/azure-iot/figures/45.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/45.png rename to content/learning-paths/iot/azure-iot/figures/45.png diff --git a/content/learning-paths/iot/azure-iot/Figures/46.png b/content/learning-paths/iot/azure-iot/figures/46.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/46.png rename to content/learning-paths/iot/azure-iot/figures/46.png diff --git a/content/learning-paths/iot/azure-iot/Figures/47.png b/content/learning-paths/iot/azure-iot/figures/47.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/47.png rename to content/learning-paths/iot/azure-iot/figures/47.png diff --git a/content/learning-paths/iot/azure-iot/Figures/48.png b/content/learning-paths/iot/azure-iot/figures/48.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/48.png rename to content/learning-paths/iot/azure-iot/figures/48.png diff --git a/content/learning-paths/iot/azure-iot/Figures/49.png b/content/learning-paths/iot/azure-iot/figures/49.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/49.png rename to content/learning-paths/iot/azure-iot/figures/49.png diff --git a/content/learning-paths/iot/azure-iot/Figures/50.png b/content/learning-paths/iot/azure-iot/figures/50.png similarity index 100% rename from content/learning-paths/iot/azure-iot/Figures/50.png rename to content/learning-paths/iot/azure-iot/figures/50.png diff --git a/content/learning-paths/iot/azure-iot/iot-hub.md b/content/learning-paths/iot/azure-iot/iot-hub.md index f215e28989..edb824d357 100644 --- a/content/learning-paths/iot/azure-iot/iot-hub.md +++ b/content/learning-paths/iot/azure-iot/iot-hub.md @@ -24,14 +24,14 @@ Start by creating an Azure IoT Hub 2. Create a new Azure IoT Hub resource * On the Azure Portal home page, select “Create a resource” at the top left as shown below -![img1 alt-text#center](Figures/01.png) +![img1 alt-text#center](figures/01.png) * In the Search services and marketplace box, type “IoT Hub” and press Enter. * Click on IoT Hub from the search results: -![img2 alt-text#center](Figures/02.png) +![img2 alt-text#center](figures/02.png) 3. Click the “Create” button: -![img3 alt-text#center](Figures/03.png) +![img3 alt-text#center](figures/03.png) 4. Configure Basic IoT Hub Settings * Subscription: Select your Azure subscription. @@ -39,7 +39,7 @@ Start by creating an Azure IoT Hub * IoT Hub Name: Enter a unique name for your IoT Hub (must be globally unique, e.g., iot-hub-arm-64). * Region: Select a region closest to your location or users. * Tier: Free. This will update the daily message limit accordingly: -![img4 alt-text#center](Figures/04.png) +![img4 alt-text#center](figures/04.png) 5. Click “Next: Networking”. 6. Configure Networking: @@ -56,7 +56,7 @@ Start by creating an Azure IoT Hub * Once deployed, you’ll see a message stating “Your deployment is complete”. * Click “Go to resource” to open the newly created Azure IoT Hub. 12. Check IoT Hub Overview and Details. From the IoT Hub overview page, verify important details such as the hub name, region, status, and hostname, which you’ll use to connect devices: -![img5 alt-text#center](Figures/05.png) +![img5 alt-text#center](figures/05.png) ## Next steps Now that your Azure IoT Hub is ready, you can proceed to register and configure your IoT devices. In the next step, you’ll learn how to register an Arm64-based IoT device and start streaming data using Python and Azure IoT SDK. \ No newline at end of file diff --git a/content/learning-paths/iot/azure-iot/monitoring.md b/content/learning-paths/iot/azure-iot/monitoring.md index 13c71fa98e..7f37472686 100644 --- a/content/learning-paths/iot/azure-iot/monitoring.md +++ b/content/learning-paths/iot/azure-iot/monitoring.md @@ -60,9 +60,9 @@ For Python functions on Linux-based plans, local development and deployment repr We will start by creating an Azure Function App, in which we will create an Azure Function that regularly queries temperature data from Cosmos DB. In the next step, we will add the capability to send notifications, whenever the temperature reading exceeds a predefined threshold. Proceed as follows: 1. Sign in to the Azure Portal. 2. Click “Create a resource”, type “Function App”, and select it: -![img24 alt-text#center](Figures/24.png) +![img24 alt-text#center](figures/24.png) 3. Click Create, then select Consumption as a hosting option: -![img25 alt-text#center](Figures/25.png) +![img25 alt-text#center](figures/25.png) 4. Provide the required details: * Subscription: Your Azure subscription. * Resource Group: Select your existing IoT resource group. @@ -73,7 +73,7 @@ We will start by creating an Azure Function App, in which we will create an Azur * Operating System: Linux as Windows is unavailable for Python. 5. Click Review + Create, and then Create. -![img26 alt-text#center](Figures/26.png) +![img26 alt-text#center](figures/26.png) ## Install Prerequisites Before writing the code make sure you have the following tools installed: @@ -99,11 +99,11 @@ Ensure you also see a v4.x.x output, indicating compatibility with Python v2 mod Follow these steps to create an Azure Function locally using Visual Studio Code: 1. In Visual Studio Cod, click View->Command Palette... 2. Type "Create Function": -![img27 alt-text#center](Figures/27.png) +![img27 alt-text#center](figures/27.png) 3. Select Azure Functions: Create Function... 4. Select folder for your new function. For example create a new folder Arm.AzureIoT.AzureFunctions 5. Visual Studio Code will display the wizard, which enables you to configure your function: -![img28 alt-text#center](Figures/28.png) +![img28 alt-text#center](figures/28.png) 6. Use this wizard to configure the function: * Select a language: pick Python * Select a Python programming model: Model V2 (Recommended) @@ -176,7 +176,7 @@ func start You should then see the following logs, depending on the generated temperature values: -![img29 alt-text#center](Figures/29.png) +![img29 alt-text#center](figures/29.png) ## Monitoring and Notifications In this section, you'll extend your existing Azure Function to send email notifications using SendGrid whenever the temperature exceeds the defined threshold @@ -185,23 +185,23 @@ In this section, you'll extend your existing Azure Function to send email notifi Follow these steps to create a SendGrid account: 1. Sign in to the Azure Portal. 2. Click “Create a resource” and search for SendGrid. -![img30 alt-text#center](Figures/30.png) +![img30 alt-text#center](figures/30.png) 3. Select Twilio SendGrid, choose the Free 100 (2022) plan, and then click Subscribe. 4. Provide the following details: * Subscription: Select your Azure subscription. * Resource group: Choose your existing IoT project resource group. * Name: Enter a descriptive name (e.g., iot-alerts-sendgrid). -![img31 alt-text#center](Figures/31.png) +![img31 alt-text#center](figures/31.png) 5. Click Review + subscribe and then Subscribe. 6. On the next screen, click Configure account now: -![img32 alt-text#center](Figures/32.png) +![img32 alt-text#center](figures/32.png) 7. Accept any permissions required by SendGrid, and then enter your details to create a sender identity: -![img33 alt-text#center](Figures/33.png) +![img33 alt-text#center](figures/33.png) 8. Fill out the required details, such as your name and email address. 9. After the sender identity is verified, click API Keys in the left menu: -![img34 alt-text#center](Figures/34.png) +![img34 alt-text#center](figures/34.png) 10. Click Create API Key. In the popup window, enter a key name (e.g., iot-api-key), and select Full Access. -![img35 alt-text#center](Figures/35.png) +![img35 alt-text#center](figures/35.png) 11. Copy the generated API key securely. You will not be able to retrieve it later. ### Configure SendGrid API Key in your Azure Function diff --git a/content/learning-paths/iot/azure-iot/portal.md b/content/learning-paths/iot/azure-iot/portal.md index 9db8dbcc38..f4d9d15839 100644 --- a/content/learning-paths/iot/azure-iot/portal.md +++ b/content/learning-paths/iot/azure-iot/portal.md @@ -135,7 +135,7 @@ Make sure you’ve saved all three files (index.html, main.js, and styles.css). You should now see real-time temperature readings displayed: -![img44 alt-text#center](Figures/44.png) +![img44 alt-text#center](figures/44.png) ## Deployment to Azure Blob Storage You will now deploy the web portal you’ve created to Azure Blob Storage, making it accessible online. @@ -145,21 +145,21 @@ You will now deploy the web portal you’ve created to Azure Blob Storage, makin 2. Create a Storage Account: * Click “Create a resource” * Search for “Storage account” -![img45 alt-text#center](Figures/45.png) +![img45 alt-text#center](figures/45.png) * Click “Create”. -![img46 alt-text#center](Figures/46.png) +![img46 alt-text#center](figures/46.png) 3. Provide required details: * Subscription, resource group, storage account name (e.g. armiotstorage). * For Primary service, choose Azure Blob Storage or Azure Data Lake Storage Gen 2. * Select Standard performance and Locally-redundant storage (LRS). -![img47 alt-text#center](Figures/47.png) +![img47 alt-text#center](figures/47.png) * Click "Review + create", then "Create". 3. Enable Static Website Hosting: * Navigate to your newly created storage account. * Under Data management, click “Static website”. * Select “Enabled”. * Set index.html as the index document name. -![img48 alt-text#center](Figures/48.png) +![img48 alt-text#center](figures/48.png) * Click Save. After saving, Azure provides you with a URL like: https://.z22.web.core.windows.net/. @@ -172,12 +172,12 @@ You can upload your website files directly using the Azure Portal or via Azure S 3. Open the container named ”$web” (created automatically when enabling static websites). 4. Click Upload and select your three website files (index.html, main.js, styles.css), and upload them. -![img49 alt-text#center](Figures/49.png) +![img49 alt-text#center](figures/49.png) ### Verify the Deployment After uploading your files, open a browser and navigate to https://.z22.web.core.windows.net/. Your static website should load, allowing you to test the “Get temperature” button (to see temperatures make sure to start the IoT simulator): -![img50 alt-text#center](Figures/50.png) +![img50 alt-text#center](figures/50.png) ## Summary In this learning path, we successfully built a complete, end-to-end prototype of an IoT solution. Our journey began with a simulator streaming realistic telemetry data to Azure through IoT Hub. We leveraged Azure Stream Analytics to process and route this streaming data directly into Cosmos DB, providing scalable and reliable storage. Additionally, we developed two Azure Functions: the first continuously monitors incoming temperature readings, sending email notifications whenever the temperature exceeds a predefined threshold, ensuring proactive alerts. The second Azure Function aggregates recent temperature data from the last minute and provides this information via an HTTP endpoint. Finally, we utilized this aggregation function within our user-friendly web portal, enabling real-time visualization of temperature data, thus completing our robust and interactive IoT solution. diff --git a/content/learning-paths/iot/azure-iot/stream-analytics-dynamo-db.md b/content/learning-paths/iot/azure-iot/stream-analytics-dynamo-db.md index 6672e3d3b0..f2b96faa98 100644 --- a/content/learning-paths/iot/azure-iot/stream-analytics-dynamo-db.md +++ b/content/learning-paths/iot/azure-iot/stream-analytics-dynamo-db.md @@ -52,11 +52,11 @@ You will now configure the stream analytics job such that the telemetry data wil Start by creating the Create Cosmos DB account and database: 1. Log in to the Azure Portal. 2. Select “Create a resource”, search for “Azure Cosmos DB”, and click Create: -![img14 alt-text#center](Figures/14.png) -![img15 alt-text#center](Figures/15.png) +![img14 alt-text#center](figures/14.png) +![img15 alt-text#center](figures/15.png) 3. Select Azure Cosmos DB for NoSQL, then click Create. -![img16 alt-text#center](Figures/16.png) +![img16 alt-text#center](figures/16.png) 4. Fill in the required details: * Subscription: Select your subscription. @@ -67,7 +67,7 @@ Start by creating the Create Cosmos DB account and database: * Select Servleress as capacity mode. * Apply Free Tier Discount: Apply * Check Limit total account throughput. -![img17 alt-text#center](Figures/17.png) +![img17 alt-text#center](figures/17.png) 5. Click Review + create, then click Create. Once the deployment completes: @@ -76,16 +76,16 @@ Once the deployment completes: * Select an appropriate partition key (recommended: /deviceId). * Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads: Off. * Click OK at the bottom. -![img18 alt-text#center](Figures/18.png) +![img18 alt-text#center](figures/18.png) ### Modify Stream Analytics Job Now update your query in Stream Analytics to write data from IoT Hub directly into Cosmos DB: 1. Go to IoTStreamAnalyticsJob. 2. Under Job topology, select Outputs. 3. Click Add output, and select Cosmos DB: -![img19 alt-text#center](Figures/19.png) +![img19 alt-text#center](figures/19.png) 4. In the Cosmos DB pane, type CosmosDBOutput for the alias name, leave other fields at their default values, and click the Save button: -![img20 alt-text#center](Figures/20.png) +![img20 alt-text#center](figures/20.png) ### Update Your Stream Analytics Query Now once, you have the output configured, modify the query. To do so, select Query under Job topology. Then, modify your existing query to explicitly specify your Cosmos DB output alias: @@ -103,16 +103,16 @@ FROM IoTHubInput ``` -![img21 alt-text#center](Figures/21.png) +![img21 alt-text#center](figures/21.png) Afterwards, click Start job, and then Start: -![img22 alt-text#center](Figures/22.png) +![img22 alt-text#center](figures/22.png) ## Verify data flow in Cosmos DB To verify that your data pipeline is working correctly, first start your Python IoT simulator application (iot_simulator.py). Ensure it’s actively sending telemetry data. Next, open the Azure Portal and navigate to your Azure Cosmos DB resource. Under Data Explorer, select your database and then your container (e.g., SensorReadings). Once selected, click Items to view your stored data. Sensor readings streamed from your IoT device will appear on the right-hand side of the Data Explorer interface, similar to the screenshot below: -![img23 alt-text#center](Figures/23.png) +![img23 alt-text#center](figures/23.png) Azure Cosmos DB stores data as JSON documents within a NoSQL (document-based) structure, making it ideal for flexible and dynamic data, such as IoT telemetry. Each record (also called a document) is stored in a container (or collection) that doesn’t enforce a rigid schema. As a result, each document can contain different fields without requiring schema changes or migrations, which is particularly valuable when collecting data from diverse IoT devices with evolving attributes. diff --git a/content/learning-paths/iot/azure-iot/stream-analytics.md b/content/learning-paths/iot/azure-iot/stream-analytics.md index a25fb69c97..dcad6f1cd4 100644 --- a/content/learning-paths/iot/azure-iot/stream-analytics.md +++ b/content/learning-paths/iot/azure-iot/stream-analytics.md @@ -22,7 +22,7 @@ To process and analyze the telemetry data we’re streaming to Azure IoT Hub, we 1. Sign in to the Azure Portal. 2. Click “Create a resource”, type “Stream Analytics job” into the search box, and press Enter. 3. From the search results, select Stream Analytics job, then click Create: -![img9 alt-text#center](Figures/09.png) +![img9 alt-text#center](figures/09.png) 4. Provide the necessary information: * Subscription: Choose the Azure subscription you want to use for this job. * Resource group: Select the resource group you previously created (e.g., your IoT project’s resource group). @@ -31,7 +31,7 @@ To process and analyze the telemetry data we’re streaming to Azure IoT Hub, we * Hosting environment: Select Cloud for Azure-managed infrastructure. * Streaming units: Set this to 1 (appropriate for initial testing and smaller workloads, you can scale up later). -![img10 alt-text#center](Figures/10.png) +![img10 alt-text#center](figures/10.png) 5. After reviewing your settings carefully, click Review + create, confirm that all details are correct, and then click Create to deploy your Azure Stream Analytics job. @@ -42,7 +42,7 @@ After successfully creating the Stream Analytics job, you’ll need to configure 1. Navigate to your newly created Stream Analytics job in the Azure Portal. 2. In the left-hand menu, under the Job topology section, select Inputs. 3. Click “Add input”, and choose “IoT Hub” as the input type. -![img11 alt-text#center](Figures/11.png) +![img11 alt-text#center](figures/11.png) 4. Enter the following configuration details: * Input Alias: Provide a name, such as IoTHubInput. * IoT Hub: Select the Azure IoT Hub you created earlier. @@ -54,7 +54,7 @@ After successfully creating the Stream Analytics job, you’ll need to configure * Encoding: Choose UTF-8. * Event compression type: Set this to None. -![img12 alt-text#center](Figures/12.png) +![img12 alt-text#center](figures/12.png) 4. After entering these details, carefully verify them for accuracy and completeness. Click “Save” to apply the changes and successfully link your Azure IoT Hub as the input source for your Stream Analytics job. @@ -80,7 +80,7 @@ This straightforward query selects all relevant fields (deviceId, temperature, p Before running this query, ensure your Python IoT simulator (iot_simulator.py) is actively sending telemetry data. After the simulator begins transmitting data, you can test and verify your Stream Analytics query within the Azure Portal using the built-in Test query feature. Doing this allows you to view live-streamed sensor data in real-time and confirm that your streaming pipeline is working as expected: -![img13 alt-text#center](Figures/13.png) +![img13 alt-text#center](figures/13.png) Constructing queries in Azure Stream Analytics involves using a straightforward SQL-like syntax specifically optimized for real-time stream processing. Typically, a query contains a SELECT statement to specify which fields from the incoming telemetry data to process, and a FROM statement to indicate the source stream. Queries can be expanded with advanced features such as filtering, aggregations, and temporal window functions to handle more complex scenarios. diff --git a/content/learning-paths/iot/iot-sdk/aws.md b/content/learning-paths/iot/iot-sdk/aws.md index 7f1bf5b619..882bc9c1e6 100644 --- a/content/learning-paths/iot/iot-sdk/aws.md +++ b/content/learning-paths/iot/iot-sdk/aws.md @@ -168,7 +168,7 @@ ML_HEARD_ON ... ``` -![keyword #center](Images/keyword.png) +![keyword #center](images/keyword.png) ## Repeat with the speech example @@ -185,6 +185,6 @@ INFO - Complete recognition: turn down the temperature in the bedroom 134 14506 [ML_MQTT] [INFO] Publish to the MQTT topic /ml/inference. ``` -![speech #center](Images/speech.png) +![speech #center](images/speech.png) See the [README.md](https://github.com/ARM-software/open-iot-sdk/blob/main/v8m/README.md#setting-up-aws-connectivity) for full details. \ No newline at end of file diff --git a/content/learning-paths/iot/iot-sdk/Images/keyword.png b/content/learning-paths/iot/iot-sdk/images/keyword.png similarity index 100% rename from content/learning-paths/iot/iot-sdk/Images/keyword.png rename to content/learning-paths/iot/iot-sdk/images/keyword.png diff --git a/content/learning-paths/iot/iot-sdk/Images/speech.png b/content/learning-paths/iot/iot-sdk/images/speech.png similarity index 100% rename from content/learning-paths/iot/iot-sdk/Images/speech.png rename to content/learning-paths/iot/iot-sdk/images/speech.png diff --git a/content/learning-paths/laptops-and-desktops/electron/Figures/01.png b/content/learning-paths/laptops-and-desktops/electron/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/electron/Figures/01.png rename to content/learning-paths/laptops-and-desktops/electron/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/electron/Figures/02.png b/content/learning-paths/laptops-and-desktops/electron/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/electron/Figures/02.png rename to content/learning-paths/laptops-and-desktops/electron/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/electron/Figures/03.png b/content/learning-paths/laptops-and-desktops/electron/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/electron/Figures/03.png rename to content/learning-paths/laptops-and-desktops/electron/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/electron/Figures/04.png b/content/learning-paths/laptops-and-desktops/electron/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/electron/Figures/04.png rename to content/learning-paths/laptops-and-desktops/electron/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/electron/how-to-1.md b/content/learning-paths/laptops-and-desktops/electron/how-to-1.md index 53488e2390..2d8c0a1377 100644 --- a/content/learning-paths/laptops-and-desktops/electron/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/electron/how-to-1.md @@ -20,7 +20,7 @@ You can find the the complete code used in the learning path [here](https://gith ## Before you begin Before you begin, install Node.JS for Arm64. You can find the installer [here](https://nodejs.org/en/download). In this learning path, you will use version 20.10.0. The installation process is automatic. However, make sure to check the "Automatically install the necessary tools" checkbox so that it automatically installs the build tools for the NPM packages: -![fig1](Figures/01.png) +![fig1](figures/01.png) ## Initialize the project Start by initializing the project. To do this, open the command prompt or terminal and type the following commands: @@ -218,7 +218,7 @@ npm start The application window appears. Then click the Fetch data button and you will see the list of posts: -![fig2](Figures/02.png) +![fig2](figures/02.png) ## Styling an application The application works fine. However, it uses default styles and does not look very pretty. To change this you can use cascading style sheets (CSS) as per web applications. To style the application, proceed as follows: @@ -302,11 +302,11 @@ body { After the application launches, click the **Fetch data** button and you will see the following result: -![fig3](Figures/03.png) +![fig3](figures/03.png) ## Checkpoint You now have the application up and running. By default, it runs using the Arm64 architecture because we used Node.js for Arm64. To confirm this, open the Task Manager, click the Details tab and look for **electron.exe** processes: -![fig4](Figures/04.png) +![fig4](figures/04.png) In the next step, you will configure your application such that you can explicitly build it for x64 and Arm64 platforms. diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/create-self-hosted-runner-github.md b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/create-self-hosted-runner-github.md index ac41ab2bc5..fc85d4001f 100644 --- a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/create-self-hosted-runner-github.md +++ b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/create-self-hosted-runner-github.md @@ -11,13 +11,13 @@ layout: "learningpathall" ## Create the Self-Hosted Runner in GitHub Go to the Actions tab, and under Management, click on Runners. If you do not see the Actions tab, navigate to Settings > Actions and select Allow all actions and reusable workflows. This will display the following window: -![img8](Figures/08.png) +![img8](figures/08.png) Then, click on the 'New runner' button, followed by 'New self-hosted runner'. In the 'Add new self-hosted runner' section, proceed as follows: * Select Linux for the operating system. * Choose ARM64 for the architecture -![img9](Figures/09.png) +![img9](figures/09.png) Next, execute the following instructions on your machine: @@ -100,6 +100,6 @@ Current runner version: '2.314.1' The runner will now be visible in the GitHub actions: -![img10](Figures/10.png) +![img10](figures/10.png) You have successfully prepared all the tools needed to run the CI/CD pipeline on the self-hosted runner. In the next step, you will test it. \ No newline at end of file diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/01.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/01.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/02.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/02.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/03.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/03.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/04.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/04.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/05.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/05.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/06.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/06.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/07.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/07.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/08.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/08.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/09.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/09.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/09.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/09.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/10.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/10.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/10.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/10.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/11.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/11.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/11.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/11.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/12.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/12.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/12.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/12.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/13.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/13.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/13.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/13.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/14.png b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/14.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/Figures/14.png rename to content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/figures/14.png diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/how-to-2.md b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/how-to-2.md index 85be3f78f1..19899b926c 100644 --- a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/how-to-2.md +++ b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/how-to-2.md @@ -100,21 +100,21 @@ To run the above pipeline, you need to make modifications to the source code and This change updates the page's main header to greet Arm developers specifically. -![img11](Figures/11.png) +![img11](figures/11.png) 3. Below the file editor, find and click the Commit changes button. This action opens the Commit changes window. 4. In the Commit changes window, you have the option to add a commit message describing your changes. After reviewing your message, click Commit changes to finalize the update: -![img12](Figures/12.png) +![img12](figures/12.png) Committing these changes to the main branch will automatically trigger the CI/CD pipeline. You can monitor the progress of this pipeline under the Actions tab of your GitHub repository: -![img13](Figures/13.png) +![img13](figures/13.png) Upon completion of the second job, which builds and pushes the Docker image, you can find the updated Docker image in your Docker Hub repository. The new image will be tagged as latest and will reflect the changes made to the index.cshtml file: -![img14](Figures/14.png) +![img14](figures/14.png) This process demonstrates how code changes in your repository can seamlessly integrate with GitHub Actions to automate the build and deployment of your application, including updating Docker images in Docker Hub. diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/prepare-github-repo.md b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/prepare-github-repo.md index d0ad97fbb8..424d5eab49 100644 --- a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/prepare-github-repo.md +++ b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/prepare-github-repo.md @@ -14,7 +14,7 @@ Now, you will prepare your GitHub repository as follows: 1. Go to GitHub and log in with your account credentials. 2. In the top right corner, click the + dropdown and select Import repository. -![img5](Figures/05.png) +![img5](figures/05.png) 3. In the Import your project to GitHub section: * Paste the following link https://github.com/dawidborycki/arm-lp-ci-cd-net.git into the 'Your old repository's clone URL' field. @@ -23,14 +23,14 @@ Now, you will prepare your GitHub repository as follows: After the import is complete, you will create two secrets that will store your Docker username and token. Proceed as follows 1. Go to your repository settings, and on the left, scroll down to Secrets and variables, then click Actions: -![img6](Figures/06.png) +![img6](figures/06.png) 2. Under Actions secrets and variables, click the New repository secret button. This opens the New secret window, where you should configure the secret as follows: * Name: DOCKER_USERNAME * Value: * Click the Add secret button -![img7](Figures/07.png) +![img7](figures/07.png) 3. Similarly, create another secret: * Name: DOCKER_PASSWORD diff --git a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/set-up-docker-repo.md b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/set-up-docker-repo.md index 24c7764ac2..cdddbf8c5a 100644 --- a/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/set-up-docker-repo.md +++ b/content/learning-paths/laptops-and-desktops/self_hosted_cicd_github/set-up-docker-repo.md @@ -11,21 +11,21 @@ First, create a repository on DockerHub by following these steps: 1. Log in to DockerHub, then click on *Repositories* at the top. 2. Under *Repositories*, click the *Create repository* button: -![img1](Figures/01.png) +![img1](figures/01.png) 3. In the *Create repository* window, set the repository name to sampleapp and change the repository visibility to *Private*. -![img2](Figures/02.png) +![img2](figures/02.png) 4. Click the *Create* button. This will create the repository and redirect you back to the repositories list. Note the Docker push command. It should look like: docker push /sampleapp:tagname -![img3](Figures/03.png) +![img3](figures/03.png) Now you will need to generate the access token to enable the workflow to authenticate to DockerHub. To create the access token: 1. Click on your username in the top-right corner of the Docker Hub site. 2. From the dropdown menu, select My Account, and then click the Security tab on the left: -![img4](Figures/04.png) +![img4](figures/04.png) 3. Click the New Access Token button. This opens the New Access Token window, in which: * Type ci-cd for the token name. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/07.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/07.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/08.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/08.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/09.png b/content/learning-paths/laptops-and-desktops/win_aws_iot/figures/09.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot/Figures/09.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot/figures/09.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-2.md b/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-2.md index b58a1294a5..4605f8157a 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-2.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-2.md @@ -12,15 +12,15 @@ In this section, you connect the emulator to AWS IoT Core. ## Connecting the emulator Log in to the AWS Console at https://aws.amazon.com, and then search for IoT. From the list that appears, select **IoT Core**: -![fig1](Figures/01.png) +![fig1](figures/01.png) In the AWS IoT console, click **Connect one device**: -![fig2](Figures/02.png) +![fig2](figures/02.png) This will open the **Connect one device** wizard: -![fig3](Figures/03.png) +![fig3](figures/03.png) In the first step of this wizard, **Register and secure your device**, you can see the ping command, which you can use to ensure you can connect to AWS services. @@ -44,17 +44,17 @@ Note that the **a6t44uzbanvsz-ats.iot.eu-central-1.amazonaws.com** is your endpo Click the **Next** button. This takes you to the **Register and secure your device** step, in which you type **WeatherEmulator** under the **Thing** name: -![fig4](Figures/04.png) +![fig4](figures/04.png) Then, click the **Next** button, which opens the **Choose platform and SDK** window, in which you select: * **Windows** from the **Device platform operating system**. * **Node.js** from the **AWS IoT Device SDK**. -![fig5](Figures/05.png) +![fig5](figures/05.png) Afterwards, click the **Next** button, which takes you to the **Download connection kit** step, in which you click the **Download connection kit** button to get the zip with certificates and the sample code, which sends data to the AWS cloud: -![fig6](Figures/06.png) +![fig6](figures/06.png) After downloading the kit, click the **Next** button. This takes you to the **Run connection kit**, in which you click the **Continue** button. This takes you back to the AWS IoT screen. @@ -83,11 +83,11 @@ Before you can connect the device to the AWS IoT Core, you need to configure the To update the policy, under the AWS IoT console, click **Security** -> **Policies**: -![fig7](Figures/07.png) +![fig7](figures/07.png) There should be one policy, **WeatherEmulator-Policy**. Click that policy. This opens another view, in which you can see the policy details. In that view, click **Edit active version** button, which opens the Edit policy view: -![fig7](Figures/08.png) +![fig7](figures/08.png) Modify the first entry ('iot:Publish,iot:Receive,iot:PublishRetain') such that you replace the 'topic/sdk/test/js' with 'topic/*'. This will enable the application to publish and receive messages from any topic. Similarly, modify the second entry, and replace 'topicfilter/sdk/test/js' with 'topicfilter/*'. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-3.md b/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-3.md index 6af29c568a..53ef18ebb9 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-3.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot/how-to-3.md @@ -16,5 +16,5 @@ Under AWS IoT Core, click on **MQTT test client** under the **Test** menu. In the 'Topic Filter' field, type 'Emulators/Weather/SensorReadings', and click the **Subscribe** button. You will see the data sent by the emulator displayed: -![fig9](Figures/09.png) +![fig9](figures/09.png) diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/07.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/07.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/08.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/Figures/08.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/how-to-1.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/how-to-1.md index 6bc519d6b6..266324449f 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_dynamodb/how-to-1.md @@ -13,11 +13,11 @@ Start by creating the rule: 1. Open AWS IoT Core, and look for *Rules* under *Message routing*: -![fig1](Figures/01.png) +![fig1](figures/01.png) 2. Click the *Create rule* button. This will opens *Create rule* view, in which you configure the rule: -![fig2](Figures/02.png) +![fig2](figures/02.png) 3. Use the *Create rule* view to configure the rule as follows. Firstly, under the rule name, type: send_message_to_dynamodb. Then, click the *Next* button. This will take you to the Configure SQL statement, where you use the following SQL query: @@ -25,25 +25,25 @@ Start by creating the rule: SELECT * FROM 'Emulators/Weather/SensorReadings' ``` -![fig3](Figures/03.png) +![fig3](figures/03.png) 4. Click the *Next* button. -![fig4](Figures/04.png) +![fig4](figures/04.png) 5. This opens the *Attach rule actions*, where under *Action 1*, select *DynamoDBv2*. This activates additional controls (see figure below): -![fig5](Figures/05.png) +![fig5](figures/05.png) 6. Click the *Create DynamoDB* table. This will open another tab, where you type *SensorReadings* for the Table name and *timestamp* for the partition key. Ensure to check *Default settings*, scroll down, and click the *Create table* button. Wait a few moments for the table to be created. Then, return to the *Create rule* view. -![fig6](Figures/06.png) +![fig6](figures/06.png) 7. In the *Create rule* view, click the *Refresh* button next to the *Choose DynamoDB* table name drop-down. Then, select the *SensorReadings* table from that drop-down. Afterward, click the *Create new role* button (located below the IAM role). This activates the *Create role* pop-up. 8. In the *Create role* pop-up window, type 'weather_station_dynamo_db', and click the *Create* button. By now, the *Attach rule* actions screen looks like this: -![fig7](Figures/07.png) +![fig7](figures/07.png) 9. Scroll down and click the *Next* button. This displays the summary screen, where you click the *Create* button. @@ -66,7 +66,7 @@ This starts the emulator, which streams data to the AWS IoT Core. The output of To ensure this data is written to the DynamoDB table, go to the AWS console, and in the search box type 'DynamoDB'. Select *DynamoDB* from the list. This takes you to the DynamoDB dashboard, where you click *Explore items* on the left. Then, check the *SensorReadings* radio button, and you see the items in the table: -![fig8](Figures/08.png) +![fig8](figures/08.png) Note that the values in the table are the same as those generated by the emulator. This ensures that you have successfully integrated AWS IoT Core with DynamoDB. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/create-rule.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/create-rule.md index 8304f1c3de..0bb4a84cc7 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/create-rule.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/create-rule.md @@ -14,11 +14,11 @@ Start by creating the rule: 1. Open AWS IoT Core, and look for *Rules* under *Message routing*: -![fig1](Figures/01.png) +![fig1](figures/01.png) 2. Click the *Create rule* button. This opens the *Create rule* view, in which you configure the rule: -![fig2](Figures/02.png) +![fig2](figures/02.png) 3. Use the *Create rule* view to configure the rule. Firstly, under the rule name, type: **check_temperature**. Then, click the *Next* button. This takes you to the Configure SQL statement, where you use the following SQL query: @@ -32,29 +32,29 @@ This query ensures that only temperature readings are sent to the AWS Lambda fun 5. This opens the *Attach rule actions*, where under *Action 1*, select *Lambda*. This activates additional controls (see figure below): -![fig3](Figures/03.png) +![fig3](figures/03.png) 6. Click the *Create a Lambda function* button. This opens another tab. Select *Author from scratch*, type **SendNotification** for the function name, select *Node.js 20.x* for the runtime, and *arm64* for the architecture: -![fig4](Figures/04.png) +![fig4](figures/04.png) 7. Scroll down, and under the *Execution role*, select *Create a new role from AWS policy templates*. This activates additional controls, where you type **sns-email-role** for the *Role name*. Then, click the drop-down list *Policy templates*, and select *Amazon SNS publish policy*: -![fig5](Figures/05.png) +![fig5](figures/05.png) 8. Click the *Create function* button. This takes you to the AWS Lambda Console, which looks as shown below: -![fig6](Figures/06.png) +![fig6](figures/06.png) 9. Keep the AWS Lambda Console open in a separate tab and go back to the *Create rule* wizard. Select *SendNotification* from the Lambda function dropdown, and ensure that the Lambda function version points to $LATEST: -![fig7](Figures/07.png) +![fig7](figures/07.png) 10. Click the *Next* button, and then the *Create* button. 11. Go back to AWS Lambda Console and refresh the page. You will now see that your function accepts the AWS IoT trigger: -![fig8](Figures/08.png) +![fig8](figures/08.png) This means that the AWS Lambda function you have just created has an input trigger set to AWS IoT. Specifically, the AWS Lambda function is invoked whenever a new message is sent to the 'Emulators/Weather/SensorReadings' topic. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/07.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/07.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/08.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/08.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/09.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/09.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/09.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/09.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/10.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/10.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/10.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/10.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/11.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/11.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/11.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/11.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/12.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/12.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/12.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/12.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/13.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/13.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/13.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/13.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/14.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/14.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/14.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/14.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/15.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/15.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/15.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/15.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/16.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/16.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/16.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/16.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/17.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/17.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/17.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/17.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/18.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/18.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/18.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/18.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/19.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/19.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/19.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/19.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/20.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/20.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/20.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/20.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/21.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/21.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/21.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/21.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/22.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/22.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/22.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/22.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/23.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/23.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/23.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/23.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/24.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/24.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/Figures/24.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/figures/24.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/implement-lambda.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/implement-lambda.md index 55804b65a2..aee054e155 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/implement-lambda.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda/implement-lambda.md @@ -171,7 +171,7 @@ In summary, this Lambda function creates an SNS topic and subscribes an email ad After making these changes, the Lambda code editor looks as shown below: -![fig9](Figures/09.png) +![fig9](figures/09.png) Now, click the Deploy button to apply code changes. @@ -180,7 +180,7 @@ Now, click the Deploy button to apply code changes. ### Test Events You will now create two test events to manually invoke the Lambda function. Click the Test button. This opens the Configure test event window: -![fig10](Figures/10.png) +![fig10](figures/10.png) In this window, type **temperature-normal-level** for the Event name, and then under Event JSON paste the following payload: @@ -194,7 +194,7 @@ Finally, click the *Save* button. This takes you back to the Lambda function con Click the triangle icon, in the Test button. This expands the menu, from which you select *Configure test event*: -![fig11](Figures/11.png) +![fig11](figures/11.png) This opens the *Configure test event* window, where you select *Create new event*, change the event name to **temperature-high-level**, and paste the following payload for the Event JSON: @@ -206,18 +206,18 @@ This opens the *Configure test event* window, where you select *Create new event The *Configure test event* window should looks as shown: -![fig12](Figures/12.png) +![fig12](figures/12.png) Click the *Save* button to add a new test event. This takes you back to the Lambda function console. ### Testing the function In the Lambda function console, click the triangle icon next to the Test button, and select *temperature-normal-level* event: -![fig13](Figures/13.png) +![fig13](figures/13.png) Then, click the *Test* button. The Lambda function is invoked, and you see the following execution result: -![fig14](Figures/14.png) +![fig14](figures/14.png) This means that our Lambda function does not have the necessary permissions to create the SNS topic. @@ -231,40 +231,40 @@ Specifically, you will attach the policy giving the Lambda a full access to the To modify the role, proceed as shown: 1. Go the AWS console, and type **IAM** in the search box. Then, select IAM from the list: -![fig15](Figures/15.png) +![fig15](figures/15.png) 2. This takes you to the IAM Dashboard, where you click *Roles under Access management*: -![fig16](Figures/16.png) +![fig16](figures/16.png) 3. Click the *sns-email-role*. This opens another screen, which should look like shown below: -![fig17](Figures/17.png) +![fig17](figures/17.png) 4. Click the *Add permissions* button. This activates the drop-down list, from which you select *Attach policies*. 5. In the *Attach policy to sns-email-role* view, type *SNSFull* in the search box. This filters the list of policies to display one item: *AmazonSNSFullAccess*. Check the check-box on the left of the policy name, and then click the *Add permissions* button: -![fig18](Figures/18.png) +![fig18](figures/18.png) The role has been updated. You can now go back to AWS Lambda. ### Testing the function After updating the role, the SendNotification Lambda function can create SNS topics and send emails. To test this, in the Lambda function console, select the *temperature-high-level* test event, and click the Test button. You will see that the test event was processed without an error: -![fig19](Figures/19.png) +![fig19](figures/19.png) Go to your mailbox and look for an email from AWS Notification. The first email asks you to confirm the subscription: -![fig20](Figures/20.png) +![fig20](figures/20.png) Click the *Confirm subscription* link. Then, invoke the *temperature-high-level* event one more time to see that you received an alert email. It will appear like this: -![fig21](Figures/21.png) +![fig21](figures/21.png) Now, you can start the weather station emulator you created in this [Learning Path](/learning-paths/laptops-and-desktops/win_aws_iot/). Observe the values generated by the emulator. When the temperature exceeds the threshold of 35, you will receive an email notification: -![fig22](Figures/22.png) +![fig22](figures/22.png) ## Environment Variables In the above code, you hardcoded an email, SNS topic, and temperature threshold. This means that every time those values change, you need to modify the function code and redeploy it. In practice, it is better to use environment variables. @@ -274,11 +274,11 @@ You can modify the function code to use environment variables. First, you need t Proceed as shown: 1. In the Lambda function dashboard, click the *Configuration* tab, and select *Environment variables*: -![fig23](Figures/23.png) +![fig23](figures/23.png) 2. Click the *Edit* button. This opens the *Edit environment variables* view, where you click *Add environment variable*. This activates additional controls, where you type **EMAIL** for *Key* and your email for *Value*. Click the *Add environment variable* button one more time, and add another variable with **TEMPERATURE_THRESHOLD** as *Key*, and **35** as *Value*: -![fig24](Figures/24.png) +![fig24](figures/24.png) 3. Click the *Save* button. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/create-lambda.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/create-lambda.md index e41659aad4..db4218db18 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/create-lambda.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/create-lambda.md @@ -13,7 +13,7 @@ You will now implement the AWS Lambda function to scan the records from the Dyna Go the AWS Lambda console. Then, create the function as shown below: 1. Click the *Create function* button: -![fig1](Figures/01.png) +![fig1](figures/01.png) 2. This opens a *Create function* wizard, in which you: * Select **Author from scratch**. @@ -23,19 +23,19 @@ Go the AWS Lambda console. Then, create the function as shown below: At this point your wizard should look as per the image below: -![fig2](Figures/02.png) +![fig2](figures/02.png) Next, in the *Create function* wizard expand *Change default execution role*, and proceed as follows: 1. Select **Create a new role from AWS policy templates**. 2. Type **role-lambda-to-dynamodb** under the Role name. 3. Under *Policy templates - optional* select **Simple microservice permissions**. -![fig3](Figures/03.png) +![fig3](figures/03.png) This ensures that your AWS Lambda function has all the necessary permissions to access the items in the DynamoDB table. Finally, scroll down and click the **Create function** button. This will take you to the GetAverageTemperature function dashboard: -![fig4](Figures/04.png) +![fig4](figures/04.png) In the next step, you will use this dashboard to modify the function code. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/testing.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/testing.md index ca249787c9..468e032a2f 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/testing.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/testing.md @@ -12,11 +12,11 @@ To test the function, launch the IoT emulator to write some data to the DynamoDB In the `GetAverageTemperature` function dashboard click the **Deploy** button: -![fig5](Figures/05.png) +![fig5](figures/05.png) You can then click the **Test** button. This will open the *Configure test event* window, in which you type **Test** in the Event name and click the **Save** button. This will take you back to the `GetAverageTemperature` function dashboard, where you click the **Test** button again. You will see the execution status, including the average temperature value as shown below: -![fig6](Figures/06.png) +![fig6](figures/06.png) # Summary In this learning path you have implemented an AWS Lambda function that processes data stored in a DynamoDB table. You utilized AWS SDK for JavaScript v3 to interact with DynamoDB. Specifically, to create a DynamoDB Query, you constructed a DynamoDB Scan Command with a FilterExpression to retrieve records from the last N minutes. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/add-lambda.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/add-lambda.md index 2e89fef453..06a7e14c2e 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/add-lambda.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/add-lambda.md @@ -13,7 +13,7 @@ You will now use the AWS Lambda console to retrieve the AWS Lambda endpoint for 1. Go to the AWS Lambda console, and click the `GetAverageTemperature` Lambda function. 2. In the Lambda function dashboard, click the **Configuration** tab and then the **Function URL** as shown below: -![fig2](Figures/02.png) +![fig2](figures/02.png) 3. Under the Function URL, click the **Create Function URL** button. 4. In the window that appears select NONE, scroll down to Additional Settings, and check **Configure cross-origin resource sharing (CORS)**. @@ -21,7 +21,7 @@ You will now use the AWS Lambda console to retrieve the AWS Lambda endpoint for The function URL will appear as follows: -![fig3](Figures/03.png) +![fig3](figures/03.png) Copy the link, and use it to replace the YOUR_API_GATEWAY_ENDPOINT_URL placeholder in the **index.js** file as follows: @@ -42,5 +42,5 @@ document.getElementById('fetchTemperatureButton').addEventListener('click', func Save the file, and open **index.html**. Then, click the **Get temperature button** and the average temperature will appear as shown below: -![fig4](Figures/04.png) +![fig4](figures/04.png) diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/deploy.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/deploy.md index 17df6ef5bf..4089ffbb17 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/deploy.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/deploy.md @@ -85,7 +85,7 @@ aws s3 website s3:/// --index-document index.html Finally, access the website by typing **http://.s3-website-.amazonaws.com**, where is the name of your bucket and stands for the region you're using (here that is eu-central-1): -![fig5](Figures/05.png) +![fig5](figures/05.png) ## Summary In this learning path you have learned how to create a simple static website that fetches and displays temperature data from an AWS Lambda function. The website consists of an HTML file (**index.html**), a CSS file (**styles.css**), and a JavaScript file (**index.js**). The JavaScript code within the website sends an HTTP request to an AWS Lambda function, which processes the request and returns the temperature data in a JSON format. This data is then displayed on the webpage. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_aws_iot_s3/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_aws_iot_s3/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/static-website.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/static-website.md index 3016309f49..778fe3b466 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/static-website.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_s3/static-website.md @@ -139,6 +139,6 @@ document.getElementById('fetchTemperatureButton').addEventListener('click', func After saving all the files, open the **index.html** file in a web browser. It should render as follows: -![fig1](Figures/01.png) +![fig1](figures/01.png) The website is now ready. However, to fetch temperatures, you will need to add the Lambda endpoint. You will configure this in the next section. diff --git a/content/learning-paths/laptops-and-desktops/win_cef/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_cef/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_cef/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_cef/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_cef/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_cef/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_cef/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_cef/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_cef/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_cef/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_cef/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_cef/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_cef/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_cef/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_cef/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_cef/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_cef/how-to-1.md b/content/learning-paths/laptops-and-desktops/win_cef/how-to-1.md index 47156388f9..9850222ee7 100644 --- a/content/learning-paths/laptops-and-desktops/win_cef/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/win_cef/how-to-1.md @@ -15,7 +15,7 @@ In this learning path, you will learn how to create a desktop application for Wi Before you begin, install the following: 1. CMake for Arm64 [link](https://github.com/Kitware/CMake/releases/download/v3.28.1/cmake-3.28.1-windows-arm64.msi). During an installation check **Add CMake to the system PATH for the current user** as shown below -![fig1](Figures/01.png) +![fig1](figures/01.png) 2. Visual Studio 2022 with Desktop development with C++ workload @@ -103,7 +103,7 @@ Let's run the `cefsimple` application. The binaries can be found under the `buil By default, the application renders the Google homepage: -![fig2](Figures/02.png) +![fig2](figures/02.png) ## Modify the application You will now modify the `cefsimple` application to render a custom page. This page will fetch data from `JSONPlaceholder` and display it in a table. @@ -221,7 +221,7 @@ You will now create a custom index.html file to fetch the data from the JSONPlac Re-run the application and you will see the following: -![fig3](Figures/03.png) +![fig3](figures/03.png) This example demonstrates the versatility and power of CEF applications. One of their key strengths is the ability to modify the rendered content dynamically. This means you can change what's displayed in the application, like updating the HTML or CSS, without having to rebuild the entire application each time. This feature significantly streamlines the development process, making it quicker and more efficient. @@ -296,7 +296,7 @@ button:hover { After re-running the application you will see the following: -![fig4](Figures/04.png) +![fig4](figures/04.png) ## Summary CEF is a powerful tool for integrating web browser functionalities into desktop applications, utilizing modern web technologies. You have learned how to setup a project, create a basic application, and use CMake for CEF projects. Additionally, you have explored more advanced features like fetching and displaying data from JSONPlaceholder, adding and styling HTML elements like tables and buttons through separate CSS files all on your Windows on Arm machine. diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/07.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/07.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/08.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/08.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/09.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/09.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/09.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/09.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/10.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/10.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/10.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/10.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/11.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/11.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/11.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/11.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/12.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/12.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/12.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/12.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/Figures/13.png b/content/learning-paths/laptops-and-desktops/win_forms/figures/13.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_forms/Figures/13.png rename to content/learning-paths/laptops-and-desktops/win_forms/figures/13.png diff --git a/content/learning-paths/laptops-and-desktops/win_forms/how-to-1.md b/content/learning-paths/laptops-and-desktops/win_forms/how-to-1.md index 60890595ca..1363a4d065 100644 --- a/content/learning-paths/laptops-and-desktops/win_forms/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/win_forms/how-to-1.md @@ -21,11 +21,11 @@ Before you begin, install Visual Studio 2022 with .NET Desktop Development workl ## Create the project Start by creating the project. Open Visual Studio and then click 'Create a new project'. -![fig1](Figures/01.png) +![fig1](figures/01.png) In the 'Create a new project' window, select the 'Windows Forms App' template. -![fig2](Figures/02.png) +![fig2](figures/02.png) This will open the 'Configure your new project' view, in which you should configure the project as follows (refer to the figure below): @@ -34,11 +34,11 @@ This will open the 'Configure your new project' view, in which you should config 3. Check the option **Place solution and project in the same directory** 4. Click the **Next** button -![fig3](Figures/03.png) +![fig3](figures/03.png) In the final step, 'Additional Information', select **.NET 8.0 (Long-Term Support)** from the 'Framework' dropdown list. Then, click the **Create** button. -![fig4](Figures/04.png) +![fig4](figures/04.png) Your project is now ready. Next, you will create the user interface and then implement the application logic. @@ -52,12 +52,12 @@ To create the user interface proceed as follows: * one ListBox 3. Position the controls as shown in the figure below: -![fig5](Figures/05.png) +![fig5](figures/05.png) 4. Right-click the first label and select the Properties from the context menu 5. In the Properties window, change the (Name) property to **labelArchitecture**: -![fig6](Figures/06.png) +![fig6](figures/06.png) 6. Similarly, rename the ListBox from **listBox1** to **listBoxResults** 7. Modify the properties of the first NumericUpDown control as follows: @@ -221,6 +221,6 @@ When the application is running and the user clicks the Start button, the applic ## Run the application The application is now ready to run. To do this, click 'Debug' > 'Start Debugging'. Visual Studio will then build and launch the application. Afterwards, click 'Start'. Then, change the matrix size to 200 and click the 'Start' button again. Repeat this process for matrix sizes of 300, 400, and 500. The results should appear as shown in the following figure: -![fig7](Figures/07.png) +![fig7](figures/07.png) By default, the application uses the Arm64 configuration. For each matrix size, you will observe the computation time. In the next step, you will learn how to change the build configuration to x64. This will allow you to compare the results with those obtained using x64. diff --git a/content/learning-paths/laptops-and-desktops/win_forms/how-to-2.md b/content/learning-paths/laptops-and-desktops/win_forms/how-to-2.md index 60c985f32c..f7d347edc0 100644 --- a/content/learning-paths/laptops-and-desktops/win_forms/how-to-2.md +++ b/content/learning-paths/laptops-and-desktops/win_forms/how-to-2.md @@ -11,30 +11,30 @@ In this section, you will change the build configuration of the application and ## Creating new build configurations To change the build configuration, click the target platform dropdown (by default, it displays 'Any CPU') and select 'Configuration Manager...': -![fig8](Figures/08.png) +![fig8](figures/08.png) In the Configuration Manager, select '' from the Active solution platform dropdown: -![fig9](Figures/09.png) +![fig9](figures/09.png) This will open the 'New Solution Platform' window, where you should select ARM64 from the 'Type or select the new platform' dropdown: -![fig10](Figures/10.png) +![fig10](figures/10.png) Then, click the OK button. Similarly, create the x64 solution platform ## Comparing the performance You will now compare the computation performance on x64 and Arm64 platforms. First, start the application in Release mode and architecture set to x64: -![fig11](Figures/11.png) +![fig11](figures/11.png) Once the application has started, run calculations for the following matrix sizes: 100, 200, 300, 400, and 500. You should see results similar to those in the following figure. -![fig12](Figures/12.png) +![fig12](figures/12.png) Next, launch the application for the Arm64 platform. Run the matrix multiplication for the same matrix sizes as above and observe the computation times: -![fig13](Figures/13.png) +![fig13](figures/13.png) By comparing the execution times, we observe that, on average, Arm64 provides almost a 30% performance improvement over x64. diff --git a/content/learning-paths/laptops-and-desktops/win_net8/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_net8/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_net8/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_net8/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_net8/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_net8/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_net8/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_net8/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_net8/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_net8/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_net8/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_net8/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_net8/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_net8/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_net8/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_net8/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_net8/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_net8/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_net8/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_net8/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_net8/how-to-1.md b/content/learning-paths/laptops-and-desktops/win_net8/how-to-1.md index 55abad63df..5c2ac50369 100644 --- a/content/learning-paths/laptops-and-desktops/win_net8/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/win_net8/how-to-1.md @@ -95,10 +95,10 @@ dotnet run -c Release -f net8.0 The command will build and run the performance benchmarks. The output will look as follows: -![fig1](Figures/01.png) +![fig1](figures/01.png) The output lists all available benchmarks. You can type * to run all benchmarks or type a number to select individual performance tests. In this example, let's type **483** to execute the Perf_Doubles benchmark. After a while you will see a summary of the performance test. A portion of this output is shown in the figure below: -![fig2](Figures/02.png) +![fig2](figures/02.png) By default, the table summarizes the performance test results. You can see each performance test's execution time and statistics (mean, median, minimum, maximum, and standard deviation) which gives you a comprehensive overview of code performance. diff --git a/content/learning-paths/laptops-and-desktops/win_net8/how-to-2.md b/content/learning-paths/laptops-and-desktops/win_net8/how-to-2.md index 8047f083d0..1e4aea1b78 100644 --- a/content/learning-paths/laptops-and-desktops/win_net8/how-to-2.md +++ b/content/learning-paths/laptops-and-desktops/win_net8/how-to-2.md @@ -32,7 +32,7 @@ dotnet run -c Release Wait a few moments for the application to execute all the benchmarks and you will then see the results of the following form: -![fig3](Figures/03.png) +![fig3](figures/03.png) You will now compare execution times to x64 architecture (emulator on arm64) by typing: @@ -42,13 +42,13 @@ dotnet run -c Release -a x64 The above command will lead to significantly longer execution times than before: -![fig4](Figures/04.png) +![fig4](figures/04.png) The operations all take more time on the emulated x64 than when you execute them natively on Arm64. On average, native execution provides about a 19% performance improvement for list sorting, 11% for matrix multiplication, and 35% for string operations. The following chart summarizes the mean execution times for x64 and Arm64’s natively executed code. -![fig4](Figures/05.png) +![fig4](figures/05.png) ### Summary In this learning path you have learned how to benchmark .NET applications using both the default and custom benchmarks. By benchmarking the custom code, we demonstrated the performance boost when you natively execute the .NET code on an Arm64-powered device. diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/07.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/07.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/08.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/08.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/Figures/09.png b/content/learning-paths/laptops-and-desktops/win_winui3/figures/09.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_winui3/Figures/09.png rename to content/learning-paths/laptops-and-desktops/win_winui3/figures/09.png diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/how-to-1.md b/content/learning-paths/laptops-and-desktops/win_winui3/how-to-1.md index 77022c040c..5d8d88f8e2 100644 --- a/content/learning-paths/laptops-and-desktops/win_winui3/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/win_winui3/how-to-1.md @@ -25,20 +25,20 @@ Before you begin the implementation, install Visual Studio 2022 with the followi 2. Universal Windows Platform development 3. After selecting these workloads, expand the .NET desktop development group under the Installation details and ensure that the 'Windows App SDK C# Templates' option is selected. -![fig1](Figures/01.png) +![fig1](figures/01.png) Then, click the 'Individual components' tab and check '.NET 6.0 Runtime (Long Term Support)'. -![fig2](Figures/02.png) +![fig2](figures/02.png) ## Create the project Open Visual Studio and click 'Create a new project'. -![fig3](Figures/03.png) +![fig3](figures/03.png) In the next window, search for the 'Blank App, Packaged (WinUI 3 in Desktop)' template. Select this template and click the 'Next' button. -![fig4](Figures/04.png) +![fig4](figures/04.png) This action opens the 'Configure your new project' window. Here, you should: @@ -47,7 +47,7 @@ This action opens the 'Configure your new project' window. Here, you should: 3. Ensure the 'Place solution and project in the same directory' option is checked 4. Click the 'Create' button -![fig5](Figures/05.png) +![fig5](figures/05.png) Your project should now be ready. Next, you will design the view using XAML declarations and implement the logic using the C# code. @@ -116,7 +116,7 @@ There are four style declarations, which differ by the 'TargetType' attribute. T Next, you will declare the following user interface: -![fig6](Figures/06.png) +![fig6](figures/06.png) This view uses a tabular layout, comprising five rows and two columns. To create such a layout using XAML, you can use the Grid control. Open the `MainWindow.xaml` file and modify it as follows: diff --git a/content/learning-paths/laptops-and-desktops/win_winui3/how-to-2.md b/content/learning-paths/laptops-and-desktops/win_winui3/how-to-2.md index 695e5ad52a..f0c5818261 100644 --- a/content/learning-paths/laptops-and-desktops/win_winui3/how-to-2.md +++ b/content/learning-paths/laptops-and-desktops/win_winui3/how-to-2.md @@ -11,18 +11,18 @@ In this section, you will launch the application using different settings to com ## Launching the application To run the application, use the dropdown lists in Visual Studio: -![fig7](Figures/07.png) +![fig7](figures/07.png) Ensure you change the 'Configuration mode' to 'Release'. Then, select the architecture, either 'x64' or 'ARM64', and click on 'Arm64.WinUIApp (Package)'. ## Compare the performance Now, you will compare the computation performance on x64 and ARM64 platforms. First, launch the application for x64. After it starts, perform calculations for the following matrix sizes: 100, 200, 300, 400, and 500. The results should resemble those in the figure below: -![fig8](Figures/08.png) +![fig8](figures/08.png) Next, launch the application for the ARM64 platform. Execute matrix multiplication for the same matrix sizes as above and note the computation times: -![fig9](Figures/09.png) +![fig9](figures/09.png) Upon comparing the execution times, it is seen that ARM64, on average, provides almost a 50% performance improvement over x64. diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/07.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/07.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/08.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/08.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/09.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/09.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/09.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/09.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/10.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/10.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/10.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/10.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/11.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/11.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/11.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/11.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/12.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/12.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/12.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/12.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/Figures/13.png b/content/learning-paths/laptops-and-desktops/win_wpf/figures/13.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_wpf/Figures/13.png rename to content/learning-paths/laptops-and-desktops/win_wpf/figures/13.png diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/how-to-1.md b/content/learning-paths/laptops-and-desktops/win_wpf/how-to-1.md index adb19efc42..015106fd51 100644 --- a/content/learning-paths/laptops-and-desktops/win_wpf/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/win_wpf/how-to-1.md @@ -22,11 +22,11 @@ You can find the complete project code used in this learning path [here](https:/ ## Create the project You will now create the WPF project. Open Visual Studio and click **Create a new project**: -![fig1](Figures/01.png) +![fig1](figures/01.png) In the next window, search for the WPF Application template: -![fig2](Figures/02.png) +![fig2](figures/02.png) This will open the 'Configure your new project' view, in which you should configure the project as follows: @@ -35,11 +35,11 @@ This will open the 'Configure your new project' view, in which you should config 3. Check the option **Place solution and project in the same directory** 4. Click the **Next** button -![fig3](Figures/03.png) +![fig3](figures/03.png) In the last step, under Additional Information, choose the **.NET 8.0 (Long Term Support)** option. Finally, click the **Create** button. -![fig4](Figures/04.png) +![fig4](figures/04.png) Your project should be now ready. @@ -56,7 +56,7 @@ Proceed as follows: 1. Open the Solution Explorer in Visual Studio, right-click on the Arm64.DesktopApp.WPF project, and then select Add -> New Folder. Rename the folder to Helpers. 2. Right-click the Helpers folder, and select Add -> New Item... This will open the Add New Item window, where you should type PerformanceHelper.cs and click the Add button: -![fig5](Figures/05.png) +![fig5](figures/05.png) 3. Modify **PerformanceHelper.cs** as follows: ```cs @@ -143,7 +143,7 @@ Before starting the UI design, install the Syncfusion.SfChart.WPF NuGet package 2. In Solution Explorer, right-click on Dependencies under the Arm64.DesktopApp.WPF project. From the context menu, select Manage NuGet Packages... to open the NuGet Package Manager. 3. In the NuGet Package Manager, search for Syncfusion Chart WPF. Then select Syncfusion.SfChart.WPF from the results and click the Install button. Refer to the figure below for guidance: -![fig6](Figures/06.png) +![fig6](figures/06.png) 4. A Preview Changes window will appear. Click the Apply button to confirm and proceed with the changes to the solution. The installation of the NuGet package will complete. @@ -288,7 +288,7 @@ The chart is configured with two numerical axes. The horizontal (primary) axis d The complete XAML declaration results in the following view: -![fig7](Figures/07.png) +![fig7](figures/07.png) ## Application Logic You will now implement the application logic so that the computations will be launched when the user clicks the **Run calculations** button. Follow these steps: diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/how-to-2.md b/content/learning-paths/laptops-and-desktops/win_wpf/how-to-2.md index afca1b3898..af83d4c117 100644 --- a/content/learning-paths/laptops-and-desktops/win_wpf/how-to-2.md +++ b/content/learning-paths/laptops-and-desktops/win_wpf/how-to-2.md @@ -14,29 +14,29 @@ To prepare an application for ARM64 and x86_64 builds, proceed as follows: 1. Click the **Any CPU** drop-down menu and select Configuration Manager...: -![fig8](Figures/08.png) +![fig8](figures/08.png) 2. In the Configuration Manager, click New... from the **Active Solution Platform** drop-down menu: -![fig9](Figures/09.png) +![fig9](figures/09.png) 3. In the **New Solution Platform** window, select ARM64, and click the OK button, as shown below: -![fig10](Figures/10.png) +![fig10](figures/10.png) 4. Repeat these steps to create the x86_64 solution platform. To build and run the application, select the **solution platform** (for example, x86_64) and then click the **Arm64.Desktop.WPF button**: -![fig11](Figures/11.png) +![fig11](figures/11.png) When the application launches, type the execution count value, e.g., 2000 and click the "Run calculations" button. The application will display the column with the label denoting the computation time. Repeat this for other execution counts: 4000, 6000, 8000 and 10000. Your results should look like the following figure: -![fig12](Figures/12.png) +![fig12](figures/12.png) Now, relaunch the application for the ARM64 solution platform and observe the computation times: -![fig13](Figures/13.png) +![fig13](figures/13.png) On average, we have achieved about a 30% reduction in computation times. diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/01.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/01.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/01.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/01.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/02.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/02.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/02.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/02.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/03.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/03.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/03.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/03.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/04.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/04.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/04.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/04.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/05.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/05.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/05.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/05.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/06.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/06.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/06.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/06.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/07.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/07.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/07.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/07.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/08.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/08.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/08.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/08.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/09.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/09.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/09.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/09.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/10.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/10.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/10.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/10.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/11.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/11.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/11.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/11.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/12.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/12.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/12.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/12.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/13.png b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/13.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/win_xamarin_forms/Figures/13.png rename to content/learning-paths/laptops-and-desktops/win_xamarin_forms/figures/13.png diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-1.md b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-1.md index e09c46bde6..8dcfc92c55 100644 --- a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-1.md +++ b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-1.md @@ -21,11 +21,11 @@ Before you begin the implementation, install Visual Studio 2022 with the followi ## Create the project Open Visual Studio and click 'Create a new project'. -![fig1](Figures/01.png) +![fig1](figures/01.png) In the next window, search for the 'Mobile App (Xamarin.Forms)' template. -![fig2](Figures/02.png) +![fig2](figures/02.png) This will open the 'Configure your new project' view, in which you should configure the project as follows (refer to the figure below): @@ -34,21 +34,21 @@ This will open the 'Configure your new project' view, in which you should config 3. Check the option **Place solution and project in the same directory** 4. Click the **Create** button -![fig3](Figures/03.png) +![fig3](figures/03.png) In the last step, 'New Mobile App', choose the **Blank** option in the 'Select a template for your app' list. Then, check the Android, iOS and Windows (UWP) boxes under the 'I plan to develop for:'. Finally, click the **Create** button. -![fig4](Figures/04.png) +![fig4](figures/04.png) The project creation process may take a few moments. During this process, you might be prompted to enable Developer Mode for Windows. If so, follow the instructions shown to enable Developer Mode: -![fig5](Figures/05.png) +![fig5](figures/05.png) Then, accept any other license terms that appear, including Android SDK - License Agreement. Your project should be now ready. Next, open the Solution Explorer (View -> Solution Explorer) to view the created projects: -![fig6](Figures/06.png) +![fig6](figures/06.png) There are four projects: 1. Arm64.MobileApp.XamarinForms - this is the project, containing the shared code. This project is referenced by other projects. @@ -69,7 +69,7 @@ Before starting the UI design, install the `Syncfusion.SfChart.WPF` NuGet packag 2. In Solution Explorer, right-click on 'Dependencies' under the Arm64.MobileApp.XamarinForms project. From the context menu, select 'Manage NuGet Packages...' to open the NuGet Package Manager. 3. In the NuGet Package Manager, search for 'Sf chart.' Then select 'Syncfusion.SfChart.WPF' from the results and click the 'Install' button. Refer to the figure below for guidance: -![fig7](Figures/07.png) +![fig7](figures/07.png) 4. A 'Preview Changes' window will appear. Click the 'Apply' button to confirm and proceed with the changes to the solution. The installation of the NuGet package will complete. 5. Finally, in the 'License Acceptance' window that appears, click the 'I Accept' button to agree to the terms and complete the installation process. @@ -166,7 +166,7 @@ The chart is configured with two numerical axes. The horizontal (primary) axis d The complete XAML declaration results in the following view: -![fig8](Figures/08.png) +![fig8](figures/08.png) To use the chart control on UWP you will also need to make one more modification: 1. In Solution Explorer go to `Arm64.MobileApp.XamarinForms.UWP` project. diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-2.md b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-2.md index 1d28148c70..e89d2ae731 100644 --- a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-2.md +++ b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/how-to-2.md @@ -294,15 +294,15 @@ You can now run the application to demonstrate the performance differences betwe 1. Click on the platform dropdown in Visual Studio and select 'Configuration Manager...' -![fig9](Figures/09.png) +![fig9](figures/09.png) 2. In the 'Configuration Manager' window, open the 'Active solution platform' dropdown and select -![fig10](Figures/10.png) +![fig10](figures/10.png) 3. In the 'New Solution Platform' dialog, choose 'ARM64' from the list and click the 'OK' button -![fig11](Figures/11.png) +![fig11](figures/11.png) After setting up both platforms, start by running the application using the x64 platform: @@ -311,7 +311,7 @@ After setting up both platforms, start by running the application using the x64 Now, launch the application using x64 platform. Click the Run calculations button several times, and then click the Plot results button. You should get the following: -![fig12](Figures/12.png) +![fig12](figures/12.png) Next, switch to the ARM64 platform and repeat the process: @@ -320,7 +320,7 @@ Next, switch to the ARM64 platform and repeat the process: You should notice shorter computation times on the ARM64 platform, demonstrating its performance advantage: -![fig13](Figures/13.png) +![fig13](figures/13.png) ## Summary In this learning path, you developed an application using Xamarin.Forms, allowing it to run on multiple platforms while sharing the same codebase. The application included functionality to measure and compare computation times, specifically focusing on matrix multiplication performance. By running the application on both x64 and Arm64 platforms, you were able to observe and compare the performance. The results showed a noticeable performance advantage on the Arm64 architecture, highlighting its efficiency in handling computation-intensive tasks. diff --git a/content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/actions.png b/content/learning-paths/laptops-and-desktops/windows_cicd_github/images/actions.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/actions.png rename to content/learning-paths/laptops-and-desktops/windows_cicd_github/images/actions.png diff --git a/content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/ghrunner_1.png b/content/learning-paths/laptops-and-desktops/windows_cicd_github/images/ghrunner_1.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/ghrunner_1.png rename to content/learning-paths/laptops-and-desktops/windows_cicd_github/images/ghrunner_1.png diff --git a/content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/ghrunner_2.png b/content/learning-paths/laptops-and-desktops/windows_cicd_github/images/ghrunner_2.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/ghrunner_2.png rename to content/learning-paths/laptops-and-desktops/windows_cicd_github/images/ghrunner_2.png diff --git a/content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/ghrunner_3.png b/content/learning-paths/laptops-and-desktops/windows_cicd_github/images/ghrunner_3.png similarity index 100% rename from content/learning-paths/laptops-and-desktops/windows_cicd_github/Images/ghrunner_3.png rename to content/learning-paths/laptops-and-desktops/windows_cicd_github/images/ghrunner_3.png diff --git a/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-run.md b/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-run.md index 75d124cf81..4452e19a5c 100644 --- a/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-run.md +++ b/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-run.md @@ -13,7 +13,7 @@ To test this CI/CD workflow, select `Actions` from the repository menu. You will be presented with the option to start with a `Simple workflow` that gives you a file with the minimum necessary structure to build, test and deploy your code. Click `Configure`. -![ghrunner3 #center](Images/ghrunner_3.png) +![ghrunner3 #center](images/ghrunner_3.png) A file named `blank.yml` will be created for you under `/.github/workflows/`. Optionally rename this to something more meaningful (`hello.yml`). @@ -44,6 +44,6 @@ In the `PowerShell` window, you will see the progress of the Action: ``` Similarly in the `Actions` tab of your repository: -![actions #center](Images/actions.png) +![actions #center](images/actions.png) Congratulations, you have successfully run a simple GitHub Actions CI/CD workflow on your Windows on Arm machine. diff --git a/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-setup.md b/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-setup.md index e70e63debf..31aa7c22d8 100644 --- a/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-setup.md +++ b/content/learning-paths/laptops-and-desktops/windows_cicd_github/runner-setup.md @@ -40,6 +40,6 @@ Current runner version: '2.303.0' Return to the `Actions->Runners` page in your GitHub repository and you should now see your runner displayed similar to the below: -![ghrunner2 #center](Images/ghrunner_2.png) +![ghrunner2 #center](images/ghrunner_2.png) You are now ready to test a simple CI/CD GitHub Actions workflow on your Windows on Arm runner. diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/add-camera.md b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/add-camera.md index 08fbb56ed8..9e541e40ef 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/add-camera.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/add-camera.md @@ -88,7 +88,7 @@ To modify the application view, open `activity_main.xml` and replace the file co After adding this code, re-run the application. The above declarations render the following: -![img8](Figures/08.png) +![img8](figures/08.png) The view now contains two buttons: Start and Stop. They will be used to start and stop the camera preview. Below the buttons, there is a checkbox that will enable or disable real-time image processing. Though not visible, below the checkbox there is a `JavaCameraView`. @@ -145,7 +145,7 @@ override fun onCreate(savedInstanceState: Bundle?) { The above code checks if the app has the CAMERA permission. If the permission is not granted, it requests the permission using `ActivityCompat.requestPermissions`, passing the `Manifest.permission.CAMERA` and the cameraPermissionRequestCode (100). When you re-run the application, you should see the following screen: -![img9](Figures/09.png) +![img9](figures/09.png) Click the **While using the app** button to enable camera access. You are now ready to capture images from the camera. @@ -392,7 +392,7 @@ class MainActivity : AppCompatActivity(), CameraBridgeViewBase.CvCameraViewListe After re-launching the application and pressing the Start button you will see the application is accessing the camera. However, the images are not yet displayed. -![img10](Figures/10.png) +![img10](figures/10.png) ## Display images To display images, you need to modify the onCameraFrame method so that the image from the camera is displayed in the ImageView. You will also declare Mat objects to store the image from the camera. @@ -458,4 +458,4 @@ To test the code, you will need to run the application on an actual device. Foll After running the app, remember to grant permissions to the camera. Then, click the Start button, and the application will display images from the camera: -![img11](Figures/11.jpg) +![img11](figures/11.jpg) diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/create-project.md b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/create-project.md index aeda0c3260..51bca84c0c 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/create-project.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/create-project.md @@ -13,11 +13,11 @@ Follow these steps to create a project and add OpenCV: 1. Open Android Studio on your development machine and then click the **+ New Project** icon: -![img1](Figures/01.png) +![img1](figures/01.png) 2. In the New Project window, select **Empty Views Activity**: -![img2](Figures/02.png) +![img2](figures/02.png) 3. Configure the project as follows (see figure below): - Name: **Arm64.OpenCV.Camera**. @@ -27,13 +27,13 @@ Follow these steps to create a project and add OpenCV: - Minimum SDK: **API 24**. - Build configuration language: **Kotlin DSL**. -![img3](Figures/03.png) +![img3](figures/03.png) 4. Click the **Finish** button. The project will be ready in a few moments, and Android Studio should appear as shown below: -![img4](Figures/04.png) +![img4](figures/04.png) ## Add OpenCV support To add OpenCV for Arm64, open the *build.gradle.ts (Module: app)*, and add the following line under the dependencies: @@ -44,7 +44,7 @@ implementation("org.opencv:opencv:4.10.0") Then, click the **Sync Now** link in the top pane that appears: -![img5](Figures/05.png) +![img5](figures/05.png) From here on, you can use OpenCV in your application. @@ -56,7 +56,7 @@ To initialize OpenCV and check the initialization status, follow these steps: 2. Click the highlighted icon in the top-right corner to switch to the XML view. -![img6](Figures/06.png) +![img6](figures/06.png) 3. Modify the `activity_main.xml` file as shown below: @@ -129,6 +129,6 @@ Finally, you call updateControls(), which updates the UI based on whether OpenCV ## Launch the application You can now launch the application in the simulator. To do so, use the top menu, where you click Run -> Run ‘app’. The application should start, and you should see the “OpenCV initialized” text as shown below: -![img7](Figures/07.png) +![img7](figures/07.png) You have successfully added OpenCV to the Android application. In the next step, you will extend the application to get images from the device’s camera and process them in real-time. diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/01.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/01.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/01.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/01.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/02.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/02.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/02.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/02.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/03.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/03.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/03.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/03.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/04.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/04.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/04.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/04.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/05.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/05.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/05.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/05.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/06.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/06.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/06.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/06.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/07.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/07.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/07.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/07.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/08.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/08.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/08.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/08.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/09.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/09.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/09.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/09.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/10.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/10.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/10.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/10.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/11.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/11.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/11.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/11.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/12.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/12.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/Figures/12.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/figures/12.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/process-images.md b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/process-images.md index c8fb21d2c0..3984ff22b6 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/process-images.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_camera/process-images.md @@ -88,5 +88,5 @@ You use 21 as the size of the block to use for calculating the threshold for eac After re-running the application, starting the preview, and enabling the processing, you should see the following result: -![img12](Figures/12.jpg) +![img12](figures/12.jpg) diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/create-project.md b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/create-project.md index e007a76564..c53ac71805 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/create-project.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/create-project.md @@ -16,11 +16,11 @@ Follow these steps to create a project and add OpenCV: 1. Open Android Studio on your development machine and then click the **+ New Project** icon: -![img1](Figures/01.png) +![img1](figures/01.png) 2. In the New Project window, select **Empty Views Activity**: -![img2](Figures/02.png) +![img2](figures/02.png) 3. Configure the project as follows (see figure below): - Name: **Arm64.OpenCV.FaceDetection**. @@ -30,7 +30,7 @@ Follow these steps to create a project and add OpenCV: - Minimum SDK: **API 24**. - Build configuration language: **Kotlin DSL**. -![img3](Figures/03.png) +![img3](figures/03.png) 4. Click the **Finish** button. @@ -51,7 +51,7 @@ You will now initialize OpenCV and prepare the application to stream frames from 1. Under the Project in the left window, double-click *app/res/layout/activity_main.xml*. This opens the view designer. 2. Click the highlighted icon in the top right corner to switch to the XML view. -![img4](Figures/04.png) +![img4](figures/04.png) 3. Modify the *activity_main.xml* as shown below: @@ -218,7 +218,7 @@ The above code also declares CameraBridgeViewBase.CvCameraViewListener2 methods: ## Launch the application You have just prepared the application to stream camera frames. You can now launch the application in the emulator. To do so, click the green 'run' button as shown here: -![click run](Figures/05.png) +![click run](figures/05.png) The application should start, and you should see the app running in an emulator. It will ask you for camera permissions. diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/face-detection.md b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/face-detection.md index f68a8e362b..44d3e030b1 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/face-detection.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/face-detection.md @@ -194,7 +194,7 @@ If at least one face is detected, you retrieve the first detected face, and base After running the application, click the Start button, and you will see a green rectangle around the detected face as shown below: -![img5](Figures/06.jpg) +![img5](figures/06.jpg) ## Summary In this Learning Path, you learned how to use the Haar cascade classifier to detect faces in camera images. You added the pre-trained Haar cascade XML file for face detection to the project’s assets directory. You implemented the loadHaarCascade method to load the Haar cascade file from the assets and initialize the CascadeClassifier. You also created the getPath method to convert the Haar cascade asset file into a file path that can be used by OpenCV. diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/01.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/01.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/01.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/01.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/02.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/02.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/02.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/02.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/03.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/03.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/03.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/03.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/04.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/04.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/04.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/04.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/05.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/05.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/05.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/05.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/06.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/06.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/Figures/06.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_facedetection/figures/06.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/create-project.md b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/create-project.md index 9b10a48d17..dd95ee50be 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/create-project.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/create-project.md @@ -16,7 +16,7 @@ Start by creating a new project, and configuring it: 1. Open Android Studio on your development machine, and click the **+ New Project** icon. 2. In the **New Project** window, select **Empty Views Activity**: -![img1 alt-text#center](Figures/01.png "Figure 1: Creating a new project.") +![img1 alt-text#center](figures/01.png "Figure 1: Creating a new project.") 3. Figure 2 shows you how to configure the project: - Name: **Arm64.KleidiCV.Demo**. @@ -26,7 +26,7 @@ Start by creating a new project, and configuring it: - Minimum SDK: **API 24**. - Build configuration language: **Kotlin DSL**. -![img2 alt-text#center](Figures/02.png "Figure 2: Configuring your new project.") +![img2 alt-text#center](figures/02.png "Figure 2: Configuring your new project.") 4. Click the **Finish** button. diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/01.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/01.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/01.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/01.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/02.png b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/02.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/02.png rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/02.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/03.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/03.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/03.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/03.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/04.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/04.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/04.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/04.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/05.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/05.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/05.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/05.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/06.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/06.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/06.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/06.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/07.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/07.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/07.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/07.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/08.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/08.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/08.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/08.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/09.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/09.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/09.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/09.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/10.jpg b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/10.jpg similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/Figures/10.jpg rename to content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/figures/10.jpg diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/process-images.md b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/process-images.md index 325f92f285..fadf9d4fbc 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/process-images.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/process-images.md @@ -398,10 +398,10 @@ When you do so, click the **Load image** button, select the image processing ope You will see the processing results and a detailed performance analysis as Figures 3-6 show. As a reminder, this Learning Path was tested on a Samsung Galaxy S22. -![img3](Figures/03.jpg) -![img4](Figures/04.jpg) -![img5](Figures/05.jpg) -![img6](Figures/06.jpg) +![img3](figures/03.jpg) +![img4](figures/04.jpg) +![img5](figures/05.jpg) +![img6](figures/06.jpg) ## Performance uplift To appreciate the performance uplift offered by KleidiCV, now switch to one of the earliest OpenCV versions, which does not have KleidiCV. @@ -420,10 +420,10 @@ implementation("org.opencv:opencv:4.9.0") Now click the **Sync Now** button, and deploy the app to the Android device. -![img7](Figures/07.jpg) -![img8](Figures/08.jpg) -![img9](Figures/09.jpg) -![img10](Figures/10.jpg) +![img7](figures/07.jpg) +![img8](figures/08.jpg) +![img9](figures/09.jpg) +![img10](figures/10.jpg) This particular example achieves the following performance uplift: diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/01.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/01.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/01.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/01.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/02.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/02.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/02.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/02.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/03.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/03.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/03.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/03.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/04.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/04.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/04.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/04.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/05.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/05.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/05.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/05.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/06.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/06.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/06.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/06.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/07.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/07.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/07.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/07.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/08.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/08.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/08.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/08.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/09.png b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/09.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/android_sve2/Figures/09.png rename to content/learning-paths/mobile-graphics-and-gaming/android_sve2/figures/09.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part1.md b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part1.md index b4f07ec3d6..9a8664e0d6 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part1.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part1.md @@ -28,23 +28,23 @@ The process of creating and configuring this project is similar to that used for 1. Open Android Studio on your development machine and then click the **+ New Project** icon: -![img1](Figures/01.png) +![img1](figures/01.png) 2. Create a project using the **Native C++ Project** option in the Phone and Tablet section and click Next, as shown below: -![img2](Figures/02.png) +![img2](figures/02.png) 3. Set the application name to **ARM SVE2**, select `Kotlin` as the language, leave the Minimum SDK selection as `API 24: Android 7.0 (Nougat)`, ensure that the build configuration is set to use Kotlin DSL, and click Next, as shown below: -![img3](Figures/03.png) +![img3](figures/03.png) 4. Leave the `Toolchain Default` selection for C++ Standard and then click Finish, as shown below: -![img4](Figures/04.png) +![img4](figures/04.png) You have just created the Android application project, which comprises a `MainActivity`. The `MainActivity` contains a single TextView control, which displays the Hello from C++ string as shown in the figure below. This string is retrieved by invoking a `stringFromJNI` function from the C++ library, implemented under the `app/cpp/native-lib.cpp` file and built using the NDK and `CMakeLists.txt`. -![img5](Figures/05.png) +![img5](figures/05.png) SVE2 support is not enabled by default. To enable it, open the `CMakeLists.txt` (under app/cpp), and modify it by adding the following target_compile_options: @@ -160,7 +160,7 @@ There are two elements of the above code snippet that warrant further discussion To enable Kotlin code to access the native library, we need to create so-called JNI functions for the `runCalculations` method. To do this, place the cursor on the line where the `runCalculations` method is declared. Then, press Alt+Enter, and select **Create JNI function for runCalculations** from the menu that appears. -![img6](Figures/06.png) +![img6](figures/06.png) This action will navigate you to the native-lib.cpp file, which you will implement in the next step. diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part2.md b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part2.md index ca06dc4451..c2918b3313 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part2.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_sve2/part2.md @@ -201,13 +201,13 @@ This method generates three pseudo-random vectors of floats and a fourth vector ## Build and Run the code To test the code described above, you will need an Armv8-powered device (for this example, we used a Samsung Galaxy S22). Follow [these steps](https://developer.android.com/studio/run/device) to prepare your device for development. Once your phone is configured, it will appear in the drop-down list at the top of Android Studio. -![img7](Figures/07.png) +![img7](figures/07.png) Select your phone and then click the play icon. This action will build and deploy the application to your device. Next, enter the vector length, for example, 10,000, and click the **Run calculations** button. Repeat the process after selecting the "Use SVE2?" checkbox. You should observe the following results: -![img8](Figures/08.png) +![img8](figures/08.png) -![img9](Figures/09.png) +![img9](figures/09.png) As has just been demonstrated, using SVE2 intrinsics can achieve computation times that are 3-4 times shorter, depending on the vector length. From bb55710b8f4034f98ac91fb9adf3e9389c22b21c Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Wed, 23 Apr 2025 21:35:34 +0000 Subject: [PATCH 083/122] Tech review of vLLM LP --- .../vLLM-quant/1-overview.md | 13 ++++++------ .../vLLM-quant/2-quantize-model.md | 21 ++++++++++++++----- .../vLLM-quant/3-run-benchmark.md | 10 ++++----- .../vLLM-quant/_index.md | 3 ++- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md index fd0766b7b6..14f62397dc 100644 --- a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/1-overview.md @@ -40,8 +40,7 @@ These packages are needed to build libraries like OpenBLAS and manage system-lev ```bash sudo apt-get update -y -sudo apt-get install -y gcc-12 g++-12 libnuma-dev python3-pip -sudo apt install python-is-python3 +sudo apt-get install -y gcc-12 g++-12 libnuma-dev make ``` Set the system default compilers to version 12: @@ -82,12 +81,12 @@ Once the system libraries are in place, install the Python packages required for Before proceeding, make sure the following files are downloaded to your home directory: ```bash - +[PLACEHOLDER] ``` These are required to complete the installation and model quantization steps. Now, navigate to your home directory: ```bash -cd /home/ubuntu/ +cd $HOME ``` Install the vLLM wheel. This wheel contains the CPU-optimized version of `vLLM`, built specifically for Arm architecture. Installing it from a local `.whl` file ensures compatibility with the rest of your environment and avoids potential conflicts from nightly or default pip installations. @@ -101,12 +100,12 @@ pip install llmcompressor ``` Install torchvision (nightly version for CPU): ```bash -pip install --force-reinstall torchvision==0.22.0.dev20250213 --extra-index-url https://download.pytorch.org/whl/nightly/cpu -``` +pip install --force-reinstall torchvision==0.22.0.dev20250223 --extra-index-url https://download.pytorch.org/whl/nightly/cpu``` + Install the custom PyTorch CPU wheel:
This custom PyTorch wheel is prebuilt for Arm CPU architectures and includes the necessary optimizations for running inference. Installing it locally ensures compatibility with your environment and avoids conflicts with default pip packages. ```bash pip install torch-2.7.0.dev20250306-cp312-cp312-manylinux_2_28_aarch64.whl --force-reinstall --no-deps ``` -You’re now ready to quantize the model and start serving it with `vLLM` on an Arm-based system. \ No newline at end of file +You’re now ready to quantize the model and start serving it with `vLLM` on an Arm-based system. diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md index b1c6390807..7a5c745ecd 100644 --- a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/2-quantize-model.md @@ -15,7 +15,7 @@ huggingface-cli login --token $hf_token ``` ## Quantization Script Template -Create the `vllm_quantize_model.py` script shown below to quantize the model : +Using a file editor of your choice, create a file named `vllm_quantize_model.py` and copy the content shown below to quantize the model: ```bash import argparse import os @@ -153,11 +153,10 @@ if __name__ == "__main__": Then run the quantization script using `vllm_quantize_model.py`. This generates an INT8 quantized version of the model using channelwise precision, which reduces memory usage while maintaining model accuracy: ```bash -cd /home/ubuntu/ python vllm_quantize_model.py meta-llama/Llama-3.1-8B-Instruct --mode int8 --scheme channelwise ``` -The output model will be saved locally at: -`/home/ubuntu/Llama-3.1-8B-Instruct-w8a8-channelwise`. +The quantized model will be saved at: +`$HOME/Llama-3.1-8B-Instruct-w8a8-channelwise`. ## Launch the vLLM server @@ -171,8 +170,20 @@ ONEDNN_DEFAULT_FPMATH_MODE=BF16 \ VLLM_TARGET_DEVICE=cpu \ VLLM_CPU_KVCACHE_SPACE=32 \ VLLM_CPU_OMP_THREADS_BIND="0-$(($(nproc) - 1))" \ -vllm serve /home/ubuntu/Llama-3.1-8B-Instruct-w8a8-channelwise \ +vllm serve $HOME/Llama-3.1-8B-Instruct-w8a8-channelwise \ --dtype float32 --swap-space 16 ``` This command starts the vLLM server using the quantized model. It preloads `tcmalloc` for efficient memory allocation and uses OpenBLAS for accelerated matrix operations. Thread binding is dynamically set based on the number of available cores to maximize parallelism on Arm CPUs. +The output from launching the vLLM server with the quantized model should look like: + +```output +INFO 04-23 21:13:59 launcher.py:31] Route: /rerank, Methods: POST +INFO 04-23 21:13:59 launcher.py:31] Route: /v1/rerank, Methods: POST +INFO 04-23 21:13:59 launcher.py:31] Route: /v2/rerank, Methods: POST +INFO 04-23 21:13:59 launcher.py:31] Route: /invocations, Methods: POST +INFO: Started server process [77356] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md index f6eae52a1a..7dab421f50 100644 --- a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/3-run-benchmark.md @@ -8,7 +8,7 @@ layout: learningpathall ## Run Single Inference -Once the server is running, start by verifying it with a basic single-prompt request using `curl`. This confirms the server is running correctly and that the OpenAI-compatible /v1/chat/completions API is responding as expected: +Once the server is running, open another terminal and verify it is running as expected with a basic single-prompt request using `curl`. This confirms the server is running correctly and that the OpenAI-compatible /v1/chat/completions API is responding as expected: ```bash curl http://localhost:8000/v1/chat/completions \ @@ -51,8 +51,8 @@ After confirming single-prompt inference, run batch testing to simulate concurre Use the following Python script to simulate concurrent user interactions. -Save it as `batch_test.py`: -```bash +Save the content shown below in a file named `batch_test.py`: +```python import requests import json import os @@ -151,10 +151,10 @@ if __name__ == "__main__": Then, run it using: ```bash -python batch_test.py localhost 8000 --schema http --batch 16 -m /home/ubuntu/Llama-3.1-8B-Instruct-w8a8-channelwise +python3 batch_test.py localhost 8000 --schema http --batch 16 -m $HOME/Llama-3.1-8B-Instruct-w8a8-channelwise ``` This simulates multiple users interacting with the model in parallel and helps validate server-side performance under load. -You can modify the number of requests using the --batch flag or review/edit batch_test.py to customize prompt content and concurrency logic. +You can modify the number of requests using the --batch flag or review and edit `batch_test.py` to customize prompt content and concurrency logic. When the test completes, server logs will display a summary including average prompt throughput and generation throughput. This helps benchmark how well the model performs under concurrent load on your Arm-based system. diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md index 7cebfcee55..a295ca8b19 100644 --- a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md @@ -15,11 +15,12 @@ learning_objectives: prerequisites: - - An Arm-based server or cloud instance running with at least 32 CPU cores, 64 GB RAM and 80 GB of available disk space. + - An Arm-based server or cloud instance running with at least 32 CPU cores, 64 GB RAM and 32 GB of available disk space. - Familiarity with Python and machine learning concepts. - An active Hugging Face account with access to the target model. author: Rani Chowdary Mandepudi + Phalani Paladugu ### Tags skilllevels: Introductory From 693c77febd908c904c3ff049800607b039bc3050 Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Wed, 23 Apr 2025 17:37:45 -0400 Subject: [PATCH 084/122] Update _index.md --- .../servers-and-cloud-computing/vLLM-quant/_index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md index a295ca8b19..eae573c8a4 100644 --- a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md @@ -1,6 +1,9 @@ --- title: Quantize and Run a Large Language Model using vLLM on Arm Servers +draft: true +cascade: + draft: true minutes_to_complete: 45 From 3d1e62c1740a95fdeb882b7fa56024010facb99c Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Wed, 23 Apr 2025 17:42:35 -0400 Subject: [PATCH 085/122] Update _index.md --- .../win_on_arm_build_onnxruntime/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md index ba0fe78f23..b998e35af4 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md @@ -1,6 +1,10 @@ --- title: Powering Phi-3 on Arm PC with ONNX Runtime on Windows +draft: true +cascade: + draft: true + minutes_to_complete: 60 who_is_this_for: A deep-dive for advanced developers looking to build ONNX Runtime on Windows ARM (WoA) and leverage the Generate() API to run Phi-3 inference with KleidiAI acceleration. From b1cd6747a88b641d1617dacfe94b0909b7ac3f4d Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Wed, 23 Apr 2025 22:02:56 +0000 Subject: [PATCH 086/122] Fixing authors in vllm and addressed issues #1821 and #1822 --- .../embedded-and-microcontrollers/nav-mlek/sw.md | 4 ++-- .../servers-and-cloud-computing/vLLM-quant/_index.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md b/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md index a2011ab0cb..4c1de286a0 100644 --- a/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md +++ b/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md @@ -11,7 +11,7 @@ layout: "learningpathall" You should use an `x86_64` development machine running Windows or Linux for the best experience. -The [Arm ML Evaluation Kit (MLEK)](https://review.mlplatform.org/plugins/gitiles/ml/ethos-u/ml-embedded-evaluation-kit) is not fully supported on Windows. Some of the required tools work only on Linux. Linux is recommended if you plan to use MLEK extensively. +The [Arm ML Evaluation Kit (MLEK)](https://git.mlplatform.org/ml/ethos-u/ml-embedded-evaluation-kit.git/) is not fully supported on Windows. Some of the required tools work only on Linux. Linux is recommended if you plan to use MLEK extensively. There are some ML examples which can be developed using Windows tools. @@ -61,7 +61,7 @@ Use an editor or program such as `more` or `cat` to view the Dockerfile. ## Machine learning Frameworks -[TensorFlow Lite for Microcontrollers (TFLM)](https://www.tensorflow.org/lite/embedded-and-microcontrollers/) is on of the more common framework for microcontroller ML applications. +[LiteRT for Microcontrollers](https://ai.google.dev/edge/litert/microcontrollers/overview), formerly known as TensorFlow Lite for Microcontrollers is on of the more common framework for microcontroller ML applications. TensorFlow uses [`xxd`](https://linux.die.net/man/1/xxd/) to convert TensorFlow Lite models into C data structures. diff --git a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md index eae573c8a4..94a73e92e0 100644 --- a/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/vLLM-quant/_index.md @@ -22,8 +22,9 @@ prerequisites: - Familiarity with Python and machine learning concepts. - An active Hugging Face account with access to the target model. -author: Rani Chowdary Mandepudi - Phalani Paladugu +author: + - Rani Chowdary Mandepudi + - Phalani Paladugu ### Tags skilllevels: Introductory From c4929a6eed854bdaa1ce1b4b396e44e648c38124 Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Wed, 23 Apr 2025 22:04:42 +0000 Subject: [PATCH 087/122] Fixing authors in vllm and addressed issues #1821 and #1822 --- .../learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md b/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md index 4c1de286a0..1f129e9d63 100644 --- a/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md +++ b/content/learning-paths/embedded-and-microcontrollers/nav-mlek/sw.md @@ -61,7 +61,7 @@ Use an editor or program such as `more` or `cat` to view the Dockerfile. ## Machine learning Frameworks -[LiteRT for Microcontrollers](https://ai.google.dev/edge/litert/microcontrollers/overview), formerly known as TensorFlow Lite for Microcontrollers is on of the more common framework for microcontroller ML applications. +[LiteRT for Microcontrollers](https://ai.google.dev/edge/litert/microcontrollers/overview), formerly known as TensorFlow Lite for Microcontrollers is one of the more common framework for microcontroller ML applications. TensorFlow uses [`xxd`](https://linux.die.net/man/1/xxd/) to convert TensorFlow Lite models into C data structures. From ff9efbc93c8299334d454a78db361797b38f7a79 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Wed, 23 Apr 2025 22:03:17 -0500 Subject: [PATCH 088/122] Review MongoDB Learning Path --- .../mongodb/_index.md | 2 +- .../mongodb/automate_setup_pulumi.md | 14 +++--- .../mongodb/benchmark_mongodb-8.0.md | 13 +++--- .../mongodb/create_replica_set.md | 46 +++++++++---------- .../mongodb/perf_mongodb.md | 36 ++++++++------- .../mongodb/replica_set_testing.md | 9 ++-- 6 files changed, 61 insertions(+), 59 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/_index.md b/content/learning-paths/servers-and-cloud-computing/mongodb/_index.md index 02b4f03acd..a529ed6c56 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/_index.md @@ -11,7 +11,7 @@ learning_objectives: - Measure and compare the performance of MongoDB on Arm versus other architectures with Yahoo Cloud Serving Benchmark (YCSB). prerequisites: -- An Arm based instance from a cloud service provider. +- An [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider. armips: - Neoverse diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md b/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md index e522ceae68..95e3778229 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md @@ -20,7 +20,7 @@ Install the python dependencies on your Ubuntu 22.04 machine: sudo apt update sudo apt install python-is-python3 -y sudo apt install python3-pip -y -sudo apt install python3.10-venv +sudo apt install python3.10-venv -y ``` ## Install Pulumi @@ -41,7 +41,7 @@ git clone https://github.com/pbk8s/pulumi-ec2.git ``` ## Build gatord -You would also need the gatord binary for performance analysis. [gator](https://github.com/ARM-software/gator) is a target agent (daemon), part of Arm Streamline, a set of performance analysis tools. Use the following commands to build it from source. +You will also need the gatord binary for performance analysis. [gator](https://github.com/ARM-software/gator) is a target agent (daemon), part of Arm Streamline, a set of performance analysis tools. Use the following commands to build it from source. ```bash git clone https://github.com/ARM-software/gator.git @@ -65,14 +65,14 @@ cp build-native-gcc-rel/gatord ~/pulumi-ec2/ ## Install awscli and set environment variables Use the [awscli](https://learn.arm.com/install-guides/aws-cli/) learning path to install the awscli. -Set the following environment variables on your local computer to connect to your AWS account -```console +Set the following environment variables on your local computer to connect to your AWS account: +```bash export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY= export AWS_SESSION_TOKEN= ``` -Execute the following command to validate the credentials -```console +Execute the following command to validate the credentials: +```bash aws sts get-caller-identity ``` @@ -134,7 +134,7 @@ subnet = aws.ec2.Subnet("p1-subnet", }) ``` -Note: The security groups created by this script are lot less restrictive, to simplify the deployment process and to remove additional complexities. Please modify the ingress/egress rules as per your organizations' policy. +Note: The security groups created by this script are a lot less restrictive, to simplify the deployment process and to remove additional complexities. Please modify the ingress/egress rules as per your organization's policy. ```python group = aws.ec2.SecurityGroup('p1-security-grouup', diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md b/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md index 8723e40ea7..61508cbbb3 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0.md @@ -8,7 +8,7 @@ weight: 4 # (intro is 1), 2 is first, 3 is second, etc. layout: "learningpathall" --- -To further measure the performance of MongoDB, you can run the [Yahoo Cloud Serving Benchmark](http://github.com/brianfrankcooper/YCSB). +To further measure the performance of MongoDB, you can run the [Yahoo Cloud Serving Benchmark](https://github.com/brianfrankcooper/YCSB). YCSB is an open source project which provides the framework and common set of workloads to evaluate the performance of different "key-value" and "cloud" serving stores. Use the steps below to run YCSB to evaluate the performance of MongoDB running on 64-bit Arm machine. @@ -22,9 +22,10 @@ Install the additional software: {{< tab header="Ubuntu" >}} sudo apt install -y maven make gcc {{< /tab >}} - {{< tab header="RHE/Amazon" >}} + {{< tab header="RHEL / Amazon Linux" >}} sudo yum check-update -sudo yum install python2 +# Python 2 may not be available via yum on recent RHEL/Amazon Linux versions. +# If needed, follow the manual installation steps below. {{< /tab >}} {{< /tabpane >}} @@ -38,7 +39,7 @@ wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz tar xvf Python-2.7.18.tgz cd Python-2.7.18 ./configure --enable-optimizations -make -j $nproc +make -j $(nproc) sudo make altinstall sudo ln -s /usr/local/bin/python2.7 /usr/local/bin/python ``` @@ -68,14 +69,14 @@ To load and test the performance of loading data(INSERT) into default database ` ```console ./bin/ycsb load mongodb -s -P workloads/workloada -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 -threads 10 ``` -The "-P" parameter is used to load property files. In this example, you used it load the workloada parameter file which sets the recordcount to 1000 in addition to other parameters. The "-threads" parameter indicates the number of threads and is set to 1 by default. +The "-P" parameter is used to load property files. In this example, you used it load the workloada parameter file which sets the recordcount to 1000 in addition to other parameters. The "-threads" parameter indicates the number of client threads (default is 1); this example uses 10 threads. ## A simple Update/Read/Read Modify Write Test on MongoDB To test the performance of executing a workload which includes running UPDATE, Read Modify Write(RMW) and/or READ operations on the data using 10 threads for example, use the following command: ```console -./bin/ycsb load mongodb -s -P workloads/workloada -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 +./bin/ycsb run mongodb -s -P workloads/workloada -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 -threads 10 ``` The workloads/workloada file in this example sets the following values `readproportion=0.5` and `updateproportion=0.5` which means there is an even split between the number of READ and UPDATE operations performed. You can change the type of operations and the splits by providing your own workload parameter file. diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/create_replica_set.md b/content/learning-paths/servers-and-cloud-computing/mongodb/create_replica_set.md index ea02b48e0b..dff28104d3 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/create_replica_set.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/create_replica_set.md @@ -8,7 +8,7 @@ weight: 3 # (intro is 1), 2 is first, 3 is second, etc. layout: "learningpathall" --- ## MongoDB test scenarios -To test Mongodb you need two parts. A instance running the testing software([YCSB](/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0)). one or more instances running MongoDB in some configuration. The recommended MongoDB test setup is a three node relica set. These three nodes are of equal size with one instance being desigated as the primary node( the target for test traffic ) and the others as secondary nodes. +To test MongoDB you need two parts. An instance running the testing software ([YCSB](/learning-paths/servers-and-cloud-computing/mongodb/benchmark_mongodb-8.0)). One or more instances running MongoDB in some configuration. The recommended MongoDB test setup is a three node replica set. These three nodes are of equal size with one instance being designated as the primary node (the target for test traffic) and the others as secondary nodes. ## What is a replica set? @@ -16,25 +16,25 @@ A replica set is a group of instances that maintain the same dataset. A replica ## What node size should I use? -The most common size for testing MongoDB is an 8 vCPU instance. You can test with any sized instance, but if you are looking for ideal testing conditions, 8 vCPUs is enough. Each node should have atleast 32GB of RAM. +The most common size for testing MongoDB is an 8 vCPU instance. You can test with any sized instance, but if you are looking for ideal testing conditions, 8 vCPUs is enough. Each node should have at least 32GB of RAM. -To achieve the best results, its recommended to keep the complete data set in memory. If you see disk access when running tests, increase the RAM size of your instances. Additional details about the recommended configuration are provided below. +To achieve the best results, it's recommended to keep the complete data set in memory. If you see disk access when running tests, increase the RAM size of your instances. Additional details about the recommended configuration are provided below. ## Creating replica sets -You can create replica sets of any size(two is the minimum). Three is recemmended but you can add as many as you like. +You can create replica sets of any size (two is the minimum). Three is recommended but you can add as many as you like. ## Three node replica sets -To creating a three node replica set, start by launching three [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) of equal size. +To create a three node replica set, start by launching three Arm-based instances of equal size. -[install](/learning-paths/servers-and-cloud-computing/mongodb/run_mongodb) Mongodb on all three instances. +[Install](/learning-paths/servers-and-cloud-computing/mongodb/run_mongodb) MongoDB on all three instances. -Once all three instances are up and running. Modify the service and configuration file for all instances. +Once all three instances are up and running, modify the service and configuration file for all instances. ## Modify the MongoDB configuration -Use a text editor to edit the file `/etc/mongodb.conf` and replace the contents of the file with the text below. +Use a text editor to edit the file `/etc/mongod.conf` and replace the contents of the file with the text below. ```console # Configuration Options: https://docs.mongodb.org/manual/reference/configuration-options/ @@ -50,10 +50,10 @@ storage: engine: wiredTiger wiredTiger: engineConfig: - configString: "cache_size=16484MB" # 50% of your ram is recommened. Adding more helps depending on dataset. + configString: "cache_size=16484MB" # 50% of your ram is recommended. Adding more helps depending on dataset. replication: - replSetName: "rs0" # Name of your replicaset + replSetName: "rs0" # Name of your replica set oplogSizeMB: 5000 # network interfaces @@ -70,14 +70,14 @@ setParameter: tlsWithholdClientCertificate: true ``` -**Details of what all these mean is below:** +**Details of what these mean are below:** **systemLog:** Contains locations and details of where logging should be contained. - **path:** Location for logging -**storage:** Its recommended to run test within memory to get achieve the best performance. This contains details on the engine used and location of storage. +**storage:** It's recommended to run test within memory to achieve the best performance. This contains details on the engine used and location of storage. - **engine:** Wiredtiger is used in this case. Using a disk will add latency. -- **cache_size:** The minimum if using the recommend instance size is 50% of 32(16gb). But in testing using 18gb produced better results. +- **cache_size:** The minimum if using the recommended instance size is 50% of 32(16gb). However, testing showed that using 18GB produced better results. **replication:** This is used for replica set setup. - **replSetName:** This is the name of the replica set. @@ -91,15 +91,15 @@ setParameter: - **diagnosticDataCollectionDirectorySizeMB:** 400 is based on the docs. - **honorSystemUmask:** Sets read and write permissions only to the owner of new files - **lockCodeSegmentsInMemory:** Locks code into memory and prevents it from being swapped. -- **suppressNoTLSPeerCertificateWarning:** allows clients to connect without a certificate. (Only for testing purposes) -- **tlsWithholdClientCertificate:** Will not send the certification during communication. (Only for testing purposes) +- **suppressNoTLSPeerCertificateWarning:** Allows clients to connect without a certificate. (Only for testing purposes) +- **tlsWithholdClientCertificate:** Will not send the certificate during communication. (Only for testing purposes) If you want to use encryption you will need to add the security and keyFile to your configuration. As well as change some of the parameters in the `mongod.conf` file. -Run this command to reload the new configurtion. +Run this command to reload the new configuration. -``` -sudo service mongod restart +```bash +sudo systemctl restart mongod ``` ## Modify the MongoDB service @@ -136,15 +136,15 @@ LimitNPROC=64000 WantedBy=multi-user.target ``` -details on these can be found here: https://docs.mongodb.com/manual/reference/ulimit/#recommended-ulimit-settings +Details on these can be found in the [documentation](https://docs.mongodb.com/manual/reference/ulimit/#recommended-ulimit-settings). Run this command to reload the service. -``` -sudo ystemctl daemon-reload +```bash +sudo systemctl daemon-reload ``` -**Once all three instances are created and have mongodb installed, select one to be your primary node. The remaining instances will be secondary nodes.** +**Once all three instances are created and have MongoDB installed, select one to be your primary node. The remaining instances will be secondary nodes.** ## Initialize the replica set @@ -160,7 +160,7 @@ Connect to the primary node and run the following commands below. 2. Initialize the replica set with the following command: - ``` + ```bash mongosh --host $PRIMARY_NODE_IP:27017 <}} sudo apt install default-jre default-jdk -y {{< /tab >}} -{{< tab header="RHE/Amazon" >}} +{{< tab header="RHEL/Amazon Linux" >}} sudo yum install java-17-openjdk {{< /tab >}} {{< /tabpane >}} @@ -33,37 +33,41 @@ On your instance running MongoDB (you may need to start a new terminal), clone t git clone https://github.com/idealo/mongodb-performance-test.git ``` -Now `cd` into the project folder and execute the `jar` file: +Now change into the project folder and execute the JAR file to see its usage instructions: ```bash { ret_code="1" } cd mongodb-performance-test java -jar ./latest-version/mongodb-performance-test.jar ``` -This will print a description of how to use the java application +This will print a description of how to use the Java application. ## Run Insert test Run a test that inserts documents on `localhost:27017` (default). -Use the following options: - * `-m` defines the test - * `-o` defines the number of iterations - * Alternatively, use `-d` to specify a time limit (in seconds) - * `-t` defines the number of threads - * `-db` defines the database to use - * `-c` defines how the data is collected. - - For example: +First, set an environment variable for the JAR file path for convenience: ```bash { cwd="./mongodb-performance-test" } export jarfile=./latest-version/mongodb-performance-test.jar +``` + +Use the following options: + * `-m` defines the test mode (e.g., `insert`, `update_one`). + * `-o` defines the number of operations (iterations). + * Alternatively, use `-d` to specify a duration limit (in seconds). + * `-t` defines the number of threads. + * `-db` defines the database to use. + * `-c` defines the collection to use. + + For example, run an insert test for 1 million operations using 10 threads: +```bash { cwd="./mongodb-performance-test" } java -jar $jarfile -m insert -o 1000000 -t 10 -db test -c perf ``` -As the test runs, the count will be printed periodically. It will increase until it reaches 1 million and then the test will end. +As the test runs, the progress count will be printed periodically. It will increase until it reaches 1 million, and then the test will end. ## Run Update-one test -Similarly, to run this test, updating one document per query using 10, 20 and finally 30 threads for 1 hour each run (3 hours in total) run the following command: +Similarly, to run an update test (updating one document per query) using 10, 20, and finally 30 threads for 1 hour each (3 hours total), run the following command: ```console java -jar $jarfile -m update_one -d 3600 -t 10 20 30 -db test -c perf @@ -73,7 +77,7 @@ For instructions on running any other tests or more details on the metrics repor ## View the results -During each test, statistics over the last second are printed every second in the console. The following is the output from the end of running Insert test: +During each test, statistics over the last second are printed to the console every second. After the test completes, final summary statistics are displayed. The following is example output from the end of the Insert test run: ``` output -- Timers ---------------------------------------------------------------------- @@ -96,4 +100,4 @@ stats-per-run-INSERT 99.9% <= 15.59 milliseconds ``` -The metrics are also output to the `stats-per-second-[mode].csv` which is located in the same folder as the jar file. `[mode]` is the executed mode(s), i.e. either `INSERT`, `UPDATE_ONE`, `UPDATE_MANY`, `COUNT_ONE`, `COUNT_MANY`, `ITERATE_ONE`, `ITERATE_MANY`, `DELETE_ONE` or `DELETE_MANY`. +Detailed per-second metrics are also output to a CSV file named `stats-per-second-[mode].csv` (e.g., `stats-per-second-INSERT.csv`), located in the same folder as the JAR file. `[mode]` corresponds to the executed mode(s), such as `INSERT`, `UPDATE_ONE`, `DELETE_ONE`, etc. diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/replica_set_testing.md b/content/learning-paths/servers-and-cloud-computing/mongodb/replica_set_testing.md index 586f71b7a8..91268d107e 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/replica_set_testing.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/replica_set_testing.md @@ -2,20 +2,17 @@ # User change title: "Three node replica set testing with YCSB" - weight: 5 # (intro is 1), 2 is first, 3 is second, etc. # Do not modify these elements layout: "learningpathall" --- -talk about which one to get on and how to run tests and see output - ## Recommended Tests on MongoDB -The most common three tests are **95/5** (95% read and 5% update), **100/0** (100% read and 0% update) and **90/10** (90% read and 10% update). In real world testing its recommended to run a **95/5** test. +The most common three tests are **95/5** (95% read and 5% update), **100/0** (100% read and 0% update) and **90/10** (90% read and 10% update). In real world testing it's recommended to run a **95/5** test. -Once you have loaded the dataset, run the selected test for approximately **five** minutes. This will allow the system to warm up before you start collecting performance data. The goal is to reach a high cpu utilization( 90+% ). Adjusting the number of threads, operationscount and recordscount can help you achieve this. Examples below maybe need to be adjusted based on the instance type you selected. +Once you have loaded the dataset, run the selected test for approximately **five** minutes. This will allow the system to warm up before you start collecting performance data. The goal is to reach a high CPU utilization (90+%). Adjusting the number of threads, operationscount and recordscount can help you achieve this. Examples below may need to be adjusted based on the instance type you selected. ## Load dataset @@ -32,7 +29,7 @@ Once you have loaded the dataset, run the selected test for approximately **five ## Run 100/0 test: ```console -./bin/ycsb run mongodb -s -P workloads/workloadc -p mongodb.url=mongodb://Localhost:27017 -p minfieldlength=50 -p compressibility=2 -p maxexecutiontime=120 -threads 64 -p operationcount=40000000 -p recordcount=20000000 -p requestdistribution=zipfian -p readproportion=1.0 -p updateproportion=0.0 +./bin/ycsb run mongodb -s -P workloads/workloadc -p mongodb.url=mongodb://localhost:27017 -p minfieldlength=50 -p compressibility=2 -p maxexecutiontime=120 -threads 64 -p operationcount=40000000 -p recordcount=20000000 -p requestdistribution=zipfian -p readproportion=1.0 -p updateproportion=0.0 ``` ## Run 90/10 test: From 22f18c762b4f46c9d32801c6e828ac8e95ec6c4c Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Thu, 24 Apr 2025 08:49:07 +0200 Subject: [PATCH 089/122] Allow capitalization in file extensions --- .github/workflows/test-lp.yml | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index b029dd2e97..ac3ba25469 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -20,16 +20,34 @@ jobs: **.md - name: Check for capital letters or spaces in content directory run: | - echo "Checking for capital letters or spaces in content directory paths..." + echo "Checking for capital letters or spaces in content directory paths (excluding file extensions)..." + + tmpfile=$(mktemp) git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '^content/' | - grep -E '[A-Z]|[[:space:]]' && { - echo "❌ Found invalid file or directory names with capital letters or spaces in 'content/'" - exit 1 - } + while read -r path; do + name=$(basename "$path") + + # Strip file extension if it exists + base="${name%.*}" + + if [[ "$base" =~ [A-Z] || "$base" =~ [[:space:]] ]]; then + echo "Invalid name: $path" + echo "$path" >> "$tmpfile" + fi + done + + if [[ -s "$tmpfile" ]]; then + echo "One or more files or directories in 'content/' contain capital letters or spaces (excluding extensions):" + cat "$tmpfile" + rm "$tmpfile" + exit 1 + else + rm "$tmpfile" + echo "✅ No capital letters or spaces found in 'content/' paths." + fi - echo "✅ No capital letters or spaces found in 'content/' paths." - name: Install dependencies if: steps.changed-markdown-files.outputs.any_changed == 'true' run: pip install -r tools/requirements.txt From 76f0ab8dda8e632c1ae73f692c57c518835f3bc4 Mon Sep 17 00:00:00 2001 From: Annie Tallund Date: Thu, 24 Apr 2025 09:22:53 +0200 Subject: [PATCH 090/122] Update test-lp.yml --- .github/workflows/test-lp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index ac3ba25469..adb71197d7 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -39,7 +39,7 @@ jobs: done if [[ -s "$tmpfile" ]]; then - echo "One or more files or directories in 'content/' contain capital letters or spaces (excluding extensions):" + echo "❌ One or more files or directories in 'content/' contain capital letters or spaces (excluding extensions):" cat "$tmpfile" rm "$tmpfile" exit 1 From eb589604c2eab82b913f7c2a8930e1f57bc52fdd Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 09:10:04 +0000 Subject: [PATCH 091/122] Index review complete --- .../laptops-and-desktops/gh-arm-runners-win/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md index ee39512734..93f629b5b0 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md @@ -7,14 +7,14 @@ cascade: minutes_to_complete: 20 -who_is_this_for: This introductory tutorial is for software developers who want to automate Windows application builds on Arm architecture using GitHub Actions. +who_is_this_for: This introductory tutorial is for software developers looking to automate Windows application builds on Arm architecture using GitHub Actions. learning_objectives: - Describe GitHub Arm-hosted Windows runners. - Configure workflows to run on Arm-hosted runners. - Automate Windows application builds with GitHub Actions. -prerequisites: +prerequisites: - A GitHub account. - Familiarity with GitHub Actions. From b00fd32f2b7d5d199090c8cda53111f179ac0f8b Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 09:32:41 +0000 Subject: [PATCH 092/122] Intro review complete --- .../gh-arm-runners-win/introduction.md | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md index 4ec0c5bbc6..689a6fea3b 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md @@ -6,12 +6,16 @@ weight: 2 ### FIXED, DO NOT MODIFY layout: learningpathall --- -GitHub Arm-hosted runners for Windows are now available in public preview, providing a powerful and efficient way to build, test, and deploy your Windows applications on Arm without the need for virtualization. These runners come with a Windows 11 Desktop image, equipped with many tools to get started with your workflows. +### Overview + +GitHub now supports Arm-hosted Windows runners, making it easy to run workflows on Arm hardware without managing infrastructure yourself. This Learning Path introduces what they are, how to use them, and how to configure your own larger runner. ### What are GitHub Arm-hosted Runners? Runners are the machines that execute jobs in a GitHub Actions workflow. An Arm-hosted runner is managed by GitHub and uses the Arm architecture, meaning you don't need to provide a server to run Actions workflows. GitHub provides the system and runs the Action workflows for you. +GitHub Arm-hosted runners for Windows are now available in public preview, providing a powerful and efficient way to build, test, and deploy your Windows applications on Arm without the need for virtualization. These runners come with a Windows 11 Desktop image, equipped with many tools to get started with your workflows. + Arm-hosted runners are available for public repositories at no cost, subject to [standard usage limits](https://docs.github.com/en/actions/administering-github-actions/usage-limits-billing-and-administration#usage-limits). ### How do I use GitHub Arm-hosted Windows Runners? @@ -27,41 +31,41 @@ This label does not work in private repositories - the workflow will fail if you {{% /notice %}} -### What types of GitHub-hosted runners are available? +### What types of GitHub-hosted Runners are available? GitHub offers two types of hosted runners: standard and larger runners. Larger runners give you more control—you can configure the amount of RAM, number of CPUs, and disk space. You can also use the Windows 11 Desktop Arm Image with larger runners. To use this image on larger runners, you will need to create a new runner. -### How can I create a GitHub Arm-hosted Windows larger runner? +### How Do I Create a Larger GitHub Arm-hosted Windows Runner? Arm-hosted runners are created at the organization level. -Navigate to your organization and select the `Settings` tab. On the left pane, select `Actions->Runners`. +Navigate to your organization and select the **Settings** tab. On the left pane, select **Actions->Runners**. -On the `Runners` page, select the `New runner` drop-down on the top right, and then select `New GitHub-hosted runner`. +On the **Runners** page, select the **New runner** drop-down on the top right, and then select **New GitHub-hosted runner**. ![new-runner #center](_images/new-runner.png) Specify a name for the runner. This name is used in the `runs-on` field in your workflows, so make it clear for others who use it. -Choose Windows ARM64 for the platform and click `Save`. +Choose Windows ARM64 for the platform and click **Save**. ![platform #center](_images/platform.png) -Specify the operating system image for the runner, select `Microsoft Windows 11 Desktop by Arm Limited`, and click `Save`. +Specify the operating system image for the runner, select **Microsoft Windows 11 Desktop by Arm Limited**, and click **Save**. ![image #center](_images/win_image.png) -Select the size of the larger runner you need and click `Save`. +Select the size of the larger runner you need and click **Save**. ![specifications #center](_images/size.png) -The `Capacity` section includes the maximum concurrency, which is the number of jobs to run at the same time. +The **Capacity** section includes the maximum concurrency, which is the number of jobs to run at the same time. -You can also set the runner group for this runner. The runner group controls the settings for this runner. Pay attention to the runner group as you may need to return to the runner group settings if any configuration changes are needed. +You can also set the runner group for this runner. The runner group controls this runner’s settings. You may need to return to the group if configuration changes are required. ![capacity #center](_images/capacity.png) -Finally, click `Create runner`. +Finally, click **Create runner**. Your new Arm-hosted Windows larger runner is ready to use. Remember the runner name so you can use it in your workflows with the `runs-on` label. @@ -69,7 +73,7 @@ Your new Arm-hosted Windows larger runner is ready to use. Remember the runner n The Arm-hosted runners are powered by Microsoft Azure Cobalt 100 processors, based on the Arm Neoverse N2, with 4 vCPUs and Armv9-A features, including Scalable Vector Extension 2 (SVE2). -The output from running the `wmic` command on the Arm-hosted runner is below. +The following output shows the result of running the `wmic` command on the Arm-hosted runner. ![Arm-hosted runner info #center](_images/cpuinfo.png) From 73b9b031657ec0eac40b7f2b5556f7db0dc88dce Mon Sep 17 00:00:00 2001 From: Odin Shen Coder Date: Thu, 24 Apr 2025 10:37:06 +0100 Subject: [PATCH 093/122] Add 2nd automotive SOAFEE learning path --- .../patches/1_functional_safety.md | 126 ++++++++ .../patches/2_DataDistributionService.md | 65 ++++ .../patches/3_aws_setup.md | 277 ++++++++++++++++++ .../patches/4_corellium_setup.md | 18 ++ .../5_multiple_instances_executeion.md | 9 + .../patches/_index.md | 52 ++++ .../patches/_next-steps.md | 8 + 7 files changed, 555 insertions(+) create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md new file mode 100644 index 0000000000..1b263746a1 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md @@ -0,0 +1,126 @@ +--- +title: Functional Safety for automotive software development +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## What is Functional Safety? + +[Functional Safety](https://en.wikipedia.org/wiki/Functional_safety) refers to a system's ability to detect potential faults and respond appropriately to ensure that the system remains in a safe state, preventing harm to individuals or damage to equipment. + +This is particularly important in **automotive, autonomous driving, medical devices, industrial control, robotics and aerospace** applications, where system failures can lead to severe consequences. + +In software development, Functional Safety focuses on minimizing risks through **software design, testing, and validation** to ensure that critical systems operate in a predictable, reliable, and verifiable manner. This means developers must consider: +- **Error detection mechanisms** +- **Exception handling** +- **Redundancy design** +- **Development processes compliant with safety standards** + +### Definition and Importance of Functional Safety + +The core of Functional Safety lies in **risk management**, which aims to reduce the impact of system failures. + +In autonomous vehicles, Functional Safety ensures that if sensor data is incorrect, the system can enter a **safe state**, preventing incorrect driving decisions. + +Three of core objectives of Functional Safety are: +1. **Prevention** + - Reducing the likelihood of errors through rigorous software development processes and testing. In the electric vehicle, the battery systems monitor temperature to prevent overheating. +2. **Detection** + - Quickly identifying errors using built-in diagnostic mechanisms (e.g., Built-in Self-Test, BIST). +3. **Mitigation** + - Controlling the impact of failures to ensure the overall safety of the system. + +This approach is critical in applications such as **autonomous driving, flight control, and medical implants**, where failures can result in **severe consequences**. + +### ISO 26262 Standard and Applications + +[ISO 26262](https://www.iso.org/standard/68383.html) is a functional safety standard specifically for **automotive electronics and software systems**. It defines a comprehensive safety lifecycle, covering all phases from **requirement analysis, design, development, testing, to maintenance**. + +Key Concepts of ISO 26262: +- **ASIL (Automotive Safety Integrity Level)** + - Evaluates the risk level of different system components (A, B, C, D, where **D represents the highest safety requirement**). + - For example: ASIL A can be Dashboard light failure (low risk) and ASIL D is Brake system failure (high risk). + https://en.wikipedia.org/wiki/Automotive_Safety_Integrity_Level +- **HARA (Hazard Analysis and Risk Assessment)** + - Analyzes hazards and assesses risks to determine necessary safety measures. +- **Safety Mechanisms** + - Includes error detection, fault tolerance, and fail-safe modes to ensure safe operation. + +List some of typical application scenarios: +- **Autonomous Driving Systems**: + - Ensures that even if sensors (e.g., LiDAR, radar, cameras) provide faulty data, the vehicle will not make dangerous decisions. +- **Powertrain Control**: + - Prevents braking system failures that could lead to loss of control. +- **Battery Management System (BMS)**: + - Prevents battery overheating or excessive discharge in electric vehicles. + +For more details, you can check this video: [What is Functional Safety?](https://www.youtube.com/watch?v=R0CPzfYHdpQ) + + +### Safety Island + +In automotive systems, a **General ECU (Electronic Control Unit)** typically runs non-critical tasks such as infotainment or navigation, whereas a **Safety Island** is dedicated to executing safety-critical control logic (e.g., braking, steering) with strong isolation, redundancy, and determinism. + +| Feature | General ECU | Safety Island | +|------------------------|----------------------------|--------------------------------------| +| Purpose | Comfort / non-safety logic | Safety-critical decision making | +| OS/Runtime | Linux, Android | RTOS, Hypervisor, or bare-metal | +| Isolation | Soft partitioning | Hard isolation (hardware-enforced) | +| Functional Safety Req | None to moderate | ISO 26262 ASIL-B to ASIL-D compliant | +| Fault Handling | Best-effort recovery | Deterministic safe-state response | + +This contrast highlights why safety-focused software needs a dedicated hardware domain with certified execution behavior. + +**Safety Island** is an independent safety subsystem separate from the main processor. It is responsible for monitoring and managing system safety. If the main processor fails or becomes inoperable, Safety Island can take over critical safety functions such as **deceleration, stopping, and fault handling** to prevent catastrophic system failures. + +Key Functions of Safety Island +- **Monitoring System Health** + - Continuously monitors the operational status of the main processor (e.g., ADAS control unit, ECU) and detects potential errors or anomalies. +- **Fault Detection and Isolation** + - Independently evaluates and initiates emergency handling if the main processing unit encounters errors, overheating, computational failures, or unresponsiveness. +- **Providing Essential Safety Functions** + - Even if the main system crashes, Safety Island can still execute minimal safety operations, such as: + - Autonomous Vehicles → Safe stopping (Fail-Safe Mode) + - Industrial Equipment → Emergency power cutoff or speed reduction + + + + +### Integration of Safety Island and Functional Safety + +Safety Island plays a critical role in Functional Safety by ensuring that the system can handle high-risk scenarios and minimize catastrophic failures. + +How Safety Island Enhances Functional Safety +1. **Acts as an Independent Redundant Safety Layer** + - Even if the main system fails, it can still operate independently. +2. **Supports ASIL-D Safety Level** + - Monitors ECU health status and executes emergency safety strategies (e.g., emergency braking). +3. **Provides Independent Fault Detection and Recovery Mechanisms** + - **Fail-Safe**: Activates a **safe mode**, such as limiting vehicle speed or switching to manual control. + - **Fail-Operational**: Ensures that high-safety applications (e.g., aerospace systems) can continue operating under certain conditions. + +For more insights on **Arm's Functional Safety solutions**, you can refer to: [Arm Functional Safety Compute Blog](https://community.arm.com/arm-community-blogs/b/automotive-blog/posts/functional-safety-compute) + + +### Impact of Functional Safety on Software Development Processes + +Functional Safety impacts **both hardware and software development**, particularly in areas such as requirement changes, version management, and testing validation. +For example, in ASIL-D level applications, every code modification requires a complete impact analysis and regression testing to ensure that new changes do not introduce additional risks. + +List the Functional Safety Requirements in Software Development: +- **Requirement Specification** + - Clearly defining **safety-critical requirements** and conducting risk assessments. +- **Safety-Oriented Programming** + - Following **MISRA C, CERT C/C++ standards** and using static analysis tools to detect errors. +- **Fault Handling Mechanisms** + - Implementing **redundancy design and health monitoring** to handle anomalies. +- **Testing and Verification** + - Using **Hardware-in-the-Loop (HIL)** testing to ensure software safety in real hardware environments. +- **Version Management and Change Control** + - Using **Git, JIRA, Polarion** to track changes for safety audits. + +This learning path builds on the previous [learning path](https://learn.arm.com/learning-paths/automotive/openadkit1_container) and introduces how to incorporate Functional Safety design processes in the early stages of automotive software development. +By establishing an ASIL Partitioning software development environment and leveraging [**SOAFEE**](https://www.soafee.io/) technologies, developers can enhance software consistency and maintainability in Functional Safety applications. + diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md new file mode 100644 index 0000000000..c50e6712d1 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md @@ -0,0 +1,65 @@ +--- +title: How to use Data Distribution Service (DDS) +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +### Introduction to DDS +Data Distribution Service (DDS) is a real-time, high-performance middleware designed for distributed systems, particularly in automotive software development for autonomous driving and advanced driver assistance systems (ADAS). Its decentralized architecture provides scalable, low-latency, and reliable data exchange, making it an essential component in managing high-frequency sensor data. + +In modern vehicles, multiple sensors, such as LiDAR, radar, and cameras, must communicate efficiently with computing modules and decision-making units. DDS enables seamless data transmission within the vehicle, ensuring that perception, localization, and control systems receive the necessary data with minimal delay. Additionally, it plays a crucial role in vehicle-to-infrastructure (V2X) communication, allowing vehicles to exchange information with traffic signals, road sensors, and other connected systems to enhance situational awareness and safety. + + +### Why Automotive Software Needs DDS +Modern automotive software architectures, such as SOAFEE, require deterministic data communication to ensure real-time coordination between sensors, ECUs, and computing modules. Traditional client-server communication models often introduce latency and bottlenecks, whereas DDS provides a direct, decentralized solution that enhances reliability and scalability. + +A key advantage of DDS is its ability to enable direct data exchange between system components without relying on a central server. This reduces the risk of a single point of failure and minimizes delays, which is crucial for autonomous driving applications where milliseconds can make a difference. For example, a LiDAR sensor publishing obstacle detection data can simultaneously send information to multiple subscribers, including perception, SLAM (Simultaneous Localization and Mapping), and motion planning modules. This parallel data distribution ensures all relevant subsystems have the latest environmental data without requiring multiple separate transmissions. + +Additionally, DDS provides a flexible Quality of Service (QoS) configuration, allowing engineers to fine-tune communication parameters based on system requirements. Low-latency modes are ideal for real-time decision-making in vehicle control, while high-reliability configurations ensure data integrity in safety-critical applications like V2X communication. + + +### Architecture and Operation +DDS is based on a data-centric publish-subscribe (DCPS) model, allowing producers and consumers of data to communicate without direct dependencies. This modular approach enhances system flexibility and maintainability, making it well-suited for complex automotive environments. + +In DDS, all participants operate within a **domain**, which provides logical isolation between different applications. Each domain contains multiple **topics**, representing specific data types such as vehicle speed, obstacle detection, or sensor fusion results. **Publishers** use **DataWriters** to send data to these topics, while **subscribers** use **DataReaders** to receive the data. This architecture supports concurrent data processing, ensuring that multiple modules can work with the same data stream simultaneously. + +For example, in an autonomous vehicle, LiDAR, radar, and cameras continuously generate large amounts of sensor data. The perception module subscribes to these sensor topics, processes the data, and then publishes detected objects and road conditions to other components like path planning and motion control. Since DDS automatically handles participant discovery and message distribution, engineers do not need to manually configure communication paths, reducing development complexity. + + +### Applications in Autonomous Driving +DDS is widely used in autonomous driving systems, where real-time data exchange is crucial. A typical use case involves high-frequency sensor data transmission and decision-making coordination between vehicle subsystems. + +For instance, a LiDAR sensor generates millions of data points per second, which need to be shared with multiple modules. DDS allows this data to be published once and received by multiple subscribers, including perception, localization, and mapping components. After processing, the detected objects and road features are forwarded to the path planning module, which calculates the vehicle's next movement. Finally, control commands are sent to the vehicle actuators, ensuring precise execution. + +This real-time data flow must occur within milliseconds to enable safe autonomous driving. DDS ensures minimal transmission delay, enabling rapid response to dynamic road conditions. In emergency scenarios, such as detecting a pedestrian or sudden braking by a nearby vehicle, DDS facilitates instant data propagation, allowing the system to take immediate corrective action. + +For example: [Autoware](https://www.autoware.org/)—an open-source autonomous driving software stack—uses DDS to handle high-throughput communication across its modules. For example, the **Perception** stack publishes detected objects from LiDAR and camera sensors to a shared topic, which is then consumed by the **Planning** module in real-time. Using DDS allows each subsystem to scale independently while preserving low-latency and deterministic communication. + +### Publish-Subscribe Model and Data Transmission +Traditional client-server communication requires a centralized server to manage data exchange. This architecture introduces several drawbacks, including increased latency and network congestion, which can be problematic in real-time automotive applications. + +DDS adopts a publish-subscribe model, enabling direct communication between system components. Instead of relying on a central entity to relay messages, DDS allows each participant to subscribe to relevant topics and receive updates as soon as new data becomes available. This approach reduces dependency on centralized infrastructure and improves overall system performance. + +For example, in an automotive perception system, LiDAR, radar, and cameras continuously publish sensor data. Multiple subscribers, including object detection, lane recognition, and obstacle avoidance modules, can access this data simultaneously without additional network overhead. DDS automatically manages message distribution, ensuring efficient resource utilization. + +DDS supports multiple transport mechanisms to optimize communication efficiency: +- **Shared memory transport**: Ideal for ultra-low-latency communication within an ECU, minimizing processing overhead. +- **UDP or TCP/IP**: Used for inter-device communication, such as V2X applications where vehicles exchange safety-critical messages. +- **Automatic participant discovery**: Eliminates the need for manual configuration, allowing DDS nodes to detect and establish connections dynamically. + +#### Comparison of DDS and Traditional Communication Methods + +| **Feature** | **Traditional Client-Server Architecture** | **DDS Publish-Subscribe Model** | +|----------------------|--------------------------------|---------------------------| +| **Data Transmission** | Relies on a central server | Direct peer-to-peer communication | +| **Latency** | Higher latency | Low latency | +| **Scalability** | Limited by server capacity | Suitable for large-scale systems | +| **Reliability** | Server failure affects the whole system | No single point of failure | +| **Use Cases** | Small-scale applications | V2X, autonomous driving | + +These features make DDS a highly adaptable solution for automotive software engineers seeking to develop scalable, real-time communication frameworks. + +Here is an [installation guide](https://learn.arm.com/install-guides/cyclonedds) on how to install open-source DDS on an Arm platform. + diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md new file mode 100644 index 0000000000..2fee5f4f91 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md @@ -0,0 +1,277 @@ +--- +title: Split into multiple cloud container instances +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +### System Architecture and Component Design + +Once we understand Safety Island —- the safety-critical subsystem responsible for executing essential control logic in automotive systems—and the DDS (Data Distribution Service) communication mechanism, we can refactor the original OpenAD Kit architecture, which previously ran on a single instance, by splitting it into two independent execution environments. + +In the previous [learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/), OpenAD Kit launched three containers on the same hardware, each responsible for the `Simulation environment`, `Visualization` and `Planning-Control` components. + +In this session, we decouple the simulation and visualization system from the on-vehicle autonomous driving stack, deploying them on two separate Arm-based instances. Communication between the two is facilitated through the ROS 2 software architecture, using DDS as the underlying middleware, which enables real-time data exchange and modular integration across distributed systems. + +This architecture brings several practical benefits: + +- Improved system stability: Decoupling reduces the risk of resource contention or runtime interference between visualization workloads and safety-critical components. + +- Leverages DDS’s QoS and discovery features: Ensures robust, real-time, and scalable communication across nodes without requiring a central broker or manual configuration. + +- Better scalability and performance tuning: Each instance can be provisioned with resources optimized for its specific task. + +- Supports modular development and CI/CD: Teams can develop, test, and iterate on components independently, enabling better DevOps practices for autonomous systems. + + +!!! Consider a simple network diagram showing the two EC2s, DDS, and container roles. !!! + +### Networking Setting + +First, launch two Arm instances (either cloud instances or on-premise servers). + +The specifications of the two Arm instances don’t need to be identical. In my tests, 16 CPUs and 32GB of RAM have already provided good performance. + +Once the two machines are up and running, you need to decide where the `Planning-Control` will execute. After making the decision, the other machine will run `Simulation environment` and `Visualization`. + +In order for the two instances to communicate, you must configure them to allow network access to each other. For example, in AWS EC2, both instances should belong to the same security group. + +In the AWS EC2 Security Groups inbound rules setting, ensure that there is a rule allowing traffic from other members of the same security group (i.e., the security group itself as a source). Outbound traffic is typically allowed by default and usually does not require modification. +This setup ensures that both EC2 instances can discover and communicate with each other over the network as required by ROS 2 and DDS. + +Once both of your machines are set up, please note down the IP addresses of both machines, as you will need them for the upcoming configuration. + +### New Docker YAML Configur Setting + +Ensure that Docker is installed on your development environment, then clone the same repository from the previous learning path onto both machines. + +```bash +git clone https://github.com/autowarefoundation/openadkit_demo.autoware.git + +cd openadkit_demo.autoware +cp docker/docker-compose.yml docker/docker-compose-2ins.yml +touch docker/cycloneDDS.xml +``` + +First, you need create xml file called `cycloneDDS.xml` + +This CycloneDDS XML configuration file is used to customize the behavior of the CycloneDDS middleware, which is used in ROS2 for inter-process communication (IPC) and network communication over DDS. +Please replace the previously written IP addresses of the two machines with the 192.168.xx.yy and 192.168.aa.bb in the configuration. + +```xml + + + + false + + + + + + 1000 + auto + + + + + + + /root/workspace/cyclonelog.log + config + + + +``` + +{{% notice Note %}} +You can find the more detail about CycloneDDS setting [Configuration](https://cyclonedds.io/docs/cyclonedds/latest/config/config_file_reference.html#cyclonedds-domain-internal-socketreceivebuffersize) +{{% /notice %}} + +Next, we need to configure the newly created YML file `docker-compose-2ins.yml`. + +- Use the host network instead of the docker network bridge. +This is necessary for allowing the all of three of containers to access the host network interfaces. + +```YAML + visualizer: + network_mode: host +``` + +- Add the newly created XML file as an environment variable in each container, and mount the current folder as `/root/workspace`. Configure the environment variable CYCLONEDDS_URI in the docker-compose.yaml file for all containers to ensure they use this configuration file. + +```YAML + volumes: + - .:/root/workspace + environment: + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml +``` + +- Remove the dependency between the `planning-control` and `simulator containers`. +Since these containers will now be launched independently, it is necessary to eliminate the dependency configuration between them. + +```YAML + planning-control: + depends_on: + - simulator +``` + +Here is the complete XML file: +```YAML +services: + simulator: + image: ghcr.io/autowarefoundation/demo-packages:simulator + container_name: simulator + network_mode: host + volumes: + - ./etc/simulation:/autoware/scenario-sim + - .:/root/workspace + - ./log:/root/.ros/log + environment: + - ROS_DOMAIN_ID=88 + - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml + command: > + ros2 launch scenario_test_runner scenario_test_runner.launch.py + record:=false + scenario:=/autoware/scenario-sim/scenario/yield_maneuver_demo.yaml + sensor_model:=sample_sensor_kit + vehicle_model:=sample_vehicle + initialize_duration:=90 + global_timeout:=$TIMEOUT + global_frame_rate:=20 + launch_autoware:=false + launch_rviz:=false + + planning-control: + image: ghcr.io/autowarefoundation/demo-packages:planning-control + container_name: planning-control + network_mode: host + deploy: + volumes: + - ./etc/simulation:/autoware/scenario-sim + - $CONF_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/autoware_behavior_path_static_obstacle_avoidance_module/static_obstacle_avoidance.param.yaml + - $COMMON_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/common/common.param.yaml + - .:/root/workspace + - ./log:/root/.ros/log + environment: + - ROS_DOMAIN_ID=88 + - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml + command: > + ros2 launch autoware_launch planning_simulator.launch.xml + map_path:=/autoware/scenario-sim/map + vehicle_model:=sample_vehicle + sensor_model:=sample_sensor_kit + scenario_simulation:=true + rviz:=false + perception/enable_traffic_light:=false + + visualizer: + image: ghcr.io/autowarefoundation/demo-packages:visualizer + network_mode: host + container_name: visualizer + volumes: + - ./etc/simulation:/autoware/scenario-sim + - .:/root/workspace + - ./log:/root/.ros/log + ports: + - 6080:6080 + - 5999:5999 + environment: + - ROS_DOMAIN_ID=88 + - VNC_ENABLED=true + - RVIZ_CONFIG=/autoware/scenario-sim/rviz/scenario_simulator.rviz + - NGROK_AUTHTOKEN=${NGROK_AUTHTOKEN} + - NGROK_URL=${NGROK_URL} + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml + command: >- + sleep infinity +``` +Before moving to the next step, make sure that `docker-compose-2ins.yml` and `cycloneDDS.xml` are already present on both instances. + +### Network Latency and Performance Optimization + +This distributed network architecture may lead to IP packet fragmentation, which can consume system memory under certain network conditions. + +To mitigate issues related to IP fragmentation and socket receive buffer limitations, you can tune the network configuration to support heavy UDP load. + +```bash +sudo su +sysctl net.ipv4.ipfrag_time=3 +sysctl net.ipv4.ipfrag_high_thresh=134217728 +sysctl -w net.core.rmem_max=2147483647 +exit +``` + +To make the configuration permanent across reboot, modify the create the file `/etc/sysctl.d/10-cyclone-max.conf`: + +```bash +sudo su +cat << EOF > /etc/sysctl.d/10-cyclone-max.conf +net.core.rmem_max=2147483647 +net.ipv4.ipfrag_time=3 +net.ipv4.ipfrag_high_thresh=134217728 # (128 MB) +EOF +exit +``` + +Reference: + - [Autoware dds-setting](https://autowarefoundation.github.io/autoware-documentation/main/installation/additional-settings-for-developers/network-configuration/dds-settings/) + - [ROS2 documentation](https://docs.ros.org/en/humble/How-To-Guides/DDS-tuning.html#cyclone-dds-tuning) + + +### Execution + +Next, you need to configure the execution commands on the different machines. +Copy the corresponding commands into the `openadkit_demo.autoware/docker` directory of two instances. + +{{< tabpane code=true >}} + {{< tab header="Planning-Control" language="bash">}} + #!/bin/bash + # Configure the environment variables + export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + export SCRIPT_DIR="$( pwd )" + export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml + export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml + export CONF_FILE=$CONF_FILE_FAIL + export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml + export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN + export NGROK_URL=$NGROK_URL + # Start planning-control + echo "Running planning v1.." + CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up planning-control --abort-on-container-exit + {{< /tab >}} + {{< tab header="Visualizer & Simulator" language="bash">}} + #!/bin/bash + # Configure the environment variables + export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + export SCRIPT_DIR="$( pwd )" + export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml + export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml + export CONF_FILE=$CONF_FILE_FAIL + export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml + export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN + export NGROK_URL=$NGROK_URL + + # Start visualizer and show logs + docker compose -f "$SCRIPT_DIR/docker-compose.yml" up visualizer -d + echo "Waiting 10 seconds for visualizer to start..." + sleep 10 + docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" logs visualizer + + # Start simulator + echo "Running simulator v1.." + TIMEOUT=70 CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up simulator --abort-on-container-exit {{< /tab >}} +{{< /tabpane >}} + + +Once both machines execute their respective scripts, the visualizer will provide a link that can be accessed through the public IP. When you access the link, you will see that the demo execution will be very similar to the [previous learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/4_run_openadkit/). + +The only difference is that the containers are distributed across two physical machines, and at the underlying layer of the demo, there will be frequent packet exchanges. + diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md new file mode 100644 index 0000000000..296f5e2bb9 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md @@ -0,0 +1,18 @@ +--- +title: Split into cloud container instances and RD-1AE Corellium +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +### Overview and Technical Features of the Corellium Platform + + +### Integration of Corellium with AWS DDS Systems + + +### Security and Real-Time Considerations + + +### Applicability and Comparative Analysis of This Solution diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md new file mode 100644 index 0000000000..dbc500a6dc --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md @@ -0,0 +1,9 @@ +--- +title: Execute multiple instance +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## \ No newline at end of file diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md new file mode 100644 index 0000000000..8c9e8ec1a7 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md @@ -0,0 +1,52 @@ +--- +title: Early Deployment of Automotive Functional Safety application on Neoverse V3AE + +minutes_to_complete: 60 + +who_is_this_for: This is an advanced automotive software development topic, focusing on the early-stage development of mission-critical software on Arm RD-1 AE. It explores how to leverage virtual platform technology to meet functional safety regulations in software development. + +learning_objectives: + - Introduction to Automotive Functional Safety(ISO-26262). Understand the ISO-26262 architecture and the importance of a structured software development flow in achieving functional safety compliance. + - Introduction to DDS (Data Distribution Service). Learn how DDS enables real-time, reliable communication between distributed automotive software components. + - Distributed Development for Functional Safety. Learn how to split the simulation platform into two independent units and leverage distributed development architecture to ensure functional safety. + +prerequisites: + - Two Arm-based Neoverse cloud instances or a local Arm Neoverse Linux computer with at least 16 CPUs and 32GB of RAM. + - Completion of the previous learning path. http://learn.arm.com/learning-paths/automotive/openadkit1_container/ + - A Corellium virtual platform account. https://www.corellium.com/ + - Basic knowledge of Docker operations. +author: + - Odin Shen + - Julien Jayat + +### Tags +skilllevels: Advanced +subjects: Containers and Virtualization +armips: + - Neoverse V3AE +tools_software_languages: + - Python + - Docker + - ROS2 +operatingsystems: + - Linux + + +further_reading: + - resource: + title: OpenAD Kit learning path + link: https://learn.arm.com/learning-paths/automotive/openadkit1_container/ + type: documentation + - resource: + title: DDS + link: https://github.com/autowarefoundation/openadkit_demo.autoware/ + type: documentation + + + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- From d863f820f28876df31a511af79b6cdca4a28e34a Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 13:40:02 +0000 Subject: [PATCH 094/122] Reviewed introduction. --- .../gh-arm-runners-win/introduction.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md index 689a6fea3b..8b1a2d8352 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/introduction.md @@ -18,7 +18,7 @@ GitHub Arm-hosted runners for Windows are now available in public preview, provi Arm-hosted runners are available for public repositories at no cost, subject to [standard usage limits](https://docs.github.com/en/actions/administering-github-actions/usage-limits-billing-and-administration#usage-limits). -### How do I use GitHub Arm-hosted Windows Runners? +### How Do I Use GitHub Arm-hosted Windows Runners? To leverage the GitHub Windows Arm64 hosted runners, you need to use the following label in your public repository workflow runs: @@ -31,7 +31,7 @@ This label does not work in private repositories - the workflow will fail if you {{% /notice %}} -### What types of GitHub-hosted Runners are available? +### What Types of GitHub-hosted Runners Are Available? GitHub offers two types of hosted runners: standard and larger runners. Larger runners give you more control—you can configure the amount of RAM, number of CPUs, and disk space. You can also use the Windows 11 Desktop Arm Image with larger runners. To use this image on larger runners, you will need to create a new runner. @@ -69,7 +69,7 @@ Finally, click **Create runner**. Your new Arm-hosted Windows larger runner is ready to use. Remember the runner name so you can use it in your workflows with the `runs-on` label. -### How do I check the server hardware used by the Arm-hosted Windows Runners? +### How Do I Check the Server Hardware Used by the Arm-hosted Windows Runners? The Arm-hosted runners are powered by Microsoft Azure Cobalt 100 processors, based on the Arm Neoverse N2, with 4 vCPUs and Armv9-A features, including Scalable Vector Extension 2 (SVE2). @@ -78,7 +78,7 @@ The following output shows the result of running the `wmic` command on the Arm-h ![Arm-hosted runner info #center](_images/cpuinfo.png) -### How can I find the software installed on the Arm-hosted Windows Runners? +### How Can I Find the Software Installed on the Arm-hosted Windows Runners? To find out more about the software installed on the Arm-hosted runners, visit the [GitHub Actions Partner Images repository](https://github.com/actions/partner-runner-images/). Check the [image documentation](https://github.com/actions/partner-runner-images/blob/main/images/arm-windows-11-image.md) for a list of software packages installed on the Windows 11 by Arm Limited image. You can also use this repository to report issues or request additional software. From 765419b459506b3ea4a8c71aea8798db275a1615 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 13:53:11 +0000 Subject: [PATCH 095/122] automate page review --- .../gh-arm-runners-win/automate-win-app.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md index 827ab84cc9..7c64d0b23e 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md @@ -10,9 +10,9 @@ In this section, you will learn how to automate the build process of a Windows a ### Overview of the Windows Application -A basic overview of the application is provided here but for details on building the application refer to the [Optimize Windows applications using Arm Performance Libraries Learning Path](/learning-paths/laptops-and-desktops/windows_armpl/2-multithreading/). +This section provides a basic overview of the application. For detailed build instructions, see the [Optimize Windows applications using Arm Performance Libraries Learning Path](/learning-paths/laptops-and-desktops/windows_armpl/2-multithreading/). -The source code for the application that renders a rotating 3D cube to perform the calculations using different programming options is provided in this GitHub repository. +The source code for the rotating 3D cube application, which demonstrates multiple programming approaches to performing rotation calculations, is available in this GitHub repository. ```console https://github.com/arm/SpinTheCubeInGDI @@ -26,8 +26,8 @@ The application implements a spinning cube and consists of four key components: The code has two options to calculate the rotation: -1. Multithreading: the application uses multithreading to improve performance by distributing the rotation calculations across multiple threads. -2. Arm Performance Libraries: the application uses optimized math library functions for the rotation calculations. +1. **Multithreading**: distributes rotation calculations across multiple threads to improve performance. +2. **Arm Performance Libraries**: uses Arm-optimized math functions to accelerate rotation calculations. You will learn how to automate the build process for this application by using GitHub Actions with Arm-hosted Windows runners. @@ -65,17 +65,17 @@ jobs: **Upload Build Artifact**: Uploads the built executable as an artifact using `actions/upload-artifact@v4`. -This workflow automates the process of dependency management, environment setup, building the project, and storing the final artifact all using a GitHub Arm-hosted Windows runner. +This workflow automates dependency management, environment setup, project compilation, and artifact storage - all using a GitHub Arm-hosted Windows runner. -### Fork the repository and run the workflow +### Fork the Repository and Run the Workflow -To run the workflow, you can fork the repository and run the workflow in your GitHub account. +To run the workflow in your own GitHub account, start by forking the repository. -To fork the repository, go to the repository page on GitHub and click the `Fork` button in the top right corner. This will create a copy of the repository under your own GitHub account. +To fork the repository, go to the repository page on GitHub and click the **Fork** button in the top right corner. This will create a copy of the repository under your own GitHub account. -You can then run the workflow in your forked repository by navigating to the `Actions` tab and selecting the MSBuild workflow, then clicking `Run workflow`. +You can then run the workflow in your forked repository by navigating to the **Actions** tab and selecting the MSBuild workflow, then clicking **Run workflow**. -You can view the `Actions` logs in the repository for each step. +You can view the **Actions** logs in the repository for each step. ![action #center](_images/actions.png) You have learned how to build a Windows application and upload the result as an artifact of your workflow using the GitHub Arm-hosted Windows runner. From 823c723fb0834d952e2f669690501724ef1d771d Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Thu, 24 Apr 2025 08:59:46 -0500 Subject: [PATCH 096/122] Start review of Fio Learning Path --- .../servers-and-cloud-computing/disk-io-benchmark/_index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md index 28b4d145e2..71682169c2 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md @@ -1,9 +1,13 @@ --- title: Microbenchmark Storage Performance with Fio +draft: true +cascade: + draft: true + minutes_to_complete: 30 -who_is_this_for: A cloud developer who wants to optimise storage cost or performance of their application. Developers who want to uncover potential storage-bound bottlenecks or changes when migrating an application to a different platform. +who_is_this_for: A cloud developer who wants to optimize storage cost or performance of their application. Developers who want to uncover potential storage-bound bottlenecks or changes when migrating an application to a different platform. learning_objectives: - Understand the flow of data for storage devices From e2d6dead8cf79bd20dc1821b3a151a0bc5c343c6 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 14:01:13 +0000 Subject: [PATCH 097/122] final --- .../gh-arm-runners-win/automate-win-app.md | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md index 7c64d0b23e..c984dd55f4 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/automate-win-app.md @@ -6,9 +6,12 @@ weight: 3 ### FIXED, DO NOT MODIFY layout: learningpathall --- + +{{% notice Learning Tip %}} In this section, you will learn how to automate the build process of a Windows application using GitHub Arm-hosted runners. You will use the application in the [Optimize Windows applications using Arm Performance Libraries Learning Path](/learning-paths/laptops-and-desktops/windows_armpl/). +{{% /notice %}} -### Overview of the Windows Application +### About the Windows Application This section provides a basic overview of the application. For detailed build instructions, see the [Optimize Windows applications using Arm Performance Libraries Learning Path](/learning-paths/laptops-and-desktops/windows_armpl/2-multithreading/). @@ -19,10 +22,10 @@ https://github.com/arm/SpinTheCubeInGDI ``` The application implements a spinning cube and consists of four key components: -- **Shape Generation**: Generates vertices for a sphere using a golden ratio-based algorithm. -- **Rotation Calculation**: Uses a rotation matrix to rotate the 3D shape around the X, Y, and Z axes. -- **Drawing**: Draws the transformed vertices of the shapes on the screen using a Windows API. -- **Performance Measurement**: Measures and displays the number of transforms per second. +- **Shape Generation**: generates vertices for a sphere using a golden ratio-based algorithm. +- **Rotation Calculation**: uses a rotation matrix to rotate the 3D shape around the X, Y, and Z axes. +- **Drawing**: draws the transformed vertices of the shapes on the screen using a Windows API. +- **Performance Measurement**: measures and displays the number of transforms per second. The code has two options to calculate the rotation: @@ -38,32 +41,32 @@ The [GitHub Actions workflow `msbuild.yml`](https://github.com/arm/SpinTheCubeIn Below is an explanation of the steps in the workflow: - **Trigger Events**: The workflow runs when there is a push or pull request event on the main branch. + **Trigger Events**: the workflow runs when there is a push or pull request event on the main branch. - **Job Definition**: A single job named `build` is defined. It runs on the GitHub Arm-hosted Windows runner (`windows-11-arm`) as shown: + **Job Definition**: a single job named `build` is defined. It runs on the GitHub Arm-hosted Windows runner (`windows-11-arm`) as shown: ```console jobs: build: runs-on: windows-11-arm ``` - **Checkout Repository**: Uses the `actions/checkout@v4` action to fetch the code. + **Checkout Repository**: uses the `actions/checkout@v4` action to fetch the code. - **Add MSBuild to PATH**: Adds MSBuild tools for the build process using `microsoft/setup-msbuild@v1.0.2`. + **Add MSBuild to PATH**: adds MSBuild tools for the build process using `microsoft/setup-msbuild@v1.0.2`. - **Restore Dependencies**: Runs `nuget restore` to restore NuGet packages required by the solution. + **Restore Dependencies**: runs `nuget restore` to restore NuGet packages required by the solution. - **Create Download Directory**: Creates a directory to store downloaded files and verifies the Python version. + **Create Download Directory**: creates a directory to store downloaded files and verifies the Python version. - **Download ARM Performance Libraries**: Downloads the Windows installer for ARM Performance Libraries (APL) and verifies the downloaded files. + **Download ARM Performance Libraries**: downloads the Windows installer for ARM Performance Libraries (APL) and verifies the downloaded files. - **Install ARM Performance Libraries**: Installs the downloaded ARM Performance Libraries using `msiexec.exe` with a quiet mode and logs the process. + **Install ARM Performance Libraries**: installs the downloaded ARM Performance Libraries using `msiexec.exe` with a quiet mode and logs the process. - **Check Installation Success**: Verifies the success of the APL installation by checking the exit code and logs. + **Check Installation Success**: verifies the success of the APL installation by checking the exit code and logs. - **Build the Solution**: Runs MSBuild to build the solution with the specified configuration (Debug) and platform (ARM64). + **Build the Solution**: runs MSBuild to build the solution with the specified configuration (Debug) and platform (ARM64). - **Upload Build Artifact**: Uploads the built executable as an artifact using `actions/upload-artifact@v4`. + **Upload Build Artifact**: uploads the built executable as an artifact using `actions/upload-artifact@v4`. This workflow automates dependency management, environment setup, project compilation, and artifact storage - all using a GitHub Arm-hosted Windows runner. From 45afd781687b06e21d2f6239bdf3bdb5df8cf1d2 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Thu, 24 Apr 2025 09:18:26 -0500 Subject: [PATCH 098/122] Windows on Arm GitHub Arm-hosted runners ready to publish --- .../laptops-and-desktops/gh-arm-runners-win/_index.md | 4 ---- .../laptops-and-desktops/windows_armpl/2a-get-set-up-git.md | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md index 93f629b5b0..139a27acf1 100644 --- a/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md +++ b/content/learning-paths/laptops-and-desktops/gh-arm-runners-win/_index.md @@ -1,10 +1,6 @@ --- title: Automate Windows on Arm Builds with GitHub Arm-hosted Runners -draft: true -cascade: - draft: true - minutes_to_complete: 20 who_is_this_for: This introductory tutorial is for software developers looking to automate Windows application builds on Arm architecture using GitHub Actions. diff --git a/content/learning-paths/laptops-and-desktops/windows_armpl/2a-get-set-up-git.md b/content/learning-paths/laptops-and-desktops/windows_armpl/2a-get-set-up-git.md index 23f85cfe6a..18908c6bce 100644 --- a/content/learning-paths/laptops-and-desktops/windows_armpl/2a-get-set-up-git.md +++ b/content/learning-paths/laptops-and-desktops/windows_armpl/2a-get-set-up-git.md @@ -19,7 +19,7 @@ The example application renders a rotating 3D cube to perform the calculations u First, navigate to an empty directory, and clone the repository containing the example from GitHub: ```cmd -git clone https://github.com/odincodeshen/SpinTheCubeInGDI.git +git clone https://github.com/arm/SpinTheCubeInGDI.git ``` {{% notice Note %}} From d72678055ccc4275e22eab3d7a2ff1a3295d05ee Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 15:52:27 +0000 Subject: [PATCH 099/122] Editorial review. --- content/install-guides/nerdctl.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/content/install-guides/nerdctl.md b/content/install-guides/nerdctl.md index b87c5a1181..6f1b8ba4b9 100644 --- a/content/install-guides/nerdctl.md +++ b/content/install-guides/nerdctl.md @@ -27,11 +27,11 @@ weight: 1 [Nerdctl](https://github.com/containerd/nerdctl) is an open-source command-line interface (CLI) designed to be compatible with the Docker CLI, but specifically for interacting with [containerd](https://containerd.io/). It provides a familiar user experience for developers who are familiar with Docker, while leveraging the capabilities of containerd as the underlying container runtime. -Using containerd and nerdctl provides similar functionality to Docker but with a smaller memory footprint, making it ideal for IoT and edge solutions, especially on Arm devices that balance energy efficiency and performance. +Using `containerd` and `nerdctl` provides similar functionality to Docker but with a smaller memory footprint, making it ideal for IoT and edge solutions, especially on Arm devices that balance energy efficiency and performance. Nerdctl also supports running containers in rootless mode, which helps enhance security by not requiring elevated privileges. Rootless mode is not covered below but you can refer to the [documentation](https://rootlesscontaine.rs/getting-started/containerd/) for information about how to run `containerd-rootless-setuptool.sh install`. -This guide explains how to install and use containerd and nerdctl on Arm Linux and run with `sudo` +This guide explains how to install and use `containerd` and `nerdctl` on Arm Linux, and how to run commands with `sudo`. ## Before you begin @@ -49,7 +49,7 @@ The output should be: aarch64 ``` -Ensure `wget` and `tar` are installed. Most distributions will include them, but if not run: +Ensure `wget` and `tar` are installed. Most distributions will include them, but if not, run: ```bash sudo apt-get update @@ -58,13 +58,13 @@ sudo apt-get install -y wget tar ## Install containerd -Install the containerd runtime: +Install the `containerd` runtime: ```bash sudo apt-get install containerd -y ``` -Start and enable the containerd service: +Start and enable the `containerd` service: ```bash sudo systemctl start containerd @@ -77,7 +77,7 @@ Confirm the service is running: systemctl status containerd.service ``` -When containerd is running, the output is similar to: +When `containerd` is running, the output is similar to: ```output ● containerd.service - containerd container runtime @@ -94,7 +94,7 @@ When containerd is running, the output is similar to: ## Install nerdctl and CNI plugins -Install nerdctl and the necessary CNI (Container Network Interface) plugins. +Install `nerdctl` and the necessary CNI (Container Network Interface) plugins: ```bash NERDCTL_VERSION=$(curl -s https://api.github.com/repos/containerd/nerdctl/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//') @@ -123,9 +123,9 @@ The commands above attempt to fetch the latest versions automatically. If requir ## Install BuildKit -If you want to build container images with nerdctl, you need to install [BuildKit](https://github.com/moby/buildkit). +If you want to build container images with `nerdctl`, you need to install [BuildKit](https://github.com/moby/buildkit). -If you only need to run container images you can skip this step. +If you only plan to run container images (not build them), you can skip this step. ```bash BUILDKIT_VERSION=$(curl -s https://api.github.com/repos/moby/buildkit/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//') @@ -195,7 +195,7 @@ jz1h9gb0xq39ob6868cr3ev6r linux/arm64 ## Verify the installation -You can check the nerdctl version: +You can check the `nerdctl` version: ```console sudo nerdctl version @@ -207,7 +207,7 @@ Test your installation by running a simple container that prints the processor a sudo nerdctl run --name uname armswdev/uname ``` -Wait a few seconds for the container to start. The architecture is printed: +Wait a few seconds for the container to start. It will print the system architecture: ```output Architecture is aarch64 @@ -219,7 +219,7 @@ Clean up the test container: sudo nerdctl rm uname ``` -To build a container image, use a text editor to copy the lines below to a new file named `Dockerfile`. +To build a container image, save the following lines to a file named `Dockerfile`. ```console FROM ubuntu:latest @@ -308,4 +308,4 @@ Execute a command in a running container: sudo nerdctl exec -it ``` -You are now ready to use nerdctl and containerd. +You are now ready to use `nerdctl` and `containerd` to manage containers on Arm Linux. From a1bd29939ab4bc11ca41a99cd1825b64318a6ffb Mon Sep 17 00:00:00 2001 From: Odin Shen Coder Date: Thu, 24 Apr 2025 17:05:13 +0100 Subject: [PATCH 100/122] Remove unnecessary steps from the latest WindowsPerf installation method. Remove unnecessary step and add attentive link. --- content/install-guides/wperf.md | 86 +-------------------------------- 1 file changed, 2 insertions(+), 84 deletions(-) diff --git a/content/install-guides/wperf.md b/content/install-guides/wperf.md index ff9694ed96..a2badeee96 100644 --- a/content/install-guides/wperf.md +++ b/content/install-guides/wperf.md @@ -79,70 +79,11 @@ Starting package uninstall... Successfully uninstalled ``` -## Visual Studio and the Windows Driver Kit (WDK) - -WindowsPerf relies on `dll` files installed with Visual Studio, from the Community Edition or higher and, optionally, installers from the Windows Driver Kit extension. - -For information about the WDK installation process, see [Download the Windows Driver Kit (WDK)](https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk). - -See also the [Visual Studio for Windows on Arm install guide](/install-guides/vs-woa/). - -## Download WindowsPerf - -You can download the latest release package, `windowsperf-bin-.zip` from the Linaro GitLab repository: -```url -https://gitlab.com/Linaro/WindowsPerf/windowsperf/-/releases -``` - -To download directly from command prompt, use: - -```console -mkdir windowsperf-bin-4.0.0 -cd windowsperf-bin-4.0.0 -curl -L -O https://gitlab.com/api/v4/projects/40381146/packages/generic/windowsperf/4.0.0/windowsperf-bin-4.0.0.zip -``` - -Unzip the package: - -```console -tar -xmf windowsperf-bin-4.0.0.zip -``` - -## Install wperf driver - -You can install the kernel driver using the supplied `wperf-devgen` installer. - -The [wperf-devgen](https://gitlab.com/Linaro/WindowsPerf/windowsperf/-/tree/main/wperf-devgen/README.md) tool has been designated as the preferred installer and uninstaller for the WindowsPerf Kernel Driver in the latest release. This tool offers a simple process for managing the installation and removal of the driver. - {{% notice Note%}} -You must install the driver as `Administrator`. +WinPerf is an open-source project. If you would like to develop WindowsPerf yourself, you may also need to install the Windows Driver Kit (WDK). Please refer to this link for more details. +https://learn.microsoft.com/en-us/windows-hardware/drivers/wdk-release-notes {{% /notice %}} -Open a **Windows Command Prompt** terminal with **Run as administrator** selected. - -Make sure you are in the `windowsperf-bin-` directory: - -```command -cd windowsperf-bin-4.0.0 -``` - -### Install with wperf-devgen - -Navigate to the `wperf-driver` folder and run the installer: - -```command -cd wperf-driver -wperf-devgen install -``` - -The output should be similar to: - -```output -Executing command: install. -Install requested. -Device installed successfully -``` - ## Verify install You can check everything is working by running the `wperf` executable. @@ -167,30 +108,7 @@ You see output similar to: wperf-driver 4.0.0 b18197bd +etw-drv ``` -## Uninstall wperf driver - -You can uninstall (or *remove*) the kernel driver using supplied [wperf-devgen](#devgen_uninstall) uninstaller. -{{% notice Note%}} -You must uninstall the driver as `Administrator`. -{{% /notice %}} - -### Uninstall with wperf-devgen {#devgen_uninstall} - -```command -cd windowsperf-bin-4.0.0\wperf-driver -wperf-devgen uninstall -``` - -The output is similar to: - -```console -Executing command: uninstall. -Uninstall requested. -Root\WPERFDRIVER -Device found -Device uninstalled successfully -``` ## Install WindowsPerf Virtual Studio Extension (optional) {#vs2022} From bbe98b95d1c1e77b8ee500c319f0d71b0cf06cfd Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Thu, 24 Apr 2025 11:12:37 -0500 Subject: [PATCH 101/122] Nerdctl install guide ready to publish --- content/install-guides/nerdctl.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/install-guides/nerdctl.md b/content/install-guides/nerdctl.md index 6f1b8ba4b9..304217e5e5 100644 --- a/content/install-guides/nerdctl.md +++ b/content/install-guides/nerdctl.md @@ -2,8 +2,6 @@ title: Nerdctl author: Jason Andrews -draft: true - minutes_to_complete: 10 official_docs: https://github.com/containerd/nerdctl/blob/main/docs/command-reference.md From fd8e5c628139b1b72a5c8e17cba38bf13ef6ef1e Mon Sep 17 00:00:00 2001 From: Odin Shen Coder Date: Thu, 24 Apr 2025 17:15:21 +0100 Subject: [PATCH 102/122] Remove SOAFEE part 2 check-in --- .../patches/1_functional_safety.md | 126 -------- .../patches/2_DataDistributionService.md | 65 ---- .../patches/3_aws_setup.md | 277 ------------------ .../patches/4_corellium_setup.md | 18 -- .../5_multiple_instances_executeion.md | 9 - .../patches/_index.md | 52 ---- .../patches/_next-steps.md | 8 - 7 files changed, 555 deletions(-) delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md deleted file mode 100644 index 1b263746a1..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -title: Functional Safety for automotive software development -weight: 2 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -## What is Functional Safety? - -[Functional Safety](https://en.wikipedia.org/wiki/Functional_safety) refers to a system's ability to detect potential faults and respond appropriately to ensure that the system remains in a safe state, preventing harm to individuals or damage to equipment. - -This is particularly important in **automotive, autonomous driving, medical devices, industrial control, robotics and aerospace** applications, where system failures can lead to severe consequences. - -In software development, Functional Safety focuses on minimizing risks through **software design, testing, and validation** to ensure that critical systems operate in a predictable, reliable, and verifiable manner. This means developers must consider: -- **Error detection mechanisms** -- **Exception handling** -- **Redundancy design** -- **Development processes compliant with safety standards** - -### Definition and Importance of Functional Safety - -The core of Functional Safety lies in **risk management**, which aims to reduce the impact of system failures. - -In autonomous vehicles, Functional Safety ensures that if sensor data is incorrect, the system can enter a **safe state**, preventing incorrect driving decisions. - -Three of core objectives of Functional Safety are: -1. **Prevention** - - Reducing the likelihood of errors through rigorous software development processes and testing. In the electric vehicle, the battery systems monitor temperature to prevent overheating. -2. **Detection** - - Quickly identifying errors using built-in diagnostic mechanisms (e.g., Built-in Self-Test, BIST). -3. **Mitigation** - - Controlling the impact of failures to ensure the overall safety of the system. - -This approach is critical in applications such as **autonomous driving, flight control, and medical implants**, where failures can result in **severe consequences**. - -### ISO 26262 Standard and Applications - -[ISO 26262](https://www.iso.org/standard/68383.html) is a functional safety standard specifically for **automotive electronics and software systems**. It defines a comprehensive safety lifecycle, covering all phases from **requirement analysis, design, development, testing, to maintenance**. - -Key Concepts of ISO 26262: -- **ASIL (Automotive Safety Integrity Level)** - - Evaluates the risk level of different system components (A, B, C, D, where **D represents the highest safety requirement**). - - For example: ASIL A can be Dashboard light failure (low risk) and ASIL D is Brake system failure (high risk). - https://en.wikipedia.org/wiki/Automotive_Safety_Integrity_Level -- **HARA (Hazard Analysis and Risk Assessment)** - - Analyzes hazards and assesses risks to determine necessary safety measures. -- **Safety Mechanisms** - - Includes error detection, fault tolerance, and fail-safe modes to ensure safe operation. - -List some of typical application scenarios: -- **Autonomous Driving Systems**: - - Ensures that even if sensors (e.g., LiDAR, radar, cameras) provide faulty data, the vehicle will not make dangerous decisions. -- **Powertrain Control**: - - Prevents braking system failures that could lead to loss of control. -- **Battery Management System (BMS)**: - - Prevents battery overheating or excessive discharge in electric vehicles. - -For more details, you can check this video: [What is Functional Safety?](https://www.youtube.com/watch?v=R0CPzfYHdpQ) - - -### Safety Island - -In automotive systems, a **General ECU (Electronic Control Unit)** typically runs non-critical tasks such as infotainment or navigation, whereas a **Safety Island** is dedicated to executing safety-critical control logic (e.g., braking, steering) with strong isolation, redundancy, and determinism. - -| Feature | General ECU | Safety Island | -|------------------------|----------------------------|--------------------------------------| -| Purpose | Comfort / non-safety logic | Safety-critical decision making | -| OS/Runtime | Linux, Android | RTOS, Hypervisor, or bare-metal | -| Isolation | Soft partitioning | Hard isolation (hardware-enforced) | -| Functional Safety Req | None to moderate | ISO 26262 ASIL-B to ASIL-D compliant | -| Fault Handling | Best-effort recovery | Deterministic safe-state response | - -This contrast highlights why safety-focused software needs a dedicated hardware domain with certified execution behavior. - -**Safety Island** is an independent safety subsystem separate from the main processor. It is responsible for monitoring and managing system safety. If the main processor fails or becomes inoperable, Safety Island can take over critical safety functions such as **deceleration, stopping, and fault handling** to prevent catastrophic system failures. - -Key Functions of Safety Island -- **Monitoring System Health** - - Continuously monitors the operational status of the main processor (e.g., ADAS control unit, ECU) and detects potential errors or anomalies. -- **Fault Detection and Isolation** - - Independently evaluates and initiates emergency handling if the main processing unit encounters errors, overheating, computational failures, or unresponsiveness. -- **Providing Essential Safety Functions** - - Even if the main system crashes, Safety Island can still execute minimal safety operations, such as: - - Autonomous Vehicles → Safe stopping (Fail-Safe Mode) - - Industrial Equipment → Emergency power cutoff or speed reduction - - - - -### Integration of Safety Island and Functional Safety - -Safety Island plays a critical role in Functional Safety by ensuring that the system can handle high-risk scenarios and minimize catastrophic failures. - -How Safety Island Enhances Functional Safety -1. **Acts as an Independent Redundant Safety Layer** - - Even if the main system fails, it can still operate independently. -2. **Supports ASIL-D Safety Level** - - Monitors ECU health status and executes emergency safety strategies (e.g., emergency braking). -3. **Provides Independent Fault Detection and Recovery Mechanisms** - - **Fail-Safe**: Activates a **safe mode**, such as limiting vehicle speed or switching to manual control. - - **Fail-Operational**: Ensures that high-safety applications (e.g., aerospace systems) can continue operating under certain conditions. - -For more insights on **Arm's Functional Safety solutions**, you can refer to: [Arm Functional Safety Compute Blog](https://community.arm.com/arm-community-blogs/b/automotive-blog/posts/functional-safety-compute) - - -### Impact of Functional Safety on Software Development Processes - -Functional Safety impacts **both hardware and software development**, particularly in areas such as requirement changes, version management, and testing validation. -For example, in ASIL-D level applications, every code modification requires a complete impact analysis and regression testing to ensure that new changes do not introduce additional risks. - -List the Functional Safety Requirements in Software Development: -- **Requirement Specification** - - Clearly defining **safety-critical requirements** and conducting risk assessments. -- **Safety-Oriented Programming** - - Following **MISRA C, CERT C/C++ standards** and using static analysis tools to detect errors. -- **Fault Handling Mechanisms** - - Implementing **redundancy design and health monitoring** to handle anomalies. -- **Testing and Verification** - - Using **Hardware-in-the-Loop (HIL)** testing to ensure software safety in real hardware environments. -- **Version Management and Change Control** - - Using **Git, JIRA, Polarion** to track changes for safety audits. - -This learning path builds on the previous [learning path](https://learn.arm.com/learning-paths/automotive/openadkit1_container) and introduces how to incorporate Functional Safety design processes in the early stages of automotive software development. -By establishing an ASIL Partitioning software development environment and leveraging [**SOAFEE**](https://www.soafee.io/) technologies, developers can enhance software consistency and maintainability in Functional Safety applications. - diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md deleted file mode 100644 index c50e6712d1..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: How to use Data Distribution Service (DDS) -weight: 3 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -### Introduction to DDS -Data Distribution Service (DDS) is a real-time, high-performance middleware designed for distributed systems, particularly in automotive software development for autonomous driving and advanced driver assistance systems (ADAS). Its decentralized architecture provides scalable, low-latency, and reliable data exchange, making it an essential component in managing high-frequency sensor data. - -In modern vehicles, multiple sensors, such as LiDAR, radar, and cameras, must communicate efficiently with computing modules and decision-making units. DDS enables seamless data transmission within the vehicle, ensuring that perception, localization, and control systems receive the necessary data with minimal delay. Additionally, it plays a crucial role in vehicle-to-infrastructure (V2X) communication, allowing vehicles to exchange information with traffic signals, road sensors, and other connected systems to enhance situational awareness and safety. - - -### Why Automotive Software Needs DDS -Modern automotive software architectures, such as SOAFEE, require deterministic data communication to ensure real-time coordination between sensors, ECUs, and computing modules. Traditional client-server communication models often introduce latency and bottlenecks, whereas DDS provides a direct, decentralized solution that enhances reliability and scalability. - -A key advantage of DDS is its ability to enable direct data exchange between system components without relying on a central server. This reduces the risk of a single point of failure and minimizes delays, which is crucial for autonomous driving applications where milliseconds can make a difference. For example, a LiDAR sensor publishing obstacle detection data can simultaneously send information to multiple subscribers, including perception, SLAM (Simultaneous Localization and Mapping), and motion planning modules. This parallel data distribution ensures all relevant subsystems have the latest environmental data without requiring multiple separate transmissions. - -Additionally, DDS provides a flexible Quality of Service (QoS) configuration, allowing engineers to fine-tune communication parameters based on system requirements. Low-latency modes are ideal for real-time decision-making in vehicle control, while high-reliability configurations ensure data integrity in safety-critical applications like V2X communication. - - -### Architecture and Operation -DDS is based on a data-centric publish-subscribe (DCPS) model, allowing producers and consumers of data to communicate without direct dependencies. This modular approach enhances system flexibility and maintainability, making it well-suited for complex automotive environments. - -In DDS, all participants operate within a **domain**, which provides logical isolation between different applications. Each domain contains multiple **topics**, representing specific data types such as vehicle speed, obstacle detection, or sensor fusion results. **Publishers** use **DataWriters** to send data to these topics, while **subscribers** use **DataReaders** to receive the data. This architecture supports concurrent data processing, ensuring that multiple modules can work with the same data stream simultaneously. - -For example, in an autonomous vehicle, LiDAR, radar, and cameras continuously generate large amounts of sensor data. The perception module subscribes to these sensor topics, processes the data, and then publishes detected objects and road conditions to other components like path planning and motion control. Since DDS automatically handles participant discovery and message distribution, engineers do not need to manually configure communication paths, reducing development complexity. - - -### Applications in Autonomous Driving -DDS is widely used in autonomous driving systems, where real-time data exchange is crucial. A typical use case involves high-frequency sensor data transmission and decision-making coordination between vehicle subsystems. - -For instance, a LiDAR sensor generates millions of data points per second, which need to be shared with multiple modules. DDS allows this data to be published once and received by multiple subscribers, including perception, localization, and mapping components. After processing, the detected objects and road features are forwarded to the path planning module, which calculates the vehicle's next movement. Finally, control commands are sent to the vehicle actuators, ensuring precise execution. - -This real-time data flow must occur within milliseconds to enable safe autonomous driving. DDS ensures minimal transmission delay, enabling rapid response to dynamic road conditions. In emergency scenarios, such as detecting a pedestrian or sudden braking by a nearby vehicle, DDS facilitates instant data propagation, allowing the system to take immediate corrective action. - -For example: [Autoware](https://www.autoware.org/)—an open-source autonomous driving software stack—uses DDS to handle high-throughput communication across its modules. For example, the **Perception** stack publishes detected objects from LiDAR and camera sensors to a shared topic, which is then consumed by the **Planning** module in real-time. Using DDS allows each subsystem to scale independently while preserving low-latency and deterministic communication. - -### Publish-Subscribe Model and Data Transmission -Traditional client-server communication requires a centralized server to manage data exchange. This architecture introduces several drawbacks, including increased latency and network congestion, which can be problematic in real-time automotive applications. - -DDS adopts a publish-subscribe model, enabling direct communication between system components. Instead of relying on a central entity to relay messages, DDS allows each participant to subscribe to relevant topics and receive updates as soon as new data becomes available. This approach reduces dependency on centralized infrastructure and improves overall system performance. - -For example, in an automotive perception system, LiDAR, radar, and cameras continuously publish sensor data. Multiple subscribers, including object detection, lane recognition, and obstacle avoidance modules, can access this data simultaneously without additional network overhead. DDS automatically manages message distribution, ensuring efficient resource utilization. - -DDS supports multiple transport mechanisms to optimize communication efficiency: -- **Shared memory transport**: Ideal for ultra-low-latency communication within an ECU, minimizing processing overhead. -- **UDP or TCP/IP**: Used for inter-device communication, such as V2X applications where vehicles exchange safety-critical messages. -- **Automatic participant discovery**: Eliminates the need for manual configuration, allowing DDS nodes to detect and establish connections dynamically. - -#### Comparison of DDS and Traditional Communication Methods - -| **Feature** | **Traditional Client-Server Architecture** | **DDS Publish-Subscribe Model** | -|----------------------|--------------------------------|---------------------------| -| **Data Transmission** | Relies on a central server | Direct peer-to-peer communication | -| **Latency** | Higher latency | Low latency | -| **Scalability** | Limited by server capacity | Suitable for large-scale systems | -| **Reliability** | Server failure affects the whole system | No single point of failure | -| **Use Cases** | Small-scale applications | V2X, autonomous driving | - -These features make DDS a highly adaptable solution for automotive software engineers seeking to develop scalable, real-time communication frameworks. - -Here is an [installation guide](https://learn.arm.com/install-guides/cyclonedds) on how to install open-source DDS on an Arm platform. - diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md deleted file mode 100644 index 2fee5f4f91..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: Split into multiple cloud container instances -weight: 4 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -### System Architecture and Component Design - -Once we understand Safety Island —- the safety-critical subsystem responsible for executing essential control logic in automotive systems—and the DDS (Data Distribution Service) communication mechanism, we can refactor the original OpenAD Kit architecture, which previously ran on a single instance, by splitting it into two independent execution environments. - -In the previous [learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/), OpenAD Kit launched three containers on the same hardware, each responsible for the `Simulation environment`, `Visualization` and `Planning-Control` components. - -In this session, we decouple the simulation and visualization system from the on-vehicle autonomous driving stack, deploying them on two separate Arm-based instances. Communication between the two is facilitated through the ROS 2 software architecture, using DDS as the underlying middleware, which enables real-time data exchange and modular integration across distributed systems. - -This architecture brings several practical benefits: - -- Improved system stability: Decoupling reduces the risk of resource contention or runtime interference between visualization workloads and safety-critical components. - -- Leverages DDS’s QoS and discovery features: Ensures robust, real-time, and scalable communication across nodes without requiring a central broker or manual configuration. - -- Better scalability and performance tuning: Each instance can be provisioned with resources optimized for its specific task. - -- Supports modular development and CI/CD: Teams can develop, test, and iterate on components independently, enabling better DevOps practices for autonomous systems. - - -!!! Consider a simple network diagram showing the two EC2s, DDS, and container roles. !!! - -### Networking Setting - -First, launch two Arm instances (either cloud instances or on-premise servers). - -The specifications of the two Arm instances don’t need to be identical. In my tests, 16 CPUs and 32GB of RAM have already provided good performance. - -Once the two machines are up and running, you need to decide where the `Planning-Control` will execute. After making the decision, the other machine will run `Simulation environment` and `Visualization`. - -In order for the two instances to communicate, you must configure them to allow network access to each other. For example, in AWS EC2, both instances should belong to the same security group. - -In the AWS EC2 Security Groups inbound rules setting, ensure that there is a rule allowing traffic from other members of the same security group (i.e., the security group itself as a source). Outbound traffic is typically allowed by default and usually does not require modification. -This setup ensures that both EC2 instances can discover and communicate with each other over the network as required by ROS 2 and DDS. - -Once both of your machines are set up, please note down the IP addresses of both machines, as you will need them for the upcoming configuration. - -### New Docker YAML Configur Setting - -Ensure that Docker is installed on your development environment, then clone the same repository from the previous learning path onto both machines. - -```bash -git clone https://github.com/autowarefoundation/openadkit_demo.autoware.git - -cd openadkit_demo.autoware -cp docker/docker-compose.yml docker/docker-compose-2ins.yml -touch docker/cycloneDDS.xml -``` - -First, you need create xml file called `cycloneDDS.xml` - -This CycloneDDS XML configuration file is used to customize the behavior of the CycloneDDS middleware, which is used in ROS2 for inter-process communication (IPC) and network communication over DDS. -Please replace the previously written IP addresses of the two machines with the 192.168.xx.yy and 192.168.aa.bb in the configuration. - -```xml - - - - false - - - - - - 1000 - auto - - - - - - - /root/workspace/cyclonelog.log - config - - - -``` - -{{% notice Note %}} -You can find the more detail about CycloneDDS setting [Configuration](https://cyclonedds.io/docs/cyclonedds/latest/config/config_file_reference.html#cyclonedds-domain-internal-socketreceivebuffersize) -{{% /notice %}} - -Next, we need to configure the newly created YML file `docker-compose-2ins.yml`. - -- Use the host network instead of the docker network bridge. -This is necessary for allowing the all of three of containers to access the host network interfaces. - -```YAML - visualizer: - network_mode: host -``` - -- Add the newly created XML file as an environment variable in each container, and mount the current folder as `/root/workspace`. Configure the environment variable CYCLONEDDS_URI in the docker-compose.yaml file for all containers to ensure they use this configuration file. - -```YAML - volumes: - - .:/root/workspace - environment: - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml -``` - -- Remove the dependency between the `planning-control` and `simulator containers`. -Since these containers will now be launched independently, it is necessary to eliminate the dependency configuration between them. - -```YAML - planning-control: - depends_on: - - simulator -``` - -Here is the complete XML file: -```YAML -services: - simulator: - image: ghcr.io/autowarefoundation/demo-packages:simulator - container_name: simulator - network_mode: host - volumes: - - ./etc/simulation:/autoware/scenario-sim - - .:/root/workspace - - ./log:/root/.ros/log - environment: - - ROS_DOMAIN_ID=88 - - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml - command: > - ros2 launch scenario_test_runner scenario_test_runner.launch.py - record:=false - scenario:=/autoware/scenario-sim/scenario/yield_maneuver_demo.yaml - sensor_model:=sample_sensor_kit - vehicle_model:=sample_vehicle - initialize_duration:=90 - global_timeout:=$TIMEOUT - global_frame_rate:=20 - launch_autoware:=false - launch_rviz:=false - - planning-control: - image: ghcr.io/autowarefoundation/demo-packages:planning-control - container_name: planning-control - network_mode: host - deploy: - volumes: - - ./etc/simulation:/autoware/scenario-sim - - $CONF_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/autoware_behavior_path_static_obstacle_avoidance_module/static_obstacle_avoidance.param.yaml - - $COMMON_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/common/common.param.yaml - - .:/root/workspace - - ./log:/root/.ros/log - environment: - - ROS_DOMAIN_ID=88 - - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml - command: > - ros2 launch autoware_launch planning_simulator.launch.xml - map_path:=/autoware/scenario-sim/map - vehicle_model:=sample_vehicle - sensor_model:=sample_sensor_kit - scenario_simulation:=true - rviz:=false - perception/enable_traffic_light:=false - - visualizer: - image: ghcr.io/autowarefoundation/demo-packages:visualizer - network_mode: host - container_name: visualizer - volumes: - - ./etc/simulation:/autoware/scenario-sim - - .:/root/workspace - - ./log:/root/.ros/log - ports: - - 6080:6080 - - 5999:5999 - environment: - - ROS_DOMAIN_ID=88 - - VNC_ENABLED=true - - RVIZ_CONFIG=/autoware/scenario-sim/rviz/scenario_simulator.rviz - - NGROK_AUTHTOKEN=${NGROK_AUTHTOKEN} - - NGROK_URL=${NGROK_URL} - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml - command: >- - sleep infinity -``` -Before moving to the next step, make sure that `docker-compose-2ins.yml` and `cycloneDDS.xml` are already present on both instances. - -### Network Latency and Performance Optimization - -This distributed network architecture may lead to IP packet fragmentation, which can consume system memory under certain network conditions. - -To mitigate issues related to IP fragmentation and socket receive buffer limitations, you can tune the network configuration to support heavy UDP load. - -```bash -sudo su -sysctl net.ipv4.ipfrag_time=3 -sysctl net.ipv4.ipfrag_high_thresh=134217728 -sysctl -w net.core.rmem_max=2147483647 -exit -``` - -To make the configuration permanent across reboot, modify the create the file `/etc/sysctl.d/10-cyclone-max.conf`: - -```bash -sudo su -cat << EOF > /etc/sysctl.d/10-cyclone-max.conf -net.core.rmem_max=2147483647 -net.ipv4.ipfrag_time=3 -net.ipv4.ipfrag_high_thresh=134217728 # (128 MB) -EOF -exit -``` - -Reference: - - [Autoware dds-setting](https://autowarefoundation.github.io/autoware-documentation/main/installation/additional-settings-for-developers/network-configuration/dds-settings/) - - [ROS2 documentation](https://docs.ros.org/en/humble/How-To-Guides/DDS-tuning.html#cyclone-dds-tuning) - - -### Execution - -Next, you need to configure the execution commands on the different machines. -Copy the corresponding commands into the `openadkit_demo.autoware/docker` directory of two instances. - -{{< tabpane code=true >}} - {{< tab header="Planning-Control" language="bash">}} - #!/bin/bash - # Configure the environment variables - export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - export SCRIPT_DIR="$( pwd )" - export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml - export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml - export CONF_FILE=$CONF_FILE_FAIL - export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml - export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN - export NGROK_URL=$NGROK_URL - # Start planning-control - echo "Running planning v1.." - CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up planning-control --abort-on-container-exit - {{< /tab >}} - {{< tab header="Visualizer & Simulator" language="bash">}} - #!/bin/bash - # Configure the environment variables - export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - export SCRIPT_DIR="$( pwd )" - export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml - export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml - export CONF_FILE=$CONF_FILE_FAIL - export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml - export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN - export NGROK_URL=$NGROK_URL - - # Start visualizer and show logs - docker compose -f "$SCRIPT_DIR/docker-compose.yml" up visualizer -d - echo "Waiting 10 seconds for visualizer to start..." - sleep 10 - docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" logs visualizer - - # Start simulator - echo "Running simulator v1.." - TIMEOUT=70 CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up simulator --abort-on-container-exit {{< /tab >}} -{{< /tabpane >}} - - -Once both machines execute their respective scripts, the visualizer will provide a link that can be accessed through the public IP. When you access the link, you will see that the demo execution will be very similar to the [previous learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/4_run_openadkit/). - -The only difference is that the containers are distributed across two physical machines, and at the underlying layer of the demo, there will be frequent packet exchanges. - diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md deleted file mode 100644 index 296f5e2bb9..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Split into cloud container instances and RD-1AE Corellium -weight: 5 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -### Overview and Technical Features of the Corellium Platform - - -### Integration of Corellium with AWS DDS Systems - - -### Security and Real-Time Considerations - - -### Applicability and Comparative Analysis of This Solution diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md deleted file mode 100644 index dbc500a6dc..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Execute multiple instance -weight: 6 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -## \ No newline at end of file diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md deleted file mode 100644 index 8c9e8ec1a7..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Early Deployment of Automotive Functional Safety application on Neoverse V3AE - -minutes_to_complete: 60 - -who_is_this_for: This is an advanced automotive software development topic, focusing on the early-stage development of mission-critical software on Arm RD-1 AE. It explores how to leverage virtual platform technology to meet functional safety regulations in software development. - -learning_objectives: - - Introduction to Automotive Functional Safety(ISO-26262). Understand the ISO-26262 architecture and the importance of a structured software development flow in achieving functional safety compliance. - - Introduction to DDS (Data Distribution Service). Learn how DDS enables real-time, reliable communication between distributed automotive software components. - - Distributed Development for Functional Safety. Learn how to split the simulation platform into two independent units and leverage distributed development architecture to ensure functional safety. - -prerequisites: - - Two Arm-based Neoverse cloud instances or a local Arm Neoverse Linux computer with at least 16 CPUs and 32GB of RAM. - - Completion of the previous learning path. http://learn.arm.com/learning-paths/automotive/openadkit1_container/ - - A Corellium virtual platform account. https://www.corellium.com/ - - Basic knowledge of Docker operations. -author: - - Odin Shen - - Julien Jayat - -### Tags -skilllevels: Advanced -subjects: Containers and Virtualization -armips: - - Neoverse V3AE -tools_software_languages: - - Python - - Docker - - ROS2 -operatingsystems: - - Linux - - -further_reading: - - resource: - title: OpenAD Kit learning path - link: https://learn.arm.com/learning-paths/automotive/openadkit1_container/ - type: documentation - - resource: - title: DDS - link: https://github.com/autowarefoundation/openadkit_demo.autoware/ - type: documentation - - - -### FIXED, DO NOT MODIFY -# ================================================================================ -weight: 1 # _index.md always has weight of 1 to order correctly -layout: "learningpathall" # All files under learning paths have this same wrapper -learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. ---- diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md deleted file mode 100644 index c3db0de5a2..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -# ================================================================================ -# FIXED, DO NOT MODIFY THIS FILE -# ================================================================================ -weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. -title: "Next Steps" # Always the same, html page title. -layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. ---- From e40040cc2d527eb240c3ad4f20d8d5180be2994e Mon Sep 17 00:00:00 2001 From: Odin Shen Coder Date: Thu, 24 Apr 2025 17:24:00 +0100 Subject: [PATCH 103/122] Add 2nd automotive SOAFEE learning path --- .../patches/1_functional_safety.md | 126 ++++++++ .../patches/2_DataDistributionService.md | 65 ++++ .../patches/3_aws_setup.md | 277 ++++++++++++++++++ .../patches/4_corellium_setup.md | 18 ++ .../5_multiple_instances_executeion.md | 9 + .../patches/_index.md | 52 ++++ .../patches/_next-steps.md | 8 + 7 files changed, 555 insertions(+) create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md create mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md new file mode 100644 index 0000000000..1b263746a1 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md @@ -0,0 +1,126 @@ +--- +title: Functional Safety for automotive software development +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## What is Functional Safety? + +[Functional Safety](https://en.wikipedia.org/wiki/Functional_safety) refers to a system's ability to detect potential faults and respond appropriately to ensure that the system remains in a safe state, preventing harm to individuals or damage to equipment. + +This is particularly important in **automotive, autonomous driving, medical devices, industrial control, robotics and aerospace** applications, where system failures can lead to severe consequences. + +In software development, Functional Safety focuses on minimizing risks through **software design, testing, and validation** to ensure that critical systems operate in a predictable, reliable, and verifiable manner. This means developers must consider: +- **Error detection mechanisms** +- **Exception handling** +- **Redundancy design** +- **Development processes compliant with safety standards** + +### Definition and Importance of Functional Safety + +The core of Functional Safety lies in **risk management**, which aims to reduce the impact of system failures. + +In autonomous vehicles, Functional Safety ensures that if sensor data is incorrect, the system can enter a **safe state**, preventing incorrect driving decisions. + +Three of core objectives of Functional Safety are: +1. **Prevention** + - Reducing the likelihood of errors through rigorous software development processes and testing. In the electric vehicle, the battery systems monitor temperature to prevent overheating. +2. **Detection** + - Quickly identifying errors using built-in diagnostic mechanisms (e.g., Built-in Self-Test, BIST). +3. **Mitigation** + - Controlling the impact of failures to ensure the overall safety of the system. + +This approach is critical in applications such as **autonomous driving, flight control, and medical implants**, where failures can result in **severe consequences**. + +### ISO 26262 Standard and Applications + +[ISO 26262](https://www.iso.org/standard/68383.html) is a functional safety standard specifically for **automotive electronics and software systems**. It defines a comprehensive safety lifecycle, covering all phases from **requirement analysis, design, development, testing, to maintenance**. + +Key Concepts of ISO 26262: +- **ASIL (Automotive Safety Integrity Level)** + - Evaluates the risk level of different system components (A, B, C, D, where **D represents the highest safety requirement**). + - For example: ASIL A can be Dashboard light failure (low risk) and ASIL D is Brake system failure (high risk). + https://en.wikipedia.org/wiki/Automotive_Safety_Integrity_Level +- **HARA (Hazard Analysis and Risk Assessment)** + - Analyzes hazards and assesses risks to determine necessary safety measures. +- **Safety Mechanisms** + - Includes error detection, fault tolerance, and fail-safe modes to ensure safe operation. + +List some of typical application scenarios: +- **Autonomous Driving Systems**: + - Ensures that even if sensors (e.g., LiDAR, radar, cameras) provide faulty data, the vehicle will not make dangerous decisions. +- **Powertrain Control**: + - Prevents braking system failures that could lead to loss of control. +- **Battery Management System (BMS)**: + - Prevents battery overheating or excessive discharge in electric vehicles. + +For more details, you can check this video: [What is Functional Safety?](https://www.youtube.com/watch?v=R0CPzfYHdpQ) + + +### Safety Island + +In automotive systems, a **General ECU (Electronic Control Unit)** typically runs non-critical tasks such as infotainment or navigation, whereas a **Safety Island** is dedicated to executing safety-critical control logic (e.g., braking, steering) with strong isolation, redundancy, and determinism. + +| Feature | General ECU | Safety Island | +|------------------------|----------------------------|--------------------------------------| +| Purpose | Comfort / non-safety logic | Safety-critical decision making | +| OS/Runtime | Linux, Android | RTOS, Hypervisor, or bare-metal | +| Isolation | Soft partitioning | Hard isolation (hardware-enforced) | +| Functional Safety Req | None to moderate | ISO 26262 ASIL-B to ASIL-D compliant | +| Fault Handling | Best-effort recovery | Deterministic safe-state response | + +This contrast highlights why safety-focused software needs a dedicated hardware domain with certified execution behavior. + +**Safety Island** is an independent safety subsystem separate from the main processor. It is responsible for monitoring and managing system safety. If the main processor fails or becomes inoperable, Safety Island can take over critical safety functions such as **deceleration, stopping, and fault handling** to prevent catastrophic system failures. + +Key Functions of Safety Island +- **Monitoring System Health** + - Continuously monitors the operational status of the main processor (e.g., ADAS control unit, ECU) and detects potential errors or anomalies. +- **Fault Detection and Isolation** + - Independently evaluates and initiates emergency handling if the main processing unit encounters errors, overheating, computational failures, or unresponsiveness. +- **Providing Essential Safety Functions** + - Even if the main system crashes, Safety Island can still execute minimal safety operations, such as: + - Autonomous Vehicles → Safe stopping (Fail-Safe Mode) + - Industrial Equipment → Emergency power cutoff or speed reduction + + + + +### Integration of Safety Island and Functional Safety + +Safety Island plays a critical role in Functional Safety by ensuring that the system can handle high-risk scenarios and minimize catastrophic failures. + +How Safety Island Enhances Functional Safety +1. **Acts as an Independent Redundant Safety Layer** + - Even if the main system fails, it can still operate independently. +2. **Supports ASIL-D Safety Level** + - Monitors ECU health status and executes emergency safety strategies (e.g., emergency braking). +3. **Provides Independent Fault Detection and Recovery Mechanisms** + - **Fail-Safe**: Activates a **safe mode**, such as limiting vehicle speed or switching to manual control. + - **Fail-Operational**: Ensures that high-safety applications (e.g., aerospace systems) can continue operating under certain conditions. + +For more insights on **Arm's Functional Safety solutions**, you can refer to: [Arm Functional Safety Compute Blog](https://community.arm.com/arm-community-blogs/b/automotive-blog/posts/functional-safety-compute) + + +### Impact of Functional Safety on Software Development Processes + +Functional Safety impacts **both hardware and software development**, particularly in areas such as requirement changes, version management, and testing validation. +For example, in ASIL-D level applications, every code modification requires a complete impact analysis and regression testing to ensure that new changes do not introduce additional risks. + +List the Functional Safety Requirements in Software Development: +- **Requirement Specification** + - Clearly defining **safety-critical requirements** and conducting risk assessments. +- **Safety-Oriented Programming** + - Following **MISRA C, CERT C/C++ standards** and using static analysis tools to detect errors. +- **Fault Handling Mechanisms** + - Implementing **redundancy design and health monitoring** to handle anomalies. +- **Testing and Verification** + - Using **Hardware-in-the-Loop (HIL)** testing to ensure software safety in real hardware environments. +- **Version Management and Change Control** + - Using **Git, JIRA, Polarion** to track changes for safety audits. + +This learning path builds on the previous [learning path](https://learn.arm.com/learning-paths/automotive/openadkit1_container) and introduces how to incorporate Functional Safety design processes in the early stages of automotive software development. +By establishing an ASIL Partitioning software development environment and leveraging [**SOAFEE**](https://www.soafee.io/) technologies, developers can enhance software consistency and maintainability in Functional Safety applications. + diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md new file mode 100644 index 0000000000..c50e6712d1 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md @@ -0,0 +1,65 @@ +--- +title: How to use Data Distribution Service (DDS) +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +### Introduction to DDS +Data Distribution Service (DDS) is a real-time, high-performance middleware designed for distributed systems, particularly in automotive software development for autonomous driving and advanced driver assistance systems (ADAS). Its decentralized architecture provides scalable, low-latency, and reliable data exchange, making it an essential component in managing high-frequency sensor data. + +In modern vehicles, multiple sensors, such as LiDAR, radar, and cameras, must communicate efficiently with computing modules and decision-making units. DDS enables seamless data transmission within the vehicle, ensuring that perception, localization, and control systems receive the necessary data with minimal delay. Additionally, it plays a crucial role in vehicle-to-infrastructure (V2X) communication, allowing vehicles to exchange information with traffic signals, road sensors, and other connected systems to enhance situational awareness and safety. + + +### Why Automotive Software Needs DDS +Modern automotive software architectures, such as SOAFEE, require deterministic data communication to ensure real-time coordination between sensors, ECUs, and computing modules. Traditional client-server communication models often introduce latency and bottlenecks, whereas DDS provides a direct, decentralized solution that enhances reliability and scalability. + +A key advantage of DDS is its ability to enable direct data exchange between system components without relying on a central server. This reduces the risk of a single point of failure and minimizes delays, which is crucial for autonomous driving applications where milliseconds can make a difference. For example, a LiDAR sensor publishing obstacle detection data can simultaneously send information to multiple subscribers, including perception, SLAM (Simultaneous Localization and Mapping), and motion planning modules. This parallel data distribution ensures all relevant subsystems have the latest environmental data without requiring multiple separate transmissions. + +Additionally, DDS provides a flexible Quality of Service (QoS) configuration, allowing engineers to fine-tune communication parameters based on system requirements. Low-latency modes are ideal for real-time decision-making in vehicle control, while high-reliability configurations ensure data integrity in safety-critical applications like V2X communication. + + +### Architecture and Operation +DDS is based on a data-centric publish-subscribe (DCPS) model, allowing producers and consumers of data to communicate without direct dependencies. This modular approach enhances system flexibility and maintainability, making it well-suited for complex automotive environments. + +In DDS, all participants operate within a **domain**, which provides logical isolation between different applications. Each domain contains multiple **topics**, representing specific data types such as vehicle speed, obstacle detection, or sensor fusion results. **Publishers** use **DataWriters** to send data to these topics, while **subscribers** use **DataReaders** to receive the data. This architecture supports concurrent data processing, ensuring that multiple modules can work with the same data stream simultaneously. + +For example, in an autonomous vehicle, LiDAR, radar, and cameras continuously generate large amounts of sensor data. The perception module subscribes to these sensor topics, processes the data, and then publishes detected objects and road conditions to other components like path planning and motion control. Since DDS automatically handles participant discovery and message distribution, engineers do not need to manually configure communication paths, reducing development complexity. + + +### Applications in Autonomous Driving +DDS is widely used in autonomous driving systems, where real-time data exchange is crucial. A typical use case involves high-frequency sensor data transmission and decision-making coordination between vehicle subsystems. + +For instance, a LiDAR sensor generates millions of data points per second, which need to be shared with multiple modules. DDS allows this data to be published once and received by multiple subscribers, including perception, localization, and mapping components. After processing, the detected objects and road features are forwarded to the path planning module, which calculates the vehicle's next movement. Finally, control commands are sent to the vehicle actuators, ensuring precise execution. + +This real-time data flow must occur within milliseconds to enable safe autonomous driving. DDS ensures minimal transmission delay, enabling rapid response to dynamic road conditions. In emergency scenarios, such as detecting a pedestrian or sudden braking by a nearby vehicle, DDS facilitates instant data propagation, allowing the system to take immediate corrective action. + +For example: [Autoware](https://www.autoware.org/)—an open-source autonomous driving software stack—uses DDS to handle high-throughput communication across its modules. For example, the **Perception** stack publishes detected objects from LiDAR and camera sensors to a shared topic, which is then consumed by the **Planning** module in real-time. Using DDS allows each subsystem to scale independently while preserving low-latency and deterministic communication. + +### Publish-Subscribe Model and Data Transmission +Traditional client-server communication requires a centralized server to manage data exchange. This architecture introduces several drawbacks, including increased latency and network congestion, which can be problematic in real-time automotive applications. + +DDS adopts a publish-subscribe model, enabling direct communication between system components. Instead of relying on a central entity to relay messages, DDS allows each participant to subscribe to relevant topics and receive updates as soon as new data becomes available. This approach reduces dependency on centralized infrastructure and improves overall system performance. + +For example, in an automotive perception system, LiDAR, radar, and cameras continuously publish sensor data. Multiple subscribers, including object detection, lane recognition, and obstacle avoidance modules, can access this data simultaneously without additional network overhead. DDS automatically manages message distribution, ensuring efficient resource utilization. + +DDS supports multiple transport mechanisms to optimize communication efficiency: +- **Shared memory transport**: Ideal for ultra-low-latency communication within an ECU, minimizing processing overhead. +- **UDP or TCP/IP**: Used for inter-device communication, such as V2X applications where vehicles exchange safety-critical messages. +- **Automatic participant discovery**: Eliminates the need for manual configuration, allowing DDS nodes to detect and establish connections dynamically. + +#### Comparison of DDS and Traditional Communication Methods + +| **Feature** | **Traditional Client-Server Architecture** | **DDS Publish-Subscribe Model** | +|----------------------|--------------------------------|---------------------------| +| **Data Transmission** | Relies on a central server | Direct peer-to-peer communication | +| **Latency** | Higher latency | Low latency | +| **Scalability** | Limited by server capacity | Suitable for large-scale systems | +| **Reliability** | Server failure affects the whole system | No single point of failure | +| **Use Cases** | Small-scale applications | V2X, autonomous driving | + +These features make DDS a highly adaptable solution for automotive software engineers seeking to develop scalable, real-time communication frameworks. + +Here is an [installation guide](https://learn.arm.com/install-guides/cyclonedds) on how to install open-source DDS on an Arm platform. + diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md new file mode 100644 index 0000000000..2fee5f4f91 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md @@ -0,0 +1,277 @@ +--- +title: Split into multiple cloud container instances +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +### System Architecture and Component Design + +Once we understand Safety Island —- the safety-critical subsystem responsible for executing essential control logic in automotive systems—and the DDS (Data Distribution Service) communication mechanism, we can refactor the original OpenAD Kit architecture, which previously ran on a single instance, by splitting it into two independent execution environments. + +In the previous [learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/), OpenAD Kit launched three containers on the same hardware, each responsible for the `Simulation environment`, `Visualization` and `Planning-Control` components. + +In this session, we decouple the simulation and visualization system from the on-vehicle autonomous driving stack, deploying them on two separate Arm-based instances. Communication between the two is facilitated through the ROS 2 software architecture, using DDS as the underlying middleware, which enables real-time data exchange and modular integration across distributed systems. + +This architecture brings several practical benefits: + +- Improved system stability: Decoupling reduces the risk of resource contention or runtime interference between visualization workloads and safety-critical components. + +- Leverages DDS’s QoS and discovery features: Ensures robust, real-time, and scalable communication across nodes without requiring a central broker or manual configuration. + +- Better scalability and performance tuning: Each instance can be provisioned with resources optimized for its specific task. + +- Supports modular development and CI/CD: Teams can develop, test, and iterate on components independently, enabling better DevOps practices for autonomous systems. + + +!!! Consider a simple network diagram showing the two EC2s, DDS, and container roles. !!! + +### Networking Setting + +First, launch two Arm instances (either cloud instances or on-premise servers). + +The specifications of the two Arm instances don’t need to be identical. In my tests, 16 CPUs and 32GB of RAM have already provided good performance. + +Once the two machines are up and running, you need to decide where the `Planning-Control` will execute. After making the decision, the other machine will run `Simulation environment` and `Visualization`. + +In order for the two instances to communicate, you must configure them to allow network access to each other. For example, in AWS EC2, both instances should belong to the same security group. + +In the AWS EC2 Security Groups inbound rules setting, ensure that there is a rule allowing traffic from other members of the same security group (i.e., the security group itself as a source). Outbound traffic is typically allowed by default and usually does not require modification. +This setup ensures that both EC2 instances can discover and communicate with each other over the network as required by ROS 2 and DDS. + +Once both of your machines are set up, please note down the IP addresses of both machines, as you will need them for the upcoming configuration. + +### New Docker YAML Configur Setting + +Ensure that Docker is installed on your development environment, then clone the same repository from the previous learning path onto both machines. + +```bash +git clone https://github.com/autowarefoundation/openadkit_demo.autoware.git + +cd openadkit_demo.autoware +cp docker/docker-compose.yml docker/docker-compose-2ins.yml +touch docker/cycloneDDS.xml +``` + +First, you need create xml file called `cycloneDDS.xml` + +This CycloneDDS XML configuration file is used to customize the behavior of the CycloneDDS middleware, which is used in ROS2 for inter-process communication (IPC) and network communication over DDS. +Please replace the previously written IP addresses of the two machines with the 192.168.xx.yy and 192.168.aa.bb in the configuration. + +```xml + + + + false + + + + + + 1000 + auto + + + + + + + /root/workspace/cyclonelog.log + config + + + +``` + +{{% notice Note %}} +You can find the more detail about CycloneDDS setting [Configuration](https://cyclonedds.io/docs/cyclonedds/latest/config/config_file_reference.html#cyclonedds-domain-internal-socketreceivebuffersize) +{{% /notice %}} + +Next, we need to configure the newly created YML file `docker-compose-2ins.yml`. + +- Use the host network instead of the docker network bridge. +This is necessary for allowing the all of three of containers to access the host network interfaces. + +```YAML + visualizer: + network_mode: host +``` + +- Add the newly created XML file as an environment variable in each container, and mount the current folder as `/root/workspace`. Configure the environment variable CYCLONEDDS_URI in the docker-compose.yaml file for all containers to ensure they use this configuration file. + +```YAML + volumes: + - .:/root/workspace + environment: + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml +``` + +- Remove the dependency between the `planning-control` and `simulator containers`. +Since these containers will now be launched independently, it is necessary to eliminate the dependency configuration between them. + +```YAML + planning-control: + depends_on: + - simulator +``` + +Here is the complete XML file: +```YAML +services: + simulator: + image: ghcr.io/autowarefoundation/demo-packages:simulator + container_name: simulator + network_mode: host + volumes: + - ./etc/simulation:/autoware/scenario-sim + - .:/root/workspace + - ./log:/root/.ros/log + environment: + - ROS_DOMAIN_ID=88 + - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml + command: > + ros2 launch scenario_test_runner scenario_test_runner.launch.py + record:=false + scenario:=/autoware/scenario-sim/scenario/yield_maneuver_demo.yaml + sensor_model:=sample_sensor_kit + vehicle_model:=sample_vehicle + initialize_duration:=90 + global_timeout:=$TIMEOUT + global_frame_rate:=20 + launch_autoware:=false + launch_rviz:=false + + planning-control: + image: ghcr.io/autowarefoundation/demo-packages:planning-control + container_name: planning-control + network_mode: host + deploy: + volumes: + - ./etc/simulation:/autoware/scenario-sim + - $CONF_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/autoware_behavior_path_static_obstacle_avoidance_module/static_obstacle_avoidance.param.yaml + - $COMMON_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/common/common.param.yaml + - .:/root/workspace + - ./log:/root/.ros/log + environment: + - ROS_DOMAIN_ID=88 + - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml + command: > + ros2 launch autoware_launch planning_simulator.launch.xml + map_path:=/autoware/scenario-sim/map + vehicle_model:=sample_vehicle + sensor_model:=sample_sensor_kit + scenario_simulation:=true + rviz:=false + perception/enable_traffic_light:=false + + visualizer: + image: ghcr.io/autowarefoundation/demo-packages:visualizer + network_mode: host + container_name: visualizer + volumes: + - ./etc/simulation:/autoware/scenario-sim + - .:/root/workspace + - ./log:/root/.ros/log + ports: + - 6080:6080 + - 5999:5999 + environment: + - ROS_DOMAIN_ID=88 + - VNC_ENABLED=true + - RVIZ_CONFIG=/autoware/scenario-sim/rviz/scenario_simulator.rviz + - NGROK_AUTHTOKEN=${NGROK_AUTHTOKEN} + - NGROK_URL=${NGROK_URL} + - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml + command: >- + sleep infinity +``` +Before moving to the next step, make sure that `docker-compose-2ins.yml` and `cycloneDDS.xml` are already present on both instances. + +### Network Latency and Performance Optimization + +This distributed network architecture may lead to IP packet fragmentation, which can consume system memory under certain network conditions. + +To mitigate issues related to IP fragmentation and socket receive buffer limitations, you can tune the network configuration to support heavy UDP load. + +```bash +sudo su +sysctl net.ipv4.ipfrag_time=3 +sysctl net.ipv4.ipfrag_high_thresh=134217728 +sysctl -w net.core.rmem_max=2147483647 +exit +``` + +To make the configuration permanent across reboot, modify the create the file `/etc/sysctl.d/10-cyclone-max.conf`: + +```bash +sudo su +cat << EOF > /etc/sysctl.d/10-cyclone-max.conf +net.core.rmem_max=2147483647 +net.ipv4.ipfrag_time=3 +net.ipv4.ipfrag_high_thresh=134217728 # (128 MB) +EOF +exit +``` + +Reference: + - [Autoware dds-setting](https://autowarefoundation.github.io/autoware-documentation/main/installation/additional-settings-for-developers/network-configuration/dds-settings/) + - [ROS2 documentation](https://docs.ros.org/en/humble/How-To-Guides/DDS-tuning.html#cyclone-dds-tuning) + + +### Execution + +Next, you need to configure the execution commands on the different machines. +Copy the corresponding commands into the `openadkit_demo.autoware/docker` directory of two instances. + +{{< tabpane code=true >}} + {{< tab header="Planning-Control" language="bash">}} + #!/bin/bash + # Configure the environment variables + export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + export SCRIPT_DIR="$( pwd )" + export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml + export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml + export CONF_FILE=$CONF_FILE_FAIL + export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml + export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN + export NGROK_URL=$NGROK_URL + # Start planning-control + echo "Running planning v1.." + CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up planning-control --abort-on-container-exit + {{< /tab >}} + {{< tab header="Visualizer & Simulator" language="bash">}} + #!/bin/bash + # Configure the environment variables + export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + export SCRIPT_DIR="$( pwd )" + export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml + export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml + export CONF_FILE=$CONF_FILE_FAIL + export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml + export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN + export NGROK_URL=$NGROK_URL + + # Start visualizer and show logs + docker compose -f "$SCRIPT_DIR/docker-compose.yml" up visualizer -d + echo "Waiting 10 seconds for visualizer to start..." + sleep 10 + docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" logs visualizer + + # Start simulator + echo "Running simulator v1.." + TIMEOUT=70 CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up simulator --abort-on-container-exit {{< /tab >}} +{{< /tabpane >}} + + +Once both machines execute their respective scripts, the visualizer will provide a link that can be accessed through the public IP. When you access the link, you will see that the demo execution will be very similar to the [previous learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/4_run_openadkit/). + +The only difference is that the containers are distributed across two physical machines, and at the underlying layer of the demo, there will be frequent packet exchanges. + diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md new file mode 100644 index 0000000000..296f5e2bb9 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md @@ -0,0 +1,18 @@ +--- +title: Split into cloud container instances and RD-1AE Corellium +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +### Overview and Technical Features of the Corellium Platform + + +### Integration of Corellium with AWS DDS Systems + + +### Security and Real-Time Considerations + + +### Applicability and Comparative Analysis of This Solution diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md new file mode 100644 index 0000000000..dbc500a6dc --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md @@ -0,0 +1,9 @@ +--- +title: Execute multiple instance +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## \ No newline at end of file diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md new file mode 100644 index 0000000000..8c9e8ec1a7 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md @@ -0,0 +1,52 @@ +--- +title: Early Deployment of Automotive Functional Safety application on Neoverse V3AE + +minutes_to_complete: 60 + +who_is_this_for: This is an advanced automotive software development topic, focusing on the early-stage development of mission-critical software on Arm RD-1 AE. It explores how to leverage virtual platform technology to meet functional safety regulations in software development. + +learning_objectives: + - Introduction to Automotive Functional Safety(ISO-26262). Understand the ISO-26262 architecture and the importance of a structured software development flow in achieving functional safety compliance. + - Introduction to DDS (Data Distribution Service). Learn how DDS enables real-time, reliable communication between distributed automotive software components. + - Distributed Development for Functional Safety. Learn how to split the simulation platform into two independent units and leverage distributed development architecture to ensure functional safety. + +prerequisites: + - Two Arm-based Neoverse cloud instances or a local Arm Neoverse Linux computer with at least 16 CPUs and 32GB of RAM. + - Completion of the previous learning path. http://learn.arm.com/learning-paths/automotive/openadkit1_container/ + - A Corellium virtual platform account. https://www.corellium.com/ + - Basic knowledge of Docker operations. +author: + - Odin Shen + - Julien Jayat + +### Tags +skilllevels: Advanced +subjects: Containers and Virtualization +armips: + - Neoverse V3AE +tools_software_languages: + - Python + - Docker + - ROS2 +operatingsystems: + - Linux + + +further_reading: + - resource: + title: OpenAD Kit learning path + link: https://learn.arm.com/learning-paths/automotive/openadkit1_container/ + type: documentation + - resource: + title: DDS + link: https://github.com/autowarefoundation/openadkit_demo.autoware/ + type: documentation + + + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- From 4d2e9906f1d7c4cf063c88365e84f933868d3e73 Mon Sep 17 00:00:00 2001 From: Odin Shen Coder Date: Thu, 24 Apr 2025 17:26:39 +0100 Subject: [PATCH 104/122] Correct the folder location. --- .../{patches => }/1_functional_safety.md | 0 .../{patches => }/2_DataDistributionService.md | 0 .../openadkit2_virtualplatform/{patches => }/3_aws_setup.md | 0 .../openadkit2_virtualplatform/{patches => }/4_corellium_setup.md | 0 .../{patches => }/5_multiple_instances_executeion.md | 0 .../automotive/openadkit2_virtualplatform/{patches => }/_index.md | 0 .../openadkit2_virtualplatform/{patches => }/_next-steps.md | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename content/learning-paths/automotive/openadkit2_virtualplatform/{patches => }/1_functional_safety.md (100%) rename content/learning-paths/automotive/openadkit2_virtualplatform/{patches => }/2_DataDistributionService.md (100%) rename content/learning-paths/automotive/openadkit2_virtualplatform/{patches => }/3_aws_setup.md (100%) rename content/learning-paths/automotive/openadkit2_virtualplatform/{patches => }/4_corellium_setup.md (100%) rename content/learning-paths/automotive/openadkit2_virtualplatform/{patches => }/5_multiple_instances_executeion.md (100%) rename content/learning-paths/automotive/openadkit2_virtualplatform/{patches => }/_index.md (100%) rename content/learning-paths/automotive/openadkit2_virtualplatform/{patches => }/_next-steps.md (100%) diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md b/content/learning-paths/automotive/openadkit2_virtualplatform/1_functional_safety.md similarity index 100% rename from content/learning-paths/automotive/openadkit2_virtualplatform/patches/1_functional_safety.md rename to content/learning-paths/automotive/openadkit2_virtualplatform/1_functional_safety.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md b/content/learning-paths/automotive/openadkit2_virtualplatform/2_DataDistributionService.md similarity index 100% rename from content/learning-paths/automotive/openadkit2_virtualplatform/patches/2_DataDistributionService.md rename to content/learning-paths/automotive/openadkit2_virtualplatform/2_DataDistributionService.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/3_aws_setup.md similarity index 100% rename from content/learning-paths/automotive/openadkit2_virtualplatform/patches/3_aws_setup.md rename to content/learning-paths/automotive/openadkit2_virtualplatform/3_aws_setup.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/4_corellium_setup.md similarity index 100% rename from content/learning-paths/automotive/openadkit2_virtualplatform/patches/4_corellium_setup.md rename to content/learning-paths/automotive/openadkit2_virtualplatform/4_corellium_setup.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md b/content/learning-paths/automotive/openadkit2_virtualplatform/5_multiple_instances_executeion.md similarity index 100% rename from content/learning-paths/automotive/openadkit2_virtualplatform/patches/5_multiple_instances_executeion.md rename to content/learning-paths/automotive/openadkit2_virtualplatform/5_multiple_instances_executeion.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md b/content/learning-paths/automotive/openadkit2_virtualplatform/_index.md similarity index 100% rename from content/learning-paths/automotive/openadkit2_virtualplatform/patches/_index.md rename to content/learning-paths/automotive/openadkit2_virtualplatform/_index.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md b/content/learning-paths/automotive/openadkit2_virtualplatform/_next-steps.md similarity index 100% rename from content/learning-paths/automotive/openadkit2_virtualplatform/patches/_next-steps.md rename to content/learning-paths/automotive/openadkit2_virtualplatform/_next-steps.md From ed7679c1706b92327962fa7c229a8354ad1a2b59 Mon Sep 17 00:00:00 2001 From: Odin Shen Coder Date: Thu, 24 Apr 2025 19:04:50 +0100 Subject: [PATCH 105/122] Remove automotive SOAFEE 2nd learning path --- .../1_functional_safety.md | 126 -------- .../2_DataDistributionService.md | 65 ---- .../openadkit2_virtualplatform/3_aws_setup.md | 277 ------------------ .../4_corellium_setup.md | 18 -- .../5_multiple_instances_executeion.md | 9 - .../openadkit2_virtualplatform/_index.md | 52 ---- .../openadkit2_virtualplatform/_next-steps.md | 8 - 7 files changed, 555 deletions(-) delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/1_functional_safety.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/2_DataDistributionService.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/3_aws_setup.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/4_corellium_setup.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/5_multiple_instances_executeion.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/_index.md delete mode 100644 content/learning-paths/automotive/openadkit2_virtualplatform/_next-steps.md diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/1_functional_safety.md b/content/learning-paths/automotive/openadkit2_virtualplatform/1_functional_safety.md deleted file mode 100644 index 1b263746a1..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/1_functional_safety.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -title: Functional Safety for automotive software development -weight: 2 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -## What is Functional Safety? - -[Functional Safety](https://en.wikipedia.org/wiki/Functional_safety) refers to a system's ability to detect potential faults and respond appropriately to ensure that the system remains in a safe state, preventing harm to individuals or damage to equipment. - -This is particularly important in **automotive, autonomous driving, medical devices, industrial control, robotics and aerospace** applications, where system failures can lead to severe consequences. - -In software development, Functional Safety focuses on minimizing risks through **software design, testing, and validation** to ensure that critical systems operate in a predictable, reliable, and verifiable manner. This means developers must consider: -- **Error detection mechanisms** -- **Exception handling** -- **Redundancy design** -- **Development processes compliant with safety standards** - -### Definition and Importance of Functional Safety - -The core of Functional Safety lies in **risk management**, which aims to reduce the impact of system failures. - -In autonomous vehicles, Functional Safety ensures that if sensor data is incorrect, the system can enter a **safe state**, preventing incorrect driving decisions. - -Three of core objectives of Functional Safety are: -1. **Prevention** - - Reducing the likelihood of errors through rigorous software development processes and testing. In the electric vehicle, the battery systems monitor temperature to prevent overheating. -2. **Detection** - - Quickly identifying errors using built-in diagnostic mechanisms (e.g., Built-in Self-Test, BIST). -3. **Mitigation** - - Controlling the impact of failures to ensure the overall safety of the system. - -This approach is critical in applications such as **autonomous driving, flight control, and medical implants**, where failures can result in **severe consequences**. - -### ISO 26262 Standard and Applications - -[ISO 26262](https://www.iso.org/standard/68383.html) is a functional safety standard specifically for **automotive electronics and software systems**. It defines a comprehensive safety lifecycle, covering all phases from **requirement analysis, design, development, testing, to maintenance**. - -Key Concepts of ISO 26262: -- **ASIL (Automotive Safety Integrity Level)** - - Evaluates the risk level of different system components (A, B, C, D, where **D represents the highest safety requirement**). - - For example: ASIL A can be Dashboard light failure (low risk) and ASIL D is Brake system failure (high risk). - https://en.wikipedia.org/wiki/Automotive_Safety_Integrity_Level -- **HARA (Hazard Analysis and Risk Assessment)** - - Analyzes hazards and assesses risks to determine necessary safety measures. -- **Safety Mechanisms** - - Includes error detection, fault tolerance, and fail-safe modes to ensure safe operation. - -List some of typical application scenarios: -- **Autonomous Driving Systems**: - - Ensures that even if sensors (e.g., LiDAR, radar, cameras) provide faulty data, the vehicle will not make dangerous decisions. -- **Powertrain Control**: - - Prevents braking system failures that could lead to loss of control. -- **Battery Management System (BMS)**: - - Prevents battery overheating or excessive discharge in electric vehicles. - -For more details, you can check this video: [What is Functional Safety?](https://www.youtube.com/watch?v=R0CPzfYHdpQ) - - -### Safety Island - -In automotive systems, a **General ECU (Electronic Control Unit)** typically runs non-critical tasks such as infotainment or navigation, whereas a **Safety Island** is dedicated to executing safety-critical control logic (e.g., braking, steering) with strong isolation, redundancy, and determinism. - -| Feature | General ECU | Safety Island | -|------------------------|----------------------------|--------------------------------------| -| Purpose | Comfort / non-safety logic | Safety-critical decision making | -| OS/Runtime | Linux, Android | RTOS, Hypervisor, or bare-metal | -| Isolation | Soft partitioning | Hard isolation (hardware-enforced) | -| Functional Safety Req | None to moderate | ISO 26262 ASIL-B to ASIL-D compliant | -| Fault Handling | Best-effort recovery | Deterministic safe-state response | - -This contrast highlights why safety-focused software needs a dedicated hardware domain with certified execution behavior. - -**Safety Island** is an independent safety subsystem separate from the main processor. It is responsible for monitoring and managing system safety. If the main processor fails or becomes inoperable, Safety Island can take over critical safety functions such as **deceleration, stopping, and fault handling** to prevent catastrophic system failures. - -Key Functions of Safety Island -- **Monitoring System Health** - - Continuously monitors the operational status of the main processor (e.g., ADAS control unit, ECU) and detects potential errors or anomalies. -- **Fault Detection and Isolation** - - Independently evaluates and initiates emergency handling if the main processing unit encounters errors, overheating, computational failures, or unresponsiveness. -- **Providing Essential Safety Functions** - - Even if the main system crashes, Safety Island can still execute minimal safety operations, such as: - - Autonomous Vehicles → Safe stopping (Fail-Safe Mode) - - Industrial Equipment → Emergency power cutoff or speed reduction - - - - -### Integration of Safety Island and Functional Safety - -Safety Island plays a critical role in Functional Safety by ensuring that the system can handle high-risk scenarios and minimize catastrophic failures. - -How Safety Island Enhances Functional Safety -1. **Acts as an Independent Redundant Safety Layer** - - Even if the main system fails, it can still operate independently. -2. **Supports ASIL-D Safety Level** - - Monitors ECU health status and executes emergency safety strategies (e.g., emergency braking). -3. **Provides Independent Fault Detection and Recovery Mechanisms** - - **Fail-Safe**: Activates a **safe mode**, such as limiting vehicle speed or switching to manual control. - - **Fail-Operational**: Ensures that high-safety applications (e.g., aerospace systems) can continue operating under certain conditions. - -For more insights on **Arm's Functional Safety solutions**, you can refer to: [Arm Functional Safety Compute Blog](https://community.arm.com/arm-community-blogs/b/automotive-blog/posts/functional-safety-compute) - - -### Impact of Functional Safety on Software Development Processes - -Functional Safety impacts **both hardware and software development**, particularly in areas such as requirement changes, version management, and testing validation. -For example, in ASIL-D level applications, every code modification requires a complete impact analysis and regression testing to ensure that new changes do not introduce additional risks. - -List the Functional Safety Requirements in Software Development: -- **Requirement Specification** - - Clearly defining **safety-critical requirements** and conducting risk assessments. -- **Safety-Oriented Programming** - - Following **MISRA C, CERT C/C++ standards** and using static analysis tools to detect errors. -- **Fault Handling Mechanisms** - - Implementing **redundancy design and health monitoring** to handle anomalies. -- **Testing and Verification** - - Using **Hardware-in-the-Loop (HIL)** testing to ensure software safety in real hardware environments. -- **Version Management and Change Control** - - Using **Git, JIRA, Polarion** to track changes for safety audits. - -This learning path builds on the previous [learning path](https://learn.arm.com/learning-paths/automotive/openadkit1_container) and introduces how to incorporate Functional Safety design processes in the early stages of automotive software development. -By establishing an ASIL Partitioning software development environment and leveraging [**SOAFEE**](https://www.soafee.io/) technologies, developers can enhance software consistency and maintainability in Functional Safety applications. - diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/2_DataDistributionService.md b/content/learning-paths/automotive/openadkit2_virtualplatform/2_DataDistributionService.md deleted file mode 100644 index c50e6712d1..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/2_DataDistributionService.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: How to use Data Distribution Service (DDS) -weight: 3 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -### Introduction to DDS -Data Distribution Service (DDS) is a real-time, high-performance middleware designed for distributed systems, particularly in automotive software development for autonomous driving and advanced driver assistance systems (ADAS). Its decentralized architecture provides scalable, low-latency, and reliable data exchange, making it an essential component in managing high-frequency sensor data. - -In modern vehicles, multiple sensors, such as LiDAR, radar, and cameras, must communicate efficiently with computing modules and decision-making units. DDS enables seamless data transmission within the vehicle, ensuring that perception, localization, and control systems receive the necessary data with minimal delay. Additionally, it plays a crucial role in vehicle-to-infrastructure (V2X) communication, allowing vehicles to exchange information with traffic signals, road sensors, and other connected systems to enhance situational awareness and safety. - - -### Why Automotive Software Needs DDS -Modern automotive software architectures, such as SOAFEE, require deterministic data communication to ensure real-time coordination between sensors, ECUs, and computing modules. Traditional client-server communication models often introduce latency and bottlenecks, whereas DDS provides a direct, decentralized solution that enhances reliability and scalability. - -A key advantage of DDS is its ability to enable direct data exchange between system components without relying on a central server. This reduces the risk of a single point of failure and minimizes delays, which is crucial for autonomous driving applications where milliseconds can make a difference. For example, a LiDAR sensor publishing obstacle detection data can simultaneously send information to multiple subscribers, including perception, SLAM (Simultaneous Localization and Mapping), and motion planning modules. This parallel data distribution ensures all relevant subsystems have the latest environmental data without requiring multiple separate transmissions. - -Additionally, DDS provides a flexible Quality of Service (QoS) configuration, allowing engineers to fine-tune communication parameters based on system requirements. Low-latency modes are ideal for real-time decision-making in vehicle control, while high-reliability configurations ensure data integrity in safety-critical applications like V2X communication. - - -### Architecture and Operation -DDS is based on a data-centric publish-subscribe (DCPS) model, allowing producers and consumers of data to communicate without direct dependencies. This modular approach enhances system flexibility and maintainability, making it well-suited for complex automotive environments. - -In DDS, all participants operate within a **domain**, which provides logical isolation between different applications. Each domain contains multiple **topics**, representing specific data types such as vehicle speed, obstacle detection, or sensor fusion results. **Publishers** use **DataWriters** to send data to these topics, while **subscribers** use **DataReaders** to receive the data. This architecture supports concurrent data processing, ensuring that multiple modules can work with the same data stream simultaneously. - -For example, in an autonomous vehicle, LiDAR, radar, and cameras continuously generate large amounts of sensor data. The perception module subscribes to these sensor topics, processes the data, and then publishes detected objects and road conditions to other components like path planning and motion control. Since DDS automatically handles participant discovery and message distribution, engineers do not need to manually configure communication paths, reducing development complexity. - - -### Applications in Autonomous Driving -DDS is widely used in autonomous driving systems, where real-time data exchange is crucial. A typical use case involves high-frequency sensor data transmission and decision-making coordination between vehicle subsystems. - -For instance, a LiDAR sensor generates millions of data points per second, which need to be shared with multiple modules. DDS allows this data to be published once and received by multiple subscribers, including perception, localization, and mapping components. After processing, the detected objects and road features are forwarded to the path planning module, which calculates the vehicle's next movement. Finally, control commands are sent to the vehicle actuators, ensuring precise execution. - -This real-time data flow must occur within milliseconds to enable safe autonomous driving. DDS ensures minimal transmission delay, enabling rapid response to dynamic road conditions. In emergency scenarios, such as detecting a pedestrian or sudden braking by a nearby vehicle, DDS facilitates instant data propagation, allowing the system to take immediate corrective action. - -For example: [Autoware](https://www.autoware.org/)—an open-source autonomous driving software stack—uses DDS to handle high-throughput communication across its modules. For example, the **Perception** stack publishes detected objects from LiDAR and camera sensors to a shared topic, which is then consumed by the **Planning** module in real-time. Using DDS allows each subsystem to scale independently while preserving low-latency and deterministic communication. - -### Publish-Subscribe Model and Data Transmission -Traditional client-server communication requires a centralized server to manage data exchange. This architecture introduces several drawbacks, including increased latency and network congestion, which can be problematic in real-time automotive applications. - -DDS adopts a publish-subscribe model, enabling direct communication between system components. Instead of relying on a central entity to relay messages, DDS allows each participant to subscribe to relevant topics and receive updates as soon as new data becomes available. This approach reduces dependency on centralized infrastructure and improves overall system performance. - -For example, in an automotive perception system, LiDAR, radar, and cameras continuously publish sensor data. Multiple subscribers, including object detection, lane recognition, and obstacle avoidance modules, can access this data simultaneously without additional network overhead. DDS automatically manages message distribution, ensuring efficient resource utilization. - -DDS supports multiple transport mechanisms to optimize communication efficiency: -- **Shared memory transport**: Ideal for ultra-low-latency communication within an ECU, minimizing processing overhead. -- **UDP or TCP/IP**: Used for inter-device communication, such as V2X applications where vehicles exchange safety-critical messages. -- **Automatic participant discovery**: Eliminates the need for manual configuration, allowing DDS nodes to detect and establish connections dynamically. - -#### Comparison of DDS and Traditional Communication Methods - -| **Feature** | **Traditional Client-Server Architecture** | **DDS Publish-Subscribe Model** | -|----------------------|--------------------------------|---------------------------| -| **Data Transmission** | Relies on a central server | Direct peer-to-peer communication | -| **Latency** | Higher latency | Low latency | -| **Scalability** | Limited by server capacity | Suitable for large-scale systems | -| **Reliability** | Server failure affects the whole system | No single point of failure | -| **Use Cases** | Small-scale applications | V2X, autonomous driving | - -These features make DDS a highly adaptable solution for automotive software engineers seeking to develop scalable, real-time communication frameworks. - -Here is an [installation guide](https://learn.arm.com/install-guides/cyclonedds) on how to install open-source DDS on an Arm platform. - diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/3_aws_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/3_aws_setup.md deleted file mode 100644 index 2fee5f4f91..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/3_aws_setup.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: Split into multiple cloud container instances -weight: 4 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -### System Architecture and Component Design - -Once we understand Safety Island —- the safety-critical subsystem responsible for executing essential control logic in automotive systems—and the DDS (Data Distribution Service) communication mechanism, we can refactor the original OpenAD Kit architecture, which previously ran on a single instance, by splitting it into two independent execution environments. - -In the previous [learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/), OpenAD Kit launched three containers on the same hardware, each responsible for the `Simulation environment`, `Visualization` and `Planning-Control` components. - -In this session, we decouple the simulation and visualization system from the on-vehicle autonomous driving stack, deploying them on two separate Arm-based instances. Communication between the two is facilitated through the ROS 2 software architecture, using DDS as the underlying middleware, which enables real-time data exchange and modular integration across distributed systems. - -This architecture brings several practical benefits: - -- Improved system stability: Decoupling reduces the risk of resource contention or runtime interference between visualization workloads and safety-critical components. - -- Leverages DDS’s QoS and discovery features: Ensures robust, real-time, and scalable communication across nodes without requiring a central broker or manual configuration. - -- Better scalability and performance tuning: Each instance can be provisioned with resources optimized for its specific task. - -- Supports modular development and CI/CD: Teams can develop, test, and iterate on components independently, enabling better DevOps practices for autonomous systems. - - -!!! Consider a simple network diagram showing the two EC2s, DDS, and container roles. !!! - -### Networking Setting - -First, launch two Arm instances (either cloud instances or on-premise servers). - -The specifications of the two Arm instances don’t need to be identical. In my tests, 16 CPUs and 32GB of RAM have already provided good performance. - -Once the two machines are up and running, you need to decide where the `Planning-Control` will execute. After making the decision, the other machine will run `Simulation environment` and `Visualization`. - -In order for the two instances to communicate, you must configure them to allow network access to each other. For example, in AWS EC2, both instances should belong to the same security group. - -In the AWS EC2 Security Groups inbound rules setting, ensure that there is a rule allowing traffic from other members of the same security group (i.e., the security group itself as a source). Outbound traffic is typically allowed by default and usually does not require modification. -This setup ensures that both EC2 instances can discover and communicate with each other over the network as required by ROS 2 and DDS. - -Once both of your machines are set up, please note down the IP addresses of both machines, as you will need them for the upcoming configuration. - -### New Docker YAML Configur Setting - -Ensure that Docker is installed on your development environment, then clone the same repository from the previous learning path onto both machines. - -```bash -git clone https://github.com/autowarefoundation/openadkit_demo.autoware.git - -cd openadkit_demo.autoware -cp docker/docker-compose.yml docker/docker-compose-2ins.yml -touch docker/cycloneDDS.xml -``` - -First, you need create xml file called `cycloneDDS.xml` - -This CycloneDDS XML configuration file is used to customize the behavior of the CycloneDDS middleware, which is used in ROS2 for inter-process communication (IPC) and network communication over DDS. -Please replace the previously written IP addresses of the two machines with the 192.168.xx.yy and 192.168.aa.bb in the configuration. - -```xml - - - - false - - - - - - 1000 - auto - - - - - - - /root/workspace/cyclonelog.log - config - - - -``` - -{{% notice Note %}} -You can find the more detail about CycloneDDS setting [Configuration](https://cyclonedds.io/docs/cyclonedds/latest/config/config_file_reference.html#cyclonedds-domain-internal-socketreceivebuffersize) -{{% /notice %}} - -Next, we need to configure the newly created YML file `docker-compose-2ins.yml`. - -- Use the host network instead of the docker network bridge. -This is necessary for allowing the all of three of containers to access the host network interfaces. - -```YAML - visualizer: - network_mode: host -``` - -- Add the newly created XML file as an environment variable in each container, and mount the current folder as `/root/workspace`. Configure the environment variable CYCLONEDDS_URI in the docker-compose.yaml file for all containers to ensure they use this configuration file. - -```YAML - volumes: - - .:/root/workspace - environment: - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml -``` - -- Remove the dependency between the `planning-control` and `simulator containers`. -Since these containers will now be launched independently, it is necessary to eliminate the dependency configuration between them. - -```YAML - planning-control: - depends_on: - - simulator -``` - -Here is the complete XML file: -```YAML -services: - simulator: - image: ghcr.io/autowarefoundation/demo-packages:simulator - container_name: simulator - network_mode: host - volumes: - - ./etc/simulation:/autoware/scenario-sim - - .:/root/workspace - - ./log:/root/.ros/log - environment: - - ROS_DOMAIN_ID=88 - - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml - command: > - ros2 launch scenario_test_runner scenario_test_runner.launch.py - record:=false - scenario:=/autoware/scenario-sim/scenario/yield_maneuver_demo.yaml - sensor_model:=sample_sensor_kit - vehicle_model:=sample_vehicle - initialize_duration:=90 - global_timeout:=$TIMEOUT - global_frame_rate:=20 - launch_autoware:=false - launch_rviz:=false - - planning-control: - image: ghcr.io/autowarefoundation/demo-packages:planning-control - container_name: planning-control - network_mode: host - deploy: - volumes: - - ./etc/simulation:/autoware/scenario-sim - - $CONF_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/autoware_behavior_path_static_obstacle_avoidance_module/static_obstacle_avoidance.param.yaml - - $COMMON_FILE:/opt/autoware/share/autoware_launch/config/planning/scenario_planning/common/common.param.yaml - - .:/root/workspace - - ./log:/root/.ros/log - environment: - - ROS_DOMAIN_ID=88 - - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml - command: > - ros2 launch autoware_launch planning_simulator.launch.xml - map_path:=/autoware/scenario-sim/map - vehicle_model:=sample_vehicle - sensor_model:=sample_sensor_kit - scenario_simulation:=true - rviz:=false - perception/enable_traffic_light:=false - - visualizer: - image: ghcr.io/autowarefoundation/demo-packages:visualizer - network_mode: host - container_name: visualizer - volumes: - - ./etc/simulation:/autoware/scenario-sim - - .:/root/workspace - - ./log:/root/.ros/log - ports: - - 6080:6080 - - 5999:5999 - environment: - - ROS_DOMAIN_ID=88 - - VNC_ENABLED=true - - RVIZ_CONFIG=/autoware/scenario-sim/rviz/scenario_simulator.rviz - - NGROK_AUTHTOKEN=${NGROK_AUTHTOKEN} - - NGROK_URL=${NGROK_URL} - - CYCLONEDDS_URI=/root/workspace/cycloneDDS.xml - command: >- - sleep infinity -``` -Before moving to the next step, make sure that `docker-compose-2ins.yml` and `cycloneDDS.xml` are already present on both instances. - -### Network Latency and Performance Optimization - -This distributed network architecture may lead to IP packet fragmentation, which can consume system memory under certain network conditions. - -To mitigate issues related to IP fragmentation and socket receive buffer limitations, you can tune the network configuration to support heavy UDP load. - -```bash -sudo su -sysctl net.ipv4.ipfrag_time=3 -sysctl net.ipv4.ipfrag_high_thresh=134217728 -sysctl -w net.core.rmem_max=2147483647 -exit -``` - -To make the configuration permanent across reboot, modify the create the file `/etc/sysctl.d/10-cyclone-max.conf`: - -```bash -sudo su -cat << EOF > /etc/sysctl.d/10-cyclone-max.conf -net.core.rmem_max=2147483647 -net.ipv4.ipfrag_time=3 -net.ipv4.ipfrag_high_thresh=134217728 # (128 MB) -EOF -exit -``` - -Reference: - - [Autoware dds-setting](https://autowarefoundation.github.io/autoware-documentation/main/installation/additional-settings-for-developers/network-configuration/dds-settings/) - - [ROS2 documentation](https://docs.ros.org/en/humble/How-To-Guides/DDS-tuning.html#cyclone-dds-tuning) - - -### Execution - -Next, you need to configure the execution commands on the different machines. -Copy the corresponding commands into the `openadkit_demo.autoware/docker` directory of two instances. - -{{< tabpane code=true >}} - {{< tab header="Planning-Control" language="bash">}} - #!/bin/bash - # Configure the environment variables - export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - export SCRIPT_DIR="$( pwd )" - export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml - export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml - export CONF_FILE=$CONF_FILE_FAIL - export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml - export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN - export NGROK_URL=$NGROK_URL - # Start planning-control - echo "Running planning v1.." - CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up planning-control --abort-on-container-exit - {{< /tab >}} - {{< tab header="Visualizer & Simulator" language="bash">}} - #!/bin/bash - # Configure the environment variables - export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - export SCRIPT_DIR="$( pwd )" - export CONF_FILE_PASS=$SCRIPT_DIR/etc/simulation/config/pass_static_obstacle_avoidance.param.yaml - export CONF_FILE_FAIL=$SCRIPT_DIR/etc/simulation/config/fail_static_obstacle_avoidance.param.yaml - export CONF_FILE=$CONF_FILE_FAIL - export COMMON_FILE=$SCRIPT_DIR/etc/simulation/config/common.param.yaml - export NGROK_AUTHTOKEN=$NGROK_AUTHTOKEN - export NGROK_URL=$NGROK_URL - - # Start visualizer and show logs - docker compose -f "$SCRIPT_DIR/docker-compose.yml" up visualizer -d - echo "Waiting 10 seconds for visualizer to start..." - sleep 10 - docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" logs visualizer - - # Start simulator - echo "Running simulator v1.." - TIMEOUT=70 CONF_FILE=$CONF_FILE_FAIL docker compose -f "$SCRIPT_DIR/docker-compose-2ins.yml" up simulator --abort-on-container-exit {{< /tab >}} -{{< /tabpane >}} - - -Once both machines execute their respective scripts, the visualizer will provide a link that can be accessed through the public IP. When you access the link, you will see that the demo execution will be very similar to the [previous learning path](http://learn.arm.com/learning-paths/automotive/openadkit1_container/4_run_openadkit/). - -The only difference is that the containers are distributed across two physical machines, and at the underlying layer of the demo, there will be frequent packet exchanges. - diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/4_corellium_setup.md b/content/learning-paths/automotive/openadkit2_virtualplatform/4_corellium_setup.md deleted file mode 100644 index 296f5e2bb9..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/4_corellium_setup.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Split into cloud container instances and RD-1AE Corellium -weight: 5 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -### Overview and Technical Features of the Corellium Platform - - -### Integration of Corellium with AWS DDS Systems - - -### Security and Real-Time Considerations - - -### Applicability and Comparative Analysis of This Solution diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/5_multiple_instances_executeion.md b/content/learning-paths/automotive/openadkit2_virtualplatform/5_multiple_instances_executeion.md deleted file mode 100644 index dbc500a6dc..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/5_multiple_instances_executeion.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Execute multiple instance -weight: 6 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -## \ No newline at end of file diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/_index.md b/content/learning-paths/automotive/openadkit2_virtualplatform/_index.md deleted file mode 100644 index 8c9e8ec1a7..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/_index.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Early Deployment of Automotive Functional Safety application on Neoverse V3AE - -minutes_to_complete: 60 - -who_is_this_for: This is an advanced automotive software development topic, focusing on the early-stage development of mission-critical software on Arm RD-1 AE. It explores how to leverage virtual platform technology to meet functional safety regulations in software development. - -learning_objectives: - - Introduction to Automotive Functional Safety(ISO-26262). Understand the ISO-26262 architecture and the importance of a structured software development flow in achieving functional safety compliance. - - Introduction to DDS (Data Distribution Service). Learn how DDS enables real-time, reliable communication between distributed automotive software components. - - Distributed Development for Functional Safety. Learn how to split the simulation platform into two independent units and leverage distributed development architecture to ensure functional safety. - -prerequisites: - - Two Arm-based Neoverse cloud instances or a local Arm Neoverse Linux computer with at least 16 CPUs and 32GB of RAM. - - Completion of the previous learning path. http://learn.arm.com/learning-paths/automotive/openadkit1_container/ - - A Corellium virtual platform account. https://www.corellium.com/ - - Basic knowledge of Docker operations. -author: - - Odin Shen - - Julien Jayat - -### Tags -skilllevels: Advanced -subjects: Containers and Virtualization -armips: - - Neoverse V3AE -tools_software_languages: - - Python - - Docker - - ROS2 -operatingsystems: - - Linux - - -further_reading: - - resource: - title: OpenAD Kit learning path - link: https://learn.arm.com/learning-paths/automotive/openadkit1_container/ - type: documentation - - resource: - title: DDS - link: https://github.com/autowarefoundation/openadkit_demo.autoware/ - type: documentation - - - -### FIXED, DO NOT MODIFY -# ================================================================================ -weight: 1 # _index.md always has weight of 1 to order correctly -layout: "learningpathall" # All files under learning paths have this same wrapper -learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. ---- diff --git a/content/learning-paths/automotive/openadkit2_virtualplatform/_next-steps.md b/content/learning-paths/automotive/openadkit2_virtualplatform/_next-steps.md deleted file mode 100644 index c3db0de5a2..0000000000 --- a/content/learning-paths/automotive/openadkit2_virtualplatform/_next-steps.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -# ================================================================================ -# FIXED, DO NOT MODIFY THIS FILE -# ================================================================================ -weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. -title: "Next Steps" # Always the same, html page title. -layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. ---- From 98030ec5ed480ed5a5e324be5dcadab376ae44f1 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 21:12:34 +0000 Subject: [PATCH 106/122] index file checked --- content/learning-paths/cross-platform/adler32/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/_index.md b/content/learning-paths/cross-platform/adler32/_index.md index e5f83f07c4..c18d176888 100644 --- a/content/learning-paths/cross-platform/adler32/_index.md +++ b/content/learning-paths/cross-platform/adler32/_index.md @@ -10,11 +10,11 @@ minutes_to_complete: 45 who_is_this_for: This is an introductory topic for C/C++ developers who are interested in using GitHub Copilot to improve performance using NEON intrinsics. learning_objectives: - - Use GitHub Copilot to write NEON intrinsics to improve performance of the Adler32 checksum algorithm. + - Use GitHub Copilot to write NEON intrinsics that accelerate the Adler32 checksum algorithm. prerequisites: - An Arm computer running Linux with the GNU compiler (gcc) installed. - - VS Code with GitHub Copilot installed. + - Visual Studio Code with the GitHub Copilot extension installed. author: Jason Andrews From 89ce5e66d0d05990ef685033075af31adac679ad Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Thu, 24 Apr 2025 21:17:20 +0000 Subject: [PATCH 107/122] starting rev --- content/learning-paths/cross-platform/adler32/about-2.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/about-2.md b/content/learning-paths/cross-platform/adler32/about-2.md index 04fd4fcc0e..b5c30a18cb 100644 --- a/content/learning-paths/cross-platform/adler32/about-2.md +++ b/content/learning-paths/cross-platform/adler32/about-2.md @@ -16,9 +16,9 @@ Developers recognize that Arm NEON SIMD instructions can significantly boost per The good news is that AI developer tools such as GitHub Copilot make working with NEON intrinsics much more accessible. By providing intelligent code suggestions, automated vectorization hints, and contextual examples tailored to your specific use case, GitHub Copilot can help bridge the knowledge gap and accelerate the development of NEON-optimized code. This allows developers to harness the full performance potential of Arm processors without the traditional complexity and time-consuming effort. -Writing NEON intrinsics with GitHub Copilot can be demonstrated by creating a complete project from scratch, and comparing the C implementation with the NEON implementation. +You can demonstrate writing NEON intrinsics with GitHub Copilot by creating a full project from scratch and comparing the C implementation to a NEON-optimized version. -While you may not create complete projects from scratch, and you shouldn't blindly trust the generated code, it's helpful to see what's possible using an example so you can apply the principles to your own projects. +While you may not create complete projects from scratch - and you shouldn't blindly trust the generated code - it's helpful to see what's possible using an example so you can apply the principles to your own projects. ## Accelerating Adler32 Checksum with Arm NEON Instructions @@ -34,8 +34,8 @@ Adler32 is a checksum algorithm that was invented by Mark Adler in 1995. It's us The algorithm works by calculating two 16-bit sums: -- s1: A simple sum of all bytes -- s2: A sum of all s1 values after each byte +- s1: A simple sum of all bytes. +- s2: A sum of all s1 values after each byte. - The final checksum is (s2 << 16) | s1. ## Project Overview From 19cc194c4b2499bb6eeff159e4e6a71e0d8815ca Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Fri, 25 Apr 2025 01:00:41 +0000 Subject: [PATCH 108/122] Tech review of ONNXRuntime Phi-3 on WoA LP --- assets/contributors.csv | 2 + .../1-dev-env-setup.md | 18 ++++----- .../2-build-onnxruntime.md | 11 +++--- .../3-build-onnxruntime-generate-api.md | 12 +++--- .../4-run-benchmark-on-WoA.md | 38 ++++++++++++------- .../win_on_arm_build_onnxruntime/_index.md | 14 +++---- 6 files changed, 55 insertions(+), 40 deletions(-) diff --git a/assets/contributors.csv b/assets/contributors.csv index 1d0b48cc52..61a086e88e 100644 --- a/assets/contributors.csv +++ b/assets/contributors.csv @@ -84,3 +84,5 @@ Shuheng Deng,Arm,,,, Yiyang Fan,Arm,,,, Julien Jayat,Arm,,,, Geremy Cohen,Arm,geremyCohen,geremyinanutshell,, +Barbara Corriero,Arm,,,, + diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md index c19eb442fa..2c689167ed 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/1-dev-env-setup.md @@ -1,5 +1,5 @@ --- -title: Create a development environment +title: Development environment weight: 2 ### FIXED, DO NOT MODIFY @@ -8,15 +8,15 @@ layout: learningpathall ## Set up your development environment -In this learning path, you will learn how to build and deploy a simple LLM-based tutorial on a Windows-on-ARM (WoA) laptop using ONNX Runtime for inference. +In this learning path, you will learn how to build and deploy a LLM on a Windows on Arm (WoA) laptop using ONNX Runtime for inference. -You will first learn how to build the ONNX Runtime and ONNX Runtime Generate() API library and then how to download the Phi-3 model and run the tutorial. This tutorial runs the short context (4k) mini (3.3B) variant of Phi 3 model. The short context version accepts a shorter (4K) prompts and produces shorter output text compared to the long (128K) context version. The short version will consume less memory. +You will first learn how to build the ONNX Runtime and ONNX Runtime Generate() API library and then how to download the Phi-3 model and run the inference. You will run the short context (4k) mini (3.3B) variant of Phi 3 model. The short context version accepts a shorter (4K) prompts and produces shorter output text compared to the long (128K) context version. The short version will consume less memory. Your first task is to prepare a development environment with the required software: - Visual Studio 2022 IDE (latest version recommended) -- Python 3.10+ (tested with version 3.11.9) -- CMake 3.28 or higher (tested with version 3.30.5) +- Python 3.10 or higher +- CMake 3.28 or higher The following instructions were tested on an WoA 64-bit Windows machine with at least 16GB of RAM. @@ -34,17 +34,17 @@ Follow these steps to install and configure Visual Studio 2022 IDE: 5. Once "Downloaded" and "Installed" complete select your workloads. As a minimum you should select **Desktop Development with C++**. This will install the **Microsoft Visual Studio Compiler** or **MSVC**. -## Install Python 3.10+ (Tested with version 3.11.9) +## Install Python -Download and install [Python 3.110+](https://www.python.org/downloads/) +Download and install [Python for Windows on Arm](/install-guides/py-woa) -Tested version [Python 3.11.9](https://www.python.org/downloads/release/python-3119/) +You will need Python version 3.10 or higher. This learning path was tested with version 3.11.9. ## Install CMake CMake is an open-source tool that automates the build process for software projects, helping to generate platform-specific build configurations. -[Download and install CMake](https://cmake.org/download/) +[Download and install CMake](/install-guides/cmake) {{% notice Note %}} The instructions were tested with version 3.30.5 diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md index 3699fd00bd..dba9e29407 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/2-build-onnxruntime.md @@ -6,7 +6,7 @@ weight: 3 layout: learningpathall --- -## Compile ONNX Runtime for Windows ARM64 CPU +## Compile ONNX Runtime for Windows on Arm Now that you have your environment set up correctly, you can build the ONNX Runtime inference engine. ONNX Runtime is an open-source inference engine designed to accelerate the deployment of machine learning models, particularly those in the Open Neural Network Exchange (ONNX) format. ONNX Runtime is optimized for high performance and low latency, making it popular for production deployment of AI models. You can learn more by reading the [ONNX Runtime Overview](https://onnxruntime.ai/). @@ -28,10 +28,9 @@ git checkout 4eeefd7260b7fa42a71dd1a08b423d5e7c722050 You might be able to use a later commit. These steps have been tested with the commit `4eeefd7260b7fa42a71dd1a08b423d5e7c722050`. {{% /notice %}} -### Build for Windows CPU +### Build for Windows -You can build "Release" for a build type that aims to provide an -a build optimized for performance but without debug information. +You can build the "Release" configuration for a build optimized for performance but without debug information. ```bash @@ -39,7 +38,7 @@ a build optimized for performance but without debug information. ``` -As an alternative, you can build "RelWithDebInfo" for a build type that aims to provide a release-optimized build with debug information. +As an alternative, you can build with "RelWithDebInfo" configuration for a release-optimized build with debug information. ```bash .\build.bat --config RelWithDebInfo --build_shared_lib --parallel --compile_no_warning_as_error --skip_submodule_sync --skip_tests @@ -47,7 +46,7 @@ As an alternative, you can build "RelWithDebInfo" for a build type that aims to ### Resulting Dynamic Link Library -When the build is complete, onnxruntime.dll dynamic linked library can be found in: +When the build is complete, the `onnxruntime.dll` dynamic linked library can be found in: ``` dir .\build\Windows\Release\Release\onnxruntime.dll diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md index 36af64cb93..ab91cb0146 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/3-build-onnxruntime-generate-api.md @@ -6,17 +6,17 @@ weight: 4 layout: learningpathall --- -## Compile the ONNX Runtime Generate() API for Windows ARM64 CPU +## Compile the ONNX Runtime Generate() API for Windows on Arm The Generate() API in ONNX Runtime is designed for text generation tasks using models like Phi-3. It implements the generative AI loop for ONNX models, including: - pre- and post-processing - inference with ONNX Runtime- logits processing - search and sampling -- KV cache management. +- KV cache management You can learn more by reading the [ONNX Runtime Generate() API page](https://onnxruntime.ai/docs/genai/). -In this page you will learn how to build the Generate API() from source (C/C++ build). +In this section you will learn how to build the Generate API() from source. ### Clone onnxruntime-genai Repo @@ -34,14 +34,16 @@ git checkout b2e8176c99473afb726d364454dc827d2181cbb2 You might be able to use later commits. These steps have been tested with the commit `b2e8176c99473afb726d364454dc827d2181cbb2`. {{% /notice %}} -### Build for Windows ARM64 CPU +### Build for Windows on Arm The build command below has a ---config argument, which takes the following options: - ```Release``` builds release build - ```Debug``` builds binaries with debug symbols - ```RelWithDebInfo``` builds release binaries with debug info -Below are the instruction to build ```Release```: +You will build the `Release` variant of the ONNX Runtime Generate() API: + ```bash +pip install requests python build.py --config Release --skip_tests ``` diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md index f925cf5ca7..5019c0ff53 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/4-run-benchmark-on-WoA.md @@ -1,25 +1,25 @@ --- -title: Run Phi3 model on an ARM Windows Device +title: Run Phi3 model on a Windows on Arm machine weight: 5 ### FIXED, DO NOT MODIFY layout: learningpathall --- -## Run a Phi-3 model on your ARM Windows Device +## Run a Phi-3 model on your Windows on Arm machine -In this section you will learn how to obtain and run on your ARM Windows device (or virtual device) the Phi3-mini model. To do so you will be using a simple model runner program which provides performance metrics. +In this section, you will learn how to download the Phi3-mini model and run it on your Windows on Arm machine (physical or virtual machine). You will be use a simple model runner program which provides performance metrics. -The Phi-3-mini (3.3B) model has a short (4k) context version and a long (128k) context version. The long context version can accept much longer prompts and produces longer output text, but it does consume more memory. +The Phi-3-mini (3.3B) model has a short (4k) context version and a long (128k) context version. The long context version can accept much longer prompts and produces longer output text, but it consumes more memory. In this learning path, you will use the short context version, which is quantized to 4-bits. The Phi-3-mini model used here is in an ONNX format. ### Setup -Phi-3 ONNX models are hosted on HuggingFace. +[Phi-3 ONNX models](https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx) are hosted on HuggingFace. Hugging Face uses Git for version control and to download ONNX model files, which can be quite large. -You will first need to get and install the Git Large File Storage (LFS) extension. +You will first need to install the Git Large File Storage (LFS) extension. ``` bash winget install -e --id GitHub.GitLFS @@ -34,7 +34,7 @@ You then need to install the ``HuggingFace CLI``. pip install huggingface-hub[cli] ``` -### Download the Phi-3-mini (4k) model for CPU and Mobile +### Download the Phi-3-mini (4k) model ``` bash cd C:\Users\%USERNAME% @@ -56,7 +56,7 @@ copy src\ort_genai.h examples\c\include\ copy src\ort_genai_c.h examples\c\include\ ``` -you can now build the model runner executable in the ''onnxruntime-genai'' folder using the commands below: +You can now build the model runner executable in the ''onnxruntime-genai'' folder using the commands below: ``` bash cd examples/c @@ -65,9 +65,9 @@ cd build cmake --build . --config Release ``` -After a successful build, a binary program called `phi3` will be created in the ''onnxruntime-genai'' folder. +After a successful build, a binary program called `phi3` will be created in the ''onnxruntime-genai'' folder: ```output -dir examples\c\build\Release\phi3.exe +dir Release\phi3.exe ``` #### Run the model @@ -80,10 +80,22 @@ cd repos\lp .\onnxruntime-genai\examples\c\build\Release\phi3.exe .\cpu_and_mobile\cpu-int4-rtn-block-32-acc-level-4\ cpu ``` -This will allow the runner program to load the model. It will then prompt you to input the text prompt to the model. After you enter your input prompt, the text output by the model will be displayed. On completion, performance metrics similar to those shown below should be displayed: +This will allow the runner program to load the model. It will then prompt you to input the text prompt to the model as shown: + +```output +------------- +Hello, Phi-3! +------------- +C++ API +Creating config... +Creating model... +Creating tokenizer... +Prompt: (Use quit() to exit) Or (To terminate current output generation, press Ctrl+C) +``` + +After you enter your input prompt, the text output by the model will be displayed. On completion, performance metrics similar to those shown below should be displayed: ``` Prompt length: 64, New tokens: 931, Time to first: 1.79s, Prompt tokens per second: 35.74 tps, New tokens per second: 6.34 tps ``` - -You have successfully run the Phi-3 model on your Windows device powered by ARM. +You have successfully run the Phi-3 model on your Windows device powered by Arm. diff --git a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md index b998e35af4..81ead9d54b 100644 --- a/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_on_arm_build_onnxruntime/_index.md @@ -1,5 +1,5 @@ --- -title: Powering Phi-3 on Arm PC with ONNX Runtime on Windows +title: Run Phi-3 on a Windows on Arm machine with ONNX Runtime draft: true cascade: @@ -7,11 +7,11 @@ cascade: minutes_to_complete: 60 -who_is_this_for: A deep-dive for advanced developers looking to build ONNX Runtime on Windows ARM (WoA) and leverage the Generate() API to run Phi-3 inference with KleidiAI acceleration. +who_is_this_for: A deep-dive for advanced developers looking to build ONNX Runtime on Windows on Arm (WoA) and leverage the Generate() API to run Phi-3 inference with KleidiAI acceleration. learning_objectives: - - Build ONNX Runtime and ONNX Runtime Generate() API for Windows on ARM. - - Run a Phi-3 model using ONNX Runtime on an Arm-based Windows laptop. + - Build ONNX Runtime and ONNX Runtime Generate() API for Windows on Arm. + - Run a Phi-3 model using ONNX Runtime on a Windows on Arm laptop. prerequisites: - A Windows on Arm computer such as the Lenovo Thinkpad X13 running Windows 11 or a Windows on Arm [virtual machine](https://learn.arm.com/learning-paths/cross-platform/woa_azure/) @@ -25,11 +25,11 @@ armips: - Cortex-A - Cortex-X tools_software_languages: - - Visual Studio IDE - 2022+ Community Version + - Visual Studio - C++ - - Python 3.10+ + - Python - Git - - CMake-3.28 or higher + - cmake operatingsystems: - Windows From 4002153053da593312cdeccf829d36f907e0167b Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 01:34:04 +0000 Subject: [PATCH 109/122] Updates --- .../cross-platform/adler32/about-2.md | 34 +++++++++---------- .../cross-platform/adler32/simple-c-3.md | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/about-2.md b/content/learning-paths/cross-platform/adler32/about-2.md index b5c30a18cb..91d7202849 100644 --- a/content/learning-paths/cross-platform/adler32/about-2.md +++ b/content/learning-paths/cross-platform/adler32/about-2.md @@ -6,47 +6,47 @@ weight: 2 layout: learningpathall --- -## Introduction +## Overview -In computing, optimizing performance is crucial for applications that process large amounts of data. This Learning Path focuses on implementing and optimizing the Adler32 checksum algorithm using Arm advanced SIMD (Single Instruction, Multiple Data) capabilities. You'll learn how to leverage GitHub Copilot to simplify the development process while achieving significant performance improvements. +In computing, optimizing performance is crucial for applications that process large amounts of data. This Learning Path guides you through implementing and optimizing the Adler32 checksum algorithm using Arm advanced SIMD (Single Instruction, Multiple Data) instructions. You'll learn how to leverage GitHub Copilot to simplify the development process while achieving significant performance improvements. ## Simplifying Arm NEON Development with GitHub Copilot Developers recognize that Arm NEON SIMD instructions can significantly boost performance for computationally intensive applications, particularly in areas like image processing, audio/video codecs, and machine learning. However, writing NEON intrinsics directly requires specialized knowledge of the instruction set, careful consideration of data alignment, and complex vector operations that can be error-prone and time-consuming. Many developers avoid implementing these optimizations due to the steep learning curve and development overhead. -The good news is that AI developer tools such as GitHub Copilot make working with NEON intrinsics much more accessible. By providing intelligent code suggestions, automated vectorization hints, and contextual examples tailored to your specific use case, GitHub Copilot can help bridge the knowledge gap and accelerate the development of NEON-optimized code. This allows developers to harness the full performance potential of Arm processors without the traditional complexity and time-consuming effort. +The good news is that AI developer tools such as GitHub Copilot make working with NEON intrinsics much more accessible. By providing intelligent code suggestions, automated vectorization hints, and contextual examples tailored to your specific use case, GitHub Copilot can help bridge the knowledge gap and accelerate the development of NEON-optimized code. This allows developers to harness the full performance potential of Arm processors - without the usual complexity and overhead. You can demonstrate writing NEON intrinsics with GitHub Copilot by creating a full project from scratch and comparing the C implementation to a NEON-optimized version. While you may not create complete projects from scratch - and you shouldn't blindly trust the generated code - it's helpful to see what's possible using an example so you can apply the principles to your own projects. + +## Accelerating Adler32 with Arm NEON -## Accelerating Adler32 Checksum with Arm NEON Instructions - -This project demonstrates how to significantly improve the performance of Adler32 checksum calculations using Arm NEON instructions. +This project demonstrates how to accelerate Adler32 checksum calculations using Arm NEON instructions. ### What is Arm NEON? Arm NEON is an advanced SIMD architecture extension for Arm processors. It provides a set of instructions that can process multiple data elements in parallel using specialized vector registers. NEON technology enables developers to accelerate computationally intensive algorithms by performing the same operation on multiple data points simultaneously, rather than processing them one at a time. This parallelism is particularly valuable for multimedia processing, scientific calculations, and cryptographic operations where the same operation needs to be applied to large datasets. -## What is Adler32? +## What Is the Adler32 Algorithm? -Adler32 is a checksum algorithm that was invented by Mark Adler in 1995. It's used in the zlib compression library and is faster than CRC32 but provides less reliable error detection. +Mark Adler developed the Adler32 checksum algorithm in 1995. It's used in the zlib compression library and is faster than CRC32 but provides less reliable error detection. The algorithm works by calculating two 16-bit sums: - s1: A simple sum of all bytes. - s2: A sum of all s1 values after each byte. -- The final checksum is (s2 << 16) | s1. +- The final checksum is `(s2 << 16) | s1`. -## Project Overview +## What You'll Build -This project explains how you can use GitHub Copilot to create everything listed below: +This project walks you through building the following components using GitHub Copilot: -- Standard C implementation of Adler32 -- Test program to confirm Adler32 works correctly for inputs of various sizes -- Makefile to build and run the program -- Performance measurement code to record how long the algorithm takes -- NEON version of Adler32 to increase performance -- Tables showing performance comparison between the standard C version and the NEON version +- A standard C implementation of Adler32. +- A test program to validate outputs for various input sizes. +- A Makefile to build and run the program. +- Performance measurement code to record how long the algorithm takes. +- A NEON-optimized version of Adler32. +- A performance comparison table for both implementations. Continue to the next section to start creating the project. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/simple-c-3.md b/content/learning-paths/cross-platform/adler32/simple-c-3.md index ffc8a3dd7d..924c3d44c7 100644 --- a/content/learning-paths/cross-platform/adler32/simple-c-3.md +++ b/content/learning-paths/cross-platform/adler32/simple-c-3.md @@ -191,6 +191,6 @@ The Adler-32 algorithm works by calculating two 16-bit sums (A and B) and combin As requested, I did not include a main function, so this code can be used as a library for other C files. ``` -You now have the files `adler32-simple.c` and `adler32-simple.h` in your directory which compute the Adler32 checksum. These will serve as the reference implementations of the algorithm with baseline performance. +You now have the files `adler32-simple.c` and `adler32-simple.h` in your directory. These serve as the reference implementation of the Adler32 algorithm with baseline performance. Continue to the next section to create the test application. \ No newline at end of file From ad6e65d0f03815de0a69eca06c009c58d34688cc Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 01:42:01 +0000 Subject: [PATCH 110/122] updates --- .../learning-paths/cross-platform/adler32/test-prog-4.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/test-prog-4.md b/content/learning-paths/cross-platform/adler32/test-prog-4.md index e19359036b..7f625c7009 100644 --- a/content/learning-paths/cross-platform/adler32/test-prog-4.md +++ b/content/learning-paths/cross-platform/adler32/test-prog-4.md @@ -6,9 +6,11 @@ weight: 4 layout: learningpathall --- -## How can I create a test program to verify Adler32 works correctly? +## How Do I Prompt Copilot to Generate a Test Program? -You can continue your GitHub Copilot Agent session to create a test program using the prompt below. Copy and paste it into GitHub Copilot to generate a test program. +You can continue your GitHub Copilot Agent session to create a test program using the prompt below. + +Copy and paste the prompt into GitHub Copilot to generate a test program: ```console Create a C file named adler32-test.c which includes a main() function and calls the adler32 function in adler32-simple.c From 95ba47eabffc6633a30f83745bc501d4659870c0 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Thu, 24 Apr 2025 23:30:50 -0500 Subject: [PATCH 111/122] review storage performance with fio --- .../disk-io-benchmark/_index.md | 15 +++-- .../characterising-workload.md | 52 +++++++-------- .../disk-io-benchmark/introduction.md | 31 ++++----- .../disk-io-benchmark/using-fio.md | 63 +++++++++++++------ 4 files changed, 94 insertions(+), 67 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md index 71682169c2..90cb9d9675 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/_index.md @@ -1,5 +1,5 @@ --- -title: Microbenchmark Storage Performance with Fio +title: Microbenchmark Storage Performance with fio draft: true cascade: @@ -7,16 +7,16 @@ cascade: minutes_to_complete: 30 -who_is_this_for: A cloud developer who wants to optimize storage cost or performance of their application. Developers who want to uncover potential storage-bound bottlenecks or changes when migrating an application to a different platform. +who_is_this_for: This is an introductory topic for developers seeking to optimize storage costs and performance, identify bottlenecks, and navigate storage considerations during application migration across platforms. learning_objectives: - - Understand the flow of data for storage devices - - Use basic observability utilities such as iostat, iotop and pidstat - - Understand how to run fio for microbenchmarking a block storage device + - Understand the flow of data for storage devices. + - Use basic observability utilities such as iostat, iotop and pidstat. + - Understand how to run fio for microbenchmarking a block storage device. prerequisites: - - Access to an Arm-based server - - Basic understanding of Linux + - An [Arm based instance](/learning-paths/servers-and-cloud-computing/csp/) from a cloud service provider or an Arm Linux server. + - Familiarity with Linux. author: Kieran Hejmadi @@ -31,7 +31,6 @@ tools_software_languages: operatingsystems: - Linux - further_reading: - resource: title: Fio documentation diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md index 06116a2a22..af58524576 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/characterising-workload.md @@ -1,5 +1,5 @@ --- -title: Characterising a Workload +title: Characterizing a workload weight: 3 ### FIXED, DO NOT MODIFY @@ -16,42 +16,44 @@ The basic attributes of a given workload are the following. - Read to Write Ratio - Random vs Sequential access -There are many more characteristics to observe, just as latency but since this is an introductory topic we will mostly stick to the high-level metrics listed above. +There are many more characteristics to observe, such as latency, but since this is an introductory topic you will mostly stick to the high-level metrics listed above. -## Running an Example Workload +## Run an Example Workload -Connect to an Arm-based cloud instance. As an example workload, we will be using the media manipulation tool, FFMPEG on an AWS `t4g.medium` instance. +Connect to an Arm-based server or cloud instance. -First install the prequistite tools. +As an example workload, you can use the media manipulation tool, FFMPEG, on an AWS `t4g.medium` instance. The `t4g.medium` is an Arm-based (AWS Graviton2) virtual machine with 2 vCPUs, 4 GiB of memory, and is designed for general-purpose workloads with a balance of compute, memory, and network resources. + +First, install the required tools. ```bash sudo apt update sudo apt install ffmpeg iotop -y ``` -Download the popular reference video for transcoding, `BigBuckBunny.mp4` which is available under the [Creative Commons 3.0 License](https://creativecommons.org/licenses/by/3.0/). +Download the popular reference video for transcoding, `BigBuckBunny.mp4`, which is available under the [Creative Commons 3.0 License](https://creativecommons.org/licenses/by/3.0/). ```bash cd ~ -mkdir src -cd src +mkdir src && cd src wget http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 ``` -Run the following command to begin transcoding the video and audio using the `H.264` and `aac` transcoders respectively. We use the `-flush_packets` flag to write each chunk of video back to storage from memory. +Run the following command to begin transcoding the video and audio using the `H.264` and `aac` transcoders respectively. The `-flush_packets` flag forces FFMPEG to write each chunk of video data from memory to storage immediately, rather than buffering it in memory. This reduces the risk of data loss in case of a crash and allows you to observe more frequent disk writes during the transcoding process. ```bash ffmpeg -i BigBuckBunny.mp4 -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k -flush_packets 1 output_video.mp4 ``` -### Observing Disk Usage +### Observe Disk Usage -Whilst the transcoding is running, we can use the `pidstat` command to see the disk statistics of that specific process. +While the transcoding is running, you can use the `pidstat` command to see the disk statistics of that specific process. ```bash pidstat -d -p $(pgrep ffmpeg) 1 ``` -Since this example `151MB` video fits within memory, we observe no `kB_rd/s` for the storage device after the initial read. However, since we are flushing to storage we observe period ~275 `kB_wr/s`. + +Since this example video (151 MB) fits within memory, you observe no `kB_rd/s` for the storage device after the initial read. However, because you are flushing to storage, you observe periodic writes of approximately 275 `kB_wr/s`. ```output Linux 6.8.0-1024-aws (ip-10-248-213-118) 04/15/25 _aarch64_ (2 CPU) @@ -67,11 +69,11 @@ Linux 6.8.0-1024-aws (ip-10-248-213-118) 04/15/25 _aarch64_ 10:01:32 1000 24250 0.00 344.00 0.00 0 ffmpeg ``` -{{% notice Please Note%}} -In this simple example, since we are interacting with a file on the mounted filesystem, we are also observing the behaviour of the filesystem. +{{% notice Note%}} +In this simple example, since you are interacting with a file on the mounted filesystem, you are also observing the behavior of the filesystem. {{% /notice %}} -Of course, there may be other processes or background services that are writing to this disk. We can use `iotop` command for inspection. As per the output below, the `ffmpeg` process has the greatest disk utilisation. +There may be other processes or background services that are writing to this disk. You can use the `iotop` command for inspection. As shown in the output below, the `ffmpeg` process has the highest disk utilization. ```bash sudo iotop @@ -86,33 +88,34 @@ Current DISK READ: 0.00 B/s | Current DISK WRITE: 0.00 B/s 2 be/4 root 0.00 B/s 0.00 B/s [kthreadd] ``` -Using the input, output statistics command (`iostat`) we can observe the system-wide metrics from the `nvme0n1` drive. Please Note that we are using a snapshot of this workload, more accurate characteristics can be obtained by measuring the distribution of a workload. +Using the input/output statistics command (`iostat`), you can observe the system-wide metrics from the `nvme0n1` drive. Please note that you are using a snapshot of this workload; more accurate characteristics can be obtained by measuring the distribution of a workload. ```bash watch -n 0.1 iostat -z nvme0n1 ``` -You should see output similar to that below. +You see output similar to that below. ```output Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd nvme0n1 3.81 31.63 217.08 0.00 831846 5709210 0 ``` -To observe the more detailed metrics we can run `iostat` with the `-x` option. +To observe more detailed metrics, you can run `iostat` with the `-x` option. ```bash iostat -xz nvme0n1 ``` +The output is similar to: + ```output Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util nvme0n1 0.66 29.64 0.24 26.27 0.73 44.80 2.92 203.88 3.17 52.01 2.16 69.70 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.01 0.15 ``` -### Basic Characteristics of our Example Workload - -This is a simple transcoding workload with flushed writes, where most data is processed and stored in memory. Disk I/O is minimal, with an IOPS of just 3.81, low throughput (248.71 kB/s), and an average IO depth of 0.01 — all summarised in very low disk utilization. The 52% write merge rate and low latencies further suggest sequential, infrequent disk access, reinforcing that the workload is primarily memory-bound. +### Basic Characteristics of the Example Workload +This is a simple transcoding workload with flushed writes, where most data is processed and stored in memory. Disk I/O is minimal, with an IOPS of just 3.81, low throughput (248.71 kB/s), and an average IO depth of 0.01 — all summarized in very low disk utilization. The 52% write merge rate and low latencies further suggest sequential, infrequent disk access, reinforcing that the workload is primarily memory-bound. | Metric | Calculation Explanation | Value | |--------------------|-------------------------------------------------------------------------------------------------------------|---------------| @@ -124,9 +127,8 @@ This is a simple transcoding workload with flushed writes, where most data is pr | Read Ratio | Read throughput ÷ total throughput: 31.63 / 248.71 | ~13% | | Write Ratio | Write throughput ÷ total throughput: 217.08 / 248.71 | ~87% | | IO Depth | Taken directly from `aqu-sz` (average number of in-flight I/Os) | 0.01 | -| Access Pattern | Based on cache hits, merge rates, and low wait times. 52% of writes were merged (`wrqm/s` = 3.17, `w/s` = 2.92) → suggests mostly sequential access | Sequential-ish (52.01% merged) | - +| Access Pattern | 52% of writes were merged (`wrqm/s` = 3.17, `w/s` = 2.92), indicating mostly sequential disk access with low wait times and frequent cache hits | Sequential (52.01% merged) | -{{% notice Please Note%}} -If you have access to the workloads source code, the expected access patterns can more easily be observed. +{{% notice Note %}} +If you have access to the workload's source code, you can more easily observe the expected access patterns. {{% /notice %}} diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md index 08a9a873d5..fac623a0d1 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/introduction.md @@ -1,5 +1,5 @@ --- -title: Fundamentals of Storage Systems +title: Fundamentals of storage systems weight: 2 ### FIXED, DO NOT MODIFY @@ -8,39 +8,40 @@ layout: learningpathall ## Introduction -The ideal storage activity of your system is 0. In this situation all of your application data and instructions are available in memory or caches with no reads or writes to a spinning hard-disk drive or solid-state SSD required. However, due to physical capacity limitations, data volatility and need to store large amounts of data, many applications require frequent access to storage media. +Ideally, your system's storage activity should be zero—meaning all application data and instructions are available in memory or cache, with no reads or writes to hard disk drives (HDDs) or solid-state drives (SSDs) required. However, due to physical capacity limits, data volatility, and the need to store large amounts of data, most applications frequently access storage media. ## High-Level Flow of Data -The diagram below is a high-level overview of how data can be written or read from a storage device. This diagram illustrates a multi-disk I/O architecture where each disk (Disk 1 to Disk N) has an I/O queue and optional disk cache, communicating with a central CPU via a disk controller. Memory is not explicitly shown but resides between the CPU and storage, offering fast access times with the tradeoff of volatile. File systems, though not depicted, operate at the OS/kernel level to handling file access metadata and offer a friendly way to interact through files and directories. +The diagram below provides a high-level overview of how data is written to or read from a storage device. It illustrates a multi-disk I/O architecture, where each disk (Disk 1 to Disk N) has its own I/O queue and optional disk cache, communicating with a central CPU via a disk controller. Memory, not explicitly shown, sits between the CPU and storage, offering fast but volatile access. File systems, also not depicted, operate at the OS/kernel level to handle file access metadata and provide a user-friendly interface through files and directories. ![disk i/o](./diskio.jpeg) - ## Key Terms #### Sectors and Blocks -Sectors are the basic physical units on a storage device. For instance, traditional hard drives typically use a sector size of 512 bytes, while many modern disks use 4096 bytes (or 4K sectors) to improve error correction and efficiency. +Sectors are the basic physical units on a storage device. Traditional hard drives typically use a sector size of 512 bytes, while many modern disks use 4096 bytes (4K sectors) for improved error correction and efficiency. + +Blocks are logical groupings of one or more sectors used by filesystems for data organization. A common filesystem block size is 4096 bytes, meaning each block might consist of eight 512-byte sectors, or map directly to a 4096-byte physical sector if supported by the disk. -Blocks are the logical grouping of one or more sectors used by filesystems for data organization. A common filesystem block size is 4096 bytes, meaning that each block might consist of 8 of the 512-byte sectors, or simply map directly to a 4096-byte physical sector layout if the disk supports it. +#### Input/Output Operations per Second (IOPS) -#### Input Output Operations per second (IOPS) -IOPS is a measure of how much random read or write requests your storage system can manage. It is worth noting that IOPS can vary by block size depending on the storage medium (e.g., flash drives). Importantly, traditional hard disk drives (HDDs) often don't specify the IOPS. For example the IOPS value for HDD volume on AWS is not shown. +IOPS measures how many random read or write requests your storage system can handle per second. IOPS can vary by block size and storage medium (e.g., flash drives). Traditional HDDs often do not specify IOPS; for example, AWS does not show IOPS values for HDD volumes. ![iops_hdd](./IOPS.png) -#### Throughput / Bandwidth -Throughput is the data transfer rate normally in MB/s with bandwidth specifying the maximum amount that a connection can transfer. IOPS x block size can be used to calculate the storage throughput of your application. +#### Throughput and Bandwidth + +Throughput is the data transfer rate, usually measured in MB/s. Bandwidth specifies the maximum amount of data a connection can transfer. You can calculate storage throughput as IOPS × block size. #### Queue Depth -Queue depth refers to the number of simultaneous I/O operations that can be pending on a device. Consumer SSDs might typically have a queue depth in the range of 32 to 64, whereas enterprise-class NVMe drives can support hundreds or even thousands of concurrent requests per queue. This parameter affects how much the device can parallelize operations and therefore influences overall I/O performance. -#### I/O Schedule Engine +Queue depth is the number of simultaneous I/O operations that can be pending on a device. Consumer SSDs typically have a queue depth of 32–64, while enterprise-class NVMe drives can support hundreds or thousands of concurrent requests per queue. Higher queue depth allows more parallelism and can improve I/O performance. + +#### I/O Engine -The I/O engine is the software component within Linux responsible for managing I/O requests between applications and the storage subsystem. For example, in Linux, the kernel’s block I/O scheduler acts as an I/O engine by queuing and dispatching requests to device drivers. Schedulers use multiple queues to reorder requests optimal disk access. -In benchmarking tools like fio, you might select I/O engines such as sync (synchronous I/O), `libaio` (Linux native asynchronous I/O library), or `io_uring` (which leverages newer Linux kernel capabilities for asynchronous I/O). +The I/O engine is the software component in Linux that manages I/O requests between applications and the storage subsystem. For example, the Linux kernel’s block I/O scheduler queues and dispatches requests to device drivers, using multiple queues to optimize disk access. In benchmarking tools like fio, you can select I/O engines such as sync (synchronous I/O), `libaio` (Linux native asynchronous I/O), or `io_uring` (which uses newer Linux kernel features for asynchronous I/O). #### I/O Wait -This is the perceived time spent waiting for I/O to return the value from the perspective of the CPU core. +I/O wait is the time a CPU core spends waiting for I/O operations to complete. diff --git a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md index bc50025ac4..6e7e0a40ed 100644 --- a/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md +++ b/content/learning-paths/servers-and-cloud-computing/disk-io-benchmark/using-fio.md @@ -1,17 +1,36 @@ --- -title: Using FIO +title: Using fio weight: 4 ### FIXED, DO NOT MODIFY layout: learningpathall --- -## Setup and Install Fio +## Install fio -I will be using the same `t4g.medium` instance from the previous section with 2 different types of SSD-based block storage devices as per the console screenshot below. Both block devices have the same, 8GiB capacity but the `io1` is geared towards throughput as opposed to the general purpose SSD `gp2`. In this section we want to observe what the real-world performance for our workload is so that it can inform our selection. +You can use the same `t4g.medium` instance from the previous section with 2 different types of SSD-based block storage devices as per the console screenshot below. + +To add the required EBS volumes to your EC2 instance: + +1. In the AWS Console, navigate to EC2 > Volumes > Create Volume +2. Create a volume with the following settings: + - Volume Type: io2 (Provisioned IOPS SSD) + - Size: 8 GiB + - IOPS: 400 + - Availability Zone: Same as your EC2 instance +3. Create another volume with the following settings: + - Volume Type: gp2 (General Purpose SSD) + - Size: 8 GiB + - Availability Zone: Same as your EC2 instance +4. Once created, select each volume and choose Actions > Attach Volume +5. Select your t4g.medium instance from the dropdown and attach each volume + +Both block devices have the same, 8GiB capacity but the `io2` is geared towards throughput as opposed to the general purpose SSD `gp2`. ![EBS](./EBS.png) +In this section you will observe what the real-world performance for your workload is so that it can inform your selection. + Flexible I/O (fio) is a command-line tool to generate a synthetic workload with specific I/O characteristics. This serves as a simpler alternative to full record and replay testing. Fio is available through most Linux distribution packages, please refer to the [documentation](https://github.com/axboe/fio) for the binary package availability. ```bash @@ -25,15 +44,17 @@ Confirm installation with the following commands. fio --version ``` +The version is printed: + ```output -fio-3.36 +fio-3.37 ``` ## Locate Device -`Fio` allows us to microbenchmark either the block device or a mounted filesystem. The disk free, `df` command to confirm our EBS volumes are not mounted. Writing to drives that hold critical information may cause issues. Hence we are writing to blank, unmounted block storage device. +Fio allows you to microbenchmark either the block device or a mounted filesystem. Use the disk free, `df` command to confirm your EBS volumes are not mounted. Writing to drives that hold critical information may cause issues. Hence you are writing to blank, unmounted block storage device. -Using the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. `nvme1n1` corresponds to the faster `io2` block device and `nvme2n1` corresponds to the slower `gp2` block device. +Use the `lsblk` command to view the EBS volumes attached to the server (`nvme1n1` and `nvme2n1`). The immediate number appended to `nvme`, e.g., `nvme0`, shows it is a physically separate device. `nvme1n1` corresponds to the faster `io2` block device and `nvme2n1` corresponds to the slower `gp2` block device. ```bash lsblk -e 7 @@ -50,22 +71,22 @@ nvme2n1 259:2 0 8G 0 disk ``` {{% notice Please Note%}} -If you have more than 1 block volumes attached to an instance, the `sudo nvme list` command from the `nvme-cli` package and be used to differentiate between volumes +If you have more than 1 block volumes attached to an instance, the `sudo nvme list` command from the `nvme-cli` package can be used to differentiate between volumes {{% /notice %}} ## Generating a Synthetic Workload -Let us say we want to simulate a fictional logging application with the following characteristics observed using the tools from the previous section. +Suppose you want to simulate a fictional logging application with the following characteristics observed using the tools from the previous section. {{% notice Workload%}} The logging workload has light sequential read and write characteristics. The system write throughput per thread is 5 MB/s with 83% writes. There are infrequent bursts of reads for approximately 5 seconds, operating at up to 16MB/s per thread. The workload can scale the infrequent reads and writes to use up to 16 threads each. The block size for the writes and reads are 64KiB and 256KiB respectively (as opposed to the standard 4KiB Page size). -Further, the application latency sensitive and given it holds critical information, needs to write directly to non-volatile storage through direct IO. +Further, the application is latency sensitive and given it holds critical information, needs to write directly to non-volatile storage through direct IO. {{% /notice %}} -The fio tool uses simple configuration `jobfiles` to describe the characterisics of your synthetic workload. Parameters under the `[global]` option are shared among jobs. From the example below, we have created 2 jobs to represent the steady write and infrequent reads. Please refer to the official [documentation](https://fio.readthedocs.io/en/latest/fio_doc.html#job-file-format) for more details. +The fio tool uses simple configuration `jobfiles` to describe the characteristics of your synthetic workload. Parameters under the `[global]` option are shared among jobs. From the example below, you can create 2 jobs to represent the steady write and infrequent reads. Please refer to the official [documentation](https://fio.readthedocs.io/en/latest/fio_doc.html#job-file-format) for more details. -Copy and paste the configuration file below into 2 files named `nvme.fio`. Replace the `` with the block devices we are comparing and just the `filename` parameter accordingly. +Copy and paste the configuration file below into 2 files named `nvme.fio`. Replace the `` with the block devices you are comparing and adjust the `filename` parameter accordingly. ```ini ; -- start job file including.fio -- @@ -89,19 +110,24 @@ bs=64k ; Block size of 64KiB (default block size of 4 KiB) [burst_read] name=burst_read rw=read -bs=256k ; adjust the block size to 64KiB writes (default is 4KiB) +bs=256k ; Block size of 256KiB for reads (default is 4KiB) startdelay=10 ; simulate infrequent reads (5 seconds out 30) runtime=5 ; -- end job file including.fio -- ``` + +{{% notice Note %}} +Running fio directly on block devices requires root privileges (hence the use of `sudo`). Be careful: writing to the wrong device can result in data loss. Always ensure you are targeting a blank, unmounted device. +{{% /notice %}} + Run the following commands to run each test back to back. ```bash sudo NUM_JOBS=16 IO_DEPTH=64 fio nvme1.fio ``` -Then +Then run again with the following command: ```bash sudo NUM_JOBS=16 IO_DEPTH=64 fio nvme2.fio @@ -131,10 +157,9 @@ Disk stats (read/write): nvme2n1: ios=1872/28855, sectors=935472/3693440, merge=0/0, ticks=159753/1025104, in_queue=1184857, util=89.83% ``` -Here we can see that the faster `io2` block storage (`nvme1`) is able to meet the throughput requirement of 80MB/s for steady writes when all 16 write threads are running (5MB/s per thread). However `gp2` saturates at 60.3 MiB/s with over 89.8% SSD utilisation. - -We are told the fictional logging application is sensitive to operation latency. The output belows highlights that over ~35% operations have a latency above 1s on nvme2 compared to ~7% on nvme1. +Here you can see that the faster `io2` block storage (`nvme1`) is able to meet the throughput requirement of 80MB/s for steady writes when all 16 write threads are running (5MB/s per thread). However, `gp2` saturates at 60.3 MiB/s with over 89.8% SSD utilization. +Suppose your fictional logging application is sensitive to operation latency. The output below highlights that over ~35% of operations have a latency above 1s on nvme2 compared to ~7% on nvme1. High latency percentiles can significantly impact application responsiveness, especially for latency-sensitive workloads like logging. ```output @@ -153,14 +178,14 @@ We are told the fictional logging application is sensitive to operation latency. lat (msec) : 2000=3.62%, >=2000=2.38% ``` -This insights above suggest the SSD designed for throughput, `io2` is more suitable than the general purpose `gp2` storage to meet the requirements of our logging application. +These insights suggest the SSD designed for throughput, `io2`, is more suitable than the general purpose `gp2` storage to meet the requirements of your logging application. {{% notice Tip%}} -If the text output is hard to follow, you can use the `fio2gnuplot` package to plot the data graphically or use the visualisations available from the cloud service provider's dashboard. See image below for an example. +If the text output is hard to follow, you can use the `fio2gnuplot` package to plot the data graphically or use the visualizations available from your cloud service provider's dashboard. See image below for an example. ![plot](./visualisations.png) {{% /notice %}} -The insights gathered by microbenchmarking with fio above can lead to more informed decisions about which block storage to connect to your Arm-based instance. +The insights gathered by microbenchmarking with fio above can lead to more informed decisions about which block storage to connect to your Arm-based instance. From 67e9cbb2e9026b0a6789cf927166d710c9ffe526 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 08:55:40 +0000 Subject: [PATCH 112/122] prompt copilot --- .../learning-paths/cross-platform/adler32/test-prog-4.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/test-prog-4.md b/content/learning-paths/cross-platform/adler32/test-prog-4.md index 7f625c7009..6bc0e260d6 100644 --- a/content/learning-paths/cross-platform/adler32/test-prog-4.md +++ b/content/learning-paths/cross-platform/adler32/test-prog-4.md @@ -8,7 +8,7 @@ layout: learningpathall ## How Do I Prompt Copilot to Generate a Test Program? -You can continue your GitHub Copilot Agent session to create a test program using the prompt below. +You can now move on to create a test program using the prompt below. Copy and paste the prompt into GitHub Copilot to generate a test program: @@ -21,7 +21,7 @@ Add code to run all test data with different sizes and measure performance. Don't create the Makefile yet. ``` -The output is below: +The output is shown below: ```output @@ -243,7 +243,7 @@ If you'd like me to create the Makefile for easier compilation, just let me know ``` -You now have a test program which confirms the Adler32 algorithm is working correctly for various input data and can measure the performance of the previously generated C function. +You now have a test program which confirms the Adler32 algorithm is working correctly for various input data and can measure the performance of the previously-generated C function. -Continue to the next section to run the test program and see the results. +Now continue to the next section to run the test program and view the results. From 32106176565abbe817d5d1fbe1f8caed83f4b8b2 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 09:18:34 +0000 Subject: [PATCH 113/122] updates --- .../cross-platform/adler32/build-6.md | 16 ++++++++-------- .../cross-platform/adler32/makefile-5.md | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/build-6.md b/content/learning-paths/cross-platform/adler32/build-6.md index d0fac641c2..485d90af0c 100644 --- a/content/learning-paths/cross-platform/adler32/build-6.md +++ b/content/learning-paths/cross-platform/adler32/build-6.md @@ -6,14 +6,14 @@ weight: 6 layout: learningpathall --- -## How can I test the build and run? +## How can I build and run the test program? -The required files are now complete to test the Adler32 algorithm. -- Adler32 C function -- Test program to call the Adler32 function to test for correctness and measure performance -- Makefile to build and run +You now have all the required files to test the Adler32 algorithm: +- A C implementation of the Adler32 function. +- A test program to verify correctness and measure performance. +- A Makefile to build and run the project. -Copy the information below to your GitHub Copilot Agent session: +Paste the following prompt into your GitHub Copilot Agent session: ```console Use the Makefile to build the project and run to make sure the checksum results are correct for all data sizes. @@ -57,6 +57,6 @@ The results confirm that your Adler-32 checksum implementation is correct for al ``` -The results from GitHub Copilot explain that the Adler32 checksum calculations are correct and give some initial performance results. The results don't mean much yet as there is nothing to compare with. +The results from GitHub Copilot confirm that the Adler32 checksum calculations are correct and provide initial performance benchmarks. These results offer a solid baseline, but a meaningful comparison requires an optimized implementation. -Continue to the next section to implement Adler32 using NEON and compare the performance. \ No newline at end of file +In the next section, you’ll implement Adler32 using NEON intrinsics and compare its performance against this baseline. \ No newline at end of file diff --git a/content/learning-paths/cross-platform/adler32/makefile-5.md b/content/learning-paths/cross-platform/adler32/makefile-5.md index 3edf9ae2e3..8766b62620 100644 --- a/content/learning-paths/cross-platform/adler32/makefile-5.md +++ b/content/learning-paths/cross-platform/adler32/makefile-5.md @@ -8,7 +8,7 @@ layout: learningpathall ## How can I create a Makefile to build and run the test program? -To create a Makefile, copy and paste the information below to GitHub Copilot. The prompt explains that the Makefile should use `gcc` as the compiler and target the Neoverse N1 processor. +Paste the following prompt into GitHub Copilot. It tells Copilot to generate a Makefile that uses `gcc` and targets the Neoverse N1 processor for optimized performance. ```console Read the .c files in my project and From 1e391a579292a0f3fdb3da8b7acc02c21cd45dad Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 09:23:28 +0000 Subject: [PATCH 114/122] updates --- content/learning-paths/cross-platform/adler32/neon-7.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/neon-7.md b/content/learning-paths/cross-platform/adler32/neon-7.md index fc009548ed..cc4c9ee3cf 100644 --- a/content/learning-paths/cross-platform/adler32/neon-7.md +++ b/content/learning-paths/cross-platform/adler32/neon-7.md @@ -6,11 +6,11 @@ weight: 7 layout: learningpathall --- -## How can I create a NEON version of Adler32 to improve performance? +## How Can I Create a NEON Version of Adler32 To Improve Performance? Writing NEON intrinsics is an advanced programming topic which requires time and effort to learn and apply. GitHub Copilot is a good way to get started with NEON, both to write new code and to debug issues during development. -You can instruct Github Copilot to create a NEON version of Adler32 using the prompt below. +You can instruct Github Copilot to create a NEON-optimized version of Adler32 using the prompt below: ```console Add a second implementation of adler32 that processes data in blocks and uses Arm NEON intrinsics to improve performance. @@ -553,6 +553,6 @@ Now that I've created the NEON implementation and updated the test code and Make ``` -GitHub Copilot generated a new implementation of Adler32 using NEON intrinsics. It also updated the test program and the Makefile to make it easy to compare the two versions. +GitHub Copilot generated a NEON-optimized implementation of Adler32, along with updates to the test program and Makefile. These changes make it easy to compare the performance of both versions. -Continue to the next section to compile and run the NEON version and investigate performance. +Continue to the next section to compile and run the NEON version, and to compare its performance against the original implementation. From 9c5cd030a1b81f44611d14144e904322801ce45e Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 09:26:46 +0000 Subject: [PATCH 115/122] updates --- content/learning-paths/cross-platform/adler32/neon-run-8.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/neon-run-8.md b/content/learning-paths/cross-platform/adler32/neon-run-8.md index 1f12830456..8553d11998 100644 --- a/content/learning-paths/cross-platform/adler32/neon-run-8.md +++ b/content/learning-paths/cross-platform/adler32/neon-run-8.md @@ -8,7 +8,7 @@ layout: learningpathall ## How can I run the NEON version of Adler32 and compare performance? -The first step to investigate if NEON can provide improved performance is to make sure the NEON version builds, runs, and matches the results of the C version. +Before comparing performance, you need to ensure that the NEON implementation builds, runs, and produces the same results as the standard C version. To build the NEON version and compare it to the C version use the prompt below in your GitHub Copilot Agent session: @@ -123,6 +123,6 @@ The NEON implementation offers significant performance improvements (up to 5x fa If you'd like, I can analyze the code to identify and fix the issue in the NEON implementation so you can maintain both correctness and the performance benefits. ``` -GitHub Copilot has come to the conclusion that although the NEON version looks like it offers better performance, it does not produce the correct results for the checksums. Obviously, this is not a good result which needs to be fixed. +GitHub Copilot determined that while the NEON version delivers better performance, it currently produces incorrect checksums. This must be addressed before the NEON implementation can be considered correct or production-ready. -Continue to the next section to fix the functional issues and continue with the performance comparison. \ No newline at end of file +In the next section, you'll fix the functional issues in the NEON implementation and re-run the performance comparison. \ No newline at end of file From 4ef6c8ebf781c00bd60e34702582f84133b5d654 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 09:29:38 +0000 Subject: [PATCH 116/122] updates --- .../cross-platform/adler32/neon-debug-9.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/neon-debug-9.md b/content/learning-paths/cross-platform/adler32/neon-debug-9.md index dd147bcd7c..9c69ef7689 100644 --- a/content/learning-paths/cross-platform/adler32/neon-debug-9.md +++ b/content/learning-paths/cross-platform/adler32/neon-debug-9.md @@ -6,9 +6,9 @@ weight: 9 layout: learningpathall --- -## How can I debug the NEON version Adler32 and compare performance? +## How Can I Debug the NEON Version Adler32 and Compare Performance? -During the last step, GitHub Copilot revealed that the functional results with NEON do not match the C version. +In the previous step, GitHub Copilot revealed that the NEON implementation did not produce the same checksum results as the standard C version. Use the prompt below to instruct GitHub Copilot to try to debug and fix the issue. @@ -17,7 +17,7 @@ Can you modify adler32-neon.c to produce the correct checksum results for the ad The NEON version should produce the same checksum as adler32-simple.c but run faster. ``` -If the LLM you are using doesn't resolve the NEON functional issues, you can try with another LLM such as Gemini 2.5 Pro. +If the LLM you’re using cannot resolve the NEON functional issues, consider trying another model, such as Gemini 2.5 Pro. The output is: @@ -94,6 +94,6 @@ I then checked for errors in the modified file and rebuilt the project using `ma You can now run the test program adler32-test to verify that the NEON implementation produces the same results as the simple implementation. ``` -GitHub Copilot was able to modify the NEON version to get the correct functional results. +GitHub Copilot successfully modified the NEON version to produce correct checksum results matching the standard C implementation. -Continue to the next section to summarize the project, including the performance improvements, and generate a full project description. \ No newline at end of file +Continue to the next section to summarize the project, highlight the performance improvements, and generate a full project description. \ No newline at end of file From 53262c522ca73a9623be283a994468034a395149 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 10:05:58 +0000 Subject: [PATCH 117/122] updates --- .../cross-platform/adler32/more-11.md | 12 ++++++------ .../cross-platform/adler32/summary-10.md | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/more-11.md b/content/learning-paths/cross-platform/adler32/more-11.md index 50a263a8ac..446a2ea136 100644 --- a/content/learning-paths/cross-platform/adler32/more-11.md +++ b/content/learning-paths/cross-platform/adler32/more-11.md @@ -8,11 +8,11 @@ layout: learningpathall ## What else can I do with GitHub Copilot on this project? -You can investigate more topics using GitHub Copilot. +GitHub Copilot can help you explore additional performance and optimization ideas: -- Direct GitHub Copilot to try different compiler flags and use Agent mode to iterate through the options to find the best solution. -- Add support for the Clang compiler to the Makefile and compare the results to GCC. Depending on the application code, changing the compiler can result in improved performance. -- Use GitHub Copilot to generate different data sizes and random data patterns to further investigate correct functionality and performance. -- Try different algorithm implementations that use compiler autovectorization instead of NEON intrinsics or break down the Adler32 checksum into smaller blocks of data. It may be possible to get even better performance without NEON using the compiler and a better structure for the C code. +- Test different compiler flags using Agent mode to automate iteration and identify the best combinations. +- Add Clang support to your Makefile and compare performance against GCC — performance can differ significantly depending on your code structure. +- Generate a wider range of data sizes and random patterns to stress-test functionality and measure performance under varied conditions. +- Explore alternative algorithm structures that rely on compiler autovectorization instead of NEON intrinsics — you might discover better performance simply by restructuring the C code. -While AI tools do not create performance code for every programming problem, they can be a big help to get you started in a new area of programming such as performance optimization using NEON intrinsics. \ No newline at end of file +AI tools won’t always generate high-performance code out of the box, but they can rapidly accelerate your experimentation and learning — especially in new areas of programming like NEON-based performance optimization. diff --git a/content/learning-paths/cross-platform/adler32/summary-10.md b/content/learning-paths/cross-platform/adler32/summary-10.md index 7ff80a22cc..e173805957 100644 --- a/content/learning-paths/cross-platform/adler32/summary-10.md +++ b/content/learning-paths/cross-platform/adler32/summary-10.md @@ -10,7 +10,9 @@ layout: learningpathall You can use GitHub Copilot to generate a project summary in a README file. -Copy the prompt below to your GitHub Copilot Agent chat and review the created README file. +Use the prompt below to collaborate with GitHub Copilot Agent to generate your README. + +Review and refine the results to align them with your project's goals. ```console Review the files in my project. @@ -20,7 +22,7 @@ Add a note that the performance results recorded on the Neoverse N1 processor. Use a table to compare the original version and the NEON version and show the performance improvement factor. ``` -Below is the created README.md file. The formatting doesn't match the Learning Path template exactly, but you can copy the the README file to a new repository in GitHub for improved results. +Below is the created README.md file. The formatting doesn't match the Learning Path template exactly, but you can copy the README file to a new repository in GitHub for improved results. ## Adler-32 Checksum Implementation Comparison @@ -93,6 +95,15 @@ The table summarizes the speedup obtained by the NEON version. Using Agent mode in GitHub Copilot is a significant benefit when you are actively building and running software. Agent mode can create files and modify them to make needed improvements. -The entire project was done without modifying any of the generated files. While you may not need to do this on a real project, the concept of writing NEON intrinsics to improve performance was demonstrated. You can also use GitHub Copilot to fix issues in NEON code that are difficult to debug for developers who are not experts. +### Tips for Using GitHub Copilot Effectively + +This project was completed using GitHub Copilot Agent without modifying the generated files. While that might not be practical in every case, the demonstration shows how NEON intrinsics can significantly boost performance. + +GitHub Copilot is especially useful for: +* Generating vectorized versions of scalar code. +* Writing and adapting NEON intrinsics. +* Identifying and fixing bugs in complex low-level code, even for developers who aren’t SIMD experts. Make sure to try different LLMs with Copilot as the results will vary greatly depending on the model. + + From 7b8fc79ebb959ddf0313b0c6afc7d5fe99f81124 Mon Sep 17 00:00:00 2001 From: Maddy Underwood Date: Fri, 25 Apr 2025 10:10:20 +0000 Subject: [PATCH 118/122] final --- content/learning-paths/cross-platform/adler32/build-6.md | 2 +- content/learning-paths/cross-platform/adler32/makefile-5.md | 2 +- content/learning-paths/cross-platform/adler32/neon-run-8.md | 2 +- content/learning-paths/cross-platform/adler32/simple-c-3.md | 4 ++-- content/learning-paths/cross-platform/adler32/summary-10.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/build-6.md b/content/learning-paths/cross-platform/adler32/build-6.md index 485d90af0c..5639b7d00d 100644 --- a/content/learning-paths/cross-platform/adler32/build-6.md +++ b/content/learning-paths/cross-platform/adler32/build-6.md @@ -6,7 +6,7 @@ weight: 6 layout: learningpathall --- -## How can I build and run the test program? +## How Can I Build and Run the Test Program? You now have all the required files to test the Adler32 algorithm: - A C implementation of the Adler32 function. diff --git a/content/learning-paths/cross-platform/adler32/makefile-5.md b/content/learning-paths/cross-platform/adler32/makefile-5.md index 8766b62620..5e3b72b230 100644 --- a/content/learning-paths/cross-platform/adler32/makefile-5.md +++ b/content/learning-paths/cross-platform/adler32/makefile-5.md @@ -6,7 +6,7 @@ weight: 5 layout: learningpathall --- -## How can I create a Makefile to build and run the test program? +## How Can I Create a Makefile to Build and Run the Test Program? Paste the following prompt into GitHub Copilot. It tells Copilot to generate a Makefile that uses `gcc` and targets the Neoverse N1 processor for optimized performance. diff --git a/content/learning-paths/cross-platform/adler32/neon-run-8.md b/content/learning-paths/cross-platform/adler32/neon-run-8.md index 8553d11998..ff2a761cc7 100644 --- a/content/learning-paths/cross-platform/adler32/neon-run-8.md +++ b/content/learning-paths/cross-platform/adler32/neon-run-8.md @@ -6,7 +6,7 @@ weight: 8 layout: learningpathall --- -## How can I run the NEON version of Adler32 and compare performance? +## How Can I Run the NEON Version of Adler32 and Compare Performance? Before comparing performance, you need to ensure that the NEON implementation builds, runs, and produces the same results as the standard C version. diff --git a/content/learning-paths/cross-platform/adler32/simple-c-3.md b/content/learning-paths/cross-platform/adler32/simple-c-3.md index 924c3d44c7..76a17750b5 100644 --- a/content/learning-paths/cross-platform/adler32/simple-c-3.md +++ b/content/learning-paths/cross-platform/adler32/simple-c-3.md @@ -1,12 +1,12 @@ --- -title: Create a C version of Adler32 +title: Create a C Version of Adler32 weight: 3 ### FIXED, DO NOT MODIFY layout: learningpathall --- -## How can I generate a C function for Adler32? +## How Can I Generate a C Function for Adler32? You can use GitHub Copilot to generate a simple C version of the Adler32 checksum algorithm. diff --git a/content/learning-paths/cross-platform/adler32/summary-10.md b/content/learning-paths/cross-platform/adler32/summary-10.md index e173805957..0c87aef025 100644 --- a/content/learning-paths/cross-platform/adler32/summary-10.md +++ b/content/learning-paths/cross-platform/adler32/summary-10.md @@ -6,7 +6,7 @@ weight: 10 layout: learningpathall --- -## How can I summarize the project results? +## How Can I Summarize the Project Results? You can use GitHub Copilot to generate a project summary in a README file. From bd1ee8cf525579456a3c248cf058baff5ea2a831 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Fri, 25 Apr 2025 08:10:21 -0500 Subject: [PATCH 119/122] Write NEON with GitHub Copilot ready to publish --- content/learning-paths/cross-platform/adler32/_index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/learning-paths/cross-platform/adler32/_index.md b/content/learning-paths/cross-platform/adler32/_index.md index c18d176888..f038fe9c70 100644 --- a/content/learning-paths/cross-platform/adler32/_index.md +++ b/content/learning-paths/cross-platform/adler32/_index.md @@ -1,10 +1,6 @@ --- title: Write NEON intrinsics using GitHub Copilot to improve Adler32 performance -draft: true -cascade: - draft: true - minutes_to_complete: 45 who_is_this_for: This is an introductory topic for C/C++ developers who are interested in using GitHub Copilot to improve performance using NEON intrinsics. From bd5834b759c4619b6d468a00dcac552ce2603556 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Fri, 25 Apr 2025 09:12:45 -0500 Subject: [PATCH 120/122] Refine language in Streamline CLI documentation for clarity and readability --- content/install-guides/streamline-cli.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/install-guides/streamline-cli.md b/content/install-guides/streamline-cli.md index 300eb55ba1..9b56af96d6 100644 --- a/content/install-guides/streamline-cli.md +++ b/content/install-guides/streamline-cli.md @@ -74,7 +74,7 @@ If you are using the `workflow_topdown_basic option`, ensure that your applicati ## Using Python scripts -The Python scripts provided with Streamline CLI tools require Python 3.8 or later, and depend on several third-party modules. We recommend creating a Python virtual environment containing these modules to run the tools. +The Python scripts provided with Streamline CLI tools require Python 3.8 or later, and depend on several third-party modules. It is recommended to create a Python virtual environment containing these modules to run the tools. Create a virtual environment: @@ -141,7 +141,7 @@ https://artifacts.tools.arm.com/arm-performance-studio/Streamline_CLI_Tools/ ## Applying the kernel patch -For best results, we provide a Linux kernel patch that modifies the behavior of Linux perf to improve support for capturing function-attributed top-down metrics on Arm systems. This patch provides two new capabilities: +For best results, a Linux kernel patch is available that modifies the behavior of Linux perf to improve support for capturing function-attributed top-down metrics on Arm systems. This patch provides two new capabilities: * It allows a new thread to inherit the perf counter group configuration of its parent. * It decouples the perf event-based sampling window size from the overall sample rate. This allows strobed mark-space sampling patterns where the tool can capture a small window without using a high sample rate. From 06729caf7d04cbf6bb79e5aaddc6618745c50a3c Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Fri, 25 Apr 2025 12:12:17 -0500 Subject: [PATCH 121/122] spelling and links --- .wordlist.txt | 55 ++++++++++++++++++- .../embedded-and-microcontrollers/_index.md | 15 +++-- content/learning-paths/iot/_index.md | 3 +- .../laptops-and-desktops/_index.md | 26 +++++---- .../mobile-graphics-and-gaming/_index.md | 8 +-- .../04-generic_library.md | 1 - .../servers-and-cloud-computing/_index.md | 19 ++++--- 7 files changed, 94 insertions(+), 33 deletions(-) diff --git a/.wordlist.txt b/.wordlist.txt index f5dcabd506..232cca1f39 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -4008,4 +4008,57 @@ sdks sendgrid soafee timestamping -transactional \ No newline at end of file +transactional +Biquad +CFFT +Christophe +Corriero +EBS +Favergeon +Fio +HDDs +Hanning +IFFT +Microbenchmark +NVMe +Nerdctl +NoiseSuppression +NoiseSuppressionReference +Paladugu +Phalani +PythonWrapper +Rani +RelWithDebInfo +Rescaling +SNR +VisualStudioSetup +WebUI +buildctl +channelwise +checksums +cmsisdsp +fio +frac +hanning +hdd +iops +iostat +iotop +microbenchmark +microbenchmarking +nerdctl +nr +nvme +observability +operationscount +paddings +pidstat +preloads +recordscount +rescaled +rescaling +subnoise +transcoders +transcoding +upi +windowsdeveloper \ No newline at end of file diff --git a/content/learning-paths/embedded-and-microcontrollers/_index.md b/content/learning-paths/embedded-and-microcontrollers/_index.md index 3e971a13be..1008783e14 100644 --- a/content/learning-paths/embedded-and-microcontrollers/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/_index.md @@ -11,15 +11,15 @@ maintopic: true operatingsystems_filter: - Android: 1 - Baremetal: 29 -- Linux: 25 -- macOS: 5 +- Linux: 26 +- macOS: 6 - RTOS: 9 -- Windows: 3 +- Windows: 4 subjects_filter: - CI-CD: 5 - Containers and Virtualization: 6 - Embedded Linux: 3 -- Libraries: 2 +- Libraries: 3 - ML: 12 - Performance and Architecture: 21 - RTOS Fundamentals: 4 @@ -36,11 +36,12 @@ tools_software_languages_filter: - Arm Fast Models: 4 - Arm Virtual Hardware: 10 - AVH: 1 -- C: 1 +- C: 2 - C/C++: 1 - ChatGPT: 1 - Clang: 1 - CMSIS: 4 +- CMSIS-DSP: 1 - CMSIS-Toolbox: 3 - CNN: 1 - Coding: 26 @@ -59,6 +60,7 @@ tools_software_languages_filter: - GitLab: 1 - Himax SDK: 1 - IP Explorer: 4 +- Jupyter Notebook: 1 - K3s: 1 - Keil: 5 - Keil MDK: 3 @@ -70,7 +72,7 @@ tools_software_languages_filter: - Neon: 1 - Paddle: 1 - Porcupine: 1 -- Python: 4 +- Python: 5 - PyTorch: 2 - QEMU: 1 - Raspberry Pi: 5 @@ -86,6 +88,7 @@ tools_software_languages_filter: - TrustZone: 2 - TVMC: 1 - vcpkg: 1 +- VS Code: 1 - Yocto Linux: 1 - Zephyr: 1 weight: 5 diff --git a/content/learning-paths/iot/_index.md b/content/learning-paths/iot/_index.md index 10086206f5..001ca38f09 100644 --- a/content/learning-paths/iot/_index.md +++ b/content/learning-paths/iot/_index.md @@ -24,9 +24,10 @@ tools_software_languages_filter: - Arm Compiler for Embedded: 1 - Arm Virtual Hardware: 6 - AWS IoT Greengrass: 1 +- Azure: 1 - Balena Cloud: 1 - Balena OS: 1 -- Coding: 4 +- Coding: 3 - Docker: 2 - Fixed Virtual Platform: 1 - GitHub: 3 diff --git a/content/learning-paths/laptops-and-desktops/_index.md b/content/learning-paths/laptops-and-desktops/_index.md index 4211895996..20a7e5952f 100644 --- a/content/learning-paths/laptops-and-desktops/_index.md +++ b/content/learning-paths/laptops-and-desktops/_index.md @@ -10,15 +10,15 @@ maintopic: true operatingsystems_filter: - Android: 2 - ChromeOS: 1 -- Linux: 30 +- Linux: 31 - macOS: 7 -- Windows: 40 +- Windows: 42 subjects_filter: -- CI-CD: 4 +- CI-CD: 5 - Containers and Virtualization: 5 - Migration to Arm: 27 -- ML: 1 -- Performance and Architecture: 24 +- ML: 2 +- Performance and Architecture: 25 subtitle: Create and migrate apps for power efficient performance title: Laptops and Desktops tools_software_languages_filter: @@ -26,21 +26,24 @@ tools_software_languages_filter: - Alacritty: 1 - Android Studio: 1 - Arm Development Studio: 1 +- Arm Performance Libraries: 2 - Arm64EC: 1 - assembly: 1 - C: 3 - C#: 6 -- C++: 5 +- C++: 6 - C/C++: 4 - CCA: 1 - Clang: 11 +- cmake: 1 - CMake: 2 - Coding: 16 - CSS: 1 - Daytona: 1 - Docker: 4 -- GCC: 9 -- GitHub: 2 +- GCC: 10 +- Git: 1 +- GitHub: 3 - GitLab: 1 - GoogleTest: 1 - HTML: 2 @@ -52,22 +55,23 @@ tools_software_languages_filter: - Linux: 1 - LLVM: 1 - llvm-mca: 1 +- MSBuild: 1 - MTE: 1 - Neon: 1 - Neovim: 1 - Node.js: 3 - OpenCV: 1 - perf: 4 -- Python: 3 +- Python: 4 - Qt: 2 - Remote.It: 1 - RME: 1 -- Runbook: 17 +- Runbook: 18 - Rust: 2 - SVE: 1 - SVE2: 1 - Trusted Firmware: 1 -- Visual Studio: 12 +- Visual Studio: 14 - Visual Studio Code: 9 - VS Code: 3 - Windows Forms: 1 diff --git a/content/learning-paths/mobile-graphics-and-gaming/_index.md b/content/learning-paths/mobile-graphics-and-gaming/_index.md index 205be57b6a..ee87a4b1d2 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/_index.md @@ -11,14 +11,14 @@ key_ip: maintopic: true operatingsystems_filter: - Android: 28 -- Linux: 23 +- Linux: 24 - macOS: 10 - Windows: 11 subjects_filter: - Gaming: 6 - Graphics: 6 - ML: 10 -- Performance and Architecture: 26 +- Performance and Architecture: 27 subtitle: Optimize Android apps and build faster games using cutting-edge Arm tech title: Mobile, Graphics, and Gaming tools_software_languages_filter: @@ -43,7 +43,7 @@ tools_software_languages_filter: - Coding: 16 - ExecuTorch: 1 - Frame Advisor: 1 -- GCC: 9 +- GCC: 10 - GenAI: 2 - GoogleTest: 1 - Java: 5 @@ -65,7 +65,7 @@ tools_software_languages_filter: - PyTorch: 1 - QEMU: 1 - RME: 1 -- Runbook: 14 +- Runbook: 15 - Rust: 2 - SDDiskTool: 1 - SVE2: 1 diff --git a/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md b/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md index 8fc60785e6..22e2830b25 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md +++ b/content/learning-paths/mobile-graphics-and-gaming/get-started-with-arm-asr/04-generic_library.md @@ -20,7 +20,6 @@ You can integrate Arm ASR into your custom engine using one of two methods: See the following sections to learn how to configure Arm ASR: - [Quality presets](#quality-presets). -- [Performance](#performance). - [Shader variants and extensions](#shader-variants-and-extensions). - [Input resources](#input-resources). - [Providing motion vectors](#providing-motion-vectors). diff --git a/content/learning-paths/servers-and-cloud-computing/_index.md b/content/learning-paths/servers-and-cloud-computing/_index.md index 8dbd81bfc3..bea38eabf4 100644 --- a/content/learning-paths/servers-and-cloud-computing/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/_index.md @@ -8,7 +8,7 @@ key_ip: maintopic: true operatingsystems_filter: - Android: 2 -- Linux: 135 +- Linux: 138 - macOS: 10 - Windows: 14 pinned_modules: @@ -22,8 +22,8 @@ subjects_filter: - Containers and Virtualization: 27 - Databases: 15 - Libraries: 8 -- ML: 24 -- Performance and Architecture: 49 +- ML: 25 +- Performance and Architecture: 51 - Storage: 1 - Web: 10 subtitle: Optimize cloud native apps on Arm for performance and cost @@ -52,6 +52,7 @@ tools_software_languages_filter: - AWS Elastic Kubernetes Service (EKS): 3 - AWS Graviton: 1 - Bash: 1 +- bash: 1 - Bastion: 3 - BOLT: 1 - bpftool: 1 @@ -80,10 +81,10 @@ tools_software_languages_filter: - Fortran: 1 - FunASR: 1 - FVP: 4 -- GCC: 20 +- GCC: 21 - gdb: 1 - Geekbench: 1 -- GenAI: 10 +- GenAI: 11 - GitHub: 6 - GitLab: 1 - Glibc: 1 @@ -104,7 +105,7 @@ tools_software_languages_filter: - libbpf: 1 - Linaro Forge: 1 - Litmus7: 1 -- LLM: 8 +- LLM: 9 - llvm-mca: 1 - LSE: 1 - MariaDB: 1 @@ -123,13 +124,13 @@ tools_software_languages_filter: - PAPI: 1 - perf: 4 - PostgreSQL: 4 -- Python: 25 +- Python: 26 - PyTorch: 8 - RAG: 1 - Redis: 3 - Remote.It: 2 - RME: 4 -- Runbook: 63 +- Runbook: 65 - Rust: 2 - snappy: 1 - Snort3: 1 @@ -150,7 +151,7 @@ tools_software_languages_filter: - Vectorscan: 1 - Veraison: 1 - Visual Studio Code: 4 -- vLLM: 1 +- vLLM: 2 - VS Code: 1 - vvenc: 1 - Whisper: 1 From 9ef3ad81ee63ce6de3baa38b67afbb367297b272 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Fri, 25 Apr 2025 14:47:35 -0500 Subject: [PATCH 122/122] Review wperf install guide --- content/install-guides/wperf.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/install-guides/wperf.md b/content/install-guides/wperf.md index a2badeee96..aa36586b37 100644 --- a/content/install-guides/wperf.md +++ b/content/install-guides/wperf.md @@ -39,7 +39,7 @@ WindowsPerf consists of a kernel-mode driver and a user-space command-line tool. You cannot use WindowsPerf on virtual machines, such as cloud instances. {{% /notice %}} -## Using winget (Recommended) +## Using winget ### Install @@ -95,7 +95,6 @@ Once you have installed the driver, you can use `wperf` without `Administrator` For example: ```command -cd ..\wperf wperf --version ```