[.NET]Using Transformers Models : Extending TransformersSharp

최근 프로젝트에서 텍스트/이미지 임베딩을 .NET 환경에서 직접 활용할 방법을 찾아보다가, TransformersSharp라는 프로젝트를 발견했다. 이 라이브러리는 Hugging Face의 transformers Python 라이브러리를 C#에서 직접 호출할 수 있도록 wrapping한 형태다. 덕분에 별도의 Python 서버를 띄우고 REST API를 붙이지 않아도 .NET 코드 안에서 곧바로 모델을 다룰 수 있다.

TransformersSharp는 기본적으로 텍스트 기반 임베딩과 파이프라인 기능들을 제공한다. 예를 들어 SentenceTransformer를 통해 문장을 벡터화할 수 있지만 이미지를 벡터화 할 수는 없다.

그래서 직접 TransformersSharp를 fork(Link)해서 기능을 다음과 같이 확장했다.

Python Wrapper

sentence_transformers_wrapper.py에 다음 함수를 추가했다.

def load_image(url_or_path):
    if url_or_path.startswith("http://") or url_or_path.startswith("https://"):
        return Image.open(requests.get(url_or_path, stream=True).raw)
    else:
        return Image.open(url_or_path)

def encode_image(model: SentenceTransformer, image_path: str) -> Buffer:
    """
    Encode an image using the SentenceTransformer model.
    """
    image = load_image(image_path)
    return model.encode(image)

C# Wrapper

SentenceTransformer.cs에는 다음 메서드를 추가했다.

public float[] GenerateImage(string image_path)
{
    var result = TransformerEnvironment.SentenceTransformersWrapper
        .EncodeImage(transformerObject, image_path);
    return result.AsFloatReadOnlySpan().ToArray();
}

이제 텍스트와 이미지를 같은 환경에서 임베딩할 수 있다.

using Microsoft.Extensions.AI;
using TransformersSharp;

var transformer_text = SentenceTransformer.FromModel(
    "sentence-transformers/clip-ViT-B-32-multilingual-v1", 
    trustRemoteCode: true);

var transformer_image = SentenceTransformer.FromModel(
    "clip-ViT-B-32", 
    trustRemoteCode: true);

var text_embeddings = transformer_text.GenerateSentence("A dog in the snow");
var image_embeddings = transformer_image.GenerateImage(
    "https://images.unsplash.com/photo-1547494912-c69d3ad40e7f?...");

Console.WriteLine($"Text Vector: {string.Join(", ", text_embeddings.Take(10))}...");
Console.WriteLine($"Image Vector: {string.Join(", ", image_embeddings.Take(10))}...");

Python 서버 없이도 .NET 안에서 바로 모델을 다룰 수 있다는 점이 굉장히 편리한 부분이 있었다. TransformersSharp 자체는 아직 완성도가 높은 수준은 아니지만, 필요한 기능을 확장할 수 있어서 오히려 테스트 해보기 좋았다.

[LLM] Experimenting with AutoTrain vs SFTTrainer

대규모 언어 모델을 Fine tuning할 때 HuggingFace에서는 AutoTrainSFTTrainer 두 가지를 사용 할 수 있다. AutoTrain은 CLI 기반으로 손쉽게 학습을 시작할 수 있는 도구이고, SFTTrainer는 파이썬 코드 기반으로 세밀한 제어가 가능한 도구이다.

AutoTrain은 학습 환경을 자동으로 구성해주기 때문에 초보자나 빠르게 결과를 확인하고 싶은 경우에 적합하다. 몇 가지 옵션만 주면 모델과 데이터 로딩, 학습 파라미터 세팅, 저장 경로까지 모두 알아서 설정해주는 것이 장점이다. 하지만 CLI 환경이라서 옵션의 유연성이 제한되고, 체크포인트 재개 기능이 옵티마이저 상태까지 복원되지 않는 제약이 있는 것이 단점이다.

