Как смоделировать дерево вычислений с помощью Slurm? - PullRequest
0 голосов
/ 02 ноября 2019

У меня есть симуляция, которая состоит из N шагов, запускаемых последовательно. Каждый из этих шагов изменяет глобальное состояние в памяти до последнего шага, который является результатом. После выполнения шага можно записать на диск промежуточное состояние, которое этот шаг только что рассчитал, и загрузить такое промежуточное состояние вместо того, чтобы начинать с нуля. Написание и загрузка промежуточных состояний требует немалых затрат.

Я хочу запустить много вариантов симуляции на кластере Slurm. Каждый вариант будет изменять параметр некоторых шагов.

Пример

Шаг моделирования

S1 --> S2 --> S3 --> S4

Вариации

run1: S2.speed=2, S3.height=12
run2: S2.speed=2, S3.height=20
run3: S2.speed=2, S3.height=40
run4: S2.speed=5, S3.height=12
run5: S2.speed=5, S3.height=80

Что я хочуdo предназначен для различных прогонов для совместного использования общих вычислений путем сброса промежуточного состояния общих шагов. Это сформирует дерево этапов:

S1
├─ S2 (speed=2)
│  ├─ S3 (height=12)
│  │  └─ S4
│  ├─ S3 (height=20)
│  │  └─ S4
│  └─ S3 (height=40)
│     └─ S4
└─ S2 (speed=5)
   ├─ S3 (height=12)
   │  └─ S4
   └─ S3 (height=80)
      └─ S4

Я знаю, что могу получить результат 5 запусков, запустив 5 процессов:

run1: S1 --> S2 (speed=2) --> S3 (height=12) --> S4
run2: (dump of run1.S2) --> S3 (height=20) --> S4
run3: (dump of run1.S2) --> S3 (height=40) --> S4
run4: (dump of run1.S1) --> S2 (speed=5) --> S3 (height=12) --> S4
run5: (dump of run4.S2) --> S3 (height=80) --> S4

Это уменьшает вычисления с 20 шаговиспользуя наивный подход, до 13 шагов с 3 дампами и 4 загрузками.

Теперь мой вопрос: как смоделировать это с помощью Slurm, чтобы наилучшим образом использовать планировщик?

Одно решениеЯ могу думать о том, что каждый прогон отвечает за передачу заданий тех прогонов, которые зависят от него, после сброса промежуточного состояния. Run1 отправит run4 после сброса S1, а затем отправит run2 и run3 после сброса S2, а run4 отправит run5 после сброса S2. С этим решением, есть ли смысл объявлять зависимость при отправке задания в Slurm?

Еще одно решение, которое я вижу, - разорвать длинные цепочки вычислений в нескольких зависимых заданиях. Список работ для отправки и их зависимостей будет в основном тем деревом, которое я нарисовал выше (за исключением пар S3 / S4, которые будут объединены в одной и той же работе). Это 8 заданий вместо 5, но я могу отправить их сразу с самого начала, с правильными зависимостями. Тем не менее, я не уверен, каковы будут преимущества этого подхода. Будет ли Slurm лучше работать в качестве планировщика, если он знает полный список заданий и их зависимостей с самого начала? Есть ли какие-то преимущества с точки зрения пользователя, чтобы все задания были отправлены и связаны с зависимостями (например, чтобы отменить все задания, которые зависят от корневого задания)? Я знаю, что могу отправлять много заданий одновременно с массивом заданий, но я не вижу способа объявить зависимости между заданиями одного и того же массива. Возможно или даже желательно?

Наконец, есть ли другие подходы, о которых я не задумывался?

Edit

Пример, который я привел, конечно, сильно упрощен. Реальные симуляции будут содержать сотни шагов, около тысячи вариантов. Масштабируемость выбранного решения важна.

Ответы [ 2 ]

2 голосов
/ 06 ноября 2019

