본 테스트의 목적은 Exynos 1380 프로세서가 지원하는 ARMv8.2-A 아키텍처의 점곱 하드웨어 가속 명령어와 C++ 컴파일러 간의 기계어 번역 충돌 여부를 격리하여 검증하는 것이다.
BitNet(1.58-bit) 모델 구동 시 발생하는 심각한 텐서 오염(Word Salad, 외계어 출력 현상)의 원인을 두고,
타 생태계(Apple Silicon, x86)에 편향되어 작성된 C++ 커널이 안드로이드(Exynos) 환경에서 실행될 때 마이크로아키텍처 수준의 처리 방식 차이로 인해 오류를 일으킨다는 가설을 세웠습니다.
음...
쉽게 이해하자면 텐서 연산(행렬 곱셈)은 결국 수천만 개의 숫자들을 더하고 곱하는 막노동입니다.
스칼라 연산 (Scalar Fallback - 가속기 All OFF):
- 비유: 자전거에 벽돌(숫자)을 한 개씩 싣고 나르는 짓입니다.
- 특징: 미친 듯이 느리지만, 계산이 꼬일 일은 절대 없습니다.
NEON (ARM의 범용 SIMD 가속기):
- 비유: 자전거 대신 8톤 트럭을 뽑은 겁니다. 트럭 짐칸에 벽돌을 4개~16개씩 한 번에 싣고 나릅니다.
- 특징: 엑시노스를 포함한 스마트폰 CPU들이 연산을 고속으로 처리하게 해주는 핵심 엔진입니다.
DotProd (NEON 안의 '점곱' 특수 기능):
- 비유: 트럭(NEON) 안에 지게차를 하나 더 산 겁니다. 행렬 계산의 핵심인 곱하고 더하기를 지게차가 한 방에 처리해 줍니다.
- 우리의 의심: 최신 엑시노스는 이 지게차를 지원한다고 자랑하지만, 삼성이 만든 이 지게차가 결함품이라 숫자를 나르다가 자꾸 땅에 떨어뜨려서 텐서를 박살낸다(워드 샐러드)고 강력하게 의심하는 중입니다.
즉 트럭도 조절해보고 결함이 의심되는 지게차도 전원을 끄고 직접 손으로 상하차를 해보자! 는 완벽한 전략입니다.
검증 환경 및 변인 통제
https://uno-kim.tistory.com/457 과 같은 환경으로 쓰레드1개만 사용해서 진행합니다.
- OS 레벨 통제: taskset -c 0 명령어를 통해 단일 코어(0번)에만 프로세스를 할당하여 Big.LITTLE 구조의 스케줄러 캐시 꼬임 원천 차단.
- 소프트웨어 통제: 추론 시 -t 1 옵션을 주어 단일 스레드로만 연산 수행.
시계열 디버깅 과정 및 결과
Phase 1: DotProd 비활성화

- 과정: CMakeLists.txt 파일에 set(GGML_ARM_DOTPROD OFF CACHE BOOL "" FORCE) 옵션을 주어 DotProd만 강제로 비활성화한 뒤 재빌드.
- 결과: 실패. 여전히 외계어가 출력됨. (로그 확인 결과 NEON = 1 상태 유지 중)

