Skip to content

Commit 69a9a36

Browse files
committed
Fix custom gradients: support NoGradient and stabilize adapter
1 parent 1d43d4c commit 69a9a36

3 files changed

Lines changed: 130 additions & 135 deletions

File tree

tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/AbstractGradientAdapter.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,25 +80,24 @@ private static List<Output<?>> fromNativeOutputs(Graph g, TF_Output nativeOutput
8080
}
8181

8282
/**
83-
* Put the Java outputs into the array of native outputs, resizing it to the necessary size.
84-
*
85-
* @param outputs the outputs to put
86-
* @return pointer to the native array of outputs
87-
*/
83+
* Put the Java outputs into the array of native outputs, resizing it to the necessary size.
84+
*
85+
* @param outputs the outputs to put
86+
* @return pointer to the native array of outputs
87+
*/
8888
private static TF_Output toNativeOutputs(List<Operand<?>> outputs) {
8989
// Use malloc to allocate native outputs, as they will be freed by the native layer and we do
9090
// not want JavaCPP to deallocate them
9191
var nativeOutputs =
9292
new TF_Output(Pointer.malloc((long) outputs.size() * Pointer.sizeof(TF_Output.class)));
9393

9494
for (int i = 0; i < outputs.size(); ++i) {
95+
Operand<?> operand = outputs.get(i);
9596
var nativeOutput = nativeOutputs.getPointer(i);
9697

97-
Operand<?> operand = outputs.get(i);
98+
// Convention: null Operand => NoGradient
9899
if (operand == null) {
99-
// "NoGradient" sentinel: null oper + index 0.
100-
// Native side must tolerate TF_Output.oper == nullptr.
101-
nativeOutput.oper((org.tensorflow.internal.c_api.TF_Operation) null);
100+
nativeOutput.oper((TF_Operation) null);
102101
nativeOutput.index(0);
103102
continue;
104103
}

tensorflow-core/tensorflow-core-api/src/test/java/org/tensorflow/CustomGradientsTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertNotNull;
55
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
import static org.junit.jupiter.api.Assertions.assertFalse;
67

78
import java.util.List;
89
import org.junit.jupiter.api.Test;
@@ -96,7 +97,7 @@ public void sigmoidGradHasCustomGradientWithoutOnesLikeSeed() {
9697
// dL/d(y) not needed for this test; return zeros to keep it non-null.
9798
Operand<TFloat32> dY = tf.zerosLike(y);
9899

99-
return List.of(dY, dDy);
100+
return java.util.Arrays.asList(dY, dDy);
100101
});
101102

102103
try (Graph g = new Graph()) {
@@ -118,7 +119,7 @@ public void sigmoidGradHasCustomGradientWithoutOnesLikeSeed() {
118119
assertNotNull(grads);
119120
assertEquals(1, grads.length);
120121
assertNotNull(grads[0], "Expected a non-null gradient for sigmoid(x) wrt x.");
121-
assertTrue(!grads[0].isClosed(), "Expected an active Output for d(sigmoid)/dx.");
122+
assertFalse(grads[0].isClosed(), "Expected an active Output for d(sigmoid)/dx.");
122123
}
123124
}
124125
}

tensorflow-core/tensorflow-core-native/src/main/native/org/tensorflow/internal/c_api/tfj_gradients_impl.cc

Lines changed: 119 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ limitations under the License.
2121
// IMPORTANT: explicit STL includes (this .cc is included via tfj_gradients.h in some builds,
2222
// so we must not rely on transitive includes from other headers).
2323
#include <string>
24-
#include <vector>
2524
#include <unordered_map>
25+
#include <vector>
2626

2727
#include "tfj_graph.h"
2828
#include "tsl/platform/errors.h"
@@ -31,141 +31,136 @@ limitations under the License.
3131
#include "tensorflow/cc/framework/grad_op_registry.h"
3232

