1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Image tiling and temporal grouping for multimodal input preparation.
//!
//! The LFM2 tiling and adjacent-frame contracts follow the MIT-licensed Prism
//! source pinned by `tools/prism/source.lock.json`. These operations describe
//! input layout; they do not imply a vision/audio model implementation.
use crate::{Result, invalid};
pub mod chunks;
/// A multimodal input part with independently owned metadata.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Part {
/// Ordinary text, which separates temporal image groups.
Text(String),
/// Image dimensions and permission to merge with an adjacent frame.
Image {
/// Width in pixels.
width: u32,
/// Height in pixels.
height: u32,
/// Whether the frame may join an adjacent equal-sized frame.
mergeable: bool,
},
/// Audio inputs never join temporal image groups.
Audio {
/// Sample count.
samples: u32,
},
}
/// Whether aligned image area exceeds LFM2's two-times token-area allowance.
///
/// Alignment rounds to nearest with ties to even, matching `nearbyint` in
/// the pinned implementation. Minimum token count is validated with maximum
/// count although only the maximum determines the tiling decision.
///
/// # Errors
/// Rejects zero, unordered or excessive dimensions and token/patch settings.
pub fn should_tile(
patch: u32,
merge: u32,
min_tokens: u32,
max_tokens: u32,
width: u32,
height: u32,
) -> Result<bool> {
if !(1..=64).contains(&patch)
|| !(1..=8).contains(&merge)
|| !(1..=4096).contains(&min_tokens)
|| !(min_tokens..=4096).contains(&max_tokens)
|| !(1..=4096).contains(&width)
|| !(1..=4096).contains(&height)
{
return Err(invalid("invalid image tiling dimensions or token bounds"));
}
let align = u64::from(patch) * u64::from(merge);
let round = |dimension: u32| {
let value = u64::from(dimension);
let quotient = value / align;
let remainder = value % align;
let up = remainder * 2 > align || (remainder * 2 == align && !quotient.is_multiple_of(2));
u64::from(patch).max((quotient + u64::from(up)) * align)
};
Ok(round(width) * round(height) > align * align * u64::from(max_tokens) * 2)
}
fn compatible(left: &Part, right: &Part) -> bool {
matches!(
(left, right),
(Part::Image { width: lw, height: lh, mergeable: true },
Part::Image { width: rw, height: rh, mergeable: true })
if lw == rw && lh == rh
)
}
/// Groups adjacent compatible frames and removes each group's second frame.
///
/// The pinned temporal operation merges at most two frames even when `merge`
/// is larger. Text and audio preserve their positions and break image groups.
/// Returned groups own their metadata; the first frame remains in `parts`.
///
/// # Errors
/// Rejects an invalid merge setting, more than 64 parts or invalid part bounds.
pub fn group_bitmaps(parts: &mut Vec<Part>, merge: u32) -> Result<Vec<Vec<Part>>> {
if !(1..=64).contains(&merge) || parts.len() > 64 {
return Err(invalid("invalid temporal merge setting or part count"));
}
for part in parts.iter() {
let valid = match part {
Part::Text(text) => text.len() <= 65536,
Part::Image { width, height, .. } => {
(1..=4096).contains(width) && (1..=4096).contains(height)
}
Part::Audio { samples } => (1..=1_048_576).contains(samples),
};
if !valid {
return Err(invalid("invalid multimodal input part"));
}
}
let mut groups = Vec::new();
let mut index = 0;
while index < parts.len() {
if !matches!(parts[index], Part::Text(_)) {
let mut group = vec![parts[index].clone()];
if merge > 1 && index + 1 < parts.len() && compatible(&parts[index], &parts[index + 1])
{
group.push(parts.remove(index + 1));
}
groups.push(group);
}
index += 1;
}
Ok(groups)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tiling_ties_and_temporal_mutation() -> Result<()> {
assert!(!should_tile(16, 2, 64, 256, 720, 720)?);
assert!(should_tile(16, 2, 64, 256, 736, 736)?);
assert!(should_tile(0, 2, 64, 256, 736, 736).is_err());
let image = Part::Image {
width: 100,
height: 100,
mergeable: true,
};
let mut parts = vec![
image.clone(),
image.clone(),
Part::Text("gap".into()),
image,
];
let groups = group_bitmaps(&mut parts, 64)?;
assert_eq!(groups.iter().map(Vec::len).collect::<Vec<_>>(), [2, 1]);
assert_eq!(parts.len(), 3);
assert!(matches!(&parts[1], Part::Text(text) if text == "gap"));
Ok(())
}
}