@@ -715,6 +715,83 @@ def _backward_kernel(
715715 )
716716 joint -= 1
717717
718+ @triton .jit
719+ def _local_forward_kernel (
720+ joint_params ,
721+ offsets ,
722+ prerotations ,
723+ output ,
724+ batch_size ,
725+ n_joints ,
726+ BLOCK_BATCH : tl .constexpr ,
727+ ):
728+ # Local skeleton state: the per-joint state from _load_local_state, WITHOUT
729+ # the parent composition that _forward_one_joint applies (i.e. the global FK
730+ # before the prefix product up the kinematic tree).
731+ batch = tl .program_id (0 ) * BLOCK_BATCH + tl .arange (0 , BLOCK_BATCH )
732+ mask = batch < batch_size
733+ joint = 0
734+ while joint < n_joints :
735+ tx , ty , tz , qx , qy , qz , qw , s = _load_local_state (
736+ joint_params , offsets , prerotations , batch , joint , n_joints , mask
737+ )
738+ out_base = output + batch * n_joints * 8 + joint * 8
739+ tl .store (out_base + 0 , tx , mask = mask )
740+ tl .store (out_base + 1 , ty , mask = mask )
741+ tl .store (out_base + 2 , tz , mask = mask )
742+ tl .store (out_base + 3 , qx , mask = mask )
743+ tl .store (out_base + 4 , qy , mask = mask )
744+ tl .store (out_base + 5 , qz , mask = mask )
745+ tl .store (out_base + 6 , qw , mask = mask )
746+ tl .store (out_base + 7 , s , mask = mask )
747+ joint += 1
748+
749+ @triton .jit
750+ def _local_backward_kernel (
751+ joint_params ,
752+ offsets ,
753+ prerotations ,
754+ grad_output ,
755+ grad_joint_params ,
756+ batch_size ,
757+ n_joints ,
758+ BLOCK_BATCH : tl .constexpr ,
759+ ):
760+ # Backward of the local FK: there is no tree, so grad w.r.t. the local
761+ # skeleton state IS grad_output per joint. _store_joint_param_gradients
762+ # propagates it to joint_params (translation identity, rotation through the
763+ # prerotation*euler quaternion multiply + euler, scale through exp2).
764+ batch = tl .program_id (0 ) * BLOCK_BATCH + tl .arange (0 , BLOCK_BATCH )
765+ mask = batch < batch_size
766+ joint = 0
767+ while joint < n_joints :
768+ _ , _ , _ , _ , _ , _ , _ , ls = _load_local_state (
769+ joint_params , offsets , prerotations , batch , joint , n_joints , mask
770+ )
771+ gtx , gty , gtz , gqx , gqy , gqz , gqw , gs = _load_state (
772+ grad_output , batch , joint , n_joints , mask
773+ )
774+ _store_joint_param_gradients (
775+ joint_params ,
776+ prerotations ,
777+ grad_joint_params ,
778+ batch ,
779+ joint ,
780+ n_joints ,
781+ mask ,
782+ True ,
783+ gtx ,
784+ gty ,
785+ gtz ,
786+ gqx ,
787+ gqy ,
788+ gqz ,
789+ gqw ,
790+ gs ,
791+ ls ,
792+ )
793+ joint += 1
794+
718795
719796def _require_triton () -> None :
720797 if triton is None or tl is None :
@@ -907,3 +984,134 @@ def joint_parameters_to_skeleton_state(
907984 joint_parents ,
908985 active_joints ,
909986 )
987+
988+
989+ def _validate_local_inputs (
990+ joint_parameters : torch .Tensor ,
991+ joint_translation_offsets : torch .Tensor ,
992+ joint_prerotations : torch .Tensor ,
993+ ) -> None :
994+ # Same as _validate_inputs but the local FK needs no kinematic tree (parents).
995+ if not joint_parameters .is_cuda :
996+ raise RuntimeError ("Triton local FK requested, but joint_parameters is on CPU." )
997+ if joint_parameters .dtype != torch .float32 :
998+ raise RuntimeError (
999+ "Triton local FK currently only supports float32 joint parameters."
1000+ )
1001+ if joint_parameters .ndim < 2 or joint_parameters .shape [- 1 ] != 7 :
1002+ raise RuntimeError ("joint_parameters must have shape [..., num_joints, 7]." )
1003+ if joint_translation_offsets .device != joint_parameters .device :
1004+ raise RuntimeError ("joint_translation_offsets must be on the same device." )
1005+ if joint_prerotations .device != joint_parameters .device :
1006+ raise RuntimeError ("joint_prerotations must be on the same device." )
1007+ if joint_translation_offsets .dtype != torch .float32 :
1008+ raise RuntimeError ("joint_translation_offsets must be float32." )
1009+ if joint_prerotations .dtype != torch .float32 :
1010+ raise RuntimeError ("joint_prerotations must be float32." )
1011+ num_joints = joint_parameters .shape [- 2 ]
1012+ if joint_translation_offsets .shape != (num_joints , 3 ):
1013+ raise RuntimeError ("joint_translation_offsets must have shape [num_joints, 3]." )
1014+ if joint_prerotations .shape != (num_joints , 4 ):
1015+ raise RuntimeError ("joint_prerotations must have shape [num_joints, 4]." )
1016+
1017+
1018+ def _launch_local_forward (
1019+ joint_parameters : torch .Tensor ,
1020+ joint_translation_offsets : torch .Tensor ,
1021+ joint_prerotations : torch .Tensor ,
1022+ ) -> torch .Tensor :
1023+ _validate_local_inputs (
1024+ joint_parameters , joint_translation_offsets , joint_prerotations
1025+ )
1026+ _require_triton ()
1027+ num_joints = joint_parameters .shape [- 2 ]
1028+ batch_size = joint_parameters .numel () // (num_joints * 7 )
1029+ output = torch .empty (
1030+ (* joint_parameters .shape [:- 1 ], 8 ),
1031+ device = joint_parameters .device ,
1032+ dtype = joint_parameters .dtype ,
1033+ )
1034+ grid = (triton .cdiv (batch_size , _BLOCK_BATCH ),)
1035+ _local_forward_kernel [grid ](
1036+ joint_parameters ,
1037+ joint_translation_offsets ,
1038+ joint_prerotations ,
1039+ output ,
1040+ batch_size ,
1041+ num_joints ,
1042+ BLOCK_BATCH = _BLOCK_BATCH ,
1043+ )
1044+ return output
1045+
1046+
1047+ def _launch_local_backward (
1048+ joint_parameters : torch .Tensor ,
1049+ joint_translation_offsets : torch .Tensor ,
1050+ joint_prerotations : torch .Tensor ,
1051+ grad_output : torch .Tensor ,
1052+ ) -> torch .Tensor :
1053+ _require_triton ()
1054+ grad_output = grad_output .contiguous ()
1055+ grad_joint_parameters = torch .empty_like (joint_parameters )
1056+ num_joints = joint_parameters .shape [- 2 ]
1057+ batch_size = joint_parameters .numel () // (num_joints * 7 )
1058+ grid = (triton .cdiv (batch_size , _BLOCK_BATCH ),)
1059+ _local_backward_kernel [grid ](
1060+ joint_parameters ,
1061+ joint_translation_offsets ,
1062+ joint_prerotations ,
1063+ grad_output ,
1064+ grad_joint_parameters ,
1065+ batch_size ,
1066+ num_joints ,
1067+ BLOCK_BATCH = _BLOCK_BATCH ,
1068+ )
1069+ return grad_joint_parameters
1070+
1071+
1072+ class _JointParametersToLocalSkeletonState (torch .autograd .Function ):
1073+ @staticmethod
1074+ def forward (
1075+ ctx : torch .autograd .function .FunctionCtx ,
1076+ joint_parameters : torch .Tensor ,
1077+ joint_translation_offsets : torch .Tensor ,
1078+ joint_prerotations : torch .Tensor ,
1079+ ) -> torch .Tensor :
1080+ joint_parameters = joint_parameters .contiguous ()
1081+ joint_translation_offsets = joint_translation_offsets .contiguous ()
1082+ joint_prerotations = joint_prerotations .contiguous ()
1083+ output = _launch_local_forward (
1084+ joint_parameters , joint_translation_offsets , joint_prerotations
1085+ )
1086+ ctx .save_for_backward (
1087+ joint_parameters , joint_translation_offsets , joint_prerotations
1088+ )
1089+ return output
1090+
1091+ @staticmethod
1092+ def backward (
1093+ ctx : torch .autograd .function .FunctionCtx ,
1094+ grad_output : torch .Tensor ,
1095+ ) -> tuple [torch .Tensor | None , None , None ]:
1096+ joint_parameters , joint_translation_offsets , joint_prerotations = (
1097+ ctx .saved_tensors
1098+ )
1099+ grad_joint_parameters = _launch_local_backward (
1100+ joint_parameters ,
1101+ joint_translation_offsets ,
1102+ joint_prerotations ,
1103+ grad_output ,
1104+ )
1105+ return grad_joint_parameters , None , None
1106+
1107+
1108+ def joint_parameters_to_local_skeleton_state (
1109+ joint_parameters : torch .Tensor ,
1110+ joint_translation_offsets : torch .Tensor ,
1111+ joint_prerotations : torch .Tensor ,
1112+ ) -> torch .Tensor :
1113+ return _JointParametersToLocalSkeletonState .apply (
1114+ joint_parameters ,
1115+ joint_translation_offsets ,
1116+ joint_prerotations ,
1117+ )
0 commit comments