3333
namespace tensorflow {
34-
namespace java {
35-
using namespace tsl;
36-
using namespace std;
37-
38-
unordered_map<string, TFJ_GradFuncAdapter> g_grad_func_adapters;
39-
40-
/// This method can be used to cast a pointer to/from a C struct that contains only that pointer. It is a bit
41-
/// It has been "inspired" by the TensorFlow C API code, as found at this location when time of writing:
42-
/// https://github.com/tensorflow/tensorflow/blob/9d637f69f699c0c422716b56153a8b27b681891a/tensorflow/c/c_api.cc#L658
43-
template <typename T, typename U> T* struct_cast(U* ptr) {
44-
return static_cast<T*>(static_cast<void*>(ptr));
45-
}
46-
47-
/// This function is called by the TensorFlow runtime when it is time to add gradient operations of `op` to the
48-
/// graph using the given `scope`.
49-
/// We use it as a bridge between the C++ signature in TensorFlow (tensorflow::op::GradFunc) and our custom
50-
/// "C" version (TFJ_GradFuncAdapter).
51-
Status CustomGradFunc(const Scope& scope,
52-
const Operation& op,
53-
const vector<Output>& grad_inputs,
54-
vector<Output>* grad_outputs)
55-
{
56-
const string& op_type = op.node()->type_string();
57-
auto found_adapter = g_grad_func_adapters.find(op_type);
58-
if (found_adapter == g_grad_func_adapters.end()) {
59-
return errors::NotFound("No gradient adapter found for operation ", op_type);
60-
}
61-
62-
const int num_inputs = static_cast<int>(grad_inputs.size());
63-
64-
TF_Output* inputs = nullptr;
65-
if (num_inputs > 0) {
66-
inputs = static_cast<TF_Output*>(malloc(num_inputs * sizeof(TF_Output)));
67-
if (inputs == nullptr) {
68-
return errors::ResourceExhausted(
69-
"Out of memory allocating inputs for custom gradient of op ", op_type);
70-
}
71-
}
72-
73-
for (int i = 0; i < num_inputs; ++i) {
74-
const Output& grad_input = grad_inputs[i];
75-
inputs[i].oper = struct_cast<TF_Operation>(grad_input.node());
76-
inputs[i].index = grad_input.index();
77-
}
78-
79-
TF_Output* outputs = nullptr;
80-
LOG(INFO) << "Calling Java gradient function for operation of type " << op_type;
81-
82-
TFJ_GradFuncAdapter adapter = found_adapter->second;
83-
if (adapter == nullptr) {
84-
if (inputs != nullptr) free(inputs);
85-
return errors::Unknown("Null Java gradient adapter for op ", op_type);
86-
}
87-
LOG(INFO) << "Adapter ptr for " << op_type << " = " << reinterpret_cast<void*>(found_adapter->second);
88-
const int num_outputs = adapter(
89-
static_cast<TFJ_GraphId>(scope.graph()),
90-
struct_cast<TFJ_Scope>(const_cast<Scope*>(&scope)),
91-
struct_cast<TF_Operation>(op.node()),
92-
inputs,
93-
num_inputs,
94-
&outputs
95-
);
96-
97-
// Always free inputs, even on error paths.
98-
if (inputs != nullptr) free(inputs);
99-
100-
// Adapter contract hardening:
101-
// - On Java exception / failure, adapter should return negative or outputs==nullptr.
102-
if (num_outputs < 0) {
103-
if (outputs != nullptr) free(outputs);
104-
return errors::Unknown("Java custom gradient adapter failed for op ", op_type,
105-
" (num_outputs=", num_outputs, ")");
106-
}
107-
if (num_outputs > 0 && outputs == nullptr) {
108-
return errors::Unknown("Java custom gradient adapter returned null outputs for op ",
109-
op_type, " with num_outputs=", num_outputs);
110-
}
111-
112-
grad_outputs->reserve(grad_outputs->size() + static_cast<size_t>(num_outputs));
113-
114-
for (int i = 0; i < num_outputs; ++i) {
115-
const TF_Output out = outputs[i];
116-
117-
// "NoGradient" sentinel from Java: TF_Output.oper == nullptr
118-
if (out.oper == nullptr) {
119-
// Represent "no gradient" as an empty Output.
120-
// TF's gradient builder should tolerate missing gradients for non-differentiable inputs.
121-
grad_outputs->push_back(Output());
122-
continue;
123-
}
124-
125-
grad_outputs->push_back(Output(struct_cast<Node>(out.oper), out.index));
126-
}
127-
128-
if (outputs != nullptr) free(outputs);
129-
return OkStatus();
130-
}
34+
namespace java {
35+
36+
using namespace tsl;
37+
using namespace std;
38+
39+
unordered_map<string, TFJ_GradFuncAdapter> g_grad_func_adapters;
40+
41+
// Cast helper (inspired by TF C-API)
42+
template <typename T, typename U>
43+
T* struct_cast(U* ptr) {
44+
return static_cast<T*>(static_cast<void*>(ptr));
45+
}
46+
47+
// Bridge called by TF runtime when building gradients for op
48+
Status CustomGradFunc(const Scope& scope,
49+
const Operation& op,
50+
const vector<Output>& grad_inputs,
51+
vector<Output>* grad_outputs) {
52+
const string& op_type = op.node()->type_string();
53+
auto found_adapter = g_grad_func_adapters.find(op_type);
54+
if (found_adapter == g_grad_func_adapters.end()) {
55+
return errors::NotFound("No gradient adapter found for operation ", op_type);
56+
}
57+
58+
TFJ_GradFuncAdapter adapter = found_adapter->second;
59+
if (adapter == nullptr) {
60+
return errors::Unknown("Null Java gradient adapter for op ", op_type);
61+
}
62+
63+
const int num_inputs = static_cast<int>(grad_inputs.size());
64+
65+
TF_Output* inputs = nullptr;
66+
if (num_inputs > 0) {
67+
inputs = static_cast<TF_Output*>(malloc(num_inputs * sizeof(TF_Output)));
68+
if (inputs == nullptr) {
69+
return errors::ResourceExhausted(
70+
"Out of memory allocating inputs for custom gradient of op ", op_type);
71+
}
72+
}
73+
74+
for (int i = 0; i < num_inputs; ++i) {
75+
const Output& grad_input = grad_inputs[i];
76+
inputs[i].oper = struct_cast<TF_Operation>(grad_input.node());
77+
inputs[i].index = grad_input.index();
78+
}
79+
80+
TF_Output* outputs = nullptr;
81+
82+
LOG(INFO) << "Calling Java gradient function for operation of type " << op_type;
83+
const int num_outputs = adapter(
84+
static_cast<TFJ_GraphId>(scope.graph()),
85+
struct_cast<TFJ_Scope>(const_cast<Scope*>(&scope)),
86+
struct_cast<TF_Operation>(op.node()),
87+
inputs,
88+
num_inputs,
89+
&outputs);
90+
91+
if (inputs != nullptr) free(inputs);
92+
93+
// Adapter contract:
94+
// - num_outputs < 0 indicates failure
95+
// - num_outputs == 0: OK, outputs may be nullptr
96+
// - num_outputs > 0: outputs must be non-null
97+
if (num_outputs < 0) {
98+
if (outputs != nullptr) free(outputs);
99+
return errors::Unknown("Java custom gradient adapter failed for op ", op_type,
100+
" (num_outputs=", num_outputs, ")");
101+
}
102+
if (num_outputs > 0 && outputs == nullptr) {
103+
return errors::Unknown("Java custom gradient adapter returned null outputs for op ",
104+
op_type, " with num_outputs=", num_outputs);
105+
}
106+
107+
grad_outputs->reserve(grad_outputs->size() + static_cast<size_t>(num_outputs));
108+
109+
for (int i = 0; i < num_outputs; ++i) {
110+
const TF_Output out = outputs[i];
111+
112+
// Convention: out.oper == nullptr => NoGradient
113+
if (out.oper == nullptr) {
114+
grad_outputs->push_back(Output()); // TF interprets empty Output as "no grad"
115+
continue;
131116
}
117+
118+
grad_outputs->push_back(Output(struct_cast<Node>(out.oper), out.index));
119+
}
120+
121+
if (outputs != nullptr) free(outputs); // allocated from Java via malloc
122+
return OkStatus();
132123
}
133124

125+
} // namespace java
126+
} // namespace tensorflow
127+
134128
using namespace tensorflow::ops;
135129
using namespace tensorflow::java;
136130

137131
bool TFJ_HasGradient(const char* op_type) {
138-
GradFunc dummy;
139-
tsl::Status status = GradOpRegistry::Global()->Lookup(op_type, &dummy);
140-
return status.ok();
132+
GradFunc dummy;
133+
tsl::Status status = GradOpRegistry::Global()->Lookup(op_type, &dummy);
134+
return status.ok();
141135
}
142136

143137
bool TFJ_RegisterCustomGradient(const char* op_type, TFJ_GradFuncAdapter grad_func_adapter) {
144-
LOG(INFO) << "TFJ_RegisterCustomGradient(" << op_type << ") adapter_ptr="
145-
<< reinterpret_cast<void*>(grad_func_adapter);
146-
147-
if (grad_func_adapter == nullptr) {
148-
LOG(ERROR) << "Refusing to register NULL Java gradient adapter for op " << op_type;
149-
return false;
150-
}
151-
152-
if (TFJ_HasGradient(op_type)) { // Check if gradient already exists otherwise the JVM might abort/crash
153-
LOG(WARNING) << "Tried to register Java gradient function for operation " << op_type
154-
<< ", which has already a registered function";
155-
return false;
156-
}
157-
bool registered = GradOpRegistry::Global()->Register(op_type, CustomGradFunc);
158-
if (registered) {
159-
g_grad_func_adapters.insert({op_type, grad_func_adapter});
160-
}
161-
return registered;
138+
LOG(INFO) << "TFJ_RegisterCustomGradient(" << op_type << ") adapter_ptr="
139+
<< reinterpret_cast<void*>(grad_func_adapter);
140+
141+
if (grad_func_adapter == nullptr) {
142+
LOG(ERROR) << "Refusing to register NULL Java gradient adapter for op " << op_type;
143+
return false;
144+
}
145+
146+
if (TFJ_HasGradient(op_type)) {
147+
LOG(WARNING) << "Tried to register Java gradient function for operation " << op_type
148+
<< ", which has already a registered function";
149+
return false;
150+
}
151+
152+
bool registered = GradOpRegistry::Global()->Register(op_type, CustomGradFunc);
153+
if (registered) {
154+
g_grad_func_adapters.insert({op_type, grad_func_adapter});
155+
}
156+
return registered;
162157
}
163158

164-
#else // #ifndef _WIN32
165-
166-
/* This extension is not available on Windows */
159+
#else // _WIN32
167160

168161
bool TFJ_HasGradient(const char* op_type) { return true; }
169-
bool TFJ_RegisterCustomGradient(const char* op_type, TFJ_GradFuncAdapter grad_func_adapter) { return false; }
162+
bool TFJ_RegisterCustomGradient(const char* op_type, TFJ_GradFuncAdapter grad_func_adapter) {
163+
return false;
164+
}
170165

171-
#endif // #ifndef _WIN32
166+
#endif // _WIN32

0 commit comments

Comments
 (0)