Другим возможным решением является использование инструментов конвейера. В области биоинформатики SnakeMake становится действительно популярным. SnakeMake основан на GNU Make, но сделан на Python, отсюда и название SnakeMake. Для работы SnakeMake вы указываете, какой выход вы хотите, и SnakeMake определит, какой rules он должен запустить для этого вывода. Одна из приятных особенностей SnakeMake заключается в том, что он очень легко масштабируется от персональных ноутбуков до более крупных компьютеров и даже кластеров (например, кластеров с зернистостью). Ваш пример будет выглядеть примерно так:

rule all:
    input:
        ['S4_speed_2_height_12.out',
         'S4_speed_2_height_20.out',
         'S4_speed_2_height_40.out',
         'S4_speed_5_height_12.out',
         'S4_speed_5_height_80.out']

rule S1:
    output:
        "S1.out"
    shell:
        "touch {output}"  # do your heavy computations here

rule S2:
    input:
        "S1.out"
    output:
        "S2_speed_{speed}.out"
    shell:
        "touch {output}"

rule S3:
    input:
        "S2_speed_{speed}.out"
    output:
        "S3_speed_{speed}_height_{height}.out"
    shell:
        "touch {output}"

rule S4:
    input:
        "S3_speed_{speed}_height_{height}.out"
    output:
        "S4_speed_{speed}_height_{height}.out"
    shell:
        "touch {output}"

Затем мы можем попросить snakemake создать красивое изображение того, как он будет выполнять эти вычисления: enter image description here

Snakemake автоматически определяет, какие выходные данные могут быть использованы по разным правилам.

Выполнить это на локальном компьютере так же просто, как выполнить snakemake, а отправить действия в slurm - просто snakemake --cluster "sbatch". Приведенный мною пример явно упрощен, но SnakeMake обладает широкими возможностями настройки (количество потоков на правило, использование памяти и т. Д.) И имеет преимущество в том, что он основан на Python. Нужно немного разобраться, как все работает в SnakeMake, но я определенно рекомендую это.

2 голосов
/ 05 ноября 2019

Одно решение, о котором я могу подумать, заключается в том, что каждый прогон отвечает за отправку заданий зависимых от него прогонов после сброса промежуточного состояния. С этим решением, есть ли смысл объявлять зависимость при отправке задания в Slurm?

Этот подход часто сопровождается простыми рабочими процессами, которые включают длительные задания, которые должны проверяться и перезапускаться.

Другое решение, которое я вижу, это разорвать длинные цепочки вычислений в нескольких зависимых заданиях. Будет ли Slurm лучше работать в качестве планировщика, если он знает полный список заданий и их зависимостей с самого начала?

Нет. Slurm будет просто игнорировать задания, которые не могут быть запущены, потому что их зависимые задания еще не завершены.

Есть ли некоторые преимущества с точки зрения пользователя, чтобы все задания отправлялись и были связаны с зависимостями(например, отменить все задания, которые зависят от корневого задания)?

Да, но это незначительно полезно.

Я знаю, что могу отправить сразу несколько заданий с помощью массива заданий, но не вижу способа объявитьзависимости между заданиями одного массива. Возможно или даже желательно?

Нет, вы не можете установить зависимости между заданиями одного и того же массива.

Наконец, есть ли другие подходы, о которых я не думал?

Вы можете использовать систему управления рабочим процессом .

Одним из самых простых решений является Makeflow . Он использует файлы, которые выглядят как классические Makefiles, которые описывают зависимости между заданиями. Затем просто запустите что-то вроде makeflow –T slurm makefile.mf

Другой вариант - Bosco . Bosco предлагает немного больше возможностей и подходит для личного использования. Он прост в настройке и может отправлять задания в несколько кластеров.

Наконец, Fireworks - очень мощное решение. Для него требуется MongoDB, и он больше подходит для лабораторного использования, но он может реализовать очень сложную логику для отправки / повторной передачи задания на основе результатов задания и может обрабатывать ошибки умным способом. Например, вы можете реализовать рабочий процесс, в котором задание отправляется с заданным значением для заданного параметра, и Fireworks контролирует сходимость на основе выходного файла, а также отменяет и повторно отправляет другое значение в случае, если конвергенция не является удовлетворительной.

...