Scheduler
mlumina.scheduler
Отдельный FastAPI-сервис — приоритетная очередь задач поверх Ray Jobs API. Не заменяет ray.tune/ray.train — решает, когда и в каком порядке отправлять уже готовые job-скрипты в Ray-кластер, с учётом заявленных ресурсов и приоритета.
В Docker-стеке (docker-compose.yml) это сервис scheduler: собирается из корневого Dockerfile, зависит от head, слушает порт 8264, точка входа — python /home/ray/src/mlumina/scheduler/main.py.
import mlumina.scheduler.main имеет побочные эффекты: на уровне модуля создаётся Scheduler() (что сразу пытается подключиться к Ray-кластеру через JobSubmissionClient и ray.init()) и вызывается scheduler.start() (блокирующий запуск uvicorn на 0.0.0.0:8264). Импортировать этот модуль просто "для класса" нельзя — он запустит сервер.
Как это работает
Scheduler.__init__подключается к Ray dashboard (RAY_DASHBOARD_ADDRESS, по умолчаниюhttp://head:8265) черезJobSubmissionClientи к самому кластеру (RAY_CLUSTER_ADDRESS, по умолчаниюray://head:10001) черезray.init().- Задачи хранятся в
priority_queue: {0: [...], 1: [...], ..., 9: [...]}(9 — наивысший приоритет). - Фоновый поток (
_main, цикл раз вsleep_time=1сек, подthreading.Condition) на каждой итерации:- проверяет статус запущенных задач через
ray_client.get_job_status(завершённые/упавшие — убирает изrunning_tasks, логирует, дляFAILEDдополнительно подтягиваетget_job_logs); - отменяет задачи, превысившие свой
timeout; - пытается запустить из очереди задачи с наивысшим непустым приоритетом, если под них хватает незанятых ресурсов кластера (
_check_resources_by_allocation— сравнивает уже выделенное текущим задачам сray.cluster_resources(); учитываютсяcpu/gpu/memory, GPU-квота не проверяется на приём в очередь методом_add_task_to_queue, но участвует в допуске к запуску); - пишет снепшот использования ресурсов в
src/mlumina/scheduler/data/resource_history.jsonl(файл пересоздаётся пустым при каждом старте сервиса).
- проверяет статус запущенных задач через
- Задача с
cpu == 0 and gpu == 0 and memory == 0отклоняется сразу ("task must request at least one resource"), как и задача, запрашивающая больше ресурсов, чем есть во всём кластере (ray.cluster_resources()).
HTTP API (порт 8264)
| Метод | Путь | Описание |
|---|---|---|
| GET | / |
HTML-дашборд (utils/index.html) |
| POST | /add_task |
Поставить задачу в очередь (тело — TaskData, см. ниже) |
| POST | /change_task_priority?task_id=...&set_priority=... |
Изменить приоритет задачи в очереди (0–9) |
| POST | /cancel_task?task_id=... |
Отменить задачу (в очереди или уже запущенную — для запущенной вызывает ray_client.stop_job) |
| GET | /get_task_queue |
Текущая очередь по приоритетам |
| GET | /get_running_tasks_list |
Список запущенных задач |
| GET | /get_resources_status |
Текущее использование/выделение ресурсов кластера |
| GET | /get_resource_history |
История снепшотов ресурсов (накопленная с момента старта сервиса) |
TaskData (тело POST /add_task)
class TaskData(BaseModel):
name: str
entrypoint: str # команда запуска, например "python ray_wrapper/examples/fcnn/tune.py"
runtime_env: dict # ray runtime_env: working_dir, pip, env_vars и т.д.
priority: int # 0..9, больше — выше приоритет
cpu: int # >= 0
gpu: int # >= 0
memory: int # >= 0, в ГБ
timeout: int # >= 0, секунды; 0 = без таймаута
Пример вызова:
import requests
response = requests.post(
"http://127.0.0.1:8264/add_task",
json={
"name": "my_job",
"entrypoint": "python examples/scheduler/test.py",
"runtime_env": {"working_dir": "/home/ray/src/mlumina"},
"priority": 1,
"cpu": 2,
"gpu": 0,
"memory": 4,
"timeout": 300,
},
timeout=60,
)
print(response.status_code, response.json())
entrypoint выполняется в рабочей директории runtime_env["working_dir"] — это путь внутри контейнера, где запущен scheduler (в docker-compose это /home/ray/src, с примонтированным ./src), а не на машине, откуда отправляется HTTP-запрос. Больше примеров вызова — src/mlumina/ray_wrapper/examples/*/schedule_*.py и examples/scheduler/test.py (тривиальная тестовая задача-заглушка).
Идентификатор задачи (Task.id) генерируется как mlumina-{name}-priority-{priority}-{cpu}cpu-{gpu}gpu-{memory}memory-{uuid4} и используется и как ключ внутри планировщика, и как submission_id в Ray Jobs API — по нему же нужно передавать task_id в /change_task_priority и /cancel_task.