Skip to main content

rlx_pkg/
flat.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//! Flat `.rlxp` binary — hybrid hot / warm / cold data region.
6
7use crate::manifest::Manifest;
8use crate::placement::Placement;
9use crate::tier::{Codec, StorageTier, decode_payload, encode_for_tier};
10use crate::weights_index::{WeightEntry, WeightsIndex};
11use anyhow::{Context, Result, bail};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::io::Write;
15use std::path::Path;
16
17/// Magic for the flat package (`RLXPFLAT`).
18pub const FLAT_MAGIC: &[u8; 8] = b"RLXPFLAT";
19
20/// Flat container format version. v2 = hybrid tiers; v1 still readable.
21pub const FLAT_CONTAINER_VERSION: u32 = 2;
22
23/// Align data region to this many bytes (SIMD / DMA friendly).
24pub const DATA_ALIGN: u64 = 64;
25
26/// Flag bit: package uses hybrid hot/warm/cold codecs.
27pub const FLAG_HYBRID: u32 = 1;
28
29/// Flag bit: TOC is bincode rather than JSON.
30pub const FLAG_BINCODE_TOC: u32 = 2;
31
32/// One tensor in the flat TOC (offsets relative to the data region).
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct FlatTensor {
35    #[serde(default, skip_serializing_if = "String::is_empty")]
36    pub name: String,
37    /// Index into [`FlatToc::strings`] when names are interned.
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub name_i: Option<u32>,
40    pub shape: Vec<usize>,
41    pub scheme: String,
42    pub layout: String,
43    pub offset: u64,
44    /// Stored (possibly compressed) byte length.
45    pub length: u64,
46    /// Uncompressed length (equals `length` for hot/raw).
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub raw_length: Option<u64>,
49    #[serde(default)]
50    pub tier: StorageTier,
51    #[serde(default)]
52    pub codec: Codec,
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub rank: Option<u32>,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub checksum: Option<String>,
57}
58
59impl FlatTensor {
60    pub fn resolved_name<'a>(&'a self, strings: &'a [String]) -> Result<&'a str> {
61        if !self.name.is_empty() {
62            return Ok(&self.name);
63        }
64        if let Some(i) = self.name_i {
65            strings
66                .get(i as usize)
67                .map(|s| s.as_str())
68                .with_context(|| format!("name_i {i} out of strings table"))
69        } else {
70            bail!("flat tensor missing name and name_i")
71        }
72    }
73}
74
75/// Sidecar blob in the flat TOC.
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77pub struct FlatSidecar {
78    pub id: String,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub media_type: Option<String>,
81    pub offset: u64,
82    pub length: u64,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub raw_length: Option<u64>,
85    #[serde(default)]
86    pub tier: StorageTier,
87    #[serde(default)]
88    pub codec: Codec,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub checksum: Option<String>,
91}
92
93/// Table of contents after the fixed header.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct FlatToc {
96    pub manifest: Manifest,
97    pub graph_offset: u64,
98    pub graph_length: u64,
99    /// Graph stays hot/raw (mmap + bincode).
100    #[serde(default)]
101    pub graph_tier: StorageTier,
102    #[serde(default)]
103    pub graph_codec: Codec,
104    pub tensors: Vec<FlatTensor>,
105    #[serde(default, skip_serializing_if = "Vec::is_empty")]
106    pub sidecars: Vec<FlatSidecar>,
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub placement_offset: Option<u64>,
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub placement_length: Option<u64>,
111    #[serde(default)]
112    pub placement_tier: StorageTier,
113    #[serde(default)]
114    pub placement_codec: Codec,
115    /// Uncompressed warm block size used when writing (informational).
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub warm_block_size: Option<u32>,
118    /// Interned strings for tensor names (`name_i` indexes this).
119    #[serde(default, skip_serializing_if = "Vec::is_empty")]
120    pub strings: Vec<String>,
121}
122
123impl FlatToc {
124    /// Resolve interned tensor names into `FlatTensor::name`.
125    pub fn resolve_names(&mut self) -> Result<()> {
126        for t in &mut self.tensors {
127            if t.name.is_empty() {
128                if let Some(i) = t.name_i {
129                    t.name = self
130                        .strings
131                        .get(i as usize)
132                        .cloned()
133                        .with_context(|| format!("name_i {i} out of strings table"))?;
134                }
135            }
136        }
137        Ok(())
138    }
139}
140
141/// Fixed header before the TOC (24 bytes).
142#[derive(Debug, Clone, Copy)]
143pub struct FlatHeader {
144    pub container_version: u32,
145    pub flags: u32,
146    pub toc_len: u64,
147}
148
149impl FlatHeader {
150    pub const SIZE: usize = 8 + 4 + 4 + 8;
151
152    pub fn encode(self) -> [u8; Self::SIZE] {
153        let mut out = [0u8; Self::SIZE];
154        out[..8].copy_from_slice(FLAT_MAGIC);
155        out[8..12].copy_from_slice(&self.container_version.to_le_bytes());
156        out[12..16].copy_from_slice(&self.flags.to_le_bytes());
157        out[16..24].copy_from_slice(&self.toc_len.to_le_bytes());
158        out
159    }
160
161    pub fn decode(bytes: &[u8]) -> Result<Self> {
162        if bytes.len() < Self::SIZE {
163            bail!("flat .rlxp too short for header");
164        }
165        if &bytes[..8] != FLAT_MAGIC {
166            bail!("not a flat RLXPFLAT package");
167        }
168        let container_version = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
169        let flags = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
170        let toc_len = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
171        if container_version > FLAT_CONTAINER_VERSION {
172            bail!(
173                "flat container version {container_version} newer than loader {FLAT_CONTAINER_VERSION}"
174            );
175        }
176        Ok(Self {
177            container_version,
178            flags,
179            toc_len,
180        })
181    }
182
183    pub fn is_hybrid(self) -> bool {
184        self.flags & FLAG_HYBRID != 0 || self.container_version >= 2
185    }
186
187    pub fn is_bincode_toc(self) -> bool {
188        self.flags & FLAG_BINCODE_TOC != 0
189    }
190}
191
192pub fn is_flat_magic(bytes: &[u8]) -> bool {
193    bytes.len() >= 8 && &bytes[..8] == FLAT_MAGIC
194}
195
196fn align_up(n: u64, align: u64) -> u64 {
197    (n + align - 1) & !(align - 1)
198}
199
200/// Wire form when `FLAG_BINCODE_TOC` is set: bincode envelope with JSON manifest
201/// (avoids bincode ↔ `serde_json::Value` in `Manifest::extensions`).
202/// Tensor/sidecar rows use always-present fields — bincode is not self-describing,
203/// so `skip_serializing_if` on the JSON TOC types cannot be reused here.
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub(crate) struct FlatTocBin {
206    manifest_json: Vec<u8>,
207    graph_offset: u64,
208    graph_length: u64,
209    graph_tier: StorageTier,
210    graph_codec: Codec,
211    tensors: Vec<FlatTensorBin>,
212    sidecars: Vec<FlatSidecarBin>,
213    placement_offset: Option<u64>,
214    placement_length: Option<u64>,
215    placement_tier: StorageTier,
216    placement_codec: Codec,
217    warm_block_size: Option<u32>,
218    strings: Vec<String>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222struct FlatTensorBin {
223    name: String,
224    name_i: Option<u32>,
225    shape: Vec<usize>,
226    scheme: String,
227    layout: String,
228    offset: u64,
229    length: u64,
230    raw_length: Option<u64>,
231    tier: StorageTier,
232    codec: Codec,
233    rank: Option<u32>,
234    checksum: Option<String>,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238struct FlatSidecarBin {
239    id: String,
240    media_type: Option<String>,
241    offset: u64,
242    length: u64,
243    raw_length: Option<u64>,
244    tier: StorageTier,
245    codec: Codec,
246    checksum: Option<String>,
247}
248
249impl From<&FlatTensor> for FlatTensorBin {
250    fn from(t: &FlatTensor) -> Self {
251        Self {
252            name: t.name.clone(),
253            name_i: t.name_i,
254            shape: t.shape.clone(),
255            scheme: t.scheme.clone(),
256            layout: t.layout.clone(),
257            offset: t.offset,
258            length: t.length,
259            raw_length: t.raw_length,
260            tier: t.tier,
261            codec: t.codec,
262            rank: t.rank,
263            checksum: t.checksum.clone(),
264        }
265    }
266}
267
268impl From<FlatTensorBin> for FlatTensor {
269    fn from(t: FlatTensorBin) -> Self {
270        Self {
271            name: t.name,
272            name_i: t.name_i,
273            shape: t.shape,
274            scheme: t.scheme,
275            layout: t.layout,
276            offset: t.offset,
277            length: t.length,
278            raw_length: t.raw_length,
279            tier: t.tier,
280            codec: t.codec,
281            rank: t.rank,
282            checksum: t.checksum,
283        }
284    }
285}
286
287impl From<&FlatSidecar> for FlatSidecarBin {
288    fn from(s: &FlatSidecar) -> Self {
289        Self {
290            id: s.id.clone(),
291            media_type: s.media_type.clone(),
292            offset: s.offset,
293            length: s.length,
294            raw_length: s.raw_length,
295            tier: s.tier,
296            codec: s.codec,
297            checksum: s.checksum.clone(),
298        }
299    }
300}
301
302impl From<FlatSidecarBin> for FlatSidecar {
303    fn from(s: FlatSidecarBin) -> Self {
304        Self {
305            id: s.id,
306            media_type: s.media_type,
307            offset: s.offset,
308            length: s.length,
309            raw_length: s.raw_length,
310            tier: s.tier,
311            codec: s.codec,
312            checksum: s.checksum,
313        }
314    }
315}
316
317impl FlatTocBin {
318    pub(crate) fn from_toc(toc: &FlatToc) -> Result<Self> {
319        Ok(Self {
320            manifest_json: serde_json::to_vec(&toc.manifest)
321                .context("manifest json for bincode TOC")?,
322            graph_offset: toc.graph_offset,
323            graph_length: toc.graph_length,
324            graph_tier: toc.graph_tier,
325            graph_codec: toc.graph_codec,
326            tensors: toc.tensors.iter().map(FlatTensorBin::from).collect(),
327            sidecars: toc.sidecars.iter().map(FlatSidecarBin::from).collect(),
328            placement_offset: toc.placement_offset,
329            placement_length: toc.placement_length,
330            placement_tier: toc.placement_tier,
331            placement_codec: toc.placement_codec,
332            warm_block_size: toc.warm_block_size,
333            strings: toc.strings.clone(),
334        })
335    }
336
337    pub(crate) fn into_toc(self) -> Result<FlatToc> {
338        let manifest: Manifest =
339            serde_json::from_slice(&self.manifest_json).context("manifest in bincode TOC")?;
340        Ok(FlatToc {
341            manifest,
342            graph_offset: self.graph_offset,
343            graph_length: self.graph_length,
344            graph_tier: self.graph_tier,
345            graph_codec: self.graph_codec,
346            tensors: self.tensors.into_iter().map(FlatTensor::from).collect(),
347            sidecars: self.sidecars.into_iter().map(FlatSidecar::from).collect(),
348            placement_offset: self.placement_offset,
349            placement_length: self.placement_length,
350            placement_tier: self.placement_tier,
351            placement_codec: self.placement_codec,
352            warm_block_size: self.warm_block_size,
353            strings: self.strings,
354        })
355    }
356}
357
358/// Payload pieces for [`write_flat`].
359pub struct FlatPayload {
360    pub graph: Vec<u8>,
361    pub weights: Vec<(FlatTensorMeta, Vec<u8>)>,
362    pub sidecars: Vec<(String, Option<String>, Vec<u8>)>,
363    pub placement: Option<Vec<u8>>,
364    pub warm_block_size: u32,
365    /// Compress sidecars as cold zstd (default true for hybrid).
366    pub compress_sidecars: bool,
367    /// Keep placement hot/raw (default true).
368    pub placement_hot: bool,
369    /// Prefer bincode TOC.
370    pub bincode_toc: bool,
371    /// Intern tensor names into TOC `strings`.
372    pub intern_strings: bool,
373    /// When false, `graph` should be empty (weight-only pack).
374    pub include_graph: bool,
375}
376
377/// Tensor metadata without bytes.
378#[derive(Debug, Clone)]
379pub struct FlatTensorMeta {
380    pub name: String,
381    pub shape: Vec<usize>,
382    pub scheme: String,
383    pub layout: String,
384    pub rank: Option<u32>,
385    pub tier: StorageTier,
386    pub checksum: Option<String>,
387}
388
389/// Serialize a flat (optionally hybrid) package to `path`.
390pub fn write_flat(path: impl AsRef<Path>, manifest: Manifest, payload: FlatPayload) -> Result<()> {
391    let path = path.as_ref();
392    if let Some(parent) = path.parent() {
393        if !parent.as_os_str().is_empty() {
394            std::fs::create_dir_all(parent)?;
395        }
396    }
397
398    let mut data = Vec::new();
399    let graph_offset = 0u64;
400    data.extend_from_slice(&payload.graph);
401    let graph_length = payload.graph.len() as u64;
402
403    let mut string_table: Vec<String> = Vec::new();
404    let mut string_index: HashMap<String, u32> = HashMap::new();
405    let mut intern = |s: &str| -> u32 {
406        if let Some(&i) = string_index.get(s) {
407            return i;
408        }
409        let i = string_table.len() as u32;
410        string_table.push(s.to_string());
411        string_index.insert(s.to_string(), i);
412        i
413    };
414
415    let mut tensors = Vec::with_capacity(payload.weights.len());
416    for (meta, raw) in &payload.weights {
417        let (codec, stored) = encode_for_tier(meta.tier, raw, payload.warm_block_size)?;
418        let offset = data.len() as u64;
419        let length = stored.len() as u64;
420        let raw_length = if codec == Codec::None {
421            None
422        } else {
423            Some(raw.len() as u64)
424        };
425        data.extend_from_slice(&stored);
426        let (name, name_i) = if payload.intern_strings {
427            (String::new(), Some(intern(&meta.name)))
428        } else {
429            (meta.name.clone(), None)
430        };
431        tensors.push(FlatTensor {
432            name,
433            name_i,
434            shape: meta.shape.clone(),
435            scheme: meta.scheme.clone(),
436            layout: meta.layout.clone(),
437            offset,
438            length,
439            raw_length,
440            tier: meta.tier,
441            codec,
442            rank: meta.rank,
443            checksum: meta.checksum.clone(),
444        });
445    }
446
447    let mut sidecars = Vec::new();
448    for (id, media, raw) in &payload.sidecars {
449        let (tier, codec, stored, raw_length) = if payload.compress_sidecars {
450            let (c, s) = encode_for_tier(StorageTier::Cold, raw, payload.warm_block_size)?;
451            (StorageTier::Cold, c, s, Some(raw.len() as u64))
452        } else {
453            (StorageTier::Hot, Codec::None, raw.clone(), None)
454        };
455        let offset = data.len() as u64;
456        let length = stored.len() as u64;
457        data.extend_from_slice(&stored);
458        let checksum = meta_checksum_for_sidecar(raw, &payload);
459        sidecars.push(FlatSidecar {
460            id: id.clone(),
461            media_type: media.clone(),
462            offset,
463            length,
464            raw_length,
465            tier,
466            codec,
467            checksum,
468        });
469    }
470
471    let (placement_offset, placement_length, placement_tier, placement_codec) =
472        if let Some(pl) = &payload.placement {
473            let (tier, codec, stored) = if payload.placement_hot {
474                (StorageTier::Hot, Codec::None, pl.clone())
475            } else {
476                let (c, s) = encode_for_tier(StorageTier::Cold, pl, payload.warm_block_size)?;
477                (StorageTier::Cold, c, s)
478            };
479            let offset = data.len() as u64;
480            data.extend_from_slice(&stored);
481            (Some(offset), Some(stored.len() as u64), tier, codec)
482        } else {
483            (None, None, StorageTier::Hot, Codec::None)
484        };
485
486    let hybrid = tensors.iter().any(|t| t.tier != StorageTier::Hot)
487        || sidecars.iter().any(|s| s.codec != Codec::None)
488        || placement_codec != Codec::None;
489
490    let mut manifest = manifest;
491    if !payload.include_graph || graph_length == 0 {
492        manifest.graph.encoding = "none".into();
493        push_feat(&mut manifest.features, "weight_only");
494    }
495    if hybrid {
496        push_feat(&mut manifest.features, "hybrid_storage");
497        if tensors.iter().any(|t| t.tier == StorageTier::Warm) {
498            push_feat(&mut manifest.features, "zstd_blocks_warm");
499        }
500        if sidecars.iter().any(|s| s.codec == Codec::Zstd) || placement_codec == Codec::Zstd {
501            push_feat(&mut manifest.features, "zstd_cold");
502        }
503    }
504    if payload.intern_strings {
505        push_feat(&mut manifest.features, "toc_string_table");
506    }
507    if payload.bincode_toc {
508        push_feat(&mut manifest.features, "toc_bincode");
509    }
510
511    let toc = FlatToc {
512        manifest,
513        graph_offset,
514        graph_length,
515        graph_tier: StorageTier::Hot,
516        graph_codec: Codec::None,
517        tensors,
518        sidecars,
519        placement_offset,
520        placement_length,
521        placement_tier,
522        placement_codec,
523        warm_block_size: Some(payload.warm_block_size),
524        strings: if payload.intern_strings {
525            string_table
526        } else {
527            Vec::new()
528        },
529    };
530    let toc_bytes = if payload.bincode_toc {
531        let wire = FlatTocBin::from_toc(&toc)?;
532        bincode::serialize(&wire).context("serialize flat TOC (bincode)")?
533    } else {
534        serde_json::to_vec(&toc).context("serialize flat TOC")?
535    };
536
537    let mut flags = 0u32;
538    if hybrid {
539        flags |= FLAG_HYBRID;
540    }
541    if payload.bincode_toc {
542        flags |= FLAG_BINCODE_TOC;
543    }
544    let header = FlatHeader {
545        container_version: FLAT_CONTAINER_VERSION,
546        flags,
547        toc_len: toc_bytes.len() as u64,
548    };
549    let header_bytes = header.encode();
550    let data_start = align_up((FlatHeader::SIZE as u64) + header.toc_len, DATA_ALIGN);
551    let pad = data_start as usize - FlatHeader::SIZE - toc_bytes.len();
552
553    let mut file =
554        std::fs::File::create(path).with_context(|| format!("creating {}", path.display()))?;
555    file.write_all(&header_bytes)?;
556    file.write_all(&toc_bytes)?;
557    if pad > 0 {
558        file.write_all(&vec![0u8; pad])?;
559    }
560    file.write_all(&data)?;
561    Ok(())
562}
563
564fn meta_checksum_for_sidecar(raw: &[u8], payload: &FlatPayload) -> Option<String> {
565    // Checksums for sidecars follow the same policy as weights: only when any
566    // weight carried a checksum (write_checksums). Detect via first weight.
567    if payload.weights.iter().any(|(m, _)| m.checksum.is_some()) {
568        Some(crate::tier::checksum_hex(raw))
569    } else {
570        None
571    }
572}
573
574fn push_feat(feats: &mut Vec<String>, f: &str) {
575    if !feats.iter().any(|x| x == f) {
576        feats.push(f.to_string());
577    }
578}
579
580/// Parsed flat package views into an mmap.
581pub struct FlatView<'a> {
582    #[allow(dead_code)]
583    pub header: FlatHeader,
584    pub toc: FlatToc,
585    pub data: &'a [u8],
586    pub data_start: u64,
587}
588
589impl<'a> FlatView<'a> {
590    pub fn parse(map: &'a [u8]) -> Result<Self> {
591        let header = FlatHeader::decode(map)?;
592        let toc_end = FlatHeader::SIZE
593            .checked_add(header.toc_len as usize)
594            .context("toc overflow")?;
595        let toc_bytes = map
596            .get(FlatHeader::SIZE..toc_end)
597            .context("truncated flat TOC")?;
598        let mut toc: FlatToc = if header.is_bincode_toc() {
599            let wire: FlatTocBin =
600                bincode::deserialize(toc_bytes).context("deserialize flat TOC (bincode)")?;
601            wire.into_toc()?
602        } else {
603            serde_json::from_slice(toc_bytes).context("deserialize flat TOC")?
604        };
605        toc.resolve_names()?;
606        toc.manifest.validate()?;
607        let data_start = align_up((FlatHeader::SIZE as u64) + header.toc_len, DATA_ALIGN);
608        let data = map
609            .get(data_start as usize..)
610            .context("truncated flat data region")?;
611        Ok(Self {
612            header,
613            toc,
614            data,
615            data_start,
616        })
617    }
618
619    pub fn slice(&self, offset: u64, length: u64) -> Result<&'a [u8]> {
620        let start = offset as usize;
621        let end = start
622            .checked_add(length as usize)
623            .context("flat slice overflow")?;
624        self.data
625            .get(start..end)
626            .with_context(|| format!("flat data slice [{offset}..+{length}]"))
627    }
628
629    pub fn decode_slice(&self, offset: u64, length: u64, codec: Codec) -> Result<Vec<u8>> {
630        let stored = self.slice(offset, length)?;
631        decode_payload(codec, stored)
632    }
633
634    pub fn weights_index(&self) -> WeightsIndex {
635        WeightsIndex {
636            tensors: self
637                .toc
638                .tensors
639                .iter()
640                .map(|t| WeightEntry {
641                    name: t.name.clone(),
642                    shape: t.shape.clone(),
643                    scheme: t.scheme.clone(),
644                    layout: t.layout.clone(),
645                    shard: "__flat__".into(),
646                    offset: t.offset,
647                    length: t.length,
648                    raw_length: t.raw_length.or(Some(t.length)),
649                    tier: t.tier,
650                    codec: t.codec,
651                    rank: t.rank,
652                    checksum: t.checksum.clone(),
653                })
654                .collect(),
655        }
656    }
657
658    pub fn placement(&self) -> Result<Option<Placement>> {
659        match (self.toc.placement_offset, self.toc.placement_length) {
660            (Some(off), Some(len)) => {
661                let bytes = self.decode_slice(off, len, self.toc.placement_codec)?;
662                Ok(Some(
663                    serde_json::from_slice(&bytes).context("placement in flat pack")?,
664                ))
665            }
666            _ => Ok(None),
667        }
668    }
669}