"""Compose a pipeline config from independent parts: Δ = PostTrain(M, D_train, D_eval; θ_method). Each fixed term is a TOML fragment under configs/: - models/.toml: [model] - suites/.toml: [suite], one sealed held-out suite; its task list lives in fixture/task-lists/ - methods/.toml: every other pipeline table (runtime, harness, evaluation, sft, grpo, ...) Every fragment has a [meta] table the pipeline never sees. D_train is the submission. A run config is exactly one model + one method + one or more suites + the submission, so adding a model, suite or recipe is adding a file. Not every combination makes a sensible challenge (a 64K recipe on one A100, or a suite listed twice); serving() rejects layouts that cannot run, and configs/challenges/ names the combinations that are open. """ import copy import tomllib from pathlib import Path ROOT = Path(__file__).parent CONFIGS = ROOT / 'configs' TASK_LISTS = ROOT / 'fixture' / 'task-lists' KINDS = ('models', 'suites', 'methods') FILLED = ('model', 'train_dataset', 'eval_dataset', 'eval_suites', 'output') # tables the composer owns def fragment(kind, fragment_id): if kind not in KINDS: raise KeyError(kind) path = CONFIGS / kind / f'{fragment_id}.toml' if not path.is_file(): raise KeyError(f'unknown {kind[:-1]} {fragment_id!r}') return tomllib.loads(path.read_text()) def ids(kind): return sorted(p.stem for p in (CONFIGS / kind).glob('*.toml')) def task_ids(suite_id): suite = fragment('suites', suite_id)['suite'] return [line.strip() for line in (TASK_LISTS / suite['task_list']).read_text().splitlines() if line.strip() and not line.startswith('#')] def task_domains(suite_id): """{task: domain} for a suite whose [meta] names a domains file beside its task list (every task of the list must have one), else {}. A sealed suite has none: its per-task facts stay private.""" name = fragment('suites', suite_id).get('meta', {}).get('domains') if not name: return {} import json mapping = json.loads((TASK_LISTS / name).read_text()) missing = [t for t in task_ids(suite_id) if not mapping.get(t)] if missing: raise ValueError(f'suite {suite_id!r}: no domain for {len(missing)} task(s), e.g. {missing[0]!r}') return {t: mapping[t] for t in task_ids(suite_id)} def domain_counts(suite_id): """[{name, task_count}] for a suite's domains, most tasks first (then by name); [] without a domains file.""" counts = {} for d in task_domains(suite_id).values(): counts[d] = counts.get(d, 0) + 1 return [{'name': d, 'task_count': n} for d, n in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))] def registry(kind): """Public view of every fragment of one kind, for the API and the dashboard.""" rows = [] for fragment_id in ids(kind): data = fragment(kind, fragment_id); meta = data.get('meta', {}) if kind == 'models': rows.append({'id': fragment_id, 'repo_id': data['model']['id'], 'revision': data['model']['revision'], **meta}) elif kind == 'suites': s = data['suite'] rows.append({'id': fragment_id, 'repo_id': s['repo_id'], 'revision': s['revision'], 'task_count': len(task_ids(fragment_id)), **meta, 'default': bool(meta.get('default')), 'domains': domain_counts(fragment_id), 'suite_name': s['name']}) else: grpo, group = data.get('grpo', {}), data.get('runtime', {}).get('num_generations') batch = grpo.get('generation_batch_size') rows.append({'id': fragment_id, 'steps': grpo.get('max_steps'), 'group_size': group, 'tasks_per_step': batch // group if batch and group else None, 'learning_rate': grpo.get('learning_rate'), 'trials': data.get('evaluation', {}).get('trials', 1) if data.get('evaluation') else None, 'agent_timeout_sec': data.get('harness', {}).get('agent_timeout_sec'), 'infra_error_fraction': data.get('harness', {}).get('max_infra_error_fraction', 0.1) if data.get('harness') else None, # 0.1 is the pipeline's default **meta}) return sorted(rows, key=lambda r: (r.get('status') != 'active', r['id'])) def compose(model_id, method_id, suite_ids, train, *, project, task_list_root='../../task-lists/'): """The pipeline config for one run. One suite is written as the legacy [eval_dataset] table, which every pinned pipeline reads; several are written as [[eval_suites]], which needs a method whose pipeline supports it (meta.multi_suite).""" model, method = fragment('models', model_id), fragment('methods', method_id) tables = {k: copy.deepcopy(v) for k, v in method.items() if k != 'meta' and k not in FILLED} if not tables: raise ValueError(f'method {method_id!r} has no pipeline tables yet') if not suite_ids: raise ValueError('at least one held-out suite is required') suites = [fragment('suites', s)['suite'] for s in suite_ids] if len({s['name'] for s in suites}) != len(suites): raise ValueError('held-out suite names must be unique') data = {'model': dict(model['model']), 'train_dataset': dict(train)} listed = lambda s: {'repo_id': s['repo_id'], 'revision': s['revision'], 'path': s.get('path', ''), 'task_list': task_list_root + s['task_list']} if len(suites) == 1: data['eval_dataset'] = listed(suites[0]) else: if not method.get('meta', {}).get('multi_suite'): raise ValueError(f'method {method_id!r} evaluates a single suite') data['eval_suites'] = [{'name': s['name'], **listed(s)} for s in suites] data.update(tables) data.setdefault('tracking', {})['project'] = project data['output'] = {'root': '../../runs'} return data # GPUs per HF job flavor this arena may launch on; add a flavor here before a model fragment uses it. GPUS = {'a100-large': 1, 'a100x4': 4, 'a100x8': 8, 'h200': 1, 'h200x2': 2, 'h200x4': 4, 'h200x8': 8, 'l40sx1': 1, 'l40sx4': 4, 'l40sx8': 8} def devices(value, what): """A CUDA device list ("6,7") as distinct integers; refuses blanks, non-numbers and repeats.""" parts = [p.strip() for p in str(value).split(',')] if not parts or not all(p.isdigit() for p in parts): raise ValueError(f'{what} must list GPU indices like "6,7", got {value!r}') found = [int(p) for p in parts] if len(set(found)) != len(found): raise ValueError(f'{what} lists a GPU twice: {value!r}') return found def serving(model_id, method_id, layout=None): """Job layout for one model and recipe: hardware flavor, vLLM GPUs and tensor parallelism, trainer GPUs, and the context caps (vLLM max_model_len, the bridge's trim and logprob limits). Read from [meta.serving] and checked so a bad layout fails when the challenge loads, never after compute is reserved. ``layout``: a challenge's own hardware ([compute.layout]: flavor, vllm_gpus, tensor_parallel, trainer_gpus) over the model fragment's.""" model, method = fragment('models', model_id), fragment('methods', method_id) hw, ctx = {**model.get('meta', {}).get('serving', {}), **(layout or {})}, dict(method.get('meta', {}).get('serving', {})) missing = [k for k in ('flavor', 'vllm_gpus', 'tensor_parallel', 'trainer_gpus') if k not in hw] + [k for k in ('max_model_len', 'bridge_max_context', 'bridge_max_logprob_context') if k not in ctx] if missing: raise ValueError(f'serving layout incomplete for {model_id} x {method_id}: {", ".join(missing)}') if hw['flavor'] not in GPUS: raise ValueError(f'unknown hardware flavor {hw["flavor"]!r}; known: {", ".join(GPUS)}') vllm, trainer, count = devices(hw['vllm_gpus'], 'vllm_gpus'), devices(hw['trainer_gpus'], 'trainer_gpus'), GPUS[hw['flavor']] if any(g >= count for g in vllm + trainer): raise ValueError(f'{hw["flavor"]} has GPUs 0-{count - 1}; the layout uses {max(vllm + trainer)}') if not isinstance(hw['tensor_parallel'], int) or len(vllm) != hw['tensor_parallel']: raise ValueError('tensor_parallel must equal the number of vLLM GPUs') if set(vllm) & set(trainer): raise ValueError('vLLM and trainer GPUs overlap') if not ctx['bridge_max_logprob_context'] <= ctx['bridge_max_context'] <= ctx['max_model_len']: raise ValueError('context caps must satisfy bridge_max_logprob_context <= bridge_max_context <= max_model_len') completion = method.get('runtime', {}).get('max_completion_length') if completion is not None and completion > ctx['max_model_len']: raise ValueError('runtime.max_completion_length exceeds what vLLM serves (max_model_len)') hw.update(vllm_gpus=','.join(map(str, vllm)), trainer_gpus=','.join(map(str, trainer))) return {'model': model['model']['id'], 'revision': model['model']['revision'], **hw, **ctx}