본문 바로가기
AI Project/Edge AI Agent - LLM(연구,분석,검증)

#4. BitNet과 A35의 한계.. 갤럭시 A35 x BitNet.cpp (실패기이자 학습기)

by 으노으뇨 2026. 3. 23.
728x90
SMALL

결론  : 멍멍이 같이 실패... 

내가 직접 Termux + Android + A35(Exynos 1380) 환경에서
BitNet-b1.58-2B-4T(i2_s)를 빌드부터 실행까지 끝까지 밀어붙인 실전 기록이다.
결론부터 말하면: BitNet i2_s는 “빌드는 성공할 수 있다”.
하지만 “자연어 품질”은 결코 제대로 나오지 않는다..................
직접 겪어본 진짜 이야기...
AI 가 대 딸123깎쑈 하는 세상에서 진짜 쳐맞고 두들겨맞고 경험한 블로그입니다. 

 

1. 전황 보고: 왜 시작했는가?

연봉 높으신 엘리트들이 맥북 프로에서 코파일럿이나 딸깎거리며 64비트 연산을 논할 때

나는 연봉 2천 대의 독기를 품고 갤럭시 A35(엑시노스 1380)라는 척박한 땅에서

세상을 전복할 자율형 에이전트 '아메바'를 심기로 했었다.

목표는 마이크로소프트의 BitNet.cpp를 안드로이드 Termux 환경에서 네이티브로 빌드하여 구동하는 것

2. 겪어온 지옥과 시행착오

우리는 단순히 cmake 버튼을 누른 것이 아니라, 소스 코드 하나하나를 수술하며 전진했다.

시행착오 내용

학습된 내용
유령 링크와 404의 저주 마이크로소프트의 공식 리포지토리는 터뮤즈 환경을 배려하지 않았다. 
헤더 파일들은 깨진 심볼릭 링크뿐이었고, 자동화 스크립트(setup_env.py)는 에러 페이지를 파일로 저장하는 등 끔찍했다. 그리고 터뮤즈 환경에서 잘 돌아가지 않았고 이상했다. 여튼
curl을 통해 GitHub RAW 소스에서 직접 헤더를 탈취하고, 수동으로 include 폴더를 재구성하는 '강제 확보' 기술을 습득학습
Clang 21의 엄격한..태도 최신 컴파일러 Clang 21은 const 규약을 어기는 꼴을 보지 못했다. 빌드가 16번에서 멈출 때마다 우리는 포기하는 대신 sed 명령어로 소스 코드의 함수 선언부를 직접 수술했다 ed를 이용한 소스 레벨의 자동 수술과 컴파일러 에러 메시지를 뜯어보는 '코드 수정' 감각을 익혔다
매크로 지뢰밭 수동 빌드 시 PARALLEL_SIZE, ROW_BLOCK_SIZE 같은 핵심 연산 상수가 정의되지 않아 빌드가 터졌다 gemm-config.h를 직접 설계하여 하드웨어(ARMv8.2)에 최적화된 연산 규격을 주입하는 '저수준 최적화'를 경험했다

결과 : 빌드 마라톤인 [120/120] 완주, 그러나 꼬여버린 ... 모델들ㅠㅠㅠ

결국 닌자(Ninja) 빌드 120번 고지를 정복했다. 하지만 기쁨도 잠시, 1.1GB짜리 i2_s 실탄을 장전하고 격발하자 아메바는 **"단어 샐러드(Word Salad)"**와 "느낌표(!!!!) 테러"를 뱉어냈다.

또는 이상한 헛소리...


학습된 내용 정리

BitNet i2_s는 단순한 ‘2bit 양자화 모델’이 아니다

처음엔 단순히 “i2_s 모델이니까 i2_s 커널로 돌리면 되겠지?”라고 생각했다. 하지만 파고들면 들수록 정반대였다.

BitNet i2_s 모델은 가중치(1.58bit)는 i2_s 방식이지만, 커널과 LUT는 tl1 스타일을 요구한다.

