@@ -34,7 +34,7 @@ type IndexConfig struct {
3434 Quantization store.QuantizationMode // Vector quantization mode (none, int8, binary)
3535 SmartSkip bool // Enable smart skipping for large repos (default: true)
3636 AdaptiveColBERTSegments bool // Enable token-aware sqrt(M) ColBERT segment budgets
37- ColBERTCodec store.ColBERTCodec // Late interaction segment codec (int8, pq6)
37+ ColBERTCodec store.ColBERTCodec // Late interaction segment codec (tqmse, int8, pq6)
3838}
3939
4040// Large repo threshold - above this we enable smart skipping
@@ -47,10 +47,30 @@ const (
4747 colbertPQMinTrainingVectors = 256
4848 colbertPQTrainIterations = 25
4949 colbertPQMinWorthwhileSegments = 5000
50+ colbertTQMSEBits = 4
51+ colbertTQMSESeed = 42
5052 colbertPreindexChunkFetchBatchSize = 64
5153 colbertRewriteChunkBatchSize = 128
5254)
5355
56+ func colbertEmbeddingDims () int {
57+ if v := os .Getenv ("SGREP_DIMS" ); v != "" {
58+ var dims int
59+ if _ , err := fmt .Sscanf (v , "%d" , & dims ); err == nil && dims > 0 {
60+ return dims
61+ }
62+ }
63+ return 768
64+ }
65+
66+ func newDefaultColBERTTQMSEQuantizer () (* util.TQMSEQuantizer , error ) {
67+ return util .NewTQMSEQuantizer (util.TQMSEConfig {
68+ Dims : colbertEmbeddingDims (),
69+ Bits : colbertTQMSEBits ,
70+ Seed : colbertTQMSESeed ,
71+ })
72+ }
73+
5474// DefaultIndexConfig returns sensible defaults for indexing.
5575func DefaultIndexConfig () * IndexConfig {
5676 // Workers = parallel file readers (CPU-bound: read + chunk)
@@ -86,6 +106,7 @@ type Indexer struct {
86106 ignore * IgnoreRules
87107 colbertCodec store.ColBERTCodec
88108 colbertPQ * util.ProductQuantizer
109+ colbertTQMSE * util.TQMSEQuantizer
89110 processed int64
90111 errors int64
91112}
@@ -132,14 +153,19 @@ func NewWithConfig(path string, cfg *IndexConfig) (*Indexer, error) {
132153 }
133154 existingCodec := store .ColBERTCodecUnspecified
134155 var existingPQ * util.ProductQuantizer
156+ var existingTQMSE * util.TQMSEQuantizer
135157 if provider , ok := s .(store.ColBERTMetadataProvider ); ok {
136158 existingCodec = provider .ColBERTCodec ()
137159 existingPQ = provider .ProductQuantizer ()
160+ existingTQMSE = provider .TQMSEQuantizer ()
138161 }
139162 effectiveCodec := store .ResolveColBERTCodec (cfg .ColBERTCodec , existingCodec )
140163 if effectiveCodec != store .ColBERTCodecPQ6 {
141164 existingPQ = nil
142165 }
166+ if effectiveCodec != store .ColBERTCodecTQMSE {
167+ existingTQMSE = nil
168+ }
143169
144170 // Load ignore rules
145171 ignore := NewIgnoreRules (absPath )
@@ -154,6 +180,7 @@ func NewWithConfig(path string, cfg *IndexConfig) (*Indexer, error) {
154180 ignore : ignore ,
155181 colbertCodec : effectiveCodec ,
156182 colbertPQ : existingPQ ,
183+ colbertTQMSE : existingTQMSE ,
157184 }, nil
158185}
159186
@@ -1553,6 +1580,7 @@ func (idx *Indexer) applyPQCodecSizeGate(ctx context.Context, segmentStore store
15531580 estimatedSegments , colbertPQMinWorthwhileSegments )
15541581 idx .colbertCodec = store .ColBERTCodecInt8
15551582 idx .colbertPQ = nil
1583+ idx .colbertTQMSE = nil
15561584 return nil
15571585}
15581586
@@ -1727,6 +1755,7 @@ func (idx *Indexer) computeFreshPQSegmentsFromScratch(ctx context.Context, segme
17271755 fmt .Printf ("Skipping PQ6: only %d segment vectors available, keeping int8\n " , totalVectors )
17281756 idx .colbertCodec = store .ColBERTCodecInt8
17291757 idx .colbertPQ = nil
1758+ idx .colbertTQMSE = nil
17301759 written , err := idx .rewriteScratchColBERTSegments (ctx , segmentStore , scratchPath , totalChunks )
17311760 if err != nil {
17321761 return written , err
@@ -1754,6 +1783,7 @@ func (idx *Indexer) computeFreshPQSegmentsFromScratch(ctx context.Context, segme
17541783 fmt .Printf ("ColBERT PQ train wall time: %v\n " , time .Since (trainStart ).Round (time .Millisecond ))
17551784
17561785 idx .colbertPQ = pq
1786+ idx .colbertTQMSE = nil
17571787 written , err := idx .rewriteScratchColBERTSegments (ctx , segmentStore , scratchPath , totalChunks )
17581788 if err != nil {
17591789 return written , err
@@ -1787,6 +1817,9 @@ func (idx *Indexer) rewriteScratchColBERTSegments(ctx context.Context, segmentSt
17871817 if idx .colbertCodec == store .ColBERTCodecPQ6 && idx .colbertPQ != nil {
17881818 label = "Re-encoding ColBERT segments with PQ6"
17891819 progressVerb = "Re-encoded"
1820+ } else if idx .colbertCodec == store .ColBERTCodecTQMSE && idx .colbertTQMSE != nil {
1821+ label = "Encoding ColBERT segments with TQ-MSE"
1822+ progressVerb = "Encoded"
17901823 }
17911824 fmt .Printf ("%s for %d chunks...\n " , label , totalChunks )
17921825
@@ -1814,7 +1847,7 @@ func (idx *Indexer) rewriteScratchColBERTSegments(ctx context.Context, segmentSt
18141847 }
18151848 return processed , fmt .Errorf ("read ColBERT PQ scratch record: %w" , err )
18161849 }
1817- converted , err := convertColBERTSegmentsForCodecStrict (record .ChunkID , record .Segments , idx .colbertCodec , idx .colbertPQ )
1850+ converted , err := convertColBERTSegmentsForCodecStrict (record .ChunkID , record .Segments , idx .colbertCodec , idx .colbertPQ , idx . colbertTQMSE )
18181851 if err != nil {
18191852 return processed , fmt .Errorf ("convert scratch chunk %s to %s: %w" , record .ChunkID , idx .colbertCodec , err )
18201853 }
@@ -1927,7 +1960,7 @@ func (idx *Indexer) ExportColBERTToMMap(ctx context.Context, outputDir string) (
19271960 defer func () { _ = os .RemoveAll (tempDir ) }()
19281961
19291962 // Build the mmap in a temp location, then atomically replace the live file.
1930- mmapStore , err := store .OpenMMapSegmentStore (tempDir , 768 )
1963+ mmapStore , err := store .OpenMMapSegmentStore (tempDir , colbertEmbeddingDims () )
19311964 if err != nil {
19321965 return 0 , fmt .Errorf ("failed to create MMap store: %w" , err )
19331966 }
@@ -1936,8 +1969,21 @@ func (idx *Indexer) ExportColBERTToMMap(ctx context.Context, outputDir string) (
19361969 _ = mmapStore .Close ()
19371970 }
19381971 }()
1972+ exportCodec := store .ColBERTCodecInt8
1973+ var exportPQ * util.ProductQuantizer
1974+ var exportTQ * util.TQMSEQuantizer
19391975 if provider , ok := segmentStore .(store.ColBERTMetadataProvider ); ok {
1940- mmapStore .SetColBERTCodec (provider .ColBERTCodec (), provider .ProductQuantizer ())
1976+ switch provider .ColBERTCodec () {
1977+ case store .ColBERTCodecPQ6 :
1978+ exportCodec = store .ColBERTCodecPQ6
1979+ exportPQ = provider .ProductQuantizer ()
1980+ case store .ColBERTCodecTQMSE :
1981+ exportCodec = store .ColBERTCodecTQMSE
1982+ exportTQ = provider .TQMSEQuantizer ()
1983+ default :
1984+ exportCodec = store .ColBERTCodecInt8
1985+ }
1986+ mmapStore .SetColBERTCodec (exportCodec , exportPQ , exportTQ )
19411987 }
19421988
19431989 mmapStore .BeginWrite ()
@@ -1978,10 +2024,15 @@ func (idx *Indexer) ExportColBERTToMMap(ctx context.Context, outputDir string) (
19782024 return 0 , fmt .Errorf ("failed to load segments: %w" , err )
19792025 }
19802026
1981- // Write segments to MMap
2027+ // Write segments to MMap in the artifact codec. SQLite stores may be
2028+ // mixed after an upgrade or partial refresh.
19822029 for chunkID , chunkSegments := range segments {
1983- mmapStore .WriteSegments (chunkID , chunkSegments )
1984- totalSegments += len (chunkSegments )
2030+ encodedSegments , err := convertColBERTSegmentsForCodecStrict (chunkID , chunkSegments , exportCodec , exportPQ , exportTQ )
2031+ if err != nil {
2032+ return 0 , fmt .Errorf ("failed to encode chunk %s for MMap export: %w" , chunkID , err )
2033+ }
2034+ mmapStore .WriteSegments (chunkID , encodedSegments )
2035+ totalSegments += len (encodedSegments )
19852036 }
19862037 }
19872038
@@ -2060,7 +2111,7 @@ func (idx *Indexer) buildColBERTChunkSegments(ctx context.Context, chunks []stor
20602111
20612112 chunkSegments := make (map [string ][]store.ColBERTSegment , len (floatSegments ))
20622113 for chunkID , segments := range floatSegments {
2063- chunkSegments [chunkID ] = convertColBERTSegmentsForCodec (chunkID , segments , idx .colbertCodec , idx .colbertPQ )
2114+ chunkSegments [chunkID ] = convertColBERTSegmentsForCodec (chunkID , segments , idx .colbertCodec , idx .colbertPQ , idx . colbertTQMSE )
20642115 }
20652116 return chunkSegments , nil
20662117}
@@ -2115,9 +2166,9 @@ func (idx *Indexer) buildFloat32ColBERTChunkSegments(ctx context.Context, chunks
21152166 return chunkSegments , nil
21162167}
21172168
2118- func buildStoredColBERTSegments (chunkID string , segmentTexts []string , embeddings [][]float32 , adaptive bool , codec store.ColBERTCodec , pq * util.ProductQuantizer ) []store.ColBERTSegment {
2169+ func buildStoredColBERTSegments (chunkID string , segmentTexts []string , embeddings [][]float32 , adaptive bool , codec store.ColBERTCodec , pq * util.ProductQuantizer , tq * util. TQMSEQuantizer ) []store.ColBERTSegment {
21192170 segments := buildFloat32ColBERTSegments (segmentTexts , embeddings , adaptive )
2120- return convertColBERTSegmentsForCodec (chunkID , segments , codec , pq )
2171+ return convertColBERTSegmentsForCodec (chunkID , segments , codec , pq , tq )
21212172}
21222173
21232174func buildFloat32ColBERTSegments (segmentTexts []string , embeddings [][]float32 , adaptive bool ) []store.ColBERTSegment {
@@ -2147,27 +2198,38 @@ func buildFloat32ColBERTSegments(segmentTexts []string, embeddings [][]float32,
21472198 return segments
21482199}
21492200
2150- func convertColBERTSegmentsForCodec (chunkID string , segments []store.ColBERTSegment , codec store.ColBERTCodec , pq * util.ProductQuantizer ) []store.ColBERTSegment {
2151- converted , _ := convertColBERTSegmentsForCodecWithFallback (chunkID , segments , codec , pq , true )
2201+ func convertColBERTSegmentsForCodec (chunkID string , segments []store.ColBERTSegment , codec store.ColBERTCodec , pq * util.ProductQuantizer , tq * util. TQMSEQuantizer ) []store.ColBERTSegment {
2202+ converted , _ := convertColBERTSegmentsForCodecWithFallback (chunkID , segments , codec , pq , tq , true )
21522203 return converted
21532204}
21542205
2155- func convertColBERTSegmentsForCodecStrict (chunkID string , segments []store.ColBERTSegment , codec store.ColBERTCodec , pq * util.ProductQuantizer ) ([]store.ColBERTSegment , error ) {
2156- return convertColBERTSegmentsForCodecWithFallback (chunkID , segments , codec , pq , false )
2206+ func convertColBERTSegmentsForCodecStrict (chunkID string , segments []store.ColBERTSegment , codec store.ColBERTCodec , pq * util.ProductQuantizer , tq * util. TQMSEQuantizer ) ([]store.ColBERTSegment , error ) {
2207+ return convertColBERTSegmentsForCodecWithFallback (chunkID , segments , codec , pq , tq , false )
21572208}
21582209
2159- func convertColBERTSegmentsForCodecWithFallback (chunkID string , segments []store.ColBERTSegment , codec store.ColBERTCodec , pq * util.ProductQuantizer , allowPQFallback bool ) ([]store.ColBERTSegment , error ) {
2210+ func convertColBERTSegmentsForCodecWithFallback (chunkID string , segments []store.ColBERTSegment , codec store.ColBERTCodec , pq * util.ProductQuantizer , tq * util. TQMSEQuantizer , allowFallback bool ) ([]store.ColBERTSegment , error ) {
21602211 if codec == store .ColBERTCodecPQ6 && pq != nil {
21612212 encoded , err := encodeSegmentsToPQ (chunkID , segments , pq )
21622213 if err != nil {
2163- if ! allowPQFallback {
2214+ if ! allowFallback {
21642215 return nil , err
21652216 }
21662217 fmt .Fprintf (os .Stderr , "Warning: ColBERT PQ encode failed for chunk %s, falling back to int8: %v\n " , chunkID , err )
21672218 } else {
21682219 return encoded , nil
21692220 }
21702221 }
2222+ if codec == store .ColBERTCodecTQMSE && tq != nil {
2223+ encoded , err := encodeSegmentsToTQMSE (chunkID , segments , tq )
2224+ if err != nil {
2225+ if ! allowFallback {
2226+ return nil , err
2227+ }
2228+ fmt .Fprintf (os .Stderr , "Warning: ColBERT TQ-MSE encode failed for chunk %s, falling back to int8: %v\n " , chunkID , err )
2229+ } else {
2230+ return encoded , nil
2231+ }
2232+ }
21712233
21722234 for i := range segments {
21732235 if segments [i ].Embedding != nil {
@@ -2181,15 +2243,67 @@ func convertColBERTSegmentsForCodecWithFallback(chunkID string, segments []store
21812243 return segments , nil
21822244}
21832245
2246+ func encodeSegmentsToTQMSE (chunkID string , segments []store.ColBERTSegment , tq * util.TQMSEQuantizer ) ([]store.ColBERTSegment , error ) {
2247+ if tq == nil {
2248+ return nil , fmt .Errorf ("nil tqmse quantizer" )
2249+ }
2250+ encoded := make ([]store.ColBERTSegment , len (segments ))
2251+ encoder := tq .NewEncoder ()
2252+ codeSize := tq .CodeSize ()
2253+ for i , seg := range segments {
2254+ if len (seg .TQCodes ) == codeSize {
2255+ encoded [i ] = store.ColBERTSegment {
2256+ SegmentIdx : seg .SegmentIdx ,
2257+ Text : seg .Text ,
2258+ TQCodes : append ([]byte (nil ), seg .TQCodes ... ),
2259+ }
2260+ continue
2261+ }
2262+ emb := seg .Embedding
2263+ if emb == nil && len (seg .EmbeddingInt8 ) > 0 {
2264+ emb = util .DequantizeInt8 (seg .EmbeddingInt8 , seg .QuantScale , seg .QuantMin )
2265+ emb = util .NormalizeVector (emb )
2266+ }
2267+ if emb == nil {
2268+ if len (seg .TQCodes ) > 0 {
2269+ return nil , fmt .Errorf ("segment %d in chunk %s has malformed TQ-MSE code length %d (expected %d)" , seg .SegmentIdx , chunkID , len (seg .TQCodes ), codeSize )
2270+ }
2271+ return nil , fmt .Errorf ("segment %d in chunk %s is missing an encodable embedding" , seg .SegmentIdx , chunkID )
2272+ }
2273+ code , err := encoder .Encode (emb )
2274+ if err != nil {
2275+ return nil , err
2276+ }
2277+ encoded [i ] = store.ColBERTSegment {
2278+ SegmentIdx : seg .SegmentIdx ,
2279+ Text : seg .Text ,
2280+ TQCodes : append ([]byte (nil ), code .Codes ... ),
2281+ }
2282+ }
2283+ return encoded , nil
2284+ }
2285+
21842286func encodeSegmentsToPQ (chunkID string , segments []store.ColBERTSegment , pq * util.ProductQuantizer ) ([]store.ColBERTSegment , error ) {
21852287 encoded := make ([]store.ColBERTSegment , len (segments ))
2288+ codeSize := pq .CodeSize ()
21862289 for i , seg := range segments {
2290+ if len (seg .PQCodes ) == codeSize {
2291+ encoded [i ] = store.ColBERTSegment {
2292+ SegmentIdx : seg .SegmentIdx ,
2293+ Text : seg .Text ,
2294+ PQCodes : append ([]byte (nil ), seg .PQCodes ... ),
2295+ }
2296+ continue
2297+ }
21872298 emb := seg .Embedding
21882299 if emb == nil && len (seg .EmbeddingInt8 ) > 0 {
21892300 emb = util .DequantizeInt8 (seg .EmbeddingInt8 , seg .QuantScale , seg .QuantMin )
21902301 emb = util .NormalizeVector (emb )
21912302 }
21922303 if emb == nil {
2304+ if len (seg .PQCodes ) > 0 {
2305+ return nil , fmt .Errorf ("segment %d in chunk %s has malformed PQ code length %d (expected %d)" , seg .SegmentIdx , chunkID , len (seg .PQCodes ), codeSize )
2306+ }
21932307 return nil , fmt .Errorf ("segment %d in chunk %s is missing an encodable embedding" , seg .SegmentIdx , chunkID )
21942308 }
21952309 codes , err := pq .Encode (emb )
@@ -2215,9 +2329,24 @@ func decomposeDocumentForColBERT(content string, adaptive bool) []string {
22152329}
22162330
22172331func (idx * Indexer ) ensureColBERTCodecReady (ctx context.Context , segmentStore store.ColBERTSegmentStorer ) error {
2332+ if idx .colbertCodec == store .ColBERTCodecTQMSE {
2333+ if idx .colbertTQMSE == nil {
2334+ tq , err := newDefaultColBERTTQMSEQuantizer ()
2335+ if err != nil {
2336+ return fmt .Errorf ("create colbert tqmse: %w" , err )
2337+ }
2338+ idx .colbertTQMSE = tq
2339+ }
2340+ idx .colbertPQ = nil
2341+ return idx .persistColBERTMetadata (ctx )
2342+ }
2343+
22182344 if idx .colbertCodec != store .ColBERTCodecPQ6 {
2345+ idx .colbertPQ = nil
2346+ idx .colbertTQMSE = nil
22192347 return idx .persistColBERTMetadata (ctx )
22202348 }
2349+ idx .colbertTQMSE = nil
22212350 if idx .colbertPQ != nil {
22222351 return nil
22232352 }
@@ -2231,6 +2360,7 @@ func (idx *Indexer) ensureColBERTCodecReady(ctx context.Context, segmentStore st
22312360 fmt .Printf ("Skipping PQ6: only %d segment vectors available, keeping int8\n " , totalVectors )
22322361 idx .colbertCodec = store .ColBERTCodecInt8
22332362 idx .colbertPQ = nil
2363+ idx .colbertTQMSE = nil
22342364 return idx .persistColBERTMetadata (ctx )
22352365 }
22362366 fmt .Printf ("Training ColBERT PQ codebook on %d sampled vectors (%d total)...\n " , len (sample ), totalVectors )
@@ -2249,6 +2379,7 @@ func (idx *Indexer) ensureColBERTCodecReady(ctx context.Context, segmentStore st
22492379 }
22502380
22512381 idx .colbertPQ = pq
2382+ idx .colbertTQMSE = nil
22522383 return nil
22532384}
22542385
@@ -2257,7 +2388,7 @@ func (idx *Indexer) persistColBERTMetadata(ctx context.Context) error {
22572388 if ! ok {
22582389 return nil
22592390 }
2260- return metadataStore .SaveColBERTMetadata (ctx , idx .colbertCodec , idx .colbertPQ )
2391+ return metadataStore .SaveColBERTMetadata (ctx , idx .colbertCodec , idx .colbertPQ , idx . colbertTQMSE )
22612392}
22622393
22632394func (idx * Indexer ) collectColBERTPQTrainingSample (ctx context.Context , segmentStore store.ColBERTSegmentStorer ) ([][]float32 , int , error ) {
0 commit comments