SFTTrainer는 HuggingFace의 trl 라이브러리에서 제공하는 학습기반 클래스이다.
Trainer를 확장하여 Supervised Fine-Tuning에 맞게 최적화되어 있으며, resume_from_checkpoint 옵션을 통해 학습을 완전히 재개할 수 있는 것이 장점이다.
Data preprocessing, Training loop, Logging, Callback 등을 세밀하게 커스터마이징할 수 있기 때문에 연구 목적이나 장기 학습에서 강력한 도구이다. 다만 AutoTrain에 비해 설정을 직접 챙겨야 하므로 알아야 할 것이 많다.

AutoTrain의 CLI 실행 예시는 다음과 같다.

autotrain llm \
--train \
--model {base model name} \
--project-name {fine tuning model name} \
--data-path data/ \
--text-column text \
--lr 2e-4 \
--batch-size 1 \
--epochs 1 \
--block-size 512 \
--warmup-ratio 0.1 \
--lora-r 16 \
--lora-alpha 32 \
--lora-dropout 0.05 \
--weight-decay 0.01 \
--gradient-accumulation 8 \
--mixed-precision fp16 \
--peft \
--quantization int4 \
--trainer sft

위 명령어는 SFTTrainer 코드로 다음과 같이 변환할 수 있다.

from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments
from trl import SFTTrainer
from peft import LoraConfig, get_peft_model
from datasets import load_dataset

base_model = f"{base_model_name}"
finetuned_model = f"{fine_tuning_model_name}"

dataset = load_dataset("csv", data_files="data/train.csv")
train_dataset = dataset["train"]

tokenizer = AutoTokenizer.from_pretrained(base_model, use_fast=True)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(
    base_model,
    load_in_4bit=True,
    device_map="auto"
)

training_args = TrainingArguments(
    output_dir=finetuned_model,
    num_train_epochs=1,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    weight_decay=0.01,
    warmup_ratio=0.1,
    logging_dir=f"{finetuned_model}/logs",
    logging_steps=50,
    save_strategy="steps",
    save_steps=200,
    save_total_limit=2,
    fp16=True,
    report_to="none",
)

peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=["q_proj", "k_proj", "v_proj"]
)

model = get_peft_model(model, peft_config)

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=train_dataset,
    max_seq_length=512,
    packing=False,
    args=training_args,
)

trainer.train()

trainer.save_model(finetuned_model)
tokenizer.save_pretrained(finetuned_model)

이와 같이 AutoTrain CLI는 빠른 실험에 적합하고, SFTTrainer는 완전한 재개와 세밀한 제어가 필요한 상황에 적합하다. 상황에 따라 두 가지 도구를 병행하여 사용하는 것이 가장 효율적이다.

[LLM] Fixing AutoTrain’s use_flash_attention_2 TypeError

LLM 학습 중 아래처럼 TypeError가 터지면서 학습이 바로 종료되는 경우가 있다.

ERROR | 2025-08-27 13:47:10 | autotrain.trainers.common:wrapper:215 - train has failed due to an exception:
Traceback (most recent call last):
  ...
  File "/usr/local/lib/python3.12/dist-packages/transformers/modeling_utils.py", line 4999, in from_pretrained
    model = cls(config, *model_args, **model_kwargs)
TypeError: Exaone4ForCausalLM.__init__() got an unexpected keyword argument 'use_flash_attention_2'

오류가 발생하는 원인은 autotrain이 동작할때 내부적으로 AutoModelForCausalLM.from_pretrained() 호출 시 항상 use_flash_attention_2=config.use_flash_attention_2를 전달하는데 실제 원본 모델 클래스는 이 인자를 받지 않아 TypeError 발생하는 현상이다.

이 문제를 간단히 해결하는 방법은 autotrain 패키지의 실제 파일 위치(utils.py)에서 해당 인자 전달 한 줄을 주석 처리(또는 삭제)하는 것이다.

사용자의 환경에 따라 위치가 조금 다를 수 있지만 대략 적으로 autotrain utils.py 위치는 다음과 같다.

/usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py

위치를 확인했으면 다음과 같이 먼저 백업을 해두고 해당라인을 주석 처리 한다.

cp /usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py \
   /usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py.bak

sed -i 's/use_flash_attention_2=config.use_flash_attention_2/# use_flash_attention_2=config.use_flash_attention_2/' \
/usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py

grep -n "use_flash_attention_2" /usr/local/lib/python3.12/dist-packages/autotrain/trainers/clm/utils.py

