<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://vatai.github.io/feed.xml" rel="self" type="application/atom+xml"/><link href="https://vatai.github.io/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-05-19T06:40:28+00:00</updated><id>https://vatai.github.io/feed.xml</id><title type="html">blank</title><subtitle>Homepage of Emil VATAI, HPC/ML research scientist@Riken R-CCS </subtitle><entry><title type="html">My bag of LaTeX tips and tricks</title><link href="https://vatai.github.io/blog/2026/my-bag-of-latex-tips-and-tricks/" rel="alternate" type="text/html" title="My bag of LaTeX tips and tricks"/><published>2026-05-19T00:00:00+00:00</published><updated>2026-05-19T00:00:00+00:00</updated><id>https://vatai.github.io/blog/2026/my-bag-of-latex-tips-and-tricks</id><content type="html" xml:base="https://vatai.github.io/blog/2026/my-bag-of-latex-tips-and-tricks/"><![CDATA[<h1 id="this-post-will-be-updated">This post will be updated</h1> <p>I plan to keep this post as a constantly updating webpage of tips and tricks I’ve encountered while writing papers.</p> <h2 id="subfigures-for-ieee-template">Subfigures for IEEE template</h2> <p>Use the Subcaption</p> <div class="language-latex highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">\usepackage</span><span class="p">{</span>subcaption
</code></pre></div></div> <p>with these settings:</p> <div class="language-latex highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">%%IEEE%% The following block of 4 lines makes subcaption behave the way IEEEtran expects it to behave.</span>
<span class="k">\DeclareCaptionLabelSeparator</span><span class="p">{</span>periodspace<span class="p">}{</span>.<span class="k">\quad</span><span class="p">}</span> <span class="c">%%IEEE%%</span>
<span class="k">\captionsetup</span><span class="p">{</span>font=footnotesize,labelsep=periodspace,singlelinecheck=false<span class="p">}</span> <span class="c">%%IEEE%%</span>
<span class="k">\captionsetup</span><span class="na">[sub]</span><span class="p">{</span>font=footnotesize,singlelinecheck=true<span class="p">}</span> <span class="c">%%IEEE%%</span>
<span class="k">\renewcommand\thesubfigure</span><span class="p">{</span><span class="k">\alph</span><span class="p">{</span>subfigure<span class="p">}}</span> <span class="c">%%IEEE%%</span>
</code></pre></div></div> <p>Officially, <code class="language-plaintext highlighter-rouge">subcaption</code> is not supported by IEEE, but these settings make it appear as it should.</p>]]></content><author><name></name></author><category term="science"/><category term="latex"/><summary type="html"><![CDATA[This post will be updated]]></summary></entry><entry><title type="html">CIBuildWheel is DA BOMB!</title><link href="https://vatai.github.io/blog/2025/cibuildwheel-is-da-bomb/" rel="alternate" type="text/html" title="CIBuildWheel is DA BOMB!"/><published>2025-11-18T23:30:00+00:00</published><updated>2025-11-18T23:30:00+00:00</updated><id>https://vatai.github.io/blog/2025/cibuildwheel-is-da-bomb</id><content type="html" xml:base="https://vatai.github.io/blog/2025/cibuildwheel-is-da-bomb/"><![CDATA[<p>After the last submission of our <a href="/projects/tadashi">Tadashi</a> paper <a class="citation" href="#vatai2025tadashi">(Vatai et al., 2025)</a>, one of the reviewers complained about installing Tadashi is similarly cumbersome as installing PET or ISL (which I also struggled with in the beginning and can relate to). So, to solve this problem, yesterday I spent the whole day hacking and trying to figure out <a href="https://cibuildwheel.pypa.io/en/stable/">CIBuildWheel</a> and I’m really happy with what I found! <strong>TL;DR: With CIBuildWheel we should be able to upload Tadashi to PyPI and have <code class="language-plaintext highlighter-rouge">pip install tadashi</code> fully working.</strong></p> <p>In the current setup, we can use <code class="language-plaintext highlighter-rouge">pip install git+https://github.com/vatai/tadashi.git</code>, which, in theory would take care of everything, however, in practice, since it is building PET and ISL in the background, this can go wrong if some dependencies are not installed. CIBuildWheel<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> solves this problem by building the extensions<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> in isolation and packaging them into a wheel<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.</p> <p>Obviously, <strong>the problem with binary packages is portability</strong>! And I’m not just talking about Linux vs Windows, but also withing Linux, a binary built on one Linux may not work on another Linux. To solve this, CIBuildWheel uses something called <a href="https://github.com/pypa/manylinux">manylinux</a>. As I understand, to the user (package maintainer), manylinux a set of Docker images, with somewhat standard (read older) glibc and other libraries, so that if you compile against those libraries, you cover a lot of systems. Also there are multiple manylinux Docker images, covering different versions of different libraries. CIBuildWheel builds wheels in these Docker containers and by default generates <a href="https://github.com/vatai/complicated-project-with-extension/actions/runs/19494788268">these</a> Linux wheels (look at the filenames, the <code class="language-plaintext highlighter-rouge">cpXYY</code> indicates the Python version X-YY, and the rest of the name includes which manylinux was used – it’s a deep rabbit hole and I’ll stop here with the explanation). In theory it could generate wheels for Windows and Mac too :shrug:.</p> <h1 id="prototyping">Prototyping</h1> <p>Before going “all in” to apply this to Tadashi, I made a PoC repo (called <a href="https://github.com/vatai/complicated-project-with-extension">Complicated project with extensions</a>, or <code class="language-plaintext highlighter-rouge">cpwe</code> for short) to test the critical parts (i.e., parts I didn’t quite understand) of this setup. This also included Cython integration, the new approach I plan to implement to interface the PET/ISL libraries with Python (instead of SWIG, which I currently use). Cython is not just a more “pythonic” approach, it also simplifies many things both in Tadashi’s code and in the build process.</p> <h2 id="the-goals">The goals</h2> <p>I originally intended to cover (a simplified version of) everything including uploading binary packages to PyPI, and test <code class="language-plaintext highlighter-rouge">pip install cpwe</code>, but then I realised I can do things locally: When I saw all the wheels (<code class="language-plaintext highlighter-rouge">.whl</code> files) created by CIBuildWheel, I realised PyPI most likely just stores these <code class="language-plaintext highlighter-rouge">.whl</code> files and <code class="language-plaintext highlighter-rouge">pip install cpwe</code> probably just selects the appropriate <code class="language-plaintext highlighter-rouge">.whl</code> file, downloads it, and installs it, which can also simulated locally with <code class="language-plaintext highlighter-rouge">pip install the_appropriate_wheel.whl</code>!</p> <p>One goal was to <strong>set up GitHub actions to generate all the wheels automatically</strong>. This was easy to achieve following the <a href="https://cibuildwheel.pypa.io/en/stable/ci-services/">CIBuildWheel docs</a> and also easy to verify, bacause the example in the docs uploaded all the wheels as <a href="https://github.com/vatai/complicated-project-with-extension/actions/runs/19494788268">artifacts</a>. These wheels would then be uploaded via GH actions (using twine) to PyPI, something I already implemented in <a href="/projects/radicalpy">another project</a>.</p> <p>The other, more important, goal was <strong>making a wheel which includes binaries from a separate C library</strong>, i.e. has Python code which calls functions from a C library (i.e., ISL). I should elaborate.</p> <h2 id="getting-the-right-wheels">Getting the right wheels</h2> <p>To summarise, we want to</p> <ul> <li>build a Python extension,</li> <li>using Cython,</li> <li>which would be calling a third party C library (namely ISL).</li> </ul> <h3 id="extensions-the-simple-case">Extensions: The simple case</h3> <p>Most of the docs describe Cython extensions (or SWIG extensions for that matter) as standalone code, that are converted into C/C++ code and then built into an <code class="language-plaintext highlighter-rouge">.so</code> file. It looks something like this:</p> <pre><code class="language-mermaid">flowchart LR
    Cython["Cython foo.py or foo.pyx files"]
    C["Generated foo.c or foo.cpp files"]
    SO["Compiled foo.so binary file"]
    Cython --&gt; C --&gt; SO
    style C fill:#ff0,color:#000
    style SO fill:#f9f,color:#000
</code></pre> <p>And that final <code class="language-plaintext highlighter-rouge">foo.so</code> file has all the code you need! So the dependencies are simple:</p> <pre><code class="language-mermaid">---
title: "Cython extensions: the simple case"
---
classDiagram
    direction LR
    PythonPackage &lt;|-- Extension
    class PythonPackage{
      +cpwe.py Python files
      +build_from_pyproject_toml()
    }
    class Extension{
      +foo.pyx Cython files
      +cythonize_in_setup_py()
    }
</code></pre> <h3 id="extensions-with-dependencies">Extensions with dependencies</h3> <p>However our situation is more complicated: Our <code class="language-plaintext highlighter-rouge">foo.so</code> has an extra dependency (ISL), which itself pulls in other dependencies (namely GMP) which are not necessary installed on the end-user’s system.</p> <pre><code class="language-mermaid">---
title: "Cython extensions with a dependencies"
---
classDiagram
    direction LR
    PythonPackage &lt;|-- Extension
    Extension &lt;|-- Dependency
    Dependency &lt;|-- Lv2Dependency
    class PythonPackage{
      +cpwe.py Python files
      +build_from_pyproject_toml()
    }
    class Extension{
      +foo.pyx Cython files
      +cythonize_in_setup_py()
    }
    class Dependency{
      +isl.c files
      +build_with_make()
    }
    class Lv2Dependency{
      +GMP system package
      +apt_install()
    }
</code></pre> <p>And this is why CIBuildWheel is <em>DA BOOOOMB</em>! It takes care of all this!</p> <p>These are the relevant parts of the <code class="language-plaintext highlighter-rouge">pyproject.toml</code> file that configures the whole project:</p> <figure class="highlight"><pre><code class="language-toml" data-lang="toml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="code"><pre><span class="k">[</span><span class="n">build-system</span><span class="k">]</span>
<span class="n">requires</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"setuptools"</span><span class="p">,</span> <span class="s">"cython"</span><span class="p">,</span> <span class="s">"cibuildwheel"</span><span class="p">]</span>
<span class="n">build-backend</span> <span class="o">=</span><span class="w"> </span><span class="s">"setuptools.build_meta"</span>

