/home/liu/actions-runner/_work/ccv/ccv/lib/nnc/ccv_cnnp_model_core.c
Line | Count | Source |
1 | | #include "ccv_nnc.h" |
2 | | #include "ccv_nnc_easy.h" |
3 | | #include "ccv_nnc_internal.h" |
4 | | #include "ccv_internal.h" |
5 | | #include "_ccv_cnnp_model.h" |
6 | | #include "3rdparty/khash/khash.h" |
7 | | |
8 | | // MARK - Baisc Layers |
9 | | |
10 | | static const ccv_cnnp_model_vtab_t ccv_cnnp_input_isa; |
11 | | |
12 | 2.78k | #define CCV_CNNP_IS_MODEL_INPUT(x) ((x)->isa == &ccv_cnnp_input_isa) |
13 | | |
14 | 2.87k | #define CCV_CNNP_IS_MODEL_PARAMETER(x) ((x)->param_ref != 0 || (x)->param_sel != 02.87k ) |
15 | | |
16 | | typedef struct { |
17 | | ccv_cnnp_model_t super; |
18 | | int sequence_size; |
19 | | ccv_cnnp_model_t* sequence[1]; |
20 | | } ccv_cnnp_sequential_model_t; |
21 | | |
22 | | static void _ccv_cnnp_sequential_model_deinit(ccv_cnnp_model_t* const super) |
23 | 1.10k | { |
24 | 1.10k | ccv_cnnp_sequential_model_t* const self = (ccv_cnnp_sequential_model_t*)super; |
25 | 1.10k | int i, j = 0; |
26 | 3.48k | for (i = 0; i < self->sequence_size; i++2.38k ) |
27 | 2.38k | { |
28 | 2.38k | ccv_cnnp_model_t* const model = self->sequence[i]; |
29 | 2.38k | if (model->deinit_state) |
30 | 8 | continue; |
31 | 2.37k | ccv_cnnp_model_deinit(model); |
32 | 2.37k | self->sequence[j++] = model; |
33 | 2.37k | } |
34 | 1.10k | self->sequence_size = j; |
35 | 1.10k | } |
36 | | |
37 | | static void _ccv_cnnp_sequential_model_dealloc(ccv_cnnp_model_t* const super) |
38 | 1.10k | { |
39 | 1.10k | ccv_cnnp_sequential_model_t* const self = (ccv_cnnp_sequential_model_t*)super; |
40 | 1.10k | int i; |
41 | 3.48k | for (i = 0; i < self->sequence_size; i++2.37k ) |
42 | 2.37k | ccv_cnnp_model_free(self->sequence[i]); |
43 | 1.10k | } |
44 | | |
45 | | static void _ccv_cnnp_sequential_model_build(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_nnc_tensor_symbol_t* const inputs, const int input_size, ccv_nnc_tensor_symbol_t* const outputs, const int output_size) |
46 | 1.09k | { |
47 | 1.09k | ccv_cnnp_sequential_model_t* const self = (ccv_cnnp_sequential_model_t*)super; |
48 | 1.09k | PRINT(CCV_CLI_VERBOSE, "[cnnp_sequential_model_build] 1. %p, sequence_size: %d\n", self, self->sequence_size); |
49 | 1.09k | ccv_cnnp_model_t* const sub_model = self->sequence[0]; |
50 | | // Go through each sub model to build the graph. |
51 | 1.09k | ccv_nnc_tensor_symbol_t input; |
52 | 1.09k | sub_model->data = self->super.data; |
53 | 1.09k | ccv_cnnp_model_build(sub_model, graph, inputs, input_size, &input, 1); |
54 | 1.09k | sub_model->data = 0; |
55 | 1.09k | int i; |
56 | 2.33k | for (i = 1; i < self->sequence_size; i++1.23k ) |
57 | 1.23k | { |
58 | 1.23k | ccv_nnc_tensor_symbol_t output; |
59 | 1.23k | ccv_cnnp_model_t* const sub_model = self->sequence[i]; |
60 | | // Go through each sub model to build the graph. |
61 | 1.23k | sub_model->data = self->super.data; |
62 | 1.23k | ccv_cnnp_model_build(sub_model, graph, &input, 1, &output, 1); |
63 | 1.23k | sub_model->data = 0; |
64 | 1.23k | input = output; |
65 | 1.23k | } |
66 | 1.09k | outputs[0] = input; |
67 | 1.09k | PRINT(CCV_CLI_VERBOSE, "[cnnp_sequential_model_build] 2. %p\n", self); |
68 | 1.09k | } |
69 | | |
70 | | static void _ccv_cnnp_sequential_model_init_states(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_cnnp_state_initializer_f initializer, void* const context) |
71 | 43 | { |
72 | 43 | ccv_cnnp_sequential_model_t* const self = (ccv_cnnp_sequential_model_t*)super; |
73 | 43 | int i; |
74 | 213 | for (i = 0; i < self->sequence_size; i++170 ) |
75 | 170 | ccv_cnnp_model_init_states(self->sequence[i], graph, initializer, context); |
76 | 43 | } |
77 | | |
78 | | static void _ccv_cnnp_sequential_model_set_is_test(ccv_cnnp_model_t* const super, const int is_test, const ccv_cnnp_cmd_updater_f updater, void* const context) |
79 | 67 | { |
80 | 67 | ccv_cnnp_sequential_model_t* const self = (ccv_cnnp_sequential_model_t*)super; |
81 | 67 | int i; |
82 | 370 | for (i = 0; i < self->sequence_size; i++303 ) |
83 | 303 | ccv_cnnp_model_set_is_test(self->sequence[i], is_test, updater, context); |
84 | 67 | } |
85 | | |
86 | | static ccv_cnnp_model_t* _ccv_cnnp_sequential_model_copy(const ccv_cnnp_model_t* const super, void* const context); |
87 | | |
88 | | static void _ccv_cnnp_sequential_model_add_to_parameter_indices(ccv_cnnp_model_t* const super, const int index, ccv_array_t* const parameter_indices) |
89 | 2.66k | { |
90 | 2.66k | ccv_cnnp_sequential_model_t* const self = (ccv_cnnp_sequential_model_t*)super; |
91 | 2.66k | int i; |
92 | 11.9k | for (i = 0; i < self->sequence_size; i++9.27k ) |
93 | 9.27k | ccv_cnnp_model_add_to_parameter_indices(self->sequence[i], index, parameter_indices); |
94 | 2.66k | } |
95 | | |
96 | | static void _ccv_cnnp_sequential_model_notify(const ccv_cnnp_model_t* const super, const int tag, void* const payload) |
97 | 0 | { |
98 | 0 | ccv_cnnp_sequential_model_t* const self = (ccv_cnnp_sequential_model_t*)super; |
99 | 0 | int i; |
100 | 0 | for (i = 0; i < self->sequence_size; i++) |
101 | 0 | ccv_cnnp_model_notify(self->sequence[i], tag, payload); |
102 | 0 | } |
103 | | |
104 | | static const ccv_cnnp_model_vtab_t ccv_cnnp_sequential_model_isa = { |
105 | | .deinit = _ccv_cnnp_sequential_model_deinit, |
106 | | .dealloc = _ccv_cnnp_sequential_model_dealloc, |
107 | | .build = _ccv_cnnp_sequential_model_build, |
108 | | .init_states = _ccv_cnnp_sequential_model_init_states, |
109 | | .copy = _ccv_cnnp_sequential_model_copy, |
110 | | .set_is_test = _ccv_cnnp_sequential_model_set_is_test, |
111 | | .add_to_parameter_indices = _ccv_cnnp_sequential_model_add_to_parameter_indices, |
112 | | .notify = _ccv_cnnp_sequential_model_notify, |
113 | | }; |
114 | | |
115 | | KHASH_MAP_INIT_INT64(model, ccv_cnnp_model_t*) |
116 | | |
117 | | static ccv_cnnp_model_t* _ccv_cnnp_sequential_model_copy(const ccv_cnnp_model_t* const super, void* const context) |
118 | 1.01k | { |
119 | 1.01k | const ccv_cnnp_sequential_model_t* const self = (const ccv_cnnp_sequential_model_t*)super; |
120 | 1.01k | ccv_cnnp_sequential_model_t* const sequential_model = (ccv_cnnp_sequential_model_t*)cccalloc(1, sizeof(ccv_cnnp_sequential_model_t) + sizeof(ccv_cnnp_model_t*) * (self->sequence_size - 1) + sizeof(ccv_nnc_tensor_symbol_t)); |
121 | 1.01k | sequential_model->super.isa = &ccv_cnnp_sequential_model_isa; |
122 | 1.01k | sequential_model->super.input_size = 1; |
123 | 1.01k | sequential_model->super.outputs = (ccv_nnc_tensor_symbol_t*)(sequential_model->sequence + self->sequence_size); |
124 | 1.01k | sequential_model->super.output_size = 1; |
125 | 1.01k | ccv_cnnp_model_copy_name(&sequential_model->super, self->super.name); |
126 | 1.01k | sequential_model->sequence_size = self->sequence_size; |
127 | 1.01k | int i; |
128 | 1.01k | khash_t(model)* model_map = context ? (khash_t(model)*)context10 : kh_init1.00k (model); |
129 | 3.06k | for (i = 0; i < self->sequence_size; i++2.04k ) |
130 | 2.04k | { |
131 | 2.04k | ccv_cnnp_model_t* const sub_model = self->sequence[i]; |
132 | 2.04k | int ret; |
133 | 2.04k | khiter_t k = kh_put(model, model_map, (uint64_t)(uintptr_t)sub_model, &ret); |
134 | 2.04k | ccv_cnnp_model_t* model_copy; |
135 | 2.04k | if (ret != 0) |
136 | 2.04k | model_copy = kh_val(model_map, k) = _ccv_cnnp_model_copy(sub_model, model_map); |
137 | 1 | else |
138 | 1 | model_copy = kh_val(model_map, k); |
139 | 2.04k | sequential_model->sequence[i] = model_copy; |
140 | 2.04k | } |
141 | 1.01k | if (!context) |
142 | 1.00k | kh_destroy(model, model_map); |
143 | 1.01k | return (ccv_cnnp_model_t*)sequential_model; |
144 | 1.01k | } |
145 | | |
146 | | ccv_cnnp_model_t* ccv_cnnp_sequential_new(ccv_cnnp_model_t* const* const models, const int model_size, const int is_trainable, const char* const name) |
147 | 97 | { |
148 | 97 | assert(model_size > 0); |
149 | 97 | ccv_cnnp_sequential_model_t* const sequential_model = (ccv_cnnp_sequential_model_t*)cccalloc(1, sizeof(ccv_cnnp_sequential_model_t) + sizeof(ccv_cnnp_model_t*) * (model_size - 1) + sizeof(ccv_nnc_tensor_symbol_t)); |
150 | 97 | sequential_model->super.isa = &ccv_cnnp_sequential_model_isa; |
151 | 97 | sequential_model->super.input_size = models[0]->input_size; |
152 | 97 | sequential_model->super.outputs = (ccv_nnc_tensor_symbol_t*)(sequential_model->sequence + model_size); |
153 | 97 | sequential_model->super.output_size = 1; |
154 | 97 | sequential_model->super.is_trainable = is_trainable; |
155 | 97 | ccv_cnnp_model_copy_name(&sequential_model->super, name); |
156 | 97 | sequential_model->sequence_size = model_size; |
157 | 97 | memcpy(sequential_model->sequence, models, sizeof(ccv_cnnp_model_t*) * model_size); |
158 | 97 | return (ccv_cnnp_model_t*)sequential_model; |
159 | 97 | } |
160 | | |
161 | | typedef struct { |
162 | | ccv_cnnp_model_t super; |
163 | | // The model's outputs, it is different from super.output_size, as latter is for actual tensor symbols. |
164 | | int model_output_size; |
165 | | // The name is similar to sequential model, but it is just topological sorted models. |
166 | | int sequence_size; |
167 | | int* model_outputs; // Which model, as in sequences, have some outputs. |
168 | | ccv_cnnp_model_io_t sequence[1]; |
169 | | } ccv_cnnp_functional_model_t; |
170 | | |
171 | | static void _ccv_cnnp_functional_model_deinit(ccv_cnnp_model_t* const super) |
172 | 101 | { |
173 | 101 | ccv_cnnp_functional_model_t* const self = (ccv_cnnp_functional_model_t*)super; |
174 | 101 | int i, j = 0, k; |
175 | 845 | for (i = 0; i < self->sequence_size; i++744 ) |
176 | 744 | { |
177 | 744 | ccv_cnnp_model_t* const model = self->sequence[i]->model; |
178 | 744 | if (!model || model->deinit_state735 ) |
179 | 9 | continue; |
180 | 735 | self->sequence[j++] = (ccv_cnnp_model_io_t)model; |
181 | | // Go through all their IO to remove itself as model. |
182 | 735 | assert(model->io); |
183 | 1.51k | for (k = 0; 735 k < model->io->rnum; k++776 ) |
184 | 776 | { |
185 | 776 | ccv_cnnp_model_io_t model_io = *(ccv_cnnp_model_io_t*)ccv_array_get(model->io, k); |
186 | 776 | model_io->model = 0; |
187 | 776 | } |
188 | 735 | } |
189 | 836 | for (i = 0; 101 i < j; i++735 ) |
190 | 735 | ccv_cnnp_model_deinit((ccv_cnnp_model_t*)self->sequence[i]); |
191 | 101 | self->sequence_size = j; |
192 | 101 | } |
193 | | |
194 | | static void _ccv_cnnp_functional_model_dealloc(ccv_cnnp_model_t* const super) |
195 | 101 | { |
196 | 101 | ccv_cnnp_functional_model_t* const self = (ccv_cnnp_functional_model_t*)super; |
197 | 101 | int i; |
198 | 836 | for (i = 0; i < self->sequence_size; i++735 ) |
199 | 735 | ccv_cnnp_model_free((ccv_cnnp_model_t*)self->sequence[i]); |
200 | 101 | } |
201 | | |
202 | | KHASH_MAP_INIT_INT64(io_node, ccv_array_t*) |
203 | | |
204 | | typedef struct { |
205 | | ccv_array_t* nodes; |
206 | | ccv_nnc_graph_exec_symbol_new_hook_f previous_func; |
207 | | void* previous_context; |
208 | | } ccv_functional_model_build_node_hook_t; |
209 | | |
210 | | static void _ccv_cnnp_functional_model_build_node_new(void* context, const ccv_nnc_graph_exec_symbol_t symbol, const ccv_nnc_cmd_t cmd, const ccv_nnc_tensor_symbol_t* const inputs, const int input_size, const ccv_nnc_tensor_symbol_t* const outputs, const int output_size, const char* const name) |
211 | 5 | { |
212 | 5 | ccv_functional_model_build_node_hook_t* const hook = (ccv_functional_model_build_node_hook_t*)context; |
213 | 5 | ccv_array_push(hook->nodes, &symbol); |
214 | 5 | if (hook->previous_func) |
215 | 5 | hook->previous_func(hook->previous_context, symbol, cmd, inputs, input_size, outputs, output_size, name); |
216 | 5 | } |
217 | | |
218 | | static void _ccv_cnnp_functional_model_build(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_nnc_tensor_symbol_t* const inputs, const int input_size, ccv_nnc_tensor_symbol_t* const outputs, const int output_size) |
219 | 99 | { |
220 | 99 | ccv_cnnp_functional_model_t* const self = (ccv_cnnp_functional_model_t*)super; |
221 | 99 | PRINT(CCV_CLI_VERBOSE, "[cnnp_functional_model_build] 1. %p, input_size: %d, output_size: %d\n", self, input_size, output_size); |
222 | 99 | assert(self->super.input_size == input_size); |
223 | 99 | assert(self->super.output_size == output_size); |
224 | 99 | int i, j, k; |
225 | 259 | for (i = 0; i < self->super.input_size; i++160 ) |
226 | 160 | self->sequence[i]->outputs[0] = self->sequence[i]->model->outputs[0] = inputs[i]; // Assigning the output symbol of input layer to be the input symbol. |
227 | 99 | ccv_array_t* input_symbols = ccv_array_new(sizeof(ccv_nnc_tensor_symbol_t), 1, 0); |
228 | 99 | ccv_array_t* parameter_indices = 0; |
229 | 99 | khash_t(io_node)* io_node_map = kh_init(io_node); |
230 | 675 | for (i = self->super.input_size; i < self->sequence_size; i++576 ) |
231 | 576 | { |
232 | 576 | ccv_cnnp_model_t* const sub_model = self->sequence[i]->model; |
233 | 576 | ccv_array_clear(input_symbols); |
234 | 576 | const ccv_array_t* const incomings = self->sequence[i]->incomings; |
235 | 576 | if (incomings) |
236 | 1.29k | for (j = 0; 573 j < incomings->rnum; j++725 ) |
237 | 725 | { |
238 | 725 | const ccv_cnnp_model_io_t input = *(ccv_cnnp_model_io_t*)ccv_array_get(incomings, j); |
239 | 725 | if (CCV_CNNP_IS_MODEL_PARAMETER(input)) |
240 | 2 | { |
241 | 2 | if (!parameter_indices) |
242 | 2 | parameter_indices = ccv_array_new(sizeof(int), 0, 0); |
243 | 0 | else |
244 | 0 | ccv_array_clear(parameter_indices); |
245 | 2 | const int param_sel = input->param_sel > 0 ? input->param_sel - 1 : input->param_sel0 ; |
246 | 2 | assert(input->param_sel != 0); |
247 | 2 | ccv_cnnp_model_add_to_parameter_indices(input->model, param_sel, parameter_indices); |
248 | 2 | assert(parameter_indices->rnum > 0); |
249 | 2 | const int param_ref = input->param_ref > 0 ? input->param_ref - 1 : input->param_ref0 ; |
250 | 2 | assert(input->param_ref != 0); |
251 | 2 | if (param_ref >= 0) |
252 | 2 | { |
253 | 2 | assert(param_ref < parameter_indices->rnum); |
254 | 2 | const ccv_nnc_tensor_symbol_t parameter = ccv_cnnp_parameter_from_indice(super, *(int*)ccv_array_get(parameter_indices, param_ref)); |
255 | 2 | ccv_array_push(input_symbols, ¶meter); |
256 | 2 | } else // Otherwise, all of them. |
257 | 0 | for (k = 0; k < parameter_indices->rnum; k++) |
258 | 0 | { |
259 | 0 | const ccv_nnc_tensor_symbol_t parameter = ccv_cnnp_parameter_from_indice(super, *(int*)ccv_array_get(parameter_indices, k)); |
260 | 0 | ccv_array_push(input_symbols, ¶meter); |
261 | 0 | } |
262 | 723 | } else { |
263 | 1.45k | for (k = 0; k < input->model->output_size; k++731 ) |
264 | 731 | ccv_array_push(input_symbols, &input->outputs[k]); |
265 | 723 | } |
266 | 725 | } |
267 | | // Go through each sub model to build the graph. |
268 | 576 | ccv_array_t* nodes; |
269 | 576 | ccv_functional_model_build_node_hook_t hook; |
270 | 576 | const ccv_array_t* const dependencies = self->sequence[i]->dependencies; |
271 | 576 | if ((dependencies && dependencies->rnum > 02 ) || self->sequence[i]->dependents > 0574 ) |
272 | 5 | { |
273 | 5 | int ret; |
274 | 5 | khiter_t k = kh_put(io_node, io_node_map, (uint64_t)(uintptr_t)self->sequence[i], &ret); |
275 | 5 | if (ret != 0) |
276 | 5 | nodes = kh_val(io_node_map, k) = ccv_array_new(sizeof(ccv_nnc_graph_exec_symbol_t), 1, 0); |
277 | 0 | else |
278 | 0 | nodes = kh_val(io_node_map, k); |
279 | 5 | hook.nodes = nodes; |
280 | 5 | hook.previous_context = ccv_nnc_graph_exec_symbol_new_hook(graph, _ccv_cnnp_functional_model_build_node_new, &hook, &hook.previous_func); |
281 | 5 | } |
282 | 576 | sub_model->data = self->super.data; |
283 | 576 | ccv_cnnp_model_build(sub_model, graph, (ccv_nnc_tensor_symbol_t*)ccv_array_get(input_symbols, 0), input_symbols->rnum, self->sequence[i]->outputs, sub_model->output_size); |
284 | 576 | if ((dependencies && dependencies->rnum > 02 ) || self->sequence[i]->dependents > 0574 ) |
285 | 5 | { |
286 | 5 | ccv_nnc_graph_exec_symbol_new_hook(graph, hook.previous_func, hook.previous_context, 0); |
287 | 5 | if (dependencies) |
288 | 5 | for (j = 0; 2 j < dependencies->rnum; j++3 ) |
289 | 3 | { |
290 | 3 | const ccv_cnnp_model_io_t dependency = *(ccv_cnnp_model_io_t*)ccv_array_get(dependencies, j); |
291 | 3 | khiter_t k = kh_get(io_node, io_node_map, (uint64_t)(uintptr_t)dependency); |
292 | 3 | if (k == kh_end(io_node_map)) |
293 | 0 | continue; |
294 | 3 | const ccv_array_t* const dependency_nodes = kh_val(io_node_map, k); |
295 | 3 | int x, y; |
296 | 6 | for (y = 0; y < dependency_nodes->rnum; y++3 ) |
297 | 6 | for (x = 0; 3 x < nodes->rnum; x++3 ) |
298 | 3 | ccv_nnc_graph_exec_symbol_concat(graph, *(ccv_nnc_graph_exec_symbol_t*)ccv_array_get(dependency_nodes, y), *(ccv_nnc_graph_exec_symbol_t*)ccv_array_get(nodes, x)); |
299 | 3 | } |
300 | 5 | } |
301 | 576 | sub_model->data = 0; |
302 | 576 | } |
303 | 99 | khiter_t it; |
304 | 107 | for (it = kh_begin99 (io_node_map); it != kh_end(io_node_map); ++it8 ) |
305 | 8 | { |
306 | 8 | if (!kh_exist(io_node_map, it)) |
307 | 3 | continue; |
308 | 5 | ccv_array_t* const nodes = kh_val(io_node_map, it); |
309 | 5 | ccv_array_free(nodes); |
310 | 5 | } |
311 | 99 | kh_destroy(io_node, io_node_map); |
312 | 99 | ccv_array_free(input_symbols); |
313 | 99 | if (parameter_indices) |
314 | 2 | ccv_array_free(parameter_indices); |
315 | 212 | for (i = 0, k = 0; k < self->model_output_size; k++113 ) |
316 | 113 | { |
317 | 113 | ccv_cnnp_model_t* const sub_model = self->sequence[self->model_outputs[k]]->model; |
318 | 227 | for (j = 0; j < sub_model->output_size; j++114 ) |
319 | 114 | outputs[i + j] = self->sequence[self->model_outputs[k]]->outputs[j]; |
320 | 113 | i += sub_model->output_size; |
321 | 113 | } |
322 | 99 | assert(i == output_size); |
323 | 99 | PRINT(CCV_CLI_VERBOSE, "[cnnp_functional_model_build] 2. %p\n", self); |
324 | 99 | } |
325 | | |
326 | | static void _ccv_cnnp_functional_model_init_states(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_cnnp_state_initializer_f initializer, void* const context) |
327 | 45 | { |
328 | 45 | ccv_cnnp_functional_model_t* const self = (ccv_cnnp_functional_model_t*)super; |
329 | 45 | int i; |
330 | 355 | for (i = self->super.input_size; i < self->sequence_size; i++310 ) |
331 | 310 | ccv_cnnp_model_init_states(self->sequence[i]->model, graph, initializer, context); |
332 | 45 | } |
333 | | |
334 | | static void _ccv_cnnp_functional_model_set_is_test(ccv_cnnp_model_t* const super, const int is_test, const ccv_cnnp_cmd_updater_f updater, void* const context) |
335 | 110 | { |
336 | 110 | ccv_cnnp_functional_model_t* const self = (ccv_cnnp_functional_model_t*)super; |
337 | 110 | int i; |
338 | 787 | for (i = self->super.input_size; i < self->sequence_size; i++677 ) |
339 | 677 | ccv_cnnp_model_set_is_test(self->sequence[i]->model, is_test, updater, context); |
340 | 110 | } |
341 | | |
342 | | static void _ccv_cnnp_functional_model_add_to_parameter_indices(ccv_cnnp_model_t* const super, const int index, ccv_array_t* const parameter_indices) |
343 | 928 | { |
344 | 928 | ccv_cnnp_functional_model_t* const self = (ccv_cnnp_functional_model_t*)super; |
345 | 928 | int i; |
346 | 4.59k | for (i = self->super.input_size; i < self->sequence_size; i++3.66k ) |
347 | 3.66k | ccv_cnnp_model_add_to_parameter_indices(self->sequence[i]->model, index, parameter_indices); |
348 | 928 | } |
349 | | |
350 | | static void _ccv_cnnp_functional_model_notify(const ccv_cnnp_model_t* const super, const int tag, void* const payload) |
351 | 1 | { |
352 | 1 | ccv_cnnp_functional_model_t* const self = (ccv_cnnp_functional_model_t*)super; |
353 | 1 | int i; |
354 | 14 | for (i = 0; i < self->sequence_size; i++13 ) |
355 | 13 | { |
356 | 13 | const ccv_cnnp_model_t* const model = self->sequence[i]->model; |
357 | 13 | ccv_cnnp_model_notify(model, tag, payload); |
358 | 13 | } |
359 | 1 | } |
360 | | |
361 | | static ccv_cnnp_model_t* _ccv_cnnp_functional_model_copy(const ccv_cnnp_model_t* const super, void* const context); |
362 | | |
363 | | static const ccv_cnnp_model_vtab_t ccv_cnnp_functional_model_isa = { |
364 | | .deinit = _ccv_cnnp_functional_model_deinit, |
365 | | .dealloc = _ccv_cnnp_functional_model_dealloc, |
366 | | .build = _ccv_cnnp_functional_model_build, |
367 | | .init_states = _ccv_cnnp_functional_model_init_states, |
368 | | .copy = _ccv_cnnp_functional_model_copy, |
369 | | .set_is_test = _ccv_cnnp_functional_model_set_is_test, |
370 | | .add_to_parameter_indices = _ccv_cnnp_functional_model_add_to_parameter_indices, |
371 | | .notify = _ccv_cnnp_functional_model_notify, |
372 | | }; |
373 | | |
374 | | KHASH_MAP_INIT_INT64(model_io, ccv_cnnp_model_io_t) |
375 | | |
376 | | static ccv_cnnp_model_t* _ccv_cnnp_functional_model_copy(const ccv_cnnp_model_t* const super, void* const context) |
377 | 8 | { |
378 | 8 | const ccv_cnnp_functional_model_t* const self = (const ccv_cnnp_functional_model_t*)super; |
379 | 8 | ccv_cnnp_functional_model_t* const functional_model = (ccv_cnnp_functional_model_t*)cccalloc(1, sizeof(ccv_cnnp_functional_model_t) + sizeof(ccv_cnnp_model_t*) * (self->sequence_size - 1) + sizeof(ccv_nnc_tensor_symbol_t) * self->super.output_size + sizeof(int) * self->model_output_size); |
380 | 8 | functional_model->super.isa = &ccv_cnnp_functional_model_isa; |
381 | 8 | functional_model->super.outputs = (ccv_nnc_tensor_symbol_t*)(functional_model->sequence + self->sequence_size); |
382 | 8 | functional_model->super.output_size = self->super.output_size; |
383 | 8 | functional_model->super.input_size = self->super.input_size; |
384 | 8 | ccv_cnnp_model_copy_name(&functional_model->super, self->super.name); |
385 | 8 | functional_model->sequence_size = self->sequence_size; |
386 | 8 | functional_model->model_output_size = self->model_output_size; |
387 | 8 | functional_model->model_outputs = (int*)(functional_model->super.outputs + functional_model->super.output_size); |
388 | 8 | memcpy(functional_model->model_outputs, self->model_outputs, sizeof(int) * self->model_output_size); |
389 | | // Now the difficult part, copy over the model_io. |
390 | 8 | khash_t(model_io)* model_io_map = kh_init(model_io); |
391 | 8 | khash_t(model)* model_map = context ? (khash_t(model)*)context3 : kh_init5 (model); |
392 | 8 | int i, j; |
393 | 57 | for (i = 0; i < self->sequence_size; i++49 ) |
394 | 49 | { |
395 | 49 | const ccv_cnnp_model_t* const sub_model = self->sequence[i]->model; |
396 | 49 | int ret; |
397 | 49 | khiter_t k = kh_put(model, model_map, (uint64_t)(uintptr_t)sub_model, &ret); |
398 | 49 | ccv_cnnp_model_t* model_copy; |
399 | 49 | if (ret != 0) |
400 | 49 | model_copy = kh_val(model_map, k) = _ccv_cnnp_model_copy(sub_model, model_map); |
401 | 0 | else |
402 | 0 | model_copy = kh_val(model_map, k); |
403 | 49 | ccv_cnnp_model_io_t model_io = functional_model->sequence[i] = ccmalloc(sizeof(struct ccv_cnnp_model_io_s) + sizeof(ccv_nnc_tensor_symbol_t) * sub_model->output_size); |
404 | 49 | model_io->param_ref = 0; |
405 | 49 | model_io->param_sel = 0; |
406 | 49 | model_io->visit = 0; |
407 | 49 | model_io->model = model_copy; |
408 | 49 | model_io->dependencies = 0; |
409 | 49 | model_io->dependents = 0; |
410 | 49 | model_io->incomings = 0; |
411 | 49 | model_io->outgoings = 0; |
412 | 49 | model_io->outputs = (ccv_nnc_tensor_symbol_t*)(model_io + 1); |
413 | 49 | if (!model_copy->io) |
414 | 49 | model_copy->io = ccv_array_new(sizeof(ccv_cnnp_model_io_t), 1, 0); |
415 | 49 | ccv_array_push(model_copy->io, &model_io); |
416 | 49 | k = kh_put(model_io, model_io_map, (uint64_t)(uintptr_t)self->sequence[i], &ret); |
417 | 49 | kh_val(model_io_map, k) = functional_model->sequence[i]; |
418 | 49 | } |
419 | 45 | for (i = self->super.input_size; i < self->sequence_size; i++37 ) |
420 | 37 | { |
421 | 37 | if (self->sequence[i]->incomings) |
422 | 87 | for (j = 0; 37 j < self->sequence[i]->incomings->rnum; j++50 ) |
423 | 50 | { |
424 | 50 | const ccv_cnnp_model_io_t input = *(ccv_cnnp_model_io_t*)ccv_array_get(self->sequence[i]->incomings, j); |
425 | 50 | if (CCV_CNNP_IS_MODEL_PARAMETER(input)) // I am pretty sure this is not in the model_io_map. |
426 | 1 | { |
427 | 1 | int ret; |
428 | 1 | khiter_t k = kh_put(model_io, model_io_map, (uint64_t)(uintptr_t)input, &ret); |
429 | 1 | if (ret != 0) |
430 | 1 | { |
431 | | // The model may not exist on the map due to wrapping (it is inside another sequential or functional model). |
432 | 1 | khiter_t m = kh_get(model, model_map, (uint64_t)(uintptr_t)input->model); |
433 | 1 | assert(m != kh_end(model_map)); |
434 | 1 | ccv_cnnp_model_t* const model_copy = kh_val(model_map, m); |
435 | 1 | ccv_cnnp_model_io_t model_io = ccmalloc(sizeof(struct ccv_cnnp_model_io_s)); |
436 | 1 | model_io->param_ref = input->param_ref; |
437 | 1 | model_io->param_sel = input->param_sel; |
438 | 1 | model_io->visit = 0; |
439 | 1 | model_io->model = model_copy; |
440 | 1 | model_io->incomings = 0; |
441 | 1 | model_io->dependencies = 0; |
442 | 1 | model_io->dependents = 0; |
443 | 1 | model_io->outgoings = 0; |
444 | 1 | model_io->outputs = 0; |
445 | 1 | if (!model_copy->io) |
446 | 1 | model_copy->io = ccv_array_new(sizeof(ccv_cnnp_model_io_t), 1, 0); |
447 | 1 | ccv_array_push(model_copy->io, &model_io); |
448 | 1 | kh_val(model_io_map, k) = model_io; |
449 | 1 | if (input->outgoings) |
450 | 1 | { |
451 | 1 | model_io->outgoings = ccv_array_new(sizeof(ccv_cnnp_model_io_t), input->outgoings->rnum, 0); |
452 | 1 | int x; |
453 | 2 | for (x = 0; x < input->outgoings->rnum; x++1 ) |
454 | 1 | { |
455 | 1 | khiter_t k = kh_get(model_io, model_io_map, (uint64_t)(uintptr_t)(*(ccv_cnnp_model_io_t*)ccv_array_get(input->outgoings, x))); |
456 | 1 | assert(k != kh_end(model_io_map)); |
457 | 1 | ccv_cnnp_model_io_t outgoing_io = kh_val(model_io_map, k); |
458 | 1 | ccv_array_push(model_io->outgoings, &outgoing_io); |
459 | 1 | } |
460 | 1 | } |
461 | 1 | } |
462 | 1 | } |
463 | 50 | } |
464 | 37 | } |
465 | 8 | if (!context) |
466 | 5 | kh_destroy(model, model_map); |
467 | 57 | for (i = 0; i < self->sequence_size; i++49 ) |
468 | 49 | { |
469 | 49 | const ccv_cnnp_model_io_t model_io = self->sequence[i]; |
470 | 49 | ccv_cnnp_model_io_t model_io_copy = functional_model->sequence[i]; |
471 | 49 | model_io_copy->param_ref = model_io->param_ref; |
472 | 49 | model_io_copy->param_sel = model_io->param_sel; |
473 | 49 | if (model_io->incomings) |
474 | 37 | { |
475 | 37 | model_io_copy->incomings = ccv_array_new(sizeof(ccv_cnnp_model_io_t), model_io->incomings->rnum, 0); |
476 | 87 | for (j = 0; j < model_io->incomings->rnum; j++50 ) |
477 | 50 | { |
478 | 50 | khiter_t k = kh_get(model_io, model_io_map, (uint64_t)(uintptr_t)(*(ccv_cnnp_model_io_t*)ccv_array_get(model_io->incomings, j))); |
479 | 50 | assert(k != kh_end(model_io_map)); |
480 | 50 | ccv_cnnp_model_io_t input_io = kh_val(model_io_map, k); |
481 | 50 | ccv_array_push(model_io_copy->incomings, &input_io); |
482 | 50 | } |
483 | 37 | } |
484 | 49 | if (model_io->dependencies) |
485 | 0 | { |
486 | 0 | model_io_copy->dependencies = ccv_array_new(sizeof(ccv_cnnp_model_io_t), model_io->dependencies->rnum, 0); |
487 | 0 | for (j = 0; j < model_io->dependencies->rnum; j++) |
488 | 0 | { |
489 | 0 | khiter_t k = kh_get(model_io, model_io_map, (uint64_t)(uintptr_t)(*(ccv_cnnp_model_io_t*)ccv_array_get(model_io->dependencies, j))); |
490 | 0 | assert(k != kh_end(model_io_map)); |
491 | 0 | ccv_cnnp_model_io_t input_io = kh_val(model_io_map, k); |
492 | 0 | ccv_array_push(model_io_copy->dependencies, &input_io); |
493 | 0 | } |
494 | 0 | } |
495 | 49 | model_io_copy->dependents = model_io->dependents; |
496 | 49 | if (model_io->outgoings) |
497 | 41 | { |
498 | 41 | model_io_copy->outgoings = ccv_array_new(sizeof(ccv_cnnp_model_io_t), model_io->outgoings->rnum, 0); |
499 | 90 | for (j = 0; j < model_io->outgoings->rnum; j++49 ) |
500 | 49 | { |
501 | 49 | khiter_t k = kh_get(model_io, model_io_map, (uint64_t)(uintptr_t)(*(ccv_cnnp_model_io_t*)ccv_array_get(model_io->outgoings, j))); |
502 | 49 | assert(k != kh_end(model_io_map)); |
503 | 49 | ccv_cnnp_model_io_t outgoing_io = kh_val(model_io_map, k); |
504 | 49 | ccv_array_push(model_io_copy->outgoings, &outgoing_io); |
505 | 49 | } |
506 | 41 | } |
507 | 49 | } |
508 | 8 | kh_destroy(model_io, model_io_map); |
509 | 8 | return (ccv_cnnp_model_t*)functional_model; |
510 | 8 | } |
511 | | |
512 | | ccv_cnnp_model_t* ccv_cnnp_model_new(const ccv_cnnp_model_io_t* const inputs, const int input_size, const ccv_cnnp_model_io_t* const outputs, const int output_size, const int is_trainable, const char* const name) |
513 | 93 | { |
514 | 93 | assert(output_size > 0); |
515 | | // Do topological sort. |
516 | 93 | ccv_array_t* const reverse_top = ccv_array_new(sizeof(ccv_cnnp_model_io_t), output_size, 0); |
517 | 93 | int i, j, k; |
518 | | // Go through output one by one, reverse traversal them, to detect potential overlap (overlap means, for example, |
519 | | // outputs[1] is an incoming node for outputs[0]. Thus, if we reverse them, we may have outputs[0] build before outputs[1], |
520 | | // hence, having issues. |
521 | 200 | for (i = 0; i < output_size; i++107 ) |
522 | 107 | outputs[i]->visit = 2; |
523 | 200 | for (i = output_size - 1; i >= 0; i--107 ) |
524 | 107 | { |
525 | 107 | if (outputs[i]->visit == 3) // If we need to remove it, no need to visit. |
526 | 5 | continue; |
527 | 107 | assert(outputs[i]->visit == 2)102 ; |
528 | 102 | ccv_array_clear(reverse_top); |
529 | 102 | ccv_array_push(reverse_top, &outputs[i]); |
530 | 686 | for (j = 0; j < reverse_top->rnum; j++584 ) |
531 | 584 | { |
532 | 584 | const ccv_cnnp_model_io_t output = *(ccv_cnnp_model_io_t*)ccv_array_get(reverse_top, j); |
533 | 584 | assert(!CCV_CNNP_IS_MODEL_INPUT(output->model)); |
534 | | // If it is input, push it here. |
535 | 584 | if (output->incomings && !581 CCV_CNNP_IS_MODEL_PARAMETER581 (output)) |
536 | 1.30k | for (k = 0; 581 k < output->incomings->rnum; k++722 ) |
537 | 722 | { |
538 | 722 | const ccv_cnnp_model_io_t input = *(ccv_cnnp_model_io_t*)ccv_array_get(output->incomings, k); |
539 | | // If it is an input or parameter, skip. |
540 | 722 | if (CCV_CNNP_IS_MODEL_INPUT(input->model) || CCV_CNNP_IS_MODEL_PARAMETER529 (input)) |
541 | 194 | continue; |
542 | 528 | if (input->visit == 1 || input->visit == 3480 ) // Visited, skip. |
543 | 48 | continue; |
544 | | // If this is an output, we need to remove it from the output array. Otherwise mark it as visited. |
545 | 480 | input->visit = input->visit == 2 ? 35 : 1475 ; |
546 | 480 | ccv_array_push(reverse_top, &input); |
547 | 480 | } |
548 | | // Similar for dependencies. |
549 | 584 | if (output->dependencies && !2 CCV_CNNP_IS_MODEL_PARAMETER2 (output)) |
550 | 5 | for (k = 0; 2 k < output->dependencies->rnum; k++3 ) |
551 | 3 | { |
552 | 3 | const ccv_cnnp_model_io_t dependency = *(ccv_cnnp_model_io_t*)ccv_array_get(output->dependencies, k); |
553 | | // If it is an input or parameter, skip. |
554 | 3 | if (CCV_CNNP_IS_MODEL_INPUT(dependency->model) || CCV_CNNP_IS_MODEL_PARAMETER(dependency)) |
555 | 0 | continue; |
556 | 3 | if (dependency->visit == 1 || dependency->visit == 3) // Visited, skip. |
557 | 1 | continue; |
558 | | // If this is an output, we need to remove it from the output array. Otherwise mark it as visited. |
559 | 2 | dependency->visit = dependency->visit == 2 ? 30 : 1; |
560 | 2 | ccv_array_push(reverse_top, &dependency); |
561 | 2 | } |
562 | 584 | } |
563 | 584 | for (j = 1; 102 j < reverse_top->rnum; j++482 ) |
564 | 482 | { |
565 | 482 | const ccv_cnnp_model_io_t output = *(ccv_cnnp_model_io_t*)ccv_array_get(reverse_top, j); |
566 | 482 | if (output->visit == 1) // Clean the visit back. |
567 | 477 | output->visit = 0; |
568 | 482 | } |
569 | 102 | } |
570 | 93 | ccv_array_clear(reverse_top); |
571 | 200 | for (i = 0; i < output_size; i++107 ) // We will assign sequence in reverse order, thus, reverse the reverse top when copying the outputs. |
572 | 107 | { |
573 | 107 | if (outputs[output_size - 1 - i]->visit == 2) |
574 | 102 | ccv_array_push(reverse_top, &outputs[output_size - 1 - i]); |
575 | 107 | assert(outputs[output_size - 1 - i]->visit == 2 || outputs[output_size - 1 - i]->visit == 3); |
576 | 107 | outputs[output_size - 1 - i]->visit = 0; // Clean up all visits. |
577 | 107 | } |
578 | | // Go from the output, until we meet inputs. |
579 | 93 | uint64_t input_bitmask[((input_size - 1) >> 6) + 1]; |
580 | 93 | memset(input_bitmask, 0, sizeof(uint64_t) * (((input_size - 1) >> 6) + 1)); |
581 | 93 | int tensor_output_size = 0; // io can be mapped to multiple tensor outputs, therefore, need to compute the exact tensor output size. |
582 | 200 | for (i = 0; i < output_size; i++107 ) |
583 | 107 | tensor_output_size += outputs[i]->model->output_size; |
584 | 638 | for (i = 0; i < reverse_top->rnum; i++545 ) |
585 | 545 | { |
586 | 545 | const ccv_cnnp_model_io_t output = *(ccv_cnnp_model_io_t*)ccv_array_get(reverse_top, i); |
587 | 545 | assert(!CCV_CNNP_IS_MODEL_INPUT(output->model)); |
588 | | // If it is input, push it here. |
589 | 545 | if (output->incomings && !542 CCV_CNNP_IS_MODEL_PARAMETER542 (output)) |
590 | 1.22k | for (j = 0; 542 j < output->incomings->rnum; j++680 ) |
591 | 680 | { |
592 | 680 | const ccv_cnnp_model_io_t input = *(ccv_cnnp_model_io_t*)ccv_array_get(output->incomings, j); |
593 | 680 | ++input->visit; // Mark it as visited. |
594 | 680 | if (input->visit != input->outgoings->rnum + input->dependents) // Not all dependencies visited. |
595 | 89 | continue; |
596 | 591 | if (!CCV_CNNP_IS_MODEL_INPUT(input->model) && !441 CCV_CNNP_IS_MODEL_PARAMETER441 (input)) |
597 | 440 | ccv_array_push(reverse_top, &input); |
598 | 151 | else if (CCV_CNNP_IS_MODEL_INPUT(input->model)) { |
599 | 226 | for (k = 0; k < input_size; k++76 ) |
600 | 226 | if (input == inputs[k]) |
601 | 150 | break; |
602 | 150 | assert(k < input_size); |
603 | 150 | input_bitmask[k >> 6] |= ((uint64_t)1 << (k & 63)); |
604 | 150 | } |
605 | 591 | } |
606 | 545 | if (output->dependencies && !2 CCV_CNNP_IS_MODEL_PARAMETER2 (output)) |
607 | 5 | for (j = 0; 2 j < output->dependencies->rnum; j++3 ) |
608 | 3 | { |
609 | 3 | const ccv_cnnp_model_io_t dependency = *(ccv_cnnp_model_io_t*)ccv_array_get(output->dependencies, j); |
610 | 3 | ++dependency->visit; // Mark it as visited. |
611 | 3 | if (dependency->visit != (dependency->outgoings ? dependency->outgoings->rnum1 : 02 ) + dependency->dependents) // Not all dependencies visited. |
612 | 0 | continue; |
613 | 3 | if (!CCV_CNNP_IS_MODEL_INPUT(dependency->model) && !CCV_CNNP_IS_MODEL_PARAMETER(dependency)) |
614 | 3 | ccv_array_push(reverse_top, &dependency); |
615 | 0 | else if (CCV_CNNP_IS_MODEL_INPUT(dependency->model)) { |
616 | 0 | for (k = 0; k < input_size; k++) |
617 | 0 | if (dependency == inputs[k]) |
618 | 0 | break; |
619 | 0 | assert(k < input_size); |
620 | 0 | input_bitmask[k >> 6] |= ((uint64_t)1 << (k & 63)); |
621 | 0 | } |
622 | 3 | } |
623 | 545 | } |
624 | 638 | for (i = 0; 93 i < reverse_top->rnum; i++545 ) |
625 | 545 | { |
626 | 545 | const ccv_cnnp_model_io_t output = *(ccv_cnnp_model_io_t*)ccv_array_get(reverse_top, i); |
627 | 545 | output->visit = 0; // Clean the visit back. |
628 | 545 | } |
629 | 243 | for (i = 0; i < input_size; i++150 ) |
630 | 150 | inputs[i]->visit = 0; // Clean the visit back. |
631 | 243 | for (i = 0; i < input_size; i++150 ) |
632 | 150 | { assert((input_bitmask[i >> 6] & ((uint64_t)1 << (i & 63)))); } // Assuming they all match. |
633 | 93 | const int sequence_size = reverse_top->rnum + input_size; |
634 | 93 | ccv_cnnp_functional_model_t* const functional_model = (ccv_cnnp_functional_model_t*)cccalloc(1, sizeof(ccv_cnnp_functional_model_t) + sizeof(ccv_cnnp_model_t*) * (sequence_size - 1) + sizeof(ccv_nnc_tensor_symbol_t) * tensor_output_size + sizeof(int) * output_size); |
635 | 93 | functional_model->super.isa = &ccv_cnnp_functional_model_isa; |
636 | 93 | functional_model->super.outputs = (ccv_nnc_tensor_symbol_t*)(functional_model->sequence + sequence_size); |
637 | 93 | functional_model->super.output_size = tensor_output_size; |
638 | 93 | functional_model->super.input_size = input_size; |
639 | 93 | functional_model->super.is_trainable = is_trainable; |
640 | 93 | functional_model->model_output_size = output_size; |
641 | 93 | functional_model->model_outputs = (int*)(functional_model->super.outputs + tensor_output_size); |
642 | 93 | ccv_cnnp_model_copy_name(&functional_model->super, name); |
643 | 93 | functional_model->sequence_size = sequence_size; |
644 | 93 | memcpy(functional_model->sequence, inputs, sizeof(ccv_cnnp_model_io_t) * input_size); |
645 | 638 | for (i = 0; i < reverse_top->rnum; i++545 ) |
646 | 545 | functional_model->sequence[input_size + i] = *(ccv_cnnp_model_io_t*)ccv_array_get(reverse_top, reverse_top->rnum - 1 - i); |
647 | 200 | for (i = 0; i < output_size; i++107 ) |
648 | 107 | { |
649 | 129 | for (j = sequence_size - 1; j >= input_size; j--22 ) |
650 | 129 | if (functional_model->sequence[j] == outputs[i]) |
651 | 107 | { |
652 | 107 | functional_model->model_outputs[i] = j; |
653 | 107 | break; |
654 | 107 | } |
655 | 107 | } |
656 | 93 | ccv_array_free(reverse_top); |
657 | 93 | return (ccv_cnnp_model_t*)functional_model; |
658 | 93 | } |
659 | | |
660 | | static ccv_cnnp_model_t* _ccv_cnnp_input_copy(const ccv_cnnp_model_t* const self, void* const context) |
661 | 12 | { |
662 | 12 | ccv_cnnp_model_t* const input = (ccv_cnnp_model_t*)cccalloc(1, sizeof(ccv_cnnp_model_t) + sizeof(ccv_nnc_tensor_symbol_t)); |
663 | 12 | input->isa = &ccv_cnnp_input_isa; |
664 | 12 | input->outputs = (ccv_nnc_tensor_symbol_t*)(input + 1); |
665 | 12 | input->output_size = 1; |
666 | 12 | return input; |
667 | 12 | } |
668 | | |
669 | | static const ccv_cnnp_model_vtab_t ccv_cnnp_input_isa = { |
670 | | .copy = _ccv_cnnp_input_copy, |
671 | | }; |
672 | | |
673 | | ccv_cnnp_model_io_t ccv_cnnp_input(void) |
674 | 150 | { |
675 | 150 | ccv_cnnp_model_t* const input = (ccv_cnnp_model_t*)cccalloc(1, sizeof(ccv_cnnp_model_t) + sizeof(ccv_nnc_tensor_symbol_t)); |
676 | 150 | input->isa = &ccv_cnnp_input_isa; |
677 | 150 | input->io = ccv_array_new(sizeof(ccv_cnnp_model_io_t), 1, 0); |
678 | 150 | ccv_cnnp_model_io_t input_io = ccmalloc(sizeof(struct ccv_cnnp_model_io_s) + sizeof(ccv_nnc_tensor_symbol_t)); |
679 | 150 | input_io->param_ref = 0; |
680 | 150 | input_io->param_sel = 0; |
681 | 150 | input_io->visit = 0; |
682 | 150 | input_io->incomings = 0; |
683 | 150 | input_io->dependencies = 0; |
684 | 150 | input_io->dependents = 0; |
685 | 150 | input_io->outgoings = 0; |
686 | 150 | input_io->model = input; |
687 | 150 | input_io->outputs = (ccv_nnc_tensor_symbol_t*)(input_io + 1); |
688 | 150 | ccv_array_push(input->io, &input_io); |
689 | 150 | input->outputs = (ccv_nnc_tensor_symbol_t*)(input + 1); |
690 | 150 | input->output_size = 1; |
691 | 150 | return input_io; |
692 | 150 | } |
693 | | |
694 | | // MARK - Dynamic Layer |
695 | | |
696 | | typedef struct { |
697 | | ccv_cnnp_model_t super; |
698 | | ccv_cnnp_model_dynamic_f func; |
699 | | void* context; |
700 | | ccv_cnnp_model_t* model; |
701 | | } ccv_cnnp_dynamic_model_t; |
702 | | |
703 | | static void _ccv_cnnp_dynamic_model_deinit(ccv_cnnp_model_t* const super) |
704 | 4 | { |
705 | 4 | ccv_cnnp_dynamic_model_t* const self = (ccv_cnnp_dynamic_model_t*)super; |
706 | 4 | if (self->model) |
707 | 4 | ccv_cnnp_model_deinit(self->model); |
708 | 4 | } |
709 | | |
710 | | static void _ccv_cnnp_dynamic_model_dealloc(ccv_cnnp_model_t* const super) |
711 | 4 | { |
712 | 4 | ccv_cnnp_dynamic_model_t* const self = (ccv_cnnp_dynamic_model_t*)super; |
713 | 4 | if (self->model) |
714 | 4 | ccv_cnnp_model_free(self->model); |
715 | 4 | } |
716 | | |
717 | | static void _ccv_cnnp_dynamic_model_build(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_nnc_tensor_symbol_t* const inputs, const int input_size, ccv_nnc_tensor_symbol_t* const outputs, const int output_size) |
718 | 4 | { |
719 | 4 | ccv_cnnp_dynamic_model_t* const self = (ccv_cnnp_dynamic_model_t*)super; |
720 | 4 | PRINT(CCV_CLI_VERBOSE, "[cnnp_dynamic_model_build] 1. %p, func: %p\n", self, self->func); |
721 | 4 | if (!self->model) |
722 | 4 | { |
723 | 4 | ccv_nnc_tensor_param_t input_params[input_size]; |
724 | 4 | int i; |
725 | 14 | for (i = 0; i < input_size; i++10 ) |
726 | 10 | input_params[i] = ccv_nnc_tensor_symbol_params(graph, inputs[i]); |
727 | 4 | self->model = self->func(input_params, input_size, self->context); |
728 | | // Update to use the settings of the compiled model. |
729 | 4 | self->super.input_size = self->model->input_size; |
730 | 4 | self->super.outputs = self->model->outputs; |
731 | 4 | self->super.output_size = self->model->output_size; |
732 | 4 | } |
733 | 4 | self->model->data = self->super.data; |
734 | 4 | ccv_cnnp_model_build(self->model, graph, inputs, input_size, outputs, output_size); |
735 | 4 | self->model->data = 0; |
736 | 4 | PRINT(CCV_CLI_VERBOSE, "[cnnp_dynamic_model_build] 2. %p\n", self); |
737 | 4 | } |
738 | | |
739 | | static void _ccv_cnnp_dynamic_model_init_states(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_cnnp_state_initializer_f initializer, void* const context) |
740 | 3 | { |
741 | 3 | ccv_cnnp_dynamic_model_t* const self = (ccv_cnnp_dynamic_model_t*)super; |
742 | 3 | assert(self->model); |
743 | 3 | ccv_cnnp_model_init_states(self->model, graph, initializer, context); |
744 | 3 | } |
745 | | |
746 | | static void _ccv_cnnp_dynamic_model_set_is_test(ccv_cnnp_model_t* const super, const int is_test, const ccv_cnnp_cmd_updater_f updater, void* const context) |
747 | 6 | { |
748 | 6 | ccv_cnnp_dynamic_model_t* const self = (ccv_cnnp_dynamic_model_t*)super; |
749 | 6 | assert(self->model); |
750 | 6 | ccv_cnnp_model_set_is_test(self->model, is_test, updater, context); |
751 | 6 | } |
752 | | |
753 | | static ccv_cnnp_model_t* _ccv_cnnp_dynamic_model_copy(const ccv_cnnp_model_t* const super, void* const context); |
754 | | |
755 | | static void _ccv_cnnp_dynamic_model_add_to_parameter_indices(ccv_cnnp_model_t* const super, const int index, ccv_array_t* const parameter_indices) |
756 | 0 | { |
757 | 0 | ccv_cnnp_dynamic_model_t* const self = (ccv_cnnp_dynamic_model_t*)super; |
758 | 0 | assert(self->model); |
759 | 0 | ccv_cnnp_model_add_to_parameter_indices(self->model, index, parameter_indices); |
760 | 0 | } |
761 | | |
762 | | static void _ccv_cnnp_dynamic_model_notify(const ccv_cnnp_model_t* const super, const int tag, void* const payload) |
763 | 0 | { |
764 | 0 | ccv_cnnp_dynamic_model_t* const self = (ccv_cnnp_dynamic_model_t*)super; |
765 | 0 | if (self->model) |
766 | 0 | ccv_cnnp_model_notify(self->model, tag, payload); |
767 | 0 | } |
768 | | |
769 | | static const ccv_cnnp_model_vtab_t ccv_cnnp_dynamic_model_isa = { |
770 | | .deinit = _ccv_cnnp_dynamic_model_deinit, |
771 | | .dealloc = _ccv_cnnp_dynamic_model_dealloc, |
772 | | .build = _ccv_cnnp_dynamic_model_build, |
773 | | .init_states = _ccv_cnnp_dynamic_model_init_states, |
774 | | .copy = _ccv_cnnp_dynamic_model_copy, |
775 | | .set_is_test = _ccv_cnnp_dynamic_model_set_is_test, |
776 | | .add_to_parameter_indices = _ccv_cnnp_dynamic_model_add_to_parameter_indices, |
777 | | .notify = _ccv_cnnp_dynamic_model_notify, |
778 | | }; |
779 | | |
780 | | ccv_cnnp_model_t* ccv_cnnp_dynamic_new(ccv_cnnp_model_dynamic_f func, void* const context, const char* const name) |
781 | 4 | { |
782 | 4 | ccv_cnnp_dynamic_model_t* const dynamic_model = (ccv_cnnp_dynamic_model_t*)cccalloc(1, sizeof(ccv_cnnp_dynamic_model_t)); |
783 | 4 | dynamic_model->super.isa = &ccv_cnnp_dynamic_model_isa; |
784 | 4 | dynamic_model->super.is_trainable = -1; |
785 | 4 | dynamic_model->func = func; |
786 | 4 | dynamic_model->context = context; |
787 | 4 | ccv_cnnp_model_copy_name(&dynamic_model->super, name); |
788 | 4 | return (ccv_cnnp_model_t*)dynamic_model; |
789 | 4 | } |
790 | | |
791 | | static ccv_cnnp_model_t* _ccv_cnnp_dynamic_model_copy(const ccv_cnnp_model_t* const super, void* const context) |
792 | 0 | { |
793 | 0 | const ccv_cnnp_dynamic_model_t* const self = (const ccv_cnnp_dynamic_model_t*)super; |
794 | 0 | return ccv_cnnp_dynamic_new(self->func, self->context, self->super.name); |
795 | 0 | } |
796 | | |
797 | | // MARK - Command Layer |
798 | | |
799 | | typedef struct { |
800 | | ccv_cnnp_model_t super; |
801 | | ccv_nnc_cmd_t cmd; |
802 | | ccv_nnc_hint_t hint; |
803 | | ccv_nnc_tensor_symbol_t* input_symbols; // This is only valid for INIT_SHARED_TENSOR / INIT_SHARED_TENSOR_AS_TRAINABLE |
804 | | ccv_nnc_tensor_symbol_t* output_symbols; // This is just for the output symbol (in case we need to have no tensor symbol). |
805 | | ccv_cnnp_cmd_exec_io_t* inputs; |
806 | | int flags; |
807 | | int input_size; |
808 | | int* outputs; |
809 | | int output_size; |
810 | | } ccv_cnnp_model_cmd_exec_t; |
811 | | |
812 | | static void _ccv_cnnp_cmd_exec_build(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_nnc_tensor_symbol_t* const inputs, const int input_size, ccv_nnc_tensor_symbol_t* const outputs, const int output_size) |
813 | 81 | { |
814 | 81 | ccv_cnnp_model_cmd_exec_t* const self = (ccv_cnnp_model_cmd_exec_t*)super; |
815 | 81 | PRINT(CCV_CLI_VERBOSE, "[cnnp_cmd_exec_build] -\n"); |
816 | 81 | ccv_nnc_tensor_param_t input_params[ccv_max(1, self->input_size)]; |
817 | 81 | int i, j; |
818 | 243 | for (i = 0, j = 0; i < self->input_size; i++162 ) |
819 | 162 | if (self->inputs[i].type == CCV_CNNP_IO) |
820 | 131 | { |
821 | 131 | self->input_symbols[i] = inputs[j++]; |
822 | 131 | input_params[i] = ccv_nnc_tensor_symbol_params(graph, self->input_symbols[i]); |
823 | 131 | } else if (31 self->inputs[i].type == CCV_CNNP_NO_TENSOR31 ) { |
824 | 0 | self->input_symbols[i] = NO_TENSOR_SYMBOL; |
825 | 31 | } else if (!self->input_symbols[i].graph) { |
826 | | // Otherwise, we only create this symbol if it doesn't exist. |
827 | 22 | const ccv_nnc_tensor_param_t params = self->inputs[i].init_state.info; |
828 | 22 | input_params[i] = params; |
829 | 22 | self->input_symbols[i] = ccv_nnc_tensor_symbol_new(graph, params, 0); |
830 | 22 | } |
831 | | // We cannot simply mark the outputs as auto, because the subsequent build call may require this output to have params setup. |
832 | | // Infer the parameters here. |
833 | 81 | ccv_nnc_tensor_param_t output_params[ccv_max(1, self->output_size)]; |
834 | 81 | ccv_nnc_hint_tensor_auto(self->cmd, input_params, self->input_size, self->hint, output_params, self->output_size); |
835 | 162 | for (i = 0, j = 0; i < self->output_size; i++81 ) |
836 | 81 | if (self->outputs[i] == CCV_CNNP_IO) |
837 | 81 | self->output_symbols[i] = outputs[j++] = ccv_nnc_tensor_symbol_new(graph, output_params[i], 0); |
838 | 0 | else if (self->outputs[i] == CCV_CNNP_TENSOR_NOT_OUTPUT) |
839 | 0 | self->output_symbols[i] = ccv_nnc_tensor_symbol_new(graph, output_params[i], 0); |
840 | 0 | else |
841 | 0 | self->output_symbols[i] = NO_TENSOR_SYMBOL; |
842 | 81 | ccv_nnc_graph_exec_symbol_new(graph, self->cmd, self->input_symbols, self->input_size, self->output_symbols, self->output_size, 0); |
843 | 81 | } |
844 | | |
845 | | static void _ccv_cnnp_cmd_exec_init_states(ccv_cnnp_model_t* const super, ccv_nnc_symbolic_graph_t* const graph, const ccv_cnnp_state_initializer_f initializer, void* const context) |
846 | 65 | { |
847 | 65 | ccv_cnnp_model_cmd_exec_t* const self = (ccv_cnnp_model_cmd_exec_t*)super; |
848 | 65 | int i; |
849 | 195 | for (i = 0; i < self->input_size; i++130 ) |
850 | 130 | if (self->inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR || self->inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR_AS_TRAINABLE110 ) |
851 | 31 | self->inputs[i].init_state.init(self->input_symbols[i], initializer, context, self->inputs[i].init_state.context); |
852 | 65 | } |
853 | | |
854 | | static void _ccv_cnnp_cmd_exec_add_to_output(ccv_cnnp_model_t* const super, const ccv_cnnp_add_to_array_f add_to_array, void* const outputs) |
855 | 81 | { |
856 | 81 | ccv_cnnp_model_cmd_exec_t* const self = (ccv_cnnp_model_cmd_exec_t*)super; |
857 | 81 | int i; |
858 | 243 | for (i = 0; i < self->input_size; i++162 ) |
859 | 162 | if (self->inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR) |
860 | 20 | add_to_array(outputs, self->input_symbols[i], 0); // Push this as retainable because it need to be init. |
861 | 81 | } |
862 | | |
863 | | static void _ccv_cnnp_cmd_exec_add_to_parameter(ccv_cnnp_model_t* const super, const ccv_cnnp_add_to_array_f add_to_array, void* const parameters, const int is_trainable) |
864 | 81 | { |
865 | 81 | ccv_cnnp_model_cmd_exec_t* const self = (ccv_cnnp_model_cmd_exec_t*)super; |
866 | 81 | int i; |
867 | 243 | for (i = 0; i < self->input_size; i++162 ) |
868 | 162 | if (self->inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR_AS_TRAINABLE) |
869 | 11 | add_to_array(parameters, self->input_symbols[i], is_trainable); // Push this as parameter. |
870 | 81 | } |
871 | | |
872 | | static void _ccv_cnnp_cmd_exec_deinit(ccv_cnnp_model_t* const super) |
873 | 72 | { |
874 | 72 | ccv_cnnp_model_cmd_exec_t* const self = (ccv_cnnp_model_cmd_exec_t*)super; |
875 | 72 | int i, j; |
876 | 216 | for (i = 0; i < self->input_size; i++144 ) |
877 | 144 | if ((self->inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR || self->inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR_AS_TRAINABLE133 ) && |
878 | 144 | self->inputs[i].init_state.context22 ) |
879 | 22 | { |
880 | 22 | void* const context = self->inputs[i].init_state.context; |
881 | 22 | if (self->inputs[i].init_state.deinit) |
882 | 9 | self->inputs[i].init_state.deinit(context); |
883 | 22 | self->inputs[i].init_state.init = 0; |
884 | 22 | self->inputs[i].init_state.deinit = 0; |
885 | 22 | self->inputs[i].init_state.context = 0; |
886 | 22 | for (j = i + 1; j < self->input_size; j++0 ) |
887 | 0 | if (self->inputs[j].init_state.context == context) |
888 | 0 | { |
889 | 0 | self->inputs[j].init_state.init = 0; |
890 | 0 | self->inputs[j].init_state.deinit = 0; |
891 | 0 | self->inputs[j].init_state.context = 0; |
892 | 0 | } |
893 | 22 | } |
894 | 72 | } |
895 | | |
896 | | static ccv_cnnp_model_t* _ccv_cnnp_cmd_exec_copy(const ccv_cnnp_model_t* const super, void* const context); |
897 | | |
898 | | static const ccv_cnnp_model_vtab_t ccv_cnnp_cmd_exec_isa = { |
899 | | .build = _ccv_cnnp_cmd_exec_build, |
900 | | .init_states = _ccv_cnnp_cmd_exec_init_states, |
901 | | .add_to_parameter = _ccv_cnnp_cmd_exec_add_to_parameter, |
902 | | .add_to_output = _ccv_cnnp_cmd_exec_add_to_output, |
903 | | .deinit = _ccv_cnnp_cmd_exec_deinit, |
904 | | .copy = _ccv_cnnp_cmd_exec_copy, |
905 | | }; |
906 | | |
907 | | static ccv_cnnp_model_t* _ccv_cnnp_cmd_exec(const ccv_nnc_cmd_t cmd, int copy_io, const ccv_nnc_hint_t hint, const int flags, const ccv_cnnp_cmd_exec_io_t* const inputs, const int input_size, const int* const outputs, const int output_size, const int is_trainable, const char* const name) |
908 | 72 | { |
909 | 72 | assert(input_size >= 0); |
910 | 72 | assert(output_size > 0); |
911 | 72 | int i; |
912 | 72 | int io_input_size = 0; |
913 | 216 | for (i = 0; i < input_size; i++144 ) |
914 | 144 | if (inputs[i].type == CCV_CNNP_IO) |
915 | 122 | ++io_input_size; |
916 | 22 | else { |
917 | 22 | assert(inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR || inputs[i].type == CCV_CNNP_INIT_SHARED_TENSOR_AS_TRAINABLE); |
918 | 22 | assert(inputs[i].init_state.init); |
919 | 22 | } |
920 | 72 | int io_output_size = 0; |
921 | 144 | for (i = 0; i < output_size; i++72 ) |
922 | 72 | if (outputs[i] == CCV_CNNP_IO) |
923 | 72 | ++io_output_size; |
924 | 0 | else { |
925 | 0 | assert(outputs[i] == CCV_CNNP_TENSOR_NOT_OUTPUT || outputs[i] == CCV_CNNP_NO_TENSOR); |
926 | 0 | } |
927 | 72 | assert(io_output_size > 0); |
928 | 72 | ccv_cnnp_model_cmd_exec_t* const model_cmd_exec = (ccv_cnnp_model_cmd_exec_t*)cccalloc(1, sizeof(ccv_cnnp_model_cmd_exec_t) + sizeof(ccv_nnc_tensor_symbol_t) * (io_output_size + input_size + output_size) + sizeof(ccv_cnnp_cmd_exec_io_t) * input_size + sizeof(int) * output_size); |
929 | 72 | model_cmd_exec->super.isa = &ccv_cnnp_cmd_exec_isa; |
930 | 72 | model_cmd_exec->super.input_size = io_input_size; |
931 | 72 | model_cmd_exec->super.outputs = (ccv_nnc_tensor_symbol_t*)(model_cmd_exec + 1); |
932 | 72 | model_cmd_exec->super.output_size = io_output_size; |
933 | 72 | model_cmd_exec->super.is_trainable = is_trainable; |
934 | 72 | ccv_cnnp_model_copy_name(&model_cmd_exec->super, name); |
935 | 72 | model_cmd_exec->cmd = cmd; |
936 | 72 | model_cmd_exec->hint = hint; |
937 | 72 | model_cmd_exec->flags = flags; |
938 | 72 | model_cmd_exec->input_size = input_size; |
939 | 72 | model_cmd_exec->input_symbols = model_cmd_exec->super.outputs + io_output_size; |
940 | 72 | model_cmd_exec->output_symbols = model_cmd_exec->input_symbols + input_size; |
941 | 72 | model_cmd_exec->inputs = (ccv_cnnp_cmd_exec_io_t*)(model_cmd_exec->output_symbols + output_size); |
942 | 72 | if (input_size > 0) |
943 | 72 | { |
944 | 72 | memcpy(model_cmd_exec->inputs, inputs, sizeof(ccv_cnnp_cmd_exec_io_t) * input_size); |
945 | 72 | if (copy_io) |
946 | 30 | for (i = 0; 10 i < input_size; i++20 ) |
947 | 20 | if (inputs[i].type != CCV_CNNP_IO && inputs[i].init_state.copy2 ) |
948 | 1 | model_cmd_exec->inputs[i].init_state.context = inputs[i].init_state.copy(inputs[i].init_state.context); |
949 | 72 | } |
950 | 72 | model_cmd_exec->output_size = output_size; |
951 | 72 | model_cmd_exec->outputs = (int*)(model_cmd_exec->inputs + input_size); |
952 | 72 | if (output_size > 0) |
953 | 72 | memcpy(model_cmd_exec->outputs, outputs, sizeof(int) * output_size); |
954 | 72 | return (ccv_cnnp_model_t*)model_cmd_exec; |
955 | 72 | } |
956 | | |
957 | | ccv_cnnp_model_t* ccv_cnnp_cmd_exec(const ccv_nnc_cmd_t cmd, const ccv_nnc_hint_t hint, const int flags, const ccv_cnnp_cmd_exec_io_t* const inputs, const int input_size, const int* const outputs, const int output_size, const int is_trainable, const char* const name) |
958 | 62 | { |
959 | 62 | return _ccv_cnnp_cmd_exec(cmd, 0, hint, flags, inputs, input_size, outputs, output_size, is_trainable, name); |
960 | 62 | } |
961 | | |
962 | | static ccv_cnnp_model_t* _ccv_cnnp_cmd_exec_copy(const ccv_cnnp_model_t* const super, void* const context) |
963 | 10 | { |
964 | 10 | const ccv_cnnp_model_cmd_exec_t* const self = (const ccv_cnnp_model_cmd_exec_t*)super; |
965 | 10 | return _ccv_cnnp_cmd_exec(self->cmd, 1, self->hint, self->flags, self->inputs, self->input_size, self->outputs, self->output_size, self->super.is_trainable, self->super.name); |
966 | 10 | } |
967 | | |
968 | | static void _ccv_cnnp_cmd_exec_io_copy(const ccv_nnc_tensor_symbol_t tensor_symbol, const ccv_cnnp_state_initializer_f initializer, void* const initializer_context, void* const context) |
969 | 20 | { |
970 | 20 | initializer(initializer_context, CMD_DATA_TRANSFER_FORWARD(), ccv_nnc_no_hint, 0, (ccv_nnc_tensor_t*)context, tensor_symbol); |
971 | 20 | } |
972 | | |
973 | | ccv_cnnp_cmd_exec_io_init_state_t ccv_cnnp_cmd_exec_io_copy(const ccv_nnc_tensor_t* const tensor) |
974 | 12 | { |
975 | 12 | return (ccv_cnnp_cmd_exec_io_init_state_t){ |
976 | 12 | .info = tensor->info, |
977 | 12 | .context = (void *)tensor, |
978 | 12 | .init = _ccv_cnnp_cmd_exec_io_copy, |
979 | 12 | }; |
980 | 12 | } |
981 | | |
982 | | typedef struct { |
983 | | ccv_nnc_cmd_t cmd; |
984 | | ccv_nnc_hint_t hint; |
985 | | int flags; |
986 | | } ccv_cnnp_cmd_exec_io_set_by_t; |
987 | | |
988 | | static void _ccv_cnnp_cmd_exec_io_set_by(const ccv_nnc_tensor_symbol_t tensor_symbol, const ccv_cnnp_state_initializer_f initializer, void* const initializer_context, void* const context) |
989 | 11 | { |
990 | 11 | const ccv_cnnp_cmd_exec_io_set_by_t* const set_by = (ccv_cnnp_cmd_exec_io_set_by_t*)context; |
991 | 11 | initializer(initializer_context, set_by->cmd, set_by->hint, set_by->flags, 0, tensor_symbol); |
992 | 11 | } |
993 | | |
994 | | static void* _ccv_cnnp_cmd_exec_io_set_by_copy(void* const context) |
995 | 1 | { |
996 | 1 | ccv_cnnp_cmd_exec_io_set_by_t* const set_by = (ccv_cnnp_cmd_exec_io_set_by_t*)ccmalloc(sizeof(ccv_cnnp_cmd_exec_io_set_by_t)); |
997 | 1 | memcpy(set_by, context, sizeof(ccv_cnnp_cmd_exec_io_set_by_t)); |
998 | 1 | return set_by; |
999 | 1 | } |
1000 | | |
1001 | | ccv_cnnp_cmd_exec_io_init_state_t ccv_cnnp_cmd_exec_io_set_by(const ccv_nnc_cmd_t cmd, const ccv_nnc_hint_t hint, const int flags, const ccv_nnc_tensor_param_t params) |
1002 | 8 | { |
1003 | 8 | ccv_cnnp_cmd_exec_io_set_by_t* const set_by = (ccv_cnnp_cmd_exec_io_set_by_t*)ccmalloc(sizeof(ccv_cnnp_cmd_exec_io_set_by_t)); |
1004 | 8 | set_by->cmd = cmd; |
1005 | 8 | set_by->hint = hint; |
1006 | 8 | set_by->flags = flags; |
1007 | 8 | return (ccv_cnnp_cmd_exec_io_init_state_t){ |
1008 | 8 | .info = params, |
1009 | 8 | .context = set_by, |
1010 | 8 | .init = _ccv_cnnp_cmd_exec_io_set_by, |
1011 | 8 | .copy = _ccv_cnnp_cmd_exec_io_set_by_copy, |
1012 | 8 | .deinit = ccfree, |
1013 | 8 | }; |
1014 | 8 | } |