Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support keras style programming in eager mode and enable tf.function #196

Merged
merged 5 commits into from
Mar 23, 2022

Conversation

Lifann
Copy link
Member

@Lifann Lifann commented Dec 15, 2021

Background

Some of the design on Variable in current TensorFlow built on prerequisite of that variables are dense. And there is a gap between sparse Variable and dense Variable, since they have different shape mechanism, initializer implication, updating logic, etc., with agreement of RFC. Sparse Variable can be assume to be a superset to dense one, with more functionality and complex working mechanism.

Currently, in graph mode, the dynamic_embedding works fine, but in eager mode, we lack of either performance and not eazy to use. This PR aims to help the situation with supporting tf.function on dynamic_embedding and provide keras programming support.

In past versions of dynamic_embedding, we create a local variable to store the embedding lookup result temporarily, by calling embedding_lookup. If it was called inside scope of tf.function, the temporary variables will be translated to EagerTensor in the outputs:

@tf.function
def foo(devar, ids):
  lookup_result, trainable = de.embedding_lookup(devar, ids, return_trainable=True)
  return lookup_result, trainable

...

some_funcs_with_calling_foo()
tape.gradient(loss, [trainable])  # This will raise an error, since trainable is a EagerTensor, not a Variable.

Same issues are also existed when optimizers try to create slots.

Changes

  • Add a new class inherited from TrainableWrapper to lift the local variable out of the tf.function scope.
  • Support keras layers on dynamic_embedding

@Lifann Lifann requested a review from rhdong as a code owner December 15, 2021 08:32
@Lifann Lifann force-pushed the Lifann/implicit-func-transform branch from 1112cb8 to 001d90f Compare December 15, 2021 09:05
@Lifann Lifann force-pushed the Lifann/implicit-func-transform branch 18 times, most recently from cb56f4a to 1de368e Compare December 29, 2021 08:35
"""
ids = tf.convert_to_tensor(ids)
input_shape = tf.shape(ids)
lookup_result = de.shadow_ops.embedding_lookup(self.shadow, ids)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

embedding_lookup_unique is a better choice for following Keras Embedding layer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does! In dynamic_embedding_ops.py, the xxx_embedding_lookup APIs are wrappers of embedding_lookup. So I'm trying to reuse the codes in these wrappers, by applying code transform on de.embedding_lookup. It will take some time, maybe just making it unique_embedding_lookup on current keras layer is realistic.

@Lifann Lifann force-pushed the Lifann/implicit-func-transform branch 11 times, most recently from 8b65cd2 to cd67f46 Compare March 15, 2022 02:15
@Lifann
Copy link
Member Author

Lifann commented Mar 15, 2022

The CI was failed caused by keras-team/keras#15586

@Lifann Lifann force-pushed the Lifann/implicit-func-transform branch 2 times, most recently from a71ea0f to 8b0a4bc Compare March 15, 2022 09:51
@Lifann
Copy link
Member Author

Lifann commented Mar 15, 2022

Keras initializer may not only come from tf.keras.initializer, when Keras save and load model, it could come from keras.initializer. Also when Keras save model, it could be a functools.partial(). So in file "tensorflow_recommenders_addons/dynamic_embedding/python/ops/dynamic_embedding_variable.py", it may be better like this in _convert_anything_to_init function:

try:
  from keras.initializers import initializers_v2 as ikinit2
except ImportError:
  ikinit2 = None
  pass  # for compatible with TF < 2.3.x
def _convert_anything_to_init(self, raw_init, dim):
  try:
    init = raw_init.func # init may be a functools.partial
  except:
    init = raw_init
  valid_list = [init_ops.Initializer, init_ops_v2.Initializer]
  if kinit2 is not None:
    valid_list.append(kinit2.Initializer)
  if ikinit2 is not None:
    valid_list.append(ikinit2.Initializer) # init may come from keras.initializer
  valid_list = tuple(valid_list)
  while callable(init):
    if isinstance(init, valid_list):
      self.initializer = init
      init = init(shape=[1])
    else:
      try:
        init = init(shape=[1])
      except:
        init = init()
  try:
    init = array_ops.reshape(init, [dim])
  except:
    init = array_ops.fill([dim], array_ops.reshape(init, [-1])[0])
  init = math_ops.cast(init, dtype=self.value_dtype)
  return init

Accept

@Lifann Lifann force-pushed the Lifann/implicit-func-transform branch 4 times, most recently from 3b4bdff to 7cc07c3 Compare March 22, 2022 01:10
@Lifann Lifann force-pushed the Lifann/implicit-func-transform branch from 7cc07c3 to 7f9505b Compare March 22, 2022 06:36
@Lifann Lifann force-pushed the Lifann/implicit-func-transform branch from 7f9505b to bfb404a Compare March 23, 2022 05:17
Copy link
Member

@rhdong rhdong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@rhdong rhdong merged commit 6f7ed09 into tensorflow:master Mar 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants