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.0% 56,83-84,88,92,96,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 5.3% 60-63,68-70,74-76,80,84-86,91,93,98,102
hyper_parallel/core/expert_parallel/expert_parallel.py 27.3% 585,589-591,597,606-608
hyper_parallel/core/expert_parallel/static_splits.py 50.0% 102,115,127-129,140-145,154-155,173-174
hyper_parallel/core/optimizer/dtensor_compat.py 30.8% 154,159,164-169,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 30.8% 108-109,111-113,115-120,122-123,125,127,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 13.6% 71-78,83,86-91,100-106,108-111,113,118-127,131-133,136-138,142,149-156,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 30.0% 33-35,43,47-49,51-54,56,58-62,64-66,68-70,72-73,100,112,126,131,135-136,140,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 0.0% 17,22,24
hyper_parallel/data/omni/build_dataset.py 0.0% 17,19-20,22-23,27,34,57-60,62-64,66-67,72-73,76,79,81-83,85-86,88-94,96,101,103-104,106-107,109,111-112,114,116-117,120
hyper_parallel/data/omni/omni_transform.py 0.0% 17-21,23,25,28,44,49-56,58,61,73,75-80,82-83,85-88,90,92,94,96,98,100,102,104,106,108-110,112-115,117-124,126-127,129-130,132-139,141-143,145-146,148-149,151-152,155,158,160-162,164,171,179-183,185-188,190-193,196,199-200,203-204,206-207,209-211,213-214,218-219,222-223,230-234,236-238,240,244,246-247,249-252,254,256-259,261,263-266,268,270-272,275
hyper_parallel/data/online/__init__.py 0.0% 17,21,25,30
hyper_parallel/data/online/iterable.py 0.0% 17,19-22,24,26-28,34,37,56-58,60,62,64-65,67,72,75,77-79,82,85-86,96-97,99-100,102,104-105,107-108,110-111,113-114,116-117,119-120,122,124,126-127,129-131,133,135,139-141,143,145-151,153,155,157-162,164,166-168,170,172,174-176,178,180-182,184,187,190,200-208,210,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,267,269-274,276,278-284,286-287,294,296,298-300,302-308,310-313,315,322-324,326,328-330,333,336,343-344,346,352-358,360-362,364-366,371-375,377,384,386-389,391-392,394-397,399-400,402,404-405,407,409,411-414,416,418-419,421-423,425-426,429
hyper_parallel/data/online/mapping.py 0.0% 17,19-23,25,27-29,35,37,40,60-62,65,68,70-71,73-74,84-90,92-93,95-96,98-99,101,103-104,106,108-112,114-117,119,121,123-125,128-129,131-132,135,138,144-148,150-152,154,156-157,159,161-165,167-174,176,181-184,186-193,195,197-200,202,208-220,222,224-230,232,234-237,239-243,245,247-249,251-252,254-260,263,266,274-279,281-286,288-292,297,299,301,303-307,309-310,312-313,315-319,321,323-325,327-332,336-338,340-342,344,346,352,356-360,364-366,369,372,379-380,382,388-392,396-400,402-405,408-416,418,420-422,424-425,430,433-434,436-437,443-446,450-451,453-462,464,468,470-471,477-478,480-487,489-497,499-500,502-510,512-517,519,521-522,524-527,529,536-542,544-545,547,553,555-556,558-561,563-566,569
hyper_parallel/data/online/source.py 0.0% 17,19-21,23,25-26,29,32,34-35,37-38,40,42-43,45-47,51,53,78-83,85-88,90-103,105-111,113,115-116,118,120-122,124-128,130,136,139,142,144-145,147,154,156-158,161-170,172,180-184,186-187,189-197,199-200,203-212,214-215,217,223-226,228-229,231-232,234,236-240,242-245,248-252,255-258,260-261,263-264,266-269,271-278,280,285-286,288-292,297-299,301-302,304-306,308-310,312-314,316-318,320-321,323-324,326,328-332,334-337,339-340,342-347,349-350,352,354-355,357-362,364-365,367-370,372-373,375-376,378,380-382,384-393,395-396,398-399,402
hyper_parallel/data/parallel/batch_parallel.py 6.7% 202,220-221,246-252,254-259,261-263,265-270,272-274,276-277,279,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 6.2% 126,131,133,141-144,165,176,294,296,397-398,401,769
hyper_parallel/data/tools/offline_preparation.py 0.0% 23,51,786
hyper_parallel/distributed/_builder/forward_rewriter.py 9.1% 343-349,484,681,689
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 23.3% 277,356,368,378,380,440,811,832-838,843-849,944,1481
hyper_parallel/distributed/_builder/rule_resolver.py 10.0% 59,120-121,123-125,129-130,132-133,136,149,159,168-174,203,205,209-215,223-225,230-231,233-234
hyper_parallel/distributed/_builder/source_shard.py 50.0% 154,361,363
hyper_parallel/distributed/activation_checkpoint.py 11.1% 776-788,792-795,803-804,807,811,815-816,819-820,831-832,835-836,846-850,854-856,858-861,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,1087,1141-1143
hyper_parallel/distributed/expert_parallel/collectives.py 32.2% 107-111,138-139,147,175-177,218-221,237,239,257-262,272,296-302,307-310,331-333,337-340,357,361,380-382,421-425,443,445,543-548,552,554-556,573-578,626-635
hyper_parallel/distributed/expert_parallel/experts.py 20.0% 184-186,206-208,219,221-222,224-227,229-231,234,240-242,247,251,254,258-262,264,266,269,272-273,282,338-339,343-345,366-371,373-379,399-401,404,407,414-415,418,443-448,451-454,456-457,462,469,473-475,478-480,483-485,487-492,522-526,530-531,534-536,540-541,560,574,623-629,631,633-634,636,638,681-682,705-706,708-709,711,713,743-746,769,774,776,816-817,824,828,830,882-883,889-890,892-894,928,930,939-940,948-950,953-955,958,964-965,983,985,993,1065,1078-1079,1156,1161-1165,1172,1189,1191-1194,1202-1204,1256-1258,1261,1263-1268,1270,1280-1288,1292-1293,1301,1304,1306,1346-1350,1352,1355,1361,1369,1372,1374
hyper_parallel/distributed/expert_parallel/recipes.py 6.2% 184,187,189-190,192,194,196,198,200-202,362-363,365,367
hyper_parallel/distributed/expert_parallel/routing.py 23.0% 218-219,223-224,226-235,239,255-257,260,269-271,306-307,311,313-315,320-322,327,329-330,350-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 11.9% 33-34,39,49-50,54-57,67-71,74-80,84,102-106,110-112,116,125-127,140-144,150,159-168,173,182,189-191,194,196-197,223-226,230,235-247,252-255,259-264,272-278,290,299,306-309,317-329,333,337,342-345,347-354,358-361,365-369,373-379,384-385,388-390,403-405,412-417,422-423,435-439,449-454,457-458,463-465,475-486,505-508,512-515,524-532,536-537,545-546,549-550,568-573,578,583-584,589,594,608-612,617-618,623,628-630,635,646-648,651,659,668-675,683-685,693-697,702-703,710,718-720,728-729,732-733,736,744,754-762,767-768,774-781,785,788-789,792
hyper_parallel/tools/model_integration/case_matrix.py 16.9% 56,60-73,78-80,83-84,88-89,92-96,98-100,103,107,110-112,117-123,126-127,131,139-143,152-159,163-172,175,184-195,204,208-212,216-217,223-228,231,239,242-245,253-256,260-265,272,280-282,290-292,298-299,306,317,326-330,334-337,346-361
hyper_parallel/tools/model_integration/checkpoint_coverage.py 18.7% 32-40,44,49-59,70-80,93-96,109-110,120,129-131,153-154,170,178-180,185-189,194,199-203,212,217
hyper_parallel/tools/model_integration/cli.py 12.5% 82-84,88-91,96-97,102-105,116-120,124-131,136-140,143-146,149-151,156,164-167,170-175,178,182,193-195,202-204,207,215-223,227,231-232,241-242,264-271,317-320,324-325,330-340,351-354,359-361,365-367,371,384-385,390-393,397-398,402-407,410-413,418-425,433-437,442-452,455-457,462-467,474-476,488,492-495,500-504,512-516,524,542,545-546,552-553,559-560,565,569-571,578,590-598,606,623-626,636,640,649-654,667,676-679,712-713,724-729,733-734,739-740,750-754,773-782,791-795,804,808-810,816-821,831-835,840-843,852-857,868-871,875,879,885,892-895,900-901,917,921,930-935,940-945,950-956,960-969,973,980-981,986-988,997-999,1004-1006,1011-1014,1018,1024,1029-1033,1038-1042,1047-1058,1063,1071-1073,1078-1085,1090-1093,1099,1105-1107,1111,1116-1120,1125-1126,1131,1133,1135-1136,1138,1160-1164,1171,1182-1191,1208,1235-1236,1239,1250-1254,1259,1272-1283,1302,1330-1335,1353-1358,1361-1362,1373,1381-1394,1400,1413-1418,1422-1427,1433,1438-1441,1446-1448,1454,1459,1465,1469-1472,1480-1488,1498,1507,1517-1522,1526,1542-1547,1553,1558-1559,1563,1568-1569,1574,1586-1587,1592-1595,1601,1612-1616,1620-1623,1630-1631,1635-1638,1645,1650-1654,1658-1659,1662-1663,1668-1671,1675-1678,1683-1685,1690-1698,1703,1707,1715-1721,1726,1743-1746,1762,1771,1787,1795,1818-1825,1829,1842,1856-1857,1861,1866-1867,1874-1888,1893-1898,1902
hyper_parallel/tools/model_integration/data_contract.py 20.0% 48-54,56-57,80-83,96-100,105,110-111,135,141,149-156,180-186,191-196,201-204
hyper_parallel/tools/model_integration/evidence_store.py 24.7% 50,55-61,69-70,74-77,81-82,88-99,103-104,111-112,116-119,123-128,137-144,148-151,158-160,168-169,171,174-177,179,183-186,188,192-195,204-215,218-219,222,229,233-236,250-255,275-281,284-286
hyper_parallel/tools/model_integration/integration_validator.py 18.0% 50,63-80,84,88-90,94-98,102-107,112-125,128-131,134,146-148,160-163,175,179-182,188-199,208-210,226-229,233,252-253,270,274-275,279-283,296-311,317-318,331,338-345,358,368-371,377-379,392-399,412,425-427,437-445,450,455-460,470
hyper_parallel/tools/model_integration/manifest.py 24.7% 38-42,46-51,54-55,74-77,82,87,92,101,109-111,115-124,128-129,133-141,150,156,161-165,174-175,181,186-190,194-198,202-208,211-212,216-221,225-226,231,235-236,240-241,247,250-252,259-271,274-277,283-295,311-316,318-331,334-336,338-339
hyper_parallel/tools/model_integration/module_parity.py 18.9% 58-64,68-73,77-85,87-89,93,100-103,116-121,131-134,137-143,164-167,182-186,189,200-201,213,222-226,230-231,233-234,237-241,245-248,252,260-269,273,280-286,300-302,307,312-314,319-321,332,345-348,351-355,366,378-387,390,401,405-413,422-424,436-439,450,460-461,476-483,486,488-490,496-497,504-505,509-510,515-517,521-522
hyper_parallel/tools/model_integration/optimizer_layout.py 17.0% 35-44,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 66.7% 35-38,75-79,82,86-88,92-95,98-100,111-112,126,133,137,141-142,146,169
hyper_parallel/tools/model_integration/structure_inventory.py 24.4% 50-52,69,73-74,78,82-85,87-91,99-105,109-116,121,140-142,151-159,172-186,198,202,215-220,227,229-242,248-268,282,284,286-292,294-295,298,301-308,311-313,316
hyper_parallel/tools/model_integration/tensor_probes.py 20.1% 43-44,49-51,56-67,70-71,76-82,85-91,99-101,122-132,136,143-149,154,164,169-170,180-184,204-207,212-217,222-227,239-242,246-247,252-255,261-262,267-268,273,277-285,298,301,308-312,317,324-325,336,341-345,352,359,366-370,373-379,387-393,397-398,402-408,412,416-420,442-452,455-457,460-462,467,472,485-488,491-492,495,502,506-508,512-520,523,526,530,533-539,543-552,554-560,568-572
hyper_parallel/trainer/base.py 19.0% 122,152-155,157-158,160-163,297,386,392,461,513-514,518-520,711,767-768,775-776,779,783-791,796,800-805,811,814,826-835,840-849,860,875,886,890
hyper_parallel/trainer/callbacks/checkpoint_callback.py 7.1% 111,274-276,362-364,373,416-417,442-443,447
hyper_parallel/trainer/config/data.py 42.9% 45-46,85,88
hyper_parallel/trainer/config/optimization.py 66.7% 70-71,74-75,78-79,94
hyper_parallel/trainer/config/parallelism.py 37.0% 74-75,79-81,86-87,92,97,101-104,108,112,116-117
hyper_parallel/trainer/config/resolver.py 12.1% 111,170,180,310-311,523-526,530-531,535-536,540,563,634-637,645,656,692-693,698,711,716,730,771-772
hyper_parallel/trainer/config/target.py 9.1% 106,135-138,142-146
hyper_parallel/trainer/config/trainer.py 100%  
hyper_parallel/trainer/config/training.py 75.0% 78-79
hyper_parallel/trainer/runtime/fsdp.py 18.4% 59-66,71,75-85,87-90,94-95,100,109,111-118,120,125,127-132,137-138,140-141,143,145-151,153-154,156,160-164,169-172,176-177,182-184,190,194-203,208-209,214,219,223-231,233-234,237-240,245-255,260,265-266,273,277-285,289-293,298-299,302,306-312,316-317,321-322,326-327,336,351-353,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:
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
            value_name: str,
            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]

    def publish_compressed_kv(self, source_layer: int, value: torch.Tensor) -> None:
        """Publish compressed K=V while preserving its autograd graph."""
        self.compressed_kv_by_source[source_layer] = value

    def require_compressed_kv(self, source_layer: int | None, consumer_layer: int) -> torch.Tensor:
        """Read compressed K=V from the consumer's configured Full layer."""
        return self._require(
            self.compressed_kv_by_source,
            source_layer,
            "compressed K=V",
            consumer_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
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
    already detached from autograd, so the DTensor object itself is preserved.
    Plain parameters retain the detached local-tensor path used by non-FSDP and
    third-party integrations.
    """
    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)
    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:
        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(
                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.
        prepared_gradient = gradient
    else:
        prepared_gradient = gradient_local.detach()
    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


class MixedPrecisionOptimizer:
    """Base composition for an optimizer that owns precision-conversion state."""
hyper_parallel/core/expert_parallel/expert_parallel.py
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595

        # --- Steps 1-2: token counts and the split vectors ---
        # Under a static routing plan the answer never changes, so it is computed once and
        # reused: that is what removes the per-layer counts a2a and its two D2H syncs.
        plan_key = static_plan_key(
            num_tokens_per_expert.shape[0], ep_size, num_local_experts,
            device=num_tokens_per_expert.device,
        )
        cached_plan = get_static_plan(plan_key)
        if cached_plan is not None:
            counts_out, input_splits, output_splits = cached_plan
        else:
            # The counts exchange is shared with the other dispatchers (see
            # ``core.utils.communication``): async with an explicit wait, because the
            # implicit cross-stream sync is NCCL-only and on HCCL the compute stream
593
594
595
596
597
598
599
600
601
            # The counts exchange is shared with the other dispatchers (see
            # ``core.utils.communication``): async with an explicit wait, because the
            # implicit cross-stream sync is NCCL-only and on HCCL the compute stream
            # could read ``counts_out`` before the collective write is visible.
            counts_out = exchange_splits_via_all_to_all(num_tokens_per_expert, ep_group)
            # counts_out shape: [ep_size * num_local_experts]
            # counts_out[r * num_local_experts + e] = tokens from rank r for expert e

            # input_splits[r] = tokens this rank sends to rank r
602
603
604
605
606
607
608
609
610
611
612
            # output_splits[r] = tokens this rank receives from rank r
            # Reshape to [ep_size, num_local_experts] and sum per rank on device;
            # a single ``tolist()`` drains the rank-sum vector to host, replacing
            # ``2 * ep_size`` scalar ``int()`` D2H syncs with 2.
            input_splits = num_tokens_per_expert.view(ep_size, num_local_experts).sum(dim=1).tolist()
            output_splits = counts_out.view(ep_size, num_local_experts).sum(dim=1).tolist()
            store_static_plan(
                plan_key, (counts_out, input_splits, output_splits),
                f"(ep={ep_size}, local_experts={num_local_experts}, "
                f"tokens/peer={input_splits[0] if input_splits else 0})",
            )
hyper_parallel/core/expert_parallel/static_splits.py
 98
 99
100
101
102
103
104
105
106


def static_splits_enabled() -> bool:
    """Return whether ``HP_EP_STATIC_SPLITS`` asked for the cached-plan fast path."""
    return _ENABLED


def static_plan_key(*parts: Any, device: Optional[Any] = None) -> Hashable:
    """Build a cache key from the quantities that define a routing plan.
111
112
113
114
115
116
117
118
119

    Returns:
        A hashable key.
    """
    return (*parts, str(device))


def get_static_plan(key: Hashable) -> Any:
    """Return the cached plan for ``key``, or ``None`` when there is none.
123
124
125
126
127
128
129
130
131
132
133

    Returns:
        The stored value, or ``None``.
    """
    if not _ENABLED:
        return None
    return _PLANS.get(key)


def store_static_plan(key: Hashable, value: Any, description: str = "") -> None:
    """Cache a plan and log it once, so the fast path is never invisible.
136
137
138
139
140
141
142
143
144
145
146
147
148
149
        key: Key from :func:`static_plan_key`.
        value: The plan to reuse (split lists, counts tensor, ...).
        description: Short human-readable note for the log line.
    """
    if not _ENABLED:
        return
    _PLANS[key] = value
    if key not in _LOGGED:
        _LOGGED.add(key)
        logger.info(
            "EP static splits: reusing one routing plan for every layer %s -- "
            "benchmark-only, requires a static router such as fix_router=True",
            description or f"(key={key})",
        )
150
151
152
153
154
155
156
157
158
159


def clear_static_plans() -> None:
    """Drop every cached plan (used by tests and by shape changes)."""
    _PLANS.clear()
    _LOGGED.clear()


def probe_mode() -> str:
    """Return the bisect probe selected by ``HP_EP_STATIC_SPLITS_PROBE``.
169
170
171
172
173
174

    Returns:
        ``"a2a"``, ``"drains"`` or ``""``.
    """
    mode = os.environ.get("HP_EP_STATIC_SPLITS_PROBE", "").strip().lower()
    return mode if mode in ("a2a", "drains") else ""
hyper_parallel/core/optimizer/dtensor_compat.py
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

def is_dtensor(tensor: Any) -> bool:
    """Return whether ``tensor`` is a native or HyperParallel DTensor."""
    dtensor_type = _LAZY_CACHE.get("DTensor") or _resolve_dtensor_union()
    return isinstance(tensor, dtensor_type)


def to_local_if_dtensor(tensor: Any) -> Any:
    """Return the local shard if `tensor` is a DTensor, otherwise return as-is."""
    return tensor.to_local() if is_dtensor(tensor) else tensor


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 (
            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
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
    """Batch already prepared Omni samples without performing sequence packing."""

    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)

        batch = {}
        for field in sample_fields:
            values = []
            for model_sample in model_samples:
                if field in model_sample:
                    values.append(model_sample[field])

            if field in OMNI_CONCAT_FIELDS:
                batch[field] = torch.cat(values, dim=0)
            else:
                batch[field] = default_collate(values)

        return batch


def build_default_collate_fn() -> Callable[[list[Any]], Any]:
    """Build ordinary PyTorch collation for already prepared samples.
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
137
138
139
140
141
142
143
144
145
146
            *,
            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)
        if collisions:
            raise ValueError(
                "[HP-DATA-001] runtime inputs cannot replace Omni model inputs: "
                f"{sorted(collisions)}"
            )
        return runtime_inputs

    @staticmethod
    def _split_model_and_loss_inputs(
            batch: Mapping[str, Any],
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    def _split_model_and_loss_inputs(
            batch: Mapping[str, Any],
    ) -> tuple[dict[str, Any], dict[str, Any]]:
        """Keep processor fields as model inputs and isolate internal metadata."""
        model_inputs = {}
        loss_inputs = {}
        for field, value in batch.items():
            if field not in OMNI_INTERNAL_FIELDS:
                model_inputs[field] = value
            if field in OMNI_LOSS_INPUT_FIELDS:
                loss_inputs[field] = value
        return model_inputs, loss_inputs


class TextParallelBatch:
    """Define the DataLoader-to-forward-batch processing boundary.
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:
 96
 97
 98
 99
100
101
102
103
104
            parallel_context: Any,
            options: Mapping[str, Any] | None = None,
    ) -> RuntimeInputContext:
        """Build runtime context from one parallel-local batch."""
        runtime_context = cls(
            local_input_shape=batch["input_ids"].shape,
            parallel_ranks={
                "tp": parallel_context.tp_rank,
                "cp": parallel_context.cp_rank,
108
109
110
111
112
113
114
115
116
                "cp": parallel_context.cp_world_size,
            },
            options={} if options is None else options,
        )
        return runtime_context


class RuntimeInputAdapter(ABC):
    """Extend a generic batch with model-owned forward inputs."""
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
            parallel_context: Any,
            options: Mapping[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Build runtime context and produce one runtime's model inputs."""
        runtime_context = RuntimeInputContext.from_batch(
            batch=batch,
            parallel_context=parallel_context,
            options=options,
        )
        runtime_inputs = self.build_runtime_inputs(
            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)

    def runtime_input_fields(self) -> tuple[str, ...]:
        """Return the forward fields exclusively owned by this adapter.
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/__init__.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# limitations under the License.
# ============================================================================
"""Unified image, video, and audio data facade."""

from hyper_parallel.data.omni.omni_transform import (
    AutoProcessorTransform,
    OmniDataTransform,
    build_auto_processor,
)
from hyper_parallel.data.omni.build_dataset import build_online_omni_mapping_dataset

__all__ = [
    "AutoProcessorTransform",
    "OmniDataTransform",
    "build_auto_processor",
    "build_online_omni_mapping_dataset",
hyper_parallel/data/omni/build_dataset.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# limitations under the License.
# ============================================================================
"""Compose an Online source with the configured Omni transform lifecycle."""

from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from hyper_parallel.data.constants import ONLINE_SPLIT_COUNT
from hyper_parallel.data.omni.omni_transform import (
    OmniDataTransform,
    _OmniTransformStrategy,
)
from hyper_parallel.data.online import (
    MappingTransformDataset,
    OnlineDataPath,
    build_online_mapping_source,
)
30
31
32
33
34
35
36
37
38
    build_online_mapping_source,
)


def build_online_omni_mapping_dataset(
    *,
    data_config: Mapping[str, Any],
    data_path: OnlineDataPath | None = None,
    transform: OmniDataTransform | None = None,
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    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."""

    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
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# limitations under the License.
# ============================================================================
"""Omni transform lifecycle and the default AutoProcessor implementation."""

import copy
import os
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import Any

from transformers import AutoProcessor

from hyper_parallel.data.constants import IGNORE_INDEX, OMNI_CP_TOKEN_FIELDS, ONLINE_SOURCE_PATH_KEY


def build_auto_processor(
        *,
        pretrained_model_name_or_path: str,
        trust_remote_code: bool = True,
        **processor_kwargs: Any,
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

    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.

    Implement ``encode_sample`` for eager encoding, ``preencode_sample`` and
    ``postencode_sample`` for deferred encoding, or only ``postencode_sample``
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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
    Packing selection and concatenation belong to the optional packing path
    rather than this transform.
    """

    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."""

    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
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
            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]


@dataclass(frozen=True)
class _OmniTransformStrategy:
    """Resolve the encoding stages implemented by an Omni transform."""

    sample_encoder: SampleEncoder | None
    selected_sample_encoder: SampleEncoder | None

    @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
277
278
279

        # 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",
    "OmniDataTransform",
    "AutoProcessorTransform",
]
hyper_parallel/data/online/__init__.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# limitations under the License.
# ============================================================================
"""Online Mapping and Iterable source adapters."""

from hyper_parallel.data.online.iterable import (
    IterableTransformDataset,
    build_online_iterable_source,
)
from hyper_parallel.data.online.mapping import (
    MappingTransformDataset,
    build_online_mapping_source,
)
from hyper_parallel.data.online.source import (
    OnlineDataPath,
    OnlinePathSpec,
)

__all__ = [
    "IterableTransformDataset",
    "MappingTransformDataset",
    "OnlineDataPath",
    "OnlinePathSpec",
hyper_parallel/data/online/iterable.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# limitations under the License.
# ============================================================================
"""Build streaming Online sources and their local/global blend policy."""

from __future__ import annotations

import random
from collections.abc import Callable, Mapping, Sequence
from dataclasses import replace
from typing import Any

from torch.utils.data import IterableDataset  # pylint: disable=forbidden-backend-import

from hyper_parallel.data.constants import ONLINE_BLEND_STRATEGIES
from hyper_parallel.data.online.source import OnlineDataPath, _OnlineSourceBuilder
from hyper_parallel.data.parallel import (
    DataLoaderParallelContext,
    build_dataset_for_dataloader,
    split_iterable_dataset_by_dp,
)
30
31
32
33
34
35
36
37
38
39
40
41
    build_dataset_for_dataloader,
    split_iterable_dataset_by_dp,
)

SampleTransform = Callable[[Any], list[Any]]


def build_online_iterable_source(
    *,
    data_config: Mapping[str, Any],
    data_path: OnlineDataPath | None = None,
    dataloader_context: DataLoaderParallelContext | None = None,
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

    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."""

    @classmethod
    def apply(cls, source_dataset: Any, transform: SampleTransform) -> Any:
        """Transform one Iterable Dataset built on a DataLoader-owning rank.

        Args:
            source_dataset: Iterable Dataset, or ``None`` on a non-owning rank.
 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
189
190
191
192
193
194

        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."""

    def __init__(
        self,
        source_datasets: Sequence[Any],
        weights: Sequence[float],
        *,
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
318
319
            "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,
        active_indices: Sequence[int],
        exhausted_once: set[int],
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
        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."""

    def __init__(
        self,
        data_path: OnlineDataPath | None,
        data_config: Mapping[str, Any],
        dataloader_context: DataLoaderParallelContext,
339
340
341
342
343
344
345
346
347
348
349
350
        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,
        *,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None = None,
    ) -> Any:
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
431
432
                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",
    "build_online_iterable_source",
]
hyper_parallel/data/online/mapping.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# limitations under the License.
# ============================================================================
"""Build finite integer-indexed Online sources."""

from __future__ import annotations

import glob
import json
import os
from collections.abc import Callable, Mapping, Sequence
from typing import Any

import numpy as np

from hyper_parallel.data.constants import ONLINE_SOURCE_PATH_KEY, ONLINE_SPLIT_COUNT, ONLINE_SPLIT_NAMES
from hyper_parallel.data.dataset_logging import get_dataset_logger
from hyper_parallel.data.online.source import (
    OnlineDataPath,
    OnlinePathSpec,
    _OnlineSourceBuilder,
)
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    OnlinePathSpec,
    _OnlineSourceBuilder,
)

logger = get_dataset_logger(__name__)

SampleTransform = Callable[[Any], list[Any]]


def build_online_mapping_source(
    *,
    data_config: Mapping[str, Any],
    data_path: OnlineDataPath | None = None,
    sample_filter: Callable[[Mapping[str, Any]], bool] | None = None,
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

    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."""

    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.

        Args:
            source_dataset: One Mapping Dataset or a train-valid-test tuple.
 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

        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."""

    def __init__(
        self,
        data_files: Sequence[str],
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> None:
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
        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],
    ) -> 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,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> None:
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
        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."""

    def __init__(
        self,
        source_datasets: Sequence[Any],
        weights: Sequence[float],
        size: int,
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
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
        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,
        )

    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],
        random_seed: int,
    ) -> tuple[np.ndarray, ...]:
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
        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."""

    def __init__(
        self,
        data_path: OnlineDataPath | None,
        data_config: Mapping[str, Any],
        train_valid_test_num_samples: Sequence[int] | None,
375
376
377
378
379
380
381
382
383
384
385
386
        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,
        *,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None = None,
    ) -> Any:
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
439
440
441
        *,
        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,
        source_loader: Any,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> Any:
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
473
474
475
        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,
        data_path: Mapping[str, OnlinePathSpec],
        sample_filter: Callable[[Mapping[str, Any]], bool] | None,
    ) -> tuple[Any, Any, Any] | None:
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
532
533
        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],
        weights: Sequence[float],
        target_size: int,
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
571
572
            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",
    "MappingTransformDataset",
]
hyper_parallel/data/online/source.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# limitations under the License.
# ============================================================================
"""Shared Online source loading and multi-source construction policy."""

from __future__ import annotations

import os
from collections.abc import Callable, Mapping, Sequence
from typing import Any, TypeAlias

from hyper_parallel.data.constants import ONLINE_FILE_FORMATS, ONLINE_SPLIT_NAMES, ONLINE_STOPPING_STRATEGIES

OnlinePathSpec: TypeAlias = str | Sequence[str]
OnlineDataPath: TypeAlias = OnlinePathSpec | Mapping[str, OnlinePathSpec]


class _OnlineSourceBuilder:
    """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.

        Example:
            ``data_path="/data/train.jsonl"`` produces one source with weight
 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
141
142
143
144
145
146
147
148
149
150
151
            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``."""

    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,
        *,
        streaming: bool,
        sample_filter: Callable[[Mapping[str, Any]], bool] | None = None,
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
220
221
            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,
        *,
        streaming: bool,
    ) -> tuple[str, dict[str, Any], tuple[str | None, str | None, str | None] | None]:
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
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
404
405
        *,
        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],
    ) -> 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",
    "OnlinePathSpec",
]
hyper_parallel/data/parallel/batch_parallel.py
198
199
200
201
202
203
204
205
206
            token_pad_values: Optional sequence fields and their padding values.
                When supplied, fields not listed here remain complete on each CP rank.
        """
        self.parallel_context = parallel_context
        self.token_pad_values = token_pad_values

    def shard(
            self,
            canonical_batch: Mapping[str, Any] | None,
216
217
218
219
220
221
222
223
224
225
        if canonical_batch is None:
            cp_batch = None
            return cp_batch

        if self.token_pad_values is not None:
            return self._shard_configured_fields(canonical_batch)

        input_ids = canonical_batch["input_ids"]
        cp_batch = {
            "input_ids": input_ids,
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
        return cp_batch

    def _shard_configured_fields(self, canonical_batch: Mapping[str, Any]) -> dict[str, Any]:
        """Shard configured token fields and preserve all modality metadata."""
        cp_batch = dict(canonical_batch)
        cp_size = self.parallel_context.cp_world_size
        if cp_size == 1:
            return cp_batch
        input_ids = cp_batch["input_ids"]
        if not torch.is_tensor(input_ids) or input_ids.ndim != 2 or input_ids.shape[0] != 1:
            raise ValueError("Configured CP sharding requires input_ids with shape [1, sequence]")

        sequence_length = input_ids.shape[1]
        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

        chunk_length = (sequence_length + pad_length) // cp_size
        local_start = self.parallel_context.cp_rank * chunk_length
        local_end = local_start + chunk_length
        for field, pad_value in self.token_pad_values.items():
            if field not in cp_batch:
                continue

            value = cp_batch[field]
            if not torch.is_tensor(value) or value.ndim != 2 or value.shape != input_ids.shape:
                raise ValueError(f"CP token field {field!r} must match input_ids shape")

            padded = torch.nn.functional.pad(value, (0, pad_length), value=pad_value)
            cp_batch[field] = padded[:, local_start:local_end].contiguous()

        return cp_batch


class TPBatchBroadcaster:
    """Broadcast CP-local batch fields within one TP group."""
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`."
290
291
292
293
294
295
296
297
298
299
            _IndexWriter: The instance
        """
        self.idx_writer = open(self.idx_path, "wb")
        # fixed, vestigial practice
        self.idx_writer.write(INDEX_HEADER)
        # fixed, vestigial practice
        self.idx_writer.write(struct.pack("<Q", INDEX_VERSION))
        # the numeric code for the dtype
        self.idx_writer.write(struct.pack("<B", DType.code_from_dtype(self.dtype)))
        return self
