112112DESCRIPTOR_SHARED_SCRATCH_SPILL_THRESHOLD_BYTES = 4 * 1024
113113DESCRIPTOR_SHARED_SCRATCH_BUDGET_BYTES = 24 * 1024
114114_GENERIC_MODEL_REAL_ABI_INPUT_SUFFIXES = ("residual_norm_weight" ,)
115+ _TRAIN_STEP_SCALAR_OUTPUT_ABI_NAMES = ("loss" , "ntokens" )
116+ _TRAIN_STEP_SCALAR_OUTPUT_ABI_REASON = (
117+ "train-step scalar ABI slots are declared, but suffix loss codegen is not "
118+ "fused into the descriptor body yet"
119+ )
115120_DTYPE_NBYTES = {
116121 "bool" : 1 ,
117122 "uint8" : 1 ,
@@ -820,6 +825,7 @@ def make_path_c_descriptor_schedule_template(
820825 internal_buffer_policy : str = DESCRIPTOR_INTERNAL_BUFFER_POLICY_SCALAR_LOCAL ,
821826 loop_policy : str = DESCRIPTOR_LOOP_POLICY_FLAT ,
822827 physical_abi_policy : str = DESCRIPTOR_PHYSICAL_ABI_POLICY_DIRECT ,
828+ train_step_output_abi : bool = False ,
823829) -> Callable [[Any ], Any ]:
824830 """Return a schedule template generated from brick descriptors."""
825831
@@ -845,6 +851,7 @@ def descriptor_schedule_template(template_region: Any) -> Any:
845851 internal_buffer_policy = validated_internal_buffer_policy ,
846852 loop_policy = validated_loop_policy ,
847853 physical_abi_policy = validated_physical_abi_policy ,
854+ train_step_output_abi = bool (train_step_output_abi ),
848855 )
849856
850857 descriptor_schedule_template ._cppmega_path_c_schedule_generator = (
@@ -867,6 +874,9 @@ def descriptor_schedule_template(template_region: Any) -> Any:
867874 if _descriptor_chain_uses_kv_history_workspace (descriptors )
868875 else ()
869876 )
877+ descriptor_schedule_template ._cppmega_path_c_train_step_output_abi_enabled = (
878+ bool (train_step_output_abi )
879+ )
870880 return descriptor_schedule_template
871881
872882
@@ -918,6 +928,7 @@ def build_path_c_descriptor_prim_func(
918928 internal_buffer_policy : str = DESCRIPTOR_INTERNAL_BUFFER_POLICY_SCALAR_LOCAL ,
919929 loop_policy : str = DESCRIPTOR_LOOP_POLICY_FLAT ,
920930 physical_abi_policy : str = DESCRIPTOR_PHYSICAL_ABI_POLICY_DIRECT ,
931+ train_step_output_abi : bool = False ,
921932) -> Any :
922933 """Generate a single-entry TileLang PrimFunc from a descriptor chain."""
923934
@@ -949,6 +960,18 @@ def build_path_c_descriptor_prim_func(
949960 for name in (* node .inputs , * node .outputs )
950961 }
951962 external_buffers = _external_buffers_for_nodes (nodes , internal_buffers )
963+ train_step_output_abi_payload = _train_step_output_abi_payload (
964+ declared = bool (train_step_output_abi )
965+ )
966+ train_step_output_buffers = tuple (
967+ train_step_output_abi_payload ["logical_outputs" ]
968+ if train_step_output_abi_payload ["declared" ]
969+ else ()
970+ )
971+ external_buffers_for_abi = _append_unique_names (
972+ external_buffers ,
973+ train_step_output_buffers ,
974+ )
952975 extent = _validated_buffer_extent (buffer_extent )
953976 entry_name = _safe_identifier (
954977 entry_symbol or getattr (region , "entry_symbol" , None ) or getattr (region , "name" , None )
@@ -963,13 +986,17 @@ def build_path_c_descriptor_prim_func(
963986 name : _buffer_shape (name , extent , resolved_shape_env )
964987 for name in external_buffers
965988 }
989+ dtype_by_buffer = dict (dtype_by_buffer )
990+ for name in train_step_output_buffers :
991+ dtype_by_buffer [name ] = "float32"
992+ shape_by_buffer [name ] = (1 ,)
966993 loop_extent = _descriptor_loop_extent (
967994 external_buffers ,
968995 extent ,
969996 resolved_shape_env ,
970997 )
971998 physical_abi_plan = _physical_abi_plan (
972- external_buffers = external_buffers ,
999+ external_buffers = external_buffers_for_abi ,
9731000 shape_by_buffer = shape_by_buffer ,
9741001 dtype_by_buffer = dtype_by_buffer ,
9751002 buffer_extent = extent ,
@@ -986,7 +1013,7 @@ def build_path_c_descriptor_prim_func(
9861013 physical_abi_plan = physical_abi_plan ,
9871014 internal_buffer_policy = validated_internal_buffer_policy ,
9881015 loop_policy = validated_loop_policy ,
989- external_buffers = external_buffers ,
1016+ external_buffers = external_buffers_for_abi ,
9901017 shape_by_buffer = shape_by_buffer ,
9911018 dtype_by_buffer = dtype_by_buffer ,
9921019 buffer_extent = extent ,
@@ -1033,6 +1060,9 @@ def build_path_c_descriptor_prim_func(
10331060 ).with_attr (
10341061 "tl.fusion.physical_abi.physical_buffer_shapes" ,
10351062 json .dumps (physical_abi_plan .physical_buffer_shapes , sort_keys = True ),
1063+ ).with_attr (
1064+ "tl.fusion.train_step_output_abi" ,
1065+ json .dumps (train_step_output_abi_payload , sort_keys = True ),
10361066 )
10371067 compile_pass_configs = _descriptor_tilelang_compile_pass_configs (
10381068 descriptors ,
@@ -1056,8 +1086,8 @@ def build_path_c_descriptor_prim_func(
10561086 prim_func ._cppmega_path_c_physical_abi_policy = validated_physical_abi_policy
10571087 prim_func ._cppmega_path_c_internal_buffer_shapes = internal_buffer_shapes
10581088 prim_func ._cppmega_path_c_buffer_abi_shapes = {
1059- name : _buffer_shape ( name , extent , resolved_shape_env )
1060- for name in external_buffers
1089+ name : shape_by_buffer [ name ]
1090+ for name in external_buffers_for_abi
10611091 }
10621092 prim_func ._cppmega_path_c_physical_buffer_abi_shapes = dict (
10631093 physical_abi_plan .physical_buffer_shapes
@@ -1074,6 +1104,9 @@ def build_path_c_descriptor_prim_func(
10741104 prim_func ._cppmega_path_c_loop_extent = loop_extent
10751105 prim_func ._cppmega_path_c_generated_source = source
10761106 prim_func ._cppmega_path_c_compile_pass_configs = compile_pass_configs
1107+ prim_func ._cppmega_path_c_train_step_output_abi = dict (
1108+ train_step_output_abi_payload
1109+ )
10771110 return prim_func
10781111
10791112
@@ -1090,6 +1123,43 @@ def _descriptor_tilelang_compile_pass_configs(
10901123 return {}
10911124
10921125
1126+ def _append_unique_names (
1127+ names : Sequence [str ],
1128+ extra_names : Sequence [str ],
1129+ ) -> tuple [str , ...]:
1130+ values = [str (name ) for name in names ]
1131+ seen = set (values )
1132+ for raw_name in extra_names :
1133+ name = str (raw_name )
1134+ if name in seen :
1135+ continue
1136+ values .append (name )
1137+ seen .add (name )
1138+ return tuple (values )
1139+
1140+
1141+ def _train_step_output_abi_payload (
1142+ * ,
1143+ declared : bool ,
1144+ outputs_computed : bool = False ,
1145+ ) -> dict [str , Any ]:
1146+ return {
1147+ "declared" : bool (declared ),
1148+ "outputs_computed" : bool (outputs_computed ),
1149+ "logical_outputs" : _TRAIN_STEP_SCALAR_OUTPUT_ABI_NAMES
1150+ if declared
1151+ else (),
1152+ "reason" : _TRAIN_STEP_SCALAR_OUTPUT_ABI_REASON
1153+ if declared and not outputs_computed
1154+ else (
1155+ "train-step scalar ABI slots are generated and populated by fused "
1156+ "suffix loss codegen"
1157+ if declared
1158+ else "train-step scalar ABI slots are not required for this descriptor"
1159+ ),
1160+ }
1161+
1162+
10931163def _descriptor_prim_func_source (
10941164 * ,
10951165 entry_name : str ,
@@ -7412,6 +7482,7 @@ def _dynamic_descriptor_target_for_region(
74127482 internal_buffer_policy = internal_buffer_policy ,
74137483 loop_policy = loop_policy ,
74147484 )
7485+ train_step_output_abi = _region_requires_train_step_output_abi (region , nodes )
74157486 schedule_name = (
74167487 acceptance_profile .schedule_name
74177488 if acceptance_profile is not None
@@ -7430,6 +7501,8 @@ def _dynamic_descriptor_target_for_region(
74307501 extra_steps .append ("z3_sync_async_schedule_points" )
74317502 if shape_env is not None :
74327503 extra_steps .append ("cache_key_shape_specialization_audit" )
7504+ if train_step_output_abi :
7505+ extra_steps .append ("train_step_scalar_output_abi" )
74337506 if extra_steps :
74347507 seen = set (required_codegen_steps )
74357508 required_codegen_steps = (
@@ -7493,6 +7566,7 @@ def _dynamic_descriptor_target_for_region(
74937566 internal_buffer_policy = internal_buffer_policy ,
74947567 loop_policy = loop_policy ,
74957568 physical_abi_policy = physical_abi_policy ,
7569+ train_step_output_abi = train_step_output_abi ,
74967570 ),
74977571 required_real_abi_inputs = required_real_abi_inputs ,
74987572 brick_descriptors = descriptors ,
@@ -7503,6 +7577,20 @@ def _dynamic_descriptor_target_for_region(
75037577 )
75047578
75057579
7580+ def _region_requires_train_step_output_abi (
7581+ region : Any ,
7582+ nodes : Sequence [_ScheduleNodeView ],
7583+ ) -> bool :
7584+ metadata = getattr (region , "metadata" , {}) or {}
7585+ if bool (metadata .get ("path_c_acceptance_fixture_abi" , False )):
7586+ return False
7587+ if metadata .get ("path_c_chain_source_region" ):
7588+ return False
7589+ if not metadata .get ("path_c_bricks" ):
7590+ return False
7591+ return any (str (node .op_name ).endswith ("_bwd" ) for node in nodes )
7592+
7593+
75067594def _physical_abi_policy_for_region (
75077595 nodes : Sequence [_ScheduleNodeView ],
75087596 * ,
0 commit comments