<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>min_tech.log</title>
        <link>https://velog.io/</link>
        <description>ai_tech_blog</description>
        <lastBuildDate>Fri, 09 Oct 2026 16:07:37 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>min_tech.log</title>
            <url>https://velog.velcdn.com/images/min_tech/profile/aa7de802-41a0-4ebd-902b-64f95cd2c306/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. min_tech.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/min_tech" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[RLHF, DPO]]></title>
            <link>https://velog.io/@min_tech/RLHF-DPO</link>
            <guid>https://velog.io/@min_tech/RLHF-DPO</guid>
            <pubDate>Fri, 09 Oct 2026 16:07:37 GMT</pubDate>
            <description><![CDATA[<h1 id="llm은-사람의-선호를-어떻게-배우는가">LLM은 사람의 선호를 어떻게 배우는가</h1>
<p>LLM이 문장을 자연스럽게 생성하는 것과 사람이 원하는 답변을 만드는 것은 다른 문제다. 사람의 선호를 반영하려면, 어떤 답변이 더 좋은지 학습하는 과정이 필요하다.</p>
<p>대표적인 방법으로 <strong>보상 모델과 PPO를 사용하는 RLHF</strong>, 그리고 <strong>선호 데이터로 모델을 직접 학습하는 DPO</strong>가 있다.</p>
<h2 id="1-시작은-선호-데이터">1. 시작은 선호 데이터</h2>
<p>같은 질문에 대한 두 답변 중 사람이 더 좋은 답변을 선택한다.</p>
<pre><code class="language-python">{
    &quot;prompt&quot;: &quot;제공된 자료만으로 결론을 확정할 수 있어?&quot;,
    &quot;chosen&quot;: &quot;예외 조건이 빠져 있어 추가 확인이 필요합니다.&quot;,
    &quot;rejected&quot;: &quot;네, 확정할 수 있습니다.&quot;
}</code></pre>
<p><code>chosen</code>은 선호 답변, <code>rejected</code>는 비선호 답변이다. 실제 데이터에는 판단에 필요한 자료도 질문과 함께 포함해야 한다.</p>
<p>두 방법은 이 데이터를 사용하는 방식이 다르다.</p>
<h2 id="2-rlhf-채점-모델을-만들고-답변-모델을-학습한다">2. RLHF: 채점 모델을 만들고 답변 모델을 학습한다</h2>
<p>여기서는 이미 지도 미세조정(SFT)이 된 답변 모델을 시작점으로 가정한다.</p>
<pre><code class="language-text">선호 데이터 → 보상 모델 학습
                  ↓
답변 생성 → 채점 → 보상 조정 → PPO 업데이트 → 반복</code></pre>
<p>이 구성에는 네 가지 모델 역할이 등장한다.</p>
<table>
<thead>
<tr>
<th>모델</th>
<th>역할</th>
<th>PPO 중 학습 여부</th>
</tr>
</thead>
<tbody><tr>
<td>Policy</td>
<td>실제 답변 생성</td>
<td>학습</td>
</tr>
<tr>
<td>Reward</td>
<td>완성된 답변 평가</td>
<td>고정</td>
</tr>
<tr>
<td>Value</td>
<td>생성 도중 미래 누적 보상 예측</td>
<td>학습</td>
</tr>
<tr>
<td>Reference</td>
<td>학습 시작 시점의 기준 policy</td>
<td>고정</td>
</tr>
</tbody></table>
<p>Policy와 value는 내부 Transformer를 공유하고 출력층만 다르게 구성할 수도 있다.</p>
<h3 id="보상-모델은-무엇을-배우나">보상 모델은 무엇을 배우나</h3>
<p>보상 모델은 질문과 답변을 입력받아 실수 점수 하나를 출력한다. 선호 답변의 점수가 더 높아지도록 학습한다.</p>
<pre><code class="language-python">chosen_score = reward_model(prompt, chosen)
rejected_score = reward_model(prompt, rejected)

loss = -F.logsigmoid(chosen_score - rejected_score).mean()</code></pre>
<p>수식은 다음과 같다.</p>
<p>$$
L_{RM}=-\log\sigma(r_{chosen}-r_{rejected})
$$</p>
<p>점수 차이가 커질수록 loss는 작아진다. <strong>보상 점수 자체는 확률이 아니므로 0~1로 제한하지 않는다.</strong> <code>logsigmoid</code>는 시그모이드와 로그를 따로 계산할 때 생길 수 있는 수치 문제를 피한다.</p>
<p>이후 PPO 단계에서는 학습된 보상 모델을 고정한다.</p>
<h2 id="3-kl-페널티-보상-점수만-쫓지-않도록-한다">3. KL 페널티: 보상 점수만 쫓지 않도록 한다</h2>
<p>보상 모델은 완벽하지 않다. 예를 들어 긴 답변을 과하게 높게 평가한다면, policy는 내용보다 길이를 늘리는 방향으로 학습할 수 있다.</p>
<p>이를 억제하기 위해 기준 모델과의 차이에 비용을 부여한다.</p>
<p>$$
J(\pi)=\mathbb{E}[r(x,y)]-\beta D_{KL}(\pi|\pi_{ref})
$$</p>
<p>직관적으로는 다음과 같다.</p>
<pre><code class="language-python">학습_목표 = 평균_보상 - 기준_모델에서_벗어난_정도의_페널티</code></pre>
<p>실행 시에는 policy가 생성한 답변을 reference에도 넣고, <strong>같은 토큰에 대한 로그 확률을 비교</strong>해 KL 항을 추정할 수 있다.</p>
<pre><code class="language-python">token_log_ratio = policy_log_probs - reference_log_probs</code></pre>
<p>토큰별 페널티를 주고, 답변 마지막 위치에 보상 모델 점수를 더하는 구성이 흔하다.</p>
<pre><code class="language-python">token_rewards = -beta * token_log_ratio
token_rewards[last_token] += reward_score</code></pre>
<p>두 모델의 평균 토큰 확률을 비교하는 계산이 아니다. 또한 개별 토큰의 로그 비율은 음수일 수 있지만, policy에서 샘플링한 답변들에 대한 로그 비율의 기댓값은 KL에 해당한다.</p>
<h2 id="4-value와-advantage-예상보다-얼마나-잘했는가">4. Value와 advantage: 예상보다 얼마나 잘했는가</h2>
<p>Value는 각 시점에서 <strong>앞으로 받을 누적 보상</strong>을 예상한다. 토큰 자체의 좋고 나쁨을 채점하는 모델은 아니다.</p>
<p>최종 보상만 있고 중간 보상과 할인이 없다고 단순화하면:</p>
<pre><code class="language-python">final_reward = 3.0

values     = [1.0, 2.0, 2.5]
returns    = [3.0, 3.0, 3.0]
advantages = [2.0, 1.0, 0.5]  # returns - values</code></pre>
<ul>
<li><strong>Return:</strong> 그 시점부터 얻은 누적 보상</li>
<li><strong>Advantage:</strong> 예상보다 얼마나 좋았는지를 나타내는 값</li>
</ul>
<p>최종 보상은 하나여도 학습 목표는 토큰 위치마다 구성된다. Value들을 전부 더하는 것은 아니다.</p>
<p>실제 PPO에서는 보통 GAE로 advantage를 추정한다. 이 경우 value 학습에 쓰는 return 목표는 가치 추정을 섞어 구성하므로, 단순한 실제 보상 합과 항상 같지는 않다.</p>
<h2 id="5-ppo-좋은-선택을-강화하되-과도한-업데이트를-억제한다">5. PPO: 좋은 선택을 강화하되 과도한 업데이트를 억제한다</h2>
<p>PPO는 <strong>Proximal Policy Optimization</strong>의 약자다. 보상에서 계산한 advantage로 policy를 업데이트하는 강화학습 알고리즘이다.</p>
<p>먼저 이미 생성한 답변을 현재 policy에 다시 넣어 토큰별 로그 확률을 계산한다.</p>
<pre><code class="language-python">old_log_probs = rollout.log_probs.detach()
new_log_probs = policy_log_probs(policy, prompts, responses)

ratio = torch.exp(new_log_probs - old_log_probs)</code></pre>
<p><code>rollout</code>은 답변 생성 기록이다. <code>old_log_probs</code>는 생성 당시의 고정된 확률이며, <code>new_log_probs</code>는 현재 가중치와 연결되어 역전파할 수 있는 값이다.</p>
<p>이 비율에 advantage를 반영하고 clipping을 적용한다.</p>
<pre><code class="language-python">advantages = advantages.detach()

objective = ratio * advantages
clipped = torch.clamp(ratio, 0.8, 1.2) * advantages

policy_loss = -masked_mean(torch.minimum(objective, clipped))</code></pre>
<p>예상보다 좋은 선택은 확률을 높이고, 나쁜 선택은 낮추는 방향으로 학습한다. Clipping은 특정 방향으로 확률 비율을 더 바꿔도 추가 이득을 주지 않게 한다. 비율이 반드시 해당 범위 안에 머문다는 강제 제한은 아니다.</p>
<p>Value는 예측이 return 목표에 가까워지도록 학습한다.</p>
<pre><code class="language-python">value_loss = masked_mean((predicted_values - returns.detach()) ** 2)</code></pre>
<p><strong>KL과 PPO clipping은 비교 기준이 다르다.</strong></p>
<table>
<thead>
<tr>
<th>장치</th>
<th>비교 기준</th>
</tr>
</thead>
<tbody><tr>
<td>Reference KL</td>
<td>학습 시작 때 고정한 모델</td>
</tr>
<tr>
<td>PPO clipping</td>
<td>이번 답변을 생성한 당시의 policy</td>
</tr>
</tbody></table>
<h2 id="6-dpo-선호-데이터로-policy를-직접-업데이트한다">6. DPO: 선호 데이터로 policy를 직접 업데이트한다</h2>
<p>DPO는 <strong>Direct Preference Optimization</strong>이다. 기본 DPO에는 별도의 reward 모델이나 value 모델이 필요 없다. 학습 중 새 답변을 생성하는 과정도 필요하지 않다.</p>
<p>대신 질문에 대한 <code>chosen</code>, <code>rejected</code>의 로그 확률을 policy와 고정 reference에서 계산한다.</p>
<pre><code class="language-python">policy_gap = policy_logprob(chosen) - policy_logprob(rejected)
reference_gap = reference_logprob(chosen) - reference_logprob(rejected)

z = beta * (policy_gap - reference_gap)
loss = -F.logsigmoid(z).mean()</code></pre>
<p>위 코드는 질문 입력 등을 생략한 의사코드다. 답변의 로그 확률은 <strong>질문을 조건으로 계산한 답변 토큰들의 로그 확률 합</strong>이며, 질문 토큰과 패딩은 제외한다.</p>
<p>학습 방향은 다음과 같다.</p>
<blockquote>
<p>기준 모델과 비교해, 비선호 답변보다 선호 답변을 상대적으로 더 선호하게 만든다.</p>
</blockquote>
<h3 id="policy-gap이-음수여도-개선일-수-있다">Policy gap이 음수여도 개선일 수 있다</h3>
<p>Beta가 0.1일 때 다음 두 경우를 비교해보자.</p>
<table>
<thead>
<tr>
<th align="right">Policy gap</th>
<th align="right">Reference gap</th>
<th align="right">z</th>
<th align="right">Loss</th>
</tr>
</thead>
<tbody><tr>
<td align="right">-2</td>
<td align="right">0</td>
<td align="right">-0.2</td>
<td align="right">약 0.798</td>
</tr>
<tr>
<td align="right">-2</td>
<td align="right">-5</td>
<td align="right">0.3</td>
<td align="right">약 0.554</td>
</tr>
</tbody></table>
<p>두 번째는 여전히 rejected의 확률이 더 높지만, 기준 모델보다는 chosen 쪽으로 기울었다.</p>
<p><strong>Policy gap 자체의 부호보다, reference gap을 뺀 결과가 중요하다.</strong> 이 결과가 커질수록 loss가 작아진다. 이는 상대적인 선호를 학습하는 것으로, chosen의 절대 확률이 매번 증가한다는 보장은 아니다.</p>
<h2 id="7-ppo-기반-rlhf와-dpo-비교">7. PPO 기반 RLHF와 DPO 비교</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>PPO 기반 RLHF</th>
<th>기본 DPO</th>
</tr>
</thead>
<tbody><tr>
<td>학습 신호</td>
<td>보상에서 계산한 advantage</td>
<td>선호·비선호 답변 쌍</td>
</tr>
<tr>
<td>별도 reward 모델</td>
<td>필요</td>
<td>불필요</td>
</tr>
<tr>
<td>Value 모델</td>
<td>사용</td>
<td>불필요</td>
</tr>
<tr>
<td>학습 중 답변 생성</td>
<td>필요</td>
<td>불필요</td>
</tr>
<tr>
<td>고정 reference</td>
<td>사용</td>
<td>사용</td>
</tr>
<tr>
<td>구현·운영 복잡도</td>
<td>상대적으로 높음</td>
<td>상대적으로 낮음</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Diffusion Transformer(DiT)]]></title>
            <link>https://velog.io/@min_tech/Diffusion-Transformer</link>
            <guid>https://velog.io/@min_tech/Diffusion-Transformer</guid>
            <pubDate>Sun, 26 Jul 2026 16:16:43 GMT</pubDate>
            <description><![CDATA[<h2 id="1-dit란">1. DiT란?</h2>
<p><strong>DiT(Diffusion Transformer)</strong>는 확산 모델에서 기존 U-Net 대신 Transformer를 사용하는 구조다.</p>
<p>확산 모델은 원본 이미지에 노이즈를 추가하고, 모델이 그 노이즈를 예측하도록 학습한다.</p>
<pre><code class="language-text">원본 이미지 x₀
→ 노이즈 추가
→ noisy image xₜ
→ DiT
→ 예측 노이즈 ε̂</code></pre>
<p>학습 손실은 보통 실제 노이즈와 예측 노이즈의 MSE를 사용한다.</p>
<pre><code class="language-python">loss = F.mse_loss(predicted_noise, noise)</code></pre>
<p>즉, DiT는 확산 과정 전체가 아니라 <strong>노이즈를 예측하는 Backbone</strong>이다.</p>
<hr>
<h2 id="2-u-net과-dit-차이">2. U-Net과 DiT 차이</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>U-Net</th>
<th>DiT</th>
</tr>
</thead>
<tbody><tr>
<td>구조</td>
<td>CNN 기반</td>
<td>Transformer 기반</td>
</tr>
<tr>
<td>입력 처리</td>
<td>Feature Map</td>
<td>Patch Token</td>
</tr>
<tr>
<td>전역 정보</td>
<td>계층적으로 처리</td>
<td>Self-Attention</td>
</tr>
<tr>
<td>확장 방식</td>
<td>채널과 Block 증가</td>
<td>Depth, Width, Head 증가</td>
</tr>
</tbody></table>
<p>DiT는 Transformer의 확장성을 활용하기 쉽지만, token 수가 많아지면 연산량도 크게 증가한다.</p>
<hr>
<h2 id="3-dit-구조">3. DiT 구조</h2>
<pre><code class="language-text">Noisy Image 또는 Latent
        ↓
Patch Embedding
        ↓
Position Embedding 추가
        ↓
Timestep Embedding 생성
        ↓
DiT Transformer Blocks
        ↓
Patch 복원
        ↓
Predicted Noise</code></pre>
<h3 id="patch-embedding">Patch Embedding</h3>
<p>이미지를 작은 패치로 나누고 token으로 변환한다.</p>
<p>예를 들어 <code>32×32</code> 이미지에 <code>4×4</code> 패치를 사용하면:</p>
<pre><code class="language-text">(32 / 4) × (32 / 4)
= 64 Tokens</code></pre>
<p>PyTorch에서는 <code>Conv2d</code>로 구현할 수 있다.</p>
<pre><code class="language-python">self.patch_embed = nn.Conv2d(
    in_channels=3,
    out_channels=hidden_size,
    kernel_size=patch_size,
    stride=patch_size,
)</code></pre>
<h3 id="timestep-embedding">Timestep Embedding</h3>
<p>현재 이미지에 노이즈가 얼마나 섞였는지 모델에 알려준다.</p>
<pre><code class="language-text">작은 timestep → 노이즈가 적음
큰 timestep   → 노이즈가 많음</code></pre>
<h3 id="self-attention">Self-Attention</h3>
<p>각 패치가 이미지의 다른 패치를 참고한다.</p>
<p>예를 들어 머리카락 패치가 얼굴, 눈, 배경 패치를 함께 참고할 수 있다.</p>
<p>다만 Attention 연산량은 일반적으로 token 수에 대해 다음과 같이 증가한다.</p>
<pre><code class="language-text">O(N²)</code></pre>
<hr>
<h2 id="4-adaln-zero">4. adaLN-Zero</h2>
<p>DiT는 timestep 조건을 Transformer Block에 넣기 위해 <strong>adaLN-Zero</strong>를 사용한다.</p>
<p>기본 형태는 다음과 같다.</p>
<pre><code class="language-text">output = normalized_x × (1 + scale) + shift</code></pre>
<p>Attention과 MLP 출력에는 gate를 적용한다.</p>
<pre><code class="language-python">x = x + gate * attention_output</code></pre>
<p>초기 gate를 0으로 설정해 학습 초기에 Block이 입력을 크게 변경하지 않도록 한다.</p>
<hr>
<h2 id="5-pytorch-간단-구현">5. PyTorch 간단 구현</h2>
<pre><code class="language-python">import math

import torch
import torch.nn.functional as F
from torch import nn


def timestep_embedding(t, dim):
    half = dim // 2

    frequencies = torch.exp(
        -math.log(10000)
        * torch.arange(
            half,
            device=t.device,
            dtype=torch.float32,
        )
        / half
    )

    angles = t.float()[:, None] * frequencies[None]

    return torch.cat(
        [
            torch.cos(angles),
            torch.sin(angles),
        ],
        dim=-1,
    )


class DiTBlock(nn.Module):
    def __init__(self, hidden_size, num_heads):
        super().__init__()

        self.norm1 = nn.LayerNorm(hidden_size)

        self.attention = nn.MultiheadAttention(
            hidden_size,
            num_heads,
            batch_first=True,
        )

        self.norm2 = nn.LayerNorm(hidden_size)

        self.mlp = nn.Sequential(
            nn.Linear(hidden_size, hidden_size * 4),
            nn.GELU(),
            nn.Linear(hidden_size * 4, hidden_size),
        )

    def forward(self, x):
        hidden = self.norm1(x)

        hidden, _ = self.attention(
            hidden,
            hidden,
            hidden,
            need_weights=False,
        )

        x = x + hidden
        x = x + self.mlp(self.norm2(x))

        return x


class MiniDiT(nn.Module):
    def __init__(
        self,
        image_size=32,
        patch_size=4,
        hidden_size=256,
        depth=4,
        num_heads=4,
    ):
        super().__init__()

        self.patch_size = patch_size
        self.grid_size = image_size // patch_size

        num_patches = self.grid_size ** 2

        self.patch_embed = nn.Conv2d(
            3,
            hidden_size,
            kernel_size=patch_size,
            stride=patch_size,
        )

        self.position_embedding = nn.Parameter(
            torch.zeros(
                1,
                num_patches,
                hidden_size,
            )
        )

        self.time_mlp = nn.Sequential(
            nn.Linear(hidden_size, hidden_size),
            nn.SiLU(),
            nn.Linear(hidden_size, hidden_size),
        )

        self.blocks = nn.ModuleList(
            [
                DiTBlock(
                    hidden_size,
                    num_heads,
                )
                for _ in range(depth)
            ]
        )

        self.output = nn.Linear(
            hidden_size,
            patch_size * patch_size * 3,
        )

    def forward(self, x, timestep):
        batch_size = x.shape[0]

        x = self.patch_embed(x)
        x = x.flatten(2).transpose(1, 2)

        x = x + self.position_embedding

        time_condition = timestep_embedding(
            timestep,
            x.shape[-1],
        )

        time_condition = self.time_mlp(
            time_condition
        )

        x = x + time_condition[:, None, :]

        for block in self.blocks:
            x = block(x)

        x = self.output(x)

        x = x.view(
            batch_size,
            self.grid_size,
            self.grid_size,
            self.patch_size,
            self.patch_size,
            3,
        )

        x = torch.einsum(
            &quot;bhwpqc-&gt;bchpwq&quot;,
            x,
        )

        return x.reshape(
            batch_size,
            3,
            self.grid_size * self.patch_size,
            self.grid_size * self.patch_size,
        )</code></pre>
<p>간단한 학습 흐름은 다음과 같다.</p>
<pre><code class="language-python">model = MiniDiT()

x0 = torch.randn(2, 3, 32, 32)
noise = torch.randn_like(x0)

timestep = torch.randint(
    0,
    1000,
    (2,),
)

noisy_image = x0 + noise

predicted_noise = model(
    noisy_image,
    timestep,
)

loss = F.mse_loss(
    predicted_noise,
    noise,
)

loss.backward()</code></pre>
<p>실제 diffusion 학습에서는 <code>x0 + noise</code> 대신 timestep에 맞는 noise scheduler를 사용해야 한다.</p>
<hr>
<h2 id="6-latent-dit">6. Latent DiT</h2>
<p>실제 대규모 모델은 RGB 이미지를 직접 처리하기보다 VAE latent에서 DiT를 실행한다.</p>
<pre><code class="language-text">Image
→ VAE Encoder
→ Latent
→ DiT
→ VAE Decoder
→ Generated Image</code></pre>
<p>latent를 사용하면 이미지 크기와 token 수를 줄일 수 있다.</p>
<hr>
<h2 id="7-영상-dit">7. 영상 DiT</h2>
<p>영상에서는 시간축이 추가된다.</p>
<pre><code class="language-text">이미지: [B, C, H, W]
영상:   [B, C, T, H, W]</code></pre>
<p>일반적으로 공간 방향의 Spatial Attention과 시간 방향의 Temporal Attention을 함께 사용한다.</p>
<pre><code class="language-text">Video
→ Spatial Patch
→ Spatial Attention
→ Temporal Attention
→ Output Video</code></pre>
<p>영상은 프레임 수만큼 token이 증가하기 때문에 이미지 DiT보다 훨씬 많은 GPU 메모리가 필요하다.</p>
<hr>
<h2 id="정리">정리</h2>
<p>DiT는 확산 모델의 노이즈 예측 Backbone을 Transformer로 구현한 구조다.</p>
<pre><code class="language-text">DiT
=
Diffusion
+
Transformer</code></pre>
<p>핵심 과정은 다음과 같다.</p>
<pre><code class="language-text">이미지 또는 latent
→ Patch Token 변환
→ Timestep 조건 추가
→ Transformer 처리
→ 이미지 형태 복원
→ 노이즈 예측</code></pre>
<p>Transformer의 확장성과 전역 정보 처리 능력 때문에 이미지뿐 아니라 최근 영상 생성 모델에서도 많이 사용되고 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[U-Net]]></title>
            <link>https://velog.io/@min_tech/U-Net</link>
            <guid>https://velog.io/@min_tech/U-Net</guid>
            <pubDate>Tue, 05 May 2026 13:02:12 GMT</pubDate>
            <description><![CDATA[<h1 id="deep-learning-이미지-세그멘테이션-u-net-정리">[Deep Learning] 이미지 세그멘테이션, U-Net 정리</h1>
<hr>
<h2 id="1-u-net이란">1. U-Net이란?</h2>
<ul>
<li><strong>배경:</strong> 2015년 Olaf Ronneberger 연구팀이 제안.</li>
<li><strong>특징:</strong> 적은 양의 데이터로도 매우 정교한 세그멘테이션이 가능함.</li>
<li><strong>용도:</strong> 의료 영상(세포/장기 검출), 자율 주행, 위성 이미지 분석, 심박 그래프 추출 등.</li>
</ul>
<hr>
<h2 id="2-u-net의-구조-왜-u인가">2. U-Net의 구조: 왜 &#39;U&#39;인가?</h2>
<h3 id="①-인코더-contracting-path---수축-경로">① 인코더 (Contracting Path - 수축 경로)</h3>
<ul>
<li>이미지의 <strong>전체적인 맥락(Context)</strong>을 파악하는 단계입니다.</li>
<li>일반적인 CNN처럼 컨볼루션과 풀링을 반복하며 이미지 크기는 줄이고 채널(특징)은 늘립니다.</li>
</ul>
<h3 id="②-디코더-expanding-path---확장-경로">② 디코더 (Expanding Path - 확장 경로)</h3>
<ul>
<li>줄어든 이미지를 다시 원래 크기로 복원하며 <strong>정확한 위치(Localization)</strong> 정보를 찾아냅니다.</li>
<li>Up-convolution을 통해 해상도를 높입니다.</li>
</ul>
<h3 id="③-스킵-커넥션-skip-connection">③ 스킵 커넥션 (Skip Connection)</h3>
<ul>
<li>인코더의 고해상도 특징 맵을 디코더의 상응하는 층에 직접 <strong>이어붙이는(Concatenate)</strong> 방식입니다.</li>
<li><strong>효과:</strong> 인코더가 압축 과정에서 잃어버린 미세한 위치 정보를 디코더가 다시 참고하게 하여, 얇은 선이나 복잡한 경계도 정교하게 복원합니다.</li>
</ul>
<hr>
<h2 id="3-pytorch-핵심-코드-구현">3. PyTorch 핵심 코드 구현</h2>
<h3 id="1-모델-정의-unetpy">1) 모델 정의 (<code>UNet.py</code>)</h3>
<pre><code class="language-python">import torch
import torch.nn as nn

class UNet(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(UNet, self).__init__()

        def conv_block(in_c, out_c):
            return nn.Sequential(
                nn.Conv2d(in_c, out_c, kernel_size=3, padding=1),
                nn.BatchNorm2d(out_c),
                nn.ReLU(inplace=True),
                nn.Conv2d(out_c, out_c, kernel_size=3, padding=1),
                nn.BatchNorm2d(out_c),
                nn.ReLU(inplace=True)
            )

        # Encoder (Downsampling)
        self.enc1 = conv_block(in_channels, 64)
        self.pool1 = nn.MaxPool2d(2)
        self.enc2 = conv_block(64, 128)
        self.pool2 = nn.MaxPool2d(2)
        self.enc3 = conv_block(128, 256)
        self.pool3 = nn.MaxPool2d(2)
        self.enc4 = conv_block(256, 512)
        self.pool4 = nn.MaxPool2d(2)

        # Bottleneck
        self.bottleneck = conv_block(512, 1024)

        # Decoder (Upsampling)
        self.up4 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)
        self.dec4 = conv_block(1024, 512) # Skip Connection 반영
        self.up3 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)
        self.dec3 = conv_block(512, 256)
        self.up2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)
        self.dec2 = conv_block(256, 128)
        self.up1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)
        self.dec1 = conv_block(128, 64)

        self.final_conv = nn.Conv2d(64, out_channels, kernel_size=1)

    def forward(self, x):
        e1 = self.enc1(x)
        e2 = self.enc2(self.pool1(e1))
        e3 = self.enc3(self.pool2(e2))
        e4 = self.enc4(self.pool3(e3))

        b = self.bottleneck(self.pool4(e4))

        d4 = self.up4(b)
        d4 = torch.cat((d4, e4), dim=1) # 스킵 커넥션 결합
        d4 = self.dec4(d4)

        d3 = self.up3(d4)
        d3 = torch.cat((d3, e3), dim=1)
        d3 = self.dec3(d3)

        d2 = self.up2(d3)
        d2 = torch.cat((d2, e2), dim=1)
        d2 = self.dec2(d2)

        d1 = self.up1(d2)
        d1 = torch.cat((d1, e1), dim=1)
        d1 = self.dec1(d1)

        return self.final_conv(d1)</code></pre>
