use super::super::data::NamModelData;
#[derive(Debug, Clone, PartialEq)]
pub enum A2TopologyResult {
KnownFastPath(u8),
Dynamic,
}
pub fn is_a2_shape(data: &NamModelData) -> Option<A2TopologyResult> {
use crate::models::a2::{
A2_DILATIONS, A2_KERNEL_SIZES, A2_LEAKY_SLOPE, A2_NUM_LAYERS, A2_VALID_CHANNELS,
};
if data.architecture != "WaveNet" {
return None;
}
for l in &data.config.layers {
if l.activation.as_deref() == Some("Tanh") {
return None;
}
}
let layers = &data.config.layers;
if layers.is_empty() {
return None;
}
if layers.len() > 1 {
let mut has_a2_feature = false;
let mut any_layer_raw = false;
for l in layers.iter() {
if let Some(ref raw) = l.layer_raw {
any_layer_raw = true;
if raw
.get("head1x1")
.and_then(|h| h.get("active"))
.and_then(|a| a.as_bool())
.unwrap_or(false)
{
has_a2_feature = true;
break;
}
for key in &[
"conv_pre_film",
"conv_post_film",
"input_mixin_pre_film",
"input_mixin_post_film",
"activation_pre_film",
"activation_post_film",
"layer1x1_post_film",
"head1x1_post_film",
] {
if raw
.get(key)
.and_then(|v| v.get("active"))
.and_then(|a| a.as_bool())
.unwrap_or(false)
{
has_a2_feature = true;
break;
}
}
if let Some(act) = raw.get("activation")
&& (act.is_array() || act.is_object())
{
has_a2_feature = true;
break;
}
}
}
if any_layer_raw && !has_a2_feature {
if data.config.condition_dsp.is_some() {
has_a2_feature = true;
}
}
if any_layer_raw && !has_a2_feature {
return None;
}
}
if let Some(ref head) = data.config.head
&& !head.is_null()
{
return None;
}
data.config.head_scale.as_ref()?;
let l0 = &layers[0];
let ks = match l0.kernel_sizes.as_deref() {
Some(s) => s.to_vec(),
None => {
if l0.kernel_size.is_some() && l0.dilations.is_some() {
return Some(A2TopologyResult::Dynamic);
}
return None;
}
};
if ks.len() != A2_NUM_LAYERS || ks.iter().zip(A2_KERNEL_SIZES.iter()).any(|(a, b)| *a != *b) {
return Some(A2TopologyResult::Dynamic);
}
let dils = l0.dilations.as_deref()?;
if dils.len() != A2_NUM_LAYERS || dils.iter().zip(A2_DILATIONS.iter()).any(|(a, b)| *a != *b) {
return Some(A2TopologyResult::Dynamic);
}
if data.config.in_channels.unwrap_or(1) != 1 {
return Some(A2TopologyResult::Dynamic);
}
if l0.input_size != Some(1) {
return Some(A2TopologyResult::Dynamic);
}
if l0.condition_size != Some(1) {
return Some(A2TopologyResult::Dynamic);
}
let ch = match l0.channels {
Some(c) => c as u8,
None => return Some(A2TopologyResult::Dynamic),
};
let bn = l0.bottleneck.unwrap_or(0);
if ch as usize != bn {
return Some(A2TopologyResult::Dynamic);
}
if !A2_VALID_CHANNELS.contains(&ch) {
return Some(A2TopologyResult::Dynamic);
}
if let Some(ref raw) = l0.layer_raw {
if !check_activations_are_leaky_relu(raw, A2_NUM_LAYERS, A2_LEAKY_SLOPE as f64) {
return Some(A2TopologyResult::Dynamic);
}
if !check_gating_mode_all_none(raw, A2_NUM_LAYERS) {
return Some(A2TopologyResult::Dynamic);
}
if !check_secondary_activation_all_null(raw) {
return Some(A2TopologyResult::Dynamic);
}
if !check_head1x1_inactive(raw) {
return Some(A2TopologyResult::Dynamic);
}
if !check_layer1x1_active_groups1(raw) {
return Some(A2TopologyResult::Dynamic);
}
if !check_layer_array_head(raw) {
return Some(A2TopologyResult::Dynamic);
}
if !check_film_all_inactive(raw) {
return Some(A2TopologyResult::Dynamic);
}
if !check_groups_are_1(raw) {
return Some(A2TopologyResult::Dynamic);
}
if !check_not_slimmable(raw) {
return Some(A2TopologyResult::Dynamic);
}
}
Some(A2TopologyResult::KnownFastPath(ch))
}
fn get_f64(val: &serde_json::Value) -> Option<f64> {
val.as_f64().or_else(|| val.as_i64().map(|i| i as f64))
}
fn check_activations_are_leaky_relu(
raw: &serde_json::Value,
num_layers: usize,
slope: f64,
) -> bool {
let arr = match raw.get("activation").and_then(|v| v.as_array()) {
Some(a) if a.len() == num_layers => a,
_ => return false,
};
for entry in arr {
let obj = match entry.as_object() {
Some(o) => o,
None => return false,
};
if obj.get("type").and_then(|v| v.as_str()) != Some("LeakyReLU") {
return false;
}
let ns = match obj.get("negative_slope").and_then(get_f64) {
Some(n) => n,
None => return false,
};
if (ns - slope).abs() > 1e-6 {
return false;
}
}
true
}
fn check_gating_mode_all_none(raw: &serde_json::Value, num_layers: usize) -> bool {
match raw.get("gating_mode") {
None | Some(serde_json::Value::Null) => true,
Some(arr) => {
let elems = match arr.as_array() {
Some(a) if a.len() == num_layers => a,
_ => return false,
};
elems
.iter()
.all(|v| v.as_str() == Some("none") || v.is_null())
}
}
}
fn check_secondary_activation_all_null(raw: &serde_json::Value) -> bool {
match raw.get("secondary_activation") {
None | Some(serde_json::Value::Null) => true,
Some(arr) => match arr.as_array() {
Some(a) => a.iter().all(|v| v.is_null()),
None => false,
},
}
}
fn check_head1x1_inactive(raw: &serde_json::Value) -> bool {
match raw.get("head1x1") {
None | Some(serde_json::Value::Null) => true,
Some(v) => match v.as_object() {
Some(obj) => obj.get("active").and_then(|a| a.as_bool()) != Some(true),
None => true,
},
}
}
fn check_layer1x1_active_groups1(raw: &serde_json::Value) -> bool {
let obj = match raw.get("layer1x1").and_then(|v| v.as_object()) {
Some(o) => o,
None => return false,
};
if obj.get("active").and_then(|v| v.as_bool()) != Some(true) {
return false;
}
obj.get("groups").and_then(|g| g.as_u64()) == Some(1)
}
fn check_layer_array_head(raw: &serde_json::Value) -> bool {
use crate::models::a2::A2_HEAD_KERNEL_SIZE;
let obj = match raw.get("head").and_then(|v| v.as_object()) {
Some(o) => o,
None => return false,
};
if obj.get("out_channels").and_then(|c| c.as_u64()) != Some(1) {
return false;
}
if obj.get("kernel_size").and_then(|k| k.as_u64()) != Some(A2_HEAD_KERNEL_SIZE as u64) {
return false;
}
obj.get("bias").and_then(|b| b.as_bool()) == Some(true)
}
fn check_film_all_inactive(raw: &serde_json::Value) -> bool {
const FILM_KEYS: &[&str] = &[
"conv_pre_film",
"conv_post_film",
"input_mixin_pre_film",
"input_mixin_post_film",
"activation_pre_film",
"activation_post_film",
"layer1x1_post_film",
"head1x1_post_film",
];
for key in FILM_KEYS {
match raw.get(key) {
None | Some(serde_json::Value::Null) => continue,
Some(v) => match v.as_object() {
Some(obj) => {
if obj.get("active").and_then(|a| a.as_bool()) == Some(true) {
return false;
}
}
None => continue,
},
}
}
true
}
fn check_groups_are_1(raw: &serde_json::Value) -> bool {
let gi = raw
.get("groups_input")
.and_then(|v| v.as_u64())
.unwrap_or(1);
let gim = raw
.get("groups_input_mixin")
.and_then(|v| v.as_u64())
.unwrap_or(1);
gi == 1 && gim == 1
}
fn check_not_slimmable(raw: &serde_json::Value) -> bool {
matches!(raw.get("slimmable"), None | Some(serde_json::Value::Null))
}
#[cfg(test)]
#[path = "a2_test.rs"]
mod tests;