2
$\begingroup$

I have a bash script that I would like to parellelize to run on multiple nodes. My goal is to run my python sample_script.py script on pairwise comparisons of samples to see if their variants are a match or not (output is written by echo for the function so we can compare results). I unfortunately can’t post the contents of the Python script, but it is basically comparing germline variants between samples to determine if a sample is a match or not. I’m trying to figure out how to make the script run faster or run multiple pair wise comparisons at once then concatenate results. The samples I'm comparing are listed in the array. Running the script on multiple samples takes too long, so I'd like to qsub the process or run the script on multiple nodes, then concatenate the results. Can someone advise on the best way to tackle this? I don't have much experience with jobs using qsub. Should I consider an SGE array job and if so how would I go about this?

Example code below using bash within jupyter notebook:

%%bash
array=( A037375001 A037646101 A037710601 )
for i in "${array[@]}"
      do 
        for j in "${array[@]}"
        do
        python3 sample_script.py --run-sample-id-A "$i" --run-sample-id-B "$j"
        echo run-sample-id-A "$i" run-sample-id-B "$j"
      done
    done > "/outputdir/out.csv"

Thank you in advance!

$\endgroup$

4 Answers 4

5
$\begingroup$

What's best is quite subjective. My preference would be use Nextflow to abstract away the underlying job submission system and then use the sge executor to submit the jobs to the cluster. The only issue I see, is that it looks like you're feeding in a list of sample IDs, which your script presumably uses to find some VCF files containing the variants. If the script could be refactored to accept file inputs, we can have Nextflow localize them into its working directory. This would ensure your workflow (however trivial) is portable and could be run on AWS Batch, for example. The following example uses the combine operator to create the cartesian product and the collectFile operator to gather the results. With the following in a file called main.nf:

params.vcf_files = './path/to/*.vcf'


process compare_variants {

    input:
    tuple(
        path(sample_A_vcf, stageAs: 'A/*'),
        path(sample_B_vcf, stageAs: 'B/*'),
    ) 

    output:
    stdout

    """
    your_script.py \\
        --sample-vcf-A "${sample_A_vcf}" \\
        --sample-vcf-B "${sample_B_vcf}" 
    """
}

workflow {
     
     samples = Channel.fromPath( params.vcf_files )
     product = samples.combine(samples)

     compare_variants( product )
         .collectFile( name: 'results.txt' )
         .view { "Saved results to: $it" }
}

And the following in a file called nextflow.config:

process {

  executor = 'sge'
}

And your Python script in a folder called 'bin' in the root directory of your project repository. Nextflow automatically adds this folder to the $PATH in the execution environment. So make it executable, for example using chmod +x your_script.py. Then, with Nextflow installed give it a test:

$ nextflow run main.nf
N E X T F L O W  ~  version 22.10.7
Launching `main.nf` [chaotic_watson] DSL2 - revision: ac224d863b
executor >  local (484)
[bc/a39b27] process > compare_variants (482) [100%] 484 of 484 ✔
Saved results to: /path/to/work/tmp/3e/95330f32edec3f109b197bc836b2ca/results.txt

$\endgroup$
4
  • $\begingroup$ Thanks Steve! I'm going to try using nextflow. What is stdout referring to? I am getting an error message in my log that this is an unknown variable. $\endgroup$
    – che625
    Apr 3, 2023 at 18:10
  • $\begingroup$ Also, where would I enter in my array info? e.g. the sample IDs? $\endgroup$
    – che625
    Apr 3, 2023 at 18:12
  • $\begingroup$ Steve, I am getting the error message "No such variable: stdout". Any suggestions? $\endgroup$
    – che625
    Apr 3, 2023 at 19:59
  • $\begingroup$ @che625 The above uses the stdout qualifier, but you could also have your script redirect stdout to a file and then use the path qualifier to output the file. You would obtain the sample IDs by either parsing the VCF filenames, or by parsing a CSV containing the sample IDs. For the latter, you could use the splitCsv operator. $\endgroup$
    – Steve
    Apr 3, 2023 at 23:10
2
$\begingroup$

Consider using a workflow language such as Snakemake, Nextflow or Cromwell to handle distributing the jobs for you. It will take a little more time investment upfront to set them up, but it will save you many hours and headaches later on.

$\endgroup$
1
2
$\begingroup$

You may use asub (array job submitter) for this. Modify your script to myscript.sh:

array=( A037375001 A037646101 A037710601 A037710701 A037753001 A037844701 A038153001 A038172201 A038184401 A038272502 A038298602 A038533901 A038797201 A038944801 A038973401 A039034701 A039048101 A039089601 A039164101 A039303301 A039323801 A039538301 )
for i in "${array[@]}"; do
  for j in "${array[@]}"; do
    echo "python3 sample_script.py --run-sample-id-A $i --run-sample-id-B $j > out-$i-$j.csv"
  done
done

Note that the main difference is this script prints command lines, not executing them. You then run asub with

bash myscript.sh | asub -q myqueue -j myjobname

asub will submit a job array. If you don't like asub, you may submit with

bash myscript.sh | xargs -i echo "echo '{}' | qsub -q myqueue" | sh

This will submit separate jobs, not a job array. It will be slower and less convenient but you don't need to install anything or learn anything.

With either approach, you need to run the following to collate all the results:

cat out-*-*.csv > our.csv

On a side note, this approach can be adapted for GNU parallel to spawn multiple processes on the same machine:

bash myscript.sh | parallel -j 16
$\endgroup$
5
  • $\begingroup$ GNU parallel can also send processes to multiple remote machines, it isn't only for parallelizing on a single machine. See gnu.org/software/parallel/… $\endgroup$
    – terdon
    Apr 3, 2023 at 10:17
  • $\begingroup$ I should clarify that I'm looking to parallelize across samples, so split up running a certain number of samples on different nodes and then concatenate the results. I think parallel will just run the same script on X nodes. Can anyone provide guidance on this? $\endgroup$
    – che625
    Apr 5, 2023 at 16:57
  • $\begingroup$ @che625 I know what you want to achieve. In my answer, parallel executes each line, in parallel, on the same node. I wouldn't recommend to use parallel across node. $\endgroup$
    – user172818
    Apr 5, 2023 at 21:56
  • $\begingroup$ So should I divide up my input by positions if I want to use parallel? $\endgroup$
    – che625
    Apr 6, 2023 at 16:31
  • $\begingroup$ @che625 I am not sure what you mean. Anyway, for your original question, just use the "parallel" command line I showed at the end. $\endgroup$
    – user172818
    Apr 6, 2023 at 22:01
0
$\begingroup$

Background Just for everyone else SGE = Sun Grid Engine and is the qsub system mentioned in the title of the question.

qsub:

If by parallelising you mean multithreading a single job across loads of cores then the first thing is how many cores per node are there?

Usually it is around 10 (but could be much more). That exact number is super important to know because if you request more cores than exist on a single node the job will never run. At best qsub will refuse the job, at worst it will forever be stuck in the queue. For multi-processing on qsub its ...

qsub -pe omp 8  myscript.sh

Here its requesting 8 cores and myscript.sh will contain the shell command to run the python script. If there's a node with 10-cores 2 are in use, it will then load the job to give its max. capacity.

Thus, qsub does not do course grain-parallelisation, that is MPI. Thus qsub will only parallelise across the cores in a single node. This is without question a limitation for cluster computing.

qsub is more than just submitting to a queue, you need to monitor the queuing system pre- and post- submission. Firstly, this is for the number of existing jobs across that system. Secondly, this is to see whats happening to your stuff.

qstat is the way to understand whats there:

qstat -f # whats what on the cluster
qstat -q

This lists all the queues.

qstat -u username # looks at what a given user is doing
qstat long-queue

Targets availability on a specific queue.

A complete list of qstat is here. Also qusage -l is useful.


"Parallelising" loops

If you are submitting loads of jobs which are all working independently in parallel it better to use a different strategy on qsub. It is better to submit each job separately to the queue via a qsub loop. Thus there would be two scripts the first is the script in the question, the second is a submission loop: this would comprise a qsub argument within a loop that would submit each job sequentially. Thus the array might be better in the qsub submission loop.

Monitoring the queue takes place as per normal via qstat.

Rationale

The reason for this is the way the queue works qsub prioritises single jobs over multi-threaded parallelisation, thus you get your results much quicker.

$\endgroup$
1
  • $\begingroup$ I should clarify that I'm looking to parallelize across samples, so split up running a certain number of samples on different nodes and then concatenate the results. I think parallel will just run the same script on X nodes. Can anyone provide guidance on this? $\endgroup$
    – che625
    Apr 5, 2023 at 16:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.