393
394
395
396
397
398
399
400
401
402
403
404
405
        """
        logger.info("Loading index file %s", idx_path)

        with open(idx_path, "rb") as f:
            header = f.read(len(INDEX_HEADER))
            assert header == INDEX_HEADER, f"Bad header in {idx_path}"

            version = struct.unpack("<Q", f.read(8))[0]
            assert version == INDEX_VERSION, f"Unsupported index version {version} in {idx_path}"

            code = struct.unpack("<B", f.read(1))[0]
            self.dtype = DType.dtype_from_code(code)
            self.dtype_size = DType.size(self.dtype)
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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353


def _unwrap_dtensor_outputs(output):
    """Return local tensors while preserving tuple/list output structure."""
    if isinstance(output, DTensor):
        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


# ────────────────────────────────────────────────────────────────────────────
# Production/validate boundary wrappers and output-contract validation
480
481
482
483
484
485
486
487
        # §13.4) keep the DTensor so the outer forward's dispatch chain is
        # unbroken; the outermost boundary exit unwraps.
        if nested or keep_output_dtensor:
            return outputs
        return _unwrap_dtensor_outputs(outputs)

    module.forward = validate_forward

677
678
679
680
681
682
683
684
685
        # Step 3: validate restores the DTensor metadata broken by all-to-all.
        # Production keeps the output local so lowered boundary operations can
        # execute directly; an unlowerable transition wraps only in its
        # necessary DTensor fallback inside RedistOp.
        if validate_mode and not isinstance(output, DTensor):
            output = _rewrap_local_outputs(
                output, spec, mesh, mesh_dim_names, type(module).__name__)

        # Step 4: PrecompiledBoundary exit (e.g. TP reduce-scatter)
685
686
687
688
689
690
691
692
693
        # Step 4: PrecompiledBoundary exit (e.g. TP reduce-scatter)
        output = boundary.redistribute_outputs(
            output, as_dtensor_input=validate_mode)
        # The final boundary exit is always local.
        output = _unwrap_dtensor_outputs(output)
        # D-22: deferred rowwise biases — added once after the exit reduction
        return _maybe_add_deferred_biases(module, spec, output)

    module.forward = local_region_forward
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
273
274
275
276
277
278
279
280
    ) -> None:
        """Normalize overrides and finish placement-dependent boundary metadata."""
        _merge_plan_overrides(self._plan_overrides, plan, model, derive=self._derive)
        _normalize_contract_fields(plan)
        self._mark_virtual_ep_parameters(plan, ep_size, mesh)
        _finalize_tp_local_attr_plans(plan, model, tp_size=tp_size, mesh_dim_names=mesh_dim_names)
        self._finalize_deferred_biases(plan, model, mesh_dim_names)
        self._finalize_fused_expert_tp_guard(plan, tp_size=tp_size)
352
353
354
355
356
357
358
359
360
        """
        arch = _get_architecture(model)
        self._check_overrides_no_dp()   # fail-first: the plan's coordinate system = a single dp slice
        mesh_dim_names = self._build_mesh_dim_names(mesh, tp_size, cp_size, ep_size)
        plan, param_roles, param_metadata = self._build_plan(
            model,
            mesh,
            mesh_dim_names,
            arch,
364
365
366
367
368
369
370
371
372
            loss_parallel=loss_parallel,
        )

        # D-14 invariants, Phase 5/6, F4 lints, DX guard, tied weights
        plan = self._finalize_plan(
            plan,
            model,
            param_roles,
            param_metadata,
374
375
376
377
378
379
380
381
382
383
384
            cp_size=cp_size,
            ep_size=ep_size,
        )

        self._log_explanation(plan, explain)

        return plan

    def _build_plan(
        self,
        model: Any,
436
437
438
439
440
441
442
443
444
            ep_size=ep_size,
            mesh=mesh,
            mesh_dim_names=mesh_dim_names,
        )
        return plan, param_roles, param_metadata

    def _finalize_plan(
        self,
        plan: ShardingPlan,
807
808
809
810
811
812
813
814
815

    @classmethod
    def _validate_ep_extend(cls, ep_extend, mesh, model) -> None:
        """Validate virtual EP plus the routed-expert count constraint."""
        cls._validate_ep_domain(ep_extend, mesh)
        num_experts = (getattr(getattr(model, "config", None), "num_experts", None)
                       or getattr(getattr(model, "config", None), "n_routed_experts", 0))
        if num_experts and num_experts % ep_extend != 0:
            raise ValueError(
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
        This deliberately keys off placement semantics rather than parameter
        names, so routed experts and sparse lookup tables use one mechanism.
        The boundary still has to inject its matching all-to-all computation.
        """
        if ep_size <= 1 or "ep" in tuple(getattr(mesh, "mesh_dim_names", ()) or ()):
            return
        marked = []
        for fqn, spec in plan.modules.items():
            if getattr(spec, "_ep_size", 0):  # pylint: disable=protected-access
                continue
            ep_params = [
                name
                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,
        )
940
941
942
943
944
945
946
947
948
                spec.params[stacked] = _multi_dim(
                    tp=None, cp=Replicate(), ep=template.moe_expert_placement
                )
                # The planner owns the spec DSL internals.
                spec._ep_stack[stacked] = sources  # pylint: disable=protected-access
            spec.region_dispatch = None
            return
        if batched:
            for rel in batched:
1477
1478
1479
1480
1481
1482
1483
1484
1485
        )
        terminal = sorted_fqns[-1] if sorted_fqns else None
        for fqn, spec in plan.modules.items():
            # The planner owns the spec DSL internals.
            spec._is_terminal = fqn == terminal  # pylint: disable=protected-access
        return plan

    @staticmethod
    def _topological_sort_by_forward_order(fqns: List[str], model) -> List[str]:
hyper_parallel/distributed/_builder/rule_resolver.py
55
56
57
58
59
60
61
62


def _can_insert_glob(user_spec: ModuleShardingSpec) -> bool:
    """Return whether a glob declares a concrete boundary contract."""
    return any(
        isinstance(getattr(user_spec, attr), dict)
        for attr in ("params", "in_src", "in_dst", "out_src", "out_dst")
    )
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
    ]
    if not entries:
        return

    module_names = [name for name, _ in model.named_modules()]
    _validate_override_entries(entries, plan, set(module_names))

    for key, user_spec, source in entries:
        if _is_glob_key(key):
            _apply_glob_override(
                plan, key, user_spec, source, model, module_names,
                derive=derive,
            )
        elif key in plan.modules:
            _warn_dropped_params(
                source, key, key, plan.modules[key], user_spec)
            _merge_into(plan.modules[key], user_spec)
            logger.info("%s: merge into the spec of module %s",
                        source, key)
        else:
            _insert_spec(plan, key, user_spec, source, model,
                         derive=derive)


def _validate_override_entries(entries, plan, module_name_set) -> None:
145
146
147
148
149
150
151
152
153
                f"{source}[{key!r}] must be a ModuleShardingSpec, "
                f"got {type(user_spec).__name__}"
            )
        _validate_override_axes(key, user_spec, source, plan)
        if not _is_glob_key(key) and key not in module_name_set:
            raise ValueError(
                f"[HP-PLAN-001] {source} FQN not found in the model's "
                f"named_modules: {key!r} (check spelling; in PP "
                f"scenarios plan each single-part model separately)"
155
156
157
158
159
160
161
162
163


def _raise_merge_only_glob_error(key, user_spec, source, model, model_hits) -> None:
    """Fail fast when an insert-incapable glob matches unbounded modules."""
    configured_fields = [
        attr
        for attr in (
            "local_compute_fn", "inner_target", "inner_wrapper",
            "inner_out_src", "region_dispatch", "out_names",
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
            "tp_divide_attrs",
        )
        if getattr(user_spec, attr) is not None
    ]
    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 "
        f"boundary: {matched}. The override declares only "
        f"merge-only fields {configured_fields or ['none']} and "
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
                         user_spec: ModuleShardingSpec, source: str,
                         model, module_names, *,
                         derive: bool = True) -> None:
    """Merge-apply one glob override and insert matching unbounded modules."""
    hits = [fqn for fqn in plan.modules
            if fnmatch.fnmatchcase(fqn, key)]
    model_hits = [
        fqn for fqn in module_names
        if fqn not in plan.modules and fnmatch.fnmatchcase(fqn, key)
    ]
    inserted = []
    can_insert = _can_insert_glob(user_spec)
    if model_hits and not can_insert:
        _raise_merge_only_glob_error(key, user_spec, source, model, model_hits)
    if can_insert:
        for fqn in model_hits:
            _insert_spec(
                plan,
                fqn,
                user_spec,
                source,
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
                source,
                model,
                derive=derive,
            )
            inserted.append(fqn)
    if not hits and not inserted:
        logger.warning(
            "%s match=%r hit no boundary spec or model module — check the "
            "spelling (plan boundaries: %s)",
            source, key, sorted(plan.modules)[:8],
        )
    for fqn in hits:
        _warn_dropped_params(
            source, key, fqn, plan.modules[fqn], user_spec)
        _merge_into(plan.modules[fqn], user_spec)
        logger.info("%s: merge into %s (glob %r)",
                    source, fqn, key)


_CONTRACT_FIELDS = ("params", "in_src", "in_dst",
hyper_parallel/distributed/_builder/source_shard.py
150
151
152
153
154
155
156
157
158
    for fqn, spec in plan.modules.items():
        for param_name, named_placement in spec.params.items():
            full_fqn = f"{fqn}.{param_name}"
            _check_fsdp_owned_axes(full_fqn, named_placement)
            if (spec._ep_size > 0  # pylint: disable=protected-access
                    and isinstance((named_placement or {}).get(EP), Shard)):
                if expert_source_mesh is None:
                    raise ValueError(
                        "Virtual-EP parameter metadata requires an EP source mesh"
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,
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
    layer_indices: Any,
    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
        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(
            "model-adapter safe recompute layer index "
            f"{max(layer_indices)} exceeds the maximum index {len(blocks) - 1}"
        )
827
828
829
830
831
832
833
834
835
836
837
838
839
    layer_count: Any,
    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)}"
        )

842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
    containers: list[_LayerContainerInfo],
    selection: Any,
) -> set[str]:
    """Select a leading layer count or exact layer indices."""
    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:
        _validate_selected_layer_indices(layer_indices, blocks)
        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(
    model: nn.Module,
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,
1083
1084
1085
1086
1087
1088
1089
1090
1091
        The number of wrapped submodules, or ``None`` when the HuggingFace-native
        implementation was enabled instead, in which case no per-layer wrapping
        happened and swap prefetch chains must not be registered.
    """
    if not has_kv_sharing and _should_use_hf_native_gradient_checkpointing(
        model,
        ac_layers,
        enable_compile=enable_compile,
    ):
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
    containers = _resolve_activation_checkpoint_containers(model)
    ac_layers = _flatten_layer_container_infos(containers)
    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
103
104
105
106
107
108
109
110
111
112
113
114
    Raises:
        ValueError: If the knob is none of the accepted values; a typo must fail
            loudly instead of silently leaving the fast path off.
    """
    raw = _EQUAL_A2A_RAW.strip()
    try:
        return _EQUAL_A2A_MODES[raw]
    except KeyError:
        raise ValueError(
            f"HP_EP_EQUAL_A2A must be one of "
            f"{sorted(_EQUAL_A2A_MODES)}, but got {raw!r}"
        ) from None
134
135
136
137
138
139
140
141
142
143
        ctx.send_counts = send_counts
        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],
143
144
145
146
147
148
149
150
151
                input_split_sizes=[int(count) for count in send_counts],
                group=group,
            )
        else:
            dist.all_to_all(list(out.split(recv_counts)),
                            list(x.split(send_counts)), group=group)
        return out

    @staticmethod
171
172
173
174
175
176
177
178
179
180
181
    alone, and the received chunks are concatenated in peer order.  Both the
    eager and the lazy equal-length exchange issue this, so a change here cannot
    make the two disagree about the layout.
    """
    out = x.new_empty((rows_per_peer * ep_size,) + tuple(x.shape[1:]))
    dist.all_to_all_single(out, x, group=group)
    return out


class _EPAllToAllEqual(torch.autograd.Function):  # pylint: disable=abstract-method
    """Equal-length ``all_to_all_single`` (NCCL/HCCL path when counts are uniform).
214
215
216
217
218
219
220
221
222
223
224
225

        Returns:
            The received rows, concatenated in peer order.
        """
        ctx.rows_per_peer = rows_per_peer
        ctx.ep_size = ep_size
        ctx.group = group
        return _equal_a2a_exchange(x, rows_per_peer, ep_size, group)

    @staticmethod
    def backward(
        ctx: Any,
233
234
235
236
237
238
239
240
241
242
243

        Returns:
            The input gradient, followed by ``None`` for the non-tensor arguments.
        """
        grad = _equal_a2a_exchange(
            grad_output.contiguous(), ctx.rows_per_peer, ctx.ep_size, ctx.group)
        return grad, None, None, None


def _lazy_a2a_resources() -> tuple[Any, Any, Any]:
    """Return the ``(stream, ready_event, done_event)`` the lazy exchange reuses.
253
254
255
256
257
258
259
260
261
262
263
264
265
266
        The shared comm stream, the event that hands the payload over to it, and
        the event that hands the result back.
    """
    global _LAZY_A2A_STREAM, _LAZY_A2A_READY_EVENT, _LAZY_A2A_DONE_EVENT  # pylint: disable=global-statement
    with _LAZY_A2A_LOCK:
        if _LAZY_A2A_DONE_EVENT is None:
            _LAZY_A2A_STREAM = get_device_handle().Stream()
            _LAZY_A2A_READY_EVENT = get_device_handle().Event()
            _LAZY_A2A_DONE_EVENT = get_device_handle().Event()
        return _LAZY_A2A_STREAM, _LAZY_A2A_READY_EVENT, _LAZY_A2A_DONE_EVENT


def _record_stream(tensor: torch.Tensor, stream: Any) -> None:
    """Tell the caching allocator that ``stream`` touches ``tensor``'s storage.
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.
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
    Returns:
        The event recorded when the exchange has completed; the consumer's
        stream waits on it (see :class:`_PendingEqualA2A`).
    """
    compute_stream = get_device_handle().current_stream()
    stream, ready_event, done_event = _lazy_a2a_resources()
    stream_context = get_device_handle().stream
    ready_event.record(compute_stream)
    with stream_context(stream):
        ready_event.wait(stream)
        dist.all_to_all_single(out, x, group=group)
        # Both buffers outlive the call by different means: the receive buffer
        # was allocated on the compute stream, and the payload is a temporary of
        # the caller's dispatch, so its storage may be freed and reused while
        # this stream is still reading it.
        _record_stream(out, stream)
        _record_stream(x, stream)
        done_event.record(stream)
    return done_event


class _PendingEqualA2A:
    """Handle to a split-free exchange that is still in flight.
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
    __slots__ = ("completed", "_event", "_tensor")

    def __init__(self, tensor: torch.Tensor, event: Any, completed: bool = False) -> None:
        """Wrap an issued-but-unwaited exchange result."""
        self._tensor = tensor
        self._event = event
        self.completed = completed

    def wait(self) -> torch.Tensor:
        """Order the current stream after the exchange and return its result."""
        if not self.completed:
            self._event.wait(get_device_handle().current_stream())
            self.completed = True
        return self._tensor

    def squeeze(self, dim: int) -> "_PendingEqualA2A":
        """Return a handle to the squeezed result (a view, so the wait is pending).
353
354
355
356
357
358
359
360
361
362
363
364
365
        Returns:
            A handle to the squeezed result; it still holds the exchange's result
            as pending as this one does.
        """
        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:
376
377
378
379
380
381
382
383
384
385
386
    Returns:
        The exchange result as a regular tensor; ``value`` unchanged when it is
        not a pending handle.
    """
    if isinstance(value, _PendingEqualA2A):
        return value.wait()
    return value


class _EPAllToAllEqualLazy(torch.autograd.Function):  # pylint: disable=abstract-method
    """Split-free equal-length exchange issued on the comm stream, waited lazily.
417
418
419
420
421
422
423
424
425
426
427
428
429
        Returns:
            The receive buffer the exchange writes -- the output the autograd
            graph is built on -- and the event that says when it is final.
        """
        ctx.rows_per_peer = rows_per_peer
        ctx.ep_size = ep_size
        ctx.group = group
        out = x.new_empty((rows_per_peer * ep_size,) + tuple(x.shape[1:]))
        return out, _issue_split_free_a2a(out, x, group)

    @staticmethod
    def backward(  # pylint: disable=arguments-differ
        ctx: Any,
439
440
441
442
443
444
445
446
447
448
449

        Returns:
            The input gradient, followed by ``None`` for the non-tensor arguments.
        """
        grad = _equal_a2a_exchange(
            grad_output.contiguous(), ctx.rows_per_peer, ctx.ep_size, ctx.group)
        return grad, None, None, None


class _EPAllToAllPadded(torch.autograd.Function):  # pylint: disable=abstract-method
    """pad-to-max + all_to_all_single (gloo test path).
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
    Returns:
        The uniform per-peer row count, or ``None`` when the fast path does not
        apply.
    """
    if _equal_a2a_mode() == _EQUAL_A2A_OFF or not _backend_supports_uneven_a2a(group):
        return None
    ep_size = len(send_counts)
    if ep_size == 0 or len(recv_counts) != ep_size or not x.is_contiguous():
        return None
    rows_per_peer = send_counts[0]
    # The split-free call takes the chunk size from the tensor shape and needs
    # the receive buffer to be the input's size, so a uniform plan is only
    # equivalent when both count lists hold that same count.
    uniform = (all(count == rows_per_peer for count in send_counts)
               and all(count == rows_per_peer for count in recv_counts))
    if not uniform or x.shape[0] != rows_per_peer * ep_size:
        return None
    return rows_per_peer


def ep_all_to_all(
    x: torch.Tensor,
569
570
571
572
573
574
575
576
577
578
579
580
581
582
    With ``HP_EP_EQUAL_A2A`` on, a uniform plan is exchanged with the equal-length
    a2a instead (same rows, same order, cheaper kernel).  This entry always
    returns a materialized tensor; :func:`ep_all_to_all_async` is the lazy one.
    """
    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:
        return _EPAllToAllEqual.apply(x, rows_per_peer, len(send_counts), group)
    return _EPAllToAllUneven.apply(x, send_counts, recv_counts, group)


def ep_all_to_all_async(
    x: torch.Tensor,
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637

    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:
            tensor, event = _EPAllToAllEqualLazy.apply(x, rows_per_peer, ep_size, group)
            return _PendingEqualA2A(tensor, event)
        return _EPAllToAllEqual.apply(x, rows_per_peer, ep_size, group)
    return differentiable_all_to_all_single_async(
        x, send_counts, recv_counts, group,
    )
hyper_parallel/distributed/expert_parallel/experts.py
180
181
182
183
184
185
186
187
188
189
190

    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:
    """Count routed tokens per expert with device ops only.
202
203
204
205
206
207
208
209
210
211
212

    Returns:
        int64 counts of shape ``[num_experts]`` on ``indices.device``.
    """
    flat = indices.to(torch.int64).reshape(-1)
    counts = torch.zeros(num_experts, dtype=torch.int64, device=flat.device)
    return counts.scatter_add_(0, flat, torch.ones_like(flat))


def _local_swiglu_expert_forward(experts, dispatched_states, local_expert_indices):
    """Compute dispatched tokens with the local stacked SwiGLU experts.
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    path. Calling the expert module, instead of indexing its parameters from
    the parent MoE forward, allows nested FSDP forward hooks to unshard and
    reshard expert parameters around the local computation.
    """
    token_order = _argsort_keys(
        local_expert_indices, bound=experts.local_expert_count)
    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
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
                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.
    expert_token_counts = local_expert_counts.tolist()
    sorted_outputs = []
    token_start = 0
    for local_expert_index in range(experts.local_expert_count):
        expert_token_count = expert_token_counts[local_expert_index]
        expert_states = sorted_states[token_start:token_start + expert_token_count]
        if up_weight is None:
            gate_up_states = F.linear(  # pylint: disable=not-callable
                expert_states,
                gate_weight[local_expert_index],
            )
            apply_gate = getattr(experts, "_ep_apply_gate", None)
            if apply_gate is None:
                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]
            )
            up_states = F.linear(  # pylint: disable=not-callable
                expert_states, up_weight[local_expert_index]
            )
            activation = getattr(experts, "_ep_act_fn", F.silu)
            activated_states = activation(gate_states) * up_states
        sorted_outputs.append(
            F.linear(  # pylint: disable=not-callable
                activated_states,
                down_weight[local_expert_index],
278
279
280
281
282
283
284
285
            )
        )
        token_start += expert_token_count

    sorted_output = torch.cat(sorted_outputs)
    output = torch.empty_like(sorted_output)
    output[token_order] = sorted_output
    return output
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
            raise ValueError(
                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
    module.experts._ep_apply_gate = apply_gate
    module.experts._ep_use_grouped_gemm = use_grouped_gemm
    # The forward write itself lives in the forward rewriter (05 §15.2.3:
    # the single MethodType/assignment site); this binder only sets the
    # companion attributes above.
    _install_bound_forward(module.experts, _local_swiglu_expert_forward)
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383

    Returns:
        A positive float, or ``None`` when the capacity limit is disabled.
    """
    override = os.environ.get("HP_EP_CAPACITY_FACTOR", "").strip()
    if override:
        try:
            value = float(override)
        except ValueError:
            logger.warning(
                "HP_EP_CAPACITY_FACTOR=%r is not a number; capacity limit stays off", override)
            return None
        return value if value > 0 else None
    value = getattr(getattr(module, "experts", None), "capacity_factor", None)
    if value is None:
        return None
    value = float(value)
    return value if value > 0 else None


def _routed_slot_token_ids(token_count: int, experts_per_token: int, device: Any) -> torch.Tensor:
    """Return the source token of every routed slot, as an index tensor.
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411

    Raises:
        ValueError: If ``HP_EP_SOURCE_INDEX`` names an unknown mode.
    """
    mode = _SOURCE_INDEX_MODE
    if mode not in _SOURCE_INDEX_MODES:
        raise ValueError(
            f"HP_EP_SOURCE_INDEX must be one of {_SOURCE_INDEX_MODES}, but got {mode!r}"
        )
    if mode == "expand" and experts_per_token > 0:
        # ``reshape`` may hand back a stride-0 view when the token dim is 1, so
        # materialize: the callers index with the result and expect a flat buffer.
        return (
            torch.arange(token_count, device=device)
            .unsqueeze(1)
            .expand(token_count, experts_per_token)
            .reshape(-1)
410
411
412
413
414
415
416
417
418
419
420
421
422
            .expand(token_count, experts_per_token)
            .reshape(-1)
            .contiguous()
        )
    if mode == "div" and experts_per_token > 0:
        return torch.arange(token_count * experts_per_token, device=device).div(
            experts_per_token, rounding_mode="floor"
        )
    return torch.arange(token_count, device=device).repeat_interleave(experts_per_token)


def _prepare_ep_dispatch(
    hidden_states: torch.Tensor,
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
    accumulates with ``index_add_`` over ``source_indices`` -- so a token with fewer than ``K``
    surviving slots simply receives fewer contributions.
    """
    flattened_states = hidden_states.reshape(-1, hidden_states.shape[-1])
    token_count = flattened_states.shape[0]
    experts_per_token = topk_indices.shape[1]
    if keep is None:
        expert_indices = topk_indices.reshape(-1)
        expert_weights = topk_weights.reshape(-1).to(flattened_states.dtype)
        source_indices = _routed_slot_token_ids(
            token_count, experts_per_token, flattened_states.device)
    else:
        flat_keep = keep.reshape(-1)
        expert_indices = topk_indices.reshape(-1)[flat_keep]
        expert_weights = topk_weights.reshape(-1)[flat_keep].to(flattened_states.dtype)
        source_indices = _routed_slot_token_ids(
            token_count, experts_per_token, flattened_states.device)[flat_keep]
    destination_ranks = torch.div(expert_indices, local_expert_count, rounding_mode="floor")
    dispatch_order = _argsort_keys(
        destination_ranks * global_expert_count + expert_indices,
        bound=ep_size * global_expert_count,
    )
    dispatched_states = flattened_states[source_indices[dispatch_order]].contiguous()
    dispatched_indices = expert_indices[dispatch_order].unsqueeze(-1).contiguous()
    # The counts exchange is a host-serialising step: a synchronous all-to-all followed
    # by two ``.tolist()`` drains, and a drain waits for everything already enqueued on
    # the stream, so the token all-to-all cannot be enqueued until the counts land.  When
    # the routing plan is static (``fix_router: true``) the answer is the same in every
465
466
467
468
469
470
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
    # the stream, so the token all-to-all cannot be enqueued until the counts land.  When
    # the routing plan is static (``fix_router: true``) the answer is the same in every
    # layer, so it is computed once and reused -- that removes the extra collective and
    # both syncs from every later dispatch.  See ``static_splits`` for the caveats.
    plan_key = static_plan_key(
        token_count, experts_per_token, ep_size, local_expert_count, global_expert_count,
        device=flattened_states.device,
    )
    cached_counts = get_static_plan(plan_key)
    if cached_counts is not None:
        send_counts, receive_counts, send_counts_tensor, receive_counts_tensor = cached_counts
        # 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)
        send_counts = send_counts_tensor.tolist()
        receive_counts = receive_counts_tensor.tolist()
        store_static_plan(
            plan_key, (send_counts, receive_counts, send_counts_tensor, receive_counts_tensor),
            f"(ep={ep_size}, tokens={token_count}, topk={experts_per_token}, "
            f"tokens/peer={send_counts[0] if send_counts else 0})",
        )
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
544
545

    Raises:
        ValueError: If the knob is not a positive integer.
    """
    raw = _DISPATCH_CHUNKS_RAW.strip()
    try:
        chunk_count = int(raw, 10)
    except ValueError:
        raise ValueError(
            "HP_EP_DISPATCH_CHUNKS must be a positive integer, but got "
            f"{raw!r}; leave it unset (or 1) for the unchunked schedule"
        ) from None
    if chunk_count < 1:
        raise ValueError(
            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
    return chunk_count


def _chunk_rank_range(group: int, ep_size: int, chunk_count: int) -> tuple[int, int]:
    """Half-open EP rank range covered by chunk group ``group``.
556
557
558
559
560
561
562
563
564

    Returns:
        ``(first_rank, last_rank)``, the first inclusive / last exclusive rank.
    """
    return group * ep_size // chunk_count, (group + 1) * ep_size // chunk_count


def _chunk_rank_group(ep_rank: int, ep_size: int, chunk_count: int) -> int:
    """Chunk group owning ``ep_rank`` (the inverse of :func:`_chunk_rank_range`).
570
571
572
573
574
575
576
577
578

    Returns:
        The group index whose range contains ``ep_rank``.
    """
    return ((ep_rank + 1) * chunk_count - 1) // ep_size


class _EPDispatchChunks(NamedTuple):
    """Chunk plan of one routed exchange (see :func:`_ep_dispatch_chunks`).
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642

    Returns:
        The :class:`_EPDispatchChunks` plan, in chunk order.
    """
    row_offsets = [0]
    for count in send_counts:
        row_offsets.append(row_offsets[-1] + count)
    own_group = _chunk_rank_group(ep_rank, ep_size, chunk_count)
    send_plan, recv_plan, row_ranges = [], [], []
    for chunk in range(chunk_count):
        first, last = _chunk_rank_range(
            (chunk + own_group) % chunk_count, ep_size, chunk_count)
        send_plan.append(
            [send_counts[rank] if first <= rank < last else 0 for rank in range(ep_size)])
        row_ranges.append((row_offsets[first], row_offsets[last]))
        first, last = _chunk_rank_range(
            (own_group - chunk) % chunk_count, ep_size, chunk_count)
        recv_plan.append(
            [receive_counts[rank] if first <= rank < last else 0 for rank in range(ep_size)])
    return _EPDispatchChunks(send_plan, recv_plan, row_ranges)


def _run_ep_local_experts(
    module: Any,
677
678
679
680
681
682
683
684
685
686
    Returns:
        ``(head_slots, index_slots)`` in state elements; the row is
        ``hidden_size + head_slots + index_slots`` state elements wide.
    """
    index_slots = math.lcm(state_dtype.itemsize, index_dtype.itemsize) // state_dtype.itemsize
    return (-hidden_size) % index_slots, index_slots


def _pack_fused_dispatch(
    dispatched_states: torch.Tensor,
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716

    Returns:
        ``[tokens, H + head_slots + index_slots]`` in the states' dtype.
    """
    token_count, hidden_size = dispatched_states.shape
    head_slots, index_slots = _fused_row_layout(
        hidden_size, dispatched_states.dtype, dispatched_expert_indices.dtype)
    index_bytes = dispatched_expert_indices.reshape(token_count, 1).view(torch.uint8)
    index_slots_buffer = F.pad(
        index_bytes, (0, index_slots * dispatched_states.element_size() - index_bytes.shape[1]))
    head_buffer = torch.zeros(
        token_count, head_slots, dtype=dispatched_states.dtype, device=dispatched_states.device)
    return torch.cat(
        [dispatched_states, head_buffer, index_slots_buffer.view(dispatched_states.dtype)],
        dim=1,
    )
739
740
741
742
743
744
745
746
747
748
749

    Returns:
        ``(states [tokens, H], indices [tokens])``, both views of ``packed``.
    """
    head_slots, index_slots = _fused_row_layout(hidden_size, packed.dtype, index_dtype)
    index_start = hidden_size + head_slots
    index_bytes = packed.detach()[:, index_start:index_start + index_slots].view(torch.uint8)
    return (
        packed[:, :hidden_size],
        index_bytes[:, :index_dtype.itemsize].view(index_dtype).squeeze(-1),
    )
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
    keeps that possible: the unpack below is a view chain, which the split-free
    exchange's pending handle cannot carry, so this exchange stays on the
    async-tensor path even with ``HP_EP_EQUAL_A2A`` on.
    """
    packed = _pack_fused_dispatch(dispatched_states, dispatched_expert_indices)
    # ``allow_pending=False``: the unpack below reads the exchanged buffer
    # through a view chain (slice / detach / view-as-dtype), which a pending
    # handle cannot express, so the fused exchange keeps the async-tensor lazy
    # path and with it its own overlap window.
    exchanged = ep_all_to_all_async(
        packed, send_counts, receive_counts, ep_group, allow_pending=False)
    return _unpack_fused_dispatch(
        exchanged,
        hidden_size=dispatched_states.shape[-1],
        index_dtype=dispatched_expert_indices.dtype,
    )
812
813
814
815
816
817
818
819
820
821
        ``(received_states, received_indices)``, pending their exchange -- a
        tensor or a :class:`_PendingEqualA2A` handle, depending on the knob, so
        a caller must pass them through :func:`wait_ep_all_to_all`.
    """
    if fused:
        return _fused_dispatch_exchange(
            dispatched_states,
            dispatched_expert_indices,
            chunk_send_counts,
            chunk_recv_counts,
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
            chunk_send_counts,
            chunk_recv_counts,
            ep_group,
        )
    received_states = ep_all_to_all_async(
        dispatched_states, chunk_send_counts, chunk_recv_counts, ep_group)
    # squeeze is a view (the handle's own, or a tensor's), so the wait stays
    # deferred until the experts read it.
    received_indices = ep_all_to_all_async(
        dispatched_expert_indices, chunk_send_counts, chunk_recv_counts, ep_group).squeeze(-1)
    return received_states, received_indices


def _aggregate_ep_outputs(
    combined_outputs: torch.Tensor,
878
879
880
881
882
883
884
885
886
887

    Returns:
        The routed output, shape ``output_shape``.
    """
    reference = combined_chunks[0]
    output = torch.zeros(
        output_shape[0] * output_shape[1],
        output_shape[-1],
        dtype=reference.dtype,
        device=reference.device,
885
886
887
888
889
890
891
892
893
894
895
896
897
898
        output_shape[-1],
        dtype=reference.dtype,
        device=reference.device,
    )
    for combined, (row_start, row_end) in zip(combined_chunks, row_ranges):
        rows = dispatch_order[row_start:row_end]
        # Reading a chunk is what orders its stream on the pending combine.
        weighted_outputs = wait_ep_all_to_all(combined) * expert_weights[rows].unsqueeze(-1)
        output.index_add_(0, source_indices[rows], weighted_outputs)
    return output.view(*output_shape)


def _run_ep_local_experts_chunked(
    module: Any,
924
925
926
927
928
929
930
931
932
933
934

    Returns:
        One combine output per chunk, in plan order (their wait is pending).
    """
    chunk_count = len(plan.row_ranges)

    def issue(chunk: int) -> tuple[torch.Tensor, torch.Tensor]:
        """Issue chunk ``chunk``'s dispatch exchange over its stream slice.

        Args:
            chunk: Chunk index into ``plan``.
935
936
937
938
939
940
941
942
943
944

        Returns:
            The chunk's pending ``(received_states, received_indices)``.
        """
        row_start, row_end = plan.row_ranges[chunk]
        return _dispatch_chunk(
            dispatched_states[row_start:row_end],
            dispatched_indices[row_start:row_end],
            plan.send_counts[chunk],
            plan.recv_counts[chunk],
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
            plan.recv_counts[chunk],
            ep_group,
        )

    incoming = issue(0)
    combined_chunks = []
    for chunk in range(chunk_count):
        # Issued before the experts of this chunk run: that exchange is what
        # their GEMM overlaps.
        following = issue(chunk + 1) if chunk + 1 < chunk_count else None
        received_states, received_indices = incoming
        local_outputs = module.experts(
            wait_ep_all_to_all(received_states),
            wait_ep_all_to_all(received_indices) - expert_offset)
        combined_chunks.append(ep_all_to_all_async(
            local_outputs.contiguous(),
            plan.recv_counts[chunk],
            plan.send_counts[chunk],
            ep_group,
960
961
962
963
964
965
966
967
968
969
            plan.recv_counts[chunk],
            plan.send_counts[chunk],
            ep_group,
        ))
        incoming = following
    return combined_chunks


def _chunked_ep_forward(
    module: Any,
979
980
981
982
983
984
985
986
987
988
989

    Split out of :func:`ep_routed_forward` so that entry point stays readable; the schedule
    itself is unchanged (see :func:`_run_ep_local_experts_chunked`).
    """
    plan = _ep_dispatch_chunks(
        dispatch.send_counts, dispatch.receive_counts, ep_size, chunk_count, ep_rank)
    combined_chunks = _run_ep_local_experts_chunked(
        module,
        plan,
        dispatch.states,
        dispatch.expert_indices,
989
990
991
992
993
994
995
996
997
        dispatch.expert_indices,
        ep_group,
        expert_offset,
    )
    return _aggregate_ep_chunks(
        combined_chunks,
        plan.row_ranges,
        dispatch.expert_weights,
        dispatch.source_indices,
1061
1062
1063
1064
1065
1066
1067
1068
1069
    global_expert_count = local_expert_count * ep_size
    expert_offset = ep_rank * local_expert_count
    # Resolved before the dispatch preparation so a mistyped knob fails before
    # any collective is issued.
    chunk_count = _resolve_dispatch_chunks(ep_size)

    output_shape = tuple(hidden_states.shape)
    topk_indices, topk_weights = router_fn(module, hidden_states)  # [T, K]
    dispatch = _prepare_ep_dispatch(
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
        global_expert_count=global_expert_count,
        ep_size=ep_size,
        ep_group=ep_group,
    )
    if chunk_count > 1:
        return _chunked_ep_forward(
            module, dispatch, ep_group, ep_size, ep_rank, chunk_count, expert_offset,
            output_shape,
        )
    combined_expert_outputs = _run_ep_local_experts(
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",
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
    """Issue every chunk's split exchange up front and keep the handles pending.

    Split out of :func:`ep_routed_dispatch`; the pending-handle contract is unchanged.
    """
    plan = _ep_dispatch_chunks(
        dispatch.send_counts, dispatch.receive_counts, ep_size, chunk_count, ep_rank)
    chunk_states, chunk_indices = [], []
    for chunk in range(chunk_count):
        row_start, row_end = plan.row_ranges[chunk]
        chunk_state, chunk_index = _dispatch_chunk(
            dispatch.states[row_start:row_end],
            dispatch.expert_indices[row_start:row_end],
            plan.send_counts[chunk],
            plan.recv_counts[chunk],
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
            plan.recv_counts[chunk],
            ep_group,
            fused=_FUSED_DISPATCH_ENABLED,
        )
        chunk_states.append(chunk_state)
        chunk_indices.append(chunk_index)
    return EPRoutedState(
        dispatch.source_indices,
        dispatch.expert_weights,
        dispatch.dispatch_order,
        None,
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
    Returns:
        The pending :class:`EPRoutedState` to hand to
        :func:`ep_routed_experts_and_combine`.
    """
    ep_size = ep_group.size()
    local_expert_count = module.experts.local_expert_count
    global_expert_count = local_expert_count * ep_size
    # Resolved before the dispatch preparation so a mistyped knob fails before
    # any collective is issued.
    chunk_count = _resolve_dispatch_chunks(ep_size)

    batch_size, sequence_length, hidden_size = hidden_states.shape
    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,
        topk_weights,
        local_expert_count=local_expert_count,
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
        ep_size=ep_size,
        ep_group=ep_group,
        keep=keep,
    )
    source_token_indices = dispatch.source_indices
    flattened_expert_weights = dispatch.expert_weights
    dispatch_order = dispatch.dispatch_order
    dispatched_states = dispatch.states
    dispatched_expert_indices = dispatch.expert_indices
    send_counts = dispatch.send_counts
    receive_counts = dispatch.receive_counts
    if chunk_count > 1:
        return _issue_chunked_dispatch(
            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,
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
            receive_counts,
            ep_group,
        )
    else:
        received_states = ep_all_to_all_async(
            dispatched_states, send_counts, receive_counts, ep_group)
        # squeeze is a view, so the wait stays deferred until the experts read it.
        received_indices = ep_all_to_all_async(
            dispatched_expert_indices, send_counts, receive_counts, ep_group).squeeze(-1)
    return EPRoutedState(
        source_token_indices,
        flattened_expert_weights,
        dispatch_order,
        received_states,
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359

    Returns:
        The routed branch output, shape ``state.output_shape``.
    """
    expert_offset = dist.get_rank(group=ep_group) * module.experts.local_expert_count
    if state.chunks is not None:
        plan = state.chunks.plan
        combined_chunks = []
        for chunk, (received_states, received_indices) in enumerate(
                zip(state.chunks.received_states, state.chunks.received_indices)):
            local_outputs = module.experts(
                wait_ep_all_to_all(received_states),
                wait_ep_all_to_all(received_indices) - expert_offset)
            combined_chunks.append(ep_all_to_all_async(
                local_outputs.contiguous(),
                plan.recv_counts[chunk],
                plan.send_counts[chunk],
                ep_group,
1357
1358
1359
1360
1361
1362
1363
1364
1365
                plan.recv_counts[chunk],
                plan.send_counts[chunk],
                ep_group,
            ))
        return _aggregate_ep_chunks(
            combined_chunks,
            plan.row_ranges,
            state.flattened_expert_weights,
            state.source_token_indices,
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
            state.source_token_indices,
            state.dispatch_order,
            state.output_shape,
        )
    local_outputs = module.experts(
        wait_ep_all_to_all(state.received_states),
        wait_ep_all_to_all(state.received_indices) - expert_offset)
    combined_expert_outputs = ep_all_to_all_async(
        local_outputs.contiguous(), state.receive_counts, state.send_counts, ep_group)
    return _aggregate_ep_outputs(
        wait_ep_all_to_all(combined_expert_outputs),
        state.flattened_expert_weights,
        state.source_token_indices,
        state.dispatch_order,
hyper_parallel/distributed/expert_parallel/recipes.py
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
        # Keep the established call contract unchanged for every reference
        # archetype and for Qwen3 when grouped GEMM is disabled.
        bind_local_expert_forward(module, ep_mesh["ep"].size())

    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."""
            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
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
    Raises:
        ValueError: If ``num_experts`` is not positive, or ``HP_EP_CAPACITY_ORDER`` is neither
            ``score`` nor ``first``.
    """
    if capacity_factor is None or float(capacity_factor) <= 0:
        return (
            torch.ones_like(topk_idx, dtype=torch.bool),
            torch.zeros((), dtype=torch.int64, device=topk_idx.device),
        )
    if num_experts <= 0:
        raise ValueError(f"num_experts must be positive, got {num_experts}")

    flat_expert = topk_idx.reshape(-1).to(torch.int64)
    slots = flat_expert.numel()
    capacity = int(math.ceil(float(capacity_factor) * slots / float(num_experts)))
    keep_flat = torch.zeros_like(flat_expert, dtype=torch.bool)
    if capacity > 0:
        order = _slot_order(flat_expert, weights, num_experts)
        counts = torch.bincount(flat_expert, minlength=num_experts)
        starts = torch.cumsum(counts, dim=0) - counts
        ranks = torch.arange(slots, device=flat_expert.device) - starts[flat_expert[order]]
        keep_flat[order] = ranks < capacity
    # ``dropped`` stays a device tensor on purpose: draining it costs a host sync, and this
    # runs once per MoE layer, so callers that want the number must ask for it explicitly
    # (they log it once, see ``ep_routed_dispatch``) instead of paying it every layer.
    return keep_flat.view_as(topk_idx), (~keep_flat).sum()


def _resolve_capacity_order() -> str:
    """Resolve ``HP_EP_CAPACITY_ORDER`` for the current call.
251
252
253
254
255
256
257
258
259
260
261
262
263
264
    Raises:
        ValueError: If the knob is neither; a typo must not silently keep a policy the
            operator did not ask for.
    """
    raw = os.environ.get("HP_EP_CAPACITY_ORDER", _CAPACITY_ORDER_SCORE).strip().lower()
    if raw not in _CAPACITY_ORDERS:
        raise ValueError(
            f"HP_EP_CAPACITY_ORDER must be one of {list(_CAPACITY_ORDERS)}, but got {raw!r}"
        )
    return raw


def _slot_order(
    flat_expert: "torch.Tensor",
265
266
267
268
269
270
271
272
273
274
275
    flat_weights: Optional["torch.Tensor"],
    num_experts: int,
) -> "torch.Tensor":
    """Order slots by expert, and under the ``score`` policy by descending weight as well."""
    if flat_weights is None or _resolve_capacity_order() == _CAPACITY_ORDER_FIRST:
        return _expert_major_order(flat_expert, num_experts)
    return _expert_score_order(flat_expert, flat_weights, num_experts)


def _expert_score_order(
    flat_expert: "torch.Tensor",
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

    Raises:
        ValueError: If ``flat_weights`` does not carry exactly one weight per slot.
    """
    if flat_weights.numel() != flat_expert.numel():
        raise ValueError(
            f"weights must have one entry per slot: got {flat_weights.numel()} for "
            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,
    # so the fraction stays zero and the expert-major order stands.  ``0 * inf`` cannot happen:
    # a zero span means ``inverted`` is exactly zero.
    span = inverted.max().clamp_min(torch.finfo(weight.dtype).tiny)
    # One multiply-add instead of three elementwise passes: ``expert + inverted * 0.5 / span``.
    key = torch.addcmul(flat_expert.to(torch.float32), inverted, 0.5 / span)
    return key.argsort()


def _expert_major_order(flat_expert: "torch.Tensor", num_experts: int) -> "torch.Tensor":
    """Return the permutation that groups slots by expert.
346
347
348
349
350
351
352
353
354
355
356

    Returns:
        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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43


def _optional_tolerance(tolerance: Mapping[str, Any], name: str) -> Optional[float]:
    """Return one declared tolerance without inventing an absent zero bound."""
    value = tolerance.get(name)
    return None if value is None else float(value)


def _relative_error(reference: float, candidate: float) -> float:
    """Return reference-relative error with a finite zero-safe denominator."""
    return abs(candidate - reference) / max(abs(reference), 1.0e-30)


def _combine_tolerance_checks(
        checks: Iterable[bool],
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
        *,
        owner: str,
) -> bool:
    """Combine explicitly declared numerical checks with validated semantics."""
    if combination not in ("all", "any"):
        raise ValueError(
            f"{owner} tolerance combination must be 'all' or 'any', got "
            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(
        reference: float,
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
        tolerance: Mapping[str, Any],
        prefix: str,
) -> dict[str, Any]:
    """Compare one scalar using explicitly declared absolute/relative bounds."""
    max_abs = abs(candidate - reference)
    max_rel = _relative_error(reference, candidate)
    max_abs_limit = _optional_tolerance(tolerance, f"{prefix}_max_abs")
    max_rel_limit = _optional_tolerance(tolerance, f"{prefix}_max_rel")
    if max_abs_limit is None and max_rel_limit is None:
        # Preserve the schema-v1 contract: an omitted scalar policy means
        # exact absolute equality, rather than silently disabling the gate.
        max_abs_limit = 0.0
    checks = []
    if max_abs_limit is not None:
        checks.append(max_abs <= max_abs_limit)
    if max_rel_limit is not None:
        checks.append(max_rel <= max_rel_limit)
    combination = tolerance.get(
        f"{prefix}_combination",
        tolerance.get("combination", "all"),
    )
    return {
        "status": _combine_tolerance_checks(
            checks,
            combination,
            owner=f"{prefix} scalar",
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
        candidate: list[Mapping[str, Any]],
        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


def _optional_pair_equal(
        baseline_row: Mapping[str, Any],
121
122
123
124
125
126
127
128
129
130
131
        candidate_row: Mapping[str, Any],
        field: str,
) -> bool:
    """Compare one optional field, treating absent values as unequal."""
    baseline_value = baseline_row.get(field)
    candidate_value = candidate_row.get(field)
    return (
        baseline_value is not None
        and candidate_value is not None
        and baseline_value == candidate_value
    )
136
137
138
139
140
141
142
143
144
145
146
147
148
        candidate_row: Mapping[str, Any],
        tolerance: Mapping[str, Any],
) -> tuple[Optional[float], Optional[float], bool]:
    """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,
        "norm",
146
147
148
149
150
151
152
153
154
        float(candidate_post_norm),
        tolerance,
        "norm",
    )
    return comparison["max_abs"], comparison["max_rel"], comparison["status"]


def _compare_scalar_row(
        baseline_row: Mapping[str, Any],
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
        candidate_row: Mapping[str, Any],
        tolerance: Mapping[str, Any],
) -> dict[str, Any]:
    """Compare one aligned step row against the declared scalar tolerances."""
    baseline_loss = float(baseline_row["loss"])
    candidate_loss = float(candidate_row["loss"])
    baseline_norm = float(baseline_row["grad_norm_pre_clip"])
    candidate_norm = float(candidate_row["grad_norm_pre_clip"])
    loss_delta = _scalar_metric_delta(baseline_loss, candidate_loss, tolerance, "loss")
    norm_delta = _scalar_metric_delta(baseline_norm, candidate_norm, tolerance, "norm")
    input_equal = _optional_pair_equal(baseline_row, candidate_row, "global_input_sha256")
    learning_rate_equal = _optional_pair_equal(baseline_row, candidate_row, "lr")
    step_equal = _optional_pair_equal(baseline_row, candidate_row, "step")
    post_norm_delta, post_norm_relative_delta, post_norm_equal = _post_clip_norm_delta(
        baseline_row,
        candidate_row,
        tolerance,
    )
    finite = all(
        math.isfinite(float(value))
        for value in (
            baseline_row["loss"],
            candidate_row["loss"],
178
179
180
181
182
183
184
185
186
            baseline_row["grad_norm_pre_clip"],
            candidate_row["grad_norm_pre_clip"],
        )
    )
    numerical_pass = (
        loss_delta["status"]
        and norm_delta["status"]
        and post_norm_equal
        and learning_rate_equal
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
        and post_norm_equal
        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"
    else:
        row_status = "PASS" if numerical_pass else "FAIL"
    return {
        "step": baseline_row.get("step"),
        "status": row_status,
        "loss_max_abs": loss_delta["max_abs"],
        "loss_max_rel": loss_delta["max_rel"],
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
    *,
    allow_candidate_subset: bool = False,
) -> dict[str, Any]:
    """Compare aligned loss/norm/LR rows without parsing Trainer text logs."""
    baseline = list(baseline_rows)
    candidate = list(candidate_rows)
    if not baseline or not candidate:
        return {
            "status": "BLOCKED",
            "reason": "structured scalar evidence is missing or empty",
        }
    baseline, alignment_error = _align_scalar_rows(
        baseline,
        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)
        row_status = comparison["status"]
        if row_status == "FAIL":
            status = "FAIL"
        elif row_status == "BLOCKED" and status == "PASS":
            status = "BLOCKED"
        comparisons.append(comparison)
    return {"status": status, "steps": comparisons}


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,
    }
302
303
304
305
306
307
308
309
310
311
312
313
    }


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(
        baseline: Mapping[str, Any],
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
        baseline: Mapping[str, Any],
        candidate: Mapping[str, Any],
        tolerance: Mapping[str, Any],
) -> dict[str, Any]:
    max_abs_limit = tolerance_value(tolerance, "max_abs", "atol")
    relative_l2_limit = tolerance_value(tolerance, "relative_l2", "rtol")
    tolerance_combination = tolerance.get("combination", "all")
    baseline_values = baseline.get("values")
    candidate_values = candidate.get("values")
    exact_values = baseline_values is not None and candidate_values is not None
    summary_policy_source = None
    if exact_values:
        baseline_tensor = torch.tensor(baseline_values, dtype=torch.float64)
        candidate_tensor = torch.tensor(candidate_values, dtype=torch.float64)
        difference = candidate_tensor - baseline_tensor
        max_abs = float(difference.abs().max()) if difference.numel() else 0.0
        denominator = max(
            float(torch.linalg.vector_norm(baseline_tensor)),  # pylint: disable=not-callable
            1.0e-30,
        )
        relative_l2 = (
            float(torch.linalg.vector_norm(difference))  # pylint: disable=not-callable
            / denominator
        )
        within_tolerance = _combine_tolerance_checks(
            (max_abs <= max_abs_limit, relative_l2 <= relative_l2_limit),
            tolerance_combination,
            owner="parameter",
        )
        comparison_mode = "exact_values"
        l2_norm_max_abs = None
        l2_norm_relative_error = None
        aggregate_deltas = None
    else:
        max_abs = None
        relative_l2 = None
        comparison_mode = "aggregate_summary"
        baseline_l2 = float(baseline.get("l2") or 0.0)
        candidate_l2 = float(candidate.get("l2") or 0.0)
        l2_norm_max_abs = abs(baseline_l2 - candidate_l2)
        l2_norm_relative_error = _relative_error(baseline_l2, candidate_l2)
        aggregate_deltas = {
            name: abs(float(baseline.get(name) or 0.0) - float(candidate.get(name) or 0.0))
            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",
        )
        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",
        )
        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(
            summary_checks,
            summary_combination,
            owner="parameter summary",
        )
        tolerance_combination = summary_combination
    layout_equal = baseline.get("layout", {}).get("global_shape") == candidate.get(
        "layout", {}
    ).get("global_shape")
    finite = bool(baseline.get("finite")) and bool(candidate.get("finite"))
    status = "PASS" if finite and layout_equal and within_tolerance else "FAIL"
    result = {
        "status": status,
        "comparison_mode": comparison_mode,
        "max_abs": max_abs,
        "relative_l2": relative_l2,
399
400
401
402
403
404
405
406
407
408
409
        "finite": finite,
        "full_value_comparison": exact_values,
        "tolerance_combination": tolerance_combination,
    }
    if summary_policy_source is not None:
        result["summary_policy_source"] = summary_policy_source
    return result


def _first_rank_rows(
        rows: Iterable[Mapping[str, Any]],
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
def _first_rank_rows(
        rows: Iterable[Mapping[str, Any]],
) -> dict[tuple[Any, ...], Mapping[str, Any]]:
    """Index one canonical row per step, stage, and parameter."""
    indexed = {}
    for row in rows:
        key = (row.get("step"), row.get("stage"), row.get("name"))
        if key not in indexed or int(row.get("rank", 0)) < int(indexed[key].get("rank", 0)):
            indexed[key] = row
    return indexed


def _zero_optimizer_state(row: Optional[Mapping[str, Any]]) -> bool:
    """Treat a load-primed zero state as equivalent to an absent lazy state."""
    states = None if row is None else row.get("optimizer_state")
    return bool(states) and all(
        bool(summary.get("finite")) and float(summary.get("l2") or 0.0) == 0.0
        for summary in states.values()
    )
431
432
433
434
435
436
437
438
439
440
441
442
443
        candidate: Mapping[tuple[Any, ...], Mapping[str, Any]],
        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(
        key: tuple[Any, ...],
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
        candidate_row: Mapping[str, Any],
        tolerance: Mapping[str, Any],
) -> list[dict[str, Any]]:
    """Compare every declared optimizer-state summary for one parameter."""
    baseline_states = baseline_row.get("optimizer_state", {})
    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,
        )
        comparison["key"] = (*key, state_name)
        comparisons.append(comparison)
    return comparisons


def _compare_parameter_probe_row(
        key: tuple[Any, ...],
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
        candidate_row: Mapping[str, Any],
        tolerance: Mapping[str, Any],
) -> dict[str, Any]:
    """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(
        baseline_dir: str | Path,
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
    identical. Cross-topology cases therefore validate their live layouts and
    checkpoint recovery, but reserve numerical state comparison for matching
    topologies.
    """
    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)
    candidate = _first_rank_rows(candidate_rows)
    unmatched = _unmatched_probe_keys(baseline, candidate, allow_candidate_subset)
    zero_initialized = sorted(
        (
            key
            for key in unmatched
            if _zero_optimizer_state(baseline.get(key))
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
            or _zero_optimizer_state(candidate.get(key))
        ),
        key=repr,
    )
    missing = sorted(set(unmatched) - set(zero_initialized), key=repr)
    comparisons = []
    skipped_optimizer_states = 0
    for key in sorted(set(baseline).intersection(candidate), key=repr):
        baseline_row = baseline[key]
        candidate_row = candidate[key]
        if "optimizer_state" in baseline_row or "optimizer_state" in candidate_row:
            if not compare_optimizer_states:
                skipped_optimizer_states += len(
                    set(baseline_row.get("optimizer_state", {}))
                    | set(candidate_row.get("optimizer_state", {}))
                )
                continue
            comparisons.extend(
                _compare_optimizer_state_probes(
                    key,
                    baseline_row,
                    candidate_row,
541
542
543
544
545
546
547
548
549
550
551
552
553
554
                    candidate_row,
                    tolerance,
                )
            )
            continue
        comparisons.append(
            _compare_parameter_probe_row(key, baseline_row, candidate_row, tolerance)
        )
    status = "FAIL" if missing or any(row["status"] == "FAIL" for row in comparisons) else "PASS"
    return {
        "status": status,
        "missing": [repr(key) for key in missing],
        "zero_initialized_optimizer_states": [repr(key) for key in zero_initialized],
        "optimizer_state_numeric_comparison": (
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
598
        warmup_steps: int = 1,
        memory_rows: Optional[Iterable[Mapping[str, Any]]] = None,
) -> dict[str, Any]:
    """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
        if row.get("tokens_per_second") is not None
    ]
    sample_rates = [
        float(row["samples_per_second"])
        for row in values
        if row.get("samples_per_second") is not None
    ]
    allocator_rows = list(memory_rows) if memory_rows is not None else values
    peak_allocated = [
        int(row["peak_memory_allocated_bytes"])
        for row in allocator_rows
        if row.get("peak_memory_allocated_bytes") is not None
    ]
    peak_reserved = [
        int(row["peak_memory_reserved_bytes"])
        for row in allocator_rows
        if row.get("peak_memory_reserved_bytes") is not None
    ]
    return {
        "status": "MEASURED",
        "steady_steps": len(step_times),
        "step_time_p50_seconds": statistics.median(step_times),
        "step_time_p90_seconds": step_times[p90_index],
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638


def _group_checkpoint_events(files: list[Path]) -> dict[str, list[Path]]:
    """Group every rank file by its versioned checkpoint event name."""
    grouped: dict[str, list[Path]] = {}
    for path in files:
        event_name = path.name.rsplit("_rank", 1)[0]
        grouped.setdefault(event_name, []).append(path)
    return grouped


def _checkpoint_event_order(event_name: str) -> tuple[int, str]:
    """Sort numbered checkpoint events after legacy unnumbered events."""
    suffix = event_name.rsplit("_", 1)[-1]
    return (int(suffix), event_name) if suffix.isdigit() else (-1, event_name)


def _latest_checkpoint_event(events: Mapping[str, list[Path]], preferred: str) -> str:
    """Choose the newest numbered event, retaining legacy-name support."""
    matching = [
        event_name
        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]]:
    """Load checkpoint tensor-layout maps for all selected ranks."""
    return [
        json.loads(path.read_text(encoding="utf-8")).get("tensor_layouts", {})
        for path in files
    ]
642
643
644
645
646
647
648
649
650
651
652
653
654
655
        baseline: Mapping[str, Any],
        candidate: Mapping[str, Any],
) -> tuple[list[str], list[str]]:
    """List one-sided keys, excluding lazily initialized optimizer state."""
    baseline_only = set(baseline) - set(candidate)
    candidate_only = set(candidate) - set(baseline)
    initialized_optimizer_entries = sorted(
        name for name in candidate_only if name.startswith("optimizer.")
    )
    missing = sorted(
        baseline_only
        | {
            name
            for name in candidate_only
655
656
657
658
659
660
661
662
663
            for name in candidate_only
            if name not in initialized_optimizer_entries
        }
    )
    return missing, initialized_optimizer_entries


def _layout_mismatches(
        baseline: Mapping[str, Any],
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
        candidate: Mapping[str, Any],
        same_topology: bool,
) -> list[dict[str, Any]]:
    """Diff shared tensor layouts over the topology-appropriate field set."""
    fields = ["global_shape"]
    if same_topology:
        fields.extend(("mesh_dim_names", "mesh_shape", "placements"))
    mismatches = []
    for name in sorted(set(baseline).intersection(candidate)):
        baseline_layout = baseline[name]
        candidate_layout = candidate[name]
        differences = {
            field: {
                "baseline": baseline_layout.get(field),
                "candidate": candidate_layout.get(field),
            }
679
680
681
682
683
684
685
686
687
688
689
            }
            for field in fields
            if baseline_layout.get(field) != candidate_layout.get(field)
        }
        if differences:
            mismatches.append({"name": name, "differences": differences})
    return mismatches


def _rank_consistency_inconsistencies(
        side_name: str,
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
        side_name: str,
        rank_layouts: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    """Flag keys whose rank-local payloads disagree on presence or shape."""
    inconsistencies = []
    all_names = set().union(*(set(layouts) for layouts in rank_layouts))
    for name in sorted(all_names):
        values = [layouts.get(name) for layouts in rank_layouts]
        global_shapes = {
            tuple(value.get("global_shape", ()))
            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",
706
707
708
709
710
711
712
713
714
                    "name": name,
                    "reason": "rank-local payloads disagree on key presence or global shape",
                }
            )
    return inconsistencies


def _same_topology_rank_inconsistencies(
        baseline_ranks: list[dict[str, Any]],
714
715
716
717
718
719
720
721
722
723
724
        baseline_ranks: list[dict[str, Any]],
        candidate_ranks: list[dict[str, Any]],
) -> 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
733
734
735
736
737
738
739
740
                "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)):
            if baseline_layouts[name].get("local_shape") != candidate_layouts[name].get(
                    "local_shape"
            ):
                inconsistencies.append(
                    {
                        "side": "comparison",
                        "rank": rank,
                        "name": name,
740
741
742
743
744
745
746
747
748
                        "name": name,
                        "reason": "same-topology local shard shape changed",
                    }
                )
    return inconsistencies


def compare_checkpoint_layouts(
        baseline_dir: str | Path,
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
        *,
        same_topology: bool,
) -> dict[str, Any]:
    """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("_")
    baseline_event = (
        f"before_save_{checkpoint_identity}"
        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,
770
771
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
            "reason": "baseline checkpoint evidence for the restored checkpoint is missing",
            "baseline_event": baseline_event,
            "candidate_event": candidate_event,
        }
    baseline_ranks = _load_rank_layouts(baseline_events[baseline_event])
    candidate_ranks = _load_rank_layouts(candidate_events[candidate_event])
    baseline = baseline_ranks[0]
    candidate = candidate_ranks[0]
    missing, initialized_optimizer_entries = _missing_layout_names(baseline, candidate)
    mismatches = _layout_mismatches(baseline, candidate, same_topology)
    rank_inconsistencies = []
    for side_name, rank_layouts in (
        ("baseline", baseline_ranks),
        ("candidate", candidate_ranks),
    ):
        rank_inconsistencies.extend(
            _rank_consistency_inconsistencies(side_name, rank_layouts)
        )
    if same_topology:
        rank_inconsistencies.extend(
            _same_topology_rank_inconsistencies(baseline_ranks, candidate_ranks)
        )
    return {
        "status": "FAIL" if missing or mismatches or rank_inconsistencies else "PASS",
        "missing": missing,
        "mismatches": mismatches,
        "rank_inconsistencies": rank_inconsistencies,
hyper_parallel/tools/model_integration/case_matrix.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
 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
    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")
    pieces = []
    for name in ordered:
        if name not in topology:
            continue
        value = topology[name]
        if name == "recompute" and isinstance(value, Mapping):
            if "layer_count" in value:
                value = f"count{value['layer_count']}"
            elif "layer_indices" in value:
                value = "indices" + "-".join(str(index) for index in value["layer_indices"])
        pieces.append(f"{name}{value}")
    name = "_".join(str(piece).replace("/", "-") for piece in pieces)
    return f"{prefix}_{name}" if prefix else name


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"]
        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
        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]:
    """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(
            resolved["recompute"],
            f"{path}.recompute",
        )
    return resolved


def _acceptance_class(
        baseline: Mapping[str, Any],
135
136
137
138
139
140
141
142
143
144
145
146
147
        baseline: Mapping[str, Any],
        candidate: Mapping[str, Any],
) -> str:
    """Classify tolerances from the parallel topology actually launched."""
    for field_name in _PARALLEL_TOPOLOGY_FIELDS:
        default = _TOPOLOGY_DEFAULTS[field_name]
        if baseline.get(field_name, default) != candidate.get(field_name, default):
            return "cross_topology"
    return "same_topology"


def _axis_cases(
        matrix: Mapping[str, Any],
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
        baseline: Mapping[str, Any],
        seen: set[str],
) -> 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)
            raise ValueError(
                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)
            if key in seen:
                continue
            seen.add(key)
            cases.append(
                ValidationCase(_case_name(topology, f"axis-{axis}"), topology)
            )
    return cases


def _combined_cases(
        matrix: Mapping[str, Any],
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
        baseline: Mapping[str, Any],
        seen: set[str],
) -> list[ValidationCase]:
    """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


def _recompute_cases(
        matrix: Mapping[str, Any],
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
        baseline: Mapping[str, Any],
        seen: set[str],
) -> list[ValidationCase]:
    """Generate one case per requested recompute layer selection."""
    requested_selections = matrix.get(
        "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,
            f"matrix.recompute_selections[{index}]",
        )
        topology = dict(baseline)
        topology.update(
            _validate_topology(
                matrix.get("recompute_topology") or {},
                "matrix.recompute_topology",
            )
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
                matrix.get("recompute_topology") or {},
                "matrix.recompute_topology",
            )
        )
        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)
        )
    return cases


def _ep1_fallback_case(
        cases: list[ValidationCase],
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
        cases: list[ValidationCase],
        baseline: Mapping[str, Any],
) -> Optional[ValidationCase]:
    """Retain a single-device-EP reference when every case parallelizes experts."""
    if int(baseline.get("ep", 1)) <= 1 or any(
        int(case.topology.get("ep", 1)) == 1 for case in cases
    ):
        return None
    topology = dict(baseline)
    topology["ep"] = 1
    return ValidationCase(_case_name(topology, "axis-ep"), topology)


def _production_validate_cases(
        matrix: Mapping[str, Any],
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
        matrix: Mapping[str, Any],
        baseline: Mapping[str, Any],
) -> list[ValidationCase]:
    """Generate the production/validate placement pair when declared."""
    production_validate = matrix.get("production_validate_pair")
    if not production_validate:
        return []
    production_validate = _validate_topology(
        production_validate,
        "matrix.production_validate_pair",
    )
    cases = []
    for mode in ("production", "validate"):
        topology = dict(baseline)
        topology.update(production_validate)
        topology["validate_placement"] = mode == "validate"
        cases.append(
            ValidationCase(
                _case_name(topology, mode),
                topology,
                kind="production_validate",
268
269
270
271
272
273
274
275
276
                topology,
                kind="production_validate",
            )
        )
    return cases


def _resume_cases(
        matrix: Mapping[str, Any],
276
277
278
279
280
281
282
283
284
285
286
        matrix: Mapping[str, Any],
        baseline: Mapping[str, Any],
) -> 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",
286
287
288
289
290
291
292
293
294
295
296
                kind="resume",
                metadata={"resume": "same_topology"},
            )
        )
    if matrix.get("cross_topology_resume"):
        topology = dict(baseline)
        topology.update(
            _validate_topology(
                matrix["cross_topology_resume"],
                "matrix.cross_topology_resume",
            )
294
295
296
297
298
299
300
301
302
303
                matrix["cross_topology_resume"],
                "matrix.cross_topology_resume",
            )
        )
        if _acceptance_class(baseline, topology) == "same_topology":
            raise ValueError(
                "matrix.cross_topology_resume must change at least one of "
                f"{_PARALLEL_TOPOLOGY_FIELDS}. A recompute-only change is not a "
                "cross-topology restore; same_topology_resume always uses the "
                "baseline recompute policy, and a recompute-only resume matrix "
302
303
304
305
306
307
308
309
310
                "cross-topology restore; same_topology_resume always uses the "
                "baseline recompute policy, and a recompute-only resume matrix "
                "is not supported"
            )
        cases.append(
            ValidationCase(
                "cross_topology_resume",
                topology,
                kind="resume",
313
314
315
316
317
318
319
320
321
                    "prepare_topology": dict(baseline),
                },
            )
        )
    return cases


def _finalize_cases(
        cases: list[ValidationCase],
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
        baseline: Mapping[str, Any],
        topology_constraints: Iterable[TopologyConstraint],
) -> tuple[ValidationCase, ...]:
    """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)
        metadata["acceptance"] = _acceptance_class(baseline, case.topology)
        resolved_cases.append(dataclasses.replace(case, metadata=metadata))
    return tuple(resolved_cases)


def generate_validation_cases(
    matrix: Mapping[str, Any],
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
    recompute_policy: Optional[RecomputePolicy] = None,
    topology_constraints: Iterable[TopologyConstraint] = (),
) -> 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))
    cases.extend(_combined_cases(matrix, baseline, seen))
    if recompute_policy is not None:
        cases.extend(_recompute_cases(matrix, baseline, seen))
    ep1_case = _ep1_fallback_case(cases, baseline)
    if ep1_case is not None:
        cases.insert(1, ep1_case)
    cases.extend(_production_validate_cases(matrix, baseline))
    cases.extend(_resume_cases(matrix, baseline))
    return _finalize_cases(cases, baseline, topology_constraints)


__all__ = [
    "ValidationCase",
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63


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)


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(
        target_name: str,
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
        intentional_missing: set[str],
        unsupported_missing: set[str],
) -> str:
    """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,
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
        unexpected: set[str],
        inference_only: set[str],
) -> list[dict[str, Any]]:
    """Build one coverage entry per target and per unexpected source key."""
    entries = []
    for target_name in sorted(model.state_dict()):
        matching = [transform for transform in transforms if _matches_target(transform, target_name)]
        entries.append(
            {
                "target_key": target_name,
                "category": _target_category(
                    target_name,
105
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"
116
117
118
119
120
121
122
123
124
                    else "unsupported_architectural_mismatch"
                ),
            }
        )
    return entries


def _coverage_findings(
        unsupported_missing: set[str],
125
126
127
128
129
130
131
132
133
134
135
        unsupported_unexpected: set[str],
        non_reversible: list[str],
) -> list[ModelIntegrationFinding]:
    """Emit ERROR findings for unsupported keys and one-way transforms."""
    findings = []
    if unsupported_missing or unsupported_unexpected:
        findings.append(
            ModelIntegrationFinding(
                code="HP-CKPT-001",
                phase="D5",
                owner_fqn="<root>",
149
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>",
166
167
168
169
170
171
172
173
174
                remediation="provide inverse conversion operations and add a tensor round-trip parity case",
                related_config=("model adapter checkpoint",),
            )
        )
    return findings


def build_checkpoint_coverage(
        model: Any,
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
        model: Any,
        validation_spec: Optional[CheckpointValidationSpec] = None,
) -> tuple[dict[str, Any], list[ModelIntegrationFinding]]:
    """Classify every finalized persistent state key using the last load report."""
    report = getattr(model, "_hp_checkpoint_load_report", None)
    if report is None:
        return {
            "status": "NOT_LOADED",
            "entries": [],
            "reason": "model was initialized from scratch or no load report was retained",
        }, []
    loaded = set(getattr(report, "loaded_keys", ()))
    missing = set(getattr(report, "missing_keys", ()))
    unexpected = set(getattr(report, "unexpected_keys", ()))
    validation_spec = validation_spec or CheckpointValidationSpec()
    intentional_missing = {
        name
        for name in missing
        if _matches_any(name, validation_spec.training_only_target_patterns)
    }
    inference_only = {
        name
        for name in unexpected
        if _matches_any(name, validation_spec.inference_only_source_patterns)
    }
    unsupported_missing = missing - intentional_missing
    unsupported_unexpected = unexpected - inference_only
    transforms = tuple(getattr(model, "_weight_conversions", ()) or ())
    non_reversible = _non_reversible_transforms(transforms)
    entries = _coverage_entries(
        model,
        transforms,
        loaded,
        intentional_missing,
208
209
210
211
212
213
214
215
216
217
218
219
220
221
        unsupported_missing,
        unexpected,
        inference_only,
    )
    findings = _coverage_findings(
        unsupported_missing,
        unsupported_unexpected,
        non_reversible,
    )
    return {
        "status": "FAIL" if findings else "PASS",
        "loaded": len(loaded),
        "missing": len(missing),
        "unexpected": len(unexpected),
hyper_parallel/tools/model_integration/cli.py
 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
_HCCL_PORT_STRIDE = 256


def _manifest_from_args(args: argparse.Namespace) -> ValidationManifest:
    manifest = load_manifest(args.manifest, output_dir=args.output_dir)
    manifest.validate_for(args.command)
    return manifest


def _store(manifest: ValidationManifest) -> EvidenceStore:
    store = EvidenceStore(manifest.output_dir)
    store.write_yaml("manifest.resolved.yaml", manifest.to_dict())
    store.capture_environment()
    return store


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"
        )
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
    store: EvidenceStore,
    manifest: ValidationManifest,
) -> 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"
        )
    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 (
        ("structure_findings", "check/findings.json", "check/findings.json"),
        ("module_parity", "module_parity/comparison.json", "module_parity/comparison.json"),
        ("checkpoint_coverage", "checkpoint/coverage.json", "checkpoint/coverage.json"),
    ):
        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)
    for state in (
        IntegrationState.DISCOVERED,
        IntegrationState.STRUCTURE_VALIDATED,
        IntegrationState.MODULE_PARITY_PASSED,
    ):
        store.advance_if_before(
            state,
            ("integration_handoff.yaml", *imported_evidence),
            reason="imported passing integration handoff",
        )
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
        )


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)
    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


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", {})),
    )
189
190
191
192
193
194
195
196
197
198
199
def _inspect_side(
    manifest: ValidationManifest,
    section_name: str,
) -> ModelInventory:
    section = getattr(manifest, section_name)
    if isinstance(section.get("builder"), Mapping):
        return build_model_inventory(
            _build_declared_model(manifest, section_name),
            source={
                "section": section_name,
                "revision": section.get("revision"),
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")},
    )
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


def command_inspect(args: argparse.Namespace) -> int:
    """Create reference/candidate inventories and a classified diff."""
    manifest = _manifest_from_args(args)
    store = _store(manifest)
    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(
        "inventory/structure_diff.json",
        {"status": "REVIEW_REQUIRED" if differences else "PASS", "differences": differences},
    )
    store.write_json("source_fingerprints.json", manifest.fingerprint_paths())
    store.advance_if_before(
        IntegrationState.DISCOVERED,
        (
            "environment.json",
            "manifest.resolved.yaml",
237
238
239
240
241
242
243
244
245
246
            "inventory/candidate.json",
            "inventory/structure_diff.json",
        ),
    )
    print(store.root)
    return 0


_LICENSE = """# Copyright 2026 Huawei Technologies Co., Ltd
#
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275


def command_scaffold(args: argparse.Namespace) -> int:
    """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 = {
        destination / "__init__.py": _LICENSE + f'"""{family} model integration package."""\n',
        adapter_dir / "__init__.py": _LICENSE + f'"""{family} adapter providers."""\n',
        adapter_dir / "registration.py": _LICENSE
        + '"""Register model-owned integration providers."""\n\n'
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
        + "    def test_registration(self) -> None:\n"
        + '        """Require the generated adapter to be discoverable."""\n'
        + f"        self.assertIsNotNone(get_model_adapter({family!r}))\n",
    }
    for path, content in files.items():
        path.write_text(content, encoding="utf-8")
    store = _store(manifest)
    store.write_json(
        "scaffold.json",
        {"files": tuple(str(path.relative_to(destination)) for path in files)},
    )
    print(destination)
    return 0


def command_check(args: argparse.Namespace) -> int:
    """Run strict final-tree/materialization/FSDP ownership diagnostics."""
    manifest = _manifest_from_args(args)
    store = _store(manifest)
    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
365
366
367
368
369
370
371
372
373
374
375
            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()},
    )
    store.write_json("check/findings.json", report.to_dict())
    store.write_json("check/fsdp_units.json", fsdp_units)
    store.write_json(
        "checkpoint/coverage.json",
        report.metadata.get("checkpoint_coverage", {}),
    )
    store.write_csv(
        "check/parameter_ownership.csv",
        (entry.to_dict() for entry in ownership),
        fieldnames=(
            "parameter_fqn",
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
            "global_shape",
            "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"),
    )
    print(store.root)
    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],
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
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
        acceptance: Mapping[str, Any],
        profile: str,
) -> 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()
        if name not in profile_names
    }
    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"]
        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)
    return tolerance


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
500
501
502
503
504
505
506
507
508
                "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."""
    if phase == "initialize":
        return _SHARED_INITIAL_CHECKPOINT_STEP
    if phase == "prepare":
        return split_step
    return steps


def _shared_checkpoint_overrides(
        manifest: ValidationManifest,
508
509
510
511
512
513
514
515
516
517
518
519
520
        manifest: ValidationManifest,
        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"
    )
    # The warm-start step exists to align model/optimizer/RNG state. Every
    # measured case must replay from the same configured data start; a
520
521
522
523
524
525
526
527
528
    # measured case must replay from the same configured data start; a
    # stateful Online cursor may also be impossible to restore after TP/CP
    # changes the DP world size. True resume phases keep the default and
    # restore the cursor saved at resume_split_step.
    return {
        "checkpoint.restore_from": str(
            initial_checkpoint_dir
            / f"{STEP_PREFIX}{_SHARED_INITIAL_CHECKPOINT_STEP}"
        ),
538
539
540
541
542
543
544
545
546
547
548
549
550
        resume_dir: Path,
        split_step: int,
) -> dict[str, Any]:
    """Resolve checkpoint save/restore overrides for one launch phase."""
    initial_checkpoint_dir = (
        manifest.output_dir / "cases" / "baseline" / "initial_checkpoint"
    )
    if phase == "initialize":
        return {
            "checkpoint.save_ckpt": True,
            "checkpoint.save_steps": 1,
            "checkpoint.save_epochs": 0,
            "checkpoint.checkpoint_dir": str(initial_checkpoint_dir),
548
549
550
551
552
553
554
555
556
557
            "checkpoint.save_steps": 1,
            "checkpoint.save_epochs": 0,
            "checkpoint.checkpoint_dir": str(initial_checkpoint_dir),
        }
    if phase == "prepare":
        return {
            "checkpoint.save_ckpt": True,
            "checkpoint.save_steps": split_step,
            "checkpoint.save_epochs": 0,
            "checkpoint.checkpoint_dir": str(resume_dir),
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
            "checkpoint.save_steps": split_step,
            "checkpoint.save_epochs": 0,
            "checkpoint.checkpoint_dir": str(resume_dir),
        }
    if phase == "restore":
        return {
            "checkpoint.save_ckpt": False,
            "checkpoint.restore_from": str(resume_dir / f"{STEP_PREFIX}{split_step}"),
            "checkpoint.checkpoint_dir": str(resume_dir),
        }
    checkpoint_enabled = case.name == "baseline" and bool(
        manifest.matrix.get("same_topology_resume")
        or manifest.matrix.get("cross_topology_resume")
    )
    overrides: dict[str, Any] = {"checkpoint.save_ckpt": checkpoint_enabled}
    if checkpoint_enabled:
        overrides.update(
            {
                "checkpoint.save_steps": split_step,
                "checkpoint.save_epochs": 0,
                "checkpoint.checkpoint_dir": str(evidence_dir / "dcp"),
574
575
576
577
578
579
580
581
582
                "checkpoint.save_epochs": 0,
                "checkpoint.checkpoint_dir": str(evidence_dir / "dcp"),
            }
        )
    return overrides


def _trainer_launch_argv(
    manifest: ValidationManifest,
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
    resume_dir: Path,
    topology: Optional[Mapping[str, Any]] = None,
) -> list[str]:
    """Build the standard Trainer command from one concise launcher declaration."""
    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 = [
        "torchrun",
        "--standalone",
        f"--nproc_per_node={manifest.matrix['devices']}",
        "--module",
602
603
604
605
606
607
608
609
610
        "--module",
        module,
        str(config),
    ]
    controlled_overrides = {
        "training.train_iters": _phase_train_iters(phase, steps, split_step),
        # Split-run preparation stops early but must retain the full-run LR
        # curve so its checkpoint is numerically comparable with the baseline.
        "training.lr_scheduler_iters": steps,
619
620
621
622
623
624
625
626
627
628
629
630
            **topology.get("recompute", {"layer_count": 0}),
        },
        "model_integration.mode": "runtime",
    }
    if "validate_placement" in topology:
        controlled_overrides["model.validate_placement"] = topology["validate_placement"]
    controlled_overrides.update(_shared_checkpoint_overrides(manifest, phase))
    controlled_overrides.update(
        _phase_checkpoint_overrides(
            manifest,
            case,
            phase,
632
633
634
635
636
637
638
639
640
641
642
643
644
            resume_dir,
            split_step,
        )
    )
    tokens.extend(
        f"--{name}={value}"
        for name, value in controlled_overrides.items()
    )
    return tokens


def _resolve_case_launches(
    manifest: ValidationManifest,
645
646
647
648
649
650
651
652
653
654
655
656
657
658
    case: Any,
    case_dir: Path,
) -> list[tuple[str, list[str], Path]]:
    """Resolve train or prepare/restore commands without executing them."""
    resume_dir = case_dir / "resume_checkpoint"
    if case.kind != "resume":
        launches = []
        if case.name == "baseline" and manifest.matrix.get("shared_initial_checkpoint"):
            initialize_dir = case_dir / "initialize"
            launches.append(
                (
                    "initialize",
                    _trainer_launch_argv(
                        manifest,
663
664
665
666
667
668
669
670
671
                    ),
                    initialize_dir,
                )
            )
        launches.append(
            (
                "train",
                _trainer_launch_argv(
                    manifest, case, "train", case_dir, resume_dir
672
673
674
675
676
677
678
679
680
681
682
683
                ),
                case_dir,
            )
        )
        return launches
    prepare_dir = case_dir / "prepare"
    prepare_topology = case.metadata.get("prepare_topology", case.topology)
    return [
        (
            "prepare",
            _trainer_launch_argv(
                manifest,
708
709
710
711
712
713
714
715
716
717
    port_slot: int = 0,
    port_stride: int = 1,
) -> dict[str, str]:
    """Add case identity and isolate sequential distributed launch ports."""
    environment = os.environ.copy()
    environment.update(
        {
            "HP_MODEL_INTEGRATION_CASE": case.name,
            "HP_MODEL_INTEGRATION_KIND": case.kind,
            "HP_MODEL_INTEGRATION_PHASE": phase,
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
            "HP_MODEL_INTEGRATION_RESUME_DIR": str(resume_dir),
            "HYPER_PARALLEL_MODEL_INTEGRATION_OUTPUT_DIR": str(evidence_dir),
        }
    )
    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}"
            )
        environment["HCCL_IF_BASE_PORT"] = str(isolated_port)
    return environment


def _execute_case_launch(
    manifest: ValidationManifest,
746
747
748
749
750
751
752
753
754
755
756
757
758
    launch: Mapping[str, Any],
    resume_dir: Path,
) -> tuple[Optional[int], Optional[str]]:
    """Execute one resolved process and persist its stdout and stderr."""
    phase = launch["phase"]
    evidence_dir = Path(launch["evidence_dir"])
    evidence_dir.mkdir(parents=True, exist_ok=True)
    try:
        result = subprocess.run(
            launch["argv"],
            check=False,
            capture_output=True,
            text=True,
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
            ),
            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


def _run_validation_case(
    manifest: ValidationManifest,
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,
    )
848
849
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,
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
    candidate_dir: Path,
    launch_statuses: tuple[str, str],
) -> dict[str, Any]:
    """Compare one candidate against the baseline using declared contracts."""
    try:
        tolerance_name = case.metadata["acceptance"]
    except (AttributeError, KeyError) as error:
        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
940
941
942
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
969
970
971
972
973
974
975
976
977
            "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."""
    manifest = _manifest_from_args(args)
    store = _store(manifest)
    _require_precision_handoff(store, manifest)
    validation_spec = _validation_spec(manifest)
    try:
        cases = generate_validation_cases(
            manifest.matrix,
            _recompute_policy(manifest),
            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
986
987
988
989
990
991
992
            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."""
    if not path.exists():
        return None
    return json.loads(path.read_text(encoding="utf-8"))


def _report_entries(
    findings: Optional[Mapping[str, Any]],
 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
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
            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."""
    cases_path = store.path("cases/resolved_cases.json")
    if not cases_path.is_file():
        return []
    payload = json.loads(cases_path.read_text(encoding="utf-8"))
    return payload if isinstance(payload, list) else []


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 {}


def _case_category(case: Mapping[str, Any]) -> str:
    """Classify one case without inflating strategy coverage with mechanics."""
    name = str(case.get("name", ""))
    if name == "baseline":
        return "baseline"
    if name.startswith(("axis-", "combined-")):
        return "strategy"
    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."""
    counts = {
        "baseline": 0,
        "strategy": 0,
        "recompute": 0,
        "resume": 0,
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
        "resume": 0,
        "production_validate": 0,
        "other": 0,
    }
    for case in resolved_cases:
        counts[_case_category(case)] += 1
    return counts


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]:
    """Summarize real matrix coverage separately from resume mechanics."""
    resolved_cases = _resolved_report_cases(store)
    counts = _coverage_counts(resolved_cases)
    if language == "zh":
        entries = [
            f"基线={counts['baseline']},策略泛化={counts['strategy']},"
            f"重计算={counts['recompute']},断点续训={counts['resume']},"
            f"生产/Validate 对照={counts['production_validate']}"
        ]
1095
1096
1097
1098
1099
1100
1101
1102
1103
            f"重计算={counts['recompute']},断点续训={counts['resume']},"
            f"生产/Validate 对照={counts['production_validate']}"
        ]
    else:
        entries = [
            f"baseline={counts['baseline']}, strategy_generalization={counts['strategy']}, "
            f"recompute={counts['recompute']}, resume={counts['resume']}, "
            f"production_validate={counts['production_validate']}"
        ]
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
1137
1138
1139
1140
1141
1142
            f"recompute={counts['recompute']}, resume={counts['resume']}, "
            f"production_validate={counts['production_validate']}"
        ]

    manifest = _report_manifest(store)
    exclusion_prefix = "排除项" if language == "zh" else "excluded"
    entries.extend(
        f"{exclusion_prefix}: {note}"
        for note in _coverage_notes(manifest, language)
    )
    return entries


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"),
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
        label: str,
        language: str,
) -> 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'))},"
            f"组合规则={tolerance.get('combination', 'all')};仅摘要不声明逐元素误差,"
            "L2 范数相对误差 <= "
1167
1168
1169
1170
1171
1172
1173
1174
1175
            f"组合规则={tolerance.get('combination', 'all')};仅摘要不声明逐元素误差,"
            "L2 范数相对误差 <= "
            f"{_format_report_number(summary.get('l2_norm_relative'))}"
        )
    return (
        f"{label}: exact-value max_abs <= {_format_report_number(tolerance.get('max_abs'))}, "
        f"relative_l2 <= {_format_report_number(tolerance.get('relative_l2'))}, "
        f"combination={tolerance.get('combination', 'all')}; summary-only probes make no "
        "elementwise claim, L2-norm relative error <= "
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195


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")
    cross_parameters = _parameter_tolerance(acceptance, "cross_topology")
    if language == "zh":
        return [
            "同拓扑:"
            f"loss 最大绝对误差 <= {_format_report_number(same.get('loss_max_abs'))},"
            f"loss 最大相对误差 <= {_format_report_number(same.get('loss_max_rel'))},"
            f"梯度范数最大绝对误差 <= {_format_report_number(same.get('norm_max_abs'))},"
1204
1205
1206
1207
1208
1209
1210
1211
1212
            _parameter_acceptance_entry(parity_parameters, "模块对拍参数", "zh"),
            _parameter_acceptance_entry(same_parameters, "同拓扑参数", "zh"),
            _parameter_acceptance_entry(cross_parameters, "跨拓扑参数", "zh"),
        ]
    return [
        "same topology: "
        f"loss max abs <= {_format_report_number(same.get('loss_max_abs'))}, "
        f"loss max rel <= {_format_report_number(same.get('loss_max_rel'))}, "
        f"gradient-norm max abs <= {_format_report_number(same.get('norm_max_abs'))}, "
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'))}, "
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
    manifest: Mapping[str, Any],
    language: str,
) -> list[str]:
    """Render topology, thresholds, scalar gates, probes, and DCP per case."""
    comparisons = {} if comparison is None else comparison.get("comparisons", {})
    acceptance = manifest.get("acceptance", {})
    matrix = manifest.get("matrix", {})
    default_steps = matrix.get("steps", "n/a") if isinstance(matrix, Mapping) else "n/a"
    entries = []
    category_names = {
        "en": {
            "baseline": "baseline",
            "strategy": "strategy generalization",
            "recompute": "recompute",
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
            "production_validate": "生产/Validate 对照",
            "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,
1369
1370
1371
1372
1373
1374
1375
1376
1377
                acceptance,
                language,
            )
        )
    return entries or (["未执行"] if language == "zh" else ["not executed"])


def _worst_case_entries(
    comparison: Optional[Mapping[str, Any]],
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
    comparison: Optional[Mapping[str, Any]],
    language: str,
) -> list[str]:
    """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]}",
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
    parity: Optional[Mapping[str, Any]],
    language: str,
) -> 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]],
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
    comparison: Optional[Mapping[str, Any]],
    language: str,
) -> list[str]:
    """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'))}; "
1503
1504
1505
1506
1507
1508
1509
1510
1511
                f"samples/s p50={_format_report_number(value.get('samples_per_second_p50'))}; "
                f"peak allocated={_format_report_number(allocated_mib)} MiB; "
                f"reserved={_format_report_number(reserved_mib)} MiB."
            )
    return entries or (["未测量"] if language == "zh" else ["not measured"])


def _reproducibility_entries(
    store: EvidenceStore,
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
    manifest: Mapping[str, Any],
    language: str,
) -> list[str]:
    """Render revisions and import paths needed to replay the evidence."""
    environment = environment or {}
    torch_info = environment.get("torch") or {}
    transformers_info = environment.get("transformers") or {}
    framework_info = environment.get("hyper_parallel") or {}
    reference = manifest.get("reference", {})
    labels = {
        "en": ("resolved manifest", "environment", "repository revision", "framework import", "reference source"),
        "zh": ("已解析清单", "运行环境", "代码仓 revision", "框架导入路径", "参考源码"),
    }[language]
    return [
        f"{labels[0]}: `{store.path('manifest.resolved.yaml').name}`",
        f"{labels[1]}: `{store.path('environment.json').name}`",
        f"{labels[2]}: `{environment.get('git_revision', 'n/a')}`",
        f"{labels[3]}: `{framework_info.get('import_path', 'n/a')}`",
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
    findings: Optional[Mapping[str, Any]],
    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 = [
            f"状态={findings.get('status')};model_type={metadata.get('model_type', 'n/a')};"
            f"模块={counts.get('modules', 'n/a')};参数={counts.get('parameters', 'n/a')};"
            f"buffer={counts.get('buffers', 'n/a')}。"
        ]
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
            f"模块={counts.get('modules', 'n/a')};参数={counts.get('parameters', 'n/a')};"
            f"buffer={counts.get('buffers', 'n/a')}。"
        ]
    else:
        entries = [
            f"status={findings.get('status')}; model_type={metadata.get('model_type', 'n/a')}; "
            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


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,
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
    checkpoint: Optional[Mapping[str, Any]],
    language: str,
) -> list[str]:
    """State exactly what the aggregate PASS covers and does not cover."""
    operator_audit = _read_json(store.path("analysis/operator_support.json")) or {}
    statuses = (
        (findings or {}).get("status", "NOT_EXECUTED"),
        (parity or {}).get("status", "NOT_EXECUTED"),
        (comparison or {}).get("status", "NOT_EXECUTED"),
    )
    strict_validate = operator_audit.get("strict_validate_status", "NOT_RECORDED")
    checkpoint_status = (checkpoint or {}).get("status", "NOT_EXECUTED")
    if language == "zh":
        return [
            f"已执行门禁:结构={statuses[0]};原生模块对齐={statuses[1]};"
            f"已声明精度矩阵={statuses[2]}。",
            "首行 PASS 只适用于 resolved cases 中实际执行的范围;未执行或明确排除的能力不会因该 PASS "
            f"自动视为已覆盖。严格 Validate={strict_validate};预训练 checkpoint={checkpoint_status}。",
1597
1598
1599
1600
1601
1602
1603
1604
1605
            f"已声明精度矩阵={statuses[2]}。",
            "首行 PASS 只适用于 resolved cases 中实际执行的范围;未执行或明确排除的能力不会因该 PASS "
            f"自动视为已覆盖。严格 Validate={strict_validate};预训练 checkpoint={checkpoint_status}。",
        ]
    return [
        f"executed gates: structure={statuses[0]}; native module parity={statuses[1]}; "
        f"declared precision matrix={statuses[2]}.",
        "The first-line PASS applies only to the executed resolved cases; omitted or explicitly excluded "
        f"capabilities are not covered by that PASS. strict Validate={strict_validate}; "
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627


def _operator_support_entries(store: EvidenceStore, language: str) -> list[str]:
    """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
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
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
            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."""
    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."""
    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]],
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
    checkpoint: Optional[Mapping[str, Any]],
    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 = "模型从随机状态初始化,或没有保留权重加载报告"
        return [
            f"状态={checkpoint.get('status')};loaded={checkpoint.get('loaded')};"
            f"missing={checkpoint.get('missing')};unexpected={checkpoint.get('unexpected')};"
            f"原因={reason}。"
        ]
    return [
        f"status={checkpoint.get('status')}; loaded={checkpoint.get('loaded')}; "
        f"missing={checkpoint.get('missing')}; unexpected={checkpoint.get('unexpected')}; "
        f"reason={checkpoint.get('reason', 'n/a')}."
    ]
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
    environment: Optional[Mapping[str, Any]],
    language: str,
) -> list[tuple[str, list[str]]]:
    """Build the detailed English or Chinese report sections."""
    manifest = _report_manifest(store)
    cases = _resolved_report_cases(store)
    if language == "zh":
        headings = (
            "可复现性",
            "结论与适用范围",
            "结构验证",
            "Checkpoint 覆盖",
1758
1759
1760
1761
1762
1763
1764
1765
1766
            "全局最差误差",
            "性能与内存(不作为精度门禁)",
            "证据索引",
        )
        evidence_entries = [
            "完整机器可读比较:`comparison.json`",
            "展开后的用例:`cases/resolved_cases.json`",
            "分布式算子审计:`analysis/operator_support.json`",
            "并行归属证据:`analysis/parallel_ownership.json`",
1767
1768
1769
1770
1771
1772
1773
1774
1775
            "问题台账:`analysis/issues.json`",
            "每个用例的日志、metrics、参数探针和 checkpoint 证据:`cases/<case>/`",
        ]
    else:
        headings = (
            "Reproducibility",
            "Verdict and scope",
            "Structure validation",
            "Checkpoint coverage",
1783
1784
1785
1786
1787
1788
1789
1790
1791
            "Worst observed errors",
            "Performance and memory (non-gating)",
            "Evidence index",
        )
        evidence_entries = [
            "complete machine-readable comparison: `comparison.json`",
            "resolved cases: `cases/resolved_cases.json`",
            "distributed operator audit: `analysis/operator_support.json`",
            "parallel ownership evidence: `analysis/parallel_ownership.json`",
1791
1792
1793
1794
1795
1796
1797
1798
1799
            "parallel ownership evidence: `analysis/parallel_ownership.json`",
            "issue ledger: `analysis/issues.json`",
            "per-case logs, metrics, parameter probes, and checkpoint evidence: `cases/<case>/`",
        ]
    return [
        (headings[0], _reproducibility_entries(store, environment, manifest, language)),
        (
            headings[1],
            _verdict_entries(store, findings, parity, comparison, checkpoint, language),
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833


def command_report(args: argparse.Namespace) -> int:
    """Render detailed English and Chinese PASS/FAIL/BLOCKED summaries."""
    store = EvidenceStore(args.output_dir)
    findings = _read_json(store.path("check/findings.json"))
    parity = _read_json(store.path("module_parity/comparison.json"))
    comparison = _read_json(store.path("comparison.json"))
    checkpoint = _read_json(store.path("checkpoint/coverage.json"))
    environment = _read_json(store.path("environment.json"))
    required_reports = (findings, parity, comparison)
    status = _aggregate_statuses(
        [value.get("status") for value in required_reports if value is not None]
        + (["BLOCKED"] if any(value is None for value in required_reports) else [])
    )
    english_summary = store.render_summary(
        status,
        "Model integration validation",
        _report_sections(
            store,
1838
1839
1840
1841
1842
1843
1844
1845
1846
            environment,
            "en",
        ),
    )
    chinese_summary = store.render_summary(
        status,
        "模型接入验证报告",
        _report_sections(
            store,
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
            "zh",
        ),
        relative_path="summary.zh-CN.md",
    )
    print(f"{status}: {english_summary}; {chinese_summary}")
    return 0 if status == "PASS" else 1 if status == "FAIL" else 2


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="python -m hyper_parallel.tools.model_integration",
        description=__doc__,
        epilog="exit codes: 0=PASS, 1=FAIL or execution error, 2=BLOCKED or manifest error",
    )
    subparsers = parser.add_subparsers(dest="command", required=True)
    for name, handler, help_text in (
        ("inspect", command_inspect, "inspect source and final model structure"),
        ("scaffold", command_scaffold, "generate a minimal model adapter scaffold"),
        ("check", command_check, "validate the final built model"),
        ("parity", command_parity, "run model-owned module parity cases"),
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
        ("check", command_check, "validate the final built model"),
        ("parity", command_parity, "run model-owned module parity cases"),
        ("validate", command_validate, "run the precision validation matrix"),
    ):
        subparser = subparsers.add_parser(name, help=help_text)
        subparser.add_argument("--manifest", required=True)
        subparser.add_argument("--output-dir")
        subparser.set_defaults(handler=handler)
        if name == "scaffold":
            subparser.add_argument("--scaffold-dir", required=True)
        elif name == "parity":
            subparser.add_argument("--device", default="cpu")
            subparser.add_argument("--dtype", default="float32")
        elif name == "validate":
            subparser.add_argument("--generate-only", action="store_true")
    report_parser = subparsers.add_parser("report", help="render an evidence summary")
    report_parser.add_argument("--output-dir", required=True)
    report_parser.set_defaults(handler=command_report)
    return parser


def main(argv: Optional[list[str]] = None) -> int:
    """Execute the selected model-integration command."""
    args = _parser().parse_args(argv)
    try:
        return int(args.handler(args))
    except ManifestError as error:
        print(f"manifest error: {error}", file=sys.stderr)
        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],
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
        validation_spec: Optional[ModelValidationSpec],
        model_inputs: Mapping[str, Any],
) -> 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",
            owner_fqn="model.forward",
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
        validation_spec: Optional[ModelValidationSpec],
        model: Any,
) -> 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 = [
            parameter
            for parameter_name, parameter in named_parameters
            if parameter.requires_grad and fnmatch.fnmatchcase(parameter_name, pattern)
        ]
        finite_gradients = []
        for parameter in matched:
            gradient = getattr(parameter, "main_grad", None)
            if gradient is None:
                gradient = parameter.grad
            local = (
                gradient.to_local()
                if callable(getattr(gradient, "to_local", None))
                else gradient
            )
            if local is not None:
                finite_gradients.append(bool(local.isfinite().all()))
        result[pattern] = bool(finite_gradients) and all(finite_gradients)
    return result


