* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* MindIE is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <cassert>
#include <future>
#include <iostream>
#include <vector>
#include "log.h"
#include "model_execute_data.pb.h"
namespace py = pybind11;
using model_execute_data::CompletionSequenceGroupOutput;
using model_execute_data::ExecuteModelResponse;
using model_execute_data::ExecuteRequest;
using model_execute_data::ExecuteResponse;
using model_execute_data::ExecuteType;
using model_execute_data::MODEL_INFER;
using model_execute_data::SequenceOutput;
namespace mindie_llm {
struct ChunkProcessParams {
const int64_t *group_indices_data;
const int32_t *num_top_tokens;
const int64_t *sequence_ids;
const int64_t *parent_sequence_ids;
const int64_t *eos_info;
const int64_t *truncation_indices;
int64_t num_parallel_tokens;
const float *cumulative_logprobs;
const int64_t *token_ids;
const float *logprobs;
ssize_t top_token_ids_shape1;
ssize_t top_token_ids_shape2;
const int64_t *top_token_ids;
const float *top_logprobs;
};
static boost::asio::thread_pool thread_pool(boost::thread::hardware_concurrency());
void process_chunk(int index, const ChunkProcessParams ¶ms, CompletionSequenceGroupOutput *group_output) {
int64_t start_index = params.group_indices_data[index * 2];
int64_t end_index = params.group_indices_data[index * 2 + 1];
int64_t seq_count = end_index - start_index;
int64_t num_top_token = params.num_top_tokens[start_index];
if (seq_count <= 0) {
return;
}
for (int64_t i = 0; i < seq_count; ++i) {
SequenceOutput *seq_output = group_output->add_samples();
int current_idx = start_index + i;
seq_output->set_seq_id(params.sequence_ids[current_idx]);
seq_output->set_parent_seq_id(params.parent_sequence_ids[current_idx]);
seq_output->set_num_speculative_tokens(params.eos_info[2 * current_idx + 1]);
seq_output->set_finish_reason(static_cast<int32_t>(params.eos_info[2 * current_idx]));
seq_output->set_truncation_index(params.truncation_indices[current_idx]);
seq_output->set_num_parallel_tokens(params.num_parallel_tokens);
seq_output->set_cumulative_logprobs(params.cumulative_logprobs[current_idx]);
for (ssize_t j = 0; j < params.num_parallel_tokens; ++j) {
seq_output->add_output_token(params.token_ids[current_idx * params.num_parallel_tokens + j]);
}
for (ssize_t j = 0; j < params.num_parallel_tokens; ++j) {
seq_output->add_logprob(params.logprobs[current_idx * params.num_parallel_tokens + j]);
}
for (ssize_t j = 0; j < params.top_token_ids_shape1; ++j) {
for (ssize_t k = 0; k < num_top_token; ++k) {
seq_output->add_top_token_ids(
params.top_token_ids[current_idx * params.top_token_ids_shape1 * params.top_token_ids_shape2 +
j * params.top_token_ids_shape2 + k]);
}
}
for (ssize_t j = 0; j < params.top_token_ids_shape1; ++j) {
for (ssize_t k = 0; k < num_top_token; ++k) {
seq_output->add_top_logprobs(
params.top_logprobs[current_idx * params.top_token_ids_shape1 * params.top_token_ids_shape2 +
j * params.top_token_ids_shape2 + k]);
}
}
}
}
py::object convert_generate_output(const py::object &generate_output) {
ExecuteResponse cpp_response;
cpp_response.set_status(0);
cpp_response.set_msg_type(1);
auto *cpp_execute_model_response = cpp_response.mutable_execute_model_response();
py::array group_indices_arr = generate_output.attr("group_indices").cast<py::array>();
auto group_indices_numpy = group_indices_arr.request();
int64_t *group_indices_data = static_cast<int64_t *>(group_indices_numpy.ptr);
size_t num_groups = group_indices_numpy.size / 2;
auto sequence_ids = static_cast<int64_t *>(generate_output.attr("sequence_ids").cast<py::array>().request().ptr);
auto parent_sequence_ids =
static_cast<int64_t *>(generate_output.attr("parent_sequence_ids").cast<py::array>().request().ptr);
auto num_top_tokens =
static_cast<int32_t *>(generate_output.attr("num_top_tokens").cast<py::array>().request().ptr);
auto token_ids_numpy = generate_output.attr("token_ids").cast<py::array>().request();
int64_t *token_ids = static_cast<int64_t *>(token_ids_numpy.ptr);
ssize_t num_parallel_tokens = token_ids_numpy.shape[1];
auto logprobs_numpy = generate_output.attr("logprobs").cast<py::array>().request();
float *logprobs = static_cast<float *>(logprobs_numpy.ptr);
int64_t *eos_info = static_cast<int64_t *>(generate_output.attr("eos_info").cast<py::array>().request().ptr);
int64_t *truncation_indices =
static_cast<int64_t *>(generate_output.attr("truncation_indices").cast<py::array>().request().ptr);
float *cumulative_logprobs =
static_cast<float *>(generate_output.attr("cumulative_logprobs").cast<py::array>().request().ptr);
auto top_token_ids_numpy = generate_output.attr("top_token_ids").cast<py::array>().request();
int64_t *top_token_ids = static_cast<int64_t *>(top_token_ids_numpy.ptr);
ssize_t top_token_ids_shape1 = top_token_ids_numpy.shape[1];
ssize_t top_token_ids_shape2 = top_token_ids_numpy.shape[2];
float *top_logprobs = static_cast<float *>(generate_output.attr("top_logprobs").cast<py::array>().request().ptr);
ChunkProcessParams params{.group_indices_data = group_indices_data,
.num_top_tokens = num_top_tokens,
.sequence_ids = sequence_ids,
.parent_sequence_ids = parent_sequence_ids,
.eos_info = eos_info,
.truncation_indices = truncation_indices,
.num_parallel_tokens = num_parallel_tokens,
.cumulative_logprobs = cumulative_logprobs,
.token_ids = token_ids,
.logprobs = logprobs,
.top_token_ids_shape1 = top_token_ids_shape1,
.top_token_ids_shape2 = top_token_ids_shape2,
.top_token_ids = top_token_ids,
.top_logprobs = top_logprobs};
std::vector<std::future<void>> futures;
for (size_t g = 0; g < num_groups; ++g) {
CompletionSequenceGroupOutput *group_output = cpp_execute_model_response->add_outputs();
auto task_ptr = std::make_shared<std::packaged_task<void()>>(
[g, ¶ms, group_output]() {
process_chunk(g, params, group_output);
});
futures.push_back(task_ptr->get_future());
boost::asio::post(thread_pool, [task_ptr]() {
(*task_ptr)();
});
}
for (auto &fut : futures) {
fut.get();
}
const size_t msg_size = cpp_response.ByteSizeLong();
std::string buffer;
buffer.resize(msg_size);
cpp_response.SerializeToArray(buffer.data(), static_cast<int>(msg_size));
return pybind11::bytes(buffer);
}
void lwd_build_cpp_response(ExecuteResponse &cpp_response, ChunkProcessParams ¶ms, bool isPrefill,
size_t numGroups) {
cpp_response.set_status(0);
cpp_response.set_msg_type(1);
auto *cpp_execute_model_response = cpp_response.mutable_execute_model_response();
cpp_execute_model_response->set_layerwise_is_prefill(isPrefill);
std::vector<std::future<void>> futures;
for (size_t g = 0; g < numGroups; ++g) {
CompletionSequenceGroupOutput *group_output = cpp_execute_model_response->add_outputs();
auto task_ptr = std::make_shared<std::packaged_task<void()>>(
[g, ¶ms, group_output]() {
process_chunk(g, params, group_output);
});
futures.push_back(task_ptr->get_future());
boost::asio::post(thread_pool, [task_ptr]() {
(*task_ptr)();
});
}
for (auto &fut : futures) {
fut.get();
}
return;
}
py::object lwd_convert_generate_output(const py::object &generate_output, bool is_prefill) {
ExecuteResponse cpp_response;
py::array group_indices_arr = generate_output.attr("group_indices").cast<py::array>();
auto group_indices_numpy = group_indices_arr.request();
int64_t *groupIndicesData = static_cast<int64_t *>(group_indices_numpy.ptr);
size_t numGroups = group_indices_numpy.size / 2;
auto sequence_ids = static_cast<int64_t *>(generate_output.attr("sequence_ids").cast<py::array>().request().ptr);
auto parent_sequence_ids =
static_cast<int64_t *>(generate_output.attr("parent_sequence_ids").cast<py::array>().request().ptr);
auto num_top_tokens =
static_cast<int32_t *>(generate_output.attr("num_top_tokens").cast<py::array>().request().ptr);
auto token_ids_numpy = generate_output.attr("token_ids").cast<py::array>().request();
int64_t *tokenIds = static_cast<int64_t *>(token_ids_numpy.ptr);
ssize_t numParallelTokens = token_ids_numpy.shape[1];
auto logprobs_numpy = generate_output.attr("logprobs").cast<py::array>().request();
float *logprobs = static_cast<float *>(logprobs_numpy.ptr);
int64_t *eosInfo = static_cast<int64_t *>(generate_output.attr("eos_info").cast<py::array>().request().ptr);
int64_t *truncationIndices =
static_cast<int64_t *>(generate_output.attr("truncation_indices").cast<py::array>().request().ptr);
float *cumulativeLogprobs =
static_cast<float *>(generate_output.attr("cumulative_logprobs").cast<py::array>().request().ptr);
auto top_token_ids_numpy = generate_output.attr("top_token_ids").cast<py::array>().request();
int64_t *topTokenIds = static_cast<int64_t *>(top_token_ids_numpy.ptr);
ssize_t topTokenIdsShape1 = top_token_ids_numpy.shape[1];
ssize_t topTokenIdsShape2 = top_token_ids_numpy.shape[2];
float *topLogprobs = static_cast<float *>(generate_output.attr("top_logprobs").cast<py::array>().request().ptr);
ChunkProcessParams params{.group_indices_data = groupIndicesData,
.num_top_tokens = num_top_tokens,
.sequence_ids = sequence_ids,
.parent_sequence_ids = parent_sequence_ids,
.eos_info = eosInfo,
.truncation_indices = truncationIndices,
.num_parallel_tokens = numParallelTokens,
.cumulative_logprobs = cumulativeLogprobs,
.token_ids = tokenIds,
.logprobs = logprobs,
.top_token_ids_shape1 = topTokenIdsShape1,
.top_token_ids_shape2 = topTokenIdsShape2,
.top_token_ids = topTokenIds,
.top_logprobs = topLogprobs};
lwd_build_cpp_response(cpp_response, params, is_prefill, numGroups);
const size_t msgSize = cpp_response.ByteSizeLong();
std::string buffer;
buffer.resize(msgSize);
cpp_response.SerializeToArray(buffer.data(), static_cast<int>(msgSize));
return pybind11::bytes(buffer);
}
PYBIND11_MODULE(_mindie_llm_connector, m) {
m.doc() = "_mindie_llm_connector: C++ Methods Used by the Connector Module.";
m.def("convert_generate_output", &convert_generate_output, "Convert generate output to protobuf response",
py::arg("generate_output"));
m.def(
"lwd_convert_generate_output",
[](const py::object &generate_output, bool is_prefill = false) {
return lwd_convert_generate_output(generate_output, is_prefill);
},
"Layerwise-Convert generate output to protobuf response", py::arg("generate_output"),
py::arg("is_prefill") = false);
}
}