use turbospark_vision_io::{ImageSpan, MropePositions};
use super::VisionEmbedding;
use crate::real_forward_types::RealForwardError;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RopePosition {
Sequential,
Triple(i32, i32, i32),
}
#[derive(Debug, Clone)]
pub struct PromptVision {
rows: Vec<u8>,
row_bytes: usize,
spans: Vec<ImageSpan>,
prefix: Vec<usize>,
triples: Vec<(i32, i32, i32)>,
rope_delta: i32,
deepstack: Vec<Vec<u8>>,
deepstack_buffers: Vec<gpu::MetalBuffer>,
}
impl PromptVision {
pub fn new(
embeddings: &[VisionEmbedding],
positions: &MropePositions,
prompt_len: usize,
hidden_size: usize,
) -> Result<Self, RealForwardError> {
let refuse = |detail: String| Err(RealForwardError::Unsupported(detail));
if positions.triples.len() != prompt_len {
return refuse(format!(
"mrope triples cover {} positions; the prompt has {prompt_len}",
positions.triples.len()
));
}
if embeddings.len() != positions.spans.len() {
return refuse(format!(
"{} encoded image(s) against {} placeholder span(s) in the prompt",
embeddings.len(),
positions.spans.len()
));
}
let row_bytes = hidden_size * 2;
let mut rows: Vec<u8> = Vec::new();
let mut prefix = Vec::with_capacity(embeddings.len());
let mut last_end = 0usize;
let deepstack_len = embeddings.first().map(|e| e.deepstack.len()).unwrap_or(0);
if let Some(mismatched) = embeddings
.iter()
.position(|e| e.deepstack.len() != deepstack_len)
{
return refuse(format!(
"image {mismatched}: tower produced {} deepstack row set(s) against \
{deepstack_len} from image 0",
embeddings[mismatched].deepstack.len()
));
}
let mut deepstack: Vec<Vec<u8>> = vec![Vec::new(); deepstack_len];
for (i, (embedding, span)) in embeddings.iter().zip(positions.spans.iter()).enumerate() {
if embedding.out_hidden != hidden_size {
return refuse(format!(
"image {i}: tower emits {}-wide rows; the trunk's residual stream is \
{hidden_size}-wide",
embedding.out_hidden
));
}
if embedding.merged_tokens != span.len {
return refuse(format!(
"image {i}: tower produced {} merged token(s) against a {}-token \
placeholder run at position {}",
embedding.merged_tokens, span.len, span.start
));
}
if embedding.rows.len() != embedding.merged_tokens * embedding.out_hidden {
return refuse(format!(
"image {i}: {} row value(s) for {} x {}",
embedding.rows.len(),
embedding.merged_tokens,
embedding.out_hidden
));
}
for (k, ds) in embedding.deepstack.iter().enumerate() {
if ds.rows.len() != embedding.merged_tokens * embedding.out_hidden {
return refuse(format!(
"image {i}: deepstack row set {k} carries {} value(s) for {} x {}",
ds.rows.len(),
embedding.merged_tokens,
embedding.out_hidden
));
}
deepstack[k].reserve(ds.rows.len() * 2);
for value in &ds.rows {
deepstack[k].extend_from_slice(&value.to_le_bytes());
}
}
if span.start < last_end {
return refuse(format!(
"image {i}: placeholder run at {} overlaps the previous run, which ended \
at {last_end}",
span.start
));
}
if span.start + span.len > prompt_len {
return refuse(format!(
"image {i}: placeholder run [{}, {}) runs past a {prompt_len}-token prompt",
span.start,
span.start + span.len
));
}
last_end = span.start + span.len;
prefix.push(rows.len() / row_bytes.max(1));
rows.reserve(embedding.rows.len() * 2);
for value in &embedding.rows {
rows.extend_from_slice(&value.to_le_bytes());
}
}
if prompt_len as i32 + positions.rope_delta < 0 {
return refuse(format!(
"rope_delta {} would put the first decode position before zero on a \
{prompt_len}-token prompt",
positions.rope_delta
));
}
Ok(Self {
rows,
row_bytes,
spans: positions.spans.clone(),
prefix,
triples: positions.triples.clone(),
rope_delta: positions.rope_delta,
deepstack,
deepstack_buffers: Vec::new(),
})
}
pub(crate) fn attach_deepstack_buffers(
&mut self,
context: &mut gpu::MetalContext,
) -> Result<(), RealForwardError> {
if !self.deepstack_buffers.is_empty() {
return Ok(());
}
self.deepstack_buffers = self
.deepstack
.iter()
.map(|bytes| context.new_buffer_with_data(bytes.as_slice()))
.collect();
Ok(())
}
#[allow(dead_code)]
pub(crate) fn deepstack_buffer(&self, k: usize) -> Option<&gpu::MetalBuffer> {
self.deepstack_buffers.get(k)
}
pub(crate) fn deepstack_buffers(&self) -> &[gpu::MetalBuffer] {
&self.deepstack_buffers
}
pub(crate) fn row_for(&self, position: usize) -> Option<&[u8]> {
let row = self.row_index_for(position)?;
let start = row * self.row_bytes;
Some(&self.rows[start..start + self.row_bytes])
}
pub(crate) fn row_index_for(&self, position: usize) -> Option<usize> {
let i = self
.spans
.partition_point(|span| span.start <= position)
.checked_sub(1)?;
let span = self.spans[i];
if position >= span.start + span.len {
return None;
}
Some(self.prefix[i] + (position - span.start))
}
pub(crate) fn rope_position(&self, position: usize) -> (i32, i32, i32) {
match self.triples.get(position) {
Some(&triple) => triple,
None => {
let p = position as i32 + self.rope_delta;
(p, p, p)
}
}
}
pub fn image_rows(&self) -> usize {
self.rows.len() / self.row_bytes.max(1)
}
pub fn spans(&self) -> &[ImageSpan] {
&self.spans
}
}