__all__ = [
    "build_field_ownership",
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."""
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    """Write versioned evidence without parsing free-form Trainer logs."""

    def __init__(self, root: str | Path) -> None:
        """Create an evidence writer rooted at one independent run directory."""
        self.root = Path(root).expanduser().resolve()
        self.root.mkdir(parents=True, exist_ok=True)

    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:
        path.parent.mkdir(parents=True, exist_ok=True)
        descriptor, temporary_name = tempfile.mkstemp(
            dir=path.parent,
            prefix=f".{path.name}.",
            suffix=".tmp",
            text=True,
 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
            prefix=f".{path.name}.",
            suffix=".tmp",
            text=True,
        )
        try:
            with os.fdopen(descriptor, "w", encoding="utf-8") as output_file:
                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)
        content = json.dumps(
            payload,
            indent=2,
            ensure_ascii=False,
            sort_keys=True,
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
            ensure_ascii=False,
            sort_keys=True,
            default=_json_default,
        ) + "\n"
        self._atomic_write(output_path, content)
        return output_path

    def write_yaml(self, relative_path: str | Path, payload: Any) -> Path:
        """Write a resolved YAML object atomically."""
        output_path = self.path(relative_path)
        content = yaml.safe_dump(payload, allow_unicode=True, sort_keys=False)
        self._atomic_write(output_path, content)
        return output_path

    def append_jsonl(self, relative_path: str | Path, payload: Any) -> Path:
        """Append one structured record to a JSON-lines evidence stream."""
        output_path = self.path(relative_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        with output_path.open("a", encoding="utf-8") as output_file:
            output_file.write(json.dumps(payload, sort_keys=True, default=_json_default) + "\n")
            output_file.flush()
        return output_path

    def write_csv(
        self,
        relative_path: str | Path,
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
        rows: Iterable[Mapping[str, Any]],
        fieldnames: Sequence[str],
    ) -> Path:
        """Write a stable-column CSV evidence table."""
        output_path = self.path(relative_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        with output_path.open("w", encoding="utf-8", newline="") as output_file:
            writer = csv.DictWriter(output_file, fieldnames=fieldnames)
            writer.writeheader()
            for row in rows:
                writer.writerow({name: row.get(name, "") for name in fieldnames})
        return output_path

    def capture_environment(self) -> dict[str, Any]:
        """Capture imports and revisions that determine reproducibility."""
        git_revision = None
        source_root = find_repository_root()
        try:
            git_revision = subprocess.run(
                ["git", "rev-parse", "HEAD"],
                cwd=source_root,
                check=True,
                capture_output=True,
154
155
156
157
158
159
160
161
162
163
164
                check=True,
                capture_output=True,
                text=True,
            ).stdout.strip()
        except (OSError, subprocess.CalledProcessError):
            pass
        environment = {
            "python": sys.version,
            "executable": sys.executable,
            "platform": platform.platform(),
            "git_revision": git_revision,
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
            "git_revision": git_revision,
            "source_root": str(source_root),
            "sys_path": list(sys.path),
        }
        try:
            import hyper_parallel  # pylint: disable=import-outside-toplevel

            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"] = {
                "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"] = {
                "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(
        self,
        state: IntegrationState,
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
        evidence: Iterable[str] = (),
        reason: str = "",
    ) -> Path:
        """Advance the evidence-backed workflow state without skipping gates."""
        state_path = self.path("integration_state.json")
        previous = None
        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:
                raise ValueError(
                    f"cannot regress integration state {previous.value} -> {state.value}"
                )
            if next_index > previous_index + 1:
                raise ValueError(
                    f"cannot skip integration state {previous.value} -> {state.value}"
                )
        payload = {
            "schema_version": SCHEMA_VERSION,
            "state": state.value,
            "previous_state": previous.value if previous else None,
            "evidence": sorted(set(evidence)),
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
            "previous_state": previous.value if previous else None,
            "evidence": sorted(set(evidence)),
            "reason": reason,
        }
        return self.write_json("integration_state.json", payload)

    def current_state(self) -> IntegrationState | None:
        """Return the persisted workflow state, if this run has one."""
        state_path = self.path("integration_state.json")
        if not state_path.exists():
            return None
        return IntegrationState(json.loads(state_path.read_text(encoding="utf-8"))["state"])

    def advance_if_before(
        self,
        state: IntegrationState,
246
247
248
249
250
251
252
253
254
255
256
257
258
259
        This is the restart-safe counterpart to :meth:`update_state`. It never
        rewrites the persisted state backwards, while ``update_state`` remains
        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)

    def render_summary(
        self,
        status: str,
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289

        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)
        return self._atomic_summary(relative_path, "\n".join(lines) + "\n")

    def _atomic_summary(self, relative_path: str, content: str) -> Path:
        output_path = self.path(relative_path)
        self._atomic_write(output_path, content)
        return output_path


__all__ = ["EvidenceStore", "find_repository_root"]
hyper_parallel/tools/model_integration/integration_validator.py
46
47
48
49
50
51
52
53
54
    parameter_identity: int = 0

    def to_dict(self) -> dict[str, Any]:
        """Return a flat representation suitable for CSV or JSON."""
        return {
            "parameter_fqn": self.parameter_fqn,
            "owner_fqn": self.owner_fqn,
            "gradient_domain": self.gradient_domain,
            "source_mesh": ",".join(self.source_mesh),
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138


def resolve_model_validation_spec(model: Any) -> Optional[ModelValidationSpec]:
    """Resolve and type-check the final model family's validation provider."""
    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, "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)
    local = to_local() if callable(to_local) else tensor
    return tuple(int(size) for size in getattr(local, "shape", ()))


def _source_layout(hsdp_param: Any) -> tuple[tuple[str, ...], tuple[str, ...]]:
    source_info = getattr(hsdp_param, "source_shard_info", None)
    source_mesh = getattr(source_info, "mesh", None)
    names = tuple(getattr(source_mesh, "mesh_dim_names", None) or ())
    placements = tuple(repr(value) for value in getattr(source_info, "placements", ()) or ())
    return names, placements


def _gradient_domain(hsdp_param: Any) -> str:
    names, placements = _source_layout(hsdp_param)
    expert_axis = any(name in ("ep", "expert") or name.startswith("ep_") for name in names)
    expert_shard = any("Shard" in placement for placement in placements)
    if expert_axis and expert_shard:
        return "EXPERT"
    return "DENSE"


def collect_parameter_ownership(model: Any) -> tuple[list[ParameterOwnership], dict[str, Any]]:
    """Collect parameter-to-FSDP-unit ownership from final runtime state."""
    ownership = []
    units = {}
    seen_parameters = set()
    for module_fqn, module in model.named_modules():
        scheduler = getattr(module, "hsdp_scheduler", None)
        state = getattr(scheduler, "hsdp_state", None)
        hsdp_params = tuple(getattr(state, "hsdp_params", ()) or ())
        if scheduler is None:
            continue
        owner_fqn = module_fqn or "<root>"
        unit_domains = defaultdict(list)
        for parameter_index, hsdp_param in enumerate(hsdp_params):
            parameter = getattr(hsdp_param, "sharded_param", None)
            parameter_fqn = getattr(hsdp_param, "_param_fqn", None) or (
                f"{owner_fqn}.param[{parameter_index}]"
            )
            names, placements = _source_layout(hsdp_param)
            domain = _gradient_domain(hsdp_param)
            unit_domains[domain].append(parameter_fqn)
            global_shape = tuple(
                int(size) for size in getattr(hsdp_param, "_orig_size", getattr(parameter, "shape", ()))
            )
            ownership.append(
                ParameterOwnership(
                    parameter_fqn=parameter_fqn,
                    owner_fqn=owner_fqn,
                    gradient_domain=domain,
142
143
144
145
146
147
148
149
150
151
152
                    local_shape=_tensor_local_shape(parameter),
                    parameter_identity=id(parameter) if parameter is not None else 0,
                )
            )
            if parameter is not None:
                seen_parameters.add(id(parameter))
        units[owner_fqn] = {
            "domains": {name: sorted(fqns) for name, fqns in sorted(unit_domains.items())},
            "parameter_count": len(hsdp_params),
            "forward_prefetch_targets": [
                _module_fqn(model, target)
156
157
158
159
160
161
162
163
164
165
166
167
                _module_fqn(model, target)
                for target in getattr(scheduler, "backward_prefetch_cells", ())
            ],
        }
    for parameter_fqn, parameter in model.named_parameters(remove_duplicate=False):
        if id(parameter) in seen_parameters:
            continue
        ownership.append(
            ParameterOwnership(
                parameter_fqn=parameter_fqn,
                owner_fqn="<unmanaged>",
                gradient_domain="LOCAL" if not parameter.requires_grad else "DENSE",
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
                local_shape=_tensor_local_shape(parameter),
                parameter_identity=id(parameter),
            )
        )
    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],
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

