shithub: opus

Download patch

ref: 98da33500966c6ba2b7b70a7259a33c23abe6b8c
parent: c7b6935bf2bb6287ceca3b3977618c03d1c007d1
author: Jean-Marc Valin <jmvalin@amazon.com>
date: Sun May 21 23:32:53 EDT 2023

Convert PLC weights to blob format

--- a/dnn/lpcnet_plc.c
+++ b/dnn/lpcnet_plc.c
@@ -44,6 +44,7 @@
 }
 
 LPCNET_EXPORT int lpcnet_plc_init(LPCNetPLCState *st, int options) {
+  int ret;
   RNN_CLEAR(st, 1);
   lpcnet_init(&st->lpcnet);
   lpcnet_encoder_init(&st->enc);
@@ -67,7 +68,9 @@
     return -1;
   }
   st->remove_dc = !!(options&LPCNET_PLC_DC_FILTER);
-  return 0;
+  ret = init_plc_model(&st->model, lpcnet_plc_arrays);
+  celt_assert(ret == 0);
+  return ret;
 }
 
 LPCNET_EXPORT LPCNetPLCState *lpcnet_plc_create(int options) {
@@ -105,13 +108,14 @@
 }
 
 
-static void compute_plc_pred(PLCNetState *net, float *out, const float *in) {
+static void compute_plc_pred(LPCNetPLCState *st, float *out, const float *in) {
   float zeros[3*PLC_MAX_RNN_NEURONS] = {0};
   float dense_out[PLC_DENSE1_OUT_SIZE];
-  _lpcnet_compute_dense(&plc_dense1, dense_out, in);
-  compute_gruB(&plc_gru1, zeros, net->plc_gru1_state, dense_out);
-  compute_gruB(&plc_gru2, zeros, net->plc_gru2_state, net->plc_gru1_state);
-  _lpcnet_compute_dense(&plc_out, out, net->plc_gru2_state);
+  PLCNetState *net = &st->plc_net;
+  _lpcnet_compute_dense(&st->model.plc_dense1, dense_out, in);
+  compute_gruB(&st->model.plc_gru1, zeros, net->plc_gru1_state, dense_out);
+  compute_gruB(&st->model.plc_gru2, zeros, net->plc_gru2_state, net->plc_gru1_state);
+  _lpcnet_compute_dense(&st->model.plc_out, out, net->plc_gru2_state);
   /* Artificially boost the correlation to make harmonics cleaner. */
   out[19] = MIN16(.5f, out[19]+.1f);
 }
@@ -127,11 +131,11 @@
     /* Update PLC state using FEC, so without Burg features. */
     RNN_COPY(&plc_features[2*NB_BANDS], out, NB_FEATURES);
     plc_features[2*NB_BANDS+NB_FEATURES] = -1;
-    compute_plc_pred(&st->plc_net, discard, plc_features);
+    compute_plc_pred(st, discard, plc_features);
     return 1;
   } else {
     float zeros[2*NB_BANDS+NB_FEATURES+1] = {0};
-    compute_plc_pred(&st->plc_net, out, zeros);
+    compute_plc_pred(st, out, zeros);
     if (st->fec_skip > 0) st->fec_skip--;
     return 0;
   }