# 848:            - use_flash_attention_2 (bool): Flag to use flash attention 2.
# 949:            # use_flash_attention_2=config.use_flash_attention_2,
# 957:            # use_flash_attention_2=config.use_flash_attention_2,

변경 사항이 제대로 반영되었는지 확인한 뒤 다시 실행하면, 오류가 해결된 것을 확인할 수 있다.

[LLM] Parameter-Efficient Fine-Tuning: LoRA and QLoRA

LLM은 수십억 개의 파라미터를 가지고 있기 때문에, 모든 파라미터를 학습시키는 Full Fine-Tuning 방식은 막대한 GPU 메모리와 시간이 필요하다. 특히 제한된 환경에서는 학습이 더 어려운데 이를 해결하기 위해 등장한 기법이 이를 해결하기 위해 등장한 기법이 PEFT(Parameter-Efficient Fine-Tuning)이다.

PEFT는 전체 파라미터를 학습하지 않고, 일부 파라미터만 학습하도록 하여 메모리 사용량과 연산량을 크게 줄인다. 그 결과, 학습 데이터가 적어도 효과적인 성능 향상을 기대할 수 있다.

LoRA (Low-Rank Adaptation)

LoRA는 모델의 기존 파라미터를 그대로 두고, 작은 보조 파라미터만 추가로 학습하는 방법이다. 기존의 Weight 행렬을 직접 업데이트하지 않고, 작은 두 개의 행렬 A, B를 학습하여 모델을 조정한다.

동작 원리

일반적인 선형 변환:

LoRA 적용 후:

  • W: 기존 모델 파라미터(freeze)
  • A, B: 학습할 작은 행렬
  • r: 행렬의 랭크(rank), 매우 작게 설정 (low-rank)

이 방식을 사용하면 내부 구조 rank가 낮아, 학습해야 하는 파라미터 수가 매우 적어진다.

이해하기 쉽게 예를 들어 보면, 전체 파라미터가 1024 * 1024로 총 1,048,576개인 모델이 있다고 가정해보자. r을 8로 해서 LoRA를 적용하면, A와 B행렬 각각이 8,192개의 파라미터만 가지게 되고 두 개를 합쳐도 16,384개의 파라미터만 학습하면 된다. 이는 전체 파라미터의 약 1.6%에 불과하다. 100만개가 넘는 파라미터 전부를 학습하는 대신 극히 일부만 학습해도 전부를 학습한것과 비슷한 학습 효과를 볼 수 있다.

QLoRA (Quantized LoRA)

QLoRA는 LoRA에 양자화(Quantization) 를 적용한 방식이다. 기존의 float32 파라미터를 int4와 같이 작은 비트수로 압축하여 GPU 메모리를 크게 절약한다.

특징

  • 최대 4배 이상 GPU 메모리 사용량을 감소할 수 있음.
  • 모델 본체는 작아지지만, optimizer state는 여전히 저장해야 하므로 경우에 따라 더 많은 메모리가 필요할 수 있음.
  • optimizer state는 전부 GPU에 올리지 않고, 필요할 때만 GPU로 이동하여 학습 하고 사용하지 않을 때는 CPU 메모리에 올려두도록 해야함.

[LLM] Understanding Encoder and Decoder

대형 언어 모델(LLM)에서 Encoder와 Decoder는 Transformer 구조의 두 핵심 구성 요소이다. 두 모듈은 서로 다른 역할과 구조적 특징을 가지고 있으며, 자연어 처리에서 입력을 이해하고 출력을 생성하는 데 중요한 역할을 한다.

Encoder

인코더는 입력 데이터를 처리하여 의미를 압축하고, 모델이 이해할 수 있는 벡터 표현으로 변환하는 역할을 한다.

구조적으로 Multi-Head Attention, Layer Normalization, Feed Forward Layer가 반복적으로 쌓인 형태다.

Encoder의 각 층 사이에는 Residual Connection이 있어, 입력 값을 변환 결과에 다시 더해 안정적인 학습이 가능하게 한다. Residual Connection은 깊은 네트워크에서 정보 손실을 방지하고, Vanishing Gradient 문제를 완화 한다.