<span class="k">[</span><span class="n">tool</span><span class="k">.</span><span class="n">cibuildwheel</span><span class="k">]</span>
<span class="n">build-frontend</span><span class="o">=</span><span class="s">"build[uv]"</span>
<span class="n">before-all</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"./third_party/install.sh"</span><span class="p">]</span>
<span class="c"># build = "cp313-manylinux_x86_64"</span>
<span class="c"># manylinux-x86_64-image = "manylinux-mod"</span>
</pre></td></tr></tbody></table></code></pre></figure> <p>In line 2 we list <code class="language-plaintext highlighter-rouge">cibuildwheel</code> as a build dependency (it can just be <code class="language-plaintext highlighter-rouge">pip install</code>ed as any other Python package).</p> <p>The block from line 5 covers the <code class="language-plaintext highlighter-rouge">cibuildwheel</code> specific settings. Setting the frontend to use <code class="language-plaintext highlighter-rouge">uv</code> potentially speeds up the build process. And finally <strong>the <code class="language-plaintext highlighter-rouge">before-all</code> allows us to call a script which builds our dependency, i.e. ISL</strong>.</p> <p>The commented out lines were <strong>hacks to speed up debugging</strong>.</p> <ul> <li>The <code class="language-plaintext highlighter-rouge">build</code> field specifies which wheels should be build (uncommenting line 8 would produce only the wheel built agains Python 3.13 and <code class="language-plaintext highlighter-rouge">manylinux_x86_64</code>).</li> <li>The <code class="language-plaintext highlighter-rouge">manylinux-x86_64-image</code> field specifies the Docker image to be used for <code class="language-plaintext highlighter-rouge">manylinux-x86_64</code>. I created a custom Docker image with ISL prebuilt which sped up testing by skipping both downloading the GMP system packages from the Alma Linux repo, and building ISL from source. For this I obviously disabled the <code class="language-plaintext highlighter-rouge">install.sh</code> script in <code class="language-plaintext highlighter-rouge">before-all</code>.</li> </ul> <h2 id="verification">Verification</h2> <p>To verify that everything works as Intended I wrote a small <a href="https://github.com/vatai/complicated-project-with-extension/blob/main/tests/test_local_uv_project_from_scratch.sh">test script</a>. The main steps of the script were the following:</p> <ol> <li>Call <code class="language-plaintext highlighter-rouge">uvx cibuildwheel</code>, which simulates what happens in the GH action and builds the <code class="language-plaintext highlighter-rouge">.whl</code> files locally.</li> <li>Create a temporary Python project, with <code class="language-plaintext highlighter-rouge">uv</code>.</li> <li>Installing the <code class="language-plaintext highlighter-rouge">cpwe</code> package, i.e., newly generated<code class="language-plaintext highlighter-rouge">.whl</code> file in the project’s <code class="language-plaintext highlighter-rouge">venv</code>.</li> <li>Running code which invokes <code class="language-plaintext highlighter-rouge">cpwe</code> so it calls ISL functions.</li> </ol> <p><code class="language-plaintext highlighter-rouge">uv</code> is used out of convenience to automate the process of creating a <code class="language-plaintext highlighter-rouge">venv</code>, activating the <code class="language-plaintext highlighter-rouge">venv</code>, installing a wheel to the <code class="language-plaintext highlighter-rouge">venv</code>, running a python script in the <code class="language-plaintext highlighter-rouge">venv</code> and disabling the <code class="language-plaintext highlighter-rouge">venv</code>. Both the directories where wheels are created (step 1) and the directory where the <code class="language-plaintext highlighter-rouge">uv</code> project is created (step 2) are delete and regenerated from scratch every time the script executes.</p> <h2 id="additional-verification">Additional verification</h2> <p>I used the process described above as a quick indicator whether things are working or not. To simulate installing and running the code in the package on a random computer, i.e., to double check that CIBuildWheel actually builds a wheel that has all the code required to be executed on any system (without any dependency outside of the Python ecosystem), I spun up an clean Ubuntu container, I copied the appropriate <code class="language-plaintext highlighter-rouge">.whl</code> file to it, installed <code class="language-plaintext highlighter-rouge">uv</code> and some other Python packages, and executed a similar script as above. This confirmed that it worked on any system, even if it didn’t have GMP or ISL installed.</p> <p>I looked inside the <code class="language-plaintext highlighter-rouge">venv</code> after installing the wheel to see how this is achieved. Inside the <code class="language-plaintext highlighter-rouge">site-packages</code> I found the following files:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── cpwe
│   ├── foo.c # C code (generated by Cython)
│   ├── foo.cpython-313-x86_64-linux-gnu.so #!!! .so file (built from the C code)
│   ├── foo.py # source containing Cython code (written by me)
│   └── isl.pxd # Cython "header" file to declaring the ISL functions (written by me)
...
├── cpwe.libs
│   ├── libgmp-d944b113.so.10.3.2  manylinux container #!!! .so copied from the system package within the
│   └── libisl-8ba7b133.so.23.4.0 #!!! .so file built in the container


