Files
sgl-project--sglang/python/sglang/jit_kernel/include/sgl_kernel/tensor.h
T
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

606 lines
17 KiB
C++

/// \file tensor.h
/// \brief Tensor validation and symbolic matching utilities.
///
/// Provides the `TensorMatcher` fluent API for validating tensor shapes,
/// strides, dtypes, and devices at kernel entry points, along with
/// `SymbolicSize`, `SymbolicDType`, and `SymbolicDevice` for capturing
/// and cross-checking tensor metadata across multiple tensors.
///
/// See the "Tensor Checking" section in the JIT kernel dev guide for
/// usage examples.
#pragma once
#include <sgl_kernel/utils.h>
#include <dlpack/dlpack.h>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/dtype.h>
#include <algorithm>
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <optional>
#include <ranges>
#include <span>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#ifdef __CUDACC__
#include <sgl_kernel/utils.cuh>
#elif defined(__HIPCC__)
#include <sgl_kernel/utils.cuh>
#endif
namespace host {
namespace details {
inline constexpr auto kAnyDeviceID = -1;
inline constexpr auto kAnySize = static_cast<int64_t>(-1);
inline constexpr auto kNullSize = static_cast<int64_t>(-1);
inline constexpr auto kNullDType = static_cast<DLDataTypeCode>(18u);
inline constexpr auto kNullDevice = static_cast<DLDeviceType>(-1);
struct SizeRef;
struct DTypeRef;
struct DeviceRef;
template <typename T>
struct _dtype_trait {};
template <std::integral T>
struct _dtype_trait<T> {
inline static constexpr DLDataType value = {
.code = std::is_signed_v<T> ? DLDataTypeCode::kDLInt : DLDataTypeCode::kDLUInt,
.bits = static_cast<std::uint8_t>(sizeof(T) * 8),
.lanes = 1};
};
template <std::floating_point T>
struct _dtype_trait<T> {
inline static constexpr DLDataType value = {
.code = DLDataTypeCode::kDLFloat, .bits = static_cast<std::uint8_t>(sizeof(T) * 8), .lanes = 1};
};
#ifdef __CUDACC__
template <>
struct _dtype_trait<fp16_t> {
inline static constexpr DLDataType value = {.code = DLDataTypeCode::kDLFloat, .bits = 16, .lanes = 1};
};
template <>
struct _dtype_trait<bf16_t> {
inline static constexpr DLDataType value = {.code = DLDataTypeCode::kDLBfloat, .bits = 16, .lanes = 1};
};
template <>
struct _dtype_trait<fp8_e4m3_t> {
inline static constexpr DLDataType value = {.code = DLDataTypeCode::kDLFloat8_e4m3fn, .bits = 8, .lanes = 1};
};
#elif defined(__HIPCC__)
template <>
struct _dtype_trait<fp16_t> {
inline static constexpr DLDataType value = {.code = DLDataTypeCode::kDLFloat, .bits = 16, .lanes = 1};
};
template <>
struct _dtype_trait<bf16_t> {
inline static constexpr DLDataType value = {.code = DLDataTypeCode::kDLBfloat, .bits = 16, .lanes = 1};
};
#endif
template <DLDeviceType Code>
struct _device_trait {
inline static constexpr DLDevice value = {.device_type = Code, .device_id = kAnyDeviceID};
};
template <typename... Ts>
inline constexpr auto kDTypeList = std::array<DLDataType, sizeof...(Ts)>{_dtype_trait<Ts>::value...};
template <DLDeviceType... Codes>
inline constexpr auto kDeviceList = std::array<DLDevice, sizeof...(Codes)>{_device_trait<Codes>::value...};
template <typename T>
struct PrintAbleSpan {
explicit PrintAbleSpan(std::span<const T> data) : data(data) {}
std::span<const T> data;
};
// define DLDataType comparison and printing in root namespace
inline constexpr auto kDeviceStringMap = [] {
constexpr auto map = std::array<std::pair<DLDeviceType, const char*>, 16>{
std::pair{DLDeviceType::kDLCPU, "cpu"},
std::pair{DLDeviceType::kDLCUDA, "cuda"},
std::pair{DLDeviceType::kDLCUDAHost, "cuda_host"},
std::pair{DLDeviceType::kDLOpenCL, "opencl"},
std::pair{DLDeviceType::kDLVulkan, "vulkan"},
std::pair{DLDeviceType::kDLMetal, "metal"},
std::pair{DLDeviceType::kDLVPI, "vpi"},
std::pair{DLDeviceType::kDLROCM, "rocm"},
std::pair{DLDeviceType::kDLROCMHost, "rocm_host"},
std::pair{DLDeviceType::kDLExtDev, "ext_dev"},
std::pair{DLDeviceType::kDLCUDAManaged, "cuda_managed"},
std::pair{DLDeviceType::kDLOneAPI, "oneapi"},
std::pair{DLDeviceType::kDLWebGPU, "webgpu"},
std::pair{DLDeviceType::kDLHexagon, "hexagon"},
std::pair{DLDeviceType::kDLMAIA, "maia"},
std::pair{DLDeviceType::kDLTrn, "trn"},
};
constexpr auto max_type = stdr::max(map | stdv::keys);
auto result = std::array<std::string_view, max_type + 1>{};
for (const auto& [code, name] : map) {
result[static_cast<std::size_t>(code)] = name;
}
return result;
}();
struct PrintableDevice {
DLDevice device;
};
inline auto& operator<<(std::ostream& os, DLDevice device) {
const auto& mapping = kDeviceStringMap;
const auto entry = static_cast<std::size_t>(device.device_type);
RuntimeCheck(entry < mapping.size());
const auto name = mapping[entry];
RuntimeCheck(!name.empty(), "Unknown device: ", int(device.device_type));
os << name;
if (device.device_id != kAnyDeviceID && device.device_type != DLDeviceType::kDLCPU) {
os << ":" << device.device_id;
}
return os;
}
inline auto& operator<<(std::ostream& os, PrintableDevice pd) {
return os << pd.device;
}
template <typename T>
inline auto& operator<<(std::ostream& os, PrintAbleSpan<T> span) {
os << "[";
for (const auto i : irange(span.data.size())) {
if (i > 0) {
os << ", ";
}
os << span.data[i];
}
os << "]";
return os;
}
} // namespace details
/// \brief Check whether `dtype` matches the DLDataType for C++ type `T`.
template <typename T>
inline bool is_type(DLDataType dtype) {
return dtype == details::_dtype_trait<T>::value;
}
/**
* \brief A symbolic dimension size that can be bound once and
* verified across multiple tensors.
*
* Create with an optional annotation string for error messages:
* \code
* auto N = SymbolicSize{"num_tokens"};
* \endcode
*
* Call `verify()` during tensor matching to either bind the first
* observed value or check subsequent values match. Call `unwrap()`
* to retrieve the bound value (panics if unset).
*/
struct SymbolicSize {
public:
SymbolicSize(std::string_view annotation = {}) : m_value(details::kNullSize), m_annotation(annotation) {}
SymbolicSize(const SymbolicSize&) = delete;
SymbolicSize& operator=(const SymbolicSize&) = delete;
auto get_name() const -> std::string_view {
return m_annotation;
}
auto set_value(int64_t value) -> void {
RuntimeCheck(!this->has_value(), "Size value already set");
m_value = value;
}
auto has_value() const -> bool {
return m_value != details::kNullSize;
}
auto get_value() const -> std::optional<int64_t> {
return this->has_value() ? std::optional{m_value} : std::nullopt;
}
auto unwrap(DebugInfo info = {}) const -> int64_t {
RuntimeCheck(info, this->has_value(), "Size value is not set");
return m_value;
}
auto verify(int64_t value, const char* prefix, int64_t dim) -> void {
if (this->has_value()) {
if (m_value != value) {
[[unlikely]];
Panic("Size mismatch for ", m_name_str(prefix, dim), ": expected ", m_value, " but got ", value);
}
} else {
this->set_value(value);
}
}
auto value_or_name(const char* prefix, int64_t dim) const -> std::string {
if (const auto value = this->get_value()) {
return std::to_string(*value);
} else {
return m_name_str(prefix, dim);
}
}
private:
auto m_name_str(const char* prefix, int64_t dim) const -> std::string {
std::ostringstream os;
os << prefix << '#' << dim;
if (!m_annotation.empty()) os << "('" << m_annotation << "')";
return std::move(os).str();
}
std::int64_t m_value;
std::string_view m_annotation;
};
inline auto operator==(DLDevice lhs, DLDevice rhs) -> bool {
return lhs.device_type == rhs.device_type && lhs.device_id == rhs.device_id;
}
/**
* \brief A symbolic data type that can be constrained and verified.
*
* Optionally restrict allowed types via `set_options<fp16_t, bf16_t>()`.
* Use `verify()` to bind/check the dtype, and `unwrap()` to retrieve it.
*/
struct SymbolicDType {
public:
SymbolicDType() : m_value({details::kNullDType, 0, 0}) {}
SymbolicDType(const SymbolicDType&) = delete;
SymbolicDType& operator=(const SymbolicDType&) = delete;
auto set_value(DLDataType value) -> void {
RuntimeCheck(!this->has_value(), "Dtype value already set");
RuntimeCheck(
m_check(value), "Dtype value [", value, "] not in the allowed options: ", details::PrintAbleSpan{m_options});
m_value = value;
}
auto has_value() const -> bool {
return m_value.code != details::kNullDType;
}
auto get_value() const -> std::optional<DLDataType> {
return this->has_value() ? std::optional{m_value} : std::nullopt;
}
auto unwrap(DebugInfo info = {}) const -> DLDataType {
RuntimeCheck(info, this->has_value(), "Dtype value is not set");
return m_value;
}
auto set_options(std::span<const DLDataType> options) -> void {
m_options = options;
}
template <typename... Ts>
auto set_options() -> void {
m_options = details::kDTypeList<Ts...>;
}
auto verify(DLDataType dtype) -> void {
if (this->has_value()) {
RuntimeCheck(m_value == dtype, "DType mismatch: expected ", m_value, " but got ", dtype);
} else {
this->set_value(dtype);
}
}
template <typename T>
auto is_type() const -> bool {
return ::host::is_type<T>(m_value);
}
private:
auto m_check(DLDataType value) const -> bool {
return stdr::empty(m_options) || (stdr::find(m_options, value) != stdr::end(m_options));
}
std::span<const DLDataType> m_options;
DLDataType m_value;
};
/**
* \brief A symbolic device that can be constrained and verified.
*
* Optionally restrict allowed device types via
* `set_options<kDLCUDA, kDLCPU>()`. The device id can be wildcarded.
*/
struct SymbolicDevice {
public:
SymbolicDevice() : m_value({details::kNullDevice, details::kAnyDeviceID}) {}
SymbolicDevice(const SymbolicDevice&) = delete;
SymbolicDevice& operator=(const SymbolicDevice&) = delete;
auto set_value(DLDevice value) -> void {
RuntimeCheck(!this->has_value(), "Device value already set");
RuntimeCheck(
m_check(value),
"Device value [",
details::PrintableDevice{value},
"] not in the allowed options: ",
details::PrintAbleSpan{m_options});
m_value = value;
}
auto has_value() const -> bool {
return m_value.device_type != details::kNullDevice;
}
auto get_value() const -> std::optional<DLDevice> {
return this->has_value() ? std::optional{m_value} : std::nullopt;
}
auto unwrap(DebugInfo info = {}) const -> DLDevice {
RuntimeCheck(info, this->has_value(), "Device value is not set");
return m_value;
}
auto set_options(std::span<const DLDevice> options) -> void {
m_options = options;
}
template <DLDeviceType... Codes>
auto set_options() -> void {
m_options = details::kDeviceList<Codes...>;
}
auto verify(DLDevice device) -> void {
if (this->has_value()) {
RuntimeCheck(
m_value == device,
"Device mismatch: expected ",
details::PrintableDevice{m_value},
" but got ",
details::PrintableDevice{device});
} else {
this->set_value(device);
}
}
private:
auto m_check(DLDevice value) const -> bool {
return stdr::empty(m_options) || (stdr::any_of(m_options, [value](const DLDevice& opt) {
// device type must exactly match
if (opt.device_type != value.device_type) return false;
// device id can be wildcarded
return opt.device_id == details::kAnyDeviceID || opt.device_id == value.device_id;
}));
}
std::span<const DLDevice> m_options;
DLDevice m_value;
};
namespace details {
template <typename T>
struct BaseRef {
public:
BaseRef(const BaseRef&) = delete;
BaseRef& operator=(const BaseRef&) = delete;
auto operator->() const -> T* {
return m_ref;
}
auto operator*() const -> T& {
return *m_ref;
}
auto rebind(T& other) -> void {
m_ref = &other;
}
explicit BaseRef() : m_ref(&m_cache), m_cache() {}
BaseRef(T& size) : m_ref(&size), m_cache() {}
private:
T* m_ref;
T m_cache;
};
struct SizeRef : BaseRef<SymbolicSize> {
using BaseRef::BaseRef;
SizeRef(int64_t value) {
if (value != kAnySize) {
(**this).set_value(value);
} else {
// otherwise, we can match any size
}
}
};
struct DTypeRef : BaseRef<SymbolicDType> {
using BaseRef::BaseRef;
DTypeRef(DLDataType options) {
(**this).set_value(options);
}
DTypeRef(std::initializer_list<DLDataType> options) {
(**this).set_options(options);
}
DTypeRef(std::span<const DLDataType> options) {
(**this).set_options(options);
}
};
struct DeviceRef : BaseRef<SymbolicDevice> {
using BaseRef::BaseRef;
DeviceRef(DLDevice options) {
(**this).set_value(options);
}
DeviceRef(std::initializer_list<DLDevice> options) {
(**this).set_options(options);
}
DeviceRef(std::span<const DLDevice> options) {
(**this).set_options(options);
}
};
} // namespace details
/**
* \brief Fluent API for validating tensor shape, strides, dtype, and device.
*
* Construct with the expected shape (using `SymbolicSize` or literal
* integers), chain `.with_strides()`, `.with_dtype<...>()`, and
* `.with_device<...>()`, then call `.verify(tensor)`.
*
* Example:
* \code
* auto N = SymbolicSize{"N"};
* TensorMatcher({N, 128})
* .with_dtype<fp16_t, bf16_t>()
* .with_device<kDLCUDA>()
* .verify(input_tensor);
* \endcode
*
* \note `TensorMatcher` is a move-only temporary. Do not store in a variable.
*/
struct TensorMatcher {
private:
using SizeRef = details::SizeRef;
using DTypeRef = details::DTypeRef;
using DeviceRef = details::DeviceRef;
public:
TensorMatcher(const TensorMatcher&) = delete;
TensorMatcher& operator=(const TensorMatcher&) = delete;
explicit TensorMatcher(std::initializer_list<SizeRef> shape) : m_shape(shape), m_strides(), m_dtype() {}
auto with_strides(std::initializer_list<SizeRef> strides) && -> TensorMatcher&& {
// no partial update allowed
RuntimeCheck(m_strides.size() == 0, "Strides already specified");
RuntimeCheck(m_shape.size() == strides.size(), "Strides size must match shape size");
m_strides = strides;
return std::move(*this);
}
template <typename... Ts>
auto with_dtype(DTypeRef&& dtype) && -> TensorMatcher&& {
m_init_dtype();
m_dtype.rebind(*dtype);
m_dtype->set_options<Ts...>();
return std::move(*this);
}
template <typename... Ts>
auto with_dtype() && -> TensorMatcher&& {
static_assert(sizeof...(Ts) > 0, "At least one dtype option must be specified");
m_init_dtype();
m_dtype->set_options<Ts...>();
return std::move(*this);
}
template <DLDeviceType... Codes>
auto with_device(DeviceRef&& device) && -> TensorMatcher&& {
m_init_device();
m_device.rebind(*device);
m_device->set_options<Codes...>();
return std::move(*this);
}
template <DLDeviceType... Codes>
auto with_device() && -> TensorMatcher&& {
static_assert(sizeof...(Codes) > 0, "At least one device option must be specified");
m_init_device();
m_device->set_options<Codes...>();
return std::move(*this);
}
// once we start verification, we cannot modify anymore
auto verify(tvm::ffi::TensorView view, DebugInfo info = {}) const&& -> const TensorMatcher&& {
try {
m_verify_impl(view);
} catch (PanicError& e) {
auto oss = std::ostringstream{};
oss << "Tensor match failed for ";
s_print_tensor(oss, view);
oss << " at " << info.file_name() << ":" << info.line() << "\n- Root cause: " << e.root_cause();
throw PanicError(std::move(oss).str());
}
return std::move(*this);
}
private:
static auto s_print_tensor(std::ostringstream& oss, tvm::ffi::TensorView view) -> void {
oss << "Tensor<";
int64_t dim = 0;
for (const auto& size : view.shape()) {
if (dim++ > 0) oss << ", ";
oss << size;
}
oss << ">[strides=<";
dim = 0;
for (const auto& stride : view.strides()) {
if (dim++ > 0) {
oss << ", ";
}
oss << stride;
}
oss << ">, dtype=" << view.dtype();
oss << ", device=" << details::PrintableDevice{view.device()} << "]";
}
auto m_verify_impl(tvm::ffi::TensorView view) const -> void {
const auto dim = static_cast<std::size_t>(view.dim());
RuntimeCheck(dim == m_shape.size(), "Tensor dimension mismatch: expected ", m_shape.size(), " but got ", dim);
for (const auto i : irange(dim)) {
m_shape[i]->verify(view.size(i), "shape", i);
}
if (m_has_strides()) {
for (const auto i : irange(dim)) {
if (view.size(i) != 1 || !m_strides[i]->has_value()) {
// skip stride check for size 1 dimension
m_strides[i]->verify(view.stride(i), "stride", i);
}
}
} else {
RuntimeCheck(view.is_contiguous(), "Tensor is not contiguous as expected");
}
// since we may double verify, we will force to check
m_dtype->verify(view.dtype());
m_device->verify(view.device());
}
auto m_init_dtype() -> void {
RuntimeCheck(!m_has_dtype, "DType already specified");
m_has_dtype = true;
}
auto m_init_device() -> void {
RuntimeCheck(!m_has_device, "Device already specified");
m_has_device = true;
}
auto m_has_strides() const -> bool {
return !m_strides.empty();
}
std::span<const SizeRef> m_shape;
std::span<const SizeRef> m_strides;
DTypeRef m_dtype;
DeviceRef m_device;
bool m_has_dtype = false;
bool m_has_device = false;
};
} // namespace host