
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://yp-edu.github.io/projects/feed.xml" rel="self" type="application/atom+xml" /><link href="https://yp-edu.github.io/" rel="alternate" type="text/html" /><updated>2026-07-03T03:36:02+00:00</updated><id>https://yp-edu.github.io/projects/feed.xml</id><title type="html">Yoann Poupart | Projects</title><subtitle>Personal website, blog &amp; portfolio.</subtitle><entry><title type="html">Training GPT-2 on Stockfish Games</title><link href="https://yp-edu.github.io/projects/training-gpt2-on-stockfish-games" rel="alternate" type="text/html" title="Training GPT-2 on Stockfish Games" /><published>2026-07-03T03:36:02+00:00</published><updated>2026-07-03T03:36:02+00:00</updated><id>https://yp-edu.github.io/projects/training-gpt2-on-stockfish-games</id><content type="html" xml:base="https://yp-edu.github.io/projects/training-gpt2-on-stockfish-games"><![CDATA[<p><img src="/assets/images/training-gpt2-on-stockfish-games.webp" alt="Training GPT-2 on Stockfish Games" /></p>

<blockquote class="callout tldr"> <div class="callout-title"> <i class="fa-solid fa-clipboard-list" href="##"></i> <em> TL;DR</em></div> <p>I trained a GPT-2 model on Stockfish self-played games in the most naive way, with no search, and it can play decently. The model is trained to output the next move given the FEN string of the board (single state). While I present some gotchas and caveats, the results are quite acceptable for the amount of work and computing invested. I also present a basic attention visualiser parsing the attention of the text tokens into the board.</p> </blockquote>

<blockquote class="callout example"> <details> <summary><div class="callout-title"> <i class="fa-solid fa-list" href="#"></i> <em> Table of content</em></div></summary> <ul>
    <li><a href="#context">Context</a>
      <ul>
        <li><a href="#deepmind-paper">DeepMind Paper</a></li>
        <li><a href="#personal-background">Personal Background</a></li>
      </ul>
    </li>
    <li><a href="#training-method">Training Method</a>
      <ul>
        <li><a href="#technical-details">Technical Details</a></li>
        <li><a href="#straight-forward-improvements">Straight-Forward Improvements</a></li>
      </ul>
    </li>
    <li><a href="#inspecting-the-model">Inspecting the Model</a>
      <ul>
        <li><a href="#attention-as-an-interpretation">Attention as an Interpretation</a></li>
        <li><a href="#parsing-text-attention-in-the-board">Parsing Text Attention in the Board</a></li>
        <li><a href="#simple-interpretation">Simple Interpretation</a></li>
      </ul>
    </li>
    <li><a href="#resources">Resources</a></li>
  </ul> </details> </blockquote>

<h2 id="context"># Context</h2>

<h3 id="deepmind-paper">DeepMind Paper</h3>

<p>Deepming recently released a paper where they achieved grand-master level without search <a href="#resources">@1</a>. They used a decoder only trained on the board state (<a href="https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation">FEN</a> string) to predict whether the value of the board, the value of the action ($Q$ values) or the best action. I decided to take a similar approach but make it as simple as possible; I only trained the model to predict the next action (not necessarily the best).</p>

<p>With this approach, there is no need to compute tons of evals using the Stockfish engine, no need to filter out illegal moves and no need to use the fancy RL scheme (yes, I have been traumatised). There are some catches that come with this oversimplification that I present throughout the post, but I think it was a nice reframing.</p>

<h3 id="personal-background">Personal Background</h3>

<p>For the record, this project only took me 1 full day of work to set up and around 3 more days to release everything. The overhead is mostly making this blog post, but it’s a good exercise for me, so it’s definitely worth it.
I have already trained LLMs in the past, although I am far from being an expert. I have also worked on chess models, most recently <a href="https://lczero.org/">lc0</a>, and I am familiar with XAI / MI.</p>

<p>This project was not really a challenge for me; it was more of a hobby project I wanted to share.</p>

<h2 id="training-method"># Training Method</h2>

<h3 id="technical-details">Technical Details</h3>

<p><strong>General Framing:</strong> Fine-tuning of a pre-trained model.</p>

<p>The training code is available on the associated GitHub repository <a href="https://github.com/yp-edu/training-gpt2-on-stockfish-games">training-gpt2-on-stockfish-games</a> (mostly quick and dirty). It is really straightforward and self-explanatory, as I relied on the Hugging Face Trainer (all the complexity is under the hood). Maybe the contribution you’ll find more worth is the Apptainer code to launch the training (not that difficult), which is cluster-friendly.</p>

<p>For the <a href="https://huggingface.co/datasets/yp-edu/stockfish-debug">dataset</a> I used the simple format of prompt + completion:</p>

<pre><code>{"prompt": "FEN: {fen}\nMOVE:", "completion": " {move}"}
</code></pre>

<p><strong>The run details:</strong></p>

