matrix_ops.c

← Back to explorer
src/ matrix_ops.c
Raw
C 526 lines · UTF-8
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
// Created by AG on 22-03-2026

#include "matrix_ops.h"
#include "math_ops.h"
#include <stddef.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#ifdef SYSTEM_HAS_OPENCL
static inline int is_gpu_mode(void) {
  ComputeBackend *backend = compute_backend_global();
  return backend && backend->type == COMPUTE_BACKEND_GPU;
}

static inline OpenCLDevice *get_gpu(void) {
  return (OpenCLDevice *)compute_backend_global()->device_handle;
}
#endif

Matrix *matrix_create(int rows, int columns) {
  Matrix *matrix = (Matrix *) calloc(1, sizeof(Matrix));

  matrix->rows = rows;
  matrix->columns = columns;
  int total = rows * columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    matrix->device_buffer = opencl_device_alloc(gpu, sizeof(float) * total);
    matrix->host_data = NULL;

    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_CLEAR);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &matrix->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_CLEAR, total);
    return matrix;
  }
#endif

  matrix->host_data = (float *) calloc(total, sizeof(float));
  return matrix;
}

void matrix_destroy(Matrix *mat) {
  if (!mat) {
    return;
  }

#ifdef SYSTEM_HAS_OPENCL
  if (mat->device_buffer) {
    opencl_device_free(mat->device_buffer);
  }
#endif

  if (mat->host_data) {
    free(mat->host_data);
  }

  free(mat);
}

Matrix *matrix_clone(const Matrix *source) {
  Matrix *cpy = matrix_create(source->rows, source->columns);
  matrix_copy(cpy, source);
  return cpy;
}

void matrix_upload(Matrix *mat, const float *data) {
#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    opencl_device_upload(get_gpu(), mat->device_buffer, data, sizeof(float) * mat->rows * mat->columns);
    return;
  }
#endif

  memcpy(mat->host_data, data, sizeof(float) * mat->rows * mat->columns);
}

void matrix_download(const Matrix *mat, float *data) {
#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    opencl_device_download(get_gpu(), mat->device_buffer, data, sizeof(float) * mat->rows * mat->columns);
    return;
  }
#endif

  memcpy(data, mat->host_data, sizeof(float) * mat->rows * mat->columns);
}

void matrix_clear(Matrix *mat) {
  int total = mat->rows * mat->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_CLEAR);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &mat->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_CLEAR, total);
    return;
  }
#endif

  compute_math_clear(mat->host_data, total);
}

void matrix_fill(Matrix *mat, float value) {
  int total = mat->rows * mat->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_FILL);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &mat->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(float), &value);
    clSetKernelArg(kernel, 2, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_FILL, total);
    return;
  }
#endif

  compute_math_fill(mat->host_data, value, total);
}

void matrix_fill_random(Matrix *mat, float lower, float upper) {
  int total = mat->rows * mat->columns;
  float *data = (float *)malloc(sizeof(float) * total);
  float range = upper - lower;

  for (int i = 0; i < total; i++) {
    data[i] = lower + ((float)rand() / (float)RAND_MAX) * range;
  }
  matrix_upload(mat, data);
  free(data);
}

void matrix_copy(Matrix *dest, const Matrix *src) {
  int total = src->rows * src->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_COPY);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &dest->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &src->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_COPY, total);
    return;
  }
#endif

  compute_math_copy(dest->host_data, src->host_data, total);
}

void matrix_add(Matrix *out, const Matrix *mat_a, const Matrix *mat_b) {
  int total = out->rows * out->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_ADD);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &out->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &mat_a->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &mat_b->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_ADD, total);
    return;
  }
#endif

  compute_math_add(out->host_data, mat_a->host_data, mat_b->host_data, total);
}

void matrix_sub(Matrix *out, const Matrix *mat_a, const Matrix *mat_b) {
  int total = out->rows * out->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_SUBTRACT);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &out->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &mat_a->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &mat_b->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_SUBTRACT, total);
    return;
  }
#endif

  compute_math_subtract(out->host_data, mat_a->host_data, mat_b->host_data, total);
}

void matrix_accumulate(Matrix *dest, const Matrix *src) {
  int total = dest->rows * dest->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_ACCUMULATE);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &dest->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &src->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_ACCUMULATE, total);
    return;
  }
#endif

  compute_math_accumulate(dest->host_data, src->host_data, total);
}

void matrix_add_bias(Matrix *out, const Matrix *value, const Matrix *bias) {
#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_ADD_BIAS);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &out->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &value->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &bias->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &out->rows);
    clSetKernelArg(kernel, 4, sizeof(int), &out->columns);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_ADD_BIAS, out->rows * out->columns);
    return;
  }