def _validate_gradient_domains(
    ownership: Iterable[ParameterOwnership],
) -> list[ModelIntegrationFinding]:
    grouped = defaultdict(lambda: defaultdict(list))
    for entry in ownership:
        if entry.owner_fqn == "<unmanaged>" or entry.gradient_domain == "LOCAL":
            continue
        grouped[entry.owner_fqn][entry.gradient_domain].append(entry)
    findings = []
    aliases = defaultdict(list)
    for entry in ownership:
        if entry.parameter_identity:
            aliases[entry.parameter_identity].append(entry)
    for entries in aliases.values():
        contracts = {
            (
                entry.owner_fqn,
                entry.gradient_domain,
                entry.source_mesh,
204
205
206
207
208
209
210
211
212
213
214
                entry.source_placements,
            )
            for entry in entries
        }
        if len(contracts) <= 1:
            continue
        findings.append(
            ModelIntegrationFinding(
                code="HP-FSDP-006",
                phase="D3",
                owner_fqn=entries[0].parameter_fqn,
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
                remediation="assign every alias of the tied parameter to one owner and source layout",
                related_config=("plan_overrides", "model adapter fsdp_wrap_modules"),
            )
        )
    for owner_fqn, domain_entries in sorted(grouped.items()):
        domains = set(domain_entries)
        if len(domains) > 1:
            facts = {
                domain: [entry.parameter_fqn for entry in entries]
                for domain, entries in sorted(domain_entries.items())
            }
            findings.append(
                ModelIntegrationFinding(
                    code="HP-FSDP-004",
                    phase="D3",
                    owner_fqn=owner_fqn,
248
249
250
251
252
253
254
255
256
257
                    ),
                    related_config=("model adapter fsdp_wrap_modules", "plan_overrides"),
                )
            )
        if owner_fqn == "<root>" and "EXPERT" in domains:
            findings.append(
                ModelIntegrationFinding(
                    code="HP-FSDP-005",
                    phase="D3",
                    owner_fqn=owner_fqn,
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
                    remediation="declare a homogeneous nested expert FSDP unit",
                    related_config=("model adapter fsdp_wrap_modules",),
                )
            )
    return findings


