use crate::raw::bindings::VkComponentTypeKHR;
use crate::safe::{PhysicalDevice, SubgroupFeatureFlags};
use kiss_vulkan_vocab::{
Arith, ComponentType, CoopMatrix, CoopShape, CoopVecCombo, CoopVector, OpClasses, Subgroup,
VulkanTarget,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeviceCapabilities {
pub default_subgroup: u32,
pub subgroup_range: Option<(u32, u32)>,
pub ops: OpClasses,
pub arith: Arith,
pub coop: Vec<CoopShape>,
pub coopvec: Vec<CoopVecCombo>,
}
impl DeviceCapabilities {
pub fn of(physical: &PhysicalDevice) -> Option<Self> {
let sg = physical.subgroup_properties()?;
let mut ops = OpClasses::NONE;
let supported = sg.supported_operations;
for (flag, class) in [
(SubgroupFeatureFlags::BASIC, OpClasses::BASIC),
(SubgroupFeatureFlags::VOTE, OpClasses::VOTE),
(SubgroupFeatureFlags::ARITHMETIC, OpClasses::ARITHMETIC),
(SubgroupFeatureFlags::BALLOT, OpClasses::BALLOT),
(SubgroupFeatureFlags::SHUFFLE, OpClasses::SHUFFLE),
(
SubgroupFeatureFlags::SHUFFLE_RELATIVE,
OpClasses::SHUFFLE_RELATIVE,
),
(SubgroupFeatureFlags::CLUSTERED, OpClasses::CLUSTERED),
(SubgroupFeatureFlags::QUAD, OpClasses::QUAD),
(SubgroupFeatureFlags::ROTATE, OpClasses::ROTATE),
(
SubgroupFeatureFlags::ROTATE_CLUSTERED,
OpClasses::ROTATE_CLUSTERED,
),
(SubgroupFeatureFlags::PARTITIONED_NV, OpClasses::PARTITIONED),
] {
if supported.contains(flag) {
ops |= class;
}
}
let mut arith = Arith::NONE;
if let Some(f) = physical.shader_arithmetic_features() {
if f.shader_float16 {
arith |= Arith::FLOAT16;
}
if f.shader_int8 {
arith |= Arith::INT8;
}
if f.storage_buffer_16bit {
arith |= Arith::STORAGE16;
}
if f.storage_buffer_8bit {
arith |= Arith::STORAGE8;
}
}
if physical
.shader_integer_dot_product_properties()
.is_some_and(|d| d.has_any_int8_acceleration())
{
arith |= Arith::DOT8;
}
let mut coop: Vec<CoopShape> = physical
.cooperative_matrix_properties()
.ok()?
.iter()
.map(|p| CoopShape {
m: p.m_size(),
n: p.n_size(),
k: p.k_size(),
a: component(p.a_type_raw() as u32),
b: component(p.b_type_raw() as u32),
c: component(p.c_type_raw() as u32),
result: component(p.result_type_raw() as u32),
saturating: p.saturating_accumulation(),
})
.collect();
coop.sort();
coop.dedup();
let mut coopvec: Vec<CoopVecCombo> = physical
.cooperative_vector_properties()
.ok()?
.iter()
.map(|p| CoopVecCombo {
input: component(p.input_type_raw() as u32),
input_interpretation: component(p.input_interpretation_raw() as u32),
matrix_interpretation: component(p.matrix_interpretation_raw() as u32),
bias_interpretation: component(p.bias_interpretation_raw() as u32),
result: component(p.result_type_raw() as u32),
transpose: p.transpose(),
})
.collect();
coopvec.sort();
coopvec.dedup();
Some(Self {
default_subgroup: sg.subgroup_size,
subgroup_range: sg
.size_control
.map(|s| (s.min_subgroup_size, s.max_subgroup_size)),
ops,
arith,
coop,
coopvec,
})
}
pub fn admissible_subgroups(&self) -> Vec<Subgroup> {
let mut out = vec![Subgroup::Dynamic];
match self.subgroup_range {
Some((min, max)) => {
let mut w = min.max(1).next_power_of_two();
while w <= max {
out.push(Subgroup::Fixed(w));
match w.checked_mul(2) {
Some(next) => w = next,
None => break,
}
}
}
None => out.push(Subgroup::Fixed(self.default_subgroup)),
}
out
}
pub fn target_for(&self, subgroup: Subgroup) -> VulkanTarget {
VulkanTarget {
subgroup,
ops: self.ops,
arith: self.arith,
coop: CoopMatrix::from_shapes(self.coop.clone()),
coopvec: CoopVector::from_combos(self.coopvec.clone()),
}
}
pub fn admits(&self, target: &VulkanTarget) -> bool {
let width_ok = match target.subgroup {
Subgroup::Dynamic => true,
Subgroup::Fixed(w) => self.admissible_subgroups().contains(&Subgroup::Fixed(w)),
};
let coop_ok = match &target.coop {
CoopMatrix::None => true,
CoopMatrix::Shapes(s) => s.iter().all(|x| self.coop.contains(x)),
CoopMatrix::Digest(_) => {
matches!(
CoopMatrix::from_shapes(self.coop.clone()),
CoopMatrix::Digest(d) if CoopMatrix::Digest(d) == target.coop
)
}
};
width_ok && self.ops.contains(target.ops) && self.arith.contains(target.arith) && coop_ok
}
}
fn component(raw: u32) -> ComponentType {
const BFLOAT16: u32 = VkComponentTypeKHR::COMPONENT_TYPE_BFLOAT16_KHR as u32;
const F8E4M3: u32 = VkComponentTypeKHR::COMPONENT_TYPE_FLOAT8_E4M3_EXT as u32;
const F8E5M2: u32 = VkComponentTypeKHR::COMPONENT_TYPE_FLOAT8_E5M2_EXT as u32;
const S8_PACKED: u32 = VkComponentTypeKHR::COMPONENT_TYPE_SINT8_PACKED_NV as u32;
const U8_PACKED: u32 = VkComponentTypeKHR::COMPONENT_TYPE_UINT8_PACKED_NV as u32;
match raw {
0 => ComponentType::F16,
1 => ComponentType::F32,
2 => ComponentType::F64,
3 => ComponentType::S8,
4 => ComponentType::S16,
5 => ComponentType::S32,
6 => ComponentType::S64,
7 => ComponentType::U8,
8 => ComponentType::U16,
9 => ComponentType::U32,
10 => ComponentType::U64,
BFLOAT16 => ComponentType::BF16,
F8E4M3 => ComponentType::F8E4M3FN,
F8E5M2 => ComponentType::F8E5M2,
S8_PACKED => ComponentType::S8Packed,
U8_PACKED => ComponentType::U8Packed,
n => ComponentType::Other(n),
}
}
#[cfg(test)]
mod component_tests {
use super::*;
#[test]
fn bfloat16_derives_from_the_raw_device_value() {
assert_eq!(
component(VkComponentTypeKHR::COMPONENT_TYPE_BFLOAT16_KHR as u32),
ComponentType::BF16,
"a device reporting VK_COMPONENT_TYPE_BFLOAT16_KHR must derive BF16, \
not Other — otherwise bf16 is spellable but not derivable"
);
}
#[test]
fn every_base_component_type_maps_to_its_documented_variant() {
let expected = [
(0, ComponentType::F16),
(1, ComponentType::F32),
(2, ComponentType::F64),
(3, ComponentType::S8),
(4, ComponentType::S16),
(5, ComponentType::S32),
(6, ComponentType::S64),
(7, ComponentType::U8),
(8, ComponentType::U16),
(9, ComponentType::U32),
(10, ComponentType::U64),
];
for (raw, want) in expected {
assert_eq!(component(raw), want, "VkComponentTypeKHR value {raw}");
}
}
#[test]
fn packed_types_derive_to_their_named_variants() {
for (raw, want, spelling) in [
(
VkComponentTypeKHR::COMPONENT_TYPE_SINT8_PACKED_NV as u32,
ComponentType::S8Packed,
"i8packed",
),
(
VkComponentTypeKHR::COMPONENT_TYPE_UINT8_PACKED_NV as u32,
ComponentType::U8Packed,
"u8packed",
),
] {
assert_eq!(
component(raw),
want,
concat!(
"VkComponentTypeKHR {} must derive {:?}, not Other — ",
"an RTX 4070 reports this value in its cooperative-vector ",
"combinations, so `Other` here is a wrong token on real hardware"
),
raw,
want
);
let token = VulkanTarget {
subgroup: Subgroup::Fixed(32),
ops: kiss_vulkan_vocab::OpClasses::NONE,
arith: kiss_vulkan_vocab::Arith::NONE,
coop: CoopMatrix::None,
coopvec: CoopVector::from_combos(vec![kiss_vulkan_vocab::CoopVecCombo {
input: ComponentType::U32,
input_interpretation: want,
matrix_interpretation: ComponentType::S8,
bias_interpretation: ComponentType::S32,
result: ComponentType::S32,
transpose: false,
}]),
}
.to_token();
assert!(
token.contains(spelling),
"the derived token must spell {spelling}: {token}"
);
assert!(
!token.contains("x1000491"),
"a named packed type must not fall through to the x<n> escape: {token}"
);
}
}
#[test]
fn fp8_derives_from_the_raw_device_value_as_the_finite_variant() {
for (raw, want, spelling) in [
(
VkComponentTypeKHR::COMPONENT_TYPE_FLOAT8_E4M3_EXT as u32,
ComponentType::F8E4M3FN,
"f8e4m3fn",
),
(
VkComponentTypeKHR::COMPONENT_TYPE_FLOAT8_E5M2_EXT as u32,
ComponentType::F8E5M2,
"f8e5m2",
),
] {
assert_eq!(
component(raw),
want,
"VkComponentTypeKHR value {raw} must derive {want:?}, not Other — \
otherwise FP8 is spellable but not derivable, which is the exact \
defect bfloat16 had"
);
let token = VulkanTarget {
subgroup: Subgroup::Fixed(32),
ops: kiss_vulkan_vocab::OpClasses::NONE,
arith: kiss_vulkan_vocab::Arith::NONE,
coop: CoopMatrix::from_shapes(vec![kiss_vulkan_vocab::CoopShape {
m: 16,
n: 16,
k: 16,
a: want,
b: want,
c: want,
result: want,
saturating: false,
}]),
coopvec: CoopVector::None,
}
.to_token();
assert!(
token.contains(spelling),
"the derived token must spell {spelling}: {token}"
);
assert!(
!token.contains("fnuz"),
"a derived token must never carry a reserved `fnuz` spelling — \
KISS gives those no computation semantics: {token}"
);
}
}
#[test]
fn the_nv_fp8_names_alias_the_ext_enumerants() {
assert_eq!(
VkComponentTypeKHR::COMPONENT_TYPE_FLOAT8_E4M3_EXT as u32,
vulkane_raw_nv_e4m3(),
"VK_COMPONENT_TYPE_FLOAT_E4M3_NV must alias FLOAT8_E4M3_EXT"
);
assert_eq!(
VkComponentTypeKHR::COMPONENT_TYPE_FLOAT8_E5M2_EXT as u32,
vulkane_raw_nv_e5m2(),
"VK_COMPONENT_TYPE_FLOAT_E5M2_NV must alias FLOAT8_E5M2_EXT"
);
}
fn vulkane_raw_nv_e4m3() -> u32 {
crate::raw::bindings::COMPONENT_TYPE_FLOAT_E4M3_NV as u32
}
fn vulkane_raw_nv_e5m2() -> u32 {
crate::raw::bindings::COMPONENT_TYPE_FLOAT_E5M2_NV as u32
}
}