setup_env.py가 이 LUT를 자동 생성해줘야 하는데 Termux에서 sentencepiece/gguf-py 의존성 때문에 실행 실패.

spm-headers 내부 파일은 진짜 파일이 아니라 심볼릭 링크라 cp도 실패........

gemm-config.h는 아예 존재하지도 않는다.

이처럼 BitNet은 단순 양자화 LLM들과 구조 자체가 다르다.

그래서 엔진을 i2_s로 빌드하는 게 아니라, codegen_tl1.py로 LUT를 먼저 생성해야 한다. 그리고 그렇게 했었다. ㅠ

이걸 모르고 템플릿 LUT로 돌리면 출력은 반드시 난수처럼 깨진다.

우리가 실제로 해결했던 부분들

솔직히 여기까지 오기까지 꽤 많은 삽질이 필요했다.

spm-headers의 깨진 심볼릭 링크

bitnet-lut-kernels.h 복사하려고 했는데
찾아보니 “404: Not Found”가 문자열로 들어 있는 파일이었다.
이래서 cp가 실패하고 빌드도 실패.

gemm-config.h는 BitNet에서 자동 생성해야 하는 파일

Termux에서는 생성이 안 돼서 수동으로 직접 제작.

Clang 21 const 시그니처 버그

ggml_compute_forward_* 함수가 const 문제로 컴파일 오류 발생 → 수동 패치.

high_resolution_clock → steady_clock 패치

Android NDK 환경에서 흔히 발생하는 chrono 버그 대응.

codegen_tl1.py로 LUT 생성

bitnet_b1_58-3B 모델 구조로 LUT 생성 → 2B 모델에도 적용 가능.
여기까지 하면진은 완벽하게 빌드된다.

하지만 결국 넘어야 할 마지막 벽 : 토크나이저 불일치

BitNet 모델을 실행하자마자 llama.cpp가 이렇게 말한다:

llm_load_vocab: GENERATION QUALITY WILL BE DEGRADED!

이 메시지의 의미는 다음과 같다.

모델의 토크나이저 구조가 llama.cpp가 기대하는 형식과 다르다

BitNet i2_s 모델:

GPT‑2 기반 tokenizer를 사용하면서

gguf 변환 시 토크나이저 정보 일부가 분리되지 않고 tokenizer.json 하나에 몰려 있음.

llama.cpp는 GPT‑2 vocab/merges 방식만 부분적으로 지원.

즉, 모델의 말문이 막혀 있었다.

엔진은 정상 구동
커널은 정상 적용
속도도 잘 나옴
하지만 문자를 조합하는 단계에서 토크나이저가 오작동 → 출력 품질 붕괴

이건 커널 문제가 아니라 토큰 분해/합성 실패다.


결국 얻은 결론

BitNet i2_s 모델은 Termux + llama.cpp로는 정상 자연어 생성이 어렵다.

빌드는 성공한다
인퍼런스도 돌아간다
속도도 나온다
메모리도 충분히 버틴다

자연스러운 문장을 만들 수 있는 토크나이저 호환성이 현재 llama.cpp에는 부족하거나...

또는 내 갤 A35환경과 터뮤즈 , 그리고 나의 부족한 경험과 연봉 2000만원에 맞는 개병신같은 능력의 한계로 다음에 또 도전해야겠다는 생각이 들었다.

그러나 이번 실험을 통해 아주 중요한 걸 배웠다.

1. LLM은 “모델 파일”만 받으면 끝이 아니다

양자화 방식(i2_s), 커널 구조, LUT, gemm-config, tokenizer 구조모두 맞아야 한다....

2. ARM/모바일 환경은 사소한 것 하나가 전체 출력을 망칠 수 있다

특히 tokenizer mismatch는 진짜 치명적이다.

3. 하지만, 빌드는 좋은 공부가 된다

ggml 내부 구조
ARM NEON / DOTPROD
Clang ARM 패치
Quantization 구조
정말 많은 걸 배웠다.

728x90
LIST

댓글