Diff Coverage

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

Source File Diff Coverage (%) Missing Lines
hyper_parallel/components/checkpoint/config.py 100%  
hyper_parallel/components/functional/mhc_post.py 0.0% 87-90,93
hyper_parallel/components/modules/engram.py 13.5% 43-55,59-60,63-67,73-74,83-89,94-102,104-114,116-123,128-130,145-163,167-168,171-181,196-197,200-211,215-220,224-227,232,248-262,264,268-277,287-291,310,321,329-332
hyper_parallel/components/modules/mhc.py 16.3% 209-227,234-235,238-239,242,245-251,253-254,264-265,272
hyper_parallel/components/modules/shared_compressed_dsa_attention.py 16.6% 56,84,105,109,118,122,131,139,175-177,200-204,208-210,215-216,220-225,233-235,245-250,263-266,271-274,297-303,306-307,310-315,323-332,335-344,359-371,373-379,394-406,408-412,429-442,445-449,457,475-481,484-485,488-493,500,502-512,515-523,531,549-557,560-561,564-565,569-570,572-576,584-592,595-601,606-616,638-643,645-654,656-659,662-666,668-669,674-675,678-682,686-689,691-693,698,701,704,707-709,715-716,721-722,750-756,776-796,807-819,836-837,839,841-843,846-851,855-860,873-876,890-891,902,919-923,930-936,953,955-963,970,977-978,996-1011,1021-1022,1027,1037-1047,1067-1071,1083,1104-1110,1115-1132,1143-1144,1148-1149,1153-1155,1159,1169-1173,1182-1186,1203-1209,1212-1214,1217,1221-1223,1227-1229,1233,1245-1250,1255,1260,1265-1269,1279-1284,1286-1287,1302-1304,1309,1316,1328-1332,1336,1350,1358-1363,1367,1371-1375,1385,1391,1404-1405,1415-1416,1424,1441-1445,1448-1451,1463,1471-1474,1481,1490-1494,1497,1509-1514,1516,1526-1527,1530-1531,1552-1553
hyper_parallel/components/optim/mixed_precision_optimizer.py 68.4% 63,70,76,84,86,98
hyper_parallel/core/expert_parallel/expert_parallel.py 100%  
hyper_parallel/core/expert_parallel/static_splits.py 100%  
hyper_parallel/core/optimizer/dtensor_compat.py 84.6% 165,173
hyper_parallel/core/optimizer/lr_scheduler.py 0.0% 188-191,195,199
hyper_parallel/data/batching/__init__.py 100%  
hyper_parallel/data/batching/build_collate_fn.py 84.6% 109,147-148,168
hyper_parallel/data/batching/build_dataloader.py 18.8% 191-192,195,226-229,282,285-287,302-304,314,337-338,342-345,347-348,350-351,354-355,357,362-364,378-379,384,386-387,390-391,396,400-401,404-406,433,444-446,459-460,468-469
hyper_parallel/data/batching/dynamic_batch.py 28.3% 42-43,45-46,51,55-58,62-65,70,74-75,84-86,88-89,93-94,96-100,104-107,109-110,112-115,126-128,132-133,135-137,139,143-144,146-151,153-155,158-160,162-165,169-176,178-180,182-185,187,192-194,196-200,202-203,205,214-221,223-230,235-236,238,246,250-251,255-257,261-264,266,268,278,283-285,290-293,298-299,301-302,304,313-315,317,319-321,331-332,337-339,344-345,347,356-358,360,362-364,378,386,390,403,407,414,429-432,435-439,443-447,449,455,464,469,476-481,483-485,487-488,492-493
hyper_parallel/data/batching/get_batch.py 29.5% 71-78,83,86-91,100-106,108-111,113,118-127,132,212-214,217-218,233,240,246,283,285,297,301-302,304,314-316,320-321,454-457,461
hyper_parallel/data/batching/packing.py 18.8% 35,44,52-54,56-58,60,68-69,71-78,80,85-86,88-92,94-95,97-100,113-115,122-123,125-128,130-132,134-136,138-141,143-144,146-147,151-152,154-157,159-164,166-170,172-173,175,183-188,190-193,195-196,198-199,206-210,212-217,219-222,224-226,228-229,231,235
hyper_parallel/data/batching/runtime_input.py 36.0% 33-35,43,47-49,51-54,56,58-62,64-66,68-70,72-73,136,148,169,184-187,196-198,200-203,209-211,213,224-226,228-229,231-233,238-239,244,246-249,251-255,257
hyper_parallel/data/constants.py 100%  
hyper_parallel/data/dataset_logging.py 25.0% 52,119,125
hyper_parallel/data/indexed/indexed_data_reader.py 0.0% 27,30,40-41,45-46,49
hyper_parallel/data/indexed/indexed_pretrain_dataset.py 0.0% 31,108,110,134
hyper_parallel/data/omni/__init__.py 100%  
hyper_parallel/data/omni/build_dataset.py 31.1% 57-60,62-64,66-67,72-73,81-83,88-94,96,101,103-104,106-107,111-112,116-117
hyper_parallel/data/omni/omni_transform.py 25.0% 44,49-56,58,75-80,85-88,92,96,100,104,108-110,112-115,117-124,129-130,132-139,141-143,148-149,151-152,160-162,164,171,179-183,185-188,190-193,209-211,213-214,218-219,222-223,230-234,236-238,240,244,249-252,256-259,263-266,270-272
hyper_parallel/data/online/__init__.py 100%  
hyper_parallel/data/online/iterable.py 18.7% 56-58,60,62,64-65,67,72,77-79,96-97,99-100,104-105,110-111,113-114,119-120,122,126-127,129-131,135,139-141,143,145-151,153,157-162,166-168,170,174-176,180-182,184,200-208,212-215,217-219,221-223,225-226,228,230-231,233,235-238,240-246,248-252,254-256,258-260,262-263,265,269-274,278-284,286-287,294,298-300,302-308,310-313,322-324,328-330,343-344,352-358,360-362,364-366,371-375,377,384,386-389,391-392,394-397,402,407,411-414,418-419,421-423,425-426
hyper_parallel/data/online/mapping.py 16.1% 60-62,70-71,84-90,92-93,95-96,98-99,103-104,108-112,114-117,119,123-125,128-129,131-132,144-148,150-152,156-157,161-165,167-174,181-184,186-193,197-200,208-220,224-230,234-237,239-243,247-249,254-260,274-279,281-286,288-292,299,303-307,312-313,315-319,323-325,327-332,336-338,340-342,344,352,356-360,364-366,379-380,388-392,396-400,402-405,408-416,418,420-422,424-425,430,433-434,443-446,450-451,453-462,464,468,477-478,480-487,489-497,502-510,512-517,521-522,524-527,536-542,544-545,547,553,558-561,563-566
hyper_parallel/data/online/source.py 11.7% 34-35,40,45-47,51,78-83,85-88,90-103,105-111,115-116,118,120-122,124-128,130,136,144-145,154,156-158,161-170,172,180-184,186-187,189-197,199-200,203-212,214-215,223-226,228-229,231-232,234,236-240,242-245,248-252,255-258,260-261,266-269,271-278,285-286,288-292,297-299,301-302,304-306,308-310,312-314,316-318,323-324,326,328-332,334-337,339-340,342-347,349-350,352,357-362,364-365,367-370,372-373,378,380-382,384-393,395-396,398-399
hyper_parallel/data/parallel/batch_parallel.py 56.7% 259,316-317,382-390,392,394-397,400-407,411
hyper_parallel/data/text/__init__.py 100%  
hyper_parallel/data/text/build_dataset.py 0.0% 26,57,60,74,78,107-111,117-118,121,147-148,150-153,157,163-164,204
hyper_parallel/data/text/chat_template.py 100%  
hyper_parallel/data/text/text_transform.py 0.0% 25,28,96,98-102,126-128,136,138-140,143,171,173
hyper_parallel/data/tools/io.py 37.5% 126,131,133,141-144,165,176,769
hyper_parallel/data/tools/offline_preparation.py 0.0% 23,51,786
hyper_parallel/distributed/_builder/forward_rewriter.py 90.9% 348
hyper_parallel/distributed/_builder/fsdp_adapter.py 9.8% 233,235,237,244-245,251-253,305-308,312-317,340-342,344-346,349-354,357-359,363-364,368-370,375-383,386-389,392-393,397,405-407,411,414,417,421,424,451,557,561-566,568,571,575-576,579-580,583,588,592-593,609,642,699
hyper_parallel/distributed/_builder/parameter_sharding.py 50.0% 236
hyper_parallel/distributed/_builder/planner.py 86.7% 844-845,848-849
hyper_parallel/distributed/_builder/rule_resolver.py 97.5% 172
hyper_parallel/distributed/_builder/source_shard.py 66.7% 361,363
hyper_parallel/distributed/activation_checkpoint.py 31.3% 776-788,792-795,804,811,816,832,836,850,860,870-873,877-878,882-883,887,890-892,895,900-902,906,919-920,925-927,929-934,947-955,958-959,962-964,970,1143
hyper_parallel/distributed/expert_parallel/collectives.py 96.6% 139,272,361,627
hyper_parallel/distributed/expert_parallel/experts.py 90.2% 185,225-227,229-231,234,240-242,264,339,480,484-485,536,1156,1161-1165,1172,1268,1293
hyper_parallel/distributed/expert_parallel/recipes.py 37.5% 187,196,198,200-202,362-363,365,367
hyper_parallel/distributed/expert_parallel/routing.py 66.2% 313-315,320,352,361-370,413-415,418,420-421,428-429,431,433
hyper_parallel/tools/model_integration/__init__.py 100%  
hyper_parallel/tools/model_integration/__main__.py 0.0% 17,19-20
hyper_parallel/tools/model_integration/case_compare.py 85.0% 56,103-106,110,112,143,190,236,254,259-264,272-278,290,299,308,360,367-368,375,436-438,454,457,478-480,485,508,571,629,703,720,728,736,757,768
hyper_parallel/tools/model_integration/case_matrix.py 88.1% 56,79,84,89,95,100,107,111,118,122-123,154,164,187,192,209,226,282,329-330,348
hyper_parallel/tools/model_integration/checkpoint_coverage.py 69.3% 32-40,51-58,73-75,80,110,154
hyper_parallel/tools/model_integration/cli.py 59.4% 97,102-105,118,126-127,140,145-146,167,175,182,202-204,207,221-223,267,336-340,351-354,359-360,385,390-391,402-407,410-413,418-425,435,446,452,462-467,474-476,488,492-495,513,594,728-729,734,773-779,791-795,804,808-810,816-821,831-835,840-843,854,857,875,879,885,892-895,900-901,917,921,930-935,952-956,960-969,973,980-981,997-999,1004-1006,1011-1014,1018,1024,1040,1056-1058,1080,1084,1118-1120,1125-1126,1131,1133,1135-1136,1138,1162,1184,1235-1236,1239,1250-1254,1259,1272-1283,1302,1354-1358,1361-1362,1384-1390,1393-1394,1400,1415-1418,1422-1427,1433,1438-1441,1446-1448,1454,1459,1465,1469-1472,1483-1488,1498,1543,1559,1568-1569,1574,1615-1616,1620-1623,1630-1631,1635-1638,1645,1653-1654,1658-1659,1662-1663,1668-1671,1675-1678,1683-1685,1693-1698,1703,1707,1716,1902
hyper_parallel/tools/model_integration/data_contract.py 56.7% 48-54,56-57,80-83,96-100,105,110-111,135,141,151,155,182
hyper_parallel/tools/model_integration/evidence_store.py 83.6% 50,55-61,76,94-99,174-175,183-184,192-193,210,252,276
hyper_parallel/tools/model_integration/integration_validator.py 73.7% 70-72,76-80,84,179-182,210,280,283,300-311,318,340-345,358,393-399,450,455-460,470
hyper_parallel/tools/model_integration/manifest.py 79.9% 41,49,76,120-121,123,138-139,175,195,198,205,208,212,218,221,231,247,261,264-271,274-277,287,313,316,319-320,323,326,336
hyper_parallel/tools/model_integration/module_parity.py 79.7% 59,63,69,78-79,81-82,84-85,102,121,167,200-201,223,226,233,241,246-247,264,269,302,307,312-314,319-321,352,354,385,387,479,482-483,486,489-490,496-497,517
hyper_parallel/tools/model_integration/optimizer_layout.py 28.4% 57-59,67-75,80-85,94,118-122,130-135,143,146-148,156-157,162,178,183-186,190-196,201-202,207-218
hyper_parallel/tools/model_integration/schemas.py 81.6% 78,86-88,92-95,98-100,111-112,133,141-142
hyper_parallel/tools/model_integration/structure_inventory.py 77.4% 50-52,84-85,91,101,104-105,112-116,121,175-177,181-182,185-186,217,230,233,241,255-261,287,292,305,312-313
hyper_parallel/tools/model_integration/tensor_probes.py 76.8% 89,169,215,239-242,246-247,252-255,261-262,267-268,273,277-285,298,301,325,373,377-378,397,412,416-420,486-487,492,495,502,517-520,523,526,530,533-539,546,549-552,557,559
hyper_parallel/trainer/base.py 54.8% 297,386,392,461,518-520,711,767-768,776,779,811,814,826-835,840-849,860,875,886,890
hyper_parallel/trainer/callbacks/checkpoint_callback.py 71.4% 274-276,364
hyper_parallel/trainer/config/data.py 42.9% 45-46,85,88
hyper_parallel/trainer/config/optimization.py 100%  
hyper_parallel/trainer/config/parallelism.py 88.9% 75,104,112
hyper_parallel/trainer/config/resolver.py 60.6% 170,536,634-637,645,656,692-693,698,711,716
hyper_parallel/trainer/config/target.py 100%  
hyper_parallel/trainer/config/trainer.py 100%  
hyper_parallel/trainer/config/training.py 100%  
hyper_parallel/trainer/runtime/fsdp.py 90.3% 71,83,85,113,117,131,138,227,231,278,336,360-366,370
hyper_parallel/trainer/runtime/memory.py 33.3% 82-83,88-89,93-94
hyper_parallel/trainer/runtime/model_integration.py 24.4% 58-59,63,117-121,123-133,135,138,141,157,161,165,173,180,187,191,200,215-221,224-229,235-242,247,250,254-258,271-273,277-278,286,290,295,299,303,307,311-314,322-326,335-336,343-345,351-352,362,369-372,379-380,384-390,395-399,412-415,419-421,424-425,432,442-445,449-451,457-464,472-475,479-480,484-485,492-494,501-507,511-517,521-529,545,553,557-559,566-573,578-579,583,588,594-595,607-610,626-628
hyper_parallel/trainer/text_trainer.py 0.0% 61,65,253,265,275-276,282
hyper_parallel/trainer/vlm_trainer.py 0.0% 23,59,63,79,84-86,88-91,99-100,103-105,118,125-127,133,199,205,228,236,240,244,253,257
hyper_parallel/components/functional/mhc_post.py
83
84
85
86
87
88
89
90
91
92
93
        Flattened mixed residual streams.
    """
    x_shape = x.size()
    residual = residual.reshape(x_shape[0], x_shape[1], num_stream, -1)
    if x.device.type == "npu" and omni_training_custom_ops is not None:
        return _MhcPost.apply(residual, h_res, x, h_post).flatten(2)
    output = h_post.unsqueeze(-1) * x.unsqueeze(-2)
    output = output + torch.sum(
        h_res.unsqueeze(-1) * residual.unsqueeze(-2), dim=2
    )
    return output.flatten(2).to(x.dtype)
hyper_parallel/components/modules/engram.py
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
        Args:
            assets: Prepared tokenizer map and per-layer hash layout.
            layer_id: Decoder layer owning this Engram table.
        """
        super().__init__()
        layer_ids = tuple(int(value) for value in assets["layer_ids"])
        if layer_id not in layer_ids:
            raise ValueError(f"Engram layer {layer_id} is absent from assets layer_ids={layer_ids}")
        layer_index = layer_ids.index(layer_id)
        self.layer_id = layer_id
        self.max_ngram_size = int(assets["max_ngram_size"])
        self.num_heads = int(assets["num_heads"])
        token_map = torch.tensor(assets["token_map"], dtype=torch.long)
        self.pad_id = int(token_map[int(assets["pad_token_id"])])
        primes = torch.tensor(assets["primes"][layer_index], dtype=torch.long)
        if tuple(primes.shape) != (self.max_ngram_size - 1, self.num_heads):
            raise ValueError(
                "Engram prime layout must be [max_ngram_size - 1, num_heads], "
                f"got {tuple(primes.shape)}"
            )
        flattened_primes = primes.flatten()
        offsets = torch.cat(
            [torch.zeros(1, dtype=torch.long), flattened_primes.cumsum(0)[:-1]]
        ).view_as(primes)
        multipliers = torch.tensor(assets["multipliers"][layer_index], dtype=torch.long)
        if multipliers.numel() != self.max_ngram_size:
            raise ValueError("Engram multiplier count must equal max_ngram_size")
        self.logical_num_embeddings = int(flattened_primes.sum().item())
        initial_buffers = {
            "token_map": token_map,
            "primes": primes,
            "offsets": offsets,
            "multipliers": multipliers,
69
70
71
72
73
74
75
76
77
78
            "primes": primes,
            "offsets": offsets,
            "multipliers": multipliers,
        }
        for name, value in initial_buffers.items():
            register_rebuildable_buffer(self, name, value=value, persistent=False)

    def forward(
        self,
        input_ids: torch.Tensor,
 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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
        segment_starts: torch.Tensor | None = None,
        token_mask: torch.Tensor | None = None,
    ) -> torch.Tensor:
        """Return global flat-table rows shaped ``[batch, seq, hash_columns]``."""
        if input_ids.ndim != 2:
            raise ValueError(f"Engram expects input_ids [B,S], got {tuple(input_ids.shape)}")
        compressed = self.token_map[input_ids]
        if token_mask is not None:
            if token_mask.shape != input_ids.shape:
                raise ValueError("Engram token_mask must have the same shape as input_ids")
            compressed = torch.where(
                token_mask.to(torch.bool),
                compressed,
                compressed.new_full((), self.DEAD_TOKEN),
            )
        batch_size, sequence_length = compressed.shape
        positions = torch.arange(sequence_length, device=input_ids.device).expand(batch_size, -1)
        segment_ids = None
        if segment_starts is not None:
            if segment_starts.shape != input_ids.shape:
                raise ValueError("Engram segment_starts must have the same shape as input_ids")
            starts = segment_starts.to(torch.bool).clone()
            starts[:, 0] = True
            segment_ids = starts.to(torch.long).cumsum(dim=1)

        tokens = []
        blocked = torch.zeros_like(positions, dtype=torch.bool)
        for shift in range(self.max_ngram_size):
            source_positions = (positions - shift).clamp_min(0)
            source = compressed.gather(1, source_positions)
            blocked = blocked | (positions < shift)
            if segment_ids is not None:
                source_segments = segment_ids.gather(1, source_positions)
                blocked = blocked | (source_segments != segment_ids)
            blocked = blocked | (source == self.DEAD_TOKEN)
            tokens.append(torch.where(blocked, self.pad_id, source))

        products = torch.stack(tokens, dim=-1) * self.multipliers
        rolling = products[..., 0]
        hashes = []
        for ngram_index in range(self.max_ngram_size - 1):
            rolling = torch.bitwise_xor(rolling, products[..., ngram_index + 1])
            row = rolling.unsqueeze(-1) % self.primes[ngram_index]
            hashes.append(row + self.offsets[ngram_index])
        return torch.cat(hashes, dim=-1)


def _all_gather_sequence(value: torch.Tensor, group: Any, group_size: int) -> torch.Tensor:
    """Gather equal-size sequence chunks and concatenate along dimension one."""
    gathered = [torch.empty_like(value) for _ in range(group_size)]
    dist.all_gather(gathered, value.contiguous(), group=group)
    return torch.cat(gathered, dim=1)


@module_replacement
class EngramModule(nn.Module):
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
        module_fqn: str = "",
        context: Mapping[str, Any] | None = None,
    ) -> None:
        """Reuse a source Engram module's parameters and hash mapping."""
        super().__init__()
        del module_fqn, context
        required = ("hash_mapping", "embed", "wkv", "q_weight", "k_weight")
        missing = [name for name in required if not hasattr(module, name)]
        if missing:
            raise TypeError(f"Engram source is missing required attributes: {missing}")
        self.hash_mapping = module.hash_mapping
        self.embed = module.embed
        self.wkv = module.wkv
        self.q_weight = module.q_weight
        self.k_weight = module.k_weight
        self.layer_id = int(module.layer_id)
        self.hidden_size = int(module.hidden_size)
        self.hc_mult = int(module.hc_mult)
        self.eps = float(module.eps)
        self.clamp_value = float(module.clamp_value)
        self.logical_num_embeddings = int(module.logical_num_embeddings)
        self.padded_num_embeddings = int(module.padded_num_embeddings)
        self.train(module.training)

    def _fuse(self, hidden_states: torch.Tensor, embeddings: torch.Tensor) -> torch.Tensor:
        """Apply the exact V4.1 WKV projection and signed-root gate."""
        key_value = self.wkv(embeddings.flatten(start_dim=-2))
        key, value = key_value.split(
            [self.hc_mult * self.hidden_size, self.hidden_size], dim=-1
        )
        key = key.float().unflatten(-1, (self.hc_mult, self.hidden_size))
        hidden_fp32 = hidden_states.float()
        weight = self.q_weight.float() * self.k_weight.float()
        reciprocal_std = torch.rsqrt(hidden_fp32.square().mean(-1) + self.eps)
        reciprocal_std = reciprocal_std * torch.rsqrt(key.square().mean(-1) + self.eps)
        dot = (hidden_fp32 * weight * key).sum(-1)
        dot = dot * reciprocal_std * self.hidden_size**-0.5
        root = dot.abs().clamp_min(self.clamp_value).sqrt()
        signed_root = torch.where(dot < 0, -root, root)
        gate = torch.sigmoid(signed_root)
        return (
            hidden_fp32 + gate.unsqueeze(-1) * value.float().unsqueeze(-2)
        ).to(hidden_states.dtype)

    @staticmethod
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
        ep_size: int,
        padded_num_embeddings: int,
    ) -> torch.Tensor:
        """Route requested global rows to their EP owners and restore order."""
        if padded_num_embeddings % ep_size:
            raise ValueError(
                f"Engram padded rows {padded_num_embeddings} are not divisible by ep_size={ep_size}"
            )
        num_local_rows = padded_num_embeddings // ep_size
        row_start = ep_rank * num_local_rows
        flat_ids = hash_ids.reshape(-1)
        owner = torch.div(flat_ids, num_local_rows, rounding_mode="floor")
        if owner.numel() and int(owner.max()) >= ep_size:
            raise IndexError("Engram hash requested a row outside the padded table")
        local_counts = torch.bincount(owner, minlength=ep_size).to(torch.long)
        gathered_counts = [torch.empty_like(local_counts) for _ in range(ep_size)]
        dist.all_gather(gathered_counts, local_counts, group=ep_group)
        all_counts = torch.stack(gathered_counts)
        send_counts = local_counts.cpu().tolist()
        recv_counts = all_counts[:, ep_rank].cpu().tolist()

        # Ascend ArgSort executes integer inputs on AiCPU. The owner range is
        # bounded by ep_size, so float32 preserves it exactly and stays on AiCore.
        sort_indices = torch.argsort(owner.float(), stable=True)
        sorted_ids = flat_ids.index_select(0, sort_indices)
        received_ids = ep_all_to_all(sorted_ids, send_counts, recv_counts, ep_group)
        local_ids = received_ids - row_start
        if local_ids.numel() and (int(local_ids.min()) < 0 or int(local_ids.max()) >= num_local_rows):
            raise IndexError("Engram EP routing delivered a row to the wrong owner")
        # Call the child module instead of reading ``embedding.weight``
        # directly. This preserves the FSDP pre/post-forward lifecycle when
        # the EP table is wrapped independently from the dense Engram gates.
        values = embedding(local_ids)
        returned = ep_all_to_all(values, recv_counts, send_counts, ep_group)
        restored = torch.zeros_like(returned)
        restored.scatter_add_(
            0,
            sort_indices.unsqueeze(1).expand(-1, returned.shape[1]),
            returned,
        )
        return restored.view(*hash_ids.shape, embedding.embedding_dim)

    def _aligned_hash_ids(
        self,
        hidden_states: torch.Tensor,
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
        tp_rank: int = 0,
        tp_size: int = 1,
    ) -> torch.Tensor:
        """Hash the local sequence plus the left halo required by n-grams."""
        cp_local_ids = input_ids
        if cp_size > 1:
            input_ids = _all_gather_sequence(input_ids, cp_group, cp_size)
            if segment_starts is not None:
                segment_starts = _all_gather_sequence(segment_starts, cp_group, cp_size)
            if token_mask is not None:
                token_mask = _all_gather_sequence(token_mask, cp_group, cp_size)
        local_sequence = hidden_states.shape[1]
        cp_sequence = cp_local_ids.shape[1]
        if cp_sequence == local_sequence:
            sequence_parallel_parts = 1
            local_tp_rank = 0
        elif cp_sequence % local_sequence == 0 and cp_sequence // local_sequence == tp_size:
            sequence_parallel_parts = tp_size
            local_tp_rank = tp_rank
        else:
            raise ValueError(
                "Engram input_ids length is incompatible with the local hidden sequence: "
                f"ids={cp_sequence}, hidden={local_sequence}, tp_size={tp_size}"
            )
        del sequence_parallel_parts
        start = cp_rank * cp_sequence + local_tp_rank * local_sequence
        end = start + local_sequence
        context_start = max(0, start - self.hash_mapping.max_ngram_size + 1)
        ids_window = input_ids[:, context_start:end]
        starts_window = None if segment_starts is None else segment_starts[:, context_start:end]
        mask_window = None if token_mask is None else token_mask[:, context_start:end]
        hashes = self.hash_mapping(ids_window, starts_window, mask_window)
        target_offset = start - context_start
        return hashes[:, target_offset:target_offset + local_sequence]

    def forward(
        self,
        hidden_states: torch.Tensor,
283
284
285
286
287
288
289
290
291
292
293
294
295
        segment_starts: torch.Tensor | None = None,
        token_mask: torch.Tensor | None = None,
    ) -> torch.Tensor:
        """Run replicated single-card/EP1 Engram."""
        hash_ids = self._aligned_hash_ids(hidden_states, input_ids, segment_starts, token_mask)
        fused = self._fuse(hidden_states, self.embed(hash_ids))
        if token_mask is None:
            return fused
        return torch.where(token_mask.to(torch.bool).unsqueeze(-1).unsqueeze(-1), fused, hidden_states)

    def parallel_forward(
        self,
        hidden_states: torch.Tensor,
306
307
308
309
310
311
312
313
314
        tp_rank: int = 0,
        tp_size: int = 1,
    ) -> torch.Tensor:
        """Run CP/TP sequence alignment and sparse EP lookup."""
        hash_ids = self._aligned_hash_ids(
            hidden_states,
            input_ids,
            segment_starts,
            token_mask,
317
318
319
320
321
322
323
324
325
            cp_size=cp_size,
            tp_rank=tp_rank,
            tp_size=tp_size,
        )
        embeddings = self._sparse_lookup(
            hash_ids,
            self.embed,
            ep_group=ep_group,
            ep_rank=ep_rank,
325
326
327
328
329
330
331
332
333
334
335
            ep_rank=ep_rank,
            ep_size=ep_size,
            padded_num_embeddings=self.padded_num_embeddings,
        )
        fused = self._fuse(hidden_states, embeddings)
        if token_mask is None:
            return fused
        return torch.where(token_mask.to(torch.bool).unsqueeze(-1).unsqueeze(-1), fused, hidden_states)


__all__ = ["EngramModule", "NgramHashMapping"]
hyper_parallel/components/modules/mhc.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
        module_fqn: str = "",
        context: Mapping[str, Any] | None = None,
    ) -> None:
        """Reuse the source module's ``fn/base/scale`` parameter layout."""
        super().__init__()
        del module_fqn, context
        required = ("input_norm", "fn", "base", "scale", "hc_mult")
        missing = [name for name in required if not hasattr(module, name)]
        if missing:
            raise TypeError(f"pipelined MHC source is missing required attributes: {missing}")
        self.input_norm = module.input_norm
        self.fn = module.fn
        self.base = module.base
        self.scale = module.scale
        self.hc_mult = int(module.hc_mult)
        self.hc_sinkhorn_iters = int(module.hc_sinkhorn_iters)
        self.hc_eps = float(module.hc_eps)
        expected_mix = (self.hc_mult + 2) * self.hc_mult
        if tuple(self.fn.shape)[0] != expected_mix or self.base.numel() != expected_mix:
            raise ValueError("pipelined MHC fn/base shapes do not match hc_mult")
        if self.scale.numel() != 3:
            raise ValueError("pipelined MHC scale must contain three values")
        self.train(module.training)

    def forward(
        self,
        hidden_streams: torch.Tensor,
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
        self,
        hidden_streams: torch.Tensor,
    ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
        """Produce the coefficients consumed by the following sublayer."""
        flattened = self.input_norm(hidden_streams.flatten(start_dim=2).float())
        mix = F.linear(  # pylint: disable=not-callable
            flattened, self.fn.float()
        )
        num_stream = self.hc_mult
        pre, post, residual = mix.split(
            [num_stream, num_stream, num_stream * num_stream], dim=-1
        )
        pre_bias, post_bias, residual_bias = self.base.split(
            [num_stream, num_stream, num_stream * num_stream]
        )
        pre_scale, post_scale, residual_scale = self.scale.unbind(0)
        pre = torch.sigmoid(pre * pre_scale + pre_bias) + self.hc_eps
        post = 2 * torch.sigmoid(post * post_scale + post_bias)
        residual = residual.view(*residual.shape[:-1], num_stream, num_stream)
        residual = residual * residual_scale + residual_bias.view(num_stream, num_stream)
        if hidden_streams.device.type == "npu" and hasattr(torch.ops.custom, "npu_sinkhorn"):
            residual = sinkhorn(residual, self.hc_sinkhorn_iters, self.hc_eps)
        else:
            residual = sinkhorn_knopps(residual, self.hc_sinkhorn_iters, self.hc_eps)
        return pre, post, residual


def pipelined_mhc_post(
    sublayer_output: torch.Tensor,
260
261
262
263
264
265
266
267
268
269
    post: torch.Tensor,
    combine: torch.Tensor,
) -> torch.Tensor:
    """Apply the shared high-performance mHC post path to 4-D streams."""
    num_stream = residual.shape[-2]
    flattened = mhc_post(
        sublayer_output,
        residual.flatten(start_dim=2),
        post,
        combine,
268
269
270
271
272
        post,
        combine,
        num_stream,
    )
    return flattened.unflatten(-1, (num_stream, residual.shape[-1]))
hyper_parallel/components/modules/shared_compressed_dsa_attention.py
52
53
54
55
56
57
58
59
60
    gather_sequence: Callable[[torch.Tensor, int], torch.Tensor]

    def wait(self) -> torch.Tensor:
        """Run the fallback gather at the first consumer boundary."""
        return self.gather_sequence(self.tensor, self.sequence_dim)


@dataclass
class SharedCompressedAttentionState:
80
81
82
83
84
85
86
87
88
            consumer_layer: int,
    ) -> torch.Tensor:
        """Return one published tensor or report the broken layer dependency."""
        if source_layer is None or source_layer not in values:
            raise RuntimeError(
                f"layer {consumer_layer} requires {value_name} from source "
                f"layer {source_layer}, but that source has not run in this forward"
            )
        return values[source_layer]
101
102
103
104
105
106
107
108
109
110
111
112
113
        )

    def publish_index_key(self, source_layer: int, value: torch.Tensor) -> None:
        """Publish the Full Indexer's shared key tensor."""
        self.index_key_by_source[source_layer] = value

    def require_index_key(self, source_layer: int | None, consumer_layer: int) -> torch.Tensor:
        """Read the Indexer key associated with a compressed-KV source."""
        return self._require(
            self.index_key_by_source,
            source_layer,
            "Indexer key",
            consumer_layer,
114
115
116
117
118
119
120
121
122
123
124
125
126
        )

    def publish_topk_indices(self, source_layer: int, value: torch.Tensor) -> None:
        """Publish one Full/Reindex layer's token selection."""
        self.topk_indices_by_source[source_layer] = value

    def require_topk_indices(self, source_layer: int | None, consumer_layer: int) -> torch.Tensor:
        """Read the latest configured Full/Reindex selection."""
        return self._require(
            self.topk_indices_by_source,
            source_layer,
            "Top-K indices",
            consumer_layer,
127
128
129
130
131
132
133
134
135
        )

    def publish_candidate_blocks(self, source_layer: int, value: torch.Tensor) -> None:
        """Publish hierarchical candidate blocks from the configured source."""
        self.candidate_blocks_by_source[source_layer] = value

    def require_candidate_blocks(
            self,
            source_layer: int | None,
135
136
137
138
139
140
141
142
143
            source_layer: int | None,
            consumer_layer: int,
    ) -> torch.Tensor:
        """Read hierarchical candidates for a later Reindex layer."""
        return self._require(
            self.candidate_blocks_by_source,
            source_layer,
            "candidate blocks",
            consumer_layer,
171
172
173
174
175
176
177
178
179
180
181
    launch_sequence: Callable[[torch.Tensor, int], SequenceGatherHandle] | None = None

    def launch(self, tensor: torch.Tensor, sequence_dim: int) -> SequenceGatherHandle:
        """Launch an async gather or create a deferred synchronous gather."""
        if self.launch_sequence is not None:
            return self.launch_sequence(tensor, sequence_dim)
        return _DeferredSequenceGather(tensor, sequence_dim, self.gather_sequence)


@dataclass(frozen=True)
class SharedCompressedAttentionTPContext:
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
    global_sequence_length: int

    def local_segment_starts(self, device: torch.device) -> torch.Tensor:
        """Return each local query's global packed-sample start position."""
        boundaries = self.cu_seq_lens.to(device=device, dtype=torch.long)
        if boundaries.ndim != 1 or boundaries.numel() < 2 or int(boundaries[0]) != 0:
            raise ValueError("packed cu_seq_lens must be one-dimensional and start with zero")
        if int(boundaries[-1]) != self.global_sequence_length:
            raise ValueError(
                "packed cu_seq_lens must cover global_sequence_length: "
                f"{int(boundaries[-1])} versus {self.global_sequence_length}"
            )
        if torch.any(boundaries[1:] <= boundaries[:-1]):
            raise ValueError("packed cu_seq_lens must be strictly increasing")
        query_positions = torch.arange(
            self.local_query_start,
            self.local_query_start + self.local_query_length,
            device=device,
        )
        segment_ids = torch.bucketize(query_positions, boundaries[1:], right=True)
        return boundaries[:-1].index_select(0, segment_ids).unsqueeze(0)

    def validate_compression_alignment(self, compress_ratio: int) -> None:
        """Reject packed samples whose compressor groups would cross boundaries."""
        if compress_ratio <= 1:
            return
        boundaries = self.cu_seq_lens.to(dtype=torch.long)
        misaligned = boundaries[boundaries.remainder(compress_ratio) != 0]
        if misaligned.numel():
            raise ValueError(
                "packed sample boundaries must align with the CSA2 compression ratio "
                f"{compress_ratio}; misaligned boundaries={misaligned.tolist()}"
            )
229
230
231
232
233
234
235
236
237
238
239


def _rotate_half(tensor: torch.Tensor) -> torch.Tensor:
    """Rotate adjacent real/imaginary pairs for V4 interleaved RoPE."""
    first = tensor[..., 0::2]
    second = tensor[..., 1::2]
    return torch.stack((-second, first), dim=-1).flatten(-2)


def _apply_v41_rope(
        tensor: torch.Tensor,
241
242
243
244
245
246
247
248
249
250
251
252
253
254
        sin: torch.Tensor,
        unsqueeze_dim: int = 1,
) -> torch.Tensor:
    """Apply V4 interleaved RoPE to the trailing rotary channels."""
    cos = cos.repeat_interleave(2, dim=-1).unsqueeze(unsqueeze_dim)
    sin = sin.repeat_interleave(2, dim=-1).unsqueeze(unsqueeze_dim)
    rope_dim = cos.shape[-1]
    pass_through, rotary = tensor[..., :-rope_dim], tensor[..., -rope_dim:]
    rotary = ((rotary.float() * cos) + (_rotate_half(rotary).float() * sin)).to(tensor.dtype)
    return torch.cat((pass_through, rotary), dim=-1)


def build_sliding_window_indices(
        batch_size: int,
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
        query_offset: int = 0,
        key_length: int | None = None,
) -> torch.Tensor:
    """Build fixed-width causal sliding-window indices."""
    if key_length is None:
        key_length = sequence_length
    width = min(key_length, window_size)
    query = torch.arange(
        query_offset,
        query_offset + sequence_length,
        device=device,
    ).unsqueeze(-1)
    offsets = torch.arange(width - 1, -1, -1, device=device)
    indices = query - offsets
    indices = indices.masked_fill(indices < 0, -1)
    return indices.unsqueeze(0).expand(batch_size, -1, -1)


def compressed_causal_topk(
        query: torch.Tensor,
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
    groups of ``compress_ratio`` source tokens. This implementation retains
    the source rule ``key < floor((query + 1) / ratio)`` while using batched
    matrix multiplication and bounded query chunks on accelerator cores.
    """
    if compress_ratio <= 0:
        raise ValueError(f"compress_ratio must be positive, got {compress_ratio}")
    if query_chunk_size <= 0:
        raise ValueError(f"query_chunk_size must be positive, got {query_chunk_size}")
    batch_size, sequence_length, num_heads, head_dim = query.shape
    if key.ndim != 3 or key.shape[0] != batch_size or key.shape[2] != head_dim:
        raise ValueError(
            "compressed index key must have shape [batch, compressed_sequence, head_dim]"
        )
    if merge_weight.shape != query.shape[:3]:
        raise ValueError(
            f"merge_weight must have shape {tuple(query.shape[:3])}, got {tuple(merge_weight.shape)}"
        )
    compressed_length = key.shape[1]
    if minimum_key_indices is not None and minimum_key_indices.shape != query.shape[:2]:
        raise ValueError("minimum_key_indices must have shape [batch, query]")
    top_k = min(sparse_count, compressed_length)
    if top_k <= 0:
        return torch.empty(
            batch_size,
            sequence_length,
            0,
            dtype=torch.int32,
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
            dtype=torch.int32,
            device=query.device,
        )

    key_fp32 = key.float().transpose(1, 2).unsqueeze(1)
    key_positions = torch.arange(compressed_length, device=query.device).view(1, 1, -1)
    selected_chunks = []
    for start in range(0, sequence_length, query_chunk_size):
        end = min(start + query_chunk_size, sequence_length)
        scores = torch.matmul(query[:, start:end].float(), key_fp32).relu_()
        scores = (scores * merge_weight[:, start:end].float().unsqueeze(-1)).sum(dim=2)
        if reduce_sum is not None:
            scores = reduce_sum(scores)
        visible = (
            torch.arange(query_offset + start, query_offset + end, device=query.device) + 1
        ) // compress_ratio
        scores.masked_fill_(key_positions >= visible.view(1, -1, 1), float("-inf"))
        if minimum_key_indices is not None:
            minimum = minimum_key_indices[:, start:end].unsqueeze(-1)
            scores.masked_fill_(key_positions < minimum, float("-inf"))
        top = scores.topk(top_k, dim=-1, sorted=False)
        indices = top.indices.masked_fill(~torch.isfinite(top.values), compressed_length)
        indices = indices.sort(dim=-1).values
        indices = indices.masked_fill(indices == compressed_length, -1)
        selected_chunks.append(indices.to(torch.int32))
    return torch.cat(selected_chunks, dim=1)


def select_candidate_blocks(
        logits: torch.Tensor,
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
    The newest reachable partial block is pinned into the candidate set, then
    the remaining blocks compete by their maximum position score. ``logits``
    must already contain ``-inf`` at causally unreachable positions.
    """
    if topk_blocks <= 0:
        raise ValueError(f"topk_blocks must be positive, got {topk_blocks}")
    if block_size <= 0:
        raise ValueError(f"block_size must be positive, got {block_size}")
    width = logits.shape[-1]
    if width == 0:
        return torch.zeros_like(logits, dtype=torch.bool)
    padding = -width % block_size
    scores = torch.nn.functional.pad(logits, (0, padding), value=float("-inf"))
    scores = scores.unflatten(-1, (-1, block_size)).amax(dim=-1)
    num_blocks = scores.shape[-1]
    if isinstance(compress_lens, torch.Tensor):
        last = (compress_lens.to(device=logits.device) - 1) // block_size
    else:
        last = (compress_lens - 1) // block_size
    block_positions = torch.arange(num_blocks, device=logits.device)
    scores = scores.masked_fill(block_positions == last, float("inf"))
    top = scores.topk(min(topk_blocks, num_blocks), dim=-1)
    keep = torch.zeros_like(scores, dtype=torch.bool)
    keep.scatter_(-1, top.indices, top.values > float("-inf"))
    return keep.repeat_interleave(block_size, dim=-1)[..., :width]


def select_candidate_block_indices(
        logits: torch.Tensor,
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
    Storing block ids uses ``block_size`` times less cross-layer state than
    expanded position ids and avoids the full ``[batch, query, key]`` boolean
    mask used by the released inference reference. Invalid slots are ``-1``.
    """
    if topk_blocks <= 0:
        raise ValueError(f"topk_blocks must be positive, got {topk_blocks}")
    if block_size <= 0:
        raise ValueError(f"block_size must be positive, got {block_size}")
    width = logits.shape[-1]
    if width == 0:
        return torch.empty(*logits.shape[:-1], 0, dtype=torch.int32, device=logits.device)
    padding = -width % block_size
    scores = torch.nn.functional.pad(logits, (0, padding), value=float("-inf"))
    scores = scores.unflatten(-1, (-1, block_size)).amax(dim=-1)
    num_blocks = scores.shape[-1]
    if isinstance(compress_lens, torch.Tensor):
        last = (compress_lens.to(device=logits.device) - 1) // block_size
    else:
        last = (compress_lens - 1) // block_size
    block_positions = torch.arange(num_blocks, device=logits.device)
    scores = scores.masked_fill(block_positions == last, float("inf"))
    top = scores.topk(min(topk_blocks, num_blocks), dim=-1, sorted=False)
    return top.indices.masked_fill(top.values == float("-inf"), -1).to(torch.int32)


def compressed_causal_candidates(
        query: torch.Tensor,
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
        reduce_sum: Callable[[torch.Tensor], torch.Tensor] | None = None,
        minimum_key_indices: torch.Tensor | None = None,
) -> torch.Tensor:
    """Build compact blockwise candidate ids in bounded query chunks."""
    _, sequence_length, _, _ = query.shape
    compressed_length = key.shape[1]
    if minimum_key_indices is not None and minimum_key_indices.shape != query.shape[:2]:
        raise ValueError("minimum_key_indices must have shape [batch, query]")
    key_fp32 = key.float().transpose(1, 2).unsqueeze(1)
    key_positions = torch.arange(compressed_length, device=query.device).view(1, 1, -1)
    candidate_chunks = []
    for start in range(0, sequence_length, query_chunk_size):
        end = min(start + query_chunk_size, sequence_length)
        scores = torch.matmul(query[:, start:end].float(), key_fp32).relu_()
        scores = (scores * merge_weight[:, start:end].float().unsqueeze(-1)).sum(dim=2)
        if reduce_sum is not None:
            scores = reduce_sum(scores)
        visible = (
            torch.arange(query_offset + start, query_offset + end, device=query.device) + 1
        ) // compress_ratio
        scores.masked_fill_(key_positions >= visible.view(1, -1, 1), float("-inf"))
        if minimum_key_indices is not None:
            minimum = minimum_key_indices[:, start:end].unsqueeze(-1)
            scores.masked_fill_(key_positions < minimum, float("-inf"))
        candidate_chunks.append(
            select_candidate_block_indices(
                scores,
                visible.view(1, -1, 1),
                topk_blocks,
453
454
455
456
457
458
459
460
461
                topk_blocks,
                block_size,
            )
        )
    return torch.cat(candidate_chunks, dim=1)


def compressed_causal_topk_and_candidates(
        query: torch.Tensor,
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
        reduce_sum: Callable[[torch.Tensor], torch.Tensor] | None = None,
        minimum_key_indices: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
    """Select Full-layer Top-K and candidate blocks from one score pass."""
    if compress_ratio <= 0:
        raise ValueError(f"compress_ratio must be positive, got {compress_ratio}")
    if query_chunk_size <= 0:
        raise ValueError(f"query_chunk_size must be positive, got {query_chunk_size}")
    batch_size, sequence_length, _, head_dim = query.shape
    if key.ndim != 3 or key.shape[0] != batch_size or key.shape[2] != head_dim:
        raise ValueError(
            "compressed index key must have shape [batch, compressed_sequence, head_dim]"
        )
    if merge_weight.shape != query.shape[:3]:
        raise ValueError(
            f"merge_weight must have shape {tuple(query.shape[:3])}, got {tuple(merge_weight.shape)}"
        )
    compressed_length = key.shape[1]
    if minimum_key_indices is not None and minimum_key_indices.shape != query.shape[:2]:
        raise ValueError("minimum_key_indices must have shape [batch, query]")
    top_k = min(sparse_count, compressed_length)
    if top_k <= 0:
        empty_topk = torch.empty(
            batch_size,
            sequence_length,
            0,
            dtype=torch.int32,
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
            0,
            dtype=torch.int32,
            device=query.device,
        )
        return empty_topk, empty_topk

    key_fp32 = key.float().transpose(1, 2).unsqueeze(1)
    key_positions = torch.arange(compressed_length, device=query.device).view(1, 1, -1)
    selected_chunks = []
    candidate_chunks = []
    for start in range(0, sequence_length, query_chunk_size):
        end = min(start + query_chunk_size, sequence_length)
        scores = torch.matmul(query[:, start:end].float(), key_fp32).relu_()
        scores = (scores * merge_weight[:, start:end].float().unsqueeze(-1)).sum(dim=2)
        if reduce_sum is not None:
            scores = reduce_sum(scores)
        visible = (
            torch.arange(query_offset + start, query_offset + end, device=query.device) + 1
        ) // compress_ratio
        scores.masked_fill_(key_positions >= visible.view(1, -1, 1), float("-inf"))
        if minimum_key_indices is not None:
            minimum = minimum_key_indices[:, start:end].unsqueeze(-1)
            scores.masked_fill_(key_positions < minimum, float("-inf"))
        top = scores.topk(top_k, dim=-1, sorted=False)
        indices = top.indices.masked_fill(~torch.isfinite(top.values), compressed_length)
        indices = indices.sort(dim=-1).values
        selected_chunks.append(indices.masked_fill(indices == compressed_length, -1).to(torch.int32))
        candidate_chunks.append(
            select_candidate_block_indices(
                scores,
                visible.view(1, -1, 1),
                topk_blocks,
527
528
529
530
531
532
533
534
535
                topk_blocks,
                block_size,
            )
        )
    return torch.cat(selected_chunks, dim=1), torch.cat(candidate_chunks, dim=1)


def compressed_candidate_topk(
        query: torch.Tensor,
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
        reduce_sum: Callable[[torch.Tensor], torch.Tensor] | None = None,
        minimum_key_indices: torch.Tensor | None = None,
) -> torch.Tensor:
    """Score only hierarchical candidate blocks and return global key ids."""
    if compress_ratio <= 0:
        raise ValueError(f"compress_ratio must be positive, got {compress_ratio}")
    if block_size <= 0:
        raise ValueError(f"block_size must be positive, got {block_size}")
    if query_chunk_size <= 0:
        raise ValueError(f"query_chunk_size must be positive, got {query_chunk_size}")
    batch_size, sequence_length, _, head_dim = query.shape
    if key.ndim != 3 or key.shape[0] != batch_size or key.shape[2] != head_dim:
        raise ValueError(
            "compressed index key must have shape [batch, compressed_sequence, head_dim]"
        )
    if merge_weight.shape != query.shape[:3]:
        raise ValueError(
            f"merge_weight must have shape {tuple(query.shape[:3])}, got {tuple(merge_weight.shape)}"
        )
    if candidate_blocks.ndim != 3 or candidate_blocks.shape[:2] != query.shape[:2]:
        raise ValueError(
            "candidate_blocks must have shape [batch, query, candidate_blocks], "
            f"got {tuple(candidate_blocks.shape)}"
        )
    if minimum_key_indices is not None and minimum_key_indices.shape != query.shape[:2]:
        raise ValueError("minimum_key_indices must have shape [batch, query]")

    compressed_length = key.shape[1]
    candidate_width = candidate_blocks.shape[-1] * block_size
    top_k = min(sparse_count, candidate_width)
    if top_k <= 0:
        return torch.empty(
            batch_size,
            sequence_length,
            0,
            dtype=torch.int32,
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
            dtype=torch.int32,
            device=query.device,
        )

    block_offsets = torch.arange(block_size, device=query.device)
    batch_indices = torch.arange(batch_size, device=query.device).view(-1, 1, 1)
    selected_chunks = []
    for start in range(0, sequence_length, query_chunk_size):
        end = min(start + query_chunk_size, sequence_length)
        blocks = candidate_blocks[:, start:end].long()
        block_positions = blocks.unsqueeze(-1) * block_size + block_offsets
        positions = block_positions.flatten(-2)
        visible = (
            torch.arange(query_offset + start, query_offset + end, device=query.device) + 1
        ) // compress_ratio
        valid = (blocks.unsqueeze(-1) >= 0).expand_as(block_positions).flatten(-2)
        valid = valid & (positions < compressed_length) & (positions < visible.view(1, -1, 1))
        if minimum_key_indices is not None:
            valid = valid & (positions >= minimum_key_indices[:, start:end].unsqueeze(-1))
        safe_positions = positions.clamp(min=0, max=max(compressed_length - 1, 0))
        selected_key = key[batch_indices, safe_positions].float()
        dots = torch.einsum(
            "bchd,bckd->bchk",
            query[:, start:end].float(),
            selected_key,
        ).relu_()
        scores = (dots * merge_weight[:, start:end].float().unsqueeze(-1)).sum(dim=2)
        if reduce_sum is not None:
            scores = reduce_sum(scores)
        scores.masked_fill_(~valid, float("-inf"))
        top = scores.topk(top_k, dim=-1, sorted=False)
        selected = positions.gather(-1, top.indices)
        selected.masked_fill_(~torch.isfinite(top.values), compressed_length)
        selected = selected.sort(dim=-1).values
        selected.masked_fill_(selected == compressed_length, -1)
        selected_chunks.append(selected.to(torch.int32))
    return torch.cat(selected_chunks, dim=1)


class _SharedCompressedIndexerKLLoss(torch.autograd.Function):
    """PanGu-style precomputed-gradient KL loss for compressed index keys."""
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
            query_chunk_size: int,
            reduce_sum: Callable[[torch.Tensor], torch.Tensor] | None,
    ) -> torch.Tensor:
        """Compute sparse KL and save only the three Indexer gradients."""
        batch_size, sequence_length, _, _ = index_query.shape
        denominator = batch_size * sequence_length
        total_loss = index_query.new_zeros((), dtype=torch.float32)
        grad_index_query = torch.zeros_like(index_query, dtype=torch.float32)
        grad_index_key = torch.zeros_like(index_key, dtype=torch.float32)
        grad_merge_weight = torch.zeros_like(merge_weight, dtype=torch.float32)

        with torch.no_grad():
            for start in range(0, sequence_length, query_chunk_size):
                end = min(start + query_chunk_size, sequence_length)
                selected = topk_indices[:, start:end]
                valid = selected >= 0
                valid_rows = valid.any(dim=-1)
                if not torch.any(valid_rows):
                    continue
                safe_indices = selected.clamp_min(0).long()
                batch_indices = torch.arange(batch_size, device=index_query.device).view(-1, 1, 1)

                query_chunk = index_query[:, start:end].float()
                weight_chunk = merge_weight[:, start:end].float()
                selected_index_key = index_key[batch_indices, safe_indices].float()
                index_dots = torch.einsum(
                    "bcid,bckd->bcik", query_chunk, selected_index_key
                )
                index_relu = index_dots.relu()
                index_scores = (index_relu * weight_chunk.unsqueeze(-1)).sum(dim=2)
                if reduce_sum is not None:
                    index_scores = reduce_sum(index_scores)
                index_scores.masked_fill_(~valid, -1.0e9)

                selected_attention_key = compressed_key[batch_indices, safe_indices].float()
                attention_scores = torch.einsum(
                    "bhcd,bckd->bhck",
                    attention_query[:, :, start:end].float(),
                    selected_attention_key,
                ) * attention_scale
                attention_scores.masked_fill_(~valid.unsqueeze(1), -1.0e9)
                sink_logits = sinks.float().view(1, -1, 1, 1).expand(
                    batch_size, -1, end - start, -1
                )
                target = torch.cat((attention_scores, sink_logits), dim=-1).softmax(dim=-1)
                target = target[..., :-1].masked_fill(~valid.unsqueeze(1), 0.0).sum(dim=1)
                if reduce_sum is not None:
                    target = reduce_sum(target)
                target = target / target.sum(dim=-1, keepdim=True).clamp_min(
                    torch.finfo(torch.float32).tiny
                )

                log_prediction = index_scores.log_softmax(dim=-1)
                target_log = target.clamp_min(torch.finfo(torch.float32).tiny).log()
                row_loss = (target * (target_log - log_prediction)).sum(dim=-1)
                total_loss.add_(row_loss[valid_rows].sum() * (loss_coeff / denominator))

                grad_scores = (log_prediction.exp() - target) * (loss_coeff / denominator)
                grad_scores.masked_fill_(~valid, 0.0)
                grad_dots = (
                    grad_scores.unsqueeze(2)
                    * weight_chunk.unsqueeze(-1)
                    * (index_dots > 0).to(index_dots.dtype)
                )
                grad_index_query[:, start:end] = torch.einsum(
                    "bcik,bckd->bcid", grad_dots, selected_index_key
                )
                grad_merge_weight[:, start:end] = (
                    grad_scores.unsqueeze(2) * index_relu
                ).sum(dim=-1)
                selected_key_grad = torch.einsum(
                    "bcik,bcid->bckd", grad_dots, query_chunk
                )
                selected_key_grad.masked_fill_(~valid.unsqueeze(-1), 0.0)
                scatter_indices = safe_indices.unsqueeze(-1).expand_as(selected_key_grad)
                grad_index_key.scatter_add_(
                    1,
                    scatter_indices.reshape(batch_size, -1, index_key.shape[-1]),
                    selected_key_grad.reshape(batch_size, -1, index_key.shape[-1]),
                )
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
                    scatter_indices.reshape(batch_size, -1, index_key.shape[-1]),
                    selected_key_grad.reshape(batch_size, -1, index_key.shape[-1]),
                )

        ctx.save_for_backward(grad_index_query, grad_index_key, grad_merge_weight)
        return total_loss

    @staticmethod
    def backward(ctx: Any, grad_loss: torch.Tensor) -> tuple:
        """Return the precomputed Indexer gradients and detach the teacher."""
        grad_index_query, grad_index_key, grad_merge_weight = ctx.saved_tensors
        return (
            grad_index_query * grad_loss,
            grad_index_key * grad_loss,
            grad_merge_weight * grad_loss,
            *((None,) * 8),
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
    The target first softmaxes selected main-attention scores per attention
    head, then averages and L1-normalizes across heads. Indexer inputs are
    detached by the caller, so this objective updates only Indexer parameters.
    """
    if query_chunk_size <= 0:
        raise ValueError(f"query_chunk_size must be positive, got {query_chunk_size}")
    if not loss_coeff:
        return index_query.sum(dtype=torch.float32) * 0.0
    if topk_indices.shape[:2] != index_query.shape[:2]:
        raise ValueError("topk_indices and index_query must share batch/query dimensions")
    return _SharedCompressedIndexerKLLoss.apply(
        index_query,
        index_key,
        merge_weight,
        attention_query.detach(),
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
    """Reusable V4.1 compressed Lightning Indexer semantics."""

    def __init__(self, module: nn.Module) -> None:
        """Transfer the source projections without changing checkpoint names."""
        super().__init__()
        required = ("q_b_proj", "weights_proj")
        missing = [name for name in required if not hasattr(module, name)]
        if missing:
            raise TypeError(f"shared compressed DSA indexer is missing: {missing}")
        for name, child in module._modules.items():  # pylint: disable=protected-access
            self.add_module(name, child)
        for name, parameter in module._parameters.items():  # pylint: disable=protected-access
            self.register_parameter(name, parameter)
        self.compress_ratio = module.compress_ratio
        self.global_num_heads = module.num_heads
        self.head_dim = module.head_dim
        self.index_topk = module.index_topk
        self.owns_key = module.owns_key
        self.is_candidate_source = module.is_candidate_source
        self.uses_candidates = module.uses_candidates
        self.candidate_topk_blocks = module.candidate_topk_blocks
        self.candidate_block_size = module.candidate_block_size
        self.loss_coeff = module.loss_coeff
        self.query_chunk_size = int(getattr(module, "query_chunk_size", 256))
        self.train(module.training)

    def _project_index_key(
            self,
            latent: torch.Tensor | None,
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
            index_key: torch.Tensor | None,
            cp_context: SharedCompressedAttentionCPContext | None,
    ) -> tuple[torch.Tensor, "SequenceGatherHandle | None"]:
        """Project or adopt the index key and launch its CP gather."""
        if self.owns_key:
            if latent is None:
                raise RuntimeError("a Full Indexer requires the compressor latent")
            key = self.k_norm(self.wk(latent.detach()))
            compressed_length = key.shape[1]
            key_cos = cos[:, :compressed_length * self.compress_ratio:self.compress_ratio]
            key_sin = sin[:, :compressed_length * self.compress_ratio:self.compress_ratio]
            key = _apply_v41_rope(key.unsqueeze(1), key_cos, key_sin).squeeze(1)
            key_handle = cp_context.launch(key, 1) if cp_context is not None else None
            return key, key_handle
        if index_key is None:
            raise RuntimeError("a Reindex layer requires index K from the preceding Full layer")
        return index_key, None

    def forward(
            self,
            hidden_states: torch.Tensor,
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
            query_offset: int = 0,
            minimum_key_indices: torch.Tensor | None = None,
    ) -> SharedCompressedIndexerOutput:
        """Project local queries and score them against global compressed keys."""
        batch_size, sequence_length, _ = hidden_states.shape
        cos, sin = compress_position_embeddings

        key, key_handle = self._project_index_key(latent, cos, sin, index_key, cp_context)

        query = self.q_b_proj(query_residual.detach())
        if query.shape[-1] % self.head_dim:
            raise ValueError(
                f"Indexer query width {query.shape[-1]} is not divisible by head_dim {self.head_dim}"
            )
        local_num_heads = query.shape[-1] // self.head_dim
        query = query.view(batch_size, sequence_length, local_num_heads, self.head_dim)
        query = _apply_v41_rope(query.transpose(1, 2), cos, sin).transpose(1, 2)
        merge_weight = self.weights_proj(hidden_states.detach())
        if merge_weight.shape[-1] != local_num_heads:
            raise ValueError(
                "Indexer q_b_proj and weights_proj must use the same TP head shard: "
                f"{local_num_heads} query heads versus {merge_weight.shape[-1]} weights"
            )
        merge_weight = merge_weight * (self.head_dim**-0.5 * self.global_num_heads**-0.5)
        reduce_sum = None if tp_context is None else tp_context.reduce_sum
        if key_handle is not None:
            key = key_handle.wait()
        if self.is_candidate_source:
            topk_indices, candidate_blocks = compressed_causal_topk_and_candidates(
                query,
                key,
                merge_weight,
                compress_ratio=self.compress_ratio,
869
870
871
872
873
874
875
876
877
878
879
880
                query_chunk_size=self.query_chunk_size,
                reduce_sum=reduce_sum,
                minimum_key_indices=minimum_key_indices,
            )
        elif self.uses_candidates:
            if candidate_blocks is None:
                raise RuntimeError("a hierarchical Reindex layer requires candidate block ids")
            topk_indices = compressed_candidate_topk(
                query,
                key,
                merge_weight,
                candidate_blocks,
886
887
888
889
890
891
892
893
894
895
                reduce_sum=reduce_sum,
                minimum_key_indices=minimum_key_indices,
            )
        else:
            candidate_blocks = None
            topk_indices = compressed_causal_topk(
                query,
                key,
                merge_weight,
                compress_ratio=self.compress_ratio,
898
899
900
901
902
903
904
905
906
                query_chunk_size=self.query_chunk_size,
                reduce_sum=reduce_sum,
                minimum_key_indices=minimum_key_indices,
            )
        return SharedCompressedIndexerOutput(
            topk_indices=topk_indices,
            index_key=key,
            candidate_blocks=candidate_blocks,
            index_query=query,
915
916
917
918
919
920
921
922
923
924
925
926
927
        sinks: torch.Tensor,
        scale: float,
) -> torch.Tensor:
    """Compute indexed attention through a dense mask for numeric validation."""
    batch_size, num_heads, sequence_length, _ = query.shape
    key_length = key_value.shape[2]
    valid = sparse_indices >= 0
    safe_indices = sparse_indices.clamp_min(0).long()
    allowed_counts = torch.zeros(
        batch_size,
        sequence_length,
        key_length,
        dtype=torch.int32,
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
        key_length,
        dtype=torch.int32,
        device=query.device,
    )
    allowed_counts.scatter_add_(2, safe_indices, valid.to(torch.int32))
    allowed = allowed_counts > 0
    logits = torch.matmul(query, key_value.transpose(-1, -2)) * scale
    logits = logits.masked_fill(~allowed.unsqueeze(1), float("-inf"))
    sink_logits = sinks.view(1, num_heads, 1, 1).expand(batch_size, -1, sequence_length, -1)
    probabilities = torch.cat((logits, sink_logits), dim=-1).softmax(dim=-1, dtype=torch.float32)
    return torch.matmul(probabilities[..., :-1].to(key_value.dtype), key_value).transpose(1, 2)


class _NpuSparseAttentionWithScalarSink(torch.autograd.Function):
    """Autograd bridge adding V4.1's scalar sink to sparse attention."""
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
            rope_head_dim: int,
            scale: float,
    ) -> torch.Tensor:
        """Run sparse attention and merge the analytically zero-valued sink."""
        import omni_training_custom_ops  # noqa: F401  # pylint: disable=C0415,unused-import

        batch_size, num_heads, sequence_length, head_dim = query.shape
        key_length = key_value.shape[2]
        query_rope = query.new_zeros(batch_size, num_heads, sequence_length, rope_head_dim)
        key_rope = key_value.new_zeros(batch_size, 1, key_length + 1, rope_head_dim)
        dummy_key_value = key_value.new_zeros(batch_size, 1, 1, head_dim)
        key_value_with_dummy = torch.cat((key_value, dummy_key_value), dim=2)
        valid_indices = sparse_indices >= 0
        safe_indices = sparse_indices.masked_fill(~valid_indices, key_length)
        query_lengths = torch.arange(
            sequence_length,
            (batch_size + 1) * sequence_length,
            sequence_length,
            dtype=torch.int32,
966
967
968
969
970
971
972
973
974
            sequence_length,
            dtype=torch.int32,
            device=query.device,
        )
        key_lengths = torch.arange(
            key_length + 1,
            (batch_size + 1) * (key_length + 1),
            key_length + 1,
            dtype=torch.int32,
973
974
975
976
977
978
979
980
981
982
            key_length + 1,
            dtype=torch.int32,
            device=query.device,
        )
        sparse_indices_tnd = safe_indices.reshape(-1, 1, safe_indices.shape[-1]).to(torch.int32)
        output, softmax_max, softmax_sum = torch.ops.custom.npu_sparse_flash_attention_enhance(
            query.transpose(1, 2).reshape(-1, num_heads, head_dim),
            key_value_with_dummy.transpose(1, 2).reshape(-1, 1, head_dim),
            key_value_with_dummy.transpose(1, 2).reshape(-1, 1, head_dim),
            sparse_indices_tnd,
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
            sparse_mode=0,
            attention_mode=2,
            return_softmax_lse=True,
        )
        output = output.view(batch_size, sequence_length, num_heads, head_dim)
        sparse_max = softmax_max.squeeze(0).view(batch_size, sequence_length, num_heads)
        sparse_sum = softmax_sum.squeeze(0).view(batch_size, sequence_length, num_heads)
        sink_logits = sinks.float().view(1, 1, num_heads)
        combined_max = torch.maximum(sparse_max, sink_logits)
        dummy_count = (~valid_indices).sum(dim=-1, dtype=torch.float32).unsqueeze(-1)
        dummy_mass = dummy_count * torch.exp(-sparse_max)
        actual_sparse_sum = (sparse_sum - dummy_mass).clamp_min(torch.finfo(torch.float32).tiny)
        sparse_mass = actual_sparse_sum * torch.exp(sparse_max - combined_max)
        sink_mass = torch.exp(sink_logits - combined_max)
        desired_sum = sparse_mass + sink_mass
        kernel_mass = sparse_sum * torch.exp(sparse_max - combined_max)
        output_scale = kernel_mass / desired_sum
        sink_probability = sink_mass / desired_sum
        rescaled_output = output * output_scale.unsqueeze(-1).to(output.dtype)
        ctx.save_for_backward(
            query,
            key_value,
            sparse_indices_tnd,
            softmax_max,
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
            rescaled_output,
            output_scale,
            sink_probability,
        )
        ctx.params = (rope_head_dim, scale, query_lengths, key_lengths)
        return rescaled_output

    @staticmethod
    def backward(ctx: Any, grad_output: torch.Tensor) -> tuple:
        """Apply sparse-attention backward and the analytic sink gradient."""
        (
            query,
            key_value,
            sparse_indices_tnd,
            softmax_max,
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
            rescaled_output,
            output_scale,
            sink_probability,
        ) = ctx.saved_tensors
        rope_head_dim, scale, query_lengths, key_lengths = ctx.params
        batch_size, num_heads, sequence_length, head_dim = query.shape
        key_length = key_value.shape[2]
        query_tnd = query.transpose(1, 2).reshape(-1, num_heads, head_dim)
        query_rope = query.new_zeros(batch_size, num_heads, sequence_length, rope_head_dim)
        dummy_key_value = key_value.new_zeros(batch_size, 1, 1, head_dim)
        key_value_with_dummy = torch.cat((key_value, dummy_key_value), dim=2)
        key_tnd = key_value_with_dummy.transpose(1, 2).reshape(-1, 1, head_dim)
        key_rope = key_value.new_zeros(batch_size, 1, key_length + 1, rope_head_dim)
        scaled_grad = grad_output * output_scale.unsqueeze(-1).to(grad_output.dtype)
        grad_query, grad_key, grad_value, _, _ = torch.ops.custom.npu_sparse_flash_attention_grad_enhance(
            query_tnd,
            key_tnd,
            key_tnd,
            sparse_indices_tnd,
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
            sparse_mode=0,
            attention_mode=2,
            deterministic=torch.are_deterministic_algorithms_enabled(),
        )
        grad_query = grad_query.view(batch_size, sequence_length, num_heads, head_dim).transpose(1, 2)
        grad_key = (grad_key + grad_value).view(batch_size, key_length + 1, 1, head_dim)
        grad_key = grad_key[:, :key_length].transpose(1, 2)
        sink_grad = -(sink_probability * (grad_output.float() * rescaled_output.float()).sum(-1)).sum((0, 1))
        return grad_query, grad_key, None, sink_grad, None, None


def npu_sparse_attention_with_scalar_sink(
        query: torch.Tensor,
1079
1080
1081
1082
1083
1084
1085
1086
1087
        rope_head_dim: int,
        scale: float,
) -> torch.Tensor:
    """Run V4.1 sparse attention through enhanced Ascend operators."""
    return _NpuSparseAttentionWithScalarSink.apply(
        query,
        key_value,
        sparse_indices,
        sinks,
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
            replace_indexer: bool,
            use_optimized_sparse_attention: bool,
    ) -> None:
        """Transfer source state and configure the execution implementation."""
        super().__init__()
        for name, child in module._modules.items():  # pylint: disable=protected-access
            self.add_module(name, child)
        for name, parameter in module._parameters.items():  # pylint: disable=protected-access
            self.register_parameter(name, parameter)
        indexer = getattr(self, "indexer", None)
        if (
                replace_indexer
                and indexer is not None
                and not isinstance(indexer, SharedCompressedDSAIndexer)
        ):
            self.indexer = SharedCompressedDSAIndexer(indexer)
        self.config = module.config
        self.layer_idx = module.layer_idx
        self.num_heads = module.num_heads
        self.num_key_value_groups = module.num_key_value_groups
        self.num_groups = module.config.o_groups
        self.compress_ratio = module.compress_ratio
        self.is_kv_source = module.is_kv_source
        self.is_index_source = module.is_index_source
        self.kv_source_layer_idx = module.kv_source_layer_idx
        self.index_source_layer_idx = module.index_source_layer_idx
        self.candidate_source_layer_idx = module.candidate_source_layer_idx
        self.head_dim = module.head_dim
        self.rope_head_dim = module.config.qk_rope_head_dim
        self.sliding_window = module.sliding_window
        self.scaling = module.scaling
        self.use_optimized_sparse_attention = use_optimized_sparse_attention
        self.train(module.training)

    @staticmethod
    def _validate_cp_inputs(
            cp_context: SharedCompressedAttentionCPContext,
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
            compress_ratio: int,
            is_kv_source: bool,
    ) -> tuple[int, int]:
        """Validate contiguous CP shards and return global query geometry."""
        if cp_context.size <= 1 or not 0 <= cp_context.rank < cp_context.size:
            raise ValueError(
                "shared compressed attention CP requires size > 1 and "
                f"0 <= rank < size, got rank={cp_context.rank}, size={cp_context.size}"
            )
        if is_kv_source and sequence_length % compress_ratio:
            raise ValueError(
                "compressed-KV CP requires every local sequence shard to divide "
                f"compress_ratio={compress_ratio}, got {sequence_length}"
            )
        query_offset = cp_context.rank * sequence_length
        if not torch.all(position_ids[..., 0] == query_offset):
            raise ValueError(
                "shared compressed attention CP requires contiguous global position_ids; "
                f"rank {cp_context.rank} expected first position {query_offset}"
            )
        return query_offset, sequence_length * cp_context.size

    def _project_raw_kv(
            self,
            hidden_states: torch.Tensor,
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
            sin: torch.Tensor,
            cp_context: SharedCompressedAttentionCPContext | None,
    ) -> tuple[torch.Tensor | None, SequenceGatherHandle | None]:
        """Project raw K=V and launch CP communication before query GEMMs."""
        key_value = self.kv_norm(self.kv_proj(hidden_states)).unsqueeze(1)
        key_value = _apply_v41_rope(key_value, cos, sin)
        if cp_context is None:
            return key_value, None
        return None, cp_context.launch(key_value, 2)

    def _project_query(
            self,
            hidden_states: torch.Tensor,
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
            cos: torch.Tensor,
            sin: torch.Tensor,
    ) -> tuple[torch.Tensor, torch.Tensor]:
        """Project and normalize the local query heads."""
        batch_size, sequence_length, _ = hidden_states.shape
        query_residual = self.q_a_norm(self.q_a_proj(hidden_states))
        query = self.q_b_proj(query_residual).view(batch_size, sequence_length, -1, self.head_dim)
        query = query.transpose(1, 2)
        return query_residual, _apply_v41_rope(query, cos, sin)

    @staticmethod
    def _validate_forward_inputs(
            hidden_states: torch.Tensor,
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
        int,
        int,
    ]:
        """Pop and validate the shared-attention keyword arguments."""
        if past_key_values is not None:
            raise NotImplementedError("shared compressed DSA training does not support KV cache")
        shared_state = kwargs.pop("shared_attention_state", None)
        if not isinstance(shared_state, SharedCompressedAttentionState):
            raise ValueError("shared_attention_state is required for shared compressed DSA")
        if attention_mask is not None:
            raise NotImplementedError(
                "shared compressed DSA consumes compact packed_seq_params instead of a dense mask"
            )
        batch_size, sequence_length, _ = hidden_states.shape
        packed_sequence = kwargs.pop("packed_seq_params", None)
        if packed_sequence is not None and not isinstance(
                packed_sequence, SharedCompressedPackedSequence
        ):
            raise TypeError(
                "packed_seq_params must be SharedCompressedPackedSequence, "
                f"got {type(packed_sequence).__name__}"
            )
        cp_context = kwargs.pop("shared_attention_cp_context", None)
        if cp_context is not None and not isinstance(cp_context, SharedCompressedAttentionCPContext):
            raise TypeError(
                "shared_attention_cp_context must be SharedCompressedAttentionCPContext, "
                f"got {type(cp_context).__name__}"
            )
        tp_context = kwargs.pop("shared_attention_tp_context", None)
        if tp_context is not None and not isinstance(tp_context, SharedCompressedAttentionTPContext):
            raise TypeError(
                "shared_attention_tp_context must be SharedCompressedAttentionTPContext, "
                f"got {type(tp_context).__name__}"
            )
        return shared_state, packed_sequence, cp_context, tp_context, batch_size, sequence_length

    def _resolve_packed_geometry(
            self,
            packed_sequence: "SharedCompressedPackedSequence | None",
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
            global_sequence_length: int,
            device: torch.device,
    ) -> tuple[torch.Tensor | None, torch.Tensor | None]:
        """Validate packed geometry and derive segment/minimum-key indices."""
        segment_starts = None
        minimum_key_indices = None
        if packed_sequence is not None:
            if batch_size != 1:
                raise ValueError("V4.1 compact packed attention currently requires micro_batch_size=1")
            if (
                    packed_sequence.local_query_start != query_offset
                    or packed_sequence.local_query_length != sequence_length
                    or packed_sequence.global_sequence_length != global_sequence_length
            ):
                packed_geometry = (
                    packed_sequence.local_query_start,
                    packed_sequence.local_query_length,
                    packed_sequence.global_sequence_length,
                )
                raise ValueError(
                    "packed sequence geometry does not match the local CP shard: "
                    f"packed={packed_geometry}, "
                    f"attention={(query_offset, sequence_length, global_sequence_length)}"
                )
            packed_sequence.validate_compression_alignment(self.compress_ratio)
            segment_starts = packed_sequence.local_segment_starts(device)
            if self.compress_ratio:
                minimum_key_indices = segment_starts // self.compress_ratio
        return segment_starts, minimum_key_indices

    def _compute_compressed_kv(
            self,
            hidden_states: torch.Tensor,
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
            shared_state: "SharedCompressedAttentionState",
            cp_context: "SharedCompressedAttentionCPContext | None",
    ) -> tuple[torch.Tensor | None, "SequenceGatherHandle | None"]:
        """Compress local hidden states and publish or launch the compressed KV."""
        latent = None
        compressed_handle = None
        if self.is_kv_source:
            latent, compressed = self.compressor(hidden_states, position_embeddings["compress"])
            if cp_context is None:
                shared_state.publish_compressed_kv(self.layer_idx, compressed)
            else:
                compressed_handle = cp_context.launch(compressed, 1)
        return latent, compressed_handle

    def _run_indexer(
            self,
            hidden_states: torch.Tensor,
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
            query_offset: int,
            minimum_key_indices: torch.Tensor | None,
    ) -> Any | None:
        """Run the indexer on index-source layers and publish its outputs."""
        if not self.is_index_source:
            return None
        index_key = (
            None
            if self.indexer.owns_key
            else shared_state.require_index_key(self.kv_source_layer_idx, self.layer_idx)
        )
        candidate_blocks = (
            shared_state.require_candidate_blocks(
                self.candidate_source_layer_idx,
                self.layer_idx,
            )
1312
1313
1314
1315
1316
1317
1318
1319
1320
                self.layer_idx,
            )
            if self.indexer.uses_candidates else None
        )
        indexer_output = self.indexer(
            hidden_states,
            query_residual,
            latent,
            position_embeddings["compress"],
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
            tp_context=tp_context,
            query_offset=query_offset,
            minimum_key_indices=minimum_key_indices,
        )
        if self.indexer.owns_key:
            shared_state.publish_index_key(self.layer_idx, indexer_output.index_key)
        shared_state.publish_topk_indices(self.layer_idx, indexer_output.topk_indices)
        if indexer_output.candidate_blocks is not None:
            shared_state.publish_candidate_blocks(
                self.layer_idx,
                indexer_output.candidate_blocks,
            )
        return indexer_output

    def _build_sparse_indices(
            self,
            shared_state: "SharedCompressedAttentionState",
1346
1347
1348
1349
1350
1351
1352
1353
1354
            global_sequence_length: int,
            segment_starts: torch.Tensor | None,
    ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
        """Assemble the sliding-window plus compressed sparse index sets."""
        window = build_sliding_window_indices(
            batch_size,
            sequence_length,
            self.sliding_window,
            device,
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
            device,
            query_offset=query_offset,
            key_length=global_sequence_length,
        )
        if segment_starts is not None:
            window.masked_fill_(window < segment_starts.unsqueeze(-1), -1)
        compressed_kv = None
        if not self.compress_ratio:
            return key_value, window, None
        compressed_kv = shared_state.require_compressed_kv(
            self.kv_source_layer_idx,
            self.layer_idx,
        )
        topk_indices = shared_state.require_topk_indices(
            self.index_source_layer_idx,
            self.layer_idx,
        )
        combined_key_value = torch.cat((key_value, compressed_kv.unsqueeze(1)), dim=2)
        compressed_indices = topk_indices.long() + global_sequence_length
        compressed_indices.masked_fill_(topk_indices < 0, -1)
        sparse_indices = torch.cat((window, compressed_indices), dim=-1)
        return combined_key_value, sparse_indices, compressed_kv

    def _apply_indexer_loss(
            self,
            indexer_output: Any | None,
1381
1382
1383
1384
1385
1386
1387
1388
1389
            compressed_kv: torch.Tensor | None,
            tp_context: "SharedCompressedAttentionTPContext | None",
    ) -> torch.Tensor:
        """Auto-scale the query by the indexer KL loss when configured."""
        if (
                indexer_output is not None
                and self.training
                and self.indexer.loss_coeff
                and compressed_kv is not None
1387
1388
1389
1390
1391
1392
1393
1394
1395
                and self.training
                and self.indexer.loss_coeff
                and compressed_kv is not None
        ):
            indexer_loss = shared_compressed_indexer_kl_loss(
                indexer_output.index_query,
                indexer_output.index_key,
                indexer_output.merge_weight,
                query,
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
                loss_coeff=self.indexer.loss_coeff,
                query_chunk_size=self.indexer.query_chunk_size,
                tp_context=tp_context,
            )
            query = aux_loss_auto_scale(query, indexer_loss)
        return query

    def _run_sparse_attention(
            self,
            query: torch.Tensor,
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
            sparse_indices: torch.Tensor,
            device: torch.device,
    ) -> torch.Tensor:
        """Dispatch to the enhanced NPU kernel or the reference implementation."""
        if self.use_optimized_sparse_attention and device.type == "npu":
            return npu_sparse_attention_with_scalar_sink(
                query,
                combined_key_value,
                sparse_indices,
                self.sinks,
1420
1421
1422
1423
1424
1425
1426
1427
1428
                self.sinks,
                self.rope_head_dim,
                self.scaling,
            )
        return _reference_sparse_attention(
            query,
            combined_key_value,
            sparse_indices,
            self.sinks,
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
            batch_size: int,
            sequence_length: int,
    ) -> torch.Tensor:
        """Undo RoPE and apply the grouped low-rank output projection."""
        attention_output = _apply_v41_rope(attention_output.transpose(1, 2), cos, -sin).transpose(1, 2)
        grouped = attention_output.reshape(batch_size, sequence_length, self.num_groups, -1)
        hidden_per_group = grouped.shape[-1]
        output_per_group = self.o_a_proj.weight.shape[0] // self.num_groups
        projection = self.o_a_proj.weight.view(
            self.num_groups, output_per_group, hidden_per_group
        ).transpose(1, 2)
        flattened = grouped.reshape(-1, self.num_groups, hidden_per_group).transpose(0, 1)
        projected = torch.bmm(flattened, projection).transpose(0, 1)
        projected = projected.reshape(batch_size, sequence_length, -1)
        return self.o_b_proj(projected)

    def forward(
            self,
            hidden_states: torch.Tensor,
1459
1460
1461
1462
1463
1464
1465
1466
1467
            past_key_values: Any | None = None,
            **kwargs: Any,
    ) -> tuple[torch.Tensor, torch.Tensor | None]:
        """Publish or consume compressed KV and execute indexed K=V attention."""
        (
            shared_state,
            packed_sequence,
            cp_context,
            tp_context,
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
            tp_context,
            batch_size,
            sequence_length,
        ) = self._validate_forward_inputs(hidden_states, attention_mask, past_key_values, kwargs)
        query_offset = 0
        global_sequence_length = sequence_length
        if cp_context is not None:
            query_offset, global_sequence_length = self._validate_cp_inputs(
                cp_context,
                position_ids,
                sequence_length,
                self.compress_ratio,
1477
1478
1479
1480
1481
1482
1483
1484
1485
                sequence_length,
                self.compress_ratio,
                self.is_kv_source,
            )
        segment_starts, minimum_key_indices = self._resolve_packed_geometry(
            packed_sequence,
            batch_size,
            sequence_length,
            query_offset,
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
            global_sequence_length,
            hidden_states.device,
        )

        rope_type = "compress" if self.compress_ratio else "main"
        cos, sin = position_embeddings[rope_type]
        key_value, raw_kv_handle = self._project_raw_kv(hidden_states, cos, sin, cp_context)
        query_residual, query = self._project_query(hidden_states, cos, sin)
        latent, compressed_handle = self._compute_compressed_kv(
            hidden_states, position_embeddings, shared_state, cp_context
        )
        indexer_output = self._run_indexer(
            hidden_states,
            query_residual,
            latent,
            position_embeddings,
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
            query_offset,
            minimum_key_indices,
        )

        if raw_kv_handle is not None:
            key_value = raw_kv_handle.wait()
        if compressed_handle is not None:
            shared_state.publish_compressed_kv(self.layer_idx, compressed_handle.wait())
        if key_value is None:
            raise RuntimeError("raw KV all-gather did not produce a tensor")

        combined_key_value, sparse_indices, compressed_kv = self._build_sparse_indices(
            shared_state,
            key_value,
            batch_size,
            sequence_length,
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
            query_offset,
            global_sequence_length,
            segment_starts,
        )
        query = self._apply_indexer_loss(indexer_output, query, compressed_kv, tp_context)
        attention_output = self._run_sparse_attention(
            query, combined_key_value, sparse_indices, hidden_states.device
        )
        output = self._project_output(attention_output, cos, sin, batch_size, sequence_length)
        return output, None


@module_replacement
class SharedCompressedDSAAttention(SharedCompressedDSAAttentionBase):
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
                structural contract.
            module_fqn: Fully qualified source name supplied by replacement.
            context: Read-only replacement context supplied by Trainer.
        """
        del module_fqn, context
        super().__init__(
            module,
            replace_indexer=True,
            use_optimized_sparse_attention=True,
        )
hyper_parallel/components/optim/mixed_precision_optimizer.py
59
60
61
62
63
64
65
66
67
    """
    optimizer_is_dtensor = is_dtensor(optimizer_param)
    gradient_is_dtensor = is_dtensor(gradient)
    if optimizer_is_dtensor != gradient_is_dtensor:
        raise TypeError(
            "Optimizer parameter and gradient must both be DTensors or both be local tensors"
        )

    optimizer_local = to_local_if_dtensor(optimizer_param)
66
67
68
69
70
71
72
73
74

    optimizer_local = to_local_if_dtensor(optimizer_param)
    gradient_local = to_local_if_dtensor(gradient)
    if tuple(gradient_local.shape) != tuple(optimizer_local.shape):
        raise ValueError(
            "Optimizer gradient local shape must match its parameter shard: "
            f"got {tuple(gradient_local.shape)} and {tuple(optimizer_local.shape)}"
        )
    if optimizer_is_dtensor:
72
73
74
75
76
77
78
79
80
            f"got {tuple(gradient_local.shape)} and {tuple(optimizer_local.shape)}"
        )
    if optimizer_is_dtensor:
        if tuple(gradient.shape) != tuple(optimizer_param.shape):
            raise ValueError(
                "Optimizer gradient global shape must match its parameter: "
                f"got {tuple(gradient.shape)} and {tuple(optimizer_param.shape)}"
            )
        if not device_meshes_are_compatible(
80
81
82
83
84
85
86
87
88
89
90
        if not device_meshes_are_compatible(
                gradient.device_mesh,
                optimizer_param.device_mesh,
        ):
            raise ValueError("Optimizer gradient and parameter must use the same device mesh")
        if tuple(gradient.placements) != tuple(optimizer_param.placements):
            raise ValueError("Optimizer gradient and parameter must use the same placements")
        # FSDP main_grad is already an autograd-detached accumulation buffer.
        # Keep that DTensor object intact: detach() rebuilds an uneven empty
        # shard without its logical tensor metadata, which makes a subsequent
        # zeros_like() infer the optimizer state's global shape as zero.
 94
 95
 96
 97
 98
 99
100
101
102
    if (
        gradient_local.device != optimizer_local.device
        or gradient_local.dtype != optimizer_local.dtype
    ):
        prepared_gradient = prepared_gradient.to(
            device=optimizer_local.device,
            dtype=optimizer_local.dtype,
        )
    return prepared_gradient
hyper_parallel/core/optimizer/dtensor_compat.py
161
162
163
164
165
166
167
168
169

def device_meshes_are_compatible(lhs: Any, rhs: Any) -> bool:
    """Return whether two device meshes describe the same topology."""
    if lhs is rhs:
        return True
    lhs_hash = getattr(lhs, "to_hash", None)
    rhs_hash = getattr(rhs, "to_hash", None)
    if callable(lhs_hash) and callable(rhs_hash):
        return (
169
170
171
172
173
174
175
176
177
        return (
            getattr(lhs, "device_type", None) == getattr(rhs, "device_type", None)
            and lhs_hash() == rhs_hash()
        )
    return bool(lhs == rhs)


# lazy exports
_LAZY_RESOLVERS = {
hyper_parallel/core/optimizer/lr_scheduler.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
                )
            self._schedulers_by_name[name].load_state_dict(
                copy.deepcopy(state_dict[name]),
            )
            scheduler = self._schedulers_by_name[name]
            last_lrs = scheduler.get_last_lr()
            if len(last_lrs) != len(scheduler.optimizer.param_groups):
                raise RuntimeError(
                    f"Scheduler '{name}' restored {len(last_lrs)} learning rates "
                    f"for {len(scheduler.optimizer.param_groups)} parameter groups."
                )
            for parameter_group, learning_rate in zip(
                    scheduler.optimizer.param_groups,
                    last_lrs,
            ):
                parameter_group["lr"] = learning_rate
hyper_parallel/data/batching/build_collate_fn.py
105
106
107
108
109
110
111
112
113

    def __call__(self, model_samples: Sequence[Mapping[str, Any]]) -> Mapping[str, Any]:
        """Stack token fields and concatenate variable-length modality fields."""
        if not model_samples:
            raise ValueError("model_samples must contain at least one Omni sample")

        sample_fields = set()
        for model_sample in model_samples:
            sample_fields.update(model_sample)
143
144
145
146
147
148
149
150
151
152


def build_omni_collate_fn() -> OmniCollator:
    """Build ordinary collation for encoded or finally packed Omni samples."""
    collate_fn = OmniCollator()
    return collate_fn


def build_online_text_collate_fn(mesh_context: Any | None = None) -> TextPackingCollator:
    """Build Online text collation shared by fixed N and dynamic K batching.
164
165
166
167
168
169
170
    Returns:
        A collator producing one forward-backward batch.
    """
    sequence_parallel_size = _get_sequence_parallel_size(mesh_context)
    collate_fn = TextPackingCollator(sequence_parallel_size=sequence_parallel_size)

    return collate_fn
hyper_parallel/data/batching/build_dataloader.py
187
188
189
190
191
192
193
194
195
196
197
198
199
            pin_memory: bool = False,
            prefetch_factor: int | None = None,
    ) -> None:
        """Initialize the stateful DataLoader."""
        if sampler_type not in ("single", "cyclic"):
            raise ValueError("sampler_type must be 'single' or 'cyclic'")

        self.drop_last = drop_last
        self.sampler_type = sampler_type
        generator = torch.Generator().manual_seed(seed)
        worker_options = {
            "num_workers": num_workers,
            "generator": generator,
222
223
224
225
226
227
228
229
230
231
232
233
        """Forward epoch state to the Dataset and configured batch sampler."""
        if self.batch_sampler is not None and hasattr(self.batch_sampler, "set_epoch"):
            self.batch_sampler.set_epoch(epoch)

        epoch_setter = getattr(self.dataset, "set_epoch", None)
        if callable(epoch_setter):
            dataset_epoch = epoch if self.sampler_type == "cyclic" else 0
            epoch_setter(dataset_epoch)


class _DynamicBatchLoader(ABC):
    """Own common dynamic source iteration, buffering, and checkpoint state.
278
279
280
281
282
283
284
285
286
287
288
289
290
291
    ) -> None:
        """Initialize source reading, token selection, and Online packing."""
        # pylint: disable=too-many-locals
        if collate_fn is None:
            raise ValueError(f"{type(self).__name__} requires a collate_fn")

        if max_seq_len is None:
            raise ValueError(f"{type(self).__name__} requires data_transform.max_seq_len")
        if token_budget is not None and token_budget <= 0:
            raise ValueError("token_budget must be positive")

        resolved_dp_world_size = dp_world_size
        if resolved_dp_world_size <= 0:
            raise ValueError("dp_world_size must be positive")
298
299
300
301
302
303
304
305
306
307
308
        self.batch_collate_fn = collate_fn
        self.dp_world_size = resolved_dp_world_size

        # build_candidate_buffer
        self._batch_pipeline = self._build_batch_pipeline(dataset)
        resolved_token_budget = batch_size * max_seq_len if token_budget is None else token_budget
        self.candidate_buffer = self._batch_pipeline.build_candidate_buffer(
            token_budget=resolved_token_budget,
            min_buffered_samples=min_buffered_samples,
        )
        if batch_sampler is not None:
310
311
312
313
314
315
316
317
318
            if callable(enable_source_resume):
                enable_source_resume()

        # Resuming training from a checkpoint
        self._buffer_checkpoint = self._build_buffer_checkpoint(dataset, save_by_idx)

        self.source_dataloader = FixedBatchDataLoader(
            dataset=self._buffer_checkpoint.source_dataset,
            batch_sampler=batch_sampler,
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
            dataset: Any,
            save_by_idx: bool | None,
    ) -> IndexReplayCheckpoint | FullSampleCheckpoint:
        """Build candidate-buffer checkpoint storage for the Dataset access mode."""
        is_iterable = _is_iterable_dataset(dataset)
        resolved_save_by_idx = not is_iterable if save_by_idx is None else save_by_idx

        # Mapping Datasets have stable indices. Iterable Datasets must explicitly
        # provide replay access before their buffer can be checkpointed by index.
        replay_dataset = dataset if is_iterable else MappingReplayDataset(dataset)
        supports_replay = not is_iterable or IndexReplayCheckpoint.supports(dataset)
        if resolved_save_by_idx and not supports_replay:
            raise ValueError("save_by_idx=True requires get_item() and output_index_for_resume")

        if is_iterable and hasattr(dataset, "output_index_for_resume"):
            dataset.output_index_for_resume = resolved_save_by_idx

        if resolved_save_by_idx:
            buffer_checkpoint = IndexReplayCheckpoint(replay_dataset)
        else:
            # Keep replay access only for loading an earlier index-buffer checkpoint.
            checkpoint_replay_dataset = replay_dataset if supports_replay else None
            buffer_checkpoint = FullSampleCheckpoint(dataset, checkpoint_replay_dataset)

        return buffer_checkpoint

    def __iter__(self) -> Iterator[Mapping[str, Any]]:
        """Start source iteration while retaining restored buffer state."""
        if not self.resume_pending:
            self.candidate_buffer.buffer.clear()
            self.candidate_buffer.buffer_output_indices.clear()
            self.candidate_buffer.buffer_token_count = 0

        source_iterator = iter(self.source_dataloader)
        batch_iterator = self._batch_data_generator(source_iterator)
        self.resume_pending = False
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
            source_iterator: Iterator[list[Any]],
    ) -> Iterator[Mapping[str, Any]]:
        """Lazily yield micro-batches as source samples fill the candidate buffer."""
        # Stage 1: Use ready checkpointed candidates before advancing the source.
        while self.candidate_buffer.is_ready_for_micro_batch():
            yield self._build_micro_batch()

        # Stage 2: Retain unselected candidates while incrementally reading source batches.
        for source_samples in source_iterator:
            for source_sample in source_samples:
                self._buffer_checkpoint.put_source_item(source_sample, self.candidate_buffer)

            while self.candidate_buffer.is_ready_for_micro_batch():
                yield self._build_micro_batch()

        # Stage 3: At EOF, bypass readiness thresholds so no pulled candidate is lost.
        while not self.candidate_buffer.empty():
            yield self._build_micro_batch()

    @abstractmethod
    def _build_batch_pipeline(self, dataset: Any) -> _BatchPipeline:
        """Build the batching pipeline selected by the concrete Loader."""
        raise NotImplementedError

    def _build_micro_batch(self) -> Mapping[str, Any]:
        """Select, finalize, collate, and encode one micro-batch."""
        selected_samples = self.candidate_buffer.get_micro_batch()
        finalized_samples = self._batch_pipeline.finalize_selected_samples(selected_samples)

        # build batch data
        batch = self.batch_collate_fn(finalized_samples)
        encoded_batch = self._batch_pipeline.encode_batch(batch)
        return encoded_batch

    def state_dict(self) -> dict[str, Any]:
        """Capture the future source cursor and unconsumed dynamic buffer."""
        state = {
429
430
431
432
433
434
435
436
437
            )

        previous_save_by_idx = bool(checkpoint_state.get("save_by_idx", False))
        saved_buffer = checkpoint_state["buffer"]
        restored_buffer, restored_indices = self._buffer_checkpoint.restore_buffer(
            saved_buffer,
            previous_save_by_idx,
            self.candidate_buffer,
        )
440
441
442
443
444
445
446
447
448
449
450
        if restored_token_count != checkpoint_state["buffer_token_count"]:
            raise ValueError("buffer_token_count does not match the restored dynamic buffer")

        self.source_dataloader.load_state_dict(checkpoint_state["source_dataloader"])
        self.candidate_buffer.buffer = restored_buffer
        self.candidate_buffer.buffer_output_indices = restored_indices
        self.candidate_buffer.buffer_token_count = restored_token_count
        self.resume_pending = True

    def set_epoch(self, epoch: int) -> None:
        """Forward epoch state to the stateful source DataLoader."""
455
456
457
458
459
460
461
462
463
464
    """Select fully encoded samples within one token budget."""

    def _build_batch_pipeline(self, _dataset: Any) -> _BatchPipeline:
        """Build token-budget batching for fully encoded Dataset samples."""
        batch_pipeline = _TokenBatchPipeline()
        return batch_pipeline


class OmniPackingLoader(_DynamicBatchLoader):
    """Pack selected samples after applying the Dataset encoding lifecycle."""
464
465
466
467
468
469
    """Pack selected samples after applying the Dataset encoding lifecycle."""

    def _build_batch_pipeline(self, dataset: Any) -> _BatchPipeline:
        """Build packing around the normalized Dataset encoding lifecycle."""
        batch_pipeline = _OmniPackingPipeline.from_dataset(dataset)
        return batch_pipeline
hyper_parallel/data/batching/dynamic_batch.py
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
    buffer_token_count: int = field(default=0, init=False)

    def __post_init__(self) -> None:
        """Validate the candidate-buffer limits."""
        if self.token_budget <= 0:
            raise ValueError("token_budget must be positive")

        if self.min_buffered_samples <= 0:
            raise ValueError("min_buffered_samples must be positive")

    @abstractmethod
    def build_buffer_entry(self, model_sample: Mapping[str, Any]) -> tuple[Mapping[str, Any], int]:
        """Attach the scheduling cost used to select this sample."""
        raise NotImplementedError

    def put_item(self, model_sample: Mapping[str, Any], output_index_entry: Any | None = None) -> None:
        """Append one candidate and its optional checkpoint replay index."""
        buffered_sample = self.build_buffer_entry(model_sample)
        self.buffer.append(buffered_sample)
        self.buffer_output_indices.append(output_index_entry)
        self.buffer_token_count += buffered_sample[1]

    def is_ready_for_micro_batch(self) -> bool:
        """Return whether both configured candidate limits are reached."""
        enough_samples = len(self.buffer) >= self.min_buffered_samples
        enough_tokens = self.buffer_token_count >= self.token_budget
        is_ready = enough_samples and enough_tokens
        return is_ready

    @abstractmethod
    def get_micro_batch(self) -> list[Mapping[str, Any]]:
        """Select and remove the next sample group."""
        raise NotImplementedError

    def empty(self) -> bool:
        """Return whether no candidates remain."""
        is_empty = not self.buffer
        return is_empty


@dataclass
class TokenBudgetCandidateBuffer(BaseCandidateBuffer):
 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
116
117
118
119
    """Select encoded samples in source order within one token budget."""

    def build_buffer_entry(self, model_sample: Mapping[str, Any]) -> tuple[Mapping[str, Any], int]:
        """Attach the encoded token length to one candidate."""
        sample_length = int(model_sample["input_ids"].shape[-1])
        if sample_length <= 0:
            raise ValueError("Dynamic batching samples must contain at least one token")

        buffered_sample = (model_sample, sample_length)
        return buffered_sample

    def get_micro_batch(self) -> list[Mapping[str, Any]]:
        """Select fitting candidates and retain the remainder."""
        if not self.buffer:
            raise ValueError("Dynamic token buffer is empty")

        selected_samples = []
        remaining_buffer = []
        remaining_output_indices = []
        selected_token_count = 0
        for (model_sample, sample_length), output_index_entry in zip(
                self.buffer,
                self.buffer_output_indices,
        ):
            sample_fits = selected_token_count == 0 or selected_token_count + sample_length <= self.token_budget
            if sample_fits:
                selected_samples.append(model_sample)
                selected_token_count += sample_length
            else:
                remaining_buffer.append((model_sample, sample_length))
                remaining_output_indices.append(output_index_entry)

        self.buffer = remaining_buffer
        self.buffer_output_indices = remaining_output_indices
        self.buffer_token_count -= selected_token_count
        return selected_samples


@dataclass
class PackingCandidateBuffer(BaseCandidateBuffer):
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    packing_selector: PackingSelector

    def build_buffer_entry(self, model_sample: Mapping[str, Any]) -> tuple[Mapping[str, Any], int]:
        """Attach selector-defined scheduling cost to one candidate."""
        sample_cost = self.packing_selector.get_sample_cost(model_sample)
        buffered_sample = (model_sample, sample_cost)
        return buffered_sample

    def get_micro_batch(self) -> list[Mapping[str, Any]]:
        """Select and remove the next candidate group."""
        if not self.buffer:
            raise ValueError("Packing candidate buffer is empty")

        candidates = []
        for model_sample, _ in self.buffer:
            candidates.append(model_sample)

        selected_indices = self.packing_selector.select_samples_to_pack(
            candidates,
            self.token_budget,
        )
        resolved_indices = self._validate_selected_indices(selected_indices)
        selected_index_set = set(resolved_indices)

        selected_samples = []
        selected_cost = 0
        for selected_index in resolved_indices:
            model_sample, sample_cost = self.buffer[selected_index]
            selected_samples.append(model_sample)
            selected_cost += sample_cost

        remaining_buffer = []
        remaining_output_indices = []
        for sample_index, (buffered_sample, output_index) in enumerate(
                zip(self.buffer, self.buffer_output_indices)
        ):
            if sample_index not in selected_index_set:
                remaining_buffer.append(buffered_sample)
                remaining_output_indices.append(output_index)

        self.buffer = remaining_buffer
        self.buffer_output_indices = remaining_output_indices
        self.buffer_token_count -= selected_cost
        return selected_samples

    def _validate_selected_indices(self, selected_indices: Sequence[int]) -> list[int]:
        """Validate selector output before mutating the candidate buffer."""
        resolved_indices = []
        for selected_index in selected_indices:
            if isinstance(selected_index, bool):
                raise TypeError("PackingSelector indices must be integers")
            try:
                resolved_index = operator.index(selected_index)
            except TypeError as exc:
                raise TypeError("PackingSelector indices must be integers") from exc

            if resolved_index < 0 or resolved_index >= len(self.buffer):
                raise IndexError("PackingSelector returned an out-of-range candidate index")
            resolved_indices.append(resolved_index)

        if not resolved_indices:
            raise ValueError("PackingSelector must select at least one candidate")
        if len(set(resolved_indices)) != len(resolved_indices):
            raise ValueError("PackingSelector must not select a candidate more than once")

        return resolved_indices


def _normalize_source_samples(source_item: Any) -> list[Mapping[str, Any]]:
    """Normalize one source output into its ordered model samples."""
    if isinstance(source_item, Mapping):
        model_samples = [source_item]
        return model_samples

    if isinstance(source_item, Sequence) and not isinstance(source_item, (str, bytes)):
        model_samples = []
        for model_sample in source_item:
            if not isinstance(model_sample, Mapping):
                raise ValueError("Every dynamic source sample must be a mapping")

            model_samples.append(model_sample)
        return model_samples

    raise ValueError("A dynamic source item must be a mapping or a sequence of mappings")


def _restore_index_buffer(
        source_dataset: Any,
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
        saved_buffer: Sequence[Any],
        candidate_buffer: BaseCandidateBuffer,
) -> list[tuple[Mapping[str, Any], int]]:
    """Rebuild buffered samples from their output and sample indices."""
    restored_buffer = []
    cached_output_index: Any = object()
    cached_model_samples: list[Mapping[str, Any]] = []
    for output_index_entry in saved_buffer:
        output_index, sample_index = output_index_entry
        resolved_sample_index = operator.index(sample_index)
        if resolved_sample_index < 0:
            raise ValueError("Buffered sample index must be a non-negative integer")

        if output_index != cached_output_index:
            restored_item = source_dataset.get_item(output_index)
            cached_model_samples = _normalize_source_samples(restored_item)
            cached_output_index = output_index
        try:
            model_sample = cached_model_samples[resolved_sample_index]
        except IndexError as exc:
            raise ValueError(
                f"Buffered sample index {resolved_sample_index} is out of range "
                f"for output index {output_index!r}"
            ) from exc

        buffer_entry = candidate_buffer.build_buffer_entry(model_sample)
        restored_buffer.append(buffer_entry)

    return restored_buffer


class MappingReplayDataset:
    """Attach a stable replay index to each mapping Dataset output."""
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
    """Attach a stable replay index to each mapping Dataset output."""

    def __init__(self, dataset: Any) -> None:
        """Store the mapping Dataset used to reconstruct buffered samples."""
        self.dataset = dataset

    def __len__(self) -> int:
        """Return the wrapped Dataset length."""
        dataset_length = len(self.dataset)
        return dataset_length

    def __getitem__(self, index: int) -> tuple[Any, int]:
        """Return one source item and the index that can reproduce it."""
        source_item = self.get_item(index)
        indexed_source_item = (source_item, index)
        return indexed_source_item

    def get_item(self, index: Any) -> Any:
        """Rebuild one source item without advancing the source iterator."""
        resolved_index = operator.index(index)
        get_item = getattr(self.dataset, "get_item", None)
        if callable(get_item):
            source_item = get_item(resolved_index)
        else:
            source_item = self.dataset[resolved_index]

        return source_item


class IndexReplayCheckpoint:
    """Checkpoint candidates as compact mapping Dataset replay indices."""
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
    save_by_idx = True

    def __init__(self, source_dataset: Any) -> None:
        """Store a Dataset that emits source items with stable replay indices."""
        self.source_dataset = source_dataset

    @staticmethod
    def supports(dataset: Any) -> bool:
        """Return whether an iterable Dataset supports index replay."""
        get_item = getattr(dataset, "get_item", None)
        supports_index_replay = callable(get_item) and hasattr(dataset, "output_index_for_resume")
        return supports_index_replay

    @staticmethod
    def put_source_item(source_item: Any, candidate_buffer: BaseCandidateBuffer) -> None:
        """Append an indexed source item and retain each sample index."""
        model_samples_item, output_index = source_item
        model_samples = _normalize_source_samples(model_samples_item)
        for sample_index, model_sample in enumerate(model_samples):
            candidate_buffer.put_item(model_sample, (output_index, sample_index))

    @staticmethod
    def get_buffer_state(candidate_buffer: BaseCandidateBuffer) -> list[Any]:
        """Return replay indices for buffered model samples."""
        if len(candidate_buffer.buffer) != len(candidate_buffer.buffer_output_indices):
            raise RuntimeError("Dynamic sample and output-index buffers are inconsistent")

        if any(index is None for index in candidate_buffer.buffer_output_indices):
            raise RuntimeError("Index-buffer checkpoint requires a replay key for every buffered sample")

        return candidate_buffer.buffer_output_indices

    def restore_buffer(
            self,
            saved_buffer: Sequence[Any],
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
            saved_by_idx: bool,
            candidate_buffer: BaseCandidateBuffer,
    ) -> tuple[list[tuple[Mapping[str, Any], int]], list[Any]]:
        """Rebuild buffered samples from their replay indices."""
        if not saved_by_idx:
            if saved_buffer:
                raise ValueError("save_by_idx=True cannot restore a checkpoint containing full samples")

            return [], []

        restored_buffer = _restore_index_buffer(self.source_dataset, saved_buffer, candidate_buffer)
        restored_indices = list(saved_buffer)
        return restored_buffer, restored_indices


class FullSampleCheckpoint:
    """Checkpoint full candidate samples alongside the source state."""
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
    save_by_idx = False

    def __init__(self, dataset: Any, replay_dataset: Any | None = None) -> None:
        """Retain the source Dataset and optional mapping replay Dataset."""
        self.source_dataset = dataset
        self.replay_dataset = replay_dataset

    @staticmethod
    def put_source_item(source_item: Any, candidate_buffer: BaseCandidateBuffer) -> None:
        """Append one streaming source item to the candidate buffer."""
        model_samples = _normalize_source_samples(source_item)
        for model_sample in model_samples:
            candidate_buffer.put_item(model_sample)

    @staticmethod
    def get_buffer_state(candidate_buffer: BaseCandidateBuffer) -> list[Any]:
        """Return full samples already pulled beyond the source cursor."""
        if len(candidate_buffer.buffer) != len(candidate_buffer.buffer_output_indices):
            raise RuntimeError("Dynamic sample and output-index buffers are inconsistent")

        return candidate_buffer.buffer

    def restore_buffer(
            self,
            saved_buffer: Sequence[Any],
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
            saved_by_idx: bool,
            candidate_buffer: BaseCandidateBuffer,
    ) -> tuple[list[tuple[Mapping[str, Any], int]], list[Any]]:
        """Restore full samples or an earlier index-buffer checkpoint."""
        if saved_by_idx:
            if saved_buffer and self.replay_dataset is None:
                raise ValueError("An index-buffer checkpoint requires a replayable Dataset")

            restored_buffer = _restore_index_buffer(self.replay_dataset, saved_buffer, candidate_buffer)
        else:
            restored_buffer = list(saved_buffer)
        restored_indices = [None] * len(restored_buffer)
        return restored_buffer, restored_indices


class _BatchPipeline(ABC):
    """Define candidate selection and finalization for one dynamic Loader."""
374
375
376
377
378
379
380
381
382
            token_budget: int,
            min_buffered_samples: int,
    ) -> BaseCandidateBuffer:
        """Build the candidate buffer used by this pipeline."""
        raise NotImplementedError

    @abstractmethod
    def finalize_selected_samples(
            self,
382
383
384
385
386
387
388
389
390
391
392
393
394
            self,
            selected_samples: Sequence[Mapping[str, Any]],
    ) -> Sequence[Mapping[str, Any]]:
        """Finish selected samples before final collation."""
        raise NotImplementedError

    def encode_batch(self, batch: Mapping[str, Any]) -> Mapping[str, Any]:
        """Apply an optional final batch encoding stage."""
        return batch


class _TokenBatchPipeline(_BatchPipeline):
    """Build batches directly from fully encoded token samples."""
399
400
401
402
403
404
405
406
407
408
409
410
411
            token_budget: int,
            min_buffered_samples: int,
    ) -> BaseCandidateBuffer:
        """Build the token-budget candidate buffer."""
        candidate_buffer = TokenBudgetCandidateBuffer(
            token_budget=token_budget,
            min_buffered_samples=min_buffered_samples,
        )
        return candidate_buffer

    def finalize_selected_samples(
            self,
            selected_samples: Sequence[Mapping[str, Any]],
410
411
412
413
414
415
416
417
418
            self,
            selected_samples: Sequence[Mapping[str, Any]],
    ) -> Sequence[Mapping[str, Any]]:
        """Return fully encoded samples for final collation."""
        return selected_samples


@dataclass(frozen=True)
class _OmniPackingPipeline(_BatchPipeline):
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453

    @classmethod
    def from_dataset(cls, dataset: Any) -> "_OmniPackingPipeline":
        """Build the packing pipeline from one normalized Omni Dataset interface."""
        selected_sample_encoder = getattr(dataset, "encode_selected_sample", None)
        batch_encoder = getattr(dataset, "encode_batch", None)
        if not callable(selected_sample_encoder) or not callable(batch_encoder):
            raise TypeError("OmniPackingLoader requires encode_selected_sample() and encode_batch()")

        # Hook—a criterion used to select data objects; this criterion can be extended to support load balancing.
        selector = getattr(dataset, "packing_selector", None)
        if selector is None:
            selector = FirstFitPackingSelector()
        if not isinstance(selector, PackingSelector):
            raise TypeError("dataset.packing_selector must implement PackingSelector")

        # This is a hook function used to package data into different fields;
        # you need to verify that the keyword mappings match.
        packer = getattr(dataset, "sample_packer", None)
        if packer is None:
            packer = SamplePacker()
        if not isinstance(packer, SamplePacker):
            raise TypeError("dataset.sample_packer must implement SamplePacker")

        packing_pipeline = cls(
            selector=selector,
            selected_sample_encoder=selected_sample_encoder,
            packer=packer,
            batch_encoder=batch_encoder,
451
452
453
454
455
456
457
458
459
            selected_sample_encoder=selected_sample_encoder,
            packer=packer,
            batch_encoder=batch_encoder,
        )
        return packing_pipeline

    def build_candidate_buffer(
            self,
            *,
460
461
462
463
464
465
466
467
468
469
470
471
472
473
            token_budget: int,
            min_buffered_samples: int,
    ) -> BaseCandidateBuffer:
        """Build candidate selection around the composed selector."""
        candidate_buffer = PackingCandidateBuffer(
            token_budget=token_budget,
            min_buffered_samples=min_buffered_samples,
            packing_selector=self.selector,
        )
        return candidate_buffer

    def finalize_selected_samples(
            self,
            selected_samples: Sequence[Mapping[str, Any]],
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
            self,
            selected_samples: Sequence[Mapping[str, Any]],
    ) -> Sequence[Mapping[str, Any]]:
        """Encode and pack one selected sample group."""
        encoded_samples = []
        for selected_sample in selected_samples:
            encoded_sample = self.selected_sample_encoder(selected_sample)
            if not isinstance(encoded_sample, Mapping):
                raise TypeError("postencode_sample must return a mapping")
            encoded_samples.append(encoded_sample)

        packed_sample = self.packer.pack_selected_samples(encoded_samples)
        if not isinstance(packed_sample, Mapping):
            raise TypeError("SamplePacker must return one mapping")

        packed_samples = [packed_sample]
        return packed_samples

    def encode_batch(self, batch: Mapping[str, Any]) -> Mapping[str, Any]:
        """Apply the composed final batch encoder."""
        encoded_batch = self.batch_encoder(batch)
        return encoded_batch


__all__ = [
    "BaseCandidateBuffer",
hyper_parallel/data/batching/get_batch.py
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
            encoder_dp: Reserved for image-bucket distribution; not implemented.
            runtime_input_adapter: Model-owned hook for additional forward inputs.
            data_config: Dataset cache settings used by DataLoader ownership.
        """
        if encoder_dp:
            raise NotImplementedError("OmniParallelBatch encoder_dp image-bucket distribution is not implemented")
        if runtime_input_adapter is not None and not isinstance(runtime_input_adapter, RuntimeInputAdapter):
            raise TypeError("OmniParallelBatch runtime_input_adapter must be a RuntimeInputAdapter")
        if int(getattr(mesh_context, "pp_size", 1)) != 1 or pp_shared_data:
            raise NotImplementedError("OmniParallelBatch does not support pipeline parallelism or pp_shared_data")
        source_config = data_config or {}
        self.parallel_context = create_dataloader_parallel_context(
            mesh_context,
            data_index_cache=bool(source_config.get("data_index_cache", False)),
            shared_storage=not bool(source_config.get("no_shared_storage", False)),
        )
        if not self.parallel_context.distributed_enabled and (
                int(getattr(mesh_context, "tp_size", 1)) > 1 or int(getattr(mesh_context, "cp_size", 1)) > 1
        ):
            raise ValueError("OmniParallelBatch requires an initialized process group for TP or CP")
        self.device = device
        self.runtime_input_adapter = runtime_input_adapter
        token_pad_values = {field: OMNI_CP_PAD_VALUES.get(field, 0) for field in OMNI_CP_TOKEN_FIELDS}
        self.cp_sharder = CPBatchSharder(self.parallel_context, token_pad_values=token_pad_values)
        self.tp_broadcaster = TPBatchBroadcaster(self.parallel_context, device)

    def __call__(
            self,
            data_iterator: Any,
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
            *,
            external_batch: Mapping[str, Any] | None = None,
    ) -> tuple[dict[str, Any], dict[str, Any]]:
        """Read on TP0, slice token fields for CP, then broadcast within TP."""
        local_batch = None
        if self.parallel_context.build_on_rank():
            source_batch = external_batch if external_batch is not None else next(data_iterator)
            if not isinstance(source_batch, Mapping):
                raise TypeError("Omni DataLoader must yield a mapping batch")
            normalized_batch = self._normalize_source_batch(source_batch)
            local_batch = self.cp_sharder.shard(normalized_batch)

        device_batch = self.tp_broadcaster.broadcast(local_batch, broadcast_all_fields=True)
        runtime_inputs = self._build_runtime_inputs(device_batch)
        model_inputs, loss_inputs = self._split_model_and_loss_inputs(device_batch)
        model_inputs.update(runtime_inputs)

        return model_inputs, loss_inputs

    @staticmethod
    def _normalize_source_batch(source_batch: Mapping[str, Any]) -> dict[str, Any]:
        """Validate required fields and derive the default loss mask."""
        batch = dict(source_batch)
        if "input_ids" not in batch:
            raise ValueError("Omni batch must contain input_ids")
        if "labels" not in batch:
            raise ValueError("Omni batch must contain labels")
        if "loss_mask" not in batch:
            batch["loss_mask"] = batch["labels"] >= 0
        if "cu_seq_lens" in batch:
            batch["cu_seq_lens"] = batch["cu_seq_lens"].reshape(-1)
        return batch

    def _build_runtime_inputs(self, batch: Mapping[str, Any]) -> dict[str, Any]:
        """Call the configured model hook after TP/CP batch transport."""
        if self.runtime_input_adapter is None:
            return {}
        runtime_inputs = self.runtime_input_adapter.build(
            batch=batch, parallel_context=self.parallel_context
        )
        collisions = set(batch).intersection(runtime_inputs)
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
        self.device = device
        self.tokenizer = tokenizer
        self.data_config = dict(data_config)
        self.source_type = source_type
        self.causal = causal
        self.sliding_window = sliding_window
        if runtime_input_adapter is not None and not isinstance(
                runtime_input_adapter, RuntimeInputAdapter
        ):
            raise TypeError("TextParallelBatch runtime_input_adapter must be a RuntimeInputAdapter")
        self.runtime_input_adapter = runtime_input_adapter
        self._batch_flow_logged = False

        if source_type == "online":
            self.boundary_resolver = OnlineBoundaryResolver()
229
230
231
232
233
234
235
236
237
            raise ValueError(f"Unsupported batch source type: {source_type!r}")

        self.pp_shared_data = pp_shared_data
        self.labels_are_shifted = bool(self.data_config.get("labels_are_shifted", True))
        create_attention_mask = bool(
            self.data_config.get("create_attention_mask_in_dataloader", attention_mode == "dense")
        )
        self.cp_sharder = CPBatchSharder(self.parallel_context)
        self.tp_broadcaster = TPBatchBroadcaster(self.parallel_context, device)
236
237
238
239
240
241
242
243
244
        self.cp_sharder = CPBatchSharder(self.parallel_context)
        self.tp_broadcaster = TPBatchBroadcaster(self.parallel_context, device)

        self.reset_position_ids = reset_position_ids or bool(self.data_config.get("reset_position_ids", False))
        resolved_reset_attention_mask = (
            reset_attention_mask
            or bool(self.data_config.get("reset_attention_mask", False))
            or source_type == "online"
        )
242
243
244
245
246
247
248
249
250
            or bool(self.data_config.get("reset_attention_mask", False))
            or source_type == "online"
        )
        self.eod_mask_loss = eod_mask_loss or bool(self.data_config.get("eod_mask_loss", False))
        self.attention_runtime = AttentionRuntime(
            mode=attention_mode,
            create_mask=create_attention_mask,
            reset_mask=resolved_reset_attention_mask,
            sliding_window=sliding_window,
279
280
281
282
283
284
285
286
287
288
        parallel_batch["position_ids"] = position_ids

        loss_mask = self._build_loss_mask(parallel_batch)
        parallel_batch["loss_mask"] = loss_mask
        runtime_inputs = self._build_runtime_inputs(parallel_batch)

        model_inputs, loss_inputs = self._split_model_and_loss_inputs(
            parallel_batch,
            runtime_inputs,
        )
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
            self,
            parallel_batch: Mapping[str, Any],
    ) -> dict[str, Any]:
        """Build common attention inputs plus a model-owned extension."""
        runtime_inputs = self.attention_runtime.build(
            batch=parallel_batch,
            parallel_context=self.parallel_context,
        )
        if self.runtime_input_adapter is None:
            return runtime_inputs

        model_runtime_inputs = self.runtime_input_adapter.build(
            batch=parallel_batch,
            parallel_context=self.parallel_context,
            options={
                "source_type": self.source_type,
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
                "causal": self.causal,
                "sliding_window": self.sliding_window,
            },
        )
        collisions = set(runtime_inputs).intersection(model_runtime_inputs)
        if collisions:
            raise ValueError(
                "[HP-DATA-001] model runtime inputs cannot replace common attention inputs: "
                f"{sorted(collisions)}"
            )
        runtime_inputs.update(model_runtime_inputs)
        return runtime_inputs

    def _log_batch_flow(
            self,
            canonical_batch: Mapping[str, Any] | None,
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
        }
        if self.labels_are_shifted:
            model_inputs["shift_labels"] = parallel_batch["labels"]

        runtime_inputs = runtime_inputs or {}
        collisions = set(model_inputs).intersection(runtime_inputs)
        if collisions:
            raise ValueError(
                "[HP-DATA-001] runtime inputs cannot replace framework-owned model inputs: "
                f"{sorted(collisions)}"
            )
        model_inputs.update(runtime_inputs)

        loss_inputs = {
            "labels": parallel_batch["labels"],
            "loss_mask": parallel_batch["loss_mask"],
hyper_parallel/data/batching/packing.py
31
32
33
34
35
36
37
38
39

    @abstractmethod
    def get_sample_cost(self, sample: Mapping[str, Any]) -> int:
        """Return the scheduling cost retained with one candidate sample."""
        raise NotImplementedError

    @abstractmethod
    def select_samples_to_pack(
            self,
40
41
42
43
44
45
46
47
48
            samples: Sequence[Mapping[str, Any]],
            token_budget: int,
    ) -> Sequence[int]:
        """Return indices of candidates that form the next packed sequence."""
        raise NotImplementedError


class FirstFitPackingSelector(PackingSelector):
    """Select candidates in source order until the token budget is full."""
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    """Select candidates in source order until the token budget is full."""

    def get_sample_cost(self, sample: Mapping[str, Any]) -> int:
        """Read standard packing metadata or derive cost from encoded input IDs."""
        sample_cost = sample.get("packing_length")
        if sample_cost is None:
            sample_cost = self._get_input_length(sample)

        resolved_cost = int(sample_cost)
        if resolved_cost <= 0:
            raise ValueError("Packing sample cost must be positive")

        return resolved_cost

    def select_samples_to_pack(
            self,
            samples: Sequence[Mapping[str, Any]],
 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
            samples: Sequence[Mapping[str, Any]],
            token_budget: int,
    ) -> Sequence[int]:
        """Select the next ordered first-fit group from the candidate buffer."""
        if not samples:
            raise ValueError("Packing candidate buffer is empty")

        selected_indices = []
        selected_cost = 0
        for sample_index, sample in enumerate(samples):
            sample_cost = self.get_sample_cost(sample)
            sample_fits = selected_cost == 0 or selected_cost + sample_cost <= token_budget
            if sample_fits:
                selected_indices.append(sample_index)
                selected_cost += sample_cost

        return selected_indices

    @staticmethod
    def _get_input_length(sample: Mapping[str, Any]) -> int:
        """Derive sequence length from a fully encoded sample."""
        if "input_ids" not in sample:
            raise ValueError("Packing samples must provide packing_length or input_ids")

        input_ids = sample["input_ids"]
        input_shape = getattr(input_ids, "shape", None)
        if input_shape is not None:
            input_length = int(input_shape[-1])
            return input_length

        if not isinstance(input_ids, Sequence) or isinstance(input_ids, (str, bytes)):
            raise TypeError("Packing input_ids must be a tensor or sequence")

        input_length = len(input_ids)
        if input_ids and isinstance(input_ids[0], Sequence):
            input_length = len(input_ids[0])
        return input_length


class SamplePacker:
    """Combine selected encoded samples into one packed sample."""
109
110
111
112
113
114
115
116
117
118
119
        Args:
            field_pack_dims: Optional additions or overrides to the default
                token-last and modality-first field rules.
        """
        self.field_pack_dims = dict(DEFAULT_FIELD_PACK_DIMS)
        if field_pack_dims is not None:
            self.field_pack_dims.update(field_pack_dims)

    def pack_selected_samples(
            self,
            samples: Sequence[Mapping[str, Any]],
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
            self,
            samples: Sequence[Mapping[str, Any]],
    ) -> Mapping[str, Any]:
        """Concatenate one selected group and emit its sequence boundaries."""
        if not samples:
            raise ValueError("samples must contain at least one encoded model sample")

        packed_sample = {}
        sample_fields = set()
        for sample in samples:
            sample_fields.update(sample)

        for field in sample_fields:
            if field in PACKING_METADATA_FIELDS:
                continue

            if field.endswith("_token_starts"):
                packed_sample[field] = self._pack_token_starts(field, samples)
                continue

            field_values = []
            for sample in samples:
                if field in sample:
                    field_values.append(sample[field])

            packed_value = self._pack_field(field, field_values)
            packed_sample[field] = packed_value

        packed_sample["cu_seq_lens"] = self._build_sequence_boundaries(samples)
        return packed_sample

    def _pack_field(self, field: str, values: Sequence[Any]) -> Any:
        """Pack one field using its configured or inferred leading dimension."""
        if not values:
            raise ValueError(f"Cannot pack empty field {field!r}")

        pack_dim = self.field_pack_dims.get(field, 0)
        if all(isinstance(value, torch.Tensor) for value in values):
            packed_value = torch.cat(values, dim=pack_dim)
            return packed_value

        if field in self.field_pack_dims:
            tensor_values = []
            for value in values:
                tensor_values.append(torch.as_tensor(value))
            packed_value = torch.cat(tensor_values, dim=pack_dim)
            return packed_value

        if all(isinstance(value, Sequence) and not isinstance(value, (str, bytes)) for value in values):
            packed_items = []
            for value in values:
                packed_items.extend(value)
            return packed_items

        if len(values) == 1:
            return values[0]

        raise TypeError(f"Field {field!r} requires a model-specific SamplePacker rule")

    @staticmethod
    def _pack_token_starts(
            field: str,
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
            field: str,
            samples: Sequence[Mapping[str, Any]],
    ) -> torch.Tensor:
        """Move sample-local modality starts into the packed token coordinate."""
        packed_starts = []
        sequence_offset = 0
        for sample in samples:
            if field in sample:
                token_starts = torch.as_tensor(sample[field])
                packed_starts.append(token_starts + sequence_offset)

            input_ids = sample.get("input_ids")
            if input_ids is None:
                raise ValueError(f"Packing {field!r} requires input_ids in every sample")
            sequence_offset += int(torch.as_tensor(input_ids).shape[-1])

        if not packed_starts:
            raise ValueError(f"Cannot pack empty field {field!r}")

        packed_token_starts = torch.cat(packed_starts, dim=0)
        return packed_token_starts

    @staticmethod
    def _build_sequence_boundaries(
            samples: Sequence[Mapping[str, Any]],
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    def _build_sequence_boundaries(
            samples: Sequence[Mapping[str, Any]],
    ) -> torch.Tensor:
        """Build cumulative text boundaries for packed attention isolation."""
        sequence_lengths = []
        input_ids_reference = None
        for sample in samples:
            if "input_ids" not in sample:
                raise ValueError("Every packed sample must contain input_ids")

            input_ids = sample["input_ids"]
            if input_ids_reference is None and isinstance(input_ids, torch.Tensor):
                input_ids_reference = input_ids
            input_shape = getattr(input_ids, "shape", None)
            if input_shape is not None:
                sequence_length = int(input_shape[-1])
            else:
                sequence_length = len(input_ids)
            if sequence_length <= 0:
                raise ValueError("Packed samples must contain at least one token")
            sequence_lengths.append(sequence_length)

        cumulative_lengths = [0]
        for sequence_length in sequence_lengths:
            cumulative_lengths.append(cumulative_lengths[-1] + sequence_length)

        if input_ids_reference is None:
            sequence_boundaries = torch.tensor(cumulative_lengths, dtype=torch.int32)
        else:
            sequence_boundaries = input_ids_reference.new_tensor(
                cumulative_lengths,
                dtype=torch.int32,
            )
        return sequence_boundaries


__all__ = ["FirstFitPackingSelector", "PackingSelector", "SamplePacker"]
hyper_parallel/data/batching/runtime_input.py
29
30
31
32
33
34
35
36
37
38
39

    @staticmethod
    def resolve(canonical_batch: Mapping[str, Any]) -> Any:
        """Read leading-zero ``cu_seq_lens`` from an Online batch."""
        raw_cu_seq_lens = canonical_batch["cu_seq_lens"]
        cu_seq_lens = raw_cu_seq_lens.to(torch.int32)
        return cu_seq_lens


class IndexedBoundaryResolver:
    """Recover global cumulative sequence boundaries from Indexed input IDs."""
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
    """Recover global cumulative sequence boundaries from Indexed input IDs."""

    def __init__(self, eod_token_id: int | None) -> None:
        """Store the token separating packed Indexed sequences."""
        self.eod_token_id = eod_token_id

    def resolve(self, canonical_batch: Mapping[str, Any]) -> Any:
        """Recover leading-zero ``cu_seq_lens`` from Indexed tokens."""
        input_ids = canonical_batch["input_ids"]
        batch_size, seq_len = input_ids.shape
        token_indices = torch.arange(seq_len, dtype=input_ids.dtype, device=input_ids.device)

        seq_ends = []
        for batch_idx in range(batch_size):
            if self.eod_token_id is None:
                eod_indices = token_indices[:0]
            else:
                eod_indices = token_indices[input_ids[batch_idx] == self.eod_token_id]

            prev_eod_idx = -1
            for eod_idx in eod_indices:
                eod_idx = int(eod_idx.item())
                if eod_idx == prev_eod_idx:
                    break

                seq_end = batch_idx * seq_len + eod_idx + 1
                seq_ends.append(seq_end)
                prev_eod_idx = eod_idx + 1

            row_end = (batch_idx + 1) * seq_len
            if not seq_ends or seq_ends[-1] != row_end:
                seq_ends.append(row_end)

        cu_seq_lens = torch.tensor([0, *seq_ends], dtype=torch.int32)
        return cu_seq_lens


@dataclass(frozen=True)
class RuntimeInputContext:
132
133
134
135
136
137
138
139
140
            batch=batch,
            context=runtime_context,
        )
        if not isinstance(runtime_inputs, Mapping):
            raise TypeError(
                "RuntimeInputAdapter.build_runtime_inputs must return a mapping, "
                f"got {type(runtime_inputs).__name__}"
            )
        return dict(runtime_inputs)
144
145
146
147
148
149
150
151
152

        Dynamic values may vary by batch, but field ownership must be stable so
        diagnostics can reject collisions before interpreting precision runs.
        """
        return ()

    @abstractmethod
    def build_runtime_inputs(
            self,
165
166
167
168
169
170
171
172
173
        Returns:
            Mapping merged into the model forward inputs. Keys must not replace
            fields already owned by the generic batch path.
        """
        raise NotImplementedError


class AttentionRuntime(RuntimeInputAdapter):
    """Build dense attention fields from common runtime context."""
180
181
182
183
184
185
186
187
188
189
190
191
            reset_mask: bool,
            sliding_window: int | None,
    ) -> None:
        """Store the attention representation and mask policy."""
        self.mode = mode
        self.create_mask = create_mask
        self.reset_mask = reset_mask
        self.sliding_window = sliding_window

    def build_runtime_inputs(
            self,
            *,
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
            batch: Mapping[str, Any],
            context: RuntimeInputContext,
    ) -> Mapping[str, Any]:
        """Build attention fields without owning model-specific packed metadata."""
        attention_inputs = {"attention_mask": None}
        if not self.create_mask or self.mode == "compressed":
            return attention_inputs

        micro_batch_size, local_sequence_length = batch["input_ids"].shape
        cp_size = context.parallel_sizes["cp"]
        global_sequence_length = local_sequence_length * cp_size
        attention_mask, sliding_window_mask = self._build_dense_attention_masks(
            cu_seq_lens=batch["cu_seq_lens"],
            micro_batch_size=micro_batch_size,
            seq_length=global_sequence_length,
            device=batch["input_ids"].device,
205
206
207
208
209
210
211
212
213
214
215
216
217
            micro_batch_size=micro_batch_size,
            seq_length=global_sequence_length,
            device=batch["input_ids"].device,
        )
        attention_inputs["attention_mask"] = attention_mask
        if sliding_window_mask is not None:
            attention_inputs["swa_mask"] = sliding_window_mask

        return attention_inputs

    def _build_dense_attention_masks(
            self,
            *,
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
            seq_length: int,
            device: Any,
    ) -> tuple[Any, Any | None]:
        """Build global dense attention and optional sliding-window masks."""
        boundaries = [int(boundary) for boundary in cu_seq_lens.tolist()]
        if len(boundaries) < 2 or boundaries[0] != 0:
            raise ValueError("cu_seq_lens must contain a leading zero and at least one sequence")

        if any(end <= start for start, end in zip(boundaries[:-1], boundaries[1:])):
            raise ValueError("cu_seq_lens must be strictly increasing")

        physical_sequence_length = micro_batch_size * seq_length
        if boundaries[-1] != physical_sequence_length:
            raise ValueError(
                "cu_seq_lens must cover the physical batch length "
                f"({physical_sequence_length}), but got {boundaries[-1]}"
            )

        attention_batch_size = micro_batch_size if self.reset_mask else 1
        attention_mask = torch.ones(
            (attention_batch_size, seq_length, seq_length),
            dtype=torch.bool,
            device=device,
        ).tril()
        attention_mask = attention_mask.view(attention_batch_size, 1, seq_length, seq_length)

        if self.reset_mask:
            for sequence_start in boundaries[:-1]:
                batch_index, row_sequence_start = divmod(sequence_start, seq_length)
                attention_mask[batch_index, 0, row_sequence_start:, :row_sequence_start] = False

        sliding_window_mask = None
        if self.sliding_window is not None:
            positions = torch.arange(seq_length, dtype=torch.int64, device=device)
            outside_window = positions.unsqueeze(1) - positions.unsqueeze(0) > self.sliding_window
            sliding_window_mask = attention_mask & ~outside_window.unsqueeze(0).unsqueeze(0)

        return attention_mask, sliding_window_mask


__all__ = [
    "AttentionRuntime",
hyper_parallel/data/dataset_logging.py
48
49
50
51
52
53
54
55
56
    def filter(self, record: logging.LogRecord) -> bool:
        """Keep regular logs and Dataset DEBUG logs from selected ranks."""
        rank = _get_rank()
        record.rank_id = rank
        if record.levelno != logging.DEBUG or not record.name.startswith(DATASET_LOGGER_NAME):
            return True
        rank_enabled = getattr(record, "dataset_rank_enabled", None)
        if rank_enabled is not None:
            return bool(rank_enabled)
115
116
117
118
119
120
121
122
123
                                     for rank in selected_ranks):
            raise ValueError("ranks must contain non-negative integers, or be None for all ranks")

    _DEBUG_RANK_FILTER.ranks = selected_ranks
    dataset_logger = logging.getLogger(DATASET_LOGGER_NAME)
    dataset_logger.setLevel(log_levels[level])
    dataset_logger.propagate = False
    dataset_handlers = [handler for handler in dataset_logger.handlers if _DEBUG_RANK_FILTER in handler.filters]
    if not dataset_handlers:
121
122
123
124
125
126
127
128
129
    dataset_logger.propagate = False
    dataset_handlers = [handler for handler in dataset_logger.handlers if _DEBUG_RANK_FILTER in handler.filters]
    if not dataset_handlers:
        dataset_handler = logging.StreamHandler()
        dataset_handler.setFormatter(_DatasetLogFormatter(DATASET_LOG_FORMAT, datefmt=DATASET_DATE_FORMAT))
        dataset_handler.addFilter(_DEBUG_RANK_FILTER)
        dataset_logger.addHandler(dataset_handler)
        dataset_handlers.append(dataset_handler)
    for dataset_handler in dataset_handlers:
hyper_parallel/data/indexed/indexed_data_reader.py
23
24
25
26
27
28
29
30
31
32
33
34
from typing import ClassVar

import numpy as np

from hyper_parallel.data.constants import INDEX_DTYPE_CODES, INDEX_HEADER, INDEX_VERSION
from hyper_parallel.data.dataset_logging import get_dataset_logger

logger = get_dataset_logger(__name__)


class _IndexReader:
    """Memory-map the compact sequence metadata stored in an index file."""
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    def __init__(self, idx_path: str) -> None:
        """Parse and memory-map one index metadata file."""
        # Read the fixed-size header, which describes how the following index arrays are decoded.
        with open(idx_path, "rb") as stream:
            header = stream.read(len(INDEX_HEADER))
            if header != INDEX_HEADER:
                raise ValueError(f"Invalid indexed Dataset header in {idx_path!r}")

            version = struct.unpack("<Q", stream.read(8))[0]
            if version != INDEX_VERSION:
                raise ValueError(f"Unsupported indexed Dataset version {version}; expected {INDEX_VERSION}")

            dtype_code = struct.unpack("<B", stream.read(1))[0]
            self.dtype = np.dtype(INDEX_DTYPE_CODES[dtype_code])
            self.dtype_size = self.dtype.itemsize

            self.sequence_count = struct.unpack("<Q", stream.read(8))[0]
            self.document_count = struct.unpack("<Q", stream.read(8))[0]
hyper_parallel/data/indexed/indexed_pretrain_dataset.py
27
28
29
30
31
32
33
34

import numpy as np
from numpy.random import RandomState

from hyper_parallel.data.constants import PAD_TOKEN_ID
from hyper_parallel.data.dataset_logging import get_dataset_logger
from hyper_parallel.data.indexed.indexed_data_config import GPTDatasetConfig
from hyper_parallel.data.indexed.indexed_data_reader import IndexedDataReader
104
105
106
107
108
109
110
111
112
113
114

        # Handle pad token id provided by the tokenizer.
        try:
            pad_token_id = self.config.tokenizer.pad
            self._pad_token_id = PAD_TOKEN_ID if pad_token_id is None else int(pad_token_id)
        except (AttributeError, NotImplementedError):
            self._pad_token_id = PAD_TOKEN_ID

        try:
            special_token_ids = [
                token_id
130
131
132
133
134
135
136
137
                    "The tokenizer PAD ID collides with another special token ID; matching tokens will also be "
                    "masked as padding"
                )
            else:
                self._pad_token_id = PAD_TOKEN_ID
                logger.warning(
                    "The tokenizer PAD ID collides with another special token ID; using an internal padding sentinel"
                )
hyper_parallel/data/omni/build_dataset.py
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
    Raises:
        TypeError: If transform is not an OmniDataTransform.
        ValueError: If no Omni transform is configured.
    """
    if transform is None:
        raise ValueError("Online Omni Dataset requires a data_transform")
    if not isinstance(transform, OmniDataTransform):
        raise TypeError("Online Omni Dataset transform must be an OmniDataTransform")

    dataset_config = dict(data_config)
    training_seed = getattr(training_config, "seed", None)
    dataset_config["random_seed"] = 42 if training_seed is None else int(training_seed)

    sample_filter = transform.is_valid_sample
    source_dataset = build_online_mapping_source(
        data_path=data_path,
        data_config=dataset_config,
        sample_filter=sample_filter,
    )
    transformed_dataset = _OmniMappingDataset.apply(source_dataset, transform)
    return transformed_dataset


class _OmniMappingDataset(MappingTransformDataset):
    """Retain the Omni encoding lifecycle around one Mapping source."""
 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
116
117
118
119
120
    """Retain the Omni encoding lifecycle around one Mapping source."""

    def __init__(self, source_dataset: Any, transform: OmniDataTransform) -> None:
        """Build the pre-selection strategy and retain post-selection hooks."""
        self.data_transform = transform
        self._transform_strategy = _OmniTransformStrategy.from_transform(transform)
        super().__init__(source_dataset, self._transform_strategy.prepare_samples)

    @classmethod
    def apply(cls, source_dataset: Any, transform: OmniDataTransform) -> Any:
        """Wrap one Mapping source or each available train-valid-test split."""
        if isinstance(source_dataset, tuple) and len(source_dataset) == ONLINE_SPLIT_COUNT:
            transformed_splits = []
            for split_dataset in source_dataset:
                transformed_dataset = None
                if split_dataset is not None:
                    transformed_dataset = cls(split_dataset, transform)
                transformed_splits.append(transformed_dataset)

            mapping_splits = (
                transformed_splits[0],
                transformed_splits[1],
                transformed_splits[2],
            )
            return mapping_splits

        if source_dataset is None:
            return None

        mapping_dataset = cls(source_dataset, transform)
        return mapping_dataset

    def encode_selected_sample(self, sample: Any) -> Any:
        """Finish encoding one sample after packing selection."""
        encoded_sample = self._transform_strategy.encode_selected_sample(sample)
        return encoded_sample

    def encode_batch(self, batch: Any) -> Any:
        """Apply the transform's final batch encoding hook."""
        encoded_batch = self.data_transform.encode_batch(batch)
        return encoded_batch


__all__ = ["build_online_omni_mapping_dataset"]
hyper_parallel/data/omni/omni_transform.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

    Returns:
        Processor with an attached tokenizer and resolved chat template.
    """
    processor = AutoProcessor.from_pretrained(
        pretrained_model_name_or_path,
        trust_remote_code=trust_remote_code,
        **processor_kwargs,
    )
    tokenizer = getattr(processor, "tokenizer", None)
    if tokenizer is None:
        raise ValueError("AutoProcessor must provide a tokenizer")
    chat_template = getattr(processor, "chat_template", None)
    if chat_template is None:
        chat_template = getattr(tokenizer, "chat_template", None)
        if chat_template is not None:
            processor.chat_template = chat_template

    return processor


class OmniDataTransform:
    """Define model-specific sample and batch encoding hooks.
 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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
    """

    def __init__(self, *, max_seq_len: int, processor: Any) -> None:
        """Store the sequence limit and externally built processor."""
        if max_seq_len <= 0:
            raise ValueError("max_seq_len must be positive")
        if processor is None:
            raise ValueError("processor is required")
        self.max_seq_len = max_seq_len
        self.processor = processor

    @staticmethod
    def is_valid_sample(sample: Mapping[str, Any]) -> bool:
        """Return whether one source record contains conversation messages."""
        messages = sample.get("messages")
        if not isinstance(messages, list):
            raise ValueError("Omni sample messages must be a list")
        return bool(messages)

    def encode_sample(self, sample: dict[str, Any]) -> dict[str, Any]:
        """Encode one source sample into one model-ready sample."""
        raise NotImplementedError(f"{type(self).__name__} does not implement encode_sample")

    def preencode_sample(self, sample: dict[str, Any]) -> dict[str, Any]:
        """Prepare metadata used by a packing selector without full encoding."""
        raise NotImplementedError(f"{type(self).__name__} does not implement preencode_sample")

    def postencode_sample(self, sample: dict[str, Any]) -> dict[str, Any]:
        """Encode selected online or offline metadata into a model-ready sample."""
        raise NotImplementedError(f"{type(self).__name__} does not implement postencode_sample")

    def encode_batch(self, batch: Any) -> Any:
        """Apply an optional model-specific batch encoding stage."""
        return batch

    def prepare_messages(self, sample: dict[str, Any]) -> list[dict[str, Any]]:
        """Copy messages and resolve media paths relative to their JSONL file."""
        messages = sample.get("messages")
        if not isinstance(messages, list) or not messages:
            raise ValueError("Omni sample must contain non-empty messages")

        prepared_messages = copy.deepcopy(messages)
        source_path = sample.get(ONLINE_SOURCE_PATH_KEY)
        if source_path is None:
            return prepared_messages

        source_directory = os.path.dirname(os.path.abspath(source_path))
        for message in prepared_messages:
            content = message.get("content")
            if not isinstance(content, list):
                continue
            for content_block in content:
                self._resolve_media_block(content_block, source_directory)
        return prepared_messages

    @classmethod
    def _resolve_media_block(cls, content_block: Any, source_directory: str) -> None:
        """Resolve path-bearing fields without changing the processor's message format."""
        if not isinstance(content_block, dict):
            return

        block_type = content_block.get("type")
        if block_type == "image_url":
            image_url = content_block.get("image_url")
            if isinstance(image_url, str):
                content_block["image_url"] = cls._resolve_media_path(image_url, source_directory)
            elif isinstance(image_url, dict) and isinstance(image_url.get("url"), str):
                image_url["url"] = cls._resolve_media_path(image_url["url"], source_directory)
            return

        media_field = content_block.get(block_type)
        if block_type in ("image", "video", "audio") and isinstance(media_field, str):
            content_block[block_type] = cls._resolve_media_path(media_field, source_directory)

    @staticmethod
    def _resolve_media_path(media_path: str, source_directory: str) -> str:
        """Keep remote and absolute references, and anchor local relative paths."""
        if os.path.isabs(media_path) or media_path.startswith(("http://", "https://", "data:")):
            return media_path

        resolved_path = os.path.normpath(os.path.join(source_directory, media_path))
        return resolved_path


class AutoProcessorTransform(OmniDataTransform):
    """Encode a conversation and supervise only its final assistant response."""
156
157
158
159
160
161
162
163
164
165
166
167
168
    """Encode a conversation and supervise only its final assistant response."""

    def encode_sample(self, sample: dict[str, Any]) -> dict[str, Any]:
        """Build model inputs and assistant-only labels from processor messages."""
        messages = self.prepare_messages(sample)
        if len(messages) < 2 or messages[-1].get("role") != "assistant":
            raise ValueError("Omni training sample must end with a final assistant message")

        encoded_sample = dict(self.processor.apply_chat_template(
            messages,
            tokenize=True,
            return_dict=True,
            return_tensors="pt",
167
168
169
170
171
172
173
174
175
            return_dict=True,
            return_tensors="pt",
            add_generation_prompt=False,
        ))
        prompt_sample = self.processor.apply_chat_template(
            messages[:-1],
            tokenize=True,
            return_dict=True,
            return_tensors="pt",
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
            return_tensors="pt",
            add_generation_prompt=True,
        )

        input_ids = encoded_sample["input_ids"].squeeze(0)
        prompt_ids = prompt_sample["input_ids"].squeeze(0)
        prompt_length = len(prompt_ids)
        if prompt_length >= len(input_ids) or not input_ids[:prompt_length].equal(prompt_ids):
            raise ValueError("Assistant prompt tokens must be a prefix of the full conversation")

        labels = input_ids.clone()
        labels[:prompt_length] = IGNORE_INDEX
        if "mm_token_type_ids" in encoded_sample:
            labels[encoded_sample["mm_token_type_ids"].squeeze(0) != 0] = IGNORE_INDEX

        for field in OMNI_CP_TOKEN_FIELDS.intersection(encoded_sample):
            encoded_sample[field] = encoded_sample[field].squeeze(0)
        encoded_sample["labels"] = labels
        return encoded_sample


SampleEncoder = Callable[[Any], Any]
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227

    @classmethod
    def from_transform(cls, transform: OmniDataTransform) -> "_OmniTransformStrategy":
        """Build one strategy from Energon-style overridden hooks."""
        encode_sample = cls._is_overridden(transform, "encode_sample")
        preencode_sample = cls._is_overridden(transform, "preencode_sample")
        postencode_sample = cls._is_overridden(transform, "postencode_sample")

        if encode_sample and (preencode_sample or postencode_sample):
            raise TypeError(
                f"{type(transform).__name__} cannot combine encode_sample with "
                "preencode_sample or postencode_sample"
            )
        if preencode_sample and not postencode_sample:
            raise TypeError(
                f"{type(transform).__name__} must pair preencode_sample with postencode_sample"
            )
        if not encode_sample and not postencode_sample:
            raise TypeError(
                f"{type(transform).__name__} must implement encode_sample or postencode_sample"
            )

        # encode_sample                     -> encode before candidate selection
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276

        # encode_sample                     -> encode before candidate selection
        # preencode_sample + postencode_sample -> prepare metadata, then defer full encoding
        # postencode_sample only            -> preserve offline metadata, then defer encoding
        sample_encoder = None
        if preencode_sample:
            sample_encoder = transform.preencode_sample
        elif encode_sample:
            sample_encoder = transform.encode_sample

        selected_sample_encoder = None
        if postencode_sample:
            selected_sample_encoder = transform.postencode_sample

        transform_strategy = cls(
            sample_encoder=sample_encoder,
            selected_sample_encoder=selected_sample_encoder,
        )
        return transform_strategy

    @staticmethod
    def _is_overridden(transform: OmniDataTransform, method_name: str) -> bool:
        """Return whether a transform hook differs from its base implementation."""
        transform_method = getattr(type(transform), method_name)
        base_method = getattr(OmniDataTransform, method_name)
        method_overridden = transform_method is not base_method
        return method_overridden

    def prepare_sample(self, sample: Any) -> Any:
        """Encode a sample or attach metadata before packing selection."""
        prepared_sample = sample
        if self.sample_encoder is not None:
            prepared_sample = self.sample_encoder(sample)
        return prepared_sample

    def encode_selected_sample(self, sample: Any) -> Any:
        """Finish encoding a sample after packing selection."""
        encoded_sample = sample
        if self.selected_sample_encoder is not None:
            encoded_sample = self.selected_sample_encoder(sample)
        return encoded_sample

    def prepare_samples(self, raw_sample: Any) -> list[Any]:
        """Return encoded, preencoded, or unchanged samples for candidate selection."""
        prepared_sample = self.prepare_sample(raw_sample)
        prepared_samples = [prepared_sample]
        return prepared_samples


__all__ = [
    "build_auto_processor",
hyper_parallel/data/online/iterable.py
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

    Returns:
        One Iterable Dataset on each DataLoader-owning rank.
    """
    normalized_context = dataloader_context or DataLoaderParallelContext()
    if normalized_context.data_index_cache:
        normalized_context = replace(normalized_context, data_index_cache=False)

    dataset_builder = _OnlineIterableSourceBuilder(data_path, data_config, normalized_context)

    def dataset_factory() -> Any:
        """Build the configured single-source or multi-source stream."""
        iterable_dataset = dataset_builder.build_source(sample_filter=sample_filter)
        return iterable_dataset

    iterable_source = build_dataset_for_dataloader(
        dataset_factory,
        normalized_context,
        barrier_needed=False,
    )
    return iterable_source


def _supports_output_index_for_resume(dataset: Any) -> bool:
    """Return whether a Dataset can emit and rebuild stable output indices."""
    get_item = getattr(dataset, "get_item", None)
    supports_output_index = callable(get_item) and hasattr(dataset, "output_index_for_resume")
    return supports_output_index


class IterableTransformDataset(IterableDataset):
    """Apply a sample transform lazily while preserving streaming source state."""
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188

        Returns:
            A transformed Iterable Dataset, or ``None`` on a non-owning rank.
        """
        if source_dataset is None:
            return None

        iterable_dataset = cls(source_dataset, transform)
        return iterable_dataset

    def __init__(self, source_dataset: Any, transform: SampleTransform) -> None:
        """Store the streaming source and its lazy sample transform."""
        self.source_dataset = source_dataset
        self.transform = transform

    @property
    def output_index_for_resume(self) -> bool:
        """Return whether the upstream source emits stable output indices."""
        if not _supports_output_index_for_resume(self.source_dataset):
            raise AttributeError("The upstream iterable does not support output-index resume")

        output_index_enabled = bool(self.source_dataset.output_index_for_resume)
        return output_index_enabled

    @output_index_for_resume.setter
    def output_index_for_resume(self, value: bool) -> None:
        """Forward output-index mode to a replayable upstream source."""
        if not _supports_output_index_for_resume(self.source_dataset):
            raise ValueError("The upstream iterable does not support output-index resume")

        self.source_dataset.output_index_for_resume = value

    def get_item(self, output_index: Any) -> list[Any]:
        """Rebuild and transform one upstream item from its stable output index."""
        if not _supports_output_index_for_resume(self.source_dataset):
            raise AttributeError("The upstream iterable does not support get_item")

        raw_sample = self.source_dataset.get_item(output_index)
        transformed_samples = self._transform_sample(raw_sample)
        return transformed_samples

    def __iter__(self) -> Any:
        """Transform source records lazily while preserving source order."""
        output_index_enabled = (
            _supports_output_index_for_resume(self.source_dataset)
            and bool(self.source_dataset.output_index_for_resume)
        )
        for source_item in self.source_dataset:
            if output_index_enabled:
                raw_sample, output_index = source_item
            else:
                raw_sample = source_item

            transformed_samples = self._transform_sample(raw_sample)
            if not transformed_samples:
                continue
            if output_index_enabled:
                yield transformed_samples, output_index
            elif len(transformed_samples) == 1:
                yield transformed_samples[0]
            else:
                yield transformed_samples

    def state_dict(self) -> dict[str, Any]:
        """Forward checkpoint state to a stateful upstream stream."""
        state_builder = getattr(self.source_dataset, "state_dict", None)
        source_state = {}
        if callable(state_builder):
            source_state = state_builder()
        transform_state = {"source_dataset": source_state}
        return transform_state

    def load_state_dict(self, state_dict: Mapping[str, Any]) -> None:
        """Restore checkpoint state through the upstream stream interface."""
        state_loader = getattr(self.source_dataset, "load_state_dict", None)
        if not callable(state_loader):
            raise ValueError("Online iterable source does not support load_state_dict")

        state_loader(state_dict["source_dataset"])

    def set_epoch(self, epoch: int) -> None:
        """Forward deterministic epoch state when supported upstream."""
        epoch_setter = getattr(self.source_dataset, "set_epoch", None)
        if callable(epoch_setter):
            epoch_setter(epoch)

    def _transform_sample(self, raw_sample: Any) -> list[Any]:
        """Transform one RawSample and enforce the zero/one/many contract."""
        transformed_samples = self.transform(raw_sample)
        if not isinstance(transformed_samples, list):
            raise TypeError("Online Iterable sample transform must return a list")

        return transformed_samples


class _LocalWeightedIterableDataset(IterableDataset):
    """Choose among already-sharded Iterable sources on the local rank."""
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
        stopping_strategy: str,
        vary_by_epoch: bool,
    ) -> None:
        """Store the rank-local sources and weighted selection policy."""
        self.source_datasets = tuple(source_datasets)
        self.weights = tuple(weights)
        self.random_seed = random_seed
        self.stopping_strategy = stopping_strategy
        self.vary_by_epoch = vary_by_epoch
        self.epoch = 0
        self._random_state = None
        self._active_indices = None
        self._exhausted_once = None

    def __iter__(self) -> Any:
        """Yield samples according to the configured local source weights."""
        source_epoch = getattr(self.source_datasets[0], "epoch", self.epoch)
        epoch = 0
        if self.vary_by_epoch:
            epoch = int(source_epoch)

        random_generator = random.Random(self.random_seed + epoch)
        if self._random_state is not None:
            random_generator.setstate(self._random_state)

        source_iterators = []
        for source_dataset in self.source_datasets:
            source_iterators.append(iter(source_dataset))

        if self._active_indices is None:
            active_indices = list(range(len(source_iterators)))
        else:
            active_indices = list(self._active_indices)

        if self._exhausted_once is None:
            exhausted_once = set()
        else:
            exhausted_once = set(self._exhausted_once)

        while active_indices:
            active_weights = []
            for active_index in active_indices:
                active_weights.append(self.weights[active_index])

            source_index = random_generator.choices(active_indices, weights=active_weights, k=1)[0]
            try:
                source_sample = next(source_iterators[source_index])
            except StopIteration:
                if self.stopping_strategy == "first_exhausted":
                    self._clear_iteration_state()
                    return

                exhausted_once.add(source_index)
                if self.stopping_strategy == "all_exhausted_without_replacement":
                    active_indices.remove(source_index)
                    self._save_iteration_state(random_generator, active_indices, exhausted_once)
                    continue

                if len(exhausted_once) == len(source_iterators):
                    self._clear_iteration_state()
                    return

                source_iterators[source_index] = iter(self.source_datasets[source_index])
                self._save_iteration_state(random_generator, active_indices, exhausted_once)
                continue

            self._save_iteration_state(random_generator, active_indices, exhausted_once)
            yield source_sample

        self._clear_iteration_state()

    def set_epoch(self, epoch: int) -> None:
        """Set the local blend epoch and forward it to every source."""
        self.epoch = epoch
        self._clear_iteration_state()
        for source_dataset in self.source_datasets:
            epoch_setter = getattr(source_dataset, "set_epoch", None)
            if callable(epoch_setter):
                epoch_setter(epoch)

    def state_dict(self) -> dict[str, Any]:
        """Save source cursors and the local weighted-selection cursor."""
        source_states = []
        for source_dataset in self.source_datasets:
            state_builder = getattr(source_dataset, "state_dict", None)
            source_state = None
            if callable(state_builder):
                source_state = state_builder()
            source_states.append(source_state)

        source_epoch = getattr(self.source_datasets[0], "epoch", self.epoch)
        dataset_state = {
            "epoch": int(source_epoch),
            "random_state": self._random_state,
            "active_indices": self._active_indices,
            "exhausted_once": self._exhausted_once,
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
            "active_indices": self._active_indices,
            "exhausted_once": self._exhausted_once,
            "sources": source_states,
        }
        return dataset_state

    def load_state_dict(self, state_dict: Mapping[str, Any]) -> None:
        """Restore source cursors and the local weighted-selection cursor."""
        source_states = state_dict["sources"]
        if len(source_states) != len(self.source_datasets):
            raise ValueError("Online source count does not match the saved local blend state")

        for source_dataset, source_state in zip(self.source_datasets, source_states):
            if source_state is None:
                continue
            state_loader = getattr(source_dataset, "load_state_dict", None)
            if not callable(state_loader):
                raise ValueError("Online iterable source does not support load_state_dict")
            state_loader(source_state)

        self.epoch = int(state_dict["epoch"])
        self._random_state = state_dict["random_state"]
        self._active_indices = state_dict["active_indices"]
        self._exhausted_once = state_dict["exhausted_once"]

    def _save_iteration_state(
        self,
        random_generator: random.Random,
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
        active_indices: Sequence[int],
        exhausted_once: set[int],
    ) -> None:
        """Record the local source-selection cursor after one decision."""
        self._random_state = random_generator.getstate()
        self._active_indices = tuple(active_indices)
        self._exhausted_once = tuple(sorted(exhausted_once))

    def _clear_iteration_state(self) -> None:
        """Reset the local source-selection cursor for a new epoch."""
        self._random_state = None
        self._active_indices = None
        self._exhausted_once = None


class _OnlineIterableSourceBuilder(_OnlineSourceBuilder):
    """Build the rank-local or globally interleaved streaming source graph."""
339
340
341
342
343
344
345
346
347
348
        data_config: Mapping[str, Any],
        dataloader_context: DataLoaderParallelContext,
    ) -> None:
        """Store source configuration and DataLoader ownership."""
        super().__init__(data_path, data_config)
        self.dataloader_context = dataloader_context

    def build_source(
        self,
        *,
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
        *,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None = None,
    ) -> Any:
        """Load Iterable sources and apply their shard, shuffle, and blend policy."""
        source_loaders, weights = self._create_source_loaders()
        source_datasets = []
        for source_loader in source_loaders:
            source_dataset = source_loader.load(streaming=True, sample_filter=sample_filter)
            if isinstance(source_dataset, tuple):
                raise ValueError("Online Iterable Dataset requires one train source per entry")
            source_datasets.append(source_dataset)

        if len(source_datasets) == 1:
            iterable_dataset = self._prepare_local_source(source_datasets[0])
            return iterable_dataset

        blend_strategy = str(self.data_config.get("blend_strategy", "local_weighted"))
        if blend_strategy not in ONLINE_BLEND_STRATEGIES:
            raise ValueError(
                f"Unsupported Online blend_strategy {blend_strategy!r}; "
                f"expected one of {ONLINE_BLEND_STRATEGIES!r}"
            )

        if blend_strategy == "local_weighted":
            local_sources = []
            for source_dataset in source_datasets:
                local_source = self._prepare_local_source(source_dataset)
                local_sources.append(local_source)

            iterable_dataset = _LocalWeightedIterableDataset(
                local_sources,
                weights,
                random_seed=self._random_seed,
                stopping_strategy=self._stopping_strategy,
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
                random_seed=self._random_seed,
                stopping_strategy=self._stopping_strategy,
                vary_by_epoch=self._shuffle_enabled,
            )
            return iterable_dataset

        shuffled_sources = []
        for source_dataset in source_datasets:
            shuffled_source = self._shuffle(source_dataset)
            shuffled_sources.append(shuffled_source)

        iterable_dataset = self._interleave_sources(shuffled_sources, weights)
        if self._shuffle_enabled:
            # buffer_size=1 preserves output order while making set_epoch reseed the full source graph.
            iterable_dataset = iterable_dataset.shuffle(seed=self._random_seed, buffer_size=1)
        if self._split_by_data_parallel:
            iterable_dataset = split_iterable_dataset_by_dp(iterable_dataset, self.dataloader_context)
        return iterable_dataset

    @property
    def _shuffle_enabled(self) -> bool:
        """Return whether Iterable sources use local buffer shuffle."""
        return bool(self.data_config.get("shuffle", True))

    @property
    def _split_by_data_parallel(self) -> bool:
        """Return whether Iterable sources own DP sharding."""
        return bool(self.data_config.get("split_by_data_parallel", True))

    def _prepare_local_source(self, source_dataset: Any) -> Any:
        """Apply per-source DP sharding before local buffer shuffle."""
        if self._split_by_data_parallel:
            source_dataset = split_iterable_dataset_by_dp(source_dataset, self.dataloader_context)
        local_dataset = self._shuffle(source_dataset)
        return local_dataset

    def _shuffle(self, source_dataset: Any) -> Any:
        """Apply deterministic buffer shuffle when configured."""
        if not self._shuffle_enabled:
            return source_dataset

        buffer_size = int(self.data_config.get("shuffle_buffer_size", 10_000))
        if buffer_size <= 0:
            raise ValueError("Online shuffle_buffer_size must be positive")

        shuffled_dataset = source_dataset.shuffle(seed=self._random_seed, buffer_size=buffer_size)
        return shuffled_dataset


__all__ = [
    "IterableTransformDataset",
hyper_parallel/data/online/mapping.py
56
57
58
59
60
61
62
63
64
65
66

    Returns:
        One Mapping Dataset or a train-valid-test tuple.
    """
    dataset_builder = _OnlineMappingSourceBuilder(data_path, data_config, train_valid_test_num_samples)
    mapping_source = dataset_builder.build_source(sample_filter=sample_filter)
    return mapping_source


class MappingTransformDataset:
    """Apply a sample transform lazily to an integer-indexed source Dataset."""
66
67
68
69
70
71
72
73
74
75
    """Apply a sample transform lazily to an integer-indexed source Dataset."""

    def __init__(self, source_dataset: Any, transform: SampleTransform) -> None:
        """Store the source Dataset and its lazy sample transform."""
        self.source_dataset = source_dataset
        self.transform = transform

    @classmethod
    def apply(cls, source_dataset: Any, transform: SampleTransform) -> Any:
        """Transform one Mapping Dataset or each available split.
 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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

        Returns:
            A transformed Mapping Dataset or transformed split tuple.
        """
        if isinstance(source_dataset, tuple) and len(source_dataset) == ONLINE_SPLIT_COUNT:
            transformed_splits = []
            for split_dataset in source_dataset:
                transformed_dataset = None
                if split_dataset is not None:
                    transformed_dataset = cls(split_dataset, transform)
                transformed_splits.append(transformed_dataset)

            mapping_splits = (transformed_splits[0], transformed_splits[1], transformed_splits[2])
            return mapping_splits

        if source_dataset is None:
            return None

        mapping_dataset = cls(source_dataset, transform)
        return mapping_dataset

    def __len__(self) -> int:
        """Return the source Dataset length."""
        dataset_length = len(self.source_dataset)
        return dataset_length

    def get_item(self, index: int) -> list[Any]:
        """Transform one source index into zero, one, or many output samples."""
        dataset_length = len(self.source_dataset)
        if index < 0:
            index += dataset_length
        if index < 0 or index >= dataset_length:
            raise IndexError("Online Mapping Dataset index out of range")

        raw_sample = self.source_dataset[index]
        transformed_samples = self.transform(raw_sample)
        if not isinstance(transformed_samples, list):
            raise TypeError("Online Mapping sample transform must return a list")

        return transformed_samples

    def __getitem__(self, index: int) -> Any:
        """Return exactly one sample for fixed-size DataLoader consumers."""
        transformed_samples = self.get_item(index)
        if not transformed_samples:
            raise ValueError(
                "An empty transform result requires source filtering or TokenBatchLoader"
            )
        if len(transformed_samples) != 1:
            raise ValueError("A multi-sample transform result requires TokenBatchLoader")

        transformed_sample = transformed_samples[0]
        return transformed_sample


class _JsonlMappingSource:
    """Read heterogeneous JSONL records without imposing an Arrow schema."""
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
        data_files: Sequence[str],
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> None:
        """Index non-empty JSONL records by file path and byte offset."""
        self._record_locations: list[tuple[str, int]] = []
        self._record_start = 0
        self._record_end = 0
        self._file_handles: dict[str, Any] = {}
        self._reader_process_id = os.getpid()

        for data_file in data_files:
            self._index_file(data_file, sample_filter)
        self._record_end = len(self._record_locations)

    def __len__(self) -> int:
        """Return the number of indexed JSONL records."""
        record_count = self._record_end - self._record_start
        return record_count

    def __getitem__(self, index: int) -> dict[str, Any]:
        """Deserialize one record and attach its source file context."""
        record_count = len(self)
        if index < 0:
            index += record_count
        if index < 0 or index >= record_count:
            raise IndexError("Online JSONL source index out of range")

        record_index = self._record_start + index
        data_file, byte_offset = self._record_locations[record_index]
        file_handle = self._get_file_handle(data_file)
        file_handle.seek(byte_offset)
        raw_record = file_handle.readline()
        source_record = self._parse_record(raw_record, data_file)
        source_record[ONLINE_SOURCE_PATH_KEY] = data_file
        return source_record

    def split_by_percentages(
        self,
        percentages: Sequence[int],
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
        self,
        percentages: Sequence[int],
    ) -> tuple[_JsonlMappingSource | None, _JsonlMappingSource | None, _JsonlMappingSource | None]:
        """Create train, validation, and test views over the filtered index."""
        record_count = len(self)
        train_end = int(round(percentages[0] * record_count / 100.0))
        valid_end = int(round((percentages[0] + percentages[1]) * record_count / 100.0))
        split_ranges = ((0, train_end), (train_end, valid_end), (valid_end, record_count))

        split_sources = []
        for percentage, (split_start, split_end) in zip(percentages, split_ranges):
            split_source = None
            if percentage > 0:
                split_source = self._create_view(split_start, split_end)
            split_sources.append(split_source)
        jsonl_splits = (split_sources[0], split_sources[1], split_sources[2])
        return jsonl_splits

    def __getstate__(self) -> dict[str, Any]:
        """Exclude process-local file handles when DataLoader workers pickle the source."""
        source_state = dict(self.__dict__)
        source_state["_file_handles"] = {}
        source_state["_reader_process_id"] = None
        return source_state

    def _index_file(
        self,
        data_file: str,
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
        data_file: str,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> None:
        """Record byte offsets and evaluate an optional raw-sample filter."""
        with open(data_file, "rb") as file_handle:
            while True:
                byte_offset = file_handle.tell()
                raw_record = file_handle.readline()
                if not raw_record:
                    break
                if not raw_record.strip():
                    continue
                if sample_filter is not None:
                    source_record = self._parse_record(raw_record, data_file)
                    if not sample_filter(source_record):
                        continue
                self._record_locations.append((data_file, byte_offset))

    def _create_view(self, start: int, end: int) -> _JsonlMappingSource:
        """Create a zero-copy view that shares this source's record locations."""
        split_source = self.__class__.__new__(self.__class__)
        split_source._record_locations = self._record_locations
        split_source._record_start = self._record_start + start
        split_source._record_end = self._record_start + end
        split_source._file_handles = {}
        split_source._reader_process_id = os.getpid()
        return split_source

    def _get_file_handle(self, data_file: str) -> Any:
        """Return a binary reader owned by the current DataLoader process."""
        process_id = os.getpid()
        if self._reader_process_id != process_id:
            self._close_file_handles()
            self._reader_process_id = process_id

        file_handle = self._file_handles.get(data_file)
        if file_handle is None:
            file_handle = open(data_file, "rb")
            self._file_handles[data_file] = file_handle
        return file_handle

    def _close_file_handles(self) -> None:
        """Close readers inherited from another process or opened locally."""
        for file_handle in self._file_handles.values():
            file_handle.close()
        self._file_handles = {}

    @staticmethod
    def _parse_record(raw_record: bytes, data_file: str) -> dict[str, Any]:
        """Decode one JSON object while preserving heterogeneous nested values."""
        try:
            source_record = json.loads(raw_record)
        except (UnicodeDecodeError, json.JSONDecodeError) as error:
            raise ValueError(f"Invalid JSONL record in {data_file}") from error
        if not isinstance(source_record, dict):
            raise ValueError(f"Online JSONL records must be objects: {data_file}")
        return source_record


class _OnlineBlendedMappingDataset:
    """Expose Mapping sources through Megatron-style deterministic indices."""
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
        size: int,
        random_seed: int,
    ) -> None:
        """Build the source schedule and source-local shuffle indices."""
        if not source_datasets or len(source_datasets) != len(weights):
            raise ValueError("Mapping blend sources and weights must be non-empty and aligned")
        if len(source_datasets) >= np.iinfo(np.int16).max:
            raise ValueError("Mapping blend supports fewer than 32767 sources")
        if size <= 0:
            raise ValueError("Mapping blend size must be positive")

        source_lengths = []
        for source_dataset in source_datasets:
            source_length = len(source_dataset)
            if source_length <= 0:
                raise ValueError("Mapping blend sources must not be empty")
            source_lengths.append(source_length)

        self.source_datasets = tuple(source_datasets)
        self.weights = self._normalize_weights(weights)
        self.size = size
        self.source_index, self.source_sample_index = self._build_blend_indices()
        self.source_shuffle_indices = self._build_source_shuffle_indices(
            source_lengths,
            random_seed,
        )
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
        )

    def __len__(self) -> int:
        """Return the requested global sample count."""
        return self.size

    def __getitem__(self, index: int) -> Any:
        """Resolve one global index to its source and shuffled source sample."""
        source_id = int(self.source_index[index])
        source_sample_id = int(self.source_sample_index[index])
        shuffled_sample_id = int(self.source_shuffle_indices[source_id][source_sample_id])
        source_sample = self.source_datasets[source_id][shuffled_sample_id]
        return source_sample

    @staticmethod
    def _normalize_weights(weights: Sequence[float]) -> tuple[float, ...]:
        """Validate and normalize source weights."""
        if not weights or any(weight <= 0.0 for weight in weights):
            raise ValueError("Mapping blend weights must be positive")

        weight_sum = sum(weights)
        normalized_weights = []
        for weight in weights:
            normalized_weights.append(float(weight) / weight_sum)
        return tuple(normalized_weights)

    def _build_blend_indices(self) -> tuple[np.ndarray, np.ndarray]:
        """Build Megatron's deterministic largest-deficit source schedule."""
        source_index = np.empty(self.size, dtype=np.int16)
        source_logical_index = np.empty(self.size, dtype=np.int64)
        source_sample_counts = [0] * len(self.source_datasets)

        for global_sample_index in range(self.size):
            scheduled_samples = max(global_sample_index, 1)
            selected_source = 0
            largest_deficit = self.weights[0] * scheduled_samples - source_sample_counts[0]
            for source_id in range(1, len(self.source_datasets)):
                source_deficit = (
                    self.weights[source_id] * scheduled_samples
                    - source_sample_counts[source_id]
                )
                if source_deficit > largest_deficit:
                    selected_source = source_id
                    largest_deficit = source_deficit

            source_index[global_sample_index] = selected_source
            source_logical_index[global_sample_index] = source_sample_counts[selected_source]
            source_sample_counts[selected_source] += 1

        return source_index, source_logical_index

    def _build_source_shuffle_indices(
        self,
        source_lengths: Sequence[int],
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
        source_lengths: Sequence[int],
        random_seed: int,
    ) -> tuple[np.ndarray, ...]:
        """Map source-local counters to repeated deterministic sample permutations."""
        source_sample_counts = np.bincount(
            self.source_index,
            minlength=len(self.source_datasets),
        )
        source_random = np.random.RandomState(random_seed)
        source_shuffle_indices = []
        for source_length, requested_samples in zip(source_lengths, source_sample_counts):
            repeated_epochs = int(np.ceil(int(requested_samples) / source_length))
            source_sample_index = np.tile(
                np.arange(source_length, dtype=np.int64),
                repeated_epochs,
            )
            source_random.shuffle(source_sample_index)
            source_shuffle_indices.append(source_sample_index[:requested_samples])
        return tuple(source_shuffle_indices)


class _OnlineMappingSourceBuilder(_OnlineSourceBuilder):
    """Build split-local, DP-global Mapping sample indices."""
375
376
377
378
379
380
381
382
383
384
        data_config: Mapping[str, Any],
        train_valid_test_num_samples: Sequence[int] | None,
    ) -> None:
        """Store source configuration and optional training sample targets."""
        super().__init__(data_path, data_config)
        self.train_valid_test_num_samples = self._validate_split_sizes(train_valid_test_num_samples)

    def build_source(
        self,
        *,
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
        *,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None = None,
    ) -> Any:
        """Load Mapping sources and compose their global split graphs."""
        source_loaders, weights = self._create_source_loaders()
        source_results = []
        for source_loader in source_loaders:
            source_result = self._build_leaf_source(source_loader, sample_filter)
            source_results.append(source_result)

        # Example: source A returns Dataset, while source B returns (train, valid, test).
        # The combined result follows B's split structure, and A contributes only to train.
        has_split_result = False
        for source_result in source_results:
            if isinstance(source_result, tuple):
                has_split_result = True
                break

        if not has_split_result:
            target_size = self._get_target_size(0, source_results)
            mapping_dataset = self._build_blended_split(source_results, weights, target_size)
            return mapping_dataset

        # source of train, valid, test => each source blend
        blended_splits = []
        for split_index in range(ONLINE_SPLIT_COUNT):
            split_datasets = []
            split_weights = []
            for source_result, weight in zip(source_results, weights):
                if isinstance(source_result, tuple):
                    split_dataset = source_result[split_index]
                elif split_index == 0:
                    split_dataset = source_result
                else:
                    split_dataset = None

                if split_dataset is not None:
                    split_datasets.append(split_dataset)
                    split_weights.append(weight)

            target_size = self._get_target_size(split_index, split_datasets)
            blended_split = self._build_blended_split(
                split_datasets,
                split_weights,
                target_size,
            )
            blended_splits.append(blended_split)

        # train, valid, test
        mapping_splits = (blended_splits[0], blended_splits[1], blended_splits[2])
        return mapping_splits

    @classmethod
    def _build_leaf_source(
        cls,
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
        source_loader: Any,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> Any:
        """Build one leaf through native JSONL indexing or the general loader."""
        data_path = source_loader.data_path
        split_config = source_loader.data_config.get("split")
        if isinstance(data_path, Mapping) and split_config is None:
            jsonl_split_sources = cls._try_build_presplit_jsonl_sources(
                data_path,
                sample_filter,
            )
            if jsonl_split_sources is not None:
                return jsonl_split_sources

        if not isinstance(data_path, Mapping):
            jsonl_files = cls._resolve_native_jsonl_files(data_path)
            logger.debug("Resolved native JSONL files: %s", jsonl_files)
            if jsonl_files is not None:
                jsonl_source = _JsonlMappingSource(jsonl_files, sample_filter)
                if split_config is not None:
                    split_percentages = source_loader._parse_split_percentages(split_config)
                    jsonl_splits = jsonl_source.split_by_percentages(split_percentages)
                    return jsonl_splits
                return jsonl_source

        mapping_source = source_loader.load(
            streaming=False,
            sample_filter=sample_filter,
        )
        return mapping_source

    @classmethod
    def _try_build_presplit_jsonl_sources(
        cls,
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
        data_path: Mapping[str, OnlinePathSpec],
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> tuple[Any, Any, Any] | None:
        """Try to preserve configured splits with native JSONL sources."""
        if not data_path:
            return None

        resolved_split_files = {}
        for split_name, split_path in data_path.items():
            if split_name not in ONLINE_SPLIT_NAMES:
                return None
            jsonl_files = cls._resolve_native_jsonl_files(split_path)
            if jsonl_files is None:
                return None
            resolved_split_files[split_name] = jsonl_files

        split_sources = []
        for split_name in ONLINE_SPLIT_NAMES:
            split_source = None
            jsonl_files = resolved_split_files.get(split_name)
            if jsonl_files is not None:
                split_source = _JsonlMappingSource(jsonl_files, sample_filter)
            split_sources.append(split_source)
        jsonl_split_sources = (split_sources[0], split_sources[1], split_sources[2])
        return jsonl_split_sources

    @staticmethod
    def _resolve_native_jsonl_files(data_path: OnlinePathSpec) -> list[str] | None:
        """Resolve a local JSONL file, glob, directory, or ordered file list."""
        configured_paths = [data_path] if isinstance(data_path, str) else list(data_path)
        data_files = []
        for configured_path in configured_paths:
            matched_paths = sorted(glob.glob(configured_path))
            if os.path.isdir(configured_path):
                matched_paths = sorted(glob.glob(os.path.join(configured_path, "*.jsonl")))
            if not matched_paths:
                return None
            data_files.extend(matched_paths)

        if not data_files:
            return None
        for data_file in data_files:
            if not os.path.isfile(data_file) or os.path.splitext(data_file)[1].lower() != ".jsonl":
                return None
        return data_files

    def _get_target_size(self, split_index: int, source_datasets: Sequence[Any]) -> int:
        """Return the configured sample target or one natural blended epoch."""
        if self.train_valid_test_num_samples is not None:
            return self.train_valid_test_num_samples[split_index]

        natural_size = 0
        for source_dataset in source_datasets:
            natural_size += len(source_dataset)
        return natural_size

    def _build_blended_split(
        self,
        source_datasets: Sequence[Any],
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
        weights: Sequence[float],
        target_size: int,
    ) -> Any:
        """Build one deterministic source and sample index graph."""
        nonempty_datasets = []
        nonempty_weights = []
        for source_dataset, weight in zip(source_datasets, weights):
            if len(source_dataset) == 0:
                continue
            nonempty_datasets.append(source_dataset)
            nonempty_weights.append(weight)

        if not nonempty_datasets or target_size == 0:
            return None

        blended_dataset = _OnlineBlendedMappingDataset(
            nonempty_datasets,
            nonempty_weights,
            target_size,
            self._random_seed,
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
            nonempty_weights,
            target_size,
            self._random_seed,
        )
        return blended_dataset

    @staticmethod
    def _validate_split_sizes(split_sizes: Sequence[int] | None) -> tuple[int, int, int] | None:
        """Validate optional train, validation, and test sample targets."""
        if split_sizes is None:
            return None
        if len(split_sizes) != ONLINE_SPLIT_COUNT:
            raise ValueError("Online Mapping sample targets must contain train, validation, and test sizes")

        normalized_sizes = (int(split_sizes[0]), int(split_sizes[1]), int(split_sizes[2]))
        if any(size < 0 for size in normalized_sizes):
            raise ValueError("Online Mapping sample targets must be non-negative")
        return normalized_sizes


__all__ = [
    "build_online_mapping_source",
hyper_parallel/data/online/source.py
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
    """Share source parsing and interleave policy across Online adapters."""

    def __init__(self, data_path: OnlineDataPath | None, data_config: Mapping[str, Any]) -> None:
        """Store the source and composition configuration."""
        self.data_path = data_path
        self.data_config = data_config

    @property
    def _random_seed(self) -> int:
        """Return the common source shuffle and blend seed."""
        return int(self.data_config.get("random_seed", 42))

    @property
    def _stopping_strategy(self) -> str:
        """Return the multi-source exhaustion policy."""
        stopping_strategy = str(self.data_config.get("stopping_strategy", "first_exhausted"))
        if stopping_strategy not in ONLINE_STOPPING_STRATEGIES:
            raise ValueError(
                f"Unsupported Online stopping_strategy {stopping_strategy!r}; "
                f"expected one of {ONLINE_STOPPING_STRATEGIES!r}"
            )
        return stopping_strategy

    def _create_source_loaders(self) -> tuple[list[_OnlineLeafSourceLoader], list[float]]:
        """Create one leaf source loader per configured source.
 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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134

        Returns:
            Leaf source loaders and their corresponding unnormalized weights.
        """
        sources = self.data_config.get("sources")
        if sources is None:
            if self.data_path is None:
                raise ValueError("Online Dataset requires data_path or data_config.sources")
            source_loader = _OnlineLeafSourceLoader(self.data_path, self.data_config)
            return [source_loader], [1.0]

        if self.data_path is not None:
            raise ValueError("Online Dataset cannot combine data_path with data_config.sources")
        if isinstance(sources, (str, bytes)) or not isinstance(sources, Sequence) or not sources:
            raise ValueError("Online data_config.sources must be a non-empty sequence")

        common_config = dict(self.data_config)
        common_config.pop("sources")
        source_loaders = []
        source_weights = []
        for source in sources:
            if not isinstance(source, Mapping):
                raise ValueError("Each Online source must be a mapping with data_path and weight")
            try:
                source_path = source["data_path"]
                source_weight = float(source["weight"])
            except KeyError as error:
                raise ValueError("Each Online source must define data_path and weight") from error
            if source_weight <= 0:
                raise ValueError("Online source weight must be positive")

            source_config = dict(common_config)
            for option_name, option_value in source.items():
                if option_name not in ("data_path", "weight"):
                    source_config[option_name] = option_value
            source_loaders.append(_OnlineLeafSourceLoader(source_path, source_config))
            source_weights.append(source_weight)
        return source_loaders, source_weights

    def _interleave_sources(self, source_datasets: Sequence[Any], weights: Sequence[float]) -> Any:
        """Interleave Mapping or Iterable sources through the Hugging Face interface."""
        if len(source_datasets) == 1:
            return source_datasets[0]

        try:
            # datasets remains optional for Indexed-only installations.
            from datasets import interleave_datasets  # pylint: disable=C0415
        except ImportError as error:
            raise ImportError("Online Dataset requires the optional 'datasets' package") from error

        weight_sum = sum(weights)
        probabilities = []
        for weight in weights:
            probability = weight / weight_sum
            probabilities.append(probability)

        interleaved_dataset = interleave_datasets(
            list(source_datasets),
            probabilities=probabilities,
            seed=self._random_seed,
            stopping_strategy=self._stopping_strategy,
132
133
134
135
136
137
138
139
140
            probabilities=probabilities,
            seed=self._random_seed,
            stopping_strategy=self._stopping_strategy,
        )
        return interleaved_dataset


class _OnlineLeafSourceLoader:
    """Load one local or Hub source with ``datasets.load_dataset``."""
140
141
142
143
144
145
146
147
148
149
    """Load one local or Hub source with ``datasets.load_dataset``."""

    def __init__(self, data_path: OnlineDataPath, data_config: Mapping[str, Any]) -> None:
        """Store one leaf source description."""
        self.data_path = data_path
        self.data_config = data_config

    def load(
        self,
        *,
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
        streaming: bool,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None = None,
    ) -> Any:
        """Load, restore configured splits, and filter one leaf source."""
        try:
            # datasets remains optional for Indexed-only installations.
            from datasets import load_dataset  # pylint: disable=C0415
        except ImportError as error:
            raise ImportError("Online Dataset requires the optional 'datasets' package") from error

        # Examples: "Salesforce/wikitext", "/data/*.parquet", {"train": "/data/train.jsonl"}.
        load_dataset_path, load_options, split_expressions = self._build_load_request(streaming=streaming)
        ratio_split_config = self.data_config.get("split")
        filter_before_ratio_split = ratio_split_config is not None and sample_filter is not None
        selected_split: str | list[str] = "train"
        if split_expressions is not None and not filter_before_ratio_split:
            selected_splits = []
            for split_expression in split_expressions:
                if split_expression is not None:
                    selected_splits.append(split_expression)
            selected_split = selected_splits

        loaded_dataset = load_dataset(
            load_dataset_path,
            **load_options,
            split=selected_split,
            streaming=streaming,
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
            streaming=streaming,
            cache_dir=self.data_config.get("cache_dir"),
        )

        if filter_before_ratio_split:
            filtered_dataset = loaded_dataset.filter(sample_filter)
            split_percentages = self._parse_split_percentages(ratio_split_config)
            filtered_splits = self._split_filtered_dataset(filtered_dataset, split_percentages)
            return filtered_splits

        dataset_result = loaded_dataset
        if split_expressions is not None:
            # Example: ("train", None, "test") keeps the empty valid slot.
            loaded_dataset_iterator = iter(loaded_dataset)
            restored_splits = []
            for split_expression in split_expressions:
                if split_expression is None:
                    restored_splits.append(None)
                    continue
                loaded_split = next(loaded_dataset_iterator)
                restored_splits.append(loaded_split)
            dataset_result = (restored_splits[0], restored_splits[1], restored_splits[2])

        if sample_filter is None:
            return dataset_result

        # Example: (train, None, test) filters available splits and preserves the empty valid slot.
        if isinstance(dataset_result, tuple):
            filtered_splits = []
            for split_dataset in dataset_result:
                if split_dataset is None:
                    filtered_splits.append(None)
                    continue
                filtered_split = split_dataset.filter(sample_filter)
                filtered_splits.append(filtered_split)
            filtered_dataset = (filtered_splits[0], filtered_splits[1], filtered_splits[2])
            return filtered_dataset

        filtered_dataset = dataset_result.filter(sample_filter)
        return filtered_dataset

    def _build_load_request(
        self,
        *,
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
        *,
        streaming: bool,
    ) -> tuple[str, dict[str, Any], tuple[str | None, str | None, str | None] | None]:
        """Build the path, keyword arguments, and split selections for ``load_dataset``."""
        split_config = self.data_config.get("split")
        split_expressions = None
        if split_config is not None and streaming:
            raise ValueError("Online ratio split does not support streaming")

        if split_config is not None and isinstance(self.data_path, Mapping):
            raise ValueError("Online ratio split cannot be combined with pre-split paths")

        if split_config is not None:
            split_expressions = self._parse_split_ratios(split_config)

        load_options: dict[str, Any] = {}
        # Example: {"train": "/data/train.jsonl", "valid": "/data/valid.jsonl"}.
        if isinstance(self.data_path, Mapping):
            data_files, load_dataset_path, split_expressions = self._resolve_split_data_files(self.data_path)
            load_options["data_files"] = data_files
            resolved_request = (load_dataset_path, load_options, split_expressions)
            return resolved_request

        is_local_path = not isinstance(self.data_path, str)
        if isinstance(self.data_path, str):
            file_extension = os.path.splitext(self.data_path)[1].lower()
            is_local_path = os.path.exists(self.data_path) or file_extension in ONLINE_FILE_FORMATS

        # Examples: ["/data/a.jsonl", "/data/b.jsonl"], "/data/*.parquet".
        if is_local_path:
            data_files, load_dataset_path = self._collect_local_data_files(self.data_path)
            load_options["data_files"] = data_files
            resolved_request = (load_dataset_path, load_options, split_expressions)
            return resolved_request

        # Example: "Salesforce/wikitext" is a Hub dataset ID.
        load_dataset_path = self.data_path
        config_name = self.data_config.get("config_name")
        if config_name is not None:
            load_options["name"] = config_name

        resolved_request = (load_dataset_path, load_options, split_expressions)
        return resolved_request

    @staticmethod
    def _split_filtered_dataset(dataset: Any, percentages: Sequence[int]) -> tuple[Any, Any, Any]:
        """Split one filtered Mapping Dataset using deterministic contiguous ranges."""
        dataset_length = len(dataset)
        train_end = int(round(percentages[0] * dataset_length / 100.0))
        valid_end = int(round((percentages[0] + percentages[1]) * dataset_length / 100.0))
        split_ranges = ((0, train_end), (train_end, valid_end), (valid_end, dataset_length))

        split_datasets = []
        for percentage, (split_start, split_end) in zip(percentages, split_ranges):
            split_dataset = None
            if percentage > 0:
                split_dataset = dataset.select(range(split_start, split_end))
            split_datasets.append(split_dataset)
        dataset_splits = (split_datasets[0], split_datasets[1], split_datasets[2])
        return dataset_splits

    def _resolve_split_data_files(
        self,
        data_path: Mapping[str, OnlinePathSpec],
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
        self,
        data_path: Mapping[str, OnlinePathSpec],
    ) -> tuple[dict[str, list[str]], str, tuple[str | None, str | None, str | None]]:
        """Resolve pre-split local paths into ``load_dataset`` arguments."""
        if not data_path:
            raise ValueError("Online split data_path mapping must not be empty")

        split_data_files: dict[str, list[str]] = {}
        loader_formats = set()
        for split_name, split_path in data_path.items():
            if split_name not in ONLINE_SPLIT_NAMES:
                raise ValueError(
                    f"Unsupported Online data_path split {split_name!r}; "
                    f"expected one of {ONLINE_SPLIT_NAMES!r}"
                )

            data_files, loader_format = self._collect_local_data_files(split_path)
            split_data_files[split_name] = data_files
            loader_formats.add(loader_format)

        if len(loader_formats) != 1:
            raise ValueError("Online train/valid/test paths must use one common file format")

        train_split = None
        if "train" in split_data_files:
            train_split = "train"

        valid_split = None
        if "valid" in split_data_files:
            valid_split = "valid"

        test_split = None
        if "test" in split_data_files:
            test_split = "test"

        selected_splits = (train_split, valid_split, test_split)
        loader_format = loader_formats.pop()
        return split_data_files, loader_format, selected_splits

    @staticmethod
    def _collect_local_data_files(data_path: OnlinePathSpec) -> tuple[list[str], str]:
        """Collect local files and select their ``load_dataset`` format."""
        if isinstance(data_path, str):
            configured_paths = [data_path]
        else:
            configured_paths = list(data_path)

        data_files = []
        for configured_path in configured_paths:
            if not os.path.isdir(configured_path):
                data_files.append(configured_path)
                continue

            for filename in sorted(os.listdir(configured_path)):
                file_extension = os.path.splitext(filename)[1].lower()
                if file_extension in ONLINE_FILE_FORMATS:
                    data_files.append(os.path.join(configured_path, filename))

        if not data_files:
            raise ValueError("Online files must use one format: JSON/JSONL/Parquet/CSV/Arrow")

        first_extension = os.path.splitext(data_files[0])[1].lower()
        loader_format = ONLINE_FILE_FORMATS.get(first_extension)
        for data_file in data_files[1:]:
            file_extension = os.path.splitext(data_file)[1].lower()
            if ONLINE_FILE_FORMATS.get(file_extension) != loader_format:
                raise ValueError("Online files must use one format: JSON/JSONL/Parquet/CSV/Arrow")

        if loader_format is None:
            raise ValueError("Online files must use one format: JSON/JSONL/Parquet/CSV/Arrow")

        return data_files, loader_format

    @staticmethod
    def _parse_split_percentages(split: Any) -> tuple[int, int, int]:
        """Parse and validate train, validation, and test percentages."""
        try:
            percentages = []
            for split_part in split.split(","):
                percentages.append(int(split_part.strip()))
        except (AttributeError, ValueError) as error:
            raise ValueError("Online split must contain three integers such as '98, 1, 1'") from error

        if len(percentages) != 3:
            raise ValueError("Online split must contain three integers such as '98, 1, 1'")

        if sum(percentages) != 100:
            raise ValueError("Online split percentages must sum to 100")
        if any(percentage < 0 for percentage in percentages):
            raise ValueError("Online split percentages must be non-negative")

        split_percentages = (percentages[0], percentages[1], percentages[2])
        return split_percentages

    @classmethod
    def _parse_split_ratios(cls, split: Any) -> tuple[str | None, str | None, str | None]:
        """Translate train/valid/test percentages into ``load_dataset`` split expressions."""
        train_percentage, valid_percentage, test_percentage = cls._parse_split_percentages(split)

        train_end = train_percentage
        valid_end = train_percentage + valid_percentage
        split_ranges = ((0, train_end), (train_end, valid_end), (valid_end, 100))

        split_expressions: list[str | None] = []
        for split_begin, split_end in split_ranges:
            if split_begin == split_end:
                split_expression = None
            elif split_begin == 0 and split_end == 100:
                split_expression = "train"
            elif split_begin == 0:
                split_expression = f"train[:{split_end}%]"
            elif split_end == 100:
                split_expression = f"train[{split_begin}%:]"
            else:
                split_expression = f"train[{split_begin}%:{split_end}%]"
            split_expressions.append(split_expression)

        online_split_expressions = (split_expressions[0], split_expressions[1], split_expressions[2])
        return online_split_expressions


__all__ = [
    "OnlineDataPath",
hyper_parallel/data/parallel/batch_parallel.py
255
256
257
258
259
260
261
262
263
        pad_length = (-sequence_length) % (2 * cp_size)
        if "cu_seq_lens" in cp_batch:
            boundaries = cp_batch["cu_seq_lens"].reshape(-1)
            if int(boundaries[0]) != 0 or int(boundaries[-1]) != sequence_length:
                raise ValueError("cu_seq_lens must cover the complete CP input sequence")

            if pad_length:
                boundaries = torch.cat((boundaries, boundaries[-1:] + pad_length))
            cp_batch["cu_seq_lens"] = boundaries
312
313
314
315
316
317
318
319
320
321

        Returns:
            Local batch populated on every TP rank.
        """
        if broadcast_all_fields:
            return self._broadcast_mapping(cp_local_batch)
        tp_rank = self.parallel_context.tp_rank
        # TP rank zero owns the CP-local DataLoader fields and moves them to
        # the target device before communication.
        if tp_rank == 0:
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
        return parallel_batch

    def _broadcast_mapping(self, cp_local_batch: Mapping[str, Any] | None) -> dict[str, Any]:
        """Broadcast variable-shaped Omni tensors and scalar metadata."""
        if self.parallel_context.tp_world_size == 1:
            return self._move_mapping_to_device(cp_local_batch)
        is_source = self.parallel_context.tp_rank == 0
        if is_source:
            parallel_batch = self._move_mapping_to_device(cp_local_batch)
            metadata = []
            for name, value in parallel_batch.items():
                if torch.is_tensor(value):
                    metadata.append((name, tuple(value.shape), value.dtype, None))
                else:
                    metadata.append((name, None, None, value))
        else:
            parallel_batch = {}
            metadata = None
        object_list = [metadata]
        dist.broadcast_object_list(
            object_list, group=self.parallel_context.tp_group, group_src=0, device=self.device
        )
        for name, shape, dtype, value in object_list[0]:
            if shape is None:
                parallel_batch[name] = value
                continue
            if not is_source:
                parallel_batch[name] = torch.empty(shape, dtype=dtype, device=self.device)
            dist.broadcast(parallel_batch[name], group=self.parallel_context.tp_group, group_src=0)
        return parallel_batch

    def _move_mapping_to_device(self, batch: Mapping[str, Any]) -> dict[str, Any]:
        """Transfer Omni tensors without changing their native dtypes."""
        return {
            field: value.to(self.device, non_blocking=True) if torch.is_tensor(value) else value
            for field, value in batch.items()
        }
hyper_parallel/data/text/build_dataset.py
22
23
24
25
26
27
28
29
30
from hyper_parallel.data.dataset_logging import get_dataset_logger
from hyper_parallel.data.indexed.indexed_dataset import (
    build_indexed_dataset as _build_indexed_dataset,
)
from hyper_parallel.data.online import (
    IterableTransformDataset,
    MappingTransformDataset,
    OnlineDataPath,
    build_online_iterable_source,
53
54
55
56
57
58
59
60
61
62
63
64
    )
    return dataloader_context


def _get_train_valid_test_num_samples(training_config: Any) -> tuple[int, int, int]:
    """Calculate train, validation, and test sizes from the training plan."""
    if training_config is None:
        raise ValueError("Dataset sample planning requires a training configuration")

    global_batch_size = training_config.global_batch_size
    if training_config.train_iters is not None:
        train_iters = training_config.train_iters
70
71
72
73
74
75
76
77
78
79
80
81
82
    train_samples = training_config.train_samples or train_iters * global_batch_size
    eval_iters = training_config.eval_iters
    valid_iters = (train_iters // eval_iters + 1) * eval_iters if eval_iters else 0
    split_sizes = (train_samples, valid_iters * global_batch_size, eval_iters * global_batch_size)
    logger.debug("Dataset target sizes: train=%d, validation=%d, test=%d", *split_sizes)
    return split_sizes


def build_online_text_mapping_dataset(
    *,
    data_config: Mapping[str, Any],
    data_path: OnlineDataPath | None = None,
    transform: Callable[[Any], Any] | None = None,
103
104
105
106
107
108
109
110
111
112
113
114
115

    dataset_config = dict(data_config)
    training_seed = getattr(training_config, "seed", None)
    dataset_config["random_seed"] = 42 if training_seed is None else int(training_seed)
    train_valid_test_num_samples = None
    if training_config is not None:
        train_valid_test_num_samples = _get_train_valid_test_num_samples(training_config)
    sample_filter = transform.is_valid_sample
    source_dataset = build_online_mapping_source(
        data_path=data_path,
        data_config=dataset_config,
        sample_filter=sample_filter,
        train_valid_test_num_samples=train_valid_test_num_samples,
113
114
115
116
117
118
119
120
121
122
123
124
125
        data_config=dataset_config,
        sample_filter=sample_filter,
        train_valid_test_num_samples=train_valid_test_num_samples,
    )
    text_dataset = MappingTransformDataset.apply(source_dataset, transform)
    return text_dataset


def build_online_iterable_dataset(
    *,
    data_config: Mapping[str, Any],
    data_path: OnlineDataPath | None = None,
    transform: Callable[[Any], Any] | None = None,
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

    Raises:
        ValueError: If no Online text transform is configured.
    """
    if transform is None:
        raise ValueError("Online Dataset requires a plaintext or conversation data_transform")

    dataset_config = dict(data_config)
    training_seed = getattr(training_config, "seed", None)
    dataset_config["random_seed"] = 42 if training_seed is None else int(training_seed)
    sample_filter = transform.is_valid_sample
    if dataloader_context is None:
        dataloader_context = _build_dataloader_context(mesh_context, dataset_config)

    source_dataset = build_online_iterable_source(
        data_path=data_path,
        data_config=dataset_config,
        dataloader_context=dataloader_context,
        sample_filter=sample_filter,
159
160
161
162
163
164
165
166
167
168
        data_config=dataset_config,
        dataloader_context=dataloader_context,
        sample_filter=sample_filter,
    )
    text_dataset = IterableTransformDataset.apply(source_dataset, transform)
    return text_dataset


def build_indexed_text_dataset(
        *,
200
201
202
203
204
205
206
207
    if data_path is None and not bool(dataset_config.get("mock_data", False)):
        raise ValueError("Indexed Dataset requires data_path")

    if train_valid_test_num_samples is None:
        train_valid_test_num_samples = _get_train_valid_test_num_samples(training_config)

    if dataloader_context is None:
        dataloader_context = _build_dataloader_context(mesh_context, dataset_config)
hyper_parallel/data/text/text_transform.py
21
22
23
24
25
26
27
28
29
30
31
32
from typing import Any, Literal

import torch

from hyper_parallel.data.constants import IGNORE_INDEX
from hyper_parallel.data.dataset_logging import get_dataset_logger

TextDataType = Literal["plaintext", "conversation"]
logger = get_dataset_logger(__name__)


def _get_record_value(sample: Mapping[str, Any], keys: str | Sequence[str]) -> Any:
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
            }
            transformed.append(model_sample)
        return transformed

    def is_valid_sample(self, sample: Mapping[str, Any]) -> bool:
        """Return whether one source record contains non-empty plaintext."""
        text = _get_record_value(sample, self.text_keys)
        if not isinstance(text, str):
            raise ValueError("Plaintext sample text must be a string")
        is_valid = text.strip() != ""
        return is_valid


@dataclass
class TextConversationTransform:
122
123
124
125
126
127
128
129
130
131
132
        messages = _get_record_value(sample, self.text_keys)
        encoded = self.chat_template.encode_messages(messages, max_seq_len=self.max_seq_len)
        input_ids = torch.as_tensor(encoded["input_ids"], dtype=torch.long)
        labels = torch.as_tensor(encoded["labels"], dtype=torch.long)
        shifted_labels = labels[1:]
        if not bool(shifted_labels.ne(IGNORE_INDEX).any()):
            return []

        model_sample = {
            "input_ids": input_ids[:-1],
            "labels": shifted_labels,
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
            "labels": shifted_labels,
        }
        return [model_sample]

    def is_valid_sample(self, sample: Mapping[str, Any]) -> bool:
        """Return whether one source record contains conversation messages."""
        messages = _get_record_value(sample, self.text_keys)
        is_valid = bool(messages)
        return is_valid


def build_text_transform(
    data_type: TextDataType,
    *,
    tokenizer: Any = None,
    chat_template: Any = None,
167
168
169
170
171
172
173
174
        data_transform = PlaintextTransform(tokenizer, max_seq_len, text_keys)
    elif data_type == "conversation":
        data_transform = TextConversationTransform(chat_template, max_seq_len, text_keys)
    else:
        raise ValueError(f"Unsupported text data type: {data_type!r}")

    logger.debug("Built text transform: data_type=%s, transform=%s", data_type, type(data_transform).__name__)
    return data_transform
hyper_parallel/data/tools/io.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136


def _is_object_storage_path(path: str) -> bool:
    """Return ``True`` if ``path`` is an ``s3://`` or ``msc://`` URI."""
    return path.startswith(S3_PREFIX) or path.startswith(MSC_PREFIX)


def _parse_s3_path(path: str) -> Tuple[str, str]:
    """Split an ``s3://bucket/key`` URI into ``(bucket, key)``."""
    if not path.startswith(S3_PREFIX):
        raise ValueError(f"Not an S3 path: {path}")
    parts = path[len(S3_PREFIX) :].split("/")
    bucket = parts[0]
    key = "/".join(parts[1:]) if len(parts) > 1 else ""
    return bucket, key
137
138
139
140
141
142
143
144
145
146
147


def _get_index_cache_path(idx_path: str, object_storage_config: ObjectStorageConfig) -> str:
    """Return the local cache path for ``idx_path`` under ``path_to_idx_cache``."""
    if idx_path.startswith(S3_PREFIX):
        stripped = idx_path[len(S3_PREFIX) :]
    elif idx_path.startswith(MSC_PREFIX):
        stripped = idx_path[len(MSC_PREFIX) :]
    else:
        raise ValueError(f"Not an object storage path: {idx_path}")
    return os.path.join(object_storage_config.path_to_idx_cache, stripped)
161
162
163
164
165
166
167
168
169
    """
    torch_dist_enabled = torch.distributed.is_initialized()
    rank = torch.distributed.get_rank() if torch_dist_enabled else 0

    if remote_path.startswith(S3_PREFIX):
        if not HAS_BOTO3:
            raise ImportError("boto3 is required to read s3:// datasets. Install via `pip install boto3`.")
        if not os.path.exists(local_path):
            if not torch_dist_enabled or rank == 0:
172
173
174
175
176
177
178
179
180
                client = boto3.client("s3")
                logger.info("Downloading %s -> %s", remote_path, local_path)
                client.download_file(bucket, key, local_path)
                client.close()
    elif remote_path.startswith(MSC_PREFIX):
        if not HAS_MSC:
            raise ImportError(
                "multi_storage_client is required to read msc:// datasets. "
                "Install via `pip install multi-storage-client`."
765
766
767
768
769
770
771
772
773
            # .idx is already cached locally; determine local path for _IndexReader
            local_idx_path = _get_index_cache_path(idx_path, object_storage_config)
            if not os.path.exists(local_idx_path):
                raise RuntimeError(f"Cached .idx not found: {local_idx_path}")
            access = "s3" if path_prefix.startswith(S3_PREFIX) else "msc"
            bin_reader: _BinReader = OBJECT_STORAGE_BIN_READERS[access](bin_path, object_storage_config)
            index_reader = _IndexReader(local_idx_path, multimodal)
        else:
            if not os.path.exists(idx_path) or not os.path.exists(bin_path):
hyper_parallel/data/tools/offline_preparation.py
19
20
21
22
23
24
25
26
27
import argparse
import glob
import gzip
import json
import logging
import math
import multiprocessing
import os
import time
47
48
49
50
51
52
53
54
55
from hyper_parallel.data.dataset_logging import get_dataset_logger

logger = get_dataset_logger(__name__)

logger = logging.getLogger(__name__)


class CustomLanguageVars(PunktLanguageVars):
    """Preserve newline runs when Punkt detects sentence boundaries."""
782
783
784
785
786
787
788
789
790

    performance = {}
    input_files = _resolve_input_files(args.dataset_name_or_path)
    for workers in worker_candidates:
        logger.info("Processing data with %s workers", workers)
        workers_per_partition = workers // args.partitions

        if args.split_sentences:
            nltk.download("punkt", quiet=True, download_dir=os.environ.get("NLTK_DATA"))
hyper_parallel/distributed/_builder/forward_rewriter.py
344
345
346
347
348
349
350
351
352
        return output.to_local()
    if isinstance(output, tuple):
        return tuple(_unwrap_dtensor_outputs(item) for item in output)
    if isinstance(output, list):
        return [_unwrap_dtensor_outputs(item) for item in output]
    return output


# ────────────────────────────────────────────────────────────────────────────
hyper_parallel/distributed/_builder/fsdp_adapter.py
229
230
231
232
233
234
235
236
237
238
239
240
241
                decoder discovery skips these roots and their descendants.
        """
        wrap_modules = []
        wrapped_module_ids = set()
        excluded_subtree_module_fqns = excluded_subtree_module_fqns or set()

        def is_inside_declared_subtree(module_fqn: str) -> bool:
            """Whether one module is inside an automatic-discovery exclusion."""
            return any(
                module_fqn == declared_fqn
                or module_fqn.startswith(f"{declared_fqn}.")
                for declared_fqn in excluded_subtree_module_fqns
            )
240
241
242
243
244
245
246
247
248
249
                for declared_fqn in excluded_subtree_module_fqns
            )

        for container_fqn, container in model.named_modules():
            if container_fqn and is_inside_declared_subtree(container_fqn):
                continue
            if id(container) in wrapped_module_ids:
                continue
            if not hasattr(container, "gradient_checkpointing"):
                continue
247
248
249
250
251
252
253
254
255
256
257
                continue
            if not hasattr(container, "gradient_checkpointing"):
                continue
            for child_name, child in container.named_children():
                child_fqn = f"{container_fqn}.{child_name}" if container_fqn else child_name
                if is_inside_declared_subtree(child_fqn):
                    continue
                blocks = list(child.children())
                if not blocks:
                    continue
                for block_index, block in enumerate(blocks):
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321

    @staticmethod
    def _get_model_adapter_spec(model: ModuleClass) -> Any | None:
        """Resolve a model adapter from the final model configuration."""
        config = getattr(model, "config", None)
        if config is None:
            return None
        identities = (
            getattr(config, "model_type", ""),
            *((getattr(config, "architectures", None) or [])[:1]),
        )
        for identity in identities:
            if identity:
                adapter_spec = get_model_adapter(identity)
                if adapter_spec is not None:
                    return adapter_spec
        return None

    @classmethod
    def _find_adapter_declared_wrap_modules(
        cls,
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
        Raises:
            ValueError: If a declaration has an invalid type, targets no
                module in the model tree, or aliases the same module twice.
        """
        adapter_spec = cls._get_model_adapter_spec(model)
        if adapter_spec is None or adapter_spec.fsdp_wrap_modules is None:
            return []

        declared_fqns = adapter_spec.fsdp_wrap_modules(model)
        if isinstance(declared_fqns, str) or not isinstance(declared_fqns, Sequence):
            raise ValueError(
                "ModelAdapterSpec.fsdp_wrap_modules must return a sequence of exact module FQNs"
            )
        module_by_fqn = dict(model.named_modules())
        declared_wrap_modules = []
        wrapped_module_ids = set()
        for module_fqn in declared_fqns:
            if not isinstance(module_fqn, str) or not module_fqn:
                raise ValueError(
                    "ModelAdapterSpec.fsdp_wrap_modules entries must be non-empty strings"
                )
            wrap_module = module_by_fqn.get(module_fqn)
            if wrap_module is None:
                raise ValueError(
                    "ModelAdapterSpec.fsdp_wrap_modules declared an unknown module: "
                    f"{module_fqn}"
                )
            if id(wrap_module) in wrapped_module_ids:
                raise ValueError(
                    "ModelAdapterSpec.fsdp_wrap_modules aliases one module more than once: "
                    f"{module_fqn}"
                )
            wrapped_module_ids.add(id(wrap_module))
            declared_wrap_modules.append(_WrapModuleInfo(module_fqn, wrap_module))
        return declared_wrap_modules

    @classmethod
    def _find_adapter_excluded_subtrees(cls, model: ModuleClass) -> set[str]:
        """Resolve model branches excluded from automatic decoder discovery."""
        adapter_spec = cls._get_model_adapter_spec(model)
        excluded_subtrees_provider = getattr(adapter_spec, "fsdp_excluded_subtrees", None)
        if excluded_subtrees_provider is None:
            return set()
        if not callable(excluded_subtrees_provider):
            raise ValueError("ModelAdapterSpec.fsdp_excluded_subtrees must be callable")
        excluded_fqns = excluded_subtrees_provider(model)  # pylint: disable=not-callable
        if isinstance(excluded_fqns, str) or not isinstance(excluded_fqns, Sequence):
            raise ValueError(
                "ModelAdapterSpec.fsdp_excluded_subtrees must return a sequence of exact module FQNs"
            )
        module_by_fqn = dict(model.named_modules())
        for module_fqn in excluded_fqns:
            if not isinstance(module_fqn, str) or not module_fqn:
                raise ValueError(
                    "ModelAdapterSpec.fsdp_excluded_subtrees entries must be non-empty strings"
                )
            if module_fqn not in module_by_fqn:
                raise ValueError(
                    "ModelAdapterSpec.fsdp_excluded_subtrees declared an unknown module: "
                    f"{module_fqn}"
                )
        return set(excluded_fqns)

    def _find_wrap_modules(
        self,
        model: ModuleClass,
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
        model: ModuleClass,
        metadata_by_parameter: SourceShardInfoByParam | None = None,
    ) -> list[_WrapModuleInfo]:
        """Find automatic decoder/expert units and adapter-declared units."""
        declared_wrap_modules = self._find_adapter_declared_wrap_modules(model)
        if declared_wrap_modules:
            logger.info(
                "Adapter-declared FSDP child units: %s",
                [wrap_module.fqn for wrap_module in declared_wrap_modules],
            )
        excluded_subtree_module_fqns = {
            wrap_module.fqn for wrap_module in declared_wrap_modules
        }
        excluded_subtree_module_fqns.update(
            self._find_adapter_excluded_subtrees(model)
        )
        wrap_modules, wrapped_module_ids = self._find_transformer_block_modules(
            model,
            excluded_subtree_module_fqns=excluded_subtree_module_fqns,
        )
        wrapped_module_ids.update(
            id(wrap_module.module) for wrap_module in declared_wrap_modules
        )
        wrap_modules.extend(declared_wrap_modules)
        wrap_modules.extend(
            self._find_expert_wrap_modules(
                model,
                metadata_by_parameter,
447
448
449
450
451
452
453
454
455
            aliases_by_parameter[parameter].append(parameter_fqn)

        owner_by_parameter = {}
        for parameter, parameter_fqns in aliases_by_parameter.items():
            common_wrap_modules = [
                wrap_module
                for wrap_module in wrap_modules
                if all(
                    parameter_fqn.startswith(f"{wrap_module.fqn}.")
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
        module_order = {
            id(module): module_index
            for module_index, (_, module) in enumerate(model.named_modules())
        }
        default_order = sorted(
            wrap_modules,
            key=lambda wrap_module: module_order.get(id(wrap_module.module), len(module_order)),
        )
        adapter_spec = self._get_model_adapter_spec(model)
        execution_order_provider = getattr(adapter_spec, "fsdp_execution_order", None)
        if execution_order_provider is None:
            return default_order
        if not callable(execution_order_provider):
            raise ValueError("ModelAdapterSpec.fsdp_execution_order must be callable")

        wrap_module_by_fqn = {
            wrap_module.fqn: wrap_module for wrap_module in wrap_modules
        }
        execution_fqns = execution_order_provider(  # pylint: disable=not-callable
            model,
            tuple(wrap_module.fqn for wrap_module in default_order),
        )
        if isinstance(execution_fqns, str) or not isinstance(execution_fqns, Sequence):
            raise ValueError(
                "ModelAdapterSpec.fsdp_execution_order must return a sequence of module FQNs"
            )
        if any(not isinstance(module_fqn, str) for module_fqn in execution_fqns):
            raise ValueError(
                "ModelAdapterSpec.fsdp_execution_order entries must be module FQN strings"
            )
        if (
            len(execution_fqns) != len(wrap_module_by_fqn)
            or len(set(execution_fqns)) != len(execution_fqns)
            or set(execution_fqns) != set(wrap_module_by_fqn)
        ):
            raise ValueError(
                "ModelAdapterSpec.fsdp_execution_order must return every selected FSDP "
                "child-unit FQN exactly once"
            )
        logger.info("Adapter-declared FSDP execution order: %s", list(execution_fqns))
        return [wrap_module_by_fqn[module_fqn] for module_fqn in execution_fqns]

    def _parallelize_child_units(
        self,
        wrap_modules: list[_WrapModuleInfo],
605
606
607
608
609
610
611
612
613
        wrapping_order = sorted(
            wrap_modules, key=lambda wrap_module: wrap_module.fqn.count("."), reverse=True
        )
        for wrap_module in wrapping_order:
            managed_source_info = _build_managed_source_shard_info(
                self,
                wrap_module.module,
                metadata_by_parameter,
            )
638
639
640
641
642
643
644
645
646
        replicate_params,
        dense_root_kwargs: dict[str, Any],
    ) -> int:
        """Apply root FSDP and report whether gradient scaling was configured."""
        root_source_info = _build_managed_source_shard_info(
            self,
            model,
            metadata_by_parameter,
        )
695
696
697
698
699
700
701
702
703
        Raises:
            ValueError: If TP is enabled without FQN metadata or configured
                parameter FQNs cannot be resolved.
        """
        metadata_by_parameter = _build_source_shard_info_by_param(
            self,
            model,
            source_shard_info,
        )
hyper_parallel/distributed/_builder/parameter_sharding.py
232
233
234
235
236
237
238
239
240
        )
    else:
        expert_mesh = None
    if ep_size > 0 and expert_mesh is None:
        raise ValueError("Virtual-EP plan requires MeshContext.fsdp_moe_mesh")
    if expert_mesh is not None:
        logger.info(
            "virtual-EP mesh: using %s for parameter sharding, explicit compute injection, "
            "and FSDP source metadata",
hyper_parallel/distributed/_builder/planner.py
840
841
842
843
844
845
846
847
848
849
850
851
852
853
                for name, named in (spec.params or {}).items()
                if isinstance((named or {}).get(EP), Shard)
            ]
            if ep_params:
                spec._ep_size = ep_size  # pylint: disable=protected-access
                marked.append((fqn, ep_params))
        if not marked:
            return
        cls._validate_ep_domain(ep_size, mesh)
        logger.info(
            "virtual EP: mapped explicitly EP-sharded parameters to the "
            "derived expert mesh: %s",
            marked,
        )
hyper_parallel/distributed/_builder/rule_resolver.py
168
169
170
171
172
173
174
175
176
    preview = model_hits[:8]
    omitted = len(model_hits) - len(preview)
    matched = f"{preview}"
    if omitted:
        matched += f" (and {omitted} more)"
    example_fqn = model_hits[0]
    raise ValueError(
        f"[HP-PLAN-002] {source}[{key!r}] matched {len(model_hits)} module(s) "
        "in the final model that have no planner-derived "
hyper_parallel/distributed/_builder/source_shard.py
357
358
359
360
361
362
363
364
365
366
367
    if (
        manager.mesh_context.fsdp_non_moe_mesh is not None
        or manager.mesh_context.device_mesh is not None
    ):
        default_source_shard_info = _get_default_source_shard_info(manager)
    managed_source_shard_info = {}
    for parameter in get_managed_modules_parameters((owner,)):
        source_shard_info = metadata_by_parameter.get(parameter)
        if source_shard_info is None:
            if default_source_shard_info is None:
                raise ValueError(
hyper_parallel/distributed/activation_checkpoint.py
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799


def _adapter_recompute_spec(model: nn.Module):
    """Resolve a model-owned recompute declaration without family branches."""
    config = getattr(model, "config", None)
    identities = [getattr(config, "model_type", None)]
    identities.extend(getattr(config, "architectures", None) or ())
    adapter_spec = None
    for identity in identities:
        if not identity:
            continue
        adapter_spec = get_model_adapter(identity)
        if adapter_spec is not None:
            break
    provider = getattr(adapter_spec, "recompute", None)
    if provider is None or not callable(provider):
        raise ValueError(
            "activation_checkpoint.selection.source=model_adapter_safe_regions "
            "requires ModelAdapterSpec.recompute"
        )
    recompute_policy = provider()  # pylint: disable=not-callable
    if not isinstance(recompute_policy, RecomputePolicy):
        raise TypeError("ModelAdapterSpec.recompute must return RecomputePolicy")
    return recompute_policy


def _validate_selected_layer_indices(
    layer_indices: Any,
800
801
802
803
804
805
806
807
808
    blocks: list,
) -> None:
    """Validate an explicit list of layer indices against the blocks."""
    if not isinstance(layer_indices, (list, tuple)) or not layer_indices:
        raise ValueError(
            "model-adapter safe recompute layer_indices must be a non-empty sequence"
        )
    if any(
        isinstance(index, bool) or not isinstance(index, int) or index < 0
807
808
809
810
811
812
813
814
815
816
817
818
819
820
    if any(
        isinstance(index, bool) or not isinstance(index, int) or index < 0
        for index in layer_indices
    ):
        raise ValueError(
            "model-adapter safe recompute layer_indices must contain only "
            "non-negative integers"
        )
    if len(set(layer_indices)) != len(layer_indices):
        raise ValueError(
            "model-adapter safe recompute layer_indices must not contain duplicates"
        )
    if max(layer_indices) >= len(blocks):
        raise ValueError(
828
829
830
831
832
833
834
835
836
837
838
839
    blocks: list,
) -> None:
    """Validate a leading layer count against the blocks."""
    if isinstance(layer_count, bool) or not isinstance(layer_count, int) or layer_count < 0:
        raise ValueError(
            "model-adapter safe recompute layer_count must be a non-negative integer"
        )
    if layer_count > len(blocks):
        raise ValueError(
            f"adapter recompute layer_count {layer_count} exceeds discovered layers {len(blocks)}"
        )

846
847
848
849
850
851
852
853
854
    blocks = [block for container in containers for block in container.blocks]
    layer_count = getattr(selection, "layer_count", None)
    layer_indices = getattr(selection, "layer_indices", None)
    if (layer_count is None) == (layer_indices is None):
        raise ValueError(
            "model-adapter safe recompute requires exactly one of "
            "selection.layer_count or selection.layer_indices"
        )
    if layer_indices is not None:
856
857
858
859
860
861
862
863
864
        return {blocks[index].fqn for index in layer_indices}

    _validate_selected_layer_count(layer_count, blocks)
    if layer_count == 0:
        return set()
    return {blocks[index].fqn for index in range(layer_count)}


def _select_safe_checkpoint_targets(
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
    selected_layer_fqns: set[str],
    recompute_spec: Any,
) -> tuple[dict, list]:
    """Collect adapter-safe modules inside the selected layers."""
    module_by_fqn = dict(model.named_modules())
    selected_targets = []
    for module_fqn, module in module_by_fqn.items():
        if not any(
            module_fqn.startswith(f"{layer_fqn}.")
            for layer_fqn in selected_layer_fqns
        ):
            continue
        if not any(
            fnmatch.fnmatchcase(module_fqn, pattern)
            for pattern in recompute_spec.safe_module_patterns
        ):
            continue
        if any(
            fnmatch.fnmatchcase(module_fqn, pattern)
            for pattern in recompute_spec.no_replay_module_patterns
        ):
            raise ValueError(
                f"[HP-STATE-002] safe recompute target {module_fqn!r} is also a no-replay producer"
            )
        selected_targets.append((module_fqn, module))
    if not selected_targets:
        raise ValueError(
            "model adapter safe recompute patterns matched no module in the selected layers"
        )
    return module_by_fqn, selected_targets


def _check_no_nested_checkpoint_targets(selected_targets: list) -> None:
    """Reject overlapping safe-region selections."""
    selected_ids = {id(module) for _, module in selected_targets}
    for module_fqn, module in selected_targets:
        if any(
            child is not module and id(child) in selected_ids
            for child in module.modules()
        ):
            raise ValueError(
                "model adapter safe recompute patterns selected nested targets: "
                f"{module_fqn}"
            )
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
    selected_targets: list,
    checkpoint_kwargs: dict,
) -> int:
    """Install checkpoint wrappers deepest-first; return the wrapped count."""
    wrapped_count = 0
    for module_fqn, module in sorted(
        selected_targets,
        key=lambda item: item[0].count("."),
        reverse=True,
    ):
        if "." in module_fqn:
            parent_fqn, child_name = module_fqn.rsplit(".", 1)
            parent = module_by_fqn[parent_fqn]
        else:
            parent, child_name = model, module_fqn
        if _is_checkpoint_wrapped(module):
            continue
        setattr(parent, child_name, checkpoint_wrapper(module, **checkpoint_kwargs))
        wrapped_count += 1
    return wrapped_count


def _apply_model_adapter_safe_checkpointing(
    model: nn.Module,
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
    enable_compile: bool,
    swap_inputs: bool,
) -> nn.Module:
    """Wrap only model-adapter-declared safe regions in the selected layers."""
    recompute_spec = _adapter_recompute_spec(model)
    selected_layer_fqns = _selected_checkpoint_layer_fqns(containers, selection)
    if not selected_layer_fqns:
        logger.info("Model-adapter safe recompute selected zero layers")
        return model
    if hasattr(model, "gradient_checkpointing_disable"):
        model.gradient_checkpointing_disable()
    checkpoint_kwargs = {"swap_inputs": swap_inputs} if not enable_compile else {}
    module_by_fqn, selected_targets = _select_safe_checkpoint_targets(
        model, selected_layer_fqns, recompute_spec
    )
    _check_no_nested_checkpoint_targets(selected_targets)
    wrapped_count = _wrap_safe_checkpoint_targets(
        model, module_by_fqn, selected_targets, checkpoint_kwargs
    )
    if swap_inputs and not enable_compile:
        _register_forward_prefetch_layers(containers)
    logger.info(
        "Model-adapter safe recompute wrapped %d region(s) across %d/%d layers",
        wrapped_count,
        len(selected_layer_fqns),
        len(ac_layers),
966
967
968
969
970
971
972
973
974
        wrapped_count,
        len(selected_layer_fqns),
        len(ac_layers),
    )
    return model


def _resolve_activation_checkpoint_containers(
    model: nn.Module,
1139
1140
1141
1142
1143
1144
1145
1146
1147
    has_kv_sharing = _detect_kv_sharing_and_maybe_disable_cache(model)

    selection_source = getattr(selection, "source", "default")
    if selection_source == "model_adapter_safe_regions":
        return _apply_model_adapter_safe_checkpointing(
            model,
            containers,
            ac_layers,
            selection,
hyper_parallel/distributed/expert_parallel/collectives.py
135
136
137
138
139
140
141
142
143
        ctx.recv_counts = recv_counts
        ctx.group = group
        out = x.new_empty((sum(recv_counts),) + tuple(x.shape[1:]))
        if _A2A_SINGLE:
            dist.all_to_all_single(
                out,
                x.contiguous(),
                output_split_sizes=[int(count) for count in recv_counts],
                input_split_sizes=[int(count) for count in send_counts],
268
269
270
271
272
273
274
275
276
    Wrapped rather than called inline so the exchange can be driven with a fake
    stream in unit tests: ``Tensor.record_stream`` rejects anything that is not a
    real device stream.
    """
    tensor.record_stream(stream)


def _issue_split_free_a2a(out: torch.Tensor, x: torch.Tensor, group: Any) -> Any:
    """Issue the split-free exchange on the shared comm stream.
357
358
359
360
361
362
363
364
365
        return _PendingEqualA2A(self._tensor.squeeze(dim), self._event, self.completed)

    def __repr__(self) -> str:
        """Name the pending exchange and what is known about it without reading it."""
        return (f"PendingEqualA2A(shape={tuple(self._tensor.shape)}, "
                f"completed={self.completed})")


def wait_ep_all_to_all(value: Any) -> Any:
623
624
625
626
627
628
629
630
631
    Returns:
        The exchanged rows, materialized lazily on the async path.
    """
    if not _backend_supports_uneven_a2a(group):
        return _EPAllToAllPadded.apply(x, send_counts, recv_counts, group)
    rows_per_peer = _equal_a2a_rows(x, send_counts, recv_counts, group)
    if rows_per_peer is not None:
        ep_size = len(send_counts)
        if _equal_a2a_mode() == _EQUAL_A2A_LAZY and allow_pending:
hyper_parallel/distributed/expert_parallel/experts.py
181
182
183
184
185
186
187
188
189
    Returns:
        The int64 permutation that sorts ``keys``.
    """
    if _SORT_FP32_ENABLED and 0 < bound <= _SORT_KEY_FP32_LIMIT:
        return keys.to(torch.float32).argsort()
    return keys.argsort()


def _expert_token_counts(indices: torch.Tensor, num_experts: int) -> torch.Tensor:
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    sorted_states = dispatched_states[token_order]
    local_expert_counts = _expert_token_counts(
        local_expert_indices, experts.local_expert_count)
    if getattr(experts, "_ep_use_grouped_gemm", False):
        grouped_forward = getattr(experts, "forward_expert_major", None)
        if callable(grouped_forward):
            sorted_output = grouped_forward(sorted_states, local_expert_counts)
        else:
            gate_weight, up_weight, down_weight = resolve_swiglu_weights(experts)
            if up_weight is not None:
                raise ValueError(
                    "EP grouped GEMM currently requires packed gate_up_proj weights"
                )
            sorted_output = npu_grouped_swiglu(
                sorted_states,
                gate_weight,
                down_weight,
                local_expert_counts,
236
237
238
239
240
241
242
243
244
245
246
                gate_weight,
                down_weight,
                local_expert_counts,
            )
        output = torch.empty_like(sorted_output)
        output[token_order] = sorted_output
        return output

    gate_weight, up_weight, down_weight = resolve_swiglu_weights(experts)
    # The eager path needs host ints: read them in one transfer rather than
    # draining the queue once per local expert.
260
261
262
263
264
265
266
267
268
                gate_states, up_states = gate_up_states.chunk(2, dim=-1)
                activation = getattr(experts, "_ep_act_fn", F.silu)
                activated_states = activation(gate_states) * up_states
            else:
                activated_states = apply_gate(gate_up_states)
        else:
            gate_states = F.linear(  # pylint: disable=not-callable
                expert_states, gate_weight[local_expert_index]
            )
335
336
337
338
339
340
341
342
343
                f"{type(module).__name__}: unsupported expert activation {hidden_act!r}; "
                "provide experts.act_fn or extend the EP activation registry"
            )
    if apply_gate is not None and use_grouped_gemm:
        raise ValueError("custom expert gate activation is not supported by grouped GEMM")
    # ``_ep_*`` is the contract the local compute path reads back off the module's
    # experts child; setting them here is the point of the binder.
    # pylint: disable=protected-access
    module.experts._ep_act_fn = activation
476
477
478
479
480
481
482
483
484
485
486
487
488
489
        # Bisect probes (see ``probe_mode``): run one half of the original work so the
        # 4/4 OOM can be attributed.  Both branches are no-ops in a normal run.
        mode = probe_mode()
        if mode == "a2a":
            dist.all_to_all_single(
                torch.empty_like(send_counts_tensor), send_counts_tensor, group=ep_group,
            )
        elif mode == "drains":
            send_counts_tensor.tolist()
            receive_counts_tensor.tolist()
    else:
        send_counts_tensor = _expert_token_counts(destination_ranks, ep_size)
        receive_counts_tensor = torch.empty_like(send_counts_tensor)
        dist.all_to_all_single(receive_counts_tensor, send_counts_tensor, group=ep_group)
532
533
534
535
536
537
538
539
540
            f"HP_EP_DISPATCH_CHUNKS must be >= 1, but got {chunk_count}"
        )
    if chunk_count > ep_size:
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug(
                "HP_EP_DISPATCH_CHUNKS=%d exceeds the EP group size %d; clamping to %d",
                chunk_count, ep_size, ep_size,
            )
        chunk_count = ep_size
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169

    Split out of :func:`ep_routed_dispatch` to keep that entry point readable; the reporting
    cadence is part of the contract (see the comment inside).
    """
    keep, dropped = apply_capacity_limit(
        topk_indices, capacity_factor, global_expert_count, topk_weights)
    # Draining the drop count is a host sync, so pay it on the first dispatch and then
    # sparsely -- never once per layer, which is what made the first sweep look +90%.
    global _CAPACITY_SYNC_COUNT  # pylint: disable=global-statement
    _CAPACITY_SYNC_COUNT += 1
    if _CAPACITY_SYNC_COUNT == 1 or _CAPACITY_SYNC_COUNT % 200 == 0:
        total_slots = topk_indices.numel()
        dropped_slots = int(dropped.item())
        logger.info(
            "EP capacity limit: factor=%s order=%s dropped %.2f%% of routed slots "
            "(%d experts, call %d)",
            capacity_factor, _resolve_capacity_order(),
            100.0 * dropped_slots / max(total_slots, 1),
1168
1169
1170
1171
1172
1173
1174
1175
1176
            capacity_factor, _resolve_capacity_order(),
            100.0 * dropped_slots / max(total_slots, 1),
            global_expert_count, _CAPACITY_SYNC_COUNT,
        )
    return keep


def _issue_chunked_dispatch(
    dispatch: "_EPDispatch",
1264
1265
1266
1267
1268
1269
1270
1271
1272
    topk_indices, topk_weights = router_fn(module, hidden_states)
    capacity_factor = _resolve_capacity_factor(module)
    keep = None
    if capacity_factor is not None:
        keep = _capacity_limited_slots(
            topk_indices, topk_weights, capacity_factor, global_expert_count)
    dispatch = _prepare_ep_dispatch(
        hidden_states,
        topk_indices,
1289
1290
1291
1292
1293
1294
1295
1296
1297
            dispatch, ep_group, ep_size, dist.get_rank(group=ep_group), chunk_count,
            batch_size, sequence_length, hidden_size,
        )
    if _FUSED_DISPATCH_ENABLED:
        received_states, received_indices = _fused_dispatch_exchange(
            dispatched_states,
            dispatched_expert_indices,
            send_counts,
            receive_counts,
hyper_parallel/distributed/expert_parallel/recipes.py
183
184
185
186
187
188
189
190
191

    if capacity_factor is not None:
        # Read back by ``experts._resolve_capacity_factor``; bounds each expert's routed slots
        # so the busiest rank's buffers cannot blow up under a skewed real router.
        module.experts.capacity_factor = float(capacity_factor)

    if overlap_fn is None:
        def compute_fn(module: Any, hidden_states: torch.Tensor) -> torch.Tensor:
            """Run the routed branch and compose the MoE block output."""
192
193
194
195
196
197
198
199
200
201
202
203
204
205
            routed = ep_routed_forward(
                module, hidden_states, router_fn=router_fn, ep_group=ep_group)
            return combine(module, hidden_states, routed)
    else:
        def compute_fn(module: Any, hidden_states: torch.Tensor) -> torch.Tensor:
            """Dispatch, run ``overlap_fn`` against the in-flight exchange, then finish."""
            state = ep_routed_dispatch(
                module, hidden_states, router_fn=router_fn, ep_group=ep_group)
            overlapped = overlap_fn(module, hidden_states)
            routed = ep_routed_experts_and_combine(module, state, ep_group)
            return combine(module, hidden_states, routed, overlapped)

    return compute_fn

358
359
360
361
362
363
364
365
366
367
368
369
370
371
        routed: torch.Tensor,
        overlapped: Optional[torch.Tensor] = None,
    ) -> torch.Tensor:
        """Merge the routed branch with the shared-experts branch."""
        shared = module.shared_experts(hidden_states) if overlapped is None else overlapped
        return routed + shared    # nested boundary

    def overlap_shared(module: Any, hidden_states: torch.Tensor) -> torch.Tensor:
        """Shared-experts branch, evaluated while the dispatch exchange is in flight."""
        return module.shared_experts(hidden_states)    # nested boundary

    return build_ep_compute(
        module,
        ep_mesh,
hyper_parallel/distributed/expert_parallel/routing.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
            f"{flat_expert.numel()} slots"
        )
    if not 0 < num_experts <= _SORT_KEY_FP32_LIMIT:
        global _FP32_KEY_WARNED  # pylint: disable=global-statement
        if not _FP32_KEY_WARNED:
            _FP32_KEY_WARNED = True
            logger.warning(
                "HP_EP_CAPACITY_ORDER=score needs the expert index to be exact in a float32 "
                "key, but num_experts=%d is outside (0, 2**24]; keeping the expert-major order",
                num_experts,
            )
        return _expert_major_order(flat_expert, num_experts)
    weight = flat_weights.reshape(-1).to(torch.float32)
    inverted = weight.max() - weight
    # ``clamp_min`` rather than a host-side ``if span == 0``: reading the span would drain the
    # device, and this runs once per MoE layer.  A zero span leaves every ``inverted`` at zero,
348
349
350
351
352
353
354
355
356
        An int64 permutation of ``range(flat_expert.numel())`` ordering slots by expert.
    """
    if 0 < num_experts <= _SORT_KEY_FP32_LIMIT:
        return flat_expert.to(torch.float32).argsort()
    return torch.argsort(flat_expert, stable=True)


def _global_expert_count(module):
    """Model-level routed expert count (mirrors ``experts._get_global_expert_count``).
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

    Kept local so this module stays a torch-only leaf (it is imported by
    model adapters that do not want the expert compute machinery).
    """
    for owner in (getattr(module, "experts", None), module):
        count = getattr(owner, "num_experts", None)
        if count is not None:
            return int(count)
    cfg = getattr(module, "config", None)
    for name in ("num_experts", "n_routed_experts"):
        count = getattr(cfg, name, None)
        if count is not None:
            return int(count)
    raise ValueError(
        f"{type(module).__name__}: cannot determine the global routed expert count"
    )

409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
    False``), where the real ``_sigmoid_group_router`` runs unchanged. The
    true ``topk_w`` are kept so the gate still receives gradient and the
    router GEMM stays in the step's compute profile.
    """
    topk_idx, topk_w = _sigmoid_group_router(module, hidden_states)
    token_count, experts_per_token = topk_idx.shape
    expert_count = _global_expert_count(module)
    # Set by the EP binder (bind_local_expert_forward) before any forward; a
    # missing value degrades to a plain per-expert round robin.
    local_count = getattr(
        getattr(module, "experts", None), "local_expert_count", None) or 1
    if expert_count % local_count != 0:
        raise ValueError(
            f"num_experts ({expert_count}) must be divisible by the local "
            f"expert count ({local_count})"
        )
    # Spread the slots over DESTINATION RANKS first (slot % destinations), then
424
425
426
427
428
429
430
431
432
433
434
435
436
437
        )
    # Spread the slots over DESTINATION RANKS first (slot % destinations), then
    # over that rank's local experts: every rank receives exactly one out of
    # every `destinations` slots.
    destinations = expert_count // local_count
    slots = torch.arange(
        token_count * experts_per_token, device=topk_idx.device)
    balanced_idx = ((slots % destinations) * local_count
                    + (slots // destinations) % local_count)
    return balanced_idx.view(
        token_count, experts_per_token).to(topk_idx.dtype), topk_w


MOE_ROUTER_ADAPTERS = {
hyper_parallel/tools/model_integration/__main__.py
13
14
15
16
17
18
19
20
# limitations under the License.
# ============================================================================
"""Module entry point for ``python -m hyper_parallel.tools.model_integration``."""

from hyper_parallel.tools.model_integration.cli import main

if __name__ == "__main__":
    raise SystemExit(main())
hyper_parallel/tools/model_integration/case_compare.py
52
53
54
55
56
57
58
59
60
            f"{combination!r}"
        )
    resolved_checks = tuple(checks)
    if not resolved_checks:
        raise ValueError(f"{owner} tolerance must declare at least one numerical bound")
    return all(resolved_checks) if combination == "all" else any(resolved_checks)


def _scalar_metric_delta(
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
        allow_candidate_subset: bool,
) -> tuple[list[Mapping[str, Any]], Optional[dict[str, Any]]]:
    """Align candidate rows with baseline steps or explain why not."""
    if allow_candidate_subset:
        baseline_by_step = {row.get("step"): row for row in baseline}
        missing_steps = [row.get("step") for row in candidate if row.get("step") not in baseline_by_step]
        if missing_steps:
            return baseline, {
                "status": "FAIL",
                "reason": f"resume steps are absent from uninterrupted baseline: {missing_steps}",
            }
        return [baseline_by_step[row.get("step")] for row in candidate], None
    if len(baseline) != len(candidate):
        return baseline, {
            "status": "FAIL",
            "reason": f"step count differs: baseline={len(baseline)}, candidate={len(candidate)}",
        }
    return baseline, None
139
140
141
142
143
144
145
146
147
    """Compare optional post-clip norms only when both sides recorded them."""
    baseline_post_norm = baseline_row.get("grad_norm_post_clip")
    candidate_post_norm = candidate_row.get("grad_norm_post_clip")
    if baseline_post_norm is None or candidate_post_norm is None:
        return None, None, baseline_post_norm is None and candidate_post_norm is None
    comparison = _scalar_metric_delta(
        float(baseline_post_norm),
        float(candidate_post_norm),
        tolerance,
186
187
188
189
190
191
192
193
194
        and learning_rate_equal
        and step_equal
    )
    if not finite:
        row_status = "FAIL"
    elif not input_equal:
        # Different logical inputs make a numerical A/B verdict invalid;
        # the topology is not proven wrong, but this experiment is blocked.
        row_status = "BLOCKED"
232
233
234
235
236
237
238
239
240
        candidate,
        allow_candidate_subset,
    )
    if alignment_error is not None:
        return alignment_error
    comparisons = []
    status = "PASS"
    for baseline_row, candidate_row in zip(baseline, candidate):
        comparison = _compare_scalar_row(baseline_row, candidate_row, tolerance)
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def load_jsonl(path: str | Path) -> list[dict[str, Any]]:
    """Load a structured JSON-lines evidence stream."""
    input_path = Path(path)
    if not input_path.is_file():
        return []
    return [json.loads(line) for line in input_path.read_text(encoding="utf-8").splitlines() if line]


def _nested_value(value: Mapping[str, Any], path: str) -> Any:
    current: Any = value
    for name in path.split("."):
        if not isinstance(current, Mapping) or name not in current:
            return None
        current = current[name]
    return current


def compare_preflight_configs(
        baseline_dir: str | Path,
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
        baseline_dir: str | Path,
        candidate_dir: str | Path,
) -> dict[str, Any]:
    """Prove that topology cases retain one training/precision contract."""
    baseline_path = Path(baseline_dir) / "trainer_config.resolved.json"
    candidate_path = Path(candidate_dir) / "trainer_config.resolved.json"
    if not baseline_path.is_file() or not candidate_path.is_file():
        return {"status": "BLOCKED", "reason": "resolved Trainer config evidence is missing"}
    baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
    candidate = json.loads(candidate_path.read_text(encoding="utf-8"))
    invariant_paths = (
        "model_init_dtype",
        "training.global_batch_size",
        "training.max_grad_norm",
        "training.loss_aggregation",
286
287
288
289
290
291
292
293
294
        "fsdp_config.mix_precision.output_dtype",
        "fsdp_config.mix_precision.cast_forward_inputs",
        "optimizer.fp32_main_params",
    )
    mismatches = [
        {
            "path": path,
            "baseline": _nested_value(baseline, path),
            "candidate": _nested_value(candidate, path),
295
296
297
298
299
300
301
302
        }
        for path in invariant_paths
        if _nested_value(baseline, path) != _nested_value(candidate, path)
    ]
    return {
        "status": "FAIL" if mismatches else "PASS",
        "mismatches": mismatches,
    }
304
305
306
307
308
309
310
311
312

def _probe_rows(case_dir: Path) -> list[dict[str, Any]]:
    candidates = tuple(case_dir.glob("parameter_probes/rank*.jsonl"))
    if not candidates:
        candidates = tuple(case_dir.glob("**/parameter_probes/rank*.jsonl"))
    return [row for path in sorted(candidates) for row in load_jsonl(path)]


def _numeric_summary_delta(
356
357
358
359
360
361
362
363
364
            for name in ("sum", "min", "max")
        }
        summary_tolerance = tolerance.get("summary", {})
        if not isinstance(summary_tolerance, Mapping):
            raise ValueError("parameter summary tolerance must be a mapping")
        summary_relative_limit = _optional_tolerance(
            summary_tolerance,
            "l2_norm_relative",
        )
363
364
365
366
367
368
369
370
371
372
            "l2_norm_relative",
        )
        summary_policy_source = "summary.l2_norm_relative"
        if summary_relative_limit is None:
            summary_relative_limit = relative_l2_limit
            summary_policy_source = "parameters.relative_l2 (compatibility fallback)"
        summary_abs_limit = _optional_tolerance(
            summary_tolerance,
            "l2_norm_max_abs",
        )
371
372
373
374
375
376
377
378
379
            "l2_norm_max_abs",
        )
        summary_checks = []
        if summary_abs_limit is not None:
            summary_checks.append(l2_norm_max_abs <= summary_abs_limit)
        if summary_relative_limit is not None:
            summary_checks.append(l2_norm_relative_error <= summary_relative_limit)
        summary_combination = summary_tolerance.get("combination", "all")
        within_tolerance = _combine_tolerance_checks(
432
433
434
435
436
437
438
439
440
441
442
        allow_candidate_subset: bool,
) -> set[tuple[Any, ...]]:
    """Diff the canonical key spaces of both evidence directories."""
    if allow_candidate_subset:
        candidate_steps = {key[0] for key in candidate}
        expected_baseline = {key for key in baseline if key[0] in candidate_steps}
        return expected_baseline ^ set(candidate)
    return set(baseline) ^ set(candidate)


def _compare_optimizer_state_probes(
450
451
452
453
454
455
456
457
458
459
460
461
    candidate_states = candidate_row.get("optimizer_state", {})
    comparisons = []
    for state_name in sorted(set(baseline_states) | set(candidate_states)):
        if state_name not in baseline_states or state_name not in candidate_states:
            comparisons.append(
                {"key": (*key, state_name), "status": "FAIL", "reason": "state missing"}
            )
            continue
        comparison = _numeric_summary_delta(
            baseline_states[state_name],
            candidate_states[state_name],
            tolerance,
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
    """Compare one parameter lifecycle row including hash and replica gates."""
    comparison = _numeric_summary_delta(baseline_row, candidate_row, tolerance)
    comparison["key"] = key
    if baseline_row.get("comparison") == "exact_hash":
        comparison["hash_equal"] = baseline_row.get("sha256") == candidate_row.get("sha256")
        if not comparison["hash_equal"]:
            comparison["status"] = "FAIL"
    baseline_replica = baseline_row.get("replica", {}).get("replicas_equal", True)
    candidate_replica = candidate_row.get("replica", {}).get("replicas_equal", True)
    comparison["replicas_equal"] = baseline_replica and candidate_replica
    if not comparison["replicas_equal"]:
        comparison["status"] = "FAIL"
    return comparison


def compare_parameter_probes(
504
505
506
507
508
509
510
511
512
    """
    baseline_rows = _probe_rows(Path(baseline_dir))
    candidate_rows = _probe_rows(Path(candidate_dir))
    if not baseline_rows or not candidate_rows:
        return {
            "status": "BLOCKED",
            "reason": "strict parameter probe evidence is missing",
        }
    baseline = _first_rank_rows(baseline_rows)
567
568
569
570
571
572
573
574
575
    """Summarize steady-state wall time and throughput independently of precision."""
    values = list(rows)[warmup_steps:]
    step_times = sorted(float(row["step_time_seconds"]) for row in values)
    if not step_times:
        return {"status": "BLOCKED", "reason": "no steady-state performance steps"}
    p90_index = min(math.ceil(0.9 * len(step_times)) - 1, len(step_times) - 1)
    token_rates = [
        float(row["tokens_per_second"])
        for row in values
625
626
627
628
629
630
631
632
633
        for event_name in events
        if event_name == preferred or event_name.startswith(f"{preferred}_")
    ]
    if not matching:
        return sorted(events)[-1]
    return max(matching, key=_checkpoint_event_order)


def _load_rank_layouts(files: list[Path]) -> list[dict[str, Any]]:
699
700
701
702
703
704
705
706
707
            for value in values
            if value is not None
        }
        if any(value is None for value in values) or len(global_shapes) != 1:
            inconsistencies.append(
                {
                    "side": side_name,
                    "name": name,
                    "reason": "rank-local payloads disagree on key presence or global shape",
716
717
718
719
720
721
722
723
724
) -> list[dict[str, Any]]:
    """Require equal rank counts and stable local shard shapes."""
    inconsistencies = []
    if len(baseline_ranks) != len(candidate_ranks):
        inconsistencies.append(
            {
                "side": "comparison",
                "reason": "same-topology checkpoint evidence has different rank counts",
                "baseline_ranks": len(baseline_ranks),
724
725
726
727
728
729
730
731
732
                "baseline_ranks": len(baseline_ranks),
                "candidate_ranks": len(candidate_ranks),
            }
        )
        return inconsistencies
    for rank, (baseline_layouts, candidate_layouts) in enumerate(
            zip(baseline_ranks, candidate_ranks)
    ):
        for name in sorted(set(baseline_layouts).intersection(candidate_layouts)):
732
733
734
735
736
737
738
739
740
        for name in sorted(set(baseline_layouts).intersection(candidate_layouts)):
            if baseline_layouts[name].get("local_shape") != candidate_layouts[name].get(
                    "local_shape"
            ):
                inconsistencies.append(
                    {
                        "side": "comparison",
                        "rank": rank,
                        "name": name,
753
754
755
756
757
758
759
760
761
    """Compare DCP-bound logical layouts while allowing legal resharding."""
    baseline_files = sorted(Path(baseline_dir).glob("**/checkpoint/*_rank*.json"))
    candidate_files = sorted(Path(candidate_dir).glob("**/checkpoint/*_rank*.json"))
    if not baseline_files or not candidate_files:
        return {"status": "BLOCKED", "reason": "checkpoint layout evidence is missing"}
    baseline_events = _group_checkpoint_events(baseline_files)
    candidate_events = _group_checkpoint_events(candidate_files)
    candidate_event = _latest_checkpoint_event(candidate_events, "after_load")
    checkpoint_identity = candidate_event.removeprefix("after_load").lstrip("_")
764
765
766
767
768
769
770
771
772
        if checkpoint_identity
        else _latest_checkpoint_event(baseline_events, "before_save")
    )
    if baseline_event not in baseline_events:
        return {
            "status": "BLOCKED",
            "reason": "baseline checkpoint evidence for the restored checkpoint is missing",
            "baseline_event": baseline_event,
            "candidate_event": candidate_event,
hyper_parallel/tools/model_integration/case_matrix.py
52
53
54
55
56
57
58
59
60
    metadata: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        """Return a JSON-serializable representation."""
        return asdict(self)


def _case_name(topology: Mapping[str, Any], prefix: str = "") -> str:
    ordered = (*_PARALLEL_TOPOLOGY_FIELDS, "recompute")
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

def _validate_recompute_selection(selection: Any, path: str) -> dict[str, Any]:
    """Validate one exact layer-count or layer-index recompute selection."""
    if not isinstance(selection, Mapping):
        raise ValueError(f"{path} must be a mapping")
    unsupported = tuple(
        name for name in selection if name not in ("layer_count", "layer_indices")
    )
    if unsupported:
        raise ValueError(
            f"{path} contains unsupported fields {unsupported}; supported fields: "
            "layer_count, layer_indices"
        )
    if ("layer_count" in selection) == ("layer_indices" in selection):
        raise ValueError(
            f"{path} must contain exactly one of layer_count or layer_indices"
        )
    if "layer_count" in selection:
        layer_count = selection["layer_count"]
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
        )
    if "layer_count" in selection:
        layer_count = selection["layer_count"]
        if isinstance(layer_count, bool) or not isinstance(layer_count, int) or layer_count < 0:
            raise ValueError(f"{path}.layer_count must be a non-negative integer")
        return {"layer_count": layer_count}

    layer_indices = selection["layer_indices"]
    if not isinstance(layer_indices, list) or not layer_indices:
        raise ValueError(
            f"{path}.layer_indices must be a non-empty list of non-negative integers"
        )
    if any(
        isinstance(index, bool) or not isinstance(index, int) or index < 0
103
104
105
106
107
108
109
110
111
112
113
114
115
    if any(
        isinstance(index, bool) or not isinstance(index, int) or index < 0
        for index in layer_indices
    ):
        raise ValueError(
            f"{path}.layer_indices must contain only non-negative integers"
        )
    if len(set(layer_indices)) != len(layer_indices):
        raise ValueError(f"{path}.layer_indices must not contain duplicates")
    return {"layer_indices": list(layer_indices)}


def _validate_topology(topology: Mapping[str, Any], path: str) -> dict[str, Any]:
114
115
116
117
118
119
120
121
122
123
124
125
126
127

def _validate_topology(topology: Mapping[str, Any], path: str) -> dict[str, Any]:
    """Reject fields that the standard Trainer launcher cannot consume."""
    if not isinstance(topology, Mapping):
        raise ValueError(f"{path} must be a mapping")
    resolved = dict(topology)
    unsupported = tuple(name for name in resolved if name not in _TOPOLOGY_DEFAULTS)
    if unsupported:
        supported = ", ".join(_TOPOLOGY_DEFAULTS)
        raise ValueError(
            f"{path} contains unsupported fields {unsupported}; supported fields: {supported}"
        )
    if "recompute" in resolved:
        resolved["recompute"] = _validate_recompute_selection(
150
151
152
153
154
155
156
157
158
) -> list[ValidationCase]:
    """Generate one case per declared single-axis deviation."""
    axes = matrix.get("axes") or {}
    if not isinstance(axes, Mapping):
        raise ValueError("matrix.axes must be a mapping")
    cases = []
    for axis, values in axes.items():
        if axis not in _TOPOLOGY_DEFAULTS:
            supported = ", ".join(_TOPOLOGY_DEFAULTS)
160
161
162
163
164
165
166
167
168
                f"matrix.axes contains unsupported field {axis!r}; "
                f"supported fields: {supported}"
            )
        if not isinstance(values, list) or not values:
            raise ValueError(f"matrix.axes.{axis} must be a non-empty list")
        for value in values:
            topology = dict(baseline)
            topology[axis] = value
            key = json.dumps(topology, sort_keys=True)
183
184
185
186
187
188
189
190
191
192
193
194
195
    """Generate one case per declared multi-axis combination."""
    cases = []
    for index, combined in enumerate(matrix.get("combined") or ()):
        if not isinstance(combined, Mapping):
            raise ValueError("matrix.combined entries must be mappings")
        topology = dict(baseline)
        topology.update(_validate_topology(combined, f"matrix.combined[{index}]"))
        key = json.dumps(topology, sort_keys=True)
        if key in seen:
            continue
        seen.add(key)
        cases.append(ValidationCase(_case_name(topology, f"combined-{index}"), topology))
    return cases
205
206
207
208
209
210
211
212
213
        "recompute_selections",
        ({"layer_count": 0},),
    )
    if not isinstance(requested_selections, (list, tuple)) or not requested_selections:
        raise ValueError("matrix.recompute_selections must be a non-empty list")
    cases = []
    for index, requested_selection in enumerate(requested_selections):
        selection = _validate_recompute_selection(
            requested_selection,
222
223
224
225
226
227
228
229
230
        )
        topology["recompute"] = selection
        key = json.dumps(topology, sort_keys=True)
        if key in seen:
            continue
        seen.add(key)
        cases.append(
            ValidationCase(_case_name(topology, "recompute"), topology)
        )
278
279
280
281
282
283
284
285
286
) -> list[ValidationCase]:
    """Generate the declared same/cross-topology resume cases."""
    cases = []
    if matrix.get("same_topology_resume"):
        cases.append(
            ValidationCase(
                "same_topology_resume",
                dict(baseline),
                kind="resume",
325
326
327
328
329
330
331
332
333
334
    """Apply topology constraints and classify each case acceptance profile."""
    resolved_cases = []
    for case in cases:
        for constraint in topology_constraints:
            if not constraint.predicate(case.topology):
                raise ValueError(
                    f"case {case.name!r} violates topology constraint "
                    f"{constraint.name!r}: {constraint.remediation}"
                )
        metadata = dict(case.metadata)
344
345
346
347
348
349
350
351
352
) -> tuple[ValidationCase, ...]:
    """Generate a minimum single-axis-plus-combined validation matrix."""
    baseline = dict(matrix.get("baseline") or {})
    if not baseline:
        raise ValueError("matrix.baseline is required")
    baseline = _validate_topology(baseline, "matrix.baseline")
    cases = [ValidationCase("baseline", baseline, compare_to="baseline")]
    seen = {json.dumps(baseline, sort_keys=True)}
    cases.extend(_axis_cases(matrix, baseline, seen))
hyper_parallel/tools/model_integration/checkpoint_coverage.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44


def _matches_target(transform: Any, target_name: str) -> bool:
    """Match processed WeightTransform regexes, not adapter-owned FQN globs."""
    scope = getattr(transform, "scope_prefix", None)
    scoped_name = target_name
    if scope and target_name.startswith(f"{scope}."):
        scoped_name = target_name.removeprefix(f"{scope}.")
    for pattern in getattr(transform, "target_patterns", ()):
        normalized = str(pattern).replace(".*.", ".0.")
        if re.search(normalized, scoped_name):
            return True
    return False


def _matches_any(name: str, patterns: tuple[str, ...]) -> bool:
    return any(fnmatch.fnmatchcase(name, pattern) for pattern in patterns)
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def _non_reversible_transforms(transforms: tuple[Any, ...]) -> list[str]:
    """Probe each declared transform for an executable reverse transform."""
    non_reversible = []
    for transform in transforms:
        reverse_transform = getattr(transform, "reverse_transform", None)
        if not callable(reverse_transform):
            non_reversible.append(type(transform).__name__)
            continue
        try:
            reverse_transform()
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            non_reversible.append(type(transform).__name__)
    return non_reversible


def _target_category(
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    """Classify one finalized persistent target key."""
    if target_name in loaded:
        if not matching:
            return "direct_load"
        if any(hasattr(transform, "operations") for transform in matching):
            return "reversible_transform"
        return "same_shape_rename"
    if target_name in intentional_missing:
        return "training_only_initialization"
    if target_name in unsupported_missing:
        return "unsupported_missing_target"
    return "alias_or_non_persistent"


def _coverage_entries(
        model: Any,
106
107
108
109
110
111
112
113
114
                "transforms": [type(transform).__name__ for transform in matching],
            }
        )
    for source_name in sorted(unexpected):
        entries.append(
            {
                "source_key": source_name,
                "category": (
                    "inference_only_exclusion"
150
151
152
153
154
155
156
157
158
                related_config=("model.load_base_model", "model adapter checkpoint"),
            )
        )
    if non_reversible:
        findings.append(
            ModelIntegrationFinding(
                code="HP-CKPT-002",
                phase="D5",
                owner_fqn="<root>",
hyper_parallel/tools/model_integration/cli.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108

def _ensure_discovered(store: EvidenceStore) -> None:
    """Initialize the first validation gate for an existing adapter."""
    if store.current_state() is None:
        store.update_state(IntegrationState.DISCOVERED, ("manifest.resolved.yaml",))


def _require_state(store: EvidenceStore, expected: IntegrationState, command: str) -> None:
    """Require the previous evidence-backed gate before a dependent command."""
    actual = store.current_state()
    if actual is not expected:
        actual_name = "none" if actual is None else actual.value
        raise ManifestError(
            f"{command} requires integration state {expected.value}, got {actual_name}; "
            "run the preceding model-integration gates in the same output directory"
        )
114
115
116
117
118
119
120
121
122
) -> None:
    """Accept same-run parity state or import an explicit passing handoff."""
    current_state = store.current_state()
    if current_state is IntegrationState.MODULE_PARITY_PASSED:
        return
    if current_state in (IntegrationState.FAILED, IntegrationState.BLOCKED):
        raise ManifestError(
            f"validate cannot import a handoff into terminal integration state "
            f"{current_state.value}; use a new output directory"
122
123
124
125
126
127
128
129
130
131
            f"{current_state.value}; use a new output directory"
        )
    handoff_path = manifest.integration_handoff_path
    if handoff_path is None:
        _require_state(store, IntegrationState.MODULE_PARITY_PASSED, "validate")
        return
    handoff = yaml.safe_load(handoff_path.read_text(encoding="utf-8"))
    store.write_yaml("integration_handoff.yaml", handoff)
    imported_evidence = []
    for handoff_key, default_path, destination in (
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
        source = Path(handoff.get(handoff_key, default_path))
        if not source.is_absolute():
            source = handoff_path.parent / source
        if not source.is_file():
            raise ManifestError(
                f"integration_handoff {handoff_key} evidence does not exist: {source}"
            )
        try:
            payload = json.loads(source.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError) as error:
            raise ManifestError(
                f"could not read integration_handoff {handoff_key} evidence: {error}"
            ) from error
        store.write_json(destination, payload)
        imported_evidence.append(destination)
163
164
165
166
167
168
169
170
171
def _build_declared_model(manifest: ValidationManifest, section_name: str) -> Any:
    section = getattr(manifest, section_name)
    builder = section.get("builder")
    if not isinstance(builder, Mapping):
        raise ManifestError(
            f"{section_name}.builder is required and must contain _target_ for this command"
        )
    target = builder.get("_target_")
    callable_value = manifest.import_callable(target)
171
172
173
174
175
176
177
178
    callable_value = manifest.import_callable(target)
    kwargs = {name: value for name, value in builder.items() if name != "_target_"}
    result = callable_value(**kwargs)
    if not hasattr(result, "named_modules"):
        raise ManifestError(
            f"{section_name}.builder returned {type(result).__name__}, expected a module"
        )
    return result
178
179
180
181
182
183
184
185
186
    return result


def _static_inventory(inspection: Mapping[str, Any], source: Mapping[str, Any]) -> ModelInventory:
    return ModelInventory(
        source=dict(source),
        config=dict(inspection.get("config", {})),
        checkpoint=dict(inspection.get("checkpoint", {})),
    )
198
199
200
201
202
203
204
205
206
207
208
209
210
                "section": section_name,
                "revision": section.get("revision"),
            },
        )
    path = manifest.model_path if section_name == "model" else manifest.reference_path
    if path is None:
        raise ManifestError(
            f"{section_name} requires either a local path or an explicit builder"
        )
    return _static_inventory(
        inspect_local_model_assets(path),
        {"section": section_name, "path": str(path), "revision": section.get("revision")},
    )
217
218
219
220
221
222
223
224
225
226
227
    candidate = _inspect_side(manifest, "model")
    store.write_json("inventory/candidate.json", candidate.to_dict())
    differences = []
    if manifest.reference:
        reference = _inspect_side(manifest, "reference")
        store.write_json("inventory/reference.json", reference.to_dict())
        differences = [
            difference.to_dict()
            for difference in diff_inventories(reference, candidate)
        ]
    store.write_json(
263
264
265
266
267
268
269
270
271
    """Generate a minimal adapter/validation skeleton in a new directory."""
    manifest = _manifest_from_args(args)
    destination = Path(args.scaffold_dir).expanduser().resolve()
    if destination.exists() and any(destination.iterdir()):
        raise ManifestError(f"scaffold destination must be new or empty: {destination}")
    adapter_dir = destination / "adapter"
    adapter_dir.mkdir(parents=True, exist_ok=True)
    family = manifest.family
    files = {
332
333
334
335
336
337
338
339
340
341
342
343
344
    try:
        model = _build_declared_model(manifest, "model")
    except ManifestError:
        raise
    except (ImportError, OSError, RuntimeError, TypeError, ValueError) as error:
        match = re.search(r"\[(HP-[A-Z]+-[0-9]{3})\]", str(error))
        code = match.group(1) if match is not None else "HP-BUILD-001"
        phase = "D1" if code.startswith("HP-REPLACE") else "D2"
        finding = ModelIntegrationFinding(
            code=code,
            phase=phase,
            owner_fqn="<model_builder>",
            severity=FindingSeverity.ERROR,
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
            why_unsafe="a partially built or partially planned model cannot enter training",
            remediation="follow the coded replacement/planner error and rebuild the final model",
            related_config=("model.builder", "plan_overrides"),
        )
        report = ModelIntegrationReport(findings=[finding])
        store.write_json("check/findings.json", report.to_dict())
        _ensure_discovered(store)
        store.update_state(
            IntegrationState.FAILED,
            ("check/findings.json",),
            reason="model construction failed",
        )
        print(finding.format_human(), file=sys.stderr)
        return 1
    report, ownership, fsdp_units = validate_final_model(
        model,
        context={"manifest": manifest.to_dict()},
    )
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
            "local_shape",
        ),
    )
    if report.errors:
        store.update_state(
            IntegrationState.FAILED,
            ("check/findings.json", "check/parameter_ownership.csv"),
            reason="structure model integration reported ERROR findings",
        )
        print("\n\n".join(finding.format_human() for finding in report.errors), file=sys.stderr)
        return 1
    _ensure_discovered(store)
    store.update_state(
        IntegrationState.STRUCTURE_VALIDATED,
        ("check/findings.json", "check/parameter_ownership.csv", "check/fsdp_units.json"),
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
    return 0


def _validation_spec(manifest: ValidationManifest) -> ModelValidationSpec:
    adapter = get_model_adapter(manifest.family)
    if adapter is None:
        raise ManifestError(f"no model adapter registered for {manifest.family!r}")
    provider = adapter.validation
    if provider is None or not callable(provider):
        raise ManifestError(
            f"model adapter {manifest.family!r} does not declare a validation provider"
        )
    validation_spec = provider()
    if not isinstance(validation_spec, ModelValidationSpec):
        raise ManifestError("adapter validation provider returned the wrong type")
    return validation_spec


def _recompute_policy(manifest: ValidationManifest) -> Optional[RecomputePolicy]:
    """Resolve normal-training recompute policy for precision case generation."""
    adapter = get_model_adapter(manifest.family)
    provider = None if adapter is None else adapter.recompute
    if provider is None:
        return None
    recompute_policy = provider()
    if not isinstance(recompute_policy, RecomputePolicy):
        raise ManifestError("adapter recompute provider returned the wrong type")
    return recompute_policy


def _parameter_tolerance(
        acceptance: Mapping[str, Any],
431
432
433
434
435
436
437
438
439
) -> dict[str, Any]:
    """Resolve strict base parameter limits plus an optional named override."""
    parameters = acceptance.get("parameters", {})
    if not isinstance(parameters, Mapping):
        raise ManifestError("acceptance.parameters must be a mapping")
    profile_names = ("module_parity", "same_topology", "cross_topology")
    tolerance = {
        name: value
        for name, value in parameters.items()
442
443
444
445
446
447
448
449
450
    override = parameters.get(profile)
    if override is None:
        return tolerance
    if not isinstance(override, Mapping):
        raise ManifestError(f"acceptance.parameters.{profile} must be a mapping")
    override = dict(override)
    if "summary" in override:
        base_summary = tolerance.get("summary", {})
        override_summary = override["summary"]
448
449
450
451
452
453
454
455
456
    if "summary" in override:
        base_summary = tolerance.get("summary", {})
        override_summary = override["summary"]
        if not isinstance(base_summary, Mapping) or not isinstance(override_summary, Mapping):
            raise ManifestError(
                f"acceptance.parameters.{profile}.summary must be a mapping"
            )
        override["summary"] = {**base_summary, **override_summary}
    tolerance.update(override)
458
459
460
461
462
463
464
465
466
467
468
469
470
471


def command_parity(args: argparse.Namespace) -> int:
    """Run final-module authoritative parity cases from the model provider."""
    manifest = _manifest_from_args(args)
    store = _store(manifest)
    _require_state(store, IntegrationState.STRUCTURE_VALIDATED, "parity")
    validation_spec = _validation_spec(manifest)
    tolerance = _parameter_tolerance(manifest.acceptance, "module_parity")
    context = ParityContext(
        manifest=manifest,
        dtype=args.dtype,
        device=args.device,
        evidence_store=store,
470
471
472
473
474
475
476
477
478
479
480
        device=args.device,
        evidence_store=store,
        options={"tolerance": tolerance},
    )
    report = run_module_parity(validation_spec, context)
    if report["status"] == "PASS":
        store.write_yaml(
            "integration_handoff.yaml",
            {
                "schema_version": SCHEMA_VERSION,
                "family": manifest.family,
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
                "checkpoint_coverage": "checkpoint/coverage.json",
                "status": "PASS",
            },
        )
        store.update_state(
            IntegrationState.MODULE_PARITY_PASSED,
            ("module_parity/comparison.json", "integration_handoff.yaml"),
        )
        return 0
    terminal = IntegrationState.FAILED if report["status"] == "FAIL" else IntegrationState.BLOCKED
    store.update_state(terminal, ("module_parity/comparison.json",), reason=report["status"])
    return 1 if report["status"] == "FAIL" else 2


def _phase_train_iters(phase: str, steps: int, split_step: int) -> int:
    """Map the launch phase to the number of executed training steps."""
509
510
511
512
513
514
515
516
517
        phase: str,
) -> dict[str, Any]:
    """Replay every measured case from the same warm-start checkpoint."""
    if not bool(manifest.matrix.get("shared_initial_checkpoint")):
        return {}
    if phase in ("initialize", "restore"):
        return {}
    initial_checkpoint_dir = (
        manifest.output_dir / "cases" / "baseline" / "initial_checkpoint"
590
591
592
593
594
595
596
597
598
    launcher = manifest.launcher
    module = launcher["module"]
    config = manifest.launcher_config_path
    if config is None:
        raise ManifestError("launcher.config is required for the standard Trainer launcher")
    topology = case.topology if topology is None else topology
    steps = int(manifest.matrix["steps"])
    split_step = int(manifest.matrix.get("resume_split_step", steps))
    tokens = [
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
    hccl_base_port = environment.get("HCCL_IF_BASE_PORT")
    if hccl_base_port is not None:
        try:
            isolated_port = int(hccl_base_port) + port_slot * port_stride
        except ValueError as error:
            raise ManifestError(
                "HCCL_IF_BASE_PORT must be an integer when model-integration "
                f"validation isolates case ports, got {hccl_base_port!r}"
            ) from error
        if not 0 < isolated_port <= 65535:
            raise ManifestError(
                "model-integration HCCL port isolation exceeds the valid port "
                f"range: base={hccl_base_port}, slot={port_slot}, "
                f"stride={port_stride}"
            )
769
770
771
772
773
774
775
776
777
778
779
780
781
782
            ),
            cwd=_REPOSITORY_ROOT,
            timeout=manifest.launcher.get("timeout_seconds"),
        )
    except OSError as error:
        return None, f"{phase} launcher could not start: {error}"
    except subprocess.TimeoutExpired as error:
        evidence_dir.joinpath("stdout.log").write_text(error.stdout or "", encoding="utf-8")
        evidence_dir.joinpath("stderr.log").write_text(error.stderr or "", encoding="utf-8")
        timeout = manifest.launcher.get("timeout_seconds")
        return None, f"{phase} launcher exceeded timeout_seconds={timeout}"
    evidence_dir.joinpath("stdout.log").write_text(result.stdout, encoding="utf-8")
    evidence_dir.joinpath("stderr.log").write_text(result.stderr, encoding="utf-8")
    return result.returncode, None
787
788
789
790
791
792
793
794
795
796
797
798
799
    store: EvidenceStore,
    case: Any,
    case_index: int,
) -> dict[str, Any]:
    case_dir = store.path(f"cases/{case.name}")
    case_dir.mkdir(parents=True, exist_ok=True)
    resume_dir = case_dir / "resume_checkpoint"
    launches = _resolve_case_launches(manifest, case, case_dir)
    commands = [
        {
            "phase": phase,
            "argv": phase_argv,
            "evidence_dir": str(phase_evidence_dir),
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
            "port_slot": case_index * 2 + launch_index,
        }
        for launch_index, (phase, phase_argv, phase_evidence_dir) in enumerate(launches)
    ]
    store.write_json(
        f"cases/{case.name}/preflight.json",
        {"case": case.to_dict(), "launches": commands},
    )
    returncodes = []
    for launch in commands:
        returncode, blocked_reason = _execute_case_launch(
            manifest,
            case,
            launch,
            resume_dir,
812
813
814
815
816
817
818
819
820
821
822
823
824
825
            case,
            launch,
            resume_dir,
        )
        if blocked_reason is not None:
            return {"case": case.name, "status": "BLOCKED", "reason": blocked_reason}
        returncodes.append(returncode)
        if returncode:
            break
    return {
        "case": case.name,
        "status": "PASS" if not any(returncodes) else "FAIL",
        "returncodes": returncodes,
        "metrics": f"cases/{case.name}/metrics.jsonl",
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847


def _case_artifact(case_dir: Path, relative_path: str) -> Path:
    """Resolve a launcher-owned artifact or the standard Trainer model integration location."""
    direct = case_dir / relative_path
    if direct.exists():
        return direct
    runtime = case_dir / "cases" / "runtime" / relative_path
    return runtime


def _performance_summary(case_dir: Path, warmup_steps: int) -> dict[str, Any]:
    """Summarize rank-zero timing and maximum allocator usage over all ranks."""
    runtime_dir = case_dir / "cases" / "runtime"
    rank_files = tuple(sorted(runtime_dir.glob("performance_rank*.jsonl")))
    memory_rows = [row for path in rank_files for row in load_jsonl(path)]
    return summarize_performance(
        load_jsonl(_case_artifact(case_dir, "performance.jsonl")),
        warmup_steps=warmup_steps,
        memory_rows=memory_rows,
    )
850
851
852
853
854
855
856
857
858
859
860
861
def _aggregate_statuses(statuses: Iterable[str]) -> str:
    """Reduce phase statuses using FAIL > BLOCKED > PASS precedence."""
    statuses = tuple(statuses)
    if "FAIL" in statuses:
        return "FAIL"
    if "BLOCKED" in statuses:
        return "BLOCKED"
    return "PASS"


def _compare_validation_case(
    manifest: ValidationManifest,
871
872
873
874
875
876
877
878
879
880
881
882
883
        raise ManifestError(
            f"validation case {case.name!r} has no acceptance classification; "
            "regenerate the case matrix with generate_validation_cases()"
        ) from error
    preflight = compare_preflight_configs(
        baseline_dir,
        candidate_dir,
    )
    scalar = compare_scalar_curves(
        load_jsonl(_case_artifact(baseline_dir, "metrics.jsonl")),
        load_jsonl(_case_artifact(candidate_dir, "metrics.jsonl")),
        manifest.acceptance.get(tolerance_name, {}),
        allow_candidate_subset=case.kind == "resume",
881
882
883
884
885
886
887
888
889
        load_jsonl(_case_artifact(candidate_dir, "metrics.jsonl")),
        manifest.acceptance.get(tolerance_name, {}),
        allow_candidate_subset=case.kind == "resume",
    )
    parameter = compare_parameter_probes(
        baseline_dir,
        candidate_dir,
        _parameter_tolerance(manifest.acceptance, tolerance_name),
        allow_candidate_subset=case.kind == "resume",
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
        _parameter_tolerance(manifest.acceptance, tolerance_name),
        allow_candidate_subset=case.kind == "resume",
        compare_optimizer_states=tolerance_name == "same_topology",
    )
    checkpoint_layout = None
    statuses = (*launch_statuses, preflight["status"], scalar["status"], parameter["status"])
    if case.kind == "resume":
        checkpoint_layout = compare_checkpoint_layouts(
            baseline_dir,
            candidate_dir,
            same_topology=case.metadata.get("resume") == "same_topology",
        )
        statuses = (*statuses, checkpoint_layout["status"])
    return {
        "status": _aggregate_statuses(statuses),
        "preflight": preflight,
        "scalar": scalar,
        "parameter_probes": parameter,
913
914
915
916
917
918
919
920
921
922
923
924
925
    comparisons: Mapping[str, Mapping[str, Any]],
    performance: Mapping[str, Mapping[str, Any]],
) -> int:
    """Persist the matrix result and advance the evidence state machine."""
    status = _aggregate_statuses(
        [result["status"] for result in results]
        + [comparison["status"] for comparison in comparisons.values()]
    )
    store.write_json(
        "comparison.json",
        {
            "status": status,
            "cases": results,
926
927
928
929
930
931
932
933
934
935
936
937
938
939
            "comparisons": comparisons,
            "performance": performance,
        },
    )
    if status == "PASS":
        store.advance_if_before(IntegrationState.MATRIX_PASSED, ("comparison.json",))
        return 0
    terminal = IntegrationState.FAILED if status == "FAIL" else IntegrationState.BLOCKED
    store.update_state(terminal, ("comparison.json",), reason=status)
    return 1 if status == "FAIL" else 2


def command_validate(args: argparse.Namespace) -> int:
    """Generate and optionally execute the minimum precision matrix."""
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
            validation_spec.topology_constraints,
        )
    except (TypeError, ValueError) as error:
        raise ManifestError(f"invalid validation matrix: {error}") from error
    store.write_json("cases/resolved_cases.json", [case.to_dict() for case in cases])
    if args.generate_only:
        print(store.path("cases/resolved_cases.json"))
        return 0
    results = [
        _run_validation_case(manifest, store, case, case_index)
        for case_index, case in enumerate(cases)
    ]
    results_by_name = {result["case"]: result for result in results}
    baseline_dir = store.path("cases/baseline")
    warmup_steps = int(manifest.matrix.get("performance_warmup_steps", 1))
    comparisons = {}
    performance = {"baseline": _performance_summary(baseline_dir, warmup_steps)}
    for case in cases:
        if case.name == "baseline":
            continue
        candidate_dir = store.path(f"cases/{case.name}")
        launch_statuses = (
            results_by_name["baseline"]["status"],
            results_by_name[case.name]["status"],
        )
        comparisons[case.name] = _compare_validation_case(
            manifest,
            case,
            baseline_dir,
            candidate_dir,
976
977
978
979
980
981
982
983
984
985
            baseline_dir,
            candidate_dir,
            launch_statuses,
        )
        performance[case.name] = _performance_summary(candidate_dir, warmup_steps)
    return _finalize_validation(store, results, comparisons, performance)


def _read_json(path: Path) -> Optional[dict[str, Any]]:
    """Read an optional JSON evidence file."""
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
    parity: Optional[Mapping[str, Any]],
    comparison: Optional[Mapping[str, Any]],
) -> tuple[list[str], list[str], list[str], list[str]]:
    """Render compact structure, parity, precision, and performance entries."""
    structure_entries = ["not executed"]
    if findings is not None:
        structure_entries = [
            f"{finding['code']} {finding['severity']}: {finding['message']} "
            f"(fix: {finding.get('remediation') or 'n/a'})"
            for finding in findings.get("findings", ())
        ] or ["PASS: no ERROR findings"]
    parity_entries = ["not executed"]
    if parity is not None:
        parity_entries = [
            f"{case.get('case', '<unnamed>')}: {case.get('status')}"
            + (f" ({case.get('reason')})" if case.get("reason") else "")
            for case in parity.get("cases", ())
        ] or [f"{parity.get('status')}: no executed cases"]
    precision_entries = ["not executed"]
    performance_entries = ["not executed"]
    if comparison is not None:
        precision_entries = [
            f"{name}: {value.get('status')}"
            for name, value in comparison.get("comparisons", {}).items()
        ] or [f"{comparison.get('status')}: baseline only"]
        performance_entries = [
            f"{name}: step_p50={value.get('step_time_p50_seconds')}, "
            f"tokens/s={value.get('tokens_per_second_p50')}, "
            f"peak_allocated={value.get('peak_memory_allocated_bytes')} bytes"
            for name, value in comparison.get("performance", {}).items()
1020
1021
1022
1023
1024
1025
1026
1027
1028
            f"tokens/s={value.get('tokens_per_second_p50')}, "
            f"peak_allocated={value.get('peak_memory_allocated_bytes')} bytes"
            for name, value in comparison.get("performance", {}).items()
        ] or ["not measured"]
    return structure_entries, parity_entries, precision_entries, performance_entries


def _resolved_report_cases(store: EvidenceStore) -> list[dict[str, Any]]:
    """Load resolved matrix cases for report rendering."""
1036
1037
1038
1039
1040
1041
1042
1043
def _report_manifest(store: EvidenceStore) -> dict[str, Any]:
    """Load the resolved manifest without requiring the original input path."""
    manifest_path = store.path("manifest.resolved.yaml")
    if not manifest_path.is_file():
        return {}
    payload = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) or {}
    return payload if isinstance(payload, dict) else {}

1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
    if name.startswith("recompute_"):
        return "recompute"
    if case.get("kind") == "resume":
        return "resume"
    if case.get("kind") == "production_validate":
        return "production_validate"
    return "other"


def _coverage_counts(resolved_cases: Iterable[Mapping[str, Any]]) -> dict[str, int]:
    """Count independent evidence categories."""
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
def _coverage_notes(manifest: Mapping[str, Any], language: str) -> list[str]:
    """Return localized exclusions, falling back to the canonical notes."""
    matrix = manifest.get("matrix", {})
    if not isinstance(matrix, Mapping):
        return []
    key = "coverage_notes_zh" if language == "zh" else "coverage_notes"
    notes = matrix.get(key, matrix.get("coverage_notes", ()))
    if not isinstance(notes, (list, tuple)):
        return []
    return [note for note in notes if isinstance(note, str)]


def _precision_coverage_entries(store: EvidenceStore, language: str = "en") -> list[str]:
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
def _format_report_number(value: Any) -> str:
    """Format optional numerical evidence compactly and deterministically."""
    if value is None:
        return "n/a"
    if isinstance(value, float):
        return f"{value:.6g}"
    return str(value)


def _format_topology(topology: Mapping[str, Any]) -> str:
    """Render the stable topology dimensions in one line."""
    keys = ("tp", "cp", "ep", "fsdp", "sequence_parallel", "recompute")
    return ", ".join(f"{key}={topology.get(key)}" for key in keys)


def _scalar_case_summary(comparison: Mapping[str, Any]) -> dict[str, Any]:
    """Collapse per-step scalar evidence while preserving identity gates."""
    steps = comparison.get("scalar", {}).get("steps", ())

    def maximum(name: str) -> Optional[float]:
        """Return the largest observed value without converting absent evidence."""
        values = [float(row[name]) for row in steps if row.get(name) is not None]
        return max(values, default=None)

    return {
        "steps": len(steps),
        "max_loss": maximum("loss_max_abs"),
        "max_loss_rel": maximum("loss_max_rel"),
        "max_norm": maximum("norm_max_abs"),
1158
1159
1160
1161
1162
1163
1164
1165
1166
) -> str:
    """Render exact-value and summary-only parameter limits without conflating them."""
    summary = tolerance.get("summary", {})
    if not isinstance(summary, Mapping):
        summary = {}
    if language == "zh":
        return (
            f"{label}:完整值 max_abs <= {_format_report_number(tolerance.get('max_abs'))},"
            f"relative_l2 <= {_format_report_number(tolerance.get('relative_l2'))},"
1180
1181
1182
1183
1184
1185
1186
1187
1188
def _acceptance_entries(manifest: Mapping[str, Any], language: str) -> list[str]:
    """Render every scalar and parameter threshold used by the matrix."""
    acceptance = manifest.get("acceptance", {})
    if not isinstance(acceptance, Mapping):
        acceptance = {}
    same = acceptance.get("same_topology", {})
    cross = acceptance.get("cross_topology", {})
    parity_parameters = _parameter_tolerance(acceptance, "module_parity")
    same_parameters = _parameter_tolerance(acceptance, "same_topology")
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
        default_steps: Any,
        language: str,
) -> str:
    """Render the reference-curve entry for the baseline case."""
    if language == "zh":
        return (
            f"[{category}] `{name}`:{topology};正式步数={default_steps};作为参考曲线。"
        )
    return (
        f"[{category}] `{name}`: {topology}; measured_steps={default_steps}; reference curve."
    )

1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
        case_comparison: Mapping[str, Any],
        language: str,
) -> str:
    """Render the DCP layout verdict or explain why none was recorded."""
    checkpoint = case_comparison.get("checkpoint_layout")
    if isinstance(checkpoint, Mapping):
        return checkpoint.get("status", "n/a")
    if case.get("kind") != "resume":
        return (
            "not applicable (non-resume case)"
            if language == "en"
            else "不适用(非断点续训用例)"
        )
    return "not recorded" if language == "en" else "未记录"


def _case_detail_entry(
        case: Mapping[str, Any],
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
        acceptance: Any,
        language: str,
) -> str:
    """Render thresholds, scalar gates, probes, and DCP for one case."""
    case_comparison = comparisons.get(name, {})
    scalar = _scalar_case_summary(case_comparison)
    metadata = case.get("metadata", {})
    profile = metadata.get("acceptance", "unknown")
    threshold = acceptance.get(profile, {}) if isinstance(acceptance, Mapping) else {}
    parameter = case_comparison.get("parameter_probes", {})
    checkpoint_status = _checkpoint_layout_status(case, case_comparison, language)
    identity_status = "PASS" if scalar["input_identity"] else "FAIL"
    lr_status = "PASS" if scalar["learning_rate_identity"] else "FAIL"
    finite_status = "PASS" if scalar["finite"] else "FAIL"
    if language == "zh":
        return (
            f"[{category}] `{name}`:{topology};阈值档={profile} "
            f"(loss<={_format_report_number(threshold.get('loss_max_abs'))}, "
            f"loss_rel<={_format_report_number(threshold.get('loss_max_rel'))}, "
            f"norm<={_format_report_number(threshold.get('norm_max_abs'))}, "
1298
1299
1300
1301
1302
1303
1304
1305
1306
            f"(optimizer 数值={parameter.get('optimizer_state_numeric_comparison', 'n/a')}, "
            f"跳过状态数={parameter.get('skipped_optimizer_states', 0)});"
            f"checkpoint layout={checkpoint_status};总状态={case_comparison.get('status', 'n/a')}。"
        )
    return (
        f"[{category}] `{name}`: {topology}; tolerance_profile={profile} "
        f"(loss<={_format_report_number(threshold.get('loss_max_abs'))}, "
        f"loss_rel<={_format_report_number(threshold.get('loss_max_rel'))}, "
        f"norm<={_format_report_number(threshold.get('norm_max_abs'))}, "
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
            "other": "其他",
        },
    }
    for case in resolved_cases:
        name = str(case.get("name", "<unnamed>"))
        category = category_names[language][_case_category(case)]
        topology = _format_topology(case.get("topology", {}))
        if name == "baseline":
            entries.append(
                _baseline_detail_entry(category, name, topology, default_steps, language)
            )
            continue
        entries.append(
            _case_detail_entry(
                case,
                name,
                category,
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
    """Identify the worst observed scalar deltas with case and step."""
    worst_loss = (-1.0, "n/a", "n/a")
    worst_norm = (-1.0, "n/a", "n/a")
    for name, case in ({} if comparison is None else comparison.get("comparisons", {})).items():
        for row in case.get("scalar", {}).get("steps", ()):
            loss = float(row.get("loss_max_abs", 0.0))
            norm = float(row.get("norm_max_abs", 0.0))
            if loss > worst_loss[0]:
                worst_loss = (loss, name, row.get("step"))
            if norm > worst_norm[0]:
                worst_norm = (norm, name, row.get("step"))
    if worst_loss[0] < 0:
        return ["未测量"] if language == "zh" else ["not measured"]
    if language == "zh":
        return [
            f"全矩阵最大 loss 绝对误差={_format_report_number(worst_loss[0])},"
            f"用例=`{worst_loss[1]}`,step={worst_loss[2]}",
            f"全矩阵最大梯度范数绝对误差={_format_report_number(worst_norm[0])},"
            f"用例=`{worst_norm[1]}`,step={worst_norm[2]}",
1396
1397
1398
1399
1400
1401
1402
1403
1404
            f"用例=`{worst_loss[1]}`,step={worst_loss[2]}",
            f"全矩阵最大梯度范数绝对误差={_format_report_number(worst_norm[0])},"
            f"用例=`{worst_norm[1]}`,step={worst_norm[2]}",
        ]
    return [
        f"matrix maximum loss absolute error={_format_report_number(worst_loss[0])}, "
        f"case=`{worst_loss[1]}`, step={worst_loss[2]}",
        f"matrix maximum gradient-norm absolute error={_format_report_number(worst_norm[0])}, "
        f"case=`{worst_norm[1]}`, step={worst_norm[2]}",
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
) -> list[str]:
    """Render each native module parity observation and compatibility note."""
    if parity is None:
        return ["未执行"] if language == "zh" else ["not executed"]
    entries = []
    for case in parity.get("cases", ()):
        case_name = case.get("case", "<unnamed>")
        entries.append(
            f"`{case_name}`:{case.get('status')}。" if language == "zh"
            else f"`{case_name}`: {case.get('status')}."
        )
        metric_rows = case.get("metrics", ())
        for metric in metric_rows:
            maximum = metric.get("max_abs")
            relative_l2 = metric.get("relative_l2")
            if language == "zh":
                entries.append(
                    f"模块 `{metric.get('name', '<unnamed>')}`:{metric.get('status')};"
                    f"最大绝对误差={_format_report_number(maximum)};"
                    f"相对 L2 误差={_format_report_number(relative_l2)}。"
                )
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
                    f"最大绝对误差={_format_report_number(maximum)};"
                    f"相对 L2 误差={_format_report_number(relative_l2)}。"
                )
            else:
                entries.append(
                    f"module `{metric.get('name', '<unnamed>')}`: {metric.get('status')}; "
                    f"maximum absolute error={_format_report_number(maximum)}; "
                    f"relative L2 error={_format_report_number(relative_l2)}."
                )
        for observation in case.get("observations", ()):
            metrics = observation.get("metrics")
            errors = observation.get("max_abs_errors", {})
            maximum = (
                metrics.get("max_abs")
                if isinstance(metrics, Mapping)
                else max((float(value) for value in errors.values()), default=None)
            )
            relative_l2 = metrics.get("relative_l2") if isinstance(metrics, Mapping) else None
            if language == "zh":
                detail = (
                    f"最大绝对误差={_format_report_number(maximum)};"
                    f"相对 L2 误差={_format_report_number(relative_l2)}"
                    if maximum is not None
                    else f"精确一致性={observation.get('status')}"
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
                    f"相对 L2 误差={_format_report_number(relative_l2)}"
                    if maximum is not None
                    else f"精确一致性={observation.get('status')}"
                )
                entries.append(
                    f"模块 `{observation.get('name', '<unnamed>')}`:{observation.get('status')};"
                    f"{detail}。"
                )
            else:
                detail = (
                    f"maximum absolute error={_format_report_number(maximum)}; "
                    f"relative L2 error={_format_report_number(relative_l2)}"
                    if maximum is not None
                    else f"exact equality={observation.get('status')}"
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
                    f"relative L2 error={_format_report_number(relative_l2)}"
                    if maximum is not None
                    else f"exact equality={observation.get('status')}"
                )
                entries.append(
                    f"module `{observation.get('name', '<unnamed>')}`: {observation.get('status')}; "
                    f"{detail}."
                )
        for note in case.get("compatibility", ()):
            prefix = "兼容性说明(原文)" if language == "zh" else "compatibility note"
            entries.append(f"{prefix}: {note}")
    return entries or (["无已执行用例"] if language == "zh" else ["no executed cases"])


def _detailed_performance_entries(
    comparison: Optional[Mapping[str, Any]],
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
    """Render warm performance and memory without making them precision gates."""
    performance = {} if comparison is None else comparison.get("performance", {})
    entries = []
    for name, value in performance.items():
        allocated = value.get("peak_memory_allocated_bytes")
        reserved = value.get("peak_memory_reserved_bytes")
        allocated_mib = None if allocated is None else float(allocated) / (1024 ** 2)
        reserved_mib = None if reserved is None else float(reserved) / (1024 ** 2)
        if language == "zh":
            entries.append(
                f"`{name}`:稳态步数={value.get('steady_steps', 'n/a')};"
                f"step p50={_format_report_number(value.get('step_time_p50_seconds'))}s;"
                f"p90={_format_report_number(value.get('step_time_p90_seconds'))}s;"
                f"tokens/s p50={_format_report_number(value.get('tokens_per_second_p50'))};"
1494
1495
1496
1497
1498
1499
1500
1501
1502
                f"峰值 allocated={_format_report_number(allocated_mib)} MiB;"
                f"reserved={_format_report_number(reserved_mib)} MiB。"
            )
        else:
            entries.append(
                f"`{name}`: steady_steps={value.get('steady_steps', 'n/a')}; "
                f"step p50={_format_report_number(value.get('step_time_p50_seconds'))}s; "
                f"p90={_format_report_number(value.get('step_time_p90_seconds'))}s; "
                f"tokens/s p50={_format_report_number(value.get('tokens_per_second_p50'))}; "
1539
1540
1541
1542
1543
1544
1545
1546
1547
    language: str,
) -> list[str]:
    """Render structure status, inventory size, and actionable findings."""
    if findings is None:
        return ["未执行"] if language == "zh" else ["not executed"]
    metadata = findings.get("metadata", {})
    counts = metadata.get("inventory_counts", {})
    if language == "zh":
        entries = [
1555
1556
1557
1558
1559
1560
1561
1562
1563
            f"modules={counts.get('modules', 'n/a')}; parameters={counts.get('parameters', 'n/a')}; "
            f"buffers={counts.get('buffers', 'n/a')}."
        ]
    for finding in findings.get("findings", ()):
        entries.append(
            f"{finding.get('code')} {finding.get('severity')}: {finding.get('message')} "
            f"(fix: {finding.get('remediation') or 'n/a'})"
        )
    return entries
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578


def _localized_value(value: Mapping[str, Any], key: str, language: str) -> Any:
    """Read a localized evidence field with an English fallback."""
    localized_key = f"{key}_zh" if language == "zh" else key
    return value.get(localized_key, value.get(key, "n/a"))


def _localized_sentence(value: Mapping[str, Any], key: str, language: str) -> str:
    """Return localized prose without duplicate terminal punctuation."""
    return str(_localized_value(value, key, language)).rstrip(".。")


def _verdict_entries(
    store: EvidenceStore,
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
    """Render the exact-callable distributed-operator audit when recorded."""
    audit = _read_json(store.path("analysis/operator_support.json"))
    if audit is None:
        return ["未记录"] if language == "zh" else ["not recorded"]
    if language == "zh":
        entries = [
            f"审计状态={audit.get('status', 'n/a')};"
            f"严格 Validate 状态={audit.get('strict_validate_status', 'n/a')}。"
        ]
        for operator in audit.get("operators", ()):
            consumer = _localized_sentence(operator, "consumer", language)
            reason = _localized_sentence(operator, "reason", language)
            entries.append(
                f"`{operator.get('callable', 'n/a')}`:状态={operator.get('status', 'n/a')};"
                f"registry key=`{operator.get('registry_key', 'n/a')}`;"
                f"实现={operator.get('implementation', 'n/a')};"
                f"注册={operator.get('registered', False)};"
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
                f"实现={operator.get('implementation', 'n/a')};"
                f"注册={operator.get('registered', False)};"
                f"使用方={consumer};结论={reason}。"
            )
        return entries
    entries = [
        f"audit status={audit.get('status', 'n/a')}; "
        f"strict Validate status={audit.get('strict_validate_status', 'n/a')}."
    ]
    for operator in audit.get("operators", ()):
        consumer = _localized_sentence(operator, "consumer", language)
        reason = _localized_sentence(operator, "reason", language)
        entries.append(
            f"`{operator.get('callable', 'n/a')}`: status={operator.get('status', 'n/a')}; "
            f"registry_key=`{operator.get('registry_key', 'n/a')}`; "
            f"implementation={operator.get('implementation', 'n/a')}; "
            f"registered={operator.get('registered', False)}; "
1641
1642
1643
1644
1645
1646
1647
1648
1649
            f"implementation={operator.get('implementation', 'n/a')}; "
            f"registered={operator.get('registered', False)}; "
            f"consumer={consumer}; finding={reason}."
        )
    return entries


def _parallel_ownership_entries(store: EvidenceStore, language: str) -> list[str]:
    """Render actual parameter placements instead of inferring them from launch size."""
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
    """Render actual parameter placements instead of inferring them from launch size."""
    audit = _read_json(store.path("analysis/parallel_ownership.json"))
    if audit is None:
        return ["未记录"] if language == "zh" else ["not recorded"]
    if language == "zh":
        entries = [
            f"审计状态={audit.get('status', 'n/a')};证据拓扑="
            f"{_format_topology(audit.get('topology', {}))}。"
        ]
        for subtree in audit.get("subtrees", ()):
            examples = ";".join(
                subtree.get("placement_evidence_zh", subtree.get("placement_evidence", ()))
            ) or "n/a"
            conclusion = _localized_sentence(subtree, "conclusion", language)
            entries.append(
                f"{_localized_value(subtree, 'name', language)}:"
                f"TP 状态={subtree.get('tp_status', 'n/a')};证据={examples};"
                f"结论={conclusion}。"
            )
        conclusion = _localized_sentence(audit, "conclusion", language)
        entries.append(f"总体结论:{conclusion}。")
        return entries
    entries = [
        f"audit status={audit.get('status', 'n/a')}; evidence topology="
        f"{_format_topology(audit.get('topology', {}))}."
    ]
    for subtree in audit.get("subtrees", ()):
        examples = "; ".join(subtree.get("placement_evidence", ())) or "n/a"
        conclusion = _localized_sentence(subtree, "conclusion", language)
        entries.append(
            f"{_localized_value(subtree, 'name', language)}: "
            f"TP status={subtree.get('tp_status', 'n/a')}; evidence={examples}; "
            f"conclusion={conclusion}."
        )
    conclusion = _localized_sentence(audit, "conclusion", language)
    entries.append(f"overall conclusion: {conclusion}.")
    return entries


def _issue_ledger_entries(store: EvidenceStore, language: str) -> list[str]:
    """Render resolved and open integration issues with stable identifiers."""
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
    """Render resolved and open integration issues with stable identifiers."""
    ledger = _read_json(store.path("analysis/issues.json"))
    if ledger is None:
        return ["未记录"] if language == "zh" else ["not recorded"]
    entries = []
    for issue in ledger.get("issues", ()):
        title = _localized_value(issue, "title", language)
        detail = _localized_value(issue, "detail", language)
        if language == "zh":
            entries.append(
                f"`{issue.get('id', 'n/a')}` [{issue.get('status', 'n/a')}/"
                f"{issue.get('severity', 'n/a')}] {title}:{detail}"
            )
        else:
            entries.append(
                f"`{issue.get('id', 'n/a')}` [{issue.get('status', 'n/a')}/"
                f"{issue.get('severity', 'n/a')}] {title}: {detail}"
            )
    return entries or (["无记录"] if language == "zh" else ["no entries"])


def _checkpoint_entries(
    checkpoint: Optional[Mapping[str, Any]],
1712
1713
1714
1715
1716
1717
1718
1719
1720
    language: str,
) -> list[str]:
    """Explain whether checkpoint tensors were loaded or scratch-initialized."""
    if checkpoint is None:
        return ["未执行"] if language == "zh" else ["not executed"]
    if language == "zh":
        reason = checkpoint.get("reason", "n/a")
        if reason == "model was initialized from scratch or no load report was retained":
            reason = "模型从随机状态初始化,或没有保留权重加载报告"
1898
1899
1900
1901
1902
        return 2


if __name__ == "__main__":
    raise SystemExit(main())
hyper_parallel/tools/model_integration/data_contract.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

    Returns:
        Declared field owners and any duplicate ownership claims.
    """
    ownership = {name: "framework" for name in sorted(_FRAMEWORK_FIELDS)}
    runtime_fields = getattr(runtime_adapter, "runtime_input_fields", lambda: ())()
    collisions = {}
    for field_name in runtime_fields:
        previous = ownership.get(field_name)
        if previous is not None:
            collisions.setdefault(field_name, [previous]).append("runtime_adapter")
        else:
            ownership[field_name] = "runtime_adapter"
    return {"fields": ownership, "collisions": collisions}


def validate_data_contract(
        validation_spec: Optional[ModelValidationSpec],
76
77
78
79
80
81
82
83
84
85
86
87

    Returns:
        Field-ownership evidence and actionable validation findings.
    """
    ownership = build_field_ownership(runtime_adapter)
    findings = []
    if ownership["collisions"]:
        findings.append(
            ModelIntegrationFinding(
                code="HP-DATA-001",
                phase="D2",
                owner_fqn="data.forward_fields",
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
                remediation="assign every forward field to exactly one framework or adapter owner",
                related_config=("dataset.data_transform", "dataloader.get_batch"),
            )
        )
    data_spec = validation_spec.data if validation_spec is not None else None
    if data_spec is not None:
        declared_runtime = set(ownership["fields"])
        missing_runtime = sorted(set(data_spec.runtime_fields) - declared_runtime)
        shift_mismatch = (
            data_spec.labels_are_shifted is not None
            and labels_are_shifted is not None
            and data_spec.labels_are_shifted != labels_are_shifted
        )
        missing_processor_revision = data_spec.processor_revision_required and not any(
            getattr(asset, "name_or_path", None)
            or getattr(asset, "_name_or_path", None)
            for asset in assets
        )
        if missing_runtime or shift_mismatch or missing_processor_revision:
            findings.append(
                ModelIntegrationFinding(
                    code="HP-DATA-002",
                    phase="D2",
                    owner_fqn="data.semantic_contract",
131
132
133
134
135
136
137
138
139
                    ),
                    related_config=("dataset", "dataloader", "model adapter validation"),
                )
            )
    ownership.update(
        {
            "source_type": source_type,
            "labels_are_shifted": labels_are_shifted,
        }
137
138
139
140
141
142
143
144
145
            "source_type": source_type,
            "labels_are_shifted": labels_are_shifted,
        }
    )
    return ownership, findings


def validate_observed_forward_fields(
        validation_spec: Optional[ModelValidationSpec],
147
148
149
150
151
152
153
154
155
156
157
158
159
) -> list[ModelIntegrationFinding]:
    """Validate the actual first-step kwargs against required model semantics."""
    data_spec = validation_spec.data if validation_spec is not None else None
    if data_spec is None:
        return []
    missing = sorted(set(data_spec.required_forward_fields) - set(model_inputs))
    modality_present = sorted(set(data_spec.modality_fields).intersection(model_inputs))
    if not missing:
        return []
    return [
        ModelIntegrationFinding(
            code="HP-DATA-002",
            phase="D7",
178
179
180
181
182
183
184
185
186
) -> dict[str, bool]:
    """Return whether every declared multimodal group has a finite gradient."""
    data_spec = validation_spec.data if validation_spec is not None else None
    if data_spec is None:
        return {}
    result = {}
    named_parameters = tuple(model.named_parameters(remove_duplicate=False))
    for pattern in data_spec.modality_parameter_patterns:
        matched = [
hyper_parallel/tools/model_integration/evidence_store.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    for candidate in Path(__file__).resolve().parents:
        if candidate.joinpath("pyproject.toml").is_file() and candidate.joinpath(
            "hyper_parallel"
        ).is_dir():
            return candidate
    return Path.cwd().resolve()


def _json_default(value: Any) -> Any:
    if is_dataclass(value):
        return asdict(value)
    if isinstance(value, Path):
        return str(value)
    if isinstance(value, Enum):
        return value.value
    return repr(value)


class EvidenceStore:
    """Write versioned evidence without parsing free-form Trainer logs."""
72
73
74
75
76
77
78
79
80
    def path(self, relative_path: str | Path) -> Path:
        """Resolve an evidence-relative path and reject directory traversal."""
        resolved = (self.root / relative_path).resolve()
        if resolved != self.root and self.root not in resolved.parents:
            raise ValueError(f"evidence path escapes run directory: {relative_path}")
        return resolved

    @staticmethod
    def _atomic_write(path: Path, content: str) -> None:
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
                output_file.write(content)
                output_file.flush()
                os.fsync(output_file.fileno())
            os.replace(temporary_name, path)
        except BaseException:
            try:
                os.unlink(temporary_name)
            except FileNotFoundError:
                pass
            raise

    def write_json(self, relative_path: str | Path, payload: Any) -> Path:
        """Write sorted, UTF-8 JSON atomically."""
        output_path = self.path(relative_path)
170
171
172
173
174
175
176
177
178
179

            environment["hyper_parallel"] = {
                "import_path": str(Path(hyper_parallel.__file__).resolve()),
            }
        except ImportError:
            environment["hyper_parallel"] = None
        try:
            import torch  # pylint: disable=import-outside-toplevel,forbidden-backend-import

            environment["torch"] = {
179
180
181
182
183
184
185
186
187
188
            environment["torch"] = {
                "version": torch.__version__,
                "import_path": str(Path(torch.__file__).resolve()),
            }
        except ImportError:
            environment["torch"] = None
        try:
            import transformers  # pylint: disable=import-outside-toplevel

            environment["transformers"] = {
188
189
190
191
192
193
194
195
196
197
            environment["transformers"] = {
                "version": transformers.__version__,
                "import_path": str(Path(transformers.__file__).resolve()),
            }
        except ImportError:
            environment["transformers"] = None
        self.write_json("environment.json", environment)
        return environment

    def update_state(
206
207
208
209
210
211
212
213
214
        if state_path.exists():
            previous = IntegrationState(json.loads(state_path.read_text(encoding="utf-8"))["state"])
        terminal = (IntegrationState.FAILED, IntegrationState.BLOCKED)
        if previous in terminal and state not in terminal:
            raise ValueError(f"cannot advance terminal integration state {previous.value}")
        if state not in terminal and previous is not None:
            previous_index = _STATE_ORDER.index(previous)
            next_index = _STATE_ORDER.index(state)
            if next_index < previous_index:
248
249
250
251
252
253
254
255
        strict enough to catch accidental regression in ordinary callers.
        """
        current = self.current_state()
        if current in (IntegrationState.FAILED, IntegrationState.BLOCKED):
            raise ValueError(f"cannot advance terminal integration state {current.value}")
        if current is not None and _STATE_ORDER.index(current) >= _STATE_ORDER.index(state):
            return self.path("integration_state.json")
        return self.update_state(state, evidence, reason)
272
273
274
275
276
277
278
279
280
        Returns:
            Path to the atomically written report.
        """
        if status not in ("PASS", "FAIL", "BLOCKED"):
            raise ValueError(f"invalid report status: {status!r}")
        lines = [status, "", f"# {title}"]
        for heading, entries in sections:
            lines.extend(("", f"## {heading}", ""))
            lines.extend(f"- {entry}" for entry in entries)
hyper_parallel/tools/model_integration/integration_validator.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    adapter_spec = None
    for identity in identities:
        if not identity:
            continue
        adapter_spec = get_model_adapter(identity)
        if adapter_spec is not None:
            break
    provider = getattr(adapter_spec, "validation", None)
    if provider is None:
        return None
    if not callable(provider):
        raise TypeError("ModelAdapterSpec.validation must be callable")
    validation_spec = provider()  # pylint: disable=not-callable
    if not isinstance(validation_spec, ModelValidationSpec):
        raise TypeError(
            "ModelAdapterSpec.validation must return ModelValidationSpec, "
            f"got {type(validation_spec).__name__}"
        )
    return validation_spec


def _tensor_local_shape(tensor: Any) -> tuple[int, ...]:
    to_local = getattr(tensor, "to_local", None)
175
176
177
178
179
180
181
182
183
184
185
186
    return ownership, units


def _module_fqn(model: Any, target: Any) -> str:
    for module_fqn, module in model.named_modules():
        if module is target:
            return module_fqn or "<root>"
    return f"<untracked:{type(target).__name__}>"


def _validate_gradient_domains(
    ownership: Iterable[ParameterOwnership],
206
207
208
209
210
211
212
213
214
            for entry in entries
        }
        if len(contracts) <= 1:
            continue
        findings.append(
            ModelIntegrationFinding(
                code="HP-FSDP-006",
                phase="D3",
                owner_fqn=entries[0].parameter_fqn,
276
277
278
279
280
281
282
283
284
285
286
287
        *model.named_parameters(remove_duplicate=False),
        *model.named_buffers(remove_duplicate=False),
    ):
        if bool(getattr(value, "is_meta", False)):
            meta_state.append(name)
    findings = []
    if meta_state:
        findings.append(
            ModelIntegrationFinding(
                code="HP-MAT-001",
                phase="D4",
                owner_fqn="<root>",
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    invalid_rebuildable_buffers = []
    for module_fqn, module in model.named_modules(remove_duplicate=False):
        specs = module.__dict__.get("_hp_rebuildable_buffer_specs", {})
        for buffer_name, spec in specs.items():
            buffer = module._buffers.get(buffer_name)  # pylint: disable=protected-access
            reason = None
            if buffer is None:
                reason = "buffer is missing"
            elif buffer_name not in module._non_persistent_buffers_set:  # pylint: disable=protected-access
                reason = "buffer became persistent"
            elif bool(getattr(buffer, "is_meta", False)):
                reason = "buffer still has meta storage"
            elif getattr(spec, "source", None) is None and getattr(spec, "factory", None) is None:
                reason = "rebuild recipe has neither source nor factory"
            if reason is not None:
                invalid_rebuildable_buffers.append(
                    {
                        "fqn": f"{module_fqn}.{buffer_name}" if module_fqn else buffer_name,
                        "reason": reason,
                    }
314
315
316
317
318
319
320
321
322
                        "reason": reason,
                    }
                )
    if invalid_rebuildable_buffers:
        findings.append(
            ModelIntegrationFinding(
                code="HP-MAT-002",
                phase="D4",
                owner_fqn="<root>",
336
337
338
339
340
341
342
343
344
345
346
347
348
349
    validation_spec: Optional[ModelValidationSpec],
) -> list[ModelIntegrationFinding]:
    if validation_spec is None:
        return []
    parameter_names = tuple(name for name, _ in model.named_parameters(remove_duplicate=False))
    findings = []
    for probe in validation_spec.parameter_probes:
        matches = [name for name in parameter_names if fnmatch.fnmatchcase(name, probe.match)]
        if probe.required and not matches:
            findings.append(
                ModelIntegrationFinding(
                    code="HP-PREC-001",
                    phase="V0",
                    owner_fqn=probe.match,
354
355
356
357
358
359
360
361
362
                    remediation="update the model validation provider to match final parameter FQNs",
                    related_config=("ModelAdapterSpec.validation.parameter_probes",),
                )
            )
    return findings


def validate_state_invariants(
    model: Any,
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
                    related_config=("ModelAdapterSpec.validation.state_invariants",),
                )
            )
            continue
        if invariant.phase not in phases:
            continue
        result = invariant.checker(model, context)
        if result is True or result is None:
            continue
        facts = result if isinstance(result, Mapping) else {"result": result}
        findings.append(
            ModelIntegrationFinding(
                code=invariant.error_code,
                phase=phase_by_name[invariant.phase],
                owner_fqn=invariant.name,
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464


def inventory_final_model(model: Any, source: Optional[dict[str, Any]] = None) -> dict[str, Any]:
    """Build a serializable final-tree inventory for integration evidence."""
    return build_model_inventory(model, source=source).to_dict()


def collect_activation_checkpoint_wrappers(model: Any) -> dict[str, Any]:
    """List final activation-checkpoint wrappers without backend imports."""
    wrappers = []
    for module_fqn, module in model.named_modules(remove_duplicate=False):
        if type(module).__name__ != "CheckpointWrapper":
            continue
        wrapped = getattr(module, "_wrapped_module", None)
        wrappers.append(
            {
                "fqn": module_fqn or "<root>",
                "wrapped_type": (
                    None
466
467
468
469
470
471
472
473
474
                    else f"{type(wrapped).__module__}.{type(wrapped).__qualname__}"
                ),
            }
        )
    return {"count": len(wrappers), "wrappers": wrappers}


__all__ = [
    "ParameterOwnership",
hyper_parallel/tools/model_integration/manifest.py
37
38
39
40
41
42
43
44
45
def _mapping(value: Any, path: str, required: bool = True) -> dict[str, Any]:
    if value is None and not required:
        return {}
    if not isinstance(value, Mapping):
        raise ManifestError(f"{path} must be a mapping")
    return dict(value)


def _local_path(value: Any, base_dir: Path, field_path: str) -> Optional[Path]:
45
46
47
48
49
50
51
52
53
def _local_path(value: Any, base_dir: Path, field_path: str) -> Optional[Path]:
    if value is None:
        return None
    if not isinstance(value, str) or not value:
        raise ManifestError(f"{field_path} must be a non-empty local path or null")
    if value.startswith(("http://", "https://", "hf://")):
        raise ManifestError(
            f"{field_path} must be local; model-integration tools never download implicitly"
        )
72
73
74
75
76
77
78
79
80
    def family(self) -> str:
        """Return the adapter/model family lookup key."""
        family = self.model.get("adapter") or self.model.get("model_type")
        if not isinstance(family, str) or not family:
            raise ManifestError("model.adapter or model.model_type must be a non-empty string")
        return family

    @property
    def model_path(self) -> Optional[Path]:
116
117
118
119
120
121
122
123
124
125
126
127
            raise ManifestError(f"callable target must be a dotted path, got {target!r}")
        module_name, attribute_name = target.rsplit(".", 1)
        try:
            value = getattr(importlib.import_module(module_name), attribute_name)
        except (ImportError, AttributeError) as exc:
            raise ManifestError(f"could not import callable {target!r}: {exc}") from exc
        if not callable(value):
            raise ManifestError(f"manifest target {target!r} is not callable")
        return value

    def fingerprint_paths(self) -> dict[str, Any]:
        """Hash bounded metadata from declared model and reference sources."""
134
135
136
137
138
139
140
141
142
143
            path = _local_path(value, self.path.parent, field_path)
            if path is None:
                continue
            if not path.exists():
                fingerprints[field_path] = {"path": str(path), "exists": False}
                continue
            if path.is_dir():
                metadata_names = {
                    "config.json",
                    "generation_config.json",
171
172
173
174
175
176
177
178
179
                        for candidate in metadata_files
                    },
                }
                continue
            fingerprints[field_path] = {
                "path": str(path),
                "exists": True,
                "kind": "file",
                **self._fingerprint_file(path),
191
192
193
194
195
196
197
198
199
200
201
202

    def _require_matrix_steps(self) -> None:
        """Require an explicit baseline and a positive step budget."""
        if not self.matrix.get("baseline"):
            raise ManifestError("matrix.baseline is required for validate")
        steps = self.matrix.get("steps")
        if not isinstance(steps, int) or steps <= 0:
            raise ManifestError("matrix.steps must be a positive integer")

    def _require_launcher_contract(self) -> None:
        """Require an existing local Trainer config and a module path."""
        trainer_config = self.launcher.get("config")
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
        """Require an existing local Trainer config and a module path."""
        trainer_config = self.launcher.get("config")
        trainer_module = self.launcher.get("module")
        if not isinstance(trainer_config, str) or not trainer_config:
            raise ManifestError("launcher.config must be a non-empty path")
        trainer_config_path = self.launcher_config_path
        if trainer_config_path is None or not trainer_config_path.is_file():
            raise ManifestError(
                f"launcher.config does not exist: {trainer_config_path}"
            )
        if not isinstance(trainer_module, str) or not trainer_module:
            raise ManifestError("launcher.module must be a non-empty dotted module")

    def _require_matrix_resources(self) -> None:
        """Require a positive device count and a bool warm-start flag."""
        devices = self.matrix.get("devices")
214
215
216
217
218
219
220
221
222
223
224
225
    def _require_matrix_resources(self) -> None:
        """Require a positive device count and a bool warm-start flag."""
        devices = self.matrix.get("devices")
        if not isinstance(devices, int) or devices <= 0:
            raise ManifestError("matrix.devices must be a positive integer")
        shared_initial_checkpoint = self.matrix.get("shared_initial_checkpoint", False)
        if not isinstance(shared_initial_checkpoint, bool):
            raise ManifestError("matrix.shared_initial_checkpoint must be a bool")

    def _require_launcher_timeout(self) -> None:
        """Require any declared launcher timeout to be positive."""
        timeout = self.launcher.get("timeout_seconds")
227
228
229
230
231
232
233
234
235
            isinstance(timeout, bool)
            or not isinstance(timeout, (int, float))
            or timeout <= 0
        ):
            raise ManifestError("launcher.timeout_seconds must be positive")

    def _require_resume_split_step(self) -> None:
        """Bound the resume split step whenever resume cases are declared."""
        steps = self.matrix.get("steps")
243
244
245
246
247
248
249
250
251
            or not isinstance(split_step, int)
            or split_step <= 0
            or split_step >= steps
        ):
            raise ManifestError(
                "resume cases require 0 < matrix.resume_split_step < matrix.steps"
            )
        shared_initial_checkpoint = self.matrix.get("shared_initial_checkpoint", False)
        if has_resume_case and shared_initial_checkpoint and split_step <= 1:
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
    def _require_integration_handoff(self) -> None:
        """Require a PASS handoff for the same adapter family when declared."""
        handoff = self.integration_handoff_path
        if handoff is not None and not handoff.is_file():
            raise ManifestError(f"integration_handoff does not exist: {handoff}")
        if handoff is None:
            return
        try:
            handoff_payload = yaml.safe_load(handoff.read_text(encoding="utf-8"))
        except (OSError, yaml.YAMLError) as exc:
            raise ManifestError(f"could not parse integration_handoff: {exc}") from exc
        if not isinstance(handoff_payload, Mapping):
            raise ManifestError("integration_handoff must contain a mapping")
        if handoff_payload.get("schema_version") != SCHEMA_VERSION:
            raise ManifestError(
                f"integration_handoff schema_version must be {SCHEMA_VERSION}"
            )
        if handoff_payload.get("status") != "PASS":
            raise ManifestError("integration_handoff status must be PASS")
        if handoff_payload.get("family") != self.family:
            raise ManifestError(
                "integration_handoff family does not match model adapter"
            )

    def validate_for(self, command: str) -> None:
283
284
285
286
287
288
289
290
291
        _ = self.model_path
        _ = self.reference_path
        _ = self.integration_handoff_path
        if command == "parity" and not self.reference:
            raise ManifestError(f"reference is required for {command}")
        if command != "validate":
            return
        self._require_matrix_steps()
        self._require_launcher_contract()
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
        ManifestError: If schema, types, or local-only constraints are invalid.
    """
    manifest_path = Path(path).expanduser().resolve()
    if not manifest_path.is_file():
        raise ManifestError(f"manifest does not exist: {manifest_path}")
    try:
        if manifest_path.suffix.lower() == ".json":
            loaded = json.loads(manifest_path.read_text(encoding="utf-8"))
        else:
            loaded = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError, yaml.YAMLError) as exc:
        raise ManifestError(f"could not parse manifest {manifest_path}: {exc}") from exc
    raw = _mapping(loaded, "$")
    if raw.get("schema_version") != SCHEMA_VERSION:
        raise ManifestError(f"schema_version must be {SCHEMA_VERSION}")
    model = _mapping(raw.get("model"), "model")
    if not (model.get("adapter") or model.get("model_type")):
        raise ManifestError("model.adapter or model.model_type is required")
    reference = _mapping(raw.get("reference"), "reference", required=False)
    matrix = _mapping(raw.get("matrix"), "matrix", required=False)
    acceptance = _mapping(raw.get("acceptance"), "acceptance", required=False)
    launcher = _mapping(raw.get("launcher"), "launcher", required=False)
332
333
334
335
336
337
338
339
340
        manifest_path.parent / "output" / "model_integration" / str(model.get("adapter"))
    )
    resolved_output = Path(configured_output).expanduser()
    if not resolved_output.is_absolute():
        resolved_output = (manifest_path.parent / resolved_output).resolve()
    else:
        resolved_output = resolved_output.resolve()
    return ValidationManifest(
        path=manifest_path,
hyper_parallel/tools/model_integration/module_parity.py
55
56
57
58
59
60
61
62
63
64
65
66
67


def _select_module(value: Any, selector: str) -> Any:
    if selector in ("", "<root>"):
        return value
    module_by_fqn = dict(value.named_modules())
    selected = module_by_fqn.get(selector)
    if selected is None:
        raise ValueError(f"candidate selector matched no final module: {selector!r}")
    return selected


def _clone_value(value: Any) -> Any:
65
66
67
68
69
70
71
72
73


def _clone_value(value: Any) -> Any:
    if not torch.is_tensor(value):
        return value
    clone = value.detach().clone()
    if value.requires_grad and (clone.is_floating_point() or clone.is_complex()):
        clone.requires_grad_(True)
    return clone
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89


def _clone_call_inputs(inputs: Any) -> tuple[tuple[Any, ...], dict[str, Any]]:
    if isinstance(inputs, Mapping) and ("args" in inputs or "kwargs" in inputs):
        args = tuple(inputs.get("args", ()))
        kwargs = dict(inputs.get("kwargs", {}))
    elif isinstance(inputs, Mapping):
        args = ()
        kwargs = dict(inputs)
    elif isinstance(inputs, tuple):
        args = inputs
        kwargs = {}
    else:
        args = (inputs,)
        kwargs = {}
    return tree_map(_clone_value, args), tree_map(_clone_value, kwargs)
 98
 99
100
101
102
103
104
105
106

def _default_objective(output: Any) -> torch.Tensor:
    tensors = _floating_tensors(output)
    if not tensors:
        raise ValueError("parity output contains no differentiable tensor")
    return sum(tensor.float().sum() for tensor in tensors)


def _metric(
117
118
119
120
121
122
123
124
125
    candidate_cpu = candidate.detach().float().cpu()
    same_shape = tuple(reference_cpu.shape) == tuple(candidate_cpu.shape)
    finite = bool(torch.isfinite(reference_cpu).all() and torch.isfinite(candidate_cpu).all())
    if not same_shape:
        return ComparisonMetric(
            name=name,
            status="FAIL",
            max_abs=None,
            relative_l2=None,
163
164
165
166
167
168
169
170
171
) -> list[ComparisonMetric]:
    reference_values, reference_spec = tree_flatten(reference)
    candidate_values, candidate_spec = tree_flatten(candidate)
    if reference_spec != candidate_spec:
        return [
            ComparisonMetric(
                name=prefix,
                status="FAIL",
                max_abs=None,
196
197
198
199
200
201
202
203
204
205
                    rtol=rtol,
                    relative_l2_limit=relative_l2_limit,
                )
            )
        elif reference_value != candidate_value:
            metrics.append(
                ComparisonMetric(
                    name=name,
                    status="FAIL",
                    max_abs=None,
219
220
221
222
223
224
225
226
227
228
229
230
    candidate: Any,
    context: ParityContext,
) -> dict[str, str]:
    if case.weight_adapter is None:
        raise TypeError("in-process parity case requires weight_adapter")
    mapping_result = case.weight_adapter(reference, candidate, context)
    if mapping_result is None:
        mapping = {
            name: name for name, parameter in candidate.named_parameters()
            if parameter.requires_grad
        }
    elif isinstance(mapping_result, Mapping):
229
230
231
232
233
234
235
236
237
        }
    elif isinstance(mapping_result, Mapping):
        mapping = {str(key): str(value) for key, value in mapping_result.items()}
    else:
        raise TypeError("parity weight_adapter must return a candidate-to-reference mapping or None")
    candidate_trainable = {
        name for name, parameter in candidate.named_parameters() if parameter.requires_grad
    }
    reference_names = dict(reference.named_parameters())
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
    reference_names = dict(reference.named_parameters())
    missing = sorted(candidate_trainable - set(mapping))
    invalid = sorted(set(mapping.values()) - set(reference_names))
    if missing or invalid:
        raise ValueError(
            "parity weight coverage is incomplete: "
            f"missing_candidate={missing}, unknown_reference={invalid}"
        )
    for pattern in case.required_parameter_patterns:
        if not any(fnmatch.fnmatchcase(name, pattern) for name in candidate_trainable):
            raise ValueError(f"required parity parameter pattern matched nothing: {pattern!r}")
    return mapping


def _input_gradients(args: tuple[Any, ...], kwargs: dict[str, Any]) -> list[torch.Tensor | None]:
260
261
262
263
264
265
266
267
268
269
270
271
272
273
    candidate_builder = case.candidate_builder
    reference_builder = case.reference_builder
    input_builder = case.input_builder
    if not all(callable(value) for value in (candidate_builder, reference_builder, input_builder)):
        raise TypeError("in-process parity case has an incomplete callable contract")
    candidate_tree = candidate_builder(context)
    candidate = _select_module(candidate_tree, case.candidate_selector)
    reference = reference_builder(context)
    if candidate is reference or type(candidate) is type(reference):
        raise ValueError(
            "reference and candidate must be independent implementations; "
            f"got {type(candidate).__module__}.{type(candidate).__qualname__}"
        )
    return candidate, reference
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
) -> list[ComparisonMetric]:
    """Compare every declared observation between reference and candidate."""
    metrics = []
    for observation in case.observations:
        reference_value = (
            observation.reference_getter(reference_execution)
            if observation.reference_getter is not None
            else reference_output
        )
        candidate_value = (
            observation.candidate_getter(candidate_execution)
            if observation.candidate_getter is not None
            else candidate_output
        )
        if reference_value is None or candidate_value is None:
            if observation.required:
                raise ValueError(
                    f"required parity observation {observation.name!r} was not produced: "
                    f"reference={reference_value is not None}, "
                    f"candidate={candidate_value is not None}"
                )
            if reference_value is None and candidate_value is None:
                continue
        metrics.extend(
            _compare_tree(
                observation.name,
                reference_value,
                candidate_value,
348
349
350
351
352
353
354
355
356
357
358
    for index, (reference_grad, candidate_grad) in enumerate(
        zip(reference_input_grads, candidate_input_grads)
    ):
        if reference_grad is None and candidate_grad is None:
            continue
        if reference_grad is None or candidate_grad is None:
            raise ValueError(f"input gradient presence differs at flattened input {index}")
        metrics.append(
            _metric(
                f"input_grad.{index}",
                reference_grad,
381
382
383
384
385
386
387
388
389
390
391
    for candidate_name, reference_name in sorted(mapping.items()):
        reference_grad = reference_parameters[reference_name].grad
        candidate_grad = candidate_parameters[candidate_name].grad
        if reference_grad is None and candidate_grad is None:
            continue
        if reference_grad is None or candidate_grad is None:
            raise ValueError(
                f"parameter gradient presence differs: {candidate_name} -> {reference_name}"
            )
        metrics.append(
            _metric(
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
    """Execute every declared parity case and persist per-case evidence."""
    reports = []
    for case in validation_spec.module_cases:
        if context.dtype not in case.required_dtypes:
            continue
        try:
            if case.execution == "isolated_process":
                report = case.isolated_runner(context)  # type: ignore[misc]
                if not isinstance(report, dict) or report.get("status") not in (
                    "PASS", "FAIL", "BLOCKED"
                ):
                    raise ValueError("isolated parity runner returned an invalid report")
            else:
                report = _run_in_process_case(case, context)
        except NotImplementedError as error:
            report = {
                "case": case.name,
                "status": "BLOCKED",
                "reason": "OPERATOR_UNSUPPORTED",
                "error": str(error),
492
493
494
495
496
497
498
499
500
501
                "status": "BLOCKED",
                "reason": "OPERATOR_UNSUPPORTED",
                "error": str(error),
            }
        except Exception as error:  # pylint: disable=broad-except
            report = {
                "case": case.name,
                "status": "FAIL",
                "error_type": type(error).__name__,
                "error": str(error),
513
514
515
516
517
518
519
520
521
        else "PASS"
    )
    aggregate = {"status": status, "cases": reports}
    if not reports:
        aggregate["reason"] = (
            "no module parity case supports the requested dtype, or the adapter "
            "declared no authoritative case"
        )
    context.evidence_store.write_json("module_parity/comparison.json", aggregate)
hyper_parallel/tools/model_integration/optimizer_layout.py
53
54
55
56
57
58
59
60
61
62
    }


def _same_logical_layout(first: Any, second: Any) -> bool:
    first_layout = tensor_layout(first)
    second_layout = tensor_layout(second)
    return all(
        first_layout[name] == second_layout[name]
        for name in ("global_shape", "mesh_dim_names", "mesh_shape", "placements")
    )
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


def optimizer_leaves(optimizer: Any) -> tuple[Any, ...]:
    """Return optimizer leaves through list, chain, and wrapper containers."""
    chained = getattr(optimizer, "chained_optimizers", None)
    if chained is not None:
        return tuple(leaf for item in chained for leaf in optimizer_leaves(item))
    inner = getattr(optimizer, "optimizer", None)
    if inner is not None and inner is not optimizer:
        return optimizer_leaves(inner)
    if isinstance(optimizer, (list, tuple)):
        return tuple(leaf for item in optimizer for leaf in optimizer_leaves(item))
    return (optimizer,)


def optimizer_state_for_parameter(optimizer: Any, parameter: Any) -> Mapping[str, Any]:
    """Find one parameter's state through supported optimizer wrappers."""
    for leaf in optimizer_leaves(optimizer):
        state = getattr(leaf, "state", None)
        if isinstance(state, Mapping) and parameter in state:
            value = state[parameter]
            return value if isinstance(value, Mapping) else {}
    return {}


def _layout_mismatch_finding(
        parameter_fqn: str,
90
91
92
93
94
95
96
97
98
        owner_name: str,
        expected: Any,
        actual: Any,
) -> ModelIntegrationFinding:
    return ModelIntegrationFinding(
        code="HP-OPT-001",
        phase="D6",
        owner_fqn=parameter_fqn,
        severity=FindingSeverity.ERROR,
114
115
116
117
118
119
120
121
122
123
124
125
126


def validate_optimizer_layout(model: Any, optimizer: Any) -> list[ModelIntegrationFinding]:
    """Validate param/main-param/main-grad/moment layouts from live objects."""
    findings = []
    for parameter_fqn, model_parameter in model.named_parameters(remove_duplicate=False):
        main_parameter = getattr(model_parameter, "main_param", model_parameter)
        if not _same_logical_layout(model_parameter, main_parameter):
            findings.append(
                _layout_mismatch_finding(
                    parameter_fqn,
                    "main_parameter",
                    model_parameter,
126
127
128
129
130
131
132
133
134
135
136
137
138
139
                    model_parameter,
                    main_parameter,
                )
            )
            continue
        gradient = getattr(model_parameter, "main_grad", None)
        if gradient is None:
            gradient = getattr(main_parameter, "grad", None)
        if gradient is not None and not _same_logical_layout(main_parameter, gradient):
            findings.append(
                _layout_mismatch_finding(
                    parameter_fqn,
                    "main_gradient",
                    main_parameter,
139
140
141
142
143
144
145
146
147
148
149
150
151
152
                    main_parameter,
                    gradient,
                )
            )
        for state_name, state_value in optimizer_state_for_parameter(
                optimizer, main_parameter
        ).items():
            if torch.is_tensor(state_value) and state_value.ndim > 0:
                if not _same_logical_layout(main_parameter, state_value):
                    findings.append(
                        _layout_mismatch_finding(
                            parameter_fqn,
                            f"optimizer_state.{state_name}",
                            main_parameter,
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
                            main_parameter,
                            state_value,
                        )
                    )
        layout = tensor_layout(main_parameter)
        if (
            math.prod(layout["global_shape"])
            and not math.prod(layout["local_shape"])
            and not layout["is_dtensor"]
        ):
            findings.append(
                ModelIntegrationFinding(
                    code="HP-OPT-002",
                    phase="D8",
                    owner_fqn=parameter_fqn,
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
                    remediation="keep uneven FSDP shards as DTensors through optimizer state creation",
                    related_config=("parallel.fsdp", "optimizer.fp32_main_params"),
                )
            )
    return findings


def snapshot_training_layout(model: Any, optimizer: Any) -> dict[str, Any]:
    """Build model/main-gradient/optimizer-state layout evidence."""
    records = {}
    for parameter_fqn, model_parameter in model.named_parameters(remove_duplicate=False):
        main_parameter = getattr(model_parameter, "main_param", model_parameter)
        record = {
            "model_parameter": tensor_layout(model_parameter),
            "main_parameter": tensor_layout(main_parameter),
        }
        gradient = getattr(model_parameter, "main_grad", None)
        if gradient is None:
            gradient = getattr(main_parameter, "grad", None)
        if gradient is not None:
            record["main_gradient"] = tensor_layout(gradient)
        state = optimizer_state_for_parameter(optimizer, main_parameter)
        record["optimizer_state"] = {
            name: tensor_layout(value)
            for name, value in sorted(state.items())
            if torch.is_tensor(value)
        }
        records[parameter_fqn] = record
    return {"schema_version": SCHEMA_VERSION, "parameters": records}


def flatten_tensor_layouts(value: Any, prefix: str = "") -> dict[str, dict[str, Any]]:
    """Flatten tensor leaves from a checkpoint payload into layout records."""
    records = {}
    if torch.is_tensor(value):
        records[prefix or "<root>"] = tensor_layout(value)
    elif isinstance(value, Mapping):
        for name, child in value.items():
            child_prefix = f"{prefix}.{name}" if prefix else str(name)
            records.update(flatten_tensor_layouts(child, child_prefix))
    elif isinstance(value, (list, tuple)):
        for index, child in enumerate(value):
            child_prefix = f"{prefix}.{index}" if prefix else str(index)
            records.update(flatten_tensor_layouts(child, child_prefix))
    return records


__all__ = [
    "flatten_tensor_layouts",
hyper_parallel/tools/model_integration/schemas.py
74
75
76
77
78
79
80
81
82
    def __post_init__(self) -> None:
        if not _ERROR_CODE.fullmatch(self.code):
            raise ValueError(f"invalid model integration error code: {self.code!r}")
        if not self.phase or not self.message:
            raise ValueError("model integration finding phase and message must be non-empty")
        if self.severity is FindingSeverity.ERROR and (
            not self.why_unsafe or not self.remediation
        ):
            raise ValueError("ERROR findings require why_unsafe and remediation")
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
            raise ValueError("ERROR findings require why_unsafe and remediation")

    def to_dict(self) -> dict[str, Any]:
        """Return a JSON-serializable representation."""
        payload = asdict(self)
        payload["severity"] = self.severity.value
        return payload

    def format_human(self) -> str:
        """Format a stable human-readable diagnostic block."""
        owner = f"\nowner: {self.owner_fqn}" if self.owner_fqn else ""
        facts = ""
        if self.facts:
            facts = "\nfacts:" + "".join(
                f"\n  {name}: {value}" for name, value in sorted(self.facts.items())
            )
        unsafe = f"\nwhy unsafe: {self.why_unsafe}" if self.why_unsafe else ""
        remediation = f"\nfix: {self.remediation}" if self.remediation else ""
        return (
            f"[{self.code}] {self.severity.value} phase={self.phase}: {self.message}"
            f"{owner}{facts}{unsafe}{remediation}"
        )
107
108
109
110
111
112
113
114
115
116
    """Raised when a model integration report contains one or more ERROR findings."""

    def __init__(self, findings: Iterable[ModelIntegrationFinding]) -> None:
        """Build one exception from ordered structured findings."""
        self.findings = tuple(findings)
        super().__init__("\n\n".join(finding.format_human() for finding in self.findings))


@dataclass
class ModelIntegrationReport:
129
130
131
132
133
134
135
136
137
        )

    def add(self, finding: ModelIntegrationFinding) -> None:
        """Append a finding."""
        self.findings.append(finding)

    def extend(self, findings: Iterable[ModelIntegrationFinding]) -> None:
        """Append findings from another validator."""
        self.findings.extend(findings)
137
138
139
140
141
142
143
144
145
146
        self.findings.extend(findings)

    def raise_for_errors(self) -> None:
        """Raise a structured exception when any ERROR is present."""
        if self.errors:
            raise ModelIntegrationValidationError(self.errors)

    def to_dict(self) -> dict[str, Any]:
        """Return the stable JSON payload."""
        return {
hyper_parallel/tools/model_integration/structure_inventory.py
46
47
48
49
50
51
52
53
54
55
56
    reason: str

    def to_dict(self) -> dict[str, Any]:
        """Return a JSON-serializable representation."""
        payload = asdict(self)
        payload["category"] = self.category.value
        return payload


@dataclass
class ModelInventory:
80
81
82
83
84
85
86
87
88
89

def _layout(value: Any) -> dict[str, Any] | None:
    layout = getattr(value, "layout", None)
    if layout is None:
        placements = getattr(value, "placements", None)
        mesh = getattr(value, "device_mesh", None)
    else:
        placements = getattr(layout, "placements", None)
        mesh = getattr(layout, "mesh", None)
    if placements is None and mesh is None:
87
88
89
90
91
92
93
94
95
        placements = getattr(layout, "placements", None)
        mesh = getattr(layout, "mesh", None)
    if placements is None and mesh is None:
        return None
    return {
        "mesh_dim_names": list(getattr(mesh, "mesh_dim_names", ()) or ()),
        "mesh_shape": list(getattr(getattr(mesh, "mesh", None), "shape", ()) or ()),
        "placements": [repr(placement) for placement in (placements or ())],
    }
 97
 98
 99
100
101
102
103
104
105
106
107
108
109

def _forward_signature(module: Any) -> str | None:
    forward = getattr(module, "forward", None)
    if not callable(forward):
        return None
    try:
        return str(inspect.signature(forward))
    except (TypeError, ValueError):
        return None


def _config_dict(model: Any) -> dict[str, Any]:
    config = getattr(model, "config", None)
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def _config_dict(model: Any) -> dict[str, Any]:
    config = getattr(model, "config", None)
    if config is None:
        return {}
    to_dict = getattr(config, "to_dict", None)
    if callable(to_dict):
        return to_dict()
    if hasattr(config, "__dict__"):
        return {
            name: value
            for name, value in vars(config).items()
            if isinstance(value, (str, int, float, bool, type(None), list, dict))
        }
    return {"repr": repr(config)}


def build_model_inventory(
    model: Any,
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

    non_persistent_ids = set()
    for module in model.modules():
        for name in getattr(module, "_non_persistent_buffers_set", set()):
            buffer_value = getattr(module, name, None)
            if buffer_value is not None:
                non_persistent_ids.add(id(buffer_value))
    buffer_aliases: dict[int, list[str]] = {}
    buffer_by_id = {}
    for buffer_fqn, buffer in model.named_buffers(remove_duplicate=False):
        buffer_aliases.setdefault(id(buffer), []).append(buffer_fqn)
        buffer_by_id[id(buffer)] = buffer
    buffers = []
    for buffer_id, fqns in sorted(buffer_aliases.items(), key=lambda item: min(item[1])):
        buffer = buffer_by_id[buffer_id]
        buffers.append(
            {
                "fqn": min(fqns),
                "aliases": sorted(fqns),
                "shape": _shape(buffer),
213
214
215
216
217
218
219
220
221
def inspect_local_model_assets(model_path: str | Path) -> dict[str, Any]:
    """Inspect local config and checkpoint indices without loading tensors."""
    root = Path(model_path).expanduser().resolve()
    if not root.is_dir():
        raise ValueError(f"model asset path is not a directory: {root}")
    config_path = root / "config.json"
    config = json.loads(config_path.read_text(encoding="utf-8")) if config_path.is_file() else {}
    checkpoint = {
        "keys": [],
226
227
228
229
230
231
232
233
234
235
236
237

    def lfs_pointer(path: Path) -> dict[str, Any] | None:
        """Parse one small Git-LFS pointer without resolving its payload."""
        if path.stat().st_size > 1024:
            return None
        lines = path.read_text(encoding="utf-8").splitlines()
        if not lines or lines[0] != "version https://git-lfs.github.com/spec/v1":
            return None
        fields = {}
        for line in lines[1:]:
            name, _, value = line.partition(" ")
            fields[name] = value
237
238
239
240
241
242
243
244
245
            fields[name] = value
        oid = fields.get("oid", "")
        size = fields.get("size", "")
        if not oid.startswith("sha256:") or not size.isdigit():
            raise ValueError(f"invalid Git-LFS pointer: {path}")
        return {
            "oid": oid,
            "payload_size": int(size),
            "payload_available": False,
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
        if pointer is not None:
            checkpoint["lfs_pointers"][index_path.name] = pointer
            checkpoint["metadata"][index_path.name] = {"git_lfs_pointer": True}
            continue
        try:
            index = json.loads(index_path.read_text(encoding="utf-8"))
        except json.JSONDecodeError as exc:
            raise ValueError(f"checkpoint index is neither JSON nor a Git-LFS pointer: {index_path}") from exc
        weight_map = index.get("weight_map", {})
        checkpoint["weight_map"].update(weight_map)
        checkpoint["metadata"][index_path.name] = index.get("metadata", {})
    for weight_path in sorted(root.glob("*.safetensors")):
        pointer = lfs_pointer(weight_path)
        if pointer is not None:
            checkpoint["lfs_pointers"][weight_path.name] = pointer
283
284
285
286
287
288
289
290
291
292
293
294
295
296

    def classify(path: str, expected: Any, actual: Any) -> tuple[DifferenceCategory, str]:
        """Classify one inventory difference into its owning integration layer."""
        if classifier is not None:
            return classifier(path, expected, actual)
        if path.startswith("parameters.") or path.startswith("buffers."):
            return DifferenceCategory.MODEL_ADAPTER, "state layout/name requires explicit adapter mapping"
        if path.startswith("modules."):
            return DifferenceCategory.COMPONENT_REPLACEMENT, "module structure requires replacement review"
        return DifferenceCategory.CUSTOM_MODEL, "configuration differs from the authoritative architecture"

    for collection_name in ("modules", "parameters", "buffers"):
        reference_entries = {
            entry["fqn"]: entry for entry in getattr(reference, collection_name)
301
302
303
304
305
306
307
308
309
        for fqn in sorted(set(reference_entries) | set(candidate_entries)):
            expected = reference_entries.get(fqn)
            actual = candidate_entries.get(fqn)
            if expected == actual:
                continue
            path = f"{collection_name}.{fqn}"
            category, reason = classify(path, expected, actual)
            differences.append(
                InventoryDifference(path, expected, actual, category, reason)
308
309
310
311
312
313
314
315
316
            differences.append(
                InventoryDifference(path, expected, actual, category, reason)
            )
    if reference.config != candidate.config:
        category, reason = classify("config", reference.config, candidate.config)
        differences.append(
            InventoryDifference("config", reference.config, candidate.config, category, reason)
        )
    return differences
hyper_parallel/tools/model_integration/tensor_probes.py
85
86
87
88
89
90
91
92
93
            shard_placements.append(placement)
    if not shard_placements:
        return True
    if len(shard_placements) != 1:
        return False
    placement = shard_placements[0]
    return getattr(placement, "dim", None) == 0 and not hasattr(
        placement,
        "split_factor",
165
166
167
168
169
170
171
172
173
        _local_shards_follow_flattened_order(tensor)
        and sum(shard["numel"] for shard in shards) <= _SMALL_TENSOR_NUMEL
        and all(shard["values"] is not None for shard in shards)
    ):
        return None
    return [value for shard in shards for value in shard["values"]]


def _global_shard_summary(
211
212
213
214
215
216
217
218
    """Build one global summary while allowing rank-local lazy state absence."""
    local_summary = None if tensor is None else _local_tensor_summary(tensor)
    shards, rank_summaries = _gather_shard_summaries(local_summary)
    if not shards:
        return None
    shards.sort(key=lambda item: item["shard"])
    return _global_shard_summary(name, tensor, shards, rank_summaries)

235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
            mesh_context: Any = None,
            cp_replicated_forward_fields: Iterable[str] = (),
    ) -> None:
        """Configure mesh-aware canonicalization for one Trainer run."""
        self.mesh_context = mesh_context
        self.cp_replicated_forward_fields = set(cp_replicated_forward_fields)
        self._entries: list[dict[str, Any]] = []
        self._micro_steps = 0

    def begin_step(self) -> None:
        """Reset the current optimizer-step identity."""
        self._entries = []
        self._micro_steps = 0

    @staticmethod
    def _tensor_leaves(value: Any) -> list[Any]:
        """Find tensors inside mappings, sequences, and metadata dataclasses."""
        if torch.is_tensor(value):
            return [value]
        if dataclasses.is_dataclass(value) and not isinstance(value, type):
            return [
                tensor
                for dataclass_field in dataclasses.fields(value)
                for field_value in (getattr(value, dataclass_field.name),)
                for tensor in InputIdentityRecorder._tensor_leaves(field_value)
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
                for dataclass_field in dataclasses.fields(value)
                for field_value in (getattr(value, dataclass_field.name),)
                for tensor in InputIdentityRecorder._tensor_leaves(field_value)
            ]
        if isinstance(value, Mapping):
            return [
                tensor
                for name in sorted(value)
                for tensor in InputIdentityRecorder._tensor_leaves(value[name])
            ]
        if isinstance(value, (list, tuple)):
            return [
                tensor
                for child in value
                for tensor in InputIdentityRecorder._tensor_leaves(child)
            ]
        return []

    def record(self, model_inputs: Mapping[str, Any], loss_inputs: Mapping[str, Any]) -> None:
        """Add model/loss tensor fields without changing their device or values."""
        for owner, values in (("model", model_inputs), ("loss", loss_inputs)):
            for field_name in sorted(values):
                flattened = self._tensor_leaves(values[field_name])
                for value_index, value in enumerate(flattened):
                    local = value.detach()
                    to_local = getattr(local, "to_local", None)
                    if callable(to_local):
                        local = to_local()
                    self._entries.append(
                        {
                            "micro_step": self._micro_steps,
                            "owner": owner,
                            "field": field_name,
294
295
296
297
298
299
300
301
302
303
304
                            ),
                            "tensor": local.cpu().contiguous(),
                        }
                    )
        self._micro_steps += 1

    def _coordinates(self) -> dict[str, int]:
        return {
            name: int(getattr(self.mesh_context, f"{name}_rank", 0))
            for name in ("dp", "cp", "tp", "pp")
        }
321
322
323
324
325
326
327
328
329
            or "coordinates" not in payload
            or "entries" not in payload
        ]
        if malformed_ranks:
            raise RuntimeError(
                "input-identity collective received non-input payloads from ranks "
                f"{malformed_ranks}; parameter probes must execute a rank-consistent "
                "collective sequence"
            )
369
370
371
372
373
374
375
376
377
378
379
380
381
382
        cp_replicated = all(replicated for _, _, replicated in ordered_shards)
        if cp_replicated and any(
                not torch.equal(tensor, replica) for replica in ordered[1:]
        ):
            cp_replicated = False
        if len(ordered) > 1 and tensor.ndim > 0 and not cp_replicated:
            try:
                tensor = torch.cat(ordered, dim=-1)
            except RuntimeError:
                tensor = torch.cat([value.reshape(-1) for value in ordered])
        return tensor

    @classmethod
    def _entry_component_hashes(
393
394
395
396
397
398
399
400
401
                component_hashes.extend(
                    f"{field_identity}:{cls._hash_tensor(value)}" for value in values
                )
            else:
                component_hashes.append(f"{field_identity}:{cls._hash_tensor(tensor)}")
        return component_hashes

    @classmethod
    def _canonical_global_hash(cls, rank_payloads: list[dict[str, Any]]) -> str:
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
        return hasher.hexdigest()

    def finish_step(self) -> dict[str, Any]:
        """Return rank and global digests for the optimizer step."""
        local_payload = {
            "coordinates": self._coordinates(),
            "entries": self._entries,
        }
        rank_payloads = [local_payload]
        if dist.is_available() and dist.is_initialized():
            rank_payloads = [None] * dist.get_world_size()
            dist.all_gather_object(rank_payloads, local_payload)
        return {
            "global_sha256": self._canonical_global_hash(rank_payloads),
            "rank_count": len(rank_payloads),
            "micro_steps": self._micro_steps,
        }
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
                        "optimizer_state",
                    ),
                )
            for probe in declared:
                if fnmatch.fnmatchcase(parameter_name, probe.match):
                    probes[parameter_name] = probe
        return dict(sorted(probes.items()))

    def _validate_required_patterns(self) -> None:
        for probe in self.validation_spec.parameter_probes if self.validation_spec else ():
            if probe.required and not any(
                fnmatch.fnmatchcase(name, probe.match) for name in self._named_parameters
            ):
                raise ValueError(
                    f"required parameter probe {probe.match!r} matched no final parameter"
                )

    @property
498
499
500
501
502
503
504
505
506

    @property
    def watched_parameter_names(self) -> tuple[str, ...]:
        """Return stable final FQNs in capture order."""
        return tuple(self._probes)

    def begin_step(self, step: int) -> None:
        """Start evidence for one optimizer step."""
        self._step = step
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
        if stage in ("model_before", "model_after_copy_back"):
            return parameter
        if stage in ("main_before", "main_after_optimizer"):
            return main_parameter
        if stage in ("main_grad_before_clip", "main_grad_after_clip"):
            main_gradient = getattr(parameter, "main_grad", None)
            return main_gradient if main_gradient is not None else getattr(main_parameter, "grad", None)
        return None

    def _optimizer_state(self, parameter: Any) -> dict[str, Any]:
        main_parameter = (
            None if parameter is None else getattr(parameter, "main_param", parameter)
        )
        state = (
            {} if main_parameter is None
            else optimizer_state_for_parameter(self.optimizer, main_parameter)
        )
        state_names = _global_tensor_state_names(
            name for name, value in state.items() if torch.is_tensor(value)
        )
        summaries = {}
        for name in state_names:
            value = state.get(name)
            summary = _tensor_summary(name, value if torch.is_tensor(value) else None)
            if summary is not None:
                summaries[name] = summary
        return summaries

    def capture(self, stage: str) -> None:
        """Capture global summaries and persist one canonical rank-zero copy."""
        records = []
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
        """Capture global summaries and persist one canonical rank-zero copy."""
        records = []
        for parameter_name, probe in self._probes.items():
            if stage not in probe.stages:
                continue
            parameter = self._named_parameters.get(parameter_name)
            if stage == "optimizer_state":
                summary = self._optimizer_state(parameter)
                if not summary:
                    continue
                record = {"name": parameter_name, "optimizer_state": summary}
            else:
                tensor = None if parameter is None else self._stage_tensor(parameter, stage)
                record = _tensor_summary(parameter_name, tensor)
                if record is None:
                    continue
                if not probe.check_replica_consistency:
                    record.pop("replica", None)
            record.update(
                {
                    "step": self._step,
                    "stage": stage,
hyper_parallel/trainer/base.py
293
294
295
296
297
298
299
300
        self._build_dataloader()
        self._compute_train_iters()

        self._build_optimizer()
        self.model_integration.attach_optimizer(self.optimizer)
        self._build_lr_scheduler()
        self._build_training_context()
        self._init_callbacks()
382
383
384
385
386
387
388
389
390
            model_part
            for model_part in self.model_parts
            if isinstance(model_part, HSDPModule)
        ]
        self.model_integration = model_integration_runtime.build_model_integration_session(
            self.model,
            self.config,
            self.global_rank,
            self.mesh,
388
389
390
391
392
393
394
395
396
            self.config,
            self.global_rank,
            self.mesh,
        )
        self.fsdp_runtime_diagnostics = (
            fsdp_runtime.build_fsdp_runtime_diagnostics(self.hsdp_model_parts)
            if getattr(self.config.debug, "check_fsdp_runtime", False)
            else None
        )
457
458
459
460
461
462
463
464
465
            setattr(self, f"{split_name}_batch_sampler", batch_sampler)

    def attach_model_integration_data_pipeline(self) -> None:
        """Attach the final data pipeline through one shared Trainer path."""
        self.model_integration.attach_data_pipeline(
            runtime_adapter=getattr(self.get_batch, "runtime_input_adapter", None),
            get_batch=self.get_batch,
            model_assets=tuple(self.model_assets),
        )
514
515
516
517
518
519
520
521
522
523
524
            self.optimizer = _attach_optimizer_swap(self.optimizer, config.optimizer.swap)

    def _build_lr_scheduler(self):
        config: TrainerConfig = self.config
        scheduler_iters = config.training.lr_scheduler_iters or self.train_iters
        if scheduler_iters <= 0:
            raise ValueError("training.lr_scheduler_iters must be positive when configured")
        lr_scheduler = config.lr_scheduler.build(
            optimizer=self.optimizer,
            train_iters=scheduler_iters,
        )
707
708
709
710
711
712
713
714
715
            if callable(prepare_model_inputs):
                micro_batch = prepare_model_inputs(micro_batch, loss_inputs)
            if channel_loss_callback is not None:
                channel_loss_callback.strip_model_inputs(micro_batch)
            self.model_integration.record_batch(micro_batch, loss_inputs)

            model_fwd_context = (
                self.model_fwd_context()
                if callable(self.model_fwd_context)
763
764
765
766
767
768
769
770
771
        self._configure_fsdp_gradient_sync(micro_step, num_micro_steps)

    def begin_fsdp_runtime_diagnostics(self, micro_step: int) -> None:
        """Begin observing one FSDP forward/backward micro-step when enabled."""
        if self.fsdp_runtime_diagnostics is not None:
            self.fsdp_runtime_diagnostics.begin_micro_step(
                self.state.global_step,
                micro_step,
            )
772
773
774
775
776
777
778
779
780
781
782
783

    def validate_fsdp_runtime_before_optimizer(self) -> None:
        """Fail before optimizer updates when FSDP prefetch state is unsafe."""
        if self.fsdp_runtime_diagnostics is not None:
            self.model_integration.record_fsdp_trace(
                self.fsdp_runtime_diagnostics.trace_evidence()
            )
            self.fsdp_runtime_diagnostics.validate_before_optimizer_step()

    def prepare_optimizer_step(self) -> Any:
        """Run pre-optimizer diagnostics and clipping in their required order."""
        self.model_integration.after_backward_before_clip()
807
808
809
810
811
812
813
814
815
816
817
818
    def step_optimizers_and_schedulers(self) -> None:
        """Step every optimizer and scheduler, then expose the final state."""
        optimizers = self.optimizer if isinstance(self.optimizer, list) else [self.optimizer]
        for optimizer in optimizers:
            with SkipDTensorDispatch(no_skip={torch.zeros_like}):
                optimizer.step()
            optimizer.zero_grad()
        self.model_integration.after_optimizer()

        schedulers = (
            self.lr_scheduler
            if isinstance(self.lr_scheduler, list)
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
            scheduler.step()

    def _end_model_integration_step(self, metrics: Dict[str, Any]) -> None:
        """Persist structured step metrics and global input identity."""
        if not self.model_integration.runtime_enabled:
            return
        resolved_metrics = dict(metrics)
        optimizers = self.optimizer if isinstance(self.optimizer, list) else [self.optimizer]
        learning_rates = []
        for optimizer in optimizers:
            inner = getattr(optimizer, "optimizer", optimizer)
            leaves = getattr(inner, "chained_optimizers", (inner,))
            for leaf in leaves:
                learning_rates.extend(
                    group.get("lr")
                    for group in getattr(leaf, "param_groups", ())
                    if group.get("lr") is not None
                )
        if learning_rates:
            resolved_metrics["lr"] = float(learning_rates[0])
        token_counts = getattr(self, "step_token_counts", {})
        token_values = []
        for value in token_counts.values():
            token_values.append(float(value.item() if torch.is_tensor(value) else value))
        if token_values:
            resolved_metrics["tokens"] = sum(token_values)
        resolved_metrics["samples"] = self.config.training.global_batch_size
        self.model_integration.end_step(resolved_metrics)

    def train_step(
        self,
        data_iterator: Any,
856
857
858
859
860
861
862
863
        micro_batches: List[Dict[str, Any]] = next(data_iterator)
        self.state.global_step += 1

        self.on_step_begin(micro_batches=micro_batches)
        self.model_integration.begin_step(self.state.global_step)

        # Forward and backward for each micro batch
        synchronize()
871
872
873
874
875
876
877
878
879
        # forward and backward pass with gradient_accumulationsteps
        for micro_step, micro_batch in enumerate(micro_batches):
            self.model_reshard(micro_step, num_micro_steps)
            self._configure_fsdp_gradient_sync(micro_step, num_micro_steps)
            self.begin_fsdp_runtime_diagnostics(micro_step)
            loss: torch.Tensor
            loss_dict: Dict[str, torch.Tensor]
            # token num for fixed_ce_loss in postforward
            self.current_token_counts = count_loss_token(micro_batch)
882
883
884
885
886
887
888
889
890
891
892
893
894
            total_loss += loss.item()
            for k, v in loss_dict.items():
                total_loss_dict[k] += v.item()

        grad_norm = self.prepare_optimizer_step()
        self.step_optimizers_and_schedulers()

        grad_norm_value = grad_norm.item() if isinstance(grad_norm, torch.Tensor) else float(grad_norm)
        self._end_model_integration_step({"loss": total_loss, "grad_norm": grad_norm_value})
        self.on_step_end(loss=total_loss, loss_dict=total_loss_dict, grad_norm=grad_norm_value)
        return {
            "loss": total_loss,
            "grad_norm": grad_norm_value,
hyper_parallel/trainer/callbacks/checkpoint_callback.py
270
271
272
273
274
275
276
277
278
279
280
            )
        if self._save_train_state:
            checkpoint_state["extra_state"] = self._collect_extra_state(state)

        model_integration = getattr(self.trainer, "model_integration", None)
        if model_integration is not None:
            model_integration.capture_checkpoint_payload(
                "before_save",
                save_dir,
                checkpoint_state,
            )
360
361
362
363
364
365
366
367
368
            extra_state_skeleton=extra_state_skeleton,
        )
        model_integration = getattr(self.trainer, "model_integration", None)
        if model_integration is not None:
            model_integration.capture_checkpoint_payload(
                "after_load",
                restore_path,
                checkpoint_state,
            )
hyper_parallel/trainer/config/data.py
41
42
43
44
45
46
47
48
49
50
    data_transform: Optional[Target[Any]] = None

    def build(self, **runtime_kwargs: Any) -> Any:
        """Build the Dataset target with runtime Trainer arguments."""
        dataset = self.target.build(**runtime_kwargs)
        return dataset

    def __getattr__(self, name: str) -> Any:
        """Expose configured Dataset options through the wrapped target."""
        return getattr(self.target, name)
81
82
83
84
85
86
87
88
89
        """Serialize components in their compact nested YAML shape."""
        config = self.target.to_dict()
        config["collate_fn"] = _serialize_config_value(self.collate_fn)
        config["get_batch"] = _serialize_config_value(self.get_batch)
        config["sampler_type"] = self.sampler_type
        config["data_rearrange_map"] = _serialize_config_value(self.data_rearrange_map)
        config["data_sharding"] = self.data_sharding
        config["use_background_prefetcher"] = self.use_background_prefetcher
        return config
hyper_parallel/trainer/config/parallelism.py
71
72
73
74
75
76
77
78
79

    def __post_init__(self) -> None:
        """Validate the mutually exclusive layer-count and layer-index selectors."""
        if self.source not in ("default", "model_adapter_safe_regions"):
            raise ValueError(
                "activation_checkpoint.selection.source must be default or "
                "model_adapter_safe_regions"
            )
        selectors = (self.layer_count is not None, self.layer_indices is not None)
100
101
102
103
104
105
106
107
108
            )
        if self.layer_indices is None:
            return
        if not isinstance(self.layer_indices, list) or not self.layer_indices:
            raise ValueError(
                "activation_checkpoint.selection.layer_indices must be a "
                "non-empty list of non-negative integers"
            )
        if any(
108
109
110
111
112
113
114
115
116
        if any(
            isinstance(index, bool) or not isinstance(index, int) or index < 0
            for index in self.layer_indices
        ):
            raise ValueError(
                "activation_checkpoint.selection.layer_indices must contain "
                "only non-negative integers"
            )
        if len(set(self.layer_indices)) != len(self.layer_indices):
hyper_parallel/trainer/config/resolver.py
166
167
168
169
170
171
172
173
174


def _require_none_allowed(annotation: object, *, path: str) -> None:
    """Accept ``None`` only when the annotation permits it."""
    if annotation is _NONE_TYPE or (
        _is_union(annotation) and _NONE_TYPE in get_args(annotation)
    ):
        return None
    raise ConfigResolutionError(path, f"expected {_annotation_name(annotation)}, got None")
532
533
534
535
536
537
538
539
540
            _resolve_nested_target_nodes(item, path=f"{path}[{index}]")
            for index, item in enumerate(value)
        ]
    if isinstance(value, tuple):
        return tuple(
            _resolve_nested_target_nodes(item, path=f"{path}[{index}]")
            for index, item in enumerate(value)
        )
    return value
630
631
632
633
634
635
636
637
638
639
640
641
    if not isinstance(node, Mapping):
        raise ConfigResolutionError(path, "DataLoader configuration must be a YAML mapping")

    target_node = dict(node)
    if "dataloader_type" in target_node:
        raise _fail(f"{path}.dataloader_type", "renamed to sampler_type")
    if "batch_adapter" in target_node:
        raise _fail(
            f"{path}.batch_adapter",
            "removed by the Omni data lifecycle; use dataset.data_transform for "
            "sample/batch encoding and dataloader.get_batch.runtime_input_adapter "
            "for model-owned forward metadata",
641
642
643
644
645
646
647
648
649
            "for model-owned forward metadata",
        )
    collate_node = target_node.pop("collate_fn", None)
    get_batch_node = target_node.pop("get_batch", None)
    sampler_type = normalize_value(
        target_node.pop("sampler_type", "single"),
        Literal["single", "cyclic"],
        path=f"{path}.sampler_type",
    )
652
653
654
655
656
657
658
659
660
        target_node.pop("data_sharding", False),
        bool,
        path=f"{path}.data_sharding",
    )
    use_background_prefetcher = normalize_value(
        target_node.pop("use_background_prefetcher", False),
        bool,
        path=f"{path}.use_background_prefetcher",
    )
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
    target_node = dict(node)
    model_assets_node = target_node.pop("model_assets", {})
    data_transform_node = target_node.pop("data_transform", None)
    target = _resolve_target(target_node, path=path)
    if isinstance(model_assets_node, Mapping) and "_target_" in model_assets_node:
        model_assets = _resolve_target(
            model_assets_node,
            path=f"{path}.model_assets",
        )
    else:
        model_assets = resolve_component(
            model_assets_node,
            annotation=ModelAssetsConfig,
            path=f"{path}.model_assets",
        )
707
708
709
710
711
712
713
714
715
716
717
718
719
720
            data_transform_node,
            path=f"{path}.data_transform",
        )
    )
    dataset_config = DatasetConfig(
        target=target,
        model_assets=model_assets,
        data_transform=data_transform,
    )
    return dataset_config


def _resolve_optimizer_config(node: object, *, path: str) -> OptimizerConfig:
    """Resolve an optimizer target with its precision and swap policies."""
hyper_parallel/trainer/runtime/fsdp.py
67
68
69
70
71
72
73
74
75

    @property
    def unit_count(self) -> int:
        """Return the number of observed HSDP modules."""
        return len(self._module_by_name)

    def _discover_units(self, hsdp_model_parts: List[Any]) -> None:
        """Build stable names for HSDP modules without importing backend internals."""
        multiple_parts = len(hsdp_model_parts) > 1
79
80
81
82
83
84
85
86
87
88
89
                scheduler = getattr(module, "hsdp_scheduler", None)
                if scheduler is None or id(module) in self._name_by_module_id:
                    continue
                if part_prefix and module_fqn:
                    module_name = f"{part_prefix}.{module_fqn}"
                elif part_prefix:
                    module_name = part_prefix
                else:
                    module_name = module_fqn or "<root>"
                self._module_by_name[module_name] = module
                self._name_by_module_id[id(module)] = module_name
109
110
111
112
113
114
115
116
117
118
119
120
        def record_forward(module: Any, inputs: Any) -> None:
            """Record one observed module forward entry."""
            del inputs
            if self._active_trace is None:
                return
            scheduler = getattr(module, "hsdp_scheduler", None)
            scheduler_ctx = getattr(scheduler, "scheduler_ctx", None)
            if getattr(scheduler_ctx, "root_bp_state", False):
                return
            self._active_trace.forward.append(module_name)

        return record_forward
127
128
129
130
131
132
133
134
135
            del inputs
            scheduler = getattr(module, "hsdp_scheduler", None)
            scheduler_ctx = getattr(scheduler, "scheduler_ctx", None)
            if getattr(scheduler_ctx, "root_bp_state", False):
                return
            output_tensors = [
                output
                for output in tree_flatten(outputs)[0]
                if torch.is_tensor(output) and output.requires_grad
134
135
136
137
138
139
140
141
                for output in tree_flatten(outputs)[0]
                if torch.is_tensor(output) and output.requires_grad
            ]
            if not output_tensors:
                return

            fired = [False]
            hook_handles = []
223
224
225
226
227
228
229
230
231
232
233
234
235
        pending = []
        seen_schedulers = set()
        for module_name, scheduler in self._scheduler_by_name.items():
            if id(scheduler) in seen_schedulers:
                continue
            seen_schedulers.add(id(scheduler))
            state = getattr(scheduler, "hsdp_state", None)
            if state is None:
                continue

            param_group = getattr(state, "param_group", None)
            for bucket_index, bucket in enumerate(
                getattr(param_group, "all_gather_buckets", ())
274
275
276
277
278
279
280
281
282

    def _find_bypassed_child_units(self) -> list[str]:
        """Find child FSDP parameters used without executing their owner module."""
        if not self._traces:
            return []
        forward_counts = defaultdict(int)
        for trace in self._traces:
            for module_name in trace.forward:
                forward_counts[module_name] += 1
332
333
334
335
336
337
338
339
340
            )

    def trace_evidence(self) -> dict[str, Any]:
        """Return stable execution-order evidence for structured reports."""
        return {
            "units": sorted(self._module_by_name),
            "micro_steps": [
                {
                    "global_step": trace.global_step,
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
def build_fsdp_runtime_diagnostics(
    hsdp_model_parts: List[Any],
) -> Optional[FSDPRuntimeDiagnostics]:
    """Create diagnostics for Torch Trainer model parts, or return ``None``."""
    if not hsdp_model_parts:
        return None
    diagnostics = FSDPRuntimeDiagnostics(hsdp_model_parts)
    if diagnostics.unit_count == 0:
        diagnostics.close()
        return None
    logger.info(
        "Enabled Trainer FSDP runtime diagnostics for %d modules",
        diagnostics.unit_count,
    )
    return diagnostics


def model_reshard(
    hsdp_model_parts: List[Any],
hyper_parallel/trainer/runtime/memory.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97


def reset_peak_device_memory() -> None:
    """Reset peak allocator counters for one measured training step."""
    if IS_CUDA_AVAILABLE or IS_NPU_AVAILABLE:
        get_torch_device().reset_peak_memory_stats()


def peak_device_memory() -> Dict[str, Optional[int]]:
    """Return peak allocated/reserved bytes, or nulls for CPU execution."""
    if not (IS_CUDA_AVAILABLE or IS_NPU_AVAILABLE):
        return {
            "peak_memory_allocated_bytes": None,
            "peak_memory_reserved_bytes": None,
        }
    device = get_torch_device()
    return {
        "peak_memory_allocated_bytes": int(device.max_memory_allocated()),
        "peak_memory_reserved_bytes": int(device.max_memory_reserved()),
    }
hyper_parallel/trainer/runtime/model_integration.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67


def _checkpoint_event_name(event: str, checkpoint_path: str) -> str:
    """Build a filesystem-safe event name that preserves checkpoint identity."""
    checkpoint_name = Path(checkpoint_path).name
    safe_name = "".join(
        character if character.isalnum() or character in "._-" else "_"
        for character in checkpoint_name
    )
    return f"{event}_{safe_name}" if safe_name else event


class ModelIntegrationSession(Protocol):
    """Trainer-facing lifecycle shared by disabled and enabled sessions."""
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    producer_keys: tuple[str, ...],
    consumer_keys: tuple[str, ...],
) -> dict[str, Any]:
    """Normalize trace identities and classify shared-state contract failures."""
    state_indices: dict[Any, int] = {}
    normalized_events = []
    for event in events:
        state_index = state_indices.setdefault(event["state_id"], len(state_indices))
        normalized_events.append({**event, "state_id": state_index})

    published = set()
    duplicate_publications = []
    missing_sources = []
    for event in normalized_events:
        identity = (event["state_id"], event["key"], event["source_layer"])
        if event["action"] == "publish":
            if identity in published:
                duplicate_publications.append(identity)
            published.add(identity)
        elif identity not in published:
            missing_sources.append(event)

    produced_keys = {
        event["key"] for event in normalized_events if event["action"] == "publish"
    }
    consumed_keys = {
        event["key"] for event in normalized_events if event["action"] == "consume"
    }
    return {
        "events": normalized_events,
        "missing_producer_keys": sorted(set(producer_keys) - produced_keys),
        "missing_consumer_keys": sorted(set(consumer_keys) - consumed_keys),
        "missing_sources": missing_sources,
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    runtime_enabled = False

    def attach_optimizer(self, optimizer: Any) -> None:
        """Ignore optimizer construction when validation is disabled."""
        del optimizer

    def attach_data_pipeline(self, **pipeline: Any) -> None:
        """Ignore data-pipeline construction when validation is disabled."""
        del pipeline

    def begin_step(self, step: int) -> None:
        """Ignore the optimizer-step boundary when validation is disabled."""
        del step

    def record_batch(
        self,
        model_inputs: Mapping[str, Any],
169
170
171
172
173
174
175
176
        model_inputs: Mapping[str, Any],
        loss_inputs: Mapping[str, Any],
    ) -> None:
        """Ignore model inputs when validation is disabled."""
        del model_inputs, loss_inputs

    def after_backward_before_clip(self) -> None:
        """Ignore the pre-clip boundary when validation is disabled."""
176
177
178
179
180
181
182
183
        """Ignore the pre-clip boundary when validation is disabled."""

    def after_clip(self, grad_norm: Any) -> None:
        """Ignore the post-clip boundary when validation is disabled."""
        del grad_norm

    def after_optimizer(self) -> None:
        """Ignore the optimizer boundary when validation is disabled."""
183
184
185
186
187
188
189
190
191
192
193
194
195
        """Ignore the optimizer boundary when validation is disabled."""

    def end_step(self, metrics: Mapping[str, Any]) -> None:
        """Ignore step metrics when validation is disabled."""
        del metrics

    def record_fsdp_trace(self, trace: Mapping[str, Any]) -> None:
        """Ignore FSDP traces when validation is disabled."""
        del trace

    def capture_checkpoint_payload(
        self,
        event: str,
196
197
198
199
200
201
202
203
204
        checkpoint_path: str,
        payload: Mapping[str, Any],
    ) -> None:
        """Ignore checkpoint payloads when validation is disabled."""
        del event, checkpoint_path, payload


class TrainerModelIntegrationSession:
    """Coordinate build checks and runtime evidence without changing execution."""
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
        global_rank: int,
        mesh_context: Any = None,
    ) -> None:
        """Validate the final model and persist integration evidence."""
        self.model = model
        self.config = config
        self.global_rank = global_rank
        self.mode = config.model_integration.mode
        configured_output = os.environ.get(_OUTPUT_DIR_ENV)
        if not configured_output:
            configured_output = str(
                Path(config.checkpoint.checkpoint_dir) / "model_integration"
            )
        self.evidence_store = EvidenceStore(configured_output)
        self.parameter_probes: Optional[ParameterProbeRecorder] = None
        self.optimizer: Any = None
        self.validation_spec = resolve_model_validation_spec(self.model)
        data_spec = self.validation_spec.data if self.validation_spec is not None else None
        self.input_identity = InputIdentityRecorder(
            mesh_context,
            cp_replicated_forward_fields=(
                () if data_spec is None else data_spec.cp_replicated_forward_fields
            ),
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
            cp_replicated_forward_fields=(
                () if data_spec is None else data_spec.cp_replicated_forward_fields
            ),
        )
        self._current_step = 0
        self._step_start_time = 0.0
        self._grad_norm_post_clip: Optional[float] = None
        self._shared_trace_token: Any = None
        self._observed_forward_fields = False
        self._modality_fields_observed = False
        self._field_ownership: dict[str, str] = {}
        self._validate_final_model()

    @property
    def runtime_enabled(self) -> bool:
        """Whether step-level precision and execution probes are enabled."""
        return self.mode == "runtime"

    def _validate_final_model(self) -> None:
        report, ownership, fsdp_units = validate_final_model(
            self.model,
            context={"trainer_config": self.config.to_dict()},
        )
        self.parameter_ownership = ownership
        if self.global_rank == 0:
            self.evidence_store.write_json("check/findings.json", report.to_dict())
            self.evidence_store.write_json("check/fsdp_units.json", fsdp_units)
            self.evidence_store.write_csv(
                "check/parameter_ownership.csv",
                (entry.to_dict() for entry in ownership),
                fieldnames=(
                    "parameter_fqn",
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
                    "global_shape",
                    "local_shape",
                ),
            )
            self.evidence_store.write_json("trainer_config.resolved.json", self.config.to_dict())
            self.evidence_store.capture_environment()
            self.evidence_store.advance_if_before(
                IntegrationState.DISCOVERED,
                ("environment.json",),
            )
            if not report.errors:
                self.evidence_store.advance_if_before(
                    IntegrationState.STRUCTURE_VALIDATED,
                    (
                        "check/findings.json",
                        "check/parameter_ownership.csv",
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
                        "check/parameter_ownership.csv",
                        "check/fsdp_units.json",
                    ),
                )
            self.evidence_store.write_json(
                "inventory/candidate.json",
                inventory_final_model(self.model),
            )
            checkpoint_spec = (
                self.validation_spec.checkpoint
                if self.validation_spec is not None
                else None
            )
            checkpoint_coverage, _ = build_checkpoint_coverage(
                self.model,
                checkpoint_spec,
            )
            self.evidence_store.write_json(
                "checkpoint/coverage.json",
                checkpoint_coverage,
            )
            self.evidence_store.write_json(
                "check/activation_checkpoint.json",
                collect_activation_checkpoint_wrappers(self.model),
            )
        report.raise_for_errors()

    def attach_optimizer(self, optimizer: Any) -> None:
        """Resolve the semantic parameter watchlist after optimizer construction."""
        self.optimizer = optimizer
        findings = validate_optimizer_layout(self.model, optimizer)
        if self.global_rank == 0:
            self.evidence_store.write_json(
                "check/optimizer_layout_initial.json",
                {
                    "status": "FAIL" if findings else "PASS",
                    "findings": [finding.to_dict() for finding in findings],
318
319
320
321
322
323
324
325
326
327
328
329
330
                    "findings": [finding.to_dict() for finding in findings],
                    "layout": snapshot_training_layout(self.model, optimizer),
                },
            )
        if findings:
            ModelIntegrationReport(findings=findings).raise_for_errors()
        if not self.runtime_enabled:
            return
        self.parameter_probes = ParameterProbeRecorder(
            self.model,
            optimizer,
            self.evidence_store,
            rank=self.global_rank,
331
332
333
334
335
336
337
338
339
            validation_spec=self.validation_spec,
            include_small_parameters_numel_le=_SMALL_PARAMETER_NUMEL,
            representative_parameter_names=self._representative_parameter_names(),
        )
        if self.global_rank == 0:
            self.evidence_store.write_json(
                "check/parameter_watchlist.json",
                {"parameters": list(self.parameter_probes.watched_parameter_names)},
            )
339
340
341
342
343
344
345
346
347
348
349
            )

    def _representative_parameter_names(self) -> tuple[str, ...]:
        """Select at least one parameter from every owner/domain/placement group."""
        representatives = {}
        for entry in self.parameter_ownership:
            key = (
                entry.owner_fqn,
                entry.gradient_domain,
                entry.source_mesh,
                entry.source_placements,
347
348
349
350
351
352
353
354
355
356
                entry.gradient_domain,
                entry.source_mesh,
                entry.source_placements,
            )
            representatives.setdefault(key, entry.parameter_fqn)
        return tuple(sorted(representatives.values()))

    def attach_data_pipeline(
            self,
            *,
358
359
360
361
362
363
364
365
366
            get_batch: Any,
            model_assets: tuple[Any, ...],
    ) -> None:
        """Validate and record the resolved data/forward-input ownership."""
        ownership, findings = validate_data_contract(
            self.validation_spec,
            runtime_adapter,
            labels_are_shifted=getattr(get_batch, "labels_are_shifted", None),
            source_type=getattr(get_batch, "source_type", None),
365
366
367
368
369
370
371
372
373
374
375
376
            labels_are_shifted=getattr(get_batch, "labels_are_shifted", None),
            source_type=getattr(get_batch, "source_type", None),
            assets=model_assets,
        )
        self._field_ownership = dict(ownership["fields"])
        if self.global_rank == 0:
            self.evidence_store.write_json("data/field_ownership.json", ownership)
            self.evidence_store.write_json(
                "data/findings.json",
                {
                    "status": "FAIL" if findings else "PASS",
                    "findings": [finding.to_dict() for finding in findings],
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
                    "status": "FAIL" if findings else "PASS",
                    "findings": [finding.to_dict() for finding in findings],
                },
            )
        if findings:
            ModelIntegrationReport(findings=findings).raise_for_errors()

    def begin_step(self, step: int) -> None:
        """Begin input and parameter evidence for one optimizer step."""
        if not self.runtime_enabled:
            return
        self._current_step = step
        self._step_start_time = time.perf_counter()
        self._grad_norm_post_clip = None
        reset_peak_device_memory()
        shared_state_spec = (
            self.validation_spec.shared_state
            if self.validation_spec is not None
            else None
        )
        if shared_state_spec is not None and shared_state_spec.begin_trace is not None:
            self._shared_trace_token = shared_state_spec.begin_trace()
        self.input_identity.begin_step()
        if self.parameter_probes is not None:
            self.parameter_probes.begin_step(step)

    def record_batch(
        self,
        model_inputs: Mapping[str, Any],
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
        Args:
            model_inputs: Keyword inputs passed to the model forward call.
            loss_inputs: Labels and masks consumed by the loss path.
        """
        if not self.runtime_enabled:
            return
        if not self._observed_forward_fields:
            findings = validate_observed_forward_fields(
                self.validation_spec,
                model_inputs,
            )
            data_spec = self.validation_spec.data if self.validation_spec is not None else None
            if data_spec is not None:
                self._modality_fields_observed = bool(
                    set(data_spec.modality_fields).intersection(model_inputs)
                )
            if self.global_rank == 0:
                actual_ownership = {
                    field_name: self._field_ownership.get(
                        field_name,
                        "source_or_get_batch",
                    )
428
429
430
431
432
433
434
435
436
                        "source_or_get_batch",
                    )
                    for field_name in sorted(model_inputs)
                }
                self.evidence_store.write_json(
                    "data/observed_forward_fields.json",
                    {
                        "status": "FAIL" if findings else "PASS",
                        "model_fields": sorted(model_inputs),
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
                        "field_ownership": actual_ownership,
                        "findings": [finding.to_dict() for finding in findings],
                    },
                )
            self._observed_forward_fields = True
            if findings:
                ModelIntegrationReport(findings=findings).raise_for_errors()
        self.input_identity.record(model_inputs, loss_inputs)

    def after_backward_before_clip(self) -> None:
        """Capture un-clipped main gradients."""
        if not self.runtime_enabled:
            return
        runtime_findings = validate_state_invariants(
            self.model,
            self.validation_spec,
            {"trainer": self, "step": self._current_step},
            phases=("runtime",),
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
            self.validation_spec,
            {"trainer": self, "step": self._current_step},
            phases=("runtime",),
        )
        if runtime_findings:
            ModelIntegrationReport(findings=runtime_findings).raise_for_errors()
        if self.parameter_probes is not None:
            self.parameter_probes.capture("main_grad_before_clip")
        if self.optimizer is not None:
            findings = validate_optimizer_layout(self.model, self.optimizer)
            if self.global_rank == 0:
                self.evidence_store.write_json(
                    f"cases/runtime/optimizer_layout_step{self._current_step}.json",
                    {
                        "status": "FAIL" if findings else "PASS",
                        "findings": [finding.to_dict() for finding in findings],
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
                        "findings": [finding.to_dict() for finding in findings],
                        "layout": snapshot_training_layout(self.model, self.optimizer),
                    },
                )
            if findings:
                ModelIntegrationReport(findings=findings).raise_for_errors()
        if self._modality_fields_observed:
            modality_gradients = modality_gradient_parameters(
                self.validation_spec,
                self.model,
            )
            if self.global_rank == 0:
                self.evidence_store.write_json(
                    f"cases/runtime/modality_gradients_step{self._current_step}.json",
                    modality_gradients,
                )
            if modality_gradients and not all(modality_gradients.values()):
                raise RuntimeError(
                    "[HP-DATA-002] multimodal inputs reached the model but one or more "
                    "declared visual parameters have missing or non-finite gradients"
                )
488
489
490
491
492
493
494
495
496
497
                )

    def record_fsdp_trace(self, trace: Mapping[str, Any]) -> None:
        """Persist rank-local runtime order before the optimizer-boundary gate."""
        if not self.runtime_enabled:
            return
        self.evidence_store.write_json(
            f"check/fsdp_runtime_rank{self.global_rank}.json",
            dict(trace),
        )
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
        )

    def after_clip(self, grad_norm: Any) -> None:
        """Capture clipped main gradients."""
        if not self.runtime_enabled:
            return
        if grad_norm is None:
            raise ValueError("runtime model integration requires the post-clip gradient norm")
        if self.parameter_probes is not None:
            self.parameter_probes.capture("main_grad_after_clip")
        self._grad_norm_post_clip = float(grad_norm)

    def after_optimizer(self) -> None:
        """Capture main/model updates and optimizer moment layouts."""
        if not self.runtime_enabled:
            return
        if self.parameter_probes is None:
            return
        self.parameter_probes.capture("main_after_optimizer")
        self.parameter_probes.capture("model_after_copy_back")
        self.parameter_probes.capture("optimizer_state")

    def end_step(self, metrics: Mapping[str, Any]) -> None:
        """Persist structured scalar and global input identity evidence."""
        if not self.runtime_enabled:
            return
        self._finish_shared_state_trace()
        input_identity = self.input_identity.finish_step()
        elapsed = time.perf_counter() - self._step_start_time
        tokens = metrics.get("tokens")
        samples = metrics.get("samples")
        memory = peak_device_memory()
        row = {
            "step": self._current_step,
            "loss": metrics.get("loss"),
            "grad_norm_pre_clip": metrics.get("grad_norm"),
            "grad_norm_post_clip": metrics.get(
541
542
543
544
545
546
547
548
549
            "tokens_per_second": None if tokens is None else float(tokens) / elapsed,
            "samples_per_second": None if samples is None else float(samples) / elapsed,
            **memory,
        }
        performance_row = {
            "step": self._current_step,
            "rank": self.global_rank,
            "step_time_seconds": elapsed,
            "tokens_per_second": row["tokens_per_second"],
549
550
551
552
553
554
555
556
557
558
559
560
561
562
            "tokens_per_second": row["tokens_per_second"],
            "samples_per_second": row["samples_per_second"],
            **memory,
        }
        self.evidence_store.append_jsonl(
            f"cases/runtime/performance_rank{self.global_rank}.jsonl",
            performance_row,
        )
        if self.global_rank == 0:
            self.evidence_store.append_jsonl("cases/runtime/metrics.jsonl", row)
            self.evidence_store.append_jsonl(
                "cases/runtime/performance.jsonl",
                performance_row,
            )
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
            )

    def _finish_shared_state_trace(self) -> None:
        """Validate model-declared shared-state publication and consumption."""
        if self._shared_trace_token is None or self.validation_spec is None:
            return
        shared_state_spec = self.validation_spec.shared_state
        if shared_state_spec is None or shared_state_spec.finish_trace is None:
            return
        events = shared_state_spec.finish_trace(self._shared_trace_token)
        self._shared_trace_token = None
        analysis = _analyze_shared_state_events(
            events,
            shared_state_spec.producer_keys,
            shared_state_spec.consumer_keys,
        )
        if self.global_rank == 0:
            self.evidence_store.write_json(
                f"cases/runtime/shared_state_step{self._current_step}.json",
                analysis,
            )
        if (
            analysis["missing_sources"]
            or analysis["missing_producer_keys"]
            or analysis["missing_consumer_keys"]
        ):
            raise RuntimeError(
                "[HP-STATE-001] shared-state producer/consumer contract failed: "
                f"missing_producers={analysis['missing_producer_keys']}, "
                f"missing_consumers={analysis['missing_consumer_keys']}, "
                f"missing_sources={analysis['missing_sources']}"
590
591
592
593
594
595
596
597
598
                f"missing_producers={analysis['missing_producer_keys']}, "
                f"missing_consumers={analysis['missing_consumer_keys']}, "
                f"missing_sources={analysis['missing_sources']}"
            )
        if analysis["duplicate_publications"]:
            raise RuntimeError(
                "[HP-STATE-002] shared-state producer replayed inside one forward: "
                f"{analysis['duplicate_publications']}"
            )
603
604
605
606
607
608
609
610
611
612
613
614
            checkpoint_path: str,
            payload: Mapping[str, Any],
    ) -> None:
        """Record versioned DCP layouts without rebuilding checkpoint chunks."""
        if not self.runtime_enabled:
            return
        event_name = _checkpoint_event_name(event, checkpoint_path)
        self.evidence_store.write_json(
            f"checkpoint/{event_name}_rank{self.global_rank}.json",
            {
                "checkpoint_path": checkpoint_path,
                "tensor_layouts": flatten_tensor_layouts(payload),
622
623
624
625
626
627
628
629
630
631
632
    global_rank: int,
    mesh_context: Any = None,
) -> TrainerModelIntegrationSession | DisabledModelIntegrationSession:
    """Build the configured validation session or a branch-free no-op session."""
    if getattr(getattr(config, "model_integration", None), "mode", "off") == "off":
        return DisabledModelIntegrationSession()
    return TrainerModelIntegrationSession(
        model=model,
        config=config,
        global_rank=global_rank,
        mesh_context=mesh_context,
hyper_parallel/trainer/text_trainer.py
57
58
59
60
61
62
63
64
65
66
67
68
        self.base._build_dataloader()

        # get_batch
        self._build_get_batch()
        self.base.attach_model_integration_data_pipeline()
        self.base._compute_train_iters()

        self.base._build_optimizer()
        self.base.model_integration.attach_optimizer(self.base.optimizer)
        self.base._build_lr_scheduler()
        self.base._build_training_context()
        self.base._init_callbacks()
249
250
251
252
253
254
255
256
257
        """
        num_micro_steps = self.base.num_micro_batches

        self.on_step_begin()
        self.base.model_integration.begin_step(self.base.state.global_step + 1)
        synchronize()

        total_loss = 0.0
        total_loss_dict = defaultdict(int)
261
262
263
264
265
266
267
268
269
            self.base.configure_fsdp_gradient_sync(
                micro_step,
                num_micro_steps,
            )
            self.base.begin_fsdp_runtime_diagnostics(micro_step)
            loss, loss_dict = self.forward_backward_step(
                data_iterator,
                num_micro_steps,
            )
271
272
273
274
275
276
277
278
279
280
            total_loss += loss.item()
            for loss_name, loss_value in loss_dict.items():
                total_loss_dict[loss_name] += loss_value.item()

        grad_norm = self.base.prepare_optimizer_step()
        self.base.step_optimizers_and_schedulers()

        # Checkpoint and logging callbacks observe the number of completed
        # optimizer updates.
        self.base.state.global_step += 1
278
279
280
281
282
283
284
285
286
        # Checkpoint and logging callbacks observe the number of completed
        # optimizer updates.
        self.base.state.global_step += 1
        grad_norm_value = float(grad_norm)
        self.base._end_model_integration_step(
            {"loss": total_loss, "grad_norm": grad_norm_value}
        )
        self.on_step_end(
            loss=total_loss,
hyper_parallel/trainer/vlm_trainer.py
19
20
21
22
23
24
25
26
27
from collections import defaultdict
from typing import Any, Dict

from hyper_parallel.data.batching import calculate_num_micro_batches
from hyper_parallel.data.omni import OmniDataTransform
from hyper_parallel.trainer.runtime.loss_aggregation import count_loss_token
from hyper_parallel.trainer.runtime.logging import create_logger
from hyper_parallel.trainer.runtime.memory import print_device_mem_info
from hyper_parallel.trainer.runtime.device import synchronize  # pylint: disable=syntax-error
55
56
57
58
59
60
61
62
63
64
65
66
        self.base._build_dataloader()

        # get_batch
        self._build_get_batch()
        self.base.attach_model_integration_data_pipeline()
        self.base._compute_train_iters()

        self.base._build_optimizer()
        self.base.model_integration.attach_optimizer(self.base.optimizer)
        self.base._build_lr_scheduler()
        self.base._build_training_context()
        self.base._init_callbacks()
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
        # target; forward the model's ``trust_remote_code`` flag so a natively
        # registered architecture such as the Kimi-K2.6 family (``kimi_k26``)
        # stays on the local implementation unless the config opts into remote
        # code.
        processor = config.dataset.model_assets.build(
            trust_remote_code=bool(
                getattr(config.model, "trust_remote_code", True)
            ),
        )
        tokenizer = getattr(processor, "tokenizer", None)
        if tokenizer is None:
            raise ValueError("dataset.model_assets must build a processor with a tokenizer")

        self.base.processor = processor
        self.base.tokenizer = tokenizer
        self.base.chat_template = getattr(processor, "chat_template", None)
        self.base.model_assets = [self.base.model_config, processor]

    def _build_data_transform(self) -> None:
        """Build the Omni encoding lifecycle from the prebuilt processor."""
        dataset_config = self.base.config.dataset
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
        dataset_config = self.base.config.dataset
        if dataset_config is None:
            raise ValueError("dataset must define a build target")
        if dataset_config.data_transform is None:
            raise ValueError("dataset.data_transform must define an Omni transform build target")
        data_transform = dataset_config.data_transform.build(
            processor=self.base.processor,
        )
        if not isinstance(data_transform, OmniDataTransform):
            raise TypeError("dataset.data_transform must build an OmniDataTransform")
        self.base.data_transform = data_transform

    def _build_collate_fn(self) -> None:
        """Build the VLM collator and gradient-accumulation batch count."""
        dataloader_config = self.base.config.dataloader
114
115
116
117
118
119
120
121
122
            global_batch_size=training_config.global_batch_size,
            micro_batch_size=training_config.micro_batch_size,
            dp_world_size=self.base.mesh.dp_size,
        )
        self.base.collate_fn = dataloader_config.collate_fn.build(
            mesh_context=self.base.mesh,
        )

    def _build_get_batch(self) -> None:
121
122
123
124
125
126
127
128
129
130
131

    def _build_get_batch(self) -> None:
        """Build the DataLoader-to-LLM batch runtime."""
        config = self.base.config
        if config.dataloader.get_batch is None:
            raise ValueError("dataloader.get_batch must define a batching runtime target")
        get_batch = config.dataloader.get_batch.build(
            mesh_context=self.base.mesh,
            device=self.base.device,
            data_config=getattr(config.dataset, "data_config", {}),
            pp_shared_data=bool(getattr(config.dataloader, "pp_shared_data", False)),
129
130
131
132
133
134
135
136
137
            device=self.base.device,
            data_config=getattr(config.dataset, "data_config", {}),
            pp_shared_data=bool(getattr(config.dataloader, "pp_shared_data", False)),
        )
        self.base.get_batch = get_batch

    @property
    def distributed_setup(self) -> Any:
        """Return the shared distributed setup."""
195
196
197
198
199
200
201
202
203
            self.base.configure_fsdp_gradient_sync(
                micro_step,
                num_micro_steps,
            )
            self.base.begin_fsdp_runtime_diagnostics(micro_step)
            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()
201
202
203
204
205
206
207
208
209
            self.base.step_token_counts = {
                name: token_count * num_micro_steps
                for name, token_count in self.base.current_token_counts.items()
            }
            loss, loss_dict = self.base.forward_backward_step(model_inputs, loss_inputs)

            # Release each device batch as soon as its backward pass completes;
            # prefetching all micro-batches must not pin them until step end.
            training_batches[micro_step] = None
224
225
226
227
228
229
230
231
232

        self.on_step_begin(
            micro_batches=[model_inputs for model_inputs, _ in training_batches]
        )
        self.base.model_integration.begin_step(self.base.state.global_step + 1)
        synchronize()

        total_loss, total_loss_dict = self._forward_backward_micro_batches(
            training_batches,
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
            training_batches,
            num_micro_steps,
        )

        grad_norm = self.base.prepare_optimizer_step()
        self.base.step_optimizers_and_schedulers()

        # Checkpoint and logging callbacks observe completed optimizer updates.
        self.base.state.global_step += 1
        grad_norm_value = float(grad_norm)
        # The base trainer owns this integration hook and the VLM subclass only
        # mirrors the base step order, so reaching the private method is intended.
        self.base._end_model_integration_step(  # pylint: disable=protected-access
            {"loss": total_loss, "grad_norm": grad_norm_value}
        )
        self.on_step_end(
            loss=total_loss,
249
250
251
252
253
254
255
256
257
258
259
260
261
            loss_dict=total_loss_dict,
            grad_norm=grad_norm_value,
        )

        step_metrics = {
            "loss": total_loss,
            "grad_norm": grad_norm_value,
        }
        return step_metrics

    def train(self) -> None:
        """Run the VLM training loop."""
        config = self.base.config