트랜스포머 모델#
이 장에서는 HuggingFace transformers 모델을 PyTorch 경로로 컴파일하는 LLM 흐름을 설명합니다. 이때 backend="torch"를 사용합니다.
LLM도 다른 모델과 동일하게 qb Compiler의 기본 파이프라인을 따릅니다.
HuggingFace transformers 모델
-> MBLT
-> MXQ
LLM에서 중요한 차이는 컴파일된 모델이 런타임 상태를 갖는 transformer decoder 본체로 취급된다는 점입니다. 런타임은 KV 캐시를 유지하고, NPU는 반복되는 transformer block을 실행합니다.
Vision Transformer(ViT) 모델은 비전 모델 — Vision Transformer를 참고합니다.
LLM 컴파일 개요#
일반적인 HuggingFace LLM 컴파일은 다음 네 부분으로 구성됩니다.
HuggingFace
transformers형식의 모델을 준비합니다.컴파일된 decoder 본체의 텐서 입력과 일치하는 캘리브레이션 데이터를 준비합니다.
backend="torch"를 지정하고hf_config로 HuggingFace 로더 정보를 전달합니다.LlmConfig를 직접 설정하거나llm/llm_fast프리셋으로 LLM 설정을 활성화합니다.
실제 LLM 컴파일에는 GPU 사용을 권장합니다. Transformer decoder 모델은 크기가 크며, 캘리브레이션과 양자화 과정에서 많은 메모리를 사용할 수 있습니다.
지원 가능한 모델 구조: 트랜스포머 + KV 캐시#
qb Compiler는 KV cache를 사용하는 causal decoder 계열 transformer 모델을 대상으로 합니다. 기대하는 구조는 다음과 같습니다.
토큰 임베딩과 토크나이저는 컴파일된 NPU 본문 외부에서 처리합니다.
컴파일된 본문은 임베딩된 토큰 텐서 또는 프런트엔드가 선택한 모델 입력 형식을 받습니다.
각 transformer block은 어텐션, RoPE 또는 다른 위치 인코딩 경로, MLP/FFN, residual connection, 정규화로 구성됩니다.
어텐션은 과거 key/value 상태를 사용하여 매 생성 단계마다 전체 prefix를 다시 계산하지 않습니다.
출력 head는 내부 transformer block과 별도로 양자화할 수 있습니다.
모델은 보통 HuggingFace transformers에서 AutoModelForCausalLM으로 로드할 수 있어야 합니다. LLaMA, Qwen, Gemma와 같은 decoder-only 계열이 주 대상입니다. 비표준 어텐션, sliding-window 동작, MoE 라우팅, 비표준 캐시 레이아웃을 사용하는 모델은 모델별 처리가 필요할 수 있으므로 최종 MXQ를 사용하기 전에 반드시 검증해야 합니다.
HuggingFace LLM 컴파일#
HuggingFace 모델은 PyTorch 프런트엔드를 통해 컴파일합니다.
hf_config = {
"library": "transformers",
"loader": "AutoModelForCausalLM",
"tokenizer": "AutoTokenizer",
"model_args": (),
"model_kwargs": {
"trust_remote_code": True,
},
"tokenizer_args": (),
"tokenizer_kwargs": {},
}
model에는 HuggingFace model id 또는 로컬 모델 디렉터리를 전달하고, backend="torch"와 hf_config를 mxq_compile에 전달합니다. 접근 권한이 필요한 HuggingFace 모델은 컴파일 전에 huggingface-cli login으로 인증해야 합니다.
weight_dtype는 원본 모델 가중치의 데이터 타입과 맞춰야 합니다. 예를 들어 bfloat16 또는 float16을 사용합니다. 이 값은 보통 모델의 config.json 또는 model card에서 확인할 수 있습니다.
LlmConfig 설정#
LlmConfig는 LLM 전용 하위 설정입니다. LLM 연산 그래프 처리를 활성화하고 시퀀스, 캐시, 캘리브레이션, 런타임 메타데이터를 기록합니다.
from qbcompiler.configs import LlmConfig
llm_config = LlmConfig(
apply=True,
attributes=LlmConfig.Attributes(
maxSequenceLength=4096,
maxCacheLength=4096,
maxDataLength=4096,
maxCoreDataLength=128,
calibration=LlmConfig.Attributes.Calibration(
useFullSeqLength=True,
),
runtime=LlmConfig.Attributes.Runtime(
batchSize=1,
dynamicRope=False,
dynamicMask=False,
),
),
)
동일한 필드는 JSON/YAML 컴파일 설정 파일에서 스키마 이름으로 작성할 수 있습니다.
{
"llm": {
"apply": true,
"attributes": {
"maxSequenceLength": 4096,
"maxCacheLength": 4096,
"calibration": {
"useFullSeqLength": true
},
"runtime": {
"batchSize": 1,
"dynamicRope": false,
"dynamicMask": false
}
}
}
}
일부 호환 예제에서는 get_llm_config(...)와 같은 helper 함수를 사용할 수 있습니다. 재사용 가능한 설정 파일을 작성할 때는 CompileConfig와 직접 대응되는 위 스키마 기반 형식을 권장합니다.
시퀀스 길이#
maxSequenceLength는 컴파일된 LLM 연산 그래프가 표현하는 최대 토큰 시퀀스 길이입니다. 이 값은 어텐션 형상, RoPE/mask 생성, 캘리브레이션 형상, 메모리 사용량, 컴파일 시간, 결과 MXQ가 지원하는 최대 프롬프트 또는 디코딩 창에 영향을 줍니다.
배포 요구사항을 만족하는 가장 작은 값을 선택하는 것이 좋습니다. 4K에서 8K 이상으로 늘리면 컴파일 비용과 런타임 메모리 요구량이 크게 증가할 수 있습니다.
현재 LLM 흐름에서는 컴파일 시점의 형상 정책과 캘리브레이션 샘플의 형상 정책을 맞추는 것이 중요합니다. useFullSeqLength=True를 사용하면 전체 길이 시퀀스로 캘리브레이션을 수행하여 최악의 경우에 해당하는 활성화 범위를 더 잘 반영합니다.
캐시 길이#
maxCacheLength는 런타임 생성에서 사용할 최대 KV 캐시 길이입니다. 일반적으로 maxSequenceLength와 같은 값으로 설정합니다.
maxCacheLength가 실제 프롬프트와 생성 컨텍스트보다 작으면 생성 중 캐시 한도에 도달합니다. 반대로 필요 이상으로 크면 컴파일된 패키지와 런타임 메모리 예산이 불필요하게 커질 수 있습니다.
배치 크기#
LLM 런타임 배치 크기는 llm.attributes.runtime.batchSize로 설정합니다. 대부분의 edge interactive generation에서는 batchSize=1을 사용합니다. batchSize가 1보다 크면 qb Compiler는 컴파일 시점의 inferenceScheme을 자동으로 single로 고정합니다.
이는 multi 모드를 사용할 수 있는 비전 모델 배치 추론과 다릅니다. LLM 및 KV 캐시를 사용하는 transformer 모델에서는 NPU가 배치 transformer 연산을 1개 코어 단위로 나누어 분산 수행합니다. Transformer 런타임 상태와 KV 캐시를 시퀀스별로 관리해야 하므로, 배치 LLM 컴파일은 single 모드에서만 지원됩니다. LlmConfig 배치 컴파일에서 multi, global4, global8은 사용할 수 없습니다. 일반적인 코어 모드의 의미는 ARIES Core 모드를 참고합니다.
배치 LLM에서는 qb Runtime의 infer API 형태도 달라집니다. 비전 배치처럼 독립적인 배치 텐서만 전달하는 것이 아니라, 이어 붙인 LLM 입력과 함께 BatchParam 같은 배치 메타데이터를 전달합니다. 현재 API 형태는 qb Runtime 릴리즈 노트의 Batch LLM 항목을 참고합니다.
배치 크기를 1보다 크게 사용할 경우에는 다음 사항을 확인합니다.
runtime.batchSize런타임 입력 패킹 및
BatchParam메타데이터target device 메모리 예산. 설정한 배치 크기만큼 KV 캐시 메모리를 미리 확보합니다.
KV 캐시#
KV 캐시는 이전 토큰에서 생성된 key/value 텐서를 저장합니다. Autoregressive generation 중 런타임은 새 토큰의 K/V 텐서를 캐시에 추가하고, 어텐션은 캐시된 prefix와 현재 토큰을 함께 참조합니다.
이 방식은 매 생성 토큰마다 전체 프롬프트를 다시 계산하지 않게 해 줍니다. 따라서 컴파일된 모델과 런타임 메타데이터는 캐시 길이, 배치 크기, head 레이아웃, 위치 인코딩 동작에 대해 일관되어야 합니다.
모델이 sliding-window attention 또는 다른 bounded-cache 메커니즘을 사용한다면, 최종 MXQ를 만들기 전에 HuggingFace 모델 경로와 생성된 MBLT가 해당 캐시 정책을 올바르게 표현하는지 확인해야 합니다.
llm 및 llm_fast 프리셋#
내장 llm 프리셋은 LLM 설정을 활성화하고 기본 sequence/cache length를 4096으로 설정합니다.
설정 |
값 |
|---|---|
|
|
|
|
|
|
|
|
mxq_compile(
model=model_id,
backend="torch",
config_preset="llm",
hf_config=hf_config,
calib_data_path=calib_path,
save_path="model.mxq",
)
llm_fast 프리셋은 llm을 확장하며, 컴파일 시간을 줄이기 위해 일부 정확도 중심 최적화 비용을 낮춥니다. HessianQuant를 비활성화하고 transformer block에 사용되는 여러 동등 변환 패스를 활성화합니다. 여기에는 QK, UD, VO, SpinR1, SpinR2, OptimizeFFN이 포함됩니다. 또한 전체 시퀀스 캘리브레이션을 활성화합니다.
보수적인 시작점으로는 llm을 사용합니다. 컴파일 시간이 더 중요하다면 llm_fast를 사용한 뒤 대표 prompt에서 모델 품질을 검증합니다.
multimodal 프리셋#
multimodal 프리셋은 이미지 텐서와 토큰 텐서처럼 둘 이상의 입력 계열을 받는 모델을 위한 설정 시작점입니다. calibration.method=3과 llm.apply=true를 설정합니다.
이 프리셋은 이미지/텍스트 전처리, 입력 크기, 시퀀스 길이, 캐시 길이를 정의하지 않습니다. 내보낸 비전 및 언어 하위 모델에 맞게 해당 값을 지정해야 합니다. 캘리브레이션에서는 컴파일되는 모든 입력에 대한 데이터를 준비합니다. 이미지와 텍스트를 함께 쓰는 모델에서는 비전 경로용 이미지 텐서와 언어 경로용 토큰, 임베딩, 마스크, 캐시 텐서가 함께 필요할 수 있습니다.
연산 그래프가 보통 여러 입력 명세를 결합하므로 일반 CLI 예제 대신 모델별 Python 스크립트를 사용합니다. multimodal 프리셋은 시작 설정을 제공하지만, 모델별 내보내기, 전처리, 캘리브레이션 코드를 대체하지 않습니다.
LLM 양자화 설정#
양자화 하위 설정의 상세 설명과 권장 설정 레시피는 모델 양자화 — 양자화 설정을 참고합니다. 프리셋, 설정 파일, dump-config 사용법은 컴파일 설정을 참고합니다.
LLM 양자화는 트랜스포머 프로젝션별로 다른 정밀도 선택이 필요한 경우가 많습니다. BitConfig의 transformer.weight 필드에서 query, key, value, output, ffn, head 가중치를 독립적으로 제어할 수 있습니다.
from qbcompiler.configs import BitConfig, CalibrationConfig, SearchWeightScaleConfig
calibration_config = CalibrationConfig(mode=0, output=0)
bit_config = BitConfig(
transformer=BitConfig.Transformer(
weight=BitConfig.Transformer.Weight(
query=4,
key=4,
value=8,
output=4,
ffn=4,
head=8,
),
),
)
search_weight_scale_config = SearchWeightScaleConfig(
apply=True,
transformer=SearchWeightScaleConfig.Transformer(
query=True,
key=True,
value=True,
out=True,
ffn=True,
),
)
실무에서는 다음 순서로 조정하는 것이 좋습니다.
먼저 8-bit transformer 가중치로 품질 기준선을 확인합니다.
크기와 대역폭을 줄이기 위해 query/key/output/FFN을 4-bit로 낮춥니다.
perplexity 또는 생성 품질이 떨어지면 value와 head는 8-bit로 유지합니다.
공격적인 low-bit 설정에서는 가중치 스케일 탐색을 활성화합니다.
컴파일 성공 여부만 보지 말고 실제 작업 프롬프트로 검증합니다.
동적 RoPE와 동적 Mask#
dynamicRope와 dynamicMask는 LlmConfig.Attributes.Runtime 안의 런타임 옵션입니다.
dynamicRope=True는 RoPE 위치 데이터를 컴파일 시점에 완전히 고정하지 않고 런타임에서 동적으로 처리하게 합니다. 고정된 컴파일 시점 위치 경로로 표현하기 어려운 가변 디코딩 위치가 필요한 배포에서 사용합니다.
dynamicMask=True는 어텐션 마스크를 런타임 입력으로 만듭니다. 프롬프트 길이, 패딩 패턴, causal/padding 마스크 조합이 런타임마다 달라질 때 사용합니다.
동적 설정은 하나의 컴파일된 패키지가 더 많은 런타임 상황을 처리할 수 있게 해 주지만, 추가 런타임 입력이 필요하고 최적화 기회가 줄어들 수 있습니다. 배포에서 이 유연성이 필요하지 않다면 비활성화 상태를 유지하는 것이 좋습니다.
splitBlocks와 splitParts#
큰 LLM은 여러 MXQ 파일로 나눌 수 있습니다. 이는 매우 큰 transformer stack, 패키징 제약, 런타임 스케줄링에 도움이 될 수 있습니다.
splitBlocks는 transformer block index 기준의 명시적 분할 지점을 지정합니다.
{
"splitBlocks": [8, 16, 24]
}
splitParts는 transformer block을 요청한 부분 수로 균등하게 나눕니다.
{
"splitParts": 4
}
하나의 컴파일 작업에서는 한 가지 분할 전략만 사용합니다. 블록이 균일하고 단순한 균등 분할이면 splitParts를 우선 사용합니다. 모델 블록이 균일하지 않거나 target device 메모리 측정 결과 특정 경계가 더 적합하다면 splitBlocks를 사용합니다.
엔드투엔드 예제#
다음 예제는 명시적인 LlmConfig를 사용하여 HuggingFace LLaMA 계열 모델을 backend="torch" 경로로 컴파일합니다.
from qbcompiler import mxq_compile
from qbcompiler.configs import (
BitConfig,
CalibrationConfig,
LlmConfig,
SearchWeightScaleConfig,
)
model_id = "meta-llama/Llama-3.2-1B-Instruct"
calib_path = "/workspace/Llama-3.2-1B-Instruct-Wikipedia-en"
save_path = "./Llama-3.2-1B-Instruct.mxq"
hf_config = {
"library": "transformers",
"loader": "AutoModelForCausalLM",
"tokenizer": "AutoTokenizer",
"model_args": (),
"model_kwargs": {
"trust_remote_code": True,
},
"tokenizer_args": (),
"tokenizer_kwargs": {},
}
llm_config = LlmConfig(
apply=True,
attributes=LlmConfig.Attributes(
maxSequenceLength=4096,
maxCacheLength=4096,
calibration=LlmConfig.Attributes.Calibration(
useFullSeqLength=True,
),
runtime=LlmConfig.Attributes.Runtime(
batchSize=1,
dynamicRope=False,
dynamicMask=False,
),
),
)
calibration_config = CalibrationConfig(mode=0, output=0)
bit_config = BitConfig(
transformer=BitConfig.Transformer(
weight=BitConfig.Transformer.Weight(
query=8,
key=8,
value=8,
output=8,
ffn=8,
head=8,
),
),
)
search_weight_scale_config = SearchWeightScaleConfig(
apply=False,
transformer=SearchWeightScaleConfig.Transformer(
query=True,
key=True,
value=True,
out=True,
ffn=True,
),
)
mxq_compile(
model=model_id,
backend="torch",
target_device="aries-rb",
hf_config=hf_config,
calib_data_path=calib_path,
save_path=save_path,
device="gpu",
weight_dtype="bfloat16",
use_gpu_only_for_calibration=True,
calibration_config=calibration_config,
bit_config=bit_config,
search_weight_scale_config=search_weight_scale_config,
llm_config=llm_config,
)
더 큰 모델에서는 CompileConfig 또는 JSON/YAML 설정 파일에 splitParts나 splitBlocks를 추가합니다. 이후 애플리케이션에서 사용할 런타임 흐름으로 생성된 각 MXQ를 검증합니다.
LLM 캘리브레이션 데이터#
많은 LLM에서는 임베딩을 NPU 연산 그래프 밖에서 처리할 수 있으므로 토큰 ID가 아니라 임베딩된 텐서를 캘리브레이션에 사용합니다. 일반적인 작업 흐름은 다음과 같습니다.
배포 프롬프트와 유사한 텍스트를 준비합니다.
모델 토크나이저로 텍스트를 토큰화합니다.
토큰화된 입력을 모델 임베딩 레이어에 통과시킵니다.
결과 텐서를 캘리브레이션 데이터셋으로 저장합니다.
캘리브레이션 텐서의 형상은 컴파일된 decoder 본체 입력과 일치해야 합니다. 모델 프런트엔드가 특정 입력 형식을 선택하면 캘리브레이션 텐서도 같은 명세를 따라야 합니다.
컴파일이 실패하면 먼저 시퀀스와 캐시 길이를 줄여 모델 경로가 유효한지 확인한 뒤 메모리 한도 안에서 다시 늘립니다.
멀티모달(VLM) 컴파일#
HuggingFace 멀티모달(VLM) 모델은 일반 CLI 예제가 아니라 mblt_compile과 mxq_compile을 사용하는 전용 Python 스크립트로 컴파일합니다. 각 모델의 전용 튜토리얼 코드 또는 참고 구현에서 시작한 뒤, 내보낸 모델 명세에 맞게 스크립트와 설정을 조정합니다. 참고 구현은 https://github.com/mobilint 를 참고합니다.
multimodal 프리셋이 이러한 스크립트의 설정 시작점입니다.
실무 가이드:
가능하면 비전 인코더만 먼저 컴파일하고 검증합니다.
가능하면 LLM path만 먼저 컴파일하고 검증합니다.
각 하위 경로의 형상과 전처리 명세가 명확해진 뒤 연산 그래프를 결합합니다.
토크나이저 로직, 이미지 디코딩, 프롬프트 형식 지정, 기타 Python 수준 전처리는 컴파일된 연산 그래프 밖으로 이동합니다.
컴파일되는 모든 입력에 대해 캘리브레이션 데이터를 준비합니다. 샘플 이름, 형상, 순서는 원본 모델 입력과 일치해야 합니다.
CPU 오프로딩은 배포 런타임이 지원하는 제한된 연산 범위에만 사용합니다.