입력 텍스트의 전후 관계를 이해하고, Context을 반영한 Embedding을 생성하고, 이 결과를 DecoderCross Attention단계에서 활용할 수 있게 한다.

Decoder

디코더는 인코더의 출력을 바탕으로 최종적으로 텍스트를 생성하는 역할을 한다.

구조적으로는 Encoder와 다르게 Multi-Head Attention대신 Masked Multi-Head Attention을 사용한다.

Cross Attention Layer를 통해서 Encoder의 결과를 입력으로 받아 사용한다.

토큰을 생성할 때, 사람의 글쓰기처엄 순차적으로 토큰을 하나씩 생성한다. 이미 생성된 토큰을 기반으로 다음 토큰을 예측한다.

학습할 때는 모두 완성된 텍스트를 입력 받는데 이 어텐션을 그대로 활용하는 경우 미래시점에 작성해야하는 텍스트를 미리 확인하게 되는 문제가 생긴다. 이런 문제를 막기위해 Masking을 적용한다.

[LLM] Self-Attention and Multi-head Attention

자연어처리(NLP)에서 트랜스포머 모델은 언어의 문맥과 의미를 깊이 이해할 수 있게 해주는 혁신적인 구조이다. 그 중심에는 어텐션 있으며, 특히 Self-AttentionMulti-Head Attention이 있다.

Attention이란?

Aattention은 말 그대로 어디에 주의를 기울일 것인가를 계산하는 것이다.
쉽게 설명하면, 우리는 평범한 글을 읽을 때는 왼쪽에서 오른쪽으로 자연스럽게 읽어 내려가지만, 어려운 글을 읽을 때는 특정 단어나 문장에 집중하며 앞뒤 내용을 다시 확인하곤 한다. 이러한 집중과 상호참조의 과정을 딥러닝 모델에 반영하기 위한 연산 방식이 바로 어텐션이다.

Query, Key, Value

Attention은 Q(Query), K(Key), V(Value)라는 세 가지 개념을 사용한다.

  • Query – 찾고자 하는 정보, 즉 ‘검색어’ 역할을 한다.
  • Key – 데이터가 어떤 쿼리와 관련이 있는지를 판단할 수 있게 해주는 특징 값. 예를 들어 문서 검색에서는 키가 문서의 제목, 본문, 저자일 수 있다.
  • Value – 관련 있는 데이터를 실제로 가져오는 값. 키를 통해 필터링된 후, 관련도에 따라 가중합되어 출력으로 사용됨.

Self-Attention

입력된 문장의 각 단어가 문장 내의 다른 단어들과 얼마나 관련이 있는지 계산하는 방식입니다. 입력된 문장의 모든 토큰을 Q, K, V로 변환한 뒤, 아래 그림과 같은 순서로 연산이 진행된다.

Self-Attention
  1. Matrix product – Query와 Key의 내적해서 관련도를 측정한다.
  2. Sacling – 안정적인 학습을 위해 임베딩 차원수로 나눈다.
  3. Mask(Option) – Decoder 에서 미래의 토큰을 마스킹 할 것인지 선택하고 마스킹 적용.
  4. Softmax – 각 토큰 간 관련도 점수를 확률로 변환.
  5. Output – Value와 내적해서 최종 출력 생성.

Multi-head Attention

하나의 어텐션만 사용하는 것보다 여러 개의 어텐션을 병렬로 사용하면 더 다양한 관계를 동시에 파악할 수 있다.

단순히 하나의 Attention으로만 문장을 이해하면 놓치는 정보가 생길 수 있다.
그래서 여러 개의 head를 만들어서 서로 다른 방식으로 문장을 분석한 후, 그걸 합쳐서 더 풍부하고 정확한 표현을 얻는 방식이다.

Multi-head Attention
  1. 입력을 여러 개의 Query, Key, Value를 head 크기로 분리하고 선형 변환을 한다.
  2. 각각의 헤드에서 Scaled Dot-Product Attention을 수행.
  3. 각 Attention 결과를 연결한다.
  4. 최종 선형 변환을 거쳐 결과 출력 한다.

셀프 어텐션과 멀티헤드 어텐션은 트랜스포머가 자연어의 의미를 효과적으로 이해하게 해주는 핵심 기술이다. 문장 내에서 중요한 단어들 간의 관계를 파악하고, 다양한 시각에서 이를 종합하는 능력을 부여한다.