</code></pre></div></div> <p>CIBuildWheel added all the relevant <code class="language-plaintext highlighter-rouge">.so</code> files: the <code class="language-plaintext highlighter-rouge">foo.cython*.so</code> generated from my Cython code in <code class="language-plaintext highlighter-rouge">foo.py</code> (which would be the only file if this were the simple case) and the <code class="language-plaintext highlighter-rouge">libisl*.so*</code> which was Cython extension’s dependency, and <code class="language-plaintext highlighter-rouge">libgmp*.so*</code> which was <code class="language-plaintext highlighter-rouge">libisl*.so*</code>’s dependency. :tada::tada::tada:</p> <h2 id="future-work">Future work</h2> <p>For now, this really was a minimal working example for building portable wheel packages, and only included ISL as a dependency. I need to see if this works also for PET which includes LLVM as a dependency – which is <em>slightly</em> larger than GMP. :cold_sweat:</p> <p>In a separate attempt (which I didn’t publish in a repo or blog post), I managed to write a Cython function which was used as the callback parameter in the C function which is PET’s entry point. This callback mechanism was one of the main reasons I wrote most of Tadashi in C, I imagined it would be hard to interface Python code with ISL’s callback setup. Figuring out how to create callbacks in Cython I can more freely mix Python and C code, which would enable a sane way to organise code such that it includes both the current PET and future (Fortran enabled) LLVM/Polly backend.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1"> <p>CI refers to <strong>Continuous Integration</strong> because CIBuildWheel is primarily used in CI, i.e., to automatically build wheels on, e.g., like GitHub using GitHub actions. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:2"> <p><strong>Extensions</strong> are compiled, binary <code class="language-plaintext highlighter-rouge">.so</code> files which can be <code class="language-plaintext highlighter-rouge">import</code>ed into python scripts. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:3"> <p>A <strong>wheel</strong> Python package is a binary distribution (aka <strong>bdist</strong>) package, also containing binary files (such as .pyc/.so/.dll/.dylib files), in contrast to source distribution (aka <strong>sdist</strong>) packages, which contain only source files. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div>]]></content><author><name></name></author><category term="programming"/><category term="tadashi"/><summary type="html"><![CDATA[A day of figuring out how to package Tadashi]]></summary></entry><entry><title type="html">a post with plotly.js</title><link href="https://vatai.github.io/blog/2025/plotly/" rel="alternate" type="text/html" title="a post with plotly.js"/><published>2025-03-26T14:24:00+00:00</published><updated>2025-03-26T14:24:00+00:00</updated><id>https://vatai.github.io/blog/2025/plotly</id><content type="html" xml:base="https://vatai.github.io/blog/2025/plotly/"><![CDATA[<p>This is an example post with some <a href="https://plotly.com/javascript/">plotly</a> code.</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">plotly
</span><span class="sb">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "type": "scatter"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [16, 5, 11, 9],
      "type": "scatter"
    }
  ]
}</span>
<span class="p">```</span>
</code></pre></div></div> <p>Which generates:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "type": "scatter"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [16, 5, 11, 9],
      "type": "scatter"
    }
  ]
}
</code></pre> <p>Also another example chart.</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">plotly
</span><span class="sb">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "mode": "markers"
    },
    {
      "x": [2, 3, 4, 5],
      "y": [16, 5, 11, 9],
      "mode": "lines"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [12, 9, 15, 12],
      "mode": "lines+markers"
    }
  ],
  "layout": {
    "title": {
      "text": "Line and Scatter Plot"
    }
  }
}</span>
<span class="p">```</span>
</code></pre></div></div> <p>This is how it looks like:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "mode": "markers"
    },
    {
      "x": [2, 3, 4, 5],
      "y": [16, 5, 11, 9],
      "mode": "lines"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [12, 9, 15, 12],
      "mode": "lines+markers"
    }
  ],
  "layout": {
    "title": {
      "text": "Line and Scatter Plot"
    }
  }
}
</code></pre>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="charts"/><summary type="html"><![CDATA[this is what included plotly.js code could look like]]></summary></entry><entry><title type="html">MPI4py under Slurm</title><link href="https://vatai.github.io/blog/2025/mpi4py-with-slurm/" rel="alternate" type="text/html" title="MPI4py under Slurm"/><published>2025-02-11T00:00:00+00:00</published><updated>2025-02-11T00:00:00+00:00</updated><id>https://vatai.github.io/blog/2025/mpi4py-with-slurm</id><content type="html" xml:base="https://vatai.github.io/blog/2025/mpi4py-with-slurm/"><![CDATA[<p>For <a href="/projects/tadashi">TADASHI</a> we are building a “benchmarking harness”, which would have a main instance running on one node of a cluster, and distribute to other nodes the code transformation (potentially), the compilation and the measurement of transformed apps.</p> <h1 id="benchmarking-harness-specifications">Benchmarking harness specifications</h1> <p>The key functionalities required by the harness are:</p> <ul> <li>it should have a python interface and</li> <li>it should distribute the benchmarking across nodes of a cluster/supercomputer.</li> </ul> <h1 id="candidate-solutions">Candidate solutions</h1> <p><a href="https://docs.celeryq.dev/en/stable/">Celery</a> and <a href="https://jolt.readthedocs.io/en/latest/">Jolt</a> came up as possible solutions, however we ended up trying only <a href="https://www.ray.io/">Ray</a> and <a href="https://mpi4py.readthedocs.io/en/stable/">MPI4py</a>.</p> <p>Both Ray and MPI4py had some sort of implementations for <a href="https://docs.python.org/3/library/concurrent.futures.html">Python futures</a>, and this looked like a good way to implement the benchmarking harness. I opted for MPI4py since it is a better fit for the MPI-based HPC clusters we have access to.</p> <p>The plan™ was to implement everything using Pythons <code class="language-plaintext highlighter-rouge">concurrent.futures</code> on my trusty little laptop, and then just swap out <code class="language-plaintext highlighter-rouge">concurrent.futures</code> with <code class="language-plaintext highlighter-rouge">mpi4py.futures</code>. But as it is often the case, life wasn’t so simple.</p> <h1 id="the-cluster-environment">The cluster environment</h1> <p>Also, as it is often the case, the required software is often not available on the cluster. So, the first round of crocodile wrestling was compiling a bunch of libraries and getting them all to work together. That was a pain in the neck, but doable.</p> <h1 id="the-wrench-in-the-gears-figuring-out-how-to-invoke-mpi4py-when-we-want-to-use-futures">The wrench in the gears: Figuring out (how to invoke) MPI4py when we want to use futures</h1> <p>After figuring Tadashi’s dependencies, the time came to “just swap out”™ <code class="language-plaintext highlighter-rouge">futures</code> (and <code class="language-plaintext highlighter-rouge">Executor</code>) from Python’s <code class="language-plaintext highlighter-rouge">concurrent</code> with MPI4py’s implementation.</p> <h2 id="the-right-mpi-which-supports-mpi_comm_spawn">The right MPI, which supports <code class="language-plaintext highlighter-rouge">MPI_Comm_spawn</code></h2> <p>Something which came up earlier in the development of the benchmarking harness was <a href="https://en.wikipedia.org/wiki/Fugaku_(supercomputer)">Fugaku</a>’s support for master-worker jobs/workloads, which uses <a href="https://www.mpi-forum.org/docs/mpi-4.1/mpi41-report/node289.htm#Node289"><code class="language-plaintext highlighter-rouge">MPI_Comm_spawn</code></a> to dynamically spawn processes, and incidentally, MPI4py <code class="language-plaintext highlighter-rouge">futures</code>, more precisely the <a href="https://mpi4py.readthedocs.io/en/stable/mpi4py.futures.html#mpipoolexecutor"><code class="language-plaintext highlighter-rouge">MPIPoolExecutor</code></a>, also uses <code class="language-plaintext highlighter-rouge">MPI_Comm_spawn</code> under the hood. So it was a bit disappointing when I realised <a href="">OpenMPI doesn’t support <code class="language-plaintext highlighter-rouge">MPI_Comm_spawn</code></a>. However, MPICH, which does support <code class="language-plaintext highlighter-rouge">MPI_Comm_spawn</code>, was also available on the cluster and I just needed to recompile MPI4py with MPICH loaded to use it.</p> <h2 id="testing-went-well">Testing went well</h2> <p>Initially, it was a bit hard to wrap my head around how <code class="language-plaintext highlighter-rouge">MPI_Comm_spawn</code> works, in my head <code class="language-plaintext highlighter-rouge">MPI_COMM_WORLD</code> is everything MPI is/can be aware of, but it turns out, if you have an allocation larger then <code class="language-plaintext highlighter-rouge">MPI_COMM_WORLD</code>, MPI still knows about it. This means, if you have an allocation of 10 nodes, you don’t lunch your master/parent program with <code class="language-plaintext highlighter-rouge">mpirun -n 10</code> but with <code class="language-plaintext highlighter-rouge">mpirun -n 1</code> and it will spawn processes on the remaining 9 nodes. So I logged in the cluster, got an interactive node, copy-pasted some example code for MPI4py spawn, and tested it – everything looked fine.</p> <h2 id="unwanted-behaviour--back-to-the-basics">Unwanted behaviour &amp; back to the basics</h2> <p>However, when I swapped <code class="language-plaintext highlighter-rouge">concurrent.futures</code> with <code class="language-plaintext highlighter-rouge">mpi4py.futures</code>, and wrote a submission script (to be launched by <code class="language-plaintext highlighter-rouge">sbatch</code>), things didn’t quite work. First, I realised the state of Tadashi which is in the binary <code class="language-plaintext highlighter-rouge">.so</code> files did not get pickled and transferred to the workers. After, temporarily disabling <code class="language-plaintext highlighter-rouge">.so</code> dependent code, I tried to rerun things, which did not fail!</p> <p>However, when, for some reason I remembered to check if the workers are actually being executed on different nodes, it turned out this is not the case: when checking <code class="language-plaintext highlighter-rouge">gethostname</code> both master and workers were executed on the same node (and the other allocated nodes remained idle).</p> <h2 id="3-ways-to-run-thing-in-slurm-and-finding-what-threw-the-wrench-in-the-gears">3 ways to run thing in Slurm, and finding what threw the wrench in the gears</h2> <p>Ultimately, the proverbial wrench in the gears, (aka bug, aka WTF) came down to the different ways you can launch programs with Slurm: using <code class="language-plaintext highlighter-rouge">srun</code>, <code class="language-plaintext highlighter-rouge">salloc</code> and <code class="language-plaintext highlighter-rouge">sbatch</code>.</p> <p><code class="language-plaintext highlighter-rouge">srun</code> allocates you resources from a cluster, and runs your binary (I like to think about <code class="language-plaintext highlighter-rouge">srun</code> as <code class="language-plaintext highlighter-rouge">mpirun</code>, but it is “aware” of the resources). <code class="language-plaintext highlighter-rouge">salloc</code> doesn’t run the program, it just allocates resources, and if a command is provided it executes that command only once, i.e. not on all nodes. To utilise all nodes within an allocation obtained by <code class="language-plaintext highlighter-rouge">salloc</code>, one would call <code class="language-plaintext highlighter-rouge">srun</code>. Finally, <code class="language-plaintext highlighter-rouge">sbatch</code> is like <code class="language-plaintext highlighter-rouge">srun</code> but instead of getting the allocation and running it immediately (dumping stdout to the terminal), <code class="language-plaintext highlighter-rouge">sbatch</code> puts the job/command in the queue, and saves the output into a <code class="language-plaintext highlighter-rouge">slurm-*.out</code> file.</p> <h2 id="getting-it-right">Getting it right</h2> <p>To get to the bottom of things, I ended up writing a (pair of) simple MPI programs, <code class="language-plaintext highlighter-rouge">spawn_main.c</code> and <code class="language-plaintext highlighter-rouge">spawn_child.c</code>, each reporting the hostname. And again I made the mistake of running things from an interactive node, which I obtained using <code class="language-plaintext highlighter-rouge">srun -N 3 -p genoa --pty bash</code>. From thin interactive instance, running <code class="language-plaintext highlighter-rouge">mpirun -N 1 ./spawn_main</code> gave the desired results: the main and child processes were all reporting different hostnames.</p> <p>The moment of clarity came when I wanted to present the full example, and wrote a <code class="language-plaintext highlighter-rouge">spawn_submit.sh</code> submission script, which I launched with <code class="language-plaintext highlighter-rouge">sbatch</code>. Lo and behold, I was back to the undesired behaviour of both main and child instances reporting the same hostname! This meant, that calling <code class="language-plaintext highlighter-rouge">mpirun -N 1 ./spawn_main</code> didn’t do the same thing when called from a submission script and when called from an interactive session.</p> <p>I tried emulating the interactive session by running <code class="language-plaintext highlighter-rouge">mpirun</code> inside <code class="language-plaintext highlighter-rouge">bash</code> inside <code class="language-plaintext highlighter-rouge">srun</code>, i.e.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>srun bash -c 'mpirun -N 1 ./spawn_main`
</code></pre></div></div> <p>The results was a new kinda of undesired behaviour. Now <code class="language-plaintext highlighter-rouge">mpirun</code> saw all the allocated nodes and processes were reporting different hostnames, but each hostname was printed 3 times. It seems <code class="language-plaintext highlighter-rouge">srun</code> executed <code class="language-plaintext highlighter-rouge">mpirun</code> 3x, but oddly enough the main process was always on the same node (i.e. not on all 3 nodes for the 3x execution of <code class="language-plaintext highlighter-rouge">srun</code>).</p> <p>I obtained the first working solution by adding another <code class="language-plaintext highlighter-rouge">if</code> to the monstrosity above which checked the “slurm rank”, i.e.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>srun bash -c '[ $SLURM_NODEID = 0 ] &amp;&amp; mpirun -N 1 ./spawn_main || true'
</code></pre></div></div> <p>Calling <code class="language-plaintext highlighter-rouge">mpirun</code> inside <code class="language-plaintext highlighter-rouge">srun</code> already felt wrong, and the added complications didn’t improve the situation, so after some googling I fond an <a href="https://stackoverflow.com/questions/74160847/spawning-child-processing-on-hpc-using-slurm">SO question</a> asking about launching <code class="language-plaintext highlighter-rouge">MPI_Comm_spawn</code> from slurm, and copied the batch script parameters from there and it worked.</p> <p>It turns out, the missing ingredient was a missing <code class="language-plaintext highlighter-rouge">-n 3</code> for the job allocation. The final working solution looks like this:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/usr/bin/bash
#SBATCH -p genoa
#SBATCH -N 3
#SBATCH -n 3
#SBATCH -c 1

# ... snip ...