def _validate_materialization(model: Any) -> list[ModelIntegrationFinding]:
    meta_state = []
    for name, value in (
        *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>",
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
                remediation="materialize storage and run the adapter rebuild lifecycle before validation",
                related_config=("training.init_device", "model adapter materialization"),
            )
        )
    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,
                    }
313
314
315
316
317
318
319
320
321
322
                        "fqn": f"{module_fqn}.{buffer_name}" if module_fqn else buffer_name,
                        "reason": reason,
                    }
                )
    if invalid_rebuildable_buffers:
        findings.append(
            ModelIntegrationFinding(
                code="HP-MAT-002",
                phase="D4",
                owner_fqn="<root>",
327
328
329
330
331
332
333
334
335
                remediation="register and execute a deterministic rebuild recipe after materialization",
                related_config=("training.init_device", "model adapter materialization"),
            )
        )
    return findings


def _validate_parameter_probes(
    model: Any,
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
def _validate_parameter_probes(
    model: Any,
    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,
364
365
366
367
368
369
370
371
372
373
374
375
    context: Any,
    phases: tuple[str, ...] = ("structure", "materialization", "checkpoint"),
) -> list[ModelIntegrationFinding]:
    """Evaluate model-owned invariants at their declared lifecycle phase."""
    findings = []
    if validation_spec is None:
        return findings
    phase_by_name = {
        "structure": "D2",
        "materialization": "D4",
        "checkpoint": "D5",
        "runtime": "D7",
373
374
375
376
377
378
379
380
381
382
383
        "materialization": "D4",
        "checkpoint": "D5",
        "runtime": "D7",
    }
    for invariant in validation_spec.state_invariants:
        if invariant.phase not in phase_by_name:
            findings.append(
                ModelIntegrationFinding(
                    code="HP-SPEC-001",
                    phase="D1",
                    owner_fqn=invariant.name,
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
                    remediation="use structure, materialization, checkpoint, or runtime",
                    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,
408
409
410
411
412
413
414
415
416
                remediation="fix model initialization/checkpoint mapping or the adapter invariant declaration",
                related_config=("model adapter validation",),
            )
        )
    return findings


def validate_final_model(
    model: Any,
421
422
423
424
425
426
427
428
429
430
431

    This function is observational. It does not change module structure,
    parameters, communication state, or optimizer state.
    """
    validation_spec = resolve_model_validation_spec(model)
    ownership, fsdp_units = collect_parameter_ownership(model)
    report = ModelIntegrationReport(
        metadata={
            "model_type": getattr(getattr(model, "config", None), "model_type", None),
            "inventory_counts": {
                "modules": len(list(model.modules())),
433
434
435
436
437
438
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
                "buffers": len(list(model.buffers())),
            },
        }
    )
    report.extend(_validate_materialization(model))
    report.extend(_validate_gradient_domains(ownership))
    checkpoint_spec = validation_spec.checkpoint if validation_spec is not None else None
    checkpoint_coverage, checkpoint_findings = build_checkpoint_coverage(model, checkpoint_spec)
    report.metadata["checkpoint_coverage"] = checkpoint_coverage
    report.extend(checkpoint_findings)
    report.extend(_validate_parameter_probes(model, validation_spec))
    report.extend(validate_state_invariants(model, validation_spec, context))
    return report, ownership, fsdp_units


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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    """Raised when a model-integration manifest violates its public schema."""


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]:
    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"
        )
    path = Path(value).expanduser()
    return (base_dir / path).resolve() if not path.is_absolute() else path.resolve()


@dataclass(frozen=True)
class ValidationManifest:
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

    @property
    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]:
        """Return the local authoritative model asset path when declared."""
        return _local_path(self.model.get("id_or_path"), self.path.parent, "model.id_or_path")

    @property
    def reference_path(self) -> Optional[Path]:
        """Return the local reference repository path when declared."""
        return _local_path(self.reference.get("source_path"), self.path.parent, "reference.source_path")

    @property
    def integration_handoff_path(self) -> Optional[Path]:
        """Return the prior integration handoff required by precision validation."""
        return _local_path(
            self.raw.get("integration_handoff"),
            self.path.parent,
            "integration_handoff",
        )
 97
 98
 99
100
101
102
103
104
105

    @property
    def launcher_config_path(self) -> Optional[Path]:
        """Return the Trainer recipe path relative to the validation manifest."""
        return _local_path(
            self.launcher.get("config"),
            self.path.parent,
            "launcher.config",
        )
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
        )

    def to_dict(self) -> dict[str, Any]:
        """Return a deep copy safe for evidence serialization."""
        payload = copy.deepcopy(self.raw)
        payload["output_dir"] = str(self.output_dir)
        return payload

    def import_callable(self, target: str) -> Callable[..., Any]:
        """Import an explicit dotted callable without mutating ``sys.path``."""
        if not isinstance(target, str) or "." not in target:
            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."""
        fingerprints = {}
        path_fields = {
            "model.id_or_path": self.model.get("id_or_path"),
            "reference.source_path": self.reference.get("source_path"),
        }
        for field_path, value in path_fields.items():
            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",
                    "preprocessor_config.json",
                    "processor_config.json",
146
147
148
149
150
151
152
153
154
                    "special_tokens_map.json",
                    "tokenizer.json",
                    "tokenizer_config.json",
                }
                metadata_files = [
                    candidate
                    for candidate in path.iterdir()
                    if candidate.is_file()
                    and (candidate.name in metadata_names or candidate.name.endswith(".index.json"))
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
                    for candidate in path.iterdir()
                    if candidate.is_file()
                    and (candidate.name in metadata_names or candidate.name.endswith(".index.json"))
                ]
                for relative_name in (
                    "inference/model.py",
                    "inference/engram.py",
                    "inference/vision.py",
                ):
                    candidate = path / relative_name
                    if candidate.is_file():
                        metadata_files.append(candidate)
                metadata_files.sort(key=lambda candidate: str(candidate.relative_to(path)))
                fingerprints[field_path] = {
                    "path": str(path),
                    "exists": True,
                    "kind": "directory",
                    "metadata": {
170
171
172
173
174
175
176
177
178
179
                        str(candidate.relative_to(path)): self._fingerprint_file(candidate)
                        for candidate in metadata_files
                    },
                }
                continue
            fingerprints[field_path] = {
                "path": str(path),
                "exists": True,
                "kind": "file",
                **self._fingerprint_file(path),
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
                "exists": True,
                "kind": "file",
                **self._fingerprint_file(path),
            }
        return fingerprints

    @staticmethod
    def _fingerprint_file(path: Path) -> dict[str, Any]:
        """Return a stable SHA-256 and size for one explicit local file."""
        digest = hashlib.sha256()
        with path.open("rb") as input_file:
            for chunk in iter(lambda: input_file.read(1024 * 1024), b""):
                digest.update(chunk)
        return {"sha256": digest.hexdigest(), "size": path.stat().st_size}

    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")
        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")
        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")
        if timeout is not None and (
            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")
        has_resume_case = bool(
            self.matrix.get("same_topology_resume")
            or self.matrix.get("cross_topology_resume")
        )
        split_step = self.matrix.get("resume_split_step")
        if has_resume_case and (
            isinstance(split_step, bool)
            or not isinstance(split_step, int)
            or split_step <= 0
            or split_step >= steps
243
244
245
246
247
248
249
250
251
252
253
254
255
            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:
            raise ManifestError(
                "resume cases with matrix.shared_initial_checkpoint=true require "
                "matrix.resume_split_step > 1 because the shared warm-start is global step 1"
            )
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
            )

    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:
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
            )

    def validate_for(self, command: str) -> None:
        """Validate command-specific fields without silently adding defaults."""
        _ = 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()
        self._require_matrix_resources()
        self._require_launcher_timeout()
        self._require_resume_split_step()
        self._require_integration_handoff()


def load_manifest(path: str | Path, output_dir: str | Path | None = None) -> ValidationManifest:
    """Load and validate a local YAML/JSON integration manifest.
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

    Raises:
        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)
    configured_output = output_dir or raw.get("output_dir") or (
        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,
        raw=raw,
        model=model,
        reference=reference,
hyper_parallel/tools/model_integration/module_parity.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
    kwargs: dict[str, Any]


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:
    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


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)


def _floating_tensors(value: Any) -> list[torch.Tensor]:
    return [
        item for item in tree_flatten(value)[0]
        if torch.is_tensor(item) and (item.is_floating_point() or item.is_complex())
    ]
 96
 97
 98
 99
100
101
102
103
104
105
106
107
    ]


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(
    name: str,
112
113
114
115
116
117
118
119
120
121
122
123
124
125
    atol: float,
    rtol: float,
    relative_l2_limit: float | None = None,
) -> ComparisonMetric:
    reference_cpu = reference.detach().float().cpu()
    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,
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
            reference_shape=tuple(reference_cpu.shape),
            candidate_shape=tuple(candidate_cpu.shape),
            reason="shape mismatch",
        )
    equal = bool(torch.equal(reference_cpu, candidate_cpu))
    delta = candidate_cpu.double() - reference_cpu.double()
    max_abs = float(delta.abs().max()) if delta.numel() else 0.0
    reference_norm = torch.linalg.vector_norm(  # pylint: disable=not-callable
        reference_cpu.double().reshape(-1)
    )
    delta_norm = torch.linalg.vector_norm(delta.reshape(-1))  # pylint: disable=not-callable
    relative_l2 = float(delta_norm / reference_norm.clamp_min(torch.finfo(torch.float64).eps))
    passed = equal if exact else bool(torch.allclose(reference_cpu, candidate_cpu, atol=atol, rtol=rtol))
    if not exact and relative_l2_limit is not None:
        passed = passed and relative_l2 <= relative_l2_limit
    passed = passed and finite
    return ComparisonMetric(
        name=name,
        status="PASS" if passed else "FAIL",
        max_abs=max_abs,
        relative_l2=relative_l2,
160
161
162
163
164
165
166
167
168
169
170
171
    atol: float,
    rtol: float,
    relative_l2_limit: float | None = None,
) -> 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,
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
                    f"reference={reference_spec!r}, candidate={candidate_spec!r}"
                ),
            )
        ]
    metrics = []
    for index, (reference_value, candidate_value) in enumerate(zip(reference_values, candidate_values)):
        name = f"{prefix}.{index}"
        if torch.is_tensor(reference_value) and torch.is_tensor(candidate_value):
            tensor_exact = exact or not (
                reference_value.is_floating_point() or reference_value.is_complex()
            )
            metrics.append(
                _metric(
                    name,
                    reference_value,
                    candidate_value,
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,
209
210
211
212
213
214
215
216
217
                    candidate_shape=(),
                    reason=f"non-tensor values differ: {reference_value!r} != {candidate_value!r}",
                )
            )
    return metrics


def _parameter_mapping(
    case: ModuleParityCase,
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
    reference: Any,
    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):
        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())
    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]:
    return [
        value.grad if torch.is_tensor(value) and value.requires_grad else None
        for value in tree_flatten((args, kwargs))[0]
    ]
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277


def _build_parity_modules(case: ModuleParityCase, context: ParityContext) -> tuple[Any, Any]:
    """Instantiate reference/candidate and reject aliased implementations."""
    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


def _resolve_parity_tolerance(
        context: ParityContext,
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def _resolve_parity_tolerance(
        context: ParityContext,
) -> tuple[float, float, float | None]:
    """Read the scalar tolerance contract from the parity context options."""
    tolerance = context.options.get("tolerance", {})
    atol = tolerance_value(tolerance, "atol", "max_abs")
    rtol = tolerance_value(tolerance, "rtol")
    relative_l2_limit = tolerance.get("relative_l2")
    if relative_l2_limit is not None:
        relative_l2_limit = float(relative_l2_limit)
    return atol, rtol, relative_l2_limit


def _observation_metrics(
        case: ModuleParityCase,
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
        rtol: float,
        relative_l2_limit: float | None,
) -> 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,
328
329
330
331
332
333
334
335
336
                rtol=observation.rtol if observation.rtol is not None else rtol,
                relative_l2_limit=relative_l2_limit,
            )
        )
    return metrics


def _input_gradient_metrics(
        reference_args: tuple[Any, ...],
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
        rtol: float,
        relative_l2_limit: float | None,
) -> list[ComparisonMetric]:
    """Compare flattened input gradients pairwise after backward."""
    metrics = []
    reference_input_grads = _input_gradients(reference_args, reference_kwargs)
    candidate_input_grads = _input_gradients(candidate_args, candidate_kwargs)
    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,
                candidate_grad,
362
363
364
365
366
367
368
369
370
                rtol=rtol,
                relative_l2_limit=relative_l2_limit,
            )
        )
    return metrics


def _parameter_gradient_metrics(
        mapping: dict[str, str],
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
        rtol: float,
        relative_l2_limit: float | None,
) -> list[ComparisonMetric]:
    """Compare mapped parameter gradients pairwise after backward."""
    metrics = []
    reference_parameters = dict(reference.named_parameters())
    candidate_parameters = dict(candidate.named_parameters())
    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(
                f"parameter_grad.{candidate_name}",
                reference_grad,
                candidate_grad,
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
                rtol=rtol,
                relative_l2_limit=relative_l2_limit,
            )
        )
    return metrics


def _run_in_process_case(case: ModuleParityCase, context: ParityContext) -> dict[str, Any]:
    candidate, reference = _build_parity_modules(case, context)
    mapping = _parameter_mapping(case, reference, candidate, context)
    inputs = case.input_builder(context)
    reference_args, reference_kwargs = _clone_call_inputs(inputs)
    candidate_args, candidate_kwargs = _clone_call_inputs(inputs)
    reference_output = reference(*reference_args, **reference_kwargs)
    candidate_output = candidate(*candidate_args, **candidate_kwargs)
    atol, rtol, relative_l2_limit = _resolve_parity_tolerance(context)
    metrics = _compare_tree(
        "output",
        reference_output,
        candidate_output,
        exact=False,
418
419
420
421
422
423
424
425
426
427
428
        atol=atol,
        rtol=rtol,
        relative_l2_limit=relative_l2_limit,
    )
    reference_execution = ParityExecution(reference, reference_output, reference_args, reference_kwargs)
    candidate_execution = ParityExecution(candidate, candidate_output, candidate_args, candidate_kwargs)
    metrics.extend(
        _observation_metrics(
            case,
            reference_execution,
            candidate_execution,
432
433
434
435
436
437
438
439
440
441
442
443
            rtol,
            relative_l2_limit,
        )
    )
    objective = case.objective or _default_objective
    objective(reference_output).backward()
    objective(candidate_output).backward()
    metrics.extend(
        _input_gradient_metrics(
            reference_args,
            reference_kwargs,
            candidate_args,
446
447
448
449
450
451
452
453
454
            rtol,
            relative_l2_limit,
        )
    )
    metrics.extend(
        _parameter_gradient_metrics(
            mapping,
            reference,
            candidate,
456
457
458
459
460
461
462
463
464
465
            rtol,
            relative_l2_limit,
        )
    )
    status = "PASS" if all(metric.status == "PASS" for metric in metrics) else "FAIL"
    return {
        "case": case.name,
        "status": status,
        "candidate_type": f"{type(candidate).__module__}.{type(candidate).__qualname__}",
        "reference_type": f"{type(reference).__module__}.{type(reference).__qualname__}",
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
    validation_spec: ModelValidationSpec,
    context: ParityContext,
) -> dict[str, Any]:
    """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),
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
                "error_type": type(error).__name__,
                "error": str(error),
                "traceback": traceback.format_exc(),
            }
        reports.append(report)
        context.evidence_store.write_json(
            f"module_parity/{case.name}/comparison.json",
            report,
        )
    statuses = [report["status"] for report in reports]
    status = (
        "FAIL" if "FAIL" in statuses
        else "BLOCKED" if "BLOCKED" in statuses or not statuses
        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)
    return aggregate


