1use 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
17pub const FLAT_MAGIC: &[u8; 8] = b"RLXPFLAT";
19
20pub const FLAT_CONTAINER_VERSION: u32 = 2;
22
23pub const DATA_ALIGN: u64 = 64;
25
26pub const FLAG_HYBRID: u32 = 1;
28
29pub const FLAG_BINCODE_TOC: u32 = 2;
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct FlatTensor {
35 #[serde(default, skip_serializing_if = "String::is_empty")]
36 pub name: String,
37 #[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 pub length: u64,
46 #[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct FlatToc {
96 pub manifest: Manifest,
97 pub graph_offset: u64,
98 pub graph_length: u64,
99 #[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 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub warm_block_size: Option<u32>,
118 #[serde(default, skip_serializing_if = "Vec::is_empty")]
120 pub strings: Vec<String>,
121}
122
123impl FlatToc {
124 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#[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#[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
358pub 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 pub compress_sidecars: bool,
367 pub placement_hot: bool,
369 pub bincode_toc: bool,
371 pub intern_strings: bool,
373 pub include_graph: bool,
375}
376
377#[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
389pub 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 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
580pub 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}