mpirun -N 1 spawn_main
</code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">-N</code> is the number of nodes allocated, <code class="language-plaintext highlighter-rouge">-n</code> is the number of tasks (i.e. number of MPI ranks). The solution is valid without <code class="language-plaintext highlighter-rouge">-c 1</code> (number of CPUs per task/rank), but I left it in just in case.</p>]]></content><author><name></name></author><category term="programming"/><category term="tadashi"/><summary type="html"><![CDATA[For TADASHI we are building a “benchmarking harness”, which would have a main instance running on one node of a cluster, and distribute to other nodes the code transformation (potentially), the compilation and the measurement of transformed apps.]]></summary></entry><entry><title type="html">Making Tadashi into a Python package</title><link href="https://vatai.github.io/blog/2025/making-tadashi-into-a-python-package/" rel="alternate" type="text/html" title="Making Tadashi into a Python package"/><published>2025-01-04T00:00:00+00:00</published><updated>2025-01-04T00:00:00+00:00</updated><id>https://vatai.github.io/blog/2025/making-tadashi-into-a-python-package</id><content type="html" xml:base="https://vatai.github.io/blog/2025/making-tadashi-into-a-python-package/"><![CDATA[<h1 id="the-current-situation">The current situation</h1> <p>Currently, to run Tadashi, you need to compile some .so files first with CMake. It needs to be built into the <code class="language-plaintext highlighter-rouge">build</code> directory under the project root. Finally, adding the project root to <code class="language-plaintext highlighter-rouge">PYTHONPATH</code> will allow python to find both the python files and the binary .so files.</p> <h2 id="swig">SWIG</h2> <p>First thing to clean up was implementing SWIG instead the ad-hoc CDLL approach currently used to call the C/C++ functions from Python. CDLL takes the path of the .so file, which is hard-coded in the Python files (hence the ad-hoc character of the implementation). CDLL also requires the arguments and return values of the functions exposed to Python from the .so files need to be copied from the .h files manually.</p> <p>The SWIG implementation has the following advantages:</p> <ul> <li>The arguments and return values of the functions are automagically generated based on the .h files, thus letting us have a single source of truth (well, technically two, since the function prototypes in .h and .cc need to be synced manually, but the compiler catches any discrepancies).</li> <li>SWIG generates the wrapper Python file in the same directory as the .so file (as part of the build process), eliminating the need to manually specify the path to the .so.</li> </ul> <h2 id="building-a-python-package">Building a Python package</h2> <p>To the best of my knowledge, the way to do python packages is to write a <code class="language-plaintext highlighter-rouge">pyproject.toml</code> file. However, <code class="language-plaintext highlighter-rouge">pyproject.toml</code> does not support build extensions and they must be configured in <code class="language-plaintext highlighter-rouge">setup.py</code>. The <code class="language-plaintext highlighter-rouge">cmake-build-extension</code> build extension invokes CMake as part of the build process of the wheel.</p> <p>TODO</p> <ul> <li>double check</li> </ul> <hr/> <ul> <li>Python package</li> <li>cmake-build-extension</li> </ul> <h1 id="the-new-problems">The new problems</h1> <hr/>]]></content><author><name></name></author><category term="programming"/><category term="tadashi"/><summary type="html"><![CDATA[The current situation]]></summary></entry><entry><title type="html">a post with image galleries</title><link href="https://vatai.github.io/blog/2024/photo-gallery/" rel="alternate" type="text/html" title="a post with image galleries"/><published>2024-12-04T01:59:00+00:00</published><updated>2024-12-04T01:59:00+00:00</updated><id>https://vatai.github.io/blog/2024/photo-gallery</id><content type="html" xml:base="https://vatai.github.io/blog/2024/photo-gallery/"><![CDATA[<p>The images in this post are all zoomable, arranged into different mini-galleries using different libraries.</p> <h2 id="lightbox2"><a href="https://lokeshdhakar.com/projects/lightbox2/">Lightbox2</a></h2> <p><a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-2500.jpg" data-lightbox="roadtrip"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-200.jpg"/></a> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg" data-lightbox="roadtrip"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg"/></a> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg" data-lightbox="roadtrip"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg"/></a></p> <hr/> <h2 id="photoswipe"><a href="https://photoswipe.com/">PhotoSwipe</a></h2> <div class="pswp-gallery pswp-gallery--single-column" id="gallery--getting-started"> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg" data-pswp-width="1669" data-pswp-height="2500" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg" alt=""/> </a> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/7/img-2500.jpg" data-pswp-width="1875" data-pswp-height="2500" data-cropped="true" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/7/img-200.jpg" alt=""/> </a> <a href="https://unsplash.com" data-pswp-src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg" data-pswp-width="2500" data-pswp-height="1666" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg" alt=""/> </a> <div> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-2500.jpg" data-pswp-width="2500" data-pswp-height="1667" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-200.jpg" alt=""/> </a> </div> </div> <hr/> <h2 id="spotlight-js"><a href="https://nextapps-de.github.io/spotlight/">Spotlight JS</a></h2> <div class="spotlight-group"> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg"/> </a> </div> <div class="spotlight-group"> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/4/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/4/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/5/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/5/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-200.jpg"/> </a> </div> <hr/> <h2 id="venobox"><a href="https://veno.es/venobox/">Venobox</a></h2> <p><a class="venobox" data-gall="myGallery" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-2500.jpg"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-200.jpg"/></a> <a class="venobox" data-gall="myGallery" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg"/></a> <a class="venobox" data-gall="myGallery" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg"/></a></p>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="images"/><summary type="html"><![CDATA[this is what included image galleries could look like]]></summary></entry><entry><title type="html">Flattening loops of combinations</title><link href="https://vatai.github.io/blog/2024/flattening-loops-of-combinations/" rel="alternate" type="text/html" title="Flattening loops of combinations"/><published>2024-11-11T00:00:00+00:00</published><updated>2024-11-11T00:00:00+00:00</updated><id>https://vatai.github.io/blog/2024/flattening-loops-of-combinations</id><content type="html" xml:base="https://vatai.github.io/blog/2024/flattening-loops-of-combinations/"><![CDATA[<p>In <a class="citation" href="#dash2021scaling">(Dash et al., 2021; Al Hajri et al., 2020)</a>, when iterating through all 2-hit combinations (of \(G\) number of genes), the outer two \(i\) and \(j\) loops are “flattened” into a single \(\lambda\) loop (\(\lambda \gets 1\ldots\binom{G}{2}\)). To reconstruct the \(i\) and \(j\) the following formulas are used:</p> \[\begin{align} j &amp;= \lfloor \sqrt{1/4 + 2 \lambda} + 1/2 \rfloor \\ i &amp;= \lambda - j (j - 1) / 2 \end{align}\] <p>Let’s try to derive those formulas.</p> <p>It is easy to spot the \(S_{j-1} := \sum_{t=1}^{j-1} t = \frac{j (j - 1)}{2}\) formula for the sum of all positive integers going up to \(j-1\). Which leads us to:</p> \[\lambda = i + \frac{j(j - 1)}{2} = i + \sum_{t=1}^{j-1} t\] <p>The same \(S_{j-1}\) formula is also present in the expression for \(j\) (we begin by removing the \(\lfloor \cdot \rfloor\)):</p> \[\begin{align} j &amp;= \sqrt{1/4 + 2 \lambda} + 1/2 \\ j - 1/2 &amp;= \sqrt{1/4 + 2 \lambda} \\ (j - 1/2)^2 &amp;= 1/4 + 2 \lambda \\ j^2 - j + 1/4 &amp;= 1/4 + 2 \lambda \\ j^2 - j &amp;= 2 \lambda \\ \lambda &amp;= \frac{j (j-1)}{2} \end{align}\] <p>According to <a class="citation" href="#dash2021scaling">(Dash et al., 2021; Al Hajri et al., 2020)</a> this flattened \(\lambda\) loop corresponds to the following \(i\) and \(j\) loop:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">N</span> <span class="o">=</span> <span class="mi">5</span>
<span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">N</span><span class="p">):</span>
        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="nf">print</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span><span class="n">j</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 : 0 1
2 : 0 2
3 : 0 3
4 : 0 4
5 : 1 2
6 : 1 3
7 : 1 4
8 : 2 3
9 : 2 4
10 : 3 4
</code></pre></div></div> <p>If we implement the flattened loop we see that this is only true in the sense that the set of visited combination is the same, however, the order is different.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">math</span>

