diff --git a/.devcontainer/.env b/.devcontainer/.env new file mode 100644 index 0000000..bc546b9 --- /dev/null +++ b/.devcontainer/.env @@ -0,0 +1,5 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=postgres +POSTGRES_HOSTNAME=localhost +POSTGRES_PORT=5432 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..2310526 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/devcontainers/rust:0-1-bullseye + +# Include lld linker to improve build times either by using environment variable +# RUSTFLAGS="-C link-arg=-fuse-ld=lld" or with Cargo's configuration file (i.e see .cargo/config.toml). +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install clang lld lshw \ + && apt-get autoremove -y && apt-get clean -y + +RUN apt-get update && apt-get -y install --no-install-recommends \ + ffmpeg \ + gcc \ + pciutils + +#create global virtual environment using python standard library tools of virtualenv +ARG USER="codespace" +ARG VENV_PATH="/home/${USER}/venv" +COPY requirements.txt /tmp/ +COPY Makefile /tmp/ +RUN su $USER -c "/usr/bin/python -m venv /home/${USER}/venv" \ + && su $USER -c "${VENV_PATH}/bin/pip --disable-pip-version-check --no-cache-dir install -r /tmp/requirements.txt" \ + && rm -rf /tmp/requirements.txt \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b9dd6d8 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,37 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/rust-postgres +{ + "name": "Rust GPU PyTorch", + "features": { + "ghcr.io/devcontainers/features/nvidia-cuda:1": { + }, + "ghcr.io/devcontainers/features/rust:1": {}, + "ghcr.io/eitsupi/devcontainer-features/mdbook:0": {}, + "ghcr.io/guiyomh/features/vim:0": {} + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5432], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "bash setup.sh", + "customizations": { + "vscode": { + "extensions": [ + "GitHub.copilot-labs", + "GitHub.copilot-nightly", + "ms-azuretools.vscode-docker", + "technosophos.vscode-make", + "ms-vscode.makefile-tools" + ] + } + }, + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4a40b01 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: noahgift + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..eb98670 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..a1ae047 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,32 @@ +name: Rust CI/CD Pipeline +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] +env: + CARGO_TERM_COLOR: always +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: clippy, rustfmt + override: true + - name: update linux + run: sudo apt update + - name: update Rust + run: make install + - name: Check Rust versions + run: make rust-version + - name: Format + run: make format + - name: Lint + run: make lint + - name: Test + run: make test + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f20108 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +#don't check in pretrained models +resnet18.ot +*.ot +*.onnx + +#ignore reference repo +diffusers-rs +tch-rs +rust-bert + +#libtorch +libtorch + +#minst +data + +# Generated by Cargo +# will have compiled files and executables +target +debug +*.swp + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +#ignore the diffusers project (large data) +diffusers-rs + +# Added by cargo + +/target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f73a56a --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +install: + python3 -m pip install --upgrade pip \ + && pip install -r requirements.txt +format: + cargo fmt --quiet + +lint: + cargo clippy --quiet + +test: + cargo test --quiet + +clean: + #cargo install cargo-cache + #cargo cache -a + #rm -rf Cargo.lock + cargo clean + +run: + cargo run + +all: format lint test run \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f82941 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +### PyTorch Rust GPU Example + +Exploration of these EXCELLENT bindings of PyTorch and Rust: https://github.com/LaurentMazare + +### Hugging Face GPU Example of Translation + +![building-gpu-translator-hugging-face](https://user-images.githubusercontent.com/58792/215615054-8a8d6b4b-2967-4daa-bf78-9554593f0015.png) + + +Goal: Translate a spanish song to english +* `cargo new translate` and cd into it +fully working GPU Hugging Face Translation CLI in Rust + +run it: `time cargo run -- translate --path lyrics.txt` + +```rust +/*A library that uses Hugging Face to Translate Text +*/ +use rust_bert::pipelines::translation::{Language, TranslationModelBuilder}; +use std::fs::File; +use std::io::Read; + +//build a function that reads a file and returns a string +pub fn read_file(path: String) -> anyhow::Result { + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Ok(contents) +} + +//build a function that reads a file and returns an array of the lines of the file +pub fn read_file_array(path: String) -> anyhow::Result> { + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let array = contents.lines().map(|s| s.to_string()).collect(); + Ok(array) +} + + +//build a function that reads a file and translates it +pub fn translate_file(path: String) -> anyhow::Result<()> { + let model = TranslationModelBuilder::new() + .with_source_languages(vec![Language::Spanish]) + .with_target_languages(vec![Language::English]) + .create_model()?; + let text = read_file_array(path)?; + //pass in the text to the model + let output = model.translate(&text, None, Language::English)?; + for sentence in output { + println!("{}", sentence); + } + Ok(()) +} +``` + + + + +### PyTorch: Day 1-Live Coding: Stress Test CLI for both CPU and GPU PyTorch using Clap + +* `cargo new stress` cd into `stress` +* To test CPU for PyTorch do: `cargo run -- cpu` +* To test GPU for PyTorch do: `cargo run -- gpu` +* To monitor CPU/Memory run `htop` +* To monitor GPU run `nvidia-smi -l 1` +* To use threaded GPU load test use: `cargo run -- tgpu` + + +![stress-test-cuda-gpu-with-pytorch-rust](https://user-images.githubusercontent.com/58792/215621711-c222e59b-f4e1-4fd0-8202-339b986b4090.png) + + + +#### Hello World Stress Test +A repo to show how GPUs work with Rust and PyTorch. +`export TORCH_CUDA_VERSION=cu117` + +The cd into `pytorch-gpu-util` and run `cargo run -- gpu` + + +One tip is to look into your build to ensure the crate actually downloaded the cuda version: +```bash + ls -l /workspaces/rust-pytorch-gpu-template/pytorch-gpu-util/target/debug/build/torch-sys-0893541a21a2091d/out/libtorch/libtorch/lib | grep cuda +-rw-rw-rw- 1 codespace codespace 1235152 Jan 16 22:18 libc10_cuda.so +-rw-rw-rw- 1 codespace codespace 828800 Jan 16 22:18 libc10d_cuda_test.so +-rw-rw-rw- 1 codespace codespace 687320 Jan 16 22:20 libcudart-e409450e.so.11.0 +-rw-rw-rw- 1 codespace codespace 7221084 Jan 16 22:18 libgloo_cuda.a +-rw-rw-rw- 1 codespace codespace 3769170 Jan 16 22:18 libtensorpipe_cuda.a +-rw-rw-rw- 1 codespace codespace 382610744 Jan 16 22:19 libtorch_cuda_cpp.so +-rw-rw-rw- 1 codespace codespace 753941192 Jan 16 22:20 libtorch_cuda_cu.so +-rw-rw-rw- 1 codespace codespace 219665888 Jan 16 22:20 libtorch_cuda_linalg.so +-rw-rw-rw- 1 codespace codespace 7496 Jan 16 22:20 libtorch_cuda.so +``` + +#### MNIST Convolutional Neural-Network + +Ensure this variable is set: `export TORCH_CUDA_VERSION=cu117` +cd into `pytorch-mnist` and run `cargo run -- conv`. + +#### Stable diffusion demo + +* clone this repo: https://github.com/LaurentMazare/diffusers-rs +* Follow these setup instructions: https://github.com/LaurentMazare/diffusers-rs#clip-encoding-weights + +After all the weights are downloaded run: + +`cargo run --example stable-diffusion --features clap -- --prompt "A very rusty robot holding a fire torch to notebooks"` +![Screenshot 2023-01-16 at 5 57 59 PM](https://user-images.githubusercontent.com/58792/212777548-0d9619e8-ad1b-4cc9-8871-505b0b5b2345.png) + +Stable Diffusion 2.1 Pegging GPU +![Screenshot 2023-01-17 at 9 30 47 AM](https://user-images.githubusercontent.com/58792/212926307-351db4bc-46ff-4e8d-8630-ce996dca65c9.png) + +Rusty Robot Torching Notebooks +![sd_final](https://user-images.githubusercontent.com/58792/212926379-d460a54c-29cf-42bb-801a-29e50557369e.png) + +#### TBD: Linking PyTorch into binary + +Ideas From Jeremy Wall: + +I believe the easiest way is to use the #[link] attribute https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute. But you can also force it with rustc's -l argument https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library You can just set the RUSTFLAGS env variable to set that flag for cargo builds + + + + + + + diff --git a/cuda-11.7.sh b/cuda-11.7.sh new file mode 100755 index 0000000..b8c3da8 --- /dev/null +++ b/cuda-11.7.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +#manually install CUDA 11.7 +wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin +sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600 +wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda-repo-ubuntu1804-11-7-local_11.7.0-515.43.04-1_amd64.deb +sudo dpkg -i cuda-repo-ubuntu1804-11-7-local_11.7.0-515.43.04-1_amd64.deb +sudo cp /var/cuda-repo-ubuntu1804-11-7-local/cuda-*-keyring.gpg /usr/share/keyrings/ +sudo apt-get update +sudo apt-get -y install cuda \ No newline at end of file diff --git a/env.sh b/env.sh new file mode 100644 index 0000000..ce4d036 --- /dev/null +++ b/env.sh @@ -0,0 +1,3 @@ +echo 'export TORCH_CUDA_VERSION="cu117"' >> ~/.bashrc +#echo 'export LIBTORCH="/workspaces/rust-pytorch-gpu-template/libtorch"' >> ~/.bashrc +#echo 'export LD_LIBRARY_PATH=${LIBTORCH}/lib' >> ~/.bashrc \ No newline at end of file diff --git a/hello-pytorch/Cargo.toml b/hello-pytorch/Cargo.toml new file mode 100644 index 0000000..f838dee --- /dev/null +++ b/hello-pytorch/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hello-pytorch" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tch = "0.10.1" \ No newline at end of file diff --git a/hello-pytorch/src/main.rs b/hello-pytorch/src/main.rs new file mode 100644 index 0000000..376a6cf --- /dev/null +++ b/hello-pytorch/src/main.rs @@ -0,0 +1,7 @@ +use tch::Tensor; + +fn main() { + let t = Tensor::of_slice(&[3, 1, 4, 1, 5]); + let t = t * 2; + t.print(); +} \ No newline at end of file diff --git a/install-libtorch.sh b/install-libtorch.sh new file mode 100755 index 0000000..0025b35 --- /dev/null +++ b/install-libtorch.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +#download libtorch v1.13.0 and extract and overwrite to libtorch with CUDA support 11.7 +rm -rf libtorch +wget https://download.pytorch.org/libtorch/cu117/libtorch-cxx11-abi-shared-with-deps-1.13.1%2Bcu117.zip +unzip libtorch-cxx11-abi-shared-with-deps-1.13.1+cu117.zip +rm -rf libtorch-cxx11-abi-shared-with-deps-1.13.1+cu117.zip \ No newline at end of file diff --git a/load-mnist.sh b/load-mnist.sh new file mode 100755 index 0000000..2dba258 --- /dev/null +++ b/load-mnist.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Load the MNIST dataset into a data directory +# Usage: load-mnist.sh +# Example: load-mnist.sh data +set -e +if [ "$#" -ne 1 ]; then + echo "Usage: load-mnist.sh " + exit 1 +fi +DATA_DIR=$1 +mkdir -p $DATA_DIR +cd $DATA_DIR +if [ ! -f train-images-idx3-ubyte.gz ]; then + wget http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz +fi +if [ ! -f train-labels-idx1-ubyte.gz ]; then + wget http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz +fi +if [ ! -f t10k-images-idx3-ubyte.gz ]; then + wget http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz +fi +if [ ! -f t10k-labels-idx1-ubyte.gz ]; then + wget http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz +fi +gunzip -f train-images-idx3-ubyte.gz +gunzip -f train-labels-idx1-ubyte.gz +gunzip -f t10k-images-idx3-ubyte.gz +gunzip -f t10k-labels-idx1-ubyte.gz +cd - diff --git a/lyrics.txt b/lyrics.txt new file mode 100644 index 0000000..0b37e02 --- /dev/null +++ b/lyrics.txt @@ -0,0 +1,54 @@ +Uh-uh-uh-uh, uh-uh +Ella despidió a su amor +El partió en un barco en el muelle de San Blas +El juró que volvería +Y empapada en llanto, ella juró que esperaría +Miles de lunas pasaron +Y siempre ella estaba en el muelle, esperando +Muchas tardes se anidaron +Se anidaron en su pelo y en sus labios +Uh-uh-uh-uh, uh-uh +Uh-uh-uh-uh, uh-uh +Llevaba el mismo vestido +Y por si él volviera, no se fuera a equivocar +Los cangrejos le mordían +Su ropaje, su tristeza y su ilusión +Y el tiempo se escurrió +Y sus ojos se le llenaron de amaneceres +Y del mar se enamoró +Y su cuerpo se enraizó en el muelle +, sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con su amor el mar +(Sola), en el muelle de San Blas +Su cabello se blanqueó +Pero ningún barco a su amor le devolvía +Y en el pueblo le decían +Le decían la loca del muelle de San Blas +Y una tarde de abril +La intentaron trasladar al manicomio +Nadie la pudo arrancar +Y del mar nunca jamás la separaron +, sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con su amor el mar +(Sola), en el muelle de San Blas +, sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con el sol y el mar +(Sola), ¡Oh, sola! +Sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con su amor el mar +(Sola), en el muelle de San Blas +Se quedó +Se quedó sola, sola +Se quedó +Se quedó con el sol y con el mar +Se quedó ahí +Se quedó hasta el fin +Se quedó ahí +Se quedó en el muelle de San Blas +Uoh, oh-oh-oh +Sola, sola se quedó +Uoh, oh-oh-oh \ No newline at end of file diff --git a/mnist-cli-gpu/Cargo.toml b/mnist-cli-gpu/Cargo.toml new file mode 100644 index 0000000..b6dc9d3 --- /dev/null +++ b/mnist-cli-gpu/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mnist-cli-gpu" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = {version="4.0.32", features=["derive"]} +anyhow = "1.0.40" +#pytorch rust +tch = "~0.10.1" \ No newline at end of file diff --git a/mnist-cli-gpu/Makefile b/mnist-cli-gpu/Makefile new file mode 100644 index 0000000..f73a56a --- /dev/null +++ b/mnist-cli-gpu/Makefile @@ -0,0 +1,22 @@ +install: + python3 -m pip install --upgrade pip \ + && pip install -r requirements.txt +format: + cargo fmt --quiet + +lint: + cargo clippy --quiet + +test: + cargo test --quiet + +clean: + #cargo install cargo-cache + #cargo cache -a + #rm -rf Cargo.lock + cargo clean + +run: + cargo run + +all: format lint test run \ No newline at end of file diff --git a/mnist-cli-gpu/src/lib.rs b/mnist-cli-gpu/src/lib.rs new file mode 100644 index 0000000..d1f85a7 --- /dev/null +++ b/mnist-cli-gpu/src/lib.rs @@ -0,0 +1,54 @@ +// This is the main library that will be called by the CLI. + +use anyhow::Result; +use tch::{nn, nn::ModuleT, nn::OptimizerConfig, Device, Tensor}; + +#[derive(Debug)] +struct Net { + conv2: nn::Conv2D, +} +impl Net { + fn new(vs: &nn::Path) -> Net { + let conv2 = nn::conv2d(vs, 32, 64, 5, Default::default()); + Net { conv2, } + } +} + +impl nn::ModuleT for Net { + fn forward_t(&self, xs: &Tensor, train: bool) -> Tensor { + xs.view([-1, 1, 28, 28]) + .apply(&self.conv1) + .max_pool2d_default(2) + .apply(&self.conv2) + .max_pool2d_default(2) + .view([-1, 1024]) + .apply(&self.fc1) + .relu() + .dropout(0.5, train) + .apply(&self.fc2) + } +} + +/* +This is the main function that will be called by the CLI. +This accepts a path to the MNIST dataset and trains a model on it. +It defaults to data/ if no path is provided. +*/ +pub fn run(data: &str) -> Result<()> { + //default to data if not provided + let data = if data.is_empty() { "data" } else { data }; + let m = tch::vision::mnist::load_dir(data)?; + let vs = nn::VarStore::new(Device::cuda_if_available()); + let net = Net::new(&vs.root()); + let mut opt = nn::Adam::default().build(&vs, 1e-4)?; + for epoch in 1..100 { + for (bimages, blabels) in m.train_iter(256).shuffle().to_device(vs.device()) { + let loss = net.forward_t(&bimages, true).cross_entropy_for_logits(&blabels); + opt.backward_step(&loss); + } + let test_accuracy = + net.batch_accuracy_for_logits(&m.test_images, &m.test_labels, vs.device(), 1024); + println!("epoch: {:4} test acc: {:5.2}%", epoch, 100. * test_accuracy,); + } + Ok(()) +} \ No newline at end of file diff --git a/mnist-cli-gpu/src/main.rs b/mnist-cli-gpu/src/main.rs new file mode 100644 index 0000000..de5e088 --- /dev/null +++ b/mnist-cli-gpu/src/main.rs @@ -0,0 +1,30 @@ +/* Invoke CLI */ +//A command-line tool to invoke the run function +use clap::Parser; + +#[derive(Parser)] +#[clap(version = "1.0", author = "Noah Gift", about = "Run MNIST on GPU")] +struct Cli { + #[clap(subcommand)] + command: Option, +} + +#[derive(Parser)] +enum Commands { + #[clap(version = "1.0", author = "Noah Gift")] + Train { + #[clap(short, long, default_value = "data/")] + path: String, + }, +} + +fn main() { + let args = Cli::parse(); + match args.command { + Some(Commands::Train { path }) => { + println!("Training MNIST on GPU"); + mnist_cli_gpu::run(&path); + } + None => println!("No command was used"), + } +} \ No newline at end of file diff --git a/model.pth b/model.pth new file mode 100644 index 0000000..28dde67 Binary files /dev/null and b/model.pth differ diff --git a/portable-pytorch/Cargo.toml b/portable-pytorch/Cargo.toml new file mode 100644 index 0000000..2ffd30a --- /dev/null +++ b/portable-pytorch/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "portable-pytorch" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tract-onnx = { version = "0.19.3-pre", git="https://github.com/sonos/tract" } +clap = {version="4.0.32", features=["derive"]} +image = "0.24.1" +ndarray = "0.15.3" diff --git a/portable-pytorch/Makefile b/portable-pytorch/Makefile new file mode 100644 index 0000000..0a31549 --- /dev/null +++ b/portable-pytorch/Makefile @@ -0,0 +1,26 @@ +install: + python3 -m pip install --upgrade pip \ + && pip install -r requirements.txt + +export: + python export.py + +format: + cargo fmt --quiet + +lint: + cargo clippy --quiet + +test: + cargo test --quiet + +clean: + #cargo install cargo-cache + #cargo cache -a + #rm -rf Cargo.lock + cargo clean + +run: + cargo run + +all: format lint test run \ No newline at end of file diff --git a/portable-pytorch/README.md b/portable-pytorch/README.md new file mode 100644 index 0000000..4638841 --- /dev/null +++ b/portable-pytorch/README.md @@ -0,0 +1 @@ +You will need to download resnet18.ot \ No newline at end of file diff --git a/portable-pytorch/export.py b/portable-pytorch/export.py new file mode 100644 index 0000000..32e8e92 --- /dev/null +++ b/portable-pytorch/export.py @@ -0,0 +1,10 @@ +import torch +import torchvision + + +def export(): + #TBD: add your model here + + +if __name__ == "__main__": + export() \ No newline at end of file diff --git a/portable-pytorch/requirements.txt b/portable-pytorch/requirements.txt new file mode 100644 index 0000000..abf5036 --- /dev/null +++ b/portable-pytorch/requirements.txt @@ -0,0 +1 @@ +torchvision \ No newline at end of file diff --git a/portable-pytorch/src/lib.rs b/portable-pytorch/src/lib.rs new file mode 100644 index 0000000..ea5d511 --- /dev/null +++ b/portable-pytorch/src/lib.rs @@ -0,0 +1,42 @@ +use tract_onnx::prelude::DatumExt; +use tract_ndarray::Array; +use tract_ndarray::prelude::* + + +//accepts image as input string +pub fn export(imagepath: String) -> Result<(), Box> { + let img = image::open(imagepath).unwrap().to_rgb8(); //imagepath is the path to the image + let model = tract_onnx::onnx() + // load the model + .model_for_path("resnet18-v1-7.onnx")? + // specify input type and shape + .with_input_fact(0, f32::fact([1, 3, 224, 224]).into())? + // optimize the model + .into_optimized()? + // make the model runnable and fix its inputs and outputs + .into_runnable()?; + + // Imagenet mean and standard deviation + let mean = Array::from_shape_vec((1, 3, 1, 1), vec![0.485, 0.456, 0.406])?; + let std = Array::from_shape_vec((1, 3, 1, 1), vec![0.229, 0.224, 0.225])?; + + let resized = image::imageops::resize(&img, 224, 224, ::image::imageops::FilterType::Triangle); + let image: Tensor = + ((tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| { + resized[(x as _, y as _)][c] as f32 / 255.0 + }) - mean) + / std) + .into(); + + let result = model.run(tvec!(image.into()))?; + + // find and display the max value with its index + let best = result[0] + .to_array_view::()? + .iter() + .cloned() + .zip(1..) + .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + println!("result: {best:?}"); + Ok(()) +} diff --git a/portable-pytorch/src/main.rs b/portable-pytorch/src/main.rs new file mode 100644 index 0000000..4649976 --- /dev/null +++ b/portable-pytorch/src/main.rs @@ -0,0 +1,69 @@ +/*Command-line interface for ONNX */ +use clap::Parser; + +#[derive(Parser)] +#[clap( + version = "1.0", + author = "Noah Gift", + about = "Command-line interface for ONNX resnet18 (portable-pytorch)" +)] +struct Cli { + #[clap(subcommand)] + command: Option, +} + +#[derive(Parser)] +enum Commands { + #[clap(version = "1.0", author = "Noah Gift")] + Infer { + #[clap(short, long)] + imagepath: String, + }, +} + +fn main() -> Result<(), Box> { + let args = Cli::parse(); + match args.command { + Some(Commands::Infer { imagepath }) => { + println!("imagepath: {}", imagepath); + let img = image::open(imagepath).unwrap().to_rgb8(); //imagepath is the path to the image + let model = tract_onnx::onnx() + // load the model + .model_for_path("resnet18-v1-7.onnx")? + // specify input type and shape + .with_input_fact(0, f32::fact([1, 3, 224, 224]).into())? + // optimize the model + .into_optimized()? + // make the model runnable and fix its inputs and outputs + .into_runnable()?; + + // Imagenet mean and standard deviation + let mean = Array::from_shape_vec((1, 3, 1, 1), vec![0.485, 0.456, 0.406])?; + let std = Array::from_shape_vec((1, 3, 1, 1), vec![0.229, 0.224, 0.225])?; + + let resized = image::imageops::resize(&img, 224, 224, ::image::imageops::FilterType::Triangle); + let image: Tensor = + ((tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| { + resized[(x as _, y as _)][c] as f32 / 255.0 + }) - mean) + / std) + .into(); + + let result = model.run(tvec!(image.into()))?; + + // find and display the max value with its index + let best = result[0] + .to_array_view::()? + .iter() + .cloned() + .zip(1..) + .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + println!("result: {best:?}"); + Ok(()) + } + None => { + println!("No command provided"); + Ok(()) + } + } +} \ No newline at end of file diff --git a/post-install.sh b/post-install.sh new file mode 100755 index 0000000..4f34408 --- /dev/null +++ b/post-install.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +/home/codespace/.python/current/bin/python -m venv /home/codespace/venv/ +source /home/codespace/venv/bin/activate \ No newline at end of file diff --git a/pytorch-cli b/pytorch-cli new file mode 100755 index 0000000..8d0e342 Binary files /dev/null and b/pytorch-cli differ diff --git a/pytorch-gpu-util/Cargo.toml b/pytorch-gpu-util/Cargo.toml new file mode 100644 index 0000000..8199663 --- /dev/null +++ b/pytorch-gpu-util/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pytorch-gpu-util" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tch = "0.10.1" \ No newline at end of file diff --git a/pytorch-gpu-util/src/main.rs b/pytorch-gpu-util/src/main.rs new file mode 100644 index 0000000..1d22227 --- /dev/null +++ b/pytorch-gpu-util/src/main.rs @@ -0,0 +1,19 @@ +// This example is useful to check for memory leaks. +// A large number of tensors are created either on the cpu or on the gpu and one +// can monitor the main memory usage or the gpu memory at the same time. +use tch::Tensor; + +fn main() { + let a: Vec = std::env::args().collect(); + let device = match a.iter().map(|x| x.as_str()).collect::>().as_slice() { + [_] => tch::Device::Cpu, + [_, "cpu"] => tch::Device::Cpu, + [_, "gpu"] => tch::Device::Cuda(0), + _ => panic!("usage: main cpu|gpu"), + }; + let slice = vec![0; 1_000_000]; + for i in 1..1_000_000 { + let t = Tensor::of_slice(&slice).to_device(device); + println!("{} {:?}", i, t.size()) + } +} \ No newline at end of file diff --git a/pytorch-mnist/Cargo.toml b/pytorch-mnist/Cargo.toml new file mode 100644 index 0000000..fb14939 --- /dev/null +++ b/pytorch-mnist/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pytorch-mnist" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.40" +#pytorch rust +tch = "~0.10.1" \ No newline at end of file diff --git a/pytorch-mnist/README.md b/pytorch-mnist/README.md new file mode 100644 index 0000000..1820c0b --- /dev/null +++ b/pytorch-mnist/README.md @@ -0,0 +1,3 @@ +### Notes + +Still figuring out why GPU not firing \ No newline at end of file diff --git a/pytorch-mnist/src/main.rs b/pytorch-mnist/src/main.rs new file mode 100644 index 0000000..f52c59a --- /dev/null +++ b/pytorch-mnist/src/main.rs @@ -0,0 +1,25 @@ +/* Some very simple models trained on the MNIST dataset. + The 4 following dataset files can be downloaded from http://yann.lecun.com/exdb/mnist/ + These files should be extracted in the 'data' directory. + train-images-idx3-ubyte.gz + train-labels-idx1-ubyte.gz + t10k-images-idx3-ubyte.gz + t10k-labels-idx1-ubyte.gz +*/ + +use anyhow::Result; + +mod mnist_conv; +mod mnist_linear; +mod mnist_nn; + +fn main() -> Result<()> { + let args: Vec = std::env::args().collect(); + let model = if args.len() < 2 { None } else { Some(args[1].as_str()) }; + match model { + None => mnist_nn::run(), + Some("linear") => mnist_linear::run(), + Some("conv") => mnist_conv::run(), + Some(_) => mnist_nn::run(), + } +} \ No newline at end of file diff --git a/pytorch-mnist/src/mnist_conv.rs b/pytorch-mnist/src/mnist_conv.rs new file mode 100644 index 0000000..f8aa55e --- /dev/null +++ b/pytorch-mnist/src/mnist_conv.rs @@ -0,0 +1,54 @@ +// CNN model. This should rearch 99.1% accuracy. + +use anyhow::Result; +use tch::{nn, nn::ModuleT, nn::OptimizerConfig, Device, Tensor}; + +#[derive(Debug)] +struct Net { + conv1: nn::Conv2D, + conv2: nn::Conv2D, + fc1: nn::Linear, + fc2: nn::Linear, +} + +impl Net { + fn new(vs: &nn::Path) -> Net { + let conv1 = nn::conv2d(vs, 1, 32, 5, Default::default()); + let conv2 = nn::conv2d(vs, 32, 64, 5, Default::default()); + let fc1 = nn::linear(vs, 1024, 1024, Default::default()); + let fc2 = nn::linear(vs, 1024, 10, Default::default()); + Net { conv1, conv2, fc1, fc2 } + } +} + +impl nn::ModuleT for Net { + fn forward_t(&self, xs: &Tensor, train: bool) -> Tensor { + xs.view([-1, 1, 28, 28]) + .apply(&self.conv1) + .max_pool2d_default(2) + .apply(&self.conv2) + .max_pool2d_default(2) + .view([-1, 1024]) + .apply(&self.fc1) + .relu() + .dropout(0.5, train) + .apply(&self.fc2) + } +} + +pub fn run() -> Result<()> { + let m = tch::vision::mnist::load_dir("data")?; + let vs = nn::VarStore::new(Device::cuda_if_available()); + let net = Net::new(&vs.root()); + let mut opt = nn::Adam::default().build(&vs, 1e-4)?; + for epoch in 1..100 { + for (bimages, blabels) in m.train_iter(256).shuffle().to_device(vs.device()) { + let loss = net.forward_t(&bimages, true).cross_entropy_for_logits(&blabels); + opt.backward_step(&loss); + } + let test_accuracy = + net.batch_accuracy_for_logits(&m.test_images, &m.test_labels, vs.device(), 1024); + println!("epoch: {:4} test acc: {:5.2}%", epoch, 100. * test_accuracy,); + } + Ok(()) +} \ No newline at end of file diff --git a/pytorch-mnist/src/mnist_linear.rs b/pytorch-mnist/src/mnist_linear.rs new file mode 100644 index 0000000..3c12246 --- /dev/null +++ b/pytorch-mnist/src/mnist_linear.rs @@ -0,0 +1,42 @@ +// This should rearch 91.5% accuracy. + +use anyhow::Result; +use tch::{kind, no_grad, vision, Kind, Tensor}; + +const IMAGE_DIM: i64 = 784; +const LABELS: i64 = 10; + +pub fn run() -> Result<()> { + let m = vision::mnist::load_dir("data")?; + println!("train-images: {:?}", m.train_images.size()); + println!("train-labels: {:?}", m.train_labels.size()); + println!("test-images: {:?}", m.test_images.size()); + println!("test-labels: {:?}", m.test_labels.size()); + let mut ws = Tensor::zeros(&[IMAGE_DIM, LABELS], kind::FLOAT_CPU).set_requires_grad(true); + let mut bs = Tensor::zeros(&[LABELS], kind::FLOAT_CPU).set_requires_grad(true); + for epoch in 1..200 { + let logits = m.train_images.mm(&ws) + &bs; + let loss = logits.log_softmax(-1, Kind::Float).nll_loss(&m.train_labels); + ws.zero_grad(); + bs.zero_grad(); + loss.backward(); + no_grad(|| { + ws += ws.grad() * (-1); + bs += bs.grad() * (-1); + }); + let test_logits = m.test_images.mm(&ws) + &bs; + let test_accuracy = test_logits + .argmax(Some(-1), false) + .eq_tensor(&m.test_labels) + .to_kind(Kind::Float) + .mean(Kind::Float) + .double_value(&[]); + println!( + "epoch: {:4} train loss: {:8.5} test acc: {:5.2}%", + epoch, + loss.double_value(&[]), + 100. * test_accuracy + ); + } + Ok(()) +} \ No newline at end of file diff --git a/pytorch-mnist/src/mnist_nn.rs b/pytorch-mnist/src/mnist_nn.rs new file mode 100644 index 0000000..d22dc68 --- /dev/null +++ b/pytorch-mnist/src/mnist_nn.rs @@ -0,0 +1,34 @@ +// This should rearch 97% accuracy. + +use anyhow::Result; +use tch::{nn, nn::Module, nn::OptimizerConfig, Device}; + +const IMAGE_DIM: i64 = 784; +const HIDDEN_NODES: i64 = 128; +const LABELS: i64 = 10; + +fn net(vs: &nn::Path) -> impl Module { + nn::seq() + .add(nn::linear(vs / "layer1", IMAGE_DIM, HIDDEN_NODES, Default::default())) + .add_fn(|xs| xs.relu()) + .add(nn::linear(vs, HIDDEN_NODES, LABELS, Default::default())) +} + +pub fn run() -> Result<()> { + let m = tch::vision::mnist::load_dir("data")?; + let vs = nn::VarStore::new(Device::Cpu); + let net = net(&vs.root()); + let mut opt = nn::Adam::default().build(&vs, 1e-3)?; + for epoch in 1..200 { + let loss = net.forward(&m.train_images).cross_entropy_for_logits(&m.train_labels); + opt.backward_step(&loss); + let test_accuracy = net.forward(&m.test_images).accuracy_for_logits(&m.test_labels); + println!( + "epoch: {:4} train loss: {:8.5} test acc: {:5.2}%", + epoch, + f64::from(&loss), + 100. * f64::from(&test_accuracy), + ); + } + Ok(()) +} \ No newline at end of file diff --git a/quickstart_pytorch.py b/quickstart_pytorch.py new file mode 100755 index 0000000..7289b1c --- /dev/null +++ b/quickstart_pytorch.py @@ -0,0 +1,241 @@ +""" +`Learn the Basics `_ || +**Quickstart** || +`Tensors `_ || +`Datasets & DataLoaders `_ || +`Transforms `_ || +`Build Model `_ || +`Autograd `_ || +`Optimization `_ || +`Save & Load Model `_ + +Quickstart +=================== +This section runs through the API for common tasks in machine learning. Refer to the links in each section to dive deeper. + +Working with data +----------------- +PyTorch has two `primitives to work with data `_: +``torch.utils.data.DataLoader`` and ``torch.utils.data.Dataset``. +``Dataset`` stores the samples and their corresponding labels, and ``DataLoader`` wraps an iterable around +the ``Dataset``. + +""" + +import torch +from torch import nn +from torch.utils.data import DataLoader +from torchvision import datasets +from torchvision.transforms import ToTensor + +###################################################################### +# PyTorch offers domain-specific libraries such as `TorchText `_, +# `TorchVision `_, and `TorchAudio `_, +# all of which include datasets. For this tutorial, we will be using a TorchVision dataset. +# +# The ``torchvision.datasets`` module contains ``Dataset`` objects for many real-world vision data like +# CIFAR, COCO (`full list here `_). In this tutorial, we +# use the FashionMNIST dataset. Every TorchVision ``Dataset`` includes two arguments: ``transform`` and +# ``target_transform`` to modify the samples and labels respectively. + +# Download training data from open datasets. +training_data = datasets.FashionMNIST( + root="data", + train=True, + download=True, + transform=ToTensor(), +) + +# Download test data from open datasets. +test_data = datasets.FashionMNIST( + root="data", + train=False, + download=True, + transform=ToTensor(), +) + +###################################################################### +# We pass the ``Dataset`` as an argument to ``DataLoader``. This wraps an iterable over our dataset, and supports +# automatic batching, sampling, shuffling and multiprocess data loading. Here we define a batch size of 64, i.e. each element +# in the dataloader iterable will return a batch of 64 features and labels. + +batch_size = 64 + +# Create data loaders. +train_dataloader = DataLoader(training_data, batch_size=batch_size) +test_dataloader = DataLoader(test_data, batch_size=batch_size) + +for X, y in test_dataloader: + print(f"Shape of X [N, C, H, W]: {X.shape}") + print(f"Shape of y: {y.shape} {y.dtype}") + break + +###################################################################### +# Read more about `loading data in PyTorch `_. +# + +###################################################################### +# -------------- +# + +################################ +# Creating Models +# ------------------ +# To define a neural network in PyTorch, we create a class that inherits +# from `nn.Module `_. We define the layers of the network +# in the ``__init__`` function and specify how data will pass through the network in the ``forward`` function. To accelerate +# operations in the neural network, we move it to the GPU if available. + +# Get cpu or gpu device for training. +device = "cuda" if torch.cuda.is_available() else "cpu" +print(f"Using {device} device") + +# Define model +class NeuralNetwork(nn.Module): + def __init__(self): + super().__init__() + self.flatten = nn.Flatten() + self.linear_relu_stack = nn.Sequential( + nn.Linear(28*28, 512), + nn.ReLU(), + nn.Linear(512, 512), + nn.ReLU(), + nn.Linear(512, 10) + ) + + def forward(self, x): + x = self.flatten(x) + logits = self.linear_relu_stack(x) + return logits + +model = NeuralNetwork().to(device) +print(model) + +###################################################################### +# Read more about `building neural networks in PyTorch `_. +# + + +###################################################################### +# -------------- +# + + +##################################################################### +# Optimizing the Model Parameters +# ---------------------------------------- +# To train a model, we need a `loss function `_ +# and an `optimizer `_. + +loss_fn = nn.CrossEntropyLoss() +optimizer = torch.optim.SGD(model.parameters(), lr=1e-3) + + +####################################################################### +# In a single training loop, the model makes predictions on the training dataset (fed to it in batches), and +# backpropagates the prediction error to adjust the model's parameters. + +def train(dataloader, model, loss_fn, optimizer): + size = len(dataloader.dataset) + model.train() + for batch, (X, y) in enumerate(dataloader): + X, y = X.to(device), y.to(device) + + # Compute prediction error + pred = model(X) + loss = loss_fn(pred, y) + + # Backpropagation + optimizer.zero_grad() + loss.backward() + optimizer.step() + + if batch % 100 == 0: + loss, current = loss.item(), batch * len(X) + print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") + +############################################################################## +# We also check the model's performance against the test dataset to ensure it is learning. + +def test(dataloader, model, loss_fn): + size = len(dataloader.dataset) + num_batches = len(dataloader) + model.eval() + test_loss, correct = 0, 0 + with torch.no_grad(): + for X, y in dataloader: + X, y = X.to(device), y.to(device) + pred = model(X) + test_loss += loss_fn(pred, y).item() + correct += (pred.argmax(1) == y).type(torch.float).sum().item() + test_loss /= num_batches + correct /= size + print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n") + +############################################################################## +# The training process is conducted over several iterations (*epochs*). During each epoch, the model learns +# parameters to make better predictions. We print the model's accuracy and loss at each epoch; we'd like to see the +# accuracy increase and the loss decrease with every epoch. + +epochs = 5 +for t in range(epochs): + print(f"Epoch {t+1}\n-------------------------------") + train(train_dataloader, model, loss_fn, optimizer) + test(test_dataloader, model, loss_fn) +print("Done!") + +###################################################################### +# Read more about `Training your model `_. +# + +###################################################################### +# -------------- +# + +###################################################################### +# Saving Models +# ------------- +# A common way to save a model is to serialize the internal state dictionary (containing the model parameters). + +torch.save(model.state_dict(), "model.pth") +print("Saved PyTorch Model State to model.pth") + + + +###################################################################### +# Loading Models +# ---------------------------- +# +# The process for loading a model includes re-creating the model structure and loading +# the state dictionary into it. + +model = NeuralNetwork() +model.load_state_dict(torch.load("model.pth")) + +############################################################# +# This model can now be used to make predictions. + +classes = [ + "T-shirt/top", + "Trouser", + "Pullover", + "Dress", + "Coat", + "Sandal", + "Shirt", + "Sneaker", + "Bag", + "Ankle boot", +] + +model.eval() +x, y = test_data[0][0], test_data[0][1] +with torch.no_grad(): + pred = model(x) + predicted, actual = classes[pred[0].argmax(0)], classes[y] + print(f'Predicted: "{predicted}", Actual: "{actual}"') + + +###################################################################### +# Read more about `Saving & Loading your model `_. +# \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ffa89b2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +torch==1.13.1 +torchaudio==0.13.1 +torchvision==0.14.1 \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..3bc4830 --- /dev/null +++ b/setup.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +source env.sh +source post-install.sh +#append it to bash so every shell launches with it +echo 'source /home/codespace/venv/bin/activate' >> ~/.bashrc \ No newline at end of file diff --git a/stress/Cargo.toml b/stress/Cargo.toml new file mode 100644 index 0000000..05f9295 --- /dev/null +++ b/stress/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "stress" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = {version="4.0.32", features=["derive"]} +tch = "0.10.1" +rayon = "1.5.1" \ No newline at end of file diff --git a/stress/Makefile b/stress/Makefile new file mode 100644 index 0000000..f73a56a --- /dev/null +++ b/stress/Makefile @@ -0,0 +1,22 @@ +install: + python3 -m pip install --upgrade pip \ + && pip install -r requirements.txt +format: + cargo fmt --quiet + +lint: + cargo clippy --quiet + +test: + cargo test --quiet + +clean: + #cargo install cargo-cache + #cargo cache -a + #rm -rf Cargo.lock + cargo clean + +run: + cargo run + +all: format lint test run \ No newline at end of file diff --git a/stress/src/lib.rs b/stress/src/lib.rs new file mode 100644 index 0000000..8e5f015 --- /dev/null +++ b/stress/src/lib.rs @@ -0,0 +1,31 @@ +/*A stress test that hammers the CPU and GPU using PyTorch +*/ +use rayon::prelude::*; +use tch::Tensor; + +//build a cpu load test function +pub fn cpu_load_test() { + let slice = vec![0; 1_000_000]; + for i in 1..1_000_000 { + let t = Tensor::of_slice(&slice).to_device(tch::Device::Cpu); + println!("{} {:?}", i, t.size()) + } +} + +//build a gpu load test function +pub fn gpu_load_test() { + let slice = vec![0; 1_000_000]; + for i in 1..1_000_000 { + let t = Tensor::of_slice(&slice).to_device(tch::Device::Cuda(0)); + println!("{} {:?}", i, t.size()) + } +} + +//build a gpu load test function that uses threads via rayon iterator that sends the load to the GPU +pub fn gpu_load_test_rayon() { + let slice = vec![0; 1_000_000]; + (1..1_000_000).into_par_iter().for_each(|i| { + let t = Tensor::of_slice(&slice).to_device(tch::Device::Cuda(0)); + println!("{} {:?}", i, t.size()) + }); +} diff --git a/stress/src/main.rs b/stress/src/main.rs new file mode 100644 index 0000000..29b1cfb --- /dev/null +++ b/stress/src/main.rs @@ -0,0 +1,43 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap( + version = "1.0", + author = "Noah Gift", + about = "A Stress Test for PyTorch CPU and GPU. There are three subcommands: cpu, gpu, and tgpu. The tgpu subcommand uses Rayon to send the load to the GPU." +)] +struct Cli { + #[clap(subcommand)] + command: Option, +} + +#[derive(Parser)] +enum Commands { + #[clap(version = "1.0", author = "Noah Gift")] + Cpu {}, + Gpu {}, + Tgpu {}, +} + +//build the main function and import stress:: namespace +fn main() { + let args = Cli::parse(); + match args.command { + Some(Commands::Cpu {}) => { + println!("Running CPU Stress Test"); + stress::cpu_load_test(); + } + Some(Commands::Gpu {}) => { + println!("Running GPU Stress Test"); + stress::gpu_load_test(); + } + Some(Commands::Tgpu {}) => { + println!("Running GPU Stress Test with Rayon"); + stress::gpu_load_test_rayon(); + } + + None => { + println!("Please specify a subcommand"); + } + } +} diff --git a/translate/Cargo.toml b/translate/Cargo.toml new file mode 100644 index 0000000..8a84887 --- /dev/null +++ b/translate/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "translate" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-bert = "0.20.0" +clap = {version="4.0.32", features=["derive"]} +anyhow = "1.0.51" diff --git a/translate/Makefile b/translate/Makefile new file mode 100644 index 0000000..f73a56a --- /dev/null +++ b/translate/Makefile @@ -0,0 +1,22 @@ +install: + python3 -m pip install --upgrade pip \ + && pip install -r requirements.txt +format: + cargo fmt --quiet + +lint: + cargo clippy --quiet + +test: + cargo test --quiet + +clean: + #cargo install cargo-cache + #cargo cache -a + #rm -rf Cargo.lock + cargo clean + +run: + cargo run + +all: format lint test run \ No newline at end of file diff --git a/translate/lyrics.txt b/translate/lyrics.txt new file mode 100644 index 0000000..0b37e02 --- /dev/null +++ b/translate/lyrics.txt @@ -0,0 +1,54 @@ +Uh-uh-uh-uh, uh-uh +Ella despidió a su amor +El partió en un barco en el muelle de San Blas +El juró que volvería +Y empapada en llanto, ella juró que esperaría +Miles de lunas pasaron +Y siempre ella estaba en el muelle, esperando +Muchas tardes se anidaron +Se anidaron en su pelo y en sus labios +Uh-uh-uh-uh, uh-uh +Uh-uh-uh-uh, uh-uh +Llevaba el mismo vestido +Y por si él volviera, no se fuera a equivocar +Los cangrejos le mordían +Su ropaje, su tristeza y su ilusión +Y el tiempo se escurrió +Y sus ojos se le llenaron de amaneceres +Y del mar se enamoró +Y su cuerpo se enraizó en el muelle +, sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con su amor el mar +(Sola), en el muelle de San Blas +Su cabello se blanqueó +Pero ningún barco a su amor le devolvía +Y en el pueblo le decían +Le decían la loca del muelle de San Blas +Y una tarde de abril +La intentaron trasladar al manicomio +Nadie la pudo arrancar +Y del mar nunca jamás la separaron +, sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con su amor el mar +(Sola), en el muelle de San Blas +, sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con el sol y el mar +(Sola), ¡Oh, sola! +Sola en el olvido +(Sola), sola con su espíritu +(Sola), sola con su amor el mar +(Sola), en el muelle de San Blas +Se quedó +Se quedó sola, sola +Se quedó +Se quedó con el sol y con el mar +Se quedó ahí +Se quedó hasta el fin +Se quedó ahí +Se quedó en el muelle de San Blas +Uoh, oh-oh-oh +Sola, sola se quedó +Uoh, oh-oh-oh \ No newline at end of file diff --git a/translate/src/lib.rs b/translate/src/lib.rs new file mode 100644 index 0000000..691e57a --- /dev/null +++ b/translate/src/lib.rs @@ -0,0 +1,38 @@ +/*A library that uses Hugging Face to Translate Text +*/ +use rust_bert::pipelines::translation::{Language, TranslationModelBuilder}; +use std::fs::File; +use std::io::Read; + +//build a function that reads a file and returns a string +pub fn read_file(path: String) -> anyhow::Result { + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Ok(contents) +} + +//build a function that reads a file and returns an array of the lines of the file +pub fn read_file_array(path: String) -> anyhow::Result> { + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let array = contents.lines().map(|s| s.to_string()).collect(); + Ok(array) +} + + +//build a function that reads a file and translates it +pub fn translate_file(path: String) -> anyhow::Result<()> { + let model = TranslationModelBuilder::new() + .with_source_languages(vec![Language::Spanish]) + .with_target_languages(vec![Language::English]) + .create_model()?; + let text = read_file_array(path)?; + //pass in the text to the model + let output = model.translate(&text, None, Language::English)?; + for sentence in output { + println!("{}", sentence); + } + Ok(()) +} diff --git a/translate/src/main.rs b/translate/src/main.rs new file mode 100644 index 0000000..fe5b989 --- /dev/null +++ b/translate/src/main.rs @@ -0,0 +1,41 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap( + version = "1.0", + author = "Noah Gift", + about = "A Hugging Face Translation Tool in Rust" +)] +struct Cli { + #[clap(subcommand)] + command: Option, +} + +#[derive(Parser)] +enum Commands { + #[clap(version = "1.0", author = "Noah Gift")] + Translate { + #[clap(short, long)] + path: String, + }, + Print { + #[clap(short, long)] + path: String, + }, +} +// create main function that uses lib.rs +fn main() -> anyhow::Result<()> { + let args = Cli::parse(); + match args.command { + Some(Commands::Translate { path }) => { + translate::translate_file(path)?; + } + Some(Commands::Print { path }) => { + println!("{}", translate::read_file(path)?); + } + None => { + println!("No command given"); + } + } + Ok(()) +} diff --git a/verify-PyTorch.py b/verify-PyTorch.py new file mode 100644 index 0000000..9986aa1 --- /dev/null +++ b/verify-PyTorch.py @@ -0,0 +1,12 @@ +import torch + +if torch.cuda.is_available(): + print("CUDA is available") + print("CUDA version: {}".format(torch.version.cuda)) + print("PyTorch version: {}".format(torch.__version__)) + print("cuDNN version: {}".format(torch.backends.cudnn.version())) + print("Number of CUDA devices: {}".format(torch.cuda.device_count())) + print("Current CUDA device: {}".format(torch.cuda.current_device())) + print("Device name: {}".format(torch.cuda.get_device_name(torch.cuda.current_device()))) +else: + print("CUDA is not available") \ No newline at end of file