__all__ = ["ParityContext", "ParityExecution", "run_module_parity"]
hyper_parallel/tools/model_integration/optimizer_layout.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48


def tensor_layout(value: Any) -> dict[str, Any]:
    """Return logical/local shape and distributed layout without custom chunks."""
    to_local = getattr(value, "to_local", None)
    local = to_local() if callable(to_local) else value
    layout = getattr(value, "layout", None)
    mesh = getattr(value, "device_mesh", None)
    placements = getattr(value, "placements", None)
    if layout is not None:
        mesh = getattr(layout, "mesh", mesh)
        placements = getattr(layout, "placements", placements)
    mesh_tensor = getattr(mesh, "mesh", None)
    return {
        "global_shape": [int(size) for size in getattr(value, "shape", ())],
        "local_shape": [int(size) for size in getattr(local, "shape", ())],
        "dtype": str(getattr(value, "dtype", None)),
        "device": str(getattr(local, "device", None)),
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
31
32
33
34
35
36
37
38
39
40
41
42
        name: str,
        fallback: str | None = None,
) -> float:
    """Resolve an optional tolerance, treating YAML null as unspecified."""
    value = tolerance.get(name)
    if value is None and fallback is not None:
        value = tolerance.get(fallback)
    return 0.0 if value is None else float(value)


class FindingSeverity(str, Enum):
    """Stable severity levels consumed by CLI and Agent workflows."""
 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
    remediation: str = ""
    related_config: tuple[str, ...] = ()

    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")

    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:
122
123
124
125
126
127
128
129

    @property
    def errors(self) -> tuple[ModelIntegrationFinding, ...]:
        """Return ERROR findings."""
        return tuple(
            finding for finding in self.findings
            if finding.severity is FindingSeverity.ERROR
        )
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
        )

    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)

    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 {
            "schema_version": self.schema_version,
            "status": "FAIL" if self.errors else "PASS",
            "metadata": self.metadata,
            "findings": [finding.to_dict() for finding in self.findings],
165
166
167
168
169
170
171
172
173
    reason: str = ""

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


__all__ = [
    "ComparisonMetric",
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:
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
    checkpoint: dict[str, Any] = field(default_factory=dict)

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


def _qualified_type(value: Any) -> str:
    value_type = type(value)
    return f"{value_type.__module__}.{value_type.__qualname__}"


def _shape(value: Any) -> list[int]:
    return [int(size) for size in getattr(value, "shape", ())]


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:
        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 ())],
    }
 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
    }


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)
    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,
136
137
138
139
140
141
142
143
144
145
146

    Returns:
        Serializable model inventory.
    """
    modules = []
    for module_fqn, module in model.named_modules(remove_duplicate=False):
        modules.append(
            {
                "fqn": module_fqn or "<root>",
                "type": _qualified_type(module),
                "forward_signature": _forward_signature(module),
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
                "training": bool(getattr(module, "training", False)),
            }
        )

    parameter_aliases: dict[int, list[str]] = {}
    parameter_by_id = {}
    for parameter_fqn, parameter in model.named_parameters(remove_duplicate=False):
        parameter_aliases.setdefault(id(parameter), []).append(parameter_fqn)
        parameter_by_id[id(parameter)] = parameter
    parameters = []
    for parameter_id, fqns in sorted(parameter_aliases.items(), key=lambda item: min(item[1])):
        parameter = parameter_by_id[parameter_id]
        parameters.append(
            {
                "fqn": min(fqns),
                "aliases": sorted(fqns),
                "shape": _shape(parameter),
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
                "layout": _layout(parameter),
            }
        )

    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),
194
195
196
197
198
199
200
201
202
203
204
205
206
                "persistent": buffer_id not in non_persistent_ids,
                "layout": _layout(buffer),
            }
        )
    aliases = {
        "parameters": [fqns for fqns in parameter_aliases.values() if len(fqns) > 1],
        "buffers": [fqns for fqns in buffer_aliases.values() if len(fqns) > 1],
    }
    return ModelInventory(
        source=dict(source or {}),
        config=_config_dict(model),
        modules=modules,
        parameters=parameters,
211
212
213
214
215
216
217
218
219
220
221
222
223
224


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": [],
        "weight_map": {},
        "metadata": {},
        "lfs_pointers": {},
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
        "metadata": {},
        "lfs_pointers": {},
    }

    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
        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,
        }
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
            "payload_size": int(size),
            "payload_available": False,
        }

    index_paths = sorted(root.glob("*.index.json"))
    for index_path in index_paths:
        pointer = lfs_pointer(index_path)
        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
    checkpoint["keys"] = sorted(checkpoint["weight_map"])
    checkpoint["weights_available"] = not checkpoint["lfs_pointers"]
    return {
        "path": str(root),
        "config": config,
        "checkpoint": checkpoint,
        "files": sorted(path.name for path in root.iterdir() if path.is_file()),
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
309
310
311
312
313
314
315
316
317
318
319
320
    candidate: ModelInventory,
    classifier: Optional[Callable[[str, Any, Any], tuple[DifferenceCategory, str]]] = None,
) -> list[InventoryDifference]:
    """Compare module/state records and classify every difference."""
    differences = []

    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)
        }
        candidate_entries = {
            entry["fqn"]: entry for entry in getattr(candidate, collection_name)
        }
        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)
            )
    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


__all__ = [
    "DifferenceCategory",
hyper_parallel/tools/model_integration/tensor_probes.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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

_SMALL_TENSOR_NUMEL = 8192

def _tensor_numel(tensor: Any) -> int:
    shape = getattr(tensor, "shape", ())
    return math.prod(int(size) for size in shape)


def _local_tensor(tensor: Any) -> torch.Tensor:
    """Return one detached local shard without requesting redistribution."""
    to_local = getattr(tensor, "to_local", None)
    value = to_local() if callable(to_local) else tensor
    return value.detach().float().cpu().contiguous()


def _shard_identity(tensor: Any) -> tuple[tuple[int, int], ...]:
    """Identify the logical shard while excluding replicated mesh axes."""
    layout = getattr(tensor, "layout", None)
    mesh = getattr(layout, "mesh", getattr(tensor, "device_mesh", None))
    placements = getattr(layout, "placements", getattr(tensor, "placements", ()))
    get_coordinate = getattr(mesh, "get_coordinate", None)
    coordinate = get_coordinate() if callable(get_coordinate) else None
    if coordinate is None:
        return ()
    shard_coordinates = []
    for mesh_dim, placement in enumerate(placements or ()):
        is_shard = getattr(placement, "is_shard", None)
        is_ragged = getattr(placement, "is_ragged_shard", None)
        if (callable(is_shard) and is_shard()) or (
            callable(is_ragged) and is_ragged()
        ):
            shard_coordinates.append((mesh_dim, int(coordinate[mesh_dim])))
    return tuple(shard_coordinates)


def _local_shards_follow_flattened_order(tensor: Any) -> bool:
    """Return whether rank-ordered shard values reconstruct the flattened tensor."""
    layout = getattr(tensor, "layout", None)
    placements = getattr(layout, "placements", getattr(tensor, "placements", ()))
    shard_placements = []
    for placement in placements or ():
        is_shard = getattr(placement, "is_shard", None)
        is_ragged = getattr(placement, "is_ragged_shard", None)
        if (callable(is_shard) and is_shard()) or (
            callable(is_ragged) and is_ragged()
        ):
            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",
    )
 95
 96
 97
 98
 99
100
101
102
103
104
105


def _local_tensor_summary(tensor: Any) -> dict[str, Any]:
    """Summarize one local shard for cross-rank aggregation."""
    value = _local_tensor(tensor).reshape(-1)
    digest = hashlib.sha256(value.numpy().tobytes()).hexdigest()
    return {
        "rank": dist.get_rank() if dist.is_available() and dist.is_initialized() else 0,
        "shard": _shard_identity(tensor),
        "sha256": digest,
        "numel": value.numel(),
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def _gather_shard_summaries(
    local_summary: Optional[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[Optional[dict[str, Any]]]]:
    """Gather optional metadata records and retain one value per logical shard."""
    rank_summaries = [local_summary]
    if dist.is_available() and dist.is_initialized():
        rank_summaries = [None] * dist.get_world_size()
        dist.all_gather_object(rank_summaries, local_summary)
    by_shard: dict[tuple[tuple[int, int], ...], list[dict[str, Any]]] = {}
    for summary in rank_summaries:
        if summary is None:
            continue
        shard = tuple(tuple(item) for item in summary["shard"])
        by_shard.setdefault(shard, []).append(summary)
    shards = [
        min(replicas, key=lambda item: item["rank"])
        for replicas in by_shard.values()
    ]
    return shards, rank_summaries


def _replica_digest_groups(
        rank_summaries: list[Optional[dict[str, Any]]],
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def _replica_digest_groups(
        rank_summaries: list[Optional[dict[str, Any]]],
) -> dict[tuple[Any, ...], set[str]]:
    """Group per-rank payload digests by their logical shard identity."""
    replica_groups: dict[tuple[Any, ...], set[str]] = {}
    for rank_summary in rank_summaries:
        if rank_summary is None:
            continue
        shard = tuple(tuple(item) for item in rank_summary["shard"])
        replica_groups.setdefault(shard, set()).add(rank_summary["sha256"])
    return replica_groups


def _canonical_shard_digest(shards: list[dict[str, Any]]) -> str:
    """Hash the ordered shard digests into one canonical tensor identity."""
    return hashlib.sha256(
        json.dumps(
            [(summary["shard"], summary["sha256"]) for summary in shards],
            sort_keys=True,
        ).encode("utf-8")
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174


def _flattened_shard_values(tensor: Any, shards: list[dict[str, Any]]) -> Optional[list[Any]]:
    """Reconstruct full values only from flattened-order small shard sets."""
    if not (
        _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(
        name: str,
176
177
178
179
180
181
182
183
184
185
186
187
188
        shards: list[dict[str, Any]],
        rank_summaries: list[Optional[dict[str, Any]]],
) -> dict[str, Any]:
    """Aggregate sorted shard summaries into one canonical global record."""
    replica_groups = _replica_digest_groups(rank_summaries)
    minima = [summary["min"] for summary in shards if summary["min"] is not None]
    maxima = [summary["max"] for summary in shards if summary["max"] is not None]
    reference = shards[0]
    summary = {
        "name": name,
        "global_shape": reference["global_shape"],
        "canonical_shape": reference["global_shape"],
        "dtype": reference["dtype"],
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
        "missing_ranks": [
            rank for rank, rank_summary in enumerate(rank_summaries) if rank_summary is None
        ],
    }
    values = _flattened_shard_values(tensor, shards)
    if values is not None:
        summary["values"] = values
    return summary


def _tensor_summary(name: str, tensor: Any) -> Optional[dict[str, Any]]:
    """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)


def _global_tensor_state_names(local_names: Iterable[str]) -> tuple[str, ...]:
    """Return a rank-consistent tensor-state key order for collective probes."""
    local_names = tuple(sorted(local_names))
    rank_names = [local_names]
    if dist.is_available() and dist.is_initialized():
        rank_names = [None] * dist.get_world_size()
        dist.all_gather_object(rank_names, local_names)
    return tuple(sorted({name for names in rank_names for name in (names or ())}))


