Diff Coverage

Diff: origin/master...HEAD, staged and unstaged changes

Source File Diff Coverage (%) Missing Lines
hyper_parallel/compile/__init__.py 100%  
hyper_parallel/compile/pass_config.py 37.5% 138-140,144-145
hyper_parallel/compile/text_trainer.py 17.6% 26,28,31,34,39-41,50,57-62,64,66-67,69,75-77,81-82,85,87-88,92,94
hyper_parallel/compile/tracer/graph_tracer.py 0.0% 600,603
hyper_parallel/compile/trainer.py 20.0% 89,96,103,118-122,154-155,243-244
hyper_parallel/distributed/compile.py 0.0% 236,245-246
hyper_parallel/trainer/config/trainer.py 0.0% 87
hyper_parallel/compile/pass_config.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
    graph-pass surface from the trainer topology without altering runtime
    objects. Accepting ``Any`` keeps this module importable without the
    trainer package.
    """
    accelerator = config.accelerator
    if fsdp_enabled is None:
        fsdp_enabled = (
            config.fsdp_config.dp_shard_size > 1
            or config.fsdp_config.edp_shard_size > 1
        )
    fsdp_degree = config.fsdp_config.dp_shard_size if fsdp_enabled else None
    return PassConfig(
        enable_overlap=enable_overlap,
        fsdp_enabled=fsdp_enabled,
        fsdp_degree=fsdp_degree,
        tp_size=accelerator.tp_size,
hyper_parallel/compile/text_trainer.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import torch

from hyper_parallel.trainer.runtime.loss_aggregation import count_loss_token
from hyper_parallel.trainer.text_trainer import TextTrainer
from hyper_parallel.trainer.config import TrainerConfig

from .trainer import GraphTrainer


class GraphTextTrainer(TextTrainer):
    """Reuse TextTrainer and delegate graph execution to ``GraphTrainer``."""

    def __init__(
        self,
        config: TrainerConfig,
    ) -> None:
        """Build graph-mode text training on top of the eager TextTrainer stages."""
        super().__init__(config)
        self._graph_train_fn = self._default_train_fn
        self.base_graph_trainer = GraphTrainer(
            model=self.base.model,
            train_fn=self._graph_train_fn,
            trainer_config=config,
            device=self.base.device,
46
47
48
49
50
51
52
53
54
            mesh_context=self.base.mesh,
            manage_optimizer=False,
        )

    def _default_train_fn(
        self,
        model: torch.nn.Module,
        model_inputs: Mapping[str, Any],
        loss_inputs: Mapping[str, Any],
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
        model_inputs: Mapping[str, Any],
        loss_inputs: Mapping[str, Any],
    ) -> torch.Tensor:
        """Default graph trace function for Transformer-style text training."""
        outputs = model(**dict(model_inputs), use_cache=False)
        labels = loss_inputs.get("labels")
        loss = self.base.loss_fn(model_output=outputs, labels=labels)
        if isinstance(loss, dict):
            return torch.stack(list(loss.values())).sum()
        return loss

    def set_pytree_pre_hook(self, hook: Any) -> "GraphTextTrainer":
        """Register a tracer pre-hook on the underlying graph executor."""
        self.base_graph_trainer.set_pytree_pre_hook(hook)
        return self

    def forward_backward_step(
        self,
        data_iterator: Any,
        num_micro_steps: int,
    ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]:
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
        data_iterator: Any,
        num_micro_steps: int,
    ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]:
        """Fetch one text batch and execute graph-mode forward/backward."""
        model_inputs, loss_inputs = self.base.get_batch(data_iterator)
        self.base.current_token_counts = count_loss_token(loss_inputs)
        self.base.step_token_counts = {
            name: token_count * num_micro_steps
            for name, token_count in self.base.current_token_counts.items()
        }
        loss = self.base_graph_trainer.train_step(model_inputs, loss_inputs)
        return loss, _loss_to_metrics(loss)


def _loss_to_metrics(loss: Any) -> dict[str, Any]:
    """Normalize graph loss output to a callback-friendly metrics mapping."""
    if isinstance(loss, dict):
        return {
            str(name): value.detach() if hasattr(value, "detach") else value
            for name, value in loss.items()
        }
    return {"graph_loss": loss.detach() if hasattr(loss, "detach") else loss}

__all__ = ["GraphTextTrainer"]
hyper_parallel/compile/tracer/graph_tracer.py
596
597
598
599
600
601
602
603
604
605
606
            f"  Got:    {list(model_state.keys())}"
        )

    state_flat, _ = torch.utils._pytree.tree_flatten({"model": model_state})
    user_inputs_flat, _ = torch.utils._pytree.tree_flatten(
        (input_batch, label_batch)
    )
    flat_inputs = list(state_flat) + list(user_inputs_flat)

    with torch.no_grad():
        outputs = joint_graph.graph_module(*flat_inputs)
hyper_parallel/compile/trainer.py
85
86
87
88
89
90
91
92
93
                builds and steps the optimizer on the live model.
        """
        self.model = model
        self.train_fn = train_fn
        self.pass_config = self._resolve_pass_config(
            pass_config=pass_config,
            trainer_config=trainer_config,
        )
        self.pass_plan = pass_plan
 92
 93
 94
 95
 96
 97
 98
 99
