Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / tools / model_integration / data_contract.py: 57%
60 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Data/forward-input ownership and semantic diagnostics."""
17from __future__ import annotations
19import fnmatch
20from collections.abc import Mapping
21from typing import Any, Optional
23from hyper_parallel.tools.model_integration.schemas import (
24 FindingSeverity,
25 ModelIntegrationFinding,
26)
27from hyper_parallel.models.validation_spec import ModelValidationSpec
30_FRAMEWORK_FIELDS = {
31 "input_ids",
32 "labels",
33 "shift_labels",
34}
37def build_field_ownership(
38 runtime_adapter: Any,
39) -> dict[str, str]:
40 """Build declared field ownership without executing another data batch.
42 Args:
43 runtime_adapter: Optional model-owned runtime-input adapter.
45 Returns:
46 Declared field owners and any duplicate ownership claims.
47 """
48 ownership = {name: "framework" for name in sorted(_FRAMEWORK_FIELDS)}
49 runtime_fields = getattr(runtime_adapter, "runtime_input_fields", lambda: ())()
50 collisions = {}
51 for field_name in runtime_fields:
52 previous = ownership.get(field_name)
53 if previous is not None:
54 collisions.setdefault(field_name, [previous]).append("runtime_adapter")
55 else:
56 ownership[field_name] = "runtime_adapter"
57 return {"fields": ownership, "collisions": collisions}
60def validate_data_contract(
61 validation_spec: Optional[ModelValidationSpec],
62 runtime_adapter: Any,
63 *,
64 labels_are_shifted: Optional[bool],
65 source_type: Optional[str],
66 assets: tuple[Any, ...] = (),
67) -> tuple[dict[str, Any], list[ModelIntegrationFinding]]:
68 """Validate stable adapter ownership and model-declared data semantics.
70 Args:
71 validation_spec: Optional model-owned validation declaration.
72 runtime_adapter: Optional model-owned runtime-input adapter.
73 labels_are_shifted: Resolved get-batch label alignment, when declared.
74 source_type: Resolved get-batch source type, when declared.
75 assets: Tokenizer or processor assets used by the data pipeline.
77 Returns:
78 Field-ownership evidence and actionable validation findings.
79 """
80 ownership = build_field_ownership(runtime_adapter)
81 findings = []
82 if ownership["collisions"]:
83 findings.append(
84 ModelIntegrationFinding(
85 phase="D2",
86 owner_fqn="data.forward_fields",
87 severity=FindingSeverity.ERROR,
88 message="multiple data stages claim the same model forward field",
89 facts={"collisions": ownership["collisions"]},
90 why_unsafe="a later stage can silently replace authoritative batch semantics",
91 remediation="assign every forward field to exactly one framework or adapter owner",
92 related_config=("dataset.data_transform", "dataloader.get_batch"),
93 )
94 )
95 data_spec = validation_spec.data if validation_spec is not None else None
96 if data_spec is not None:
97 declared_runtime = set(ownership["fields"])
98 missing_runtime = sorted(set(data_spec.runtime_fields) - declared_runtime)
99 shift_mismatch = (
100 data_spec.labels_are_shifted is not None
101 and labels_are_shifted is not None
102 and data_spec.labels_are_shifted != labels_are_shifted
103 )
104 missing_processor_revision = data_spec.processor_revision_required and not any(
105 getattr(asset, "name_or_path", None)
106 or getattr(asset, "_name_or_path", None)
107 for asset in assets
108 )
109 if missing_runtime or shift_mismatch or missing_processor_revision:
110 findings.append(
111 ModelIntegrationFinding(
112 phase="D2",
113 owner_fqn="data.semantic_contract",
114 severity=FindingSeverity.ERROR,
115 message="resolved data pipeline disagrees with the model validation contract",
116 facts={
117 "missing_runtime_fields": missing_runtime,
118 "expected_labels_are_shifted": data_spec.labels_are_shifted,
119 "actual_labels_are_shifted": labels_are_shifted,
120 "processor_revision_missing": missing_processor_revision,
121 },
122 why_unsafe=(
123 "label shift, packed boundaries, modality metadata, and processor revision "
124 "change the mathematical training input"
125 ),
126 remediation=(
127 "fix the dataset/get_batch recipe or adapter declarations before using "
128 "the run for precision conclusions"
129 ),
130 related_config=("dataset", "dataloader", "model adapter validation"),
131 )
132 )
133 ownership.update(
134 {
135 "source_type": source_type,
136 "labels_are_shifted": labels_are_shifted,
137 }
138 )
139 return ownership, findings
142def validate_observed_forward_fields(
143 validation_spec: Optional[ModelValidationSpec],
144 model_inputs: Mapping[str, Any],
145) -> list[ModelIntegrationFinding]:
146 """Validate the actual first-step kwargs against required model semantics."""
147 data_spec = validation_spec.data if validation_spec is not None else None
148 if data_spec is None:
149 return []
150 missing = sorted(set(data_spec.required_forward_fields) - set(model_inputs))
151 modality_present = sorted(set(data_spec.modality_fields).intersection(model_inputs))
152 if not missing:
153 return []
154 return [
155 ModelIntegrationFinding(
156 phase="D7",
157 owner_fqn="model.forward",
158 severity=FindingSeverity.ERROR,
159 message="actual model forward kwargs omit required training fields",
160 facts={
161 "missing": missing,
162 "observed": sorted(model_inputs),
163 "modality_fields_present": modality_present,
164 },
165 why_unsafe="the executed model input is not the declared mathematical training sample",
166 remediation="fix transform/collation/runtime-input ownership for the missing fields",
167 related_config=("dataset", "dataloader.get_batch"),
168 )
169 ]
172def modality_gradient_parameters(
173 validation_spec: Optional[ModelValidationSpec],
174 model: Any,
175) -> dict[str, bool]:
176 """Return whether every declared multimodal group has a finite gradient."""
177 data_spec = validation_spec.data if validation_spec is not None else None
178 if data_spec is None:
179 return {}
180 result = {}
181 named_parameters = tuple(model.named_parameters(remove_duplicate=False))
182 for pattern in data_spec.modality_parameter_patterns:
183 matched = [
184 parameter
185 for parameter_name, parameter in named_parameters
186 if parameter.requires_grad and fnmatch.fnmatchcase(parameter_name, pattern)
187 ]
188 finite_gradients = []
189 for parameter in matched:
190 gradient = getattr(parameter, "main_grad", None)
191 if gradient is None:
192 gradient = parameter.grad
193 local = (
194 gradient.to_local()
195 if callable(getattr(gradient, "to_local", None))
196 else gradient
197 )
198 if local is not None:
199 finite_gradients.append(bool(local.isfinite().all()))
200 result[pattern] = bool(finite_gradients) and all(finite_gradients)
201 return result
204__all__ = [
205 "build_field_ownership",
206 "modality_gradient_parameters",
207 "validate_data_contract",
208 "validate_observed_forward_fields",
209]