1use std::collections::BTreeSet;
7use std::fmt;
8
9use serde_json::Value;
10
11use crate::census::{CensusReport, WeightsManifest};
12use crate::fttsq::{
13 AccessClass, FttsqError, FttsqStreamPlan, FttsqStreamingWriter, StoredDtype,
14 TensorEntry as ArtifactTensorEntry,
15};
16use crate::safetensors::{Dtype, SafetensorsIndex, TensorView, WeightsError};
17use crate::sha256::Sha256;
18
19pub const MAX_Q8_OUTPUT_CHANNEL_WIDTH: usize = 65_536;
26
27pub const MAX_Q8_OUTPUT_CHANNELS: usize = 262_144;
34
35pub const Q8_GROUP_WIDTH: usize = 64;
42
43pub const MAX_Q8_GROUP_SCALES: usize = 8_388_608;
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum TensorStoragePolicy {
58 Verbatim,
60 Q8PerOutputChannel,
62 Q8PerGroup64,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct TensorConversion {
75 source_name: String,
76 artifact_name: String,
77 access_class: AccessClass,
78 storage: TensorStoragePolicy,
79}
80
81impl TensorConversion {
82 #[must_use]
84 pub fn verbatim(
85 source_name: impl Into<String>,
86 artifact_name: impl Into<String>,
87 access_class: AccessClass,
88 ) -> Self {
89 Self {
90 source_name: source_name.into(),
91 artifact_name: artifact_name.into(),
92 access_class,
93 storage: TensorStoragePolicy::Verbatim,
94 }
95 }
96
97 #[must_use]
99 pub fn q8_per_output_channel(
100 source_name: impl Into<String>,
101 artifact_name: impl Into<String>,
102 access_class: AccessClass,
103 ) -> Self {
104 Self {
105 source_name: source_name.into(),
106 artifact_name: artifact_name.into(),
107 access_class,
108 storage: TensorStoragePolicy::Q8PerOutputChannel,
109 }
110 }
111
112 #[must_use]
114 pub fn q8_per_group_64(
115 source_name: impl Into<String>,
116 artifact_name: impl Into<String>,
117 access_class: AccessClass,
118 ) -> Self {
119 Self {
120 source_name: source_name.into(),
121 artifact_name: artifact_name.into(),
122 access_class,
123 storage: TensorStoragePolicy::Q8PerGroup64,
124 }
125 }
126
127 fn section_name(&self) -> &'static str {
134 self.access_class.as_str()
135 }
136
137 fn scales_name(&self) -> String {
138 format!("{}.scales", self.artifact_name)
139 }
140}
141
142#[derive(Clone, Debug)]
148pub struct StreamingConversionPlan {
149 model_family: String,
150 source_sha256: String,
151 license_notice: String,
152 model_config: Value,
153 quantization_manifest: Value,
154 tensors: Vec<TensorConversion>,
155}
156
157impl StreamingConversionPlan {
158 #[must_use]
160 pub fn new(model_family: impl Into<String>, source_sha256: impl Into<String>) -> Self {
161 Self {
162 model_family: model_family.into(),
163 source_sha256: source_sha256.into(),
164 license_notice: String::new(),
165 model_config: Value::Null,
166 quantization_manifest: Value::Null,
167 tensors: Vec::new(),
168 }
169 }
170
171 #[must_use]
173 pub fn license_notice(mut self, notice: impl Into<String>) -> Self {
174 self.license_notice = notice.into();
175 self
176 }
177
178 #[must_use]
180 pub fn model_config(mut self, config: Value) -> Self {
181 self.model_config = config;
182 self
183 }
184
185 #[must_use]
187 pub fn quantization_manifest(mut self, manifest: Value) -> Self {
188 self.quantization_manifest = manifest;
189 self
190 }
191
192 #[must_use]
194 pub fn tensor(mut self, tensor: TensorConversion) -> Self {
195 self.tensors.push(tensor);
196 self
197 }
198}
199
200#[derive(Clone, Debug, PartialEq, Eq)]
202pub enum ConversionPlanError {
203 NoTensorPolicies,
205 DuplicateSourcePolicy {
207 name: String,
209 },
210 SourceTensorMissing {
212 name: String,
214 },
215 SourceTensorUnplanned {
217 name: String,
219 },
220 DuplicateArtifactTensor {
222 name: String,
224 },
225 EmptyArtifactTensorName {
227 source_name: String,
229 },
230 Q8RequiresMatrix {
232 name: String,
234 rank: usize,
236 },
237 Q8EmptyOutputChannel {
239 name: String,
241 },
242 Q8OutputChannelTooWide {
244 name: String,
246 width: usize,
248 limit: usize,
250 },
251 Q8OutputChannelCountTooLarge {
253 name: String,
255 rows: usize,
257 limit: usize,
259 },
260 ShapeOutOfRange {
262 name: String,
264 },
265 SectionLengthOverflow {
267 name: String,
269 },
270}
271
272impl fmt::Display for ConversionPlanError {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 match self {
275 Self::NoTensorPolicies => f.write_str("conversion plan has no tensor policies"),
276 Self::DuplicateSourcePolicy { name } => {
277 write!(
278 f,
279 "conversion plan names source tensor `{name}` more than once"
280 )
281 }
282 Self::SourceTensorMissing { name } => {
283 write!(
284 f,
285 "conversion plan names source tensor `{name}`, which is absent"
286 )
287 }
288 Self::SourceTensorUnplanned { name } => {
289 write!(
290 f,
291 "source tensor `{name}` has no explicit conversion policy"
292 )
293 }
294 Self::DuplicateArtifactTensor { name } => {
295 write!(
296 f,
297 "conversion plan would emit artifact tensor `{name}` more than once"
298 )
299 }
300 Self::EmptyArtifactTensorName { source_name } => write!(
301 f,
302 "conversion plan gives source tensor `{source_name}` an empty artifact name"
303 ),
304 Self::Q8RequiresMatrix { name, rank } => write!(
305 f,
306 "Q8 conversion for `{name}` requires rank 2 or greater, got rank {rank}"
307 ),
308 Self::Q8EmptyOutputChannel { name } => {
309 write!(f, "Q8 conversion for `{name}` has an empty output channel")
310 }
311 Self::Q8OutputChannelTooWide { name, width, limit } => write!(
312 f,
313 "Q8 conversion for `{name}` has output-channel width {width}, exceeding {limit}"
314 ),
315 Self::Q8OutputChannelCountTooLarge { name, rows, limit } => write!(
316 f,
317 "Q8 conversion for `{name}` has {rows} output channels, exceeding {limit}"
318 ),
319 Self::ShapeOutOfRange { name } => {
320 write!(
321 f,
322 "source tensor `{name}` has a shape outside the artifact range"
323 )
324 }
325 Self::SectionLengthOverflow { name } => {
326 write!(
327 f,
328 "source tensor `{name}` overflows its planned artifact section length"
329 )
330 }
331 }
332 }
333}
334
335impl std::error::Error for ConversionPlanError {}
336
337#[derive(Debug)]
339pub enum StreamingConversionError {
340 Source(WeightsError),
342 SourceCensus(Box<CensusReport>),
344 SourceDigestMismatch {
346 expected: String,
348 actual: String,
350 },
351 Plan(ConversionPlanError),
353 Artifact(FttsqError),
355 Quantization(MatrixQuantizationError<Q8SectionSinkError>),
357}
358
359impl fmt::Display for StreamingConversionError {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 match self {
362 Self::Source(error) => write!(f, "cannot parse source checkpoint: {error}"),
363 Self::SourceCensus(report) => f.write_str(&report.render()),
364 Self::SourceDigestMismatch { expected, actual } => write!(
365 f,
366 "source checkpoint SHA-256 mismatch: expected {expected}, got {actual}"
367 ),
368 Self::Plan(error) => write!(f, "invalid conversion plan: {error}"),
369 Self::Artifact(error) => write!(f, "cannot write .fttsq artifact: {error}"),
370 Self::Quantization(error) => write!(f, "cannot quantize artifact matrix: {error}"),
371 }
372 }
373}
374
375impl std::error::Error for StreamingConversionError {
376 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
377 match self {
378 Self::Source(error) => Some(error),
379 Self::SourceCensus(report) => Some(report),
380 Self::Plan(error) => Some(error),
381 Self::Artifact(error) => Some(error),
382 Self::Quantization(error) => Some(error),
383 Self::SourceDigestMismatch { .. } => None,
384 }
385 }
386}
387
388#[derive(Clone, Debug, PartialEq)]
390pub enum QuantizationError {
391 OutputLength {
393 values: usize,
395 output: usize,
397 },
398 NonFiniteValue {
400 index: usize,
402 value: f32,
404 },
405}
406
407impl fmt::Display for QuantizationError {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 match self {
410 Self::OutputLength { values, output } => write!(
411 f,
412 "Q8 output length {output} does not match input row length {values}"
413 ),
414 Self::NonFiniteValue { index, value } => {
415 write!(
416 f,
417 "Q8 input row has non-finite value {value} at index {index}"
418 )
419 }
420 }
421 }
422}
423
424impl std::error::Error for QuantizationError {}
425
426pub trait Q8RowSink {
433 type Error;
435
436 fn write_q8_row(&mut self, row: usize, scale: f32, values: &[i8]) -> Result<(), Self::Error>;
441}
442
443#[derive(Clone, Debug, PartialEq)]
445pub enum MatrixQuantizationError<E> {
446 ExpectedMatrix {
448 rank: usize,
450 },
451 EmptyOutputChannel {
453 shape: Vec<usize>,
455 },
456 OutputChannelTooWide {
458 width: usize,
460 limit: usize,
462 },
463 SourceRowUnavailable {
468 row: usize,
470 },
471 Quantization {
473 row: usize,
475 source: QuantizationError,
477 },
478 Sink {
480 row: usize,
482 source: E,
484 },
485}
486
487impl<E: fmt::Display> fmt::Display for MatrixQuantizationError<E> {
488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 match self {
490 Self::ExpectedMatrix { rank } => {
491 write!(
492 f,
493 "Q8 matrix quantization requires rank 2 or greater, got rank {rank}"
494 )
495 }
496 Self::EmptyOutputChannel { shape } => write!(
497 f,
498 "Q8 matrix quantization refuses empty output channels for shape {shape:?}"
499 ),
500 Self::OutputChannelTooWide { width, limit } => write!(
501 f,
502 "Q8 output-channel width {width} exceeds the bounded adapter limit {limit}"
503 ),
504 Self::SourceRowUnavailable { row } => {
505 write!(f, "Q8 source row {row} is unavailable or incomplete")
506 }
507 Self::Quantization { row, source } => {
508 write!(f, "Q8 source row {row} cannot be quantized: {source}")
509 }
510 Self::Sink { row, source } => {
511 write!(f, "Q8 destination rejected row {row}: {source}")
512 }
513 }
514 }
515}
516
517impl<E> std::error::Error for MatrixQuantizationError<E>
518where
519 E: std::error::Error + 'static,
520{
521 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
522 match self {
523 Self::Quantization { source, .. } => Some(source),
524 Self::Sink { source, .. } => Some(source),
525 Self::ExpectedMatrix { .. }
526 | Self::EmptyOutputChannel { .. }
527 | Self::OutputChannelTooWide { .. }
528 | Self::SourceRowUnavailable { .. } => None,
529 }
530 }
531}
532
533#[derive(Clone, Debug, PartialEq, Eq)]
535pub enum Q8SectionSinkError {
536 OutputChannelCountTooLarge {
538 rows: usize,
540 limit: usize,
542 },
543 OutputChannelTooWide {
545 width: usize,
547 limit: usize,
549 },
550 RowOutOfOrder {
552 expected: usize,
554 actual: usize,
556 },
557 Incomplete {
559 expected: usize,
561 written: usize,
563 },
564 Artifact(FttsqError),
566}
567
568impl fmt::Display for Q8SectionSinkError {
569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570 match self {
571 Self::OutputChannelCountTooLarge { rows, limit } => write!(
572 f,
573 "Q8 matrix has {rows} output channels, exceeding the bounded scale-tail limit {limit}"
574 ),
575 Self::OutputChannelTooWide { width, limit } => write!(
576 f,
577 "Q8 section row width {width} exceeds the bounded row limit {limit}"
578 ),
579 Self::RowOutOfOrder { expected, actual } => write!(
580 f,
581 "Q8 section expected source row {expected}, received row {actual}"
582 ),
583 Self::Incomplete { expected, written } => write!(
584 f,
585 "Q8 section needs {expected} scales but received {written}"
586 ),
587 Self::Artifact(error) => write!(f, "cannot write Q8 section: {error}"),
588 }
589 }
590}
591
592impl std::error::Error for Q8SectionSinkError {
593 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
594 match self {
595 Self::Artifact(error) => Some(error),
596 Self::OutputChannelCountTooLarge { .. }
597 | Self::OutputChannelTooWide { .. }
598 | Self::RowOutOfOrder { .. }
599 | Self::Incomplete { .. } => None,
600 }
601 }
602}
603
604pub struct Q8SectionSink<'a, W> {
613 writer: &'a mut FttsqStreamingWriter<W>,
614 section: String,
615 expected_rows: usize,
616 next_row: usize,
617 value_bytes: Vec<u8>,
618 scale_bytes: Vec<u8>,
619}
620
621impl<'a, W: std::io::Write + std::io::Seek> Q8SectionSink<'a, W> {
622 pub fn new(
629 writer: &'a mut FttsqStreamingWriter<W>,
630 section: impl Into<String>,
631 expected_rows: usize,
632 ) -> Result<Self, Q8SectionSinkError> {
633 if expected_rows > MAX_Q8_OUTPUT_CHANNELS {
634 return Err(Q8SectionSinkError::OutputChannelCountTooLarge {
635 rows: expected_rows,
636 limit: MAX_Q8_OUTPUT_CHANNELS,
637 });
638 }
639 Ok(Self::unbounded(writer, section, expected_rows))
640 }
641
642 pub fn new_grouped(
653 writer: &'a mut FttsqStreamingWriter<W>,
654 section: impl Into<String>,
655 expected_groups: usize,
656 ) -> Result<Self, Q8SectionSinkError> {
657 if expected_groups > MAX_Q8_GROUP_SCALES {
658 return Err(Q8SectionSinkError::OutputChannelCountTooLarge {
659 rows: expected_groups,
660 limit: MAX_Q8_GROUP_SCALES,
661 });
662 }
663 Ok(Self::unbounded(writer, section, expected_groups))
664 }
665
666 fn unbounded(
667 writer: &'a mut FttsqStreamingWriter<W>,
668 section: impl Into<String>,
669 expected_rows: usize,
670 ) -> Self {
671 Self {
672 writer,
673 section: section.into(),
674 expected_rows,
675 next_row: 0,
676 value_bytes: Vec::new(),
677 scale_bytes: Vec::with_capacity(expected_rows * std::mem::size_of::<f32>()),
678 }
679 }
680
681 pub fn finish(self) -> Result<(), Q8SectionSinkError> {
687 if self.next_row != self.expected_rows {
688 return Err(Q8SectionSinkError::Incomplete {
689 expected: self.expected_rows,
690 written: self.next_row,
691 });
692 }
693 self.writer
694 .write_section(&self.section, &self.scale_bytes)
695 .map_err(Q8SectionSinkError::Artifact)
696 }
697}
698
699impl<W: std::io::Write + std::io::Seek> Q8RowSink for Q8SectionSink<'_, W> {
700 type Error = Q8SectionSinkError;
701
702 fn write_q8_row(&mut self, row: usize, scale: f32, values: &[i8]) -> Result<(), Self::Error> {
703 if row != self.next_row {
704 return Err(Q8SectionSinkError::RowOutOfOrder {
705 expected: self.next_row,
706 actual: row,
707 });
708 }
709 if values.len() > MAX_Q8_OUTPUT_CHANNEL_WIDTH {
710 return Err(Q8SectionSinkError::OutputChannelTooWide {
711 width: values.len(),
712 limit: MAX_Q8_OUTPUT_CHANNEL_WIDTH,
713 });
714 }
715 self.value_bytes.clear();
716 self.value_bytes.extend(
717 values
718 .iter()
719 .map(|&value| u8::from_ne_bytes(value.to_ne_bytes())),
720 );
721 self.writer
722 .write_section(&self.section, &self.value_bytes)
723 .map_err(Q8SectionSinkError::Artifact)?;
724 self.scale_bytes.extend_from_slice(&scale.to_le_bytes());
725 self.next_row += 1;
726 Ok(())
727 }
728}
729
730pub fn stream_matrix_q8_section<W: std::io::Write + std::io::Seek>(
741 matrix: &TensorView<'_>,
742 writer: &mut FttsqStreamingWriter<W>,
743 section: &str,
744) -> Result<(), MatrixQuantizationError<Q8SectionSinkError>> {
745 let shape = matrix.shape();
746 if shape.len() < 2 {
747 return Err(MatrixQuantizationError::ExpectedMatrix { rank: shape.len() });
748 }
749 let Some(&row_count) = shape.first() else {
750 return Err(MatrixQuantizationError::ExpectedMatrix { rank: 0 });
751 };
752 let mut sink = Q8SectionSink::new(writer, section, row_count)
753 .map_err(|source| MatrixQuantizationError::Sink { row: 0, source })?;
754 quantize_matrix_q8_rows(matrix, &mut sink)?;
755 sink.finish()
756 .map_err(|source| MatrixQuantizationError::Sink {
757 row: row_count,
758 source,
759 })
760}
761
762pub fn stream_matrix_q8_group64_section<W: std::io::Write + std::io::Seek>(
776 matrix: &TensorView<'_>,
777 writer: &mut FttsqStreamingWriter<W>,
778 section: &str,
779) -> Result<(), MatrixQuantizationError<Q8SectionSinkError>> {
780 let shape = matrix.shape();
781 if shape.len() < 2 {
782 return Err(MatrixQuantizationError::ExpectedMatrix { rank: shape.len() });
783 }
784 let Some(&row_count) = shape.first() else {
785 return Err(MatrixQuantizationError::ExpectedMatrix { rank: 0 });
786 };
787 let row_width = matrix.row_len();
788 if row_width == 0 || !row_width.is_multiple_of(Q8_GROUP_WIDTH) {
789 return Err(MatrixQuantizationError::EmptyOutputChannel {
790 shape: shape.to_vec(),
791 });
792 }
793 if row_width > MAX_Q8_OUTPUT_CHANNEL_WIDTH {
794 return Err(MatrixQuantizationError::OutputChannelTooWide {
795 width: row_width,
796 limit: MAX_Q8_OUTPUT_CHANNEL_WIDTH,
797 });
798 }
799 let groups_per_row = row_width / Q8_GROUP_WIDTH;
800 let total_groups = row_count.checked_mul(groups_per_row).ok_or(
801 MatrixQuantizationError::OutputChannelTooWide {
802 width: row_width,
803 limit: MAX_Q8_OUTPUT_CHANNEL_WIDTH,
804 },
805 )?;
806 let mut sink = Q8SectionSink::new_grouped(writer, section, total_groups)
807 .map_err(|source| MatrixQuantizationError::Sink { row: 0, source })?;
808
809 let mut source_row = vec![0.0_f32; row_width];
810 let mut quantized_group = [0_i8; Q8_GROUP_WIDTH];
811 for row in 0..row_count {
812 if !matrix.copy_row_f32(row, &mut source_row) {
813 return Err(MatrixQuantizationError::SourceRowUnavailable { row });
814 }
815 for (group_index, group) in source_row
816 .as_chunks::<Q8_GROUP_WIDTH>()
817 .0
818 .iter()
819 .enumerate()
820 {
821 let scale = quantize_output_channel_q8(group, &mut quantized_group)
822 .map_err(|source| MatrixQuantizationError::Quantization { row, source })?;
823 sink.write_q8_row(row * groups_per_row + group_index, scale, &quantized_group)
824 .map_err(|source| MatrixQuantizationError::Sink { row, source })?;
825 }
826 }
827 sink.finish()
828 .map_err(|source| MatrixQuantizationError::Sink {
829 row: row_count,
830 source,
831 })
832}
833
834pub fn convert_safetensors_streaming<W: std::io::Write + std::io::Seek>(
853 source: &[u8],
854 manifest: &WeightsManifest,
855 plan: &StreamingConversionPlan,
856 destination: W,
857) -> Result<W, StreamingConversionError> {
858 let index = SafetensorsIndex::parse(source).map_err(StreamingConversionError::Source)?;
859 manifest
860 .verify(&index)
861 .map_err(StreamingConversionError::SourceCensus)?;
862
863 let actual_digest = sha256_hex(source);
864 if actual_digest != plan.source_sha256 {
865 return Err(StreamingConversionError::SourceDigestMismatch {
866 expected: plan.source_sha256.clone(),
867 actual: actual_digest,
868 });
869 }
870
871 let artifact_plan =
872 build_artifact_plan(&index, plan).map_err(StreamingConversionError::Plan)?;
873 let mut writer = artifact_plan
874 .begin(destination)
875 .map_err(StreamingConversionError::Artifact)?;
876
877 for tensor in tensors_in_write_order(plan) {
878 let matrix_or_values = index.view(&tensor.source_name, source).ok_or_else(|| {
879 StreamingConversionError::Plan(ConversionPlanError::SourceTensorMissing {
880 name: tensor.source_name.clone(),
881 })
882 })?;
883 let section = tensor.section_name();
884 match tensor.storage {
885 TensorStoragePolicy::Verbatim => writer
886 .write_section(section, matrix_or_values.as_bytes())
887 .map_err(StreamingConversionError::Artifact)?,
888 TensorStoragePolicy::Q8PerOutputChannel => {
889 stream_matrix_q8_section(&matrix_or_values, &mut writer, section)
890 .map_err(StreamingConversionError::Quantization)?;
891 }
892 TensorStoragePolicy::Q8PerGroup64 => {
893 stream_matrix_q8_group64_section(&matrix_or_values, &mut writer, section)
894 .map_err(StreamingConversionError::Quantization)?;
895 }
896 }
897 }
898
899 writer.finish().map_err(StreamingConversionError::Artifact)
900}
901
902fn build_artifact_plan(
903 index: &SafetensorsIndex,
904 plan: &StreamingConversionPlan,
905) -> Result<FttsqStreamPlan, ConversionPlanError> {
906 if plan.tensors.is_empty() {
907 return Err(ConversionPlanError::NoTensorPolicies);
908 }
909
910 let mut seen_sources = BTreeSet::<String>::new();
911 let mut seen_artifacts = BTreeSet::<String>::new();
912 for tensor in &plan.tensors {
913 if !seen_sources.insert(tensor.source_name.clone()) {
914 return Err(ConversionPlanError::DuplicateSourcePolicy {
915 name: tensor.source_name.clone(),
916 });
917 }
918 if index.entry(&tensor.source_name).is_none() {
919 return Err(ConversionPlanError::SourceTensorMissing {
920 name: tensor.source_name.clone(),
921 });
922 }
923 if tensor.artifact_name.is_empty() {
924 return Err(ConversionPlanError::EmptyArtifactTensorName {
925 source_name: tensor.source_name.clone(),
926 });
927 }
928 if !seen_artifacts.insert(tensor.artifact_name.clone()) {
929 return Err(ConversionPlanError::DuplicateArtifactTensor {
930 name: tensor.artifact_name.clone(),
931 });
932 }
933 if matches!(
934 tensor.storage,
935 TensorStoragePolicy::Q8PerOutputChannel | TensorStoragePolicy::Q8PerGroup64
936 ) {
937 let entry = index.entry(&tensor.source_name).ok_or_else(|| {
938 ConversionPlanError::SourceTensorMissing {
939 name: tensor.source_name.clone(),
940 }
941 })?;
942 if entry.shape.len() < 2 {
943 return Err(ConversionPlanError::Q8RequiresMatrix {
944 name: tensor.source_name.clone(),
945 rank: entry.shape.len(),
946 });
947 }
948 let Some((&rows, trailing_shape)) = entry.shape.split_first() else {
949 return Err(ConversionPlanError::Q8RequiresMatrix {
950 name: tensor.source_name.clone(),
951 rank: 0,
952 });
953 };
954 let row_width = trailing_shape
955 .iter()
956 .try_fold(1_usize, |product, &dimension| {
957 product.checked_mul(dimension)
958 })
959 .ok_or_else(|| ConversionPlanError::ShapeOutOfRange {
960 name: tensor.source_name.clone(),
961 })?;
962 if row_width == 0 {
963 return Err(ConversionPlanError::Q8EmptyOutputChannel {
964 name: tensor.source_name.clone(),
965 });
966 }
967 if row_width > MAX_Q8_OUTPUT_CHANNEL_WIDTH {
968 return Err(ConversionPlanError::Q8OutputChannelTooWide {
969 name: tensor.source_name.clone(),
970 width: row_width,
971 limit: MAX_Q8_OUTPUT_CHANNEL_WIDTH,
972 });
973 }
974 if rows > MAX_Q8_OUTPUT_CHANNELS {
975 return Err(ConversionPlanError::Q8OutputChannelCountTooLarge {
976 name: tensor.source_name.clone(),
977 rows,
978 limit: MAX_Q8_OUTPUT_CHANNELS,
979 });
980 }
981 if tensor.storage == TensorStoragePolicy::Q8PerGroup64 {
982 if !row_width.is_multiple_of(Q8_GROUP_WIDTH) {
985 return Err(ConversionPlanError::Q8EmptyOutputChannel {
986 name: tensor.source_name.clone(),
987 });
988 }
989 let groups = rows * (row_width / Q8_GROUP_WIDTH);
990 if groups > MAX_Q8_GROUP_SCALES {
991 return Err(ConversionPlanError::Q8OutputChannelCountTooLarge {
992 name: tensor.source_name.clone(),
993 rows: groups,
994 limit: MAX_Q8_GROUP_SCALES,
995 });
996 }
997 }
998 let scales_name = tensor.scales_name();
999 if !seen_artifacts.insert(scales_name.clone()) {
1000 return Err(ConversionPlanError::DuplicateArtifactTensor { name: scales_name });
1001 }
1002 }
1003 }
1004
1005 for entry in index.entries() {
1006 if !seen_sources.contains(&entry.name) {
1007 return Err(ConversionPlanError::SourceTensorUnplanned {
1008 name: entry.name.clone(),
1009 });
1010 }
1011 }
1012
1013 let mut artifact_plan = FttsqStreamPlan::new(&plan.model_family, &plan.source_sha256)
1014 .license_notice(&plan.license_notice)
1015 .model_config(plan.model_config.clone())
1016 .quantization_manifest(plan.quantization_manifest.clone());
1017
1018 let mut section_offsets: std::collections::BTreeMap<&'static str, u64> =
1021 std::collections::BTreeMap::new();
1022 let mut declared_sections: Vec<&'static str> = Vec::new();
1023 for tensor in tensors_in_write_order(plan) {
1024 let entry = index.entry(&tensor.source_name).ok_or_else(|| {
1025 ConversionPlanError::SourceTensorMissing {
1026 name: tensor.source_name.clone(),
1027 }
1028 })?;
1029 let shape = artifact_shape(entry, &tensor.source_name)?;
1030 let section = tensor.section_name();
1031 if !declared_sections.contains(§ion) {
1032 declared_sections.push(section);
1033 }
1034 let running = section_offsets.entry(section).or_insert(0);
1035 match tensor.storage {
1036 TensorStoragePolicy::Verbatim => {
1037 let length = u64::try_from(entry.byte_len()).map_err(|_| {
1038 ConversionPlanError::SectionLengthOverflow {
1039 name: tensor.source_name.clone(),
1040 }
1041 })?;
1042 artifact_plan = artifact_plan.tensor(ArtifactTensorEntry {
1043 name: tensor.artifact_name.clone(),
1044 section: section.to_owned(),
1045 dtype: stored_dtype(entry.dtype),
1046 shape,
1047 offset: *running,
1048 length,
1049 scales: None,
1050 });
1051 *running = running.checked_add(length).ok_or_else(|| {
1052 ConversionPlanError::SectionLengthOverflow {
1053 name: tensor.source_name.clone(),
1054 }
1055 })?;
1056 }
1057 TensorStoragePolicy::Q8PerOutputChannel | TensorStoragePolicy::Q8PerGroup64 => {
1058 let rows = entry.shape.first().copied().ok_or_else(|| {
1059 ConversionPlanError::Q8RequiresMatrix {
1060 name: tensor.source_name.clone(),
1061 rank: entry.shape.len(),
1062 }
1063 })?;
1064 let (scale_count, scales_shape) = if tensor.storage
1068 == TensorStoragePolicy::Q8PerGroup64
1069 {
1070 let row_width = entry
1071 .element_count()
1072 .checked_div(rows)
1073 .filter(|width| width.is_multiple_of(Q8_GROUP_WIDTH))
1074 .ok_or_else(|| ConversionPlanError::Q8EmptyOutputChannel {
1075 name: tensor.source_name.clone(),
1076 })?;
1077 let groups_per_row = row_width / Q8_GROUP_WIDTH;
1078 let rows_u64 =
1079 u64::try_from(rows).map_err(|_| ConversionPlanError::ShapeOutOfRange {
1080 name: tensor.source_name.clone(),
1081 })?;
1082 let groups_u64 = u64::try_from(groups_per_row).map_err(|_| {
1083 ConversionPlanError::ShapeOutOfRange {
1084 name: tensor.source_name.clone(),
1085 }
1086 })?;
1087 (rows * groups_per_row, vec![rows_u64, groups_u64])
1088 } else {
1089 (
1090 rows,
1091 vec![u64::try_from(rows).map_err(|_| {
1092 ConversionPlanError::ShapeOutOfRange {
1093 name: tensor.source_name.clone(),
1094 }
1095 })?],
1096 )
1097 };
1098 let values_len = u64::try_from(entry.element_count()).map_err(|_| {
1099 ConversionPlanError::SectionLengthOverflow {
1100 name: tensor.source_name.clone(),
1101 }
1102 })?;
1103 let scales_len = u64::try_from(scale_count)
1104 .ok()
1105 .and_then(|count| {
1106 count.checked_mul(u64::try_from(std::mem::size_of::<f32>()).ok()?)
1107 })
1108 .ok_or_else(|| ConversionPlanError::SectionLengthOverflow {
1109 name: tensor.source_name.clone(),
1110 })?;
1111 let section_len = values_len.checked_add(scales_len).ok_or_else(|| {
1112 ConversionPlanError::SectionLengthOverflow {
1113 name: tensor.source_name.clone(),
1114 }
1115 })?;
1116 let scales_name = tensor.scales_name();
1117 artifact_plan = artifact_plan
1118 .tensor(ArtifactTensorEntry {
1119 name: tensor.artifact_name.clone(),
1120 section: section.to_owned(),
1121 dtype: StoredDtype::Q8,
1122 shape,
1123 offset: *running,
1124 length: values_len,
1125 scales: Some(scales_name.clone()),
1126 })
1127 .tensor(ArtifactTensorEntry {
1128 name: scales_name,
1129 section: section.to_owned(),
1130 dtype: StoredDtype::F32,
1131 shape: scales_shape,
1132 offset: running.checked_add(values_len).ok_or_else(|| {
1133 ConversionPlanError::SectionLengthOverflow {
1134 name: tensor.source_name.clone(),
1135 }
1136 })?,
1137 length: scales_len,
1138 scales: None,
1139 });
1140 *running = running.checked_add(section_len).ok_or_else(|| {
1141 ConversionPlanError::SectionLengthOverflow {
1142 name: tensor.source_name.clone(),
1143 }
1144 })?;
1145 }
1146 }
1147 }
1148
1149 for section in declared_sections {
1152 let class = section_access_class(section);
1153 let length = section_offsets
1154 .get(section)
1155 .copied()
1156 .expect("declared sections accumulate a length");
1157 artifact_plan = artifact_plan.section(section, class, length);
1158 }
1159
1160 Ok(artifact_plan)
1161}
1162
1163fn tensors_in_write_order(plan: &StreamingConversionPlan) -> Vec<&TensorConversion> {
1166 let mut order: Vec<&'static str> = Vec::new();
1167 for tensor in &plan.tensors {
1168 let section = tensor.section_name();
1169 if !order.contains(§ion) {
1170 order.push(section);
1171 }
1172 }
1173 let mut grouped = Vec::with_capacity(plan.tensors.len());
1174 for section in order {
1175 grouped.extend(
1176 plan.tensors
1177 .iter()
1178 .filter(|tensor| tensor.section_name() == section),
1179 );
1180 }
1181 grouped
1182}
1183
1184fn section_access_class(name: &str) -> AccessClass {
1186 for class in [
1187 AccessClass::HotRecurrentMicrodecoder,
1188 AccessClass::HotRecurrentTalker,
1189 AccessClass::HotCodecDecoder,
1190 AccessClass::ColdTextEmbedding,
1191 AccessClass::EnrollmentSpeakerEncoder,
1192 AccessClass::EnrollmentCodecEncoder,
1193 AccessClass::Metadata,
1194 ] {
1195 if class.as_str() == name {
1196 return class;
1197 }
1198 }
1199 unreachable!("section names are minted from AccessClass::as_str")
1200}
1201
1202fn artifact_shape(
1203 entry: &crate::safetensors::TensorEntry,
1204 source_name: &str,
1205) -> Result<Vec<u64>, ConversionPlanError> {
1206 entry
1207 .shape
1208 .iter()
1209 .copied()
1210 .map(u64::try_from)
1211 .collect::<Result<Vec<_>, _>>()
1212 .map_err(|_| ConversionPlanError::ShapeOutOfRange {
1213 name: source_name.to_owned(),
1214 })
1215}
1216
1217const fn stored_dtype(source: Dtype) -> StoredDtype {
1218 match source {
1219 Dtype::Bf16 => StoredDtype::Bf16,
1220 Dtype::F32 => StoredDtype::F32,
1221 }
1222}
1223
1224fn sha256_hex(bytes: &[u8]) -> String {
1225 const HEX: &[u8; 16] = b"0123456789abcdef";
1226 let digest = {
1227 let mut hasher = Sha256::new();
1228 hasher.update(bytes);
1229 hasher.finish()
1230 };
1231 let mut output = String::with_capacity(64);
1232 for byte in digest {
1233 output.push(char::from(HEX[usize::from(byte >> 4)]));
1234 output.push(char::from(HEX[usize::from(byte & 0x0f)]));
1235 }
1236 output
1237}
1238
1239pub fn quantize_output_channel_q8(
1254 row: &[f32],
1255 output: &mut [i8],
1256) -> Result<f32, QuantizationError> {
1257 if output.len() != row.len() {
1258 return Err(QuantizationError::OutputLength {
1259 values: row.len(),
1260 output: output.len(),
1261 });
1262 }
1263
1264 let mut maximum = 0.0_f32;
1265 for (index, &value) in row.iter().enumerate() {
1266 if !value.is_finite() {
1267 return Err(QuantizationError::NonFiniteValue { index, value });
1268 }
1269 maximum = maximum.max(value.abs());
1270 }
1271
1272 if maximum == 0.0 {
1273 output.fill(0);
1274 return Ok(1.0);
1275 }
1276
1277 let scale = maximum / 127.0;
1278 for (&value, slot) in row.iter().zip(output) {
1279 let rounded = (value / scale).clamp(-127.0, 127.0).round_ties_even();
1280 *slot = rounded as i8;
1283 }
1284 Ok(scale)
1285}
1286
1287pub fn quantize_matrix_q8_rows<S: Q8RowSink>(
1304 matrix: &TensorView<'_>,
1305 sink: &mut S,
1306) -> Result<(), MatrixQuantizationError<S::Error>> {
1307 let shape = matrix.shape();
1308 if shape.len() < 2 {
1309 return Err(MatrixQuantizationError::ExpectedMatrix { rank: shape.len() });
1310 }
1311
1312 let Some(&row_count) = shape.first() else {
1313 return Err(MatrixQuantizationError::ExpectedMatrix { rank: 0 });
1314 };
1315 let row_width = matrix.row_len();
1316 if row_width == 0 {
1317 return Err(MatrixQuantizationError::EmptyOutputChannel {
1318 shape: shape.to_vec(),
1319 });
1320 }
1321 if row_width > MAX_Q8_OUTPUT_CHANNEL_WIDTH {
1322 return Err(MatrixQuantizationError::OutputChannelTooWide {
1323 width: row_width,
1324 limit: MAX_Q8_OUTPUT_CHANNEL_WIDTH,
1325 });
1326 }
1327
1328 let mut source_row = vec![0.0_f32; row_width];
1329 let mut quantized_row = vec![0_i8; row_width];
1330 for row in 0..row_count {
1331 if !matrix.copy_row_f32(row, &mut source_row) {
1332 return Err(MatrixQuantizationError::SourceRowUnavailable { row });
1333 }
1334 let scale = quantize_output_channel_q8(&source_row, &mut quantized_row)
1335 .map_err(|source| MatrixQuantizationError::Quantization { row, source })?;
1336 sink.write_q8_row(row, scale, &quantized_row)
1337 .map_err(|source| MatrixQuantizationError::Sink { row, source })?;
1338 }
1339 Ok(())
1340}
1341
1342#[cfg(test)]
1343mod tests {
1344 use super::*;
1345 use crate::census::ExpectedTensor;
1346 use crate::fttsq::{AccessClass, FttsqReader, FttsqStreamPlan, StoredDtype, TensorEntry};
1347 use crate::safetensors::SafetensorsIndex;
1348 use serde_json::json;
1349 use std::convert::Infallible;
1350 use std::io::Cursor;
1351
1352 #[derive(Default)]
1353 struct RecordingSink {
1354 rows: Vec<(usize, f32, Vec<i8>)>,
1355 }
1356
1357 impl Q8RowSink for RecordingSink {
1358 type Error = Infallible;
1359
1360 fn write_q8_row(
1361 &mut self,
1362 row: usize,
1363 scale: f32,
1364 values: &[i8],
1365 ) -> Result<(), Self::Error> {
1366 self.rows.push((row, scale, values.to_vec()));
1367 Ok(())
1368 }
1369 }
1370
1371 fn f32_matrix(rows: usize, columns: usize, values: &[f32]) -> Vec<u8> {
1372 assert_eq!(values.len(), rows * columns);
1373 let payload: Vec<u8> = values
1374 .iter()
1375 .flat_map(|value| value.to_le_bytes())
1376 .collect();
1377 let header = serde_json::to_vec(&json!({
1378 "matrix": {
1379 "dtype": "F32",
1380 "shape": [rows, columns],
1381 "data_offsets": [0, payload.len()],
1382 }
1383 }))
1384 .expect("fixture directory serializes");
1385
1386 let mut bytes = (header.len() as u64).to_le_bytes().to_vec();
1387 bytes.extend_from_slice(&header);
1388 bytes.extend_from_slice(&payload);
1389 bytes
1390 }
1391
1392 fn safetensors(parts: &[(&str, Dtype, &[usize], &[u8])]) -> Vec<u8> {
1393 let mut directory = serde_json::Map::new();
1394 let mut payload = Vec::new();
1395 for (name, dtype, shape, bytes) in parts {
1396 let begin = payload.len();
1397 payload.extend_from_slice(bytes);
1398 directory.insert(
1399 (*name).to_owned(),
1400 json!({
1401 "dtype": dtype.as_str(),
1402 "shape": shape,
1403 "data_offsets": [begin, payload.len()],
1404 }),
1405 );
1406 }
1407 let header = serde_json::to_vec(&serde_json::Value::Object(directory))
1408 .expect("fixture directory serializes");
1409 let mut source = (header.len() as u64).to_le_bytes().to_vec();
1410 source.extend_from_slice(&header);
1411 source.extend_from_slice(&payload);
1412 source
1413 }
1414
1415 #[test]
1416 fn q8_uses_symmetric_ties_to_even_rounding_and_never_emits_negative_128() {
1417 let row = [
1418 -127.0, -126.5, -125.5, -1.5, -0.5, 0.5, 1.5, 125.5, 126.5, 127.0,
1419 ];
1420 let mut output = [0_i8; 10];
1421
1422 let scale = quantize_output_channel_q8(&row, &mut output).expect("finite row");
1423
1424 assert_eq!(scale, 1.0);
1425 assert_eq!(output, [-127, -126, -126, -2, 0, 0, 2, 126, 126, 127]);
1426 assert!(!output.contains(&i8::MIN));
1427 }
1428
1429 #[test]
1430 fn q8_all_zero_row_has_a_finite_unit_scale() {
1431 let row = [0.0_f32; 4];
1432 let mut output = [9_i8; 4];
1433
1434 let scale = quantize_output_channel_q8(&row, &mut output).expect("zero row is valid");
1435
1436 assert_eq!(scale, 1.0);
1437 assert_eq!(output, [0; 4]);
1438 }
1439
1440 #[test]
1441 fn q8_refuses_length_mismatch_and_non_finite_input() {
1442 let error = quantize_output_channel_q8(&[1.0, 2.0], &mut [0]).expect_err("wrong length");
1443 assert_eq!(
1444 error,
1445 QuantizationError::OutputLength {
1446 values: 2,
1447 output: 1,
1448 }
1449 );
1450
1451 let error = quantize_output_channel_q8(&[1.0, f32::NAN], &mut [0; 2])
1452 .expect_err("NaN cannot be quantized deterministically");
1453 assert!(matches!(
1454 error,
1455 QuantizationError::NonFiniteValue { index: 1, value } if value.is_nan()
1456 ));
1457 }
1458
1459 #[test]
1460 fn runtime_and_offline_callers_receive_byte_identical_q8_rows() {
1461 let row = [-3.0_f32, -0.75, 0.5, 1.5, 3.0];
1462 let mut runtime = [0_i8; 5];
1463 let mut offline = [0_i8; 5];
1464
1465 let runtime_scale = quantize_output_channel_q8(&row, &mut runtime).expect("runtime Q8");
1466 let offline_scale = quantize_output_channel_q8(&row, &mut offline).expect("offline Q8");
1467
1468 assert_eq!(runtime, offline);
1469 assert_eq!(runtime_scale.to_bits(), offline_scale.to_bits());
1470 }
1471
1472 #[test]
1473 fn matrix_rows_stream_through_the_shared_primitive_in_order() {
1474 let bytes = f32_matrix(2, 3, &[1.0, -2.0, 0.5, 3.0, 0.0, -3.0]);
1475 let index = SafetensorsIndex::parse(&bytes).expect("fixture parses");
1476 let matrix = index.view("matrix", &bytes).expect("matrix view exists");
1477 let mut sink = RecordingSink::default();
1478
1479 quantize_matrix_q8_rows(&matrix, &mut sink).expect("finite matrix quantizes");
1480
1481 assert_eq!(sink.rows.len(), 2);
1482 assert_eq!(sink.rows[0].0, 0);
1483 assert_eq!(sink.rows[0].1.to_bits(), (2.0_f32 / 127.0).to_bits());
1484 assert_eq!(sink.rows[0].2, vec![64, -127, 32]);
1485 assert_eq!(sink.rows[1].0, 1);
1486 assert_eq!(sink.rows[1].1.to_bits(), (3.0_f32 / 127.0).to_bits());
1487 assert_eq!(sink.rows[1].2, vec![127, 0, -127]);
1488 }
1489
1490 #[test]
1491 fn matrix_q8_section_streams_values_then_bounded_scale_tail() {
1492 let source = f32_matrix(2, 3, &[1.0, -2.0, 0.5, 3.0, 0.0, -3.0]);
1493 let index = SafetensorsIndex::parse(&source).expect("fixture parses");
1494 let matrix = index.view("matrix", &source).expect("matrix view exists");
1495 let plan = FttsqStreamPlan::new("test-model", "a".repeat(64))
1496 .license_notice("Copyright 2026 Alibaba Cloud\nApache-2.0")
1497 .section("matrix", AccessClass::HotRecurrentTalker, 14)
1498 .tensor(TensorEntry {
1499 name: "matrix.weight".to_owned(),
1500 section: "matrix".to_owned(),
1501 dtype: StoredDtype::Q8,
1502 shape: vec![2, 3],
1503 offset: 0,
1504 length: 6,
1505 scales: Some("matrix.weight.scales".to_owned()),
1506 })
1507 .tensor(TensorEntry {
1508 name: "matrix.weight.scales".to_owned(),
1509 section: "matrix".to_owned(),
1510 dtype: StoredDtype::F32,
1511 shape: vec![2],
1512 offset: 6,
1513 length: 8,
1514 scales: None,
1515 });
1516 let mut writer = plan
1517 .begin(Cursor::new(Vec::new()))
1518 .expect("section metadata is valid");
1519
1520 stream_matrix_q8_section(&matrix, &mut writer, "matrix")
1521 .expect("matrix streams through the canonical Q8 primitive");
1522 let artifact = writer
1523 .finish()
1524 .expect("completed section finalizes its digest")
1525 .into_inner();
1526 let reader = FttsqReader::open(&artifact).expect("artifact verifies");
1527
1528 assert_eq!(
1529 reader
1530 .tensor_bytes("matrix.weight", &artifact)
1531 .expect("Q8 bytes resolve"),
1532 &[64, 129, 32, 127, 0, 129]
1533 );
1534 let scales = reader
1535 .tensor_bytes("matrix.weight.scales", &artifact)
1536 .expect("scale bytes resolve");
1537 assert_eq!(
1538 scales,
1539 &[
1540 (2.0_f32 / 127.0).to_le_bytes(),
1541 (3.0_f32 / 127.0).to_le_bytes(),
1542 ]
1543 .concat()
1544 );
1545 }
1546
1547 #[test]
1548 fn grouped_q8_section_carries_one_scale_per_group_and_dequantizes_per_group() {
1549 let quiet = [0.00127_f32, -0.0005];
1553 let source = f32_matrix(
1554 2,
1555 2 * Q8_GROUP_WIDTH,
1556 &[
1557 std::iter::repeat_n(1.27_f32, Q8_GROUP_WIDTH).collect::<Vec<_>>(),
1558 quiet.iter().copied().cycle().take(Q8_GROUP_WIDTH).collect(),
1559 std::iter::repeat_n(-2.54_f32, Q8_GROUP_WIDTH).collect(),
1560 quiet.iter().copied().cycle().take(Q8_GROUP_WIDTH).collect(),
1561 ]
1562 .concat(),
1563 );
1564 let index = SafetensorsIndex::parse(&source).expect("fixture parses");
1565 let matrix = index.view("matrix", &source).expect("matrix view exists");
1566 let values_len = 2 * 2 * Q8_GROUP_WIDTH as u64;
1567 let plan = FttsqStreamPlan::new("test-model", "a".repeat(64))
1568 .license_notice("Copyright 2026 Alibaba Cloud\nApache-2.0")
1569 .section("matrix", AccessClass::ColdTextEmbedding, values_len + 16)
1570 .tensor(TensorEntry {
1571 name: "matrix.weight".to_owned(),
1572 section: "matrix".to_owned(),
1573 dtype: StoredDtype::Q8,
1574 shape: vec![2, 2 * Q8_GROUP_WIDTH as u64],
1575 offset: 0,
1576 length: values_len,
1577 scales: Some("matrix.weight.scales".to_owned()),
1578 })
1579 .tensor(TensorEntry {
1580 name: "matrix.weight.scales".to_owned(),
1581 section: "matrix".to_owned(),
1582 dtype: StoredDtype::F32,
1583 shape: vec![2, 2],
1584 offset: values_len,
1585 length: 16,
1586 scales: None,
1587 });
1588 let mut writer = plan
1589 .begin(Cursor::new(Vec::new()))
1590 .expect("section metadata is valid");
1591 stream_matrix_q8_group64_section(&matrix, &mut writer, "matrix")
1592 .expect("grouped matrix streams through the canonical primitive");
1593 let artifact = writer
1594 .finish()
1595 .expect("completed section finalizes its digest")
1596 .into_inner();
1597 let reader = FttsqReader::open(&artifact).expect("artifact verifies");
1598
1599 let scales: Vec<f32> = reader
1600 .tensor_bytes("matrix.weight.scales", &artifact)
1601 .expect("scale bytes resolve")
1602 .as_chunks::<4>()
1603 .0
1604 .iter()
1605 .map(|bytes| f32::from_le_bytes(*bytes))
1606 .collect();
1607 assert_eq!(
1608 scales.iter().map(|s| s.to_bits()).collect::<Vec<_>>(),
1609 [
1610 1.27_f32 / 127.0,
1611 0.00127_f32 / 127.0,
1612 2.54_f32 / 127.0,
1613 0.00127_f32 / 127.0,
1614 ]
1615 .iter()
1616 .map(|s| s.to_bits())
1617 .collect::<Vec<_>>(),
1618 "each group carries its own max-abs scale"
1619 );
1620 let bytes = reader
1623 .tensor_bytes("matrix.weight", &artifact)
1624 .expect("Q8 bytes resolve");
1625 let quiet_group_of_row_0 = &bytes[Q8_GROUP_WIDTH..2 * Q8_GROUP_WIDTH];
1626 for (index, &byte) in quiet_group_of_row_0.iter().enumerate() {
1627 let value = f32::from(i8::from_ne_bytes([byte])) * scales[1];
1628 let expected = quiet[index % 2];
1629 assert!(
1630 (value - expected).abs() < 1e-9,
1631 "quiet element {index}: {value} vs {expected}"
1632 );
1633 }
1634 }
1635
1636 #[test]
1637 fn manifest_verified_multi_tensor_stream_is_deterministic_and_verbatim_where_required() {
1638 let weight = [1.0_f32, -2.0, 0.5, 3.0, 0.0, -3.0]
1639 .iter()
1640 .flat_map(|value| value.to_le_bytes())
1641 .collect::<Vec<_>>();
1642 let bias = [0x80_u16, 0x3f80]
1643 .iter()
1644 .flat_map(|value| value.to_le_bytes())
1645 .collect::<Vec<_>>();
1646 let source = safetensors(&[
1647 ("weight", Dtype::F32, &[2, 3], &weight),
1648 ("bias", Dtype::Bf16, &[2], &bias),
1649 ]);
1650 let manifest = WeightsManifest::from_expectations(
1651 "small pinned fixture",
1652 [
1653 ExpectedTensor::new("weight", vec![2, 3], Dtype::F32),
1654 ExpectedTensor::new("bias", vec![2], Dtype::Bf16),
1655 ],
1656 );
1657 let plan = StreamingConversionPlan::new("qwen3-tts-fixture", sha256_hex(&source))
1658 .license_notice("Copyright 2026 Alibaba Cloud\nApache-2.0")
1659 .model_config(json!({ "fixture": true }))
1660 .quantization_manifest(json!({
1661 "weight": "q8_per_output_channel",
1662 "bias": "verbatim_bf16",
1663 }))
1664 .tensor(TensorConversion::q8_per_output_channel(
1665 "weight",
1666 "weight",
1667 AccessClass::HotRecurrentTalker,
1668 ))
1669 .tensor(TensorConversion::verbatim(
1670 "bias",
1671 "bias",
1672 AccessClass::Metadata,
1673 ));
1674
1675 let first =
1676 convert_safetensors_streaming(&source, &manifest, &plan, Cursor::new(Vec::new()))
1677 .expect("fixture converts")
1678 .into_inner();
1679 let second =
1680 convert_safetensors_streaming(&source, &manifest, &plan, Cursor::new(Vec::new()))
1681 .expect("second fixture conversion is deterministic")
1682 .into_inner();
1683 assert_eq!(
1684 first, second,
1685 "identical source and plan must be byte-identical"
1686 );
1687
1688 let reader = FttsqReader::open(&first).expect("artifact verifies its section digests");
1689 let mut runtime_q8 = [0_i8; 6];
1690 let runtime_first_scale =
1691 quantize_output_channel_q8(&[1.0_f32, -2.0, 0.5], &mut runtime_q8[..3])
1692 .expect("shared runtime primitive quantizes the first row");
1693 let runtime_second_scale =
1694 quantize_output_channel_q8(&[3.0_f32, 0.0, -3.0], &mut runtime_q8[3..])
1695 .expect("shared runtime primitive quantizes the second row");
1696 assert_eq!(
1697 reader
1698 .tensor_bytes("weight", &first)
1699 .expect("Q8 weights resolve"),
1700 runtime_q8.map(|value| value as u8)
1701 );
1702 assert_eq!(
1703 reader
1704 .tensor_bytes("weight.scales", &first)
1705 .expect("Q8 scales resolve"),
1706 &[
1707 runtime_first_scale.to_le_bytes(),
1708 runtime_second_scale.to_le_bytes(),
1709 ]
1710 .concat()
1711 );
1712 assert_eq!(
1713 reader
1714 .tensor_bytes("bias", &first)
1715 .expect("protected BF16 values resolve"),
1716 bias
1717 );
1718 }
1719
1720 #[test]
1721 fn streaming_conversion_refuses_unpinned_source_before_writing() {
1722 let source = f32_matrix(1, 2, &[1.0, -1.0]);
1723 let manifest = WeightsManifest::from_expectations(
1724 "digest fixture",
1725 [ExpectedTensor::new("matrix", vec![1, 2], Dtype::F32)],
1726 );
1727 let plan = StreamingConversionPlan::new("qwen3-tts-fixture", "0".repeat(64))
1728 .license_notice("Copyright 2026 Alibaba Cloud\nApache-2.0")
1729 .tensor(TensorConversion::q8_per_output_channel(
1730 "matrix",
1731 "matrix",
1732 AccessClass::HotRecurrentTalker,
1733 ));
1734
1735 let error =
1736 convert_safetensors_streaming(&source, &manifest, &plan, Cursor::new(Vec::new()))
1737 .expect_err("a wrong source digest must refuse before artifact construction");
1738 assert!(matches!(
1739 error,
1740 StreamingConversionError::SourceDigestMismatch { .. }
1741 ));
1742 }
1743
1744 #[test]
1745 fn matrix_quantization_refuses_vector_policy_ambiguity() {
1746 let header = serde_json::to_vec(&json!({
1747 "vector": {
1748 "dtype": "F32",
1749 "shape": [2],
1750 "data_offsets": [0, 8],
1751 }
1752 }))
1753 .expect("fixture directory serializes");
1754 let mut bytes = (header.len() as u64).to_le_bytes().to_vec();
1755 bytes.extend_from_slice(&header);
1756 bytes.extend_from_slice(&1.0_f32.to_le_bytes());
1757 bytes.extend_from_slice(&2.0_f32.to_le_bytes());
1758 let index = SafetensorsIndex::parse(&bytes).expect("fixture parses");
1759 let vector = index.view("vector", &bytes).expect("vector view exists");
1760
1761 let error = quantize_matrix_q8_rows(&vector, &mut RecordingSink::default())
1762 .expect_err("vector policy must be explicit");
1763 assert_eq!(error, MatrixQuantizationError::ExpectedMatrix { rank: 1 });
1764 }
1765
1766 #[test]
1767 fn matrix_quantization_refuses_a_row_that_breaks_its_memory_ceiling() {
1768 let values = vec![0.0_f32; MAX_Q8_OUTPUT_CHANNEL_WIDTH + 1];
1769 let bytes = f32_matrix(1, values.len(), &values);
1770 let index = SafetensorsIndex::parse(&bytes).expect("fixture parses");
1771 let matrix = index.view("matrix", &bytes).expect("matrix view exists");
1772
1773 let error = quantize_matrix_q8_rows(&matrix, &mut RecordingSink::default())
1774 .expect_err("row width must be bounded before scratch allocation");
1775 assert_eq!(
1776 error,
1777 MatrixQuantizationError::OutputChannelTooWide {
1778 width: MAX_Q8_OUTPUT_CHANNEL_WIDTH + 1,
1779 limit: MAX_Q8_OUTPUT_CHANNEL_WIDTH,
1780 }
1781 );
1782 }
1783}