mod block;
mod budget;
mod inject;
mod overflow;
mod scratch;
mod shape;
mod stages;
mod weights;
pub use budget::{PixelBudget, VisionBudgetTooSmall};
pub use inject::{PromptVision, RopePosition};
use std::path::Path;
use model_io::{ArchConfig, ResidentIndex};
use turbospark_vision_io::{PreprocessParams, PreprocessedImage};
use crate::real_forward::PACKED_LAYOUT_MAX_BYTES;
use crate::real_forward_types::RealForwardError;
use scratch::VisionScratch;
use shape::VisionShape;
use weights::{BlockRoles, VisionResident};
pub const VISION_SLOTS: usize = 2;
fn vision_mapped_residency_requested() -> bool {
std::env::var("TURBOSPARK_VISION_RESIDENCY")
.map(|v| v.eq_ignore_ascii_case("mapped"))
.unwrap_or(false)
}
#[derive(Debug, Clone, PartialEq)]
pub struct VisionEmbedding {
pub rows: Vec<u16>,
pub merged_tokens: usize,
pub out_hidden: usize,
pub grid: turbospark_vision_io::GridThw,
pub deepstack: Vec<DeepstackRows>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeepstackRows {
pub rows: Vec<u16>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct VisionStages {
pub patch_embed: Vec<u16>,
pub block_first: Vec<u16>,
pub block_last: Vec<u16>,
pub deepstack: Vec<Vec<u16>>,
}
pub struct VisionTower {
slot_buffers: Vec<gpu::MetalBuffer>,
streamer: Option<streaming::PreadExpertStreamer>,
mapped_buffer: Option<gpu::MetalBuffer>,
mapped_layer: Option<streaming::MappedExpertLayer>,
blocks: Vec<BlockRoles>,
resident: VisionResident,
pos_table: Vec<f32>,
shape: VisionShape,
pub(crate) slot_bytes: u64,
pub(crate) last_scratch_bytes: u64,
overflow: Option<overflow::VisionOverflowCapture>,
sidecar_weights: Option<gpu::ResidentGpuWeights>,
pub(crate) mlp_tile_rows: usize,
}
impl VisionTower {
pub(crate) fn open(
dir: &Path,
context: &gpu::MetalContext,
weights: &gpu::ResidentGpuWeights,
index: &ResidentIndex,
arch: &ArchConfig,
) -> Result<Self, RealForwardError> {
Self::build(dir, context, weights, index, arch)
}
pub(crate) fn open_with_sidecar(
sidecar_dir: &Path,
context: &gpu::MetalContext,
arch: &ArchConfig,
) -> Result<Self, RealForwardError> {
let index = model_io::load_resident_index(&sidecar_dir.join("model_weights.bin"))
.map_err(RealForwardError::Model)?;
if let Some(entry) = index
.entries
.values()
.find(|e| !crate::real_forward_layout::readable_resident_dtype(&e.name, e.dtype))
{
return Err(RealForwardError::Unsupported(format!(
"vision sidecar tensor {} carries resident dtype {}, which no reader in this \
crate honours; a well-formed sidecar carries only `vision.`-prefixed FP16 \
tensors",
entry.name, entry.dtype
)));
}
let buffer = model_io::ResidentBuffer::map(
&sidecar_dir.join("model_weights.bin"),
index.header.index_size,
index.header.resident_size,
)
.map_err(RealForwardError::Model)?;
let weights = gpu::ResidentGpuWeights::wrap(context.device(), buffer)
.map_err(RealForwardError::Gpu)?;
let mut tower = Self::build(sidecar_dir, context, &weights, &index, arch)?;
tower.sidecar_weights = Some(weights);
Ok(tower)
}
fn build(
dir: &Path,
context: &gpu::MetalContext,
weights: &gpu::ResidentGpuWeights,
index: &ResidentIndex,
arch: &ArchConfig,
) -> Result<Self, RealForwardError> {
let shape = VisionShape::resolve(&arch.vision)?;
let layout = model_io::load_packed_layout_from(
dir,
model_io::PACKED_VISION_DIR,
PACKED_LAYOUT_MAX_BYTES,
)
.map_err(RealForwardError::Model)?;
let layer = layout.layers.iter().find(|l| l.layer == 0).ok_or_else(|| {
RealForwardError::Unsupported(
"packed_vision/layout.json declares no layer 0; the tower is written as one \
layer of `depth` blobs"
.to_string(),
)
})?;
if layer.experts.len() != shape.depth {
return Err(RealForwardError::Unsupported(format!(
"packed_vision carries {} blocks but the manifest declares a depth of {}",
layer.experts.len(),
shape.depth
)));
}
let mut blocks: Vec<Option<BlockRoles>> = vec![None; shape.depth];
for entry in &layer.experts {
let slot = blocks.get_mut(entry.expert).ok_or_else(|| {
RealForwardError::Unsupported(format!(
"packed_vision names a block {} past the declared depth of {}",
entry.expert, shape.depth
))
})?;
if slot
.replace(weights::resolve_block_roles(
entry,
entry.expert,
layer.expert_stride,
&shape,
)?)
.is_some()
{
return Err(RealForwardError::Unsupported(format!(
"packed_vision names block {} twice",
entry.expert
)));
}
}
let blocks: Vec<BlockRoles> = blocks
.into_iter()
.enumerate()
.map(|(i, b)| {
b.ok_or_else(|| {
RealForwardError::MissingTensor(format!("packed_vision has no block {i}"))
})
})
.collect::<Result<_, _>>()?;
let stream_layout =
streaming::StreamLayout::from_packed_layer_in(layer, dir, model_io::PACKED_VISION_DIR);
let (streamer, slot_buffers, mapped_buffer, mapped_layer, slot_bytes) =
if vision_mapped_residency_requested() {
let mapped_layer =
streaming::MappedExpertLayer::open(stream_layout).map_err(|e| {
RealForwardError::Unsupported(format!("mapped vision residency: {e}"))
})?;
let bytes = mapped_layer.page_aligned_bytes();
let buffer =
gpu::wrap_page_aligned_no_copy(context.device(), bytes.as_ptr(), bytes.len())
.map_err(RealForwardError::Gpu)?;
(None, Vec::new(), Some(buffer), Some(mapped_layer), 0u64)
} else {
let streamer = streaming::PreadExpertStreamer::open(
stream_layout,
VISION_SLOTS,
streaming::ExpertCachePolicy::DEFAULT,
)
.map_err(|e| {
RealForwardError::Unsupported(format!(
"vision block streamer: {e} ({VISION_SLOTS} slots x {:.1} MiB per block)",
layer.expert_stride as f64 / (1024.0 * 1024.0),
))
})?;
let mut slot_buffers = Vec::with_capacity(VISION_SLOTS);
for slot in 0..VISION_SLOTS {
let (ptr, len) = streamer.slot_allocation(slot);
slot_buffers.push(
gpu::wrap_page_aligned_no_copy(context.device(), ptr, len)
.map_err(RealForwardError::Gpu)?,
);
}
(
Some(streamer),
slot_buffers,
None,
None,
VISION_SLOTS as u64 * layer.expert_stride,
)
};
let resident = VisionResident::resolve(index, &shape)?;
let pos_table = read_fp16_host(
weights,
resident.pos_embed_host,
shape.pos_rows * shape.hidden,
)?;
Ok(Self {
slot_buffers,
streamer,
mapped_buffer,
mapped_layer,
blocks,
resident,
pos_table,
shape,
slot_bytes,
last_scratch_bytes: 0,
overflow: overflow::VisionOverflowCapture::from_env(),
sidecar_weights: None,
mlp_tile_rows: scratch::VISION_MLP_TILE_ROWS,
})
}
pub(crate) fn is_sidecar(&self) -> bool {
self.sidecar_weights.is_some()
}
#[allow(dead_code)]
pub(crate) fn is_mapped_residency(&self) -> bool {
self.mapped_buffer.is_some()
}
pub(crate) fn run(
&mut self,
context: &mut gpu::MetalContext,
weights: &gpu::ResidentGpuWeights,
image: &PreprocessedImage,
params: &PreprocessParams,
) -> Result<VisionEmbedding, RealForwardError> {
self.run_inner(context, weights, image, params, None)
.map(|(e, _)| e)
}
pub(crate) fn run_with_stages(
&mut self,
context: &mut gpu::MetalContext,
weights: &gpu::ResidentGpuWeights,
image: &PreprocessedImage,
params: &PreprocessParams,
) -> Result<(VisionEmbedding, VisionStages), RealForwardError> {
let (embedding, stages) = self.run_inner(
context,
weights,
image,
params,
Some(VisionStages::default()),
)?;
Ok((embedding, stages.expect("requested just above")))
}
fn run_inner(
&mut self,
context: &mut gpu::MetalContext,
weights: &gpu::ResidentGpuWeights,
image: &PreprocessedImage,
params: &PreprocessParams,
mut capture: Option<VisionStages>,
) -> Result<(VisionEmbedding, Option<VisionStages>), RealForwardError> {
if params.patch_dim() != self.shape.patch_dim {
return Err(RealForwardError::Unsupported(format!(
"preprocessing produced {}-element patch rows but this tower's patch embedding \
takes {}",
params.patch_dim(),
self.shape.patch_dim
)));
}
if params.merge_size != self.shape.merge {
return Err(RealForwardError::Unsupported(format!(
"preprocessing used a merge size of {} but this tower declares {}",
params.merge_size, self.shape.merge
)));
}
let seq = image.grid.patches();
let rows: Vec<half::f16> = image
.patch_rows
.iter()
.map(|&v| half::f16::from_f32(v))
.collect();
let freqs: Vec<f32> = turbospark_vision_io::rope::vision_rope_freq_rows_default(
image.grid,
self.shape.head_dim,
params,
)
.map_err(|e| RealForwardError::Unsupported(format!("vision rope rows: {e}")))?;
let table =
turbospark_vision_io::pos_embed_weights(image.grid, self.shape.pos_side, params)
.map_err(|e| {
RealForwardError::Unsupported(format!("vision position table: {e}"))
})?;
let pos = stages::pos_embed_rows(&self.pos_table, &table, &self.shape)?;
gpu::autorelease_pool(|| {
let mut deepstack_out: Vec<DeepstackRows> =
Vec::with_capacity(self.shape.deepstack.len());
let s = VisionScratch::allocate(
context,
&self.shape,
seq,
self.mlp_tile_rows,
&rows,
&freqs,
&pos,
)?;
self.last_scratch_bytes = s.bytes;
let pass = context.begin_pass();
stages::encode_patch_embed(
context,
&pass,
self.sidecar_weights.as_ref().unwrap_or(weights),
&self.resident,
&s,
&self.shape,
)?;
pass.commit_and_wait();
let wide = seq * self.shape.hidden;
if let Some(c) = capture.as_mut() {
c.patch_embed = read_stage(&s.x, wide);
}
if let Some(o) = self.overflow.as_mut() {
o.note_image_start();
}
for n in 0..self.shape.depth {
let pass = context.begin_pass();
let mapped_active = self.mapped_buffer.is_some();
if let Some(mapped_buffer) = self.mapped_buffer.as_ref() {
let mapping = self
.mapped_layer
.as_ref()
.expect("mapped_buffer implies mapped_layer");
let base = mapping.expert_offset(n).map_err(|e| {
RealForwardError::Unsupported(format!(
"vision block {n} mapped offset: {e}"
))
})?;
block::encode_block(
context,
&pass,
mapped_buffer,
base,
&self.blocks[n],
&s,
&self.shape,
self.mlp_tile_rows,
)?;
} else {
let slot = n % VISION_SLOTS;
self.streamer
.as_mut()
.expect("pread arm: streamer opened in VisionTower::open")
.load_expert_into_slot(0, n, slot)
.map_err(|e| {
RealForwardError::Unsupported(format!("vision block {n} read: {e}"))
})?;
block::encode_block(
context,
&pass,
&self.slot_buffers[slot],
0,
&self.blocks[n],
&s,
&self.shape,
self.mlp_tile_rows,
)?;
}
let needs_sync = capture.is_some() || self.overflow.is_some();
if mapped_active && !needs_sync {
pass.commit();
} else {
pass.commit_and_wait();
}
if let Some(c) = capture.as_mut() {
if n == 0 {
c.block_first = read_stage(&s.x, wide);
}
if n + 1 == self.shape.depth {
c.block_last = read_stage(&s.x, wide);
}
}
if let Some(o) = self.overflow.as_mut() {
o.check_block(n, &s.x, wide)?;
}
if let Some(k) = self.shape.deepstack.iter().position(|&i| i == n) {
let merger = self.resident.deepstack_mergers.get(k).ok_or_else(|| {
RealForwardError::Unsupported(format!(
"deepstack block {n} is slot {k} of {} resolved mergers; the \
install's resident index is missing the merger this config \
declares",
self.resident.deepstack_mergers.len()
))
})?;
let pass = context.begin_pass();
stages::encode_deepstack_merger(
context,
&pass,
self.sidecar_weights.as_ref().unwrap_or(weights),
merger,
&s,
&self.shape,
)?;
pass.commit_and_wait();
let count = s.merged * self.shape.out_hidden;
let ds_rows: Vec<u16> = gpu::read_buffer_f16(&s.out, 0, count)
.into_iter()
.map(|v| v.to_bits())
.collect();
if let Some(c) = capture.as_mut() {
c.deepstack.push(ds_rows.clone());
}
deepstack_out.push(DeepstackRows { rows: ds_rows });
}
}
let pass = context.begin_pass();
stages::encode_merger(
context,
&pass,
self.sidecar_weights.as_ref().unwrap_or(weights),
&self.resident,
&s,
&self.shape,
)?;
pass.commit_and_wait();
let count = s.merged * self.shape.out_hidden;
let rows: Vec<u16> = gpu::read_buffer_f16(&s.out, 0, count)
.into_iter()
.map(|v| v.to_bits())
.collect();
Ok((
VisionEmbedding {
rows,
merged_tokens: s.merged,
out_hidden: self.shape.out_hidden,
grid: image.grid,
deepstack: deepstack_out,
},
capture,
))
})
}
pub(crate) fn scratch_bytes(&self, seq: usize) -> u64 {
scratch::scratch_bytes(&self.shape, seq, self.mlp_tile_rows)
}
}
fn read_stage(buffer: &gpu::MetalBuffer, count: usize) -> Vec<u16> {
gpu::read_buffer_f16(buffer, 0, count)
.into_iter()
.map(|v| v.to_bits())
.collect()
}
fn read_fp16_host(
weights: &gpu::ResidentGpuWeights,
local_offset: usize,
elems: usize,
) -> Result<Vec<f32>, RealForwardError> {
let bytes = weights
.data()
.get(local_offset..local_offset + elems * 2)
.ok_or_else(|| {
RealForwardError::Unsupported(format!(
"vision tensor at resident offset {local_offset} spans past the mapped region"
))
})?;
Ok(bytes
.chunks_exact(2)
.map(|c| half::f16::from_bits(u16::from_le_bytes([c[0], c[1]])).to_f32())
.collect())
}