<span class="n">N</span> <span class="o">=</span> <span class="mi">5</span>
<span class="n">Nc2</span> <span class="o">=</span> <span class="mi">10</span>
<span class="k">for</span> <span class="n">L</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">Nc2</span><span class="p">):</span>
    <span class="n">j</span> <span class="o">=</span> <span class="n">math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="mf">0.25</span> <span class="o">+</span> <span class="mi">2</span><span class="o">*</span><span class="n">L</span><span class="p">)</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">)</span>
    <span class="n">i</span> <span class="o">=</span> <span class="n">L</span> <span class="o">-</span> <span class="n">j</span><span class="o">*</span><span class="p">(</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">//</span><span class="mi">2</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 : 0 1
1 : 0 2
2 : 1 2
3 : 0 3
4 : 1 3
5 : 2 3
6 : 0 4
7 : 1 4
8 : 2 4
9 : 3 4
</code></pre></div></div> <p>So to generate the combinations in the same order as the initial \(i\), \(j\) loops, we need to modify the code as follows:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">math</span>

<span class="n">N</span> <span class="o">=</span> <span class="mi">5</span>
<span class="n">Nc2</span> <span class="o">=</span> <span class="mi">10</span>
<span class="k">for</span> <span class="n">L</span> <span class="ow">in</span> <span class="nf">reversed</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="n">Nc2</span><span class="p">)):</span>
    <span class="n">j</span> <span class="o">=</span> <span class="n">math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="mf">0.25</span> <span class="o">+</span> <span class="mi">2</span><span class="o">*</span><span class="n">L</span><span class="p">)</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">)</span>
    <span class="n">i</span> <span class="o">=</span> <span class="n">L</span> <span class="o">-</span> <span class="n">j</span><span class="o">*</span><span class="p">(</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">//</span><span class="mi">2</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="p">,</span> <span class="n">N</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">j</span><span class="p">,</span> <span class="n">N</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">i</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>9 : 0 1
8 : 0 2
7 : 0 3
6 : 0 4
5 : 1 2
4 : 1 3
3 : 1 4
2 : 2 3
1 : 2 4
0 : 3 4
</code></pre></div></div> <p>Or alternatively, if we want to modify the original \(i\), \(j\) loop to match the \(\lambda\) loop and the mathematical derivation:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">N</span> <span class="o">=</span> <span class="mi">5</span>
<span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">j</span><span class="p">):</span>
        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="nf">print</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 : 0 1
2 : 0 2
3 : 1 2
4 : 0 3
5 : 1 3
6 : 2 3
7 : 0 4
8 : 1 4
9 : 2 4
10 : 3 4
</code></pre></div></div> <h1 id="graphical-representation">Graphical representation</h1> <script type="text/tikz">
\begin{tikzpicture}

% GRAY
\foreach \i in {0,1,2,3,4}
  \foreach \j in {0,1,2,3}
    \node at (\i, \j) [draw, lightgray, circle]{};

% RED
\foreach \j [evaluate=\j as \jj using int(\j-1)] in {1,2,3}
  \foreach \i in {\jj,...,0}
    \node at (\j, \i) [fill, blue, circle]{};
\foreach \i in {0,1} \node at (4,\i) [fill, red, circle]{};

% labels
\node at (-1,-1) [anchor=east]{$\lambda=\frac{j(j-1)}{2}+i=6+1$};
\node at (-1,1) [draw]{$i=1$};
\node at (4,-1) [draw]{$j=4$};
\foreach \i in {0,2,3} \node at (-1,\i) {$\i$};
\foreach \j in {0,1,...,3} \node at (\j,-1) {$\j$};

\end{tikzpicture}
</script> <p>Because \(\lambda \mapsto j(\lambda) = \lfloor \sqrt{1/4 + 2 \lambda} + 1/2 \rfloor\) is monotonically increasing (non-decreasing), returning the \(\lfloor \cdot \rfloor\) which we skipped in the calculations above, means that \(j\) is the largest possible integer such that \(\lambda = i + \frac{j(j - 1)}{2}\) for a non-negative integer \(i\). In the figure we can read \(j=4\) since the blue dots represent \(\sum_{t=1}^{j-1} t\) and the red dots show the calculation of \(i = \lambda - \sum_{t=1}^{j-1} t\).</p>]]></content><author><name></name></author><category term="programming"/><summary type="html"><![CDATA[In (Dash et al., 2021; Al Hajri et al., 2020), when iterating through all 2-hit combinations (of \(G\) number of genes), the outer two \(i\) and \(j\) loops are “flattened” into a single \(\lambda\) loop (\(\lambda \gets 1\ldots\binom{G}{2}\)). To reconstruct the \(i\) and \(j\) the following formulas are used:]]></summary></entry><entry><title type="html">Continuous benchmarking on supercomputers</title><link href="https://vatai.github.io/blog/2022/continuous-benchmarking-on-supercomputers/" rel="alternate" type="text/html" title="Continuous benchmarking on supercomputers"/><published>2022-05-18T11:12:00+00:00</published><updated>2022-05-18T11:12:00+00:00</updated><id>https://vatai.github.io/blog/2022/continuous-benchmarking-on-supercomputers</id><content type="html" xml:base="https://vatai.github.io/blog/2022/continuous-benchmarking-on-supercomputers/"><![CDATA[<p>The <a href="https://doi.org/10.1145/3324989.3325719">paper</a> <em>“Towards Continuous Benchmarking: An Automated Performance Evaluation Framework for High Performance Software”</em>, by Anzt, H. et al. describes the posibilities of <em>continuous benchmarking</em> (CB), however it doesn’t give direct instructions how to implement it. Looking at the source code is always a possibility, but the <code class="language-plaintext highlighter-rouge">yaml</code> files there aren’t really documented (which is no surprises since they are pretty self documenting). Still, I hope this post will help people who unfamiliar with <em>Continuous X</em> approaches (where X can be integration, development, benchmarking etc).</p> <h1 id="overview">Overview</h1> <p>We will need 3 things:</p> <ul> <li>The <strong>app/benchmark</strong> itself, which we will assume to be given.</li> <li>We need to set up a <strong>“self-hosted runner”</strong>, the program running on the supercomputer, which will be executing the actions such as compiling the app and submitting job to the scheduler of the supercomputer.</li> <li>The <strong>yaml file</strong> which describes when and what should be executed by the “runner”.</li> </ul> <p>I’ll be describing the scenario for GitHub. Other sites like GitLab have similar systems.</p> <h2 id="self-hosted-runner">Self-hosted runner</h2> <p>In the Github repo <strong>settings</strong>, on the left-hand side, under <strong>Actions</strong> there is a <strong>Runners</strong> page. In the top-right corner there is a green <strong>New self-hosted runner</strong> button. Clicking on this button brings up a page where you can select the OS and architecture. For supercomputers Linux, x64 is a good choice since usually that is something that will run on the login node.</p> <p>Below the OS and architecture choice, the page lists the commands needed to install the self-hosted runner. This consists of several sections.</p> <p>The first section described how to download, validate and extract the runner software. <strong>Don’t use these instructions</strong>, use the ones from the GitHub settings page.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Create a folder
$ mkdir actions-runner &amp;&amp; cd actions-runner
# Download the latest runner package
$ curl -o actions-runner-linux-x64-2.291.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.291.1/actions-runner-linux-x64-2.291.1.tar.gz
# Optional: Validate the hash
$ echo "1bde3f2baf514adda5f8cf2ce531edd2f6be52ed84b9b6733bf43006d36dcd4c  actions-runner-linux-x64-2.291.1.tar.gz" | shasum -a 256 -c
# Extract the installer
$ tar xzf ./actions-runner-linux-x64-2.291.1.tar.gz
</code></pre></div></div> <p>The second section describes how to configure and run the self-hosted runner. Again <strong>don’t use these instructions</strong>, use the ones provided on the settings page, since the <code class="language-plaintext highlighter-rouge">--url</code> and the <code class="language-plaintext highlighter-rouge">--token</code> are dependent on the repo you want to add the runner to. The <code class="language-plaintext highlighter-rouge">./config.sh</code> asks a few questions, but generally it is very simple and usually the default answers are acceptable. The last command <code class="language-plaintext highlighter-rouge">./run.sh</code> is the runner itself, it connects to GitHub, and needs to be running to be able to accept workflows/jobs. See <a href="#security">note</a> about security.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/&lt;user&gt;/&lt;repo&gt; --token &lt;token&gt;
# Last step, run it!
$ ./run.sh
</code></pre></div></div> <p>The third section describes how to enable the runner in the Yaml file which is described in the next section.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Use this YAML in your workflow file for each job
runs-on: self-hosted
</code></pre></div></div> <p>This is the procedure to add a self-hosted runner to a repo. To the best of my knowledge, self-hosted runners can be added to GitHub users or GitHub organisations.</p> <h2 id="yaml-workflow-file">Yaml workflow file</h2> <p>To automatically run commands, we need to create a <code class="language-plaintext highlighter-rouge">&lt;name&gt;.yml</code> file in the <code class="language-plaintext highlighter-rouge">&lt;repo&gt;/.github/workflows/</code> directory, for example with the following contents:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># .github/workflows/build-and-submit.yml
name: Build and submit
on: push
jobs:
  build:
    name: Build
    runs-on: [self-hosted,login-node]
    steps:
    - uses: actions/checkout@master
    - name: Create build dir
      run: mkdir build
    - name: Run cmake
      working-directory: ./build
      run: CXX=FCCpx cmake ..
    - name: Build
      run: cmake --build build --clean-first
    - name: Submit
      run: pjsub -g $(stat . -c %G) sub.sh