<h3 id="2-학습-루프-training">2) 학습 루프 (Training)</h3>
<pre><code class="language-python"># 모델 및 하이퍼파라미터 설정
model = UNet(in_channels=3, out_channels=1).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.BCEWithLogitsLoss() # 이진 분류를 위한 손실 함수

for epoch in range(num_epochs):
    model.train()
    for images, masks in train_loader:
        images, masks = images.to(device), masks.to(device)

        # 순전파 (Forward)
        outputs = model(images)
        loss = criterion(outputs, masks)

        # 역전파 및 최적화 (Backward)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    print(f&quot;Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}&quot;)</code></pre>
<hr>
<h2 id="4-실전-활용-심박-그래프-추출">4. 실전 활용: 심박 그래프 추출</h2>
<p>U-Net을 활용하면 복잡한 배경(격자, 노이즈)이 있는 심박지 이미지에서 <strong>그래프 선만 깨끗하게 추출</strong>할 수 있습니다.</p>
<ul>
<li><strong>핵심 원리:</strong> <ul>
<li><strong>인코더</strong>가 격자와 그래프의 형태학적 특징을 구분하는 법을 학습합니다.</li>
<li><strong>스킵 커넥션</strong>이 1픽셀 수준의 아주 얇은 그래프 선 정보를 디코더에 전달하여 뭉개짐 없이 복원합니다.</li>
</ul>
</li>
<li><strong>Output 처리:</strong> 모델이 내뱉은 확률값을 임계값(Threshold, 0.5)으로 끊어주면 검은색 바탕에 흰색 줄만 남은 결과물을 얻게 됩니다.</li>
</ul>
<hr>
<h2 id="5-마치며">5. 마치며</h2>
<p>U-Net은 단순히 채널을 줄이고 늘리는 구조를 넘어, <strong>스킵 커넥션</strong>이라는 혁신적인 아이디어로 세그멘테이션의 정밀도를 한 차원 끌어올린 모델입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Qwen3-VL]]></title>
            <link>https://velog.io/@min_tech/Qwen3-VL</link>
            <guid>https://velog.io/@min_tech/Qwen3-VL</guid>
            <pubDate>Sat, 21 Mar 2026 15:37:39 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="qwen3-vl">Qwen3-VL</h1>