<ul>
  <li>1 epoch of the dataset</li>
  <li>LR of $10^{-5}$</li>
  <li>12h on one A100 (40GB)</li>
</ul>

<blockquote class="callout danger"> <div class="callout-title"> <i class="fa-solid fa-bolt" href="##"></i> <em> Tokenisation Gotcha</em></div> <p>For simplicity, I didn’t change the tokeniser, so the tokenisation, <a href="https://huggingface.co/learn/nlp-course/en/chapter6/5">BPE</a>, is not tailored for chess modelling. For example, <code>_r</code>  (<code>_</code> for whitespace) and <code>r</code> get encoded differently, and <code>nb</code> is encoded as a single token while representing a knight and a bishop. Also note that I used the raw FEN string, so <code>8</code> actually represents 8 empty squares; definitely problematic for the model to grasp a spatial representation of the board with positional encodings (it still manages somewhat).</p> </blockquote>

<p>As discussed in the next section, changing the tokenisation is a low-hanging fruit to improve the model.</p>

<h3 id="straightforward-improvements">Straightforward Improvements</h3>

<p><strong>Prompt Engineering:</strong> Similarly to using your favourite chatbot, prompt engineering could be a simple way of making the model better. The model could be prompted (during training) with the strength,  <a href="https://en.wikipedia.org/wiki/Elo_rating_system">ELO</a>, of the player to move and the final result. On inference time, the model can then be prompted with the desired ELO and outcome. It obviously is limited; don’t expect your model to reach ELO 10,000 with this method.</p>

<blockquote class="callout tip"> <div class="callout-title"> <i class="fa-solid fa-fire-flame-curved" href="##"></i> <em> Conditioned Model</em></div> <p>Recent framing of RL as a (self-)supervised learning problem showed that training an agent on sub-optimal trajectories can lead to optimal strategies should the agent be conditioned on a reward signal. For example, this is the setup of Imitation Learning <a href="#resources">@2</a> and Decision Transformer <a href="#resources">@3</a>.</p> </blockquote>

<p><strong>Tokenisation:</strong> I think one the best way to encode the board would be to only represent the board tokens, with 7 different tokens (<code>P</code>, <code>N</code>, <code>B</code>, <code>R</code>, <code>Q</code>, <code>K</code> and <code>X</code> for empty squares), then using vertical and horizontal positional encodings (like in ViT), as well as for meta information. A piece’s colour encodings are different for each piece, and common encodings for castling rights are player colour, en passant, half-clock move, and full move number.</p>

<p><strong>Model:</strong> A text encoder with a special pooling token, <code>[CLS]</code>, and an input sequence of length 64 representing the board using the aforementioned tokenisation. The output of the encoder would be fed to a classification head of size <code>1858</code>, minimal move encodings but still complete (DeepMind didn’t get this right ^^ (neither in Alpha Zero for the record ^^)).</p>

<h2 id="inspecting-the-model"># Inspecting the Model</h2>

<p>A Gradio space is available to interact with the model and its attention; check it out on Hugging Face <a href="https://huggingface.co/spaces/yp-edu/viz-gpt2-stockfish-debug">-&gt;</a>.</p>

<h3 id="attention-as-an-interpretation">Attention as an Interpretation</h3>

<p>Attention is the most simple interpretation you can think of when studying Transformers. It is built into the model, and so it does reflect some internal knowledge of the model. In the autoregressive case (our case), you are often interested in “Why did my model predict this token?”. Basically, looking at a row of the attention coefficients $A$, defined by equation $\ref{eq:attention}$, for the predicted query token and the past key tokens gives you an importance distribution on the past tokens.</p>

\[\begin{equation}
\label{eq:attention}
A={\rm softmax}( QK^T) 
\end{equation}\]

<blockquote class="callout danger"> <div class="callout-title"> <i class="fa-solid fa-bolt" href="##"></i> <em> Interpretation Gotcha</em></div> <p>While attention can be used to do simple interpretation, the claim needs to be measured. Indeed, when interpreting the attention coefficients, you say nothing about the output values $O=AV$. This weighted sum needs to be interpreted as a whole for the conclusion to be more rigorous.</p> </blockquote>

<p>While <a href="#resources">@4</a> claimed that attention is not an explanation on its own, <a href="#resources">@5</a> conducted a more in-depth study pointing out the limitation of attention. You should especially remember the computation graph as its whole. Namely, attention heads will be merged and, in addition, composed via the residual stream, so the computation graph becomes a mess.</p>

<h3 id="parsing-text-attention-on-the-board">Parsing Text Attention on the board</h3>

<p>In order to project attention on the board, I simply parsed the attention per token and kept only the attention on pieces. The attention on the last part of the FEN string is summed into <code>Meta</code> while the rest of the tokens are summed in <code>Dump</code>. This is a simple workaround and proxy to circumvent the unadapted tokenisation.</p>

