@@ -73,20 +73,19 @@ def vjp_from_jacobian(
7373 dict mapping input paths to gradient arrays.
7474 """
7575 jac = jacobian_fn (inputs = inputs , jac_inputs = vjp_inputs , jac_outputs = vjp_outputs )
76- out = {}
77- for dx in vjp_inputs :
78- grad = None
79- for dy in vjp_outputs :
80- J = np .asarray (jac [dy ][dx ]) # shape: (*dy_shape, *dx_shape)
81- v = np .asarray (cotangent_vector [dy ]) # shape: (*dy_shape)
82- if diagonal :
83- diag = np .diag (J .reshape (v .size , v .size ))
84- term = diag .reshape (v .shape ) * v
85- else :
86- term = np .tensordot (v , J , axes = v .ndim )
87- grad = term if grad is None else grad + term
88- out [dx ] = grad
89- return out
76+
77+ def contract (J : np .ndarray , v : np .ndarray ) -> np .ndarray :
78+ if diagonal :
79+ return np .diag (J .reshape (v .size , v .size )).reshape (v .shape ) * v
80+ return np .tensordot (v , J , axes = v .ndim )
81+
82+ return {
83+ dx : sum (
84+ contract (np .asarray (jac [dy ][dx ]), np .asarray (cotangent_vector [dy ]))
85+ for dy in vjp_outputs
86+ )
87+ for dx in vjp_inputs
88+ }
9089
9190
9291def jvp_from_jacobian (
@@ -118,20 +117,19 @@ def jvp_from_jacobian(
118117 dict mapping output paths to JVP result arrays.
119118 """
120119 jac = jacobian_fn (inputs = inputs , jac_inputs = jvp_inputs , jac_outputs = jvp_outputs )
121- out = {}
122- for dy in jvp_outputs :
123- result = None
124- for dx in jvp_inputs :
125- J = np .asarray (jac [dy ][dx ]) # shape: (*dy_shape, *dx_shape)
126- t = np .asarray (tangent_vector [dx ]) # shape: (*dx_shape)
127- if diagonal :
128- diag = np .diag (J .reshape (t .size , t .size ))
129- term = diag .reshape (t .shape ) * t
130- else :
131- term = np .tensordot (J , t , axes = t .ndim )
132- result = term if result is None else result + term
133- out [dy ] = result
134- return out
120+
121+ def contract (J : np .ndarray , t : np .ndarray ) -> np .ndarray :
122+ if diagonal :
123+ return np .diag (J .reshape (t .size , t .size )).reshape (t .shape ) * t
124+ return np .tensordot (J , t , axes = t .ndim )
125+
126+ return {
127+ dy : sum (
128+ contract (np .asarray (jac [dy ][dx ]), np .asarray (tangent_vector [dx ]))
129+ for dx in jvp_inputs
130+ )
131+ for dy in jvp_outputs
132+ }
135133
136134
137135def jacobian_from_vjp (
@@ -161,33 +159,31 @@ def jacobian_from_vjp(
161159 raw_outputs .model_dump () if hasattr (raw_outputs , "model_dump" ) else raw_outputs
162160 )
163161
164- jac = {}
165- output_vals = {}
166- for dy in jac_outputs :
167- dy_val = np .asarray (get_at_path (outputs_dict , dy ))
168- output_vals [dy ] = dy_val
169- jac [dy ] = {}
170- for dx in jac_inputs :
171- dx_val = np .asarray (get_at_path (inputs , dx ))
172- jac [dy ][dx ] = np .zeros ((* dy_val .shape , * dx_val .shape ), dtype = dy_val .dtype )
173-
174- for dy in jac_outputs :
175- dy_val = output_vals [dy ]
176- dy_shape = dy_val .shape
177- for nd_idx in np .ndindex (* dy_shape ) if dy_shape else [()]:
178- cotangent = {dy : np .zeros_like (dy_val )}
179- if dy_shape :
180- cotangent [dy ][nd_idx ] = 1.0
162+ out_vals = {dy : np .asarray (get_at_path (outputs_dict , dy )) for dy in jac_outputs }
163+ in_vals = {dx : np .asarray (get_at_path (inputs , dx )) for dx in jac_inputs }
164+ jac = {
165+ dy : {
166+ dx : np .zeros ((* v .shape , * in_vals [dx ].shape ), dtype = v .dtype )
167+ for dx in jac_inputs
168+ }
169+ for dy , v in out_vals .items ()
170+ }
171+
172+ for dy , dy_val in out_vals .items ():
173+ for nd_idx in np .ndindex (* dy_val .shape ) if dy_val .shape else [()]:
174+ e = np .zeros_like (dy_val )
175+ if dy_val .shape :
176+ e [nd_idx ] = 1.0
181177 else :
182- cotangent [ dy ] = np .array ( 1.0 , dtype = dy_val .dtype )
178+ e = np .ones (() , dtype = dy_val .dtype )
183179 grad = vjp_fn (
184180 inputs = inputs ,
185181 vjp_inputs = jac_inputs ,
186182 vjp_outputs = {dy },
187- cotangent_vector = cotangent ,
183+ cotangent_vector = { dy : e } ,
188184 )
189185 for dx in jac_inputs :
190- if dy_shape :
186+ if dy_val . shape :
191187 jac [dy ][dx ][nd_idx ] = np .asarray (grad [dx ])
192188 else :
193189 jac [dy ][dx ] = np .asarray (grad [dx ])
@@ -221,32 +217,32 @@ def jacobian_from_jvp(
221217 raw_outputs .model_dump () if hasattr (raw_outputs , "model_dump" ) else raw_outputs
222218 )
223219
224- jac = {}
225- for dy in jac_outputs :
226- dy_val = np . asarray ( get_at_path ( outputs_dict , dy ))
227- jac [ dy ] = {}
228- for dx in jac_inputs :
229- dx_val = np . asarray ( get_at_path ( inputs , dx ))
230- jac [ dy ][ dx ] = np . zeros (( * dy_val . shape , * dx_val . shape ), dtype = dy_val . dtype )
231-
232- for dx in jac_inputs :
233- dx_val = np . asarray ( get_at_path ( inputs , dx ))
234- dx_shape = dx_val . shape
235- for nd_idx in np .ndindex (* dx_shape ) if dx_shape else [()]:
236- tangent = { dx : np .zeros_like (dx_val )}
237- if dx_shape :
238- tangent [ dx ] [nd_idx ] = 1.0
220+ out_vals = {dy : np . asarray ( get_at_path ( outputs_dict , dy )) for dy in jac_outputs }
221+ in_vals = { dx : np . asarray ( get_at_path ( inputs , dx )) for dx in jac_inputs }
222+ jac = {
223+ dy : {
224+ dx : np . zeros (( * v . shape , * in_vals [ dx ]. shape ), dtype = v . dtype )
225+ for dx in jac_inputs
226+ }
227+ for dy , v in out_vals . items ()
228+ }
229+
230+ for dx , dx_val in in_vals . items ():
231+ for nd_idx in np .ndindex (* dx_val . shape ) if dx_val . shape else [()]:
232+ e = np .zeros_like (dx_val )
233+ if dx_val . shape :
234+ e [nd_idx ] = 1.0
239235 else :
240- tangent [ dx ] = np .array ( 1.0 , dtype = dx_val .dtype )
236+ e = np .ones (() , dtype = dx_val .dtype )
241237 result = jvp_fn (
242238 inputs = inputs ,
243239 jvp_inputs = {dx },
244240 jvp_outputs = jac_outputs ,
245- tangent_vector = tangent ,
241+ tangent_vector = { dx : e } ,
246242 )
247243 for dy in jac_outputs :
248244 dy_result = np .asarray (result [dy ])
249- if dx_shape :
245+ if dx_val . shape :
250246 jac [dy ][dx ][(..., * nd_idx )] = dy_result
251247 else :
252248 jac [dy ][dx ] = dy_result
0 commit comments