Phase 2: NEON 완전 차단
set(GGML_NEON OFF CACHE BOOL "Force disable GGML NEON" FORCE)
set(LLAMA_NEON OFF CACHE BOOL "Force disable LLAMA NEON" FORCE)
set(GGML_ARM_DOTPROD OFF CACHE BOOL "Force disable GGML DotProd" FORCE)
set(LLAMA_ARM_DOTPROD OFF CACHE BOOL "Force disable LLAMA DotProd" FORCE)
set(GGML_LLAMAFILE OFF CACHE BOOL "Force disable GGML llamafile" FORCE)
set(LLAMA_LLAMAFILE OFF CACHE BOOL "Force disable LLAMA llamafile" FORCE)
set(GGML_NATIVE OFF CACHE BOOL "Force disable GGML Native" FORCE)
set(LLAMA_NATIVE OFF CACHE BOOL "Force disable LLAMA Native" FORCE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a+nosimd -U__ARM_NEON")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+nosimd -U__ARM_NEON")
- 과정: DotProd가 문제가 아니라면 NEON 자체가 문제일 수 있다고 판단. CMake 설정에 GGML_NEON OFF, LLAMA_NEON OFF, LLAMAFILE OFF 등 가동 가능한 모든 킬 스위치(Kill Switch)를 동원.
- 결과: 실패. 코드를 강제해도 출력 로그에는 계속해서 NEON = 1이 찍히며 좀비처럼 가속기가 살아남ㅠㅠㅠ
Phase 3: setup_env.py분석
- 원인 분석: 왜 우리의 CMake 명령이 무시되었는가? 범인은 우리가 빌드를 위해 습관적으로 실행하던 python setup_env.py 스크립트에 있었다. 이 파이썬 코드는 내부적으로 폰이 ARM64 구조임을 감지하면, 사용자가 지정한 C++ 플래그를 모두 무시하고 자신들이 하드코딩해 둔 기본 옵션으로 CMake를 덮어쓰며 실행하고 있었다.
자세히는...
디버깅 과정에서 우리의 CMakeLists.txt 수정과 컴파일러 플래그 주입이 번번이 무시되었던 이유는, 빌드 자동화를 위해 제공된 setup_env.py 스크립트 내부의 하드코딩된(Hardcoded) 아키텍처 분기 로직 이 있었습니다.
실제 BitNet.cpp의 setup_env.py 내 compile_code() 함수를 분석해 보면 다음과 같은 치명적인 구조적 한계(오지랖)를 발견할 수 있습니다.
사용자의 옵션을 원천 차단하는 COMPILER_EXTRA_ARGS 환경이 있었고,
COMPILER_EXTRA_ARGS = {
"arm64": ["-DBITNET_ARM_TL1=OFF"],
"x86_64": ["-DBITNET_X86_TL2=OFF"]
}
여기서 폰 아키텍처가 arm64로 인식되면 무조건 -DBITNET_ARM_TL1=OFF 옵션 단 하나만 들고 간다. 사용자가 터미널 환경변수로 넘겨주거나 외부에서 추가하고 싶은 그 어떤 옵션(예: GGML_NEON=OFF 등)도 받아들일 수 있는 구조(Hook) 자체가 아예 없다.
그리고 compile() 함수에서는
def compile():
# ... (생략) ...
run_command(["cmake", "-B", "build", *COMPILER_EXTRA_ARGS[arch], *OS_EXTRA_ARGS.get(platform.system(), []), "-DCMAKE_C_COMPILER=clang", "-DCMAKE_CXX_COMPILER=clang++"], log_step="generate_build_files")
사용자가 CMakeLists.txt를 아무리 수정하고 변형해도, 이 스크립트를 실행하는 순간 CMake는 오직 저 배열 안에 하드코딩된 옵션만으로 새롭게 build 폴더를 덮어씌워 버린다. 우리가 C++ 플래그로 멱살을 잡으려던 시도가 이 단계에서 다 초기화되고 컴파일러의 기본값(NEON=ON)으로 돌아가 버린 것이다.
- 대응: 파이썬 스크립트를 배제하고 수동으로 정석적인 CLI(명령행) 빌드 주입.
cmake -B build -DGGML_NEON=OFF -DLLAMA_NEON=OFF -DGGML_NATIVE=OFF -DLLAMA_NATIVE=OFF -DGGML_LLAMAFILE=OFF -DLLAMA_LLAMAFILE=OFF -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_FLAGS="-march=armv8-a+nosimd -U__ARM_NEON" -DCMAKE_CXX_FLAGS="-march=armv8-a+nosimd -U__ARM_NEON"
Phase 4: NEON 강제 종료와 오류발견
파이썬 스크립트(setup_env.py)의 간섭을 완벽히 차단하고, 수동 빌드(CLI)를 통해 기계어 레벨에서 NEON을 모두 껐다.
결과는 예상치 못한 '컴파일 에러(Build Failed)'였다.

warning: unused parameter 'vx'
error: use of undeclared identifier 'PARALLEL_SIZE'
왜 터졌는가?
에러의 원인은 어처구니가 없었다. 마이크로소프트의 BitNet ARM 커널은 NEON 가속기가 없을 때 돌아갈 '순수 C++ 기본 계산 코드(Scalar Fallback)'를 아예 만들어두지 않았다.
NEON을 끄면 일반 C++로 느리게라도 계산해야 하는데, 그냥 함수 내부가 텅 비어버려서 컴파일러가 비명을 지른 것이다.
우선 NEON을 끄는 것까지 성공을 한것같다.
워드샐러드는 NEON=1 경로에서만 발생한다.
NEON=0이면 BitNet 연산이 존재하지 않는다.
따라서 원인은 “NEON 자체”가 아니라
Exynos에서의 NEON 이 동작하다가 발생하는 것으로 유추할 수 있게되었다.
그럼 스칼라 풀백을 구현하여(NEON이 안돌때 CPU 연산으로만 돌리게하는 로직) 엄청 느려터지더라도
진짜 NEON 때문이었는지 검증하는 방법이 있겠다...
우선은 하나 알아냈다...
NEON을 직접 끄면 빌드하다가 터진다.
'AI Project > Edge AI Agent - LLM(연구,분석,검증)' 카테고리의 다른 글
| [BitNet.Cpp] llama.cpp의 이스터에그 발견과 NEON=1로그의 진실! (0) | 2026.04.25 |
|---|---|
| [분석/검증-3] BitNet.cpp ARM 커널 재작성 : 스칼라 폴백구현을 통한 최종 검증 (0) | 2026.04.25 |
| [분석/검증-1] BitNet.cpp 텐서 연산 붕괴(텐서 오류) 현상 분석 : Big.LITTLE 구조의 캐시 불일치 (0) | 2026.04.23 |
| [분석] BitNet.cpp 1.58비트 모델의 모바일 환경 추론 실패 원인 분석 (0) | 2026.04.21 |
| #5. [Bitnet.cpp]모바일 환경에서 컨테이너띄우고 빌드 (1) | 2026.04.21 |
댓글