use serde::Deserialize;
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct HfModelConfig {
pub model_type: String,
#[serde(default)]
pub architectures: Vec<String>,
pub hidden_size: Option<u64>,
pub num_attention_heads: Option<u64>,
pub num_key_value_heads: Option<u64>,
pub num_hidden_layers: Option<u64>,
pub intermediate_size: Option<u64>,
pub vocab_size: Option<u64>,
pub max_position_embeddings: Option<u64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct KernelRequirement {
pub op: String,
pub contract: String,
}
pub fn parse_hf_config(path: &Path) -> Result<HfModelConfig, String> {
let data =
std::fs::read_to_string(path).map_err(|e| format!("read {}: {e}", path.display()))?;
parse_hf_config_str(&data)
}
pub fn parse_hf_config_str(json: &str) -> Result<HfModelConfig, String> {
serde_json::from_str(json).map_err(|e| format!("parse config.json: {e}"))
}
#[derive(Clone, Copy)]
struct ArchConstraints {
norm_type: NormType,
activation: Activation,
positional_encoding: PosEncoding,
mlp_type: MlpType,
has_bias: bool,
tied_embeddings: bool,
has_qk_norm: bool,
}
#[derive(Clone, Copy)]
enum NormType {
RmsNorm,
LayerNorm,
}
#[derive(Clone, Copy)]
enum Activation {
Silu,
Gelu,
}
#[derive(Clone, Copy)]
enum PosEncoding {
Rope,
Absolute,
}
#[derive(Clone, Copy)]
enum MlpType {
SwiGlu,
GeluMlp,
}
const ARCH_QWEN2: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Silu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::SwiGlu,
has_bias: true,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_LLAMA: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Silu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::SwiGlu,
has_bias: false,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_MISTRAL: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Silu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::SwiGlu,
has_bias: false,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_GEMMA: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Gelu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::GeluMlp,
has_bias: false,
tied_embeddings: true,
has_qk_norm: false,
};
const ARCH_PHI: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Silu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::SwiGlu,
has_bias: true,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_STARCODER2: ArchConstraints = ArchConstraints {
norm_type: NormType::LayerNorm,
activation: Activation::Gelu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::GeluMlp,
has_bias: true,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_GPT2: ArchConstraints = ArchConstraints {
norm_type: NormType::LayerNorm,
activation: Activation::Gelu,
positional_encoding: PosEncoding::Absolute,
mlp_type: MlpType::GeluMlp,
has_bias: true,
tied_embeddings: true,
has_qk_norm: false,
};
const ARCH_FALCON: ArchConstraints = ArchConstraints {
norm_type: NormType::LayerNorm,
activation: Activation::Gelu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::GeluMlp,
has_bias: false,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_INTERNLM2: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Silu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::SwiGlu,
has_bias: false,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_DEEPSEEK_V2: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Silu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::SwiGlu,
has_bias: false,
tied_embeddings: false,
has_qk_norm: true,
};
const ARCH_DEFAULT: ArchConstraints = ArchConstraints {
norm_type: NormType::RmsNorm,
activation: Activation::Silu,
positional_encoding: PosEncoding::Rope,
mlp_type: MlpType::SwiGlu,
has_bias: false,
tied_embeddings: false,
has_qk_norm: false,
};
const ARCH_TABLE: &[(&str, ArchConstraints)] = &[
("qwen2", ARCH_QWEN2),
("qwen2_moe", ARCH_QWEN2),
("llama", ARCH_LLAMA),
("codellama", ARCH_LLAMA),
("mistral", ARCH_MISTRAL),
("mixtral", ARCH_MISTRAL),
("gemma", ARCH_GEMMA),
("gemma2", ARCH_GEMMA),
("phi", ARCH_PHI),
("phi3", ARCH_PHI),
("starcoder2", ARCH_STARCODER2),
("gpt2", ARCH_GPT2),
("gpt_neo", ARCH_GPT2),
("gpt_neox", ARCH_GPT2),
("falcon", ARCH_FALCON),
("internlm2", ARCH_INTERNLM2),
("deepseek_v2", ARCH_DEEPSEEK_V2),
];
fn arch_constraints(model_type: &str) -> ArchConstraints {
ARCH_TABLE
.iter()
.find(|(name, _)| *name == model_type)
.map_or(ARCH_DEFAULT, |(_, constraints)| *constraints)
}
fn kernel(op: &str, contract: &str) -> KernelRequirement {
KernelRequirement {
op: op.to_string(),
contract: contract.to_string(),
}
}
fn norm_kernel(norm_type: NormType) -> KernelRequirement {
match norm_type {
NormType::RmsNorm => kernel("rmsnorm", "rmsnorm-kernel-v1"),
NormType::LayerNorm => kernel("layernorm", "layernorm-kernel-v1"),
}
}
fn activation_kernel(activation: Activation) -> KernelRequirement {
match activation {
Activation::Silu => kernel("silu", "silu-kernel-v1"),
Activation::Gelu => kernel("gelu", "gelu-kernel-v1"),
}
}
fn positional_kernel(positional_encoding: PosEncoding) -> KernelRequirement {
match positional_encoding {
PosEncoding::Rope => kernel("rope", "rope-kernel-v1"),
PosEncoding::Absolute => kernel("absolute_position", "absolute-position-v1"),
}
}
fn mlp_kernel(mlp_type: MlpType) -> KernelRequirement {
match mlp_type {
MlpType::SwiGlu => kernel("swiglu", "swiglu-kernel-v1"),
MlpType::GeluMlp => kernel("gelu_mlp", "gelu-kernel-v1"),
}
}
fn flag_kernels(ac: &ArchConstraints) -> Vec<KernelRequirement> {
let flags = [
(ac.has_bias, "bias_add", "bias-add-v1"),
(ac.tied_embeddings, "tied_embeddings", "tied-embeddings-v1"),
(ac.has_qk_norm, "qk_norm", "qk-norm-v1"),
];
flags
.iter()
.filter(|(set, _, _)| *set)
.map(|(_, op, contract)| kernel(op, contract))
.collect()
}
fn attention_kernel(config: &HfModelConfig) -> KernelRequirement {
let is_gqa = match (config.num_attention_heads, config.num_key_value_heads) {
(Some(heads), Some(kv_heads)) => kv_heads < heads,
_ => false,
};
if is_gqa {
kernel("gqa", "gqa-kernel-v1")
} else {
kernel("attention", "attention-kernel-v1")
}
}
const UNIVERSAL_KERNELS: &[(&str, &str)] = &[
("softmax", "softmax-kernel-v1"),
("matmul", "matmul-kernel-v1"),
("embedding_lookup", "embedding-lookup-v1"),
];
pub fn required_kernels(config: &HfModelConfig) -> Vec<KernelRequirement> {
let ac = arch_constraints(&config.model_type);
let mut kernels = vec![
norm_kernel(ac.norm_type),
activation_kernel(ac.activation),
positional_kernel(ac.positional_encoding),
mlp_kernel(ac.mlp_type),
];
kernels.extend(flag_kernels(&ac));
kernels.push(attention_kernel(config));
kernels.extend(
UNIVERSAL_KERNELS
.iter()
.map(|(op, contract)| kernel(op, contract)),
);
kernels
}