#endif

  compute_add_bias(out->host_data, value->host_data, bias->host_data, out->rows, out->columns);
}

void matrix_add_bias_gradient(Matrix *bias_gradient, const Matrix *upstream_gradient) {
#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_ADD_BIAS_GRADIENT);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &bias_gradient->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &upstream_gradient->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(int), &upstream_gradient->rows);
    clSetKernelArg(kernel, 3, sizeof(int), &upstream_gradient->columns);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_ADD_BIAS_GRADIENT, upstream_gradient->rows);
    return;
  }
#endif

  compute_add_bias_gradient(bias_gradient->host_data, upstream_gradient->host_data, upstream_gradient->rows, upstream_gradient->columns);
}

void matrix_scale(Matrix *mat, float scalar) {
  int total = mat->rows * mat->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_MATH_SCALE);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &mat->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(float), &scalar);
    clSetKernelArg(kernel, 2, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_MATH_SCALE, total);
    return;
  }
#endif

  compute_math_scale(mat->host_data, scalar, total);
}

void matrix_multiply(Matrix *out, const Matrix *mat_a, const Matrix *mat_b, int transpose_a, int transpose_b, int zero_output) {
  int matrix_a_rows = transpose_a ? mat_a->columns : mat_a ->rows;
  int matrix_a_columns = transpose_a ? mat_a->rows : mat_a->columns;
  int matrix_b_columns = transpose_b ? mat_b->rows : mat_b->columns;

  int m = matrix_a_rows;
  int n = matrix_b_columns;
  int k = matrix_a_columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();

    OpenCLKernelId kernel_id;
    if (!transpose_a && !transpose_b) {
      kernel_id = OPENCL_KERNEL_MATRIX_MULTIPLICATION_NN;
    } else if (!transpose_a && transpose_b) {
      kernel_id = OPENCL_KERNEL_MATRIX_MULTIPLICATION_NT;
    } else if (transpose_a && !transpose_b) {
      kernel_id = OPENCL_KERNEL_MATRIX_MULTIPLICATION_TN;
    } else {
      kernel_id = OPENCL_KERNEL_MATRIX_MULTIPLICATION_TT;
    }

    cl_kernel kernel = opencl_device_get_kernel(gpu, kernel_id);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &out->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &mat_a->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &mat_b->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &m);
    clSetKernelArg(kernel, 4, sizeof(int), &n);
    clSetKernelArg(kernel, 5, sizeof(int), &k);
    clSetKernelArg(kernel, 6, sizeof(int), &zero_output);

    size_t global_work_size[2] = { (size_t)m, (size_t)n };
    clEnqueueNDRangeKernel(gpu->queue, kernel, 2, NULL, global_work_size, NULL, 0, NULL, NULL);
    return;
  }
#endif

  if (!transpose_a && !transpose_b) {
    compute_math_matrix_multiplication_nn(out->host_data, mat_a->host_data, mat_b->host_data, m, n, k, zero_output);
  } else if (!transpose_a && transpose_b) {
    compute_math_matrix_multiplication_nt(out->host_data, mat_a->host_data, mat_b->host_data, m, n, k, zero_output);
  } else if(transpose_a && !transpose_b) {
    compute_math_matrix_multiplication_tn(out->host_data, mat_a->host_data, mat_b->host_data, m, n, k, zero_output);
  } else {
    compute_math_matrix_multiplication_tt(out->host_data, mat_a->host_data, mat_b->host_data, m, n, k, zero_output);
  }
}

void matrix_reLU(Matrix *out, const Matrix *in) {
  int total = in->rows * in->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_RELU_FORWARD);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &out->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &in->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_RELU_FORWARD, total);
    return;
  }
#endif

  compute_relu_forward( out->host_data, in->host_data, total);
}

void matrix_softmax(Matrix *out, const Matrix *in) {
#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_SOFTMAX_FORWARD);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &out->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &in->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(int), &in->rows);
    clSetKernelArg(kernel, 3, sizeof(int), &in->columns);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_SOFTMAX_FORWARD, in->columns);
    return;
  }
#endif

  compute_softmax_forward(out->host_data, in->host_data, in->rows, in->columns);
}

void matrix_cross_entropy(Matrix *out, const Matrix *predicted, const Matrix *expected) {
  int total = predicted->rows * predicted->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_CROSS_ENTROPY_FORWARD);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &out->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &predicted->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &expected->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_CROSS_ENTROPY_FORWARD, total);
    return;
  }
