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

Commit 15900b0

Browse files
authored
Update 20201221-tfmot-compression-api.md
* Change get_compressible_weights method return type from str to tf.Variable. * Move the user-facing API into the algorithm class.
1 parent 3d2b7f9 commit 15900b0

File tree

1 file changed

+48
-62
lines changed

1 file changed

+48
-62
lines changed

rfcs/20201221-tfmot-compression-api.md

Lines changed: 48 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,21 @@ Our API also provides guidelines for testing and benchmark. For now, we only hav
4949
We provide the tutorial for [SVD](https://en.wikipedia.org/wiki/Singular_value_decomposition) compression algorithm that shows how we implement the SVD algorithm using TFMOT compression API by colab. This tutorial includes:
5050

5151
#### Algorithm developer side
52-
1. The algorithm developer implementing the SVD algorithm uses the `WeightCompressor` class.
52+
The algorithm developer implementing the SVD algorithm uses the `WeightCompressor` class. It also includes a custom model developer API for the SVD algorithm.
5353

5454
```python
5555
class SVD(algorithm.WeightCompressor):
5656
"""SVD compression module config."""
5757

58-
def __init__(self, params):
59-
self.params = params
58+
def __init__(self, rank):
59+
self.rank = rank
6060

6161
def init_training_weights(
6262
self, pretrained_weight: tf.Tensor):
6363
"""Init function from pre-trained model case."""
64-
rank = self.params.rank
65-
6664
# Dense Layer
6765
if len(pretrained_weight.shape) == 2:
68-
u, sv = tf_svd_factorization_2d(pretrained_weight, rank)
66+
u, sv = tf_svd_factorization_2d(pretrained_weight, self.rank)
6967
else:
7068
raise NotImplementedError('Only for dimension=2 is supported.')
7169

@@ -84,45 +82,34 @@ class SVD(algorithm.WeightCompressor):
8482
return tf.matmul(u, sv)
8583

8684
def get_compressible_weights(
87-
self, original_layer: tf.keras.layers.Layer) -> List[str]:
88-
rank = self.params.rank
85+
self, original_layer: tf.keras.layers.Layer) -> List[tf.Variable]:
8986
if isinstance(original_layer, tf.keras.layers.Dense):
9087
input_dim = original_layer.kernel.shape[0]
9188
output_dim = original_layer.kernel.shape[1]
92-
if input_dim * output_dim > (input_dim + output_dim) * rank:
93-
return ['kernel']
89+
if input_dim * output_dim > (input_dim + output_dim) * self.rank:
90+
return [original_layer.kernel]
9491
return []
95-
```
96-
97-
2. Export the model developer API for the SVD algorithm.
98-
```python
99-
class SVDParams(object):
100-
"""Define container for parameters for SVD algorithm."""
101-
102-
def __init__(self, rank):
103-
self.rank = rank
10492

105-
def optimize(to_optimize: tf.keras.Model, params: SVDParams) -> tf.keras.Model:
106-
"""Model developer API for optimizing a model."""
93+
def optimize(self, to_optimize: tf.keras.Model) -> tf.keras.Model:
94+
"""Model developer API for optimizing a model."""
10795

108-
def _optimize_layer(layer):
109-
# Require layer to be built so that the SVD-factorized weights
110-
# can be initialized from the weights.
111-
if not layer.built:
112-
raise ValueError(
113-
'Applying SVD currently requires passing in a built model')
96+
def _optimize_layer(layer):
97+
# Require layer to be built so that the SVD-factorized weights
98+
# can be initialized from the weights.
99+
if not layer.built:
100+
raise ValueError(
101+
'Applying SVD currently requires passing in a built model')
114102

115-
return algorithm.create_layer_for_training(layer, algorithm=SVD(params))
103+
return algorithm.create_layer_for_training(layer, algorithm=self)
116104

117-
return tf.keras.models.clone_model(
118-
to_optimize, clone_function=_optimize_layer)
105+
return tf.keras.models.clone_model(
106+
to_optimize, clone_function=_optimize_layer)
119107
```
120108

121109
#### Model developer side
122110
1. The model developer uses the SVD algorithm.
123111
```python
124-
params = SVDParams(rank=32)
125-
compressed_model = optimize(model, params)
112+
compressed_model = SVD(rank=32).optimize(model)
126113

127114
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
128115
compressed_model.compile(optimizer='adam', loss=loss_fn, metrics=['accuracy'])
@@ -175,17 +162,15 @@ class WeightCompressor(metaclass=abc.ABCMeta):
175162

176163
@abc.abstractmethod
177164
def get_compressible_weights(
178-
self, original_layer: tf.keras.layers.Layer) -> List[str]:
165+
self, original_layer: tf.keras.layers.Layer) -> List[tf.Variable]:
179166
"""Define compressible weights for each layer.
180167
181168
Args:
182169
original_layer: tf.keras.layers.Layer representing a layer from the
183170
original model.
184171
185172
Returns:
186-
List of attribute names as string representing list of compressible
187-
weights for the given layer. (e.g. return value ['kernel'] means
188-
layer.kernel is compressible.)
173+
List of compressible weights for the given layer.
189174
"""
190175

191176
@abc.abstractmethod
@@ -332,50 +317,51 @@ We have two steps for user facing API for general cases.
332317
(The SVD case training model is the same as the compressed model, so it only has one step.)
333318

334319
```python
335-
def optimize_training(to_optimize: tf.keras.Model, params: CustomParams) -> tf.keras.Model:
336-
"""Model developer API for optimizing a model."""
320+
class CustomWeightCompressor(WeightCompressor):
321+
def optimize_training(self, to_optimize: tf.keras.Model) -> tf.keras.Model:
322+
"""Model developer API for optimizing a model."""
337323

338-
def _optimize_layer(layer: tf.keras.layers.Layer) -> tf.keras.layers.Layer:
339-
if not layer.built:
340-
raise ValueError(
341-
'Applying compression currently requires passing in a built model')
324+
def _optimize_layer(layer: tf.keras.layers.Layer) -> tf.keras.layers.Layer:
325+
if not layer.built:
326+
raise ValueError(
327+
'Applying compression currently requires passing in a built model')
342328

343-
return algorithm.create_layer_for_training(
344-
layer, algorithm=CustomWeightCompressor(params))
329+
return algorithm.create_layer_for_training(
330+
layer, algorithm=self)
345331

346-
return tf.keras.models.clone_model(
347-
to_optimize, clone_function=_optimize_layer)
332+
return tf.keras.models.clone_model(
333+
to_optimize, clone_function=_optimize_layer)
348334

349335

350-
def optimize_inference(to_optimize: tf.keras.Model, params: CustomParams) -> tf.keras.Model:
351-
"""Model developer API for optimizing a model."""
336+
def optimize_inference(self, to_optimize: tf.keras.Model) -> tf.keras.Model:
337+
"""Model developer API for optimizing a model."""
352338

353-
def _optimize_layer(layer: tf.keras.layers.Layer) -> tf.keras.layers.Layer:
354-
if not layer.built:
355-
raise ValueError(
356-
'Applying compression currently requires passing in a built model')
339+
def _optimize_layer(layer: tf.keras.layers.Layer) -> tf.keras.layers.Layer:
340+
if not layer.built:
341+
raise ValueError(
342+
'Applying compression currently requires passing in a built model')
357343

358-
return algorithm.create_layer_for_inference(
359-
layer, algorithm=CustomWeightCompressor(params))
344+
return algorithm.create_layer_for_inference(
345+
layer, algorithm=self)
360346

361-
return tf.keras.models.clone_model(
362-
to_optimize, clone_function=_optimize_layer)
347+
return tf.keras.models.clone_model(
348+
to_optimize, clone_function=_optimize_layer)
363349
```
364350

365351
#### Model developer best practice.
366352

367353
Here's the best practice for general compression algorithm model developer code.
368354

369355
```python
370-
params = CustomParams()
371-
training_model = optimize_training(model, params)
356+
compressor = CustomWeightCompressor()
357+
training_model = compressor.optimize_training(model)
372358

373359
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
374360
training_model.compile(optimizer='adam', loss=loss_fn, metrics=['accuracy'])
375361

376362
training_model.fit(x_train, y_train, epochs=2)
377363

378-
compressed_model = optimize_inference(training_model, params)
364+
compressed_model = compressor.optimize_inference(training_model)
379365
compressed_model.evaluate(x_test, y_test, verbose=2)
380366
```
381367

@@ -389,7 +375,7 @@ Now we'll explain when each method is called and how many that method called for
389375
</p>
390376

391377
```python
392-
training_model = optimize_training(model, params)
378+
training_model = compressor.optimize_training(model)
393379
```
394380

395381
`get_compressible_weights` is called when we want to get a list of variables that we will apply compression.
@@ -401,7 +387,7 @@ When we try to compress the pre-trained model, we just call this method for each
401387
</p>
402388

403389
```python
404-
training_model = optimize_training(model, params)
390+
training_model = compressor.optimize_training(model)
405391
```
406392

407393
`init_training_weights` is called when we initialize the cloned training model from the pre-trained model. `optimize_training` method basically clones the model to create a training model for compression, wrapping compressible layers by the training wrapper to create training weights. The number of the method calling is (# of compressible weights).
@@ -423,7 +409,7 @@ training_model.fit(x_train, y_train, epochs=2)
423409
</p>
424410

425411
```python
426-
compressed_model = optimize_inference(training_model, params)
412+
compressed_model = compressor.optimize_inference(training_model)
427413
```
428414

429415
`compress_training_weights` is called when we convert the training model to the compressed model. The number of the method calling is (# of compressible weights).

0 commit comments

Comments
 (0)