[카테고리:] 미분류

  • llama_ggml


    📌 전체 구조 요약

    이 코드는 ggml로 간단한 행렬 곱셈(matmul)을 수행하는 예제입니다:

    • CPU 또는 GPU(Metal / CUDA) backend에서 연산 수행
    • ggml 계산 그래프 구성 → 실행 → 결과 확인
    • ggml의 “그래프 기반 연산 모델” 구조를 보여주는 좋은 예제

    🗺️ 구조 단계별 분석

    1️⃣ 준비

    #include "ggml.h"
    #include "ggml-alloc.h"
    #include "ggml-backend.h"
    
    • ggml core, 메모리 할당기, backend API 사용

    2️⃣ 모델 정의

    struct simple_model {
        struct ggml_tensor* a{};
        struct ggml_tensor* b{};
        ggml_backend_t backend = nullptr;
        ggml_backend_buffer_t buffer{};
        struct ggml_context* ctx{};
    };
    
    • simple_model 구조체에 2개의 텐서 a, b 선언
    • backend 설정 (CPU / CUDA / Metal)
    • backend buffer 사용 (a, b 데이터를 저장)
    • context는 ggml에서 텐서 메타 정보, 메모리 관리를 담당

    3️⃣ 데이터 준비

    float matrix_A[4 * 2] = { ... };
    float matrix_B[3 * 2] = { ... };
    
    • matrix_A: (4×2) matrix
    • matrix_B: (3×2) matrix → 내부에서 transpose되어 곱해짐

    4️⃣ Backend 초기화

    #ifdef GGML_USE_CUDA
        model.backend = ggml_backend_cuda_init(0);
    #endif
    #ifdef GGML_USE_METAL
        model.backend = ggml_backend_metal_init();
    #endif
    if (!model.backend) {
        model.backend = ggml_backend_cpu_init();
    }
    
    • CUDA → Metal → CPU 순으로 backend 초기화
    • llama.cpp도 비슷한 구조 (다양한 backend 지원)

    5️⃣ ggml context 생성 + 텐서 생성

    ggml_init_params params{...};
    model.ctx = ggml_init(params);
    model.a = ggml_new_tensor_2d(model.ctx, GGML_TYPE_F32, 2, 4);
    model.b = ggml_new_tensor_2d(model.ctx, GGML_TYPE_F32, 2, 3);
    
    • ggml context 생성 → 텐서 생성
    • ggml_new_tensor_2d 로 2D tensor 생성 (shape = cols, rows 주의)

    6️⃣ Backend buffer 사용

    model.buffer = ggml_backend_alloc_ctx_tensors(model.ctx, model.backend);
    ggml_backend_tensor_set(model.a, a, ...);
    ggml_backend_tensor_set(model.b, b, ...);
    
    • backend buffer에 텐서 데이터를 직접 업로드
    • CPU → GPU 메모리 복사 직접 컨트롤 → llama.cpp와 동일한 설계 철학

    7️⃣ 계산 그래프 구성

    ggml_context* ctx0 = ggml_init(params0);
    ggml_cgraph* gf = ggml_new_graph(ctx0);
    ggml_tensor* result = ggml_mul_mat(ctx0, model.a, model.b);
    ggml_build_forward_expand(gf, result);
    
    • 별도의 temporay context 생성 → 계산 그래프 구성
    • ggml_mul_mat : 행렬 곱 연산 노드 생성
    • 그래프 빌드 단계 → llama.cpp에서도 필수

    8️⃣ 메모리 예약 + 그래프 실행

    ggml_gallocr_alloc_graph(allocr, gf);
    ggml_backend_graph_compute(model.backend, gf);
    
    • 메모리 할당기(allocator)로 그래프 최적화 실행
    • backend에서 그래프 실행 → CPU든 GPU든 동일한 API 사용 (llama.cpp에서도 동일)

    9️⃣ 결과 가져오기 + 출력

    ggml_backend_tensor_get(result, out_data.data(), ...);
    
    • backend 메모리 → CPU 메모리로 결과 복사
    • 결과 행렬 출력

    10️⃣ 마무리 정리

    ggml_gallocr_free(allocr);
    ggml_free(model.ctx);
    ggml_backend_buffer_free(model.buffer);
    ggml_backend_free(model.backend);
    
    • 메모리 및 backend clean-up

    🚩 핵심 포인트

    ggml는 그래프 기반 연산을 사용
    메모리 컨트롤과 backend 제어를 직접적으로 노출
    llama.cpp와 구조적으로 동일:

    • llama.cpp도 ggml_graph_compute 사용
    • backend abstraction 사용
    • memory 관리 explicit

    on-device 최적화에 매우 유리한 구조


    🔍 총평

    • 매우 좋은 ggml 샘플
    • llama.cpp의 내부 구조 이해에 딱 적합
    • “C/C++ 기반 + 그래프 기반 + backend abstraction” → llama.cpp가 임베디드에 강한 이유 그대로 보여줌

    🚀 추가로 볼만한 확장 방향

    1️⃣ ggml_compute_forward vs backend_graph_compute 차이 → CPU 전용 vs multi-backend 지원 차이
    2️⃣ ggml-alloc의 역할 → runtime 메모리 최적화
    3️⃣ gguf 파일 로딩 → llama.cpp에서는 gguf → ggml_tensor 구성 → ggml_graph 실행 흐름



    🗺️ ggml-simple.cpp vs llama.cpp 구조 비교

    🏗️ 1️⃣ 공통 기본 구조

    단계ggml-simple.cppllama.cpp
    Backend 초기화CUDA/Metal/CPU backend 선택동일 (llama.cpp도 backend layer 존재)
    ggml_context 생성ggml_init(params)ggml_init(params) (llama.cpp 내부 llama_init_from_file)
    Tensor 구성ggml_new_tensor_2d 직접 호출모델 로딩시 gguf → tensor 자동 구성
    데이터 업로드ggml_backend_tensor_set 수동 업로드gguf 로딩시 자동 tensor 초기화 + backend buffer 업로드
    계산 그래프 구성ggml_mul_matggml_build_forward_expandllama_decode_internal 내에서 여러 ggml_xxx 연산 호출 후 build
    메모리 할당 최적화ggml_gallocr_alloc_graphllama_batch_decode시 내부 alloc 사용
    그래프 실행ggml_backend_graph_computeggml_backend_graph_compute (llama.cpp도 동일 호출)
    결과 추출ggml_backend_tensor_getllama_decode_internal에서 logits 추출시 사용

    🖼️ 구조 흐름도 시각화

    📌 ggml-simple.cpp

    [main]
      ↓
    [backend init] → [ggml_init]
      ↓
    [create tensor A, B] → [upload tensor data]
      ↓

    [build graph: ggml_mul_mat]

    [alloc graph memory]

    ↓ [compute graph] → [get result tensor]

    📌 llama.cpp (inference 경로)

    [llama_init_from_file]
      ↓
    [backend init] → [ggml_init]
      ↓

    [load model.gguf → create all ggml_tensor]

    [upload tensor data to backend buffer]

    [user calls llama_decode or llama_batch_decode]

    [llama_decode_internal builds ggml_graph]

    [alloc graph memory (alloc + scratch)]

    [compute graph (ggml_backend_graph_compute)]

    [extract logits tensor → tokenizer / output]


    🎁 핵심 차이점

    ggml-simple.cpp수동으로 텐서 만들고 그래프 구성
    ✅ llama.cpp는 gguf 기반으로 텐서 자동 구성 + LLM 아키텍처(Attention/MLP/Norm 등) 구성

    • llama.cpp 내부의 핵심 그래프는:
      • ggml_mul_mat
      • ggml_norm
      • ggml_silu
      • ggml_repeat
      • ggml_add / ggml_mul / ggml_reshape 등
        → 이 모든 노드들이 ggml_cgraph로 빌드되고 실행됨 → 지금 올려주신 코드 흐름과 거의 동일한 구조

    🚀 정리

    • 지금 올려주신 ggml-simple.cpp는 llama.cpp 구조 이해에 매우 좋은 샘플임 → llama.cpp 내부도 결국 ggml_graph를 구성해 실행하는 구조
    • llama.cpp는:
      • model.gguf 로드 시 → tensor 구성
      • llama_decode 호출 시 → ggml_graph 구성
      • ggml_backend_graph_compute로 실행
    • 이식성 + 최적화 포인트도 동일하게 ggml 구조에 기반

    🎨 추천 학습 순서

    1️⃣ ggml-simple.cpp에서 ggml_mul_mat → → MLP Layer 구성도 연습
    2️⃣ llama.cpp의 llama_decode_internal 함수 분석
    3️⃣ gguf 파일 구조 분석 (gguf = LLM weights + arch config + metadata)
    4️⃣ llama.cpp에 커스텀 ggml 연산 추가 실험



    🗺️ llama.cpp 최신(2025) llama_decode_internal 구조 흐름

    1️⃣ llama_init_from_file

    • gguf 모델 파일 로드
    • 모든 ggml_tensor 생성 (weights, embeddings, norms 등)
    • backend init + backend buffer alloc + tensor data 업로드
    • → 기본적으로 ggml_new_tensor_xd + ggml_backend_tensor_set 사용됨 → ggml-simple.cpp와 동일

    2️⃣ llama_decode_internal 호출

    • 핵심 LLM “forward pass” 수행
    [llama_decode_internal]
      ↓

    [ggml_init graph context]

    [ggml_cgraph* gf = ggml_new_graph(ctx)]

    [build graph for N tokens → 1 layer씩 loop]


    3️⃣ Per-layer graph 구성

    For each Transformer layer:

    [ggml_rms_norm] → Norm
      ↓
    [ggml_mul_mat] Q_proj → Q
    [ggml_mul_mat] K_proj → K
    [ggml_mul_mat] V_proj → V
      ↓
    [ggml_rope] apply rotary positional embedding to Q,K
      ↓
    [ggml_mul_mat] Q x K^T → attention scores
    [ggml_soft_max] attention scores → weights
    [ggml_mul_mat] weights x V → attention output
      ↓
    [ggml_add] residual connection
      ↓
    [ggml_rms_norm] → Norm
      ↓
    [ggml_mul_mat] FFN_1 (intermediate proj)
    [ggml_silu] activation
    [ggml_mul] gated activation
    [ggml_mul_mat] FFN_2 (final proj)
      ↓
    [ggml_add] residual connection
    

    Transformer layer 1개 당 위와 같은 ggml node 그래프 구성


    4️⃣ 전체 Graph 최종 구성

    [embedding lookup]
      ↓

    [layer loop: N layers → ggml nodes push]

    [ggml_norm + projection to logits]

    [ggml_cgraph build complete → ggml_build_forward_expand(gf, final_logits)]


    5️⃣ 그래프 실행

    ggml_backend_graph_compute(backend, gf);
    
    • 바로 아까 ggml-simple.cpp와 동일한 호출
    • backend(CPU, CUDA, Metal 등)에 따라 최적화 실행

    6️⃣ 결과 추출

    ggml_backend_tensor_get(final_logits_tensor, ...)
    
    • logits 결과 추출 후 softmax / sampling → output token 결정

    🎁 정리

    ggml-simple.cppllama.cpp 내부 구조
    ggml_mul_matggml_mul_mat (Q_proj, K_proj, V_proj, FFN 등)
    ggml_build_forward_expandggml_build_forward_expand
    ggml_backend_graph_computeggml_backend_graph_compute
    tensor 수동 입력gguf로 자동 로드 후 tensor 구성
    matmul 결과 출력logits 결과 출력

    🚩 결론

    llama.cpp는 결국 ggml_graph 기반으로 모든 LLM forward pass를 구현
    ✅ 아까 올려주신 ggml-simple.cpp가 구조적으로 거의 동일:

    • Backend 초기화 → Tensor 구성 → Graph 구성 → Graph 실행 → 결과 출력

    ✅ llama.cpp에서 ggml_mul_mat / ggml_norm / ggml_silu / ggml_rope 등 기본 node들만 조합해서 LLM 완성

    ✅ 학습은 없고 inference-only라 그래프를 build → run → destroy cycle로 반복 (token마다 반복)


    🎨 요약 그림

    [Embedding] → [N layers (Attention + FFN)] → [Norm + Final projection] → [Logits] → [Sampling → Output token]