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).
We will need 3 things:
I’ll be describing the scenario for GitHub. Other sites like GitLab have similar systems.
In the Github repo settings, on the left-hand side, under Actions there is a Runners page. In the top-right corner there is a green New self-hosted runner 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.
Below the OS and architecture choice, the page lists the commands needed to install the self-hosted runner. This consists of several sections.
The first section described how to download, validate and extract the runner software. Don’t use these instructions, use the ones from the GitHub settings page.
# Create a folder
$ mkdir actions-runner && 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
The second section describes how to configure and run the self-hosted
runner. Again don’t use these instructions, use the ones provided
on the settings page, since the --url
and the --token
are
dependent on the repo you want to add the runner to. The
./config.sh
asks a few questions, but generally it is very simple
and usually the default answers are acceptable. The last command
./run.sh
is the runner itself, it connects to GitHub, and needs to
be running to be able to accept workflows/jobs. See note
about security.
# Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/<user>/<repo> --token <token>
# Last step, run it!
$ ./run.sh
The third section describes how to enable the runner in the Yaml file which is described in the next section.
# Use this YAML in your workflow file for each job
runs-on: self-hosted
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.
To automatically run commands, we need to create a <name>.yml
file in the
<repo>/.github/workflows/
directory, for example with the following
contents:
# .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
Each <name>.yml
file (which can have any name) describes a workflow,
with its name:
(which can be any string), and the event when it will
be executed. The example above will be exectuted on: push
.
Each workflow consists of one or more jobs:
. Multiple jobs are, by
default, executed in parallel. In the example, for simplicity, there
is only one job, with the custom identifier build:
(this can be a
different identifier e.g. job1:
). Each job has a name:
(similarly
to a workflow), and each job needs to specify where it is should run
using the runs-on:
value. Without self-hosted runners, we can
specify here a docker image (something like ubuntu-20.04
), but in
our case [self-hosted,login-node]
specifies that the job should be
executed on a self-hosted
runner. The login-node
is custom label
which can be added to the runner on GitHub.
The main part of a job is the steps:
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 build
directory, call
cmake
in that directory (using the working-directory:
), and builds
the app using cmake --build
. Finally, the last step, calls the
command of the supercomputer scheduler to submit the sub.sh
script.
The top bar of a GitHub repository has an “Actions” page.
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
This is obviously a security issue. The runner script ./run.sh
should be running all the time, connected to GitHub.com, waiting for
jobs. As stated on GitHub, this should be enabled only for
private repositories.
Next, I’d like to figure out how to write a workflow or a job which monitors when the submitted script finishes.
]]>This blog posts is the first in a series of posts about polyhedral compilation, a mathematical model used to describe and reason about certain types of loops, with the aim to generate faster code.
This post revisits “Some efficient solutions to the affine scheduling problem. I. One-dimensional time” by Paul Feautrier, the seminal paper of the field, which describes how to formulate the the search for an optimal schedule as an integer linear programming (ILP) problem.
Formulated as a source-to-source compilation, the following steps give a (very simplified) overview of the entire process:
scop
and endscop
#pragma
s or PET also has an auto-detect
feature.This post only addresses (the first half) of Problem/step 3.
for (i = 0; i <= n; i++) {
S1: a[i] = 0.0;
for (j = 0; j <= n; j++)
S2: a[i] += b[j] * M[i][j];
}
The above code has two relevant statements which access the
memory: a[i] = 0.0;
labelled as $S_1$ and a[i] += b[j] * M[i][j];
labelled as $S_2$. Each of the two statements is executed multiple
times, it has multiple instances, for example the instances of
statement $S_1$ are:
a[0] = 0.0;
for $i = 0$,a[1] = 0.0;
for $i = 1$ etc.Since instances may need to be described by multiple loop variables, we adopt the notation $\vec{i}$ for vectors in the iteration space, vectors with integer entries, such that the first element corresponds to the outermost and the last to the innermost loop variable. For example
a[0] += b[1] * M[0][1];
for $\vec{i} = (i, j) = (0, 1)$ anda[2] += b[3] * M[2][3];
for $\vec{i} = (i, j) = (2, 3)$.For each statement $S$ the corresponding vertex of the GDG is labelled with the domain (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.
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).
The edges of GDG are the dependencies 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})$.
A simplified (ergo very conservative) dependency analysis (there are programs which can perform such analysis) could yield two dependencies:
a[i] = 0.0
) to precede (all instances
of) statement $S_2$ when the two statements share the same value for
the loop variable $i$ (hence $i’ = i$).This dependency analysis is very 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).
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.
where the statement $\sigma(e)$ is the start, statement $\delta(e)$ is the end of edge $e$ (of the GDG).
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.
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:
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})$.
for (t = 0; t <= L; t++) {
#pragma omp parallel
for (inst : F(t))
execute(inst);
barrier();
}
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.
The paper cites Theorems which say that finding a schedule of arbitrary form is an undecidable problem. Because of this, we restrict ourselves to affine schedules, 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$.
Descriptions such as GDG and DDG can enable some optimisations.
The depth 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} < i_ {p_ e}$ where $\vec{i’} = (i’_ 1, i’_ 2, \ldots)$ and $\vec{i} = (i_ 1, i_2, \ldots)$.
In the example, both edges of the GDG have depth 1:
In both cases the $i’=i$ part implies depth $p_e \ge 1$ and the rest ensures $p _e \le 1$.
This can be used to infer, that we are allowed to execute the outermost loop in parallel.
A more detailed description of the dependencies can be given using symbols such as $<, \le, =, *, \ldots$ combined in a dependence direction vector (the asterisk denotes a wildcard, meaning any relation). Depth can be expressed with DDVs as
\[(\overbrace{=, \ldots, =}^{p_e}, <, *, \ldots)\]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 uniform dependence. 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
\[(\vec{i'}, \vec{i}) \in \mathscr{R}_ e \iff \vec{i'} = h_ e(\vec{i}) \land \vec{i} \in \mathscr{P}_e\]A more detailed analysis shows that the second edge of our example has such a uniform dependency.
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 a[i]
updated, they are
updated independently for each index $i$, and making no assumptions
about the +
operation (such as associativity, which could be used
for further optimisations), we observe that
a[i]
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:
a[i]
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:
We will continue with these reduced forms.
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:
\[a_{S_k} \begin{pmatrix} \vec{i} \\ \vec{n} \end{pmatrix} + b_{S_k} \ge 0 \quad (\forall k=1, \ldots m_S)\]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).
\[\begin{align} \mathscr{D}_1 &= \{ i : 0 \le i \le n \} \\&= \{ i : 0 \le i \land 0 \le n - i \} \\ \mathscr{D}_2 &= \{ (i, j) : 0 \le i, j \le n \} \\ &= \{ (i, j) : 0 \le i \land 0 \le n - i \land 0 \le j \land 0 \le n - j \} \end{align}\]In the example of $\mathscr{D} _1$ there are two inequalities, implying $m _1 = 2$:
\[0 \le i = (1, 0) \begin{pmatrix} i \\ n \end{pmatrix} + 0\]implies $a _{S _1} = (1, 0)$ and $b _{S _1} = 0$ and
\[0 \le n - i = (-1, 1) \begin{pmatrix} i \\ n \end{pmatrix} + 0\]implies $a _{S _2} = (-1, 1)$ and $b _{S _2} = 0$.
Domain $\mathscr{D} _2$ can be described with $m _2 = 4$ such equations.
The edges $\mathscr{R}_ e$ of the GDG is described by $(c_e, d_e)$ such that:
\[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)\]or for a restricted schedule with the affine map $\vec{i’} = h_e(\vec{i})$ and the rewritten reduced domain $\mathscr{P} _e$:
\[c_{e_k} \begin{pmatrix} \vec{i} \\ \vec{n} \end{pmatrix} + d_{e_k} \ge 0 \quad (\forall k=1, \ldots m_S)\]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$.
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:
\[\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)\]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 delay corresponding to the edges.
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 delay $\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’}$:
\[\Delta = \theta(S, \vec{i}) - \theta(S', \vec{i'}) - 1 \ge 0\]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).
\[\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)\]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.
\[\theta(S, \vec{i}) - \theta(S', \vec{i'}) - 1 \equiv \Delta \ge 0\]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$).
For the first edge $e _1$ between statement $S_1$ to $S_2$ the equations from above give rise to the following
\[\begin{align*} &\bigl[\mu_{2, 0} + \mu_{2, 1} i + \mu_{2, 2} (n - i) + \mu_{2, 3} j + \mu_{2, 4} (n - j) \bigr] \\ -& \bigl[\mu_{1, 0} + \mu_{1, 1} i + \mu_{1, 2} (n - i) \bigr] - 1 \\ \equiv& \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*}\]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 Describing vertices/domains section, plugged in the “$\Theta$ equation”, while the third line is the result of taking $\mathscr{P} _{e _1}$ Dataflow analysis, which is $-j \ge 0$ and the inequalities from the $\mathscr{D} _2$ (hence the similarity to the first line).
The previous equation is equivalent to the following system of equations by equating the coefficients of $i$, $j$, $n$ and the constant term.
\[\begin{align} \mu_{2, 0} - \mu_{1, 0} - 1 &= \lambda_{1, 0} &\text{const. terms}\\ \mu_{2, 1} - \mu_{2, 2} - \mu_{1, 1} + \mu_{1, 2} &= \lambda_{1, 1} - \lambda_{1, 2} &\text{$i$ terms}\\ \mu_{2, 3} - \mu_{2, 4} &= \lambda_{1, 3} - \lambda_{1, 4} - \lambda_{1, 5} &\text{$j$ terms}\\ \mu_{2, 2} + \mu_{2, 4} - \mu_{1, 2} &= \lambda_{1, 2} + \lambda_{1, 4} &\text{$n$ terms} \end{align}\]The second edge is a uniform dependency, 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).
\[\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]\]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):
\[\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]\]As a result, the loop edge on $S _2$ results in the following equation (not the lack of $\lambda _{S _k}$ multipliers).
\[\Delta = \theta(S _2, i, j) - \theta(S _2, i, j - 1) - 1 = \mu_{2, 3} - \mu_{2, 4} - 1 \ge 0\]Collecting and rearranging the inequalities for $e _1 : S _1 \to S _2$ and $e _2 : S _2 \to S _2$.
\[\begin{align} \lambda_{1, 0} =& \mu_{2, 0} - \mu_{1, 0} - 1 \ge 0 \\ \lambda_{1, 1} =& \mu_{2, 1} + \mu_{2, 4} - \mu_{1, 1} - \lambda_{1, 4} \ge 0 \\ \lambda_{1, 3} =& \mu_{2, 3} - \mu_{2, 4} - \lambda_{1, 4} - \lambda_{1, 5} \ge 0 \\ \lambda_{1, 2} =& \mu_{2, 2} + \mu_{2, 4} - \mu_{1, 2} - \lambda_{1, 4} \ge 0 \\ & \mu_{2, 3} - \mu_{2, 4} - 1 \ge 0 \end{align}\]Simplifying it gives:
\[\begin{align*} \mu_{2, 0} - \mu_{1, 0} - 1 \ge& 0 \\ \mu_{2, 3} - \mu_{2, 4} - 1 \ge& 0 \\ \mu_{2, 3} + \mu_{2, 4} - \mu_{1, 1} \ge& 0 \\ \mu_{2, 2} + \mu_{2, 4} - \mu_{1, 2} \ge& 0 \end{align*}\]All these manipulations can be performed by algorithms automatically.
One valid choice for the $\mu _{S _k}$ values is:
The resulting schedule is:
Generating code from this is a separate task and will be disucussed in the next blog post, but the paper suggests something similar to:
#pragma omp parallel
for (i = 0; i <= n; n++)
a[i] = 0.0;
for (j = 0; j <= n; j++)
#pragma omp parallel
for (i = 0; i <= n; i++)
a[i] += b[j] * M[i][j];
@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}
}
Feedback is very much welcome. I don’t have a comment section set up, but you can raise an issue on GitHub.
]]>torch distributed
working you need to
recompile PyTorch.
On ABCI to get this working, you need to load these modules (some of
them might be not needed, I just grabbed a modules.sh
file):
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
After this we just need to clone the PyTorch repo:
git clone git@github.com:pytorch/pytorch.git
and build it:
python3 setup.py develop --user
This overwrites your current PyTorch installation, and you need to
use --upgrade --forece-reinstall
with pip3
to install the original
one.
Some things I’ve already set up and promised to write about:
To set up MathJax for Jekyll/Minimal-mistakes theme, you need two things:
html
” (and optionally configure MathJax markup such
as $
or \(
, \)
)If we were working with simple html
, the place to include this would
be inside the <head>
. To include something in the head, we just
need to put it in _includes/head/custom.html
- anything in this file
will be inserted into the heading of all html
files generated by
Jekyll.
<script type="text/javascript" async
src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
extensions: ["tex2jax.js"],
jax: ["input/TeX", "output/HTML-CSS"],
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
processEscapes: true
},
"HTML-CSS": { availableFonts: ["TeX"] }
});
</script>
In addition, we can make it a configurable option, if we want to load
MathJax on each of our pages using the Liquid template language, since
_includes/head/custom.html
also passes trough it (more one this
below).
kramdown is the default markdown
parser for Jekyll, and it is also explicitly set in the
Minimal-mistakes _config.yml
like this:
markdown: kramdown
Since we want to use MathJax in conjunction with markdown, kramdown
has to be made aware of it, which can be achieved like this in
_config.yml
:
kramdown:
math_engine: mathjax
To relieve the pressure on MathJax CDN servers, the MathJax loading
code can be wrapped inside a Liquid if
statement like this:
{% if page.usemathjax %}
<script type="text/javascript" async
src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<script type="text/x-mathjax-config">
...
</script>
{% endif %}
This way, the MathJax loading code is generating only when the
usemathjax
variable is set for a page. You can set this variable in
the front matter of each page like this:
---
...
usemathjax: true
...
---
Stylesheets are configured similar to how MathJax was added to
<head>
. In this case, the /assets/css/main.scss
file needs to be
created, based on the same file in the Minimal-mistakes repo. Nice
explanation can be found in the Minimal-mistakes
docs.
You can write a .md
file which iterates trough the categories using
Liquid, or you can use a plugin like
[jekyll-archives](https://github.com/jekyll/jekyll-archives)
to
generate these pages automatically. If you copied the _config.yml
from Minimal-mistakes, most of the settings should be there (it might
be commented, just search for archive
).
Don’t forget to create the following category-archive.md
and
tag-archive.md
.
/_pages/category-archive.md
with contents:
---
title: "Posts by Category"
layout: categories
permalink: /categories/
author_profile: true
---
/_pages/tag-archive.md
with contents:
---
title: "Posts by Tag"
permalink: /tags/
layout: tags
author_profile: true
---
This should create the /categories
and /tags
pages on your website.
The links (and the large icon) on top of the page, including the title
(with the link that takes you to the root/home) is called the
masthead. It can be filled by adding entries to
/_data/navigation.yml
such as this:
main:
- title: "About me"
url: /aboutme
More details in the Minimal-mistakes docs.
]]>Describe how to set up a website/statically generated microblog like this one, which is essentially
Github pages (HTML only) with Jekyll is super simple to set up, but the themes and the basic built-in stuff is relatively limited and Minimal-mistakes is a good theme to add a bunch of extra stuff to your website/microblog/whatever you want to call it.
Jekyll is like a build system, which “put’s it all together” and
generates the static .html
pages, so Jekyll’s documentation is
what you should most probably be reading.
Liquid is the template language,
which ties the data from the Jekyll to the webpages. Liquid is
applied independently to both .md
and .html
files. It is nicely
explained in the docs.
Probably the most important thing about Jekyll is the _config.yml
.
A lot of things can be controlled from there.
I am not sure if a Gemfile
should be uploaded in your GitHub pages
repo. Since Jekyll is a Ruby app, it might be needed, but I am
definitely not sure about it. It might only be needed if you want to
use plugins.
You can “build” (i.e. generate) your site locally following the instructions in the Jekyll docs.
I use these command line options when writing posts and fiddling with the website (livereload is the good stuff).
jekyll serve --drafts --incremental --livereload
To get the Minimal-mistakes theme running with your GitHub pages, just
copy the
_config.yml
(link to
raw
file) from the Minimal-mistakes GitHub repo to your GitHub pages repo,
and make sure you uncomment remote_theme :
"mmistakes/minimal-mistakes"
. This _config.yml
file is in sync
with the Minimal-mistakes
docs
and it is a good idea to go trough it quickly.
Alternatively, you could fork the Minimal-mistakes
repo and add your
contents with theme : "mmistakes/minimal-mistakes"
(instead of
remote_theme
) in _config.yml
.
I somehow missed this and it caused a lot of problems: Front matter is a way to add metadata and/or commands to Jekyll how to render the page. Front matter looks like this:
---
title: "How to set up a website like this"
categories: tutorial
layout: post
---
It has to
---
and nothing else,:
; and finally---
.Front matter applies to .md
files (maybe to .html
files as well - need to check).
Some things I’ve already set up in this page but did not describe here are:
It is basically just a test
With dollar signs for variable $a$. And then with backslash parenthesis for \(b\).
\[\sum_{k=0}^\infty (-1)\frac{x^{2k+1}}{(2k+1)!}\]
And with a formula above this.
]]>