| |
|
|
| #include <memory> |
| #include <sstream> |
| #include <stdexcept> |
|
|
| #include <cuda_runtime.h> |
|
|
| #include <pybind11/functional.h> |
| #include <pybind11/pybind11.h> |
| #include <pybind11/pytypes.h> |
| #include <pybind11/stl.h> |
| #include <pybind11/stl_bind.h> |
|
|
| #include "xgrammar/compiler.h" |
|
|
| #include "src/turbomind/core/data_type.h" |
| #include "src/turbomind/core/tensor.h" |
| #include "src/turbomind/engine/model_request.h" |
| #include "src/turbomind/python/dlpack.h" |
| #include "src/turbomind/turbomind.h" |
| #include "src/turbomind/utils/cuda_utils.h" |
| #include "src/turbomind/utils/metrics.h" |
|
|
| namespace py = pybind11; |
| namespace ft = turbomind; |
| using namespace pybind11::literals; |
|
|
| using ft::core::Tensor; |
|
|
| |
| using TensorMap = ft::core::TensorMap; |
| PYBIND11_MAKE_OPAQUE(TensorMap); |
| static const char kDlTensorCapsuleName[] = "dltensor"; |
|
|
| DLDevice getDLDevice(const Tensor& tensor) |
| { |
| int device_id = 0; |
| if (tensor.device().type == ft::kDEVICE) { |
| cudaPointerAttributes ptr_attr{}; |
| cudaPointerGetAttributes(&ptr_attr, tensor.raw_data()); |
| device_id = ptr_attr.device; |
| } |
|
|
| DLDevice device{kDLCPU, device_id}; |
|
|
| switch (tensor.device().type) { |
| case ft::kCPU: |
| device.device_type = DLDeviceType::kDLCPU; |
| break; |
| case ft::kCPUpinned: |
| device.device_type = DLDeviceType::kDLCUDAHost; |
| break; |
| case ft::kDEVICE: |
| device.device_type = DLDeviceType::kDLCUDA; |
| break; |
| default: |
| break; |
| } |
|
|
| return device; |
| } |
|
|
| DLManagedTensor* TritonTensorToDLManagedTensor(Tensor& tensor) |
| { |
| DLDevice device = getDLDevice(tensor); |
| DLDataType data_type{0, 0, 1}; |
| using ft::data_type_v; |
| switch (tensor.dtype()) { |
| case data_type_v<bool>: |
| data_type.code = DLDataTypeCode::kDLBool; |
| data_type.bits = 8; |
| break; |
| case data_type_v<uint8_t>: |
| data_type.code = DLDataTypeCode::kDLUInt; |
| data_type.bits = 8; |
| break; |
| case data_type_v<uint16_t>: |
| data_type.code = DLDataTypeCode::kDLUInt; |
| data_type.bits = 16; |
| break; |
| case data_type_v<uint32_t>: |
| data_type.code = DLDataTypeCode::kDLUInt; |
| data_type.bits = 32; |
| break; |
| case data_type_v<uint64_t>: |
| data_type.code = DLDataTypeCode::kDLUInt; |
| data_type.bits = 64; |
| break; |
| case data_type_v<int8_t>: |
| data_type.code = DLDataTypeCode::kDLInt; |
| data_type.bits = 8; |
| break; |
| case data_type_v<int16_t>: |
| data_type.code = DLDataTypeCode::kDLInt; |
| data_type.bits = 16; |
| break; |
| case data_type_v<int32_t>: |
| data_type.code = DLDataTypeCode::kDLInt; |
| data_type.bits = 32; |
| break; |
| case data_type_v<int64_t>: |
| data_type.code = DLDataTypeCode::kDLInt; |
| data_type.bits = 64; |
| break; |
| case data_type_v<turbomind::half_t>: |
| data_type.code = DLDataTypeCode::kDLFloat; |
| data_type.bits = 16; |
| break; |
| case data_type_v<float>: |
| data_type.code = DLDataTypeCode::kDLFloat; |
| data_type.bits = 32; |
| break; |
| case data_type_v<double>: |
| data_type.code = DLDataTypeCode::kDLFloat; |
| data_type.bits = 64; |
| break; |
| case data_type_v<turbomind::bfloat16_t>: |
| data_type.code = DLDataTypeCode::kDLBfloat; |
| data_type.bits = 16; |
| break; |
| default: |
| break; |
| } |
|
|
| static_assert(sizeof(int64_t) == sizeof(tensor.shape(0))); |
|
|
| Tensor* ctx = new Tensor(tensor); |
| DLTensor dl_tensor{const_cast<void*>(ctx->raw_data()), |
| device, |
| (int32_t)(ctx->ndim()), |
| data_type, |
| (int64_t*)ctx->shape().data(), |
| (int64_t*)(nullptr), |
| 0}; |
| return new DLManagedTensor{dl_tensor, ctx, [](DLManagedTensor* dlmt) { |
| delete (Tensor*)dlmt->manager_ctx; |
| delete dlmt; |
| }}; |
| } |
|
|
| ft::DeviceType getMemoryType(DLDevice device) |
| { |
| switch (device.device_type) { |
| case DLDeviceType::kDLCUDAHost: |
| return ft::DeviceType::kCPUpinned; |
| case DLDeviceType::kDLCUDA: |
| return ft::DeviceType::kDEVICE; |
| case DLDeviceType::kDLCPU: |
| default: |
| return ft::DeviceType::kCPU; |
| } |
| } |
|
|
| ft::DataType getDataType(DLDataType data_type) |
| { |
| using ft::data_type_v; |
| switch (data_type.code) { |
| case DLDataTypeCode::kDLUInt: |
| switch (data_type.bits) { |
| case 8: |
| return data_type_v<uint8_t>; |
| case 16: |
| return data_type_v<uint16_t>; |
| case 32: |
| return data_type_v<uint32_t>; |
| case 64: |
| return data_type_v<uint64_t>; |
| default: |
| return data_type_v<void>; |
| } |
| break; |
| case DLDataTypeCode::kDLInt: |
| switch (data_type.bits) { |
| case 8: |
| return data_type_v<int8_t>; |
| case 16: |
| return data_type_v<int16_t>; |
| case 32: |
| return data_type_v<int32_t>; |
| case 64: |
| return data_type_v<int64_t>; |
| default: |
| return data_type_v<void>; |
| } |
| break; |
| case DLDataTypeCode::kDLFloat: |
| switch (data_type.bits) { |
| case 16: |
| return data_type_v<turbomind::half_t>; |
| case 32: |
| return data_type_v<float>; |
| case 64: |
| return data_type_v<double>; |
| default: |
| return data_type_v<void>; |
| } |
| break; |
| case DLDataTypeCode::kDLBfloat: |
| switch (data_type.bits) { |
| case 16: |
| return data_type_v<turbomind::bfloat16_t>; |
| default: |
| return data_type_v<void>; |
| } |
| break; |
| case DLDataTypeCode::kDLBool: |
| return data_type_v<bool>; |
| default: |
| return data_type_v<void>; |
| } |
| } |
|
|
| std::shared_ptr<Tensor> DLManagedTensorToTritonTensor(DLManagedTensor* tensor) |
| { |
| auto& dl_tensor = tensor->dl_tensor; |
| auto where = getMemoryType(dl_tensor.device); |
| auto dtype = getDataType(dl_tensor.dtype); |
| assert(dl_tensor.ndim > 0); |
| std::vector<ft::core::ssize_t> shape(dl_tensor.shape, dl_tensor.shape + dl_tensor.ndim); |
|
|
| std::shared_ptr<void> ptr{dl_tensor.data, [tensor](void* p) { |
| if (tensor->deleter) { |
| tensor->deleter(tensor); |
| } |
| }}; |
| return std::make_shared<Tensor>(ptr, std::move(shape), dtype, where); |
| } |
|
|
| static void safe_memcpy(void* dst, const void* src, size_t size) |
| { |
| cudaPointerAttributes dat{}; |
| cudaPointerAttributes sat{}; |
| ft::check_cuda_error(cudaPointerGetAttributes(&dat, dst)); |
| ft::check_cuda_error(cudaPointerGetAttributes(&sat, src)); |
| try { |
| if (dat.devicePointer && sat.devicePointer) { |
| |
| ft::check_cuda_error(cudaMemcpy(dst, src, size, cudaMemcpyDefault)); |
| } |
| else if (dat.type == cudaMemoryTypeDevice && sat.type == cudaMemoryTypeDevice) { |
| if (dat.device != sat.device) { |
| |
| ft::check_cuda_error(cudaMemcpyPeer(dst, dat.device, src, sat.device, size)); |
| } |
| else { |
| |
| ft::CudaDeviceGuard guard(dat.device); |
| ft::check_cuda_error(cudaMemcpy(dst, src, size, cudaMemcpyDefault)); |
| } |
| } |
| else { |
| |
| ft::check_cuda_error(cudaMemcpy(dst, src, size, cudaMemcpyDefault)); |
| } |
| } |
| catch (...) { |
| int device_id{-1}; |
| cudaGetDevice(&device_id); |
| TM_LOG_ERROR("cudaMemcpy failed: dst=({}, {}, {}, {}), src=({}, {}, {}, {}), size={}, device={}", |
| (int)dat.type, |
| dat.device, |
| dat.devicePointer, |
| dat.hostPointer, |
| (int)sat.type, |
| sat.device, |
| sat.devicePointer, |
| sat.hostPointer, |
| size, |
| device_id); |
| throw; |
| } |
| } |
|
|
| namespace { |
|
|
| struct ScopedGIL { |
| ScopedGIL(const ScopedGIL&) = delete; |
| ScopedGIL& operator=(const ScopedGIL&) = delete; |
| ScopedGIL(ScopedGIL&&) = delete; |
| ScopedGIL& operator=(ScopedGIL&&) = delete; |
| ScopedGIL() |
| { |
| state = PyGILState_Ensure(); |
| } |
| ~ScopedGIL() |
| { |
| PyGILState_Release(state); |
| } |
| PyGILState_STATE state; |
| }; |
|
|
| } |
|
|
| PYBIND11_MODULE(_turbomind, m) |
| { |
| py::class_<ft::RequestMetrics, std::shared_ptr<ft::RequestMetrics>>(m, "RequestMetrics") |
| .def(py::init()) |
| .def_property_readonly("enqueue_time", |
| [](ft::RequestMetrics& m) { return m.enqueue_time.load(std::memory_order_relaxed); }) |
| .def_property_readonly("scheduled_time", |
| [](ft::RequestMetrics& m) { return m.scheduled_time.load(std::memory_order_relaxed); }); |
|
|
| py::class_<ft::ScheduleMetrics, std::shared_ptr<ft::ScheduleMetrics>>(m, "ScheduleMetrics") |
| .def(py::init()) |
| .def_readonly("total_seqs", &ft::ScheduleMetrics::total_seqs) |
| .def_readonly("active_seqs", &ft::ScheduleMetrics::active_seqs) |
| .def_readonly("waiting_seqs", &ft::ScheduleMetrics::waiting_seqs) |
| .def_readonly("total_blocks", &ft::ScheduleMetrics::total_blocks) |
| .def_readonly("active_blocks", &ft::ScheduleMetrics::active_blocks) |
| .def_readonly("cached_blocks", &ft::ScheduleMetrics::cached_blocks) |
| .def_readonly("free_blocks", &ft::ScheduleMetrics::free_blocks); |
|
|
| py::class_<ft::SessionParam>(m, "SessionParam") |
| .def(py::init([](uint64_t id, int step, bool start, bool end) { |
| if (!start && end) { |
| throw std::logic_error("unsupported arguments: start=false, end=true"); |
| } |
| ft::SessionParam param{}; |
| param.id = id; |
| param.step = step; |
| param.start_flag = start; |
| param.end_flag = end; |
| return param; |
| }), |
| "id"_a, |
| "step"_a, |
| "start"_a, |
| "end"_a) |
| .def_readwrite("id", &ft::SessionParam::id) |
| .def_readwrite("step", &ft::SessionParam::step) |
| .def_readwrite("start", &ft::SessionParam::start_flag) |
| .def_readwrite("end", &ft::SessionParam::end_flag); |
|
|
| py::class_<ft::GenerationConfig>(m, "GenerationConfig") |
| .def(py::init()) |
| .def_readwrite("max_new_tokens", &ft::GenerationConfig::max_new_tokens) |
| .def_readwrite("min_new_tokens", &ft::GenerationConfig::min_new_tokens) |
| .def_readwrite("eos_ids", &ft::GenerationConfig::eos_ids) |
| .def_readwrite("stop_ids", &ft::GenerationConfig::stop_ids) |
| .def_readwrite("bad_ids", &ft::GenerationConfig::bad_ids) |
| .def_readwrite("top_p", &ft::GenerationConfig::top_p) |
| .def_readwrite("top_k", &ft::GenerationConfig::top_k) |
| .def_readwrite("min_p", &ft::GenerationConfig::min_p) |
| .def_readwrite("temperature", &ft::GenerationConfig::temperature) |
| .def_readwrite("repetition_penalty", &ft::GenerationConfig::repetition_penalty) |
| .def_readwrite("random_seed", &ft::GenerationConfig::random_seed) |
| .def_readwrite("output_logprobs", &ft::GenerationConfig::output_logprobs) |
| .def_readwrite("output_last_hidden_state", &ft::GenerationConfig::output_last_hidden_state) |
| .def_readwrite("output_logits", &ft::GenerationConfig::output_logits) |
| .def("__repr__", [](const ft::GenerationConfig& c) { |
| std::ostringstream oss; |
| oss << c; |
| return oss.str(); |
| }); |
|
|
| py::class_<ft::RequestState, std::unique_ptr<ft::RequestState>>(m, "RequestState") |
| .def_readonly("status", &ft::RequestState::status) |
| .def_readonly("seq_len", &ft::RequestState::seq_len); |
|
|
| py::class_<ft::AtomicRequestState, std::shared_ptr<ft::AtomicRequestState>>(m, "AtomicRequestState") |
| .def("consume", [](ft::AtomicRequestState& s) { return s.exchange(nullptr); }); |
|
|
| |
| { |
| using namespace turbomind; |
| py::enum_<ft::DataType>(m, "DataType") |
| .value("TYPE_INVALID", kNull) |
| .value("TYPE_BOOL", kBool) |
| .value("TYPE_UINT8", kUint8) |
| .value("TYPE_UINT16", kUint16) |
| .value("TYPE_UINT32", kUint32) |
| .value("TYPE_UINT64", kUint64) |
| .value("TYPE_INT8", kInt8) |
| .value("TYPE_INT16", kInt16) |
| .value("TYPE_INT32", kInt32) |
| .value("TYPE_INT64", kInt64) |
| .value("TYPE_FP16", kFloat16) |
| .value("TYPE_FP32", kFloat32) |
| .value("TYPE_FP64", kFloat64) |
| .value("TYPE_BF16", kBfloat16); |
|
|
| |
| py::enum_<ft::DeviceType>(m, "MemoryType") |
| .value("MEMORY_CPU", ft::DeviceType::kCPU) |
| .value("MEMORY_CPU_PINNED", ft::DeviceType::kCPUpinned) |
| .value("MEMORY_GPU", ft::DeviceType::kDEVICE); |
| } |
|
|
| |
| py::class_<Tensor, std::shared_ptr<Tensor>>(m, "Tensor") |
| .def_property_readonly("where", [](const Tensor& t) { return t.device().type; }) |
| .def_property_readonly("type", [](const Tensor& t) { return t.dtype(); }) |
| .def_property_readonly("shape", [](const Tensor& t) { return t.shape(); }) |
| .def_property_readonly("data", [](const Tensor& t) { return t.raw_data(); }) |
| .def( |
| "copy_from", |
| [](Tensor& self, py::object obj) { |
| py::capsule cap = obj.attr("__dlpack__")(); |
| DLManagedTensor* dlmt = |
| static_cast<DLManagedTensor*>(PyCapsule_GetPointer(cap.ptr(), kDlTensorCapsuleName)); |
| auto src = DLManagedTensorToTritonTensor(dlmt); |
| |
| cap.set_name("used_dltensor"); |
|
|
| TM_CHECK_EQ(self.byte_size(), src->byte_size()) << self << " " << *src; |
| safe_memcpy(self.raw_data(), src->raw_data(), self.byte_size()); |
| }, |
| "tensor"_a) |
| .def( |
| "__dlpack__", |
| [](Tensor& self, long stream) { |
| DLManagedTensor* dlmt = TritonTensorToDLManagedTensor(self); |
| return py::capsule(dlmt, kDlTensorCapsuleName, [](PyObject* obj) { |
| DLManagedTensor* dlmt = |
| static_cast<DLManagedTensor*>(PyCapsule_GetPointer(obj, kDlTensorCapsuleName)); |
| if (dlmt) { |
| dlmt->deleter(dlmt); |
| } |
| else { |
| |
| |
| PyErr_Clear(); |
| } |
| }); |
| }, |
| "stream"_a = 0) |
| .def("__dlpack_device__", [](const Tensor& self) { |
| auto device = getDLDevice(self); |
| return std::tuple<int, int>(int(device.device_type), device.device_id); |
| }); |
| m.def( |
| "from_dlpack", |
| [](py::object obj) { |
| py::capsule cap = obj.attr("__dlpack__")(); |
| DLManagedTensor* dlmt = |
| static_cast<DLManagedTensor*>(PyCapsule_GetPointer(cap.ptr(), kDlTensorCapsuleName)); |
| auto ret = DLManagedTensorToTritonTensor(dlmt); |
| |
| cap.set_name("used_dltensor"); |
| return ret; |
| }, |
| "dl_managed_tensor"_a); |
|
|
| py::bind_map<TensorMap, std::shared_ptr<TensorMap>>(m, "TensorMap"); |
|
|
| using ft::ModelRequest; |
| py::class_<ModelRequest>(m, "ModelRequest") |
| .def( |
| "forward", |
| [](ModelRequest* model_request, |
| std::shared_ptr<TensorMap> input_tensors, |
| const ft::SessionParam& session, |
| const ft::GenerationConfig& gen_cfg, |
| bool stream_output, |
| bool enable_metrics, |
| std::function<void()> cb) { |
| ModelRequest::InputParam param{}; |
| param.tensors = std::move(input_tensors); |
| param.session = session; |
| param.gen_cfg = gen_cfg; |
| param.stream_output = stream_output; |
| param.enable_metrics = enable_metrics; |
|
|
| auto ret = model_request->Forward(std::move(param), [cb = std::move(cb)]() { |
| try { |
| cb(); |
| } |
| catch (const py::error_already_set& e) { |
| std::cerr << e.what() << std::endl; |
| } |
| }); |
| return std::make_tuple(std::move(ret.tensors), std::move(ret.state), std::move(ret.metrics)); |
| }, |
| py::call_guard<py::gil_scoped_release>(), |
| "input_tensors"_a, |
| "session"_a, |
| "gen_cfg"_a, |
| "stream_output"_a, |
| "enable_metrics"_a, |
| "cb"_a) |
| .def( |
| "cancel", |
| [](ModelRequest* model_request) { |
| model_request->Cancel(); |
| }, |
| py::call_guard<py::gil_scoped_release>()) |
| .def( |
| "end", |
| [](ModelRequest* model_request, std::function<void(int)> cb, uint64_t session_id) { |
| model_request->End(std::move(cb), session_id); |
| }, |
| py::call_guard<py::gil_scoped_release>(), |
| "cb"_a, |
| "session_id"_a) |
| .def( |
| "set_grammar", |
| [](ModelRequest* model_request, const xgrammar::CompiledGrammar& grammar) { |
| TM_LOG_INFO("Set grammar for model_request"); |
| model_request->setGrammar(grammar); |
| }, |
| py::call_guard<py::gil_scoped_release>(), |
| "grammar"_a); |
|
|
| |
| using ft::TurboMind; |
| py::class_<TurboMind, std::shared_ptr<TurboMind>>(m, "TurboMind") |
| .def_static( |
| "create", |
| [](std::string model_dir, std::string config, std::string weight_type) -> std::shared_ptr<TurboMind> { |
| auto gil_factory = [] { |
| |
| return std::static_pointer_cast<void>(std::make_shared<ScopedGIL>()); |
| }; |
| auto no_gil_deleter = [](TurboMind* ptr) { |
| pybind11::gil_scoped_release release; |
| delete ptr; |
| }; |
|
|
| std::shared_ptr<TurboMind> model(new TurboMind(model_dir, config, gil_factory), no_gil_deleter); |
| return model; |
| }, |
| "model_dir"_a, |
| "config"_a = "", |
| "weight_type"_a = "half") |
| .def( |
| "create_request", |
| [](TurboMind* model) { return model->CreateRequest(); }, |
| py::call_guard<py::gil_scoped_release>()) |
| .def("create_weights", &TurboMind::CreateWeights, py::call_guard<py::gil_scoped_release>(), "index"_a) |
| .def( |
| "get_weights", |
| [](TurboMind* model, int index) { return model->GetWeights(index); }, |
| py::call_guard<py::gil_scoped_release>(), |
| "index"_a) |
| .def( |
| "process_weight", |
| [](TurboMind* model, int index) { model->ProcessWeights(index); }, |
| py::call_guard<py::gil_scoped_release>(), |
| "index"_a) |
| .def( |
| "create_engine", |
| [](TurboMind* model, int index) { model->CreateEngine(index); }, |
| py::call_guard<py::gil_scoped_release>(), |
| "index"_a) |
| .def( |
| "get_schedule_metrics", |
| [](TurboMind* model, int index) { return model->GetScheduleMetrics(index); }, |
| py::call_guard<py::gil_scoped_release>(), |
| "index"_a) |
| .def( |
| "sleep", |
| [](TurboMind* model, int index, int level) { model->Sleep(index, level); }, |
| py::call_guard<py::gil_scoped_release>(), |
| "index"_a, |
| "level"_a) |
| .def( |
| "wakeup", |
| [](TurboMind* model, int index, const std::vector<std::string>& tags) { model->WakeUp(index, tags); }, |
| py::call_guard<py::gil_scoped_release>(), |
| "index"_a, |
| "tags"_a) |
| .def("is_dummy_node", [](TurboMind* model) { return model->is_dummy_node(); }); |
| } |
|
|