<blockquote class="callout danger"> <div class="callout-title"> <i class="fa-solid fa-bolt" href="##"></i> <em> Measure Gotcha</em></div> <p>Always think twice about what you’re actually measuring. Here, the heatmaps only carry information about the piece configuration, so my heuristic would be to leave out heatmaps where less than 50% of attention is put on pieces (<code>Config</code> metric).</p> </blockquote>

<p>Always look at the distribution among the three categories of tokens and the raw attention figure. The raw attention can give you insight into when not to trust grouped attention, i.e. when a token refers to more than one square. This is especially important since it creates illusory patterns, so you should refrain from your deepest instinct to interpret this as a meaningful pattern (everyone loves to see patterns ^^).</p>

<h3 id="simple-interpretation">Simple Interpretation</h3>

<blockquote class="callout warning"> <div class="callout-title"> <i class="fa-solid fa-triangle-exclamation" href="##"></i> <em> Disclaimer</em></div> <p>The following section contains rough preliminary results, experiments and takes. Thus, everything should be taken with care.</p> </blockquote>

<p><strong>Configuration attention:</strong> In terms of configuration attention, the crux is to predict the first letter of the move. I would mostly aim to interpret the attention of this token. While I didn’t conduct an in-depth analysis, it seems that the latter tokens are mostly doing a pooling of the previous representation.</p>

<p><strong>Discovering simple heads:</strong> The most simple heads will be located in the early layers. Interpreting later heads is harder due to polysemanticity, convoluted circuits, and a high degree of composition, you name it.</p>

<blockquote>
  <p>[!success] Advice
If you’re not familiar with chess notation or attention, maybe the better is to interact with the associated Hugging Face <a href="https://huggingface.co/spaces/yp-edu/viz-gpt2-stockfish-debug">space</a>. By copying the <a href="https://en.wikipedia.org/wiki/Portable_Game_Notation">PGN</a> (sequence of moves) or <a href="https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation">FEN</a> (state of the board) you can visualise the plot of this post and more. It’s especially important if you disagree with my claims or don’t trust me (you shouldn’t ^^).</p>
</blockquote>

<p><strong>L1 H7:</strong> Focuses on the knights but only when it is paired in a token; see the figure <a href="#L1-H7">1</a>. This is where this tokenisation gets annoying since it’s hard to say what the model might have learned from these paired tokens.</p>

<p class="im-center" id="L1-H7"><img src="/assets/images/training-gpt2-on-stockfish-games_L1_H7.svg" alt="L1 H7" />
<em>Figure 1: Configuration attention of the head 7 in layer 1.<br />
[FEN] <code>r1b1k1nr/pppp1ppp/2n1pq2/8/1b1PP3/2NQ4/PPP2PPP/R1B1KBNR w KQkq - 5 5</code>,<br />
[Distribution] <code>Config</code>: 0.78 <code>Meta</code>: 0.10 <code>Dump</code>: 0.13.</em></p>

<p><strong>e2e3 opening:</strong> This opening is by far my favourite (not a strong player here ^^), but out of the <code>262k</code> training games, only <code>13k</code> opens with <code>e2e3</code>. The main response to this opening is <code>e7e5</code> with 4k games. I then like to push my queen to <code>f3</code>, but since this combination is not in the training games, it seems that the model blunders doing <code>c7c5</code>. Pushing the bishop to <code>c4</code> leads the model to respond, moving the knight to <code>c6</code>, letting the queen go for the checkmate (so in 4). This feels expected since, most likely, as a predictor, the LLM learned in a Bayesian way</p>

\[p(a_4|s_4)=p(a_4|a_1, a_2, a_3)=p(a_4),\]

<p>since there is no prior on this sequence.</p>

<blockquote class="callout tip"> <div class="callout-title"> <i class="fa-solid fa-fire-flame-curved" href="##"></i> <em> Counting Experiment</em></div> <p>I think that counting all the games present in the dataset and comparing them to the learned distribution could be a nice low-hanging fruit experiment. The aim would be to examine the learned distribution on boards where the model has a prior against boards where it has no prior. This toy experiment could be framed in the larger picture of prediction generalisation and coherence.</p> </blockquote>

<p><strong>c7c5 blunder:</strong> I am not giving the reason for the aforementioned blunder here but mostly pointing out the limitation of doing interpretability with attention. Inspecting the attention of the last layer after <code>1. e3 e5 2. Qf3</code> most heads are strongly attending to the pawn in <code>e5</code>, see figure <a href="#L12-H2">2</a>, but in the end, the pawn <code>c7</code> is moved. By the way, <strong>L12 H2</strong> seems to focus on “player to move best-placed pawn”.</p>

<p class="im-center" id="L12-H2"><img src="/assets/images/training-gpt2-on-stockfish-games_L12_H2.svg" alt="L1 H7" />
<em>Figure 2: Configuration attention of the head 2 in layer 12.<br />
[FEN] <code>rnbqkbnr/pppp1ppp/8/4p3/8/4PQ2/PPPP1PPP/RNB1KBNR b KQkq - 1 2</code>,<br />
[Distribution] <code>Config</code>: 0.95 <code>Meta</code>: 0.01 <code>Dump</code>: 0.04.</em></p>