<p><img src="https://velog.velcdn.com/images/min_tech/post/2e4a429c-b75a-4312-9cc8-70ce2e9537a5/image.png" alt="">
<strong>Qwen3-VL</strong>은 기존 VLM(Vision Language Model)의 한계를 넘어서기 위해 두 가지 핵심 전략을 취하고 있습니다. 바로 <strong>Native Resolution Input</strong>과 <strong>DeepStack Decoder</strong> 구조입니다.</p>
<hr>
<h2 id="1-입력부-native-resolution--dynamic-tokenization">1. 입력부: Native Resolution &amp; Dynamic Tokenization</h2>
<p>대부분의 VLM은 입력 이미지를 고정된 해상도(예: 224x224)로 리사이징하지만, Qwen3-VL은 <strong>가변 해상도(Native Resolution)</strong>를 지원하여 정보 손실을 최소화합니다.</p>
<ul>
<li><strong>비전 인코더(Vision Encoder):</strong> 이미지의 왜곡 없이 원본의 종횡비를 유지하며 패치(Patch)를 추출합니다.</li>
<li><strong>다이내믹 토큰화(Dynamic Tokenization):</strong> 입력 이미지의 해상도와 정보량에 따라 할당되는 토큰 수가 유동적으로 결정됩니다.<ul>
<li><strong>Picture 1 (11,427 tokens):</strong> 고해상도 문서나 세로로 긴 블로그 이미지. OCR 및 세부 디테일 보존을 위해 방대한 토큰 할당.</li>
<li><strong>Picture 2 (8 tokens):</strong> 아주 작은 아이콘이나 정보량이 적은 썸네일. 연산 효율을 위해 최소한의 토큰으로 압축.</li>
<li><strong>Picture 3 (1,125 tokens):</strong> 일반적인 표준 해상도 이미지.</li>
</ul>
</li>
<li><strong>프로젝션 레이어(Projection Layer):</strong> 비전 인코더의 출력을 LLM의 임베딩 차원(Dimension)과 일치시켜 &#39;비전 토큰&#39; 시퀀스로 변환합니다.</li>
</ul>
<hr>
<h2 id="2-핵심-메커니즘-deepstack-decoder">2. 핵심 메커니즘: DeepStack Decoder</h2>
<p>Qwen3-VL의 가장 큰 혁신은 <strong>DeepStack</strong> 구조입니다. 비전 정보를 모델의 입구에서만 넣어주는 것이 아니라, <strong>LLM의 모든 블록에서 반복적으로 주입</strong>하여 시각 정보와 텍스트의 결합도(Fusion)를 향상시킵니다.</p>
<h3 id="층block-내부의-데이터-흐름">층(Block) 내부의 데이터 흐름</h3>
<p>각 LLM 블록($N$) 내부에서는 다음과 같은 정교한 연산이 수행됩니다.</p>
<ol>
<li><strong>Masked Self-Attention (텍스트 전용):</strong>
이전 층에서 올라온 Hidden State($H_n$)를 통해 텍스트 간의 문맥을 먼저 파악합니다. 이 단계에서 $H_n$은 $Q, K, V$로 분화되어 텍스트 자기들끼리의 관계를 정리합니다.</li>
<li><strong>Cross-Attention (텍스트-비전 결합):</strong><ul>
<li><strong>Query ($Q$):</strong> Self-Attention을 거쳐 &#39;똑똑해진&#39; 텍스트 벡터를 사용합니다.</li>
<li><strong>Key &amp; Value ($K, V$):</strong> 프로젝션 레이어를 통과해 대기 중인 <strong>정적인 비전 토큰</strong>을 참조합니다.</li>
<li><strong>의미:</strong> 층이 깊어질수록 텍스트($Q$)는 더 구체적인 질문을 던지고, 비전($K, V$)에서 필요한 정보를 세밀하게 추출(Feature Refinement)합니다.</li>
</ul>
</li>
<li><strong>Feed-Forward Network (FFN) &amp; Residual Connection:</strong>
통합된 멀티모달 정보를 정제하고, 잔차 연결을 통해 원래의 텍스트 정보를 유지하며 다음 블록으로 전달합니다.</li>
</ol>
<p>$$Attention(Q_{text}, K_{vision}, V_{vision}) = \text{softmax}\left(\frac{Q_{text}K_{vision}^T}{\sqrt{d_k}}\right)V_{vision}$$</p>
<hr>
<h2 id="3-학습-및-추론-프로세스">3. 학습 및 추론 프로세스</h2>
<h3 id="학습-training">학습 (Training)</h3>
<p>단순히 마지막 층만 학습시키는 것이 아니라, 최종 출력값과 정답(Label) 간의 오차(Loss)를 계산하여 모델 전체를 업데이트합니다.</p>
<ul>
<li><strong>역전파(Backpropagation):</strong> Loss가 발생하면 <strong>비전 인코더 → 프로젝터 → LLM 디코더</strong>의 모든 연결 고리를 동시에 수정하여 &quot;이미지를 보고 정답 단어를 맞히는&quot; 최적의 가중치를 찾아냅니다.</li>
</ul>
<h3 id="추론-inference">추론 (Inference)</h3>
<ul>
<li><strong>Prefill 단계:</strong> 이미지와 질문을 한꺼번에 처리하여 첫 번째 토큰을 생성합니다. (이미지는 단 한 번만 인코딩)</li>
<li><strong>Decoding 단계:</strong> 생성된 단어를 다시 입력으로 넣어 다음 단어를 예측하는 자기회귀(Autoregressive) 방식을 사용합니다.</li>
<li><strong>KV Caching:</strong> 매 단계마다 비전 토큰과 이전 텍스트 정보를 다시 계산하지 않도록 메모리에 저장(Cache)하여 추론 속도를 비약적으로 높입니다.</li>
</ul>
<hr>
<h2 id="4-요약-및-결론">4. 요약 및 결론</h2>
<p>Qwen3-VL은 <strong>&quot;이미지를 텍스트처럼 읽되, 모든 사고 과정(Layer)에서 이미지를 다시 정밀하게 들여다보는&quot;</strong> 모델입니다.</p>
<blockquote>
<p><strong>Insight:</strong> 이러한 구조 덕분에 Qwen3-VL은 복잡한 문서 이해(OCR), 긴 영상 분석, 고도의 시각적 추론 작업에서 기존 모델들을 압도하는 성능을 보여줍니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[vLLM]]></title>
            <link>https://velog.io/@min_tech/vLLM</link>
            <guid>https://velog.io/@min_tech/vLLM</guid>
            <pubDate>Mon, 16 Mar 2026 06:23:52 GMT</pubDate>
            <description><![CDATA[<h1 id="vllm의-pagedattention-아키텍처와-gpu-메모리-최적화-원리">vLLM의 PagedAttention 아키텍처와 GPU 메모리 최적화 원리</h1>
<p><img src="https://velog.velcdn.com/images/min_tech/post/c5d88ed2-e7ac-4a2b-831f-72b61fe5047d/image.png" alt=""></p>
<p>LLM(Large Language Model)의 추론(Inference) 효율성은 대규모 서비스 운영의 핵심입니다. 특히 <strong>KV 캐시(Key-Value Cache)</strong> 관리의 비효율성은 GPU 메모리 부족과 처리량(Throughput) 저하의 주된 원인이 됩니다. vLLM은 운영체제의 가상 메모리 기법을 차용한 <strong>PagedAttention</strong>을 통해 이 문제를  해결했습니다. </p>
<h2 id="1-kv-캐싱과-증분-디코딩incremental-decoding">1. KV 캐싱과 증분 디코딩(Incremental Decoding)</h2>
<p>트랜스포머 디코더 모델의 추론 과정은 이전에 생성된 토큰들을 기반으로 다음 토큰을 하나씩 예측하는 자기회귀(Auto-regressive) 방식을 따릅니다.</p>
<ul>
<li><strong>연산의 효율성:</strong> 새로운 토큰이 입력될 때마다 전체 문장에 대한 $Q, K, V$를 재계산하지 않습니다. 새로 들어온 토큰에 대해서만 $Q, K, V$ 벡터를 투영(Projection)하고, 이전 토큰들의 $K, V$ 값은 메모리에 저장된 값을 재사용합니다.</li>
<li><strong>문제점:</strong> 기존 프레임워크는 문장의 최대 길이(Max Sequence Length)에 맞춰 메모리를 미리 정적 할당(Static Allocation)합니다. 이는 실제 사용되지 않는 메모리 공간을 점유하여 <strong>내부 파편화(Internal Fragmentation)</strong>를 유발하고, 동시 처리 가능한 배치 크기를 제한합니다.</li>
</ul>
<h2 id="2-pagedattention-동적-메모리-할당의-구현">2. PagedAttention: 동적 메모리 할당의 구현</h2>
<p>vLLM의 핵심인 <strong>PagedAttention</strong>은 고정된 크기의 <strong>블록(Block)</strong> 단위로 KV 캐시를 관리합니다.</p>
<ul>
<li><strong>블록 단위 관리:</strong> KV 캐시를 연속된 공간이 아닌, 일정한 토큰 개수(Block Size, 예: 16)를 담는 블록들로 쪼갭니다.</li>
<li><strong>블록 테이블(Block Table):</strong> 논리적인 토큰 순서와 물리적인 GPU 메모리 주소를 매핑하는 페이지 테이블 역할을 합니다. 데이터가 물리적으로 흩어져 있어도 모델은 지도를 참조하여 논리적으로 연속된 데이터처럼 읽어 들일 수 있습니다.</li>
</ul>
<h2 id="3-gpu-하드웨어-가속과-커널-최적화">3. GPU 하드웨어 가속과 커널 최적화</h2>
<ol>
<li><strong>VRAM(Global Memory):</strong> 모든 KV 블록이 저장되는 메인 저장소입니다.</li>
<li><strong>Shared Memory (On-chip):</strong> 연산 장치인 SM(Streaming Multiprocessor) 내부에 위치한 초고속 메모리입니다. vLLM 커널은 페이지 테이블을 참조해 필요한 블록을 VRAM에서 <strong>Shared Memory</strong>로 가져옵니다.</li>
<li><strong>Tiling &amp; Parallelism:</strong> 각 SM은 서로 다른 블록들을 병렬로 맡아 처리합니다. 쉐어드 메모리에 로드된 $K$ 벡터들과 현재 쿼리($Q$)의 <strong>내적(Dot Product)</strong> 연산이 Registers에서 수행되며, 이는 높은 하드웨어 점유율(Occupancy)을 유지하며 처리됩니다.</li>
</ol>
<h2 id="4-kv-캐시의-물리적-구조-4차원-텐서-레이아웃">4. KV 캐시의 물리적 구조: 4차원 텐서 레이아웃</h2>
<p>vLLM은 물리적 메모리를 다음과 같은 <strong>4차원 텐서($4D$ Tensor)</strong> 구조로 선언하여 관리합니다.</p>
<p>$$[Block, Head, Block_Size, Head_D]$$</p>
<ul>
<li><strong>Block ($B$):</strong> 할당된 총 블록의 개수입니다.</li>
<li><strong>Head ($H$):</strong> 멀티 헤드 어텐션의 헤드 수입니다. 각 헤드는 독립적인 파라미터를 가지므로 별도 저장됩니다.</li>
<li><strong>Block_Size ($L$):</strong> 한 블록에 포함된 토큰의 개수입니다. 연산의 규칙성을 보장합니다.</li>
<li><strong>Head_D ($D$):</strong> 각 헤드별 임베딩 차원의 크기입니다.</li>
</ul>
<p>이러한 레이아웃은 GPU 가속기가 메모리 스라이드(Stride)를 통해 데이터를 읽어올 때 최적의 성능을 낼 수 있도록 설계된 결과입니다.</p>
<h2 id="5-결론-vllm이-서빙-효율에-미치는-영향">5. 결론: vLLM이 서빙 효율에 미치는 영향</h2>
<p>vLLM은 PagedAttention을 통해 메모리 낭비를 거의 0%에 가깝게 줄였습니다.</p>
<ul>
<li><strong>연속 배치(Continuous Batching):</strong> 요청의 종료 시점이 달라도 빈 블록을 즉시 새로운 요청에 할당할 수 있어 처리량이 비약적으로 상승합니다.</li>
<li><strong>복합 요청 처리:</strong> 영수증 OCR과 같이 비전 토큰이 대량으로 발생하는 경우에도, 효율적인 블록 관리를 통해 대규모 동시 요청을 안정적으로 소화합니다.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vision Transformer(ViT) 구현]]></title>
            <link>https://velog.io/@min_tech/Vision-TransformerViT-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@min_tech/Vision-TransformerViT-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 28 Nov 2025 14:37:00 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/min_tech/post/1d63b504-1a53-43d9-a656-905c2579db09/image.jpg" width="100%" />

