Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Commit 6fbc0f0

Browse files
committed
changes
1 parent 0415879 commit 6fbc0f0

File tree

2 files changed

+430
-0
lines changed

2 files changed

+430
-0
lines changed

governance/api-reviews.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# tf-api-owners review practices
2+
3+
## Overview
4+
5+
This is an attempt to gather commonly discussed topics when doing tf-api-owners
6+
reviews. It’ll hopefully be a useful resource to both API owners and people
7+
proposing API changes. Process TF API Owners meet twice weekly to discuss
8+
changes. We try to get to PRs on the next meeting, but we don’t always make it
9+
all the way through. If your change is particularly urgent, please ping the PR
10+
to notify us of any urgency.
11+
12+
## Process
13+
14+
We only look at changes which have already been approved by other reviewers. If
15+
there are major outstanding comments, we will wait with API review until those
16+
are resolved. If there are questions for API owners, explicitly raise this in
17+
the comments to get an answer.
18+
19+
20+
## High level points
21+
22+
### Backward and forward compatibility
23+
We avoid backwards-incompatible API changes. We also avoid
24+
backwards-incompatible behavior changes, such as restricting the set of valid
25+
inputs to a function or extending the set of valid outputs of a function. Adding
26+
support for previously not supported behavior is okay, as are changes to
27+
explicitly experimental APIs (within reason). When needing to provide a new or
28+
different behavior, we strongly prefer a new version of the API over breaking
29+
backwards compatibility. Note that we are free to deprecate APIs; we just cannot
30+
break code which relies on their documented behavior. We need to worry about
31+
backward compatibility both of our python APIs and of the serialized GraphDefs,
32+
and in general breaking serialized GraphDefs is worse than breaking the python
33+
APIs.
34+
35+
Forward compatibility is more subtle: we should avoid changing the graph
36+
produced by currently correct python code without a three weeks notice . This
37+
comes up most frequently when adding new ops, but also applies to non-obvious
38+
things such as the graph emitted by gradients or pfor.
39+
40+
Including the name “experimental” in an API endpoint allows you to break
41+
backwards compatibility in the future, as noted above. However, we still prefer
42+
to mark the experimental API as deprecated for one release before removing it in
43+
the subsequent release. Please do not use the experimental namespace as an
44+
escape hatch for bad code or functionality you
45+
don’t-really-want-but-need-for-now; experimental APIs should be APIs that we
46+
intend to make standard in the near future, but have some lingering questions to
47+
resolve first.
48+
49+
50+
### Docstrings
51+
52+
TF APIs should have comprehensive documentation in the form of docstrings. If at
53+
all possible these docstrings should have runnable examples, and these examples
54+
should form a doctest so they stay correct. The examples should demonstrate an
55+
end-to-end user workflow, such that it’s clear how to generate the necessary
56+
inputs for the API and what to do with the outputs. The docstring should be
57+
understandable by someone who is not familiar with TF.
58+
59+
Our documentation generator for classes only sees methods, so prefer defining
60+
members as properties instead of assigning them in `__init__`.
61+
62+
Docstrings should only refer to other public TF API symbols (i.e. do not refer
63+
to other symbols defined in the same file as a function which is just now being
64+
made public) and should refer to public API symbols by their full exported name.
65+
66+
### Common names
67+
68+
69+
Prefer keepdims over keep_dims. Prefer axis over dim. Data types are called
70+
dtype. name is a common last argument of ops but backward compatibility mandates
71+
that new arguments are added after the last existing argument, even if that
72+
results in name not being the last argument.
73+
74+
We generally prefer spelling things out over using abbreviations except when
75+
abbreviations are more standard than spelling things out (i.e. don’t spell out
76+
linalg or svd). When in doubt we ask domain experts or use web search to see
77+
what spelling is most common.
78+
79+
If possible we prefer to name things in a similar way to numpy (e.g., we would
80+
not pick einsum as a name, but numpy and MatLab before it has, and that
81+
precedent is very strong).
82+
83+
We prefer experimental namespaces (i.e. tf.data.experimental.foobar) over
84+
experimental-prefixed names (i.e. tf.data.experimental_foobar) except when
85+
adding an experimental class method, or an experimental argument. Experimental
86+
endpoints should be deprecated in a minor release before they can be removed in
87+
the next. We would like new experimental symbols to be things which will
88+
eventually end up in core TF as opposed to things we expect will be phased out
89+
with no clear replacement. The best expectation to have for an experimental
90+
endpoint is that the “experimental” will simply be removed. If you don’t believe
91+
that’ll work, it should probably not be added in its current form. Style
92+
Generally, follow Google style.
93+
94+
Avoid redundancy. Do not write arguments of the form `function(...,
95+
enable_feature=False, feature_config=None)` if you can also write `function(...,
96+
feature_config=None)`, where implicitly, `enable_feature = feature_config is not
97+
None`.
98+
99+
Try to embed well with the ambient language. Think about how your API interacts
100+
with language idioms (e.g., in Python: can it be hashed, i.e., used as a dict
101+
key? Is it iterable? Is it a mapping? Can it be equality compared?
102+
Ordered?). Think about how your API interacts with other pieces of the Python
103+
ecosystem as well— is there an analogue in Numpy or PyTorch that we should
104+
consider aligning with?
105+
106+
Use language-native constructs wherever you can. In Python, a tuple should be a
107+
tuple. The bar for custom configuration objects is relatively high, a dict or
108+
namedtuple goes a long way.
109+
110+
In particular, do not expose protobufs directly as part of an API. You can use
111+
protobufs for serialization or to encode network traffic. Protobufs should
112+
always be an implementation detail, and never visible on the API surface. Use
113+
language native constructs (dicts or classes for Python, structs for C/C++) if
114+
you need configuration objects.
115+
116+
Avoid global (or any non-local) state as much as possible (this includes Python
117+
'with' scopes). If you need global context, consider whether it can be
118+
thread-local. The TF API is supposed to be thread-safe. Avoid stateful operation
119+
(mutability) if you can. Both features make it hard to reason about code, and
120+
make composability harder to achieve.
121+
122+
### Orthogonality and integration with the existing APIs
123+
124+
Is the new API implementable in terms of existing APIs? If so, we might want to
125+
consider pointing users to using the existing APIs. Does the new API add enough
126+
value over a combination of existing APIs? Does the API solve only a specific
127+
problem (that’s usually a sign combinations of existing APIs would be
128+
preferred)?
129+
130+
If not, are existing APIs implementable in terms of the new API? If this is
131+
simple, we might want to steer users towards the new and away from the old API
132+
(possibly, old APIs should be deprecated along with introducing the new API).
133+
134+
If neither is the case, it might be possible that there is a more general API
135+
which makes both the existing API and the new API easy to express. We try to
136+
keep global consistency of our API in mind when reviewing new changes.
137+
138+
How will this API work together with others? Does it do something slightly
139+
differently than others? Does it expect inputs which match what other parts of
140+
TensorFlow produce? Does its output match what other parts of TensorFlow can
141+
consume?
142+
143+
Does it do things the same way other similar pieces in TensorFlow do it? E.g.,
144+
if a common pattern to achieve a behavior is an extra argument, don't use a
145+
function decorator to achieve the same in a different area of the API.
146+
147+
Two wrongs don’t make a right. That is, if a bad API already exists in TF, that
148+
does not give license to new APIs to be bad in the same way. Improvement must be
149+
balanced with consistency, however, and sometimes it’s okay to carry small
150+
imperfections into new APIs for the sake of consistency with old APIs.
151+
152+
### Does it belong in TF at all?
153+
154+
As TF evolves there’s a tendency to put everything inside of it, with costs
155+
compounding over the long term. If there is a reasonable home for a new API
156+
outside core TF (say in addons, io, TFP, or other projects entirely) that can be
157+
strongly preferrable. If new code can be released as independent libraries, it
158+
should be. This is especially true for APIs that are actively evolving; core TF
159+
imposes many restrictions, so it’s far better to trial new APIs outside of the
160+
core library.
161+
162+
## Adding new ops
163+
164+
Adding new ops to TF should be done with care. We generally prefer not adding
165+
new ops if possible, but performance, hardware compatibility, and other concerns
166+
often do require new ops.
167+
168+
When adding new ops, look for:
169+
- closure under automatic differentiation (i.e. we avoid ops which are
170+
differentiable but not twice-differentiable, or which are technically
171+
differentiable but not marked as such)
172+
- performant kernels (it’s better not to have an op than to have an op with a
173+
suboptimal kernel; we need to make sure kernel experts have reviewed the
174+
code)
175+
- broadcasting (all numerical ops should broadcast using numpy rules)
176+
- does support for this op have to be added to pfor?
177+
- dtype support (in general all numerical ops should support the common
178+
integer, floating point, and complex dtypes, if they all make sense; we need
179+
to watch out for int32 on GPUs though)
180+
- device support (cuda kernels should be implemented if possible, and similarly
181+
a tf/xla bridge entry should be added if it makes sense)
182+
- attributes versus inputs (anything which can be an input to an operation
183+
should be an input, and attributes should only be used to parametrize the
184+
operation in ways that affect the output dtypes or sometimes shapes)
185+
- state management (is the op stateful? Can it instead be made stateless and
186+
rely on optimizations like memory reuse for performance? Can it be made to
187+
keep its state using one of the existing mechanisms like variables? If not,
188+
its state should be encapsulated using resource handles if at all possible)
189+
- we generally don’t like ops which are supported only on a single device (be
190+
it CPU, GPU, XLA, TPU, etc) and prefer to have at least a plan for writing
191+
device-agnostic code
192+
- should the python layer for this operation support raggedtensor/sparsetensor?
193+
194+
195+
## Keras changes
196+
197+
Keras has its own external API and maintainers, as well as an internal
198+
team. Keras API changes tend to be cross-cutting as core Keras classes are base
199+
classes to many TF API endpoints. We generally only look at keras changes after
200+
a keras owner (say, fchollet or karmel) has already approved it on their
201+
end. fchollet has guidelines for changes to the Keras API at
202+
go/keras-api-guidelines. These include many generally good practices that are
203+
useful to keep in mind outside of Keras as well.

0 commit comments

Comments
 (0)