use std::any::Any;
use std::borrow::Cow;
use std::sync::Arc;
use onnx_runtime_ir::{DataType, TensorLayout};
use onnx_runtime_memory_governor::MemoryRole;
use crate::ExecutionProvider;
use crate::error::Result;
use crate::tensor::{DevicePtrMut, TensorMut, TensorView};
use crate::weight::WeightHandle;
#[derive(Clone, Copy, Debug)]
pub struct KernelConstantInput<'a> {
pub dtype: DataType,
pub shape: &'a [usize],
pub bytes: &'a [u8],
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Cost {
pub compute_us: f64,
pub memory_us: f64,
pub transfer_us: f64,
pub launch_us: f64,
pub bytes_moved: u64,
}
impl Cost {
pub const ZERO: Cost = Cost {
compute_us: 0.0,
memory_us: 0.0,
transfer_us: 0.0,
launch_us: 0.0,
bytes_moved: 0,
};
pub fn new(compute_us: f64, memory_us: f64, transfer_us: f64) -> Self {
Self {
compute_us,
memory_us,
transfer_us,
..Self::ZERO
}
}
pub fn with_launch_us(mut self, launch_us: f64) -> Self {
self.launch_us = launch_us;
self
}
pub fn with_bytes_moved(mut self, bytes_moved: u64) -> Self {
self.bytes_moved = bytes_moved;
self
}
pub fn total_us(&self) -> f64 {
self.compute_us + self.memory_us + self.transfer_us + self.launch_us
}
}
pub fn structural_input_bytes(
shapes: &[onnx_runtime_ir::Shape],
input_dtypes: &[onnx_runtime_ir::DataType],
) -> u64 {
shapes
.iter()
.zip(input_dtypes.iter())
.map(|(shape, &dtype)| {
let numel: usize = shape
.iter()
.map(|d| d.as_static().unwrap_or(1))
.product::<usize>();
dtype.checked_storage_bytes(numel).unwrap_or(usize::MAX) as u64
})
.fold(0_u64, u64::saturating_add)
}
#[derive(Debug)]
pub enum KernelMatch {
Supported {
cost: Cost,
required_input_layouts: Option<Vec<TensorLayout>>,
output_layouts: Vec<TensorLayout>,
},
Unsupported {
reason: Cow<'static, str>,
},
}
impl KernelMatch {
pub fn unsupported(reason: impl Into<Cow<'static, str>>) -> Self {
Self::Unsupported {
reason: reason.into(),
}
}
pub fn is_supported(&self) -> bool {
matches!(self, KernelMatch::Supported { .. })
}
pub fn reason(&self) -> Option<&str> {
match self {
Self::Supported { .. } => None,
Self::Unsupported { reason } => Some(reason),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CaptureSupport {
Supported,
Unsupported {
reason: Cow<'static, str>,
},
}
impl CaptureSupport {
pub fn unsupported(reason: impl Into<Cow<'static, str>>) -> Self {
Self::Unsupported {
reason: reason.into(),
}
}
pub fn is_supported(&self) -> bool {
matches!(self, Self::Supported)
}
pub fn reason(&self) -> Option<&str> {
match self {
Self::Supported => None,
Self::Unsupported { reason } => Some(reason),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KernelVariantSelection {
pub variant: &'static str,
pub reason: Cow<'static, str>,
}
impl KernelVariantSelection {
pub fn new(variant: &'static str, reason: impl Into<Cow<'static, str>>) -> Self {
Self {
variant,
reason: reason.into(),
}
}
}
pub const ARG_KERNEL_VARIANT: &str = "kernel_variant";
pub const ARG_KERNEL_VARIANT_REASON: &str = "kernel_variant_reason";
pub const ARG_DEVICE: &str = "device";
pub const ARG_BYTES: &str = "bytes";
pub const ARG_FLOPS: &str = "flops";
pub const CAT_KERNEL_WORKER: &str = "op.worker";
#[inline]
pub fn record_kernel_metrics(
device: &'static str,
inputs: &[TensorView<'_>],
outputs: &[TensorMut<'_>],
flops: impl FnOnce() -> u64,
) {
if !kernel_variant_tracing_enabled() {
return;
}
onnx_runtime_tracer::annotate_current_span_with(|| {
let input_bytes = inputs
.iter()
.filter(|input| !input.is_absent())
.fold(0_u64, |total, input| {
total.saturating_add(input.byte_size() as u64)
});
let bytes = outputs.iter().fold(input_bytes, |total, output| {
total.saturating_add(output.byte_size() as u64)
});
onnx_runtime_tracer::Args::new()
.with(ARG_DEVICE, device)
.with(ARG_BYTES, bytes)
.with(ARG_FLOPS, flops())
});
}
#[must_use]
#[inline]
pub fn kernel_worker_span(label: &'static str) -> Option<onnx_runtime_tracer::SpanGuard> {
let context = onnx_runtime_tracer::global_context()?;
if !context
.verbosity()
.includes(onnx_runtime_tracer::TraceVerbosity::Full)
{
return None;
}
Some(context.span(label, CAT_KERNEL_WORKER))
}
#[must_use]
#[inline]
pub fn kernel_variant_tracing_enabled() -> bool {
onnx_runtime_tracer::tracing_active()
}
#[inline]
pub fn record_kernel_variant_selection(selection: &KernelVariantSelection) {
if !kernel_variant_tracing_enabled() {
return;
}
onnx_runtime_tracer::annotate_current_span_with(|| {
onnx_runtime_tracer::Args::new()
.with(ARG_KERNEL_VARIANT, selection.variant)
.with(ARG_KERNEL_VARIANT_REASON, selection.reason.as_ref())
});
}
#[inline]
pub fn record_kernel_variant_stage_selection(stage: &str, selection: &KernelVariantSelection) {
if !kernel_variant_tracing_enabled() {
return;
}
let variant_key = format!("{ARG_KERNEL_VARIANT}.{stage}");
let reason_key = format!("{ARG_KERNEL_VARIANT_REASON}.{stage}");
onnx_runtime_tracer::annotate_current_span_with(|| {
onnx_runtime_tracer::Args::new()
.with(variant_key, selection.variant)
.with(reason_key, selection.reason.as_ref())
});
}
#[macro_export]
macro_rules! record_kernel_variant {
($variant:expr, $($arg:tt)+) => {{
if $crate::kernel_variant_tracing_enabled() {
let selection = $crate::KernelVariantSelection::new(
$variant,
::std::format!($($arg)+),
);
$crate::record_kernel_variant_selection(&selection);
}
}};
}
#[macro_export]
macro_rules! record_kernel_variant_stage {
($stage:expr, $variant:expr, $($arg:tt)+) => {{
if $crate::kernel_variant_tracing_enabled() {
let selection = $crate::KernelVariantSelection::new(
$variant,
::std::format!($($arg)+),
);
$crate::record_kernel_variant_stage_selection($stage, &selection);
}
}};
}
#[macro_export]
macro_rules! decline_capture {
($($arg:tt)+) => {
return $crate::CaptureSupport::unsupported(format!($($arg)+))
};
}
#[macro_export]
macro_rules! require_capture {
($condition:expr, $($arg:tt)+) => {
if !$condition {
$crate::decline_capture!($($arg)+);
}
};
}
#[macro_export]
macro_rules! deny {
($($arg:tt)+) => {
return $crate::KernelMatch::unsupported(format!($($arg)+))
};
}
#[macro_export]
macro_rules! require {
($condition:expr, $($arg:tt)+) => {
if !$condition {
$crate::deny!($($arg)+);
}
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ViewOutput {
pub input_index: usize,
pub shape: Vec<usize>,
pub strides: Vec<i64>,
pub byte_offset: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KernelSizedOutput {
pub shape: Vec<usize>,
pub dtype: DataType,
pub bytes: Vec<u8>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KernelSizedOutputPolicy {
HostOwned,
DeviceWorkspace,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KernelSizedOutputMetadata {
pub shape: Vec<usize>,
pub dtype: DataType,
}
pub enum KernelInput<'a> {
Tensor(TensorView<'a>),
Weight(&'a WeightHandle),
}
impl<'a> KernelInput<'a> {
pub fn tensor(&self) -> Option<&TensorView<'a>> {
match self {
Self::Tensor(view) => Some(view),
Self::Weight(_) => None,
}
}
pub fn weight(&self) -> Option<&WeightHandle> {
match self {
Self::Tensor(_) => None,
Self::Weight(weight) => Some(weight),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct TensorMetadata<'a> {
pub dtype: onnx_runtime_ir::DataType,
pub shape: &'a [usize],
pub present: bool,
}
impl<'a> TensorMetadata<'a> {
pub const fn new(dtype: onnx_runtime_ir::DataType, shape: &'a [usize], present: bool) -> Self {
Self {
dtype,
shape,
present,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WorkspaceLifetime {
StepScoped,
SessionPersistent,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WorkspaceRequirement {
pub bytes: u64,
pub alignment: usize,
pub lifetime: WorkspaceLifetime,
pub role: MemoryRole,
}
impl WorkspaceRequirement {
pub const NONE: Self = Self {
bytes: 0,
alignment: 1,
lifetime: WorkspaceLifetime::StepScoped,
role: MemoryRole::Workspace { step_scoped: true },
};
}
#[derive(Clone, Copy, Debug)]
pub struct WorkspaceView {
ptr: DevicePtrMut,
bytes: usize,
}
impl WorkspaceView {
pub const fn new(ptr: DevicePtrMut, bytes: usize) -> Self {
Self { ptr, bytes }
}
pub const fn ptr(self) -> DevicePtrMut {
self.ptr
}
pub const fn bytes(self) -> usize {
self.bytes
}
}
pub trait KernelType {
fn as_any(&self) -> &dyn Any;
}
impl<T: Any> KernelType for T {
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Clone)]
pub struct DeviceGraphResource {
identity: usize,
owner: Arc<dyn Any + Send + Sync>,
}
impl DeviceGraphResource {
pub fn new<T>(identity: usize, owner: Arc<T>) -> Self
where
T: Any + Send + Sync,
{
Self { identity, owner }
}
pub fn identity(&self) -> usize {
self.identity
}
pub fn owner(&self) -> &Arc<dyn Any + Send + Sync> {
&self.owner
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BlockQuantizedMoeTraffic {
pub uploaded_whole_bank_bytes: u64,
pub committed_whole_bank_bytes: u64,
pub logical_route_demand_bytes: u64,
pub unique_selected_expert_bytes: u64,
pub physical_dram_bytes: Option<u64>,
pub page_ins: u64,
pub byte_hit_rate: Option<f64>,
}
pub trait Kernel: Send + KernelType {
fn set_constant_inputs(&mut self, constant_inputs: &[bool]) {
let _ = constant_inputs;
}
fn prepare_constant_inputs(
&mut self,
constants: &[Option<KernelConstantInput<'_>>],
provider: &dyn ExecutionProvider,
) -> Result<()> {
let _ = (constants, provider);
Ok(())
}
fn shareable_constant_state(&self) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
None
}
fn adopt_shareable_constant_state(
&mut self,
state: Arc<dyn std::any::Any + Send + Sync>,
) -> Result<bool> {
let _ = state;
Ok(false)
}
fn constant_input_override(&self, input_idx: usize) -> Option<TensorView<'_>> {
let _ = input_idx;
None
}
fn device_graph_resources(&self) -> Vec<DeviceGraphResource> {
Vec::new()
}
fn arm_block_quantized_moe_traffic(&mut self, request_id: u32) -> Result<bool> {
let _ = request_id;
Ok(false)
}
fn reset_block_quantized_moe_traffic(&mut self) -> Result<()> {
Ok(())
}
fn snapshot_block_quantized_moe_traffic(&self) -> Result<Option<BlockQuantizedMoeTraffic>> {
Ok(None)
}
fn disarm_block_quantized_moe_traffic(&mut self) -> Result<()> {
Ok(())
}
fn set_capture_seq_independent(&mut self, seq_independent: bool) {
let _ = seq_independent;
}
fn execute(&self, inputs: &[TensorView], outputs: &mut [TensorMut]) -> Result<()>;
fn has_kernel_sized_outputs(&self) -> bool {
false
}
fn kernel_sized_output_policy(&self) -> KernelSizedOutputPolicy {
KernelSizedOutputPolicy::HostOwned
}
fn execute_kernel_sized(
&self,
inputs: &[TensorView],
requested_outputs: &[bool],
) -> Result<Vec<Option<KernelSizedOutput>>> {
let _ = (inputs, requested_outputs);
Err(crate::EpError::KernelFailed(
"kernel does not implement kernel-sized outputs".into(),
))
}
fn prepare_kernel_sized_device(
&self,
inputs: &[TensorView],
requested_outputs: &[bool],
workspace: Option<WorkspaceView>,
) -> Result<Vec<Option<KernelSizedOutputMetadata>>> {
let _ = (inputs, requested_outputs, workspace);
Err(crate::EpError::KernelFailed(
"kernel does not implement device-workspace kernel-sized outputs".into(),
))
}
fn materialize_kernel_sized_device(
&self,
inputs: &[TensorView],
outputs: &mut [TensorMut],
workspace: Option<WorkspaceView>,
) -> Result<()> {
let _ = (inputs, outputs, workspace);
Err(crate::EpError::KernelFailed(
"kernel does not implement device-workspace materialization".into(),
))
}
fn workspace_requirement(&self, inputs: &[TensorMetadata<'_>]) -> Result<WorkspaceRequirement> {
let _ = inputs;
Ok(WorkspaceRequirement::NONE)
}
fn workspace_requirement_for_execution(
&self,
inputs: &[TensorView],
metadata: &[TensorMetadata<'_>],
) -> Result<WorkspaceRequirement> {
let _ = inputs;
self.workspace_requirement(metadata)
}
fn execute_with_workspace(
&self,
inputs: &[TensorView],
outputs: &mut [TensorMut],
workspace: Option<WorkspaceView>,
) -> Result<()> {
let _ = workspace;
self.execute(inputs, outputs)
}
fn execute_with_inputs(
&self,
inputs: &[KernelInput<'_>],
outputs: &mut [TensorMut],
) -> Result<()> {
let views = inputs
.iter()
.map(|input| {
input.tensor().copied().ok_or_else(|| {
crate::EpError::KernelFailed(
"kernel received a lazy WeightHandle without implementing \
execute_with_inputs"
.into(),
)
})
})
.collect::<Result<Vec<_>>>()?;
self.execute(&views, outputs)
}
fn estimated_flops(&self) -> Option<u64> {
None
}
fn view_outputs(
&self,
inputs: &[TensorView],
output_shapes: &[Vec<usize>],
num_outputs: usize,
) -> Option<Vec<ViewOutput>> {
let _ = (inputs, output_shapes, num_outputs);
None
}
fn may_produce_views(&self) -> bool {
false
}
fn can_run_in_place(&self, input_index: usize) -> bool {
let _ = input_index;
false
}
fn supports_strided_input(&self, input_idx: usize) -> bool {
let _ = input_idx;
false
}
fn preferred_output_layout(&self) -> Option<TensorLayout> {
None
}
fn capture_support(&self) -> CaptureSupport {
CaptureSupport::Supported
}
fn cuda_graph_compatible(&self) -> bool {
self.capture_support().is_supported()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use super::*;
use crate::{DeviceId, DevicePtr};
#[test]
fn cost_zero_and_total() {
assert_eq!(Cost::ZERO.total_us(), 0.0);
let c = Cost::new(10.0, 5.0, 2.0);
assert_eq!(c.total_us(), 17.0);
assert_eq!(c.bytes_moved, 0);
assert_eq!(c.launch_us, 0.0);
}
#[test]
fn cost_builders_are_additive() {
let c = Cost::new(10.0, 5.0, 2.0)
.with_launch_us(3.0)
.with_bytes_moved(4096);
assert_eq!(c.total_us(), 20.0);
assert_eq!(c.bytes_moved, 4096);
}
#[test]
fn kernel_match_reports_support() {
let supported = KernelMatch::Supported {
cost: Cost::new(1.0, 0.0, 0.0),
required_input_layouts: None,
output_layouts: vec![],
};
assert!(supported.is_supported());
assert_eq!(supported.reason(), None);
let unsupported = KernelMatch::unsupported("test EP supports no ops");
assert!(!unsupported.is_supported());
assert_eq!(unsupported.reason(), Some("test EP supports no ops"));
}
fn require_positive(value: i32) -> KernelMatch {
require!(value > 0, "value must be positive, got {value}");
KernelMatch::Supported {
cost: Cost::ZERO,
required_input_layouts: None,
output_layouts: vec![],
}
}
#[test]
fn require_macro_carries_formatted_decline_reason() {
let rejected = require_positive(-2);
assert_eq!(rejected.reason(), Some("value must be positive, got -2"));
assert!(require_positive(2).is_supported());
}
#[test]
fn kernel_variant_selection_carries_winner_and_reason() {
let selection =
KernelVariantSelection::new("gemv", "M==1 decode uses the packed int4 GEMV path");
assert_eq!(selection.variant, "gemv");
assert_eq!(
selection.reason,
"M==1 decode uses the packed int4 GEMV path"
);
}
#[test]
fn record_kernel_variant_without_active_span_skips_formatting() {
let formatted = AtomicBool::new(false);
record_kernel_variant!("gemv", "{}", {
formatted.store(true, Ordering::Relaxed);
"M==1 decode"
});
assert!(!formatted.load(Ordering::Relaxed));
}
#[test]
fn record_kernel_variant_annotates_active_span() {
use onnx_runtime_tracer::TraceContext;
let (trace, events) = TraceContext::in_memory();
{
let _span = trace.span("MatMulNBits", "op");
record_kernel_variant!(
"gemv_f16",
"K={}, N={} chose the vectorized GEMV",
4864,
896
);
}
let events = events.events();
assert_eq!(events.len(), 1);
let args = events[0].args.as_ref().unwrap();
assert_eq!(args[ARG_KERNEL_VARIANT], "gemv_f16");
assert!(
args[ARG_KERNEL_VARIANT_REASON]
.as_str()
.unwrap()
.contains("K=4864, N=896")
);
}
#[test]
fn record_kernel_variant_stage_namespaces_multiple_subdecisions() {
use onnx_runtime_tracer::TraceContext;
let (trace, events) = TraceContext::in_memory();
{
let _span = trace.span("GroupQueryAttention", "op");
record_kernel_variant_stage!("prep", "gqa_prep_fused", "Sq==1, even head_dim");
record_kernel_variant!("attention_gqa_decode_fp16_splitk", "split-K decode");
}
let events = events.events();
assert_eq!(events.len(), 1);
let args = events[0].args.as_ref().unwrap();
assert_eq!(args[ARG_KERNEL_VARIANT], "attention_gqa_decode_fp16_splitk");
assert_eq!(args["kernel_variant.prep"], "gqa_prep_fused");
assert!(
args["kernel_variant_reason.prep"]
.as_str()
.unwrap()
.contains("even head_dim")
);
}
struct DecliningCaptureKernel;
impl Kernel for DecliningCaptureKernel {
fn execute(&self, _inputs: &[TensorView], _outputs: &mut [TensorMut]) -> Result<()> {
Ok(())
}
fn capture_support(&self) -> CaptureSupport {
CaptureSupport::unsupported("per-call workspace allocation is not capturable")
}
}
#[test]
fn cuda_graph_compatible_shim_matches_capture_support() {
let supported = LegacyKernel {
called: Arc::new(AtomicBool::new(false)),
};
assert_eq!(
supported.cuda_graph_compatible(),
supported.capture_support().is_supported()
);
let unsupported = DecliningCaptureKernel;
assert_eq!(
unsupported.cuda_graph_compatible(),
unsupported.capture_support().is_supported()
);
assert_eq!(
unsupported.capture_support().reason(),
Some("per-call workspace allocation is not capturable")
);
}
struct LegacyKernel {
called: Arc<AtomicBool>,
}
impl Kernel for LegacyKernel {
fn execute(&self, inputs: &[TensorView], _outputs: &mut [TensorMut]) -> Result<()> {
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].shape, &[4]);
self.called.store(true, Ordering::Relaxed);
Ok(())
}
}
#[test]
fn legacy_kernel_adapter_receives_the_resident_tensor_path() {
let called = Arc::new(AtomicBool::new(false));
let kernel = LegacyKernel {
called: Arc::clone(&called),
};
let bytes = [1u8, 2, 3, 4];
let shape = [4usize];
let strides = [1i64];
let inputs = [KernelInput::Tensor(TensorView::new(
DevicePtr(bytes.as_ptr().cast()),
onnx_runtime_ir::DataType::Uint8,
&shape,
&strides,
DeviceId::cpu(),
))];
kernel.execute_with_inputs(&inputs, &mut []).unwrap();
assert!(called.load(Ordering::Relaxed));
}
}
#[cfg(test)]
mod trace_standard_tests {
use super::*;
use onnx_runtime_tracer::{TraceContext, TraceVerbosity, set_global_context};
#[test]
fn worker_span_is_gated_on_full_verbosity() {
set_global_context(None);
assert!(
kernel_worker_span("probe").is_none(),
"a worker span opened with no ambient context installed"
);
let (context, _collector) = TraceContext::in_memory();
set_global_context(Some(context.with_verbosity(TraceVerbosity::Ops)));
assert!(
kernel_worker_span("probe").is_none(),
"a worker span opened at Ops verbosity, which would put the \
per-worker cost on every ordinary traced run"
);
let (context, collector) = TraceContext::in_memory();
set_global_context(Some(context.with_verbosity(TraceVerbosity::Full)));
{
let span = kernel_worker_span("probe");
assert!(span.is_some(), "no worker span at Full verbosity");
}
let events = collector.events();
assert_eq!(events.len(), 1, "expected exactly one recorded worker span");
assert_eq!(events[0].cat, CAT_KERNEL_WORKER);
assert_eq!(events[0].name, "probe");
set_global_context(None);
}
}