</code></pre></div></div> <p>Each <code class="language-plaintext highlighter-rouge">&lt;name&gt;.yml</code> file (which can have any name) describes a workflow, with its <code class="language-plaintext highlighter-rouge">name:</code> (which can be any string), and the event when it will be executed. The example above will be exectuted <code class="language-plaintext highlighter-rouge">on: push</code>.</p> <p>Each workflow consists of one or more <code class="language-plaintext highlighter-rouge">jobs:</code>. Multiple jobs are, by default, executed in parallel. In the example, for simplicity, there is only one job, with the custom identifier <code class="language-plaintext highlighter-rouge">build:</code> (this can be a different identifier e.g. <code class="language-plaintext highlighter-rouge">job1:</code>). Each job has a <code class="language-plaintext highlighter-rouge">name:</code> (similarly to a workflow), and each job needs to specify where it is should run using the <code class="language-plaintext highlighter-rouge">runs-on:</code> value. Without self-hosted runners, we can specify here a docker image (something like <code class="language-plaintext highlighter-rouge">ubuntu-20.04</code>), but in our case <code class="language-plaintext highlighter-rouge">[self-hosted,login-node]</code> specifies that the job should be executed on a <code class="language-plaintext highlighter-rouge">self-hosted</code> runner. The <code class="language-plaintext highlighter-rouge">login-node</code> is custom label which can be added to the runner on GitHub.</p> <p>The main part of a job is the <code class="language-plaintext highlighter-rouge">steps:</code> field, which describes a list of steps which are executed sequentially. The job in the example has 5 steps. The first step is an “external” step (like importing a library), which checks out the master branch of the repository. The second, third and fourth steps create a <code class="language-plaintext highlighter-rouge">build</code> directory, call <code class="language-plaintext highlighter-rouge">cmake</code> in that directory (using the <code class="language-plaintext highlighter-rouge">working-directory:</code>), and builds the app using <code class="language-plaintext highlighter-rouge">cmake --build</code>. Finally, the last step, calls the command of the supercomputer scheduler to submit the <code class="language-plaintext highlighter-rouge">sub.sh</code> script.</p> <h1 id="observing-the-actions">Observing the actions</h1> <p>The top bar of a GitHub repository has an “Actions” page.</p> <p><img src="/assets/img/2022-05-18-continuous-benchmarking-on-supercomputers/actions.png" alt="Actions button" title="Actions button"/></p> <p>This page lists the workflows which were executed for the given repository. Clicking on a workflow, brings up a list of jobs defined for that workflow, and clicking on a job brings up the steps of that job. Clicking on a step expands it and displays the</p> <p><img src="/assets/img/2022-05-18-continuous-benchmarking-on-supercomputers/observing.png" alt="Observing actions" title="Observing actions"/></p> <h1 id="security">Security</h1> <p>This is obviously a security issue. The runner script <code class="language-plaintext highlighter-rouge">./run.sh</code> should be running all the time, connected to GitHub.com, waiting for jobs. As stated on GitHub, this should be enabled only for <strong>private</strong> repositories.</p> <h1 id="future-work">Future work</h1> <p>Next, I’d like to figure out how to write a workflow or a job which monitors when the submitted script finishes.</p>]]></content><author><name></name></author><category term="programming"/><summary type="html"><![CDATA[The paper “Towards Continuous Benchmarking: An Automated Performance Evaluation Framework for High Performance Software”, by Anzt, H. et al. describes the posibilities of continuous benchmarking (CB), however it doesn’t give direct instructions how to implement it. Looking at the source code is always a possibility, but the yaml files there aren’t really documented (which is no surprises since they are pretty self documenting). Still, I hope this post will help people who unfamiliar with Continuous X approaches (where X can be integration, development, benchmarking etc).]]></summary></entry><entry><title type="html">Polyhedral compilation: part 1</title><link href="https://vatai.github.io/blog/2022/polyhedral-compilation-part-1/" rel="alternate" type="text/html" title="Polyhedral compilation: part 1"/><published>2022-01-29T00:00:00+00:00</published><updated>2022-01-29T00:00:00+00:00</updated><id>https://vatai.github.io/blog/2022/polyhedral-compilation-part-1</id><content type="html" xml:base="https://vatai.github.io/blog/2022/polyhedral-compilation-part-1/"><![CDATA[<h1 id="abstract">Abstract</h1> <p>This blog posts is the first in a series of posts about <strong>polyhedral compilation</strong>, a mathematical model used to describe and reason about certain types of loops, with the aim to generate faster code.</p> <p>This post revisits <a href="https://link.springer.com/article/10.1007/BF01407835">“Some efficient solutions to the affine scheduling problem. I. One-dimensional time” by Paul Feautrier</a>, the seminal paper of the field, which describes how to <em>formulate the the search for an optimal schedule as an integer linear programming (ILP) problem</em>.</p> <h1 id="overview-of-the-process">Overview of the process</h1> <p>Formulated as a source-to-source compilation, the following steps give a (<em>very simplified</em>) overview of the entire process:</p> <ul> <li><strong>The input</strong> is source code with “nice” loops (where “nice” means that the loops satisfy such properties, that they are simple enough to be handled by ILPs).</li> <li>Problem/step 1: Finding the “nice” loops in the source code. This is handled by <a href="https://repo.or.cz/w/pet.git">Polyhedral Extraction Tool (PET)</a> which extracts affine description of the source code into <a href="https://repo.or.cz/w/isl.git">ISL</a> objects (named integer tuple sets/relations etc.). The loops in the source code can be marked with <code class="language-plaintext highlighter-rouge">scop</code> and <code class="language-plaintext highlighter-rouge">endscop</code> <code class="language-plaintext highlighter-rouge">#pragma</code>s or PET also has an auto-detect feature.</li> <li>Problem/step 2: Find or approximate the dependencies in the code.</li> <li><strong>Problem/step 3</strong>: Formulate an ILP, which describes the statements from in step 1 and the dependencies from step 2. Given an optimisation objective, the ILP can be solved to find an <strong>(optimal) schedule</strong>.</li> <li>Problem/step 4: Based on the schedule obtained in the previous step <strong>generate</strong> (improved) source code</li> <li><strong>The output</strong> is a source code with optimised loops.</li> </ul> <p>This post only addresses (the first half) of <strong>Problem/step 3</strong>.</p> <h1 id="example-code-matrix-vector-product">Example code: matrix vector product</h1> <pre><code class="language-C">for (i = 0; i &lt;= n; i++) {
S1: a[i] = 0.0;
    for (j = 0; j &lt;= n; j++)
