11//
2- // DefaultDiarySummaryRepository .swift
2+ // DefaultSummaryRepository .swift
33// GAMSS
44//
5- // Created by cchanmi on 7/31 /26.
5+ // Created by cchanmi on 8/7 /26.
66//
77
88import Foundation
@@ -12,7 +12,7 @@ import Tokenizers
1212// actor로 선언해 encoder/decoder ORTSession과 tokenizer에 대한 동시 접근을 직렬화한다.
1313// ONNX Runtime 세션은 스레드 세이프하지 않아서, summarize가 여러 곳에서 동시에
1414// 호출되면 run() 호출들이 서로 레이스할 수 있다.
15- actor DefaultDiarySummaryRepository : DiarySummaryRepository {
15+ actor DefaultSummaryRepository : SummaryRepository {
1616 // 모델이 이 값들에 맞춰 학습/export되어 있으므로 임의 변경 금지.
1717 private static let decoderStartToken = 1
1818 private static let eosToken = 1
@@ -40,12 +40,12 @@ actor DefaultDiarySummaryRepository: DiarySummaryRepository {
4040 self . tokenizer = tokenizer
4141 }
4242
43- static func make( ) async throws -> DefaultDiarySummaryRepository {
43+ static func make( ) async throws -> DefaultSummaryRepository {
4444 guard
4545 let encoderPath = Bundle . main. path ( forResource: " kobart_encoder_int8 " , ofType: " onnx " ) ,
4646 let decoderPath = Bundle . main. path ( forResource: " kobart_decoder_int8 " , ofType: " onnx " )
4747 else {
48- throw DiarySummaryError . modelLoadFailed ( )
48+ throw SummaryError . modelLoadFailed ( )
4949 }
5050
5151 let encoder : ORTSession
@@ -55,11 +55,11 @@ actor DefaultDiarySummaryRepository: DiarySummaryRepository {
5555 encoder = try ORTSession ( env: env, modelPath: encoderPath, sessionOptions: nil )
5656 decoder = try ORTSession ( env: env, modelPath: decoderPath, sessionOptions: nil )
5757 } catch {
58- throw DiarySummaryError . modelLoadFailed ( underlying: error)
58+ throw SummaryError . modelLoadFailed ( underlying: error)
5959 }
6060
6161 let tokenizer = try await Self . loadTokenizer ( )
62- return DefaultDiarySummaryRepository ( encoder: encoder, decoder: decoder, tokenizer: tokenizer)
62+ return DefaultSummaryRepository ( encoder: encoder, decoder: decoder, tokenizer: tokenizer)
6363 }
6464
6565 // AutoTokenizer.from(modelFolder:)는 폴더 안에서 표준 파일명("tokenizer.json"/"tokenizer_config.json")을
@@ -68,7 +68,7 @@ actor DefaultDiarySummaryRepository: DiarySummaryRepository {
6868 // 이를 피하기 위해 표준 파일명으로만 구성된 격리된 임시 폴더를 만들어 그 안에서 로드한다.
6969 private static func loadTokenizer( ) async throws -> Tokenizer {
7070 guard let tokenizerURL = Bundle . main. url ( forResource: " kobart_tokenizer " , withExtension: " json " ) else {
71- throw DiarySummaryError . modelLoadFailed ( )
71+ throw SummaryError . modelLoadFailed ( )
7272 }
7373
7474 let isolatedFolder = FileManager . default. temporaryDirectory. appendingPathComponent ( UUID ( ) . uuidString, isDirectory: true )
@@ -87,7 +87,7 @@ actor DefaultDiarySummaryRepository: DiarySummaryRepository {
8787
8888 return try await AutoTokenizer . from ( modelFolder: isolatedFolder)
8989 } catch {
90- throw DiarySummaryError . modelLoadFailed ( underlying: error)
90+ throw SummaryError . modelLoadFailed ( underlying: error)
9191 }
9292 }
9393
@@ -108,20 +108,26 @@ actor DefaultDiarySummaryRepository: DiarySummaryRepository {
108108 runOptions: nil
109109 )
110110 guard let encoderHidden = encoderOutputs [ Self . encoderOutputKey] else {
111- throw DiarySummaryError . inferenceFailed ( )
111+ throw SummaryError . inferenceFailed ( )
112112 }
113113
114114 let generatedTokens = try greedyDecode ( encoderHidden: encoderHidden, encoderAttentionMask: maskTensor)
115115 let summary = tokenizer. decode ( tokens: generatedTokens, skipSpecialTokens: true )
116116 return summary. trimmingCharacters ( in: . whitespacesAndNewlines)
117- } catch let error as DiarySummaryError {
117+ } catch let error as SummaryError {
118118 throw error
119119 } catch {
120120 Log . error ( " 요약 추론 실패: \( error) " )
121- throw DiarySummaryError . inferenceFailed ( underlying: error)
121+ throw SummaryError . inferenceFailed ( underlying: error)
122122 }
123123 }
124124
125+ /// 절단 없는 실제 토큰 수. swift-transformers의 encode()는 자체적으로 truncation을 하지 않으므로
126+ /// (그 truncation은 summarize()의 buildEncoderInputs에서만 수동으로 함) 그대로 개수를 세면 된다.
127+ func countTokens( text: String ) async throws -> Int {
128+ tokenizer. encode ( text: text) . count
129+ }
130+
125131 // 인코더 1회 실행 결과(encoderHidden)를 매 스텝 재사용하며, 지금까지 생성된 전체 시퀀스를
126132 // 다시 디코더에 통째로 넣는 cache-free 그리디 디코딩(디코더가 KV 캐시를 안 쓰므로).
127133 private func greedyDecode( encoderHidden: ORTValue , encoderAttentionMask: ORTValue ) throws -> [ Int ] {
@@ -140,7 +146,7 @@ actor DefaultDiarySummaryRepository: DiarySummaryRepository {
140146 runOptions: nil
141147 )
142148 guard let logitsValue = decoderOutputs [ Self . decoderOutputKey] else {
143- throw DiarySummaryError . inferenceFailed ( )
149+ throw SummaryError . inferenceFailed ( )
144150 }
145151
146152 let nextToken = try Self . argmaxLastPosition (
@@ -189,7 +195,7 @@ actor DefaultDiarySummaryRepository: DiarySummaryRepository {
189195 let shapeInfo = try logits. tensorTypeAndShapeInfo ( )
190196 let shape = shapeInfo. shape. map ( \. intValue)
191197 guard shape. count == 3 , shape [ 1 ] == sequenceLength, shape [ 2 ] == vocabSize else {
192- throw DiarySummaryError . inferenceFailed ( )
198+ throw SummaryError . inferenceFailed ( )
193199 }
194200
195201 let data = try logits. tensorData ( ) as Data
0 commit comments