"""The Task interface — bring your own model + data. A DaisyChain task is any object with three methods: build_model() -> torch.nn.Module # the model to train (identical on every node) sample(n) -> (X, y) # draw n training samples (this node's shard) loss(model, X, y) -> scalar tensor # mean loss over the batch Point DaisyChain at your task with DAISY_TASK="my_module:MyTask" (or --task). An example lives in examples/example_task.py. Keep build_model deterministic (seed it) so every node starts from the same weights. """ from __future__ import annotations import importlib from typing import Protocol, Tuple import torch class Task(Protocol): def build_model(self) -> torch.nn.Module: ... def sample(self, n: int) -> Tuple[torch.Tensor, torch.Tensor]: ... def loss(self, model: torch.nn.Module, X: torch.Tensor, y: torch.Tensor) -> torch.Tensor: ... def load_task(spec: str): """spec = 'package.module:ClassName' -> instantiated task object.""" if ":" not in spec: raise ValueError(f"task spec must be 'module:Class', got {spec!r}") mod_name, cls_name = spec.split(":", 1) mod = importlib.import_module(mod_name) return getattr(mod, cls_name)()