S2:   a[i] += b[j] * M[i][j];
}
</code></pre> <p>The above code has two relevant <strong>statements</strong> which access the memory: <code class="language-plaintext highlighter-rouge">a[i] = 0.0;</code> labelled as \(S_1\) and <code class="language-plaintext highlighter-rouge">a[i] += b[j] * M[i][j];</code> labelled as \(S_2\). Each of the two statements is executed multiple times, it has multiple <strong>instances</strong>, for example the instances of statement \(S_1\) are:</p> <ul> <li><code class="language-plaintext highlighter-rouge">a[0] = 0.0;</code> for \(i = 0\),</li> <li><code class="language-plaintext highlighter-rouge">a[1] = 0.0;</code> for \(i = 1\) etc.</li> </ul> <p>Since instances may need to be described by multiple loop variables, we adopt the notation \(\vec{i}\) for <strong>vectors in the iteration space</strong>, vectors with integer entries, such that the first element corresponds to the outermost and the last to the innermost loop variable. For example</p> <ul> <li><code class="language-plaintext highlighter-rouge">a[0] += b[1] * M[0][1];</code> for \(\vec{i} = (i, j) = (0, 1)\) and</li> <li><code class="language-plaintext highlighter-rouge">a[2] += b[3] * M[2][3];</code> for \(\vec{i} = (i, j) = (2, 3)\).</li> </ul> <h1 id="describing-dependencies">Describing dependencies</h1> <h2 id="generalised-dependency-graph-gdg">Generalised Dependency Graph (GDG)</h2> <h3 id="verticesdomains">Vertices/domains</h3> <p>For each statement \(S\) the corresponding <strong>vertex</strong> of the GDG is labelled with the <strong>domain</strong> (hence the \(\mathscr{D}\) notation below) of the statement \(S\), i.e. the subset of the iteration space containing the instances of \(S\) executed by the loop.</p> <ul> <li>\(\mathscr{D}_1 = \\{ i : 0 \le i \le n \\}\) for statement \(S_1\)</li> <li>\(\mathscr{D}_2 = \\{ (i, j) : 0 \le i, j \le n \\}\) for statement \(S_2\)</li> </ul> <p>Technically, the domains are not sets, but families of sets, depending on parameters (in this example on the single parameter \(n\)), so the domain for statement 1 is the map \(n \mapsto \\{ i : 0 \le i \le n \\}\), but we omit the “\(n \mapsto\)” part, and treat \(n\) as a constant (but this will be included in a more).</p> <h3 id="edgesdependencies">Edges/dependencies</h3> <p>The <strong>edges</strong> of GDG are the <strong>dependencies</strong> between two statements and are labelled with a subset of the direct product (a relation, hence the \(\mathscr{R}\) notation below) between the two domains of statements of the start and end of the edge, that is, if \(S'\) and \(S\) are two statements and there is a dependency between the instances \(\vec{i'} \in \mathscr{D}_ {S'}\) and \(\vec{i} \in \mathscr{D}_ S\) then there is and edge from vertex \(\mathscr{D}_{S'}\) to \(\mathscr{D} _S\) labelled with a set that contains \((\vec{i'}, \vec{i})\).</p> <p>A simplified (ergo very conservative) dependency analysis (there are programs which can perform such analysis) could yield two dependencies:</p> <ul> <li>\(\mathscr{R}_{1, 2} = \{ \bigl( i', (i, j) \bigr) : i' = i \}\) describes the dependency between \(S_1\) and \(S_2\) which requires for the initialisation in \(S_1\) (<code class="language-plaintext highlighter-rouge">a[i] = 0.0</code>) to precede (all instances of) statement \(S_2\) when the two statements share the same value for the loop variable \(i\) (hence \(i' = i\)).</li> <li>\(\mathscr{R}_{2, 2} = \{ \bigl( (i', j'), (i, j) : i' = i \land j' &lt; j \}\) describes the dependency of \(S_2\) on itself, which requires, for a given \(i\) (\(i' = i\)) the earlier instances of (in \(j\)) are executed before the later instances (hence \(j' &lt; j\)).</li> </ul> <p>This dependency analysis is <strong>very</strong> coarse and/or conservative (read poor), we’ll discuss a simple data flow dependency later (which is still quite simple, but a slight improvement over the one above).</p> <h2 id="detailed-dependency-graph-ddg">Detailed Dependency Graph (DDG)</h2> <p>The GDG is structured: the vertices in GDG are statements, and these statements represent multiple instances, but we actually care about the dependencies between the instances. For this reason the Detailed Dependency Graph “flattens” the graph, and every vertex is an instance of a statement, and the edges are the dependencies between these instances.</p> <h3 id="vertices">Vertices</h3> \[\Omega = \bigcup _{S \in V} \{ (S, \vec{i}) : \vec{i} \in \mathscr{D} _S \}\] <h3 id="edges">Edges</h3> \[\Gamma = \bigcup _{e \in E} \bigl\{ \bigl( (\sigma(e), \vec{i'}), (\delta(e), \vec{i}) \bigr) : \vec{i'} \in \mathscr{D} _{\sigma(e)}, \vec{i} \in \mathscr{D} _{\delta(e)}, (\vec{i'}, \vec{i}) \in \mathscr{R}_e \bigr\}\] <p>where the statement \(\sigma(e)\) is the start, statement \(\delta(e)\) is the end of edge \(e\) (of the GDG).</p> <h1 id="schedule">Schedule</h1> <p>The schedule is a map \(\theta: \Omega \to \mathbb{R}_0^+\) from the set of instances to some non-negative value which is the “date” (or timestamp, or time) of the instance.</p> <h2 id="generating-code">Generating code</h2> <p>As mentioned above, generating code is a separate, and very much non-trivial problem. But to get a better feeling how to interpret the schedule \(\theta\) a simplified code generations is presented:</p> <p>Let \(\mathtt{F}(t) = \\{ (S, \vec{i}) \in \Omega: \theta(S, \vec{i}) = t \\}\), i.e. the set of all instances of all statements which should be executed at time step \(t\). Let \(\mathtt{L} = \max_{(S, \vec{i}) \in \Omega} \theta(S, \vec{i})\).</p> <pre><code class="language-C++">for (t = 0; t &lt;= L; t++) {
  #pragma omp parallel
  for (inst : F(t))
    execute(inst);
  barrier();
}
</code></pre> <p>Of course, actual code generation is a much harder task than this naive pseudo-code, but it can be handled separately, the objective of this now is how to obtain the optimal schedule.</p> <h2 id="there-is-no-optimal-schedule">There is (no) optimal schedule</h2> <p>The paper cites Theorems which say that finding a schedule <strong>of arbitrary form</strong> is an undecidable problem. Because of this, we restrict ourselves to <strong>affine schedules</strong>, that is schedules of the form: \(\theta(S, \vec{i}) = \tau_S \vec{i} + \sigma_s \vec{n} + \alpha_s\) for each statement \(S\). The vector \(\vec{n}\) is the vector of parameters, for the example above the vector of length 1 containing \(n\). In this case the triplet \((\tau_S, \sigma_S, \alpha_S)\) completely define \(\theta\) (for a given \(S\)), so the goal is finding a \((\tau_S, \sigma_S, \alpha_S)\) triplet for each statement \(S\).</p> <h1 id="more-advanced-dependency-analysis">More advanced dependency analysis</h1> <h2 id="depth">Depth</h2> <p>Descriptions such as GDG and DDG can enable some optimisations.</p> <p>The <strong>depth</strong> of an edge is the position until which both instances at the start and the end of the edge share values, and after which the end instance has a larger value, that is \(p_ e\) is the depth of edge \(e\) iff \((\vec{i'}, \vec{i}) \in \mathscr{R} _ e\) and \(i'_ k = i_ k\) for \(1 \le k \le p_ e\) and \(i'_ {p_ e} &lt; i_ {p_ e}\) where \(\vec{i'} = (i'_ 1, i'_ 2, \ldots)\) and \(\vec{i} = (i_ 1, i_2, \ldots)\).</p> <p>In the example, both edges of the GDG have depth 1:</p> <ul> <li>\(\mathscr{R}_{1, 2} = \{ \bigl( i', (i, j) \bigr) : i' = i \}\),</li> <li>\(\mathscr{R}_{2, 2} = \{ \bigl( (i', j'), (i, j) : i' = i \land j' &lt; j \}\).</li> </ul> <p>In both cases the \(i'=i\) part implies depth \(p_e \ge 1\) and the rest ensures \(p _e \le 1\).</p> <p>This can be used to infer, that we are allowed to execute the outermost loop in parallel.</p> <h2 id="dependence-direction-vectors">Dependence direction vectors</h2> <p>A more detailed description of the dependencies can be given using symbols such as \(&lt;, \le, =, *, \ldots\) combined in a <strong>dependence direction vector</strong> (the asterisk denotes a wildcard, meaning any relation). Depth can be expressed with DDVs as</p> \[(\overbrace{=, \ldots, =}^{p_e}, &lt;, *, \ldots)\] <h2 id="uniform-dependence">Uniform dependence</h2> <p>The case where there is a constant difference between the instances of both ends of an edge, that is when \(i' = i + d\) if \((i', i) \in \mathscr{R}_ e\), the edge \(e\) is said to have a <strong>uniform dependence</strong>. In this case, instead of keeping track of \(\mathscr{D}_ {\sigma(e)}\), \(\mathscr{D}_ {\delta(e)}\) and the set of \((\vec{i'}, \vec{i})\) pairs, we can just keep track of a single set (polyhedron) of instances \(\mathscr{P}_ e\) and a affine map \(h_ e\) such that \(y \in \mathscr{P}_ e \implies y \in \mathscr{D}_ {\delta(e)} \land h_e(y) \in \mathscr{D} _{\sigma(e)}\) and then</p> \[(\vec{i'}, \vec{i}) \in \mathscr{R}_ e \iff \vec{i'} = h_ e(\vec{i}) \land \vec{i} \in \mathscr{P}_e\] <p>A more detailed analysis shows that the second edge of our example has such a uniform dependency.</p> <h2 id="dataflow-analysis">Dataflow analysis</h2> <p>A little more advanced (but still very much conservative) dataflow analysis can further restrict the polyhedrons \(\mathscr{R} _{1, 2}\) and \(\mathscr{R} _{2, 2}\). The analysis of the memory reads and writes tells us that only the entries of <code class="language-plaintext highlighter-rouge">a[i]</code> updated, they are updated independently for each index \(i\), and making no assumptions about the <code class="language-plaintext highlighter-rouge">+</code> operation (such as associativity, which <em>could</em> be used for further optimisations), we observe that</p> <ul> <li> <p><code class="language-plaintext highlighter-rouge">a[i]</code> is initialised in statement \(S_ 1\) and only the first iteration of the \(j\) loop depends on it: \(\bigl(i', (i, j) \bigr) \in \mathscr{R}_ {1,2} \iff i' = i \land j = 0\) (I think there is a typo in the paper saying \(j = 1\)?). This is reduced as:</p> \[\mathscr{P}_ {e _1} = \mathscr{D}_2 \cap \{ (i, j) : j \le 0 \}, \quad h _{e _1}(i, j) = i\] </li> <li> <p><code class="language-plaintext highlighter-rouge">a[i]</code> is updated with each iteration of \(j\), so every iteration (instance) of \(j\) depends only on the previous iteration (\(j - 1\)), and this only applies starting from the second iteration (\(j \ge 1\)): \(\bigl( (i', j'), (i, j) \bigr) \in \mathscr{R} _{2,2} \iff i' = i \land j' = j - 1 \land j \ge 1\) (Again, this might be a typo \(j \ge 2\) in the paper?) This is reduced as:</p> \[\mathscr{P} _{e _2} = \mathscr{D} _2 \cap \{ (i, j) : j \ge 1 \}, \quad h _{e _2}(i, j) = (i, j - 1)\] </li> </ul> <p>We will continue with these reduced forms.</p> <h1 id="formulating-the-integer-linear-program">Formulating the integer linear program</h1> <h2 id="describing-verticesdomains">Describing vertices/domains</h2> <p>The \(\mathscr{D}_ S\) domains (including the parameters, represented as \(\vec{n}\)) need to be rewritten in the form where given the parameters \(\vec{n}\) the instance \(\vec{i}\) is in domaind \(\mathscr{D} _S\) iff:</p> \[a_{S_k} \begin{pmatrix} \vec{i} \\ \vec{n} \end{pmatrix} + b_{S_k} \ge 0 \quad (\forall k=1, \ldots m_S)\] <p>This way, the \((a_ {S_ k}, b_ {S_ k})\) pairs completely describe \(\mathscr{D} _S\) (that is, you can use these vectors to represent them in a computer program).</p> \[\begin{align} \mathscr{D}_1 &amp;= \{ i : 0 \le i \le n \} \\&amp;= \{ i : 0 \le i \land 0 \le n - i \} \\ \mathscr{D}_2 &amp;= \{ (i, j) : 0 \le i, j \le n \} \\ &amp;= \{ (i, j) : 0 \le i \land 0 \le n - i \land 0 \le j \land 0 \le n - j \} \end{align}\] <p>In the example of \(\mathscr{D} _1\) there are two inequalities, implying \(m _1 = 2\):</p> \[0 \le i = (1, 0) \begin{pmatrix} i \\ n \end{pmatrix} + 0\] <p>implies \(a _{S _1} = (1, 0)\) and \(b _{S _1} = 0\) and</p> \[0 \le n - i = (-1, 1) \begin{pmatrix} i \\ n \end{pmatrix} + 0\] <p>implies \(a _{S _2} = (-1, 1)\) and \(b _{S _2} = 0\).</p> <p>Domain \(\mathscr{D} _2\) can be described with \(m _2 = 4\) such equations.</p> <h2 id="describing-edgesdependencies">Describing edges/dependencies</h2> <p>The edges \(\mathscr{R}_ e\) of the GDG is described by \((c_e, d_e)\) such that:</p> \[c _{e _k} \begin{pmatrix} \vec{i'} \\ \vec{i} \\ \vec{n} \end{pmatrix} + d_ {e_k} \ge 0 \quad (\forall k=1, \ldots m _e)\] <p>or for a restricted schedule with the affine map \(\vec{i'} = h_e(\vec{i})\) and the rewritten reduced domain \(\mathscr{P} _e\):</p> \[c_{e_k} \begin{pmatrix} \vec{i} \\ \vec{n} \end{pmatrix} + d_{e_k} \ge 0 \quad (\forall k=1, \ldots m_S)\] <p>The reduced domains \(\mathscr{P} _{e _1}\) and \(\mathscr{P} _{ e _2}\) can be described similarly as the other domains \(\mathscr{D} _1\) and \(\mathscr{D} _2\).</p> <h2 id="describing-schedules">Describing schedules</h2> <p>The schedule \(\theta(S, \vec{i})\) is also going to be rewritten using a set of \(\mu\) Farkas multipliers. For each statement \(S\) we assume that the schedule can be expressed as:</p> \[\theta(S, \vec{i}) \equiv \mu_{S_0} + \sum_{k=1}^{m_S} \mu_{S_k} \Bigl( a_{S_k} \begin{pmatrix} \vec{i} \\ n \end{pmatrix} + b_{S_k} \Bigr)\] <p>This captures the information provided by the domains \(\mathscr{D} _S\) captured in the (\(m _S\) number of) \((a _{S _k}, b _{S _k})\) pairs. To combine this with the information from the dependencies/edges we will need the <em>delay</em> corresponding to the edges.</p> <h2 id="the-delay">The delay</h2> <p>We assume that if the instance \(\vec{i}\) of a statement \(S\) depends on the instance \(\vec{i'}\) of the statement \(S'\), then there is a <strong>delay</strong> \(\Delta\) associated with that dependency/edge \(e\). This means that the date of \(S, \vec{i}\) assigned by the schedule \(\theta\) is greater (by at least \(1\)) than the date of \(S', \vec{i'}\):</p> \[\Delta = \theta(S, \vec{i}) - \theta(S', \vec{i'}) - 1 \ge 0\] <p>We assume that this delay can be rewritten with a different set of \(\lambda\) Farkas multipliers (these will be just placeholders to express dependencies between inequalities across inequalities resulting from the dependencies/edges).</p> \[\Delta \equiv \lambda_{e_0} + \sum_{k=1}^{m_e} \lambda_{e_k} \Bigl( c_{e_k} \begin{pmatrix} \vec{i} \\ n \end{pmatrix} + d_{e_k} \Bigr)\] <h1 id="putting-it-all-together">Putting it all together</h1> <p>The \(\equiv\) in the last equation was alluding to the next step where we combine the “\(\theta\) equations” expressing the domains and the “\(\Delta\) equations” expressing the dependencies.</p> \[\theta(S, \vec{i}) - \theta(S', \vec{i'}) - 1 \equiv \Delta \ge 0\] <p>On the left side of \(\equiv\) in the expression above we use two instances of the “\(\theta\) equations” (with \(a _S{ _k}\), \(b _S{ _k}\) and \(\mu _S{ _k}\)), on the right “\(\Delta\) equations” (with \(c _{e _k}\), \(d _{e _k}\) and \(\lambda _{e _k}\)) and solve the ILP for the \(\mu _{S _k}\) variables (for each statement \(S\)).</p> <h2 id="edge-e_1--1-to-2">Edge \(e_1 : 1 \to 2\)</h2> <p>For the first edge \(e _1\) between statement \(S_1\) to \(S_2\) the equations from above give rise to the following</p> \[\begin{align*} &amp;\bigl[\mu_{2, 0} + \mu_{2, 1} i + \mu_{2, 2} (n - i) + \mu_{2, 3} j + \mu_{2, 4} (n - j) \bigr] \\ -&amp; \bigl[\mu_{1, 0} + \mu_{1, 1} i + \mu_{1, 2} (n - i) \bigr] - 1 \\ \equiv&amp; \lambda_{1, 0} + \lambda_{1, 1} i + \lambda_{1, 2} (n - i) + \lambda_{1, 3} j + \lambda_{1, 4} (n - j) - \lambda_{1, 5} j \ge 0 \end{align*}\] <p>The first and second line (except the \(-1\) at the end of it) of the ILP come from the rewritten form of \(\mathscr{D}_2\) and \(\mathscr{D}_1\) from the <a href="#describing-verticesdomains">Describing vertices/domains</a> section, plugged in the “\(\Theta\) equation”, while the third line is the result of taking \(\mathscr{P} _{e _1}\) <a href="#dataflow-analysis">Dataflow analysis</a>, which is \(-j \ge 0\) and the inequalities from the \(\mathscr{D} _2\) (hence the similarity to the first line).</p> <p>The previous equation is equivalent to the following system of equations by equating the coefficients of \(i\), \(j\), \(n\) and the constant term.</p> \[\begin{align} \mu_{2, 0} - \mu_{1, 0} - 1 &amp;= \lambda_{1, 0} &amp;\text{const. terms}\\ \mu_{2, 1} - \mu_{2, 2} - \mu_{1, 1} + \mu_{1, 2} &amp;= \lambda_{1, 1} - \lambda_{1, 2} &amp;\text{$i$ terms}\\ \mu_{2, 3} - \mu_{2, 4} &amp;= \lambda_{1, 3} - \lambda_{1, 4} - \lambda_{1, 5} &amp;\text{$j$ terms}\\ \mu_{2, 2} + \mu_{2, 4} - \mu_{1, 2} &amp;= \lambda_{1, 2} + \lambda_{1, 4} &amp;\text{$n$ terms} \end{align}\] <h2 id="edge-e_2--2-to-2">Edge \(e_2 : 2 \to 2\)</h2> <p>The second edge is a <a href="#uniform-dependence">uniform dependency</a>, the schedule for the start and end of the edge, \(\theta(S _2, h(\vec{i}))\) and \(\theta(S _2, \vec{i})\) is nearly identical (difference highlighted in the formulae below).</p> \[\mu_{S_0} + \sum_{k=1}^{m_S} \mu_{S_k} \bigl( a_{S_k} (\begin{smallmatrix} {\color{magenta}{\vec{i}}} \\ n \end{smallmatrix}) + b_{S_k} \bigr) - \bigl[ \mu_{S_0} + \sum_{k=1}^{m_S} \mu_{S_k} \bigl( a_{S_k} (\begin{smallmatrix} \color{magenta}{h(\vec{i})} \\ n \end{smallmatrix}) + b_{S_k} \bigr) \bigr]\] <p>This results to most of the terms cancelling each other out in the expression \(\theta(S _2, \vec{i}) - \theta(S _1, h(\vec{i}))\) (written with the \(\mu _{S _k}\) Farkas multipliers):</p> \[\mu_{S_0} + \sum_{k=1}^{m_S} \mu_{S_k} \bigl( a_{S_k} \Bigl(\begin{smallmatrix} i \\ j \\ n \end{smallmatrix}\Bigr) + b_{S_k} \bigr) - \bigl[ \mu_{S_0} + \sum_{k=1}^{m_S} \mu_{S_k} \bigl( a_{S_k} \Bigl(\begin{smallmatrix} i \\ j \color{magenta}{-1} \\ n \end{smallmatrix}\Bigr) + b_{S_k} \bigr) \bigr]\] <p>As a result, the loop edge on \(S _2\) results in the following equation (not the lack of \(\lambda _{S _k}\) multipliers).</p> \[\Delta = \theta(S _2, i, j) - \theta(S _2, i, j - 1) - 1 = \mu_{2, 3} - \mu_{2, 4} - 1 \ge 0\] <h2 id="the-calculations">The calculations</h2> <p>Collecting and rearranging the inequalities for \(e _1 : S _1 \to S _2\) and \(e _2 : S _2 \to S _2\).</p> \[\begin{align} \lambda_{1, 0} =&amp; \mu_{2, 0} - \mu_{1, 0} - 1 \ge 0 \\ \lambda_{1, 1} =&amp; \mu_{2, 1} + \mu_{2, 4} - \mu_{1, 1} - \lambda_{1, 4} \ge 0 \\ \lambda_{1, 3} =&amp; \mu_{2, 3} - \mu_{2, 4} - \lambda_{1, 4} - \lambda_{1, 5} \ge 0 \\ \lambda_{1, 2} =&amp; \mu_{2, 2} + \mu_{2, 4} - \mu_{1, 2} - \lambda_{1, 4} \ge 0 \\ &amp; \mu_{2, 3} - \mu_{2, 4} - 1 \ge 0 \end{align}\] <p>Simplifying it gives:</p> \[\begin{align*} \mu_{2, 0} - \mu_{1, 0} - 1 \ge&amp; 0 \\ \mu_{2, 3} - \mu_{2, 4} - 1 \ge&amp; 0 \\ \mu_{2, 3} + \mu_{2, 4} - \mu_{1, 1} \ge&amp; 0 \\ \mu_{2, 2} + \mu_{2, 4} - \mu_{1, 2} \ge&amp; 0 \end{align*}\] <p>All these manipulations can be performed by algorithms automatically.</p> <h2 id="one-possible-result">One possible result</h2> <p>One valid choice for the \(\mu _{S _k}\) values is:</p> <ul> <li> \[\mu_{1, 0} = \mu_{2, 1} = \mu_{2, 2} = \mu_{2, 4} = \mu_{1, 1} = \mu_{1, 2} = 0\] </li> <li> \[\mu_{2, 0} = \mu_{2, 3} = 1\] </li> <li> \[\theta(1, i) = 0\] </li> <li> \[\theta(2, i, j) = j + 1\] </li> </ul> <h2 id="generated-code">Generated code</h2> <p>The resulting schedule is:</p> <ul> <li> \[\theta(S _1, i) = 0\] </li> <li> \[\theta(S _2, i, j) = j + 1\] </li> </ul> <p>Generating code from this is a separate task and will be disucussed in the next blog post, but the paper suggests something similar to:</p> <pre><code class="language-C++">#pragma omp parallel
for (i = 0; i &lt;= n; n++)
  a[i] = 0.0;
for (j = 0; j &lt;= n; j++)
  #pragma omp parallel
  for (i = 0; i &lt;= n; i++)
    a[i] += b[j] * M[i][j];
</code></pre> <h1 id="citing-this-blog-post">Citing this blog post</h1> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@misc{vatai2022polytutor1,
  title={Polyhedral compilation: part 1},
  url={https://vatai.github.io/math/compsci/polyhedral/polyhedral-compilation-part-1/},
  author={Vatai, Emil},
  year={2022},
  month={Feb}
}
</code></pre></div></div> <h1 id="feedback">Feedback</h1> <p>Feedback is very much welcome. I don’t have a comment section set up, but you can raise an <a href="https://github.com/vatai/vatai.github.io/issues">issue</a> on GitHub.</p>]]></content><author><name></name></author><category term="math"/><category term="compsci"/><category term="polyhedral"/><summary type="html"><![CDATA[Abstract]]></summary></entry><entry><title type="html">Installing PyTorch with MPI support on ABCI</title><link href="https://vatai.github.io/blog/2021/Installing-PyTorch-with-MPI-support-on-ABCI/" rel="alternate" type="text/html" title="Installing PyTorch with MPI support on ABCI"/><published>2021-09-01T08:00:00+00:00</published><updated>2021-09-01T08:00:00+00:00</updated><id>https://vatai.github.io/blog/2021/Installing-PyTorch-with-MPI-support-on-ABCI</id><content type="html" xml:base="https://vatai.github.io/blog/2021/Installing-PyTorch-with-MPI-support-on-ABCI/"><![CDATA[<p>To get MPI backend for <a href="https://pytorch.org/docs/stable/distributed.html"><code class="language-plaintext highlighter-rouge">torch distributed</code></a> working you need to recompile PyTorch.</p> <p>On ABCI to get this working, you need to load these modules (some of them might be not needed, I just grabbed a <code class="language-plaintext highlighter-rouge">modules.sh</code> file):</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module load gcc/9.3.0
module load cuda/11.2/11.2.2
module load cudnn/8.1/8.1.1
module load nccl/2.8/2.8.4-1
module load openmpi/4.0.5
module load python/3.8/3.8.7
module load cmake/3.19
</code></pre></div></div> <p>After this we just need to clone the PyTorch repo:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:pytorch/pytorch.git
</code></pre></div></div> <p>and build it:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 setup.py develop --user
</code></pre></div></div> <p>This <strong>overwrites</strong> your current PyTorch installation, and you need to use <code class="language-plaintext highlighter-rouge">--upgrade --forece-reinstall</code> with <code class="language-plaintext highlighter-rouge">pip3</code> to install the original one.</p>]]></content><author><name></name></author><category term="tutorial"/><category term="MPI"/><category term="ABCI"/><summary type="html"><![CDATA[To get MPI backend for torch distributed working you need to recompile PyTorch.]]></summary></entry></feed>