#endif

  compute_cross_entropy_forward(out->host_data, predicted->host_data, expected->host_data, total);
}

void matrix_reLU_gradient(Matrix *input_grad, const Matrix *input_val, const Matrix *upstream_grad) {
  int total = input_val->rows * input_val->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_RELU_BACKWARD);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &input_grad->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &input_val->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &upstream_grad->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_RELU_BACKWARD, total);
    return;
  }
#endif

  compute_relu_backward(input_grad->host_data, input_val->host_data, upstream_grad->host_data, total);
}

void matrix_softmax_gradient(Matrix *input_grad, const Matrix *softmax_out, const Matrix *upstream_grad) {
#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_SOFTMAX_BACKWARD);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &input_grad->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &softmax_out->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &upstream_grad->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &softmax_out->rows);
    clSetKernelArg(kernel, 4, sizeof(int), &softmax_out->columns);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_SOFTMAX_BACKWARD, softmax_out->columns);
    return;
  }
#endif

  compute_softmax_backward(input_grad->host_data, softmax_out->host_data, upstream_grad->host_data, softmax_out->rows, softmax_out->columns);
}

void matrix_cross_entropy_gradient_predicted(Matrix *predicted_grad, const Matrix *predicted_val, const Matrix *expected_val, const Matrix *upstream_grad) {
  int total = predicted_val->rows * predicted_val->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_CROSS_ENTROPY_PREDICTED);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &predicted_grad->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &predicted_val->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &expected_val->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(cl_mem), &upstream_grad->device_buffer);
    clSetKernelArg(kernel, 4, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_CROSS_ENTROPY_PREDICTED, total);
    return;
  }
#endif

  compute_cross_entropy_predicted(predicted_grad->host_data,predicted_val->host_data, expected_val->host_data, upstream_grad->host_data, total);
}

void matrix_cross_entropy_gradient_expected(Matrix *expected_grad, const Matrix *predicted_val, const Matrix *upstream_grad) {
  int total = predicted_val->rows * predicted_val->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_CROSS_ENTROPY_EXPECTED);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &expected_grad->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &predicted_val->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &upstream_grad->device_buffer);
    clSetKernelArg(kernel, 3, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_CROSS_ENTROPY_EXPECTED, total);
    return;
  }
#endif

  compute_cross_entropy_expected(expected_grad->host_data, predicted_val->host_data, upstream_grad->host_data, total);
}

void matrix_param_update(Matrix *parameter, const Matrix *gradient, float scaled_learning_rate) {
  int total = parameter->rows * parameter->columns;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    OpenCLDevice *gpu = get_gpu();
    cl_kernel kernel = opencl_device_get_kernel(gpu, OPENCL_KERNEL_PARAM_UPDATE);
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &parameter->device_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &gradient->device_buffer);
    clSetKernelArg(kernel, 2, sizeof(float), &scaled_learning_rate);
    clSetKernelArg(kernel, 3, sizeof(int), &total);
    opencl_device_dispatch_1d(gpu, OPENCL_KERNEL_PARAM_UPDATE, total);
    return;
  }
#endif

  compute_param_update(parameter->host_data, gradient->host_data, scaled_learning_rate, total);
}

float matrix_sum(const Matrix *mat) {
  int total = mat->rows * mat->columns;
  float *host_buffer;
  int free_required = 0;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    host_buffer = (float *)malloc(sizeof(float) * total);
    opencl_device_download(get_gpu(), mat->device_buffer, host_buffer, sizeof(float) * total);
    free_required = 1;
  } else
#endif
  {
    host_buffer = mat->host_data;
  }

  float accumlator = 0.0f;

  for (int i = 0; i < total; i++) {
    accumlator += host_buffer[i];
  }

  if (free_required) {
    free(host_buffer);
  }

  return accumlator;
}

int matrix_argmax(const Matrix *mat) {
  int total = mat->rows * mat->columns;
  float *host_buffer;
  int free_required = 0;

#ifdef SYSTEM_HAS_OPENCL
  if (is_gpu_mode()) {
    host_buffer = (float *)malloc(sizeof(float) * total);
    opencl_device_download(get_gpu(), mat->device_buffer, host_buffer, sizeof(float) * total);
    free_required = 1;
  } else
#endif
  {
    host_buffer = mat->host_data;
  }

  int best_index = 0;
  float best_value = host_buffer[0];

  for(int i = 1; i < total; i++) {
    if (host_buffer[i] > best_value) {
      best_value = host_buffer[i];
      best_index = i;
    }
  }

  if (free_required) {
    free(host_buffer);
  }

  return best_index;
}