#![deny(missing_docs)]
pub mod nixl;
use thiserror::Error;
use crate::block_manager::storage::{Storage, StorageAllocator};
use crate::common::dtype::DType;
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use tracing::instrument;
use validator::Validate;
use super::storage::StorageType;
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum LayoutError {
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("Validation failed: {0}")]
ValidationError(#[from] validator::ValidationErrors),
#[error("Invalid block index: {0}")]
InvalidBlockIndex(usize),
#[error("Invalid layer index: {0}")]
InvalidLayerIndex(usize),
#[error("Operation failed: {0}")]
OperationFailed(String),
#[error("Serialization error: {0}")]
SerdeError(#[from] serde_json::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LayoutType {
FullyContiguous,
}
pub trait BlockLayout:
BlockLayoutConfig + BlockLayoutLookup + Send + Sync + std::fmt::Debug
{
type StorageType: Storage;
fn storage(&self) -> Vec<&Self::StorageType>;
fn storage_mut(&mut self) -> Vec<&mut Self::StorageType>;
fn storage_type(&self) -> StorageType;
}
pub trait BlockLayoutConfig: std::fmt::Debug {
fn layout_type(&self) -> LayoutType;
fn num_blocks(&self) -> usize;
fn num_layers(&self) -> usize;
fn page_size(&self) -> usize;
fn inner_dim(&self) -> usize;
}
pub trait BlockLayoutLookup {
fn memory_region_addr(&self, block_idx: usize, layer_idx: usize) -> Result<u64, LayoutError>;
fn memory_region_size(&self) -> usize;
}
#[derive(Debug, Clone, Builder, Validate, Serialize, Deserialize)]
pub struct LayoutConfig {
#[validate(range(min = 1))]
pub num_blocks: usize,
#[validate(range(min = 1))]
pub num_layers: usize,
#[validate(range(min = 1))]
pub page_size: usize,
#[validate(range(min = 1))]
pub inner_dim: usize,
#[validate(custom(function = "validate_power_of_2"))]
#[builder(default = "1")]
pub alignment: usize,
#[builder(default = "DType::FP16")]
pub dtype: DType,
}
impl LayoutConfig {
pub fn builder() -> LayoutConfigBuilder {
LayoutConfigBuilder::default()
}
}
fn validate_power_of_2(alignment: usize) -> Result<(), validator::ValidationError> {
if !alignment.is_power_of_two() {
return Err(validator::ValidationError::new(
"alignment_must_be_power_of_2",
));
}
Ok(())
}
fn align_up(value: usize, alignment: usize) -> usize {
(value + alignment - 1) & !(alignment - 1)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct FullyContiguousConfig {
inner: LayoutConfig,
memory_region_size: usize,
layer_stride_in_bytes: usize,
natural_block_stride: usize,
block_stride_in_bytes: usize, layout_data_bytes: usize, }
impl FullyContiguousConfig {
fn new(config: LayoutConfig) -> Result<Self, LayoutError> {
config.validate()?;
let alignment = config.alignment;
let memory_region_size = config.page_size * config.inner_dim * config.dtype.size_in_bytes();
let layer_stride_in_bytes = memory_region_size;
let natural_block_stride = config.num_layers * layer_stride_in_bytes;
let block_stride_in_bytes = if alignment > 1 {
align_up(natural_block_stride, alignment)
} else {
natural_block_stride
};
let layout_data_bytes =
(config.num_blocks - 1) * block_stride_in_bytes + natural_block_stride;
Ok(Self {
inner: config,
memory_region_size,
layer_stride_in_bytes,
natural_block_stride,
block_stride_in_bytes,
layout_data_bytes,
})
}
pub fn required_allocation_size(&self) -> usize {
let initial_padding = if self.inner.alignment > 1 {
self.inner.alignment - 1
} else {
0
};
self.layout_data_bytes + initial_padding
}
}
impl BlockLayoutConfig for FullyContiguousConfig {
fn layout_type(&self) -> LayoutType {
LayoutType::FullyContiguous
}
fn num_blocks(&self) -> usize {
self.inner.num_blocks
}
fn num_layers(&self) -> usize {
self.inner.num_layers
}
fn page_size(&self) -> usize {
self.inner.page_size
}
fn inner_dim(&self) -> usize {
self.inner.inner_dim
}
}
#[derive(Debug)]
pub struct FullyContiguous<S: Storage> {
config: FullyContiguousConfig,
storage: S,
storage_type: StorageType,
base_offset: usize,
}
impl<S: Storage> FullyContiguous<S> {
#[instrument(level = "debug", skip(storage), fields(config = ?config))]
pub fn new(config: LayoutConfig, storage: Vec<S>) -> Result<Self, LayoutError> {
let config = FullyContiguousConfig::new(config)?;
if storage.len() != 1 {
return Err(LayoutError::InvalidConfig(
"FullyContiguous layout requires exactly one storage region".to_string(),
));
}
let mut storage = storage;
let storage = storage.remove(0);
let storage_type = storage.storage_type();
let provided_size = storage.size();
let storage_addr = storage.addr();
let alignment = config.inner.alignment;
let base_offset = if alignment > 1 {
align_up(storage_addr as usize, alignment) - storage_addr as usize
} else {
0
};
let total_required_size_with_offset = base_offset + config.layout_data_bytes;
tracing::debug!(
provided_size,
total_required_size_with_offset,
base_offset,
required_layout_data_bytes = config.layout_data_bytes,
alignment,
"Validating storage size with base offset and alignment"
);
if provided_size < total_required_size_with_offset {
tracing::warn!(
provided_size,
total_required_size_with_offset,
"Storage size too small for aligned layout including base offset"
);
return Err(LayoutError::InvalidConfig(format!(
"Storage size {} is less than required size {} (including base offset for alignment)",
provided_size,
total_required_size_with_offset
)));
}
tracing::debug!(
config.memory_region_size,
config.layer_stride_in_bytes,
config.block_stride_in_bytes,
config.natural_block_stride,
alignment = config.inner.alignment,
base_offset,
"Calculated layout strides (aligned)"
);
Ok(Self {
config,
storage,
storage_type,
base_offset,
})
}
pub(crate) fn new_internal(
config: FullyContiguousConfig,
storage: S,
base_offset: usize,
storage_type: StorageType,
) -> Result<Self, LayoutError> {
Ok(Self {
config,
storage,
storage_type,
base_offset,
})
}
#[instrument(level = "debug", skip(allocator), fields(config = ?config))]
pub fn allocate(
config: LayoutConfig,
allocator: &dyn StorageAllocator<S>,
) -> Result<Self, LayoutError> {
let config = FullyContiguousConfig::new(config)?;
let bytes_to_allocate = config.required_allocation_size();
tracing::debug!(
bytes_to_allocate,
alignment = config.inner.alignment,
"Calculated storage size for allocation (with alignment padding)"
);
let storage = allocator.allocate(bytes_to_allocate).map_err(|e| {
LayoutError::OperationFailed(format!("Storage allocation failed: {}", e))
})?;
tracing::debug!(
allocated_size = storage.size(),
allocated_addr = storage.addr(),
"Storage allocated successfully"
);
Self::new(config.inner, vec![storage])
}
}
impl<S: Storage> BlockLayout for FullyContiguous<S> {
type StorageType = S;
fn storage(&self) -> Vec<&Self::StorageType> {
vec![&self.storage]
}
fn storage_mut(&mut self) -> Vec<&mut Self::StorageType> {
vec![&mut self.storage]
}
fn storage_type(&self) -> StorageType {
self.storage_type.clone()
}
}
impl<S: Storage> BlockLayoutConfig for FullyContiguous<S> {
fn layout_type(&self) -> LayoutType {
LayoutType::FullyContiguous
}
fn num_blocks(&self) -> usize {
self.config.inner.num_blocks
}
fn num_layers(&self) -> usize {
self.config.inner.num_layers
}
fn page_size(&self) -> usize {
self.config.inner.page_size
}
fn inner_dim(&self) -> usize {
self.config.inner.inner_dim
}
}
impl<S: Storage> BlockLayoutLookup for FullyContiguous<S> {
fn memory_region_addr(&self, block_idx: usize, layer_idx: usize) -> Result<u64, LayoutError> {
if block_idx >= self.num_blocks() {
return Err(LayoutError::InvalidBlockIndex(block_idx));
}
if layer_idx >= self.num_layers() {
return Err(LayoutError::InvalidLayerIndex(layer_idx));
}
let aligned_start_addr = self.storage.addr() + self.base_offset as u64;
let block_offset = block_idx * self.config.block_stride_in_bytes;
let layer_offset = layer_idx * self.config.layer_stride_in_bytes;
let final_addr = aligned_start_addr + block_offset as u64 + layer_offset as u64;
Ok(final_addr)
}
fn memory_region_size(&self) -> usize {
self.config.memory_region_size
}
}
#[allow(missing_docs)]
#[cfg(test)]
pub mod tests {
use super::*;
use crate::block_manager::storage::tests::{NullDeviceAllocator, NullDeviceStorage};
use crate::block_manager::storage::{StorageType, SystemAllocator};
use crate::common::dtype::DType;
use dynamo_runtime::logging::init as init_logging;
const NUM_BLOCKS: usize = 7;
const NUM_LAYERS: usize = 5;
const PAGE_SIZE: usize = 4;
const INNER_DIM: usize = 13;
const DTYPE: DType = DType::FP32;
fn calculate_expected_offset(
base_addr: u64,
block_idx: usize,
layer_idx: usize,
block_stride: usize,
layer_stride: usize,
) -> u64 {
base_addr + (block_idx * block_stride + layer_idx * layer_stride) as u64
}
pub fn setup_layout(
alignment: Option<usize>, ) -> Result<FullyContiguous<NullDeviceStorage>, LayoutError> {
let config = LayoutConfig {
num_blocks: NUM_BLOCKS,
num_layers: NUM_LAYERS,
page_size: PAGE_SIZE,
inner_dim: INNER_DIM,
alignment: alignment.unwrap_or(1),
dtype: DTYPE,
};
FullyContiguous::allocate(config, &NullDeviceAllocator)
}
#[test]
fn test_fc_creation_invalid_alignment() {
let config = LayoutConfig::builder()
.num_blocks(NUM_BLOCKS)
.num_layers(NUM_LAYERS)
.page_size(PAGE_SIZE)
.inner_dim(INNER_DIM)
.alignment(3)
.build()
.unwrap();
assert!(config.validate().is_err());
}
#[test]
fn test_fc_creation_success() {
let layout_result = setup_layout(None);
assert!(
layout_result.is_ok(),
"Layout creation failed: {:?}",
layout_result.err()
);
}
#[test]
fn test_fc_creation_insufficient_storage() {
init_logging();
let config = LayoutConfig {
num_blocks: NUM_BLOCKS,
num_layers: NUM_LAYERS,
page_size: PAGE_SIZE,
inner_dim: INNER_DIM,
alignment: 1,
dtype: DTYPE,
};
let fc_config = FullyContiguousConfig::new(config.clone()).unwrap();
let required_size = fc_config.required_allocation_size();
let storage = NullDeviceStorage::new((required_size - 1) as u64);
let layout_result = FullyContiguous::new(config, vec![storage]);
assert!(layout_result.is_err());
match layout_result.err().unwrap() {
LayoutError::InvalidConfig(_) => {} e => panic!("Expected InvalidConfig error, got {:?}", e),
}
}
#[test]
fn test_fc_accessor_methods() {
let layout = setup_layout(None).expect("Layout setup failed");
assert_eq!(layout.num_blocks(), NUM_BLOCKS);
assert_eq!(layout.num_layers(), NUM_LAYERS);
assert_eq!(layout.page_size(), PAGE_SIZE);
assert_eq!(layout.inner_dim(), INNER_DIM);
}
#[test]
fn test_fc_memory_region_size() {
let layout = setup_layout(None).expect("Layout setup failed");
let expected_region_size = PAGE_SIZE * INNER_DIM * DTYPE.size_in_bytes();
assert_eq!(layout.memory_region_size(), expected_region_size);
}
#[test]
fn test_fc_offset_calculation() {
let layout = setup_layout(None).expect("Layout setup failed");
let dims = layout.config.clone();
let block_stride = dims.block_stride_in_bytes;
let layer_stride = dims.layer_stride_in_bytes;
let base_addr = layout.storage.addr() + layout.base_offset as u64;
let expected_offset_0_0 =
calculate_expected_offset(base_addr, 0, 0, block_stride, layer_stride);
assert_eq!(
layout.memory_region_addr(0, 0).unwrap(),
expected_offset_0_0
);
let last_layer_idx = NUM_LAYERS - 1;
let expected_offset_0_last =
calculate_expected_offset(base_addr, 0, last_layer_idx, block_stride, layer_stride);
assert_eq!(
layout.memory_region_addr(0, last_layer_idx).unwrap(),
expected_offset_0_last
);
let last_block_idx = NUM_BLOCKS - 1;
let expected_offset_last_0 =
calculate_expected_offset(base_addr, last_block_idx, 0, block_stride, layer_stride);
assert_eq!(
layout.memory_region_addr(last_block_idx, 0).unwrap(),
expected_offset_last_0
);
let expected_offset_last_last = calculate_expected_offset(
base_addr,
last_block_idx,
last_layer_idx,
block_stride,
layer_stride,
);
assert_eq!(
layout
.memory_region_addr(last_block_idx, last_layer_idx)
.unwrap(),
expected_offset_last_last
);
let mid_block_idx = NUM_BLOCKS / 2;
let mid_layer_idx = NUM_LAYERS / 2;
let expected_offset_mid_mid = calculate_expected_offset(
base_addr,
mid_block_idx,
mid_layer_idx,
block_stride,
layer_stride,
);
assert_eq!(
layout
.memory_region_addr(mid_block_idx, mid_layer_idx)
.unwrap(),
expected_offset_mid_mid
);
}
#[test]
fn test_fc_invalid_block_index() {
let layout = setup_layout(None).expect("Layout setup failed");
let result = layout.memory_region_addr(NUM_BLOCKS, 0); assert!(result.is_err());
assert!(matches!(
result.err().unwrap(),
LayoutError::InvalidBlockIndex(NUM_BLOCKS)
));
}
#[test]
fn test_fc_invalid_layer_index() {
let layout = setup_layout(None).expect("Layout setup failed");
let result = layout.memory_region_addr(0, NUM_LAYERS); assert!(result.is_err());
assert!(matches!(
result.err().unwrap(),
LayoutError::InvalidLayerIndex(NUM_LAYERS)
));
}
#[test]
fn test_fc_allocation_system() {
init_logging();
let config = LayoutConfig {
num_blocks: NUM_BLOCKS,
num_layers: NUM_LAYERS,
page_size: PAGE_SIZE,
inner_dim: INNER_DIM,
alignment: 1,
dtype: DTYPE,
};
let allocator = SystemAllocator;
let layout_result = FullyContiguous::allocate(config, &allocator);
assert!(layout_result.is_ok());
let layout = layout_result.unwrap();
assert_eq!(layout.num_blocks(), NUM_BLOCKS);
assert_eq!(layout.num_layers(), NUM_LAYERS);
assert_eq!(layout.page_size(), PAGE_SIZE);
assert_eq!(layout.inner_dim(), INNER_DIM);
assert_eq!(layout.storage.storage_type(), StorageType::System);
assert_eq!(
layout.storage.size(),
layout.config.required_allocation_size()
);
assert_eq!(
layout.storage.size(),
NUM_BLOCKS * NUM_LAYERS * PAGE_SIZE * INNER_DIM * DTYPE.size_in_bytes()
);
}
#[test]
fn test_fc_alignment() {
init_logging();
const ALIGNMENT: usize = 256;
let config = LayoutConfig {
num_blocks: NUM_BLOCKS,
num_layers: NUM_LAYERS,
page_size: PAGE_SIZE,
inner_dim: INNER_DIM,
alignment: ALIGNMENT,
dtype: DTYPE,
};
let memory_region_size = PAGE_SIZE * INNER_DIM * DTYPE.size_in_bytes();
assert_eq!(memory_region_size, 208);
let natural_block_stride = NUM_LAYERS * memory_region_size;
assert_eq!(natural_block_stride, 1040);
let aligned_block_stride = align_up(natural_block_stride, ALIGNMENT);
assert_eq!(aligned_block_stride, 1280);
let fc_config = FullyContiguousConfig::new(config.clone()).unwrap();
let expected_allocated_size = fc_config.required_allocation_size();
let allocator = SystemAllocator;
let layout_result = FullyContiguous::allocate(config.clone(), &allocator);
assert!(
layout_result.is_ok(),
"Allocation failed: {:?}",
layout_result.err()
);
let layout = layout_result.unwrap();
assert_eq!(
layout.storage.size(),
expected_allocated_size,
"Allocated storage size mismatch"
);
assert_eq!(
layout.config.block_stride_in_bytes, aligned_block_stride,
"Stored block stride mismatch"
);
let addr_block_0 = layout
.memory_region_addr(0, 0)
.expect("Failed to get addr block 0");
let addr_block_1 = layout
.memory_region_addr(1, 0)
.expect("Failed to get addr block 1");
let addr_block_2 = layout
.memory_region_addr(2, 0)
.expect("Failed to get addr block 2");
assert_eq!(
addr_block_0 % ALIGNMENT as u64,
0,
"Block 0 start address is not aligned"
);
assert_eq!(
addr_block_1 % ALIGNMENT as u64,
0,
"Block 1 start address is not aligned"
);
assert_eq!(
addr_block_2 % ALIGNMENT as u64,
0,
"Block 2 start address is not aligned"
);
assert_eq!(
addr_block_1 - addr_block_0,
aligned_block_stride as u64,
"Stride between block 0 and 1 mismatch"
);
assert_eq!(
addr_block_2 - addr_block_1,
aligned_block_stride as u64,
"Stride between block 1 and 2 mismatch"
);
}
}