{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": { "vscode": { "languageId": "plaintext" } }, "source": [ "# Linear Algebra" ] }, { "cell_type": "markdown", "id": "1", "metadata": {}, "source": [ "That linear algebra is fun, is a widely accepted fact. This notebooks will guide you through some of the linear algebra fun you can realize with Heat. " ] }, { "cell_type": "code", "execution_count": null, "id": "2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 engines found\n" ] } ], "source": [ "from ipyparallel import Client\n", "rc = Client(profile=\"default\")\n", "rc.ids\n", "\n", "if len(rc.ids) == 0:\n", " print(\"No engines found\")\n", "else:\n", " print(f\"{len(rc.ids)} engines found\")" ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": {}, "outputs": [], "source": [ "%%px\n", "import heat as ht" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "## Matrix-Matrix Multiplication" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "The most basic operation in linear algebra is matrix-matrix multiplication (\"matmul\"). Doing it by hand for a small matrix is not difficult and in fact not very spectacular. However, in the distributed setting (e.g., on 4 GPUs) even such a simple operation is not trivial any more: just imagine you work together with 3 other people and each of you only knows one fourth of the columns of a matrix $A$ and one fourth of the rows of a matrix $B$. Together, you have to compute the product $AB$ such that in the end each of you only has one fourth of the columns of $AB$..." ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [ { "data": { "text/plain": [] }, "metadata": { "after": null, "completed": null, "data": {}, "engine_id": 3, "engine_uuid": "e9fac413-5ad332b3f3d1b43d6c63c9ee", "error": null, "execute_input": "split_A=0 \nsplit_B=1 \nM = 10000\nN = 10000\nK = 10000\nA = ht.random.randn(M, N, split=split_A, device=\"gpu\")\nB = ht.random.randn(N, K, split=split_B, device=\"gpu\")\nC = ht.matmul(A, B)\nC\n", "execute_result": { "data": { "text/plain": "" }, "execution_count": 21, "metadata": {} }, "follow": null, "msg_id": null, "outputs": [], "received": null, "started": null, "status": null, "stderr": "", "stdout": "", "submitted": "2025-11-21T14:40:53.538635Z" }, "output_type": "display_data" }, { "data": { "text/plain": [] }, "metadata": { "after": null, "completed": null, "data": {}, "engine_id": 2, "engine_uuid": "f85a0b27-08b851a725daa8d52a074bfd", "error": null, "execute_input": "split_A=0 \nsplit_B=1 \nM = 10000\nN = 10000\nK = 10000\nA = ht.random.randn(M, N, split=split_A, device=\"gpu\")\nB = ht.random.randn(N, K, split=split_B, device=\"gpu\")\nC = ht.matmul(A, B)\nC\n", "execute_result": { "data": { "text/plain": "" }, "execution_count": 21, "metadata": {} }, "follow": null, "msg_id": null, "outputs": [], "received": null, "started": null, "status": null, "stderr": "", "stdout": "", "submitted": "2025-11-21T14:40:53.538422Z" }, "output_type": "display_data" }, { "data": { "text/plain": [] }, "metadata": { "after": null, "completed": null, "data": {}, "engine_id": 1, "engine_uuid": "a1070193-d3a142f960197ef957f774ee", "error": null, "execute_input": "split_A=0 \nsplit_B=1 \nM = 10000\nN = 10000\nK = 10000\nA = ht.random.randn(M, N, split=split_A, device=\"gpu\")\nB = ht.random.randn(N, K, split=split_B, device=\"gpu\")\nC = ht.matmul(A, B)\nC\n", "execute_result": { "data": { "text/plain": "" }, "execution_count": 21, "metadata": {} }, "follow": null, "msg_id": null, "outputs": [], "received": null, "started": null, "status": null, "stderr": "", "stdout": "", "submitted": "2025-11-21T14:40:53.538350Z" }, "output_type": "display_data" }, { "data": { "text/plain": [ "\u001b[0;31mOut[0:21]: \u001b[0m\n", "DNDarray([[ 7.2963e+01, -1.4339e+02, 1.1302e+02, ..., 2.1846e+01, 1.1797e+02, 2.3660e+01],\n", " [-7.5554e+01, 3.2531e+01, 1.3144e+01, ..., -1.0171e+02, -2.5141e+02, 1.4358e+02],\n", " [ 3.4069e+01, -2.3046e+01, 1.9501e+02, ..., 8.7619e+01, -7.5115e+01, -1.7974e+02],\n", " ...,\n", " [-8.0653e+00, -7.5908e+01, 6.8068e+01, ..., -5.8282e+01, -2.5834e+01, -4.9030e+01],\n", " [ 5.2255e+01, 2.8832e+01, -7.5600e+01, ..., -2.5936e+01, -3.2145e-01, -4.5561e+01],\n", " [ 9.0530e+01, 1.3209e-01, 7.0801e+01, ..., 1.2266e+02, 1.5415e+01, -1.0515e+02]], dtype=ht.float32, device=gpu:0, split=0)" ] }, "metadata": { "after": null, "completed": null, "data": {}, "engine_id": 0, "engine_uuid": "2f5ee137-24f77a09dc6e8333cba67c27", "error": null, "execute_input": "split_A=0 \nsplit_B=1 \nM = 10000\nN = 10000\nK = 10000\nA = ht.random.randn(M, N, split=split_A, device=\"gpu\")\nB = ht.random.randn(N, K, split=split_B, device=\"gpu\")\nC = ht.matmul(A, B)\nC\n", "execute_result": { "data": { "text/plain": "DNDarray([[ 7.2963e+01, -1.4339e+02, 1.1302e+02, ..., 2.1846e+01, 1.1797e+02, 2.3660e+01],\n [-7.5554e+01, 3.2531e+01, 1.3144e+01, ..., -1.0171e+02, -2.5141e+02, 1.4358e+02],\n [ 3.4069e+01, -2.3046e+01, 1.9501e+02, ..., 8.7619e+01, -7.5115e+01, -1.7974e+02],\n ...,\n [-8.0653e+00, -7.5908e+01, 6.8068e+01, ..., -5.8282e+01, -2.5834e+01, -4.9030e+01],\n [ 5.2255e+01, 2.8832e+01, -7.5600e+01, ..., -2.5936e+01, -3.2145e-01, -4.5561e+01],\n [ 9.0530e+01, 1.3209e-01, 7.0801e+01, ..., 1.2266e+02, 1.5415e+01, -1.0515e+02]], dtype=ht.float32, device=gpu:0, split=0)" }, "execution_count": 21, "metadata": {} }, "follow": null, "msg_id": null, "outputs": [], "received": null, "started": null, "status": null, "stderr": "", "stdout": "", "submitted": "2025-11-21T14:40:53.537927Z" }, "output_type": "display_data" } ], "source": [ "%%px\n", "split_A=0 \n", "split_B=1 \n", "M = 10000\n", "N = 10000\n", "K = 10000\n", "A = ht.random.randn(M, N, split=split_A, device=\"gpu\")\n", "B = ht.random.randn(N, K, split=split_B, device=\"gpu\")\n", "C = ht.matmul(A, B)\n", "C" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "## QR Decomposition and Triangular Solve\n", "\n", "Given a matrix $A$, its QR decomposition is given by $A=QR$ where $Q$ is an orthogonal matrix (i.e. its columns are pairwise orthonormal) and $R$ is an upper triangular matrix. \n", "\n", "Further information: [QR on Wikipedia](https://en.wikipedia.org/wiki/QR_decomposition)" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[stdout:3] (100000, 1000) (1000, 1000)\n", "\n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[stdout:2] (100000, 1000) (1000, 1000)\n", "\n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[stdout:1] (100000, 1000) (1000, 1000)\n", "\n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[stdout:0] (100000, 1000) (1000, 1000)\n", "DNDarray([[ 1.0000e+00, -2.7940e-09, 2.3283e-09, ..., 2.6776e-09, 5.8208e-09, 7.1595e-09],\n", " [-2.7940e-09, 1.0000e+00, 4.6566e-10, ..., -1.3970e-09, -8.1491e-10, 6.7521e-09],\n", " [ 2.3283e-09, 4.6566e-10, 1.0000e+00, ..., -1.0477e-09, 5.8208e-10, 2.9104e-09],\n", " ...,\n", " [ 2.6776e-09, -1.3970e-09, -1.0477e-09, ..., 1.0000e+00, 9.3132e-10, -1.3970e-09],\n", " [ 5.8208e-09, -8.1491e-10, 5.8208e-10, ..., 9.3132e-10, 1.0000e+00, -8.7311e-10],\n", " [ 7.1595e-09, 6.7521e-09, 2.9104e-09, ..., -1.3970e-09, -8.7311e-10, 1.0000e+00]], dtype=ht.float32, device=gpu:0, split=1)\n", "DNDarray([[ 3.1642e+02, 3.9908e-01, -1.7685e+00, ..., 4.7194e-01, 1.5867e+00, -1.0417e+00],\n", " [ 0.0000e+00, -3.1472e+02, -1.0726e-01, ..., 3.7303e-01, 1.2852e+00, -2.5343e-01],\n", " [ 0.0000e+00, 0.0000e+00, -3.1664e+02, ..., -9.1426e-01, 7.1782e-01, -1.1222e+00],\n", " ...,\n", " [ 0.0000e+00, 0.0000e+00, 0.0000e+00, ..., 3.1383e+02, -9.6070e-01, 3.5290e-01],\n", " [ 0.0000e+00, 0.0000e+00, 0.0000e+00, ..., 0.0000e+00, 3.1372e+02, 8.9383e-01],\n", " [ 0.0000e+00, 0.0000e+00, 0.0000e+00, ..., 0.0000e+00, 0.0000e+00, 3.1452e+02]], dtype=ht.float32, device=gpu:0, split=None)\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%px\n", "A = ht.random.randn(100000, 1000, split=0, device=\"gpu\")\n", "Q,R = ht.linalg.qr(A)\n", "print(Q.shape, R.shape)\n", "print(Q.T @ Q) \n", "print(R)" ] }, { "cell_type": "markdown", "id": "10", "metadata": {}, "source": [ "With a little bit of linear algebra fun, you find out that a linear least squares problem of type $\\min \\lVert Ax - b \\rVert_2$ boils down to computing the QR decomposition $A=QR$ and then solving for $Rx = Q^T b$. (Of course, we need to assume that if $A \\in \\mathbb{R}^{m \\times n}$ that $m \\geq n$ and $R$ is invertible...)" ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[stdout:3] \n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[stdout:0] DNDarray(315.8452, dtype=ht.float32, device=gpu:0, split=None)\n", "DNDarray(0.0107, dtype=ht.float32, device=gpu:0, split=None)\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[stdout:1] \n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[stdout:2] \n", "\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%px\n", "b = ht.random.randn(100000,1,split=None, device=\"gpu\")\n", "Qtb = Q.T @ b\n", "x = ht.linalg.solve_triangular(R,Qtb.resplit_(None)) \n", "print(ht.norm(A @ x - b))\n", "print(ht.norm(A.T @ A @ x - A.T @ b))" ] }, { "cell_type": "markdown", "id": "12", "metadata": {}, "source": [ "If you want to solve a LASSO-regularized version of this linear regression problem, try out `heat.regression.Lasso`!" ] }, { "cell_type": "markdown", "id": "13", "metadata": {}, "source": [ "## Singular Value Decomposition\n", "\n", "Let $X \\in \\mathbb{R}^{m \\times n}$ be a matrix, e.g., given by a data set consisting of $m$ data points $\\in \\mathbb{R}^n$ stacked together. The so-called **singular value decomposition (SVD)** of $X$ is given by \n", "\n", "$$\n", "\tX = U \\Sigma V^T\n", "$$\n", "\n", "where $U \\in \\mathbb{R}^{m \\times r_X}$ and $V \\in \\mathbb{R}^{n \\times r_X}$ have orthonormal columns, $\\Sigma = \\text{diag}(\\sigma_1,...,\\sigma_{r_X}) \\in \\mathbb{R}^{r_X \\times r_X}$ is a diagonal matrix containing the so-called singular values $\\sigma_1 \\geq \\sigma_2 \\geq ... \\geq \\sigma_{r_X} > 0$, and $r_X \\leq \\min(m,n)$ denotes the rank of $X$ (i.e. the dimension of the subspace of $\\mathbb{R}^m$ spanned by the columns of $X$). Since $\\Sigma = U^T X V$ is diagonal, one can imagine this decomposition as finding orthogonal coordinate transformations under which $X$ looks \"linear\". Further information: [SVD on Wikipedia](https://en.wikipedia.org/wiki/Singular_value_decomposition)\n", "\n", "Computing the **full** SVD in a distributed environment can be quite **expensive**; nevertheless, we have implemented it: " ] }, { "cell_type": "code", "execution_count": null, "id": "14", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a0ddc4f8b5494d4b8c33a1283c447bd5", "version_major": 2, "version_minor": 0 }, "text/plain": [ "%px: 0%| | 0/4 [00:00\n", " \n", " \n", " \n", "" ] }, { "cell_type": "markdown", "id": "24", "metadata": {}, "source": [ "## Exercises \n", "\n", "1. Try out different split combinations `split_A=0,1,None`, `split_B=0,1,None` for matrix-matrix multiplication. What do you observe for the split of the outcome? Does every combination take the same computing time? (Whats the likely cause for this?)\n", "\n", "2. Compare the approximate singular values computed by randomized SVD / hierarchiacl SVD with the reference ones computed by full SVD. What do you observe for different choices of the parameters (number of oversamples, power iterations, truncation rank, truncation tolerance)? " ] }, { "cell_type": "markdown", "id": "25", "metadata": {}, "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 5 }