<p><strong>b8c6 second blunder:</strong> First, it seems to fail to see any danger or threat and continue to focus on pieces that would develop his position (like the knight move). Then, as a white, before the kill, the model doesn’t find the checkmate with the queen (obviously out-of-distribution here). It is important to note that all training games are long since the self-play means two players of approximately the same level.</p>

<blockquote class="callout tip"> <div class="callout-title"> <i class="fa-solid fa-fire-flame-curved" href="##"></i> <em> Adaptative Strength Experiment</em></div> <p>It could be interesting to see if such a predictor model adapts its level to the adversary. Did it blunder because I played such weak moves (w.r.t. to the meta)? Does it play better if you use the book openings? This might not be straightforward since this correlates with out-of-distribution evaluation.</p> </blockquote>

<p>This analysis is only preliminary, so feel free to continue it and send me feedback; the discussion happens in this Discord <a href="https://discord.com/channels/729741769192767510/1112497516928315442">thread</a> on Eleuther AI.</p>

<h2 id="resources"># Resources</h2>

<p>With this blog post, I release everything needed to reproduce, explore, understand and extend this work. In summary:</p>

<ul>
  <li>The model <a href="https://huggingface.co/yp-edu/gpt2-stockfish-debug">-&gt;</a></li>
  <li>The dataset <a href="https://huggingface.co/datasets/yp-edu/stockfish-debug">-&gt;</a></li>
  <li>The interactive space <a href="https://huggingface.co/spaces/yp-edu/viz-gpt2-stockfish-debug">-&gt;</a></li>
  <li>The code for the training and the space <a href="https://github.com/yp-edu/training-gpt2-on-stockfish-games">-&gt;</a></li>
</ul>

