use model_io::LoadGuard;
use crate::vision::shape::VisionShape;
pub(crate) fn scratch_bytes_for_pixels(shape: &VisionShape, pixels: usize, tile: usize) -> u64 {
crate::vision::scratch::scratch_bytes(shape, patches_for_pixel_ceiling(shape, pixels), tile)
}
fn patches_for_pixel_ceiling(shape: &VisionShape, pixels: usize) -> usize {
let spatial_factor = shape.patch_size * shape.merge;
let factor_sq = spatial_factor * spatial_factor;
if factor_sq == 0 {
return 0;
}
let rounded_pixels = (pixels / factor_sq) * factor_sq;
rounded_pixels / (shape.patch_size * shape.patch_size)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PixelBudget {
pub resolved_max_pixels: usize,
pub clamped: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VisionBudgetTooSmall {
pub declared_max_pixels: usize,
pub min_pixels: usize,
pub needs: u64,
pub available: u64,
pub reserve: u64,
pub physical: u64,
pub committed: u64,
pub kv_bytes: u64,
}
impl std::fmt::Display for VisionBudgetTooSmall {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"no page within the declared {}-pixel budget fits this session's memory: even the \
{}-pixel floor needs {} of vision scratch; {} available ({} physical - {} \
weights/expert cache - {} KV cache - {} reserve)",
self.declared_max_pixels,
self.min_pixels,
gib(self.needs),
gib(self.available),
gib(self.physical),
gib(self.committed),
gib(self.kv_bytes),
gib(self.reserve),
)
}
}
impl std::error::Error for VisionBudgetTooSmall {}
fn gib(bytes: u64) -> String {
format!("{:.1} GiB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn resolve_max_pixels(
declared_max_pixels: usize,
min_pixels: usize,
shape: &VisionShape,
tile: usize,
guard: LoadGuard,
physical: u64,
committed: u64,
kv_bytes: u64,
) -> Result<PixelBudget, VisionBudgetTooSmall> {
if matches!(guard, LoadGuard::Off) {
return Ok(PixelBudget {
resolved_max_pixels: declared_max_pixels,
clamped: false,
});
}
let spoken_for = committed.saturating_add(kv_bytes);
let available = guard.available(physical, spoken_for);
if scratch_bytes_for_pixels(shape, declared_max_pixels, tile) <= available {
return Ok(PixelBudget {
resolved_max_pixels: declared_max_pixels,
clamped: false,
});
}
let needs_at_min = scratch_bytes_for_pixels(shape, min_pixels, tile);
if needs_at_min > available {
return Err(VisionBudgetTooSmall {
declared_max_pixels,
min_pixels,
needs: needs_at_min,
available,
reserve: guard.budget().reserve_bytes,
physical,
committed,
kv_bytes,
});
}
let (mut lo, mut hi) = (min_pixels, declared_max_pixels);
while hi - lo > 1 {
let mid = lo + (hi - lo) / 2;
if scratch_bytes_for_pixels(shape, mid, tile) <= available {
lo = mid;
} else {
hi = mid;
}
}
Ok(PixelBudget {
resolved_max_pixels: lo,
clamped: true,
})
}
impl crate::real_forward::RealForwardRunner {
pub fn resolve_vision_pixel_budget(
&self,
declared_max_pixels: usize,
min_pixels: usize,
guard: LoadGuard,
physical: u64,
committed: u64,
kv_bytes: u64,
) -> Result<PixelBudget, VisionBudgetTooSmall> {
let tile = self
.vision
.as_ref()
.map(|v| v.mlp_tile_rows)
.unwrap_or(crate::vision::scratch::VISION_MLP_TILE_ROWS);
match VisionShape::resolve(&self.arch.vision) {
Ok(shape) => resolve_max_pixels(
declared_max_pixels,
min_pixels,
&shape,
tile,
guard,
physical,
committed,
kv_bytes,
),
Err(_) => Ok(PixelBudget {
resolved_max_pixels: declared_max_pixels,
clamped: false,
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vision::scratch::VISION_MLP_TILE_ROWS;
fn qwen38_tower_shape() -> VisionShape {
VisionShape {
depth: 27,
hidden: 1152,
intermediate: 4304,
heads: 16,
head_dim: 72,
merge: 2,
out_hidden: 5120,
patch_dim: 2 * 16 * 16 * 3,
pos_rows: 48 * 48,
pos_side: 48,
patch_size: 16,
deepstack: Vec::new(),
}
}
const GIB: u64 = 1024 * 1024 * 1024;
const REAL_MIN_PIXELS: usize = 65_536;
const REAL_MAX_PIXELS: usize = 16_777_216;
#[test]
fn off_never_clamps_at_any_machine_size() {
let shape = qwen38_tower_shape();
for physical in [0, GIB, 4 * GIB, 36 * GIB] {
let budget = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
VISION_MLP_TILE_ROWS,
LoadGuard::Off,
physical,
0,
0,
)
.expect("Off never refuses");
assert_eq!(budget.resolved_max_pixels, REAL_MAX_PIXELS);
assert!(!budget.clamped);
}
}
const SMALL_MACHINE: u64 = 4 * GIB + 512 * 1024 * 1024;
#[test]
fn a_small_machine_clamps_the_real_ceiling() {
let shape = qwen38_tower_shape();
let budget = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
VISION_MLP_TILE_ROWS,
LoadGuard::Relaxed,
SMALL_MACHINE,
0,
0,
)
.expect("min_pixels fits a 4.5 GiB machine");
assert!(budget.clamped);
assert!(budget.resolved_max_pixels < REAL_MAX_PIXELS);
assert!(budget.resolved_max_pixels >= REAL_MIN_PIXELS);
let available = LoadGuard::Relaxed.available(SMALL_MACHINE, 0);
assert!(
scratch_bytes_for_pixels(&shape, budget.resolved_max_pixels, VISION_MLP_TILE_ROWS)
<= available
);
}
#[test]
fn a_36_gib_machine_and_the_real_tower_ceiling() {
let shape = qwen38_tower_shape();
let budget = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
VISION_MLP_TILE_ROWS,
LoadGuard::Relaxed,
36 * GIB,
0,
0,
)
.expect("min_pixels fits a 36 GiB machine");
assert!(
!budget.clamped,
"expected the real tower's declared ceiling to fit a 36 GiB machine under Relaxed \
with nothing else committed; it did not (resolved {} of {})",
budget.resolved_max_pixels, REAL_MAX_PIXELS
);
assert_eq!(budget.resolved_max_pixels, REAL_MAX_PIXELS);
}
#[test]
fn below_min_pixels_refuses_with_the_subtraction_shown() {
let shape = qwen38_tower_shape();
let err = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
VISION_MLP_TILE_ROWS,
LoadGuard::Relaxed,
4 * GIB,
4 * GIB, 0,
)
.expect_err("min_pixels cannot fit a fully-committed machine");
assert_eq!(err.declared_max_pixels, REAL_MAX_PIXELS);
assert_eq!(err.min_pixels, REAL_MIN_PIXELS);
assert!(err.needs > 0);
let message = err.to_string();
assert!(message.contains(&REAL_MAX_PIXELS.to_string()));
assert!(message.contains(&REAL_MIN_PIXELS.to_string()));
assert!(message.contains("physical"));
assert!(message.contains("GiB"));
}
#[test]
fn a_stricter_tier_clamps_at_least_as_hard() {
let shape = qwen38_tower_shape();
let relaxed = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
VISION_MLP_TILE_ROWS,
LoadGuard::Relaxed,
SMALL_MACHINE,
0,
0,
)
.expect("relaxed resolves on a 4.5 GiB machine");
assert!(
relaxed.clamped,
"this test needs Relaxed itself to clamp, or the comparison below is vacuous"
);
if let Ok(strict) = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
VISION_MLP_TILE_ROWS,
LoadGuard::Strict,
SMALL_MACHINE,
0,
0,
) {
assert!(strict.resolved_max_pixels <= relaxed.resolved_max_pixels);
}
}
#[test]
fn the_small_machine_test_is_sensitive_to_the_scratch_formula() {
let shape = qwen38_tower_shape();
let uncapped_tile = usize::MAX;
let with_cap = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
VISION_MLP_TILE_ROWS,
LoadGuard::Relaxed,
SMALL_MACHINE,
0,
0,
)
.expect("min_pixels fits");
let without_cap = resolve_max_pixels(
REAL_MAX_PIXELS,
REAL_MIN_PIXELS,
&shape,
uncapped_tile,
LoadGuard::Relaxed,
SMALL_MACHINE,
0,
0,
)
.expect("min_pixels fits");
assert!(
without_cap.resolved_max_pixels != with_cap.resolved_max_pixels,
"the machine-size test must be sensitive to the tile term: dropping it moved \
nothing, which means the test cannot see a broken formula"
);
}
}