100
        )
        self.pass_plan = pass_plan
        self.optimizer_config = optimizer_config or {}
        self._mesh_context = mesh_context
        self._manage_optimizer = manage_optimizer
        self.device = device or (
            torch.device("npu")
            if (hasattr(torch, "npu") and torch.npu.is_available())
            else torch.device("cpu")
 99
100
101
102
103
104
105
106
107
            if (hasattr(torch, "npu") and torch.npu.is_available())
            else torch.device("cpu")
        )

        self.pass_config.validate()

        self._joint_graph = None
        self.optimizer = None
        # Optional hook run right before the first compile, for model-specific
114
115
116
117
118
119
120
121
122
123
124
125
126
        pass_config: Optional[PassConfig],
        trainer_config: Optional[Any],
    ) -> PassConfig:
        """Resolve the graph pass config from explicit or trainer-level input."""
        if pass_config is not None:
            return pass_config
        if trainer_config is not None:
            return build_pass_config_from_trainer_config(trainer_config)
        return PassConfig()

    def compile(self, sample_input: torch.Tensor, sample_label: torch.Tensor) -> None:
        """
        Compile model into parallel graph
150
151
152
153
154
155
156
157
158
        pipeline.run(joint_graph.graph_module, **pass_kwargs)

        self._joint_graph = joint_graph

        if self._manage_optimizer:
            self._init_optimizer()

    def _init_device_mesh(self, mesh_context: Optional[Any] = None):
        """Initialize the FSDP process group.
239
240
241
242
243
244
245
246
247
248
        return loss

    def optimizer_step(self) -> None:
        """Optimizer update"""
        if not self._manage_optimizer:
            raise RuntimeError(
                "optimizer_step() is disabled when manage_optimizer=False. "
                "Let the outer trainer runtime own optimizer stepping."
            )
        if self.optimizer is None:
hyper_parallel/distributed/compile.py
232
233
234
235
236
237
238
239
240
    if isinstance(compile_config, dict):
        compile_config = CompileConfig(enabled=True, **compile_config)
    if compile_config is not None and not isinstance(compile_config, CompileConfig):
        raise TypeError("compile_config must be a CompileConfig, mapping, or None")
    graph_mode_requested = bool(
        compile_config is not None and compile_config.selects_graph_trainer()
    )
    compile_for_execution = bool(
        not validate_placement
241
242
243
244
245
246
247
248
249
250
        and compile_config is not None
        and compile_config.enabled
        and not graph_mode_requested
    )
    if graph_mode_requested:
        logger.info(
            "compile.graphtrainer_enabled=True selects graph-mode trainer; "
            "skipping decoder-layer compile",
        )
    if validate_placement and compile_config is not None and compile_config.enabled:
hyper_parallel/trainer/config/trainer.py
83
84
85
86
87
88
89
90
91
    peft: Optional[Any] = None

    def __post_init__(self) -> None:
        """Validate combinations that span multiple config sections."""
        if (
            self.compile.enabled
            and not self.compile.selects_graph_trainer()
            and self.accelerator.pp_size > 1
        ):