template.py 3.65 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Deploy YAML template rendering and application.

Supports ${VARIABLE} placeholders using Python's string.Template.
This enables arbitrary backend deployments (mocker, vLLM, TensorRT-LLM, etc.)
without hardcoding DGD structures.
"""

from __future__ import annotations

import string
from pathlib import Path
from typing import Dict

from sweep_core.models import DeployDimension, SweepConfig
from sweep_k8s.kubectl import apply_yaml

# Tokenizer backend mapping for template substitution
TOKENIZER_TEMPLATE_MAP = {
    "hf": "default",
    "default": "default",
    "fast": "fast",
    "fastokens": "fast",
}

DEFAULT_HF_TOKEN_SECRET_NAME = "hf-token-secret"


def _indent_block(text: str, spaces: int) -> str:
    prefix = " " * spaces
    return "\n".join(f"{prefix}{line}" if line else "" for line in text.splitlines())


def _build_image_pull_secrets_block(image_pull_secret: str) -> str:
    if not image_pull_secret:
        return ""
    return _indent_block(
        f"""imagePullSecrets:
  - name: {image_pull_secret}""",
        8,
    )


def build_substitution_dict(
    deploy: DeployDimension,
    config: SweepConfig,
) -> Dict[str, str]:
    """Build a variable substitution dictionary for template rendering.

    Combines deploy dimensions, sweep config, and k8s config into a flat
    dictionary suitable for string.Template substitution.
    """
    k8s = config.k8s
    hf_token_secret_name = DEFAULT_HF_TOKEN_SECRET_NAME
    variables: Dict[str, str] = {
        # Deploy dimensions
        "DYN_TOKENIZER_BACKEND": TOKENIZER_TEMPLATE_MAP.get(
            deploy.tokenizer, deploy.tokenizer
        ),
        "NUM_WORKERS": str(deploy.workers),
        # Model info
        "MODEL": config.model,
        "MODEL_NAME": config.model_name,
        "MODEL_PATH": config.model,
        # Image
        "IMAGE": k8s.image,
        # K8s config
        "NAMESPACE": k8s.namespace,
        "DGD_NAME": k8s.dgd_name,
        "FRONTEND_PORT": str(k8s.frontend_port),
        "WORKER_REPLICAS": str(k8s.worker_replicas),
        "FRONTEND_REPLICAS": str(k8s.frontend_replicas),
        "SPEEDUP_RATIO": str(config.speedup_ratio),
        "REQUEST_PLANE": k8s.request_plane,
        "EVENT_PLANE": k8s.event_plane,
        "ROUTER_MODE": k8s.router_mode,
        "HF_TOKEN_SECRET_NAME": hf_token_secret_name,
        "FRONTEND_IMAGE_PULL_SECRETS_BLOCK": _build_image_pull_secrets_block(
            k8s.image_pull_secret
        ),
        "WORKER_IMAGE_PULL_SECRETS_BLOCK": _build_image_pull_secrets_block(
            k8s.image_pull_secret
        ),
    }

    # Add any env_overrides from the deploy dimension
    variables.update(deploy.env_overrides)

    return variables


def render_template(template_path: Path, variables: Dict[str, str]) -> str:
    """Read a deploy YAML template and substitute ${VAR} placeholders.

    Uses safe_substitute so missing variables are left as-is rather than
    raising KeyError. This is important because DGD templates may contain
    ${VARIABLE} references that are resolved by the k8s operator at runtime.
    """
    raw = template_path.read_text()
    tmpl = string.Template(raw)
    return tmpl.safe_substitute(variables)


def apply_rendered_template(
    template_path: Path,
    deploy: DeployDimension,
    config: SweepConfig,
) -> None:
    """Render a deploy template and apply it via kubectl."""
    variables = build_substitution_dict(deploy, config)
    rendered = render_template(template_path, variables)
    print(f"  Applying rendered template: {template_path.name}")
    apply_yaml(rendered, config.k8s.namespace)