use onnx_runtime_ir::{Attribute, DataType, Graph, NodeId, TensorData, ValueId, WeightRef};
use onnx_runtime_loader::WeightStore;
use crate::DecodePrecision;
pub(crate) fn maybe_convert_decode_fp16(
graph: &mut Graph,
weights: &WeightStore,
precision: DecodePrecision,
device_is_gpu: bool,
) -> bool {
if precision != DecodePrecision::Fp16 || !device_is_gpu {
return false;
}
if !graph_has_fp32_matmul_nbits(graph) {
return false;
}
convert_graph_fp32_to_fp16(graph, weights);
true
}
fn graph_has_fp32_matmul_nbits(graph: &Graph) -> bool {
graph.nodes.iter().any(|(_, node)| {
if node.op_type != "MatMulNBits" {
return false;
}
let Some(&Some(scales)) = node.inputs.get(2) else {
return false;
};
graph
.initializers
.get(&scales)
.is_some_and(|weight| weight.dtype() == DataType::Float32)
})
}
fn convert_graph_fp32_to_fp16(graph: &mut Graph, weights: &WeightStore) {
let float_inits: Vec<(ValueId, WeightRef)> = graph
.initializers
.iter()
.filter(|(_, weight)| weight.dtype() == DataType::Float32)
.map(|(id, weight)| (*id, weight.clone()))
.collect();
for (id, weight) in float_inits {
let Some(bytes) = weights.bytes(&weight) else {
continue;
};
let fp16 = f32_bytes_to_f16(bytes);
let dims = weight.dims().to_vec();
graph.set_initializer(
id,
WeightRef::Inline(TensorData::from_raw(DataType::Float16, dims, fp16)),
);
}
retype_float_values(graph);
let node_ids: Vec<NodeId> = graph.nodes.iter().map(|(id, _)| id).collect();
for node_id in node_ids {
rewrite_node_attributes_fp32_to_fp16(graph.node_mut(node_id));
}
let subgraph_keys: Vec<(NodeId, String)> = graph.subgraphs.keys().cloned().collect();
for key in subgraph_keys {
if let Some(mut sub) = graph.subgraphs.remove(&key) {
retype_float_values(&mut sub);
let sub_nodes: Vec<NodeId> = sub.nodes.iter().map(|(id, _)| id).collect();
for node_id in sub_nodes {
rewrite_node_attributes_fp32_to_fp16(sub.node_mut(node_id));
}
graph.subgraphs.insert(key, sub);
}
}
}
fn retype_float_values(graph: &mut Graph) {
let float_values: Vec<ValueId> = graph
.values
.iter()
.filter(|(_, value)| value.dtype == DataType::Float32)
.map(|(id, _)| id)
.collect();
for id in float_values {
if let Some(value) = graph.values.get_mut(id) {
value.dtype = DataType::Float16;
}
}
}
fn rewrite_node_attributes_fp32_to_fp16(node: &mut onnx_runtime_ir::Node) {
let is_cast = node.op_type == "Cast" && node.is_default_domain();
for (name, attr) in node.attributes.iter_mut() {
match attr {
Attribute::Int(v)
if is_cast && name == "to" && *v == DataType::Float32.to_onnx() as i64 =>
{
*v = DataType::Float16.to_onnx() as i64;
}
Attribute::Tensor(tensor) => convert_tensor_fp32_to_fp16(tensor),
Attribute::Tensors(tensors) => {
for tensor in tensors.iter_mut() {
convert_tensor_fp32_to_fp16(tensor);
}
}
Attribute::Graph(sub) => convert_inline_subgraph_fp32_to_fp16(sub),
Attribute::Graphs(subs) => {
for sub in subs.iter_mut() {
convert_inline_subgraph_fp32_to_fp16(sub);
}
}
_ => {}
}
}
}
fn convert_inline_subgraph_fp32_to_fp16(sub: &mut Graph) {
retype_float_values(sub);
let node_ids: Vec<NodeId> = sub.nodes.iter().map(|(id, _)| id).collect();
for node_id in node_ids {
rewrite_node_attributes_fp32_to_fp16(sub.node_mut(node_id));
}
}
fn convert_tensor_fp32_to_fp16(tensor: &mut TensorData) {
if tensor.dtype != DataType::Float32 {
return;
}
tensor.data = f32_bytes_to_f16(&tensor.data);
tensor.dtype = DataType::Float16;
}
fn f32_bytes_to_f16(bytes: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(bytes.len() / 2);
for chunk in bytes.chunks_exact(4) {
let value = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
let half = half::f16::from_f32(value);
out.extend_from_slice(&half.to_le_bytes());
}
out
}