<blockquote class="callout quote"> <div class="callout-title"> <i class="fa-solid fa-quote-right" href="#"></i> <em> References</em></div> <ol>
    <li>Ruoss, Anian, et al. “Grandmaster-Level Chess Without Search.” <em>ArXiv</em>, 2024, /abs/2402.04494.</li>
    <li>Jonathan Ho et al. Generative adversarial imitation learning. Advances in neural information processing systems, 29, 2016.</li>
    <li>Chen, Lili, et al. “Decision Transformer: Reinforcement Learning via Sequence Modeling.” <em>ArXiv</em>, 2021, /abs/2106.01345.</li>
    <li>Jain, Sarthak, and Byron C. Wallace. “Attention Is Not Explanation.” <em>ArXiv</em>, 2019, /abs/1902.10186.</li>
    <li>Wiegreffe, Sarah, and Yuval Pinter. “Attention Is Not Not Explanation.” <em>ArXiv</em>, 2019, /abs/1908.04626.</li>
  </ol> </blockquote>]]></content><author><name>[[Yoann Poupart]]</name></author><category term="Chess" /><category term="LLM" /><category term="XAI" /><category term="Attention" /><category term="Training" /><summary type="html"><![CDATA[TL;DR> I trained a GPT-2 model on Stockfish self-played games in the most naive way, with no search, and it can play decently. The model is trained to output the next move given the FEN string of the board (single state). While I present some gotchas and caveats, the results are quite acceptable for the amount of work and computing invested. I also present a basic attention visualiser parsing the attention of the text tokens into the board.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://yp-edu.github.io/assets/images/training-gpt2-on-stockfish-games_thumbnail.webp" /><media:content medium="image" url="https://yp-edu.github.io/assets/images/training-gpt2-on-stockfish-games_thumbnail.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">MARL Cluster Training</title><link href="https://yp-edu.github.io/projects/marl-cluster-training" rel="alternate" type="text/html" title="MARL Cluster Training" /><published>2026-07-03T03:36:02+00:00</published><updated>2026-07-03T03:36:02+00:00</updated><id>https://yp-edu.github.io/projects/marl-cluster-training</id><content type="html" xml:base="https://yp-edu.github.io/projects/marl-cluster-training"><![CDATA[<p><img src="/assets/images/marl-cluster-training.png" alt="MARL Cluster Training" /></p>

<blockquote class="callout tldr"> <div class="callout-title"> <i class="fa-solid fa-clipboard-list" href="##"></i> <em> TL;DR</em></div> <p>Let’s dive into MARL training with BenchMARL and how to scale up your experiments to a cluster. We’ll cover environment and model customisation, as well as how to run your experiments on a cluster even without internet access.</p> </blockquote>

<blockquote class="callout example"> <details> <summary><div class="callout-title"> <i class="fa-solid fa-list" href="#"></i> <em> Table of Contents</em></div></summary> <ul>
    <li><a href="#context">Context</a>
      <ul>
        <li><a href="#benchmarl">BenchMARL</a></li>
      </ul>
    </li>
    <li><a href="#marl-training">MARL Training</a>
      <ul>
        <li><a href="#basic-setup">Basic Setup</a></li>
        <li><a href="#custom-task">Custom Task</a></li>
        <li><a href="#custom-model">Custom Model</a></li>
      </ul>
    </li>
    <li><a href="#cluster-training">Cluster Training</a>
      <ul>
        <li><a href="#cluster-setup">Cluster Setup</a></li>
        <li><a href="#running-experiments">Running Experiments</a></li>
        <li><a href="#no-internet">No Internet</a></li>
        <li><a href="#results">Results</a></li>
      </ul>
    </li>
    <li><a href="#resources">Resources</a></li>
  </ul> </details> </blockquote>

<h2 id="context"># Context</h2>

<p>As a part of <a href="https://yp-edu.github.io/stories/my-phd">my PhD</a>, I’m working on Multi-Agent Reinforcement Learning (MARL), more precisely on interpretability of MARL, as I outlined in <a href="https://arxiv.org/abs/2502.00726">this article</a>. Yet to be able to interpret any agent it’s important to master MARL training. My primary goal is to spend as little time as possible training MARL agents. I also want to use classical MARL algorithms and environments to be able to contextualise my results with the state of the art, and after testing a few tools, I chose to go with <a href="https://github.com/facebookresearch/BenchMARL">BenchMARL</a>.</p>

<h3 id="benchmarl">BenchMARL</h3>

<p>BenchMARL is a specialised library designed to ease MARL training. It provides a standardised interface that enables reproducibility and fair benchmarking across various MARL algorithms and environments.</p>

<blockquote class="callout tip"> <div class="callout-title"> <i class="fa-solid fa-fire-flame-curved" href="##"></i> <em> Extra</em></div> <p>BenchMARL is really well packaged, easily extensible and already embeds configs’ defaults.</p> </blockquote>

<p>BenchMARL’s backends:</p>

<ul>
  <li>TorchRL: provides a standardised interface for MARL algorithms and environments</li>
  <li>Hydra: provides a flexible and modular configuration system</li>
  <li>marl-eval: provides a standardised evaluation system</li>
</ul>

<p>I encourage you to check out the <a href="https://benchmarl.readthedocs.io/en/latest/">BenchMARL documentation</a> for more details.</p>

<h2 id="marl-training"># MARL Training</h2>

<h3 id="basic-setup">Basic Setup</h3>

<p>Along with this blog post, I have prepared a <a href="https://github.com/yp-edu/marl-cluster-training">repository</a> with a basic setup of BenchMARL. Please refer to the README for installation instructions and details. You can also check some results on my <a href="https://wandb.ai/yp-edu/marl-cluster-training/reports/MARL-Cluster-Training--VmlldzoxMzQzMjU0Mg">wandb report</a>.</p>

<blockquote class="callout success"> <div class="callout-title"> <i class="fa-solid fa-check" href="##"></i> <em> Feedback</em></div> <p>Feel free to open an issue if you have any questions, problems or remarks you want to share.</p> </blockquote>

<p>The first thing you might want to do is train supported algorithms on a supported environment. Let’s say you want to compare MAPPO and IPPO on Multiwalker. Such a benchmark is made of 2 independent experiments, that you can test individually using the script <a href="https://github.com/yp-edu/marl-cluster-training/blob/main/scripts/experiments/run_local.py"><code>experiments/run_local.py</code></a>, inspired by the <a href="https://github.com/facebookresearch/BenchMARL/blob/main/benchmarl/run.py"><code>run.py</code></a> script provided by BenchMARL.</p>

<ul>
  <li>IPPO &amp; Multiwalker: <br />
<code>uv run -m scripts.experiments.run_local algorithm=ippo task=pettingzoo/multiwalker</code></li>
  <li>MAPPO &amp; Multiwalker: <br />
<code>uv run -m scripts.experiments.run_local algorithm=mappo task=pettingzoo/multiwalker</code></li>
</ul>

<p>These scripts are based on Hydra’s configuration system, which allows you to easily modify the configuration of your experiment in a YAML file or through the command line. This is especially important when you want to run multiple experiments with different configurations, e.g. for hyperparameter search. Additionally, the defaults can be loaded directly from BenchMARL since the script’s config (<code>exp:run_local.yaml</code>) adds it to the Hydra search path.</p>

<blockquote class="callout tip"> <div class="callout-title"> <i class="fa-solid fa-fire-flame-curved" href="##"></i> <em> Tweaking</em></div> <p>You can easily tweak an algorithm by creating a new file in the <code>algorithm</code> config group and iterating on it manually or with a hyperparameter search. You can similarly tweak experiments and models.</p> </blockquote>

<p>But the strength of BenchMARL is to run benchmarks, i.e. a group of reproducible experiments with a similar config. You can start by running the <a href="https://github.com/yp-edu/marl-cluster-training/blob/main/scripts/benchmarks/multiwalker.py"><code>benchmarks/multiwalker.py</code></a> script, which is equivalent to running the previous experiments (with multiple seeds) but fully baked with the powerful plots from <code>marl-eval</code> to compare the experiments.</p>

<ul>
  <li>Run the benchmark: <code>uv run -m scripts.benchmarks.multiwalker</code></li>
</ul>

<p>As this becomes tedious to run on a personal computer, the next step is to run it on a cluster. Jump to the <a href="#cluster-training">Cluster Training</a> section for more details.</p>

<blockquote class="callout success"> <div class="callout-title"> <i class="fa-solid fa-check" href="##"></i> <em> Benchmark Config</em></div> <p>I proposed a <a href="https://github.com/yp-edu/marl-cluster-training/blob/main/configs/bench:multiwalker.yaml">config</a> for the multiwalker benchmark based on object packing similar to how layers are composed. You can use it as a template to create your own benchmark configs.</p> </blockquote>

<h3 id="custom-tas">Custom Tas</h3>

<p>One of BenchMARL’s strengths is its ability to integrate custom tasks. First, let’s say I want to create a supported task variation, e.g. Multiwalker with a shared reward.</p>

<blockquote class="callout bug"> <div class="callout-title"> <i class="fa-solid fa-bug" href="##"></i> <em> Composition bug</em></div> <p>Unfortunately, unlike algorithms, tasks are nested in the framework folder (e.g. <code>pettingzoo</code>). Because of this and a bug in Hydra, it is not possible to compose defaults from nested tasks, see <a href="https://github.com/facebookresearch/hydra/issues/3060">this issue</a>.</p> </blockquote>

<p>But it is still possible to derive a custom task in a few steps:</p>

<ul>
  <li>Create a config file: <code>multiwalker/shared.yaml</code></li>
  <li>Register the task (before creating the experiment object):</li>
</ul>

<script src="https://gist.github.com/Xmaster6y/0499dff7b7725f6160df03a58fccec6c.js"></script>

<ul>
  <li>Run the experiment: <br />
<code>uv run -m scripts.experiments.run_local algorithm=mappo task=multiwalker/shared</code></li>
</ul>

<blockquote class="callout danger"> <div class="callout-title"> <i class="fa-solid fa-bolt" href="##"></i> <em> Hack</em></div> <p>Contrary to other groups (algorithms, experiments or models), tasks need to be registered as they are not spawned directly. Indeed, they are spawned through their factored environment class.</p> </blockquote>

<p>Another kind of custom tasks are unsupported tasks from a supported environment class, for example, KAZ (Knights Archers Zombies) from PettingZoo. First, you need to create a custom task class:</p>

<script src="https://gist.github.com/Xmaster6y/b9ea4c83c88789b45ddcee2ccdbc03d5.js"></script>

<p>Which is used in the task:</p>

<script src="https://gist.github.com/Xmaster6y/0916d6f7187191b251d8db7893cc3d6f.js"></script>

<p>Then you can register the task:</p>

<script src="https://gist.github.com/Xmaster6y/4ba879431ccd864dc96b298789946140.js"></script>

<p>And additionally, you can validate the task using a <code>dataclass</code> to serve as a schema, which you would add in a <code>ConfigStore</code>:</p>

<script src="https://gist.github.com/Xmaster6y/114c4b4ffc3c256ba7b830ffa766be14.js"></script>

<p>For truly custom environments, see the <a href="https://github.com/facebookresearch/BenchMARL/tree/main/examples/extending/task">examples</a>. You might also want to dig into the <a href="https://pytorch.org/rl/stable/api/torchrl.envs.env.html">torchrl</a> documentation first, to understand how to create your own environments.</p>

<blockquote class="callout question"> <div class="callout-title"> <i class="fa-solid fa-circle-question" href="##"></i> <em> Question</em></div> <p>How to handle the KAZ vector state <code>(B, N, F)</code>?</p> </blockquote>

<blockquote class="callout answer"> <details> <summary><div class="callout-title"> <i class="fa-solid fa-circle-info" href="#"></i> <em> Answer</em></div></summary> <p>As <code>N</code> is not an agent dimension, you cannot directly use the <code>Mlp</code> (<code>(B, F)</code> inputs) model nor the <code>Cnn</code> (<code>(B, F)</code> or <code>(B, H, W, C)</code> inputs) model. You’ll need either to modify the environment to output the correct shape or use a custom model based on a <code>Cnn</code>, <code>Mlp</code> or a flattening layer.</p> </details> </blockquote>

<h3 id="custom-model">Custom Model</h3>

<p>As noted in the previous section, KAZ vector state requires a custom model. The easiest will be to modify the <code>MlP</code> model, flattening any extra dimension. The basic idea is to introduce a new <code>num_extra_dims</code> parameter in the model config, which will be used to flatten the input tensor.</p>

<script src="https://gist.github.com/Xmaster6y/c828476db539b3c72e02bd53b83552c2.js"></script>

<p>This parameter will first be used in the <code>_perform_checks</code> method to check that the input tensor has the correct shape, and finally in the <code>_forward</code> method to simplify the input tensor:</p>

<script src="https://gist.github.com/Xmaster6y/51e77d720c191f9c67866de020ba0f17.js"></script>

<p>It then needs to come with a config file <code>extra_mlp.yaml</code> and should be registered in the <code>model_config_registry</code>:</p>

<script src="https://gist.github.com/Xmaster6y/b6149b984003df83519d9e5517918e28.js"></script>

<p>And that’s it! You can now use your custom model in your experiments.</p>

<blockquote class="callout success"> <div class="callout-title"> <i class="fa-solid fa-check" href="##"></i> <em> Extra Dims</em></div> <p>See the refined PR <a href="https://github.com/facebookresearch/BenchMARL/pull/211">here</a>.</p> </blockquote>

<h2 id="cluster-training"># Cluster Training</h2>

<p>Now that we can run the experiments we want locally, let’s scale up our experiments to a cluster.</p>

<blockquote class="callout note"> <div class="callout-title"> <i class="fa-solid fa-pen" href="##"></i> <em> Tweaks and tricks</em></div> <p>The more optimisations you’ll want on the training process, the more you’ll need to dig into the <code>torchrl</code> backend for more control over the environments and objects, and in the <code>torch</code> backend for more control over the models and tensors.</p> </blockquote>

<h3 id="cluster-setup">Cluster Setup</h3>

<p>After having tested a few different setups, I ended up settling on a full <code>uv</code> config. Here is my opinionated setup:</p>

<ul>
  <li>[Optional] Setup <code>git</code> in your project</li>
  <li>[Optional] Link a GitHub repository</li>
  <li>Sync your project to the cluster (I prefer using a <code>git</code> remote for easy bidirectional edits but <code>rsync</code> or <code>scp</code> can be simpler)</li>
  <li>Use <code>uv sync</code> to install the dependencies on the cluster (this step can be delegated to the job, should you have an internet connection, see <a href="#no-internet">No Internet</a>)</li>
  <li>Run your jobs or notebooks on the cluster</li>
</ul>

<p>You should do this on a Work or Draft partition, avoid installing in your home directory which might have a limited space (in terms of disk space and inodes). Always be aware of your cluster configuration and check with your admin in case of doubts.</p>

<blockquote class="callout success"> <div class="callout-title"> <i class="fa-solid fa-check" href="#"></i> <em> Pros</em></div> <ul>
    <li>It’s super easy to use once you’re used to <code>uv</code></li>
    <li>The configuration is the same as the local one</li>
    <li>Fully compatible with <code>slurm</code> jobs and <code>jupyter</code> notebooks/hubs</li>
    <li>Works without internet on the nodes (see <a href="#no-internet">No Internet</a>)</li>
  </ul> </blockquote>

<blockquote class="callout fail"> <div class="callout-title"> <i class="fa-solid fa-xmark" href="#"></i> <em> Cons</em></div> <ul>
    <li>You might miss package optimisations tailored for your cluster</li>
    <li>It can consume a lot of inodes (you might need to remove old virtual environments)</li>
    <li>Not super compatible with classical <code>cuda</code> echosystem</li>
  </ul> </blockquote>

<h3 id="running-experiments">Running Experiments</h3>

<p>The easy part with the setup I presented is that the same script can be used to run your experiments locally or on a cluster. So, except for the <code>slurm</code> arguments, which might differ depending on your cluster, you can use the same script.</p>

<p>A typical <code>slurm</code> script, that you can launch using <code>sbatch launch/bench:multiwalker-jz.sh</code>, would look like this:</p>

<script src="https://gist.github.com/Xmaster6y/fc467b0b45ea3727acb4a8613d57dfea.js"></script>

<p>And to make use of the GPU you can just switch the experiment config by adding the argument <code>experiment=gpu</code>. It will simply load the default experiment config (<code>base_experiment</code>) and override the <code>gpu</code> config:</p>

<script src="https://gist.github.com/Xmaster6y/7245814c6fb5337403cc2e6b4f466827.js"></script>

<p>Now you’re ready to launch a bunch of jobs doing wild hyperparameter search, with bigger models, bigger batch sizes, etc.</p>

<blockquote class="callout info"> <div class="callout-title"> <i class="fa-solid fa-circle-info" href="##"></i> <em> JupyterHub</em></div> <p>You can find an example of how to run a notebook on a cluster JupyterHub in my <a href="https://github.com/yp-edu/research-project-template/tree/main/notebooks">project template</a>, this can vary depending on your cluster (example made for JeanZay).</p> </blockquote>

<h3 id="no-internet">No Internet</h3>

<p>Some clusters, for security reasons, don’t allow internet access on the nodes (e.g. JeanZay in France). This can be a problem if you want to set up your environment (e.g. with <code>uv</code>) directly on the nodes (which can be easier). So let’s see how we can easily transpose what we’ve seen so far to a cluster without an internet connection.</p>

<p>As noted in the <a href="#cluster-config-setup">Cluster Config Setup</a> section, you should install your dependencies before starting your jobs. If you don’t have an internet connection on the head node (never seen this though), you might try to transfer your local setup, or if allowed, use a Docker image.</p>

<p>Then, the only thing you need to do is to remove or disable the tools that require internet access. In our experiments, you just need to use <code>wandb</code> in “offline” mode (e.g. using <code>experiment=gpu_offline</code> in the script):</p>

<script src="https://gist.github.com/Xmaster6y/52a8a593df6ae73b6d1bab48ca36c683.js"></script>

<p>And when running a script with <code>uv</code>, you should use the <code>--no-sync</code> flag to avoid syncing your dependencies again. Depending on your use case, you might need to download your datasets to a special partition beforehand.</p>

<blockquote class="callout bug"> <div class="callout-title"> <i class="fa-solid fa-bug" href="##"></i> <em> Wandb offline</em></div> <p>Unfortunately, wandb has a bug related to the config logging for offline runs, see <a href="https://github.com/wandb/wandb/issues/6974">this issue</a>. In order to circumvent this issue, you’ll need to slightly modify the <code>logger</code> of the <code>Experiment</code> class in BenchMARL to pass the config to <code>wandb.init</code>, see <a href="https://github.com/facebookresearch/BenchMARL/pull/216">this PR</a>.</p> </blockquote>

<h3 id="results">Results</h3>

<p>You’ll find more detailed script launchers in the <code>launch</code> folder and in the commit history (I did quite a few tries). I was able to match results from the original paper <a href="#resources">@1</a>, and for some runs, get higher returns. I’ll try to make a clean report on wandb when I find some time, which will be <a href="https://wandb.ai/yp-edu/marl-cluster-training/reports/MARL-Cluster-Training--VmlldzoxMzQzMjU0Mg">here</a>.</p>

<p>Some considerations if you want to go deeper. In this post, I only scratched the surface of the possibilities of BenchMARL. When using it to run pure experiments from YAML, you’ll find some limitations about customisation, which are totally overcome by manipulating the Python classes directly. One of the most powerful ways to customise <code>Experiment</code> is through callbacks that can enable parameter scheduling (e.g. for <code>lr</code> or <code>batch_size</code>) or add custom pre/post-processing.</p>

<blockquote class="callout tip"> <div class="callout-title"> <i class="fa-solid fa-fire-flame-curved" href="##"></i> <em> Further Customisation</em></div> <p>As you saw in the previous sections, BenchMARL is a really powerful tool to train MARL agents. However, there are still some things you might want to customise to fit your experiment’s needs. And the beauty of BenchMARL is that you can keep all the features you want and simply add your secret sauce, whether it’s an algorithm, an environment, or anything else.</p> </blockquote>

<h2 id="resources"># Resources</h2>

<p>To learn more about BenchMARL and MARL in general, here are some valuable resources:</p>

<ul>
  <li><a href="https://benchmarl.readthedocs.io/en/latest/">BenchMARL Documentation</a></li>
  <li><a href="https://github.com/facebookresearch/BenchMARL">BenchMARL GitHub Repository</a></li>
  <li><a href="https://pytorch.org/rl/">TorchRL Documentation</a></li>
  <li><a href="https://hydra.cc/">Hydra Configuration Framework</a></li>
</ul>

<p>BenchMARL’s Discord community is also a great place to ask questions and share experiences with other users. Also, feel free to open an issue or a PR if you want to add or suggest an edit.</p>

<blockquote class="callout quote"> <div class="callout-title"> <i class="fa-solid fa-quote-right" href="#"></i> <em> References</em></div> <ol>
    <li>Gupta, Jayesh K. et al. “Cooperative Multi-agent Control Using Deep Reinforcement Learning.” <em>AAMAS Workshops</em> (2017).</li>
  </ol> </blockquote>]]></content><author><name>[[Yoann Poupart]]</name></author><category term="MARL" /><category term="Cluster" /><summary type="html"><![CDATA[TL;DR> Let's dive into MARL training with BenchMARL and how to scale up your experiments to a cluster. We'll cover environment and model customisation, as well as how to run your experiments on a cluster even without internet access.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://yp-edu.github.io/assets/images/marl-cluster-training_thumbnail.png" /><media:content medium="image" url="https://yp-edu.github.io/assets/images/marl-cluster-training_thumbnail.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Projects</title><link href="https://yp-edu.github.io/projects/index" rel="alternate" type="text/html" title="Projects" /><published>2026-07-03T03:36:02+00:00</published><updated>2026-07-03T03:36:02+00:00</updated><id>https://yp-edu.github.io/projects/index</id><content type="html" xml:base="https://yp-edu.github.io/projects/index"><![CDATA[]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry></feed>