class InputIdentityRecorder:
    """Hash global step inputs from deterministic per-rank tensor payloads."""
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")
        }
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
        }

    @staticmethod
    def _hash_tensor(value: torch.Tensor) -> str:
        hasher = hashlib.sha256()
        hasher.update(f"{tuple(value.shape)}:{value.dtype}".encode())
        byte_view = value.contiguous().view(torch.uint8)
        hasher.update(byte_view.numpy().tobytes())
        return hasher.hexdigest()

    @staticmethod
    def _require_input_payloads(rank_payloads: list[dict[str, Any]]) -> None:
        """Reject collectives polluted by non-input rank payloads."""
        malformed_ranks = [
            rank
            for rank, payload in enumerate(rank_payloads)
            if not isinstance(payload, Mapping)
            or "coordinates" not in payload
320
321
322
323
324
325
326
327
328
329
            if not isinstance(payload, Mapping)
            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"
            )
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
    def _group_input_entries(
            rank_payloads: list[dict[str, Any]],
    ) -> dict[tuple[Any, ...], list[tuple[int, torch.Tensor, bool]]]:
        """Group entry shards by logical field across tp0/pp0 ranks."""
        selected = [
            payload
            for payload in rank_payloads
            if payload["coordinates"]["tp"] == 0 and payload["coordinates"]["pp"] == 0
        ]
        grouped: dict[tuple[Any, ...], list[tuple[int, torch.Tensor, bool]]] = {}
        for payload in selected:
            coordinates = payload["coordinates"]
            for entry in payload["entries"]:
                key = (
                    entry["micro_step"],
                    coordinates["dp"],
                    entry["owner"],
                    entry["field"],
348
349
350
351
352
353
354
355
356
                    entry["owner"],
                    entry["field"],
                    entry["value_index"],
                )
                grouped.setdefault(key, []).append(
                    (
                        coordinates["cp"],
                        entry["tensor"],
                        bool(entry.get("cp_replicated", False)),
355
356
357
358
359
360
361
362
363
                        entry["tensor"],
                        bool(entry.get("cp_replicated", False)),
                    )
                )
        return grouped

    @staticmethod
    def _merge_entry_shards(
            shards: list[tuple[int, torch.Tensor, bool]],
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
    def _merge_entry_shards(
            shards: list[tuple[int, torch.Tensor, bool]],
    ) -> torch.Tensor:
        """Rebuild one logical field tensor from ordered CP shards."""
        ordered_shards = sorted(shards, key=lambda item: item[0])
        ordered = [tensor for _, tensor, _ in ordered_shards]
        tensor = ordered[0]
        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(
            cls,
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
            cls,
            grouped: dict[tuple[Any, ...], list[tuple[int, torch.Tensor, bool]]],
    ) -> list[str]:
        """Hash every logical field occurrence independently of rank order."""
        component_hashes = []
        for key, shards in grouped.items():
            tensor = cls._merge_entry_shards(shards)
            field_identity = f"{key[2]}:{key[3]}:{key[4]}"
            if tensor.ndim > 0:
                values = tensor.reshape(tensor.shape[0], -1)
                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:
        cls._require_input_payloads(rank_payloads)
        grouped = cls._group_input_entries(rank_payloads)
        component_hashes = cls._entry_component_hashes(grouped)
        hasher = hashlib.sha256()
        for component_hash in sorted(component_hashes):
            hasher.update(component_hash.encode())
        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,
        }
438
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
473
474
475
476
        include_small_parameters_numel_le: int = _SMALL_TENSOR_NUMEL,
        representative_parameter_names: Iterable[str] = (),
    ) -> None:
        """Resolve a stable watchlist from the final model and adapter declarations."""
        self.model = model
        self.optimizer = optimizer
        self.evidence_store = evidence_store
        self.rank = rank
        self.validation_spec = validation_spec
        self.include_small_parameters_numel_le = include_small_parameters_numel_le
        self.representative_parameter_names = set(representative_parameter_names)
        self._named_parameters = dict(model.named_parameters(remove_duplicate=False))
        self._probes = self._resolve_probes()
        self._validate_required_patterns()
        self._step = 0

    def _resolve_probes(self) -> dict[str, ParameterProbeSpec]:
        probes = {}
        declared = self.validation_spec.parameter_probes if self.validation_spec else ()
        replaced_module_fqns = tuple(
            getattr(self.model, "_hp_replaced_module_fqns", ()) or ()
        )
        for parameter_name, parameter in self._named_parameters.items():
            main_parameter = getattr(parameter, "main_param", parameter)
            replacement_parameter = any(
                parameter_name == module_fqn
                or parameter_name.startswith(f"{module_fqn}.")
                for module_fqn in replaced_module_fqns
            )
            if (
                replacement_parameter
                or parameter_name in self.representative_parameter_names
                or _tensor_numel(main_parameter) <= self.include_small_parameters_numel_le
            ):
                probes[parameter_name] = ParameterProbeSpec(
                    match=parameter_name,
                    required=True,
                    stages=(
                        "model_before",
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
                        "model_after_copy_back",
                        "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
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
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564

    @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
        self.capture("model_before")
        self.capture("main_before")

    @staticmethod
    def _stage_tensor(parameter: Any, stage: str) -> Any:
        main_parameter = getattr(parameter, "main_param", parameter)
        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 = []
        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,
                    "rank": self.rank,
564
565
566
567
568
569
570
571
572
573
574
575
                    "rank": self.rank,
                    "comparison": probe.compare,
                }
            )
            records.append(record)
        if self.rank != 0:
            return
        for record in records:
            self.evidence_store.append_jsonl(
                f"parameter_probes/rank{self.rank}.jsonl",
                record,
            )
hyper_parallel/trainer/base.py
118
119
120
121
122
123
124
125
126

    Returns:
        The backend-neutral swap configuration consumed by ``swap_optimizer``.
    """
    return SwapOptimizerConfig(
        swap_times=config.swap_times,
        state_keys=config.state_keys,
        min_numel=config.min_numel,
        include_master_params=config.include_master_params,
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
    Returns:
        The optimizer carrying swap-wrapped leaves. A leaf the swap runtime does
        not support fails here, before the first training step.
    """
    swap_config = _to_swap_optimizer_config(config)
    chained = optimizer.optimizer if isinstance(optimizer, MixedPrecisionOptimizer) else optimizer
    if not isinstance(chained, ChainedOptimizer):
        return swap_optimizer(optimizer, swap_config)

    names = list(chained.optimizers_dict)
    leaves = [swap_optimizer(chained.optimizers_dict[name], swap_config) for name in names]
    # In place: the fp32 main-parameter wrapper aliases both containers.
    chained.chained_optimizers[:] = leaves
    for name, leaf in zip(names, leaves):
        chained.optimizers_dict[name] = leaf
    return optimizer


class BaseTrainer(Stateful, ABC):
    """
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),
        )
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
        )
        # Swap is attached after the fp32 main-parameter wrap: that wrapper owns
        # the master parameters ``include_master_params`` swaps, and the lr
        # scheduler built right after reads ``self.optimizer.param_groups``.
        if config.optimizer.swap.enabled:
            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,
            )
771
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
801
802
803
804
805
806
807
808
809
            )

    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()
        self.validate_fsdp_runtime_before_optimizer()
        max_grad_norm = self.config.training.max_grad_norm
        grad_norm: Any = 0.0
        post_clip_norm = None
        if max_grad_norm > 0:
            grad_norm = clip_grad_norm_(self.model, max_grad_norm)
            if self.model_integration.runtime_enabled:
                grad_norm_value = (
                    float(grad_norm.item())
                    if isinstance(grad_norm, torch.Tensor)
                    else float(grad_norm)
                )
                clip_coefficient = min(
                    float(max_grad_norm) / (grad_norm_value + 1.0e-6),
                    1.0,
                )
                post_clip_norm = grad_norm_value * clip_coefficient
        elif self.model_integration.runtime_enabled:
            grad_norm = clip_grad_norm_(self.model, float("inf"))
            post_clip_norm = grad_norm
        self.model_integration.after_clip(post_clip_norm)
        return grad_norm

    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]
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
107
108
109
110
111
112
113
114
115

        self._restore_from = ckpt_cfg.restore_from
        self._restore_optimizer = ckpt_cfg.restore_optimizer
        self._restore_train_state = ckpt_cfg.restore_train_state
        self._restore_dataloader_state = ckpt_cfg.restore_dataloader_state

        self._last_saved_step: int = -1
        self.checkpointer = build_checkpointer(
            extra_state_per_rank=self._save_extra_state_per_rank,
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,
            )
358
359
360
361
362
363
364
365
366
367
368
            checkpoint_state,
            strict_model=not self._is_peft,
            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,
            )
369
370
371
372
373
374
375
376
377

        self.trainer.model.load_state_dict(
            checkpoint_state["model"], strict=not self._is_peft
        )
        validate_model_init_dtype(
            self.trainer.model,
            self.trainer.config.model_init_dtype,
        )
        # ``checkpoint_state["optimizer"]`` was built from ``optimizers`` above
412
413
414
415
416
417
418
419
420
421
    def _apply_extra_state(self, extra: Dict[str, Any]) -> None:
        """Restore progress, scheduler, dataloader position and RNG state."""
        trainer = self.trainer
        trainer.state.global_step = extra["global_step"]
        persisted_epoch = extra.get("epoch", 0)
        trainer.state.epoch = persisted_epoch

        # The restored step is already on disk. Without this, resuming a run that
        # had nothing left to do would have ``on_train_end`` rewrite the very
        # checkpoint it just loaded.
438
439
440
441
442
443
444
445
446
447
448
449
450
451
            for scheduler, scheduler_sd in zip(schedulers, scheduler_sds):
                scheduler.load_state_dict(scheduler_sd)

        dataloader_sd = extra.get("train_dataloader")
        if dataloader_sd and not self._restore_dataloader_state:
            logger.info(
                "restore_dataloader_state=False: retaining the configured "
                "dataloader start instead of restoring its checkpoint cursor."
            )
        elif dataloader_sd and hasattr(trainer.train_dataloader, "load_state_dict"):
            trainer.train_dataloader.load_state_dict(dataloader_sd)
        elif dataloader_sd:
            logger.warning(
                "Checkpoint carries a dataloader position but %s is not stateful; "
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/optimization.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    packed_swap: Optional[bool] = None

    def __post_init__(self) -> None:
        """Reject a pipeline width or state key set the swap runtime cannot use."""
        if self.swap_times < 1:
            raise ValueError(
                f"optimizer.swap.swap_times must be at least 1, got {self.swap_times}"
            )
        if self.min_numel < 0:
            raise ValueError(
                f"optimizer.swap.min_numel must be non-negative, got {self.min_numel}"
            )
        if self.state_keys is not None:
            validate_state_keys(self.state_keys)


@dataclass
class OptimizerConfig:
90
91
92
93
94
95
    def to_dict(self) -> dict[str, Any]:
        """Serialize optimizer options in their compact target YAML shape."""
        config = self.target.to_dict()
        config["fp32_main_params"] = self.fp32_main_params
        config["swap"] = _serialize_config_value(self.swap)
        return config
hyper_parallel/trainer/config/parallelism.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    layer_indices: Optional[List[int]] = None

    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)
        if self.source == "default" and any(selectors):
            raise ValueError(
                "activation_checkpoint.selection.layer_count and layer_indices "
                "are only valid when "
                "selection.source=model_adapter_safe_regions"
            )
        if self.source == "model_adapter_safe_regions" and sum(selectors) != 1:
            raise ValueError(
                "activation_checkpoint.selection requires exactly one of "
                "layer_count or layer_indices when "
                "selection.source=model_adapter_safe_regions"
            )
        if self.layer_count is not None and (
            isinstance(self.layer_count, bool)
            or not isinstance(self.layer_count, int)
            or self.layer_count < 0
        ):
            raise ValueError(
                "activation_checkpoint.selection.layer_count must be a "
                "non-negative integer"
            )
        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(
            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):
            raise ValueError(
                "activation_checkpoint.selection.layer_indices must not "
                "contain duplicates"
            )
hyper_parallel/trainer/config/resolver.py
107
108
109
110
111
112
113
114
115


def _is_union(annotation: object) -> bool:
    """Return whether the annotation is a ``Union`` or a PEP 604 union."""
    return get_origin(annotation) in (Union, _UNION_TYPE)


def _annotation_name(annotation: object) -> str:
    """Format an annotation for configuration error messages."""
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")
176
177
178
179
180
181
182
183
184

def _normalize_union(value: object, annotation: object, *, path: str) -> object:
    """Normalize a value against the first compatible union member."""
    members = get_args(annotation)
    non_none_members = tuple(member for member in members if member is not _NONE_TYPE)
    if len(non_none_members) == 1 and len(non_none_members) != len(members):
        return normalize_value(value, non_none_members[0], path=path)

    for member in members:
306
307
308
309
310
311
312
313
314
315
    """
    # A nested target is a deferred instance of the annotated runtime type.
    # Its constructor arguments are validated when its own node is resolved;
    # the parent Target materializes it immediately before invocation.
    if isinstance(value, Target):
        return value
    if annotation in (Any, object):
        return value
    if isinstance(annotation, dataclasses.InitVar):
        return normalize_value(value, annotation.type, path=path)
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
544


def _resolve_nested_target_nodes(value: object, *, path: str) -> object:
    """Resolve reserved ``_target_`` nodes inside a target argument tree."""
    if isinstance(value, Mapping):
        if "_target_" in value:
            return _resolve_target(value, path=path)
        return {
            key: _resolve_nested_target_nodes(item, path=f"{path}.{key}")
            for key, item in value.items()
        }
    if isinstance(value, list):
        return [
            _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


def _resolve_target_args(
    raw_args: Mapping[str, object],
559
560
561
562
563
564
565
566

    normalized = {}
    for name, value in raw_args.items():
        parameter = signature.parameters.get(name)
        value = _resolve_nested_target_nodes(value, path=f"{path}.{name}")
        if parameter is None:
            normalized[name] = value
            continue
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."""
726
727
728
729
730
731
732
733
734
        target_node.pop("fp32_main_params", False),
        bool,
        path=f"{path}.fp32_main_params",
    )
    swap = normalize_value(
        target_node.pop("swap", {}),
        OptimizerSwapConfig,
        path=f"{path}.swap",
    )
767
768
769
770
771
772
773
774
775
    if annotation is DataLoaderConfig:
        return _resolve_dataloader_config(node, path=path)
    if annotation is OptimizerConfig:
        return _resolve_optimizer_config(node, path=path)
    if isinstance(node, Mapping) and "_target_" in node:
        return _resolve_target(node, path=path)
    if isinstance(annotation, type) and dataclasses.is_dataclass(annotation):
        return _resolve_dataclass(node, annotation, path=path)
    return normalize_value(node, annotation, path=path)
hyper_parallel/trainer/config/target.py
102
103
104
105
106
107
108
109
110
                if name in signature.parameters
            }

        kwargs = {**self._kwargs, **runtime_kwargs}
        kwargs = {
            name: _build_nested_targets(value)
            for name, value in kwargs.items()
        }
        return self._target_(**kwargs)
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146


def _build_nested_targets(value: Any) -> Any:
    """Materialize deferred targets inside one configured argument tree."""
    if isinstance(value, Target):
        return value.build()
    if isinstance(value, dict):
        return {
            key: _build_nested_targets(item)
            for key, item in value.items()
        }
    if isinstance(value, list):
        return [_build_nested_targets(item) for item in value]
    if isinstance(value, tuple):
        return tuple(_build_nested_targets(item) for item in value)
    return value
hyper_parallel/trainer/config/training.py
74
75
76
77
78
79
80
81
82
    mode: Literal["off", "build", "runtime"] = "off"

    def __post_init__(self) -> None:
        """Reject ambiguous validation scopes at the configuration boundary."""
        if self.mode not in ("off", "build", "runtime"):
            raise ValueError(
                "model_integration.mode must be one of: off, build, runtime"
            )

hyper_parallel/trainer/runtime/fsdp.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
    """

    def __init__(self, hsdp_model_parts: List[Any]) -> None:
        """Attach diagnostic hooks to all HSDP units in the model parts."""
        self._module_by_name: dict[str, Any] = {}
        self._name_by_module_id: dict[int, str] = {}
        self._scheduler_by_name: dict[str, Any] = {}
        self._hook_handles: list[Any] = []
        self._traces: list[_FSDPMicroStepTrace] = []
        self._active_trace: Optional[_FSDPMicroStepTrace] = None
        self._discover_units(hsdp_model_parts)
        self._register_hooks()

    @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
        for part_index, model_part in enumerate(hsdp_model_parts):
            part_prefix = f"part[{part_index}]" if multiple_parts else ""
            for module_fqn, module in model_part.named_modules():
                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
                self._scheduler_by_name[module_name] = scheduler

    def _register_hooks(self) -> None:
        """Register hooks on every unit for order and child-bypass diagnostics."""
        for module_name, module in self._module_by_name.items():
            self._hook_handles.append(
                module.register_forward_pre_hook(
                    self._make_forward_pre_hook(module_name)
                )
            )
            self._hook_handles.append(
                module.register_forward_hook(
                    self._make_backward_registration_hook(module_name)
                )
            )
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

    def _make_forward_pre_hook(self, module_name: str):
        """Create a hook recording non-recompute forward execution."""

        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

    def _make_backward_registration_hook(self, module_name: str):
        """Create a forward hook attaching a one-shot output-gradient observer."""

        def register_backward_observer(module: Any, inputs: Any, outputs: Any) -> None:
            """Attach a one-shot gradient observer to differentiable outputs."""
            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
            ]
            if not output_tensors:
                return

            fired = [False]
            hook_handles = []

            def record_backward(gradient: Any) -> Any:
                """Record the module on its first output-gradient callback."""
                if not fired[0]:
                    fired[0] = True
                    if self._active_trace is not None:
                        self._active_trace.backward.append(module_name)
                    for hook_handle in hook_handles:
                        hook_handle.remove()
                return gradient

            for output_tensor in output_tensors:
                hook_handles.append(output_tensor.register_hook(record_backward))

        return register_backward_observer

    def begin_micro_step(self, global_step: int, micro_step: int) -> None:
        """Start tracing one micro-step in the current optimizer step."""
        if micro_step == 0:
            self._traces.clear()
        trace = _FSDPMicroStepTrace(global_step, micro_step)
        self._traces.append(trace)
        self._active_trace = trace

    @staticmethod
    def _first_positions(execution_order: list[str]) -> dict[str, int]:
        """Return first-observed positions while preserving repeated calls in logs."""
        positions = {}
        for position, module_name in enumerate(execution_order):
            positions.setdefault(module_name, position)
        return positions

    def _prefetch_target_names(self, module_name: str, phase: str) -> list[str]:
        """Resolve configured prefetch targets to diagnostic module names."""
        scheduler = self._scheduler_by_name[module_name]
        attribute = (
            "forward_prefetch_cells"
            if phase == "forward"
            else "backward_prefetch_cells"
        )
        target_names = []
        for target in getattr(scheduler, attribute, ()):
            target_names.append(
                self._name_by_module_id.get(
                    id(target),
                    f"<untracked:{type(target).__name__}>",
                )
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
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
                    id(target),
                    f"<untracked:{type(target).__name__}>",
                )
            )
        return target_names

    def _find_order_violations(self) -> list[str]:
        """Compare configured prefetch edges with each observed execution trace."""
        violations = []
        for trace in self._traces:
            for phase in ("forward", "backward"):
                execution_order = getattr(trace, phase)
                positions = self._first_positions(execution_order)
                for source_name, source_position in positions.items():
                    for target_name in self._prefetch_target_names(source_name, phase):
                        target_position = positions.get(target_name)
                        if target_position is None:
                            violations.append(
                                f"step={trace.global_step} micro_step={trace.micro_step} "
                                f"phase={phase}: {source_name} prefetched {target_name}, "
                                "but the target did not execute"
                            )
                        elif target_position <= source_position:
                            violations.append(
                                f"step={trace.global_step} micro_step={trace.micro_step} "
                                f"phase={phase}: {source_name} prefetched {target_name} "
                                f"at positions {source_position}->{target_position}"
                            )
        return violations

    @staticmethod
    def _parameter_name(hsdp_param: Any, param_index: int) -> str:
        """Return a useful parameter name for diagnostics."""
        return getattr(hsdp_param, "_param_fqn", None) or f"param[{param_index}]"

    def _find_pending_all_gathers(self) -> list[str]:
        """Find unconsumed fused and non-fused all-gathers without mutating them."""
        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", ())
            ):
                result = getattr(bucket, "all_gather_result", None)
                if result is not None:
                    handle = getattr(result, "handle", None)
                    pending.append(
                        f"{module_name}: fused_bucket[{bucket_index}] "
                        f"handle={'set' if handle is not None else 'completed-not-consumed'}"
                    )

            for param_index, hsdp_param in enumerate(getattr(state, "hsdp_params", ())):
                comm_ctx = getattr(hsdp_param, "allgather_comm_ctx", None)
                handle = getattr(comm_ctx, "allgather_handle", None)
                output = getattr(comm_ctx, "allgather_output", None)
                if handle is not None or output is not None:
                    state_details = []
                    if handle is not None:
                        state_details.append("handle=set")
                    if output is not None:
                        state_details.append("output=not-consumed")
                    pending.append(
                        f"{module_name}: "
                        f"{self._parameter_name(hsdp_param, param_index)} "
                        + ", ".join(state_details)
                    )
        return pending

    @staticmethod
    def _hsdp_parameter_has_gradient(hsdp_param: Any) -> bool:
        """Return whether a managed parameter participated in backward."""
        sharded_param = getattr(hsdp_param, "sharded_param", None)
        candidates = (
            getattr(sharded_param, "grad", None),
            getattr(sharded_param, "main_grad", None),
            getattr(hsdp_param, "_grad", None),
            getattr(hsdp_param, "unsharded_accumulated_grad", None),
269
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
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
            getattr(hsdp_param, "_grad", None),
            getattr(hsdp_param, "unsharded_accumulated_grad", None),
            getattr(hsdp_param, "unsharded_accumulated_grad_data", None),
        )
        return any(value is not None for value in candidates)

    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
        bypassed = []
        root_names = {"<root>"}
        root_names.update(
            name for name in self._module_by_name
            if name.startswith("part[") and "." not in name
        )
        for module_name, scheduler in self._scheduler_by_name.items():
            if module_name in root_names or forward_counts[module_name] > 0:
                continue
            state = getattr(scheduler, "hsdp_state", None)
            gradient_parameters = [
                self._parameter_name(hsdp_param, param_index)
                for param_index, hsdp_param in enumerate(getattr(state, "hsdp_params", ()))
                if self._hsdp_parameter_has_gradient(hsdp_param)
            ]
            if gradient_parameters:
                bypassed.append(
                    f"{module_name}: forward_count=0, gradient_parameters={gradient_parameters}"
                )
        return bypassed

    def validate_before_optimizer_step(self) -> None:
        """Fail before parameter updates if the traced FSDP lifecycle is unsafe."""
        self._active_trace = None
        order_violations = self._find_order_violations()
        pending_all_gathers = self._find_pending_all_gathers()
        bypassed_child_units = self._find_bypassed_child_units()
        error_sections = []
        if bypassed_child_units:
            error_sections.append(
                "[HP-FSDP-007] child FSDP module was bypassed while its parameter "
                "participated in backward:\n  - " + "\n  - ".join(bypassed_child_units)
            )
        if order_violations:
            error_sections.append(
                "[HP-FSDP-008] configured prefetch targets do not follow "
                "the observed runtime order:\n  - " + "\n  - ".join(order_violations)
            )
        if pending_all_gathers:
            error_sections.append(
                "[HP-FSDP-009] unconsumed parameter all-gather exists at "
                "the optimizer boundary:\n  - " + "\n  - ".join(pending_all_gathers)
            )
        if error_sections:
            raise RuntimeError(
                "\n".join(error_sections)
                + "\nThe optimizer was not executed. Configure FSDP prefetch from the "
                "model's actual forward/backward execution order; do not carry an "
                "all-gather across optimizer.step()."
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,
347
348
349
350
351
352
353
354
355
356
357
        }

    def close(self) -> None:
        """Remove diagnostic hooks when a caller no longer needs the observer."""
        for hook_handle in self._hook_handles:
            hook_handle.remove()
        self._hook_handles.clear()


def build_fsdp_runtime_diagnostics(
    hsdp_model_parts: List[Any],
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