sokuto_core/contract/
model_spec.rs1use crate::error::{CoreError, Result};
7
8pub const TENSOR_INPUT_IDS: &str = "input_ids";
10
11pub const TENSOR_ATTENTION_MASK: &str = "attention_mask";
13
14pub const TENSOR_OP_INDICES: &str = "op_indices";
16
17pub const TENSOR_LOGITS: &str = "logits";
19
20pub const TENSOR_STATE_HIDDEN_STATES: &str = "state_hidden_states";
22
23pub const TENSOR_STATE_MASK: &str = "state_mask";
25
26pub const TENSOR_QUERY_IDS: &str = "query_ids";
28
29pub const TENSOR_QUERY_MASK: &str = "query_mask";
31
32pub const TOKEN_OPTION_MARKER: &str = "[OP]";
34
35pub const MAX_SEQUENCE_LENGTH: usize = 8192;
37
38pub const MAX_NUM_OPTIONS: usize = 255;
40
41pub const MIN_NUM_OPTIONS: usize = 1;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct ModelInputDimensions {
47 pub batch_size: usize,
49 pub sequence_length: usize,
51 pub num_options: usize,
53}
54
55impl ModelInputDimensions {
56 pub fn new(batch_size: usize, sequence_length: usize, num_options: usize) -> Result<Self> {
58 if batch_size == 0 {
59 return Err(CoreError::MathError {
60 message: "バッチサイズは 1 以上である必要があります。".to_string(),
61 });
62 }
63 if sequence_length == 0 || sequence_length > MAX_SEQUENCE_LENGTH {
64 return Err(CoreError::MathError {
65 message: format!(
66 "入力系列長は 1〜{MAX_SEQUENCE_LENGTH} の範囲内である必要がありますが、{sequence_length} が指定されました。"
67 ),
68 });
69 }
70 if !(MIN_NUM_OPTIONS..=MAX_NUM_OPTIONS).contains(&num_options) {
71 return Err(CoreError::InvalidChoiceCount { count: num_options });
72 }
73
74 Ok(Self {
75 batch_size,
76 sequence_length,
77 num_options,
78 })
79 }
80
81 pub fn validate_tensor_shapes(
83 input_ids_shape: &[usize],
84 attention_mask_shape: &[usize],
85 op_indices_shape: &[usize],
86 ) -> Result<Self> {
87 if input_ids_shape.len() != 2 {
88 return Err(CoreError::MathError {
89 message: format!(
90 "{TENSOR_INPUT_IDS} は 2 次元テンソル [batch, seq_len] である必要があります: {input_ids_shape:?}。"
91 ),
92 });
93 }
94 if attention_mask_shape != input_ids_shape {
95 return Err(CoreError::MathError {
96 message: format!(
97 "{TENSOR_ATTENTION_MASK} の形状 {attention_mask_shape:?} は {TENSOR_INPUT_IDS} の形状 {input_ids_shape:?} と一致する必要があります。"
98 ),
99 });
100 }
101 if op_indices_shape.len() != 2 {
102 return Err(CoreError::MathError {
103 message: format!(
104 "{TENSOR_OP_INDICES} は 2 次元テンソル [batch, num_options] である必要があります: {op_indices_shape:?}。"
105 ),
106 });
107 }
108 if op_indices_shape[0] != input_ids_shape[0] {
109 return Err(CoreError::MathError {
110 message: format!(
111 "{TENSOR_OP_INDICES} のバッチサイズ {} は {TENSOR_INPUT_IDS} のバッチサイズ {} と一致する必要があります。",
112 op_indices_shape[0], input_ids_shape[0]
113 ),
114 });
115 }
116
117 Self::new(input_ids_shape[0], input_ids_shape[1], op_indices_shape[1])
118 }
119
120 pub fn validate_output_shape(&self, logits_shape: &[usize]) -> Result<()> {
122 if logits_shape.len() != 2 {
123 return Err(CoreError::MathError {
124 message: format!(
125 "{TENSOR_LOGITS} は 2 次元テンソル [batch, num_options] である必要があります: {logits_shape:?}。"
126 ),
127 });
128 }
129 if logits_shape[0] != self.batch_size || logits_shape[1] != self.num_options {
130 return Err(CoreError::MathError {
131 message: format!(
132 "{TENSOR_LOGITS} の形状 {logits_shape:?} は期待される形状 [{}, {}] と一致しません。",
133 self.batch_size, self.num_options
134 ),
135 });
136 }
137 Ok(())
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_tensor_shape_validation_success() {
147 let input_ids = [2, 128];
148 let attention_mask = [2, 128];
149 let op_indices = [2, 4];
150
151 let dims =
152 ModelInputDimensions::validate_tensor_shapes(&input_ids, &attention_mask, &op_indices)
153 .unwrap();
154
155 assert_eq!(dims.batch_size, 2);
156 assert_eq!(dims.sequence_length, 128);
157 assert_eq!(dims.num_options, 4);
158
159 assert!(dims.validate_output_shape(&[2, 4]).is_ok());
161
162 assert!(dims.validate_output_shape(&[2, 5]).is_err());
164 }
165
166 #[test]
167 fn test_tensor_shape_validation_mismatch() {
168 let res = ModelInputDimensions::validate_tensor_shapes(&[1, 64], &[1, 32], &[1, 3]);
170 assert!(res.is_err());
171
172 let res2 = ModelInputDimensions::validate_tensor_shapes(&[2, 64], &[2, 64], &[1, 3]);
174 assert!(res2.is_err());
175 }
176}