fix: 16KB 페이지 대응 완료와 광고 ID 권한 제거 - #37
Merged
Merged
Conversation
1.20.0 과 1.21.0 은 libonnxruntime4j_jni.so 의 PT_LOAD 정렬이 4KB 라 16KB 페이지로 구성된 기기에서 로드에 실패한다. 1.21.1 부터 16KB 로 정렬된다. AAR 의 ELF 프로그램 헤더를 직접 읽어 확인했고, 빌드한 APK 에서도 4096 에서 16384 로 바뀐 것을 확인했다. 쓰는 API 는 createSession, OnnxTensor.createTensor, Session.run 뿐이고 두 버전의 공개 시그니처에 차이가 없다. arm64-v8a .so 합계는 약 478KB 줄어든다. libdjl_tokenizer.so 는 아직 4KB 라 이 커밋만으로 16KB 대응이 끝나지 않는다. ai.djl.android:tokenizer-native 는 0.33.0 이 유일한 배포본이라 버전으로는 못 고친다.
16KB 페이지 대응의 마지막 걸림돌이던 libdjl_tokenizer.so 를 없앤다. ai.djl.android:tokenizer-native 는 Maven Central 에 0.33.0 하나만 published 되어 있고 그 .so 가 4KB 정렬이라 버전 상향으로는 해결할 수 없었다. 대체 후보였던 onnxruntime-extensions-android 0.13.0 도 arm64-v8a 가 4KB 였다. - WordPieceTokenizer: BertNormalizer + BertPreTokenizer + WordPiece 를 직접 구현 - KobartTokenizer: NFKC + Metaspace + BPE + RobertaProcessing 과 decode 를 직접 구현 - AddedVocabulary 신규: 정규화 이전에 원문에서 추가 토큰을 leftmost-longest 로 추출한다. kobart 는 이모티콘·이모지 122개가 추가 토큰이라 이 단계가 없으면 일기 본문이 통째로 다르게 토큰화된다 - TextRules 신규: 제어문자·유니코드 공백·CJK·구두점 분류를 코드포인트 단위로 이식 - tokenizer_golden.json: 교체 직전 DJL 0.33.0 의 실제 출력을 94케이스로 덤프해 회귀 기준으로 고정한다. 절단 경계, 이모티콘 접두 관계, 보조 평면 문자를 포함한다 - 로드 시점에 model/normalizer/pre_tokenizer 형태를 검증해, 모델을 재export 하면서 파이프라인이 바뀌면 조용히 틀어지는 대신 즉시 실패하게 한다 - DJL 전용으로 번들돼 있던 jniLibs/arm64-v8a/libc++_shared.so(9.25MB) 제거. 릴리즈 APK 의 네이티브 6개 중 이를 참조하는 것이 없음을 DT_NEEDED 로 확인했다 - DJL/JNA 데스크톱 네이티브를 털어내던 packaging 제외 설정도 함께 제거 릴리즈 APK 네이티브 6개 전부 ELF p_align 16384 + APK 내 16KB 경계 정렬을 확인했다.
우리 매니페스트에는 선언이 없고, firebase-analytics 가 끌어오는 play-services-measurement-api 가 AD_ID, ACCESS_ADSERVICES_AD_ID, ACCESS_ADSERVICES_ATTRIBUTION 세 권한을 머지한다. 광고를 붙이지 않고 Google Ads 전환 추적도 쓰지 않으므로 제거해, Play Console 데이터 안전 섹션에서 광고 ID 미수집으로 신고할 수 있게 한다. Analytics 본체는 그대로 동작한다. 캠페인 전환 추적을 도입하면 되돌려야 하는 결정이라 주석으로 남겼다. 릴리즈 APK 권한 목록에서 세 권한이 모두 사라진 것을 확인했다.
4KB 정렬된 .so 는 16KB 페이지 기기에서 dlopen 자체가 실패한다. APK 정적 검사로도 잡히지만 실제 로더를 태워보는 것이 최종 확인이라, ONNX Runtime 과 LiteRT 네이티브 초기화를 강제하는 테스트를 둔다. 4KB 기기에서는 검증 의미가 없어 assumeTrue 로 건너뛴다. 16KB 환경은 google_apis_playstore_ps16k 시스템 이미지 AVD 로 만들 수 있다. Pixel_10_Pro(ps16k, API 37) 에서 통과, 4KB 실기기에서 건너뛰는 것을 확인했다.
:data 의 평가 테스트 두 건은 구조적으로 실행이 불가능했다. 라이브러리 모듈이라
테스트 APK 가 스스로 대상 앱(com.gamss.android.data.test)이 되는데, 애셋팩 APK 는
앱 모듈 패키지(com.gamss.android.dev)로 선언돼 있어 Play Core 가 패키지 불일치로
거부한다. local_testing_dir 메타데이터로 FakeAssetPackService 까지 태워봐도
"Failed fetching APKs for pack" 에서 끊긴다.
덧붙여 두 테스트는 애셋팩 도입 시점부터 이미 실행 자체가 안 되고 있었다.
`fun x() = runBlocking { ... Log.i(...) }` 의 표현식 본문이 Int 를 반환해
JUnit 이 InvalidTestClassError 로 클래스를 통째로 거부했다.
패키지가 애셋팩과 일치하는 :app 으로 옮겨 실제로 돌아가게 한다.
- OnDeviceModelEvalTest 신규: 요약 4건과 감정 분류 60건을 실기기에서 돌린다.
요약은 비어 있지 않은지, 분류는 정확도 하한을 넘는지 단정한다. 라벨 대응은
테스트셋의 label_ko 를 뒤집어 쓰고, 대응이 없으면 정확도 저하로 위장되지 않도록
즉시 끊는다. 어느 감정이 무너지는지 보이도록 라벨별 집계를 남긴다
- app/src/debug 에 local_testing_dir 메타데이터 추가(디버그 전용). 미출시 앱이라
Play 로는 팩을 못 받으므로 로컬에 넣어둔 팩 APK 를 읽게 한다
- androidx.test.runner 를 app 의 androidTest 에 추가. :data 에는 있었는데 :app 에만
빠져 있어 러너를 찾지 못했다
- 실행 절차는 OnDeviceModelEvalTest KDoc 에 런북으로 남겼다
실기기(SM-S911N, 4KB)와 16KB 에뮬레이터(Pixel_10_Pro, ps16k) 양쪽에서
요약 4건 정상, 감정 분류 44/60(73.3%) 동일함을 확인했다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
작업 개요
targetSdk 36 이라 Play 의 16KB 페이지 지원이 필수 요건인데, 마지막까지 남아 있던 걸림돌인
libdjl_tokenizer.so를 제거해 대응을 완료한다. 이.so를 제공하는ai.djl.android:tokenizer-native는 Maven Central 에 0.33.0 하나만 published 되어 있고 그것이 4KB 정렬이라 버전 상향으로는 해결할 수 없었다. 대체 후보였던onnxruntime-extensions-android0.13.0 도 arm64-v8a 가 4KB 였다.DJL 사용처가 토크나이저 래퍼 2개뿐이라, 두 토크나이저를 순수 Kotlin 으로 구현해 의존성 자체를 없앴다. 함께 Play Console 이 지적한 광고 ID 권한도 정리한다.
빌드된 APK 의
lib/arm64-v8a전수 검사 결과는 다음과 같았다.작업 유형
변경 사항
토크나이저 순수 Kotlin 교체 (DJL 제거)
WordPieceTokenizer: BertNormalizer + BertPreTokenizer + WordPiece 직접 구현KobartTokenizer: NFKC + Metaspace + BPE + RobertaProcessing 과 decode 직접 구현AddedVocabulary신규: 정규화 이전에 원문에서 추가 토큰을 leftmost-longest 로 추출. kobart 는:-),^^같은 이모티콘과 이모지 122개가 추가 토큰이라, 이 단계가 없으면 일기 본문이 통째로 다르게 토큰화된다TextRules신규: 제어문자, 유니코드 공백, CJK, 구두점 분류를 코드포인트 단위로 이식. Kotlin String 은 UTF-16 이라 UTF-16 단위로 다루면 이모지가 깨진다정확성 보증
tokenizer_golden.json: 교체 직전 DJL 0.33.0 의 실제 출력을 94케이스로 덤프해 회귀 기준으로 고정. 절단 경계, 이모티콘 접두 관계(:-)vs:-)8<), 보조 평면 문자, NFKC 호환 문자 포함TokenizerGoldenTest: 94케이스의 ids / attentionMask / typeIds / decode 완전 일치 검증용량 및 설정 정리
jniLibs/arm64-v8a/libc++_shared.so(9.25MB) 제거. 릴리즈 APK 의 네이티브 중 이를 참조하는 것이 없음을DT_NEEDED로 확인광고 ID 권한 제거
firebase-analytics가 끌어오는play-services-measurement-api가 머지하던AD_ID,ACCESS_ADSERVICES_AD_ID,ACCESS_ADSERVICES_ATTRIBUTION을tools:node="remove"로 제거. Play Console 데이터 안전 섹션에서 광고 ID 미수집으로 신고 가능해진다. Analytics 본체는 그대로 동작한다온디바이스 테스트 재편
:data의 평가 테스트 2건 삭제. 라이브러리 모듈이라 테스트 APK 가 스스로 대상 앱이 되는데 애셋팩 APK 는 앱 모듈 패키지로 선언돼 있어 Play Core 가 패키지 불일치로 거부한다. 구조적으로 실행이 불가능했다. 덧붙여 이 두 건은 애셋팩 도입 시점부터fun x() = runBlocking { ... Log.i(...) }의 표현식 본문이 Int 를 반환해 JUnit 이InvalidTestClassError로 클래스를 통째로 거부하고 있었다OnDeviceModelEvalTest신규(:app): 패키지가 애셋팩과 일치해 실제로 돌아간다. 요약 4건과 감정 분류 60건을 검증하고 라벨별 정확도를 남긴다. 실행 절차는 KDoc 에 런북으로 정리NativeLibraryLoadTest신규(:data): 16KB 페이지 기기에서 ONNX Runtime 과 LiteRT 네이티브 dlopen 을 확인. 4KB 기기에서는 건너뛴다관련 이슈
없음
관련 작업 (Notion)
없음
스크린샷 / 동작 화면
UI 변경 없음.
체크리스트
develop으로 설정되어 있다feat:,fix:등)을 따른다리뷰 요청 사항
검증 결과
.soSHA-256기능 테스트는 계측 테스트라 디버그 빌드로만 돌 수 있어서, 릴리즈와 디버그의
.so가 바이트 단위로 같음을 확인해 간극을 메웠다.isMinifyEnabled = false라 릴리즈에서도 축소가 없다.중점적으로 봐주셨으면 하는 곳
AddedVocabulary.split의 leftmost-longest 처리와 서로게이트 쌍 전진. 이모티콘 접두 관계 케이스를 골든에 넣어뒀다KobartTokenizer.encodePiece의 BPE 병합 루프. 순위가 가장 낮은 쌍의 모든 자리를 한 번에 병합하는데, 이것이 HuggingFace 의 힙 기반 구현과 같은 결과를 내는 근거를 주석에 적어뒀다WordPieceTokenizer.encode/KobartTokenizer.encode의 절단 예산 산술. HuggingFace 는 maxLength 를 특수 토큰 포함 길이로 센다알아두실 점
src/main,src/test)만 검사한다.src/androidTest는 검사 범위 밖이므로, detekt 통과를 새 테스트 파일의 품질 보증으로 읽으면 안 된다app/src/debug에 추가한local_testing_dir메타데이터 때문에, 디버그 빌드는 Play 대신 로컬 경로에서만 애셋팩을 찾는다. 미출시 앱이라 Play 경로는 어차피 막혀 있었지만, 팩을 푸시하지 않고 디버그 앱을 켜면 모델 다운로드가 실패한다:app은abiFilters가 arm64-v8a 하나라OnDeviceModelEvalTest는 arm64 기기/AVD 에서만 돈다minSdk 26은 DJL Android 요구로 24 에서 올렸던 값이다. 근거가 사라졌으니 되돌릴 수 있는 상태가 됐다(이번 PR 범위 밖)