use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use trustformers_core::error::{CoreError, Result};
use trustformers_core::Tensor;
use trustformers_core::TrustformersError;
pub struct NNAPIModelConverter {
config: NNAPIConverterConfig,
operation_validator: OperationValidator,
device_optimizer: DeviceOptimizer,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NNAPIConverterConfig {
pub target_api_level: u32,
pub target_devices: Vec<NNAPITargetDevice>,
pub enable_partitioning: bool,
pub quantization: Option<NNAPIQuantizationConfig>,
pub optimization: NNAPIOptimizationConfig,
pub fallback_strategy: FallbackStrategy,
pub output_format: NNAPIFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NNAPITargetDevice {
CPU,
GPU,
HexagonDSP,
EdgeTPU,
MediaTekAPU,
SamsungNPU,
HiSiliconNPU,
GenericAccelerator,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NNAPIFormat {
Binary,
FlatBuffer,
TFLite,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FallbackStrategy {
Fail,
CPUFallback,
Partition,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NNAPIQuantizationConfig {
pub scheme: NNAPIQuantizationScheme,
pub calibration: CalibrationConfig,
pub per_channel: bool,
pub symmetric: bool,
pub quantize_io: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NNAPIQuantizationScheme {
Dynamic,
FullInteger,
IntegerWithFloat,
Float16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalibrationConfig {
pub num_samples: usize,
pub method: CalibrationMethod,
pub dataset_path: Option<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum CalibrationMethod {
MinMax,
Entropy,
Percentile(f32),
MSE,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NNAPIOptimizationConfig {
pub enable_fusion: bool,
pub optimize_layout: bool,
pub constant_folding: bool,
pub dead_code_elimination: bool,
pub device_optimizations: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NNAPIModel {
pub metadata: NNAPIModelMetadata,
pub operands: Vec<NNAPIOperand>,
pub operations: Vec<NNAPIOperation>,
pub inputs: Vec<u32>,
pub outputs: Vec<u32>,
pub constants: HashMap<u32, Vec<u8>>,
pub execution_preference: ExecutionPreference,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NNAPIModelMetadata {
pub name: String,
pub version: String,
pub min_api_level: u32,
pub supported_devices: Vec<NNAPITargetDevice>,
pub model_hash: String,
pub performance_hints: PerformanceHints,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceHints {
pub expected_latency_ms: Option<f32>,
pub expected_power_mw: Option<f32>,
pub memory_usage_mb: Option<f32>,
pub recommended_batch_size: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExecutionPreference {
LowLatency,
SustainedSpeed,
LowPower,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NNAPIOperand {
pub index: u32,
pub dtype: NNAPIDataType,
pub dimensions: Vec<u32>,
pub scale: Option<f32>,
pub zero_point: Option<i32>,
pub lifetime: OperandLifetime,
pub location: Option<DataLocation>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NNAPIDataType {
Float32,
Float16,
Int32,
UInt32,
Int8,
UInt8,
Bool,
TensorFloat32,
TensorFloat16,
TensorInt32,
TensorQuant8Asymm,
TensorQuant8Symm,
TensorQuant16Asymm,
TensorQuant16Symm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OperandLifetime {
TemporaryVariable,
ModelInput,
ModelOutput,
ConstantReference,
NoValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataLocation {
pub offset: usize,
pub length: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NNAPIOperation {
pub operation_type: NNAPIOperationType,
pub inputs: Vec<u32>,
pub outputs: Vec<u32>,
pub params: OperationParams,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NNAPIOperationType {
Add,
Sub,
Mul,
Div,
Conv2D,
DepthwiseConv2D,
FullyConnected,
AveragePool2D,
MaxPool2D,
L2Pool2D,
Relu,
Relu1,
Relu6,
Sigmoid,
Tanh,
BatchNorm,
LocalResponseNorm,
Softmax,
Reshape,
Transpose,
Concat,
Split,
Squeeze,
StridedSlice,
Pad,
Quantize,
Dequantize,
LSTM,
RNN,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationParams {
pub params: HashMap<String, OperationParam>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OperationParam {
Int(i32),
Float(f32),
Bool(bool),
IntArray(Vec<i32>),
FloatArray(Vec<f32>),
Operand(u32),
}
struct OperationValidator {
supported_ops: HashMap<u32, HashSet<NNAPIOperationType>>,
device_capabilities: HashMap<NNAPITargetDevice, DeviceCapabilities>,
}
#[derive(Debug, Clone)]
struct DeviceCapabilities {
supported_operations: HashSet<NNAPIOperationType>,
supported_data_types: HashSet<NNAPIDataType>,
max_operand_size: usize,
supports_relaxed_fp32: bool,
supports_low_power: bool,
}
struct DeviceOptimizer {
device_profiles: HashMap<NNAPITargetDevice, DeviceProfile>,
}
#[derive(Debug, Clone)]
struct DeviceProfile {
compute_throughput: f32,
memory_bandwidth: f32,
power_efficiency: f32,
preferred_layouts: Vec<TensorLayout>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TensorLayout {
NHWC,
NCHW,
NC,
}
impl NNAPIModelConverter {
pub fn new(config: NNAPIConverterConfig) -> Self {
let operation_validator = OperationValidator::new(config.target_api_level);
let device_optimizer = DeviceOptimizer::new(&config.target_devices);
Self {
config,
operation_validator,
device_optimizer,
}
}
pub fn convert(&self, model_path: &Path, output_path: &Path) -> Result<ConversionResult> {
let trustformers_model = self.load_trustformers_model(model_path)?;
self.validate_model_compatibility(&trustformers_model)?;
let mut nnapi_model = self.convert_to_nnapi(trustformers_model)?;
if self.config.optimization.enable_fusion {
self.apply_operator_fusion(&mut nnapi_model)?;
}
if self.config.optimization.optimize_layout {
self.optimize_tensor_layout(&mut nnapi_model)?;
}
if self.config.optimization.constant_folding {
self.apply_constant_folding(&mut nnapi_model)?;
}
if let Some(ref quant_config) = self.config.quantization {
self.apply_quantization(&mut nnapi_model, quant_config)?;
}
let partitions = if self.config.enable_partitioning {
self.partition_model(&nnapi_model)?
} else {
vec![nnapi_model.clone()]
};
let output_info = self.write_nnapi_model(&partitions, output_path)?;
Ok(ConversionResult {
output_path: output_info.path,
model_size_mb: output_info.size_mb,
num_operations: nnapi_model.operations.len(),
num_partitions: partitions.len(),
supported_devices: nnapi_model.metadata.supported_devices.clone(),
optimization_report: self.generate_optimization_report(&nnapi_model),
compatibility_report: self.generate_compatibility_report(&nnapi_model),
})
}
fn load_trustformers_model(&self, path: &Path) -> Result<TrustformersModel> {
Ok(TrustformersModel {
weights: HashMap::new(),
graph: Vec::new(),
})
}
fn validate_model_compatibility(&self, model: &TrustformersModel) -> Result<()> {
for op in &model.graph {
let nnapi_op = self.map_operation_type(&op.op_type)?;
let supported = self.config.target_devices.iter().any(|device| {
self.operation_validator.is_supported(
*device,
nnapi_op.clone(),
self.config.target_api_level,
)
});
if !supported && self.config.fallback_strategy == FallbackStrategy::Fail {
return Err(TrustformersError::runtime_error(format!(
"Operation {} not supported on target devices",
op.op_type
))
.into());
}
}
Ok(())
}
fn convert_to_nnapi(&self, model: TrustformersModel) -> Result<NNAPIModel> {
let mut operands = Vec::new();
let mut operations = Vec::new();
let mut constants = HashMap::new();
let mut operand_index = 0u32;
let input_indices = self.create_input_operands(&mut operands, &mut operand_index);
for op in model.graph {
let (nnapi_op, output_indices) =
self.convert_operation(op, &mut operands, &mut operand_index, &mut constants)?;
operations.push(nnapi_op);
}
let output_indices = self.identify_output_operands(&operations);
Ok(NNAPIModel {
metadata: self.create_metadata(),
operands,
operations,
inputs: input_indices,
outputs: output_indices,
constants,
execution_preference: self.select_execution_preference(),
})
}
fn map_operation_type(&self, op_type: &str) -> Result<NNAPIOperationType> {
match op_type {
"Conv2d" => Ok(NNAPIOperationType::Conv2D),
"Linear" | "Dense" => Ok(NNAPIOperationType::FullyConnected),
"BatchNorm2d" => Ok(NNAPIOperationType::BatchNorm),
"ReLU" => Ok(NNAPIOperationType::Relu),
"MaxPool2d" => Ok(NNAPIOperationType::MaxPool2D),
"AvgPool2d" => Ok(NNAPIOperationType::AveragePool2D),
"Softmax" => Ok(NNAPIOperationType::Softmax),
"Add" => Ok(NNAPIOperationType::Add),
"Mul" => Ok(NNAPIOperationType::Mul),
"Reshape" => Ok(NNAPIOperationType::Reshape),
"Transpose" => Ok(NNAPIOperationType::Transpose),
"Concat" => Ok(NNAPIOperationType::Concat),
_ => {
if self.config.fallback_strategy == FallbackStrategy::Fail {
Err(
TrustformersError::runtime_error(format!("Unknown operation: {}", op_type))
.into(),
)
} else {
Ok(NNAPIOperationType::Custom(op_type.to_string()))
}
},
}
}
fn create_input_operands(
&self,
operands: &mut Vec<NNAPIOperand>,
operand_index: &mut u32,
) -> Vec<u32> {
let mut input_indices = Vec::new();
let input_operand = NNAPIOperand {
index: *operand_index,
dtype: NNAPIDataType::TensorFloat32,
dimensions: vec![1, 3, 224, 224],
scale: None,
zero_point: None,
lifetime: OperandLifetime::ModelInput,
location: None,
};
input_indices.push(*operand_index);
operands.push(input_operand);
*operand_index += 1;
input_indices
}
fn convert_operation(
&self,
op: Operation,
operands: &mut Vec<NNAPIOperand>,
operand_index: &mut u32,
constants: &mut HashMap<u32, Vec<u8>>,
) -> Result<(NNAPIOperation, Vec<u32>)> {
let operation_type = self.map_operation_type(&op.op_type)?;
let output_operand = NNAPIOperand {
index: *operand_index,
dtype: NNAPIDataType::TensorFloat32,
dimensions: vec![1, 64, 112, 112], scale: None,
zero_point: None,
lifetime: OperandLifetime::TemporaryVariable,
location: None,
};
let output_index = *operand_index;
operands.push(output_operand);
*operand_index += 1;
let params = self.convert_operation_params(op.params);
let nnapi_op = NNAPIOperation {
operation_type,
inputs: vec![0], outputs: vec![output_index],
params,
};
Ok((nnapi_op, vec![output_index]))
}
fn convert_operation_params(&self, params: HashMap<String, String>) -> OperationParams {
let mut converted = HashMap::new();
for (key, value) in params {
if let Ok(int_val) = value.parse::<i32>() {
converted.insert(key, OperationParam::Int(int_val));
} else if let Ok(float_val) = value.parse::<f32>() {
converted.insert(key, OperationParam::Float(float_val));
} else if value == "true" || value == "false" {
converted.insert(key, OperationParam::Bool(value == "true"));
}
}
OperationParams { params: converted }
}
fn identify_output_operands(&self, operations: &[NNAPIOperation]) -> Vec<u32> {
let mut all_outputs: HashSet<u32> = HashSet::new();
let mut all_inputs: HashSet<u32> = HashSet::new();
for op in operations {
for &output in &op.outputs {
all_outputs.insert(output);
}
for &input in &op.inputs {
all_inputs.insert(input);
}
}
all_outputs.difference(&all_inputs).cloned().collect()
}
fn create_metadata(&self) -> NNAPIModelMetadata {
NNAPIModelMetadata {
name: "TrustformersModel".to_string(),
version: "1.0.0".to_string(),
min_api_level: self.config.target_api_level,
supported_devices: self.config.target_devices.clone(),
model_hash: self.compute_model_hash(),
performance_hints: PerformanceHints {
expected_latency_ms: None,
expected_power_mw: None,
memory_usage_mb: None,
recommended_batch_size: Some(1),
},
}
}
fn compute_model_hash(&self) -> String {
"model_hash_placeholder".to_string()
}
fn select_execution_preference(&self) -> ExecutionPreference {
if self.config.target_devices.contains(&NNAPITargetDevice::CPU) {
ExecutionPreference::LowPower
} else if self.config.target_devices.contains(&NNAPITargetDevice::GPU) {
ExecutionPreference::SustainedSpeed
} else {
ExecutionPreference::LowLatency
}
}
fn apply_operator_fusion(&self, model: &mut NNAPIModel) -> Result<()> {
let mut i = 0;
while i < model.operations.len() - 2 {
if matches!(
model.operations[i].operation_type,
NNAPIOperationType::Conv2D
) && matches!(
model.operations[i + 1].operation_type,
NNAPIOperationType::BatchNorm
) && matches!(
model.operations[i + 2].operation_type,
NNAPIOperationType::Relu
) {
println!("Fusing Conv2D + BatchNorm + ReLU at index {}", i);
i += 3;
} else {
i += 1;
}
}
Ok(())
}
fn optimize_tensor_layout(&self, model: &mut NNAPIModel) -> Result<()> {
for device in &self.config.target_devices {
if let Some(profile) = self.device_optimizer.device_profiles.get(device) {
if profile.preferred_layouts.contains(&TensorLayout::NHWC) {
println!("Optimizing tensor layout for {:?}", device);
}
}
}
Ok(())
}
fn apply_constant_folding(&self, model: &mut NNAPIModel) -> Result<()> {
println!("Applying constant folding optimization");
Ok(())
}
fn apply_quantization(
&self,
model: &mut NNAPIModel,
config: &NNAPIQuantizationConfig,
) -> Result<()> {
match config.scheme {
NNAPIQuantizationScheme::Dynamic => {
self.apply_dynamic_quantization(model, config)?;
},
NNAPIQuantizationScheme::FullInteger => {
self.apply_full_integer_quantization(model, config)?;
},
NNAPIQuantizationScheme::Float16 => {
self.apply_float16_quantization(model)?;
},
_ => {},
}
Ok(())
}
fn apply_dynamic_quantization(
&self,
model: &mut NNAPIModel,
config: &NNAPIQuantizationConfig,
) -> Result<()> {
for operand in &mut model.operands {
if operand.lifetime == OperandLifetime::ConstantReference {
operand.dtype = NNAPIDataType::TensorQuant8Asymm;
operand.scale = Some(0.1);
operand.zero_point = Some(128);
}
}
Ok(())
}
fn apply_full_integer_quantization(
&self,
model: &mut NNAPIModel,
config: &NNAPIQuantizationConfig,
) -> Result<()> {
for operand in &mut model.operands {
if operand.dtype == NNAPIDataType::TensorFloat32 {
operand.dtype = NNAPIDataType::TensorQuant8Asymm;
operand.scale = Some(0.1);
operand.zero_point = Some(128);
}
}
Ok(())
}
fn apply_float16_quantization(&self, model: &mut NNAPIModel) -> Result<()> {
for operand in &mut model.operands {
if operand.dtype == NNAPIDataType::TensorFloat32 {
operand.dtype = NNAPIDataType::TensorFloat16;
}
}
Ok(())
}
fn partition_model(&self, model: &NNAPIModel) -> Result<Vec<NNAPIModel>> {
let mut partitions = Vec::new();
partitions.push(model.clone());
Ok(partitions)
}
fn write_nnapi_model(
&self,
partitions: &[NNAPIModel],
output_path: &Path,
) -> Result<OutputInfo> {
let model_data = self.serialize_model(partitions)?;
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)?;
}
let model_path = match self.config.output_format {
NNAPIFormat::Binary => output_path.with_extension("nnapi"),
NNAPIFormat::FlatBuffer => output_path.with_extension("fb"),
NNAPIFormat::TFLite => output_path.with_extension("tflite"),
};
std::fs::write(&model_path, &model_data)?;
let size_mb = model_data.len() as f32 / (1024.0 * 1024.0);
Ok(OutputInfo {
path: model_path,
size_mb,
})
}
fn serialize_model(&self, partitions: &[NNAPIModel]) -> Result<Vec<u8>> {
Ok(serde_json::to_vec(partitions)?)
}
fn generate_optimization_report(&self, model: &NNAPIModel) -> OptimizationReport {
OptimizationReport {
fusions_applied: 0, layout_optimized: self.config.optimization.optimize_layout,
constants_folded: 0, quantization_applied: self.config.quantization.is_some(),
model_partitioned: self.config.enable_partitioning,
}
}
fn generate_compatibility_report(&self, model: &NNAPIModel) -> CompatibilityReport {
CompatibilityReport {
min_api_level: model.metadata.min_api_level,
supported_devices: model.metadata.supported_devices.clone(),
unsupported_ops: vec![], warnings: vec![],
fallback_required: false,
}
}
}
impl OperationValidator {
fn new(api_level: u32) -> Self {
let mut validator = Self {
supported_ops: HashMap::new(),
device_capabilities: HashMap::new(),
};
validator.init_supported_ops(api_level);
validator.init_device_capabilities();
validator
}
fn init_supported_ops(&mut self, api_level: u32) {
let mut ops_27 = HashSet::new();
ops_27.insert(NNAPIOperationType::Add);
ops_27.insert(NNAPIOperationType::Conv2D);
ops_27.insert(NNAPIOperationType::FullyConnected);
ops_27.insert(NNAPIOperationType::Relu);
self.supported_ops.insert(27, ops_27);
let mut ops_28 = self.supported_ops[&27].clone();
ops_28.insert(NNAPIOperationType::BatchNorm);
ops_28.insert(NNAPIOperationType::Transpose);
self.supported_ops.insert(28, ops_28);
let mut ops_29 = self.supported_ops[&28].clone();
ops_29.insert(NNAPIOperationType::LSTM);
ops_29.insert(NNAPIOperationType::Quantize);
ops_29.insert(NNAPIOperationType::Dequantize);
self.supported_ops.insert(29, ops_29.clone());
let ops_30 = ops_29; self.supported_ops.insert(30, ops_30);
}
fn init_device_capabilities(&mut self) {
self.device_capabilities.insert(
NNAPITargetDevice::CPU,
DeviceCapabilities {
supported_operations: self.supported_ops[&30].clone(),
supported_data_types: vec![
NNAPIDataType::Float32,
NNAPIDataType::Int32,
NNAPIDataType::TensorFloat32,
NNAPIDataType::TensorQuant8Asymm,
]
.into_iter()
.collect(),
max_operand_size: 1024 * 1024 * 1024, supports_relaxed_fp32: true,
supports_low_power: true,
},
);
self.device_capabilities.insert(
NNAPITargetDevice::GPU,
DeviceCapabilities {
supported_operations: vec![
NNAPIOperationType::Conv2D,
NNAPIOperationType::FullyConnected,
NNAPIOperationType::Relu,
NNAPIOperationType::MaxPool2D,
]
.into_iter()
.collect(),
supported_data_types: vec![
NNAPIDataType::Float32,
NNAPIDataType::Float16,
NNAPIDataType::TensorFloat32,
NNAPIDataType::TensorFloat16,
]
.into_iter()
.collect(),
max_operand_size: 512 * 1024 * 1024, supports_relaxed_fp32: true,
supports_low_power: false,
},
);
}
fn is_supported(
&self,
device: NNAPITargetDevice,
op: NNAPIOperationType,
api_level: u32,
) -> bool {
if let Some(supported_ops) = self.supported_ops.get(&api_level.min(30)) {
if !supported_ops.contains(&op) {
return false;
}
}
if let Some(capabilities) = self.device_capabilities.get(&device) {
capabilities.supported_operations.contains(&op)
} else {
false
}
}
}
impl DeviceOptimizer {
fn new(target_devices: &[NNAPITargetDevice]) -> Self {
let mut optimizer = Self {
device_profiles: HashMap::new(),
};
optimizer.init_device_profiles();
optimizer
}
fn init_device_profiles(&mut self) {
self.device_profiles.insert(
NNAPITargetDevice::CPU,
DeviceProfile {
compute_throughput: 1.0,
memory_bandwidth: 10.0,
power_efficiency: 0.8,
preferred_layouts: vec![TensorLayout::NHWC],
},
);
self.device_profiles.insert(
NNAPITargetDevice::GPU,
DeviceProfile {
compute_throughput: 10.0,
memory_bandwidth: 50.0,
power_efficiency: 0.5,
preferred_layouts: vec![TensorLayout::NCHW],
},
);
self.device_profiles.insert(
NNAPITargetDevice::HexagonDSP,
DeviceProfile {
compute_throughput: 5.0,
memory_bandwidth: 20.0,
power_efficiency: 0.9,
preferred_layouts: vec![TensorLayout::NHWC],
},
);
}
}
struct TrustformersModel {
weights: HashMap<String, Tensor>,
graph: Vec<Operation>,
}
struct Operation {
name: String,
op_type: String,
inputs: Vec<String>,
outputs: Vec<String>,
params: HashMap<String, String>,
}
struct OutputInfo {
path: PathBuf,
size_mb: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversionResult {
pub output_path: PathBuf,
pub model_size_mb: f32,
pub num_operations: usize,
pub num_partitions: usize,
pub supported_devices: Vec<NNAPITargetDevice>,
pub optimization_report: OptimizationReport,
pub compatibility_report: CompatibilityReport,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationReport {
pub fusions_applied: usize,
pub layout_optimized: bool,
pub constants_folded: usize,
pub quantization_applied: bool,
pub model_partitioned: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompatibilityReport {
pub min_api_level: u32,
pub supported_devices: Vec<NNAPITargetDevice>,
pub unsupported_ops: Vec<String>,
pub warnings: Vec<String>,
pub fallback_required: bool,
}
impl Default for NNAPIConverterConfig {
fn default() -> Self {
Self {
target_api_level: 29, target_devices: vec![NNAPITargetDevice::CPU],
enable_partitioning: true,
quantization: None,
optimization: NNAPIOptimizationConfig {
enable_fusion: true,
optimize_layout: true,
constant_folding: true,
dead_code_elimination: true,
device_optimizations: true,
},
fallback_strategy: FallbackStrategy::CPUFallback,
output_format: NNAPIFormat::Binary,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_converter_creation() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
assert!(converter.operation_validator.supported_ops.contains_key(&29));
}
#[test]
fn test_quantization_config() {
let config = NNAPIQuantizationConfig {
scheme: NNAPIQuantizationScheme::FullInteger,
calibration: CalibrationConfig {
num_samples: 1000,
method: CalibrationMethod::MinMax,
dataset_path: None,
},
per_channel: true,
symmetric: false,
quantize_io: true,
};
assert_eq!(config.scheme, NNAPIQuantizationScheme::FullInteger);
assert!(config.per_channel);
}
#[test]
fn test_operation_mapping() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
assert_eq!(
converter.map_operation_type("Conv2d").expect("operation failed in test"),
NNAPIOperationType::Conv2D
);
assert_eq!(
converter.map_operation_type("ReLU").expect("operation failed in test"),
NNAPIOperationType::Relu
);
}
#[test]
fn test_default_converter_config() {
let config = NNAPIConverterConfig::default();
assert!(config.target_api_level >= 29);
assert!(!config.target_devices.is_empty());
assert!(matches!(
config.fallback_strategy,
FallbackStrategy::CPUFallback
));
}
#[test]
fn test_nnapi_target_device_variants() {
let devices = vec![
NNAPITargetDevice::CPU,
NNAPITargetDevice::GPU,
NNAPITargetDevice::HexagonDSP,
NNAPITargetDevice::EdgeTPU,
NNAPITargetDevice::MediaTekAPU,
NNAPITargetDevice::SamsungNPU,
NNAPITargetDevice::HiSiliconNPU,
NNAPITargetDevice::GenericAccelerator,
];
assert_eq!(devices.len(), 8);
}
#[test]
fn test_nnapi_format_variants() {
let formats = vec![
NNAPIFormat::Binary,
NNAPIFormat::FlatBuffer,
NNAPIFormat::TFLite,
];
assert_eq!(formats.len(), 3);
}
#[test]
fn test_fallback_strategy_variants() {
let strategies = vec![
FallbackStrategy::Fail,
FallbackStrategy::CPUFallback,
FallbackStrategy::Partition,
];
assert_eq!(strategies.len(), 3);
}
#[test]
fn test_quantization_scheme_variants() {
let schemes = vec![
NNAPIQuantizationScheme::Dynamic,
NNAPIQuantizationScheme::FullInteger,
NNAPIQuantizationScheme::IntegerWithFloat,
NNAPIQuantizationScheme::Float16,
];
assert_eq!(schemes.len(), 4);
}
#[test]
fn test_calibration_method_variants() {
let methods = vec![CalibrationMethod::MinMax];
assert_eq!(methods.len(), 1);
}
#[test]
fn test_quantization_config_per_channel() {
let config = NNAPIQuantizationConfig {
scheme: NNAPIQuantizationScheme::FullInteger,
calibration: CalibrationConfig {
num_samples: 500,
method: CalibrationMethod::MinMax,
dataset_path: None,
},
per_channel: false,
symmetric: true,
quantize_io: false,
};
assert!(!config.per_channel);
assert!(config.symmetric);
assert!(!config.quantize_io);
}
#[test]
fn test_calibration_config_with_path() {
let config = CalibrationConfig {
num_samples: 2000,
method: CalibrationMethod::MinMax,
dataset_path: Some(PathBuf::from("/tmp/calibration_data")),
};
assert!(config.dataset_path.is_some());
assert_eq!(config.num_samples, 2000);
}
#[test]
fn test_operation_mapping_matmul() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
let result = converter.map_operation_type("MatMul");
assert!(result.is_ok());
}
#[test]
fn test_operation_mapping_unknown() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
let result = converter.map_operation_type("UnknownOp123");
let _ = result; }
#[test]
fn test_operation_mapping_batch_norm() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
let result = converter.map_operation_type("BatchNorm");
assert!(result.is_ok());
}
#[test]
fn test_converter_supported_ops_not_empty() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
assert!(!converter.operation_validator.supported_ops.is_empty());
}
#[test]
fn test_operation_mapping_pooling() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
let result = converter.map_operation_type("MaxPool2d");
assert!(result.is_ok());
}
#[test]
fn test_operation_mapping_softmax() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
let result = converter.map_operation_type("Softmax");
assert!(result.is_ok());
}
#[test]
fn test_operation_mapping_add() {
let config = NNAPIConverterConfig::default();
let converter = NNAPIModelConverter::new(config);
let result = converter.map_operation_type("Add");
assert!(result.is_ok());
}
#[test]
fn test_converter_with_quantization() {
let mut config = NNAPIConverterConfig::default();
config.quantization = Some(NNAPIQuantizationConfig {
scheme: NNAPIQuantizationScheme::FullInteger,
calibration: CalibrationConfig {
num_samples: 100,
method: CalibrationMethod::MinMax,
dataset_path: None,
},
per_channel: true,
symmetric: true,
quantize_io: true,
});
let converter = NNAPIModelConverter::new(config);
assert!(!converter.operation_validator.supported_ops.is_empty());
}
#[test]
fn test_converter_with_partitioning() {
let mut config = NNAPIConverterConfig::default();
config.enable_partitioning = true;
let converter = NNAPIModelConverter::new(config);
assert!(!converter.operation_validator.supported_ops.is_empty());
}
#[test]
fn test_nnapi_target_device_equality() {
assert_eq!(NNAPITargetDevice::CPU, NNAPITargetDevice::CPU);
assert_ne!(NNAPITargetDevice::CPU, NNAPITargetDevice::GPU);
}
#[test]
fn test_nnapi_format_equality() {
assert_eq!(NNAPIFormat::Binary, NNAPIFormat::Binary);
assert_ne!(NNAPIFormat::Binary, NNAPIFormat::TFLite);
}
}