@@ -187,7 +191,7 @@
       if (st->enable_blending) {
         LPCNetState copy;
         st->plc_net = st->plc_copy[FEATURES_DELAY];
-        compute_plc_pred(&st->plc_net, st->features, zeros);
+        compute_plc_pred(st, st->features, zeros);
         for (i=0;i<FEATURES_DELAY;i++) {
           /* FIXME: backtrack state, replace features. */
           run_frame_network_deferred(&st->lpcnet, st->features);
@@ -227,7 +231,7 @@
   if (!st->blend) {
     RNN_COPY(&plc_features[2*NB_BANDS], st->enc.features[0], NB_FEATURES);
     plc_features[2*NB_BANDS+NB_FEATURES] = 1;
-    compute_plc_pred(&st->plc_net, st->features, plc_features);
+    compute_plc_pred(st, st->features, plc_features);
     /* Discard an FEC frame that we know we will no longer need. */
     if (st->fec_skip) st->fec_skip--;
     else if (st->fec_read_pos < st->fec_fill_pos) st->fec_read_pos++;
@@ -352,7 +356,7 @@
     float zeros[2*NB_BANDS+NB_FEATURES+1] = {0};
     RNN_COPY(zeros, plc_features, 2*NB_BANDS);
     zeros[2*NB_BANDS+NB_FEATURES] = 1;
-    compute_plc_pred(&st->plc_net, st->features, zeros);
+    compute_plc_pred(st, st->features, zeros);
     copy = st->lpcnet;
     lpcnet_synthesize_impl(&st->lpcnet, st->features, &st->pcm[FRAME_SIZE-TRAINING_OFFSET], TRAINING_OFFSET, 0);
     /* Undo initial DC offset removal so that we can take into account the last 5ms of synthesis. */
@@ -405,7 +409,7 @@
   if (st->loss_count == 0) {
     RNN_COPY(&plc_features[2*NB_BANDS], st->enc.features[0], NB_FEATURES);
     plc_features[2*NB_BANDS+NB_FEATURES] = 1;
-    compute_plc_pred(&st->plc_net, st->features, plc_features);
+    compute_plc_pred(st, st->features, plc_features);
     lpcnet_synthesize_impl(&st->lpcnet, st->enc.features[0], &st->pcm[FRAME_SIZE-TRAINING_OFFSET], TRAINING_OFFSET, TRAINING_OFFSET);
     lpcnet_synthesize_tail_impl(&st->lpcnet, pcm, FRAME_SIZE-TRAINING_OFFSET, FRAME_SIZE-TRAINING_OFFSET);
   }
@@ -428,7 +432,7 @@
   process_queued_update(st);
   st->enc.pcount = 0;
 
-  compute_plc_pred(&st->plc_net, st->features, zeros);
+  compute_plc_pred(st, st->features, zeros);
   if (st->loss_count >= 10) st->features[0] = MAX16(-10, st->features[0]+att_table[9] - 2*(st->loss_count-9));
   else st->features[0] = MAX16(-10, st->features[0]+att_table[st->loss_count]);
 
--- a/dnn/lpcnet_private.h
+++ b/dnn/lpcnet_private.h
@@ -98,6 +98,7 @@
   short dc_buf[TRAINING_OFFSET];
   int queued_update;
   short queued_samples[FRAME_SIZE];
+  PLCModel model;
 };
 
 extern float ceps_codebook1[];
--- a/dnn/nnet.h
+++ b/dnn/nnet.h
@@ -147,7 +147,7 @@
 
 
 extern const WeightArray lpcnet_arrays[];
-
+extern const WeightArray lpcnet_plc_arrays[];
 
 int mdense_init(MDenseLayer *layer, const WeightArray *arrays,
   const char *bias,
--- a/dnn/training_tf2/dump_plc.py
+++ b/dnn/training_tf2/dump_plc.py
@@ -27,6 +27,7 @@
 '''
 
 import lpcnet_plc
+import io
 import sys
 import numpy as np
 from tensorflow.keras.optimizers import Adam
@@ -41,11 +42,17 @@
 max_conv_inputs = 1
 
 def printVector(f, vector, name, dtype='float', dotp=False):
+    global array_list
     if dotp:
         vector = vector.reshape((vector.shape[0]//4, 4, vector.shape[1]//8, 8))
         vector = vector.transpose((2, 0, 3, 1))
     v = np.reshape(vector, (-1));
     #print('static const float ', name, '[', len(v), '] = \n', file=f)
+    if name not in array_list:
+        array_list.append(name)
+    f.write('#ifndef USE_WEIGHTS_FILE\n')
+    f.write('#define WEIGHTS_{}_DEFINED\n'.format(name))
+    f.write('#define WEIGHTS_{}_TYPE WEIGHT_TYPE_{}\n'.format(name, dtype))
     f.write('static const {} {}[{}] = {{\n   '.format(dtype, name, len(v)))
     for i in range(0, len(v)):
         f.write('{}'.format(v[i]))
@@ -58,7 +65,8 @@
         else:
             f.write(" ")
     #print(v, file=f)
-    f.write('\n};\n\n')
+    f.write('\n};\n')
+    f.write('#endif\n\n')
     return;
 
 def printSparseVector(f, A, name, have_diag=True):
@@ -122,11 +130,11 @@
         reset_after = 1
     neurons = weights[0].shape[1]//3
     max_rnn_neurons = max(max_rnn_neurons, neurons)
-    f.write('const SparseGRULayer {} = {{\n   {}_bias,\n   {}_subias,\n   {}_recurrent_weights_diag,\n   {}_recurrent_weights,\n   {}_recurrent_weights_idx,\n   {}, ACTIVATION_{}, {}\n}};\n\n'
-            .format(name, name, name, name, name, name, weights[0].shape[1]//3, activation, reset_after))
     hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
     hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
-    hf.write('extern const SparseGRULayer {};\n\n'.format(name));
+    model_struct.write('  SparseGRULayer {};\n'.format(name));
+    model_init.write('  if (sparse_gru_init(&model->{}, arrays, "{}_bias", "{}_subias", "{}_recurrent_weights_diag", "{}_recurrent_weights", "{}_recurrent_weights_idx",  {}, ACTIVATION_{}, {})) return 1;\n'
+            .format(name, name, name, name, name, name, weights[0].shape[1]//3, activation, reset_after))
     return True
 
 def dump_gru_layer(self, f, hf):
@@ -158,11 +166,11 @@
         reset_after = 1
     neurons = weights[0].shape[1]//3
     max_rnn_neurons = max(max_rnn_neurons, neurons)
-    f.write('const GRULayer {} = {{\n   {}_bias,\n   {}_subias,\n   {}_weights,\n   {}_weights_idx,\n   {}_recurrent_weights,\n   {}, {}, ACTIVATION_{}, {}\n}};\n\n'
-            .format(name, name, name, name, name, name, weights[0].shape[0], weights[0].shape[1]//3, activation, reset_after))
     hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
     hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
-    hf.write('extern const GRULayer {};\n\n'.format(name));
+    model_struct.write('  GRULayer {};\n'.format(name));
+    model_init.write('  if (gru_init(&model->{}, arrays, "{}_bias", "{}_subias", "{}_weights", "{}_weights_idx", "{}_recurrent_weights", {}, {}, ACTIVATION_{}, {})) return 1;\n'
+             .format(name, name, name, name, name, name, weights[0].shape[0], weights[0].shape[1]//3, activation, reset_after))
     return True
 GRU.dump_layer = dump_gru_layer
 
@@ -178,10 +186,10 @@
 def dump_dense_layer_impl(name, weights, bias, activation, f, hf):
     printVector(f, weights, name + '_weights')
     printVector(f, bias, name + '_bias')
-    f.write('const DenseLayer {} = {{\n   {}_bias,\n   {}_weights,\n   {}, {}, ACTIVATION_{}\n}};\n\n'
-            .format(name, name, name, weights.shape[0], weights.shape[1], activation))
     hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights.shape[1]))
-    hf.write('extern const DenseLayer {};\n\n'.format(name));
+    model_struct.write('  DenseLayer {};\n'.format(name));
+    model_init.write('  if (dense_init(&model->{}, arrays, "{}_bias", "{}_weights", {}, {}, ACTIVATION_{})) return 1;\n'
+            .format(name, name, name, weights.shape[0], weights.shape[1], activation))
 
 def dump_dense_layer(self, f, hf):
     name = self.name
@@ -202,12 +210,12 @@
     printVector(f, weights[-1], name + '_bias')
     activation = self.activation.__name__.upper()
     max_conv_inputs = max(max_conv_inputs, weights[0].shape[1]*weights[0].shape[0])
-    f.write('const Conv1DLayer {} = {{\n   {}_bias,\n   {}_weights,\n   {}, {}, {}, ACTIVATION_{}\n}};\n\n'
-            .format(name, name, name, weights[0].shape[1], weights[0].shape[0], weights[0].shape[2], activation))
     hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[2]))
     hf.write('#define {}_STATE_SIZE ({}*{})\n'.format(name.upper(), weights[0].shape[1], (weights[0].shape[0]-1)))
     hf.write('#define {}_DELAY {}\n'.format(name.upper(), (weights[0].shape[0]-1)//2))
-    hf.write('extern const Conv1DLayer {};\n\n'.format(name));
+    model_struct.write('  Conv1DLayer {};\n'.format(name));
+    model_init.write('  if (conv1d_init(&model->{}, arrays, "{}_bias", "{}_weights", {}, {}, {}, ACTIVATION_{})) return 1;\n'
+            .format(name, name, name, weights[0].shape[1], weights[0].shape[0], weights[0].shape[2], activation))
     return True
 Conv1D.dump_layer = dump_conv1d_layer
 
@@ -235,6 +243,12 @@
 
 f = open(cfile, 'w')
 hf = open(hfile, 'w')
+model_struct = io.StringIO()
+model_init = io.StringIO()
+model_struct.write('typedef struct {\n')
+model_init.write('#ifndef DUMP_BINARY_WEIGHTS\n')
+model_init.write('int init_plc_model(PLCModel *model, const WeightArray *arrays) {\n')
+array_list = []
 
 
 f.write('/*This file is automatically generated from a Keras model*/\n')
@@ -250,7 +264,20 @@
         layer_list.append(layer.name)
 
 #dump_sparse_gru(model.get_layer('gru_a'), f, hf)
+f.write('#ifndef USE_WEIGHTS_FILE\n')
+f.write('const WeightArray lpcnet_plc_arrays[] = {\n')
+for name in array_list:
+    f.write('#ifdef WEIGHTS_{}_DEFINED\n'.format(name))
+    f.write('  {{"{}", WEIGHTS_{}_TYPE, sizeof({}), {}}},\n'.format(name, name, name, name))
+    f.write('#endif\n')
+f.write('  {NULL, 0, 0, NULL}\n};\n')
+f.write('#endif\n')
 
+model_init.write('  return 0;\n}\n')
+model_init.write('#endif\n')
+f.write(model_init.getvalue())
+
+
 hf.write('#define PLC_MAX_RNN_NEURONS {}\n\n'.format(max_rnn_neurons))
 #hf.write('#define PLC_MAX_CONV_INPUTS {}\n\n'.format(max_conv_inputs))
 
@@ -257,7 +284,11 @@
 hf.write('typedef struct {\n')
 for i, name in enumerate(layer_list):
     hf.write('  float {}_state[{}_STATE_SIZE];\n'.format(name, name.upper())) 
-hf.write('} PLCNetState;\n')
+hf.write('} PLCNetState;\n\n')
+
+model_struct.write('} PLCModel;\n\n')
+hf.write(model_struct.getvalue())
+hf.write('int init_plc_model(PLCModel *model, const WeightArray *arrays);\n\n')
 
 hf.write('\n\n#endif\n')
 
--