@@ -523,3 +523,115 @@ def export_zips(
523523 )
524524 return zips
525525
526+
527+
528+ def collect_decode_calibration (
529+ enc_sess , dec_sess , texts : list [str ], steps : int = 64 ,
530+ ) -> list [dict ]:
531+ """Decoder feeds across framings for static activation calibration:
532+ prefills, incremental single-token steps, and 8-token windows —
533+ the shapes whose scales must hold (the framing axis). Mirrors
534+ scripts/static_int8_experiment.py, which measured the recipe."""
535+ import numpy as np
536+
537+ EOS_ID , PAD_ID = 1 , 0
538+ encode = encode_bytes
539+
540+ past_names = [i .name for i in dec_sess .get_inputs () if i .name .startswith ("past_" )]
541+ meta = {i .name : i for i in dec_sess .get_inputs ()}
542+ present_of = {
543+ o .name .replace ("present_" , "past_" ): o .name
544+ for o in dec_sess .get_outputs ()
545+ if o .name .startswith ("present_" )
546+ }
547+
548+ def run (tokens , hidden , pasts ):
549+ feed = {
550+ "input_ids" : np .array ([tokens ], dtype = np .int64 ),
551+ "encoder_hidden_states" : hidden ,
552+ }
553+ for n in past_names :
554+ feed [n ] = pasts .get (n ) if pasts else np .zeros (
555+ (1 , meta [n ].shape [1 ], 0 , meta [n ].shape [3 ]), dtype = np .float32
556+ )
557+ out = dec_sess .run (None , feed )
558+ names = [o .name for o in dec_sess .get_outputs ()]
559+ return dict (zip (names , out , strict = True ))
560+
561+ def split (out ):
562+ return int (np .argmax (out ["logits" ][0 , - 1 ])), {
563+ k : out [v ] for k , v in present_of .items ()
564+ }
565+
566+ def trim (pasts , n ):
567+ return {k : v [:, :, :n , :].copy () for k , v in pasts .items ()}
568+
569+ samples : list [dict ] = []
570+
571+ def record (tokens , hidden , pasts ):
572+ feed = {"input_ids" : np .array ([tokens ], dtype = np .int64 ),
573+ "encoder_hidden_states" : hidden }
574+ for n in past_names :
575+ feed [n ] = pasts .get (n ) if pasts else np .zeros (
576+ (1 , meta [n ].shape [1 ], 0 , meta [n ].shape [3 ]), dtype = np .float32
577+ )
578+ samples .append (feed )
579+
580+ for text in texts :
581+ ids = encode (text )
582+ hidden = enc_sess .run (None , {"input_ids" : np .array ([ids ], dtype = np .int64 )})[0 ]
583+ tok , pasts = split (run ([PAD_ID ], hidden , None ))
584+ record ([PAD_ID ], hidden , None )
585+ window = []
586+ for _ in range (steps ):
587+ window .append (tok )
588+ record ([tok ], hidden , pasts )
589+ tok , pasts = split (run ([tok ], hidden , pasts ))
590+ if tok == EOS_ID :
591+ break
592+ if len (window ) == 8 :
593+ seq_len = next (iter (pasts .values ())).shape [2 ]
594+ record (window , hidden , trim (pasts , max (seq_len - len (window ), 0 )))
595+ window = []
596+ return samples
597+
598+
599+ def quantize_int8_static (
600+ src : Path | str , dst : Path | str , calibration : list [dict ],
601+ nodes_to_exclude : list [str ] | None = None ,
602+ ) -> Path :
603+ """fp32 -> static-int8 (calibrated QUInt8 activations, MatMul-only,
604+ head fp32). Activation scales live in the graph — the export-side
605+ half of TODO.impl/11: quality-clean at full set (4.6241 vs dynamic
606+ 4.5701) and +8% CPU decode speed. Note the measured composition:
607+ fp32 encoder + static decoder; an int8-encoder + static-decoder
608+ (browser-size) composition needs its own gate run before shipping.
609+ """
610+ from onnxruntime .quantization import (
611+ CalibrationDataReader ,
612+ QuantFormat ,
613+ QuantType ,
614+ quantize_static ,
615+ )
616+
617+ class _Reader (CalibrationDataReader ):
618+ def __init__ (self , data ):
619+ self .data = list (data )
620+
621+ def get_next (self ):
622+ return self .data .pop (0 ) if self .data else None
623+
624+ def rewind (self ):
625+ pass # one pass; the corpus is the calibration set
626+
627+ quantize_static (
628+ str (src ),
629+ str (dst ),
630+ calibration_data_reader = _Reader (calibration ),
631+ quant_format = QuantFormat .QOperator ,
632+ activation_type = QuantType .QUInt8 ,
633+ weight_type = QuantType .QInt8 ,
634+ op_types_to_quantize = ["MatMul" ],
635+ nodes_to_exclude = nodes_to_exclude or [],
636+ )
637+ return Path (dst )
0 commit comments