Skip to main content

rlx_pkg/
from_nemo.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! Import NVIDIA NeMo (`.nemo`) into an `.rlxp` package.
6
7use 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/// Options for [`nemo_to_rlxp`].
15#[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
34/// Convert `model.nemo` → `.rlxp` (f32 weights + config / tokenizer sidecars).
35pub 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}