<p><img src="https://velog.velcdn.com/images/min_tech/post/1e5fff3f-f79f-433f-a9a5-5a5ac3e6e8f8/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/6d61da7f-b9a7-4799-b608-ca785454142b/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/3ae6e147-f465-47ce-80b0-4387402d6d0d/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/35586d32-89c6-4294-8797-bbcbce561cb1/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/0ac23d44-0ba7-4416-9471-6ac7dbd95df9/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Transformer 구현]]></title>
            <link>https://velog.io/@min_tech/Transformer</link>
            <guid>https://velog.io/@min_tech/Transformer</guid>
            <pubDate>Tue, 04 Nov 2025 06:16:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/min_tech/post/91f34b6f-fb74-451b-a604-140c4fb846af/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/min_tech/post/339909a3-bf6c-4480-952b-add7a5bc9ddc/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/00fb020d-dff9-4b01-a577-55b0d70bff9b/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/9f4a5d9e-5d5e-4e39-9504-dbc3b2d9c670/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/9d74ab1c-2846-4b34-80e5-8e25994573dd/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[YOLO v1]]></title>
            <link>https://velog.io/@min_tech/YOLO-v1</link>
            <guid>https://velog.io/@min_tech/YOLO-v1</guid>
            <pubDate>Wed, 29 Oct 2025 07:49:35 GMT</pubDate>
            <description><![CDATA[<p><strong>YOLO v1</strong></p>
<p>YOLO(You Only Look Once)는 이름 그대로, 이미지를 한 번의 forward pass로 객체 탐지를 수행하는 모델이다.
기존 R-CNN 계열이 &quot;영역 제안 → 분류&quot;의 2단계 구조였다면, YOLO는 이 모든 과정을 단일 CNN 네트워크 안에 통합했다.</p>
<p><img src="https://velog.velcdn.com/images/min_tech/post/30175e1c-c6f9-47a3-8242-8b24b8338197/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/min_tech/post/3006ec55-8cfe-4c4b-aad3-ec14bf0e7408/image.png" alt="">
<strong>Feature Extractor</strong>
기존 GoogLeNet 구조를 수정한 backbone을 사용합니다.
이 부분은 CNN 기반 특징 추출기 역할을 하며,
입력 이미지로부터 edge, texture, shape, pattern 등 시각적 특징(feature)을 추출합니다.</p>
<p><strong>Convolution + ReLU Block (C, R)</strong>
여러 개의 conv/relu 연산을 통해 더 깊은 특징을 학습합니다.
“C, R”은 각각 Convolution과 ReLU 활성화 함수를 의미합니다.
점점 공간 해상도는 줄어들고, 채널 수는 유지(1024) 되며, 추상적인 고수준 의미(feature abstraction)를 얻습니다.</p>
<p><strong>Fully Connected Layer (FC)</strong>
Convolution feature를 벡터 형태로 평탄화(flatten) 하여 완전연결층으로 전달합니다.</p>
<p><strong>Reshape Layer</strong>
최종적으로 모델은 7×7개의 grid 셀마다 30개의 값을 출력한다.
<br><br><br><br><br></p>
<p><img src="https://velog.velcdn.com/images/min_tech/post/106a961e-d984-4692-9cb1-b5e0ab0c2e77/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/min_tech/post/59f37e34-9d97-42cd-9d04-eefaa4cd8686/image.png" alt=""></p>
<p>YOLO는 입력 이미지를 7×7 grid로 나눈 뒤, 각 셀마다 두 개의 bounding box와 클래스 확률을 예측한다.
7 × 7 × 30  →  (S × S × (B*5 + C))
<br><br><br><br><br></p>
<p><strong>손실 함수 (Loss Function)</strong>
<img src="https://velog.velcdn.com/images/min_tech/post/1fe6aa48-cf30-47fd-afe1-253f0bd7a772/image.png" alt=""></p>
<p>YOLO는 모든 예측값을 하나의 멀티태스크 손실 함수로 학습한다.
즉, 좌표 손실 + 크기 손실 + confidence 손실 + 클래스 손실을 동시에 최소화한다.
<br><br><br><br><br></p>
<p><strong>학습과 가중치 업데이트</strong>
Forward pass: CNN이 이미지를 통해 예측값(7×7×30)을 출력
Loss 계산: 실제 라벨과 비교하여 전체 손실 산출
Backward pass: 손실의 기울기를 각 파라미터에 대해 계산 (∂Loss/∂W)
Optimizer update: SGD 또는 Adam으로 가중치 갱신</p>
<p><br><br>
<strong>결론</strong>
YOLO v1은 “객체 탐지 = 회귀(regression)”라는 새로운 관점을 제시했다.
한 번의 추론으로 객체의 위치와 클래스를 동시에 예측함으로써,
실시간성(Real-time)과 정확도(Accuracy)의 균형을 달성한 최초의 구조였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[재귀 호출시 메모리 구조]]></title>
            <link>https://velog.io/@min_tech/%EC%9E%AC%EA%B7%80-%ED%98%B8%EC%B6%9C%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@min_tech/%EC%9E%AC%EA%B7%80-%ED%98%B8%EC%B6%9C%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Wed, 22 Oct 2025 05:49:25 GMT</pubDate>
            <description><![CDATA[<p>— 재귀함수 사용시 데이터가 스택에 어떻게 쌓이는가 —</p>
<p>메모리
고주소 (High Address)
┌───────────────────────────┐
│     커널 영역 (Kernel)    │  ← 운영체제가 쓰는 공간
├───────────────────────────┤
│         스택 (Stack)       │  ↓ (위 → 아래로 성장)
│  함수 호출 시마다 프레임   │
│  지역 변수, 매개변수 저장  │
├───────────────────────────┤
│           힙 (Heap)        │  ↑ (아래 → 위로 성장)
│  동적 할당 (malloc/new)    │
├───────────────────────────┤
│     데이터 영역 (Data)     │
│  전역 변수, static 변수    │
├───────────────────────────┤
│     코드 영역 (Text)       │
│  실행 명령어(기계어 코드)  │
저주소 (Low Address)</p>
<p><strong>스택이 힙 영역을 침범하거나, OS가 보호해둔 guard 영역에 도달하면 Stack Overflow 발생</strong>
<br><br><br><br><br></p>
<pre><code>def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

factorial(3)
print(&quot;done&quot;)</code></pre><p>고주소
0x7fff_FFD0  [ return addr → main으로 돌아갈 위치 ]
0x7fff_FFC8  [ n = 3 ]
0x7fff_FFC0  [ 대기: 3 * … ]</p>
<p>0x7fff_FFB0  [ return addr → factorial(3)로 돌아갈 위치 ]
0x7fff_FFA8  [ n = 2 ]
0x7fff_FFA0  [ 대기: 2 * ... ]</p>
<p>0x7fff_FF90  [ return addr → factorial(2)로 돌아갈 위치 ]
0x7fff_FF88  [ n = 1 ]
0x7fff_FF80  [ 대기: 1 * … ]</p>
<p>0x7fff_FF70  [ return addr → factorial(1)로 돌아갈 위치 ]
0x7fff_FF68  [ n = 0 ]
0x7fff_FF60  [ 리턴: 1 ]
저주소
<br><br><br><br><br></p>
<pre><code>main:
    push    rbp              ; _start의 rbp 푸쉬, rsp 8바이트 감소
    mov     rbp, rsp

mov     edi, 3           ; 인자 준비: n=3을 edi 레지스터에 넣음
call    factorial        ; ★ factorial(3) 호출


factorial:
push    rbp                  ; main의 rbp를 스택에 저장
mov     rbp, rsp             ; rbp = rsp (새 프레임 시작점)
sub     rsp, 16              ; 지역 변수 공간 확보 (16바이트)</code></pre><p>factorial 진입 시 프레임 생성
-이전 함수(main)의 rbp를 스택에 저장
-새 프레임 기준점을 rbp로 설정
-지역 변수용 공간 16바이트 확보 (rsp를 아래로 내림)
<br><br><br><br><br></p>
<pre><code>mov     DWORD PTR [rbp-4], edi    ; 매개변수 n=3 저장
mov     eax, DWORD PTR [rbp-4]    ; eax = n
mov     DWORD PTR [rbp-8], eax    ; tmp = n (곱셈 위해 따로 저장)</code></pre><p>매개변수와 임시 변수 저장
-EDI에 있던 매개변수 3을 [rbp-4] 위치(스택)에 저장
-계산에 쓸 임시 변수(tmp)도 [rbp-8]에 보관
<br><br><br><br><br></p>
<pre><code>mov     eax, DWORD PTR [rbp-4]    ; eax = n
sub     eax, 1                    ; eax = n-1
mov     edi, eax                  ; edi = n-1 (다음 호출 인자 준비)
call    factorial        ; ★ factorial(2) 호출</code></pre><p>요약: call이 실행될 때마다 CPU는 리턴 주소와 지역 변수를 스택에 저장,
새로운 rbp 기반의 독립된 함수 프레임을 만들어 재귀를 이어갑니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Closure]]></title>
            <link>https://velog.io/@min_tech/closure</link>
            <guid>https://velog.io/@min_tech/closure</guid>
            <pubDate>Wed, 15 Oct 2025 08:10:29 GMT</pubDate>
            <description><![CDATA[<h3 id="클로저-closure">클로저 (Closure)</h3>
<h4 id="정의">정의:</h4>
<p>함수 내부에서 정의된 함수가 외부 함수의 지역 변수를 참조하고, 외부 함수 실행이 끝난 뒤에도 그 변수를 기억하는 함수.</p>
<h4 id="효과">효과:</h4>
<p>상태 유지
캡슐화 (외부에서 변수 직접 접근 차단)</p>
<h4 id="예시">예시:</h4>
<p><img src="https://velog.velcdn.com/images/min_tech/post/25899e2d-ffbe-4b76-b621-38c84715e948/image.png" alt=""></p>
<p><br><br></p>
<p><img src="https://velog.velcdn.com/images/min_tech/post/a79cd967-afcb-43ff-8de5-7398a2252a5a/image.png" alt=""></p>
<p>→ inner 함수는 외부 함수 outer의 변수 x=5를 기억하고 있음.
add5는 사실상 &quot;y를 받아서 5 + y를 계산하는 함수 객체&quot;
이때 x=5 값은 add5 함수 객체의 내부 속성(<strong>closure</strong>)에 저장됨
<br><br><br><br><br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CUDA]]></title>
            <link>https://velog.io/@min_tech/CUDA</link>
            <guid>https://velog.io/@min_tech/CUDA</guid>
            <pubDate>Wed, 15 Oct 2025 07:47:06 GMT</pubDate>
            <description><![CDATA[<p><strong>GPU architecture</strong>
<img src="https://velog.velcdn.com/images/min_tech/post/829d561d-050e-4a63-b300-11f966471f1b/image.png" alt="">
<br><br><br></p>
<p><strong>CUDA</strong>
GPU를 그래픽 처리기뿐만 아니라 범용 연산 장치로 쓸 수 있도록, NVIDIA가 만든 프로그래밍 환경
<br><br><br>
<strong>구성요소</strong>
언어 확장: C/C++/Fortran 확장
라이브러리: cuBLAS(행렬연산), cuDNN(딥러닝), cuFFT(푸리에 변환)
툴킷: NVCC컴파일러, 디버거, 프로파일러
드라이버: GPU 하드웨어 제어
<br><br><br></p>
<p><strong>딥러닝에서 CUDA가 필요한 이유</strong>
 딥러닝 모델은 매트릭스 곱셈, 합성곱(Convolution), 벡터 연산 같은 대규모 수학 계산을 반복합니다.
이 연산들은 병렬화가 가능해서, GPU의 수천 개 코어로 동시에 처리하면 CPU보다 훨씬 빠르게 실행됩니다.</p>
<p> 예: 1만 장의 이미지를 학습 → CPU는 순차적으로 처리, GPU는 CUDA를 이용해 동시에 병렬 연산</p>
<p><br><br><br>
<strong>CUDA 병렬 연산</strong>
<img src="https://velog.velcdn.com/images/min_tech/post/455cec8a-b27f-4b8b-8056-3cbdfd32f84c/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_tech/post/a3874d21-d517-4e2e-891f-74a7f9a205eb/image.png" alt="     ">
<img src="https://velog.velcdn.com/images/min_tech/post/df9d01a6-9a56-428a-b7be-ae72aef512dc/image.png" alt=""></p>
<p><br><br><br></p>
<p>*<em>행렬 곱셈(A x B = C) 커널 *</em></p>
<p>$A(M \times K)$ 행렬과 $B(K \times N)$ 행렬을 곱해 $C(M \times N)$ 행렬을 만드는 코드</p>
<p><strong><strong>global</strong></strong> void matMulKernel(float* A, float* B, float* C, int M, int K, int N) {</p>
<pre><code>// 1. 현재 스레드가 담당할 결과 행렬 C의 위치(row, col)를 정합니다.
int row = blockIdx.y * blockDim.y + threadIdx.y; // C의 행 번호 (0 ~ M-1)
int col = blockIdx.x * blockDim.x + threadIdx.x; // C의 열 번호 (0 ~ N-1)

// 2. 결과 행렬 C의 범위 안인지 확인합니다.
if (row &lt; M &amp;&amp; col &lt; N) {
    float sum = 0.0f;

    // 3. 루프를 돌며 A의 한 행과 B의 한 열을 내적(Dot Product)합니다.
    for (int k = 0; k &lt; K; k++) {

        // ★ 여기서 각 행렬의 offset(번지수)을 계산합니다! ★

        // A의 주소: row번째 행의 k번째 열
        int offset_A = row * K + k;

        // B의 주소: k번째 행의 col번째 열
        int offset_B = k * N + col;

        // VRAM에서 데이터를 가져와 곱한 뒤 누적합니다.
        sum += A[offset_A] * B[offset_B];
    }

    // 4. 최종적으로 계산된 한 점(sum)을 C의 자기 자리에 저장합니다.
    int offset_C = row * N + col;
    C[offset_C] = sum;
}</code></pre><p>}</p>
<p><strong>GPU에서 SM(Streaming Multiprocessor)의 개수</strong>
<img src="https://velog.velcdn.com/images/min_tech/post/ff4e48bf-2df5-4161-9552-b4cb6050bace/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>