Content from Esecuzione di comandi con Snakemake
Ultimo aggiornamento il 2025-04-05 | Modifica questa pagina
Tempo stimato: 60 minuti
Panoramica
Domande
- “Come si esegue un semplice comando con Snakemake?”
Obiettivi
- “Creare una ricetta di Snakemake (uno Snakefile)”
Qual è il flusso di lavoro che mi interessa?
In questa lezione faremo un esperimento che prende un’applicazione che gira in parallelo e ne studia la scalabilità. Per farlo dovremo raccogliere dati, in questo caso significa eseguire l’applicazione più volte con un numero diverso di core della CPU e registrare il tempo di esecuzione. Una volta fatto questo, dobbiamo creare una visualizzazione dei dati per vedere come si confronta con il caso ideale.
Dalla visualizzazione possiamo decidere a quale scala ha più senso eseguire l’applicazione in produzione per massimizzare l’uso della CPU allocata nel sistema.
Potremmo fare tutto questo manualmente, ma ci sono strumenti utili per aiutarci a gestire le pipeline di analisi dei dati come quelle del nostro esperimento. Oggi ne conosceremo uno: Snakemake.
Per iniziare a usare Snakemake, iniziamo con un semplice comando e
vediamo come eseguirlo tramite Snakemake. Scegliamo il comando
hostname
che stampa il nome dell’host in cui viene eseguito
il comando:
OUTPUT
node1.int.jetstream2.hpc-carpentry.org
Questo stampa il risultato, ma Snakemake si affida ai file per conoscere lo stato del flusso di lavoro, quindi reindirizziamo l’output a un file:
Creazione di un file Snake
Modificare un nuovo file di testo chiamato
Snakefile
.
Contenuto di Snakefile
:
PYTHON
rule hostname_login:
output: "hostname_login.txt"
input:
shell:
"hostname > hostname_login.txt"
Punti chiave di questo file
- Il file si chiama
Snakefile
- con la maiuscolaS
e senza estensione. - Alcune righe sono rientrate. I rientri devono essere fatti con caratteri di spazio, non con tabulazioni. Si veda la sezione di impostazione per sapere come far sì che il proprio editor di testo faccia questo.
- La definizione della regola inizia con la parola chiave
rule
seguita dal nome della regola, poi da due punti. - Abbiamo chiamato la regola
hostname_login
. Si possono usare lettere, numeri o trattini bassi, ma il nome della regola deve iniziare con una lettera e non può essere una parola chiave. - Le parole chiave
input
,output
eshell
sono tutte seguite da due punti (“:”). - I nomi dei file e il comando di shell sono tutti in
"quotes"
. - Il nome del file di output viene dato prima del nome del file di input. In realtà, a Snakemake non interessa l’ordine in cui appaiono, ma in questo corso daremo prima l’output. Vedremo presto perché.
- In questo caso d’uso non c’è un file di input per il comando, quindi lo lasciamo vuoto.
Torniamo alla shell ed eseguiamo la nostra nuova regola. A questo punto, se ci sono virgolette mancanti, rientri errati e così via, potremmo vedere un errore.
bash: snakemake: command not found...
Se la shell vi dice che non riesce a trovare il comando
snakemake
, allora dobbiamo rendere il software disponibile
in qualche modo. Nel nostro caso, ciò significa cercare il modulo che
dobbiamo caricare:
OUTPUT
[ocaisa@node1 ~]$ module spider snakemake
--------------------------------------------------------------------------------------------------------
snakemake:
--------------------------------------------------------------------------------------------------------
Versions:
snakemake/8.2.1-foss-2023a
snakemake/8.2.1 (E)
Names marked by a trailing (E) are extensions provided by another module.
--------------------------------------------------------------------------------------------------------
For detailed information about a specific "snakemake" package (including how to load the modules) use the module's full name.
Note that names that have a trailing (E) are extensions provided by other modules.
For example:
$ module spider snakemake/8.2.1
--------------------------------------------------------------------------------------------------------
Ora vogliamo il modulo, quindi carichiamolo per rendere disponibile il pacchetto
e poi assicurarsi di avere a disposizione il comando
snakemake
OUTPUT
/cvmfs/software.eessi.io/host_injections/2023.06/software/linux/x86_64/amd/zen3/software/snakemake/8.2.1-foss-2023a/bin/snakemake
Esecuzione di Snakemake
Eseguire snakemake --help | less
per vedere la guida per
tutte le opzioni disponibili. Che cosa fa l’opzione -p
nel
comando snakemake
di cui sopra?
- protegge i file di output esistenti
- stampa i comandi di shell che vengono eseguiti sul terminale
- Indica a Snakemake di eseguire solo un processo alla volta
- chiede all’utente il file di input corretto
È possibile cercare nel testo premendo /, e tornare alla shell con q.
- Stampa i comandi di shell che vengono eseguiti sul terminale
Questa è una cosa così utile che non sappiamo perché non sia
l’opzione predefinita! L’opzione -j1
indica a Snakemake di
eseguire solo un processo alla volta, e per ora ci atterremo a questa
opzione perché rende le cose più semplici. La risposta 4 è un’assurdità,
poiché Snakemake non richiede mai un input interattivo da parte
dell’utente.
Punti Chiave
- “Prima di eseguire Snakemake è necessario scrivere uno Snakefile”
- “Uno Snakefile è un file di testo che definisce un elenco di regole”
- “Le regole hanno ingressi, uscite e comandi di shell da eseguire”
- “Si dice a Snakemake quale file creare e questo esegue il comando di shell definito nella regola appropriata”
Content from Esecuzione di Snakemake sul cluster
Ultimo aggiornamento il 2025-04-05 | Modifica questa pagina
Tempo stimato: 50 minuti
Panoramica
Domande
- “Come si esegue la mia regola di Snakemake nel cluster?”
Obiettivi
- “Definire le regole da eseguire localmente e sul cluster”
Cosa succede quando vogliamo che la nostra regola venga eseguita sul cluster piuttosto che sul nodo di accesso? Il cluster che stiamo usando usa Slurm e si dà il caso che Snakemake abbia un supporto integrato per Slurm, dobbiamo solo dirgli che vogliamo usarlo.
Snakemake utilizza l’opzione executor
per consentire di
selezionare il plugin che si desidera eseguire la regola. Il modo più
rapido per applicarlo al proprio file Snake è definirlo sulla riga di
comando. Proviamo
OUTPUT
Building DAG of jobs...
Retrieving input from storage.
Nothing to be done (all requested files are present and up to date).
Non è successo nulla! Perché no? Quando gli viene chiesto di costruire un target, Snakemake controlla il “tempo di ultima modifica” del target e delle sue dipendenze. Se una dipendenza è stata aggiornata dopo il target, le azioni vengono rieseguite per aggiornare il target. Utilizzando questo approccio, Snakemake sa che deve ricostruire solo i file che, direttamente o indirettamente, dipendono dal file che è stato modificato. Questo è chiamato costruzione incrementale.
Le build incrementali migliorano l’efficienza
Ricostruendo i file solo quando necessario, Snakemake rende l’elaborazione più efficiente.
In esecuzione sul cluster
Ora abbiamo bisogno di un’altra regola che esegua la
hostname
sul cluster. Creare una nuova regola nel
file Snake e provare a eseguirla sul cluster con le opzioni da
--executor slurm
a snakemake
.
La regola è quasi identica alla regola precedente, tranne che per il nome della regola e il file di output:
PYTHON
rule hostname_remote:
output: "hostname_remote.txt"
input:
shell:
"hostname > hostname_remote.txt"
Si può quindi eseguire la regola con
OUTPUT
Building DAG of jobs...
Retrieving input from storage.
Using shell: /cvmfs/software.eessi.io/versions/2023.06/compat/linux/x86_64/bin/bash
Provided remote nodes: 1
Job stats:
job count
--------------- -------
hostname_remote 1
total 1
Select jobs to execute...
Execute 1 jobs...
[Mon Jan 29 18:03:46 2024]
rule hostname_remote:
output: hostname_remote.txt
jobid: 0
reason: Missing output files: hostname_remote.txt
resources: tmpdir=<TBD>
hostname > hostname_remote.txt
No SLURM account given, trying to guess.
Guessed SLURM account: def-users
No wall time information given. This might or might not work on your cluster.
If not, specify the resource runtime in your rule or as a reasonable default
via --default-resources. No job memory information ('mem_mb' or
'mem_mb_per_cpu') is given - submitting without.
This might or might not work on your cluster.
Job 0 has been submitted with SLURM jobid 326 (log: /home/ocaisa/.snakemake/slurm_logs/rule_hostname_remote/326.log).
[Mon Jan 29 18:04:26 2024]
Finished job 0.
1 of 1 steps (100%) done
Complete log: .snakemake/log/2024-01-29T180346.788174.snakemake.log
Notate tutti gli avvertimenti che Snakemake ci dà sul fatto che la
regola potrebbe non essere in grado di essere eseguita sul nostro
cluster, perché forse non abbiamo fornito informazioni sufficienti.
Fortunatamente per noi, questa regola funziona sul nostro cluster e
possiamo dare un’occhiata al file di output creato dalla nuova regola,
hostname_remote.txt
:
OUTPUT
tmpnode1.int.jetstream2.hpc-carpentry.org
Profilo di Snakemake
L’adattamento di Snakemake a un particolare ambiente può comportare molti flag e opzioni. Pertanto, è possibile specificare un profilo di configurazione da utilizzare per ottenere le opzioni predefinite. Questo profilo si presenta come
La cartella del profilo deve contenere un file chiamato
config.yaml
che è quello che memorizzerà le nostre opzioni.
La cartella può contenere anche altri file necessari per il profilo.
Creiamo il file cluster_profile/config.yaml
e inseriamo
alcune delle nostre opzioni esistenti:
Ora dovremmo essere in grado di rilanciare il nostro flusso di lavoro
puntando al profilo invece di elencare le opzioni. Per forzare
l’esecuzione del nostro flusso di lavoro, dobbiamo prima rimuovere il
file di output hostname_remote.txt
e poi possiamo provare
il nostro nuovo profilo
BASH
[ocaisa@node1 ~]$ rm hostname_remote.txt
[ocaisa@node1 ~]$ snakemake --profile cluster_profile hostname_remote
Il profilo è estremamente utile nel contesto del nostro cluster, poiché l’esecutore Slurm ha molte opzioni e a volte è necessario usarle per poter inviare lavori al cluster a cui si ha accesso. Sfortunatamente, i nomi delle opzioni in Snakemake non sono esattamente gli stessi di Slurm, quindi abbiamo bisogno dell’aiuto di una tabella di traduzione:
SLURM | Snakemake | Description |
---|---|---|
--partition |
slurm_partition |
the partition a rule/job is to use |
--time |
runtime |
the walltime per job in minutes |
--constraint |
constraint |
may hold features on some clusters |
--mem |
mem, mem_mb |
memory a cluster node must |
provide (mem: string with unit), mem_mb: int | ||
--mem-per-cpu |
mem_mb_per_cpu |
memory per reserved CPU |
--ntasks |
tasks |
number of concurrent tasks / ranks |
--cpus-per-task |
cpus_per_task |
number of cpus per task (in case of SMP, rather use
threads ) |
--nodes |
nodes |
number of nodes |
Le avvertenze fornite da Snakemake suggeriscono che potrebbe essere
necessario fornire queste opzioni. Un modo per farlo è fornirle come
parte della regola di Snakemake usando la parola chiave
resources
, ad esempio,
e possiamo anche usare il profilo per definire valori predefiniti per
queste opzioni da usare con il nostro progetto, usando la parola chiave
default-resources
. Ad esempio, la memoria disponibile sul
nostro cluster è di circa 4 GB per core, quindi possiamo aggiungerla al
nostro profilo:
Sfida
Sappiamo che il nostro problema viene eseguito in un tempo molto breve. Cambiare la durata predefinita dei nostri lavori a due minuti per Slurm.
Esistono varie opzioni sbatch
non supportate
direttamente dalle definizioni delle risorse nella tabella precedente. È
possibile utilizzare la risorsa slurm_extra
per specificare
uno qualsiasi di questi flag aggiuntivi a sbatch
:
Esecuzione di regole locali
La nostra regola iniziale era di ottenere il nome host del nodo di accesso. Vogliamo sempre eseguire la regola sul nodo di accesso perché abbia senso. Se diciamo a Snakemake di eseguire tutte le regole tramite l’esecutore Slurm (cosa che stiamo facendo tramite il nostro nuovo profilo), questo non accadrà più. Quindi come si fa a forzare l’esecuzione della regola sul nodo di login?
Beh, nel caso in cui una regola di Snakemake esegua un compito
banale, l’invio di un lavoro potrebbe essere eccessivo (ad esempio, meno
di un minuto di tempo di calcolo). Come nel nostro caso, sarebbe meglio
che queste regole venissero eseguite localmente (cioè dove viene
eseguito il comando snakemake
) invece che come lavoro.
Snakemake consente di indicare quali regole devono essere sempre
eseguite localmente con la parola chiave localrules
.
Definiamo hostname_login
come regola locale vicino alla
parte superiore del nostro Snakefile
.
Punti Chiave
- “Snakemake genera e invia i propri script batch per lo scheduler”
- “È possibile memorizzare le impostazioni di configurazione predefinite in un profilo Snakemake”
- “
localrules
definisce le regole che vengono eseguite localmente e non vengono mai inviate a un cluster”
Content from Segnaposto
Ultimo aggiornamento il 2025-04-05 | Modifica questa pagina
Tempo stimato: 70 minuti
Panoramica
Domande
- “Come si crea una regola generica?”
Obiettivi
- “Vedere come Snakemake gestisce alcuni errori”
Il nostro Snakefile presenta alcune duplicazioni. Per esempio, i nomi dei file di testo sono ripetuti in alcuni punti delle regole dello Snakefile. Gli Snakefile sono una forma di codice e, in qualsiasi codice, la ripetizione può portare a problemi (ad esempio, rinominiamo un file di dati in una parte dello Snakefile, ma dimentichiamo di rinominarlo altrove).
D.R.Y. (Don’t Repeat Yourself)
In molti linguaggi di programmazione, la maggior parte delle caratteristiche del linguaggio serve a consentire al programmatore di descrivere lunghe routine di calcolo come codice breve, espressivo e bello. Le caratteristiche di Python, R o Java, come le variabili e le funzioni definite dall’utente, sono utili in parte perché ci evitano di dover scrivere (o pensare) tutti i dettagli più volte. Questa buona abitudine di scrivere le cose solo una volta è nota come principio “Non ripetere te stesso” o D.R.Y.
Rimuoviamo alcune ripetizioni dal nostro Snakefile.
Segnaposto
Per creare una regola più generica abbiamo bisogno di placeholder. Vediamo l’aspetto di un segnaposto
Come promemoria, ecco la versione precedente dell’ultimo episodio:
PYTHON
rule hostname_remote:
output: "hostname_remote.txt"
input:
shell:
"hostname > hostname_remote.txt"
La nuova regola ha sostituito i nomi di file espliciti con cose in
{curly brackets}
, in particolare {output}
(ma
avrebbe potuto essere anche {input}
… se avesse avuto un
valore e fosse stato utile).
{input}
e {output}
sono dei
segnaposto
I segnaposto sono usati nella sezione shell
di una
regola e Snakemake li sostituisce con valori appropriati -
{input}
con il nome completo del file di input e
{output}
con il nome completo del file di output - prima di
eseguire il comando.
anche {resources}
è un segnaposto e si può accedere a un
elemento nominato di {resources}
con la notazione
{resources.runtime}
(per esempio).
Punti Chiave
- “Le regole di Snakemake sono rese più generiche con i segnaposto”
- “I segnaposto nella parte shell della regola vengono sostituiti con valori basati sui caratteri jolly scelti”
Content from Applicazioni MPI e Snakemake
Ultimo aggiornamento il 2025-04-05 | Modifica questa pagina
Tempo stimato: 50 minuti
Panoramica
Domande
- “Come si esegue un’applicazione MPI tramite Snakemake sul cluster?”
Obiettivi
- “Definire le regole da eseguire localmente e sul cluster”
Ora è il momento di tornare al nostro flusso di lavoro reale.
Possiamo eseguire un comando sul cluster, ma che dire dell’esecuzione
dell’applicazione MPI che ci interessa? La nostra applicazione si chiama
amdahl
ed è disponibile come modulo d’ambiente.
Sfida
Individuare e caricare il modulo amdahl
e quindi
sostituire la nostra regola hostname_remote
con
una versione che esegue amdahl
. (Non preoccupatevi ancora
dell’MPI parallelo, ma eseguitelo con una sola CPU,
mpiexec -n 1 amdahl
).
La regola viene eseguita correttamente? In caso contrario, cercare nei file di log per scoprirne il motivo?
individuerà e caricherà il modulo amdahl
. Possiamo
quindi aggiornare/sostituire la nostra regola per eseguire
l’applicazione amdahl
:
Tuttavia, quando si tenta di eseguire la regola si ottiene un errore
(a meno che non sia già disponibile una versione diversa di
amdahl
nel proprio percorso). Snakemake riporta la
posizione dei log e se guardiamo all’interno possiamo (alla fine)
trovare
OUTPUT
...
mpiexec -n 1 amdahl > amdahl_run.txt
--------------------------------------------------------------------------
mpiexec was unable to find the specified executable file, and therefore
did not launch the job. This error was first reported for process
rank 0; it may have occurred for other processes as well.
NOTE: A common cause for this error is misspelling a mpiexec command
line parameter option (remember that mpiexec interprets the first
unrecognized command line token as the executable).
Node: tmpnode1
Executable: amdahl
--------------------------------------------------------------------------
...
Quindi, anche se abbiamo caricato il modulo prima di eseguire il flusso di lavoro, la nostra regola Snakemake non ha trovato l’eseguibile. Questo perché la regola Snakemake viene eseguita in un ambiente runtime pulito e dobbiamo dirgli in qualche modo di caricare il modulo ambiente necessario prima di provare a eseguire la regola.
Snakemake e moduli di ambiente
La nostra applicazione si chiama amdahl
ed è disponibile
sul sistema tramite un modulo d’ambiente, quindi dobbiamo dire a
Snakemake di caricare il modulo prima di provare a eseguire la regola.
Snakemake conosce i moduli d’ambiente, che possono essere specificati
tramite (un’altra) opzione:
PYTHON
rule amdahl_run:
output: "amdahl_run.txt"
input:
envmodules:
"mpi4py",
"amdahl"
input:
shell:
"mpiexec -n 1 amdahl > {output}"
L’aggiunta di queste righe non è tuttavia sufficiente a far eseguire la regola. Non solo bisogna dire a Snakemake quali moduli caricare, ma bisogna anche dirgli di usare i moduli d’ambiente in generale (poiché si ritiene che l’uso dei moduli d’ambiente renda l’ambiente di runtime meno riproducibile, dato che i moduli disponibili possono differire da cluster a cluster). Per questo è necessario dare a Snakemake un’opzione aggiuntiva
Sfida
Utilizzeremo i moduli d’ambiente per tutto il resto del tutorial,
quindi rendiamola un’opzione predefinita del nostro profilo (impostando
il suo valore a True
)
Snakemake e MPI
Nell’ultima sezione non abbiamo eseguito un’applicazione MPI, in quanto abbiamo eseguito solo su un core. Come si fa a richiedere l’esecuzione su più core per una singola regola?
Snakemake ha un supporto generale per MPI, ma l’unico esecutore che attualmente supporta esplicitamente MPI è l’esecutore Slurm (per nostra fortuna!). Se guardiamo alla nostra tabella di traduzione da Slurm a Snakemake, notiamo che le opzioni rilevanti appaiono in fondo:
SLURM | Snakemake | Description |
---|---|---|
… | … | … |
--ntasks |
tasks |
number of concurrent tasks / ranks |
--cpus-per-task |
cpus_per_task |
number of cpus per task (in case of SMP, rather use
threads ) |
--nodes |
nodes |
number of nodes |
L’opzione che ci interessa è tasks
, poiché aumenteremo
solo il numero di ranghi. Possiamo definirli in una sezione
resources
della nostra regola e fare riferimento ad essi
usando dei segnaposto:
PYTHON
rule amdahl_run:
output: "amdahl_run.txt"
input:
envmodules:
"amdahl"
resources:
mpi='mpiexec',
tasks=2
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
Questo ha funzionato, ma ora abbiamo un piccolo problema. Vogliamo
farlo per alcuni valori diversi di tasks
, il che significa
che abbiamo bisogno di un file di output diverso per ogni esecuzione.
Sarebbe fantastico se potessimo indicare in qualche modo nel file
output
il valore che vogliamo usare per tasks
…
e far sì che Snakemake lo prenda.
Potremmo usare un wildcard nel output
per
permetterci di definire il tasks
che vogliamo usare. La
sintassi di questo carattere jolly è la seguente
dove parallel_tasks
è il nostro carattere jolly.
Caratteri jolly
I caratteri jolly sono usati nelle righe input
e
output
della regola per rappresentare parti di nomi di
file. Come lo schema *
nella shell, il carattere jolly può
sostituire qualsiasi testo per comporre il nome del file desiderato.
Come per la denominazione delle regole, si può scegliere un nome a
piacere per i caratteri jolly, qui abbiamo usato
parallel_tasks
. L’uso degli stessi caratteri jolly
nell’input e nell’output indica a Snakemake come abbinare i file di
input a quelli di output.
Se due regole utilizzano un carattere jolly con lo stesso nome, Snakemake le tratterà come entità diverse - le regole in Snakemake sono autocontenute in questo modo.
Nella riga shell
si può fare riferimento al carattere
jolly con {wildcards.parallel_tasks}
Ordine delle operazioni di Snakemake
Abbiamo appena iniziato con alcune semplici regole, ma vale la pena di pensare a cosa fa esattamente Snakemake quando lo si esegue. Ci sono tre fasi distinte:
- Prepara l’esecuzione:
- Legge tutte le definizioni delle regole dal file Snakefile
- Pianifica cosa fare:
- vede quale/i file si sta chiedendo di creare
- Cerca una regola corrispondente guardando le
output
di tutte le regole che conosce - Compila i caratteri jolly per ottenere il valore
input
per questa regola - verifica che questo file di input (se richiesto) sia effettivamente disponibile
- Esegue i passi:
- Crea la directory per il file di output, se necessario
- Rimuove il vecchio file di output, se è già presente
- Solo allora, esegue il comando di shell con i segnaposto sostituiti
- controlla che il comando sia stato eseguito senza errori e che il nuovo file di output sia stato creato come previsto
La quantità di controlli può sembrare pedante in questo momento, ma con l’aumentare dei passi del flusso di lavoro questo diventerà davvero molto utile.
Usando i caratteri jolly nella nostra regola
Vorremmo usare un carattere jolly nel output
per
permetterci di definire il numero di tasks
che vogliamo
usare. Sulla base di quanto visto finora, si potrebbe immaginare che
questo potrebbe essere simile a
PYTHON
rule amdahl_run:
output: "amdahl_run_{parallel_tasks}.txt"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
tasks="{parallel_tasks}"
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
ma ci sono due problemi:
- L’unico modo per Snakemake di conoscere il valore del carattere jolly è che l’utente richieda esplicitamente un file di output concreto (invece di chiamare la regola):
questo è perfettamente valido, poiché Snakemake è in grado di capire che ha una regola che può corrispondere a quel nome di file.
-
Il problema maggiore è che anche così non funziona, sembra che non si possa usare un carattere jolly per
tasks
:OUTPUT
WorkflowError: SLURM job submission failed. The error message was sbatch: error: Invalid numeric value "{parallel_tasks}" for --ntasks.
Purtroppo per noi, non c’è un modo diretto per accedere ai caratteri
jolly per tasks
. Il motivo è che Snakemake cerca di usare
il valore di tasks
durante la fase di inizializzazione,
quindi prima di conoscere il valore del carattere jolly. È necessario
rimandare la determinazione di tasks
a un momento
successivo. Questo si può ottenere specificando una funzione di input
invece di un valore per questo scenario. La soluzione è quindi quella di
scrivere una funzione da usare una sola volta per manipolare Snakemake
in modo che lo faccia per noi. Poiché la funzione è specifica per la
regola, possiamo usare una funzione di una riga senza nome. Questo tipo
di funzioni sono chiamate funzioni anonime o funzioni lamdba (entrambe
hanno lo stesso significato) e sono una caratteristica di Python (e di
altri linguaggi di programmazione).
Per definire una funzione lambda in python, la sintassi generale è la seguente:
Poiché la nostra funzione può accettare i caratteri jolly come
argomenti, possiamo usarli per impostare il valore di
tasks
:
PYTHON
rule amdahl_run:
output: "amdahl_run_{parallel_tasks}.txt"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No direct way to access the wildcard in tasks, so we need to do this
# indirectly by declaring a short function that takes the wildcards as an
# argument
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
Ora abbiamo una regola che può essere usata per generare output da esecuzioni di un numero arbitrario di task paralleli.
Commenti in Snakefiles
Nel codice precedente, la riga che inizia con #
è una
riga di commento. Si spera che abbiate già l’abitudine di aggiungere
commenti ai vostri script. Un buon commento rende qualsiasi script più
leggibile, e questo vale anche per Snakefiles.
Poiché la nostra regola è ora in grado di generare un numero
arbitrario di file di output, le cose potrebbero diventare molto
affollate nella nostra directory corrente. Probabilmente è meglio
mettere le esecuzioni in una cartella separata per mantenere l’ordine.
Possiamo aggiungere la cartella direttamente al nostro
output
e Snakemake si occuperà della creazione della
directory per noi:
PYTHON
rule amdahl_run:
output: "runs/amdahl_run_{parallel_tasks}.txt"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No direct way to access the wildcard in tasks, so we need to do this
# indirectly by declaring a short function that takes the wildcards as an
# argument
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
Sfida
Creare un file di output (sotto la cartella runs
) per il
caso in cui si abbiano 6 task paralleli
(SUGGERIMENTO: ricordate che Snakemake deve essere in grado di far
corrispondere il file richiesto al output
di una
regola)
Un’altra cosa della nostra applicazione amdahl
è che
alla fine vogliamo elaborare l’output per generare il nostro grafico in
scala. L’output in questo momento è utile per la lettura, ma rende più
difficile l’elaborazione. in amdahl
c’è un’opzione che ci
facilita questo compito. Per vedere le opzioni di amdahl
si
può usare
OUTPUT
usage: amdahl [-h] [-p [PARALLEL_PROPORTION]] [-w [WORK_SECONDS]] [-t] [-e]
options:
-h, --help show this help message and exit
-p [PARALLEL_PROPORTION], --parallel-proportion [PARALLEL_PROPORTION]
Parallel proportion should be a float between 0 and 1
-w [WORK_SECONDS], --work-seconds [WORK_SECONDS]
Total seconds of workload, should be an integer greater than 0
-t, --terse Enable terse output
-e, --exact Disable random jitter
L’opzione che stiamo cercando è --terse
, che farà
stampare l’output di amdahl
in un formato molto più facile
da elaborare, JSON. Il formato JSON in un file usa tipicamente
l’estensione .json
, quindi aggiungiamo questa opzione al
nostro comando shell
e cambiamo il formato del file
output
per adattarlo al nostro nuovo comando:
PYTHON
rule amdahl_run:
output: "runs/amdahl_run_{parallel_tasks}.json"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No direct way to access the wildcard in tasks, so we need to do this
# indirectly by declaring a short function that takes the wildcards as an
# argument
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl --terse > {output}"
C’era un altro parametro per amdahl
che ha attirato la
mia attenzione. amdahl
ha un’opzione
--parallel-proportion
(o -p
) che potremmo
essere interessati a cambiare perché modifica il comportamento del
codice e quindi ha un impatto sui valori che otteniamo nei nostri
risultati. Aggiungiamo un altro livello di directory al nostro formato
di output per riflettere una scelta particolare per questo valore.
Possiamo usare un carattere jolly in modo da non dover scegliere subito
il valore:
PYTHON
rule amdahl_run:
output: "p_{parallel_proportion}/runs/amdahl_run_{parallel_tasks}.json"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No direct way to access the wildcard in tasks, so we need to do this
# indirectly by declaring a short function that takes the wildcards as an
# argument
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl --terse -p {wildcards.parallel_proportion} > {output}"
Sfida
Crea un file di output per un valore di -p
di 0,999 (il
valore predefinito è 0,8) per il caso in cui abbiamo 6 task
paralleli.
Punti Chiave
- “Snakemake sceglie la regola appropriata sostituendo i caratteri jolly in modo che l’output corrisponda all’obiettivo”
- “Snakemake controlla varie condizioni di errore e si ferma se vede un problema”
Content from Concatenazione di regole
Ultimo aggiornamento il 2025-04-05 | Modifica questa pagina
Tempo stimato: 70 minuti
Panoramica
Domande
- “Come si combinano le regole in un flusso di lavoro?”
- “Come faccio a creare una regola con più ingressi e uscite?”
Obiettivi
- “”
Una pipeline di regole multiple
Ora abbiamo una regola che può generare output per qualsiasi valore
di -p
e per qualsiasi numero di task, dobbiamo solo
chiamare Snakemake con i parametri che vogliamo:
Questo però non è esattamente comodo, per generare un set di dati completo dobbiamo eseguire Snakemake molte volte con diversi target di file di output. Creiamo invece una regola che generi questi file per noi.
Il concatenamento delle regole in Snakemake è una questione di scelta di modelli di nomi di file che collegano le regole. Si tratta di un’arte: nella maggior parte dei casi ci sono diverse opzioni che funzionano:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: "p_{parallel_proportion}/runs/amdahl_run_6.json"
shell:
"echo {input} done > {output}"
Sfida
La nuova regola non fa nulla, si assicura solo che venga creato il file desiderato. Non vale la pena eseguirla sul cluster. Come assicurarsi che venga eseguita solo sul nodo di accesso?
Ora eseguiamo la nuova regola (ricordiamo che dobbiamo richiedere il
file di output per nome, poiché il output
nella nostra
regola contiene un pattern jolly):
OUTPUT
Using profile cluster_profile/ for setting default command line arguments.
Building DAG of jobs...
Retrieving input from storage.
Using shell: /cvmfs/software.eessi.io/versions/2023.06/compat/linux/x86_64/bin/bash
Provided remote nodes: 3
Job stats:
job count
------------------ -------
amdahl_run 1
generate_run_files 1
total 2
Select jobs to execute...
Execute 1 jobs...
[Tue Jan 30 17:39:29 2024]
rule amdahl_run:
output: p_0.999/runs/amdahl_run_6.json
jobid: 1
reason: Missing output files: p_0.999/runs/amdahl_run_6.json
wildcards: parallel_proportion=0.999, parallel_tasks=6
resources: mem_mb=1000, mem_mib=954, disk_mb=1000, disk_mib=954,
tmpdir=<TBD>, mem_mb_per_cpu=3600, runtime=2, mpi=mpiexec, tasks=6
mpiexec -n 6 amdahl --terse -p 0.999 > p_0.999/runs/amdahl_run_6.json
No SLURM account given, trying to guess.
Guessed SLURM account: def-users
Job 1 has been submitted with SLURM jobid 342 (log: /home/ocaisa/.snakemake/slurm_logs/rule_amdahl_run/342.log).
[Tue Jan 30 17:47:31 2024]
Finished job 1.
1 of 2 steps (50%) done
Select jobs to execute...
Execute 1 jobs...
[Tue Jan 30 17:47:31 2024]
localrule generate_run_files:
input: p_0.999/runs/amdahl_run_6.json
output: p_0.999_runs.txt
jobid: 0
reason: Missing output files: p_0.999_runs.txt;
Input files updated by another job: p_0.999/runs/amdahl_run_6.json
wildcards: parallel_proportion=0.999
resources: mem_mb=1000, mem_mib=954, disk_mb=1000, disk_mib=954,
tmpdir=/tmp, mem_mb_per_cpu=3600, runtime=2
echo p_0.999/runs/amdahl_run_6.json done > p_0.999_runs.txt
[Tue Jan 30 17:47:31 2024]
Finished job 0.
2 of 2 steps (100%) done
Complete log: .snakemake/log/2024-01-30T173929.781106.snakemake.log
Osservate i messaggi di log che Snakemake stampa nel terminale. Che cosa è successo?
- Snakemake cerca una regola per creare
p_0.999_runs.txt
- Determina che “generate_run_files” può fare questo se
parallel_proportion=0.999
- Vede che l’input necessario è quindi
p_0.999/runs/amdahl_run_6.json
- Snakemake cerca una regola per creare
p_0.999/runs/amdahl_run_6.json
- Determina che “amdahl_run” può fare questo se
parallel_proportion=0.999
eparallel_tasks=6
- Ora Snakemake ha raggiunto un file di input disponibile (in questo caso, non è richiesto alcun file di input), esegue entrambi i passaggi per ottenere l’output finale
Questo, in breve, è il modo in cui costruiamo i flussi di lavoro in Snakemake.
- Definizione delle regole per tutte le fasi di elaborazione
- Scegliere modelli di denominazione
input
eoutput
che consentano a Snakemake di collegare le regole - Indica a Snakemake di generare i file di output finali
Se si è abituati a scrivere script regolari, ci vuole un po’ di tempo per abituarsi. Invece di elencare i passi in ordine di esecuzione, si lavora sempre a ritroso** dal risultato finale desiderato. L’ordine delle operazioni è determinato dall’applicazione delle regole di corrispondenza dei pattern ai nomi dei file, non dall’ordine delle regole nel file Snake.
Prima le uscite?
L’approccio di Snakemake di lavorare a ritroso partendo dall’output
desiderato per determinare il flusso di lavoro è il motivo per cui
mettiamo le righe output
per prime in tutte le nostre
regole - per ricordarci che sono queste le prime cose che Snakemake
guarda!
Molti utenti di Snakemake, e in effetti la documentazione ufficiale,
preferiscono avere il input
per primo, quindi in pratica si
dovrebbe usare l’ordine che si ritiene più opportuno.
Uscite log
in Snakemake
Snakemake ha un campo di regole dedicato per le uscite che sono file
di log, e queste sono trattate per lo più come uscite normali,
tranne per il fatto che i file di log non vengono rimossi se il lavoro
produce un errore. Ciò significa che è possibile consultare il log per
diagnosticare l’errore. In un flusso di lavoro reale questo può essere
molto utile, ma in termini di apprendimento dei fondamenti di Snakemake
ci atterremo ai normali campi input
e
output
.
Gli errori sono normali
Non scoraggiatevi se vedete degli errori quando testate per la prima volta le vostre nuove pipeline di Snakemake. Ci sono molte cose che possono andare storte quando si scrive un nuovo flusso di lavoro e di solito sono necessarie diverse iterazioni per ottenere le cose giuste. Un vantaggio dell’approccio di Snakemake rispetto ai normali script è che Snakemake fallisce rapidamente quando c’è un problema, invece di continuare e potenzialmente eseguire calcoli inutili su dati parziali o corrotti. Un altro vantaggio è che quando un passo fallisce si può riprendere tranquillamente da dove si era interrotto.
Punti Chiave
- “Snakemake collega le regole cercando iterativamente le regole che hanno ingressi mancanti”
- “Le regole possono avere più ingressi e/o uscite con nome”
- “Se un comando di shell non produce l’output previsto, Snakemake lo considererà un fallimento”
Content from Elaborazione di elenchi di input
Ultimo aggiornamento il 2025-04-05 | Modifica questa pagina
Tempo stimato: 80 minuti
Panoramica
Domande
- “Come posso elaborare più file contemporaneamente?”
- “Come si combinano più file insieme?”
Obiettivi
- “Usare Snakemake per elaborare tutti i campioni in una volta sola”
- “Creare un grafico di scalabilità che riunisca i nostri risultati”
Abbiamo creato una regola che può generare un singolo file di output, ma non abbiamo intenzione di creare più regole per ogni file di output. Vogliamo generare tutti i file di esecuzione con una sola regola, se possibile, ma Snakemake può accettare un elenco di file di input:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: "p_{parallel_proportion}/runs/amdahl_run_2.json", "p_{parallel_proportion}/runs/amdahl_run_6.json"
shell:
"echo {input} done > {output}"
È fantastico, ma non vogliamo dover elencare singolarmente tutti i file che ci interessano. Come possiamo fare?
Definizione di un elenco di campioni da elaborare
Per fare questo, possiamo definire alcuni elenchi come variabili globali di Snakemake.
Le variabili globali devono essere aggiunte prima delle regole nel file Snake.
- A differenza di quanto avviene con le variabili negli script di
shell, possiamo mettere degli spazi intorno al segno
=
, ma non sono obbligatori. - Gli elenchi di stringhe quotate sono racchiusi tra parentesi quadre e separati da virgole. Se conoscete un po’ di Python, riconoscerete che si tratta della sintassi delle liste di Python.
- Una buona convenzione è quella di usare nomi in maiuscolo per queste variabili, ma non è obbligatorio.
- Sebbene questi elenchi siano definiti variabili, non è possibile modificare i valori una volta che il flusso di lavoro è in esecuzione, quindi gli elenchi definiti in questo modo sono più simili a costanti.
Utilizzo di una regola di Snakemake per definire un gruppo di uscite
Ora aggiorniamo il nostro Snakefile per sfruttare la nuova variabile globale e creare un elenco di file:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: expand("p_{{parallel_proportion}}/runs/amdahl_run_{count}.json", count=NTASK_SIZES)
shell:
"echo {input} done > {output}"
La funzione expand(...)
di questa regola genera un
elenco di nomi di file, prendendo la prima cosa tra le parentesi singole
come modello e sostituendo {count}
con tutti gli
NTASK_SIZES
. Poiché ci sono 5 elementi nell’elenco, si
otterranno 5 file che vogliamo creare. Si noti che abbiamo dovuto
proteggere il nostro carattere jolly in una seconda serie di parentesi,
in modo che non venisse interpretato come qualcosa che doveva essere
espanso.
Nel nostro caso attuale ci basiamo ancora sul nome del file per
definire il valore del carattere jolly parallel_proportion
,
quindi non possiamo chiamare direttamente la regola, ma dobbiamo
richiedere un file specifico:
Se non si specifica un nome di regola di destinazione o un nome di file sulla riga di comando quando si esegue Snakemake, l’impostazione predefinita è di usare la prima regola nel file Snake come destinazione.
Regole come target
Dare il nome di una regola a Snakemake sulla riga di comando funziona solo se quella regola non ha parole jolly in uscita, perché Snakemake non ha modo di sapere quali potrebbero essere le parole jolly desiderate. Verrà visualizzato l’errore “Le regole di destinazione non possono contenere caratteri jolly” Questo può accadere anche quando non si fornisce alcun target esplicito sulla riga di comando e Snakemake cerca di eseguire la prima regola definita nel file Snake.
Regole che combinano più input
La nostra regola generate_run_files
è una regola che
accetta un elenco di file di input. La lunghezza di tale elenco non è
fissata dalla regola, ma può cambiare in base a
NTASK_SIZES
.
Nel nostro flusso di lavoro il passo finale consiste nel prendere
tutti i file generati e combinarli in una trama. Per fare questo, forse
avete sentito che alcune persone usano una libreria python chiamata
matplotlib
. Non è compito di questo tutorial scrivere lo
script python per creare un grafico finale, quindi vi forniremo lo
script come parte di questa lezione. È possibile scaricarlo con
Lo script plot_terse_amdahl_results.py
ha bisogno di una
riga di comando che assomiglia a:
BASH
python plot_terse_amdahl_results.py --output <output image filename> <1st input file> <2nd input file> ...
Introduciamo questa funzione nella nostra regola
generate_run_files
:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: expand("p_{{parallel_proportion}}/runs/amdahl_run_{count}.json", count=NTASK_SIZES)
shell:
"python plot_terse_amdahl_results.py --output {output} {input}"
Sfida
Questo script si basa su matplotlib
, è disponibile come
modulo d’ambiente? Aggiungere questo requisito alla regola.
Ora finalmente possiamo generare un grafico in scala! Eseguire il comando finale di Snakemake:
Sfida
Generare il grafico della scalabilità per tutti i valori da 1 a 10 core.
Sfida
Eseguire nuovamente il flusso di lavoro per un valore p
di 0,8
Bonus round
Creare una regola finale che possa essere richiamata direttamente e
che generi un grafico in scala per 3 diversi valori di
p
.
Punti Chiave
- “Utilizzare la funzione
expand()
per generare elenchi di nomi di file da combinare” - “Ogni
{input}
a una regola può essere un elenco di lunghezza variabile”