Подготовьте задание
- Создайте занятие формата «Консилиум» или выберите положение «Консилиум» в меню замка у отдельной ячейки. Замок виден, когда правило «Печатать в ячейках» или «Запускать код» оставлено преподавателю, например в формате «Лекция». В комнате, где всем можно и печатать, и запускать код, замка нет.
- В общих ячейках подготовьте данные, импорты и условие задачи.
- Выполните подготовительный код, чтобы необходимые значения появились в ядре.
- Откройте ячейку задания: каждый участник получит свой лист.
Формат «Консилиум» отличается от лекции действием открытия ячейки. Остальные права можно настроить отдельно.
Студенты отвечают отдельно
Ответы хранятся на сервере отдельно от общего документа. Студент пишет свой вариант (до 8 000 знаков) и нажимает «Сдать» или ⌘ / Ctrl + Shift + Enter. «Изменить» возвращает сданный ответ в работу, «Восстановить» возвращает лист к исходному заданию. До показа преподавателем участник видит только собственную работу, а преподаватель — и сданные ответы, и черновики. Если запуск попытки упал с ошибкой, студент может нажать «Подсказка оракула»: ответ увидят только он и преподаватель, а вопрос расходует лимит оракула.
Кто запускает попытки, задаёт регламент в пульте: «Только я» (по умолчанию), «Все по очереди» или «По просьбе». Общее правило «Запускать код» само не разрешает запуск попыток Консилиума. Во всех режимах попытки проходят по одной через ядро тетради — кроме консилиума на машинах, где они считаются одновременно, каждая в своём исполнителе.
Регламент ячейки
Правила Консилиума задаются для каждой ячейки в окне пульта. В его шапке они записаны одной строкой, например «Запускаю только я · каждый запуск до 30 с · на экране с именами». Чтобы изменить правило, нажмите «Регламент», любое значение в этой строке или клавишу G. Изменения действуют сразу, кнопки «Сохранить» нет.
| Правило | Варианты | Что оно означает |
|---|---|---|
| Кто запускает | «Только я» (по умолчанию), «Все по очереди», «По просьбе» | Студенты пишут код, а запускаете его вы; студент запускает свою попытку сам; студент просит, и вы разрешаете именно эту версию кода. При смене режима ожидающие просьбы снимаются. |
| Предел запуска | 5 с, 15 с, 30 с (по умолчанию), 1 мин, 5 мин, «Без предела» | Если запуск попытки длится дольше, сервер прерывает его, и очередь идёт дальше. Предел касается и ваших запусков. Новый предел сразу действует на ожидающие попытки и на идущую, считая от её начала. Под вариантами видно, сколько обычно длятся запуски этой ячейки. |
| Повтор запуска | «Сразу» (по умолчанию), 15 с, 30 с, 1 мин, 2 мин | Сколько студент ждёт после окончания своего запуска, прежде чем снова запустить или попросить запуск. На преподавателя пауза не действует. Если запускает только преподаватель, правило ни на что не влияет. |
| На экране класса | «С именами» (по умолчанию), «Без имён» | Без имён показанное решение подписано номером, например «Вариант 12», — одинаково в тетради у всех и на проекторе. |
У остановленного пределом запуска в выводе появляется строка «Остановлено: запуск шёл дольше …», а в пульте — отметка «Остановлен: дольше …». Прерывание останавливает не любой код. Если попытка в ядре тетради не останавливается, пульт напишет «не останавливается — прервите или перезапустите ядро»; у консилиума на машинах исполнитель такой попытки после второго прерывания перезапускается сам. Во время паузы студент видит вместо кнопки обратный отсчёт «Запуск через …».
У консилиума на машинах в регламенте ещё две строки: «Попытки считаются» — «Отдельно, параллельно» (по умолчанию) или «В общем ядре по очереди», и «Состояние для попыток» — «Следует за ядром» (по умолчанию) или «По кнопке».
Запуск по запросу
Студент нажимает «Запустить» (или Shift + Enter) у своего листа: в этом режиме нажатие отправляет просьбу преподавателю. Пока она ждёт, на месте кнопки стоит «В очереди» со ссылкой «отменить»; после отказа студент видит «Преподаватель не запустил. Можно нажать снова.» Запустить или попросить запуск можно только до сдачи: у сданного ответа кнопки запуска нет, она вернётся после «Изменить». Правка текста снимает прежний запрос: для новой версии нужно отправить новый.
В окне пульта просьбы собраны на вкладке «Очередь» в разделе «Ждут вашего разрешения»; рядом с вкладками появляется ссылка «N ждут разрешения →», а в списке работ такая работа помечена «Просит запуск». Проверьте код и нажмите «Разрешить» или «Отклонить»; «Разрешить все» одобряет все ожидающие просьбы разом. Разрешение ставит проверенную версию в общую очередь (у консилиума на машинах — в очередь пула); до разрешения код не запускается. Сам запрос не означает сдачу ответа. Смена режима запуска или закрытие Консилиума снимает запросы.
Разберите ответы с группой
Работы разбирают в отдельном окне пульта. Под ячейкой Консилиума у преподавателя остаются только счётчик («N сдали из M», черновики) и кнопка «Пульт». Окно не выводите на проектор: в нём имена, черновики и отметки. Если браузер не открыл окно, разрешите всплывающие окна для этой страницы. На вкладке «Работы» слева список сдач: свежие сверху, три и больше одинаковых ответа сворачиваются в группу. Справа открыта выбранная работа. Её можно запустить, отметить «Верно» или «Исправить», написать автору и нажать «Показать классу». Код появится подписанной плашкой под ячейкой у всех, а текст самой ячейки не изменится. «Убрать с экрана» снимает показ. Автор видит отметку («✓ Верно» или «✗ Есть ошибка») и ваши письма под своим листом. На вкладке «Оракул о классе» можно спросить оракула о классе — о задании, о том, кто застрял, и о самих решениях, — если оракул настроен.
Клавиши в окне пульта: J / K или ↓ / ↑ — следующая и предыдущая работа, Space — раскрыть группу, ⌘ / Ctrl + F — поиск по имени, Enter — показать классу, R — запустить, 1 — «Верно», 2 — «Исправить», 3 — убрать с экрана, ← / → — соседняя работа той же группы, G — регламент, ? — список всех клавиш.
Что именно остаётся общим
Личные копии получают таблицы и ряды pandas, массивы numpy, списки, словари, множества и кортежи, тензоры и модели PyTorch вместе с их оптимизаторами, разреженные матрицы SciPy, оценщики scikit-learn и объекты классов, объявленных в самой тетради. Тем же входом закрыты три способа кончить занятие всем сразу: exit() и quit() в попытке отказывают (в ядре Jupyter они завершают процесс, и переменные теряют все), os._exit() и os.abort() отказывают тоже (они кончают процесс ядра мгновенно, без всякой трассировки, и вместе с ним — переменные всей комнаты), а память попытки ограничена пределом контейнера, поэтому np.ones((40000, 40000)) падает с MemoryError на своей карточке, а не уносит ядро тетради. Это лежачий полицейский, а не песочница: ctypes, signal.raise_signal и перезагрузка os через importlib до процесса всё равно достают. Дочернему процессу после fork() остаётся настоящий os._exit, так что multiprocessing и joblib работают как прежде. Возвращается и состояние процесса: зерно случайных чисел, sys.stdout, sys.path, os.environ, встроенные имена, фильтры предупреждений, опции печати numpy и pandas, фигуры matplotlib.
Общими остаются файлы на диске, модули, которые попытка импортировала, объекты типов, которые не копируются (открытые файлы, генераторы, соединения), и объекты сверх бюджета копий. Потоки, запущенные попыткой, остановить нечем — их число попытка видит в своём выводе. О последних попытка читает в собственном выводе строкой вида «Переменная big (1,2 ГБ) слишком велика для личной копии — попытка работает с общей». Тогда, как и раньше, работайте с копией явно:
# df подготовлен преподавателем
local_df = df.copy()
local_df = local_df.dropna()
print(local_df.shape)У консилиума на машинах общего меньше. Файлы занятия попытке доступны только для чтения, а свои она пишет в личную папку; то, что не копируется, в снимок не попадает вовсе, и такие попытки считаются в ядре тетради; исполнитель, у которого попытка оставила потоки или процессы, перезапускается. Общими остаются импортированные модули и объекты сверх бюджета копий — и только между попытками одного исполнителя, пока он не перезапустится (не реже чем через 20 попыток).
Личные копии помогают провести занятие, но не создают техническую границу безопасности. Для независимого оценивания, защиты от вмешательства и проверки личности нужна отдельная система.
Консилиум на машинах
Обычно попытки идут по одной в ядре тетради, в одной очереди с ячейками преподавателя: при тридцати попытках по 2 секунды последний ждёт больше минуты, а долгая ячейка преподавателя держит всех. Если в «Где считается» у части «Консилиум» выбрано «На машинах» — так её ставят наборы «Консилиум и личные тетради — на машинах», «Всё на машинах» и «Всё на машине с картой», — попытки считаются отдельно и одновременно, в пуле исполнителей на машине, открытой занятиям. Попытка не стоит в очереди за вашими ячейками, чужих попыток ждёт, только когда заняты все исполнители, а очередь тетради ими не занята: свои ячейки можно спокойно выполнять и даже перезапустить ядро — консилиум продолжит работать. Без машин всё идёт по-старому.
Снимок тетради. Исполнители не видят ядро тетради напрямую. Ядро делает снимок своих переменных — «состояние для попыток»: таблицы и массивы, модели, функции и классы из тетради, импорты (по имени модуля), состояние генераторов случайных чисел, опции pandas и numpy, настройки matplotlib и текущую папку. Каждый исполнитель — обычное ядро Python — один раз загружает снимок и дальше считает попытки по одной. Большие числовые куски — столбцы таблиц, массивы, веса моделей — лежат в снимке отдельными файлами, названными по содержимому: на машину они едут один раз, при обновлении — только изменившиеся, а в памяти машины лежат один раз на всех исполнителей; остальное — строки, списки, небольшие объекты — каждый исполнитель держит у себя. Попытка получает свою копию только того, что меняет. Если тетрадь сама считается на машине пула, снимок никуда не едет. Иначе он едет через этот сервер со скоростью «Файлы занятий» и за одно обновление везёт не больше 256 МБ новых данных: первый снимок крупнее оставляет попытки в ядре тетради («Состояние тетради не помещается — попытки считаются в общем ядре.»), а при обновлении крупнее попытки остаются на прежнем снимке («Новое состояние не помещается — попытки на состоянии 14:05.»).
Когда снимок обновляется:
- когда вы открываете ячейку консилиума;
- кнопкой «Обновить» в полосе пула;
- сам, если в регламенте ячейки «Состояние для попыток» — «Следует за ядром» (так по умолчанию). Вы выполнили ячейку после снимка — следующая попытка сначала ждёт новый снимок, но не дольше 5 секунд от нажатия. Переснимается и едет на машину только изменившееся, поэтому небольшое изменение доезжает быстро, а крупное, если тетрадь считается не на машине пула, едет со скоростью «Файлы занятий». Не дождавшись, попытка считается на прежнем снимке, и её карточка говорит, каких ячеек в нём нет: «Состояние 14:05 — без ячеек 8–9.» Так же считается и попытка, нажатая во время долгой ячейки: снимок делается между вашими ячейками, а не посреди них. Если свободный исполнитель держит прежний снимок, он перед попыткой перезапускается и загружает новый — это время попытка тоже ждёт.
«По кнопке» — ячейка сама снимок не обновляет: её попытки не ждут нового снимка после ваших ячеек и считаются на последнем готовом. Но снимок у тетради один на все её ячейки консилиума, поэтому меняется он и по «Обновить», и когда открывают ячейку консилиума этой тетради, и когда его обновляет попытка другой открытой ячейки, которая следует за ядром; а пул снимает его и сам, когда у него не остаётся снимка, который можно взять. Перезапуск ядра тетради снимок не стирает: попытки считаются на прежнем, пока вы снова не выполните ячейки.
Что видит и меняет попытка.
- Переменные снимка — те, что были в ядре тетради в момент снимка, с тем же состоянием случайных чисел: одинаковые попытки печатают одинаковые числа. Свои переменные попытка меняет как хочет: после неё исполнитель возвращается к снимку, а в ядро тетради не возвращается ничего — испортить переменные преподавателя попытка не может.
- Файлы занятия — только для чтения: на машине лежит копия папки, свежая к началу попытки.
open('data.csv')читает файл занятия, аto_csv('out.csv')иsavefig('fig.png')пишут в личную папку попытки, так что тридцать попыток больше не перетирают одинout.csv. Перезаписать файл занятия нельзя:Read-only file system. - Свой процессор и свой предел памяти — «Память на попытку» в «Где считается», 1 ГБ по умолчанию; личные копии данных идут в тот же предел. Жадная попытка получает
MemoryErrorсама, а соседи и тетрадь этого не замечают.
Что возвращается. Вывод — текст, картинки, ошибки — приходит автору и в пульт, как прежде; «Верно», «Исправить» и «Показать классу» работают так же. Файлы, которые написала попытка, перечислены на её карточке с размерами: до 20 файлов, каждый до 1 МБ и всего до 5 МБ, сервер забирает с машины и хранит при попытке, остальные только названы. Пока файл едет с машины, рядом с ним написано «в пути»; доехавший файл скачивают с карточки (↓) — автор со своего листа, преподаватель из пульта; другим студентам и классу на экране файлы попытки не выдаются. Файл, который так и не доехал — машина пропала, агент его не отдал, — назван как не сохранённый. Общее состояние по-прежнему меняет только «В ячейку и запустить»: решение выполняется в ядре тетради по-настоящему, на его живых переменных, и его файлы ложатся в общую папку. Если ядро с момента снимка ушло вперёд, пульт предупредит: «Ядро ушло вперёд на 3 ячейки после снимка — результат в тетради может отличаться.» — в работе перед «В ячейку и запустить», а для попытки на более старом снимке — после нажатия, под строкой о том, что код в ячейке.
Что нельзя перенести. В снимок не попадают открытое соединение с базой, открытый файл, сетевое соединение, генератор, поток, блокировка, данные на видеокарте, всё остальное, что не копируется, и то, что в снимок не помещается: все его файлы вместе — до 2 ГБ, а один объект не из списка личных копий (например, таблица polars или бустер LightGBM) — до 64 МБ. Их список пульт показывает один раз: «Не переносится: conn (соединение с базой), batches (генератор).» Скрытый крестиком, он возвращается, только когда в списке появится новое имя или новая причина, — а не с каждым снимком. В тот же список попадают переменные, для которых на машине нет модуля: пакет поставлен только в ядро тетради (!pip install в ней), и исполнитель загрузить их не может — «model (модуль есть только в ядре тетради)». Попытку, которая читает такую переменную, исполнитель сам отправляет в ядро тетради — в общую очередь, на её место, — и на карточке написано почему: «Посчитано в общем ядре: использует conn — её нельзя перенести.» Если попытка добирается до такой переменной окольным путём — через функцию из тетради или globals()['conn'], — она получает понятную ошибку, а не чужие данные: «Переменная conn не перенесена на машину консилиума (соединение с базой), а эта попытка добралась до неё не напрямую. Попытки, в тексте которых есть conn, считаются в общем ядре.» Сама она в ядро тетради не переходит. Чтобы все попытки ячейки считались в ядре тетради, поставьте ей в регламенте «Попытки считаются» — «В общем ядре по очереди». Если в снимке есть данные на видеокарте, весь консилиум считается в ядре тетради: исполнителям видеокарта не достаётся.
Когда попытки считаются по-старому. В ядре тетради, в общей очереди, попытка считается, если у ячейки «Попытки считаются» — «В общем ядре по очереди»; пока пул поднимается — исполнители запускаются и загружают снимок; если ни одна машина не может взять пул, машина пула не отвечает или пропала; если первый снимок не собрался или не помещается; если в снимке есть данные на видеокарте; и если попытка читает переменную, которую нельзя перенести. Обновление, которое не собралось или не поместилось, оставляет попытки на прежнем снимке, их карточки говорят, каких ячеек в нём нет, а полоса пула — почему: «Новое состояние не собралось — попытки на состоянии 14:05.» или «Новое состояние не помещается — попытки на состоянии 14:05.» Карточка попытки, посчитанной в ядре тетради, говорит почему — например «Пул ещё поднимается — эта попытка посчитана в общем ядре.» Если пул не поднялся, пульт пишет «Пул не поднялся: …» с причиной: сразу, когда ни один исполнитель не запустился или не загрузил снимок — например, ни одна машина не может взять пул («Пул не поднялся: Пул консилиума бывает только на машинах.»), и тогда то же пишут карточки, — а иначе через минуту после первой попытки, не заставшей пул готовым, и тогда после двоеточия сказано, чего пул ещё ждёт: «машина пула не отвечает», «состояние тетради ещё не дошло до машины», «исполнители ещё не загрузили состояние тетради» или слова машины о запуске контейнера («Запускаем ядро на машине «vast-3»… образ ↓ 45 %»). Кнопки «Обновить» в полосе тогда нет: новый снимок пул не поднимет.
Сбои.
- Машина пула пропала посреди попытки — попытка один раз пересчитывается в ядре тетради, на своём месте в очереди, а пауза между запусками студенту не засчитывается: «Машина пропала посреди попытки — пересчитано в общем ядре. Пауза между запусками не засчитана.» Ждавшие попытки уходят туда же. Если тетради класса считаются на той же машине — а пул встаёт к ним первым, так что при «Всё на машинах» и «Всё на машине с картой» обычно так и есть, — пропадает и ядро тетради: переменные преподавателя теряются, как при любом переезде, эти попытки считаются уже в новом ядре тетради, без них, а пул начинает со снимка нового ядра. Выполните подготовительные ячейки снова (если у ячейки «По кнопке» — и нажмите «Обновить»).
- Попытка не останавливается по пределу запуска — после второго прерывания исполнитель сам перезапускается и загружает снимок заново; ядро тетради и его очередь это не трогает. Так же пул перезапускает исполнителя, которому попытка оставила потоки, процессы или больше памяти, чем «Память на попытку», и того, чей Python упал; пульт минуту пишет в полосе «исполнитель перезапущен».
- Ядро тетради перезапустилось — исполнители считают дальше на своём снимке; пропало вместе со своей машиной — тоже, если пул стоит на другой машине.
- Сервер перезапустился посреди попыток — каждая из них получает «Сервер перезапустился во время выполнения попытки. Вывод недоступен.», а исполнители перезапускаются, прежде чем считать снова.
Сколько исполнителей — «Попыток одновременно» в «Где считается». «Авто» — от 2 до 16: при открытии до четырёх, а пока попытки ждут, пул растёт — до одного исполнителя на трёх человек в комнате и сколько поместится в памяти машины; с пустой очередью он сокращается. Своё число — до 32, например по исполнителю на студента. Лишние попытки ждут в очереди пула, и номер в ней виден как раньше; ваши запуски идут в ней впереди попыток студентов. Через 15 минут после того, как закрыта последняя ячейка консилиума в тетради, пул останавливается.
В пульте у такого консилиума есть полоса пула: сколько исполнителей готовы, сколько считают и сколько попыток ждут (пока пул поднимается — «готовы 2 из 4»), на какой машине пул, на какой момент снимок и следует ли он за ядром, и кнопка «Обновить»; пока снимок переснимается, там написано «снимок состояния». Во вкладке «Очередь» снимок стоит текущим запуском очереди тетради — сколько он уже снимается и после какой вашей ячейки («снимок состояния · 0,4 с», «после вашей ячейки 9»), — а в листе «Регламент» сказано, сколько весит снимок и сколько переснимался последний раз («Сейчас: состояние 14:05 · снимок 1,2 ГБ, переснимается за 0,4 с»). Время в полосе и на карточках — по часам браузера. Если контейнер ядра тетради запущен до 0.25.0, без PYTHONHASHSEED класса, пульт один раз скажет: «Порядок множеств в попытках может отличаться от тетради: её ядро запущено до обновления.» Попытку, ждущую исполнителя, снимают с очереди из меню «⋯» её работы. На карточке закончившейся попытки — у автора и в пульте — под выводом сказано, где и на каком снимке она посчитана, например «посчитано отдельно · состояние 14:05» или «в общем ядре: использует conn» (целиком — в подсказке при наведении), и перечислены её файлы: «файлы попытки: out.csv 12 КБ».
Удобный сценарий на занятии
Начните с короткого вопроса, дайте время на индивидуальный ответ, выберите два разных подхода и покажите их по очереди. Затем верните объяснение в общую тетрадь и сохраните итоговый корректный вариант. Публикация тетради не является выгрузкой всех личных ответов.