blob: 8c540d6148cff5b404fd0adf5a0d2e86d5d8f96a [file] [log] [blame]
Tim Hall79d07d22020-04-27 18:20:16 +01001/*
Raul Farkas428a8d52023-01-16 16:52:18 +00002 * SPDX-FileCopyrightText: Copyright 2020-2021, 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
Tim Hall79d07d22020-04-27 18:20:16 +01003 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Licensed under the Apache License, Version 2.0 (the License); you may
7 * not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
14 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
Tim Hall79d07d22020-04-27 18:20:16 +010018#define PY_SSIZE_T_CLEAN
19#include <Python.h>
Mauricio Briceno67e11f72021-05-05 12:47:28 +020020#include <numpy/ndarrayobject.h>
Tim Hall79d07d22020-04-27 18:20:16 +010021
22#include "mlw_decode.h"
23#include "mlw_encode.h"
24
Mauricio Briceno67e11f72021-05-05 12:47:28 +020025/* C extension wrapper for mlw_reorder_encode
26 *
27 * This method is exposed directly in python with the arguments with a
28 * prototype of the form:
29 *
30 * output = mlw_codec.reorder_encode(
31 * ifm_ublock_depth,
32 * ofm_ublock_depth,
33 * input,
34 * ofm_block_depth,
35 * is_depthwise,
36 * is_partkernel,
37 * ifm_bitdepth,
38 * decomp_h,
39 * decomp_w,
40 * verbose=0)
41 *
Fredrik Svedberg93d5c352021-05-11 13:51:47 +020042 * output: (bytearray, int)
Mauricio Briceno67e11f72021-05-05 12:47:28 +020043 */
44
45static PyObject *
46method_reorder_encode (PyObject *self, PyObject *args)
47{
48 /* Object to hold the input integer list. */
49 int ifm_ublock_depth;
50 int ofm_ublock_depth;
51 PyObject *input_object;
52 int ofm_block_depth;
53 int is_depthwise;
54 int is_partkernel;
55 int ifm_bitdepth;
56 int decomp_h;
57 int decomp_w;
58
59 /* Object to hold the input verbosity integer, the verbose argument
60 * is optional so defaulted to 0.
61 */
62 int verbose = 0;
63
64 /* Arguments to the method are delivered as a tuple, unpack the
65 * tuple to get the individual arguments, note the second is
66 * optional.
67 */
68 if (!PyArg_ParseTuple(args, "iiOiiiiii|i",
69 &ifm_ublock_depth,
70 &ofm_ublock_depth,
71 &input_object,
72 &ofm_block_depth,
73 &is_depthwise,
74 &is_partkernel,
75 &ifm_bitdepth,
76 &decomp_h,
77 &decomp_w,
78 &verbose))
79 return NULL;
80
Fredrik Svedberg93d5c352021-05-11 13:51:47 +020081 PyArrayObject* input_ndarray_object = (PyArrayObject*)PyArray_FROM_OTF(
Mauricio Briceno67e11f72021-05-05 12:47:28 +020082 input_object,
Mauricio Briceno3e4168d2021-06-09 09:49:05 +020083 NPY_INT16,
Mauricio Briceno67e11f72021-05-05 12:47:28 +020084 NPY_ARRAY_ALIGNED);
85 if (input_ndarray_object == NULL)
86 {
87 return NULL;
88 }
89
90 if ((int)PyArray_NDIM(input_ndarray_object) < 4)
91 {
92 PyErr_SetString(PyExc_ValueError, "Invalid input shape");
93 return NULL;
94 }
95
96 int ofm_depth = (int)PyArray_DIM(input_ndarray_object, 0);
97 int kernel_height = (int)PyArray_DIM(input_ndarray_object, 1);
98 int kernel_width = (int)PyArray_DIM(input_ndarray_object, 2);
99 int ifm_depth = (int)PyArray_DIM(input_ndarray_object, 3);
100
Mauricio Briceno3e4168d2021-06-09 09:49:05 +0200101 int16_t* brick_weights = (int16_t*)PyArray_DATA(input_ndarray_object);
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200102 int brick_strides[4];
103 for (int i = 0; i < 4; i++)
104 {
Mauricio Briceno3e4168d2021-06-09 09:49:05 +0200105 int stride = (int)PyArray_STRIDE(input_ndarray_object, i);
106 if (stride % sizeof(int16_t))
107 {
108 PyErr_SetString(PyExc_ValueError, "Invalid stride");
109 return NULL;
110 }
111 brick_strides[i] = stride / sizeof(int16_t);
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200112 }
Mauricio Briceno3e4168d2021-06-09 09:49:05 +0200113 if ((unsigned)PyArray_ITEMSIZE(input_ndarray_object) != sizeof(int16_t))
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200114 {
115 PyErr_SetString(PyExc_ValueError, "Invalid input type");
116 return NULL;
117 }
118 uint8_t* output_buffer = NULL;
Fredrik Svedberg93d5c352021-05-11 13:51:47 +0200119 int64_t padded_length;
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200120
121 int output_length = mlw_reorder_encode(
122 ifm_ublock_depth,
123 ofm_ublock_depth,
124 ofm_depth,
125 kernel_height,
126 kernel_width,
127 ifm_depth,
128 brick_strides,
129 brick_weights,
130 ofm_block_depth,
131 is_depthwise,
132 is_partkernel,
133 ifm_bitdepth,
134 decomp_h,
135 decomp_w,
136 &output_buffer,
137 &padded_length,
138 verbose);
139
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200140 PyObject *output_byte_array = PyByteArray_FromStringAndSize((char*)output_buffer, output_length);
141 PyObject *padded_length_obj = Py_BuildValue("i", padded_length);
142
143 /* Discard the output buffer */
144 mlw_free_outbuf(output_buffer);
145
146 PyObject* ret = PyTuple_Pack(2, output_byte_array, padded_length_obj);
Fredrik Svedberg93d5c352021-05-11 13:51:47 +0200147
148 Py_DECREF(input_ndarray_object);
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200149 Py_DECREF(output_byte_array);
150 Py_DECREF(padded_length_obj);
151 return ret;
152}
153
Tim Hall79d07d22020-04-27 18:20:16 +0100154/* C extension wrapper for mlw_encode
155 *
156 * This method is exposed directly in python with the arguments with a
157 * prototype of the form:
158 *
159 * output = mlw_codec.encode(input, verbose=0)
160 *
161 * input: [int]
162 * verbose: int
163 * output: bytearray
164 */
165
166static PyObject *
167method_encode (PyObject *self, PyObject *args)
168{
169 /* Object to hold the input integer list. */
170 PyObject *input_list_object;
171
172 /* Object to hold the input verbosity integer, the verbose argument
173 * is optional so defaulted to 0.
174 */
175 int verbose = 0;
176
177 /* Arguments to the method are delivered as a tuple, unpack the
178 * tuple to get the individual arguments, note the second is
179 * optional.
180 */
181 if (!PyArg_ParseTuple(args, "O|i", &input_list_object, &verbose))
182 return NULL;
183
184 /* Unpack the length of the input integer list. */
Louis Verhaard60232142021-01-22 14:11:15 +0100185 Py_ssize_t input_length = PyObject_Length (input_list_object);
Fredrik Svedberg0e938a32021-05-20 11:13:00 +0200186 if (input_length < 0 || input_length > INT32_MAX) {
Louis Verhaard60232142021-01-22 14:11:15 +0100187 return NULL;
188 }
Tim Hall79d07d22020-04-27 18:20:16 +0100189
190 /* We need to marshall the integer list into an input buffer
191 * suitable for mlw_encode, use a temporary heap allocated buffer
192 * for that purpose.
193 */
194 int16_t *input_buffer = (int16_t *) malloc(sizeof(int16_t *) * input_length);
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200195 uint8_t *output_buffer = NULL;
Tim Hall79d07d22020-04-27 18:20:16 +0100196 if (input_buffer == NULL)
197 return PyErr_NoMemory();
198
199 /* Unpack the input integer list into the temporary buffer.
200 */
201 for (int i = 0; i < input_length; i++)
202 {
203 PyObject *item;
204 item = PyList_GetItem(input_list_object, i);
Louis Verhaard60232142021-01-22 14:11:15 +0100205 long value = PyLong_AsLong(item);
206 if (value < -255 || value > 255) {
207 PyErr_SetString(PyExc_ValueError, "Input value out of bounds");
208 return NULL;
209 }
Fredrik Svedberg0e938a32021-05-20 11:13:00 +0200210 input_buffer[i] = (int16_t)value;
Tim Hall79d07d22020-04-27 18:20:16 +0100211 }
Louis Verhaard60232142021-01-22 14:11:15 +0100212 if (PyErr_Occurred() != NULL) {
213 PyErr_SetString(PyExc_ValueError, "Invalid input");
214 return NULL;
215 }
Tim Hall79d07d22020-04-27 18:20:16 +0100216
Fredrik Svedberg0e938a32021-05-20 11:13:00 +0200217 int output_length = mlw_encode(input_buffer, (int)input_length, &output_buffer, verbose);
Tim Hall79d07d22020-04-27 18:20:16 +0100218
219 PyObject *output_byte_array = PyByteArray_FromStringAndSize ((char *) output_buffer, output_length);
220
221 /* Discard the temporary input and output buffers. */
222 free (input_buffer);
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200223 mlw_free_outbuf(output_buffer);
Tim Hall79d07d22020-04-27 18:20:16 +0100224
225 return output_byte_array;
226}
227
228/* C extension wrapper for mlw_decode
229 *
230 * This method is exposed directly in python with the arguments with a
231 * prototype of the form:
232 *
233 * output = mlw_codec.decode(input, verbose=0)
234 *
235 * input: bytearray
236 * verbose: int
237 * output: [int]
238 */
239
240static PyObject *
241method_decode(PyObject *self, PyObject *args)
242{
243 /* Object to hold the input bytearray. */
244 PyObject *input_bytearray_object;
245
246 /* Object to hold the input verbosity integer, the verbose argument
247 * is optional so defaulted to 0.
248 */
249 int verbose = 0;
250
251 /* Arguments to the method are delivered as a tuple, unpack the
252 * tuple to get the individual arguments, note the second is
253 * optional.
254 */
255 if (!PyArg_ParseTuple(args, "Y|i", &input_bytearray_object, &verbose))
256 return NULL;
257
258 /* Unpack the input buffer and length from the bytearray object. */
259 uint8_t *input_buffer = (uint8_t *) PyByteArray_AsString(input_bytearray_object);
Louis Verhaard60232142021-01-22 14:11:15 +0100260 Py_ssize_t input_length = PyByteArray_Size(input_bytearray_object);
Fredrik Svedberg0e938a32021-05-20 11:13:00 +0200261 if (input_length < 0 || input_length > INT32_MAX) {
262 return NULL;
263 }
Tim Hall79d07d22020-04-27 18:20:16 +0100264
265 /* We don't know the output length required, we guess, but the guess
266 * will be too small, the mlw_decode call will do a resize (upwards)
267 * anyway.
268 */
Louis Verhaard60232142021-01-22 14:11:15 +0100269 int16_t *output_buffer = (int16_t *) malloc (input_length);
Tim Hall79d07d22020-04-27 18:20:16 +0100270 if (output_buffer == NULL)
271 return PyErr_NoMemory();
272
Fredrik Svedberg0e938a32021-05-20 11:13:00 +0200273 int output_length = mlw_decode (input_buffer, (int)input_length, &output_buffer, verbose);
Tim Hall79d07d22020-04-27 18:20:16 +0100274
275 /* Construct a new integer list and marshall the output buffer
276 * contents into the list. */
277 PyObject *output_list = PyList_New(output_length);
278 for (int i = 0; i <output_length; i++)
279 PyList_SetItem (output_list, i, PyLong_FromLong (output_buffer[i]));
280
281 free (output_buffer);
282
283 return output_list;
284}
285
286/* mlw_codec method descriptors.
287 */
288
289static PyMethodDef mlw_methods[] = {
290 {"decode", method_decode, METH_VARARGS, "Python interface for decode"},
291 {"encode", method_encode, METH_VARARGS, "Python interface for encode"},
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200292 {"reorder_encode", method_reorder_encode, METH_VARARGS, "Python interface for reorder and encode"},
Tim Hall79d07d22020-04-27 18:20:16 +0100293 {NULL, NULL, 0, NULL}
294};
295
296/* mlw_codec module descriptor.
297 */
298
299static struct PyModuleDef mlw_codecmodule = {
300 PyModuleDef_HEAD_INIT,
301 "mlw_codec",
302 "Python interface for the mlw encoder",
303 -1,
304 mlw_methods
305};
306
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200307PyMODINIT_FUNC PyInit_mlw_codec(void)
308{
Raul Farkas428a8d52023-01-16 16:52:18 +0000309 PyObject *ptype, *pvalue, *ptraceback;
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200310 PyObject* ret = PyModule_Create(&mlw_codecmodule);
Raul Farkas428a8d52023-01-16 16:52:18 +0000311 if (_import_array() < 0)
312 {
313 // Fetch currently set error
314 PyErr_Fetch(&ptype, &pvalue, &ptraceback);
315 // Extract the error message
316 const char *pStrErrorMessage = PyUnicode_AsUTF8(pvalue);
317 // Re-format error message to start with "mlw_codec Error: " so it is
318 // clearer it comes from mlw_codec.
319 PyErr_Format(PyExc_RuntimeError, "mlw_codec error: %s", pStrErrorMessage);
320 return NULL;
321 }
322
Mauricio Briceno67e11f72021-05-05 12:47:28 +0200323 return ret;
Tim Hall79d07d22020-04-27 18:20:16 +0100324}