ik_llama_cpp_2/context/session.rs
1//! utilities for working with session files
2
3use crate::context::LlamaContext;
4use crate::token::LlamaToken;
5use std::ffi::{CString, NulError};
6use std::fmt::{Debug, Formatter};
7use std::path::{Path, PathBuf};
8
9/// Flags for state sequence operations.
10///
11/// These flags control what parts of the state are included when saving/restoring
12/// sequence state.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct LlamaStateSeqFlags(pub(crate) ik_llama_cpp_sys::llama_state_seq_flags);
15
16impl LlamaStateSeqFlags {
17 /// Work only with partial states, such as SWA KV cache or recurrent cache (e.g. Mamba).
18 ///
19 /// This flag is useful when you only want to save/restore the recurrent state
20 /// without affecting the KV cache.
21 pub const PARTIAL_ONLY: LlamaStateSeqFlags = LlamaStateSeqFlags(1);
22
23 /// Keep the copied data on device (GPU) memory rather than host.
24 ///
25 /// Getting the state for a `seq_id` with this flag invalidates all
26 /// prior states obtained for that `seq_id` with this flag set.
27 pub const ON_DEVICE: LlamaStateSeqFlags = LlamaStateSeqFlags(2);
28
29 /// Create an empty flags set.
30 pub const fn empty() -> LlamaStateSeqFlags {
31 LlamaStateSeqFlags(0)
32 }
33
34 /// Get the raw flags value.
35 pub const fn bits(&self) -> u32 {
36 self.0
37 }
38
39 /// Construct from raw bits.
40 pub const fn from_bits(bits: u32) -> LlamaStateSeqFlags {
41 LlamaStateSeqFlags(bits)
42 }
43
44 /// Check if a flag is set.
45 pub const fn contains(&self, other: LlamaStateSeqFlags) -> bool {
46 (self.0 & other.0) != 0
47 }
48}
49
50impl Default for LlamaStateSeqFlags {
51 fn default() -> Self {
52 Self::empty()
53 }
54}
55
56impl std::ops::BitOr for LlamaStateSeqFlags {
57 type Output = Self;
58 fn bitor(self, other: Self) -> Self {
59 Self(self.0 | other.0)
60 }
61}
62
63/// Errors from the sequence-state save/restore API.
64///
65/// Module-local mirror of the anchor crate's `crate::StateSeqError`, which
66/// lives in the crate root there; kept local here so this module compiles
67/// independently.
68#[derive(Debug, Eq, PartialEq, thiserror::Error)]
69pub enum StateSeqError {
70 /// llama.cpp read or wrote a different number of bytes than expected.
71 #[error("state seq size mismatch: expected {expected}, actual {actual}")]
72 SizeMismatch {
73 /// Number of bytes the caller expected to transfer.
74 expected: usize,
75 /// Number of bytes llama.cpp actually transferred.
76 actual: usize,
77 },
78}
79
80/// Failed to save a sequence state file
81#[derive(Debug, Eq, PartialEq, thiserror::Error)]
82pub enum SaveSeqStateError {
83 /// llama.cpp failed to save the sequence state file
84 #[error("Failed to save sequence state file")]
85 FailedToSave,
86
87 /// null byte in string
88 #[error("null byte in string {0}")]
89 NullError(#[from] NulError),
90
91 /// failed to convert path to str
92 #[error("failed to convert path {0} to str")]
93 PathToStrError(PathBuf),
94}
95
96/// Failed to load a sequence state file
97#[derive(Debug, Eq, PartialEq, thiserror::Error)]
98pub enum LoadSeqStateError {
99 /// llama.cpp failed to load the sequence state file
100 #[error("Failed to load sequence state file")]
101 FailedToLoad,
102
103 /// null byte in string
104 #[error("null byte in string {0}")]
105 NullError(#[from] NulError),
106
107 /// failed to convert path to str
108 #[error("failed to convert path {0} to str")]
109 PathToStrError(PathBuf),
110
111 /// Insufficient max length
112 #[error("max_length is not large enough to hold {n_out} (was {max_tokens})")]
113 InsufficientMaxLength {
114 /// The length of the loaded sequence
115 n_out: usize,
116 /// The maximum length
117 max_tokens: usize,
118 },
119}
120
121/// Failed to save a Session file
122#[derive(Debug, Eq, PartialEq, thiserror::Error)]
123pub enum SaveSessionError {
124 /// llama.cpp failed to save the session file
125 #[error("Failed to save session file")]
126 FailedToSave,
127
128 /// null byte in string
129 #[error("null byte in string {0}")]
130 NullError(#[from] NulError),
131
132 /// failed to convert path to str
133 #[error("failed to convert path {0} to str")]
134 PathToStrError(PathBuf),
135}
136
137/// Failed to load a Session file
138#[derive(Debug, Eq, PartialEq, thiserror::Error)]
139pub enum LoadSessionError {
140 /// llama.cpp failed to load the session file
141 #[error("Failed to load session file")]
142 FailedToLoad,
143
144 /// null byte in string
145 #[error("null byte in string {0}")]
146 NullError(#[from] NulError),
147
148 /// failed to convert path to str
149 #[error("failed to convert path {0} to str")]
150 PathToStrError(PathBuf),
151
152 /// Insufficient max length
153 #[error("max_length is not large enough to hold {n_out} (was {max_tokens})")]
154 InsufficientMaxLength {
155 /// The length of the session file
156 n_out: usize,
157 /// The maximum length
158 max_tokens: usize,
159 },
160}
161
162impl LlamaContext<'_> {
163 /// Save the current session to a file.
164 ///
165 /// # Parameters
166 ///
167 /// * `path_session` - The file to save to.
168 /// * `tokens` - The tokens to associate the session with. This should be a prefix of a sequence of tokens that the context has processed, so that the relevant KV caches are already filled.
169 ///
170 /// # Errors
171 ///
172 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to save the session file.
173 #[deprecated(since = "0.1.136", note = "Use `state_save_file` instead")]
174 pub fn save_session_file(
175 &self,
176 path_session: impl AsRef<Path>,
177 tokens: &[LlamaToken],
178 ) -> Result<(), SaveSessionError> {
179 let path = path_session.as_ref();
180 let path = path
181 .to_str()
182 .ok_or_else(|| SaveSessionError::PathToStrError(path.to_path_buf()))?;
183
184 let cstr = CString::new(path)?;
185
186 if unsafe {
187 ik_llama_cpp_sys::llama_save_session_file(
188 self.context.as_ptr(),
189 cstr.as_ptr(),
190 tokens.as_ptr().cast::<ik_llama_cpp_sys::llama_token>(),
191 tokens.len(),
192 )
193 } {
194 Ok(())
195 } else {
196 Err(SaveSessionError::FailedToSave)
197 }
198 }
199 /// Load a session file into the current context.
200 ///
201 /// You still need to pass the returned tokens to the context for inference to work. What this function buys you is that the KV caches are already filled with the relevant data.
202 ///
203 /// # Parameters
204 ///
205 /// * `path_session` - The file to load from. It must be a session file from a compatible context, otherwise the function will error.
206 /// * `max_tokens` - The maximum token length of the loaded session. If the session was saved with a longer length, the function will error.
207 ///
208 /// # Errors
209 ///
210 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to load the session file. (e.g. the file does not exist, is not a session file, etc.)
211 #[deprecated(since = "0.1.136", note = "Use `state_load_file` instead")]
212 pub fn load_session_file(
213 &mut self,
214 path_session: impl AsRef<Path>,
215 max_tokens: usize,
216 ) -> Result<Vec<LlamaToken>, LoadSessionError> {
217 let path = path_session.as_ref();
218 let path = path
219 .to_str()
220 .ok_or(LoadSessionError::PathToStrError(path.to_path_buf()))?;
221
222 let cstr = CString::new(path)?;
223 let mut tokens: Vec<LlamaToken> = Vec::with_capacity(max_tokens);
224 let mut n_out = 0;
225
226 // SAFETY: cast is valid as LlamaToken is repr(transparent)
227 let tokens_out = tokens.as_mut_ptr().cast::<ik_llama_cpp_sys::llama_token>();
228
229 let load_session_success = unsafe {
230 ik_llama_cpp_sys::llama_load_session_file(
231 self.context.as_ptr(),
232 cstr.as_ptr(),
233 tokens_out,
234 max_tokens,
235 &mut n_out,
236 )
237 };
238 if load_session_success {
239 if n_out > max_tokens {
240 return Err(LoadSessionError::InsufficientMaxLength { n_out, max_tokens });
241 }
242 // SAFETY: we checked that n_out <= max_tokens and llama.cpp promises that n_out tokens will be written
243 unsafe {
244 tokens.set_len(n_out);
245 }
246 Ok(tokens)
247 } else {
248 Err(LoadSessionError::FailedToLoad)
249 }
250 }
251
252 /// Save the full state to a file.
253 ///
254 /// This is the non-deprecated replacement for [`save_session_file`](Self::save_session_file).
255 ///
256 /// # Parameters
257 ///
258 /// * `path_session` - The file to save to.
259 /// * `tokens` - The tokens to associate the state with. This should be a prefix of a sequence
260 /// of tokens that the context has processed, so that the relevant KV caches are already filled.
261 ///
262 /// # Errors
263 ///
264 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to save
265 /// the state file.
266 pub fn state_save_file(
267 &self,
268 path_session: impl AsRef<Path>,
269 tokens: &[LlamaToken],
270 ) -> Result<(), SaveSessionError> {
271 let path = path_session.as_ref();
272 let path = path
273 .to_str()
274 .ok_or_else(|| SaveSessionError::PathToStrError(path.to_path_buf()))?;
275
276 let cstr = CString::new(path)?;
277
278 if unsafe {
279 ik_llama_cpp_sys::llama_state_save_file(
280 self.context.as_ptr(),
281 cstr.as_ptr(),
282 tokens.as_ptr().cast::<ik_llama_cpp_sys::llama_token>(),
283 tokens.len(),
284 )
285 } {
286 Ok(())
287 } else {
288 Err(SaveSessionError::FailedToSave)
289 }
290 }
291
292 /// Load a state file into the current context.
293 ///
294 /// This is the non-deprecated replacement for [`load_session_file`](Self::load_session_file).
295 ///
296 /// You still need to pass the returned tokens to the context for inference to work. What this
297 /// function buys you is that the KV caches are already filled with the relevant data.
298 ///
299 /// # Parameters
300 ///
301 /// * `path_session` - The file to load from. It must be a state file from a compatible context,
302 /// otherwise the function will error.
303 /// * `max_tokens` - The maximum token length of the loaded state. If the state was saved with a
304 /// longer length, the function will error.
305 ///
306 /// # Errors
307 ///
308 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to load
309 /// the state file.
310 pub fn state_load_file(
311 &mut self,
312 path_session: impl AsRef<Path>,
313 max_tokens: usize,
314 ) -> Result<Vec<LlamaToken>, LoadSessionError> {
315 let path = path_session.as_ref();
316 let path = path
317 .to_str()
318 .ok_or(LoadSessionError::PathToStrError(path.to_path_buf()))?;
319
320 let cstr = CString::new(path)?;
321 let mut tokens: Vec<LlamaToken> = Vec::with_capacity(max_tokens);
322 let mut n_out = 0;
323
324 // SAFETY: cast is valid as LlamaToken is repr(transparent)
325 let tokens_out = tokens.as_mut_ptr().cast::<ik_llama_cpp_sys::llama_token>();
326
327 let success = unsafe {
328 ik_llama_cpp_sys::llama_state_load_file(
329 self.context.as_ptr(),
330 cstr.as_ptr(),
331 tokens_out,
332 max_tokens,
333 &mut n_out,
334 )
335 };
336 if success {
337 if n_out > max_tokens {
338 return Err(LoadSessionError::InsufficientMaxLength { n_out, max_tokens });
339 }
340 // SAFETY: we checked that n_out <= max_tokens and llama.cpp promises that n_out tokens will be written
341 unsafe {
342 tokens.set_len(n_out);
343 }
344 Ok(tokens)
345 } else {
346 Err(LoadSessionError::FailedToLoad)
347 }
348 }
349
350 /// Save state for a single sequence to a file.
351 ///
352 /// This enables saving state for individual sequences, which is useful for multi-sequence
353 /// inference scenarios.
354 ///
355 /// # Parameters
356 ///
357 /// * `filepath` - The file to save to.
358 /// * `seq_id` - The sequence ID whose state to save.
359 /// * `tokens` - The tokens to associate with the saved state.
360 ///
361 /// # Errors
362 ///
363 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to save
364 /// the sequence state file.
365 ///
366 /// # Returns
367 ///
368 /// The number of bytes written on success.
369 pub fn state_seq_save_file(
370 &self,
371 filepath: impl AsRef<Path>,
372 seq_id: i32,
373 tokens: &[LlamaToken],
374 ) -> Result<usize, SaveSeqStateError> {
375 let path = filepath.as_ref();
376 let path = path
377 .to_str()
378 .ok_or_else(|| SaveSeqStateError::PathToStrError(path.to_path_buf()))?;
379
380 let cstr = CString::new(path)?;
381
382 let bytes_written = unsafe {
383 ik_llama_cpp_sys::llama_state_seq_save_file(
384 self.context.as_ptr(),
385 cstr.as_ptr(),
386 seq_id,
387 tokens.as_ptr().cast::<ik_llama_cpp_sys::llama_token>(),
388 tokens.len(),
389 )
390 };
391
392 if bytes_written == 0 {
393 Err(SaveSeqStateError::FailedToSave)
394 } else {
395 Ok(bytes_written)
396 }
397 }
398
399 /// Load state for a single sequence from a file.
400 ///
401 /// This enables loading state for individual sequences, which is useful for multi-sequence
402 /// inference scenarios.
403 ///
404 /// # Parameters
405 ///
406 /// * `filepath` - The file to load from.
407 /// * `dest_seq_id` - The destination sequence ID to load the state into.
408 /// * `max_tokens` - The maximum number of tokens to read.
409 ///
410 /// # Errors
411 ///
412 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to load
413 /// the sequence state file.
414 ///
415 /// # Returns
416 ///
417 /// A tuple of `(tokens, bytes_read)` on success.
418 pub fn state_seq_load_file(
419 &mut self,
420 filepath: impl AsRef<Path>,
421 dest_seq_id: i32,
422 max_tokens: usize,
423 ) -> Result<(Vec<LlamaToken>, usize), LoadSeqStateError> {
424 let path = filepath.as_ref();
425 let path = path
426 .to_str()
427 .ok_or(LoadSeqStateError::PathToStrError(path.to_path_buf()))?;
428
429 let cstr = CString::new(path)?;
430 let mut tokens: Vec<LlamaToken> = Vec::with_capacity(max_tokens);
431 let mut n_out = 0;
432
433 // SAFETY: cast is valid as LlamaToken is repr(transparent)
434 let tokens_out = tokens.as_mut_ptr().cast::<ik_llama_cpp_sys::llama_token>();
435
436 let bytes_read = unsafe {
437 ik_llama_cpp_sys::llama_state_seq_load_file(
438 self.context.as_ptr(),
439 cstr.as_ptr(),
440 dest_seq_id,
441 tokens_out,
442 max_tokens,
443 &mut n_out,
444 )
445 };
446
447 if bytes_read == 0 {
448 return Err(LoadSeqStateError::FailedToLoad);
449 }
450
451 if n_out > max_tokens {
452 return Err(LoadSeqStateError::InsufficientMaxLength { n_out, max_tokens });
453 }
454
455 // SAFETY: we checked that n_out <= max_tokens and llama.cpp promises that n_out tokens will be written
456 unsafe {
457 tokens.set_len(n_out);
458 }
459
460 Ok((tokens, bytes_read))
461 }
462
463 /// Returns the maximum size in bytes of the state (rng, logits, embedding
464 /// and `kv_cache`) - will often be smaller after compacting tokens
465 #[must_use]
466 pub fn get_state_size(&self) -> usize {
467 unsafe { ik_llama_cpp_sys::llama_get_state_size(self.context.as_ptr()) }
468 }
469
470 /// Copies the state to the specified destination address.
471 ///
472 /// Returns the number of bytes copied
473 ///
474 /// # Safety
475 ///
476 /// Destination needs to have allocated enough memory.
477 pub unsafe fn copy_state_data(&self, dest: *mut u8) -> usize {
478 unsafe { ik_llama_cpp_sys::llama_copy_state_data(self.context.as_ptr(), dest) }
479 }
480
481 /// Set the state reading from the specified address
482 /// Returns the number of bytes read
483 ///
484 /// # Safety
485 ///
486 /// help wanted: not entirely sure what the safety requirements are here.
487 pub unsafe fn set_state_data(&mut self, src: &[u8]) -> usize {
488 unsafe { ik_llama_cpp_sys::llama_set_state_data(self.context.as_ptr(), src.as_ptr()) }
489 }
490
491 /// Get the size of the state for a single sequence with optional flags.
492 ///
493 /// This is the extended version that supports flags for partial state operations.
494 ///
495 /// # Parameters
496 ///
497 /// * `seq_id` - The sequence ID to get the state size for.
498 /// * `flags` - Optional flags (e.g., [`LlamaStateSeqFlags::PARTIAL_ONLY`]).
499 ///
500 /// # Returns
501 ///
502 /// The size in bytes needed to store the sequence state.
503 #[must_use]
504 pub fn state_seq_get_size_ext(&self, seq_id: i32, flags: LlamaStateSeqFlags) -> usize {
505 unsafe {
506 ik_llama_cpp_sys::llama_state_seq_get_size(self.context.as_ptr(), seq_id, flags.0)
507 }
508 }
509
510 /// Copy the state of a single sequence into the specified buffer with optional flags.
511 ///
512 /// This is the extended version that supports flags for partial state operations.
513 ///
514 /// # Parameters
515 ///
516 /// * `dest` - Destination buffer to copy state into.
517 /// * `seq_id` - The sequence ID to get the state for.
518 /// * `flags` - Optional flags (e.g., [`LlamaStateSeqFlags::PARTIAL_ONLY`]).
519 ///
520 /// # Safety
521 ///
522 /// Destination needs to have allocated enough memory.
523 ///
524 /// # Returns
525 ///
526 /// The number of bytes copied.
527 pub unsafe fn state_seq_get_data_ext(
528 &self,
529 dest: *mut u8,
530 seq_id: i32,
531 flags: LlamaStateSeqFlags,
532 ) -> usize {
533 unsafe {
534 ik_llama_cpp_sys::llama_state_seq_get_data(
535 self.context.as_ptr(),
536 dest,
537 usize::MAX,
538 seq_id,
539 flags.0,
540 )
541 }
542 }
543
544 /// Set the state for a single sequence from the specified buffer with optional flags.
545 ///
546 /// This is the extended version that supports flags for partial state operations.
547 /// Useful for restoring only the recurrent/partial state without affecting the KV cache.
548 ///
549 /// # Parameters
550 ///
551 /// * `src` - Source buffer containing the state data.
552 /// * `dest_seq_id` - The destination sequence ID to load the state into.
553 /// * `flags` - Optional flags (e.g., [`LlamaStateSeqFlags::PARTIAL_ONLY`]).
554 ///
555 /// # Safety
556 ///
557 /// The source buffer must contain valid state data.
558 ///
559 /// # Returns
560 ///
561 /// `true` on success (the whole `src` buffer was consumed). ik returns
562 /// `SIZE_MAX` when state IO is unsupported and `0` on other failures; both
563 /// are reported as `false` (only `n == src.len()` counts as success).
564 pub unsafe fn state_seq_set_data_ext(
565 &mut self,
566 src: &[u8],
567 dest_seq_id: i32,
568 flags: LlamaStateSeqFlags,
569 ) -> bool {
570 let n = unsafe {
571 ik_llama_cpp_sys::llama_state_seq_set_data(
572 self.context.as_ptr(),
573 src.as_ptr(),
574 src.len(),
575 dest_seq_id,
576 flags.0,
577 )
578 };
579 n == src.len()
580 }
581
582 /// Serialize sequence `seq_id`'s state into an opaque [`SeqState`].
583 ///
584 /// Enables save/restore of context state at arbitrary points in a
585 /// sequence. This is particularly useful on architectures where
586 /// `clear_kv_cache_seq` cannot roll back partial state (Mamba, RWKV,
587 /// Gated Delta Networks, and other recurrent / hybrid-recurrent
588 /// models): pair with [`LlamaStateSeqFlags::PARTIAL_ONLY`] to save
589 /// just the running recurrent and SWA state, then restore it via
590 /// [`Self::state_seq_set`] to effectively "rewind" the sequence.
591 ///
592 /// The returned [`SeqState`] is opaque — its bytes cannot be
593 /// inspected or forged from safe code, so [`Self::state_seq_set`]
594 /// only ever sees data produced by this method.
595 ///
596 /// Wraps `llama_state_seq_get_data`.
597 ///
598 /// # Errors
599 ///
600 /// Returns [`StateSeqError::SizeMismatch`] if llama.cpp writes a
601 /// different number of bytes than the reported state size.
602 pub fn state_seq_get(
603 &self,
604 seq_id: i32,
605 flags: LlamaStateSeqFlags,
606 ) -> Result<SeqState, StateSeqError> {
607 let size = unsafe {
608 ik_llama_cpp_sys::llama_state_seq_get_size(self.context.as_ptr(), seq_id, flags.0)
609 };
610 let mut bytes = vec![0u8; size];
611 let n = unsafe {
612 ik_llama_cpp_sys::llama_state_seq_get_data(
613 self.context.as_ptr(),
614 bytes.as_mut_ptr(),
615 size,
616 seq_id,
617 flags.0,
618 )
619 };
620 if n != size {
621 return Err(StateSeqError::SizeMismatch {
622 expected: size,
623 actual: n,
624 });
625 }
626 Ok(SeqState { bytes, flags })
627 }
628
629 /// Restore sequence state previously captured by [`Self::state_seq_get`]
630 /// into `seq_id`.
631 ///
632 /// Cross-sequence restore (`seq_id` different from the sequence the
633 /// state was captured from) is supported — llama.cpp treats the
634 /// destination sequence id independently of the source.
635 ///
636 /// Wraps `llama_state_seq_set_data`.
637 ///
638 /// # Errors
639 ///
640 /// Returns [`StateSeqError::SizeMismatch`] if llama.cpp reads a
641 /// different number of bytes than the state buffer contains — this
642 /// covers shape mismatches (different `n_ctx`, `n_layer`, quantization,
643 /// etc.) that llama.cpp's own deserializer detects and aborts on.
644 pub fn state_seq_set(&mut self, state: &SeqState, seq_id: i32) -> Result<(), StateSeqError> {
645 let n = unsafe {
646 ik_llama_cpp_sys::llama_state_seq_set_data(
647 self.context.as_ptr(),
648 state.bytes.as_ptr(),
649 state.bytes.len(),
650 seq_id,
651 state.flags.0,
652 )
653 };
654 if n != state.bytes.len() {
655 return Err(StateSeqError::SizeMismatch {
656 expected: state.bytes.len(),
657 actual: n,
658 });
659 }
660 Ok(())
661 }
662}
663
664/// Opaque, immutable snapshot of a single sequence's state.
665///
666/// Produced by [`LlamaContext::state_seq_get`] and consumed by
667/// [`LlamaContext::state_seq_set`]. Bytes cannot be constructed, forged,
668/// or mutated from safe code — the only way to obtain a `SeqState` is
669/// via a get call, which guarantees the payload came from llama.cpp
670/// itself. Combined with llama.cpp's own shape validation on the
671/// deserialize path, this closes the "arbitrary bytes into C parser"
672/// unsoundness that motivates the unsafe raw setters.
673///
674/// The state carries the flag set it was captured with; restore uses
675/// those same flags automatically so the byte layout always matches.
676#[derive(Clone)]
677pub struct SeqState {
678 bytes: Vec<u8>,
679 flags: LlamaStateSeqFlags,
680}
681
682impl SeqState {
683 /// The flag set that was used to capture this state.
684 #[must_use]
685 pub fn flags(&self) -> LlamaStateSeqFlags {
686 self.flags
687 }
688
689 /// Size in bytes of the serialized state.
690 #[must_use]
691 pub fn byte_len(&self) -> usize {
692 self.bytes.len()
693 }
694}
695
696impl Debug for SeqState {
697 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
698 f.debug_struct("SeqState")
699 .field("byte_len", &self.bytes.len())
700 .field("flags", &self.flags)
701 .finish()
702 }
703}