[LLM] Model Parallelism and Data Parallelism

대규모 언어 모델(LLM, Large Language Model)의 학습은 수십억 개 이상의 파라미터를 다루기 때문에, 단일 GPU의 메모리나 연산 능력으로는 처리하기 어렵다. 이러한 문제를 해결하기 위해 병렬화(parallelism) 전략이 도입되며, 주로 두 가지 방식인 모델 병렬화(Model Parallelism)데이터 병렬화(Data Parallelism)가 사용된다.

Model Parallelism

모델 병렬화는 하나의 모델을 여러 GPU에 분산시켜 학습하는 방식입니다. 모델이 너무 커서 단일 GPU 메모리에 올릴 수 없을 때 사용된다. 대표적인 두 가지 방식이 있다.

  1. Pipeline Parallelism
    • 딥러닝 모델의 층(Layer) 단위로 모델을 분할하고, 각 분할을 다른 GPU에 할당한다.
    • 입력 데이터가 GPU를 순차적으로 통과하며 forward/backward 연산이 수행된다.
    • 모델 크기를 효과 적으로 분산 할 수 있지만, 각 GPU가 순차적으로 동작하게 되어서 파이프라인 버블 문제가 생길 수 있다.
    • 그림에서 머신을 1, 2와 3, 4로 나누면 파이프라인 병렬화이다.
  2. Tensor Parallelism
    • 하나의 층 내부에서 계산되는 텐서 연산을 여러 GPU에 나누어 수행한다.
    • 행렬 곱 연산에서 행 또는 열을 나눠 병렬 연산 후 결과를 합치는 연산을 하면서 수행된다.
    • 레이어 수준보다 더 미세하게 병렬화할 수 있어서 계산량을 세밀하게 분산이 가능하지만, GPU간 통신이 빈번하게 발생하게 되어 통신 오버헤드가 커질 수 있음.
    • 그림에서 머신을 1, 3과 2, 4로 나누면 텐서 병렬화이다.
Model Parallelism

Data Parallelism

모델 크기가 하나의 GPU에 올라가기 적당하고 데이터가 많을 경우 사용할 수 있는 방식으로 데 모델을 그대로 복제한 후, 서로 다른 GPU에서 다른 데이터를 학습시키는 방식이다.

그림 처럼 각 GPU는 동일한 모델을 가지고 있으며, 각기 다른 미니배치 데이터를 학습한다.

Data Parallelism

forward/backward 연산 후, 파라미터의 그레이디언트(gradient) 를 모든 GPU가 서로 동기화하여 업데이트한다.

구현이 간단하고 대부분의 프레임워크에서 지원이 되지만, 모델 크기가 GPU 메모리에 들어가야 하고, 모든 GPU 간의 gradient 통신 비용이 크다.

LLM 모델 학습은 단순히 모델을 만드는 것 이상으로 효율적인 리소스 분산이 핵심이다. 모델 크기, 하드웨어 환경, 통신 병목 등을 고려하여 파이프라인 병렬화, 텐서 병렬화, 데이터 병렬화를 적절히 조합여 사용해야 한다.

[LLM] Strategies to Reduce GPU Memory Usage During LLM Training

대규모 언어 모델(LLM)을 학습하거나 미세 조정할 때 가장 큰 제약 중 하나는 GPU 메모리 부족이다. 학습 과정에서 어떤 데이터가 메모리에 올라가는지, 이를 어떻게 줄일 수 있는지 정리해 보자.

GPU 메모리에 저장되는 주요 데이터

  1. Model Parameters
    • 모델 가중치(Weight)와 편향(Bias) 등의 학습 가능한 값.
    • 예를 들어 7B (7 Billion) 파라미터 모델의 경우 FP32 기준 약 28GB 메모리가 필요함.
  2. Gradient
    • 각 파라미터에 대해 손실 함수의 기울기 값
    • 역전파(Back propagation) 과정에서 계산되며, 파라미터와 같은 크기의 메모리를 필요로 함.
  3. Optimizer State
    • Optimizer는 모델을 학습시키기 위해 파라미터를 업데이트 하는 알고리즘임. 이때 대부분의 고급 옵티마이저는 각 파라미터마다 추가적인 정보를 저장하는데 이 정보들이 Optimizer State임.
    • 예를 들어 Adam 옵티마이저는 각 파라미터에 대해 1st moment (이전 기울기의 평균), 2nd moment (기울기 제곱의 평균) 추적 값을 저장함.
    • 파라미터 크기의 2~3배에 달하는 추가 메모리 소모가 발생할 수 있음.
  4. Forward Activation
    • 순전파(Forward pass)에서 중간 레이어의 출력값.
    • 역전파 시 Gradient를 계산하기 위해 반드시 필요한 값.
    • 가장 많은 메모리를 차지할 수 있음.

메모리 절약을 위한 전략

  1. Gradient Accumulation
    • GPU 메모리 한계를 넘지 않기 위해 작은 배치(batch) 크기로 여러 번 계산하고 그래디언트를 누적(accumulate).
    • 일정 스텝마다 누적된 그래디언트를 이용해 파라미터를 한 번 업데이트함.
    • Batch size 16에서 OOM이 발생할 경우
      -> Batch size 4 (gradient_accumulation_steps=4)로 설정.
      -> Loss를 4로 나누어 누적하면 batch size 16과 동일한 학습 효과를 얻을 수 있음.
      -> 실제 메모리 사용량은 Batch size 4로 계산 됨.
      -> 작은 Batch size로 큰 Batch size로 학습로 학습한 것과 동일한 효과를 얻을 수 있지만 누적 연산을 추가로 해야 해서 학습 시간은 증가됨.
  1. Checkpointing (Activation Checkpointing)
    • 순전파 시 전체 activation을 저장하지 않고, 마지막 출력값만 저장.
    • 역전파 시 필요한 이전 상태는 다시 계산하여 사용.
    • 순전파 상태 값을 모두 저장하지 않아서 메모리 사용량은 대폭 감소되지만, 역전파 시 순전파를 매번 반복 계산하므로 연산량 증가하여 학습 시간은 증가됨.
  2. Gradient Checkpointing
    • 순전파의 일부 중간 지점(checkpoint)만 저장하고 나머지는 역전파 시 재계산.
    • 전체를 저장하는 일반 방식과, 끝만 저장하는 기본 checkpointing의 중간 전략.
    • 메모리 효율과 연산 효율을 적절히 절충한 방식이라 약간의 오버헤드는 존재하지만 일반적인 Checkpointing 보다는 효율적임.

GPU 메모리는 LLM 학습에서 가장 중요한 자원 중 하나이다. 위에서 소개한 전략들을 적절히 활용하면 제한된 자원 내에서도 효율적으로 모델을 학습시킬 수 있다.

[LLM] Floating Point Formats

AI 모델 학습과 추론에서 부동소수점(Floating Point) 형식은 성능과 정확도에 큰 영향을 준다. 특히, 연산 속도와 메모리 사용량을 줄이기 위해 다양한 정밀도의 부동소수점 형식이 사용되는데, 대표적으로 사용되는 BF16, FP32, FP16에 대해 간단히 정리해 본다.

  1. FP32 (Single Precision Floating Point)
    • Exponent – 8 Bit
    • Mantissa – 23 Bit
    • Sign – 1 Bit
    • Total 32 Bit
    • 높은 정밀도와 넓은 표현 범위를 모두 갖춘 표준 부동소수점 형식. 대부분의 AI 학습, 고정밀 연산이 필요한 경우 사용한다.
  2. FP16 (Half Precision Floating Point)
    • Exponent – 5 Bit
    • Mantissa – 10 Bit
    • Sign – 1 Bit
    • Total 16 Bit
    • 정밀도와 표현 범위 모두 FP32보다 줄어들지만, 메모리 절약과 연산 속도는 증가 시킬 수 있다.
  3. BF16 (bfloat16, Brain Floating Point Format)
    • Exponent – 8 Bit
    • Mantissa – 7 Bit
    • Sign – 1 Bit
    • Total 16 Bit
    • FP16과 마찬가지로 16 Bit를 사용하여 표현 범위가 줄어들었지만, FP32와 동일한 지수부를 가져서 표현 가능한 수의 범위가 넓다.