use crate::MlxBindingsContext;
use crate::MlxCError;
use crate::{MlxArray, MlxDtype, raw};
impl MlxBindingsContext {
pub fn rope(
&self,
input: &MlxArray,
dimensions: i32,
base: f32,
offset_tokens: i32,
) -> Result<MlxArray, MlxCError> {
let optional_base = raw::mlx_optional_float {
value: base,
has_value: true,
};
self.output_array("apply MLX rotary embedding", |output, stream| {
unsafe {
raw::mlx_fast_rope(
output,
input.raw(),
dimensions,
false,
optional_base,
1.0,
offset_tokens,
MlxArray::empty_raw(),
stream,
)
}
})
}
pub fn rope_with_token_position_offsets(
&self,
input: &MlxArray,
token_position_offsets: &MlxArray,
dimensions: i32,
base: f32,
) -> Result<MlxArray, MlxCError> {
let input_shape = input.shape();
if input_shape.len() != 4
|| input_shape[0] != 1
|| token_position_offsets.shape() != [input_shape[2]]
|| token_position_offsets.dtype() != MlxDtype::Int32
{
return Err(MlxCError {
operation: "apply MLX rotary embedding at token positions",
description: "input must be one rank-four sequence with one int32 offset per token"
.to_owned(),
});
}
let token_batched_input = self.transpose_axes(input, &[2, 1, 0, 3])?;
let optional_base = raw::mlx_optional_float {
value: base,
has_value: true,
};
let token_batched_output =
self.output_array("apply dynamic MLX rotary embedding", |output, stream| {
unsafe {
raw::mlx_fast_rope_dynamic(
output,
token_batched_input.raw(),
dimensions,
false,
optional_base,
1.0,
token_position_offsets.raw(),
MlxArray::empty_raw(),
stream,
)
}
})?;
self.transpose_axes(&token_batched_output, &[2, 1, 0, 3])
}
pub fn rope_with_custom_frequencies(
&self,
input: &MlxArray,
dimensions: i32,
frequency_denominators: &MlxArray,
scale: f32,
offset_tokens: i32,
) -> Result<MlxArray, MlxCError> {
validate_rope_dimensions(input, dimensions, frequency_denominators, scale)?;
let no_base = raw::mlx_optional_float {
value: 0.0,
has_value: false,
};
self.output_array(
"apply MLX rotary embedding with custom frequency denominators",
|output, stream| {
unsafe {
raw::mlx_fast_rope(
output,
input.raw(),
dimensions,
false,
no_base,
scale,
offset_tokens,
frequency_denominators.raw(),
stream,
)
}
},
)
}
pub fn rope_with_custom_frequencies_at_positions(
&self,
input: &MlxArray,
token_position_offsets: &MlxArray,
dimensions: i32,
frequency_denominators: &MlxArray,
scale: f32,
) -> Result<MlxArray, MlxCError> {
let input_shape = input.shape();
if input_shape.len() != 4 || input_shape[0] != 1 {
return Err(MlxCError {
operation: "apply custom MLX rotary embedding at token positions",
description: "input must be one rank-four sequence".to_owned(),
});
}
validate_rope_dimensions(input, dimensions, frequency_denominators, scale)?;
if token_position_offsets.shape() != [input_shape[2]]
|| token_position_offsets.dtype() != MlxDtype::Int32
{
return Err(MlxCError {
operation: "apply custom MLX rotary embedding at token positions",
description: "token positions must be int32 with one offset per token".to_owned(),
});
}
let token_batched_input = self.transpose_axes(input, &[2, 1, 0, 3])?;
let no_base = raw::mlx_optional_float {
value: 0.0,
has_value: false,
};
let token_batched_output = self.output_array(
"apply dynamic MLX rotary embedding with custom frequency denominators",
|output, stream| {
unsafe {
raw::mlx_fast_rope_dynamic(
output,
token_batched_input.raw(),
dimensions,
false,
no_base,
scale,
token_position_offsets.raw(),
frequency_denominators.raw(),
stream,
)
}
},
)?;
self.transpose_axes(&token_batched_output, &[2, 1, 0, 3])
}
}
fn validate_rope_dimensions(
input: &MlxArray,
dimensions: i32,
frequency_denominators: &MlxArray,
scale: f32,
) -> Result<(), MlxCError> {
const OPERATION: &str = "validate MLX rotary embedding dimensions";
if dimensions <= 0 || dimensions % 2 != 0 {
return Err(MlxCError {
operation: OPERATION,
description: "rotary dimensions must be positive and even".to_owned(),
});
}
let frequency_shape = frequency_denominators.shape();
if frequency_shape.as_slice() != [dimensions / 2] {
return Err(MlxCError {
operation: OPERATION,
description: "frequency denominators must contain one value per rotary pair".to_owned(),
});
}
if frequency_denominators.dtype() != MlxDtype::Float32 {
return Err(MlxCError {
operation: OPERATION,
description: "frequency denominators must be Float32".to_owned(),
});
}
if !scale.is_finite() || scale <= 0.0 {
return Err(MlxCError {
operation: OPERATION,
description: "rotary position scale must be positive and finite".to_owned(),
});
}
let input_shape = input.shape();
if input_shape.len() != 4 || dimensions > input_shape[3] {
return Err(MlxCError {
operation: OPERATION,
description: "rotary input must be rank four and contain the rotary width".to_owned(),
});
}
Ok(())
}