1use crate::tier::StorageTier;
8use crate::write::{ContainerKind, PackedWeight, WriteOptions, write_package};
9use anyhow::{Context, Result};
10use rlx_ir::{DType, Graph, Shape};
11use rlx_nemo::{NemoModel, build_nemo_probe_graph};
12use std::path::Path;
13
14#[derive(Debug, Clone)]
16pub struct NemoImportOptions {
17 pub container: ContainerKind,
18 pub include_graph: bool,
19 pub compress_sidecars: bool,
20 pub auto_tier: bool,
21}
22
23impl Default for NemoImportOptions {
24 fn default() -> Self {
25 Self {
26 container: ContainerKind::Flat,
27 include_graph: false,
28 compress_sidecars: true,
29 auto_tier: true,
30 }
31 }
32}
33
34pub fn nemo_to_rlxp(
36 nemo_path: impl AsRef<Path>,
37 out: impl AsRef<Path>,
38 opts: &NemoImportOptions,
39) -> Result<()> {
40 let nemo_path = nemo_path.as_ref();
41 let m =
42 NemoModel::open(nemo_path).with_context(|| format!("open nemo {}", nemo_path.display()))?;
43
44 let mut sidecars = Vec::new();
45 let cfg_yaml =
46 serde_yaml::to_string(m.config().root()).context("serialize model_config.yaml")?;
47 sidecars.push((
48 "model_config.yaml".into(),
49 "application/yaml".into(),
50 cfg_yaml.into_bytes(),
51 ));
52 for tok in m.tokenizers() {
53 sidecars.push((
54 tok.name.clone(),
55 "application/octet-stream".into(),
56 tok.bytes.clone(),
57 ));
58 }
59
60 let mut weights = Vec::new();
61 for name in m.names() {
62 let t = m.tensor(&name)?;
63 let mut bytes = Vec::with_capacity(t.data.len() * 4);
64 for x in &t.data {
65 bytes.extend_from_slice(&x.to_le_bytes());
66 }
67 weights.push(PackedWeight {
68 name,
69 shape: t.shape,
70 scheme: "f32".into(),
71 layout: "row_major".into(),
72 data: bytes,
73 rank: None,
74 tier: StorageTier::Hot,
75 });
76 }
77
78 let mut wopts = WriteOptions {
79 name: nemo_path
80 .file_stem()
81 .and_then(|s| s.to_str())
82 .unwrap_or("nemo")
83 .into(),
84 producer: Some("rlx-pkg/nemo_import".into()),
85 container: opts.container,
86 compress_sidecars: opts.compress_sidecars,
87 include_graph: opts.include_graph,
88 write_checksums: true,
89 ..WriteOptions::default()
90 };
91 wopts.sidecars.extend(sidecars);
92
93 if opts.auto_tier {
94 crate::auto_tier::apply_auto_tier(&mut weights, &Default::default());
95 }
96
97 let graph = if opts.include_graph {
98 if let Some(g) = build_nemo_probe_graph(&m, &wopts.name)? {
99 g
100 } else {
101 let mut g = Graph::new(&wopts.name);
102 let s = Shape::new(&[1], DType::F32);
103 let x = g.input("x", s);
104 g.set_outputs(vec![x]);
105 g
106 }
107 } else {
108 Graph::new(&wopts.name)
109 };
110
111 write_package(out.as_ref(), &graph, &weights, &wopts)
112 .with_context(|| format!("write {}", out.as_ref().display()))?;
113 Ok(())
114}